[clang] ee6b08e - [-Wunsafe-buffer-usage] Group variables associated by pointer assignments

Rashmi Mudduluru via cfe-commits cfe-commits at lists.llvm.org
Wed May 24 16:21:15 PDT 2023


Author: Rashmi Mudduluru
Date: 2023-05-24T16:20:55-07:00
New Revision: ee6b08e99375fc48d1e5848704a66c2e8e57eb3b

URL: https://github.com/llvm/llvm-project/commit/ee6b08e99375fc48d1e5848704a66c2e8e57eb3b
DIFF: https://github.com/llvm/llvm-project/commit/ee6b08e99375fc48d1e5848704a66c2e8e57eb3b.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..d2fb56fbdac07
--- /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' 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;
+}


        


More information about the cfe-commits mailing list