150
|
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
|
252
|
16 namespace clang::tidy::hicpp {
|
150
|
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(
|
236
|
37 declRefExpr(hasType(hasCanonicalType(enumType())))
|
|
38 .bind("enum-condition"))))))
|
150
|
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
|
173
|
67 /// numeric_limits<std::size_t>::max() if an overflow occurred.
|
150
|
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;
|
221
|
85 if (T->isIntegralType(Context))
|
150
|
86 return twoPow(Context.getTypeSize(T));
|
221
|
87 return 1;
|
150
|
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;
|
|
99 bool SwitchHasDefault;
|
|
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 "
|
173
|
154 "separately.");
|
150
|
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(*Result.Context));
|
|
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 }
|
252
|
175 } // namespace clang::tidy::hicpp
|