
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: xazax.hun Differential Revision: https://reviews.llvm.org/D116368
541 lines
18 KiB
C++
541 lines
18 KiB
C++
//===- unittests/Analysis/FlowSensitive/TransferTest.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 "NoopAnalysis.h"
|
|
#include "TestingSupport.h"
|
|
#include "clang/AST/ASTContext.h"
|
|
#include "clang/AST/Decl.h"
|
|
#include "clang/ASTMatchers/ASTMatchFinder.h"
|
|
#include "clang/ASTMatchers/ASTMatchers.h"
|
|
#include "clang/Analysis/FlowSensitive/DataflowEnvironment.h"
|
|
#include "clang/Analysis/FlowSensitive/StorageLocation.h"
|
|
#include "clang/Analysis/FlowSensitive/Value.h"
|
|
#include "llvm/ADT/ArrayRef.h"
|
|
#include "llvm/ADT/StringRef.h"
|
|
#include "llvm/Support/Casting.h"
|
|
#include "gmock/gmock.h"
|
|
#include "gtest/gtest.h"
|
|
#include <cassert>
|
|
#include <string>
|
|
#include <utility>
|
|
|
|
namespace {
|
|
|
|
using namespace clang;
|
|
using namespace dataflow;
|
|
using ::testing::_;
|
|
using ::testing::ElementsAre;
|
|
using ::testing::IsNull;
|
|
using ::testing::NotNull;
|
|
using ::testing::Pair;
|
|
|
|
class TransferTest : public ::testing::Test {
|
|
protected:
|
|
template <typename Matcher>
|
|
void runDataflow(llvm::StringRef Code, Matcher Match) {
|
|
test::checkDataflow<NoopAnalysis>(
|
|
Code, "target",
|
|
[](ASTContext &C, Environment &) { return NoopAnalysis(C); },
|
|
[&Match](llvm::ArrayRef<
|
|
std::pair<std::string, DataflowAnalysisState<NoopLattice>>>
|
|
Results,
|
|
ASTContext &ASTCtx) { Match(Results, ASTCtx); },
|
|
{"-fsyntax-only", "-std=c++17"});
|
|
}
|
|
};
|
|
|
|
/// Returns the `ValueDecl` for the given identifier.
|
|
///
|
|
/// Requirements:
|
|
///
|
|
/// `Name` must be unique in `ASTCtx`.
|
|
static const ValueDecl *findValueDecl(ASTContext &ASTCtx,
|
|
llvm::StringRef Name) {
|
|
auto TargetNodes = ast_matchers::match(
|
|
ast_matchers::valueDecl(ast_matchers::hasName(Name)).bind("v"), ASTCtx);
|
|
assert(TargetNodes.size() == 1 && "Name must be unique");
|
|
auto *const Result = ast_matchers::selectFirst<ValueDecl>("v", TargetNodes);
|
|
assert(Result != nullptr);
|
|
return Result;
|
|
}
|
|
|
|
TEST_F(TransferTest, IntVarDecl) {
|
|
std::string Code = R"(
|
|
void target() {
|
|
int 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 StorageLocation *FooLoc = Env.getStorageLocation(*FooDecl);
|
|
ASSERT_TRUE(isa_and_nonnull<ScalarStorageLocation>(FooLoc));
|
|
|
|
const Value *FooVal = Env.getValue(*FooLoc);
|
|
ASSERT_TRUE(isa_and_nonnull<IntegerValue>(FooVal));
|
|
});
|
|
}
|
|
|
|
TEST_F(TransferTest, StructVarDecl) {
|
|
std::string Code = R"(
|
|
struct Foo {
|
|
int Bar;
|
|
};
|
|
|
|
void target() {
|
|
Foo 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());
|
|
|
|
ASSERT_TRUE(FooDecl->getType()->isStructureType());
|
|
auto FooFields = FooDecl->getType()->getAsRecordDecl()->fields();
|
|
|
|
FieldDecl *BarDecl = nullptr;
|
|
for (FieldDecl *Field : FooFields) {
|
|
if (Field->getNameAsString() == "Bar") {
|
|
BarDecl = Field;
|
|
} else {
|
|
FAIL() << "Unexpected field: " << Field->getNameAsString();
|
|
}
|
|
}
|
|
ASSERT_THAT(BarDecl, NotNull());
|
|
|
|
const auto *FooLoc =
|
|
cast<AggregateStorageLocation>(Env.getStorageLocation(*FooDecl));
|
|
const auto *BarLoc =
|
|
cast<ScalarStorageLocation>(&FooLoc->getChild(*BarDecl));
|
|
|
|
const auto *FooVal = cast<StructValue>(Env.getValue(*FooLoc));
|
|
const auto *BarVal = cast<IntegerValue>(&FooVal->getChild(*BarDecl));
|
|
ASSERT_EQ(Env.getValue(*BarLoc), BarVal);
|
|
});
|
|
}
|
|
|
|
TEST_F(TransferTest, ClassVarDecl) {
|
|
std::string Code = R"(
|
|
class Foo {
|
|
int Bar;
|
|
};
|
|
|
|
void target() {
|
|
Foo 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());
|
|
|
|
ASSERT_TRUE(FooDecl->getType()->isClassType());
|
|
auto FooFields = FooDecl->getType()->getAsRecordDecl()->fields();
|
|
|
|
FieldDecl *BarDecl = nullptr;
|
|
for (FieldDecl *Field : FooFields) {
|
|
if (Field->getNameAsString() == "Bar") {
|
|
BarDecl = Field;
|
|
} else {
|
|
FAIL() << "Unexpected field: " << Field->getNameAsString();
|
|
}
|
|
}
|
|
ASSERT_THAT(BarDecl, NotNull());
|
|
|
|
const auto *FooLoc =
|
|
cast<AggregateStorageLocation>(Env.getStorageLocation(*FooDecl));
|
|
const auto *BarLoc =
|
|
cast<ScalarStorageLocation>(&FooLoc->getChild(*BarDecl));
|
|
|
|
const auto *FooVal = cast<StructValue>(Env.getValue(*FooLoc));
|
|
const auto *BarVal = cast<IntegerValue>(&FooVal->getChild(*BarDecl));
|
|
ASSERT_EQ(Env.getValue(*BarLoc), BarVal);
|
|
});
|
|
}
|
|
|
|
TEST_F(TransferTest, ReferenceVarDecl) {
|
|
std::string Code = R"(
|
|
struct Foo {};
|
|
|
|
Foo& getFoo();
|
|
|
|
void target() {
|
|
Foo& foo = getFoo();
|
|
// [[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 StorageLocation *FooLoc = Env.getStorageLocation(*FooDecl);
|
|
ASSERT_TRUE(isa_and_nonnull<ScalarStorageLocation>(FooLoc));
|
|
|
|
const ReferenceValue *FooVal =
|
|
cast<ReferenceValue>(Env.getValue(*FooLoc));
|
|
const StorageLocation &FooPointeeLoc = FooVal->getPointeeLoc();
|
|
ASSERT_TRUE(isa<AggregateStorageLocation>(&FooPointeeLoc));
|
|
|
|
const Value *FooPointeeVal = Env.getValue(FooPointeeLoc);
|
|
ASSERT_TRUE(isa_and_nonnull<StructValue>(FooPointeeVal));
|
|
});
|
|
}
|
|
|
|
TEST_F(TransferTest, SelfReferentialReferenceVarDecl) {
|
|
std::string Code = R"(
|
|
struct Foo;
|
|
|
|
struct Baz {};
|
|
|
|
struct Bar {
|
|
Foo& FooRef;
|
|
Foo* FooPtr;
|
|
Baz& BazRef;
|
|
Baz* BazPtr;
|
|
};
|
|
|
|
struct Foo {
|
|
Bar& Bar;
|
|
};
|
|
|
|
Foo& getFoo();
|
|
|
|
void target() {
|
|
Foo& foo = getFoo();
|
|
// [[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());
|
|
|
|
ASSERT_TRUE(FooDecl->getType()->isReferenceType());
|
|
ASSERT_TRUE(FooDecl->getType().getNonReferenceType()->isStructureType());
|
|
const auto FooFields =
|
|
FooDecl->getType().getNonReferenceType()->getAsRecordDecl()->fields();
|
|
|
|
FieldDecl *BarDecl = nullptr;
|
|
for (FieldDecl *Field : FooFields) {
|
|
if (Field->getNameAsString() == "Bar") {
|
|
BarDecl = Field;
|
|
} else {
|
|
FAIL() << "Unexpected field: " << Field->getNameAsString();
|
|
}
|
|
}
|
|
ASSERT_THAT(BarDecl, NotNull());
|
|
|
|
ASSERT_TRUE(BarDecl->getType()->isReferenceType());
|
|
ASSERT_TRUE(BarDecl->getType().getNonReferenceType()->isStructureType());
|
|
const auto BarFields =
|
|
BarDecl->getType().getNonReferenceType()->getAsRecordDecl()->fields();
|
|
|
|
FieldDecl *FooRefDecl = nullptr;
|
|
FieldDecl *FooPtrDecl = nullptr;
|
|
FieldDecl *BazRefDecl = nullptr;
|
|
FieldDecl *BazPtrDecl = nullptr;
|
|
for (FieldDecl *Field : BarFields) {
|
|
if (Field->getNameAsString() == "FooRef") {
|
|
FooRefDecl = Field;
|
|
} else if (Field->getNameAsString() == "FooPtr") {
|
|
FooPtrDecl = Field;
|
|
} else if (Field->getNameAsString() == "BazRef") {
|
|
BazRefDecl = Field;
|
|
} else if (Field->getNameAsString() == "BazPtr") {
|
|
BazPtrDecl = Field;
|
|
} else {
|
|
FAIL() << "Unexpected field: " << Field->getNameAsString();
|
|
}
|
|
}
|
|
ASSERT_THAT(FooRefDecl, NotNull());
|
|
ASSERT_THAT(FooPtrDecl, NotNull());
|
|
ASSERT_THAT(BazRefDecl, NotNull());
|
|
ASSERT_THAT(BazPtrDecl, NotNull());
|
|
|
|
const auto *FooLoc =
|
|
cast<ScalarStorageLocation>(Env.getStorageLocation(*FooDecl));
|
|
const auto *FooVal = cast<ReferenceValue>(Env.getValue(*FooLoc));
|
|
const auto *FooPointeeVal =
|
|
cast<StructValue>(Env.getValue(FooVal->getPointeeLoc()));
|
|
|
|
const auto *BarVal =
|
|
cast<ReferenceValue>(&FooPointeeVal->getChild(*BarDecl));
|
|
const auto *BarPointeeVal =
|
|
cast<StructValue>(Env.getValue(BarVal->getPointeeLoc()));
|
|
|
|
const auto *FooRefVal =
|
|
cast<ReferenceValue>(&BarPointeeVal->getChild(*FooRefDecl));
|
|
const StorageLocation &FooRefPointeeLoc = FooRefVal->getPointeeLoc();
|
|
ASSERT_THAT(Env.getValue(FooRefPointeeLoc), IsNull());
|
|
|
|
const auto *FooPtrVal =
|
|
cast<PointerValue>(&BarPointeeVal->getChild(*FooPtrDecl));
|
|
const StorageLocation &FooPtrPointeeLoc = FooPtrVal->getPointeeLoc();
|
|
ASSERT_THAT(Env.getValue(FooPtrPointeeLoc), IsNull());
|
|
|
|
const auto *BazRefVal =
|
|
cast<ReferenceValue>(&BarPointeeVal->getChild(*BazRefDecl));
|
|
const StorageLocation &BazRefPointeeLoc = BazRefVal->getPointeeLoc();
|
|
ASSERT_THAT(Env.getValue(BazRefPointeeLoc), NotNull());
|
|
|
|
const auto *BazPtrVal =
|
|
cast<PointerValue>(&BarPointeeVal->getChild(*BazPtrDecl));
|
|
const StorageLocation &BazPtrPointeeLoc = BazPtrVal->getPointeeLoc();
|
|
ASSERT_THAT(Env.getValue(BazPtrPointeeLoc), NotNull());
|
|
});
|
|
}
|
|
|
|
TEST_F(TransferTest, PointerVarDecl) {
|
|
std::string Code = R"(
|
|
struct Foo {};
|
|
|
|
Foo* getFoo();
|
|
|
|
void target() {
|
|
Foo* foo = getFoo();
|
|
// [[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 StorageLocation *FooLoc = Env.getStorageLocation(*FooDecl);
|
|
ASSERT_TRUE(isa_and_nonnull<ScalarStorageLocation>(FooLoc));
|
|
|
|
const PointerValue *FooVal = cast<PointerValue>(Env.getValue(*FooLoc));
|
|
const StorageLocation &FooPointeeLoc = FooVal->getPointeeLoc();
|
|
ASSERT_TRUE(isa<AggregateStorageLocation>(&FooPointeeLoc));
|
|
|
|
const Value *FooPointeeVal = Env.getValue(FooPointeeLoc);
|
|
ASSERT_TRUE(isa_and_nonnull<StructValue>(FooPointeeVal));
|
|
});
|
|
}
|
|
|
|
TEST_F(TransferTest, SelfReferentialPointerVarDecl) {
|
|
std::string Code = R"(
|
|
struct Foo;
|
|
|
|
struct Baz {};
|
|
|
|
struct Bar {
|
|
Foo& FooRef;
|
|
Foo* FooPtr;
|
|
Baz& BazRef;
|
|
Baz* BazPtr;
|
|
};
|
|
|
|
struct Foo {
|
|
Bar* Bar;
|
|
};
|
|
|
|
Foo* getFoo();
|
|
|
|
void target() {
|
|
Foo* foo = getFoo();
|
|
// [[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());
|
|
|
|
ASSERT_TRUE(FooDecl->getType()->isPointerType());
|
|
ASSERT_TRUE(FooDecl->getType()
|
|
->getAs<PointerType>()
|
|
->getPointeeType()
|
|
->isStructureType());
|
|
const auto FooFields = FooDecl->getType()
|
|
->getAs<PointerType>()
|
|
->getPointeeType()
|
|
->getAsRecordDecl()
|
|
->fields();
|
|
|
|
FieldDecl *BarDecl = nullptr;
|
|
for (FieldDecl *Field : FooFields) {
|
|
if (Field->getNameAsString() == "Bar") {
|
|
BarDecl = Field;
|
|
} else {
|
|
FAIL() << "Unexpected field: " << Field->getNameAsString();
|
|
}
|
|
}
|
|
ASSERT_THAT(BarDecl, NotNull());
|
|
|
|
ASSERT_TRUE(BarDecl->getType()->isPointerType());
|
|
ASSERT_TRUE(BarDecl->getType()
|
|
->getAs<PointerType>()
|
|
->getPointeeType()
|
|
->isStructureType());
|
|
const auto BarFields = BarDecl->getType()
|
|
->getAs<PointerType>()
|
|
->getPointeeType()
|
|
->getAsRecordDecl()
|
|
->fields();
|
|
|
|
FieldDecl *FooRefDecl = nullptr;
|
|
FieldDecl *FooPtrDecl = nullptr;
|
|
FieldDecl *BazRefDecl = nullptr;
|
|
FieldDecl *BazPtrDecl = nullptr;
|
|
for (FieldDecl *Field : BarFields) {
|
|
if (Field->getNameAsString() == "FooRef") {
|
|
FooRefDecl = Field;
|
|
} else if (Field->getNameAsString() == "FooPtr") {
|
|
FooPtrDecl = Field;
|
|
} else if (Field->getNameAsString() == "BazRef") {
|
|
BazRefDecl = Field;
|
|
} else if (Field->getNameAsString() == "BazPtr") {
|
|
BazPtrDecl = Field;
|
|
} else {
|
|
FAIL() << "Unexpected field: " << Field->getNameAsString();
|
|
}
|
|
}
|
|
ASSERT_THAT(FooRefDecl, NotNull());
|
|
ASSERT_THAT(FooPtrDecl, NotNull());
|
|
ASSERT_THAT(BazRefDecl, NotNull());
|
|
ASSERT_THAT(BazPtrDecl, NotNull());
|
|
|
|
const auto *FooLoc =
|
|
cast<ScalarStorageLocation>(Env.getStorageLocation(*FooDecl));
|
|
const auto *FooVal = cast<PointerValue>(Env.getValue(*FooLoc));
|
|
const auto *FooPointeeVal =
|
|
cast<StructValue>(Env.getValue(FooVal->getPointeeLoc()));
|
|
|
|
const auto *BarVal =
|
|
cast<PointerValue>(&FooPointeeVal->getChild(*BarDecl));
|
|
const auto *BarPointeeVal =
|
|
cast<StructValue>(Env.getValue(BarVal->getPointeeLoc()));
|
|
|
|
const auto *FooRefVal =
|
|
cast<ReferenceValue>(&BarPointeeVal->getChild(*FooRefDecl));
|
|
const StorageLocation &FooRefPointeeLoc = FooRefVal->getPointeeLoc();
|
|
ASSERT_THAT(Env.getValue(FooRefPointeeLoc), IsNull());
|
|
|
|
const auto *FooPtrVal =
|
|
cast<PointerValue>(&BarPointeeVal->getChild(*FooPtrDecl));
|
|
const StorageLocation &FooPtrPointeeLoc = FooPtrVal->getPointeeLoc();
|
|
ASSERT_THAT(Env.getValue(FooPtrPointeeLoc), IsNull());
|
|
|
|
const auto *BazRefVal =
|
|
cast<ReferenceValue>(&BarPointeeVal->getChild(*BazRefDecl));
|
|
const StorageLocation &BazRefPointeeLoc = BazRefVal->getPointeeLoc();
|
|
ASSERT_THAT(Env.getValue(BazRefPointeeLoc), NotNull());
|
|
|
|
const auto *BazPtrVal =
|
|
cast<PointerValue>(&BarPointeeVal->getChild(*BazPtrDecl));
|
|
const StorageLocation &BazPtrPointeeLoc = BazPtrVal->getPointeeLoc();
|
|
ASSERT_THAT(Env.getValue(BazPtrPointeeLoc), NotNull());
|
|
});
|
|
}
|
|
|
|
TEST_F(TransferTest, JoinVarDecl) {
|
|
std::string Code = R"(
|
|
void target(bool b) {
|
|
int foo;
|
|
// [[p1]]
|
|
if (b) {
|
|
int bar;
|
|
// [[p2]]
|
|
} else {
|
|
int baz;
|
|
// [[p3]]
|
|
}
|
|
(void)0;
|
|
// [[p4]]
|
|
}
|
|
)";
|
|
runDataflow(
|
|
Code, [](llvm::ArrayRef<
|
|
std::pair<std::string, DataflowAnalysisState<NoopLattice>>>
|
|
Results,
|
|
ASTContext &ASTCtx) {
|
|
ASSERT_THAT(Results, ElementsAre(Pair("p4", _), Pair("p3", _),
|
|
Pair("p2", _), Pair("p1", _)));
|
|
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 Environment &Env1 = Results[3].second.Env;
|
|
const StorageLocation *FooLoc = Env1.getStorageLocation(*FooDecl);
|
|
ASSERT_THAT(FooLoc, NotNull());
|
|
ASSERT_THAT(Env1.getStorageLocation(*BarDecl), IsNull());
|
|
ASSERT_THAT(Env1.getStorageLocation(*BazDecl), IsNull());
|
|
|
|
const Environment &Env2 = Results[2].second.Env;
|
|
ASSERT_EQ(Env2.getStorageLocation(*FooDecl), FooLoc);
|
|
ASSERT_THAT(Env2.getStorageLocation(*BarDecl), NotNull());
|
|
ASSERT_THAT(Env2.getStorageLocation(*BazDecl), IsNull());
|
|
|
|
const Environment &Env3 = Results[1].second.Env;
|
|
ASSERT_EQ(Env3.getStorageLocation(*FooDecl), FooLoc);
|
|
ASSERT_THAT(Env3.getStorageLocation(*BarDecl), IsNull());
|
|
ASSERT_THAT(Env3.getStorageLocation(*BazDecl), NotNull());
|
|
|
|
const Environment &Env4 = Results[0].second.Env;
|
|
ASSERT_EQ(Env4.getStorageLocation(*FooDecl), FooLoc);
|
|
ASSERT_THAT(Env4.getStorageLocation(*BarDecl), IsNull());
|
|
ASSERT_THAT(Env4.getStorageLocation(*BazDecl), IsNull());
|
|
});
|
|
}
|
|
|
|
} // namespace
|