[clang] [clang-tools-extra] [Tooling] Resolve relative "directory" against the compilation database (PR #235400)
via cfe-commits
cfe-commits at lists.llvm.org
Sat Oct 24 23:02:12 PDT 2026
llvmorg-github-actions[bot] wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-clang-tools-extra
Author: Fangrui Song (MaskRay)
<details>
<summary>Changes</summary>
JSONCompilationDatabase does not resolve a relative "directory", e.g.
`{"directory": ".", "command": "cc ../src/a.c", "file": "../src/a.c"}`.
getCompileCommands finds nothing, so clang-check reports "Compile
command not found" and clangd uses a fallback command.
getAllCompileCommands returns the directory verbatim, so each consumer
resolves it against its own working directory.
Resolve it against the directory containing compile_commands.json,
after following symlinks, so that a compile_commands.json symlinked from
the source root into the build directory keeps its meaning. Since
loadFromBuffer has no file path, it takes the base directory as a new
first parameter.
LLM-aided
---
Full diff: https://github.com/llvm/llvm-project/pull/235400.diff
7 Files Affected:
- (modified) clang-tools-extra/clangd/GlobalCompilationDatabase.cpp (+13-5)
- (modified) clang/docs/JSONCompilationDatabase.md (+1)
- (modified) clang/docs/ReleaseNotes.md (+2)
- (modified) clang/include/clang/Tooling/JSONCompilationDatabase.h (+16-11)
- (modified) clang/lib/Tooling/JSONCompilationDatabase.cpp (+24-13)
- (modified) clang/unittests/Tooling/CompilationDatabaseTest.cpp (+63-5)
- (modified) clang/unittests/Tooling/ToolingTest.cpp (+1-1)
``````````diff
diff --git a/clang-tools-extra/clangd/GlobalCompilationDatabase.cpp b/clang-tools-extra/clangd/GlobalCompilationDatabase.cpp
index adb771ecbbaada..7e12a040345a17 100644
--- a/clang-tools-extra/clangd/GlobalCompilationDatabase.cpp
+++ b/clang-tools-extra/clangd/GlobalCompilationDatabase.cpp
@@ -247,9 +247,15 @@ DirectoryBasedGlobalCompilationDatabase::DirectoryCache::CachedFile::load(
// Adapt CDB-loading functions to a common interface for DirectoryCache::load().
static std::unique_ptr<tooling::CompilationDatabase>
-parseJSON(PathRef Path, llvm::StringRef Data, std::string &Error) {
+parseJSON(PathRef Path, llvm::StringRef Data, llvm::vfs::FileSystem &FS,
+ std::string &Error) {
+ llvm::SmallString<256> RealPath;
+ llvm::StringRef BaseDirectory;
+ if (!FS.getRealPath(Path, RealPath))
+ BaseDirectory = llvm::sys::path::parent_path(RealPath);
if (auto CDB = tooling::JSONCompilationDatabase::loadFromBuffer(
- Data, Error, tooling::JSONCommandLineSyntax::AutoDetect)) {
+ BaseDirectory, Data, Error,
+ tooling::JSONCommandLineSyntax::AutoDetect)) {
// FS used for expanding response files.
// FIXME: ExpandResponseFilesDatabase appears not to provide the usual
// thread-safety guarantees, as the access to FS is not locked!
@@ -264,7 +270,8 @@ parseJSON(PathRef Path, llvm::StringRef Data, std::string &Error) {
return nullptr;
}
static std::unique_ptr<tooling::CompilationDatabase>
-parseFixed(PathRef Path, llvm::StringRef Data, std::string &Error) {
+parseFixed(PathRef Path, llvm::StringRef Data, llvm::vfs::FileSystem &,
+ std::string &Error) {
return tooling::FixedCompilationDatabase::loadFromBuffer(
llvm::sys::path::parent_path(Path), Data, Error);
}
@@ -283,7 +290,7 @@ bool DirectoryBasedGlobalCompilationDatabase::DirectoryCache::load(
// Wrapper for {Fixed,JSON}CompilationDatabase::loadFromBuffer.
std::unique_ptr<tooling::CompilationDatabase> (*Parser)(
PathRef,
- /*Data*/ llvm::StringRef,
+ /*Data*/ llvm::StringRef, llvm::vfs::FileSystem &,
/*ErrorMsg*/ std::string &);
};
for (const auto &Entry : {CDBFile{&CompileCommandsJson, parseJSON},
@@ -309,7 +316,8 @@ bool DirectoryBasedGlobalCompilationDatabase::DirectoryCache::load(
return true;
case CachedFile::LoadResult::FoundNewData:
// We have a new CDB!
- CDB = Entry.Parser(Entry.File->Path, Loaded.Buffer->getBuffer(), Error);
+ CDB =
+ Entry.Parser(Entry.File->Path, Loaded.Buffer->getBuffer(), FS, Error);
if (CDB)
log("{0} compilation database from {1}", Active ? "Reloaded" : "Loaded",
Entry.File->Path);
diff --git a/clang/docs/JSONCompilationDatabase.md b/clang/docs/JSONCompilationDatabase.md
index 35fdea8fc3b1a5..649d164d2d52d3 100644
--- a/clang/docs/JSONCompilationDatabase.md
+++ b/clang/docs/JSONCompilationDatabase.md
@@ -76,6 +76,7 @@ The contracts for each field in the command object are:
- **directory:** The working directory of the compilation. All paths
specified in the **command** or **file** fields must be either
absolute or relative to this directory.
+ A relative **directory** is relative to the directory containing the compilation database file, after resolving symlinks.
- **file:** The main translation unit source processed by this
compilation step. This is used by tools as the key into the
diff --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md
index 44f30666457f94..e7ee0001f4e4a3 100644
--- a/clang/docs/ReleaseNotes.md
+++ b/clang/docs/ReleaseNotes.md
@@ -312,6 +312,8 @@ documentation](CIR/index.md) for more information regarding ClangIR's status and
- Clang tools now resolve tool names without a path in compilation databases
through `PATH`.
+- A relative `directory` in a JSON compilation database is now resolved against the directory containing the compilation database file, after resolving symlinks.
+
- On musl targets, the driver now links ``libssp_nonshared.a`` when stack
protection is enabled and the library is present in the toolchain library
paths, matching what musl distributions configure GCC to do.
diff --git a/clang/include/clang/Tooling/JSONCompilationDatabase.h b/clang/include/clang/Tooling/JSONCompilationDatabase.h
index 96582457c63d58..8350ca6a65d134 100644
--- a/clang/include/clang/Tooling/JSONCompilationDatabase.h
+++ b/clang/include/clang/Tooling/JSONCompilationDatabase.h
@@ -20,6 +20,7 @@
#include "llvm/ADT/ArrayRef.h"
#include "llvm/ADT/StringMap.h"
#include "llvm/ADT/StringRef.h"
+#include "llvm/ADT/StringSet.h"
#include "llvm/Support/MemoryBuffer.h"
#include "llvm/Support/SourceMgr.h"
#include "llvm/Support/YAMLParser.h"
@@ -60,7 +61,9 @@ namespace tooling {
enum class JSONCommandLineSyntax { Windows, Gnu, AutoDetect };
class JSONCompilationDatabase : public CompilationDatabase {
public:
- /// Loads a JSON compilation database from the specified file.
+ /// Loads a JSON compilation database from the specified file. A relative
+ /// "directory" is resolved against the directory containing the file, after
+ /// resolving symlinks.
///
/// Returns NULL and sets ErrorMessage if the database could not be
/// loaded from the given file.
@@ -68,12 +71,13 @@ class JSONCompilationDatabase : public CompilationDatabase {
loadFromFile(StringRef FilePath, std::string &ErrorMessage,
JSONCommandLineSyntax Syntax);
- /// Loads a JSON compilation database from a data buffer.
+ /// Loads a JSON compilation database from a data buffer. If BaseDirectory
+ /// is non-empty, a relative "directory" is resolved against it.
///
/// Returns NULL and sets ErrorMessage if the database could not be loaded.
static std::unique_ptr<JSONCompilationDatabase>
- loadFromBuffer(StringRef DatabaseString, std::string &ErrorMessage,
- JSONCommandLineSyntax Syntax);
+ loadFromBuffer(StringRef BaseDirectory, StringRef DatabaseString,
+ std::string &ErrorMessage, JSONCommandLineSyntax Syntax);
/// Returns all compile commands in which the specified file was
/// compiled.
@@ -103,19 +107,19 @@ class JSONCompilationDatabase : public CompilationDatabase {
///
/// Returns whether parsing succeeded. Sets ErrorMessage if parsing
/// failed.
- bool parse(std::string &ErrorMessage);
+ bool parse(StringRef BaseDirectory, std::string &ErrorMessage);
- // Tuple (directory, filename, commandline, output) where 'commandline'
- // points to the corresponding scalar nodes in the YAML stream.
+ // Tuple (directory, filename, commandline, output) where 'directory' is
+ // owned by Directories and the others point to the corresponding scalar
+ // nodes in the YAML stream.
// If the command line contains a single argument, it is a shell-escaped
// command line.
// Otherwise, each entry in the command line vector is a literal
// argument to the compiler.
// The output field may be a nullptr.
- using CompileCommandRef =
- std::tuple<llvm::yaml::ScalarNode *, llvm::yaml::ScalarNode *,
- std::vector<llvm::yaml::ScalarNode *>,
- llvm::yaml::ScalarNode *>;
+ using CompileCommandRef = std::tuple<StringRef, llvm::yaml::ScalarNode *,
+ std::vector<llvm::yaml::ScalarNode *>,
+ llvm::yaml::ScalarNode *>;
/// Converts the given array of CompileCommandRefs to CompileCommands.
void getCommands(ArrayRef<CompileCommandRef> CommandsRef,
@@ -130,6 +134,7 @@ class JSONCompilationDatabase : public CompilationDatabase {
FileMatchTrie MatchTrie;
+ llvm::StringSet<> Directories;
std::unique_ptr<llvm::MemoryBuffer> Database;
JSONCommandLineSyntax Syntax;
llvm::SourceMgr SM;
diff --git a/clang/lib/Tooling/JSONCompilationDatabase.cpp b/clang/lib/Tooling/JSONCompilationDatabase.cpp
index 0efa75970d9864..5f54a74b308161 100644
--- a/clang/lib/Tooling/JSONCompilationDatabase.cpp
+++ b/clang/lib/Tooling/JSONCompilationDatabase.cpp
@@ -197,22 +197,27 @@ JSONCompilationDatabase::loadFromFile(StringRef FilePath,
ErrorMessage = "Error while opening JSON database: " + Result.message();
return nullptr;
}
+ SmallString<128> RealPath;
+ StringRef BaseDirectory;
+ if (!llvm::sys::fs::real_path(FilePath, RealPath))
+ BaseDirectory = llvm::sys::path::parent_path(RealPath);
std::unique_ptr<JSONCompilationDatabase> Database(
new JSONCompilationDatabase(std::move(*DatabaseBuffer), Syntax));
- if (!Database->parse(ErrorMessage))
+ if (!Database->parse(BaseDirectory, ErrorMessage))
return nullptr;
return Database;
}
std::unique_ptr<JSONCompilationDatabase>
-JSONCompilationDatabase::loadFromBuffer(StringRef DatabaseString,
+JSONCompilationDatabase::loadFromBuffer(StringRef BaseDirectory,
+ StringRef DatabaseString,
std::string &ErrorMessage,
JSONCommandLineSyntax Syntax) {
std::unique_ptr<llvm::MemoryBuffer> DatabaseBuffer(
llvm::MemoryBuffer::getMemBufferCopy(DatabaseString));
std::unique_ptr<JSONCompilationDatabase> Database(
new JSONCompilationDatabase(std::move(DatabaseBuffer), Syntax));
- if (!Database->parse(ErrorMessage))
+ if (!Database->parse(BaseDirectory, ErrorMessage))
return nullptr;
return Database;
}
@@ -308,19 +313,18 @@ void JSONCompilationDatabase::getCommands(
ArrayRef<CompileCommandRef> CommandsRef,
std::vector<CompileCommand> &Commands) const {
for (const auto &CommandRef : CommandsRef) {
- SmallString<8> DirectoryStorage;
SmallString<32> FilenameStorage;
SmallString<32> OutputStorage;
auto Output = std::get<3>(CommandRef);
- Commands.emplace_back(
- std::get<0>(CommandRef)->getValue(DirectoryStorage),
- std::get<1>(CommandRef)->getValue(FilenameStorage),
- nodeToCommandLine(Syntax, std::get<2>(CommandRef)),
- Output ? Output->getValue(OutputStorage) : "");
+ Commands.emplace_back(std::get<0>(CommandRef),
+ std::get<1>(CommandRef)->getValue(FilenameStorage),
+ nodeToCommandLine(Syntax, std::get<2>(CommandRef)),
+ Output ? Output->getValue(OutputStorage) : "");
}
}
-bool JSONCompilationDatabase::parse(std::string &ErrorMessage) {
+bool JSONCompilationDatabase::parse(StringRef BaseDirectory,
+ std::string &ErrorMessage) {
llvm::yaml::document_iterator I = YAMLStream.begin();
if (I == YAMLStream.end()) {
ErrorMessage = "Error while parsing YAML.";
@@ -409,19 +413,26 @@ bool JSONCompilationDatabase::parse(std::string &ErrorMessage) {
ErrorMessage = "Missing key: \"directory\".";
return false;
}
+ SmallString<8> DirectoryStorage;
+ SmallString<128> DirectoryPath(Directory->getValue(DirectoryStorage));
+ if (!BaseDirectory.empty() && !DirectoryPath.empty() &&
+ !llvm::sys::path::is_absolute(DirectoryPath)) {
+ llvm::sys::path::make_absolute(BaseDirectory, DirectoryPath);
+ llvm::sys::path::remove_dots(DirectoryPath);
+ }
+ StringRef DirectoryName = Directories.insert(DirectoryPath).first->getKey();
SmallString<8> FileStorage;
StringRef FileName = File->getValue(FileStorage);
SmallString<128> NativeFilePath;
if (llvm::sys::path::is_relative(FileName)) {
- SmallString<8> DirectoryStorage;
- SmallString<128> AbsolutePath(Directory->getValue(DirectoryStorage));
+ SmallString<128> AbsolutePath(DirectoryName);
llvm::sys::path::append(AbsolutePath, FileName);
llvm::sys::path::native(AbsolutePath, NativeFilePath);
} else {
llvm::sys::path::native(FileName, NativeFilePath);
}
llvm::sys::path::remove_dots(NativeFilePath, /*remove_dot_dot=*/true);
- auto Cmd = CompileCommandRef(Directory, File, *Command, Output);
+ auto Cmd = CompileCommandRef(DirectoryName, File, *Command, Output);
IndexByFile[NativeFilePath].push_back(Cmd);
AllCommands.push_back(Cmd);
MatchTrie.insert(NativeFilePath);
diff --git a/clang/unittests/Tooling/CompilationDatabaseTest.cpp b/clang/unittests/Tooling/CompilationDatabaseTest.cpp
index d571f992e763e6..5ce5223f0a4be7 100644
--- a/clang/unittests/Tooling/CompilationDatabaseTest.cpp
+++ b/clang/unittests/Tooling/CompilationDatabaseTest.cpp
@@ -12,6 +12,7 @@
#include "clang/Tooling/Tooling.h"
#include "llvm/Support/Path.h"
#include "llvm/Support/TargetSelect.h"
+#include "llvm/Testing/Support/SupportHelpers.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include <algorithm>
@@ -27,8 +28,8 @@ using testing::UnorderedElementsAreArray;
static void expectFailure(StringRef JSONDatabase, StringRef Explanation) {
std::string ErrorMessage;
EXPECT_EQ(nullptr,
- JSONCompilationDatabase::loadFromBuffer(JSONDatabase, ErrorMessage,
- JSONCommandLineSyntax::Gnu))
+ JSONCompilationDatabase::loadFromBuffer(
+ "", JSONDatabase, ErrorMessage, JSONCommandLineSyntax::Gnu))
<< "Expected an error because of: " << Explanation.str();
}
@@ -56,7 +57,7 @@ static std::vector<std::string> getAllFiles(StringRef JSONDatabase,
std::string &ErrorMessage,
JSONCommandLineSyntax Syntax) {
std::unique_ptr<CompilationDatabase> Database(
- JSONCompilationDatabase::loadFromBuffer(JSONDatabase, ErrorMessage,
+ JSONCompilationDatabase::loadFromBuffer("", JSONDatabase, ErrorMessage,
Syntax));
if (!Database) {
ADD_FAILURE() << ErrorMessage;
@@ -71,7 +72,7 @@ static std::vector<CompileCommand>
getAllCompileCommands(JSONCommandLineSyntax Syntax, StringRef JSONDatabase,
std::string &ErrorMessage) {
std::unique_ptr<CompilationDatabase> Database(
- JSONCompilationDatabase::loadFromBuffer(JSONDatabase, ErrorMessage,
+ JSONCompilationDatabase::loadFromBuffer("", JSONDatabase, ErrorMessage,
Syntax));
if (!Database) {
ADD_FAILURE() << ErrorMessage;
@@ -193,7 +194,7 @@ static CompileCommand findCompileArgsInJsonDatabase(StringRef FileName,
std::string JSONDatabase,
std::string &ErrorMessage) {
std::unique_ptr<CompilationDatabase> Database(
- JSONCompilationDatabase::loadFromBuffer(JSONDatabase, ErrorMessage,
+ JSONCompilationDatabase::loadFromBuffer("", JSONDatabase, ErrorMessage,
JSONCommandLineSyntax::Gnu));
if (!Database)
return CompileCommand();
@@ -225,6 +226,63 @@ TEST(JSONCompilationDatabase, ArgumentsPreferredOverCommand) {
EXPECT_EQ(Arguments, FoundCommand.CommandLine[0]) << ErrorMessage;
}
+static std::string native(StringRef Path) {
+ SmallString<32> Result;
+ llvm::sys::path::native(Path, Result);
+ return std::string(Result);
+}
+
+TEST(JSONCompilationDatabase, RelativeDirectory) {
+ StringRef JSONDatabase = R"([
+ {"directory": ".", "command": "cc a.cc", "file": "a.cc"},
+ {"directory": "../out", "command": "cc b.cc", "file": "b.cc"},
+ {"directory": "//net/abs/.", "command": "cc c.cc", "file": "c.cc"},
+ {"directory": "", "command": "cc d.cc", "file": "d.cc"}
+ ])";
+ std::string ErrorMessage;
+ std::unique_ptr<JSONCompilationDatabase> Database =
+ JSONCompilationDatabase::loadFromBuffer(
+ "//net/db", JSONDatabase, ErrorMessage, JSONCommandLineSyntax::Gnu);
+ ASSERT_TRUE(Database) << ErrorMessage;
+ std::vector<CompileCommand> Commands = Database->getAllCompileCommands();
+ ASSERT_EQ(4u, Commands.size());
+ EXPECT_EQ(native("//net/db"), Commands[0].Directory);
+ EXPECT_EQ(native("//net/db/../out"), Commands[1].Directory);
+ EXPECT_EQ("//net/abs/.", Commands[2].Directory);
+ EXPECT_EQ("", Commands[3].Directory);
+ EXPECT_EQ(1u, Database->getCompileCommands("//net/out/b.cc").size());
+
+ Database = JSONCompilationDatabase::loadFromBuffer(
+ "", JSONDatabase, ErrorMessage, JSONCommandLineSyntax::Gnu);
+ ASSERT_TRUE(Database) << ErrorMessage;
+ EXPECT_EQ(".", Database->getAllCompileCommands()[0].Directory);
+}
+
+TEST(JSONCompilationDatabase, RelativeDirectoryFromFile) {
+ llvm::unittest::TempDir Dir("relative-directory", /*Unique=*/true);
+ llvm::unittest::TempDir Build(Dir.path("build"));
+ llvm::unittest::TempFile File(
+ Build.path("compile_commands.json"), "",
+ R"([{"directory": ".", "command": "cc a.cc", "file": "../a.cc"}])");
+ auto Check = [&](StringRef DatabasePath) {
+ std::string ErrorMessage;
+ std::unique_ptr<JSONCompilationDatabase> Database =
+ JSONCompilationDatabase::loadFromFile(DatabasePath, ErrorMessage,
+ JSONCommandLineSyntax::Gnu);
+ ASSERT_TRUE(Database) << ErrorMessage;
+ std::vector<CompileCommand> Commands =
+ Database->getCompileCommands(Dir.path("a.cc"));
+ ASSERT_EQ(1u, Commands.size());
+ EXPECT_EQ(Build.path(), Commands[0].Directory);
+ };
+ Check(File.path());
+#ifndef _WIN32
+ // A symlink to the database does not change the base directory.
+ llvm::unittest::TempLink Link(File.path(), Dir.path("compile_commands.json"));
+ Check(Link.path());
+#endif
+}
+
struct FakeComparator : public PathComparator {
~FakeComparator() override {}
bool equivalent(StringRef FileA, StringRef FileB) const override {
diff --git a/clang/unittests/Tooling/ToolingTest.cpp b/clang/unittests/Tooling/ToolingTest.cpp
index 11a5491dd35fb1..96240f2bbcf26a 100644
--- a/clang/unittests/Tooling/ToolingTest.cpp
+++ b/clang/unittests/Tooling/ToolingTest.cpp
@@ -1096,7 +1096,7 @@ std::string runToolWithProgress(llvm::ArrayRef<TestCommand> Commands,
OS << llvm::json::Value(std::move(Entries));
std::unique_ptr<CompilationDatabase> Database(
- JSONCompilationDatabase::loadFromBuffer(DatabaseContent, ErrorMessage,
+ JSONCompilationDatabase::loadFromBuffer("", DatabaseContent, ErrorMessage,
JSONCommandLineSyntax::Gnu));
if (!Database) {
ADD_FAILURE() << "Failed to load compilation database: " << ErrorMessage;
``````````
</details>
https://github.com/llvm/llvm-project/pull/235400
More information about the cfe-commits
mailing list