[clang] 171dfc5 - [-Wunsafe-buffer-usage] Group variables associated by pointer assignments
Rashmi Mudduluru via cfe-commits
cfe-commits at lists.llvm.org
Thu May 25 11:32:24 PDT 2023
Author: Rashmi Mudduluru
Date: 2023-05-25T11:31:27-07:00
New Revision: 171dfc5462a23b7e8ace31f4d9206b972b38ffbc
URL: https://github.com/llvm/llvm-project/commit/171dfc5462a23b7e8ace31f4d9206b972b38ffbc
DIFF: https://github.com/llvm/llvm-project/commit/171dfc5462a23b7e8ace31f4d9206b972b38ffbc.diff
LOG: [-Wunsafe-buffer-usage] Group variables associated by pointer assignments
Differential Revision: https://reviews.llvm.org/D145739
Added:
clang/test/SemaCXX/warn-unsafe-buffer-usage-multi-decl-fixits-test.cpp
clang/test/SemaCXX/warn-unsafe-buffer-usage-multi-decl-uuc-fixits.cpp
clang/test/SemaCXX/warn-unsafe-buffer-usage-multi-decl-uuc.cpp
clang/test/SemaCXX/warn-unsafe-buffer-usage-multi-decl-warnings.cpp
Modified:
clang/include/clang/Analysis/Analyses/UnsafeBufferUsage.h
clang/include/clang/Analysis/Analyses/UnsafeBufferUsageGadgets.def
clang/include/clang/Basic/DiagnosticSemaKinds.td
clang/lib/Analysis/UnsafeBufferUsage.cpp
clang/lib/Sema/AnalysisBasedWarnings.cpp
Removed:
################################################################################
diff --git a/clang/include/clang/Analysis/Analyses/UnsafeBufferUsage.h b/clang/include/clang/Analysis/Analyses/UnsafeBufferUsage.h
index 10635e8f3a29f..617bc7c77c565 100644
--- a/clang/include/clang/Analysis/Analyses/UnsafeBufferUsage.h
+++ b/clang/include/clang/Analysis/Analyses/UnsafeBufferUsage.h
@@ -19,6 +19,8 @@
namespace clang {
+using DefMapTy = llvm::DenseMap<const VarDecl *, std::vector<const VarDecl *>>;
+
/// The interface that lets the caller handle unsafe buffer usage analysis
/// results by overriding this class's handle... methods.
class UnsafeBufferUsageHandler {
@@ -34,9 +36,12 @@ class UnsafeBufferUsageHandler {
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,
+ const DefMapTy &VarGrpMap,
+ FixItList &&Fixes) = 0;
/// Returns a reference to the `Preprocessor`:
virtual bool isSafeBufferOptOut(const SourceLocation &Loc) const = 0;
diff --git a/clang/include/clang/Analysis/Analyses/UnsafeBufferUsageGadgets.def b/clang/include/clang/Analysis/Analyses/UnsafeBufferUsageGadgets.def
index a112b6d105025..57d9dcc5bdcb7 100644
--- a/clang/include/clang/Analysis/Analyses/UnsafeBufferUsageGadgets.def
+++ b/clang/include/clang/Analysis/Analyses/UnsafeBufferUsageGadgets.def
@@ -36,6 +36,7 @@ FIXABLE_GADGET(PointerDereference)
FIXABLE_GADGET(UPCAddressofArraySubscript) // '&DRE[any]' in an Unspecified Pointer Context
FIXABLE_GADGET(UPCStandalonePointer)
FIXABLE_GADGET(UPCPreIncrement) // '++Ptr' in an Unspecified Pointer Context
+FIXABLE_GADGET(PointerAssignment)
#undef FIXABLE_GADGET
#undef WARNING_GADGET
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index f203ac6c2a84e..a777d43f1468f 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -11822,8 +11822,8 @@ def warn_unsafe_buffer_operation : Warning<
InGroup<UnsafeBufferUsage>, DefaultIgnore;
def note_unsafe_buffer_operation : Note<
"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%select{|, and change %2 to '%select{std::span|std::array|std::span::iterator}1' to propagate bounds information between them}3">;
def note_safe_buffer_usage_suggestions_disabled : Note<
"pass -fsafe-buffer-usage-suggestions to receive code hardening suggestions">;
def err_loongarch_builtin_requires_la32 : Error<
diff --git a/clang/lib/Analysis/UnsafeBufferUsage.cpp b/clang/lib/Analysis/UnsafeBufferUsage.cpp
index 87e3ec90dbf2f..c9cc4ccbfb5d5 100644
--- a/clang/lib/Analysis/UnsafeBufferUsage.cpp
+++ b/clang/lib/Analysis/UnsafeBufferUsage.cpp
@@ -16,6 +16,7 @@
#include <memory>
#include <optional>
#include <sstream>
+#include <queue>
using namespace llvm;
using namespace clang;
@@ -256,6 +257,29 @@ isInUnspecifiedPointerContext(internal::Matcher<Stmt> InnerMatcher) {
// FIXME: any more cases? (UPC excludes the RHS of an assignment. For now we
// don't have to check that.)
}
+
+// Returns a matcher that matches any expression 'e' such that `innerMatcher`
+// matches 'e' and 'e' is in an unspecified untyped context (i.e the expression
+// 'e' isn't evaluated to an RValue). For example, consider the following code:
+// int *p = new int[4];
+// int *q = new int[4];
+// if ((p = q)) {}
+// p = q;
+// The expression `p = q` in the conditional of the `if` statement
+// `if ((p = q))` is evaluated as an RValue, whereas the expression `p = q;`
+// in the assignment statement is in an untyped context.
+static internal::Matcher<Stmt>
+isInUnspecifiedUntypedContext(internal::Matcher<Stmt> InnerMatcher) {
+ // An unspecified context can be
+ // 1. A compound statement,
+ // 2. The body of an if statement
+ // 3. Body of a loop
+ auto CompStmt = compoundStmt(forEach(InnerMatcher));
+ auto IfStmtThen = ifStmt(hasThen(InnerMatcher));
+ auto IfStmtElse = ifStmt(hasElse(InnerMatcher));
+ // FIXME: Handle loop bodies.
+ return stmt(anyOf(CompStmt, IfStmtThen, IfStmtElse));
+}
} // namespace clang::ast_matchers
namespace {
@@ -337,6 +361,16 @@ class FixableGadget : public Gadget {
virtual std::optional<FixItList> 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<std::pair<const VarDecl *, const VarDecl *>>
+ getStrategyImplications() const {
+ return std::nullopt;
+ }
};
using FixableGadgetList = std::vector<std::unique_ptr<FixableGadget>>;
@@ -499,6 +533,58 @@ class PointerArithmeticGadget : public WarningGadget {
// FIXME: this gadge will need a fix-it
};
+/// A pointer assignment expression of the form:
+/// \code
+/// p = q;
+/// \endcode
+class PointerAssignmentGadget : public FixableGadget {
+private:
+ static constexpr const char *const PointerAssignmentTag = "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<BinaryOperator>(PointerAssignmentTag)),
+ PtrLHS(Result.Nodes.getNodeAs<DeclRefExpr>(PointerAssignLHSTag)),
+ PtrRHS(Result.Nodes.getNodeAs<DeclRefExpr>(PointerAssignRHSTag)) {}
+
+ static bool classof(const Gadget *G) {
+ return G->getKind() == Kind::PointerAssignment;
+ }
+
+ static Matcher matcher() {
+ auto PtrAssignExpr = binaryOperator(allOf(hasOperatorName("="),
+ hasRHS(ignoringParenImpCasts(declRefExpr(hasPointerType(),
+ to(varDecl())).
+ bind(PointerAssignRHSTag))),
+ hasLHS(declRefExpr(hasPointerType(),
+ to(varDecl())).
+ bind(PointerAssignLHSTag))));
+
+ //FIXME: Handle declarations at assignments
+ return stmt(isInUnspecifiedUntypedContext(PtrAssignExpr));
+ }
+
+ virtual std::optional<FixItList> 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<std::pair<const VarDecl *, const VarDecl *>>
+ getStrategyImplications() const override {
+ return std::make_pair(cast<VarDecl>(PtrLHS->getDecl()),
+ cast<VarDecl>(PtrRHS->getDecl()));
+ }
+};
+
/// A call of a function or method that performs unchecked buffer operations
/// over one of its pointer parameters.
class UnsafeBufferUsageAttrGadget : public WarningGadget {
@@ -986,17 +1072,17 @@ template <typename NodeTy> struct CompareNode {
};
struct WarningGadgetSets {
- std::map<const VarDecl *, std::set<std::unique_ptr<WarningGadget>>,
+ std::map<const VarDecl *, std::set<const WarningGadget *>,
// To keep keys sorted by their locations in the map so that the
// order is deterministic:
CompareNode<VarDecl>>
byVar;
// These Gadgets are not related to pointer variables (e. g. temporaries).
- llvm::SmallVector<std::unique_ptr<WarningGadget>, 16> noVar;
+ llvm::SmallVector<const WarningGadget *, 16> 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.
@@ -1006,13 +1092,13 @@ groupWarningGadgetsByVar(WarningGadgetList &&AllUnsafeOperations) {
bool AssociatedWithVarDecl = false;
for (const DeclRefExpr *DRE : ClaimedVarUseSites) {
if (const auto *VD = dyn_cast<VarDecl>(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;
}
}
@@ -1020,7 +1106,7 @@ groupWarningGadgetsByVar(WarningGadgetList &&AllUnsafeOperations) {
}
struct FixableGadgetSets {
- std::map<const VarDecl *, std::set<std::unique_ptr<FixableGadget>>> byVar;
+ std::map<const VarDecl *, std::set<const FixableGadget *>> byVar;
};
static FixableGadgetSets
@@ -1031,7 +1117,7 @@ groupFixablesByVar(FixableGadgetList &&AllFixableOperations) {
for (const DeclRefExpr *DRE : DREs) {
if (const auto *VD = dyn_cast<VarDecl>(DRE->getDecl())) {
- FixablesForUnsafeVars.byVar[VD].emplace(std::move(F));
+ FixablesForUnsafeVars.byVar[VD].insert(F.get());
}
}
}
@@ -1069,6 +1155,26 @@ bool clang::internal::anyConflict(const SmallVectorImpl<FixItHint> &FixIts,
return false;
}
+std::optional<FixItList>
+PointerAssignmentGadget::getFixits(const Strategy &S) const {
+ const auto *LeftVD = cast<VarDecl>(PtrLHS->getDecl());
+ const auto *RightVD = cast<VarDecl>(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;
+}
+
+
std::optional<FixItList>
ULCArraySubscriptGadget::getFixits(const Strategy &S) const {
if (const auto *DRE =
@@ -1556,14 +1662,28 @@ static bool overlapWithMacro(const FixItList &FixIts) {
});
}
+static bool impossibleToFixForVar(const FixableGadgetSets &FixablesForUnsafeVars,
+ const Strategy &S,
+ const VarDecl * Var) {
+ for (const auto &F : FixablesForUnsafeVars.byVar.find(Var)->second) {
+ std::optional<FixItList> Fixits = F->getFixits(S);
+ if (!Fixits) {
+ return true;
+ }
+ }
+ return false;
+}
+
static std::map<const VarDecl *, FixItList>
getFixIts(FixableGadgetSets &FixablesForUnsafeVars, const Strategy &S,
- const DeclUseTracker &Tracker, const ASTContext &Ctx,
- UnsafeBufferUsageHandler &Handler) {
+ const DeclUseTracker &Tracker, const ASTContext &Ctx,
+ UnsafeBufferUsageHandler &Handler,
+ const DefMapTy &VarGrpMap) {
std::map<const VarDecl *, FixItList> FixItsForVariable;
for (const auto &[VD, Fixables] : FixablesForUnsafeVars.byVar) {
+ const Strategy::Kind ReplacementTypeForVD = S.lookup(VD);
FixItsForVariable[VD] =
- fixVariable(VD, S.lookup(VD), Tracker, Ctx, Handler);
+ fixVariable(VD, ReplacementTypeForVD, Tracker, Ctx, Handler);
// If we fail to produce Fix-It for the declaration we have to skip the
// variable entirely.
if (FixItsForVariable[VD].empty()) {
@@ -1583,23 +1703,71 @@ getFixIts(FixableGadgetSets &FixablesForUnsafeVars, const Strategy &S,
CorrectFixes.end());
}
}
- if (ImpossibleToFix)
+
+ if (ImpossibleToFix) {
FixItsForVariable.erase(VD);
- else
- FixItsForVariable[VD].insert(FixItsForVariable[VD].end(),
- FixItsForVD.begin(), FixItsForVD.end());
- // We conservatively discard fix-its of a variable if
- // a fix-it overlaps with macros; or
- // a fix-it conflicts with another one
+ 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());
+
+ // 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;
+ }
+ }
+
+ 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(), Handler);
+ } else {
+ GroupFix = FixItsForVariable[Var];
+ }
+
+ for (auto Fix : GroupFix) {
+ FixItsForVariable[VD.first].push_back(Fix);
+ }
+ }
}
}
return FixItsForVariable;
}
+
static Strategy
getNaiveStrategy(const llvm::SmallVectorImpl<const VarDecl *> &UnsafeVars) {
Strategy S;
@@ -1614,54 +1782,135 @@ void clang::checkUnsafeBufferUsage(const Decl *D,
bool EmitSuggestions) {
assert(D && D->getBody());
WarningGadgetSets UnsafeOps;
- FixableGadgetSets FixablesForUnsafeVars;
- DeclUseTracker Tracker;
-
- {
- auto [FixableGadgets, WarningGadgets, TrackerRes] =
- findGadgets(D, Handler, EmitSuggestions);
-
- if (!EmitSuggestions) {
- // Our job is very easy without suggestions. Just warn about
- // every problematic operation and consider it done. No need to deal
- // with fixable gadgets, no need to group operations by variable.
- for (const auto &G : WarningGadgets) {
- Handler.handleUnsafeOperation(G->getBaseStmt(),
- /*IsRelatedToDecl=*/false);
- }
+ FixableGadgetSets FixablesForAllVars;
+
+ auto [FixableGadgets, WarningGadgets, Tracker] =
+ findGadgets(D, Handler, EmitSuggestions);
- // This return guarantees that most of the machine doesn't run when
- // suggestions aren't requested.
- assert(FixableGadgets.size() == 0 &&
- "Fixable gadgets found but suggestions not requested!");
- return;
+ if (!EmitSuggestions) {
+ // Our job is very easy without suggestions. Just warn about
+ // every problematic operation and consider it done. No need to deal
+ // with fixable gadgets, no need to group operations by variable.
+ for (const auto &G : WarningGadgets) {
+ Handler.handleUnsafeOperation(G->getBaseStmt(),
+ /*IsRelatedToDecl=*/false);
}
- UnsafeOps = groupWarningGadgetsByVar(std::move(WarningGadgets));
- FixablesForUnsafeVars = groupFixablesByVar(std::move(FixableGadgets));
- Tracker = std::move(TrackerRes);
+ // This return guarantees that most of the machine doesn't run when
+ // suggestions aren't requested.
+ assert(FixableGadgets.size() == 0 &&
+ "Fixable gadgets found but suggestions not requested!");
+ return;
}
- std::map<const VarDecl *, FixItList> FixItsForVariable;
+ UnsafeOps = groupWarningGadgetsByVar(std::move(WarningGadgets));
+ FixablesForAllVars = groupFixablesByVar(std::move(FixableGadgets));
+
+ std::map<const VarDecl *, FixItList> FixItsForVariableGroup;
+ DefMapTy VariableGroupsMap{};
// 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();) {
// FIXME: Support ParmVarDecl as well.
if (!it->first->isLocalVarDecl() || Tracker.hasUnclaimedUses(it->first)) {
- it = FixablesForUnsafeVars.byVar.erase(it);
+ it = FixablesForAllVars.byVar.erase(it);
} else {
++it;
}
}
llvm::SmallVector<const VarDecl *, 16> 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<const VarDecl *, std::set<const VarDecl *>>;
+ DepMapTy DependenciesMap{};
+ DepMapTy PtrAssignmentGraph{};
+
+ for (auto it : FixablesForAllVars.byVar) {
+ for (const FixableGadget *fixable : it.second) {
+ std::optional<std::pair<const VarDecl *, const VarDecl *>> ImplPair =
+ fixable->getStrategyImplications();
+ if (ImplPair) {
+ std::pair<const VarDecl *, const VarDecl *> 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<const VarDecl *> VisitedVarsDirected{};
+ for (const auto &[Var, ignore] : UnsafeOps.byVar) {
+ if (VisitedVarsDirected.find(Var) == VisitedVarsDirected.end()) {
+
+ std::queue<const VarDecl*> 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<const VarDecl *> VisitedVars{};
+ for (const auto &[Var, ignore] : UnsafeOps.byVar) {
+ if (VisitedVars.find(Var) == VisitedVars.end()) {
+ std::vector<const VarDecl *> VarGroup{};
+
+ std::queue<const VarDecl*> 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(), Handler);
+ FixItsForVariableGroup = getFixIts(FixablesForAllVars, NaiveStrategy, Tracker,
+ D->getASTContext(), Handler, VariableGroupsMap);
// FIXME Detect overlapping FixIts.
@@ -1670,10 +1919,11 @@ void clang::checkUnsafeBufferUsage(const Decl *D,
}
for (const auto &[VD, WarningGadgets] : UnsafeOps.byVar) {
- auto FixItsIt = FixItsForVariable.find(VD);
- Handler.handleFixableVariable(VD, FixItsIt != FixItsForVariable.end()
- ? std::move(FixItsIt->second)
- : FixItList{});
+ auto FixItsIt = FixItsForVariableGroup.find(VD);
+ Handler.handleUnsafeVariableGroup(VD, VariableGroupsMap,
+ FixItsIt != FixItsForVariableGroup.end()
+ ? std::move(FixItsIt->second)
+ : FixItList{});
for (const auto &G : WarningGadgets) {
Handler.handleUnsafeOperation(G->getBaseStmt(), /*IsRelatedToDecl=*/true);
}
diff --git a/clang/lib/Sema/AnalysisBasedWarnings.cpp b/clang/lib/Sema/AnalysisBasedWarnings.cpp
index eced15ea62a4e..5a194d8ad70a8 100644
--- a/clang/lib/Sema/AnalysisBasedWarnings.cpp
+++ b/clang/lib/Sema/AnalysisBasedWarnings.cpp
@@ -2218,8 +2218,8 @@ class UnsafeBufferUsageReporter : public UnsafeBufferUsageHandler {
}
}
- // FIXME: rename to handleUnsafeVariable
- void handleFixableVariable(const VarDecl *Variable,
+ void handleUnsafeVariableGroup(const VarDecl *Variable,
+ const DefMapTy &VarGrpMap,
FixItList &&Fixes) override {
assert(!SuggestSuggestions &&
"Unsafe buffer usage fixits displayed without suggestions!");
@@ -2227,11 +2227,52 @@ class UnsafeBufferUsageReporter : public UnsafeBufferUsageHandler {
<< Variable << (Variable->getType()->isPointerType() ? 0 : 1)
<< Variable->getSourceRange();
if (!Fixes.empty()) {
- unsigned FixItStrategy = 0; // For now we only has 'std::span' strategy
+ const auto VarGroupForVD = VarGrpMap.find(Variable)->second;
+ unsigned FixItStrategy = 0; // For now we only have 'std::span' strategy
const auto &FD = S.Diag(Variable->getLocation(),
- diag::note_unsafe_buffer_variable_fixit);
+ diag::note_unsafe_buffer_variable_fixit_group);
+
+ FD << Variable << FixItStrategy;
+ std::string AllVars = "";
+ if (VarGroupForVD.size() > 1) {
+ 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() + "'");
+ }
+ }
+ }
+ }
+ FD << AllVars << 1;
+ } else {
+ FD << "" << 0;
+ }
- FD << Variable->getName() << FixItStrategy;
for (const auto &F : Fixes)
FD << F;
}
diff --git a/clang/test/SemaCXX/warn-unsafe-buffer-usage-multi-decl-fixits-test.cpp b/clang/test/SemaCXX/warn-unsafe-buffer-usage-multi-decl-fixits-test.cpp
new file mode 100644
index 0000000000000..73bf2cb7a689a
--- /dev/null
+++ b/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 -fsafe-buffer-usage-suggestions %s 2>&1 | FileCheck %s
+
+void foo1a() {
+ int *r = new int[7];
+ // CHECK-DAG: fix-it:"{{.*}}":{[[@LINE-1]]:3-[[@LINE-1]]:11}:"std::span<int> 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<int> 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<int> q"
+ q = r;
+}
+
+void foo1b() {
+ int *r = new int[7];
+ // CHECK-DAG: fix-it:"{{.*}}":{[[@LINE-1]]:3-[[@LINE-1]]:11}:"std::span<int> 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<int> 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<int> 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<int> 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<int> p"
+ p = r;
+ int tmp = r[9];
+ int *q;
+ // CHECK-NOT: fix-it:"{{.*}}":{[[@LINE-1]]:3-[[@LINE-1]]:11}:"std::span<int> 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<int> 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<int> 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<int> 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<int> 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<int> 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<int> 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<int> 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<int> 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<int> 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<int> r"
+ int *p = new int[5];
+ // CHECK-DAG: fix-it:"{{.*}}":{[[@LINE-1]]:3-[[@LINE-1]]:11}:"std::span<int> 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<int> 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<int> 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<int> 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<int> 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;
+}
diff --git a/clang/test/SemaCXX/warn-unsafe-buffer-usage-multi-decl-uuc-fixits.cpp b/clang/test/SemaCXX/warn-unsafe-buffer-usage-multi-decl-uuc-fixits.cpp
new file mode 100644
index 0000000000000..0c5716ba19478
--- /dev/null
+++ b/clang/test/SemaCXX/warn-unsafe-buffer-usage-multi-decl-uuc-fixits.cpp
@@ -0,0 +1,89 @@
+// RUN: %clang_cc1 -std=c++20 -Wunsafe-buffer-usage \
+// RUN: -fsafe-buffer-usage-suggestions \
+// RUN: -fdiagnostics-parseable-fixits %s 2>&1 | FileCheck %s
+
+void bar(int * param) {}
+
+void foo1a() {
+ int *r = new int[7];
+ // CHECK-DAG: fix-it:"{{.*}}":{[[@LINE-1]]:3-[[@LINE-1]]:11}:"std::span<int> 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<int> 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;
+ q = r;
+}
+
+void uuc_if_body() {
+ int *r = new int[7];
+ // CHECK-DAG: fix-it:"{{.*}}":{[[@LINE-1]]:3-[[@LINE-1]]:11}:"std::span<int> 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<int> p"
+ // CHECK-DAG: fix-it:"{{.*}}":{[[@LINE-2]]:12-[[@LINE-2]]:12}:"{"
+ // CHECK-DAG: fix-it:"{{.*}}":{[[@LINE-3]]:22-[[@LINE-3]]:22}:", 4}"
+ if (true)
+ p = r;
+ p[5] = 4;
+}
+
+void uuc_if_body1(bool flag) {
+ int *r = new int[7];
+ // CHECK-DAG: fix-it:"{{.*}}":{[[@LINE-1]]:3-[[@LINE-1]]:11}:"std::span<int> 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<int> p"
+ // CHECK-DAG: fix-it:"{{.*}}":{[[@LINE-2]]:12-[[@LINE-2]]:12}:"{"
+ // CHECK-DAG: fix-it:"{{.*}}":{[[@LINE-3]]:22-[[@LINE-3]]:22}:", 4}"
+ if (flag) {
+ p = r;
+ }
+ p[5] = 4;
+}
+
+void uuc_if_cond_no_unsafe_op() {
+ int *r = new int[7];
+ int *p = new int[4];
+ if ((p = r)) {
+ int x = 0;
+ }
+}
+
+void uuc_if_cond_unsafe_op() {
+ int *r = new int[7];
+ int *p = new int[4]; //expected-warning{{'p' is an unsafe pointer used for buffer access}}
+ if ((p = r)) {
+ p[3] = 2; // expected-note{{used in buffer access here}}
+ }
+}
+
+void uuc_if_cond_unsafe_op1() {
+ int *r = new int[7]; // expected-warning{{'r' is an unsafe pointer used for buffer access}}
+ int *p = new int[4];
+ if ((p = r)) {
+ r[3] = 2; // expected-note{{used in buffer access here}}
+ }
+}
+
+void uuc_if_cond_unsafe_op2() {
+ int *r = new int[7]; // expected-warning{{'r' is an unsafe pointer used for buffer access}}
+ int *p = new int[4]; // expected-warning{{'p' is an unsafe pointer used for buffer access}}
+ if ((p = r)) {
+ r[3] = 2; // expected-note{{used in buffer access here}}
+ }
+ p[4] = 6; // expected-note{{used in buffer access here}}
+}
+
+void uuc_call1() {
+ int *w = new int[4]; // expected-warning{{'w' is an unsafe pointer used for buffer access}}
+ int *y = new int[4];
+ bar(w = y);
+ w[5] = 0; // expected-note{{used in buffer access here}}
+}
diff --git a/clang/test/SemaCXX/warn-unsafe-buffer-usage-multi-decl-uuc.cpp b/clang/test/SemaCXX/warn-unsafe-buffer-usage-multi-decl-uuc.cpp
new file mode 100644
index 0000000000000..2b37442cbd84b
--- /dev/null
+++ b/clang/test/SemaCXX/warn-unsafe-buffer-usage-multi-decl-uuc.cpp
@@ -0,0 +1,88 @@
+// RUN: %clang_cc1 -std=c++20 -Wunsafe-buffer-usage -fsafe-buffer-usage-suggestions -verify %s
+void bar(int * param) {}
+
+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 uuc_if_body() {
+ 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$}}}}
+ if (true)
+ p = r;
+ p[5] = 4; // expected-note{{used in buffer access here}}
+}
+
+void uuc_if_body1(bool flag) {
+ 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$}}}}
+ if (flag) {
+ p = r;
+ }
+ p[5] = 4; // expected-note{{used in buffer access here}}
+}
+
+void uuc_if_body2(bool flag) {
+ 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$}}}}
+ if (flag) {
+ } else {
+ p = r;
+ }
+
+ p[5] = 4; // expected-note{{used in buffer access here}}
+}
+
+void uuc_if_cond_no_unsafe_op() {
+ int *r = new int[7];
+ int *p = new int[4];
+ if ((p = r)) {
+ int x = 0;
+ }
+}
+
+void uuc_if_cond_no_unsafe_op1() {
+ int *r = new int[7];
+ int *p = new int[4];
+ if (true) {
+ int x = 0;
+ } else if ((p = r))
+ int y = 10;
+}
+
+void uuc_if_cond_unsafe_op() {
+ int *r = new int[7];
+ int *p = new int[4]; //expected-warning{{'p' is an unsafe pointer used for buffer access}}
+ if ((p = r)) {
+ p[3] = 2; // expected-note{{used in buffer access here}}
+ }
+}
+
+void uuc_if_cond_unsafe_op1() {
+ int *r = new int[7]; // expected-warning{{'r' is an unsafe pointer used for buffer access}}
+ int *p = new int[4];
+ if ((p = r)) {
+ r[3] = 2; // expected-note{{used in buffer access here}}
+ }
+}
+
+void uuc_if_cond_unsafe_op2() {
+ int *r = new int[7]; // expected-warning{{'r' is an unsafe pointer used for buffer access}}
+ int *p = new int[4]; // expected-warning{{'p' is an unsafe pointer used for buffer access}}
+ if ((p = r)) {
+ r[3] = 2; // expected-note{{used in buffer access here}}
+ }
+ p[4] = 6; // expected-note{{used in buffer access here}}
+}
+
+void uuc_call1() {
+ int *w = new int[4]; // expected-warning{{'w' is an unsafe pointer used for buffer access}}
+ int *y = new int[4];
+ bar(w = y);
+ w[5] = 0; // expected-note{{used in buffer access here}}
+}
diff --git a/clang/test/SemaCXX/warn-unsafe-buffer-usage-multi-decl-warnings.cpp b/clang/test/SemaCXX/warn-unsafe-buffer-usage-multi-decl-warnings.cpp
new file mode 100644
index 0000000000000..d2aa57997e6d4
--- /dev/null
+++ b/clang/test/SemaCXX/warn-unsafe-buffer-usage-multi-decl-warnings.cpp
@@ -0,0 +1,347 @@
+// RUN: %clang_cc1 -std=c++20 -Wunsafe-buffer-usage -fsafe-buffer-usage-suggestions -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;
+}
+
+
+// FIXME: Support initializations at declarations.
+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'|'q' and '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; // 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'|'r' and 'p') 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'|'q' and 'r') 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'|'q' and 'r') 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'|'r' and 'p') 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'|'p' and 'r') 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'|'p' and 'q') 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'|'q' and 'r') 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'|'p' and 'r') 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'|'p' and 'r') 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'|'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 = 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'|'p' and 'q') 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'|'p' and 'r') 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'|'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[1] = 5; // expected-note{{used in buffer access here}}
+ r = p;
+}
More information about the cfe-commits
mailing list