clang-tools 19.0.0git
MultiwayPathsCoveredCheck.cpp
Go to the documentation of this file.
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
10#include "clang/AST/ASTContext.h"
11
12#include <limits>
13
14using namespace clang::ast_matchers;
15
16namespace clang::tidy::hicpp {
17
20 Options.store(Opts, "WarnOnMissingElse", WarnOnMissingElse);
21}
22
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
50static 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.
68static 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.
79static 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
90void 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
126void 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
137void 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(*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}
175} // namespace clang::tidy::hicpp
void store(ClangTidyOptions::OptionMap &Options, StringRef LocalName, StringRef Value) const
Stores an option with the check-local name LocalName with string value Value to Options.
DiagnosticBuilder diag(SourceLocation Loc, StringRef Description, DiagnosticIDs::Level Level=DiagnosticIDs::Warning)
Add a diagnostic with the check's name.
void registerMatchers(ast_matchers::MatchFinder *Finder) override
Override this to register AST matchers with Finder.
void storeOptions(ClangTidyOptions::OptionMap &Opts) override
Should store all options supported by this check with their current values or default values for opti...
void check(const ast_matchers::MatchFinder::MatchResult &Result) override
ClangTidyChecks that register ASTMatchers should do the actual work in here.
static std::size_t getNumberOfPossibleValues(QualType T, const ASTContext &Context)
Get the number of possible values that can be switched on for the type T.
static std::pair< std::size_t, bool > countCaseLabels(const SwitchStmt *Switch)
static std::size_t twoPow(std::size_t Bits)
This function calculate 2 ** Bits and returns numeric_limits<std::size_t>::max() if an overflow occur...
llvm::StringMap< ClangTidyValue > OptionMap