Index: clang/include/clang/Analysis/Analyses/UnsafeBufferUsage.h =================================================================== --- clang/include/clang/Analysis/Analyses/UnsafeBufferUsage.h +++ clang/include/clang/Analysis/Analyses/UnsafeBufferUsage.h @@ -36,9 +36,12 @@ virtual void handleUnsafeOperation(const Stmt *Operation, bool IsRelatedToDecl) = 0; - /// Invoked when a fix is suggested against a variable. - virtual void handleFixableVariable(const VarDecl *Variable, - FixItList &&List) = 0; + /// Invoked when a fix is suggested against a variable. This function groups + /// all variables that must be fixed together (i.e their types must be changed to the + /// same target type to prevent type mismatches) into a single fixit. + virtual void handleUnsafeVariableGroup(const VarDecl *Variable, + std::map> VarGrpMap, + FixItList &&Fixes) = 0; /// Returns a reference to the `Preprocessor`: virtual bool isSafeBufferOptOut(const SourceLocation &Loc) const = 0; Index: clang/include/clang/Analysis/Analyses/UnsafeBufferUsageGadgets.def =================================================================== --- clang/include/clang/Analysis/Analyses/UnsafeBufferUsageGadgets.def +++ clang/include/clang/Analysis/Analyses/UnsafeBufferUsageGadgets.def @@ -36,6 +36,7 @@ FIXABLE_GADGET(UPCAddressofArraySubscript) // '&DRE[any]' in an Unspecified Pointer Context FIXABLE_GADGET(DerefSimplePtrArithFixable) FIXABLE_GADGET(PointerCtxAccess) +FIXABLE_GADGET(PointerAssignment) #undef FIXABLE_GADGET #undef WARNING_GADGET Index: clang/include/clang/Basic/DiagnosticSemaKinds.td =================================================================== --- clang/include/clang/Basic/DiagnosticSemaKinds.td +++ clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -11792,6 +11792,8 @@ "used%select{| in pointer arithmetic| in buffer access}0 here">; def note_unsafe_buffer_variable_fixit : Note< "change type of '%0' to '%select{std::span|std::array|std::span::iterator}1' to preserve bounds information">; +def note_unsafe_buffer_variable_fixit_group : Note< + "change type of '%0' to '%select{std::span|std::array|std::span::iterator}1' to preserve bounds information%2">; def err_loongarch_builtin_requires_la32 : Error< "this builtin requires target: loongarch32">; } // end of sema component. Index: clang/lib/Analysis/UnsafeBufferUsage.cpp =================================================================== --- clang/lib/Analysis/UnsafeBufferUsage.cpp +++ clang/lib/Analysis/UnsafeBufferUsage.cpp @@ -24,6 +24,7 @@ #include "llvm/ADT/SmallVector.h" #include #include +#include using namespace llvm; using namespace clang; @@ -270,6 +271,16 @@ virtual std::optional getFixits(const Strategy &) const { return std::nullopt; } + + /// Returns a list of two elements where the first element is the LHS of a pointer assignment + /// statement and the second element is the RHS. This two-element list represents the fact that + /// the LHS buffer gets its bounds information from the RHS buffer. This information will be used + /// later to group all those variables whose types must be modified together to prevent type + /// mismatches. + virtual std::optional> + getStrategyImplications() const { + return std::nullopt; + } }; using FixableGadgetList = std::vector>; @@ -536,6 +547,57 @@ } }; +/// A pointer assignment expression of the form: +/// \code +/// p = q; +/// \endcode +class PointerAssignmentGadget : public FixableGadget { +private: + static constexpr const char *const PointerAssignemntTag = "ptrAssign"; + static constexpr const char *const PointerAssignLHSTag = "ptrLHS"; + static constexpr const char *const PointerAssignRHSTag = "ptrRHS"; + const BinaryOperator *PA; // pointer arithmetic expression + const DeclRefExpr * PtrLHS; // the LHS pointer expression in `PA` + const DeclRefExpr * PtrRHS; // the RHS pointer expression in `PA` + +public: + PointerAssignmentGadget(const MatchFinder::MatchResult &Result) + : FixableGadget(Kind::PointerAssignment), + PA(Result.Nodes.getNodeAs(PointerAssignemntTag)), + PtrLHS(Result.Nodes.getNodeAs(PointerAssignLHSTag)), + PtrRHS(Result.Nodes.getNodeAs(PointerAssignRHSTag)) {} + + static bool classof(const Gadget *G) { + return G->getKind() == Kind::PointerAssignment; + } + + static Matcher matcher() { + auto PtrAtRight = allOf(hasOperatorName("="), + hasRHS(ignoringParenImpCasts(declRefExpr(hasPointerType(), to(varDecl())). + bind(PointerAssignRHSTag)))); + auto PtrAtLeft = allOf(hasOperatorName("="), + hasLHS(declRefExpr(hasPointerType(), to(varDecl())).bind(PointerAssignLHSTag))); + + //FIXME: Handle declarations at assignments + + return stmt(binaryOperator(allOf(PtrAtLeft, PtrAtRight))); + } + + virtual std::optional getFixits(const Strategy &S) const override; + + virtual const Stmt *getBaseStmt() const override { return PA; } + + virtual DeclUseList getClaimedVarUseSites() const override { + return DeclUseList{PtrLHS, PtrRHS}; + } + + virtual std::optional> + getStrategyImplications() const override { + return std::make_pair(dyn_cast(PtrLHS->getDecl()), + dyn_cast(PtrRHS->getDecl())); + } +}; + class PointerDereferenceGadget : public FixableGadget { static constexpr const char *const BaseDeclRefExprTag = "BaseDRE"; static constexpr const char *const OperatorTag = "op"; @@ -927,13 +989,13 @@ } struct WarningGadgetSets { - std::map>> byVar; + std::map> byVar; // These Gadgets are not related to pointer variables (e. g. temporaries). - llvm::SmallVector, 16> noVar; + llvm::SmallVector noVar; }; static WarningGadgetSets -groupWarningGadgetsByVar(WarningGadgetList &&AllUnsafeOperations) { +groupWarningGadgetsByVar(const WarningGadgetList &AllUnsafeOperations) { WarningGadgetSets result; // If some gadgets cover more than one // variable, they'll appear more than once in the map. @@ -943,13 +1005,13 @@ bool AssociatedWithVarDecl = false; for (const DeclRefExpr *DRE : ClaimedVarUseSites) { if (const auto *VD = dyn_cast(DRE->getDecl())) { - result.byVar[VD].emplace(std::move(G)); + result.byVar[VD].insert(G.get()); AssociatedWithVarDecl = true; } } if (!AssociatedWithVarDecl) { - result.noVar.emplace_back(std::move(G)); + result.noVar.push_back(G.get()); continue; } } @@ -957,7 +1019,7 @@ } struct FixableGadgetSets { - std::map>> byVar; + std::map> byVar; }; static FixableGadgetSets @@ -968,7 +1030,7 @@ for (const DeclRefExpr *DRE : DREs) { if (const auto *VD = dyn_cast(DRE->getDecl())) { - FixablesForUnsafeVars.byVar[VD].emplace(std::move(F)); + FixablesForUnsafeVars.byVar[VD].insert(F.get()); } } } @@ -1057,6 +1119,26 @@ return std::nullopt; // something went wrong, no fix-it } +std::optional +PointerAssignmentGadget::getFixits(const Strategy &S) const { + if (const VarDecl *LeftVD = dyn_cast(PtrLHS->getDecl())) + if (const VarDecl *RightVD = dyn_cast(PtrRHS->getDecl())) { + switch (S.lookup(LeftVD)) { + case Strategy::Kind::Span: + if (S.lookup(RightVD) == Strategy::Kind::Span) + return FixItList{}; + return std::nullopt; + case Strategy::Kind::Wontfix: + return std::nullopt; + case Strategy::Kind::Iterator: + case Strategy::Kind::Array: + case Strategy::Kind::Vector: + llvm_unreachable("unsupported strategies for FixableGadgets"); + } + } + return std::nullopt; +} + // Return the text representation of the given `APInt Val`: static std::string getAPIntText(APInt Val) { SmallVector Txt; @@ -1565,10 +1647,23 @@ }; } +static bool impossibleToFixForVar(const FixableGadgetSets &FixablesForUnsafeVars, + const Strategy &S, + const VarDecl * Var) { + for (const auto &F : FixablesForUnsafeVars.byVar.find(Var)->second) { + std::optional Fixits = F->getFixits(S); + if (!Fixits) { + return true; + } + } + return false; +} + static std::map getFixIts(FixableGadgetSets &FixablesForUnsafeVars, const Strategy &S, - const DeclUseTracker &Tracker, const ASTContext &Ctx, Sema &SA, - UnsafeBufferUsageHandler &Handler) { + const DeclUseTracker &Tracker, const ASTContext &Ctx, Sema &SA, + UnsafeBufferUsageHandler &Handler, + const std::map> &VarGrpMap) { const SourceManager &SM = Ctx.getSourceManager(); std::map FixItsForVariable; for (const auto &[VD, Fixables] : FixablesForUnsafeVars.byVar) { @@ -1599,7 +1694,27 @@ FixItsForVariable.erase(VD); continue; } + + const auto VarGroupForVD = VarGrpMap.find(VD); + if (VarGroupForVD != VarGrpMap.end()) { + for (const VarDecl * V : VarGroupForVD->second) { + if (V == VD) { + continue; + } + if (impossibleToFixForVar(FixablesForUnsafeVars, S, V)) { + ImpossibleToFix = true; + break; + } + } + if (ImpossibleToFix) { + FixItsForVariable.erase(VD); + for (const VarDecl * V : VarGroupForVD->second) { + FixItsForVariable.erase(V); + } + continue; + } + } FixItsForVariable[VD].insert(FixItsForVariable[VD].end(), FixItsForVD.begin(), FixItsForVD.end()); @@ -1642,6 +1757,30 @@ HeaderInsertLoc, Include->getReplacementText())); } } + + for (auto VD : FixItsForVariable) { + const auto VarGroupForVD = VarGrpMap.find(VD.first); + const Strategy::Kind ReplacementTypeForVD = S.lookup(VD.first); + if (VarGroupForVD != VarGrpMap.end()) { + for (const VarDecl * Var : VarGroupForVD->second) { + if (Var == VD.first) { + continue; + } + + FixItList GroupFix; + if (FixItsForVariable.find(Var) == FixItsForVariable.end()) { + GroupFix = fixVariable(Var, ReplacementTypeForVD, Tracker, + Var->getASTContext(), SA, Handler); + } else { + GroupFix = FixItsForVariable[Var]; + } + + for (auto Fix : GroupFix) { + FixItsForVariable[VD.first].push_back(Fix); + } + } + } + } return FixItsForVariable; } @@ -1660,36 +1799,119 @@ assert(D && D->getBody()); WarningGadgetSets UnsafeOps; - FixableGadgetSets FixablesForUnsafeVars; - DeclUseTracker Tracker; + FixableGadgetSets FixablesForAllVars; - { - auto [FixableGadgets, WarningGadgets, TrackerRes] = findGadgets(D, Handler); - UnsafeOps = groupWarningGadgetsByVar(std::move(WarningGadgets)); - FixablesForUnsafeVars = groupFixablesByVar(std::move(FixableGadgets)); - Tracker = std::move(TrackerRes); - } + auto [FixableGadgets, WarningGadgets, Tracker] = findGadgets(D, Handler); + UnsafeOps = groupWarningGadgetsByVar(std::move(WarningGadgets)); + FixablesForAllVars = groupFixablesByVar(std::move(FixableGadgets)); std::map FixItsForVariable; + std::map FixItsForVariableGroup; + std::map> VariableGroupsMap{}; if (EmitFixits) { // Filter out non-local vars and vars with unclaimed DeclRefExpr-s. - for (auto it = FixablesForUnsafeVars.byVar.cbegin(); - it != FixablesForUnsafeVars.byVar.cend();) { + for (auto it = FixablesForAllVars.byVar.cbegin(); + it != FixablesForAllVars.byVar.cend();) { if (Tracker.hasUnclaimedUses(it->first)) { - it = FixablesForUnsafeVars.byVar.erase(it); + it = FixablesForAllVars.byVar.erase(it); } else { ++it; } } llvm::SmallVector UnsafeVars; - for (const auto &[VD, ignore] : FixablesForUnsafeVars.byVar) + for (const auto &[VD, ignore] : FixablesForAllVars.byVar) UnsafeVars.push_back(VD); + // Fixpoint iteration for pointer assignments + using DepMapTy = DenseMap>; + DepMapTy DependenciesMap{}; + DepMapTy PtrAssignmentGraph{}; + + for (auto it : FixablesForAllVars.byVar) { + for (const FixableGadget *fixable : it.second) { + std::optional> ImplPair = + fixable->getStrategyImplications(); + if (ImplPair) { + std::pair Impl = ImplPair.value(); + PtrAssignmentGraph[Impl.first].insert(Impl.second); + } + } + } + + /* + The following code does a BFS traversal of the `PtrAssignmentGraph` + considering all unsafe vars as starting nodes and constructs an undirected + graph `DependenciesMap`. Constructing the `DependenciesMap` in this manner + elimiates all variables that are unreachable from any unsafe var. In other + words, this removes all dependencies that don't include any unsafe variable + and consequently don't need any fixit generation. + Note: A careful reader would observe that the code traverses + `PtrAssignmentGraph` using `CurrentVar` but adds edges between `Var` and + `Adj` and not between `CurrentVar` and `Adj`. Both approaches would + achieve the same result but the one used here dramatically cuts the + amount of hoops the second part of the algorithm needs to jump, given that + a lot of these connections become "direct". The reader is advised not to + imagine how the graph is transformed because of using `Var` instead of + `CurrentVar`. The reader can continue reading as if `CurrentVar` was used, + and think about why it's equivalent later. + */ + std::set VisitedVarsDirected{}; + for (const auto &[Var, ignore] : UnsafeOps.byVar) { + if (VisitedVarsDirected.find(Var) == VisitedVarsDirected.end()) { + + std::queue QueueDirected{}; + QueueDirected.push(Var); + while(!QueueDirected.empty()) { + const VarDecl* CurrentVar = QueueDirected.front(); + QueueDirected.pop(); + VisitedVarsDirected.insert(CurrentVar); + auto AdjacentNodes = PtrAssignmentGraph[CurrentVar]; + for (const VarDecl *Adj : AdjacentNodes) { + if (VisitedVarsDirected.find(Adj) == VisitedVarsDirected.end()) { + QueueDirected.push(Adj); + } + DependenciesMap[Var].insert(Adj); + DependenciesMap[Adj].insert(Var); + } + } + } + } + + // Group Connected Components for Unsafe Vars + // (Dependencies based on pointer assignments) + std::set VisitedVars{}; + for (const auto &[Var, ignore] : UnsafeOps.byVar) { + if (VisitedVars.find(Var) == VisitedVars.end()) { + std::vector VarGroup{}; + + std::queue Queue{}; + Queue.push(Var); + while(!Queue.empty()) { + const VarDecl* CurrentVar = Queue.front(); + Queue.pop(); + VisitedVars.insert(CurrentVar); + VarGroup.push_back(CurrentVar); + auto AdjacentNodes = DependenciesMap[CurrentVar]; + for (const VarDecl *Adj : AdjacentNodes) { + if (VisitedVars.find(Adj) == VisitedVars.end()) { + Queue.push(Adj); + } + } + } + for (const VarDecl * V : VarGroup) { + if (UnsafeOps.byVar.find(V) != UnsafeOps.byVar.end()) { + VariableGroupsMap[V] = VarGroup; + } + } + } + } + Strategy NaiveStrategy = getNaiveStrategy(UnsafeVars); - FixItsForVariable = getFixIts(FixablesForUnsafeVars, NaiveStrategy, Tracker, - D->getASTContext(), S, Handler); + FixItsForVariableGroup = + getFixIts(FixablesForAllVars, NaiveStrategy, Tracker, + D->getASTContext(), S, Handler, VariableGroupsMap); // FIXME Detect overlapping FixIts. } @@ -1699,9 +1921,9 @@ } for (const auto &[VD, WarningGadgets] : UnsafeOps.byVar) { - auto FixItsIt = - EmitFixits ? FixItsForVariable.find(VD) : FixItsForVariable.end(); - Handler.handleFixableVariable(VD, FixItsIt != FixItsForVariable.end() + auto FixItsIt = FixItsForVariableGroup.find(VD); + Handler.handleUnsafeVariableGroup(VD, VariableGroupsMap, FixItsIt != + FixItsForVariableGroup.end() ? std::move(FixItsIt->second) : FixItList{}); for (const auto &G : WarningGadgets) { Index: clang/lib/Sema/AnalysisBasedWarnings.cpp =================================================================== --- clang/lib/Sema/AnalysisBasedWarnings.cpp +++ clang/lib/Sema/AnalysisBasedWarnings.cpp @@ -2197,18 +2197,60 @@ S.Diag(Loc, diag::warn_unsafe_buffer_operation) << MsgParam << Range; } - // FIXME: rename to handleUnsafeVariable - void handleFixableVariable(const VarDecl *Variable, - FixItList &&Fixes) override { + void handleUnsafeVariableGroup(const VarDecl *Variable, + std::map> VarGrpMap, + FixItList &&Fixes) override { S.Diag(Variable->getLocation(), diag::warn_unsafe_buffer_variable) << Variable << (Variable->getType()->isPointerType() ? 0 : 1) << Variable->getSourceRange(); + std::vector VarGroupForVD = VarGrpMap[Variable]; if (!Fixes.empty()) { - unsigned FixItStrategy = 0; // For now we only has 'std::span' strategy - const auto &FD = S.Diag(Variable->getLocation(), - diag::note_unsafe_buffer_variable_fixit); - + unsigned FixItStrategy = 0; // For now we only have 'std::span' strategy + Sema::SemaDiagnosticBuilder const &FD = S.Diag(Variable->getLocation(), + diag::note_unsafe_buffer_variable_fixit_group); + FD << Variable->getName() << FixItStrategy; + std::string AllVars = ""; + if (VarGroupForVD.size() > 1) { + AllVars.append(", and change "); + if (VarGroupForVD.size() == 2) { + if (VarGroupForVD[0] == Variable) { + AllVars.append("'" + VarGroupForVD[1]->getName().str() + "'"); + } else { + AllVars.append("'" + VarGroupForVD[0]->getName().str() + "'"); + } + } else { + bool first = false; + if (VarGroupForVD.size() == 3) { + for (const VarDecl * V : VarGroupForVD) { + if (V == Variable) { + continue; + } + if (!first) { + first = true; + AllVars.append("'" + V->getName().str() + "'" + " and "); + } else { + AllVars.append("'" + V->getName().str() + "'"); + } + } + } else { + for (const VarDecl * V : VarGroupForVD) { + if (V == Variable) { + continue; + } + if (VarGroupForVD.back() != V) { + AllVars.append("'" + V->getName().str() + "'" + ", "); + } else { + AllVars.append("and '" + V->getName().str() + "'"); + } + } + } + } + // FIXME: change std::span to the correct type (array/iterator) + AllVars.append(" to 'std::span' to propagate bounds information between them"); + } + FD << AllVars; + for (const auto &F : Fixes) FD << F; } Index: clang/test/SemaCXX/warn-unsafe-buffer-usage-multi-decl-fixits-test.cpp =================================================================== --- /dev/null +++ clang/test/SemaCXX/warn-unsafe-buffer-usage-multi-decl-fixits-test.cpp @@ -0,0 +1,138 @@ +// RUN: %clang_cc1 -std=c++20 -Wunsafe-buffer-usage -fdiagnostics-parseable-fixits %s 2>&1 | FileCheck %s + +void foo1a() { + int *r = new int[7]; + // CHECK-DAG: fix-it:"{{.*}}":{[[@LINE-1]]:3-[[@LINE-1]]:11}:"std::span r" + // CHECK-DAG: fix-it:"{{.*}}":{[[@LINE-2]]:12-[[@LINE-2]]:12}:"{" + // CHECK-DAG: fix-it:"{{.*}}":{[[@LINE-3]]:22-[[@LINE-3]]:22}:", 7}" + int *p = new int[4]; + // CHECK-DAG: fix-it:"{{.*}}":{[[@LINE-1]]:3-[[@LINE-1]]:11}:"std::span p" + // CHECK-DAG: fix-it:"{{.*}}":{[[@LINE-2]]:12-[[@LINE-2]]:12}:"{" + // CHECK-DAG: fix-it:"{{.*}}":{[[@LINE-3]]:22-[[@LINE-3]]:22}:", 4}" + p = r; + int tmp = p[9]; + int *q; + // CHECK-NOT: fix-it:"{{.*}}":{[[@LINE-1]]:3-[[@LINE-1]]:11}:"std::span q" + q = r; +} + +void foo1b() { + int *r = new int[7]; + // CHECK-DAG: fix-it:"{{.*}}":{[[@LINE-1]]:3-[[@LINE-1]]:11}:"std::span r" + // CHECK-DAG: fix-it:"{{.*}}":{[[@LINE-2]]:12-[[@LINE-2]]:12}:"{" + // CHECK-DAG: fix-it:"{{.*}}":{[[@LINE-3]]:22-[[@LINE-3]]:22}:", 7}" + int *p = new int[4]; + // CHECK-DAG: fix-it:"{{.*}}":{[[@LINE-1]]:3-[[@LINE-1]]:11}:"std::span p" + // CHECK-DAG: fix-it:"{{.*}}":{[[@LINE-2]]:12-[[@LINE-2]]:12}:"{" + // CHECK-DAG: fix-it:"{{.*}}":{[[@LINE-3]]:22-[[@LINE-3]]:22}:", 4}" + p = r; + int tmp = p[9]; + int *q = new int[4]; + // CHECK-DAG: fix-it:"{{.*}}":{[[@LINE-1]]:3-[[@LINE-1]]:11}:"std::span q" + // CHECK-DAG: fix-it:"{{.*}}":{[[@LINE-2]]:12-[[@LINE-2]]:12}:"{" + // CHECK-DAG: fix-it:"{{.*}}":{[[@LINE-3]]:22-[[@LINE-3]]:22}:", 4}" + q = r; + tmp = q[9]; +} + +void foo1c() { + int *r = new int[7]; + // CHECK-DAG: fix-it:"{{.*}}":{[[@LINE-1]]:3-[[@LINE-1]]:11}:"std::span r" + // CHECK-DAG: fix-it:"{{.*}}":{[[@LINE-2]]:12-[[@LINE-2]]:12}:"{" + // CHECK-DAG: fix-it:"{{.*}}":{[[@LINE-3]]:22-[[@LINE-3]]:22}:", 7}" + int *p = new int[4]; + // CHECK-NOT: fix-it:"{{.*}}":{[[@LINE-1]]:3-[[@LINE-1]]:11}:"std::span p" + p = r; + int tmp = r[9]; + int *q; + // CHECK-NOT: fix-it:"{{.*}}":{[[@LINE-1]]:3-[[@LINE-1]]:11}:"std::span q" + q = r; + tmp = q[9]; +} + +void foo2a() { + int *r = new int[7]; + // CHECK-DAG: fix-it:"{{.*}}":{[[@LINE-1]]:3-[[@LINE-1]]:11}:"std::span r" + // CHECK-DAG: fix-it:"{{.*}}":{[[@LINE-2]]:12-[[@LINE-2]]:12}:"{" + // CHECK-DAG: fix-it:"{{.*}}":{[[@LINE-3]]:22-[[@LINE-3]]:22}:", 7}" + int *p = new int[5]; + // CHECK-DAG: fix-it:"{{.*}}":{[[@LINE-1]]:3-[[@LINE-1]]:11}:"std::span p" + // CHECK-DAG: fix-it:"{{.*}}":{[[@LINE-2]]:12-[[@LINE-2]]:12}:"{" + // CHECK-DAG: fix-it:"{{.*}}":{[[@LINE-3]]:22-[[@LINE-3]]:22}:", 5}" + int *q = new int[4]; + // CHECK-DAG: fix-it:"{{.*}}":{[[@LINE-1]]:3-[[@LINE-1]]:11}:"std::span q" + // CHECK-DAG: fix-it:"{{.*}}":{[[@LINE-2]]:12-[[@LINE-2]]:12}:"{" + // CHECK-DAG: fix-it:"{{.*}}":{[[@LINE-3]]:22-[[@LINE-3]]:22}:", 4}" + p = q; + int tmp = p[8]; + q = r; +} + +void foo2b() { + int *r = new int[7]; + // CHECK-DAG: fix-it:"{{.*}}":{[[@LINE-1]]:3-[[@LINE-1]]:11}:"std::span r" + // CHECK-DAG: fix-it:"{{.*}}":{[[@LINE-2]]:12-[[@LINE-2]]:12}:"{" + // CHECK-DAG: fix-it:"{{.*}}":{[[@LINE-3]]:22-[[@LINE-3]]:22}:", 7}" + int *p = new int[5]; + // CHECK-NOT: fix-it:"{{.*}}":{[[@LINE-1]]:3-[[@LINE-1]]:11}:"std::span p" + // CHECK-NOT: fix-it:"{{.*}}":{[[@LINE-2]]:12-[[@LINE-2]]:12}:"{" + // CHECK-NOT: fix-it:"{{.*}}":{[[@LINE-3]]:22-[[@LINE-3]]:22}:", 5}" + int *q = new int[4]; + // CHECK-DAG: fix-it:"{{.*}}":{[[@LINE-1]]:3-[[@LINE-1]]:11}:"std::span q" + // CHECK-DAG: fix-it:"{{.*}}":{[[@LINE-2]]:12-[[@LINE-2]]:12}:"{" + // CHECK-DAG: fix-it:"{{.*}}":{[[@LINE-3]]:22-[[@LINE-3]]:22}:", 4}" + p = q; + int tmp = q[8]; + q = r; +} + +void foo2c() { + int *r = new int[7]; + // CHECK-DAG: fix-it:"{{.*}}":{[[@LINE-1]]:3-[[@LINE-1]]:11}:"std::span r" + // CHECK-DAG: fix-it:"{{.*}}":{[[@LINE-2]]:12-[[@LINE-2]]:12}:"{" + // CHECK-DAG: fix-it:"{{.*}}":{[[@LINE-3]]:22-[[@LINE-3]]:22}:", 7}" + int *p = new int[5]; + // CHECK-DAG: fix-it:"{{.*}}":{[[@LINE-1]]:3-[[@LINE-1]]:11}:"std::span p" + // CHECK-DAG: fix-it:"{{.*}}":{[[@LINE-2]]:12-[[@LINE-2]]:12}:"{" + // CHECK-DAG: fix-it:"{{.*}}":{[[@LINE-3]]:22-[[@LINE-3]]:22}:", 5}" + int *q = new int[4]; + // CHECK-DAG: fix-it:"{{.*}}":{[[@LINE-1]]:3-[[@LINE-1]]:11}:"std::span q" + // CHECK-DAG: fix-it:"{{.*}}":{[[@LINE-2]]:12-[[@LINE-2]]:12}:"{" + // CHECK-DAG: fix-it:"{{.*}}":{[[@LINE-3]]:22-[[@LINE-3]]:22}:", 4}" + p = q; + int tmp = p[8]; + q = r; + tmp = q[8]; +} + +void foo3a() { + int *r = new int[7]; + // CHECK-NOT: fix-it:"{{.*}}":{[[@LINE-1]]:3-[[@LINE-1]]:11}:"std::span r" + int *p = new int[5]; + // CHECK-DAG: fix-it:"{{.*}}":{[[@LINE-1]]:3-[[@LINE-1]]:11}:"std::span p" + // CHECK-DAG: fix-it:"{{.*}}":{[[@LINE-2]]:12-[[@LINE-2]]:12}:"{" + // CHECK-DAG: fix-it:"{{.*}}":{[[@LINE-3]]:22-[[@LINE-3]]:22}:", 5}" + int *q = new int[4]; + // CHECK-NOT: fix-it:"{{.*}}":{[[@LINE-1]]:3-[[@LINE-1]]:11}:"std::span q" + q = p; + int tmp = p[8]; + q = r; +} + +void foo3b() { + int *r = new int[10]; + // CHECK-DAG: fix-it:"{{.*}}":{[[@LINE-1]]:3-[[@LINE-1]]:11}:"std::span r" + // CHECK-DAG: fix-it:"{{.*}}":{[[@LINE-2]]:12-[[@LINE-2]]:12}:"{" + // CHECK-DAG: fix-it:"{{.*}}":{[[@LINE-3]]:23-[[@LINE-3]]:23}:", 10}" + int *p = new int[10]; + // CHECK-DAG: fix-it:"{{.*}}":{[[@LINE-1]]:3-[[@LINE-1]]:11}:"std::span p" + // CHECK-DAG: fix-it:"{{.*}}":{[[@LINE-2]]:12-[[@LINE-2]]:12}:"{" + // CHECK-DAG: fix-it:"{{.*}}":{[[@LINE-3]]:23-[[@LINE-3]]:23}:", 10}" + int *q = new int[10]; + // CHECK-DAG: fix-it:"{{.*}}":{[[@LINE-1]]:3-[[@LINE-1]]:11}:"std::span q" + // CHECK-DAG: fix-it:"{{.*}}":{[[@LINE-2]]:12-[[@LINE-2]]:12}:"{" + // CHECK-DAG: fix-it:"{{.*}}":{[[@LINE-3]]:23-[[@LINE-3]]:23}:", 10}" + q = p; + int tmp = q[8]; + q = r; +} Index: clang/test/SemaCXX/warn-unsafe-buffer-usage-multi-decl-warnings.cpp =================================================================== --- /dev/null +++ clang/test/SemaCXX/warn-unsafe-buffer-usage-multi-decl-warnings.cpp @@ -0,0 +1,346 @@ +// RUN: %clang_cc1 -std=c++20 -Wunsafe-buffer-usage -verify %s + +namespace std { + class type_info { }; +} + +void local_assign_both_span() { + int tmp; + int* p = new int[10]; // expected-warning{{'p' is an unsafe pointer used for buffer access}} expected-note-re{{{{^change type of 'p' to 'std::span' to preserve bounds information, and change 'q' to 'std::span' to propagate bounds information between them$}}}} + tmp = p[4]; // expected-note{{used in buffer access here}} + + int* q = new int[10]; // expected-warning{{'q' is an unsafe pointer used for buffer access}} expected-note-re{{{{^change type of 'q' to 'std::span' to preserve bounds information, and change 'p' to 'std::span' to propagate bounds information between them$}}}} + tmp = q[4]; // expected-note{{used in buffer access here}} + + q = p; +} + +void local_assign_rhs_span() { + int tmp; + int* p = new int[10]; + int* q = new int[10]; // expected-warning{{'q' is an unsafe pointer used for buffer access}} expected-note-re{{{{^change type of 'q' to 'std::span' to preserve bounds information$}}}} + tmp = q[4]; // expected-note{{used in buffer access here}} + p = q; +} + +void local_assign_no_span() { + int tmp; + int* p = new int[10]; + int* q = new int[10]; + p = q; +} + +void local_assign_lhs_span() { + int tmp; + int* p = new int[10]; // expected-warning{{'p' is an unsafe pointer used for buffer access}} expected-note-re{{{{^change type of 'p' to 'std::span' to preserve bounds information, and change 'q' to 'std::span' to propagate bounds information between them$}}}} + tmp = p[4]; // expected-note{{used in buffer access here}} + int* q = new int[10]; + + p = q; +} + + +void lhs_span_multi_assign() { + int *a = new int[2]; + int *b = a; + int *c = b; + int *d = c; // expected-warning{{'d' is an unsafe pointer used for buffer access}} expected-note-re{{{{^change type of 'd' to 'std::span' to preserve bounds information$}}}} + int tmp = d[2]; // expected-note{{used in buffer access here}} +} + +void rhs_span() { + int *x = new int[3]; + int *y; // expected-warning{{'y' is an unsafe pointer used for buffer access}} expected-note-re{{{{^change type of 'y' to 'std::span' to preserve bounds information$}}}} + y[5] = 10; // expected-note{{used in buffer access here}} + + x = y; +} + +void rhs_span1() { + int *q = new int[12]; + int *p = q; // expected-warning{{'p' is an unsafe pointer used for buffer access}} expected-note-re{{{{^change type of 'p' to 'std::span' to preserve bounds information$}}}} + p[5] = 10; // expected-note{{used in buffer access here}} + int *r = q; // expected-warning{{'r' is an unsafe pointer used for buffer access}} expected-note-re{{{{^change type of 'r' to 'std::span' to preserve bounds information$}}}} + r[10] = 5; // expected-note{{used in buffer access here}} +} + +void rhs_span2() { + int *q = new int[6]; + int *p = q; // expected-warning{{'p' is an unsafe pointer used for buffer access}} expected-note-re{{{{^change type of 'p' to 'std::span' to preserve bounds information$}}}} + p[5] = 10; // expected-note{{used in buffer access here}} + int *r = q; +} + +void test_grouping() { + int *z = new int[8]; + int tmp; + int *y = new int[10]; // expected-warning{{'y' is an unsafe pointer used for buffer access}} expected-note-re{{{{^change type of 'y' to 'std::span' to preserve bounds information$}}}} + tmp = y[5]; // expected-note{{used in buffer access here}} + + int *x = new int[10]; + x = y; + + int *w = z; +} + +void test_grouping1() { + int tmp; + int *y = new int[10]; // expected-warning{{'y' is an unsafe pointer used for buffer access}} expected-note-re{{{{^change type of 'y' to 'std::span' to preserve bounds information$}}}} + tmp = y[5]; // expected-note{{used in buffer access here}} + int *x = new int[10]; + x = y; + + int *w = new int[10]; // expected-warning{{'w' is an unsafe pointer used for buffer access}} expected-note-re{{{{^change type of 'w' to 'std::span' to preserve bounds information$}}}} + tmp = w[5]; // expected-note{{used in buffer access here}} + int *z = new int[10]; + z = w; +} + +void foo1a() { + int *r = new int[7]; + int *p = new int[4]; // expected-warning{{'p' is an unsafe pointer used for buffer access}} expected-note-re{{{{^change type of 'p' to 'std::span' to preserve bounds information, and change 'r' to 'std::span' to propagate bounds information between them$}}}} + p = r; + int tmp = p[9]; // expected-note{{used in buffer access here}} + int *q; + q = r; +} + +void foo1b() { + int *r = new int[7]; + int *p = new int[4]; // expected-warning{{'p' is an unsafe pointer used for buffer access}} expected-note-re{{{{^change type of 'p' to 'std::span' to preserve bounds information, and change 'r' and 'q' to 'std::span' to propagate bounds information between them$}}}} + p = r; + int tmp = p[9]; // expected-note{{used in buffer access here}} + int *q; // expected-warning{{'q' is an unsafe pointer used for buffer access}} expected-note-re{{{{^change type of 'q' to 'std::span' to preserve bounds information, and change 'p' and 'r' to 'std::span' to propagate bounds information between them$}}}} + q = r; + tmp = q[9]; // expected-note{{used in buffer access here}} +} + +void foo1c() { + int *r = new int[7]; // expected-warning{{'r' is an unsafe pointer used for buffer access}} expected-note-re{{{{^change type of 'r' to 'std::span' to preserve bounds information, and change 'q' to 'std::span' to propagate bounds information between them$}}}} + int *p = new int[4]; + p = r; + int tmp = r[9]; // expected-note{{used in buffer access here}} + int *q; // expected-warning{{'q' is an unsafe pointer used for buffer access}} expected-note-re{{{{^change type of 'q' to 'std::span' to preserve bounds information, and change 'r' to 'std::span' to propagate bounds information between them$}}}} + q = r; + tmp = q[9]; // expected-note{{used in buffer access here}} +} + +void foo2a() { + int *r = new int[7]; + int *p = new int[5]; // expected-warning{{'p' is an unsafe pointer used for buffer access}} expected-note-re{{{{^change type of 'p' to 'std::span' to preserve bounds information, and change 'r' and 'q' to 'std::span' to propagate bounds information between them$}}}} + int *q = new int[4]; + p = q; + int tmp = p[8]; // expected-note{{used in buffer access here}} + q = r; +} + +void foo2b() { + int *r = new int[7]; + int *p = new int[5]; + int *q = new int[4]; // expected-warning{{'q' is an unsafe pointer used for buffer access}} expected-note-re{{{{^change type of 'q' to 'std::span' to preserve bounds information, and change 'r' to 'std::span' to propagate bounds information between them$}}}} + p = q; + int tmp = q[8]; // expected-note{{used in buffer access here}} + q = r; +} + +void foo2c() { + int *r = new int[7]; + int *p = new int[5]; // expected-warning{{'p' is an unsafe pointer used for buffer access}} expected-note-re{{{{^change type of 'p' to 'std::span' to preserve bounds information, and change 'r' and 'q' to 'std::span' to propagate bounds information between them$}}}} + int *q = new int[4]; // expected-warning{{'q' is an unsafe pointer used for buffer access}} expected-note-re{{{{^change type of 'q' to 'std::span' to preserve bounds information, and change 'p' and 'r' to 'std::span' to propagate bounds information between them$}}}} + p = q; + int tmp = p[8]; // expected-note{{used in buffer access here}} + q = r; + tmp = q[8]; // expected-note{{used in buffer access here}} +} + +void foo3a() { + int *r = new int[7]; + int *p = new int[5]; // expected-warning{{'p' is an unsafe pointer used for buffer access}} expected-note-re{{{{^change type of 'p' to 'std::span' to preserve bounds information$}}}} + int *q = new int[4]; + q = p; + int tmp = p[8]; // expected-note{{used in buffer access here}} + q = r; +} + +void foo3b() { + int *r = new int[7]; + int *p = new int[5]; + int *q = new int[4]; // expected-warning{{'q' is an unsafe pointer used for buffer access}} //expected-note-re{{{{^change type of 'q' to 'std::span' to preserve bounds information, and change 'r' and 'p' to 'std::span' to propagate bounds information between them$}}}} + q = p; + int tmp = q[8]; // expected-note{{used in buffer access here}} + q = r; +} + +void test_crash() { + int *r = new int[8]; + int *q = r; + int *p; // expected-warning{{'p' is an unsafe pointer used for buffer access}} expected-note-re{{{{^change type of 'p' to 'std::span' to preserve bounds information, and change 'q' to 'std::span' to propagate bounds information between them$}}}} + p = q; + int tmp = p[9]; // expected-note{{used in buffer access here}} +} + +void foo_uuc() { + int *ptr; + int *local; // expected-warning{{'local' is an unsafe pointer used for buffer access}} + local = ptr; + local++; // expected-note{{used in pointer arithmetic here}} + + (local = ptr) += 5; // expected-warning{{unsafe pointer arithmetic}} +} + +void check_rhs_fix() { + int *r = new int[8]; // expected-warning{{'r' is an unsafe pointer used for buffer access}} // expected-note-re{{{{^change type of 'r' to 'std::span' to preserve bounds information, and change 'x' to 'std::span' to propagate bounds information between them$}}}} + int *x; + r[7] = 9; // expected-note{{used in buffer access here}} + r = x; +} + +void check_rhs_nofix() { + int *r = new int[8]; // expected-warning{{'r' is an unsafe pointer used for buffer access}} + int *x; // expected-warning{{'x' is an unsafe pointer used for buffer access}} + r[7] = 9; // expected-note{{used in buffer access here}} + r = x; + x++; // expected-note{{used in pointer arithmetic here}} +} + +void check_rhs_nofix_order() { + int *r = new int[8]; // expected-warning{{'r' is an unsafe pointer used for buffer access}} + int *x; // expected-warning{{'x' is an unsafe pointer used for buffer access}} + x++; // expected-note{{used in pointer arithmetic here}} + r[7] = 9; // expected-note{{used in buffer access here}} + r = x; +} + +void check_rhs_nofix_order1() { + int *r = new int[8]; // expected-warning{{'r' is an unsafe pointer used for buffer access}} + r[7] = 9; // expected-note{{used in buffer access here}} + int *x; // expected-warning{{'x' is an unsafe pointer used for buffer access}} + x++; // expected-note{{used in pointer arithmetic here}} + r = x; +} + +void check_rhs_nofix_order2() { + int *x; // expected-warning{{'x' is an unsafe pointer used for buffer access}} + int *r = new int[8]; // expected-warning{{'r' is an unsafe pointer used for buffer access}} + r[7] = 9; // expected-note{{used in buffer access here}} + x++; // expected-note{{used in pointer arithmetic here}} + r = x; +} + +void check_rhs_nofix_order3() { + int *x; // expected-warning{{'x' is an unsafe pointer used for buffer access}} + int *r = new int[8]; // expected-warning{{'r' is an unsafe pointer used for buffer access}} + r = x; + r[7] = 9; // expected-note{{used in buffer access here}} + x++; // expected-note{{used in pointer arithmetic here}} +} + +void check_rhs_nofix_order4() { + int *x; // expected-warning{{'x' is an unsafe pointer used for buffer access}} + int *r = new int[8]; // expected-warning{{'r' is an unsafe pointer used for buffer access}} + r[7] = 9; // expected-note{{used in buffer access here}} + r = x; + x++; // expected-note{{used in pointer arithmetic here}} +} + +void no_unhandled_lhs() { + int *r = new int[8]; // expected-warning{{'r' is an unsafe pointer used for buffer access}} // expected-note-re{{{{^change type of 'r' to 'std::span' to preserve bounds information, and change 'x' to 'std::span' to propagate bounds information between them$}}}} + r[7] = 9; // expected-note{{used in buffer access here}} + int *x; + r = x; +} + +const std::type_info unhandled_lhs() { + int *r = new int[8]; // expected-warning{{'r' is an unsafe pointer used for buffer access}} + r[7] = 9; // expected-note{{used in buffer access here}} + int *x; + r = x; + return typeid(*r); +} + +const std::type_info unhandled_rhs() { + int *r = new int[8]; // expected-warning{{'r' is an unsafe pointer used for buffer access}} + r[7] = 9; // expected-note{{used in buffer access here}} + int *x; + r = x; + return typeid(*x); +} + +void test_negative_index() { + int *x = new int[4]; // expected-warning{{'x' is an unsafe pointer used for buffer access}} + int *p; // expected-warning{{'p' is an unsafe pointer used for buffer access}} + p = &x[1]; // expected-note{{used in buffer access here}} + p[-1] = 9; // expected-note{{used in buffer access here}} +} + +void test_unfixable() { + int *r = new int[8]; // expected-warning{{'r' is an unsafe pointer used for buffer access}} + int *x; // expected-warning{{'x' is an unsafe pointer used for buffer access}} + x[7] = 9; // expected-note{{used in buffer access here}} + r = x; + r++; // expected-note{{used in pointer arithmetic here}} +} + +void test_cyclic_deps() { + int *r = new int[10]; // expected-warning{{'r' is an unsafe pointer used for buffer access}} expected-note-re{{{{^change type of 'r' to 'std::span' to preserve bounds information, and change 'q' and 'p' to 'std::span' to propagate bounds information between them$}}}} + int *q; + q = r; + int *p; + p = q; + r[3] = 9; // expected-note{{used in buffer access here}} + r = p; +} + +void test_cyclic_deps_a() { + int *r = new int[10]; // expected-warning{{'r' is an unsafe pointer used for buffer access}} + int *q; + q = r; + int *p; // expected-warning{{'p' is an unsafe pointer used for buffer access}} + p = q; + r[3] = 9; // expected-note{{used in buffer access here}} + r = p; + p++; // expected-note{{used in pointer arithmetic here}} +} + +void test_cyclic_deps1() { + int *r = new int[10]; + int *q; + q = r; + int *p; // expected-warning{{'p' is an unsafe pointer used for buffer access}} expected-note-re{{{{^change type of 'p' to 'std::span' to preserve bounds information, and change 'r' and 'q' to 'std::span' to propagate bounds information between them$}}}} + p = q; + p[3] = 9; // expected-note{{used in buffer access here}} + r = p; +} + +void test_cyclic_deps2() { + int *r = new int[10]; + int *q; // expected-warning{{'q' is an unsafe pointer used for buffer access}} expected-note-re{{{{^change type of 'q' to 'std::span' to preserve bounds information, and change 'r' and 'p' to 'std::span' to propagate bounds information between them$}}}} + q = r; + int *p; + p = q; + q[3] = 9; // expected-note{{used in buffer access here}} + r = p; +} + +void test_cyclic_deps3() { + int *r = new int[10]; + int *q; // expected-warning{{'q' is an unsafe pointer used for buffer access}} expected-note-re{{{{^change type of 'q' to 'std::span' to preserve bounds information, and change 'r' and 'p' to 'std::span' to propagate bounds information between them$}}}} + q = r; + int *p; // expected-warning{{'p' is an unsafe pointer used for buffer access}} expected-note-re{{{{^change type of 'p' to 'std::span' to preserve bounds information, and change 'q' and 'r' to 'std::span' to propagate bounds information between them$}}}} + p = q; + q[3] = 9; // expected-note{{used in buffer access here}} + p[4] = 7; // expected-note{{used in buffer access here}} + r = p; +} + +void test_cyclic_deps4() { + int *r = new int[10]; // expected-warning{{'r' is an unsafe pointer used for buffer access}} expected-note-re{{{{^change type of 'r' to 'std::span' to preserve bounds information, and change 'q' and 'p' to 'std::span' to propagate bounds information between them$}}}} + int *q; // expected-warning{{'q' is an unsafe pointer used for buffer access}} expected-note-re{{{{^change type of 'q' to 'std::span' to preserve bounds information, and change 'r' and 'p' to 'std::span' to propagate bounds information between them$}}}} + q = r; + int *p; // expected-warning{{'p' is an unsafe pointer used for buffer access}} expected-note-re{{{{^change type of 'p' to 'std::span' to preserve bounds information, and change 'r' and 'q' to 'std::span' to propagate bounds information between them$}}}} + p = q; + q[3] = 9; // expected-note{{used in buffer access here}} + p[4] = 7; // expected-note{{used in buffer access here}} + r[1] = 5; // expected-note{{used in buffer access here}} + r = p; +}