diff --git a/clang-tools-extra/clangd/SystemIncludeExtractor.cpp b/clang-tools-extra/clangd/SystemIncludeExtractor.cpp --- a/clang-tools-extra/clangd/SystemIncludeExtractor.cpp +++ b/clang-tools-extra/clangd/SystemIncludeExtractor.cpp @@ -32,15 +32,22 @@ #include "CompileCommands.h" #include "GlobalCompilationDatabase.h" #include "support/Logger.h" -#include "support/Path.h" +#include "support/Threading.h" #include "support/Trace.h" #include "clang/Basic/Diagnostic.h" +#include "clang/Basic/DiagnosticIDs.h" +#include "clang/Basic/DiagnosticOptions.h" #include "clang/Basic/TargetInfo.h" #include "clang/Basic/TargetOptions.h" #include "clang/Driver/Types.h" #include "clang/Tooling/CompilationDatabase.h" +#include "llvm/ADT/DenseMap.h" +#include "llvm/ADT/Hashing.h" +#include "llvm/ADT/IntrusiveRefCntPtr.h" +#include "llvm/ADT/STLExtras.h" #include "llvm/ADT/ScopeExit.h" #include "llvm/ADT/SmallString.h" +#include "llvm/ADT/SmallVector.h" #include "llvm/ADT/StringExtras.h" #include "llvm/ADT/StringRef.h" #include "llvm/Support/FileSystem.h" @@ -49,15 +56,17 @@ #include "llvm/Support/Program.h" #include "llvm/Support/Regex.h" #include "llvm/Support/ScopedPrinter.h" -#include +#include "llvm/Support/raw_ostream.h" +#include #include -#include +#include #include #include +#include +#include #include -namespace clang { -namespace clangd { +namespace clang::clangd { namespace { struct DriverInfo { @@ -65,12 +74,133 @@ std::string Target; }; +struct DriverArgs { + // Name of the driver to execute. + std::string Driver; + std::string Directory; + // Whether certain includes should be part of query. + bool StandardIncludes = true; + bool StandardCXXIncludes = true; + bool BuiltinIncludes = true; + // Language to use while querying. + std::string Lang; + std::string Sysroot; + std::string ISysroot; + + bool operator==(const DriverArgs &RHS) const { + return std::tie(Driver, Directory, StandardIncludes, StandardCXXIncludes, + BuiltinIncludes, Lang, Sysroot, ISysroot) == + std::tie(RHS.Driver, RHS.Directory, RHS.StandardIncludes, + RHS.StandardCXXIncludes, RHS.BuiltinIncludes, RHS.Lang, + RHS.Sysroot, ISysroot); + } + + DriverArgs(const tooling::CompileCommand &Cmd, llvm::StringRef File) { + Driver = Cmd.CommandLine.front(); + Directory = Cmd.Directory; + for (size_t I = 0, E = Cmd.CommandLine.size(); I < E; ++I) { + llvm::StringRef Arg = Cmd.CommandLine[I]; + + // Look for Language related flags. + if (Arg.consume_front("-x")) { + if (Arg.empty() && I + 1 < E) + Lang = Cmd.CommandLine[I + 1]; + else + Lang = Arg.str(); + } + // Look for standard/builtin includes. + else if (Arg == "-nostdinc" || Arg == "--no-standard-includes") + StandardIncludes = false; + else if (Arg == "-nostdinc++") + StandardCXXIncludes = false; + else if (Arg == "-nobuiltininc") + BuiltinIncludes = false; + // Figure out sysroot + else if (Arg.consume_front("--sysroot")) { + if (Arg.consume_front("=")) + Sysroot = Arg.str(); + else if (Arg.empty() && I + 1 < E) + Sysroot = Cmd.CommandLine[I + 1]; + } else if (Arg.consume_front("-isysroot")) { + if (Arg.empty() && I + 1 < E) + ISysroot = Cmd.CommandLine[I + 1]; + else + ISysroot = Arg.str(); + } + } + + // If language is not explicit in the flags, infer from the file. + // This is important as we want to cache each language separately. + if (Lang.empty()) { + llvm::StringRef Ext = llvm::sys::path::extension(File).trim('.'); + auto Type = driver::types::lookupTypeForExtension(Ext); + if (Type == driver::types::TY_INVALID) { + elog("System include extraction: invalid file type for {0}", Ext); + } else { + Lang = driver::types::getTypeName(Type); + } + } + } + llvm::SmallVector render() const { + llvm::SmallVector Args = {"-x", Lang}; + if (!StandardIncludes) + Args.push_back("-nostdinc"); + if (!StandardCXXIncludes) + Args.push_back("-nostdinc++"); + if (!BuiltinIncludes) + Args.push_back("-nobuiltininc++"); + if (!Sysroot.empty()) + Args.append({"--sysroot", Sysroot}); + if (!ISysroot.empty()) + Args.append({"-isysroot", ISysroot}); + return Args; + } + + static DriverArgs getEmpty() { return {}; } + +private: + DriverArgs() = default; +}; +} // namespace +} // namespace clang::clangd +namespace llvm { +using DriverArgs = clang::clangd::DriverArgs; +template <> struct DenseMapInfo { + static DriverArgs getEmptyKey() { + auto Driver = DriverArgs::getEmpty(); + Driver.Driver = "EMPTY_KEY"; + return Driver; + } + static DriverArgs getTombstoneKey() { + auto Driver = DriverArgs::getEmpty(); + Driver.Driver = "TOMBSTONE_KEY"; + return Driver; + } + static unsigned getHashValue(const DriverArgs &Val) { + return llvm::hash_value(std::tuple{ + Val.Driver, + Val.Directory, + Val.StandardIncludes, + Val.StandardCXXIncludes, + Val.BuiltinIncludes, + Val.Lang, + Val.Sysroot, + Val.ISysroot, + }); + } + static bool isEqual(const DriverArgs &LHS, const DriverArgs &RHS) { + return LHS == RHS; + } +}; +} // namespace llvm +namespace clang::clangd { +namespace { bool isValidTarget(llvm::StringRef Triple) { std::shared_ptr TargetOpts(new TargetOptions); TargetOpts->Triple = Triple.str(); DiagnosticsEngine Diags(new DiagnosticIDs, new DiagnosticOptions, new IgnoringDiagConsumer); - IntrusiveRefCntPtr Target = + llvm::IntrusiveRefCntPtr Target = TargetInfo::CreateTargetInfo(Diags, TargetOpts); return bool(Target); } @@ -137,15 +267,17 @@ } std::optional -extractSystemIncludesAndTarget(llvm::SmallString<128> Driver, - llvm::StringRef Lang, - llvm::ArrayRef CommandLine, +extractSystemIncludesAndTarget(const DriverArgs &InputArgs, const llvm::Regex &QueryDriverRegex) { trace::Span Tracer("Extract system includes and target"); - if (!llvm::sys::path::is_absolute(Driver)) { - assert(llvm::none_of( - Driver, [](char C) { return llvm::sys::path::is_separator(C); })); + llvm::SmallString<128> Driver(InputArgs.Driver); + // Driver is a not a single executable name but instead a path (either + // relative or absolute). + if (llvm::any_of(Driver, + [](char C) { return llvm::sys::path::is_separator(C); })) { + llvm::sys::fs::make_absolute(InputArgs.Directory, Driver); + } else { auto DriverProgram = llvm::sys::findProgramByName(Driver); if (DriverProgram) { vlog("System include extraction: driver {0} expanded to {1}", Driver, @@ -158,7 +290,7 @@ } SPAN_ATTACH(Tracer, "driver", Driver); - SPAN_ATTACH(Tracer, "lang", Lang); + SPAN_ATTACH(Tracer, "lang", InputArgs.Lang); if (!QueryDriverRegex.match(Driver)) { vlog("System include extraction: not allowed driver {0}", Driver); @@ -178,36 +310,10 @@ std::optional Redirects[] = {{""}, {""}, StdErrPath.str()}; - llvm::SmallVector Args = {Driver, "-E", "-x", - Lang, "-", "-v"}; - - // These flags will be preserved - const llvm::StringRef FlagsToPreserve[] = { - "-nostdinc", "--no-standard-includes", "-nostdinc++", "-nobuiltininc"}; - // Preserves these flags and their values, either as separate args or with an - // equalsbetween them - const llvm::StringRef ArgsToPreserve[] = {"--sysroot", "-isysroot"}; - - for (size_t I = 0, E = CommandLine.size(); I < E; ++I) { - llvm::StringRef Arg = CommandLine[I]; - if (llvm::is_contained(FlagsToPreserve, Arg)) { - Args.push_back(Arg); - } else { - const auto *Found = - llvm::find_if(ArgsToPreserve, [&Arg](llvm::StringRef S) { - return Arg.startswith(S); - }); - if (Found == std::end(ArgsToPreserve)) - continue; - Arg = Arg.drop_front(Found->size()); - if (Arg.empty() && I + 1 < E) { - Args.push_back(CommandLine[I]); - Args.push_back(CommandLine[++I]); - } else if (Arg.startswith("=")) { - Args.push_back(CommandLine[I]); - } - } - } + llvm::SmallVector Args = {Driver, "-E", "-v"}; + Args.append(InputArgs.render()); + // Input needs to go after Lang flags. + Args.push_back("-"); std::string ErrMsg; if (int RC = llvm::sys::ExecuteAndWait(Driver, Args, /*Env=*/std::nullopt, @@ -325,43 +431,17 @@ if (Cmd.CommandLine.empty()) return; - llvm::StringRef Lang; - for (size_t I = 0, E = Cmd.CommandLine.size(); I < E; ++I) { - llvm::StringRef Arg = Cmd.CommandLine[I]; - if (Arg == "-x" && I + 1 < E) - Lang = Cmd.CommandLine[I + 1]; - else if (Arg.startswith("-x")) - Lang = Arg.drop_front(2).trim(); - } - if (Lang.empty()) { - llvm::StringRef Ext = llvm::sys::path::extension(File).trim('.'); - auto Type = driver::types::lookupTypeForExtension(Ext); - if (Type == driver::types::TY_INVALID) { - elog("System include extraction: invalid file type for {0}", Ext); - return; - } - Lang = driver::types::getTypeName(Type); - } - - llvm::SmallString<128> Driver(Cmd.CommandLine.front()); - if (llvm::any_of(Driver, - [](char C) { return llvm::sys::path::is_separator(C); })) - // Driver is a not a single executable name but instead a path (either - // relative or absolute). - llvm::sys::fs::make_absolute(Cmd.Directory, Driver); - - if (auto Info = - QueriedDrivers.get(/*Key=*/(Driver + ":" + Lang).str(), [&] { - return extractSystemIncludesAndTarget( - Driver, Lang, Cmd.CommandLine, QueryDriverRegex); - })) { + DriverArgs Args(Cmd, File); + if (auto Info = QueriedDrivers.get(Args, [&] { + return extractSystemIncludesAndTarget(Args, QueryDriverRegex); + })) { setTarget(addSystemIncludes(Cmd, Info->SystemIncludes), Info->Target); } } private: // Caches includes extracted from a driver. Key is driver:lang. - Memoize>> QueriedDrivers; + Memoize>> QueriedDrivers; llvm::Regex QueryDriverRegex; }; } // namespace @@ -373,5 +453,4 @@ return SystemIncludeExtractor(QueryDriverGlobs); } -} // namespace clangd -} // namespace clang +} // namespace clang::clangd diff --git a/clang-tools-extra/clangd/test/system-include-extractor.test b/clang-tools-extra/clangd/test/system-include-extractor.test --- a/clang-tools-extra/clangd/test/system-include-extractor.test +++ b/clang-tools-extra/clangd/test/system-include-extractor.test @@ -14,8 +14,8 @@ # Check that clangd preserves certain flags like `-nostdinc` from # original invocation in compile_commands.json. # RUN: echo '[ -z "${args##*"-nostdinc"*}" ] || exit' >> %t.dir/bin/my_driver.sh -# RUN: echo '[ -z "${args##*"-isysroot=/isysroot"*}" ] || exit' >> %t.dir/bin/my_driver.sh # RUN: echo '[ -z "${args##*"--sysroot /my/sysroot/path"*}" ] || exit' >> %t.dir/bin/my_driver.sh +# RUN: echo '[ -z "${args##*"-isysroot /isysroot"*}" ] || exit' >> %t.dir/bin/my_driver.sh # RUN: echo 'echo line to ignore >&2' >> %t.dir/bin/my_driver.sh # RUN: echo 'printf "Target: arm-linux-gnueabihf\r\n" >&2' >> %t.dir/bin/my_driver.sh # RUN: echo 'printf "#include <...> search starts here:\r\n" >&2' >> %t.dir/bin/my_driver.sh @@ -32,7 +32,7 @@ # Generate a compile_commands.json that will query the mock driver we've # created. Which should add a.h and b.h into include search path. -# RUN: echo '[{"directory": "%/t.dir", "command": "my_driver.sh the-file.cpp -nostdinc --sysroot /my/sysroot/path -isysroot=/isysroot", "file": "the-file.cpp"}]' > %t.dir/compile_commands.json +# RUN: echo '[{"directory": "%/t.dir", "command": "my_driver.sh the-file.cpp -nostdinc --sysroot /my/sysroot/path -isysroot/isysroot", "file": "the-file.cpp"}]' > %t.dir/compile_commands.json # RUN: sed -e "s|INPUT_DIR|%/t.dir|g" %s > %t.test.1 # On Windows, we need the URI in didOpen to look like "uri":"file:///C:/..." @@ -42,7 +42,7 @@ # Bless the mock driver we've just created so that clangd can execute it. # Note: include clangd's stderr in the FileCheck input with "2>&1" so that we # can match output lines like "ASTWorker building file" -# RUN: clangd -lit-test -query-driver="**.test,**.sh" < %t.test 2>&1 | FileCheck -strict-whitespace %t.test +# RUN: clangd -lit-test -query-driver="**.test,**.sh" < %t.test 2>&1 | FileCheck -strict-whitespace %t.test -dump-input=fail {"jsonrpc":"2.0","id":0,"method":"initialize","params":{}} --- { @@ -70,7 +70,7 @@ {"jsonrpc":"2.0","method":"exit"} # Generate a different compile_commands.json which does not point to the mock driver -# RUN: echo '[{"directory": "%/t.dir", "command": "gcc the-file.cpp -nostdinc --sysroot /my/sysroot/path -isysroot=/isysroot", "file": "the-file.cpp"}]' > %t.dir/compile_commands.json +# RUN: echo '[{"directory": "%/t.dir", "command": "gcc the-file.cpp -nostdinc --sysroot /my/sysroot/path -isysroot/isysroot", "file": "the-file.cpp"}]' > %t.dir/compile_commands.json # Generate a clangd config file which points to the mock driver instead # RUN: echo 'CompileFlags:' > %t.dir/.clangd