1 | //===--- ProBoundsConstantArrayIndexCheck.cpp - clang-tidy-----------------===// |
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-exception |
6 | // |
7 | //===----------------------------------------------------------------------===// |
8 | |
9 | #include "ProBoundsConstantArrayIndexCheck.h" |
10 | #include "clang/AST/ASTContext.h" |
11 | #include "clang/ASTMatchers/ASTMatchFinder.h" |
12 | #include "clang/Frontend/CompilerInstance.h" |
13 | #include "clang/Lex/Preprocessor.h" |
14 | #include <optional> |
15 | |
16 | using namespace clang::ast_matchers; |
17 | |
18 | namespace clang::tidy::cppcoreguidelines { |
19 | |
20 | ProBoundsConstantArrayIndexCheck::ProBoundsConstantArrayIndexCheck( |
21 | StringRef Name, ClangTidyContext *Context) |
22 | : ClangTidyCheck(Name, Context), GslHeader(Options.get(LocalName: "GslHeader" , Default: "" )), |
23 | Inserter(Options.getLocalOrGlobal(LocalName: "IncludeStyle" , |
24 | Default: utils::IncludeSorter::IS_LLVM), |
25 | areDiagsSelfContained()) {} |
26 | |
27 | void ProBoundsConstantArrayIndexCheck::storeOptions( |
28 | ClangTidyOptions::OptionMap &Opts) { |
29 | Options.store(Options&: Opts, LocalName: "GslHeader" , Value: GslHeader); |
30 | Options.store(Options&: Opts, LocalName: "IncludeStyle" , Value: Inserter.getStyle()); |
31 | } |
32 | |
33 | void ProBoundsConstantArrayIndexCheck::registerPPCallbacks( |
34 | const SourceManager &SM, Preprocessor *PP, Preprocessor *ModuleExpanderPP) { |
35 | Inserter.registerPreprocessor(PP); |
36 | } |
37 | |
38 | void ProBoundsConstantArrayIndexCheck::registerMatchers(MatchFinder *Finder) { |
39 | // Note: if a struct contains an array member, the compiler-generated |
40 | // constructor has an arraySubscriptExpr. |
41 | Finder->addMatcher(NodeMatch: arraySubscriptExpr(hasBase(InnerMatcher: ignoringImpCasts(InnerMatcher: hasType( |
42 | InnerMatcher: constantArrayType().bind(ID: "type" )))), |
43 | hasIndex(InnerMatcher: expr().bind(ID: "index" )), |
44 | unless(hasAncestor(decl(isImplicit())))) |
45 | .bind(ID: "expr" ), |
46 | Action: this); |
47 | |
48 | Finder->addMatcher( |
49 | NodeMatch: cxxOperatorCallExpr( |
50 | hasOverloadedOperatorName(Name: "[]" ), |
51 | callee(InnerMatcher: cxxMethodDecl( |
52 | ofClass(InnerMatcher: cxxRecordDecl(hasName(Name: "::std::array" )).bind(ID: "type" )))), |
53 | hasArgument(N: 1, InnerMatcher: expr().bind(ID: "index" ))) |
54 | .bind(ID: "expr" ), |
55 | Action: this); |
56 | } |
57 | |
58 | void ProBoundsConstantArrayIndexCheck::check( |
59 | const MatchFinder::MatchResult &Result) { |
60 | const auto *Matched = Result.Nodes.getNodeAs<Expr>(ID: "expr" ); |
61 | const auto *IndexExpr = Result.Nodes.getNodeAs<Expr>(ID: "index" ); |
62 | |
63 | // This expression can only appear inside ArrayInitLoopExpr, which |
64 | // is always implicitly generated. ArrayInitIndexExpr is not a |
65 | // constant, but we shouldn't report a warning for it. |
66 | if (isa<ArrayInitIndexExpr>(Val: IndexExpr)) |
67 | return; |
68 | |
69 | if (IndexExpr->isValueDependent()) |
70 | return; // We check in the specialization. |
71 | |
72 | std::optional<llvm::APSInt> Index = |
73 | IndexExpr->getIntegerConstantExpr(Ctx: *Result.Context); |
74 | if (!Index) { |
75 | SourceRange BaseRange; |
76 | if (const auto *ArraySubscriptE = dyn_cast<ArraySubscriptExpr>(Val: Matched)) |
77 | BaseRange = ArraySubscriptE->getBase()->getSourceRange(); |
78 | else |
79 | BaseRange = |
80 | cast<CXXOperatorCallExpr>(Val: Matched)->getArg(0)->getSourceRange(); |
81 | SourceRange IndexRange = IndexExpr->getSourceRange(); |
82 | |
83 | auto Diag = diag(Loc: Matched->getExprLoc(), |
84 | Description: "do not use array subscript when the index is " |
85 | "not an integer constant expression" ); |
86 | if (!GslHeader.empty()) { |
87 | Diag << FixItHint::CreateInsertion(InsertionLoc: BaseRange.getBegin(), Code: "gsl::at(" ) |
88 | << FixItHint::CreateReplacement( |
89 | RemoveRange: SourceRange(BaseRange.getEnd().getLocWithOffset(Offset: 1), |
90 | IndexRange.getBegin().getLocWithOffset(Offset: -1)), |
91 | Code: ", " ) |
92 | << FixItHint::CreateReplacement(Matched->getEndLoc(), ")" ) |
93 | << Inserter.createMainFileIncludeInsertion(Header: GslHeader); |
94 | } |
95 | return; |
96 | } |
97 | |
98 | const auto *StdArrayDecl = |
99 | Result.Nodes.getNodeAs<ClassTemplateSpecializationDecl>(ID: "type" ); |
100 | |
101 | // For static arrays, this is handled in clang-diagnostic-array-bounds. |
102 | if (!StdArrayDecl) |
103 | return; |
104 | |
105 | if (Index->isSigned() && Index->isNegative()) { |
106 | diag(Loc: Matched->getExprLoc(), Description: "std::array<> index %0 is negative" ) |
107 | << toString(I: *Index, Radix: 10); |
108 | return; |
109 | } |
110 | |
111 | const TemplateArgumentList &TemplateArgs = StdArrayDecl->getTemplateArgs(); |
112 | if (TemplateArgs.size() < 2) |
113 | return; |
114 | // First template arg of std::array is the type, second arg is the size. |
115 | const auto &SizeArg = TemplateArgs[1]; |
116 | if (SizeArg.getKind() != TemplateArgument::Integral) |
117 | return; |
118 | llvm::APInt ArraySize = SizeArg.getAsIntegral(); |
119 | |
120 | // Get uint64_t values, because different bitwidths would lead to an assertion |
121 | // in APInt::uge. |
122 | if (Index->getZExtValue() >= ArraySize.getZExtValue()) { |
123 | diag(Loc: Matched->getExprLoc(), |
124 | Description: "std::array<> index %0 is past the end of the array " |
125 | "(which contains %1 elements)" ) |
126 | << toString(I: *Index, Radix: 10) << toString(I: ArraySize, Radix: 10, Signed: false); |
127 | } |
128 | } |
129 | |
130 | } // namespace clang::tidy::cppcoreguidelines |
131 | |