Skip to content

Commit 806342b

Browse files
committed
[clangd] Resolve driver symlinks, and look up unknown relative drivers in PATH.
Summary: This fixes a reported bug: if clang and libc++ are installed under /usr/lib/llvm-11/... but there'- a symlink /usr/bin/clang++-11, then a compile_commands.json with "/usr/bin/clang++-11 -stdlib=libc++" would previously look for libc++ under /usr/include instead of /usr/lib/llvm-11/include. The PATH change makes this work if the compiler is just "clang++-11" too. As this is now doing IO potentially on every getCompileCommand(), we cache the results for each distinct driver. While here: - Added a Memoize helper for this as multithreaded caching is a bit noisy. - Used this helper to simplify QueryDriverDatabase and reduce blocking there. (This makes use of the fact that llvm::Regex is now threadsafe) Reviewers: kadircet Subscribers: jyknight, ormris, ilya-biryukov, MaskRay, jkorous, arphaman, jfb, usaxena95, cfe-commits Tags: #clang Differential Revision: https://reviews.llvm.org/D75414
1 parent af7587d commit 806342b

File tree

7 files changed

+242
-37
lines changed

7 files changed

+242
-37
lines changed

clang-tools-extra/clangd/ClangdLSPServer.cpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -514,7 +514,7 @@ void ClangdLSPServer::onInitialize(const InitializeParams &Params,
514514
if (ClangdServerOpts.ResourceDir)
515515
Mangler.ResourceDir = *ClangdServerOpts.ResourceDir;
516516
CDB.emplace(BaseCDB.get(), Params.initializationOptions.fallbackFlags,
517-
tooling::ArgumentsAdjuster(Mangler));
517+
tooling::ArgumentsAdjuster(std::move(Mangler)));
518518
{
519519
// Switch caller's context with LSPServer's background context. Since we
520520
// rather want to propagate information from LSPServer's context into the

clang-tools-extra/clangd/CompileCommands.cpp

+54-15
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,48 @@ std::string detectStandardResourceDir() {
119119
return CompilerInvocation::GetResourcesPath("clangd", (void *)&Dummy);
120120
}
121121

122+
// The path passed to argv[0] is important:
123+
// - its parent directory is Driver::Dir, used for library discovery
124+
// - its basename affects CLI parsing (clang-cl) and other settings
125+
// Where possible it should be an absolute path with sensible directory, but
126+
// with the original basename.
127+
static std::string resolveDriver(llvm::StringRef Driver, bool FollowSymlink,
128+
llvm::Optional<std::string> ClangPath) {
129+
auto SiblingOf = [&](llvm::StringRef AbsPath) {
130+
llvm::SmallString<128> Result = llvm::sys::path::parent_path(AbsPath);
131+
llvm::sys::path::append(Result, llvm::sys::path::filename(Driver));
132+
return Result.str().str();
133+
};
134+
135+
// First, eliminate relative paths.
136+
std::string Storage;
137+
if (!llvm::sys::path::is_absolute(Driver)) {
138+
// If the driver is a generic like "g++" with no path, add clang dir.
139+
if (ClangPath &&
140+
(Driver == "clang" || Driver == "clang++" || Driver == "gcc" ||
141+
Driver == "g++" || Driver == "cc" || Driver == "c++")) {
142+
return SiblingOf(*ClangPath);
143+
}
144+
// Otherwise try to look it up on PATH. This won't change basename.
145+
auto Absolute = llvm::sys::findProgramByName(Driver);
146+
if (Absolute && llvm::sys::path::is_absolute(*Absolute))
147+
Driver = Storage = std::move(*Absolute);
148+
else if (ClangPath) // If we don't find it, use clang dir again.
149+
return SiblingOf(*ClangPath);
150+
else // Nothing to do: can't find the command and no detected dir.
151+
return Driver.str();
152+
}
153+
154+
// Now we have an absolute path, but it may be a symlink.
155+
assert(llvm::sys::path::is_absolute(Driver));
156+
if (FollowSymlink) {
157+
llvm::SmallString<256> Resolved;
158+
if (!llvm::sys::fs::real_path(Driver, Resolved))
159+
return SiblingOf(Resolved);
160+
}
161+
return Driver.str();
162+
}
163+
122164
} // namespace
123165

