| //===--- FrontendActions.cpp ----------------------------------------------===// |
| // |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| // See https://llvm.org/LICENSE.txt for license information. |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #include "clang/Rewrite/Frontend/FrontendActions.h" |
| #include "clang/AST/ASTConsumer.h" |
| #include "clang/Basic/CharInfo.h" |
| #include "clang/Basic/LangStandard.h" |
| #include "clang/Config/config.h" |
| #include "clang/Frontend/CompilerInstance.h" |
| #include "clang/Frontend/FrontendActions.h" |
| #include "clang/Frontend/FrontendDiagnostic.h" |
| #include "clang/Frontend/Utils.h" |
| #include "clang/Lex/Preprocessor.h" |
| #include "clang/Lex/PreprocessorOptions.h" |
| #include "clang/Rewrite/Frontend/ASTConsumers.h" |
| #include "clang/Rewrite/Frontend/FixItRewriter.h" |
| #include "clang/Rewrite/Frontend/Rewriters.h" |
| #include "clang/Serialization/ASTReader.h" |
| #include "clang/Serialization/ModuleFile.h" |
| #include "clang/Serialization/ModuleManager.h" |
| #include "llvm/ADT/DenseSet.h" |
| #include "llvm/Support/CrashRecoveryContext.h" |
| #include "llvm/Support/FileSystem.h" |
| #include "llvm/Support/Path.h" |
| #include "llvm/Support/raw_ostream.h" |
| #include <memory> |
| #include <utility> |
| |
| using namespace clang; |
| |
| //===----------------------------------------------------------------------===// |
| // AST Consumer Actions |
| //===----------------------------------------------------------------------===// |
| |
| std::unique_ptr<ASTConsumer> |
| HTMLPrintAction::CreateASTConsumer(CompilerInstance &CI, StringRef InFile) { |
| if (std::unique_ptr<raw_ostream> OS = |
| CI.createDefaultOutputFile(false, InFile)) |
| return CreateHTMLPrinter(std::move(OS), CI.getPreprocessor()); |
| return nullptr; |
| } |
| |
| FixItAction::FixItAction() {} |
| FixItAction::~FixItAction() {} |
| |
| std::unique_ptr<ASTConsumer> |
| FixItAction::CreateASTConsumer(CompilerInstance &CI, StringRef InFile) { |
| return std::make_unique<ASTConsumer>(); |
| } |
| |
| namespace { |
| class FixItRewriteInPlace : public FixItOptions { |
| public: |
| FixItRewriteInPlace() { InPlace = true; } |
| |
| std::string RewriteFilename(const std::string &Filename, int &fd) override { |
| llvm_unreachable("don't call RewriteFilename for inplace rewrites"); |
| } |
| }; |
| |
| class FixItActionSuffixInserter : public FixItOptions { |
| std::string NewSuffix; |
| |
| public: |
| FixItActionSuffixInserter(std::string NewSuffix, bool FixWhatYouCan) |
| : NewSuffix(std::move(NewSuffix)) { |
| this->FixWhatYouCan = FixWhatYouCan; |
| } |
| |
| std::string RewriteFilename(const std::string &Filename, int &fd) override { |
| fd = -1; |
| SmallString<128> Path(Filename); |
| llvm::sys::path::replace_extension(Path, |
| NewSuffix + llvm::sys::path::extension(Path)); |
| return std::string(Path.str()); |
| } |
| }; |
| |
| class FixItRewriteToTemp : public FixItOptions { |
| public: |
| std::string RewriteFilename(const std::string &Filename, int &fd) override { |
| SmallString<128> Path; |
| llvm::sys::fs::createTemporaryFile(llvm::sys::path::filename(Filename), |
| llvm::sys::path::extension(Filename).drop_front(), fd, |
| Path); |
| return std::string(Path.str()); |
| } |
| }; |
| } // end anonymous namespace |
| |
| bool FixItAction::BeginSourceFileAction(CompilerInstance &CI) { |
| const FrontendOptions &FEOpts = getCompilerInstance().getFrontendOpts(); |
| if (!FEOpts.FixItSuffix.empty()) { |
| FixItOpts.reset(new FixItActionSuffixInserter(FEOpts.FixItSuffix, |
| FEOpts.FixWhatYouCan)); |
| } else { |
| FixItOpts.reset(new FixItRewriteInPlace); |
| FixItOpts->FixWhatYouCan = FEOpts.FixWhatYouCan; |
| } |
| Rewriter.reset(new FixItRewriter(CI.getDiagnostics(), CI.getSourceManager(), |
| CI.getLangOpts(), FixItOpts.get())); |
| return true; |
| } |
| |
| void FixItAction::EndSourceFileAction() { |
| // Otherwise rewrite all files. |
| Rewriter->WriteFixedFiles(); |
| } |
| |
| bool FixItRecompile::BeginInvocation(CompilerInstance &CI) { |
| |
| std::vector<std::pair<std::string, std::string> > RewrittenFiles; |
| bool err = false; |
| { |
| const FrontendOptions &FEOpts = CI.getFrontendOpts(); |
| std::unique_ptr<FrontendAction> FixAction(new SyntaxOnlyAction()); |
| if (FixAction->BeginSourceFile(CI, FEOpts.Inputs[0])) { |
| std::unique_ptr<FixItOptions> FixItOpts; |
| if (FEOpts.FixToTemporaries) |
| FixItOpts.reset(new FixItRewriteToTemp()); |
| else |
| FixItOpts.reset(new FixItRewriteInPlace()); |
| FixItOpts->Silent = true; |
| FixItOpts->FixWhatYouCan = FEOpts.FixWhatYouCan; |
| FixItOpts->FixOnlyWarnings = FEOpts.FixOnlyWarnings; |
| FixItRewriter Rewriter(CI.getDiagnostics(), CI.getSourceManager(), |
| CI.getLangOpts(), FixItOpts.get()); |
| if (llvm::Error Err = FixAction->Execute()) { |
| // FIXME this drops the error on the floor. |
| consumeError(std::move(Err)); |
| return false; |
| } |
| |
| err = Rewriter.WriteFixedFiles(&RewrittenFiles); |
| |
| FixAction->EndSourceFile(); |
| CI.setSourceManager(nullptr); |
| CI.setFileManager(nullptr); |
| } else { |
| err = true; |
| } |
| } |
| if (err) |
| return false; |
| CI.getDiagnosticClient().clear(); |
| CI.getDiagnostics().Reset(); |
| |
| PreprocessorOptions &PPOpts = CI.getPreprocessorOpts(); |
| PPOpts.RemappedFiles.insert(PPOpts.RemappedFiles.end(), |
| RewrittenFiles.begin(), RewrittenFiles.end()); |
| PPOpts.RemappedFilesKeepOriginalName = false; |
| |
| return true; |
| } |
| |
| #if CLANG_ENABLE_OBJC_REWRITER |
| |
| std::unique_ptr<ASTConsumer> |
| RewriteObjCAction::CreateASTConsumer(CompilerInstance &CI, StringRef InFile) { |
| if (std::unique_ptr<raw_ostream> OS = |
| CI.createDefaultOutputFile(false, InFile, "cpp")) { |
| if (CI.getLangOpts().ObjCRuntime.isNonFragile()) |
| return CreateModernObjCRewriter( |
| std::string(InFile), std::move(OS), CI.getDiagnostics(), |
| CI.getLangOpts(), CI.getDiagnosticOpts().NoRewriteMacros, |
| (CI.getCodeGenOpts().getDebugInfo() != codegenoptions::NoDebugInfo)); |
| return CreateObjCRewriter(std::string(InFile), std::move(OS), |
| CI.getDiagnostics(), CI.getLangOpts(), |
| CI.getDiagnosticOpts().NoRewriteMacros); |
| } |
| return nullptr; |
| } |
| |
| #endif |
| |
| //===----------------------------------------------------------------------===// |
| // Preprocessor Actions |
| //===----------------------------------------------------------------------===// |
| |
| void RewriteMacrosAction::ExecuteAction() { |
| CompilerInstance &CI = getCompilerInstance(); |
| std::unique_ptr<raw_ostream> OS = |
| CI.createDefaultOutputFile(/*Binary=*/true, getCurrentFileOrBufferName()); |
| if (!OS) return; |
| |
| RewriteMacrosInInput(CI.getPreprocessor(), OS.get()); |
| } |
| |
| void RewriteTestAction::ExecuteAction() { |
| CompilerInstance &CI = getCompilerInstance(); |
| std::unique_ptr<raw_ostream> OS = |
| CI.createDefaultOutputFile(/*Binary=*/false, getCurrentFileOrBufferName()); |
| if (!OS) return; |
| |
| DoRewriteTest(CI.getPreprocessor(), OS.get()); |
| } |
| |
| class RewriteIncludesAction::RewriteImportsListener : public ASTReaderListener { |
| CompilerInstance &CI; |
| std::weak_ptr<raw_ostream> Out; |
| |
| llvm::DenseSet<const FileEntry*> Rewritten; |
| |
| public: |
| RewriteImportsListener(CompilerInstance &CI, std::shared_ptr<raw_ostream> Out) |
| : CI(CI), Out(Out) {} |
| |
| void visitModuleFile(StringRef Filename, |
| serialization::ModuleKind Kind) override { |
| auto File = CI.getFileManager().getFile(Filename); |
| assert(File && "missing file for loaded module?"); |
| |
| // Only rewrite each module file once. |
| if (!Rewritten.insert(*File).second) |
| return; |
| |
| serialization::ModuleFile *MF = |
| CI.getASTReader()->getModuleManager().lookup(*File); |
| assert(MF && "missing module file for loaded module?"); |
| |
| // Not interested in PCH / preambles. |
| if (!MF->isModule()) |
| return; |
| |
| auto OS = Out.lock(); |
| assert(OS && "loaded module file after finishing rewrite action?"); |
| |
| (*OS) << "#pragma clang module build "; |
| if (isValidAsciiIdentifier(MF->ModuleName)) |
| (*OS) << MF->ModuleName; |
| else { |
| (*OS) << '"'; |
| OS->write_escaped(MF->ModuleName); |
| (*OS) << '"'; |
| } |
| (*OS) << '\n'; |
| |
| // Rewrite the contents of the module in a separate compiler instance. |
| CompilerInstance Instance(CI.getPCHContainerOperations(), |
| &CI.getModuleCache()); |
| Instance.setInvocation( |
| std::make_shared<CompilerInvocation>(CI.getInvocation())); |
| Instance.createDiagnostics( |
| new ForwardingDiagnosticConsumer(CI.getDiagnosticClient()), |
| /*ShouldOwnClient=*/true); |
| Instance.getFrontendOpts().DisableFree = false; |
| Instance.getFrontendOpts().Inputs.clear(); |
| Instance.getFrontendOpts().Inputs.emplace_back( |
| Filename, InputKind(Language::Unknown, InputKind::Precompiled)); |
| Instance.getFrontendOpts().ModuleFiles.clear(); |
| Instance.getFrontendOpts().ModuleMapFiles.clear(); |
| // Don't recursively rewrite imports. We handle them all at the top level. |
| Instance.getPreprocessorOutputOpts().RewriteImports = false; |
| |
| llvm::CrashRecoveryContext().RunSafelyOnThread([&]() { |
| RewriteIncludesAction Action; |
| Action.OutputStream = OS; |
| Instance.ExecuteAction(Action); |
| }); |
| |
| (*OS) << "#pragma clang module endbuild /*" << MF->ModuleName << "*/\n"; |
| } |
| }; |
| |
| bool RewriteIncludesAction::BeginSourceFileAction(CompilerInstance &CI) { |
| if (!OutputStream) { |
| OutputStream = |
| CI.createDefaultOutputFile(/*Binary=*/true, getCurrentFileOrBufferName()); |
| if (!OutputStream) |
| return false; |
| } |
| |
| auto &OS = *OutputStream; |
| |
| // If we're preprocessing a module map, start by dumping the contents of the |
| // module itself before switching to the input buffer. |
| auto &Input = getCurrentInput(); |
| if (Input.getKind().getFormat() == InputKind::ModuleMap) { |
| if (Input.isFile()) { |
| OS << "# 1 \""; |
| OS.write_escaped(Input.getFile()); |
| OS << "\"\n"; |
| } |
| getCurrentModule()->print(OS); |
| OS << "#pragma clang module contents\n"; |
| } |
| |
| // If we're rewriting imports, set up a listener to track when we import |
| // module files. |
| if (CI.getPreprocessorOutputOpts().RewriteImports) { |
| CI.createASTReader(); |
| CI.getASTReader()->addListener( |
| std::make_unique<RewriteImportsListener>(CI, OutputStream)); |
| } |
| |
| return true; |
| } |
| |
| void RewriteIncludesAction::ExecuteAction() { |
| CompilerInstance &CI = getCompilerInstance(); |
| |
| // If we're rewriting imports, emit the module build output first rather |
| // than switching back and forth (potentially in the middle of a line). |
| if (CI.getPreprocessorOutputOpts().RewriteImports) { |
| std::string Buffer; |
| llvm::raw_string_ostream OS(Buffer); |
| |
| RewriteIncludesInInput(CI.getPreprocessor(), &OS, |
| CI.getPreprocessorOutputOpts()); |
| |
| (*OutputStream) << OS.str(); |
| } else { |
| RewriteIncludesInInput(CI.getPreprocessor(), OutputStream.get(), |
| CI.getPreprocessorOutputOpts()); |
| } |
| |
| OutputStream.reset(); |
| } |