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 }