diff --git a/clang/include/clang/Analysis/FlowSensitive/DataflowAnalysis.h b/clang/include/clang/Analysis/FlowSensitive/DataflowAnalysis.h --- a/clang/include/clang/Analysis/FlowSensitive/DataflowAnalysis.h +++ b/clang/include/clang/Analysis/FlowSensitive/DataflowAnalysis.h @@ -62,6 +62,8 @@ using Lattice = LatticeT; explicit DataflowAnalysis(ASTContext &Context) : Context(Context) {} + explicit DataflowAnalysis(ASTContext &Context, bool ApplyBuiltinTransfer) + : TypeErasedDataflowAnalysis(ApplyBuiltinTransfer), Context(Context) {} ASTContext &getASTContext() final { return Context; } diff --git a/clang/include/clang/Analysis/FlowSensitive/TypeErasedDataflowAnalysis.h b/clang/include/clang/Analysis/FlowSensitive/TypeErasedDataflowAnalysis.h --- a/clang/include/clang/Analysis/FlowSensitive/TypeErasedDataflowAnalysis.h +++ b/clang/include/clang/Analysis/FlowSensitive/TypeErasedDataflowAnalysis.h @@ -41,7 +41,17 @@ /// Type-erased base class for dataflow analyses built on a single lattice type. class TypeErasedDataflowAnalysis : public Environment::Merger { + /// Determines whether to apply the built-in transfer functions. + // FIXME: Remove this option once the framework supports composing analyses + // (at which point the built-in transfer functions can be simply a standalone + // analysis). + bool ApplyBuiltinTransfer; + public: + TypeErasedDataflowAnalysis() : ApplyBuiltinTransfer(true) {} + TypeErasedDataflowAnalysis(bool ApplyBuiltinTransfer) + : ApplyBuiltinTransfer(ApplyBuiltinTransfer) {} + virtual ~TypeErasedDataflowAnalysis() {} /// Returns the `ASTContext` that is used by the analysis. @@ -66,6 +76,10 @@ /// type-erased lattice element. virtual void transferTypeErased(const Stmt *, TypeErasedLattice &, Environment &) = 0; + + /// Determines whether to apply the built-in transfer functions, which model + /// the heap and stack in the `Environment`. + bool applyBuiltinTransfer() const { return ApplyBuiltinTransfer; } }; /// Type-erased model of the program at a given program point. diff --git a/clang/lib/Analysis/FlowSensitive/TypeErasedDataflowAnalysis.cpp b/clang/lib/Analysis/FlowSensitive/TypeErasedDataflowAnalysis.cpp --- a/clang/lib/Analysis/FlowSensitive/TypeErasedDataflowAnalysis.cpp +++ b/clang/lib/Analysis/FlowSensitive/TypeErasedDataflowAnalysis.cpp @@ -119,7 +119,8 @@ const Stmt *S = CfgStmt.getStmt(); assert(S != nullptr); - transfer(*S, State.Env); + if (Analysis.applyBuiltinTransfer()) + transfer(*S, State.Env); Analysis.transferTypeErased(S, State.Lattice, State.Env); if (HandleTransferredStmt != nullptr) @@ -178,7 +179,8 @@ HandleTransferredStmt); break; case CFGElement::Initializer: - transferCFGInitializer(*Element.getAs(), State); + if (Analysis.applyBuiltinTransfer()) + transferCFGInitializer(*Element.getAs(), State); break; default: // FIXME: Evaluate other kinds of `CFGElement`. diff --git a/clang/unittests/Analysis/FlowSensitive/NoopAnalysis.h b/clang/unittests/Analysis/FlowSensitive/NoopAnalysis.h --- a/clang/unittests/Analysis/FlowSensitive/NoopAnalysis.h +++ b/clang/unittests/Analysis/FlowSensitive/NoopAnalysis.h @@ -39,8 +39,13 @@ class NoopAnalysis : public DataflowAnalysis { public: - NoopAnalysis(ASTContext &Context) - : DataflowAnalysis(Context) {} + /// `ApplyBuiltinTransfer` controls whether to run the built-in transfer + /// functions that model memory during the analysis. Their results are not + /// used by `NoopAnalysis`, but tests that need to inspect the environment + /// should enable them. + NoopAnalysis(ASTContext &Context, bool ApplyBuiltinTransfer) + : DataflowAnalysis(Context, + ApplyBuiltinTransfer) {} static NoopLattice initialElement() { return {}; } diff --git a/clang/unittests/Analysis/FlowSensitive/TestingSupportTest.cpp b/clang/unittests/Analysis/FlowSensitive/TestingSupportTest.cpp --- a/clang/unittests/Analysis/FlowSensitive/TestingSupportTest.cpp +++ b/clang/unittests/Analysis/FlowSensitive/TestingSupportTest.cpp @@ -79,12 +79,12 @@ ASTContext &)> Expectations) { ASSERT_THAT_ERROR( - test::checkDataflow(Code, Target, - [](ASTContext &Context, Environment &) { - return NoopAnalysis(Context); - }, - std::move(Expectations), - {"-fsyntax-only", "-std=c++17"}), + test::checkDataflow( + Code, Target, + [](ASTContext &Context, Environment &) { + return NoopAnalysis(Context, /*ApplyBuiltinTransfer=*/false); + }, + std::move(Expectations), {"-fsyntax-only", "-std=c++17"}), llvm::Succeeded()); } 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 @@ -40,11 +40,14 @@ protected: template void runDataflow(llvm::StringRef Code, Matcher Match, - LangStandard::Kind Std = LangStandard::lang_cxx17) { + LangStandard::Kind Std = LangStandard::lang_cxx17, + bool ApplyBuiltinTransfer = true) { ASSERT_THAT_ERROR( test::checkDataflow( Code, "target", - [](ASTContext &C, Environment &) { return NoopAnalysis(C); }, + [ApplyBuiltinTransfer](ASTContext &C, Environment &) { + return NoopAnalysis(C, ApplyBuiltinTransfer); + }, [&Match]( llvm::ArrayRef< std::pair>> @@ -58,6 +61,31 @@ } }; +TEST_F(TransferTest, IntVarDeclNotTrackedWhenTransferDisabled) { + std::string Code = R"( + void target() { + int 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()); + + EXPECT_EQ(Env.getStorageLocation(*FooDecl, SkipPast::None), nullptr); + }, + LangStandard::lang_cxx17, + /*ApplyBuiltinTransfer=*/false); +} + TEST_F(TransferTest, IntVarDecl) { std::string Code = R"( void target() { diff --git a/clang/unittests/Analysis/FlowSensitive/TypeErasedDataflowAnalysisTest.cpp b/clang/unittests/Analysis/FlowSensitive/TypeErasedDataflowAnalysisTest.cpp --- a/clang/unittests/Analysis/FlowSensitive/TypeErasedDataflowAnalysisTest.cpp +++ b/clang/unittests/Analysis/FlowSensitive/TypeErasedDataflowAnalysisTest.cpp @@ -51,6 +51,8 @@ template class AnalysisCallback : public ast_matchers::MatchFinder::MatchCallback { public: + AnalysisCallback(AnalysisT (*MakeAnalysis)(ASTContext &)) + : MakeAnalysis(MakeAnalysis) {} void run(const ast_matchers::MatchFinder::MatchResult &Result) override { assert(BlockStates.empty()); @@ -63,12 +65,13 @@ auto CFCtx = llvm::cantFail( ControlFlowContext::build(nullptr, Body, Result.Context)); - AnalysisT Analysis(*Result.Context); + AnalysisT Analysis = MakeAnalysis(*Result.Context); DataflowAnalysisContext DACtx; Environment Env(DACtx); BlockStates = runDataflowAnalysis(CFCtx, Analysis, Env); } + AnalysisT (*MakeAnalysis)(ASTContext &); std::vector< llvm::Optional>> BlockStates; @@ -76,11 +79,11 @@ template std::vector>> -runAnalysis(llvm::StringRef Code) { +runAnalysis(llvm::StringRef Code, AnalysisT (*MakeAnalysis)(ASTContext &)) { std::unique_ptr AST = tooling::buildASTFromCodeWithArgs(Code, {"-std=c++11"}); - AnalysisCallback Callback; + AnalysisCallback Callback(MakeAnalysis); ast_matchers::MatchFinder Finder; Finder.addMatcher( ast_matchers::functionDecl(ast_matchers::hasName("target")).bind("func"), @@ -91,9 +94,8 @@ } TEST(DataflowAnalysisTest, NoopAnalysis) { - auto BlockStates = runAnalysis(R"( - void target() {} - )"); + auto BlockStates = runAnalysis( + "void target() {}", [](ASTContext &C) { return NoopAnalysis(C, false); }); EXPECT_EQ(BlockStates.size(), 2u); EXPECT_TRUE(BlockStates[0].hasValue()); EXPECT_TRUE(BlockStates[1].hasValue()); @@ -118,8 +120,9 @@ : public DataflowAnalysis { public: explicit NonConvergingAnalysis(ASTContext &Context) - : DataflowAnalysis(Context) { - } + : DataflowAnalysis( + Context, + /*ApplyBuiltinTransfer=*/false) {} static NonConvergingLattice initialElement() { return {0}; } @@ -129,11 +132,13 @@ }; TEST(DataflowAnalysisTest, NonConvergingAnalysis) { - auto BlockStates = runAnalysis(R"( + auto BlockStates = runAnalysis( + R"( void target() { while(true) {} } - )"); + )", + [](ASTContext &C) { return NonConvergingAnalysis(C); }); EXPECT_EQ(BlockStates.size(), 4u); EXPECT_TRUE(BlockStates[0].hasValue()); EXPECT_TRUE(BlockStates[1].hasValue());