Path: blob/main/contrib/llvm-project/clang/lib/Analysis/UnsafeBufferUsage.cpp
35233 views
//===- UnsafeBufferUsage.cpp - Replace pointers with modern C++ -----------===//1//2// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.3// See https://llvm.org/LICENSE.txt for license information.4// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception5//6//===----------------------------------------------------------------------===//78#include "clang/Analysis/Analyses/UnsafeBufferUsage.h"9#include "clang/AST/ASTContext.h"10#include "clang/AST/Decl.h"11#include "clang/AST/Expr.h"12#include "clang/AST/RecursiveASTVisitor.h"13#include "clang/AST/Stmt.h"14#include "clang/AST/StmtVisitor.h"15#include "clang/ASTMatchers/ASTMatchFinder.h"16#include "clang/ASTMatchers/ASTMatchers.h"17#include "clang/Basic/CharInfo.h"18#include "clang/Basic/SourceLocation.h"19#include "clang/Lex/Lexer.h"20#include "clang/Lex/Preprocessor.h"21#include "llvm/ADT/APSInt.h"22#include "llvm/ADT/SmallVector.h"23#include "llvm/ADT/StringRef.h"24#include "llvm/Support/Casting.h"25#include <memory>26#include <optional>27#include <queue>28#include <sstream>2930using namespace llvm;31using namespace clang;32using namespace ast_matchers;3334#ifndef NDEBUG35namespace {36class StmtDebugPrinter37: public ConstStmtVisitor<StmtDebugPrinter, std::string> {38public:39std::string VisitStmt(const Stmt *S) { return S->getStmtClassName(); }4041std::string VisitBinaryOperator(const BinaryOperator *BO) {42return "BinaryOperator(" + BO->getOpcodeStr().str() + ")";43}4445std::string VisitUnaryOperator(const UnaryOperator *UO) {46return "UnaryOperator(" + UO->getOpcodeStr(UO->getOpcode()).str() + ")";47}4849std::string VisitImplicitCastExpr(const ImplicitCastExpr *ICE) {50return "ImplicitCastExpr(" + std::string(ICE->getCastKindName()) + ")";51}52};5354// Returns a string of ancestor `Stmt`s of the given `DRE` in such a form:55// "DRE ==> parent-of-DRE ==> grandparent-of-DRE ==> ...".56static std::string getDREAncestorString(const DeclRefExpr *DRE,57ASTContext &Ctx) {58std::stringstream SS;59const Stmt *St = DRE;60StmtDebugPrinter StmtPriner;6162do {63SS << StmtPriner.Visit(St);6465DynTypedNodeList StParents = Ctx.getParents(*St);6667if (StParents.size() > 1)68return "unavailable due to multiple parents";69if (StParents.size() == 0)70break;71St = StParents.begin()->get<Stmt>();72if (St)73SS << " ==> ";74} while (St);75return SS.str();76}77} // namespace78#endif /* NDEBUG */7980namespace clang::ast_matchers {81// A `RecursiveASTVisitor` that traverses all descendants of a given node "n"82// except for those belonging to a different callable of "n".83class MatchDescendantVisitor84: public RecursiveASTVisitor<MatchDescendantVisitor> {85public:86typedef RecursiveASTVisitor<MatchDescendantVisitor> VisitorBase;8788// Creates an AST visitor that matches `Matcher` on all89// descendants of a given node "n" except for the ones90// belonging to a different callable of "n".91MatchDescendantVisitor(const internal::DynTypedMatcher *Matcher,92internal::ASTMatchFinder *Finder,93internal::BoundNodesTreeBuilder *Builder,94internal::ASTMatchFinder::BindKind Bind,95const bool ignoreUnevaluatedContext)96: Matcher(Matcher), Finder(Finder), Builder(Builder), Bind(Bind),97Matches(false), ignoreUnevaluatedContext(ignoreUnevaluatedContext) {}9899// Returns true if a match is found in a subtree of `DynNode`, which belongs100// to the same callable of `DynNode`.101bool findMatch(const DynTypedNode &DynNode) {102Matches = false;103if (const Stmt *StmtNode = DynNode.get<Stmt>()) {104TraverseStmt(const_cast<Stmt *>(StmtNode));105*Builder = ResultBindings;106return Matches;107}108return false;109}110111// The following are overriding methods from the base visitor class.112// They are public only to allow CRTP to work. They are *not *part113// of the public API of this class.114115// For the matchers so far used in safe buffers, we only need to match116// `Stmt`s. To override more as needed.117118bool TraverseDecl(Decl *Node) {119if (!Node)120return true;121if (!match(*Node))122return false;123// To skip callables:124if (isa<FunctionDecl, BlockDecl, ObjCMethodDecl>(Node))125return true;126// Traverse descendants127return VisitorBase::TraverseDecl(Node);128}129130bool TraverseGenericSelectionExpr(GenericSelectionExpr *Node) {131// These are unevaluated, except the result expression.132if (ignoreUnevaluatedContext)133return TraverseStmt(Node->getResultExpr());134return VisitorBase::TraverseGenericSelectionExpr(Node);135}136137bool TraverseUnaryExprOrTypeTraitExpr(UnaryExprOrTypeTraitExpr *Node) {138// Unevaluated context.139if (ignoreUnevaluatedContext)140return true;141return VisitorBase::TraverseUnaryExprOrTypeTraitExpr(Node);142}143144bool TraverseTypeOfExprTypeLoc(TypeOfExprTypeLoc Node) {145// Unevaluated context.146if (ignoreUnevaluatedContext)147return true;148return VisitorBase::TraverseTypeOfExprTypeLoc(Node);149}150151bool TraverseDecltypeTypeLoc(DecltypeTypeLoc Node) {152// Unevaluated context.153if (ignoreUnevaluatedContext)154return true;155return VisitorBase::TraverseDecltypeTypeLoc(Node);156}157158bool TraverseCXXNoexceptExpr(CXXNoexceptExpr *Node) {159// Unevaluated context.160if (ignoreUnevaluatedContext)161return true;162return VisitorBase::TraverseCXXNoexceptExpr(Node);163}164165bool TraverseCXXTypeidExpr(CXXTypeidExpr *Node) {166// Unevaluated context.167if (ignoreUnevaluatedContext)168return true;169return VisitorBase::TraverseCXXTypeidExpr(Node);170}171172bool TraverseStmt(Stmt *Node, DataRecursionQueue *Queue = nullptr) {173if (!Node)174return true;175if (!match(*Node))176return false;177return VisitorBase::TraverseStmt(Node);178}179180bool shouldVisitTemplateInstantiations() const { return true; }181bool shouldVisitImplicitCode() const {182// TODO: let's ignore implicit code for now183return false;184}185186private:187// Sets 'Matched' to true if 'Matcher' matches 'Node'188//189// Returns 'true' if traversal should continue after this function190// returns, i.e. if no match is found or 'Bind' is 'BK_All'.191template <typename T> bool match(const T &Node) {192internal::BoundNodesTreeBuilder RecursiveBuilder(*Builder);193194if (Matcher->matches(DynTypedNode::create(Node), Finder,195&RecursiveBuilder)) {196ResultBindings.addMatch(RecursiveBuilder);197Matches = true;198if (Bind != internal::ASTMatchFinder::BK_All)199return false; // Abort as soon as a match is found.200}201return true;202}203204const internal::DynTypedMatcher *const Matcher;205internal::ASTMatchFinder *const Finder;206internal::BoundNodesTreeBuilder *const Builder;207internal::BoundNodesTreeBuilder ResultBindings;208const internal::ASTMatchFinder::BindKind Bind;209bool Matches;210bool ignoreUnevaluatedContext;211};212213// Because we're dealing with raw pointers, let's define what we mean by that.214static auto hasPointerType() {215return hasType(hasCanonicalType(pointerType()));216}217218static auto hasArrayType() { return hasType(hasCanonicalType(arrayType())); }219220AST_MATCHER_P(Stmt, forEachDescendantEvaluatedStmt, internal::Matcher<Stmt>,221innerMatcher) {222const DynTypedMatcher &DTM = static_cast<DynTypedMatcher>(innerMatcher);223224MatchDescendantVisitor Visitor(&DTM, Finder, Builder, ASTMatchFinder::BK_All,225true);226return Visitor.findMatch(DynTypedNode::create(Node));227}228229AST_MATCHER_P(Stmt, forEachDescendantStmt, internal::Matcher<Stmt>,230innerMatcher) {231const DynTypedMatcher &DTM = static_cast<DynTypedMatcher>(innerMatcher);232233MatchDescendantVisitor Visitor(&DTM, Finder, Builder, ASTMatchFinder::BK_All,234false);235return Visitor.findMatch(DynTypedNode::create(Node));236}237238// Matches a `Stmt` node iff the node is in a safe-buffer opt-out region239AST_MATCHER_P(Stmt, notInSafeBufferOptOut, const UnsafeBufferUsageHandler *,240Handler) {241return !Handler->isSafeBufferOptOut(Node.getBeginLoc());242}243244AST_MATCHER_P(Stmt, ignoreUnsafeBufferInContainer,245const UnsafeBufferUsageHandler *, Handler) {246return Handler->ignoreUnsafeBufferInContainer(Node.getBeginLoc());247}248249AST_MATCHER_P(CastExpr, castSubExpr, internal::Matcher<Expr>, innerMatcher) {250return innerMatcher.matches(*Node.getSubExpr(), Finder, Builder);251}252253// Matches a `UnaryOperator` whose operator is pre-increment:254AST_MATCHER(UnaryOperator, isPreInc) {255return Node.getOpcode() == UnaryOperator::Opcode::UO_PreInc;256}257258// Returns a matcher that matches any expression 'e' such that `innerMatcher`259// matches 'e' and 'e' is in an Unspecified Lvalue Context.260static auto isInUnspecifiedLvalueContext(internal::Matcher<Expr> innerMatcher) {261// clang-format off262return263expr(anyOf(264implicitCastExpr(265hasCastKind(CastKind::CK_LValueToRValue),266castSubExpr(innerMatcher)),267binaryOperator(268hasAnyOperatorName("="),269hasLHS(innerMatcher)270)271));272// clang-format on273}274275// Returns a matcher that matches any expression `e` such that `InnerMatcher`276// matches `e` and `e` is in an Unspecified Pointer Context (UPC).277static internal::Matcher<Stmt>278isInUnspecifiedPointerContext(internal::Matcher<Stmt> InnerMatcher) {279// A UPC can be280// 1. an argument of a function call (except the callee has [[unsafe_...]]281// attribute), or282// 2. the operand of a pointer-to-(integer or bool) cast operation; or283// 3. the operand of a comparator operation; or284// 4. the operand of a pointer subtraction operation285// (i.e., computing the distance between two pointers); or ...286287// clang-format off288auto CallArgMatcher = callExpr(289forEachArgumentWithParamType(290InnerMatcher,291isAnyPointer() /* array also decays to pointer type*/),292unless(callee(293functionDecl(hasAttr(attr::UnsafeBufferUsage)))));294295auto CastOperandMatcher =296castExpr(anyOf(hasCastKind(CastKind::CK_PointerToIntegral),297hasCastKind(CastKind::CK_PointerToBoolean)),298castSubExpr(allOf(hasPointerType(), InnerMatcher)));299300auto CompOperandMatcher =301binaryOperator(hasAnyOperatorName("!=", "==", "<", "<=", ">", ">="),302eachOf(hasLHS(allOf(hasPointerType(), InnerMatcher)),303hasRHS(allOf(hasPointerType(), InnerMatcher))));304305// A matcher that matches pointer subtractions:306auto PtrSubtractionMatcher =307binaryOperator(hasOperatorName("-"),308// Note that here we need both LHS and RHS to be309// pointer. Then the inner matcher can match any of310// them:311allOf(hasLHS(hasPointerType()),312hasRHS(hasPointerType())),313eachOf(hasLHS(InnerMatcher),314hasRHS(InnerMatcher)));315// clang-format on316317return stmt(anyOf(CallArgMatcher, CastOperandMatcher, CompOperandMatcher,318PtrSubtractionMatcher));319// FIXME: any more cases? (UPC excludes the RHS of an assignment. For now we320// don't have to check that.)321}322323// Returns a matcher that matches any expression 'e' such that `innerMatcher`324// matches 'e' and 'e' is in an unspecified untyped context (i.e the expression325// 'e' isn't evaluated to an RValue). For example, consider the following code:326// int *p = new int[4];327// int *q = new int[4];328// if ((p = q)) {}329// p = q;330// The expression `p = q` in the conditional of the `if` statement331// `if ((p = q))` is evaluated as an RValue, whereas the expression `p = q;`332// in the assignment statement is in an untyped context.333static internal::Matcher<Stmt>334isInUnspecifiedUntypedContext(internal::Matcher<Stmt> InnerMatcher) {335// An unspecified context can be336// 1. A compound statement,337// 2. The body of an if statement338// 3. Body of a loop339auto CompStmt = compoundStmt(forEach(InnerMatcher));340auto IfStmtThen = ifStmt(hasThen(InnerMatcher));341auto IfStmtElse = ifStmt(hasElse(InnerMatcher));342// FIXME: Handle loop bodies.343return stmt(anyOf(CompStmt, IfStmtThen, IfStmtElse));344}345346// Given a two-param std::span construct call, matches iff the call has the347// following forms:348// 1. `std::span<T>{new T[n], n}`, where `n` is a literal or a DRE349// 2. `std::span<T>{new T, 1}`350// 3. `std::span<T>{&var, 1}`351// 4. `std::span<T>{a, n}`, where `a` is of an array-of-T with constant size352// `n`353// 5. `std::span<T>{any, 0}`354AST_MATCHER(CXXConstructExpr, isSafeSpanTwoParamConstruct) {355assert(Node.getNumArgs() == 2 &&356"expecting a two-parameter std::span constructor");357const Expr *Arg0 = Node.getArg(0)->IgnoreImplicit();358const Expr *Arg1 = Node.getArg(1)->IgnoreImplicit();359auto HaveEqualConstantValues = [&Finder](const Expr *E0, const Expr *E1) {360if (auto E0CV = E0->getIntegerConstantExpr(Finder->getASTContext()))361if (auto E1CV = E1->getIntegerConstantExpr(Finder->getASTContext())) {362return APSInt::compareValues(*E0CV, *E1CV) == 0;363}364return false;365};366auto AreSameDRE = [](const Expr *E0, const Expr *E1) {367if (auto *DRE0 = dyn_cast<DeclRefExpr>(E0))368if (auto *DRE1 = dyn_cast<DeclRefExpr>(E1)) {369return DRE0->getDecl() == DRE1->getDecl();370}371return false;372};373std::optional<APSInt> Arg1CV =374Arg1->getIntegerConstantExpr(Finder->getASTContext());375376if (Arg1CV && Arg1CV->isZero())377// Check form 5:378return true;379switch (Arg0->IgnoreImplicit()->getStmtClass()) {380case Stmt::CXXNewExprClass:381if (auto Size = cast<CXXNewExpr>(Arg0)->getArraySize()) {382// Check form 1:383return AreSameDRE((*Size)->IgnoreImplicit(), Arg1) ||384HaveEqualConstantValues(*Size, Arg1);385}386// TODO: what's placeholder type? avoid it for now.387if (!cast<CXXNewExpr>(Arg0)->hasPlaceholderType()) {388// Check form 2:389return Arg1CV && Arg1CV->isOne();390}391break;392case Stmt::UnaryOperatorClass:393if (cast<UnaryOperator>(Arg0)->getOpcode() ==394UnaryOperator::Opcode::UO_AddrOf)395// Check form 3:396return Arg1CV && Arg1CV->isOne();397break;398default:399break;400}401402QualType Arg0Ty = Arg0->IgnoreImplicit()->getType();403404if (Arg0Ty->isConstantArrayType()) {405const APSInt ConstArrSize =406APSInt(cast<ConstantArrayType>(Arg0Ty)->getSize());407408// Check form 4:409return Arg1CV && APSInt::compareValues(ConstArrSize, *Arg1CV) == 0;410}411return false;412}413414AST_MATCHER(ArraySubscriptExpr, isSafeArraySubscript) {415// FIXME: Proper solution:416// - refactor Sema::CheckArrayAccess417// - split safe/OOB/unknown decision logic from diagnostics emitting code418// - e. g. "Try harder to find a NamedDecl to point at in the note."419// already duplicated420// - call both from Sema and from here421422const auto *BaseDRE =423dyn_cast<DeclRefExpr>(Node.getBase()->IgnoreParenImpCasts());424if (!BaseDRE)425return false;426if (!BaseDRE->getDecl())427return false;428const auto *CATy = Finder->getASTContext().getAsConstantArrayType(429BaseDRE->getDecl()->getType());430if (!CATy)431return false;432433if (const auto *IdxLit = dyn_cast<IntegerLiteral>(Node.getIdx())) {434const APInt ArrIdx = IdxLit->getValue();435// FIXME: ArrIdx.isNegative() we could immediately emit an error as that's a436// bug437if (ArrIdx.isNonNegative() &&438ArrIdx.getLimitedValue() < CATy->getLimitedSize())439return true;440}441442return false;443}444445} // namespace clang::ast_matchers446447namespace {448// Because the analysis revolves around variables and their types, we'll need to449// track uses of variables (aka DeclRefExprs).450using DeclUseList = SmallVector<const DeclRefExpr *, 1>;451452// Convenience typedef.453using FixItList = SmallVector<FixItHint, 4>;454} // namespace455456namespace {457/// Gadget is an individual operation in the code that may be of interest to458/// this analysis. Each (non-abstract) subclass corresponds to a specific459/// rigid AST structure that constitutes an operation on a pointer-type object.460/// Discovery of a gadget in the code corresponds to claiming that we understand461/// what this part of code is doing well enough to potentially improve it.462/// Gadgets can be warning (immediately deserving a warning) or fixable (not463/// always deserving a warning per se, but requires our attention to identify464/// it warrants a fixit).465class Gadget {466public:467enum class Kind {468#define GADGET(x) x,469#include "clang/Analysis/Analyses/UnsafeBufferUsageGadgets.def"470};471472/// Common type of ASTMatchers used for discovering gadgets.473/// Useful for implementing the static matcher() methods474/// that are expected from all non-abstract subclasses.475using Matcher = decltype(stmt());476477Gadget(Kind K) : K(K) {}478479Kind getKind() const { return K; }480481#ifndef NDEBUG482StringRef getDebugName() const {483switch (K) {484#define GADGET(x) \485case Kind::x: \486return #x;487#include "clang/Analysis/Analyses/UnsafeBufferUsageGadgets.def"488}489llvm_unreachable("Unhandled Gadget::Kind enum");490}491#endif492493virtual bool isWarningGadget() const = 0;494// TODO remove this method from WarningGadget interface. It's only used for495// debug prints in FixableGadget.496virtual SourceLocation getSourceLoc() const = 0;497498/// Returns the list of pointer-type variables on which this gadget performs499/// its operation. Typically, there's only one variable. This isn't a list500/// of all DeclRefExprs in the gadget's AST!501virtual DeclUseList getClaimedVarUseSites() const = 0;502503virtual ~Gadget() = default;504505private:506Kind K;507};508509/// Warning gadgets correspond to unsafe code patterns that warrants510/// an immediate warning.511class WarningGadget : public Gadget {512public:513WarningGadget(Kind K) : Gadget(K) {}514515static bool classof(const Gadget *G) { return G->isWarningGadget(); }516bool isWarningGadget() const final { return true; }517518virtual void handleUnsafeOperation(UnsafeBufferUsageHandler &Handler,519bool IsRelatedToDecl,520ASTContext &Ctx) const = 0;521};522523/// Fixable gadgets correspond to code patterns that aren't always unsafe but524/// need to be properly recognized in order to emit fixes. For example, if a raw525/// pointer-type variable is replaced by a safe C++ container, every use of such526/// variable must be carefully considered and possibly updated.527class FixableGadget : public Gadget {528public:529FixableGadget(Kind K) : Gadget(K) {}530531static bool classof(const Gadget *G) { return !G->isWarningGadget(); }532bool isWarningGadget() const final { return false; }533534/// Returns a fixit that would fix the current gadget according to535/// the current strategy. Returns std::nullopt if the fix cannot be produced;536/// returns an empty list if no fixes are necessary.537virtual std::optional<FixItList> getFixits(const FixitStrategy &) const {538return std::nullopt;539}540541/// Returns a list of two elements where the first element is the LHS of a542/// pointer assignment statement and the second element is the RHS. This543/// two-element list represents the fact that the LHS buffer gets its bounds544/// information from the RHS buffer. This information will be used later to545/// group all those variables whose types must be modified together to prevent546/// type mismatches.547virtual std::optional<std::pair<const VarDecl *, const VarDecl *>>548getStrategyImplications() const {549return std::nullopt;550}551};552553static auto toSupportedVariable() { return to(varDecl()); }554555using FixableGadgetList = std::vector<std::unique_ptr<FixableGadget>>;556using WarningGadgetList = std::vector<std::unique_ptr<WarningGadget>>;557558/// An increment of a pointer-type value is unsafe as it may run the pointer559/// out of bounds.560class IncrementGadget : public WarningGadget {561static constexpr const char *const OpTag = "op";562const UnaryOperator *Op;563564public:565IncrementGadget(const MatchFinder::MatchResult &Result)566: WarningGadget(Kind::Increment),567Op(Result.Nodes.getNodeAs<UnaryOperator>(OpTag)) {}568569static bool classof(const Gadget *G) {570return G->getKind() == Kind::Increment;571}572573static Matcher matcher() {574return stmt(575unaryOperator(hasOperatorName("++"),576hasUnaryOperand(ignoringParenImpCasts(hasPointerType())))577.bind(OpTag));578}579580void handleUnsafeOperation(UnsafeBufferUsageHandler &Handler,581bool IsRelatedToDecl,582ASTContext &Ctx) const override {583Handler.handleUnsafeOperation(Op, IsRelatedToDecl, Ctx);584}585SourceLocation getSourceLoc() const override { return Op->getBeginLoc(); }586587DeclUseList getClaimedVarUseSites() const override {588SmallVector<const DeclRefExpr *, 2> Uses;589if (const auto *DRE =590dyn_cast<DeclRefExpr>(Op->getSubExpr()->IgnoreParenImpCasts())) {591Uses.push_back(DRE);592}593594return std::move(Uses);595}596};597598/// A decrement of a pointer-type value is unsafe as it may run the pointer599/// out of bounds.600class DecrementGadget : public WarningGadget {601static constexpr const char *const OpTag = "op";602const UnaryOperator *Op;603604public:605DecrementGadget(const MatchFinder::MatchResult &Result)606: WarningGadget(Kind::Decrement),607Op(Result.Nodes.getNodeAs<UnaryOperator>(OpTag)) {}608609static bool classof(const Gadget *G) {610return G->getKind() == Kind::Decrement;611}612613static Matcher matcher() {614return stmt(615unaryOperator(hasOperatorName("--"),616hasUnaryOperand(ignoringParenImpCasts(hasPointerType())))617.bind(OpTag));618}619620void handleUnsafeOperation(UnsafeBufferUsageHandler &Handler,621bool IsRelatedToDecl,622ASTContext &Ctx) const override {623Handler.handleUnsafeOperation(Op, IsRelatedToDecl, Ctx);624}625SourceLocation getSourceLoc() const override { return Op->getBeginLoc(); }626627DeclUseList getClaimedVarUseSites() const override {628if (const auto *DRE =629dyn_cast<DeclRefExpr>(Op->getSubExpr()->IgnoreParenImpCasts())) {630return {DRE};631}632633return {};634}635};636637/// Array subscript expressions on raw pointers as if they're arrays. Unsafe as638/// it doesn't have any bounds checks for the array.639class ArraySubscriptGadget : public WarningGadget {640static constexpr const char *const ArraySubscrTag = "ArraySubscript";641const ArraySubscriptExpr *ASE;642643public:644ArraySubscriptGadget(const MatchFinder::MatchResult &Result)645: WarningGadget(Kind::ArraySubscript),646ASE(Result.Nodes.getNodeAs<ArraySubscriptExpr>(ArraySubscrTag)) {}647648static bool classof(const Gadget *G) {649return G->getKind() == Kind::ArraySubscript;650}651652static Matcher matcher() {653// clang-format off654return stmt(arraySubscriptExpr(655hasBase(ignoringParenImpCasts(656anyOf(hasPointerType(), hasArrayType()))),657unless(anyOf(658isSafeArraySubscript(),659hasIndex(660anyOf(integerLiteral(equals(0)), arrayInitIndexExpr())661)662))).bind(ArraySubscrTag));663// clang-format on664}665666void handleUnsafeOperation(UnsafeBufferUsageHandler &Handler,667bool IsRelatedToDecl,668ASTContext &Ctx) const override {669Handler.handleUnsafeOperation(ASE, IsRelatedToDecl, Ctx);670}671SourceLocation getSourceLoc() const override { return ASE->getBeginLoc(); }672673DeclUseList getClaimedVarUseSites() const override {674if (const auto *DRE =675dyn_cast<DeclRefExpr>(ASE->getBase()->IgnoreParenImpCasts())) {676return {DRE};677}678679return {};680}681};682683/// A pointer arithmetic expression of one of the forms:684/// \code685/// ptr + n | n + ptr | ptr - n | ptr += n | ptr -= n686/// \endcode687class PointerArithmeticGadget : public WarningGadget {688static constexpr const char *const PointerArithmeticTag = "ptrAdd";689static constexpr const char *const PointerArithmeticPointerTag = "ptrAddPtr";690const BinaryOperator *PA; // pointer arithmetic expression691const Expr *Ptr; // the pointer expression in `PA`692693public:694PointerArithmeticGadget(const MatchFinder::MatchResult &Result)695: WarningGadget(Kind::PointerArithmetic),696PA(Result.Nodes.getNodeAs<BinaryOperator>(PointerArithmeticTag)),697Ptr(Result.Nodes.getNodeAs<Expr>(PointerArithmeticPointerTag)) {}698699static bool classof(const Gadget *G) {700return G->getKind() == Kind::PointerArithmetic;701}702703static Matcher matcher() {704auto HasIntegerType = anyOf(hasType(isInteger()), hasType(enumType()));705auto PtrAtRight =706allOf(hasOperatorName("+"),707hasRHS(expr(hasPointerType()).bind(PointerArithmeticPointerTag)),708hasLHS(HasIntegerType));709auto PtrAtLeft =710allOf(anyOf(hasOperatorName("+"), hasOperatorName("-"),711hasOperatorName("+="), hasOperatorName("-=")),712hasLHS(expr(hasPointerType()).bind(PointerArithmeticPointerTag)),713hasRHS(HasIntegerType));714715return stmt(binaryOperator(anyOf(PtrAtLeft, PtrAtRight))716.bind(PointerArithmeticTag));717}718719void handleUnsafeOperation(UnsafeBufferUsageHandler &Handler,720bool IsRelatedToDecl,721ASTContext &Ctx) const override {722Handler.handleUnsafeOperation(PA, IsRelatedToDecl, Ctx);723}724SourceLocation getSourceLoc() const override { return PA->getBeginLoc(); }725726DeclUseList getClaimedVarUseSites() const override {727if (const auto *DRE = dyn_cast<DeclRefExpr>(Ptr->IgnoreParenImpCasts())) {728return {DRE};729}730731return {};732}733// FIXME: pointer adding zero should be fine734// FIXME: this gadge will need a fix-it735};736737class SpanTwoParamConstructorGadget : public WarningGadget {738static constexpr const char *const SpanTwoParamConstructorTag =739"spanTwoParamConstructor";740const CXXConstructExpr *Ctor; // the span constructor expression741742public:743SpanTwoParamConstructorGadget(const MatchFinder::MatchResult &Result)744: WarningGadget(Kind::SpanTwoParamConstructor),745Ctor(Result.Nodes.getNodeAs<CXXConstructExpr>(746SpanTwoParamConstructorTag)) {}747748static bool classof(const Gadget *G) {749return G->getKind() == Kind::SpanTwoParamConstructor;750}751752static Matcher matcher() {753auto HasTwoParamSpanCtorDecl = hasDeclaration(754cxxConstructorDecl(hasDeclContext(isInStdNamespace()), hasName("span"),755parameterCountIs(2)));756757return stmt(cxxConstructExpr(HasTwoParamSpanCtorDecl,758unless(isSafeSpanTwoParamConstruct()))759.bind(SpanTwoParamConstructorTag));760}761762void handleUnsafeOperation(UnsafeBufferUsageHandler &Handler,763bool IsRelatedToDecl,764ASTContext &Ctx) const override {765Handler.handleUnsafeOperationInContainer(Ctor, IsRelatedToDecl, Ctx);766}767SourceLocation getSourceLoc() const override { return Ctor->getBeginLoc(); }768769DeclUseList getClaimedVarUseSites() const override {770// If the constructor call is of the form `std::span{var, n}`, `var` is771// considered an unsafe variable.772if (auto *DRE = dyn_cast<DeclRefExpr>(Ctor->getArg(0))) {773if (isa<VarDecl>(DRE->getDecl()))774return {DRE};775}776return {};777}778};779780/// A pointer initialization expression of the form:781/// \code782/// int *p = q;783/// \endcode784class PointerInitGadget : public FixableGadget {785private:786static constexpr const char *const PointerInitLHSTag = "ptrInitLHS";787static constexpr const char *const PointerInitRHSTag = "ptrInitRHS";788const VarDecl *PtrInitLHS; // the LHS pointer expression in `PI`789const DeclRefExpr *PtrInitRHS; // the RHS pointer expression in `PI`790791public:792PointerInitGadget(const MatchFinder::MatchResult &Result)793: FixableGadget(Kind::PointerInit),794PtrInitLHS(Result.Nodes.getNodeAs<VarDecl>(PointerInitLHSTag)),795PtrInitRHS(Result.Nodes.getNodeAs<DeclRefExpr>(PointerInitRHSTag)) {}796797static bool classof(const Gadget *G) {798return G->getKind() == Kind::PointerInit;799}800801static Matcher matcher() {802auto PtrInitStmt = declStmt(hasSingleDecl(803varDecl(hasInitializer(ignoringImpCasts(804declRefExpr(hasPointerType(), toSupportedVariable())805.bind(PointerInitRHSTag))))806.bind(PointerInitLHSTag)));807808return stmt(PtrInitStmt);809}810811virtual std::optional<FixItList>812getFixits(const FixitStrategy &S) const override;813SourceLocation getSourceLoc() const override {814return PtrInitRHS->getBeginLoc();815}816817virtual DeclUseList getClaimedVarUseSites() const override {818return DeclUseList{PtrInitRHS};819}820821virtual std::optional<std::pair<const VarDecl *, const VarDecl *>>822getStrategyImplications() const override {823return std::make_pair(PtrInitLHS, cast<VarDecl>(PtrInitRHS->getDecl()));824}825};826827/// A pointer assignment expression of the form:828/// \code829/// p = q;830/// \endcode831/// where both `p` and `q` are pointers.832class PtrToPtrAssignmentGadget : public FixableGadget {833private:834static constexpr const char *const PointerAssignLHSTag = "ptrLHS";835static constexpr const char *const PointerAssignRHSTag = "ptrRHS";836const DeclRefExpr *PtrLHS; // the LHS pointer expression in `PA`837const DeclRefExpr *PtrRHS; // the RHS pointer expression in `PA`838839public:840PtrToPtrAssignmentGadget(const MatchFinder::MatchResult &Result)841: FixableGadget(Kind::PtrToPtrAssignment),842PtrLHS(Result.Nodes.getNodeAs<DeclRefExpr>(PointerAssignLHSTag)),843PtrRHS(Result.Nodes.getNodeAs<DeclRefExpr>(PointerAssignRHSTag)) {}844845static bool classof(const Gadget *G) {846return G->getKind() == Kind::PtrToPtrAssignment;847}848849static Matcher matcher() {850auto PtrAssignExpr = binaryOperator(851allOf(hasOperatorName("="),852hasRHS(ignoringParenImpCasts(853declRefExpr(hasPointerType(), toSupportedVariable())854.bind(PointerAssignRHSTag))),855hasLHS(declRefExpr(hasPointerType(), toSupportedVariable())856.bind(PointerAssignLHSTag))));857858return stmt(isInUnspecifiedUntypedContext(PtrAssignExpr));859}860861virtual std::optional<FixItList>862getFixits(const FixitStrategy &S) const override;863SourceLocation getSourceLoc() const override { return PtrLHS->getBeginLoc(); }864865virtual DeclUseList getClaimedVarUseSites() const override {866return DeclUseList{PtrLHS, PtrRHS};867}868869virtual std::optional<std::pair<const VarDecl *, const VarDecl *>>870getStrategyImplications() const override {871return std::make_pair(cast<VarDecl>(PtrLHS->getDecl()),872cast<VarDecl>(PtrRHS->getDecl()));873}874};875876/// An assignment expression of the form:877/// \code878/// ptr = array;879/// \endcode880/// where `p` is a pointer and `array` is a constant size array.881class CArrayToPtrAssignmentGadget : public FixableGadget {882private:883static constexpr const char *const PointerAssignLHSTag = "ptrLHS";884static constexpr const char *const PointerAssignRHSTag = "ptrRHS";885const DeclRefExpr *PtrLHS; // the LHS pointer expression in `PA`886const DeclRefExpr *PtrRHS; // the RHS pointer expression in `PA`887888public:889CArrayToPtrAssignmentGadget(const MatchFinder::MatchResult &Result)890: FixableGadget(Kind::CArrayToPtrAssignment),891PtrLHS(Result.Nodes.getNodeAs<DeclRefExpr>(PointerAssignLHSTag)),892PtrRHS(Result.Nodes.getNodeAs<DeclRefExpr>(PointerAssignRHSTag)) {}893894static bool classof(const Gadget *G) {895return G->getKind() == Kind::CArrayToPtrAssignment;896}897898static Matcher matcher() {899auto PtrAssignExpr = binaryOperator(900allOf(hasOperatorName("="),901hasRHS(ignoringParenImpCasts(902declRefExpr(hasType(hasCanonicalType(constantArrayType())),903toSupportedVariable())904.bind(PointerAssignRHSTag))),905hasLHS(declRefExpr(hasPointerType(), toSupportedVariable())906.bind(PointerAssignLHSTag))));907908return stmt(isInUnspecifiedUntypedContext(PtrAssignExpr));909}910911virtual std::optional<FixItList>912getFixits(const FixitStrategy &S) const override;913SourceLocation getSourceLoc() const override { return PtrLHS->getBeginLoc(); }914915virtual DeclUseList getClaimedVarUseSites() const override {916return DeclUseList{PtrLHS, PtrRHS};917}918919virtual std::optional<std::pair<const VarDecl *, const VarDecl *>>920getStrategyImplications() const override {921return {};922}923};924925/// A call of a function or method that performs unchecked buffer operations926/// over one of its pointer parameters.927class UnsafeBufferUsageAttrGadget : public WarningGadget {928constexpr static const char *const OpTag = "call_expr";929const CallExpr *Op;930931public:932UnsafeBufferUsageAttrGadget(const MatchFinder::MatchResult &Result)933: WarningGadget(Kind::UnsafeBufferUsageAttr),934Op(Result.Nodes.getNodeAs<CallExpr>(OpTag)) {}935936static bool classof(const Gadget *G) {937return G->getKind() == Kind::UnsafeBufferUsageAttr;938}939940static Matcher matcher() {941auto HasUnsafeFnDecl =942callee(functionDecl(hasAttr(attr::UnsafeBufferUsage)));943return stmt(callExpr(HasUnsafeFnDecl).bind(OpTag));944}945946void handleUnsafeOperation(UnsafeBufferUsageHandler &Handler,947bool IsRelatedToDecl,948ASTContext &Ctx) const override {949Handler.handleUnsafeOperation(Op, IsRelatedToDecl, Ctx);950}951SourceLocation getSourceLoc() const override { return Op->getBeginLoc(); }952953DeclUseList getClaimedVarUseSites() const override { return {}; }954};955956/// A call of a constructor that performs unchecked buffer operations957/// over one of its pointer parameters, or constructs a class object that will958/// perform buffer operations that depend on the correctness of the parameters.959class UnsafeBufferUsageCtorAttrGadget : public WarningGadget {960constexpr static const char *const OpTag = "cxx_construct_expr";961const CXXConstructExpr *Op;962963public:964UnsafeBufferUsageCtorAttrGadget(const MatchFinder::MatchResult &Result)965: WarningGadget(Kind::UnsafeBufferUsageCtorAttr),966Op(Result.Nodes.getNodeAs<CXXConstructExpr>(OpTag)) {}967968static bool classof(const Gadget *G) {969return G->getKind() == Kind::UnsafeBufferUsageCtorAttr;970}971972static Matcher matcher() {973auto HasUnsafeCtorDecl =974hasDeclaration(cxxConstructorDecl(hasAttr(attr::UnsafeBufferUsage)));975// std::span(ptr, size) ctor is handled by SpanTwoParamConstructorGadget.976auto HasTwoParamSpanCtorDecl = SpanTwoParamConstructorGadget::matcher();977return stmt(978cxxConstructExpr(HasUnsafeCtorDecl, unless(HasTwoParamSpanCtorDecl))979.bind(OpTag));980}981982void handleUnsafeOperation(UnsafeBufferUsageHandler &Handler,983bool IsRelatedToDecl,984ASTContext &Ctx) const override {985Handler.handleUnsafeOperation(Op, IsRelatedToDecl, Ctx);986}987SourceLocation getSourceLoc() const override { return Op->getBeginLoc(); }988989DeclUseList getClaimedVarUseSites() const override { return {}; }990};991992// Warning gadget for unsafe invocation of span::data method.993// Triggers when the pointer returned by the invocation is immediately994// cast to a larger type.995996class DataInvocationGadget : public WarningGadget {997constexpr static const char *const OpTag = "data_invocation_expr";998const ExplicitCastExpr *Op;9991000public:1001DataInvocationGadget(const MatchFinder::MatchResult &Result)1002: WarningGadget(Kind::DataInvocation),1003Op(Result.Nodes.getNodeAs<ExplicitCastExpr>(OpTag)) {}10041005static bool classof(const Gadget *G) {1006return G->getKind() == Kind::DataInvocation;1007}10081009static Matcher matcher() {1010Matcher callExpr = cxxMemberCallExpr(1011callee(cxxMethodDecl(hasName("data"), ofClass(hasName("std::span")))));1012return stmt(1013explicitCastExpr(anyOf(has(callExpr), has(parenExpr(has(callExpr)))))1014.bind(OpTag));1015}10161017void handleUnsafeOperation(UnsafeBufferUsageHandler &Handler,1018bool IsRelatedToDecl,1019ASTContext &Ctx) const override {1020Handler.handleUnsafeOperation(Op, IsRelatedToDecl, Ctx);1021}1022SourceLocation getSourceLoc() const override { return Op->getBeginLoc(); }10231024DeclUseList getClaimedVarUseSites() const override { return {}; }1025};10261027// Represents expressions of the form `DRE[*]` in the Unspecified Lvalue1028// Context (see `isInUnspecifiedLvalueContext`).1029// Note here `[]` is the built-in subscript operator.1030class ULCArraySubscriptGadget : public FixableGadget {1031private:1032static constexpr const char *const ULCArraySubscriptTag =1033"ArraySubscriptUnderULC";1034const ArraySubscriptExpr *Node;10351036public:1037ULCArraySubscriptGadget(const MatchFinder::MatchResult &Result)1038: FixableGadget(Kind::ULCArraySubscript),1039Node(Result.Nodes.getNodeAs<ArraySubscriptExpr>(ULCArraySubscriptTag)) {1040assert(Node != nullptr && "Expecting a non-null matching result");1041}10421043static bool classof(const Gadget *G) {1044return G->getKind() == Kind::ULCArraySubscript;1045}10461047static Matcher matcher() {1048auto ArrayOrPtr = anyOf(hasPointerType(), hasArrayType());1049auto BaseIsArrayOrPtrDRE = hasBase(1050ignoringParenImpCasts(declRefExpr(ArrayOrPtr, toSupportedVariable())));1051auto Target =1052arraySubscriptExpr(BaseIsArrayOrPtrDRE).bind(ULCArraySubscriptTag);10531054return expr(isInUnspecifiedLvalueContext(Target));1055}10561057virtual std::optional<FixItList>1058getFixits(const FixitStrategy &S) const override;1059SourceLocation getSourceLoc() const override { return Node->getBeginLoc(); }10601061virtual DeclUseList getClaimedVarUseSites() const override {1062if (const auto *DRE =1063dyn_cast<DeclRefExpr>(Node->getBase()->IgnoreImpCasts())) {1064return {DRE};1065}1066return {};1067}1068};10691070// Fixable gadget to handle stand alone pointers of the form `UPC(DRE)` in the1071// unspecified pointer context (isInUnspecifiedPointerContext). The gadget emits1072// fixit of the form `UPC(DRE.data())`.1073class UPCStandalonePointerGadget : public FixableGadget {1074private:1075static constexpr const char *const DeclRefExprTag = "StandalonePointer";1076const DeclRefExpr *Node;10771078public:1079UPCStandalonePointerGadget(const MatchFinder::MatchResult &Result)1080: FixableGadget(Kind::UPCStandalonePointer),1081Node(Result.Nodes.getNodeAs<DeclRefExpr>(DeclRefExprTag)) {1082assert(Node != nullptr && "Expecting a non-null matching result");1083}10841085static bool classof(const Gadget *G) {1086return G->getKind() == Kind::UPCStandalonePointer;1087}10881089static Matcher matcher() {1090auto ArrayOrPtr = anyOf(hasPointerType(), hasArrayType());1091auto target = expr(ignoringParenImpCasts(1092declRefExpr(allOf(ArrayOrPtr, toSupportedVariable()))1093.bind(DeclRefExprTag)));1094return stmt(isInUnspecifiedPointerContext(target));1095}10961097virtual std::optional<FixItList>1098getFixits(const FixitStrategy &S) const override;1099SourceLocation getSourceLoc() const override { return Node->getBeginLoc(); }11001101virtual DeclUseList getClaimedVarUseSites() const override { return {Node}; }1102};11031104class PointerDereferenceGadget : public FixableGadget {1105static constexpr const char *const BaseDeclRefExprTag = "BaseDRE";1106static constexpr const char *const OperatorTag = "op";11071108const DeclRefExpr *BaseDeclRefExpr = nullptr;1109const UnaryOperator *Op = nullptr;11101111public:1112PointerDereferenceGadget(const MatchFinder::MatchResult &Result)1113: FixableGadget(Kind::PointerDereference),1114BaseDeclRefExpr(1115Result.Nodes.getNodeAs<DeclRefExpr>(BaseDeclRefExprTag)),1116Op(Result.Nodes.getNodeAs<UnaryOperator>(OperatorTag)) {}11171118static bool classof(const Gadget *G) {1119return G->getKind() == Kind::PointerDereference;1120}11211122static Matcher matcher() {1123auto Target =1124unaryOperator(1125hasOperatorName("*"),1126has(expr(ignoringParenImpCasts(1127declRefExpr(toSupportedVariable()).bind(BaseDeclRefExprTag)))))1128.bind(OperatorTag);11291130return expr(isInUnspecifiedLvalueContext(Target));1131}11321133DeclUseList getClaimedVarUseSites() const override {1134return {BaseDeclRefExpr};1135}11361137virtual std::optional<FixItList>1138getFixits(const FixitStrategy &S) const override;1139SourceLocation getSourceLoc() const override { return Op->getBeginLoc(); }1140};11411142// Represents expressions of the form `&DRE[any]` in the Unspecified Pointer1143// Context (see `isInUnspecifiedPointerContext`).1144// Note here `[]` is the built-in subscript operator.1145class UPCAddressofArraySubscriptGadget : public FixableGadget {1146private:1147static constexpr const char *const UPCAddressofArraySubscriptTag =1148"AddressofArraySubscriptUnderUPC";1149const UnaryOperator *Node; // the `&DRE[any]` node11501151public:1152UPCAddressofArraySubscriptGadget(const MatchFinder::MatchResult &Result)1153: FixableGadget(Kind::ULCArraySubscript),1154Node(Result.Nodes.getNodeAs<UnaryOperator>(1155UPCAddressofArraySubscriptTag)) {1156assert(Node != nullptr && "Expecting a non-null matching result");1157}11581159static bool classof(const Gadget *G) {1160return G->getKind() == Kind::UPCAddressofArraySubscript;1161}11621163static Matcher matcher() {1164return expr(isInUnspecifiedPointerContext(expr(ignoringImpCasts(1165unaryOperator(1166hasOperatorName("&"),1167hasUnaryOperand(arraySubscriptExpr(hasBase(1168ignoringParenImpCasts(declRefExpr(toSupportedVariable()))))))1169.bind(UPCAddressofArraySubscriptTag)))));1170}11711172virtual std::optional<FixItList>1173getFixits(const FixitStrategy &) const override;1174SourceLocation getSourceLoc() const override { return Node->getBeginLoc(); }11751176virtual DeclUseList getClaimedVarUseSites() const override {1177const auto *ArraySubst = cast<ArraySubscriptExpr>(Node->getSubExpr());1178const auto *DRE =1179cast<DeclRefExpr>(ArraySubst->getBase()->IgnoreParenImpCasts());1180return {DRE};1181}1182};1183} // namespace11841185namespace {1186// An auxiliary tracking facility for the fixit analysis. It helps connect1187// declarations to its uses and make sure we've covered all uses with our1188// analysis before we try to fix the declaration.1189class DeclUseTracker {1190using UseSetTy = SmallSet<const DeclRefExpr *, 16>;1191using DefMapTy = DenseMap<const VarDecl *, const DeclStmt *>;11921193// Allocate on the heap for easier move.1194std::unique_ptr<UseSetTy> Uses{std::make_unique<UseSetTy>()};1195DefMapTy Defs{};11961197public:1198DeclUseTracker() = default;1199DeclUseTracker(const DeclUseTracker &) = delete; // Let's avoid copies.1200DeclUseTracker &operator=(const DeclUseTracker &) = delete;1201DeclUseTracker(DeclUseTracker &&) = default;1202DeclUseTracker &operator=(DeclUseTracker &&) = default;12031204// Start tracking a freshly discovered DRE.1205void discoverUse(const DeclRefExpr *DRE) { Uses->insert(DRE); }12061207// Stop tracking the DRE as it's been fully figured out.1208void claimUse(const DeclRefExpr *DRE) {1209assert(Uses->count(DRE) &&1210"DRE not found or claimed by multiple matchers!");1211Uses->erase(DRE);1212}12131214// A variable is unclaimed if at least one use is unclaimed.1215bool hasUnclaimedUses(const VarDecl *VD) const {1216// FIXME: Can this be less linear? Maybe maintain a map from VDs to DREs?1217return any_of(*Uses, [VD](const DeclRefExpr *DRE) {1218return DRE->getDecl()->getCanonicalDecl() == VD->getCanonicalDecl();1219});1220}12211222UseSetTy getUnclaimedUses(const VarDecl *VD) const {1223UseSetTy ReturnSet;1224for (auto use : *Uses) {1225if (use->getDecl()->getCanonicalDecl() == VD->getCanonicalDecl()) {1226ReturnSet.insert(use);1227}1228}1229return ReturnSet;1230}12311232void discoverDecl(const DeclStmt *DS) {1233for (const Decl *D : DS->decls()) {1234if (const auto *VD = dyn_cast<VarDecl>(D)) {1235// FIXME: Assertion temporarily disabled due to a bug in1236// ASTMatcher internal behavior in presence of GNU1237// statement-expressions. We need to properly investigate this1238// because it can screw up our algorithm in other ways.1239// assert(Defs.count(VD) == 0 && "Definition already discovered!");1240Defs[VD] = DS;1241}1242}1243}12441245const DeclStmt *lookupDecl(const VarDecl *VD) const {1246return Defs.lookup(VD);1247}1248};1249} // namespace12501251// Representing a pointer type expression of the form `++Ptr` in an Unspecified1252// Pointer Context (UPC):1253class UPCPreIncrementGadget : public FixableGadget {1254private:1255static constexpr const char *const UPCPreIncrementTag =1256"PointerPreIncrementUnderUPC";1257const UnaryOperator *Node; // the `++Ptr` node12581259public:1260UPCPreIncrementGadget(const MatchFinder::MatchResult &Result)1261: FixableGadget(Kind::UPCPreIncrement),1262Node(Result.Nodes.getNodeAs<UnaryOperator>(UPCPreIncrementTag)) {1263assert(Node != nullptr && "Expecting a non-null matching result");1264}12651266static bool classof(const Gadget *G) {1267return G->getKind() == Kind::UPCPreIncrement;1268}12691270static Matcher matcher() {1271// Note here we match `++Ptr` for any expression `Ptr` of pointer type.1272// Although currently we can only provide fix-its when `Ptr` is a DRE, we1273// can have the matcher be general, so long as `getClaimedVarUseSites` does1274// things right.1275return stmt(isInUnspecifiedPointerContext(expr(ignoringImpCasts(1276unaryOperator(isPreInc(),1277hasUnaryOperand(declRefExpr(toSupportedVariable())))1278.bind(UPCPreIncrementTag)))));1279}12801281virtual std::optional<FixItList>1282getFixits(const FixitStrategy &S) const override;1283SourceLocation getSourceLoc() const override { return Node->getBeginLoc(); }12841285virtual DeclUseList getClaimedVarUseSites() const override {1286return {dyn_cast<DeclRefExpr>(Node->getSubExpr())};1287}1288};12891290// Representing a pointer type expression of the form `Ptr += n` in an1291// Unspecified Untyped Context (UUC):1292class UUCAddAssignGadget : public FixableGadget {1293private:1294static constexpr const char *const UUCAddAssignTag =1295"PointerAddAssignUnderUUC";1296static constexpr const char *const OffsetTag = "Offset";12971298const BinaryOperator *Node; // the `Ptr += n` node1299const Expr *Offset = nullptr;13001301public:1302UUCAddAssignGadget(const MatchFinder::MatchResult &Result)1303: FixableGadget(Kind::UUCAddAssign),1304Node(Result.Nodes.getNodeAs<BinaryOperator>(UUCAddAssignTag)),1305Offset(Result.Nodes.getNodeAs<Expr>(OffsetTag)) {1306assert(Node != nullptr && "Expecting a non-null matching result");1307}13081309static bool classof(const Gadget *G) {1310return G->getKind() == Kind::UUCAddAssign;1311}13121313static Matcher matcher() {1314// clang-format off1315return stmt(isInUnspecifiedUntypedContext(expr(ignoringImpCasts(1316binaryOperator(hasOperatorName("+="),1317hasLHS(1318declRefExpr(1319hasPointerType(),1320toSupportedVariable())),1321hasRHS(expr().bind(OffsetTag)))1322.bind(UUCAddAssignTag)))));1323// clang-format on1324}13251326virtual std::optional<FixItList>1327getFixits(const FixitStrategy &S) const override;1328SourceLocation getSourceLoc() const override { return Node->getBeginLoc(); }13291330virtual DeclUseList getClaimedVarUseSites() const override {1331return {dyn_cast<DeclRefExpr>(Node->getLHS())};1332}1333};13341335// Representing a fixable expression of the form `*(ptr + 123)` or `*(123 +1336// ptr)`:1337class DerefSimplePtrArithFixableGadget : public FixableGadget {1338static constexpr const char *const BaseDeclRefExprTag = "BaseDRE";1339static constexpr const char *const DerefOpTag = "DerefOp";1340static constexpr const char *const AddOpTag = "AddOp";1341static constexpr const char *const OffsetTag = "Offset";13421343const DeclRefExpr *BaseDeclRefExpr = nullptr;1344const UnaryOperator *DerefOp = nullptr;1345const BinaryOperator *AddOp = nullptr;1346const IntegerLiteral *Offset = nullptr;13471348public:1349DerefSimplePtrArithFixableGadget(const MatchFinder::MatchResult &Result)1350: FixableGadget(Kind::DerefSimplePtrArithFixable),1351BaseDeclRefExpr(1352Result.Nodes.getNodeAs<DeclRefExpr>(BaseDeclRefExprTag)),1353DerefOp(Result.Nodes.getNodeAs<UnaryOperator>(DerefOpTag)),1354AddOp(Result.Nodes.getNodeAs<BinaryOperator>(AddOpTag)),1355Offset(Result.Nodes.getNodeAs<IntegerLiteral>(OffsetTag)) {}13561357static Matcher matcher() {1358// clang-format off1359auto ThePtr = expr(hasPointerType(),1360ignoringImpCasts(declRefExpr(toSupportedVariable()).1361bind(BaseDeclRefExprTag)));1362auto PlusOverPtrAndInteger = expr(anyOf(1363binaryOperator(hasOperatorName("+"), hasLHS(ThePtr),1364hasRHS(integerLiteral().bind(OffsetTag)))1365.bind(AddOpTag),1366binaryOperator(hasOperatorName("+"), hasRHS(ThePtr),1367hasLHS(integerLiteral().bind(OffsetTag)))1368.bind(AddOpTag)));1369return isInUnspecifiedLvalueContext(unaryOperator(1370hasOperatorName("*"),1371hasUnaryOperand(ignoringParens(PlusOverPtrAndInteger)))1372.bind(DerefOpTag));1373// clang-format on1374}13751376virtual std::optional<FixItList>1377getFixits(const FixitStrategy &s) const final;1378SourceLocation getSourceLoc() const override {1379return DerefOp->getBeginLoc();1380}13811382virtual DeclUseList getClaimedVarUseSites() const final {1383return {BaseDeclRefExpr};1384}1385};13861387/// Scan the function and return a list of gadgets found with provided kits.1388static std::tuple<FixableGadgetList, WarningGadgetList, DeclUseTracker>1389findGadgets(const Decl *D, const UnsafeBufferUsageHandler &Handler,1390bool EmitSuggestions) {13911392struct GadgetFinderCallback : MatchFinder::MatchCallback {1393FixableGadgetList FixableGadgets;1394WarningGadgetList WarningGadgets;1395DeclUseTracker Tracker;13961397void run(const MatchFinder::MatchResult &Result) override {1398// In debug mode, assert that we've found exactly one gadget.1399// This helps us avoid conflicts in .bind() tags.1400#if NDEBUG1401#define NEXT return1402#else1403[[maybe_unused]] int numFound = 0;1404#define NEXT ++numFound1405#endif14061407if (const auto *DRE = Result.Nodes.getNodeAs<DeclRefExpr>("any_dre")) {1408Tracker.discoverUse(DRE);1409NEXT;1410}14111412if (const auto *DS = Result.Nodes.getNodeAs<DeclStmt>("any_ds")) {1413Tracker.discoverDecl(DS);1414NEXT;1415}14161417// Figure out which matcher we've found, and call the appropriate1418// subclass constructor.1419// FIXME: Can we do this more logarithmically?1420#define FIXABLE_GADGET(name) \1421if (Result.Nodes.getNodeAs<Stmt>(#name)) { \1422FixableGadgets.push_back(std::make_unique<name##Gadget>(Result)); \1423NEXT; \1424}1425#include "clang/Analysis/Analyses/UnsafeBufferUsageGadgets.def"1426#define WARNING_GADGET(name) \1427if (Result.Nodes.getNodeAs<Stmt>(#name)) { \1428WarningGadgets.push_back(std::make_unique<name##Gadget>(Result)); \1429NEXT; \1430}1431#include "clang/Analysis/Analyses/UnsafeBufferUsageGadgets.def"14321433assert(numFound >= 1 && "Gadgets not found in match result!");1434assert(numFound <= 1 && "Conflicting bind tags in gadgets!");1435}1436};14371438MatchFinder M;1439GadgetFinderCallback CB;14401441// clang-format off1442M.addMatcher(1443stmt(1444forEachDescendantEvaluatedStmt(stmt(anyOf(1445// Add Gadget::matcher() for every gadget in the registry.1446#define WARNING_GADGET(x) \1447allOf(x ## Gadget::matcher().bind(#x), \1448notInSafeBufferOptOut(&Handler)),1449#define WARNING_CONTAINER_GADGET(x) \1450allOf(x ## Gadget::matcher().bind(#x), \1451notInSafeBufferOptOut(&Handler), \1452unless(ignoreUnsafeBufferInContainer(&Handler))),1453#include "clang/Analysis/Analyses/UnsafeBufferUsageGadgets.def"1454// Avoid a hanging comma.1455unless(stmt())1456)))1457),1458&CB1459);1460// clang-format on14611462if (EmitSuggestions) {1463// clang-format off1464M.addMatcher(1465stmt(1466forEachDescendantStmt(stmt(eachOf(1467#define FIXABLE_GADGET(x) \1468x ## Gadget::matcher().bind(#x),1469#include "clang/Analysis/Analyses/UnsafeBufferUsageGadgets.def"1470// In parallel, match all DeclRefExprs so that to find out1471// whether there are any uncovered by gadgets.1472declRefExpr(anyOf(hasPointerType(), hasArrayType()),1473to(anyOf(varDecl(), bindingDecl()))).bind("any_dre"),1474// Also match DeclStmts because we'll need them when fixing1475// their underlying VarDecls that otherwise don't have1476// any backreferences to DeclStmts.1477declStmt().bind("any_ds")1478)))1479),1480&CB1481);1482// clang-format on1483}14841485M.match(*D->getBody(), D->getASTContext());1486return {std::move(CB.FixableGadgets), std::move(CB.WarningGadgets),1487std::move(CB.Tracker)};1488}14891490// Compares AST nodes by source locations.1491template <typename NodeTy> struct CompareNode {1492bool operator()(const NodeTy *N1, const NodeTy *N2) const {1493return N1->getBeginLoc().getRawEncoding() <1494N2->getBeginLoc().getRawEncoding();1495}1496};14971498struct WarningGadgetSets {1499std::map<const VarDecl *, std::set<const WarningGadget *>,1500// To keep keys sorted by their locations in the map so that the1501// order is deterministic:1502CompareNode<VarDecl>>1503byVar;1504// These Gadgets are not related to pointer variables (e. g. temporaries).1505llvm::SmallVector<const WarningGadget *, 16> noVar;1506};15071508static WarningGadgetSets1509groupWarningGadgetsByVar(const WarningGadgetList &AllUnsafeOperations) {1510WarningGadgetSets result;1511// If some gadgets cover more than one1512// variable, they'll appear more than once in the map.1513for (auto &G : AllUnsafeOperations) {1514DeclUseList ClaimedVarUseSites = G->getClaimedVarUseSites();15151516bool AssociatedWithVarDecl = false;1517for (const DeclRefExpr *DRE : ClaimedVarUseSites) {1518if (const auto *VD = dyn_cast<VarDecl>(DRE->getDecl())) {1519result.byVar[VD].insert(G.get());1520AssociatedWithVarDecl = true;1521}1522}15231524if (!AssociatedWithVarDecl) {1525result.noVar.push_back(G.get());1526continue;1527}1528}1529return result;1530}15311532struct FixableGadgetSets {1533std::map<const VarDecl *, std::set<const FixableGadget *>,1534// To keep keys sorted by their locations in the map so that the1535// order is deterministic:1536CompareNode<VarDecl>>1537byVar;1538};15391540static FixableGadgetSets1541groupFixablesByVar(FixableGadgetList &&AllFixableOperations) {1542FixableGadgetSets FixablesForUnsafeVars;1543for (auto &F : AllFixableOperations) {1544DeclUseList DREs = F->getClaimedVarUseSites();15451546for (const DeclRefExpr *DRE : DREs) {1547if (const auto *VD = dyn_cast<VarDecl>(DRE->getDecl())) {1548FixablesForUnsafeVars.byVar[VD].insert(F.get());1549}1550}1551}1552return FixablesForUnsafeVars;1553}15541555bool clang::internal::anyConflict(const SmallVectorImpl<FixItHint> &FixIts,1556const SourceManager &SM) {1557// A simple interval overlap detection algorithm. Sorts all ranges by their1558// begin location then finds the first overlap in one pass.1559std::vector<const FixItHint *> All; // a copy of `FixIts`15601561for (const FixItHint &H : FixIts)1562All.push_back(&H);1563std::sort(All.begin(), All.end(),1564[&SM](const FixItHint *H1, const FixItHint *H2) {1565return SM.isBeforeInTranslationUnit(H1->RemoveRange.getBegin(),1566H2->RemoveRange.getBegin());1567});15681569const FixItHint *CurrHint = nullptr;15701571for (const FixItHint *Hint : All) {1572if (!CurrHint ||1573SM.isBeforeInTranslationUnit(CurrHint->RemoveRange.getEnd(),1574Hint->RemoveRange.getBegin())) {1575// Either to initialize `CurrHint` or `CurrHint` does not1576// overlap with `Hint`:1577CurrHint = Hint;1578} else1579// In case `Hint` overlaps the `CurrHint`, we found at least one1580// conflict:1581return true;1582}1583return false;1584}15851586std::optional<FixItList>1587PtrToPtrAssignmentGadget::getFixits(const FixitStrategy &S) const {1588const auto *LeftVD = cast<VarDecl>(PtrLHS->getDecl());1589const auto *RightVD = cast<VarDecl>(PtrRHS->getDecl());1590switch (S.lookup(LeftVD)) {1591case FixitStrategy::Kind::Span:1592if (S.lookup(RightVD) == FixitStrategy::Kind::Span)1593return FixItList{};1594return std::nullopt;1595case FixitStrategy::Kind::Wontfix:1596return std::nullopt;1597case FixitStrategy::Kind::Iterator:1598case FixitStrategy::Kind::Array:1599return std::nullopt;1600case FixitStrategy::Kind::Vector:1601llvm_unreachable("unsupported strategies for FixableGadgets");1602}1603return std::nullopt;1604}16051606/// \returns fixit that adds .data() call after \DRE.1607static inline std::optional<FixItList> createDataFixit(const ASTContext &Ctx,1608const DeclRefExpr *DRE);16091610std::optional<FixItList>1611CArrayToPtrAssignmentGadget::getFixits(const FixitStrategy &S) const {1612const auto *LeftVD = cast<VarDecl>(PtrLHS->getDecl());1613const auto *RightVD = cast<VarDecl>(PtrRHS->getDecl());1614// TLDR: Implementing fixits for non-Wontfix strategy on both LHS and RHS is1615// non-trivial.1616//1617// CArrayToPtrAssignmentGadget doesn't have strategy implications because1618// constant size array propagates its bounds. Because of that LHS and RHS are1619// addressed by two different fixits.1620//1621// At the same time FixitStrategy S doesn't reflect what group a fixit belongs1622// to and can't be generally relied on in multi-variable Fixables!1623//1624// E. g. If an instance of this gadget is fixing variable on LHS then the1625// variable on RHS is fixed by a different fixit and its strategy for LHS1626// fixit is as if Wontfix.1627//1628// The only exception is Wontfix strategy for a given variable as that is1629// valid for any fixit produced for the given input source code.1630if (S.lookup(LeftVD) == FixitStrategy::Kind::Span) {1631if (S.lookup(RightVD) == FixitStrategy::Kind::Wontfix) {1632return FixItList{};1633}1634} else if (S.lookup(LeftVD) == FixitStrategy::Kind::Wontfix) {1635if (S.lookup(RightVD) == FixitStrategy::Kind::Array) {1636return createDataFixit(RightVD->getASTContext(), PtrRHS);1637}1638}1639return std::nullopt;1640}16411642std::optional<FixItList>1643PointerInitGadget::getFixits(const FixitStrategy &S) const {1644const auto *LeftVD = PtrInitLHS;1645const auto *RightVD = cast<VarDecl>(PtrInitRHS->getDecl());1646switch (S.lookup(LeftVD)) {1647case FixitStrategy::Kind::Span:1648if (S.lookup(RightVD) == FixitStrategy::Kind::Span)1649return FixItList{};1650return std::nullopt;1651case FixitStrategy::Kind::Wontfix:1652return std::nullopt;1653case FixitStrategy::Kind::Iterator:1654case FixitStrategy::Kind::Array:1655return std::nullopt;1656case FixitStrategy::Kind::Vector:1657llvm_unreachable("unsupported strategies for FixableGadgets");1658}1659return std::nullopt;1660}16611662static bool isNonNegativeIntegerExpr(const Expr *Expr, const VarDecl *VD,1663const ASTContext &Ctx) {1664if (auto ConstVal = Expr->getIntegerConstantExpr(Ctx)) {1665if (ConstVal->isNegative())1666return false;1667} else if (!Expr->getType()->isUnsignedIntegerType())1668return false;1669return true;1670}16711672std::optional<FixItList>1673ULCArraySubscriptGadget::getFixits(const FixitStrategy &S) const {1674if (const auto *DRE =1675dyn_cast<DeclRefExpr>(Node->getBase()->IgnoreImpCasts()))1676if (const auto *VD = dyn_cast<VarDecl>(DRE->getDecl())) {1677switch (S.lookup(VD)) {1678case FixitStrategy::Kind::Span: {16791680// If the index has a negative constant value, we give up as no valid1681// fix-it can be generated:1682const ASTContext &Ctx = // FIXME: we need ASTContext to be passed in!1683VD->getASTContext();1684if (!isNonNegativeIntegerExpr(Node->getIdx(), VD, Ctx))1685return std::nullopt;1686// no-op is a good fix-it, otherwise1687return FixItList{};1688}1689case FixitStrategy::Kind::Array:1690return FixItList{};1691case FixitStrategy::Kind::Wontfix:1692case FixitStrategy::Kind::Iterator:1693case FixitStrategy::Kind::Vector:1694llvm_unreachable("unsupported strategies for FixableGadgets");1695}1696}1697return std::nullopt;1698}16991700static std::optional<FixItList> // forward declaration1701fixUPCAddressofArraySubscriptWithSpan(const UnaryOperator *Node);17021703std::optional<FixItList>1704UPCAddressofArraySubscriptGadget::getFixits(const FixitStrategy &S) const {1705auto DREs = getClaimedVarUseSites();1706const auto *VD = cast<VarDecl>(DREs.front()->getDecl());17071708switch (S.lookup(VD)) {1709case FixitStrategy::Kind::Span:1710return fixUPCAddressofArraySubscriptWithSpan(Node);1711case FixitStrategy::Kind::Wontfix:1712case FixitStrategy::Kind::Iterator:1713case FixitStrategy::Kind::Array:1714return std::nullopt;1715case FixitStrategy::Kind::Vector:1716llvm_unreachable("unsupported strategies for FixableGadgets");1717}1718return std::nullopt; // something went wrong, no fix-it1719}17201721// FIXME: this function should be customizable through format1722static StringRef getEndOfLine() {1723static const char *const EOL = "\n";1724return EOL;1725}17261727// Returns the text indicating that the user needs to provide input there:1728std::string getUserFillPlaceHolder(StringRef HintTextToUser = "placeholder") {1729std::string s = std::string("<# ");1730s += HintTextToUser;1731s += " #>";1732return s;1733}17341735// Return the source location of the last character of the AST `Node`.1736template <typename NodeTy>1737static std::optional<SourceLocation>1738getEndCharLoc(const NodeTy *Node, const SourceManager &SM,1739const LangOptions &LangOpts) {1740unsigned TkLen = Lexer::MeasureTokenLength(Node->getEndLoc(), SM, LangOpts);1741SourceLocation Loc = Node->getEndLoc().getLocWithOffset(TkLen - 1);17421743if (Loc.isValid())1744return Loc;17451746return std::nullopt;1747}17481749// Return the source location just past the last character of the AST `Node`.1750template <typename NodeTy>1751static std::optional<SourceLocation> getPastLoc(const NodeTy *Node,1752const SourceManager &SM,1753const LangOptions &LangOpts) {1754SourceLocation Loc =1755Lexer::getLocForEndOfToken(Node->getEndLoc(), 0, SM, LangOpts);1756if (Loc.isValid())1757return Loc;1758return std::nullopt;1759}17601761// Return text representation of an `Expr`.1762static std::optional<StringRef> getExprText(const Expr *E,1763const SourceManager &SM,1764const LangOptions &LangOpts) {1765std::optional<SourceLocation> LastCharLoc = getPastLoc(E, SM, LangOpts);17661767if (LastCharLoc)1768return Lexer::getSourceText(1769CharSourceRange::getCharRange(E->getBeginLoc(), *LastCharLoc), SM,1770LangOpts);17711772return std::nullopt;1773}17741775// Returns the literal text in `SourceRange SR`, if `SR` is a valid range.1776static std::optional<StringRef> getRangeText(SourceRange SR,1777const SourceManager &SM,1778const LangOptions &LangOpts) {1779bool Invalid = false;1780CharSourceRange CSR = CharSourceRange::getCharRange(SR);1781StringRef Text = Lexer::getSourceText(CSR, SM, LangOpts, &Invalid);17821783if (!Invalid)1784return Text;1785return std::nullopt;1786}17871788// Returns the begin location of the identifier of the given variable1789// declaration.1790static SourceLocation getVarDeclIdentifierLoc(const VarDecl *VD) {1791// According to the implementation of `VarDecl`, `VD->getLocation()` actually1792// returns the begin location of the identifier of the declaration:1793return VD->getLocation();1794}17951796// Returns the literal text of the identifier of the given variable declaration.1797static std::optional<StringRef>1798getVarDeclIdentifierText(const VarDecl *VD, const SourceManager &SM,1799const LangOptions &LangOpts) {1800SourceLocation ParmIdentBeginLoc = getVarDeclIdentifierLoc(VD);1801SourceLocation ParmIdentEndLoc =1802Lexer::getLocForEndOfToken(ParmIdentBeginLoc, 0, SM, LangOpts);18031804if (ParmIdentEndLoc.isMacroID() &&1805!Lexer::isAtEndOfMacroExpansion(ParmIdentEndLoc, SM, LangOpts))1806return std::nullopt;1807return getRangeText({ParmIdentBeginLoc, ParmIdentEndLoc}, SM, LangOpts);1808}18091810// We cannot fix a variable declaration if it has some other specifiers than the1811// type specifier. Because the source ranges of those specifiers could overlap1812// with the source range that is being replaced using fix-its. Especially when1813// we often cannot obtain accurate source ranges of cv-qualified type1814// specifiers.1815// FIXME: also deal with type attributes1816static bool hasUnsupportedSpecifiers(const VarDecl *VD,1817const SourceManager &SM) {1818// AttrRangeOverlapping: true if at least one attribute of `VD` overlaps the1819// source range of `VD`:1820bool AttrRangeOverlapping = llvm::any_of(VD->attrs(), [&](Attr *At) -> bool {1821return !(SM.isBeforeInTranslationUnit(At->getRange().getEnd(),1822VD->getBeginLoc())) &&1823!(SM.isBeforeInTranslationUnit(VD->getEndLoc(),1824At->getRange().getBegin()));1825});1826return VD->isInlineSpecified() || VD->isConstexpr() ||1827VD->hasConstantInitialization() || !VD->hasLocalStorage() ||1828AttrRangeOverlapping;1829}18301831// Returns the `SourceRange` of `D`. The reason why this function exists is1832// that `D->getSourceRange()` may return a range where the end location is the1833// starting location of the last token. The end location of the source range1834// returned by this function is the last location of the last token.1835static SourceRange getSourceRangeToTokenEnd(const Decl *D,1836const SourceManager &SM,1837const LangOptions &LangOpts) {1838SourceLocation Begin = D->getBeginLoc();1839SourceLocation1840End = // `D->getEndLoc` should always return the starting location of the1841// last token, so we should get the end of the token1842Lexer::getLocForEndOfToken(D->getEndLoc(), 0, SM, LangOpts);18431844return SourceRange(Begin, End);1845}18461847// Returns the text of the pointee type of `T` from a `VarDecl` of a pointer1848// type. The text is obtained through from `TypeLoc`s. Since `TypeLoc` does not1849// have source ranges of qualifiers ( The `QualifiedTypeLoc` looks hacky too me1850// :( ), `Qualifiers` of the pointee type is returned separately through the1851// output parameter `QualifiersToAppend`.1852static std::optional<std::string>1853getPointeeTypeText(const VarDecl *VD, const SourceManager &SM,1854const LangOptions &LangOpts,1855std::optional<Qualifiers> *QualifiersToAppend) {1856QualType Ty = VD->getType();1857QualType PteTy;18581859assert(Ty->isPointerType() && !Ty->isFunctionPointerType() &&1860"Expecting a VarDecl of type of pointer to object type");1861PteTy = Ty->getPointeeType();18621863TypeLoc TyLoc = VD->getTypeSourceInfo()->getTypeLoc().getUnqualifiedLoc();1864TypeLoc PteTyLoc;18651866// We only deal with the cases that we know `TypeLoc::getNextTypeLoc` returns1867// the `TypeLoc` of the pointee type:1868switch (TyLoc.getTypeLocClass()) {1869case TypeLoc::ConstantArray:1870case TypeLoc::IncompleteArray:1871case TypeLoc::VariableArray:1872case TypeLoc::DependentSizedArray:1873case TypeLoc::Decayed:1874assert(isa<ParmVarDecl>(VD) && "An array type shall not be treated as a "1875"pointer type unless it decays.");1876PteTyLoc = TyLoc.getNextTypeLoc();1877break;1878case TypeLoc::Pointer:1879PteTyLoc = TyLoc.castAs<PointerTypeLoc>().getPointeeLoc();1880break;1881default:1882return std::nullopt;1883}1884if (PteTyLoc.isNull())1885// Sometimes we cannot get a useful `TypeLoc` for the pointee type, e.g.,1886// when the pointer type is `auto`.1887return std::nullopt;18881889SourceLocation IdentLoc = getVarDeclIdentifierLoc(VD);18901891if (!(IdentLoc.isValid() && PteTyLoc.getSourceRange().isValid())) {1892// We are expecting these locations to be valid. But in some cases, they are1893// not all valid. It is a Clang bug to me and we are not responsible for1894// fixing it. So we will just give up for now when it happens.1895return std::nullopt;1896}18971898// Note that TypeLoc.getEndLoc() returns the begin location of the last token:1899SourceLocation PteEndOfTokenLoc =1900Lexer::getLocForEndOfToken(PteTyLoc.getEndLoc(), 0, SM, LangOpts);19011902if (!PteEndOfTokenLoc.isValid())1903// Sometimes we cannot get the end location of the pointee type, e.g., when1904// there are macros involved.1905return std::nullopt;1906if (!SM.isBeforeInTranslationUnit(PteEndOfTokenLoc, IdentLoc)) {1907// We only deal with the cases where the source text of the pointee type1908// appears on the left-hand side of the variable identifier completely,1909// including the following forms:1910// `T ident`,1911// `T ident[]`, where `T` is any type.1912// Examples of excluded cases are `T (*ident)[]` or `T ident[][n]`.1913return std::nullopt;1914}1915if (PteTy.hasQualifiers()) {1916// TypeLoc does not provide source ranges for qualifiers (it says it's1917// intentional but seems fishy to me), so we cannot get the full text1918// `PteTy` via source ranges.1919*QualifiersToAppend = PteTy.getQualifiers();1920}1921return getRangeText({PteTyLoc.getBeginLoc(), PteEndOfTokenLoc}, SM, LangOpts)1922->str();1923}19241925// Returns the text of the name (with qualifiers) of a `FunctionDecl`.1926static std::optional<StringRef> getFunNameText(const FunctionDecl *FD,1927const SourceManager &SM,1928const LangOptions &LangOpts) {1929SourceLocation BeginLoc = FD->getQualifier()1930? FD->getQualifierLoc().getBeginLoc()1931: FD->getNameInfo().getBeginLoc();1932// Note that `FD->getNameInfo().getEndLoc()` returns the begin location of the1933// last token:1934SourceLocation EndLoc = Lexer::getLocForEndOfToken(1935FD->getNameInfo().getEndLoc(), 0, SM, LangOpts);1936SourceRange NameRange{BeginLoc, EndLoc};19371938return getRangeText(NameRange, SM, LangOpts);1939}19401941// Returns the text representing a `std::span` type where the element type is1942// represented by `EltTyText`.1943//1944// Note the optional parameter `Qualifiers`: one needs to pass qualifiers1945// explicitly if the element type needs to be qualified.1946static std::string1947getSpanTypeText(StringRef EltTyText,1948std::optional<Qualifiers> Quals = std::nullopt) {1949const char *const SpanOpen = "std::span<";19501951if (Quals)1952return SpanOpen + EltTyText.str() + ' ' + Quals->getAsString() + '>';1953return SpanOpen + EltTyText.str() + '>';1954}19551956std::optional<FixItList>1957DerefSimplePtrArithFixableGadget::getFixits(const FixitStrategy &s) const {1958const VarDecl *VD = dyn_cast<VarDecl>(BaseDeclRefExpr->getDecl());19591960if (VD && s.lookup(VD) == FixitStrategy::Kind::Span) {1961ASTContext &Ctx = VD->getASTContext();1962// std::span can't represent elements before its begin()1963if (auto ConstVal = Offset->getIntegerConstantExpr(Ctx))1964if (ConstVal->isNegative())1965return std::nullopt;19661967// note that the expr may (oddly) has multiple layers of parens1968// example:1969// *((..(pointer + 123)..))1970// goal:1971// pointer[123]1972// Fix-It:1973// remove '*('1974// replace ' + ' with '['1975// replace ')' with ']'19761977// example:1978// *((..(123 + pointer)..))1979// goal:1980// 123[pointer]1981// Fix-It:1982// remove '*('1983// replace ' + ' with '['1984// replace ')' with ']'19851986const Expr *LHS = AddOp->getLHS(), *RHS = AddOp->getRHS();1987const SourceManager &SM = Ctx.getSourceManager();1988const LangOptions &LangOpts = Ctx.getLangOpts();1989CharSourceRange StarWithTrailWhitespace =1990clang::CharSourceRange::getCharRange(DerefOp->getOperatorLoc(),1991LHS->getBeginLoc());19921993std::optional<SourceLocation> LHSLocation = getPastLoc(LHS, SM, LangOpts);1994if (!LHSLocation)1995return std::nullopt;19961997CharSourceRange PlusWithSurroundingWhitespace =1998clang::CharSourceRange::getCharRange(*LHSLocation, RHS->getBeginLoc());19992000std::optional<SourceLocation> AddOpLocation =2001getPastLoc(AddOp, SM, LangOpts);2002std::optional<SourceLocation> DerefOpLocation =2003getPastLoc(DerefOp, SM, LangOpts);20042005if (!AddOpLocation || !DerefOpLocation)2006return std::nullopt;20072008CharSourceRange ClosingParenWithPrecWhitespace =2009clang::CharSourceRange::getCharRange(*AddOpLocation, *DerefOpLocation);20102011return FixItList{2012{FixItHint::CreateRemoval(StarWithTrailWhitespace),2013FixItHint::CreateReplacement(PlusWithSurroundingWhitespace, "["),2014FixItHint::CreateReplacement(ClosingParenWithPrecWhitespace, "]")}};2015}2016return std::nullopt; // something wrong or unsupported, give up2017}20182019std::optional<FixItList>2020PointerDereferenceGadget::getFixits(const FixitStrategy &S) const {2021const VarDecl *VD = cast<VarDecl>(BaseDeclRefExpr->getDecl());2022switch (S.lookup(VD)) {2023case FixitStrategy::Kind::Span: {2024ASTContext &Ctx = VD->getASTContext();2025SourceManager &SM = Ctx.getSourceManager();2026// Required changes: *(ptr); => (ptr[0]); and *ptr; => ptr[0]2027// Deletes the *operand2028CharSourceRange derefRange = clang::CharSourceRange::getCharRange(2029Op->getBeginLoc(), Op->getBeginLoc().getLocWithOffset(1));2030// Inserts the [0]2031if (auto LocPastOperand =2032getPastLoc(BaseDeclRefExpr, SM, Ctx.getLangOpts())) {2033return FixItList{{FixItHint::CreateRemoval(derefRange),2034FixItHint::CreateInsertion(*LocPastOperand, "[0]")}};2035}2036break;2037}2038case FixitStrategy::Kind::Iterator:2039case FixitStrategy::Kind::Array:2040return std::nullopt;2041case FixitStrategy::Kind::Vector:2042llvm_unreachable("FixitStrategy not implemented yet!");2043case FixitStrategy::Kind::Wontfix:2044llvm_unreachable("Invalid strategy!");2045}20462047return std::nullopt;2048}20492050static inline std::optional<FixItList> createDataFixit(const ASTContext &Ctx,2051const DeclRefExpr *DRE) {2052const SourceManager &SM = Ctx.getSourceManager();2053// Inserts the .data() after the DRE2054std::optional<SourceLocation> EndOfOperand =2055getPastLoc(DRE, SM, Ctx.getLangOpts());20562057if (EndOfOperand)2058return FixItList{{FixItHint::CreateInsertion(*EndOfOperand, ".data()")}};20592060return std::nullopt;2061}20622063// Generates fix-its replacing an expression of the form UPC(DRE) with2064// `DRE.data()`2065std::optional<FixItList>2066UPCStandalonePointerGadget::getFixits(const FixitStrategy &S) const {2067const auto VD = cast<VarDecl>(Node->getDecl());2068switch (S.lookup(VD)) {2069case FixitStrategy::Kind::Array:2070case FixitStrategy::Kind::Span: {2071return createDataFixit(VD->getASTContext(), Node);2072// FIXME: Points inside a macro expansion.2073break;2074}2075case FixitStrategy::Kind::Wontfix:2076case FixitStrategy::Kind::Iterator:2077return std::nullopt;2078case FixitStrategy::Kind::Vector:2079llvm_unreachable("unsupported strategies for FixableGadgets");2080}20812082return std::nullopt;2083}20842085// Generates fix-its replacing an expression of the form `&DRE[e]` with2086// `&DRE.data()[e]`:2087static std::optional<FixItList>2088fixUPCAddressofArraySubscriptWithSpan(const UnaryOperator *Node) {2089const auto *ArraySub = cast<ArraySubscriptExpr>(Node->getSubExpr());2090const auto *DRE = cast<DeclRefExpr>(ArraySub->getBase()->IgnoreImpCasts());2091// FIXME: this `getASTContext` call is costly, we should pass the2092// ASTContext in:2093const ASTContext &Ctx = DRE->getDecl()->getASTContext();2094const Expr *Idx = ArraySub->getIdx();2095const SourceManager &SM = Ctx.getSourceManager();2096const LangOptions &LangOpts = Ctx.getLangOpts();2097std::stringstream SS;2098bool IdxIsLitZero = false;20992100if (auto ICE = Idx->getIntegerConstantExpr(Ctx))2101if ((*ICE).isZero())2102IdxIsLitZero = true;2103std::optional<StringRef> DreString = getExprText(DRE, SM, LangOpts);2104if (!DreString)2105return std::nullopt;21062107if (IdxIsLitZero) {2108// If the index is literal zero, we produce the most concise fix-it:2109SS << (*DreString).str() << ".data()";2110} else {2111std::optional<StringRef> IndexString = getExprText(Idx, SM, LangOpts);2112if (!IndexString)2113return std::nullopt;21142115SS << "&" << (*DreString).str() << ".data()"2116<< "[" << (*IndexString).str() << "]";2117}2118return FixItList{2119FixItHint::CreateReplacement(Node->getSourceRange(), SS.str())};2120}21212122std::optional<FixItList>2123UUCAddAssignGadget::getFixits(const FixitStrategy &S) const {2124DeclUseList DREs = getClaimedVarUseSites();21252126if (DREs.size() != 1)2127return std::nullopt; // In cases of `Ptr += n` where `Ptr` is not a DRE, we2128// give up2129if (const VarDecl *VD = dyn_cast<VarDecl>(DREs.front()->getDecl())) {2130if (S.lookup(VD) == FixitStrategy::Kind::Span) {2131FixItList Fixes;21322133const Stmt *AddAssignNode = Node;2134StringRef varName = VD->getName();2135const ASTContext &Ctx = VD->getASTContext();21362137if (!isNonNegativeIntegerExpr(Offset, VD, Ctx))2138return std::nullopt;21392140// To transform UUC(p += n) to UUC(p = p.subspan(..)):2141bool NotParenExpr =2142(Offset->IgnoreParens()->getBeginLoc() == Offset->getBeginLoc());2143std::string SS = varName.str() + " = " + varName.str() + ".subspan";2144if (NotParenExpr)2145SS += "(";21462147std::optional<SourceLocation> AddAssignLocation = getEndCharLoc(2148AddAssignNode, Ctx.getSourceManager(), Ctx.getLangOpts());2149if (!AddAssignLocation)2150return std::nullopt;21512152Fixes.push_back(FixItHint::CreateReplacement(2153SourceRange(AddAssignNode->getBeginLoc(), Node->getOperatorLoc()),2154SS));2155if (NotParenExpr)2156Fixes.push_back(FixItHint::CreateInsertion(2157Offset->getEndLoc().getLocWithOffset(1), ")"));2158return Fixes;2159}2160}2161return std::nullopt; // Not in the cases that we can handle for now, give up.2162}21632164std::optional<FixItList>2165UPCPreIncrementGadget::getFixits(const FixitStrategy &S) const {2166DeclUseList DREs = getClaimedVarUseSites();21672168if (DREs.size() != 1)2169return std::nullopt; // In cases of `++Ptr` where `Ptr` is not a DRE, we2170// give up2171if (const VarDecl *VD = dyn_cast<VarDecl>(DREs.front()->getDecl())) {2172if (S.lookup(VD) == FixitStrategy::Kind::Span) {2173FixItList Fixes;2174std::stringstream SS;2175StringRef varName = VD->getName();2176const ASTContext &Ctx = VD->getASTContext();21772178// To transform UPC(++p) to UPC((p = p.subspan(1)).data()):2179SS << "(" << varName.data() << " = " << varName.data()2180<< ".subspan(1)).data()";2181std::optional<SourceLocation> PreIncLocation =2182getEndCharLoc(Node, Ctx.getSourceManager(), Ctx.getLangOpts());2183if (!PreIncLocation)2184return std::nullopt;21852186Fixes.push_back(FixItHint::CreateReplacement(2187SourceRange(Node->getBeginLoc(), *PreIncLocation), SS.str()));2188return Fixes;2189}2190}2191return std::nullopt; // Not in the cases that we can handle for now, give up.2192}21932194// For a non-null initializer `Init` of `T *` type, this function returns2195// `FixItHint`s producing a list initializer `{Init, S}` as a part of a fix-it2196// to output stream.2197// In many cases, this function cannot figure out the actual extent `S`. It2198// then will use a place holder to replace `S` to ask users to fill `S` in. The2199// initializer shall be used to initialize a variable of type `std::span<T>`.2200// In some cases (e. g. constant size array) the initializer should remain2201// unchanged and the function returns empty list. In case the function can't2202// provide the right fixit it will return nullopt.2203//2204// FIXME: Support multi-level pointers2205//2206// Parameters:2207// `Init` a pointer to the initializer expression2208// `Ctx` a reference to the ASTContext2209static std::optional<FixItList>2210FixVarInitializerWithSpan(const Expr *Init, ASTContext &Ctx,2211const StringRef UserFillPlaceHolder) {2212const SourceManager &SM = Ctx.getSourceManager();2213const LangOptions &LangOpts = Ctx.getLangOpts();22142215// If `Init` has a constant value that is (or equivalent to) a2216// NULL pointer, we use the default constructor to initialize the span2217// object, i.e., a `std:span` variable declaration with no initializer.2218// So the fix-it is just to remove the initializer.2219if (Init->isNullPointerConstant(2220Ctx,2221// FIXME: Why does this function not ask for `const ASTContext2222// &`? It should. Maybe worth an NFC patch later.2223Expr::NullPointerConstantValueDependence::2224NPC_ValueDependentIsNotNull)) {2225std::optional<SourceLocation> InitLocation =2226getEndCharLoc(Init, SM, LangOpts);2227if (!InitLocation)2228return std::nullopt;22292230SourceRange SR(Init->getBeginLoc(), *InitLocation);22312232return FixItList{FixItHint::CreateRemoval(SR)};2233}22342235FixItList FixIts{};2236std::string ExtentText = UserFillPlaceHolder.data();2237StringRef One = "1";22382239// Insert `{` before `Init`:2240FixIts.push_back(FixItHint::CreateInsertion(Init->getBeginLoc(), "{"));2241// Try to get the data extent. Break into different cases:2242if (auto CxxNew = dyn_cast<CXXNewExpr>(Init->IgnoreImpCasts())) {2243// In cases `Init` is `new T[n]` and there is no explicit cast over2244// `Init`, we know that `Init` must evaluates to a pointer to `n` objects2245// of `T`. So the extent is `n` unless `n` has side effects. Similar but2246// simpler for the case where `Init` is `new T`.2247if (const Expr *Ext = CxxNew->getArraySize().value_or(nullptr)) {2248if (!Ext->HasSideEffects(Ctx)) {2249std::optional<StringRef> ExtentString = getExprText(Ext, SM, LangOpts);2250if (!ExtentString)2251return std::nullopt;2252ExtentText = *ExtentString;2253}2254} else if (!CxxNew->isArray())2255// Although the initializer is not allocating a buffer, the pointer2256// variable could still be used in buffer access operations.2257ExtentText = One;2258} else if (Ctx.getAsConstantArrayType(Init->IgnoreImpCasts()->getType())) {2259// std::span has a single parameter constructor for initialization with2260// constant size array. The size is auto-deduced as the constructor is a2261// function template. The correct fixit is empty - no changes should happen.2262return FixItList{};2263} else {2264// In cases `Init` is of the form `&Var` after stripping of implicit2265// casts, where `&` is the built-in operator, the extent is 1.2266if (auto AddrOfExpr = dyn_cast<UnaryOperator>(Init->IgnoreImpCasts()))2267if (AddrOfExpr->getOpcode() == UnaryOperatorKind::UO_AddrOf &&2268isa_and_present<DeclRefExpr>(AddrOfExpr->getSubExpr()))2269ExtentText = One;2270// TODO: we can handle more cases, e.g., `&a[0]`, `&a`, `std::addressof`,2271// and explicit casting, etc. etc.2272}22732274SmallString<32> StrBuffer{};2275std::optional<SourceLocation> LocPassInit = getPastLoc(Init, SM, LangOpts);22762277if (!LocPassInit)2278return std::nullopt;22792280StrBuffer.append(", ");2281StrBuffer.append(ExtentText);2282StrBuffer.append("}");2283FixIts.push_back(FixItHint::CreateInsertion(*LocPassInit, StrBuffer.str()));2284return FixIts;2285}22862287#ifndef NDEBUG2288#define DEBUG_NOTE_DECL_FAIL(D, Msg) \2289Handler.addDebugNoteForVar((D), (D)->getBeginLoc(), \2290"failed to produce fixit for declaration '" + \2291(D)->getNameAsString() + "'" + (Msg))2292#else2293#define DEBUG_NOTE_DECL_FAIL(D, Msg)2294#endif22952296// For the given variable declaration with a pointer-to-T type, returns the text2297// `std::span<T>`. If it is unable to generate the text, returns2298// `std::nullopt`.2299static std::optional<std::string>2300createSpanTypeForVarDecl(const VarDecl *VD, const ASTContext &Ctx) {2301assert(VD->getType()->isPointerType());23022303std::optional<Qualifiers> PteTyQualifiers = std::nullopt;2304std::optional<std::string> PteTyText = getPointeeTypeText(2305VD, Ctx.getSourceManager(), Ctx.getLangOpts(), &PteTyQualifiers);23062307if (!PteTyText)2308return std::nullopt;23092310std::string SpanTyText = "std::span<";23112312SpanTyText.append(*PteTyText);2313// Append qualifiers to span element type if any:2314if (PteTyQualifiers) {2315SpanTyText.append(" ");2316SpanTyText.append(PteTyQualifiers->getAsString());2317}2318SpanTyText.append(">");2319return SpanTyText;2320}23212322// For a `VarDecl` of the form `T * var (= Init)?`, this2323// function generates fix-its that2324// 1) replace `T * var` with `std::span<T> var`; and2325// 2) change `Init` accordingly to a span constructor, if it exists.2326//2327// FIXME: support Multi-level pointers2328//2329// Parameters:2330// `D` a pointer the variable declaration node2331// `Ctx` a reference to the ASTContext2332// `UserFillPlaceHolder` the user-input placeholder text2333// Returns:2334// the non-empty fix-it list, if fix-its are successfuly generated; empty2335// list otherwise.2336static FixItList fixLocalVarDeclWithSpan(const VarDecl *D, ASTContext &Ctx,2337const StringRef UserFillPlaceHolder,2338UnsafeBufferUsageHandler &Handler) {2339if (hasUnsupportedSpecifiers(D, Ctx.getSourceManager()))2340return {};23412342FixItList FixIts{};2343std::optional<std::string> SpanTyText = createSpanTypeForVarDecl(D, Ctx);23442345if (!SpanTyText) {2346DEBUG_NOTE_DECL_FAIL(D, " : failed to generate 'std::span' type");2347return {};2348}23492350// Will hold the text for `std::span<T> Ident`:2351std::stringstream SS;23522353SS << *SpanTyText;2354// Fix the initializer if it exists:2355if (const Expr *Init = D->getInit()) {2356std::optional<FixItList> InitFixIts =2357FixVarInitializerWithSpan(Init, Ctx, UserFillPlaceHolder);2358if (!InitFixIts)2359return {};2360FixIts.insert(FixIts.end(), std::make_move_iterator(InitFixIts->begin()),2361std::make_move_iterator(InitFixIts->end()));2362}2363// For declaration of the form `T * ident = init;`, we want to replace2364// `T * ` with `std::span<T>`.2365// We ignore CV-qualifiers so for `T * const ident;` we also want to replace2366// just `T *` with `std::span<T>`.2367const SourceLocation EndLocForReplacement = D->getTypeSpecEndLoc();2368if (!EndLocForReplacement.isValid()) {2369DEBUG_NOTE_DECL_FAIL(D, " : failed to locate the end of the declaration");2370return {};2371}2372// The only exception is that for `T *ident` we'll add a single space between2373// "std::span<T>" and "ident".2374// FIXME: The condition is false for identifiers expended from macros.2375if (EndLocForReplacement.getLocWithOffset(1) == getVarDeclIdentifierLoc(D))2376SS << " ";23772378FixIts.push_back(FixItHint::CreateReplacement(2379SourceRange(D->getBeginLoc(), EndLocForReplacement), SS.str()));2380return FixIts;2381}23822383static bool hasConflictingOverload(const FunctionDecl *FD) {2384return !FD->getDeclContext()->lookup(FD->getDeclName()).isSingleResult();2385}23862387// For a `FunctionDecl`, whose `ParmVarDecl`s are being changed to have new2388// types, this function produces fix-its to make the change self-contained. Let2389// 'F' be the entity defined by the original `FunctionDecl` and "NewF" be the2390// entity defined by the `FunctionDecl` after the change to the parameters.2391// Fix-its produced by this function are2392// 1. Add the `[[clang::unsafe_buffer_usage]]` attribute to each declaration2393// of 'F';2394// 2. Create a declaration of "NewF" next to each declaration of `F`;2395// 3. Create a definition of "F" (as its' original definition is now belongs2396// to "NewF") next to its original definition. The body of the creating2397// definition calls to "NewF".2398//2399// Example:2400//2401// void f(int *p); // original declaration2402// void f(int *p) { // original definition2403// p[5];2404// }2405//2406// To change the parameter `p` to be of `std::span<int>` type, we2407// also add overloads:2408//2409// [[clang::unsafe_buffer_usage]] void f(int *p); // original decl2410// void f(std::span<int> p); // added overload decl2411// void f(std::span<int> p) { // original def where param is changed2412// p[5];2413// }2414// [[clang::unsafe_buffer_usage]] void f(int *p) { // added def2415// return f(std::span(p, <# size #>));2416// }2417//2418static std::optional<FixItList>2419createOverloadsForFixedParams(const FixitStrategy &S, const FunctionDecl *FD,2420const ASTContext &Ctx,2421UnsafeBufferUsageHandler &Handler) {2422// FIXME: need to make this conflict checking better:2423if (hasConflictingOverload(FD))2424return std::nullopt;24252426const SourceManager &SM = Ctx.getSourceManager();2427const LangOptions &LangOpts = Ctx.getLangOpts();2428const unsigned NumParms = FD->getNumParams();2429std::vector<std::string> NewTysTexts(NumParms);2430std::vector<bool> ParmsMask(NumParms, false);2431bool AtLeastOneParmToFix = false;24322433for (unsigned i = 0; i < NumParms; i++) {2434const ParmVarDecl *PVD = FD->getParamDecl(i);24352436if (S.lookup(PVD) == FixitStrategy::Kind::Wontfix)2437continue;2438if (S.lookup(PVD) != FixitStrategy::Kind::Span)2439// Not supported, not suppose to happen:2440return std::nullopt;24412442std::optional<Qualifiers> PteTyQuals = std::nullopt;2443std::optional<std::string> PteTyText =2444getPointeeTypeText(PVD, SM, LangOpts, &PteTyQuals);24452446if (!PteTyText)2447// something wrong in obtaining the text of the pointee type, give up2448return std::nullopt;2449// FIXME: whether we should create std::span type depends on the2450// FixitStrategy.2451NewTysTexts[i] = getSpanTypeText(*PteTyText, PteTyQuals);2452ParmsMask[i] = true;2453AtLeastOneParmToFix = true;2454}2455if (!AtLeastOneParmToFix)2456// No need to create function overloads:2457return {};2458// FIXME Respect indentation of the original code.24592460// A lambda that creates the text representation of a function declaration2461// with the new type signatures:2462const auto NewOverloadSignatureCreator =2463[&SM, &LangOpts, &NewTysTexts,2464&ParmsMask](const FunctionDecl *FD) -> std::optional<std::string> {2465std::stringstream SS;24662467SS << ";";2468SS << getEndOfLine().str();2469// Append: ret-type func-name "("2470if (auto Prefix = getRangeText(2471SourceRange(FD->getBeginLoc(), (*FD->param_begin())->getBeginLoc()),2472SM, LangOpts))2473SS << Prefix->str();2474else2475return std::nullopt; // give up2476// Append: parameter-type-list2477const unsigned NumParms = FD->getNumParams();24782479for (unsigned i = 0; i < NumParms; i++) {2480const ParmVarDecl *Parm = FD->getParamDecl(i);24812482if (Parm->isImplicit())2483continue;2484if (ParmsMask[i]) {2485// This `i`-th parameter will be fixed with `NewTysTexts[i]` being its2486// new type:2487SS << NewTysTexts[i];2488// print parameter name if provided:2489if (IdentifierInfo *II = Parm->getIdentifier())2490SS << ' ' << II->getName().str();2491} else if (auto ParmTypeText =2492getRangeText(getSourceRangeToTokenEnd(Parm, SM, LangOpts),2493SM, LangOpts)) {2494// print the whole `Parm` without modification:2495SS << ParmTypeText->str();2496} else2497return std::nullopt; // something wrong, give up2498if (i != NumParms - 1)2499SS << ", ";2500}2501SS << ")";2502return SS.str();2503};25042505// A lambda that creates the text representation of a function definition with2506// the original signature:2507const auto OldOverloadDefCreator =2508[&Handler, &SM, &LangOpts, &NewTysTexts,2509&ParmsMask](const FunctionDecl *FD) -> std::optional<std::string> {2510std::stringstream SS;25112512SS << getEndOfLine().str();2513// Append: attr-name ret-type func-name "(" param-list ")" "{"2514if (auto FDPrefix = getRangeText(2515SourceRange(FD->getBeginLoc(), FD->getBody()->getBeginLoc()), SM,2516LangOpts))2517SS << Handler.getUnsafeBufferUsageAttributeTextAt(FD->getBeginLoc(), " ")2518<< FDPrefix->str() << "{";2519else2520return std::nullopt;2521// Append: "return" func-name "("2522if (auto FunQualName = getFunNameText(FD, SM, LangOpts))2523SS << "return " << FunQualName->str() << "(";2524else2525return std::nullopt;25262527// Append: arg-list2528const unsigned NumParms = FD->getNumParams();2529for (unsigned i = 0; i < NumParms; i++) {2530const ParmVarDecl *Parm = FD->getParamDecl(i);25312532if (Parm->isImplicit())2533continue;2534// FIXME: If a parameter has no name, it is unused in the2535// definition. So we could just leave it as it is.2536if (!Parm->getIdentifier())2537// If a parameter of a function definition has no name:2538return std::nullopt;2539if (ParmsMask[i])2540// This is our spanified paramter!2541SS << NewTysTexts[i] << "(" << Parm->getIdentifier()->getName().str()2542<< ", " << getUserFillPlaceHolder("size") << ")";2543else2544SS << Parm->getIdentifier()->getName().str();2545if (i != NumParms - 1)2546SS << ", ";2547}2548// finish call and the body2549SS << ");}" << getEndOfLine().str();2550// FIXME: 80-char line formatting?2551return SS.str();2552};25532554FixItList FixIts{};2555for (FunctionDecl *FReDecl : FD->redecls()) {2556std::optional<SourceLocation> Loc = getPastLoc(FReDecl, SM, LangOpts);25572558if (!Loc)2559return {};2560if (FReDecl->isThisDeclarationADefinition()) {2561assert(FReDecl == FD && "inconsistent function definition");2562// Inserts a definition with the old signature to the end of2563// `FReDecl`:2564if (auto OldOverloadDef = OldOverloadDefCreator(FReDecl))2565FixIts.emplace_back(FixItHint::CreateInsertion(*Loc, *OldOverloadDef));2566else2567return {}; // give up2568} else {2569// Adds the unsafe-buffer attribute (if not already there) to `FReDecl`:2570if (!FReDecl->hasAttr<UnsafeBufferUsageAttr>()) {2571FixIts.emplace_back(FixItHint::CreateInsertion(2572FReDecl->getBeginLoc(), Handler.getUnsafeBufferUsageAttributeTextAt(2573FReDecl->getBeginLoc(), " ")));2574}2575// Inserts a declaration with the new signature to the end of `FReDecl`:2576if (auto NewOverloadDecl = NewOverloadSignatureCreator(FReDecl))2577FixIts.emplace_back(FixItHint::CreateInsertion(*Loc, *NewOverloadDecl));2578else2579return {};2580}2581}2582return FixIts;2583}25842585// To fix a `ParmVarDecl` to be of `std::span` type.2586static FixItList fixParamWithSpan(const ParmVarDecl *PVD, const ASTContext &Ctx,2587UnsafeBufferUsageHandler &Handler) {2588if (hasUnsupportedSpecifiers(PVD, Ctx.getSourceManager())) {2589DEBUG_NOTE_DECL_FAIL(PVD, " : has unsupport specifier(s)");2590return {};2591}2592if (PVD->hasDefaultArg()) {2593// FIXME: generate fix-its for default values:2594DEBUG_NOTE_DECL_FAIL(PVD, " : has default arg");2595return {};2596}25972598std::optional<Qualifiers> PteTyQualifiers = std::nullopt;2599std::optional<std::string> PteTyText = getPointeeTypeText(2600PVD, Ctx.getSourceManager(), Ctx.getLangOpts(), &PteTyQualifiers);26012602if (!PteTyText) {2603DEBUG_NOTE_DECL_FAIL(PVD, " : invalid pointee type");2604return {};2605}26062607std::optional<StringRef> PVDNameText = PVD->getIdentifier()->getName();26082609if (!PVDNameText) {2610DEBUG_NOTE_DECL_FAIL(PVD, " : invalid identifier name");2611return {};2612}26132614std::stringstream SS;2615std::optional<std::string> SpanTyText = createSpanTypeForVarDecl(PVD, Ctx);26162617if (PteTyQualifiers)2618// Append qualifiers if they exist:2619SS << getSpanTypeText(*PteTyText, PteTyQualifiers);2620else2621SS << getSpanTypeText(*PteTyText);2622// Append qualifiers to the type of the parameter:2623if (PVD->getType().hasQualifiers())2624SS << ' ' << PVD->getType().getQualifiers().getAsString();2625// Append parameter's name:2626SS << ' ' << PVDNameText->str();2627// Add replacement fix-it:2628return {FixItHint::CreateReplacement(PVD->getSourceRange(), SS.str())};2629}26302631static FixItList fixVariableWithSpan(const VarDecl *VD,2632const DeclUseTracker &Tracker,2633ASTContext &Ctx,2634UnsafeBufferUsageHandler &Handler) {2635const DeclStmt *DS = Tracker.lookupDecl(VD);2636if (!DS) {2637DEBUG_NOTE_DECL_FAIL(VD,2638" : variables declared this way not implemented yet");2639return {};2640}2641if (!DS->isSingleDecl()) {2642// FIXME: to support handling multiple `VarDecl`s in a single `DeclStmt`2643DEBUG_NOTE_DECL_FAIL(VD, " : multiple VarDecls");2644return {};2645}2646// Currently DS is an unused variable but we'll need it when2647// non-single decls are implemented, where the pointee type name2648// and the '*' are spread around the place.2649(void)DS;26502651// FIXME: handle cases where DS has multiple declarations2652return fixLocalVarDeclWithSpan(VD, Ctx, getUserFillPlaceHolder(), Handler);2653}26542655static FixItList fixVarDeclWithArray(const VarDecl *D, const ASTContext &Ctx,2656UnsafeBufferUsageHandler &Handler) {2657FixItList FixIts{};26582659// Note: the code below expects the declaration to not use any type sugar like2660// typedef.2661if (auto CAT = dyn_cast<clang::ConstantArrayType>(D->getType())) {2662const QualType &ArrayEltT = CAT->getElementType();2663assert(!ArrayEltT.isNull() && "Trying to fix a non-array type variable!");2664// FIXME: support multi-dimensional arrays2665if (isa<clang::ArrayType>(ArrayEltT.getCanonicalType()))2666return {};26672668const SourceLocation IdentifierLoc = getVarDeclIdentifierLoc(D);26692670// Get the spelling of the element type as written in the source file2671// (including macros, etc.).2672auto MaybeElemTypeTxt =2673getRangeText({D->getBeginLoc(), IdentifierLoc}, Ctx.getSourceManager(),2674Ctx.getLangOpts());2675if (!MaybeElemTypeTxt)2676return {};2677const llvm::StringRef ElemTypeTxt = MaybeElemTypeTxt->trim();26782679// Find the '[' token.2680std::optional<Token> NextTok = Lexer::findNextToken(2681IdentifierLoc, Ctx.getSourceManager(), Ctx.getLangOpts());2682while (NextTok && !NextTok->is(tok::l_square) &&2683NextTok->getLocation() <= D->getSourceRange().getEnd())2684NextTok = Lexer::findNextToken(NextTok->getLocation(),2685Ctx.getSourceManager(), Ctx.getLangOpts());2686if (!NextTok)2687return {};2688const SourceLocation LSqBracketLoc = NextTok->getLocation();26892690// Get the spelling of the array size as written in the source file2691// (including macros, etc.).2692auto MaybeArraySizeTxt = getRangeText(2693{LSqBracketLoc.getLocWithOffset(1), D->getTypeSpecEndLoc()},2694Ctx.getSourceManager(), Ctx.getLangOpts());2695if (!MaybeArraySizeTxt)2696return {};2697const llvm::StringRef ArraySizeTxt = MaybeArraySizeTxt->trim();2698if (ArraySizeTxt.empty()) {2699// FIXME: Support array size getting determined from the initializer.2700// Examples:2701// int arr1[] = {0, 1, 2};2702// int arr2{3, 4, 5};2703// We might be able to preserve the non-specified size with `auto` and2704// `std::to_array`:2705// auto arr1 = std::to_array<int>({0, 1, 2});2706return {};2707}27082709std::optional<StringRef> IdentText =2710getVarDeclIdentifierText(D, Ctx.getSourceManager(), Ctx.getLangOpts());27112712if (!IdentText) {2713DEBUG_NOTE_DECL_FAIL(D, " : failed to locate the identifier");2714return {};2715}27162717SmallString<32> Replacement;2718raw_svector_ostream OS(Replacement);2719OS << "std::array<" << ElemTypeTxt << ", " << ArraySizeTxt << "> "2720<< IdentText->str();27212722FixIts.push_back(FixItHint::CreateReplacement(2723SourceRange{D->getBeginLoc(), D->getTypeSpecEndLoc()}, OS.str()));2724}27252726return FixIts;2727}27282729static FixItList fixVariableWithArray(const VarDecl *VD,2730const DeclUseTracker &Tracker,2731const ASTContext &Ctx,2732UnsafeBufferUsageHandler &Handler) {2733const DeclStmt *DS = Tracker.lookupDecl(VD);2734assert(DS && "Fixing non-local variables not implemented yet!");2735if (!DS->isSingleDecl()) {2736// FIXME: to support handling multiple `VarDecl`s in a single `DeclStmt`2737return {};2738}2739// Currently DS is an unused variable but we'll need it when2740// non-single decls are implemented, where the pointee type name2741// and the '*' are spread around the place.2742(void)DS;27432744// FIXME: handle cases where DS has multiple declarations2745return fixVarDeclWithArray(VD, Ctx, Handler);2746}27472748// TODO: we should be consistent to use `std::nullopt` to represent no-fix due2749// to any unexpected problem.2750static FixItList2751fixVariable(const VarDecl *VD, FixitStrategy::Kind K,2752/* The function decl under analysis */ const Decl *D,2753const DeclUseTracker &Tracker, ASTContext &Ctx,2754UnsafeBufferUsageHandler &Handler) {2755if (const auto *PVD = dyn_cast<ParmVarDecl>(VD)) {2756auto *FD = dyn_cast<clang::FunctionDecl>(PVD->getDeclContext());2757if (!FD || FD != D) {2758// `FD != D` means that `PVD` belongs to a function that is not being2759// analyzed currently. Thus `FD` may not be complete.2760DEBUG_NOTE_DECL_FAIL(VD, " : function not currently analyzed");2761return {};2762}27632764// TODO If function has a try block we can't change params unless we check2765// also its catch block for their use.2766// FIXME We might support static class methods, some select methods,2767// operators and possibly lamdas.2768if (FD->isMain() || FD->isConstexpr() ||2769FD->getTemplatedKind() != FunctionDecl::TemplatedKind::TK_NonTemplate ||2770FD->isVariadic() ||2771// also covers call-operator of lamdas2772isa<CXXMethodDecl>(FD) ||2773// skip when the function body is a try-block2774(FD->hasBody() && isa<CXXTryStmt>(FD->getBody())) ||2775FD->isOverloadedOperator()) {2776DEBUG_NOTE_DECL_FAIL(VD, " : unsupported function decl");2777return {}; // TODO test all these cases2778}2779}27802781switch (K) {2782case FixitStrategy::Kind::Span: {2783if (VD->getType()->isPointerType()) {2784if (const auto *PVD = dyn_cast<ParmVarDecl>(VD))2785return fixParamWithSpan(PVD, Ctx, Handler);27862787if (VD->isLocalVarDecl())2788return fixVariableWithSpan(VD, Tracker, Ctx, Handler);2789}2790DEBUG_NOTE_DECL_FAIL(VD, " : not a pointer");2791return {};2792}2793case FixitStrategy::Kind::Array: {2794if (VD->isLocalVarDecl() &&2795isa<clang::ConstantArrayType>(VD->getType().getCanonicalType()))2796return fixVariableWithArray(VD, Tracker, Ctx, Handler);27972798DEBUG_NOTE_DECL_FAIL(VD, " : not a local const-size array");2799return {};2800}2801case FixitStrategy::Kind::Iterator:2802case FixitStrategy::Kind::Vector:2803llvm_unreachable("FixitStrategy not implemented yet!");2804case FixitStrategy::Kind::Wontfix:2805llvm_unreachable("Invalid strategy!");2806}2807llvm_unreachable("Unknown strategy!");2808}28092810// Returns true iff there exists a `FixItHint` 'h' in `FixIts` such that the2811// `RemoveRange` of 'h' overlaps with a macro use.2812static bool overlapWithMacro(const FixItList &FixIts) {2813// FIXME: For now we only check if the range (or the first token) is (part of)2814// a macro expansion. Ideally, we want to check for all tokens in the range.2815return llvm::any_of(FixIts, [](const FixItHint &Hint) {2816auto Range = Hint.RemoveRange;2817if (Range.getBegin().isMacroID() || Range.getEnd().isMacroID())2818// If the range (or the first token) is (part of) a macro expansion:2819return true;2820return false;2821});2822}28232824// Returns true iff `VD` is a parameter of the declaration `D`:2825static bool isParameterOf(const VarDecl *VD, const Decl *D) {2826return isa<ParmVarDecl>(VD) &&2827VD->getDeclContext() == dyn_cast<DeclContext>(D);2828}28292830// Erases variables in `FixItsForVariable`, if such a variable has an unfixable2831// group mate. A variable `v` is unfixable iff `FixItsForVariable` does not2832// contain `v`.2833static void eraseVarsForUnfixableGroupMates(2834std::map<const VarDecl *, FixItList> &FixItsForVariable,2835const VariableGroupsManager &VarGrpMgr) {2836// Variables will be removed from `FixItsForVariable`:2837SmallVector<const VarDecl *, 8> ToErase;28382839for (const auto &[VD, Ignore] : FixItsForVariable) {2840VarGrpRef Grp = VarGrpMgr.getGroupOfVar(VD);2841if (llvm::any_of(Grp,2842[&FixItsForVariable](const VarDecl *GrpMember) -> bool {2843return !FixItsForVariable.count(GrpMember);2844})) {2845// At least one group member cannot be fixed, so we have to erase the2846// whole group:2847for (const VarDecl *Member : Grp)2848ToErase.push_back(Member);2849}2850}2851for (auto *VarToErase : ToErase)2852FixItsForVariable.erase(VarToErase);2853}28542855// Returns the fix-its that create bounds-safe function overloads for the2856// function `D`, if `D`'s parameters will be changed to safe-types through2857// fix-its in `FixItsForVariable`.2858//2859// NOTE: In case `D`'s parameters will be changed but bounds-safe function2860// overloads cannot created, the whole group that contains the parameters will2861// be erased from `FixItsForVariable`.2862static FixItList createFunctionOverloadsForParms(2863std::map<const VarDecl *, FixItList> &FixItsForVariable /* mutable */,2864const VariableGroupsManager &VarGrpMgr, const FunctionDecl *FD,2865const FixitStrategy &S, ASTContext &Ctx,2866UnsafeBufferUsageHandler &Handler) {2867FixItList FixItsSharedByParms{};28682869std::optional<FixItList> OverloadFixes =2870createOverloadsForFixedParams(S, FD, Ctx, Handler);28712872if (OverloadFixes) {2873FixItsSharedByParms.append(*OverloadFixes);2874} else {2875// Something wrong in generating `OverloadFixes`, need to remove the2876// whole group, where parameters are in, from `FixItsForVariable` (Note2877// that all parameters should be in the same group):2878for (auto *Member : VarGrpMgr.getGroupOfParms())2879FixItsForVariable.erase(Member);2880}2881return FixItsSharedByParms;2882}28832884// Constructs self-contained fix-its for each variable in `FixablesForAllVars`.2885static std::map<const VarDecl *, FixItList>2886getFixIts(FixableGadgetSets &FixablesForAllVars, const FixitStrategy &S,2887ASTContext &Ctx,2888/* The function decl under analysis */ const Decl *D,2889const DeclUseTracker &Tracker, UnsafeBufferUsageHandler &Handler,2890const VariableGroupsManager &VarGrpMgr) {2891// `FixItsForVariable` will map each variable to a set of fix-its directly2892// associated to the variable itself. Fix-its of distinct variables in2893// `FixItsForVariable` are disjoint.2894std::map<const VarDecl *, FixItList> FixItsForVariable;28952896// Populate `FixItsForVariable` with fix-its directly associated with each2897// variable. Fix-its directly associated to a variable 'v' are the ones2898// produced by the `FixableGadget`s whose claimed variable is 'v'.2899for (const auto &[VD, Fixables] : FixablesForAllVars.byVar) {2900FixItsForVariable[VD] =2901fixVariable(VD, S.lookup(VD), D, Tracker, Ctx, Handler);2902// If we fail to produce Fix-It for the declaration we have to skip the2903// variable entirely.2904if (FixItsForVariable[VD].empty()) {2905FixItsForVariable.erase(VD);2906continue;2907}2908for (const auto &F : Fixables) {2909std::optional<FixItList> Fixits = F->getFixits(S);29102911if (Fixits) {2912FixItsForVariable[VD].insert(FixItsForVariable[VD].end(),2913Fixits->begin(), Fixits->end());2914continue;2915}2916#ifndef NDEBUG2917Handler.addDebugNoteForVar(2918VD, F->getSourceLoc(),2919("gadget '" + F->getDebugName() + "' refused to produce a fix")2920.str());2921#endif2922FixItsForVariable.erase(VD);2923break;2924}2925}29262927// `FixItsForVariable` now contains only variables that can be2928// fixed. A variable can be fixed if its' declaration and all Fixables2929// associated to it can all be fixed.29302931// To further remove from `FixItsForVariable` variables whose group mates2932// cannot be fixed...2933eraseVarsForUnfixableGroupMates(FixItsForVariable, VarGrpMgr);2934// Now `FixItsForVariable` gets further reduced: a variable is in2935// `FixItsForVariable` iff it can be fixed and all its group mates can be2936// fixed.29372938// Fix-its of bounds-safe overloads of `D` are shared by parameters of `D`.2939// That is, when fixing multiple parameters in one step, these fix-its will2940// be applied only once (instead of being applied per parameter).2941FixItList FixItsSharedByParms{};29422943if (auto *FD = dyn_cast<FunctionDecl>(D))2944FixItsSharedByParms = createFunctionOverloadsForParms(2945FixItsForVariable, VarGrpMgr, FD, S, Ctx, Handler);29462947// The map that maps each variable `v` to fix-its for the whole group where2948// `v` is in:2949std::map<const VarDecl *, FixItList> FinalFixItsForVariable{2950FixItsForVariable};29512952for (auto &[Var, Ignore] : FixItsForVariable) {2953bool AnyParm = false;2954const auto VarGroupForVD = VarGrpMgr.getGroupOfVar(Var, &AnyParm);29552956for (const VarDecl *GrpMate : VarGroupForVD) {2957if (Var == GrpMate)2958continue;2959if (FixItsForVariable.count(GrpMate))2960FinalFixItsForVariable[Var].append(FixItsForVariable[GrpMate]);2961}2962if (AnyParm) {2963// This assertion should never fail. Otherwise we have a bug.2964assert(!FixItsSharedByParms.empty() &&2965"Should not try to fix a parameter that does not belong to a "2966"FunctionDecl");2967FinalFixItsForVariable[Var].append(FixItsSharedByParms);2968}2969}2970// Fix-its that will be applied in one step shall NOT:2971// 1. overlap with macros or/and templates; or2972// 2. conflict with each other.2973// Otherwise, the fix-its will be dropped.2974for (auto Iter = FinalFixItsForVariable.begin();2975Iter != FinalFixItsForVariable.end();)2976if (overlapWithMacro(Iter->second) ||2977clang::internal::anyConflict(Iter->second, Ctx.getSourceManager())) {2978Iter = FinalFixItsForVariable.erase(Iter);2979} else2980Iter++;2981return FinalFixItsForVariable;2982}29832984template <typename VarDeclIterTy>2985static FixitStrategy2986getNaiveStrategy(llvm::iterator_range<VarDeclIterTy> UnsafeVars) {2987FixitStrategy S;2988for (const VarDecl *VD : UnsafeVars) {2989if (isa<ConstantArrayType>(VD->getType().getCanonicalType()))2990S.set(VD, FixitStrategy::Kind::Array);2991else2992S.set(VD, FixitStrategy::Kind::Span);2993}2994return S;2995}29962997// Manages variable groups:2998class VariableGroupsManagerImpl : public VariableGroupsManager {2999const std::vector<VarGrpTy> Groups;3000const std::map<const VarDecl *, unsigned> &VarGrpMap;3001const llvm::SetVector<const VarDecl *> &GrpsUnionForParms;30023003public:3004VariableGroupsManagerImpl(3005const std::vector<VarGrpTy> &Groups,3006const std::map<const VarDecl *, unsigned> &VarGrpMap,3007const llvm::SetVector<const VarDecl *> &GrpsUnionForParms)3008: Groups(Groups), VarGrpMap(VarGrpMap),3009GrpsUnionForParms(GrpsUnionForParms) {}30103011VarGrpRef getGroupOfVar(const VarDecl *Var, bool *HasParm) const override {3012if (GrpsUnionForParms.contains(Var)) {3013if (HasParm)3014*HasParm = true;3015return GrpsUnionForParms.getArrayRef();3016}3017if (HasParm)3018*HasParm = false;30193020auto It = VarGrpMap.find(Var);30213022if (It == VarGrpMap.end())3023return std::nullopt;3024return Groups[It->second];3025}30263027VarGrpRef getGroupOfParms() const override {3028return GrpsUnionForParms.getArrayRef();3029}3030};30313032void clang::checkUnsafeBufferUsage(const Decl *D,3033UnsafeBufferUsageHandler &Handler,3034bool EmitSuggestions) {3035#ifndef NDEBUG3036Handler.clearDebugNotes();3037#endif30383039assert(D && D->getBody());3040// We do not want to visit a Lambda expression defined inside a method3041// independently. Instead, it should be visited along with the outer method.3042// FIXME: do we want to do the same thing for `BlockDecl`s?3043if (const auto *fd = dyn_cast<CXXMethodDecl>(D)) {3044if (fd->getParent()->isLambda() && fd->getParent()->isLocalClass())3045return;3046}30473048// Do not emit fixit suggestions for functions declared in an3049// extern "C" block.3050if (const auto *FD = dyn_cast<FunctionDecl>(D)) {3051for (FunctionDecl *FReDecl : FD->redecls()) {3052if (FReDecl->isExternC()) {3053EmitSuggestions = false;3054break;3055}3056}3057}30583059WarningGadgetSets UnsafeOps;3060FixableGadgetSets FixablesForAllVars;30613062auto [FixableGadgets, WarningGadgets, Tracker] =3063findGadgets(D, Handler, EmitSuggestions);30643065if (!EmitSuggestions) {3066// Our job is very easy without suggestions. Just warn about3067// every problematic operation and consider it done. No need to deal3068// with fixable gadgets, no need to group operations by variable.3069for (const auto &G : WarningGadgets) {3070G->handleUnsafeOperation(Handler, /*IsRelatedToDecl=*/false,3071D->getASTContext());3072}30733074// This return guarantees that most of the machine doesn't run when3075// suggestions aren't requested.3076assert(FixableGadgets.size() == 0 &&3077"Fixable gadgets found but suggestions not requested!");3078return;3079}30803081// If no `WarningGadget`s ever matched, there is no unsafe operations in the3082// function under the analysis. No need to fix any Fixables.3083if (!WarningGadgets.empty()) {3084// Gadgets "claim" variables they're responsible for. Once this loop3085// finishes, the tracker will only track DREs that weren't claimed by any3086// gadgets, i.e. not understood by the analysis.3087for (const auto &G : FixableGadgets) {3088for (const auto *DRE : G->getClaimedVarUseSites()) {3089Tracker.claimUse(DRE);3090}3091}3092}30933094// If no `WarningGadget`s ever matched, there is no unsafe operations in the3095// function under the analysis. Thus, it early returns here as there is3096// nothing needs to be fixed.3097//3098// Note this claim is based on the assumption that there is no unsafe3099// variable whose declaration is invisible from the analyzing function.3100// Otherwise, we need to consider if the uses of those unsafe varuables needs3101// fix.3102// So far, we are not fixing any global variables or class members. And,3103// lambdas will be analyzed along with the enclosing function. So this early3104// return is correct for now.3105if (WarningGadgets.empty())3106return;31073108UnsafeOps = groupWarningGadgetsByVar(std::move(WarningGadgets));3109FixablesForAllVars = groupFixablesByVar(std::move(FixableGadgets));31103111std::map<const VarDecl *, FixItList> FixItsForVariableGroup;31123113// Filter out non-local vars and vars with unclaimed DeclRefExpr-s.3114for (auto it = FixablesForAllVars.byVar.cbegin();3115it != FixablesForAllVars.byVar.cend();) {3116// FIXME: need to deal with global variables later3117if ((!it->first->isLocalVarDecl() && !isa<ParmVarDecl>(it->first))) {3118#ifndef NDEBUG3119Handler.addDebugNoteForVar(it->first, it->first->getBeginLoc(),3120("failed to produce fixit for '" +3121it->first->getNameAsString() +3122"' : neither local nor a parameter"));3123#endif3124it = FixablesForAllVars.byVar.erase(it);3125} else if (it->first->getType().getCanonicalType()->isReferenceType()) {3126#ifndef NDEBUG3127Handler.addDebugNoteForVar(it->first, it->first->getBeginLoc(),3128("failed to produce fixit for '" +3129it->first->getNameAsString() +3130"' : has a reference type"));3131#endif3132it = FixablesForAllVars.byVar.erase(it);3133} else if (Tracker.hasUnclaimedUses(it->first)) {3134it = FixablesForAllVars.byVar.erase(it);3135} else if (it->first->isInitCapture()) {3136#ifndef NDEBUG3137Handler.addDebugNoteForVar(it->first, it->first->getBeginLoc(),3138("failed to produce fixit for '" +3139it->first->getNameAsString() +3140"' : init capture"));3141#endif3142it = FixablesForAllVars.byVar.erase(it);3143} else {3144++it;3145}3146}31473148#ifndef NDEBUG3149for (const auto &it : UnsafeOps.byVar) {3150const VarDecl *const UnsafeVD = it.first;3151auto UnclaimedDREs = Tracker.getUnclaimedUses(UnsafeVD);3152if (UnclaimedDREs.empty())3153continue;3154const auto UnfixedVDName = UnsafeVD->getNameAsString();3155for (const clang::DeclRefExpr *UnclaimedDRE : UnclaimedDREs) {3156std::string UnclaimedUseTrace =3157getDREAncestorString(UnclaimedDRE, D->getASTContext());31583159Handler.addDebugNoteForVar(3160UnsafeVD, UnclaimedDRE->getBeginLoc(),3161("failed to produce fixit for '" + UnfixedVDName +3162"' : has an unclaimed use\nThe unclaimed DRE trace: " +3163UnclaimedUseTrace));3164}3165}3166#endif31673168// Fixpoint iteration for pointer assignments3169using DepMapTy = DenseMap<const VarDecl *, llvm::SetVector<const VarDecl *>>;3170DepMapTy DependenciesMap{};3171DepMapTy PtrAssignmentGraph{};31723173for (auto it : FixablesForAllVars.byVar) {3174for (const FixableGadget *fixable : it.second) {3175std::optional<std::pair<const VarDecl *, const VarDecl *>> ImplPair =3176fixable->getStrategyImplications();3177if (ImplPair) {3178std::pair<const VarDecl *, const VarDecl *> Impl = std::move(*ImplPair);3179PtrAssignmentGraph[Impl.first].insert(Impl.second);3180}3181}3182}31833184/*3185The following code does a BFS traversal of the `PtrAssignmentGraph`3186considering all unsafe vars as starting nodes and constructs an undirected3187graph `DependenciesMap`. Constructing the `DependenciesMap` in this manner3188elimiates all variables that are unreachable from any unsafe var. In other3189words, this removes all dependencies that don't include any unsafe variable3190and consequently don't need any fixit generation.3191Note: A careful reader would observe that the code traverses3192`PtrAssignmentGraph` using `CurrentVar` but adds edges between `Var` and3193`Adj` and not between `CurrentVar` and `Adj`. Both approaches would3194achieve the same result but the one used here dramatically cuts the3195amount of hoops the second part of the algorithm needs to jump, given that3196a lot of these connections become "direct". The reader is advised not to3197imagine how the graph is transformed because of using `Var` instead of3198`CurrentVar`. The reader can continue reading as if `CurrentVar` was used,3199and think about why it's equivalent later.3200*/3201std::set<const VarDecl *> VisitedVarsDirected{};3202for (const auto &[Var, ignore] : UnsafeOps.byVar) {3203if (VisitedVarsDirected.find(Var) == VisitedVarsDirected.end()) {32043205std::queue<const VarDecl *> QueueDirected{};3206QueueDirected.push(Var);3207while (!QueueDirected.empty()) {3208const VarDecl *CurrentVar = QueueDirected.front();3209QueueDirected.pop();3210VisitedVarsDirected.insert(CurrentVar);3211auto AdjacentNodes = PtrAssignmentGraph[CurrentVar];3212for (const VarDecl *Adj : AdjacentNodes) {3213if (VisitedVarsDirected.find(Adj) == VisitedVarsDirected.end()) {3214QueueDirected.push(Adj);3215}3216DependenciesMap[Var].insert(Adj);3217DependenciesMap[Adj].insert(Var);3218}3219}3220}3221}32223223// `Groups` stores the set of Connected Components in the graph.3224std::vector<VarGrpTy> Groups;3225// `VarGrpMap` maps variables that need fix to the groups (indexes) that the3226// variables belong to. Group indexes refer to the elements in `Groups`.3227// `VarGrpMap` is complete in that every variable that needs fix is in it.3228std::map<const VarDecl *, unsigned> VarGrpMap;3229// The union group over the ones in "Groups" that contain parameters of `D`:3230llvm::SetVector<const VarDecl *>3231GrpsUnionForParms; // these variables need to be fixed in one step32323233// Group Connected Components for Unsafe Vars3234// (Dependencies based on pointer assignments)3235std::set<const VarDecl *> VisitedVars{};3236for (const auto &[Var, ignore] : UnsafeOps.byVar) {3237if (VisitedVars.find(Var) == VisitedVars.end()) {3238VarGrpTy &VarGroup = Groups.emplace_back();3239std::queue<const VarDecl *> Queue{};32403241Queue.push(Var);3242while (!Queue.empty()) {3243const VarDecl *CurrentVar = Queue.front();3244Queue.pop();3245VisitedVars.insert(CurrentVar);3246VarGroup.push_back(CurrentVar);3247auto AdjacentNodes = DependenciesMap[CurrentVar];3248for (const VarDecl *Adj : AdjacentNodes) {3249if (VisitedVars.find(Adj) == VisitedVars.end()) {3250Queue.push(Adj);3251}3252}3253}32543255bool HasParm = false;3256unsigned GrpIdx = Groups.size() - 1;32573258for (const VarDecl *V : VarGroup) {3259VarGrpMap[V] = GrpIdx;3260if (!HasParm && isParameterOf(V, D))3261HasParm = true;3262}3263if (HasParm)3264GrpsUnionForParms.insert(VarGroup.begin(), VarGroup.end());3265}3266}32673268// Remove a `FixableGadget` if the associated variable is not in the graph3269// computed above. We do not want to generate fix-its for such variables,3270// since they are neither warned nor reachable from a warned one.3271//3272// Note a variable is not warned if it is not directly used in any unsafe3273// operation. A variable `v` is NOT reachable from an unsafe variable, if it3274// does not exist another variable `u` such that `u` is warned and fixing `u`3275// (transitively) implicates fixing `v`.3276//3277// For example,3278// ```3279// void f(int * p) {3280// int * a = p; *p = 0;3281// }3282// ```3283// `*p = 0` is a fixable gadget associated with a variable `p` that is neither3284// warned nor reachable from a warned one. If we add `a[5] = 0` to the end of3285// the function above, `p` becomes reachable from a warned variable.3286for (auto I = FixablesForAllVars.byVar.begin();3287I != FixablesForAllVars.byVar.end();) {3288// Note `VisitedVars` contain all the variables in the graph:3289if (!VisitedVars.count((*I).first)) {3290// no such var in graph:3291I = FixablesForAllVars.byVar.erase(I);3292} else3293++I;3294}32953296// We assign strategies to variables that are 1) in the graph and 2) can be3297// fixed. Other variables have the default "Won't fix" strategy.3298FixitStrategy NaiveStrategy = getNaiveStrategy(llvm::make_filter_range(3299VisitedVars, [&FixablesForAllVars](const VarDecl *V) {3300// If a warned variable has no "Fixable", it is considered unfixable:3301return FixablesForAllVars.byVar.count(V);3302}));3303VariableGroupsManagerImpl VarGrpMgr(Groups, VarGrpMap, GrpsUnionForParms);33043305if (isa<NamedDecl>(D))3306// The only case where `D` is not a `NamedDecl` is when `D` is a3307// `BlockDecl`. Let's not fix variables in blocks for now3308FixItsForVariableGroup =3309getFixIts(FixablesForAllVars, NaiveStrategy, D->getASTContext(), D,3310Tracker, Handler, VarGrpMgr);33113312for (const auto &G : UnsafeOps.noVar) {3313G->handleUnsafeOperation(Handler, /*IsRelatedToDecl=*/false,3314D->getASTContext());3315}33163317for (const auto &[VD, WarningGadgets] : UnsafeOps.byVar) {3318auto FixItsIt = FixItsForVariableGroup.find(VD);3319Handler.handleUnsafeVariableGroup(VD, VarGrpMgr,3320FixItsIt != FixItsForVariableGroup.end()3321? std::move(FixItsIt->second)3322: FixItList{},3323D, NaiveStrategy);3324for (const auto &G : WarningGadgets) {3325G->handleUnsafeOperation(Handler, /*IsRelatedToDecl=*/true,3326D->getASTContext());3327}3328}3329}333033313332