Skip to content

Conversation

@Dominicentek
Copy link
Contributor

Adds an ability to change the current working directory for fallback commands.

@github-actions
Copy link

Thank you for submitting a Pull Request (PR) to the LLVM Project!

This PR will be automatically labeled and the relevant teams will be notified.

If you wish to, you can add reviewers by using the "Reviewers" section on this page.

If this is not working for you, it is probably because you do not have write permissions for the repository. In which case you can instead tag reviewers by name in a comment by using @ followed by their GitHub username.

If you have received no comments on your PR for a week, you can request a review by "ping"ing the PR by adding a comment “Ping”. The common courtesy "ping" rate is once a week. Please remember that you are asking for valuable time from other developers.

If you have further questions, they may be answered by the LLVM GitHub User Guide.

You can also ask questions in a comment on this PR, on the LLVM Discord or on the forums.

@llvmbot
Copy link
Member

llvmbot commented Aug 28, 2025

@llvm/pr-subscribers-clangd

@llvm/pr-subscribers-clang-tools-extra

Author: Dominicentek (Dominicentek)

Changes

Adds an ability to change the current working directory for fallback commands.


Full diff: https://github.com/llvm/llvm-project/pull/155905.diff

8 Files Affected:

  • (modified) clang-tools-extra/clangd/ClangdServer.cpp (+1)
  • (modified) clang-tools-extra/clangd/ClangdServer.h (+4)
  • (modified) clang-tools-extra/clangd/GlobalCompilationDatabase.cpp (+7-7)
  • (modified) clang-tools-extra/clangd/GlobalCompilationDatabase.h (+3-3)
  • (modified) clang-tools-extra/clangd/TUScheduler.cpp (+4-2)
  • (modified) clang-tools-extra/clangd/TUScheduler.h (+4)
  • (modified) clang-tools-extra/clangd/tool/Check.cpp (+2-2)
  • (modified) clang-tools-extra/clangd/tool/ClangdMain.cpp (+10)
diff --git a/clang-tools-extra/clangd/ClangdServer.cpp b/clang-tools-extra/clangd/ClangdServer.cpp
index ac1e9aa5f0ff1..51230b4506b1a 100644
--- a/clang-tools-extra/clangd/ClangdServer.cpp
+++ b/clang-tools-extra/clangd/ClangdServer.cpp
@@ -208,6 +208,7 @@ ClangdServer::Options::operator TUScheduler::Options() const {
   Opts.UpdateDebounce = UpdateDebounce;
   Opts.ContextProvider = ContextProvider;
   Opts.PreambleThrottler = PreambleThrottler;
+  Opts.FallbackProjectRoot = FallbackProjectRoot;
   return Opts;
 }
 
