diff options
author | Dimitry Andric <dim@FreeBSD.org> | 2019-08-20 20:50:49 +0000 |
---|---|---|
committer | Dimitry Andric <dim@FreeBSD.org> | 2019-08-20 20:50:49 +0000 |
commit | 2298981669bf3bd63335a4be179bc0f96823a8f4 (patch) | |
tree | 1cbe2eb27f030d2d70b80ee5ca3c86bee7326a9f /lib/Tooling | |
parent | 9a83721404652cea39e9f02ae3e3b5c964602a5c (diff) |
Diffstat (limited to 'lib/Tooling')
41 files changed, 2380 insertions, 220 deletions
diff --git a/lib/Tooling/ASTDiff/ASTDiff.cpp b/lib/Tooling/ASTDiff/ASTDiff.cpp index 592e8572c770..69eff20bff7a 100644 --- a/lib/Tooling/ASTDiff/ASTDiff.cpp +++ b/lib/Tooling/ASTDiff/ASTDiff.cpp @@ -1,9 +1,8 @@ //===- ASTDiff.cpp - AST differencing implementation-----------*- C++ -*- -===// // -// The LLVM Compiler Infrastructure -// -// This file is distributed under the University of Illinois Open Source -// License. See LICENSE.TXT for details. +// 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 // //===----------------------------------------------------------------------===// // @@ -238,8 +237,8 @@ struct PreorderVisitor : public RecursiveASTVisitor<PreorderVisitor> { return true; } bool TraverseStmt(Stmt *S) { - if (S) - S = S->IgnoreImplicit(); + if (auto *E = dyn_cast_or_null<Expr>(S)) + S = E->IgnoreImplicit(); if (isNodeExcluded(Tree.AST.getSourceManager(), S)) return true; auto SavedState = PreTraverse(S); diff --git a/lib/Tooling/AllTUsExecution.cpp b/lib/Tooling/AllTUsExecution.cpp index 0f172b782963..ca9db7a561be 100644 --- a/lib/Tooling/AllTUsExecution.cpp +++ b/lib/Tooling/AllTUsExecution.cpp @@ -1,15 +1,15 @@ //===- lib/Tooling/AllTUsExecution.cpp - Execute actions on all TUs. ------===// // -// The LLVM Compiler Infrastructure -// -// This file is distributed under the University of Illinois Open Source -// License. See LICENSE.TXT for details. +// 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/AllTUsExecution.h" #include "clang/Tooling/ToolExecutorPluginRegistry.h" #include "llvm/Support/ThreadPool.h" +#include "llvm/Support/VirtualFileSystem.h" namespace clang { namespace tooling { @@ -115,25 +115,22 @@ llvm::Error AllTUsToolExecutor::execute( { llvm::ThreadPool Pool(ThreadCount == 0 ? llvm::hardware_concurrency() : ThreadCount); - llvm::SmallString<128> InitialWorkingDir; - if (auto EC = llvm::sys::fs::current_path(InitialWorkingDir)) { - InitialWorkingDir = ""; - llvm::errs() << "Error while getting current working directory: " - << EC.message() << "\n"; - } for (std::string File : Files) { Pool.async( [&](std::string Path) { Log("[" + std::to_string(Count()) + "/" + TotalNumStr + "] Processing file " + Path); - ClangTool Tool(Compilations, {Path}); + // Each thread gets an indepent copy of a VFS to allow different + // concurrent working directories. + IntrusiveRefCntPtr<llvm::vfs::FileSystem> FS = + llvm::vfs::createPhysicalFileSystem().release(); + ClangTool Tool(Compilations, {Path}, + std::make_shared<PCHContainerOperations>(), FS); Tool.appendArgumentsAdjuster(Action.second); Tool.appendArgumentsAdjuster(getDefaultArgumentsAdjusters()); for (const auto &FileAndContent : OverlayFiles) Tool.mapVirtualFile(FileAndContent.first(), FileAndContent.second); - // Do not restore working dir from multiple threads to avoid races. - Tool.setRestoreWorkingDir(false); if (Tool.run(Action.first.get())) AppendError(llvm::Twine("Failed to run action on ") + Path + "\n"); @@ -142,11 +139,6 @@ llvm::Error AllTUsToolExecutor::execute( } // Make sure all tasks have finished before resetting the working directory. Pool.wait(); - if (!InitialWorkingDir.empty()) { - if (auto EC = llvm::sys::fs::set_current_path(InitialWorkingDir)) - llvm::errs() << "Error while restoring working directory: " - << EC.message() << "\n"; - } } if (!ErrorMsg.empty()) diff --git a/lib/Tooling/ArgumentsAdjusters.cpp b/lib/Tooling/ArgumentsAdjusters.cpp index c8e9c167422e..942b35df453e 100644 --- a/lib/Tooling/ArgumentsAdjusters.cpp +++ b/lib/Tooling/ArgumentsAdjusters.cpp @@ -1,9 +1,8 @@ //===- ArgumentsAdjusters.cpp - Command line arguments adjuster -----------===// // -// The LLVM Compiler Infrastructure -// -// This file is distributed under the University of Illinois Open Source -// License. See LICENSE.TXT for details. +// 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 // //===----------------------------------------------------------------------===// // @@ -24,14 +23,18 @@ namespace tooling { ArgumentsAdjuster getClangSyntaxOnlyAdjuster() { return [](const CommandLineArguments &Args, StringRef /*unused*/) { CommandLineArguments AdjustedArgs; + bool HasSyntaxOnly = false; for (size_t i = 0, e = Args.size(); i < e; ++i) { StringRef Arg = Args[i]; // FIXME: Remove options that generate output. if (!Arg.startswith("-fcolor-diagnostics") && !Arg.startswith("-fdiagnostics-color")) AdjustedArgs.push_back(Args[i]); + if (Arg == "-fsyntax-only") + HasSyntaxOnly = true; } - AdjustedArgs.push_back("-fsyntax-only"); + if (!HasSyntaxOnly) + AdjustedArgs.push_back("-fsyntax-only"); return AdjustedArgs; }; } @@ -108,5 +111,27 @@ ArgumentsAdjuster combineAdjusters(ArgumentsAdjuster First, }; } +ArgumentsAdjuster getStripPluginsAdjuster() { + return [](const CommandLineArguments &Args, StringRef /*unused*/) { + CommandLineArguments AdjustedArgs; + for (size_t I = 0, E = Args.size(); I != E; I++) { + // According to https://clang.llvm.org/docs/ClangPlugins.html + // plugin arguments are in the form: + // -Xclang {-load, -plugin, -plugin-arg-<plugin-name>, -add-plugin} + // -Xclang <arbitrary-argument> + if (I + 4 < E && Args[I] == "-Xclang" && + (Args[I + 1] == "-load" || Args[I + 1] == "-plugin" || + llvm::StringRef(Args[I + 1]).startswith("-plugin-arg-") || + Args[I + 1] == "-add-plugin") && + Args[I + 2] == "-Xclang") { + I += 3; + continue; + } + AdjustedArgs.push_back(Args[I]); + } + return AdjustedArgs; + }; +} + } // end namespace tooling } // end namespace clang diff --git a/lib/Tooling/CommonOptionsParser.cpp b/lib/Tooling/CommonOptionsParser.cpp index 74ad4e83ee3f..f7956f7998f5 100644 --- a/lib/Tooling/CommonOptionsParser.cpp +++ b/lib/Tooling/CommonOptionsParser.cpp @@ -1,9 +1,8 @@ //===--- CommonOptionsParser.cpp - common options for clang tools ---------===// // -// The LLVM Compiler Infrastructure -// -// This file is distributed under the University of Illinois Open Source -// License. See LICENSE.TXT for details. +// 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 // //===----------------------------------------------------------------------===// // @@ -40,7 +39,7 @@ const char *const CommonOptionsParser::HelpMessage = "\tCMake option to get this output). When no build path is specified,\n" "\ta search for compile_commands.json will be attempted through all\n" "\tparent paths of the first input file . See:\n" - "\thttp://clang.llvm.org/docs/HowToSetupToolingForLLVM.html for an\n" + "\thttps://clang.llvm.org/docs/HowToSetupToolingForLLVM.html for an\n" "\texample of setting up Clang Tooling on a source tree.\n" "\n" "<source0> ... specify the paths of source files. These paths are\n" @@ -84,8 +83,6 @@ std::vector<CompileCommand> ArgumentsAdjustingCompilations::adjustCommands( llvm::Error CommonOptionsParser::init( int &argc, const char **argv, cl::OptionCategory &Category, llvm::cl::NumOccurrencesFlag OccurrencesFlag, const char *Overview) { - static cl::opt<bool> Help("h", cl::desc("Alias for -help"), cl::Hidden, - cl::sub(*cl::AllSubCommands)); static cl::opt<std::string> BuildPath("p", cl::desc("Build path"), cl::Optional, cl::cat(Category), diff --git a/lib/Tooling/CompilationDatabase.cpp b/lib/Tooling/CompilationDatabase.cpp index cce8e1f1df24..4c64750bef19 100644 --- a/lib/Tooling/CompilationDatabase.cpp +++ b/lib/Tooling/CompilationDatabase.cpp @@ -1,9 +1,8 @@ //===- CompilationDatabase.cpp --------------------------------------------===// // -// The LLVM Compiler Infrastructure -// -// This file is distributed under the University of Illinois Open Source -// License. See LICENSE.TXT for details. +// 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 // //===----------------------------------------------------------------------===// // diff --git a/lib/Tooling/Core/Diagnostic.cpp b/lib/Tooling/Core/Diagnostic.cpp index e3a33d9a3755..235bd7fc1433 100644 --- a/lib/Tooling/Core/Diagnostic.cpp +++ b/lib/Tooling/Core/Diagnostic.cpp @@ -1,9 +1,8 @@ //===--- Diagnostic.cpp - Framework for clang diagnostics tools ----------===// // -// The LLVM Compiler Infrastructure -// -// This file is distributed under the University of Illinois Open Source -// License. See LICENSE.TXT for details. +// 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 // //===----------------------------------------------------------------------===// // @@ -13,6 +12,7 @@ #include "clang/Tooling/Core/Diagnostic.h" #include "clang/Basic/SourceManager.h" +#include "llvm/ADT/STLExtras.h" namespace clang { namespace tooling { @@ -41,11 +41,21 @@ Diagnostic::Diagnostic(llvm::StringRef DiagnosticName, Diagnostic::Diagnostic(llvm::StringRef DiagnosticName, const DiagnosticMessage &Message, - const llvm::StringMap<Replacements> &Fix, const SmallVector<DiagnosticMessage, 1> &Notes, Level DiagLevel, llvm::StringRef BuildDirectory) - : DiagnosticName(DiagnosticName), Message(Message), Fix(Fix), Notes(Notes), + : DiagnosticName(DiagnosticName), Message(Message), Notes(Notes), DiagLevel(DiagLevel), BuildDirectory(BuildDirectory) {} +const llvm::StringMap<Replacements> *selectFirstFix(const Diagnostic& D) { + if (!D.Message.Fix.empty()) + return &D.Message.Fix; + auto Iter = llvm::find_if(D.Notes, [](const tooling::DiagnosticMessage &D) { + return !D.Fix.empty(); + }); + if (Iter != D.Notes.end()) + return &Iter->Fix; + return nullptr; +} + } // end namespace tooling } // end namespace clang diff --git a/lib/Tooling/Core/Lookup.cpp b/lib/Tooling/Core/Lookup.cpp index cc448d144e2c..735a5df5ed21 100644 --- a/lib/Tooling/Core/Lookup.cpp +++ b/lib/Tooling/Core/Lookup.cpp @@ -1,9 +1,8 @@ //===--- Lookup.cpp - Framework for clang refactoring tools ---------------===// // -// The LLVM Compiler Infrastructure -// -// This file is distributed under the University of Illinois Open Source -// License. See LICENSE.TXT for details. +// 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 // //===----------------------------------------------------------------------===// // @@ -15,6 +14,8 @@ #include "clang/AST/Decl.h" #include "clang/AST/DeclCXX.h" #include "clang/AST/DeclarationName.h" +#include "clang/Basic/SourceLocation.h" +#include "llvm/ADT/SmallVector.h" using namespace clang; using namespace clang::tooling; @@ -115,38 +116,72 @@ static bool isFullyQualified(const NestedNameSpecifier *NNS) { return false; } -// Returns true if spelling symbol \p QName as \p Spelling in \p UseContext is -// ambiguous. For example, if QName is "::y::bar" and the spelling is "y::bar" -// in `UseContext` "a" that contains a nested namespace "a::y", then "y::bar" -// can be resolved to ::a::y::bar, which can cause compile error. +// Adds more scope specifier to the spelled name until the spelling is not +// ambiguous. A spelling is ambiguous if the resolution of the symbol is +// ambiguous. For example, if QName is "::y::bar", the spelling is "y::bar", and +// context contains a nested namespace "a::y", then "y::bar" can be resolved to +// ::a::y::bar in the context, which can cause compile error. // FIXME: consider using namespaces. -static bool isAmbiguousNameInScope(StringRef Spelling, StringRef QName, - const DeclContext &UseContext) { +static std::string disambiguateSpellingInScope(StringRef Spelling, + StringRef QName, + const DeclContext &UseContext, + SourceLocation UseLoc) { assert(QName.startswith("::")); + assert(QName.endswith(Spelling)); if (Spelling.startswith("::")) - return false; + return Spelling; - // Lookup the first component of Spelling in all enclosing namespaces and - // check if there is any existing symbols with the same name but in different - // scope. - StringRef Head = Spelling.split("::").first; + auto UnspelledSpecifier = QName.drop_back(Spelling.size()); + llvm::SmallVector<llvm::StringRef, 2> UnspelledScopes; + UnspelledSpecifier.split(UnspelledScopes, "::", /*MaxSplit=*/-1, + /*KeepEmpty=*/false); - llvm::SmallVector<const NamespaceDecl *, 4> UseNamespaces = + llvm::SmallVector<const NamespaceDecl *, 4> EnclosingNamespaces = getAllNamedNamespaces(&UseContext); auto &AST = UseContext.getParentASTContext(); StringRef TrimmedQName = QName.substr(2); - for (const auto *NS : UseNamespaces) { - auto LookupRes = NS->lookup(DeclarationName(&AST.Idents.get(Head))); - if (!LookupRes.empty()) { - for (const NamedDecl *Res : LookupRes) - if (!TrimmedQName.startswith(Res->getQualifiedNameAsString())) - return true; + const auto &SM = UseContext.getParentASTContext().getSourceManager(); + UseLoc = SM.getSpellingLoc(UseLoc); + + auto IsAmbiguousSpelling = [&](const llvm::StringRef CurSpelling) { + if (CurSpelling.startswith("::")) + return false; + // Lookup the first component of Spelling in all enclosing namespaces + // and check if there is any existing symbols with the same name but in + // different scope. + StringRef Head = CurSpelling.split("::").first; + for (const auto *NS : EnclosingNamespaces) { + auto LookupRes = NS->lookup(DeclarationName(&AST.Idents.get(Head))); + if (!LookupRes.empty()) { + for (const NamedDecl *Res : LookupRes) + // If `Res` is not visible in `UseLoc`, we don't consider it + // ambiguous. For example, a reference in a header file should not be + // affected by a potentially ambiguous name in some file that includes + // the header. + if (!TrimmedQName.startswith(Res->getQualifiedNameAsString()) && + SM.isBeforeInTranslationUnit( + SM.getSpellingLoc(Res->getLocation()), UseLoc)) + return true; + } + } + return false; + }; + + // Add more qualifiers until the spelling is not ambiguous. + std::string Disambiguated = Spelling; + while (IsAmbiguousSpelling(Disambiguated)) { + if (UnspelledScopes.empty()) { + Disambiguated = "::" + Disambiguated; + } else { + Disambiguated = (UnspelledScopes.back() + "::" + Disambiguated).str(); + UnspelledScopes.pop_back(); } } - return false; + return Disambiguated; } std::string tooling::replaceNestedName(const NestedNameSpecifier *Use, + SourceLocation UseLoc, const DeclContext *UseContext, const NamedDecl *FromDecl, StringRef ReplacementString) { @@ -180,12 +215,7 @@ std::string tooling::replaceNestedName(const NestedNameSpecifier *Use, // specific). StringRef Suggested = getBestNamespaceSubstr(UseContext, ReplacementString, isFullyQualified(Use)); - // Use the fully qualified name if the suggested name is ambiguous. - // FIXME: consider re-shortening the name until the name is not ambiguous. We - // are not doing this because ambiguity is pretty bad and we should not try to - // be clever in handling such cases. Making this noticeable to users seems to - // be a better option. - return isAmbiguousNameInScope(Suggested, ReplacementString, *UseContext) - ? ReplacementString - : Suggested; + + return disambiguateSpellingInScope(Suggested, ReplacementString, *UseContext, + UseLoc); } diff --git a/lib/Tooling/Core/Replacement.cpp b/lib/Tooling/Core/Replacement.cpp index 3b7e39814afa..546158714e3c 100644 --- a/lib/Tooling/Core/Replacement.cpp +++ b/lib/Tooling/Core/Replacement.cpp @@ -1,9 +1,8 @@ //===- Replacement.cpp - Framework for clang refactoring tools ------------===// // -// The LLVM Compiler Infrastructure -// -// This file is distributed under the University of Illinois Open Source -// License. See LICENSE.TXT for details. +// 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 // //===----------------------------------------------------------------------===// // @@ -520,12 +519,11 @@ calculateRangesAfterReplacements(const Replacements &Replaces, return MergedRanges; tooling::Replacements FakeReplaces; for (const auto &R : MergedRanges) { - auto Err = FakeReplaces.add(Replacement(Replaces.begin()->getFilePath(), - R.getOffset(), R.getLength(), - std::string(R.getLength(), ' '))); - assert(!Err && - "Replacements must not conflict since ranges have been merged."); - llvm::consumeError(std::move(Err)); + llvm::cantFail( + FakeReplaces.add(Replacement(Replaces.begin()->getFilePath(), + R.getOffset(), R.getLength(), + std::string(R.getLength(), ' '))), + "Replacements must not conflict since ranges have been merged."); } return FakeReplaces.merge(Replaces).getAffectedRanges(); } diff --git a/lib/Tooling/DependencyScanning/DependencyScanningWorker.cpp b/lib/Tooling/DependencyScanning/DependencyScanningWorker.cpp new file mode 100644 index 000000000000..4868f2663796 --- /dev/null +++ b/lib/Tooling/DependencyScanning/DependencyScanningWorker.cpp @@ -0,0 +1,149 @@ +//===- DependencyScanningWorker.cpp - clang-scan-deps worker --------------===// +// +// 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/DependencyScanning/DependencyScanningWorker.h" +#include "clang/Frontend/CompilerInstance.h" +#include "clang/Frontend/FrontendActions.h" +#include "clang/Frontend/TextDiagnosticPrinter.h" +#include "clang/Frontend/Utils.h" +#include "clang/Tooling/Tooling.h" + +using namespace clang; +using namespace tooling; +using namespace dependencies; + +namespace { + +/// Prints out all of the gathered dependencies into a string. +class DependencyPrinter : public DependencyFileGenerator { +public: + DependencyPrinter(std::unique_ptr<DependencyOutputOptions> Opts, + std::string &S) + : DependencyFileGenerator(*Opts), Opts(std::move(Opts)), S(S) {} + + void finishedMainFile(DiagnosticsEngine &Diags) override { + llvm::raw_string_ostream OS(S); + outputDependencyFile(OS); + } + +private: + std::unique_ptr<DependencyOutputOptions> Opts; + std::string &S; +}; + +/// A proxy file system that doesn't call `chdir` when changing the working +/// directory of a clang tool. +class ProxyFileSystemWithoutChdir : public llvm::vfs::ProxyFileSystem { +public: + ProxyFileSystemWithoutChdir( + llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> FS) + : ProxyFileSystem(std::move(FS)) {} + + llvm::ErrorOr<std::string> getCurrentWorkingDirectory() const override { + assert(!CWD.empty() && "empty CWD"); + return CWD; + } + + std::error_code setCurrentWorkingDirectory(const Twine &Path) override { + CWD = Path.str(); + return {}; + } + +private: + std::string CWD; +}; + +/// A clang tool that runs the preprocessor in a mode that's optimized for +/// dependency scanning for the given compiler invocation. +class DependencyScanningAction : public tooling::ToolAction { +public: + DependencyScanningAction(StringRef WorkingDirectory, + std::string &DependencyFileContents) + : WorkingDirectory(WorkingDirectory), + DependencyFileContents(DependencyFileContents) {} + + bool runInvocation(std::shared_ptr<CompilerInvocation> Invocation, + FileManager *FileMgr, + std::shared_ptr<PCHContainerOperations> PCHContainerOps, + DiagnosticConsumer *DiagConsumer) override { + // Create a compiler instance to handle the actual work. + CompilerInstance Compiler(std::move(PCHContainerOps)); + Compiler.setInvocation(std::move(Invocation)); + FileMgr->getFileSystemOpts().WorkingDir = WorkingDirectory; + Compiler.setFileManager(FileMgr); + + // Don't print 'X warnings and Y errors generated'. + Compiler.getDiagnosticOpts().ShowCarets = false; + // Create the compiler's actual diagnostics engine. + Compiler.createDiagnostics(DiagConsumer, /*ShouldOwnClient=*/false); + if (!Compiler.hasDiagnostics()) + return false; + + Compiler.createSourceManager(*FileMgr); + + // Create the dependency collector that will collect the produced + // dependencies. + // + // This also moves the existing dependency output options from the + // invocation to the collector. The options in the invocation are reset, + // which ensures that the compiler won't create new dependency collectors, + // and thus won't write out the extra '.d' files to disk. + auto Opts = llvm::make_unique<DependencyOutputOptions>( + std::move(Compiler.getInvocation().getDependencyOutputOpts())); + // We need at least one -MT equivalent for the generator to work. + if (Opts->Targets.empty()) + Opts->Targets = {"clang-scan-deps dependency"}; + Compiler.addDependencyCollector(std::make_shared<DependencyPrinter>( + std::move(Opts), DependencyFileContents)); + + auto Action = llvm::make_unique<PreprocessOnlyAction>(); + const bool Result = Compiler.ExecuteAction(*Action); + FileMgr->clearStatCache(); + return Result; + } + +private: + StringRef WorkingDirectory; + /// The dependency file will be written to this string. + std::string &DependencyFileContents; +}; + +} // end anonymous namespace + +DependencyScanningWorker::DependencyScanningWorker() { + DiagOpts = new DiagnosticOptions(); + PCHContainerOps = std::make_shared<PCHContainerOperations>(); + /// FIXME: Use the shared file system from the service for fast scanning + /// mode. + WorkerFS = new ProxyFileSystemWithoutChdir(llvm::vfs::getRealFileSystem()); +} + +llvm::Expected<std::string> +DependencyScanningWorker::getDependencyFile(const std::string &Input, + StringRef WorkingDirectory, + const CompilationDatabase &CDB) { + // 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.get()); + + WorkerFS->setCurrentWorkingDirectory(WorkingDirectory); + tooling::ClangTool Tool(CDB, Input, PCHContainerOps, WorkerFS); + Tool.clearArgumentsAdjusters(); + Tool.setRestoreWorkingDir(false); + Tool.setPrintErrorMessage(false); + Tool.setDiagnosticConsumer(&DiagPrinter); + std::string Output; + DependencyScanningAction Action(WorkingDirectory, Output); + if (Tool.run(&Action)) { + return llvm::make_error<llvm::StringError>(DiagnosticsOS.str(), + llvm::inconvertibleErrorCode()); + } + return Output; +} diff --git a/lib/Tooling/Execution.cpp b/lib/Tooling/Execution.cpp index 9ddb18a57b46..c39a4fcdac82 100644 --- a/lib/Tooling/Execution.cpp +++ b/lib/Tooling/Execution.cpp @@ -1,9 +1,8 @@ //===- lib/Tooling/Execution.cpp - Implements tool execution framework. ---===// // -// The LLVM Compiler Infrastructure -// -// This file is distributed under the University of Illinois Open Source -// License. See LICENSE.TXT for details. +// 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 // //===----------------------------------------------------------------------===// diff --git a/lib/Tooling/FileMatchTrie.cpp b/lib/Tooling/FileMatchTrie.cpp index 202b3f00f3fb..7df5a16fd88f 100644 --- a/lib/Tooling/FileMatchTrie.cpp +++ b/lib/Tooling/FileMatchTrie.cpp @@ -1,9 +1,8 @@ //===- FileMatchTrie.cpp --------------------------------------------------===// // -// The LLVM Compiler Infrastructure -// -// This file is distributed under the University of Illinois Open Source -// License. See LICENSE.TXT for details. +// 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 // //===----------------------------------------------------------------------===// // diff --git a/lib/Tooling/FixIt.cpp b/lib/Tooling/FixIt.cpp index 70942c5ac845..76c92c543736 100644 --- a/lib/Tooling/FixIt.cpp +++ b/lib/Tooling/FixIt.cpp @@ -1,9 +1,8 @@ //===--- FixIt.cpp - FixIt Hint utilities -----------------------*- C++ -*-===// // -// The LLVM Compiler Infrastructure -// -// This file is distributed under the University of Illinois Open Source -// License. See LICENSE.TXT for details. +// 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 // //===----------------------------------------------------------------------===// // @@ -19,12 +18,11 @@ namespace tooling { namespace fixit { namespace internal { -StringRef getText(SourceRange Range, const ASTContext &Context) { - return Lexer::getSourceText(CharSourceRange::getTokenRange(Range), - Context.getSourceManager(), +StringRef getText(CharSourceRange Range, const ASTContext &Context) { + return Lexer::getSourceText(Range, Context.getSourceManager(), Context.getLangOpts()); } -} // end namespace internal +} // namespace internal } // end namespace fixit } // end namespace tooling diff --git a/lib/Tooling/GuessTargetAndModeCompilationDatabase.cpp b/lib/Tooling/GuessTargetAndModeCompilationDatabase.cpp new file mode 100644 index 000000000000..ac3faf1b01f9 --- /dev/null +++ b/lib/Tooling/GuessTargetAndModeCompilationDatabase.cpp @@ -0,0 +1,57 @@ +//===- GuessTargetAndModeCompilationDatabase.cpp --------------------------===// +// +// 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/CompilationDatabase.h" +#include "clang/Tooling/Tooling.h" +#include <memory> + +namespace clang { +namespace tooling { + +namespace { +class TargetAndModeAdderDatabase : public CompilationDatabase { +public: + TargetAndModeAdderDatabase(std::unique_ptr<CompilationDatabase> Base) + : Base(std::move(Base)) { + assert(this->Base != nullptr); + } + + std::vector<std::string> getAllFiles() const override { + return Base->getAllFiles(); + } + + std::vector<CompileCommand> getAllCompileCommands() const override { + return addTargetAndMode(Base->getAllCompileCommands()); + } + + std::vector<CompileCommand> + getCompileCommands(StringRef FilePath) const override { + return addTargetAndMode(Base->getCompileCommands(FilePath)); + } + +private: + std::vector<CompileCommand> + addTargetAndMode(std::vector<CompileCommand> Cmds) const { + for (auto &Cmd : Cmds) { + if (Cmd.CommandLine.empty()) + continue; + addTargetAndModeForProgramName(Cmd.CommandLine, Cmd.CommandLine.front()); + } + return Cmds; + } + std::unique_ptr<CompilationDatabase> Base; +}; +} // namespace + +std::unique_ptr<CompilationDatabase> +inferTargetAndDriverMode(std::unique_ptr<CompilationDatabase> Base) { + return llvm::make_unique<TargetAndModeAdderDatabase>(std::move(Base)); +} + +} // namespace tooling +} // namespace clang diff --git a/lib/Tooling/Inclusions/HeaderIncludes.cpp b/lib/Tooling/Inclusions/HeaderIncludes.cpp index c74ad0b9cd56..a7f79c33bc55 100644 --- a/lib/Tooling/Inclusions/HeaderIncludes.cpp +++ b/lib/Tooling/Inclusions/HeaderIncludes.cpp @@ -1,15 +1,15 @@ //===--- HeaderIncludes.cpp - Insert/Delete #includes --*- C++ -*----------===// // -// The LLVM Compiler Infrastructure -// -// This file is distributed under the University of Illinois Open Source -// License. See LICENSE.TXT for details. +// 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/HeaderIncludes.h" #include "clang/Basic/SourceManager.h" #include "clang/Lex/Lexer.h" +#include "llvm/ADT/Optional.h" #include "llvm/Support/FormatVariadic.h" namespace clang { @@ -51,12 +51,16 @@ unsigned getOffsetAfterTokenSequence( // Check if a sequence of tokens is like "#<Name> <raw_identifier>". If it is, // \p Tok will be the token after this directive; otherwise, it can be any token -// after the given \p Tok (including \p Tok). -bool checkAndConsumeDirectiveWithName(Lexer &Lex, StringRef Name, Token &Tok) { +// after the given \p Tok (including \p Tok). If \p RawIDName is provided, the +// (second) raw_identifier name is checked. +bool checkAndConsumeDirectiveWithName( + Lexer &Lex, StringRef Name, Token &Tok, + llvm::Optional<StringRef> RawIDName = llvm::None) { bool Matched = Tok.is(tok::hash) && !Lex.LexFromRawLexer(Tok) && Tok.is(tok::raw_identifier) && Tok.getRawIdentifier() == Name && !Lex.LexFromRawLexer(Tok) && - Tok.is(tok::raw_identifier); + Tok.is(tok::raw_identifier) && + (!RawIDName || Tok.getRawIdentifier() == *RawIDName); if (Matched) Lex.LexFromRawLexer(Tok); return Matched; @@ -69,24 +73,45 @@ void skipComments(Lexer &Lex, Token &Tok) { } // Returns the offset after header guard directives and any comments -// before/after header guards. If no header guard presents in the code, this -// will returns the offset after skipping all comments from the start of the -// code. +// before/after header guards (e.g. #ifndef/#define pair, #pragma once). If no +// header guard is present in the code, this will return the offset after +// skipping all comments from the start of the code. unsigned getOffsetAfterHeaderGuardsAndComments(StringRef FileName, StringRef Code, const IncludeStyle &Style) { - return getOffsetAfterTokenSequence( - FileName, Code, Style, - [](const SourceManager &SM, Lexer &Lex, Token Tok) { - skipComments(Lex, Tok); - unsigned InitialOffset = SM.getFileOffset(Tok.getLocation()); - if (checkAndConsumeDirectiveWithName(Lex, "ifndef", Tok)) { - skipComments(Lex, Tok); - if (checkAndConsumeDirectiveWithName(Lex, "define", Tok)) - return SM.getFileOffset(Tok.getLocation()); - } - return InitialOffset; - }); + // \p Consume returns location after header guard or 0 if no header guard is + // found. + auto ConsumeHeaderGuardAndComment = + [&](std::function<unsigned(const SourceManager &SM, Lexer &Lex, + Token Tok)> + Consume) { + return getOffsetAfterTokenSequence( + FileName, Code, Style, + [&Consume](const SourceManager &SM, Lexer &Lex, Token Tok) { + skipComments(Lex, Tok); + unsigned InitialOffset = SM.getFileOffset(Tok.getLocation()); + return std::max(InitialOffset, Consume(SM, Lex, Tok)); + }); + }; + return std::max( + // #ifndef/#define + ConsumeHeaderGuardAndComment( + [](const SourceManager &SM, Lexer &Lex, Token Tok) -> unsigned { + if (checkAndConsumeDirectiveWithName(Lex, "ifndef", Tok)) { + skipComments(Lex, Tok); + if (checkAndConsumeDirectiveWithName(Lex, "define", Tok)) + return SM.getFileOffset(Tok.getLocation()); + } + return 0; + }), + // #pragma once + ConsumeHeaderGuardAndComment( + [](const SourceManager &SM, Lexer &Lex, Token Tok) -> unsigned { + if (checkAndConsumeDirectiveWithName(Lex, "pragma", Tok, + StringRef("once"))) + return SM.getFileOffset(Tok.getLocation()); + return 0; + })); } // Check if a sequence of tokens is like diff --git a/lib/Tooling/Inclusions/IncludeStyle.cpp b/lib/Tooling/Inclusions/IncludeStyle.cpp index 3597710f1f6e..c53c82c83630 100644 --- a/lib/Tooling/Inclusions/IncludeStyle.cpp +++ b/lib/Tooling/Inclusions/IncludeStyle.cpp @@ -1,9 +1,8 @@ //===--- IncludeStyle.cpp - Style of C++ #include directives -----*- C++-*-===// // -// The LLVM Compiler Infrastructure -// -// This file is distributed under the University of Illinois Open Source -// License. See LICENSE.TXT for details. +// 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 // //===----------------------------------------------------------------------===// diff --git a/lib/Tooling/InterpolatingCompilationDatabase.cpp b/lib/Tooling/InterpolatingCompilationDatabase.cpp index 4d0d84f660a2..53c8dd448fd9 100644 --- a/lib/Tooling/InterpolatingCompilationDatabase.cpp +++ b/lib/Tooling/InterpolatingCompilationDatabase.cpp @@ -1,9 +1,8 @@ //===- InterpolatingCompilationDatabase.cpp ---------------------*- C++ -*-===// // -// The LLVM Compiler Infrastructure -// -// This file is distributed under the University of Illinois Open Source -// License. See LICENSE.TXT for details. +// 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 // //===----------------------------------------------------------------------===// // @@ -151,7 +150,8 @@ struct TransferableCommand { // spelling of each argument; re-rendering is lossy for aliased flags. // E.g. in CL mode, /W4 maps to -Wall. auto OptTable = clang::driver::createDriverOptTable(); - Cmd.CommandLine.emplace_back(OldArgs.front()); + if (!OldArgs.empty()) + Cmd.CommandLine.emplace_back(OldArgs.front()); for (unsigned Pos = 1; Pos < OldArgs.size();) { using namespace driver::options; @@ -206,10 +206,13 @@ struct TransferableCommand { bool TypeCertain; auto TargetType = guessType(Filename, &TypeCertain); // If the filename doesn't determine the language (.h), transfer with -x. - if (TargetType != types::TY_INVALID && !TypeCertain && Type) { - TargetType = types::onlyPrecompileType(TargetType) // header? - ? types::lookupHeaderTypeForSourceType(*Type) - : *Type; + if ((!TargetType || !TypeCertain) && Type) { + // Use *Type, or its header variant if the file is a header. + // Treat no/invalid extension as header (e.g. C++ standard library). + TargetType = + (!TargetType || types::onlyPrecompileType(TargetType)) // header? + ? types::lookupHeaderTypeForSourceType(*Type) + : *Type; if (ClangCLMode) { const StringRef Flag = toCLFlag(TargetType); if (!Flag.empty()) @@ -227,6 +230,7 @@ struct TransferableCommand { LangStandard::getLangStandardForKind(Std).getName()).str()); } Result.CommandLine.push_back(Filename); + Result.Heuristic = "inferred from " + Cmd.Filename; return Result; } @@ -240,7 +244,8 @@ private: } // Otherwise just check the clang executable file name. - return llvm::sys::path::stem(CmdLine.front()).endswith_lower("cl"); + return !CmdLine.empty() && + llvm::sys::path::stem(CmdLine.front()).endswith_lower("cl"); } // Map the language from the --std flag to that of the -x flag. @@ -473,8 +478,7 @@ private: ArrayRef<SubstringAndIndex> Idx) const { assert(!Idx.empty()); // Longest substring match will be adjacent to a direct lookup. - auto It = - std::lower_bound(Idx.begin(), Idx.end(), SubstringAndIndex{Key, 0}); + auto It = llvm::lower_bound(Idx, SubstringAndIndex{Key, 0}); if (It == Idx.begin()) return *It; if (It == Idx.end()) diff --git a/lib/Tooling/JSONCompilationDatabase.cpp b/lib/Tooling/JSONCompilationDatabase.cpp index b0feaa229c11..f19a0f7550b9 100644 --- a/lib/Tooling/JSONCompilationDatabase.cpp +++ b/lib/Tooling/JSONCompilationDatabase.cpp @@ -1,9 +1,8 @@ //===- JSONCompilationDatabase.cpp ----------------------------------------===// // -// The LLVM Compiler Infrastructure -// -// This file is distributed under the University of Illinois Open Source -// License. See LICENSE.TXT for details. +// 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 // //===----------------------------------------------------------------------===// // @@ -15,7 +14,9 @@ #include "clang/Basic/LLVM.h" #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" #include "llvm/ADT/StringRef.h" @@ -166,7 +167,9 @@ class JSONCompilationDatabasePlugin : public CompilationDatabasePlugin { llvm::sys::path::append(JSONDatabasePath, "compile_commands.json"); auto Base = JSONCompilationDatabase::loadFromFile( JSONDatabasePath, ErrorMessage, JSONCommandLineSyntax::AutoDetect); - return Base ? inferMissingCompileCommands(std::move(Base)) : nullptr; + return Base ? inferTargetAndDriverMode( + inferMissingCompileCommands(std::move(Base))) + : nullptr; } }; @@ -191,8 +194,11 @@ std::unique_ptr<JSONCompilationDatabase> JSONCompilationDatabase::loadFromFile(StringRef FilePath, std::string &ErrorMessage, JSONCommandLineSyntax Syntax) { + // Don't mmap: if we're a long-lived process, the build system may overwrite. llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>> DatabaseBuffer = - llvm::MemoryBuffer::getFile(FilePath); + llvm::MemoryBuffer::getFile(FilePath, /*FileSize=*/-1, + /*RequiresNullTerminator=*/true, + /*IsVolatile=*/true); if (std::error_code Result = DatabaseBuffer.getError()) { ErrorMessage = "Error while opening JSON database: " + Result.message(); return nullptr; @@ -250,15 +256,57 @@ JSONCompilationDatabase::getAllCompileCommands() const { return Commands; } +static llvm::StringRef stripExecutableExtension(llvm::StringRef Name) { + Name.consume_back(".exe"); + return Name; +} + +// There are compiler-wrappers (ccache, distcc, gomacc) that take the "real" +// compiler as an argument, e.g. distcc gcc -O3 foo.c. +// These end up in compile_commands.json when people set CC="distcc gcc". +// Clang's driver doesn't understand this, so we need to unwrap. +static bool unwrapCommand(std::vector<std::string> &Args) { + if (Args.size() < 2) + return false; + StringRef Wrapper = + stripExecutableExtension(llvm::sys::path::filename(Args.front())); + if (Wrapper == "distcc" || Wrapper == "gomacc" || Wrapper == "ccache") { + // Most of these wrappers support being invoked 3 ways: + // `distcc g++ file.c` This is the mode we're trying to match. + // We need to drop `distcc`. + // `distcc file.c` This acts like compiler is cc or similar. + // Clang's driver can handle this, no change needed. + // `g++ file.c` g++ is a symlink to distcc. + // We don't even notice this case, and all is well. + // + // We need to distinguish between the first and second case. + // The wrappers themselves don't take flags, so Args[1] is a compiler flag, + // an input file, or a compiler. Inputs have extensions, compilers don't. + bool HasCompiler = + (Args[1][0] != '-') && + !llvm::sys::path::has_extension(stripExecutableExtension(Args[1])); + if (HasCompiler) { + Args.erase(Args.begin()); + return true; + } + // If !HasCompiler, wrappers act like GCC. Fine: so do we. + } + return false; +} + static std::vector<std::string> nodeToCommandLine(JSONCommandLineSyntax Syntax, const std::vector<llvm::yaml::ScalarNode *> &Nodes) { SmallString<1024> Storage; - if (Nodes.size() == 1) - return unescapeCommandLine(Syntax, Nodes[0]->getValue(Storage)); std::vector<std::string> Arguments; - for (const auto *Node : Nodes) - Arguments.push_back(Node->getValue(Storage)); + if (Nodes.size() == 1) + Arguments = unescapeCommandLine(Syntax, Nodes[0]->getValue(Storage)); + else + for (const auto *Node : Nodes) + Arguments.push_back(Node->getValue(Storage)); + // There may be multiple wrappers: using distcc and ccache together is common. + while (unwrapCommand(Arguments)) + ; return Arguments; } @@ -371,6 +419,7 @@ bool JSONCompilationDatabase::parse(std::string &ErrorMessage) { 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); diff --git a/lib/Tooling/Refactoring.cpp b/lib/Tooling/Refactoring.cpp index db34c952d794..f379a9c3bcf6 100644 --- a/lib/Tooling/Refactoring.cpp +++ b/lib/Tooling/Refactoring.cpp @@ -1,9 +1,8 @@ //===--- Refactoring.cpp - Framework for clang refactoring tools ----------===// // -// The LLVM Compiler Infrastructure -// -// This file is distributed under the University of Illinois Open Source -// License. See LICENSE.TXT for details. +// 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 // //===----------------------------------------------------------------------===// // diff --git a/lib/Tooling/Refactoring/ASTSelection.cpp b/lib/Tooling/Refactoring/ASTSelection.cpp index b8f996d8218c..64e57af59011 100644 --- a/lib/Tooling/Refactoring/ASTSelection.cpp +++ b/lib/Tooling/Refactoring/ASTSelection.cpp @@ -1,9 +1,8 @@ //===--- ASTSelection.cpp - Clang refactoring library ---------------------===// // -// The LLVM Compiler Infrastructure -// -// This file is distributed under the University of Illinois Open Source -// License. See LICENSE.TXT for details. +// 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 // //===----------------------------------------------------------------------===// diff --git a/lib/Tooling/Refactoring/ASTSelectionRequirements.cpp b/lib/Tooling/Refactoring/ASTSelectionRequirements.cpp index c0232c5da442..14fc66a979ae 100644 --- a/lib/Tooling/Refactoring/ASTSelectionRequirements.cpp +++ b/lib/Tooling/Refactoring/ASTSelectionRequirements.cpp @@ -1,9 +1,8 @@ //===--- ASTSelectionRequirements.cpp - Clang refactoring library ---------===// // -// The LLVM Compiler Infrastructure -// -// This file is distributed under the University of Illinois Open Source -// License. See LICENSE.TXT for details. +// 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 // //===----------------------------------------------------------------------===// diff --git a/lib/Tooling/Refactoring/AtomicChange.cpp b/lib/Tooling/Refactoring/AtomicChange.cpp index e8b0fdbeb662..4cf63306d262 100644 --- a/lib/Tooling/Refactoring/AtomicChange.cpp +++ b/lib/Tooling/Refactoring/AtomicChange.cpp @@ -1,9 +1,8 @@ //===--- AtomicChange.cpp - AtomicChange implementation -----------------*- C++ -*-===// // -// The LLVM Compiler Infrastructure -// -// This file is distributed under the University of Illinois Open Source -// License. See LICENSE.TXT for details. +// 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 // //===----------------------------------------------------------------------===// diff --git a/lib/Tooling/Refactoring/Extract/Extract.cpp b/lib/Tooling/Refactoring/Extract/Extract.cpp index 7a741bdb2e91..f5b94a462103 100644 --- a/lib/Tooling/Refactoring/Extract/Extract.cpp +++ b/lib/Tooling/Refactoring/Extract/Extract.cpp @@ -1,9 +1,8 @@ //===--- Extract.cpp - Clang refactoring library --------------------------===// // -// The LLVM Compiler Infrastructure -// -// This file is distributed under the University of Illinois Open Source -// License. See LICENSE.TXT for details. +// 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 // //===----------------------------------------------------------------------===// /// diff --git a/lib/Tooling/Refactoring/Extract/SourceExtraction.cpp b/lib/Tooling/Refactoring/Extract/SourceExtraction.cpp index 7fd8cc2d3c7f..533c373e35c4 100644 --- a/lib/Tooling/Refactoring/Extract/SourceExtraction.cpp +++ b/lib/Tooling/Refactoring/Extract/SourceExtraction.cpp @@ -1,9 +1,8 @@ //===--- SourceExtraction.cpp - Clang refactoring library -----------------===// // -// The LLVM Compiler Infrastructure -// -// This file is distributed under the University of Illinois Open Source -// License. See LICENSE.TXT for details. +// 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 // //===----------------------------------------------------------------------===// diff --git a/lib/Tooling/Refactoring/Extract/SourceExtraction.h b/lib/Tooling/Refactoring/Extract/SourceExtraction.h index 4b4bd8b477ff..545eb6c1a11c 100644 --- a/lib/Tooling/Refactoring/Extract/SourceExtraction.h +++ b/lib/Tooling/Refactoring/Extract/SourceExtraction.h @@ -1,9 +1,8 @@ //===--- SourceExtraction.cpp - Clang refactoring library -----------------===// // -// The LLVM Compiler Infrastructure -// -// This file is distributed under the University of Illinois Open Source -// License. See LICENSE.TXT for details. +// 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 // //===----------------------------------------------------------------------===// diff --git a/lib/Tooling/Refactoring/RangeSelector.cpp b/lib/Tooling/Refactoring/RangeSelector.cpp new file mode 100644 index 000000000000..768c02e2277b --- /dev/null +++ b/lib/Tooling/Refactoring/RangeSelector.cpp @@ -0,0 +1,296 @@ +//===--- RangeSelector.cpp - RangeSelector implementations ------*- 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/Refactoring/RangeSelector.h" +#include "clang/AST/Expr.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Basic/SourceLocation.h" +#include "clang/Lex/Lexer.h" +#include "clang/Tooling/Refactoring/SourceCode.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/Support/Errc.h" +#include "llvm/Support/Error.h" +#include <string> +#include <utility> +#include <vector> + +using namespace clang; +using namespace tooling; + +using ast_matchers::MatchFinder; +using ast_type_traits::ASTNodeKind; +using ast_type_traits::DynTypedNode; +using llvm::Error; +using llvm::StringError; + +using MatchResult = MatchFinder::MatchResult; + +static Error invalidArgumentError(Twine Message) { + return llvm::make_error<StringError>(llvm::errc::invalid_argument, Message); +} + +static Error typeError(StringRef ID, const ASTNodeKind &Kind) { + return invalidArgumentError("mismatched type (node id=" + ID + + " kind=" + Kind.asStringRef() + ")"); +} + +static Error typeError(StringRef ID, const ASTNodeKind &Kind, + Twine ExpectedType) { + return invalidArgumentError("mismatched type: expected one of " + + ExpectedType + " (node id=" + ID + + " kind=" + Kind.asStringRef() + ")"); +} + +static Error missingPropertyError(StringRef ID, Twine Description, + StringRef Property) { + return invalidArgumentError(Description + " requires property '" + Property + + "' (node id=" + ID + ")"); +} + +static Expected<DynTypedNode> getNode(const ast_matchers::BoundNodes &Nodes, + StringRef ID) { + auto &NodesMap = Nodes.getMap(); + auto It = NodesMap.find(ID); + if (It == NodesMap.end()) + return invalidArgumentError("ID not bound: " + ID); + return It->second; +} + +// FIXME: handling of macros should be configurable. +static SourceLocation findPreviousTokenStart(SourceLocation Start, + const SourceManager &SM, + const LangOptions &LangOpts) { + if (Start.isInvalid() || Start.isMacroID()) + return SourceLocation(); + + SourceLocation BeforeStart = Start.getLocWithOffset(-1); + if (BeforeStart.isInvalid() || BeforeStart.isMacroID()) + return SourceLocation(); + + return Lexer::GetBeginningOfToken(BeforeStart, SM, LangOpts); +} + +// Finds the start location of the previous token of kind \p TK. +// FIXME: handling of macros should be configurable. +static SourceLocation findPreviousTokenKind(SourceLocation Start, + const SourceManager &SM, + const LangOptions &LangOpts, + tok::TokenKind TK) { + while (true) { + SourceLocation L = findPreviousTokenStart(Start, SM, LangOpts); + if (L.isInvalid() || L.isMacroID()) + return SourceLocation(); + + Token T; + if (Lexer::getRawToken(L, T, SM, LangOpts, /*IgnoreWhiteSpace=*/true)) + return SourceLocation(); + + if (T.is(TK)) + return T.getLocation(); + + Start = L; + } +} + +static SourceLocation findOpenParen(const CallExpr &E, const SourceManager &SM, + const LangOptions &LangOpts) { + SourceLocation EndLoc = + E.getNumArgs() == 0 ? E.getRParenLoc() : E.getArg(0)->getBeginLoc(); + return findPreviousTokenKind(EndLoc, SM, LangOpts, tok::TokenKind::l_paren); +} + +RangeSelector tooling::before(RangeSelector Selector) { + return [Selector](const MatchResult &Result) -> Expected<CharSourceRange> { + Expected<CharSourceRange> SelectedRange = Selector(Result); + if (!SelectedRange) + return SelectedRange.takeError(); + return CharSourceRange::getCharRange(SelectedRange->getBegin()); + }; +} + +RangeSelector tooling::after(RangeSelector Selector) { + return [Selector](const MatchResult &Result) -> Expected<CharSourceRange> { + Expected<CharSourceRange> SelectedRange = Selector(Result); + if (!SelectedRange) + return SelectedRange.takeError(); + if (SelectedRange->isCharRange()) + return CharSourceRange::getCharRange(SelectedRange->getEnd()); + return CharSourceRange::getCharRange(Lexer::getLocForEndOfToken( + SelectedRange->getEnd(), 0, Result.Context->getSourceManager(), + Result.Context->getLangOpts())); + }; +} + +RangeSelector tooling::node(std::string ID) { + return [ID](const MatchResult &Result) -> Expected<CharSourceRange> { + Expected<DynTypedNode> Node = getNode(Result.Nodes, ID); + if (!Node) + return Node.takeError(); + return Node->get<Stmt>() != nullptr && Node->get<Expr>() == nullptr + ? getExtendedRange(*Node, tok::TokenKind::semi, *Result.Context) + : CharSourceRange::getTokenRange(Node->getSourceRange()); + }; +} + +RangeSelector tooling::statement(std::string ID) { + return [ID](const MatchResult &Result) -> Expected<CharSourceRange> { + Expected<DynTypedNode> Node = getNode(Result.Nodes, ID); + if (!Node) + return Node.takeError(); + return getExtendedRange(*Node, tok::TokenKind::semi, *Result.Context); + }; +} + +RangeSelector tooling::range(RangeSelector Begin, RangeSelector End) { + return [Begin, End](const MatchResult &Result) -> Expected<CharSourceRange> { + Expected<CharSourceRange> BeginRange = Begin(Result); + if (!BeginRange) + return BeginRange.takeError(); + Expected<CharSourceRange> EndRange = End(Result); + if (!EndRange) + return EndRange.takeError(); + SourceLocation B = BeginRange->getBegin(); + SourceLocation E = EndRange->getEnd(); + // Note: we are precluding the possibility of sub-token ranges in the case + // that EndRange is a token range. + if (Result.SourceManager->isBeforeInTranslationUnit(E, B)) { + return invalidArgumentError("Bad range: out of order"); + } + return CharSourceRange(SourceRange(B, E), EndRange->isTokenRange()); + }; +} + +RangeSelector tooling::range(std::string BeginID, std::string EndID) { + return tooling::range(node(std::move(BeginID)), node(std::move(EndID))); +} + +RangeSelector tooling::member(std::string ID) { + return [ID](const MatchResult &Result) -> Expected<CharSourceRange> { + Expected<DynTypedNode> Node = getNode(Result.Nodes, ID); + if (!Node) + return Node.takeError(); + if (auto *M = Node->get<clang::MemberExpr>()) + return CharSourceRange::getTokenRange( + M->getMemberNameInfo().getSourceRange()); + return typeError(ID, Node->getNodeKind(), "MemberExpr"); + }; +} + +RangeSelector tooling::name(std::string ID) { + return [ID](const MatchResult &Result) -> Expected<CharSourceRange> { + Expected<DynTypedNode> N = getNode(Result.Nodes, ID); + if (!N) + return N.takeError(); + auto &Node = *N; + if (const auto *D = Node.get<NamedDecl>()) { + if (!D->getDeclName().isIdentifier()) + return missingPropertyError(ID, "name", "identifier"); + SourceLocation L = D->getLocation(); + auto R = CharSourceRange::getTokenRange(L, L); + // Verify that the range covers exactly the name. + // FIXME: extend this code to support cases like `operator +` or + // `foo<int>` for which this range will be too short. Doing so will + // require subcasing `NamedDecl`, because it doesn't provide virtual + // access to the \c DeclarationNameInfo. + if (getText(R, *Result.Context) != D->getName()) + return CharSourceRange(); + return R; + } + if (const auto *E = Node.get<DeclRefExpr>()) { + if (!E->getNameInfo().getName().isIdentifier()) + return missingPropertyError(ID, "name", "identifier"); + SourceLocation L = E->getLocation(); + return CharSourceRange::getTokenRange(L, L); + } + if (const auto *I = Node.get<CXXCtorInitializer>()) { + if (!I->isMemberInitializer() && I->isWritten()) + return missingPropertyError(ID, "name", "explicit member initializer"); + SourceLocation L = I->getMemberLocation(); + return CharSourceRange::getTokenRange(L, L); + } + return typeError(ID, Node.getNodeKind(), + "DeclRefExpr, NamedDecl, CXXCtorInitializer"); + }; +} + +namespace { +// Creates a selector from a range-selection function \p Func, which selects a +// range that is relative to a bound node id. \c T is the node type expected by +// \p Func. +template <typename T, CharSourceRange (*Func)(const MatchResult &, const T &)> +class RelativeSelector { + std::string ID; + +public: + RelativeSelector(std::string ID) : ID(std::move(ID)) {} + + Expected<CharSourceRange> operator()(const MatchResult &Result) { + Expected<DynTypedNode> N = getNode(Result.Nodes, ID); + if (!N) + return N.takeError(); + if (const auto *Arg = N->get<T>()) + return Func(Result, *Arg); + return typeError(ID, N->getNodeKind()); + } +}; +} // namespace + +// FIXME: Change the following functions from being in an anonymous namespace +// to static functions, after the minimum Visual C++ has _MSC_VER >= 1915 +// (equivalent to Visual Studio 2017 v15.8 or higher). Using the anonymous +// namespace works around a bug in earlier versions. +namespace { +// Returns the range of the statements (all source between the braces). +CharSourceRange getStatementsRange(const MatchResult &, + const CompoundStmt &CS) { + return CharSourceRange::getCharRange(CS.getLBracLoc().getLocWithOffset(1), + CS.getRBracLoc()); +} +} // namespace + +RangeSelector tooling::statements(std::string ID) { + return RelativeSelector<CompoundStmt, getStatementsRange>(std::move(ID)); +} + +namespace { +// Returns the range of the source between the call's parentheses. +CharSourceRange getCallArgumentsRange(const MatchResult &Result, + const CallExpr &CE) { + return CharSourceRange::getCharRange( + findOpenParen(CE, *Result.SourceManager, Result.Context->getLangOpts()) + .getLocWithOffset(1), + CE.getRParenLoc()); +} +} // namespace + +RangeSelector tooling::callArgs(std::string ID) { + return RelativeSelector<CallExpr, getCallArgumentsRange>(std::move(ID)); +} + +namespace { +// Returns the range of the elements of the initializer list. Includes all +// source between the braces. +CharSourceRange getElementsRange(const MatchResult &, + const InitListExpr &E) { + return CharSourceRange::getCharRange(E.getLBraceLoc().getLocWithOffset(1), + E.getRBraceLoc()); +} +} // namespace + +RangeSelector tooling::initListElements(std::string ID) { + return RelativeSelector<InitListExpr, getElementsRange>(std::move(ID)); +} + +RangeSelector tooling::expansion(RangeSelector S) { + return [S](const MatchResult &Result) -> Expected<CharSourceRange> { + Expected<CharSourceRange> SRange = S(Result); + if (!SRange) + return SRange.takeError(); + return Result.SourceManager->getExpansionRange(*SRange); + }; +} diff --git a/lib/Tooling/Refactoring/RefactoringActions.cpp b/lib/Tooling/Refactoring/RefactoringActions.cpp index 37a1639cb446..1a3833243ab4 100644 --- a/lib/Tooling/Refactoring/RefactoringActions.cpp +++ b/lib/Tooling/Refactoring/RefactoringActions.cpp @@ -1,9 +1,8 @@ //===--- RefactoringActions.cpp - Constructs refactoring actions ----------===// // -// The LLVM Compiler Infrastructure -// -// This file is distributed under the University of Illinois Open Source -// License. See LICENSE.TXT for details. +// 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 // //===----------------------------------------------------------------------===// diff --git a/lib/Tooling/Refactoring/Rename/RenamingAction.cpp b/lib/Tooling/Refactoring/Rename/RenamingAction.cpp index 44ffae90efa7..1649513a077a 100644 --- a/lib/Tooling/Refactoring/Rename/RenamingAction.cpp +++ b/lib/Tooling/Refactoring/Rename/RenamingAction.cpp @@ -1,9 +1,8 @@ //===--- RenamingAction.cpp - Clang refactoring library -------------------===// // -// The LLVM Compiler Infrastructure -// -// This file is distributed under the University of Illinois Open Source -// License. See LICENSE.TXT for details. +// 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 // //===----------------------------------------------------------------------===// /// @@ -75,6 +74,8 @@ RenameOccurrences::initiate(RefactoringRuleContext &Context, std::move(NewName)); } +const NamedDecl *RenameOccurrences::getRenameDecl() const { return ND; } + Expected<AtomicChanges> RenameOccurrences::createSourceReplacements(RefactoringRuleContext &Context) { Expected<SymbolOccurrences> Occurrences = findSymbolOccurrences(ND, Context); diff --git a/lib/Tooling/Refactoring/Rename/SymbolOccurrences.cpp b/lib/Tooling/Refactoring/Rename/SymbolOccurrences.cpp index ea64b2c1aa8c..8cc1ffaf4482 100644 --- a/lib/Tooling/Refactoring/Rename/SymbolOccurrences.cpp +++ b/lib/Tooling/Refactoring/Rename/SymbolOccurrences.cpp @@ -1,9 +1,8 @@ //===--- SymbolOccurrences.cpp - Clang refactoring library ----------------===// // -// The LLVM Compiler Infrastructure -// -// This file is distributed under the University of Illinois Open Source -// License. See LICENSE.TXT for details. +// 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 // //===----------------------------------------------------------------------===// diff --git a/lib/Tooling/Refactoring/Rename/USRFinder.cpp b/lib/Tooling/Refactoring/Rename/USRFinder.cpp index 4ed805fd504c..55111202ac88 100644 --- a/lib/Tooling/Refactoring/Rename/USRFinder.cpp +++ b/lib/Tooling/Refactoring/Rename/USRFinder.cpp @@ -1,9 +1,8 @@ //===--- USRFinder.cpp - Clang refactoring library ------------------------===// // -// The LLVM Compiler Infrastructure -// -// This file is distributed under the University of Illinois Open Source -// License. See LICENSE.TXT for details. +// 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 // //===----------------------------------------------------------------------===// /// diff --git a/lib/Tooling/Refactoring/Rename/USRFindingAction.cpp b/lib/Tooling/Refactoring/Rename/USRFindingAction.cpp index 2e7c9b0cc31b..54c6f3e734b1 100644 --- a/lib/Tooling/Refactoring/Rename/USRFindingAction.cpp +++ b/lib/Tooling/Refactoring/Rename/USRFindingAction.cpp @@ -1,9 +1,8 @@ //===--- USRFindingAction.cpp - Clang refactoring library -----------------===// // -// The LLVM Compiler Infrastructure -// -// This file is distributed under the University of Illinois Open Source -// License. See LICENSE.TXT for details. +// 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 // //===----------------------------------------------------------------------===// /// diff --git a/lib/Tooling/Refactoring/Rename/USRLocFinder.cpp b/lib/Tooling/Refactoring/Rename/USRLocFinder.cpp index 7f60cf54c8ec..408e184f5bf5 100644 --- a/lib/Tooling/Refactoring/Rename/USRLocFinder.cpp +++ b/lib/Tooling/Refactoring/Rename/USRLocFinder.cpp @@ -1,9 +1,8 @@ //===--- USRLocFinder.cpp - Clang refactoring library ---------------------===// // -// The LLVM Compiler Infrastructure -// -// This file is distributed under the University of Illinois Open Source -// License. See LICENSE.TXT for details. +// 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 // //===----------------------------------------------------------------------===// /// @@ -543,8 +542,8 @@ createRenameAtomicChanges(llvm::ArrayRef<std::string> USRs, if (!llvm::isa<clang::TranslationUnitDecl>( RenameInfo.Context->getDeclContext())) { ReplacedName = tooling::replaceNestedName( - RenameInfo.Specifier, RenameInfo.Context->getDeclContext(), - RenameInfo.FromDecl, + RenameInfo.Specifier, RenameInfo.Begin, + RenameInfo.Context->getDeclContext(), RenameInfo.FromDecl, NewName.startswith("::") ? NewName.str() : ("::" + NewName).str()); } else { diff --git a/lib/Tooling/Refactoring/SourceCode.cpp b/lib/Tooling/Refactoring/SourceCode.cpp new file mode 100644 index 000000000000..3a97e178bbd4 --- /dev/null +++ b/lib/Tooling/Refactoring/SourceCode.cpp @@ -0,0 +1,31 @@ +//===--- SourceCode.cpp - Source code manipulation routines -----*- 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 +// +//===----------------------------------------------------------------------===// +// +// This file provides functions that simplify extraction of source code. +// +//===----------------------------------------------------------------------===// +#include "clang/Tooling/Refactoring/SourceCode.h" +#include "clang/Lex/Lexer.h" + +using namespace clang; + +StringRef clang::tooling::getText(CharSourceRange Range, + const ASTContext &Context) { + return Lexer::getSourceText(Range, Context.getSourceManager(), + Context.getLangOpts()); +} + +CharSourceRange clang::tooling::maybeExtendRange(CharSourceRange Range, + tok::TokenKind Next, + ASTContext &Context) { + Optional<Token> Tok = Lexer::findNextToken( + Range.getEnd(), Context.getSourceManager(), Context.getLangOpts()); + if (!Tok || !Tok->is(Next)) + return Range; + return CharSourceRange::getTokenRange(Range.getBegin(), Tok->getLocation()); +} diff --git a/lib/Tooling/Refactoring/Stencil.cpp b/lib/Tooling/Refactoring/Stencil.cpp new file mode 100644 index 000000000000..09eca21c3cef --- /dev/null +++ b/lib/Tooling/Refactoring/Stencil.cpp @@ -0,0 +1,175 @@ +//===--- Stencil.cpp - Stencil implementation -------------------*- 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/Refactoring/Stencil.h" +#include "clang/AST/ASTContext.h" +#include "clang/AST/ASTTypeTraits.h" +#include "clang/AST/Expr.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/ASTMatchers/ASTMatchers.h" +#include "clang/Lex/Lexer.h" +#include "clang/Tooling/Refactoring/SourceCode.h" +#include "llvm/Support/Errc.h" +#include <atomic> +#include <memory> +#include <string> + +using namespace clang; +using namespace tooling; + +using ast_matchers::MatchFinder; +using llvm::Error; + +// A down_cast function to safely down cast a StencilPartInterface to a subclass +// D. Returns nullptr if P is not an instance of D. +template <typename D> const D *down_cast(const StencilPartInterface *P) { + if (P == nullptr || D::typeId() != P->typeId()) + return nullptr; + return static_cast<const D *>(P); +} + +static llvm::Expected<ast_type_traits::DynTypedNode> +getNode(const ast_matchers::BoundNodes &Nodes, StringRef Id) { + auto &NodesMap = Nodes.getMap(); + auto It = NodesMap.find(Id); + if (It == NodesMap.end()) + return llvm::make_error<llvm::StringError>(llvm::errc::invalid_argument, + "Id not bound: " + Id); + return It->second; +} + +namespace { +// An arbitrary fragment of code within a stencil. +struct RawTextData { + explicit RawTextData(std::string T) : Text(std::move(T)) {} + std::string Text; +}; + +// A debugging operation to dump the AST for a particular (bound) AST node. +struct DebugPrintNodeOpData { + explicit DebugPrintNodeOpData(std::string S) : Id(std::move(S)) {} + std::string Id; +}; + +// The fragment of code corresponding to the selected range. +struct SelectorOpData { + explicit SelectorOpData(RangeSelector S) : Selector(std::move(S)) {} + RangeSelector Selector; +}; +} // namespace + +bool isEqualData(const RawTextData &A, const RawTextData &B) { + return A.Text == B.Text; +} + +bool isEqualData(const DebugPrintNodeOpData &A, const DebugPrintNodeOpData &B) { + return A.Id == B.Id; +} + +// Equality is not (yet) defined for \c RangeSelector. +bool isEqualData(const SelectorOpData &, const SelectorOpData &) { return false; } + +// The `evalData()` overloads evaluate the given stencil data to a string, given +// the match result, and append it to `Result`. We define an overload for each +// type of stencil data. + +Error evalData(const RawTextData &Data, const MatchFinder::MatchResult &, + std::string *Result) { + Result->append(Data.Text); + return Error::success(); +} + +Error evalData(const DebugPrintNodeOpData &Data, + const MatchFinder::MatchResult &Match, std::string *Result) { + std::string Output; + llvm::raw_string_ostream Os(Output); + auto NodeOrErr = getNode(Match.Nodes, Data.Id); + if (auto Err = NodeOrErr.takeError()) + return Err; + NodeOrErr->print(Os, PrintingPolicy(Match.Context->getLangOpts())); + *Result += Os.str(); + return Error::success(); +} + +Error evalData(const SelectorOpData &Data, const MatchFinder::MatchResult &Match, + std::string *Result) { + auto Range = Data.Selector(Match); + if (!Range) + return Range.takeError(); + *Result += getText(*Range, *Match.Context); + return Error::success(); +} + +template <typename T> +class StencilPartImpl : public StencilPartInterface { + T Data; + +public: + template <typename... Ps> + explicit StencilPartImpl(Ps &&... Args) + : StencilPartInterface(StencilPartImpl::typeId()), + Data(std::forward<Ps>(Args)...) {} + + // Generates a unique identifier for this class (specifically, one per + // instantiation of the template). + static const void* typeId() { + static bool b; + return &b; + } + + Error eval(const MatchFinder::MatchResult &Match, + std::string *Result) const override { + return evalData(Data, Match, Result); + } + + bool isEqual(const StencilPartInterface &Other) const override { + if (const auto *OtherPtr = down_cast<StencilPartImpl>(&Other)) + return isEqualData(Data, OtherPtr->Data); + return false; + } +}; + +namespace { +using RawText = StencilPartImpl<RawTextData>; +using DebugPrintNodeOp = StencilPartImpl<DebugPrintNodeOpData>; +using SelectorOp = StencilPartImpl<SelectorOpData>; +} // namespace + +StencilPart Stencil::wrap(StringRef Text) { + return stencil::text(Text); +} + +StencilPart Stencil::wrap(RangeSelector Selector) { + return stencil::selection(std::move(Selector)); +} + +void Stencil::append(Stencil OtherStencil) { + for (auto &Part : OtherStencil.Parts) + Parts.push_back(std::move(Part)); +} + +llvm::Expected<std::string> +Stencil::eval(const MatchFinder::MatchResult &Match) const { + std::string Result; + for (const auto &Part : Parts) + if (auto Err = Part.eval(Match, &Result)) + return std::move(Err); + return Result; +} + +StencilPart stencil::text(StringRef Text) { + return StencilPart(std::make_shared<RawText>(Text)); +} + +StencilPart stencil::selection(RangeSelector Selector) { + return StencilPart(std::make_shared<SelectorOp>(std::move(Selector))); +} + +StencilPart stencil::dPrint(StringRef Id) { + return StencilPart(std::make_shared<DebugPrintNodeOp>(Id)); +} diff --git a/lib/Tooling/Refactoring/Transformer.cpp b/lib/Tooling/Refactoring/Transformer.cpp new file mode 100644 index 000000000000..8e6fe6c7a940 --- /dev/null +++ b/lib/Tooling/Refactoring/Transformer.cpp @@ -0,0 +1,263 @@ +//===--- Transformer.cpp - Transformer library implementation ---*- 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/Refactoring/Transformer.h" +#include "clang/AST/Expr.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/ASTMatchers/ASTMatchers.h" +#include "clang/Basic/Diagnostic.h" +#include "clang/Basic/SourceLocation.h" +#include "clang/Rewrite/Core/Rewriter.h" +#include "clang/Tooling/Refactoring/AtomicChange.h" +#include "clang/Tooling/Refactoring/SourceCode.h" +#include "llvm/ADT/Optional.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/Support/Errc.h" +#include "llvm/Support/Error.h" +#include <deque> +#include <string> +#include <utility> +#include <vector> + +using namespace clang; +using namespace tooling; + +using ast_matchers::MatchFinder; +using ast_matchers::internal::DynTypedMatcher; +using ast_type_traits::ASTNodeKind; +using ast_type_traits::DynTypedNode; +using llvm::Error; +using llvm::StringError; + +using MatchResult = MatchFinder::MatchResult; + +// Did the text at this location originate in a macro definition (aka. body)? +// For example, +// +// #define NESTED(x) x +// #define MACRO(y) { int y = NESTED(3); } +// if (true) MACRO(foo) +// +// The if statement expands to +// +// if (true) { int foo = 3; } +// ^ ^ +// Loc1 Loc2 +// +// For SourceManager SM, SM.isMacroArgExpansion(Loc1) and +// SM.isMacroArgExpansion(Loc2) are both true, but isOriginMacroBody(sm, Loc1) +// is false, because "foo" originated in the source file (as an argument to a +// macro), whereas isOriginMacroBody(SM, Loc2) is true, because "3" originated +// in the definition of MACRO. +static bool isOriginMacroBody(const clang::SourceManager &SM, + clang::SourceLocation Loc) { + while (Loc.isMacroID()) { + if (SM.isMacroBodyExpansion(Loc)) + return true; + // Otherwise, it must be in an argument, so we continue searching up the + // invocation stack. getImmediateMacroCallerLoc() gives the location of the + // argument text, inside the call text. + Loc = SM.getImmediateMacroCallerLoc(Loc); + } + return false; +} + +Expected<SmallVector<tooling::detail::Transformation, 1>> +tooling::detail::translateEdits(const MatchResult &Result, + llvm::ArrayRef<ASTEdit> Edits) { + SmallVector<tooling::detail::Transformation, 1> Transformations; + for (const auto &Edit : Edits) { + Expected<CharSourceRange> Range = Edit.TargetRange(Result); + if (!Range) + return Range.takeError(); + if (Range->isInvalid() || + isOriginMacroBody(*Result.SourceManager, Range->getBegin())) + return SmallVector<Transformation, 0>(); + auto Replacement = Edit.Replacement(Result); + if (!Replacement) + return Replacement.takeError(); + tooling::detail::Transformation T; + T.Range = *Range; + T.Replacement = std::move(*Replacement); + Transformations.push_back(std::move(T)); + } + return Transformations; +} + +ASTEdit tooling::change(RangeSelector S, TextGenerator Replacement) { + ASTEdit E; + E.TargetRange = std::move(S); + E.Replacement = std::move(Replacement); + return E; +} + +RewriteRule tooling::makeRule(DynTypedMatcher M, SmallVector<ASTEdit, 1> Edits, + TextGenerator Explanation) { + return RewriteRule{{RewriteRule::Case{ + std::move(M), std::move(Edits), std::move(Explanation), {}}}}; +} + +void tooling::addInclude(RewriteRule &Rule, StringRef Header, + IncludeFormat Format) { + for (auto &Case : Rule.Cases) + Case.AddedIncludes.emplace_back(Header.str(), Format); +} + +// Determines whether A is a base type of B in the class hierarchy, including +// the implicit relationship of Type and QualType. +static bool isBaseOf(ASTNodeKind A, ASTNodeKind B) { + static auto TypeKind = ASTNodeKind::getFromNodeKind<Type>(); + static auto QualKind = ASTNodeKind::getFromNodeKind<QualType>(); + /// Mimic the implicit conversions of Matcher<>. + /// - From Matcher<Type> to Matcher<QualType> + /// - From Matcher<Base> to Matcher<Derived> + return (A.isSame(TypeKind) && B.isSame(QualKind)) || A.isBaseOf(B); +} + +// Try to find a common kind to which all of the rule's matchers can be +// converted. +static ASTNodeKind +findCommonKind(const SmallVectorImpl<RewriteRule::Case> &Cases) { + assert(!Cases.empty() && "Rule must have at least one case."); + ASTNodeKind JoinKind = Cases[0].Matcher.getSupportedKind(); + // Find a (least) Kind K, for which M.canConvertTo(K) holds, for all matchers + // M in Rules. + for (const auto &Case : Cases) { + auto K = Case.Matcher.getSupportedKind(); + if (isBaseOf(JoinKind, K)) { + JoinKind = K; + continue; + } + if (K.isSame(JoinKind) || isBaseOf(K, JoinKind)) + // JoinKind is already the lowest. + continue; + // K and JoinKind are unrelated -- there is no least common kind. + return ASTNodeKind(); + } + return JoinKind; +} + +// Binds each rule's matcher to a unique (and deterministic) tag based on +// `TagBase`. +static std::vector<DynTypedMatcher> +taggedMatchers(StringRef TagBase, + const SmallVectorImpl<RewriteRule::Case> &Cases) { + std::vector<DynTypedMatcher> Matchers; + Matchers.reserve(Cases.size()); + size_t count = 0; + for (const auto &Case : Cases) { + std::string Tag = (TagBase + Twine(count)).str(); + ++count; + auto M = Case.Matcher.tryBind(Tag); + assert(M && "RewriteRule matchers should be bindable."); + Matchers.push_back(*std::move(M)); + } + return Matchers; +} + +// Simply gathers the contents of the various rules into a single rule. The +// actual work to combine these into an ordered choice is deferred to matcher +// registration. +RewriteRule tooling::applyFirst(ArrayRef<RewriteRule> Rules) { + RewriteRule R; + for (auto &Rule : Rules) + R.Cases.append(Rule.Cases.begin(), Rule.Cases.end()); + return R; +} + +static DynTypedMatcher joinCaseMatchers(const RewriteRule &Rule) { + assert(!Rule.Cases.empty() && "Rule must have at least one case."); + if (Rule.Cases.size() == 1) + return Rule.Cases[0].Matcher; + + auto CommonKind = findCommonKind(Rule.Cases); + assert(!CommonKind.isNone() && "Cases must have compatible matchers."); + return DynTypedMatcher::constructVariadic( + DynTypedMatcher::VO_AnyOf, CommonKind, taggedMatchers("Tag", Rule.Cases)); +} + +DynTypedMatcher tooling::detail::buildMatcher(const RewriteRule &Rule) { + DynTypedMatcher M = joinCaseMatchers(Rule); + M.setAllowBind(true); + // `tryBind` is guaranteed to succeed, because `AllowBind` was set to true. + return *M.tryBind(RewriteRule::RootID); +} + +// Finds the case that was "selected" -- that is, whose matcher triggered the +// `MatchResult`. +const RewriteRule::Case & +tooling::detail::findSelectedCase(const MatchResult &Result, + const RewriteRule &Rule) { + if (Rule.Cases.size() == 1) + return Rule.Cases[0]; + + auto &NodesMap = Result.Nodes.getMap(); + for (size_t i = 0, N = Rule.Cases.size(); i < N; ++i) { + std::string Tag = ("Tag" + Twine(i)).str(); + if (NodesMap.find(Tag) != NodesMap.end()) + return Rule.Cases[i]; + } + llvm_unreachable("No tag found for this rule."); +} + +constexpr llvm::StringLiteral RewriteRule::RootID; + +void Transformer::registerMatchers(MatchFinder *MatchFinder) { + MatchFinder->addDynamicMatcher(tooling::detail::buildMatcher(Rule), this); +} + +void Transformer::run(const MatchResult &Result) { + if (Result.Context->getDiagnostics().hasErrorOccurred()) + return; + + // Verify the existence and validity of the AST node that roots this rule. + auto &NodesMap = Result.Nodes.getMap(); + auto Root = NodesMap.find(RewriteRule::RootID); + assert(Root != NodesMap.end() && "Transformation failed: missing root node."); + SourceLocation RootLoc = Result.SourceManager->getExpansionLoc( + Root->second.getSourceRange().getBegin()); + assert(RootLoc.isValid() && "Invalid location for Root node of match."); + + RewriteRule::Case Case = tooling::detail::findSelectedCase(Result, Rule); + auto Transformations = tooling::detail::translateEdits(Result, Case.Edits); + if (!Transformations) { + Consumer(Transformations.takeError()); + return; + } + + if (Transformations->empty()) { + // No rewrite applied (but no error encountered either). + RootLoc.print(llvm::errs() << "note: skipping match at loc ", + *Result.SourceManager); + llvm::errs() << "\n"; + return; + } + + // Record the results in the AtomicChange. + AtomicChange AC(*Result.SourceManager, RootLoc); + for (const auto &T : *Transformations) { + if (auto Err = AC.replace(*Result.SourceManager, T.Range, T.Replacement)) { + Consumer(std::move(Err)); + return; + } + } + + for (const auto &I : Case.AddedIncludes) { + auto &Header = I.first; + switch (I.second) { + case IncludeFormat::Quoted: + AC.addHeader(Header); + break; + case IncludeFormat::Angled: + AC.addHeader((llvm::Twine("<") + Header + ">").str()); + break; + } + } + + Consumer(std::move(AC)); +} diff --git a/lib/Tooling/RefactoringCallbacks.cpp b/lib/Tooling/RefactoringCallbacks.cpp index 9fd333ca554e..2aa5b5bf9d98 100644 --- a/lib/Tooling/RefactoringCallbacks.cpp +++ b/lib/Tooling/RefactoringCallbacks.cpp @@ -1,9 +1,8 @@ //===--- RefactoringCallbacks.cpp - Structural query framework ------------===// // -// The LLVM Compiler Infrastructure -// -// This file is distributed under the University of Illinois Open Source -// License. See LICENSE.TXT for details. +// 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 // //===----------------------------------------------------------------------===// // diff --git a/lib/Tooling/StandaloneExecution.cpp b/lib/Tooling/StandaloneExecution.cpp index 1daf792fb86f..ad82ee083a40 100644 --- a/lib/Tooling/StandaloneExecution.cpp +++ b/lib/Tooling/StandaloneExecution.cpp @@ -1,9 +1,8 @@ //===- lib/Tooling/Execution.cpp - Standalone clang action execution. -----===// // -// The LLVM Compiler Infrastructure -// -// This file is distributed under the University of Illinois Open Source -// License. See LICENSE.TXT for details. +// 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 // //===----------------------------------------------------------------------===// diff --git a/lib/Tooling/Syntax/BuildTree.cpp b/lib/Tooling/Syntax/BuildTree.cpp new file mode 100644 index 000000000000..03c439c59e39 --- /dev/null +++ b/lib/Tooling/Syntax/BuildTree.cpp @@ -0,0 +1,273 @@ +//===- BuildTree.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/Syntax/BuildTree.h" +#include "clang/AST/RecursiveASTVisitor.h" +#include "clang/AST/Stmt.h" +#include "clang/Basic/LLVM.h" +#include "clang/Basic/SourceLocation.h" +#include "clang/Basic/SourceManager.h" +#include "clang/Basic/TokenKinds.h" +#include "clang/Lex/Lexer.h" +#include "clang/Tooling/Syntax/Nodes.h" +#include "clang/Tooling/Syntax/Tokens.h" +#include "clang/Tooling/Syntax/Tree.h" +#include "llvm/ADT/ArrayRef.h" +#include "llvm/ADT/STLExtras.h" +#include "llvm/ADT/SmallVector.h" +#include "llvm/Support/Allocator.h" +#include "llvm/Support/Casting.h" +#include "llvm/Support/FormatVariadic.h" +#include "llvm/Support/raw_ostream.h" +#include <map> + +using namespace clang; + +/// A helper class for constructing the syntax tree while traversing a clang +/// AST. +/// +/// At each point of the traversal we maintain a list of pending nodes. +/// Initially all tokens are added as pending nodes. When processing a clang AST +/// node, the clients need to: +/// - create a corresponding syntax node, +/// - assign roles to all pending child nodes with 'markChild' and +/// 'markChildToken', +/// - replace the child nodes with the new syntax node in the pending list +/// with 'foldNode'. +/// +/// Note that all children are expected to be processed when building a node. +/// +/// Call finalize() to finish building the tree and consume the root node. +class syntax::TreeBuilder { +public: + TreeBuilder(syntax::Arena &Arena) : Arena(Arena), Pending(Arena) {} + + llvm::BumpPtrAllocator &allocator() { return Arena.allocator(); } + + /// Populate children for \p New node, assuming it covers tokens from \p + /// Range. + void foldNode(llvm::ArrayRef<syntax::Token> Range, syntax::Tree *New); + + /// Set role for a token starting at \p Loc. + void markChildToken(SourceLocation Loc, tok::TokenKind Kind, NodeRole R); + + /// Finish building the tree and consume the root node. + syntax::TranslationUnit *finalize() && { + auto Tokens = Arena.tokenBuffer().expandedTokens(); + // Build the root of the tree, consuming all the children. + Pending.foldChildren(Tokens, + new (Arena.allocator()) syntax::TranslationUnit); + + return cast<syntax::TranslationUnit>(std::move(Pending).finalize()); + } + + /// getRange() finds the syntax tokens corresponding to the passed source + /// locations. + /// \p First is the start position of the first token and \p Last is the start + /// position of the last token. + llvm::ArrayRef<syntax::Token> getRange(SourceLocation First, + SourceLocation Last) const { + assert(First.isValid()); + assert(Last.isValid()); + assert(First == Last || + Arena.sourceManager().isBeforeInTranslationUnit(First, Last)); + return llvm::makeArrayRef(findToken(First), std::next(findToken(Last))); + } + llvm::ArrayRef<syntax::Token> getRange(const Decl *D) const { + return getRange(D->getBeginLoc(), D->getEndLoc()); + } + llvm::ArrayRef<syntax::Token> getRange(const Stmt *S) const { + return getRange(S->getBeginLoc(), S->getEndLoc()); + } + +private: + /// Finds a token starting at \p L. The token must exist. + const syntax::Token *findToken(SourceLocation L) const; + + /// A collection of trees covering the input tokens. + /// When created, each tree corresponds to a single token in the file. + /// Clients call 'foldChildren' to attach one or more subtrees to a parent + /// node and update the list of trees accordingly. + /// + /// Ensures that added nodes properly nest and cover the whole token stream. + struct Forest { + Forest(syntax::Arena &A) { + // FIXME: do not add 'eof' to the tree. + + // Create all leaf nodes. + for (auto &T : A.tokenBuffer().expandedTokens()) + Trees.insert(Trees.end(), + {&T, NodeAndRole{new (A.allocator()) syntax::Leaf(&T)}}); + } + + void assignRole(llvm::ArrayRef<syntax::Token> Range, + syntax::NodeRole Role) { + assert(!Range.empty()); + auto It = Trees.lower_bound(Range.begin()); + assert(It != Trees.end() && "no node found"); + assert(It->first == Range.begin() && "no child with the specified range"); + assert((std::next(It) == Trees.end() || + std::next(It)->first == Range.end()) && + "no child with the specified range"); + It->second.Role = Role; + } + + /// Add \p Node to the forest and fill its children nodes based on the \p + /// NodeRange. + void foldChildren(llvm::ArrayRef<syntax::Token> NodeTokens, + syntax::Tree *Node) { + assert(!NodeTokens.empty()); + assert(Node->firstChild() == nullptr && "node already has children"); + + auto *FirstToken = NodeTokens.begin(); + auto BeginChildren = Trees.lower_bound(FirstToken); + assert(BeginChildren != Trees.end() && + BeginChildren->first == FirstToken && + "fold crosses boundaries of existing subtrees"); + auto EndChildren = Trees.lower_bound(NodeTokens.end()); + assert((EndChildren == Trees.end() || + EndChildren->first == NodeTokens.end()) && + "fold crosses boundaries of existing subtrees"); + + // (!) we need to go in reverse order, because we can only prepend. + for (auto It = EndChildren; It != BeginChildren; --It) + Node->prependChildLowLevel(std::prev(It)->second.Node, + std::prev(It)->second.Role); + + Trees.erase(BeginChildren, EndChildren); + Trees.insert({FirstToken, NodeAndRole(Node)}); + } + + // EXPECTS: all tokens were consumed and are owned by a single root node. + syntax::Node *finalize() && { + assert(Trees.size() == 1); + auto *Root = Trees.begin()->second.Node; + Trees = {}; + return Root; + } + + std::string str(const syntax::Arena &A) const { + std::string R; + for (auto It = Trees.begin(); It != Trees.end(); ++It) { + unsigned CoveredTokens = + It != Trees.end() + ? (std::next(It)->first - It->first) + : A.tokenBuffer().expandedTokens().end() - It->first; + + R += llvm::formatv("- '{0}' covers '{1}'+{2} tokens\n", + It->second.Node->kind(), + It->first->text(A.sourceManager()), CoveredTokens); + R += It->second.Node->dump(A); + } + return R; + } + + private: + /// A with a role that should be assigned to it when adding to a parent. + struct NodeAndRole { + explicit NodeAndRole(syntax::Node *Node) + : Node(Node), Role(NodeRole::Unknown) {} + + syntax::Node *Node; + NodeRole Role; + }; + + /// Maps from the start token to a subtree starting at that token. + /// FIXME: storing the end tokens is redundant. + /// FIXME: the key of a map is redundant, it is also stored in NodeForRange. + std::map<const syntax::Token *, NodeAndRole> Trees; + }; + + /// For debugging purposes. + std::string str() { return Pending.str(Arena); } + + syntax::Arena &Arena; + Forest Pending; +}; + +namespace { +class BuildTreeVisitor : public RecursiveASTVisitor<BuildTreeVisitor> { +public: + explicit BuildTreeVisitor(ASTContext &Ctx, syntax::TreeBuilder &Builder) + : Builder(Builder), LangOpts(Ctx.getLangOpts()) {} + + bool shouldTraversePostOrder() const { return true; } + + bool TraverseDecl(Decl *D) { + if (!D || isa<TranslationUnitDecl>(D)) + return RecursiveASTVisitor::TraverseDecl(D); + if (!llvm::isa<TranslationUnitDecl>(D->getDeclContext())) + return true; // Only build top-level decls for now, do not recurse. + return RecursiveASTVisitor::TraverseDecl(D); + } + + bool VisitDecl(Decl *D) { + assert(llvm::isa<TranslationUnitDecl>(D->getDeclContext()) && + "expected a top-level decl"); + assert(!D->isImplicit()); + Builder.foldNode(Builder.getRange(D), + new (allocator()) syntax::TopLevelDeclaration()); + return true; + } + + bool WalkUpFromTranslationUnitDecl(TranslationUnitDecl *TU) { + // (!) we do not want to call VisitDecl(), the declaration for translation + // unit is built by finalize(). + return true; + } + + bool WalkUpFromCompoundStmt(CompoundStmt *S) { + using NodeRole = syntax::NodeRole; + + Builder.markChildToken(S->getLBracLoc(), tok::l_brace, + NodeRole::CompoundStatement_lbrace); + Builder.markChildToken(S->getRBracLoc(), tok::r_brace, + NodeRole::CompoundStatement_rbrace); + + Builder.foldNode(Builder.getRange(S), + new (allocator()) syntax::CompoundStatement); + return true; + } + +private: + /// A small helper to save some typing. + llvm::BumpPtrAllocator &allocator() { return Builder.allocator(); } + + syntax::TreeBuilder &Builder; + const LangOptions &LangOpts; +}; +} // namespace + +void syntax::TreeBuilder::foldNode(llvm::ArrayRef<syntax::Token> Range, + syntax::Tree *New) { + Pending.foldChildren(Range, New); +} + +void syntax::TreeBuilder::markChildToken(SourceLocation Loc, + tok::TokenKind Kind, NodeRole Role) { + if (Loc.isInvalid()) + return; + Pending.assignRole(*findToken(Loc), Role); +} + +const syntax::Token *syntax::TreeBuilder::findToken(SourceLocation L) const { + auto Tokens = Arena.tokenBuffer().expandedTokens(); + auto &SM = Arena.sourceManager(); + auto It = llvm::partition_point(Tokens, [&](const syntax::Token &T) { + return SM.isBeforeInTranslationUnit(T.location(), L); + }); + assert(It != Tokens.end()); + assert(It->location() == L); + return &*It; +} + +syntax::TranslationUnit * +syntax::buildSyntaxTree(Arena &A, const TranslationUnitDecl &TU) { + TreeBuilder Builder(A); + BuildTreeVisitor(TU.getASTContext(), Builder).TraverseAST(TU.getASTContext()); + return std::move(Builder).finalize(); +} diff --git a/lib/Tooling/Syntax/Nodes.cpp b/lib/Tooling/Syntax/Nodes.cpp new file mode 100644 index 000000000000..061ed73bbebd --- /dev/null +++ b/lib/Tooling/Syntax/Nodes.cpp @@ -0,0 +1,35 @@ +//===- Nodes.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/Syntax/Nodes.h" +#include "clang/Basic/TokenKinds.h" + +using namespace clang; + +llvm::raw_ostream &syntax::operator<<(llvm::raw_ostream &OS, NodeKind K) { + switch (K) { + case NodeKind::Leaf: + return OS << "Leaf"; + case NodeKind::TranslationUnit: + return OS << "TranslationUnit"; + case NodeKind::TopLevelDeclaration: + return OS << "TopLevelDeclaration"; + case NodeKind::CompoundStatement: + return OS << "CompoundStatement"; + } + llvm_unreachable("unknown node kind"); +} + +syntax::Leaf *syntax::CompoundStatement::lbrace() { + return llvm::cast_or_null<syntax::Leaf>( + findChild(NodeRole::CompoundStatement_lbrace)); +} + +syntax::Leaf *syntax::CompoundStatement::rbrace() { + return llvm::cast_or_null<syntax::Leaf>( + findChild(NodeRole::CompoundStatement_rbrace)); +} diff --git a/lib/Tooling/Syntax/Tokens.cpp b/lib/Tooling/Syntax/Tokens.cpp new file mode 100644 index 000000000000..d82dc1f35c94 --- /dev/null +++ b/lib/Tooling/Syntax/Tokens.cpp @@ -0,0 +1,618 @@ +//===- Tokens.cpp - collect tokens from preprocessing ---------------------===// +// +// 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/Syntax/Tokens.h" + +#include "clang/Basic/Diagnostic.h" +#include "clang/Basic/IdentifierTable.h" +#include "clang/Basic/LLVM.h" +#include "clang/Basic/LangOptions.h" +#include "clang/Basic/SourceLocation.h" +#include "clang/Basic/SourceManager.h" +#include "clang/Basic/TokenKinds.h" +#include "clang/Lex/PPCallbacks.h" +#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" +#include "llvm/Support/FormatVariadic.h" +#include "llvm/Support/raw_ostream.h" +#include <algorithm> +#include <cassert> +#include <iterator> +#include <string> +#include <utility> +#include <vector> + +using namespace clang; +using namespace clang::syntax; + +syntax::Token::Token(SourceLocation Location, unsigned Length, + tok::TokenKind Kind) + : Location(Location), Length(Length), Kind(Kind) { + assert(Location.isValid()); +} + +syntax::Token::Token(const clang::Token &T) + : Token(T.getLocation(), T.getLength(), T.getKind()) { + assert(!T.isAnnotation()); +} + +llvm::StringRef syntax::Token::text(const SourceManager &SM) const { + bool Invalid = false; + const char *Start = SM.getCharacterData(location(), &Invalid); + assert(!Invalid); + return llvm::StringRef(Start, length()); +} + +FileRange syntax::Token::range(const SourceManager &SM) const { + assert(location().isFileID() && "must be a spelled token"); + FileID File; + unsigned StartOffset; + std::tie(File, StartOffset) = SM.getDecomposedLoc(location()); + return FileRange(File, StartOffset, StartOffset + length()); +} + +FileRange syntax::Token::range(const SourceManager &SM, + const syntax::Token &First, + const syntax::Token &Last) { + auto F = First.range(SM); + auto L = Last.range(SM); + assert(F.file() == L.file() && "tokens from different files"); + assert(F.endOffset() <= L.beginOffset() && "wrong order of tokens"); + return FileRange(F.file(), F.beginOffset(), L.endOffset()); +} + +llvm::raw_ostream &syntax::operator<<(llvm::raw_ostream &OS, const Token &T) { + return OS << T.str(); +} + +FileRange::FileRange(FileID File, unsigned BeginOffset, unsigned EndOffset) + : File(File), Begin(BeginOffset), End(EndOffset) { + assert(File.isValid()); + assert(BeginOffset <= EndOffset); +} + +FileRange::FileRange(const SourceManager &SM, SourceLocation BeginLoc, + unsigned Length) { + assert(BeginLoc.isValid()); + assert(BeginLoc.isFileID()); + + std::tie(File, Begin) = SM.getDecomposedLoc(BeginLoc); + End = Begin + Length; +} +FileRange::FileRange(const SourceManager &SM, SourceLocation BeginLoc, + SourceLocation EndLoc) { + assert(BeginLoc.isValid()); + assert(BeginLoc.isFileID()); + assert(EndLoc.isValid()); + assert(EndLoc.isFileID()); + assert(SM.getFileID(BeginLoc) == SM.getFileID(EndLoc)); + assert(SM.getFileOffset(BeginLoc) <= SM.getFileOffset(EndLoc)); + + std::tie(File, Begin) = SM.getDecomposedLoc(BeginLoc); + End = SM.getFileOffset(EndLoc); +} + +llvm::raw_ostream &syntax::operator<<(llvm::raw_ostream &OS, + const FileRange &R) { + return OS << llvm::formatv("FileRange(file = {0}, offsets = {1}-{2})", + R.file().getHashValue(), R.beginOffset(), + R.endOffset()); +} + +llvm::StringRef FileRange::text(const SourceManager &SM) const { + bool Invalid = false; + StringRef Text = SM.getBufferData(File, &Invalid); + if (Invalid) + return ""; + assert(Begin <= Text.size()); + assert(End <= Text.size()); + return Text.substr(Begin, length()); +} + +std::pair<const syntax::Token *, const TokenBuffer::Mapping *> +TokenBuffer::spelledForExpandedToken(const syntax::Token *Expanded) const { + assert(Expanded); + assert(ExpandedTokens.data() <= Expanded && + Expanded < ExpandedTokens.data() + ExpandedTokens.size()); + + auto FileIt = Files.find( + SourceMgr->getFileID(SourceMgr->getExpansionLoc(Expanded->location()))); + assert(FileIt != Files.end() && "no file for an expanded token"); + + const MarkedFile &File = FileIt->second; + + unsigned ExpandedIndex = Expanded - ExpandedTokens.data(); + // Find the first mapping that produced tokens after \p Expanded. + auto It = llvm::partition_point(File.Mappings, [&](const Mapping &M) { + return M.BeginExpanded <= ExpandedIndex; + }); + // Our token could only be produced by the previous mapping. + if (It == File.Mappings.begin()) { + // No previous mapping, no need to modify offsets. + return {&File.SpelledTokens[ExpandedIndex - File.BeginExpanded], nullptr}; + } + --It; // 'It' now points to last mapping that started before our token. + + // Check if the token is part of the mapping. + if (ExpandedIndex < It->EndExpanded) + return {&File.SpelledTokens[It->BeginSpelled], /*Mapping*/ &*It}; + + // Not part of the mapping, use the index from previous mapping to compute the + // corresponding spelled token. + return { + &File.SpelledTokens[It->EndSpelled + (ExpandedIndex - It->EndExpanded)], + /*Mapping*/ nullptr}; +} + +llvm::ArrayRef<syntax::Token> TokenBuffer::spelledTokens(FileID FID) const { + auto It = Files.find(FID); + assert(It != Files.end()); + return It->second.SpelledTokens; +} + +std::string TokenBuffer::Mapping::str() const { + return llvm::formatv("spelled tokens: [{0},{1}), expanded tokens: [{2},{3})", + BeginSpelled, EndSpelled, BeginExpanded, EndExpanded); +} + +llvm::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; + + // FIXME: also allow changes uniquely mapping to macro arguments. + + 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()); + + FileID FID = SourceMgr->getFileID(BeginSpelled->location()); + // FIXME: Handle multi-file changes by trying to map onto a common root. + if (FID != SourceMgr->getFileID(LastSpelled->location())) + return llvm::None; + + const MarkedFile &File = Files.find(FID)->second; + + // Do not allow changes that cross macro expansion boundaries. + unsigned BeginExpanded = Expanded.begin() - ExpandedTokens.data(); + unsigned EndExpanded = Expanded.end() - ExpandedTokens.data(); + if (BeginMapping && BeginMapping->BeginExpanded < BeginExpanded) + return llvm::None; + if (LastMapping && EndExpanded < LastMapping->EndExpanded) + return llvm::None; + // All is good, return the result. + return llvm::makeArrayRef( + BeginMapping ? File.SpelledTokens.data() + BeginMapping->BeginSpelled + : BeginSpelled, + LastMapping ? File.SpelledTokens.data() + LastMapping->EndSpelled + : LastSpelled + 1); +} + +llvm::Optional<TokenBuffer::Expansion> +TokenBuffer::expansionStartingAt(const syntax::Token *Spelled) const { + assert(Spelled); + assert(Spelled->location().isFileID() && "not a spelled token"); + auto FileIt = Files.find(SourceMgr->getFileID(Spelled->location())); + assert(FileIt != Files.end() && "file not tracked by token buffer"); + + auto &File = FileIt->second; + assert(File.SpelledTokens.data() <= Spelled && + Spelled < (File.SpelledTokens.data() + File.SpelledTokens.size())); + + unsigned SpelledIndex = Spelled - File.SpelledTokens.data(); + auto M = llvm::partition_point(File.Mappings, [&](const Mapping &M) { + return M.BeginSpelled < SpelledIndex; + }); + if (M == File.Mappings.end() || M->BeginSpelled != SpelledIndex) + return llvm::None; + + Expansion E; + E.Spelled = llvm::makeArrayRef(File.SpelledTokens.data() + M->BeginSpelled, + File.SpelledTokens.data() + M->EndSpelled); + E.Expanded = llvm::makeArrayRef(ExpandedTokens.data() + M->BeginExpanded, + ExpandedTokens.data() + M->EndExpanded); + return E; +} + +std::vector<syntax::Token> syntax::tokenize(FileID FID, const SourceManager &SM, + const LangOptions &LO) { + std::vector<syntax::Token> Tokens; + IdentifierTable Identifiers(LO); + auto AddToken = [&](clang::Token T) { + // Fill the proper token kind for keywords, etc. + if (T.getKind() == tok::raw_identifier && !T.needsCleaning() && + !T.hasUCN()) { // FIXME: support needsCleaning and hasUCN cases. + clang::IdentifierInfo &II = Identifiers.get(T.getRawIdentifier()); + T.setIdentifierInfo(&II); + T.setKind(II.getTokenID()); + } + Tokens.push_back(syntax::Token(T)); + }; + + Lexer L(FID, SM.getBuffer(FID), SM, LO); + + clang::Token T; + while (!L.LexFromRawLexer(T)) + AddToken(T); + // 'eof' is only the last token if the input is null-terminated. Never store + // it, for consistency. + if (T.getKind() != tok::eof) + AddToken(T); + return Tokens; +} + +/// Records information reqired to construct mappings for the token buffer that +/// we are collecting. +class TokenCollector::CollectPPExpansions : public PPCallbacks { +public: + CollectPPExpansions(TokenCollector &C) : Collector(&C) {} + + /// Disabled instance will stop reporting anything to TokenCollector. + /// This ensures that uses of the preprocessor after TokenCollector::consume() + /// is called do not access the (possibly invalid) collector instance. + void disable() { Collector = nullptr; } + + void MacroExpands(const clang::Token &MacroNameTok, const MacroDefinition &MD, + SourceRange Range, const MacroArgs *Args) override { + if (!Collector) + return; + // Only record top-level expansions, not those where: + // - the macro use is inside a macro body, + // - the macro appears in an argument to another macro. + if (!MacroNameTok.getLocation().isFileID() || + (LastExpansionEnd.isValid() && + Collector->PP.getSourceManager().isBeforeInTranslationUnit( + Range.getBegin(), LastExpansionEnd))) + return; + Collector->Expansions[Range.getBegin().getRawEncoding()] = Range.getEnd(); + LastExpansionEnd = Range.getEnd(); + } + // FIXME: handle directives like #pragma, #include, etc. +private: + TokenCollector *Collector; + /// Used to detect recursive macro expansions. + SourceLocation LastExpansionEnd; +}; + +/// Fills in the TokenBuffer by tracing the run of a preprocessor. The +/// implementation tracks the tokens, macro expansions and directives coming +/// from the preprocessor and: +/// - for each token, figures out if it is a part of an expanded token stream, +/// spelled token stream or both. Stores the tokens appropriately. +/// - records mappings from the spelled to expanded token ranges, e.g. for macro +/// expansions. +/// FIXME: also properly record: +/// - #include directives, +/// - #pragma, #line and other PP directives, +/// - skipped pp regions, +/// - ... + +TokenCollector::TokenCollector(Preprocessor &PP) : PP(PP) { + // Collect the expanded token stream during preprocessing. + PP.setTokenWatcher([this](const clang::Token &T) { + if (T.isAnnotation()) + return; + DEBUG_WITH_TYPE("collect-tokens", llvm::dbgs() + << "Token: " + << syntax::Token(T).dumpForTests( + this->PP.getSourceManager()) + << "\n" + + ); + Expanded.push_back(syntax::Token(T)); + }); + // And locations of macro calls, to properly recover boundaries of those in + // case of empty expansions. + auto CB = llvm::make_unique<CollectPPExpansions>(*this); + this->Collector = CB.get(); + PP.addPPCallbacks(std::move(CB)); +} + +/// Builds mappings and spelled tokens in the TokenBuffer based on the expanded +/// token stream. +class TokenCollector::Builder { +public: + Builder(std::vector<syntax::Token> Expanded, PPExpansions CollectedExpansions, + const SourceManager &SM, const LangOptions &LangOpts) + : Result(SM), CollectedExpansions(std::move(CollectedExpansions)), SM(SM), + LangOpts(LangOpts) { + Result.ExpandedTokens = std::move(Expanded); + } + + TokenBuffer build() && { + buildSpelledTokens(); + + // Walk over expanded tokens and spelled tokens in parallel, building the + // mappings between those using source locations. + // To correctly recover empty macro expansions, we also take locations + // reported to PPCallbacks::MacroExpands into account as we do not have any + // expanded tokens with source locations to guide us. + + // The 'eof' token is special, it is not part of spelled token stream. We + // handle it separately at the end. + assert(!Result.ExpandedTokens.empty()); + assert(Result.ExpandedTokens.back().kind() == tok::eof); + for (unsigned I = 0; I < Result.ExpandedTokens.size() - 1; ++I) { + // (!) I might be updated by the following call. + processExpandedToken(I); + } + + // 'eof' not handled in the loop, do it here. + assert(SM.getMainFileID() == + SM.getFileID(Result.ExpandedTokens.back().location())); + fillGapUntil(Result.Files[SM.getMainFileID()], + Result.ExpandedTokens.back().location(), + Result.ExpandedTokens.size() - 1); + Result.Files[SM.getMainFileID()].EndExpanded = Result.ExpandedTokens.size(); + + // Some files might have unaccounted spelled tokens at the end, add an empty + // mapping for those as they did not have expanded counterparts. + fillGapsAtEndOfFiles(); + + return std::move(Result); + } + +private: + /// Process the next token in an expanded stream and move corresponding + /// spelled tokens, record any mapping if needed. + /// (!) \p I will be updated if this had to skip tokens, e.g. for macros. + void processExpandedToken(unsigned &I) { + auto L = Result.ExpandedTokens[I].location(); + if (L.isMacroID()) { + processMacroExpansion(SM.getExpansionRange(L), I); + return; + } + if (L.isFileID()) { + auto FID = SM.getFileID(L); + TokenBuffer::MarkedFile &File = Result.Files[FID]; + + fillGapUntil(File, L, I); + + // Skip the token. + assert(File.SpelledTokens[NextSpelled[FID]].location() == L && + "no corresponding token in the spelled stream"); + ++NextSpelled[FID]; + return; + } + } + + /// Skipped expanded and spelled tokens of a macro expansion that covers \p + /// SpelledRange. Add a corresponding mapping. + /// (!) \p I will be the index of the last token in an expansion after this + /// function returns. + void processMacroExpansion(CharSourceRange SpelledRange, unsigned &I) { + auto FID = SM.getFileID(SpelledRange.getBegin()); + assert(FID == SM.getFileID(SpelledRange.getEnd())); + TokenBuffer::MarkedFile &File = Result.Files[FID]; + + fillGapUntil(File, SpelledRange.getBegin(), I); + + // Skip all expanded tokens from the same macro expansion. + unsigned BeginExpanded = I; + for (; I + 1 < Result.ExpandedTokens.size(); ++I) { + auto NextL = Result.ExpandedTokens[I + 1].location(); + if (!NextL.isMacroID() || + SM.getExpansionLoc(NextL) != SpelledRange.getBegin()) + break; + } + unsigned EndExpanded = I + 1; + consumeMapping(File, SM.getFileOffset(SpelledRange.getEnd()), BeginExpanded, + EndExpanded, NextSpelled[FID]); + } + + /// Initializes TokenBuffer::Files and fills spelled tokens and expanded + /// ranges for each of the files. + void buildSpelledTokens() { + for (unsigned I = 0; I < Result.ExpandedTokens.size(); ++I) { + auto FID = + SM.getFileID(SM.getExpansionLoc(Result.ExpandedTokens[I].location())); + auto It = Result.Files.try_emplace(FID); + TokenBuffer::MarkedFile &File = It.first->second; + + File.EndExpanded = I + 1; + if (!It.second) + continue; // we have seen this file before. + + // This is the first time we see this file. + File.BeginExpanded = I; + File.SpelledTokens = tokenize(FID, SM, LangOpts); + } + } + + void consumeEmptyMapping(TokenBuffer::MarkedFile &File, unsigned EndOffset, + unsigned ExpandedIndex, unsigned &SpelledIndex) { + consumeMapping(File, EndOffset, ExpandedIndex, ExpandedIndex, SpelledIndex); + } + + /// Consumes spelled tokens that form a macro expansion and adds a entry to + /// the resulting token buffer. + /// (!) SpelledIndex is updated in-place. + void consumeMapping(TokenBuffer::MarkedFile &File, unsigned EndOffset, + unsigned BeginExpanded, unsigned EndExpanded, + unsigned &SpelledIndex) { + // We need to record this mapping before continuing. + unsigned MappingBegin = SpelledIndex; + ++SpelledIndex; + + bool HitMapping = + tryConsumeSpelledUntil(File, EndOffset + 1, SpelledIndex).hasValue(); + (void)HitMapping; + assert(!HitMapping && "recursive macro expansion?"); + + TokenBuffer::Mapping M; + M.BeginExpanded = BeginExpanded; + M.EndExpanded = EndExpanded; + M.BeginSpelled = MappingBegin; + M.EndSpelled = SpelledIndex; + + File.Mappings.push_back(M); + } + + /// Consumes spelled tokens until location \p L is reached and adds a mapping + /// covering the consumed tokens. The mapping will point to an empty expanded + /// range at position \p ExpandedIndex. + void fillGapUntil(TokenBuffer::MarkedFile &File, SourceLocation L, + unsigned ExpandedIndex) { + assert(L.isFileID()); + FileID FID; + unsigned Offset; + std::tie(FID, Offset) = SM.getDecomposedLoc(L); + + unsigned &SpelledIndex = NextSpelled[FID]; + unsigned MappingBegin = SpelledIndex; + while (true) { + auto EndLoc = tryConsumeSpelledUntil(File, Offset, SpelledIndex); + if (SpelledIndex != MappingBegin) { + TokenBuffer::Mapping M; + M.BeginSpelled = MappingBegin; + M.EndSpelled = SpelledIndex; + M.BeginExpanded = M.EndExpanded = ExpandedIndex; + File.Mappings.push_back(M); + } + if (!EndLoc) + break; + consumeEmptyMapping(File, SM.getFileOffset(*EndLoc), ExpandedIndex, + SpelledIndex); + + MappingBegin = SpelledIndex; + } + }; + + /// Consumes spelled tokens until it reaches Offset or a mapping boundary, + /// i.e. a name of a macro expansion or the start '#' token of a PP directive. + /// (!) NextSpelled is updated in place. + /// + /// returns None if \p Offset was reached, otherwise returns the end location + /// of a mapping that starts at \p NextSpelled. + llvm::Optional<SourceLocation> + tryConsumeSpelledUntil(TokenBuffer::MarkedFile &File, unsigned Offset, + unsigned &NextSpelled) { + for (; NextSpelled < File.SpelledTokens.size(); ++NextSpelled) { + auto L = File.SpelledTokens[NextSpelled].location(); + if (Offset <= SM.getFileOffset(L)) + return llvm::None; // reached the offset we are looking for. + auto Mapping = CollectedExpansions.find(L.getRawEncoding()); + if (Mapping != CollectedExpansions.end()) + return Mapping->second; // found a mapping before the offset. + } + return llvm::None; // no more tokens, we "reached" the offset. + } + + /// Adds empty mappings for unconsumed spelled tokens at the end of each file. + void fillGapsAtEndOfFiles() { + for (auto &F : Result.Files) { + if (F.second.SpelledTokens.empty()) + continue; + fillGapUntil(F.second, F.second.SpelledTokens.back().endLocation(), + F.second.EndExpanded); + } + } + + TokenBuffer Result; + /// For each file, a position of the next spelled token we will consume. + llvm::DenseMap<FileID, unsigned> NextSpelled; + PPExpansions CollectedExpansions; + const SourceManager &SM; + const LangOptions &LangOpts; +}; + +TokenBuffer TokenCollector::consume() && { + PP.setTokenWatcher(nullptr); + Collector->disable(); + return Builder(std::move(Expanded), std::move(Expansions), + PP.getSourceManager(), PP.getLangOpts()) + .build(); +} + +std::string syntax::Token::str() const { + return llvm::formatv("Token({0}, length = {1})", tok::getTokenName(kind()), + length()); +} + +std::string syntax::Token::dumpForTests(const SourceManager &SM) const { + return llvm::formatv("{0} {1}", tok::getTokenName(kind()), text(SM)); +} + +std::string TokenBuffer::dumpForTests() const { + auto PrintToken = [this](const syntax::Token &T) -> std::string { + if (T.kind() == tok::eof) + return "<eof>"; + return T.text(*SourceMgr); + }; + + auto DumpTokens = [this, &PrintToken](llvm::raw_ostream &OS, + llvm::ArrayRef<syntax::Token> Tokens) { + if (Tokens.empty()) { + OS << "<empty>"; + return; + } + OS << Tokens[0].text(*SourceMgr); + for (unsigned I = 1; I < Tokens.size(); ++I) { + if (Tokens[I].kind() == tok::eof) + continue; + OS << " " << PrintToken(Tokens[I]); + } + }; + + std::string Dump; + llvm::raw_string_ostream OS(Dump); + + OS << "expanded tokens:\n" + << " "; + // (!) we do not show '<eof>'. + DumpTokens(OS, llvm::makeArrayRef(ExpandedTokens).drop_back()); + OS << "\n"; + + std::vector<FileID> Keys; + for (auto F : Files) + Keys.push_back(F.first); + llvm::sort(Keys); + + for (FileID ID : Keys) { + const MarkedFile &File = Files.find(ID)->second; + auto *Entry = SourceMgr->getFileEntryForID(ID); + if (!Entry) + continue; // Skip builtin files. + OS << llvm::formatv("file '{0}'\n", Entry->getName()) + << " spelled tokens:\n" + << " "; + DumpTokens(OS, File.SpelledTokens); + OS << "\n"; + + if (File.Mappings.empty()) { + OS << " no mappings.\n"; + continue; + } + OS << " mappings:\n"; + for (auto &M : File.Mappings) { + OS << llvm::formatv( + " ['{0}'_{1}, '{2}'_{3}) => ['{4}'_{5}, '{6}'_{7})\n", + PrintToken(File.SpelledTokens[M.BeginSpelled]), M.BeginSpelled, + M.EndSpelled == File.SpelledTokens.size() + ? "<eof>" + : PrintToken(File.SpelledTokens[M.EndSpelled]), + M.EndSpelled, PrintToken(ExpandedTokens[M.BeginExpanded]), + M.BeginExpanded, PrintToken(ExpandedTokens[M.EndExpanded]), + M.EndExpanded); + } + } + return OS.str(); +} diff --git a/lib/Tooling/Syntax/Tree.cpp b/lib/Tooling/Syntax/Tree.cpp new file mode 100644 index 000000000000..1549b6724fa4 --- /dev/null +++ b/lib/Tooling/Syntax/Tree.cpp @@ -0,0 +1,149 @@ +//===- Tree.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/Syntax/Tree.h" +#include "clang/Basic/TokenKinds.h" +#include "clang/Tooling/Syntax/Nodes.h" +#include "llvm/ADT/ArrayRef.h" +#include "llvm/ADT/STLExtras.h" +#include "llvm/Support/Casting.h" + +using namespace clang; + +syntax::Arena::Arena(SourceManager &SourceMgr, const LangOptions &LangOpts, + TokenBuffer Tokens) + : SourceMgr(SourceMgr), LangOpts(LangOpts), Tokens(std::move(Tokens)) {} + +const clang::syntax::TokenBuffer &syntax::Arena::tokenBuffer() const { + return Tokens; +} + +std::pair<FileID, llvm::ArrayRef<syntax::Token>> +syntax::Arena::lexBuffer(std::unique_ptr<llvm::MemoryBuffer> Input) { + auto FID = SourceMgr.createFileID(std::move(Input)); + auto It = ExtraTokens.try_emplace(FID, tokenize(FID, SourceMgr, LangOpts)); + assert(It.second && "duplicate FileID"); + return {FID, It.first->second}; +} + +syntax::Leaf::Leaf(const syntax::Token *Tok) : Node(NodeKind::Leaf), Tok(Tok) { + assert(Tok != nullptr); +} + +bool syntax::Leaf::classof(const Node *N) { + return N->kind() == NodeKind::Leaf; +} + +syntax::Node::Node(NodeKind Kind) + : Parent(nullptr), NextSibling(nullptr), Kind(static_cast<unsigned>(Kind)), + Role(static_cast<unsigned>(NodeRole::Detached)) {} + +bool syntax::Tree::classof(const Node *N) { return N->kind() > NodeKind::Leaf; } + +void syntax::Tree::prependChildLowLevel(Node *Child, NodeRole Role) { + assert(Child->Parent == nullptr); + assert(Child->NextSibling == nullptr); + assert(Child->role() == NodeRole::Detached); + assert(Role != NodeRole::Detached); + + Child->Parent = this; + Child->NextSibling = this->FirstChild; + Child->Role = static_cast<unsigned>(Role); + this->FirstChild = Child; +} + +namespace { +static void traverse(const syntax::Node *N, + llvm::function_ref<void(const syntax::Node *)> Visit) { + if (auto *T = dyn_cast<syntax::Tree>(N)) { + for (auto *C = T->firstChild(); C; C = C->nextSibling()) + traverse(C, Visit); + } + Visit(N); +} +static void dumpTokens(llvm::raw_ostream &OS, ArrayRef<syntax::Token> Tokens, + const SourceManager &SM) { + assert(!Tokens.empty()); + bool First = true; + for (const auto &T : Tokens) { + if (!First) + OS << " "; + else + First = false; + // Handle 'eof' separately, calling text() on it produces an empty string. + if (T.kind() == tok::eof) { + OS << "<eof>"; + continue; + } + OS << T.text(SM); + } +} + +static void dumpTree(llvm::raw_ostream &OS, const syntax::Node *N, + const syntax::Arena &A, std::vector<bool> IndentMask) { + if (N->role() != syntax::NodeRole::Unknown) { + // FIXME: print the symbolic name of a role. + if (N->role() == syntax::NodeRole::Detached) + OS << "*: "; + else + OS << static_cast<int>(N->role()) << ": "; + } + if (auto *L = llvm::dyn_cast<syntax::Leaf>(N)) { + dumpTokens(OS, *L->token(), A.sourceManager()); + OS << "\n"; + return; + } + + auto *T = llvm::cast<syntax::Tree>(N); + OS << T->kind() << "\n"; + + for (auto It = T->firstChild(); It != nullptr; It = It->nextSibling()) { + for (bool Filled : IndentMask) { + if (Filled) + OS << "| "; + else + OS << " "; + } + if (!It->nextSibling()) { + OS << "`-"; + IndentMask.push_back(false); + } else { + OS << "|-"; + IndentMask.push_back(true); + } + dumpTree(OS, It, A, IndentMask); + IndentMask.pop_back(); + } +} +} // namespace + +std::string syntax::Node::dump(const Arena &A) const { + std::string Str; + llvm::raw_string_ostream OS(Str); + dumpTree(OS, this, A, /*IndentMask=*/{}); + return std::move(OS.str()); +} + +std::string syntax::Node::dumpTokens(const Arena &A) const { + std::string Storage; + llvm::raw_string_ostream OS(Storage); + traverse(this, [&](const syntax::Node *N) { + auto *L = llvm::dyn_cast<syntax::Leaf>(N); + if (!L) + return; + ::dumpTokens(OS, *L->token(), A.sourceManager()); + }); + return OS.str(); +} + +syntax::Node *syntax::Tree::findChild(NodeRole R) { + for (auto *C = FirstChild; C; C = C->nextSibling()) { + if (C->role() == R) + return C; + } + return nullptr; +} diff --git a/lib/Tooling/Tooling.cpp b/lib/Tooling/Tooling.cpp index 63aa64a5330d..291df0ae333d 100644 --- a/lib/Tooling/Tooling.cpp +++ b/lib/Tooling/Tooling.cpp @@ -1,9 +1,8 @@ //===- Tooling.cpp - Running clang standalone tools -----------------------===// // -// The LLVM Compiler Infrastructure -// -// This file is distributed under the University of Illinois Open Source -// License. See LICENSE.TXT for details. +// 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 // //===----------------------------------------------------------------------===// // @@ -302,7 +301,7 @@ bool ToolInvocation::run() { DiagConsumer ? DiagConsumer : &DiagnosticPrinter, false); const std::unique_ptr<driver::Driver> Driver( - newDriver(&Diagnostics, BinaryName, Files->getVirtualFileSystem())); + newDriver(&Diagnostics, BinaryName, &Files->getVirtualFileSystem())); // The "input file not found" diagnostics from the driver are useful. // The driver is only aware of the VFS working directory, but some clients // change this at the FileManager level instead. @@ -482,7 +481,7 @@ int ClangTool::run(ToolAction *Action) { if (OverlayFileSystem->setCurrentWorkingDirectory( CompileCommand.Directory)) llvm::report_fatal_error("Cannot chdir into \"" + - Twine(CompileCommand.Directory) + "\n!"); + Twine(CompileCommand.Directory) + "\"!"); // Now fill the in-memory VFS with the relative file mappings so it will // have the correct relative paths. We never remove mappings but that @@ -518,7 +517,8 @@ int ClangTool::run(ToolAction *Action) { if (!Invocation.run()) { // FIXME: Diagnostics should be used instead. - llvm::errs() << "Error while processing " << File << ".\n"; + if (PrintErrorMessage) + llvm::errs() << "Error while processing " << File << ".\n"; ProcessingFailed = true; } } @@ -570,6 +570,10 @@ void ClangTool::setRestoreWorkingDir(bool RestoreCWD) { this->RestoreCWD = RestoreCWD; } +void ClangTool::setPrintErrorMessage(bool PrintErrorMessage) { + this->PrintErrorMessage = PrintErrorMessage; +} + namespace clang { namespace tooling { |