1 //===- unittests/Analysis/FlowSensitive/SingleVarConstantPropagation.cpp --===//
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
7 //===----------------------------------------------------------------------===//
9 // This file defines a simplistic version of Constant Propagation as an example
10 // of a forward, monotonic dataflow analysis. The analysis only tracks one
11 // variable at a time -- the one with the most recent declaration encountered.
13 //===----------------------------------------------------------------------===//
15 #include "TestingSupport.h"
16 #include "clang/AST/ASTContext.h"
17 #include "clang/AST/Decl.h"
18 #include "clang/AST/Expr.h"
19 #include "clang/AST/Stmt.h"
20 #include "clang/ASTMatchers/ASTMatchFinder.h"
21 #include "clang/ASTMatchers/ASTMatchers.h"
22 #include "clang/Analysis/CFG.h"
23 #include "clang/Analysis/FlowSensitive/DataflowAnalysis.h"
24 #include "clang/Analysis/FlowSensitive/DataflowEnvironment.h"
25 #include "clang/Analysis/FlowSensitive/DataflowLattice.h"
26 #include "llvm/ADT/StringRef.h"
27 #include "llvm/ADT/Twine.h"
28 #include "llvm/Support/Error.h"
29 #include "llvm/Testing/ADT/StringMapEntry.h"
30 #include "llvm/Testing/Annotations/Annotations.h"
31 #include "llvm/Testing/Support/Error.h"
32 #include "gmock/gmock.h"
33 #include "gtest/gtest.h"
44 using namespace ast_matchers
;
46 // A semi-lattice for dataflow analysis that tracks the value of a single
47 // integer variable. If it can be identified with a single (constant) value,
48 // then that value is stored.
49 struct ConstantPropagationLattice
{
50 // A null `Var` represents "top": either more than one value is possible or
51 // more than one variable was encountered. Otherwise, `Data` indicates that
52 // `Var` has the given `Value` at the program point with which this lattice
53 // element is associated, for all paths through the program.
58 friend bool operator==(VarValue Lhs
, VarValue Rhs
) {
59 return Lhs
.Var
== Rhs
.Var
&& Lhs
.Value
== Rhs
.Value
;
62 // `std::nullopt` is "bottom".
63 std::optional
<VarValue
> Data
;
65 static constexpr ConstantPropagationLattice
bottom() {
66 return {std::nullopt
};
68 static constexpr ConstantPropagationLattice
top() {
69 return {VarValue
{nullptr, 0}};
72 friend bool operator==(const ConstantPropagationLattice
&Lhs
,
73 const ConstantPropagationLattice
&Rhs
) {
74 return Lhs
.Data
== Rhs
.Data
;
77 LatticeJoinEffect
join(const ConstantPropagationLattice
&Other
) {
78 if (*this == Other
|| Other
== bottom() || *this == top())
79 return LatticeJoinEffect::Unchanged
;
81 if (*this == bottom()) {
83 return LatticeJoinEffect::Changed
;
87 return LatticeJoinEffect::Changed
;
91 std::ostream
&operator<<(std::ostream
&OS
,
92 const ConstantPropagationLattice
&L
) {
97 return OS
<< L
.Data
->Var
->getName().str() << " = " << L
.Data
->Value
;
102 static constexpr char kVar
[] = "var";
103 static constexpr char kInit
[] = "init";
104 static constexpr char kJustAssignment
[] = "just-assignment";
105 static constexpr char kAssignment
[] = "assignment";
106 static constexpr char kRHS
[] = "rhs";
108 static auto refToVar() { return declRefExpr(to(varDecl().bind(kVar
))); }
111 // N.B. This analysis is deliberately simplistic, leaving out many important
112 // details needed for a real analysis in production. Most notably, the transfer
113 // function does not account for the variable's address possibly escaping, which
114 // would invalidate the analysis.
115 class ConstantPropagationAnalysis
116 : public DataflowAnalysis
<ConstantPropagationAnalysis
,
117 ConstantPropagationLattice
> {
119 explicit ConstantPropagationAnalysis(ASTContext
&Context
)
120 : DataflowAnalysis
<ConstantPropagationAnalysis
,
121 ConstantPropagationLattice
>(Context
) {}
123 static ConstantPropagationLattice
initialElement() {
124 return ConstantPropagationLattice::bottom();
127 void transfer(const CFGElement
&E
, ConstantPropagationLattice
&Element
,
129 auto CS
= E
.getAs
<CFGStmt
>();
132 auto S
= CS
->getStmt();
134 anyOf(declStmt(hasSingleDecl(varDecl(hasType(isInteger()),
135 hasInitializer(expr().bind(kInit
)))
137 binaryOperator(hasOperatorName("="), hasLHS(refToVar()),
138 hasRHS(expr().bind(kRHS
)))
139 .bind(kJustAssignment
),
140 binaryOperator(isAssignmentOperator(), hasLHS(refToVar()))
141 .bind(kAssignment
)));
143 ASTContext
&Context
= getASTContext();
144 auto Results
= match(matcher
, *S
, Context
);
147 const BoundNodes
&Nodes
= Results
[0];
149 const auto *Var
= Nodes
.getNodeAs
<clang::VarDecl
>(kVar
);
150 assert(Var
!= nullptr);
152 if (const auto *E
= Nodes
.getNodeAs
<clang::Expr
>(kInit
)) {
155 (E
->EvaluateAsInt(R
, Context
) && R
.Val
.isInt())
156 ? ConstantPropagationLattice
{{{Var
,
157 R
.Val
.getInt().getExtValue()}}}
158 : ConstantPropagationLattice::top();
159 } else if (Nodes
.getNodeAs
<clang::Expr
>(kJustAssignment
)) {
160 const auto *RHS
= Nodes
.getNodeAs
<clang::Expr
>(kRHS
);
161 assert(RHS
!= nullptr);
165 (RHS
->EvaluateAsInt(R
, Context
) && R
.Val
.isInt())
166 ? ConstantPropagationLattice
{{{Var
,
167 R
.Val
.getInt().getExtValue()}}}
168 : ConstantPropagationLattice::top();
169 } else if (Nodes
.getNodeAs
<clang::Expr
>(kAssignment
))
170 // Any assignment involving the expression itself resets the variable to
171 // "unknown". A more advanced analysis could try to evaluate the compound
172 // assignment. For example, `x += 0` need not invalidate `x`.
173 Element
= ConstantPropagationLattice::top();
177 using ::clang::dataflow::test::AnalysisInputs
;
178 using ::clang::dataflow::test::AnalysisOutputs
;
179 using ::clang::dataflow::test::checkDataflow
;
180 using ::llvm::IsStringMapEntry
;
181 using ::testing::UnorderedElementsAre
;
183 MATCHER_P(HasConstantVal
, v
, "") { return arg
.Data
&& arg
.Data
->Value
== v
; }
185 MATCHER(IsUnknown
, "") { return arg
== arg
.bottom(); }
186 MATCHER(Varies
, "") { return arg
== arg
.top(); }
188 MATCHER_P(HoldsCPLattice
, m
,
189 ((negation
? "doesn't hold" : "holds") +
190 llvm::StringRef(" a lattice element that ") +
191 ::testing::DescribeMatcher
<ConstantPropagationLattice
>(m
, negation
))
193 return ExplainMatchResult(m
, arg
.Lattice
, result_listener
);
196 template <typename Matcher
>
197 void RunDataflow(llvm::StringRef Code
, Matcher Expectations
) {
199 checkDataflow
<ConstantPropagationAnalysis
>(
200 AnalysisInputs
<ConstantPropagationAnalysis
>(
201 Code
, hasName("fun"),
202 [](ASTContext
&C
, Environment
&) {
203 return ConstantPropagationAnalysis(C
);
205 .withASTBuildArgs({"-fsyntax-only", "-std=c++17"}),
207 [&Expectations
](const llvm::StringMap
<DataflowAnalysisState
<
208 ConstantPropagationAnalysis::Lattice
>> &Results
,
209 const AnalysisOutputs
&) {
210 EXPECT_THAT(Results
, Expectations
);
215 TEST(ConstantPropagationTest
, JustInit
) {
216 std::string Code
= R
"(
222 RunDataflow(Code
, UnorderedElementsAre(IsStringMapEntry(
223 "p", HoldsCPLattice(HasConstantVal(1)))));
226 // Verifies that the analysis tracks the last variable seen.
227 TEST(ConstantPropagationTest
, TwoVariables
) {
228 std::string Code
= R
"(
239 UnorderedElementsAre(
240 IsStringMapEntry("p1", HoldsCPLattice(HasConstantVal(1))),
241 IsStringMapEntry("p2", HoldsCPLattice(HasConstantVal(2))),
242 IsStringMapEntry("p3", HoldsCPLattice(HasConstantVal(3)))));
245 TEST(ConstantPropagationTest
, Assignment
) {
246 std::string Code
= R
"(
255 UnorderedElementsAre(
256 IsStringMapEntry("p1", HoldsCPLattice(HasConstantVal(1))),
257 IsStringMapEntry("p2", HoldsCPLattice(HasConstantVal(2)))));
260 TEST(ConstantPropagationTest
, AssignmentCall
) {
261 std::string Code
= R
"(
269 RunDataflow(Code
, UnorderedElementsAre(
270 IsStringMapEntry("p", HoldsCPLattice(Varies()))));
273 TEST(ConstantPropagationTest
, AssignmentBinOp
) {
274 std::string Code
= R
"(
281 RunDataflow(Code
, UnorderedElementsAre(IsStringMapEntry(
282 "p", HoldsCPLattice(HasConstantVal(5)))));
285 TEST(ConstantPropagationTest
, PlusAssignment
) {
286 std::string Code
= R
"(
295 UnorderedElementsAre(
296 IsStringMapEntry("p1", HoldsCPLattice(HasConstantVal(1))),
297 IsStringMapEntry("p2", HoldsCPLattice(Varies()))));
300 TEST(ConstantPropagationTest
, SameAssignmentInBranches
) {
301 std::string Code
= R
"cc(
317 UnorderedElementsAre(
318 IsStringMapEntry("p1", HoldsCPLattice(IsUnknown())),
319 IsStringMapEntry("pT", HoldsCPLattice(HasConstantVal(2))),
320 IsStringMapEntry("pF", HoldsCPLattice(HasConstantVal(2))),
321 IsStringMapEntry("p2", HoldsCPLattice(HasConstantVal(2)))));
324 TEST(ConstantPropagationTest
, SameAssignmentInBranch
) {
325 std::string Code
= R
"cc(
337 UnorderedElementsAre(
338 IsStringMapEntry("p1", HoldsCPLattice(HasConstantVal(1))),
339 IsStringMapEntry("p2", HoldsCPLattice(HasConstantVal(1)))));
342 TEST(ConstantPropagationTest
, NewVarInBranch
) {
343 std::string Code
= R
"cc(
359 UnorderedElementsAre(
360 IsStringMapEntry("p1", HoldsCPLattice(IsUnknown())),
361 IsStringMapEntry("p2", HoldsCPLattice(HasConstantVal(1))),
362 IsStringMapEntry("p3", HoldsCPLattice(IsUnknown())),
363 IsStringMapEntry("p4", HoldsCPLattice(HasConstantVal(1)))));
366 TEST(ConstantPropagationTest
, DifferentAssignmentInBranches
) {
367 std::string Code
= R
"cc(
383 UnorderedElementsAre(
384 IsStringMapEntry("p1", HoldsCPLattice(IsUnknown())),
385 IsStringMapEntry("pT", HoldsCPLattice(HasConstantVal(1))),
386 IsStringMapEntry("pF", HoldsCPLattice(HasConstantVal(2))),
387 IsStringMapEntry("p2", HoldsCPLattice(Varies()))));
390 TEST(ConstantPropagationTest
, DifferentAssignmentInBranch
) {
391 std::string Code
= R
"cc(
403 UnorderedElementsAre(
404 IsStringMapEntry("p1", HoldsCPLattice(HasConstantVal(1))),
405 IsStringMapEntry("p2", HoldsCPLattice(Varies()))));
409 } // namespace dataflow