Files
TheRock/compiler/amd-llvm/clang/unittests/ScalableStaticAnalysisFramework/Analyses/UnsafeBufferUsage/UnsafeBufferUsageTest.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

574 lines
16 KiB
C++

//===- UnsafeBufferUsageTest.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/UnsafeBufferUsage/UnsafeBufferUsage.h"
#include "FindDecl.h"
#include "TestFixture.h"
#include "clang/Frontend/ASTUnit.h"
#include "clang/ScalableStaticAnalysisFramework/Analyses/EntityPointerLevel/EntityPointerLevel.h"
#include "clang/ScalableStaticAnalysisFramework/Core/Model/EntityId.h"
#include "clang/ScalableStaticAnalysisFramework/Core/Model/EntityIdTable.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/ScalableStaticAnalysisFramework/Core/TUSummary/TUSummaryExtractor.h"
#include "clang/Tooling/Tooling.h"
#include "llvm/ADT/STLExtras.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include <initializer_list>
#include <memory>
#include <optional>
using namespace clang;
using namespace ssaf;
using testing::UnorderedElementsAre;
namespace {
class UnsafeBufferUsageTest : public TestFixture {
protected:
TUSummary TUSum;
TUSummaryBuilder Builder;
std::unique_ptr<TUSummaryExtractor> Extractor;
std::unique_ptr<ASTUnit> AST;
UnsafeBufferUsageTest()
: TUSum(BuildNamespace(BuildNamespaceKind::CompilationUnit, "Mock.cpp")),
Builder(TUSum) {}
bool setUpTest(StringRef Code) {
AST = tooling::buildASTFromCodeWithArgs(
Code, {"-Wno-unused-value", "-Wno-int-to-pointer-cast"});
Extractor =
makeTUSummaryExtractor(UnsafeBufferUsageEntitySummary::Name, Builder);
if (!Extractor) {
ADD_FAILURE() << "failed to find UnsafeBufferUsageTUSummaryExtractor";
return false;
}
Extractor->HandleTranslationUnit(AST->getASTContext());
return true;
}
template <typename ContributorDecl = NamedDecl>
const UnsafeBufferUsageEntitySummary *
getEntitySummary(StringRef ContributorEntityName) {
auto *ContributorDefn = findDeclByName<ContributorDecl>(
ContributorEntityName, AST->getASTContext());
if (!ContributorDefn) {
ADD_FAILURE() << "failed to find Decl of \"" << ContributorEntityName
<< "\"";
return nullptr;
}
std::optional<EntityId> ContributorEntityId =
Extractor->addEntity(ContributorDefn);
if (!ContributorEntityId) {
ADD_FAILURE() << "failed to get EntityName for contributor \""
<< ContributorEntityName << "\"";
return nullptr;
}
auto &TUSumData = getData(TUSum);
auto EntitiesSumIter =
TUSumData.find(UnsafeBufferUsageEntitySummary::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 UnsafeBufferUsageEntitySummary *>(
EntitySumIter->second.get());
}
std::optional<EntityId> getEntityId(StringRef Name) {
if (const auto *D = findDeclByName(Name, AST->getASTContext()))
return Extractor->addEntity(D);
return std::nullopt;
}
std::optional<EntityId> getEntityIdForReturn(StringRef FunName) {
if (const auto *D = findFnByName(FunName, AST->getASTContext()))
return Extractor->addEntityForReturn(D);
return std::nullopt;
}
// 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:
struct EPLPair {
EPLPair(StringRef Name, unsigned Lv, bool isFunRet = false)
: Name(Name), Lv(Lv), isFunRet(isFunRet) {}
StringRef Name;
unsigned Lv;
bool isFunRet;
};
EntityPointerLevelSet makeSet(unsigned Line, ArrayRef<EPLPair> Pairs) {
auto EPLs = llvm::map_range(
Pairs, [this, Line](const EPLPair &Pair) -> EntityPointerLevel {
std::optional<EntityId> Entity = Pair.isFunRet
? getEntityIdForReturn(Pair.Name)
: getEntityId(Pair.Name);
if (!Entity)
ADD_FAILURE_AT(__FILE__, Line) << "Entity not found: " << Pair.Name;
return buildEntityPointerLevel(*Entity, Pair.Lv);
});
return EntityPointerLevelSet{EPLs.begin(), EPLs.end()};
}
};
//////////////////////////////////////////////////////////////
// Data Structure Tests //
//////////////////////////////////////////////////////////////
static llvm::iterator_range<EntityPointerLevelSet::iterator>
getSubsetOf(const EntityPointerLevelSet &Set, EntityId Entity) {
return llvm::make_range(Set.equal_range(Entity));
}
TEST_F(UnsafeBufferUsageTest, EntityPointerLevelComparison) {
EntityIdTable Table;
EntityId E1 = Table.getId({"c:@F@foo", "", {}});
EntityId E2 = Table.getId({"c:@F@bar", "", {}});
auto P1 = buildEntityPointerLevel(E1, 2);
auto P2 = buildEntityPointerLevel(E1, 2);
auto P3 = buildEntityPointerLevel(E1, 1);
auto P4 = buildEntityPointerLevel(E2, 2);
EXPECT_EQ(P1, P2);
EXPECT_NE(P1, P3);
EXPECT_NE(P1, P4);
EXPECT_NE(P3, P4);
EXPECT_TRUE(P3 < P2);
EXPECT_TRUE(P3 < P4);
EXPECT_FALSE(P1 < P2);
EXPECT_FALSE(P2 < P1);
}
TEST_F(UnsafeBufferUsageTest, UnsafeBufferUsageEntityPointerLevelSetTest) {
EntityIdTable Table;
EntityId E1 = Table.getId({"c:@F@foo", "", {}});
EntityId E2 = Table.getId({"c:@F@bar", "", {}});
EntityId E3 = Table.getId({"c:@F@baz", "", {}});
auto P1 = buildEntityPointerLevel(E1, 1);
auto P2 = buildEntityPointerLevel(E1, 2);
auto P3 = buildEntityPointerLevel(E2, 1);
auto P4 = buildEntityPointerLevel(E2, 2);
auto P5 = buildEntityPointerLevel(E3, 1);
EntityPointerLevelSet Set{P1, P2, P3, P4, P5};
EXPECT_THAT(Set, UnorderedElementsAre(P1, P2, P3, P4, P5));
EXPECT_THAT(getSubsetOf(Set, E1), UnorderedElementsAre(P1, P2));
EXPECT_THAT(getSubsetOf(Set, E2), UnorderedElementsAre(P3, P4));
EXPECT_THAT(getSubsetOf(Set, E3), UnorderedElementsAre(P5));
}
//////////////////////////////////////////////////////////////
// Extractor Tests //
//////////////////////////////////////////////////////////////
TEST_F(UnsafeBufferUsageTest, SimpleFunctionWithUnsafePointer) {
ASSERT_TRUE(setUpTest(R"cpp(
void foo(int *p) {
p[5];
}
)cpp"));
const auto *Sum = getEntitySummary("foo");
EXPECT_NE(Sum, nullptr);
EXPECT_EQ(*Sum, makeSet(__LINE__, {{"p", 1U}}));
}
TEST_F(UnsafeBufferUsageTest, PointerArithmetic) {
ASSERT_TRUE(setUpTest(R"cpp(
void foo(int *p, int *q) {
*(p + 5);
*(q - 3);
}
)cpp"));
const auto *Sum = getEntitySummary("foo");
EXPECT_NE(Sum, nullptr);
EXPECT_EQ(*Sum, makeSet(__LINE__, {{"p", 1U}, {"q", 1U}}));
}
TEST_F(UnsafeBufferUsageTest, PointerIncrementDecrement) {
ASSERT_TRUE(setUpTest(R"cpp(
void foo(int *p, int *q, int *r, int *s) {
(++p)[5];
(q++)[5];
(--r)[5];
(s--)[5];
}
)cpp"));
const auto *Sum = getEntitySummary("foo");
EXPECT_NE(Sum, nullptr);
EXPECT_EQ(*Sum,
makeSet(__LINE__, {{"p", 1U}, {"q", 1U}, {"r", 1U}, {"s", 1U}}));
}
TEST_F(UnsafeBufferUsageTest, PointerAssignment) {
ASSERT_TRUE(setUpTest(R"cpp(
void foo(int *p, int *q) {
(p = q + 5)[5];
}
)cpp"));
const auto *Sum = getEntitySummary("foo");
EXPECT_NE(Sum, nullptr);
EXPECT_EQ(*Sum, makeSet(__LINE__, {{"p", 1U}, {"q", 1U}}));
}
TEST_F(UnsafeBufferUsageTest, CompoundAssignment) {
ASSERT_TRUE(setUpTest(R"cpp(
void foo(int *p, int *q) {
(p += 5)[5];
(q -= 3)[5];
}
)cpp"));
const auto *Sum = getEntitySummary("foo");
EXPECT_NE(Sum, nullptr);
EXPECT_EQ(*Sum, makeSet(__LINE__, {{"p", 1U}, {"q", 1U}}));
}
TEST_F(UnsafeBufferUsageTest, MultiLevelPointer) {
ASSERT_TRUE(setUpTest(R"cpp(
void foo(int **p, int **q, int **r) {
(*p)[5];
*(*q);
*(q[5]);
r[5][5];
}
)cpp"));
const auto *Sum = getEntitySummary("foo");
EXPECT_NE(Sum, nullptr);
EXPECT_EQ(*Sum,
makeSet(__LINE__, {{"p", 2U}, {"q", 1U}, {"r", 1U}, {"r", 2U}}));
}
TEST_F(UnsafeBufferUsageTest, ConditionalOperator) {
ASSERT_TRUE(setUpTest(R"cpp(
void foo(int **p, int **q, int cond) {
(cond ? *p : *q)[5];
cond ? p[5] : q[5];
}
)cpp"));
const auto *Sum = getEntitySummary("foo");
EXPECT_NE(Sum, nullptr);
EXPECT_EQ(*Sum,
makeSet(__LINE__, {{"p", 1U}, {"q", 1U}, {"p", 2U}, {"q", 2U}}));
}
TEST_F(UnsafeBufferUsageTest, CastExpression) {
ASSERT_TRUE(setUpTest(R"cpp(
void foo(void *p, int q) {
((int*)p)[5];
((int*)q)[5];
}
)cpp"));
const auto *Sum = getEntitySummary("foo");
EXPECT_NE(Sum, nullptr);
EXPECT_EQ(*Sum, makeSet(__LINE__, {{"p", 1U}}));
}
TEST_F(UnsafeBufferUsageTest, CommaOperator) {
ASSERT_TRUE(setUpTest(R"cpp(
void foo(int *p, int x) {
(x++, p)[5];
}
)cpp"));
const auto *Sum = getEntitySummary("foo");
EXPECT_NE(Sum, nullptr);
EXPECT_EQ(*Sum, makeSet(__LINE__, {{"p", 1U}}));
}
TEST_F(UnsafeBufferUsageTest, CommaOperator2) {
ASSERT_TRUE(setUpTest(R"cpp(
void foo(int **p, int **q, int x) {
(p[x] = 0, q[x] = 0)[5];
}
)cpp"));
const auto *Sum = getEntitySummary("foo");
EXPECT_NE(Sum, nullptr);
EXPECT_EQ(*Sum, makeSet(__LINE__, {{"p", 1U}, {"q", 1U}, {"q", 2U}}));
}
TEST_F(UnsafeBufferUsageTest, ParenthesizedExpression) {
ASSERT_TRUE(setUpTest(R"cpp(
void foo(int *p) {
(((p)))[5];
}
)cpp"));
const auto *Sum = getEntitySummary("foo");
EXPECT_NE(Sum, nullptr);
EXPECT_EQ(*Sum, makeSet(__LINE__, {{"p", 1U}}));
}
TEST_F(UnsafeBufferUsageTest, ArrayParameter) {
ASSERT_TRUE(setUpTest(R"cpp(
void foo(int arr[], int arr2[][10]) {
int n = 5;
arr[100];
arr2[5][n];
}
)cpp"));
const auto *Sum = getEntitySummary("foo");
EXPECT_NE(Sum, nullptr);
EXPECT_EQ(*Sum, makeSet(__LINE__, {{"arr", 1U}, {"arr2", 1U}, {"arr2", 2U}}));
}
TEST_F(UnsafeBufferUsageTest, FunctionCall) {
ASSERT_TRUE(setUpTest(R"cpp(
int ** (*fp)();
int ** foo() {
fp = &foo;
foo()[5];
(*fp())[5];
}
)cpp"));
const auto *Sum = getEntitySummary("foo");
EXPECT_NE(Sum, nullptr);
// No (foo, 2) because indirect calls are ignored.
EXPECT_EQ(*Sum, makeSet(__LINE__, {{"foo", 1U, true}}));
}
TEST_F(UnsafeBufferUsageTest, StructMemberAccess) {
ASSERT_TRUE(setUpTest(R"cpp(
struct S {
int *ptr;
int (*ptr_to_arr)[10];
};
void foo(struct S obj) {
int n = 5;
obj.ptr[5];
(*obj.ptr_to_arr)[n];
}
)cpp"));
const auto *Sum = getEntitySummary("foo");
EXPECT_NE(Sum, nullptr);
EXPECT_EQ(*Sum, makeSet(__LINE__, {{"ptr", 1U}, {"ptr_to_arr", 2U}}));
}
TEST_F(UnsafeBufferUsageTest, StringLiteralSubscript) {
ASSERT_TRUE(setUpTest(R"cpp(
void foo() {
"hello"[5];
}
)cpp"));
const auto *Sum = getEntitySummary("foo");
// String literals should not generate pointer kind variables
EXPECT_EQ(Sum, nullptr);
}
TEST_F(UnsafeBufferUsageTest, OpaqueValueExpr) {
ASSERT_TRUE(setUpTest(R"cpp(
void foo(int *p, int *q) {
(p ?: q)[5];
}
)cpp"));
const auto *Sum = getEntitySummary("foo");
EXPECT_NE(Sum, nullptr);
EXPECT_EQ(*Sum, makeSet(__LINE__, {{"p", 1U}, {"q", 1U}}));
}
TEST_F(UnsafeBufferUsageTest, AddressOfOperator) {
ASSERT_TRUE(setUpTest(R"cpp(
void foo(int x) {
(&x)[5];
}
)cpp"));
const auto *Sum = getEntitySummary("foo");
// Address-of should not generate pointer kind variables for 'x':
EXPECT_EQ(Sum, nullptr);
}
TEST_F(UnsafeBufferUsageTest, AddressOfThenDereference) {
ASSERT_TRUE(setUpTest(R"cpp(
void foo(int *p, int *q) {
(*(&p))[5];
(&(*q))[5];
}
)cpp"));
const auto *Sum = getEntitySummary("foo");
EXPECT_NE(Sum, nullptr);
EXPECT_EQ(*Sum, makeSet(__LINE__, {{"p", 1}, {"q", 1}}));
}
TEST_F(UnsafeBufferUsageTest, PointerToArrayOfPointers) {
ASSERT_TRUE(setUpTest(R"cpp(
void foo() {
int * arr[10];
int * (*p)[10] = &arr;
(*p)[5][5]; // '(*p)[5]' is unsafe
// '(*p)' is fine because 5 < 10
}
)cpp"));
const auto *Sum = getEntitySummary("foo");
EXPECT_NE(Sum, nullptr);
EXPECT_EQ(*Sum, makeSet(__LINE__, {{"p", 3}}));
}
TEST_F(UnsafeBufferUsageTest, UnsafePointerInGlobalVariableInitializer) {
ASSERT_TRUE(setUpTest(R"cpp(
int *gp;
int x = gp[5];
)cpp"));
const auto *Sum = getEntitySummary("x");
EXPECT_NE(Sum, nullptr);
EXPECT_EQ(*Sum, makeSet(__LINE__, {{"gp", 1U}}));
}
TEST_F(UnsafeBufferUsageTest, UnsafePointerInFieldInitializer) {
ASSERT_TRUE(setUpTest(R"cpp(
int *gp;
struct Foo {
int field = gp[5];
};
)cpp"));
const auto *Sum = getEntitySummary("Foo");
EXPECT_NE(Sum, nullptr);
EXPECT_EQ(*Sum, makeSet(__LINE__, {{"gp", 1U}}));
}
TEST_F(UnsafeBufferUsageTest, UnsafePointerInFieldInitializer2) {
ASSERT_TRUE(setUpTest(R"cpp(
int *gp;
union Foo {
int field = gp[5];
int x;
};
)cpp"));
const auto *Sum = getEntitySummary("Foo");
EXPECT_NE(Sum, nullptr);
EXPECT_EQ(*Sum, makeSet(__LINE__, {{"gp", 1U}}));
}
TEST_F(UnsafeBufferUsageTest, InitializerList) {
ASSERT_TRUE(setUpTest(R"cpp(
int *gp;
struct Foo {
int field;
int x;
};
Foo FooObj{gp[5], 0};
)cpp"));
const auto *Sum = getEntitySummary("FooObj");
EXPECT_NE(Sum, nullptr);
EXPECT_EQ(*Sum, makeSet(__LINE__, {{"gp", 1U}}));
}
TEST_F(UnsafeBufferUsageTest, UnsafePointerInCXXCtorInitializer) {
ASSERT_TRUE(setUpTest(R"cpp(
struct Foo {
int member;
Foo(int *p) : member(p[5]) {}
};
)cpp"));
const auto *Sum = getEntitySummary<CXXConstructorDecl>("Foo");
EXPECT_NE(Sum, nullptr);
EXPECT_EQ(*Sum, makeSet(__LINE__, {{"p", 1U}}));
}
TEST_F(UnsafeBufferUsageTest, UnsafePointerInDefaultArg) {
ASSERT_TRUE(setUpTest(R"cpp(
int * gp;
void foo(int x = gp[5]);
)cpp"));
const auto *Sum = getEntitySummary("foo");
EXPECT_NE(Sum, nullptr);
EXPECT_EQ(*Sum, makeSet(__LINE__, {{"gp", 1U}}));
}
TEST_F(UnsafeBufferUsageTest, NestedDefinitions) {
ASSERT_TRUE(setUpTest(R"cpp(
int * a = [](){
struct Foo {
void bar(int * ptr) { ptr[3] = 0; }
};
return nullptr;
}();
)cpp"));
const auto *Sum = getEntitySummary("bar");
EXPECT_NE(Sum, nullptr);
// The closest contributor owns the fact:
EXPECT_EQ(*Sum, makeSet(__LINE__, {{"ptr", 1U}}));
Sum = getEntitySummary("Foo");
EXPECT_EQ(Sum, nullptr);
Sum = getEntitySummary("a");
EXPECT_EQ(Sum, nullptr);
}
TEST_F(UnsafeBufferUsageTest, NestedDefinitions2) {
bool SetupSuccess = setUpTest(R"cpp(
int main(void) {
struct Foo {
void bar(int * ptr) { ptr[3] = 0; }
};
}
)cpp");
ASSERT_TRUE(SetupSuccess);
const auto *Sum = getEntitySummary("bar");
EXPECT_NE(Sum, nullptr);
// The closest contributor owns the fact:
EXPECT_EQ(*Sum, makeSet(__LINE__, {{"ptr", 1U}}));
Sum = getEntitySummary("Foo");
EXPECT_EQ(Sum, nullptr);
Sum = getEntitySummary("main");
EXPECT_EQ(Sum, nullptr);
}
} // namespace