Index: clang/include/clang/Analysis/Analyses/UnsafeBufferUsage.h =================================================================== --- clang/include/clang/Analysis/Analyses/UnsafeBufferUsage.h +++ clang/include/clang/Analysis/Analyses/UnsafeBufferUsage.h @@ -39,6 +39,9 @@ /// Invoked when a fix is suggested against a variable. virtual void handleFixableVariable(const VarDecl *Variable, FixItList &&List) = 0; + virtual void handleFixableVariableGroupNew(const VarDecl *Variable, + std::vector> VariableGroups, + 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_group_fixit : Note< + "Change types of %0 together to preserve type safety">; 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; @@ -196,6 +197,8 @@ // track uses of variables (aka DeclRefExprs). using DeclUseList = SmallVector; +using ImplicationsList = std::vector; + // Convenience typedef. using FixItList = SmallVector; @@ -270,6 +273,10 @@ virtual std::optional getFixits(const Strategy &) const { return std::nullopt; } + + virtual std::optional getStrategyImplications() const { + return std::nullopt; + } }; using FixableGadgetList = std::vector>; @@ -536,6 +543,66 @@ } }; +/// 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)) { +// assert(PA != nullptr && "Expecting a non-null matching result"); + } + + static bool classof(const Gadget *G) { + return G->getKind() == Kind::PointerAssignment; + } + + static Matcher matcher() { + auto PtrAtRight = eachOf(allOf(hasOperatorName("="), + hasRHS(ignoringParenImpCasts(declRefExpr(hasPointerType()). + bind(PointerAssignRHSTag)))), + declRefExpr(expr()).bind(PointerAssignRHSTag)); + auto PtrAtLeft = eachOf(allOf(hasOperatorName("="), + hasLHS(declRefExpr(hasPointerType()).bind(PointerAssignLHSTag))), + declRefExpr(hasPointerType(),to(varDecl())) + .bind(PointerAssignLHSTag)); + + auto DeclPtrAtLeft = declStmt(hasSingleDecl(varDecl(hasType(pointerType()), + hasInitializer(expr() + .bind(PointerAssignRHSTag))) + .bind(PointerAssignLHSTag))); + + 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 ImplicationsList{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 +994,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 +1010,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 +1024,7 @@ } struct FixableGadgetSets { - std::map>> byVar; + std::map> byVar; }; static FixableGadgetSets @@ -968,7 +1035,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 +1124,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 FixItList{}; + 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; @@ -1645,6 +1732,106 @@ return FixItsForVariable; } +static std::map +getFixItsGroup(FixableGadgetSets &FixablesForUnsafeVars, const Strategy &S, + const DeclUseTracker &Tracker, const ASTContext &Ctx, Sema &SA, + UnsafeBufferUsageHandler &Handler, + std::vector> VariableGroups) { + const SourceManager &SM = Ctx.getSourceManager(); + std::map FixItsForVariable; + for (const auto &[VD, Fixables] : FixablesForUnsafeVars.byVar) { + const Strategy::Kind ReplacementTypeForVD = S.lookup(VD); + FixItsForVariable[VD] = + fixVariable(VD, ReplacementTypeForVD, Tracker, Ctx, SA, Handler); + // If we fail to produce Fix-It for the declaration we have to skip the + // variable entirely. + if (FixItsForVariable[VD].empty()) { + FixItsForVariable.erase(VD); + continue; + } + bool ImpossibleToFix = false; + llvm::SmallVector FixItsForVD; + for (const auto &F : Fixables) { + std::optional Fixits = F->getFixits(S); + if (!Fixits) { + ImpossibleToFix = true; + break; + } else { + const FixItList CorrectFixes = Fixits.value(); + FixItsForVD.insert(FixItsForVD.end(), CorrectFixes.begin(), + CorrectFixes.end()); + } + } + + if (ImpossibleToFix) { + FixItsForVariable.erase(VD); + continue; + } + + FixItsForVariable[VD].insert(FixItsForVariable[VD].end(), + FixItsForVD.begin(), FixItsForVD.end()); + + // Fix-it shall not overlap with macros or/and templates: + if (overlapWithMacro(FixItsForVariable[VD]) || + clang::internal::anyConflict(FixItsForVariable[VD], + Ctx.getSourceManager())) { + FixItsForVariable.erase(VD); + continue; + } + + assert(FixItsForVariable[VD].front().RemoveRange.isValid()); + const FileID FileOfVarDeclFixIt = + SM.getFileID(FixItsForVariable[VD].front().RemoveRange.getBegin()); + + const FileEntry *FEOfVarDeclFixIt = + SM.getFileEntryForID(FileOfVarDeclFixIt); + if (!FEOfVarDeclFixIt) { + FixItsForVariable.erase(VD); + continue; + } + std::optional FileContent = + SM.getBufferOrNone(FileOfVarDeclFixIt); + if (!FileContent) { + FixItsForVariable.erase(VD); + continue; + } + + tooling::IncludeStyle InclStyle; // TODO set IncludeStyle + tooling::HeaderIncludes HeaderIncls(FEOfVarDeclFixIt->getName(), + FileContent.value().getBuffer(), + InclStyle); + std::optional Include = HeaderIncls.insert( + getHeaderFilenameForStrategyKind(ReplacementTypeForVD), true, + clang::tooling::IncludeDirective::Include); + if (Include.has_value()) { + SourceLocation HeaderInsertLoc = + SM.getComposedLoc(FileOfVarDeclFixIt, Include->getOffset()); + FixItsForVariable[VD].push_back(FixItHint::CreateInsertion( + HeaderInsertLoc, Include->getReplacementText())); + } + + std::vector VarGroupForVD; + for (std::vector VG : VariableGroups) { + if (std::find(VG.begin(), VG.end(), VD) != VG.end()) { + VarGroupForVD = VG; + break; + } + } + for (const VarDecl * Var : VarGroupForVD) { + if (Var->getName() == VD->getName()) { + continue; + } + FixItList GroupFix = fixVariable(Var, ReplacementTypeForVD, Tracker, + Var->getASTContext(), SA, Handler); + for (auto Fix : GroupFix) { + FixItsForVariable[VD].push_back(Fix); + } + } + } + return FixItsForVariable; +} + + static Strategy getNaiveStrategy(const llvm::SmallVectorImpl &UnsafeVars) { Strategy S; @@ -1661,16 +1848,14 @@ WarningGadgetSets UnsafeOps; FixableGadgetSets FixablesForUnsafeVars; - DeclUseTracker Tracker; - { - 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)); + FixablesForUnsafeVars = groupFixablesByVar(std::move(FixableGadgets)); std::map FixItsForVariable; + std::map FixItsForVariableGroup; + std::vector> VariableGroups{}; if (EmitFixits) { // Filter out non-local vars and vars with unclaimed DeclRefExpr-s. @@ -1687,9 +1872,78 @@ for (const auto &[VD, ignore] : FixablesForUnsafeVars.byVar) UnsafeVars.push_back(VD); + // Fixpoint iteration for pointer assignments + using DepMapTy = DenseMap>; + DepMapTy DependenciesMap{}; + DepMapTy DirectedGraph{}; + + for (auto it : FixablesForUnsafeVars.byVar) { + for (const FixableGadget *fixable : it.second) { + std::optional ImplList = + fixable->getStrategyImplications(); + if (ImplList) { + ImplicationsList Deps = ImplList.value(); + int size = Deps.size(); + for (int i = 1; i < size; i++) { + DirectedGraph[Deps[0]].insert(Deps[i]); + } + } + } + } + + 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 = DirectedGraph[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); + } + } + } + VariableGroups.push_back(VarGroup); + } + } + Strategy NaiveStrategy = getNaiveStrategy(UnsafeVars); - FixItsForVariable = getFixIts(FixablesForUnsafeVars, NaiveStrategy, Tracker, - D->getASTContext(), S, Handler); +// FixItsForVariable = getFixIts(FixablesForUnsafeVars, NaiveStrategy, Tracker, +// D->getASTContext(), S, Handler); + FixItsForVariableGroup = + getFixItsGroup(FixablesForUnsafeVars, NaiveStrategy, Tracker, + D->getASTContext(), S, Handler, VariableGroups); // FIXME Detect overlapping FixIts. } @@ -1699,11 +1953,17 @@ } for (const auto &[VD, WarningGadgets] : UnsafeOps.byVar) { - auto FixItsIt = - EmitFixits ? FixItsForVariable.find(VD) : FixItsForVariable.end(); - Handler.handleFixableVariable(VD, FixItsIt != FixItsForVariable.end() - ? std::move(FixItsIt->second) - : FixItList{}); +// auto FixItsIt = +// EmitFixits ? FixItsForVariable.find(VD) : FixItsForVariable.end(); +// Handler.handleFixableVariable(VD, FixItsIt != FixItsForVariable.end() +// ? std::move(FixItsIt->second) +// : FixItList{}); + auto FixItsIt = FixItsForVariableGroup.find(VD); + Handler.handleFixableVariableGroupNew(VD, VariableGroups, FixItsIt != + FixItsForVariableGroup.end() + ? std::move(FixItsIt->second) + : FixItList{}); + for (const auto &G : WarningGadgets) { Handler.handleUnsafeOperation(G->getBaseStmt(), /*IsRelatedToDecl=*/true); } Index: clang/lib/Sema/AnalysisBasedWarnings.cpp =================================================================== --- clang/lib/Sema/AnalysisBasedWarnings.cpp +++ clang/lib/Sema/AnalysisBasedWarnings.cpp @@ -2214,6 +2214,52 @@ } } + void handleFixableVariableGroupNew(const VarDecl *Variable, + std::vector> VariableGroups, + FixItList &&Fixes) override { + S.Diag(Variable->getLocation(), diag::warn_unsafe_buffer_variable) + << Variable << (Variable->getType()->isPointerType() ? 0 : 1) + << Variable->getSourceRange(); + + std::vector VarGroupForVD; + for (std::vector VG : VariableGroups) { + if (std::find(VG.begin(), VG.end(), Variable) != VG.end()) { + VarGroupForVD = VG; + break; + } + } + + if (VarGroupForVD.size() > 1) { + Sema::SemaDiagnosticBuilder const &FD = S.Diag(Variable->getLocation(), + diag::note_unsafe_buffer_variable_group_fixit); + std::string AllVars = ""; + if (VarGroupForVD.size() == 2) { + AllVars.append(VarGroupForVD[0]->getName().str() + " and " + + VarGroupForVD[1]->getName().str()); + } else { + for (const VarDecl * V : VarGroupForVD) { + if (VarGroupForVD.back() != V) { + AllVars.append(V->getName().str() + ", "); + } else { + AllVars.append("and " + V->getName().str()); + } + } + } + FD << AllVars; + } + + if (!Fixes.empty()) { + 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); + + FD << Variable->getName() << FixItStrategy; + for (const auto &F : Fixes) + FD << F; + } + } + + bool isSafeBufferOptOut(const SourceLocation &Loc) const override { return S.PP.isSafeBufferOptOut(S.getSourceManager(), Loc); } 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,186 @@ +// RUN: %clang_cc1 -std=c++20 -Wunsafe-buffer-usage -verify %s + + +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{{change type of 'p' to 'std::span' to preserve bounds information}} // expected-note{{Change types of p and q together to preserve type safety}} + 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{{change type of 'q' to 'std::span' to preserve bounds information}} // expected-note{{Change types of p and q together to preserve type safety}} + 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{{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{{change type of 'p' to 'std::span' to preserve bounds information}} // expected-note{{Change types of p and q together to preserve type safety}} + 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{{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{{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{{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{{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{{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{{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{{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{{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{{change type of 'p' to 'std::span' to preserve bounds information}} // expected-note{{Change types of p and r together to preserve type safety}} + 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{{change type of 'p' to 'std::span' to preserve bounds information}} // expected-note{{Change types of p, r, and q together to preserve type safety}} + 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{{change type of 'q' to 'std::span' to preserve bounds information}} // expected-note{{Change types of p, r, and q together to preserve type safety}} + 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{{change type of 'r' to 'std::span' to preserve bounds information}} // expected-note{{Change types of r and q together to preserve type safety}} + 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{{change type of 'q' to 'std::span' to preserve bounds information}} // expected-note{{Change types of r and q together to preserve type safety}} + 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{{change type of 'p' to 'std::span' to preserve bounds information}} // expected-note{{Change types of p, r, and q together to preserve type safety}} + 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{{change type of 'q' to 'std::span' to preserve bounds information}} // expected-note{{Change types of q and r together to preserve type safety}} + 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{{change type of 'p' to 'std::span' to preserve bounds information}} // expected-note{{Change types of p, r, and q together to preserve type safety}} + int *q = new int[4]; // expected-warning{{'q' is an unsafe pointer used for buffer access}} // expected-note{{change type of 'q' to 'std::span' to preserve bounds information}} // expected-note{{Change types of p, r, and q together to preserve type safety}} + 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{{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{{change type of 'q' to 'std::span' to preserve bounds information}} // expected-note{{Change types of q, r, and p together to preserve type safety}} + 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{{change type of 'p' to 'std::span' to preserve bounds information}} // expected-note{{Change types of p and q together to preserve type safety}} + 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}} // expected-note{{Change types of local and ptr together to preserve type safety}} + local = ptr; + local++; // expected-note{{used in pointer arithmetic here}} + + (local = ptr) += 5; // expected-warning{{unsafe pointer arithmetic}} +}