https://github.com/jkorous-apple updated 
https://github.com/llvm/llvm-project/pull/210201

>From 25c8b80f6c973d8ea9ac9b9ddf14719388ff3a57 Mon Sep 17 00:00:00 2001
From: Jan Korous <[email protected]>
Date: Tue, 16 Jun 2026 17:45:06 -0700
Subject: [PATCH] [SSAF] Add EntitySourceLocationExtractor

Walks every entity-producing decl in the TU and emits
SourceLocationRecord(s) per entity-kind anchor:

* Identifier-bearing decls (FunctionDecl/VarDecl/FieldDecl/
  RecordDecl): Decl::getLocation().
* Synthetic function return-slot entity:
  FD->getReturnTypeSourceRange().getBegin().
* ParmVarDecl (named or unnamed):
  P->getTypeSpecStartLoc().

Skips compiler-synthesized implicits and builtins via the
getEntityName gate, system-header decls via SourceManager,
and silently drops records whose location is invalid or whose
file path cannot be canonicalized via real_path. Path
canonicalization is wrapped in llvm::sys::sandbox::scopedDisable
so extraction works under sandboxed compile drivers.

Assisted-By: Claude Opus 4.7
---
 .../BuiltinAnchorSources.def                  |   1 +
 .../Analyses/CMakeLists.txt                   |   1 +
 .../EntitySourceLocationExtractor.cpp         | 188 +++++++
 .../EntitySourceLocationExtractorTest.cpp     | 479 ++++++++++++++++++
 .../ScalableStaticAnalysis/CMakeLists.txt     |   1 +
 5 files changed, 670 insertions(+)
 create mode 100644 
clang/lib/ScalableStaticAnalysis/Analyses/SharedLexicalRepresentation/EntitySourceLocationExtractor.cpp
 create mode 100644 
clang/unittests/ScalableStaticAnalysis/Analyses/SharedLexicalRepresentation/EntitySourceLocationExtractorTest.cpp

diff --git 
a/clang/include/clang/ScalableStaticAnalysis/BuiltinAnchorSources.def 
b/clang/include/clang/ScalableStaticAnalysis/BuiltinAnchorSources.def
index 5104259590207..a128ced676ed3 100644
--- a/clang/include/clang/ScalableStaticAnalysis/BuiltinAnchorSources.def
+++ b/clang/include/clang/ScalableStaticAnalysis/BuiltinAnchorSources.def
@@ -19,6 +19,7 @@
 ANCHOR(AnalysisRegistryAnchorSource)
 ANCHOR(CallGraphExtractorAnchorSource)
 ANCHOR(CallGraphJSONFormatAnchorSource)
+ANCHOR(EntitySourceLocationExtractorAnchorSource)
 ANCHOR(JSONFormatAnchorSource)
 ANCHOR(TypeConstrainedPointersAnchorSource)
 ANCHOR(PointerFlowAnalysisAnchorSource)