diff --git a/clang-tools-extra/clangd/ClangdServer.h b/clang-tools-extra/clangd/ClangdServer.h
index 4a1eae188f7eb..2c56d6f7e6d6c 100644
--- a/clang-tools-extra/clangd/ClangdServer.h
+++ b/clang-tools-extra/clangd/ClangdServer.h
@@ -152,6 +152,10 @@ class ClangdServer {
     /// FIXME: If not set, should use the current working directory.
     std::optional<std::string> WorkspaceRoot;
 
+    /// If set, fallback command uses this path as its current working directory
+    /// instead of the file's parent path.
+    std::optional<std::string> FallbackProjectRoot;
+
     /// The resource directory is used to find internal headers, overriding
     /// defaults and -resource-dir compiler flag).
     /// If std::nullopt, ClangdServer calls
diff --git a/clang-tools-extra/clangd/GlobalCompilationDatabase.cpp b/clang-tools-extra/clangd/GlobalCompilationDatabase.cpp
index c6afd0bc07cbd..b73697d4ee7e5 100644
--- a/clang-tools-extra/clangd/GlobalCompilationDatabase.cpp
+++ b/clang-tools-extra/clangd/GlobalCompilationDatabase.cpp
@@ -55,7 +55,7 @@ void actOnAllParentDirectories(PathRef FileName,
 } // namespace
 
 tooling::CompileCommand
-GlobalCompilationDatabase::getFallbackCommand(PathRef File) const {
+GlobalCompilationDatabase::getFallbackCommand(PathRef File, std::optional<std::string> ProjectRoot) const {
   std::vector<std::string> Argv = {"clang"};
   // Clang treats .h files as C by default and files without extension as linker
   // input, resulting in unhelpful diagnostics.
@@ -64,7 +64,7 @@ GlobalCompilationDatabase::getFallbackCommand(PathRef File) const {
   if (FileExtension.empty() || FileExtension == ".h")
     Argv.push_back("-xobjective-c++-header");
   Argv.push_back(std::string(File));
-  tooling::CompileCommand Cmd(llvm::sys::path::parent_path(File),
+  tooling::CompileCommand Cmd(ProjectRoot ? *ProjectRoot : llvm::sys::path::parent_path(File),
                               llvm::sys::path::filename(File), std::move(Argv),
                               /*Output=*/"");
   Cmd.Heuristic = "clangd fallback";
@@ -797,8 +797,8 @@ OverlayCDB::getCompileCommand(PathRef File) const {
   return Cmd;
 }
 
-tooling::CompileCommand OverlayCDB::getFallbackCommand(PathRef File) const {
-  auto Cmd = DelegatingCDB::getFallbackCommand(File);
+tooling::CompileCommand OverlayCDB::getFallbackCommand(PathRef File, std::optional<std::string> ProjectRoot) const {
+  auto Cmd = DelegatingCDB::getFallbackCommand(File, ProjectRoot);
   std::lock_guard<std::mutex> Lock(Mutex);
   Cmd.CommandLine.insert(Cmd.CommandLine.end(), FallbackFlags.begin(),
                          FallbackFlags.end());
@@ -877,10 +877,10 @@ DelegatingCDB::getProjectModules(PathRef File) const {
   return Base->getProjectModules(File);
 }
 
-tooling::CompileCommand DelegatingCDB::getFallbackCommand(PathRef File) const {
+tooling::CompileCommand DelegatingCDB::getFallbackCommand(PathRef File, std::optional<std::string> ProjectRoot) const {
   if (!Base)
-    return GlobalCompilationDatabase::getFallbackCommand(File);
-  return Base->getFallbackCommand(File);
+    return GlobalCompilationDatabase::getFallbackCommand(File, ProjectRoot);
+  return Base->getFallbackCommand(File, ProjectRoot);
 }
 
 bool DelegatingCDB::blockUntilIdle(Deadline D) const {
diff --git a/clang-tools-extra/clangd/GlobalCompilationDatabase.h b/clang-tools-extra/clangd/GlobalCompilationDatabase.h
index 1d636d73664be..5d1b5cb632154 100644
--- a/clang-tools-extra/clangd/GlobalCompilationDatabase.h
+++ b/clang-tools-extra/clangd/GlobalCompilationDatabase.h
@@ -55,7 +55,7 @@ class GlobalCompilationDatabase {
   /// Makes a guess at how to build a file.
   /// The default implementation just runs clang on the file.
   /// Clangd should treat the results as unreliable.
-  virtual tooling::CompileCommand getFallbackCommand(PathRef File) const;
+  virtual tooling::CompileCommand getFallbackCommand(PathRef File, std::optional<std::string> ProjectRoot = std::nullopt) const;
 
   /// If the CDB does any asynchronous work, wait for it to complete.
   /// For use in tests.
@@ -86,7 +86,7 @@ class DelegatingCDB : public GlobalCompilationDatabase {
   std::unique_ptr<ProjectModules>
   getProjectModules(PathRef File) const override;
 
-  tooling::CompileCommand getFallbackCommand(PathRef File) const override;
+  tooling::CompileCommand getFallbackCommand(PathRef File, std::optional<std::string> ProjectRoot = std::nullopt) const override;
 
   bool blockUntilIdle(Deadline D) const override;
 
@@ -200,7 +200,7 @@ class OverlayCDB : public DelegatingCDB {
 
   std::optional<tooling::CompileCommand>
   getCompileCommand(PathRef File) const override;
-  tooling::CompileCommand getFallbackCommand(PathRef File) const override;
+  tooling::CompileCommand getFallbackCommand(PathRef File, std::optional<std::string> ProjectRoot = std::nullopt) const override;
 
   /// Sets or clears the compilation command for a particular file.
   /// Returns true if the command was changed (including insertion and removal),
diff --git a/clang-tools-extra/clangd/TUScheduler.cpp b/clang-tools-extra/clangd/TUScheduler.cpp
index 035e5e63d8fbb..3dc53767e0ea4 100644
--- a/clang-tools-extra/clangd/TUScheduler.cpp
+++ b/clang-tools-extra/clangd/TUScheduler.cpp
@@ -723,6 +723,7 @@ class ASTWorker {
   const GlobalCompilationDatabase &CDB;
   /// Callback invoked when preamble or main file AST is built.
   ParsingCallbacks &Callbacks;
+  std::optional<std::string> FallbackProjectRoot;
 
   Semaphore &Barrier;
   /// Whether the 'onMainAST' callback ran for the current FileInputs.
@@ -840,13 +841,14 @@ ASTWorker::ASTWorker(PathRef FileName, const GlobalCompilationDatabase &CDB,
     : IdleASTs(LRUCache), HeaderIncluders(HeaderIncluders), RunSync(RunSync),
       UpdateDebounce(Opts.UpdateDebounce), FileName(FileName),
       ContextProvider(Opts.ContextProvider), CDB(CDB), Callbacks(Callbacks),
+      FallbackProjectRoot(Opts.FallbackProjectRoot),
       Barrier(Barrier), Done(false), Status(FileName, Callbacks),
       PreamblePeer(FileName, Callbacks, Opts.StorePreamblesInMemory, RunSync,
                    Opts.PreambleThrottler, Status, HeaderIncluders, *this) {
   // Set a fallback command because compile command can be accessed before
   // `Inputs` is initialized. Other fields are only used after initialization
   // from client inputs.
-  FileInputs.CompileCommand = CDB.getFallbackCommand(FileName);
+  FileInputs.CompileCommand = CDB.getFallbackCommand(FileName, FallbackProjectRoot);
 }
 
 ASTWorker::~ASTWorker() {
@@ -888,7 +890,7 @@ void ASTWorker::update(ParseInputs Inputs, WantDiagnostics WantDiags,
     if (Cmd)
       Inputs.CompileCommand = std::move(*Cmd);
     else
-      Inputs.CompileCommand = CDB.getFallbackCommand(FileName);
+      Inputs.CompileCommand = CDB.getFallbackCommand(FileName, FallbackProjectRoot);
 
     bool InputsAreTheSame =
         std::tie(FileInputs.CompileCommand, FileInputs.Contents) ==
diff --git a/clang-tools-extra/clangd/TUScheduler.h b/clang-tools-extra/clangd/TUScheduler.h
index d0da20310a8b2..581a639646527 100644
--- a/clang-tools-extra/clangd/TUScheduler.h
+++ b/clang-tools-extra/clangd/TUScheduler.h
@@ -236,6 +236,10 @@ class TUScheduler {
     /// Typically to inject per-file configuration.
     /// If the path is empty, context sholud be "generic".
     std::function<Context(PathRef)> ContextProvider;
+
+    /// If set, fallback command uses this path as its current working directory
+    /// instead of the file's parent path.
+    std::optional<std::string> FallbackProjectRoot;
   };
 
   TUScheduler(const GlobalCompilationDatabase &CDB, const Options &Opts,
diff --git a/clang-tools-extra/clangd/tool/Check.cpp b/clang-tools-extra/clangd/tool/Check.cpp
index df8d075e80596..8d49b82d2ca53 100644
--- a/clang-tools-extra/clangd/tool/Check.cpp
+++ b/clang-tools-extra/clangd/tool/Check.cpp
@@ -187,7 +187,7 @@ class Checker {
           Cmd.Heuristic.empty() ? "from CDB" : Cmd.Heuristic, Cmd.Directory,
           printArgv(Cmd.CommandLine));
     } else {
-      Cmd = CDB->getFallbackCommand(File);
+      Cmd = CDB->getFallbackCommand(File, Opts.FallbackProjectRoot);
       log("Generic fallback command is: [{0}] {1}", Cmd.Directory,
           printArgv(Cmd.CommandLine));
     }
@@ -502,7 +502,7 @@ bool check(llvm::StringRef File, const ThreadsafeFS &TFS,
                  config::DiagnosticCallback Diag) const override {
       config::Fragment F;
       // If we're timing clang-tidy checks, implicitly disabling the slow ones
-      // is counterproductive! 
+      // is counterproductive!
       if (CheckTidyTime.getNumOccurrences())
         F.Diagnostics.ClangTidy.FastCheckFilter.emplace("None");
       return {std::move(F).compile(Diag)};
diff --git a/clang-tools-extra/clangd/tool/ClangdMain.cpp b/clang-tools-extra/clangd/tool/ClangdMain.cpp
index f287439f10cab..75d71c5a78f45 100644
--- a/clang-tools-extra/clangd/tool/ClangdMain.cpp
+++ b/clang-tools-extra/clangd/tool/ClangdMain.cpp
@@ -499,6 +499,14 @@ opt<bool> EnableConfig{
     init(true),
 };
 
+opt<Path> ProjectRoot{
+    "project-root",
+    cat(Misc),
+    desc("Path to use as the current working directory for fallback commands."),
+    init(""),
+    ValueOptional,
+};
+
 opt<bool> UseDirtyHeaders{"use-dirty-headers", cat(Misc),
                           desc("Use files open in the editor when parsing "
                                "headers instead of reading from the disk"),
@@ -906,6 +914,8 @@ clangd accepts flags on the commandline, and in the CLANGD_FLAGS environment var
   }
   if (!ResourceDir.empty())
     Opts.ResourceDir = ResourceDir;
+  if (!ProjectRoot.empty())
+    Opts.FallbackProjectRoot = ProjectRoot;
   Opts.BuildDynamicSymbolIndex = true;
 #if CLANGD_ENABLE_REMOTE
   if (RemoteIndexAddress.empty() != ProjectRoot.empty()) {

@HighCommander4
Copy link
Collaborator

HighCommander4 commented Nov 18, 2025

Hi @Dominicentek! Thanks for submitting this PR.

The topic of this PR actually touches on a set of improvements I've been wanting to make to clangd for a long time, called "strong workspace mode". Some background on this proposed mode can be found in clangd/clangd#907 (with credit to @sam-mccall for the name and initial description).

Basically, "strong workspace mode" would be a mode of operation of clangd where an instance of clangd is more strongly coupled to a particular workspace/project, and clangd's default behaviours are adjusted to reflect this. The specific behaviours that I have in mind are:

  1. A compile_commands.json file in the workspace's root directory (or in build/) is used for all files opened by the clangd instance, as if --compile-commands-dir=<workspace_dir> were used.
  2. Settings in a .clangd file in the workspace's root directory are applied to all files opened by the clangd instance, including files outside the directory tree of the workspace.
  3. (This patch) the workspace's root directory is used as the working directory for fallback commands.

In addition, there is some discussion in clangd/clangd#1341 that suggests to me that it might make sense for "strong workspace mode" to be something the user opts into at the client level (for example, in vscode, using a setting provided by the vscode-clangd extension), which then allows it to enable additional client behaviours as well (for example, launching clangd as soon as the workspace is opened, so that operations like "Go to Symbol" are available even before a C/C++ file is opened).

I'd be curious to hear your opinion on the following:

  • Does the above outline of "strong workspace mode" make sense to you as a cohesive concept? Would it make sense to have these behaviours controlled by a single setting, rather than e.g. having a separate setting that controls just the working directory for fallback commands?
    • (Note that this doesn't have to mean implementing all these behaviours at once. We could implement them piece by piece, starting with the fallback commands. But it does mean adding any new setting with the understanding that in the future it will affect other behaviours as well.)
  • What do you think about opting into "strong workspace mode" at the client level (e.g. with a vscode-clangd setting) rather than at the server level (e.g. with a clangd command line argument)? Are you looking to use this with a client that has a clear notion of a "workspace", such as vscode?

@Dominicentek
Copy link
Contributor Author

Hi, thanks for reaching out.

I made this pull request as my editor's (Zed) built-in clangd client doesn't set the project root directory as the working directory for fallback commands. VSCode does actually have this behavior. Since I was relying on fallback commands rather than compile_commands.json and couldn't really find a way to make clangd behave like this with Zed, I made it so you can change the working directory for fallback commands via the --project-root flag with this patch.

As for the "strong workspace mode", I am quite fond of this idea. Though I'd like to see this being done at the server level (with a CLI flag), since both VSCode and Zed let you customize CLI arguments for clangd, but Zed doesn't really have any clangd-specific settings, as opposed to VSCode.

@HighCommander4
Copy link
Collaborator

my editor's (Zed) built-in clangd client doesn't set the project root directory as the working directory for fallback commands. VSCode does actually have this behavior.

Do you know how VSCode does this?

@Dominicentek
Copy link
Contributor Author

No, I'm not entirely sure.

@HighCommander4
Copy link
Collaborator

my editor's (Zed) built-in clangd client doesn't set the project root directory as the working directory for fallback commands. VSCode does actually have this behavior.

Do you know how VSCode does this?

I tried to reproduce the described behaviour in VSCode, and I haven't been able to.

Here's what I tried specifically:

  • Created /tmp/workspace with the directory structure:
    .
    ├── include
    │   └── foo.hpp
    └── src
        └── test.cpp
    ```
    
  • Gave src/test.cpp the contents #include "include/foo.hpp"
  • Added the vscode setting "clangd.fallbackFlags": ["-Iinclude"]
  • Opened src/test.cpp

The clangd logs are showing:

I[02:12:28.500] ASTWorker building file /tmp/workspace/src/test.cpp version 1 with command clangd fallback
[/tmp/workspace/src]
/usr/bin/clang -Iinclude -resource-dir=/usr/lib/llvm-21/lib/clang/21 -- /tmp/workspace/src/test.cpp

indicating that it's using the source file's containing directory (/tmp/workspace/src), and not the project root directory (/tmp/workspace) as the working directory for the command (and indeed, the include in the opened file is not resolved).

Could you elaborate on what VSCode behaviour you were observing?

@Dominicentek
Copy link
Contributor Author

Ah I apologize, I was misremembering.

I used -I${workspaceFolder}/include in my VSCode configuration, not -Iinclude.

@HighCommander4
Copy link
Collaborator

Thanks for the clarification.

I have a couple of follow-up questions about using clangd with Zed, with a view to understanding what sort of constraints we have on how to configure this behaviour:

  1. Does Zed send a suitable workspace path in InitializeParams, in any of the rootPath, rootUri, or workspaceFolders fields? (If unsure, feel free to post clangd logs taken with --log=verbose, which will include the entire contents of the client's initialize request.) I'm trying to understand whether we have the potential option of using this directory, rather than specifying an additional (possibly conflicting) directory as the value of a command-line argument such as --project-root.
  2. Does Zed provide some way of customizing the initialize request, such as injecting additional fields into it? (E.g. neovim allows this, with a short piece of Lua script.) If it does, and the way to activate this feature would be to inject e.g. strongWorkspaceMode: true or some such, would you consider that a sufficiently usable alternative to a command line option?

@Dominicentek
Copy link
Contributor Author

  1. I've attached my clangd log below.
  2. Zed only lets you configure CLI options, environment variables and path to the binary. I'm using ~/.config/clangd/config.yaml to configure the fallback flags.

clangd.log

@HighCommander4
Copy link
Collaborator

  1. I've attached my clangd log below.

I'm seeing the client include "rootUri":"file:///home/dominicentek/Desktop/project" in the initialize request.

I take it this is the same directory you were envisioning passing to --project-root?

  1. Zed only lets you configure CLI options, environment variables and path to the binary. I'm using ~/.config/clangd/config.yaml to configure the fallback flags.

Ok, thanks for checking.

So given all that, what do you think about the following approach:

  • Introduce a command-line flag --strong-workspace-mode which is boolean-valued rather than taking a string argument.
    • If set to true, this flag would currently have the effect of using the rootUri sent by the client as the working directory for fallback commands.
    • In the future, the effect of this flag would be extended to have the other effects described in this comment.

@Dominicentek
Copy link
Contributor Author

Yep, sounds great!

@HighCommander4
Copy link
Collaborator

Yep, sounds great!

Ok, cool.

Would you like to update this PR to implement this revised approach? I'd be happy to review and (if no one else objects) approve it.

@Dominicentek
Copy link
Contributor Author

Alright, I've made the changes.

@github-actions
Copy link

github-actions bot commented Nov 25, 2025

✅ With the latest revision this PR passed the C/C++ code formatter.

@github-actions
Copy link

github-actions bot commented Nov 25, 2025

🐧 Linux x64 Test Results

  • 3055 tests passed
  • 7 tests skipped

✅ The build succeeded and all tests passed.

Copy link
Collaborator

@HighCommander4 HighCommander4 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I haven't had a chance to take a very detailed look, but some high-level feedback:

  • We shouldn't assume the current working directory (current_path) will be the workspace root, even if that happens to be the case today. Instead, let's propagate the rootUri (or rootPath) that the client sent in the initialize request to the place where we need it. We already propagate it into Opts.WorkspaceRoot, we can plumb it further from there into GlobalCompilationDatabase.
  • Rather than changing the GlobalCompilationDatabase::getFallbackCommand signature, let's provide the flag to the GlobalCompilationDatabase via its constructor (and derived classes' constructors).
    • In fact, since we need to tell it both the bool and the path, maybe we can group them into an optional<string>, which would contain the path in strong workspace mode, and be empty in the default mode.
  • We should add some sort of test coverage. We can add a unit test similar to GlobalCompilationDatabaseTest.FallbackCommand which makes assertions about Cmd.Directory.

Copy link
Collaborator

@HighCommander4 HighCommander4 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, I think the general shape of the patch looks pretty good now.

/// If the path is empty, context sholud be "generic".
std::function<Context(PathRef)> ContextProvider;

bool test;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Leftover debug code?

/// Provides compilation arguments used for parsing C and C++ files.
class GlobalCompilationDatabase {
public:
GlobalCompilationDatabase(std::optional<std::string> WorkingDirectory)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would give this parameter a default to minimize the disruption caused by this change. (I'm not just talking about test code in this repo; I think this class has out-of-tree subclasses as well.)

}

protected:
std::optional<std::string> WorkingDirectory;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this only applies to fallback commands, let's call it FallbackWorkingDirectory. Likewise for the field in DirectoryBasedGlobalCompilationDatabase::Options, the method that sets it, etc.

public:
DelegatingCDB(const GlobalCompilationDatabase *Base);
DelegatingCDB(std::unique_ptr<GlobalCompilationDatabase> Base);
DelegatingCDB(const GlobalCompilationDatabase *Base,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Likewise here, we may want to give these added parameters a default

std::optional<std::string> WorkingDirectory;

void
applyWorkingDirectory(const std::optional<std::string> &&WorkingDirectory);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the purpose of using const && here?

"the workspace.\n"
"When enabled, fallback commands use the workspace directory as their "
"working directory instead of the parent folder."),
init(false),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's make this option hidden for now (example), until the feature is more fleshed out with other behaviours. (This just means it won't show up in clangd --help, only in clangd --help-hidden.)

opt<bool> StrongWorkspaceMode{
"strong-workspace-mode",
cat(Features),
desc("An alternate mode of operation for clangd, operating more closely to "
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested reword: "operating more closely to the workspace" --> "where the clangd instance is used to edit a single workspace".

if (WorkingDirectory)
this->WorkingDirectory = *WorkingDirectory;
else {
SmallString<256> CWD;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's worth a comment explaining why we need a fallback here. Something like "the user asked for strong workspace mode, but the client didn't provide a workspace path, so use the working directory as a fallback".

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was wondering if an error instead of a silent fallback helps with immediate user feedback, since this is an opt-in feature. Strong workspace's precondition is that a the directory should be set anyway.

Copy link
Collaborator

@HighCommander4 HighCommander4 Nov 30, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I could go either way on this. The advantage of a fallback is that it can help work around a client that's unhelpfully failing to set rootUri, for which you as a user may not have much other recourse.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tweaked the comment wording a bit here. I think it's important to mention that it's the LSP client not providing a workspace path that necessitates a fallback (or error) here, i.e. something we as the server can't control.

@MythreyaK
Copy link
Contributor

For multi-workspace (not vscode workspace) projects, say a monorepo where each subproject is built individually (and therefore separate build dirs and compile_commands.json), is the idea that each subproject will (should?) now have its own clangd instance running?

It mostly just worked in my experience so far, but wanted to understand this better.

I am not completely sure how this might interact with vscode workspaces.

@HighCommander4
Copy link
Collaborator

HighCommander4 commented Nov 29, 2025

For multi-workspace (not vscode workspace) projects, say a monorepo where each subproject is built individually (and therefore separate build dirs and compile_commands.json), is the idea that each subproject will (should?) now have its own clangd instance running?

I would say this patch doesn't make any changes to how clangd works on, or should be used with, such projects.

This patch is adding (the beginnings of) a non-default mode of operation ("strong workspace mode") that users need to opt into. If you don't opt into it, everything will work just as it did before.

For a project of the sort you describe, even if you opt into "strong workspace mode" (and after the remaining behaviours described in this comment) are implemented, not a lot is likely to change:

  • If you don't have a compile_commands.json file in your workspace's root directory, the new behaviour that clangd looks for such a file there even for files outside the workspace directory is basically a no-op. Clangd will continue using the sub-project compile_commands.json files based on ancestor-directory lookup just as it did before.
  • Clangd will also look at a .clangd file in the workspace root, even for files outside the workspace directory. That might be convenient (you can stash some extra flags there that you want to apply for any file you open while working on this project), but shouldn't make a big difference.
  • The change to the working directory for fallback commands is probably not relevant if you have compile_comands.json files.

is the idea that each subproject will (should?) now have its own clangd instance running?

It has separately been proposed that clients like vscode-clangd gain the ability to manage multiple clangd instances and launch one per "workspace root" (in the sense of vscode's multi-root workspaces). For example, one such proposal is being pursued in clangd/vscode-clangd#810.

If such a proposal is accepted, then using it for a project of the sort you describe (i.e. making each sub-project a "workspace root") would be a separate knob you can opt into. I think it would come with both advantages and disadvantages. (For example, clangd's index would no longer be able to cross-reference symbols between sub-projects. Depending on how your sub-projects are related, this may be a desirable change or an undesirable one.)

If you do opt into such a hypothetical multi-root workspace feature, you could then also opt into "strong workspace mode" for each sub-project, and that may bring more benefits than using "strong workspace mode" with a single workspace root for your overall project would have. (For example, if you have an ARM sub-project and an x86 sub-project, and you open a system header from a source file in your ARM sub-project, clangd will automatically use a compile command from one of your ARM source files, more reliably than it does today.)

@MythreyaK
Copy link
Contributor

MythreyaK commented Nov 29, 2025

That sounds reasonable, thank you!

Two more questions,

If you don't have a compile_commands.json file in your workspace's root directory

For the example I was suggesting, only the subprojects have the compilation database, but --project-root would point to the parent directory. My understanding is that a single index is generated across all subprojects, with their respective compile_commands.json, allowing for cross-referencing, which is really handy!

the new behaviour that clangd looks for such a file there even for files outside the workspace directory is basically a no-op.

Does this impact indexing files located outside of the projects but are #includeed in the project?

the new behaviour that clangd looks for such a file there even for files outside the workspace directory is basically a no-op. Clangd will continue using the sub-project compile_commands.json files based on ancestor-directory lookup just as it did before.

So as long as an open file is in one of these compile_commands.json, clangd will use that for indexing, correct? How will indexing for included files (from outside of the project) be handled? For example, one subproject's macros (say, USE_WAYLAND vs USE_XCB) could expose symbols differently when including a system header.

@HighCommander4
Copy link
Collaborator

HighCommander4 commented Nov 30, 2025

If you don't have a compile_commands.json file in your workspace's root directory, the new behaviour that clangd looks for such a file there even for files outside the workspace directory is basically a no-op. Clangd will continue using the sub-project compile_commands.json files based on ancestor-directory lookup just as it did before.

I guess in retrospect this is not an obvious choice, e.g. it's not how --compile-commands-dir works (if you specify --compile-commands-dir=<workspace_root> but your compile_commands.json files are actually in subdirectories, clangd won't use those even for source files inside the subdirectories).

So we could say --strong-workspace-mode's behaviours are a strict superset of --compile-commands-dir=<workspace_root>, and in that case the guidance would be "don't use strong workspace mode for projects of the sort you describe".

But I think it would more user-friendly for strong workspace mode to use <workspace_root>/compile_commands.json if it exists and fall back to ancestor-directory lookup otherwise. This would make it usable with a wider variety of workflows (and maybe even leave room for someday making it the default for some editors like vscode).

@HighCommander4
Copy link
Collaborator

the new behaviour that clangd looks for such a file there even for files outside the workspace directory is basically a no-op.

Does this impact indexing files located outside of the projects but are #includeed in the project?

I think clangd today stores index data for such files in $HOME/.cache/clangd/index, which is not very intuitive in my opinion. With strong workspace mode, it would store them in <workspace_root>/.cache/clangd/index.

the new behaviour that clangd looks for such a file there even for files outside the workspace directory is basically a no-op. Clangd will continue using the sub-project compile_commands.json files based on ancestor-directory lookup just as it did before.

So as long as an open file is in one of these compile_commands.json, clangd will use that for indexing, correct? How will indexing for included files (from outside of the project) be handled? For example, one subproject's macros (say, USE_WAYLAND vs USE_XCB) could expose symbols differently when including a system header.

Other than the index storage location being <workspace_root>/.cache/clangd/index, I would expect it to work the same as it does today without strong workspace mode.

Copy link
Collaborator

@HighCommander4 HighCommander4 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The patch updates look good to me, just one final nit: can we use the terminology FallbackWorkingDirectory consistently, i.e. in the name of the CDBOpts field, the name of the applyWorkingDirectory method, the various constructor parameters etc.?

@Dominicentek
Copy link
Contributor Author

Hi @HighCommander4, I apologize if you're busy, but is there any progress on this patch?

Copy link
Collaborator

@HighCommander4 HighCommander4 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! LGTM with some minor tweaks which I went ahead and made.

if (WorkingDirectory)
this->WorkingDirectory = *WorkingDirectory;
else {
SmallString<256> CWD;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tweaked the comment wording a bit here. I think it's important to mention that it's the LSP client not providing a workspace path that necessitates a fallback (or error) here, i.e. something we as the server can't control.

@HighCommander4 HighCommander4 enabled auto-merge (squash) December 9, 2025 07:00
@HighCommander4 HighCommander4 enabled auto-merge (squash) December 9, 2025 07:04
@HighCommander4 HighCommander4 merged commit 2fa4927 into llvm:main Dec 9, 2025
10 checks passed
@github-actions
Copy link

github-actions bot commented Dec 9, 2025

@Dominicentek Congratulations on having your first Pull Request (PR) merged into the LLVM Project!

Your changes will be combined with recent changes from other authors, then tested by our build bots. If there is a problem with a build, you may receive a report in an email or a comment on this PR.

Please check whether problems have been caused by your change specifically, as the builds can include changes from many authors. It is not uncommon for your change to be included in a build that fails due to someone else's changes, or infrastructure issues.

How to do this, and the rest of the post-merge process, is covered in detail here.

If your change does cause a problem, it may be reverted, or you can revert it yourself. This is a normal part of LLVM development. You can fix your changes and open a new PR to merge them again.

If you don't get any reports, no action is required from you. Your changes are working as expected, well done!

@fmayer
Copy link
Contributor

fmayer commented Dec 9, 2025

This might have caused buildbot errors. Running some builds to confirm, but in case it looks familiar posting it now:

https://lab.llvm.org/buildbot/#/builders/164/builds/16465/steps/11/logs/stdio

[ RUN      ] LSPTest.CDBConfigIntegration
 #0 0x0000580ed5359812 ___interceptor_backtrace /home/b/sanitizer-x86_64-linux-bootstrap-msan/build/llvm-project/compiler-rt/lib/msan/../sanitizer_common/sanitizer_common_interceptors.inc:4556:13
 #1 0x0000580ed6d2028f llvm::sys::PrintStackTrace(llvm::raw_ostream&, int) /home/b/sanitizer-x86_64-linux-bootstrap-msan/build/llvm-project/llvm/lib/Support/Unix/Signals.inc:0:13
 #2 0x0000580ed6d19648 llvm::sys::RunSignalHandlers() /home/b/sanitizer-x86_64-linux-bootstrap-msan/build/llvm-project/llvm/lib/Support/Signals.cpp:0:5
 #3 0x0000580ed6d2278a SignalHandler(int, siginfo_t*, void*) /home/b/sanitizer-x86_64-linux-bootstrap-msan/build/llvm-project/llvm/lib/Support/Unix/Signals.inc:426:38
 #4 0x0000580ed538d58e IsInInterceptorScope /home/b/sanitizer-x86_64-linux-bootstrap-msan/build/llvm-project/compiler-rt/lib/msan/msan_interceptors.cpp:78:10
 #5 0x0000580ed538d58e SignalAction(int, void*, void*) /home/b/sanitizer-x86_64-linux-bootstrap-msan/build/llvm-project/compiler-rt/lib/msan/msan_interceptors.cpp:1167:3
 #6 0x00007ec17a4458d0 (/lib/x86_64-linux-gnu/libc.so.6+0x458d0)
 #7 0x00007ec17a4a49bc pthread_kill (/lib/x86_64-linux-gnu/libc.so.6+0xa49bc)
 #8 0x00007ec17a44579e raise (/lib/x86_64-linux-gnu/libc.so.6+0x4579e)
 #9 0x00007ec17a4288cd abort (/lib/x86_64-linux-gnu/libc.so.6+0x288cd)
#10 0x0000580ed531addc (/home/b/sanitizer-x86_64-linux-bootstrap-msan/build/llvm_build_msan/tools/clang/tools/extra/clangd/unittests/./ClangdTests+0x1666ddc)
#11 0x0000580ed531986e __sanitizer::Die() /home/b/sanitizer-x86_64-linux-bootstrap-msan/build/llvm-project/compiler-rt/lib/sanitizer_common/sanitizer_termination.cpp:52:5
#12 0x0000580ed532ceb3 (/home/b/sanitizer-x86_64-linux-bootstrap-msan/build/llvm_build_msan/tools/clang/tools/extra/clangd/unittests/./ClangdTests+0x1678eb3)
#13 0x0000580edad88299 reset /home/b/sanitizer-x86_64-linux-bootstrap-msan/build/libcxx_install_msan/include/c++/v1/__memory/unique_ptr.h:287:9
#14 0x0000580edad88299 operator=<clang::clangd::DirectoryBasedGlobalCompilationDatabase, std::__1::default_delete<clang::clangd::DirectoryBasedGlobalCompilationDatabase>, void, void> /home/b/sanitizer-x86_64-linux-bootstrap-msan/build/libcxx_install_msan/include/c++/v1/__memory/unique_ptr.h:235:5
#15 0x0000580edad88299 clang::clangd::ClangdLSPServer::onInitialize(clang::clangd::InitializeParams const&, llvm::unique_function<void (llvm::Expected<llvm::json::Value>)>) /home/b/sanitizer-x86_64-linux-bootstrap-msan/build/llvm-project/clang-tools-extra/clangd/ClangdLSPServer.cpp:559:13
#16 0x0000580edae002b8 asInt /home/b/sanitizer-x86_64-linux-bootstrap-msan/build/llvm-project/llvm/include/llvm/ADT/PointerIntPair.h:41:5
#17 0x0000580edae002b8 operator long /home/b/sanitizer-x86_64-linux-bootstrap-msan/build/llvm-project/llvm/include/llvm/ADT/PointerIntPair.h:45:48
#18 0x0000580edae002b8 getPointer /home/b/sanitizer-x86_64-linux-bootstrap-msan/build/llvm-project/llvm/include/llvm/ADT/PointerIntPair.h:94:58
#19 0x0000580edae002b8 ~UniqueFunctionBase /home/b/sanitizer-x86_64-linux-bootstrap-msan/build/llvm-project/llvm/include/llvm/ADT/FunctionExtras.h:274:32
#20 0x0000580edae002b8 void clang::clangd::LSPBinder::method<clang::clangd::InitializeParams, llvm::json::Value, clang::clangd::ClangdLSPServer>(llvm::StringLiteral, clang::clangd::ClangdLSPServer*, void (clang::clangd::ClangdLSPServer::*)(clang::clangd::InitializeParams const&, llvm::unique_function<void (llvm::Expected<llvm::json::Value>)>))::'lambda'(llvm::json::Value, llvm::unique_function<void (llvm::Expected<llvm::json::Value>)>)::operator()(llvm::json::Value, llvm::unique_function<void (llvm::Expected<llvm::json::Value>)>) const /home/b/sanitizer-x86_64-linux-bootstrap-msan/build/llvm-project/clang-tools-extra/clangd/LSPBinder.h:141:5
#21 0x0000580edadffc12 asInt /home/b/sanitizer-x86_64-linux-bootstrap-msan/build/llvm-project/llvm/include/llvm/ADT/PointerIntPair.h:41:5
#22 0x0000580edadffc12 operator long /home/b/sanitizer-x86_64-linux-bootstrap-msan/build/llvm-project/llvm/include/llvm/ADT/PointerIntPair.h:45:48
#23 0x0000580edadffc12 getPointer /home/b/sanitizer-x86_64-linux-bootstrap-msan/build/llvm-project/llvm/include/llvm/ADT/PointerIntPair.h:94:58
#24 0x0000580edadffc12 ~UniqueFunctionBase /home/b/sanitizer-x86_64-linux-bootstrap-msan/build/llvm-project/llvm/include/llvm/ADT/FunctionExtras.h:274:32
#25 0x0000580edadffc12 void llvm::detail::UniqueFunctionBase<void, llvm::json::Value, llvm::unique_function<void (llvm::Expected<llvm::json::Value>)>>::CallImpl<void clang::clangd::LSPBinder::method<clang::clangd::InitializeParams, llvm::json::Value, clang::clangd::ClangdLSPServer>(llvm::StringLiteral, clang::clangd::ClangdLSPServer*, void (clang::clangd::ClangdLSPServer::*)(clang::clangd::InitializeParams const&, llvm::unique_function<void (llvm::Expected<llvm::json::Value>)>))::'lambda'(llvm::json::Value, llvm::unique_function<void (llvm::Expected<llvm::json::Value>)>)>(void*, llvm::json::Value&, llvm::unique_function<void (llvm::Expected<llvm::json::Value>)>&) /home/b/sanitizer-x86_64-linux-bootstrap-msan/build/llvm-project/llvm/include/llvm/ADT/FunctionExtras.h:212:5
#26 0x0000580edadc328a asInt /home/b/sanitizer-x86_64-linux-bootstrap-msan/build/llvm-project/llvm/include/llvm/ADT/PointerIntPair.h:41:5
#27 0x0000580edadc328a operator long /home/b/sanitizer-x86_64-linux-bootstrap-msan/build/llvm-project/llvm/include/llvm/ADT/PointerIntPair.h:45:48
#28 0x0000580edadc328a getPointer /home/b/sanitizer-x86_64-linux-bootstrap-msan/build/llvm-project/llvm/include/llvm/ADT/PointerIntPair.h:94:58
#29 0x0000580edadc328a ~UniqueFunctionBase /home/b/sanitizer-x86_64-linux-bootstrap-msan/build/llvm-project/llvm/include/llvm/ADT/FunctionExtras.h:274:32
#30 0x0000580edadc328a clang::clangd::ClangdLSPServer::MessageHandler::onCall(llvm::StringRef, llvm::json::Value, llvm::json::Value) /home/b/sanitizer-x86_64-linux-bootstrap-msan/build/llvm-project/clang-tools-extra/clangd/ClangdLSPServer.cpp:243:7
#31 0x0000580ed62f8339 ~Value /home/b/sanitizer-x86_64-linux-bootstrap-msan/build/llvm-project/llvm/include/llvm/Support/JSON.h:385:14
#32 0x0000580ed62f8339 operator() /home/b/sanitizer-x86_64-linux-bootstrap-msan/build/llvm-project/clang-tools-extra/clangd/unittests/LSPClient.cpp:186:5
#33 0x0000580ed62f8339 __invoke<(lambda at /home/b/sanitizer-x86_64-linux-bootstrap-msan/build/llvm-project/clang-tools-extra/clangd/unittests/LSPClient.cpp:183:14) &, clang::clangd::Transport::MessageHandler &> /home/b/sanitizer-x86_64-linux-bootstrap-msan/build/libcxx_install_msan/include/c++/v1/__type_traits/invoke.h:90:27
#34 0x0000580ed62f8339 __call<(lambda at /home/b/sanitizer-x86_64-linux-bootstrap-msan/build/llvm-project/clang-tools-extra/clangd/unittests/LSPClient.cpp:183:14) &, clang::clangd::Transport::MessageHandler &> /home/b/sanitizer-x86_64-linux-bootstrap-msan/build/libcxx_install_msan/include/c++/v1/__type_traits/invoke.h:350:5
#35 0x0000580ed62f8339 __invoke_r<void, (lambda at /home/b/sanitizer-x86_64-linux-bootstrap-msan/build/llvm-project/clang-tools-extra/clangd/unittests/LSPClient.cpp:183:14) &, clang::clangd::Transport::MessageHandler &> /home/b/sanitizer-x86_64-linux-bootstrap-msan/build/libcxx_install_msan/include/c++/v1/__type_traits/invoke.h:356:10
#36 0x0000580ed62f8339 std::__1::__function::__func<clang::clangd::LSPClient::call(llvm::StringRef, llvm::json::Value)::$_0, void (clang::clangd::Transport::MessageHandler&)>::operator()(clang::clangd::Transport::MessageHandler&) /home/b/sanitizer-x86_64-linux-bootstrap-msan/build/libcxx_install_msan/include/c++/v1/__functional/function.h:172:12
#37 0x0000580ed62f6817 lock /home/b/sanitizer-x86_64-linux-bootstrap-msan/build/libcxx_install_msan/include/c++/v1/__mutex/unique_lock.h:118:7
#38 0x0000580ed62f6817 clang::clangd::LSPClient::TransportImpl::loop(clang::clangd::Transport::MessageHandler&) /home/b/sanitizer-x86_64-linux-bootstrap-msan/build/llvm-project/clang-tools-extra/clangd/unittests/LSPClient.cpp:165:12
#39 0x0000580edadb5cb4 getPtr /home/b/sanitizer-x86_64-linux-bootstrap-msan/build/llvm-project/llvm/include/llvm/Support/Error.h:278:42
#40 0x0000580edadb5cb4 operator bool /home/b/sanitizer-x86_64-linux-bootstrap-msan/build/llvm-project/llvm/include/llvm/Support/Error.h:241:16
#41 0x0000580edadb5cb4 clang::clangd::ClangdLSPServer::run() /home/b/sanitizer-x86_64-linux-bootstrap-msan/build/llvm-project/clang-tools-extra/clangd/ClangdLSPServer.cpp:1758:12
#42 0x0000580ed5811ca0 AssertionResult<bool> /home/b/sanitizer-x86_64-linux-bootstrap-msan/build/llvm-project/third-party/unittest/googletest/include/gtest/gtest-assertion-result.h:161:9
#43 0x0000580ed5811ca0 operator() /home/b/sanitizer-x86_64-linux-bootstrap-msan/build/llvm-project/clang-tools-extra/clangd/unittests/ClangdLSPServerTests.cpp:71:32
#44 0x0000580ed5811ca0 __invoke<(lambda at /home/b/sanitizer-x86_64-linux-bootstrap-msan/build/llvm-project/clang-tools-extra/clangd/unittests/ClangdLSPServerTests.cpp:71:26)> /home/b/sanitizer-x86_64-linux-bootstrap-msan/build/libcxx_install_msan/include/c++/v1/__type_traits/invoke.h:90:27
#45 0x0000580ed5811ca0 __thread_execute<std::__1::unique_ptr<std::__1::__thread_struct, std::__1::default_delete<std::__1::__thread_struct> >, (lambda at /home/b/sanitizer-x86_64-linux-bootstrap-msan/build/llvm-project/clang-tools-extra/clangd/unittests/ClangdLSPServerTests.cpp:71:26), 0UL> /home/b/sanitizer-x86_64-linux-bootstrap-msan/build/libcxx_install_msan/include/c++/v1/__thread/thread.h:159:3
#46 0x0000580ed5811ca0 void* std::__1::__thread_proxy[abi:nqn220000]<std::__1::tuple<std::__1::unique_ptr<std::__1::__thread_struct, std::__1::default_delete<std::__1::__thread_struct>>, clang::clangd::(anonymous namespace)::LSPTest::start()::'lambda'()>>(void*) /home/b/sanitizer-x86_64-linux-bootstrap-msan/build/libcxx_install_msan/include/c++/v1/__thread/thread.h:167:3
#47 0x00007ec17a4a27f1 (/lib/x86_64-linux-gnu/libc.so.6+0xa27f1)
#48 0x00007ec17a533b5c (/lib/x86_64-linux-gnu/libc.so.6+0x133b5c)

boomanaiden154 added a commit that referenced this pull request Dec 10, 2025
#155905)"

This reverts commit 2fa4927.

This caused sanitizer bots to fail and sanitizer errors to show up in
our downstream testing:

```
[ RUN      ] LSPTest.DiagnosticsHeaderSaved
<<< initialize: {}
<-- initialize(0)
third_party/llvm/llvm-project/clang-tools-extra/clangd/ClangdLSPServer.cpp:557:14: runtime error: load of value 112, which is not a valid value for type 'bool'
```

With ASan at -O1.
@boomanaiden154
Copy link
Contributor

Seeing the same thing with ASan at -O1:

<<< initialize: {}
<-- initialize(0)
third_party/llvm/llvm-project/clang-tools-extra/clangd/ClangdLSPServer.cpp:557:14: runtime error: load of value 112, which is not a valid value for type 'bool'

Only --gtest_filter="LSPTest*:GlobalCompilationDatabaseTest*" is needed to reproduce this.

I've reverted this in 01c4eb5.

@HighCommander4
Copy link
Collaborator

Seeing the same thing with ASan at -O1:

<<< initialize: {}
<-- initialize(0)
third_party/llvm/llvm-project/clang-tools-extra/clangd/ClangdLSPServer.cpp:557:14: runtime error: load of value 112, which is not a valid value for type 'bool'

Only --gtest_filter="LSPTest*:GlobalCompilationDatabaseTest*" is needed to reproduce this.

Do you happen to have the full ASAN output for this? It may allow us to understand the issue without having to do a local ASAN build.

@boomanaiden154
Copy link
Contributor

boomanaiden154 commented Dec 10, 2025

Details
third_party/llvm/llvm-project/clang-tools-extra/clangd/ClangdLSPServer.cpp:557:14: runtime error: load of value 49, which is not a valid value for type 'bool'
    #0 0x7f7fd30a1f29 in clang::clangd::ClangdLSPServer::onInitialize(clang::clangd::InitializeParams const&, llvm::unique_function<void (llvm::Expected<llvm::json::Value>)>) third_party/llvm/llvm-project/clang-tools-extra/clangd/ClangdLSPServer.cpp:557:14
    #1 0x7f7fd30fe138 in void clang::clangd::LSPBinder::method<clang::clangd::InitializeParams, llvm::json::Value, clang::clangd::ClangdLSPServer>(llvm::StringLiteral, clang::clangd::ClangdLSPServer*, void (clang::clangd::ClangdLSPServer::*)(clang::clangd::InitializeParams const&, llvm::unique_function<void (llvm::Expected<llvm::json::Value>)>))::'lambda'(llvm::json::Value, llvm::unique_function<void (llvm::Expected<llvm::json::Value>)>)::operator()(llvm::json::Value, llvm::unique_function<void (llvm::Expected<llvm::json::Value>)>) const third_party/llvm/llvm-project/clang-tools-extra/clangd/LSPBinder.h:141:5
    #2 0x7f7fd30fddeb in void llvm::detail::UniqueFunctionBase<void, llvm::json::Value, llvm::unique_function<void (llvm::Expected<llvm::json::Value>)>>::CallImpl<void clang::clangd::LSPBinder::method<clang::clangd::InitializeParams, llvm::json::Value, clang::clangd::ClangdLSPServer>(llvm::StringLiteral, clang::clangd::ClangdLSPServer*, void (clang::clangd::ClangdLSPServer::*)(clang::clangd::InitializeParams const&, llvm::unique_function<void (llvm::Expected<llvm::json::Value>)>))::'lambda'(llvm::json::Value, llvm::unique_function<void (llvm::Expected<llvm::json::Value>)>)>(void*, llvm::json::Value&, llvm::unique_function<void (llvm::Expected<llvm::json::Value>)>&) third_party/llvm/llvm-project/llvm/include/llvm/ADT/FunctionExtras.h:212:12
    #3 0x7f7fd30cf794 in operator() third_party/llvm/llvm-project/llvm/include/llvm/ADT/FunctionExtras.h:364:12
    #4 0x7f7fd30cf794 in clang::clangd::ClangdLSPServer::MessageHandler::onCall(llvm::StringRef, llvm::json::Value, llvm::json::Value) third_party/llvm/llvm-project/clang-tools-extra/clangd/ClangdLSPServer.cpp:243:7
    #5 0x7f7fd8c443d6 in operator() third_party/llvm/llvm-project/clang-tools-extra/clangd/unittests/LSPClient.cpp:186:7
    #6 0x7f7fd8c443d6 in __invoke<(lambda at third_party/llvm/llvm-project/clang-tools-extra/clangd/unittests/LSPClient.cpp:183:14) &, clang::clangd::Transport::MessageHandler &> <compiler sources>/src/libcxx/include/__type_traits/invoke.h:90:27
    #7 0x7f7fd8c443d6 in __call<(lambda at third_party/llvm/llvm-project/clang-tools-extra/clangd/unittests/LSPClient.cpp:183:14) &, clang::clangd::Transport::MessageHandler &> <compiler sources>/src/libcxx/include/__type_traits/invoke.h:350:5
    #8 0x7f7fd8c443d6 in __invoke_r<void, (lambda at third_party/llvm/llvm-project/clang-tools-extra/clangd/unittests/LSPClient.cpp:183:14) &, clang::clangd::Transport::MessageHandler &> <compiler sources>/src/libcxx/include/__type_traits/invoke.h:356:10
    #9 0x7f7fd8c443d6 in void std::__u::__function::__policy_func<void (clang::clangd::Transport::MessageHandler&)>::__call_func<clang::clangd::LSPClient::call(llvm::StringRef, llvm::json::Value)::$_0>(std::__u::__function::__policy_storage const*, clang::clangd::Transport::MessageHandler&) <compiler sources>/src/libcxx/include/__functional/function.h:443:12
    #10 0x7f7fd8c4320f in operator() <compiler sources>/src/libcxx/include/__functional/function.h:502:12
    #11 0x7f7fd8c4320f in operator() <compiler sources>/src/libcxx/include/__functional/function.h:754:10
    #12 0x7f7fd8c4320f in clang::clangd::LSPClient::TransportImpl::loop(clang::clangd::Transport::MessageHandler&) third_party/llvm/llvm-project/clang-tools-extra/clangd/unittests/LSPClient.cpp:164:7
    #13 0x7f7fd30bd29a in clang::clangd::ClangdLSPServer::run() third_party/llvm/llvm-project/clang-tools-extra/clangd/ClangdLSPServer.cpp:1758:25
    #14 0x7f7fd84c47f9 in operator() third_party/llvm/llvm-project/clang-tools-extra/clangd/unittests/ClangdLSPServerTests.cpp:71:52
    #15 0x7f7fd84c47f9 in __invoke<(lambda at third_party/llvm/llvm-project/clang-tools-extra/clangd/unittests/ClangdLSPServerTests.cpp:71:26)> <compiler sources>/src/libcxx/include/__type_traits/invoke.h:90:27
    #16 0x7f7fd84c47f9 in __thread_execute<std::__u::unique_ptr<std::__u::__thread_struct, std::__u::default_delete<std::__u::__thread_struct> >, (lambda at third_party/llvm/llvm-project/clang-tools-extra/clangd/unittests/ClangdLSPServerTests.cpp:71:26), 0UL> <compiler sources>/src/libcxx/include/__thread/thread.h:159:3
    #17 0x7f7fd84c47f9 in void* std::__u::__thread_proxy<std::__u::tuple<std::__u::unique_ptr<std::__u::__thread_struct, std::__u::default_delete<std::__u::__thread_struct>>, clang::clangd::(anonymous namespace)::LSPTest::start()::'lambda'()>>(void*) <compiler sources>/src/libcxx/include/__thread/thread.h:167:3
    #18 0x56224d8e2290 in asan_thread_start(void*) third_party/llvm/llvm-project/compiler-rt/lib/asan/asan_interceptors.cpp:246:28
    #19 0x7f7fc3b847dc in start_thread (libpthread.so.0)
    #20 0x7f7ba4ec6b7e in clone (libc.so.6)

SUMMARY: UndefinedBehaviorSanitizer: invalid-bool-load third_party/llvm/llvm-project/clang-tools-extra/clangd/ClangdLSPServer.cpp:557:14

is the stack trace I got. ASan didn't print out anything beyond that.

@HighCommander4
Copy link
Collaborator

Ah, I see, so it's actually a UBSan error, and it's complaining at the read of ClangdServer::Options::StrongWorkspaceMode in ClangdLSPServer::onInitialize() that we're reading a value of 49 from a bool, which suggests that bool was not initialized.

Should be a simple fix, just need to give this field a class member initializer.

@Dominicentek heads up that this has been reverted and you'll need to resubmit with this fix to move forward with this change

@Dominicentek
Copy link
Contributor Author

Yeah thanks, I'm aware.
I'll get to it in a couple of days when I'm not busy.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants