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