[clang-tools-extra] [clang-tidy] Added Conflicting Global Accesses checker (PR #130421)
Áron Hárnási via cfe-commits
cfe-commits at lists.llvm.org
Tue Jun 10 12:24:53 PDT 2025
https://github.com/ConcreteCactus updated https://github.com/llvm/llvm-project/pull/130421
>From c01a4c0c255bd52bd07a850b9d593128263d4db1 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=C3=81ron=20H=C3=A1rn=C3=A1si?= <aron.harnasi at gmail.com>
Date: Fri, 22 Nov 2024 21:43:04 +0100
Subject: [PATCH] [clang-tidy] Added bugprone-unsequenced-global-accesses check
This check attempts to detect unsequenced accesses to global
variables. It recurses into function calls in the same translation unit,
and can handle fields on global structs/unions.
---
.../bugprone/BugproneTidyModule.cpp | 3 +
.../clang-tidy/bugprone/CMakeLists.txt | 1 +
.../UnsequencedGlobalAccessesCheck.cpp | 747 ++++++++++++++++++
.../bugprone/UnsequencedGlobalAccessesCheck.h | 35 +
.../clang-tidy/cert/CERTTidyModule.cpp | 6 +
.../clang-tidy/utils/ExecutionVisitor.h | 195 +++++
clang-tools-extra/docs/ReleaseNotes.rst | 14 +
.../bugprone/unsequenced-global-accesses.rst | 86 ++
.../docs/clang-tidy/checks/cert/exp30-c.rst | 10 +
.../docs/clang-tidy/checks/cert/exp50-cpp.rst | 10 +
.../docs/clang-tidy/checks/list.rst | 3 +
.../bugprone/unsequenced-global-accesses.cpp | 601 ++++++++++++++
12 files changed, 1711 insertions(+)
create mode 100644 clang-tools-extra/clang-tidy/bugprone/UnsequencedGlobalAccessesCheck.cpp
create mode 100644 clang-tools-extra/clang-tidy/bugprone/UnsequencedGlobalAccessesCheck.h
create mode 100644 clang-tools-extra/clang-tidy/utils/ExecutionVisitor.h
create mode 100644 clang-tools-extra/docs/clang-tidy/checks/bugprone/unsequenced-global-accesses.rst
create mode 100644 clang-tools-extra/docs/clang-tidy/checks/cert/exp30-c.rst
create mode 100644 clang-tools-extra/docs/clang-tidy/checks/cert/exp50-cpp.rst
create mode 100644 clang-tools-extra/test/clang-tidy/checkers/bugprone/unsequenced-global-accesses.cpp
diff --git a/clang-tools-extra/clang-tidy/bugprone/BugproneTidyModule.cpp b/clang-tools-extra/clang-tidy/bugprone/BugproneTidyModule.cpp
index 64f4a524daf0d..db94933b8bd5a 100644
--- a/clang-tools-extra/clang-tidy/bugprone/BugproneTidyModule.cpp
+++ b/clang-tools-extra/clang-tidy/bugprone/BugproneTidyModule.cpp
@@ -95,6 +95,7 @@
#include "UnintendedCharOstreamOutputCheck.h"
#include "UniquePtrArrayMismatchCheck.h"
#include "UnsafeFunctionsCheck.h"
+#include "UnsequencedGlobalAccessesCheck.h"
#include "UnusedLocalNonTrivialVariableCheck.h"
#include "UnusedRaiiCheck.h"
#include "UnusedReturnValueCheck.h"
@@ -128,6 +129,8 @@ class BugproneModule : public ClangTidyModule {
"bugprone-chained-comparison");
CheckFactories.registerCheck<ComparePointerToMemberVirtualFunctionCheck>(
"bugprone-compare-pointer-to-member-virtual-function");
+ CheckFactories.registerCheck<UnsequencedGlobalAccessesCheck>(
+ "bugprone-unsequenced-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 d862794cde323..8f361ba9eb5f3 100644
--- a/clang-tools-extra/clang-tidy/bugprone/CMakeLists.txt
+++ b/clang-tools-extra/clang-tidy/bugprone/CMakeLists.txt
@@ -96,6 +96,7 @@ add_clang_library(clangTidyBugproneModule STATIC
UnhandledSelfAssignmentCheck.cpp
UniquePtrArrayMismatchCheck.cpp
UnsafeFunctionsCheck.cpp
+ UnsequencedGlobalAccessesCheck.cpp
UnusedLocalNonTrivialVariableCheck.cpp
UnusedRaiiCheck.cpp
UnusedReturnValueCheck.cpp
diff --git a/clang-tools-extra/clang-tidy/bugprone/UnsequencedGlobalAccessesCheck.cpp b/clang-tools-extra/clang-tidy/bugprone/UnsequencedGlobalAccessesCheck.cpp
new file mode 100644
index 0000000000000..e275df1c40b4c
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/bugprone/UnsequencedGlobalAccessesCheck.cpp
@@ -0,0 +1,747 @@
+//===----------------------------------------------------------------------===//
+//
+// 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 "UnsequencedGlobalAccessesCheck.h"
+
+#include "../utils/ExecutionVisitor.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).
+enum AccessKind : uint8_t {
+ AkRead = 0,
+ AkWrite,
+ AkLast
+};
+
+static constexpr uint8_t AkCount = AkLast;
+
+// 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;
+
+// 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;
+ // Double reads are not reportable.
+
+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;
+};
+
+/// 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;
+};
+
+using utils::ExecutionVisitor;
+
+/// GlobalRWVisitor (global read write visitor) does all the traversals.
+class GlobalRWVisitor : public ExecutionVisitor<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(const Expr *E);
+
+ const llvm::SmallVector<TraversalAggregation> &getGlobalsFound() const;
+
+ const llvm::SmallVector<ObjectTraversalAggregation> &
+ getObjectGlobalsFound() const;
+
+ // RecursiveASTVisitor overrides
+ bool VisitDeclRefExpr(DeclRefExpr *S);
+ bool VisitUnaryOperator(UnaryOperator *Op);
+ bool VisitBinaryOperator(BinaryOperator *Op);
+ bool VisitCallExpr(CallExpr *CE);
+ bool VisitCXXConstructExpr(CXXConstructExpr *CE);
+ bool VisitMemberExpr(MemberExpr *ME);
+
+private:
+ void visitFunctionLikeExprArgs(const FunctionProtoType *FT,
+ CallExpr::const_arg_range Arguments);
+ void visitCallExprArgs(const CallExpr *CE);
+ void visitConstructExprArgs(const CXXConstructExpr *CE);
+
+ llvm::SmallVector<TraversalAggregation> GlobalsFound;
+ llvm::SmallVector<ObjectTraversalAggregation> ObjectGlobalsFound;
+
+ // 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;
+
+ // Same as the HandleMutableFunctionParametersAsWrites option.
+ bool IsWritePossibleThroughFunctionParam;
+
+ void addGlobal(DeclarationName Name, SourceLocation Loc, bool IsWrite);
+ void addGlobal(const DeclRefExpr *DR, bool IsWrite);
+ void addField(DeclarationName Name, FieldIndexArray FieldIndices,
+ SourceLocation Loc, bool IsWrite);
+ bool handleModified(const Expr *Modified);
+ bool handleModifiedVariable(const DeclRefExpr *DE);
+ bool handleAccessedObject(const Expr *E, bool IsWrite);
+ bool isVariable(const Expr *E);
+};
+
+AST_MATCHER_P(BinaryOperator, unsequencedBinaryOperator, const LangStandard *,
+ LangStd) {
+ assert(LangStd);
+
+ const BinaryOperator *Op = &Node;
+
+ 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;
+}
+} // namespace
+
+static bool isGlobalDecl(const VarDecl *VD) {
+ return VD && VD->hasGlobalStorage() && VD->getLocation().isValid() &&
+ !VD->getType().isConstQualified();
+}
+
+UnsequencedGlobalAccessesCheck::UnsequencedGlobalAccessesCheck(
+ StringRef Name, ClangTidyContext *Context)
+ : ClangTidyCheck(Name, Context),
+ HandleMutableFunctionParametersAsWrites(
+ Options.get("HandleMutableFunctionParametersAsWrites", false)) {}
+
+void UnsequencedGlobalAccessesCheck::storeOptions(
+ ClangTidyOptions::OptionMap &Opts) {
+ Options.store(Opts, "HandleMutableFunctionParametersAsWrites",
+ HandleMutableFunctionParametersAsWrites);
+}
+
+void UnsequencedGlobalAccessesCheck::registerMatchers(MatchFinder *Finder) {
+
+ const LangStandard *LangStd =
+ &LangStandard::getLangStandardForKind(getLangOpts().LangStd);
+
+ auto BinaryOperatorMatcher = unsequencedBinaryOperator(LangStd);
+
+ Finder->addMatcher(
+ stmt(traverse(TK_AsIs, binaryOperator(BinaryOperatorMatcher).bind("gw"))),
+ this);
+
+ // Array subscript expressions became sequenced in C++17
+ if (!LangStd->isCPlusPlus17())
+ Finder->addMatcher(stmt(traverse(TK_AsIs, arraySubscriptExpr().bind("gw"))),
+ this);
+
+ Finder->addMatcher(stmt(traverse(TK_AsIs, callExpr().bind("gw"))), this);
+
+ if (!LangStd->isCPlusPlus11())
+ Finder->addMatcher(stmt(traverse(TK_AsIs, initListExpr().bind("gw"))),
+ this);
+
+ Finder->addMatcher(stmt(traverse(TK_AsIs, cxxConstructExpr().bind("gw"))),
+ this);
+}
+
+void UnsequencedGlobalAccessesCheck::check(
+ const MatchFinder::MatchResult &Result) {
+ const Expr *E = Result.Nodes.getNodeAs<Expr>("gw");
+
+ 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)) {
+ // A CallExpr will include its defaulted arguments as well.
+ for (const Expr *Arg : CE->arguments()) {
+ // For some reason, calling TraverseStmt on Arg directly
+ // doesn't recurse when Arg is a default argument.
+ if (const auto *DA = dyn_cast<CXXDefaultArgExpr>(Arg)) {
+ Visitor.startTraversal(DA->getExpr());
+ continue;
+ }
+ Visitor.startTraversal(Arg);
+ }
+ } else if (const auto *AS = dyn_cast<ArraySubscriptExpr>(E)) {
+ Visitor.startTraversal(AS->getLHS());
+ Visitor.startTraversal(AS->getRHS());
+ } else if (const auto *IL = dyn_cast<InitListExpr>(E)) {
+ llvm::SmallVector<const InitListExpr *> NestedInitializers;
+ NestedInitializers.push_back(IL);
+ while (!NestedInitializers.empty()) {
+ const InitListExpr *CurrentIL =
+ NestedInitializers[NestedInitializers.size() - 1];
+ NestedInitializers.pop_back();
+
+ for (const auto *I : CurrentIL->inits()) {
+ if (const InitListExpr *Nested = dyn_cast<InitListExpr>(I)) {
+ NestedInitializers.push_back(Nested);
+ continue;
+ }
+
+ Visitor.startTraversal(I);
+ }
+ }
+ } else if (const auto *CE = dyn_cast<CXXConstructExpr>(E)) {
+ for (const Expr *Arg : CE->arguments()) {
+ // For some reason, calling TraverseStmt on Arg directly
+ // doesn't recurse when Arg is a default argument.
+ if (const auto *DA = dyn_cast<CXXDefaultArgExpr>(Arg)) {
+ Visitor.startTraversal(DA->getExpr());
+ continue;
+ }
+ Visitor.startTraversal(Arg);
+ }
+ }
+
+ const llvm::SmallVector<TraversalAggregation> &Globals =
+ Visitor.getGlobalsFound();
+
+ for (const TraversalAggregation& Global : Globals)
+ if (Global.shouldBeReported())
+ diag(E->getBeginLoc(), "read/write conflict on global variable " +
+ Global.getDeclName().getAsString());
+
+ const llvm::SmallVector<ObjectTraversalAggregation> &ObjectGlobals =
+ Visitor.getObjectGlobalsFound();
+
+ for (const ObjectTraversalAggregation& ObjectGlobal : ObjectGlobals)
+ if (ObjectGlobal.shouldBeReported())
+ diag(E->getBeginLoc(), "read/write conflict on the field of the global "
+ "object " +
+ ObjectGlobal.getDeclName().getAsString());
+}
+
+GlobalRWVisitor::GlobalRWVisitor(bool IsWritePossibleThroughFunctionParam)
+ : TraversalIndex(0),
+ IsWritePossibleThroughFunctionParam(IsWritePossibleThroughFunctionParam) {
+}
+
+void GlobalRWVisitor::startTraversal(const Expr *E) {
+ TraversalIndex++;
+ traverseExecution(const_cast<Expr *>(E));
+}
+
+bool GlobalRWVisitor::isVariable(const Expr *E) {
+ const Type *T = E->getType().getTypePtrOrNull();
+ if (!T)
+ return false;
+
+ return isa<DeclRefExpr>(E) && (!T->isRecordType() || T->isUnionType());
+}
+
+bool GlobalRWVisitor::VisitDeclRefExpr(DeclRefExpr *DR) {
+ const auto *VD = dyn_cast<VarDecl>(DR->getDecl());
+ if (!VD)
+ return true;
+
+ if (!isVariable(DR))
+ return handleAccessedObject(DR, /*IsWrite*/ false);
+
+ if (isGlobalDecl(VD)) {
+ addGlobal(VD->getDeclName(), VD->getBeginLoc(), /*IsWrite*/ false);
+ return true;
+ }
+ return true;
+}
+
+bool GlobalRWVisitor::VisitMemberExpr(MemberExpr *ME) {
+ return handleAccessedObject(ME, /*IsWrite*/ false);
+}
+
+bool GlobalRWVisitor::handleModifiedVariable(const DeclRefExpr *DR) {
+ const auto *VD = dyn_cast<VarDecl>(DR->getDecl());
+ if (!VD)
+ return true;
+
+ if (isGlobalDecl(VD)) {
+ addGlobal(VD->getDeclName(), VD->getBeginLoc(), /*IsWrite*/ true);
+ return false;
+ }
+
+ return true;
+}
+
+bool GlobalRWVisitor::handleAccessedObject(const Expr *E, bool IsWrite) {
+ 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++;
+ }
+
+ const DeclRefExpr *Base = dyn_cast<DeclRefExpr>(CurrentNode);
+ if (!Base)
+ return true;
+
+ const VarDecl *BaseDecl = dyn_cast<VarDecl>(Base->getDecl());
+ if (!BaseDecl)
+ return true;
+
+ 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);
+ return false;
+}
+
+bool GlobalRWVisitor::handleModified(const Expr *Modified) {
+ assert(Modified);
+
+ if (isVariable(Modified))
+ return handleModifiedVariable(dyn_cast<DeclRefExpr>(Modified));
+
+ return handleAccessedObject(Modified, /*IsWrite*/ true);
+}
+
+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());
+
+ // Ignore the AddressOf operator as it doesn't read the variable.
+ if (Code == UO_AddrOf && isa<DeclRefExpr>(Op->getSubExpr()))
+ return false;
+
+ return true;
+}
+
+bool GlobalRWVisitor::VisitBinaryOperator(BinaryOperator *Op) {
+ if (Op->isAssignmentOp())
+ return handleModified(Op->getLHS());
+
+ return true;
+}
+
+void GlobalRWVisitor::visitFunctionLikeExprArgs(
+ const FunctionProtoType *FT, CallExpr::const_arg_range Arguments) {
+
+ uint32_t I = 0;
+ auto ArgumentsEnd = Arguments.end();
+ for (auto It = Arguments.begin(); It != ArgumentsEnd; It++, I++) {
+ const Expr *Arg = *It;
+
+ if (I >= FT->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>(
+ FT->getParamType(I).getTypePtrOrNull())) {
+ if (PtrType->getPointeeType().isConstQualified())
+ continue;
+
+ if (handleModified(Op->getSubExpr()))
+ continue;
+ }
+ }
+
+ if (const auto *RefType = dyn_cast_if_present<ReferenceType>(
+ FT->getParamType(I).getTypePtrOrNull())) {
+ if (RefType->getPointeeType().isConstQualified())
+ continue;
+
+ if (handleModified(Arg))
+ continue;
+ }
+ }
+}
+
+void GlobalRWVisitor::visitCallExprArgs(const 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);
+ if (!ProtoType)
+ return;
+
+ visitFunctionLikeExprArgs(ProtoType, CE->arguments());
+}
+
+void GlobalRWVisitor::visitConstructExprArgs(const CXXConstructExpr *E) {
+ const FunctionDecl *Decl = E->getConstructor();
+ const Type *T = Decl->getType().getTypePtrOrNull();
+ if (!T)
+ return;
+
+ const auto *FT = dyn_cast<FunctionProtoType>(T);
+ if (!FT)
+ return;
+
+ visitFunctionLikeExprArgs(FT, E->arguments());
+}
+
+bool GlobalRWVisitor::VisitCallExpr(CallExpr *CE) {
+
+ if (IsWritePossibleThroughFunctionParam || isa<CXXOperatorCallExpr>(CE))
+ visitCallExprArgs(CE);
+
+ return ExecutionVisitor::VisitCallExpr(CE);
+}
+
+bool GlobalRWVisitor::VisitCXXConstructExpr(CXXConstructExpr *CE) {
+ if (IsWritePossibleThroughFunctionParam)
+ visitConstructExprArgs(CE);
+
+ return ExecutionVisitor::VisitCXXConstructExpr(CE);
+}
+
+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) {
+ AccessKind Access = IsWrite ? AkWrite : AkRead;
+
+ for (TraversalAggregation& Global : GlobalsFound) {
+ if (Global.getDeclName() == Name) {
+ Global.addGlobalRW(Loc, Access, TraversalIndex);
+ return;
+ }
+ }
+
+ GlobalsFound.emplace_back(Name, Loc, Access, TraversalIndex);
+}
+
+void GlobalRWVisitor::addField(DeclarationName Name,
+ FieldIndexArray FieldIndices, SourceLocation Loc,
+ bool IsWrite) {
+ AccessKind Access = IsWrite ? AkWrite : AkRead;
+
+ for (ObjectTraversalAggregation& ObjectGlobal : ObjectGlobalsFound) {
+ if (ObjectGlobal.getDeclName() == Name) {
+ ObjectGlobal.addFieldRW(Loc, FieldIndices, Access, TraversalIndex);
+ return;
+ }
+ }
+
+ ObjectGlobalsFound.emplace_back(Name, Loc, FieldIndices, Access,
+ TraversalIndex);
+}
+
+static TraversalResultKind akToTr(AccessKind Ak) { return 1 << Ak; }
+
+TraversalAggregation::TraversalAggregation() {}
+
+TraversalAggregation::TraversalAggregation(DeclarationName Name,
+ SourceLocation Loc,
+ AccessKind Access, int Index)
+ : DeclName(Name), MainPart(Index, Loc, Access) {}
+
+void TraversalAggregation::addGlobalRW(SourceLocation Loc, AccessKind Access,
+ int Index) {
+ if (!isValid()) {
+ MainPart = TraversalResult(Index, Loc, Access);
+ return;
+ }
+
+ if (MainPart.IndexCreated == Index) {
+ MainPart.addNewAccess(Loc, Access);
+ return;
+ }
+
+ if (!hasTwoAccesses()) {
+ OtherPart = TraversalResult(Index, Loc, Access);
+ return;
+ }
+
+ if (OtherPart.IndexCreated == Index) {
+ OtherPart.addNewAccess(Loc, Access);
+ return;
+ }
+
+ switch (Access) {
+ case AkWrite: {
+ if (OtherPart.Kind & (TrRead | TrWrite))
+ MainPart = OtherPart;
+
+ OtherPart = TraversalResult(Index, Loc, Access);
+ break;
+ }
+ case AkRead: {
+ if (!(MainPart.Kind & TrWrite) &&
+ (OtherPart.Kind & TrWrite))
+ MainPart = OtherPart;
+ OtherPart = TraversalResult(Index, Loc, Access);
+ break;
+ }
+ default: {
+ break;
+ }
+ }
+}
+
+bool TraversalAggregation::isValid() const {
+ return MainPart.Kind != TrInvalid;
+}
+
+DeclarationName TraversalAggregation::getDeclName() const { return DeclName; }
+
+bool TraversalAggregation::hasTwoAccesses() const {
+ return OtherPart.Kind != TrInvalid;
+}
+
+bool TraversalAggregation::hasConflictingOperations() const {
+ return hasTwoAccesses() &&
+ ((MainPart.Kind | OtherPart.Kind) & TrWrite);
+}
+
+bool TraversalAggregation::shouldBeReported() const {
+ return hasConflictingOperations();
+}
+
+TraversalResult::TraversalResult() : IndexCreated(-1), Kind(TrInvalid) {}
+
+TraversalResult::TraversalResult(int Index, SourceLocation Location,
+ AccessKind Access)
+ : IndexCreated(Index), Kind(akToTr(Access)) {
+ Loc[Access] = Location;
+}
+
+void TraversalResult::addNewAccess(SourceLocation NewLoc, AccessKind Access) {
+ Kind |= 1 << Access;
+ Loc[Access] = NewLoc;
+}
+
+ObjectTraversalAggregation::ObjectTraversalAggregation(
+ DeclarationName Name, SourceLocation Loc, FieldIndexArray FieldIndices,
+ AccessKind Access, int Index)
+ : DeclName(Name), AccessTree(TraversalAggregation()) {
+ addFieldRW(Loc, FieldIndices, Access, Index);
+}
+
+void ObjectTraversalAggregation::addFieldRW(SourceLocation Loc,
+ FieldIndexArray FieldIndices,
+ AccessKind Access, int Index) {
+ ObjectAccessTree *CurrentNode = &AccessTree;
+ for (FieldIndex FIndex : FieldIndices) {
+ bool IsUnion = (FIndex & FiUnion) != 0;
+ uint16_t FieldKey = FIndex & ~FiUnion;
+
+ ObjectAccessTree *PrevNode = CurrentNode;
+ ObjectAccessTree::FieldMap::iterator It =
+ CurrentNode->Fields.find(FieldKey);
+
+ if (It == CurrentNode->Fields.end()) {
+ CurrentNode =
+ new ObjectAccessTree(IsUnion ? CurrentNode->UnionTemporalAccesses
+ : CurrentNode->OwnAccesses);
+ PrevNode->Fields[FieldKey] =
+ std::unique_ptr<ObjectAccessTree>(CurrentNode);
+ } else {
+ CurrentNode = It->second.get();
+ }
+
+ if (IsUnion) {
+ if (!PrevNode->IsUnion) {
+ PrevNode->IsUnion = IsUnion; // Setting the parent of the
+ // field instead of the field
+ // itself.
+ PrevNode->UnionTemporalAccesses = PrevNode->OwnAccesses;
+ }
+ PrevNode->addFieldToAllExcept(FieldKey, Loc, Access, Index);
+ }
+ }
+ CurrentNode->addFieldToAll(Loc, Access, Index);
+}
+
+bool ObjectTraversalAggregation::shouldBeReported() const {
+ return shouldBeReportedRec(&AccessTree);
+}
+
+bool ObjectTraversalAggregation::shouldBeReportedRec(
+ const ObjectAccessTree *Node) const {
+ if (Node->OwnAccesses.hasConflictingOperations())
+ return true;
+
+ ObjectAccessTree::FieldMap::const_iterator FieldIt = Node->Fields.begin();
+ ObjectAccessTree::FieldMap::const_iterator FieldsEnd = Node->Fields.end();
+ for (; FieldIt != FieldsEnd; FieldIt++)
+ if (shouldBeReportedRec(FieldIt->second.get()))
+ return true;
+
+ return false;
+}
+
+DeclarationName ObjectTraversalAggregation::getDeclName() const {
+ return DeclName;
+}
+
+ObjectAccessTree::ObjectAccessTree(TraversalAggregation Own)
+ : OwnAccesses(Own), IsUnion(false) {}
+
+void ObjectAccessTree::addFieldToAll(SourceLocation Loc, AccessKind Access,
+ int Index) {
+ OwnAccesses.addGlobalRW(Loc, Access, Index);
+ UnionTemporalAccesses.addGlobalRW(Loc, Access, Index);
+
+ FieldMap::iterator FieldIt = Fields.begin();
+ FieldMap::iterator FieldsEnd = Fields.end();
+
+ for (; FieldIt != FieldsEnd; FieldIt++)
+ FieldIt->second->addFieldToAll(Loc, Access, Index);
+}
+
+void ObjectAccessTree::addFieldToAllExcept(uint16_t ExceptIndex,
+ SourceLocation Loc,
+ AccessKind Access, int Index) {
+
+ UnionTemporalAccesses.addGlobalRW(Loc, Access, Index);
+
+ FieldMap::const_iterator FieldIt = Fields.begin();
+ FieldMap::iterator FieldsEnd = Fields.end();
+
+ for (; FieldIt != FieldsEnd; FieldIt++)
+ if (FieldIt->first != ExceptIndex)
+ FieldIt->second->addFieldToAll(Loc, Access, Index);
+}
+
+} // namespace clang::tidy::bugprone
diff --git a/clang-tools-extra/clang-tidy/bugprone/UnsequencedGlobalAccessesCheck.h b/clang-tools-extra/clang-tidy/bugprone/UnsequencedGlobalAccessesCheck.h
new file mode 100644
index 0000000000000..64947efe4b306
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/bugprone/UnsequencedGlobalAccessesCheck.h
@@ -0,0 +1,35 @@
+//===--- UnsequencedGlobalAccessesCheck.h - clang-tidy ----------*- C++ -*-===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_UNSEQUENCEDGLOBALACCESSES\
+CHECK_H
+#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_UNSEQUENCEDGLOBALACCESSES\
+CHECK_H
+
+#include "../ClangTidyCheck.h"
+
+namespace clang::tidy::bugprone {
+
+/// Finds conflicting accesses on global variables.
+class UnsequencedGlobalAccessesCheck : public ClangTidyCheck {
+public:
+ UnsequencedGlobalAccessesCheck(StringRef Name, ClangTidyContext *Context);
+ void registerMatchers(ast_matchers::MatchFinder *Finder) override;
+ void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
+ void storeOptions(ClangTidyOptions::OptionMap &Opts) override;
+ bool isLanguageVersionSupported(const LangOptions &LangOpts) const override {
+ return LangOpts.CPlusPlus;
+ }
+
+private:
+ bool HandleMutableFunctionParametersAsWrites;
+};
+
+} // namespace clang::tidy::bugprone
+
+#endif
diff --git a/clang-tools-extra/clang-tidy/cert/CERTTidyModule.cpp b/clang-tools-extra/clang-tidy/cert/CERTTidyModule.cpp
index 6614b10d4ce40..135aae7e54df8 100644
--- a/clang-tools-extra/clang-tidy/cert/CERTTidyModule.cpp
+++ b/clang-tools-extra/clang-tidy/cert/CERTTidyModule.cpp
@@ -19,6 +19,7 @@
#include "../bugprone/SuspiciousMemoryComparisonCheck.h"
#include "../bugprone/UnhandledSelfAssignmentCheck.h"
#include "../bugprone/UnsafeFunctionsCheck.h"
+#include "../bugprone/UnsequencedGlobalAccessesCheck.h"
#include "../bugprone/UnusedReturnValueCheck.h"
#include "../concurrency/ThreadCanceltypeAsynchronousCheck.h"
#include "../google/UnnamedNamespaceInHeaderCheck.h"
@@ -261,6 +262,9 @@ class CERTModule : public ClangTidyModule {
CheckFactories.registerCheck<ThrownExceptionTypeCheck>("cert-err60-cpp");
CheckFactories.registerCheck<misc::ThrowByValueCatchByReferenceCheck>(
"cert-err61-cpp");
+ // EXP
+ CheckFactories.registerCheck<bugprone::UnsequencedGlobalAccessesCheck>(
+ "cert-exp50-cpp");
// MEM
CheckFactories.registerCheck<DefaultOperatorNewAlignmentCheck>(
"cert-mem57-cpp");
@@ -299,6 +303,8 @@ class CERTModule : public ClangTidyModule {
"cert-err33-c");
CheckFactories.registerCheck<StrToNumCheck>("cert-err34-c");
// EXP
+ CheckFactories.registerCheck<bugprone::UnsequencedGlobalAccessesCheck>(
+ "cert-exp30-c");
CheckFactories.registerCheck<bugprone::SuspiciousMemoryComparisonCheck>(
"cert-exp42-c");
// FLP
diff --git a/clang-tools-extra/clang-tidy/utils/ExecutionVisitor.h b/clang-tools-extra/clang-tidy/utils/ExecutionVisitor.h
new file mode 100644
index 0000000000000..992330d6f0a77
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/utils/ExecutionVisitor.h
@@ -0,0 +1,195 @@
+//===--- ExecutionVisitor.h - clang-tidy ------------------------*- C++ -*-===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_UTILS_EXECUTIONVISITOR_H
+#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_UTILS_EXECUTIONVISITOR_H
+
+#include "clang/AST/RecursiveASTVisitor.h"
+
+namespace clang::tidy::utils {
+
+/// Helper class that can be used to traverse all statements (including
+/// expressions) that can execute while executing a given statement.
+template <typename T> class ExecutionVisitor : public RecursiveASTVisitor<T> {
+public:
+ ExecutionVisitor() : IsInFunction(false) {}
+
+protected:
+ void traverseExecution(Stmt *S) {
+ FunctionsToBeChecked.clear();
+ IsInFunction = false;
+ RecursiveASTVisitor<T>::TraverseStmt(S);
+
+ // We keep a list of functions to be checked during traversal so that they
+ // are not checked multiple times. If this weren't the case, we would get
+ // infinite recursion on recursive functions.
+ traverseFunctionsToBeChecked();
+ }
+
+ bool isInFunction() const { return IsInFunction; }
+
+ void checkFunctionLater(const FunctionDecl *FD) {
+ if (!FD->hasBody())
+ return;
+
+ for (const FunctionDecl *Fun : FunctionsToBeChecked)
+ if (Fun->getDeclName() == FD->getDeclName())
+ return;
+
+ FunctionsToBeChecked.push_back(FD);
+ }
+
+ void checkDestructorLater(const CXXRecordDecl *D) {
+ if (!D->hasDefinition() || D->hasIrrelevantDestructor())
+ return;
+
+ const CXXMethodDecl *Destructor = D->getDestructor();
+ checkFunctionLater(static_cast<const FunctionDecl *>(Destructor));
+
+ // We recurse into struct/class members and base classes, as their
+ // destructors will run as well.
+
+ for (const FieldDecl *F : D->fields()) {
+ const Type *FieldType = F->getType().getTypePtrOrNull();
+ if (!FieldType) {
+ continue;
+ }
+
+ const CXXRecordDecl *FieldRecordDecl = FieldType->getAsCXXRecordDecl();
+ if (!FieldRecordDecl)
+ continue;
+
+ checkDestructorLater(FieldRecordDecl);
+ }
+
+ for (const CXXBaseSpecifier Base : D->bases()) {
+ const Type *BaseType = Base.getType().getTypePtrOrNull();
+ if (!BaseType)
+ continue;
+
+ const CXXRecordDecl *BaseRecordDecl = BaseType->getAsCXXRecordDecl();
+ if (!BaseRecordDecl)
+ continue;
+
+ checkDestructorLater(BaseRecordDecl);
+ }
+ }
+
+public:
+ bool VisitCallExpr(CallExpr *CE) {
+ if (!isa_and_nonnull<FunctionDecl>(CE->getCalleeDecl()))
+ return true;
+
+ const auto *FD = dyn_cast<FunctionDecl>(CE->getCalleeDecl());
+
+ if (const auto *DD = dyn_cast<CXXDestructorDecl>(FD)) {
+ const CXXRecordDecl *Parent = DD->getParent();
+ checkDestructorLater(Parent);
+ return true;
+ }
+
+ checkFunctionLater(FD);
+ return true;
+ }
+
+ bool VisitVarDecl(VarDecl *VD) {
+ if (VD->isStaticLocal())
+ return true;
+
+ const Type *DT = VD->getType().getTypePtrOrNull();
+ if (!DT)
+ return true;
+
+ const CXXRecordDecl *DeleteDecl = DT->getAsCXXRecordDecl();
+ if (!DeleteDecl)
+ return true;
+
+ checkDestructorLater(DeleteDecl);
+ return true;
+ }
+
+ bool VisitCXXConstructExpr(CXXConstructExpr *CE) {
+ const CXXConstructorDecl *CD = CE->getConstructor();
+
+ checkFunctionLater(static_cast<const FunctionDecl *>(CD));
+
+ // If we are traversing a function, then all the temporary and non-temporary
+ // objects will have their destructor called at the end of the scope. So
+ // better traverse the destructors as well.
+ if (isInFunction()) {
+ const CXXRecordDecl *RD = CD->getParent();
+ checkDestructorLater(RD);
+ }
+ return true;
+ }
+
+ bool VisitCXXDeleteExpr(CXXDeleteExpr *DE) {
+ const Type *DeleteType = DE->getDestroyedType().getTypePtrOrNull();
+ if (!DeleteType)
+ return true;
+
+ const CXXRecordDecl *DeleteDecl = DeleteType->getAsCXXRecordDecl();
+ if (!DeleteDecl)
+ return true;
+
+ checkDestructorLater(DeleteDecl);
+ return true;
+ }
+
+ bool VisitUnaryExprOrTypeTraitExpr(UnaryExprOrTypeTraitExpr *UE) {
+ return false;
+ }
+ bool VisitOffsetOfExpr(OffsetOfExpr *OE) { return false; }
+
+private:
+ void 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) {
+ const FunctionDecl *Func = FunctionsToBeChecked[I];
+
+ if (const auto *Constructor = dyn_cast<CXXConstructorDecl>(Func))
+ for (const CXXCtorInitializer *Init : Constructor->inits())
+ RecursiveASTVisitor<T>::TraverseStmt(Init->getInit());
+
+ // Look at the function parameters as well. They get destroyed at the end
+ // of the scope.
+ for (const ParmVarDecl *Param : Func->parameters()) {
+ const Type *ParamType = Param->getType().getTypePtrOrNull();
+ if (!ParamType)
+ continue;
+
+ const CXXRecordDecl *TypeDecl = ParamType->getAsCXXRecordDecl();
+ if (!TypeDecl)
+ continue;
+
+ checkDestructorLater(TypeDecl);
+ }
+
+ // The hasBody check should happen before we add the function to the
+ // array.
+ assert(Func->hasBody());
+ RecursiveASTVisitor<T>::TraverseStmt(Func->getBody());
+ }
+ }
+
+ // Will be true if we are traversing function/constructor/destructor bodies
+ // that can be called from the original starting point of the traversal.
+ bool IsInFunction;
+
+ // We check inside functions only if the functions hasn't already been checked
+ // during the current traversal. We use this array to check if the function is
+ // already registered to be checked.
+ llvm::SmallVector<const FunctionDecl *> FunctionsToBeChecked;
+};
+
+} // namespace clang::tidy::utils
+
+#endif
diff --git a/clang-tools-extra/docs/ReleaseNotes.rst b/clang-tools-extra/docs/ReleaseNotes.rst
index 882ee0015df17..b65b547319b0b 100644
--- a/clang-tools-extra/docs/ReleaseNotes.rst
+++ b/clang-tools-extra/docs/ReleaseNotes.rst
@@ -130,6 +130,12 @@ New checks
Finds setter-like member functions that take a pointer parameter and set a
reference member of the same class with the pointed value.
+- New :doc: `bugprone-unsequenced-global-accesses
+ <clang-tidy/checks/bugprone/unsequenced-global-accesses>` check.
+
+ Finds unsequenced actions (i.e. unsequenced write and read/write)
+ on global variables nested in functions in the same translation unit.
+
- New :doc:`bugprone-unintended-char-ostream-output
<clang-tidy/checks/bugprone/unintended-char-ostream-output>` check.
@@ -151,6 +157,14 @@ New checks
New check aliases
^^^^^^^^^^^^^^^^^
+- New `cert-exp30-c <cert/exp30-c>` alias for
+ `bugprone-unsequenced-global-accesses
+ <clang-tidy/checks/bugprone/unsequenced-global-accesses>`.
+
+- New `cert-exp50-cpp <cert/exp50-cpp>` alias for
+ `bugprone-unsequenced-global-accesses
+ <clang-tidy/checks/bugprone/unsequenced-global-accesses>`.
+
Changes in existing checks
^^^^^^^^^^^^^^^^^^^^^^^^^^
diff --git a/clang-tools-extra/docs/clang-tidy/checks/bugprone/unsequenced-global-accesses.rst b/clang-tools-extra/docs/clang-tidy/checks/bugprone/unsequenced-global-accesses.rst
new file mode 100644
index 0000000000000..fa2730452825b
--- /dev/null
+++ b/clang-tools-extra/docs/clang-tidy/checks/bugprone/unsequenced-global-accesses.rst
@@ -0,0 +1,86 @@
+.. title:: clang-tidy - bugprone-unsequenced-global-accesses
+
+bugprone-unsequenced-global-accesses
+====================================
+
+Finds unsequenced actions (i.e. unsequenced write and read/write)
+on global variables nested in functions in the same translation unit.
+
+Modifying twice or reading and modifying a memory location without a
+defined sequence of the operations is either undefined behavior or has
+unspecified order. This check is similar to the `-Wunsequenced` Clang warning,
+however it only looks at global variables and therefore can find unsequenced
+actions recursively inside function calls as well.
+
+For example code-block:: c++
+
+ int a = 0;
+ int b = (a++) - a; // This is flagged by -Wunsequenced.
+
+However global variables allow for more complex scenarios that
+`-Wunsequenced` doesn't detect. E.g. code-block:: c++
+
+ int globalVar = 0;
+
+ int incFun() {
+ globalVar++;
+ return globalVar;
+ }
+
+ int main() {
+ return globalVar + incFun(); // This is not detected by -Wunsequenced.
+ }
+
+This check attempts to detect such cases. It recurses into functions that are
+inside the same translation unit. The flagged cases can overlap with
+`-Wunsequenced`. Global unions and structs are also handled.
+For example code-block:: c++
+
+ typedef struct {
+ int A;
+ float B;
+ } IntAndFloat;
+
+ IntAndFloat GlobalIF;
+
+ int globalIFGetSum() {
+ int sum = GlobalIF.A + (int)GlobalIF.B;
+ GlobalIF = (IntAndFloat){};
+ return sum;
+ }
+
+ int main() {
+ // The following printf could give different results on different
+ // compilers.
+ printf("sum: %i, int: %i", globalIFGetSum(), GlobalIF.A);
+ }
+
+Options
+~~~~~~~
+
+.. option:: HandleMutableFunctionParametersAsWrites
+
+When `true`, treat function calls with mutable reference or pointer parameters
+as writes to the parameter.
+
+The default value is `false`.
+
+For example, the following code block will get flagged if
+`HandleMutableFunctionParametersAsWrites` is `true` code-block:: c++
+
+ void func(int& a);
+ int globalVar;
+
+ int main() {
+ int a = globalVar + func(globalVar);
+ // func could write to globalVar here
+ }
+
+When `HandleMutableFunctionParametersAsWrites` is set to `true`, the
+``func(globalVar)`` call is treated as a write to `globalVar`. Because no
+sequencing is defined for the `+` operator, a write to `globalVar`
+inside `func`, would be undefined behavior.
+
+When `HandleMutableFunctionParametersAsWrites` is set to `false` the expression
+does not get flagged as the call expression is only treated as a read from
+`globalVar`.
diff --git a/clang-tools-extra/docs/clang-tidy/checks/cert/exp30-c.rst b/clang-tools-extra/docs/clang-tidy/checks/cert/exp30-c.rst
new file mode 100644
index 0000000000000..3854b6596e2cc
--- /dev/null
+++ b/clang-tools-extra/docs/clang-tidy/checks/cert/exp30-c.rst
@@ -0,0 +1,10 @@
+.. title:: clang-tidy - cert-exp30-c
+.. meta::
+ :http-equiv=refresh: 5;URL=../bugprone/unsequenced-global-accesses.html
+
+cert-exp30-c
+==============
+
+The `cert-exp30-c` check is an alias, please see
+:doc:`bugprone-unsequenced-global-accesses <../bugprone/unsequenced-global-accesses>`
+for more information.
diff --git a/clang-tools-extra/docs/clang-tidy/checks/cert/exp50-cpp.rst b/clang-tools-extra/docs/clang-tidy/checks/cert/exp50-cpp.rst
new file mode 100644
index 0000000000000..342b29a26ed98
--- /dev/null
+++ b/clang-tools-extra/docs/clang-tidy/checks/cert/exp50-cpp.rst
@@ -0,0 +1,10 @@
+.. title:: clang-tidy - cert-exp50-cpp
+.. meta::
+ :http-equiv=refresh: 5;URL=../bugprone/unsequenced-global-accesses.html
+
+cert-con54-cpp
+==============
+
+The `cert-exp50-cpp` check is an alias, please see
+:doc:`bugprone-unsequenced-global-accesses <../bugprone/unsequenced-global-accesses>`
+for more information.
diff --git a/clang-tools-extra/docs/clang-tidy/checks/list.rst b/clang-tools-extra/docs/clang-tidy/checks/list.rst
index 5a79d61b1fd7e..ff689402615ce 100644
--- a/clang-tools-extra/docs/clang-tidy/checks/list.rst
+++ b/clang-tools-extra/docs/clang-tidy/checks/list.rst
@@ -87,6 +87,7 @@ Clang-Tidy Checks
:doc:`bugprone-capturing-this-in-member-variable <bugprone/capturing-this-in-member-variable>`,
:doc:`bugprone-casting-through-void <bugprone/casting-through-void>`,
:doc:`bugprone-chained-comparison <bugprone/chained-comparison>`,
+ :doc:`bugprone-conflicting-global-accesses <bugprone/conflicting-global-accesses>`,
:doc:`bugprone-compare-pointer-to-member-virtual-function <bugprone/compare-pointer-to-member-virtual-function>`,
:doc:`bugprone-copy-constructor-init <bugprone/copy-constructor-init>`, "Yes"
:doc:`bugprone-crtp-constructor-accessibility <bugprone/crtp-constructor-accessibility>`, "Yes"
@@ -173,9 +174,11 @@ Clang-Tidy Checks
:doc:`cert-env33-c <cert/env33-c>`,
:doc:`cert-err33-c <cert/err33-c>`,
:doc:`cert-err34-c <cert/err34-c>`,
+ :doc:`cert-exp30-c <cert/exp30-c>`,
:doc:`cert-err52-cpp <cert/err52-cpp>`,
:doc:`cert-err58-cpp <cert/err58-cpp>`,
:doc:`cert-err60-cpp <cert/err60-cpp>`,
+ :doc:`cert-exp50-cpp <cert/exp50-cpp>`,
:doc:`cert-flp30-c <cert/flp30-c>`,
:doc:`cert-mem57-cpp <cert/mem57-cpp>`,
:doc:`cert-msc50-cpp <cert/msc50-cpp>`,
diff --git a/clang-tools-extra/test/clang-tidy/checkers/bugprone/unsequenced-global-accesses.cpp b/clang-tools-extra/test/clang-tidy/checkers/bugprone/unsequenced-global-accesses.cpp
new file mode 100644
index 0000000000000..901b19a6fb5a7
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/checkers/bugprone/unsequenced-global-accesses.cpp
@@ -0,0 +1,601 @@
+// RUN: %check_clang_tidy -std=c++98 -check-suffixes=,PRE-CPP11,PRE-CPP17 %s bugprone-unsequenced-global-accesses %t
+// RUN: %check_clang_tidy -std=c++11,c++14 -check-suffixes=,POST-CPP11,PRE-CPP17 %s bugprone-unsequenced-global-accesses %t
+// RUN: %check_clang_tidy -std=c++17-or-later -check-suffixes=,POST-CPP11 %s bugprone-unsequenced-global-accesses %t
+// RUN: %check_clang_tidy -std=c++17-or-later -check-suffixes=,POST-CPP11,PARAM %s bugprone-unsequenced-global-accesses %t -config="{CheckOptions: {bugprone-unsequenced-global-accesses.HandleMutableFunctionParametersAsWrites: true}}"
+
+#if __cplusplus > 199711L
+ // Used to exclude code that would give compiler errors on older standards.
+ #define POST_CPP11
+#endif
+
+int GlobalVarA;
+
+int incGlobalVarA(void) {
+ GlobalVarA++;
+ return 0;
+}
+
+int getGlobalVarA(void) {
+ return GlobalVarA;
+}
+
+int undefinedFunc1(int);
+
+int testFunc1(void) {
+
+ int B = getGlobalVarA() + incGlobalVarA();
+ // CHECK-MESSAGES: :[[@LINE-1]]:13: warning: read/write conflict on global variable GlobalVarA
+ (void)B;
+
+ return GlobalVarA + incGlobalVarA();
+ // CHECK-MESSAGES: :[[@LINE-1]]:12: warning: read/write conflict on global variable GlobalVarA
+
+ return GlobalVarA + undefinedFunc1(incGlobalVarA());
+ // CHECK-MESSAGES: :[[@LINE-1]]:12: warning: read/write conflict on global variable GlobalVarA
+
+}
+
+int addAll(int A, int B, int C, int D) {
+ return A + B + C + D;
+}
+
+int testFunc2(void) {
+ int B;
+ (void)B;
+ // Make sure the order does not affect the outcome
+
+ B = getGlobalVarA() + (GlobalVarA++);
+ // CHECK-MESSAGES: :[[@LINE-1]]:9: warning: read/write conflict on global variable GlobalVarA
+
+ B = (GlobalVarA++) + getGlobalVarA();
+ // CHECK-MESSAGES: :[[@LINE-1]]:9: warning: read/write conflict on global variable GlobalVarA
+
+ B = incGlobalVarA() + GlobalVarA;
+ // CHECK-MESSAGES: :[[@LINE-1]]:9: warning: read/write conflict on global variable GlobalVarA
+
+ B = addAll(GlobalVarA++, getGlobalVarA(), 0, 0);
+ // CHECK-MESSAGES: :[[@LINE-1]]:9: warning: read/write conflict on global variable GlobalVarA
+
+ B = addAll(getGlobalVarA(), GlobalVarA++, 0, 0);
+ // CHECK-MESSAGES: :[[@LINE-1]]:9: warning: read/write conflict on global variable GlobalVarA
+
+ // This is already checked by the unsequenced clang warning, so we don't
+ // want to warn about this.
+ return GlobalVarA + (++GlobalVarA);
+ // CHECK-MESSAGES: :[[@LINE-1]]:12: warning: read/write conflict on global variable GlobalVarA
+}
+
+int testFunc3(void) {
+
+ // Make sure double reads are not flagged
+ int B = GlobalVarA + GlobalVarA; (void)B;
+ B = GlobalVarA + getGlobalVarA();
+
+ return GlobalVarA - GlobalVarA;
+}
+
+bool testFunc4(void) {
+
+ // Make sure || and && operators are not flagged
+ bool B = GlobalVarA || (GlobalVarA++);
+ if(GlobalVarA && (GlobalVarA--)) {
+
+ B = GlobalVarA || (GlobalVarA++) + getGlobalVarA();
+ // CHECK-MESSAGES: :[[@LINE-1]]:27: warning: read/write conflict on global variable GlobalVarA
+
+ return (++GlobalVarA) || B || getGlobalVarA();
+ }
+
+ int C = GlobalVarA << incGlobalVarA(); (void)C;
+ // CHECK-MESSAGES-PRE-CPP17: :[[@LINE-1]]:13: warning: read/write conflict on global variable GlobalVarA
+
+ return false;
+}
+
+int incArg(int& P) {
+ P++;
+ return 0;
+}
+
+int incArgPtr(int* P) {
+ (*P)++;
+ return 0;
+}
+
+int incAndAddFn(int* A, int B) {
+ return (*A)++ + B;
+}
+
+int incAndAddBothPtrFn(int *A, int* B) {
+ return (*A)++ + (*B)++;
+}
+
+int testFunc5() {
+
+ // Also check if statements
+
+ if(GlobalVarA > incGlobalVarA()) {
+ // CHECK-MESSAGES: :[[@LINE-1]]:8: warning: read/write conflict on global variable GlobalVarA
+
+ return 1;
+ }
+
+ if(addAll(GlobalVarA, incArg(GlobalVarA), 0, 0)) {
+ // CHECK-MESSAGES-PARAM: :[[@LINE-1]]:8: warning: read/write conflict on global variable GlobalVarA
+ return 1;
+ }
+
+ if(addAll(GlobalVarA, incArgPtr(&GlobalVarA), 0, 0)) {
+ // CHECK-MESSAGES-PARAM: :[[@LINE-1]]:8: warning: read/write conflict on global variable GlobalVarA
+ return 2;
+ }
+
+ if(addAll(GlobalVarA, 0, incGlobalVarA(), 0)) {
+ // CHECK-MESSAGES: :[[@LINE-1]]:8: warning: read/write conflict on global variable GlobalVarA
+ return 2;
+ }
+
+ // Shouldn't warn here, as the value gets copied before the
+ // addition/increment happens.
+ int C = incAndAddFn(&GlobalVarA, GlobalVarA); (void)C;
+
+ // -Wunsequenced doesn't warn here. Neither do we. Not sure if we should
+ // cover this case.
+ incAndAddBothPtrFn(&GlobalVarA, &GlobalVarA);
+
+ return 0;
+}
+
+void *memset(void* S, int C, unsigned int N);
+int* GlobalPtrA;
+
+int* incGlobalPtrA() {
+ GlobalPtrA++;
+ return GlobalPtrA;
+}
+
+typedef struct TwoStringStruct {
+ char* A;
+ char* B;
+} TwoStringStruct;
+
+TwoStringStruct* TwoStringPtr;
+
+struct TwoStringStruct* incTwoStringPtr() {
+ TwoStringPtr++;
+ return TwoStringPtr;
+}
+
+int testFunc6() {
+
+ // Shouldn't warn here as the write takes place after the expression is
+ // evaluated.
+ GlobalVarA = GlobalVarA + 1;
+ GlobalVarA = incGlobalVarA();
+
+ // Also check the assignment expression, array element assignment, and
+ // pointer dereference lvalues.
+ int A = (GlobalVarA = 1) + incGlobalVarA(); (void)A;
+ // CHECK-MESSAGES: :[[@LINE-1]]:13: warning: read/write conflict on global variable GlobalVarA
+
+ int Array[] = {1, 2, 3};
+ Array[GlobalVarA] = incGlobalVarA();
+ // CHECK-MESSAGES-PRE-CPP17: :[[@LINE-1]]:5: warning: read/write conflict on global variable GlobalVarA
+
+ *(Array + GlobalVarA) = incGlobalVarA();
+ // CHECK-MESSAGES-PRE-CPP17: :[[@LINE-1]]:5: warning: read/write conflict on global variable GlobalVarA
+
+ *(Array + GlobalVarA) = getGlobalVarA();
+ // This is fine
+
+ // Should also check the array subscript operator
+
+ int B = (Array + GlobalVarA)[incGlobalVarA()]; (void)B;
+ // CHECK-MESSAGES-PRE-CPP17: :[[@LINE-1]]:13: warning: read/write conflict on global variable GlobalVarA
+
+ int C = (Array + GlobalVarA)[getGlobalVarA()]; (void)C;
+ // This is also fine
+
+ // Shouldn't warn here as the clang warning takes care of it.
+ return addAll(GlobalVarA, getGlobalVarA(), GlobalVarA++, 0);
+ // CHECK-MESSAGES: :[[@LINE-1]]:12: warning: read/write conflict on global variable GlobalVarA
+
+ // Shouldn't warn here as the ampersand operator doesn't read the variable.
+ return addAll(&GlobalVarA == &A ? 1 : 0, 1, incGlobalVarA(), 0);
+
+ memset(incGlobalPtrA(), 0, *GlobalPtrA);
+ // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: read/write conflict on global variable GlobalPtrA
+
+ // Shouldn't warn here as sizeof doesn't read the value.
+ memset(incGlobalPtrA(), 0, sizeof(*GlobalPtrA));
+
+ memset(incTwoStringPtr(), 0, (int)TwoStringPtr->A[0]);
+ // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: read/write conflict on global variable TwoStringPtr
+}
+
+class TestClass1 {
+public:
+ static int StaticVar1;
+
+ int incStaticVar1() {
+ StaticVar1++;
+ return 0;
+ }
+
+ int getStaticVar1() {
+ return StaticVar1;
+ }
+
+ int testClass1MemberFunc1() {
+
+ return incStaticVar1() + getStaticVar1();
+ // CHECK-MESSAGES: :[[@LINE-1]]:16: warning: read/write conflict on global variable StaticVar1
+
+ }
+
+ TestClass1 operator++() {
+ incStaticVar1();
+ return *this;
+ }
+
+ operator int() {
+ return StaticVar1;
+ }
+
+ int operator[](int N) {
+ return N;
+ }
+};
+
+void testFunc7() {
+ TestClass1 Obj;
+ addAll(TestClass1::StaticVar1, Obj.incStaticVar1(), 0, 0);
+ // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: read/write conflict on global variable StaticVar1
+ addAll(TestClass1::StaticVar1, (Obj.incStaticVar1()), 0, 0);
+ // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: read/write conflict on global variable StaticVar1
+ addAll(TestClass1::StaticVar1, (Obj.incStaticVar1(), 0), 0, 0);
+ // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: read/write conflict on global variable StaticVar1
+ addAll(TestClass1::StaticVar1, ++Obj, 0, 0);
+ // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: read/write conflict on global variable StaticVar1
+
+ TestClass1 Objects[3];
+ int A = (Objects + Objects[0].getStaticVar1())[TestClass1::StaticVar1++]; (void)A;
+ // CHECK-MESSAGES-PRE-CPP17: :[[@LINE-1]]:13: warning: read/write conflict on global variable StaticVar1
+}
+
+struct {
+ int VarA;
+ int VarB;
+ struct {
+ int VarC;
+ int VarD;
+ } StructA;
+} GlobalStruct;
+
+struct {
+ int VarA;
+ union {
+ struct {
+ int VarB;
+ int VarC;
+ } StructA;
+ int VarD;
+ } UnionA;
+ int VarE;
+} ComplexGlobalStruct;
+
+struct QuiteComplexStruct {
+ int VarA;
+ union {
+ union {
+ int VarB;
+ int VarC;
+ struct QuiteComplexStruct* PtrA;
+ } UnionB;
+ int VarD;
+ } UnionA;
+ int VarE;
+} QuiteComplexGlobalStruct;
+
+union {
+ int VarA;
+ struct {
+ int VarB, VarC;
+ } StructA;
+} GlobalUnion;
+
+
+void testFunc8() {
+
+ // Check if unions and structs are handled properly
+
+ addAll(GlobalStruct.VarA, GlobalStruct.VarB++, 0, 0);
+ addAll(GlobalStruct.StructA.VarD, GlobalStruct.VarA++, 0, 0);
+ addAll(GlobalStruct.StructA.VarC, GlobalStruct.StructA.VarD++, GlobalStruct.VarB++, GlobalStruct.VarA++);
+
+ addAll(GlobalStruct.VarA, (GlobalStruct.VarA++, 0), 0, 0);
+ // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: read/write conflict on the field of the global object GlobalStruct
+ addAll(ComplexGlobalStruct.UnionA.VarD, ComplexGlobalStruct.UnionA.StructA.VarC++, 0, 0);
+ // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: read/write conflict on the field of the global object ComplexGlobalStruct
+ addAll(ComplexGlobalStruct.UnionA.StructA.VarB, ComplexGlobalStruct.UnionA.VarD++, 0, 0);
+ // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: read/write conflict on the field of the global object ComplexGlobalStruct
+
+ addAll(ComplexGlobalStruct.UnionA.StructA.VarB, ComplexGlobalStruct.UnionA.StructA.VarC++, 0, 0);
+
+ addAll(QuiteComplexGlobalStruct.UnionA.UnionB.VarC, QuiteComplexGlobalStruct.UnionA.VarD++, 0, 0);
+ // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: read/write conflict on the field of the global object QuiteComplexGlobalStruct
+ addAll(QuiteComplexGlobalStruct.UnionA.UnionB.VarC, QuiteComplexGlobalStruct.UnionA.UnionB.VarB++, 0, 0);
+ // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: read/write conflict on the field of the global object QuiteComplexGlobalStruct
+
+ addAll(QuiteComplexGlobalStruct.UnionA.UnionB.VarC, QuiteComplexGlobalStruct.UnionA.UnionB.VarB++, 0, 0);
+ // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: read/write conflict on the field of the global object QuiteComplexGlobalStruct
+
+ addAll(QuiteComplexGlobalStruct.UnionA.UnionB.PtrA->VarA, QuiteComplexGlobalStruct.UnionA.UnionB.VarB++, 0, 0);
+ // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: read/write conflict on the field of the global object QuiteComplexGlobalStruct
+ addAll(QuiteComplexGlobalStruct.UnionA.UnionB.PtrA->VarA, QuiteComplexGlobalStruct.VarA++, 0, 0);
+
+ addAll(QuiteComplexGlobalStruct.UnionA.UnionB.PtrA->VarA, (long)QuiteComplexGlobalStruct.UnionA.UnionB.PtrA++, 0, 0);
+ // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: read/write conflict on the field of the global object QuiteComplexGlobalStruct
+
+ addAll(GlobalUnion.VarA, 0, GlobalUnion.StructA.VarB++, 0);
+ // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: read/write conflict on the field of the global object GlobalUnion
+
+#ifdef POST_CPP11
+ addAll(GlobalStruct.StructA.VarD, (GlobalStruct.StructA = {}, 0), 0, 0);
+ // CHECK-MESSAGES-POST-CPP11: :[[@LINE-1]]:5: warning: read/write conflict on the field of the global object GlobalStruct
+ addAll(GlobalStruct.StructA.VarC, (GlobalStruct = {}, 0), 0, 0);
+ // CHECK-MESSAGES-POST-CPP11: :[[@LINE-1]]:5: warning: read/write conflict on the field of the global object GlobalStruct
+
+ addAll(GlobalStruct.VarA, (GlobalStruct.StructA = {}, 0), 0, 0);
+
+ addAll((GlobalStruct.StructA = {}, 1), (GlobalStruct = {}, 0), 0, 0);
+ // CHECK-MESSAGES-POST-CPP11: :[[@LINE-1]]:5: warning: read/write conflict on the field of the global object GlobalStruct
+ addAll((GlobalStruct.StructA = {}, 1), (GlobalStruct.VarA++, 0), GlobalStruct.StructA.VarD, 0);
+ // CHECK-MESSAGES-POST-CPP11: :[[@LINE-1]]:5: warning: read/write conflict on the field of the global object GlobalStruct
+
+ addAll((ComplexGlobalStruct.UnionA = {}, 0), ComplexGlobalStruct.UnionA.VarD++, 0, 0);
+ // CHECK-MESSAGES-POST-CPP11: :[[@LINE-1]]:5: warning: read/write conflict on the field of the global object ComplexGlobalStruct
+
+ addAll(ComplexGlobalStruct.UnionA.StructA.VarB, (ComplexGlobalStruct.UnionA.StructA = {}, 0), 0, 0);
+ // CHECK-MESSAGES-POST-CPP11: :[[@LINE-1]]:5: warning: read/write conflict on the field of the global object ComplexGlobalStruct
+#endif
+}
+
+
+int GlobalVarB;
+
+int incGlobalVarB() {
+ ++GlobalVarB;
+ return GlobalVarB;
+}
+
+struct TwoValue {
+ int A, B;
+};
+
+// Check initializers
+void testFunc9() {
+ int Arr[] = { incGlobalVarB(), incGlobalVarB() }; (void)Arr;
+ // CHECK-MESSAGES-PRE-CPP11: :[[@LINE-1]]:17: warning: read/write conflict on global variable GlobalVarB
+
+ TwoValue Ts1 = { incGlobalVarB(), GlobalVarB }; (void)Ts1;
+ // CHECK-MESSAGES-PRE-CPP11: :[[@LINE-1]]:20: warning: read/write conflict on global variable GlobalVarB
+
+ Ts1 = (TwoValue){ GlobalVarB, incGlobalVarB() };
+ // CHECK-MESSAGES-PRE-CPP11: :[[@LINE-1]]:21: warning: read/write conflict on global variable GlobalVarB
+
+ TwoValue TsArr[] = { { incGlobalVarB(), 0 }, { 0, GlobalVarB } }; (void)TsArr;
+ // CHECK-MESSAGES-PRE-CPP11: :[[@LINE-1]]:24: warning: read/write conflict on global variable GlobalVarB
+
+ TwoValue TsArr2[4] = { [1].A = incGlobalVarB(), [3] = { .B = 0, .A = GlobalVarB } }; (void)TsArr2;
+ // CHECK-MESSAGES-PRE-CPP11: :[[@LINE-1]]:26: warning: read/write conflict on global variable GlobalVarB
+
+ TwoValue Ts2 = { .A = incGlobalVarB(), .B = GlobalVarB }; (void)Ts2;
+ // CHECK-MESSAGES-PRE-CPP11: :[[@LINE-1]]:20: warning: read/write conflict on global variable GlobalVarB
+}
+
+class InstanceCountedClass {
+public:
+ static int InstanceCount;
+ InstanceCountedClass() {
+ InstanceCount++;
+ }
+
+ ~InstanceCountedClass() {
+ InstanceCount--;
+ }
+};
+
+int InstanceCountedClass::InstanceCount = 0;
+
+class GlobalDefaultFieldClass {
+public:
+ GlobalDefaultFieldClass(int PA, int PB) : A(PA), B(PB) { (void)A; (void)B; }
+ GlobalDefaultFieldClass() : A(GlobalVarB), B(0) {}
+private:
+ int A, B;
+};
+
+class NestedInstanceCountedClass {
+ InstanceCountedClass C2;
+public:
+ NestedInstanceCountedClass();
+};
+
+class InstanceCountedBaseClass : public InstanceCountedClass {
+public:
+ InstanceCountedBaseClass();
+};
+
+class NestedGlobalDefaultFieldClass {
+ GlobalDefaultFieldClass G;
+};
+
+class GlobalDefaultFieldBaseClass : public GlobalDefaultFieldClass {
+};
+
+class DestructCountedClass {
+public:
+ static int DestructCount;
+ ~DestructCountedClass() {
+ DestructCount++;
+ }
+
+ operator int() {
+ return 1;
+ }
+};
+
+int DestructCountedClass::DestructCount = 0;
+
+int createAndDestructTestClass4() {
+ NestedInstanceCountedClass Test; (void)Test;
+ return InstanceCountedClass::InstanceCount;
+}
+
+template <class T>
+int destructParam(T Param) {
+ (void)Param;
+ return 0;
+}
+
+int temporaryDestroy() {
+ int K = 42 + DestructCountedClass();
+ // Destructor gets called here.
+ return K;
+}
+
+// Check constructors/destructors
+void testFunc10() {
+ InstanceCountedClass* TestArr[] = { new InstanceCountedClass(), new InstanceCountedClass() };
+ // CHECK-MESSAGES-PRE-CPP11: :[[@LINE-1]]:39: warning: read/write conflict on global variable InstanceCount
+ (void)TestArr;
+
+ InstanceCountedClass TestArr2[2]; // I'm not sure about this. Is this sequenced?
+
+ InstanceCountedClass* NewTestArr = new InstanceCountedClass[2]; // Is this sequenced?
+
+ GlobalDefaultFieldClass Simple1(GlobalVarB, incGlobalVarB());
+ // CHECK-MESSAGES: :[[@LINE-1]]:29: warning: read/write conflict on global variable GlobalVarB
+
+ GlobalDefaultFieldClass Simple2(GlobalVarB, GlobalVarB);
+ // This is fine
+
+ int A = InstanceCountedClass::InstanceCount + (delete TestArr[0], 1); (void)A;
+ // CHECK-MESSAGES: :[[@LINE-1]]:13: warning: read/write conflict on global variable InstanceCount
+
+ int B = InstanceCountedClass::InstanceCount + (TestArr[1]->~InstanceCountedClass(), 1);
+ // CHECK-MESSAGES: :[[@LINE-1]]:13: warning: read/write conflict on global variable InstanceCount
+ (void)B;
+
+ NestedInstanceCountedClass* TestArr3 = new NestedInstanceCountedClass[2];
+
+ int C = InstanceCountedClass::InstanceCount + (delete &TestArr3[0], 1); (void)C;
+ // CHECK-MESSAGES: :[[@LINE-1]]:13: warning: read/write conflict on global variable InstanceCount
+
+ int D = InstanceCountedClass::InstanceCount + (TestArr3[1].~NestedInstanceCountedClass(), 1); (void)D;
+ // CHECK-MESSAGES: :[[@LINE-1]]:13: warning: read/write conflict on global variable InstanceCount
+
+ delete[] NewTestArr; // Same thing. Is this sequenced?
+
+ bool E = InstanceCountedClass::InstanceCount == createAndDestructTestClass4(); (void)E;
+ // CHECK-MESSAGES: :[[@LINE-1]]:14: warning: read/write conflict on global variable InstanceCount
+
+ InstanceCountedClass Test4;
+ int F = InstanceCountedClass::InstanceCount + destructParam(Test4); (void)F;
+ // CHECK-MESSAGES: :[[@LINE-1]]:14: warning: read/write conflict on global variable InstanceCount
+
+ int G = incGlobalVarB() + (GlobalDefaultFieldClass(), 1); (void)G;
+ // CHECK-MESSAGES: :[[@LINE-1]]:13: warning: read/write conflict on global variable GlobalVarB
+
+ InstanceCountedBaseClass* BadDestructor = new InstanceCountedBaseClass();
+ int H = InstanceCountedClass::InstanceCount + (delete BadDestructor, 1); (void)H;
+ // CHECK-MESSAGES: :[[@LINE-1]]:13: warning: read/write conflict on global variable InstanceCount
+ int I = incGlobalVarB() + (NestedGlobalDefaultFieldClass(), 1); (void)I;
+ // CHECK-MESSAGES: :[[@LINE-1]]:13: warning: read/write conflict on global variable GlobalVarB
+
+ int J = incGlobalVarB() + (GlobalDefaultFieldBaseClass(), 1); (void)J;
+ // CHECK-MESSAGES: :[[@LINE-1]]:13: warning: read/write conflict on global variable GlobalVarB
+
+ int K = DestructCountedClass::DestructCount + DestructCountedClass(); (void)K;
+ // The temporary should be destroyed at the end of the full-expression, so
+ // this should be fine.
+
+ int L = DestructCountedClass::DestructCount + temporaryDestroy(); (void)L;
+ // CHECK-MESSAGES: :[[@LINE-1]]:13: warning: read/write conflict on global variable DestructCount
+
+}
+
+void functionWithDefaultParam(int A, int B = GlobalVarB) {
+ (void)(A + B);
+}
+
+void testFunc11() {
+ functionWithDefaultParam(GlobalVarB);
+ // This is fine
+
+ functionWithDefaultParam(incGlobalVarB());
+ // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: read/write conflict on global variable GlobalVarB
+
+ functionWithDefaultParam(incGlobalVarB(), GlobalVarB);
+ // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: read/write conflict on global variable GlobalVarB
+
+ functionWithDefaultParam(incGlobalVarB(), 0);
+ // This is fine
+}
+
+// Check default parameters
+class DefaultConstructorArgClass {
+public:
+ DefaultConstructorArgClass(void* A, int B, int C = GlobalVarB++);
+ DefaultConstructorArgClass(int A, int B, int C = incGlobalVarB());
+};
+
+void testFunc12() {
+ DefaultConstructorArgClass TestObj1(0, 0, 0);
+ // This is fine
+
+ DefaultConstructorArgClass TestObj2((void*)0, GlobalVarB, incGlobalVarB());
+ // CHECK-MESSAGES: :[[@LINE-1]]:32: warning: read/write conflict on global variable GlobalVarB
+
+ DefaultConstructorArgClass TestObj3((void*)0, GlobalVarB);
+ // CHECK-MESSAGES: :[[@LINE-1]]:32: warning: read/write conflict on global variable GlobalVarB
+
+ DefaultConstructorArgClass TestObj4(0, GlobalVarB);
+ // CHECK-MESSAGES: :[[@LINE-1]]:32: warning: read/write conflict on global variable GlobalVarB
+}
+
+class ConstructorArgWritingClass {
+public:
+ ConstructorArgWritingClass(int& A) {
+ A++;
+ }
+ ConstructorArgWritingClass(int* A) {
+ (*A)++;
+ }
+
+ operator int() {
+ return 1;
+ }
+};
+
+// Check constructor argument writes
+int functionThatPassesReferenceToCtor() {
+ ConstructorArgWritingClass Ref(GlobalVarB);
+
+ return Ref;
+}
+
+int functionThatPassesPtrToCtor() {
+ ConstructorArgWritingClass Ptr(&GlobalVarB);
+
+ return Ptr;
+}
+
+void testFunc13() {
+ int A = functionThatPassesReferenceToCtor() + GlobalVarB; (void)A;
+ // CHECK-MESSAGES-PARAM: :[[@LINE-1]]:13: warning: read/write conflict on global variable GlobalVarB
+
+ int B = functionThatPassesPtrToCtor() + GlobalVarB; (void)B;
+ // CHECK-MESSAGES-PARAM: :[[@LINE-1]]:13: warning: read/write conflict on global variable GlobalVarB
+}
More information about the cfe-commits
mailing list