//===- unittests/Analysis/FlowSensitive/DataflowEnvironmentTest.cpp -------===// // // 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/DataflowEnvironment.h" #include "TestingSupport.h" #include "clang/AST/DeclCXX.h" #include "clang/AST/ExprCXX.h" #include "clang/AST/Stmt.h" #include "clang/ASTMatchers/ASTMatchFinder.h" #include "clang/ASTMatchers/ASTMatchers.h" #include "clang/Analysis/FlowSensitive/DataflowAnalysisContext.h" #include "clang/Analysis/FlowSensitive/StorageLocation.h" #include "clang/Analysis/FlowSensitive/Value.h" #include "clang/Analysis/FlowSensitive/WatchedLiteralsSolver.h" #include "clang/Tooling/Tooling.h" #include "gmock/gmock.h" #include "gtest/gtest.h" #include #include namespace { using namespace clang; using namespace dataflow; using ::clang::dataflow::test::findValueDecl; using ::clang::dataflow::test::getFieldValue; using ::testing::Contains; using ::testing::IsNull; using ::testing::NotNull; class EnvironmentTest : public ::testing::Test { protected: EnvironmentTest() : DAContext(std::make_unique()) {} DataflowAnalysisContext DAContext; }; TEST_F(EnvironmentTest, FlowCondition) { Environment Env(DAContext); auto &A = Env.arena(); EXPECT_TRUE(Env.proves(A.makeLiteral(true))); EXPECT_TRUE(Env.allows(A.makeLiteral(true))); EXPECT_FALSE(Env.proves(A.makeLiteral(false))); EXPECT_FALSE(Env.allows(A.makeLiteral(false))); auto &X = A.makeAtomRef(A.makeAtom()); EXPECT_FALSE(Env.proves(X)); EXPECT_TRUE(Env.allows(X)); Env.assume(X); EXPECT_TRUE(Env.proves(X)); EXPECT_TRUE(Env.allows(X)); auto &NotX = A.makeNot(X); EXPECT_FALSE(Env.proves(NotX)); EXPECT_FALSE(Env.allows(NotX)); } TEST_F(EnvironmentTest, SetAndGetValueOnCfgOmittedNodes) { // Check that we can set a value on an expression that is omitted from the CFG // (see `ignoreCFGOmittedNodes()`), then retrieve that same value from the // expression. This is a regression test; `setValue()` and `getValue()` // previously did not use `ignoreCFGOmittedNodes()` consistently. using namespace ast_matchers; std::string Code = R"cc( struct S { int f(); }; void target() { // Method call on a temporary produces an `ExprWithCleanups`. S().f(); (1); } )cc"; auto Unit = tooling::buildASTFromCodeWithArgs(Code, {"-fsyntax-only", "-std=c++17"}); auto &Context = Unit->getASTContext(); ASSERT_EQ(Context.getDiagnostics().getClient()->getNumErrors(), 0U); const ExprWithCleanups *WithCleanups = selectFirst( "cleanups", match(exprWithCleanups(hasType(isInteger())).bind("cleanups"), Context)); ASSERT_NE(WithCleanups, nullptr); const ParenExpr *Paren = selectFirst( "paren", match(parenExpr(hasType(isInteger())).bind("paren"), Context)); ASSERT_NE(Paren, nullptr); Environment Env(DAContext); IntegerValue *Val1 = cast(Env.createValue(Unit->getASTContext().IntTy)); Env.setValue(*WithCleanups, *Val1); EXPECT_EQ(Env.getValue(*WithCleanups), Val1); IntegerValue *Val2 = cast(Env.createValue(Unit->getASTContext().IntTy)); Env.setValue(*Paren, *Val2); EXPECT_EQ(Env.getValue(*Paren), Val2); } TEST_F(EnvironmentTest, CreateValueRecursiveType) { using namespace ast_matchers; std::string Code = R"cc( struct Recursive { bool X; Recursive *R; }; // Use both fields to force them to be created with `createValue`. void Usage(Recursive R) { (void)R.X; (void)R.R; } )cc"; auto Unit = tooling::buildASTFromCodeWithArgs(Code, {"-fsyntax-only", "-std=c++11"}); auto &Context = Unit->getASTContext(); ASSERT_EQ(Context.getDiagnostics().getClient()->getNumErrors(), 0U); auto Results = match(qualType(hasDeclaration(recordDecl( hasName("Recursive"), has(fieldDecl(hasName("R")).bind("field-r"))))) .bind("target"), Context); const QualType *TyPtr = selectFirst("target", Results); ASSERT_THAT(TyPtr, NotNull()); QualType Ty = *TyPtr; ASSERT_FALSE(Ty.isNull()); const FieldDecl *R = selectFirst("field-r", Results); ASSERT_THAT(R, NotNull()); Results = match(functionDecl(hasName("Usage")).bind("fun"), Context); const auto *Fun = selectFirst("fun", Results); ASSERT_THAT(Fun, NotNull()); // Verify that the struct and the field (`R`) with first appearance of the // type is created successfully. Environment Env(DAContext, *Fun); Env.initialize(); auto &SLoc = cast(Env.createObject(Ty)); PointerValue *PV = cast_or_null(getFieldValue(&SLoc, *R, Env)); EXPECT_THAT(PV, NotNull()); } TEST_F(EnvironmentTest, DifferentReferenceLocInJoin) { // This tests the case where the storage location for a reference-type // variable is different for two states being joined. We used to believe this // could not happen and therefore had an assertion disallowing this; this test // exists to demonstrate that we can handle this condition without a failing // assertion. See also the discussion here: // https://discourse.llvm.org/t/70086/6 using namespace ast_matchers; std::string Code = R"cc( void f(int &ref) {} )cc"; auto Unit = tooling::buildASTFromCodeWithArgs(Code, {"-fsyntax-only", "-std=c++11"}); auto &Context = Unit->getASTContext(); ASSERT_EQ(Context.getDiagnostics().getClient()->getNumErrors(), 0U); const ValueDecl *Ref = findValueDecl(Context, "ref"); Environment Env1(DAContext); StorageLocation &Loc1 = Env1.createStorageLocation(Context.IntTy); Env1.setStorageLocation(*Ref, Loc1); Environment Env2(DAContext); StorageLocation &Loc2 = Env2.createStorageLocation(Context.IntTy); Env2.setStorageLocation(*Ref, Loc2); EXPECT_NE(&Loc1, &Loc2); Environment::ValueModel Model; Environment EnvJoined = Environment::join(Env1, Env2, Model, Environment::DiscardExprState); // Joining environments with different storage locations for the same // declaration results in the declaration being removed from the joined // environment. EXPECT_EQ(EnvJoined.getStorageLocation(*Ref), nullptr); } TEST_F(EnvironmentTest, InitGlobalVarsFun) { using namespace ast_matchers; std::string Code = R"cc( int Global = 0; int Target () { return Global; } )cc"; auto Unit = tooling::buildASTFromCodeWithArgs(Code, {"-fsyntax-only", "-std=c++11"}); auto &Context = Unit->getASTContext(); ASSERT_EQ(Context.getDiagnostics().getClient()->getNumErrors(), 0U); auto Results = match(decl(anyOf(varDecl(hasName("Global")).bind("global"), functionDecl(hasName("Target")).bind("target"))), Context); const auto *Fun = selectFirst("target", Results); const auto *Var = selectFirst("global", Results); ASSERT_THAT(Fun, NotNull()); ASSERT_THAT(Var, NotNull()); // Verify the global variable is populated when we analyze `Target`. Environment Env(DAContext, *Fun); Env.initialize(); EXPECT_THAT(Env.getValue(*Var), NotNull()); } // Tests that fields mentioned only in default member initializers are included // in the set of tracked fields. TEST_F(EnvironmentTest, IncludeFieldsFromDefaultInitializers) { using namespace ast_matchers; std::string Code = R"cc( struct S { S() {} int X = 3; int Y = X; }; S foo(); )cc"; auto Unit = tooling::buildASTFromCodeWithArgs(Code, {"-fsyntax-only", "-std=c++11"}); auto &Context = Unit->getASTContext(); ASSERT_EQ(Context.getDiagnostics().getClient()->getNumErrors(), 0U); auto Results = match( qualType(hasDeclaration( cxxRecordDecl(hasName("S"), hasMethod(cxxConstructorDecl().bind("target"))) .bind("struct"))) .bind("ty"), Context); const auto *Constructor = selectFirst("target", Results); const auto *Rec = selectFirst("struct", Results); const auto QTy = *selectFirst("ty", Results); ASSERT_THAT(Constructor, NotNull()); ASSERT_THAT(Rec, NotNull()); ASSERT_FALSE(QTy.isNull()); auto Fields = Rec->fields(); FieldDecl *XDecl = nullptr; for (FieldDecl *Field : Fields) { if (Field->getNameAsString() == "X") { XDecl = Field; break; } } ASSERT_THAT(XDecl, NotNull()); // Verify that the `X` field of `S` is populated when analyzing the // constructor, even though it is not referenced directly in the constructor. Environment Env(DAContext, *Constructor); Env.initialize(); auto &Loc = cast(Env.createObject(QTy)); EXPECT_THAT(getFieldValue(&Loc, *XDecl, Env), NotNull()); } TEST_F(EnvironmentTest, InitGlobalVarsFieldFun) { using namespace ast_matchers; std::string Code = R"cc( struct S { int Bar; }; S Global = {0}; int Target () { return Global.Bar; } )cc"; auto Unit = tooling::buildASTFromCodeWithArgs(Code, {"-fsyntax-only", "-std=c++11"}); auto &Context = Unit->getASTContext(); ASSERT_EQ(Context.getDiagnostics().getClient()->getNumErrors(), 0U); auto Results = match(decl(anyOf(varDecl(hasName("Global")).bind("global"), functionDecl(hasName("Target")).bind("target"))), Context); const auto *Fun = selectFirst("target", Results); const auto *GlobalDecl = selectFirst("global", Results); ASSERT_THAT(Fun, NotNull()); ASSERT_THAT(GlobalDecl, NotNull()); ASSERT_TRUE(GlobalDecl->getType()->isStructureType()); auto GlobalFields = GlobalDecl->getType()->getAsRecordDecl()->fields(); FieldDecl *BarDecl = nullptr; for (FieldDecl *Field : GlobalFields) { if (Field->getNameAsString() == "Bar") { BarDecl = Field; break; } FAIL() << "Unexpected field: " << Field->getNameAsString(); } ASSERT_THAT(BarDecl, NotNull()); // Verify the global variable is populated when we analyze `Target`. Environment Env(DAContext, *Fun); Env.initialize(); const auto *GlobalLoc = cast(Env.getStorageLocation(*GlobalDecl)); auto *BarVal = getFieldValue(GlobalLoc, *BarDecl, Env); EXPECT_TRUE(isa(BarVal)); } TEST_F(EnvironmentTest, InitGlobalVarsConstructor) { using namespace ast_matchers; std::string Code = R"cc( int Global = 0; struct Target { Target() : Field(Global) {} int Field; }; )cc"; auto Unit = tooling::buildASTFromCodeWithArgs(Code, {"-fsyntax-only", "-std=c++11"}); auto &Context = Unit->getASTContext(); ASSERT_EQ(Context.getDiagnostics().getClient()->getNumErrors(), 0U); auto Results = match(decl(anyOf( varDecl(hasName("Global")).bind("global"), cxxConstructorDecl(ofClass(hasName("Target"))).bind("target"))), Context); const auto *Ctor = selectFirst("target", Results); const auto *Var = selectFirst("global", Results); ASSERT_TRUE(Ctor != nullptr); ASSERT_THAT(Var, NotNull()); // Verify the global variable is populated when we analyze `Target`. Environment Env(DAContext, *Ctor); Env.initialize(); EXPECT_THAT(Env.getValue(*Var), NotNull()); } // Pointers to Members are a tricky case of accessor calls, complicated further // when using templates where the pointer to the member is a template argument. // This is a repro of a failure case seen in the wild. TEST_F(EnvironmentTest, ModelMemberForAccessorUsingMethodPointerThroughTemplate) { using namespace ast_matchers; std::string Code = R"cc( struct S { int accessor() {return member;} int member = 0; }; template int Target(S* S) { return (S->*method)(); } // We want to analyze the instantiation of Target for the accessor. int Instantiator () {S S; return Target<&S::accessor>(&S); } )cc"; auto Unit = // C++17 for the simplifying use of auto in the template declaration. tooling::buildASTFromCodeWithArgs(Code, {"-fsyntax-only", "-std=c++17"}); auto &Context = Unit->getASTContext(); ASSERT_EQ(Context.getDiagnostics().getClient()->getNumErrors(), 0U); auto Results = match( decl(anyOf(functionDecl(hasName("Target"), isTemplateInstantiation()) .bind("target"), fieldDecl(hasName("member")).bind("member"), recordDecl(hasName("S")).bind("struct"))), Context); const auto *Fun = selectFirst("target", Results); const auto *Struct = selectFirst("struct", Results); const auto *Member = selectFirst("member", Results); ASSERT_THAT(Fun, NotNull()); ASSERT_THAT(Struct, NotNull()); ASSERT_THAT(Member, NotNull()); // Verify that `member` is modeled for `S` when we analyze // `Target<&S::accessor>`. Environment Env(DAContext, *Fun); Env.initialize(); EXPECT_THAT(DAContext.getModeledFields(QualType(Struct->getTypeForDecl(), 0)), Contains(Member)); } // This is a repro of a failure case seen in the wild. TEST_F(EnvironmentTest, CXXDefaultInitExprResultObjIsWrappedExprResultObj) { using namespace ast_matchers; std::string Code = R"cc( struct Inner {}; struct S { S() {} Inner i = {}; }; )cc"; auto Unit = tooling::buildASTFromCodeWithArgs(Code, {"-fsyntax-only", "-std=c++11"}); auto &Context = Unit->getASTContext(); ASSERT_EQ(Context.getDiagnostics().getClient()->getNumErrors(), 0U); auto Results = match(cxxConstructorDecl( hasAnyConstructorInitializer(cxxCtorInitializer( withInitializer(expr().bind("default_init_expr"))))) .bind("ctor"), Context); const auto *Constructor = selectFirst("ctor", Results); const auto *DefaultInit = selectFirst("default_init_expr", Results); Environment Env(DAContext, *Constructor); Env.initialize(); EXPECT_EQ(&Env.getResultObjectLocation(*DefaultInit), &Env.getResultObjectLocation(*DefaultInit->getExpr())); } // This test verifies the behavior of `getResultObjectLocation()` in // scenarios involving inherited constructors. // Since the specific AST node of interest `CXXConstructorDecl` is implicitly // generated, we cannot annotate any statements inside of it as we do in tests // within TransferTest. Thus, the only way to get the right `Environment` is by // explicitly initializing it as we do in tests within EnvironmentTest. // This is why this test is not inside TransferTest, where most of the tests for // `getResultObjectLocation()` are located. TEST_F(EnvironmentTest, ResultObjectLocationForInheritedCtorInitExpr) { using namespace ast_matchers; std::string Code = R"( struct Base { Base(int b) {} }; struct Derived : Base { using Base::Base; }; Derived d = Derived(0); )"; auto Unit = tooling::buildASTFromCodeWithArgs(Code, {"-fsyntax-only", "-std=c++20"}); auto &Context = Unit->getASTContext(); ASSERT_EQ(Context.getDiagnostics().getClient()->getNumErrors(), 0U); auto Results = match(cxxConstructorDecl( hasAnyConstructorInitializer(cxxCtorInitializer( withInitializer(expr().bind("inherited_ctor_init_expr"))))) .bind("ctor"), Context); const auto *Constructor = selectFirst("ctor", Results); const auto *InheritedCtorInit = selectFirst( "inherited_ctor_init_expr", Results); EXPECT_EQ(InheritedCtorInit->child_begin(), InheritedCtorInit->child_end()); Environment Env(DAContext, *Constructor); Env.initialize(); RecordStorageLocation &Loc = Env.getResultObjectLocation(*InheritedCtorInit); EXPECT_NE(&Loc, nullptr); EXPECT_EQ(&Loc, Env.getThisPointeeStorageLocation()); } TEST_F(EnvironmentTest, Stmt) { using namespace ast_matchers; std::string Code = R"cc( struct S { int i; }; void foo() { S AnS = S{1}; } )cc"; auto Unit = tooling::buildASTFromCodeWithArgs(Code, {"-fsyntax-only", "-std=c++11"}); auto &Context = Unit->getASTContext(); ASSERT_EQ(Context.getDiagnostics().getClient()->getNumErrors(), 0U); auto *DeclStatement = const_cast(selectFirst( "d", match(declStmt(hasSingleDecl(varDecl(hasName("AnS")))).bind("d"), Context))); ASSERT_THAT(DeclStatement, NotNull()); auto *Init = (cast(*DeclStatement->decl_begin()))->getInit(); ASSERT_THAT(Init, NotNull()); // Verify that we can retrieve the result object location for the initializer // expression when we analyze the DeclStmt for `AnS`. Environment Env(DAContext, *DeclStatement); // Don't crash when initializing. Env.initialize(); // And don't crash when retrieving the result object location. Env.getResultObjectLocation(*Init); } // This is a crash repro. TEST_F(EnvironmentTest, LambdaCapturingThisInFieldInitializer) { using namespace ast_matchers; std::string Code = R"cc( struct S { int f{[this]() { return 1; }()}; }; )cc"; auto Unit = tooling::buildASTFromCodeWithArgs(Code, {"-fsyntax-only", "-std=c++11"}); auto &Context = Unit->getASTContext(); ASSERT_EQ(Context.getDiagnostics().getClient()->getNumErrors(), 0U); auto *LambdaCallOperator = selectFirst( "method", match(cxxMethodDecl(hasName("operator()"), ofClass(cxxRecordDecl(isLambda()))) .bind("method"), Context)); Environment Env(DAContext, *LambdaCallOperator); // Don't crash when initializing. Env.initialize(); // And initialize the captured `this` pointee. ASSERT_NE(nullptr, Env.getThisPointeeStorageLocation()); } } // namespace