[clang] 7d941d6 - [clang][dataflow] Add transfer functions for constructors
Stanislav Gatev via cfe-commits
cfe-commits at lists.llvm.org
Fri Jan 14 06:59:18 PST 2022
Author: Stanislav Gatev
Date: 2022-01-14T14:58:01Z
New Revision: 7d941d6d21e91e8466bf200da094d027338b92fa
URL: https://github.com/llvm/llvm-project/commit/7d941d6d21e91e8466bf200da094d027338b92fa
DIFF: https://github.com/llvm/llvm-project/commit/7d941d6d21e91e8466bf200da094d027338b92fa.diff
LOG: [clang][dataflow] Add transfer functions for constructors
This is part of the implementation of the dataflow analysis framework.
See "[RFC] A dataflow analysis framework for Clang AST" on cfe-dev.
Reviewed-by: ymandel, xazax.hun
Differential Revision: https://reviews.llvm.org/D117218
Added:
Modified:
clang/lib/Analysis/FlowSensitive/DataflowEnvironment.cpp
clang/lib/Analysis/FlowSensitive/Transfer.cpp
clang/unittests/Analysis/FlowSensitive/TransferTest.cpp
Removed:
################################################################################
diff --git a/clang/lib/Analysis/FlowSensitive/DataflowEnvironment.cpp b/clang/lib/Analysis/FlowSensitive/DataflowEnvironment.cpp
index 5824bc13ce7b6..7d0cbf3f656bd 100644
--- a/clang/lib/Analysis/FlowSensitive/DataflowEnvironment.cpp
+++ b/clang/lib/Analysis/FlowSensitive/DataflowEnvironment.cpp
@@ -154,8 +154,20 @@ StorageLocation *Environment::getThisPointeeStorageLocation() const {
return DACtx->getThisPointeeStorageLocation();
}
-void Environment::setValue(const StorageLocation &Loc, Value &Value) {
- LocToVal[&Loc] = &Value;
+void Environment::setValue(const StorageLocation &Loc, Value &Val) {
+ LocToVal[&Loc] = &Val;
+
+ if (auto *StructVal = dyn_cast<StructValue>(&Val)) {
+ auto &AggregateLoc = *cast<AggregateStorageLocation>(&Loc);
+
+ const QualType Type = AggregateLoc.getType();
+ assert(Type->isStructureOrClassType());
+
+ for (const FieldDecl *Field : Type->getAsRecordDecl()->fields()) {
+ assert(Field != nullptr);
+ setValue(AggregateLoc.getChild(*Field), StructVal->getChild(*Field));
+ }
+ }
}
Value *Environment::getValue(const StorageLocation &Loc) const {
diff --git a/clang/lib/Analysis/FlowSensitive/Transfer.cpp b/clang/lib/Analysis/FlowSensitive/Transfer.cpp
index 7681b729d2b8e..8b35ec023ecc1 100644
--- a/clang/lib/Analysis/FlowSensitive/Transfer.cpp
+++ b/clang/lib/Analysis/FlowSensitive/Transfer.cpp
@@ -14,6 +14,7 @@
#include "clang/Analysis/FlowSensitive/Transfer.h"
#include "clang/AST/Decl.h"
#include "clang/AST/DeclBase.h"
+#include "clang/AST/DeclCXX.h"
#include "clang/AST/Expr.h"
#include "clang/AST/ExprCXX.h"
#include "clang/AST/OperationKinds.h"
@@ -28,6 +29,12 @@
namespace clang {
namespace dataflow {
+static const Expr *skipExprWithCleanups(const Expr *E) {
+ if (auto *C = dyn_cast_or_null<ExprWithCleanups>(E))
+ return C->getSubExpr();
+ return E;
+}
+
class TransferVisitor : public ConstStmtVisitor<TransferVisitor> {
public:
TransferVisitor(Environment &Env) : Env(Env) {}
@@ -89,23 +96,39 @@ class TransferVisitor : public ConstStmtVisitor<TransferVisitor> {
}
void VisitImplicitCastExpr(const ImplicitCastExpr *S) {
- if (S->getCastKind() == CK_LValueToRValue) {
- // The CFG does not contain `ParenExpr` as top-level statements in basic
- // blocks, however sub-expressions can still be of that type.
- assert(S->getSubExpr() != nullptr);
- const Expr *SubExpr = S->getSubExpr()->IgnoreParens();
-
- assert(SubExpr != nullptr);
+ // The CFG does not contain `ParenExpr` as top-level statements in basic
+ // blocks, however sub-expressions can still be of that type.
+ assert(S->getSubExpr() != nullptr);
+ const Expr *SubExpr = S->getSubExpr()->IgnoreParens();
+ assert(SubExpr != nullptr);
+
+ switch (S->getCastKind()) {
+ case CK_LValueToRValue: {
auto *SubExprVal = Env.getValue(*SubExpr, SkipPast::Reference);
if (SubExprVal == nullptr)
- return;
+ break;
auto &ExprLoc = Env.createStorageLocation(*S);
Env.setStorageLocation(*S, ExprLoc);
Env.setValue(ExprLoc, *SubExprVal);
+ break;
+ }
+ case CK_NoOp: {
+ // FIXME: Consider making `Environment::getStorageLocation` skip noop
+ // expressions (this and other similar expressions in the file) instead of
+ // assigning them storage locations.
+ auto *SubExprLoc = Env.getStorageLocation(*SubExpr, SkipPast::None);
+ if (SubExprLoc == nullptr)
+ break;
+
+ Env.setStorageLocation(*S, *SubExprLoc);
+ break;
+ }
+ default:
+ // FIXME: Add support for CK_UserDefinedConversion,
+ // CK_ConstructorConversion, CK_UncheckedDerivedToBase.
+ break;
}
- // FIXME: Add support for CK_NoOp, CK_UserDefinedConversion,
- // CK_ConstructorConversion, CK_UncheckedDerivedToBase.
}
void VisitUnaryOperator(const UnaryOperator *S) {
@@ -181,15 +204,115 @@ class TransferVisitor : public ConstStmtVisitor<TransferVisitor> {
Env.setValue(FieldLoc, *InitExprVal);
}
+ void VisitCXXConstructExpr(const CXXConstructExpr *S) {
+ const CXXConstructorDecl *ConstructorDecl = S->getConstructor();
+ assert(ConstructorDecl != nullptr);
+
+ if (ConstructorDecl->isCopyOrMoveConstructor()) {
+ assert(S->getNumArgs() == 1);
+
+ const Expr *Arg = S->getArg(0);
+ assert(Arg != nullptr);
+
+ if (S->isElidable()) {
+ auto *ArgLoc = Env.getStorageLocation(*Arg, SkipPast::Reference);
+ if (ArgLoc == nullptr)
+ return;
+
+ Env.setStorageLocation(*S, *ArgLoc);
+ } else if (auto *ArgVal = Env.getValue(*Arg, SkipPast::Reference)) {
+ auto &Loc = Env.createStorageLocation(*S);
+ Env.setStorageLocation(*S, Loc);
+ Env.setValue(Loc, *ArgVal);
+ }
+ return;
+ }
+
+ auto &Loc = Env.createStorageLocation(*S);
+ Env.setStorageLocation(*S, Loc);
+ Env.initValueInStorageLocation(Loc, S->getType());
+ }
+
+ void VisitCXXOperatorCallExpr(const CXXOperatorCallExpr *S) {
+ if (S->getOperator() == OO_Equal) {
+ assert(S->getNumArgs() == 2);
+
+ const Expr *Arg0 = S->getArg(0);
+ assert(Arg0 != nullptr);
+
+ const Expr *Arg1 = S->getArg(1);
+ assert(Arg1 != nullptr);
+
+ // Evaluate only copy and move assignment operators.
+ auto *Arg0Type = Arg0->getType()->getUnqualifiedDesugaredType();
+ auto *Arg1Type = Arg1->getType()->getUnqualifiedDesugaredType();
+ if (Arg0Type != Arg1Type)
+ return;
+
+ auto *ObjectLoc = Env.getStorageLocation(*Arg0, SkipPast::Reference);
+ if (ObjectLoc == nullptr)
+ return;
+
+ auto *Val = Env.getValue(*Arg1, SkipPast::Reference);
+ if (Val == nullptr)
+ return;
+
+ Env.setValue(*ObjectLoc, *Val);
+ }
+ }
+
+ void VisitCXXFunctionalCastExpr(const CXXFunctionalCastExpr *S) {
+ if (S->getCastKind() == CK_ConstructorConversion) {
+ // The CFG does not contain `ParenExpr` as top-level statements in basic
+ // blocks, however sub-expressions can still be of that type.
+ assert(S->getSubExpr() != nullptr);
+ const Expr *SubExpr = S->getSubExpr();
+ assert(SubExpr != nullptr);
+
+ auto *SubExprLoc = Env.getStorageLocation(*SubExpr, SkipPast::None);
+ if (SubExprLoc == nullptr)
+ return;
+
+ Env.setStorageLocation(*S, *SubExprLoc);
+ }
+ }
+
+ void VisitCXXTemporaryObjectExpr(const CXXTemporaryObjectExpr *S) {
+ auto &Loc = Env.createStorageLocation(*S);
+ Env.setStorageLocation(*S, Loc);
+ Env.initValueInStorageLocation(Loc, S->getType());
+ }
+
+ void VisitCallExpr(const CallExpr *S) {
+ if (S->isCallToStdMove()) {
+ assert(S->getNumArgs() == 1);
+
+ const Expr *Arg = S->getArg(0);
+ assert(Arg != nullptr);
+
+ auto *ArgLoc = Env.getStorageLocation(*Arg, SkipPast::None);
+ if (ArgLoc == nullptr)
+ return;
+
+ Env.setStorageLocation(*S, *ArgLoc);
+ }
+ }
+
+ void VisitMaterializeTemporaryExpr(const MaterializeTemporaryExpr *S) {
+ const Expr *SubExpr = S->getSubExpr();
+ assert(SubExpr != nullptr);
+
+ auto *SubExprLoc = Env.getStorageLocation(*SubExpr, SkipPast::None);
+ if (SubExprLoc == nullptr)
+ return;
+
+ Env.setStorageLocation(*S, *SubExprLoc);
+ }
+
// FIXME: Add support for:
- // - CallExpr
// - CXXBindTemporaryExpr
// - CXXBoolLiteralExpr
- // - CXXConstructExpr
- // - CXXFunctionalCastExpr
- // - CXXOperatorCallExpr
// - CXXStaticCastExpr
- // - MaterializeTemporaryExpr
private:
void visitVarDecl(const VarDecl &D) {
@@ -203,6 +326,11 @@ class TransferVisitor : public ConstStmtVisitor<TransferVisitor> {
return;
}
+ // The CFG does not contain `ParenExpr` as top-level statements in basic
+ // blocks, however sub-expressions can still be of that type.
+ InitExpr = skipExprWithCleanups(D.getInit()->IgnoreParens());
+ assert(InitExpr != nullptr);
+
if (D.getType()->isReferenceType()) {
// Initializing a reference variable - do not create a reference to
// reference.
@@ -222,11 +350,13 @@ class TransferVisitor : public ConstStmtVisitor<TransferVisitor> {
if (auto *InitExprVal = Env.getValue(*InitExpr, SkipPast::None)) {
Env.setValue(Loc, *InitExprVal);
- } else {
+ } else if (!D.getType()->isStructureOrClassType()) {
// FIXME: The initializer expression must always be assigned a value.
// Replace this with an assert when we have sufficient coverage of
// language features.
Env.initValueInStorageLocation(Loc, D.getType());
+ } else {
+ llvm_unreachable("structs and classes must always be assigned values");
}
}
diff --git a/clang/unittests/Analysis/FlowSensitive/TransferTest.cpp b/clang/unittests/Analysis/FlowSensitive/TransferTest.cpp
index 1c5e49df8da9a..ac97cce8a9567 100644
--- a/clang/unittests/Analysis/FlowSensitive/TransferTest.cpp
+++ b/clang/unittests/Analysis/FlowSensitive/TransferTest.cpp
@@ -15,6 +15,7 @@
#include "clang/Analysis/FlowSensitive/DataflowEnvironment.h"
#include "clang/Analysis/FlowSensitive/StorageLocation.h"
#include "clang/Analysis/FlowSensitive/Value.h"
+#include "clang/Basic/LangStandard.h"
#include "llvm/ADT/ArrayRef.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/Support/Casting.h"
@@ -37,7 +38,8 @@ using ::testing::Pair;
class TransferTest : public ::testing::Test {
protected:
template <typename Matcher>
- void runDataflow(llvm::StringRef Code, Matcher Match) {
+ void runDataflow(llvm::StringRef Code, Matcher Match,
+ LangStandard::Kind Std = LangStandard::lang_cxx17) {
test::checkDataflow<NoopAnalysis>(
Code, "target",
[](ASTContext &C, Environment &) { return NoopAnalysis(C); },
@@ -45,7 +47,9 @@ class TransferTest : public ::testing::Test {
std::pair<std::string, DataflowAnalysisState<NoopLattice>>>
Results,
ASTContext &ASTCtx) { Match(Results, ASTCtx); },
- {"-fsyntax-only", "-std=c++17"});
+ {"-fsyntax-only",
+ "-std=" +
+ std::string(LangStandard::getLangStandardForKind(Std).getName())});
}
};
@@ -1281,4 +1285,308 @@ TEST_F(TransferTest, DefaultInitializerReference) {
});
}
+TEST_F(TransferTest, TemporaryObject) {
+ std::string Code = R"(
+ struct A {
+ int Bar;
+ };
+
+ void target() {
+ A Foo = A();
+ // [[p]]
+ }
+ )";
+ runDataflow(
+ Code, [](llvm::ArrayRef<
+ std::pair<std::string, DataflowAnalysisState<NoopLattice>>>
+ Results,
+ ASTContext &ASTCtx) {
+ ASSERT_THAT(Results, ElementsAre(Pair("p", _)));
+ const Environment &Env = Results[0].second.Env;
+
+ const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo");
+ ASSERT_THAT(FooDecl, NotNull());
+
+ const ValueDecl *BarDecl = findValueDecl(ASTCtx, "Bar");
+ ASSERT_THAT(BarDecl, NotNull());
+
+ const auto *FooLoc = cast<AggregateStorageLocation>(
+ Env.getStorageLocation(*FooDecl, SkipPast::None));
+ const auto *BarLoc =
+ cast<ScalarStorageLocation>(&FooLoc->getChild(*BarDecl));
+
+ const auto *FooVal = cast<StructValue>(Env.getValue(*FooLoc));
+ const auto *BarVal = cast<IntegerValue>(&FooVal->getChild(*BarDecl));
+ EXPECT_EQ(Env.getValue(*BarLoc), BarVal);
+ });
+}
+
+TEST_F(TransferTest, ElidableConstructor) {
+ // This test is effectively the same as TransferTest.TemporaryObject, but
+ // the code is compiled as C++ 14.
+ std::string Code = R"(
+ struct A {
+ int Bar;
+ };
+
+ void target() {
+ A Foo = A();
+ // [[p]]
+ }
+ )";
+ runDataflow(
+ Code,
+ [](llvm::ArrayRef<
+ std::pair<std::string, DataflowAnalysisState<NoopLattice>>>
+ Results,
+ ASTContext &ASTCtx) {
+ ASSERT_THAT(Results, ElementsAre(Pair("p", _)));
+ const Environment &Env = Results[0].second.Env;
+
+ const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo");
+ ASSERT_THAT(FooDecl, NotNull());
+
+ const ValueDecl *BarDecl = findValueDecl(ASTCtx, "Bar");
+ ASSERT_THAT(BarDecl, NotNull());
+
+ const auto *FooLoc = cast<AggregateStorageLocation>(
+ Env.getStorageLocation(*FooDecl, SkipPast::None));
+ const auto *BarLoc =
+ cast<ScalarStorageLocation>(&FooLoc->getChild(*BarDecl));
+
+ const auto *FooVal = cast<StructValue>(Env.getValue(*FooLoc));
+ const auto *BarVal = cast<IntegerValue>(&FooVal->getChild(*BarDecl));
+ EXPECT_EQ(Env.getValue(*BarLoc), BarVal);
+ },
+ LangStandard::lang_cxx14);
+}
+
+TEST_F(TransferTest, AssignmentOperator) {
+ std::string Code = R"(
+ struct A {
+ int Baz;
+ };
+
+ void target() {
+ A Foo;
+ A Bar;
+ // [[p1]]
+ Foo = Bar;
+ // [[p2]]
+ }
+ )";
+ runDataflow(
+ Code, [](llvm::ArrayRef<
+ std::pair<std::string, DataflowAnalysisState<NoopLattice>>>
+ Results,
+ ASTContext &ASTCtx) {
+ ASSERT_THAT(Results, ElementsAre(Pair("p1", _), Pair("p2", _)));
+ const Environment &Env1 = Results[0].second.Env;
+ const Environment &Env2 = Results[1].second.Env;
+
+ const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo");
+ ASSERT_THAT(FooDecl, NotNull());
+
+ const ValueDecl *BarDecl = findValueDecl(ASTCtx, "Bar");
+ ASSERT_THAT(BarDecl, NotNull());
+
+ const ValueDecl *BazDecl = findValueDecl(ASTCtx, "Baz");
+ ASSERT_THAT(BazDecl, NotNull());
+
+ const auto *FooLoc1 = cast<AggregateStorageLocation>(
+ Env1.getStorageLocation(*FooDecl, SkipPast::None));
+ const auto *BarLoc1 = cast<AggregateStorageLocation>(
+ Env1.getStorageLocation(*BarDecl, SkipPast::None));
+
+ const auto *FooVal1 = cast<StructValue>(Env1.getValue(*FooLoc1));
+ const auto *BarVal1 = cast<StructValue>(Env1.getValue(*BarLoc1));
+ EXPECT_NE(FooVal1, BarVal1);
+
+ const auto *FooBazVal1 =
+ cast<IntegerValue>(Env1.getValue(FooLoc1->getChild(*BazDecl)));
+ const auto *BarBazVal1 =
+ cast<IntegerValue>(Env1.getValue(BarLoc1->getChild(*BazDecl)));
+ EXPECT_NE(FooBazVal1, BarBazVal1);
+
+ const auto *FooLoc2 = cast<AggregateStorageLocation>(
+ Env2.getStorageLocation(*FooDecl, SkipPast::None));
+ const auto *BarLoc2 = cast<AggregateStorageLocation>(
+ Env2.getStorageLocation(*BarDecl, SkipPast::None));
+
+ const auto *FooVal2 = cast<StructValue>(Env2.getValue(*FooLoc2));
+ const auto *BarVal2 = cast<StructValue>(Env2.getValue(*BarLoc2));
+ EXPECT_EQ(FooVal2, BarVal2);
+
+ const auto *FooBazVal2 =
+ cast<IntegerValue>(Env2.getValue(FooLoc1->getChild(*BazDecl)));
+ const auto *BarBazVal2 =
+ cast<IntegerValue>(Env2.getValue(BarLoc1->getChild(*BazDecl)));
+ EXPECT_EQ(FooBazVal2, BarBazVal2);
+ });
+}
+
+TEST_F(TransferTest, CopyConstructor) {
+ std::string Code = R"(
+ struct A {
+ int Baz;
+ };
+
+ void target() {
+ A Foo;
+ A Bar = Foo;
+ // [[p]]
+ }
+ )";
+ runDataflow(
+ Code, [](llvm::ArrayRef<
+ std::pair<std::string, DataflowAnalysisState<NoopLattice>>>
+ Results,
+ ASTContext &ASTCtx) {
+ ASSERT_THAT(Results, ElementsAre(Pair("p", _)));
+ const Environment &Env = Results[0].second.Env;
+
+ const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo");
+ ASSERT_THAT(FooDecl, NotNull());
+
+ const ValueDecl *BarDecl = findValueDecl(ASTCtx, "Bar");
+ ASSERT_THAT(BarDecl, NotNull());
+
+ const ValueDecl *BazDecl = findValueDecl(ASTCtx, "Baz");
+ ASSERT_THAT(BazDecl, NotNull());
+
+ const auto *FooLoc = cast<AggregateStorageLocation>(
+ Env.getStorageLocation(*FooDecl, SkipPast::None));
+ const auto *BarLoc = cast<AggregateStorageLocation>(
+ Env.getStorageLocation(*BarDecl, SkipPast::None));
+
+ const auto *FooVal = cast<StructValue>(Env.getValue(*FooLoc));
+ const auto *BarVal = cast<StructValue>(Env.getValue(*BarLoc));
+ EXPECT_EQ(FooVal, BarVal);
+
+ const auto *FooBazVal =
+ cast<IntegerValue>(Env.getValue(FooLoc->getChild(*BazDecl)));
+ const auto *BarBazVal =
+ cast<IntegerValue>(Env.getValue(BarLoc->getChild(*BazDecl)));
+ EXPECT_EQ(FooBazVal, BarBazVal);
+ });
+}
+
+TEST_F(TransferTest, CopyConstructorWithParens) {
+ std::string Code = R"(
+ struct A {
+ int Baz;
+ };
+
+ void target() {
+ A Foo;
+ A Bar((A(Foo)));
+ // [[p]]
+ }
+ )";
+ runDataflow(
+ Code, [](llvm::ArrayRef<
+ std::pair<std::string, DataflowAnalysisState<NoopLattice>>>
+ Results,
+ ASTContext &ASTCtx) {
+ ASSERT_THAT(Results, ElementsAre(Pair("p", _)));
+ const Environment &Env = Results[0].second.Env;
+
+ const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo");
+ ASSERT_THAT(FooDecl, NotNull());
+
+ const ValueDecl *BarDecl = findValueDecl(ASTCtx, "Bar");
+ ASSERT_THAT(BarDecl, NotNull());
+
+ const ValueDecl *BazDecl = findValueDecl(ASTCtx, "Baz");
+ ASSERT_THAT(BazDecl, NotNull());
+
+ const auto *FooLoc = cast<AggregateStorageLocation>(
+ Env.getStorageLocation(*FooDecl, SkipPast::None));
+ const auto *BarLoc = cast<AggregateStorageLocation>(
+ Env.getStorageLocation(*BarDecl, SkipPast::None));
+
+ const auto *FooVal = cast<StructValue>(Env.getValue(*FooLoc));
+ const auto *BarVal = cast<StructValue>(Env.getValue(*BarLoc));
+ EXPECT_EQ(FooVal, BarVal);
+
+ const auto *FooBazVal =
+ cast<IntegerValue>(Env.getValue(FooLoc->getChild(*BazDecl)));
+ const auto *BarBazVal =
+ cast<IntegerValue>(Env.getValue(BarLoc->getChild(*BazDecl)));
+ EXPECT_EQ(FooBazVal, BarBazVal);
+ });
+}
+
+TEST_F(TransferTest, MoveConstructor) {
+ std::string Code = R"(
+ namespace std {
+
+ template <typename T> struct remove_reference { using type = T; };
+ template <typename T> struct remove_reference<T&> { using type = T; };
+ template <typename T> struct remove_reference<T&&> { using type = T; };
+
+ template <typename T>
+ using remove_reference_t = typename remove_reference<T>::type;
+
+ template <typename T>
+ std::remove_reference_t<T>&& move(T&& x);
+
+ } // namespace std
+
+ struct A {
+ int Baz;
+ };
+
+ void target() {
+ A Foo;
+ A Bar;
+ // [[p1]]
+ Foo = std::move(Bar);
+ // [[p2]]
+ }
+ )";
+ runDataflow(
+ Code, [](llvm::ArrayRef<
+ std::pair<std::string, DataflowAnalysisState<NoopLattice>>>
+ Results,
+ ASTContext &ASTCtx) {
+ ASSERT_THAT(Results, ElementsAre(Pair("p1", _), Pair("p2", _)));
+ const Environment &Env1 = Results[0].second.Env;
+ const Environment &Env2 = Results[1].second.Env;
+
+ const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo");
+ ASSERT_THAT(FooDecl, NotNull());
+
+ const ValueDecl *BarDecl = findValueDecl(ASTCtx, "Bar");
+ ASSERT_THAT(BarDecl, NotNull());
+
+ const ValueDecl *BazDecl = findValueDecl(ASTCtx, "Baz");
+ ASSERT_THAT(BazDecl, NotNull());
+
+ const auto *FooLoc1 = cast<AggregateStorageLocation>(
+ Env1.getStorageLocation(*FooDecl, SkipPast::None));
+ const auto *BarLoc1 = cast<AggregateStorageLocation>(
+ Env1.getStorageLocation(*BarDecl, SkipPast::None));
+
+ const auto *FooVal1 = cast<StructValue>(Env1.getValue(*FooLoc1));
+ const auto *BarVal1 = cast<StructValue>(Env1.getValue(*BarLoc1));
+ EXPECT_NE(FooVal1, BarVal1);
+
+ const auto *FooBazVal1 =
+ cast<IntegerValue>(Env1.getValue(FooLoc1->getChild(*BazDecl)));
+ const auto *BarBazVal1 =
+ cast<IntegerValue>(Env1.getValue(BarLoc1->getChild(*BazDecl)));
+ EXPECT_NE(FooBazVal1, BarBazVal1);
+
+ const auto *FooLoc2 = cast<AggregateStorageLocation>(
+ Env2.getStorageLocation(*FooDecl, SkipPast::None));
+ const auto *FooVal2 = cast<StructValue>(Env2.getValue(*FooLoc2));
+ EXPECT_EQ(FooVal2, BarVal1);
+
+ const auto *FooBazVal2 =
+ cast<IntegerValue>(Env2.getValue(FooLoc1->getChild(*BazDecl)));
+ EXPECT_EQ(FooBazVal2, BarBazVal1);
+ });
+}
+
} // namespace
More information about the cfe-commits
mailing list