diff options
author | Dimitry Andric <dim@FreeBSD.org> | 2022-07-04 19:20:19 +0000 |
---|---|---|
committer | Dimitry Andric <dim@FreeBSD.org> | 2023-02-08 19:02:26 +0000 |
commit | 81ad626541db97eb356e2c1d4a20eb2a26a766ab (patch) | |
tree | 311b6a8987c32b1e1dcbab65c54cfac3fdb56175 /contrib/llvm-project/clang/lib/Analysis | |
parent | 5fff09660e06a66bed6482da9c70df328e16bbb6 (diff) | |
parent | 145449b1e420787bb99721a429341fa6be3adfb6 (diff) |
Diffstat (limited to 'contrib/llvm-project/clang/lib/Analysis')
19 files changed, 2736 insertions, 405 deletions
diff --git a/contrib/llvm-project/clang/lib/Analysis/AnalysisDeclContext.cpp b/contrib/llvm-project/clang/lib/Analysis/AnalysisDeclContext.cpp index 06f1f813aeed..f20924604f64 100644 --- a/contrib/llvm-project/clang/lib/Analysis/AnalysisDeclContext.cpp +++ b/contrib/llvm-project/clang/lib/Analysis/AnalysisDeclContext.cpp @@ -352,7 +352,7 @@ std::string AnalysisDeclContext::getFunctionName(const Decl *D) { for (const auto &P : FD->parameters()) { if (P != *FD->param_begin()) OS << ", "; - OS << P->getType().getAsString(); + OS << P->getType(); } OS << ')'; } diff --git a/contrib/llvm-project/clang/lib/Analysis/BodyFarm.cpp b/contrib/llvm-project/clang/lib/Analysis/BodyFarm.cpp index 92c236ed9080..23d37b881069 100644 --- a/contrib/llvm-project/clang/lib/Analysis/BodyFarm.cpp +++ b/contrib/llvm-project/clang/lib/Analysis/BodyFarm.cpp @@ -20,6 +20,7 @@ #include "clang/AST/ExprObjC.h" #include "clang/AST/NestedNameSpecifier.h" #include "clang/Analysis/CodeInjector.h" +#include "clang/Basic/Builtins.h" #include "clang/Basic/OperatorKinds.h" #include "llvm/ADT/StringSwitch.h" #include "llvm/Support/Debug.h" @@ -86,6 +87,9 @@ public: ImplicitCastExpr *makeImplicitCast(const Expr *Arg, QualType Ty, CastKind CK = CK_LValueToRValue); + /// Create a cast to reference type. + CastExpr *makeReferenceCast(const Expr *Arg, QualType Ty); + /// Create an Objective-C bool literal. ObjCBoolLiteralExpr *makeObjCBool(bool Val); @@ -130,7 +134,8 @@ BinaryOperator *ASTMaker::makeComparison(const Expr *LHS, const Expr *RHS, } CompoundStmt *ASTMaker::makeCompound(ArrayRef<Stmt *> Stmts) { - return CompoundStmt::Create(C, Stmts, SourceLocation(), SourceLocation()); + return CompoundStmt::Create(C, Stmts, FPOptionsOverride(), SourceLocation(), + SourceLocation()); } DeclRefExpr *ASTMaker::makeDeclRefExpr( @@ -173,6 +178,16 @@ ImplicitCastExpr *ASTMaker::makeImplicitCast(const Expr *Arg, QualType Ty, /* FPFeatures */ FPOptionsOverride()); } +CastExpr *ASTMaker::makeReferenceCast(const Expr *Arg, QualType Ty) { + assert(Ty->isReferenceType()); + return CXXStaticCastExpr::Create( + C, Ty.getNonReferenceType(), + Ty->isLValueReferenceType() ? VK_LValue : VK_XValue, CK_NoOp, + const_cast<Expr *>(Arg), /*CXXCastPath=*/nullptr, + /*Written=*/C.getTrivialTypeSourceInfo(Ty), FPOptionsOverride(), + SourceLocation(), SourceLocation(), SourceRange()); +} + Expr *ASTMaker::makeIntegralCast(const Expr *Arg, QualType Ty) { if (Arg->getType() == Ty) return const_cast<Expr*>(Arg); @@ -296,6 +311,22 @@ static CallExpr *create_call_once_lambda_call(ASTContext &C, ASTMaker M, /*FPFeatures=*/FPOptionsOverride()); } +/// Create a fake body for 'std::move' or 'std::forward'. This is just: +/// +/// \code +/// return static_cast<return_type>(param); +/// \endcode +static Stmt *create_std_move_forward(ASTContext &C, const FunctionDecl *D) { + LLVM_DEBUG(llvm::dbgs() << "Generating body for std::move / std::forward\n"); + + ASTMaker M(C); + + QualType ReturnType = D->getType()->castAs<FunctionType>()->getReturnType(); + Expr *Param = M.makeDeclRefExpr(D->getParamDecl(0)); + Expr *Cast = M.makeReferenceCast(Param, ReturnType); + return M.makeReturn(Cast); +} + /// Create a fake body for std::call_once. /// Emulates the following function body: /// @@ -667,7 +698,7 @@ static Stmt *create_OSAtomicCompareAndSwap(ASTContext &C, const FunctionDecl *D) Stmt *BodyFarm::getBody(const FunctionDecl *D) { Optional<Stmt *> &Val = Bodies[D]; - if (Val.hasValue()) + if (Val) return Val.getValue(); Val = nullptr; @@ -681,8 +712,20 @@ Stmt *BodyFarm::getBody(const FunctionDecl *D) { FunctionFarmer FF; - if (Name.startswith("OSAtomicCompareAndSwap") || - Name.startswith("objc_atomicCompareAndSwap")) { + if (unsigned BuiltinID = D->getBuiltinID()) { + switch (BuiltinID) { + case Builtin::BIas_const: + case Builtin::BIforward: + case Builtin::BImove: + case Builtin::BImove_if_noexcept: + FF = create_std_move_forward; + break; + default: + FF = nullptr; + break; + } + } else if (Name.startswith("OSAtomicCompareAndSwap") || + Name.startswith("objc_atomicCompareAndSwap")) { FF = create_OSAtomicCompareAndSwap; } else if (Name == "call_once" && D->getDeclContext()->isStdNamespace()) { FF = create_call_once; @@ -695,7 +738,7 @@ Stmt *BodyFarm::getBody(const FunctionDecl *D) { if (FF) { Val = FF(C, D); } else if (Injector) { Val = Injector->getBody(D); } - return Val.getValue(); + return *Val; } static const ObjCIvarDecl *findBackingIvar(const ObjCPropertyDecl *Prop) { @@ -830,7 +873,7 @@ Stmt *BodyFarm::getBody(const ObjCMethodDecl *D) { return nullptr; Optional<Stmt *> &Val = Bodies[D]; - if (Val.hasValue()) + if (Val) return Val.getValue(); Val = nullptr; @@ -858,5 +901,5 @@ Stmt *BodyFarm::getBody(const ObjCMethodDecl *D) { Val = createObjCPropertyGetter(C, D); - return Val.getValue(); + return *Val; } diff --git a/contrib/llvm-project/clang/lib/Analysis/CFG.cpp b/contrib/llvm-project/clang/lib/Analysis/CFG.cpp index 8246854dc1b5..614d94ae31a6 100644 --- a/contrib/llvm-project/clang/lib/Analysis/CFG.cpp +++ b/contrib/llvm-project/clang/lib/Analysis/CFG.cpp @@ -441,8 +441,7 @@ reverse_children::reverse_children(Stmt *S) { } // Default case for all other statements. - for (Stmt *SubStmt : S->children()) - childrenBuf.push_back(SubStmt); + llvm::append_range(childrenBuf, S->children()); // This needs to be done *after* childrenBuf has been populated. children = childrenBuf; @@ -565,6 +564,7 @@ private: AddStmtChoice asc); CFGBlock *VisitCXXThrowExpr(CXXThrowExpr *T); CFGBlock *VisitCXXTryStmt(CXXTryStmt *S); + CFGBlock *VisitCXXTypeidExpr(CXXTypeidExpr *S, AddStmtChoice asc); CFGBlock *VisitDeclStmt(DeclStmt *DS); CFGBlock *VisitDeclSubExpr(DeclStmt *DS); CFGBlock *VisitDefaultStmt(DefaultStmt *D); @@ -598,6 +598,8 @@ private: CFGBlock *VisitObjCMessageExpr(ObjCMessageExpr *E, AddStmtChoice asc); CFGBlock *VisitPseudoObjectExpr(PseudoObjectExpr *E); CFGBlock *VisitReturnStmt(Stmt *S); + CFGBlock *VisitCoroutineSuspendExpr(CoroutineSuspendExpr *S, + AddStmtChoice asc); CFGBlock *VisitSEHExceptStmt(SEHExceptStmt *S); CFGBlock *VisitSEHFinallyStmt(SEHFinallyStmt *S); CFGBlock *VisitSEHLeaveStmt(SEHLeaveStmt *S); @@ -608,6 +610,7 @@ private: AddStmtChoice asc); CFGBlock *VisitUnaryOperator(UnaryOperator *U, AddStmtChoice asc); CFGBlock *VisitWhileStmt(WhileStmt *W); + CFGBlock *VisitArrayInitLoopExpr(ArrayInitLoopExpr *A, AddStmtChoice asc); CFGBlock *Visit(Stmt *S, AddStmtChoice asc = AddStmtChoice::NotAlwaysAdd, bool ExternallyDestructed = false); @@ -2020,13 +2023,8 @@ LocalScope* CFGBuilder::addLocalScopeForVarDecl(VarDecl *VD, return Scope; // Check if variable is local. - switch (VD->getStorageClass()) { - case SC_None: - case SC_Auto: - case SC_Register: - break; - default: return Scope; - } + if (!VD->hasLocalStorage()) + return Scope; if (BuildOpts.AddImplicitDtors) { if (!hasTrivialDestructor(VD) || BuildOpts.AddScopes) { @@ -2223,6 +2221,9 @@ CFGBlock *CFGBuilder::Visit(Stmt * S, AddStmtChoice asc, case Stmt::CXXTryStmtClass: return VisitCXXTryStmt(cast<CXXTryStmt>(S)); + case Stmt::CXXTypeidExprClass: + return VisitCXXTypeidExpr(cast<CXXTypeidExpr>(S), asc); + case Stmt::CXXForRangeStmtClass: return VisitCXXForRangeStmt(cast<CXXForRangeStmt>(S)); @@ -2303,6 +2304,10 @@ CFGBlock *CFGBuilder::Visit(Stmt * S, AddStmtChoice asc, case Stmt::CoreturnStmtClass: return VisitReturnStmt(S); + case Stmt::CoyieldExprClass: + case Stmt::CoawaitExprClass: + return VisitCoroutineSuspendExpr(cast<CoroutineSuspendExpr>(S), asc); + case Stmt::SEHExceptStmtClass: return VisitSEHExceptStmt(cast<SEHExceptStmt>(S)); @@ -2330,6 +2335,9 @@ CFGBlock *CFGBuilder::Visit(Stmt * S, AddStmtChoice asc, case Stmt::WhileStmtClass: return VisitWhileStmt(cast<WhileStmt>(S)); + + case Stmt::ArrayInitLoopExprClass: + return VisitArrayInitLoopExpr(cast<ArrayInitLoopExpr>(S), asc); } } @@ -3143,8 +3151,40 @@ CFGBlock *CFGBuilder::VisitReturnStmt(Stmt *S) { return Visit(O, AddStmtChoice::AlwaysAdd, /*ExternallyDestructed=*/true); return Block; } - // co_return - return VisitChildren(S); + + CoreturnStmt *CRS = cast<CoreturnStmt>(S); + auto *B = Block; + if (CFGBlock *R = Visit(CRS->getPromiseCall())) + B = R; + + if (Expr *RV = CRS->getOperand()) + if (RV->getType()->isVoidType() && !isa<InitListExpr>(RV)) + // A non-initlist void expression. + if (CFGBlock *R = Visit(RV)) + B = R; + + return B; +} + +CFGBlock *CFGBuilder::VisitCoroutineSuspendExpr(CoroutineSuspendExpr *E, + AddStmtChoice asc) { + // We're modelling the pre-coro-xform CFG. Thus just evalate the various + // active components of the co_await or co_yield. Note we do not model the + // edge from the builtin_suspend to the exit node. + if (asc.alwaysAdd(*this, E)) { + autoCreateBlock(); + appendStmt(Block, E); + } + CFGBlock *B = Block; + if (auto *R = Visit(E->getResumeExpr())) + B = R; + if (auto *R = Visit(E->getSuspendExpr())) + B = R; + if (auto *R = Visit(E->getReadyExpr())) + B = R; + if (auto *R = Visit(E->getCommonExpr())) + B = R; + return B; } CFGBlock *CFGBuilder::VisitSEHExceptStmt(SEHExceptStmt *ES) { @@ -3849,6 +3889,27 @@ CFGBlock *CFGBuilder::VisitWhileStmt(WhileStmt *W) { return EntryConditionBlock; } +CFGBlock *CFGBuilder::VisitArrayInitLoopExpr(ArrayInitLoopExpr *A, + AddStmtChoice asc) { + if (asc.alwaysAdd(*this, A)) { + autoCreateBlock(); + appendStmt(Block, A); + } + + CFGBlock *B = Block; + + if (CFGBlock *R = Visit(A->getSubExpr())) + B = R; + + auto *OVE = dyn_cast<OpaqueValueExpr>(A->getCommonExpr()); + assert(OVE && "ArrayInitLoopExpr->getCommonExpr() should be wrapped in an " + "OpaqueValueExpr!"); + if (CFGBlock *R = Visit(OVE->getSourceExpr())) + B = R; + + return B; +} + CFGBlock *CFGBuilder::VisitObjCAtCatchStmt(ObjCAtCatchStmt *CS) { // ObjCAtCatchStmt are treated like labels, so they are the first statement // in a block. @@ -3988,6 +4049,25 @@ CFGBlock *CFGBuilder::VisitCXXThrowExpr(CXXThrowExpr *T) { return VisitStmt(T, AddStmtChoice::AlwaysAdd); } +CFGBlock *CFGBuilder::VisitCXXTypeidExpr(CXXTypeidExpr *S, AddStmtChoice asc) { + if (asc.alwaysAdd(*this, S)) { + autoCreateBlock(); + appendStmt(Block, S); + } + + // C++ [expr.typeid]p3: + // When typeid is applied to an expression other than an glvalue of a + // polymorphic class type [...] [the] expression is an unevaluated + // operand. [...] + // We add only potentially evaluated statements to the block to avoid + // CFG generation for unevaluated operands. + if (S && !S->isTypeDependent() && S->isPotentiallyEvaluated()) + return VisitChildren(S); + + // Return block without CFG for unevaluated operands. + return Block; +} + CFGBlock *CFGBuilder::VisitDoStmt(DoStmt *D) { CFGBlock *LoopSuccessor = nullptr; @@ -5611,12 +5691,10 @@ static void print_elem(raw_ostream &OS, StmtPrinterHelper &Helper, if (Optional<CFGConstructor> CE = E.getAs<CFGConstructor>()) { print_construction_context(OS, Helper, CE->getConstructionContext()); } - OS << ", " << CCE->getType().getAsString() << ")"; + OS << ", " << CCE->getType() << ")"; } else if (const CastExpr *CE = dyn_cast<CastExpr>(S)) { - OS << " (" << CE->getStmtClassName() << ", " - << CE->getCastKindName() - << ", " << CE->getType().getAsString() - << ")"; + OS << " (" << CE->getStmtClassName() << ", " << CE->getCastKindName() + << ", " << CE->getType() << ")"; } // Expressions need a newline. @@ -6130,17 +6208,13 @@ Stmt *CFGBlock::getTerminatorCondition(bool StripParens) { // CFG Graphviz Visualization //===----------------------------------------------------------------------===// -#ifndef NDEBUG -static StmtPrinterHelper* GraphHelper; -#endif +static StmtPrinterHelper *GraphHelper; void CFG::viewCFG(const LangOptions &LO) const { -#ifndef NDEBUG StmtPrinterHelper H(this, LO); GraphHelper = &H; llvm::ViewGraph(this,"CFG"); GraphHelper = nullptr; -#endif } namespace llvm { @@ -6149,8 +6223,7 @@ template<> struct DOTGraphTraits<const CFG*> : public DefaultDOTGraphTraits { DOTGraphTraits(bool isSimple = false) : DefaultDOTGraphTraits(isSimple) {} - static std::string getNodeLabel(const CFGBlock *Node, const CFG* Graph) { -#ifndef NDEBUG + static std::string getNodeLabel(const CFGBlock *Node, const CFG *Graph) { std::string OutSStr; llvm::raw_string_ostream Out(OutSStr); print_block(Out,Graph, *Node, *GraphHelper, false, false); @@ -6166,9 +6239,6 @@ struct DOTGraphTraits<const CFG*> : public DefaultDOTGraphTraits { } return OutStr; -#else - return {}; -#endif } }; diff --git a/contrib/llvm-project/clang/lib/Analysis/CalledOnceCheck.cpp b/contrib/llvm-project/clang/lib/Analysis/CalledOnceCheck.cpp index 661f7b999f2b..c1d1d7b89ec7 100644 --- a/contrib/llvm-project/clang/lib/Analysis/CalledOnceCheck.cpp +++ b/contrib/llvm-project/clang/lib/Analysis/CalledOnceCheck.cpp @@ -1065,7 +1065,7 @@ private: // 'swift_async' goes first and overrides anything else. if (auto ConventionalAsync = isConventionalSwiftAsync(Function, ParamIndex)) { - return ConventionalAsync.getValue(); + return *ConventionalAsync; } return shouldBeCalledOnce(Function->getParamDecl(ParamIndex)) || @@ -1082,7 +1082,7 @@ private: // 'swift_async' goes first and overrides anything else. if (auto ConventionalAsync = isConventionalSwiftAsync(Method, ParamIndex)) { - return ConventionalAsync.getValue(); + return *ConventionalAsync; } const ParmVarDecl *Parameter = Method->getParamDecl(ParamIndex); diff --git a/contrib/llvm-project/clang/lib/Analysis/ExprMutationAnalyzer.cpp b/contrib/llvm-project/clang/lib/Analysis/ExprMutationAnalyzer.cpp index e9ff5e5e8765..e3bb902b1fe9 100644 --- a/contrib/llvm-project/clang/lib/Analysis/ExprMutationAnalyzer.cpp +++ b/contrib/llvm-project/clang/lib/Analysis/ExprMutationAnalyzer.cpp @@ -39,8 +39,13 @@ AST_MATCHER_P(Expr, maybeEvalCommaExpr, ast_matchers::internal::Matcher<Expr>, return InnerMatcher.matches(*Result, Finder, Builder); } -AST_MATCHER_P(Expr, canResolveToExpr, ast_matchers::internal::Matcher<Expr>, +AST_MATCHER_P(Stmt, canResolveToExpr, ast_matchers::internal::Matcher<Stmt>, InnerMatcher) { + auto *Exp = dyn_cast<Expr>(&Node); + if (!Exp) { + return stmt().matches(Node, Finder, Builder); + } + auto DerivedToBase = [](const ast_matchers::internal::Matcher<Expr> &Inner) { return implicitCastExpr(anyOf(hasCastKind(CK_DerivedToBase), hasCastKind(CK_UncheckedDerivedToBase)), @@ -71,7 +76,7 @@ AST_MATCHER_P(Expr, canResolveToExpr, ast_matchers::internal::Matcher<Expr>, IgnoreDerivedToBase(ConditionalOperator), IgnoreDerivedToBase(ElvisOperator)))); - return ComplexMatcher.matches(Node, Finder, Builder); + return ComplexMatcher.matches(*Exp, Finder, Builder); } // Similar to 'hasAnyArgument', but does not work because 'InitListExpr' does @@ -194,12 +199,13 @@ const Stmt *ExprMutationAnalyzer::tryEachDeclRef(const Decl *Dec, return nullptr; } -bool ExprMutationAnalyzer::isUnevaluated(const Expr *Exp) { - return selectFirst<Expr>( +bool ExprMutationAnalyzer::isUnevaluated(const Stmt *Exp, const Stmt &Stm, + ASTContext &Context) { + return selectFirst<Stmt>( NodeID<Expr>::value, match( findAll( - expr(canResolveToExpr(equalsNode(Exp)), + stmt(canResolveToExpr(equalsNode(Exp)), anyOf( // `Exp` is part of the underlying expression of // decltype/typeof if it has an ancestor of @@ -225,6 +231,10 @@ bool ExprMutationAnalyzer::isUnevaluated(const Expr *Exp) { Stm, Context)) != nullptr; } +bool ExprMutationAnalyzer::isUnevaluated(const Expr *Exp) { + return isUnevaluated(Exp, Stm, Context); +} + const Stmt * ExprMutationAnalyzer::findExprMutation(ArrayRef<BoundNodes> Matches) { return tryEachMatch<Expr>(Matches, this, &ExprMutationAnalyzer::findMutation); diff --git a/contrib/llvm-project/clang/lib/Analysis/FlowSensitive/ControlFlowContext.cpp b/contrib/llvm-project/clang/lib/Analysis/FlowSensitive/ControlFlowContext.cpp index 3ad2ed640cc1..fe9907a7c99b 100644 --- a/contrib/llvm-project/clang/lib/Analysis/FlowSensitive/ControlFlowContext.cpp +++ b/contrib/llvm-project/clang/lib/Analysis/FlowSensitive/ControlFlowContext.cpp @@ -33,11 +33,13 @@ buildStmtToBasicBlockMap(const CFG &Cfg) { for (const CFGElement &Element : *Block) { auto Stmt = Element.getAs<CFGStmt>(); - if (!Stmt.hasValue()) + if (!Stmt) continue; StmtToBlock[Stmt.getValue().getStmt()] = Block; } + if (const Stmt *TerminatorStmt = Block->getTerminatorStmt()) + StmtToBlock[TerminatorStmt] = Block; } return StmtToBlock; } diff --git a/contrib/llvm-project/clang/lib/Analysis/FlowSensitive/DataflowAnalysisContext.cpp b/contrib/llvm-project/clang/lib/Analysis/FlowSensitive/DataflowAnalysisContext.cpp new file mode 100644 index 000000000000..e08fc71c51dc --- /dev/null +++ b/contrib/llvm-project/clang/lib/Analysis/FlowSensitive/DataflowAnalysisContext.cpp @@ -0,0 +1,340 @@ +//===-- DataflowAnalysisContext.cpp -----------------------------*- 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 +// +//===----------------------------------------------------------------------===// +// +// This file defines a DataflowAnalysisContext class that owns objects that +// encompass the state of a program and stores context that is used during +// dataflow analysis. +// +//===----------------------------------------------------------------------===// + +#include "clang/Analysis/FlowSensitive/DataflowAnalysisContext.h" +#include "clang/AST/ExprCXX.h" +#include "clang/Analysis/FlowSensitive/Value.h" +#include <cassert> +#include <memory> +#include <utility> + +namespace clang { +namespace dataflow { + +StorageLocation & +DataflowAnalysisContext::getStableStorageLocation(QualType Type) { + assert(!Type.isNull()); + if (Type->isStructureOrClassType() || Type->isUnionType()) { + // FIXME: Explore options to avoid eager initialization of fields as some of + // them might not be needed for a particular analysis. + llvm::DenseMap<const ValueDecl *, StorageLocation *> FieldLocs; + for (const FieldDecl *Field : getObjectFields(Type)) + FieldLocs.insert({Field, &getStableStorageLocation(Field->getType())}); + return takeOwnership( + std::make_unique<AggregateStorageLocation>(Type, std::move(FieldLocs))); + } + return takeOwnership(std::make_unique<ScalarStorageLocation>(Type)); +} + +StorageLocation & +DataflowAnalysisContext::getStableStorageLocation(const VarDecl &D) { + if (auto *Loc = getStorageLocation(D)) + return *Loc; + auto &Loc = getStableStorageLocation(D.getType()); + setStorageLocation(D, Loc); + return Loc; +} + +StorageLocation & +DataflowAnalysisContext::getStableStorageLocation(const Expr &E) { + if (auto *Loc = getStorageLocation(E)) + return *Loc; + auto &Loc = getStableStorageLocation(E.getType()); + setStorageLocation(E, Loc); + return Loc; +} + +PointerValue & +DataflowAnalysisContext::getOrCreateNullPointerValue(QualType PointeeType) { + assert(!PointeeType.isNull()); + auto CanonicalPointeeType = PointeeType.getCanonicalType(); + auto Res = NullPointerVals.try_emplace(CanonicalPointeeType, nullptr); + if (Res.second) { + auto &PointeeLoc = getStableStorageLocation(CanonicalPointeeType); + Res.first->second = + &takeOwnership(std::make_unique<PointerValue>(PointeeLoc)); + } + return *Res.first->second; +} + +static std::pair<BoolValue *, BoolValue *> +makeCanonicalBoolValuePair(BoolValue &LHS, BoolValue &RHS) { + auto Res = std::make_pair(&LHS, &RHS); + if (&RHS < &LHS) + std::swap(Res.first, Res.second); + return Res; +} + +BoolValue &DataflowAnalysisContext::getOrCreateConjunction(BoolValue &LHS, + BoolValue &RHS) { + if (&LHS == &RHS) + return LHS; + + auto Res = ConjunctionVals.try_emplace(makeCanonicalBoolValuePair(LHS, RHS), + nullptr); + if (Res.second) + Res.first->second = + &takeOwnership(std::make_unique<ConjunctionValue>(LHS, RHS)); + return *Res.first->second; +} + +BoolValue &DataflowAnalysisContext::getOrCreateDisjunction(BoolValue &LHS, + BoolValue &RHS) { + if (&LHS == &RHS) + return LHS; + + auto Res = DisjunctionVals.try_emplace(makeCanonicalBoolValuePair(LHS, RHS), + nullptr); + if (Res.second) + Res.first->second = + &takeOwnership(std::make_unique<DisjunctionValue>(LHS, RHS)); + return *Res.first->second; +} + +BoolValue &DataflowAnalysisContext::getOrCreateNegation(BoolValue &Val) { + auto Res = NegationVals.try_emplace(&Val, nullptr); + if (Res.second) + Res.first->second = &takeOwnership(std::make_unique<NegationValue>(Val)); + return *Res.first->second; +} + +BoolValue &DataflowAnalysisContext::getOrCreateImplication(BoolValue &LHS, + BoolValue &RHS) { + return &LHS == &RHS ? getBoolLiteralValue(true) + : getOrCreateDisjunction(getOrCreateNegation(LHS), RHS); +} + +BoolValue &DataflowAnalysisContext::getOrCreateIff(BoolValue &LHS, + BoolValue &RHS) { + return &LHS == &RHS + ? getBoolLiteralValue(true) + : getOrCreateConjunction(getOrCreateImplication(LHS, RHS), + getOrCreateImplication(RHS, LHS)); +} + +AtomicBoolValue &DataflowAnalysisContext::makeFlowConditionToken() { + return createAtomicBoolValue(); +} + +void DataflowAnalysisContext::addFlowConditionConstraint( + AtomicBoolValue &Token, BoolValue &Constraint) { + auto Res = FlowConditionConstraints.try_emplace(&Token, &Constraint); + if (!Res.second) { + Res.first->second = &getOrCreateConjunction(*Res.first->second, Constraint); + } +} + +AtomicBoolValue & +DataflowAnalysisContext::forkFlowCondition(AtomicBoolValue &Token) { + auto &ForkToken = makeFlowConditionToken(); + FlowConditionDeps[&ForkToken].insert(&Token); + addFlowConditionConstraint(ForkToken, Token); + return ForkToken; +} + +AtomicBoolValue & +DataflowAnalysisContext::joinFlowConditions(AtomicBoolValue &FirstToken, + AtomicBoolValue &SecondToken) { + auto &Token = makeFlowConditionToken(); + FlowConditionDeps[&Token].insert(&FirstToken); + FlowConditionDeps[&Token].insert(&SecondToken); + addFlowConditionConstraint(Token, + getOrCreateDisjunction(FirstToken, SecondToken)); + return Token; +} + +Solver::Result +DataflowAnalysisContext::querySolver(llvm::DenseSet<BoolValue *> Constraints) { + Constraints.insert(&getBoolLiteralValue(true)); + Constraints.insert(&getOrCreateNegation(getBoolLiteralValue(false))); + return S->solve(std::move(Constraints)); +} + +bool DataflowAnalysisContext::flowConditionImplies(AtomicBoolValue &Token, + BoolValue &Val) { + // Returns true if and only if truth assignment of the flow condition implies + // that `Val` is also true. We prove whether or not this property holds by + // reducing the problem to satisfiability checking. In other words, we attempt + // to show that assuming `Val` is false makes the constraints induced by the + // flow condition unsatisfiable. + llvm::DenseSet<BoolValue *> Constraints = {&Token, &getOrCreateNegation(Val)}; + llvm::DenseSet<AtomicBoolValue *> VisitedTokens; + addTransitiveFlowConditionConstraints(Token, Constraints, VisitedTokens); + return isUnsatisfiable(std::move(Constraints)); +} + +bool DataflowAnalysisContext::flowConditionIsTautology(AtomicBoolValue &Token) { + // Returns true if and only if we cannot prove that the flow condition can + // ever be false. + llvm::DenseSet<BoolValue *> Constraints = {&getOrCreateNegation(Token)}; + llvm::DenseSet<AtomicBoolValue *> VisitedTokens; + addTransitiveFlowConditionConstraints(Token, Constraints, VisitedTokens); + return isUnsatisfiable(std::move(Constraints)); +} + +bool DataflowAnalysisContext::equivalentBoolValues(BoolValue &Val1, + BoolValue &Val2) { + llvm::DenseSet<BoolValue *> Constraints = { + &getOrCreateNegation(getOrCreateIff(Val1, Val2))}; + return isUnsatisfiable(Constraints); +} + +void DataflowAnalysisContext::addTransitiveFlowConditionConstraints( + AtomicBoolValue &Token, llvm::DenseSet<BoolValue *> &Constraints, + llvm::DenseSet<AtomicBoolValue *> &VisitedTokens) { + auto Res = VisitedTokens.insert(&Token); + if (!Res.second) + return; + + auto ConstraintsIT = FlowConditionConstraints.find(&Token); + if (ConstraintsIT == FlowConditionConstraints.end()) { + Constraints.insert(&Token); + } else { + // Bind flow condition token via `iff` to its set of constraints: + // FC <=> (C1 ^ C2 ^ ...), where Ci are constraints + Constraints.insert(&getOrCreateIff(Token, *ConstraintsIT->second)); + } + + auto DepsIT = FlowConditionDeps.find(&Token); + if (DepsIT != FlowConditionDeps.end()) { + for (AtomicBoolValue *DepToken : DepsIT->second) { + addTransitiveFlowConditionConstraints(*DepToken, Constraints, + VisitedTokens); + } + } +} + +BoolValue &DataflowAnalysisContext::substituteBoolValue( + BoolValue &Val, + llvm::DenseMap<BoolValue *, BoolValue *> &SubstitutionsCache) { + auto IT = SubstitutionsCache.find(&Val); + if (IT != SubstitutionsCache.end()) { + // Return memoized result of substituting this boolean value. + return *IT->second; + } + + // Handle substitution on the boolean value (and its subvalues), saving the + // result into `SubstitutionsCache`. + BoolValue *Result; + switch (Val.getKind()) { + case Value::Kind::AtomicBool: { + Result = &Val; + break; + } + case Value::Kind::Negation: { + auto &Negation = *cast<NegationValue>(&Val); + auto &Sub = substituteBoolValue(Negation.getSubVal(), SubstitutionsCache); + Result = &getOrCreateNegation(Sub); + break; + } + case Value::Kind::Disjunction: { + auto &Disjunct = *cast<DisjunctionValue>(&Val); + auto &LeftSub = + substituteBoolValue(Disjunct.getLeftSubValue(), SubstitutionsCache); + auto &RightSub = + substituteBoolValue(Disjunct.getRightSubValue(), SubstitutionsCache); + Result = &getOrCreateDisjunction(LeftSub, RightSub); + break; + } + case Value::Kind::Conjunction: { + auto &Conjunct = *cast<ConjunctionValue>(&Val); + auto &LeftSub = + substituteBoolValue(Conjunct.getLeftSubValue(), SubstitutionsCache); + auto &RightSub = + substituteBoolValue(Conjunct.getRightSubValue(), SubstitutionsCache); + Result = &getOrCreateConjunction(LeftSub, RightSub); + break; + } + default: + llvm_unreachable("Unhandled Value Kind"); + } + SubstitutionsCache[&Val] = Result; + return *Result; +} + +BoolValue &DataflowAnalysisContext::buildAndSubstituteFlowCondition( + AtomicBoolValue &Token, + llvm::DenseMap<AtomicBoolValue *, BoolValue *> Substitutions) { + assert( + Substitutions.find(&getBoolLiteralValue(true)) == Substitutions.end() && + Substitutions.find(&getBoolLiteralValue(false)) == Substitutions.end() && + "Do not substitute true/false boolean literals"); + llvm::DenseMap<BoolValue *, BoolValue *> SubstitutionsCache( + Substitutions.begin(), Substitutions.end()); + return buildAndSubstituteFlowConditionWithCache(Token, SubstitutionsCache); +} + +BoolValue &DataflowAnalysisContext::buildAndSubstituteFlowConditionWithCache( + AtomicBoolValue &Token, + llvm::DenseMap<BoolValue *, BoolValue *> &SubstitutionsCache) { + auto ConstraintsIT = FlowConditionConstraints.find(&Token); + if (ConstraintsIT == FlowConditionConstraints.end()) { + return getBoolLiteralValue(true); + } + auto DepsIT = FlowConditionDeps.find(&Token); + if (DepsIT != FlowConditionDeps.end()) { + for (AtomicBoolValue *DepToken : DepsIT->second) { + auto &NewDep = buildAndSubstituteFlowConditionWithCache( + *DepToken, SubstitutionsCache); + SubstitutionsCache[DepToken] = &NewDep; + } + } + return substituteBoolValue(*ConstraintsIT->second, SubstitutionsCache); +} + +} // namespace dataflow +} // namespace clang + +using namespace clang; + +const Expr &clang::dataflow::ignoreCFGOmittedNodes(const Expr &E) { + const Expr *Current = &E; + if (auto *EWC = dyn_cast<ExprWithCleanups>(Current)) { + Current = EWC->getSubExpr(); + assert(Current != nullptr); + } + Current = Current->IgnoreParens(); + assert(Current != nullptr); + return *Current; +} + +const Stmt &clang::dataflow::ignoreCFGOmittedNodes(const Stmt &S) { + if (auto *E = dyn_cast<Expr>(&S)) + return ignoreCFGOmittedNodes(*E); + return S; +} + +// FIXME: Does not precisely handle non-virtual diamond inheritance. A single +// field decl will be modeled for all instances of the inherited field. +static void +getFieldsFromClassHierarchy(QualType Type, + llvm::DenseSet<const FieldDecl *> &Fields) { + if (Type->isIncompleteType() || Type->isDependentType() || + !Type->isRecordType()) + return; + + for (const FieldDecl *Field : Type->getAsRecordDecl()->fields()) + Fields.insert(Field); + if (auto *CXXRecord = Type->getAsCXXRecordDecl()) + for (const CXXBaseSpecifier &Base : CXXRecord->bases()) + getFieldsFromClassHierarchy(Base.getType(), Fields); +} + +/// Gets the set of all fields in the type. +llvm::DenseSet<const FieldDecl *> +clang::dataflow::getObjectFields(QualType Type) { + llvm::DenseSet<const FieldDecl *> Fields; + getFieldsFromClassHierarchy(Type, Fields); + return Fields; +} diff --git a/contrib/llvm-project/clang/lib/Analysis/FlowSensitive/DataflowEnvironment.cpp b/contrib/llvm-project/clang/lib/Analysis/FlowSensitive/DataflowEnvironment.cpp index eca58b313761..3aea670f20aa 100644 --- a/contrib/llvm-project/clang/lib/Analysis/FlowSensitive/DataflowEnvironment.cpp +++ b/contrib/llvm-project/clang/lib/Analysis/FlowSensitive/DataflowEnvironment.cpp @@ -15,19 +15,28 @@ #include "clang/Analysis/FlowSensitive/DataflowEnvironment.h" #include "clang/AST/Decl.h" #include "clang/AST/DeclCXX.h" +#include "clang/AST/ExprCXX.h" #include "clang/AST/Type.h" #include "clang/Analysis/FlowSensitive/DataflowLattice.h" #include "clang/Analysis/FlowSensitive/StorageLocation.h" #include "clang/Analysis/FlowSensitive/Value.h" #include "llvm/ADT/DenseMap.h" #include "llvm/ADT/DenseSet.h" +#include "llvm/Support/Casting.h" #include "llvm/Support/ErrorHandling.h" +#include <cassert> #include <memory> #include <utility> namespace clang { namespace dataflow { +// FIXME: convert these to parameters of the analysis or environment. Current +// settings have been experimentaly validated, but only for a particular +// analysis. +static constexpr int MaxCompositeValueDepth = 3; +static constexpr int MaxCompositeValueSize = 1000; + /// Returns a map consisting of key-value entries that are present in both maps. template <typename K, typename V> llvm::DenseMap<K, V> intersectDenseMaps(const llvm::DenseMap<K, V> &Map1, @@ -41,25 +50,130 @@ llvm::DenseMap<K, V> intersectDenseMaps(const llvm::DenseMap<K, V> &Map1, return Result; } +static bool areEquivalentIndirectionValues(Value *Val1, Value *Val2) { + if (auto *IndVal1 = dyn_cast<ReferenceValue>(Val1)) { + auto *IndVal2 = cast<ReferenceValue>(Val2); + return &IndVal1->getReferentLoc() == &IndVal2->getReferentLoc(); + } + if (auto *IndVal1 = dyn_cast<PointerValue>(Val1)) { + auto *IndVal2 = cast<PointerValue>(Val2); + return &IndVal1->getPointeeLoc() == &IndVal2->getPointeeLoc(); + } + return false; +} + /// Returns true if and only if `Val1` is equivalent to `Val2`. -static bool equivalentValues(QualType Type, Value *Val1, Value *Val2, +static bool equivalentValues(QualType Type, Value *Val1, + const Environment &Env1, Value *Val2, + const Environment &Env2, Environment::ValueModel &Model) { - if (Val1 == Val2) - return true; + return Val1 == Val2 || areEquivalentIndirectionValues(Val1, Val2) || + Model.compareEquivalent(Type, *Val1, Env1, *Val2, Env2); +} - if (auto *IndVal1 = dyn_cast<IndirectionValue>(Val1)) { - auto *IndVal2 = cast<IndirectionValue>(Val2); - assert(IndVal1->getKind() == IndVal2->getKind()); - return &IndVal1->getPointeeLoc() == &IndVal2->getPointeeLoc(); +/// Attempts to merge distinct values `Val1` and `Val2` in `Env1` and `Env2`, +/// respectively, of the same type `Type`. Merging generally produces a single +/// value that (soundly) approximates the two inputs, although the actual +/// meaning depends on `Model`. +static Value *mergeDistinctValues(QualType Type, Value *Val1, + const Environment &Env1, Value *Val2, + const Environment &Env2, + Environment &MergedEnv, + Environment::ValueModel &Model) { + // Join distinct boolean values preserving information about the constraints + // in the respective path conditions. + // + // FIXME: Does not work for backedges, since the two (or more) paths will not + // have mutually exclusive conditions. + if (auto *Expr1 = dyn_cast<BoolValue>(Val1)) { + auto *Expr2 = cast<BoolValue>(Val2); + auto &MergedVal = MergedEnv.makeAtomicBoolValue(); + MergedEnv.addToFlowCondition(MergedEnv.makeOr( + MergedEnv.makeAnd(Env1.getFlowConditionToken(), + MergedEnv.makeIff(MergedVal, *Expr1)), + MergedEnv.makeAnd(Env2.getFlowConditionToken(), + MergedEnv.makeIff(MergedVal, *Expr2)))); + return &MergedVal; + } + + // FIXME: add unit tests that cover this statement. + if (areEquivalentIndirectionValues(Val1, Val2)) { + return Val1; + } + + // FIXME: Consider destroying `MergedValue` immediately if `ValueModel::merge` + // returns false to avoid storing unneeded values in `DACtx`. + if (Value *MergedVal = MergedEnv.createValue(Type)) + if (Model.merge(Type, *Val1, Env1, *Val2, Env2, *MergedVal, MergedEnv)) + return MergedVal; + + return nullptr; +} + +/// Initializes a global storage value. +static void initGlobalVar(const VarDecl &D, Environment &Env) { + if (!D.hasGlobalStorage() || + Env.getStorageLocation(D, SkipPast::None) != nullptr) + return; + + auto &Loc = Env.createStorageLocation(D); + Env.setStorageLocation(D, Loc); + if (auto *Val = Env.createValue(D.getType())) + Env.setValue(Loc, *Val); +} + +/// Initializes a global storage value. +static void initGlobalVar(const Decl &D, Environment &Env) { + if (auto *V = dyn_cast<VarDecl>(&D)) + initGlobalVar(*V, Env); +} + +/// Initializes global storage values that are declared or referenced from +/// sub-statements of `S`. +// FIXME: Add support for resetting globals after function calls to enable +// the implementation of sound analyses. +static void initGlobalVars(const Stmt &S, Environment &Env) { + for (auto *Child : S.children()) { + if (Child != nullptr) + initGlobalVars(*Child, Env); } - return Model.compareEquivalent(Type, *Val1, *Val2); + if (auto *DS = dyn_cast<DeclStmt>(&S)) { + if (DS->isSingleDecl()) { + initGlobalVar(*DS->getSingleDecl(), Env); + } else { + for (auto *D : DS->getDeclGroup()) + initGlobalVar(*D, Env); + } + } else if (auto *E = dyn_cast<DeclRefExpr>(&S)) { + initGlobalVar(*E->getDecl(), Env); + } else if (auto *E = dyn_cast<MemberExpr>(&S)) { + initGlobalVar(*E->getMemberDecl(), Env); + } +} + +Environment::Environment(DataflowAnalysisContext &DACtx) + : DACtx(&DACtx), FlowConditionToken(&DACtx.makeFlowConditionToken()) {} + +Environment::Environment(const Environment &Other) + : DACtx(Other.DACtx), DeclToLoc(Other.DeclToLoc), + ExprToLoc(Other.ExprToLoc), LocToVal(Other.LocToVal), + MemberLocToStruct(Other.MemberLocToStruct), + FlowConditionToken(&DACtx->forkFlowCondition(*Other.FlowConditionToken)) { +} + +Environment &Environment::operator=(const Environment &Other) { + Environment Copy(Other); + *this = std::move(Copy); + return *this; } Environment::Environment(DataflowAnalysisContext &DACtx, const DeclContext &DeclCtx) : Environment(DACtx) { if (const auto *FuncDecl = dyn_cast<FunctionDecl>(&DeclCtx)) { + assert(FuncDecl->getBody() != nullptr); + initGlobalVars(*FuncDecl->getBody(), *this); for (const auto *ParamDecl : FuncDecl->parameters()) { assert(ParamDecl != nullptr); auto &ParamLoc = createStorageLocation(*ParamDecl); @@ -70,7 +184,12 @@ Environment::Environment(DataflowAnalysisContext &DACtx, } if (const auto *MethodDecl = dyn_cast<CXXMethodDecl>(&DeclCtx)) { - if (!MethodDecl->isStatic()) { + auto *Parent = MethodDecl->getParent(); + assert(Parent != nullptr); + if (Parent->isLambda()) + MethodDecl = dyn_cast<CXXMethodDecl>(Parent->getDeclContext()); + + if (MethodDecl && !MethodDecl->isStatic()) { QualType ThisPointeeType = MethodDecl->getThisObjectType(); // FIXME: Add support for union types. if (!ThisPointeeType->isUnionType()) { @@ -93,9 +212,7 @@ bool Environment::equivalentTo(const Environment &Other, if (ExprToLoc != Other.ExprToLoc) return false; - if (LocToVal.size() != Other.LocToVal.size()) - return false; - + // Compare the contents for the intersection of their domains. for (auto &Entry : LocToVal) { const StorageLocation *Loc = Entry.first; assert(Loc != nullptr); @@ -105,10 +222,10 @@ bool Environment::equivalentTo(const Environment &Other, auto It = Other.LocToVal.find(Loc); if (It == Other.LocToVal.end()) - return false; + continue; assert(It->second != nullptr); - if (!equivalentValues(Loc->getType(), Val, It->second, Model)) + if (!equivalentValues(Loc->getType(), Val, *this, It->second, Other, Model)) return false; } @@ -121,22 +238,26 @@ LatticeJoinEffect Environment::join(const Environment &Other, auto Effect = LatticeJoinEffect::Unchanged; - const unsigned DeclToLocSizeBefore = DeclToLoc.size(); - DeclToLoc = intersectDenseMaps(DeclToLoc, Other.DeclToLoc); - if (DeclToLocSizeBefore != DeclToLoc.size()) + Environment JoinedEnv(*DACtx); + + JoinedEnv.DeclToLoc = intersectDenseMaps(DeclToLoc, Other.DeclToLoc); + if (DeclToLoc.size() != JoinedEnv.DeclToLoc.size()) Effect = LatticeJoinEffect::Changed; - const unsigned ExprToLocSizeBefore = ExprToLoc.size(); - ExprToLoc = intersectDenseMaps(ExprToLoc, Other.ExprToLoc); - if (ExprToLocSizeBefore != ExprToLoc.size()) + JoinedEnv.ExprToLoc = intersectDenseMaps(ExprToLoc, Other.ExprToLoc); + if (ExprToLoc.size() != JoinedEnv.ExprToLoc.size()) Effect = LatticeJoinEffect::Changed; - // Move `LocToVal` so that `Environment::ValueModel::merge` can safely assign - // values to storage locations while this code iterates over the current - // assignments. - llvm::DenseMap<const StorageLocation *, Value *> OldLocToVal = - std::move(LocToVal); - for (auto &Entry : OldLocToVal) { + JoinedEnv.MemberLocToStruct = + intersectDenseMaps(MemberLocToStruct, Other.MemberLocToStruct); + if (MemberLocToStruct.size() != JoinedEnv.MemberLocToStruct.size()) + Effect = LatticeJoinEffect::Changed; + + // FIXME: set `Effect` as needed. + JoinedEnv.FlowConditionToken = &DACtx->joinFlowConditions( + *FlowConditionToken, *Other.FlowConditionToken); + + for (auto &Entry : LocToVal) { const StorageLocation *Loc = Entry.first; assert(Loc != nullptr); @@ -148,59 +269,39 @@ LatticeJoinEffect Environment::join(const Environment &Other, continue; assert(It->second != nullptr); - if (equivalentValues(Loc->getType(), Val, It->second, Model)) { - LocToVal.insert({Loc, Val}); + if (Val == It->second) { + JoinedEnv.LocToVal.insert({Loc, Val}); continue; } - // FIXME: Consider destroying `MergedValue` immediately if - // `ValueModel::merge` returns false to avoid storing unneeded values in - // `DACtx`. - if (Value *MergedVal = createValue(Loc->getType())) - if (Model.merge(Loc->getType(), *Val, *It->second, *MergedVal, *this)) - LocToVal.insert({Loc, MergedVal}); + if (Value *MergedVal = mergeDistinctValues( + Loc->getType(), Val, *this, It->second, Other, JoinedEnv, Model)) + JoinedEnv.LocToVal.insert({Loc, MergedVal}); } - if (OldLocToVal.size() != LocToVal.size()) + if (LocToVal.size() != JoinedEnv.LocToVal.size()) Effect = LatticeJoinEffect::Changed; + *this = std::move(JoinedEnv); + return Effect; } StorageLocation &Environment::createStorageLocation(QualType Type) { - assert(!Type.isNull()); - if (Type->isStructureOrClassType() || Type->isUnionType()) { - // FIXME: Explore options to avoid eager initialization of fields as some of - // them might not be needed for a particular analysis. - llvm::DenseMap<const ValueDecl *, StorageLocation *> FieldLocs; - for (const FieldDecl *Field : Type->getAsRecordDecl()->fields()) { - FieldLocs.insert({Field, &createStorageLocation(Field->getType())}); - } - return takeOwnership( - std::make_unique<AggregateStorageLocation>(Type, std::move(FieldLocs))); - } - return takeOwnership(std::make_unique<ScalarStorageLocation>(Type)); + return DACtx->getStableStorageLocation(Type); } StorageLocation &Environment::createStorageLocation(const VarDecl &D) { // Evaluated declarations are always assigned the same storage locations to // ensure that the environment stabilizes across loop iterations. Storage // locations for evaluated declarations are stored in the analysis context. - if (auto *Loc = DACtx->getStorageLocation(D)) - return *Loc; - auto &Loc = createStorageLocation(D.getType()); - DACtx->setStorageLocation(D, Loc); - return Loc; + return DACtx->getStableStorageLocation(D); } StorageLocation &Environment::createStorageLocation(const Expr &E) { // Evaluated expressions are always assigned the same storage locations to // ensure that the environment stabilizes across loop iterations. Storage // locations for evaluated expressions are stored in the analysis context. - if (auto *Loc = DACtx->getStorageLocation(E)) - return *Loc; - auto &Loc = createStorageLocation(E.getType()); - DACtx->setStorageLocation(E, Loc); - return Loc; + return DACtx->getStableStorageLocation(E); } void Environment::setStorageLocation(const ValueDecl &D, StorageLocation &Loc) { @@ -215,13 +316,15 @@ StorageLocation *Environment::getStorageLocation(const ValueDecl &D, } void Environment::setStorageLocation(const Expr &E, StorageLocation &Loc) { - assert(ExprToLoc.find(&E) == ExprToLoc.end()); - ExprToLoc[&E] = &Loc; + const Expr &CanonE = ignoreCFGOmittedNodes(E); + assert(ExprToLoc.find(&CanonE) == ExprToLoc.end()); + ExprToLoc[&CanonE] = &Loc; } StorageLocation *Environment::getStorageLocation(const Expr &E, SkipPast SP) const { - auto It = ExprToLoc.find(&E); + // FIXME: Add a test with parens. + auto It = ExprToLoc.find(&ignoreCFGOmittedNodes(E)); return It == ExprToLoc.end() ? nullptr : &skip(*It->second, SP); } @@ -229,6 +332,10 @@ StorageLocation *Environment::getThisPointeeStorageLocation() const { return DACtx->getThisPointeeStorageLocation(); } +PointerValue &Environment::getOrCreateNullPointerValue(QualType PointeeType) { + return DACtx->getOrCreateNullPointerValue(PointeeType); +} + void Environment::setValue(const StorageLocation &Loc, Value &Val) { LocToVal[&Loc] = &Val; @@ -238,11 +345,28 @@ void Environment::setValue(const StorageLocation &Loc, Value &Val) { const QualType Type = AggregateLoc.getType(); assert(Type->isStructureOrClassType()); - for (const FieldDecl *Field : Type->getAsRecordDecl()->fields()) { + for (const FieldDecl *Field : getObjectFields(Type)) { assert(Field != nullptr); - setValue(AggregateLoc.getChild(*Field), StructVal->getChild(*Field)); + StorageLocation &FieldLoc = AggregateLoc.getChild(*Field); + MemberLocToStruct[&FieldLoc] = std::make_pair(StructVal, Field); + if (auto *FieldVal = StructVal->getChild(*Field)) + setValue(FieldLoc, *FieldVal); } } + + auto IT = MemberLocToStruct.find(&Loc); + if (IT != MemberLocToStruct.end()) { + // `Loc` is the location of a struct member so we need to also update the + // value of the member in the corresponding `StructValue`. + + assert(IT->second.first != nullptr); + StructValue &StructVal = *IT->second.first; + + assert(IT->second.second != nullptr); + const ValueDecl &Member = *IT->second.second; + + StructVal.setChild(Member, Val); + } } Value *Environment::getValue(const StorageLocation &Loc) const { @@ -266,25 +390,44 @@ Value *Environment::getValue(const Expr &E, SkipPast SP) const { Value *Environment::createValue(QualType Type) { llvm::DenseSet<QualType> Visited; - return createValueUnlessSelfReferential(Type, Visited); + int CreatedValuesCount = 0; + Value *Val = createValueUnlessSelfReferential(Type, Visited, /*Depth=*/0, + CreatedValuesCount); + if (CreatedValuesCount > MaxCompositeValueSize) { + llvm::errs() << "Attempting to initialize a huge value of type: " << Type + << '\n'; + } + return Val; } Value *Environment::createValueUnlessSelfReferential( - QualType Type, llvm::DenseSet<QualType> &Visited) { + QualType Type, llvm::DenseSet<QualType> &Visited, int Depth, + int &CreatedValuesCount) { assert(!Type.isNull()); + // Allow unlimited fields at depth 1; only cap at deeper nesting levels. + if ((Depth > 1 && CreatedValuesCount > MaxCompositeValueSize) || + Depth > MaxCompositeValueDepth) + return nullptr; + + if (Type->isBooleanType()) { + CreatedValuesCount++; + return &makeAtomicBoolValue(); + } + if (Type->isIntegerType()) { + CreatedValuesCount++; return &takeOwnership(std::make_unique<IntegerValue>()); } if (Type->isReferenceType()) { - QualType PointeeType = Type->getAs<ReferenceType>()->getPointeeType(); + CreatedValuesCount++; + QualType PointeeType = Type->castAs<ReferenceType>()->getPointeeType(); auto &PointeeLoc = createStorageLocation(PointeeType); - if (!Visited.contains(PointeeType.getCanonicalType())) { - Visited.insert(PointeeType.getCanonicalType()); - Value *PointeeVal = - createValueUnlessSelfReferential(PointeeType, Visited); + if (Visited.insert(PointeeType.getCanonicalType()).second) { + Value *PointeeVal = createValueUnlessSelfReferential( + PointeeType, Visited, Depth, CreatedValuesCount); Visited.erase(PointeeType.getCanonicalType()); if (PointeeVal != nullptr) @@ -295,13 +438,13 @@ Value *Environment::createValueUnlessSelfReferential( } if (Type->isPointerType()) { - QualType PointeeType = Type->getAs<PointerType>()->getPointeeType(); + CreatedValuesCount++; + QualType PointeeType = Type->castAs<PointerType>()->getPointeeType(); auto &PointeeLoc = createStorageLocation(PointeeType); - if (!Visited.contains(PointeeType.getCanonicalType())) { - Visited.insert(PointeeType.getCanonicalType()); - Value *PointeeVal = - createValueUnlessSelfReferential(PointeeType, Visited); + if (Visited.insert(PointeeType.getCanonicalType()).second) { + Value *PointeeVal = createValueUnlessSelfReferential( + PointeeType, Visited, Depth, CreatedValuesCount); Visited.erase(PointeeType.getCanonicalType()); if (PointeeVal != nullptr) @@ -312,10 +455,11 @@ Value *Environment::createValueUnlessSelfReferential( } if (Type->isStructureOrClassType()) { + CreatedValuesCount++; // FIXME: Initialize only fields that are accessed in the context that is // being analyzed. llvm::DenseMap<const ValueDecl *, Value *> FieldValues; - for (const FieldDecl *Field : Type->getAsRecordDecl()->fields()) { + for (const FieldDecl *Field : getObjectFields(Type)) { assert(Field != nullptr); QualType FieldType = Field->getType(); @@ -323,8 +467,9 @@ Value *Environment::createValueUnlessSelfReferential( continue; Visited.insert(FieldType.getCanonicalType()); - FieldValues.insert( - {Field, createValueUnlessSelfReferential(FieldType, Visited)}); + if (auto *FieldValue = createValueUnlessSelfReferential( + FieldType, Visited, Depth + 1, CreatedValuesCount)) + FieldValues.insert({Field, FieldValue}); Visited.erase(FieldType.getCanonicalType()); } @@ -343,7 +488,7 @@ StorageLocation &Environment::skip(StorageLocation &Loc, SkipPast SP) const { // References cannot be chained so we only need to skip past one level of // indirection. if (auto *Val = dyn_cast_or_null<ReferenceValue>(getValue(Loc))) - return Val->getPointeeLoc(); + return Val->getReferentLoc(); return Loc; case SkipPast::ReferenceThenPointer: StorageLocation &LocPastRef = skip(Loc, SkipPast::Reference); @@ -359,5 +504,13 @@ const StorageLocation &Environment::skip(const StorageLocation &Loc, return skip(*const_cast<StorageLocation *>(&Loc), SP); } +void Environment::addToFlowCondition(BoolValue &Val) { + DACtx->addFlowConditionConstraint(*FlowConditionToken, Val); +} + +bool Environment::flowConditionImplies(BoolValue &Val) const { + return DACtx->flowConditionImplies(*FlowConditionToken, Val); +} + } // namespace dataflow } // namespace clang diff --git a/contrib/llvm-project/clang/lib/Analysis/FlowSensitive/Models/ChromiumCheckModel.cpp b/contrib/llvm-project/clang/lib/Analysis/FlowSensitive/Models/ChromiumCheckModel.cpp new file mode 100644 index 000000000000..3910847316a5 --- /dev/null +++ b/contrib/llvm-project/clang/lib/Analysis/FlowSensitive/Models/ChromiumCheckModel.cpp @@ -0,0 +1,67 @@ +//===-- ChromiumCheckModel.cpp ----------------------------------*- 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 +// +//===----------------------------------------------------------------------===// + +#include "clang/Analysis/FlowSensitive/Models/ChromiumCheckModel.h" +#include "clang/AST/Decl.h" +#include "clang/AST/DeclCXX.h" +#include "llvm/ADT/DenseSet.h" + +namespace clang { +namespace dataflow { + +/// Determines whether `D` is one of the methods used to implement Chromium's +/// `CHECK` macros. Populates `CheckDecls`, if empty. +bool isCheckLikeMethod(llvm::SmallDenseSet<const CXXMethodDecl *> &CheckDecls, + const CXXMethodDecl &D) { + // All of the methods of interest are static, so avoid any lookup for + // non-static methods (the common case). + if (!D.isStatic()) + return false; + + if (CheckDecls.empty()) { + // Attempt to initialize `CheckDecls` with the methods in class + // `CheckError`. + const CXXRecordDecl *ParentClass = D.getParent(); + if (ParentClass == nullptr || !ParentClass->getDeclName().isIdentifier() || + ParentClass->getName() != "CheckError") + return false; + + // Check whether namespace is "logging". + const auto *N = + dyn_cast_or_null<NamespaceDecl>(ParentClass->getDeclContext()); + if (N == nullptr || !N->getDeclName().isIdentifier() || + N->getName() != "logging") + return false; + + // Check whether "logging" is a top-level namespace. + if (N->getParent() == nullptr || !N->getParent()->isTranslationUnit()) + return false; + + for (const CXXMethodDecl *M : ParentClass->methods()) + if (M->getDeclName().isIdentifier() && M->getName().endswith("Check")) + CheckDecls.insert(M); + } + + return CheckDecls.contains(&D); +} + +bool ChromiumCheckModel::transfer(const Stmt *Stmt, Environment &Env) { + if (const auto *Call = dyn_cast<CallExpr>(Stmt)) { + if (const auto *M = dyn_cast<CXXMethodDecl>(Call->getDirectCallee())) { + if (isCheckLikeMethod(CheckDecls, *M)) { + // Mark this branch as unreachable. + Env.addToFlowCondition(Env.getBoolLiteralValue(false)); + return true; + } + } + } + return false; +} + +} // namespace dataflow +} // namespace clang diff --git a/contrib/llvm-project/clang/lib/Analysis/FlowSensitive/Models/UncheckedOptionalAccessModel.cpp b/contrib/llvm-project/clang/lib/Analysis/FlowSensitive/Models/UncheckedOptionalAccessModel.cpp new file mode 100644 index 000000000000..eef3cc813a4a --- /dev/null +++ b/contrib/llvm-project/clang/lib/Analysis/FlowSensitive/Models/UncheckedOptionalAccessModel.cpp @@ -0,0 +1,753 @@ +//===-- UncheckedOptionalAccessModel.cpp ------------------------*- 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 +// +//===----------------------------------------------------------------------===// +// +// This file defines a dataflow analysis that detects unsafe uses of optional +// values. +// +//===----------------------------------------------------------------------===// + +#include "clang/Analysis/FlowSensitive/Models/UncheckedOptionalAccessModel.h" +#include "clang/AST/ASTContext.h" +#include "clang/AST/DeclCXX.h" +#include "clang/AST/Expr.h" +#include "clang/AST/ExprCXX.h" +#include "clang/AST/Stmt.h" +#include "clang/ASTMatchers/ASTMatchers.h" +#include "clang/Analysis/FlowSensitive/DataflowEnvironment.h" +#include "clang/Analysis/FlowSensitive/MatchSwitch.h" +#include "clang/Analysis/FlowSensitive/NoopLattice.h" +#include "clang/Analysis/FlowSensitive/Value.h" +#include "clang/Basic/SourceLocation.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/Support/Casting.h" +#include <cassert> +#include <memory> +#include <utility> +#include <vector> + +namespace clang { +namespace dataflow { +namespace { + +using namespace ::clang::ast_matchers; +using LatticeTransferState = TransferState<NoopLattice>; + +DeclarationMatcher optionalClass() { + return classTemplateSpecializationDecl( + anyOf(hasName("std::optional"), hasName("std::__optional_storage_base"), + hasName("__optional_destruct_base"), hasName("absl::optional"), + hasName("base::Optional")), + hasTemplateArgument(0, refersToType(type().bind("T")))); +} + +auto optionalOrAliasType() { + return hasUnqualifiedDesugaredType( + recordType(hasDeclaration(optionalClass()))); +} + +/// Matches any of the spellings of the optional types and sugar, aliases, etc. +auto hasOptionalType() { return hasType(optionalOrAliasType()); } + +auto isOptionalMemberCallWithName( + llvm::StringRef MemberName, + llvm::Optional<StatementMatcher> Ignorable = llvm::None) { + auto Exception = unless(Ignorable ? expr(anyOf(*Ignorable, cxxThisExpr())) + : cxxThisExpr()); + return cxxMemberCallExpr( + on(expr(Exception)), + callee(cxxMethodDecl(hasName(MemberName), ofClass(optionalClass())))); +} + +auto isOptionalOperatorCallWithName( + llvm::StringRef operator_name, + llvm::Optional<StatementMatcher> Ignorable = llvm::None) { + return cxxOperatorCallExpr( + hasOverloadedOperatorName(operator_name), + callee(cxxMethodDecl(ofClass(optionalClass()))), + Ignorable ? callExpr(unless(hasArgument(0, *Ignorable))) : callExpr()); +} + +auto isMakeOptionalCall() { + return callExpr( + callee(functionDecl(hasAnyName( + "std::make_optional", "base::make_optional", "absl::make_optional"))), + hasOptionalType()); +} + +auto hasNulloptType() { + return hasType(namedDecl( + hasAnyName("std::nullopt_t", "absl::nullopt_t", "base::nullopt_t"))); +} + +auto inPlaceClass() { + return recordDecl( + hasAnyName("std::in_place_t", "absl::in_place_t", "base::in_place_t")); +} + +auto isOptionalNulloptConstructor() { + return cxxConstructExpr(hasOptionalType(), argumentCountIs(1), + hasArgument(0, hasNulloptType())); +} + +auto isOptionalInPlaceConstructor() { + return cxxConstructExpr(hasOptionalType(), + hasArgument(0, hasType(inPlaceClass()))); +} + +auto isOptionalValueOrConversionConstructor() { + return cxxConstructExpr( + hasOptionalType(), + unless(hasDeclaration( + cxxConstructorDecl(anyOf(isCopyConstructor(), isMoveConstructor())))), + argumentCountIs(1), hasArgument(0, unless(hasNulloptType()))); +} + +auto isOptionalValueOrConversionAssignment() { + return cxxOperatorCallExpr( + hasOverloadedOperatorName("="), + callee(cxxMethodDecl(ofClass(optionalClass()))), + unless(hasDeclaration(cxxMethodDecl( + anyOf(isCopyAssignmentOperator(), isMoveAssignmentOperator())))), + argumentCountIs(2), hasArgument(1, unless(hasNulloptType()))); +} + +auto isOptionalNulloptAssignment() { + return cxxOperatorCallExpr(hasOverloadedOperatorName("="), + callee(cxxMethodDecl(ofClass(optionalClass()))), + argumentCountIs(2), + hasArgument(1, hasNulloptType())); +} + +auto isStdSwapCall() { + return callExpr(callee(functionDecl(hasName("std::swap"))), + argumentCountIs(2), hasArgument(0, hasOptionalType()), + hasArgument(1, hasOptionalType())); +} + +constexpr llvm::StringLiteral ValueOrCallID = "ValueOrCall"; + +auto isValueOrStringEmptyCall() { + // `opt.value_or("").empty()` + return cxxMemberCallExpr( + callee(cxxMethodDecl(hasName("empty"))), + onImplicitObjectArgument(ignoringImplicit( + cxxMemberCallExpr(on(expr(unless(cxxThisExpr()))), + callee(cxxMethodDecl(hasName("value_or"), + ofClass(optionalClass()))), + hasArgument(0, stringLiteral(hasSize(0)))) + .bind(ValueOrCallID)))); +} + +auto isValueOrNotEqX() { + auto ComparesToSame = [](ast_matchers::internal::Matcher<Stmt> Arg) { + return hasOperands( + ignoringImplicit( + cxxMemberCallExpr(on(expr(unless(cxxThisExpr()))), + callee(cxxMethodDecl(hasName("value_or"), + ofClass(optionalClass()))), + hasArgument(0, Arg)) + .bind(ValueOrCallID)), + ignoringImplicit(Arg)); + }; + + // `opt.value_or(X) != X`, for X is `nullptr`, `""`, or `0`. Ideally, we'd + // support this pattern for any expression, but the AST does not have a + // generic expression comparison facility, so we specialize to common cases + // seen in practice. FIXME: define a matcher that compares values across + // nodes, which would let us generalize this to any `X`. + return binaryOperation(hasOperatorName("!="), + anyOf(ComparesToSame(cxxNullPtrLiteralExpr()), + ComparesToSame(stringLiteral(hasSize(0))), + ComparesToSame(integerLiteral(equals(0))))); +} + +auto isCallReturningOptional() { + return callExpr(hasType(qualType(anyOf( + optionalOrAliasType(), referenceType(pointee(optionalOrAliasType())))))); +} + +/// Sets `HasValueVal` as the symbolic value that represents the "has_value" +/// property of the optional value `OptionalVal`. +void setHasValue(Value &OptionalVal, BoolValue &HasValueVal) { + OptionalVal.setProperty("has_value", HasValueVal); +} + +/// Creates a symbolic value for an `optional` value using `HasValueVal` as the +/// symbolic value of its "has_value" property. +StructValue &createOptionalValue(Environment &Env, BoolValue &HasValueVal) { + auto OptionalVal = std::make_unique<StructValue>(); + setHasValue(*OptionalVal, HasValueVal); + return Env.takeOwnership(std::move(OptionalVal)); +} + +/// Returns the symbolic value that represents the "has_value" property of the +/// optional value `OptionalVal`. Returns null if `OptionalVal` is null. +BoolValue *getHasValue(Environment &Env, Value *OptionalVal) { + if (OptionalVal != nullptr) { + auto *HasValueVal = + cast_or_null<BoolValue>(OptionalVal->getProperty("has_value")); + if (HasValueVal == nullptr) { + HasValueVal = &Env.makeAtomicBoolValue(); + OptionalVal->setProperty("has_value", *HasValueVal); + } + return HasValueVal; + } + return nullptr; +} + +/// If `Type` is a reference type, returns the type of its pointee. Otherwise, +/// returns `Type` itself. +QualType stripReference(QualType Type) { + return Type->isReferenceType() ? Type->getPointeeType() : Type; +} + +/// Returns true if and only if `Type` is an optional type. +bool IsOptionalType(QualType Type) { + if (!Type->isRecordType()) + return false; + // FIXME: Optimize this by avoiding the `getQualifiedNameAsString` call. + auto TypeName = Type->getAsCXXRecordDecl()->getQualifiedNameAsString(); + return TypeName == "std::optional" || TypeName == "absl::optional" || + TypeName == "base::Optional"; +} + +/// Returns the number of optional wrappers in `Type`. +/// +/// For example, if `Type` is `optional<optional<int>>`, the result of this +/// function will be 2. +int countOptionalWrappers(const ASTContext &ASTCtx, QualType Type) { + if (!IsOptionalType(Type)) + return 0; + return 1 + countOptionalWrappers( + ASTCtx, + cast<ClassTemplateSpecializationDecl>(Type->getAsRecordDecl()) + ->getTemplateArgs() + .get(0) + .getAsType() + .getDesugaredType(ASTCtx)); +} + +/// Tries to initialize the `optional`'s value (that is, contents), and return +/// its location. Returns nullptr if the value can't be represented. +StorageLocation *maybeInitializeOptionalValueMember(QualType Q, + Value &OptionalVal, + Environment &Env) { + // The "value" property represents a synthetic field. As such, it needs + // `StorageLocation`, like normal fields (and other variables). So, we model + // it with a `ReferenceValue`, since that includes a storage location. Once + // the property is set, it will be shared by all environments that access the + // `Value` representing the optional (here, `OptionalVal`). + if (auto *ValueProp = OptionalVal.getProperty("value")) { + auto *ValueRef = clang::cast<ReferenceValue>(ValueProp); + auto &ValueLoc = ValueRef->getReferentLoc(); + if (Env.getValue(ValueLoc) == nullptr) { + // The property was previously set, but the value has been lost. This can + // happen, for example, because of an environment merge (where the two + // environments mapped the property to different values, which resulted in + // them both being discarded), or when two blocks in the CFG, with neither + // a dominator of the other, visit the same optional value, or even when a + // block is revisited during testing to collect per-statement state. + // FIXME: This situation means that the optional contents are not shared + // between branches and the like. Practically, this lack of sharing + // reduces the precision of the model when the contents are relevant to + // the check, like another optional or a boolean that influences control + // flow. + auto *ValueVal = Env.createValue(ValueLoc.getType()); + if (ValueVal == nullptr) + return nullptr; + Env.setValue(ValueLoc, *ValueVal); + } + return &ValueLoc; + } + + auto Ty = stripReference(Q); + auto *ValueVal = Env.createValue(Ty); + if (ValueVal == nullptr) + return nullptr; + auto &ValueLoc = Env.createStorageLocation(Ty); + Env.setValue(ValueLoc, *ValueVal); + auto ValueRef = std::make_unique<ReferenceValue>(ValueLoc); + OptionalVal.setProperty("value", Env.takeOwnership(std::move(ValueRef))); + return &ValueLoc; +} + +void initializeOptionalReference(const Expr *OptionalExpr, + const MatchFinder::MatchResult &, + LatticeTransferState &State) { + if (auto *OptionalVal = + State.Env.getValue(*OptionalExpr, SkipPast::Reference)) { + if (OptionalVal->getProperty("has_value") == nullptr) { + setHasValue(*OptionalVal, State.Env.makeAtomicBoolValue()); + } + } +} + +/// Returns true if and only if `OptionalVal` is initialized and known to be +/// empty in `Env. +bool isEmptyOptional(const Value &OptionalVal, const Environment &Env) { + auto *HasValueVal = + cast_or_null<BoolValue>(OptionalVal.getProperty("has_value")); + return HasValueVal != nullptr && + Env.flowConditionImplies(Env.makeNot(*HasValueVal)); +} + +/// Returns true if and only if `OptionalVal` is initialized and known to be +/// non-empty in `Env. +bool isNonEmptyOptional(const Value &OptionalVal, const Environment &Env) { + auto *HasValueVal = + cast_or_null<BoolValue>(OptionalVal.getProperty("has_value")); + return HasValueVal != nullptr && Env.flowConditionImplies(*HasValueVal); +} + +void transferUnwrapCall(const Expr *UnwrapExpr, const Expr *ObjectExpr, + LatticeTransferState &State) { + if (auto *OptionalVal = + State.Env.getValue(*ObjectExpr, SkipPast::ReferenceThenPointer)) { + if (State.Env.getStorageLocation(*UnwrapExpr, SkipPast::None) == nullptr) + if (auto *Loc = maybeInitializeOptionalValueMember( + UnwrapExpr->getType(), *OptionalVal, State.Env)) + State.Env.setStorageLocation(*UnwrapExpr, *Loc); + } +} + +void transferMakeOptionalCall(const CallExpr *E, + const MatchFinder::MatchResult &, + LatticeTransferState &State) { + auto &Loc = State.Env.createStorageLocation(*E); + State.Env.setStorageLocation(*E, Loc); + State.Env.setValue( + Loc, createOptionalValue(State.Env, State.Env.getBoolLiteralValue(true))); +} + +void transferOptionalHasValueCall(const CXXMemberCallExpr *CallExpr, + const MatchFinder::MatchResult &, + LatticeTransferState &State) { + if (auto *HasValueVal = getHasValue( + State.Env, State.Env.getValue(*CallExpr->getImplicitObjectArgument(), + SkipPast::ReferenceThenPointer))) { + auto &CallExprLoc = State.Env.createStorageLocation(*CallExpr); + State.Env.setValue(CallExprLoc, *HasValueVal); + State.Env.setStorageLocation(*CallExpr, CallExprLoc); + } +} + +/// `ModelPred` builds a logical formula relating the predicate in +/// `ValueOrPredExpr` to the optional's `has_value` property. +void transferValueOrImpl(const clang::Expr *ValueOrPredExpr, + const MatchFinder::MatchResult &Result, + LatticeTransferState &State, + BoolValue &(*ModelPred)(Environment &Env, + BoolValue &ExprVal, + BoolValue &HasValueVal)) { + auto &Env = State.Env; + + const auto *ObjectArgumentExpr = + Result.Nodes.getNodeAs<clang::CXXMemberCallExpr>(ValueOrCallID) + ->getImplicitObjectArgument(); + + auto *HasValueVal = getHasValue( + State.Env, + State.Env.getValue(*ObjectArgumentExpr, SkipPast::ReferenceThenPointer)); + if (HasValueVal == nullptr) + return; + + auto *ExprValue = cast_or_null<BoolValue>( + State.Env.getValue(*ValueOrPredExpr, SkipPast::None)); + if (ExprValue == nullptr) { + auto &ExprLoc = State.Env.createStorageLocation(*ValueOrPredExpr); + ExprValue = &State.Env.makeAtomicBoolValue(); + State.Env.setValue(ExprLoc, *ExprValue); + State.Env.setStorageLocation(*ValueOrPredExpr, ExprLoc); + } + + Env.addToFlowCondition(ModelPred(Env, *ExprValue, *HasValueVal)); +} + +void transferValueOrStringEmptyCall(const clang::Expr *ComparisonExpr, + const MatchFinder::MatchResult &Result, + LatticeTransferState &State) { + return transferValueOrImpl(ComparisonExpr, Result, State, + [](Environment &Env, BoolValue &ExprVal, + BoolValue &HasValueVal) -> BoolValue & { + // If the result is *not* empty, then we know the + // optional must have been holding a value. If + // `ExprVal` is true, though, we don't learn + // anything definite about `has_value`, so we + // don't add any corresponding implications to + // the flow condition. + return Env.makeImplication(Env.makeNot(ExprVal), + HasValueVal); + }); +} + +void transferValueOrNotEqX(const Expr *ComparisonExpr, + const MatchFinder::MatchResult &Result, + LatticeTransferState &State) { + transferValueOrImpl(ComparisonExpr, Result, State, + [](Environment &Env, BoolValue &ExprVal, + BoolValue &HasValueVal) -> BoolValue & { + // We know that if `(opt.value_or(X) != X)` then + // `opt.hasValue()`, even without knowing further + // details about the contents of `opt`. + return Env.makeImplication(ExprVal, HasValueVal); + }); +} + +void transferCallReturningOptional(const CallExpr *E, + const MatchFinder::MatchResult &Result, + LatticeTransferState &State) { + if (State.Env.getStorageLocation(*E, SkipPast::None) != nullptr) + return; + + auto &Loc = State.Env.createStorageLocation(*E); + State.Env.setStorageLocation(*E, Loc); + State.Env.setValue( + Loc, createOptionalValue(State.Env, State.Env.makeAtomicBoolValue())); +} + +void assignOptionalValue(const Expr &E, LatticeTransferState &State, + BoolValue &HasValueVal) { + if (auto *OptionalLoc = + State.Env.getStorageLocation(E, SkipPast::ReferenceThenPointer)) { + State.Env.setValue(*OptionalLoc, + createOptionalValue(State.Env, HasValueVal)); + } +} + +/// Returns a symbolic value for the "has_value" property of an `optional<T>` +/// value that is constructed/assigned from a value of type `U` or `optional<U>` +/// where `T` is constructible from `U`. +BoolValue &value_orConversionHasValue(const FunctionDecl &F, const Expr &E, + const MatchFinder::MatchResult &MatchRes, + LatticeTransferState &State) { + assert(F.getTemplateSpecializationArgs()->size() > 0); + + const int TemplateParamOptionalWrappersCount = countOptionalWrappers( + *MatchRes.Context, + stripReference(F.getTemplateSpecializationArgs()->get(0).getAsType())); + const int ArgTypeOptionalWrappersCount = + countOptionalWrappers(*MatchRes.Context, stripReference(E.getType())); + + // Check if this is a constructor/assignment call for `optional<T>` with + // argument of type `U` such that `T` is constructible from `U`. + if (TemplateParamOptionalWrappersCount == ArgTypeOptionalWrappersCount) + return State.Env.getBoolLiteralValue(true); + + // This is a constructor/assignment call for `optional<T>` with argument of + // type `optional<U>` such that `T` is constructible from `U`. + if (auto *HasValueVal = + getHasValue(State.Env, State.Env.getValue(E, SkipPast::Reference))) + return *HasValueVal; + return State.Env.makeAtomicBoolValue(); +} + +void transferValueOrConversionConstructor( + const CXXConstructExpr *E, const MatchFinder::MatchResult &MatchRes, + LatticeTransferState &State) { + assert(E->getNumArgs() > 0); + + assignOptionalValue(*E, State, + value_orConversionHasValue(*E->getConstructor(), + *E->getArg(0), MatchRes, + State)); +} + +void transferAssignment(const CXXOperatorCallExpr *E, BoolValue &HasValueVal, + LatticeTransferState &State) { + assert(E->getNumArgs() > 0); + + auto *OptionalLoc = + State.Env.getStorageLocation(*E->getArg(0), SkipPast::Reference); + if (OptionalLoc == nullptr) + return; + + State.Env.setValue(*OptionalLoc, createOptionalValue(State.Env, HasValueVal)); + + // Assign a storage location for the whole expression. + State.Env.setStorageLocation(*E, *OptionalLoc); +} + +void transferValueOrConversionAssignment( + const CXXOperatorCallExpr *E, const MatchFinder::MatchResult &MatchRes, + LatticeTransferState &State) { + assert(E->getNumArgs() > 1); + transferAssignment(E, + value_orConversionHasValue(*E->getDirectCallee(), + *E->getArg(1), MatchRes, State), + State); +} + +void transferNulloptAssignment(const CXXOperatorCallExpr *E, + const MatchFinder::MatchResult &, + LatticeTransferState &State) { + transferAssignment(E, State.Env.getBoolLiteralValue(false), State); +} + +void transferSwap(const StorageLocation &OptionalLoc1, + const StorageLocation &OptionalLoc2, + LatticeTransferState &State) { + auto *OptionalVal1 = State.Env.getValue(OptionalLoc1); + assert(OptionalVal1 != nullptr); + + auto *OptionalVal2 = State.Env.getValue(OptionalLoc2); + assert(OptionalVal2 != nullptr); + + State.Env.setValue(OptionalLoc1, *OptionalVal2); + State.Env.setValue(OptionalLoc2, *OptionalVal1); +} + +void transferSwapCall(const CXXMemberCallExpr *E, + const MatchFinder::MatchResult &, + LatticeTransferState &State) { + assert(E->getNumArgs() == 1); + + auto *OptionalLoc1 = State.Env.getStorageLocation( + *E->getImplicitObjectArgument(), SkipPast::ReferenceThenPointer); + assert(OptionalLoc1 != nullptr); + + auto *OptionalLoc2 = + State.Env.getStorageLocation(*E->getArg(0), SkipPast::Reference); + assert(OptionalLoc2 != nullptr); + + transferSwap(*OptionalLoc1, *OptionalLoc2, State); +} + +void transferStdSwapCall(const CallExpr *E, const MatchFinder::MatchResult &, + LatticeTransferState &State) { + assert(E->getNumArgs() == 2); + + auto *OptionalLoc1 = + State.Env.getStorageLocation(*E->getArg(0), SkipPast::Reference); + assert(OptionalLoc1 != nullptr); + + auto *OptionalLoc2 = + State.Env.getStorageLocation(*E->getArg(1), SkipPast::Reference); + assert(OptionalLoc2 != nullptr); + + transferSwap(*OptionalLoc1, *OptionalLoc2, State); +} + +llvm::Optional<StatementMatcher> +ignorableOptional(const UncheckedOptionalAccessModelOptions &Options) { + if (Options.IgnoreSmartPointerDereference) + return memberExpr(hasObjectExpression(ignoringParenImpCasts( + cxxOperatorCallExpr(anyOf(hasOverloadedOperatorName("->"), + hasOverloadedOperatorName("*")), + unless(hasArgument(0, expr(hasOptionalType()))))))); + return llvm::None; +} + +StatementMatcher +valueCall(llvm::Optional<StatementMatcher> &IgnorableOptional) { + return isOptionalMemberCallWithName("value", IgnorableOptional); +} + +StatementMatcher +valueOperatorCall(llvm::Optional<StatementMatcher> &IgnorableOptional) { + return expr(anyOf(isOptionalOperatorCallWithName("*", IgnorableOptional), + isOptionalOperatorCallWithName("->", IgnorableOptional))); +} + +auto buildTransferMatchSwitch( + const UncheckedOptionalAccessModelOptions &Options) { + // FIXME: Evaluate the efficiency of matchers. If using matchers results in a + // lot of duplicated work (e.g. string comparisons), consider providing APIs + // that avoid it through memoization. + auto IgnorableOptional = ignorableOptional(Options); + return MatchSwitchBuilder<LatticeTransferState>() + // Attach a symbolic "has_value" state to optional values that we see for + // the first time. + .CaseOf<Expr>( + expr(anyOf(declRefExpr(), memberExpr()), hasOptionalType()), + initializeOptionalReference) + + // make_optional + .CaseOf<CallExpr>(isMakeOptionalCall(), transferMakeOptionalCall) + + // optional::optional + .CaseOf<CXXConstructExpr>( + isOptionalInPlaceConstructor(), + [](const CXXConstructExpr *E, const MatchFinder::MatchResult &, + LatticeTransferState &State) { + assignOptionalValue(*E, State, State.Env.getBoolLiteralValue(true)); + }) + .CaseOf<CXXConstructExpr>( + isOptionalNulloptConstructor(), + [](const CXXConstructExpr *E, const MatchFinder::MatchResult &, + LatticeTransferState &State) { + assignOptionalValue(*E, State, + State.Env.getBoolLiteralValue(false)); + }) + .CaseOf<CXXConstructExpr>(isOptionalValueOrConversionConstructor(), + transferValueOrConversionConstructor) + + // optional::operator= + .CaseOf<CXXOperatorCallExpr>(isOptionalValueOrConversionAssignment(), + transferValueOrConversionAssignment) + .CaseOf<CXXOperatorCallExpr>(isOptionalNulloptAssignment(), + transferNulloptAssignment) + + // optional::value + .CaseOf<CXXMemberCallExpr>( + valueCall(IgnorableOptional), + [](const CXXMemberCallExpr *E, const MatchFinder::MatchResult &, + LatticeTransferState &State) { + transferUnwrapCall(E, E->getImplicitObjectArgument(), State); + }) + + // optional::operator*, optional::operator-> + .CaseOf<CallExpr>(valueOperatorCall(IgnorableOptional), + [](const CallExpr *E, const MatchFinder::MatchResult &, + LatticeTransferState &State) { + transferUnwrapCall(E, E->getArg(0), State); + }) + + // optional::has_value + .CaseOf<CXXMemberCallExpr>(isOptionalMemberCallWithName("has_value"), + transferOptionalHasValueCall) + + // optional::operator bool + .CaseOf<CXXMemberCallExpr>(isOptionalMemberCallWithName("operator bool"), + transferOptionalHasValueCall) + + // optional::emplace + .CaseOf<CXXMemberCallExpr>( + isOptionalMemberCallWithName("emplace"), + [](const CXXMemberCallExpr *E, const MatchFinder::MatchResult &, + LatticeTransferState &State) { + assignOptionalValue(*E->getImplicitObjectArgument(), State, + State.Env.getBoolLiteralValue(true)); + }) + + // optional::reset + .CaseOf<CXXMemberCallExpr>( + isOptionalMemberCallWithName("reset"), + [](const CXXMemberCallExpr *E, const MatchFinder::MatchResult &, + LatticeTransferState &State) { + assignOptionalValue(*E->getImplicitObjectArgument(), State, + State.Env.getBoolLiteralValue(false)); + }) + + // optional::swap + .CaseOf<CXXMemberCallExpr>(isOptionalMemberCallWithName("swap"), + transferSwapCall) + + // std::swap + .CaseOf<CallExpr>(isStdSwapCall(), transferStdSwapCall) + + // opt.value_or("").empty() + .CaseOf<Expr>(isValueOrStringEmptyCall(), transferValueOrStringEmptyCall) + + // opt.value_or(X) != X + .CaseOf<Expr>(isValueOrNotEqX(), transferValueOrNotEqX) + + // returns optional + .CaseOf<CallExpr>(isCallReturningOptional(), + transferCallReturningOptional) + + .Build(); +} + +std::vector<SourceLocation> diagnoseUnwrapCall(const Expr *UnwrapExpr, + const Expr *ObjectExpr, + const Environment &Env) { + if (auto *OptionalVal = + Env.getValue(*ObjectExpr, SkipPast::ReferenceThenPointer)) { + auto *Prop = OptionalVal->getProperty("has_value"); + if (auto *HasValueVal = cast_or_null<BoolValue>(Prop)) { + if (Env.flowConditionImplies(*HasValueVal)) + return {}; + } + } + + // Record that this unwrap is *not* provably safe. + // FIXME: include either the name of the optional (if applicable) or a source + // range of the access for easier interpretation of the result. + return {ObjectExpr->getBeginLoc()}; +} + +auto buildDiagnoseMatchSwitch( + const UncheckedOptionalAccessModelOptions &Options) { + // FIXME: Evaluate the efficiency of matchers. If using matchers results in a + // lot of duplicated work (e.g. string comparisons), consider providing APIs + // that avoid it through memoization. + auto IgnorableOptional = ignorableOptional(Options); + return MatchSwitchBuilder<const Environment, std::vector<SourceLocation>>() + // optional::value + .CaseOf<CXXMemberCallExpr>( + valueCall(IgnorableOptional), + [](const CXXMemberCallExpr *E, const MatchFinder::MatchResult &, + const Environment &Env) { + return diagnoseUnwrapCall(E, E->getImplicitObjectArgument(), Env); + }) + + // optional::operator*, optional::operator-> + .CaseOf<CallExpr>( + valueOperatorCall(IgnorableOptional), + [](const CallExpr *E, const MatchFinder::MatchResult &, + const Environment &Env) { + return diagnoseUnwrapCall(E, E->getArg(0), Env); + }) + .Build(); +} + +} // namespace + +ast_matchers::DeclarationMatcher +UncheckedOptionalAccessModel::optionalClassDecl() { + return optionalClass(); +} + +UncheckedOptionalAccessModel::UncheckedOptionalAccessModel( + ASTContext &Ctx, UncheckedOptionalAccessModelOptions Options) + : DataflowAnalysis<UncheckedOptionalAccessModel, NoopLattice>(Ctx), + TransferMatchSwitch(buildTransferMatchSwitch(Options)) {} + +void UncheckedOptionalAccessModel::transfer(const Stmt *S, NoopLattice &L, + Environment &Env) { + LatticeTransferState State(L, Env); + TransferMatchSwitch(*S, getASTContext(), State); +} + +bool UncheckedOptionalAccessModel::compareEquivalent(QualType Type, + const Value &Val1, + const Environment &Env1, + const Value &Val2, + const Environment &Env2) { + return isNonEmptyOptional(Val1, Env1) == isNonEmptyOptional(Val2, Env2); +} + +bool UncheckedOptionalAccessModel::merge(QualType Type, const Value &Val1, + const Environment &Env1, + const Value &Val2, + const Environment &Env2, + Value &MergedVal, + Environment &MergedEnv) { + if (!IsOptionalType(Type)) + return true; + + auto &HasValueVal = MergedEnv.makeAtomicBoolValue(); + if (isNonEmptyOptional(Val1, Env1) && isNonEmptyOptional(Val2, Env2)) + MergedEnv.addToFlowCondition(HasValueVal); + else if (isEmptyOptional(Val1, Env1) && isEmptyOptional(Val2, Env2)) + MergedEnv.addToFlowCondition(MergedEnv.makeNot(HasValueVal)); + setHasValue(MergedVal, HasValueVal); + return true; +} + +UncheckedOptionalAccessDiagnoser::UncheckedOptionalAccessDiagnoser( + UncheckedOptionalAccessModelOptions Options) + : DiagnoseMatchSwitch(buildDiagnoseMatchSwitch(Options)) {} + +std::vector<SourceLocation> UncheckedOptionalAccessDiagnoser::diagnose( + ASTContext &Context, const Stmt *Stmt, const Environment &Env) { + return DiagnoseMatchSwitch(*Stmt, Context, Env); +} + +} // namespace dataflow +} // namespace clang diff --git a/contrib/llvm-project/clang/lib/Analysis/FlowSensitive/Transfer.cpp b/contrib/llvm-project/clang/lib/Analysis/FlowSensitive/Transfer.cpp index 51a86b727e33..500e1a7a9390 100644 --- a/contrib/llvm-project/clang/lib/Analysis/FlowSensitive/Transfer.cpp +++ b/contrib/llvm-project/clang/lib/Analysis/FlowSensitive/Transfer.cpp @@ -21,6 +21,8 @@ #include "clang/AST/Stmt.h" #include "clang/AST/StmtVisitor.h" #include "clang/Analysis/FlowSensitive/DataflowEnvironment.h" +#include "clang/Analysis/FlowSensitive/Value.h" +#include "clang/Basic/Builtins.h" #include "clang/Basic/OperatorKinds.h" #include "llvm/ADT/STLExtras.h" #include "llvm/Support/Casting.h" @@ -31,45 +33,76 @@ namespace clang { namespace dataflow { -static const Expr *skipExprWithCleanups(const Expr *E) { - if (auto *C = dyn_cast_or_null<ExprWithCleanups>(E)) - return C->getSubExpr(); - return E; +static BoolValue &evaluateBooleanEquality(const Expr &LHS, const Expr &RHS, + Environment &Env) { + if (auto *LHSValue = + dyn_cast_or_null<BoolValue>(Env.getValue(LHS, SkipPast::Reference))) + if (auto *RHSValue = + dyn_cast_or_null<BoolValue>(Env.getValue(RHS, SkipPast::Reference))) + return Env.makeIff(*LHSValue, *RHSValue); + + return Env.makeAtomicBoolValue(); } class TransferVisitor : public ConstStmtVisitor<TransferVisitor> { public: - TransferVisitor(Environment &Env) : Env(Env) {} + TransferVisitor(const StmtToEnvMap &StmtToEnv, Environment &Env) + : StmtToEnv(StmtToEnv), Env(Env) {} void VisitBinaryOperator(const BinaryOperator *S) { - if (S->getOpcode() == BO_Assign) { - // The CFG does not contain `ParenExpr` as top-level statements in basic - // blocks, however sub-expressions can still be of that type. - assert(S->getLHS() != nullptr); - const Expr *LHS = S->getLHS()->IgnoreParens(); + const Expr *LHS = S->getLHS(); + assert(LHS != nullptr); + + const Expr *RHS = S->getRHS(); + assert(RHS != nullptr); - assert(LHS != nullptr); + switch (S->getOpcode()) { + case BO_Assign: { auto *LHSLoc = Env.getStorageLocation(*LHS, SkipPast::Reference); if (LHSLoc == nullptr) - return; - - // The CFG does not contain `ParenExpr` as top-level statements in basic - // blocks, however sub-expressions can still be of that type. - assert(S->getRHS() != nullptr); - const Expr *RHS = S->getRHS()->IgnoreParens(); + break; - assert(RHS != nullptr); - Value *RHSVal = Env.getValue(*RHS, SkipPast::Reference); + auto *RHSVal = Env.getValue(*RHS, SkipPast::Reference); if (RHSVal == nullptr) - return; + break; // Assign a value to the storage location of the left-hand side. Env.setValue(*LHSLoc, *RHSVal); // Assign a storage location for the whole expression. Env.setStorageLocation(*S, *LHSLoc); + break; + } + case BO_LAnd: + case BO_LOr: { + BoolValue &LHSVal = getLogicOperatorSubExprValue(*LHS); + BoolValue &RHSVal = getLogicOperatorSubExprValue(*RHS); + + auto &Loc = Env.createStorageLocation(*S); + Env.setStorageLocation(*S, Loc); + if (S->getOpcode() == BO_LAnd) + Env.setValue(Loc, Env.makeAnd(LHSVal, RHSVal)); + else + Env.setValue(Loc, Env.makeOr(LHSVal, RHSVal)); + break; + } + case BO_NE: + case BO_EQ: { + auto &LHSEqRHSValue = evaluateBooleanEquality(*LHS, *RHS, Env); + auto &Loc = Env.createStorageLocation(*S); + Env.setStorageLocation(*S, Loc); + Env.setValue(Loc, S->getOpcode() == BO_EQ ? LHSEqRHSValue + : Env.makeNot(LHSEqRHSValue)); + break; + } + case BO_Comma: { + if (auto *Loc = Env.getStorageLocation(*RHS, SkipPast::None)) + Env.setStorageLocation(*S, *Loc); + break; + } + default: + break; } - // FIXME: Add support for BO_EQ, BO_NE. } void VisitDeclRefExpr(const DeclRefExpr *S) { @@ -92,6 +125,11 @@ public: // Group decls are converted into single decls in the CFG so the cast below // is safe. const auto &D = *cast<VarDecl>(S->getSingleDecl()); + + // Static local vars are already initialized in `Environment`. + if (D.hasGlobalStorage()) + return; + auto &Loc = Env.createStorageLocation(D); Env.setStorageLocation(D, Loc); @@ -103,11 +141,6 @@ public: return; } - // The CFG does not contain `ParenExpr` as top-level statements in basic - // blocks, however sub-expressions can still be of that type. - InitExpr = skipExprWithCleanups(D.getInit()->IgnoreParens()); - assert(InitExpr != nullptr); - if (D.getType()->isReferenceType()) { // Initializing a reference variable - do not create a reference to // reference. @@ -116,37 +149,75 @@ public: auto &Val = Env.takeOwnership(std::make_unique<ReferenceValue>(*InitExprLoc)); Env.setValue(Loc, Val); - } else { - // FIXME: The initializer expression must always be assigned a value. - // Replace this with an assert when we have sufficient coverage of - // language features. - if (Value *Val = Env.createValue(D.getType())) - Env.setValue(Loc, *Val); } - return; + } else if (auto *InitExprVal = Env.getValue(*InitExpr, SkipPast::None)) { + Env.setValue(Loc, *InitExprVal); } - if (auto *InitExprVal = Env.getValue(*InitExpr, SkipPast::None)) { - Env.setValue(Loc, *InitExprVal); - } else if (!D.getType()->isStructureOrClassType()) { - // FIXME: The initializer expression must always be assigned a value. - // Replace this with an assert when we have sufficient coverage of - // language features. + if (Env.getValue(Loc) == nullptr) { + // We arrive here in (the few) cases where an expression is intentionally + // "uninterpreted". There are two ways to handle this situation: propagate + // the status, so that uninterpreted initializers result in uninterpreted + // variables, or provide a default value. We choose the latter so that + // later refinements of the variable can be used for reasoning about the + // surrounding code. + // + // FIXME. If and when we interpret all language cases, change this to + // assert that `InitExpr` is interpreted, rather than supplying a default + // value (assuming we don't update the environment API to return + // references). if (Value *Val = Env.createValue(D.getType())) Env.setValue(Loc, *Val); - } else { - llvm_unreachable("structs and classes must always be assigned values"); + } + + if (const auto *Decomp = dyn_cast<DecompositionDecl>(&D)) { + // If VarDecl is a DecompositionDecl, evaluate each of its bindings. This + // needs to be evaluated after initializing the values in the storage for + // VarDecl, as the bindings refer to them. + // FIXME: Add support for ArraySubscriptExpr. + // FIXME: Consider adding AST nodes that are used for structured bindings + // to the CFG. + for (const auto *B : Decomp->bindings()) { + auto *ME = dyn_cast_or_null<MemberExpr>(B->getBinding()); + if (ME == nullptr) + continue; + + auto *DE = dyn_cast_or_null<DeclRefExpr>(ME->getBase()); + if (DE == nullptr) + continue; + + // ME and its base haven't been visited because they aren't included in + // the statements of the CFG basic block. + VisitDeclRefExpr(DE); + VisitMemberExpr(ME); + + if (auto *Loc = Env.getStorageLocation(*ME, SkipPast::Reference)) + Env.setStorageLocation(*B, *Loc); + } } } void VisitImplicitCastExpr(const ImplicitCastExpr *S) { - // The CFG does not contain `ParenExpr` as top-level statements in basic - // blocks, however sub-expressions can still be of that type. - assert(S->getSubExpr() != nullptr); - const Expr *SubExpr = S->getSubExpr()->IgnoreParens(); + const Expr *SubExpr = S->getSubExpr(); assert(SubExpr != nullptr); switch (S->getCastKind()) { + case CK_IntegralToBoolean: { + // This cast creates a new, boolean value from the integral value. We + // model that with a fresh value in the environment, unless it's already a + // boolean. + auto &Loc = Env.createStorageLocation(*S); + Env.setStorageLocation(*S, Loc); + if (auto *SubExprVal = dyn_cast_or_null<BoolValue>( + Env.getValue(*SubExpr, SkipPast::Reference))) + Env.setValue(Loc, *SubExprVal); + else + // FIXME: If integer modeling is added, then update this code to create + // the boolean based on the integer model. + Env.setValue(Loc, Env.makeAtomicBoolValue()); + break; + } + case CK_LValueToRValue: { auto *SubExprVal = Env.getValue(*SubExpr, SkipPast::Reference); if (SubExprVal == nullptr) @@ -157,6 +228,18 @@ public: Env.setValue(ExprLoc, *SubExprVal); break; } + + case CK_IntegralCast: + // FIXME: This cast creates a new integral value from the + // subexpression. But, because we don't model integers, we don't + // distinguish between this new value and the underlying one. If integer + // modeling is added, then update this code to create a fresh location and + // value. + case CK_UncheckedDerivedToBase: + case CK_ConstructorConversion: + case CK_UserDefinedConversion: + // FIXME: Add tests that excercise CK_UncheckedDerivedToBase, + // CK_ConstructorConversion, and CK_UserDefinedConversion. case CK_NoOp: { // FIXME: Consider making `Environment::getStorageLocation` skip noop // expressions (this and other similar expressions in the file) instead of @@ -168,18 +251,23 @@ public: Env.setStorageLocation(*S, *SubExprLoc); break; } + case CK_NullToPointer: + case CK_NullToMemberPointer: { + auto &Loc = Env.createStorageLocation(S->getType()); + Env.setStorageLocation(*S, Loc); + + auto &NullPointerVal = + Env.getOrCreateNullPointerValue(S->getType()->getPointeeType()); + Env.setValue(Loc, NullPointerVal); + break; + } default: - // FIXME: Add support for CK_UserDefinedConversion, - // CK_ConstructorConversion, CK_UncheckedDerivedToBase. break; } } void VisitUnaryOperator(const UnaryOperator *S) { - // The CFG does not contain `ParenExpr` as top-level statements in basic - // blocks, however sub-expressions can still be of that type. - assert(S->getSubExpr() != nullptr); - const Expr *SubExpr = S->getSubExpr()->IgnoreParens(); + const Expr *SubExpr = S->getSubExpr(); assert(SubExpr != nullptr); switch (S->getOpcode()) { @@ -212,15 +300,28 @@ public: Env.setValue(PointerLoc, PointerVal); break; } + case UO_LNot: { + auto *SubExprVal = + dyn_cast_or_null<BoolValue>(Env.getValue(*SubExpr, SkipPast::None)); + if (SubExprVal == nullptr) + break; + + auto &ExprLoc = Env.createStorageLocation(*S); + Env.setStorageLocation(*S, ExprLoc); + Env.setValue(ExprLoc, Env.makeNot(*SubExprVal)); + break; + } default: - // FIXME: Add support for UO_LNot. break; } } void VisitCXXThisExpr(const CXXThisExpr *S) { auto *ThisPointeeLoc = Env.getThisPointeeStorageLocation(); - assert(ThisPointeeLoc != nullptr); + if (ThisPointeeLoc == nullptr) + // Unions are not supported yet, and will not have a location for the + // `this` expression's pointee. + return; auto &Loc = Env.createStorageLocation(*S); Env.setStorageLocation(*S, Loc); @@ -236,6 +337,24 @@ public: if (Member->isFunctionOrFunctionTemplate()) return; + if (auto *D = dyn_cast<VarDecl>(Member)) { + if (D->hasGlobalStorage()) { + auto *VarDeclLoc = Env.getStorageLocation(*D, SkipPast::None); + if (VarDeclLoc == nullptr) + return; + + if (VarDeclLoc->getType()->isReferenceType()) { + Env.setStorageLocation(*S, *VarDeclLoc); + } else { + auto &Loc = Env.createStorageLocation(*S); + Env.setStorageLocation(*S, Loc); + Env.setValue(Loc, Env.takeOwnership( + std::make_unique<ReferenceValue>(*VarDeclLoc))); + } + return; + } + } + // The receiver can be either a value or a pointer to a value. Skip past the // indirection to handle both cases. auto *BaseLoc = cast_or_null<AggregateStorageLocation>( @@ -329,15 +448,17 @@ public: if (Val == nullptr) return; + // Assign a value to the storage location of the object. Env.setValue(*ObjectLoc, *Val); + + // FIXME: Add a test for the value of the whole expression. + // Assign a storage location for the whole expression. + Env.setStorageLocation(*S, *ObjectLoc); } } void VisitCXXFunctionalCastExpr(const CXXFunctionalCastExpr *S) { if (S->getCastKind() == CK_ConstructorConversion) { - // The CFG does not contain `ParenExpr` as top-level statements in basic - // blocks, however sub-expressions can still be of that type. - assert(S->getSubExpr() != nullptr); const Expr *SubExpr = S->getSubExpr(); assert(SubExpr != nullptr); @@ -357,6 +478,9 @@ public: } void VisitCallExpr(const CallExpr *S) { + // Of clang's builtins, only `__builtin_expect` is handled explicitly, since + // others (like trap, debugtrap, and unreachable) are handled by CFG + // construction. if (S->isCallToStdMove()) { assert(S->getNumArgs() == 1); @@ -368,6 +492,17 @@ public: return; Env.setStorageLocation(*S, *ArgLoc); + } else if (S->getDirectCallee() != nullptr && + S->getDirectCallee()->getBuiltinID() == + Builtin::BI__builtin_expect) { + assert(S->getNumArgs() > 0); + assert(S->getArg(0) != nullptr); + // `__builtin_expect` returns by-value, so strip away any potential + // references in the argument. + auto *ArgLoc = Env.getStorageLocation(*S->getArg(0), SkipPast::Reference); + if (ArgLoc == nullptr) + return; + Env.setStorageLocation(*S, *ArgLoc); } } @@ -449,13 +584,59 @@ public: Env.setValue(Loc, Env.getBoolLiteralValue(S->getValue())); } + void VisitParenExpr(const ParenExpr *S) { + // The CFG does not contain `ParenExpr` as top-level statements in basic + // blocks, however manual traversal to sub-expressions may encounter them. + // Redirect to the sub-expression. + auto *SubExpr = S->getSubExpr(); + assert(SubExpr != nullptr); + Visit(SubExpr); + } + + void VisitExprWithCleanups(const ExprWithCleanups *S) { + // The CFG does not contain `ExprWithCleanups` as top-level statements in + // basic blocks, however manual traversal to sub-expressions may encounter + // them. Redirect to the sub-expression. + auto *SubExpr = S->getSubExpr(); + assert(SubExpr != nullptr); + Visit(SubExpr); + } + private: + BoolValue &getLogicOperatorSubExprValue(const Expr &SubExpr) { + // `SubExpr` and its parent logic operator might be part of different basic + // blocks. We try to access the value that is assigned to `SubExpr` in the + // corresponding environment. + if (const Environment *SubExprEnv = StmtToEnv.getEnvironment(SubExpr)) { + if (auto *Val = dyn_cast_or_null<BoolValue>( + SubExprEnv->getValue(SubExpr, SkipPast::Reference))) + return *Val; + } + + if (Env.getStorageLocation(SubExpr, SkipPast::None) == nullptr) { + // Sub-expressions that are logic operators are not added in basic blocks + // (e.g. see CFG for `bool d = a && (b || c);`). If `SubExpr` is a logic + // operator, it may not have been evaluated and assigned a value yet. In + // that case, we need to first visit `SubExpr` and then try to get the + // value that gets assigned to it. + Visit(&SubExpr); + } + + if (auto *Val = dyn_cast_or_null<BoolValue>( + Env.getValue(SubExpr, SkipPast::Reference))) + return *Val; + + // If the value of `SubExpr` is still unknown, we create a fresh symbolic + // boolean value for it. + return Env.makeAtomicBoolValue(); + } + + const StmtToEnvMap &StmtToEnv; Environment &Env; }; -void transfer(const Stmt &S, Environment &Env) { - assert(!isa<ParenExpr>(&S)); - TransferVisitor(Env).Visit(&S); +void transfer(const StmtToEnvMap &StmtToEnv, const Stmt &S, Environment &Env) { + TransferVisitor(StmtToEnv, Env).Visit(&S); } } // namespace dataflow diff --git a/contrib/llvm-project/clang/lib/Analysis/FlowSensitive/TypeErasedDataflowAnalysis.cpp b/contrib/llvm-project/clang/lib/Analysis/FlowSensitive/TypeErasedDataflowAnalysis.cpp index 6b14b5ceaf69..6443fc1b6422 100644 --- a/contrib/llvm-project/clang/lib/Analysis/FlowSensitive/TypeErasedDataflowAnalysis.cpp +++ b/contrib/llvm-project/clang/lib/Analysis/FlowSensitive/TypeErasedDataflowAnalysis.cpp @@ -11,12 +11,15 @@ // //===----------------------------------------------------------------------===// +#include <algorithm> #include <memory> #include <system_error> #include <utility> #include <vector> #include "clang/AST/DeclCXX.h" +#include "clang/AST/OperationKinds.h" +#include "clang/AST/StmtVisitor.h" #include "clang/Analysis/Analyses/PostOrderCFGView.h" #include "clang/Analysis/CFG.h" #include "clang/Analysis/FlowSensitive/DataflowEnvironment.h" @@ -24,14 +27,131 @@ #include "clang/Analysis/FlowSensitive/Transfer.h" #include "clang/Analysis/FlowSensitive/TypeErasedDataflowAnalysis.h" #include "clang/Analysis/FlowSensitive/Value.h" +#include "llvm/ADT/ArrayRef.h" #include "llvm/ADT/DenseSet.h" #include "llvm/ADT/None.h" #include "llvm/ADT/Optional.h" +#include "llvm/ADT/STLExtras.h" #include "llvm/Support/Error.h" +#include "llvm/Support/ErrorHandling.h" namespace clang { namespace dataflow { +class StmtToEnvMapImpl : public StmtToEnvMap { +public: + StmtToEnvMapImpl( + const ControlFlowContext &CFCtx, + llvm::ArrayRef<llvm::Optional<TypeErasedDataflowAnalysisState>> + BlockToState) + : CFCtx(CFCtx), BlockToState(BlockToState) {} + + const Environment *getEnvironment(const Stmt &S) const override { + auto BlockIT = CFCtx.getStmtToBlock().find(&ignoreCFGOmittedNodes(S)); + assert(BlockIT != CFCtx.getStmtToBlock().end()); + const auto &State = BlockToState[BlockIT->getSecond()->getBlockID()]; + assert(State); + return &State.getValue().Env; + } + +private: + const ControlFlowContext &CFCtx; + llvm::ArrayRef<llvm::Optional<TypeErasedDataflowAnalysisState>> BlockToState; +}; + +/// Returns the index of `Block` in the successors of `Pred`. +static int blockIndexInPredecessor(const CFGBlock &Pred, + const CFGBlock &Block) { + auto BlockPos = llvm::find_if( + Pred.succs(), [&Block](const CFGBlock::AdjacentBlock &Succ) { + return Succ && Succ->getBlockID() == Block.getBlockID(); + }); + return BlockPos - Pred.succ_begin(); +} + +/// Extends the flow condition of an environment based on a terminator +/// statement. +class TerminatorVisitor : public ConstStmtVisitor<TerminatorVisitor> { +public: + TerminatorVisitor(const StmtToEnvMap &StmtToEnv, Environment &Env, + int BlockSuccIdx) + : StmtToEnv(StmtToEnv), Env(Env), BlockSuccIdx(BlockSuccIdx) {} + + void VisitIfStmt(const IfStmt *S) { + auto *Cond = S->getCond(); + assert(Cond != nullptr); + extendFlowCondition(*Cond); + } + + void VisitWhileStmt(const WhileStmt *S) { + auto *Cond = S->getCond(); + assert(Cond != nullptr); + extendFlowCondition(*Cond); + } + + void VisitDoStmt(const DoStmt *S) { + auto *Cond = S->getCond(); + assert(Cond != nullptr); + extendFlowCondition(*Cond); + } + + void VisitForStmt(const ForStmt *S) { + auto *Cond = S->getCond(); + if (Cond != nullptr) + extendFlowCondition(*Cond); + } + + void VisitBinaryOperator(const BinaryOperator *S) { + assert(S->getOpcode() == BO_LAnd || S->getOpcode() == BO_LOr); + auto *LHS = S->getLHS(); + assert(LHS != nullptr); + extendFlowCondition(*LHS); + } + + void VisitConditionalOperator(const ConditionalOperator *S) { + auto *Cond = S->getCond(); + assert(Cond != nullptr); + extendFlowCondition(*Cond); + } + +private: + void extendFlowCondition(const Expr &Cond) { + // The terminator sub-expression might not be evaluated. + if (Env.getStorageLocation(Cond, SkipPast::None) == nullptr) + transfer(StmtToEnv, Cond, Env); + + // FIXME: The flow condition must be an r-value, so `SkipPast::None` should + // suffice. + auto *Val = + cast_or_null<BoolValue>(Env.getValue(Cond, SkipPast::Reference)); + // Value merging depends on flow conditions from different environments + // being mutually exclusive -- that is, they cannot both be true in their + // entirety (even if they may share some clauses). So, we need *some* value + // for the condition expression, even if just an atom. + if (Val == nullptr) { + // FIXME: Consider introducing a helper for this get-or-create pattern. + auto *Loc = Env.getStorageLocation(Cond, SkipPast::None); + if (Loc == nullptr) { + Loc = &Env.createStorageLocation(Cond); + Env.setStorageLocation(Cond, *Loc); + } + Val = &Env.makeAtomicBoolValue(); + Env.setValue(*Loc, *Val); + } + + // The condition must be inverted for the successor that encompasses the + // "else" branch, if such exists. + if (BlockSuccIdx == 1) + Val = &Env.makeNot(*Val); + + Env.addToFlowCondition(*Val); + } + + const StmtToEnvMap &StmtToEnv; + Environment &Env; + int BlockSuccIdx; +}; + /// Computes the input state for a given basic block by joining the output /// states of its predecessors. /// @@ -78,6 +198,8 @@ static TypeErasedDataflowAnalysisState computeBlockInputState( } llvm::Optional<TypeErasedDataflowAnalysisState> MaybeState; + bool ApplyBuiltinTransfer = Analysis.applyBuiltinTransfer(); + for (const CFGBlock *Pred : Preds) { // Skip if the `Block` is unreachable or control flow cannot get past it. if (!Pred || Pred->hasNoReturnElement()) @@ -87,19 +209,27 @@ static TypeErasedDataflowAnalysisState computeBlockInputState( // loop back edge to `Block`. const llvm::Optional<TypeErasedDataflowAnalysisState> &MaybePredState = BlockStates[Pred->getBlockID()]; - if (!MaybePredState.hasValue()) + if (!MaybePredState) continue; - const TypeErasedDataflowAnalysisState &PredState = - MaybePredState.getValue(); - if (MaybeState.hasValue()) { + TypeErasedDataflowAnalysisState PredState = MaybePredState.getValue(); + if (ApplyBuiltinTransfer) { + if (const Stmt *PredTerminatorStmt = Pred->getTerminatorStmt()) { + const StmtToEnvMapImpl StmtToEnv(CFCtx, BlockStates); + TerminatorVisitor(StmtToEnv, PredState.Env, + blockIndexInPredecessor(*Pred, Block)) + .Visit(PredTerminatorStmt); + } + } + + if (MaybeState) { Analysis.joinTypeErased(MaybeState->Lattice, PredState.Lattice); MaybeState->Env.join(PredState.Env, Analysis); } else { - MaybeState = PredState; + MaybeState = std::move(PredState); } } - if (!MaybeState.hasValue()) { + if (!MaybeState) { // FIXME: Consider passing `Block` to `Analysis.typeErasedInitialElement()` // to enable building analyses like computation of dominators that // initialize the state of each basic block differently. @@ -111,17 +241,19 @@ static TypeErasedDataflowAnalysisState computeBlockInputState( /// Transfers `State` by evaluating `CfgStmt` in the context of `Analysis`. /// `HandleTransferredStmt` (if provided) will be applied to `CfgStmt`, after it /// is evaluated. -static void -transferCFGStmt(const CFGStmt &CfgStmt, TypeErasedDataflowAnalysis &Analysis, - TypeErasedDataflowAnalysisState &State, - std::function<void(const CFGStmt &, - const TypeErasedDataflowAnalysisState &)> - HandleTransferredStmt) { +static void transferCFGStmt( + const ControlFlowContext &CFCtx, + llvm::ArrayRef<llvm::Optional<TypeErasedDataflowAnalysisState>> BlockStates, + const CFGStmt &CfgStmt, TypeErasedDataflowAnalysis &Analysis, + TypeErasedDataflowAnalysisState &State, + std::function<void(const CFGStmt &, + const TypeErasedDataflowAnalysisState &)> + HandleTransferredStmt) { const Stmt *S = CfgStmt.getStmt(); assert(S != nullptr); if (Analysis.applyBuiltinTransfer()) - transfer(*S, State.Env); + transfer(StmtToEnvMapImpl(CFCtx, BlockStates), *S, State.Env); Analysis.transferTypeErased(S, State.Lattice, State.Env); if (HandleTransferredStmt != nullptr) @@ -137,6 +269,11 @@ static void transferCFGInitializer(const CFGInitializer &CfgInit, const CXXCtorInitializer *Initializer = CfgInit.getInitializer(); assert(Initializer != nullptr); + const FieldDecl *Member = Initializer->getMember(); + if (Member == nullptr) + // Not a field initializer. + return; + auto *InitStmt = Initializer->getInit(); assert(InitStmt != nullptr); @@ -149,9 +286,6 @@ static void transferCFGInitializer(const CFGInitializer &CfgInit, if (InitStmtVal == nullptr) return; - const FieldDecl *Member = Initializer->getMember(); - assert(Member != nullptr); - if (Member->getType()->isReferenceType()) { auto &MemberLoc = ThisLoc.getChild(*Member); State.Env.setValue(MemberLoc, @@ -176,8 +310,8 @@ TypeErasedDataflowAnalysisState transferBlock( for (const CFGElement &Element : Block) { switch (Element.getKind()) { case CFGElement::Statement: - transferCFGStmt(*Element.getAs<CFGStmt>(), Analysis, State, - HandleTransferredStmt); + transferCFGStmt(CFCtx, BlockStates, *Element.getAs<CFGStmt>(), Analysis, + State, HandleTransferredStmt); break; case CFGElement::Initializer: if (Analysis.applyBuiltinTransfer()) @@ -192,9 +326,11 @@ TypeErasedDataflowAnalysisState transferBlock( } llvm::Expected<std::vector<llvm::Optional<TypeErasedDataflowAnalysisState>>> -runTypeErasedDataflowAnalysis(const ControlFlowContext &CFCtx, - TypeErasedDataflowAnalysis &Analysis, - const Environment &InitEnv) { +runTypeErasedDataflowAnalysis( + const ControlFlowContext &CFCtx, TypeErasedDataflowAnalysis &Analysis, + const Environment &InitEnv, + std::function<void(const Stmt *, const TypeErasedDataflowAnalysisState &)> + PostVisitStmt) { PostOrderCFGView POV(&CFCtx.getCFG()); ForwardDataflowWorklist Worklist(CFCtx.getCFG(), &POV); @@ -211,10 +347,17 @@ runTypeErasedDataflowAnalysis(const ControlFlowContext &CFCtx, // converging. To limit the damage (infinite loops) that these bugs can cause, // limit the number of iterations. // FIXME: Consider making the maximum number of iterations configurable. + // FIXME: Consider restricting the number of backedges followed, rather than + // iterations. // FIXME: Set up statistics (see llvm/ADT/Statistic.h) to count average number // of iterations, number of functions that time out, etc. + static constexpr uint32_t MaxAverageVisitsPerBlock = 4; + static constexpr uint32_t AbsoluteMaxIterations = 1 << 16; + const uint32_t RelativeMaxIterations = + MaxAverageVisitsPerBlock * BlockStates.size(); + const uint32_t MaxIterations = + std::min(RelativeMaxIterations, AbsoluteMaxIterations); uint32_t Iterations = 0; - static constexpr uint32_t MaxIterations = 1 << 16; while (const CFGBlock *Block = Worklist.dequeue()) { if (++Iterations > MaxIterations) { return llvm::createStringError(std::errc::timed_out, @@ -226,7 +369,7 @@ runTypeErasedDataflowAnalysis(const ControlFlowContext &CFCtx, TypeErasedDataflowAnalysisState NewBlockState = transferBlock(CFCtx, BlockStates, *Block, InitEnv, Analysis); - if (OldBlockState.hasValue() && + if (OldBlockState && Analysis.isEqualTypeErased(OldBlockState.getValue().Lattice, NewBlockState.Lattice) && OldBlockState->Env.equivalentTo(NewBlockState.Env, Analysis)) { @@ -246,6 +389,20 @@ runTypeErasedDataflowAnalysis(const ControlFlowContext &CFCtx, // FIXME: Consider evaluating unreachable basic blocks (those that have a // state set to `llvm::None` at this point) to also analyze dead code. + if (PostVisitStmt) { + for (const CFGBlock *Block : CFCtx.getCFG()) { + // Skip blocks that were not evaluated. + if (!BlockStates[Block->getBlockID()]) + continue; + transferBlock( + CFCtx, BlockStates, *Block, InitEnv, Analysis, + [&PostVisitStmt](const clang::CFGStmt &Stmt, + const TypeErasedDataflowAnalysisState &State) { + PostVisitStmt(Stmt.getStmt(), State); + }); + } + } + return BlockStates; } diff --git a/contrib/llvm-project/clang/lib/Analysis/FlowSensitive/WatchedLiteralsSolver.cpp b/contrib/llvm-project/clang/lib/Analysis/FlowSensitive/WatchedLiteralsSolver.cpp new file mode 100644 index 000000000000..0e6e70d6d5d4 --- /dev/null +++ b/contrib/llvm-project/clang/lib/Analysis/FlowSensitive/WatchedLiteralsSolver.cpp @@ -0,0 +1,600 @@ +//===- WatchedLiteralsSolver.cpp --------------------------------*- 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 +// +//===----------------------------------------------------------------------===// +// +// This file defines a SAT solver implementation that can be used by dataflow +// analyses. +// +//===----------------------------------------------------------------------===// + +#include <cassert> +#include <cstdint> +#include <iterator> +#include <queue> +#include <vector> + +#include "clang/Analysis/FlowSensitive/Solver.h" +#include "clang/Analysis/FlowSensitive/Value.h" +#include "clang/Analysis/FlowSensitive/WatchedLiteralsSolver.h" +#include "llvm/ADT/DenseMap.h" +#include "llvm/ADT/DenseSet.h" +#include "llvm/ADT/STLExtras.h" + +namespace clang { +namespace dataflow { + +// `WatchedLiteralsSolver` is an implementation of Algorithm D from Knuth's +// The Art of Computer Programming Volume 4: Satisfiability, Fascicle 6. It is +// based on the backtracking DPLL algorithm [1], keeps references to a single +// "watched" literal per clause, and uses a set of "active" variables to perform +// unit propagation. +// +// The solver expects that its input is a boolean formula in conjunctive normal +// form that consists of clauses of at least one literal. A literal is either a +// boolean variable or its negation. Below we define types, data structures, and +// utilities that are used to represent boolean formulas in conjunctive normal +// form. +// +// [1] https://en.wikipedia.org/wiki/DPLL_algorithm + +/// Boolean variables are represented as positive integers. +using Variable = uint32_t; + +/// A null boolean variable is used as a placeholder in various data structures +/// and algorithms. +static constexpr Variable NullVar = 0; + +/// Literals are represented as positive integers. Specifically, for a boolean +/// variable `V` that is represented as the positive integer `I`, the positive +/// literal `V` is represented as the integer `2*I` and the negative literal +/// `!V` is represented as the integer `2*I+1`. +using Literal = uint32_t; + +/// A null literal is used as a placeholder in various data structures and +/// algorithms. +static constexpr Literal NullLit = 0; + +/// Returns the positive literal `V`. +static constexpr Literal posLit(Variable V) { return 2 * V; } + +/// Returns the negative literal `!V`. +static constexpr Literal negLit(Variable V) { return 2 * V + 1; } + +/// Returns the negated literal `!L`. +static constexpr Literal notLit(Literal L) { return L ^ 1; } + +/// Returns the variable of `L`. +static constexpr Variable var(Literal L) { return L >> 1; } + +/// Clause identifiers are represented as positive integers. +using ClauseID = uint32_t; + +/// A null clause identifier is used as a placeholder in various data structures +/// and algorithms. +static constexpr ClauseID NullClause = 0; + +/// A boolean formula in conjunctive normal form. +struct BooleanFormula { + /// `LargestVar` is equal to the largest positive integer that represents a + /// variable in the formula. + const Variable LargestVar; + + /// Literals of all clauses in the formula. + /// + /// The element at index 0 stands for the literal in the null clause. It is + /// set to 0 and isn't used. Literals of clauses in the formula start from the + /// element at index 1. + /// + /// For example, for the formula `(L1 v L2) ^ (L2 v L3 v L4)` the elements of + /// `Clauses` will be `[0, L1, L2, L2, L3, L4]`. + std::vector<Literal> Clauses; + + /// Start indices of clauses of the formula in `Clauses`. + /// + /// The element at index 0 stands for the start index of the null clause. It + /// is set to 0 and isn't used. Start indices of clauses in the formula start + /// from the element at index 1. + /// + /// For example, for the formula `(L1 v L2) ^ (L2 v L3 v L4)` the elements of + /// `ClauseStarts` will be `[0, 1, 3]`. Note that the literals of the first + /// clause always start at index 1. The start index for the literals of the + /// second clause depends on the size of the first clause and so on. + std::vector<size_t> ClauseStarts; + + /// Maps literals (indices of the vector) to clause identifiers (elements of + /// the vector) that watch the respective literals. + /// + /// For a given clause, its watched literal is always its first literal in + /// `Clauses`. This invariant is maintained when watched literals change. + std::vector<ClauseID> WatchedHead; + + /// Maps clause identifiers (elements of the vector) to identifiers of other + /// clauses that watch the same literals, forming a set of linked lists. + /// + /// The element at index 0 stands for the identifier of the clause that + /// follows the null clause. It is set to 0 and isn't used. Identifiers of + /// clauses in the formula start from the element at index 1. + std::vector<ClauseID> NextWatched; + + explicit BooleanFormula(Variable LargestVar) : LargestVar(LargestVar) { + Clauses.push_back(0); + ClauseStarts.push_back(0); + NextWatched.push_back(0); + const size_t NumLiterals = 2 * LargestVar + 1; + WatchedHead.resize(NumLiterals + 1, 0); + } + + /// Adds the `L1 v L2 v L3` clause to the formula. If `L2` or `L3` are + /// `NullLit` they are respectively omitted from the clause. + /// + /// Requirements: + /// + /// `L1` must not be `NullLit`. + /// + /// All literals in the input that are not `NullLit` must be distinct. + void addClause(Literal L1, Literal L2 = NullLit, Literal L3 = NullLit) { + // The literals are guaranteed to be distinct from properties of BoolValue + // and the construction in `buildBooleanFormula`. + assert(L1 != NullLit && L1 != L2 && L1 != L3 && + (L2 != L3 || L2 == NullLit)); + + const ClauseID C = ClauseStarts.size(); + const size_t S = Clauses.size(); + ClauseStarts.push_back(S); + + Clauses.push_back(L1); + if (L2 != NullLit) + Clauses.push_back(L2); + if (L3 != NullLit) + Clauses.push_back(L3); + + // Designate the first literal as the "watched" literal of the clause. + NextWatched.push_back(WatchedHead[L1]); + WatchedHead[L1] = C; + } + + /// Returns the number of literals in clause `C`. + size_t clauseSize(ClauseID C) const { + return C == ClauseStarts.size() - 1 ? Clauses.size() - ClauseStarts[C] + : ClauseStarts[C + 1] - ClauseStarts[C]; + } + + /// Returns the literals of clause `C`. + llvm::ArrayRef<Literal> clauseLiterals(ClauseID C) const { + return llvm::ArrayRef<Literal>(&Clauses[ClauseStarts[C]], clauseSize(C)); + } +}; + +/// Converts the conjunction of `Vals` into a formula in conjunctive normal +/// form where each clause has at least one and at most three literals. +BooleanFormula buildBooleanFormula(const llvm::DenseSet<BoolValue *> &Vals) { + // The general strategy of the algorithm implemented below is to map each + // of the sub-values in `Vals` to a unique variable and use these variables in + // the resulting CNF expression to avoid exponential blow up. The number of + // literals in the resulting formula is guaranteed to be linear in the number + // of sub-values in `Vals`. + + // Map each sub-value in `Vals` to a unique variable. + llvm::DenseMap<BoolValue *, Variable> SubValsToVar; + Variable NextVar = 1; + { + std::queue<BoolValue *> UnprocessedSubVals; + for (BoolValue *Val : Vals) + UnprocessedSubVals.push(Val); + while (!UnprocessedSubVals.empty()) { + BoolValue *Val = UnprocessedSubVals.front(); + UnprocessedSubVals.pop(); + + if (!SubValsToVar.try_emplace(Val, NextVar).second) + continue; + ++NextVar; + + // Visit the sub-values of `Val`. + if (auto *C = dyn_cast<ConjunctionValue>(Val)) { + UnprocessedSubVals.push(&C->getLeftSubValue()); + UnprocessedSubVals.push(&C->getRightSubValue()); + } else if (auto *D = dyn_cast<DisjunctionValue>(Val)) { + UnprocessedSubVals.push(&D->getLeftSubValue()); + UnprocessedSubVals.push(&D->getRightSubValue()); + } else if (auto *N = dyn_cast<NegationValue>(Val)) { + UnprocessedSubVals.push(&N->getSubVal()); + } + } + } + + auto GetVar = [&SubValsToVar](const BoolValue *Val) { + auto ValIt = SubValsToVar.find(Val); + assert(ValIt != SubValsToVar.end()); + return ValIt->second; + }; + + BooleanFormula Formula(NextVar - 1); + std::vector<bool> ProcessedSubVals(NextVar, false); + + // Add a conjunct for each variable that represents a top-level conjunction + // value in `Vals`. + for (BoolValue *Val : Vals) + Formula.addClause(posLit(GetVar(Val))); + + // Add conjuncts that represent the mapping between newly-created variables + // and their corresponding sub-values. + std::queue<BoolValue *> UnprocessedSubVals; + for (BoolValue *Val : Vals) + UnprocessedSubVals.push(Val); + while (!UnprocessedSubVals.empty()) { + const BoolValue *Val = UnprocessedSubVals.front(); + UnprocessedSubVals.pop(); + const Variable Var = GetVar(Val); + + if (ProcessedSubVals[Var]) + continue; + ProcessedSubVals[Var] = true; + + if (auto *C = dyn_cast<ConjunctionValue>(Val)) { + const Variable LeftSubVar = GetVar(&C->getLeftSubValue()); + const Variable RightSubVar = GetVar(&C->getRightSubValue()); + + // `X <=> (A ^ B)` is equivalent to `(!X v A) ^ (!X v B) ^ (X v !A v !B)` + // which is already in conjunctive normal form. Below we add each of the + // conjuncts of the latter expression to the result. + Formula.addClause(negLit(Var), posLit(LeftSubVar)); + Formula.addClause(negLit(Var), posLit(RightSubVar)); + Formula.addClause(posLit(Var), negLit(LeftSubVar), negLit(RightSubVar)); + + // Visit the sub-values of `Val`. + UnprocessedSubVals.push(&C->getLeftSubValue()); + UnprocessedSubVals.push(&C->getRightSubValue()); + } else if (auto *D = dyn_cast<DisjunctionValue>(Val)) { + const Variable LeftSubVar = GetVar(&D->getLeftSubValue()); + const Variable RightSubVar = GetVar(&D->getRightSubValue()); + + // `X <=> (A v B)` is equivalent to `(!X v A v B) ^ (X v !A) ^ (X v !B)` + // which is already in conjunctive normal form. Below we add each of the + // conjuncts of the latter expression to the result. + Formula.addClause(negLit(Var), posLit(LeftSubVar), posLit(RightSubVar)); + Formula.addClause(posLit(Var), negLit(LeftSubVar)); + Formula.addClause(posLit(Var), negLit(RightSubVar)); + + // Visit the sub-values of `Val`. + UnprocessedSubVals.push(&D->getLeftSubValue()); + UnprocessedSubVals.push(&D->getRightSubValue()); + } else if (auto *N = dyn_cast<NegationValue>(Val)) { + const Variable SubVar = GetVar(&N->getSubVal()); + + // `X <=> !Y` is equivalent to `(!X v !Y) ^ (X v Y)` which is already in + // conjunctive normal form. Below we add each of the conjuncts of the + // latter expression to the result. + Formula.addClause(negLit(Var), negLit(SubVar)); + Formula.addClause(posLit(Var), posLit(SubVar)); + + // Visit the sub-values of `Val`. + UnprocessedSubVals.push(&N->getSubVal()); + } + } + + return Formula; +} + +class WatchedLiteralsSolverImpl { + /// A boolean formula in conjunctive normal form that the solver will attempt + /// to prove satisfiable. The formula will be modified in the process. + BooleanFormula Formula; + + /// The search for a satisfying assignment of the variables in `Formula` will + /// proceed in levels, starting from 1 and going up to `Formula.LargestVar` + /// (inclusive). The current level is stored in `Level`. At each level the + /// solver will assign a value to an unassigned variable. If this leads to a + /// consistent partial assignment, `Level` will be incremented. Otherwise, if + /// it results in a conflict, the solver will backtrack by decrementing + /// `Level` until it reaches the most recent level where a decision was made. + size_t Level = 0; + + /// Maps levels (indices of the vector) to variables (elements of the vector) + /// that are assigned values at the respective levels. + /// + /// The element at index 0 isn't used. Variables start from the element at + /// index 1. + std::vector<Variable> LevelVars; + + /// State of the solver at a particular level. + enum class State : uint8_t { + /// Indicates that the solver made a decision. + Decision = 0, + + /// Indicates that the solver made a forced move. + Forced = 1, + }; + + /// State of the solver at a particular level. It keeps track of previous + /// decisions that the solver can refer to when backtracking. + /// + /// The element at index 0 isn't used. States start from the element at index + /// 1. + std::vector<State> LevelStates; + + enum class Assignment : int8_t { + Unassigned = -1, + AssignedFalse = 0, + AssignedTrue = 1 + }; + + /// Maps variables (indices of the vector) to their assignments (elements of + /// the vector). + /// + /// The element at index 0 isn't used. Variable assignments start from the + /// element at index 1. + std::vector<Assignment> VarAssignments; + + /// A set of unassigned variables that appear in watched literals in + /// `Formula`. The vector is guaranteed to contain unique elements. + std::vector<Variable> ActiveVars; + +public: + explicit WatchedLiteralsSolverImpl(const llvm::DenseSet<BoolValue *> &Vals) + : Formula(buildBooleanFormula(Vals)), LevelVars(Formula.LargestVar + 1), + LevelStates(Formula.LargestVar + 1) { + assert(!Vals.empty()); + + // Initialize the state at the root level to a decision so that in + // `reverseForcedMoves` we don't have to check that `Level >= 0` on each + // iteration. + LevelStates[0] = State::Decision; + + // Initialize all variables as unassigned. + VarAssignments.resize(Formula.LargestVar + 1, Assignment::Unassigned); + + // Initialize the active variables. + for (Variable Var = Formula.LargestVar; Var != NullVar; --Var) { + if (isWatched(posLit(Var)) || isWatched(negLit(Var))) + ActiveVars.push_back(Var); + } + } + + Solver::Result solve() && { + size_t I = 0; + while (I < ActiveVars.size()) { + // Assert that the following invariants hold: + // 1. All active variables are unassigned. + // 2. All active variables form watched literals. + // 3. Unassigned variables that form watched literals are active. + // FIXME: Consider replacing these with test cases that fail if the any + // of the invariants is broken. That might not be easy due to the + // transformations performed by `buildBooleanFormula`. + assert(activeVarsAreUnassigned()); + assert(activeVarsFormWatchedLiterals()); + assert(unassignedVarsFormingWatchedLiteralsAreActive()); + + const Variable ActiveVar = ActiveVars[I]; + + // Look for unit clauses that contain the active variable. + const bool unitPosLit = watchedByUnitClause(posLit(ActiveVar)); + const bool unitNegLit = watchedByUnitClause(negLit(ActiveVar)); + if (unitPosLit && unitNegLit) { + // We found a conflict! + + // Backtrack and rewind the `Level` until the most recent non-forced + // assignment. + reverseForcedMoves(); + + // If the root level is reached, then all possible assignments lead to + // a conflict. + if (Level == 0) + return WatchedLiteralsSolver::Result::Unsatisfiable; + + // Otherwise, take the other branch at the most recent level where a + // decision was made. + LevelStates[Level] = State::Forced; + const Variable Var = LevelVars[Level]; + VarAssignments[Var] = VarAssignments[Var] == Assignment::AssignedTrue + ? Assignment::AssignedFalse + : Assignment::AssignedTrue; + + updateWatchedLiterals(); + } else if (unitPosLit || unitNegLit) { + // We found a unit clause! The value of its unassigned variable is + // forced. + ++Level; + + LevelVars[Level] = ActiveVar; + LevelStates[Level] = State::Forced; + VarAssignments[ActiveVar] = + unitPosLit ? Assignment::AssignedTrue : Assignment::AssignedFalse; + + // Remove the variable that was just assigned from the set of active + // variables. + if (I + 1 < ActiveVars.size()) { + // Replace the variable that was just assigned with the last active + // variable for efficient removal. + ActiveVars[I] = ActiveVars.back(); + } else { + // This was the last active variable. Repeat the process from the + // beginning. + I = 0; + } + ActiveVars.pop_back(); + + updateWatchedLiterals(); + } else if (I + 1 == ActiveVars.size()) { + // There are no remaining unit clauses in the formula! Make a decision + // for one of the active variables at the current level. + ++Level; + + LevelVars[Level] = ActiveVar; + LevelStates[Level] = State::Decision; + VarAssignments[ActiveVar] = decideAssignment(ActiveVar); + + // Remove the variable that was just assigned from the set of active + // variables. + ActiveVars.pop_back(); + + updateWatchedLiterals(); + + // This was the last active variable. Repeat the process from the + // beginning. + I = 0; + } else { + ++I; + } + } + return WatchedLiteralsSolver::Result::Satisfiable; + } + +private: + // Reverses forced moves until the most recent level where a decision was made + // on the assignment of a variable. + void reverseForcedMoves() { + for (; LevelStates[Level] == State::Forced; --Level) { + const Variable Var = LevelVars[Level]; + + VarAssignments[Var] = Assignment::Unassigned; + + // If the variable that we pass through is watched then we add it to the + // active variables. + if (isWatched(posLit(Var)) || isWatched(negLit(Var))) + ActiveVars.push_back(Var); + } + } + + // Updates watched literals that are affected by a variable assignment. + void updateWatchedLiterals() { + const Variable Var = LevelVars[Level]; + + // Update the watched literals of clauses that currently watch the literal + // that falsifies `Var`. + const Literal FalseLit = VarAssignments[Var] == Assignment::AssignedTrue + ? negLit(Var) + : posLit(Var); + ClauseID FalseLitWatcher = Formula.WatchedHead[FalseLit]; + Formula.WatchedHead[FalseLit] = NullClause; + while (FalseLitWatcher != NullClause) { + const ClauseID NextFalseLitWatcher = Formula.NextWatched[FalseLitWatcher]; + + // Pick the first non-false literal as the new watched literal. + const size_t FalseLitWatcherStart = Formula.ClauseStarts[FalseLitWatcher]; + size_t NewWatchedLitIdx = FalseLitWatcherStart + 1; + while (isCurrentlyFalse(Formula.Clauses[NewWatchedLitIdx])) + ++NewWatchedLitIdx; + const Literal NewWatchedLit = Formula.Clauses[NewWatchedLitIdx]; + const Variable NewWatchedLitVar = var(NewWatchedLit); + + // Swap the old watched literal for the new one in `FalseLitWatcher` to + // maintain the invariant that the watched literal is at the beginning of + // the clause. + Formula.Clauses[NewWatchedLitIdx] = FalseLit; + Formula.Clauses[FalseLitWatcherStart] = NewWatchedLit; + + // If the new watched literal isn't watched by any other clause and its + // variable isn't assigned we need to add it to the active variables. + if (!isWatched(NewWatchedLit) && !isWatched(notLit(NewWatchedLit)) && + VarAssignments[NewWatchedLitVar] == Assignment::Unassigned) + ActiveVars.push_back(NewWatchedLitVar); + + Formula.NextWatched[FalseLitWatcher] = Formula.WatchedHead[NewWatchedLit]; + Formula.WatchedHead[NewWatchedLit] = FalseLitWatcher; + + // Go to the next clause that watches `FalseLit`. + FalseLitWatcher = NextFalseLitWatcher; + } + } + + /// Returns true if and only if one of the clauses that watch `Lit` is a unit + /// clause. + bool watchedByUnitClause(Literal Lit) const { + for (ClauseID LitWatcher = Formula.WatchedHead[Lit]; + LitWatcher != NullClause; + LitWatcher = Formula.NextWatched[LitWatcher]) { + llvm::ArrayRef<Literal> Clause = Formula.clauseLiterals(LitWatcher); + + // Assert the invariant that the watched literal is always the first one + // in the clause. + // FIXME: Consider replacing this with a test case that fails if the + // invariant is broken by `updateWatchedLiterals`. That might not be easy + // due to the transformations performed by `buildBooleanFormula`. + assert(Clause.front() == Lit); + + if (isUnit(Clause)) + return true; + } + return false; + } + + /// Returns true if and only if `Clause` is a unit clause. + bool isUnit(llvm::ArrayRef<Literal> Clause) const { + return llvm::all_of(Clause.drop_front(), + [this](Literal L) { return isCurrentlyFalse(L); }); + } + + /// Returns true if and only if `Lit` evaluates to `false` in the current + /// partial assignment. + bool isCurrentlyFalse(Literal Lit) const { + return static_cast<int8_t>(VarAssignments[var(Lit)]) == + static_cast<int8_t>(Lit & 1); + } + + /// Returns true if and only if `Lit` is watched by a clause in `Formula`. + bool isWatched(Literal Lit) const { + return Formula.WatchedHead[Lit] != NullClause; + } + + /// Returns an assignment for an unassigned variable. + Assignment decideAssignment(Variable Var) const { + return !isWatched(posLit(Var)) || isWatched(negLit(Var)) + ? Assignment::AssignedFalse + : Assignment::AssignedTrue; + } + + /// Returns a set of all watched literals. + llvm::DenseSet<Literal> watchedLiterals() const { + llvm::DenseSet<Literal> WatchedLiterals; + for (Literal Lit = 2; Lit < Formula.WatchedHead.size(); Lit++) { + if (Formula.WatchedHead[Lit] == NullClause) + continue; + WatchedLiterals.insert(Lit); + } + return WatchedLiterals; + } + + /// Returns true if and only if all active variables are unassigned. + bool activeVarsAreUnassigned() const { + return llvm::all_of(ActiveVars, [this](Variable Var) { + return VarAssignments[Var] == Assignment::Unassigned; + }); + } + + /// Returns true if and only if all active variables form watched literals. + bool activeVarsFormWatchedLiterals() const { + const llvm::DenseSet<Literal> WatchedLiterals = watchedLiterals(); + return llvm::all_of(ActiveVars, [&WatchedLiterals](Variable Var) { + return WatchedLiterals.contains(posLit(Var)) || + WatchedLiterals.contains(negLit(Var)); + }); + } + + /// Returns true if and only if all unassigned variables that are forming + /// watched literals are active. + bool unassignedVarsFormingWatchedLiteralsAreActive() const { + const llvm::DenseSet<Variable> ActiveVarsSet(ActiveVars.begin(), + ActiveVars.end()); + for (Literal Lit : watchedLiterals()) { + const Variable Var = var(Lit); + if (VarAssignments[Var] != Assignment::Unassigned) + continue; + if (ActiveVarsSet.contains(Var)) + continue; + return false; + } + return true; + } +}; + +Solver::Result WatchedLiteralsSolver::solve(llvm::DenseSet<BoolValue *> Vals) { + return Vals.empty() ? WatchedLiteralsSolver::Result::Satisfiable + : WatchedLiteralsSolverImpl(Vals).solve(); +} + +} // namespace dataflow +} // namespace clang diff --git a/contrib/llvm-project/clang/lib/Analysis/PathDiagnostic.cpp b/contrib/llvm-project/clang/lib/Analysis/PathDiagnostic.cpp index ee8185c2147c..8a7305000746 100644 --- a/contrib/llvm-project/clang/lib/Analysis/PathDiagnostic.cpp +++ b/contrib/llvm-project/clang/lib/Analysis/PathDiagnostic.cpp @@ -319,7 +319,7 @@ static Optional<bool> comparePath(const PathPieces &X, const PathPieces &Y) { for ( ; X_I != X_end && Y_I != Y_end; ++X_I, ++Y_I) { Optional<bool> b = comparePiece(**X_I, **Y_I); - if (b.hasValue()) + if (b) return b.getValue(); } @@ -396,7 +396,7 @@ static bool compare(const PathDiagnostic &X, const PathDiagnostic &Y) { return (*XI) < (*YI); } Optional<bool> b = comparePath(X.path, Y.path); - assert(b.hasValue()); + assert(b); return b.getValue(); } @@ -434,8 +434,8 @@ void PathDiagnosticConsumer::FlushDiagnostics( } PathDiagnosticConsumer::FilesMade::~FilesMade() { - for (PDFileEntry &Entry : Set) - Entry.~PDFileEntry(); + for (auto It = Set.begin(); It != Set.end();) + (It++)->~PDFileEntry(); } void PathDiagnosticConsumer::FilesMade::addDiagnostic(const PathDiagnostic &PD, diff --git a/contrib/llvm-project/clang/lib/Analysis/ReachableCode.cpp b/contrib/llvm-project/clang/lib/Analysis/ReachableCode.cpp index 5be8180113da..1b7d774aeb40 100644 --- a/contrib/llvm-project/clang/lib/Analysis/ReachableCode.cpp +++ b/contrib/llvm-project/clang/lib/Analysis/ReachableCode.cpp @@ -345,13 +345,13 @@ static unsigned scanFromBlock(const CFGBlock *Start, if (!UB) break; - if (!TreatAllSuccessorsAsReachable.hasValue()) { + if (!TreatAllSuccessorsAsReachable) { assert(PP); TreatAllSuccessorsAsReachable = shouldTreatSuccessorsAsReachable(item, *PP); } - if (TreatAllSuccessorsAsReachable.getValue()) { + if (*TreatAllSuccessorsAsReachable) { B = UB; break; } diff --git a/contrib/llvm-project/clang/lib/Analysis/RetainSummaryManager.cpp b/contrib/llvm-project/clang/lib/Analysis/RetainSummaryManager.cpp index 836e369758d3..9098cf370d4f 100644 --- a/contrib/llvm-project/clang/lib/Analysis/RetainSummaryManager.cpp +++ b/contrib/llvm-project/clang/lib/Analysis/RetainSummaryManager.cpp @@ -398,7 +398,7 @@ const RetainSummary *RetainSummaryManager::getSummaryForObjCOrCFObject( } else if (FName.startswith("NSLog")) { return getDoNothingSummary(); } else if (FName.startswith("NS") && FName.contains("Insert")) { - // Whitelist NSXXInsertXX, for example NSMapInsertIfAbsent, since they can + // Allowlist NSXXInsertXX, for example NSMapInsertIfAbsent, since they can // be deallocated by NSMapRemove. (radar://11152419) ScratchArgs = AF.add(ScratchArgs, 1, ArgEffect(StopTracking)); ScratchArgs = AF.add(ScratchArgs, 2, ArgEffect(StopTracking)); diff --git a/contrib/llvm-project/clang/lib/Analysis/ThreadSafety.cpp b/contrib/llvm-project/clang/lib/Analysis/ThreadSafety.cpp index 9cc990bd35a3..03bbf078d7e8 100644 --- a/contrib/llvm-project/clang/lib/Analysis/ThreadSafety.cpp +++ b/contrib/llvm-project/clang/lib/Analysis/ThreadSafety.cpp @@ -41,7 +41,6 @@ #include "llvm/ADT/DenseMap.h" #include "llvm/ADT/ImmutableMap.h" #include "llvm/ADT/Optional.h" -#include "llvm/ADT/PointerIntPair.h" #include "llvm/ADT/STLExtras.h" #include "llvm/ADT/SmallVector.h" #include "llvm/ADT/StringRef.h" @@ -75,7 +74,7 @@ static void warnInvalidLock(ThreadSafetyHandler &Handler, // FIXME: add a note about the attribute location in MutexExp or D if (Loc.isValid()) - Handler.handleInvalidLockExp(Kind, Loc); + Handler.handleInvalidLockExp(Loc); } namespace { @@ -140,12 +139,12 @@ public: SourceLocation JoinLoc, LockErrorKind LEK, ThreadSafetyHandler &Handler) const = 0; virtual void handleLock(FactSet &FSet, FactManager &FactMan, - const FactEntry &entry, ThreadSafetyHandler &Handler, - StringRef DiagKind) const = 0; + const FactEntry &entry, + ThreadSafetyHandler &Handler) const = 0; virtual void handleUnlock(FactSet &FSet, FactManager &FactMan, const CapabilityExpr &Cp, SourceLocation UnlockLoc, - bool FullyRemove, ThreadSafetyHandler &Handler, - StringRef DiagKind) const = 0; + bool FullyRemove, + ThreadSafetyHandler &Handler) const = 0; // Return true if LKind >= LK, where exclusive > shared bool isAtLeast(LockKind LK) const { @@ -866,21 +865,21 @@ public: SourceLocation JoinLoc, LockErrorKind LEK, ThreadSafetyHandler &Handler) const override { if (!asserted() && !negative() && !isUniversal()) { - Handler.handleMutexHeldEndOfScope("mutex", toString(), loc(), JoinLoc, + Handler.handleMutexHeldEndOfScope(getKind(), toString(), loc(), JoinLoc, LEK); } } void handleLock(FactSet &FSet, FactManager &FactMan, const FactEntry &entry, - ThreadSafetyHandler &Handler, - StringRef DiagKind) const override { - Handler.handleDoubleLock(DiagKind, entry.toString(), loc(), entry.loc()); + ThreadSafetyHandler &Handler) const override { + Handler.handleDoubleLock(entry.getKind(), entry.toString(), loc(), + entry.loc()); } void handleUnlock(FactSet &FSet, FactManager &FactMan, const CapabilityExpr &Cp, SourceLocation UnlockLoc, - bool FullyRemove, ThreadSafetyHandler &Handler, - StringRef DiagKind) const override { + bool FullyRemove, + ThreadSafetyHandler &Handler) const override { FSet.removeLock(FactMan, Cp); if (!Cp.negative()) { FSet.addLock(FactMan, std::make_unique<LockableFactEntry>( @@ -897,25 +896,27 @@ private: UCK_ReleasedExclusive, ///< Exclusive capability that was released. }; - using UnderlyingCapability = - llvm::PointerIntPair<const til::SExpr *, 2, UnderlyingCapabilityKind>; + struct UnderlyingCapability { + CapabilityExpr Cap; + UnderlyingCapabilityKind Kind; + }; - SmallVector<UnderlyingCapability, 4> UnderlyingMutexes; + SmallVector<UnderlyingCapability, 2> UnderlyingMutexes; public: ScopedLockableFactEntry(const CapabilityExpr &CE, SourceLocation Loc) : FactEntry(CE, LK_Exclusive, Loc, Acquired) {} void addLock(const CapabilityExpr &M) { - UnderlyingMutexes.emplace_back(M.sexpr(), UCK_Acquired); + UnderlyingMutexes.push_back(UnderlyingCapability{M, UCK_Acquired}); } void addExclusiveUnlock(const CapabilityExpr &M) { - UnderlyingMutexes.emplace_back(M.sexpr(), UCK_ReleasedExclusive); + UnderlyingMutexes.push_back(UnderlyingCapability{M, UCK_ReleasedExclusive}); } void addSharedUnlock(const CapabilityExpr &M) { - UnderlyingMutexes.emplace_back(M.sexpr(), UCK_ReleasedShared); + UnderlyingMutexes.push_back(UnderlyingCapability{M, UCK_ReleasedShared}); } void @@ -923,51 +924,45 @@ public: SourceLocation JoinLoc, LockErrorKind LEK, ThreadSafetyHandler &Handler) const override { for (const auto &UnderlyingMutex : UnderlyingMutexes) { - const auto *Entry = FSet.findLock( - FactMan, CapabilityExpr(UnderlyingMutex.getPointer(), false)); - if ((UnderlyingMutex.getInt() == UCK_Acquired && Entry) || - (UnderlyingMutex.getInt() != UCK_Acquired && !Entry)) { + const auto *Entry = FSet.findLock(FactMan, UnderlyingMutex.Cap); + if ((UnderlyingMutex.Kind == UCK_Acquired && Entry) || + (UnderlyingMutex.Kind != UCK_Acquired && !Entry)) { // If this scoped lock manages another mutex, and if the underlying // mutex is still/not held, then warn about the underlying mutex. - Handler.handleMutexHeldEndOfScope( - "mutex", sx::toString(UnderlyingMutex.getPointer()), loc(), JoinLoc, - LEK); + Handler.handleMutexHeldEndOfScope(UnderlyingMutex.Cap.getKind(), + UnderlyingMutex.Cap.toString(), loc(), + JoinLoc, LEK); } } } void handleLock(FactSet &FSet, FactManager &FactMan, const FactEntry &entry, - ThreadSafetyHandler &Handler, - StringRef DiagKind) const override { + ThreadSafetyHandler &Handler) const override { for (const auto &UnderlyingMutex : UnderlyingMutexes) { - CapabilityExpr UnderCp(UnderlyingMutex.getPointer(), false); - - if (UnderlyingMutex.getInt() == UCK_Acquired) - lock(FSet, FactMan, UnderCp, entry.kind(), entry.loc(), &Handler, - DiagKind); + if (UnderlyingMutex.Kind == UCK_Acquired) + lock(FSet, FactMan, UnderlyingMutex.Cap, entry.kind(), entry.loc(), + &Handler); else - unlock(FSet, FactMan, UnderCp, entry.loc(), &Handler, DiagKind); + unlock(FSet, FactMan, UnderlyingMutex.Cap, entry.loc(), &Handler); } } void handleUnlock(FactSet &FSet, FactManager &FactMan, const CapabilityExpr &Cp, SourceLocation UnlockLoc, - bool FullyRemove, ThreadSafetyHandler &Handler, - StringRef DiagKind) const override { + bool FullyRemove, + ThreadSafetyHandler &Handler) const override { assert(!Cp.negative() && "Managing object cannot be negative."); for (const auto &UnderlyingMutex : UnderlyingMutexes) { - CapabilityExpr UnderCp(UnderlyingMutex.getPointer(), false); - // Remove/lock the underlying mutex if it exists/is still unlocked; warn // on double unlocking/locking if we're not destroying the scoped object. ThreadSafetyHandler *TSHandler = FullyRemove ? nullptr : &Handler; - if (UnderlyingMutex.getInt() == UCK_Acquired) { - unlock(FSet, FactMan, UnderCp, UnlockLoc, TSHandler, DiagKind); + if (UnderlyingMutex.Kind == UCK_Acquired) { + unlock(FSet, FactMan, UnderlyingMutex.Cap, UnlockLoc, TSHandler); } else { - LockKind kind = UnderlyingMutex.getInt() == UCK_ReleasedShared + LockKind kind = UnderlyingMutex.Kind == UCK_ReleasedShared ? LK_Shared : LK_Exclusive; - lock(FSet, FactMan, UnderCp, kind, UnlockLoc, TSHandler, DiagKind); + lock(FSet, FactMan, UnderlyingMutex.Cap, kind, UnlockLoc, TSHandler); } } if (FullyRemove) @@ -976,11 +971,12 @@ public: private: void lock(FactSet &FSet, FactManager &FactMan, const CapabilityExpr &Cp, - LockKind kind, SourceLocation loc, ThreadSafetyHandler *Handler, - StringRef DiagKind) const { + LockKind kind, SourceLocation loc, + ThreadSafetyHandler *Handler) const { if (const FactEntry *Fact = FSet.findLock(FactMan, Cp)) { if (Handler) - Handler->handleDoubleLock(DiagKind, Cp.toString(), Fact->loc(), loc); + Handler->handleDoubleLock(Cp.getKind(), Cp.toString(), Fact->loc(), + loc); } else { FSet.removeLock(FactMan, !Cp); FSet.addLock(FactMan, @@ -989,8 +985,7 @@ private: } void unlock(FactSet &FSet, FactManager &FactMan, const CapabilityExpr &Cp, - SourceLocation loc, ThreadSafetyHandler *Handler, - StringRef DiagKind) const { + SourceLocation loc, ThreadSafetyHandler *Handler) const { if (FSet.findLock(FactMan, Cp)) { FSet.removeLock(FactMan, Cp); FSet.addLock(FactMan, std::make_unique<LockableFactEntry>( @@ -999,7 +994,7 @@ private: SourceLocation PrevLoc; if (const FactEntry *Neg = FSet.findLock(FactMan, !Cp)) PrevLoc = Neg->loc(); - Handler->handleUnmatchedUnlock(DiagKind, Cp.toString(), loc, PrevLoc); + Handler->handleUnmatchedUnlock(Cp.getKind(), Cp.toString(), loc, PrevLoc); } } }; @@ -1028,10 +1023,9 @@ public: bool inCurrentScope(const CapabilityExpr &CapE); void addLock(FactSet &FSet, std::unique_ptr<FactEntry> Entry, - StringRef DiagKind, bool ReqAttr = false); + bool ReqAttr = false); void removeLock(FactSet &FSet, const CapabilityExpr &CapE, - SourceLocation UnlockLoc, bool FullyRemove, LockKind Kind, - StringRef DiagKind); + SourceLocation UnlockLoc, bool FullyRemove, LockKind Kind); template <typename AttrType> void getMutexIDs(CapExprSet &Mtxs, AttrType *Attr, const Expr *Exp, @@ -1219,53 +1213,6 @@ public: } // namespace -static StringRef ClassifyDiagnostic(const CapabilityAttr *A) { - return A->getName(); -} - -static StringRef ClassifyDiagnostic(QualType VDT) { - // We need to look at the declaration of the type of the value to determine - // which it is. The type should either be a record or a typedef, or a pointer - // or reference thereof. - if (const auto *RT = VDT->getAs<RecordType>()) { - if (const auto *RD = RT->getDecl()) - if (const auto *CA = RD->getAttr<CapabilityAttr>()) - return ClassifyDiagnostic(CA); - } else if (const auto *TT = VDT->getAs<TypedefType>()) { - if (const auto *TD = TT->getDecl()) - if (const auto *CA = TD->getAttr<CapabilityAttr>()) - return ClassifyDiagnostic(CA); - } else if (VDT->isPointerType() || VDT->isReferenceType()) - return ClassifyDiagnostic(VDT->getPointeeType()); - - return "mutex"; -} - -static StringRef ClassifyDiagnostic(const ValueDecl *VD) { - assert(VD && "No ValueDecl passed"); - - // The ValueDecl is the declaration of a mutex or role (hopefully). - return ClassifyDiagnostic(VD->getType()); -} - -template <typename AttrTy> -static std::enable_if_t<!has_arg_iterator_range<AttrTy>::value, StringRef> -ClassifyDiagnostic(const AttrTy *A) { - if (const ValueDecl *VD = getValueDecl(A->getArg())) - return ClassifyDiagnostic(VD); - return "mutex"; -} - -template <typename AttrTy> -static std::enable_if_t<has_arg_iterator_range<AttrTy>::value, StringRef> -ClassifyDiagnostic(const AttrTy *A) { - for (const auto *Arg : A->args()) { - if (const ValueDecl *VD = getValueDecl(Arg)) - return ClassifyDiagnostic(VD); - } - return "mutex"; -} - bool ThreadSafetyAnalyzer::inCurrentScope(const CapabilityExpr &CapE) { const threadSafety::til::SExpr *SExp = CapE.sexpr(); assert(SExp && "Null expressions should be ignored"); @@ -1297,7 +1244,7 @@ bool ThreadSafetyAnalyzer::inCurrentScope(const CapabilityExpr &CapE) { /// \param ReqAttr -- true if this is part of an initial Requires attribute. void ThreadSafetyAnalyzer::addLock(FactSet &FSet, std::unique_ptr<FactEntry> Entry, - StringRef DiagKind, bool ReqAttr) { + bool ReqAttr) { if (Entry->shouldIgnore()) return; @@ -1310,7 +1257,7 @@ void ThreadSafetyAnalyzer::addLock(FactSet &FSet, } else { if (inCurrentScope(*Entry) && !Entry->asserted()) - Handler.handleNegativeNotHeld(DiagKind, Entry->toString(), + Handler.handleNegativeNotHeld(Entry->getKind(), Entry->toString(), NegC.toString(), Entry->loc()); } } @@ -1319,13 +1266,13 @@ void ThreadSafetyAnalyzer::addLock(FactSet &FSet, if (Handler.issueBetaWarnings() && !Entry->asserted() && !Entry->declared()) { GlobalBeforeSet->checkBeforeAfter(Entry->valueDecl(), FSet, *this, - Entry->loc(), DiagKind); + Entry->loc(), Entry->getKind()); } // FIXME: Don't always warn when we have support for reentrant locks. if (const FactEntry *Cp = FSet.findLock(FactMan, *Entry)) { if (!Entry->asserted()) - Cp->handleLock(FSet, FactMan, *Entry, Handler, DiagKind); + Cp->handleLock(FSet, FactMan, *Entry, Handler); } else { FSet.addLock(FactMan, std::move(Entry)); } @@ -1335,8 +1282,7 @@ void ThreadSafetyAnalyzer::addLock(FactSet &FSet, /// \param UnlockLoc The source location of the unlock (only used in error msg) void ThreadSafetyAnalyzer::removeLock(FactSet &FSet, const CapabilityExpr &Cp, SourceLocation UnlockLoc, - bool FullyRemove, LockKind ReceivedKind, - StringRef DiagKind) { + bool FullyRemove, LockKind ReceivedKind) { if (Cp.shouldIgnore()) return; @@ -1345,19 +1291,19 @@ void ThreadSafetyAnalyzer::removeLock(FactSet &FSet, const CapabilityExpr &Cp, SourceLocation PrevLoc; if (const FactEntry *Neg = FSet.findLock(FactMan, !Cp)) PrevLoc = Neg->loc(); - Handler.handleUnmatchedUnlock(DiagKind, Cp.toString(), UnlockLoc, PrevLoc); + Handler.handleUnmatchedUnlock(Cp.getKind(), Cp.toString(), UnlockLoc, + PrevLoc); return; } // Generic lock removal doesn't care about lock kind mismatches, but // otherwise diagnose when the lock kinds are mismatched. if (ReceivedKind != LK_Generic && LDat->kind() != ReceivedKind) { - Handler.handleIncorrectUnlockKind(DiagKind, Cp.toString(), LDat->kind(), + Handler.handleIncorrectUnlockKind(Cp.getKind(), Cp.toString(), LDat->kind(), ReceivedKind, LDat->loc(), UnlockLoc); } - LDat->handleUnlock(FSet, FactMan, Cp, UnlockLoc, FullyRemove, Handler, - DiagKind); + LDat->handleUnlock(FSet, FactMan, Cp, UnlockLoc, FullyRemove, Handler); } /// Extract the list of mutexIDs from the attribute on an expression, @@ -1370,8 +1316,8 @@ void ThreadSafetyAnalyzer::getMutexIDs(CapExprSet &Mtxs, AttrType *Attr, // The mutex held is the "this" object. CapabilityExpr Cp = SxBuilder.translateAttrExpr(nullptr, D, Exp, SelfDecl); if (Cp.isInvalid()) { - warnInvalidLock(Handler, nullptr, D, Exp, ClassifyDiagnostic(Attr)); - return; + warnInvalidLock(Handler, nullptr, D, Exp, Cp.getKind()); + return; } //else if (!Cp.shouldIgnore()) @@ -1382,8 +1328,8 @@ void ThreadSafetyAnalyzer::getMutexIDs(CapExprSet &Mtxs, AttrType *Attr, for (const auto *Arg : Attr->args()) { CapabilityExpr Cp = SxBuilder.translateAttrExpr(Arg, D, Exp, SelfDecl); if (Cp.isInvalid()) { - warnInvalidLock(Handler, nullptr, D, Exp, ClassifyDiagnostic(Attr)); - continue; + warnInvalidLock(Handler, nullptr, D, Exp, Cp.getKind()); + continue; } //else if (!Cp.shouldIgnore()) @@ -1522,7 +1468,6 @@ void ThreadSafetyAnalyzer::getEdgeLockset(FactSet& Result, bool Negate = false; const CFGBlockInfo *PredBlockInfo = &BlockInfo[PredBlock->getBlockID()]; const LocalVarContext &LVarCtx = PredBlockInfo->ExitContext; - StringRef CapDiagKind = "mutex"; const auto *Exp = getTrylockCallExpr(Cond, LVarCtx, Negate); if (!Exp) @@ -1543,21 +1488,18 @@ void ThreadSafetyAnalyzer::getEdgeLockset(FactSet& Result, getMutexIDs(A->isShared() ? SharedLocksToAdd : ExclusiveLocksToAdd, A, Exp, FunDecl, PredBlock, CurrBlock, A->getSuccessValue(), Negate); - CapDiagKind = ClassifyDiagnostic(A); break; }; case attr::ExclusiveTrylockFunction: { const auto *A = cast<ExclusiveTrylockFunctionAttr>(Attr); - getMutexIDs(ExclusiveLocksToAdd, A, Exp, FunDecl, - PredBlock, CurrBlock, A->getSuccessValue(), Negate); - CapDiagKind = ClassifyDiagnostic(A); + getMutexIDs(ExclusiveLocksToAdd, A, Exp, FunDecl, PredBlock, CurrBlock, + A->getSuccessValue(), Negate); break; } case attr::SharedTrylockFunction: { const auto *A = cast<SharedTrylockFunctionAttr>(Attr); - getMutexIDs(SharedLocksToAdd, A, Exp, FunDecl, - PredBlock, CurrBlock, A->getSuccessValue(), Negate); - CapDiagKind = ClassifyDiagnostic(A); + getMutexIDs(SharedLocksToAdd, A, Exp, FunDecl, PredBlock, CurrBlock, + A->getSuccessValue(), Negate); break; } default: @@ -1569,12 +1511,10 @@ void ThreadSafetyAnalyzer::getEdgeLockset(FactSet& Result, SourceLocation Loc = Exp->getExprLoc(); for (const auto &ExclusiveLockToAdd : ExclusiveLocksToAdd) addLock(Result, std::make_unique<LockableFactEntry>(ExclusiveLockToAdd, - LK_Exclusive, Loc), - CapDiagKind); + LK_Exclusive, Loc)); for (const auto &SharedLockToAdd : SharedLocksToAdd) addLock(Result, std::make_unique<LockableFactEntry>(SharedLockToAdd, - LK_Shared, Loc), - CapDiagKind); + LK_Shared, Loc)); } namespace { @@ -1595,9 +1535,8 @@ class BuildLockset : public ConstStmtVisitor<BuildLockset> { // helper functions void warnIfMutexNotHeld(const NamedDecl *D, const Expr *Exp, AccessKind AK, Expr *MutexExp, ProtectedOperationKind POK, - StringRef DiagKind, SourceLocation Loc); - void warnIfMutexHeld(const NamedDecl *D, const Expr *Exp, Expr *MutexExp, - StringRef DiagKind); + SourceLocation Loc); + void warnIfMutexHeld(const NamedDecl *D, const Expr *Exp, Expr *MutexExp); void checkAccess(const Expr *Exp, AccessKind AK, ProtectedOperationKind POK = POK_VarAccess); @@ -1630,12 +1569,12 @@ public: void BuildLockset::warnIfMutexNotHeld(const NamedDecl *D, const Expr *Exp, AccessKind AK, Expr *MutexExp, ProtectedOperationKind POK, - StringRef DiagKind, SourceLocation Loc) { + SourceLocation Loc) { LockKind LK = getLockKindFromAccessKind(AK); CapabilityExpr Cp = Analyzer->SxBuilder.translateAttrExpr(MutexExp, D, Exp); if (Cp.isInvalid()) { - warnInvalidLock(Analyzer->Handler, MutexExp, D, Exp, DiagKind); + warnInvalidLock(Analyzer->Handler, MutexExp, D, Exp, Cp.getKind()); return; } else if (Cp.shouldIgnore()) { return; @@ -1646,7 +1585,7 @@ void BuildLockset::warnIfMutexNotHeld(const NamedDecl *D, const Expr *Exp, const FactEntry *LDat = FSet.findLock(Analyzer->FactMan, !Cp); if (LDat) { Analyzer->Handler.handleFunExcludesLock( - DiagKind, D->getNameAsString(), (!Cp).toString(), Loc); + Cp.getKind(), D->getNameAsString(), (!Cp).toString(), Loc); return; } @@ -1672,28 +1611,28 @@ void BuildLockset::warnIfMutexNotHeld(const NamedDecl *D, const Expr *Exp, // Warn that there's no precise match. std::string PartMatchStr = LDat->toString(); StringRef PartMatchName(PartMatchStr); - Analyzer->Handler.handleMutexNotHeld(DiagKind, D, POK, Cp.toString(), + Analyzer->Handler.handleMutexNotHeld(Cp.getKind(), D, POK, Cp.toString(), LK, Loc, &PartMatchName); } else { // Warn that there's no match at all. - Analyzer->Handler.handleMutexNotHeld(DiagKind, D, POK, Cp.toString(), + Analyzer->Handler.handleMutexNotHeld(Cp.getKind(), D, POK, Cp.toString(), LK, Loc); } NoError = false; } // Make sure the mutex we found is the right kind. if (NoError && LDat && !LDat->isAtLeast(LK)) { - Analyzer->Handler.handleMutexNotHeld(DiagKind, D, POK, Cp.toString(), + Analyzer->Handler.handleMutexNotHeld(Cp.getKind(), D, POK, Cp.toString(), LK, Loc); } } /// Warn if the LSet contains the given lock. void BuildLockset::warnIfMutexHeld(const NamedDecl *D, const Expr *Exp, - Expr *MutexExp, StringRef DiagKind) { + Expr *MutexExp) { CapabilityExpr Cp = Analyzer->SxBuilder.translateAttrExpr(MutexExp, D, Exp); if (Cp.isInvalid()) { - warnInvalidLock(Analyzer->Handler, MutexExp, D, Exp, DiagKind); + warnInvalidLock(Analyzer->Handler, MutexExp, D, Exp, Cp.getKind()); return; } else if (Cp.shouldIgnore()) { return; @@ -1701,8 +1640,8 @@ void BuildLockset::warnIfMutexHeld(const NamedDecl *D, const Expr *Exp, const FactEntry *LDat = FSet.findLock(Analyzer->FactMan, Cp); if (LDat) { - Analyzer->Handler.handleFunExcludesLock( - DiagKind, D->getNameAsString(), Cp.toString(), Exp->getExprLoc()); + Analyzer->Handler.handleFunExcludesLock(Cp.getKind(), D->getNameAsString(), + Cp.toString(), Exp->getExprLoc()); } } @@ -1757,12 +1696,11 @@ void BuildLockset::checkAccess(const Expr *Exp, AccessKind AK, return; if (D->hasAttr<GuardedVarAttr>() && FSet.isEmpty(Analyzer->FactMan)) { - Analyzer->Handler.handleNoMutexHeld("mutex", D, POK, AK, Loc); + Analyzer->Handler.handleNoMutexHeld(D, POK, AK, Loc); } for (const auto *I : D->specific_attrs<GuardedByAttr>()) - warnIfMutexNotHeld(D, Exp, AK, I->getArg(), POK, - ClassifyDiagnostic(I), Loc); + warnIfMutexNotHeld(D, Exp, AK, I->getArg(), POK, Loc); } /// Checks pt_guarded_by and pt_guarded_var attributes. @@ -1796,12 +1734,10 @@ void BuildLockset::checkPtAccess(const Expr *Exp, AccessKind AK, return; if (D->hasAttr<PtGuardedVarAttr>() && FSet.isEmpty(Analyzer->FactMan)) - Analyzer->Handler.handleNoMutexHeld("mutex", D, PtPOK, AK, - Exp->getExprLoc()); + Analyzer->Handler.handleNoMutexHeld(D, PtPOK, AK, Exp->getExprLoc()); for (auto const *I : D->specific_attrs<PtGuardedByAttr>()) - warnIfMutexNotHeld(D, Exp, AK, I->getArg(), PtPOK, - ClassifyDiagnostic(I), Exp->getExprLoc()); + warnIfMutexNotHeld(D, Exp, AK, I->getArg(), PtPOK, Exp->getExprLoc()); } /// Process a function call, method call, constructor call, @@ -1820,7 +1756,6 @@ void BuildLockset::handleCall(const Expr *Exp, const NamedDecl *D, CapExprSet ExclusiveLocksToAdd, SharedLocksToAdd; CapExprSet ExclusiveLocksToRemove, SharedLocksToRemove, GenericLocksToRemove; CapExprSet ScopedReqsAndExcludes; - StringRef CapDiagKind = "mutex"; // Figure out if we're constructing an object of scoped lockable class bool isScopedVar = false; @@ -1841,8 +1776,6 @@ void BuildLockset::handleCall(const Expr *Exp, const NamedDecl *D, Analyzer->getMutexIDs(A->isShared() ? SharedLocksToAdd : ExclusiveLocksToAdd, A, Exp, D, VD); - - CapDiagKind = ClassifyDiagnostic(A); break; } @@ -1856,10 +1789,8 @@ void BuildLockset::handleCall(const Expr *Exp, const NamedDecl *D, Analyzer->getMutexIDs(AssertLocks, A, Exp, D, VD); for (const auto &AssertLock : AssertLocks) Analyzer->addLock( - FSet, - std::make_unique<LockableFactEntry>(AssertLock, LK_Exclusive, Loc, - FactEntry::Asserted), - ClassifyDiagnostic(A)); + FSet, std::make_unique<LockableFactEntry>( + AssertLock, LK_Exclusive, Loc, FactEntry::Asserted)); break; } case attr::AssertSharedLock: { @@ -1869,10 +1800,8 @@ void BuildLockset::handleCall(const Expr *Exp, const NamedDecl *D, Analyzer->getMutexIDs(AssertLocks, A, Exp, D, VD); for (const auto &AssertLock : AssertLocks) Analyzer->addLock( - FSet, - std::make_unique<LockableFactEntry>(AssertLock, LK_Shared, Loc, - FactEntry::Asserted), - ClassifyDiagnostic(A)); + FSet, std::make_unique<LockableFactEntry>( + AssertLock, LK_Shared, Loc, FactEntry::Asserted)); break; } @@ -1881,12 +1810,10 @@ void BuildLockset::handleCall(const Expr *Exp, const NamedDecl *D, CapExprSet AssertLocks; Analyzer->getMutexIDs(AssertLocks, A, Exp, D, VD); for (const auto &AssertLock : AssertLocks) - Analyzer->addLock(FSet, - std::make_unique<LockableFactEntry>( - AssertLock, - A->isShared() ? LK_Shared : LK_Exclusive, Loc, - FactEntry::Asserted), - ClassifyDiagnostic(A)); + Analyzer->addLock(FSet, std::make_unique<LockableFactEntry>( + AssertLock, + A->isShared() ? LK_Shared : LK_Exclusive, + Loc, FactEntry::Asserted)); break; } @@ -1900,8 +1827,6 @@ void BuildLockset::handleCall(const Expr *Exp, const NamedDecl *D, Analyzer->getMutexIDs(SharedLocksToRemove, A, Exp, D, VD); else Analyzer->getMutexIDs(ExclusiveLocksToRemove, A, Exp, D, VD); - - CapDiagKind = ClassifyDiagnostic(A); break; } @@ -1909,8 +1834,7 @@ void BuildLockset::handleCall(const Expr *Exp, const NamedDecl *D, const auto *A = cast<RequiresCapabilityAttr>(At); for (auto *Arg : A->args()) { warnIfMutexNotHeld(D, Exp, A->isShared() ? AK_Read : AK_Written, Arg, - POK_FunctionCall, ClassifyDiagnostic(A), - Exp->getExprLoc()); + POK_FunctionCall, Exp->getExprLoc()); // use for adopting a lock if (isScopedVar) Analyzer->getMutexIDs(ScopedReqsAndExcludes, A, Exp, D, VD); @@ -1921,7 +1845,7 @@ void BuildLockset::handleCall(const Expr *Exp, const NamedDecl *D, case attr::LocksExcluded: { const auto *A = cast<LocksExcludedAttr>(At); for (auto *Arg : A->args()) { - warnIfMutexHeld(D, Exp, Arg, ClassifyDiagnostic(A)); + warnIfMutexHeld(D, Exp, Arg); // use for deferring a lock if (isScopedVar) Analyzer->getMutexIDs(ScopedReqsAndExcludes, A, Exp, D, VD); @@ -1939,23 +1863,21 @@ void BuildLockset::handleCall(const Expr *Exp, const NamedDecl *D, // FIXME -- should only fully remove if the attribute refers to 'this'. bool Dtor = isa<CXXDestructorDecl>(D); for (const auto &M : ExclusiveLocksToRemove) - Analyzer->removeLock(FSet, M, Loc, Dtor, LK_Exclusive, CapDiagKind); + Analyzer->removeLock(FSet, M, Loc, Dtor, LK_Exclusive); for (const auto &M : SharedLocksToRemove) - Analyzer->removeLock(FSet, M, Loc, Dtor, LK_Shared, CapDiagKind); + Analyzer->removeLock(FSet, M, Loc, Dtor, LK_Shared); for (const auto &M : GenericLocksToRemove) - Analyzer->removeLock(FSet, M, Loc, Dtor, LK_Generic, CapDiagKind); + Analyzer->removeLock(FSet, M, Loc, Dtor, LK_Generic); // Add locks. FactEntry::SourceKind Source = isScopedVar ? FactEntry::Managed : FactEntry::Acquired; for (const auto &M : ExclusiveLocksToAdd) - Analyzer->addLock( - FSet, std::make_unique<LockableFactEntry>(M, LK_Exclusive, Loc, Source), - CapDiagKind); + Analyzer->addLock(FSet, std::make_unique<LockableFactEntry>(M, LK_Exclusive, + Loc, Source)); for (const auto &M : SharedLocksToAdd) Analyzer->addLock( - FSet, std::make_unique<LockableFactEntry>(M, LK_Shared, Loc, Source), - CapDiagKind); + FSet, std::make_unique<LockableFactEntry>(M, LK_Shared, Loc, Source)); if (isScopedVar) { // Add the managing object as a dummy mutex, mapped to the underlying mutex. @@ -1976,7 +1898,7 @@ void BuildLockset::handleCall(const Expr *Exp, const NamedDecl *D, ScopedEntry->addExclusiveUnlock(M); for (const auto &M : SharedLocksToRemove) ScopedEntry->addSharedUnlock(M); - Analyzer->addLock(FSet, std::move(ScopedEntry), CapDiagKind); + Analyzer->addLock(FSet, std::move(ScopedEntry)); } } @@ -2066,16 +1988,27 @@ void BuildLockset::VisitCallExpr(const CallExpr *Exp) { examineArguments(CE->getDirectCallee(), CE->arg_begin(), CE->arg_end()); } else if (const auto *OE = dyn_cast<CXXOperatorCallExpr>(Exp)) { - auto OEop = OE->getOperator(); + OverloadedOperatorKind OEop = OE->getOperator(); switch (OEop) { - case OO_Equal: { - const Expr *Target = OE->getArg(0); - const Expr *Source = OE->getArg(1); - checkAccess(Target, AK_Written); - checkAccess(Source, AK_Read); + case OO_Equal: + case OO_PlusEqual: + case OO_MinusEqual: + case OO_StarEqual: + case OO_SlashEqual: + case OO_PercentEqual: + case OO_CaretEqual: + case OO_AmpEqual: + case OO_PipeEqual: + case OO_LessLessEqual: + case OO_GreaterGreaterEqual: + checkAccess(OE->getArg(1), AK_Read); + LLVM_FALLTHROUGH; + case OO_PlusPlus: + case OO_MinusMinus: + checkAccess(OE->getArg(0), AK_Written); break; - } case OO_Star: + case OO_ArrowStar: case OO_Arrow: case OO_Subscript: if (!(OEop == OO_Star && OE->getNumArgs() > 1)) { @@ -2204,7 +2137,8 @@ bool ThreadSafetyAnalyzer::join(const FactEntry &A, const FactEntry &B, if (CanModify || !ShouldTakeB) return ShouldTakeB; } - Handler.handleExclusiveAndShared("mutex", B.toString(), B.loc(), A.loc()); + Handler.handleExclusiveAndShared(B.getKind(), B.toString(), B.loc(), + A.loc()); // Take the exclusive capability to reduce further warnings. return CanModify && B.kind() == LK_Exclusive; } else { @@ -2344,7 +2278,6 @@ void ThreadSafetyAnalyzer::runAnalysis(AnalysisDeclContext &AC) { CapExprSet ExclusiveLocksToAdd; CapExprSet SharedLocksToAdd; - StringRef CapDiagKind = "mutex"; SourceLocation Loc = D->getLocation(); for (const auto *Attr : D->attrs()) { @@ -2352,7 +2285,6 @@ void ThreadSafetyAnalyzer::runAnalysis(AnalysisDeclContext &AC) { if (const auto *A = dyn_cast<RequiresCapabilityAttr>(Attr)) { getMutexIDs(A->isShared() ? SharedLocksToAdd : ExclusiveLocksToAdd, A, nullptr, D); - CapDiagKind = ClassifyDiagnostic(A); } else if (const auto *A = dyn_cast<ReleaseCapabilityAttr>(Attr)) { // UNLOCK_FUNCTION() is used to hide the underlying lock implementation. // We must ignore such methods. @@ -2361,14 +2293,12 @@ void ThreadSafetyAnalyzer::runAnalysis(AnalysisDeclContext &AC) { getMutexIDs(A->isShared() ? SharedLocksToAdd : ExclusiveLocksToAdd, A, nullptr, D); getMutexIDs(LocksReleased, A, nullptr, D); - CapDiagKind = ClassifyDiagnostic(A); } else if (const auto *A = dyn_cast<AcquireCapabilityAttr>(Attr)) { if (A->args_size() == 0) return; getMutexIDs(A->isShared() ? SharedLocksAcquired : ExclusiveLocksAcquired, A, nullptr, D); - CapDiagKind = ClassifyDiagnostic(A); } else if (isa<ExclusiveTrylockFunctionAttr>(Attr)) { // Don't try to check trylock functions for now. return; @@ -2385,12 +2315,12 @@ void ThreadSafetyAnalyzer::runAnalysis(AnalysisDeclContext &AC) { for (const auto &Mu : ExclusiveLocksToAdd) { auto Entry = std::make_unique<LockableFactEntry>(Mu, LK_Exclusive, Loc, FactEntry::Declared); - addLock(InitialLockset, std::move(Entry), CapDiagKind, true); + addLock(InitialLockset, std::move(Entry), true); } for (const auto &Mu : SharedLocksToAdd) { auto Entry = std::make_unique<LockableFactEntry>(Mu, LK_Shared, Loc, FactEntry::Declared); - addLock(InitialLockset, std::move(Entry), CapDiagKind, true); + addLock(InitialLockset, std::move(Entry), true); } } diff --git a/contrib/llvm-project/clang/lib/Analysis/ThreadSafetyCommon.cpp b/contrib/llvm-project/clang/lib/Analysis/ThreadSafetyCommon.cpp index e6b4a05501e2..66413f84907a 100644 --- a/contrib/llvm-project/clang/lib/Analysis/ThreadSafetyCommon.cpp +++ b/contrib/llvm-project/clang/lib/Analysis/ThreadSafetyCommon.cpp @@ -86,6 +86,28 @@ static bool isCalleeArrow(const Expr *E) { return ME ? ME->isArrow() : false; } +static StringRef ClassifyDiagnostic(const CapabilityAttr *A) { + return A->getName(); +} + +static StringRef ClassifyDiagnostic(QualType VDT) { + // We need to look at the declaration of the type of the value to determine + // which it is. The type should either be a record or a typedef, or a pointer + // or reference thereof. + if (const auto *RT = VDT->getAs<RecordType>()) { + if (const auto *RD = RT->getDecl()) + if (const auto *CA = RD->getAttr<CapabilityAttr>()) + return ClassifyDiagnostic(CA); + } else if (const auto *TT = VDT->getAs<TypedefType>()) { + if (const auto *TD = TT->getDecl()) + if (const auto *CA = TD->getAttr<CapabilityAttr>()) + return ClassifyDiagnostic(CA); + } else if (VDT->isPointerType() || VDT->isReferenceType()) + return ClassifyDiagnostic(VDT->getPointeeType()); + + return "mutex"; +} + /// Translate a clang expression in an attribute to a til::SExpr. /// Constructs the context from D, DeclExp, and SelfDecl. /// @@ -152,16 +174,17 @@ CapabilityExpr SExprBuilder::translateAttrExpr(const Expr *AttrExp, CapabilityExpr SExprBuilder::translateAttrExpr(const Expr *AttrExp, CallingContext *Ctx) { if (!AttrExp) - return CapabilityExpr(nullptr, false); + return CapabilityExpr(); if (const auto* SLit = dyn_cast<StringLiteral>(AttrExp)) { if (SLit->getString() == StringRef("*")) // The "*" expr is a universal lock, which essentially turns off // checks until it is removed from the lockset. - return CapabilityExpr(new (Arena) til::Wildcard(), false); + return CapabilityExpr(new (Arena) til::Wildcard(), StringRef("wildcard"), + false); else // Ignore other string literals for now. - return CapabilityExpr(nullptr, false); + return CapabilityExpr(); } bool Neg = false; @@ -183,14 +206,16 @@ CapabilityExpr SExprBuilder::translateAttrExpr(const Expr *AttrExp, // Trap mutex expressions like nullptr, or 0. // Any literal value is nonsense. if (!E || isa<til::Literal>(E)) - return CapabilityExpr(nullptr, false); + return CapabilityExpr(); + + StringRef Kind = ClassifyDiagnostic(AttrExp->getType()); // Hack to deal with smart pointers -- strip off top-level pointer casts. if (const auto *CE = dyn_cast<til::Cast>(E)) { if (CE->castOpcode() == til::CAST_objToPtr) - return CapabilityExpr(CE->expr(), Neg); + return CapabilityExpr(CE->expr(), Kind, Neg); } - return CapabilityExpr(E, Neg); + return CapabilityExpr(E, Kind, Neg); } // Translate a clang statement or expression to a TIL expression. diff --git a/contrib/llvm-project/clang/lib/Analysis/UninitializedValues.cpp b/contrib/llvm-project/clang/lib/Analysis/UninitializedValues.cpp index 811146e50b45..800943a99d87 100644 --- a/contrib/llvm-project/clang/lib/Analysis/UninitializedValues.cpp +++ b/contrib/llvm-project/clang/lib/Analysis/UninitializedValues.cpp @@ -148,7 +148,7 @@ public: Value getValue(const CFGBlock *block, const CFGBlock *dstBlock, const VarDecl *vd) { const Optional<unsigned> &idx = declToIndex.getValueIndex(vd); - assert(idx.hasValue()); + assert(idx); return getValueVector(block)[idx.getValue()]; } }; @@ -209,7 +209,7 @@ void CFGBlockValues::resetScratch() { ValueVector::reference CFGBlockValues::operator[](const VarDecl *vd) { const Optional<unsigned> &idx = declToIndex.getValueIndex(vd); - assert(idx.hasValue()); + assert(idx); return scratch[idx.getValue()]; } |