blob: 88b92668c850c6129b67ecd8020f5afd4eea7fba [file] [log] [blame]
//===- unittests/Analysis/FlowSensitive/RecordOpsTest.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/RecordOps.h"
#include "TestingSupport.h"
#include "llvm/Testing/Support/Error.h"
#include "gtest/gtest.h"
namespace clang {
namespace dataflow {
namespace test {
namespace {
void runDataflow(
llvm::StringRef Code,
std::function<llvm::StringMap<QualType>(QualType)> SyntheticFieldCallback,
std::function<
void(const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &,
ASTContext &)>
VerifyResults) {
ASSERT_THAT_ERROR(checkDataflowWithNoopAnalysis(
Code, ast_matchers::hasName("target"), VerifyResults,
{BuiltinOptions()}, LangStandard::lang_cxx17,
SyntheticFieldCallback),
llvm::Succeeded());
}
const FieldDecl *getFieldNamed(RecordDecl *RD, llvm::StringRef Name) {
for (const FieldDecl *FD : RD->fields())
if (FD->getName() == Name)
return FD;
assert(false);
return nullptr;
}
TEST(RecordOpsTest, CopyRecord) {
std::string Code = R"(
struct S {
int outer_int;
int &ref;
struct {
int inner_int;
} inner;
};
void target(S s1, S s2) {
(void)s1.outer_int;
(void)s1.ref;
(void)s1.inner.inner_int;
// [[p]]
}
)";
runDataflow(
Code,
[](QualType Ty) -> llvm::StringMap<QualType> {
if (Ty.getAsString() != "S")
return {};
QualType IntTy =
getFieldNamed(Ty->getAsRecordDecl(), "outer_int")->getType();
return {{"synth_int", IntTy}};
},
[](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
ASTContext &ASTCtx) {
Environment Env = getEnvironmentAtAnnotation(Results, "p").fork();
const ValueDecl *OuterIntDecl = findValueDecl(ASTCtx, "outer_int");
const ValueDecl *RefDecl = findValueDecl(ASTCtx, "ref");
const ValueDecl *InnerDecl = findValueDecl(ASTCtx, "inner");
const ValueDecl *InnerIntDecl = findValueDecl(ASTCtx, "inner_int");
auto &S1 = getLocForDecl<RecordStorageLocation>(ASTCtx, Env, "s1");
auto &S2 = getLocForDecl<RecordStorageLocation>(ASTCtx, Env, "s2");
auto &Inner1 = *cast<RecordStorageLocation>(S1.getChild(*InnerDecl));
auto &Inner2 = *cast<RecordStorageLocation>(S2.getChild(*InnerDecl));
EXPECT_NE(getFieldValue(&S1, *OuterIntDecl, Env),
getFieldValue(&S2, *OuterIntDecl, Env));
EXPECT_NE(S1.getChild(*RefDecl), S2.getChild(*RefDecl));
EXPECT_NE(getFieldValue(&Inner1, *InnerIntDecl, Env),
getFieldValue(&Inner2, *InnerIntDecl, Env));
EXPECT_NE(Env.getValue(S1.getSyntheticField("synth_int")),
Env.getValue(S2.getSyntheticField("synth_int")));
copyRecord(S1, S2, Env);
EXPECT_EQ(getFieldValue(&S1, *OuterIntDecl, Env),
getFieldValue(&S2, *OuterIntDecl, Env));
EXPECT_EQ(S1.getChild(*RefDecl), S2.getChild(*RefDecl));
EXPECT_EQ(getFieldValue(&Inner1, *InnerIntDecl, Env),
getFieldValue(&Inner2, *InnerIntDecl, Env));
EXPECT_EQ(Env.getValue(S1.getSyntheticField("synth_int")),
Env.getValue(S2.getSyntheticField("synth_int")));
});
}
TEST(RecordOpsTest, RecordsEqual) {
std::string Code = R"(
struct S {
int outer_int;
int &ref;
struct {
int inner_int;
} inner;
};
void target(S s1, S s2) {
(void)s1.outer_int;
(void)s1.ref;
(void)s1.inner.inner_int;
// [[p]]
}
)";
runDataflow(
Code,
[](QualType Ty) -> llvm::StringMap<QualType> {
if (Ty.getAsString() != "S")
return {};
QualType IntTy =
getFieldNamed(Ty->getAsRecordDecl(), "outer_int")->getType();
return {{"synth_int", IntTy}};
},
[](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
ASTContext &ASTCtx) {
Environment Env = getEnvironmentAtAnnotation(Results, "p").fork();
const ValueDecl *OuterIntDecl = findValueDecl(ASTCtx, "outer_int");
const ValueDecl *RefDecl = findValueDecl(ASTCtx, "ref");
const ValueDecl *InnerDecl = findValueDecl(ASTCtx, "inner");
const ValueDecl *InnerIntDecl = findValueDecl(ASTCtx, "inner_int");
auto &S1 = getLocForDecl<RecordStorageLocation>(ASTCtx, Env, "s1");
auto &S2 = getLocForDecl<RecordStorageLocation>(ASTCtx, Env, "s2");
auto &Inner2 = *cast<RecordStorageLocation>(S2.getChild(*InnerDecl));
Env.setValue(S1.getSyntheticField("synth_int"),
Env.create<IntegerValue>());
// Strategy: Create two equal records, then verify each of the various
// ways in which records can differ causes recordsEqual to return false.
// changes we can make to the record.
// This test reuses the same objects for multiple checks, which isn't
// great, but seems better than duplicating the setup code for every
// check.
copyRecord(S1, S2, Env);
EXPECT_TRUE(recordsEqual(S1, S2, Env));
// S2 has a different outer_int.
Env.setValue(*S2.getChild(*OuterIntDecl), Env.create<IntegerValue>());
EXPECT_FALSE(recordsEqual(S1, S2, Env));
copyRecord(S1, S2, Env);
EXPECT_TRUE(recordsEqual(S1, S2, Env));
// S2 doesn't have outer_int at all.
Env.clearValue(*S2.getChild(*OuterIntDecl));
EXPECT_FALSE(recordsEqual(S1, S2, Env));
copyRecord(S1, S2, Env);
EXPECT_TRUE(recordsEqual(S1, S2, Env));
// S2 has a different ref.
S2.setChild(*RefDecl, &Env.createStorageLocation(
RefDecl->getType().getNonReferenceType()));
EXPECT_FALSE(recordsEqual(S1, S2, Env));
copyRecord(S1, S2, Env);
EXPECT_TRUE(recordsEqual(S1, S2, Env));
// S2 as a different inner_int.
Env.setValue(*Inner2.getChild(*InnerIntDecl),
Env.create<IntegerValue>());
EXPECT_FALSE(recordsEqual(S1, S2, Env));
copyRecord(S1, S2, Env);
EXPECT_TRUE(recordsEqual(S1, S2, Env));
// S2 has a different synth_int.
Env.setValue(S2.getSyntheticField("synth_int"),
Env.create<IntegerValue>());
EXPECT_FALSE(recordsEqual(S1, S2, Env));
copyRecord(S1, S2, Env);
EXPECT_TRUE(recordsEqual(S1, S2, Env));
// S2 doesn't have a value for synth_int.
Env.clearValue(S2.getSyntheticField("synth_int"));
EXPECT_FALSE(recordsEqual(S1, S2, Env));
copyRecord(S1, S2, Env);
EXPECT_TRUE(recordsEqual(S1, S2, Env));
});
}
TEST(TransferTest, CopyRecordBetweenDerivedAndBase) {
std::string Code = R"(
struct A {
int i;
};
struct B : public A {
};
void target(A a, B b) {
(void)a.i;
// [[p]]
}
)";
auto SyntheticFieldCallback = [](QualType Ty) -> llvm::StringMap<QualType> {
CXXRecordDecl *ADecl = nullptr;
if (Ty.getAsString() == "A")
ADecl = Ty->getAsCXXRecordDecl();
else if (Ty.getAsString() == "B")
ADecl = Ty->getAsCXXRecordDecl()
->bases_begin()
->getType()
->getAsCXXRecordDecl();
else
return {};
QualType IntTy = getFieldNamed(ADecl, "i")->getType();
return {{"synth_int", IntTy}};
};
// Test copying derived to base class.
runDataflow(
Code, SyntheticFieldCallback,
[](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
ASTContext &ASTCtx) {
Environment Env = getEnvironmentAtAnnotation(Results, "p").fork();
const ValueDecl *IDecl = findValueDecl(ASTCtx, "i");
auto &A = getLocForDecl<RecordStorageLocation>(ASTCtx, Env, "a");
auto &B = getLocForDecl<RecordStorageLocation>(ASTCtx, Env, "b");
EXPECT_NE(Env.getValue(*A.getChild(*IDecl)),
Env.getValue(*B.getChild(*IDecl)));
EXPECT_NE(Env.getValue(A.getSyntheticField("synth_int")),
Env.getValue(B.getSyntheticField("synth_int")));
copyRecord(B, A, Env);
EXPECT_EQ(Env.getValue(*A.getChild(*IDecl)),
Env.getValue(*B.getChild(*IDecl)));
EXPECT_EQ(Env.getValue(A.getSyntheticField("synth_int")),
Env.getValue(B.getSyntheticField("synth_int")));
});
// Test copying base to derived class.
runDataflow(
Code, SyntheticFieldCallback,
[](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
ASTContext &ASTCtx) {
Environment Env = getEnvironmentAtAnnotation(Results, "p").fork();
const ValueDecl *IDecl = findValueDecl(ASTCtx, "i");
auto &A = getLocForDecl<RecordStorageLocation>(ASTCtx, Env, "a");
auto &B = getLocForDecl<RecordStorageLocation>(ASTCtx, Env, "b");
EXPECT_NE(Env.getValue(*A.getChild(*IDecl)),
Env.getValue(*B.getChild(*IDecl)));
EXPECT_NE(Env.getValue(A.getSyntheticField("synth_int")),
Env.getValue(B.getSyntheticField("synth_int")));
copyRecord(A, B, Env);
EXPECT_EQ(Env.getValue(*A.getChild(*IDecl)),
Env.getValue(*B.getChild(*IDecl)));
EXPECT_EQ(Env.getValue(A.getSyntheticField("synth_int")),
Env.getValue(B.getSyntheticField("synth_int")));
});
}
} // namespace
} // namespace test
} // namespace dataflow
} // namespace clang