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 <[email protected]> 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 <[email protected]> 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 <[email protected]> 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 <[email protected]> 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 <[email protected]> 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 <[email protected]> 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 <[email protected]> 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 _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
