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(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( 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 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 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(Context)) 86 return twoPow(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>("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 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 " 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()); 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 176