diff --git a/clang/include/clang/Analysis/Scalable/ASTEntityMapping.h b/clang/include/clang/Analysis/Scalable/ASTEntityMapping.h new file mode 100644 index 0000000000000..a137e8b741821 --- /dev/null +++ b/clang/include/clang/Analysis/Scalable/ASTEntityMapping.h @@ -0,0 +1,46 @@ +//===- ASTMapping.h - AST to SSAF Entity mapping ----------------*- C++ -*-===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_ANALYSIS_SCALABLE_ASTMAPPING_H +#define LLVM_CLANG_ANALYSIS_SCALABLE_ASTMAPPING_H + +#include "clang/Analysis/Scalable/Model/EntityName.h" +#include "clang/AST/Decl.h" +#include "llvm/ADT/StringRef.h" +#include + +namespace clang { +namespace ssaf { + +/// Maps a declaration to an EntityName. +/// +/// Supported declaration types for entity mapping: +/// - Functions and methods +/// - Global Variables +/// - Function parameters +/// - Struct/class/union type definitions +/// - Struct/class/union fields +/// +/// Implicit declarations and compiler builtins are not mapped. +/// +/// \param D The declaration to map. Must not be null. +/// +/// \return An EntityName if the declaration can be mapped, std::nullopt otherwise. +std::optional getLocalEntityNameForDecl(const Decl* D); + +/// Maps a function return type to an EntityName. +/// +/// \param FD The function declaration. Must not be null. +/// +/// \return An EntityName for the function's return type. +std::optional getLocalEntityNameForFunctionReturn(const FunctionDecl* FD); + +} // namespace ssaf +} // namespace clang + +#endif // LLVM_CLANG_ANALYSIS_SCALABLE_ASTMAPPING_H diff --git a/clang/include/clang/Analysis/Scalable/Model/BuildNamespace.h b/clang/include/clang/Analysis/Scalable/Model/BuildNamespace.h new file mode 100644 index 0000000000000..c4bf7146e461f --- /dev/null +++ b/clang/include/clang/Analysis/Scalable/Model/BuildNamespace.h @@ -0,0 +1,84 @@ +//===- BuildNamespace.h -----------------------------------------*- C++ -*-===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_ANALYSIS_SCALABLE_BUILD_NAMESPACE_H +#define LLVM_CLANG_ANALYSIS_SCALABLE_BUILD_NAMESPACE_H + +#include "llvm/ADT/StringRef.h" +#include +#include +#include + +namespace clang { +namespace ssaf { + +enum class BuildNamespaceKind : unsigned short { + CompilationUnit, + LinkUnit +}; + +std::string toString(BuildNamespaceKind BNK); + +std::optional parseBuildNamespaceKind(llvm::StringRef Str); + +/// Represents a single step in the build process. +class BuildNamespace { + BuildNamespaceKind Kind; + std::string Name; +public: + BuildNamespace(BuildNamespaceKind Kind, llvm::StringRef Name) + : Kind(Kind), Name(Name.str()) {} + + static BuildNamespace makeTU(llvm::StringRef CompilationId); + + bool operator==(const BuildNamespace& Other) const; + bool operator!=(const BuildNamespace& Other) const; + bool operator<(const BuildNamespace& Other) const; + + friend class SerializationFormat; +}; + +/// Represents a sequence of steps in the build process. +class NestedBuildNamespace { + friend class SerializationFormat; + + std::vector Namespaces; + +public: + NestedBuildNamespace() = default; + + explicit NestedBuildNamespace(const std::vector& Namespaces) + : Namespaces(Namespaces) {} + + explicit NestedBuildNamespace(const BuildNamespace& N) { + Namespaces.push_back(N); + } + + static NestedBuildNamespace makeTU(llvm::StringRef CompilationId); + + NestedBuildNamespace makeQualified(NestedBuildNamespace Namespace) { + auto Copy = *this; + for (const auto& N : Namespace.Namespaces) + Copy.Namespaces.push_back(N); + return Copy; + } + + bool empty() const; + + bool operator==(const NestedBuildNamespace& Other) const; + bool operator!=(const NestedBuildNamespace& Other) const; + bool operator<(const NestedBuildNamespace& Other) const; + + friend class JSONWriter; + friend class LinkUnitResolution; +}; + +} // namespace ssaf +} // namespace clang + +#endif // LLVM_CLANG_ANALYSIS_SCALABLE_BUILD_NAMESPACE_H diff --git a/clang/include/clang/Analysis/Scalable/Model/EntityName.h b/clang/include/clang/Analysis/Scalable/Model/EntityName.h new file mode 100644 index 0000000000000..7f11ef0589bf5 --- /dev/null +++ b/clang/include/clang/Analysis/Scalable/Model/EntityName.h @@ -0,0 +1,47 @@ +//===- EntityName.h ---------------------------------------------*- C++ -*-===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_ANALYSIS_SCALABLE_ENTITY_NAME_H +#define LLVM_CLANG_ANALYSIS_SCALABLE_ENTITY_NAME_H + +#include "clang/Analysis/Scalable/Model/BuildNamespace.h" +#include "llvm/ADT/SmallString.h" +#include "llvm/ADT/StringRef.h" +#include + +namespace clang { +namespace ssaf { + +/// Uniquely identifies an entity in a program. +/// +/// EntityName provides a globally unique identifier for program entities that remains +/// stable across compilation boundaries. This enables whole-program analysis to track +/// and relate entities across separately compiled translation units. +class EntityName { + std::string USR; + llvm::SmallString<16> Suffix; + NestedBuildNamespace Namespace; + +public: + EntityName(llvm::StringRef USR, llvm::StringRef Suffix, + NestedBuildNamespace Namespace); + + bool operator==(const EntityName& Other) const; + bool operator!=(const EntityName& Other) const; + bool operator<(const EntityName& Other) const; + + EntityName makeQualified(NestedBuildNamespace Namespace); + + friend class LinkUnitResolution; + friend class SerializationFormat; +}; + +} // namespace ssaf +} // namespace clang + +#endif // LLVM_CLANG_ANALYSIS_SCALABLE_ENTITY_NAME_H diff --git a/clang/lib/Analysis/CMakeLists.txt b/clang/lib/Analysis/CMakeLists.txt index 1dbd4153d856f..99a2ec684e149 100644 --- a/clang/lib/Analysis/CMakeLists.txt +++ b/clang/lib/Analysis/CMakeLists.txt @@ -50,3 +50,4 @@ add_clang_library(clangAnalysis add_subdirectory(plugins) add_subdirectory(FlowSensitive) add_subdirectory(LifetimeSafety) +add_subdirectory(Scalable) diff --git a/clang/lib/Analysis/Scalable/ASTEntityMapping.cpp b/clang/lib/Analysis/Scalable/ASTEntityMapping.cpp new file mode 100644 index 0000000000000..87d05e8aa5dc3 --- /dev/null +++ b/clang/lib/Analysis/Scalable/ASTEntityMapping.cpp @@ -0,0 +1,85 @@ +//===- ASTMapping.cpp - AST to SSAF Entity mapping --------------*- C++ -*-===// +// +// 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 +// +//===----------------------------------------------------------------------===// +// +// This file implements utilities for mapping AST declarations to SSAF entities. +// +//===----------------------------------------------------------------------===// + +#include "clang/Analysis/Scalable/ASTEntityMapping.h" +#include "clang/AST/Decl.h" +#include "clang/Analysis/Scalable/Model/BuildNamespace.h" +#include "clang/Index/USRGeneration.h" +#include "llvm/ADT/SmallString.h" + +namespace clang { +namespace ssaf { + +std::optional getLocalEntityNameForDecl(const Decl* D) { + if (!D) + return std::nullopt; + + if (D->isImplicit()) + return std::nullopt; + + if (isa(D) && cast(D)->getBuiltinID()) + return std::nullopt; + + if (!isa(D) && !isa(D) && !isa(D) && + !isa(D) && !isa(D)) + return std::nullopt; + + llvm::SmallString<16> Suffix; + const Decl *USRDecl = D; + + // For parameters, use the parent function's USR with parameter index as suffix + if (const auto * PVD = dyn_cast(D)) { + const auto *FD = dyn_cast_or_null(PVD->getParentFunctionOrMethod()); + if (!FD) + return std::nullopt; + USRDecl = FD; + + const auto ParamIdx = PVD->getFunctionScopeIndex(); + llvm::raw_svector_ostream OS(Suffix); + // Parameter uses function's USR with 1-based index as suffix + OS << (ParamIdx + 1); + } + + llvm::SmallString<128> USRBuf; + if (clang::index::generateUSRForDecl(USRDecl, USRBuf)) { + return std::nullopt; + } + + if (USRBuf.empty()) + return std::nullopt; + + return EntityName(USRBuf.str(), Suffix, {}); +} + +std::optional getLocalEntityNameForFunctionReturn(const FunctionDecl* FD) { + if (!FD) + return std::nullopt; + + if (FD->isImplicit()) + return std::nullopt; + + if (FD->getBuiltinID()) + return std::nullopt; + + llvm::SmallString<128> USRBuf; + if (clang::index::generateUSRForDecl(FD, USRBuf)) { + return std::nullopt; + } + + if (USRBuf.empty()) + return std::nullopt; + + return EntityName(USRBuf.str(), "0", {}); +} + +} // namespace ssaf +} // namespace clang diff --git a/clang/lib/Analysis/Scalable/CMakeLists.txt b/clang/lib/Analysis/Scalable/CMakeLists.txt new file mode 100644 index 0000000000000..ea4693f102cb2 --- /dev/null +++ b/clang/lib/Analysis/Scalable/CMakeLists.txt @@ -0,0 +1,19 @@ +set(LLVM_LINK_COMPONENTS + Support + ) + +add_clang_library(clangAnalysisScalable + ASTEntityMapping.cpp + Model/BuildNamespace.cpp + Model/EntityName.cpp + + LINK_LIBS + clangAST + clangASTMatchers + clangBasic + clangIndex + clangLex + clangFrontend + + DEPENDS + ) diff --git a/clang/lib/Analysis/Scalable/Model/BuildNamespace.cpp b/clang/lib/Analysis/Scalable/Model/BuildNamespace.cpp new file mode 100644 index 0000000000000..5284a9a87a33a --- /dev/null +++ b/clang/lib/Analysis/Scalable/Model/BuildNamespace.cpp @@ -0,0 +1,72 @@ +//===- BuildNamespace.cpp ---------------------------------------*- C++ -*-===// +// +// 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/Scalable/Model/BuildNamespace.h" +#include "llvm/Support/ErrorHandling.h" + +namespace clang { +namespace ssaf { + +std::string toString(BuildNamespaceKind BNK) { + switch(BNK) { + case BuildNamespaceKind::CompilationUnit: return "compilation_unit"; + case BuildNamespaceKind::LinkUnit: return "link_unit"; + } + llvm_unreachable("Unknown BuildNamespaceKind"); +} + +std::optional parseBuildNamespaceKind(llvm::StringRef Str) { + if (Str == "compilation_unit") + return BuildNamespaceKind::CompilationUnit; + if (Str == "link_unit") + return BuildNamespaceKind::LinkUnit; + return std::nullopt; +} + +BuildNamespace BuildNamespace::makeTU(llvm::StringRef CompilationId) { + return BuildNamespace{BuildNamespaceKind::CompilationUnit, CompilationId.str()}; +} + +bool BuildNamespace::operator==(const BuildNamespace& Other) const { + return Kind == Other.Kind && Name == Other.Name; +} + +bool BuildNamespace::operator!=(const BuildNamespace& Other) const { + return !(*this == Other); +} + +bool BuildNamespace::operator<(const BuildNamespace& Other) const { + if (Kind != Other.Kind) + return Kind < Other.Kind; + return Name < Other.Name; +} + +NestedBuildNamespace NestedBuildNamespace::makeTU(llvm::StringRef CompilationId) { + NestedBuildNamespace Result; + Result.Namespaces.push_back(BuildNamespace::makeTU(CompilationId)); + return Result; +} + +bool NestedBuildNamespace::empty() const { + return Namespaces.empty(); +} + +bool NestedBuildNamespace::operator==(const NestedBuildNamespace& Other) const { + return Namespaces == Other.Namespaces; +} + +bool NestedBuildNamespace::operator!=(const NestedBuildNamespace& Other) const { + return !(*this == Other); +} + +bool NestedBuildNamespace::operator<(const NestedBuildNamespace& Other) const { + return Namespaces < Other.Namespaces; +} + +} // namespace ssaf +} // namespace clang diff --git a/clang/lib/Analysis/Scalable/Model/EntityName.cpp b/clang/lib/Analysis/Scalable/Model/EntityName.cpp new file mode 100644 index 0000000000000..3404ecc58fac2 --- /dev/null +++ b/clang/lib/Analysis/Scalable/Model/EntityName.cpp @@ -0,0 +1,44 @@ +//===- EntityName.cpp -------------------------------------------*- C++ -*-===// +// +// 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/Scalable/Model/EntityName.h" + +namespace clang { +namespace ssaf { + +EntityName::EntityName(llvm::StringRef USR, llvm::StringRef Suffix, + NestedBuildNamespace Namespace) + : USR(USR.str()), Suffix(Suffix), Namespace(std::move(Namespace)) {} + +bool EntityName::operator==(const EntityName& Other) const { + return USR == Other.USR && + Suffix == Other.Suffix && + Namespace == Other.Namespace; +} + +bool EntityName::operator!=(const EntityName& Other) const { + return !(*this == Other); +} + +bool EntityName::operator<(const EntityName& Other) const { + if (USR != Other.USR) + return USR < Other.USR; + if (Suffix != Other.Suffix) + return Suffix.str() < Other.Suffix.str(); + return Namespace < Other.Namespace; +} + +EntityName EntityName::makeQualified(NestedBuildNamespace Namespace) { + auto Copy = *this; + Copy.Namespace = Copy.Namespace.makeQualified(Namespace); + + return Copy; +} + +} // namespace ssaf +} // namespace clang diff --git a/clang/unittests/Analysis/CMakeLists.txt b/clang/unittests/Analysis/CMakeLists.txt index e0acf436b37c7..97e768b11db69 100644 --- a/clang/unittests/Analysis/CMakeLists.txt +++ b/clang/unittests/Analysis/CMakeLists.txt @@ -26,3 +26,4 @@ add_clang_unittest(ClangAnalysisTests ) add_subdirectory(FlowSensitive) +add_subdirectory(Scalable) diff --git a/clang/unittests/Analysis/Scalable/ASTEntityMappingTest.cpp b/clang/unittests/Analysis/Scalable/ASTEntityMappingTest.cpp new file mode 100644 index 0000000000000..8de0df246cb65 --- /dev/null +++ b/clang/unittests/Analysis/Scalable/ASTEntityMappingTest.cpp @@ -0,0 +1,343 @@ +//===- unittests/Analysis/Scalable/ASTEntityMappingTest.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/Scalable/ASTEntityMapping.h" +#include "clang/AST/ASTContext.h" +#include "clang/AST/Decl.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/ASTMatchers/ASTMatchers.h" +#include "clang/Tooling/Tooling.h" +#include "gtest/gtest.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace ssaf { +namespace { + +// Helper function to find a declaration by name +template +const DeclType *findDecl(ASTContext &Ctx, StringRef Name) { + auto Matcher = namedDecl(hasName(Name)).bind("decl"); + auto Matches = match(Matcher, Ctx); + if (Matches.empty()) + return nullptr; + return Matches[0].getNodeAs("decl"); +} + +TEST(ASTEntityMappingTest, FunctionDecl) { + auto AST = tooling::buildASTFromCode("void foo() {}"); + auto &Ctx = AST->getASTContext(); + + const auto *FD = findDecl(Ctx, "foo"); + ASSERT_NE(FD, nullptr); + + auto EntityName = getLocalEntityNameForDecl(FD); + EXPECT_TRUE(EntityName.has_value()); +} + +TEST(ASTEntityMappingTest, VarDecl) { + auto AST = tooling::buildASTFromCode("int x = 42;"); + auto &Ctx = AST->getASTContext(); + + const auto *VD = findDecl(Ctx, "x"); + ASSERT_NE(VD, nullptr); + + auto EntityName = getLocalEntityNameForDecl(VD); + EXPECT_TRUE(EntityName.has_value()); +} + +TEST(ASTEntityMappingTest, ParmVarDecl) { + auto AST = tooling::buildASTFromCode("void foo(int x) {}"); + auto &Ctx = AST->getASTContext(); + + const auto *FD = findDecl(Ctx, "foo"); + ASSERT_NE(FD, nullptr); + ASSERT_GT(FD->param_size(), 0u); + + const auto *PVD = FD->getParamDecl(0); + ASSERT_NE(PVD, nullptr); + + auto EntityName = getLocalEntityNameForDecl(PVD); + EXPECT_TRUE(EntityName.has_value()); +} + +TEST(ASTEntityMappingTest, RecordDecl) { + auto AST = tooling::buildASTFromCode("struct S {};"); + auto &Ctx = AST->getASTContext(); + + const auto *RD = findDecl(Ctx, "S"); + ASSERT_NE(RD, nullptr); + + auto EntityName = getLocalEntityNameForDecl(RD); + EXPECT_TRUE(EntityName.has_value()); +} + +TEST(ASTEntityMappingTest, FieldDecl) { + auto AST = tooling::buildASTFromCode("struct S { int field; };"); + auto &Ctx = AST->getASTContext(); + + const auto *FD = findDecl(Ctx, "field"); + ASSERT_NE(FD, nullptr); + + auto EntityName = getLocalEntityNameForDecl(FD); + EXPECT_TRUE(EntityName.has_value()); +} + +TEST(ASTEntityMappingTest, NullDecl) { + auto EntityName = getLocalEntityNameForDecl(nullptr); + EXPECT_FALSE(EntityName.has_value()); +} + +TEST(ASTEntityMappingTest, ImplicitDecl) { + auto AST = tooling::buildASTFromCode(R"( + struct S { + S() = default; + }; + )", "test.cpp", std::make_shared()); + auto &Ctx = AST->getASTContext(); + + const auto *RD = findDecl(Ctx, "S"); + ASSERT_NE(RD, nullptr); + + // Find the implicitly-declared copy constructor + for (const auto *Ctor : RD->ctors()) { + if (Ctor->isCopyConstructor() && Ctor->isImplicit()) { + auto EntityName = getLocalEntityNameForDecl(Ctor); + EXPECT_FALSE(EntityName.has_value()); + return; + } + } +} + +TEST(ASTEntityMappingTest, BuiltinFunction) { + auto AST = tooling::buildASTFromCode(R"( + void test() { + __builtin_memcpy(0, 0, 0); + } + )"); + auto &Ctx = AST->getASTContext(); + + // Find the builtin call + auto Matcher = callExpr().bind("call"); + auto Matches = match(Matcher, Ctx); + ASSERT_FALSE(Matches.empty()); + + const auto *CE = Matches[0].getNodeAs("call"); + ASSERT_NE(CE, nullptr); + + const auto *Callee = CE->getDirectCallee(); + if (Callee && Callee->getBuiltinID()) { + auto EntityName = getLocalEntityNameForDecl(Callee); + EXPECT_FALSE(EntityName.has_value()); + } +} + +TEST(ASTEntityMappingTest, UnsupportedDecl) { + auto AST = tooling::buildASTFromCode("namespace N {}"); + auto &Ctx = AST->getASTContext(); + + const auto *ND = findDecl(Ctx, "N"); + ASSERT_NE(ND, nullptr); + + auto EntityName = getLocalEntityNameForDecl(ND); + EXPECT_FALSE(EntityName.has_value()); +} + +TEST(ASTEntityMappingTest, FunctionReturn) { + auto AST = tooling::buildASTFromCode("int foo() { return 42; }"); + auto &Ctx = AST->getASTContext(); + + const auto *FD = findDecl(Ctx, "foo"); + ASSERT_NE(FD, nullptr); + + auto EntityName = getLocalEntityNameForFunctionReturn(FD); + EXPECT_TRUE(EntityName.has_value()); +} + +TEST(ASTEntityMappingTest, FunctionReturnNull) { + auto EntityName = getLocalEntityNameForFunctionReturn(nullptr); + EXPECT_FALSE(EntityName.has_value()); +} + +TEST(ASTEntityMappingTest, FunctionReturnBuiltin) { + auto AST = tooling::buildASTFromCode(R"( + void test() { + __builtin_memcpy(0, 0, 0); + } + )"); + auto &Ctx = AST->getASTContext(); + + // Find the builtin call + auto Matcher = callExpr().bind("call"); + auto Matches = match(Matcher, Ctx); + ASSERT_FALSE(Matches.empty()); + + const auto *CE = Matches[0].getNodeAs("call"); + ASSERT_NE(CE, nullptr); + + const auto *Callee = CE->getDirectCallee(); + if (Callee && Callee->getBuiltinID()) { + auto EntityName = getLocalEntityNameForFunctionReturn(Callee); + EXPECT_FALSE(EntityName.has_value()); + } +} + +TEST(ASTEntityMappingTest, DifferentFunctionsDifferentNames) { + auto AST = tooling::buildASTFromCode(R"( + void foo() {} + void bar() {} + )"); + auto &Ctx = AST->getASTContext(); + + const auto *Foo = findDecl(Ctx, "foo"); + const auto *Bar = findDecl(Ctx, "bar"); + ASSERT_NE(Foo, nullptr); + ASSERT_NE(Bar, nullptr); + + auto FooName = getLocalEntityNameForDecl(Foo); + auto BarName = getLocalEntityNameForDecl(Bar); + ASSERT_TRUE(FooName.has_value()); + ASSERT_TRUE(BarName.has_value()); + + EXPECT_NE(*FooName, *BarName); +} + +// Redeclaration tests + +TEST(ASTEntityMappingTest, FunctionRedeclaration) { + auto AST = tooling::buildASTFromCode(R"( + void foo(); + void foo() {} + )"); + auto &Ctx = AST->getASTContext(); + + auto Matcher = functionDecl(hasName("foo")).bind("decl"); + auto Matches = match(Matcher, Ctx); + ASSERT_EQ(Matches.size(), 2u); + + const auto *Decl1 = Matches[0].getNodeAs("decl"); + const auto *Decl2 = Matches[1].getNodeAs("decl"); + ASSERT_NE(Decl1, nullptr); + ASSERT_NE(Decl2, nullptr); + + auto Name1 = getLocalEntityNameForDecl(Decl1); + auto Name2 = getLocalEntityNameForDecl(Decl2); + ASSERT_TRUE(Name1.has_value()); + ASSERT_TRUE(Name2.has_value()); + + EXPECT_EQ(*Name1, *Name2); +} + +TEST(ASTEntityMappingTest, VarRedeclaration) { + auto AST = tooling::buildASTFromCode(R"( + extern int x; + int x = 42; + )"); + auto &Ctx = AST->getASTContext(); + + auto Matcher = varDecl(hasName("x")).bind("decl"); + auto Matches = match(Matcher, Ctx); + ASSERT_EQ(Matches.size(), 2u); + + const auto *Decl1 = Matches[0].getNodeAs("decl"); + const auto *Decl2 = Matches[1].getNodeAs("decl"); + ASSERT_NE(Decl1, nullptr); + ASSERT_NE(Decl2, nullptr); + + auto Name1 = getLocalEntityNameForDecl(Decl1); + auto Name2 = getLocalEntityNameForDecl(Decl2); + ASSERT_TRUE(Name1.has_value()); + ASSERT_TRUE(Name2.has_value()); + + EXPECT_EQ(*Name1, *Name2); +} + +TEST(ASTEntityMappingTest, RecordRedeclaration) { + auto AST = tooling::buildASTFromCode(R"( + struct S; + struct S {}; + )"); + auto &Ctx = AST->getASTContext(); + + // Use recordDecl(isStruct()) to avoid matching implicit typedefs + auto Matcher = recordDecl(hasName("S"), isStruct()).bind("decl"); + auto Matches = match(Matcher, Ctx); + ASSERT_GE(Matches.size(), 2u); + + const auto *Decl1 = Matches[0].getNodeAs("decl"); + const auto *Decl2 = Matches[1].getNodeAs("decl"); + ASSERT_NE(Decl1, nullptr); + ASSERT_NE(Decl2, nullptr); + + auto Name1 = getLocalEntityNameForDecl(Decl1); + auto Name2 = getLocalEntityNameForDecl(Decl2); + ASSERT_TRUE(Name1.has_value()); + ASSERT_TRUE(Name2.has_value()); + + EXPECT_EQ(*Name1, *Name2); +} + +TEST(ASTEntityMappingTest, ParmVarDeclRedeclaration) { + auto AST = tooling::buildASTFromCode(R"( + void foo(int x); + void foo(int x) {} + )"); + auto &Ctx = AST->getASTContext(); + + auto Matcher = functionDecl(hasName("foo")).bind("decl"); + auto Matches = match(Matcher, Ctx); + ASSERT_EQ(Matches.size(), 2u); + + const auto *Func1 = Matches[0].getNodeAs("decl"); + const auto *Func2 = Matches[1].getNodeAs("decl"); + ASSERT_NE(Func1, nullptr); + ASSERT_NE(Func2, nullptr); + ASSERT_GT(Func1->param_size(), 0u); + ASSERT_GT(Func2->param_size(), 0u); + + const auto *Param1 = Func1->getParamDecl(0); + const auto *Param2 = Func2->getParamDecl(0); + ASSERT_NE(Param1, nullptr); + ASSERT_NE(Param2, nullptr); + + auto Name1 = getLocalEntityNameForDecl(Param1); + auto Name2 = getLocalEntityNameForDecl(Param2); + ASSERT_TRUE(Name1.has_value()); + ASSERT_TRUE(Name2.has_value()); + + EXPECT_EQ(*Name1, *Name2); +} + +TEST(ASTEntityMappingTest, FunctionReturnRedeclaration) { + auto AST = tooling::buildASTFromCode(R"( + int foo(); + int foo() { return 42; } + )"); + auto &Ctx = AST->getASTContext(); + + auto Matcher = functionDecl(hasName("foo")).bind("decl"); + auto Matches = match(Matcher, Ctx); + ASSERT_EQ(Matches.size(), 2u); + + const auto *Decl1 = Matches[0].getNodeAs("decl"); + const auto *Decl2 = Matches[1].getNodeAs("decl"); + ASSERT_NE(Decl1, nullptr); + ASSERT_NE(Decl2, nullptr); + + auto Name1 = getLocalEntityNameForFunctionReturn(Decl1); + auto Name2 = getLocalEntityNameForFunctionReturn(Decl2); + ASSERT_TRUE(Name1.has_value()); + ASSERT_TRUE(Name2.has_value()); + + EXPECT_EQ(*Name1, *Name2); +} + +} // namespace +} // namespace ssaf +} // namespace clang diff --git a/clang/unittests/Analysis/Scalable/BuildNamespaceTest.cpp b/clang/unittests/Analysis/Scalable/BuildNamespaceTest.cpp new file mode 100644 index 0000000000000..aa4155faa30f8 --- /dev/null +++ b/clang/unittests/Analysis/Scalable/BuildNamespaceTest.cpp @@ -0,0 +1,99 @@ +//===- unittests/Analysis/Scalable/BuildNamespaceTest.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/Scalable/Model/BuildNamespace.h" +#include "gtest/gtest.h" + +namespace clang { +namespace ssaf { +namespace { + +TEST(BuildNamespaceTest, Equality) { + auto BN1 = BuildNamespace::makeTU("test.cpp"); + auto BN2 = BuildNamespace::makeTU("test.cpp"); + auto BN3 = BuildNamespace::makeTU("other.cpp"); + + EXPECT_EQ(BN1, BN2); + EXPECT_NE(BN1, BN3); +} + +TEST(BuildNamespaceTest, DifferentKinds) { + BuildNamespace CU(BuildNamespaceKind::CompilationUnit, "test"); + BuildNamespace LU(BuildNamespaceKind::LinkUnit, "test"); + + EXPECT_NE(CU, LU); +} + +TEST(BuildNamespaceTest, ToStringRoundtripCompilationUnit) { + auto Kind = BuildNamespaceKind::CompilationUnit; + auto Str = toString(Kind); + auto Parsed = parseBuildNamespaceKind(Str); + + ASSERT_TRUE(Parsed.has_value()); + EXPECT_EQ(Kind, *Parsed); +} + +TEST(BuildNamespaceTest, ToStringRoundtripLinkUnit) { + auto Kind = BuildNamespaceKind::LinkUnit; + auto Str = toString(Kind); + auto Parsed = parseBuildNamespaceKind(Str); + + ASSERT_TRUE(Parsed.has_value()); + EXPECT_EQ(Kind, *Parsed); +} + +// NestedBuildNamespace Tests + +TEST(NestedBuildNamespaceTest, DefaultConstruction) { + NestedBuildNamespace NBN; + EXPECT_TRUE(NBN.empty()); +} + +TEST(NestedBuildNamespaceTest, SingleNamespaceConstruction) { + auto BN = BuildNamespace::makeTU("test.cpp"); + NestedBuildNamespace NBN(BN); + + EXPECT_FALSE(NBN.empty()); +} + +TEST(NestedBuildNamespaceTest, MakeTU) { + auto NBN = NestedBuildNamespace::makeTU("test.cpp"); + EXPECT_FALSE(NBN.empty()); +} + +TEST(NestedBuildNamespaceTest, Equality) { + auto NBN1 = NestedBuildNamespace::makeTU("test.cpp"); + auto NBN2 = NestedBuildNamespace::makeTU("test.cpp"); + auto NBN3 = NestedBuildNamespace::makeTU("other.cpp"); + + EXPECT_EQ(NBN1, NBN2); + EXPECT_NE(NBN1, NBN3); +} + +TEST(NestedBuildNamespaceTest, MakeQualified) { + auto NBN1 = NestedBuildNamespace::makeTU("test.cpp"); + BuildNamespace LinkNS(BuildNamespaceKind::LinkUnit, "app"); + NestedBuildNamespace NBN2(LinkNS); + + auto Qualified = NBN1.makeQualified(NBN2); + + EXPECT_NE(Qualified, NBN1); + EXPECT_NE(Qualified, NBN2); +} + +TEST(NestedBuildNamespaceTest, EmptyQualified) { + NestedBuildNamespace Empty; + auto NBN = NestedBuildNamespace::makeTU("test.cpp"); + + auto Qualified = Empty.makeQualified(NBN); + EXPECT_EQ(Qualified, NBN); +} + +} // namespace +} // namespace ssaf +} // namespace clang diff --git a/clang/unittests/Analysis/Scalable/CMakeLists.txt b/clang/unittests/Analysis/Scalable/CMakeLists.txt new file mode 100644 index 0000000000000..95aaa2aea253c --- /dev/null +++ b/clang/unittests/Analysis/Scalable/CMakeLists.txt @@ -0,0 +1,18 @@ +add_distinct_clang_unittest(ClangScalableAnalysisFrameworkTests + BuildNamespaceTest.cpp + EntityNameTest.cpp + ASTEntityMappingTest.cpp + + CLANG_LIBS + clangAnalysisScalable + clangAST + clangASTMatchers + clangBasic + clangSerialization + clangTooling + ) + +add_custom_target(ClangScalableAnalysisTests + DEPENDS + ClangScalableAnalysisFrameworkTests + ) diff --git a/clang/unittests/Analysis/Scalable/EntityNameTest.cpp b/clang/unittests/Analysis/Scalable/EntityNameTest.cpp new file mode 100644 index 0000000000000..f32807f3be1c1 --- /dev/null +++ b/clang/unittests/Analysis/Scalable/EntityNameTest.cpp @@ -0,0 +1,62 @@ +//===- unittests/Analysis/Scalable/EntityNameTest.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/Scalable/Model/EntityName.h" +#include "clang/Analysis/Scalable/Model/BuildNamespace.h" +#include "gtest/gtest.h" + +namespace clang { +namespace ssaf { +namespace { + +TEST(EntityNameTest, Equality) { + auto NBN1 = NestedBuildNamespace::makeTU("test.cpp"); + auto NBN2 = NestedBuildNamespace::makeTU("test.cpp"); + + EntityName EN1("c:@F@foo", "", NBN1); + EntityName EN2("c:@F@foo", "", NBN2); + EntityName EN3("c:@F@bar", "", NBN1); + + EXPECT_EQ(EN1, EN2); + EXPECT_NE(EN1, EN3); +} + +TEST(EntityNameTest, EqualityWithDifferentSuffix) { + auto NBN = NestedBuildNamespace::makeTU("test.cpp"); + + EntityName EN1("c:@F@foo", "1", NBN); + EntityName EN2("c:@F@foo", "2", NBN); + + EXPECT_NE(EN1, EN2); +} + +TEST(EntityNameTest, EqualityWithDifferentNamespace) { + auto NBN1 = NestedBuildNamespace::makeTU("test1.cpp"); + auto NBN2 = NestedBuildNamespace::makeTU("test2.cpp"); + + EntityName EN1("c:@F@foo", "", NBN1); + EntityName EN2("c:@F@foo", "", NBN2); + + EXPECT_NE(EN1, EN2); +} + +TEST(EntityNameTest, MakeQualified) { + auto NBN1 = NestedBuildNamespace::makeTU("test.cpp"); + EntityName EN("c:@F@foo", "", NBN1); + + BuildNamespace LinkNS(BuildNamespaceKind::LinkUnit, "app"); + NestedBuildNamespace NBN2(LinkNS); + + auto Qualified = EN.makeQualified(NBN2); + + EXPECT_NE(Qualified, EN); +} + +} // namespace +} // namespace ssaf +} // namespace clang