| 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(Options&: Opts, LocalName: "WarnOnMissingElse" , Value: WarnOnMissingElse); |
| 21 | } |
| 22 | |
| 23 | void MultiwayPathsCoveredCheck::registerMatchers(MatchFinder *Finder) { |
| 24 | Finder->addMatcher( |
| 25 | NodeMatch: switchStmt( |
| 26 | hasCondition(InnerMatcher: 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(InnerMatcher: memberExpr(hasDeclaration( |
| 31 | InnerMatcher: fieldDecl(isBitField()).bind(ID: "bitfield" )))), |
| 32 | ignoringImpCasts(InnerMatcher: declRefExpr().bind(ID: "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 | InnerMatcher: declRefExpr(hasType(InnerMatcher: hasCanonicalType(InnerMatcher: enumType()))) |
| 38 | .bind(ID: "enum-condition" )))))) |
| 39 | .bind(ID: "switch" ), |
| 40 | Action: this); |
| 41 | |
| 42 | // This option is noisy, therefore matching is configurable. |
| 43 | if (WarnOnMissingElse) { |
| 44 | Finder->addMatcher(NodeMatch: ifStmt(hasParent(ifStmt()), unless(hasElse(InnerMatcher: anything()))) |
| 45 | .bind(ID: "else-if" ), |
| 46 | Action: 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>(Val: CurrentCase)) |
| 58 | HasDefault = true; |
| 59 | |
| 60 | CurrentCase = CurrentCase->getNextSwitchCase(); |
| 61 | } |
| 62 | |
| 63 | return std::make_pair(x&: CaseCount, y&: 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(Ctx: Context)) |
| 86 | return twoPow(Bits: 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>(ID: "else-if" )) { |
| 93 | diag(Loc: ElseIfWithoutElse->getBeginLoc(), |
| 94 | Description: "potentially uncovered codepath; add an ending else statement" ); |
| 95 | return; |
| 96 | } |
| 97 | const auto *Switch = Result.Nodes.getNodeAs<SwitchStmt>(ID: "switch" ); |
| 98 | std::size_t SwitchCaseCount = 0; |
| 99 | bool SwitchHasDefault = false; |
| 100 | std::tie(args&: SwitchCaseCount, args&: 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, CaseCount: 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, CaseCount: 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(Loc: Switch->getBeginLoc(), |
| 120 | Description: "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(Loc: Switch->getBeginLoc(), |
| 132 | Description: 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>(ID: "non-enum-condition" )) { |
| 158 | return getNumberOfPossibleValues(GeneralCondition->getType(), |
| 159 | *Result.Context); |
| 160 | } |
| 161 | if (const auto *BitfieldDecl = |
| 162 | Result.Nodes.getNodeAs<FieldDecl>(ID: "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(Loc: Switch->getBeginLoc(), |
| 172 | Description: 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 | |