Files
TheRock/compiler/amd-llvm/clang/unittests/ScalableStaticAnalysisFramework/Analyses/PointerFlow/PointerFlowTest.cpp
T
derek.holloway 7502bf64a4
CodeQL / Analyze (actions) (push) Canceled after 0s
CodeQL / Analyze (python) (push) Canceled after 0s
pre-commit / pre-commit (push) Canceled after 0s
Multi-Arch CI / setup (push) Canceled after 0s
Publish build_manylinux_x86_64 images / publish_build_manylinux_x86_64 (push) Canceled after 0s
Setup (Multi-Arch) / setup (push) Canceled after 0s
Multi-Arch CI / Linux::${{ fromJSON(needs.setup.outputs.linux_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubi10 image / publish_no_rocm_image_ubi10 (push) Canceled after 0s
Multi-Arch CI / Windows::${{ fromJSON(needs.setup.outputs.windows_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Multi-Arch CI / Manifest Diff (push) Canceled after 0s
TheRock Manifest Diff Report / Generate Manifest Diff Report (push) Canceled after 0s
Multi-Arch CI / CI Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_ocl_rt images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_rocgdb images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Unit Tests / Unit Tests :: ubuntu-24.04 (push) Canceled after 0s
Unit Tests / Unit Tests :: windows-2022 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_media (push) Canceled after 0s
Unit Tests / Unit Tests Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_openmpi (push) Canceled after 0s
Flatten subprojects into a single base monorepo
2026-09-02 16:34:47 -07:00

1130 lines
31 KiB
C++

//===- PointerFlowTest.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/ScalableStaticAnalysisFramework/Analyses/PointerFlow/PointerFlow.h"
#include "TestFixture.h"
#include "clang/AST/Decl.h"
#include "clang/AST/DeclCXX.h"
#include "clang/AST/DynamicRecursiveASTVisitor.h"
#include "clang/AST/ExprCXX.h"
#include "clang/Frontend/ASTUnit.h"
#include "clang/ScalableStaticAnalysisFramework/Core/ASTEntityMapping.h"
#include "clang/ScalableStaticAnalysisFramework/Core/Model/EntityId.h"
#include "clang/ScalableStaticAnalysisFramework/Core/Model/EntityName.h"
#include "clang/ScalableStaticAnalysisFramework/Core/TUSummary/ExtractorRegistry.h"
#include "clang/ScalableStaticAnalysisFramework/Core/TUSummary/TUSummary.h"
#include "clang/ScalableStaticAnalysisFramework/Core/TUSummary/TUSummaryBuilder.h"
#include "clang/Tooling/Tooling.h"
#include "llvm/Support/Casting.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include <memory>
#include <type_traits>
#include <variant>
using namespace clang;
using namespace ssaf;
namespace {
// Use FindEntityByName to identify entities in unit tests.
// Unit tests are simple enough to meet the following assumptions:
// - Named declarations should have unique names, they can be found by comparing
// names with strings;
// - Lambdas should initialize a variable named "X", they can be found using
// "LambdaOfVar("X")";
// - CXX Ctors should have unique combination of names and number of parameters,
// they can be found using "CXXCtorOfNumParms(name, numParms)".
struct LambdaOfVar {
StringRef VarName;
};
struct CXXCtorOfNumParms {
StringRef CXXCtorName;
unsigned NumParms;
};
using FindEntityByName =
std::variant<StringRef, CXXCtorOfNumParms, LambdaOfVar>;
template <typename... Ts> struct Overloaded : Ts... {
using Ts::operator()...;
};
template <typename... Ts> Overloaded(Ts...) -> Overloaded<Ts...>;
StringRef toStringRef(const FindEntityByName &N) {
return std::visit(
Overloaded{
[](StringRef S) -> StringRef { return S; },
[](const CXXCtorOfNumParms &L) -> StringRef { return L.CXXCtorName; },
[](const LambdaOfVar &L) -> StringRef { return L.VarName; },
},
N);
}
const NamedDecl *matchNamedDeclByFindEntityByName(const FindEntityByName &N,
const NamedDecl *D) {
return std::visit(
Overloaded{
[&D](StringRef S) -> const NamedDecl * {
if (D->getNameAsString() == S)
return D;
return nullptr;
},
[&D](const CXXCtorOfNumParms &L) -> const NamedDecl * {
if (auto *CD = dyn_cast<CXXConstructorDecl>(D)) {
if (CD->getNameAsString() == L.CXXCtorName &&
CD->getNumParams() == L.NumParms)
return D;
}
return nullptr;
},
[&D](const LambdaOfVar &L) -> const NamedDecl * {
if (const auto *VD = dyn_cast<VarDecl>(D); VD && VD->getInit()) {
const Expr *Init = VD->getInit()->IgnoreUnlessSpelledInSource();
if (isa<LambdaExpr>(Init) && VD->getNameAsString() == L.VarName)
return cast<LambdaExpr>(Init)->getCallOperator();
}
return nullptr;
},
},
N);
}
template <typename SomeDecl = NamedDecl,
typename = std::enable_if_t<std::is_base_of_v<NamedDecl, SomeDecl>>>
const SomeDecl *findEntityByName(FindEntityByName Name, ASTContext &Ctx) {
class NamedDeclFinder : public DynamicRecursiveASTVisitor {
public:
FindEntityByName SearchingName;
const SomeDecl *FoundDecl = nullptr;
NamedDeclFinder(FindEntityByName SearchingName)
: SearchingName(SearchingName) {}
bool VisitDecl(Decl *D) override {
if (auto *ND = dyn_cast<NamedDecl>(D)) {
FoundDecl = llvm::dyn_cast_or_null<SomeDecl>(
matchNamedDeclByFindEntityByName(SearchingName, ND));
if (FoundDecl)
return false;
}
return true;
}
};
NamedDeclFinder Finder(Name);
Finder.TraverseDecl(Ctx.getTranslationUnitDecl());
return dyn_cast_or_null<SomeDecl>(Finder.FoundDecl);
}
const FunctionDecl *findFnByName(FindEntityByName Name, ASTContext &Ctx) {
return findEntityByName<FunctionDecl>(Name, Ctx);
}
// Same as `std::pair<StringName, unsigned>` for a pair of entity declaration
// name and a pointer level with an extra optional flag for whether the entity
// represents a function return value. This structure is used to explicitly
// spell out components of an EPL such as "{"p", 1}" or "{"foo_fn", 2, true}".
struct EPLPair {
EPLPair(FindEntityByName Name, unsigned Lv, bool isFunRet = false)
: Name(Name), Lv(Lv), isFunRet(isFunRet) {}
FindEntityByName Name;
unsigned Lv;
bool isFunRet;
};
class PointerFlowTest : public TestFixture {
protected:
TUSummary TUSum;
TUSummaryBuilder Builder;
std::unique_ptr<TUSummaryExtractor> Extractor;
std::unique_ptr<ASTUnit> AST;
PointerFlowTest()
: TUSum(BuildNamespace(BuildNamespaceKind::CompilationUnit, "Mock.cpp")),
Builder(TUSum), Extractor(nullptr) {}
template <typename ContributorDecl = NamedDecl,
typename =
std::enable_if_t<std::is_base_of_v<NamedDecl, ContributorDecl>>>
bool setUpTest(StringRef Code) {
AST = tooling::buildASTFromCodeWithArgs(
Code, {"-Wno-unused-value", "-Wno-int-to-pointer-cast"});
for (auto &E : clang::ssaf::TUSummaryExtractorRegistry::entries()) {
if (E.getName() == PointerFlowEntitySummary::Name) {
Extractor = E.instantiate(Builder);
break;
}
}
if (!Extractor) {
ADD_FAILURE() << "failed to find PointerFlowTUSummaryExtractor";
return false;
}
Extractor->HandleTranslationUnit(AST->getASTContext());
return true;
}
template <typename ContributorDecl = NamedDecl>
const PointerFlowEntitySummary *getEntitySummary(FindEntityByName Name) {
const auto *ContributorDefn =
findEntityByName<ContributorDecl>(Name, AST->getASTContext());
if (!ContributorDefn) {
ADD_FAILURE() << "failed to find Decl of \"" << toStringRef(Name) << "\"";
return nullptr;
}
std::optional<EntityId> ContributorEntityId =
Extractor->addEntity(ContributorDefn);
if (!ContributorEntityId) {
ADD_FAILURE() << "failed to get EntityName for contributor \""
<< toStringRef(Name) << "\"";
return nullptr;
}
auto &TUSumData = getData(TUSum);
auto EntitiesSumIter =
TUSumData.find(PointerFlowEntitySummary::summaryName());
// If none entity summary was collected, it may not be an entry in
// `TUSumData`:
if (EntitiesSumIter == TUSumData.end())
return nullptr;
auto EntitySumIter = EntitiesSumIter->second.find(*ContributorEntityId);
// If entity summary is empty, it may not exist:
if (EntitySumIter == EntitiesSumIter->second.end())
return nullptr;
return static_cast<const PointerFlowEntitySummary *>(
EntitySumIter->second.get());
}
public:
std::optional<EntityId> getEntityId(FindEntityByName Name) {
if (const auto *D = findEntityByName(Name, AST->getASTContext())) {
return Extractor->addEntity(D);
}
return std::nullopt;
}
std::optional<EntityId> getEntityIdForReturn(FindEntityByName FunName) {
if (const auto *D = findFnByName(FunName, AST->getASTContext())) {
return Extractor->addEntityForReturn(D);
}
return std::nullopt;
}
EdgeSet makeEdges(unsigned Line, ArrayRef<std::pair<EPLPair, EPLPair>> Edges);
};
// 'ToEPL(Test, Line)' is a lambda that converts a 'EPLPair' to a
// 'EntityPointerLevel':
static constexpr auto ToEPL =
[](PointerFlowTest *Test,
unsigned Line) -> std::function<EntityPointerLevel(const EPLPair &)> {
return [Test, Line](const EPLPair &Pair) -> EntityPointerLevel {
std::optional<EntityId> Entity = Pair.isFunRet
? Test->getEntityIdForReturn(Pair.Name)
: Test->getEntityId(Pair.Name);
if (!Entity) {
ADD_FAILURE_AT(__FILE__, Line)
<< "Entity not found: " << toStringRef(Pair.Name);
}
return buildEntityPointerLevel(*Entity, Pair.Lv);
};
};
EdgeSet
PointerFlowTest::makeEdges(unsigned Line,
ArrayRef<std::pair<EPLPair, EPLPair>> Edges) {
EdgeSet Result;
for (auto Edge : Edges)
Result[ToEPL(this, Line)(Edge.first)].insert(
ToEPL(this, Line)(Edge.second));
return Result;
}
TEST_F(PointerFlowTest, IsExtractorRegisteredTest) {
EXPECT_TRUE(isTUSummaryExtractorRegistered("PointerFlow"));
}
//////////////////////////////////////////////////////////////
// Simple Assign Tests //
//////////////////////////////////////////////////////////////
TEST_F(PointerFlowTest, SimpleAssign) {
ASSERT_EQ(setUpTest(R"cpp(
void foo(int *p, int *q) {
q = p;
}
)cpp"),
true);
auto *Sum = getEntitySummary("foo");
ASSERT_NE(Sum, nullptr);
EXPECT_EQ(*Sum, makeEdges(__LINE__, {{{"q", 1U}, {"p", 1U}}}));
}
TEST_F(PointerFlowTest, AssignWithSubscriptLHS) {
ASSERT_EQ(setUpTest(R"cpp(
void foo(int **q, int *p, int x) {
q[x] = p;
}
)cpp"),
true);
auto *Sum = getEntitySummary("foo");
ASSERT_NE(Sum, nullptr);
EXPECT_EQ(*Sum, makeEdges(__LINE__, {{{"q", 2U}, {"p", 1U}}}));
}
TEST_F(PointerFlowTest, AssignWithPtrArithRHS) {
ASSERT_EQ(setUpTest(R"cpp(
void foo(int *p, int *q) {
q = p + 5;
}
)cpp"),
true);
auto *Sum = getEntitySummary("foo");
ASSERT_NE(Sum, nullptr);
EXPECT_EQ(*Sum, makeEdges(__LINE__, {{{"q", 1U}, {"p", 1U}}}));
}
TEST_F(PointerFlowTest, AssignInSubscript) {
ASSERT_EQ(setUpTest(R"cpp(
void foo(int *p, int *q) {
(q = p)[5];
}
)cpp"),
true);
auto *Sum = getEntitySummary("foo");
ASSERT_NE(Sum, nullptr);
EXPECT_EQ(*Sum, makeEdges(__LINE__, {{{"q", 1U}, {"p", 1U}}}));
}
TEST_F(PointerFlowTest, MultipleAssign) {
ASSERT_EQ(setUpTest(R"cpp(
void foo(int *p, int *q, int *r) {
q = p;
r = q;
}
)cpp"),
true);
auto *Sum = getEntitySummary("foo");
ASSERT_NE(Sum, nullptr);
EXPECT_EQ(*Sum, makeEdges(__LINE__, {
{{"q", 1U}, {"p", 1U}},
{{"r", 1U}, {"q", 1U}},
}));
}
TEST_F(PointerFlowTest, ChainedAssign) {
ASSERT_EQ(setUpTest(R"cpp(
void foo(int *p, int *q, int *r) {
r = q = p;
}
)cpp"),
true);
auto *Sum = getEntitySummary("foo");
ASSERT_NE(Sum, nullptr);
EXPECT_EQ(*Sum, makeEdges(__LINE__, {
{{"q", 1U}, {"p", 1U}},
{{"r", 1U}, {"q", 1U}},
}));
}
TEST_F(PointerFlowTest, CastToRValue) {
ASSERT_EQ(setUpTest(R"cpp(
void foo(int *p, int *q) {
q = static_cast<int *&&>(p);
}
)cpp"),
true);
auto *Sum = getEntitySummary("foo");
ASSERT_NE(Sum, nullptr);
EXPECT_EQ(*Sum, makeEdges(__LINE__, {{{"q", 1U}, {"p", 1U}}}));
}
TEST_F(PointerFlowTest, AssignToMember) {
ASSERT_EQ(setUpTest(R"cpp(
struct S { int *field; };
void foo(S s, int *p) {
s.field = p;
}
)cpp"),
true);
auto *Sum = getEntitySummary("foo");
ASSERT_NE(Sum, nullptr);
EXPECT_EQ(*Sum, makeEdges(__LINE__, {{{"field", 1U}, {"p", 1U}}}));
}
TEST_F(PointerFlowTest, AssignToMember2) {
ASSERT_EQ(setUpTest(R"cpp(
struct S { int *field; };
void foo(S *s, int *p) {
s->field = p;
}
)cpp"),
true);
auto *Sum = getEntitySummary("foo");
ASSERT_NE(Sum, nullptr);
EXPECT_EQ(*Sum, makeEdges(__LINE__, {{{"field", 1U}, {"p", 1U}}}));
}
//////////////////////////////////////////////////////////////
// Call Expr Tests. //
//////////////////////////////////////////////////////////////
TEST_F(PointerFlowTest, CallArg) {
ASSERT_EQ(setUpTest(R"cpp(
void bar(int *param);
void foo(int *p) {
bar(p);
}
)cpp"),
true);
auto *Sum = getEntitySummary("foo");
ASSERT_NE(Sum, nullptr);
EXPECT_EQ(*Sum, makeEdges(__LINE__, {{{"param", 1U}, {"p", 1U}}}));
}
TEST_F(PointerFlowTest, CallMultiArgs) {
ASSERT_EQ(setUpTest(R"cpp(
void bar(int *param1, int y, int *param2);
void foo(int *p, int x, int *q) {
bar(p, x, q);
}
)cpp"),
true);
auto *Sum = getEntitySummary("foo");
ASSERT_NE(Sum, nullptr);
EXPECT_EQ(*Sum, makeEdges(__LINE__, {
{{"param1", 1U}, {"p", 1U}},
{{"param2", 1U}, {"q", 1U}},
}));
}
TEST_F(PointerFlowTest, CallAsCallArg) {
ASSERT_EQ(setUpTest(R"cpp(
int *bar(int * w);
void foo(int * p) {
foo(bar(p));
}
)cpp"),
true);
auto *Sum = getEntitySummary("foo");
ASSERT_NE(Sum, nullptr);
EXPECT_EQ(*Sum, makeEdges(__LINE__, {{{"w", 1U}, {"p", 1U}},
{{"p", 1U}, {"bar", 1U, true}}}));
}
TEST_F(PointerFlowTest, CXXOperatorCallMultiArgs) {
ASSERT_EQ(setUpTest(R"cpp(
struct S {
int* operator()(int *a, int *b);
};
void foo(S obj, int *p, int *q) {
foo(obj, obj(p, q), obj(p, q));
}
)cpp"),
true);
auto *Sum = getEntitySummary("foo");
ASSERT_NE(Sum, nullptr);
EXPECT_EQ(*Sum, makeEdges(__LINE__, {
{{"a", 1U}, {"p", 1U}},
{{"b", 1U}, {"q", 1U}},
{{"p", 1U}, {"operator()", 1U, true}},
{{"q", 1U}, {"operator()", 1U, true}},
}));
}
TEST_F(PointerFlowTest, CXXMemberCall) {
ASSERT_EQ(setUpTest(R"cpp(
struct S {
int* method(int *a, int *b);
};
void foo(S obj, int *p, int *q) {
foo(obj, obj.method(p, q), obj.method(p, q));
}
)cpp"),
true);
auto *Sum = getEntitySummary("foo");
ASSERT_NE(Sum, nullptr);
EXPECT_EQ(*Sum, makeEdges(__LINE__, {{{"a", 1U}, {"p", 1U}},
{{"b", 1U}, {"q", 1U}},
{{"p", 1U}, {"method", 1U, true}},
{{"q", 1U}, {"method", 1U, true}}}));
}
TEST_F(PointerFlowTest, VirtualMethodCall) {
ASSERT_EQ(setUpTest(R"cpp(
struct Base {
virtual void method(int *a);
};
void foo(Base &obj, int *p) {
obj.method(p);
}
)cpp"),
true);
auto *Sum = getEntitySummary("foo");
ASSERT_NE(Sum, nullptr);
EXPECT_EQ(*Sum, makeEdges(__LINE__, {{{"a", 1U}, {"p", 1U}}}));
}
TEST_F(PointerFlowTest, StaticMethodCall) {
ASSERT_EQ(setUpTest(R"cpp(
struct S {
static void method(int *a, int *b);
};
void foo(int *p, int *q) {
S::method(p, q);
}
)cpp"),
true);
auto *Sum = getEntitySummary("foo");
ASSERT_NE(Sum, nullptr);
EXPECT_EQ(*Sum, makeEdges(__LINE__, {
{{"a", 1U}, {"p", 1U}},
{{"b", 1U}, {"q", 1U}},
}));
}
TEST_F(PointerFlowTest, DefaultArg) {
ASSERT_EQ(setUpTest(R"cpp(
int *g;
void bar(int *a, int *b = g);
void foo(int *p) {
bar(p);
}
)cpp"),
true);
auto *Sum = getEntitySummary("foo");
ASSERT_NE(Sum, nullptr);
EXPECT_EQ(*Sum, makeEdges(__LINE__,
{{{"a", 1U}, {"p", 1U}}, {{"b", 1U}, {"g", 1U}}}));
}
// Counter-example for the concern that matchArgsWithParams could go OOB
// when fewer explicit args are provided than params (due to default args).
// In Clang's AST, CallExpr::getNumArgs() always includes CXXDefaultArgExpr
// nodes for defaulted parameters, so getNumArgs() >= getNumParams() holds.
TEST_F(PointerFlowTest, AllArgsDefaulted) {
ASSERT_EQ(setUpTest(R"cpp(
int *g1, *g2;
void bar(int *a = g1, int *b = g2);
void foo() {
bar();
}
)cpp"),
true);
auto *Sum = getEntitySummary("foo");
ASSERT_NE(Sum, nullptr);
EXPECT_EQ(*Sum, makeEdges(__LINE__, {
{{"a", 1U}, {"g1", 1U}},
{{"b", 1U}, {"g2", 1U}},
}));
}
TEST_F(PointerFlowTest, DefaultArg2) {
ASSERT_EQ(setUpTest(R"cpp(
int *g;
void bar(int *a, int *b = g);
void foo(int *p) {
bar(p, p);
}
)cpp"),
true);
auto *Sum = getEntitySummary("foo");
ASSERT_NE(Sum, nullptr);
EXPECT_EQ(*Sum, makeEdges(__LINE__, {
{{"a", 1U}, {"p", 1U}},
{{"b", 1U}, {"p", 1U}},
}));
}
//////////////////////////////////////////////////////////////
// CXX Ctor Tests. //
//////////////////////////////////////////////////////////////
TEST_F(PointerFlowTest, CXXCtorCallMultiArgs) {
ASSERT_EQ(setUpTest(R"cpp(
struct S {
S(int *a, int *b) {}
};
void foo(int *p, int *q) {
S s{p, q};
}
)cpp"),
true);
auto *Sum = getEntitySummary("foo");
ASSERT_NE(Sum, nullptr);
EXPECT_EQ(*Sum, makeEdges(__LINE__, {
{{"a", 1U}, {"p", 1U}},
{{"b", 1U}, {"q", 1U}},
}));
}
TEST_F(PointerFlowTest, CXXCtorCallMultiArgs2) {
ASSERT_EQ(setUpTest(R"cpp(
struct S {
S(int *a, int x, int *b) {}
};
void foo(int *p, int x, int *q) {
S s{p, x, q};
}
)cpp"),
true);
auto *Sum = getEntitySummary("foo");
ASSERT_NE(Sum, nullptr);
EXPECT_EQ(*Sum, makeEdges(__LINE__, {
{{"a", 1U}, {"p", 1U}},
{{"b", 1U}, {"q", 1U}},
}));
}
TEST_F(PointerFlowTest, CXXCtorCallAsCallArg) {
ASSERT_EQ(setUpTest(R"cpp(
struct Wrapper {
Wrapper(int *q) {}
};
void bar(Wrapper w);
void foo(int *p) {
bar(Wrapper{p});
}
)cpp"),
true);
auto *Sum = getEntitySummary("foo");
ASSERT_NE(Sum, nullptr);
EXPECT_EQ(*Sum, makeEdges(__LINE__, {{{"q", 1U}, {"p", 1U}}}));
}
TEST_F(PointerFlowTest, DelegatingCXXCtorCall) {
ASSERT_EQ(setUpTest(R"cpp(
struct S {
S(int *a, int *b) {}
S(int *p) : S(p, p) {}
};
)cpp"),
true);
auto *Sum = getEntitySummary<CXXConstructorDecl>(CXXCtorOfNumParms{"S", 1});
ASSERT_NE(Sum, nullptr);
EXPECT_EQ(*Sum, makeEdges(__LINE__, {
{{"a", 1U}, {"p", 1U}},
{{"b", 1U}, {"p", 1U}},
}));
}
TEST_F(PointerFlowTest, CXXCtorBaseInit) {
ASSERT_EQ(setUpTest(R"cpp(
struct Base {
Base(int *a) {}
};
struct Derived : Base {
Derived(int *p) : Base(p) {}
};
)cpp"),
true);
auto *Sum = getEntitySummary<CXXConstructorDecl>("Derived");
ASSERT_NE(Sum, nullptr);
EXPECT_EQ(*Sum, makeEdges(__LINE__, {{{"a", 1U}, {"p", 1U}}}));
}
//////////////////////////////////////////////////////////////
// Initializers Tests. //
//////////////////////////////////////////////////////////////
TEST_F(PointerFlowTest, LocalVarDeclInit) {
ASSERT_EQ(setUpTest(R"cpp(
void foo(int *p) {
int *q = p;
}
)cpp"),
true);
auto *Sum = getEntitySummary("foo");
ASSERT_NE(Sum, nullptr);
EXPECT_EQ(*Sum, makeEdges(__LINE__, {{{"q", 1U}, {"p", 1U}}}));
}
TEST_F(PointerFlowTest, LocalVarDeclInit2) {
ASSERT_EQ(setUpTest(R"cpp(
void foo(int (*arr)[10]) {
int (*p)[10] = arr;
}
)cpp"),
true);
auto *Sum = getEntitySummary("foo");
ASSERT_NE(Sum, nullptr);
EXPECT_EQ(*Sum, makeEdges(__LINE__, {{{"p", 1U}, {"arr", 1U}}}));
}
TEST_F(PointerFlowTest, FieldInit) {
ASSERT_EQ(setUpTest(R"cpp(
void foo(int *p) {
struct Bar {
int *field = p;
};
}
)cpp"),
true);
auto *Sum = getEntitySummary("Bar");
ASSERT_NE(Sum, nullptr);
EXPECT_EQ(*Sum, makeEdges(__LINE__, {{{"field", 1U}, {"p", 1U}}}));
}
TEST_F(PointerFlowTest, CXXCtorMemberInit) {
StringRef Code = R"cpp(
void foo(int *p) {
struct Bar {
int *member;
Bar(int *q) : member(q) {}
};
Bar B{p};
}
)cpp";
ASSERT_EQ(setUpTest(Code), true);
auto *Sum = getEntitySummary<CXXConstructorDecl>("Bar");
ASSERT_NE(Sum, nullptr);
EXPECT_EQ(*Sum, makeEdges(__LINE__, {{{"member", 1U}, {"q", 1U}}}));
Sum = getEntitySummary("foo");
ASSERT_NE(Sum, nullptr);
EXPECT_EQ(*Sum, makeEdges(__LINE__, {{{"q", 1U}, {"p", 1U}}}));
}
TEST_F(PointerFlowTest, GlobalVarInit) {
ASSERT_EQ(setUpTest(R"cpp(
int *q;
int *g = q;
)cpp"),
true);
auto *Sum = getEntitySummary<VarDecl>("g");
ASSERT_NE(Sum, nullptr);
EXPECT_EQ(*Sum, makeEdges(__LINE__, {{{"g", 1U}, {"q", 1U}}}));
}
TEST_F(PointerFlowTest, StaticLocalInit) {
ASSERT_EQ(setUpTest(R"cpp(
void foo(int *p) {
static int *s = p;
}
)cpp"),
true);
auto *Sum = getEntitySummary("foo");
ASSERT_NE(Sum, nullptr);
EXPECT_EQ(*Sum, makeEdges(__LINE__, {{{"s", 1U}, {"p", 1U}}}));
}
TEST_F(PointerFlowTest, StaticMemberInit) {
ASSERT_EQ(setUpTest(R"cpp(
int *g;
struct S { static int *member; };
int *S::member = g;
)cpp"),
true);
auto *Sum = getEntitySummary<VarDecl>("member");
ASSERT_NE(Sum, nullptr);
EXPECT_EQ(*Sum, makeEdges(__LINE__, {{{"member", 1U}, {"g", 1U}}}));
}
//////////////////////////////////////////////////////////////
// InitList Tests. //
//////////////////////////////////////////////////////////////
TEST_F(PointerFlowTest, ArrayInitList) {
ASSERT_EQ(setUpTest(R"cpp(
void foo(int *p, int *q) {
int *arr[] = {p, q};
}
)cpp"),
true);
auto *Sum = getEntitySummary("foo");
ASSERT_NE(Sum, nullptr);
EXPECT_EQ(*Sum, makeEdges(__LINE__, {
{{"arr", 2U}, {"p", 1U}},
{{"arr", 2U}, {"q", 1U}},
}));
}
TEST_F(PointerFlowTest, StructInitList) {
ASSERT_EQ(setUpTest(R"cpp(
struct S { int *a; int *b; };
void foo(int *p, int *q) {
S s = {p, q};
}
)cpp"),
true);
auto *Sum = getEntitySummary("foo");
ASSERT_NE(Sum, nullptr);
EXPECT_EQ(*Sum, makeEdges(__LINE__, {
{{"a", 1U}, {"p", 1U}},
{{"b", 1U}, {"q", 1U}},
}));
}
// A union initialized with a brace-enclosed initializer:
TEST_F(PointerFlowTest, UnionInitList) {
ASSERT_EQ(setUpTest(R"cpp(
union U { int *x; int y; };
void foo(int *p) {
U u = {p};
}
)cpp"),
true);
auto *Sum = getEntitySummary("foo");
ASSERT_NE(Sum, nullptr);
EXPECT_EQ(*Sum, makeEdges(__LINE__, {{{"x", 1U}, {"p", 1U}}}));
}
TEST_F(PointerFlowTest, NestedInitList) {
ASSERT_EQ(setUpTest(R"cpp(
struct Inner { int * a; int * b; };
struct S { Inner c; int * d; };
void foo(int *p, int *q, int *r) {
S s = {{p, q}, r};
}
)cpp"),
true);
auto *Sum = getEntitySummary("foo");
ASSERT_NE(Sum, nullptr);
EXPECT_EQ(*Sum, makeEdges(__LINE__, {
{{"a", 1U}, {"p", 1U}},
{{"b", 1U}, {"q", 1U}},
{{"d", 1U}, {"r", 1U}},
}));
}
TEST_F(PointerFlowTest, NestedInitList2) {
ASSERT_EQ(setUpTest(R"cpp(
union Inner { int * a; int b; };
struct S { Inner c; int * d; };
void foo(int *p, int *q) {
S s = {{p}, q};
}
)cpp"),
true);
auto *Sum = getEntitySummary("foo");
ASSERT_NE(Sum, nullptr);
EXPECT_EQ(*Sum, makeEdges(__LINE__, {
{{"a", 1U}, {"p", 1U}},
{{"d", 1U}, {"q", 1U}},
}));
}
TEST_F(PointerFlowTest, NestedInitList3) {
ASSERT_EQ(setUpTest(R"cpp(
struct Inner { int * a; int * b; };
union S { Inner c; int * d; };
void foo(int *p, int *q) {
S s = {{p, q}};
}
)cpp"),
true);
auto *Sum = getEntitySummary("foo");
ASSERT_NE(Sum, nullptr);
EXPECT_EQ(*Sum, makeEdges(__LINE__, {
{{"a", 1U}, {"p", 1U}},
{{"b", 1U}, {"q", 1U}},
}));
}
TEST_F(PointerFlowTest, NestedArrayInitList) {
ASSERT_EQ(setUpTest(R"cpp(
void foo(int *p, int *q, int *r, int *s) {
int *arr[][2] = {{p, q}, {r, s}};
}
)cpp"),
true);
auto *Sum = getEntitySummary("foo");
ASSERT_NE(Sum, nullptr);
EXPECT_EQ(*Sum, makeEdges(__LINE__, {
{{"arr", 3U}, {"p", 1U}},
{{"arr", 3U}, {"q", 1U}},
{{"arr", 3U}, {"r", 1U}},
{{"arr", 3U}, {"s", 1U}},
}));
}
TEST_F(PointerFlowTest, MixedNestedArrayStructInitList) {
ASSERT_EQ(setUpTest(R"cpp(
struct T { int *arr[2]; };
void foo(int *p, int *q, int *r, int *s) {
T t[2] = {{p, q}, {r, s}};
}
)cpp"),
true);
auto *Sum = getEntitySummary("foo");
ASSERT_NE(Sum, nullptr);
EXPECT_EQ(*Sum, makeEdges(__LINE__, {
{{"arr", 2U}, {"p", 1U}},
{{"arr", 2U}, {"q", 1U}},
{{"arr", 2U}, {"r", 1U}},
{{"arr", 2U}, {"s", 1U}},
}));
}
TEST_F(PointerFlowTest, ArrayOfStructInitList) {
ASSERT_EQ(setUpTest(R"cpp(
struct S { int *a; int *b; };
void foo(int *p, int *q, int *r, int *s) {
S arr[] = {{p, q}, {r, s}};
}
)cpp"),
true);
auto *Sum = getEntitySummary("foo");
ASSERT_NE(Sum, nullptr);
EXPECT_EQ(*Sum, makeEdges(__LINE__, {
{{"a", 1U}, {"p", 1U}},
{{"b", 1U}, {"q", 1U}},
{{"a", 1U}, {"r", 1U}},
{{"b", 1U}, {"s", 1U}},
}));
}
//////////////////////////////////////////////////////////////
// Return Tests. //
//////////////////////////////////////////////////////////////
TEST_F(PointerFlowTest, ReturnEdge) {
ASSERT_EQ(setUpTest(R"cpp(
int *foo(int *p) {
return p;
}
)cpp"),
true);
auto *Sum = getEntitySummary("foo");
ASSERT_NE(Sum, nullptr);
EXPECT_EQ(*Sum, makeEdges(__LINE__, {{{"foo", 1U, true}, {"p", 1U}}}));
}
TEST_F(PointerFlowTest, MultipleReturnEdges) {
ASSERT_EQ(setUpTest(R"cpp(
int *foo(int *p, int *q, bool cond) {
if (cond)
return p;
return q;
}
)cpp"),
true);
auto *Sum = getEntitySummary("foo");
ASSERT_NE(Sum, nullptr);
EXPECT_EQ(*Sum, makeEdges(__LINE__, {
{{"foo", 1U, true}, {"p", 1U}},
{{"foo", 1U, true}, {"q", 1U}},
}));
}
TEST_F(PointerFlowTest, NoReturnEdgeForNonPointerReturnType) {
ASSERT_EQ(setUpTest(R"cpp(
int foo(int *p, int x) {
return x;
}
)cpp"),
true);
auto *Sum = getEntitySummary("foo");
EXPECT_THAT(Sum, testing::AnyOf(testing::IsNull(),
testing::Pointee(makeEdges(__LINE__, {}))));
}
TEST_F(PointerFlowTest, ReturnEdgeNotFromNestedFunction) {
StringRef Code = R"cpp(
int *foo(int *p) {
struct Inner {
int *bar(int *q) { return q; }
};
return p;
}
)cpp";
ASSERT_EQ(setUpTest(Code), true);
auto *Sum = getEntitySummary("foo");
ASSERT_NE(Sum, nullptr);
EXPECT_EQ(*Sum, makeEdges(__LINE__, {{{"foo", 1U, true}, {"p", 1U}}}));
Sum = getEntitySummary("bar");
ASSERT_NE(Sum, nullptr);
EXPECT_EQ(*Sum, makeEdges(__LINE__, {{{"bar", 1U, true}, {"q", 1U}}}));
}
TEST_F(PointerFlowTest, ReturnEdgeInClassMethod) {
ASSERT_EQ(setUpTest(R"cpp(
void foo() {
struct S {
int *method(int *p, int *q) { return p; }
};
}
)cpp"),
true);
auto *Sum = getEntitySummary("method");
ASSERT_NE(Sum, nullptr);
EXPECT_EQ(*Sum, makeEdges(__LINE__, {{{"method", 1U, true}, {"p", 1U}}}));
}
TEST_F(PointerFlowTest, NoEdgeFromIndirectCall) {
ASSERT_EQ(setUpTest(R"cpp(
void bar(int *param1);
void baz(int *param2);
void foo(int *p, void (*fp)(int *)) {
fp(p);
}
int main() {
int *q;
foo(q, bar);
foo(q, baz);
return 0;
}
)cpp"),
true);
/* FIXME or TBD: Currently indirect calls produce no edge: */
auto *Sum = getEntitySummary("foo");
EXPECT_THAT(Sum, testing::AnyOf(testing::IsNull(),
testing::Pointee(makeEdges(__LINE__, {}))));
}
//////////////////////////////////////////////////////////////
// Lambda Tests. //
//////////////////////////////////////////////////////////////
TEST_F(PointerFlowTest, ReturnInLambda) {
StringRef Code = R"cpp(
int* foo(int *p) {
auto local = [](int *r) { return r; };
return local(p);
}
)cpp";
ASSERT_EQ(setUpTest(Code), true);
auto *Sum = getEntitySummary("foo");
ASSERT_NE(Sum, nullptr);
EXPECT_EQ(*Sum, makeEdges(__LINE__, {{{"r", 1U}, {"p", 1U}},
{{"foo", 1U, true},
{LambdaOfVar{"local"}, 1U, true}}}));
Sum = getEntitySummary(LambdaOfVar{"local"});
ASSERT_NE(Sum, nullptr);
EXPECT_EQ(*Sum, makeEdges(__LINE__,
{{{LambdaOfVar{"local"}, 1U, true}, {"r", 1U}}}));
}
TEST_F(PointerFlowTest, NestedLambdaAssign) {
StringRef Code = R"cpp(
void foo() {
auto outer_lambda = [](int *r, int *s) {
s = r;
auto inner_lambda = [](int *x, int *y) { y = x; };
};
}
)cpp";
ASSERT_EQ(setUpTest(Code), true);
auto *Sum = getEntitySummary(LambdaOfVar{"outer_lambda"});
ASSERT_NE(Sum, nullptr);
EXPECT_EQ(*Sum, makeEdges(__LINE__, {{{"s", 1U}, {"r", 1U}}}));
Sum = getEntitySummary(LambdaOfVar{"inner_lambda"});
ASSERT_NE(Sum, nullptr);
EXPECT_EQ(*Sum, makeEdges(__LINE__, {{{"y", 1U}, {"x", 1U}}}));
}
} // namespace