llvm-project/clang-tools-extra/clang-tidy/cppcoreguidelines/ProBoundsConstantArrayIndexCheck.cpp
Carlos Galvez eb3f20e8fa [clang-tidy] Remove gsl::at suggestion from cppcoreguidelines-pro-bounds-constant-array-index
Currently the fix hint is hardcoded to gsl::at(). This poses
a problem for people who, for a number of reasons, don't want
or cannot use the GSL library (introducing a new third-party
dependency into a project is not a minor task).

In these situations, the fix hint does more harm than good
as it creates confusion as to what the fix should be. People
can even misinterpret the fix "gsl::at" as e.g. "std::array::at",
which can lead to even more trouble (e.g. when having guidelines
that disallow exceptions).

Furthermore, this is not a requirement from the C++ Core Guidelines.
simply that array indexing needs to be safe. Each project should
be able to decide upon a strategy for safe indexing.

The fix-it is kept for people who want to use the GSL library.

Differential Revision: https://reviews.llvm.org/D117857
2022-01-23 15:52:42 +00:00

127 lines
4.7 KiB
C++

//===--- ProBoundsConstantArrayIndexCheck.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 "ProBoundsConstantArrayIndexCheck.h"
#include "clang/AST/ASTContext.h"
#include "clang/ASTMatchers/ASTMatchFinder.h"
#include "clang/Frontend/CompilerInstance.h"
#include "clang/Lex/Preprocessor.h"
using namespace clang::ast_matchers;
namespace clang {
namespace tidy {
namespace cppcoreguidelines {
ProBoundsConstantArrayIndexCheck::ProBoundsConstantArrayIndexCheck(
StringRef Name, ClangTidyContext *Context)
: ClangTidyCheck(Name, Context), GslHeader(Options.get("GslHeader", "")),
Inserter(Options.getLocalOrGlobal("IncludeStyle",
utils::IncludeSorter::IS_LLVM)) {}
void ProBoundsConstantArrayIndexCheck::storeOptions(
ClangTidyOptions::OptionMap &Opts) {
Options.store(Opts, "GslHeader", GslHeader);
Options.store(Opts, "IncludeStyle", Inserter.getStyle());
}
void ProBoundsConstantArrayIndexCheck::registerPPCallbacks(
const SourceManager &SM, Preprocessor *PP, Preprocessor *ModuleExpanderPP) {
Inserter.registerPreprocessor(PP);
}
void ProBoundsConstantArrayIndexCheck::registerMatchers(MatchFinder *Finder) {
// Note: if a struct contains an array member, the compiler-generated
// constructor has an arraySubscriptExpr.
Finder->addMatcher(arraySubscriptExpr(hasBase(ignoringImpCasts(hasType(
constantArrayType().bind("type")))),
hasIndex(expr().bind("index")),
unless(hasAncestor(decl(isImplicit()))))
.bind("expr"),
this);
Finder->addMatcher(
cxxOperatorCallExpr(
hasOverloadedOperatorName("[]"),
hasArgument(
0, hasType(cxxRecordDecl(hasName("::std::array")).bind("type"))),
hasArgument(1, expr().bind("index")))
.bind("expr"),
this);
}
void ProBoundsConstantArrayIndexCheck::check(
const MatchFinder::MatchResult &Result) {
const auto *Matched = Result.Nodes.getNodeAs<Expr>("expr");
const auto *IndexExpr = Result.Nodes.getNodeAs<Expr>("index");
if (IndexExpr->isValueDependent())
return; // We check in the specialization.
Optional<llvm::APSInt> Index =
IndexExpr->getIntegerConstantExpr(*Result.Context);
if (!Index) {
SourceRange BaseRange;
if (const auto *ArraySubscriptE = dyn_cast<ArraySubscriptExpr>(Matched))
BaseRange = ArraySubscriptE->getBase()->getSourceRange();
else
BaseRange =
dyn_cast<CXXOperatorCallExpr>(Matched)->getArg(0)->getSourceRange();
SourceRange IndexRange = IndexExpr->getSourceRange();
auto Diag = diag(Matched->getExprLoc(),
"do not use array subscript when the index is "
"not an integer constant expression");
if (!GslHeader.empty()) {
Diag << FixItHint::CreateInsertion(BaseRange.getBegin(), "gsl::at(")
<< FixItHint::CreateReplacement(
SourceRange(BaseRange.getEnd().getLocWithOffset(1),
IndexRange.getBegin().getLocWithOffset(-1)),
", ")
<< FixItHint::CreateReplacement(Matched->getEndLoc(), ")")
<< Inserter.createMainFileIncludeInsertion(GslHeader);
}
return;
}
const auto *StdArrayDecl =
Result.Nodes.getNodeAs<ClassTemplateSpecializationDecl>("type");
// For static arrays, this is handled in clang-diagnostic-array-bounds.
if (!StdArrayDecl)
return;
if (Index->isSigned() && Index->isNegative()) {
diag(Matched->getExprLoc(), "std::array<> index %0 is negative")
<< toString(*Index, 10);
return;
}
const TemplateArgumentList &TemplateArgs = StdArrayDecl->getTemplateArgs();
if (TemplateArgs.size() < 2)
return;
// First template arg of std::array is the type, second arg is the size.
const auto &SizeArg = TemplateArgs[1];
if (SizeArg.getKind() != TemplateArgument::Integral)
return;
llvm::APInt ArraySize = SizeArg.getAsIntegral();
// Get uint64_t values, because different bitwidths would lead to an assertion
// in APInt::uge.
if (Index->getZExtValue() >= ArraySize.getZExtValue()) {
diag(Matched->getExprLoc(),
"std::array<> index %0 is past the end of the array "
"(which contains %1 elements)")
<< toString(*Index, 10) << toString(ArraySize, 10, false);
}
}
} // namespace cppcoreguidelines
} // namespace tidy
} // namespace clang