blob: 6488833bd14cf2151e6cb2ca7fd6e029cbb33a61 [file] [log] [blame]
//===- unittests/Analysis/FlowSensitive/CachedConstAccessorsLatticeTest.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
//
//===----------------------------------------------------------------------===//
#include "clang/Analysis/FlowSensitive/CachedConstAccessorsLattice.h"
#include <cassert>
#include <memory>
#include "clang/AST/Decl.h"
#include "clang/AST/DeclBase.h"
#include "clang/AST/DeclCXX.h"
#include "clang/AST/Expr.h"
#include "clang/AST/Type.h"
#include "clang/ASTMatchers/ASTMatchFinder.h"
#include "clang/ASTMatchers/ASTMatchers.h"
#include "clang/Analysis/FlowSensitive/DataflowAnalysisContext.h"
#include "clang/Analysis/FlowSensitive/DataflowLattice.h"
#include "clang/Analysis/FlowSensitive/NoopLattice.h"
#include "clang/Analysis/FlowSensitive/StorageLocation.h"
#include "clang/Analysis/FlowSensitive/Value.h"
#include "clang/Analysis/FlowSensitive/WatchedLiteralsSolver.h"
#include "clang/Basic/LLVM.h"
#include "clang/Testing/TestAST.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
namespace clang::dataflow {
namespace {
using ast_matchers::BoundNodes;
using ast_matchers::callee;
using ast_matchers::cxxMemberCallExpr;
using ast_matchers::functionDecl;
using ast_matchers::hasName;
using ast_matchers::match;
using ast_matchers::selectFirst;
using dataflow::DataflowAnalysisContext;
using dataflow::Environment;
using dataflow::LatticeJoinEffect;
using dataflow::RecordStorageLocation;
using dataflow::Value;
using dataflow::WatchedLiteralsSolver;
using testing::SizeIs;
NamedDecl *lookup(StringRef Name, const DeclContext &DC) {
auto Result = DC.lookup(&DC.getParentASTContext().Idents.get(Name));
EXPECT_TRUE(Result.isSingleResult()) << Name;
return Result.front();
}
class CachedConstAccessorsLatticeTest : public ::testing::Test {
protected:
using LatticeT = CachedConstAccessorsLattice<NoopLattice>;
DataflowAnalysisContext DACtx{std::make_unique<WatchedLiteralsSolver>()};
Environment Env{DACtx};
};
// Basic test AST with two const methods (return a value, and return a ref).
struct CommonTestInputs {
CommonTestInputs()
: AST(R"cpp(
struct S {
int *valProperty() const;
int &refProperty() const;
};
void target() {
S s;
s.valProperty();
S s2;
s2.refProperty();
}
)cpp") {
auto *SDecl = cast<CXXRecordDecl>(
lookup("S", *AST.context().getTranslationUnitDecl()));
SType = AST.context().getRecordType(SDecl);
CallVal = selectFirst<CallExpr>(
"call",
match(cxxMemberCallExpr(callee(functionDecl(hasName("valProperty"))))
.bind("call"),
AST.context()));
assert(CallVal != nullptr);
CallRef = selectFirst<CallExpr>(
"call",
match(cxxMemberCallExpr(callee(functionDecl(hasName("refProperty"))))
.bind("call"),
AST.context()));
assert(CallRef != nullptr);
}
TestAST AST;
QualType SType;
const CallExpr *CallVal;
const CallExpr *CallRef;
};
TEST_F(CachedConstAccessorsLatticeTest,
SamePrimitiveValBeforeClearOrDiffAfterClear) {
CommonTestInputs Inputs;
auto *CE = Inputs.CallVal;
RecordStorageLocation Loc(Inputs.SType, RecordStorageLocation::FieldToLoc(),
{});
LatticeT Lattice;
Value *Val1 = Lattice.getOrCreateConstMethodReturnValue(Loc, CE, Env);
Value *Val2 = Lattice.getOrCreateConstMethodReturnValue(Loc, CE, Env);
EXPECT_EQ(Val1, Val2);
Lattice.clearConstMethodReturnValues(Loc);
Value *Val3 = Lattice.getOrCreateConstMethodReturnValue(Loc, CE, Env);
EXPECT_NE(Val3, Val1);
EXPECT_NE(Val3, Val2);
}
TEST_F(CachedConstAccessorsLatticeTest, SameLocBeforeClearOrDiffAfterClear) {
CommonTestInputs Inputs;
auto *CE = Inputs.CallRef;
RecordStorageLocation Loc(Inputs.SType, RecordStorageLocation::FieldToLoc(),
{});
LatticeT Lattice;
auto NopInit = [](StorageLocation &) {};
StorageLocation *Loc1 = Lattice.getOrCreateConstMethodReturnStorageLocation(
Loc, CE, Env, NopInit);
auto NotCalled = [](StorageLocation &) {
ASSERT_TRUE(false) << "Not reached";
};
StorageLocation *Loc2 = Lattice.getOrCreateConstMethodReturnStorageLocation(
Loc, CE, Env, NotCalled);
EXPECT_EQ(Loc1, Loc2);
Lattice.clearConstMethodReturnStorageLocations(Loc);
StorageLocation *Loc3 = Lattice.getOrCreateConstMethodReturnStorageLocation(
Loc, CE, Env, NopInit);
EXPECT_NE(Loc3, Loc1);
EXPECT_NE(Loc3, Loc2);
}
TEST_F(CachedConstAccessorsLatticeTest,
SameStructValBeforeClearOrDiffAfterClear) {
TestAST AST(R"cpp(
struct S {
S structValProperty() const;
};
void target() {
S s;
s.structValProperty();
}
)cpp");
auto *SDecl =
cast<CXXRecordDecl>(lookup("S", *AST.context().getTranslationUnitDecl()));
QualType SType = AST.context().getRecordType(SDecl);
const CallExpr *CE = selectFirst<CallExpr>(
"call", match(cxxMemberCallExpr(
callee(functionDecl(hasName("structValProperty"))))
.bind("call"),
AST.context()));
ASSERT_NE(CE, nullptr);
RecordStorageLocation Loc(SType, RecordStorageLocation::FieldToLoc(), {});
LatticeT Lattice;
// Accessors that return a record by value are modeled by a record storage
// location (instead of a Value).
auto NopInit = [](StorageLocation &) {};
StorageLocation *Loc1 = Lattice.getOrCreateConstMethodReturnStorageLocation(
Loc, CE, Env, NopInit);
auto NotCalled = [](StorageLocation &) {
ASSERT_TRUE(false) << "Not reached";
};
StorageLocation *Loc2 = Lattice.getOrCreateConstMethodReturnStorageLocation(
Loc, CE, Env, NotCalled);
EXPECT_EQ(Loc1, Loc2);
Lattice.clearConstMethodReturnStorageLocations(Loc);
StorageLocation *Loc3 = Lattice.getOrCreateConstMethodReturnStorageLocation(
Loc, CE, Env, NopInit);
EXPECT_NE(Loc3, Loc1);
EXPECT_NE(Loc3, Loc1);
}
TEST_F(CachedConstAccessorsLatticeTest, ClearDifferentLocs) {
CommonTestInputs Inputs;
auto *CE = Inputs.CallRef;
RecordStorageLocation LocS1(Inputs.SType, RecordStorageLocation::FieldToLoc(),
{});
RecordStorageLocation LocS2(Inputs.SType, RecordStorageLocation::FieldToLoc(),
{});
LatticeT Lattice;
auto NopInit = [](StorageLocation &) {};
StorageLocation *RetLoc1 =
Lattice.getOrCreateConstMethodReturnStorageLocation(LocS1, CE, Env,
NopInit);
Lattice.clearConstMethodReturnStorageLocations(LocS2);
auto NotCalled = [](StorageLocation &) {
ASSERT_TRUE(false) << "Not reached";
};
StorageLocation *RetLoc2 =
Lattice.getOrCreateConstMethodReturnStorageLocation(LocS1, CE, Env,
NotCalled);
EXPECT_EQ(RetLoc1, RetLoc2);
}
TEST_F(CachedConstAccessorsLatticeTest, DifferentValsFromDifferentLocs) {
TestAST AST(R"cpp(
struct S {
int *valProperty() const;
};
void target() {
S s1;
s1.valProperty();
S s2;
s2.valProperty();
}
)cpp");
auto *SDecl =
cast<CXXRecordDecl>(lookup("S", *AST.context().getTranslationUnitDecl()));
QualType SType = AST.context().getRecordType(SDecl);
SmallVector<BoundNodes, 1> valPropertyCalls =
match(cxxMemberCallExpr(callee(functionDecl(hasName("valProperty"))))
.bind("call"),
AST.context());
ASSERT_THAT(valPropertyCalls, SizeIs(2));
const CallExpr *CE1 = selectFirst<CallExpr>("call", valPropertyCalls);
ASSERT_NE(CE1, nullptr);
valPropertyCalls.erase(valPropertyCalls.begin());
const CallExpr *CE2 = selectFirst<CallExpr>("call", valPropertyCalls);
ASSERT_NE(CE2, nullptr);
ASSERT_NE(CE1, CE2);
RecordStorageLocation LocS1(SType, RecordStorageLocation::FieldToLoc(), {});
RecordStorageLocation LocS2(SType, RecordStorageLocation::FieldToLoc(), {});
LatticeT Lattice;
Value *Val1 = Lattice.getOrCreateConstMethodReturnValue(LocS1, CE1, Env);
Value *Val2 = Lattice.getOrCreateConstMethodReturnValue(LocS2, CE2, Env);
EXPECT_NE(Val1, Val2);
}
TEST_F(CachedConstAccessorsLatticeTest, JoinSameNoop) {
CommonTestInputs Inputs;
auto *CE = Inputs.CallVal;
RecordStorageLocation Loc(Inputs.SType, RecordStorageLocation::FieldToLoc(),
{});
LatticeT EmptyLattice;
LatticeT EmptyLattice2;
EXPECT_EQ(EmptyLattice.join(EmptyLattice2), LatticeJoinEffect::Unchanged);
LatticeT Lattice1;
Lattice1.getOrCreateConstMethodReturnValue(Loc, CE, Env);
EXPECT_EQ(Lattice1.join(Lattice1), LatticeJoinEffect::Unchanged);
}
TEST_F(CachedConstAccessorsLatticeTest, ProducesNewValueAfterJoinDistinct) {
CommonTestInputs Inputs;
auto *CE = Inputs.CallVal;
RecordStorageLocation Loc(Inputs.SType, RecordStorageLocation::FieldToLoc(),
{});
// L1 w/ v vs L2 empty
LatticeT Lattice1;
Value *Val1 = Lattice1.getOrCreateConstMethodReturnValue(Loc, CE, Env);
LatticeT EmptyLattice;
EXPECT_EQ(Lattice1.join(EmptyLattice), LatticeJoinEffect::Changed);
Value *ValAfterJoin =
Lattice1.getOrCreateConstMethodReturnValue(Loc, CE, Env);
EXPECT_NE(ValAfterJoin, Val1);
// L1 w/ v1 vs L3 w/ v2
LatticeT Lattice3;
Value *Val3 = Lattice3.getOrCreateConstMethodReturnValue(Loc, CE, Env);
EXPECT_EQ(Lattice1.join(Lattice3), LatticeJoinEffect::Changed);
Value *ValAfterJoin2 =
Lattice1.getOrCreateConstMethodReturnValue(Loc, CE, Env);
EXPECT_NE(ValAfterJoin2, ValAfterJoin);
EXPECT_NE(ValAfterJoin2, Val3);
}
} // namespace
} // namespace clang::dataflow