xref: /llvm-project/clang-tools-extra/clang-tidy/hicpp/MultiwayPathsCoveredCheck.cpp (revision cfe26358e3051755961fb1f3b46328dc2c326895)
1 //===--- MultiwayPathsCoveredCheck.cpp - clang-tidy------------------------===//
2 //
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6 //
7 //===----------------------------------------------------------------------===//
8 
9 #include "MultiwayPathsCoveredCheck.h"
10 #include "clang/AST/ASTContext.h"
11 
12 #include <limits>
13 
14 using namespace clang::ast_matchers;
15 
16 namespace clang::tidy::hicpp {
17 
18 void MultiwayPathsCoveredCheck::storeOptions(
19     ClangTidyOptions::OptionMap &Opts) {
20   Options.store(Opts, "WarnOnMissingElse", WarnOnMissingElse);
21 }
22 
23 void MultiwayPathsCoveredCheck::registerMatchers(MatchFinder *Finder) {
24   Finder->addMatcher(
25       switchStmt(
26           hasCondition(expr(
27               // Match on switch statements that have either a bit-field or
28               // an integer condition. The ordering in 'anyOf()' is
29               // important because the last condition is the most general.
30               anyOf(ignoringImpCasts(memberExpr(hasDeclaration(
31                         fieldDecl(isBitField()).bind("bitfield")))),
32                     ignoringImpCasts(declRefExpr().bind("non-enum-condition"))),
33               // 'unless()' must be the last match here and must be bound,
34               // otherwise the matcher does not work correctly, because it
35               // will not explicitly ignore enum conditions.
36               unless(ignoringImpCasts(
37                   declRefExpr(hasType(hasCanonicalType(enumType())))
38                       .bind("enum-condition"))))))
39           .bind("switch"),
40       this);
41 
42   // This option is noisy, therefore matching is configurable.
43   if (WarnOnMissingElse) {
44     Finder->addMatcher(ifStmt(hasParent(ifStmt()), unless(hasElse(anything())))
45                            .bind("else-if"),
46                        this);
47   }
48 }
49 
50 static std::pair<std::size_t, bool> countCaseLabels(const SwitchStmt *Switch) {
51   std::size_t CaseCount = 0;
52   bool HasDefault = false;
53 
54   const SwitchCase *CurrentCase = Switch->getSwitchCaseList();
55   while (CurrentCase) {
56     ++CaseCount;
57     if (isa<DefaultStmt>(CurrentCase))
58       HasDefault = true;
59 
60     CurrentCase = CurrentCase->getNextSwitchCase();
61   }
62 
63   return std::make_pair(CaseCount, HasDefault);
64 }
65 
66 /// This function calculate 2 ** Bits and returns
67 /// numeric_limits<std::size_t>::max() if an overflow occurred.
68 static std::size_t twoPow(std::size_t Bits) {
69   return Bits >= std::numeric_limits<std::size_t>::digits
70              ? std::numeric_limits<std::size_t>::max()
71              : static_cast<size_t>(1) << Bits;
72 }
73 
74 /// Get the number of possible values that can be switched on for the type T.
75 ///
76 /// \return - 0 if bitcount could not be determined
77 ///         - numeric_limits<std::size_t>::max() when overflow appeared due to
78 ///           more than 64 bits type size.
79 static std::size_t getNumberOfPossibleValues(QualType T,
80                                              const ASTContext &Context) {
81   // `isBooleanType` must come first because `bool` is an integral type as well
82   // and would not return 2 as result.
83   if (T->isBooleanType())
84     return 2;
85   if (T->isIntegralType(Context))
86     return twoPow(Context.getTypeSize(T));
87   return 1;
88 }
89 
90 void MultiwayPathsCoveredCheck::check(const MatchFinder::MatchResult &Result) {
91   if (const auto *ElseIfWithoutElse =
92           Result.Nodes.getNodeAs<IfStmt>("else-if")) {
93     diag(ElseIfWithoutElse->getBeginLoc(),
94          "potentially uncovered codepath; add an ending else statement");
95     return;
96   }
97   const auto *Switch = Result.Nodes.getNodeAs<SwitchStmt>("switch");
98   std::size_t SwitchCaseCount = 0;
99   bool SwitchHasDefault = false;
100   std::tie(SwitchCaseCount, SwitchHasDefault) = countCaseLabels(Switch);
101 
102   // Checks the sanity of 'switch' statements that actually do define
103   // a default branch but might be degenerated by having no or only one case.
104   if (SwitchHasDefault) {
105     handleSwitchWithDefault(Switch, SwitchCaseCount);
106     return;
107   }
108   // Checks all 'switch' statements that do not define a default label.
109   // Here the heavy lifting happens.
110   if (!SwitchHasDefault && SwitchCaseCount > 0) {
111     handleSwitchWithoutDefault(Switch, SwitchCaseCount, Result);
112     return;
113   }
114   // Warns for degenerated 'switch' statements that neither define a case nor
115   // a default label.
116   // FIXME: Evaluate, if emitting a fix-it to simplify that statement is
117   // reasonable.
118   if (!SwitchHasDefault && SwitchCaseCount == 0) {
119     diag(Switch->getBeginLoc(),
120          "switch statement without labels has no effect");
121     return;
122   }
123   llvm_unreachable("matched a case, that was not explicitly handled");
124 }
125 
126 void MultiwayPathsCoveredCheck::handleSwitchWithDefault(
127     const SwitchStmt *Switch, std::size_t CaseCount) {
128   assert(CaseCount > 0 && "Switch statement with supposedly one default "
129                           "branch did not contain any case labels");
130   if (CaseCount == 1 || CaseCount == 2)
131     diag(Switch->getBeginLoc(),
132          CaseCount == 1
133              ? "degenerated switch with default label only"
134              : "switch could be better written as an if/else statement");
135 }
136 
137 void MultiwayPathsCoveredCheck::handleSwitchWithoutDefault(
138     const SwitchStmt *Switch, std::size_t CaseCount,
139     const MatchFinder::MatchResult &Result) {
140   // The matcher only works because some nodes are explicitly matched and
141   // bound but ignored. This is necessary to build the excluding logic for
142   // enums and 'switch' statements without a 'default' branch.
143   assert(!Result.Nodes.getNodeAs<DeclRefExpr>("enum-condition") &&
144          "switch over enum is handled by warnings already, explicitly ignoring "
145          "them");
146   // Determine the number of case labels. Because 'default' is not present
147   // and duplicating case labels is not allowed this number represents
148   // the number of codepaths. It can be directly compared to 'MaxPathsPossible'
149   // to see if some cases are missing.
150   // CaseCount == 0 is caught in DegenerateSwitch. Necessary because the
151   // matcher used for here does not match on degenerate 'switch'.
152   assert(CaseCount > 0 && "Switch statement without any case found. This case "
153                           "should be excluded by the matcher and is handled "
154                           "separately.");
155   std::size_t MaxPathsPossible = [&]() {
156     if (const auto *GeneralCondition =
157             Result.Nodes.getNodeAs<DeclRefExpr>("non-enum-condition")) {
158       return getNumberOfPossibleValues(GeneralCondition->getType(),
159                                        *Result.Context);
160     }
161     if (const auto *BitfieldDecl =
162             Result.Nodes.getNodeAs<FieldDecl>("bitfield")) {
163       return twoPow(BitfieldDecl->getBitWidthValue());
164     }
165 
166     return static_cast<std::size_t>(0);
167   }();
168 
169   // FIXME: Transform the 'switch' into an 'if' for CaseCount == 1.
170   if (CaseCount < MaxPathsPossible)
171     diag(Switch->getBeginLoc(),
172          CaseCount == 1 ? "switch with only one case; use an if statement"
173                         : "potential uncovered code path; add a default label");
174 }
175 } // namespace clang::tidy::hicpp
176