124166
CommandMangler CommandMangler::detect() {
@@ -162,25 +204,22 @@ void CommandMangler::adjust(std::vector<std::string> &Cmd) const {
162204
Cmd.push_back(*Sysroot);
163205
}
164206

165-
// If the driver is a generic name like "g++" with no path, add a clang path.
166-
// This makes it easier for us to find the standard libraries on mac.
167-
if (ClangPath && llvm::sys::path::is_absolute(*ClangPath) && !Cmd.empty()) {
168-
std::string &Driver = Cmd.front();
169-
if (Driver == "clang" || Driver == "clang++" || Driver == "gcc" ||
170-
Driver == "g++" || Driver == "cc" || Driver == "c++") {
171-
llvm::SmallString<128> QualifiedDriver =
172-
llvm::sys::path::parent_path(*ClangPath);
173-
llvm::sys::path::append(QualifiedDriver, Driver);
174-
Driver = std::string(QualifiedDriver.str());
175-
}
207+
if (!Cmd.empty()) {
208+
bool FollowSymlink = !Has("-no-canonical-prefixes");
209+
Cmd.front() =
210+
(FollowSymlink ? ResolvedDrivers : ResolvedDriversNoFollow)
211+
.get(Cmd.front(), [&, this] {
212+
return resolveDriver(Cmd.front(), FollowSymlink, ClangPath);
213+
});
176214
}
177215
}
178216

179-
CommandMangler::operator clang::tooling::ArgumentsAdjuster() {
180-
return [Mangler{*this}](const std::vector<std::string> &Args,
181-
llvm::StringRef File) {
217+
CommandMangler::operator clang::tooling::ArgumentsAdjuster() && {
218+
// ArgumentsAdjuster is a std::function and so must be copyable.
219+
return [Mangler = std::make_shared<CommandMangler>(std::move(*this))](
220+
const std::vector<std::string> &Args, llvm::StringRef File) {
182221
auto Result = Args;
183-
Mangler.adjust(Result);
222+
Mangler->adjust(Result);
184223
return Result;
185224
};
186225
}

clang-tools-extra/clangd/CompileCommands.h

+5-1
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,10 @@
88
#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_COMPILECOMMANDS_H
99
#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_COMPILECOMMANDS_H
1010

11+
#include "support/Threading.h"
1112
#include "clang/Tooling/ArgumentsAdjusters.h"
1213
#include "clang/Tooling/CompilationDatabase.h"
14+
#include "llvm/ADT/StringMap.h"
1315
#include <string>
1416
#include <vector>
1517

@@ -40,10 +42,12 @@ struct CommandMangler {
4042
static CommandMangler detect();
4143

4244
void adjust(std::vector<std::string> &Cmd) const;
43-
explicit operator clang::tooling::ArgumentsAdjuster();
45+
explicit operator clang::tooling::ArgumentsAdjuster() &&;
4446

4547
private:
4648
CommandMangler() = default;
49+
Memoize<llvm::StringMap<std::string>> ResolvedDrivers;
50+
Memoize<llvm::StringMap<std::string>> ResolvedDriversNoFollow;
4751
};
4852

4953
} // namespace clangd

clang-tools-extra/clangd/QueryDriverDatabase.cpp

+9-19
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ std::vector<std::string> parseDriverOutput(llvm::StringRef Output) {
8888
std::vector<std::string>
8989
extractSystemIncludes(PathRef Driver, llvm::StringRef Lang,
9090
llvm::ArrayRef<std::string> CommandLine,
91-
llvm::Regex &QueryDriverRegex) {
91+
const llvm::Regex &QueryDriverRegex) {
9292
trace::Span Tracer("Extract system includes");
9393
SPAN_ATTACH(Tracer, "driver", Driver);
9494
SPAN_ATTACH(Tracer, "lang", Lang);
@@ -267,19 +267,12 @@ class QueryDriverDatabase : public GlobalCompilationDatabase {
267267

268268
llvm::SmallString<128> Driver(Cmd->CommandLine.front());
269269
llvm::sys::fs::make_absolute(Cmd->Directory, Driver);
270-
auto Key = std::make_pair(Driver.str().str(), Lang.str());
271270

272-
std::vector<std::string> SystemIncludes;
273-
{
274-
std::lock_guard<std::mutex> Lock(Mu);
275-
276-
auto It = DriverToIncludesCache.find(Key);
277-
if (It != DriverToIncludesCache.end())
278-
SystemIncludes = It->second;
279-
else
280-
DriverToIncludesCache[Key] = SystemIncludes = extractSystemIncludes(
281-
Key.first, Key.second, Cmd->CommandLine, QueryDriverRegex);
282-
}
271+
std::vector<std::string> SystemIncludes =
272+
QueriedDrivers.get(/*Key=*/(Driver + ":" + Lang).str(), [&] {
273+
return extractSystemIncludes(Driver, Lang, Cmd->CommandLine,
274+
QueryDriverRegex);
275+
});
283276

284277
return addSystemIncludes(*Cmd, SystemIncludes);
285278
}
@@ -289,12 +282,9 @@ class QueryDriverDatabase : public GlobalCompilationDatabase {
289282
}
290283

291284
private:
292-
mutable std::mutex Mu;
293-
// Caches includes extracted from a driver.
294-
mutable std::map<std::pair<std::string, std::string>,
295-
std::vector<std::string>>
296-
DriverToIncludesCache;
297-
mutable llvm::Regex QueryDriverRegex;
285+
// Caches includes extracted from a driver. Key is driver:lang.
286+
Memoize<llvm::StringMap<std::vector<std::string>>> QueriedDrivers;
287+
llvm::Regex QueryDriverRegex;
298288

299289
std::unique_ptr<GlobalCompilationDatabase> Base;
300290
CommandChanged::Subscription BaseChanged;

clang-tools-extra/clangd/support/Threading.h

+38
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,44 @@ std::future<T> runAsync(llvm::unique_function<T()> Action) {
131131
std::move(Action), Context::current().clone());
132132
}
133133

134+
/// Memoize is a cache to store and reuse computation results based on a key.
135+
///
136+
/// Memoize<DenseMap<int, bool>> PrimeCache;
137+
/// for (int I : RepetitiveNumbers)
138+
/// if (PrimeCache.get(I, [&] { return expensiveIsPrime(I); }))
139+
/// llvm::errs() << "Prime: " << I << "\n";
140+
///
141+
/// The computation will only be run once for each key.
142+
/// This class is threadsafe. Concurrent calls for the same key may run the
143+
/// computation multiple times, but each call will return the same result.
144+
template <typename Container> class Memoize {
145+
mutable Container Cache;
146+
std::unique_ptr<std::mutex> Mu;
147+
148+
public:
149+
Memoize() : Mu(std::make_unique<std::mutex>()) {}
150+
151+
template <typename T, typename Func>
152+
typename Container::mapped_type get(T &&Key, Func Compute) const {
153+
{
154+
std::lock_guard<std::mutex> Lock(*Mu);
155+
auto It = Cache.find(Key);
156+
if (It != Cache.end())
157+
return It->second;
158+
}
159+
// Don't hold the mutex while computing.
160+
auto V = Compute();
161+
{
162+
std::lock_guard<std::mutex> Lock(*Mu);
163+
auto R = Cache.try_emplace(std::forward<T>(Key), V);
164+
// Insert into cache may fail if we raced with another thread.
165+
if (!R.second)
166+
return R.first->second; // Canonical value, from other thread.
167+
}
168+
return V;
169+
}
170+
};
171+
134172
} // namespace clangd
135173
} // namespace clang
136174
#endif

clang-tools-extra/clangd/unittests/CompileCommandsTests.cpp

+74-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,11 @@
99
#include "CompileCommands.h"
1010
#include "TestFS.h"
1111

12+
#include "llvm/ADT/ScopeExit.h"
1213
#include "llvm/ADT/StringExtras.h"
14+
#include "llvm/Support/FileSystem.h"
15+
#include "llvm/Support/Path.h"
16+
#include "llvm/Support/Process.h"
1317

1418
#include "gmock/gmock.h"
1519
#include "gtest/gtest.h"
@@ -103,12 +107,81 @@ TEST(CommandMangler, ClangPath) {
103107

104108
Cmd = {"unknown-binary", "foo.cc"};
105109
Mangler.adjust(Cmd);
106-
EXPECT_EQ("unknown-binary", Cmd.front());
110+
EXPECT_EQ(testPath("fake/unknown-binary"), Cmd.front());
107111

108112
Cmd = {testPath("path/clang++"), "foo.cc"};
109113
Mangler.adjust(Cmd);
110114
EXPECT_EQ(testPath("path/clang++"), Cmd.front());
115+
116+
Cmd = {"foo/unknown-binary", "foo.cc"};
117+
Mangler.adjust(Cmd);
118+
EXPECT_EQ(testPath("fake/unknown-binary"), Cmd.front());
119+
}
120+
121+
// Only run the PATH/symlink resolving test on unix, we need to fiddle
122+
// with permissions and environment variables...
123+
#ifdef LLVM_ON_UNIX
124+
MATCHER(Ok, "") {
125+
if (arg) {
126+
*result_listener << arg.message();
127+
return false;
128+
}
129+
return true;
130+
}
131+
132+
TEST(CommandMangler, ClangPathResolve) {
133+
// Set up filesystem:
134+
// /temp/
135+
// bin/
136+
// foo -> temp/lib/bar
137+
// lib/
138+
// bar
139+
llvm::SmallString<256> TempDir;
140+
ASSERT_THAT(llvm::sys::fs::createUniqueDirectory("ClangPathResolve", TempDir),
141+
Ok());
142+
auto CleanDir = llvm::make_scope_exit(
143+
[&] { llvm::sys::fs::remove_directories(TempDir); });
144+
ASSERT_THAT(llvm::sys::fs::create_directory(TempDir + "/bin"), Ok());
145+
ASSERT_THAT(llvm::sys::fs::create_directory(TempDir + "/lib"), Ok());
146+
int FD;
147+
ASSERT_THAT(llvm::sys::fs::openFileForWrite(TempDir + "/lib/bar", FD), Ok());
148+
ASSERT_THAT(llvm::sys::Process::SafelyCloseFileDescriptor(FD), Ok());
149+
::chmod((TempDir + "/lib/bar").str().c_str(), 0755); // executable
150+
ASSERT_THAT(
151+
llvm::sys::fs::create_link(TempDir + "/lib/bar", TempDir + "/bin/foo"),
152+
Ok());
153+
154+
// Test the case where the driver is an absolute path to a symlink.
155+
auto Mangler = CommandMangler::forTests();
156+
Mangler.ClangPath = testPath("fake/clang");
157+
std::vector<std::string> Cmd = {(TempDir + "/bin/foo").str(), "foo.cc"};
158+
Mangler.adjust(Cmd);
159+
// Directory based on resolved symlink, basename preserved.
160+
EXPECT_EQ((TempDir + "/lib/foo").str(), Cmd.front());
161+
162+
// Set PATH to point to temp/bin so we can find 'foo' on it.
163+
ASSERT_TRUE(::getenv("PATH"));
164+
auto RestorePath =
165+
llvm::make_scope_exit([OldPath = std::string(::getenv("PATH"))] {
166+
::setenv("PATH", OldPath.c_str(), 1);
167+
});
168+
::setenv("PATH", (TempDir + "/bin").str().c_str(), /*overwrite=*/1);
169+
170+
// Test the case where the driver is a $PATH-relative path to a symlink.
171+
Mangler = CommandMangler::forTests();
172+
Mangler.ClangPath = testPath("fake/clang");
173+
// Driver found on PATH.
174+
Cmd = {"foo", "foo.cc"};
175+
Mangler.adjust(Cmd);
176+
// Found the symlink and resolved the path as above.
177+
EXPECT_EQ((TempDir + "/lib/foo").str(), Cmd.front());
178+
179+
// Symlink not resolved with -no-canonical-prefixes.
180+
Cmd = {"foo", "-no-canonical-prefixes", "foo.cc"};
181+
Mangler.adjust(Cmd);
182+
EXPECT_EQ((TempDir + "/bin/foo").str(), Cmd.front());
111183
}
184+
#endif
112185

113186
} // namespace
114187
} // namespace clangd