diff --git a/clang/lib/ScalableStaticAnalysis/Analyses/CMakeLists.txt 
b/clang/lib/ScalableStaticAnalysis/Analyses/CMakeLists.txt
index 27bdab0bbad0d..98ce8e799e0e0 100644
--- a/clang/lib/ScalableStaticAnalysis/Analyses/CMakeLists.txt
+++ b/clang/lib/ScalableStaticAnalysis/Analyses/CMakeLists.txt
@@ -12,6 +12,7 @@ add_clang_library(clangScalableStaticAnalysisAnalyses
   PointerFlow/PointerFlowAnalysis.cpp
   PointerFlow/PointerFlowExtractor.cpp
   PointerFlow/PointerFlowFormat.cpp
+  SharedLexicalRepresentation/EntitySourceLocationExtractor.cpp
   SharedLexicalRepresentation/SharedLexicalRepresentationFormat.cpp
   SSAFAnalysesCommon.cpp
   TypeConstrainedPointers/TypeConstrainedPointers.cpp
diff --git 
a/clang/lib/ScalableStaticAnalysis/Analyses/SharedLexicalRepresentation/EntitySourceLocationExtractor.cpp
 
b/clang/lib/ScalableStaticAnalysis/Analyses/SharedLexicalRepresentation/EntitySourceLocationExtractor.cpp
new file mode 100644
index 0000000000000..c01f497fd4779
--- /dev/null
+++ 
b/clang/lib/ScalableStaticAnalysis/Analyses/SharedLexicalRepresentation/EntitySourceLocationExtractor.cpp
@@ -0,0 +1,188 @@
+//===- EntitySourceLocationExtractor.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/AST/ASTContext.h"
+#include "clang/AST/Decl.h"
+#include "clang/AST/DeclCXX.h"
+#include "clang/AST/DynamicRecursiveASTVisitor.h"
+#include "clang/Basic/SourceLocation.h"
+#include "clang/Basic/SourceManager.h"
+#include 
"clang/ScalableStaticAnalysis/Analyses/SharedLexicalRepresentation/SharedLexicalRepresentation.h"
+#include "clang/ScalableStaticAnalysis/Core/Model/EntityId.h"
+#include "clang/ScalableStaticAnalysis/Core/TUSummary/ExtractorRegistry.h"
+#include "clang/ScalableStaticAnalysis/Core/TUSummary/TUSummaryBuilder.h"
+#include "clang/ScalableStaticAnalysis/Core/TUSummary/TUSummaryExtractor.h"
+#include "llvm/ADT/SmallString.h"
+#include "llvm/ADT/StringRef.h"
+#include "llvm/Support/FileSystem.h"
+#include "llvm/Support/IOSandbox.h"
+#include "llvm/Support/Path.h"
+
+#include <map>
+#include <memory>
+#include <optional>
+#include <utility>
+#include <vector>
+
+namespace clang::ssaf {
+extern EntitySourceLocationsSummary
+buildEntitySourceLocationsSummary(std::vector<SourceLocationRecord> Locs);
+} // namespace clang::ssaf
+
+namespace {
+using namespace clang;
+using namespace ssaf;
+
+/// Per-EntityId aggregation buffer used during one TU walk. Each visited
+/// declaration contributes one record; the buffer is committed once per
+/// EntityId at the end of the walk.
+using LocationMap = std::map<EntityId, std::vector<SourceLocationRecord>>;
+
+/// Compute a SourceLocationRecord from \p Loc. Returns std::nullopt for
+/// invalid / system-header / unreachable-on-disk paths; these are silently
+/// dropped per the spec.
+std::optional<SourceLocationRecord> makeRecord(SourceLocation Loc,
+                                               const SourceManager &SM) {
+  if (!Loc.isValid() || SM.isInSystemHeader(Loc))
+    return std::nullopt;
+
+  PresumedLoc PLoc = SM.getPresumedLoc(Loc);
+  if (PLoc.isInvalid())
+    return std::nullopt;
+
+  llvm::StringRef Filename(PLoc.getFilename());
+  if (Filename.empty())
+    return std::nullopt;
+
+  // Path canonicalization touches the filesystem, which is denied under the
+  // sandboxed compile drivers (e.g. xcodebuild). Reading source files the
+  // compiler is already reading is benign, so disable the sandbox for the
+  // duration of these calls.
+  llvm::SmallString<256> Abs(Filename);
+  llvm::SmallString<256> Real;
+  {
+    auto SandboxOff = llvm::sys::sandbox::scopedDisable();
+    if (!llvm::sys::path::is_absolute(Abs))
+      if (auto EC = llvm::sys::fs::make_absolute(Abs))
+        return std::nullopt;
+    if (auto EC = llvm::sys::fs::real_path(Abs, Real))
+      return std::nullopt;
+  }
+
+  SourceLocationRecord R;
+  R.FilePath = Real.str().str();
+  R.Line = PLoc.getLine();
+  R.Column = PLoc.getColumn();
+  return R;
+}
+
+void handleNamedDecl(TUSummaryExtractor &Extractor, const NamedDecl *D,
+                     SourceLocation Loc, const SourceManager &SM,
+                     LocationMap &Records) {
+  auto Rec = makeRecord(Loc, SM);
+  if (!Rec)
+    return;
+  std::optional<EntityId> Id = Extractor.addEntity(D);
+  if (!Id)
+    return;
+  Records[*Id].push_back(std::move(*Rec));
+}
+
+void handleFunction(TUSummaryExtractor &Extractor, const FunctionDecl *FD,
+                    const SourceManager &SM, LocationMap &Records) {
+  handleNamedDecl(Extractor, FD, FD->getLocation(), SM, Records);
+
+  if (auto RetRec = makeRecord(FD->getReturnTypeSourceRange().getBegin(), SM))
+    if (auto RetId = Extractor.addEntityForReturn(FD))
+      Records[*RetId].push_back(std::move(*RetRec));
+
+  // The variadic '...' terminator is not represented by a ParmVarDecl in the
+  // AST, so this loop naturally skips it.
+  for (const ParmVarDecl *P : FD->parameters()) {
+    if (!P)
+      continue;
+    handleNamedDecl(Extractor, P, P->getTypeSpecStartLoc(), SM, Records);
+  }
+}
+
+/// AST visitor that records per-entity declaration source-locations as it
+/// walks. ParmVarDecls are visited as part of their parent FunctionDecl so
+/// the parameter-type-spec-start anchor is recorded once per redeclaration of
+/// the parent function.
+class EntityVisitor : public DynamicRecursiveASTVisitor {
+  TUSummaryExtractor &Extractor;
+  const SourceManager &SM;
+  LocationMap &Records;
+
+public:
+  EntityVisitor(TUSummaryExtractor &Extractor, const SourceManager &SM,
+                LocationMap &Records)
+      : Extractor(Extractor), SM(SM), Records(Records) {
+    ShouldVisitTemplateInstantiations = true;
+    ShouldVisitImplicitCode = false;
+  }
+
+  bool VisitFunctionDecl(FunctionDecl *FD) override {
+    if (FD)
+      handleFunction(Extractor, FD, SM, Records);
+    return true;
+  }
+
+  bool VisitVarDecl(VarDecl *VD) override {
+    if (VD && !isa<ParmVarDecl>(VD))
+      handleNamedDecl(Extractor, VD, VD->getLocation(), SM, Records);
+    return true;
+  }
+
+  bool VisitFieldDecl(FieldDecl *FD) override {
+    if (FD)
+      handleNamedDecl(Extractor, FD, FD->getLocation(), SM, Records);
+    return true;
+  }
+
+  bool VisitRecordDecl(RecordDecl *RD) override {
+    if (RD)
+      handleNamedDecl(Extractor, RD, RD->getLocation(), SM, Records);
+    return true;
+  }
+};
+
+class EntitySourceLocationExtractor final : public TUSummaryExtractor {
+public:
+  using TUSummaryExtractor::TUSummaryExtractor;
+
+private:
+  void HandleTranslationUnit(ASTContext &Ctx) override;
+};
+
+void EntitySourceLocationExtractor::HandleTranslationUnit(ASTContext &Ctx) {
+  const SourceManager &SM = Ctx.getSourceManager();
+  LocationMap Records;
+  EntityVisitor(*this, SM, Records).TraverseAST(Ctx);
+
+  for (auto &[Id, Locs] : Records) {
+    auto Summary = std::make_unique<EntitySourceLocationsSummary>(
+        buildEntitySourceLocationsSummary(std::move(Locs)));
+    [[maybe_unused]] auto [Ignored, Inserted] =
+        SummaryBuilder.addSummary(Id, std::move(Summary));
+    assert(Inserted &&
+           "EntitySourceLocations summary inserted twice for same EntityId");
+  }
+}
+
+} // namespace
+
+namespace clang::ssaf {
+// NOLINTNEXTLINE(misc-use-internal-linkage)
+volatile int EntitySourceLocationExtractorAnchorSource = 0;
+} // namespace clang::ssaf
+
+static clang::ssaf::TUSummaryExtractorRegistry::Add<
+    EntitySourceLocationExtractor>
+    RegisterExtractor(EntitySourceLocationsSummary::Name,
+                      "Extract per-entity declaration source-locations");
diff --git 
a/clang/unittests/ScalableStaticAnalysis/Analyses/SharedLexicalRepresentation/EntitySourceLocationExtractorTest.cpp
 
b/clang/unittests/ScalableStaticAnalysis/Analyses/SharedLexicalRepresentation/EntitySourceLocationExtractorTest.cpp
new file mode 100644
index 0000000000000..bdf162f8f2c04
--- /dev/null
+++ 
b/clang/unittests/ScalableStaticAnalysis/Analyses/SharedLexicalRepresentation/EntitySourceLocationExtractorTest.cpp
@@ -0,0 +1,479 @@
+//===- EntitySourceLocationExtractorTest.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 "TestFixture.h"
+
+#include "clang/AST/Decl.h"
+#include "clang/AST/DeclCXX.h"
+#include "clang/AST/DynamicRecursiveASTVisitor.h"
+#include "clang/Frontend/ASTUnit.h"
+#include "clang/Frontend/SSAFOptions.h"
+#include 
"clang/ScalableStaticAnalysis/Analyses/SharedLexicalRepresentation/SharedLexicalRepresentation.h"
+#include "clang/ScalableStaticAnalysis/Core/Model/EntityId.h"
+#include "clang/ScalableStaticAnalysis/Core/TUSummary/ExtractorRegistry.h"
+#include "clang/ScalableStaticAnalysis/Core/TUSummary/TUSummary.h"
+#include "clang/ScalableStaticAnalysis/Core/TUSummary/TUSummaryBuilder.h"
+#include "clang/Tooling/Tooling.h"
+#include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/SmallString.h"
+#include "llvm/ADT/StringRef.h"
+#include "llvm/Support/FileSystem.h"
+#include "llvm/Support/Path.h"
+#include "llvm/Support/raw_ostream.h"
+#include "gtest/gtest.h"
+
+#include <memory>
+#include <set>
+#include <string>
+#include <vector>
+
+namespace clang::ssaf {
+
+extern llvm::ArrayRef<SourceLocationRecord>
+getDeclLocations(const EntitySourceLocationsSummary &);
+
+namespace {
+
+class EntitySourceLocationExtractorTest : public TestFixture {
+protected:
+  using PathString = llvm::SmallString<128>;
+
+  PathString TestDir;
+  PathString SourceFile;
+  SSAFOptions Opts;
+  TUSummary TUSum;
+  TUSummaryBuilder Builder;
+  std::unique_ptr<TUSummaryExtractor> Extractor;
+  std::unique_ptr<ASTUnit> AST;
+
+  EntitySourceLocationExtractorTest()
+      : TUSum(llvm::Triple("arm64-apple-macosx"),
+              BuildNamespace(BuildNamespaceKind::CompilationUnit, "Mock.cpp")),
+        Builder(TUSum, Opts) {}
+
+  void SetUp() override {
+    std::error_code EC =
+        llvm::sys::fs::createUniqueDirectory("esl-extractor-test", TestDir);
+    ASSERT_FALSE(EC) << "Failed to create temp directory: " << EC.message();
+  }
+
+  void TearDown() override { llvm::sys::fs::remove_directories(TestDir); }
+
+  PathString makePath(llvm::StringRef RelPath) const {
+    PathString P = TestDir;
+    llvm::sys::path::append(P, RelPath);
+    return P;
+  }
+
+  PathString realPathOf(llvm::StringRef AbsPath) const {
+    PathString Real;
+    std::error_code EC = llvm::sys::fs::real_path(AbsPath, Real);
+    EXPECT_FALSE(EC) << "real_path failed for " << AbsPath.str() << ": "
+                     << EC.message();
+    return Real;
+  }
+
+  void writeFile(llvm::StringRef AbsPath, llvm::StringRef Content) const {
+    std::error_code EC;
+    llvm::raw_fd_ostream OS(AbsPath, EC);
+    ASSERT_FALSE(EC) << "Failed to open " << AbsPath.str() << ": "
+                     << EC.message();
+    OS << Content;
+  }
+
+  void runExtractor(llvm::StringRef SourceAbsPath, llvm::StringRef Code,
+                    std::vector<std::string> ExtraArgs = {}) {
+    SourceFile = SourceAbsPath;
+    AST = tooling::buildASTFromCodeWithArgs(Code, ExtraArgs, SourceAbsPath,
+                                            "clang-tool");
+    ASSERT_TRUE(AST) << "Failed to build AST";
+
+    for (auto &E : TUSummaryExtractorRegistry::entries()) {
+      if (E.getName() == EntitySourceLocationsSummary::Name) {
+        Extractor = E.instantiate(Builder);
+        break;
+      }
+    }
+    ASSERT_TRUE(Extractor) << "EntitySourceLocationExtractor not registered";
+    Extractor->HandleTranslationUnit(AST->getASTContext());
+  }
+
+  void setUpFromCode(llvm::StringRef Code,
+                     llvm::StringRef SourceName = "test.cpp",
+                     std::vector<std::string> ExtraArgs = {}) {
+    PathString AbsPath = makePath(SourceName);
+    writeFile(AbsPath, Code);
+    runExtractor(AbsPath, Code, std::move(ExtraArgs));
+  }
+
+  template <typename Pred> const NamedDecl *findDecl(Pred P) const {
+    class Finder : public DynamicRecursiveASTVisitor {
+    public:
+      Pred &Predicate;
+      const NamedDecl *Found = nullptr;
+      explicit Finder(Pred &P) : Predicate(P) {
+        ShouldVisitTemplateInstantiations = true;
+        ShouldVisitImplicitCode = false;
+      }
+      bool VisitNamedDecl(NamedDecl *D) override {
+        if (D && Predicate(D)) {
+          Found = D;
+          return false;
+        }
+        return true;
+      }
+    };
+    Finder F(P);
+    F.TraverseAST(AST->getASTContext());
+    return F.Found;
+  }
+
+  const NamedDecl *findDeclByName(llvm::StringRef Name) const {
+    return findDecl(
+        [&](const NamedDecl *D) { return D->getNameAsString() == Name.str(); 
});
+  }
+
+  std::vector<const FunctionDecl *>
+  findAllFunctions(llvm::StringRef Name) const {
+    class Finder : public DynamicRecursiveASTVisitor {
+    public:
+      std::string Name;
+      std::vector<const FunctionDecl *> Found;
+      explicit Finder(llvm::StringRef N) : Name(N.str()) {
+        ShouldVisitTemplateInstantiations = true;
+        ShouldVisitImplicitCode = false;
+      }
+      bool VisitFunctionDecl(FunctionDecl *FD) override {
+        if (FD && FD->getNameAsString() == Name)
+          Found.push_back(FD);
+        return true;
+      }
+    };
+    Finder F(Name);
+    F.TraverseAST(AST->getASTContext());
+    return F.Found;
+  }
+
+  llvm::ArrayRef<SourceLocationRecord> recordsFor(EntityId Id) const {
+    auto &Data = getData(TUSum);
+    auto It = Data.find(EntitySourceLocationsSummary::summaryName());
+    if (It == Data.end())
+      return {};
+    auto It2 = It->second.find(Id);
+    if (It2 == It->second.end())
+      return {};
+    return getDeclLocations(
+        *static_cast<const EntitySourceLocationsSummary *>(It2->second.get()));
+  }
+
+  llvm::ArrayRef<SourceLocationRecord>
+  recordsForDecl(const NamedDecl *D) const {
+    if (!D)
+      return {};
+    auto Id = Extractor->addEntity(D);
+    if (!Id)
+      return {};
+    return recordsFor(*Id);
+  }
+
+  llvm::ArrayRef<SourceLocationRecord>
+  recordsForReturn(const FunctionDecl *FD) const {
+    if (!FD)
+      return {};
+    auto Id = Extractor->addEntityForReturn(FD);
+    if (!Id)
+      return {};
+    return recordsFor(*Id);
+  }
+};
+
+TEST_F(EntitySourceLocationExtractorTest, IsExtractorRegistered) {
+  EXPECT_TRUE(isTUSummaryExtractorRegistered("EntitySourceLocations"));
+}
+
+TEST_F(EntitySourceLocationExtractorTest, SingleDecl) {
+  setUpFromCode("int *p;\n");
+
+  const auto *D = findDeclByName("p");
+  ASSERT_NE(D, nullptr);
+  auto Recs = recordsForDecl(D);
+  ASSERT_EQ(Recs.size(), 1u);
+  EXPECT_EQ(Recs[0].Line, 1u);
+  EXPECT_EQ(Recs[0].Column, 6u);
+  EXPECT_EQ(Recs[0].FilePath, realPathOf(SourceFile).str());
+}
+
+TEST_F(EntitySourceLocationExtractorTest, MultipleRedecls) {
+  setUpFromCode("extern int p;\n"
+                "int p = 0;\n");
+
+  const auto *D = findDeclByName("p");
+  ASSERT_NE(D, nullptr);
+  auto Recs = recordsForDecl(D);
+  ASSERT_EQ(Recs.size(), 2u);
+
+  EXPECT_EQ(Recs[0].FilePath, Recs[1].FilePath);
+  std::vector<std::pair<unsigned, unsigned>> LinesCols{
+      {Recs[0].Line, Recs[0].Column}, {Recs[1].Line, Recs[1].Column}};
+  llvm::sort(LinesCols);
+  EXPECT_EQ(LinesCols,
+            (std::vector<std::pair<unsigned, unsigned>>{{1u, 12u}, {2u, 5u}}));
+}
+
+TEST_F(EntitySourceLocationExtractorTest, MultiDeclarator) {
+  setUpFromCode("int *p, *q;\n");
+
+  const auto *DP = findDeclByName("p");
+  const auto *DQ = findDeclByName("q");
+  ASSERT_NE(DP, nullptr);
+  ASSERT_NE(DQ, nullptr);
+
+  auto RecP = recordsForDecl(DP);
+  auto RecQ = recordsForDecl(DQ);
+  ASSERT_EQ(RecP.size(), 1u);
+  ASSERT_EQ(RecQ.size(), 1u);
+
+  EXPECT_EQ(RecP[0].Line, RecQ[0].Line);
+  EXPECT_EQ(RecP[0].FilePath, RecQ[0].FilePath);
+  EXPECT_LT(RecP[0].Column, RecQ[0].Column);
+}
+
+TEST_F(EntitySourceLocationExtractorTest, HeaderIncluded) {
+  PathString HeaderPath = makePath("shared.h");
+  writeFile(HeaderPath, "static int x;\n");
+  PathString SrcPath = makePath("test.cpp");
+  std::string Code = "#include \"shared.h\"\n";
+  writeFile(SrcPath, Code);
+  runExtractor(SrcPath, Code, {("-I" + TestDir.str()).str()});
+
+  const auto *D = findDeclByName("x");
+  ASSERT_NE(D, nullptr);
+  auto Recs = recordsForDecl(D);
+  ASSERT_EQ(Recs.size(), 1u);
+  EXPECT_EQ(Recs[0].FilePath, realPathOf(HeaderPath).str());
+  EXPECT_EQ(Recs[0].Line, 1u);
+}
+
+TEST_F(EntitySourceLocationExtractorTest, RealPathSymlink) {
+#ifdef _WIN32
+  GTEST_SKIP() << "Symlink semantics differ on Windows.";
+#endif
+  PathString RealHeader = makePath("real-shared.h");
+  writeFile(RealHeader, "static int y;\n");
+  PathString SymlinkHeader = makePath("via-symlink.h");
+  std::error_code EC = llvm::sys::fs::create_link(RealHeader, SymlinkHeader);
+  if (EC)
+    GTEST_SKIP() << "Failed to create symlink: " << EC.message();
+
+  PathString SrcPath = makePath("symlink-test.cpp");
+  std::string Code = "#include \"via-symlink.h\"\n";
+  writeFile(SrcPath, Code);
+  runExtractor(SrcPath, Code, {("-I" + TestDir.str()).str()});
+
+  const auto *D = findDeclByName("y");
+  ASSERT_NE(D, nullptr);
+  auto Recs = recordsForDecl(D);
+  ASSERT_EQ(Recs.size(), 1u);
+  EXPECT_EQ(Recs[0].FilePath, realPathOf(RealHeader).str());
+}
+
+TEST_F(EntitySourceLocationExtractorTest, ReturnSlotRecord) {
+  setUpFromCode("int *foo();\n");
+
+  auto Funcs = findAllFunctions("foo");
+  ASSERT_EQ(Funcs.size(), 1u);
+  const FunctionDecl *FD = Funcs[0];
+
+  auto FuncRecs = recordsForDecl(FD);
+  ASSERT_EQ(FuncRecs.size(), 1u);
+  EXPECT_EQ(FuncRecs[0].Line, 1u);
+  EXPECT_EQ(FuncRecs[0].Column, 6u);
+
+  auto RetRecs = recordsForReturn(FD);
+  ASSERT_EQ(RetRecs.size(), 1u);
+  EXPECT_EQ(RetRecs[0].Line, 1u);
+  EXPECT_EQ(RetRecs[0].Column, 1u);
+}
+
+TEST_F(EntitySourceLocationExtractorTest, NamedParamTypeSpecStart) {
+  setUpFromCode("void foo(int *p);\n");
+
+  auto Funcs = findAllFunctions("foo");
+  ASSERT_EQ(Funcs.size(), 1u);
+  const FunctionDecl *FD = Funcs[0];
+  ASSERT_EQ(FD->getNumParams(), 1u);
+  const ParmVarDecl *P = FD->getParamDecl(0);
+
+  auto Recs = recordsForDecl(P);
+  ASSERT_EQ(Recs.size(), 1u);
+  EXPECT_EQ(Recs[0].Line, 1u);
+  EXPECT_EQ(Recs[0].Column, 10u);
+}
+
+TEST_F(EntitySourceLocationExtractorTest, UnnamedParamTypeSpecStart) {
+  setUpFromCode("void foo(int *);\n");
+
+  auto Funcs = findAllFunctions("foo");
+  ASSERT_EQ(Funcs.size(), 1u);
+  const FunctionDecl *FD = Funcs[0];
+  ASSERT_EQ(FD->getNumParams(), 1u);
+  const ParmVarDecl *P = FD->getParamDecl(0);
+
+  auto Recs = recordsForDecl(P);
+  ASSERT_EQ(Recs.size(), 1u);
+  EXPECT_EQ(Recs[0].Line, 1u);
+  EXPECT_EQ(Recs[0].Column, 10u);
+}
+
+TEST_F(EntitySourceLocationExtractorTest, FunctionMultiRedecl) {
+  setUpFromCode("int *foo(int *p);\n"
+                "\n"
+                "int *foo(int *p) { return p; }\n");
+
+  auto Funcs = findAllFunctions("foo");
+  ASSERT_EQ(Funcs.size(), 2u);
+  const FunctionDecl *FD0 = Funcs[0];
+  const FunctionDecl *FD1 = Funcs[1];
+  ASSERT_EQ(FD0->getNumParams(), 1u);
+
+  auto FuncRecs = recordsForDecl(FD0);
+  EXPECT_EQ(FuncRecs.size(), 2u);
+
+  auto RetRecs = recordsForReturn(FD0);
+  EXPECT_EQ(RetRecs.size(), 2u);
+
+  auto P0Id = Extractor->addEntity(FD0->getParamDecl(0));
+  auto P1Id = Extractor->addEntity(FD1->getParamDecl(0));
+  ASSERT_TRUE(P0Id.has_value());
+  ASSERT_TRUE(P1Id.has_value());
+  EXPECT_EQ(*P0Id, *P1Id);
+  auto ParmRecs = recordsFor(*P0Id);
+  EXPECT_EQ(ParmRecs.size(), 2u);
+
+  std::vector<unsigned> FuncLines{FuncRecs[0].Line, FuncRecs[1].Line};
+  llvm::sort(FuncLines);
+  EXPECT_EQ(FuncLines, (std::vector<unsigned>{1u, 3u}));
+}
+
+TEST_F(EntitySourceLocationExtractorTest, TemplateInstantiationsShareLoc) {
+  setUpFromCode("template <class T> void f(T *p) {}\n"
+                "void use() {\n"
+                "  f<int>(nullptr);\n"
+                "  f<long>(nullptr);\n"
+                "}\n");
+
+  auto Funcs = findAllFunctions("f");
+  ASSERT_GE(Funcs.size(), 3u);
+
+  std::set<EntityId> DistinctParmIds;
+  std::vector<SourceLocationRecord> ParamLocs;
+  for (const FunctionDecl *FD : Funcs) {
+    if (FD->getNumParams() != 1u)
+      continue;
+    auto Id = Extractor->addEntity(FD->getParamDecl(0));
+    if (!Id)
+      continue;
+    DistinctParmIds.insert(*Id);
+    auto Recs = recordsFor(*Id);
+    if (Recs.empty())
+      continue;
+    ParamLocs.push_back(Recs.front());
+  }
+  ASSERT_GE(DistinctParmIds.size(), 2u);
+  ASSERT_GE(ParamLocs.size(), 2u);
+
+  const SourceLocationRecord &Anchor = ParamLocs.front();
+  for (const auto &R : ParamLocs)
+    EXPECT_EQ(R, Anchor);
+}
+
+TEST_F(EntitySourceLocationExtractorTest, ImplicitDeclSkipped) {
+  setUpFromCode("struct S { int *p; };\n"
+                "void use() { S s; (void)s; }\n");
+
+  const auto *DS = findDeclByName("S");
+  const auto *DP = findDeclByName("p");
+  ASSERT_NE(DS, nullptr);
+  ASSERT_NE(DP, nullptr);
+  EXPECT_FALSE(recordsForDecl(DS).empty());
+  EXPECT_FALSE(recordsForDecl(DP).empty());
+
+  class ImplicitMethodFinder : public DynamicRecursiveASTVisitor {
+  public:
+    std::vector<const CXXMethodDecl *> ImplicitMethods;
+    ImplicitMethodFinder() {
+      ShouldVisitTemplateInstantiations = true;
+      ShouldVisitImplicitCode = true;
+    }
+    bool VisitCXXMethodDecl(CXXMethodDecl *MD) override {
+      if (MD && MD->isImplicit() && MD->getParent() &&
+          MD->getParent()->getNameAsString() == "S")
+        ImplicitMethods.push_back(MD);
+      return true;
+    }
+  };
+  ImplicitMethodFinder F;
+  F.TraverseAST(AST->getASTContext());
+
+  ASSERT_FALSE(F.ImplicitMethods.empty());
+  for (const CXXMethodDecl *MD : F.ImplicitMethods) {
+    EXPECT_FALSE(Extractor->addEntity(MD).has_value());
+    EXPECT_TRUE(recordsForDecl(MD).empty());
+  }
+}
+
+TEST_F(EntitySourceLocationExtractorTest, VariadicTerminatorNotEmitted) {
+  setUpFromCode("int my_printf(const char *fmt, ...);\n");
+
+  auto Funcs = findAllFunctions("my_printf");
+  ASSERT_EQ(Funcs.size(), 1u);
+  const FunctionDecl *FD = Funcs[0];
+  ASSERT_EQ(FD->getNumParams(), 1u);
+  EXPECT_TRUE(FD->isVariadic());
+
+  EXPECT_FALSE(recordsForDecl(FD).empty());
+  EXPECT_FALSE(recordsForReturn(FD).empty());
+  EXPECT_FALSE(recordsForDecl(FD->getParamDecl(0)).empty());
+
+  auto FuncId = Extractor->addEntity(FD);
+  auto RetId = Extractor->addEntityForReturn(FD);
+  auto ParmId = Extractor->addEntity(FD->getParamDecl(0));
+  ASSERT_TRUE(FuncId.has_value());
+  ASSERT_TRUE(RetId.has_value());
+  ASSERT_TRUE(ParmId.has_value());
+  std::set<EntityId> Distinct{*FuncId, *RetId, *ParmId};
+  EXPECT_EQ(Distinct.size(), 3u);
+}
+
+TEST_F(EntitySourceLocationExtractorTest, SystemHeaderDeclSkipped) {
+  PathString SysDir = makePath("sysinc");
+  std::error_code EC = llvm::sys::fs::create_directory(SysDir);
+  ASSERT_FALSE(EC) << EC.message();
+  PathString SysHeader = SysDir;
+  llvm::sys::path::append(SysHeader, "sysheader.h");
+  writeFile(SysHeader, "static int sys_x;\n");
+
+  PathString SrcPath = makePath("sysheader-test.cpp");
+  std::string Code = "#include <sysheader.h>\n"
+                     "int local_y;\n";
+  writeFile(SrcPath, Code);
+  runExtractor(SrcPath, Code, {("-isystem" + SysDir.str()).str()});
+
+  const auto *DSys = findDeclByName("sys_x");
+  ASSERT_NE(DSys, nullptr);
+  EXPECT_TRUE(recordsForDecl(DSys).empty())
+      << "system-header decl should not have a recorded source location";
+
+  const auto *DLocal = findDeclByName("local_y");
+  ASSERT_NE(DLocal, nullptr);
+  EXPECT_FALSE(recordsForDecl(DLocal).empty());
+}
+
+} // namespace
+
+} // namespace clang::ssaf
diff --git a/clang/unittests/ScalableStaticAnalysis/CMakeLists.txt 
b/clang/unittests/ScalableStaticAnalysis/CMakeLists.txt
index 47df5e6cc6a84..ed3b57168b069 100644
--- a/clang/unittests/ScalableStaticAnalysis/CMakeLists.txt
+++ b/clang/unittests/ScalableStaticAnalysis/CMakeLists.txt
@@ -3,6 +3,7 @@ add_distinct_clang_unittest(ClangScalableAnalysisTests
   Analyses/PointerFlow/PointerFlowWPATest.cpp
   Analyses/CallGraph/CallGraphExtractorTest.cpp
   Analyses/TypeConstrainedPointers/TypeConstrainedPointersExtractorTest.cpp
+  Analyses/SharedLexicalRepresentation/EntitySourceLocationExtractorTest.cpp
   Analyses/UnsafeBufferUsage/UnsafeBufferUsageTest.cpp
   Analyses/UnsafeBufferUsage/UnsafeBufferUsageWPATest.cpp
   ASTEntityMappingTest.cpp

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

Reply via email to