Author: Jan Korous
Date: 2025-12-10T09:34:40-08:00
New Revision: 4c6aa8fd8abe7e4f072d067f43dee9a30fcd2f2f

URL: 
https://github.com/llvm/llvm-project/commit/4c6aa8fd8abe7e4f072d067f43dee9a30fcd2f2f
DIFF: 
https://github.com/llvm/llvm-project/commit/4c6aa8fd8abe7e4f072d067f43dee9a30fcd2f2f.diff

LOG: [clang][ssaf] Introduce entity abstraction for SSAF (#169131)

Add core abstractions for identifying program entities across
compilation and link unit boundaries in the Scalable Static Analysis
Framework (SSAF).

Introduces three key components:
- BuildNamespace: Represents build artifacts (compilation units, link
units)
- EntityName: Globally unique entity identifiers across compilation
boundaries
- AST mapping: Functions to map Clang AST declarations to EntityNames

Entity identification uses Unified Symbol Resolution (USR) as the
underlying mechanism, with extensions for sub-entities (parameters,
return values) via suffixes. The abstraction allows whole-program
analysis by providing stable identifiers that persist across separately
compiled translation units.

Added: 
    clang/include/clang/Analysis/Scalable/ASTEntityMapping.h
    clang/include/clang/Analysis/Scalable/Model/BuildNamespace.h
    clang/include/clang/Analysis/Scalable/Model/EntityName.h
    clang/lib/Analysis/Scalable/ASTEntityMapping.cpp
    clang/lib/Analysis/Scalable/CMakeLists.txt
    clang/lib/Analysis/Scalable/Model/BuildNamespace.cpp
    clang/lib/Analysis/Scalable/Model/EntityName.cpp
    clang/unittests/Analysis/Scalable/ASTEntityMappingTest.cpp
    clang/unittests/Analysis/Scalable/BuildNamespaceTest.cpp
    clang/unittests/Analysis/Scalable/CMakeLists.txt
    clang/unittests/Analysis/Scalable/EntityNameTest.cpp

Modified: 
    clang/lib/Analysis/CMakeLists.txt
    clang/unittests/Analysis/CMakeLists.txt

Removed: 
    


################################################################################
diff  --git a/clang/include/clang/Analysis/Scalable/ASTEntityMapping.h 
b/clang/include/clang/Analysis/Scalable/ASTEntityMapping.h
new file mode 100644
index 0000000000000..0e5fc204c5f1b
--- /dev/null
+++ b/clang/include/clang/Analysis/Scalable/ASTEntityMapping.h
@@ -0,0 +1,47 @@
+//===- ASTEntityMapping.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_ASTENTITYMAPPING_H
+#define LLVM_CLANG_ANALYSIS_SCALABLE_ASTENTITYMAPPING_H
+
+#include "clang/AST/Decl.h"
+#include "clang/Analysis/Scalable/Model/EntityName.h"
+#include "llvm/ADT/StringRef.h"
+#include <optional>
+
+namespace clang::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<EntityName> getEntityName(const Decl *D);
+
+/// Maps return entity of a function to an EntityName.
+/// The returned name uniquely identifies the return value of function \param
+/// FD.
+///
+/// \param FD The function declaration. Must not be null.
+///
+/// \return An EntityName for the function's return entity.
+std::optional<EntityName> getEntityNameForReturn(const FunctionDecl *FD);
+
+} // namespace clang::ssaf
+
+#endif // LLVM_CLANG_ANALYSIS_SCALABLE_ASTENTITYMAPPING_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..5ca26df1e9252
--- /dev/null
+++ b/clang/include/clang/Analysis/Scalable/Model/BuildNamespace.h
@@ -0,0 +1,123 @@
+//===- 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
+//
+//===----------------------------------------------------------------------===//
+//
+// This file defines BuildNamespace and NestedBuildNamespace classes that
+// represent build namespaces in the Scalable Static Analysis Framework.
+//
+// Build namespaces provide an abstraction for grouping program entities (such
+// as those in a shared library or compilation unit) to enable analysis of
+// software projects constructed from individual components.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_ANALYSIS_SCALABLE_MODEL_BUILDNAMESPACE_H
+#define LLVM_CLANG_ANALYSIS_SCALABLE_MODEL_BUILDNAMESPACE_H
+
+#include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/StringRef.h"
+#include <optional>
+#include <string>
+#include <vector>
+
+namespace clang::ssaf {
+
+enum class BuildNamespaceKind : unsigned short { CompilationUnit, LinkUnit };
+
+llvm::StringRef toString(BuildNamespaceKind BNK);
+
+std::optional<BuildNamespaceKind> parseBuildNamespaceKind(llvm::StringRef Str);
+
+/// Represents a single namespace in the build process.
+///
+/// A BuildNamespace groups program entities, such as those belonging to a
+/// compilation unit or link unit (e.g., a shared library). Each namespace has 
a
+/// kind (CompilationUnit or LinkUnit) and a unique identifier name within that
+/// kind.
+///
+/// BuildNamespaces can be composed into NestedBuildNamespace to represent
+/// hierarchical namespace structures that model how software is constructed
+/// from its components.
+class BuildNamespace {
+  BuildNamespaceKind Kind;
+  std::string Name;
+
+  auto asTuple() const { return std::tie(Kind, Name); }
+
+public:
+  BuildNamespace(BuildNamespaceKind Kind, llvm::StringRef Name)
+      : Kind(Kind), Name(Name.str()) {}
+
+  /// Creates a BuildNamespace representing a compilation unit.
+  ///
+  /// \param CompilationId The unique identifier for the compilation unit.
+  /// \returns A BuildNamespace with CompilationUnit kind.
+  static BuildNamespace makeCompilationUnit(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 hierarchical sequence of build namespaces.
+///
+/// A NestedBuildNamespace captures namespace qualification for program 
entities
+/// by maintaining an ordered sequence of BuildNamespace steps. This models how
+/// entities are organized through multiple steps of the build process, such as
+/// first being part of a compilation unit, then incorporated into a link unit.
+///
+/// For example, an entity might be qualified by a compilation unit namespace
+/// followed by a shared library namespace.
+class NestedBuildNamespace {
+  friend class SerializationFormat;
+
+  std::vector<BuildNamespace> Namespaces;
+
+public:
+  NestedBuildNamespace() = default;
+
+  explicit NestedBuildNamespace(const std::vector<BuildNamespace> &Namespaces)
+      : Namespaces(Namespaces) {}
+
+  explicit NestedBuildNamespace(const BuildNamespace &N) {
+    Namespaces.push_back(N);
+  }
+
+  /// Creates a NestedBuildNamespace representing a compilation unit.
+  ///
+  /// \param CompilationId The unique identifier for the compilation unit.
+  /// \returns A NestedBuildNamespace containing a single CompilationUnit
+  ///          BuildNamespace.
+  static NestedBuildNamespace
+  makeCompilationUnit(llvm::StringRef CompilationId);
+
+  /// Creates a new NestedBuildNamespace by appending additional namespace.
+  ///
+  /// \param Namespace The namespace to append.
+  NestedBuildNamespace makeQualified(NestedBuildNamespace Namespace) const {
+    auto Copy = *this;
+    Copy.Namespaces.reserve(Copy.Namespaces.size() +
+                            Namespace.Namespaces.size());
+    llvm::append_range(Copy.Namespaces, Namespace.Namespaces);
+    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 clang::ssaf
+
+#endif // LLVM_CLANG_ANALYSIS_SCALABLE_MODEL_BUILDNAMESPACE_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..23890ab7bea43
--- /dev/null
+++ b/clang/include/clang/Analysis/Scalable/Model/EntityName.h
@@ -0,0 +1,56 @@
+//===- 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_MODEL_ENTITYNAME_H
+#define LLVM_CLANG_ANALYSIS_SCALABLE_MODEL_ENTITYNAME_H
+
+#include "clang/Analysis/Scalable/Model/BuildNamespace.h"
+#include "llvm/ADT/SmallString.h"
+#include "llvm/ADT/StringRef.h"
+#include <string>
+
+namespace clang::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.
+///
+/// Client code should not make assumptions about the implementation details,
+/// such as USRs.
+class EntityName {
+  std::string USR;
+  llvm::SmallString<16> Suffix;
+  NestedBuildNamespace Namespace;
+
+  auto asTuple() const { return std::tie(USR, Suffix, Namespace); }
+
+public:
+  /// Client code should not use this constructor directly.
+  /// Use getEntityName and other functions in ASTEntityMapping.h to get
+  /// entity names.
+  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;
+
+  /// Creates a new EntityName with additional build namespace qualification.
+  ///
+  /// \param Namespace The namespace steps to append to this entity's 
namespace.
+  EntityName makeQualified(NestedBuildNamespace Namespace) const;
+
+  friend class LinkUnitResolution;
+  friend class SerializationFormat;
+};
+
+} // namespace clang::ssaf
+
+#endif // LLVM_CLANG_ANALYSIS_SCALABLE_MODEL_ENTITYNAME_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..0a25e75e01631
--- /dev/null
+++ b/clang/lib/Analysis/Scalable/ASTEntityMapping.cpp
@@ -0,0 +1,83 @@
+//===- 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::ssaf {
+
+std::optional<EntityName> getEntityName(const Decl *D) {
+  if (!D)
+    return std::nullopt;
+
+  if (D->isImplicit())
+    return std::nullopt;
+
+  if (isa<FunctionDecl>(D) && cast<FunctionDecl>(D)->getBuiltinID())
+    return std::nullopt;
+
+  if (!isa<FunctionDecl, ParmVarDecl, VarDecl, FieldDecl, RecordDecl>(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<ParmVarDecl>(D)) {
+    const auto *FD =
+        dyn_cast_or_null<FunctionDecl>(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<EntityName> getEntityNameForReturn(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(), /*Suffix=*/"0", /*Namespace=*/{});
+}
+
+} // namespace clang::ssaf

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..040cfe9926be2
--- /dev/null
+++ b/clang/lib/Analysis/Scalable/Model/BuildNamespace.cpp
@@ -0,0 +1,73 @@
+//===- 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"
+#include <tuple>
+
+namespace clang::ssaf {
+
+llvm::StringRef toString(BuildNamespaceKind BNK) {
+  switch (BNK) {
+  case BuildNamespaceKind::CompilationUnit:
+    return "compilation_unit";
+  case BuildNamespaceKind::LinkUnit:
+    return "link_unit";
+  }
+  llvm_unreachable("Unknown BuildNamespaceKind");
+}
+
+std::optional<BuildNamespaceKind> parseBuildNamespaceKind(llvm::StringRef Str) 
{
+  if (Str == "compilation_unit")
+    return BuildNamespaceKind::CompilationUnit;
+  if (Str == "link_unit")
+    return BuildNamespaceKind::LinkUnit;
+  return std::nullopt;
+}
+
+BuildNamespace
+BuildNamespace::makeCompilationUnit(llvm::StringRef CompilationId) {
+  return BuildNamespace{BuildNamespaceKind::CompilationUnit,
+                        CompilationId.str()};
+}
+
+bool BuildNamespace::operator==(const BuildNamespace &Other) const {
+  return asTuple() == Other.asTuple();
+}
+
+bool BuildNamespace::operator!=(const BuildNamespace &Other) const {
+  return !(*this == Other);
+}
+
+bool BuildNamespace::operator<(const BuildNamespace &Other) const {
+  return asTuple() < Other.asTuple();
+}
+
+NestedBuildNamespace
+NestedBuildNamespace::makeCompilationUnit(llvm::StringRef CompilationId) {
+  NestedBuildNamespace Result;
+  Result.Namespaces.push_back(
+      BuildNamespace::makeCompilationUnit(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 clang::ssaf

diff  --git a/clang/lib/Analysis/Scalable/Model/EntityName.cpp 
b/clang/lib/Analysis/Scalable/Model/EntityName.cpp
new file mode 100644
index 0000000000000..7e66476d6ce0e
--- /dev/null
+++ b/clang/lib/Analysis/Scalable/Model/EntityName.cpp
@@ -0,0 +1,36 @@
+//===- 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::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 asTuple() == Other.asTuple();
+}
+
+bool EntityName::operator!=(const EntityName &Other) const {
+  return !(*this == Other);
+}
+
+bool EntityName::operator<(const EntityName &Other) const {
+  return asTuple() < Other.asTuple();
+}
+
+EntityName EntityName::makeQualified(NestedBuildNamespace Namespace) const {
+  auto Copy = *this;
+  Copy.Namespace = Copy.Namespace.makeQualified(Namespace);
+
+  return Copy;
+}
+
+} // namespace clang::ssaf

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..dd41864da3c55
--- /dev/null
+++ b/clang/unittests/Analysis/Scalable/ASTEntityMappingTest.cpp
@@ -0,0 +1,352 @@
+//===- 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/AST/DeclCXX.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::ssaf {
+namespace {
+
+// Helper function to find a declaration by name
+template <typename DeclType>
+const DeclType *findDecl(ASTContext &Ctx, StringRef Name) {
+  auto Matcher = namedDecl(hasName(Name)).bind("decl");
+  auto Matches = match(Matcher, Ctx);
+  if (Matches.empty())
+    return nullptr;
+  if (auto Result = Matches[0].getNodeAs<DeclType>("decl"))
+    return dyn_cast<DeclType>(Result->getCanonicalDecl());
+  return nullptr;
+}
+
+TEST(ASTEntityMappingTest, FunctionDecl) {
+  auto AST = tooling::buildASTFromCode(R"cpp(void foo() {})cpp");
+  auto &Ctx = AST->getASTContext();
+
+  const auto *FD = findDecl<FunctionDecl>(Ctx, "foo");
+  ASSERT_NE(FD, nullptr);
+
+  auto EntityName = getEntityName(FD);
+  EXPECT_TRUE(EntityName.has_value());
+}
+
+TEST(ASTEntityMappingTest, VarDecl) {
+  auto AST = tooling::buildASTFromCode(R"cpp(int x = 42;)cpp");
+  auto &Ctx = AST->getASTContext();
+
+  const auto *VD = findDecl<VarDecl>(Ctx, "x");
+  ASSERT_NE(VD, nullptr);
+
+  auto EntityName = getEntityName(VD);
+  EXPECT_TRUE(EntityName.has_value());
+}
+
+TEST(ASTEntityMappingTest, ParmVarDecl) {
+  auto AST = tooling::buildASTFromCode(R"cpp(void foo(int x) {})cpp");
+  auto &Ctx = AST->getASTContext();
+
+  const auto *FD = findDecl<FunctionDecl>(Ctx, "foo");
+  ASSERT_NE(FD, nullptr);
+  ASSERT_EQ(FD->param_size(), 1u);
+
+  const auto *PVD = FD->getParamDecl(0);
+  ASSERT_NE(PVD, nullptr);
+
+  auto EntityName = getEntityName(PVD);
+  EXPECT_TRUE(EntityName.has_value());
+}
+
+TEST(ASTEntityMappingTest, RecordDecl) {
+  auto AST = tooling::buildASTFromCode(R"cpp(struct S {};)cpp");
+  auto &Ctx = AST->getASTContext();
+
+  const auto *RD = findDecl<RecordDecl>(Ctx, "S");
+  ASSERT_NE(RD, nullptr);
+
+  auto EntityName = getEntityName(RD);
+  EXPECT_TRUE(EntityName.has_value());
+}
+
+TEST(ASTEntityMappingTest, FieldDecl) {
+  auto AST = tooling::buildASTFromCode(R"cpp(struct S { int field; };)cpp");
+  auto &Ctx = AST->getASTContext();
+
+  const auto *FD = findDecl<FieldDecl>(Ctx, "field");
+  ASSERT_NE(FD, nullptr);
+
+  auto EntityName = getEntityName(FD);
+  EXPECT_TRUE(EntityName.has_value());
+}
+
+TEST(ASTEntityMappingTest, NullDecl) {
+  auto EntityName = getEntityName(nullptr);
+  EXPECT_FALSE(EntityName.has_value());
+}
+
+TEST(ASTEntityMappingTest, ImplicitDeclLambda) {
+  auto AST = tooling::buildASTFromCode(
+      R"cpp(
+    auto L = [](){};
+  )cpp",
+      "test.cpp", std::make_shared<PCHContainerOperations>());
+  auto &Ctx = AST->getASTContext();
+
+  auto Matcher = cxxRecordDecl(isImplicit()).bind("decl");
+  auto Matches = match(Matcher, Ctx);
+  ASSERT_GT(Matches.size(), 0u);
+
+  const auto *ImplCXXRD = Matches[0].getNodeAs<CXXRecordDecl>("decl");
+  ASSERT_NE(ImplCXXRD, nullptr);
+
+  auto EntityName = getEntityName(ImplCXXRD);
+  EXPECT_FALSE(EntityName.has_value());
+}
+
+TEST(ASTEntityMappingTest, BuiltinFunction) {
+  auto AST = tooling::buildASTFromCode(R"cpp(
+    void test() {
+      __builtin_memcpy(0, 0, 0);
+    }
+  )cpp");
+  auto &Ctx = AST->getASTContext();
+
+  // Find the builtin call
+  auto Matcher = callExpr().bind("call");
+  auto Matches = match(Matcher, Ctx);
+  ASSERT_EQ(Matches.size(), 1ul);
+
+  const auto *CE = Matches[0].getNodeAs<CallExpr>("call");
+  ASSERT_NE(CE, nullptr);
+
+  const auto *Callee = CE->getDirectCallee();
+  ASSERT_NE(Callee, nullptr);
+  ASSERT_NE(Callee->getBuiltinID(), 0u /* not a built-in */);
+
+  auto EntityName = getEntityName(Callee);
+  EXPECT_FALSE(EntityName.has_value());
+}
+
+TEST(ASTEntityMappingTest, UnsupportedDecl) {
+  auto AST = tooling::buildASTFromCode(R"cpp(namespace N {})cpp");
+  auto &Ctx = AST->getASTContext();
+
+  const auto *ND = findDecl<NamespaceDecl>(Ctx, "N");
+  ASSERT_NE(ND, nullptr);
+
+  auto EntityName = getEntityName(ND);
+  EXPECT_FALSE(EntityName.has_value());
+}
+
+TEST(ASTEntityMappingTest, FunctionReturn) {
+  auto AST = tooling::buildASTFromCode(R"cpp(int foo() { return 42; })cpp");
+  auto &Ctx = AST->getASTContext();
+
+  const auto *FD = findDecl<FunctionDecl>(Ctx, "foo");
+  ASSERT_NE(FD, nullptr);
+
+  auto EntityName = getEntityNameForReturn(FD);
+  EXPECT_TRUE(EntityName.has_value());
+}
+
+TEST(ASTEntityMappingTest, FunctionReturnNull) {
+  auto EntityName = getEntityNameForReturn(nullptr);
+  EXPECT_FALSE(EntityName.has_value());
+}
+
+TEST(ASTEntityMappingTest, FunctionReturnBuiltin) {
+  auto AST = tooling::buildASTFromCode(R"cpp(
+    void test() {
+      __builtin_memcpy(0, 0, 0);
+    }
+  )cpp");
+  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<CallExpr>("call");
+  ASSERT_NE(CE, nullptr);
+
+  const auto *Callee = CE->getDirectCallee();
+  if (Callee && Callee->getBuiltinID()) {
+    auto EntityName = getEntityNameForReturn(Callee);
+    EXPECT_FALSE(EntityName.has_value());
+  }
+}
+
+TEST(ASTEntityMappingTest, DifferentFunctionsDifferentNames) {
+  auto AST = tooling::buildASTFromCode(R"cpp(
+    void foo() {}
+    void bar() {}
+  )cpp");
+  auto &Ctx = AST->getASTContext();
+
+  const auto *Foo = findDecl<FunctionDecl>(Ctx, "foo");
+  const auto *Bar = findDecl<FunctionDecl>(Ctx, "bar");
+  ASSERT_NE(Foo, nullptr);
+  ASSERT_NE(Bar, nullptr);
+
+  auto FooName = getEntityName(Foo);
+  auto BarName = getEntityName(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"cpp(
+    void foo();
+    void foo() {}
+  )cpp");
+  auto &Ctx = AST->getASTContext();
+
+  auto Matcher = functionDecl(hasName("foo")).bind("decl");
+  auto Matches = match(Matcher, Ctx);
+  ASSERT_GE(Matches.size(), 2u);
+
+  const auto *FirstDecl = Matches[0].getNodeAs<FunctionDecl>("decl");
+  ASSERT_NE(FirstDecl, nullptr);
+
+  auto FirstName = getEntityName(FirstDecl);
+  ASSERT_TRUE(FirstName.has_value());
+
+  for (size_t I = 1; I < Matches.size(); ++I) {
+    const auto *Decl = Matches[I].getNodeAs<FunctionDecl>("decl");
+    ASSERT_NE(Decl, nullptr);
+
+    auto Name = getEntityName(Decl);
+    ASSERT_TRUE(Name.has_value());
+    EXPECT_EQ(*FirstName, *Name);
+  }
+}
+
+TEST(ASTEntityMappingTest, VarRedeclaration) {
+  auto AST = tooling::buildASTFromCode(R"cpp(
+    extern int x;
+    int x = 42;
+  )cpp");
+  auto &Ctx = AST->getASTContext();
+
+  auto Matcher = varDecl(hasName("x")).bind("decl");
+  auto Matches = match(Matcher, Ctx);
+  ASSERT_EQ(Matches.size(), 2u);
+
+  const auto *FirstDecl = Matches[0].getNodeAs<VarDecl>("decl");
+  ASSERT_NE(FirstDecl, nullptr);
+
+  auto FirstName = getEntityName(FirstDecl);
+  ASSERT_TRUE(FirstName.has_value());
+
+  for (size_t I = 1; I < Matches.size(); ++I) {
+    const auto *Decl = Matches[I].getNodeAs<VarDecl>("decl");
+    ASSERT_NE(Decl, nullptr);
+
+    auto Name = getEntityName(Decl);
+    ASSERT_TRUE(Name.has_value());
+    EXPECT_EQ(*FirstName, *Name);
+  }
+}
+
+TEST(ASTEntityMappingTest, RecordRedeclaration) {
+  auto AST = tooling::buildASTFromCode(R"cpp(
+    struct S;
+    struct S {};
+  )cpp");
+  auto &Ctx = AST->getASTContext();
+
+  auto Matcher = recordDecl(hasName("S"), unless(isImplicit())).bind("decl");
+  auto Matches = match(Matcher, Ctx);
+  ASSERT_GE(Matches.size(), 2u);
+
+  const auto *FirstDecl = Matches[0].getNodeAs<RecordDecl>("decl");
+  ASSERT_NE(FirstDecl, nullptr);
+
+  auto FirstName = getEntityName(FirstDecl);
+  ASSERT_TRUE(FirstName.has_value());
+
+  for (size_t I = 1; I < Matches.size(); ++I) {
+    const auto *Decl = Matches[I].getNodeAs<RecordDecl>("decl");
+    ASSERT_NE(Decl, nullptr);
+
+    auto Name = getEntityName(Decl);
+    ASSERT_TRUE(Name.has_value());
+    EXPECT_EQ(*FirstName, *Name);
+  }
+}
+
+TEST(ASTEntityMappingTest, ParmVarDeclRedeclaration) {
+  auto AST = tooling::buildASTFromCode(R"cpp(
+    void foo(int);
+    void foo(int x);
+    void foo(int y);
+    void foo(int x) {}
+  )cpp");
+  auto &Ctx = AST->getASTContext();
+
+  auto Matcher = functionDecl(hasName("foo")).bind("decl");
+  auto Matches = match(Matcher, Ctx);
+  ASSERT_GE(Matches.size(), 2u);
+
+  const auto *FirstFuncDecl = Matches[0].getNodeAs<FunctionDecl>("decl");
+  ASSERT_NE(FirstFuncDecl, nullptr);
+  ASSERT_GT(FirstFuncDecl->param_size(), 0u);
+
+  auto ParamEName = getEntityName(FirstFuncDecl->getParamDecl(0));
+  ASSERT_TRUE(ParamEName.has_value());
+
+  for (size_t I = 1; I < Matches.size(); ++I) {
+    const auto *FDecl = Matches[I].getNodeAs<FunctionDecl>("decl");
+    ASSERT_NE(FDecl, nullptr);
+    ASSERT_GT(FDecl->param_size(), 0u);
+
+    auto ParamRedeclEName = getEntityName(FDecl->getParamDecl(0));
+    EXPECT_EQ(*ParamEName, *ParamRedeclEName);
+  }
+}
+
+TEST(ASTEntityMappingTest, FunctionReturnRedeclaration) {
+  auto AST = tooling::buildASTFromCode(R"cpp(
+    int foo();
+    int foo() { return 42; }
+  )cpp");
+  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<FunctionDecl>("decl");
+  ASSERT_NE(Decl1, nullptr);
+  auto Name1 = getEntityNameForReturn(Decl1);
+  ASSERT_TRUE(Name1.has_value());
+
+  for (size_t I = 1; I < Matches.size(); ++I) {
+    const auto *FDecl = Matches[I].getNodeAs<FunctionDecl>("decl");
+    ASSERT_NE(FDecl, nullptr);
+
+    auto Name = getEntityNameForReturn(Decl1);
+    ASSERT_TRUE(Name.has_value());
+    EXPECT_EQ(*Name1, *Name);
+  }
+}
+
+} // namespace
+} // namespace clang::ssaf

diff  --git a/clang/unittests/Analysis/Scalable/BuildNamespaceTest.cpp 
b/clang/unittests/Analysis/Scalable/BuildNamespaceTest.cpp
new file mode 100644
index 0000000000000..80d8a40738be3
--- /dev/null
+++ b/clang/unittests/Analysis/Scalable/BuildNamespaceTest.cpp
@@ -0,0 +1,97 @@
+//===- 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::ssaf {
+namespace {
+
+TEST(BuildNamespaceTest, Equality) {
+  auto BN1 = BuildNamespace::makeCompilationUnit("test.cpp");
+  auto BN2 = BuildNamespace::makeCompilationUnit("test.cpp");
+  auto BN3 = BuildNamespace::makeCompilationUnit("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::makeCompilationUnit("test.cpp");
+  NestedBuildNamespace NBN(BN);
+
+  EXPECT_FALSE(NBN.empty());
+}
+
+TEST(NestedBuildNamespaceTest, MakeTU) {
+  auto NBN = NestedBuildNamespace::makeCompilationUnit("test.cpp");
+  EXPECT_FALSE(NBN.empty());
+}
+
+TEST(NestedBuildNamespaceTest, Equality) {
+  auto NBN1 = NestedBuildNamespace::makeCompilationUnit("test.cpp");
+  auto NBN2 = NestedBuildNamespace::makeCompilationUnit("test.cpp");
+  auto NBN3 = NestedBuildNamespace::makeCompilationUnit("other.cpp");
+
+  EXPECT_EQ(NBN1, NBN2);
+  EXPECT_NE(NBN1, NBN3);
+}
+
+TEST(NestedBuildNamespaceTest, MakeQualified) {
+  auto NBN1 = NestedBuildNamespace::makeCompilationUnit("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::makeCompilationUnit("test.cpp");
+
+  auto Qualified = Empty.makeQualified(NBN);
+  EXPECT_EQ(Qualified, NBN);
+}
+
+} // namespace
+} // namespace clang::ssaf

diff  --git a/clang/unittests/Analysis/Scalable/CMakeLists.txt 
b/clang/unittests/Analysis/Scalable/CMakeLists.txt
new file mode 100644
index 0000000000000..e545e314b49ac
--- /dev/null
+++ b/clang/unittests/Analysis/Scalable/CMakeLists.txt
@@ -0,0 +1,18 @@
+add_distinct_clang_unittest(ClangScalableAnalysisFrameworkTests
+  ASTEntityMappingTest.cpp
+  BuildNamespaceTest.cpp
+  EntityNameTest.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..be0c2a9d52132
--- /dev/null
+++ b/clang/unittests/Analysis/Scalable/EntityNameTest.cpp
@@ -0,0 +1,60 @@
+//===- 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::ssaf {
+namespace {
+
+TEST(EntityNameTest, Equality) {
+  auto NBN1 = NestedBuildNamespace::makeCompilationUnit("test.cpp");
+  auto NBN2 = NestedBuildNamespace::makeCompilationUnit("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::makeCompilationUnit("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::makeCompilationUnit("test1.cpp");
+  auto NBN2 = NestedBuildNamespace::makeCompilationUnit("test2.cpp");
+
+  EntityName EN1("c:@F@foo", "", NBN1);
+  EntityName EN2("c:@F@foo", "", NBN2);
+
+  EXPECT_NE(EN1, EN2);
+}
+
+TEST(EntityNameTest, MakeQualified) {
+  auto NBN1 = NestedBuildNamespace::makeCompilationUnit("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 clang::ssaf


        
_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to