| //===- 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 <cstdint> |
| #include <memory> |
| #include <optional> |
| #include <ostream> |
| #include <string> |
| #include <utility> |
| |
| 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<VarValue> 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<ConstantPropagationAnalysis, |
| ConstantPropagationLattice> { |
| public: |
| explicit ConstantPropagationAnalysis(ASTContext &Context) |
| : DataflowAnalysis<ConstantPropagationAnalysis, |
| ConstantPropagationLattice>(Context) {} |
| |
| static ConstantPropagationLattice initialElement() { |
| return ConstantPropagationLattice::bottom(); |
| } |
| |
| void transfer(const CFGElement &E, ConstantPropagationLattice &Element, |
| Environment &Env) { |
| auto CS = E.getAs<CFGStmt>(); |
| 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<clang::VarDecl>(kVar); |
| assert(Var != nullptr); |
| |
| if (const auto *E = Nodes.getNodeAs<clang::Expr>(kInit)) { |
| Expr::EvalResult R; |
| Element = |
| (E->EvaluateAsInt(R, Context) && R.Val.isInt()) |
| ? ConstantPropagationLattice{{{Var, |
| R.Val.getInt().getExtValue()}}} |
| : ConstantPropagationLattice::top(); |
| } else if (Nodes.getNodeAs<clang::Expr>(kJustAssignment)) { |
| const auto *RHS = Nodes.getNodeAs<clang::Expr>(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<clang::Expr>(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<ConstantPropagationLattice>(m, negation)) |
| .str()) { |
| return ExplainMatchResult(m, arg.Lattice, result_listener); |
| } |
| |
| template <typename Matcher> |
| void RunDataflow(llvm::StringRef Code, Matcher Expectations) { |
| ASSERT_THAT_ERROR( |
| checkDataflow<ConstantPropagationAnalysis>( |
| AnalysisInputs<ConstantPropagationAnalysis>( |
| Code, hasName("fun"), |
| [](ASTContext &C, Environment &) { |
| return ConstantPropagationAnalysis(C); |
| }) |
| .withASTBuildArgs({"-fsyntax-only", "-std=c++17"}), |
| /*VerifyResults=*/ |
| [&Expectations](const llvm::StringMap<DataflowAnalysisState< |
| ConstantPropagationAnalysis::Lattice>> &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 |