[clang] [SSAF] Add EntitySourceLocationExtractor (PR #210201)
Jan Korous via cfe-commits
cfe-commits at lists.llvm.org
Thu Jul 16 16:57:45 PDT 2026
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 <jkorous at apple.com>
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
More information about the cfe-commits
mailing list