
This class no longer serves any purpose; see also the discussion here: https://reviews.llvm.org/D155204#inline-1503204 A lot of existing tests in TransferTest.cpp check for the existence of `RecordValue`s. Some of these checks are now simply redundant and have been removed. In other cases, tests were checking for the existence of a `RecordValue` as a way of testing whether a record has been initialized. I have typically changed these test to instead check whether a field of the record has a value.
273 lines
9.7 KiB
C++
273 lines
9.7 KiB
C++
//===- 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
|