1 //===- unittests/Analysis/FlowSensitive/MultiVarConstantPropagation.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 tracks all
11 // variables in the scope, but lacks escape analysis.
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 "clang/Analysis/FlowSensitive/MapLattice.h"
27 #include "llvm/ADT/StringRef.h"
28 #include "llvm/ADT/Twine.h"
29 #include "llvm/Support/Error.h"
30 #include "llvm/Testing/ADT/StringMapEntry.h"
31 #include "llvm/Testing/Support/Error.h"
32 #include "gmock/gmock.h"
33 #include "gtest/gtest.h"
44 using namespace ast_matchers
;
46 // Models the value of an expression at a program point, for all paths through
49 // FIXME: change the internal representation to use a `std::variant`, once
50 // clang admits C++17 constructs.
51 enum class ValueState
: bool {
55 // `State` determines the meaning of the lattice when `Value` is `None`:
56 // * `Undefined` -> bottom,
57 // * `Defined` -> top.
60 // When `std::nullopt`, the lattice is either at top or bottom, based on
62 std::optional
<int64_t> Value
;
64 constexpr ValueLattice()
65 : State(ValueState::Undefined
), Value(std::nullopt
) {}
66 constexpr ValueLattice(int64_t V
) : State(ValueState::Defined
), Value(V
) {}
67 constexpr ValueLattice(ValueState S
) : State(S
), Value(std::nullopt
) {}
69 static constexpr ValueLattice
bottom() {
70 return ValueLattice(ValueState::Undefined
);
72 static constexpr ValueLattice
top() {
73 return ValueLattice(ValueState::Defined
);
76 friend bool operator==(const ValueLattice
&Lhs
, const ValueLattice
&Rhs
) {
77 return Lhs
.State
== Rhs
.State
&& Lhs
.Value
== Rhs
.Value
;
79 friend bool operator!=(const ValueLattice
&Lhs
, const ValueLattice
&Rhs
) {
83 LatticeJoinEffect
join(const ValueLattice
&Other
) {
84 if (*this == Other
|| Other
== bottom() || *this == top())
85 return LatticeJoinEffect::Unchanged
;
87 if (*this == bottom()) {
89 return LatticeJoinEffect::Changed
;
93 return LatticeJoinEffect::Changed
;
97 std::ostream
&operator<<(std::ostream
&OS
, const ValueLattice
&L
) {
99 return OS
<< *L
.Value
;
101 case ValueLattice::ValueState::Undefined
:
103 case ValueLattice::ValueState::Defined
:
106 llvm_unreachable("unknown ValueState!");
109 using ConstantPropagationLattice
= VarMapLattice
<ValueLattice
>;
111 constexpr char kDecl
[] = "decl";
112 constexpr char kVar
[] = "var";
113 constexpr char kInit
[] = "init";
114 constexpr char kJustAssignment
[] = "just-assignment";
115 constexpr char kAssignment
[] = "assignment";
116 constexpr char kRHS
[] = "rhs";
118 auto refToVar() { return declRefExpr(to(varDecl().bind(kVar
))); }
120 // N.B. This analysis is deliberately simplistic, leaving out many important
121 // details needed for a real analysis. Most notably, the transfer function does
122 // not account for the variable's address possibly escaping, which would
123 // invalidate the analysis. It also could be optimized to drop out-of-scope
124 // variables from the map.
125 class ConstantPropagationAnalysis
126 : public DataflowAnalysis
<ConstantPropagationAnalysis
,
127 ConstantPropagationLattice
> {
129 explicit ConstantPropagationAnalysis(ASTContext
&Context
)
130 : DataflowAnalysis
<ConstantPropagationAnalysis
,
131 ConstantPropagationLattice
>(Context
) {}
133 static ConstantPropagationLattice
initialElement() {
134 return ConstantPropagationLattice::bottom();
137 void transfer(const CFGElement
&E
, ConstantPropagationLattice
&Vars
,
139 auto CS
= E
.getAs
<CFGStmt
>();
142 auto S
= CS
->getStmt();
144 stmt(anyOf(declStmt(hasSingleDecl(
145 varDecl(decl().bind(kVar
), hasType(isInteger()),
146 optionally(hasInitializer(expr().bind(kInit
))))
148 binaryOperator(hasOperatorName("="), hasLHS(refToVar()),
149 hasRHS(expr().bind(kRHS
)))
150 .bind(kJustAssignment
),
151 binaryOperator(isAssignmentOperator(), hasLHS(refToVar()))
152 .bind(kAssignment
)));
154 ASTContext
&Context
= getASTContext();
155 auto Results
= match(matcher
, *S
, Context
);
158 const BoundNodes
&Nodes
= Results
[0];
160 const auto *Var
= Nodes
.getNodeAs
<clang::VarDecl
>(kVar
);
161 assert(Var
!= nullptr);
163 if (Nodes
.getNodeAs
<clang::VarDecl
>(kDecl
) != nullptr) {
164 if (const auto *E
= Nodes
.getNodeAs
<clang::Expr
>(kInit
)) {
166 Vars
[Var
] = (E
->EvaluateAsInt(R
, Context
) && R
.Val
.isInt())
167 ? ValueLattice(R
.Val
.getInt().getExtValue())
168 : ValueLattice::top();
170 // An unitialized variable holds *some* value, but we don't know what it
171 // is (it is implementation defined), so we set it to top.
172 Vars
[Var
] = ValueLattice::top();
174 } else if (Nodes
.getNodeAs
<clang::Expr
>(kJustAssignment
)) {
175 const auto *E
= Nodes
.getNodeAs
<clang::Expr
>(kRHS
);
176 assert(E
!= nullptr);
179 Vars
[Var
] = (E
->EvaluateAsInt(R
, Context
) && R
.Val
.isInt())
180 ? ValueLattice(R
.Val
.getInt().getExtValue())
181 : ValueLattice::top();
182 } else if (Nodes
.getNodeAs
<clang::Expr
>(kAssignment
)) {
183 // Any assignment involving the expression itself resets the variable to
184 // "unknown". A more advanced analysis could try to evaluate the compound
185 // assignment. For example, `x += 0` need not invalidate `x`.
186 Vars
[Var
] = ValueLattice::top();
191 using ::clang::dataflow::test::AnalysisInputs
;
192 using ::clang::dataflow::test::AnalysisOutputs
;
193 using ::clang::dataflow::test::checkDataflow
;
194 using ::llvm::IsStringMapEntry
;
195 using ::testing::Pair
;
196 using ::testing::UnorderedElementsAre
;
199 (llvm::Twine(negation
? "isn't" : "is") + " a variable named `" +
202 return arg
->getName() == name
;
205 MATCHER_P(HasConstantVal
, v
, "") { return arg
.Value
&& *arg
.Value
== v
; }
207 MATCHER(Varies
, "") { return arg
== arg
.top(); }
209 MATCHER_P(HoldsCPLattice
, m
,
210 ((negation
? "doesn't hold" : "holds") +
211 llvm::StringRef(" a lattice element that ") +
212 ::testing::DescribeMatcher
<ConstantPropagationLattice
>(m
, negation
))
214 return ExplainMatchResult(m
, arg
.Lattice
, result_listener
);
217 template <typename Matcher
>
218 void RunDataflow(llvm::StringRef Code
, Matcher Expectations
) {
220 checkDataflow
<ConstantPropagationAnalysis
>(
221 AnalysisInputs
<ConstantPropagationAnalysis
>(
222 Code
, hasName("fun"),
223 [](ASTContext
&C
, Environment
&) {
224 return ConstantPropagationAnalysis(C
);
226 .withASTBuildArgs({"-fsyntax-only", "-std=c++17"}),
228 [&Expectations
](const llvm::StringMap
<DataflowAnalysisState
<
229 ConstantPropagationAnalysis::Lattice
>> &Results
,
230 const AnalysisOutputs
&) {
231 EXPECT_THAT(Results
, Expectations
);
236 TEST(MultiVarConstantPropagationTest
, JustInit
) {
237 std::string Code
= R
"(
243 RunDataflow(Code
, UnorderedElementsAre(IsStringMapEntry(
244 "p", HoldsCPLattice(UnorderedElementsAre(
245 Pair(Var("target"), HasConstantVal(1)))))));
248 TEST(MultiVarConstantPropagationTest
, Assignment
) {
249 std::string Code
= R
"(
259 UnorderedElementsAre(
260 IsStringMapEntry("p1", HoldsCPLattice(UnorderedElementsAre(
261 Pair(Var("target"), HasConstantVal(1))))),
262 IsStringMapEntry("p2", HoldsCPLattice(UnorderedElementsAre(Pair(
263 Var("target"), HasConstantVal(2)))))));
266 TEST(MultiVarConstantPropagationTest
, AssignmentCall
) {
267 std::string Code
= R
"(
275 RunDataflow(Code
, UnorderedElementsAre(IsStringMapEntry(
276 "p", HoldsCPLattice(UnorderedElementsAre(
277 Pair(Var("target"), Varies()))))));
280 TEST(MultiVarConstantPropagationTest
, AssignmentBinOp
) {
281 std::string Code
= R
"(
288 RunDataflow(Code
, UnorderedElementsAre(IsStringMapEntry(
289 "p", HoldsCPLattice(UnorderedElementsAre(
290 Pair(Var("target"), HasConstantVal(5)))))));
293 TEST(MultiVarConstantPropagationTest
, PlusAssignment
) {
294 std::string Code
= R
"(
303 Code
, UnorderedElementsAre(
304 IsStringMapEntry("p1", HoldsCPLattice(UnorderedElementsAre(Pair(
305 Var("target"), HasConstantVal(1))))),
306 IsStringMapEntry("p2", HoldsCPLattice(UnorderedElementsAre(
307 Pair(Var("target"), Varies()))))));
310 TEST(MultiVarConstantPropagationTest
, SameAssignmentInBranches
) {
311 std::string Code
= R
"cc(
328 UnorderedElementsAre(
329 IsStringMapEntry("p1", HoldsCPLattice(UnorderedElementsAre(
330 Pair(Var("target"), Varies())))),
331 IsStringMapEntry("pT", HoldsCPLattice(UnorderedElementsAre(
332 Pair(Var("target"), HasConstantVal(2))))),
333 IsStringMapEntry("pF", HoldsCPLattice(UnorderedElementsAre(
334 Pair(Var("target"), HasConstantVal(2))))),
335 IsStringMapEntry("p2", HoldsCPLattice(UnorderedElementsAre(Pair(
336 Var("target"), HasConstantVal(2)))))));
339 // Verifies that the analysis tracks multiple variables simultaneously.
340 TEST(MultiVarConstantPropagationTest
, TwoVariables
) {
341 std::string Code
= R
"(
353 UnorderedElementsAre(
354 IsStringMapEntry("p1", HoldsCPLattice(UnorderedElementsAre(
355 Pair(Var("target"), HasConstantVal(1))))),
356 IsStringMapEntry("p2", HoldsCPLattice(UnorderedElementsAre(
357 Pair(Var("target"), HasConstantVal(1)),
358 Pair(Var("other"), HasConstantVal(2))))),
359 IsStringMapEntry("p3", HoldsCPLattice(UnorderedElementsAre(
360 Pair(Var("target"), HasConstantVal(3)),
361 Pair(Var("other"), HasConstantVal(2)))))));
364 TEST(MultiVarConstantPropagationTest
, TwoVariablesInBranches
) {
365 std::string Code
= R
"cc(
383 UnorderedElementsAre(
384 IsStringMapEntry("p1", HoldsCPLattice(UnorderedElementsAre(
385 Pair(Var("target"), Varies()),
386 Pair(Var("other"), Varies())))),
387 IsStringMapEntry("pT", HoldsCPLattice(UnorderedElementsAre(
388 Pair(Var("target"), HasConstantVal(2)),
389 Pair(Var("other"), Varies())))),
390 IsStringMapEntry("pF", HoldsCPLattice(UnorderedElementsAre(
391 Pair(Var("other"), HasConstantVal(3)),
392 Pair(Var("target"), Varies())))),
393 IsStringMapEntry("p2", HoldsCPLattice(UnorderedElementsAre(
394 Pair(Var("target"), Varies()),
395 Pair(Var("other"), Varies()))))));
398 TEST(MultiVarConstantPropagationTest
, SameAssignmentInBranch
) {
399 std::string Code
= R
"cc(
412 UnorderedElementsAre(
413 IsStringMapEntry("p1", HoldsCPLattice(UnorderedElementsAre(
414 Pair(Var("target"), HasConstantVal(1))))),
415 IsStringMapEntry("p2", HoldsCPLattice(UnorderedElementsAre(Pair(
416 Var("target"), HasConstantVal(1)))))));
419 TEST(MultiVarConstantPropagationTest
, NewVarInBranch
) {
420 std::string Code
= R
"cc(
437 UnorderedElementsAre(
438 IsStringMapEntry("p1", HoldsCPLattice(UnorderedElementsAre(
439 Pair(Var("target"), Varies())))),
440 IsStringMapEntry("p2", HoldsCPLattice(UnorderedElementsAre(
441 Pair(Var("target"), HasConstantVal(1))))),
442 IsStringMapEntry("p3", HoldsCPLattice(UnorderedElementsAre(
443 Pair(Var("target"), Varies())))),
444 IsStringMapEntry("p4", HoldsCPLattice(UnorderedElementsAre(Pair(
445 Var("target"), HasConstantVal(1)))))));
448 TEST(MultiVarConstantPropagationTest
, DifferentAssignmentInBranches
) {
449 std::string Code
= R
"cc(
465 Code
, UnorderedElementsAre(
466 IsStringMapEntry("p1", HoldsCPLattice(UnorderedElementsAre(
467 Pair(Var("target"), Varies())))),
468 IsStringMapEntry("pT", HoldsCPLattice(UnorderedElementsAre(Pair(
469 Var("target"), HasConstantVal(1))))),
470 IsStringMapEntry("pF", HoldsCPLattice(UnorderedElementsAre(Pair(
471 Var("target"), HasConstantVal(2))))),
472 IsStringMapEntry("p2", HoldsCPLattice(UnorderedElementsAre(
473 Pair(Var("target"), Varies()))))));
476 TEST(MultiVarConstantPropagationTest
, DifferentAssignmentInBranch
) {
477 std::string Code
= R
"cc(
489 Code
, UnorderedElementsAre(
490 IsStringMapEntry("p1", HoldsCPLattice(UnorderedElementsAre(Pair(
491 Var("target"), HasConstantVal(1))))),
492 IsStringMapEntry("p2", HoldsCPLattice(UnorderedElementsAre(
493 Pair(Var("target"), Varies()))))));
497 } // namespace dataflow