brintos

brintos / llvm-project-archived public Read only

0
0
Text · 7.0 KiB · e9a5819 Raw
176 lines · cpp
1//===----------------------------------------------------------------------===//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-exception6//7//===----------------------------------------------------------------------===//8 9#include "MultiwayPathsCoveredCheck.h"10#include "clang/AST/ASTContext.h"11 12#include <limits>13 14using namespace clang::ast_matchers;15 16namespace clang::tidy::hicpp {17 18void MultiwayPathsCoveredCheck::storeOptions(19    ClangTidyOptions::OptionMap &Opts) {20  Options.store(Opts, "WarnOnMissingElse", WarnOnMissingElse);21}22 23void MultiwayPathsCoveredCheck::registerMatchers(MatchFinder *Finder) {24  Finder->addMatcher(25      switchStmt(26          hasCondition(expr(27              // Match on switch statements that have either a bit-field or28              // an integer condition. The ordering in 'anyOf()' is29              // 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 it35              // 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 returns67/// 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>::digits70             ? 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 determined77///         - numeric_limits<std::size_t>::max() when overflow appeared due to78///           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 well82  // 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 define103  // 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 nor115  // a default label.116  // FIXME: Evaluate, if emitting a fix-it to simplify that statement is117  // 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 == 1133             ? "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 and141  // bound but ignored. This is necessary to build the excluding logic for142  // 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 present147  // and duplicating case labels is not allowed this number represents148  // 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 the151  // 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  const 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::hicpp176