//===- unittests/Analysis/FlowSensitive/SingleVarConstantPropagation.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 // //===----------------------------------------------------------------------===// // // This file defines a simplistic version of Constant Propagation as an example // of a forward, monotonic dataflow analysis. The analysis only tracks one // variable at a time -- the one with the most recent declaration encountered. // //===----------------------------------------------------------------------===// #include "TestingSupport.h" #include "clang/AST/ASTContext.h" #include "clang/AST/Decl.h" #include "clang/AST/Expr.h" #include "clang/AST/Stmt.h" #include "clang/ASTMatchers/ASTMatchFinder.h" #include "clang/ASTMatchers/ASTMatchers.h" #include "clang/Analysis/CFG.h" #include "clang/Analysis/FlowSensitive/DataflowAnalysis.h" #include "clang/Analysis/FlowSensitive/DataflowEnvironment.h" #include "clang/Analysis/FlowSensitive/DataflowLattice.h" #include "llvm/ADT/StringRef.h" #include "llvm/ADT/Twine.h" #include "llvm/Support/Error.h" #include "llvm/Testing/ADT/StringMapEntry.h" #include "llvm/Testing/Annotations/Annotations.h" #include "llvm/Testing/Support/Error.h" #include "gmock/gmock.h" #include "gtest/gtest.h" #include #include #include #include #include #include namespace clang { namespace dataflow { namespace { using namespace ast_matchers; // A semi-lattice for dataflow analysis that tracks the value of a single // integer variable. If it can be identified with a single (constant) value, // then that value is stored. struct ConstantPropagationLattice { // A null `Var` represents "top": either more than one value is possible or // more than one variable was encountered. Otherwise, `Data` indicates that // `Var` has the given `Value` at the program point with which this lattice // element is associated, for all paths through the program. struct VarValue { const VarDecl *Var; int64_t Value; friend bool operator==(VarValue Lhs, VarValue Rhs) { return Lhs.Var == Rhs.Var && Lhs.Value == Rhs.Value; } }; // `std::nullopt` is "bottom". std::optional Data; static constexpr ConstantPropagationLattice bottom() { return {std::nullopt}; } static constexpr ConstantPropagationLattice top() { return {VarValue{nullptr, 0}}; } friend bool operator==(const ConstantPropagationLattice &Lhs, const ConstantPropagationLattice &Rhs) { return Lhs.Data == Rhs.Data; } LatticeJoinEffect join(const ConstantPropagationLattice &Other) { if (*this == Other || Other == bottom() || *this == top()) return LatticeJoinEffect::Unchanged; if (*this == bottom()) { *this = Other; return LatticeJoinEffect::Changed; } *this = top(); return LatticeJoinEffect::Changed; } }; std::ostream &operator<<(std::ostream &OS, const ConstantPropagationLattice &L) { if (L == L.bottom()) return OS << "None"; if (L == L.top()) return OS << "Any"; return OS << L.Data->Var->getName().str() << " = " << L.Data->Value; } } // namespace static constexpr char kVar[] = "var"; static constexpr char kInit[] = "init"; static constexpr char kJustAssignment[] = "just-assignment"; static constexpr char kAssignment[] = "assignment"; static constexpr char kRHS[] = "rhs"; static auto refToVar() { return declRefExpr(to(varDecl().bind(kVar))); } namespace { // N.B. This analysis is deliberately simplistic, leaving out many important // details needed for a real analysis in production. Most notably, the transfer // function does not account for the variable's address possibly escaping, which // would invalidate the analysis. class ConstantPropagationAnalysis : public DataflowAnalysis { public: explicit ConstantPropagationAnalysis(ASTContext &Context) : DataflowAnalysis(Context) {} static ConstantPropagationLattice initialElement() { return ConstantPropagationLattice::bottom(); } void transfer(const CFGElement &E, ConstantPropagationLattice &Element, Environment &Env) { auto CS = E.getAs(); if (!CS) return; auto S = CS->getStmt(); auto matcher = stmt( anyOf(declStmt(hasSingleDecl(varDecl(hasType(isInteger()), hasInitializer(expr().bind(kInit))) .bind(kVar))), binaryOperator(hasOperatorName("="), hasLHS(refToVar()), hasRHS(expr().bind(kRHS))) .bind(kJustAssignment), binaryOperator(isAssignmentOperator(), hasLHS(refToVar())) .bind(kAssignment))); ASTContext &Context = getASTContext(); auto Results = match(matcher, *S, Context); if (Results.empty()) return; const BoundNodes &Nodes = Results[0]; const auto *Var = Nodes.getNodeAs(kVar); assert(Var != nullptr); if (const auto *E = Nodes.getNodeAs(kInit)) { Expr::EvalResult R; Element = (E->EvaluateAsInt(R, Context) && R.Val.isInt()) ? ConstantPropagationLattice{{{Var, R.Val.getInt().getExtValue()}}} : ConstantPropagationLattice::top(); } else if (Nodes.getNodeAs(kJustAssignment)) { const auto *RHS = Nodes.getNodeAs(kRHS); assert(RHS != nullptr); Expr::EvalResult R; Element = (RHS->EvaluateAsInt(R, Context) && R.Val.isInt()) ? ConstantPropagationLattice{{{Var, R.Val.getInt().getExtValue()}}} : ConstantPropagationLattice::top(); } else if (Nodes.getNodeAs(kAssignment)) // Any assignment involving the expression itself resets the variable to // "unknown". A more advanced analysis could try to evaluate the compound // assignment. For example, `x += 0` need not invalidate `x`. Element = ConstantPropagationLattice::top(); } }; using ::clang::dataflow::test::AnalysisInputs; using ::clang::dataflow::test::AnalysisOutputs; using ::clang::dataflow::test::checkDataflow; using ::llvm::IsStringMapEntry; using ::testing::UnorderedElementsAre; MATCHER_P(HasConstantVal, v, "") { return arg.Data && arg.Data->Value == v; } MATCHER(IsUnknown, "") { return arg == arg.bottom(); } MATCHER(Varies, "") { return arg == arg.top(); } MATCHER_P(HoldsCPLattice, m, ((negation ? "doesn't hold" : "holds") + llvm::StringRef(" a lattice element that ") + ::testing::DescribeMatcher(m, negation)) .str()) { return ExplainMatchResult(m, arg.Lattice, result_listener); } template void RunDataflow(llvm::StringRef Code, Matcher Expectations) { ASSERT_THAT_ERROR( checkDataflow( AnalysisInputs( Code, hasName("fun"), [](ASTContext &C, Environment &) { return ConstantPropagationAnalysis(C); }) .withASTBuildArgs({"-fsyntax-only", "-std=c++17"}), /*VerifyResults=*/ [&Expectations](const llvm::StringMap> &Results, const AnalysisOutputs &) { EXPECT_THAT(Results, Expectations); }), llvm::Succeeded()); } TEST(ConstantPropagationTest, JustInit) { std::string Code = R"( void fun() { int target = 1; // [[p]] } )"; RunDataflow(Code, UnorderedElementsAre(IsStringMapEntry( "p", HoldsCPLattice(HasConstantVal(1))))); } // Verifies that the analysis tracks the last variable seen. TEST(ConstantPropagationTest, TwoVariables) { std::string Code = R"( void fun() { int target = 1; // [[p1]] int other = 2; // [[p2]] target = 3; // [[p3]] } )"; RunDataflow(Code, UnorderedElementsAre( IsStringMapEntry("p1", HoldsCPLattice(HasConstantVal(1))), IsStringMapEntry("p2", HoldsCPLattice(HasConstantVal(2))), IsStringMapEntry("p3", HoldsCPLattice(HasConstantVal(3))))); } TEST(ConstantPropagationTest, Assignment) { std::string Code = R"( void fun() { int target = 1; // [[p1]] target = 2; // [[p2]] } )"; RunDataflow(Code, UnorderedElementsAre( IsStringMapEntry("p1", HoldsCPLattice(HasConstantVal(1))), IsStringMapEntry("p2", HoldsCPLattice(HasConstantVal(2))))); } TEST(ConstantPropagationTest, AssignmentCall) { std::string Code = R"( int g(); void fun() { int target; target = g(); // [[p]] } )"; RunDataflow(Code, UnorderedElementsAre( IsStringMapEntry("p", HoldsCPLattice(Varies())))); } TEST(ConstantPropagationTest, AssignmentBinOp) { std::string Code = R"( void fun() { int target; target = 2 + 3; // [[p]] } )"; RunDataflow(Code, UnorderedElementsAre(IsStringMapEntry( "p", HoldsCPLattice(HasConstantVal(5))))); } TEST(ConstantPropagationTest, PlusAssignment) { std::string Code = R"( void fun() { int target = 1; // [[p1]] target += 2; // [[p2]] } )"; RunDataflow(Code, UnorderedElementsAre( IsStringMapEntry("p1", HoldsCPLattice(HasConstantVal(1))), IsStringMapEntry("p2", HoldsCPLattice(Varies())))); } TEST(ConstantPropagationTest, SameAssignmentInBranches) { std::string Code = R"cc( void fun(bool b) { int target; // [[p1]] if (b) { target = 2; // [[pT]] } else { target = 2; // [[pF]] } (void)0; // [[p2]] } )cc"; RunDataflow(Code, UnorderedElementsAre( IsStringMapEntry("p1", HoldsCPLattice(IsUnknown())), IsStringMapEntry("pT", HoldsCPLattice(HasConstantVal(2))), IsStringMapEntry("pF", HoldsCPLattice(HasConstantVal(2))), IsStringMapEntry("p2", HoldsCPLattice(HasConstantVal(2))))); } TEST(ConstantPropagationTest, SameAssignmentInBranch) { std::string Code = R"cc( void fun(bool b) { int target = 1; // [[p1]] if (b) { target = 1; } (void)0; // [[p2]] } )cc"; RunDataflow(Code, UnorderedElementsAre( IsStringMapEntry("p1", HoldsCPLattice(HasConstantVal(1))), IsStringMapEntry("p2", HoldsCPLattice(HasConstantVal(1))))); } TEST(ConstantPropagationTest, NewVarInBranch) { std::string Code = R"cc( void fun(bool b) { if (b) { int target; // [[p1]] target = 1; // [[p2]] } else { int target; // [[p3]] target = 1; // [[p4]] } } )cc"; RunDataflow(Code, UnorderedElementsAre( IsStringMapEntry("p1", HoldsCPLattice(IsUnknown())), IsStringMapEntry("p2", HoldsCPLattice(HasConstantVal(1))), IsStringMapEntry("p3", HoldsCPLattice(IsUnknown())), IsStringMapEntry("p4", HoldsCPLattice(HasConstantVal(1))))); } TEST(ConstantPropagationTest, DifferentAssignmentInBranches) { std::string Code = R"cc( void fun(bool b) { int target; // [[p1]] if (b) { target = 1; // [[pT]] } else { target = 2; // [[pF]] } (void)0; // [[p2]] } )cc"; RunDataflow(Code, UnorderedElementsAre( IsStringMapEntry("p1", HoldsCPLattice(IsUnknown())), IsStringMapEntry("pT", HoldsCPLattice(HasConstantVal(1))), IsStringMapEntry("pF", HoldsCPLattice(HasConstantVal(2))), IsStringMapEntry("p2", HoldsCPLattice(Varies())))); } TEST(ConstantPropagationTest, DifferentAssignmentInBranch) { std::string Code = R"cc( void fun(bool b) { int target = 1; // [[p1]] if (b) { target = 3; } (void)0; // [[p2]] } )cc"; RunDataflow(Code, UnorderedElementsAre( IsStringMapEntry("p1", HoldsCPLattice(HasConstantVal(1))), IsStringMapEntry("p2", HoldsCPLattice(Varies())))); } } // namespace } // namespace dataflow } // namespace clang