diff --git a/clang/include/clang/Analysis/FlowSensitive/MapLattice.h b/clang/include/clang/Analysis/FlowSensitive/MapLattice.h new file mode 100644 --- /dev/null +++ b/clang/include/clang/Analysis/FlowSensitive/MapLattice.h @@ -0,0 +1,110 @@ +//===- DataflowAnalysis.h ---------------------------------------*- C++ -*-===// +// +// 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 parameterized lattice that maps keys to individual +// lattice elements (of the parameter lattice type). A typical usage is lifting +// a particular lattice to all variables in a lexical scope. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_ANALYSIS_FLOWSENSITIVE__MAPLATTICE_H_ +#define LLVM_CLANG_ANALYSIS_FLOWSENSITIVE__MAPLATTICE_H_ + +#include +#include +#include + +#include "DataflowAnalysis.h" +#include "clang/AST/Decl.h" +#include "clang/Analysis/FlowSensitive/DataflowLattice.h" +#include "llvm/ADT/DenseMap.h" +#include "llvm/ADT/StringRef.h" + +namespace clang { +namespace dataflow { + +/// A lattice that maps keys to individual lattice elements. The default +/// constructor of `ElementLattice` should produce the bottom element. +template class MapLattice { + using Container = llvm::DenseMap; + +public: + using key_type = Key; + using mapped_type = ElementLattice; + using value_type = typename Container::value_type; + using iterator = typename Container::iterator; + using const_iterator = typename Container::const_iterator; + + MapLattice() = default; + + explicit MapLattice(Container C) { C = std::move(C); } + + static MapLattice bottom() { return MapLattice(); } + + void insert(const std::pair &P) { + (void)C.insert(P); + } + + void insert(std::pair &&P) { + (void)C.insert(std::move(P)); + } + + unsigned size() const { return C.size(); } + bool empty() const { return C.empty(); } + + iterator begin() { return C.begin(); } + iterator end() { return C.end(); } + const_iterator begin() const { return C.begin(); } + const_iterator end() const { return C.end(); } + + mapped_type &operator[](const key_type &K) { return C[K]; } + + friend bool operator==(const MapLattice &LHS, const MapLattice &RHS) { + return LHS.C == RHS.C; + } + + friend bool operator!=(const MapLattice &LHS, const MapLattice &RHS) { + return !(LHS == RHS); + } + + bool contains(const key_type &K) const { return C.find(K) != C.end(); } + + LatticeJoinEffect join(const MapLattice &Other) { + LatticeJoinEffect Effect = LatticeJoinEffect::Unchanged; + for (const auto &E : Other.C) + if (C[E.first].join(E.second) == LatticeJoinEffect::Changed) + Effect = LatticeJoinEffect::Changed; + return Effect; + } + +private: + Container C; +}; + +/// Convenience alias that captures the common use of map lattices to model +/// in-scope variables. +template +using VarMapLattice = MapLattice; + +template +std::ostream &operator<<(std::ostream &Os, + const VarMapLattice &M) { + std::string Separator = ""; + Os << "{"; + for (const auto &E : M) { + Os << std::exchange(Separator, ", ") << E.first->getName().str() << " => " + << E.second; + } + Os << "}"; + return Os; +} + +} // namespace dataflow +} // namespace clang + +#endif // LLVM_CLANG_ANALYSIS_FLOWSENSITIVE__MAPLATTICE_H_ diff --git a/clang/unittests/Analysis/FlowSensitive/CMakeLists.txt b/clang/unittests/Analysis/FlowSensitive/CMakeLists.txt --- a/clang/unittests/Analysis/FlowSensitive/CMakeLists.txt +++ b/clang/unittests/Analysis/FlowSensitive/CMakeLists.txt @@ -4,6 +4,7 @@ ) add_clang_unittest(ClangAnalysisFlowSensitiveTests + MapLatticeTest.cpp SingleVarConstantPropagationTest.cpp TestingSupport.cpp TestingSupportTest.cpp diff --git a/clang/unittests/Analysis/FlowSensitive/MapLatticeTest.cpp b/clang/unittests/Analysis/FlowSensitive/MapLatticeTest.cpp new file mode 100644 --- /dev/null +++ b/clang/unittests/Analysis/FlowSensitive/MapLatticeTest.cpp @@ -0,0 +1,224 @@ +#include "clang/Analysis/FlowSensitive/MapLattice.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/ASTMatchers/ASTMatchers.h" +#include "clang/Analysis/FlowSensitive/DataflowLattice.h" +#include "clang/Frontend/ASTUnit.h" +#include "clang/Tooling/Tooling.h" +#include "llvm/Support/Error.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include + +namespace clang { +namespace dataflow { +namespace { + +using ::testing::Pair; +using ::testing::UnorderedElementsAre; + +using namespace ast_matchers; + +MATCHER_P(Var, name, + (llvm::Twine(negation ? "isn't" : "is") + " a variable named `" + + name + "`") + .str()) { + return arg->getName() == name; +} + +// A simple lattice for basic tests. +class BooleanLattice { +public: + BooleanLattice() : Value(false) {} + explicit BooleanLattice(bool B) : Value(B) {} + + static BooleanLattice bottom() { return BooleanLattice(false); } + + static BooleanLattice top() { return BooleanLattice(true); } + + LatticeJoinEffect join(BooleanLattice Other) { + auto Prev = Value; + Value = Value || Other.Value; + return Prev == Value ? LatticeJoinEffect::Unchanged + : LatticeJoinEffect::Changed; + } + + friend bool operator==(BooleanLattice LHS, BooleanLattice RHS) { + return LHS.Value == RHS.Value; + } + + friend bool operator!=(BooleanLattice LHS, BooleanLattice RHS) { + return LHS.Value != RHS.Value; + } + + friend std::ostream &operator<<(std::ostream &Os, const BooleanLattice &B) { + Os << B.Value; + return Os; + } + + bool value() const { return Value; } + +private: + bool Value; +}; + +// Two `VarDecls` and the AST they are taken from. +struct ValidVarDecls { + // The AST from which the var-decls are drawn. Needs to be kept in the struct + // so that `var1`, `var2` will be valid. + std::unique_ptr Unit; + const VarDecl *Var1; + const VarDecl *Var2; +}; + +// Generates two valid `VarDecl`s named "test_var_1" and "test_var_2". +ValidVarDecls getVarDecls() { + std::string Code = R"cc( + void fun() { + int test_var_1; + int test_var_2; + } + )cc"; + + ValidVarDecls Result; + Result.Unit = tooling::buildASTFromCodeWithArgs(Code, {"-Wno-unused-value"}); + assert(Result.Unit != nullptr && "AST construction failed"); + + ASTContext &Context = Result.Unit->getASTContext(); + auto Matches = match( + functionDecl(hasBody(compoundStmt( + hasAnySubstatement(declStmt( + hasSingleDecl(varDecl(hasName("test_var_1")).bind("var1")))), + hasAnySubstatement(declStmt( + hasSingleDecl(varDecl(hasName("test_var_2")).bind("var2"))))))), + Context); + assert(Matches.size() == 1); + + Result.Var1 = Matches[0].getNodeAs("var1"); + Result.Var2 = Matches[0].getNodeAs("var2"); + assert(Result.Var1 != nullptr); + assert(Result.Var2 != nullptr); + + return Result; +} + +TEST(VarMapLatticeTest, InsertWorks) { + auto Decls = getVarDecls(); + + VarMapLattice Lattice; + Lattice.insert({Decls.Var1, BooleanLattice(false)}); + Lattice.insert({Decls.Var2, BooleanLattice(false)}); + + EXPECT_THAT(Lattice, UnorderedElementsAre( + Pair(Var("test_var_1"), BooleanLattice(false)), + Pair(Var("test_var_2"), BooleanLattice(false)))); +} + +TEST(VarMapLatticeTest, ComparisonWorks) { + auto Decls = getVarDecls(); + + VarMapLattice Lattice1; + Lattice1.insert({Decls.Var1, BooleanLattice(true)}); + Lattice1.insert({Decls.Var2, BooleanLattice(false)}); + VarMapLattice Lattice2 = Lattice1; + + EXPECT_EQ(Lattice1, Lattice2); + Lattice2[Decls.Var2] = BooleanLattice(true); + EXPECT_NE(Lattice1, Lattice2); +} + +TEST(VarMapLatticeTest, JoinChange) { + auto Decls = getVarDecls(); + + VarMapLattice Lattice1; + Lattice1.insert({Decls.Var1, BooleanLattice(false)}); + Lattice1.insert({Decls.Var2, BooleanLattice(false)}); + + VarMapLattice Lattice2; + Lattice2.insert({Decls.Var1, BooleanLattice(true)}); + Lattice2.insert({Decls.Var2, BooleanLattice(true)}); + + ASSERT_THAT(Lattice1, UnorderedElementsAre( + Pair(Var("test_var_1"), BooleanLattice(false)), + Pair(Var("test_var_2"), BooleanLattice(false)))); + + ASSERT_EQ(Lattice1.join(Lattice2), LatticeJoinEffect::Changed); + EXPECT_THAT(Lattice1, UnorderedElementsAre( + Pair(Var("test_var_1"), BooleanLattice(true)), + Pair(Var("test_var_2"), BooleanLattice(true)))); +} + +TEST(VarMapLatticeTest, JoinEqNoChange) { + auto Decls = getVarDecls(); + + VarMapLattice Lattice; + Lattice.insert({Decls.Var1, BooleanLattice(false)}); + Lattice.insert({Decls.Var2, BooleanLattice(false)}); + + ASSERT_EQ(Lattice.join(Lattice), LatticeJoinEffect::Unchanged); + EXPECT_THAT(Lattice, UnorderedElementsAre( + Pair(Var("test_var_1"), BooleanLattice(false)), + Pair(Var("test_var_2"), BooleanLattice(false)))); +} + +TEST(VarMapLatticeTest, JoinLtNoChange) { + auto Decls = getVarDecls(); + + VarMapLattice Lattice1; + Lattice1.insert({Decls.Var1, BooleanLattice(false)}); + Lattice1.insert({Decls.Var2, BooleanLattice(false)}); + + VarMapLattice Lattice2; + Lattice2.insert({Decls.Var1, BooleanLattice(true)}); + Lattice2.insert({Decls.Var2, BooleanLattice(true)}); + + ASSERT_THAT(Lattice1, UnorderedElementsAre( + Pair(Var("test_var_1"), BooleanLattice(false)), + Pair(Var("test_var_2"), BooleanLattice(false)))); + + ASSERT_THAT(Lattice2, UnorderedElementsAre( + Pair(Var("test_var_1"), BooleanLattice(true)), + Pair(Var("test_var_2"), BooleanLattice(true)))); + + ASSERT_EQ(Lattice2.join(Lattice1), LatticeJoinEffect::Unchanged); + EXPECT_THAT(Lattice2, UnorderedElementsAre( + Pair(Var("test_var_1"), BooleanLattice(true)), + Pair(Var("test_var_2"), BooleanLattice(true)))); +} + +TEST(VarMapLatticeTest, JoinDifferentDomainsProducesUnion) { + auto Decls = getVarDecls(); + + VarMapLattice Lattice1; + Lattice1.insert({Decls.Var1, BooleanLattice(true)}); + VarMapLattice Lattice2; + Lattice2.insert({Decls.Var2, BooleanLattice(true)}); + + ASSERT_EQ(Lattice1.join(Lattice2), LatticeJoinEffect::Changed); + EXPECT_THAT(Lattice1, UnorderedElementsAre( + Pair(Var("test_var_1"), BooleanLattice(true)), + Pair(Var("test_var_2"), BooleanLattice(true)))); +} + +TEST(VarMapLatticeTest, LookupWorks) { + auto Decls = getVarDecls(); + + VarMapLattice Lattice; + Lattice.insert({Decls.Var1, BooleanLattice(true)}); + Lattice.insert({Decls.Var2, BooleanLattice(false)}); + + EXPECT_EQ(Lattice[Decls.Var1], BooleanLattice(true)); + EXPECT_EQ(Lattice[Decls.Var2], BooleanLattice(false)); +} + +TEST(VarMapLatticeTest, ContainsWorks) { + auto Decls = getVarDecls(); + + VarMapLattice Lattice; + Lattice.insert({Decls.Var1, BooleanLattice(true)}); + EXPECT_TRUE(Lattice.contains(Decls.Var1)); + EXPECT_FALSE(Lattice.contains(Decls.Var2)); +} + +} // namespace +} // namespace dataflow +} // namespace clang