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/RecursiveASTVisitor.h"
11 #include "clang/ASTMatchers/ASTMatchFinder.h"
12 #include "llvm/ADT/SmallVector.h"
13 #include <memory>
14 #include <optional>
15
16 using namespace llvm;
17 using namespace clang;
18 using namespace ast_matchers;
19
20 namespace clang::ast_matchers {
21 // A `RecursiveASTVisitor` that traverses all descendants of a given node "n"
22 // except for those belonging to a different callable of "n".
23 class MatchDescendantVisitor
24 : public RecursiveASTVisitor<MatchDescendantVisitor> {
25 public:
26 typedef RecursiveASTVisitor<MatchDescendantVisitor> VisitorBase;
27
28 // Creates an AST visitor that matches `Matcher` on all
29 // descendants of a given node "n" except for the ones
30 // belonging to a different callable of "n".
MatchDescendantVisitor(const internal::DynTypedMatcher * Matcher,internal::ASTMatchFinder * Finder,internal::BoundNodesTreeBuilder * Builder,internal::ASTMatchFinder::BindKind Bind)31 MatchDescendantVisitor(const internal::DynTypedMatcher *Matcher,
32 internal::ASTMatchFinder *Finder,
33 internal::BoundNodesTreeBuilder *Builder,
34 internal::ASTMatchFinder::BindKind Bind)
35 : Matcher(Matcher), Finder(Finder), Builder(Builder), Bind(Bind),
36 Matches(false) {}
37
38 // Returns true if a match is found in a subtree of `DynNode`, which belongs
39 // to the same callable of `DynNode`.
findMatch(const DynTypedNode & DynNode)40 bool findMatch(const DynTypedNode &DynNode) {
41 Matches = false;
42 if (const Stmt *StmtNode = DynNode.get<Stmt>()) {
43 TraverseStmt(const_cast<Stmt *>(StmtNode));
44 *Builder = ResultBindings;
45 return Matches;
46 }
47 return false;
48 }
49
50 // The following are overriding methods from the base visitor class.
51 // They are public only to allow CRTP to work. They are *not *part
52 // of the public API of this class.
53
54 // For the matchers so far used in safe buffers, we only need to match
55 // `Stmt`s. To override more as needed.
56
TraverseDecl(Decl * Node)57 bool TraverseDecl(Decl *Node) {
58 if (!Node)
59 return true;
60 if (!match(*Node))
61 return false;
62 // To skip callables:
63 if (isa<FunctionDecl, BlockDecl, ObjCMethodDecl>(Node))
64 return true;
65 // Traverse descendants
66 return VisitorBase::TraverseDecl(Node);
67 }
68
TraverseStmt(Stmt * Node,DataRecursionQueue * Queue=nullptr)69 bool TraverseStmt(Stmt *Node, DataRecursionQueue *Queue = nullptr) {
70 if (!Node)
71 return true;
72 if (!match(*Node))
73 return false;
74 // To skip callables:
75 if (isa<LambdaExpr>(Node))
76 return true;
77 return VisitorBase::TraverseStmt(Node);
78 }
79
shouldVisitTemplateInstantiations() const80 bool shouldVisitTemplateInstantiations() const { return true; }
shouldVisitImplicitCode() const81 bool shouldVisitImplicitCode() const {
82 // TODO: let's ignore implicit code for now
83 return false;
84 }
85
86 private:
87 // Sets 'Matched' to true if 'Matcher' matches 'Node'
88 //
89 // Returns 'true' if traversal should continue after this function
90 // returns, i.e. if no match is found or 'Bind' is 'BK_All'.
match(const T & Node)91 template <typename T> bool match(const T &Node) {
92 internal::BoundNodesTreeBuilder RecursiveBuilder(*Builder);
93
94 if (Matcher->matches(DynTypedNode::create(Node), Finder,
95 &RecursiveBuilder)) {
96 ResultBindings.addMatch(RecursiveBuilder);
97 Matches = true;
98 if (Bind != internal::ASTMatchFinder::BK_All)
99 return false; // Abort as soon as a match is found.
100 }
101 return true;
102 }
103
104 const internal::DynTypedMatcher *const Matcher;
105 internal::ASTMatchFinder *const Finder;
106 internal::BoundNodesTreeBuilder *const Builder;
107 internal::BoundNodesTreeBuilder ResultBindings;
108 const internal::ASTMatchFinder::BindKind Bind;
109 bool Matches;
110 };
111
AST_MATCHER_P(Stmt,forEveryDescendant,internal::Matcher<Stmt>,innerMatcher)112 AST_MATCHER_P(Stmt, forEveryDescendant, internal::Matcher<Stmt>, innerMatcher) {
113 const DynTypedMatcher &DTM = static_cast<DynTypedMatcher>(innerMatcher);
114
115 MatchDescendantVisitor Visitor(&DTM, Finder, Builder, ASTMatchFinder::BK_All);
116 return Visitor.findMatch(DynTypedNode::create(Node));
117 }
118 } // namespace clang::ast_matchers
119
120 namespace {
121 // Because the analysis revolves around variables and their types, we'll need to
122 // track uses of variables (aka DeclRefExprs).
123 using DeclUseList = SmallVector<const DeclRefExpr *, 1>;
124
125 // Convenience typedef.
126 using FixItList = SmallVector<FixItHint, 4>;
127
128 // Defined below.
129 class Strategy;
130 } // namespace
131
132 // Because we're dealing with raw pointers, let's define what we mean by that.
hasPointerType()133 static auto hasPointerType() {
134 return hasType(hasCanonicalType(pointerType()));
135 }
136
hasArrayType()137 static auto hasArrayType() {
138 return hasType(hasCanonicalType(arrayType()));
139 }
140
141 namespace {
142 /// Gadget is an individual operation in the code that may be of interest to
143 /// this analysis. Each (non-abstract) subclass corresponds to a specific
144 /// rigid AST structure that constitutes an operation on a pointer-type object.
145 /// Discovery of a gadget in the code corresponds to claiming that we understand
146 /// what this part of code is doing well enough to potentially improve it.
147 /// Gadgets can be warning (immediately deserving a warning) or fixable (not
148 /// always deserving a warning per se, but requires our attention to identify
149 /// it warrants a fixit).
150 class Gadget {
151 public:
152 enum class Kind {
153 #define GADGET(x) x,
154 #include "clang/Analysis/Analyses/UnsafeBufferUsageGadgets.def"
155 };
156
157 /// Common type of ASTMatchers used for discovering gadgets.
158 /// Useful for implementing the static matcher() methods
159 /// that are expected from all non-abstract subclasses.
160 using Matcher = decltype(stmt());
161
Gadget(Kind K)162 Gadget(Kind K) : K(K) {}
163
getKind() const164 Kind getKind() const { return K; }
165
166 virtual bool isWarningGadget() const = 0;
167 virtual const Stmt *getBaseStmt() const = 0;
168
169 /// Returns the list of pointer-type variables on which this gadget performs
170 /// its operation. Typically, there's only one variable. This isn't a list
171 /// of all DeclRefExprs in the gadget's AST!
172 virtual DeclUseList getClaimedVarUseSites() const = 0;
173
174 virtual ~Gadget() = default;
175
176 private:
177 Kind K;
178 };
179
180
181 /// Warning gadgets correspond to unsafe code patterns that warrants
182 /// an immediate warning.
183 class WarningGadget : public Gadget {
184 public:
WarningGadget(Kind K)185 WarningGadget(Kind K) : Gadget(K) {}
186
classof(const Gadget * G)187 static bool classof(const Gadget *G) { return G->isWarningGadget(); }
isWarningGadget() const188 bool isWarningGadget() const final { return true; }
189 };
190
191 /// Fixable gadgets correspond to code patterns that aren't always unsafe but need to be
192 /// properly recognized in order to emit fixes. For example, if a raw pointer-type
193 /// variable is replaced by a safe C++ container, every use of such variable must be
194 /// carefully considered and possibly updated.
195 class FixableGadget : public Gadget {
196 public:
FixableGadget(Kind K)197 FixableGadget(Kind K) : Gadget(K) {}
198
classof(const Gadget * G)199 static bool classof(const Gadget *G) { return !G->isWarningGadget(); }
isWarningGadget() const200 bool isWarningGadget() const final { return false; }
201
202 /// Returns a fixit that would fix the current gadget according to
203 /// the current strategy. Returns None if the fix cannot be produced;
204 /// returns an empty list if no fixes are necessary.
getFixits(const Strategy &) const205 virtual std::optional<FixItList> getFixits(const Strategy &) const {
206 return std::nullopt;
207 }
208 };
209
210 using FixableGadgetList = std::vector<std::unique_ptr<FixableGadget>>;
211 using WarningGadgetList = std::vector<std::unique_ptr<WarningGadget>>;
212
213 /// An increment of a pointer-type value is unsafe as it may run the pointer
214 /// out of bounds.
215 class IncrementGadget : public WarningGadget {
216 static constexpr const char *const OpTag = "op";
217 const UnaryOperator *Op;
218
219 public:
IncrementGadget(const MatchFinder::MatchResult & Result)220 IncrementGadget(const MatchFinder::MatchResult &Result)
221 : WarningGadget(Kind::Increment),
222 Op(Result.Nodes.getNodeAs<UnaryOperator>(OpTag)) {}
223
classof(const Gadget * G)224 static bool classof(const Gadget *G) {
225 return G->getKind() == Kind::Increment;
226 }
227
matcher()228 static Matcher matcher() {
229 return stmt(unaryOperator(
230 hasOperatorName("++"),
231 hasUnaryOperand(ignoringParenImpCasts(hasPointerType()))
232 ).bind(OpTag));
233 }
234
getBaseStmt() const235 const UnaryOperator *getBaseStmt() const override { return Op; }
236
getClaimedVarUseSites() const237 DeclUseList getClaimedVarUseSites() const override {
238 SmallVector<const DeclRefExpr *, 2> Uses;
239 if (const auto *DRE =
240 dyn_cast<DeclRefExpr>(Op->getSubExpr()->IgnoreParenImpCasts())) {
241 Uses.push_back(DRE);
242 }
243
244 return std::move(Uses);
245 }
246 };
247
248 /// A decrement of a pointer-type value is unsafe as it may run the pointer
249 /// out of bounds.
250 class DecrementGadget : public WarningGadget {
251 static constexpr const char *const OpTag = "op";
252 const UnaryOperator *Op;
253
254 public:
DecrementGadget(const MatchFinder::MatchResult & Result)255 DecrementGadget(const MatchFinder::MatchResult &Result)
256 : WarningGadget(Kind::Decrement),
257 Op(Result.Nodes.getNodeAs<UnaryOperator>(OpTag)) {}
258
classof(const Gadget * G)259 static bool classof(const Gadget *G) {
260 return G->getKind() == Kind::Decrement;
261 }
262
matcher()263 static Matcher matcher() {
264 return stmt(unaryOperator(
265 hasOperatorName("--"),
266 hasUnaryOperand(ignoringParenImpCasts(hasPointerType()))
267 ).bind(OpTag));
268 }
269
getBaseStmt() const270 const UnaryOperator *getBaseStmt() const override { return Op; }
271
getClaimedVarUseSites() const272 DeclUseList getClaimedVarUseSites() const override {
273 if (const auto *DRE =
274 dyn_cast<DeclRefExpr>(Op->getSubExpr()->IgnoreParenImpCasts())) {
275 return {DRE};
276 }
277
278 return {};
279 }
280 };
281
282 /// Array subscript expressions on raw pointers as if they're arrays. Unsafe as
283 /// it doesn't have any bounds checks for the array.
284 class ArraySubscriptGadget : public WarningGadget {
285 static constexpr const char *const ArraySubscrTag = "arraySubscr";
286 const ArraySubscriptExpr *ASE;
287
288 public:
ArraySubscriptGadget(const MatchFinder::MatchResult & Result)289 ArraySubscriptGadget(const MatchFinder::MatchResult &Result)
290 : WarningGadget(Kind::ArraySubscript),
291 ASE(Result.Nodes.getNodeAs<ArraySubscriptExpr>(ArraySubscrTag)) {}
292
classof(const Gadget * G)293 static bool classof(const Gadget *G) {
294 return G->getKind() == Kind::ArraySubscript;
295 }
296
matcher()297 static Matcher matcher() {
298 // FIXME: What if the index is integer literal 0? Should this be
299 // a safe gadget in this case?
300 // clang-format off
301 return stmt(arraySubscriptExpr(
302 hasBase(ignoringParenImpCasts(
303 anyOf(hasPointerType(), hasArrayType()))),
304 unless(hasIndex(integerLiteral(equals(0)))))
305 .bind(ArraySubscrTag));
306 // clang-format on
307 }
308
getBaseStmt() const309 const ArraySubscriptExpr *getBaseStmt() const override { return ASE; }
310
getClaimedVarUseSites() const311 DeclUseList getClaimedVarUseSites() const override {
312 if (const auto *DRE =
313 dyn_cast<DeclRefExpr>(ASE->getBase()->IgnoreParenImpCasts())) {
314 return {DRE};
315 }
316
317 return {};
318 }
319 };
320
321 /// A pointer arithmetic expression of one of the forms:
322 /// \code
323 /// ptr + n | n + ptr | ptr - n | ptr += n | ptr -= n
324 /// \endcode
325 class PointerArithmeticGadget : public WarningGadget {
326 static constexpr const char *const PointerArithmeticTag = "ptrAdd";
327 static constexpr const char *const PointerArithmeticPointerTag = "ptrAddPtr";
328 const BinaryOperator *PA; // pointer arithmetic expression
329 const Expr * Ptr; // the pointer expression in `PA`
330
331 public:
PointerArithmeticGadget(const MatchFinder::MatchResult & Result)332 PointerArithmeticGadget(const MatchFinder::MatchResult &Result)
333 : WarningGadget(Kind::PointerArithmetic),
334 PA(Result.Nodes.getNodeAs<BinaryOperator>(PointerArithmeticTag)),
335 Ptr(Result.Nodes.getNodeAs<Expr>(PointerArithmeticPointerTag)) {}
336
classof(const Gadget * G)337 static bool classof(const Gadget *G) {
338 return G->getKind() == Kind::PointerArithmetic;
339 }
340
matcher()341 static Matcher matcher() {
342 auto HasIntegerType = anyOf(
343 hasType(isInteger()), hasType(enumType()));
344 auto PtrAtRight = allOf(hasOperatorName("+"),
345 hasRHS(expr(hasPointerType()).bind(PointerArithmeticPointerTag)),
346 hasLHS(HasIntegerType));
347 auto PtrAtLeft = allOf(
348 anyOf(hasOperatorName("+"), hasOperatorName("-"),
349 hasOperatorName("+="), hasOperatorName("-=")),
350 hasLHS(expr(hasPointerType()).bind(PointerArithmeticPointerTag)),
351 hasRHS(HasIntegerType));
352
353 return stmt(binaryOperator(anyOf(PtrAtLeft, PtrAtRight)).bind(PointerArithmeticTag));
354 }
355
getBaseStmt() const356 const Stmt *getBaseStmt() const override { return PA; }
357
getClaimedVarUseSites() const358 DeclUseList getClaimedVarUseSites() const override {
359 if (const auto *DRE =
360 dyn_cast<DeclRefExpr>(Ptr->IgnoreParenImpCasts())) {
361 return {DRE};
362 }
363
364 return {};
365 }
366 // FIXME: pointer adding zero should be fine
367 //FIXME: this gadge will need a fix-it
368 };
369 } // namespace
370
371 namespace {
372 // An auxiliary tracking facility for the fixit analysis. It helps connect
373 // declarations to its and make sure we've covered all uses with our analysis
374 // before we try to fix the declaration.
375 class DeclUseTracker {
376 using UseSetTy = SmallSet<const DeclRefExpr *, 16>;
377 using DefMapTy = DenseMap<const VarDecl *, const DeclStmt *>;
378
379 // Allocate on the heap for easier move.
380 std::unique_ptr<UseSetTy> Uses{std::make_unique<UseSetTy>()};
381 DefMapTy Defs{};
382
383 public:
384 DeclUseTracker() = default;
385 DeclUseTracker(const DeclUseTracker &) = delete; // Let's avoid copies.
386 DeclUseTracker(DeclUseTracker &&) = default;
387 DeclUseTracker &operator=(DeclUseTracker &&) = default;
388
389 // Start tracking a freshly discovered DRE.
discoverUse(const DeclRefExpr * DRE)390 void discoverUse(const DeclRefExpr *DRE) { Uses->insert(DRE); }
391
392 // Stop tracking the DRE as it's been fully figured out.
claimUse(const DeclRefExpr * DRE)393 void claimUse(const DeclRefExpr *DRE) {
394 assert(Uses->count(DRE) &&
395 "DRE not found or claimed by multiple matchers!");
396 Uses->erase(DRE);
397 }
398
399 // A variable is unclaimed if at least one use is unclaimed.
hasUnclaimedUses(const VarDecl * VD) const400 bool hasUnclaimedUses(const VarDecl *VD) const {
401 // FIXME: Can this be less linear? Maybe maintain a map from VDs to DREs?
402 return any_of(*Uses, [VD](const DeclRefExpr *DRE) {
403 return DRE->getDecl()->getCanonicalDecl() == VD->getCanonicalDecl();
404 });
405 }
406
discoverDecl(const DeclStmt * DS)407 void discoverDecl(const DeclStmt *DS) {
408 for (const Decl *D : DS->decls()) {
409 if (const auto *VD = dyn_cast<VarDecl>(D)) {
410 // FIXME: Assertion temporarily disabled due to a bug in
411 // ASTMatcher internal behavior in presence of GNU
412 // statement-expressions. We need to properly investigate this
413 // because it can screw up our algorithm in other ways.
414 // assert(Defs.count(VD) == 0 && "Definition already discovered!");
415 Defs[VD] = DS;
416 }
417 }
418 }
419
lookupDecl(const VarDecl * VD) const420 const DeclStmt *lookupDecl(const VarDecl *VD) const {
421 auto It = Defs.find(VD);
422 assert(It != Defs.end() && "Definition never discovered!");
423 return It->second;
424 }
425 };
426 } // namespace
427
428 namespace {
429 // Strategy is a map from variables to the way we plan to emit fixes for
430 // these variables. It is figured out gradually by trying different fixes
431 // for different variables depending on gadgets in which these variables
432 // participate.
433 class Strategy {
434 public:
435 enum class Kind {
436 Wontfix, // We don't plan to emit a fixit for this variable.
437 Span, // We recommend replacing the variable with std::span.
438 Iterator, // We recommend replacing the variable with std::span::iterator.
439 Array, // We recommend replacing the variable with std::array.
440 Vector // We recommend replacing the variable with std::vector.
441 };
442
443 private:
444 using MapTy = llvm::DenseMap<const VarDecl *, Kind>;
445
446 MapTy Map;
447
448 public:
449 Strategy() = default;
450 Strategy(const Strategy &) = delete; // Let's avoid copies.
451 Strategy(Strategy &&) = default;
452
set(const VarDecl * VD,Kind K)453 void set(const VarDecl *VD, Kind K) {
454 Map[VD] = K;
455 }
456
lookup(const VarDecl * VD) const457 Kind lookup(const VarDecl *VD) const {
458 auto I = Map.find(VD);
459 if (I == Map.end())
460 return Kind::Wontfix;
461
462 return I->second;
463 }
464 };
465 } // namespace
466
467 /// Scan the function and return a list of gadgets found with provided kits.
findGadgets(const Decl * D)468 static std::tuple<FixableGadgetList, WarningGadgetList, DeclUseTracker> findGadgets(const Decl *D) {
469
470 struct GadgetFinderCallback : MatchFinder::MatchCallback {
471 FixableGadgetList FixableGadgets;
472 WarningGadgetList WarningGadgets;
473 DeclUseTracker Tracker;
474
475 void run(const MatchFinder::MatchResult &Result) override {
476 // In debug mode, assert that we've found exactly one gadget.
477 // This helps us avoid conflicts in .bind() tags.
478 #if NDEBUG
479 #define NEXT return
480 #else
481 [[maybe_unused]] int numFound = 0;
482 #define NEXT ++numFound
483 #endif
484
485 if (const auto *DRE = Result.Nodes.getNodeAs<DeclRefExpr>("any_dre")) {
486 Tracker.discoverUse(DRE);
487 NEXT;
488 }
489
490 if (const auto *DS = Result.Nodes.getNodeAs<DeclStmt>("any_ds")) {
491 Tracker.discoverDecl(DS);
492 NEXT;
493 }
494
495 // Figure out which matcher we've found, and call the appropriate
496 // subclass constructor.
497 // FIXME: Can we do this more logarithmically?
498 #define FIXABLE_GADGET(name) \
499 if (Result.Nodes.getNodeAs<Stmt>(#name)) { \
500 FixableGadgets.push_back(std::make_unique<name ## Gadget>(Result)); \
501 NEXT; \
502 }
503 #include "clang/Analysis/Analyses/UnsafeBufferUsageGadgets.def"
504 #define WARNING_GADGET(name) \
505 if (Result.Nodes.getNodeAs<Stmt>(#name)) { \
506 WarningGadgets.push_back(std::make_unique<name ## Gadget>(Result)); \
507 NEXT; \
508 }
509 #include "clang/Analysis/Analyses/UnsafeBufferUsageGadgets.def"
510
511 assert(numFound >= 1 && "Gadgets not found in match result!");
512 assert(numFound <= 1 && "Conflicting bind tags in gadgets!");
513 }
514 };
515
516 MatchFinder M;
517 GadgetFinderCallback CB;
518
519 // clang-format off
520 M.addMatcher(
521 stmt(forEveryDescendant(
522 stmt(anyOf(
523 // Add Gadget::matcher() for every gadget in the registry.
524 #define GADGET(x) \
525 x ## Gadget::matcher().bind(#x),
526 #include "clang/Analysis/Analyses/UnsafeBufferUsageGadgets.def"
527 // In parallel, match all DeclRefExprs so that to find out
528 // whether there are any uncovered by gadgets.
529 declRefExpr(anyOf(hasPointerType(), hasArrayType()),
530 to(varDecl())).bind("any_dre"),
531 // Also match DeclStmts because we'll need them when fixing
532 // their underlying VarDecls that otherwise don't have
533 // any backreferences to DeclStmts.
534 declStmt().bind("any_ds")
535 ))
536 // FIXME: Idiomatically there should be a forCallable(equalsNode(D))
537 // here, to make sure that the statement actually belongs to the
538 // function and not to a nested function. However, forCallable uses
539 // ParentMap which can't be used before the AST is fully constructed.
540 // The original problem doesn't sound like it needs ParentMap though,
541 // maybe there's a more direct solution?
542 )),
543 &CB
544 );
545 // clang-format on
546
547 M.match(*D->getBody(), D->getASTContext());
548
549 // Gadgets "claim" variables they're responsible for. Once this loop finishes,
550 // the tracker will only track DREs that weren't claimed by any gadgets,
551 // i.e. not understood by the analysis.
552 for (const auto &G : CB.FixableGadgets) {
553 for (const auto *DRE : G->getClaimedVarUseSites()) {
554 CB.Tracker.claimUse(DRE);
555 }
556 }
557
558 return {std::move(CB.FixableGadgets), std::move(CB.WarningGadgets), std::move(CB.Tracker)};
559 }
560
561 struct WarningGadgetSets {
562 std::map<const VarDecl *, std::set<std::unique_ptr<WarningGadget>>> byVar;
563 // These Gadgets are not related to pointer variables (e. g. temporaries).
564 llvm::SmallVector<std::unique_ptr<WarningGadget>, 16> noVar;
565 };
566
567 static WarningGadgetSets
groupWarningGadgetsByVar(WarningGadgetList && AllUnsafeOperations)568 groupWarningGadgetsByVar(WarningGadgetList &&AllUnsafeOperations) {
569 WarningGadgetSets result;
570 // If some gadgets cover more than one
571 // variable, they'll appear more than once in the map.
572 for (auto &G : AllUnsafeOperations) {
573 DeclUseList ClaimedVarUseSites = G->getClaimedVarUseSites();
574
575 bool AssociatedWithVarDecl = false;
576 for (const DeclRefExpr *DRE : ClaimedVarUseSites) {
577 if (const auto *VD = dyn_cast<VarDecl>(DRE->getDecl())) {
578 result.byVar[VD].emplace(std::move(G));
579 AssociatedWithVarDecl = true;
580 }
581 }
582
583 if (!AssociatedWithVarDecl) {
584 result.noVar.emplace_back(std::move(G));
585 continue;
586 }
587 }
588 return result;
589 }
590
591 struct FixableGadgetSets {
592 std::map<const VarDecl *, std::set<std::unique_ptr<FixableGadget>>> byVar;
593 };
594
595 static FixableGadgetSets
groupFixablesByVar(FixableGadgetList && AllFixableOperations)596 groupFixablesByVar(FixableGadgetList &&AllFixableOperations) {
597 FixableGadgetSets FixablesForUnsafeVars;
598 for (auto &F : AllFixableOperations) {
599 DeclUseList DREs = F->getClaimedVarUseSites();
600
601 for (const DeclRefExpr *DRE : DREs) {
602 if (const auto *VD = dyn_cast<VarDecl>(DRE->getDecl())) {
603 FixablesForUnsafeVars.byVar[VD].emplace(std::move(F));
604 }
605 }
606 }
607 return FixablesForUnsafeVars;
608 }
609
610 static std::map<const VarDecl *, FixItList>
getFixIts(FixableGadgetSets & FixablesForUnsafeVars,const Strategy & S)611 getFixIts(FixableGadgetSets &FixablesForUnsafeVars, const Strategy &S) {
612 std::map<const VarDecl *, FixItList> FixItsForVariable;
613 for (const auto &[VD, Fixables] : FixablesForUnsafeVars.byVar) {
614 // TODO fixVariable - fixit for the variable itself
615 bool ImpossibleToFix = false;
616 llvm::SmallVector<FixItHint, 16> FixItsForVD;
617 for (const auto &F : Fixables) {
618 llvm::Optional<FixItList> Fixits = F->getFixits(S);
619 if (!Fixits) {
620 ImpossibleToFix = true;
621 break;
622 } else {
623 const FixItList CorrectFixes = Fixits.value();
624 FixItsForVD.insert(FixItsForVD.end(), CorrectFixes.begin(),
625 CorrectFixes.end());
626 }
627 }
628 if (ImpossibleToFix)
629 FixItsForVariable.erase(VD);
630 else
631 FixItsForVariable[VD].insert(FixItsForVariable[VD].end(),
632 FixItsForVD.begin(), FixItsForVD.end());
633 }
634 return FixItsForVariable;
635 }
636
637 static Strategy
getNaiveStrategy(const llvm::SmallVectorImpl<const VarDecl * > & UnsafeVars)638 getNaiveStrategy(const llvm::SmallVectorImpl<const VarDecl *> &UnsafeVars) {
639 Strategy S;
640 for (const VarDecl *VD : UnsafeVars) {
641 S.set(VD, Strategy::Kind::Span);
642 }
643 return S;
644 }
645
checkUnsafeBufferUsage(const Decl * D,UnsafeBufferUsageHandler & Handler)646 void clang::checkUnsafeBufferUsage(const Decl *D,
647 UnsafeBufferUsageHandler &Handler) {
648 assert(D && D->getBody());
649
650 WarningGadgetSets UnsafeOps;
651 FixableGadgetSets FixablesForUnsafeVars;
652 DeclUseTracker Tracker;
653
654 {
655 auto [FixableGadgets, WarningGadgets, TrackerRes] = findGadgets(D);
656 UnsafeOps = groupWarningGadgetsByVar(std::move(WarningGadgets));
657 FixablesForUnsafeVars = groupFixablesByVar(std::move(FixableGadgets));
658 Tracker = std::move(TrackerRes);
659 }
660
661 // Filter out non-local vars and vars with unclaimed DeclRefExpr-s.
662 for (auto it = FixablesForUnsafeVars.byVar.cbegin();
663 it != FixablesForUnsafeVars.byVar.cend();) {
664 // FIXME: Support ParmVarDecl as well.
665 if (!it->first->isLocalVarDecl() || Tracker.hasUnclaimedUses(it->first)) {
666 it = FixablesForUnsafeVars.byVar.erase(it);
667 } else {
668 ++it;
669 }
670 }
671
672 llvm::SmallVector<const VarDecl *, 16> UnsafeVars;
673 for (const auto &[VD, ignore] : FixablesForUnsafeVars.byVar)
674 UnsafeVars.push_back(VD);
675
676 Strategy NaiveStrategy = getNaiveStrategy(UnsafeVars);
677 std::map<const VarDecl *, FixItList> FixItsForVariable =
678 getFixIts(FixablesForUnsafeVars, NaiveStrategy);
679
680 // FIXME Detect overlapping FixIts.
681
682 for (const auto &G : UnsafeOps.noVar) {
683 Handler.handleUnsafeOperation(G->getBaseStmt(), /*IsRelatedToDecl=*/false);
684 }
685
686 for (const auto &[VD, WarningGadgets] : UnsafeOps.byVar) {
687 auto FixItsIt = FixItsForVariable.find(VD);
688 Handler.handleFixableVariable(VD, FixItsIt != FixItsForVariable.end()
689 ? std::move(FixItsIt->second)
690 : FixItList{});
691 for (const auto &G : WarningGadgets) {
692 Handler.handleUnsafeOperation(G->getBaseStmt(), /*IsRelatedToDecl=*/true);
693 }
694 }
695 }
696