https://github.com/ConcreteCactus created https://github.com/llvm/llvm-project/pull/130421
None >From a166904d772319901abd3ca5f71b32d4d5607f7b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81ron=20H=C3=A1rn=C3=A1si?= <aron.harn...@gmail.com> Date: Fri, 22 Nov 2024 21:43:04 +0100 Subject: [PATCH] added Conflicting Global Accesses checker --- .../bugprone/BugproneTidyModule.cpp | 3 + .../clang-tidy/bugprone/CMakeLists.txt | 1 + .../bugprone/ConflictingGlobalAccesses.cpp | 722 ++++++++++++++++++ .../bugprone/ConflictingGlobalAccesses.h | 62 ++ .../bugprone/conflicting-global-accesses.cpp | 267 +++++++ 5 files changed, 1055 insertions(+) create mode 100644 clang-tools-extra/clang-tidy/bugprone/ConflictingGlobalAccesses.cpp create mode 100644 clang-tools-extra/clang-tidy/bugprone/ConflictingGlobalAccesses.h create mode 100644 clang-tools-extra/test/clang-tidy/checkers/bugprone/conflicting-global-accesses.cpp diff --git a/clang-tools-extra/clang-tidy/bugprone/BugproneTidyModule.cpp b/clang-tools-extra/clang-tidy/bugprone/BugproneTidyModule.cpp index 0a3376949b6e5..5418e718a404e 100644 --- a/clang-tools-extra/clang-tidy/bugprone/BugproneTidyModule.cpp +++ b/clang-tools-extra/clang-tidy/bugprone/BugproneTidyModule.cpp @@ -19,6 +19,7 @@ #include "CastingThroughVoidCheck.h" #include "ChainedComparisonCheck.h" #include "ComparePointerToMemberVirtualFunctionCheck.h" +#include "ConflictingGlobalAccesses.h" #include "CopyConstructorInitCheck.h" #include "CrtpConstructorAccessibilityCheck.h" #include "DanglingHandleCheck.h" @@ -124,6 +125,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 dab139b77c770..b66ef8ea3634b 100644 --- a/clang-tools-extra/clang-tidy/bugprone/CMakeLists.txt +++ b/clang-tools-extra/clang-tidy/bugprone/CMakeLists.txt @@ -15,6 +15,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..da84864c6947a --- /dev/null +++ b/clang-tools-extra/clang-tidy/bugprone/ConflictingGlobalAccesses.cpp @@ -0,0 +1,722 @@ +//===----------------------------------------------------------------------===// +// +// 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" + +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 = std::map<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(); + + // startTraversal is called to start a new traversal. It increments the + // TraversalIndex, which in turn will generate new TraversalResults. + void startTraversal(Expr *E); + + const std::vector<TraversalAggregation> &getGlobalsFound() const; + + const std::vector<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(); + + std::vector<TraversalAggregation> GlobalsFound; + std::vector<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. + std::vector<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; + + 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) {} + +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; + 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 std::vector<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 std::vector<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() : TraversalIndex(0), IsInFunction(false) {} + +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(); + 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()); + + FieldIndices[NodeCount - 1] = Decl->getFieldIndex(); + const RecordDecl *Record = Decl->getParent(); + if (Record && Record->isUnion()) { + // Not sure what to do if Record isn't a value. + 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) { + if (!Modified) { + return true; + } + + 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) { + 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 std::vector<TraversalAggregation> & +GlobalRWVisitor::getGlobalsFound() const { + return GlobalsFound; +} + +const std::vector<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) { + GlobalsFound[I].addGlobalRW(Loc, Access, TraversalIndex); + return; + } + } + + GlobalsFound.emplace_back(Name, Loc, Access, TraversalIndex); +} + +void GlobalRWVisitor::addField(DeclarationName Name, + FieldIndexArray FieldIndices, SourceLocation Loc, + bool IsWrite, bool IsUnchecked) { + AccessKind Access = (IsInFunction || IsUnchecked) + ? (IsWrite ? AkUncheckedWrite : AkUncheckedRead) + : (IsWrite ? AkWrite : AkRead); + for (uint32_t I = 0; I < ObjectGlobalsFound.size(); I++) { + if (ObjectGlobalsFound[I].getDeclName() == Name) { + ObjectGlobalsFound[I].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 (isReportedByWunsequenced()) { + 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; + } + // We know that the current state is not reported by -Wunsequenced, so + // either one of the parts has only unchecked accesses, or both parts have + // only reads. + switch (Access) { + case AkWrite: + case AkUncheckedWrite: { + if (OtherPart.Kind & (TrRead | TrWrite)) { + MainPart = OtherPart; + } + OtherPart = TraversalResult(Index, Loc, Access); + break; + } + case AkRead: { + if (!(MainPart.Kind & TrWrite) && + (OtherPart.Kind & (TrWrite | TrUncheckedWrite))) { + MainPart = OtherPart; + } + OtherPart = TraversalResult(Index, Loc, Access); + break; + } + case AkUncheckedRead: + 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 | TrUncheckedWrite); +} + +bool TraversalAggregation::isReportedByWunsequenced() const { + return ((MainPart.Kind | OtherPart.Kind) & TrWrite) && + (MainPart.Kind & (TrWrite | TrRead)) && + (OtherPart.Kind & (TrWrite | TrRead)); +} + +bool TraversalAggregation::shouldBeReported() const { + return hasConflictingOperations() && !isReportedByWunsequenced(); +} + +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 (uint16_t I = 0; I < FieldIndices.size(); I++) { + bool IsUnion = (FieldIndices[I] & FiUnion) != 0; + uint16_t FieldKey = FieldIndices[I] & ~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(); + for (; FieldIt != Node->Fields.end(); 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::const_iterator FieldIt = Fields.begin(); + for (; FieldIt != Fields.end(); 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(); + for (; FieldIt != Fields.end(); FieldIt++) { + if (FieldIt->first != ExceptIndex) { + FieldIt->second->addFieldToAll(Loc, Access, Index); + } + } +} + +} // namespace clang::tidy::bugprone diff --git a/clang-tools-extra/clang-tidy/bugprone/ConflictingGlobalAccesses.h b/clang-tools-extra/clang-tidy/bugprone/ConflictingGlobalAccesses.h new file mode 100644 index 0000000000000..80fff9a75e41b --- /dev/null +++ b/clang-tools-extra/clang-tidy/bugprone/ConflictingGlobalAccesses.h @@ -0,0 +1,62 @@ +//===--- ConflictingGlobalAccesses.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_SIDEEFFECTBETWEENSEQUENCE\ +POINTSCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_SIDEEFFECTBETWEENSEQUENCE\ +POINTSCHECK_H + +#include "../ClangTidyCheck.h" +#include "clang/AST/RecursiveASTVisitor.h" + +namespace clang::tidy::bugprone { + +/// Finds conflicting accesses on global variables. +/// +/// Modifying twice or reading and modifying a memory location without a +/// defined sequence of the operations is undefined behavior. This checker is +/// similar to the -Wunsequenced clang warning, however it only looks at global +/// variables and can find unsequenced operations inside functions as well. +/// +/// For example: \code +/// +/// int a = 0; +/// int b = (a++) - a; // This is flagged by -Wunsequenced. +/// +/// \endcode +/// +/// However global variables allow for more complex scenarios that +/// -Wunsequenced doesn't detect. E.g. \code +/// +/// int globalVar = 0; +/// +/// int incFun() { +/// globalVar++; +/// return globalVar; +/// } +/// +/// int main() { +/// return globalVar + incFun(); // This is not detected by -Wunsequenced. +/// } +/// +/// \endcode +/// +/// This checker attempts to detect such undefined behavior. It recurses into +/// functions that are inside the same translation unit. It also attempts not to +/// flag cases that are already covered by -Wunsequenced. Global unions and +/// structs are also handled. +class ConflictingGlobalAccesses : public ClangTidyCheck { +public: + ConflictingGlobalAccesses(StringRef Name, ClangTidyContext *Context); + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace clang::tidy::bugprone + +#endif diff --git a/clang-tools-extra/test/clang-tidy/checkers/bugprone/conflicting-global-accesses.cpp b/clang-tools-extra/test/clang-tidy/checkers/bugprone/conflicting-global-accesses.cpp new file mode 100644 index 0000000000000..5fab3f5997e38 --- /dev/null +++ b/clang-tools-extra/test/clang-tidy/checkers/bugprone/conflicting-global-accesses.cpp @@ -0,0 +1,267 @@ +// RUN: %check_clang_tidy -std=c++11 -check-suffixes=,CPP11 %s bugprone-conflicting-global-accesses %t +// RUN: %check_clang_tidy -std=c++17 %s bugprone-conflicting-global-accesses %t + +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); +} + +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-CPP11: :[[@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 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: :[[@LINE-1]]:8: warning: read/write conflict on global variable GlobalVarA + return 1; + } + + if(addAll(GlobalVarA, incArgPtr(&GlobalVarA), 0, 0)) { + // CHECK-MESSAGES: :[[@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; + } + + return 0; +} + +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-CPP11: :[[@LINE-1]]:5: warning: read/write conflict on global variable GlobalVarA + + *(Array + GlobalVarA) = incGlobalVarA(); + // CHECK-MESSAGES-CPP11: :[[@LINE-1]]:5: warning: read/write conflict on global variable GlobalVarA + + // Shouldn't warn here as the clang warning takes care of it. + return addAll(GlobalVarA, getGlobalVarA(), GlobalVarA++, 0); +} + +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; + } +}; + +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 +} + +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; + + +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.StructA = {}, 0), 0, 0); + + addAll(GlobalStruct.StructA.VarD, (GlobalStruct.StructA = {}, 0), 0, 0); + // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: read/write conflict on the field of the global object GlobalStruct + 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(GlobalStruct.StructA.VarC, (GlobalStruct = {}, 0), 0, 0); + // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: read/write conflict on the field of the global object GlobalStruct + addAll((GlobalStruct.StructA = {}, 1), (GlobalStruct = {}, 0), 0, 0); + // CHECK-MESSAGES: :[[@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: :[[@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.StructA = {}, 0), 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 = {}, 0), 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); +} _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits