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