clang-tools-extra/clangd/unittests/support/ThreadingTests.cpp

+61
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
//===----------------------------------------------------------------------===//
88

99
#include "support/Threading.h"
10+
#include "llvm/ADT/DenseMap.h"
11+
#include "gmock/gmock.h"
1012
#include "gtest/gtest.h"
1113
#include <mutex>
1214

@@ -60,5 +62,64 @@ TEST_F(ThreadingTest, TaskRunner) {
6062
std::lock_guard<std::mutex> Lock(Mutex);
6163
ASSERT_EQ(Counter, TasksCnt * IncrementsPerTask);
6264
}
65+
66+
TEST_F(ThreadingTest, Memoize) {
67+
const unsigned NumThreads = 5;
68+
const unsigned NumKeys = 100;
69+
const unsigned NumIterations = 100;
70+
71+
Memoize<llvm::DenseMap<int, int>> Cache;
72+
std::atomic<unsigned> ComputeCount(0);
73+
std::atomic<int> ComputeResult[NumKeys];
74+
std::fill(std::begin(ComputeResult), std::end(ComputeResult), -1);
75+
76+
AsyncTaskRunner Tasks;
77+
for (unsigned I = 0; I < NumThreads; ++I)
78+
Tasks.runAsync("worker" + std::to_string(I), [&] {
79+
for (unsigned J = 0; J < NumIterations; J++)
80+
for (unsigned K = 0; K < NumKeys; K++) {
81+
int Result = Cache.get(K, [&] { return ++ComputeCount; });
82+
EXPECT_THAT(ComputeResult[K].exchange(Result),
83+
testing::AnyOf(-1, Result))
84+
<< "Got inconsistent results from memoize";
85+
}
86+
});
87+
Tasks.wait();
88+
EXPECT_GE(ComputeCount, NumKeys) << "Computed each key once";
89+
EXPECT_LE(ComputeCount, NumThreads * NumKeys)
90+
<< "Worst case, computed each key in every thread";
91+
for (int Result : ComputeResult)
92+
EXPECT_GT(Result, 0) << "All results in expected domain";
93+
}
94+
95+
TEST_F(ThreadingTest, MemoizeDeterministic) {
96+
Memoize<llvm::DenseMap<int, char>> Cache;
97+
98+
// Spawn two parallel computations, A and B.
99+
// Force concurrency: neither can finish until both have started.
100+
// Verify that cache returns consistent results.
101+
AsyncTaskRunner Tasks;
102+
std::atomic<char> ValueA(0), ValueB(0);
103+
Notification ReleaseA, ReleaseB;
104+
Tasks.runAsync("A", [&] {
105+
ValueA = Cache.get(0, [&] {
106+
ReleaseB.notify();
107+
ReleaseA.wait();
108+
return 'A';
109+
});
110+
});
111+
Tasks.runAsync("A", [&] {
112+
ValueB = Cache.get(0, [&] {
113+
ReleaseA.notify();
114+
ReleaseB.wait();
115+
return 'B';
116+
});
117+
});
118+
Tasks.wait();
119+
120+
ASSERT_EQ(ValueA, ValueB);
121+
ASSERT_THAT(ValueA.load(), testing::AnyOf('A', 'B'));
122+
}
123+
63124
} // namespace clangd
64125
} // namespace clang

0 commit comments

Comments
 (0)