Skip to content

Commit 34de7da

Browse files
committedMar 13, 2023
[clangd] Move standard options adaptor to CommandMangler
There is a discrepancy between how clangd processes CDB loaded from JSON file on disk and pushed via LSP. Thus the same CDB pushed via LSP protocol may not work as expected. Some difference between these two paths is expected but we still need to insert driver mode and target from binary name and expand response files. Test Plan: check-clang-tools Differential Revision: https://reviews.llvm.org/D143436
1 parent 3870857 commit 34de7da

8 files changed

+95
-41
lines changed
 

‎clang-tools-extra/clangd/CompileCommands.cpp

+23-2
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
#include "clang/Driver/Options.h"
1515
#include "clang/Frontend/CompilerInvocation.h"
1616
#include "clang/Tooling/CompilationDatabase.h"
17+
#include "clang/Tooling/Tooling.h"
1718
#include "llvm/ADT/ArrayRef.h"
1819
#include "llvm/ADT/STLExtras.h"
1920
#include "llvm/ADT/SmallVector.h"
@@ -27,6 +28,7 @@
2728
#include "llvm/Support/MemoryBuffer.h"
2829
#include "llvm/Support/Path.h"
2930
#include "llvm/Support/Program.h"
31+
#include "llvm/TargetParser/Host.h"
3032
#include <iterator>
3133
#include <optional>
3234
#include <string>
@@ -185,6 +187,12 @@ static std::string resolveDriver(llvm::StringRef Driver, bool FollowSymlink,
185187

186188
} // namespace
187189

