File indexing completed on 2024-04-14 05:32:07

0001 /*
0002     SPDX-FileCopyrightText: 2019 Christian Gagneraud <chgans@gmail.com>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include "FixItExporter.h"
0008 
0009 #include <clang/Basic/SourceManager.h>
0010 #include <clang/Frontend/FrontendDiagnostic.h>
0011 #include <clang/Rewrite/Frontend/FixItRewriter.h>
0012 #include <clang/Tooling/DiagnosticsYaml.h>
0013 
0014 // #define DEBUG_FIX_IT_EXPORTER
0015 
0016 using namespace clang;
0017 
0018 static clang::tooling::TranslationUnitDiagnostics &getTuDiag()
0019 {
0020     static clang::tooling::TranslationUnitDiagnostics s_tudiag;
0021     return s_tudiag;
0022 }
0023 
0024 FixItExporter::FixItExporter(DiagnosticsEngine &DiagEngine,
0025                              SourceManager &SourceMgr,
0026                              const LangOptions &LangOpts,
0027                              const std::string &exportFixes,
0028                              bool isClazyStandalone)
0029     : DiagEngine(DiagEngine)
0030     , SourceMgr(SourceMgr)
0031     , LangOpts(LangOpts)
0032     , exportFixes(exportFixes)
0033 {
0034     if (!isClazyStandalone) {
0035         // When using clazy as plugin each translation unit fixes goes to a separate YAML file
0036         getTuDiag().Diagnostics.clear();
0037     }
0038 
0039     Owner = DiagEngine.takeClient();
0040     Client = DiagEngine.getClient();
0041     DiagEngine.setClient(this, false);
0042 }
0043 
0044 FixItExporter::~FixItExporter()
0045 {
0046     if (Client) {
0047         DiagEngine.setClient(Client, Owner.release() != nullptr);
0048     }
0049 }
0050 
0051 void FixItExporter::BeginSourceFile(const LangOptions &LangOpts, const Preprocessor *PP)
0052 {
0053     if (Client) {
0054         Client->BeginSourceFile(LangOpts, PP);
0055     }
0056 
0057     const auto id = SourceMgr.getMainFileID();
0058     const auto *const entry = SourceMgr.getFileEntryForID(id);
0059     getTuDiag().MainSourceFile = static_cast<std::string>(entry->getName());
0060 }
0061 
0062 bool FixItExporter::IncludeInDiagnosticCounts() const
0063 {
0064     return Client ? Client->IncludeInDiagnosticCounts() : true;
0065 }
0066 
0067 void FixItExporter::EndSourceFile()
0068 {
0069     if (Client) {
0070         Client->EndSourceFile();
0071     }
0072 }
0073 
0074 tooling::Diagnostic FixItExporter::ConvertDiagnostic(const Diagnostic &Info)
0075 {
0076     SmallString<256> TmpMessageText;
0077     Info.FormatDiagnostic(TmpMessageText);
0078     // TODO: This returns an empty string: DiagEngine->getDiagnosticIDs()->getWarningOptionForDiag(Info.getID());
0079     // HACK: capture it at the end of the message: Message text [check-name]
0080 
0081     std::string checkName = static_cast<std::string>(DiagEngine.getDiagnosticIDs()->getWarningOptionForDiag(Info.getID()));
0082     std::string messageText;
0083 
0084     if (checkName.empty()) {
0085         // Non-built-in clang warnings have the [checkName] in the message
0086         messageText = TmpMessageText.slice(0, TmpMessageText.find_last_of('[') - 1).str();
0087 
0088         checkName = TmpMessageText.slice(TmpMessageText.find_last_of('[') + 3, TmpMessageText.find_last_of(']')).str();
0089     } else {
0090         messageText = TmpMessageText.c_str();
0091     }
0092 
0093     llvm::StringRef CurrentBuildDir; // Not needed?
0094 
0095     tooling::Diagnostic ToolingDiag(checkName, tooling::Diagnostic::Warning, CurrentBuildDir);
0096     // FIXME: Sometimes the file path is an empty string.
0097     if (Info.getLocation().isMacroID()) {
0098         auto MacroLoc = SourceMgr.getFileLoc(Info.getLocation());
0099         ToolingDiag.Message = tooling::DiagnosticMessage(messageText, SourceMgr, MacroLoc);
0100     } else {
0101         ToolingDiag.Message = tooling::DiagnosticMessage(messageText, SourceMgr, Info.getLocation());
0102     }
0103     return ToolingDiag;
0104 }
0105 
0106 tooling::Replacement FixItExporter::ConvertFixIt(const FixItHint &Hint)
0107 {
0108     // TODO: Proper handling of macros
0109     // https://stackoverflow.com/questions/24062989/clang-fails-replacing-a-statement-if-it-contains-a-macro
0110     tooling::Replacement Replacement;
0111     if (Hint.CodeToInsert.empty()) {
0112         if (Hint.InsertFromRange.isValid()) {
0113             clang::SourceLocation b(Hint.InsertFromRange.getBegin());
0114             clang::SourceLocation _e(Hint.InsertFromRange.getEnd());
0115             if (b.isMacroID()) {
0116                 b = SourceMgr.getSpellingLoc(b);
0117             }
0118             if (_e.isMacroID()) {
0119                 _e = SourceMgr.getSpellingLoc(_e);
0120             }
0121             clang::SourceLocation e(clang::Lexer::getLocForEndOfToken(_e, 0, SourceMgr, LangOpts));
0122             StringRef Text(SourceMgr.getCharacterData(b), SourceMgr.getCharacterData(e) - SourceMgr.getCharacterData(b));
0123             return tooling::Replacement(SourceMgr, Hint.RemoveRange, Text);
0124         }
0125         return tooling::Replacement(SourceMgr, Hint.RemoveRange, "");
0126     }
0127     return tooling::Replacement(SourceMgr, Hint.RemoveRange, Hint.CodeToInsert);
0128 }
0129 
0130 void FixItExporter::HandleDiagnostic(DiagnosticsEngine::Level DiagLevel, const Diagnostic &Info)
0131 {
0132     // Default implementation (Warnings/errors count).
0133     DiagnosticConsumer::HandleDiagnostic(DiagLevel, Info);
0134 
0135     // Let original client do it's handling
0136     if (Client) {
0137         Client->HandleDiagnostic(DiagLevel, Info);
0138     }
0139 
0140     // Convert and record warning diagnostics and their notes
0141     if (DiagLevel == DiagnosticsEngine::Warning) {
0142         auto ToolingDiag = ConvertDiagnostic(Info);
0143         for (unsigned Idx = 0, Last = Info.getNumFixItHints(); Idx < Last; ++Idx) {
0144             const FixItHint &Hint = Info.getFixItHint(Idx);
0145             const auto replacement = ConvertFixIt(Hint);
0146 #ifdef DEBUG_FIX_IT_EXPORTER
0147             const auto FileName = SourceMgr.getFilename(Info.getLocation());
0148             llvm::errs() << "Handling Fixit #" << Idx << " for " << FileName.str() << "\n";
0149             llvm::errs() << "F: " << Hint.RemoveRange.getBegin().printToString(SourceMgr) << ":" << Hint.RemoveRange.getEnd().printToString(SourceMgr) << " "
0150                          << Hint.InsertFromRange.getBegin().printToString(SourceMgr) << ":" << Hint.InsertFromRange.getEnd().printToString(SourceMgr) << " "
0151                          << Hint.BeforePreviousInsertions << " " << Hint.CodeToInsert << "\n";
0152             llvm::errs() << "R: " << replacement.toString() << "\n";
0153 #endif
0154             clang::tooling::Replacements &Replacements = ToolingDiag.Message.Fix[replacement.getFilePath()];
0155             llvm::Error error = Replacements.add(ConvertFixIt(Hint));
0156             if (error) {
0157                 Diag(Info.getLocation(), diag::note_fixit_failed);
0158             }
0159         }
0160         getTuDiag().Diagnostics.push_back(ToolingDiag);
0161         m_recordNotes = true;
0162     }
0163     // FIXME: We do not receive notes.
0164     else if (DiagLevel == DiagnosticsEngine::Note && m_recordNotes) {
0165 #ifdef DEBUG_FIX_IT_EXPORTER
0166         const auto FileName = SourceMgr.getFilename(Info.getLocation());
0167         llvm::errs() << "Handling Note for " << FileName.str() << "\n";
0168 #endif
0169         auto diags = getTuDiag().Diagnostics.back();
0170         auto diag = ConvertDiagnostic(Info);
0171         diags.Notes.append(1, diag.Message);
0172     } else {
0173         m_recordNotes = false;
0174     }
0175 }
0176 
0177 void FixItExporter::Export()
0178 {
0179     auto tuDiag = getTuDiag();
0180     if (!tuDiag.Diagnostics.empty()) {
0181         std::error_code EC;
0182         llvm::raw_fd_ostream OS(exportFixes, EC, llvm::sys::fs::OF_None);
0183         llvm::yaml::Output YAML(OS);
0184         YAML << getTuDiag();
0185     }
0186 }
0187 
0188 void FixItExporter::Diag(SourceLocation Loc, unsigned DiagID)
0189 {
0190     // When producing this diagnostic, we temporarily bypass ourselves,
0191     // clear out any current diagnostic, and let the downstream client
0192     // format the diagnostic.
0193     DiagEngine.setClient(Client, false);
0194     DiagEngine.Clear();
0195     DiagEngine.Report(Loc, DiagID);
0196     DiagEngine.setClient(this, false);
0197 }