diff --git a/clang/include/clang/Analysis/FlowSensitive/DataflowAnalysisContext.h b/clang/include/clang/Analysis/FlowSensitive/DataflowAnalysisContext.h --- a/clang/include/clang/Analysis/FlowSensitive/DataflowAnalysisContext.h +++ b/clang/include/clang/Analysis/FlowSensitive/DataflowAnalysisContext.h @@ -16,6 +16,7 @@ #define LLVM_CLANG_ANALYSIS_FLOWSENSITIVE_DATAFLOWANALYSISCONTEXT_H #include "clang/AST/Decl.h" +#include "clang/AST/Expr.h" #include "clang/Analysis/FlowSensitive/StorageLocation.h" #include "clang/Analysis/FlowSensitive/Value.h" #include "llvm/ADT/DenseMap.h" @@ -70,6 +71,23 @@ return It == DeclToLoc.end() ? nullptr : It->second; } + /// Assigns `Loc` as the storage location of `E`. + /// + /// Requirements: + /// + /// `E` must not be assigned a storage location. + void setStorageLocation(const Expr &E, StorageLocation &Loc) { + assert(ExprToLoc.find(&E) == ExprToLoc.end()); + ExprToLoc[&E] = &Loc; + } + + /// Returns the storage location assigned to `E` or null if `E` has no + /// assigned storage location. + StorageLocation *getStorageLocation(const Expr &E) const { + auto It = ExprToLoc.find(&E); + return It == ExprToLoc.end() ? nullptr : It->second; + } + private: // Storage for the state of a program. std::vector> Locs; @@ -81,7 +99,7 @@ // basic blocks are evaluated multiple times. The storage locations that are // in scope for a particular basic block are stored in `Environment`. llvm::DenseMap DeclToLoc; - // FIXME: Add `Expr` to `StorageLocation` map. + llvm::DenseMap ExprToLoc; // FIXME: Add `StorageLocation` for `this`. diff --git a/clang/include/clang/Analysis/FlowSensitive/DataflowEnvironment.h b/clang/include/clang/Analysis/FlowSensitive/DataflowEnvironment.h --- a/clang/include/clang/Analysis/FlowSensitive/DataflowEnvironment.h +++ b/clang/include/clang/Analysis/FlowSensitive/DataflowEnvironment.h @@ -16,6 +16,7 @@ #define LLVM_CLANG_ANALYSIS_FLOWSENSITIVE_DATAFLOWENVIRONMENT_H #include "clang/AST/Decl.h" +#include "clang/AST/Expr.h" #include "clang/AST/Type.h" #include "clang/AST/TypeOrdering.h" #include "clang/Analysis/FlowSensitive/DataflowAnalysisContext.h" @@ -28,6 +29,18 @@ namespace clang { namespace dataflow { +/// Indicates what kind of indirections should be skipped past when retrieving +/// storage locations or values. +/// +/// FIXME: Consider renaming this or replacing it with a more appropriate model. +/// See the discussion in https://reviews.llvm.org/D116596 for context. +enum class SkipPast { + /// No indirections should be skipped past. + None, + /// An optional reference should be skipped past. + Reference, +}; + /// Holds the state of the program (store and heap) at a given program point. class Environment { public: @@ -50,6 +63,11 @@ /// returned storage location in the environment. StorageLocation &createStorageLocation(const VarDecl &D); + /// Creates a storage location for `E`. Does not assign the returned storage + /// location to `E` in the environment. Does not assign a value to the + /// returned storage location in the environment. + StorageLocation &createStorageLocation(const Expr &E); + /// Assigns `Loc` as the storage location of `D` in the environment. /// /// Requirements: @@ -57,9 +75,22 @@ /// `D` must not be assigned a storage location in the environment. void setStorageLocation(const ValueDecl &D, StorageLocation &Loc); - /// Returns the storage location assigned to `D` in the environment or null if - /// `D` isn't assigned a storage location in the environment. - StorageLocation *getStorageLocation(const ValueDecl &D) const; + /// Returns the storage location assigned to `D` in the environment, applying + /// the `SP` policy for skipping past indirections, or null if `D` isn't + /// assigned a storage location in the environment. + StorageLocation *getStorageLocation(const ValueDecl &D, SkipPast SP) const; + + /// Assigns `Loc` as the storage location of `E` in the environment. + /// + /// Requirements: + /// + /// `E` must not be assigned a storage location in the environment. + void setStorageLocation(const Expr &E, StorageLocation &Loc); + + /// Returns the storage location assigned to `E` in the environment, applying + /// the `SP` policy for skipping past indirections, or null if `E` isn't + /// assigned a storage location in the environment. + StorageLocation *getStorageLocation(const Expr &E, SkipPast SP) const; /// Creates a value appropriate for `Type`, assigns it to `Loc`, and returns /// it, if `Type` is supported, otherwise return null. If `Type` is a pointer @@ -78,6 +109,22 @@ /// isn't assigned a value in the environment. Value *getValue(const StorageLocation &Loc) const; + /// Equivalent to `getValue(getStorageLocation(D, SP), SkipPast::None)` if `D` + /// is assigned a storage location in the environment, otherwise returns null. + Value *getValue(const ValueDecl &D, SkipPast SP) const; + + /// Equivalent to `getValue(getStorageLocation(E, SP), SkipPast::None)` if `E` + /// is assigned a storage location in the environment, otherwise returns null. + Value *getValue(const Expr &E, SkipPast SP) const; + + /// Transfers ownership of `Loc` to the analysis context and returns a + /// reference to `Loc`. + StorageLocation &takeOwnership(std::unique_ptr Loc); + + /// Transfers ownership of `Val` to the analysis context and returns a + /// reference to `Val`. + Value &takeOwnership(std::unique_ptr Val); + private: /// Returns the value assigned to `Loc` in the environment or null if `Type` /// isn't supported. @@ -94,6 +141,10 @@ const StorageLocation &Loc, QualType Type, llvm::DenseSet &Visited); + StorageLocation &skip(StorageLocation &Loc, SkipPast SP) const; + const StorageLocation &skip(const StorageLocation &Loc, SkipPast SP) const; + + // `DACtx` is not null and not owned by this object. DataflowAnalysisContext *DACtx; // Maps from program declarations and statements to storage locations that are @@ -101,7 +152,7 @@ // include only storage locations that are in scope for a particular basic // block. llvm::DenseMap DeclToLoc; - // FIXME: Add `Expr` to `StorageLocation` map. + llvm::DenseMap ExprToLoc; llvm::DenseMap LocToVal; diff --git a/clang/lib/Analysis/FlowSensitive/DataflowEnvironment.cpp b/clang/lib/Analysis/FlowSensitive/DataflowEnvironment.cpp --- a/clang/lib/Analysis/FlowSensitive/DataflowEnvironment.cpp +++ b/clang/lib/Analysis/FlowSensitive/DataflowEnvironment.cpp @@ -20,6 +20,7 @@ #include "clang/Analysis/FlowSensitive/Value.h" #include "llvm/ADT/DenseMap.h" #include "llvm/ADT/DenseSet.h" +#include "llvm/Support/ErrorHandling.h" #include #include @@ -73,10 +74,10 @@ for (const FieldDecl *Field : Type->getAsRecordDecl()->fields()) { FieldLocs.insert({Field, &createStorageLocation(Field->getType())}); } - return DACtx->takeOwnership( + return takeOwnership( std::make_unique(Type, std::move(FieldLocs))); } - return DACtx->takeOwnership(std::make_unique(Type)); + return takeOwnership(std::make_unique(Type)); } StorageLocation &Environment::createStorageLocation(const VarDecl &D) { @@ -90,14 +91,37 @@ return Loc; } +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; +} + void Environment::setStorageLocation(const ValueDecl &D, StorageLocation &Loc) { assert(DeclToLoc.find(&D) == DeclToLoc.end()); DeclToLoc[&D] = &Loc; } -StorageLocation *Environment::getStorageLocation(const ValueDecl &D) const { +StorageLocation *Environment::getStorageLocation(const ValueDecl &D, + SkipPast SP) const { auto It = DeclToLoc.find(&D); - return It == DeclToLoc.end() ? nullptr : It->second; + return It == DeclToLoc.end() ? nullptr : &skip(*It->second, SP); +} + +void Environment::setStorageLocation(const Expr &E, StorageLocation &Loc) { + assert(ExprToLoc.find(&E) == ExprToLoc.end()); + ExprToLoc[&E] = &Loc; +} + +StorageLocation *Environment::getStorageLocation(const Expr &E, + SkipPast SP) const { + auto It = ExprToLoc.find(&E); + return It == ExprToLoc.end() ? nullptr : &skip(*It->second, SP); } void Environment::setValue(const StorageLocation &Loc, Value &Value) { @@ -109,6 +133,20 @@ return It == LocToVal.end() ? nullptr : It->second; } +Value *Environment::getValue(const ValueDecl &D, SkipPast SP) const { + auto *Loc = getStorageLocation(D, SP); + if (Loc == nullptr) + return nullptr; + return getValue(*Loc); +} + +Value *Environment::getValue(const Expr &E, SkipPast SP) const { + auto *Loc = getStorageLocation(E, SP); + if (Loc == nullptr) + return nullptr; + return getValue(*Loc); +} + Value *Environment::initValueInStorageLocation(const StorageLocation &Loc, QualType Type) { llvm::DenseSet Visited; @@ -121,9 +159,9 @@ assert(!Type.isNull()); if (Type->isIntegerType()) { - auto &Value = DACtx->takeOwnership(std::make_unique()); - setValue(Loc, Value); - return &Value; + auto &Val = takeOwnership(std::make_unique()); + setValue(Loc, Val); + return &Val; } if (Type->isReferenceType()) { @@ -137,10 +175,9 @@ Visited.erase(PointeeType.getCanonicalType()); } - auto &Value = - DACtx->takeOwnership(std::make_unique(PointeeLoc)); - setValue(Loc, Value); - return &Value; + auto &Val = takeOwnership(std::make_unique(PointeeLoc)); + setValue(Loc, Val); + return &Val; } if (Type->isPointerType()) { @@ -154,10 +191,9 @@ Visited.erase(PointeeType.getCanonicalType()); } - auto &Value = - DACtx->takeOwnership(std::make_unique(PointeeLoc)); - setValue(Loc, Value); - return &Value; + auto &Val = takeOwnership(std::make_unique(PointeeLoc)); + setValue(Loc, Val); + return &Val; } if (Type->isStructureOrClassType()) { @@ -178,14 +214,42 @@ Visited.erase(FieldType.getCanonicalType()); } - auto &Value = DACtx->takeOwnership( - std::make_unique(std::move(FieldValues))); - setValue(Loc, Value); - return &Value; + auto &Val = + takeOwnership(std::make_unique(std::move(FieldValues))); + setValue(Loc, Val); + return &Val; } return nullptr; } +StorageLocation & +Environment::takeOwnership(std::unique_ptr Loc) { + return DACtx->takeOwnership(std::move(Loc)); +} + +Value &Environment::takeOwnership(std::unique_ptr Val) { + return DACtx->takeOwnership(std::move(Val)); +} + +StorageLocation &Environment::skip(StorageLocation &Loc, SkipPast SP) const { + switch (SP) { + case SkipPast::None: + return Loc; + case SkipPast::Reference: + // References cannot be chained so we only need to skip past one level of + // indirection. + if (auto *Val = dyn_cast_or_null(getValue(Loc))) + return Val->getPointeeLoc(); + return Loc; + } + llvm_unreachable("bad SkipPast kind"); +} + +const StorageLocation &Environment::skip(const StorageLocation &Loc, + SkipPast SP) const { + return skip(*const_cast(&Loc), SP); +} + } // namespace dataflow } // namespace clang diff --git a/clang/lib/Analysis/FlowSensitive/Transfer.cpp b/clang/lib/Analysis/FlowSensitive/Transfer.cpp --- a/clang/lib/Analysis/FlowSensitive/Transfer.cpp +++ b/clang/lib/Analysis/FlowSensitive/Transfer.cpp @@ -15,11 +15,14 @@ #include "clang/AST/Decl.h" #include "clang/AST/DeclBase.h" #include "clang/AST/Expr.h" +#include "clang/AST/OperationKinds.h" #include "clang/AST/Stmt.h" #include "clang/AST/StmtVisitor.h" #include "clang/Analysis/FlowSensitive/DataflowEnvironment.h" +#include "clang/Basic/OperatorKinds.h" #include "llvm/Support/Casting.h" #include +#include namespace clang { namespace dataflow { @@ -28,6 +31,53 @@ public: TransferVisitor(Environment &Env) : 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(); + + assert(LHS != nullptr); + 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(); + + assert(RHS != nullptr); + Value *RHSVal = Env.getValue(*RHS, SkipPast::Reference); + if (RHSVal == nullptr) + return; + + // 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); + } + // FIXME: Add support for BO_EQ, BO_NE. + } + + void VisitDeclRefExpr(const DeclRefExpr *S) { + assert(S->getDecl() != nullptr); + auto *DeclLoc = Env.getStorageLocation(*S->getDecl(), SkipPast::None); + if (DeclLoc == nullptr) + return; + + if (S->getDecl()->getType()->isReferenceType()) { + Env.setStorageLocation(*S, *DeclLoc); + } else { + auto &Loc = Env.createStorageLocation(*S); + auto &Val = Env.takeOwnership(std::make_unique(*DeclLoc)); + Env.setStorageLocation(*S, Loc); + Env.setValue(Loc, Val); + } + } + void VisitDeclStmt(const DeclStmt *S) { // FIXME: Add support for group decls, e.g: `int a, b;` if (S->isSingleDecl()) { @@ -37,8 +87,43 @@ } } + void VisitImplicitCastExpr(const ImplicitCastExpr *S) { + if (S->getCastKind() == CK_LValueToRValue) { + // 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(); + + assert(SubExpr != nullptr); + auto *SubExprVal = Env.getValue(*SubExpr, SkipPast::Reference); + if (SubExprVal == nullptr) + return; + + auto &ExprLoc = Env.createStorageLocation(*S); + Env.setStorageLocation(*S, ExprLoc); + Env.setValue(ExprLoc, *SubExprVal); + } + // FIXME: Add support for CK_NoOp, CK_UserDefinedConversion, + // CK_ConstructorConversion, CK_UncheckedDerivedToBase. + } + + void VisitUnaryOperator(const UnaryOperator *S) { + if (S->getOpcode() == UO_Deref) { + assert(S->getSubExpr() != nullptr); + const auto *SubExprVal = cast_or_null( + Env.getValue(*S->getSubExpr(), SkipPast::Reference)); + if (SubExprVal == nullptr) + return; + + auto &Loc = Env.createStorageLocation(*S); + Env.setStorageLocation(*S, Loc); + Env.setValue(Loc, Env.takeOwnership(std::make_unique( + SubExprVal->getPointeeLoc()))); + } + // FIXME: Add support for UO_AddrOf, UO_LNot. + } + // FIXME: Add support for: - // - BinaryOperator // - CallExpr // - CXXBindTemporaryExpr // - CXXBoolLiteralExpr @@ -47,17 +132,46 @@ // - CXXOperatorCallExpr // - CXXStaticCastExpr // - CXXThisExpr - // - DeclRefExpr - // - ImplicitCastExpr // - MaterializeTemporaryExpr // - MemberExpr - // - UnaryOperator private: void visitVarDecl(const VarDecl &D) { auto &Loc = Env.createStorageLocation(D); Env.setStorageLocation(D, Loc); - Env.initValueInStorageLocation(Loc, D.getType()); + + const Expr *InitExpr = D.getInit(); + if (InitExpr == nullptr) { + // No initializer expression - associate `Loc` with a new value. + Env.initValueInStorageLocation(Loc, D.getType()); + return; + } + + if (D.getType()->isReferenceType()) { + // Initializing a reference variable - do not create a reference to + // reference. + if (auto *InitExprLoc = + Env.getStorageLocation(*InitExpr, SkipPast::Reference)) { + auto &Val = + Env.takeOwnership(std::make_unique(*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. + Env.initValueInStorageLocation(Loc, D.getType()); + } + return; + } + + if (auto *InitExprVal = Env.getValue(*InitExpr, SkipPast::None)) { + Env.setValue(Loc, *InitExprVal); + } else { + // FIXME: The initializer expression must always be assigned a value. + // Replace this with an assert when we have sufficient coverage of + // language features. + Env.initValueInStorageLocation(Loc, D.getType()); + } } Environment &Env; diff --git a/clang/unittests/Analysis/FlowSensitive/TransferTest.cpp b/clang/unittests/Analysis/FlowSensitive/TransferTest.cpp --- a/clang/unittests/Analysis/FlowSensitive/TransferTest.cpp +++ b/clang/unittests/Analysis/FlowSensitive/TransferTest.cpp @@ -82,7 +82,8 @@ const ValueDecl *FooDecl = findValueDecl(ASTCtx, "foo"); ASSERT_THAT(FooDecl, NotNull()); - const StorageLocation *FooLoc = Env.getStorageLocation(*FooDecl); + const StorageLocation *FooLoc = + Env.getStorageLocation(*FooDecl, SkipPast::None); ASSERT_TRUE(isa_and_nonnull(FooLoc)); const Value *FooVal = Env.getValue(*FooLoc); @@ -125,8 +126,8 @@ } ASSERT_THAT(BarDecl, NotNull()); - const auto *FooLoc = - cast(Env.getStorageLocation(*FooDecl)); + const auto *FooLoc = cast( + Env.getStorageLocation(*FooDecl, SkipPast::None)); const auto *BarLoc = cast(&FooLoc->getChild(*BarDecl)); @@ -171,8 +172,8 @@ } ASSERT_THAT(BarDecl, NotNull()); - const auto *FooLoc = - cast(Env.getStorageLocation(*FooDecl)); + const auto *FooLoc = cast( + Env.getStorageLocation(*FooDecl, SkipPast::None)); const auto *BarLoc = cast(&FooLoc->getChild(*BarDecl)); @@ -204,7 +205,8 @@ const ValueDecl *FooDecl = findValueDecl(ASTCtx, "foo"); ASSERT_THAT(FooDecl, NotNull()); - const StorageLocation *FooLoc = Env.getStorageLocation(*FooDecl); + const StorageLocation *FooLoc = + Env.getStorageLocation(*FooDecl, SkipPast::None); ASSERT_TRUE(isa_and_nonnull(FooLoc)); const ReferenceValue *FooVal = @@ -293,8 +295,8 @@ ASSERT_THAT(BazRefDecl, NotNull()); ASSERT_THAT(BazPtrDecl, NotNull()); - const auto *FooLoc = - cast(Env.getStorageLocation(*FooDecl)); + const auto *FooLoc = cast( + Env.getStorageLocation(*FooDecl, SkipPast::None)); const auto *FooVal = cast(Env.getValue(*FooLoc)); const auto *FooPointeeVal = cast(Env.getValue(FooVal->getPointeeLoc())); @@ -348,7 +350,8 @@ const ValueDecl *FooDecl = findValueDecl(ASTCtx, "foo"); ASSERT_THAT(FooDecl, NotNull()); - const StorageLocation *FooLoc = Env.getStorageLocation(*FooDecl); + const StorageLocation *FooLoc = + Env.getStorageLocation(*FooDecl, SkipPast::None); ASSERT_TRUE(isa_and_nonnull(FooLoc)); const PointerValue *FooVal = cast(Env.getValue(*FooLoc)); @@ -449,8 +452,8 @@ ASSERT_THAT(BazRefDecl, NotNull()); ASSERT_THAT(BazPtrDecl, NotNull()); - const auto *FooLoc = - cast(Env.getStorageLocation(*FooDecl)); + const auto *FooLoc = cast( + Env.getStorageLocation(*FooDecl, SkipPast::None)); const auto *FooVal = cast(Env.getValue(*FooLoc)); const auto *FooPointeeVal = cast(Env.getValue(FooVal->getPointeeLoc())); @@ -498,42 +501,225 @@ // [[p4]] } )"; + runDataflow(Code, [](llvm::ArrayRef>> + Results, + ASTContext &ASTCtx) { + ASSERT_THAT(Results, ElementsAre(Pair("p4", _), Pair("p3", _), + Pair("p2", _), Pair("p1", _))); + const ValueDecl *FooDecl = findValueDecl(ASTCtx, "foo"); + ASSERT_THAT(FooDecl, NotNull()); + + const ValueDecl *BarDecl = findValueDecl(ASTCtx, "bar"); + ASSERT_THAT(BarDecl, NotNull()); + + const ValueDecl *BazDecl = findValueDecl(ASTCtx, "baz"); + ASSERT_THAT(BazDecl, NotNull()); + + const Environment &Env1 = Results[3].second.Env; + const StorageLocation *FooLoc = + Env1.getStorageLocation(*FooDecl, SkipPast::None); + ASSERT_THAT(FooLoc, NotNull()); + ASSERT_THAT(Env1.getStorageLocation(*BarDecl, SkipPast::None), IsNull()); + ASSERT_THAT(Env1.getStorageLocation(*BazDecl, SkipPast::None), IsNull()); + + const Environment &Env2 = Results[2].second.Env; + ASSERT_EQ(Env2.getStorageLocation(*FooDecl, SkipPast::None), FooLoc); + ASSERT_THAT(Env2.getStorageLocation(*BarDecl, SkipPast::None), NotNull()); + ASSERT_THAT(Env2.getStorageLocation(*BazDecl, SkipPast::None), IsNull()); + + const Environment &Env3 = Results[1].second.Env; + ASSERT_EQ(Env3.getStorageLocation(*FooDecl, SkipPast::None), FooLoc); + ASSERT_THAT(Env3.getStorageLocation(*BarDecl, SkipPast::None), IsNull()); + ASSERT_THAT(Env3.getStorageLocation(*BazDecl, SkipPast::None), NotNull()); + + const Environment &Env4 = Results[0].second.Env; + ASSERT_EQ(Env4.getStorageLocation(*FooDecl, SkipPast::None), FooLoc); + ASSERT_THAT(Env4.getStorageLocation(*BarDecl, SkipPast::None), IsNull()); + ASSERT_THAT(Env4.getStorageLocation(*BazDecl, SkipPast::None), IsNull()); + }); +} + +TEST_F(TransferTest, BinaryOperatorAssign) { + std::string Code = R"( + void target() { + int foo; + int bar; + (bar) = (foo); + // [[p]] + } + )"; + runDataflow(Code, + [](llvm::ArrayRef< + std::pair>> + Results, + ASTContext &ASTCtx) { + ASSERT_THAT(Results, ElementsAre(Pair("p", _))); + const Environment &Env = Results[0].second.Env; + + const ValueDecl *FooDecl = findValueDecl(ASTCtx, "foo"); + ASSERT_THAT(FooDecl, NotNull()); + + const Value *FooVal = Env.getValue(*FooDecl, SkipPast::None); + ASSERT_TRUE(isa_and_nonnull(FooVal)); + + const ValueDecl *BarDecl = findValueDecl(ASTCtx, "bar"); + ASSERT_THAT(BarDecl, NotNull()); + + EXPECT_EQ(Env.getValue(*BarDecl, SkipPast::None), FooVal); + }); +} + +TEST_F(TransferTest, VarDeclInitAssign) { + std::string Code = R"( + void target() { + int foo; + int bar = foo; + // [[p]] + } + )"; + runDataflow(Code, + [](llvm::ArrayRef< + std::pair>> + Results, + ASTContext &ASTCtx) { + ASSERT_THAT(Results, ElementsAre(Pair("p", _))); + const Environment &Env = Results[0].second.Env; + + const ValueDecl *FooDecl = findValueDecl(ASTCtx, "foo"); + ASSERT_THAT(FooDecl, NotNull()); + + const Value *FooVal = Env.getValue(*FooDecl, SkipPast::None); + ASSERT_TRUE(isa_and_nonnull(FooVal)); + + const ValueDecl *BarDecl = findValueDecl(ASTCtx, "bar"); + ASSERT_THAT(BarDecl, NotNull()); + + EXPECT_EQ(Env.getValue(*BarDecl, SkipPast::None), FooVal); + }); +} + +TEST_F(TransferTest, VarDeclInitAssignChained) { + std::string Code = R"( + void target() { + int foo; + int bar; + int baz = (bar = foo); + // [[p]] + } + )"; + runDataflow(Code, + [](llvm::ArrayRef< + std::pair>> + Results, + ASTContext &ASTCtx) { + ASSERT_THAT(Results, ElementsAre(Pair("p", _))); + const Environment &Env = Results[0].second.Env; + + const ValueDecl *FooDecl = findValueDecl(ASTCtx, "foo"); + ASSERT_THAT(FooDecl, NotNull()); + + const Value *FooVal = Env.getValue(*FooDecl, SkipPast::None); + ASSERT_TRUE(isa_and_nonnull(FooVal)); + + const ValueDecl *BarDecl = findValueDecl(ASTCtx, "bar"); + ASSERT_THAT(BarDecl, NotNull()); + + const ValueDecl *BazDecl = findValueDecl(ASTCtx, "baz"); + ASSERT_THAT(BazDecl, NotNull()); + + EXPECT_EQ(Env.getValue(*BarDecl, SkipPast::None), FooVal); + EXPECT_EQ(Env.getValue(*BazDecl, SkipPast::None), FooVal); + }); +} + +TEST_F(TransferTest, VarDeclInitAssignPtrDeref) { + std::string Code = R"( + void target() { + int foo; + int *bar; + *(bar) = foo; + int baz = *(bar); + // [[p]] + } + )"; + runDataflow(Code, + [](llvm::ArrayRef< + std::pair>> + Results, + ASTContext &ASTCtx) { + ASSERT_THAT(Results, ElementsAre(Pair("p", _))); + const Environment &Env = Results[0].second.Env; + + const ValueDecl *FooDecl = findValueDecl(ASTCtx, "foo"); + ASSERT_THAT(FooDecl, NotNull()); + + const Value *FooVal = Env.getValue(*FooDecl, SkipPast::None); + ASSERT_TRUE(isa_and_nonnull(FooVal)); + + const ValueDecl *BarDecl = findValueDecl(ASTCtx, "bar"); + ASSERT_THAT(BarDecl, NotNull()); + + const auto *BarVal = + cast(Env.getValue(*BarDecl, SkipPast::None)); + EXPECT_EQ(Env.getValue(BarVal->getPointeeLoc()), FooVal); + + const ValueDecl *BazDecl = findValueDecl(ASTCtx, "baz"); + ASSERT_THAT(BazDecl, NotNull()); + + EXPECT_EQ(Env.getValue(*BazDecl, SkipPast::None), FooVal); + }); +} + +TEST_F(TransferTest, AssignToAndFromReference) { + std::string Code = R"( + void target() { + int foo; + int bar; + int& baz = foo; + // [[p1]] + baz = bar; + int qux = baz; + int& quux = baz; + // [[p2]] + } + )"; runDataflow( Code, [](llvm::ArrayRef< std::pair>> Results, ASTContext &ASTCtx) { - ASSERT_THAT(Results, ElementsAre(Pair("p4", _), Pair("p3", _), - Pair("p2", _), Pair("p1", _))); + ASSERT_THAT(Results, ElementsAre(Pair("p1", _), Pair("p2", _))); + const Environment &Env1 = Results[0].second.Env; + const Environment &Env2 = Results[1].second.Env; + const ValueDecl *FooDecl = findValueDecl(ASTCtx, "foo"); ASSERT_THAT(FooDecl, NotNull()); + const Value *FooVal = Env1.getValue(*FooDecl, SkipPast::None); + ASSERT_TRUE(isa_and_nonnull(FooVal)); + const ValueDecl *BarDecl = findValueDecl(ASTCtx, "bar"); ASSERT_THAT(BarDecl, NotNull()); + const Value *BarVal = Env1.getValue(*BarDecl, SkipPast::None); + ASSERT_TRUE(isa_and_nonnull(BarVal)); + const ValueDecl *BazDecl = findValueDecl(ASTCtx, "baz"); ASSERT_THAT(BazDecl, NotNull()); - const Environment &Env1 = Results[3].second.Env; - const StorageLocation *FooLoc = Env1.getStorageLocation(*FooDecl); - ASSERT_THAT(FooLoc, NotNull()); - ASSERT_THAT(Env1.getStorageLocation(*BarDecl), IsNull()); - ASSERT_THAT(Env1.getStorageLocation(*BazDecl), IsNull()); - - const Environment &Env2 = Results[2].second.Env; - ASSERT_EQ(Env2.getStorageLocation(*FooDecl), FooLoc); - ASSERT_THAT(Env2.getStorageLocation(*BarDecl), NotNull()); - ASSERT_THAT(Env2.getStorageLocation(*BazDecl), IsNull()); - - const Environment &Env3 = Results[1].second.Env; - ASSERT_EQ(Env3.getStorageLocation(*FooDecl), FooLoc); - ASSERT_THAT(Env3.getStorageLocation(*BarDecl), IsNull()); - ASSERT_THAT(Env3.getStorageLocation(*BazDecl), NotNull()); - - const Environment &Env4 = Results[0].second.Env; - ASSERT_EQ(Env4.getStorageLocation(*FooDecl), FooLoc); - ASSERT_THAT(Env4.getStorageLocation(*BarDecl), IsNull()); - ASSERT_THAT(Env4.getStorageLocation(*BazDecl), IsNull()); + EXPECT_EQ(Env1.getValue(*BazDecl, SkipPast::Reference), FooVal); + + EXPECT_EQ(Env2.getValue(*BazDecl, SkipPast::Reference), BarVal); + EXPECT_EQ(Env2.getValue(*FooDecl, SkipPast::None), BarVal); + + const ValueDecl *QuxDecl = findValueDecl(ASTCtx, "qux"); + ASSERT_THAT(QuxDecl, NotNull()); + EXPECT_EQ(Env2.getValue(*QuxDecl, SkipPast::None), BarVal); + + const ValueDecl *QuuxDecl = findValueDecl(ASTCtx, "quux"); + ASSERT_THAT(QuuxDecl, NotNull()); + EXPECT_EQ(Env2.getValue(*QuuxDecl, SkipPast::Reference), BarVal); }); }