diff options
author | Dimitry Andric <dim@FreeBSD.org> | 2019-10-23 17:52:09 +0000 |
---|---|---|
committer | Dimitry Andric <dim@FreeBSD.org> | 2019-10-23 17:52:09 +0000 |
commit | 519fc96c475680de2cc49e7811dbbfadb912cbcc (patch) | |
tree | 310ca684459b7e9ae13c9a3b9abf308b3a634afe /lib/Tooling | |
parent | 2298981669bf3bd63335a4be179bc0f96823a8f4 (diff) | |
download | src-519fc96c475680de2cc49e7811dbbfadb912cbcc.tar.gz src-519fc96c475680de2cc49e7811dbbfadb912cbcc.zip |
Vendor import of stripped clang trunk r375505, the last commit beforevendor/clang/clang-trunk-r375505vendor/clang
the upstream Subversion repository was made read-only, and the LLVM
project migrated to GitHub:
https://llvm.org/svn/llvm-project/cfe/trunk@375505
Notes
Notes:
svn path=/vendor/clang/dist/; revision=353942
svn path=/vendor/clang/clang-r375505/; revision=353943; tag=vendor/clang/clang-trunk-r375505
Diffstat (limited to 'lib/Tooling')
38 files changed, 1439 insertions, 666 deletions
diff --git a/lib/Tooling/ASTDiff/ASTDiff.cpp b/lib/Tooling/ASTDiff/ASTDiff.cpp index 69eff20bff7a..00db7702cd8b 100644 --- a/lib/Tooling/ASTDiff/ASTDiff.cpp +++ b/lib/Tooling/ASTDiff/ASTDiff.cpp @@ -35,8 +35,8 @@ public: Mapping &operator=(Mapping &&Other) = default; Mapping(size_t Size) { - SrcToDst = llvm::make_unique<NodeId[]>(Size); - DstToSrc = llvm::make_unique<NodeId[]>(Size); + SrcToDst = std::make_unique<NodeId[]>(Size); + DstToSrc = std::make_unique<NodeId[]>(Size); } void link(NodeId Src, NodeId Dst) { @@ -565,13 +565,13 @@ public: ZhangShashaMatcher(const ASTDiff::Impl &DiffImpl, const SyntaxTree::Impl &T1, const SyntaxTree::Impl &T2, NodeId Id1, NodeId Id2) : DiffImpl(DiffImpl), S1(T1, Id1), S2(T2, Id2) { - TreeDist = llvm::make_unique<std::unique_ptr<double[]>[]>( + TreeDist = std::make_unique<std::unique_ptr<double[]>[]>( size_t(S1.getSize()) + 1); - ForestDist = llvm::make_unique<std::unique_ptr<double[]>[]>( + ForestDist = std::make_unique<std::unique_ptr<double[]>[]>( size_t(S1.getSize()) + 1); for (int I = 0, E = S1.getSize() + 1; I < E; ++I) { - TreeDist[I] = llvm::make_unique<double[]>(size_t(S2.getSize()) + 1); - ForestDist[I] = llvm::make_unique<double[]>(size_t(S2.getSize()) + 1); + TreeDist[I] = std::make_unique<double[]>(size_t(S2.getSize()) + 1); + ForestDist[I] = std::make_unique<double[]>(size_t(S2.getSize()) + 1); } } @@ -960,7 +960,7 @@ void ASTDiff::Impl::computeChangeKinds(Mapping &M) { ASTDiff::ASTDiff(SyntaxTree &T1, SyntaxTree &T2, const ComparisonOptions &Options) - : DiffImpl(llvm::make_unique<Impl>(*T1.TreeImpl, *T2.TreeImpl, Options)) {} + : DiffImpl(std::make_unique<Impl>(*T1.TreeImpl, *T2.TreeImpl, Options)) {} ASTDiff::~ASTDiff() = default; @@ -969,7 +969,7 @@ NodeId ASTDiff::getMapped(const SyntaxTree &SourceTree, NodeId Id) const { } SyntaxTree::SyntaxTree(ASTContext &AST) - : TreeImpl(llvm::make_unique<SyntaxTree::Impl>( + : TreeImpl(std::make_unique<SyntaxTree::Impl>( this, AST.getTranslationUnitDecl(), AST)) {} SyntaxTree::~SyntaxTree() = default; diff --git a/lib/Tooling/AllTUsExecution.cpp b/lib/Tooling/AllTUsExecution.cpp index ca9db7a561be..d85075f59607 100644 --- a/lib/Tooling/AllTUsExecution.cpp +++ b/lib/Tooling/AllTUsExecution.cpp @@ -8,6 +8,7 @@ #include "clang/Tooling/AllTUsExecution.h" #include "clang/Tooling/ToolExecutorPluginRegistry.h" +#include "llvm/Support/Threading.h" #include "llvm/Support/ThreadPool.h" #include "llvm/Support/VirtualFileSystem.h" @@ -147,7 +148,7 @@ llvm::Error AllTUsToolExecutor::execute( return llvm::Error::success(); } -static llvm::cl::opt<unsigned> ExecutorConcurrency( +llvm::cl::opt<unsigned> ExecutorConcurrency( "execute-concurrency", llvm::cl::desc("The number of threads used to process all files in " "parallel. Set to 0 for hardware concurrency. " @@ -162,7 +163,7 @@ public: return make_string_error( "[AllTUsToolExecutorPlugin] Please provide a directory/file path in " "the compilation database."); - return llvm::make_unique<AllTUsToolExecutor>(std::move(OptionsParser), + return std::make_unique<AllTUsToolExecutor>(std::move(OptionsParser), ExecutorConcurrency); } }; diff --git a/lib/Tooling/ArgumentsAdjusters.cpp b/lib/Tooling/ArgumentsAdjusters.cpp index 942b35df453e..f56d08c47b9a 100644 --- a/lib/Tooling/ArgumentsAdjusters.cpp +++ b/lib/Tooling/ArgumentsAdjusters.cpp @@ -57,6 +57,22 @@ ArgumentsAdjuster getClangStripOutputAdjuster() { }; } +ArgumentsAdjuster getClangStripSerializeDiagnosticAdjuster() { + return [](const CommandLineArguments &Args, StringRef /*unused*/) { + CommandLineArguments AdjustedArgs; + for (size_t i = 0, e = Args.size(); i < e; ++i) { + StringRef Arg = Args[i]; + if (Arg == "--serialize-diagnostics") { + // Skip the diagnostic output argument. + ++i; + continue; + } + AdjustedArgs.push_back(Args[i]); + } + return AdjustedArgs; + }; +} + ArgumentsAdjuster getClangStripDependencyFileAdjuster() { return [](const CommandLineArguments &Args, StringRef /*unused*/) { CommandLineArguments AdjustedArgs; diff --git a/lib/Tooling/CommonOptionsParser.cpp b/lib/Tooling/CommonOptionsParser.cpp index f7956f7998f5..5d881aab1e0d 100644 --- a/lib/Tooling/CommonOptionsParser.cpp +++ b/lib/Tooling/CommonOptionsParser.cpp @@ -142,7 +142,7 @@ llvm::Error CommonOptionsParser::init( } } auto AdjustingCompilations = - llvm::make_unique<ArgumentsAdjustingCompilations>( + std::make_unique<ArgumentsAdjustingCompilations>( std::move(Compilations)); Adjuster = getInsertArgumentAdjuster(ArgsBefore, ArgumentInsertPosition::BEGIN); diff --git a/lib/Tooling/CompilationDatabase.cpp b/lib/Tooling/CompilationDatabase.cpp index 4c64750bef19..c453e8d7df19 100644 --- a/lib/Tooling/CompilationDatabase.cpp +++ b/lib/Tooling/CompilationDatabase.cpp @@ -356,7 +356,7 @@ FixedCompilationDatabase::loadFromCommandLine(int &Argc, std::vector<std::string> StrippedArgs; if (!stripPositionalArgs(CommandLine, StrippedArgs, ErrorMsg)) return nullptr; - return llvm::make_unique<FixedCompilationDatabase>(Directory, StrippedArgs); + return std::make_unique<FixedCompilationDatabase>(Directory, StrippedArgs); } std::unique_ptr<FixedCompilationDatabase> @@ -370,7 +370,7 @@ FixedCompilationDatabase::loadFromFile(StringRef Path, std::string &ErrorMsg) { } std::vector<std::string> Args{llvm::line_iterator(**File), llvm::line_iterator()}; - return llvm::make_unique<FixedCompilationDatabase>( + return std::make_unique<FixedCompilationDatabase>( llvm::sys::path::parent_path(Path), std::move(Args)); } diff --git a/lib/Tooling/Core/Replacement.cpp b/lib/Tooling/Core/Replacement.cpp index 546158714e3c..9ed03655bf2c 100644 --- a/lib/Tooling/Core/Replacement.cpp +++ b/lib/Tooling/Core/Replacement.cpp @@ -67,11 +67,11 @@ bool Replacement::isApplicable() const { bool Replacement::apply(Rewriter &Rewrite) const { SourceManager &SM = Rewrite.getSourceMgr(); - const FileEntry *Entry = SM.getFileManager().getFile(FilePath); + auto Entry = SM.getFileManager().getFile(FilePath); if (!Entry) return false; - FileID ID = SM.getOrCreateFileID(Entry, SrcMgr::C_User); + FileID ID = SM.getOrCreateFileID(*Entry, SrcMgr::C_User); const SourceLocation Start = SM.getLocForStartOfFile(ID). getLocWithOffset(ReplacementRange.getOffset()); @@ -591,7 +591,8 @@ llvm::Expected<std::string> applyAllReplacements(StringRef Code, Rewriter Rewrite(SourceMgr, LangOptions()); InMemoryFileSystem->addFile( "<stdin>", 0, llvm::MemoryBuffer::getMemBuffer(Code, "<stdin>")); - FileID ID = SourceMgr.createFileID(Files.getFile("<stdin>"), SourceLocation(), + FileID ID = SourceMgr.createFileID(*Files.getFile("<stdin>"), + SourceLocation(), clang::SrcMgr::C_User); for (auto I = Replaces.rbegin(), E = Replaces.rend(); I != E; ++I) { Replacement Replace("<stdin>", I->getOffset(), I->getLength(), @@ -613,10 +614,10 @@ std::map<std::string, Replacements> groupReplacementsByFile( std::map<std::string, Replacements> Result; llvm::SmallPtrSet<const FileEntry *, 16> ProcessedFileEntries; for (const auto &Entry : FileToReplaces) { - const FileEntry *FE = FileMgr.getFile(Entry.first); + auto FE = FileMgr.getFile(Entry.first); if (!FE) llvm::errs() << "File path " << Entry.first << " is invalid.\n"; - else if (ProcessedFileEntries.insert(FE).second) + else if (ProcessedFileEntries.insert(*FE).second) Result[Entry.first] = std::move(Entry.second); } return Result; diff --git a/lib/Tooling/DependencyScanning/DependencyScanningFilesystem.cpp b/lib/Tooling/DependencyScanning/DependencyScanningFilesystem.cpp new file mode 100644 index 000000000000..7436c7256327 --- /dev/null +++ b/lib/Tooling/DependencyScanning/DependencyScanningFilesystem.cpp @@ -0,0 +1,234 @@ +//===- DependencyScanningFilesystem.cpp - clang-scan-deps fs --------------===// +// +// 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/DependencyScanningFilesystem.h" +#include "clang/Lex/DependencyDirectivesSourceMinimizer.h" +#include "llvm/Support/MemoryBuffer.h" +#include "llvm/Support/Threading.h" + +using namespace clang; +using namespace tooling; +using namespace dependencies; + +CachedFileSystemEntry CachedFileSystemEntry::createFileEntry( + StringRef Filename, llvm::vfs::FileSystem &FS, bool Minimize) { + // Load the file and its content from the file system. + llvm::ErrorOr<std::unique_ptr<llvm::vfs::File>> MaybeFile = + FS.openFileForRead(Filename); + if (!MaybeFile) + return MaybeFile.getError(); + llvm::ErrorOr<llvm::vfs::Status> Stat = (*MaybeFile)->status(); + if (!Stat) + return Stat.getError(); + + llvm::vfs::File &F = **MaybeFile; + llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>> MaybeBuffer = + F.getBuffer(Stat->getName()); + if (!MaybeBuffer) + return MaybeBuffer.getError(); + + llvm::SmallString<1024> MinimizedFileContents; + // Minimize the file down to directives that might affect the dependencies. + const auto &Buffer = *MaybeBuffer; + SmallVector<minimize_source_to_dependency_directives::Token, 64> Tokens; + if (!Minimize || minimizeSourceToDependencyDirectives( + Buffer->getBuffer(), MinimizedFileContents, Tokens)) { + // Use the original file unless requested otherwise, or + // if the minimization failed. + // FIXME: Propage the diagnostic if desired by the client. + CachedFileSystemEntry Result; + Result.MaybeStat = std::move(*Stat); + Result.Contents.reserve(Buffer->getBufferSize() + 1); + Result.Contents.append(Buffer->getBufferStart(), Buffer->getBufferEnd()); + // Implicitly null terminate the contents for Clang's lexer. + Result.Contents.push_back('\0'); + Result.Contents.pop_back(); + return Result; + } + + CachedFileSystemEntry Result; + size_t Size = MinimizedFileContents.size(); + Result.MaybeStat = llvm::vfs::Status(Stat->getName(), Stat->getUniqueID(), + Stat->getLastModificationTime(), + Stat->getUser(), Stat->getGroup(), Size, + Stat->getType(), Stat->getPermissions()); + // The contents produced by the minimizer must be null terminated. + assert(MinimizedFileContents.data()[MinimizedFileContents.size()] == '\0' && + "not null terminated contents"); + // Even though there's an implicit null terminator in the minimized contents, + // we want to temporarily make it explicit. This will ensure that the + // std::move will preserve it even if it needs to do a copy if the + // SmallString still has the small capacity. + MinimizedFileContents.push_back('\0'); + Result.Contents = std::move(MinimizedFileContents); + // Now make the null terminator implicit again, so that Clang's lexer can find + // it right where the buffer ends. + Result.Contents.pop_back(); + + // Compute the skipped PP ranges that speedup skipping over inactive + // preprocessor blocks. + llvm::SmallVector<minimize_source_to_dependency_directives::SkippedRange, 32> + SkippedRanges; + minimize_source_to_dependency_directives::computeSkippedRanges(Tokens, + SkippedRanges); + PreprocessorSkippedRangeMapping Mapping; + for (const auto &Range : SkippedRanges) { + if (Range.Length < 16) { + // Ignore small ranges as non-profitable. + // FIXME: This is a heuristic, its worth investigating the tradeoffs + // when it should be applied. + continue; + } + Mapping[Range.Offset] = Range.Length; + } + Result.PPSkippedRangeMapping = std::move(Mapping); + + return Result; +} + +CachedFileSystemEntry +CachedFileSystemEntry::createDirectoryEntry(llvm::vfs::Status &&Stat) { + assert(Stat.isDirectory() && "not a directory!"); + auto Result = CachedFileSystemEntry(); + Result.MaybeStat = std::move(Stat); + return Result; +} + +DependencyScanningFilesystemSharedCache:: + DependencyScanningFilesystemSharedCache() { + // This heuristic was chosen using a empirical testing on a + // reasonably high core machine (iMacPro 18 cores / 36 threads). The cache + // sharding gives a performance edge by reducing the lock contention. + // FIXME: A better heuristic might also consider the OS to account for + // the different cost of lock contention on different OSes. + NumShards = std::max(2u, llvm::hardware_concurrency() / 4); + CacheShards = std::make_unique<CacheShard[]>(NumShards); +} + +/// Returns a cache entry for the corresponding key. +/// +/// A new cache entry is created if the key is not in the cache. This is a +/// thread safe call. +DependencyScanningFilesystemSharedCache::SharedFileSystemEntry & +DependencyScanningFilesystemSharedCache::get(StringRef Key) { + CacheShard &Shard = CacheShards[llvm::hash_value(Key) % NumShards]; + std::unique_lock<std::mutex> LockGuard(Shard.CacheLock); + auto It = Shard.Cache.try_emplace(Key); + return It.first->getValue(); +} + +llvm::ErrorOr<const CachedFileSystemEntry *> +DependencyScanningWorkerFilesystem::getOrCreateFileSystemEntry( + const StringRef Filename) { + if (const CachedFileSystemEntry *Entry = getCachedEntry(Filename)) { + return Entry; + } + + // FIXME: Handle PCM/PCH files. + // FIXME: Handle module map files. + + bool KeepOriginalSource = IgnoredFiles.count(Filename); + DependencyScanningFilesystemSharedCache::SharedFileSystemEntry + &SharedCacheEntry = SharedCache.get(Filename); + const CachedFileSystemEntry *Result; + { + std::unique_lock<std::mutex> LockGuard(SharedCacheEntry.ValueLock); + CachedFileSystemEntry &CacheEntry = SharedCacheEntry.Value; + + if (!CacheEntry.isValid()) { + llvm::vfs::FileSystem &FS = getUnderlyingFS(); + auto MaybeStatus = FS.status(Filename); + if (!MaybeStatus) + CacheEntry = CachedFileSystemEntry(MaybeStatus.getError()); + else if (MaybeStatus->isDirectory()) + CacheEntry = CachedFileSystemEntry::createDirectoryEntry( + std::move(*MaybeStatus)); + else + CacheEntry = CachedFileSystemEntry::createFileEntry( + Filename, FS, !KeepOriginalSource); + } + + Result = &CacheEntry; + } + + // Store the result in the local cache. + setCachedEntry(Filename, Result); + return Result; +} + +llvm::ErrorOr<llvm::vfs::Status> +DependencyScanningWorkerFilesystem::status(const Twine &Path) { + SmallString<256> OwnedFilename; + StringRef Filename = Path.toStringRef(OwnedFilename); + const llvm::ErrorOr<const CachedFileSystemEntry *> Result = + getOrCreateFileSystemEntry(Filename); + if (!Result) + return Result.getError(); + return (*Result)->getStatus(); +} + +namespace { + +/// The VFS that is used by clang consumes the \c CachedFileSystemEntry using +/// this subclass. +class MinimizedVFSFile final : public llvm::vfs::File { +public: + MinimizedVFSFile(std::unique_ptr<llvm::MemoryBuffer> Buffer, + llvm::vfs::Status Stat) + : Buffer(std::move(Buffer)), Stat(std::move(Stat)) {} + + llvm::ErrorOr<llvm::vfs::Status> status() override { return Stat; } + + const llvm::MemoryBuffer *getBufferPtr() const { return Buffer.get(); } + + llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>> + getBuffer(const Twine &Name, int64_t FileSize, bool RequiresNullTerminator, + bool IsVolatile) override { + return std::move(Buffer); + } + + std::error_code close() override { return {}; } + +private: + std::unique_ptr<llvm::MemoryBuffer> Buffer; + llvm::vfs::Status Stat; +}; + +llvm::ErrorOr<std::unique_ptr<llvm::vfs::File>> +createFile(const CachedFileSystemEntry *Entry, + ExcludedPreprocessorDirectiveSkipMapping *PPSkipMappings) { + if (Entry->isDirectory()) + return llvm::ErrorOr<std::unique_ptr<llvm::vfs::File>>( + std::make_error_code(std::errc::is_a_directory)); + llvm::ErrorOr<StringRef> Contents = Entry->getContents(); + if (!Contents) + return Contents.getError(); + auto Result = std::make_unique<MinimizedVFSFile>( + llvm::MemoryBuffer::getMemBuffer(*Contents, Entry->getName(), + /*RequiresNullTerminator=*/false), + *Entry->getStatus()); + if (!Entry->getPPSkippedRangeMapping().empty() && PPSkipMappings) + (*PPSkipMappings)[Result->getBufferPtr()] = + &Entry->getPPSkippedRangeMapping(); + return llvm::ErrorOr<std::unique_ptr<llvm::vfs::File>>( + std::unique_ptr<llvm::vfs::File>(std::move(Result))); +} + +} // end anonymous namespace + +llvm::ErrorOr<std::unique_ptr<llvm::vfs::File>> +DependencyScanningWorkerFilesystem::openFileForRead(const Twine &Path) { + SmallString<256> OwnedFilename; + StringRef Filename = Path.toStringRef(OwnedFilename); + + const llvm::ErrorOr<const CachedFileSystemEntry *> Result = + getOrCreateFileSystemEntry(Filename); + if (!Result) + return Result.getError(); + return createFile(Result.get(), PPSkipMappings); +} diff --git a/lib/Tooling/DependencyScanning/DependencyScanningService.cpp b/lib/Tooling/DependencyScanning/DependencyScanningService.cpp new file mode 100644 index 000000000000..e5cebe381000 --- /dev/null +++ b/lib/Tooling/DependencyScanning/DependencyScanningService.cpp @@ -0,0 +1,19 @@ +//===- DependencyScanningService.cpp - clang-scan-deps service ------------===// +// +// 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/DependencyScanningService.h" + +using namespace clang; +using namespace tooling; +using namespace dependencies; + +DependencyScanningService::DependencyScanningService(ScanningMode Mode, + bool ReuseFileManager, + bool SkipExcludedPPRanges) + : Mode(Mode), ReuseFileManager(ReuseFileManager), + SkipExcludedPPRanges(SkipExcludedPPRanges) {} diff --git a/lib/Tooling/DependencyScanning/DependencyScanningTool.cpp b/lib/Tooling/DependencyScanning/DependencyScanningTool.cpp new file mode 100644 index 000000000000..82b3ae806c65 --- /dev/null +++ b/lib/Tooling/DependencyScanning/DependencyScanningTool.cpp @@ -0,0 +1,71 @@ +//===- DependencyScanningTool.cpp - clang-scan-deps service ------------===// +// +// 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/DependencyScanningTool.h" +#include "clang/Frontend/Utils.h" + +namespace clang{ +namespace tooling{ +namespace dependencies{ + +DependencyScanningTool::DependencyScanningTool(DependencyScanningService &Service, +const tooling::CompilationDatabase &Compilations) : Worker(Service), Compilations(Compilations) {} + +llvm::Expected<std::string> DependencyScanningTool::getDependencyFile(const std::string &Input, + StringRef CWD) { + /// Prints out all of the gathered dependencies into a string. + class DependencyPrinterConsumer : public DependencyConsumer { + public: + void handleFileDependency(const DependencyOutputOptions &Opts, + StringRef File) override { + if (!this->Opts) + this->Opts = std::make_unique<DependencyOutputOptions>(Opts); + Dependencies.push_back(File); + } + + void printDependencies(std::string &S) { + if (!Opts) + return; + + class DependencyPrinter : public DependencyFileGenerator { + public: + DependencyPrinter(DependencyOutputOptions &Opts, + ArrayRef<std::string> Dependencies) + : DependencyFileGenerator(Opts) { + for (const auto &Dep : Dependencies) + addDependency(Dep); + } + + void printDependencies(std::string &S) { + llvm::raw_string_ostream OS(S); + outputDependencyFile(OS); + } + }; + + DependencyPrinter Generator(*Opts, Dependencies); + Generator.printDependencies(S); + } + + private: + std::unique_ptr<DependencyOutputOptions> Opts; + std::vector<std::string> Dependencies; + }; + + DependencyPrinterConsumer Consumer; + auto Result = + Worker.computeDependencies(Input, CWD, Compilations, Consumer); + if (Result) + return std::move(Result); + std::string Output; + Consumer.printDependencies(Output); + return Output; +} + +} // end namespace dependencies +} // end namespace tooling +} // end namespace clang diff --git a/lib/Tooling/DependencyScanning/DependencyScanningWorker.cpp b/lib/Tooling/DependencyScanning/DependencyScanningWorker.cpp index 4868f2663796..f382c202f8c2 100644 --- a/lib/Tooling/DependencyScanning/DependencyScanningWorker.cpp +++ b/lib/Tooling/DependencyScanning/DependencyScanningWorker.cpp @@ -8,9 +8,12 @@ #include "clang/Tooling/DependencyScanning/DependencyScanningWorker.h" #include "clang/Frontend/CompilerInstance.h" +#include "clang/Frontend/CompilerInvocation.h" #include "clang/Frontend/FrontendActions.h" #include "clang/Frontend/TextDiagnosticPrinter.h" #include "clang/Frontend/Utils.h" +#include "clang/Lex/PreprocessorOptions.h" +#include "clang/Tooling/DependencyScanning/DependencyScanningService.h" #include "clang/Tooling/Tooling.h" using namespace clang; @@ -19,21 +22,25 @@ using namespace dependencies; namespace { -/// Prints out all of the gathered dependencies into a string. -class DependencyPrinter : public DependencyFileGenerator { +/// Forwards the gatherered dependencies to the consumer. +class DependencyConsumerForwarder : public DependencyFileGenerator { public: - DependencyPrinter(std::unique_ptr<DependencyOutputOptions> Opts, - std::string &S) - : DependencyFileGenerator(*Opts), Opts(std::move(Opts)), S(S) {} + DependencyConsumerForwarder(std::unique_ptr<DependencyOutputOptions> Opts, + DependencyConsumer &C) + : DependencyFileGenerator(*Opts), Opts(std::move(Opts)), C(C) {} void finishedMainFile(DiagnosticsEngine &Diags) override { - llvm::raw_string_ostream OS(S); - outputDependencyFile(OS); + llvm::SmallString<256> CanonPath; + for (const auto &File : getDependencies()) { + CanonPath = File; + llvm::sys::path::remove_dots(CanonPath, /*remove_dot_dot=*/true); + C.handleFileDependency(*Opts, CanonPath); + } } private: std::unique_ptr<DependencyOutputOptions> Opts; - std::string &S; + DependencyConsumer &C; }; /// A proxy file system that doesn't call `chdir` when changing the working @@ -62,10 +69,12 @@ private: /// dependency scanning for the given compiler invocation. class DependencyScanningAction : public tooling::ToolAction { public: - DependencyScanningAction(StringRef WorkingDirectory, - std::string &DependencyFileContents) - : WorkingDirectory(WorkingDirectory), - DependencyFileContents(DependencyFileContents) {} + DependencyScanningAction( + StringRef WorkingDirectory, DependencyConsumer &Consumer, + llvm::IntrusiveRefCntPtr<DependencyScanningWorkerFilesystem> DepFS, + ExcludedPreprocessorDirectiveSkipMapping *PPSkipMappings) + : WorkingDirectory(WorkingDirectory), Consumer(Consumer), + DepFS(std::move(DepFS)), PPSkipMappings(PPSkipMappings) {} bool runInvocation(std::shared_ptr<CompilerInvocation> Invocation, FileManager *FileMgr, @@ -74,8 +83,6 @@ public: // 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; @@ -84,6 +91,32 @@ public: if (!Compiler.hasDiagnostics()) return false; + // Use the dependency scanning optimized file system if we can. + if (DepFS) { + const CompilerInvocation &CI = Compiler.getInvocation(); + // Add any filenames that were explicity passed in the build settings and + // that might be opened, as we want to ensure we don't run source + // minimization on them. + DepFS->IgnoredFiles.clear(); + for (const auto &Entry : CI.getHeaderSearchOpts().UserEntries) + DepFS->IgnoredFiles.insert(Entry.Path); + for (const auto &Entry : CI.getHeaderSearchOpts().VFSOverlayFiles) + DepFS->IgnoredFiles.insert(Entry); + + // Support for virtual file system overlays on top of the caching + // filesystem. + FileMgr->setVirtualFileSystem(createVFSFromCompilerInvocation( + CI, Compiler.getDiagnostics(), DepFS)); + + // Pass the skip mappings which should speed up excluded conditional block + // skipping in the preprocessor. + if (PPSkipMappings) + Compiler.getPreprocessorOpts() + .ExcludedConditionalDirectiveSkipMappings = PPSkipMappings; + } + + FileMgr->getFileSystemOpts().WorkingDir = WorkingDirectory; + Compiler.setFileManager(FileMgr); Compiler.createSourceManager(*FileMgr); // Create the dependency collector that will collect the produced @@ -93,57 +126,76 @@ public: // 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>( + auto Opts = std::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)); + Compiler.addDependencyCollector( + std::make_shared<DependencyConsumerForwarder>(std::move(Opts), + Consumer)); - auto Action = llvm::make_unique<PreprocessOnlyAction>(); + auto Action = std::make_unique<PreprocessOnlyAction>(); const bool Result = Compiler.ExecuteAction(*Action); - FileMgr->clearStatCache(); + if (!DepFS) + FileMgr->clearStatCache(); return Result; } private: StringRef WorkingDirectory; - /// The dependency file will be written to this string. - std::string &DependencyFileContents; + DependencyConsumer &Consumer; + llvm::IntrusiveRefCntPtr<DependencyScanningWorkerFilesystem> DepFS; + ExcludedPreprocessorDirectiveSkipMapping *PPSkipMappings; }; } // end anonymous namespace -DependencyScanningWorker::DependencyScanningWorker() { +DependencyScanningWorker::DependencyScanningWorker( + DependencyScanningService &Service) { 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()); + RealFS = new ProxyFileSystemWithoutChdir(llvm::vfs::getRealFileSystem()); + if (Service.canSkipExcludedPPRanges()) + PPSkipMappings = + std::make_unique<ExcludedPreprocessorDirectiveSkipMapping>(); + if (Service.getMode() == ScanningMode::MinimizedSourcePreprocessing) + DepFS = new DependencyScanningWorkerFilesystem( + Service.getSharedCache(), RealFS, PPSkipMappings.get()); + if (Service.canReuseFileManager()) + Files = new FileManager(FileSystemOptions(), RealFS); } -llvm::Expected<std::string> -DependencyScanningWorker::getDependencyFile(const std::string &Input, - StringRef WorkingDirectory, - const CompilationDatabase &CDB) { +static llvm::Error runWithDiags( + DiagnosticOptions *DiagOpts, + llvm::function_ref<bool(DiagnosticConsumer &DC)> BodyShouldSucceed) { // 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; + TextDiagnosticPrinter DiagPrinter(DiagnosticsOS, DiagOpts); + + if (BodyShouldSucceed(DiagPrinter)) + return llvm::Error::success(); + return llvm::make_error<llvm::StringError>(DiagnosticsOS.str(), + llvm::inconvertibleErrorCode()); +} + +llvm::Error DependencyScanningWorker::computeDependencies( + const std::string &Input, StringRef WorkingDirectory, + const CompilationDatabase &CDB, DependencyConsumer &Consumer) { + RealFS->setCurrentWorkingDirectory(WorkingDirectory); + return runWithDiags(DiagOpts.get(), [&](DiagnosticConsumer &DC) { + /// Create the tool that uses the underlying file system to ensure that any + /// file system requests that are made by the driver do not go through the + /// dependency scanning filesystem. + tooling::ClangTool Tool(CDB, Input, PCHContainerOps, RealFS, Files); + Tool.clearArgumentsAdjusters(); + Tool.setRestoreWorkingDir(false); + Tool.setPrintErrorMessage(false); + Tool.setDiagnosticConsumer(&DC); + DependencyScanningAction Action(WorkingDirectory, Consumer, DepFS, + PPSkipMappings.get()); + return !Tool.run(&Action); + }); } diff --git a/lib/Tooling/GuessTargetAndModeCompilationDatabase.cpp b/lib/Tooling/GuessTargetAndModeCompilationDatabase.cpp index ac3faf1b01f9..b6c1c0952aca 100644 --- a/lib/Tooling/GuessTargetAndModeCompilationDatabase.cpp +++ b/lib/Tooling/GuessTargetAndModeCompilationDatabase.cpp @@ -50,7 +50,7 @@ private: std::unique_ptr<CompilationDatabase> inferTargetAndDriverMode(std::unique_ptr<CompilationDatabase> Base) { - return llvm::make_unique<TargetAndModeAdderDatabase>(std::move(Base)); + return std::make_unique<TargetAndModeAdderDatabase>(std::move(Base)); } } // namespace tooling diff --git a/lib/Tooling/Inclusions/HeaderIncludes.cpp b/lib/Tooling/Inclusions/HeaderIncludes.cpp index a7f79c33bc55..e746bbb7f87b 100644 --- a/lib/Tooling/Inclusions/HeaderIncludes.cpp +++ b/lib/Tooling/Inclusions/HeaderIncludes.cpp @@ -199,6 +199,20 @@ int IncludeCategoryManager::getIncludePriority(StringRef IncludeName, return Ret; } +int IncludeCategoryManager::getSortIncludePriority(StringRef IncludeName, + bool CheckMainHeader) const { + int Ret = INT_MAX; + for (unsigned i = 0, e = CategoryRegexs.size(); i != e; ++i) + if (CategoryRegexs[i].match(IncludeName)) { + Ret = Style.IncludeCategories[i].SortPriority; + if (Ret == 0) + Ret = Style.IncludeCategories[i].Priority; + break; + } + if (CheckMainHeader && IsMainFile && Ret > 0 && isMainHeader(IncludeName)) + Ret = 0; + return Ret; +} bool IncludeCategoryManager::isMainHeader(StringRef IncludeName) const { if (!IncludeName.startswith("\"")) return false; diff --git a/lib/Tooling/Inclusions/IncludeStyle.cpp b/lib/Tooling/Inclusions/IncludeStyle.cpp index c53c82c83630..26dc0b87cf9d 100644 --- a/lib/Tooling/Inclusions/IncludeStyle.cpp +++ b/lib/Tooling/Inclusions/IncludeStyle.cpp @@ -17,6 +17,7 @@ void MappingTraits<IncludeStyle::IncludeCategory>::mapping( IO &IO, IncludeStyle::IncludeCategory &Category) { IO.mapOptional("Regex", Category.Regex); IO.mapOptional("Priority", Category.Priority); + IO.mapOptional("SortPriority", Category.SortPriority); } void ScalarEnumerationTraits<IncludeStyle::IncludeBlocksStyle>::enumeration( diff --git a/lib/Tooling/InterpolatingCompilationDatabase.cpp b/lib/Tooling/InterpolatingCompilationDatabase.cpp index 53c8dd448fd9..59b66abe65e9 100644 --- a/lib/Tooling/InterpolatingCompilationDatabase.cpp +++ b/lib/Tooling/InterpolatingCompilationDatabase.cpp @@ -42,9 +42,9 @@ // //===----------------------------------------------------------------------===// +#include "clang/Basic/LangStandard.h" #include "clang/Driver/Options.h" #include "clang/Driver/Types.h" -#include "clang/Frontend/LangStandard.h" #include "clang/Tooling/CompilationDatabase.h" #include "llvm/ADT/DenseMap.h" #include "llvm/ADT/Optional.h" @@ -149,17 +149,17 @@ struct TransferableCommand { // We parse each argument individually so that we can retain the exact // 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(); + auto &OptTable = clang::driver::getDriverOptTable(); if (!OldArgs.empty()) Cmd.CommandLine.emplace_back(OldArgs.front()); for (unsigned Pos = 1; Pos < OldArgs.size();) { using namespace driver::options; const unsigned OldPos = Pos; - std::unique_ptr<llvm::opt::Arg> Arg(OptTable->ParseOneArg( + std::unique_ptr<llvm::opt::Arg> Arg(OptTable.ParseOneArg( ArgList, Pos, - /* Include */ClangCLMode ? CoreOption | CLOption : 0, - /* Exclude */ClangCLMode ? 0 : CLOption)); + /* Include */ ClangCLMode ? CoreOption | CLOption : 0, + /* Exclude */ ClangCLMode ? 0 : CLOption)); if (!Arg) continue; @@ -249,15 +249,15 @@ private: } // Map the language from the --std flag to that of the -x flag. - static types::ID toType(InputKind::Language Lang) { + static types::ID toType(Language Lang) { switch (Lang) { - case InputKind::C: + case Language::C: return types::TY_C; - case InputKind::CXX: + case Language::CXX: return types::TY_CXX; - case InputKind::ObjC: + case Language::ObjC: return types::TY_ObjC; - case InputKind::ObjCXX: + case Language::ObjCXX: return types::TY_ObjCXX; default: return types::TY_INVALID; @@ -297,15 +297,8 @@ private: // Try to interpret the argument as '-std='. Optional<LangStandard::Kind> tryParseStdArg(const llvm::opt::Arg &Arg) { using namespace driver::options; - if (Arg.getOption().matches(ClangCLMode ? OPT__SLASH_std : OPT_std_EQ)) { - return llvm::StringSwitch<LangStandard::Kind>(Arg.getValue()) -#define LANGSTANDARD(id, name, lang, ...) .Case(name, LangStandard::lang_##id) -#define LANGSTANDARD_ALIAS(id, alias) .Case(alias, LangStandard::lang_##id) -#include "clang/Frontend/LangStandards.def" -#undef LANGSTANDARD_ALIAS -#undef LANGSTANDARD - .Default(LangStandard::lang_unspecified); - } + if (Arg.getOption().matches(ClangCLMode ? OPT__SLASH_std : OPT_std_EQ)) + return LangStandard::getLangKind(Arg.getValue()); return None; } }; @@ -544,7 +537,7 @@ private: std::unique_ptr<CompilationDatabase> inferMissingCompileCommands(std::unique_ptr<CompilationDatabase> Inner) { - return llvm::make_unique<InterpolatingCompilationDatabase>(std::move(Inner)); + return std::make_unique<InterpolatingCompilationDatabase>(std::move(Inner)); } } // namespace tooling diff --git a/lib/Tooling/Refactoring.cpp b/lib/Tooling/Refactoring.cpp index f379a9c3bcf6..d45cd8c57f10 100644 --- a/lib/Tooling/Refactoring.cpp +++ b/lib/Tooling/Refactoring.cpp @@ -78,7 +78,10 @@ bool formatAndApplyAllReplacements( const std::string &FilePath = FileAndReplaces.first; auto &CurReplaces = FileAndReplaces.second; - const FileEntry *Entry = Files.getFile(FilePath); + const FileEntry *Entry = nullptr; + if (auto File = Files.getFile(FilePath)) + Entry = *File; + FileID ID = SM.getOrCreateFileID(Entry, SrcMgr::C_User); StringRef Code = SM.getBufferData(ID); diff --git a/lib/Tooling/Refactoring/ASTSelectionRequirements.cpp b/lib/Tooling/Refactoring/ASTSelectionRequirements.cpp index 14fc66a979ae..7dfd3988d904 100644 --- a/lib/Tooling/Refactoring/ASTSelectionRequirements.cpp +++ b/lib/Tooling/Refactoring/ASTSelectionRequirements.cpp @@ -35,7 +35,7 @@ Expected<CodeRangeASTSelection> CodeRangeASTSelectionRequirement::evaluate( if (!ASTSelection) return ASTSelection.takeError(); std::unique_ptr<SelectedASTNode> StoredSelection = - llvm::make_unique<SelectedASTNode>(std::move(*ASTSelection)); + std::make_unique<SelectedASTNode>(std::move(*ASTSelection)); Optional<CodeRangeASTSelection> CodeRange = CodeRangeASTSelection::create( Context.getSelectionRange(), *StoredSelection); if (!CodeRange) diff --git a/lib/Tooling/Refactoring/Extract/Extract.cpp b/lib/Tooling/Refactoring/Extract/Extract.cpp index f5b94a462103..402b56109052 100644 --- a/lib/Tooling/Refactoring/Extract/Extract.cpp +++ b/lib/Tooling/Refactoring/Extract/Extract.cpp @@ -13,12 +13,12 @@ //===----------------------------------------------------------------------===// #include "clang/Tooling/Refactoring/Extract/Extract.h" -#include "SourceExtraction.h" #include "clang/AST/ASTContext.h" #include "clang/AST/DeclCXX.h" #include "clang/AST/Expr.h" #include "clang/AST/ExprObjC.h" #include "clang/Rewrite/Core/Rewriter.h" +#include "clang/Tooling/Refactoring/Extract/SourceExtraction.h" namespace clang { namespace tooling { diff --git a/lib/Tooling/Refactoring/Extract/SourceExtraction.cpp b/lib/Tooling/Refactoring/Extract/SourceExtraction.cpp index 533c373e35c4..5d57ecf90a96 100644 --- a/lib/Tooling/Refactoring/Extract/SourceExtraction.cpp +++ b/lib/Tooling/Refactoring/Extract/SourceExtraction.cpp @@ -6,7 +6,7 @@ // //===----------------------------------------------------------------------===// -#include "SourceExtraction.h" +#include "clang/Tooling/Refactoring/Extract/SourceExtraction.h" #include "clang/AST/Stmt.h" #include "clang/AST/StmtCXX.h" #include "clang/AST/StmtObjC.h" @@ -40,8 +40,12 @@ bool isSemicolonRequiredAfter(const Stmt *S) { return isSemicolonRequiredAfter(CXXFor->getBody()); if (const auto *ObjCFor = dyn_cast<ObjCForCollectionStmt>(S)) return isSemicolonRequiredAfter(ObjCFor->getBody()); + if(const auto *Switch = dyn_cast<SwitchStmt>(S)) + return isSemicolonRequiredAfter(Switch->getBody()); + if(const auto *Case = dyn_cast<SwitchCase>(S)) + return isSemicolonRequiredAfter(Case->getSubStmt()); switch (S->getStmtClass()) { - case Stmt::SwitchStmtClass: + case Stmt::DeclStmtClass: case Stmt::CXXTryStmtClass: case Stmt::ObjCAtSynchronizedStmtClass: case Stmt::ObjCAutoreleasePoolStmtClass: diff --git a/lib/Tooling/Refactoring/Extract/SourceExtraction.h b/lib/Tooling/Refactoring/Extract/SourceExtraction.h deleted file mode 100644 index 545eb6c1a11c..000000000000 --- a/lib/Tooling/Refactoring/Extract/SourceExtraction.h +++ /dev/null @@ -1,51 +0,0 @@ -//===--- SourceExtraction.cpp - Clang refactoring library -----------------===// -// -// 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 -// -//===----------------------------------------------------------------------===// - -#ifndef LLVM_CLANG_LIB_TOOLING_REFACTORING_EXTRACT_SOURCE_EXTRACTION_H -#define LLVM_CLANG_LIB_TOOLING_REFACTORING_EXTRACT_SOURCE_EXTRACTION_H - -#include "clang/Basic/LLVM.h" - -namespace clang { - -class LangOptions; -class SourceManager; -class SourceRange; -class Stmt; - -namespace tooling { - -/// Determines which semicolons should be inserted during extraction. -class ExtractionSemicolonPolicy { -public: - bool isNeededInExtractedFunction() const { - return IsNeededInExtractedFunction; - } - - bool isNeededInOriginalFunction() const { return IsNeededInOriginalFunction; } - - /// Returns the semicolon insertion policy that is needed for extraction of - /// the given statement from the given source range. - static ExtractionSemicolonPolicy compute(const Stmt *S, - SourceRange &ExtractedRange, - const SourceManager &SM, - const LangOptions &LangOpts); - -private: - ExtractionSemicolonPolicy(bool IsNeededInExtractedFunction, - bool IsNeededInOriginalFunction) - : IsNeededInExtractedFunction(IsNeededInExtractedFunction), - IsNeededInOriginalFunction(IsNeededInOriginalFunction) {} - bool IsNeededInExtractedFunction; - bool IsNeededInOriginalFunction; -}; - -} // end namespace tooling -} // end namespace clang - -#endif // LLVM_CLANG_LIB_TOOLING_REFACTORING_EXTRACT_SOURCE_EXTRACTION_H diff --git a/lib/Tooling/Refactoring/RefactoringActions.cpp b/lib/Tooling/Refactoring/RefactoringActions.cpp index 1a3833243ab4..7ac723f67c04 100644 --- a/lib/Tooling/Refactoring/RefactoringActions.cpp +++ b/lib/Tooling/Refactoring/RefactoringActions.cpp @@ -98,8 +98,8 @@ public: std::vector<std::unique_ptr<RefactoringAction>> createRefactoringActions() { std::vector<std::unique_ptr<RefactoringAction>> Actions; - Actions.push_back(llvm::make_unique<LocalRename>()); - Actions.push_back(llvm::make_unique<ExtractRefactoring>()); + Actions.push_back(std::make_unique<LocalRename>()); + Actions.push_back(std::make_unique<ExtractRefactoring>()); return Actions; } diff --git a/lib/Tooling/Refactoring/Rename/RenamingAction.cpp b/lib/Tooling/Refactoring/Rename/RenamingAction.cpp index 1649513a077a..b0634912e3fc 100644 --- a/lib/Tooling/Refactoring/Rename/RenamingAction.cpp +++ b/lib/Tooling/Refactoring/Rename/RenamingAction.cpp @@ -266,12 +266,12 @@ private: }; std::unique_ptr<ASTConsumer> RenamingAction::newASTConsumer() { - return llvm::make_unique<RenamingASTConsumer>(NewNames, PrevNames, USRList, + return std::make_unique<RenamingASTConsumer>(NewNames, PrevNames, USRList, FileToReplaces, PrintLocations); } std::unique_ptr<ASTConsumer> QualifiedRenamingAction::newASTConsumer() { - return llvm::make_unique<USRSymbolRenamer>(NewNames, USRList, FileToReplaces); + return std::make_unique<USRSymbolRenamer>(NewNames, USRList, FileToReplaces); } } // end namespace tooling diff --git a/lib/Tooling/Refactoring/Rename/SymbolOccurrences.cpp b/lib/Tooling/Refactoring/Rename/SymbolOccurrences.cpp index 8cc1ffaf4482..9e69d37e81ad 100644 --- a/lib/Tooling/Refactoring/Rename/SymbolOccurrences.cpp +++ b/lib/Tooling/Refactoring/Rename/SymbolOccurrences.cpp @@ -25,7 +25,7 @@ SymbolOccurrence::SymbolOccurrence(const SymbolName &Name, OccurrenceKind Kind, Locations[0], Locations[0].getLocWithOffset(NamePieces[0].size())); return; } - MultipleRanges = llvm::make_unique<SourceRange[]>(Locations.size()); + MultipleRanges = std::make_unique<SourceRange[]>(Locations.size()); RangeOrNumRanges.setBegin( SourceLocation::getFromRawEncoding(Locations.size())); for (const auto &Loc : llvm::enumerate(Locations)) { diff --git a/lib/Tooling/Refactoring/Rename/USRFindingAction.cpp b/lib/Tooling/Refactoring/Rename/USRFindingAction.cpp index 54c6f3e734b1..e26248f50c29 100644 --- a/lib/Tooling/Refactoring/Rename/USRFindingAction.cpp +++ b/lib/Tooling/Refactoring/Rename/USRFindingAction.cpp @@ -102,6 +102,10 @@ public: private: void handleCXXRecordDecl(const CXXRecordDecl *RecordDecl) { + if (!RecordDecl->getDefinition()) { + USRSet.insert(getUSRForDecl(RecordDecl)); + return; + } RecordDecl = RecordDecl->getDefinition(); if (const auto *ClassTemplateSpecDecl = dyn_cast<ClassTemplateSpecializationDecl>(RecordDecl)) @@ -264,7 +268,7 @@ private: }; std::unique_ptr<ASTConsumer> USRFindingAction::newASTConsumer() { - return llvm::make_unique<NamedDeclFindingConsumer>( + return std::make_unique<NamedDeclFindingConsumer>( SymbolOffsets, QualifiedNames, SpellingNames, USRList, Force, ErrorOccurred); } diff --git a/lib/Tooling/Refactoring/SourceCode.cpp b/lib/Tooling/Refactoring/SourceCode.cpp deleted file mode 100644 index 3a97e178bbd4..000000000000 --- a/lib/Tooling/Refactoring/SourceCode.cpp +++ /dev/null @@ -1,31 +0,0 @@ -//===--- 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 deleted file mode 100644 index 09eca21c3cef..000000000000 --- a/lib/Tooling/Refactoring/Stencil.cpp +++ /dev/null @@ -1,175 +0,0 @@ -//===--- 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 deleted file mode 100644 index 8e6fe6c7a940..000000000000 --- a/lib/Tooling/Refactoring/Transformer.cpp +++ /dev/null @@ -1,263 +0,0 @@ -//===--- 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 2aa5b5bf9d98..919b83beb357 100644 --- a/lib/Tooling/RefactoringCallbacks.cpp +++ b/lib/Tooling/RefactoringCallbacks.cpp @@ -66,7 +66,7 @@ private: }; std::unique_ptr<ASTConsumer> ASTMatchRefactorer::newASTConsumer() { - return llvm::make_unique<RefactoringASTConsumer>(*this); + return std::make_unique<RefactoringASTConsumer>(*this); } static Replacement replaceStmtWithText(SourceManager &Sources, const Stmt &From, diff --git a/lib/Tooling/StandaloneExecution.cpp b/lib/Tooling/StandaloneExecution.cpp index ad82ee083a40..09094c3c23f3 100644 --- a/lib/Tooling/StandaloneExecution.cpp +++ b/lib/Tooling/StandaloneExecution.cpp @@ -76,7 +76,7 @@ public: if (OptionsParser.getSourcePathList().empty()) return make_string_error( "[StandaloneToolExecutorPlugin] No positional argument found."); - return llvm::make_unique<StandaloneToolExecutor>(std::move(OptionsParser)); + return std::make_unique<StandaloneToolExecutor>(std::move(OptionsParser)); } }; diff --git a/lib/Tooling/Syntax/BuildTree.cpp b/lib/Tooling/Syntax/BuildTree.cpp index 03c439c59e39..a0b653df133d 100644 --- a/lib/Tooling/Syntax/BuildTree.cpp +++ b/lib/Tooling/Syntax/BuildTree.cpp @@ -58,8 +58,11 @@ public: /// Finish building the tree and consume the root node. syntax::TranslationUnit *finalize() && { auto Tokens = Arena.tokenBuffer().expandedTokens(); + assert(!Tokens.empty()); + assert(Tokens.back().kind() == tok::eof); + // Build the root of the tree, consuming all the children. - Pending.foldChildren(Tokens, + Pending.foldChildren(Tokens.drop_back(), new (Arena.allocator()) syntax::TranslationUnit); return cast<syntax::TranslationUnit>(std::move(Pending).finalize()); @@ -96,10 +99,11 @@ private: /// 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. - + assert(!A.tokenBuffer().expandedTokens().empty()); + assert(A.tokenBuffer().expandedTokens().back().kind() == tok::eof); // Create all leaf nodes. - for (auto &T : A.tokenBuffer().expandedTokens()) + // Note that we do not have 'eof' in the tree. + for (auto &T : A.tokenBuffer().expandedTokens().drop_back()) Trees.insert(Trees.end(), {&T, NodeAndRole{new (A.allocator()) syntax::Leaf(&T)}}); } diff --git a/lib/Tooling/Syntax/Tokens.cpp b/lib/Tooling/Syntax/Tokens.cpp index d82dc1f35c94..a2c3bc137d6b 100644 --- a/lib/Tooling/Syntax/Tokens.cpp +++ b/lib/Tooling/Syntax/Tokens.cpp @@ -232,6 +232,21 @@ TokenBuffer::expansionStartingAt(const syntax::Token *Spelled) const { return E; } +std::vector<const syntax::Token *> +TokenBuffer::macroExpansions(FileID FID) const { + auto FileIt = Files.find(FID); + assert(FileIt != Files.end() && "file not tracked by token buffer"); + auto &File = FileIt->second; + std::vector<const syntax::Token *> Expansions; + auto &Spelled = File.SpelledTokens; + for (auto Mapping : File.Mappings) { + const syntax::Token *Token = &Spelled[Mapping.BeginSpelled]; + if (Token->kind() == tok::TokenKind::identifier) + Expansions.push_back(Token); + } + return Expansions; +} + std::vector<syntax::Token> syntax::tokenize(FileID FID, const SourceManager &SM, const LangOptions &LO) { std::vector<syntax::Token> Tokens; @@ -321,7 +336,7 @@ TokenCollector::TokenCollector(Preprocessor &PP) : PP(PP) { }); // And locations of macro calls, to properly recover boundaries of those in // case of empty expansions. - auto CB = llvm::make_unique<CollectPPExpansions>(*this); + auto CB = std::make_unique<CollectPPExpansions>(*this); this->Collector = CB.get(); PP.addPPCallbacks(std::move(CB)); } diff --git a/lib/Tooling/Tooling.cpp b/lib/Tooling/Tooling.cpp index 291df0ae333d..1d6a968331b5 100644 --- a/lib/Tooling/Tooling.cpp +++ b/lib/Tooling/Tooling.cpp @@ -91,7 +91,34 @@ static const llvm::opt::ArgStringList *getCC1Arguments( // We expect to get back exactly one Command job, if we didn't something // failed. Extract that job from the Compilation. const driver::JobList &Jobs = Compilation->getJobs(); - if (Jobs.size() != 1 || !isa<driver::Command>(*Jobs.begin())) { + const driver::ActionList &Actions = Compilation->getActions(); + bool OffloadCompilation = false; + if (Jobs.size() > 1) { + for (auto A : Actions){ + // On MacOSX real actions may end up being wrapped in BindArchAction + if (isa<driver::BindArchAction>(A)) + A = *A->input_begin(); + if (isa<driver::OffloadAction>(A)) { + // Offload compilation has 2 top-level actions, one (at the front) is + // the original host compilation and the other is offload action + // composed of at least one device compilation. For such case, general + // tooling will consider host-compilation only. For tooling on device + // compilation, device compilation only option, such as + // `--cuda-device-only`, needs specifying. + assert(Actions.size() > 1); + assert( + isa<driver::CompileJobAction>(Actions.front()) || + // On MacOSX real actions may end up being wrapped in + // BindArchAction. + (isa<driver::BindArchAction>(Actions.front()) && + isa<driver::CompileJobAction>(*Actions.front()->input_begin()))); + OffloadCompilation = true; + break; + } + } + } + if (Jobs.size() == 0 || !isa<driver::Command>(*Jobs.begin()) || + (Jobs.size() > 1 && !OffloadCompilation)) { SmallString<256> error_msg; llvm::raw_svector_ostream error_stream(error_msg); Jobs.Print(error_stream, "; ", true); @@ -118,20 +145,18 @@ CompilerInvocation *newInvocation( DiagnosticsEngine *Diagnostics, const llvm::opt::ArgStringList &CC1Args) { assert(!CC1Args.empty() && "Must at least contain the program name!"); CompilerInvocation *Invocation = new CompilerInvocation; - CompilerInvocation::CreateFromArgs( - *Invocation, CC1Args.data() + 1, CC1Args.data() + CC1Args.size(), - *Diagnostics); + CompilerInvocation::CreateFromArgs(*Invocation, CC1Args, *Diagnostics); Invocation->getFrontendOpts().DisableFree = false; Invocation->getCodeGenOpts().DisableFree = false; return Invocation; } -bool runToolOnCode(FrontendAction *ToolAction, const Twine &Code, - const Twine &FileName, +bool runToolOnCode(std::unique_ptr<FrontendAction> ToolAction, + const Twine &Code, const Twine &FileName, std::shared_ptr<PCHContainerOperations> PCHContainerOps) { - return runToolOnCodeWithArgs(ToolAction, Code, std::vector<std::string>(), - FileName, "clang-tool", - std::move(PCHContainerOps)); + return runToolOnCodeWithArgs(std::move(ToolAction), Code, + std::vector<std::string>(), FileName, + "clang-tool", std::move(PCHContainerOps)); } } // namespace tooling @@ -153,7 +178,7 @@ namespace clang { namespace tooling { bool runToolOnCodeWithArgs( - FrontendAction *ToolAction, const Twine &Code, + std::unique_ptr<FrontendAction> ToolAction, const Twine &Code, llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> VFS, const std::vector<std::string> &Args, const Twine &FileName, const Twine &ToolName, @@ -166,13 +191,12 @@ bool runToolOnCodeWithArgs( ArgumentsAdjuster Adjuster = getClangStripDependencyFileAdjuster(); ToolInvocation Invocation( getSyntaxOnlyToolArgs(ToolName, Adjuster(Args, FileNameRef), FileNameRef), - ToolAction, Files.get(), - std::move(PCHContainerOps)); + std::move(ToolAction), Files.get(), std::move(PCHContainerOps)); return Invocation.run(); } bool runToolOnCodeWithArgs( - FrontendAction *ToolAction, const Twine &Code, + std::unique_ptr<FrontendAction> ToolAction, const Twine &Code, const std::vector<std::string> &Args, const Twine &FileName, const Twine &ToolName, std::shared_ptr<PCHContainerOperations> PCHContainerOps, @@ -194,8 +218,8 @@ bool runToolOnCodeWithArgs( llvm::MemoryBuffer::getMemBuffer(FilenameWithContent.second)); } - return runToolOnCodeWithArgs(ToolAction, Code, OverlayFileSystem, Args, - FileName, ToolName); + return runToolOnCodeWithArgs(std::move(ToolAction), Code, OverlayFileSystem, + Args, FileName, ToolName); } llvm::Expected<std::string> getAbsolutePath(llvm::vfs::FileSystem &FS, @@ -249,12 +273,15 @@ void addTargetAndModeForProgramName(std::vector<std::string> &CommandLine, namespace { class SingleFrontendActionFactory : public FrontendActionFactory { - FrontendAction *Action; + std::unique_ptr<FrontendAction> Action; public: - SingleFrontendActionFactory(FrontendAction *Action) : Action(Action) {} + SingleFrontendActionFactory(std::unique_ptr<FrontendAction> Action) + : Action(std::move(Action)) {} - FrontendAction *create() override { return Action; } + std::unique_ptr<FrontendAction> create() override { + return std::move(Action); + } }; } // namespace @@ -266,11 +293,13 @@ ToolInvocation::ToolInvocation( Files(Files), PCHContainerOps(std::move(PCHContainerOps)) {} ToolInvocation::ToolInvocation( - std::vector<std::string> CommandLine, FrontendAction *FAction, - FileManager *Files, std::shared_ptr<PCHContainerOperations> PCHContainerOps) + std::vector<std::string> CommandLine, + std::unique_ptr<FrontendAction> FAction, FileManager *Files, + std::shared_ptr<PCHContainerOperations> PCHContainerOps) : CommandLine(std::move(CommandLine)), - Action(new SingleFrontendActionFactory(FAction)), OwnsAction(true), - Files(Files), PCHContainerOps(std::move(PCHContainerOps)) {} + Action(new SingleFrontendActionFactory(std::move(FAction))), + OwnsAction(true), Files(Files), + PCHContainerOps(std::move(PCHContainerOps)) {} ToolInvocation::~ToolInvocation() { if (OwnsAction) @@ -290,8 +319,7 @@ bool ToolInvocation::run() { const char *const BinaryName = Argv[0]; IntrusiveRefCntPtr<DiagnosticOptions> DiagOpts = new DiagnosticOptions(); unsigned MissingArgIndex, MissingArgCount; - std::unique_ptr<llvm::opt::OptTable> Opts = driver::createDriverOptTable(); - llvm::opt::InputArgList ParsedArgs = Opts->ParseArgs( + llvm::opt::InputArgList ParsedArgs = driver::getDriverOptTable().ParseArgs( ArrayRef<const char *>(Argv).slice(1), MissingArgIndex, MissingArgCount); ParseDiagnosticArgs(*DiagOpts, ParsedArgs); TextDiagnosticPrinter DiagnosticPrinter( @@ -375,16 +403,20 @@ bool FrontendActionFactory::runInvocation( ClangTool::ClangTool(const CompilationDatabase &Compilations, ArrayRef<std::string> SourcePaths, std::shared_ptr<PCHContainerOperations> PCHContainerOps, - IntrusiveRefCntPtr<llvm::vfs::FileSystem> BaseFS) + IntrusiveRefCntPtr<llvm::vfs::FileSystem> BaseFS, + IntrusiveRefCntPtr<FileManager> Files) : Compilations(Compilations), SourcePaths(SourcePaths), PCHContainerOps(std::move(PCHContainerOps)), OverlayFileSystem(new llvm::vfs::OverlayFileSystem(std::move(BaseFS))), InMemoryFileSystem(new llvm::vfs::InMemoryFileSystem), - Files(new FileManager(FileSystemOptions(), OverlayFileSystem)) { + Files(Files ? Files + : new FileManager(FileSystemOptions(), OverlayFileSystem)) { OverlayFileSystem->pushOverlay(InMemoryFileSystem); appendArgumentsAdjuster(getClangStripOutputAdjuster()); appendArgumentsAdjuster(getClangSyntaxOnlyAdjuster()); appendArgumentsAdjuster(getClangStripDependencyFileAdjuster()); + if (Files) + Files->setVirtualFileSystem(OverlayFileSystem); } ClangTool::~ClangTool() = default; diff --git a/lib/Tooling/Transformer/CMakeLists.txt b/lib/Tooling/Transformer/CMakeLists.txt new file mode 100644 index 000000000000..68f0cfeee8f6 --- /dev/null +++ b/lib/Tooling/Transformer/CMakeLists.txt @@ -0,0 +1,18 @@ +set(LLVM_LINK_COMPONENTS Support) + +add_clang_library(clangTransformer + RangeSelector.cpp + RewriteRule.cpp + SourceCode.cpp + SourceCodeBuilders.cpp + Stencil.cpp + Transformer.cpp + + LINK_LIBS + clangAST + clangASTMatchers + clangBasic + clangLex + clangToolingCore + clangToolingRefactoring + ) diff --git a/lib/Tooling/Refactoring/RangeSelector.cpp b/lib/Tooling/Transformer/RangeSelector.cpp index 768c02e2277b..9f81423c9022 100644 --- a/lib/Tooling/Refactoring/RangeSelector.cpp +++ b/lib/Tooling/Transformer/RangeSelector.cpp @@ -6,12 +6,12 @@ // //===----------------------------------------------------------------------===// -#include "clang/Tooling/Refactoring/RangeSelector.h" +#include "clang/Tooling/Transformer/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 "clang/Tooling/Transformer/SourceCode.h" #include "llvm/ADT/StringRef.h" #include "llvm/Support/Errc.h" #include "llvm/Support/Error.h" @@ -20,7 +20,7 @@ #include <vector> using namespace clang; -using namespace tooling; +using namespace transformer; using ast_matchers::MatchFinder; using ast_type_traits::ASTNodeKind; @@ -104,7 +104,7 @@ static SourceLocation findOpenParen(const CallExpr &E, const SourceManager &SM, return findPreviousTokenKind(EndLoc, SM, LangOpts, tok::TokenKind::l_paren); } -RangeSelector tooling::before(RangeSelector Selector) { +RangeSelector transformer::before(RangeSelector Selector) { return [Selector](const MatchResult &Result) -> Expected<CharSourceRange> { Expected<CharSourceRange> SelectedRange = Selector(Result); if (!SelectedRange) @@ -113,7 +113,7 @@ RangeSelector tooling::before(RangeSelector Selector) { }; } -RangeSelector tooling::after(RangeSelector Selector) { +RangeSelector transformer::after(RangeSelector Selector) { return [Selector](const MatchResult &Result) -> Expected<CharSourceRange> { Expected<CharSourceRange> SelectedRange = Selector(Result); if (!SelectedRange) @@ -126,27 +126,29 @@ RangeSelector tooling::after(RangeSelector Selector) { }; } -RangeSelector tooling::node(std::string ID) { +RangeSelector transformer::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) + ? tooling::getExtendedRange(*Node, tok::TokenKind::semi, + *Result.Context) : CharSourceRange::getTokenRange(Node->getSourceRange()); }; } -RangeSelector tooling::statement(std::string ID) { +RangeSelector transformer::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); + return tooling::getExtendedRange(*Node, tok::TokenKind::semi, + *Result.Context); }; } -RangeSelector tooling::range(RangeSelector Begin, RangeSelector End) { +RangeSelector transformer::range(RangeSelector Begin, RangeSelector End) { return [Begin, End](const MatchResult &Result) -> Expected<CharSourceRange> { Expected<CharSourceRange> BeginRange = Begin(Result); if (!BeginRange) @@ -165,11 +167,11 @@ RangeSelector tooling::range(RangeSelector Begin, RangeSelector End) { }; } -RangeSelector tooling::range(std::string BeginID, std::string EndID) { - return tooling::range(node(std::move(BeginID)), node(std::move(EndID))); +RangeSelector transformer::range(std::string BeginID, std::string EndID) { + return transformer::range(node(std::move(BeginID)), node(std::move(EndID))); } -RangeSelector tooling::member(std::string ID) { +RangeSelector transformer::member(std::string ID) { return [ID](const MatchResult &Result) -> Expected<CharSourceRange> { Expected<DynTypedNode> Node = getNode(Result.Nodes, ID); if (!Node) @@ -181,7 +183,7 @@ RangeSelector tooling::member(std::string ID) { }; } -RangeSelector tooling::name(std::string ID) { +RangeSelector transformer::name(std::string ID) { return [ID](const MatchResult &Result) -> Expected<CharSourceRange> { Expected<DynTypedNode> N = getNode(Result.Nodes, ID); if (!N) @@ -197,7 +199,7 @@ RangeSelector tooling::name(std::string ID) { // `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()) + if (tooling::getText(R, *Result.Context) != D->getName()) return CharSourceRange(); return R; } @@ -219,6 +221,9 @@ RangeSelector tooling::name(std::string ID) { } namespace { +// FIXME: make this available in the public API for users to easily create their +// own selectors. + // 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. @@ -253,7 +258,7 @@ CharSourceRange getStatementsRange(const MatchResult &, } } // namespace -RangeSelector tooling::statements(std::string ID) { +RangeSelector transformer::statements(std::string ID) { return RelativeSelector<CompoundStmt, getStatementsRange>(std::move(ID)); } @@ -268,7 +273,7 @@ CharSourceRange getCallArgumentsRange(const MatchResult &Result, } } // namespace -RangeSelector tooling::callArgs(std::string ID) { +RangeSelector transformer::callArgs(std::string ID) { return RelativeSelector<CallExpr, getCallArgumentsRange>(std::move(ID)); } @@ -282,11 +287,24 @@ CharSourceRange getElementsRange(const MatchResult &, } } // namespace -RangeSelector tooling::initListElements(std::string ID) { +RangeSelector transformer::initListElements(std::string ID) { return RelativeSelector<InitListExpr, getElementsRange>(std::move(ID)); } -RangeSelector tooling::expansion(RangeSelector S) { +namespace { +// Returns the range of the else branch, including the `else` keyword. +CharSourceRange getElseRange(const MatchResult &Result, const IfStmt &S) { + return tooling::maybeExtendRange( + CharSourceRange::getTokenRange(S.getElseLoc(), S.getEndLoc()), + tok::TokenKind::semi, *Result.Context); +} +} // namespace + +RangeSelector transformer::elseBranch(std::string ID) { + return RelativeSelector<IfStmt, getElseRange>(std::move(ID)); +} + +RangeSelector transformer::expansion(RangeSelector S) { return [S](const MatchResult &Result) -> Expected<CharSourceRange> { Expected<CharSourceRange> SRange = S(Result); if (!SRange) diff --git a/lib/Tooling/Transformer/RewriteRule.cpp b/lib/Tooling/Transformer/RewriteRule.cpp new file mode 100644 index 000000000000..6fa558f7b2ee --- /dev/null +++ b/lib/Tooling/Transformer/RewriteRule.cpp @@ -0,0 +1,178 @@ +//===--- 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/Transformer/RewriteRule.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/ASTMatchers/ASTMatchers.h" +#include "clang/Basic/SourceLocation.h" +#include "clang/Tooling/Transformer/SourceCode.h" +#include "llvm/ADT/Optional.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/Support/Errc.h" +#include "llvm/Support/Error.h" +#include <map> +#include <string> +#include <utility> +#include <vector> + +using namespace clang; +using namespace transformer; + +using ast_matchers::MatchFinder; +using ast_matchers::internal::DynTypedMatcher; +using ast_type_traits::ASTNodeKind; + +using MatchResult = MatchFinder::MatchResult; + +Expected<SmallVector<transformer::detail::Transformation, 1>> +transformer::detail::translateEdits(const MatchResult &Result, + llvm::ArrayRef<ASTEdit> Edits) { + SmallVector<transformer::detail::Transformation, 1> Transformations; + for (const auto &Edit : Edits) { + Expected<CharSourceRange> Range = Edit.TargetRange(Result); + if (!Range) + return Range.takeError(); + llvm::Optional<CharSourceRange> EditRange = + tooling::getRangeForEdit(*Range, *Result.Context); + // FIXME: let user specify whether to treat this case as an error or ignore + // it as is currently done. + if (!EditRange) + return SmallVector<Transformation, 0>(); + auto Replacement = Edit.Replacement(Result); + if (!Replacement) + return Replacement.takeError(); + transformer::detail::Transformation T; + T.Range = *EditRange; + T.Replacement = std::move(*Replacement); + Transformations.push_back(std::move(T)); + } + return Transformations; +} + +ASTEdit transformer::change(RangeSelector S, TextGenerator Replacement) { + ASTEdit E; + E.TargetRange = std::move(S); + E.Replacement = std::move(Replacement); + return E; +} + +RewriteRule transformer::makeRule(DynTypedMatcher M, SmallVector<ASTEdit, 1> Edits, + TextGenerator Explanation) { + return RewriteRule{{RewriteRule::Case{ + std::move(M), std::move(Edits), std::move(Explanation), {}}}}; +} + +void transformer::addInclude(RewriteRule &Rule, StringRef Header, + IncludeFormat Format) { + for (auto &Case : Rule.Cases) + Case.AddedIncludes.emplace_back(Header.str(), Format); +} + +#ifndef NDEBUG +// Filters for supported matcher kinds. FIXME: Explicitly list the allowed kinds +// (all node matcher types except for `QualType` and `Type`), rather than just +// banning `QualType` and `Type`. +static bool hasValidKind(const DynTypedMatcher &M) { + return !M.canConvertTo<QualType>(); +} +#endif + +// Binds each rule's matcher to a unique (and deterministic) tag based on +// `TagBase` and the id paired with the case. +static std::vector<DynTypedMatcher> taggedMatchers( + StringRef TagBase, + const SmallVectorImpl<std::pair<size_t, RewriteRule::Case>> &Cases) { + std::vector<DynTypedMatcher> Matchers; + Matchers.reserve(Cases.size()); + for (const auto &Case : Cases) { + std::string Tag = (TagBase + Twine(Case.first)).str(); + // HACK: Many matchers are not bindable, so ensure that tryBind will work. + DynTypedMatcher BoundMatcher(Case.second.Matcher); + BoundMatcher.setAllowBind(true); + auto M = BoundMatcher.tryBind(Tag); + 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 transformer::applyFirst(ArrayRef<RewriteRule> Rules) { + RewriteRule R; + for (auto &Rule : Rules) + R.Cases.append(Rule.Cases.begin(), Rule.Cases.end()); + return R; +} + +std::vector<DynTypedMatcher> +transformer::detail::buildMatchers(const RewriteRule &Rule) { + // Map the cases into buckets of matchers -- one for each "root" AST kind, + // which guarantees that they can be combined in a single anyOf matcher. Each + // case is paired with an identifying number that is converted to a string id + // in `taggedMatchers`. + std::map<ASTNodeKind, SmallVector<std::pair<size_t, RewriteRule::Case>, 1>> + Buckets; + const SmallVectorImpl<RewriteRule::Case> &Cases = Rule.Cases; + for (int I = 0, N = Cases.size(); I < N; ++I) { + assert(hasValidKind(Cases[I].Matcher) && + "Matcher must be non-(Qual)Type node matcher"); + Buckets[Cases[I].Matcher.getSupportedKind()].emplace_back(I, Cases[I]); + } + + std::vector<DynTypedMatcher> Matchers; + for (const auto &Bucket : Buckets) { + DynTypedMatcher M = DynTypedMatcher::constructVariadic( + DynTypedMatcher::VO_AnyOf, Bucket.first, + taggedMatchers("Tag", Bucket.second)); + M.setAllowBind(true); + // `tryBind` is guaranteed to succeed, because `AllowBind` was set to true. + Matchers.push_back(*M.tryBind(RewriteRule::RootID)); + } + return Matchers; +} + +DynTypedMatcher transformer::detail::buildMatcher(const RewriteRule &Rule) { + std::vector<DynTypedMatcher> Ms = buildMatchers(Rule); + assert(Ms.size() == 1 && "Cases must have compatible matchers."); + return Ms[0]; +} + +SourceLocation transformer::detail::getRuleMatchLoc(const MatchResult &Result) { + auto &NodesMap = Result.Nodes.getMap(); + auto Root = NodesMap.find(RewriteRule::RootID); + assert(Root != NodesMap.end() && "Transformation failed: missing root node."); + llvm::Optional<CharSourceRange> RootRange = tooling::getRangeForEdit( + CharSourceRange::getTokenRange(Root->second.getSourceRange()), + *Result.Context); + if (RootRange) + return RootRange->getBegin(); + // The match doesn't have a coherent range, so fall back to the expansion + // location as the "beginning" of the match. + return Result.SourceManager->getExpansionLoc( + Root->second.getSourceRange().getBegin()); +} + +// Finds the case that was "selected" -- that is, whose matcher triggered the +// `MatchResult`. +const RewriteRule::Case & +transformer::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; diff --git a/lib/Tooling/Transformer/SourceCode.cpp b/lib/Tooling/Transformer/SourceCode.cpp new file mode 100644 index 000000000000..836401d1e605 --- /dev/null +++ b/lib/Tooling/Transformer/SourceCode.cpp @@ -0,0 +1,65 @@ +//===--- 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/Transformer/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()); +} + +llvm::Optional<CharSourceRange> +clang::tooling::getRangeForEdit(const CharSourceRange &EditRange, + const SourceManager &SM, + const LangOptions &LangOpts) { + // FIXME: makeFileCharRange() has the disadvantage of stripping off "identity" + // macros. For example, if we're looking to rewrite the int literal 3 to 6, + // and we have the following definition: + // #define DO_NOTHING(x) x + // then + // foo(DO_NOTHING(3)) + // will be rewritten to + // foo(6) + // rather than the arguably better + // foo(DO_NOTHING(6)) + // Decide whether the current behavior is desirable and modify if not. + CharSourceRange Range = Lexer::makeFileCharRange(EditRange, SM, LangOpts); + if (Range.isInvalid()) + return None; + + if (Range.getBegin().isMacroID() || Range.getEnd().isMacroID()) + return None; + if (SM.isInSystemHeader(Range.getBegin()) || + SM.isInSystemHeader(Range.getEnd())) + return None; + + std::pair<FileID, unsigned> BeginInfo = SM.getDecomposedLoc(Range.getBegin()); + std::pair<FileID, unsigned> EndInfo = SM.getDecomposedLoc(Range.getEnd()); + if (BeginInfo.first != EndInfo.first || + BeginInfo.second > EndInfo.second) + return None; + + return Range; +} diff --git a/lib/Tooling/Transformer/SourceCodeBuilders.cpp b/lib/Tooling/Transformer/SourceCodeBuilders.cpp new file mode 100644 index 000000000000..56ec45e8fd1d --- /dev/null +++ b/lib/Tooling/Transformer/SourceCodeBuilders.cpp @@ -0,0 +1,160 @@ +//===--- SourceCodeBuilder.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/Transformer/SourceCodeBuilders.h" +#include "clang/AST/ASTContext.h" +#include "clang/AST/Expr.h" +#include "clang/AST/ExprCXX.h" +#include "clang/Tooling/Transformer/SourceCode.h" +#include "llvm/ADT/Twine.h" +#include <string> + +using namespace clang; +using namespace tooling; + +const Expr *tooling::reallyIgnoreImplicit(const Expr &E) { + const Expr *Expr = E.IgnoreImplicit(); + if (const auto *CE = dyn_cast<CXXConstructExpr>(Expr)) { + if (CE->getNumArgs() > 0 && + CE->getArg(0)->getSourceRange() == Expr->getSourceRange()) + return CE->getArg(0)->IgnoreImplicit(); + } + return Expr; +} + +bool tooling::mayEverNeedParens(const Expr &E) { + const Expr *Expr = reallyIgnoreImplicit(E); + // We always want parens around unary, binary, and ternary operators, because + // they are lower precedence. + if (isa<UnaryOperator>(Expr) || isa<BinaryOperator>(Expr) || + isa<AbstractConditionalOperator>(Expr)) + return true; + + // We need parens around calls to all overloaded operators except: function + // calls, subscripts, and expressions that are already part of an (implicit) + // call to operator->. These latter are all in the same precedence level as + // dot/arrow and that level is left associative, so they don't need parens + // when appearing on the left. + if (const auto *Op = dyn_cast<CXXOperatorCallExpr>(Expr)) + return Op->getOperator() != OO_Call && Op->getOperator() != OO_Subscript && + Op->getOperator() != OO_Arrow; + + return false; +} + +bool tooling::needParensAfterUnaryOperator(const Expr &E) { + const Expr *Expr = reallyIgnoreImplicit(E); + if (isa<BinaryOperator>(Expr) || isa<AbstractConditionalOperator>(Expr)) + return true; + + if (const auto *Op = dyn_cast<CXXOperatorCallExpr>(Expr)) + return Op->getNumArgs() == 2 && Op->getOperator() != OO_PlusPlus && + Op->getOperator() != OO_MinusMinus && Op->getOperator() != OO_Call && + Op->getOperator() != OO_Subscript; + + return false; +} + +llvm::Optional<std::string> tooling::buildParens(const Expr &E, + const ASTContext &Context) { + StringRef Text = getText(E, Context); + if (Text.empty()) + return llvm::None; + if (mayEverNeedParens(E)) + return ("(" + Text + ")").str(); + return Text.str(); +} + +llvm::Optional<std::string> +tooling::buildDereference(const Expr &E, const ASTContext &Context) { + if (const auto *Op = dyn_cast<UnaryOperator>(&E)) + if (Op->getOpcode() == UO_AddrOf) { + // Strip leading '&'. + StringRef Text = + getText(*Op->getSubExpr()->IgnoreParenImpCasts(), Context); + if (Text.empty()) + return llvm::None; + return Text.str(); + } + + StringRef Text = getText(E, Context); + if (Text.empty()) + return llvm::None; + // Add leading '*'. + if (needParensAfterUnaryOperator(E)) + return ("*(" + Text + ")").str(); + return ("*" + Text).str(); +} + +llvm::Optional<std::string> tooling::buildAddressOf(const Expr &E, + const ASTContext &Context) { + if (const auto *Op = dyn_cast<UnaryOperator>(&E)) + if (Op->getOpcode() == UO_Deref) { + // Strip leading '*'. + StringRef Text = + getText(*Op->getSubExpr()->IgnoreParenImpCasts(), Context); + if (Text.empty()) + return llvm::None; + return Text.str(); + } + // Add leading '&'. + StringRef Text = getText(E, Context); + if (Text.empty()) + return llvm::None; + if (needParensAfterUnaryOperator(E)) { + return ("&(" + Text + ")").str(); + } + return ("&" + Text).str(); +} + +llvm::Optional<std::string> tooling::buildDot(const Expr &E, + const ASTContext &Context) { + if (const auto *Op = llvm::dyn_cast<UnaryOperator>(&E)) + if (Op->getOpcode() == UO_Deref) { + // Strip leading '*', add following '->'. + const Expr *SubExpr = Op->getSubExpr()->IgnoreParenImpCasts(); + StringRef DerefText = getText(*SubExpr, Context); + if (DerefText.empty()) + return llvm::None; + if (needParensBeforeDotOrArrow(*SubExpr)) + return ("(" + DerefText + ")->").str(); + return (DerefText + "->").str(); + } + + // Add following '.'. + StringRef Text = getText(E, Context); + if (Text.empty()) + return llvm::None; + if (needParensBeforeDotOrArrow(E)) { + return ("(" + Text + ").").str(); + } + return (Text + ".").str(); +} + +llvm::Optional<std::string> tooling::buildArrow(const Expr &E, + const ASTContext &Context) { + if (const auto *Op = llvm::dyn_cast<UnaryOperator>(&E)) + if (Op->getOpcode() == UO_AddrOf) { + // Strip leading '&', add following '.'. + const Expr *SubExpr = Op->getSubExpr()->IgnoreParenImpCasts(); + StringRef DerefText = getText(*SubExpr, Context); + if (DerefText.empty()) + return llvm::None; + if (needParensBeforeDotOrArrow(*SubExpr)) + return ("(" + DerefText + ").").str(); + return (DerefText + ".").str(); + } + + // Add following '->'. + StringRef Text = getText(E, Context); + if (Text.empty()) + return llvm::None; + if (needParensBeforeDotOrArrow(E)) + return ("(" + Text + ")->").str(); + return (Text + "->").str(); +} diff --git a/lib/Tooling/Transformer/Stencil.cpp b/lib/Tooling/Transformer/Stencil.cpp new file mode 100644 index 000000000000..984950a54e96 --- /dev/null +++ b/lib/Tooling/Transformer/Stencil.cpp @@ -0,0 +1,318 @@ +//===--- 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/Transformer/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/Transformer/SourceCode.h" +#include "clang/Tooling/Transformer/SourceCodeBuilders.h" +#include "llvm/ADT/Twine.h" +#include "llvm/Support/Errc.h" +#include <atomic> +#include <memory> +#include <string> + +using namespace clang; +using namespace transformer; + +using ast_matchers::MatchFinder; +using ast_type_traits::DynTypedNode; +using llvm::errc; +using llvm::Error; +using llvm::Expected; +using llvm::StringError; + +static llvm::Expected<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 DebugPrintNodeData { + explicit DebugPrintNodeData(std::string S) : Id(std::move(S)) {} + std::string Id; +}; + +// Operators that take a single node Id as an argument. +enum class UnaryNodeOperator { + Parens, + Deref, + Address, +}; + +// Generic container for stencil operations with a (single) node-id argument. +struct UnaryOperationData { + UnaryOperationData(UnaryNodeOperator Op, std::string Id) + : Op(Op), Id(std::move(Id)) {} + UnaryNodeOperator Op; + std::string Id; +}; + +// The fragment of code corresponding to the selected range. +struct SelectorData { + explicit SelectorData(RangeSelector S) : Selector(std::move(S)) {} + RangeSelector Selector; +}; + +// A stencil operation to build a member access `e.m` or `e->m`, as appropriate. +struct AccessData { + AccessData(StringRef BaseId, StencilPart Member) + : BaseId(BaseId), Member(std::move(Member)) {} + std::string BaseId; + StencilPart Member; +}; + +struct IfBoundData { + IfBoundData(StringRef Id, StencilPart TruePart, StencilPart FalsePart) + : Id(Id), TruePart(std::move(TruePart)), FalsePart(std::move(FalsePart)) { + } + std::string Id; + StencilPart TruePart; + StencilPart FalsePart; +}; + +std::string toStringData(const RawTextData &Data) { + std::string Result; + llvm::raw_string_ostream OS(Result); + OS << "\""; + OS.write_escaped(Data.Text); + OS << "\""; + OS.flush(); + return Result; +} + +std::string toStringData(const DebugPrintNodeData &Data) { + return (llvm::Twine("dPrint(\"") + Data.Id + "\")").str(); +} + +std::string toStringData(const UnaryOperationData &Data) { + StringRef OpName; + switch (Data.Op) { + case UnaryNodeOperator::Parens: + OpName = "expression"; + break; + case UnaryNodeOperator::Deref: + OpName = "deref"; + break; + case UnaryNodeOperator::Address: + OpName = "addressOf"; + break; + } + return (OpName + "(\"" + Data.Id + "\")").str(); +} + +std::string toStringData(const SelectorData &) { return "selection(...)"; } + +std::string toStringData(const AccessData &Data) { + return (llvm::Twine("access(\"") + Data.BaseId + "\", " + + Data.Member.toString() + ")") + .str(); +} + +std::string toStringData(const IfBoundData &Data) { + return (llvm::Twine("ifBound(\"") + Data.Id + "\", " + + Data.TruePart.toString() + ", " + Data.FalsePart.toString() + ")") + .str(); +} + +std::string toStringData(const MatchConsumer<std::string> &) { + return "run(...)"; +} + +// 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 DebugPrintNodeData &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 UnaryOperationData &Data, + const MatchFinder::MatchResult &Match, std::string *Result) { + const auto *E = Match.Nodes.getNodeAs<Expr>(Data.Id); + if (E == nullptr) + return llvm::make_error<StringError>( + errc::invalid_argument, "Id not bound or not Expr: " + Data.Id); + llvm::Optional<std::string> Source; + switch (Data.Op) { + case UnaryNodeOperator::Parens: + Source = tooling::buildParens(*E, *Match.Context); + break; + case UnaryNodeOperator::Deref: + Source = tooling::buildDereference(*E, *Match.Context); + break; + case UnaryNodeOperator::Address: + Source = tooling::buildAddressOf(*E, *Match.Context); + break; + } + if (!Source) + return llvm::make_error<StringError>( + errc::invalid_argument, + "Could not construct expression source from ID: " + Data.Id); + *Result += *Source; + return Error::success(); +} + +Error evalData(const SelectorData &Data, const MatchFinder::MatchResult &Match, + std::string *Result) { + auto Range = Data.Selector(Match); + if (!Range) + return Range.takeError(); + *Result += tooling::getText(*Range, *Match.Context); + return Error::success(); +} + +Error evalData(const AccessData &Data, const MatchFinder::MatchResult &Match, + std::string *Result) { + const auto *E = Match.Nodes.getNodeAs<Expr>(Data.BaseId); + if (E == nullptr) + return llvm::make_error<StringError>(errc::invalid_argument, + "Id not bound: " + Data.BaseId); + if (!E->isImplicitCXXThis()) { + if (llvm::Optional<std::string> S = + E->getType()->isAnyPointerType() + ? tooling::buildArrow(*E, *Match.Context) + : tooling::buildDot(*E, *Match.Context)) + *Result += *S; + else + return llvm::make_error<StringError>( + errc::invalid_argument, + "Could not construct object text from ID: " + Data.BaseId); + } + return Data.Member.eval(Match, Result); +} + +Error evalData(const IfBoundData &Data, const MatchFinder::MatchResult &Match, + std::string *Result) { + auto &M = Match.Nodes.getMap(); + return (M.find(Data.Id) != M.end() ? Data.TruePart : Data.FalsePart) + .eval(Match, Result); +} + +Error evalData(const MatchConsumer<std::string> &Fn, + const MatchFinder::MatchResult &Match, std::string *Result) { + Expected<std::string> Value = Fn(Match); + if (!Value) + return Value.takeError(); + *Result += *Value; + return Error::success(); +} + +template <typename T> +class StencilPartImpl : public StencilPartInterface { + T Data; + +public: + template <typename... Ps> + explicit StencilPartImpl(Ps &&... Args) : Data(std::forward<Ps>(Args)...) {} + + Error eval(const MatchFinder::MatchResult &Match, + std::string *Result) const override { + return evalData(Data, Match, Result); + } + + std::string toString() const override { return toStringData(Data); } +}; +} // namespace + +StencilPart Stencil::wrap(StringRef Text) { + return transformer::text(Text); +} + +StencilPart Stencil::wrap(RangeSelector Selector) { + return transformer::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 transformer::text(StringRef Text) { + return StencilPart(std::make_shared<StencilPartImpl<RawTextData>>(Text)); +} + +StencilPart transformer::selection(RangeSelector Selector) { + return StencilPart( + std::make_shared<StencilPartImpl<SelectorData>>(std::move(Selector))); +} + +StencilPart transformer::dPrint(StringRef Id) { + return StencilPart(std::make_shared<StencilPartImpl<DebugPrintNodeData>>(Id)); +} + +StencilPart transformer::expression(llvm::StringRef Id) { + return StencilPart(std::make_shared<StencilPartImpl<UnaryOperationData>>( + UnaryNodeOperator::Parens, Id)); +} + +StencilPart transformer::deref(llvm::StringRef ExprId) { + return StencilPart(std::make_shared<StencilPartImpl<UnaryOperationData>>( + UnaryNodeOperator::Deref, ExprId)); +} + +StencilPart transformer::addressOf(llvm::StringRef ExprId) { + return StencilPart(std::make_shared<StencilPartImpl<UnaryOperationData>>( + UnaryNodeOperator::Address, ExprId)); +} + +StencilPart transformer::access(StringRef BaseId, StencilPart Member) { + return StencilPart( + std::make_shared<StencilPartImpl<AccessData>>(BaseId, std::move(Member))); +} + +StencilPart transformer::ifBound(StringRef Id, StencilPart TruePart, + StencilPart FalsePart) { + return StencilPart(std::make_shared<StencilPartImpl<IfBoundData>>( + Id, std::move(TruePart), std::move(FalsePart))); +} + +StencilPart transformer::run(MatchConsumer<std::string> Fn) { + return StencilPart( + std::make_shared<StencilPartImpl<MatchConsumer<std::string>>>( + std::move(Fn))); +} diff --git a/lib/Tooling/Transformer/Transformer.cpp b/lib/Tooling/Transformer/Transformer.cpp new file mode 100644 index 000000000000..71f0646f4c0e --- /dev/null +++ b/lib/Tooling/Transformer/Transformer.cpp @@ -0,0 +1,72 @@ +//===--- 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/Transformer/Transformer.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/ASTMatchers/ASTMatchersInternal.h" +#include "clang/Basic/SourceLocation.h" +#include "clang/Tooling/Refactoring/AtomicChange.h" +#include "llvm/Support/Error.h" +#include <utility> +#include <vector> + +using namespace clang; +using namespace tooling; + +using ast_matchers::MatchFinder; + +void Transformer::registerMatchers(MatchFinder *MatchFinder) { + for (auto &Matcher : transformer::detail::buildMatchers(Rule)) + MatchFinder->addDynamicMatcher(Matcher, this); +} + +void Transformer::run(const MatchFinder::MatchResult &Result) { + if (Result.Context->getDiagnostics().hasErrorOccurred()) + return; + + transformer::RewriteRule::Case Case = + transformer::detail::findSelectedCase(Result, Rule); + auto Transformations = transformer::detail::translateEdits(Result, Case.Edits); + if (!Transformations) { + Consumer(Transformations.takeError()); + return; + } + + if (Transformations->empty()) { + // No rewrite applied (but no error encountered either). + transformer::detail::getRuleMatchLoc(Result).print( + llvm::errs() << "note: skipping match at loc ", *Result.SourceManager); + llvm::errs() << "\n"; + return; + } + + // Record the results in the AtomicChange, anchored at the location of the + // first change. + AtomicChange AC(*Result.SourceManager, + (*Transformations)[0].Range.getBegin()); + 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 transformer::IncludeFormat::Quoted: + AC.addHeader(Header); + break; + case transformer::IncludeFormat::Angled: + AC.addHeader((llvm::Twine("<") + Header + ">").str()); + break; + } + } + + Consumer(std::move(AC)); +} |