1//===- UnsafeBufferUsage.cpp - Replace pointers with modern C++ -----------===//
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 "clang/Analysis/Analyses/UnsafeBufferUsage.h"
10#include "clang/AST/ASTContext.h"
11#include "clang/AST/Decl.h"
12#include "clang/AST/Expr.h"
13#include "clang/AST/RecursiveASTVisitor.h"
14#include "clang/AST/Stmt.h"
15#include "clang/AST/StmtVisitor.h"
16#include "clang/ASTMatchers/ASTMatchFinder.h"
17#include "clang/ASTMatchers/ASTMatchers.h"
18#include "clang/Basic/CharInfo.h"
19#include "clang/Basic/SourceLocation.h"
20#include "clang/Lex/Lexer.h"
21#include "clang/Lex/Preprocessor.h"
22#include "llvm/ADT/APSInt.h"
23#include "llvm/ADT/SmallVector.h"
24#include "llvm/ADT/StringRef.h"
25#include "llvm/Support/Casting.h"
26#include <memory>
27#include <optional>
28#include <queue>
29#include <sstream>
30
31using namespace llvm;
32using namespace clang;
33using namespace ast_matchers;
34
35#ifndef NDEBUG
36namespace {
37class StmtDebugPrinter
38 : public ConstStmtVisitor<StmtDebugPrinter, std::string> {
39public:
40 std::string VisitStmt(const Stmt *S) { return S->getStmtClassName(); }
41
42 std::string VisitBinaryOperator(const BinaryOperator *BO) {
43 return "BinaryOperator(" + BO->getOpcodeStr().str() + ")";
44 }
45
46 std::string VisitUnaryOperator(const UnaryOperator *UO) {
47 return "UnaryOperator(" + UO->getOpcodeStr(Op: UO->getOpcode()).str() + ")";
48 }
49
50 std::string VisitImplicitCastExpr(const ImplicitCastExpr *ICE) {
51 return "ImplicitCastExpr(" + std::string(ICE->getCastKindName()) + ")";
52 }
53};
54
55// Returns a string of ancestor `Stmt`s of the given `DRE` in such a form:
56// "DRE ==> parent-of-DRE ==> grandparent-of-DRE ==> ...".
57static std::string getDREAncestorString(const DeclRefExpr *DRE,
58 ASTContext &Ctx) {
59 std::stringstream SS;
60 const Stmt *St = DRE;
61 StmtDebugPrinter StmtPriner;
62
63 do {
64 SS << StmtPriner.Visit(St);
65
66 DynTypedNodeList StParents = Ctx.getParents(Node: *St);
67
68 if (StParents.size() > 1)
69 return "unavailable due to multiple parents";
70 if (StParents.size() == 0)
71 break;
72 St = StParents.begin()->get<Stmt>();
73 if (St)
74 SS << " ==> ";
75 } while (St);
76 return SS.str();
77}
78} // namespace
79#endif /* NDEBUG */
80
81namespace clang::ast_matchers {
82// A `RecursiveASTVisitor` that traverses all descendants of a given node "n"
83// except for those belonging to a different callable of "n".
84class MatchDescendantVisitor
85 : public RecursiveASTVisitor<MatchDescendantVisitor> {
86public:
87 typedef RecursiveASTVisitor<MatchDescendantVisitor> VisitorBase;
88
89 // Creates an AST visitor that matches `Matcher` on all
90 // descendants of a given node "n" except for the ones
91 // belonging to a different callable of "n".
92 MatchDescendantVisitor(const internal::DynTypedMatcher *Matcher,
93 internal::ASTMatchFinder *Finder,
94 internal::BoundNodesTreeBuilder *Builder,
95 internal::ASTMatchFinder::BindKind Bind,
96 const bool ignoreUnevaluatedContext)
97 : Matcher(Matcher), Finder(Finder), Builder(Builder), Bind(Bind),
98 Matches(false), ignoreUnevaluatedContext(ignoreUnevaluatedContext) {}
99
100 // Returns true if a match is found in a subtree of `DynNode`, which belongs
101 // to the same callable of `DynNode`.
102 bool findMatch(const DynTypedNode &DynNode) {
103 Matches = false;
104 if (const Stmt *StmtNode = DynNode.get<Stmt>()) {
105 TraverseStmt(Node: const_cast<Stmt *>(StmtNode));
106 *Builder = ResultBindings;
107 return Matches;
108 }
109 return false;
110 }
111
112 // The following are overriding methods from the base visitor class.
113 // They are public only to allow CRTP to work. They are *not *part
114 // of the public API of this class.
115
116 // For the matchers so far used in safe buffers, we only need to match
117 // `Stmt`s. To override more as needed.
118
119 bool TraverseDecl(Decl *Node) {
120 if (!Node)
121 return true;
122 if (!match(Node: *Node))
123 return false;
124 // To skip callables:
125 if (isa<FunctionDecl, BlockDecl, ObjCMethodDecl>(Val: Node))
126 return true;
127 // Traverse descendants
128 return VisitorBase::TraverseDecl(D: Node);
129 }
130
131 bool TraverseGenericSelectionExpr(GenericSelectionExpr *Node) {
132 // These are unevaluated, except the result expression.
133 if (ignoreUnevaluatedContext)
134 return TraverseStmt(Node->getResultExpr());
135 return VisitorBase::TraverseGenericSelectionExpr(Node);
136 }
137
138 bool TraverseUnaryExprOrTypeTraitExpr(UnaryExprOrTypeTraitExpr *Node) {
139 // Unevaluated context.
140 if (ignoreUnevaluatedContext)
141 return true;
142 return VisitorBase::TraverseUnaryExprOrTypeTraitExpr(Node);
143 }
144
145 bool TraverseTypeOfExprTypeLoc(TypeOfExprTypeLoc Node) {
146 // Unevaluated context.
147 if (ignoreUnevaluatedContext)
148 return true;
149 return VisitorBase::TraverseTypeOfExprTypeLoc(Node);
150 }
151
152 bool TraverseDecltypeTypeLoc(DecltypeTypeLoc Node) {
153 // Unevaluated context.
154 if (ignoreUnevaluatedContext)
155 return true;
156 return VisitorBase::TraverseDecltypeTypeLoc(Node);
157 }
158
159 bool TraverseCXXNoexceptExpr(CXXNoexceptExpr *Node) {
160 // Unevaluated context.
161 if (ignoreUnevaluatedContext)
162 return true;
163 return VisitorBase::TraverseCXXNoexceptExpr(Node);
164 }
165
166 bool TraverseCXXTypeidExpr(CXXTypeidExpr *Node) {
167 // Unevaluated context.
168 if (ignoreUnevaluatedContext)
169 return true;
170 return VisitorBase::TraverseCXXTypeidExpr(Node);
171 }
172
173 bool TraverseStmt(Stmt *Node, DataRecursionQueue *Queue = nullptr) {
174 if (!Node)
175 return true;
176 if (!match(Node: *Node))
177 return false;
178 return VisitorBase::TraverseStmt(S: Node);
179 }
180
181 bool shouldVisitTemplateInstantiations() const { return true; }
182 bool shouldVisitImplicitCode() const {
183 // TODO: let's ignore implicit code for now
184 return false;
185 }
186
187private:
188 // Sets 'Matched' to true if 'Matcher' matches 'Node'
189 //
190 // Returns 'true' if traversal should continue after this function
191 // returns, i.e. if no match is found or 'Bind' is 'BK_All'.
192 template <typename T> bool match(const T &Node) {
193 internal::BoundNodesTreeBuilder RecursiveBuilder(*Builder);
194
195 if (Matcher->matches(DynNode: DynTypedNode::create(Node), Finder,
196 Builder: &RecursiveBuilder)) {
197 ResultBindings.addMatch(Bindings: RecursiveBuilder);
198 Matches = true;
199 if (Bind != internal::ASTMatchFinder::BK_All)
200 return false; // Abort as soon as a match is found.
201 }
202 return true;
203 }
204
205 const internal::DynTypedMatcher *const Matcher;
206 internal::ASTMatchFinder *const Finder;
207 internal::BoundNodesTreeBuilder *const Builder;
208 internal::BoundNodesTreeBuilder ResultBindings;
209 const internal::ASTMatchFinder::BindKind Bind;
210 bool Matches;
211 bool ignoreUnevaluatedContext;
212};
213
214// Because we're dealing with raw pointers, let's define what we mean by that.
215static auto hasPointerType() {
216 return hasType(InnerMatcher: hasCanonicalType(InnerMatcher: pointerType()));
217}
218
219static auto hasArrayType() { return hasType(InnerMatcher: hasCanonicalType(InnerMatcher: arrayType())); }
220
221AST_MATCHER_P(Stmt, forEachDescendantEvaluatedStmt, internal::Matcher<Stmt>,
222 innerMatcher) {
223 const DynTypedMatcher &DTM = static_cast<DynTypedMatcher>(innerMatcher);
224
225 MatchDescendantVisitor Visitor(&DTM, Finder, Builder, ASTMatchFinder::BK_All,
226 true);
227 return Visitor.findMatch(DynNode: DynTypedNode::create(Node));
228}
229
230AST_MATCHER_P(Stmt, forEachDescendantStmt, internal::Matcher<Stmt>,
231 innerMatcher) {
232 const DynTypedMatcher &DTM = static_cast<DynTypedMatcher>(innerMatcher);
233
234 MatchDescendantVisitor Visitor(&DTM, Finder, Builder, ASTMatchFinder::BK_All,
235 false);
236 return Visitor.findMatch(DynNode: DynTypedNode::create(Node));
237}
238
239// Matches a `Stmt` node iff the node is in a safe-buffer opt-out region
240AST_MATCHER_P(Stmt, notInSafeBufferOptOut, const UnsafeBufferUsageHandler *,
241 Handler) {
242 return !Handler->isSafeBufferOptOut(Loc: Node.getBeginLoc());
243}
244
245AST_MATCHER_P(Stmt, ignoreUnsafeBufferInContainer,
246 const UnsafeBufferUsageHandler *, Handler) {
247 return Handler->ignoreUnsafeBufferInContainer(Loc: Node.getBeginLoc());
248}
249
250AST_MATCHER_P(CastExpr, castSubExpr, internal::Matcher<Expr>, innerMatcher) {
251 return innerMatcher.matches(Node: *Node.getSubExpr(), Finder, Builder);
252}
253
254// Matches a `UnaryOperator` whose operator is pre-increment:
255AST_MATCHER(UnaryOperator, isPreInc) {
256 return Node.getOpcode() == UnaryOperator::Opcode::UO_PreInc;
257}
258
259// Returns a matcher that matches any expression 'e' such that `innerMatcher`
260// matches 'e' and 'e' is in an Unspecified Lvalue Context.
261static auto isInUnspecifiedLvalueContext(internal::Matcher<Expr> innerMatcher) {
262 // clang-format off
263 return
264 expr(anyOf(
265 implicitCastExpr(
266 hasCastKind(Kind: CastKind::CK_LValueToRValue),
267 castSubExpr(innerMatcher)),
268 binaryOperator(
269 hasAnyOperatorName("="),
270 hasLHS(InnerMatcher: innerMatcher)
271 )
272 ));
273 // clang-format on
274}
275
276// Returns a matcher that matches any expression `e` such that `InnerMatcher`
277// matches `e` and `e` is in an Unspecified Pointer Context (UPC).
278static internal::Matcher<Stmt>
279isInUnspecifiedPointerContext(internal::Matcher<Stmt> InnerMatcher) {
280 // A UPC can be
281 // 1. an argument of a function call (except the callee has [[unsafe_...]]
282 // attribute), or
283 // 2. the operand of a pointer-to-(integer or bool) cast operation; or
284 // 3. the operand of a comparator operation; or
285 // 4. the operand of a pointer subtraction operation
286 // (i.e., computing the distance between two pointers); or ...
287
288 // clang-format off
289 auto CallArgMatcher = callExpr(
290 forEachArgumentWithParamType(
291 InnerMatcher,
292 isAnyPointer() /* array also decays to pointer type*/),
293 unless(callee(
294 functionDecl(hasAttr(attr::UnsafeBufferUsage)))));
295
296 auto CastOperandMatcher =
297 castExpr(anyOf(hasCastKind(Kind: CastKind::CK_PointerToIntegral),
298 hasCastKind(Kind: CastKind::CK_PointerToBoolean)),
299 castSubExpr(innerMatcher: allOf(hasPointerType(), InnerMatcher)));
300
301 auto CompOperandMatcher =
302 binaryOperator(hasAnyOperatorName("!=", "==", "<", "<=", ">", ">="),
303 eachOf(hasLHS(InnerMatcher: allOf(hasPointerType(), InnerMatcher)),
304 hasRHS(InnerMatcher: allOf(hasPointerType(), InnerMatcher))));
305
306 // A matcher that matches pointer subtractions:
307 auto PtrSubtractionMatcher =
308 binaryOperator(hasOperatorName(Name: "-"),
309 // Note that here we need both LHS and RHS to be
310 // pointer. Then the inner matcher can match any of
311 // them:
312 allOf(hasLHS(InnerMatcher: hasPointerType()),
313 hasRHS(InnerMatcher: hasPointerType())),
314 eachOf(hasLHS(InnerMatcher),
315 hasRHS(InnerMatcher)));
316 // clang-format on
317
318 return stmt(anyOf(CallArgMatcher, CastOperandMatcher, CompOperandMatcher,
319 PtrSubtractionMatcher));
320 // FIXME: any more cases? (UPC excludes the RHS of an assignment. For now we
321 // don't have to check that.)
322}
323
324// Returns a matcher that matches any expression 'e' such that `innerMatcher`
325// matches 'e' and 'e' is in an unspecified untyped context (i.e the expression
326// 'e' isn't evaluated to an RValue). For example, consider the following code:
327// int *p = new int[4];
328// int *q = new int[4];
329// if ((p = q)) {}
330// p = q;
331// The expression `p = q` in the conditional of the `if` statement
332// `if ((p = q))` is evaluated as an RValue, whereas the expression `p = q;`
333// in the assignment statement is in an untyped context.
334static internal::Matcher<Stmt>
335isInUnspecifiedUntypedContext(internal::Matcher<Stmt> InnerMatcher) {
336 // An unspecified context can be
337 // 1. A compound statement,
338 // 2. The body of an if statement
339 // 3. Body of a loop
340 auto CompStmt = compoundStmt(forEach(InnerMatcher));
341 auto IfStmtThen = ifStmt(hasThen(InnerMatcher));
342 auto IfStmtElse = ifStmt(hasElse(InnerMatcher));
343 // FIXME: Handle loop bodies.
344 return stmt(anyOf(CompStmt, IfStmtThen, IfStmtElse));
345}
346
347// Given a two-param std::span construct call, matches iff the call has the
348// following forms:
349// 1. `std::span<T>{new T[n], n}`, where `n` is a literal or a DRE
350// 2. `std::span<T>{new T, 1}`
351// 3. `std::span<T>{&var, 1}`
352// 4. `std::span<T>{a, n}`, where `a` is of an array-of-T with constant size
353// `n`
354// 5. `std::span<T>{any, 0}`
355AST_MATCHER(CXXConstructExpr, isSafeSpanTwoParamConstruct) {
356 assert(Node.getNumArgs() == 2 &&
357 "expecting a two-parameter std::span constructor");
358 const Expr *Arg0 = Node.getArg(Arg: 0)->IgnoreImplicit();
359 const Expr *Arg1 = Node.getArg(Arg: 1)->IgnoreImplicit();
360 auto HaveEqualConstantValues = [&Finder](const Expr *E0, const Expr *E1) {
361 if (auto E0CV = E0->getIntegerConstantExpr(Ctx: Finder->getASTContext()))
362 if (auto E1CV = E1->getIntegerConstantExpr(Ctx: Finder->getASTContext())) {
363 return APSInt::compareValues(I1: *E0CV, I2: *E1CV) == 0;
364 }
365 return false;
366 };
367 auto AreSameDRE = [](const Expr *E0, const Expr *E1) {
368 if (auto *DRE0 = dyn_cast<DeclRefExpr>(Val: E0))
369 if (auto *DRE1 = dyn_cast<DeclRefExpr>(Val: E1)) {
370 return DRE0->getDecl() == DRE1->getDecl();
371 }
372 return false;
373 };
374 std::optional<APSInt> Arg1CV =
375 Arg1->getIntegerConstantExpr(Ctx: Finder->getASTContext());
376
377 if (Arg1CV && Arg1CV->isZero())
378 // Check form 5:
379 return true;
380 switch (Arg0->IgnoreImplicit()->getStmtClass()) {
381 case Stmt::CXXNewExprClass:
382 if (auto Size = cast<CXXNewExpr>(Val: Arg0)->getArraySize()) {
383 // Check form 1:
384 return AreSameDRE((*Size)->IgnoreImplicit(), Arg1) ||
385 HaveEqualConstantValues(*Size, Arg1);
386 }
387 // TODO: what's placeholder type? avoid it for now.
388 if (!cast<CXXNewExpr>(Val: Arg0)->hasPlaceholderType()) {
389 // Check form 2:
390 return Arg1CV && Arg1CV->isOne();
391 }
392 break;
393 case Stmt::UnaryOperatorClass:
394 if (cast<UnaryOperator>(Val: Arg0)->getOpcode() ==
395 UnaryOperator::Opcode::UO_AddrOf)
396 // Check form 3:
397 return Arg1CV && Arg1CV->isOne();
398 break;
399 default:
400 break;
401 }
402
403 QualType Arg0Ty = Arg0->IgnoreImplicit()->getType();
404
405 if (Arg0Ty->isConstantArrayType()) {
406 const APSInt ConstArrSize =
407 APSInt(cast<ConstantArrayType>(Val&: Arg0Ty)->getSize());
408
409 // Check form 4:
410 return Arg1CV && APSInt::compareValues(I1: ConstArrSize, I2: *Arg1CV) == 0;
411 }
412 return false;
413}
414
415AST_MATCHER(ArraySubscriptExpr, isSafeArraySubscript) {
416 // FIXME: Proper solution:
417 // - refactor Sema::CheckArrayAccess
418 // - split safe/OOB/unknown decision logic from diagnostics emitting code
419 // - e. g. "Try harder to find a NamedDecl to point at in the note."
420 // already duplicated
421 // - call both from Sema and from here
422
423 const auto *BaseDRE =
424 dyn_cast<DeclRefExpr>(Val: Node.getBase()->IgnoreParenImpCasts());
425 if (!BaseDRE)
426 return false;
427 if (!BaseDRE->getDecl())
428 return false;
429 const auto *CATy = Finder->getASTContext().getAsConstantArrayType(
430 T: BaseDRE->getDecl()->getType());
431 if (!CATy)
432 return false;
433
434 if (const auto *IdxLit = dyn_cast<IntegerLiteral>(Val: Node.getIdx())) {
435 const APInt ArrIdx = IdxLit->getValue();
436 // FIXME: ArrIdx.isNegative() we could immediately emit an error as that's a
437 // bug
438 if (ArrIdx.isNonNegative() &&
439 ArrIdx.getLimitedValue() < CATy->getLimitedSize())
440 return true;
441 }
442
443 return false;
444}
445
446} // namespace clang::ast_matchers
447
448namespace {
449// Because the analysis revolves around variables and their types, we'll need to
450// track uses of variables (aka DeclRefExprs).
451using DeclUseList = SmallVector<const DeclRefExpr *, 1>;
452
453// Convenience typedef.
454using FixItList = SmallVector<FixItHint, 4>;
455} // namespace
456
457namespace {
458/// Gadget is an individual operation in the code that may be of interest to
459/// this analysis. Each (non-abstract) subclass corresponds to a specific
460/// rigid AST structure that constitutes an operation on a pointer-type object.
461/// Discovery of a gadget in the code corresponds to claiming that we understand
462/// what this part of code is doing well enough to potentially improve it.
463/// Gadgets can be warning (immediately deserving a warning) or fixable (not
464/// always deserving a warning per se, but requires our attention to identify
465/// it warrants a fixit).
466class Gadget {
467public:
468 enum class Kind {
469#define GADGET(x) x,
470#include "clang/Analysis/Analyses/UnsafeBufferUsageGadgets.def"
471 };
472
473 /// Common type of ASTMatchers used for discovering gadgets.
474 /// Useful for implementing the static matcher() methods
475 /// that are expected from all non-abstract subclasses.
476 using Matcher = decltype(stmt());
477
478 Gadget(Kind K) : K(K) {}
479
480 Kind getKind() const { return K; }
481
482#ifndef NDEBUG
483 StringRef getDebugName() const {
484 switch (K) {
485#define GADGET(x) \
486 case Kind::x: \
487 return #x;
488#include "clang/Analysis/Analyses/UnsafeBufferUsageGadgets.def"
489 }
490 llvm_unreachable("Unhandled Gadget::Kind enum");
491 }
492#endif
493
494 virtual bool isWarningGadget() const = 0;
495 virtual const Stmt *getBaseStmt() const = 0;
496
497 /// Returns the list of pointer-type variables on which this gadget performs
498 /// its operation. Typically, there's only one variable. This isn't a list
499 /// of all DeclRefExprs in the gadget's AST!
500 virtual DeclUseList getClaimedVarUseSites() const = 0;
501
502 virtual ~Gadget() = default;
503
504private:
505 Kind K;
506};
507
508/// Warning gadgets correspond to unsafe code patterns that warrants
509/// an immediate warning.
510class WarningGadget : public Gadget {
511public:
512 WarningGadget(Kind K) : Gadget(K) {}
513
514 static bool classof(const Gadget *G) { return G->isWarningGadget(); }
515 bool isWarningGadget() const final { return true; }
516};
517
518/// Fixable gadgets correspond to code patterns that aren't always unsafe but
519/// need to be properly recognized in order to emit fixes. For example, if a raw
520/// pointer-type variable is replaced by a safe C++ container, every use of such
521/// variable must be carefully considered and possibly updated.
522class FixableGadget : public Gadget {
523public:
524 FixableGadget(Kind K) : Gadget(K) {}
525
526 static bool classof(const Gadget *G) { return !G->isWarningGadget(); }
527 bool isWarningGadget() const final { return false; }
528
529 /// Returns a fixit that would fix the current gadget according to
530 /// the current strategy. Returns std::nullopt if the fix cannot be produced;
531 /// returns an empty list if no fixes are necessary.
532 virtual std::optional<FixItList> getFixits(const FixitStrategy &) const {
533 return std::nullopt;
534 }
535
536 /// Returns a list of two elements where the first element is the LHS of a
537 /// pointer assignment statement and the second element is the RHS. This
538 /// two-element list represents the fact that the LHS buffer gets its bounds
539 /// information from the RHS buffer. This information will be used later to
540 /// group all those variables whose types must be modified together to prevent
541 /// type mismatches.
542 virtual std::optional<std::pair<const VarDecl *, const VarDecl *>>
543 getStrategyImplications() const {
544 return std::nullopt;
545 }
546};
547
548static auto toSupportedVariable() { return to(InnerMatcher: varDecl()); }
549
550using FixableGadgetList = std::vector<std::unique_ptr<FixableGadget>>;
551using WarningGadgetList = std::vector<std::unique_ptr<WarningGadget>>;
552
553/// An increment of a pointer-type value is unsafe as it may run the pointer
554/// out of bounds.
555class IncrementGadget : public WarningGadget {
556 static constexpr const char *const OpTag = "op";
557 const UnaryOperator *Op;
558
559public:
560 IncrementGadget(const MatchFinder::MatchResult &Result)
561 : WarningGadget(Kind::Increment),
562 Op(Result.Nodes.getNodeAs<UnaryOperator>(ID: OpTag)) {}
563
564 static bool classof(const Gadget *G) {
565 return G->getKind() == Kind::Increment;
566 }
567
568 static Matcher matcher() {
569 return stmt(
570 unaryOperator(hasOperatorName(Name: "++"),
571 hasUnaryOperand(InnerMatcher: ignoringParenImpCasts(InnerMatcher: hasPointerType())))
572 .bind(ID: OpTag));
573 }
574
575 const UnaryOperator *getBaseStmt() const override { return Op; }
576
577 DeclUseList getClaimedVarUseSites() const override {
578 SmallVector<const DeclRefExpr *, 2> Uses;
579 if (const auto *DRE =
580 dyn_cast<DeclRefExpr>(Val: Op->getSubExpr()->IgnoreParenImpCasts())) {
581 Uses.push_back(Elt: DRE);
582 }
583
584 return std::move(Uses);
585 }
586};
587
588/// A decrement of a pointer-type value is unsafe as it may run the pointer
589/// out of bounds.
590class DecrementGadget : public WarningGadget {
591 static constexpr const char *const OpTag = "op";
592 const UnaryOperator *Op;
593
594public:
595 DecrementGadget(const MatchFinder::MatchResult &Result)
596 : WarningGadget(Kind::Decrement),
597 Op(Result.Nodes.getNodeAs<UnaryOperator>(ID: OpTag)) {}
598
599 static bool classof(const Gadget *G) {
600 return G->getKind() == Kind::Decrement;
601 }
602
603 static Matcher matcher() {
604 return stmt(
605 unaryOperator(hasOperatorName(Name: "--"),
606 hasUnaryOperand(InnerMatcher: ignoringParenImpCasts(InnerMatcher: hasPointerType())))
607 .bind(ID: OpTag));
608 }
609
610 const UnaryOperator *getBaseStmt() const override { return Op; }
611
612 DeclUseList getClaimedVarUseSites() const override {
613 if (const auto *DRE =
614 dyn_cast<DeclRefExpr>(Val: Op->getSubExpr()->IgnoreParenImpCasts())) {
615 return {DRE};
616 }
617
618 return {};
619 }
620};
621
622/// Array subscript expressions on raw pointers as if they're arrays. Unsafe as
623/// it doesn't have any bounds checks for the array.
624class ArraySubscriptGadget : public WarningGadget {
625 static constexpr const char *const ArraySubscrTag = "ArraySubscript";
626 const ArraySubscriptExpr *ASE;
627
628public:
629 ArraySubscriptGadget(const MatchFinder::MatchResult &Result)
630 : WarningGadget(Kind::ArraySubscript),
631 ASE(Result.Nodes.getNodeAs<ArraySubscriptExpr>(ID: ArraySubscrTag)) {}
632
633 static bool classof(const Gadget *G) {
634 return G->getKind() == Kind::ArraySubscript;
635 }
636
637 static Matcher matcher() {
638 // clang-format off
639 return stmt(arraySubscriptExpr(
640 hasBase(InnerMatcher: ignoringParenImpCasts(
641 InnerMatcher: anyOf(hasPointerType(), hasArrayType()))),
642 unless(anyOf(
643 isSafeArraySubscript(),
644 hasIndex(
645 InnerMatcher: anyOf(integerLiteral(equals(Value: 0)), arrayInitIndexExpr())
646 )
647 ))).bind(ID: ArraySubscrTag));
648 // clang-format on
649 }
650
651 const ArraySubscriptExpr *getBaseStmt() const override { return ASE; }
652
653 DeclUseList getClaimedVarUseSites() const override {
654 if (const auto *DRE =
655 dyn_cast<DeclRefExpr>(Val: ASE->getBase()->IgnoreParenImpCasts())) {
656 return {DRE};
657 }
658
659 return {};
660 }
661};
662
663/// A pointer arithmetic expression of one of the forms:
664/// \code
665/// ptr + n | n + ptr | ptr - n | ptr += n | ptr -= n
666/// \endcode
667class PointerArithmeticGadget : public WarningGadget {
668 static constexpr const char *const PointerArithmeticTag = "ptrAdd";
669 static constexpr const char *const PointerArithmeticPointerTag = "ptrAddPtr";
670 const BinaryOperator *PA; // pointer arithmetic expression
671 const Expr *Ptr; // the pointer expression in `PA`
672
673public:
674 PointerArithmeticGadget(const MatchFinder::MatchResult &Result)
675 : WarningGadget(Kind::PointerArithmetic),
676 PA(Result.Nodes.getNodeAs<BinaryOperator>(ID: PointerArithmeticTag)),
677 Ptr(Result.Nodes.getNodeAs<Expr>(ID: PointerArithmeticPointerTag)) {}
678
679 static bool classof(const Gadget *G) {
680 return G->getKind() == Kind::PointerArithmetic;
681 }
682
683 static Matcher matcher() {
684 auto HasIntegerType = anyOf(hasType(InnerMatcher: isInteger()), hasType(InnerMatcher: enumType()));
685 auto PtrAtRight =
686 allOf(hasOperatorName(Name: "+"),
687 hasRHS(InnerMatcher: expr(hasPointerType()).bind(ID: PointerArithmeticPointerTag)),
688 hasLHS(InnerMatcher: HasIntegerType));
689 auto PtrAtLeft =
690 allOf(anyOf(hasOperatorName(Name: "+"), hasOperatorName(Name: "-"),
691 hasOperatorName(Name: "+="), hasOperatorName(Name: "-=")),
692 hasLHS(InnerMatcher: expr(hasPointerType()).bind(ID: PointerArithmeticPointerTag)),
693 hasRHS(InnerMatcher: HasIntegerType));
694
695 return stmt(binaryOperator(anyOf(PtrAtLeft, PtrAtRight))
696 .bind(ID: PointerArithmeticTag));
697 }
698
699 const Stmt *getBaseStmt() const override { return PA; }
700
701 DeclUseList getClaimedVarUseSites() const override {
702 if (const auto *DRE = dyn_cast<DeclRefExpr>(Val: Ptr->IgnoreParenImpCasts())) {
703 return {DRE};
704 }
705
706 return {};
707 }
708 // FIXME: pointer adding zero should be fine
709 // FIXME: this gadge will need a fix-it
710};
711
712class SpanTwoParamConstructorGadget : public WarningGadget {
713 static constexpr const char *const SpanTwoParamConstructorTag =
714 "spanTwoParamConstructor";
715 const CXXConstructExpr *Ctor; // the span constructor expression
716
717public:
718 SpanTwoParamConstructorGadget(const MatchFinder::MatchResult &Result)
719 : WarningGadget(Kind::SpanTwoParamConstructor),
720 Ctor(Result.Nodes.getNodeAs<CXXConstructExpr>(
721 ID: SpanTwoParamConstructorTag)) {}
722
723 static bool classof(const Gadget *G) {
724 return G->getKind() == Kind::SpanTwoParamConstructor;
725 }
726
727 static Matcher matcher() {
728 auto HasTwoParamSpanCtorDecl = hasDeclaration(
729 InnerMatcher: cxxConstructorDecl(hasDeclContext(InnerMatcher: isInStdNamespace()), hasName(Name: "span"),
730 parameterCountIs(N: 2)));
731
732 return stmt(cxxConstructExpr(HasTwoParamSpanCtorDecl,
733 unless(isSafeSpanTwoParamConstruct()))
734 .bind(ID: SpanTwoParamConstructorTag));
735 }
736
737 const Stmt *getBaseStmt() const override { return Ctor; }
738
739 DeclUseList getClaimedVarUseSites() const override {
740 // If the constructor call is of the form `std::span{var, n}`, `var` is
741 // considered an unsafe variable.
742 if (auto *DRE = dyn_cast<DeclRefExpr>(Val: Ctor->getArg(Arg: 0))) {
743 if (isa<VarDecl>(Val: DRE->getDecl()))
744 return {DRE};
745 }
746 return {};
747 }
748};
749
750/// A pointer initialization expression of the form:
751/// \code
752/// int *p = q;
753/// \endcode
754class PointerInitGadget : public FixableGadget {
755private:
756 static constexpr const char *const PointerInitLHSTag = "ptrInitLHS";
757 static constexpr const char *const PointerInitRHSTag = "ptrInitRHS";
758 const VarDecl *PtrInitLHS; // the LHS pointer expression in `PI`
759 const DeclRefExpr *PtrInitRHS; // the RHS pointer expression in `PI`
760
761public:
762 PointerInitGadget(const MatchFinder::MatchResult &Result)
763 : FixableGadget(Kind::PointerInit),
764 PtrInitLHS(Result.Nodes.getNodeAs<VarDecl>(ID: PointerInitLHSTag)),
765 PtrInitRHS(Result.Nodes.getNodeAs<DeclRefExpr>(ID: PointerInitRHSTag)) {}
766
767 static bool classof(const Gadget *G) {
768 return G->getKind() == Kind::PointerInit;
769 }
770
771 static Matcher matcher() {
772 auto PtrInitStmt = declStmt(hasSingleDecl(
773 InnerMatcher: varDecl(hasInitializer(InnerMatcher: ignoringImpCasts(
774 InnerMatcher: declRefExpr(hasPointerType(), toSupportedVariable())
775 .bind(ID: PointerInitRHSTag))))
776 .bind(ID: PointerInitLHSTag)));
777
778 return stmt(PtrInitStmt);
779 }
780
781 virtual std::optional<FixItList>
782 getFixits(const FixitStrategy &S) const override;
783
784 virtual const Stmt *getBaseStmt() const override {
785 // FIXME: This needs to be the entire DeclStmt, assuming that this method
786 // makes sense at all on a FixableGadget.
787 return PtrInitRHS;
788 }
789
790 virtual DeclUseList getClaimedVarUseSites() const override {
791 return DeclUseList{PtrInitRHS};
792 }
793
794 virtual std::optional<std::pair<const VarDecl *, const VarDecl *>>
795 getStrategyImplications() const override {
796 return std::make_pair(x: PtrInitLHS, y: cast<VarDecl>(Val: PtrInitRHS->getDecl()));
797 }
798};
799
800/// A pointer assignment expression of the form:
801/// \code
802/// p = q;
803/// \endcode
804/// where both `p` and `q` are pointers.
805class PtrToPtrAssignmentGadget : public FixableGadget {
806private:
807 static constexpr const char *const PointerAssignLHSTag = "ptrLHS";
808 static constexpr const char *const PointerAssignRHSTag = "ptrRHS";
809 const DeclRefExpr *PtrLHS; // the LHS pointer expression in `PA`
810 const DeclRefExpr *PtrRHS; // the RHS pointer expression in `PA`
811
812public:
813 PtrToPtrAssignmentGadget(const MatchFinder::MatchResult &Result)
814 : FixableGadget(Kind::PtrToPtrAssignment),
815 PtrLHS(Result.Nodes.getNodeAs<DeclRefExpr>(ID: PointerAssignLHSTag)),
816 PtrRHS(Result.Nodes.getNodeAs<DeclRefExpr>(ID: PointerAssignRHSTag)) {}
817
818 static bool classof(const Gadget *G) {
819 return G->getKind() == Kind::PtrToPtrAssignment;
820 }
821
822 static Matcher matcher() {
823 auto PtrAssignExpr = binaryOperator(
824 allOf(hasOperatorName(Name: "="),
825 hasRHS(InnerMatcher: ignoringParenImpCasts(
826 InnerMatcher: declRefExpr(hasPointerType(), toSupportedVariable())
827 .bind(ID: PointerAssignRHSTag))),
828 hasLHS(InnerMatcher: declRefExpr(hasPointerType(), toSupportedVariable())
829 .bind(ID: PointerAssignLHSTag))));
830
831 return stmt(isInUnspecifiedUntypedContext(InnerMatcher: PtrAssignExpr));
832 }
833
834 virtual std::optional<FixItList>
835 getFixits(const FixitStrategy &S) const override;
836
837 virtual const Stmt *getBaseStmt() const override {
838 // FIXME: This should be the binary operator, assuming that this method
839 // makes sense at all on a FixableGadget.
840 return PtrLHS;
841 }
842
843 virtual DeclUseList getClaimedVarUseSites() const override {
844 return DeclUseList{PtrLHS, PtrRHS};
845 }
846
847 virtual std::optional<std::pair<const VarDecl *, const VarDecl *>>
848 getStrategyImplications() const override {
849 return std::make_pair(x: cast<VarDecl>(Val: PtrLHS->getDecl()),
850 y: cast<VarDecl>(Val: PtrRHS->getDecl()));
851 }
852};
853
854/// An assignment expression of the form:
855/// \code
856/// ptr = array;
857/// \endcode
858/// where `p` is a pointer and `array` is a constant size array.
859class CArrayToPtrAssignmentGadget : public FixableGadget {
860private:
861 static constexpr const char *const PointerAssignLHSTag = "ptrLHS";
862 static constexpr const char *const PointerAssignRHSTag = "ptrRHS";
863 const DeclRefExpr *PtrLHS; // the LHS pointer expression in `PA`
864 const DeclRefExpr *PtrRHS; // the RHS pointer expression in `PA`
865
866public:
867 CArrayToPtrAssignmentGadget(const MatchFinder::MatchResult &Result)
868 : FixableGadget(Kind::CArrayToPtrAssignment),
869 PtrLHS(Result.Nodes.getNodeAs<DeclRefExpr>(ID: PointerAssignLHSTag)),
870 PtrRHS(Result.Nodes.getNodeAs<DeclRefExpr>(ID: PointerAssignRHSTag)) {}
871
872 static bool classof(const Gadget *G) {
873 return G->getKind() == Kind::CArrayToPtrAssignment;
874 }
875
876 static Matcher matcher() {
877 auto PtrAssignExpr = binaryOperator(
878 allOf(hasOperatorName(Name: "="),
879 hasRHS(InnerMatcher: ignoringParenImpCasts(
880 InnerMatcher: declRefExpr(hasType(InnerMatcher: hasCanonicalType(InnerMatcher: constantArrayType())),
881 toSupportedVariable())
882 .bind(ID: PointerAssignRHSTag))),
883 hasLHS(InnerMatcher: declRefExpr(hasPointerType(), toSupportedVariable())
884 .bind(ID: PointerAssignLHSTag))));
885
886 return stmt(isInUnspecifiedUntypedContext(InnerMatcher: PtrAssignExpr));
887 }
888
889 virtual std::optional<FixItList>
890 getFixits(const FixitStrategy &S) const override;
891
892 virtual const Stmt *getBaseStmt() const override {
893 // FIXME: This should be the binary operator, assuming that this method
894 // makes sense at all on a FixableGadget.
895 return PtrLHS;
896 }
897
898 virtual DeclUseList getClaimedVarUseSites() const override {
899 return DeclUseList{PtrLHS, PtrRHS};
900 }
901
902 virtual std::optional<std::pair<const VarDecl *, const VarDecl *>>
903 getStrategyImplications() const override {
904 return {};
905 }
906};
907
908/// A call of a function or method that performs unchecked buffer operations
909/// over one of its pointer parameters.
910class UnsafeBufferUsageAttrGadget : public WarningGadget {
911 constexpr static const char *const OpTag = "call_expr";
912 const CallExpr *Op;
913
914public:
915 UnsafeBufferUsageAttrGadget(const MatchFinder::MatchResult &Result)
916 : WarningGadget(Kind::UnsafeBufferUsageAttr),
917 Op(Result.Nodes.getNodeAs<CallExpr>(ID: OpTag)) {}
918
919 static bool classof(const Gadget *G) {
920 return G->getKind() == Kind::UnsafeBufferUsageAttr;
921 }
922
923 static Matcher matcher() {
924 return stmt(callExpr(callee(functionDecl(hasAttr(attr::UnsafeBufferUsage))))
925 .bind(OpTag));
926 }
927 const Stmt *getBaseStmt() const override { return Op; }
928
929 DeclUseList getClaimedVarUseSites() const override { return {}; }
930};
931
932// Warning gadget for unsafe invocation of span::data method.
933// Triggers when the pointer returned by the invocation is immediately
934// cast to a larger type.
935
936class DataInvocationGadget : public WarningGadget {
937 constexpr static const char *const OpTag = "data_invocation_expr";
938 const ExplicitCastExpr *Op;
939
940public:
941 DataInvocationGadget(const MatchFinder::MatchResult &Result)
942 : WarningGadget(Kind::DataInvocation),
943 Op(Result.Nodes.getNodeAs<ExplicitCastExpr>(ID: OpTag)) {}
944
945 static bool classof(const Gadget *G) {
946 return G->getKind() == Kind::DataInvocation;
947 }
948
949 static Matcher matcher() {
950 Matcher callExpr = cxxMemberCallExpr(
951 callee(InnerMatcher: cxxMethodDecl(hasName(Name: "data"), ofClass(InnerMatcher: hasName(Name: "std::span")))));
952 return stmt(
953 explicitCastExpr(anyOf(has(callExpr), has(parenExpr(has(callExpr)))))
954 .bind(ID: OpTag));
955 }
956 const Stmt *getBaseStmt() const override { return Op; }
957
958 DeclUseList getClaimedVarUseSites() const override { return {}; }
959};
960
961// Represents expressions of the form `DRE[*]` in the Unspecified Lvalue
962// Context (see `isInUnspecifiedLvalueContext`).
963// Note here `[]` is the built-in subscript operator.
964class ULCArraySubscriptGadget : public FixableGadget {
965private:
966 static constexpr const char *const ULCArraySubscriptTag =
967 "ArraySubscriptUnderULC";
968 const ArraySubscriptExpr *Node;
969
970public:
971 ULCArraySubscriptGadget(const MatchFinder::MatchResult &Result)
972 : FixableGadget(Kind::ULCArraySubscript),
973 Node(Result.Nodes.getNodeAs<ArraySubscriptExpr>(ID: ULCArraySubscriptTag)) {
974 assert(Node != nullptr && "Expecting a non-null matching result");
975 }
976
977 static bool classof(const Gadget *G) {
978 return G->getKind() == Kind::ULCArraySubscript;
979 }
980
981 static Matcher matcher() {
982 auto ArrayOrPtr = anyOf(hasPointerType(), hasArrayType());
983 auto BaseIsArrayOrPtrDRE = hasBase(
984 InnerMatcher: ignoringParenImpCasts(InnerMatcher: declRefExpr(ArrayOrPtr, toSupportedVariable())));
985 auto Target =
986 arraySubscriptExpr(BaseIsArrayOrPtrDRE).bind(ID: ULCArraySubscriptTag);
987
988 return expr(isInUnspecifiedLvalueContext(innerMatcher: Target));
989 }
990
991 virtual std::optional<FixItList>
992 getFixits(const FixitStrategy &S) const override;
993
994 virtual const Stmt *getBaseStmt() const override { return Node; }
995
996 virtual DeclUseList getClaimedVarUseSites() const override {
997 if (const auto *DRE =
998 dyn_cast<DeclRefExpr>(Val: Node->getBase()->IgnoreImpCasts())) {
999 return {DRE};
1000 }
1001 return {};
1002 }
1003};
1004
1005// Fixable gadget to handle stand alone pointers of the form `UPC(DRE)` in the
1006// unspecified pointer context (isInUnspecifiedPointerContext). The gadget emits
1007// fixit of the form `UPC(DRE.data())`.
1008class UPCStandalonePointerGadget : public FixableGadget {
1009private:
1010 static constexpr const char *const DeclRefExprTag = "StandalonePointer";
1011 const DeclRefExpr *Node;
1012
1013public:
1014 UPCStandalonePointerGadget(const MatchFinder::MatchResult &Result)
1015 : FixableGadget(Kind::UPCStandalonePointer),
1016 Node(Result.Nodes.getNodeAs<DeclRefExpr>(ID: DeclRefExprTag)) {
1017 assert(Node != nullptr && "Expecting a non-null matching result");
1018 }
1019
1020 static bool classof(const Gadget *G) {
1021 return G->getKind() == Kind::UPCStandalonePointer;
1022 }
1023
1024 static Matcher matcher() {
1025 auto ArrayOrPtr = anyOf(hasPointerType(), hasArrayType());
1026 auto target = expr(ignoringParenImpCasts(
1027 InnerMatcher: declRefExpr(allOf(ArrayOrPtr, toSupportedVariable()))
1028 .bind(ID: DeclRefExprTag)));
1029 return stmt(isInUnspecifiedPointerContext(InnerMatcher: target));
1030 }
1031
1032 virtual std::optional<FixItList>
1033 getFixits(const FixitStrategy &S) const override;
1034
1035 virtual const Stmt *getBaseStmt() const override { return Node; }
1036
1037 virtual DeclUseList getClaimedVarUseSites() const override { return {Node}; }
1038};
1039
1040class PointerDereferenceGadget : public FixableGadget {
1041 static constexpr const char *const BaseDeclRefExprTag = "BaseDRE";
1042 static constexpr const char *const OperatorTag = "op";
1043
1044 const DeclRefExpr *BaseDeclRefExpr = nullptr;
1045 const UnaryOperator *Op = nullptr;
1046
1047public:
1048 PointerDereferenceGadget(const MatchFinder::MatchResult &Result)
1049 : FixableGadget(Kind::PointerDereference),
1050 BaseDeclRefExpr(
1051 Result.Nodes.getNodeAs<DeclRefExpr>(ID: BaseDeclRefExprTag)),
1052 Op(Result.Nodes.getNodeAs<UnaryOperator>(ID: OperatorTag)) {}
1053
1054 static bool classof(const Gadget *G) {
1055 return G->getKind() == Kind::PointerDereference;
1056 }
1057
1058 static Matcher matcher() {
1059 auto Target =
1060 unaryOperator(
1061 hasOperatorName(Name: "*"),
1062 has(expr(ignoringParenImpCasts(
1063 InnerMatcher: declRefExpr(toSupportedVariable()).bind(ID: BaseDeclRefExprTag)))))
1064 .bind(ID: OperatorTag);
1065
1066 return expr(isInUnspecifiedLvalueContext(innerMatcher: Target));
1067 }
1068
1069 DeclUseList getClaimedVarUseSites() const override {
1070 return {BaseDeclRefExpr};
1071 }
1072
1073 virtual const Stmt *getBaseStmt() const final { return Op; }
1074
1075 virtual std::optional<FixItList>
1076 getFixits(const FixitStrategy &S) const override;
1077};
1078
1079// Represents expressions of the form `&DRE[any]` in the Unspecified Pointer
1080// Context (see `isInUnspecifiedPointerContext`).
1081// Note here `[]` is the built-in subscript operator.
1082class UPCAddressofArraySubscriptGadget : public FixableGadget {
1083private:
1084 static constexpr const char *const UPCAddressofArraySubscriptTag =
1085 "AddressofArraySubscriptUnderUPC";
1086 const UnaryOperator *Node; // the `&DRE[any]` node
1087
1088public:
1089 UPCAddressofArraySubscriptGadget(const MatchFinder::MatchResult &Result)
1090 : FixableGadget(Kind::ULCArraySubscript),
1091 Node(Result.Nodes.getNodeAs<UnaryOperator>(
1092 ID: UPCAddressofArraySubscriptTag)) {
1093 assert(Node != nullptr && "Expecting a non-null matching result");
1094 }
1095
1096 static bool classof(const Gadget *G) {
1097 return G->getKind() == Kind::UPCAddressofArraySubscript;
1098 }
1099
1100 static Matcher matcher() {
1101 return expr(isInUnspecifiedPointerContext(InnerMatcher: expr(ignoringImpCasts(
1102 InnerMatcher: unaryOperator(
1103 hasOperatorName(Name: "&"),
1104 hasUnaryOperand(InnerMatcher: arraySubscriptExpr(hasBase(
1105 InnerMatcher: ignoringParenImpCasts(InnerMatcher: declRefExpr(toSupportedVariable()))))))
1106 .bind(ID: UPCAddressofArraySubscriptTag)))));
1107 }
1108
1109 virtual std::optional<FixItList>
1110 getFixits(const FixitStrategy &) const override;
1111
1112 virtual const Stmt *getBaseStmt() const override { return Node; }
1113
1114 virtual DeclUseList getClaimedVarUseSites() const override {
1115 const auto *ArraySubst = cast<ArraySubscriptExpr>(Val: Node->getSubExpr());
1116 const auto *DRE =
1117 cast<DeclRefExpr>(Val: ArraySubst->getBase()->IgnoreParenImpCasts());
1118 return {DRE};
1119 }
1120};
1121} // namespace
1122
1123namespace {
1124// An auxiliary tracking facility for the fixit analysis. It helps connect
1125// declarations to its uses and make sure we've covered all uses with our
1126// analysis before we try to fix the declaration.
1127class DeclUseTracker {
1128 using UseSetTy = SmallSet<const DeclRefExpr *, 16>;
1129 using DefMapTy = DenseMap<const VarDecl *, const DeclStmt *>;
1130
1131 // Allocate on the heap for easier move.
1132 std::unique_ptr<UseSetTy> Uses{std::make_unique<UseSetTy>()};
1133 DefMapTy Defs{};
1134
1135public:
1136 DeclUseTracker() = default;
1137 DeclUseTracker(const DeclUseTracker &) = delete; // Let's avoid copies.
1138 DeclUseTracker &operator=(const DeclUseTracker &) = delete;
1139 DeclUseTracker(DeclUseTracker &&) = default;
1140 DeclUseTracker &operator=(DeclUseTracker &&) = default;
1141
1142 // Start tracking a freshly discovered DRE.
1143 void discoverUse(const DeclRefExpr *DRE) { Uses->insert(Ptr: DRE); }
1144
1145 // Stop tracking the DRE as it's been fully figured out.
1146 void claimUse(const DeclRefExpr *DRE) {
1147 assert(Uses->count(DRE) &&
1148 "DRE not found or claimed by multiple matchers!");
1149 Uses->erase(Ptr: DRE);
1150 }
1151
1152 // A variable is unclaimed if at least one use is unclaimed.
1153 bool hasUnclaimedUses(const VarDecl *VD) const {
1154 // FIXME: Can this be less linear? Maybe maintain a map from VDs to DREs?
1155 return any_of(Range&: *Uses, P: [VD](const DeclRefExpr *DRE) {
1156 return DRE->getDecl()->getCanonicalDecl() == VD->getCanonicalDecl();
1157 });
1158 }
1159
1160 UseSetTy getUnclaimedUses(const VarDecl *VD) const {
1161 UseSetTy ReturnSet;
1162 for (auto use : *Uses) {
1163 if (use->getDecl()->getCanonicalDecl() == VD->getCanonicalDecl()) {
1164 ReturnSet.insert(Ptr: use);
1165 }
1166 }
1167 return ReturnSet;
1168 }
1169
1170 void discoverDecl(const DeclStmt *DS) {
1171 for (const Decl *D : DS->decls()) {
1172 if (const auto *VD = dyn_cast<VarDecl>(Val: D)) {
1173 // FIXME: Assertion temporarily disabled due to a bug in
1174 // ASTMatcher internal behavior in presence of GNU
1175 // statement-expressions. We need to properly investigate this
1176 // because it can screw up our algorithm in other ways.
1177 // assert(Defs.count(VD) == 0 && "Definition already discovered!");
1178 Defs[VD] = DS;
1179 }
1180 }
1181 }
1182
1183 const DeclStmt *lookupDecl(const VarDecl *VD) const {
1184 return Defs.lookup(Val: VD);
1185 }
1186};
1187} // namespace
1188
1189// Representing a pointer type expression of the form `++Ptr` in an Unspecified
1190// Pointer Context (UPC):
1191class UPCPreIncrementGadget : public FixableGadget {
1192private:
1193 static constexpr const char *const UPCPreIncrementTag =
1194 "PointerPreIncrementUnderUPC";
1195 const UnaryOperator *Node; // the `++Ptr` node
1196
1197public:
1198 UPCPreIncrementGadget(const MatchFinder::MatchResult &Result)
1199 : FixableGadget(Kind::UPCPreIncrement),
1200 Node(Result.Nodes.getNodeAs<UnaryOperator>(ID: UPCPreIncrementTag)) {
1201 assert(Node != nullptr && "Expecting a non-null matching result");
1202 }
1203
1204 static bool classof(const Gadget *G) {
1205 return G->getKind() == Kind::UPCPreIncrement;
1206 }
1207
1208 static Matcher matcher() {
1209 // Note here we match `++Ptr` for any expression `Ptr` of pointer type.
1210 // Although currently we can only provide fix-its when `Ptr` is a DRE, we
1211 // can have the matcher be general, so long as `getClaimedVarUseSites` does
1212 // things right.
1213 return stmt(isInUnspecifiedPointerContext(InnerMatcher: expr(ignoringImpCasts(
1214 InnerMatcher: unaryOperator(isPreInc(),
1215 hasUnaryOperand(InnerMatcher: declRefExpr(toSupportedVariable())))
1216 .bind(ID: UPCPreIncrementTag)))));
1217 }
1218
1219 virtual std::optional<FixItList>
1220 getFixits(const FixitStrategy &S) const override;
1221
1222 virtual const Stmt *getBaseStmt() const override { return Node; }
1223
1224 virtual DeclUseList getClaimedVarUseSites() const override {
1225 return {dyn_cast<DeclRefExpr>(Val: Node->getSubExpr())};
1226 }
1227};
1228
1229// Representing a pointer type expression of the form `Ptr += n` in an
1230// Unspecified Untyped Context (UUC):
1231class UUCAddAssignGadget : public FixableGadget {
1232private:
1233 static constexpr const char *const UUCAddAssignTag =
1234 "PointerAddAssignUnderUUC";
1235 static constexpr const char *const OffsetTag = "Offset";
1236
1237 const BinaryOperator *Node; // the `Ptr += n` node
1238 const Expr *Offset = nullptr;
1239
1240public:
1241 UUCAddAssignGadget(const MatchFinder::MatchResult &Result)
1242 : FixableGadget(Kind::UUCAddAssign),
1243 Node(Result.Nodes.getNodeAs<BinaryOperator>(ID: UUCAddAssignTag)),
1244 Offset(Result.Nodes.getNodeAs<Expr>(ID: OffsetTag)) {
1245 assert(Node != nullptr && "Expecting a non-null matching result");
1246 }
1247
1248 static bool classof(const Gadget *G) {
1249 return G->getKind() == Kind::UUCAddAssign;
1250 }
1251
1252 static Matcher matcher() {
1253 // clang-format off
1254 return stmt(isInUnspecifiedUntypedContext(InnerMatcher: expr(ignoringImpCasts(
1255 InnerMatcher: binaryOperator(hasOperatorName(Name: "+="),
1256 hasLHS(
1257 InnerMatcher: declRefExpr(
1258 hasPointerType(),
1259 toSupportedVariable())),
1260 hasRHS(InnerMatcher: expr().bind(ID: OffsetTag)))
1261 .bind(ID: UUCAddAssignTag)))));
1262 // clang-format on
1263 }
1264
1265 virtual std::optional<FixItList>
1266 getFixits(const FixitStrategy &S) const override;
1267
1268 virtual const Stmt *getBaseStmt() const override { return Node; }
1269
1270 virtual DeclUseList getClaimedVarUseSites() const override {
1271 return {dyn_cast<DeclRefExpr>(Val: Node->getLHS())};
1272 }
1273};
1274
1275// Representing a fixable expression of the form `*(ptr + 123)` or `*(123 +
1276// ptr)`:
1277class DerefSimplePtrArithFixableGadget : public FixableGadget {
1278 static constexpr const char *const BaseDeclRefExprTag = "BaseDRE";
1279 static constexpr const char *const DerefOpTag = "DerefOp";
1280 static constexpr const char *const AddOpTag = "AddOp";
1281 static constexpr const char *const OffsetTag = "Offset";
1282
1283 const DeclRefExpr *BaseDeclRefExpr = nullptr;
1284 const UnaryOperator *DerefOp = nullptr;
1285 const BinaryOperator *AddOp = nullptr;
1286 const IntegerLiteral *Offset = nullptr;
1287
1288public:
1289 DerefSimplePtrArithFixableGadget(const MatchFinder::MatchResult &Result)
1290 : FixableGadget(Kind::DerefSimplePtrArithFixable),
1291 BaseDeclRefExpr(
1292 Result.Nodes.getNodeAs<DeclRefExpr>(ID: BaseDeclRefExprTag)),
1293 DerefOp(Result.Nodes.getNodeAs<UnaryOperator>(ID: DerefOpTag)),
1294 AddOp(Result.Nodes.getNodeAs<BinaryOperator>(ID: AddOpTag)),
1295 Offset(Result.Nodes.getNodeAs<IntegerLiteral>(ID: OffsetTag)) {}
1296
1297 static Matcher matcher() {
1298 // clang-format off
1299 auto ThePtr = expr(hasPointerType(),
1300 ignoringImpCasts(InnerMatcher: declRefExpr(toSupportedVariable()).
1301 bind(ID: BaseDeclRefExprTag)));
1302 auto PlusOverPtrAndInteger = expr(anyOf(
1303 binaryOperator(hasOperatorName(Name: "+"), hasLHS(InnerMatcher: ThePtr),
1304 hasRHS(InnerMatcher: integerLiteral().bind(ID: OffsetTag)))
1305 .bind(ID: AddOpTag),
1306 binaryOperator(hasOperatorName(Name: "+"), hasRHS(InnerMatcher: ThePtr),
1307 hasLHS(InnerMatcher: integerLiteral().bind(ID: OffsetTag)))
1308 .bind(ID: AddOpTag)));
1309 return isInUnspecifiedLvalueContext(innerMatcher: unaryOperator(
1310 hasOperatorName(Name: "*"),
1311 hasUnaryOperand(InnerMatcher: ignoringParens(InnerMatcher: PlusOverPtrAndInteger)))
1312 .bind(ID: DerefOpTag));
1313 // clang-format on
1314 }
1315
1316 virtual std::optional<FixItList>
1317 getFixits(const FixitStrategy &s) const final;
1318
1319 // TODO remove this method from FixableGadget interface
1320 virtual const Stmt *getBaseStmt() const final { return nullptr; }
1321
1322 virtual DeclUseList getClaimedVarUseSites() const final {
1323 return {BaseDeclRefExpr};
1324 }
1325};
1326
1327/// Scan the function and return a list of gadgets found with provided kits.
1328static std::tuple<FixableGadgetList, WarningGadgetList, DeclUseTracker>
1329findGadgets(const Decl *D, const UnsafeBufferUsageHandler &Handler,
1330 bool EmitSuggestions) {
1331
1332 struct GadgetFinderCallback : MatchFinder::MatchCallback {
1333 FixableGadgetList FixableGadgets;
1334 WarningGadgetList WarningGadgets;
1335 DeclUseTracker Tracker;
1336
1337 void run(const MatchFinder::MatchResult &Result) override {
1338 // In debug mode, assert that we've found exactly one gadget.
1339 // This helps us avoid conflicts in .bind() tags.
1340#if NDEBUG
1341#define NEXT return
1342#else
1343 [[maybe_unused]] int numFound = 0;
1344#define NEXT ++numFound
1345#endif
1346
1347 if (const auto *DRE = Result.Nodes.getNodeAs<DeclRefExpr>(ID: "any_dre")) {
1348 Tracker.discoverUse(DRE);
1349 NEXT;
1350 }
1351
1352 if (const auto *DS = Result.Nodes.getNodeAs<DeclStmt>(ID: "any_ds")) {
1353 Tracker.discoverDecl(DS);
1354 NEXT;
1355 }
1356
1357 // Figure out which matcher we've found, and call the appropriate
1358 // subclass constructor.
1359 // FIXME: Can we do this more logarithmically?
1360#define FIXABLE_GADGET(name) \
1361 if (Result.Nodes.getNodeAs<Stmt>(#name)) { \
1362 FixableGadgets.push_back(std::make_unique<name##Gadget>(Result)); \
1363 NEXT; \
1364 }
1365#include "clang/Analysis/Analyses/UnsafeBufferUsageGadgets.def"
1366#define WARNING_GADGET(name) \
1367 if (Result.Nodes.getNodeAs<Stmt>(#name)) { \
1368 WarningGadgets.push_back(std::make_unique<name##Gadget>(Result)); \
1369 NEXT; \
1370 }
1371#include "clang/Analysis/Analyses/UnsafeBufferUsageGadgets.def"
1372
1373 assert(numFound >= 1 && "Gadgets not found in match result!");
1374 assert(numFound <= 1 && "Conflicting bind tags in gadgets!");
1375 }
1376 };
1377
1378 MatchFinder M;
1379 GadgetFinderCallback CB;
1380
1381 // clang-format off
1382 M.addMatcher(
1383 NodeMatch: stmt(
1384 forEachDescendantEvaluatedStmt(innerMatcher: stmt(anyOf(
1385 // Add Gadget::matcher() for every gadget in the registry.
1386#define WARNING_GADGET(x) \
1387 allOf(x ## Gadget::matcher().bind(#x), \
1388 notInSafeBufferOptOut(&Handler)),
1389#define WARNING_CONTAINER_GADGET(x) \
1390 allOf(x ## Gadget::matcher().bind(#x), \
1391 notInSafeBufferOptOut(&Handler), \
1392 unless(ignoreUnsafeBufferInContainer(&Handler))),
1393#include "clang/Analysis/Analyses/UnsafeBufferUsageGadgets.def"
1394 // Avoid a hanging comma.
1395 unless(stmt())
1396 )))
1397 ),
1398 Action: &CB
1399 );
1400 // clang-format on
1401
1402 if (EmitSuggestions) {
1403 // clang-format off
1404 M.addMatcher(
1405 NodeMatch: stmt(
1406 forEachDescendantStmt(innerMatcher: stmt(eachOf(
1407#define FIXABLE_GADGET(x) \
1408 x ## Gadget::matcher().bind(#x),
1409#include "clang/Analysis/Analyses/UnsafeBufferUsageGadgets.def"
1410 // In parallel, match all DeclRefExprs so that to find out
1411 // whether there are any uncovered by gadgets.
1412 declRefExpr(anyOf(hasPointerType(), hasArrayType()),
1413 to(InnerMatcher: anyOf(varDecl(), bindingDecl()))).bind(ID: "any_dre"),
1414 // Also match DeclStmts because we'll need them when fixing
1415 // their underlying VarDecls that otherwise don't have
1416 // any backreferences to DeclStmts.
1417 declStmt().bind(ID: "any_ds")
1418 )))
1419 ),
1420 Action: &CB
1421 );
1422 // clang-format on
1423 }
1424
1425 M.match(Node: *D->getBody(), Context&: D->getASTContext());
1426 return {std::move(CB.FixableGadgets), std::move(CB.WarningGadgets),
1427 std::move(CB.Tracker)};
1428}
1429
1430// Compares AST nodes by source locations.
1431template <typename NodeTy> struct CompareNode {
1432 bool operator()(const NodeTy *N1, const NodeTy *N2) const {
1433 return N1->getBeginLoc().getRawEncoding() <
1434 N2->getBeginLoc().getRawEncoding();
1435 }
1436};
1437
1438struct WarningGadgetSets {
1439 std::map<const VarDecl *, std::set<const WarningGadget *>,
1440 // To keep keys sorted by their locations in the map so that the
1441 // order is deterministic:
1442 CompareNode<VarDecl>>
1443 byVar;
1444 // These Gadgets are not related to pointer variables (e. g. temporaries).
1445 llvm::SmallVector<const WarningGadget *, 16> noVar;
1446};
1447
1448static WarningGadgetSets
1449groupWarningGadgetsByVar(const WarningGadgetList &AllUnsafeOperations) {
1450 WarningGadgetSets result;
1451 // If some gadgets cover more than one
1452 // variable, they'll appear more than once in the map.
1453 for (auto &G : AllUnsafeOperations) {
1454 DeclUseList ClaimedVarUseSites = G->getClaimedVarUseSites();
1455
1456 bool AssociatedWithVarDecl = false;
1457 for (const DeclRefExpr *DRE : ClaimedVarUseSites) {
1458 if (const auto *VD = dyn_cast<VarDecl>(Val: DRE->getDecl())) {
1459 result.byVar[VD].insert(x: G.get());
1460 AssociatedWithVarDecl = true;
1461 }
1462 }
1463
1464 if (!AssociatedWithVarDecl) {
1465 result.noVar.push_back(Elt: G.get());
1466 continue;
1467 }
1468 }
1469 return result;
1470}
1471
1472struct FixableGadgetSets {
1473 std::map<const VarDecl *, std::set<const FixableGadget *>,
1474 // To keep keys sorted by their locations in the map so that the
1475 // order is deterministic:
1476 CompareNode<VarDecl>>
1477 byVar;
1478};
1479
1480static FixableGadgetSets
1481groupFixablesByVar(FixableGadgetList &&AllFixableOperations) {
1482 FixableGadgetSets FixablesForUnsafeVars;
1483 for (auto &F : AllFixableOperations) {
1484 DeclUseList DREs = F->getClaimedVarUseSites();
1485
1486 for (const DeclRefExpr *DRE : DREs) {
1487 if (const auto *VD = dyn_cast<VarDecl>(Val: DRE->getDecl())) {
1488 FixablesForUnsafeVars.byVar[VD].insert(x: F.get());
1489 }
1490 }
1491 }
1492 return FixablesForUnsafeVars;
1493}
1494
1495bool clang::internal::anyConflict(const SmallVectorImpl<FixItHint> &FixIts,
1496 const SourceManager &SM) {
1497 // A simple interval overlap detection algorithm. Sorts all ranges by their
1498 // begin location then finds the first overlap in one pass.
1499 std::vector<const FixItHint *> All; // a copy of `FixIts`
1500
1501 for (const FixItHint &H : FixIts)
1502 All.push_back(x: &H);
1503 std::sort(first: All.begin(), last: All.end(),
1504 comp: [&SM](const FixItHint *H1, const FixItHint *H2) {
1505 return SM.isBeforeInTranslationUnit(LHS: H1->RemoveRange.getBegin(),
1506 RHS: H2->RemoveRange.getBegin());
1507 });
1508
1509 const FixItHint *CurrHint = nullptr;
1510
1511 for (const FixItHint *Hint : All) {
1512 if (!CurrHint ||
1513 SM.isBeforeInTranslationUnit(LHS: CurrHint->RemoveRange.getEnd(),
1514 RHS: Hint->RemoveRange.getBegin())) {
1515 // Either to initialize `CurrHint` or `CurrHint` does not
1516 // overlap with `Hint`:
1517 CurrHint = Hint;
1518 } else
1519 // In case `Hint` overlaps the `CurrHint`, we found at least one
1520 // conflict:
1521 return true;
1522 }
1523 return false;
1524}
1525
1526std::optional<FixItList>
1527PtrToPtrAssignmentGadget::getFixits(const FixitStrategy &S) const {
1528 const auto *LeftVD = cast<VarDecl>(Val: PtrLHS->getDecl());
1529 const auto *RightVD = cast<VarDecl>(Val: PtrRHS->getDecl());
1530 switch (S.lookup(VD: LeftVD)) {
1531 case FixitStrategy::Kind::Span:
1532 if (S.lookup(VD: RightVD) == FixitStrategy::Kind::Span)
1533 return FixItList{};
1534 return std::nullopt;
1535 case FixitStrategy::Kind::Wontfix:
1536 return std::nullopt;
1537 case FixitStrategy::Kind::Iterator:
1538 case FixitStrategy::Kind::Array:
1539 return std::nullopt;
1540 case FixitStrategy::Kind::Vector:
1541 llvm_unreachable("unsupported strategies for FixableGadgets");
1542 }
1543 return std::nullopt;
1544}
1545
1546/// \returns fixit that adds .data() call after \DRE.
1547static inline std::optional<FixItList> createDataFixit(const ASTContext &Ctx,
1548 const DeclRefExpr *DRE);
1549
1550std::optional<FixItList>
1551CArrayToPtrAssignmentGadget::getFixits(const FixitStrategy &S) const {
1552 const auto *LeftVD = cast<VarDecl>(Val: PtrLHS->getDecl());
1553 const auto *RightVD = cast<VarDecl>(Val: PtrRHS->getDecl());
1554 // TLDR: Implementing fixits for non-Wontfix strategy on both LHS and RHS is
1555 // non-trivial.
1556 //
1557 // CArrayToPtrAssignmentGadget doesn't have strategy implications because
1558 // constant size array propagates its bounds. Because of that LHS and RHS are
1559 // addressed by two different fixits.
1560 //
1561 // At the same time FixitStrategy S doesn't reflect what group a fixit belongs
1562 // to and can't be generally relied on in multi-variable Fixables!
1563 //
1564 // E. g. If an instance of this gadget is fixing variable on LHS then the
1565 // variable on RHS is fixed by a different fixit and its strategy for LHS
1566 // fixit is as if Wontfix.
1567 //
1568 // The only exception is Wontfix strategy for a given variable as that is
1569 // valid for any fixit produced for the given input source code.
1570 if (S.lookup(VD: LeftVD) == FixitStrategy::Kind::Span) {
1571 if (S.lookup(VD: RightVD) == FixitStrategy::Kind::Wontfix) {
1572 return FixItList{};
1573 }
1574 } else if (S.lookup(VD: LeftVD) == FixitStrategy::Kind::Wontfix) {
1575 if (S.lookup(VD: RightVD) == FixitStrategy::Kind::Array) {
1576 return createDataFixit(RightVD->getASTContext(), PtrRHS);
1577 }
1578 }
1579 return std::nullopt;
1580}
1581
1582std::optional<FixItList>
1583PointerInitGadget::getFixits(const FixitStrategy &S) const {
1584 const auto *LeftVD = PtrInitLHS;
1585 const auto *RightVD = cast<VarDecl>(Val: PtrInitRHS->getDecl());
1586 switch (S.lookup(VD: LeftVD)) {
1587 case FixitStrategy::Kind::Span:
1588 if (S.lookup(VD: RightVD) == FixitStrategy::Kind::Span)
1589 return FixItList{};
1590 return std::nullopt;
1591 case FixitStrategy::Kind::Wontfix:
1592 return std::nullopt;
1593 case FixitStrategy::Kind::Iterator:
1594 case FixitStrategy::Kind::Array:
1595 return std::nullopt;
1596 case FixitStrategy::Kind::Vector:
1597 llvm_unreachable("unsupported strategies for FixableGadgets");
1598 }
1599 return std::nullopt;
1600}
1601
1602static bool isNonNegativeIntegerExpr(const Expr *Expr, const VarDecl *VD,
1603 const ASTContext &Ctx) {
1604 if (auto ConstVal = Expr->getIntegerConstantExpr(Ctx)) {
1605 if (ConstVal->isNegative())
1606 return false;
1607 } else if (!Expr->getType()->isUnsignedIntegerType())
1608 return false;
1609 return true;
1610}
1611
1612std::optional<FixItList>
1613ULCArraySubscriptGadget::getFixits(const FixitStrategy &S) const {
1614 if (const auto *DRE =
1615 dyn_cast<DeclRefExpr>(Val: Node->getBase()->IgnoreImpCasts()))
1616 if (const auto *VD = dyn_cast<VarDecl>(Val: DRE->getDecl())) {
1617 switch (S.lookup(VD)) {
1618 case FixitStrategy::Kind::Span: {
1619
1620 // If the index has a negative constant value, we give up as no valid
1621 // fix-it can be generated:
1622 const ASTContext &Ctx = // FIXME: we need ASTContext to be passed in!
1623 VD->getASTContext();
1624 if (!isNonNegativeIntegerExpr(Expr: Node->getIdx(), VD, Ctx))
1625 return std::nullopt;
1626 // no-op is a good fix-it, otherwise
1627 return FixItList{};
1628 }
1629 case FixitStrategy::Kind::Array:
1630 return FixItList{};
1631 case FixitStrategy::Kind::Wontfix:
1632 case FixitStrategy::Kind::Iterator:
1633 case FixitStrategy::Kind::Vector:
1634 llvm_unreachable("unsupported strategies for FixableGadgets");
1635 }
1636 }
1637 return std::nullopt;
1638}
1639
1640static std::optional<FixItList> // forward declaration
1641fixUPCAddressofArraySubscriptWithSpan(const UnaryOperator *Node);
1642
1643std::optional<FixItList>
1644UPCAddressofArraySubscriptGadget::getFixits(const FixitStrategy &S) const {
1645 auto DREs = getClaimedVarUseSites();
1646 const auto *VD = cast<VarDecl>(Val: DREs.front()->getDecl());
1647
1648 switch (S.lookup(VD)) {
1649 case FixitStrategy::Kind::Span:
1650 return fixUPCAddressofArraySubscriptWithSpan(Node);
1651 case FixitStrategy::Kind::Wontfix:
1652 case FixitStrategy::Kind::Iterator:
1653 case FixitStrategy::Kind::Array:
1654 return std::nullopt;
1655 case FixitStrategy::Kind::Vector:
1656 llvm_unreachable("unsupported strategies for FixableGadgets");
1657 }
1658 return std::nullopt; // something went wrong, no fix-it
1659}
1660
1661// FIXME: this function should be customizable through format
1662static StringRef getEndOfLine() {
1663 static const char *const EOL = "\n";
1664 return EOL;
1665}
1666
1667// Returns the text indicating that the user needs to provide input there:
1668std::string getUserFillPlaceHolder(StringRef HintTextToUser = "placeholder") {
1669 std::string s = std::string("<# ");
1670 s += HintTextToUser;
1671 s += " #>";
1672 return s;
1673}
1674
1675// Return the source location of the last character of the AST `Node`.
1676template <typename NodeTy>
1677static std::optional<SourceLocation>
1678getEndCharLoc(const NodeTy *Node, const SourceManager &SM,
1679 const LangOptions &LangOpts) {
1680 unsigned TkLen = Lexer::MeasureTokenLength(Loc: Node->getEndLoc(), SM, LangOpts);
1681 SourceLocation Loc = Node->getEndLoc().getLocWithOffset(TkLen - 1);
1682
1683 if (Loc.isValid())
1684 return Loc;
1685
1686 return std::nullopt;
1687}
1688
1689// Return the source location just past the last character of the AST `Node`.
1690template <typename NodeTy>
1691static std::optional<SourceLocation> getPastLoc(const NodeTy *Node,
1692 const SourceManager &SM,
1693 const LangOptions &LangOpts) {
1694 SourceLocation Loc =
1695 Lexer::getLocForEndOfToken(Loc: Node->getEndLoc(), Offset: 0, SM, LangOpts);
1696 if (Loc.isValid())
1697 return Loc;
1698 return std::nullopt;
1699}
1700
1701// Return text representation of an `Expr`.
1702static std::optional<StringRef> getExprText(const Expr *E,
1703 const SourceManager &SM,
1704 const LangOptions &LangOpts) {
1705 std::optional<SourceLocation> LastCharLoc = getPastLoc(Node: E, SM, LangOpts);
1706
1707 if (LastCharLoc)
1708 return Lexer::getSourceText(
1709 Range: CharSourceRange::getCharRange(E->getBeginLoc(), *LastCharLoc), SM,
1710 LangOpts);
1711
1712 return std::nullopt;
1713}
1714
1715// Returns the literal text in `SourceRange SR`, if `SR` is a valid range.
1716static std::optional<StringRef> getRangeText(SourceRange SR,
1717 const SourceManager &SM,
1718 const LangOptions &LangOpts) {
1719 bool Invalid = false;
1720 CharSourceRange CSR = CharSourceRange::getCharRange(R: SR);
1721 StringRef Text = Lexer::getSourceText(Range: CSR, SM, LangOpts, Invalid: &Invalid);
1722
1723 if (!Invalid)
1724 return Text;
1725 return std::nullopt;
1726}
1727
1728// Returns the begin location of the identifier of the given variable
1729// declaration.
1730static SourceLocation getVarDeclIdentifierLoc(const VarDecl *VD) {
1731 // According to the implementation of `VarDecl`, `VD->getLocation()` actually
1732 // returns the begin location of the identifier of the declaration:
1733 return VD->getLocation();
1734}
1735
1736// Returns the literal text of the identifier of the given variable declaration.
1737static std::optional<StringRef>
1738getVarDeclIdentifierText(const VarDecl *VD, const SourceManager &SM,
1739 const LangOptions &LangOpts) {
1740 SourceLocation ParmIdentBeginLoc = getVarDeclIdentifierLoc(VD);
1741 SourceLocation ParmIdentEndLoc =
1742 Lexer::getLocForEndOfToken(Loc: ParmIdentBeginLoc, Offset: 0, SM, LangOpts);
1743
1744 if (ParmIdentEndLoc.isMacroID() &&
1745 !Lexer::isAtEndOfMacroExpansion(loc: ParmIdentEndLoc, SM, LangOpts))
1746 return std::nullopt;
1747 return getRangeText(SR: {ParmIdentBeginLoc, ParmIdentEndLoc}, SM, LangOpts);
1748}
1749
1750// We cannot fix a variable declaration if it has some other specifiers than the
1751// type specifier. Because the source ranges of those specifiers could overlap
1752// with the source range that is being replaced using fix-its. Especially when
1753// we often cannot obtain accurate source ranges of cv-qualified type
1754// specifiers.
1755// FIXME: also deal with type attributes
1756static bool hasUnsupportedSpecifiers(const VarDecl *VD,
1757 const SourceManager &SM) {
1758 // AttrRangeOverlapping: true if at least one attribute of `VD` overlaps the
1759 // source range of `VD`:
1760 bool AttrRangeOverlapping = llvm::any_of(VD->attrs(), [&](Attr *At) -> bool {
1761 return !(SM.isBeforeInTranslationUnit(LHS: At->getRange().getEnd(),
1762 RHS: VD->getBeginLoc())) &&
1763 !(SM.isBeforeInTranslationUnit(LHS: VD->getEndLoc(),
1764 RHS: At->getRange().getBegin()));
1765 });
1766 return VD->isInlineSpecified() || VD->isConstexpr() ||
1767 VD->hasConstantInitialization() || !VD->hasLocalStorage() ||
1768 AttrRangeOverlapping;
1769}
1770
1771// Returns the `SourceRange` of `D`. The reason why this function exists is
1772// that `D->getSourceRange()` may return a range where the end location is the
1773// starting location of the last token. The end location of the source range
1774// returned by this function is the last location of the last token.
1775static SourceRange getSourceRangeToTokenEnd(const Decl *D,
1776 const SourceManager &SM,
1777 const LangOptions &LangOpts) {
1778 SourceLocation Begin = D->getBeginLoc();
1779 SourceLocation
1780 End = // `D->getEndLoc` should always return the starting location of the
1781 // last token, so we should get the end of the token
1782 Lexer::getLocForEndOfToken(Loc: D->getEndLoc(), Offset: 0, SM, LangOpts);
1783
1784 return SourceRange(Begin, End);
1785}
1786
1787// Returns the text of the pointee type of `T` from a `VarDecl` of a pointer
1788// type. The text is obtained through from `TypeLoc`s. Since `TypeLoc` does not
1789// have source ranges of qualifiers ( The `QualifiedTypeLoc` looks hacky too me
1790// :( ), `Qualifiers` of the pointee type is returned separately through the
1791// output parameter `QualifiersToAppend`.
1792static std::optional<std::string>
1793getPointeeTypeText(const VarDecl *VD, const SourceManager &SM,
1794 const LangOptions &LangOpts,
1795 std::optional<Qualifiers> *QualifiersToAppend) {
1796 QualType Ty = VD->getType();
1797 QualType PteTy;
1798
1799 assert(Ty->isPointerType() && !Ty->isFunctionPointerType() &&
1800 "Expecting a VarDecl of type of pointer to object type");
1801 PteTy = Ty->getPointeeType();
1802
1803 TypeLoc TyLoc = VD->getTypeSourceInfo()->getTypeLoc().getUnqualifiedLoc();
1804 TypeLoc PteTyLoc;
1805
1806 // We only deal with the cases that we know `TypeLoc::getNextTypeLoc` returns
1807 // the `TypeLoc` of the pointee type:
1808 switch (TyLoc.getTypeLocClass()) {
1809 case TypeLoc::ConstantArray:
1810 case TypeLoc::IncompleteArray:
1811 case TypeLoc::VariableArray:
1812 case TypeLoc::DependentSizedArray:
1813 case TypeLoc::Decayed:
1814 assert(isa<ParmVarDecl>(VD) && "An array type shall not be treated as a "
1815 "pointer type unless it decays.");
1816 PteTyLoc = TyLoc.getNextTypeLoc();
1817 break;
1818 case TypeLoc::Pointer:
1819 PteTyLoc = TyLoc.castAs<PointerTypeLoc>().getPointeeLoc();
1820 break;
1821 default:
1822 return std::nullopt;
1823 }
1824 if (PteTyLoc.isNull())
1825 // Sometimes we cannot get a useful `TypeLoc` for the pointee type, e.g.,
1826 // when the pointer type is `auto`.
1827 return std::nullopt;
1828
1829 SourceLocation IdentLoc = getVarDeclIdentifierLoc(VD);
1830
1831 if (!(IdentLoc.isValid() && PteTyLoc.getSourceRange().isValid())) {
1832 // We are expecting these locations to be valid. But in some cases, they are
1833 // not all valid. It is a Clang bug to me and we are not responsible for
1834 // fixing it. So we will just give up for now when it happens.
1835 return std::nullopt;
1836 }
1837
1838 // Note that TypeLoc.getEndLoc() returns the begin location of the last token:
1839 SourceLocation PteEndOfTokenLoc =
1840 Lexer::getLocForEndOfToken(Loc: PteTyLoc.getEndLoc(), Offset: 0, SM, LangOpts);
1841
1842 if (!PteEndOfTokenLoc.isValid())
1843 // Sometimes we cannot get the end location of the pointee type, e.g., when
1844 // there are macros involved.
1845 return std::nullopt;
1846 if (!SM.isBeforeInTranslationUnit(LHS: PteEndOfTokenLoc, RHS: IdentLoc)) {
1847 // We only deal with the cases where the source text of the pointee type
1848 // appears on the left-hand side of the variable identifier completely,
1849 // including the following forms:
1850 // `T ident`,
1851 // `T ident[]`, where `T` is any type.
1852 // Examples of excluded cases are `T (*ident)[]` or `T ident[][n]`.
1853 return std::nullopt;
1854 }
1855 if (PteTy.hasQualifiers()) {
1856 // TypeLoc does not provide source ranges for qualifiers (it says it's
1857 // intentional but seems fishy to me), so we cannot get the full text
1858 // `PteTy` via source ranges.
1859 *QualifiersToAppend = PteTy.getQualifiers();
1860 }
1861 return getRangeText(SR: {PteTyLoc.getBeginLoc(), PteEndOfTokenLoc}, SM, LangOpts)
1862 ->str();
1863}
1864
1865// Returns the text of the name (with qualifiers) of a `FunctionDecl`.
1866static std::optional<StringRef> getFunNameText(const FunctionDecl *FD,
1867 const SourceManager &SM,
1868 const LangOptions &LangOpts) {
1869 SourceLocation BeginLoc = FD->getQualifier()
1870 ? FD->getQualifierLoc().getBeginLoc()
1871 : FD->getNameInfo().getBeginLoc();
1872 // Note that `FD->getNameInfo().getEndLoc()` returns the begin location of the
1873 // last token:
1874 SourceLocation EndLoc = Lexer::getLocForEndOfToken(
1875 Loc: FD->getNameInfo().getEndLoc(), Offset: 0, SM, LangOpts);
1876 SourceRange NameRange{BeginLoc, EndLoc};
1877
1878 return getRangeText(SR: NameRange, SM, LangOpts);
1879}
1880
1881// Returns the text representing a `std::span` type where the element type is
1882// represented by `EltTyText`.
1883//
1884// Note the optional parameter `Qualifiers`: one needs to pass qualifiers
1885// explicitly if the element type needs to be qualified.
1886static std::string
1887getSpanTypeText(StringRef EltTyText,
1888 std::optional<Qualifiers> Quals = std::nullopt) {
1889 const char *const SpanOpen = "std::span<";
1890
1891 if (Quals)
1892 return SpanOpen + EltTyText.str() + ' ' + Quals->getAsString() + '>';
1893 return SpanOpen + EltTyText.str() + '>';
1894}
1895
1896std::optional<FixItList>
1897DerefSimplePtrArithFixableGadget::getFixits(const FixitStrategy &s) const {
1898 const VarDecl *VD = dyn_cast<VarDecl>(Val: BaseDeclRefExpr->getDecl());
1899
1900 if (VD && s.lookup(VD) == FixitStrategy::Kind::Span) {
1901 ASTContext &Ctx = VD->getASTContext();
1902 // std::span can't represent elements before its begin()
1903 if (auto ConstVal = Offset->getIntegerConstantExpr(Ctx))
1904 if (ConstVal->isNegative())
1905 return std::nullopt;
1906
1907 // note that the expr may (oddly) has multiple layers of parens
1908 // example:
1909 // *((..(pointer + 123)..))
1910 // goal:
1911 // pointer[123]
1912 // Fix-It:
1913 // remove '*('
1914 // replace ' + ' with '['
1915 // replace ')' with ']'
1916
1917 // example:
1918 // *((..(123 + pointer)..))
1919 // goal:
1920 // 123[pointer]
1921 // Fix-It:
1922 // remove '*('
1923 // replace ' + ' with '['
1924 // replace ')' with ']'
1925
1926 const Expr *LHS = AddOp->getLHS(), *RHS = AddOp->getRHS();
1927 const SourceManager &SM = Ctx.getSourceManager();
1928 const LangOptions &LangOpts = Ctx.getLangOpts();
1929 CharSourceRange StarWithTrailWhitespace =
1930 clang::CharSourceRange::getCharRange(DerefOp->getOperatorLoc(),
1931 LHS->getBeginLoc());
1932
1933 std::optional<SourceLocation> LHSLocation = getPastLoc(Node: LHS, SM, LangOpts);
1934 if (!LHSLocation)
1935 return std::nullopt;
1936
1937 CharSourceRange PlusWithSurroundingWhitespace =
1938 clang::CharSourceRange::getCharRange(*LHSLocation, RHS->getBeginLoc());
1939
1940 std::optional<SourceLocation> AddOpLocation =
1941 getPastLoc(Node: AddOp, SM, LangOpts);
1942 std::optional<SourceLocation> DerefOpLocation =
1943 getPastLoc(Node: DerefOp, SM, LangOpts);
1944
1945 if (!AddOpLocation || !DerefOpLocation)
1946 return std::nullopt;
1947
1948 CharSourceRange ClosingParenWithPrecWhitespace =
1949 clang::CharSourceRange::getCharRange(B: *AddOpLocation, E: *DerefOpLocation);
1950
1951 return FixItList{
1952 {FixItHint::CreateRemoval(RemoveRange: StarWithTrailWhitespace),
1953 FixItHint::CreateReplacement(RemoveRange: PlusWithSurroundingWhitespace, Code: "["),
1954 FixItHint::CreateReplacement(RemoveRange: ClosingParenWithPrecWhitespace, Code: "]")}};
1955 }
1956 return std::nullopt; // something wrong or unsupported, give up
1957}
1958
1959std::optional<FixItList>
1960PointerDereferenceGadget::getFixits(const FixitStrategy &S) const {
1961 const VarDecl *VD = cast<VarDecl>(Val: BaseDeclRefExpr->getDecl());
1962 switch (S.lookup(VD)) {
1963 case FixitStrategy::Kind::Span: {
1964 ASTContext &Ctx = VD->getASTContext();
1965 SourceManager &SM = Ctx.getSourceManager();
1966 // Required changes: *(ptr); => (ptr[0]); and *ptr; => ptr[0]
1967 // Deletes the *operand
1968 CharSourceRange derefRange = clang::CharSourceRange::getCharRange(
1969 B: Op->getBeginLoc(), E: Op->getBeginLoc().getLocWithOffset(Offset: 1));
1970 // Inserts the [0]
1971 if (auto LocPastOperand =
1972 getPastLoc(BaseDeclRefExpr, SM, Ctx.getLangOpts())) {
1973 return FixItList{{FixItHint::CreateRemoval(RemoveRange: derefRange),
1974 FixItHint::CreateInsertion(InsertionLoc: *LocPastOperand, Code: "[0]")}};
1975 }
1976 break;
1977 }
1978 case FixitStrategy::Kind::Iterator:
1979 case FixitStrategy::Kind::Array:
1980 return std::nullopt;
1981 case FixitStrategy::Kind::Vector:
1982 llvm_unreachable("FixitStrategy not implemented yet!");
1983 case FixitStrategy::Kind::Wontfix:
1984 llvm_unreachable("Invalid strategy!");
1985 }
1986
1987 return std::nullopt;
1988}
1989
1990static inline std::optional<FixItList> createDataFixit(const ASTContext &Ctx,
1991 const DeclRefExpr *DRE) {
1992 const SourceManager &SM = Ctx.getSourceManager();
1993 // Inserts the .data() after the DRE
1994 std::optional<SourceLocation> EndOfOperand =
1995 getPastLoc(Node: DRE, SM, LangOpts: Ctx.getLangOpts());
1996
1997 if (EndOfOperand)
1998 return FixItList{{FixItHint::CreateInsertion(InsertionLoc: *EndOfOperand, Code: ".data()")}};
1999
2000 return std::nullopt;
2001}
2002
2003// Generates fix-its replacing an expression of the form UPC(DRE) with
2004// `DRE.data()`
2005std::optional<FixItList>
2006UPCStandalonePointerGadget::getFixits(const FixitStrategy &S) const {
2007 const auto VD = cast<VarDecl>(Val: Node->getDecl());
2008 switch (S.lookup(VD)) {
2009 case FixitStrategy::Kind::Array:
2010 case FixitStrategy::Kind::Span: {
2011 return createDataFixit(VD->getASTContext(), Node);
2012 // FIXME: Points inside a macro expansion.
2013 break;
2014 }
2015 case FixitStrategy::Kind::Wontfix:
2016 case FixitStrategy::Kind::Iterator:
2017 return std::nullopt;
2018 case FixitStrategy::Kind::Vector:
2019 llvm_unreachable("unsupported strategies for FixableGadgets");
2020 }
2021
2022 return std::nullopt;
2023}
2024
2025// Generates fix-its replacing an expression of the form `&DRE[e]` with
2026// `&DRE.data()[e]`:
2027static std::optional<FixItList>
2028fixUPCAddressofArraySubscriptWithSpan(const UnaryOperator *Node) {
2029 const auto *ArraySub = cast<ArraySubscriptExpr>(Val: Node->getSubExpr());
2030 const auto *DRE = cast<DeclRefExpr>(Val: ArraySub->getBase()->IgnoreImpCasts());
2031 // FIXME: this `getASTContext` call is costly, we should pass the
2032 // ASTContext in:
2033 const ASTContext &Ctx = DRE->getDecl()->getASTContext();
2034 const Expr *Idx = ArraySub->getIdx();
2035 const SourceManager &SM = Ctx.getSourceManager();
2036 const LangOptions &LangOpts = Ctx.getLangOpts();
2037 std::stringstream SS;
2038 bool IdxIsLitZero = false;
2039
2040 if (auto ICE = Idx->getIntegerConstantExpr(Ctx))
2041 if ((*ICE).isZero())
2042 IdxIsLitZero = true;
2043 std::optional<StringRef> DreString = getExprText(DRE, SM, LangOpts);
2044 if (!DreString)
2045 return std::nullopt;
2046
2047 if (IdxIsLitZero) {
2048 // If the index is literal zero, we produce the most concise fix-it:
2049 SS << (*DreString).str() << ".data()";
2050 } else {
2051 std::optional<StringRef> IndexString = getExprText(E: Idx, SM, LangOpts);
2052 if (!IndexString)
2053 return std::nullopt;
2054
2055 SS << "&" << (*DreString).str() << ".data()"
2056 << "[" << (*IndexString).str() << "]";
2057 }
2058 return FixItList{
2059 FixItHint::CreateReplacement(Node->getSourceRange(), SS.str())};
2060}
2061
2062std::optional<FixItList>
2063UUCAddAssignGadget::getFixits(const FixitStrategy &S) const {
2064 DeclUseList DREs = getClaimedVarUseSites();
2065
2066 if (DREs.size() != 1)
2067 return std::nullopt; // In cases of `Ptr += n` where `Ptr` is not a DRE, we
2068 // give up
2069 if (const VarDecl *VD = dyn_cast<VarDecl>(Val: DREs.front()->getDecl())) {
2070 if (S.lookup(VD) == FixitStrategy::Kind::Span) {
2071 FixItList Fixes;
2072
2073 const Stmt *AddAssignNode = getBaseStmt();
2074 StringRef varName = VD->getName();
2075 const ASTContext &Ctx = VD->getASTContext();
2076
2077 if (!isNonNegativeIntegerExpr(Expr: Offset, VD, Ctx))
2078 return std::nullopt;
2079
2080 // To transform UUC(p += n) to UUC(p = p.subspan(..)):
2081 bool NotParenExpr =
2082 (Offset->IgnoreParens()->getBeginLoc() == Offset->getBeginLoc());
2083 std::string SS = varName.str() + " = " + varName.str() + ".subspan";
2084 if (NotParenExpr)
2085 SS += "(";
2086
2087 std::optional<SourceLocation> AddAssignLocation = getEndCharLoc(
2088 Node: AddAssignNode, SM: Ctx.getSourceManager(), LangOpts: Ctx.getLangOpts());
2089 if (!AddAssignLocation)
2090 return std::nullopt;
2091
2092 Fixes.push_back(Elt: FixItHint::CreateReplacement(
2093 RemoveRange: SourceRange(AddAssignNode->getBeginLoc(), Node->getOperatorLoc()),
2094 Code: SS));
2095 if (NotParenExpr)
2096 Fixes.push_back(FixItHint::CreateInsertion(
2097 InsertionLoc: Offset->getEndLoc().getLocWithOffset(1), Code: ")"));
2098 return Fixes;
2099 }
2100 }
2101 return std::nullopt; // Not in the cases that we can handle for now, give up.
2102}
2103
2104std::optional<FixItList>
2105UPCPreIncrementGadget::getFixits(const FixitStrategy &S) const {
2106 DeclUseList DREs = getClaimedVarUseSites();
2107
2108 if (DREs.size() != 1)
2109 return std::nullopt; // In cases of `++Ptr` where `Ptr` is not a DRE, we
2110 // give up
2111 if (const VarDecl *VD = dyn_cast<VarDecl>(Val: DREs.front()->getDecl())) {
2112 if (S.lookup(VD) == FixitStrategy::Kind::Span) {
2113 FixItList Fixes;
2114 std::stringstream SS;
2115 const Stmt *PreIncNode = getBaseStmt();
2116 StringRef varName = VD->getName();
2117 const ASTContext &Ctx = VD->getASTContext();
2118
2119 // To transform UPC(++p) to UPC((p = p.subspan(1)).data()):
2120 SS << "(" << varName.data() << " = " << varName.data()
2121 << ".subspan(1)).data()";
2122 std::optional<SourceLocation> PreIncLocation =
2123 getEndCharLoc(Node: PreIncNode, SM: Ctx.getSourceManager(), LangOpts: Ctx.getLangOpts());
2124 if (!PreIncLocation)
2125 return std::nullopt;
2126
2127 Fixes.push_back(Elt: FixItHint::CreateReplacement(
2128 RemoveRange: SourceRange(PreIncNode->getBeginLoc(), *PreIncLocation), Code: SS.str()));
2129 return Fixes;
2130 }
2131 }
2132 return std::nullopt; // Not in the cases that we can handle for now, give up.
2133}
2134
2135// For a non-null initializer `Init` of `T *` type, this function returns
2136// `FixItHint`s producing a list initializer `{Init, S}` as a part of a fix-it
2137// to output stream.
2138// In many cases, this function cannot figure out the actual extent `S`. It
2139// then will use a place holder to replace `S` to ask users to fill `S` in. The
2140// initializer shall be used to initialize a variable of type `std::span<T>`.
2141// In some cases (e. g. constant size array) the initializer should remain
2142// unchanged and the function returns empty list. In case the function can't
2143// provide the right fixit it will return nullopt.
2144//
2145// FIXME: Support multi-level pointers
2146//
2147// Parameters:
2148// `Init` a pointer to the initializer expression
2149// `Ctx` a reference to the ASTContext
2150static std::optional<FixItList>
2151FixVarInitializerWithSpan(const Expr *Init, ASTContext &Ctx,
2152 const StringRef UserFillPlaceHolder) {
2153 const SourceManager &SM = Ctx.getSourceManager();
2154 const LangOptions &LangOpts = Ctx.getLangOpts();
2155
2156 // If `Init` has a constant value that is (or equivalent to) a
2157 // NULL pointer, we use the default constructor to initialize the span
2158 // object, i.e., a `std:span` variable declaration with no initializer.
2159 // So the fix-it is just to remove the initializer.
2160 if (Init->isNullPointerConstant(
2161 Ctx,
2162 // FIXME: Why does this function not ask for `const ASTContext
2163 // &`? It should. Maybe worth an NFC patch later.
2164 NPC: Expr::NullPointerConstantValueDependence::
2165 NPC_ValueDependentIsNotNull)) {
2166 std::optional<SourceLocation> InitLocation =
2167 getEndCharLoc(Node: Init, SM, LangOpts);
2168 if (!InitLocation)
2169 return std::nullopt;
2170
2171 SourceRange SR(Init->getBeginLoc(), *InitLocation);
2172
2173 return FixItList{FixItHint::CreateRemoval(RemoveRange: SR)};
2174 }
2175
2176 FixItList FixIts{};
2177 std::string ExtentText = UserFillPlaceHolder.data();
2178 StringRef One = "1";
2179
2180 // Insert `{` before `Init`:
2181 FixIts.push_back(FixItHint::CreateInsertion(InsertionLoc: Init->getBeginLoc(), Code: "{"));
2182 // Try to get the data extent. Break into different cases:
2183 if (auto CxxNew = dyn_cast<CXXNewExpr>(Val: Init->IgnoreImpCasts())) {
2184 // In cases `Init` is `new T[n]` and there is no explicit cast over
2185 // `Init`, we know that `Init` must evaluates to a pointer to `n` objects
2186 // of `T`. So the extent is `n` unless `n` has side effects. Similar but
2187 // simpler for the case where `Init` is `new T`.
2188 if (const Expr *Ext = CxxNew->getArraySize().value_or(u: nullptr)) {
2189 if (!Ext->HasSideEffects(Ctx)) {
2190 std::optional<StringRef> ExtentString = getExprText(E: Ext, SM, LangOpts);
2191 if (!ExtentString)
2192 return std::nullopt;
2193 ExtentText = *ExtentString;
2194 }
2195 } else if (!CxxNew->isArray())
2196 // Although the initializer is not allocating a buffer, the pointer
2197 // variable could still be used in buffer access operations.
2198 ExtentText = One;
2199 } else if (Ctx.getAsConstantArrayType(T: Init->IgnoreImpCasts()->getType())) {
2200 // std::span has a single parameter constructor for initialization with
2201 // constant size array. The size is auto-deduced as the constructor is a
2202 // function template. The correct fixit is empty - no changes should happen.
2203 return FixItList{};
2204 } else {
2205 // In cases `Init` is of the form `&Var` after stripping of implicit
2206 // casts, where `&` is the built-in operator, the extent is 1.
2207 if (auto AddrOfExpr = dyn_cast<UnaryOperator>(Val: Init->IgnoreImpCasts()))
2208 if (AddrOfExpr->getOpcode() == UnaryOperatorKind::UO_AddrOf &&
2209 isa_and_present<DeclRefExpr>(Val: AddrOfExpr->getSubExpr()))
2210 ExtentText = One;
2211 // TODO: we can handle more cases, e.g., `&a[0]`, `&a`, `std::addressof`,
2212 // and explicit casting, etc. etc.
2213 }
2214
2215 SmallString<32> StrBuffer{};
2216 std::optional<SourceLocation> LocPassInit = getPastLoc(Node: Init, SM, LangOpts);
2217
2218 if (!LocPassInit)
2219 return std::nullopt;
2220
2221 StrBuffer.append(RHS: ", ");
2222 StrBuffer.append(RHS: ExtentText);
2223 StrBuffer.append(RHS: "}");
2224 FixIts.push_back(Elt: FixItHint::CreateInsertion(InsertionLoc: *LocPassInit, Code: StrBuffer.str()));
2225 return FixIts;
2226}
2227
2228#ifndef NDEBUG
2229#define DEBUG_NOTE_DECL_FAIL(D, Msg) \
2230 Handler.addDebugNoteForVar((D), (D)->getBeginLoc(), \
2231 "failed to produce fixit for declaration '" + \
2232 (D)->getNameAsString() + "'" + (Msg))
2233#else
2234#define DEBUG_NOTE_DECL_FAIL(D, Msg)
2235#endif
2236
2237// For the given variable declaration with a pointer-to-T type, returns the text
2238// `std::span<T>`. If it is unable to generate the text, returns
2239// `std::nullopt`.
2240static std::optional<std::string>
2241createSpanTypeForVarDecl(const VarDecl *VD, const ASTContext &Ctx) {
2242 assert(VD->getType()->isPointerType());
2243
2244 std::optional<Qualifiers> PteTyQualifiers = std::nullopt;
2245 std::optional<std::string> PteTyText = getPointeeTypeText(
2246 VD, SM: Ctx.getSourceManager(), LangOpts: Ctx.getLangOpts(), QualifiersToAppend: &PteTyQualifiers);
2247
2248 if (!PteTyText)
2249 return std::nullopt;
2250
2251 std::string SpanTyText = "std::span<";
2252
2253 SpanTyText.append(str: *PteTyText);
2254 // Append qualifiers to span element type if any:
2255 if (PteTyQualifiers) {
2256 SpanTyText.append(s: " ");
2257 SpanTyText.append(str: PteTyQualifiers->getAsString());
2258 }
2259 SpanTyText.append(s: ">");
2260 return SpanTyText;
2261}
2262
2263// For a `VarDecl` of the form `T * var (= Init)?`, this
2264// function generates fix-its that
2265// 1) replace `T * var` with `std::span<T> var`; and
2266// 2) change `Init` accordingly to a span constructor, if it exists.
2267//
2268// FIXME: support Multi-level pointers
2269//
2270// Parameters:
2271// `D` a pointer the variable declaration node
2272// `Ctx` a reference to the ASTContext
2273// `UserFillPlaceHolder` the user-input placeholder text
2274// Returns:
2275// the non-empty fix-it list, if fix-its are successfuly generated; empty
2276// list otherwise.
2277static FixItList fixLocalVarDeclWithSpan(const VarDecl *D, ASTContext &Ctx,
2278 const StringRef UserFillPlaceHolder,
2279 UnsafeBufferUsageHandler &Handler) {
2280 if (hasUnsupportedSpecifiers(VD: D, SM: Ctx.getSourceManager()))
2281 return {};
2282
2283 FixItList FixIts{};
2284 std::optional<std::string> SpanTyText = createSpanTypeForVarDecl(VD: D, Ctx);
2285
2286 if (!SpanTyText) {
2287 DEBUG_NOTE_DECL_FAIL(D, " : failed to generate 'std::span' type");
2288 return {};
2289 }
2290
2291 // Will hold the text for `std::span<T> Ident`:
2292 std::stringstream SS;
2293
2294 SS << *SpanTyText;
2295 // Fix the initializer if it exists:
2296 if (const Expr *Init = D->getInit()) {
2297 std::optional<FixItList> InitFixIts =
2298 FixVarInitializerWithSpan(Init, Ctx, UserFillPlaceHolder);
2299 if (!InitFixIts)
2300 return {};
2301 FixIts.insert(I: FixIts.end(), From: std::make_move_iterator(i: InitFixIts->begin()),
2302 To: std::make_move_iterator(i: InitFixIts->end()));
2303 }
2304 // For declaration of the form `T * ident = init;`, we want to replace
2305 // `T * ` with `std::span<T>`.
2306 // We ignore CV-qualifiers so for `T * const ident;` we also want to replace
2307 // just `T *` with `std::span<T>`.
2308 const SourceLocation EndLocForReplacement = D->getTypeSpecEndLoc();
2309 if (!EndLocForReplacement.isValid()) {
2310 DEBUG_NOTE_DECL_FAIL(D, " : failed to locate the end of the declaration");
2311 return {};
2312 }
2313 // The only exception is that for `T *ident` we'll add a single space between
2314 // "std::span<T>" and "ident".
2315 // FIXME: The condition is false for identifiers expended from macros.
2316 if (EndLocForReplacement.getLocWithOffset(Offset: 1) == getVarDeclIdentifierLoc(VD: D))
2317 SS << " ";
2318
2319 FixIts.push_back(Elt: FixItHint::CreateReplacement(
2320 RemoveRange: SourceRange(D->getBeginLoc(), EndLocForReplacement), Code: SS.str()));
2321 return FixIts;
2322}
2323
2324static bool hasConflictingOverload(const FunctionDecl *FD) {
2325 return !FD->getDeclContext()->lookup(FD->getDeclName()).isSingleResult();
2326}
2327
2328// For a `FunctionDecl`, whose `ParmVarDecl`s are being changed to have new
2329// types, this function produces fix-its to make the change self-contained. Let
2330// 'F' be the entity defined by the original `FunctionDecl` and "NewF" be the
2331// entity defined by the `FunctionDecl` after the change to the parameters.
2332// Fix-its produced by this function are
2333// 1. Add the `[[clang::unsafe_buffer_usage]]` attribute to each declaration
2334// of 'F';
2335// 2. Create a declaration of "NewF" next to each declaration of `F`;
2336// 3. Create a definition of "F" (as its' original definition is now belongs
2337// to "NewF") next to its original definition. The body of the creating
2338// definition calls to "NewF".
2339//
2340// Example:
2341//
2342// void f(int *p); // original declaration
2343// void f(int *p) { // original definition
2344// p[5];
2345// }
2346//
2347// To change the parameter `p` to be of `std::span<int>` type, we
2348// also add overloads:
2349//
2350// [[clang::unsafe_buffer_usage]] void f(int *p); // original decl
2351// void f(std::span<int> p); // added overload decl
2352// void f(std::span<int> p) { // original def where param is changed
2353// p[5];
2354// }
2355// [[clang::unsafe_buffer_usage]] void f(int *p) { // added def
2356// return f(std::span(p, <# size #>));
2357// }
2358//
2359static std::optional<FixItList>
2360createOverloadsForFixedParams(const FixitStrategy &S, const FunctionDecl *FD,
2361 const ASTContext &Ctx,
2362 UnsafeBufferUsageHandler &Handler) {
2363 // FIXME: need to make this conflict checking better:
2364 if (hasConflictingOverload(FD))
2365 return std::nullopt;
2366
2367 const SourceManager &SM = Ctx.getSourceManager();
2368 const LangOptions &LangOpts = Ctx.getLangOpts();
2369 const unsigned NumParms = FD->getNumParams();
2370 std::vector<std::string> NewTysTexts(NumParms);
2371 std::vector<bool> ParmsMask(NumParms, false);
2372 bool AtLeastOneParmToFix = false;
2373
2374 for (unsigned i = 0; i < NumParms; i++) {
2375 const ParmVarDecl *PVD = FD->getParamDecl(i);
2376
2377 if (S.lookup(PVD) == FixitStrategy::Kind::Wontfix)
2378 continue;
2379 if (S.lookup(PVD) != FixitStrategy::Kind::Span)
2380 // Not supported, not suppose to happen:
2381 return std::nullopt;
2382
2383 std::optional<Qualifiers> PteTyQuals = std::nullopt;
2384 std::optional<std::string> PteTyText =
2385 getPointeeTypeText(PVD, SM, LangOpts, &PteTyQuals);
2386
2387 if (!PteTyText)
2388 // something wrong in obtaining the text of the pointee type, give up
2389 return std::nullopt;
2390 // FIXME: whether we should create std::span type depends on the
2391 // FixitStrategy.
2392 NewTysTexts[i] = getSpanTypeText(EltTyText: *PteTyText, Quals: PteTyQuals);
2393 ParmsMask[i] = true;
2394 AtLeastOneParmToFix = true;
2395 }
2396 if (!AtLeastOneParmToFix)
2397 // No need to create function overloads:
2398 return {};
2399 // FIXME Respect indentation of the original code.
2400
2401 // A lambda that creates the text representation of a function declaration
2402 // with the new type signatures:
2403 const auto NewOverloadSignatureCreator =
2404 [&SM, &LangOpts, &NewTysTexts,
2405 &ParmsMask](const FunctionDecl *FD) -> std::optional<std::string> {
2406 std::stringstream SS;
2407
2408 SS << ";";
2409 SS << getEndOfLine().str();
2410 // Append: ret-type func-name "("
2411 if (auto Prefix = getRangeText(
2412 SourceRange(FD->getBeginLoc(), (*FD->param_begin())->getBeginLoc()),
2413 SM, LangOpts))
2414 SS << Prefix->str();
2415 else
2416 return std::nullopt; // give up
2417 // Append: parameter-type-list
2418 const unsigned NumParms = FD->getNumParams();
2419
2420 for (unsigned i = 0; i < NumParms; i++) {
2421 const ParmVarDecl *Parm = FD->getParamDecl(i);
2422
2423 if (Parm->isImplicit())
2424 continue;
2425 if (ParmsMask[i]) {
2426 // This `i`-th parameter will be fixed with `NewTysTexts[i]` being its
2427 // new type:
2428 SS << NewTysTexts[i];
2429 // print parameter name if provided:
2430 if (IdentifierInfo *II = Parm->getIdentifier())
2431 SS << ' ' << II->getName().str();
2432 } else if (auto ParmTypeText =
2433 getRangeText(getSourceRangeToTokenEnd(Parm, SM, LangOpts),
2434 SM, LangOpts)) {
2435 // print the whole `Parm` without modification:
2436 SS << ParmTypeText->str();
2437 } else
2438 return std::nullopt; // something wrong, give up
2439 if (i != NumParms - 1)
2440 SS << ", ";
2441 }
2442 SS << ")";
2443 return SS.str();
2444 };
2445
2446 // A lambda that creates the text representation of a function definition with
2447 // the original signature:
2448 const auto OldOverloadDefCreator =
2449 [&Handler, &SM, &LangOpts, &NewTysTexts,
2450 &ParmsMask](const FunctionDecl *FD) -> std::optional<std::string> {
2451 std::stringstream SS;
2452
2453 SS << getEndOfLine().str();
2454 // Append: attr-name ret-type func-name "(" param-list ")" "{"
2455 if (auto FDPrefix = getRangeText(
2456 SourceRange(FD->getBeginLoc(), FD->getBody()->getBeginLoc()), SM,
2457 LangOpts))
2458 SS << Handler.getUnsafeBufferUsageAttributeTextAt(Loc: FD->getBeginLoc(), WSSuffix: " ")
2459 << FDPrefix->str() << "{";
2460 else
2461 return std::nullopt;
2462 // Append: "return" func-name "("
2463 if (auto FunQualName = getFunNameText(FD, SM, LangOpts))
2464 SS << "return " << FunQualName->str() << "(";
2465 else
2466 return std::nullopt;
2467
2468 // Append: arg-list
2469 const unsigned NumParms = FD->getNumParams();
2470 for (unsigned i = 0; i < NumParms; i++) {
2471 const ParmVarDecl *Parm = FD->getParamDecl(i);
2472
2473 if (Parm->isImplicit())
2474 continue;
2475 // FIXME: If a parameter has no name, it is unused in the
2476 // definition. So we could just leave it as it is.
2477 if (!Parm->getIdentifier())
2478 // If a parameter of a function definition has no name:
2479 return std::nullopt;
2480 if (ParmsMask[i])
2481 // This is our spanified paramter!
2482 SS << NewTysTexts[i] << "(" << Parm->getIdentifier()->getName().str()
2483 << ", " << getUserFillPlaceHolder(HintTextToUser: "size") << ")";
2484 else
2485 SS << Parm->getIdentifier()->getName().str();
2486 if (i != NumParms - 1)
2487 SS << ", ";
2488 }
2489 // finish call and the body
2490 SS << ");}" << getEndOfLine().str();
2491 // FIXME: 80-char line formatting?
2492 return SS.str();
2493 };
2494
2495 FixItList FixIts{};
2496 for (FunctionDecl *FReDecl : FD->redecls()) {
2497 std::optional<SourceLocation> Loc = getPastLoc(FReDecl, SM, LangOpts);
2498
2499 if (!Loc)
2500 return {};
2501 if (FReDecl->isThisDeclarationADefinition()) {
2502 assert(FReDecl == FD && "inconsistent function definition");
2503 // Inserts a definition with the old signature to the end of
2504 // `FReDecl`:
2505 if (auto OldOverloadDef = OldOverloadDefCreator(FReDecl))
2506 FixIts.emplace_back(FixItHint::CreateInsertion(*Loc, *OldOverloadDef));
2507 else
2508 return {}; // give up
2509 } else {
2510 // Adds the unsafe-buffer attribute (if not already there) to `FReDecl`:
2511 if (!FReDecl->hasAttr<UnsafeBufferUsageAttr>()) {
2512 FixIts.emplace_back(FixItHint::CreateInsertion(
2513 FReDecl->getBeginLoc(), Handler.getUnsafeBufferUsageAttributeTextAt(
2514 FReDecl->getBeginLoc(), " ")));
2515 }
2516 // Inserts a declaration with the new signature to the end of `FReDecl`:
2517 if (auto NewOverloadDecl = NewOverloadSignatureCreator(FReDecl))
2518 FixIts.emplace_back(FixItHint::CreateInsertion(*Loc, *NewOverloadDecl));
2519 else
2520 return {};
2521 }
2522 }
2523 return FixIts;
2524}
2525
2526// To fix a `ParmVarDecl` to be of `std::span` type.
2527static FixItList fixParamWithSpan(const ParmVarDecl *PVD, const ASTContext &Ctx,
2528 UnsafeBufferUsageHandler &Handler) {
2529 if (hasUnsupportedSpecifiers(PVD, Ctx.getSourceManager())) {
2530 DEBUG_NOTE_DECL_FAIL(PVD, " : has unsupport specifier(s)");
2531 return {};
2532 }
2533 if (PVD->hasDefaultArg()) {
2534 // FIXME: generate fix-its for default values:
2535 DEBUG_NOTE_DECL_FAIL(PVD, " : has default arg");
2536 return {};
2537 }
2538
2539 std::optional<Qualifiers> PteTyQualifiers = std::nullopt;
2540 std::optional<std::string> PteTyText = getPointeeTypeText(
2541 PVD, Ctx.getSourceManager(), Ctx.getLangOpts(), &PteTyQualifiers);
2542
2543 if (!PteTyText) {
2544 DEBUG_NOTE_DECL_FAIL(PVD, " : invalid pointee type");
2545 return {};
2546 }
2547
2548 std::optional<StringRef> PVDNameText = PVD->getIdentifier()->getName();
2549
2550 if (!PVDNameText) {
2551 DEBUG_NOTE_DECL_FAIL(PVD, " : invalid identifier name");
2552 return {};
2553 }
2554
2555 std::stringstream SS;
2556 std::optional<std::string> SpanTyText = createSpanTypeForVarDecl(PVD, Ctx);
2557
2558 if (PteTyQualifiers)
2559 // Append qualifiers if they exist:
2560 SS << getSpanTypeText(EltTyText: *PteTyText, Quals: PteTyQualifiers);
2561 else
2562 SS << getSpanTypeText(EltTyText: *PteTyText);
2563 // Append qualifiers to the type of the parameter:
2564 if (PVD->getType().hasQualifiers())
2565 SS << ' ' << PVD->getType().getQualifiers().getAsString();
2566 // Append parameter's name:
2567 SS << ' ' << PVDNameText->str();
2568 // Add replacement fix-it:
2569 return {FixItHint::CreateReplacement(RemoveRange: PVD->getSourceRange(), Code: SS.str())};
2570}
2571
2572static FixItList fixVariableWithSpan(const VarDecl *VD,
2573 const DeclUseTracker &Tracker,
2574 ASTContext &Ctx,
2575 UnsafeBufferUsageHandler &Handler) {
2576 const DeclStmt *DS = Tracker.lookupDecl(VD);
2577 if (!DS) {
2578 DEBUG_NOTE_DECL_FAIL(VD,
2579 " : variables declared this way not implemented yet");
2580 return {};
2581 }
2582 if (!DS->isSingleDecl()) {
2583 // FIXME: to support handling multiple `VarDecl`s in a single `DeclStmt`
2584 DEBUG_NOTE_DECL_FAIL(VD, " : multiple VarDecls");
2585 return {};
2586 }
2587 // Currently DS is an unused variable but we'll need it when
2588 // non-single decls are implemented, where the pointee type name
2589 // and the '*' are spread around the place.
2590 (void)DS;
2591
2592 // FIXME: handle cases where DS has multiple declarations
2593 return fixLocalVarDeclWithSpan(D: VD, Ctx, UserFillPlaceHolder: getUserFillPlaceHolder(), Handler);
2594}
2595
2596static FixItList fixVarDeclWithArray(const VarDecl *D, const ASTContext &Ctx,
2597 UnsafeBufferUsageHandler &Handler) {
2598 FixItList FixIts{};
2599
2600 // Note: the code below expects the declaration to not use any type sugar like
2601 // typedef.
2602 if (auto CAT = dyn_cast<clang::ConstantArrayType>(D->getType())) {
2603 const QualType &ArrayEltT = CAT->getElementType();
2604 assert(!ArrayEltT.isNull() && "Trying to fix a non-array type variable!");
2605 // FIXME: support multi-dimensional arrays
2606 if (isa<clang::ArrayType>(Val: ArrayEltT.getCanonicalType()))
2607 return {};
2608
2609 const SourceLocation IdentifierLoc = getVarDeclIdentifierLoc(VD: D);
2610
2611 // Get the spelling of the element type as written in the source file
2612 // (including macros, etc.).
2613 auto MaybeElemTypeTxt =
2614 getRangeText({D->getBeginLoc(), IdentifierLoc}, Ctx.getSourceManager(),
2615 Ctx.getLangOpts());
2616 if (!MaybeElemTypeTxt)
2617 return {};
2618 const llvm::StringRef ElemTypeTxt = MaybeElemTypeTxt->trim();
2619
2620 // Find the '[' token.
2621 std::optional<Token> NextTok = Lexer::findNextToken(
2622 Loc: IdentifierLoc, SM: Ctx.getSourceManager(), LangOpts: Ctx.getLangOpts());
2623 while (NextTok && !NextTok->is(K: tok::l_square) &&
2624 NextTok->getLocation() <= D->getSourceRange().getEnd())
2625 NextTok = Lexer::findNextToken(Loc: NextTok->getLocation(),
2626 SM: Ctx.getSourceManager(), LangOpts: Ctx.getLangOpts());
2627 if (!NextTok)
2628 return {};
2629 const SourceLocation LSqBracketLoc = NextTok->getLocation();
2630
2631 // Get the spelling of the array size as written in the source file
2632 // (including macros, etc.).
2633 auto MaybeArraySizeTxt = getRangeText(
2634 {LSqBracketLoc.getLocWithOffset(Offset: 1), D->getTypeSpecEndLoc()},
2635 Ctx.getSourceManager(), Ctx.getLangOpts());
2636 if (!MaybeArraySizeTxt)
2637 return {};
2638 const llvm::StringRef ArraySizeTxt = MaybeArraySizeTxt->trim();
2639 if (ArraySizeTxt.empty()) {
2640 // FIXME: Support array size getting determined from the initializer.
2641 // Examples:
2642 // int arr1[] = {0, 1, 2};
2643 // int arr2{3, 4, 5};
2644 // We might be able to preserve the non-specified size with `auto` and
2645 // `std::to_array`:
2646 // auto arr1 = std::to_array<int>({0, 1, 2});
2647 return {};
2648 }
2649
2650 std::optional<StringRef> IdentText =
2651 getVarDeclIdentifierText(VD: D, SM: Ctx.getSourceManager(), LangOpts: Ctx.getLangOpts());
2652
2653 if (!IdentText) {
2654 DEBUG_NOTE_DECL_FAIL(D, " : failed to locate the identifier");
2655 return {};
2656 }
2657
2658 SmallString<32> Replacement;
2659 raw_svector_ostream OS(Replacement);
2660 OS << "std::array<" << ElemTypeTxt << ", " << ArraySizeTxt << "> "
2661 << IdentText->str();
2662
2663 FixIts.push_back(Elt: FixItHint::CreateReplacement(
2664 RemoveRange: SourceRange{D->getBeginLoc(), D->getTypeSpecEndLoc()}, Code: OS.str()));
2665 }
2666
2667 return FixIts;
2668}
2669
2670static FixItList fixVariableWithArray(const VarDecl *VD,
2671 const DeclUseTracker &Tracker,
2672 const ASTContext &Ctx,
2673 UnsafeBufferUsageHandler &Handler) {
2674 const DeclStmt *DS = Tracker.lookupDecl(VD);
2675 assert(DS && "Fixing non-local variables not implemented yet!");
2676 if (!DS->isSingleDecl()) {
2677 // FIXME: to support handling multiple `VarDecl`s in a single `DeclStmt`
2678 return {};
2679 }
2680 // Currently DS is an unused variable but we'll need it when
2681 // non-single decls are implemented, where the pointee type name
2682 // and the '*' are spread around the place.
2683 (void)DS;
2684
2685 // FIXME: handle cases where DS has multiple declarations
2686 return fixVarDeclWithArray(D: VD, Ctx, Handler);
2687}
2688
2689// TODO: we should be consistent to use `std::nullopt` to represent no-fix due
2690// to any unexpected problem.
2691static FixItList
2692fixVariable(const VarDecl *VD, FixitStrategy::Kind K,
2693 /* The function decl under analysis */ const Decl *D,
2694 const DeclUseTracker &Tracker, ASTContext &Ctx,
2695 UnsafeBufferUsageHandler &Handler) {
2696 if (const auto *PVD = dyn_cast<ParmVarDecl>(Val: VD)) {
2697 auto *FD = dyn_cast<clang::FunctionDecl>(PVD->getDeclContext());
2698 if (!FD || FD != D) {
2699 // `FD != D` means that `PVD` belongs to a function that is not being
2700 // analyzed currently. Thus `FD` may not be complete.
2701 DEBUG_NOTE_DECL_FAIL(VD, " : function not currently analyzed");
2702 return {};
2703 }
2704
2705 // TODO If function has a try block we can't change params unless we check
2706 // also its catch block for their use.
2707 // FIXME We might support static class methods, some select methods,
2708 // operators and possibly lamdas.
2709 if (FD->isMain() || FD->isConstexpr() ||
2710 FD->getTemplatedKind() != FunctionDecl::TemplatedKind::TK_NonTemplate ||
2711 FD->isVariadic() ||
2712 // also covers call-operator of lamdas
2713 isa<CXXMethodDecl>(FD) ||
2714 // skip when the function body is a try-block
2715 (FD->hasBody() && isa<CXXTryStmt>(FD->getBody())) ||
2716 FD->isOverloadedOperator()) {
2717 DEBUG_NOTE_DECL_FAIL(VD, " : unsupported function decl");
2718 return {}; // TODO test all these cases
2719 }
2720 }
2721
2722 switch (K) {
2723 case FixitStrategy::Kind::Span: {
2724 if (VD->getType()->isPointerType()) {
2725 if (const auto *PVD = dyn_cast<ParmVarDecl>(Val: VD))
2726 return fixParamWithSpan(PVD, Ctx, Handler);
2727
2728 if (VD->isLocalVarDecl())
2729 return fixVariableWithSpan(VD, Tracker, Ctx, Handler);
2730 }
2731 DEBUG_NOTE_DECL_FAIL(VD, " : not a pointer");
2732 return {};
2733 }
2734 case FixitStrategy::Kind::Array: {
2735 if (VD->isLocalVarDecl() &&
2736 isa<clang::ConstantArrayType>(VD->getType().getCanonicalType()))
2737 return fixVariableWithArray(VD, Tracker, Ctx, Handler);
2738
2739 DEBUG_NOTE_DECL_FAIL(VD, " : not a local const-size array");
2740 return {};
2741 }
2742 case FixitStrategy::Kind::Iterator:
2743 case FixitStrategy::Kind::Vector:
2744 llvm_unreachable("FixitStrategy not implemented yet!");
2745 case FixitStrategy::Kind::Wontfix:
2746 llvm_unreachable("Invalid strategy!");
2747 }
2748 llvm_unreachable("Unknown strategy!");
2749}
2750
2751// Returns true iff there exists a `FixItHint` 'h' in `FixIts` such that the
2752// `RemoveRange` of 'h' overlaps with a macro use.
2753static bool overlapWithMacro(const FixItList &FixIts) {
2754 // FIXME: For now we only check if the range (or the first token) is (part of)
2755 // a macro expansion. Ideally, we want to check for all tokens in the range.
2756 return llvm::any_of(Range: FixIts, P: [](const FixItHint &Hint) {
2757 auto Range = Hint.RemoveRange;
2758 if (Range.getBegin().isMacroID() || Range.getEnd().isMacroID())
2759 // If the range (or the first token) is (part of) a macro expansion:
2760 return true;
2761 return false;
2762 });
2763}
2764
2765// Returns true iff `VD` is a parameter of the declaration `D`:
2766static bool isParameterOf(const VarDecl *VD, const Decl *D) {
2767 return isa<ParmVarDecl>(Val: VD) &&
2768 VD->getDeclContext() == dyn_cast<DeclContext>(Val: D);
2769}
2770
2771// Erases variables in `FixItsForVariable`, if such a variable has an unfixable
2772// group mate. A variable `v` is unfixable iff `FixItsForVariable` does not
2773// contain `v`.
2774static void eraseVarsForUnfixableGroupMates(
2775 std::map<const VarDecl *, FixItList> &FixItsForVariable,
2776 const VariableGroupsManager &VarGrpMgr) {
2777 // Variables will be removed from `FixItsForVariable`:
2778 SmallVector<const VarDecl *, 8> ToErase;
2779
2780 for (const auto &[VD, Ignore] : FixItsForVariable) {
2781 VarGrpRef Grp = VarGrpMgr.getGroupOfVar(Var: VD);
2782 if (llvm::any_of(Range&: Grp,
2783 P: [&FixItsForVariable](const VarDecl *GrpMember) -> bool {
2784 return !FixItsForVariable.count(x: GrpMember);
2785 })) {
2786 // At least one group member cannot be fixed, so we have to erase the
2787 // whole group:
2788 for (const VarDecl *Member : Grp)
2789 ToErase.push_back(Elt: Member);
2790 }
2791 }
2792 for (auto *VarToErase : ToErase)
2793 FixItsForVariable.erase(x: VarToErase);
2794}
2795
2796// Returns the fix-its that create bounds-safe function overloads for the
2797// function `D`, if `D`'s parameters will be changed to safe-types through
2798// fix-its in `FixItsForVariable`.
2799//
2800// NOTE: In case `D`'s parameters will be changed but bounds-safe function
2801// overloads cannot created, the whole group that contains the parameters will
2802// be erased from `FixItsForVariable`.
2803static FixItList createFunctionOverloadsForParms(
2804 std::map<const VarDecl *, FixItList> &FixItsForVariable /* mutable */,
2805 const VariableGroupsManager &VarGrpMgr, const FunctionDecl *FD,
2806 const FixitStrategy &S, ASTContext &Ctx,
2807 UnsafeBufferUsageHandler &Handler) {
2808 FixItList FixItsSharedByParms{};
2809
2810 std::optional<FixItList> OverloadFixes =
2811 createOverloadsForFixedParams(S, FD, Ctx, Handler);
2812
2813 if (OverloadFixes) {
2814 FixItsSharedByParms.append(RHS: *OverloadFixes);
2815 } else {
2816 // Something wrong in generating `OverloadFixes`, need to remove the
2817 // whole group, where parameters are in, from `FixItsForVariable` (Note
2818 // that all parameters should be in the same group):
2819 for (auto *Member : VarGrpMgr.getGroupOfParms())
2820 FixItsForVariable.erase(x: Member);
2821 }
2822 return FixItsSharedByParms;
2823}
2824
2825// Constructs self-contained fix-its for each variable in `FixablesForAllVars`.
2826static std::map<const VarDecl *, FixItList>
2827getFixIts(FixableGadgetSets &FixablesForAllVars, const FixitStrategy &S,
2828 ASTContext &Ctx,
2829 /* The function decl under analysis */ const Decl *D,
2830 const DeclUseTracker &Tracker, UnsafeBufferUsageHandler &Handler,
2831 const VariableGroupsManager &VarGrpMgr) {
2832 // `FixItsForVariable` will map each variable to a set of fix-its directly
2833 // associated to the variable itself. Fix-its of distinct variables in
2834 // `FixItsForVariable` are disjoint.
2835 std::map<const VarDecl *, FixItList> FixItsForVariable;
2836
2837 // Populate `FixItsForVariable` with fix-its directly associated with each
2838 // variable. Fix-its directly associated to a variable 'v' are the ones
2839 // produced by the `FixableGadget`s whose claimed variable is 'v'.
2840 for (const auto &[VD, Fixables] : FixablesForAllVars.byVar) {
2841 FixItsForVariable[VD] =
2842 fixVariable(VD, K: S.lookup(VD), D, Tracker, Ctx, Handler);
2843 // If we fail to produce Fix-It for the declaration we have to skip the
2844 // variable entirely.
2845 if (FixItsForVariable[VD].empty()) {
2846 FixItsForVariable.erase(x: VD);
2847 continue;
2848 }
2849 for (const auto &F : Fixables) {
2850 std::optional<FixItList> Fixits = F->getFixits(S);
2851
2852 if (Fixits) {
2853 FixItsForVariable[VD].insert(I: FixItsForVariable[VD].end(),
2854 From: Fixits->begin(), To: Fixits->end());
2855 continue;
2856 }
2857#ifndef NDEBUG
2858 Handler.addDebugNoteForVar(
2859 VD, Loc: F->getBaseStmt()->getBeginLoc(),
2860 Text: ("gadget '" + F->getDebugName() + "' refused to produce a fix")
2861 .str());
2862#endif
2863 FixItsForVariable.erase(x: VD);
2864 break;
2865 }
2866 }
2867
2868 // `FixItsForVariable` now contains only variables that can be
2869 // fixed. A variable can be fixed if its' declaration and all Fixables
2870 // associated to it can all be fixed.
2871
2872 // To further remove from `FixItsForVariable` variables whose group mates
2873 // cannot be fixed...
2874 eraseVarsForUnfixableGroupMates(FixItsForVariable, VarGrpMgr);
2875 // Now `FixItsForVariable` gets further reduced: a variable is in
2876 // `FixItsForVariable` iff it can be fixed and all its group mates can be
2877 // fixed.
2878
2879 // Fix-its of bounds-safe overloads of `D` are shared by parameters of `D`.
2880 // That is, when fixing multiple parameters in one step, these fix-its will
2881 // be applied only once (instead of being applied per parameter).
2882 FixItList FixItsSharedByParms{};
2883
2884 if (auto *FD = dyn_cast<FunctionDecl>(Val: D))
2885 FixItsSharedByParms = createFunctionOverloadsForParms(
2886 FixItsForVariable, VarGrpMgr, FD, S, Ctx, Handler);
2887
2888 // The map that maps each variable `v` to fix-its for the whole group where
2889 // `v` is in:
2890 std::map<const VarDecl *, FixItList> FinalFixItsForVariable{
2891 FixItsForVariable};
2892
2893 for (auto &[Var, Ignore] : FixItsForVariable) {
2894 bool AnyParm = false;
2895 const auto VarGroupForVD = VarGrpMgr.getGroupOfVar(Var, HasParm: &AnyParm);
2896
2897 for (const VarDecl *GrpMate : VarGroupForVD) {
2898 if (Var == GrpMate)
2899 continue;
2900 if (FixItsForVariable.count(x: GrpMate))
2901 FinalFixItsForVariable[Var].append(RHS: FixItsForVariable[GrpMate]);
2902 }
2903 if (AnyParm) {
2904 // This assertion should never fail. Otherwise we have a bug.
2905 assert(!FixItsSharedByParms.empty() &&
2906 "Should not try to fix a parameter that does not belong to a "
2907 "FunctionDecl");
2908 FinalFixItsForVariable[Var].append(RHS: FixItsSharedByParms);
2909 }
2910 }
2911 // Fix-its that will be applied in one step shall NOT:
2912 // 1. overlap with macros or/and templates; or
2913 // 2. conflict with each other.
2914 // Otherwise, the fix-its will be dropped.
2915 for (auto Iter = FinalFixItsForVariable.begin();
2916 Iter != FinalFixItsForVariable.end();)
2917 if (overlapWithMacro(FixIts: Iter->second) ||
2918 clang::internal::anyConflict(FixIts: Iter->second, SM: Ctx.getSourceManager())) {
2919 Iter = FinalFixItsForVariable.erase(position: Iter);
2920 } else
2921 Iter++;
2922 return FinalFixItsForVariable;
2923}
2924
2925template <typename VarDeclIterTy>
2926static FixitStrategy
2927getNaiveStrategy(llvm::iterator_range<VarDeclIterTy> UnsafeVars) {
2928 FixitStrategy S;
2929 for (const VarDecl *VD : UnsafeVars) {
2930 if (isa<ConstantArrayType>(VD->getType().getCanonicalType()))
2931 S.set(VD, K: FixitStrategy::Kind::Array);
2932 else
2933 S.set(VD, K: FixitStrategy::Kind::Span);
2934 }
2935 return S;
2936}
2937
2938// Manages variable groups:
2939class VariableGroupsManagerImpl : public VariableGroupsManager {
2940 const std::vector<VarGrpTy> Groups;
2941 const std::map<const VarDecl *, unsigned> &VarGrpMap;
2942 const llvm::SetVector<const VarDecl *> &GrpsUnionForParms;
2943
2944public:
2945 VariableGroupsManagerImpl(
2946 const std::vector<VarGrpTy> &Groups,
2947 const std::map<const VarDecl *, unsigned> &VarGrpMap,
2948 const llvm::SetVector<const VarDecl *> &GrpsUnionForParms)
2949 : Groups(Groups), VarGrpMap(VarGrpMap),
2950 GrpsUnionForParms(GrpsUnionForParms) {}
2951
2952 VarGrpRef getGroupOfVar(const VarDecl *Var, bool *HasParm) const override {
2953 if (GrpsUnionForParms.contains(key: Var)) {
2954 if (HasParm)
2955 *HasParm = true;
2956 return GrpsUnionForParms.getArrayRef();
2957 }
2958 if (HasParm)
2959 *HasParm = false;
2960
2961 auto It = VarGrpMap.find(x: Var);
2962
2963 if (It == VarGrpMap.end())
2964 return std::nullopt;
2965 return Groups[It->second];
2966 }
2967
2968 VarGrpRef getGroupOfParms() const override {
2969 return GrpsUnionForParms.getArrayRef();
2970 }
2971};
2972
2973void clang::checkUnsafeBufferUsage(const Decl *D,
2974 UnsafeBufferUsageHandler &Handler,
2975 bool EmitSuggestions) {
2976#ifndef NDEBUG
2977 Handler.clearDebugNotes();
2978#endif
2979
2980 assert(D && D->getBody());
2981 // We do not want to visit a Lambda expression defined inside a method
2982 // independently. Instead, it should be visited along with the outer method.
2983 // FIXME: do we want to do the same thing for `BlockDecl`s?
2984 if (const auto *fd = dyn_cast<CXXMethodDecl>(Val: D)) {
2985 if (fd->getParent()->isLambda() && fd->getParent()->isLocalClass())
2986 return;
2987 }
2988
2989 // Do not emit fixit suggestions for functions declared in an
2990 // extern "C" block.
2991 if (const auto *FD = dyn_cast<FunctionDecl>(Val: D)) {
2992 for (FunctionDecl *FReDecl : FD->redecls()) {
2993 if (FReDecl->isExternC()) {
2994 EmitSuggestions = false;
2995 break;
2996 }
2997 }
2998 }
2999
3000 WarningGadgetSets UnsafeOps;
3001 FixableGadgetSets FixablesForAllVars;
3002
3003 auto [FixableGadgets, WarningGadgets, Tracker] =
3004 findGadgets(D, Handler, EmitSuggestions);
3005
3006 if (!EmitSuggestions) {
3007 // Our job is very easy without suggestions. Just warn about
3008 // every problematic operation and consider it done. No need to deal
3009 // with fixable gadgets, no need to group operations by variable.
3010 for (const auto &G : WarningGadgets) {
3011 Handler.handleUnsafeOperation(Operation: G->getBaseStmt(), /*IsRelatedToDecl=*/false,
3012 Ctx&: D->getASTContext());
3013 }
3014
3015 // This return guarantees that most of the machine doesn't run when
3016 // suggestions aren't requested.
3017 assert(FixableGadgets.size() == 0 &&
3018 "Fixable gadgets found but suggestions not requested!");
3019 return;
3020 }
3021
3022 // If no `WarningGadget`s ever matched, there is no unsafe operations in the
3023 // function under the analysis. No need to fix any Fixables.
3024 if (!WarningGadgets.empty()) {
3025 // Gadgets "claim" variables they're responsible for. Once this loop
3026 // finishes, the tracker will only track DREs that weren't claimed by any
3027 // gadgets, i.e. not understood by the analysis.
3028 for (const auto &G : FixableGadgets) {
3029 for (const auto *DRE : G->getClaimedVarUseSites()) {
3030 Tracker.claimUse(DRE);
3031 }
3032 }
3033 }
3034
3035 // If no `WarningGadget`s ever matched, there is no unsafe operations in the
3036 // function under the analysis. Thus, it early returns here as there is
3037 // nothing needs to be fixed.
3038 //
3039 // Note this claim is based on the assumption that there is no unsafe
3040 // variable whose declaration is invisible from the analyzing function.
3041 // Otherwise, we need to consider if the uses of those unsafe varuables needs
3042 // fix.
3043 // So far, we are not fixing any global variables or class members. And,
3044 // lambdas will be analyzed along with the enclosing function. So this early
3045 // return is correct for now.
3046 if (WarningGadgets.empty())
3047 return;
3048
3049 UnsafeOps = groupWarningGadgetsByVar(AllUnsafeOperations: std::move(WarningGadgets));
3050 FixablesForAllVars = groupFixablesByVar(AllFixableOperations: std::move(FixableGadgets));
3051
3052 std::map<const VarDecl *, FixItList> FixItsForVariableGroup;
3053
3054 // Filter out non-local vars and vars with unclaimed DeclRefExpr-s.
3055 for (auto it = FixablesForAllVars.byVar.cbegin();
3056 it != FixablesForAllVars.byVar.cend();) {
3057 // FIXME: need to deal with global variables later
3058 if ((!it->first->isLocalVarDecl() && !isa<ParmVarDecl>(Val: it->first))) {
3059#ifndef NDEBUG
3060 Handler.addDebugNoteForVar(VD: it->first, Loc: it->first->getBeginLoc(),
3061 Text: ("failed to produce fixit for '" +
3062 it->first->getNameAsString() +
3063 "' : neither local nor a parameter"));
3064#endif
3065 it = FixablesForAllVars.byVar.erase(position: it);
3066 } else if (it->first->getType().getCanonicalType()->isReferenceType()) {
3067#ifndef NDEBUG
3068 Handler.addDebugNoteForVar(VD: it->first, Loc: it->first->getBeginLoc(),
3069 Text: ("failed to produce fixit for '" +
3070 it->first->getNameAsString() +
3071 "' : has a reference type"));
3072#endif
3073 it = FixablesForAllVars.byVar.erase(position: it);
3074 } else if (Tracker.hasUnclaimedUses(VD: it->first)) {
3075 it = FixablesForAllVars.byVar.erase(position: it);
3076 } else if (it->first->isInitCapture()) {
3077#ifndef NDEBUG
3078 Handler.addDebugNoteForVar(VD: it->first, Loc: it->first->getBeginLoc(),
3079 Text: ("failed to produce fixit for '" +
3080 it->first->getNameAsString() +
3081 "' : init capture"));
3082#endif
3083 it = FixablesForAllVars.byVar.erase(position: it);
3084 } else {
3085 ++it;
3086 }
3087 }
3088
3089#ifndef NDEBUG
3090 for (const auto &it : UnsafeOps.byVar) {
3091 const VarDecl *const UnsafeVD = it.first;
3092 auto UnclaimedDREs = Tracker.getUnclaimedUses(VD: UnsafeVD);
3093 if (UnclaimedDREs.empty())
3094 continue;
3095 const auto UnfixedVDName = UnsafeVD->getNameAsString();
3096 for (const clang::DeclRefExpr *UnclaimedDRE : UnclaimedDREs) {
3097 std::string UnclaimedUseTrace =
3098 getDREAncestorString(DRE: UnclaimedDRE, Ctx&: D->getASTContext());
3099
3100 Handler.addDebugNoteForVar(
3101 VD: UnsafeVD, Loc: UnclaimedDRE->getBeginLoc(),
3102 Text: ("failed to produce fixit for '" + UnfixedVDName +
3103 "' : has an unclaimed use\nThe unclaimed DRE trace: " +
3104 UnclaimedUseTrace));
3105 }
3106 }
3107#endif
3108
3109 // Fixpoint iteration for pointer assignments
3110 using DepMapTy = DenseMap<const VarDecl *, llvm::SetVector<const VarDecl *>>;
3111 DepMapTy DependenciesMap{};
3112 DepMapTy PtrAssignmentGraph{};
3113
3114 for (auto it : FixablesForAllVars.byVar) {
3115 for (const FixableGadget *fixable : it.second) {
3116 std::optional<std::pair<const VarDecl *, const VarDecl *>> ImplPair =
3117 fixable->getStrategyImplications();
3118 if (ImplPair) {
3119 std::pair<const VarDecl *, const VarDecl *> Impl = std::move(*ImplPair);
3120 PtrAssignmentGraph[Impl.first].insert(X: Impl.second);
3121 }
3122 }
3123 }
3124
3125 /*
3126 The following code does a BFS traversal of the `PtrAssignmentGraph`
3127 considering all unsafe vars as starting nodes and constructs an undirected
3128 graph `DependenciesMap`. Constructing the `DependenciesMap` in this manner
3129 elimiates all variables that are unreachable from any unsafe var. In other
3130 words, this removes all dependencies that don't include any unsafe variable
3131 and consequently don't need any fixit generation.
3132 Note: A careful reader would observe that the code traverses
3133 `PtrAssignmentGraph` using `CurrentVar` but adds edges between `Var` and
3134 `Adj` and not between `CurrentVar` and `Adj`. Both approaches would
3135 achieve the same result but the one used here dramatically cuts the
3136 amount of hoops the second part of the algorithm needs to jump, given that
3137 a lot of these connections become "direct". The reader is advised not to
3138 imagine how the graph is transformed because of using `Var` instead of
3139 `CurrentVar`. The reader can continue reading as if `CurrentVar` was used,
3140 and think about why it's equivalent later.
3141 */
3142 std::set<const VarDecl *> VisitedVarsDirected{};
3143 for (const auto &[Var, ignore] : UnsafeOps.byVar) {
3144 if (VisitedVarsDirected.find(x: Var) == VisitedVarsDirected.end()) {
3145
3146 std::queue<const VarDecl *> QueueDirected{};
3147 QueueDirected.push(x: Var);
3148 while (!QueueDirected.empty()) {
3149 const VarDecl *CurrentVar = QueueDirected.front();
3150 QueueDirected.pop();
3151 VisitedVarsDirected.insert(x: CurrentVar);
3152 auto AdjacentNodes = PtrAssignmentGraph[CurrentVar];
3153 for (const VarDecl *Adj : AdjacentNodes) {
3154 if (VisitedVarsDirected.find(x: Adj) == VisitedVarsDirected.end()) {
3155 QueueDirected.push(x: Adj);
3156 }
3157 DependenciesMap[Var].insert(X: Adj);
3158 DependenciesMap[Adj].insert(X: Var);
3159 }
3160 }
3161 }
3162 }
3163
3164 // `Groups` stores the set of Connected Components in the graph.
3165 std::vector<VarGrpTy> Groups;
3166 // `VarGrpMap` maps variables that need fix to the groups (indexes) that the
3167 // variables belong to. Group indexes refer to the elements in `Groups`.
3168 // `VarGrpMap` is complete in that every variable that needs fix is in it.
3169 std::map<const VarDecl *, unsigned> VarGrpMap;
3170 // The union group over the ones in "Groups" that contain parameters of `D`:
3171 llvm::SetVector<const VarDecl *>
3172 GrpsUnionForParms; // these variables need to be fixed in one step
3173
3174 // Group Connected Components for Unsafe Vars
3175 // (Dependencies based on pointer assignments)
3176 std::set<const VarDecl *> VisitedVars{};
3177 for (const auto &[Var, ignore] : UnsafeOps.byVar) {
3178 if (VisitedVars.find(x: Var) == VisitedVars.end()) {
3179 VarGrpTy &VarGroup = Groups.emplace_back();
3180 std::queue<const VarDecl *> Queue{};
3181
3182 Queue.push(x: Var);
3183 while (!Queue.empty()) {
3184 const VarDecl *CurrentVar = Queue.front();
3185 Queue.pop();
3186 VisitedVars.insert(x: CurrentVar);
3187 VarGroup.push_back(x: CurrentVar);
3188 auto AdjacentNodes = DependenciesMap[CurrentVar];
3189 for (const VarDecl *Adj : AdjacentNodes) {
3190 if (VisitedVars.find(x: Adj) == VisitedVars.end()) {
3191 Queue.push(x: Adj);
3192 }
3193 }
3194 }
3195
3196 bool HasParm = false;
3197 unsigned GrpIdx = Groups.size() - 1;
3198
3199 for (const VarDecl *V : VarGroup) {
3200 VarGrpMap[V] = GrpIdx;
3201 if (!HasParm && isParameterOf(VD: V, D))
3202 HasParm = true;
3203 }
3204 if (HasParm)
3205 GrpsUnionForParms.insert(Start: VarGroup.begin(), End: VarGroup.end());
3206 }
3207 }
3208
3209 // Remove a `FixableGadget` if the associated variable is not in the graph
3210 // computed above. We do not want to generate fix-its for such variables,
3211 // since they are neither warned nor reachable from a warned one.
3212 //
3213 // Note a variable is not warned if it is not directly used in any unsafe
3214 // operation. A variable `v` is NOT reachable from an unsafe variable, if it
3215 // does not exist another variable `u` such that `u` is warned and fixing `u`
3216 // (transitively) implicates fixing `v`.
3217 //
3218 // For example,
3219 // ```
3220 // void f(int * p) {
3221 // int * a = p; *p = 0;
3222 // }
3223 // ```
3224 // `*p = 0` is a fixable gadget associated with a variable `p` that is neither
3225 // warned nor reachable from a warned one. If we add `a[5] = 0` to the end of
3226 // the function above, `p` becomes reachable from a warned variable.
3227 for (auto I = FixablesForAllVars.byVar.begin();
3228 I != FixablesForAllVars.byVar.end();) {
3229 // Note `VisitedVars` contain all the variables in the graph:
3230 if (!VisitedVars.count(x: (*I).first)) {
3231 // no such var in graph:
3232 I = FixablesForAllVars.byVar.erase(position: I);
3233 } else
3234 ++I;
3235 }
3236
3237 // We assign strategies to variables that are 1) in the graph and 2) can be
3238 // fixed. Other variables have the default "Won't fix" strategy.
3239 FixitStrategy NaiveStrategy = getNaiveStrategy(UnsafeVars: llvm::make_filter_range(
3240 Range&: VisitedVars, Pred: [&FixablesForAllVars](const VarDecl *V) {
3241 // If a warned variable has no "Fixable", it is considered unfixable:
3242 return FixablesForAllVars.byVar.count(x: V);
3243 }));
3244 VariableGroupsManagerImpl VarGrpMgr(Groups, VarGrpMap, GrpsUnionForParms);
3245
3246 if (isa<NamedDecl>(Val: D))
3247 // The only case where `D` is not a `NamedDecl` is when `D` is a
3248 // `BlockDecl`. Let's not fix variables in blocks for now
3249 FixItsForVariableGroup =
3250 getFixIts(FixablesForAllVars, S: NaiveStrategy, Ctx&: D->getASTContext(), D,
3251 Tracker, Handler, VarGrpMgr);
3252
3253 for (const auto &G : UnsafeOps.noVar) {
3254 Handler.handleUnsafeOperation(Operation: G->getBaseStmt(), /*IsRelatedToDecl=*/false,
3255 Ctx&: D->getASTContext());
3256 }
3257
3258 for (const auto &[VD, WarningGadgets] : UnsafeOps.byVar) {
3259 auto FixItsIt = FixItsForVariableGroup.find(x: VD);
3260 Handler.handleUnsafeVariableGroup(Variable: VD, VarGrpMgr,
3261 Fixes: FixItsIt != FixItsForVariableGroup.end()
3262 ? std::move(FixItsIt->second)
3263 : FixItList{},
3264 D, VarTargetTypes: NaiveStrategy);
3265 for (const auto &G : WarningGadgets) {
3266 Handler.handleUnsafeOperation(Operation: G->getBaseStmt(), /*IsRelatedToDecl=*/true,
3267 Ctx&: D->getASTContext());
3268 }
3269 }
3270}
3271

source code of clang/lib/Analysis/UnsafeBufferUsage.cpp