diff options
author | Dimitry Andric <dim@FreeBSD.org> | 2023-02-11 12:38:04 +0000 |
---|---|---|
committer | Dimitry Andric <dim@FreeBSD.org> | 2023-02-11 12:38:11 +0000 |
commit | e3b557809604d036af6e00c60f012c2025b59a5e (patch) | |
tree | 8a11ba2269a3b669601e2fd41145b174008f4da8 /clang/lib/Tooling | |
parent | 08e8dd7b9db7bb4a9de26d44c1cbfd24e869c014 (diff) |
Diffstat (limited to 'clang/lib/Tooling')
33 files changed, 1146 insertions, 560 deletions
diff --git a/clang/lib/Tooling/ASTDiff/ASTDiff.cpp b/clang/lib/Tooling/ASTDiff/ASTDiff.cpp index 0821863adcc6..52e57976ac09 100644 --- a/clang/lib/Tooling/ASTDiff/ASTDiff.cpp +++ b/clang/lib/Tooling/ASTDiff/ASTDiff.cpp @@ -19,6 +19,7 @@ #include <limits> #include <memory> +#include <optional> #include <unordered_set> using namespace llvm; @@ -117,13 +118,11 @@ public: Impl(SyntaxTree *Parent, Stmt *N, ASTContext &AST); template <class T> Impl(SyntaxTree *Parent, - std::enable_if_t<std::is_base_of<Stmt, T>::value, T> *Node, - ASTContext &AST) + std::enable_if_t<std::is_base_of_v<Stmt, T>, T> *Node, ASTContext &AST) : Impl(Parent, dyn_cast<Stmt>(Node), AST) {} template <class T> Impl(SyntaxTree *Parent, - std::enable_if_t<std::is_base_of<Decl, T>::value, T> *Node, - ASTContext &AST) + std::enable_if_t<std::is_base_of_v<Decl, T>, T> *Node, ASTContext &AST) : Impl(Parent, dyn_cast<Decl>(Node), AST) {} SyntaxTree *Parent; @@ -688,20 +687,20 @@ ASTNodeKind Node::getType() const { return ASTNode.getNodeKind(); } StringRef Node::getTypeLabel() const { return getType().asStringRef(); } -llvm::Optional<std::string> Node::getQualifiedIdentifier() const { +std::optional<std::string> Node::getQualifiedIdentifier() const { if (auto *ND = ASTNode.get<NamedDecl>()) { if (ND->getDeclName().isIdentifier()) return ND->getQualifiedNameAsString(); } - return llvm::None; + return std::nullopt; } -llvm::Optional<StringRef> Node::getIdentifier() const { +std::optional<StringRef> Node::getIdentifier() const { if (auto *ND = ASTNode.get<NamedDecl>()) { if (ND->getDeclName().isIdentifier()) return ND->getName(); } - return llvm::None; + return std::nullopt; } namespace { diff --git a/clang/lib/Tooling/AllTUsExecution.cpp b/clang/lib/Tooling/AllTUsExecution.cpp index 5565da9b548a..f327d0139941 100644 --- a/clang/lib/Tooling/AllTUsExecution.cpp +++ b/clang/lib/Tooling/AllTUsExecution.cpp @@ -121,7 +121,7 @@ llvm::Error AllTUsToolExecutor::execute( [&](std::string Path) { Log("[" + std::to_string(Count()) + "/" + TotalNumStr + "] Processing file " + Path); - // Each thread gets an indepent copy of a VFS to allow different + // Each thread gets an independent copy of a VFS to allow different // concurrent working directories. IntrusiveRefCntPtr<llvm::vfs::FileSystem> FS = llvm::vfs::createPhysicalFileSystem(); diff --git a/clang/lib/Tooling/ArgumentsAdjusters.cpp b/clang/lib/Tooling/ArgumentsAdjusters.cpp index 7f5dc4d62f11..e40df6257378 100644 --- a/clang/lib/Tooling/ArgumentsAdjusters.cpp +++ b/clang/lib/Tooling/ArgumentsAdjusters.cpp @@ -122,7 +122,7 @@ ArgumentsAdjuster getInsertArgumentAdjuster(const CommandLineArguments &Extra, CommandLineArguments::iterator I; if (Pos == ArgumentInsertPosition::END) { - I = std::find(Return.begin(), Return.end(), "--"); + I = llvm::find(Return, "--"); } else { I = Return.begin(); ++I; // To leave the program name in place diff --git a/clang/lib/Tooling/CommonOptionsParser.cpp b/clang/lib/Tooling/CommonOptionsParser.cpp index 7d48dd505464..59ef47cc0166 100644 --- a/clang/lib/Tooling/CommonOptionsParser.cpp +++ b/clang/lib/Tooling/CommonOptionsParser.cpp @@ -86,21 +86,21 @@ llvm::Error CommonOptionsParser::init( static cl::opt<std::string> BuildPath("p", cl::desc("Build path"), cl::Optional, cl::cat(Category), - cl::sub(*cl::AllSubCommands)); + cl::sub(cl::SubCommand::getAll())); static cl::list<std::string> SourcePaths( cl::Positional, cl::desc("<source0> [... <sourceN>]"), OccurrencesFlag, - cl::cat(Category), cl::sub(*cl::AllSubCommands)); + cl::cat(Category), cl::sub(cl::SubCommand::getAll())); static cl::list<std::string> ArgsAfter( "extra-arg", cl::desc("Additional argument to append to the compiler command line"), - cl::cat(Category), cl::sub(*cl::AllSubCommands)); + cl::cat(Category), cl::sub(cl::SubCommand::getAll())); static cl::list<std::string> ArgsBefore( "extra-arg-before", cl::desc("Additional argument to prepend to the compiler command line"), - cl::cat(Category), cl::sub(*cl::AllSubCommands)); + cl::cat(Category), cl::sub(cl::SubCommand::getAll())); cl::ResetAllOptionOccurrences(); diff --git a/clang/lib/Tooling/Core/Replacement.cpp b/clang/lib/Tooling/Core/Replacement.cpp index aca2afceea44..020ad08a65e7 100644 --- a/clang/lib/Tooling/Core/Replacement.cpp +++ b/clang/lib/Tooling/Core/Replacement.cpp @@ -270,7 +270,7 @@ llvm::Error Replacements::add(const Replacement &R) { assert(R.getLength() == 0); // `I` is also an insertion, `R` and `I` conflict. if (I->getLength() == 0) { - // Check if two insertions are order-indepedent: if inserting them in + // Check if two insertions are order-independent: if inserting them in // either order produces the same text, they are order-independent. if ((R.getReplacementText() + I->getReplacementText()).str() != (I->getReplacementText() + R.getReplacementText()).str()) @@ -319,7 +319,7 @@ llvm::Error Replacements::add(const Replacement &R) { Replaces.insert(R); } else { // `I` overlaps with `R`. We need to check `R` against all overlapping - // replacements to see if they are order-indepedent. If they are, merge `R` + // replacements to see if they are order-independent. If they are, merge `R` // with them and replace them with the merged replacements. auto MergeBegin = I; auto MergeEnd = std::next(I); diff --git a/clang/lib/Tooling/DependencyScanning/DependencyScanningFilesystem.cpp b/clang/lib/Tooling/DependencyScanning/DependencyScanningFilesystem.cpp index 026bdfe03f28..97b41fc68917 100644 --- a/clang/lib/Tooling/DependencyScanning/DependencyScanningFilesystem.cpp +++ b/clang/lib/Tooling/DependencyScanning/DependencyScanningFilesystem.cpp @@ -10,6 +10,7 @@ #include "llvm/Support/MemoryBuffer.h" #include "llvm/Support/SmallVectorMemoryBuffer.h" #include "llvm/Support/Threading.h" +#include <optional> using namespace clang; using namespace tooling; @@ -67,7 +68,7 @@ EntryRef DependencyScanningWorkerFilesystem::scanForDirectivesIfNecessary( Directives)) { Contents->DepDirectiveTokens.clear(); // FIXME: Propagate the diagnostic if desired by the client. - Contents->DepDirectives.store(new Optional<DependencyDirectivesTy>()); + Contents->DepDirectives.store(new std::optional<DependencyDirectivesTy>()); return EntryRef(Filename, Entry); } @@ -76,7 +77,7 @@ EntryRef DependencyScanningWorkerFilesystem::scanForDirectivesIfNecessary( // threads may skip the // critical section (`DepDirectives != nullptr`), leading to a data race. Contents->DepDirectives.store( - new Optional<DependencyDirectivesTy>(std::move(Directives))); + new std::optional<DependencyDirectivesTy>(std::move(Directives))); return EntryRef(Filename, Entry); } diff --git a/clang/lib/Tooling/DependencyScanning/DependencyScanningService.cpp b/clang/lib/Tooling/DependencyScanning/DependencyScanningService.cpp index cda3e2dd8550..6dccb3d131cd 100644 --- a/clang/lib/Tooling/DependencyScanning/DependencyScanningService.cpp +++ b/clang/lib/Tooling/DependencyScanning/DependencyScanningService.cpp @@ -14,10 +14,10 @@ using namespace tooling; using namespace dependencies; DependencyScanningService::DependencyScanningService( - ScanningMode Mode, ScanningOutputFormat Format, bool ReuseFileManager, - bool OptimizeArgs) - : Mode(Mode), Format(Format), ReuseFileManager(ReuseFileManager), - OptimizeArgs(OptimizeArgs) { + ScanningMode Mode, ScanningOutputFormat Format, bool OptimizeArgs, + bool EagerLoadModules) + : Mode(Mode), Format(Format), OptimizeArgs(OptimizeArgs), + EagerLoadModules(EagerLoadModules) { // Initialize targets for object file support. llvm::InitializeAllTargets(); llvm::InitializeAllTargetMCs(); diff --git a/clang/lib/Tooling/DependencyScanning/DependencyScanningTool.cpp b/clang/lib/Tooling/DependencyScanning/DependencyScanningTool.cpp index 411fd9676ffd..3fcef00a5780 100644 --- a/clang/lib/Tooling/DependencyScanning/DependencyScanningTool.cpp +++ b/clang/lib/Tooling/DependencyScanning/DependencyScanningTool.cpp @@ -8,32 +8,18 @@ #include "clang/Tooling/DependencyScanning/DependencyScanningTool.h" #include "clang/Frontend/Utils.h" +#include <optional> using namespace clang; using namespace tooling; using namespace dependencies; -std::vector<std::string> FullDependencies::getCommandLine( - llvm::function_ref<std::string(const ModuleID &, ModuleOutputKind)> - LookupModuleOutput) const { - std::vector<std::string> Ret = getCommandLineWithoutModulePaths(); - - for (ModuleID MID : ClangModuleDeps) { - auto PCM = LookupModuleOutput(MID, ModuleOutputKind::ModuleFile); - Ret.push_back("-fmodule-file=" + PCM); - } - - return Ret; -} - -std::vector<std::string> -FullDependencies::getCommandLineWithoutModulePaths() const { +static std::vector<std::string> +makeTUCommandLineWithoutPaths(ArrayRef<std::string> OriginalCommandLine) { std::vector<std::string> Args = OriginalCommandLine; Args.push_back("-fno-implicit-modules"); Args.push_back("-fno-implicit-module-maps"); - for (const PrebuiltModuleDep &PMD : PrebuiltModuleDeps) - Args.push_back("-fmodule-file=" + PMD.PCMFile); // These arguments are unused in explicit compiles. llvm::erase_if(Args, [](StringRef Arg) { @@ -56,10 +42,12 @@ DependencyScanningTool::DependencyScanningTool( llvm::Expected<std::string> DependencyScanningTool::getDependencyFile( const std::vector<std::string> &CommandLine, StringRef CWD, - llvm::Optional<StringRef> ModuleName) { + std::optional<StringRef> ModuleName) { /// Prints out all of the gathered dependencies into a string. class MakeDependencyPrinterConsumer : public DependencyConsumer { public: + void handleBuildCommand(Command) override {} + void handleDependencyOutputOpts(const DependencyOutputOptions &Opts) override { this->Opts = std::make_unique<DependencyOutputOptions>(Opts); @@ -81,6 +69,11 @@ llvm::Expected<std::string> DependencyScanningTool::getDependencyFile( void handleContextHash(std::string Hash) override {} + std::string lookupModuleOutput(const ModuleID &ID, + ModuleOutputKind Kind) override { + llvm::report_fatal_error("unexpected call to lookupModuleOutput"); + } + void printDependencies(std::string &S) { assert(Opts && "Handled dependency output options."); @@ -122,77 +115,98 @@ llvm::Expected<FullDependenciesResult> DependencyScanningTool::getFullDependencies( const std::vector<std::string> &CommandLine, StringRef CWD, const llvm::StringSet<> &AlreadySeen, - llvm::Optional<StringRef> ModuleName) { - class FullDependencyPrinterConsumer : public DependencyConsumer { - public: - FullDependencyPrinterConsumer(const llvm::StringSet<> &AlreadySeen) - : AlreadySeen(AlreadySeen) {} - - void - handleDependencyOutputOpts(const DependencyOutputOptions &Opts) override {} - - void handleFileDependency(StringRef File) override { - Dependencies.push_back(std::string(File)); - } + LookupModuleOutputCallback LookupModuleOutput, + std::optional<StringRef> ModuleName) { + FullDependencyConsumer Consumer(AlreadySeen, LookupModuleOutput, + Worker.shouldEagerLoadModules()); + llvm::Error Result = + Worker.computeDependencies(CWD, CommandLine, Consumer, ModuleName); + if (Result) + return std::move(Result); + return Consumer.takeFullDependencies(); +} - void handlePrebuiltModuleDependency(PrebuiltModuleDep PMD) override { - PrebuiltModuleDeps.emplace_back(std::move(PMD)); - } +llvm::Expected<FullDependenciesResult> +DependencyScanningTool::getFullDependenciesLegacyDriverCommand( + const std::vector<std::string> &CommandLine, StringRef CWD, + const llvm::StringSet<> &AlreadySeen, + LookupModuleOutputCallback LookupModuleOutput, + std::optional<StringRef> ModuleName) { + FullDependencyConsumer Consumer(AlreadySeen, LookupModuleOutput, + Worker.shouldEagerLoadModules()); + llvm::Error Result = + Worker.computeDependencies(CWD, CommandLine, Consumer, ModuleName); + if (Result) + return std::move(Result); + return Consumer.getFullDependenciesLegacyDriverCommand(CommandLine); +} - void handleModuleDependency(ModuleDeps MD) override { - ClangModuleDeps[MD.ID.ContextHash + MD.ID.ModuleName] = std::move(MD); - } +FullDependenciesResult FullDependencyConsumer::takeFullDependencies() { + FullDependenciesResult FDR; + FullDependencies &FD = FDR.FullDeps; + + FD.ID.ContextHash = std::move(ContextHash); + FD.FileDeps = std::move(Dependencies); + FD.PrebuiltModuleDeps = std::move(PrebuiltModuleDeps); + FD.Commands = std::move(Commands); + + for (auto &&M : ClangModuleDeps) { + auto &MD = M.second; + if (MD.ImportedByMainFile) + FD.ClangModuleDeps.push_back(MD.ID); + // TODO: Avoid handleModuleDependency even being called for modules + // we've already seen. + if (AlreadySeen.count(M.first)) + continue; + FDR.DiscoveredModules.push_back(std::move(MD)); + } - void handleContextHash(std::string Hash) override { - ContextHash = std::move(Hash); - } + return FDR; +} - FullDependenciesResult getFullDependencies( - const std::vector<std::string> &OriginalCommandLine) const { - FullDependencies FD; +FullDependenciesResult +FullDependencyConsumer::getFullDependenciesLegacyDriverCommand( + const std::vector<std::string> &OriginalCommandLine) const { + FullDependencies FD; - FD.OriginalCommandLine = - ArrayRef<std::string>(OriginalCommandLine).slice(1); + FD.DriverCommandLine = makeTUCommandLineWithoutPaths( + ArrayRef<std::string>(OriginalCommandLine).slice(1)); - FD.ID.ContextHash = std::move(ContextHash); + FD.ID.ContextHash = std::move(ContextHash); - FD.FileDeps.assign(Dependencies.begin(), Dependencies.end()); + FD.FileDeps.assign(Dependencies.begin(), Dependencies.end()); - for (auto &&M : ClangModuleDeps) { - auto &MD = M.second; - if (MD.ImportedByMainFile) - FD.ClangModuleDeps.push_back(MD.ID); + for (const PrebuiltModuleDep &PMD : PrebuiltModuleDeps) + FD.DriverCommandLine.push_back("-fmodule-file=" + PMD.PCMFile); + + for (auto &&M : ClangModuleDeps) { + auto &MD = M.second; + if (MD.ImportedByMainFile) { + FD.ClangModuleDeps.push_back(MD.ID); + auto PCMPath = LookupModuleOutput(MD.ID, ModuleOutputKind::ModuleFile); + if (EagerLoadModules) { + FD.DriverCommandLine.push_back("-fmodule-file=" + PCMPath); + } else { + FD.DriverCommandLine.push_back("-fmodule-map-file=" + + MD.ClangModuleMapFile); + FD.DriverCommandLine.push_back("-fmodule-file=" + MD.ID.ModuleName + + "=" + PCMPath); } + } + } - FD.PrebuiltModuleDeps = std::move(PrebuiltModuleDeps); - - FullDependenciesResult FDR; - - for (auto &&M : ClangModuleDeps) { - // TODO: Avoid handleModuleDependency even being called for modules - // we've already seen. - if (AlreadySeen.count(M.first)) - continue; - FDR.DiscoveredModules.push_back(std::move(M.second)); - } + FD.PrebuiltModuleDeps = std::move(PrebuiltModuleDeps); - FDR.FullDeps = std::move(FD); - return FDR; - } + FullDependenciesResult FDR; - private: - std::vector<std::string> Dependencies; - std::vector<PrebuiltModuleDep> PrebuiltModuleDeps; - llvm::MapVector<std::string, ModuleDeps, llvm::StringMap<unsigned>> ClangModuleDeps; - std::string ContextHash; - std::vector<std::string> OutputPaths; - const llvm::StringSet<> &AlreadySeen; - }; + for (auto &&M : ClangModuleDeps) { + // TODO: Avoid handleModuleDependency even being called for modules + // we've already seen. + if (AlreadySeen.count(M.first)) + continue; + FDR.DiscoveredModules.push_back(std::move(M.second)); + } - FullDependencyPrinterConsumer Consumer(AlreadySeen); - llvm::Error Result = - Worker.computeDependencies(CWD, CommandLine, Consumer, ModuleName); - if (Result) - return std::move(Result); - return Consumer.getFullDependencies(CommandLine); + FDR.FullDeps = std::move(FD); + return FDR; } diff --git a/clang/lib/Tooling/DependencyScanning/DependencyScanningWorker.cpp b/clang/lib/Tooling/DependencyScanning/DependencyScanningWorker.cpp index 474808d888ec..b54b8de9157e 100644 --- a/clang/lib/Tooling/DependencyScanning/DependencyScanningWorker.cpp +++ b/clang/lib/Tooling/DependencyScanning/DependencyScanningWorker.cpp @@ -7,7 +7,12 @@ //===----------------------------------------------------------------------===// #include "clang/Tooling/DependencyScanning/DependencyScanningWorker.h" +#include "clang/Basic/DiagnosticFrontend.h" #include "clang/CodeGen/ObjectFilePCHContainerOperations.h" +#include "clang/Driver/Compilation.h" +#include "clang/Driver/Driver.h" +#include "clang/Driver/Job.h" +#include "clang/Driver/Tool.h" #include "clang/Frontend/CompilerInstance.h" #include "clang/Frontend/CompilerInvocation.h" #include "clang/Frontend/FrontendActions.h" @@ -17,6 +22,8 @@ #include "clang/Tooling/DependencyScanning/DependencyScanningService.h" #include "clang/Tooling/DependencyScanning/ModuleDepCollector.h" #include "clang/Tooling/Tooling.h" +#include "llvm/Support/Host.h" +#include <optional> using namespace clang; using namespace tooling; @@ -28,8 +35,9 @@ namespace { class DependencyConsumerForwarder : public DependencyFileGenerator { public: DependencyConsumerForwarder(std::unique_ptr<DependencyOutputOptions> Opts, - DependencyConsumer &C) - : DependencyFileGenerator(*Opts), Opts(std::move(Opts)), C(C) {} + StringRef WorkingDirectory, DependencyConsumer &C) + : DependencyFileGenerator(*Opts), WorkingDirectory(WorkingDirectory), + Opts(std::move(Opts)), C(C) {} void finishedMainFile(DiagnosticsEngine &Diags) override { C.handleDependencyOutputOpts(*Opts); @@ -37,11 +45,13 @@ public: for (const auto &File : getDependencies()) { CanonPath = File; llvm::sys::path::remove_dots(CanonPath, /*remove_dot_dot=*/true); + llvm::sys::fs::make_absolute(WorkingDirectory, CanonPath); C.handleFileDependency(CanonPath); } } private: + StringRef WorkingDirectory; std::unique_ptr<DependencyOutputOptions> Opts; DependencyConsumer &C; }; @@ -94,7 +104,7 @@ static void visitPrebuiltModule(StringRef PrebuiltModuleFilename, while (!Worklist.empty()) ASTReader::readASTFileControlBlock( - Worklist.pop_back_val(), CI.getFileManager(), + Worklist.pop_back_val(), CI.getFileManager(), CI.getModuleCache(), CI.getPCHContainerReader(), /*FindModuleFileExtensions=*/false, Listener, /*ValidateDiagnosticOptions=*/false); @@ -126,8 +136,8 @@ static void sanitizeDiagOpts(DiagnosticOptions &DiagOpts) { DiagOpts.ShowCarets = false; // Don't write out diagnostic file. DiagOpts.DiagnosticSerializationFile.clear(); - // Don't treat warnings as errors. - DiagOpts.Warnings.push_back("no-error"); + // Don't emit warnings as errors (and all other warnings too). + DiagOpts.IgnoreWarnings = true; } /// A clang tool that runs the preprocessor in a mode that's optimized for @@ -137,11 +147,12 @@ public: DependencyScanningAction( StringRef WorkingDirectory, DependencyConsumer &Consumer, llvm::IntrusiveRefCntPtr<DependencyScanningWorkerFilesystem> DepFS, - ScanningOutputFormat Format, bool OptimizeArgs, bool DisableFree, - llvm::Optional<StringRef> ModuleName = None) + ScanningOutputFormat Format, bool OptimizeArgs, bool EagerLoadModules, + bool DisableFree, std::optional<StringRef> ModuleName = std::nullopt) : WorkingDirectory(WorkingDirectory), Consumer(Consumer), DepFS(std::move(DepFS)), Format(Format), OptimizeArgs(OptimizeArgs), - DisableFree(DisableFree), ModuleName(ModuleName) {} + EagerLoadModules(EagerLoadModules), DisableFree(DisableFree), + ModuleName(ModuleName) {} bool runInvocation(std::shared_ptr<CompilerInvocation> Invocation, FileManager *FileMgr, @@ -152,8 +163,20 @@ public: // Restore the value of DisableFree, which may be modified by Tooling. OriginalInvocation.getFrontendOpts().DisableFree = DisableFree; + if (Scanned) { + // Scanning runs once for the first -cc1 invocation in a chain of driver + // jobs. For any dependent jobs, reuse the scanning result and just + // update the LastCC1Arguments to correspond to the new invocation. + // FIXME: to support multi-arch builds, each arch requires a separate scan + setLastCC1Arguments(std::move(OriginalInvocation)); + return true; + } + + Scanned = true; + // Create a compiler instance to handle the actual work. - CompilerInstance ScanInstance(std::move(PCHContainerOps)); + ScanInstanceStorage.emplace(std::move(PCHContainerOps)); + CompilerInstance &ScanInstance = *ScanInstanceStorage; ScanInstance.setInvocation(std::move(Invocation)); // Create the compiler's actual diagnostics engine. @@ -167,9 +190,14 @@ public: ScanInstance.getFrontendOpts().GenerateGlobalModuleIndex = false; ScanInstance.getFrontendOpts().UseGlobalModuleIndex = false; + ScanInstance.getFrontendOpts().ModulesShareFileManager = false; - FileMgr->getFileSystemOpts().WorkingDir = std::string(WorkingDirectory); ScanInstance.setFileManager(FileMgr); + // Support for virtual file system overlays. + FileMgr->setVirtualFileSystem(createVFSFromCompilerInvocation( + ScanInstance.getInvocation(), ScanInstance.getDiagnostics(), + FileMgr->getVirtualFileSystemPtr())); + ScanInstance.createSourceManager(*FileMgr); llvm::StringSet<> PrebuiltModulesInputFiles; @@ -184,20 +212,15 @@ public: // Use the dependency scanning optimized file system if requested to do so. if (DepFS) { - // Support for virtual file system overlays on top of the caching - // filesystem. - FileMgr->setVirtualFileSystem(createVFSFromCompilerInvocation( - ScanInstance.getInvocation(), ScanInstance.getDiagnostics(), DepFS)); - llvm::IntrusiveRefCntPtr<DependencyScanningWorkerFilesystem> LocalDepFS = DepFS; ScanInstance.getPreprocessorOpts().DependencyDirectivesForFile = [LocalDepFS = std::move(LocalDepFS)](FileEntryRef File) - -> Optional<ArrayRef<dependency_directives_scan::Directive>> { + -> std::optional<ArrayRef<dependency_directives_scan::Directive>> { if (llvm::ErrorOr<EntryRef> Entry = LocalDepFS->getOrCreateFileSystemEntry(File.getName())) return Entry->getDirectiveTokens(); - return None; + return std::nullopt; }; } @@ -221,13 +244,14 @@ public: switch (Format) { case ScanningOutputFormat::Make: ScanInstance.addDependencyCollector( - std::make_shared<DependencyConsumerForwarder>(std::move(Opts), - Consumer)); + std::make_shared<DependencyConsumerForwarder>( + std::move(Opts), WorkingDirectory, Consumer)); break; case ScanningOutputFormat::Full: - ScanInstance.addDependencyCollector(std::make_shared<ModuleDepCollector>( - std::move(Opts), ScanInstance, Consumer, - std::move(OriginalInvocation), OptimizeArgs)); + MDC = std::make_shared<ModuleDepCollector>( + std::move(Opts), ScanInstance, Consumer, OriginalInvocation, + OptimizeArgs, EagerLoadModules); + ScanInstance.addDependencyCollector(MDC); break; } @@ -246,19 +270,44 @@ public: Action = std::make_unique<ReadPCHAndPreprocessAction>(); const bool Result = ScanInstance.ExecuteAction(*Action); - if (!DepFS) - FileMgr->clearStatCache(); + + if (Result) + setLastCC1Arguments(std::move(OriginalInvocation)); + return Result; } + bool hasScanned() const { return Scanned; } + + /// Take the cc1 arguments corresponding to the most recent invocation used + /// with this action. Any modifications implied by the discovered dependencies + /// will have already been applied. + std::vector<std::string> takeLastCC1Arguments() { + std::vector<std::string> Result; + std::swap(Result, LastCC1Arguments); // Reset LastCC1Arguments to empty. + return Result; + } + +private: + void setLastCC1Arguments(CompilerInvocation &&CI) { + if (MDC) + MDC->applyDiscoveredDependencies(CI); + LastCC1Arguments = CI.getCC1CommandLine(); + } + private: StringRef WorkingDirectory; DependencyConsumer &Consumer; llvm::IntrusiveRefCntPtr<DependencyScanningWorkerFilesystem> DepFS; ScanningOutputFormat Format; bool OptimizeArgs; + bool EagerLoadModules; bool DisableFree; - llvm::Optional<StringRef> ModuleName; + std::optional<StringRef> ModuleName; + std::optional<CompilerInstance> ScanInstanceStorage; + std::shared_ptr<ModuleDepCollector> MDC; + std::vector<std::string> LastCC1Arguments; + bool Scanned = false; }; } // end anonymous namespace @@ -266,7 +315,8 @@ private: DependencyScanningWorker::DependencyScanningWorker( DependencyScanningService &Service, llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> FS) - : Format(Service.getFormat()), OptimizeArgs(Service.canOptimizeArgs()) { + : Format(Service.getFormat()), OptimizeArgs(Service.canOptimizeArgs()), + EagerLoadModules(Service.shouldEagerLoadModules()) { PCHContainerOps = std::make_shared<PCHContainerOperations>(); PCHContainerOps->registerReader( std::make_unique<ObjectFilePCHContainerReader>()); @@ -275,80 +325,151 @@ DependencyScanningWorker::DependencyScanningWorker( PCHContainerOps->registerWriter( std::make_unique<ObjectFilePCHContainerWriter>()); - auto OverlayFS = - llvm::makeIntrusiveRefCnt<llvm::vfs::OverlayFileSystem>(std::move(FS)); - InMemoryFS = llvm::makeIntrusiveRefCnt<llvm::vfs::InMemoryFileSystem>(); - OverlayFS->pushOverlay(InMemoryFS); - RealFS = OverlayFS; - - if (Service.getMode() == ScanningMode::DependencyDirectivesScan) - DepFS = new DependencyScanningWorkerFilesystem(Service.getSharedCache(), - RealFS); - if (Service.canReuseFileManager()) - Files = new FileManager(FileSystemOptions(), RealFS); + switch (Service.getMode()) { + case ScanningMode::DependencyDirectivesScan: + DepFS = + new DependencyScanningWorkerFilesystem(Service.getSharedCache(), FS); + BaseFS = DepFS; + break; + case ScanningMode::CanonicalPreprocessing: + DepFS = nullptr; + BaseFS = FS; + break; + } } -static llvm::Error -runWithDiags(DiagnosticOptions *DiagOpts, - llvm::function_ref<bool(DiagnosticConsumer &, DiagnosticOptions &)> - BodyShouldSucceed) { +llvm::Error DependencyScanningWorker::computeDependencies( + StringRef WorkingDirectory, const std::vector<std::string> &CommandLine, + DependencyConsumer &Consumer, std::optional<StringRef> ModuleName) { + std::vector<const char *> CLI; + for (const std::string &Arg : CommandLine) + CLI.push_back(Arg.c_str()); + auto DiagOpts = CreateAndPopulateDiagOpts(CLI); sanitizeDiagOpts(*DiagOpts); // Capture the emitted diagnostics and report them to the client // in the case of a failure. std::string DiagnosticOutput; llvm::raw_string_ostream DiagnosticsOS(DiagnosticOutput); - TextDiagnosticPrinter DiagPrinter(DiagnosticsOS, DiagOpts); + TextDiagnosticPrinter DiagPrinter(DiagnosticsOS, DiagOpts.release()); - if (BodyShouldSucceed(DiagPrinter, *DiagOpts)) + if (computeDependencies(WorkingDirectory, CommandLine, Consumer, DiagPrinter, + ModuleName)) return llvm::Error::success(); return llvm::make_error<llvm::StringError>(DiagnosticsOS.str(), llvm::inconvertibleErrorCode()); } -llvm::Error DependencyScanningWorker::computeDependencies( +static bool forEachDriverJob( + ArrayRef<std::string> Args, DiagnosticsEngine &Diags, FileManager &FM, + llvm::function_ref<bool(const driver::Command &Cmd)> Callback) { + std::unique_ptr<driver::Driver> Driver = std::make_unique<driver::Driver>( + Args[0], llvm::sys::getDefaultTargetTriple(), Diags, + "clang LLVM compiler", &FM.getVirtualFileSystem()); + Driver->setTitle("clang_based_tool"); + + std::vector<const char *> Argv; + for (const std::string &Arg : Args) + Argv.push_back(Arg.c_str()); + + const std::unique_ptr<driver::Compilation> Compilation( + Driver->BuildCompilation(llvm::ArrayRef(Argv))); + if (!Compilation) + return false; + + for (const driver::Command &Job : Compilation->getJobs()) { + if (!Callback(Job)) + return false; + } + return true; +} + +bool DependencyScanningWorker::computeDependencies( StringRef WorkingDirectory, const std::vector<std::string> &CommandLine, - DependencyConsumer &Consumer, llvm::Optional<StringRef> ModuleName) { + DependencyConsumer &Consumer, DiagnosticConsumer &DC, + std::optional<StringRef> ModuleName) { // Reset what might have been modified in the previous worker invocation. - RealFS->setCurrentWorkingDirectory(WorkingDirectory); - if (Files) - Files->setVirtualFileSystem(RealFS); - - llvm::IntrusiveRefCntPtr<FileManager> CurrentFiles = - Files ? Files : new FileManager(FileSystemOptions(), RealFS); + BaseFS->setCurrentWorkingDirectory(WorkingDirectory); - Optional<std::vector<std::string>> ModifiedCommandLine; + std::optional<std::vector<std::string>> ModifiedCommandLine; + llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> ModifiedFS; if (ModuleName) { ModifiedCommandLine = CommandLine; - InMemoryFS->addFile(*ModuleName, 0, llvm::MemoryBuffer::getMemBuffer("")); ModifiedCommandLine->emplace_back(*ModuleName); + + auto OverlayFS = + llvm::makeIntrusiveRefCnt<llvm::vfs::OverlayFileSystem>(BaseFS); + auto InMemoryFS = + llvm::makeIntrusiveRefCnt<llvm::vfs::InMemoryFileSystem>(); + InMemoryFS->setCurrentWorkingDirectory(WorkingDirectory); + InMemoryFS->addFile(*ModuleName, 0, llvm::MemoryBuffer::getMemBuffer("")); + OverlayFS->pushOverlay(InMemoryFS); + ModifiedFS = OverlayFS; } const std::vector<std::string> &FinalCommandLine = ModifiedCommandLine ? *ModifiedCommandLine : CommandLine; + FileSystemOptions FSOpts; + FSOpts.WorkingDir = WorkingDirectory.str(); + auto FileMgr = llvm::makeIntrusiveRefCnt<FileManager>( + FSOpts, ModifiedFS ? ModifiedFS : BaseFS); + std::vector<const char *> FinalCCommandLine(CommandLine.size(), nullptr); llvm::transform(CommandLine, FinalCCommandLine.begin(), [](const std::string &Str) { return Str.c_str(); }); - return runWithDiags(CreateAndPopulateDiagOpts(FinalCCommandLine).release(), - [&](DiagnosticConsumer &DC, DiagnosticOptions &DiagOpts) { - // DisableFree is modified by Tooling for running - // in-process; preserve the original value, which is - // always true for a driver invocation. - bool DisableFree = true; - DependencyScanningAction Action( - WorkingDirectory, Consumer, DepFS, Format, - OptimizeArgs, DisableFree, ModuleName); - // Create an invocation that uses the underlying file - // system to ensure that any file system requests that - // are made by the driver do not go through the - // dependency scanning filesystem. - ToolInvocation Invocation(FinalCommandLine, &Action, - CurrentFiles.get(), - PCHContainerOps); - Invocation.setDiagnosticConsumer(&DC); - Invocation.setDiagnosticOptions(&DiagOpts); - return Invocation.run(); - }); + auto DiagOpts = CreateAndPopulateDiagOpts(FinalCCommandLine); + sanitizeDiagOpts(*DiagOpts); + IntrusiveRefCntPtr<DiagnosticsEngine> Diags = + CompilerInstance::createDiagnostics(DiagOpts.release(), &DC, + /*ShouldOwnClient=*/false); + + // Although `Diagnostics` are used only for command-line parsing, the + // custom `DiagConsumer` might expect a `SourceManager` to be present. + SourceManager SrcMgr(*Diags, *FileMgr); + Diags->setSourceManager(&SrcMgr); + // DisableFree is modified by Tooling for running + // in-process; preserve the original value, which is + // always true for a driver invocation. + bool DisableFree = true; + DependencyScanningAction Action(WorkingDirectory, Consumer, DepFS, Format, + OptimizeArgs, EagerLoadModules, DisableFree, + ModuleName); + bool Success = forEachDriverJob( + FinalCommandLine, *Diags, *FileMgr, [&](const driver::Command &Cmd) { + if (StringRef(Cmd.getCreator().getName()) != "clang") { + // Non-clang command. Just pass through to the dependency + // consumer. + Consumer.handleBuildCommand( + {Cmd.getExecutable(), + {Cmd.getArguments().begin(), Cmd.getArguments().end()}}); + return true; + } + + std::vector<std::string> Argv; + Argv.push_back(Cmd.getExecutable()); + Argv.insert(Argv.end(), Cmd.getArguments().begin(), + Cmd.getArguments().end()); + + // Create an invocation that uses the underlying file + // system to ensure that any file system requests that + // are made by the driver do not go through the + // dependency scanning filesystem. + ToolInvocation Invocation(std::move(Argv), &Action, &*FileMgr, + PCHContainerOps); + Invocation.setDiagnosticConsumer(Diags->getClient()); + Invocation.setDiagnosticOptions(&Diags->getDiagnosticOptions()); + if (!Invocation.run()) + return false; + + std::vector<std::string> Args = Action.takeLastCC1Arguments(); + Consumer.handleBuildCommand({Cmd.getExecutable(), std::move(Args)}); + return true; + }); + + if (Success && !Action.hasScanned()) + Diags->Report(diag::err_fe_expected_compiler_job) + << llvm::join(FinalCommandLine, " "); + return Success && Action.hasScanned(); } diff --git a/clang/lib/Tooling/DependencyScanning/ModuleDepCollector.cpp b/clang/lib/Tooling/DependencyScanning/ModuleDepCollector.cpp index 725bb2c318ac..cb1c66b8d63f 100644 --- a/clang/lib/Tooling/DependencyScanning/ModuleDepCollector.cpp +++ b/clang/lib/Tooling/DependencyScanning/ModuleDepCollector.cpp @@ -12,7 +12,9 @@ #include "clang/Frontend/CompilerInstance.h" #include "clang/Lex/Preprocessor.h" #include "clang/Tooling/DependencyScanning/DependencyScanningWorker.h" +#include "llvm/Support/BLAKE3.h" #include "llvm/Support/StringSaver.h" +#include <optional> using namespace clang; using namespace tooling; @@ -41,124 +43,260 @@ static void optimizeHeaderSearchOpts(HeaderSearchOptions &Opts, Opts.UserEntries.push_back(Entries[Idx]); } -CompilerInvocation ModuleDepCollector::makeInvocationForModuleBuildWithoutPaths( +static std::vector<std::string> splitString(std::string S, char Separator) { + SmallVector<StringRef> Segments; + StringRef(S).split(Segments, Separator, /*MaxSplit=*/-1, /*KeepEmpty=*/false); + std::vector<std::string> Result; + Result.reserve(Segments.size()); + for (StringRef Segment : Segments) + Result.push_back(Segment.str()); + return Result; +} + +void ModuleDepCollector::addOutputPaths(CompilerInvocation &CI, + ModuleDeps &Deps) { + CI.getFrontendOpts().OutputFile = + Consumer.lookupModuleOutput(Deps.ID, ModuleOutputKind::ModuleFile); + if (!CI.getDiagnosticOpts().DiagnosticSerializationFile.empty()) + CI.getDiagnosticOpts().DiagnosticSerializationFile = + Consumer.lookupModuleOutput( + Deps.ID, ModuleOutputKind::DiagnosticSerializationFile); + if (!CI.getDependencyOutputOpts().OutputFile.empty()) { + CI.getDependencyOutputOpts().OutputFile = + Consumer.lookupModuleOutput(Deps.ID, ModuleOutputKind::DependencyFile); + CI.getDependencyOutputOpts().Targets = + splitString(Consumer.lookupModuleOutput( + Deps.ID, ModuleOutputKind::DependencyTargets), + '\0'); + if (!CI.getDependencyOutputOpts().OutputFile.empty() && + CI.getDependencyOutputOpts().Targets.empty()) { + // Fallback to -o as dependency target, as in the driver. + SmallString<128> Target; + quoteMakeTarget(CI.getFrontendOpts().OutputFile, Target); + CI.getDependencyOutputOpts().Targets.push_back(std::string(Target)); + } + } +} + +CompilerInvocation +ModuleDepCollector::makeInvocationForModuleBuildWithoutOutputs( const ModuleDeps &Deps, llvm::function_ref<void(CompilerInvocation &)> Optimize) const { // Make a deep copy of the original Clang invocation. CompilerInvocation CI(OriginalInvocation); - CI.getLangOpts()->resetNonModularOptions(); - CI.getPreprocessorOpts().resetNonModularOptions(); + CI.resetNonModularOptions(); + CI.clearImplicitModuleBuildOptions(); // Remove options incompatible with explicit module build or are likely to // differ between identical modules discovered from different translation // units. CI.getFrontendOpts().Inputs.clear(); CI.getFrontendOpts().OutputFile.clear(); + + // TODO: Figure out better way to set options to their default value. CI.getCodeGenOpts().MainFileName.clear(); CI.getCodeGenOpts().DwarfDebugFlags.clear(); - CI.getDiagnosticOpts().DiagnosticSerializationFile.clear(); - CI.getDependencyOutputOpts().OutputFile.clear(); + if (!CI.getLangOpts()->ModulesCodegen) { + CI.getCodeGenOpts().DebugCompilationDir.clear(); + CI.getCodeGenOpts().CoverageCompilationDir.clear(); + } + + // Map output paths that affect behaviour to "-" so their existence is in the + // context hash. The final path will be computed in addOutputPaths. + if (!CI.getDiagnosticOpts().DiagnosticSerializationFile.empty()) + CI.getDiagnosticOpts().DiagnosticSerializationFile = "-"; + if (!CI.getDependencyOutputOpts().OutputFile.empty()) + CI.getDependencyOutputOpts().OutputFile = "-"; CI.getDependencyOutputOpts().Targets.clear(); CI.getFrontendOpts().ProgramAction = frontend::GenerateModule; CI.getLangOpts()->ModuleName = Deps.ID.ModuleName; CI.getFrontendOpts().IsSystemModule = Deps.IsSystem; - // Disable implicit modules and canonicalize options that are only used by - // implicit modules. - CI.getLangOpts()->ImplicitModules = false; - CI.getHeaderSearchOpts().ImplicitModuleMaps = false; - CI.getHeaderSearchOpts().ModuleCachePath.clear(); - CI.getHeaderSearchOpts().ModulesValidateOncePerBuildSession = false; - CI.getHeaderSearchOpts().BuildSessionTimestamp = 0; - // The specific values we canonicalize to for pruning don't affect behaviour, - /// so use the default values so they will be dropped from the command-line. - CI.getHeaderSearchOpts().ModuleCachePruneInterval = 7 * 24 * 60 * 60; - CI.getHeaderSearchOpts().ModuleCachePruneAfter = 31 * 24 * 60 * 60; + // Inputs + InputKind ModuleMapInputKind(CI.getFrontendOpts().DashX.getLanguage(), + InputKind::Format::ModuleMap); + CI.getFrontendOpts().Inputs.emplace_back(Deps.ClangModuleMapFile, + ModuleMapInputKind); + + auto CurrentModuleMapEntry = + ScanInstance.getFileManager().getFile(Deps.ClangModuleMapFile); + assert(CurrentModuleMapEntry && "module map file entry not found"); + + auto DepModuleMapFiles = collectModuleMapFiles(Deps.ClangModuleDeps); + for (StringRef ModuleMapFile : Deps.ModuleMapFileDeps) { + // TODO: Track these as `FileEntryRef` to simplify the equality check below. + auto ModuleMapEntry = ScanInstance.getFileManager().getFile(ModuleMapFile); + assert(ModuleMapEntry && "module map file entry not found"); + + // Don't report module maps describing eagerly-loaded dependency. This + // information will be deserialized from the PCM. + // TODO: Verify this works fine when modulemap for module A is eagerly + // loaded from A.pcm, and module map passed on the command line contains + // definition of a submodule: "explicit module A.Private { ... }". + if (EagerLoadModules && DepModuleMapFiles.contains(*ModuleMapEntry)) + continue; + + // Don't report module map file of the current module unless it also + // describes a dependency (for symmetry). + if (*ModuleMapEntry == *CurrentModuleMapEntry && + !DepModuleMapFiles.contains(*ModuleMapEntry)) + continue; + + CI.getFrontendOpts().ModuleMapFiles.emplace_back(ModuleMapFile); + } // Report the prebuilt modules this module uses. for (const auto &PrebuiltModule : Deps.PrebuiltModuleDeps) CI.getFrontendOpts().ModuleFiles.push_back(PrebuiltModule.PCMFile); - CI.getFrontendOpts().ModuleMapFiles = Deps.ModuleMapFileDeps; + // Add module file inputs from dependencies. + addModuleFiles(CI, Deps.ClangModuleDeps); + + // Remove any macro definitions that are explicitly ignored. + if (!CI.getHeaderSearchOpts().ModulesIgnoreMacros.empty()) { + llvm::erase_if( + CI.getPreprocessorOpts().Macros, + [&CI](const std::pair<std::string, bool> &Def) { + StringRef MacroDef = Def.first; + return CI.getHeaderSearchOpts().ModulesIgnoreMacros.contains( + llvm::CachedHashString(MacroDef.split('=').first)); + }); + // Remove the now unused option. + CI.getHeaderSearchOpts().ModulesIgnoreMacros.clear(); + } Optimize(CI); - // The original invocation probably didn't have strict context hash enabled. - // We will use the context hash of this invocation to distinguish between - // multiple incompatible versions of the same module and will use it when - // reporting dependencies to the clients. Let's make sure we're using - // **strict** context hash in order to prevent accidental sharing of - // incompatible modules (e.g. with differences in search paths). - CI.getHeaderSearchOpts().ModulesStrictContextHash = true; - return CI; } -static std::vector<std::string> -serializeCompilerInvocation(const CompilerInvocation &CI) { - // Set up string allocator. - llvm::BumpPtrAllocator Alloc; - llvm::StringSaver Strings(Alloc); - auto SA = [&Strings](const Twine &Arg) { return Strings.save(Arg).data(); }; +llvm::DenseSet<const FileEntry *> ModuleDepCollector::collectModuleMapFiles( + ArrayRef<ModuleID> ClangModuleDeps) const { + llvm::DenseSet<const FileEntry *> ModuleMapFiles; + for (const ModuleID &MID : ClangModuleDeps) { + ModuleDeps *MD = ModuleDepsByID.lookup(MID); + assert(MD && "Inconsistent dependency info"); + // TODO: Track ClangModuleMapFile as `FileEntryRef`. + auto FE = ScanInstance.getFileManager().getFile(MD->ClangModuleMapFile); + assert(FE && "Missing module map file that was previously found"); + ModuleMapFiles.insert(*FE); + } + return ModuleMapFiles; +} - // Synthesize full command line from the CompilerInvocation, including "-cc1". - SmallVector<const char *, 32> Args{"-cc1"}; - CI.generateCC1CommandLine(Args, SA); +void ModuleDepCollector::addModuleMapFiles( + CompilerInvocation &CI, ArrayRef<ModuleID> ClangModuleDeps) const { + if (EagerLoadModules) + return; // Only pcm is needed for eager load. - // Convert arguments to the return type. - return std::vector<std::string>{Args.begin(), Args.end()}; + for (const ModuleID &MID : ClangModuleDeps) { + ModuleDeps *MD = ModuleDepsByID.lookup(MID); + assert(MD && "Inconsistent dependency info"); + CI.getFrontendOpts().ModuleMapFiles.push_back(MD->ClangModuleMapFile); + } } -static std::vector<std::string> splitString(std::string S, char Separator) { - SmallVector<StringRef> Segments; - StringRef(S).split(Segments, Separator, /*MaxSplit=*/-1, /*KeepEmpty=*/false); - std::vector<std::string> Result; - Result.reserve(Segments.size()); - for (StringRef Segment : Segments) - Result.push_back(Segment.str()); - return Result; +void ModuleDepCollector::addModuleFiles( + CompilerInvocation &CI, ArrayRef<ModuleID> ClangModuleDeps) const { + for (const ModuleID &MID : ClangModuleDeps) { + std::string PCMPath = + Consumer.lookupModuleOutput(MID, ModuleOutputKind::ModuleFile); + if (EagerLoadModules) + CI.getFrontendOpts().ModuleFiles.push_back(std::move(PCMPath)); + else + CI.getHeaderSearchOpts().PrebuiltModuleFiles.insert( + {MID.ModuleName, std::move(PCMPath)}); + } +} + +static bool needsModules(FrontendInputFile FIF) { + switch (FIF.getKind().getLanguage()) { + case Language::Unknown: + case Language::Asm: + case Language::LLVM_IR: + return false; + default: + return true; + } } -std::vector<std::string> ModuleDeps::getCanonicalCommandLine( - llvm::function_ref<std::string(const ModuleID &, ModuleOutputKind)> - LookupModuleOutput) const { - CompilerInvocation CI(BuildInvocation); - FrontendOptions &FrontendOpts = CI.getFrontendOpts(); +void ModuleDepCollector::applyDiscoveredDependencies(CompilerInvocation &CI) { + CI.clearImplicitModuleBuildOptions(); - InputKind ModuleMapInputKind(FrontendOpts.DashX.getLanguage(), - InputKind::Format::ModuleMap); - FrontendOpts.Inputs.emplace_back(ClangModuleMapFile, ModuleMapInputKind); - FrontendOpts.OutputFile = - LookupModuleOutput(ID, ModuleOutputKind::ModuleFile); - if (HadSerializedDiagnostics) - CI.getDiagnosticOpts().DiagnosticSerializationFile = - LookupModuleOutput(ID, ModuleOutputKind::DiagnosticSerializationFile); - if (HadDependencyFile) { - DependencyOutputOptions &DepOpts = CI.getDependencyOutputOpts(); - DepOpts.OutputFile = - LookupModuleOutput(ID, ModuleOutputKind::DependencyFile); - DepOpts.Targets = splitString( - LookupModuleOutput(ID, ModuleOutputKind::DependencyTargets), '\0'); - if (!DepOpts.OutputFile.empty() && DepOpts.Targets.empty()) { - // Fallback to -o as dependency target, as in the driver. - SmallString<128> Target; - quoteMakeTarget(FrontendOpts.OutputFile, Target); - DepOpts.Targets.push_back(std::string(Target)); - } + if (llvm::any_of(CI.getFrontendOpts().Inputs, needsModules)) { + Preprocessor &PP = ScanInstance.getPreprocessor(); + if (Module *CurrentModule = PP.getCurrentModuleImplementation()) + if (OptionalFileEntryRef CurrentModuleMap = + PP.getHeaderSearchInfo() + .getModuleMap() + .getModuleMapFileForUniquing(CurrentModule)) + CI.getFrontendOpts().ModuleMapFiles.emplace_back( + CurrentModuleMap->getName()); + + SmallVector<ModuleID> DirectDeps; + for (const auto &KV : ModularDeps) + if (KV.second->ImportedByMainFile) + DirectDeps.push_back(KV.second->ID); + + // TODO: Report module maps the same way it's done for modular dependencies. + addModuleMapFiles(CI, DirectDeps); + + addModuleFiles(CI, DirectDeps); + + for (const auto &KV : DirectPrebuiltModularDeps) + CI.getFrontendOpts().ModuleFiles.push_back(KV.second.PCMFile); + } +} + +static std::string getModuleContextHash(const ModuleDeps &MD, + const CompilerInvocation &CI, + bool EagerLoadModules) { + llvm::HashBuilder<llvm::TruncatedBLAKE3<16>, + llvm::support::endianness::native> + HashBuilder; + SmallString<32> Scratch; + + // Hash the compiler version and serialization version to ensure the module + // will be readable. + HashBuilder.add(getClangFullRepositoryVersion()); + HashBuilder.add(serialization::VERSION_MAJOR, serialization::VERSION_MINOR); + + // Hash the BuildInvocation without any input files. + SmallVector<const char *, 32> DummyArgs; + CI.generateCC1CommandLine(DummyArgs, [&](const Twine &Arg) { + Scratch.clear(); + StringRef Str = Arg.toStringRef(Scratch); + HashBuilder.add(Str); + return "<unused>"; + }); + + // Hash the module dependencies. These paths may differ even if the invocation + // is identical if they depend on the contents of the files in the TU -- for + // example, case-insensitive paths to modulemap files. Usually such a case + // would indicate a missed optimization to canonicalize, but it may be + // difficult to canonicalize all cases when there is a VFS. + for (const auto &ID : MD.ClangModuleDeps) { + HashBuilder.add(ID.ModuleName); + HashBuilder.add(ID.ContextHash); } - for (ModuleID MID : ClangModuleDeps) - FrontendOpts.ModuleFiles.push_back( - LookupModuleOutput(MID, ModuleOutputKind::ModuleFile)); + HashBuilder.add(EagerLoadModules); - return serializeCompilerInvocation(CI); + llvm::BLAKE3Result<16> Hash = HashBuilder.final(); + std::array<uint64_t, 2> Words; + static_assert(sizeof(Hash) == sizeof(Words), "Hash must match Words"); + std::memcpy(Words.data(), Hash.data(), sizeof(Hash)); + return toString(llvm::APInt(sizeof(Words) * 8, Words), 36, /*Signed=*/false); } -std::vector<std::string> -ModuleDeps::getCanonicalCommandLineWithoutModulePaths() const { - return serializeCompilerInvocation(BuildInvocation); +void ModuleDepCollector::associateWithContextHash(const CompilerInvocation &CI, + ModuleDeps &Deps) { + Deps.ID.ContextHash = getModuleContextHash(Deps, CI, EagerLoadModules); + bool Inserted = ModuleDepsByID.insert({Deps.ID, &Deps}).second; + (void)Inserted; + assert(Inserted && "duplicate module mapping"); } void ModuleDepCollectorPP::FileChanged(SourceLocation Loc, @@ -180,21 +318,20 @@ void ModuleDepCollectorPP::FileChanged(SourceLocation Loc, // Dependency generation really does want to go all the way to the // file entry for a source location to find out what is depended on. // We do not want #line markers to affect dependency generation! - if (Optional<StringRef> Filename = + if (std::optional<StringRef> Filename = SM.getNonBuiltinFilenameForID(SM.getFileID(SM.getExpansionLoc(Loc)))) - MDC.FileDeps.push_back( - std::string(llvm::sys::path::remove_leading_dotslash(*Filename))); + MDC.addFileDep(llvm::sys::path::remove_leading_dotslash(*Filename)); } void ModuleDepCollectorPP::InclusionDirective( SourceLocation HashLoc, const Token &IncludeTok, StringRef FileName, - bool IsAngled, CharSourceRange FilenameRange, Optional<FileEntryRef> File, + bool IsAngled, CharSourceRange FilenameRange, OptionalFileEntryRef File, StringRef SearchPath, StringRef RelativePath, const Module *Imported, SrcMgr::CharacteristicKind FileType) { if (!File && !Imported) { // This is a non-modular include that HeaderSearch failed to find. Add it // here as `FileChanged` will never see it. - MDC.FileDeps.push_back(std::string(FileName)); + MDC.addFileDep(FileName); } handleImport(Imported); } @@ -212,7 +349,8 @@ void ModuleDepCollectorPP::handleImport(const Module *Imported) { const Module *TopLevelModule = Imported->getTopLevelModule(); if (MDC.isPrebuiltModule(TopLevelModule)) - DirectPrebuiltModularDeps.insert(TopLevelModule); + MDC.DirectPrebuiltModularDeps.insert( + {TopLevelModule, PrebuiltModuleDep{TopLevelModule}}); else DirectModularDeps.insert(TopLevelModule); } @@ -224,18 +362,15 @@ void ModuleDepCollectorPP::EndOfMainFile() { ->getName()); if (!MDC.ScanInstance.getPreprocessorOpts().ImplicitPCHInclude.empty()) - MDC.FileDeps.push_back( - MDC.ScanInstance.getPreprocessorOpts().ImplicitPCHInclude); - - for (const Module *M : DirectModularDeps) { - // A top-level module might not be actually imported as a module when - // -fmodule-name is used to compile a translation unit that imports this - // module. In that case it can be skipped. The appropriate header - // dependencies will still be reported as expected. - if (!M->getASTFile()) - continue; + MDC.addFileDep(MDC.ScanInstance.getPreprocessorOpts().ImplicitPCHInclude); + + for (const Module *M : + MDC.ScanInstance.getPreprocessor().getAffectingClangModules()) + if (!MDC.isPrebuiltModule(M)) + DirectModularDeps.insert(M); + + for (const Module *M : DirectModularDeps) handleTopLevelModule(M); - } MDC.Consumer.handleDependencyOutputOpts(*MDC.Opts); @@ -245,13 +380,21 @@ void ModuleDepCollectorPP::EndOfMainFile() { for (auto &&I : MDC.FileDeps) MDC.Consumer.handleFileDependency(I); - for (auto &&I : DirectPrebuiltModularDeps) - MDC.Consumer.handlePrebuiltModuleDependency(PrebuiltModuleDep{I}); + for (auto &&I : MDC.DirectPrebuiltModularDeps) + MDC.Consumer.handlePrebuiltModuleDependency(I.second); } -ModuleID ModuleDepCollectorPP::handleTopLevelModule(const Module *M) { +std::optional<ModuleID> +ModuleDepCollectorPP::handleTopLevelModule(const Module *M) { assert(M == M->getTopLevelModule() && "Expected top level module!"); + // A top-level module might not be actually imported as a module when + // -fmodule-name is used to compile a translation unit that imports this + // module. In that case it can be skipped. The appropriate header + // dependencies will still be reported as expected. + if (!M->getASTFile()) + return {}; + // If this module has been handled already, just return its ID. auto ModI = MDC.ModularDeps.insert({M, nullptr}); if (!ModI.second) @@ -262,18 +405,16 @@ ModuleID ModuleDepCollectorPP::handleTopLevelModule(const Module *M) { MD.ID.ModuleName = M->getFullModuleName(); MD.ImportedByMainFile = DirectModularDeps.contains(M); - MD.ImplicitModulePCMPath = std::string(M->getASTFile()->getName()); MD.IsSystem = M->IsSystem; - const FileEntry *ModuleMap = MDC.ScanInstance.getPreprocessor() - .getHeaderSearchInfo() - .getModuleMap() - .getModuleMapFileForUniquing(M); + ModuleMap &ModMapInfo = + MDC.ScanInstance.getPreprocessor().getHeaderSearchInfo().getModuleMap(); + + OptionalFileEntryRef ModuleMap = ModMapInfo.getModuleMapFileForUniquing(M); if (ModuleMap) { - StringRef Path = ModuleMap->tryGetRealPathName(); - if (Path.empty()) - Path = ModuleMap->getName(); + SmallString<128> Path = ModuleMap->getNameAsRequested(); + ModMapInfo.canonicalizeModuleMapPath(Path); MD.ClangModuleMapFile = std::string(Path); } @@ -288,70 +429,37 @@ ModuleID ModuleDepCollectorPP::handleTopLevelModule(const Module *M) { // handle it like normal. With explicitly built modules we don't need // to play VFS tricks, so replace it with the correct module map. if (IF.getFile()->getName().endswith("__inferred_module.map")) { - MD.FileDeps.insert(ModuleMap->getName()); + MDC.addFileDep(MD, ModuleMap->getName()); return; } - MD.FileDeps.insert(IF.getFile()->getName()); + MDC.addFileDep(MD, IF.getFile()->getName()); }); - // We usually don't need to list the module map files of our dependencies when - // building a module explicitly: their semantics will be deserialized from PCM - // files. - // - // However, some module maps loaded implicitly during the dependency scan can - // describe anti-dependencies. That happens when this module, let's call it - // M1, is marked as '[no_undeclared_includes]' and tries to access a header - // "M2/M2.h" from another module, M2, but doesn't have a 'use M2;' - // declaration. The explicit build needs the module map for M2 so that it - // knows that textually including "M2/M2.h" is not allowed. - // E.g., '__has_include("M2/M2.h")' should return false, but without M2's - // module map the explicit build would return true. - // - // An alternative approach would be to tell the explicit build what its - // textual dependencies are, instead of having it re-discover its - // anti-dependencies. For example, we could create and use an `-ivfs-overlay` - // with `fall-through: false` that explicitly listed the dependencies. - // However, that's more complicated to implement and harder to reason about. - if (M->NoUndeclaredIncludes) { - // We don't have a good way to determine which module map described the - // anti-dependency (let alone what's the corresponding top-level module - // map). We simply specify all the module maps in the order they were loaded - // during the implicit build during scan. - // TODO: Resolve this by serializing and only using Module::UndeclaredUses. - MDC.ScanInstance.getASTReader()->visitTopLevelModuleMaps( - *MF, [&](const FileEntry *FE) { - if (FE->getName().endswith("__inferred_module.map")) - return; - // The top-level modulemap of this module will be the input file. We - // don't need to specify it as a module map. - if (FE == ModuleMap) - return; - MD.ModuleMapFileDeps.push_back(FE->getName().str()); - }); - } + llvm::DenseSet<const Module *> SeenDeps; + addAllSubmodulePrebuiltDeps(M, MD, SeenDeps); + addAllSubmoduleDeps(M, MD, SeenDeps); + addAllAffectingClangModules(M, MD, SeenDeps); - // Add direct prebuilt module dependencies now, so that we can use them when - // creating a CompilerInvocation and computing context hash for this - // ModuleDeps instance. - llvm::DenseSet<const Module *> SeenModules; - addAllSubmodulePrebuiltDeps(M, MD, SeenModules); + MDC.ScanInstance.getASTReader()->visitTopLevelModuleMaps( + *MF, [&](FileEntryRef FE) { + if (FE.getNameAsRequested().endswith("__inferred_module.map")) + return; + MD.ModuleMapFileDeps.emplace_back(FE.getNameAsRequested()); + }); - MD.BuildInvocation = MDC.makeInvocationForModuleBuildWithoutPaths( + CompilerInvocation CI = MDC.makeInvocationForModuleBuildWithoutOutputs( MD, [&](CompilerInvocation &BuildInvocation) { if (MDC.OptimizeArgs) optimizeHeaderSearchOpts(BuildInvocation.getHeaderSearchOpts(), *MDC.ScanInstance.getASTReader(), *MF); }); - MD.HadSerializedDiagnostics = !MDC.OriginalInvocation.getDiagnosticOpts() - .DiagnosticSerializationFile.empty(); - MD.HadDependencyFile = - !MDC.OriginalInvocation.getDependencyOutputOpts().OutputFile.empty(); - // FIXME: HadSerializedDiagnostics and HadDependencyFile should be included in - // the context hash since it can affect the command-line. - MD.ID.ContextHash = MD.BuildInvocation.getModuleHash(); - llvm::DenseSet<const Module *> AddedModules; - addAllSubmoduleDeps(M, MD, AddedModules); + MDC.associateWithContextHash(CI, MD); + + // Finish the compiler invocation. Requires dependencies and the context hash. + MDC.addOutputPaths(CI, MD); + + MD.BuildArguments = CI.getCC1CommandLine(); return MD.ID; } @@ -406,9 +514,33 @@ void ModuleDepCollectorPP::addModuleDep( for (const Module *Import : M->Imports) { if (Import->getTopLevelModule() != M->getTopLevelModule() && !MDC.isPrebuiltModule(Import)) { - ModuleID ImportID = handleTopLevelModule(Import->getTopLevelModule()); - if (AddedModules.insert(Import->getTopLevelModule()).second) - MD.ClangModuleDeps.push_back(ImportID); + if (auto ImportID = handleTopLevelModule(Import->getTopLevelModule())) + if (AddedModules.insert(Import->getTopLevelModule()).second) + MD.ClangModuleDeps.push_back(*ImportID); + } + } +} + +void ModuleDepCollectorPP::addAllAffectingClangModules( + const Module *M, ModuleDeps &MD, + llvm::DenseSet<const Module *> &AddedModules) { + addAffectingClangModule(M, MD, AddedModules); + + for (const Module *SubM : M->submodules()) + addAllAffectingClangModules(SubM, MD, AddedModules); +} + +void ModuleDepCollectorPP::addAffectingClangModule( + const Module *M, ModuleDeps &MD, + llvm::DenseSet<const Module *> &AddedModules) { + for (const Module *Affecting : M->AffectingClangModules) { + assert(Affecting == Affecting->getTopLevelModule() && + "Not quite import not top-level module"); + if (Affecting != M->getTopLevelModule() && + !MDC.isPrebuiltModule(Affecting)) { + if (auto ImportID = handleTopLevelModule(Affecting)) + if (AddedModules.insert(Affecting).second) + MD.ClangModuleDeps.push_back(*ImportID); } } } @@ -416,9 +548,10 @@ void ModuleDepCollectorPP::addModuleDep( ModuleDepCollector::ModuleDepCollector( std::unique_ptr<DependencyOutputOptions> Opts, CompilerInstance &ScanInstance, DependencyConsumer &C, - CompilerInvocation &&OriginalCI, bool OptimizeArgs) + CompilerInvocation OriginalCI, bool OptimizeArgs, bool EagerLoadModules) : ScanInstance(ScanInstance), Consumer(C), Opts(std::move(Opts)), - OriginalInvocation(std::move(OriginalCI)), OptimizeArgs(OptimizeArgs) {} + OriginalInvocation(std::move(OriginalCI)), OptimizeArgs(OptimizeArgs), + EagerLoadModules(EagerLoadModules) {} void ModuleDepCollector::attachToPreprocessor(Preprocessor &PP) { PP.addPPCallbacks(std::make_unique<ModuleDepCollectorPP>(*this)); @@ -437,3 +570,26 @@ bool ModuleDepCollector::isPrebuiltModule(const Module *M) { PrebuiltModuleFileIt->second == M->getASTFile()->getName()); return true; } + +static StringRef makeAbsoluteAndPreferred(CompilerInstance &CI, StringRef Path, + SmallVectorImpl<char> &Storage) { + if (llvm::sys::path::is_absolute(Path) && + !llvm::sys::path::is_style_windows(llvm::sys::path::Style::native)) + return Path; + Storage.assign(Path.begin(), Path.end()); + CI.getFileManager().makeAbsolutePath(Storage); + llvm::sys::path::make_preferred(Storage); + return StringRef(Storage.data(), Storage.size()); +} + +void ModuleDepCollector::addFileDep(StringRef Path) { + llvm::SmallString<256> Storage; + Path = makeAbsoluteAndPreferred(ScanInstance, Path, Storage); + FileDeps.push_back(std::string(Path)); +} + +void ModuleDepCollector::addFileDep(ModuleDeps &MD, StringRef Path) { + llvm::SmallString<256> Storage; + Path = makeAbsoluteAndPreferred(ScanInstance, Path, Storage); + MD.FileDeps.insert(Path); +} diff --git a/clang/lib/Tooling/DumpTool/ASTSrcLocProcessor.cpp b/clang/lib/Tooling/DumpTool/ASTSrcLocProcessor.cpp index 2f97067f6171..42691d556d98 100644 --- a/clang/lib/Tooling/DumpTool/ASTSrcLocProcessor.cpp +++ b/clang/lib/Tooling/DumpTool/ASTSrcLocProcessor.cpp @@ -161,7 +161,7 @@ CaptureMethods(std::string TypeString, const clang::CXXRecordDecl *ASTClass, optionally( isDerivedFrom(cxxRecordDecl(hasName("clang::TypeLoc")) .bind("typeLocBase"))))), - returns(asString(TypeString))) + returns(hasCanonicalType(asString(TypeString)))) .bind("classMethod")), *ASTClass, *Result.Context); diff --git a/clang/lib/Tooling/DumpTool/ASTSrcLocProcessor.h b/clang/lib/Tooling/DumpTool/ASTSrcLocProcessor.h index 05c4f92676e8..5f2b48173f28 100644 --- a/clang/lib/Tooling/DumpTool/ASTSrcLocProcessor.h +++ b/clang/lib/Tooling/DumpTool/ASTSrcLocProcessor.h @@ -35,7 +35,7 @@ public: private: void run(const ast_matchers::MatchFinder::MatchResult &Result) override; - llvm::Optional<TraversalKind> getCheckTraversalKind() const override { + std::optional<TraversalKind> getCheckTraversalKind() const override { return TK_IgnoreUnlessSpelledInSource; } diff --git a/clang/lib/Tooling/DumpTool/ClangSrcLocDump.cpp b/clang/lib/Tooling/DumpTool/ClangSrcLocDump.cpp index 9c825428f2ea..e67ffb037095 100644 --- a/clang/lib/Tooling/DumpTool/ClangSrcLocDump.cpp +++ b/clang/lib/Tooling/DumpTool/ClangSrcLocDump.cpp @@ -116,7 +116,7 @@ int main(int argc, const char **argv) { "ast-api-dump-tool", OFS);
std::unique_ptr<clang::driver::Compilation> Comp(
- Driver->BuildCompilation(llvm::makeArrayRef(Argv)));
+ Driver->BuildCompilation(llvm::ArrayRef(Argv)));
if (!Comp)
return 1;
diff --git a/clang/lib/Tooling/ExpandResponseFilesCompilationDatabase.cpp b/clang/lib/Tooling/ExpandResponseFilesCompilationDatabase.cpp index 75d0d50d851f..88d20ba3957d 100644 --- a/clang/lib/Tooling/ExpandResponseFilesCompilationDatabase.cpp +++ b/clang/lib/Tooling/ExpandResponseFilesCompilationDatabase.cpp @@ -60,9 +60,12 @@ private: if (!SeenRSPFile) continue; llvm::BumpPtrAllocator Alloc; - llvm::StringSaver Saver(Alloc); - llvm::cl::ExpandResponseFiles(Saver, Tokenizer, Argv, false, false, false, - llvm::StringRef(Cmd.Directory), *FS); + llvm::cl::ExpansionContext ECtx(Alloc, Tokenizer); + llvm::Error Err = ECtx.setVFS(FS.get()) + .setCurrentDir(Cmd.Directory) + .expandResponseFiles(Argv); + if (Err) + llvm::errs() << Err; // Don't assign directly, Argv aliases CommandLine. std::vector<std::string> ExpandedArgv(Argv.begin(), Argv.end()); Cmd.CommandLine = std::move(ExpandedArgv); diff --git a/clang/lib/Tooling/Inclusions/HeaderAnalysis.cpp b/clang/lib/Tooling/Inclusions/HeaderAnalysis.cpp new file mode 100644 index 000000000000..49d23908d33b --- /dev/null +++ b/clang/lib/Tooling/Inclusions/HeaderAnalysis.cpp @@ -0,0 +1,119 @@ +//===--- HeaderAnalysis.cpp -------------------------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "clang/Tooling/Inclusions/HeaderAnalysis.h" +#include "clang/Basic/SourceLocation.h" +#include "clang/Lex/HeaderSearch.h" + +namespace clang::tooling { +namespace { + +// Is Line an #if or #ifdef directive? +// FIXME: This makes headers with #ifdef LINUX/WINDOWS/MACOS marked as non +// self-contained and is probably not what we want. +bool isIf(llvm::StringRef Line) { + Line = Line.ltrim(); + if (!Line.consume_front("#")) + return false; + Line = Line.ltrim(); + return Line.startswith("if"); +} + +// Is Line an #error directive mentioning includes? +bool isErrorAboutInclude(llvm::StringRef Line) { + Line = Line.ltrim(); + if (!Line.consume_front("#")) + return false; + Line = Line.ltrim(); + if (!Line.startswith("error")) + return false; + return Line.contains_insensitive( + "includ"); // Matches "include" or "including". +} + +// Heuristically headers that only want to be included via an umbrella. +bool isDontIncludeMeHeader(StringRef Content) { + llvm::StringRef Line; + // Only sniff up to 100 lines or 10KB. + Content = Content.take_front(100 * 100); + for (unsigned I = 0; I < 100 && !Content.empty(); ++I) { + std::tie(Line, Content) = Content.split('\n'); + if (isIf(Line) && isErrorAboutInclude(Content.split('\n').first)) + return true; + } + return false; +} + +bool isImportLine(llvm::StringRef Line) { + Line = Line.ltrim(); + if (!Line.consume_front("#")) + return false; + Line = Line.ltrim(); + return Line.startswith("import"); +} + +llvm::StringRef getFileContents(const FileEntry *FE, const SourceManager &SM) { + return const_cast<SourceManager &>(SM) + .getMemoryBufferForFileOrNone(FE) + .value_or(llvm::MemoryBufferRef()) + .getBuffer(); +} + +} // namespace + +bool isSelfContainedHeader(const FileEntry *FE, const SourceManager &SM, + HeaderSearch &HeaderInfo) { + assert(FE); + if (!HeaderInfo.isFileMultipleIncludeGuarded(FE) && + !HeaderInfo.hasFileBeenImported(FE) && + // Any header that contains #imports is supposed to be #import'd so no + // need to check for anything but the main-file. + (SM.getFileEntryForID(SM.getMainFileID()) != FE || + !codeContainsImports(getFileContents(FE, SM)))) + return false; + // This pattern indicates that a header can't be used without + // particular preprocessor state, usually set up by another header. + return !isDontIncludeMeHeader(getFileContents(FE, SM)); +} + +bool codeContainsImports(llvm::StringRef Code) { + // Only sniff up to 100 lines or 10KB. + Code = Code.take_front(100 * 100); + llvm::StringRef Line; + for (unsigned I = 0; I < 100 && !Code.empty(); ++I) { + std::tie(Line, Code) = Code.split('\n'); + if (isImportLine(Line)) + return true; + } + return false; +} + +std::optional<StringRef> parseIWYUPragma(const char *Text) { + // Skip the comment start, // or /*. + if (Text[0] != '/' || (Text[1] != '/' && Text[1] != '*')) + return std::nullopt; + bool BlockComment = Text[1] == '*'; + Text += 2; + + // Per spec, direcitves are whitespace- and case-sensitive. + constexpr llvm::StringLiteral IWYUPragma = " IWYU pragma: "; + if (strncmp(Text, IWYUPragma.data(), IWYUPragma.size())) + return std::nullopt; + Text += IWYUPragma.size(); + const char *End = Text; + while (*End != 0 && *End != '\n') + ++End; + StringRef Rest(Text, End - Text); + // Strip off whitespace and comment markers to avoid confusion. This isn't + // fully-compatible with IWYU, which splits into whitespace-delimited tokens. + if (BlockComment) + Rest.consume_back("*/"); + return Rest.trim(); +} + +} // namespace clang::tooling diff --git a/clang/lib/Tooling/Inclusions/HeaderIncludes.cpp b/clang/lib/Tooling/Inclusions/HeaderIncludes.cpp index fc8773e60c58..172eff1bf6ab 100644 --- a/clang/lib/Tooling/Inclusions/HeaderIncludes.cpp +++ b/clang/lib/Tooling/Inclusions/HeaderIncludes.cpp @@ -10,9 +10,9 @@ #include "clang/Basic/FileManager.h" #include "clang/Basic/SourceManager.h" #include "clang/Lex/Lexer.h" -#include "llvm/ADT/Optional.h" #include "llvm/Support/FormatVariadic.h" #include "llvm/Support/Path.h" +#include <optional> namespace clang { namespace tooling { @@ -58,7 +58,7 @@ unsigned getOffsetAfterTokenSequence( // (second) raw_identifier name is checked. bool checkAndConsumeDirectiveWithName( Lexer &Lex, StringRef Name, Token &Tok, - llvm::Optional<StringRef> RawIDName = llvm::None) { + std::optional<StringRef> RawIDName = std::nullopt) { bool Matched = Tok.is(tok::hash) && !Lex.LexFromRawLexer(Tok) && Tok.is(tok::raw_identifier) && Tok.getRawIdentifier() == Name && !Lex.LexFromRawLexer(Tok) && @@ -266,6 +266,8 @@ bool IncludeCategoryManager::isMainHeader(StringRef IncludeName) const { return false; } +const llvm::Regex HeaderIncludes::IncludeRegex(IncludeRegexPattern); + HeaderIncludes::HeaderIncludes(StringRef FileName, StringRef Code, const IncludeStyle &Style) : FileName(FileName), Code(Code), FirstIncludeOffset(-1), @@ -274,8 +276,7 @@ HeaderIncludes::HeaderIncludes(StringRef FileName, StringRef Code, MaxInsertOffset(MinInsertOffset + getMaxHeaderInsertionOffset( FileName, Code.drop_front(MinInsertOffset), Style)), - Categories(Style, FileName), - IncludeRegex(llvm::Regex(IncludeRegexPattern)) { + Categories(Style, FileName) { // Add 0 for main header and INT_MAX for headers that are not in any // category. Priorities = {0, INT_MAX}; @@ -295,7 +296,9 @@ HeaderIncludes::HeaderIncludes(StringRef FileName, StringRef Code, addExistingInclude( Include(Matches[2], tooling::Range( - Offset, std::min(Line.size() + 1, Code.size() - Offset))), + Offset, std::min(Line.size() + 1, Code.size() - Offset)), + Matches[1] == "import" ? tooling::IncludeDirective::Import + : tooling::IncludeDirective::Include), NextLineOffset); } Offset = NextLineOffset; @@ -340,18 +343,21 @@ void HeaderIncludes::addExistingInclude(Include IncludeToAdd, } } -llvm::Optional<tooling::Replacement> -HeaderIncludes::insert(llvm::StringRef IncludeName, bool IsAngled) const { +std::optional<tooling::Replacement> +HeaderIncludes::insert(llvm::StringRef IncludeName, bool IsAngled, + IncludeDirective Directive) const { assert(IncludeName == trimInclude(IncludeName)); // If a <header> ("header") already exists in code, "header" (<header>) with - // different quotation will still be inserted. + // different quotation and/or directive will still be inserted. // FIXME: figure out if this is the best behavior. auto It = ExistingIncludes.find(IncludeName); - if (It != ExistingIncludes.end()) + if (It != ExistingIncludes.end()) { for (const auto &Inc : It->second) - if ((IsAngled && StringRef(Inc.Name).startswith("<")) || - (!IsAngled && StringRef(Inc.Name).startswith("\""))) - return llvm::None; + if (Inc.Directive == Directive && + ((IsAngled && StringRef(Inc.Name).startswith("<")) || + (!IsAngled && StringRef(Inc.Name).startswith("\"")))) + return std::nullopt; + } std::string Quoted = std::string(llvm::formatv(IsAngled ? "<{0}>" : "\"{0}\"", IncludeName)); StringRef QuotedName = Quoted; @@ -370,8 +376,10 @@ HeaderIncludes::insert(llvm::StringRef IncludeName, bool IsAngled) const { } } assert(InsertOffset <= Code.size()); + llvm::StringRef DirectiveSpelling = + Directive == IncludeDirective::Include ? "include" : "import"; std::string NewInclude = - std::string(llvm::formatv("#include {0}\n", QuotedName)); + llvm::formatv("#{0} {1}\n", DirectiveSpelling, QuotedName); // When inserting headers at end of the code, also append '\n' to the code // if it does not end with '\n'. // FIXME: when inserting multiple #includes at the end of code, only one diff --git a/clang/lib/Tooling/Inclusions/StandardLibrary.cpp b/clang/lib/Tooling/Inclusions/Stdlib/StandardLibrary.cpp index 8fb0c8474e64..9e5e421fdebc 100644 --- a/clang/lib/Tooling/Inclusions/StandardLibrary.cpp +++ b/clang/lib/Tooling/Inclusions/Stdlib/StandardLibrary.cpp @@ -7,7 +7,7 @@ //===----------------------------------------------------------------------===// #include "clang/Tooling/Inclusions/StandardLibrary.h" -#include "llvm/ADT/Optional.h" +#include "clang/AST/Decl.h" #include "llvm/ADT/StringRef.h" #include "llvm/Support/Casting.h" @@ -76,17 +76,17 @@ static void ensureInitialized() { (void)Dummy; } -llvm::Optional<Header> Header::named(llvm::StringRef Name) { +std::optional<Header> Header::named(llvm::StringRef Name) { ensureInitialized(); auto It = HeaderIDs->find(Name); if (It == HeaderIDs->end()) - return llvm::None; + return std::nullopt; return Header(It->second); } llvm::StringRef Header::name() const { return HeaderNames[ID]; } llvm::StringRef Symbol::scope() const { return SymbolNames[ID].first; } llvm::StringRef Symbol::name() const { return SymbolNames[ID].second; } -llvm::Optional<Symbol> Symbol::named(llvm::StringRef Scope, +std::optional<Symbol> Symbol::named(llvm::StringRef Scope, llvm::StringRef Name) { ensureInitialized(); if (NSSymbolMap *NSSymbols = NamespaceSymbols->lookup(Scope)) { @@ -94,7 +94,7 @@ llvm::Optional<Symbol> Symbol::named(llvm::StringRef Scope, if (It != NSSymbols->end()) return Symbol(It->second); } - return llvm::None; + return std::nullopt; } Header Symbol::header() const { return Header(SymbolHeaderIDs[ID]); } llvm::SmallVector<Header> Symbol::headers() const { @@ -123,7 +123,7 @@ NSSymbolMap *Recognizer::namespaceSymbols(const NamespaceDecl *D) { return Result; } -llvm::Optional<Symbol> Recognizer::operator()(const Decl *D) { +std::optional<Symbol> Recognizer::operator()(const Decl *D) { // If D is std::vector::iterator, `vector` is the outer symbol to look up. // We keep all the candidate DCs as some may turn out to be anon enums. // Do this resolution lazily as we may turn out not to have a std namespace. @@ -136,7 +136,7 @@ llvm::Optional<Symbol> Recognizer::operator()(const Decl *D) { } NSSymbolMap *Symbols = namespaceSymbols(cast_or_null<NamespaceDecl>(DC)); if (!Symbols) - return llvm::None; + return std::nullopt; llvm::StringRef Name = [&]() -> llvm::StringRef { for (const auto *SymDC : llvm::reverse(IntermediateDecl)) { @@ -152,11 +152,11 @@ llvm::Optional<Symbol> Recognizer::operator()(const Decl *D) { return ""; }(); if (Name.empty()) - return llvm::None; + return std::nullopt; auto It = Symbols->find(Name); if (It == Symbols->end()) - return llvm::None; + return std::nullopt; return Symbol(It->second); } diff --git a/clang/lib/Tooling/InterpolatingCompilationDatabase.cpp b/clang/lib/Tooling/InterpolatingCompilationDatabase.cpp index 0143b5f8df6b..ff9cb4b62073 100644 --- a/clang/lib/Tooling/InterpolatingCompilationDatabase.cpp +++ b/clang/lib/Tooling/InterpolatingCompilationDatabase.cpp @@ -49,9 +49,7 @@ #include "clang/Tooling/CompilationDatabase.h" #include "llvm/ADT/ArrayRef.h" #include "llvm/ADT/DenseMap.h" -#include "llvm/ADT/Optional.h" #include "llvm/ADT/StringExtras.h" -#include "llvm/ADT/StringSwitch.h" #include "llvm/Option/ArgList.h" #include "llvm/Option/OptTable.h" #include "llvm/Support/Debug.h" @@ -59,6 +57,7 @@ #include "llvm/Support/StringSaver.h" #include "llvm/Support/raw_ostream.h" #include <memory> +#include <optional> namespace clang { namespace tooling { @@ -129,7 +128,7 @@ struct TransferableCommand { // Flags that should not apply to all files are stripped from CommandLine. CompileCommand Cmd; // Language detected from -x or the filename. Never TY_INVALID. - Optional<types::ID> Type; + std::optional<types::ID> Type; // Standard specified by -std. LangStandard::Kind Std = LangStandard::lang_unspecified; // Whether the command line is for the cl-compatible driver. @@ -148,7 +147,7 @@ struct TransferableCommand { TmpArgv.push_back(S.c_str()); ClangCLMode = !TmpArgv.empty() && driver::IsClangCL(driver::getDriverMode( - TmpArgv.front(), llvm::makeArrayRef(TmpArgv).slice(1))); + TmpArgv.front(), llvm::ArrayRef(TmpArgv).slice(1))); ArgList = {TmpArgv.begin(), TmpArgv.end()}; } @@ -165,8 +164,8 @@ struct TransferableCommand { const unsigned OldPos = Pos; std::unique_ptr<llvm::opt::Arg> Arg(OptTable.ParseOneArg( ArgList, Pos, - /* Include */ ClangCLMode ? CoreOption | CLOption : 0, - /* Exclude */ ClangCLMode ? 0 : CLOption)); + /* Include */ ClangCLMode ? CoreOption | CLOption | CLDXCOption : 0, + /* Exclude */ ClangCLMode ? 0 : CLOption | CLDXCOption)); if (!Arg) continue; @@ -208,7 +207,7 @@ struct TransferableCommand { Type = foldType(*Type); // The contract is to store None instead of TY_INVALID. if (Type == types::TY_INVALID) - Type = llvm::None; + Type = std::nullopt; } // Produce a CompileCommand for \p filename, based on this one. @@ -280,7 +279,7 @@ private: } // Try to interpret the argument as a type specifier, e.g. '-x'. - Optional<types::ID> tryParseTypeArg(const llvm::opt::Arg &Arg) { + std::optional<types::ID> tryParseTypeArg(const llvm::opt::Arg &Arg) { const llvm::opt::Option &Opt = Arg.getOption(); using namespace driver::options; if (ClangCLMode) { @@ -292,15 +291,15 @@ private: if (Opt.matches(driver::options::OPT_x)) return types::lookupTypeForTypeSpecifier(Arg.getValue()); } - return None; + return std::nullopt; } // Try to interpret the argument as '-std='. - Optional<LangStandard::Kind> tryParseStdArg(const llvm::opt::Arg &Arg) { + std::optional<LangStandard::Kind> tryParseStdArg(const llvm::opt::Arg &Arg) { using namespace driver::options; if (Arg.getOption().matches(ClangCLMode ? OPT__SLASH_std : OPT_std_EQ)) return LangStandard::getLangKind(Arg.getValue()); - return None; + return std::nullopt; } }; diff --git a/clang/lib/Tooling/JSONCompilationDatabase.cpp b/clang/lib/Tooling/JSONCompilationDatabase.cpp index 5e18d7a576c0..cdda587d0925 100644 --- a/clang/lib/Tooling/JSONCompilationDatabase.cpp +++ b/clang/lib/Tooling/JSONCompilationDatabase.cpp @@ -15,7 +15,6 @@ #include "clang/Tooling/CompilationDatabase.h" #include "clang/Tooling/CompilationDatabasePluginRegistry.h" #include "clang/Tooling/Tooling.h" -#include "llvm/ADT/Optional.h" #include "llvm/ADT/STLExtras.h" #include "llvm/ADT/SmallString.h" #include "llvm/ADT/SmallVector.h" @@ -34,6 +33,7 @@ #include "llvm/Support/raw_ostream.h" #include <cassert> #include <memory> +#include <optional> #include <string> #include <system_error> #include <tuple> @@ -349,7 +349,7 @@ bool JSONCompilationDatabase::parse(std::string &ErrorMessage) { return false; } llvm::yaml::ScalarNode *Directory = nullptr; - llvm::Optional<std::vector<llvm::yaml::ScalarNode *>> Command; + std::optional<std::vector<llvm::yaml::ScalarNode *>> Command; llvm::yaml::ScalarNode *File = nullptr; llvm::yaml::ScalarNode *Output = nullptr; for (auto& NextKeyValue : *Object) { @@ -419,14 +419,13 @@ bool JSONCompilationDatabase::parse(std::string &ErrorMessage) { SmallString<128> NativeFilePath; if (llvm::sys::path::is_relative(FileName)) { SmallString<8> DirectoryStorage; - SmallString<128> AbsolutePath( - Directory->getValue(DirectoryStorage)); + SmallString<128> AbsolutePath(Directory->getValue(DirectoryStorage)); llvm::sys::path::append(AbsolutePath, FileName); - llvm::sys::path::remove_dots(AbsolutePath, /*remove_dot_dot=*/ true); 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); IndexByFile[NativeFilePath].push_back(Cmd); AllCommands.push_back(Cmd); diff --git a/clang/lib/Tooling/Refactoring/ASTSelection.cpp b/clang/lib/Tooling/Refactoring/ASTSelection.cpp index 9485c8bc04ad..058574d8ec1a 100644 --- a/clang/lib/Tooling/Refactoring/ASTSelection.cpp +++ b/clang/lib/Tooling/Refactoring/ASTSelection.cpp @@ -10,6 +10,7 @@ #include "clang/AST/LexicallyOrderedRecursiveASTVisitor.h" #include "clang/Lex/Lexer.h" #include "llvm/Support/SaveAndRestore.h" +#include <optional> using namespace clang; using namespace tooling; @@ -50,12 +51,12 @@ public: SourceSelectionKind::None)); } - Optional<SelectedASTNode> getSelectedASTNode() { + std::optional<SelectedASTNode> getSelectedASTNode() { assert(SelectionStack.size() == 1 && "stack was not popped"); SelectedASTNode Result = std::move(SelectionStack.back()); SelectionStack.pop_back(); if (Result.Children.empty()) - return None; + return std::nullopt; return std::move(Result); } @@ -63,14 +64,14 @@ public: // Avoid traversing the semantic expressions. They should be handled by // looking through the appropriate opaque expressions in order to build // a meaningful selection tree. - llvm::SaveAndRestore<bool> LookThrough(LookThroughOpaqueValueExprs, true); + llvm::SaveAndRestore LookThrough(LookThroughOpaqueValueExprs, true); return TraverseStmt(E->getSyntacticForm()); } bool TraverseOpaqueValueExpr(OpaqueValueExpr *E) { if (!LookThroughOpaqueValueExprs) return true; - llvm::SaveAndRestore<bool> LookThrough(LookThroughOpaqueValueExprs, false); + llvm::SaveAndRestore LookThrough(LookThroughOpaqueValueExprs, false); return TraverseStmt(E->getSourceExpr()); } @@ -178,7 +179,7 @@ private: } // end anonymous namespace -Optional<SelectedASTNode> +std::optional<SelectedASTNode> clang::tooling::findSelectedASTNodes(const ASTContext &Context, SourceRange SelectionRange) { assert(SelectionRange.isValid() && @@ -375,22 +376,22 @@ static void findDeepestWithKind( findDeepestWithKind(ASTSelection, MatchingNodes, Kind, ParentStack); } -Optional<CodeRangeASTSelection> +std::optional<CodeRangeASTSelection> CodeRangeASTSelection::create(SourceRange SelectionRange, const SelectedASTNode &ASTSelection) { // Code range is selected when the selection range is not empty. if (SelectionRange.getBegin() == SelectionRange.getEnd()) - return None; + return std::nullopt; llvm::SmallVector<SelectedNodeWithParents, 4> ContainSelection; findDeepestWithKind(ASTSelection, ContainSelection, SourceSelectionKind::ContainsSelection); // We are looking for a selection in one body of code, so let's focus on // one matching result. if (ContainSelection.size() != 1) - return None; + return std::nullopt; SelectedNodeWithParents &Selected = ContainSelection[0]; if (!Selected.Node.get().Node.get<Stmt>()) - return None; + return std::nullopt; const Stmt *CodeRangeStmt = Selected.Node.get().Node.get<Stmt>(); if (!isa<CompoundStmt>(CodeRangeStmt)) { Selected.canonicalize(); diff --git a/clang/lib/Tooling/Refactoring/ASTSelectionRequirements.cpp b/clang/lib/Tooling/Refactoring/ASTSelectionRequirements.cpp index 70a4df07ea67..0e052bb19768 100644 --- a/clang/lib/Tooling/Refactoring/ASTSelectionRequirements.cpp +++ b/clang/lib/Tooling/Refactoring/ASTSelectionRequirements.cpp @@ -8,6 +8,7 @@ #include "clang/Tooling/Refactoring/RefactoringActionRuleRequirements.h" #include "clang/AST/Attr.h" +#include <optional> using namespace clang; using namespace tooling; @@ -20,7 +21,7 @@ ASTSelectionRequirement::evaluate(RefactoringRuleContext &Context) const { if (!Range) return Range.takeError(); - Optional<SelectedASTNode> Selection = + std::optional<SelectedASTNode> Selection = findSelectedASTNodes(Context.getASTContext(), *Range); if (!Selection) return Context.createDiagnosticError( @@ -37,8 +38,9 @@ Expected<CodeRangeASTSelection> CodeRangeASTSelectionRequirement::evaluate( return ASTSelection.takeError(); std::unique_ptr<SelectedASTNode> StoredSelection = std::make_unique<SelectedASTNode>(std::move(*ASTSelection)); - Optional<CodeRangeASTSelection> CodeRange = CodeRangeASTSelection::create( - Context.getSelectionRange(), *StoredSelection); + std::optional<CodeRangeASTSelection> CodeRange = + CodeRangeASTSelection::create(Context.getSelectionRange(), + *StoredSelection); if (!CodeRange) return Context.createDiagnosticError( Context.getSelectionRange().getBegin(), diff --git a/clang/lib/Tooling/Refactoring/Extract/Extract.cpp b/clang/lib/Tooling/Refactoring/Extract/Extract.cpp index 402b56109052..d437f4c21f47 100644 --- a/clang/lib/Tooling/Refactoring/Extract/Extract.cpp +++ b/clang/lib/Tooling/Refactoring/Extract/Extract.cpp @@ -19,6 +19,7 @@ #include "clang/AST/ExprObjC.h" #include "clang/Rewrite/Core/Rewriter.h" #include "clang/Tooling/Refactoring/Extract/SourceExtraction.h" +#include <optional> namespace clang { namespace tooling { @@ -68,7 +69,7 @@ const RefactoringDescriptor &ExtractFunction::describe() { Expected<ExtractFunction> ExtractFunction::initiate(RefactoringRuleContext &Context, CodeRangeASTSelection Code, - Optional<std::string> DeclName) { + std::optional<std::string> DeclName) { // We would like to extract code out of functions/methods/blocks. // Prohibit extraction from things like global variable / field // initializers and other top-level expressions. diff --git a/clang/lib/Tooling/Refactoring/Extract/SourceExtraction.cpp b/clang/lib/Tooling/Refactoring/Extract/SourceExtraction.cpp index 5d57ecf90a96..5e69fb805150 100644 --- a/clang/lib/Tooling/Refactoring/Extract/SourceExtraction.cpp +++ b/clang/lib/Tooling/Refactoring/Extract/SourceExtraction.cpp @@ -12,6 +12,7 @@ #include "clang/AST/StmtObjC.h" #include "clang/Basic/SourceManager.h" #include "clang/Lex/Lexer.h" +#include <optional> using namespace clang; @@ -100,7 +101,7 @@ ExtractionSemicolonPolicy::compute(const Stmt *S, SourceRange &ExtractedRange, /// Other statements should generally have a trailing ';'. We can try to find /// it and move it together it with the extracted code. - Optional<Token> NextToken = Lexer::findNextToken(End, SM, LangOpts); + std::optional<Token> NextToken = Lexer::findNextToken(End, SM, LangOpts); if (NextToken && NextToken->is(tok::semi) && areOnSameLine(NextToken->getLocation(), End, SM)) { ExtractedRange.setEnd(NextToken->getLocation()); diff --git a/clang/lib/Tooling/Syntax/BuildTree.cpp b/clang/lib/Tooling/Syntax/BuildTree.cpp index 041cc4f939d9..cd0261989495 100644 --- a/clang/lib/Tooling/Syntax/BuildTree.cpp +++ b/clang/lib/Tooling/Syntax/BuildTree.cpp @@ -469,7 +469,7 @@ public: assert(Last.isValid()); assert(First == Last || TBTM.sourceManager().isBeforeInTranslationUnit(First, Last)); - return llvm::makeArrayRef(findToken(First), std::next(findToken(Last))); + return llvm::ArrayRef(findToken(First), std::next(findToken(Last))); } ArrayRef<syntax::Token> @@ -552,7 +552,7 @@ private: assert(Tokens.back().kind() != tok::eof); // We never consume 'eof', so looking at the next token is ok. if (Tokens.back().kind() != tok::semi && Tokens.end()->kind() == tok::semi) - return llvm::makeArrayRef(Tokens.begin(), Tokens.end() + 1); + return llvm::ArrayRef(Tokens.begin(), Tokens.end() + 1); return Tokens; } @@ -768,7 +768,7 @@ public: // Build TemplateDeclaration nodes if we had template parameters. auto ConsumeTemplateParameters = [&](const TemplateParameterList &L) { const auto *TemplateKW = Builder.findToken(L.getTemplateLoc()); - auto R = llvm::makeArrayRef(TemplateKW, DeclarationRange.end()); + auto R = llvm::ArrayRef(TemplateKW, DeclarationRange.end()); Result = foldTemplateDeclaration(R, TemplateKW, DeclarationRange, nullptr); DeclarationRange = R; @@ -1639,7 +1639,7 @@ private: auto Return = Builder.getRange(ReturnedType.getSourceRange()); const auto *Arrow = Return.begin() - 1; assert(Arrow->kind() == tok::arrow); - auto Tokens = llvm::makeArrayRef(Arrow, Return.end()); + auto Tokens = llvm::ArrayRef(Arrow, Return.end()); Builder.markChildToken(Arrow, syntax::NodeRole::ArrowToken); if (ReturnDeclarator) Builder.markChild(ReturnDeclarator, syntax::NodeRole::Declarator); diff --git a/clang/lib/Tooling/Syntax/ComputeReplacements.cpp b/clang/lib/Tooling/Syntax/ComputeReplacements.cpp index 08e09e4ebdbf..fe9a9df73cb3 100644 --- a/clang/lib/Tooling/Syntax/ComputeReplacements.cpp +++ b/clang/lib/Tooling/Syntax/ComputeReplacements.cpp @@ -31,7 +31,7 @@ void enumerateTokenSpans(const syntax::Tree *Root, process(Root); // Report the last span to the user. if (SpanBegin) - Callback(llvm::makeArrayRef(SpanBegin, SpanEnd), SpanIsOriginal); + Callback(llvm::ArrayRef(SpanBegin, SpanEnd), SpanIsOriginal); } private: @@ -52,7 +52,7 @@ void enumerateTokenSpans(const syntax::Tree *Root, } // Report the current span to the user. if (SpanBegin) - Callback(llvm::makeArrayRef(SpanBegin, SpanEnd), SpanIsOriginal); + Callback(llvm::ArrayRef(SpanBegin, SpanEnd), SpanIsOriginal); // Start recording a new span. SpanBegin = STM.getToken(L->getTokenKey()); SpanEnd = SpanBegin + 1; @@ -118,17 +118,17 @@ syntax::computeReplacements(const TokenBufferTokenManager &TBTM, // We are looking at a span of original tokens. if (NextOriginal != Tokens.begin()) { // There is a gap, record a replacement or deletion. - emitReplacement(llvm::makeArrayRef(NextOriginal, Tokens.begin())); + emitReplacement(llvm::ArrayRef(NextOriginal, Tokens.begin())); } else { // No gap, but we may have pending insertions. Emit them now. - emitReplacement(llvm::makeArrayRef(NextOriginal, /*Length=*/0)); + emitReplacement(llvm::ArrayRef(NextOriginal, /*Length=*/(size_t)0)); } NextOriginal = Tokens.end(); }); // We might have pending replacements at the end of file. If so, emit them. - emitReplacement(llvm::makeArrayRef( - NextOriginal, Buffer.expandedTokens().drop_back().end())); + emitReplacement( + llvm::ArrayRef(NextOriginal, Buffer.expandedTokens().drop_back().end())); return Replacements; } diff --git a/clang/lib/Tooling/Syntax/Mutations.cpp b/clang/lib/Tooling/Syntax/Mutations.cpp index 824f1942532d..0f04acea3d04 100644 --- a/clang/lib/Tooling/Syntax/Mutations.cpp +++ b/clang/lib/Tooling/Syntax/Mutations.cpp @@ -15,7 +15,6 @@ #include "clang/Tooling/Syntax/Tokens.h" #include "clang/Tooling/Syntax/Tree.h" #include "llvm/ADT/ArrayRef.h" -#include "llvm/ADT/Optional.h" #include "llvm/ADT/STLExtras.h" #include "llvm/Support/Casting.h" #include <cassert> diff --git a/clang/lib/Tooling/Syntax/Tokens.cpp b/clang/lib/Tooling/Syntax/Tokens.cpp index e2014f965c90..b13dc9ef4aee 100644 --- a/clang/lib/Tooling/Syntax/Tokens.cpp +++ b/clang/lib/Tooling/Syntax/Tokens.cpp @@ -18,8 +18,6 @@ #include "clang/Lex/Preprocessor.h" #include "clang/Lex/Token.h" #include "llvm/ADT/ArrayRef.h" -#include "llvm/ADT/None.h" -#include "llvm/ADT/Optional.h" #include "llvm/ADT/STLExtras.h" #include "llvm/Support/Debug.h" #include "llvm/Support/ErrorHandling.h" @@ -28,6 +26,7 @@ #include <algorithm> #include <cassert> #include <iterator> +#include <optional> #include <string> #include <utility> #include <vector> @@ -55,45 +54,140 @@ getTokensCovering(llvm::ArrayRef<syntax::Token> Toks, SourceRange R, return {Begin, End}; } -// Finds the smallest expansion range that contains expanded tokens First and -// Last, e.g.: +// Finds the range within FID corresponding to expanded tokens [First, Last]. +// Prev precedes First and Next follows Last, these must *not* be included. +// If no range satisfies the criteria, returns an invalid range. +// // #define ID(x) x // ID(ID(ID(a1) a2)) // ~~ -> a1 // ~~ -> a2 // ~~~~~~~~~ -> a1 a2 -SourceRange findCommonRangeForMacroArgs(const syntax::Token &First, - const syntax::Token &Last, - const SourceManager &SM) { - SourceRange Res; - auto FirstLoc = First.location(), LastLoc = Last.location(); - // Keep traversing up the spelling chain as longs as tokens are part of the - // same expansion. - while (!FirstLoc.isFileID() && !LastLoc.isFileID()) { - auto ExpInfoFirst = SM.getSLocEntry(SM.getFileID(FirstLoc)).getExpansion(); - auto ExpInfoLast = SM.getSLocEntry(SM.getFileID(LastLoc)).getExpansion(); - // Stop if expansions have diverged. - if (ExpInfoFirst.getExpansionLocStart() != - ExpInfoLast.getExpansionLocStart()) +SourceRange spelledForExpandedSlow(SourceLocation First, SourceLocation Last, + SourceLocation Prev, SourceLocation Next, + FileID TargetFile, + const SourceManager &SM) { + // There are two main parts to this algorithm: + // - identifying which spelled range covers the expanded tokens + // - validating that this range doesn't cover any extra tokens (First/Last) + // + // We do these in order. However as we transform the expanded range into the + // spelled one, we adjust First/Last so the validation remains simple. + + assert(SM.getSLocEntry(TargetFile).isFile()); + // In most cases, to select First and Last we must return their expansion + // range, i.e. the whole of any macros they are included in. + // + // When First and Last are part of the *same macro arg* of a macro written + // in TargetFile, we that slice of the arg, i.e. their spelling range. + // + // Unwrap such macro calls. If the target file has A(B(C)), the + // SourceLocation stack of a token inside C shows us the expansion of A first, + // then B, then any macros inside C's body, then C itself. + // (This is the reverse of the order the PP applies the expansions in). + while (First.isMacroID() && Last.isMacroID()) { + auto DecFirst = SM.getDecomposedLoc(First); + auto DecLast = SM.getDecomposedLoc(Last); + auto &ExpFirst = SM.getSLocEntry(DecFirst.first).getExpansion(); + auto &ExpLast = SM.getSLocEntry(DecLast.first).getExpansion(); + + if (!ExpFirst.isMacroArgExpansion() || !ExpLast.isMacroArgExpansion()) + break; + // Locations are in the same macro arg if they expand to the same place. + // (They may still have different FileIDs - an arg can have >1 chunks!) + if (ExpFirst.getExpansionLocStart() != ExpLast.getExpansionLocStart()) break; - // Do not continue into macro bodies. - if (!ExpInfoFirst.isMacroArgExpansion() || - !ExpInfoLast.isMacroArgExpansion()) + // Careful, given: + // #define HIDE ID(ID(a)) + // ID(ID(HIDE)) + // The token `a` is wrapped in 4 arg-expansions, we only want to unwrap 2. + // We distinguish them by whether the macro expands into the target file. + // Fortunately, the target file ones will always appear first. + auto &ExpMacro = + SM.getSLocEntry(SM.getFileID(ExpFirst.getExpansionLocStart())) + .getExpansion(); + if (ExpMacro.getExpansionLocStart().isMacroID()) break; - FirstLoc = SM.getImmediateSpellingLoc(FirstLoc); - LastLoc = SM.getImmediateSpellingLoc(LastLoc); - // Update the result afterwards, as we want the tokens that triggered the - // expansion. - Res = {FirstLoc, LastLoc}; + // Replace each endpoint with its spelling inside the macro arg. + // (This is getImmediateSpellingLoc without repeating lookups). + First = ExpFirst.getSpellingLoc().getLocWithOffset(DecFirst.second); + Last = ExpLast.getSpellingLoc().getLocWithOffset(DecLast.second); + + // Now: how do we adjust the previous/next bounds? Three cases: + // A) If they are also part of the same macro arg, we translate them too. + // This will ensure that we don't select any macros nested within the + // macro arg that cover extra tokens. Critical case: + // #define ID(X) X + // ID(prev target) // selecting 'target' succeeds + // #define LARGE ID(prev target) + // LARGE // selecting 'target' fails. + // B) They are not in the macro at all, then their expansion range is a + // sibling to it, and we can safely substitute that. + // #define PREV prev + // #define ID(X) X + // PREV ID(target) // selecting 'target' succeeds. + // #define LARGE PREV ID(target) + // LARGE // selecting 'target' fails. + // C) They are in a different arg of this macro, or the macro body. + // Now selecting the whole macro arg is fine, but the whole macro is not. + // Model this by setting using the edge of the macro call as the bound. + // #define ID2(X, Y) X Y + // ID2(prev, target) // selecting 'target' succeeds + // #define LARGE ID2(prev, target) + // LARGE // selecting 'target' fails + auto AdjustBound = [&](SourceLocation &Bound) { + if (Bound.isInvalid() || !Bound.isMacroID()) // Non-macro must be case B. + return; + auto DecBound = SM.getDecomposedLoc(Bound); + auto &ExpBound = SM.getSLocEntry(DecBound.first).getExpansion(); + if (ExpBound.isMacroArgExpansion() && + ExpBound.getExpansionLocStart() == ExpFirst.getExpansionLocStart()) { + // Case A: translate to (spelling) loc within the macro arg. + Bound = ExpBound.getSpellingLoc().getLocWithOffset(DecBound.second); + return; + } + while (Bound.isMacroID()) { + SourceRange Exp = SM.getImmediateExpansionRange(Bound).getAsRange(); + if (Exp.getBegin() == ExpMacro.getExpansionLocStart()) { + // Case B: bounds become the macro call itself. + Bound = (&Bound == &Prev) ? Exp.getBegin() : Exp.getEnd(); + return; + } + // Either case C, or expansion location will later find case B. + // We choose the upper bound for Prev and the lower one for Next: + // ID(prev) target ID(next) + // ^ ^ + // new-prev new-next + Bound = (&Bound == &Prev) ? Exp.getEnd() : Exp.getBegin(); + } + }; + AdjustBound(Prev); + AdjustBound(Next); } - // Normally mapping back to expansion location here only changes FileID, as - // we've already found some tokens expanded from the same macro argument, and - // they should map to a consecutive subset of spelled tokens. Unfortunately - // SourceManager::isBeforeInTranslationUnit discriminates sourcelocations - // based on their FileID in addition to offsets. So even though we are - // referring to same tokens, SourceManager might tell us that one is before - // the other if they've got different FileIDs. - return SM.getExpansionRange(CharSourceRange(Res, true)).getAsRange(); + + // In all remaining cases we need the full containing macros. + // If this overlaps Prev or Next, then no range is possible. + SourceRange Candidate = + SM.getExpansionRange(SourceRange(First, Last)).getAsRange(); + auto DecFirst = SM.getDecomposedExpansionLoc(Candidate.getBegin()); + auto DecLast = SM.getDecomposedLoc(Candidate.getEnd()); + // Can end up in the wrong file due to bad input or token-pasting shenanigans. + if (Candidate.isInvalid() || DecFirst.first != TargetFile || DecLast.first != TargetFile) + return SourceRange(); + // Check bounds, which may still be inside macros. + if (Prev.isValid()) { + auto Dec = SM.getDecomposedLoc(SM.getExpansionRange(Prev).getBegin()); + if (Dec.first != DecFirst.first || Dec.second >= DecFirst.second) + return SourceRange(); + } + if (Next.isValid()) { + auto Dec = SM.getDecomposedLoc(SM.getExpansionRange(Next).getEnd()); + if (Dec.first != DecLast.first || Dec.second <= DecLast.second) + return SourceRange(); + } + // Now we know that Candidate is a file range that covers [First, Last] + // without encroaching on {Prev, Next}. Ship it! + return Candidate; } } // namespace @@ -331,8 +425,8 @@ TokenBuffer::expandedForSpelled(llvm::ArrayRef<syntax::Token> Spelled) const { // Avoid returning empty ranges. if (ExpandedBegin == ExpandedEnd) return {}; - return {llvm::makeArrayRef(ExpandedTokens.data() + ExpandedBegin, - ExpandedTokens.data() + ExpandedEnd)}; + return {llvm::ArrayRef(ExpandedTokens.data() + ExpandedBegin, + ExpandedTokens.data() + ExpandedEnd)}; } llvm::ArrayRef<syntax::Token> TokenBuffer::spelledTokens(FileID FID) const { @@ -357,57 +451,54 @@ std::string TokenBuffer::Mapping::str() const { BeginSpelled, EndSpelled, BeginExpanded, EndExpanded)); } -llvm::Optional<llvm::ArrayRef<syntax::Token>> +std::optional<llvm::ArrayRef<syntax::Token>> TokenBuffer::spelledForExpanded(llvm::ArrayRef<syntax::Token> Expanded) const { // Mapping an empty range is ambiguous in case of empty mappings at either end // of the range, bail out in that case. if (Expanded.empty()) - return llvm::None; - - const syntax::Token *BeginSpelled; - const Mapping *BeginMapping; - std::tie(BeginSpelled, BeginMapping) = - spelledForExpandedToken(&Expanded.front()); - - const syntax::Token *LastSpelled; - const Mapping *LastMapping; - std::tie(LastSpelled, LastMapping) = - spelledForExpandedToken(&Expanded.back()); + return std::nullopt; + const syntax::Token *First = &Expanded.front(); + const syntax::Token *Last = &Expanded.back(); + auto [FirstSpelled, FirstMapping] = spelledForExpandedToken(First); + auto [LastSpelled, LastMapping] = spelledForExpandedToken(Last); - FileID FID = SourceMgr->getFileID(BeginSpelled->location()); + FileID FID = SourceMgr->getFileID(FirstSpelled->location()); // FIXME: Handle multi-file changes by trying to map onto a common root. if (FID != SourceMgr->getFileID(LastSpelled->location())) - return llvm::None; + return std::nullopt; const MarkedFile &File = Files.find(FID)->second; - // If both tokens are coming from a macro argument expansion, try and map to - // smallest part of the macro argument. BeginMapping && LastMapping check is - // only for performance, they are a prerequisite for Expanded.front() and - // Expanded.back() being part of a macro arg expansion. - if (BeginMapping && LastMapping && - SourceMgr->isMacroArgExpansion(Expanded.front().location()) && - SourceMgr->isMacroArgExpansion(Expanded.back().location())) { - auto CommonRange = findCommonRangeForMacroArgs(Expanded.front(), - Expanded.back(), *SourceMgr); - // It might be the case that tokens are arguments of different macro calls, - // in that case we should continue with the logic below instead of returning - // an empty range. - if (CommonRange.isValid()) - return getTokensCovering(File.SpelledTokens, CommonRange, *SourceMgr); + // If the range is within one macro argument, the result may be only part of a + // Mapping. We must use the general (SourceManager-based) algorithm. + if (FirstMapping && FirstMapping == LastMapping && + SourceMgr->isMacroArgExpansion(First->location()) && + SourceMgr->isMacroArgExpansion(Last->location())) { + // We use excluded Prev/Next token for bounds checking. + SourceLocation Prev = (First == &ExpandedTokens.front()) + ? SourceLocation() + : (First - 1)->location(); + SourceLocation Next = (Last == &ExpandedTokens.back()) + ? SourceLocation() + : (Last + 1)->location(); + SourceRange Range = spelledForExpandedSlow( + First->location(), Last->location(), Prev, Next, FID, *SourceMgr); + if (Range.isInvalid()) + return std::nullopt; + return getTokensCovering(File.SpelledTokens, Range, *SourceMgr); } + // Otherwise, use the fast version based on Mappings. // Do not allow changes that doesn't cover full expansion. - unsigned BeginExpanded = Expanded.begin() - ExpandedTokens.data(); - unsigned EndExpanded = Expanded.end() - ExpandedTokens.data(); - if (BeginMapping && BeginExpanded != BeginMapping->BeginExpanded) - return llvm::None; - if (LastMapping && LastMapping->EndExpanded != EndExpanded) - return llvm::None; - // All is good, return the result. - return llvm::makeArrayRef( - BeginMapping ? File.SpelledTokens.data() + BeginMapping->BeginSpelled - : BeginSpelled, + unsigned FirstExpanded = Expanded.begin() - ExpandedTokens.data(); + unsigned LastExpanded = Expanded.end() - ExpandedTokens.data(); + if (FirstMapping && FirstExpanded != FirstMapping->BeginExpanded) + return std::nullopt; + if (LastMapping && LastMapping->EndExpanded != LastExpanded) + return std::nullopt; + return llvm::ArrayRef( + FirstMapping ? File.SpelledTokens.data() + FirstMapping->BeginSpelled + : FirstSpelled, LastMapping ? File.SpelledTokens.data() + LastMapping->EndSpelled : LastSpelled + 1); } @@ -415,10 +506,10 @@ TokenBuffer::spelledForExpanded(llvm::ArrayRef<syntax::Token> Expanded) const { TokenBuffer::Expansion TokenBuffer::makeExpansion(const MarkedFile &F, const Mapping &M) const { Expansion E; - E.Spelled = llvm::makeArrayRef(F.SpelledTokens.data() + M.BeginSpelled, - F.SpelledTokens.data() + M.EndSpelled); - E.Expanded = llvm::makeArrayRef(ExpandedTokens.data() + M.BeginExpanded, - ExpandedTokens.data() + M.EndExpanded); + E.Spelled = llvm::ArrayRef(F.SpelledTokens.data() + M.BeginSpelled, + F.SpelledTokens.data() + M.EndSpelled); + E.Expanded = llvm::ArrayRef(ExpandedTokens.data() + M.BeginExpanded, + ExpandedTokens.data() + M.EndExpanded); return E; } @@ -441,7 +532,7 @@ TokenBuffer::fileForSpelled(llvm::ArrayRef<syntax::Token> Spelled) const { return File; } -llvm::Optional<TokenBuffer::Expansion> +std::optional<TokenBuffer::Expansion> TokenBuffer::expansionStartingAt(const syntax::Token *Spelled) const { assert(Spelled); const auto &File = fileForSpelled(*Spelled); @@ -451,7 +542,7 @@ TokenBuffer::expansionStartingAt(const syntax::Token *Spelled) const { return M.BeginSpelled < SpelledIndex; }); if (M == File.Mappings.end() || M->BeginSpelled != SpelledIndex) - return llvm::None; + return std::nullopt; return makeExpansion(File, *M); } @@ -483,8 +574,8 @@ syntax::spelledTokensTouching(SourceLocation Loc, bool AcceptRight = Right != Tokens.end() && Right->location() <= Loc; bool AcceptLeft = Right != Tokens.begin() && (Right - 1)->endLocation() >= Loc; - return llvm::makeArrayRef(Right - (AcceptLeft ? 1 : 0), - Right + (AcceptRight ? 1 : 0)); + return llvm::ArrayRef(Right - (AcceptLeft ? 1 : 0), + Right + (AcceptRight ? 1 : 0)); } llvm::ArrayRef<syntax::Token> @@ -714,7 +805,7 @@ private: // In the simplest case, skips spelled tokens until finding one that produced // the NextExpanded token, and creates an empty mapping for them. // If Drain is provided, skips remaining tokens from that file instead. - void discard(llvm::Optional<FileID> Drain = llvm::None) { + void discard(std::optional<FileID> Drain = std::nullopt) { SourceLocation Target = Drain ? SM.getLocForEndOfFile(*Drain) : SM.getExpansionLoc( @@ -751,7 +842,7 @@ private: SpelledTokens[NextSpelled].location() <= KnownEnd) ++NextSpelled; FlushMapping(); // Emits [NextSpelled, KnownEnd] - // Now the loop contitues and will emit (KnownEnd, Target). + // Now the loop continues and will emit (KnownEnd, Target). } else { ++NextSpelled; } @@ -891,7 +982,7 @@ std::string TokenBuffer::dumpForTests() const { OS << "expanded tokens:\n" << " "; // (!) we do not show '<eof>'. - DumpTokens(OS, llvm::makeArrayRef(ExpandedTokens).drop_back()); + DumpTokens(OS, llvm::ArrayRef(ExpandedTokens).drop_back()); OS << "\n"; std::vector<FileID> Keys; diff --git a/clang/lib/Tooling/Tooling.cpp b/clang/lib/Tooling/Tooling.cpp index 6314615f83c8..8966c12ef7c1 100644 --- a/clang/lib/Tooling/Tooling.cpp +++ b/clang/lib/Tooling/Tooling.cpp @@ -98,7 +98,7 @@ static bool ignoreExtraCC1Commands(const driver::Compilation *Compilation) { OffloadCompilation = true; if (Jobs.size() > 1) { - for (auto A : Actions){ + for (auto *A : Actions){ // On MacOSX real actions may end up being wrapped in BindArchAction if (isa<driver::BindArchAction>(A)) A = *A->input_begin(); @@ -161,7 +161,7 @@ getCC1Arguments(DiagnosticsEngine *Diagnostics, /// Returns a clang build invocation initialized from the CC1 flags. CompilerInvocation *newInvocation(DiagnosticsEngine *Diagnostics, - const llvm::opt::ArgStringList &CC1Args, + ArrayRef<const char *> CC1Args, const char *const BinaryName) { assert(!CC1Args.empty() && "Must at least contain the program name!"); CompilerInvocation *Invocation = new CompilerInvocation; @@ -339,7 +339,7 @@ ToolInvocation::~ToolInvocation() { } bool ToolInvocation::run() { - std::vector<const char*> Argv; + llvm::opt::ArgStringList Argv; for (const std::string &Str : CommandLine) Argv.push_back(Str.c_str()); const char *const BinaryName = Argv[0]; @@ -362,6 +362,17 @@ bool ToolInvocation::run() { SourceManager SrcMgr(*Diagnostics, *Files); Diagnostics->setSourceManager(&SrcMgr); + // We already have a cc1, just create an invocation. + if (CommandLine.size() >= 2 && CommandLine[1] == "-cc1") { + ArrayRef<const char *> CC1Args = ArrayRef(Argv).drop_front(); + std::unique_ptr<CompilerInvocation> Invocation( + newInvocation(&*Diagnostics, CC1Args, BinaryName)); + if (Diagnostics->hasErrorOccurred()) + return false; + return Action->runInvocation(std::move(Invocation), Files, + std::move(PCHContainerOps), DiagConsumer); + } + const std::unique_ptr<driver::Driver> Driver( newDriver(&*Diagnostics, BinaryName, &Files->getVirtualFileSystem())); // The "input file not found" diagnostics from the driver are useful. @@ -371,7 +382,7 @@ bool ToolInvocation::run() { if (!Files->getFileSystemOpts().WorkingDir.empty()) Driver->setCheckInputsExist(false); const std::unique_ptr<driver::Compilation> Compilation( - Driver->BuildCompilation(llvm::makeArrayRef(Argv))); + Driver->BuildCompilation(llvm::ArrayRef(Argv))); if (!Compilation) return false; const llvm::opt::ArgStringList *const CC1Args = getCC1Arguments( diff --git a/clang/lib/Tooling/Transformer/Parsing.cpp b/clang/lib/Tooling/Transformer/Parsing.cpp index 4f41e2e90def..53a78e8df22a 100644 --- a/clang/lib/Tooling/Transformer/Parsing.cpp +++ b/clang/lib/Tooling/Transformer/Parsing.cpp @@ -14,11 +14,11 @@ #include "clang/Lex/Lexer.h" #include "clang/Tooling/Transformer/RangeSelector.h" #include "clang/Tooling/Transformer/SourceCode.h" -#include "llvm/ADT/None.h" #include "llvm/ADT/StringMap.h" #include "llvm/ADT/StringRef.h" #include "llvm/Support/Errc.h" #include "llvm/Support/Error.h" +#include <optional> #include <string> #include <utility> #include <vector> @@ -46,7 +46,7 @@ struct ParseState { }; // Represents an intermediate result returned by a parsing function. Functions -// that don't generate values should use `llvm::None` +// that don't generate values should use `std::nullopt` template <typename ResultType> struct ParseProgress { ParseState State; // Intermediate result generated by the Parser. @@ -120,11 +120,11 @@ getBinaryRangeSelectors() { } template <typename Element> -llvm::Optional<Element> findOptional(const llvm::StringMap<Element> &Map, - llvm::StringRef Key) { +std::optional<Element> findOptional(const llvm::StringMap<Element> &Map, + llvm::StringRef Key) { auto it = Map.find(Key); if (it == Map.end()) - return llvm::None; + return std::nullopt; return it->second; } @@ -152,12 +152,12 @@ static StringRef consumeWhitespace(StringRef S) { // Parses a single expected character \c c from \c State, skipping preceding // whitespace. Error if the expected character isn't found. -static ExpectedProgress<llvm::NoneType> parseChar(char c, ParseState State) { +static ExpectedProgress<std::nullopt_t> parseChar(char c, ParseState State) { State.Input = consumeWhitespace(State.Input); if (State.Input.empty() || State.Input.front() != c) return makeParseError(State, ("expected char not found: " + llvm::Twine(c)).str()); - return makeParseProgress(advance(State, 1), llvm::None); + return makeParseProgress(advance(State, 1), std::nullopt); } // Parses an identitifer "token" -- handles preceding whitespace. diff --git a/clang/lib/Tooling/Transformer/RewriteRule.cpp b/clang/lib/Tooling/Transformer/RewriteRule.cpp index 3e76489782f3..eefddc349404 100644 --- a/clang/lib/Tooling/Transformer/RewriteRule.cpp +++ b/clang/lib/Tooling/Transformer/RewriteRule.cpp @@ -13,7 +13,6 @@ #include "clang/ASTMatchers/ASTMatchers.h" #include "clang/Basic/SourceLocation.h" #include "clang/Tooling/Transformer/SourceCode.h" -#include "llvm/ADT/Optional.h" #include "llvm/ADT/StringRef.h" #include "llvm/Support/Errc.h" #include "llvm/Support/Error.h" @@ -39,8 +38,8 @@ translateEdits(const MatchResult &Result, ArrayRef<ASTEdit> ASTEdits) { Expected<CharSourceRange> Range = E.TargetRange(Result); if (!Range) return Range.takeError(); - llvm::Optional<CharSourceRange> EditRange = - tooling::getRangeForEdit(*Range, *Result.Context); + std::optional<CharSourceRange> EditRange = + tooling::getFileRangeForEdit(*Range, *Result.Context); // FIXME: let user specify whether to treat this case as an error or ignore // it as is currently done. This behavior is problematic in that it hides // failures from bad ranges. Also, the behavior here differs from @@ -50,17 +49,27 @@ translateEdits(const MatchResult &Result, ArrayRef<ASTEdit> ASTEdits) { // produces a bad range, whereas the latter will simply ignore A. if (!EditRange) return SmallVector<Edit, 0>(); - auto Replacement = E.Replacement->eval(Result); - if (!Replacement) - return Replacement.takeError(); - auto Metadata = E.Metadata(Result); - if (!Metadata) - return Metadata.takeError(); transformer::Edit T; T.Kind = E.Kind; T.Range = *EditRange; - T.Replacement = std::move(*Replacement); - T.Metadata = std::move(*Metadata); + if (E.Replacement) { + auto Replacement = E.Replacement->eval(Result); + if (!Replacement) + return Replacement.takeError(); + T.Replacement = std::move(*Replacement); + } + if (E.Note) { + auto Note = E.Note->eval(Result); + if (!Note) + return Note.takeError(); + T.Note = std::move(*Note); + } + if (E.Metadata) { + auto Metadata = E.Metadata(Result); + if (!Metadata) + return Metadata.takeError(); + T.Metadata = std::move(*Metadata); + } Edits.push_back(std::move(T)); } return Edits; @@ -121,6 +130,13 @@ ASTEdit transformer::changeTo(RangeSelector Target, TextGenerator Replacement) { return E; } +ASTEdit transformer::note(RangeSelector Anchor, TextGenerator Note) { + ASTEdit E; + E.TargetRange = transformer::before(Anchor); + E.Note = std::move(Note); + return E; +} + namespace { /// A \c TextGenerator that always returns a fixed string. class SimpleTextGenerator : public MatchComputation<std::string> { @@ -433,7 +449,7 @@ SourceLocation transformer::detail::getRuleMatchLoc(const MatchResult &Result) { auto &NodesMap = Result.Nodes.getMap(); auto Root = NodesMap.find(RootID); assert(Root != NodesMap.end() && "Transformation failed: missing root node."); - llvm::Optional<CharSourceRange> RootRange = tooling::getRangeForEdit( + std::optional<CharSourceRange> RootRange = tooling::getFileRangeForEdit( CharSourceRange::getTokenRange(Root->second.getSourceRange()), *Result.Context); if (RootRange) diff --git a/clang/lib/Tooling/Transformer/SourceCode.cpp b/clang/lib/Tooling/Transformer/SourceCode.cpp index 26b204851f05..35edc261ef09 100644 --- a/clang/lib/Tooling/Transformer/SourceCode.cpp +++ b/clang/lib/Tooling/Transformer/SourceCode.cpp @@ -50,8 +50,9 @@ CharSourceRange clang::tooling::maybeExtendRange(CharSourceRange Range, return CharSourceRange::getTokenRange(Range.getBegin(), Tok.getLocation()); } -llvm::Error clang::tooling::validateEditRange(const CharSourceRange &Range, - const SourceManager &SM) { +static llvm::Error validateRange(const CharSourceRange &Range, + const SourceManager &SM, + bool AllowSystemHeaders) { if (Range.isInvalid()) return llvm::make_error<StringError>(errc::invalid_argument, "Invalid range"); @@ -60,10 +61,12 @@ llvm::Error clang::tooling::validateEditRange(const CharSourceRange &Range, return llvm::make_error<StringError>( errc::invalid_argument, "Range starts or ends in a macro expansion"); - if (SM.isInSystemHeader(Range.getBegin()) || - SM.isInSystemHeader(Range.getEnd())) - return llvm::make_error<StringError>(errc::invalid_argument, - "Range is in system header"); + if (!AllowSystemHeaders) { + if (SM.isInSystemHeader(Range.getBegin()) || + SM.isInSystemHeader(Range.getEnd())) + return llvm::make_error<StringError>(errc::invalid_argument, + "Range is in system header"); + } std::pair<FileID, unsigned> BeginInfo = SM.getDecomposedLoc(Range.getBegin()); std::pair<FileID, unsigned> EndInfo = SM.getDecomposedLoc(Range.getEnd()); @@ -72,33 +75,74 @@ llvm::Error clang::tooling::validateEditRange(const CharSourceRange &Range, errc::invalid_argument, "Range begins and ends in different files"); if (BeginInfo.second > EndInfo.second) - return llvm::make_error<StringError>( - errc::invalid_argument, "Range's begin is past its end"); + return llvm::make_error<StringError>(errc::invalid_argument, + "Range's begin is past its end"); return llvm::Error::success(); } -llvm::Optional<CharSourceRange> -clang::tooling::getRangeForEdit(const CharSourceRange &EditRange, +llvm::Error clang::tooling::validateEditRange(const CharSourceRange &Range, + const SourceManager &SM) { + return validateRange(Range, SM, /*AllowSystemHeaders=*/false); +} + +static bool spelledInMacroDefinition(SourceLocation Loc, + const SourceManager &SM) { + while (Loc.isMacroID()) { + const auto &Expansion = SM.getSLocEntry(SM.getFileID(Loc)).getExpansion(); + if (Expansion.isMacroArgExpansion()) { + // Check the spelling location of the macro arg, in case the arg itself is + // in a macro expansion. + Loc = Expansion.getSpellingLoc(); + } else { + return true; + } + } + return false; +} + +static CharSourceRange getRange(const CharSourceRange &EditRange, const SourceManager &SM, - const LangOptions &LangOpts) { - // FIXME: makeFileCharRange() has the disadvantage of stripping off "identity" - // macros. For example, if we're looking to rewrite the int literal 3 to 6, - // and we have the following definition: - // #define DO_NOTHING(x) x - // then - // foo(DO_NOTHING(3)) - // will be rewritten to - // foo(6) - // rather than the arguably better - // foo(DO_NOTHING(6)) - // Decide whether the current behavior is desirable and modify if not. - CharSourceRange Range = Lexer::makeFileCharRange(EditRange, SM, LangOpts); + const LangOptions &LangOpts, + bool IncludeMacroExpansion) { + CharSourceRange Range; + if (IncludeMacroExpansion) { + Range = Lexer::makeFileCharRange(EditRange, SM, LangOpts); + } else { + if (spelledInMacroDefinition(EditRange.getBegin(), SM) || + spelledInMacroDefinition(EditRange.getEnd(), SM)) + return {}; + + auto B = SM.getSpellingLoc(EditRange.getBegin()); + auto E = SM.getSpellingLoc(EditRange.getEnd()); + if (EditRange.isTokenRange()) + E = Lexer::getLocForEndOfToken(E, 0, SM, LangOpts); + Range = CharSourceRange::getCharRange(B, E); + } + return Range; +} + +std::optional<CharSourceRange> clang::tooling::getFileRangeForEdit( + const CharSourceRange &EditRange, const SourceManager &SM, + const LangOptions &LangOpts, bool IncludeMacroExpansion) { + CharSourceRange Range = + getRange(EditRange, SM, LangOpts, IncludeMacroExpansion); bool IsInvalid = llvm::errorToBool(validateEditRange(Range, SM)); if (IsInvalid) - return llvm::None; + return std::nullopt; return Range; +} +std::optional<CharSourceRange> clang::tooling::getFileRange( + const CharSourceRange &EditRange, const SourceManager &SM, + const LangOptions &LangOpts, bool IncludeMacroExpansion) { + CharSourceRange Range = + getRange(EditRange, SM, LangOpts, IncludeMacroExpansion); + bool IsInvalid = + llvm::errorToBool(validateRange(Range, SM, /*AllowSystemHeaders=*/true)); + if (IsInvalid) + return std::nullopt; + return Range; } static bool startsWithNewline(const SourceManager &SM, const Token &Tok) { diff --git a/clang/lib/Tooling/Transformer/SourceCodeBuilders.cpp b/clang/lib/Tooling/Transformer/SourceCodeBuilders.cpp index 7496e968469c..10588a383da0 100644 --- a/clang/lib/Tooling/Transformer/SourceCodeBuilders.cpp +++ b/clang/lib/Tooling/Transformer/SourceCodeBuilders.cpp @@ -72,17 +72,17 @@ bool tooling::isKnownPointerLikeType(QualType Ty, ASTContext &Context) { return match(PointerLikeTy, Ty, Context).size() > 0; } -llvm::Optional<std::string> tooling::buildParens(const Expr &E, - const ASTContext &Context) { +std::optional<std::string> tooling::buildParens(const Expr &E, + const ASTContext &Context) { StringRef Text = getText(E, Context); if (Text.empty()) - return llvm::None; + return std::nullopt; if (mayEverNeedParens(E)) return ("(" + Text + ")").str(); return Text.str(); } -llvm::Optional<std::string> +std::optional<std::string> tooling::buildDereference(const Expr &E, const ASTContext &Context) { if (const auto *Op = dyn_cast<UnaryOperator>(&E)) if (Op->getOpcode() == UO_AddrOf) { @@ -90,21 +90,21 @@ tooling::buildDereference(const Expr &E, const ASTContext &Context) { StringRef Text = getText(*Op->getSubExpr()->IgnoreParenImpCasts(), Context); if (Text.empty()) - return llvm::None; + return std::nullopt; return Text.str(); } StringRef Text = getText(E, Context); if (Text.empty()) - return llvm::None; + return std::nullopt; // Add leading '*'. if (needParensAfterUnaryOperator(E)) return ("*(" + Text + ")").str(); return ("*" + Text).str(); } -llvm::Optional<std::string> tooling::buildAddressOf(const Expr &E, - const ASTContext &Context) { +std::optional<std::string> tooling::buildAddressOf(const Expr &E, + const ASTContext &Context) { if (E.isImplicitCXXThis()) return std::string("this"); if (const auto *Op = dyn_cast<UnaryOperator>(&E)) @@ -113,13 +113,13 @@ llvm::Optional<std::string> tooling::buildAddressOf(const Expr &E, StringRef Text = getText(*Op->getSubExpr()->IgnoreParenImpCasts(), Context); if (Text.empty()) - return llvm::None; + return std::nullopt; return Text.str(); } // Add leading '&'. StringRef Text = getText(E, Context); if (Text.empty()) - return llvm::None; + return std::nullopt; if (needParensAfterUnaryOperator(E)) { return ("&(" + Text + ")").str(); } @@ -128,7 +128,7 @@ llvm::Optional<std::string> tooling::buildAddressOf(const Expr &E, // Append the appropriate access operation (syntactically) to `E`, assuming `E` // is a non-pointer value. -static llvm::Optional<std::string> +static std::optional<std::string> buildAccessForValue(const Expr &E, const ASTContext &Context) { if (const auto *Op = llvm::dyn_cast<UnaryOperator>(&E)) if (Op->getOpcode() == UO_Deref) { @@ -136,7 +136,7 @@ buildAccessForValue(const Expr &E, const ASTContext &Context) { const Expr *SubExpr = Op->getSubExpr()->IgnoreParenImpCasts(); StringRef DerefText = getText(*SubExpr, Context); if (DerefText.empty()) - return llvm::None; + return std::nullopt; if (needParensBeforeDotOrArrow(*SubExpr)) return ("(" + DerefText + ")->").str(); return (DerefText + "->").str(); @@ -145,7 +145,7 @@ buildAccessForValue(const Expr &E, const ASTContext &Context) { // Add following '.'. StringRef Text = getText(E, Context); if (Text.empty()) - return llvm::None; + return std::nullopt; if (needParensBeforeDotOrArrow(E)) { return ("(" + Text + ").").str(); } @@ -154,7 +154,7 @@ buildAccessForValue(const Expr &E, const ASTContext &Context) { // Append the appropriate access operation (syntactically) to `E`, assuming `E` // is a pointer value. -static llvm::Optional<std::string> +static std::optional<std::string> buildAccessForPointer(const Expr &E, const ASTContext &Context) { if (const auto *Op = llvm::dyn_cast<UnaryOperator>(&E)) if (Op->getOpcode() == UO_AddrOf) { @@ -162,7 +162,7 @@ buildAccessForPointer(const Expr &E, const ASTContext &Context) { const Expr *SubExpr = Op->getSubExpr()->IgnoreParenImpCasts(); StringRef DerefText = getText(*SubExpr, Context); if (DerefText.empty()) - return llvm::None; + return std::nullopt; if (needParensBeforeDotOrArrow(*SubExpr)) return ("(" + DerefText + ").").str(); return (DerefText + ".").str(); @@ -171,19 +171,19 @@ buildAccessForPointer(const Expr &E, const ASTContext &Context) { // Add following '->'. StringRef Text = getText(E, Context); if (Text.empty()) - return llvm::None; + return std::nullopt; if (needParensBeforeDotOrArrow(E)) return ("(" + Text + ")->").str(); return (Text + "->").str(); } -llvm::Optional<std::string> tooling::buildDot(const Expr &E, - const ASTContext &Context) { +std::optional<std::string> tooling::buildDot(const Expr &E, + const ASTContext &Context) { return buildAccessForValue(E, Context); } -llvm::Optional<std::string> tooling::buildArrow(const Expr &E, - const ASTContext &Context) { +std::optional<std::string> tooling::buildArrow(const Expr &E, + const ASTContext &Context) { return buildAccessForPointer(E, Context); } @@ -210,11 +210,12 @@ static bool treatLikePointer(QualType Ty, PLTClass C, ASTContext &Context) { // FIXME: move over the other `maybe` functionality from Stencil. Should all be // in one place. -llvm::Optional<std::string> tooling::buildAccess(const Expr &RawExpression, - ASTContext &Context, - PLTClass Classification) { +std::optional<std::string> tooling::buildAccess(const Expr &RawExpression, + ASTContext &Context, + PLTClass Classification) { if (RawExpression.isImplicitCXXThis()) - // Return the empty string, because `None` signifies some sort of failure. + // Return the empty string, because `std::nullopt` signifies some sort of + // failure. return std::string(); const Expr *E = RawExpression.IgnoreImplicitAsWritten(); diff --git a/clang/lib/Tooling/Transformer/Stencil.cpp b/clang/lib/Tooling/Transformer/Stencil.cpp index 82dd4a2587b5..2198aefddc9f 100644 --- a/clang/lib/Tooling/Transformer/Stencil.cpp +++ b/clang/lib/Tooling/Transformer/Stencil.cpp @@ -152,7 +152,7 @@ public: if (E == nullptr) return llvm::make_error<StringError>(errc::invalid_argument, "Id not bound or not Expr: " + Id); - llvm::Optional<std::string> Source; + std::optional<std::string> Source; switch (Op) { case UnaryNodeOperator::Parens: Source = tooling::buildParens(*E, *Match.Context); @@ -277,7 +277,7 @@ public: if (E == nullptr) return llvm::make_error<StringError>(errc::invalid_argument, "Id not bound: " + BaseId); - llvm::Optional<std::string> S = tooling::buildAccess(*E, *Match.Context); + std::optional<std::string> S = tooling::buildAccess(*E, *Match.Context); if (!S) return llvm::make_error<StringError>( errc::invalid_argument, |