clang/unittests/Analysis/FlowSensitive/MultiVarConstantPropagationTest.cpp (412 lines of code) (raw):

//===- unittests/Analysis/FlowSensitive/SingelVarConstantPropagation.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 tracks all // variables in the scope, but lacks escape analysis. // //===----------------------------------------------------------------------===// #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/FlowSensitive/DataflowAnalysis.h" #include "clang/Analysis/FlowSensitive/DataflowEnvironment.h" #include "clang/Analysis/FlowSensitive/DataflowLattice.h" #include "clang/Analysis/FlowSensitive/MapLattice.h" #include "clang/Tooling/Tooling.h" #include "llvm/ADT/None.h" #include "llvm/ADT/Optional.h" #include "llvm/ADT/StringRef.h" #include "llvm/ADT/Twine.h" #include "llvm/Support/Error.h" #include "llvm/Testing/Support/Annotations.h" #include "gmock/gmock.h" #include "gtest/gtest.h" #include <cstdint> #include <memory> #include <ostream> #include <string> #include <utility> namespace clang { namespace dataflow { namespace { using namespace ast_matchers; // Models the value of an expression at a program point, for all paths through // the program. struct ValueLattice { // FIXME: change the internal representation to use a `std::variant`, once // clang admits C++17 constructs. enum class ValueState : bool { Undefined, Defined, }; // `State` determines the meaning of the lattice when `Value` is `None`: // * `Undefined` -> bottom, // * `Defined` -> top. ValueState State; // When `None`, the lattice is either at top or bottom, based on `State`. llvm::Optional<int64_t> Value; constexpr ValueLattice() : State(ValueState::Undefined), Value(llvm::None) {} constexpr ValueLattice(int64_t V) : State(ValueState::Defined), Value(V) {} constexpr ValueLattice(ValueState S) : State(S), Value(llvm::None) {} static constexpr ValueLattice bottom() { return ValueLattice(ValueState::Undefined); } static constexpr ValueLattice top() { return ValueLattice(ValueState::Defined); } friend bool operator==(const ValueLattice &Lhs, const ValueLattice &Rhs) { return Lhs.State == Rhs.State && Lhs.Value == Rhs.Value; } friend bool operator!=(const ValueLattice &Lhs, const ValueLattice &Rhs) { return !(Lhs == Rhs); } LatticeJoinEffect join(const ValueLattice &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 ValueLattice &L) { if (L.Value.hasValue()) return OS << *L.Value; switch (L.State) { case ValueLattice::ValueState::Undefined: return OS << "None"; case ValueLattice::ValueState::Defined: return OS << "Any"; } llvm_unreachable("unknown ValueState!"); } using ConstantPropagationLattice = VarMapLattice<ValueLattice>; constexpr char kDecl[] = "decl"; constexpr char kVar[] = "var"; constexpr char kInit[] = "init"; constexpr char kJustAssignment[] = "just-assignment"; constexpr char kAssignment[] = "assignment"; constexpr char kRHS[] = "rhs"; auto refToVar() { return declRefExpr(to(varDecl().bind(kVar))); } // N.B. This analysis is deliberately simplistic, leaving out many important // details needed for a real analysis. Most notably, the transfer function does // not account for the variable's address possibly escaping, which would // invalidate the analysis. It also could be optimized to drop out-of-scope // variables from the map. class ConstantPropagationAnalysis : public DataflowAnalysis<ConstantPropagationAnalysis, ConstantPropagationLattice> { public: explicit ConstantPropagationAnalysis(ASTContext &Context) : DataflowAnalysis<ConstantPropagationAnalysis, ConstantPropagationLattice>(Context) {} static ConstantPropagationLattice initialElement() { return ConstantPropagationLattice::bottom(); } ConstantPropagationLattice transfer(const Stmt *S, ConstantPropagationLattice Vars, Environment &Env) { auto matcher = stmt(anyOf(declStmt(hasSingleDecl( varDecl(decl().bind(kVar), hasType(isInteger()), optionally(hasInitializer(expr().bind(kInit)))) .bind(kDecl))), 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 Vars; const BoundNodes &Nodes = Results[0]; const auto *Var = Nodes.getNodeAs<clang::VarDecl>(kVar); assert(Var != nullptr); if (Nodes.getNodeAs<clang::VarDecl>(kDecl) != nullptr) { if (const auto *E = Nodes.getNodeAs<clang::Expr>(kInit)) { Expr::EvalResult R; Vars[Var] = (E->EvaluateAsInt(R, Context) && R.Val.isInt()) ? ValueLattice(R.Val.getInt().getExtValue()) : ValueLattice::top(); } else { // An unitialized variable holds *some* value, but we don't know what it // is (it is implementation defined), so we set it to top. Vars[Var] = ValueLattice::top(); } return Vars; } if (Nodes.getNodeAs<clang::Expr>(kJustAssignment)) { const auto *E = Nodes.getNodeAs<clang::Expr>(kRHS); assert(E != nullptr); Expr::EvalResult R; Vars[Var] = (E->EvaluateAsInt(R, Context) && R.Val.isInt()) ? ValueLattice(R.Val.getInt().getExtValue()) : ValueLattice::top(); return Vars; } // 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`. if (Nodes.getNodeAs<clang::Expr>(kAssignment)) { Vars[Var] = ValueLattice::top(); return Vars; } llvm_unreachable("expected at least one bound identifier"); } }; using ::testing::IsEmpty; using ::testing::Pair; using ::testing::UnorderedElementsAre; MATCHER_P(Var, name, (llvm::Twine(negation ? "isn't" : "is") + " a variable named `" + name + "`") .str()) { return arg->getName() == name; } MATCHER_P(HasConstantVal, v, "") { return arg.Value.hasValue() && *arg.Value == v; } 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); } class MultiVarConstantPropagationTest : public ::testing::Test { protected: template <typename Matcher> void RunDataflow(llvm::StringRef Code, Matcher Expectations) { test::checkDataflow<ConstantPropagationAnalysis>( Code, "fun", [](ASTContext &C, Environment &) { return ConstantPropagationAnalysis(C); }, [&Expectations]( llvm::ArrayRef<std::pair< std::string, DataflowAnalysisState<ConstantPropagationAnalysis::Lattice>>> Results, ASTContext &) { EXPECT_THAT(Results, Expectations); }, {"-fsyntax-only", "-std=c++17"}); } }; TEST_F(MultiVarConstantPropagationTest, JustInit) { std::string Code = R"( void fun() { int target = 1; // [[p]] } )"; RunDataflow(Code, UnorderedElementsAre( Pair("p", HoldsCPLattice(UnorderedElementsAre(Pair( Var("target"), HasConstantVal(1))))))); } TEST_F(MultiVarConstantPropagationTest, Assignment) { std::string Code = R"( void fun() { int target = 1; // [[p1]] target = 2; // [[p2]] } )"; RunDataflow(Code, UnorderedElementsAre( Pair("p1", HoldsCPLattice(UnorderedElementsAre(Pair( Var("target"), HasConstantVal(1))))), Pair("p2", HoldsCPLattice(UnorderedElementsAre(Pair( Var("target"), HasConstantVal(2))))))); } TEST_F(MultiVarConstantPropagationTest, AssignmentCall) { std::string Code = R"( int g(); void fun() { int target; target = g(); // [[p]] } )"; RunDataflow(Code, UnorderedElementsAre( Pair("p", HoldsCPLattice(UnorderedElementsAre( Pair(Var("target"), Varies())))))); } TEST_F(MultiVarConstantPropagationTest, AssignmentBinOp) { std::string Code = R"( void fun() { int target; target = 2 + 3; // [[p]] } )"; RunDataflow(Code, UnorderedElementsAre( Pair("p", HoldsCPLattice(UnorderedElementsAre(Pair( Var("target"), HasConstantVal(5))))))); } TEST_F(MultiVarConstantPropagationTest, PlusAssignment) { std::string Code = R"( void fun() { int target = 1; // [[p1]] target += 2; // [[p2]] } )"; RunDataflow(Code, UnorderedElementsAre( Pair("p1", HoldsCPLattice(UnorderedElementsAre(Pair( Var("target"), HasConstantVal(1))))), Pair("p2", HoldsCPLattice(UnorderedElementsAre( Pair(Var("target"), Varies())))))); } TEST_F(MultiVarConstantPropagationTest, 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( Pair("p1", HoldsCPLattice(UnorderedElementsAre( Pair(Var("target"), Varies())))), Pair("pT", HoldsCPLattice(UnorderedElementsAre( Pair(Var("target"), HasConstantVal(2))))), Pair("pF", HoldsCPLattice(UnorderedElementsAre( Pair(Var("target"), HasConstantVal(2))))), Pair("p2", HoldsCPLattice(UnorderedElementsAre( Pair(Var("target"), HasConstantVal(2))))))); } // Verifies that the analysis tracks multiple variables simultaneously. TEST_F(MultiVarConstantPropagationTest, TwoVariables) { std::string Code = R"( void fun() { int target = 1; // [[p1]] int other = 2; // [[p2]] target = 3; // [[p3]] } )"; RunDataflow(Code, UnorderedElementsAre( Pair("p1", HoldsCPLattice(UnorderedElementsAre( Pair(Var("target"), HasConstantVal(1))))), Pair("p2", HoldsCPLattice(UnorderedElementsAre( Pair(Var("target"), HasConstantVal(1)), Pair(Var("other"), HasConstantVal(2))))), Pair("p3", HoldsCPLattice(UnorderedElementsAre( Pair(Var("target"), HasConstantVal(3)), Pair(Var("other"), HasConstantVal(2))))))); } TEST_F(MultiVarConstantPropagationTest, TwoVariablesInBranches) { std::string Code = R"cc( void fun(bool b) { int target; int other; // [[p1]] if (b) { target = 2; // [[pT]] } else { other = 3; // [[pF]] } (void)0; // [[p2]] } )cc"; RunDataflow(Code, UnorderedElementsAre( Pair("p1", HoldsCPLattice(UnorderedElementsAre( Pair(Var("target"), Varies()), Pair(Var("other"), Varies())))), Pair("pT", HoldsCPLattice(UnorderedElementsAre( Pair(Var("target"), HasConstantVal(2)), Pair(Var("other"), Varies())))), Pair("pF", HoldsCPLattice(UnorderedElementsAre( Pair(Var("other"), HasConstantVal(3)), Pair(Var("target"), Varies())))), Pair("p2", HoldsCPLattice(UnorderedElementsAre( Pair(Var("target"), Varies()), Pair(Var("other"), Varies())))))); } TEST_F(MultiVarConstantPropagationTest, 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( Pair("p1", HoldsCPLattice(UnorderedElementsAre(Pair( Var("target"), HasConstantVal(1))))), Pair("p2", HoldsCPLattice(UnorderedElementsAre(Pair( Var("target"), HasConstantVal(1))))))); } TEST_F(MultiVarConstantPropagationTest, 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( Pair("p1", HoldsCPLattice(UnorderedElementsAre( Pair(Var("target"), Varies())))), Pair("p2", HoldsCPLattice(UnorderedElementsAre(Pair( Var("target"), HasConstantVal(1))))), Pair("p3", HoldsCPLattice(UnorderedElementsAre( Pair(Var("target"), Varies())))), Pair("p4", HoldsCPLattice(UnorderedElementsAre(Pair( Var("target"), HasConstantVal(1))))))); } TEST_F(MultiVarConstantPropagationTest, 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( Pair("p1", HoldsCPLattice(UnorderedElementsAre( Pair(Var("target"), Varies())))), Pair("pT", HoldsCPLattice(UnorderedElementsAre(Pair( Var("target"), HasConstantVal(1))))), Pair("pF", HoldsCPLattice(UnorderedElementsAre(Pair( Var("target"), HasConstantVal(2))))), Pair("p2", HoldsCPLattice(UnorderedElementsAre( Pair(Var("target"), Varies())))))); } TEST_F(MultiVarConstantPropagationTest, 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( Pair("p1", HoldsCPLattice(UnorderedElementsAre(Pair( Var("target"), HasConstantVal(1))))), Pair("p2", HoldsCPLattice(UnorderedElementsAre( Pair(Var("target"), Varies())))))); } } // namespace } // namespace dataflow } // namespace clang