brintos

brintos / llvm-project-archived public Read only

0
0
Text · 10.2 KiB · 54c4692 Raw
263 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 "ProBoundsAvoidUncheckedContainerAccessCheck.h"10#include "../utils/Matchers.h"11#include "../utils/OptionsUtils.h"12#include "clang/ASTMatchers/ASTMatchFinder.h"13#include "llvm/ADT/StringRef.h"14 15using namespace clang::ast_matchers;16 17namespace clang::tidy::cppcoreguidelines {18 19static constexpr llvm::StringRef DefaultExclusionStr =20    "::std::map;::std::unordered_map;::std::flat_map";21 22ProBoundsAvoidUncheckedContainerAccessCheck::23    ProBoundsAvoidUncheckedContainerAccessCheck(StringRef Name,24                                                ClangTidyContext *Context)25    : ClangTidyCheck(Name, Context),26      ExcludedClasses(utils::options::parseStringList(27          Options.get("ExcludeClasses", DefaultExclusionStr))),28      FixMode(Options.get("FixMode", None)),29      FixFunction(Options.get("FixFunction", "gsl::at")),30      FixFunctionEmptyArgs(Options.get("FixFunctionEmptyArgs", FixFunction)) {}31 32void ProBoundsAvoidUncheckedContainerAccessCheck::storeOptions(33    ClangTidyOptions::OptionMap &Opts) {34  Options.store(Opts, "ExcludeClasses",35                utils::options::serializeStringList(ExcludedClasses));36  Options.store(Opts, "FixMode", FixMode);37  Options.store(Opts, "FixFunction", FixFunction);38  Options.store(Opts, "FixFunctionEmptyArgs", FixFunctionEmptyArgs);39}40 41// TODO: if at() is defined in another class in the class hierarchy of the class42// that defines the operator[] we matched on, findAlternative() will not detect43// it.44static const CXXMethodDecl *45findAlternativeAt(const CXXMethodDecl *MatchedOperator) {46  const CXXRecordDecl *Parent = MatchedOperator->getParent();47  const QualType SubscriptThisObjType =48      MatchedOperator->getFunctionObjectParameterReferenceType();49 50  for (const CXXMethodDecl *Method : Parent->methods()) {51    // Require 'Method' to be as accessible as 'MatchedOperator' or more52    if (MatchedOperator->getAccess() < Method->getAccess())53      continue;54 55    if (MatchedOperator->isConst() != Method->isConst())56      continue;57 58    const QualType AtThisObjType =59        Method->getFunctionObjectParameterReferenceType();60    if (SubscriptThisObjType != AtThisObjType)61      continue;62 63    if (!Method->getNameInfo().getName().isIdentifier() ||64        Method->getName() != "at")65      continue;66 67    const bool SameReturnType =68        Method->getReturnType() == MatchedOperator->getReturnType();69    if (!SameReturnType)70      continue;71 72    const bool SameNumberOfArguments =73        Method->getNumParams() == MatchedOperator->getNumParams();74    if (!SameNumberOfArguments)75      continue;76 77    for (unsigned ArgInd = 0; ArgInd < Method->getNumParams(); ArgInd++) {78      const bool SameArgType =79          Method->parameters()[ArgInd]->getOriginalType() ==80          MatchedOperator->parameters()[ArgInd]->getOriginalType();81      if (!SameArgType)82        continue;83    }84 85    return Method;86  }87  return nullptr;88}89 90void ProBoundsAvoidUncheckedContainerAccessCheck::registerMatchers(91    MatchFinder *Finder) {92  Finder->addMatcher(93      mapAnyOf(cxxOperatorCallExpr, cxxMemberCallExpr)94          .with(callee(95              cxxMethodDecl(96                  hasOverloadedOperatorName("[]"),97                  anyOf(parameterCountIs(0), parameterCountIs(1)),98                  unless(matchers::matchesAnyListedName(ExcludedClasses)))99                  .bind("operator")))100          .bind("caller"),101      this);102}103 104void ProBoundsAvoidUncheckedContainerAccessCheck::check(105    const MatchFinder::MatchResult &Result) {106  const auto *MatchedExpr = Result.Nodes.getNodeAs<CallExpr>("caller");107 108  if (FixMode == None) {109    diag(MatchedExpr->getCallee()->getBeginLoc(),110         "possibly unsafe 'operator[]', consider bounds-safe alternatives")111        << MatchedExpr->getCallee()->getSourceRange();112    return;113  }114 115  if (const auto *OCE = dyn_cast<CXXOperatorCallExpr>(MatchedExpr)) {116    // Case: a[i]117    const auto LeftBracket = SourceRange(OCE->getCallee()->getBeginLoc(),118                                         OCE->getCallee()->getBeginLoc());119    const auto RightBracket =120        SourceRange(OCE->getOperatorLoc(), OCE->getOperatorLoc());121 122    if (FixMode == At) {123      // Case: a[i] => a.at(i)124      const auto *MatchedOperator =125          Result.Nodes.getNodeAs<CXXMethodDecl>("operator");126      const CXXMethodDecl *Alternative = findAlternativeAt(MatchedOperator);127 128      if (!Alternative) {129        diag(MatchedExpr->getCallee()->getBeginLoc(),130             "possibly unsafe 'operator[]', consider "131             "bounds-safe alternatives")132            << MatchedExpr->getCallee()->getSourceRange();133        return;134      }135 136      diag(MatchedExpr->getCallee()->getBeginLoc(),137           "possibly unsafe 'operator[]', consider "138           "bounds-safe alternative 'at()'")139          << MatchedExpr->getCallee()->getSourceRange()140          << FixItHint::CreateReplacement(LeftBracket, ".at(")141          << FixItHint::CreateReplacement(RightBracket, ")");142 143      diag(Alternative->getBeginLoc(), "viable 'at()' is defined here",144           DiagnosticIDs::Note)145          << Alternative->getNameInfo().getSourceRange();146 147    } else if (FixMode == Function) {148      // Case: a[i] => f(a, i)149      //150      // Since C++23, the subscript operator may also be called without an151      // argument, which makes the following distinction necessary152      const bool EmptySubscript =153          MatchedExpr->getDirectCallee()->getNumParams() == 0;154 155      if (EmptySubscript) {156        auto D = diag(MatchedExpr->getCallee()->getBeginLoc(),157                      "possibly unsafe 'operator[]'%select{, use safe "158                      "function '%1() instead|}0")159                 << FixFunctionEmptyArgs.empty() << FixFunctionEmptyArgs.str()160                 << MatchedExpr->getCallee()->getSourceRange();161        if (!FixFunctionEmptyArgs.empty()) {162          D << FixItHint::CreateInsertion(OCE->getArg(0)->getBeginLoc(),163                                          FixFunctionEmptyArgs.str() + "(")164            << FixItHint::CreateRemoval(LeftBracket)165            << FixItHint::CreateReplacement(RightBracket, ")");166        }167      } else {168        diag(MatchedExpr->getCallee()->getBeginLoc(),169             "possibly unsafe 'operator[]', use safe function '%0()' instead")170            << FixFunction.str() << MatchedExpr->getCallee()->getSourceRange()171            << FixItHint::CreateInsertion(OCE->getArg(0)->getBeginLoc(),172                                          FixFunction.str() + "(")173            << FixItHint::CreateReplacement(LeftBracket, ", ")174            << FixItHint::CreateReplacement(RightBracket, ")");175      }176    }177  } else if (const auto *MCE = dyn_cast<CXXMemberCallExpr>(MatchedExpr)) {178    // Case: a.operator[](i) or a->operator[](i)179    const auto *Callee = dyn_cast<MemberExpr>(MCE->getCallee());180 181    if (FixMode == At) {182      // Cases: a.operator[](i) => a.at(i) and a->operator[](i) => a->at(i)183 184      const auto *MatchedOperator =185          Result.Nodes.getNodeAs<CXXMethodDecl>("operator");186 187      const CXXMethodDecl *Alternative = findAlternativeAt(MatchedOperator);188      if (!Alternative) {189        diag(Callee->getBeginLoc(), "possibly unsafe 'operator[]', consider "190                                    "bounds-safe alternative 'at()'")191            << Callee->getSourceRange();192        return;193      }194      diag(MatchedExpr->getCallee()->getBeginLoc(),195           "possibly unsafe 'operator[]', consider "196           "bounds-safe alternative 'at()'")197          << FixItHint::CreateReplacement(198                 SourceRange(Callee->getMemberLoc(), Callee->getEndLoc()),199                 "at");200 201      diag(Alternative->getBeginLoc(), "viable 'at()' defined here",202           DiagnosticIDs::Note)203          << Alternative->getNameInfo().getSourceRange();204 205    } else if (FixMode == Function) {206      // Cases: a.operator[](i) => f(a, i) and a->operator[](i) => f(*a, i)207      const auto *Callee = dyn_cast<MemberExpr>(MCE->getCallee());208 209      const bool EmptySubscript =210          MCE->getMethodDecl()->getNumNonObjectParams() == 0;211 212      std::string BeginInsertion =213          (EmptySubscript ? FixFunctionEmptyArgs.str() : FixFunction.str()) +214          "(";215 216      if (Callee->isArrow())217        BeginInsertion += "*";218 219      // Since C++23, the subscript operator may also be called without an220      // argument, which makes the following distinction necessary221      if (EmptySubscript) {222        auto D = diag(MatchedExpr->getCallee()->getBeginLoc(),223                      "possibly unsafe 'operator[]'%select{, use safe "224                      "function '%1()' instead|}0")225                 << FixFunctionEmptyArgs.empty() << FixFunctionEmptyArgs.str()226                 << Callee->getSourceRange();227 228        if (!FixFunctionEmptyArgs.empty()) {229          D << FixItHint::CreateInsertion(MatchedExpr->getBeginLoc(),230                                          BeginInsertion)231            << FixItHint::CreateRemoval(232                   SourceRange(Callee->getOperatorLoc(),233                               MCE->getRParenLoc().getLocWithOffset(-1)));234        }235      } else {236        diag(Callee->getBeginLoc(),237             "possibly unsafe 'operator[]', use safe function '%0()' instead")238            << FixFunction.str() << Callee->getSourceRange()239            << FixItHint::CreateInsertion(MatchedExpr->getBeginLoc(),240                                          BeginInsertion)241            << FixItHint::CreateReplacement(242                   SourceRange(243                       Callee->getOperatorLoc(),244                       MCE->getArg(0)->getBeginLoc().getLocWithOffset(-1)),245                   ", ");246      }247    }248  }249}250 251} // namespace clang::tidy::cppcoreguidelines252 253namespace clang::tidy {254using P = cppcoreguidelines::ProBoundsAvoidUncheckedContainerAccessCheck;255 256llvm::ArrayRef<std::pair<P::FixModes, StringRef>>257OptionEnumMapping<P::FixModes>::getEnumMapping() {258  static constexpr std::pair<P::FixModes, StringRef> Mapping[] = {259      {P::None, "none"}, {P::At, "at"}, {P::Function, "function"}};260  return {Mapping};261}262} // namespace clang::tidy263