annotate clang-tools-extra/clang-tidy/hicpp/MultiwayPathsCoveredCheck.cpp @ 266:00f31e85ec16 default tip

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