190+
CommandMangler::CommandMangler() {
191+
Tokenizer = llvm::Triple(llvm::sys::getProcessTriple()).isOSWindows()
192+
? llvm::cl::TokenizeWindowsCommandLine
193+
: llvm::cl::TokenizeGNUCommandLine;
194+
}
195+
188196
CommandMangler CommandMangler::detect() {
189197
CommandMangler Result;
190198
Result.ClangPath = detectClangPath();
@@ -201,9 +209,18 @@ void CommandMangler::operator()(tooling::CompileCommand &Command,
201209
trace::Span S("AdjustCompileFlags");
202210
// Most of the modifications below assumes the Cmd starts with a driver name.
203211
// We might consider injecting a generic driver name like "cc" or "c++", but
204-
// a Cmd missing the driver is probably rare enough in practice and errnous.
212+
// a Cmd missing the driver is probably rare enough in practice and erroneous.
205213
if (Cmd.empty())
206214
return;
215+
216+
// FS used for expanding response files.
217+
// FIXME: ExpandResponseFiles appears not to provide the usual
218+
// thread-safety guarantees, as the access to FS is not locked!
219+
// For now, use the real FS, which is known to be threadsafe (if we don't
220+
// use/change working directory, which ExpandResponseFiles doesn't).
221+
auto FS = llvm::vfs::getRealFileSystem();
222+
tooling::addExpandedResponseFiles(Cmd, Command.Directory, Tokenizer, *FS);
223+
207224
auto &OptTable = clang::driver::getDriverOptTable();
208225
// OriginalArgs needs to outlive ArgList.
209226
llvm::SmallVector<const char *, 16> OriginalArgs;
@@ -212,7 +229,7 @@ void CommandMangler::operator()(tooling::CompileCommand &Command,
212229
OriginalArgs.push_back(S.c_str());
213230
bool IsCLMode = driver::IsClangCL(driver::getDriverMode(
214231
OriginalArgs[0], llvm::ArrayRef(OriginalArgs).slice(1)));
215-
// ParseArgs propagates missig arg/opt counts on error, but preserves
232+
// ParseArgs propagates missing arg/opt counts on error, but preserves
216233
// everything it could parse in ArgList. So we just ignore those counts.
217234
unsigned IgnoredCount;
218235
// Drop the executable name, as ParseArgs doesn't expect it. This means
@@ -307,12 +324,16 @@ void CommandMangler::operator()(tooling::CompileCommand &Command,
307324
// necessary for the system include extractor to identify the file type
308325
// - AFTER applying CompileFlags.Edits, because the name of the compiler
309326
// that needs to be invoked may come from the CompileFlags->Compiler key
327+
// - BEFORE addTargetAndModeForProgramName(), because gcc doesn't support
328+
// the target flag that might be added.
310329
// - BEFORE resolveDriver() because that can mess up the driver path,
311330
// e.g. changing gcc to /path/to/clang/bin/gcc
312331
if (SystemIncludeExtractor) {
313332
SystemIncludeExtractor(Command, File);
314333
}
315334

335+
tooling::addTargetAndModeForProgramName(Cmd, Cmd.front());
336+
316337
// Check whether the flag exists, either as -flag or -flag=*
317338
auto Has = [&](llvm::StringRef Flag) {
318339
for (llvm::StringRef Arg : Cmd) {

‎clang-tools-extra/clangd/CompileCommands.h

+3-1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
#include "support/Threading.h"
1313
#include "llvm/ADT/StringMap.h"
1414
#include "llvm/ADT/StringRef.h"
15+
#include "llvm/Support/CommandLine.h"
1516
#include <deque>
1617
#include <optional>
1718
#include <string>
@@ -50,10 +51,11 @@ struct CommandMangler {
5051
llvm::StringRef TargetFile) const;
5152

5253
private:
53-
CommandMangler() = default;
54+
CommandMangler();
5455

5556
Memoize<llvm::StringMap<std::string>> ResolvedDrivers;
5657
Memoize<llvm::StringMap<std::string>> ResolvedDriversNoFollow;
58+
llvm::cl::TokenizerCallback Tokenizer;
5759
};
5860

5961
// Removes args from a command-line in a semantically-aware way.

‎clang-tools-extra/clangd/GlobalCompilationDatabase.cpp

+1-9
Original file line numberDiff line numberDiff line change
@@ -244,15 +244,7 @@ static std::unique_ptr<tooling::CompilationDatabase>
244244
parseJSON(PathRef Path, llvm::StringRef Data, std::string &Error) {
245245
if (auto CDB = tooling::JSONCompilationDatabase::loadFromBuffer(
246246
Data, Error, tooling::JSONCommandLineSyntax::AutoDetect)) {
247-
// FS used for expanding response files.
248-
// FIXME: ExpandResponseFilesDatabase appears not to provide the usual
249-
// thread-safety guarantees, as the access to FS is not locked!
250-
// For now, use the real FS, which is known to be threadsafe (if we don't
251-
// use/change working directory, which ExpandResponseFilesDatabase doesn't).
252-
auto FS = llvm::vfs::getRealFileSystem();
253-
return tooling::inferTargetAndDriverMode(
254-
tooling::inferMissingCompileCommands(
255-
expandResponseFiles(std::move(CDB), std::move(FS))));
247+
return tooling::inferMissingCompileCommands(std::move(CDB));
256248
}
257249
return nullptr;
258250
}

‎clang-tools-extra/clangd/test/did-change-configuration-params.test

+6-2
Original file line numberDiff line numberDiff line change
@@ -45,13 +45,17 @@
4545
# CHECK-NEXT: "uri": "file://{{.*}}/foo.c",
4646
# CHECK-NEXT: "version": 0
4747
# CHECK-NEXT: }
48+
---
49+
{"jsonrpc":"2.0","method":"workspace/didChangeConfiguration","params":{"settings":{"compilationDatabaseChanges":{"/clangd-test/foo.c": {"workingDirectory":"/clangd-test2", "compilationCommand": ["x86_64-linux-unknown-gcc", "-c", "foo.c", "-Wall", "-Werror"]}}}}}
50+
# CHECK: "method": "textDocument/publishDiagnostics",
4851
#
4952
# ERR: ASTWorker building file {{.*}}foo.c version 0 with command
5053
# ERR: [{{.*}}clangd-test2]
5154
# ERR: clang -c -Wall -Werror {{.*}} -- {{.*}}foo.c
55+
# ERR: ASTWorker building file {{.*}}foo.c version 0 with command
56+
# ERR: [{{.*}}clangd-test2]
57+
# ERR: x86_64-linux-unknown-gcc --target=x86_64-linux-unknown -c -Wall -Werror {{.*}} -- {{.*}}foo.c
5258
---
5359
{"jsonrpc":"2.0","id":5,"method":"shutdown"}
5460
---
5561
{"jsonrpc":"2.0","method":"exit"}
56-
57-

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

+25-4
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
#include "llvm/Support/FileSystem.h"
2121
#include "llvm/Support/Path.h"
2222
#include "llvm/Support/Process.h"
23+
#include "llvm/Support/TargetSelect.h"
2324

2425
#include "gmock/gmock.h"
2526
#include "gtest/gtest.h"
@@ -41,15 +42,17 @@ using ::testing::Not;
4142
// Make use of all features and assert the exact command we get out.
4243
// Other tests just verify presence/absence of certain args.
4344
TEST(CommandMangler, Everything) {
45+
llvm::InitializeAllTargetInfos(); // As in ClangdMain
4446
auto Mangler = CommandMangler::forTests();
4547
Mangler.ClangPath = testPath("fake/clang");
4648
Mangler.ResourceDir = testPath("fake/resources");
4749
Mangler.Sysroot = testPath("fake/sysroot");
4850
tooling::CompileCommand Cmd;
49-
Cmd.CommandLine = {"clang++", "--", "foo.cc", "bar.cc"};
51+
Cmd.CommandLine = {"x86_64-linux-unknown-clang++", "--", "foo.cc", "bar.cc"};
5052
Mangler(Cmd, "foo.cc");
5153
EXPECT_THAT(Cmd.CommandLine,
52-
ElementsAre(testPath("fake/clang++"),
54+
ElementsAre(testPath("fake/x86_64-linux-unknown-clang++"),
55+
"--target=x86_64-linux-unknown", "--driver-mode=g++",
5356
"-resource-dir=" + testPath("fake/resources"),
5457
"-isysroot", testPath("fake/sysroot"), "--",
5558
"foo.cc"));
@@ -197,8 +200,26 @@ TEST(CommandMangler, ConfigEdits) {
197200
Mangler(Cmd, "foo.cc");
198201
}
199202
// Edits are applied in given order and before other mangling and they always
200-
// go before filename.
201-
EXPECT_THAT(Cmd.CommandLine, ElementsAre(_, "--hello", "--", "FOO.CC"));
203+
// go before filename. `--driver-mode=g++` here is in lower case because
204+
// options inserted by addTargetAndModeForProgramName are not editable,
205+
// see discussion in https://reviews.llvm.org/D138546
206+
EXPECT_THAT(Cmd.CommandLine,
207+
ElementsAre(_, "--driver-mode=g++", "--hello", "--", "FOO.CC"));
208+
}
209+
210+
TEST(CommandMangler, ExpandedResponseFiles) {
211+
SmallString<1024> Path;
212+
int FD;
213+
ASSERT_FALSE(llvm::sys::fs::createTemporaryFile("args", "", FD, Path));
214+
llvm::raw_fd_ostream OutStream(FD, true);
215+
OutStream << "-Wall";
216+
OutStream.close();
217+
218+
auto Mangler = CommandMangler::forTests();
219+
tooling::CompileCommand Cmd;
220+
Cmd.CommandLine = {"clang", ("@" + Path).str(), "foo.cc"};
221+
Mangler(Cmd, "foo.cc");
222+
EXPECT_THAT(Cmd.CommandLine, ElementsAre(_, "-Wall", "--", "foo.cc"));
202223
}
203224

204225
static std::string strip(llvm::StringRef Arg, llvm::StringRef Argv) {

‎clang/include/clang/Tooling/Tooling.h

+6
Original file line numberDiff line numberDiff line change
@@ -506,6 +506,12 @@ llvm::Expected<std::string> getAbsolutePath(llvm::vfs::FileSystem &FS,
506506
void addTargetAndModeForProgramName(std::vector<std::string> &CommandLine,
507507
StringRef InvokedAs);
508508

509+
/// Helper function that expands response files in command line.
510+
void addExpandedResponseFiles(std::vector<std::string> &CommandLine,
511+
llvm::StringRef WorkingDir,
512+
llvm::cl::TokenizerCallback Tokenizer,
513+
llvm::vfs::FileSystem &FS);
514+
509515
/// Creates a \c CompilerInvocation.
510516
CompilerInvocation *newInvocation(DiagnosticsEngine *Diagnostics,
511517
ArrayRef<const char *> CC1Args,

‎clang/lib/Tooling/ExpandResponseFilesCompilationDatabase.cpp

+4-22
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
//===----------------------------------------------------------------------===//
88

99
#include "clang/Tooling/CompilationDatabase.h"
10+
#include "clang/Tooling/Tooling.h"
1011
#include "llvm/ADT/StringRef.h"
1112
#include "llvm/Support/CommandLine.h"
1213
#include "llvm/Support/ConvertUTF.h"
@@ -48,28 +49,9 @@ class ExpandResponseFilesDatabase : public CompilationDatabase {
4849

4950
private:
5051
std::vector<CompileCommand> expand(std::vector<CompileCommand> Cmds) const {
51-
for (auto &Cmd : Cmds) {
52-
bool SeenRSPFile = false;
53-
llvm::SmallVector<const char *, 20> Argv;
54-
Argv.reserve(Cmd.CommandLine.size());
55-
for (auto &Arg : Cmd.CommandLine) {
56-
Argv.push_back(Arg.c_str());
57-
if (!Arg.empty())
58-
SeenRSPFile |= Arg.front() == '@';
59-
}
60-
if (!SeenRSPFile)
61-
continue;
62-
llvm::BumpPtrAllocator Alloc;
63-
llvm::cl::ExpansionContext ECtx(Alloc, Tokenizer);
64-
llvm::Error Err = ECtx.setVFS(FS.get())
65-
.setCurrentDir(Cmd.Directory)
66-
.expandResponseFiles(Argv);
67-
if (Err)
68-
llvm::errs() << Err;
69-
// Don't assign directly, Argv aliases CommandLine.
70-
std::vector<std::string> ExpandedArgv(Argv.begin(), Argv.end());
71-
Cmd.CommandLine = std::move(ExpandedArgv);
72-
}
52+
for (auto &Cmd : Cmds)
53+
tooling::addExpandedResponseFiles(Cmd.CommandLine, Cmd.Directory,
54+
Tokenizer, *FS);
7355
return Cmds;
7456
}
7557

‎clang/lib/Tooling/Tooling.cpp

+27-1
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
#include "llvm/Option/OptTable.h"
4444
#include "llvm/Option/Option.h"
4545
#include "llvm/Support/Casting.h"
46+
#include "llvm/Support/CommandLine.h"
4647
#include "llvm/Support/Debug.h"
4748
#include "llvm/Support/ErrorHandling.h"
4849
#include "llvm/Support/FileSystem.h"
@@ -299,6 +300,31 @@ void addTargetAndModeForProgramName(std::vector<std::string> &CommandLine,
299300
}
300301
}
301302

303+
void addExpandedResponseFiles(std::vector<std::string> &CommandLine,
304+
llvm::StringRef WorkingDir,
305+
llvm::cl::TokenizerCallback Tokenizer,
306+
llvm::vfs::FileSystem &FS) {
307+
bool SeenRSPFile = false;
308+
llvm::SmallVector<const char *, 20> Argv;
309+
Argv.reserve(CommandLine.size());
310+
for (auto &Arg : CommandLine) {
311+
Argv.push_back(Arg.c_str());
312+
if (!Arg.empty())
313+
SeenRSPFile |= Arg.front() == '@';
314+
}
315+
if (!SeenRSPFile)
316+
return;
317+
llvm::BumpPtrAllocator Alloc;
318+
llvm::cl::ExpansionContext ECtx(Alloc, Tokenizer);
319+
llvm::Error Err =
320+
ECtx.setVFS(&FS).setCurrentDir(WorkingDir).expandResponseFiles(Argv);
321+
if (Err)
322+
llvm::errs() << Err;
323+
// Don't assign directly, Argv aliases CommandLine.
324+
std::vector<std::string> ExpandedArgv(Argv.begin(), Argv.end());
325+
CommandLine = std::move(ExpandedArgv);
326+
}
327+
302328
} // namespace tooling
303329
} // namespace clang
304330

@@ -684,7 +710,7 @@ std::unique_ptr<ASTUnit> buildASTFromCodeWithArgs(
684710

685711
if (!Invocation.run())
686712
return nullptr;
687-
713+
688714
assert(ASTs.size() == 1);
689715
return std::move(ASTs[0]);
690716
}

0 commit comments

Comments
 (0)
Please sign in to comment.