diff options
Diffstat (limited to 'clang/lib/Analysis/UnsafeBufferUsage.cpp')
-rw-r--r-- | clang/lib/Analysis/UnsafeBufferUsage.cpp | 695 |
1 files changed, 695 insertions, 0 deletions
diff --git a/clang/lib/Analysis/UnsafeBufferUsage.cpp b/clang/lib/Analysis/UnsafeBufferUsage.cpp new file mode 100644 index 000000000000..2f1417487967 --- /dev/null +++ b/clang/lib/Analysis/UnsafeBufferUsage.cpp @@ -0,0 +1,695 @@ +//===- UnsafeBufferUsage.cpp - Replace pointers with modern C++ -----------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "clang/Analysis/Analyses/UnsafeBufferUsage.h" +#include "clang/AST/RecursiveASTVisitor.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "llvm/ADT/SmallVector.h" +#include <memory> +#include <optional> + +using namespace llvm; +using namespace clang; +using namespace ast_matchers; + +namespace clang::ast_matchers { +// A `RecursiveASTVisitor` that traverses all descendants of a given node "n" +// except for those belonging to a different callable of "n". +class MatchDescendantVisitor + : public RecursiveASTVisitor<MatchDescendantVisitor> { +public: + typedef RecursiveASTVisitor<MatchDescendantVisitor> VisitorBase; + + // Creates an AST visitor that matches `Matcher` on all + // descendants of a given node "n" except for the ones + // belonging to a different callable of "n". + MatchDescendantVisitor(const internal::DynTypedMatcher *Matcher, + internal::ASTMatchFinder *Finder, + internal::BoundNodesTreeBuilder *Builder, + internal::ASTMatchFinder::BindKind Bind) + : Matcher(Matcher), Finder(Finder), Builder(Builder), Bind(Bind), + Matches(false) {} + + // Returns true if a match is found in a subtree of `DynNode`, which belongs + // to the same callable of `DynNode`. + bool findMatch(const DynTypedNode &DynNode) { + Matches = false; + if (const Stmt *StmtNode = DynNode.get<Stmt>()) { + TraverseStmt(const_cast<Stmt *>(StmtNode)); + *Builder = ResultBindings; + return Matches; + } + return false; + } + + // The following are overriding methods from the base visitor class. + // They are public only to allow CRTP to work. They are *not *part + // of the public API of this class. + + // For the matchers so far used in safe buffers, we only need to match + // `Stmt`s. To override more as needed. + + bool TraverseDecl(Decl *Node) { + if (!Node) + return true; + if (!match(*Node)) + return false; + // To skip callables: + if (isa<FunctionDecl, BlockDecl, ObjCMethodDecl>(Node)) + return true; + // Traverse descendants + return VisitorBase::TraverseDecl(Node); + } + + bool TraverseStmt(Stmt *Node, DataRecursionQueue *Queue = nullptr) { + if (!Node) + return true; + if (!match(*Node)) + return false; + // To skip callables: + if (isa<LambdaExpr>(Node)) + return true; + return VisitorBase::TraverseStmt(Node); + } + + bool shouldVisitTemplateInstantiations() const { return true; } + bool shouldVisitImplicitCode() const { + // TODO: let's ignore implicit code for now + return false; + } + +private: + // Sets 'Matched' to true if 'Matcher' matches 'Node' + // + // Returns 'true' if traversal should continue after this function + // returns, i.e. if no match is found or 'Bind' is 'BK_All'. + template <typename T> bool match(const T &Node) { + internal::BoundNodesTreeBuilder RecursiveBuilder(*Builder); + + if (Matcher->matches(DynTypedNode::create(Node), Finder, + &RecursiveBuilder)) { + ResultBindings.addMatch(RecursiveBuilder); + Matches = true; + if (Bind != internal::ASTMatchFinder::BK_All) + return false; // Abort as soon as a match is found. + } + return true; + } + + const internal::DynTypedMatcher *const Matcher; + internal::ASTMatchFinder *const Finder; + internal::BoundNodesTreeBuilder *const Builder; + internal::BoundNodesTreeBuilder ResultBindings; + const internal::ASTMatchFinder::BindKind Bind; + bool Matches; +}; + +AST_MATCHER_P(Stmt, forEveryDescendant, internal::Matcher<Stmt>, innerMatcher) { + const DynTypedMatcher &DTM = static_cast<DynTypedMatcher>(innerMatcher); + + MatchDescendantVisitor Visitor(&DTM, Finder, Builder, ASTMatchFinder::BK_All); + return Visitor.findMatch(DynTypedNode::create(Node)); +} +} // namespace clang::ast_matchers + +namespace { +// Because the analysis revolves around variables and their types, we'll need to +// track uses of variables (aka DeclRefExprs). +using DeclUseList = SmallVector<const DeclRefExpr *, 1>; + +// Convenience typedef. +using FixItList = SmallVector<FixItHint, 4>; + +// Defined below. +class Strategy; +} // namespace + +// Because we're dealing with raw pointers, let's define what we mean by that. +static auto hasPointerType() { + return hasType(hasCanonicalType(pointerType())); +} + +static auto hasArrayType() { + return hasType(hasCanonicalType(arrayType())); +} + +namespace { +/// Gadget is an individual operation in the code that may be of interest to +/// this analysis. Each (non-abstract) subclass corresponds to a specific +/// rigid AST structure that constitutes an operation on a pointer-type object. +/// Discovery of a gadget in the code corresponds to claiming that we understand +/// what this part of code is doing well enough to potentially improve it. +/// Gadgets can be warning (immediately deserving a warning) or fixable (not +/// always deserving a warning per se, but requires our attention to identify +/// it warrants a fixit). +class Gadget { +public: + enum class Kind { +#define GADGET(x) x, +#include "clang/Analysis/Analyses/UnsafeBufferUsageGadgets.def" + }; + + /// Common type of ASTMatchers used for discovering gadgets. + /// Useful for implementing the static matcher() methods + /// that are expected from all non-abstract subclasses. + using Matcher = decltype(stmt()); + + Gadget(Kind K) : K(K) {} + + Kind getKind() const { return K; } + + virtual bool isWarningGadget() const = 0; + virtual const Stmt *getBaseStmt() const = 0; + + /// Returns the list of pointer-type variables on which this gadget performs + /// its operation. Typically, there's only one variable. This isn't a list + /// of all DeclRefExprs in the gadget's AST! + virtual DeclUseList getClaimedVarUseSites() const = 0; + + virtual ~Gadget() = default; + +private: + Kind K; +}; + + +/// Warning gadgets correspond to unsafe code patterns that warrants +/// an immediate warning. +class WarningGadget : public Gadget { +public: + WarningGadget(Kind K) : Gadget(K) {} + + static bool classof(const Gadget *G) { return G->isWarningGadget(); } + bool isWarningGadget() const final { return true; } +}; + +/// Fixable gadgets correspond to code patterns that aren't always unsafe but need to be +/// properly recognized in order to emit fixes. For example, if a raw pointer-type +/// variable is replaced by a safe C++ container, every use of such variable must be +/// carefully considered and possibly updated. +class FixableGadget : public Gadget { +public: + FixableGadget(Kind K) : Gadget(K) {} + + static bool classof(const Gadget *G) { return !G->isWarningGadget(); } + bool isWarningGadget() const final { return false; } + + /// Returns a fixit that would fix the current gadget according to + /// the current strategy. Returns None if the fix cannot be produced; + /// returns an empty list if no fixes are necessary. + virtual std::optional<FixItList> getFixits(const Strategy &) const { + return std::nullopt; + } +}; + +using FixableGadgetList = std::vector<std::unique_ptr<FixableGadget>>; +using WarningGadgetList = std::vector<std::unique_ptr<WarningGadget>>; + +/// An increment of a pointer-type value is unsafe as it may run the pointer +/// out of bounds. +class IncrementGadget : public WarningGadget { + static constexpr const char *const OpTag = "op"; + const UnaryOperator *Op; + +public: + IncrementGadget(const MatchFinder::MatchResult &Result) + : WarningGadget(Kind::Increment), + Op(Result.Nodes.getNodeAs<UnaryOperator>(OpTag)) {} + + static bool classof(const Gadget *G) { + return G->getKind() == Kind::Increment; + } + + static Matcher matcher() { + return stmt(unaryOperator( + hasOperatorName("++"), + hasUnaryOperand(ignoringParenImpCasts(hasPointerType())) + ).bind(OpTag)); + } + + const UnaryOperator *getBaseStmt() const override { return Op; } + + DeclUseList getClaimedVarUseSites() const override { + SmallVector<const DeclRefExpr *, 2> Uses; + if (const auto *DRE = + dyn_cast<DeclRefExpr>(Op->getSubExpr()->IgnoreParenImpCasts())) { + Uses.push_back(DRE); + } + + return std::move(Uses); + } +}; + +/// A decrement of a pointer-type value is unsafe as it may run the pointer +/// out of bounds. +class DecrementGadget : public WarningGadget { + static constexpr const char *const OpTag = "op"; + const UnaryOperator *Op; + +public: + DecrementGadget(const MatchFinder::MatchResult &Result) + : WarningGadget(Kind::Decrement), + Op(Result.Nodes.getNodeAs<UnaryOperator>(OpTag)) {} + + static bool classof(const Gadget *G) { + return G->getKind() == Kind::Decrement; + } + + static Matcher matcher() { + return stmt(unaryOperator( + hasOperatorName("--"), + hasUnaryOperand(ignoringParenImpCasts(hasPointerType())) + ).bind(OpTag)); + } + + const UnaryOperator *getBaseStmt() const override { return Op; } + + DeclUseList getClaimedVarUseSites() const override { + if (const auto *DRE = + dyn_cast<DeclRefExpr>(Op->getSubExpr()->IgnoreParenImpCasts())) { + return {DRE}; + } + + return {}; + } +}; + +/// Array subscript expressions on raw pointers as if they're arrays. Unsafe as +/// it doesn't have any bounds checks for the array. +class ArraySubscriptGadget : public WarningGadget { + static constexpr const char *const ArraySubscrTag = "arraySubscr"; + const ArraySubscriptExpr *ASE; + +public: + ArraySubscriptGadget(const MatchFinder::MatchResult &Result) + : WarningGadget(Kind::ArraySubscript), + ASE(Result.Nodes.getNodeAs<ArraySubscriptExpr>(ArraySubscrTag)) {} + + static bool classof(const Gadget *G) { + return G->getKind() == Kind::ArraySubscript; + } + + static Matcher matcher() { + // FIXME: What if the index is integer literal 0? Should this be + // a safe gadget in this case? + // clang-format off + return stmt(arraySubscriptExpr( + hasBase(ignoringParenImpCasts( + anyOf(hasPointerType(), hasArrayType()))), + unless(hasIndex(integerLiteral(equals(0))))) + .bind(ArraySubscrTag)); + // clang-format on + } + + const ArraySubscriptExpr *getBaseStmt() const override { return ASE; } + + DeclUseList getClaimedVarUseSites() const override { + if (const auto *DRE = + dyn_cast<DeclRefExpr>(ASE->getBase()->IgnoreParenImpCasts())) { + return {DRE}; + } + + return {}; + } +}; + +/// A pointer arithmetic expression of one of the forms: +/// \code +/// ptr + n | n + ptr | ptr - n | ptr += n | ptr -= n +/// \endcode +class PointerArithmeticGadget : public WarningGadget { + static constexpr const char *const PointerArithmeticTag = "ptrAdd"; + static constexpr const char *const PointerArithmeticPointerTag = "ptrAddPtr"; + const BinaryOperator *PA; // pointer arithmetic expression + const Expr * Ptr; // the pointer expression in `PA` + +public: + PointerArithmeticGadget(const MatchFinder::MatchResult &Result) + : WarningGadget(Kind::PointerArithmetic), + PA(Result.Nodes.getNodeAs<BinaryOperator>(PointerArithmeticTag)), + Ptr(Result.Nodes.getNodeAs<Expr>(PointerArithmeticPointerTag)) {} + + static bool classof(const Gadget *G) { + return G->getKind() == Kind::PointerArithmetic; + } + + static Matcher matcher() { + auto HasIntegerType = anyOf( + hasType(isInteger()), hasType(enumType())); + auto PtrAtRight = allOf(hasOperatorName("+"), + hasRHS(expr(hasPointerType()).bind(PointerArithmeticPointerTag)), + hasLHS(HasIntegerType)); + auto PtrAtLeft = allOf( + anyOf(hasOperatorName("+"), hasOperatorName("-"), + hasOperatorName("+="), hasOperatorName("-=")), + hasLHS(expr(hasPointerType()).bind(PointerArithmeticPointerTag)), + hasRHS(HasIntegerType)); + + return stmt(binaryOperator(anyOf(PtrAtLeft, PtrAtRight)).bind(PointerArithmeticTag)); + } + + const Stmt *getBaseStmt() const override { return PA; } + + DeclUseList getClaimedVarUseSites() const override { + if (const auto *DRE = + dyn_cast<DeclRefExpr>(Ptr->IgnoreParenImpCasts())) { + return {DRE}; + } + + return {}; + } + // FIXME: pointer adding zero should be fine + //FIXME: this gadge will need a fix-it +}; +} // namespace + +namespace { +// An auxiliary tracking facility for the fixit analysis. It helps connect +// declarations to its and make sure we've covered all uses with our analysis +// before we try to fix the declaration. +class DeclUseTracker { + using UseSetTy = SmallSet<const DeclRefExpr *, 16>; + using DefMapTy = DenseMap<const VarDecl *, const DeclStmt *>; + + // Allocate on the heap for easier move. + std::unique_ptr<UseSetTy> Uses{std::make_unique<UseSetTy>()}; + DefMapTy Defs{}; + +public: + DeclUseTracker() = default; + DeclUseTracker(const DeclUseTracker &) = delete; // Let's avoid copies. + DeclUseTracker(DeclUseTracker &&) = default; + DeclUseTracker &operator=(DeclUseTracker &&) = default; + + // Start tracking a freshly discovered DRE. + void discoverUse(const DeclRefExpr *DRE) { Uses->insert(DRE); } + + // Stop tracking the DRE as it's been fully figured out. + void claimUse(const DeclRefExpr *DRE) { + assert(Uses->count(DRE) && + "DRE not found or claimed by multiple matchers!"); + Uses->erase(DRE); + } + + // A variable is unclaimed if at least one use is unclaimed. + bool hasUnclaimedUses(const VarDecl *VD) const { + // FIXME: Can this be less linear? Maybe maintain a map from VDs to DREs? + return any_of(*Uses, [VD](const DeclRefExpr *DRE) { + return DRE->getDecl()->getCanonicalDecl() == VD->getCanonicalDecl(); + }); + } + + void discoverDecl(const DeclStmt *DS) { + for (const Decl *D : DS->decls()) { + if (const auto *VD = dyn_cast<VarDecl>(D)) { + // FIXME: Assertion temporarily disabled due to a bug in + // ASTMatcher internal behavior in presence of GNU + // statement-expressions. We need to properly investigate this + // because it can screw up our algorithm in other ways. + // assert(Defs.count(VD) == 0 && "Definition already discovered!"); + Defs[VD] = DS; + } + } + } + + const DeclStmt *lookupDecl(const VarDecl *VD) const { + auto It = Defs.find(VD); + assert(It != Defs.end() && "Definition never discovered!"); + return It->second; + } +}; +} // namespace + +namespace { +// Strategy is a map from variables to the way we plan to emit fixes for +// these variables. It is figured out gradually by trying different fixes +// for different variables depending on gadgets in which these variables +// participate. +class Strategy { +public: + enum class Kind { + Wontfix, // We don't plan to emit a fixit for this variable. + Span, // We recommend replacing the variable with std::span. + Iterator, // We recommend replacing the variable with std::span::iterator. + Array, // We recommend replacing the variable with std::array. + Vector // We recommend replacing the variable with std::vector. + }; + +private: + using MapTy = llvm::DenseMap<const VarDecl *, Kind>; + + MapTy Map; + +public: + Strategy() = default; + Strategy(const Strategy &) = delete; // Let's avoid copies. + Strategy(Strategy &&) = default; + + void set(const VarDecl *VD, Kind K) { + Map[VD] = K; + } + + Kind lookup(const VarDecl *VD) const { + auto I = Map.find(VD); + if (I == Map.end()) + return Kind::Wontfix; + + return I->second; + } +}; +} // namespace + +/// Scan the function and return a list of gadgets found with provided kits. +static std::tuple<FixableGadgetList, WarningGadgetList, DeclUseTracker> findGadgets(const Decl *D) { + + struct GadgetFinderCallback : MatchFinder::MatchCallback { + FixableGadgetList FixableGadgets; + WarningGadgetList WarningGadgets; + DeclUseTracker Tracker; + + void run(const MatchFinder::MatchResult &Result) override { + // In debug mode, assert that we've found exactly one gadget. + // This helps us avoid conflicts in .bind() tags. +#if NDEBUG +#define NEXT return +#else + [[maybe_unused]] int numFound = 0; +#define NEXT ++numFound +#endif + + if (const auto *DRE = Result.Nodes.getNodeAs<DeclRefExpr>("any_dre")) { + Tracker.discoverUse(DRE); + NEXT; + } + + if (const auto *DS = Result.Nodes.getNodeAs<DeclStmt>("any_ds")) { + Tracker.discoverDecl(DS); + NEXT; + } + + // Figure out which matcher we've found, and call the appropriate + // subclass constructor. + // FIXME: Can we do this more logarithmically? +#define FIXABLE_GADGET(name) \ + if (Result.Nodes.getNodeAs<Stmt>(#name)) { \ + FixableGadgets.push_back(std::make_unique<name ## Gadget>(Result)); \ + NEXT; \ + } +#include "clang/Analysis/Analyses/UnsafeBufferUsageGadgets.def" +#define WARNING_GADGET(name) \ + if (Result.Nodes.getNodeAs<Stmt>(#name)) { \ + WarningGadgets.push_back(std::make_unique<name ## Gadget>(Result)); \ + NEXT; \ + } +#include "clang/Analysis/Analyses/UnsafeBufferUsageGadgets.def" + + assert(numFound >= 1 && "Gadgets not found in match result!"); + assert(numFound <= 1 && "Conflicting bind tags in gadgets!"); + } + }; + + MatchFinder M; + GadgetFinderCallback CB; + + // clang-format off + M.addMatcher( + stmt(forEveryDescendant( + stmt(anyOf( + // Add Gadget::matcher() for every gadget in the registry. +#define GADGET(x) \ + x ## Gadget::matcher().bind(#x), +#include "clang/Analysis/Analyses/UnsafeBufferUsageGadgets.def" + // In parallel, match all DeclRefExprs so that to find out + // whether there are any uncovered by gadgets. + declRefExpr(anyOf(hasPointerType(), hasArrayType()), + to(varDecl())).bind("any_dre"), + // Also match DeclStmts because we'll need them when fixing + // their underlying VarDecls that otherwise don't have + // any backreferences to DeclStmts. + declStmt().bind("any_ds") + )) + // FIXME: Idiomatically there should be a forCallable(equalsNode(D)) + // here, to make sure that the statement actually belongs to the + // function and not to a nested function. However, forCallable uses + // ParentMap which can't be used before the AST is fully constructed. + // The original problem doesn't sound like it needs ParentMap though, + // maybe there's a more direct solution? + )), + &CB + ); + // clang-format on + + M.match(*D->getBody(), D->getASTContext()); + + // Gadgets "claim" variables they're responsible for. Once this loop finishes, + // the tracker will only track DREs that weren't claimed by any gadgets, + // i.e. not understood by the analysis. + for (const auto &G : CB.FixableGadgets) { + for (const auto *DRE : G->getClaimedVarUseSites()) { + CB.Tracker.claimUse(DRE); + } + } + + return {std::move(CB.FixableGadgets), std::move(CB.WarningGadgets), std::move(CB.Tracker)}; +} + +struct WarningGadgetSets { + std::map<const VarDecl *, std::set<std::unique_ptr<WarningGadget>>> byVar; + // These Gadgets are not related to pointer variables (e. g. temporaries). + llvm::SmallVector<std::unique_ptr<WarningGadget>, 16> noVar; +}; + +static WarningGadgetSets +groupWarningGadgetsByVar(WarningGadgetList &&AllUnsafeOperations) { + WarningGadgetSets result; + // If some gadgets cover more than one + // variable, they'll appear more than once in the map. + for (auto &G : AllUnsafeOperations) { + DeclUseList ClaimedVarUseSites = G->getClaimedVarUseSites(); + + bool AssociatedWithVarDecl = false; + for (const DeclRefExpr *DRE : ClaimedVarUseSites) { + if (const auto *VD = dyn_cast<VarDecl>(DRE->getDecl())) { + result.byVar[VD].emplace(std::move(G)); + AssociatedWithVarDecl = true; + } + } + + if (!AssociatedWithVarDecl) { + result.noVar.emplace_back(std::move(G)); + continue; + } + } + return result; +} + +struct FixableGadgetSets { + std::map<const VarDecl *, std::set<std::unique_ptr<FixableGadget>>> byVar; +}; + +static FixableGadgetSets +groupFixablesByVar(FixableGadgetList &&AllFixableOperations) { + FixableGadgetSets FixablesForUnsafeVars; + for (auto &F : AllFixableOperations) { + DeclUseList DREs = F->getClaimedVarUseSites(); + + for (const DeclRefExpr *DRE : DREs) { + if (const auto *VD = dyn_cast<VarDecl>(DRE->getDecl())) { + FixablesForUnsafeVars.byVar[VD].emplace(std::move(F)); + } + } + } + return FixablesForUnsafeVars; +} + +static std::map<const VarDecl *, FixItList> +getFixIts(FixableGadgetSets &FixablesForUnsafeVars, const Strategy &S) { + std::map<const VarDecl *, FixItList> FixItsForVariable; + for (const auto &[VD, Fixables] : FixablesForUnsafeVars.byVar) { + // TODO fixVariable - fixit for the variable itself + bool ImpossibleToFix = false; + llvm::SmallVector<FixItHint, 16> FixItsForVD; + for (const auto &F : Fixables) { + llvm::Optional<FixItList> Fixits = F->getFixits(S); + if (!Fixits) { + ImpossibleToFix = true; + break; + } else { + const FixItList CorrectFixes = Fixits.value(); + FixItsForVD.insert(FixItsForVD.end(), CorrectFixes.begin(), + CorrectFixes.end()); + } + } + if (ImpossibleToFix) + FixItsForVariable.erase(VD); + else + FixItsForVariable[VD].insert(FixItsForVariable[VD].end(), + FixItsForVD.begin(), FixItsForVD.end()); + } + return FixItsForVariable; +} + +static Strategy +getNaiveStrategy(const llvm::SmallVectorImpl<const VarDecl *> &UnsafeVars) { + Strategy S; + for (const VarDecl *VD : UnsafeVars) { + S.set(VD, Strategy::Kind::Span); + } + return S; +} + +void clang::checkUnsafeBufferUsage(const Decl *D, + UnsafeBufferUsageHandler &Handler) { + assert(D && D->getBody()); + + WarningGadgetSets UnsafeOps; + FixableGadgetSets FixablesForUnsafeVars; + DeclUseTracker Tracker; + + { + auto [FixableGadgets, WarningGadgets, TrackerRes] = findGadgets(D); + UnsafeOps = groupWarningGadgetsByVar(std::move(WarningGadgets)); + FixablesForUnsafeVars = groupFixablesByVar(std::move(FixableGadgets)); + Tracker = std::move(TrackerRes); + } + + // Filter out non-local vars and vars with unclaimed DeclRefExpr-s. + for (auto it = FixablesForUnsafeVars.byVar.cbegin(); + it != FixablesForUnsafeVars.byVar.cend();) { + // FIXME: Support ParmVarDecl as well. + if (!it->first->isLocalVarDecl() || Tracker.hasUnclaimedUses(it->first)) { + it = FixablesForUnsafeVars.byVar.erase(it); + } else { + ++it; + } + } + + llvm::SmallVector<const VarDecl *, 16> UnsafeVars; + for (const auto &[VD, ignore] : FixablesForUnsafeVars.byVar) + UnsafeVars.push_back(VD); + + Strategy NaiveStrategy = getNaiveStrategy(UnsafeVars); + std::map<const VarDecl *, FixItList> FixItsForVariable = + getFixIts(FixablesForUnsafeVars, NaiveStrategy); + + // FIXME Detect overlapping FixIts. + + for (const auto &G : UnsafeOps.noVar) { + Handler.handleUnsafeOperation(G->getBaseStmt(), /*IsRelatedToDecl=*/false); + } + + for (const auto &[VD, WarningGadgets] : UnsafeOps.byVar) { + auto FixItsIt = FixItsForVariable.find(VD); + Handler.handleFixableVariable(VD, FixItsIt != FixItsForVariable.end() + ? std::move(FixItsIt->second) + : FixItList{}); + for (const auto &G : WarningGadgets) { + Handler.handleUnsafeOperation(G->getBaseStmt(), /*IsRelatedToDecl=*/true); + } + } +} |