
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
127 lines
4.7 KiB
C++
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
|