[clang] [llvm] [llvm][support] Implement `sys::path::starts_with()` (PR #198333)
Jan Svoboda via cfe-commits
cfe-commits at lists.llvm.org
Tue May 19 13:06:29 PDT 2026
https://github.com/jansvoboda11 updated https://github.com/llvm/llvm-project/pull/198333
>From 51544680c5ec667cf32139258e0557d6fb230dd5 Mon Sep 17 00:00:00 2001
From: Jan Svoboda <jan_svoboda at apple.com>
Date: Mon, 18 May 2026 08:50:36 -0700
Subject: [PATCH 1/7] [llvm][support] Implement `sys::path::starts_with()`
This PR adds a new function that checks whether a path starts with a specified prefix. Unlike `StringRef::starts_with()`, this requires an exact match of the last component, and ignores case and separator differences on Windows.
This is adopted by Clang's dependency scanner, where the iterator-based prefix matching showed up in the traces (when the `InProcessModuleCache` was already populated).
---
.../DependencyScanning/ModuleDepCollector.cpp | 12 +------
llvm/include/llvm/Support/Path.h | 11 ++++++
llvm/lib/Support/Path.cpp | 34 +++++++++++++++++--
llvm/unittests/Support/Path.cpp | 10 ++++++
4 files changed, 53 insertions(+), 14 deletions(-)
diff --git a/clang/lib/DependencyScanning/ModuleDepCollector.cpp b/clang/lib/DependencyScanning/ModuleDepCollector.cpp
index f9fe50564a5ae3..cf23b1f6ec0130 100644
--- a/clang/lib/DependencyScanning/ModuleDepCollector.cpp
+++ b/clang/lib/DependencyScanning/ModuleDepCollector.cpp
@@ -200,18 +200,8 @@ bool dependencies::isPathInStableDir(const ArrayRef<StringRef> Directories,
if (!path::is_absolute(Input))
return false;
- auto PathStartsWith = [](StringRef Prefix, StringRef Path) {
- auto PrefixIt = path::begin(Prefix), PrefixEnd = path::end(Prefix);
- for (auto PathIt = path::begin(Path), PathEnd = path::end(Path);
- PrefixIt != PrefixEnd && PathIt != PathEnd; ++PrefixIt, ++PathIt) {
- if (*PrefixIt != *PathIt)
- return false;
- }
- return PrefixIt == PrefixEnd;
- };
-
return any_of(Directories, [&](StringRef Dir) {
- return !Dir.empty() && PathStartsWith(Dir, Input);
+ return !Dir.empty() && path::starts_with(Input, Dir);
});
}
diff --git a/llvm/include/llvm/Support/Path.h b/llvm/include/llvm/Support/Path.h
index 28e1e8ba3ae36c..a8a3c49500a1c5 100644
--- a/llvm/include/llvm/Support/Path.h
+++ b/llvm/include/llvm/Support/Path.h
@@ -174,6 +174,17 @@ LLVM_ABI void replace_extension(SmallVectorImpl<char> &path,
const Twine &extension,
Style style = Style::native);
+/// Check whether \a Path starts with \a Prefix.
+///
+/// Unlike \c StringRef::starts_with, this takes care to check for exact match
+/// of the last component in order to correctly return false for path "/foo/bar"
+/// and prefix "/foo/b", which are likely different file system entities.
+///
+/// On Windows, this also ignores path separator differences and upper/lower
+/// case differences.
+LLVM_ABI bool starts_with(StringRef Path, StringRef Prefix,
+ Style style = Style::native);
+
/// Replace matching path prefix with another path.
///
/// @code
diff --git a/llvm/lib/Support/Path.cpp b/llvm/lib/Support/Path.cpp
index 457499b93362df..a15be5a4fc5bd0 100644
--- a/llvm/lib/Support/Path.cpp
+++ b/llvm/lib/Support/Path.cpp
@@ -497,8 +497,36 @@ void replace_extension(SmallVectorImpl<char> &path, const Twine &extension,
path.append(ext.begin(), ext.end());
}
-static bool starts_with(StringRef Path, StringRef Prefix,
- Style style = Style::native) {
+bool starts_with(StringRef Path, StringRef Prefix, Style style) {
+ auto ConsumeFront = [&]() {
+ if (Path.consume_front(Prefix))
+ return true;
+ // Windows prefix matching : case and separator insensitive
+ if (is_style_windows(style)) {
+ if (Path.size() < Prefix.size())
+ return false;
+ for (size_t I = 0, E = Prefix.size(); I != E; ++I) {
+ bool SepPath = is_separator(Path[I], style);
+ bool SepPrefix = is_separator(Prefix[I], style);
+ if (SepPath != SepPrefix)
+ return false;
+ if (!SepPath && toLower(Path[I]) != toLower(Prefix[I]))
+ return false;
+ }
+ Path = Path.drop_front(Prefix.size());
+ return true;
+ }
+ return false;
+ };
+ // The path must start with the prefix and last matching component must match
+ // in its entirety, not just the prefix.
+ return ConsumeFront() && (Path.empty() || is_separator(Path[0], style));
+}
+
+// Unlike \c starts_with, this returns true even when the last component is just
+// a prefix match and not an exact match.
+static bool starts_with_relaxed(StringRef Path, StringRef Prefix,
+ Style style = Style::native) {
// Windows prefix matching : case and separator insensitive
if (is_style_windows(style)) {
if (Path.size() < Prefix.size())
@@ -522,7 +550,7 @@ bool replace_path_prefix(SmallVectorImpl<char> &Path, StringRef OldPrefix,
return false;
StringRef OrigPath(Path.begin(), Path.size());
- if (!starts_with(OrigPath, OldPrefix, style))
+ if (!starts_with_relaxed(OrigPath, OldPrefix, style))
return false;
// If prefixes have the same size we can simply copy the new one over.
diff --git a/llvm/unittests/Support/Path.cpp b/llvm/unittests/Support/Path.cpp
index bb825a1b1e65ce..f9d10466d67943 100644
--- a/llvm/unittests/Support/Path.cpp
+++ b/llvm/unittests/Support/Path.cpp
@@ -441,6 +441,16 @@ TEST(Support, AbsolutePathIteratorEnd) {
}
}
+TEST(Support, PathStartsWith) {
+ EXPECT_TRUE(path::starts_with("/foo", "/foo"));
+ EXPECT_TRUE(path::starts_with("/foo/", "/foo"));
+ // FIXME: This should probably work too.
+ // EXPECT_TRUE(path::starts_with("/foo", "/foo/"));
+ EXPECT_TRUE(path::starts_with("/foo/", "/foo/"));
+ EXPECT_FALSE(path::starts_with("/foo", "/fooo"));
+ EXPECT_FALSE(path::starts_with("/fooo", "/foo"));
+}
+
#ifdef _WIN32
std::string getEnvWin(const wchar_t *Var) {
std::string expected;
>From 8b326411aa6fcd9572cee1b1a1a86e26a237f1fa Mon Sep 17 00:00:00 2001
From: Jan Svoboda <jan_svoboda at apple.com>
Date: Mon, 18 May 2026 11:29:08 -0700
Subject: [PATCH 2/7] Only handle normalized paths
---
.../DependencyScannerImpl.h | 6 ++--
.../DependencyScanning/ModuleDepCollector.h | 8 ++---
.../clang/Tooling/DependencyScanningTool.h | 2 +-
.../DependencyScannerImpl.cpp | 33 ++++++++++++-------
.../DependencyScanning/ModuleDepCollector.cpp | 11 ++++---
llvm/include/llvm/Support/Path.h | 4 +--
llvm/lib/Support/Path.cpp | 28 ++++------------
7 files changed, 44 insertions(+), 48 deletions(-)
diff --git a/clang/include/clang/DependencyScanning/DependencyScannerImpl.h b/clang/include/clang/DependencyScanning/DependencyScannerImpl.h
index 893017e68233b2..96cfd50a9d4f1a 100644
--- a/clang/include/clang/DependencyScanning/DependencyScannerImpl.h
+++ b/clang/include/clang/DependencyScanning/DependencyScannerImpl.h
@@ -107,12 +107,12 @@ void initializeScanCompilerInstance(
DiagnosticConsumer *DiagConsumer, DependencyScanningService &Service,
IntrusiveRefCntPtr<DependencyScanningWorkerFilesystem> DepFS);
-SmallVector<StringRef>
+SmallVector<SmallString<0>, 2>
getInitialStableDirs(const CompilerInstance &ScanInstance);
std::optional<PrebuiltModulesAttrsMap>
computePrebuiltModulesASTMap(CompilerInstance &ScanInstance,
- SmallVector<StringRef> &StableDirs);
+ ArrayRef<SmallString<0>> StableDirs);
/// Create the dependency collector that will collect the produced
/// dependencies. May return the created ModuleDepCollector depending
@@ -123,7 +123,7 @@ std::shared_ptr<ModuleDepCollector> initializeScanInstanceDependencyCollector(
DependencyScanningService &Service, CompilerInvocation &Inv,
DependencyActionController &Controller,
PrebuiltModulesAttrsMap PrebuiltModulesASTMap,
- SmallVector<StringRef> &StableDirs);
+ SmallVector<SmallString<0>, 2> StableDirs);
} // namespace dependencies
} // namespace clang
diff --git a/clang/include/clang/DependencyScanning/ModuleDepCollector.h b/clang/include/clang/DependencyScanning/ModuleDepCollector.h
index 4713cbd9387ecc..f54b9ccf61b378 100644
--- a/clang/include/clang/DependencyScanning/ModuleDepCollector.h
+++ b/clang/include/clang/DependencyScanning/ModuleDepCollector.h
@@ -86,7 +86,7 @@ class ModuleDepCollector final : public DependencyCollector {
DependencyActionController &Controller,
CompilerInvocation OriginalCI,
const PrebuiltModulesAttrsMap PrebuiltModulesASTMap,
- const ArrayRef<StringRef> StableDirs);
+ SmallVector<SmallString<0>, 2> StableDirs);
/// Processes the accumulated dependency information and reports it to the
/// \c Consumer.
@@ -115,7 +115,7 @@ class ModuleDepCollector final : public DependencyCollector {
const PrebuiltModulesAttrsMap PrebuiltModulesASTMap;
/// Directory paths known to be stable through an active development and build
/// cycle.
- const ArrayRef<StringRef> StableDirs;
+ SmallVector<SmallString<0>, 2> StableDirs;
/// Path to the main source file.
std::string MainFile;
/// Non-modular file dependencies. This includes the main source file and
@@ -210,7 +210,7 @@ void resetBenignCodeGenOptions(frontend::ActionKind ProgramAction,
///
/// \param Directories Paths known to be in a stable location. e.g. Sysroot.
/// \param Input Path to evaluate.
-bool isPathInStableDir(const ArrayRef<StringRef> Directories,
+bool isPathInStableDir(const ArrayRef<SmallString<0>> Directories,
const StringRef Input);
/// Determine if options collected from a module's
@@ -218,7 +218,7 @@ bool isPathInStableDir(const ArrayRef<StringRef> Directories,
///
/// \param Directories Paths known to be in a stable location. e.g. Sysroot.
/// \param HSOpts Header search options derived from the compiler invocation.
-bool areOptionsInStableDir(const ArrayRef<StringRef> Directories,
+bool areOptionsInStableDir(const ArrayRef<SmallString<0>> Directories,
const HeaderSearchOptions &HSOpts);
} // end namespace dependencies
diff --git a/clang/include/clang/Tooling/DependencyScanningTool.h b/clang/include/clang/Tooling/DependencyScanningTool.h
index 90216d8f1da82f..38e7f8aea9d38c 100644
--- a/clang/include/clang/Tooling/DependencyScanningTool.h
+++ b/clang/include/clang/Tooling/DependencyScanningTool.h
@@ -162,7 +162,7 @@ class CompilerInstanceWithContext {
std::unique_ptr<DependencyOutputOptions> OutputOpts;
// Context - stable directory handling
- llvm::SmallVector<StringRef> StableDirs;
+ SmallVector<SmallString<0>, 2> StableDirs;
dependencies::PrebuiltModulesAttrsMap PrebuiltModuleASTMap;
// Compiler Instance
diff --git a/clang/lib/DependencyScanning/DependencyScannerImpl.cpp b/clang/lib/DependencyScanning/DependencyScannerImpl.cpp
index 42f87adba84c9f..2686d8dc47893f 100644
--- a/clang/lib/DependencyScanning/DependencyScannerImpl.cpp
+++ b/clang/lib/DependencyScanning/DependencyScannerImpl.cpp
@@ -67,7 +67,7 @@ class PrebuiltModuleListener : public ASTReaderListener {
PrebuiltModulesAttrsMap &PrebuiltModulesASTMap,
const HeaderSearchOptions &HSOpts,
const LangOptions &LangOpts, DiagnosticsEngine &Diags,
- const ArrayRef<StringRef> StableDirs)
+ const ArrayRef<SmallString<0>> StableDirs)
: PrebuiltModuleFiles(PrebuiltModuleFiles),
NewModuleFiles(NewModuleFiles),
PrebuiltModulesASTMap(PrebuiltModulesASTMap), ExistingHSOpts(HSOpts),
@@ -166,7 +166,7 @@ class PrebuiltModuleListener : public ASTReaderListener {
const LangOptions &ExistingLangOpts;
DiagnosticsEngine &Diags;
std::string CurrentFile;
- const ArrayRef<StringRef> StableDirs;
+ const ArrayRef<SmallString<0>> StableDirs;
};
/// Visit the given prebuilt module and collect all of the modules it
@@ -176,7 +176,7 @@ static bool visitPrebuiltModule(StringRef PrebuiltModuleFilename,
PrebuiltModuleFilesT &ModuleFiles,
PrebuiltModulesAttrsMap &PrebuiltModulesASTMap,
DiagnosticsEngine &Diags,
- const ArrayRef<StringRef> StableDirs) {
+ const ArrayRef<SmallString<0>> StableDirs) {
// List of module files to be processed.
llvm::SmallVector<std::string> Worklist;
@@ -459,21 +459,29 @@ std::shared_ptr<CompilerInvocation> dependencies::createScanCompilerInvocation(
return ScanInvocation;
}
-llvm::SmallVector<StringRef>
+SmallVector<SmallString<0>, 2>
dependencies::getInitialStableDirs(const CompilerInstance &ScanInstance) {
// Create a collection of stable directories derived from the ScanInstance
// for determining whether module dependencies would fully resolve from
// those directories.
- llvm::SmallVector<StringRef> StableDirs;
+ SmallVector<SmallString<0>, 2> StableDirs;
const StringRef Sysroot = ScanInstance.getHeaderSearchOpts().Sysroot;
- if (!Sysroot.empty() && (llvm::sys::path::root_directory(Sysroot) != Sysroot))
- StableDirs = {Sysroot, ScanInstance.getHeaderSearchOpts().ResourceDir};
+ if (!Sysroot.empty() && llvm::sys::path::root_directory(Sysroot) != Sysroot) {
+ SmallString<0> SysrootBuf = Sysroot;
+ llvm::sys::path::remove_dots(SysrootBuf);
+ StableDirs.emplace_back(std::move(SysrootBuf));
+
+ SmallString<0> ResourceDirBuf{
+ ScanInstance.getHeaderSearchOpts().ResourceDir};
+ llvm::sys::path::remove_dots(ResourceDirBuf);
+ StableDirs.emplace_back(std::move(ResourceDirBuf));
+ }
return StableDirs;
}
std::optional<PrebuiltModulesAttrsMap>
dependencies::computePrebuiltModulesASTMap(
- CompilerInstance &ScanInstance, llvm::SmallVector<StringRef> &StableDirs) {
+ CompilerInstance &ScanInstance, ArrayRef<SmallString<0>> StableDirs) {
// Store a mapping of prebuilt module files and their properties like header
// search options. This will prevent the implicit build to create duplicate
// modules and will force reuse of the existing prebuilt module files
@@ -512,10 +520,10 @@ dependencies::initializeScanInstanceDependencyCollector(
DependencyScanningService &Service, CompilerInvocation &Inv,
DependencyActionController &Controller,
PrebuiltModulesAttrsMap PrebuiltModulesASTMap,
- SmallVector<StringRef> &StableDirs) {
+ SmallVector<SmallString<0>, 2> StableDirs) {
auto MDC = std::make_shared<ModuleDepCollector>(
Service, std::move(DepOutputOpts), ScanInstance, Controller, Inv,
- std::move(PrebuiltModulesASTMap), StableDirs);
+ std::move(PrebuiltModulesASTMap), std::move(StableDirs));
ScanInstance.addDependencyCollector(MDC);
return MDC;
}
@@ -737,7 +745,7 @@ bool DependencyScanningAction::runInvocation(
DepFS);
// FIXME: Do this only once.
- SmallVector<StringRef> StableDirs = getInitialStableDirs(ScanInstance);
+ auto StableDirs = getInitialStableDirs(ScanInstance);
auto MaybePrebuiltModulesASTMap =
computePrebuiltModulesASTMap(ScanInstance, StableDirs);
if (!MaybePrebuiltModulesASTMap)
@@ -760,7 +768,8 @@ bool DependencyScanningAction::runInvocation(
initializeScanCompilerInstance(ScanInstance, FS, DiagConsumer, Service,
DepFS);
- llvm::SmallVector<StringRef> StableDirs = getInitialStableDirs(ScanInstance);
+ SmallVector<SmallString<0>, 2> StableDirs =
+ getInitialStableDirs(ScanInstance);
auto MaybePrebuiltModulesASTMap =
computePrebuiltModulesASTMap(ScanInstance, StableDirs);
if (!MaybePrebuiltModulesASTMap)
diff --git a/clang/lib/DependencyScanning/ModuleDepCollector.cpp b/clang/lib/DependencyScanning/ModuleDepCollector.cpp
index cf23b1f6ec0130..9712803c1abbb8 100644
--- a/clang/lib/DependencyScanning/ModuleDepCollector.cpp
+++ b/clang/lib/DependencyScanning/ModuleDepCollector.cpp
@@ -193,7 +193,7 @@ void dependencies::resetBenignCodeGenOptions(frontend::ActionKind ProgramAction,
}
}
-bool dependencies::isPathInStableDir(const ArrayRef<StringRef> Directories,
+bool dependencies::isPathInStableDir(const ArrayRef<SmallString<0>> Directories,
const StringRef Input) {
using namespace llvm::sys;
@@ -205,8 +205,9 @@ bool dependencies::isPathInStableDir(const ArrayRef<StringRef> Directories,
});
}
-bool dependencies::areOptionsInStableDir(const ArrayRef<StringRef> Directories,
- const HeaderSearchOptions &HSOpts) {
+bool dependencies::areOptionsInStableDir(
+ const ArrayRef<SmallString<0>> Directories,
+ const HeaderSearchOptions &HSOpts) {
assert(isPathInStableDir(Directories, HSOpts.Sysroot) &&
"Sysroots differ between module dependencies and current TU");
@@ -849,10 +850,10 @@ ModuleDepCollector::ModuleDepCollector(
CompilerInstance &ScanInstance, DependencyActionController &Controller,
CompilerInvocation OriginalCI,
const PrebuiltModulesAttrsMap PrebuiltModulesASTMap,
- const ArrayRef<StringRef> StableDirs)
+ SmallVector<SmallString<0>, 2> StableDirs)
: Service(Service), ScanInstance(ScanInstance), Controller(Controller),
PrebuiltModulesASTMap(std::move(PrebuiltModulesASTMap)),
- StableDirs(StableDirs), Opts(std::move(Opts)),
+ StableDirs(std::move(StableDirs)), Opts(std::move(Opts)),
CommonInvocation(
makeCommonInvocationForModuleBuild(std::move(OriginalCI))) {}
diff --git a/llvm/include/llvm/Support/Path.h b/llvm/include/llvm/Support/Path.h
index a8a3c49500a1c5..75edd1a9489643 100644
--- a/llvm/include/llvm/Support/Path.h
+++ b/llvm/include/llvm/Support/Path.h
@@ -180,8 +180,8 @@ LLVM_ABI void replace_extension(SmallVectorImpl<char> &path,
/// of the last component in order to correctly return false for path "/foo/bar"
/// and prefix "/foo/b", which are likely different file system entities.
///
-/// On Windows, this also ignores path separator differences and upper/lower
-/// case differences.
+/// Both \a Path and \a Prefix need to be normalized - without './' components
+/// and with consistent separators.
LLVM_ABI bool starts_with(StringRef Path, StringRef Prefix,
Style style = Style::native);
diff --git a/llvm/lib/Support/Path.cpp b/llvm/lib/Support/Path.cpp
index a15be5a4fc5bd0..d051904152bab6 100644
--- a/llvm/lib/Support/Path.cpp
+++ b/llvm/lib/Support/Path.cpp
@@ -497,30 +497,16 @@ void replace_extension(SmallVectorImpl<char> &path, const Twine &extension,
path.append(ext.begin(), ext.end());
}
+template <typename T> static T &asLValue(T &&RValue) { return RValue; }
+
bool starts_with(StringRef Path, StringRef Prefix, Style style) {
- auto ConsumeFront = [&]() {
- if (Path.consume_front(Prefix))
- return true;
- // Windows prefix matching : case and separator insensitive
- if (is_style_windows(style)) {
- if (Path.size() < Prefix.size())
- return false;
- for (size_t I = 0, E = Prefix.size(); I != E; ++I) {
- bool SepPath = is_separator(Path[I], style);
- bool SepPrefix = is_separator(Prefix[I], style);
- if (SepPath != SepPrefix)
- return false;
- if (!SepPath && toLower(Path[I]) != toLower(Prefix[I]))
- return false;
- }
- Path = Path.drop_front(Prefix.size());
- return true;
- }
- return false;
- };
+ assert(!remove_dots(asLValue(SmallString<128>{Path}), false, style));
+ assert(!remove_dots(asLValue(SmallString<128>{Prefix}), false, style));
+
// The path must start with the prefix and last matching component must match
// in its entirety, not just the prefix.
- return ConsumeFront() && (Path.empty() || is_separator(Path[0], style));
+ return Path.consume_front(Prefix) &&
+ (Path.empty() || is_separator(Path[0], style));
}
// Unlike \c starts_with, this returns true even when the last component is just
>From be4efd6ad2f329bd7c6b015a872c0de2d147161b Mon Sep 17 00:00:00 2001
From: Jan Svoboda <jan_svoboda at apple.com>
Date: Mon, 18 May 2026 13:27:27 -0700
Subject: [PATCH 3/7] Prevent assertion of trailing separators, adjust
InMemoryFileSystem
---
llvm/lib/Support/Path.cpp | 3 ---
llvm/lib/Support/VirtualFileSystem.cpp | 3 +++
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/llvm/lib/Support/Path.cpp b/llvm/lib/Support/Path.cpp
index d051904152bab6..26746d5dff80ac 100644
--- a/llvm/lib/Support/Path.cpp
+++ b/llvm/lib/Support/Path.cpp
@@ -806,9 +806,6 @@ bool remove_dots(SmallVectorImpl<char> &the_path, bool remove_dot_dot,
if (!remaining.empty()) {
needs_change |= remaining.front() != preferred_separator(style);
remaining = remaining.drop_front();
- // The path needs to be rewritten if it has a trailing slash.
- // FIXME: This is emergent behavior that could be removed.
- needs_change |= remaining.empty();
}
// Check for path traversal components or double separators.
diff --git a/llvm/lib/Support/VirtualFileSystem.cpp b/llvm/lib/Support/VirtualFileSystem.cpp
index 69f3bb8582b87c..02778f28f90447 100644
--- a/llvm/lib/Support/VirtualFileSystem.cpp
+++ b/llvm/lib/Support/VirtualFileSystem.cpp
@@ -1018,6 +1018,9 @@ InMemoryFileSystem::lookupNode(const Twine &P, bool FollowFinalSymlink,
auto I = llvm::sys::path::begin(Path), E = llvm::sys::path::end(Path);
while (true) {
+ if (*I == ".")
+ return detail::NamedNodeOrError(Path, Dir);
+
detail::InMemoryNode *Node = Dir->getChild(*I);
++I;
if (!Node)
>From a49be930c427c669f7ca953a45fa3ac3a67c3709 Mon Sep 17 00:00:00 2001
From: Jan Svoboda <jan_svoboda at apple.com>
Date: Tue, 19 May 2026 11:17:44 -0700
Subject: [PATCH 4/7] Undo Clang change
---
.../DependencyScannerImpl.h | 9 ++---
.../DependencyScanning/ModuleDepCollector.h | 10 +++---
.../clang/Tooling/DependencyScanningTool.h | 4 ++-
.../DependencyScannerImpl.cpp | 34 +++++++++++--------
.../DependencyScanning/ModuleDepCollector.cpp | 11 +++---
clang/lib/Tooling/DependencyScanningTool.cpp | 2 +-
6 files changed, 39 insertions(+), 31 deletions(-)
diff --git a/clang/include/clang/DependencyScanning/DependencyScannerImpl.h b/clang/include/clang/DependencyScanning/DependencyScannerImpl.h
index 96cfd50a9d4f1a..daa01a28853797 100644
--- a/clang/include/clang/DependencyScanning/DependencyScannerImpl.h
+++ b/clang/include/clang/DependencyScanning/DependencyScannerImpl.h
@@ -107,12 +107,13 @@ void initializeScanCompilerInstance(
DiagnosticConsumer *DiagConsumer, DependencyScanningService &Service,
IntrusiveRefCntPtr<DependencyScanningWorkerFilesystem> DepFS);
-SmallVector<SmallString<0>, 2>
-getInitialStableDirs(const CompilerInstance &ScanInstance);
+SmallVector<StringRef>
+getInitialStableDirs(const CompilerInstance &ScanInstance,
+ llvm::StringSaver &SS);
std::optional<PrebuiltModulesAttrsMap>
computePrebuiltModulesASTMap(CompilerInstance &ScanInstance,
- ArrayRef<SmallString<0>> StableDirs);
+ ArrayRef<StringRef> StableDirs);
/// Create the dependency collector that will collect the produced
/// dependencies. May return the created ModuleDepCollector depending
@@ -123,7 +124,7 @@ std::shared_ptr<ModuleDepCollector> initializeScanInstanceDependencyCollector(
DependencyScanningService &Service, CompilerInvocation &Inv,
DependencyActionController &Controller,
PrebuiltModulesAttrsMap PrebuiltModulesASTMap,
- SmallVector<SmallString<0>, 2> StableDirs);
+ ArrayRef<StringRef> StableDirs);
} // namespace dependencies
} // namespace clang
diff --git a/clang/include/clang/DependencyScanning/ModuleDepCollector.h b/clang/include/clang/DependencyScanning/ModuleDepCollector.h
index f54b9ccf61b378..7e5a292f393ef1 100644
--- a/clang/include/clang/DependencyScanning/ModuleDepCollector.h
+++ b/clang/include/clang/DependencyScanning/ModuleDepCollector.h
@@ -86,7 +86,7 @@ class ModuleDepCollector final : public DependencyCollector {
DependencyActionController &Controller,
CompilerInvocation OriginalCI,
const PrebuiltModulesAttrsMap PrebuiltModulesASTMap,
- SmallVector<SmallString<0>, 2> StableDirs);
+ const ArrayRef<StringRef> StableDirs);
/// Processes the accumulated dependency information and reports it to the
/// \c Consumer.
@@ -115,7 +115,9 @@ class ModuleDepCollector final : public DependencyCollector {
const PrebuiltModulesAttrsMap PrebuiltModulesASTMap;
/// Directory paths known to be stable through an active development and build
/// cycle.
- SmallVector<SmallString<0>, 2> StableDirs;
+ llvm::BumpPtrAllocator Alloc;
+ llvm::StringSaver StableDirsStrings{Alloc};
+ const ArrayRef<StringRef> StableDirs;
/// Path to the main source file.
std::string MainFile;
/// Non-modular file dependencies. This includes the main source file and
@@ -210,7 +212,7 @@ void resetBenignCodeGenOptions(frontend::ActionKind ProgramAction,
///
/// \param Directories Paths known to be in a stable location. e.g. Sysroot.
/// \param Input Path to evaluate.
-bool isPathInStableDir(const ArrayRef<SmallString<0>> Directories,
+bool isPathInStableDir(const ArrayRef<StringRef> Directories,
const StringRef Input);
/// Determine if options collected from a module's
@@ -218,7 +220,7 @@ bool isPathInStableDir(const ArrayRef<SmallString<0>> Directories,
///
/// \param Directories Paths known to be in a stable location. e.g. Sysroot.
/// \param HSOpts Header search options derived from the compiler invocation.
-bool areOptionsInStableDir(const ArrayRef<SmallString<0>> Directories,
+bool areOptionsInStableDir(const ArrayRef<StringRef> Directories,
const HeaderSearchOptions &HSOpts);
} // end namespace dependencies
diff --git a/clang/include/clang/Tooling/DependencyScanningTool.h b/clang/include/clang/Tooling/DependencyScanningTool.h
index 38e7f8aea9d38c..a49c63cf592f49 100644
--- a/clang/include/clang/Tooling/DependencyScanningTool.h
+++ b/clang/include/clang/Tooling/DependencyScanningTool.h
@@ -162,7 +162,9 @@ class CompilerInstanceWithContext {
std::unique_ptr<DependencyOutputOptions> OutputOpts;
// Context - stable directory handling
- SmallVector<SmallString<0>, 2> StableDirs;
+ llvm::BumpPtrAllocator Alloc;
+ llvm::StringSaver StableDirsStrings{Alloc};
+ llvm::SmallVector<StringRef> StableDirs;
dependencies::PrebuiltModulesAttrsMap PrebuiltModuleASTMap;
// Compiler Instance
diff --git a/clang/lib/DependencyScanning/DependencyScannerImpl.cpp b/clang/lib/DependencyScanning/DependencyScannerImpl.cpp
index 2686d8dc47893f..fd25553719c979 100644
--- a/clang/lib/DependencyScanning/DependencyScannerImpl.cpp
+++ b/clang/lib/DependencyScanning/DependencyScannerImpl.cpp
@@ -67,7 +67,7 @@ class PrebuiltModuleListener : public ASTReaderListener {
PrebuiltModulesAttrsMap &PrebuiltModulesASTMap,
const HeaderSearchOptions &HSOpts,
const LangOptions &LangOpts, DiagnosticsEngine &Diags,
- const ArrayRef<SmallString<0>> StableDirs)
+ const ArrayRef<StringRef> StableDirs)
: PrebuiltModuleFiles(PrebuiltModuleFiles),
NewModuleFiles(NewModuleFiles),
PrebuiltModulesASTMap(PrebuiltModulesASTMap), ExistingHSOpts(HSOpts),
@@ -166,7 +166,7 @@ class PrebuiltModuleListener : public ASTReaderListener {
const LangOptions &ExistingLangOpts;
DiagnosticsEngine &Diags;
std::string CurrentFile;
- const ArrayRef<SmallString<0>> StableDirs;
+ const ArrayRef<StringRef> StableDirs;
};
/// Visit the given prebuilt module and collect all of the modules it
@@ -176,7 +176,7 @@ static bool visitPrebuiltModule(StringRef PrebuiltModuleFilename,
PrebuiltModuleFilesT &ModuleFiles,
PrebuiltModulesAttrsMap &PrebuiltModulesASTMap,
DiagnosticsEngine &Diags,
- const ArrayRef<SmallString<0>> StableDirs) {
+ const ArrayRef<StringRef> StableDirs) {
// List of module files to be processed.
llvm::SmallVector<std::string> Worklist;
@@ -459,29 +459,30 @@ std::shared_ptr<CompilerInvocation> dependencies::createScanCompilerInvocation(
return ScanInvocation;
}
-SmallVector<SmallString<0>, 2>
-dependencies::getInitialStableDirs(const CompilerInstance &ScanInstance) {
+SmallVector<StringRef>
+dependencies::getInitialStableDirs(const CompilerInstance &ScanInstance,
+ llvm::StringSaver &SS) {
// Create a collection of stable directories derived from the ScanInstance
// for determining whether module dependencies would fully resolve from
// those directories.
- SmallVector<SmallString<0>, 2> StableDirs;
+ SmallVector<StringRef> StableDirs;
const StringRef Sysroot = ScanInstance.getHeaderSearchOpts().Sysroot;
if (!Sysroot.empty() && llvm::sys::path::root_directory(Sysroot) != Sysroot) {
SmallString<0> SysrootBuf = Sysroot;
llvm::sys::path::remove_dots(SysrootBuf);
- StableDirs.emplace_back(std::move(SysrootBuf));
+ StableDirs.emplace_back(SS.save(Twine(SysrootBuf)));
SmallString<0> ResourceDirBuf{
ScanInstance.getHeaderSearchOpts().ResourceDir};
llvm::sys::path::remove_dots(ResourceDirBuf);
- StableDirs.emplace_back(std::move(ResourceDirBuf));
+ StableDirs.emplace_back(SS.save(Twine(ResourceDirBuf)));
}
return StableDirs;
}
std::optional<PrebuiltModulesAttrsMap>
-dependencies::computePrebuiltModulesASTMap(
- CompilerInstance &ScanInstance, ArrayRef<SmallString<0>> StableDirs) {
+dependencies::computePrebuiltModulesASTMap(CompilerInstance &ScanInstance,
+ ArrayRef<StringRef> StableDirs) {
// Store a mapping of prebuilt module files and their properties like header
// search options. This will prevent the implicit build to create duplicate
// modules and will force reuse of the existing prebuilt module files
@@ -520,10 +521,10 @@ dependencies::initializeScanInstanceDependencyCollector(
DependencyScanningService &Service, CompilerInvocation &Inv,
DependencyActionController &Controller,
PrebuiltModulesAttrsMap PrebuiltModulesASTMap,
- SmallVector<SmallString<0>, 2> StableDirs) {
+ ArrayRef<StringRef> StableDirs) {
auto MDC = std::make_shared<ModuleDepCollector>(
Service, std::move(DepOutputOpts), ScanInstance, Controller, Inv,
- std::move(PrebuiltModulesASTMap), std::move(StableDirs));
+ std::move(PrebuiltModulesASTMap), StableDirs);
ScanInstance.addDependencyCollector(MDC);
return MDC;
}
@@ -745,7 +746,9 @@ bool DependencyScanningAction::runInvocation(
DepFS);
// FIXME: Do this only once.
- auto StableDirs = getInitialStableDirs(ScanInstance);
+ llvm::BumpPtrAllocator Alloc;
+ llvm::StringSaver SS(Alloc);
+ auto StableDirs = getInitialStableDirs(ScanInstance, SS);
auto MaybePrebuiltModulesASTMap =
computePrebuiltModulesASTMap(ScanInstance, StableDirs);
if (!MaybePrebuiltModulesASTMap)
@@ -768,8 +771,9 @@ bool DependencyScanningAction::runInvocation(
initializeScanCompilerInstance(ScanInstance, FS, DiagConsumer, Service,
DepFS);
- SmallVector<SmallString<0>, 2> StableDirs =
- getInitialStableDirs(ScanInstance);
+ llvm::BumpPtrAllocator Alloc;
+ llvm::StringSaver SS(Alloc);
+ SmallVector<StringRef> StableDirs = getInitialStableDirs(ScanInstance, SS);
auto MaybePrebuiltModulesASTMap =
computePrebuiltModulesASTMap(ScanInstance, StableDirs);
if (!MaybePrebuiltModulesASTMap)
diff --git a/clang/lib/DependencyScanning/ModuleDepCollector.cpp b/clang/lib/DependencyScanning/ModuleDepCollector.cpp
index 9712803c1abbb8..cf23b1f6ec0130 100644
--- a/clang/lib/DependencyScanning/ModuleDepCollector.cpp
+++ b/clang/lib/DependencyScanning/ModuleDepCollector.cpp
@@ -193,7 +193,7 @@ void dependencies::resetBenignCodeGenOptions(frontend::ActionKind ProgramAction,
}
}
-bool dependencies::isPathInStableDir(const ArrayRef<SmallString<0>> Directories,
+bool dependencies::isPathInStableDir(const ArrayRef<StringRef> Directories,
const StringRef Input) {
using namespace llvm::sys;
@@ -205,9 +205,8 @@ bool dependencies::isPathInStableDir(const ArrayRef<SmallString<0>> Directories,
});
}
-bool dependencies::areOptionsInStableDir(
- const ArrayRef<SmallString<0>> Directories,
- const HeaderSearchOptions &HSOpts) {
+bool dependencies::areOptionsInStableDir(const ArrayRef<StringRef> Directories,
+ const HeaderSearchOptions &HSOpts) {
assert(isPathInStableDir(Directories, HSOpts.Sysroot) &&
"Sysroots differ between module dependencies and current TU");
@@ -850,10 +849,10 @@ ModuleDepCollector::ModuleDepCollector(
CompilerInstance &ScanInstance, DependencyActionController &Controller,
CompilerInvocation OriginalCI,
const PrebuiltModulesAttrsMap PrebuiltModulesASTMap,
- SmallVector<SmallString<0>, 2> StableDirs)
+ const ArrayRef<StringRef> StableDirs)
: Service(Service), ScanInstance(ScanInstance), Controller(Controller),
PrebuiltModulesASTMap(std::move(PrebuiltModulesASTMap)),
- StableDirs(std::move(StableDirs)), Opts(std::move(Opts)),
+ StableDirs(StableDirs), Opts(std::move(Opts)),
CommonInvocation(
makeCommonInvocationForModuleBuild(std::move(OriginalCI))) {}
diff --git a/clang/lib/Tooling/DependencyScanningTool.cpp b/clang/lib/Tooling/DependencyScanningTool.cpp
index b4b45798c514d4..517615741f78a2 100644
--- a/clang/lib/Tooling/DependencyScanningTool.cpp
+++ b/clang/lib/Tooling/DependencyScanningTool.cpp
@@ -473,7 +473,7 @@ bool CompilerInstanceWithContext::initialize(
CI, std::move(FS), DiagEngineWithCmdAndOpts->DiagEngine->getClient(),
Worker.Service, Worker.DepFS);
- StableDirs = getInitialStableDirs(CI);
+ StableDirs = getInitialStableDirs(CI, StableDirsStrings);
auto MaybePrebuiltModulesASTMap =
computePrebuiltModulesASTMap(CI, StableDirs);
if (!MaybePrebuiltModulesASTMap)
>From abd47e55ba761fb7144eb57a9bd51928bcb338ea Mon Sep 17 00:00:00 2001
From: Jan Svoboda <jan_svoboda at apple.com>
Date: Tue, 19 May 2026 11:21:38 -0700
Subject: [PATCH 5/7] Undo `remove_dots` change
---
llvm/lib/Support/Path.cpp | 3 +++
llvm/lib/Support/VirtualFileSystem.cpp | 3 ---
llvm/unittests/Support/Path.cpp | 5 +----
3 files changed, 4 insertions(+), 7 deletions(-)
diff --git a/llvm/lib/Support/Path.cpp b/llvm/lib/Support/Path.cpp
index 26746d5dff80ac..d051904152bab6 100644
--- a/llvm/lib/Support/Path.cpp
+++ b/llvm/lib/Support/Path.cpp
@@ -806,6 +806,9 @@ bool remove_dots(SmallVectorImpl<char> &the_path, bool remove_dot_dot,
if (!remaining.empty()) {
needs_change |= remaining.front() != preferred_separator(style);
remaining = remaining.drop_front();
+ // The path needs to be rewritten if it has a trailing slash.
+ // FIXME: This is emergent behavior that could be removed.
+ needs_change |= remaining.empty();
}
// Check for path traversal components or double separators.
diff --git a/llvm/lib/Support/VirtualFileSystem.cpp b/llvm/lib/Support/VirtualFileSystem.cpp
index 02778f28f90447..69f3bb8582b87c 100644
--- a/llvm/lib/Support/VirtualFileSystem.cpp
+++ b/llvm/lib/Support/VirtualFileSystem.cpp
@@ -1018,9 +1018,6 @@ InMemoryFileSystem::lookupNode(const Twine &P, bool FollowFinalSymlink,
auto I = llvm::sys::path::begin(Path), E = llvm::sys::path::end(Path);
while (true) {
- if (*I == ".")
- return detail::NamedNodeOrError(Path, Dir);
-
detail::InMemoryNode *Node = Dir->getChild(*I);
++I;
if (!Node)
diff --git a/llvm/unittests/Support/Path.cpp b/llvm/unittests/Support/Path.cpp
index f9d10466d67943..beb109bed983b5 100644
--- a/llvm/unittests/Support/Path.cpp
+++ b/llvm/unittests/Support/Path.cpp
@@ -443,10 +443,7 @@ TEST(Support, AbsolutePathIteratorEnd) {
TEST(Support, PathStartsWith) {
EXPECT_TRUE(path::starts_with("/foo", "/foo"));
- EXPECT_TRUE(path::starts_with("/foo/", "/foo"));
- // FIXME: This should probably work too.
- // EXPECT_TRUE(path::starts_with("/foo", "/foo/"));
- EXPECT_TRUE(path::starts_with("/foo/", "/foo/"));
+ EXPECT_TRUE(path::starts_with("/foo/bar", "/foo"));
EXPECT_FALSE(path::starts_with("/foo", "/fooo"));
EXPECT_FALSE(path::starts_with("/fooo", "/foo"));
}
>From fca5ad81865ca63d013e25dd3781bcf0d00e5189 Mon Sep 17 00:00:00 2001
From: Jan Svoboda <jan_svoboda at apple.com>
Date: Tue, 19 May 2026 11:28:23 -0700
Subject: [PATCH 6/7] Documentation wording
---
llvm/include/llvm/Support/Path.h | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/llvm/include/llvm/Support/Path.h b/llvm/include/llvm/Support/Path.h
index 75edd1a9489643..9d28a23975f203 100644
--- a/llvm/include/llvm/Support/Path.h
+++ b/llvm/include/llvm/Support/Path.h
@@ -180,8 +180,8 @@ LLVM_ABI void replace_extension(SmallVectorImpl<char> &path,
/// of the last component in order to correctly return false for path "/foo/bar"
/// and prefix "/foo/b", which are likely different file system entities.
///
-/// Both \a Path and \a Prefix need to be normalized - without './' components
-/// and with consistent separators.
+/// Both \a Path and \a Prefix must be normalized - without './' components and
+/// with consistent separators.
LLVM_ABI bool starts_with(StringRef Path, StringRef Prefix,
Style style = Style::native);
>From ff5058ad92ba622923128675d95787b624271507 Mon Sep 17 00:00:00 2001
From: Jan Svoboda <jan_svoboda at apple.com>
Date: Tue, 19 May 2026 13:06:06 -0700
Subject: [PATCH 7/7] Fix unit tests
---
llvm/unittests/Support/Path.cpp | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/llvm/unittests/Support/Path.cpp b/llvm/unittests/Support/Path.cpp
index beb109bed983b5..25918d64134369 100644
--- a/llvm/unittests/Support/Path.cpp
+++ b/llvm/unittests/Support/Path.cpp
@@ -442,10 +442,10 @@ TEST(Support, AbsolutePathIteratorEnd) {
}
TEST(Support, PathStartsWith) {
- EXPECT_TRUE(path::starts_with("/foo", "/foo"));
- EXPECT_TRUE(path::starts_with("/foo/bar", "/foo"));
- EXPECT_FALSE(path::starts_with("/foo", "/fooo"));
- EXPECT_FALSE(path::starts_with("/fooo", "/foo"));
+ EXPECT_TRUE(path::starts_with("/foo", "/foo", path::Style::posix));
+ EXPECT_TRUE(path::starts_with("/foo/bar", "/foo", path::Style::posix));
+ EXPECT_FALSE(path::starts_with("/foo", "/fooo", path::Style::posix));
+ EXPECT_FALSE(path::starts_with("/fooo", "/foo", path::Style::posix));
}
#ifdef _WIN32
More information about the cfe-commits
mailing list