Paul =?utf-8?q?Heidekrüger?= <paul.heidekrue...@tum.de>, Paul =?utf-8?q?Heidekrüger?= <paul.heidekrue...@tum.de>, Paul =?utf-8?q?Heidekrüger?= <paul.heidekrue...@tum.de>, Paul =?utf-8?q?Heidekrüger?= <paul.heidekrue...@tum.de>, Paul =?utf-8?q?Heidekrüger?= <paul.heidekrue...@tum.de>, Paul =?utf-8?q?Heidekrüger?= <paul.heidekrue...@tum.de>, Paul =?utf-8?q?Heidekrüger?= <paul.heidekrue...@tum.de>, Paul =?utf-8?q?Heidekrüger?= <paul.heidekrue...@tum.de>, Paul =?utf-8?q?Heidekrüger?= <paul.heidekrue...@tum.de>, Paul =?utf-8?q?Heidekrüger?= <paul.heidekrue...@tum.de>, Paul =?utf-8?q?Heidekrüger?= <paul.heidekrue...@tum.de>, Paul =?utf-8?q?Heidekrüger?= <paul.heidekrue...@tum.de>, Paul =?utf-8?q?Heidekrüger?= <paul.heidekrue...@tum.de>, Paul =?utf-8?q?Heidekrüger?= <paul.heidekrue...@tum.de>, Paul =?utf-8?q?Heidekrüger?= <paul.heidekrue...@tum.de>, Paul =?utf-8?q?Heidekrüger?= <paul.heidekrue...@tum.de>, Paul =?utf-8?q?Heidekrüger?= <paul.heidekrue...@tum.de>, Paul =?utf-8?q?Heidekrüger?= <paul.heidekrue...@tum.de>, Paul =?utf-8?q?Heidekrüger?= <paul.heidekrue...@tum.de>, Paul =?utf-8?q?Heidekrüger?= <paul.heidekrue...@tum.de>, Paul =?utf-8?q?Heidekrüger?= <paul.heidekrue...@tum.de>, Paul =?utf-8?q?Heidekrüger?= <paul.heidekrue...@tum.de>, Paul =?utf-8?q?Heidekrüger?= <paul.heidekrue...@tum.de>, Paul =?utf-8?q?Heidekrüger?= <paul.heidekrue...@tum.de>, Paul =?utf-8?q?Heidekrüger?= <paul.heidekrue...@tum.de>, Paul =?utf-8?q?Heidekrüger?= <paul.heidekrue...@tum.de>, Paul =?utf-8?q?Heidekrüger?= <paul.heidekrue...@tum.de>, Paul =?utf-8?q?Heidekrüger?= <paul.heidekrue...@tum.de>, Paul =?utf-8?q?Heidekrüger?= <paul.heidekrue...@tum.de>, Paul =?utf-8?q?Heidekrüger?= <paul.heidekrue...@tum.de>, Paul =?utf-8?q?Heidekrüger?= <paul.heidekrue...@tum.de>, Paul =?utf-8?q?Heidekrüger?= <paul.heidekrue...@tum.de>, Paul =?utf-8?q?Heidekrüger?= <paul.heidekrue...@tum.de>, Paul =?utf-8?q?Heidekrüger?= <paul.heidekrue...@tum.de>, Paul =?utf-8?q?Heidekrüger?= <paul.heidekrue...@tum.de>, Paul =?utf-8?q?Heidekrüger?= <paul.heidekrue...@tum.de>, Paul =?utf-8?q?Heidekrüger?= <paul.heidekrue...@tum.de>, Paul =?utf-8?q?Heidekrüger?= <paul.heidekrue...@tum.de>, Paul =?utf-8?q?Heidekrüger?= <paul.heidekrue...@tum.de>, Paul =?utf-8?q?Heidekrüger?= <paul.heidekrue...@tum.de>, Paul =?utf-8?q?Heidekrüger?= <paul.heidekrue...@tum.de>, Paul =?utf-8?q?Heidekrüger?= <paul.heidekrue...@tum.de>, Paul =?utf-8?q?Heidekrüger?= <paul.heidekrue...@tum.de>, Paul =?utf-8?q?Heidekrüger?= <paul.heidekrue...@tum.de>, Paul =?utf-8?q?Heidekrüger?= <paul.heidekrue...@tum.de>, Paul =?utf-8?q?Heidekrüger?= <paul.heidekrue...@tum.de>, Paul =?utf-8?q?Heidekrüger?= <paul.heidekrue...@tum.de>, Paul =?utf-8?q?Heidekrüger?= <paul.heidekrue...@tum.de>, Paul =?utf-8?q?Heidekrüger?= <paul.heidekrue...@tum.de>, Paul =?utf-8?q?Heidekrüger?= <paul.heidekrue...@tum.de>, Paul =?utf-8?q?Heidekrüger?= <paul.heidekrue...@tum.de>, Paul =?utf-8?q?Heidekrüger?= <paul.heidekrue...@tum.de>, Paul =?utf-8?q?Heidekrüger?= <paul.heidekrue...@tum.de>, Paul =?utf-8?q?Heidekrüger?= <paul.heidekrue...@tum.de>, Paul =?utf-8?q?Heidekrüger?= <paul.heidekrue...@tum.de>, Paul =?utf-8?q?Heidekrüger?= <paul.heidekrue...@tum.de>, Paul =?utf-8?q?Heidekrüger?= <paul.heidekrue...@tum.de>, Paul =?utf-8?q?Heidekrüger?= <paul.heidekrue...@tum.de>, Paul =?utf-8?q?Heidekrüger?= <paul.heidekrue...@tum.de>, Paul =?utf-8?q?Heidekrüger?= <p...@paulhdk.com>, Paul =?utf-8?q?Heidekrüger?= <p...@paulhdk.com>, Paul =?utf-8?q?Heidekrüger?= <p...@paulhdk.com>, Paul =?utf-8?q?Heidekrüger?= <p...@paulhdk.com>, Paul =?utf-8?q?Heidekrüger?= <p...@paulhdk.com>, Paul =?utf-8?q?Heidekrüger?= <paul.heidekrue...@tum.de>, Paul =?utf-8?q?Heidekrüger?= <paul.heidekrue...@tum.de>, Paul =?utf-8?q?Heidekrüger?= <paul.heidekrue...@tum.de>, Paul =?utf-8?q?Heidekrüger?= <paul.heidekrue...@tum.de>, Paul =?utf-8?q?Heidekrüger?= <p...@paulhdk.com>, Paul =?utf-8?q?Heidekrüger?= <p...@paulhdk.com>, Paul =?utf-8?q?Heidekrüger?= <p...@paulhdk.com>, Paul =?utf-8?q?Heidekrüger?= <p...@paulhdk.com>, Paul =?utf-8?q?Heidekrüger?= <p...@paulhdk.com> Message-ID: In-Reply-To: <llvm.org/llvm/llvm-project/pull/95...@github.com>
================ @@ -0,0 +1,231 @@ +//===--- ProBoundsAvoidUncheckedContainerAccesses.cpp - clang-tidy --------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "ProBoundsAvoidUncheckedContainerAccesses.h" +#include "../utils/Matchers.h" +#include "../utils/OptionsUtils.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "llvm/ADT/StringRef.h" + +using namespace clang::ast_matchers; + +namespace clang::tidy::cppcoreguidelines { + +static constexpr llvm::StringRef DefaultExclusionStr = + "::std::map;::std::unordered_map;::std::flat_map"; + +ProBoundsAvoidUncheckedContainerAccesses:: + ProBoundsAvoidUncheckedContainerAccesses(StringRef Name, + ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) { + ExcludedClasses = utils::options::parseStringList( + Options.get("ExcludeClasses", DefaultExclusionStr)); + FixMode = Options.get("FixMode", None); + FixFunction = Options.get("FixFunction", "gsl::at"); +} + +void ProBoundsAvoidUncheckedContainerAccesses::storeOptions( + ClangTidyOptions::OptionMap &Opts) { + + Options.store(Opts, "FixFunction", FixFunction); + Options.store(Opts, "FixMode", FixMode); + Options.store(Opts, "ExcludeClasses", + utils::options::serializeStringList(ExcludedClasses)); +} + +// TODO: if at() is defined in another class in the class hierarchy of the class +// that defines the operator[] we matched on, findAlternative() will not detect +// it. +static const CXXMethodDecl * +findAlternativeAt(const CXXMethodDecl *MatchedOperator) { + const CXXRecordDecl *Parent = MatchedOperator->getParent(); + const QualType SubscriptThisObjType = + MatchedOperator->getFunctionObjectParameterReferenceType(); + + for (const CXXMethodDecl *Method : Parent->methods()) { + // Require 'Method' to be as accessible as 'MatchedOperator' or more + if (MatchedOperator->getAccess() < Method->getAccess()) + continue; + + if (MatchedOperator->isConst() != Method->isConst()) + continue; + + const QualType AtThisObjType = + Method->getFunctionObjectParameterReferenceType(); + if (SubscriptThisObjType != AtThisObjType) + continue; + + if (!Method->getNameInfo().getName().isIdentifier() || + Method->getName() != "at") + continue; + + const bool SameReturnType = + Method->getReturnType() == MatchedOperator->getReturnType(); + if (!SameReturnType) + continue; + + const bool SameNumberOfArguments = + Method->getNumParams() == MatchedOperator->getNumParams(); + if (!SameNumberOfArguments) + continue; + + for (unsigned ArgInd = 0; ArgInd < Method->getNumParams(); ArgInd++) { + const bool SameArgType = + Method->parameters()[ArgInd]->getOriginalType() == + MatchedOperator->parameters()[ArgInd]->getOriginalType(); + if (!SameArgType) + continue; + } + + return Method; + } + return nullptr; +} + +void ProBoundsAvoidUncheckedContainerAccesses::registerMatchers( + MatchFinder *Finder) { + Finder->addMatcher( + mapAnyOf(cxxOperatorCallExpr, cxxMemberCallExpr) + .with(callee( + cxxMethodDecl( + hasOverloadedOperatorName("[]"), + anyOf(parameterCountIs(0), parameterCountIs(1)), + unless(matchers::matchesAnyListedName(ExcludedClasses))) + .bind("operator"))) + .bind("caller"), + this); +} + +void ProBoundsAvoidUncheckedContainerAccesses::check( + const MatchFinder::MatchResult &Result) { + + const auto *MatchedExpr = Result.Nodes.getNodeAs<CallExpr>("caller"); + + if (FixMode == None) { + diag(MatchedExpr->getCallee()->getBeginLoc(), + "possibly unsafe 'operator[]', consider bounds-safe alternatives") + << MatchedExpr->getCallee()->getSourceRange(); + return; + } + + if (const auto *OCE = dyn_cast<CXXOperatorCallExpr>(MatchedExpr)) { + // Case: a[i] + const auto LeftBracket = SourceRange(OCE->getCallee()->getBeginLoc(), + OCE->getCallee()->getBeginLoc()); + const auto RightBracket = + SourceRange(OCE->getOperatorLoc(), OCE->getOperatorLoc()); + + if (FixMode == At) { + // Case: a[i] => a.at(i) + const auto *MatchedOperator = + Result.Nodes.getNodeAs<CXXMethodDecl>("operator"); + const CXXMethodDecl *Alternative = findAlternativeAt(MatchedOperator); + + if (!Alternative) { + diag(MatchedExpr->getCallee()->getBeginLoc(), + "possibly unsafe 'operator[]', consider " + "bounds-safe alternatives") + << MatchedExpr->getCallee()->getSourceRange(); + return; + } + + diag(MatchedExpr->getCallee()->getBeginLoc(), + "possibly unsafe 'operator[]', consider " + "bounds-safe alternative 'at()'") + << MatchedExpr->getCallee()->getSourceRange() + << FixItHint::CreateReplacement(LeftBracket, ".at(") + << FixItHint::CreateReplacement(RightBracket, ")"); + + diag(Alternative->getBeginLoc(), "viable 'at()' is defined here", + DiagnosticIDs::Note) + << Alternative->getNameInfo().getSourceRange(); + + } else if (FixMode == Function) { + // Case: a[i] => f(a, i) + diag(MatchedExpr->getCallee()->getBeginLoc(), + "possibly unsafe 'operator[]', use safe function '" + + FixFunction.str() + "()' instead") + << MatchedExpr->getCallee()->getSourceRange() + << FixItHint::CreateInsertion(OCE->getArg(0)->getBeginLoc(), + FixFunction.str() + "(") + // Since C++23, the subscript operator may also be called without an + // argument, which makes the following distinction necessary ---------------- 5chmidti wrote: Could you add a small comment to the option documentation of `FixFunction`, explaining that the function should support `f(a)` for `a[]` accesses? Maybe this warrants an enablement flag: `EnableFixFunctionForNoArgSubscriptCall`? https://github.com/llvm/llvm-project/pull/95220 _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits