llvmbot wrote:

<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-clang-tools-extra

Author: cc (ConcreteCactus)

<details>
<summary>Changes</summary>

This checker attempts to cover the global variable case in rule 
[EXP-30](https://wiki.sei.cmu.edu/confluence/display/c/EXP30-C.+Do+not+depend+on+the+order+of+evaluation+for+side+effects)
 in the SEI CERT C and rule 
[EXP-50](https://wiki.sei.cmu.edu/confluence/display/cplusplus/EXP50-CPP.+Do+not+depend+on+the+order+of+evaluation+for+side+effects)
 in the SEI CERT C++ Coding Standards.

It recurses into functions in the same translation unit and can handle global 
struct and union members as well.

---

Patch is 40.93 KiB, truncated to 20.00 KiB below, full version: 
https://github.com/llvm/llvm-project/pull/130421.diff


5 Files Affected:

- (modified) clang-tools-extra/clang-tidy/bugprone/BugproneTidyModule.cpp (+3) 
- (modified) clang-tools-extra/clang-tidy/bugprone/CMakeLists.txt (+1) 
- (added) clang-tools-extra/clang-tidy/bugprone/ConflictingGlobalAccesses.cpp 
(+748) 
- (added) clang-tools-extra/clang-tidy/bugprone/ConflictingGlobalAccesses.h 
(+77) 
- (added) 
clang-tools-extra/test/clang-tidy/checkers/bugprone/conflicting-global-accesses.cpp
 (+290) 


``````````diff
diff --git a/clang-tools-extra/clang-tidy/bugprone/BugproneTidyModule.cpp 
b/clang-tools-extra/clang-tidy/bugprone/BugproneTidyModule.cpp
index b780a85bdf3fe..09e3f481e9d1d 100644
--- a/clang-tools-extra/clang-tidy/bugprone/BugproneTidyModule.cpp
+++ b/clang-tools-extra/clang-tidy/bugprone/BugproneTidyModule.cpp
@@ -20,6 +20,7 @@
 #include "CastingThroughVoidCheck.h"
 #include "ChainedComparisonCheck.h"
 #include "ComparePointerToMemberVirtualFunctionCheck.h"
+#include "ConflictingGlobalAccesses.h"
 #include "CopyConstructorInitCheck.h"
 #include "CrtpConstructorAccessibilityCheck.h"
 #include "DanglingHandleCheck.h"
@@ -127,6 +128,8 @@ class BugproneModule : public ClangTidyModule {
         "bugprone-chained-comparison");
     CheckFactories.registerCheck<ComparePointerToMemberVirtualFunctionCheck>(
         "bugprone-compare-pointer-to-member-virtual-function");
+    CheckFactories.registerCheck<ConflictingGlobalAccesses>(
+        "bugprone-conflicting-global-accesses");
     CheckFactories.registerCheck<CopyConstructorInitCheck>(
         "bugprone-copy-constructor-init");
     CheckFactories.registerCheck<DanglingHandleCheck>(
diff --git a/clang-tools-extra/clang-tidy/bugprone/CMakeLists.txt 
b/clang-tools-extra/clang-tidy/bugprone/CMakeLists.txt
index e310ea9c94543..9754df6c06995 100644
--- a/clang-tools-extra/clang-tidy/bugprone/CMakeLists.txt
+++ b/clang-tools-extra/clang-tidy/bugprone/CMakeLists.txt
@@ -16,6 +16,7 @@ add_clang_library(clangTidyBugproneModule STATIC
   CastingThroughVoidCheck.cpp
   ChainedComparisonCheck.cpp
   ComparePointerToMemberVirtualFunctionCheck.cpp
+  ConflictingGlobalAccesses.cpp
   CopyConstructorInitCheck.cpp
   CrtpConstructorAccessibilityCheck.cpp
   DanglingHandleCheck.cpp
diff --git 
a/clang-tools-extra/clang-tidy/bugprone/ConflictingGlobalAccesses.cpp 
b/clang-tools-extra/clang-tidy/bugprone/ConflictingGlobalAccesses.cpp
new file mode 100644
index 0000000000000..48906bf715ab2
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/bugprone/ConflictingGlobalAccesses.cpp
@@ -0,0 +1,748 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM 
Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "ConflictingGlobalAccesses.h"
+
+#include "clang/AST/RecursiveASTVisitor.h"
+
+using namespace clang::ast_matchers;
+
+namespace clang::tidy::bugprone {
+
+namespace {
+// An AccesKind represents one access to a global variable.
+//
+// The unchecked versions represent reads/writes that are not handled by
+// -Wunsequenced. (e.g. accesses inside functions).
+using AccessKind = uint8_t;
+static constexpr AccessKind AkRead = 0;
+static constexpr AccessKind AkWrite = 1;
+static constexpr AccessKind AkUncheckedRead = 2;
+static constexpr AccessKind AkUncheckedWrite = 3;
+
+static constexpr uint8_t AkCount = 4;
+
+// The TraversalResultKind represents a set of accesses.
+// Bits are corresponding to the AccessKind enum values.
+using TraversalResultKind = uint8_t;
+static constexpr TraversalResultKind TrInvalid = 0;
+static constexpr TraversalResultKind TrRead = 1 << AkRead;
+static constexpr TraversalResultKind TrWrite = 1 << AkWrite;
+static constexpr TraversalResultKind TrUncheckedWrite = 1 << AkUncheckedWrite;
+
+// To represent fields in structs or unions we use numbered FieldIndices. The
+// FieldIndexArray represents one field inside a global struct/union system.
+// The FieldIndexArray can be thought of as a path inside a tree.
+using FieldIndex = uint16_t;
+static constexpr FieldIndex FiUnion = 0x8000;
+
+// Note: This bit signals whether the field is a *field of* a struct or a
+// union, not whether the type of the field itself is a struct or a union.
+using FieldIndexArray = SmallVector<FieldIndex>;
+
+/// One traversal recurses into one side of a binary expression or one
+/// parameter of a function call. At least two of these traversals are used to
+/// find conflicting accesses.
+///
+/// A TraversalResult represents one traversal.
+struct TraversalResult {
+  int IndexCreated; // We use indices to keep track of which
+                    // traversal we are in currently. The current
+                    // index is stored in GlobalRWVisitor with the
+                    // name TraversalIndex.
+  SourceLocation Loc[AkCount];
+  TraversalResultKind Kind;
+
+  TraversalResult();
+  TraversalResult(int Index, SourceLocation Loc, AccessKind Access);
+  void addNewAccess(SourceLocation Loc, AccessKind Access);
+};
+
+/// The result of a number of traversals.
+class TraversalAggregation {
+  DeclarationName DeclName; // The name of the global variable being checked.
+
+  // We only store the result of two traversals as two conflicting accesses
+  // are enough to detect undefined behavior. The two stored TraversalResults
+  // have different traversal indices.
+  //
+  // Note: Sometimes multiple traversals are merged into one
+  // TraversalResult.
+  TraversalResult MainPart, OtherPart;
+  // Pairings that are not reportable: Read-Read, Read-Write,
+  // Read-UncheckedRead, Write-Write, UncheckedRead-UncheckedRead.
+
+public:
+  TraversalAggregation();
+  TraversalAggregation(DeclarationName Name, SourceLocation Loc,
+                       AccessKind Access, int Index);
+  void addGlobalRW(SourceLocation Loc, AccessKind Access, int Index);
+  DeclarationName getDeclName() const;
+
+  bool isValid() const;
+
+  // If there is a conflict and that conflict isn't reported by -Wunsequenced
+  // then we report the conflict.
+  bool shouldBeReported() const;
+  bool hasConflictingOperations() const;
+
+private:
+  bool hasTwoAccesses() const;
+  bool isReportedByWunsequenced() const;
+};
+
+/// The ObjectAccessTree stores the TraversalAggregations of one global
+/// struct/union. Because each field can be handled as a single variable, the
+/// tree stores one TraversalAggregation for every field.
+///
+/// Note: structs, classes, and unions are called objects in the code.
+struct ObjectAccessTree {
+  using FieldMap = llvm::DenseMap<int, std::unique_ptr<ObjectAccessTree>>;
+  TraversalAggregation OwnAccesses;
+
+  // In a union, new fields should inherit from UnionTemporalAccesses
+  // instead of OwnAccesses. That's because an access to a field of a union is
+  // also an access to every other field of the same union.
+  TraversalAggregation UnionTemporalAccesses;
+
+  // We try to be lazy and only store fields that are actually accessed.
+  FieldMap Fields;
+  bool IsUnion;
+
+  ObjectAccessTree(TraversalAggregation Own);
+
+  void addFieldToAll(SourceLocation Loc, AccessKind Access, int Index);
+  void addFieldToAllExcept(uint16_t ExceptIndex, SourceLocation Loc,
+                           AccessKind Access, int Index);
+};
+
+/// This object is the root of all ObjectAccessTrees.
+class ObjectTraversalAggregation {
+  DeclarationName DeclName; // The name of the global struct/union.
+  ObjectAccessTree AccessTree;
+
+public:
+  ObjectTraversalAggregation(DeclarationName Name, SourceLocation Loc,
+                             FieldIndexArray FieldIndices, AccessKind Access,
+                             int Index);
+  void addFieldRW(SourceLocation Loc, FieldIndexArray FieldIndices,
+                  AccessKind Access, int Index);
+  DeclarationName getDeclName() const;
+  bool shouldBeReported() const;
+
+private:
+  bool shouldBeReportedRec(const ObjectAccessTree *Node) const;
+};
+
+/// GlobalRWVisitor (global read write visitor) does all the traversals.
+class GlobalRWVisitor : public RecursiveASTVisitor<GlobalRWVisitor> {
+  friend RecursiveASTVisitor<GlobalRWVisitor>;
+
+public:
+  GlobalRWVisitor(bool IsWritePossibleThroughFunctionParam);
+
+  // startTraversal is called to start a new traversal. It increments the
+  // TraversalIndex, which in turn will generate new TraversalResults.
+  void startTraversal(Expr *E);
+
+  const llvm::SmallVector<TraversalAggregation> &getGlobalsFound() const;
+
+  const llvm::SmallVector<ObjectTraversalAggregation>
+      &getObjectGlobalsFound() const;
+
+protected:
+  // RecursiveASTVisitor overrides
+  bool VisitDeclRefExpr(DeclRefExpr *S);
+  bool VisitUnaryOperator(UnaryOperator *Op);
+  bool VisitBinaryOperator(BinaryOperator *Op);
+  bool VisitCallExpr(CallExpr *CE);
+  bool VisitMemberExpr(MemberExpr *ME);
+
+private:
+  void visitCallExprArgs(CallExpr *CE);
+  void traverseFunctionsToBeChecked();
+
+  llvm::SmallVector<TraversalAggregation> GlobalsFound;
+  llvm::SmallVector<ObjectTraversalAggregation> ObjectGlobalsFound;
+
+  // We check inside functions only if the functions hasn't been checked in
+  // the current traversal. We use this array to check if the function is
+  // already registered to be checked.
+  llvm::SmallVector<std::pair<DeclarationName, Stmt *>> FunctionsToBeChecked;
+
+  // The TraversalIndex is used to differentiate between two sides of a binary
+  // expression or the parameters of a function. Every traversal represents
+  // one such expression and the TraversalIndex is incremented between them.
+  int TraversalIndex;
+
+  // Accesses that are inside functions are not checked by -Wunsequenced,
+  // therefore we keep track of whether we are inside of a function or not.
+  bool IsInFunction;
+
+  // Same as the HandleMutableFunctionParametersAsWrites option.
+  bool IsWritePossibleThroughFunctionParam;
+
+  void addGlobal(DeclarationName Name, SourceLocation Loc, bool IsWrite,
+                 bool IsUnchecked);
+  void addGlobal(const DeclRefExpr *DR, bool IsWrite, bool IsUnchecked);
+  void addField(DeclarationName Name, FieldIndexArray FieldIndices,
+                SourceLocation Loc, bool IsWrite, bool IsUnchecked);
+  bool handleModified(const Expr *Modified, bool IsUnchecked);
+  bool handleModifiedVariable(const DeclRefExpr *DE, bool IsUnchecked);
+  bool handleAccessedObject(const Expr *E, bool IsWrite, bool IsUnchecked);
+  bool isVariable(const Expr *E);
+};
+} // namespace
+
+static bool isGlobalDecl(const VarDecl *VD) {
+  return VD && VD->hasGlobalStorage() && VD->getLocation().isValid() &&
+         !VD->getType().isConstQualified();
+}
+
+AST_MATCHER_P(Expr, twoGlobalWritesBetweenSequencePoints, const LangStandard *,
+              LangStd) {
+  assert(LangStd);
+
+  const Expr *E = &Node;
+
+  if (const BinaryOperator *Op = dyn_cast<BinaryOperator>(E)) {
+    const BinaryOperator::Opcode Code = Op->getOpcode();
+    if (Code == BO_LAnd || Code == BO_LOr || Code == BO_Comma) {
+      return false;
+    }
+
+    if (Op->isAssignmentOp() && isa<DeclRefExpr>(Op->getLHS())) {
+      return false;
+    }
+
+    if (LangStd->isCPlusPlus17() &&
+        (Code == BO_Shl || Code == BO_Shr || Code == BO_PtrMemD ||
+         Code == BO_PtrMemI || Op->isAssignmentOp())) {
+      return false;
+    }
+
+    return true;
+  }
+
+  if (isa<CallExpr>(E)) {
+    return true;
+  }
+
+  return false;
+}
+
+ConflictingGlobalAccesses::ConflictingGlobalAccesses(StringRef Name,
+                                                     ClangTidyContext *Context)
+    : ClangTidyCheck(Name, Context),
+      HandleMutableFunctionParametersAsWrites(
+              Options.get("HandleMutableFunctionParametersAsWrites", false))
+{}
+
+void ConflictingGlobalAccesses::storeOptions(ClangTidyOptions::OptionMap& Opts)
+{
+    Options.store(Opts, "HandleMutableFunctionParametersAsWrites",
+                  HandleMutableFunctionParametersAsWrites);
+}
+
+void ConflictingGlobalAccesses::registerMatchers(MatchFinder *Finder) {
+
+  const LangStandard *LangStd =
+      &LangStandard::getLangStandardForKind(getLangOpts().LangStd);
+
+  ast_matchers::internal::Matcher<Expr> GlobalAccessMatcher =
+      twoGlobalWritesBetweenSequencePoints(LangStd);
+
+  Finder->addMatcher(
+      stmt(traverse(TK_AsIs, expr(GlobalAccessMatcher).bind("gw"))), this);
+}
+
+void ConflictingGlobalAccesses::check(const MatchFinder::MatchResult &Result) {
+  const Expr *E = Result.Nodes.getNodeAs<Expr>("gw");
+  assert(E);
+
+  GlobalRWVisitor Visitor(HandleMutableFunctionParametersAsWrites);
+  if (const auto *Op = dyn_cast<BinaryOperator>(E)) {
+    Visitor.startTraversal(Op->getLHS());
+    Visitor.startTraversal(Op->getRHS());
+
+  } else if (const auto *CE = dyn_cast<CallExpr>(E)) {
+    for (uint32_t I = 0; I < CE->getNumArgs(); I++) {
+      Visitor.startTraversal(const_cast<Expr *>(CE->getArg(I)));
+    }
+  }
+
+  const llvm::SmallVector<TraversalAggregation> &Globals =
+      Visitor.getGlobalsFound();
+
+  for (uint32_t I = 0; I < Globals.size(); I++) {
+    if (Globals[I].shouldBeReported()) {
+      diag(E->getBeginLoc(), "read/write conflict on global variable " +
+                                 Globals[I].getDeclName().getAsString());
+    }
+  }
+  const llvm::SmallVector<ObjectTraversalAggregation> &ObjectGlobals =
+      Visitor.getObjectGlobalsFound();
+  for (uint32_t I = 0; I < ObjectGlobals.size(); I++) {
+    if (ObjectGlobals[I].shouldBeReported()) {
+      diag(E->getBeginLoc(), "read/write conflict on the field of the global "
+                             "object " +
+                                 ObjectGlobals[I].getDeclName().getAsString());
+    }
+  }
+}
+
+GlobalRWVisitor::GlobalRWVisitor(bool IsWritePossibleThroughFunctionParam) 
+    : TraversalIndex(0), IsInFunction(false),
+      IsWritePossibleThroughFunctionParam(IsWritePossibleThroughFunctionParam)
+{}
+
+void GlobalRWVisitor::startTraversal(Expr *E) {
+  TraversalIndex++;
+  FunctionsToBeChecked.clear();
+  IsInFunction = false;
+  TraverseStmt(E);
+
+  // We keep a list of functions to be checked during traversal so that they 
are
+  // not checked multiple times.
+  traverseFunctionsToBeChecked();
+}
+
+void GlobalRWVisitor::traverseFunctionsToBeChecked() {
+  IsInFunction = true;
+
+  // We could find more functions to be checked while checking functions.
+  // Because a simple iterator could get invalidated, we index into the array.
+  for (size_t I = 0; I < FunctionsToBeChecked.size(); ++I) {
+    TraverseStmt(FunctionsToBeChecked[I].second);
+  }
+}
+
+bool GlobalRWVisitor::isVariable(const Expr *E) {
+  const Type *T = E->getType().getTypePtrOrNull();
+  assert(T);
+
+  return isa<DeclRefExpr>(E) && (!T->isRecordType() || T->isUnionType());
+}
+
+bool GlobalRWVisitor::VisitDeclRefExpr(DeclRefExpr *DR) {
+  if (!isa<VarDecl>(DR->getDecl())) {
+    return true;
+  }
+  const auto *VD = dyn_cast<VarDecl>(DR->getDecl());
+  if (!isVariable(DR)) {
+    return handleAccessedObject(DR, /*IsWrite*/ false, /*IsUnchecked*/ false);
+  }
+  if (isGlobalDecl(VD)) {
+    addGlobal(VD->getDeclName(), VD->getBeginLoc(), /*IsWrite*/ false,
+              /*IsUnchecked*/ false);
+    return true;
+  }
+  return true;
+}
+
+bool GlobalRWVisitor::VisitMemberExpr(MemberExpr *ME) {
+  return handleAccessedObject(ME, /*IsWrite*/ false, /*IsUnchecked*/ false);
+}
+
+bool GlobalRWVisitor::handleModifiedVariable(const DeclRefExpr *DR,
+                                             bool IsUnchecked) {
+  if (!isa<VarDecl>(DR->getDecl())) {
+    return true;
+  }
+  const auto *VD = dyn_cast<VarDecl>(DR->getDecl());
+
+  if (isGlobalDecl(VD)) {
+    addGlobal(VD->getDeclName(), VD->getBeginLoc(), /*IsWrite*/ true,
+              IsUnchecked);
+    return false;
+  }
+  return true;
+}
+
+bool GlobalRWVisitor::handleAccessedObject(const Expr *E, bool IsWrite,
+                                           bool IsUnchecked) {
+  const Expr *CurrentNode = E;
+  int NodeCount = 0;
+  while (isa<MemberExpr>(CurrentNode)) {
+    const MemberExpr *CurrentField = dyn_cast<MemberExpr>(CurrentNode);
+
+    if (CurrentField->isArrow()) {
+      return true;
+    }
+
+    const ValueDecl *Decl = CurrentField->getMemberDecl();
+    if (!isa<FieldDecl>(Decl)) {
+      return true;
+    }
+
+    CurrentNode = CurrentField->getBase();
+    NodeCount++;
+  }
+
+  if (!isa<DeclRefExpr>(CurrentNode)) {
+    return true;
+  }
+  const DeclRefExpr *Base = dyn_cast<DeclRefExpr>(CurrentNode);
+
+  if (!isa<VarDecl>(Base->getDecl())) {
+    return true;
+  }
+  const VarDecl *BaseDecl = dyn_cast<VarDecl>(Base->getDecl());
+
+  if (!isGlobalDecl(BaseDecl)) {
+    return true;
+  }
+
+  FieldIndexArray FieldIndices(NodeCount);
+  CurrentNode = E;
+  while (isa<MemberExpr>(CurrentNode)) {
+    const MemberExpr *CurrentField = dyn_cast<MemberExpr>(CurrentNode);
+    const FieldDecl *Decl = dyn_cast<FieldDecl>(CurrentField->getMemberDecl());
+    assert(Decl);
+
+    FieldIndices[NodeCount - 1] = Decl->getFieldIndex();
+    const RecordDecl *Record = Decl->getParent();
+    assert(Record);
+
+    if (Record->isUnion()) {
+      FieldIndices[NodeCount - 1] |= FiUnion;
+    }
+
+    CurrentNode = CurrentField->getBase();
+    NodeCount--;
+  }
+
+  addField(BaseDecl->getDeclName(), FieldIndices, Base->getBeginLoc(), IsWrite,
+           IsUnchecked);
+  return false;
+}
+
+bool GlobalRWVisitor::handleModified(const Expr *Modified, bool IsUnchecked) {
+  assert(Modified);
+
+  if (isVariable(Modified)) {
+    return handleModifiedVariable(dyn_cast<DeclRefExpr>(Modified), 
IsUnchecked);
+  }
+
+  return handleAccessedObject(Modified, /*IsWrite*/ true, IsUnchecked);
+}
+
+bool GlobalRWVisitor::VisitUnaryOperator(UnaryOperator *Op) {
+  UnaryOperator::Opcode Code = Op->getOpcode();
+  if (Code == UO_PostInc || Code == UO_PostDec || Code == UO_PreInc ||
+      Code == UO_PreDec) {
+    return handleModified(Op->getSubExpr(), /*IsUnchecked*/ false);
+  }
+  return true;
+}
+
+bool GlobalRWVisitor::VisitBinaryOperator(BinaryOperator *Op) {
+  if (Op->isAssignmentOp()) {
+    return handleModified(Op->getLHS(), /*IsUnchecked*/ false);
+  }
+
+  return true;
+}
+
+void GlobalRWVisitor::visitCallExprArgs(CallExpr *CE) {
+  const Type *CT = CE->getCallee()->getType().getTypePtrOrNull();
+  if (const auto *PT = dyn_cast_if_present<PointerType>(CT)) {
+    CT = PT->getPointeeType().getTypePtrOrNull();
+  }
+  const auto *ProtoType = dyn_cast_if_present<FunctionProtoType>(CT);
+
+  for (uint32_t I = 0; I < CE->getNumArgs(); I++) {
+    Expr *Arg = CE->getArg(I);
+
+    if (!ProtoType || I >= ProtoType->getNumParams()) {
+      continue;
+    }
+
+    if (const auto *Op = dyn_cast<UnaryOperator>(Arg)) {
+      if (Op->getOpcode() != UO_AddrOf) {
+        continue;
+      }
+
+      if (const auto *PtrType = dyn_cast_if_present<PointerType>(
+              ProtoType->getParamType(I).getTypePtrOrNull())) {
+        if (PtrType->getPointeeType().isConstQualified()) {
+          continue;
+        }
+
+        if (handleModified(Op->getSubExpr(), /*IsUnchecked*/ true)) {
+          continue;
+        }
+      }
+    }
+
+    if (const auto *RefType = dyn_cast_if_present<ReferenceType>(
+            ProtoType->getParamType(I).getTypePtrOrNull())) {
+      if (RefType->getPointeeType().isConstQualified()) {
+        continue;
+      }
+
+      if (handleModified(Arg, /*IsUnchecked*/ true)) {
+        continue;
+      }
+    }
+  }
+}
+
+bool GlobalRWVisitor::VisitCallExpr(CallExpr *CE) {
+
+  if (IsWritePossibleThroughFunctionParam || isa<CXXOperatorCallExpr>(CE)) {
+    visitCallExprArgs(CE);
+  }
+
+  if (!isa_and_nonnull<FunctionDecl>(CE->getCalleeDecl())) {
+    return true;
+  }
+  const auto *FD = dyn_cast<FunctionDecl>(CE->getCalleeDecl());
+
+  if (!FD->hasBody()) {
+    return true;
+  }
+
+  for (const std::pair<DeclarationName, Stmt *> &Fun : FunctionsToBeChecked) {
+    if (Fun.first == FD->getDeclName()) {
+      return true;
+    }
+  }
+  FunctionsToBeChecked.emplace_back(FD->getDeclName(), FD->getBody());
+
+  return true;
+}
+
+const llvm::SmallVector<TraversalAggregation> &
+GlobalRWVisitor::getGlobalsFound() const {
+  return GlobalsFound;
+}
+
+const llvm::SmallVector<ObjectTraversalAggregation> &
+GlobalRWVisitor::getObjectGlobalsFound() const {
+  return ObjectGlobalsFound;
+}
+
+void GlobalRWVisitor::addGlobal(DeclarationName Name, SourceLocation Loc,
+                                bool IsWrite, bool IsUnchecked) {
+  AccessKind Access = (IsInFunction || IsUnchecked)
+                          ? (IsWrite ? AkUncheckedWrite : AkUncheckedRead)
+                          : (IsWrite ? AkWrite : AkRead);
+  for (uint32_t I = 0; I < GlobalsFound.size(); I++) {
+    if (GlobalsFound[I].getDeclName() == Name) {
+      Global...
[truncated]

``````````

</details>


https://github.com/llvm/llvm-project/pull/130421
_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to