1 //===- FixItRewriter.cpp - Fix-It Rewriter Diagnostic Client --------------===// 2 // 3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 4 // See https://llvm.org/LICENSE.txt for license information. 5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 6 // 7 //===----------------------------------------------------------------------===// 8 // 9 // This is a diagnostic client adaptor that performs rewrites as 10 // suggested by code modification hints attached to diagnostics. It 11 // then forwards any diagnostics to the adapted diagnostic client. 12 // 13 //===----------------------------------------------------------------------===// 14 15 #include "clang/Rewrite/Frontend/FixItRewriter.h" 16 #include "clang/Basic/Diagnostic.h" 17 #include "clang/Basic/FileManager.h" 18 #include "clang/Basic/LLVM.h" 19 #include "clang/Basic/SourceLocation.h" 20 #include "clang/Basic/SourceManager.h" 21 #include "clang/Edit/Commit.h" 22 #include "clang/Edit/EditsReceiver.h" 23 #include "clang/Frontend/FrontendDiagnostic.h" 24 #include "clang/Rewrite/Core/Rewriter.h" 25 #include "llvm/ADT/RewriteBuffer.h" 26 #include "llvm/ADT/StringRef.h" 27 #include "llvm/Support/FileSystem.h" 28 #include "llvm/Support/raw_ostream.h" 29 #include <cstdio> 30 #include <memory> 31 #include <string> 32 #include <system_error> 33 #include <utility> 34 35 using namespace clang; 36 using llvm::RewriteBuffer; 37 38 FixItRewriter::FixItRewriter(DiagnosticsEngine &Diags, SourceManager &SourceMgr, 39 const LangOptions &LangOpts, 40 FixItOptions *FixItOpts) 41 : Diags(Diags), Editor(SourceMgr, LangOpts), Rewrite(SourceMgr, LangOpts), 42 FixItOpts(FixItOpts) { 43 Owner = Diags.takeClient(); 44 Client = Diags.getClient(); 45 Diags.setClient(this, false); 46 } 47 48 FixItRewriter::~FixItRewriter() { 49 Diags.setClient(Client, Owner.release() != nullptr); 50 } 51 52 bool FixItRewriter::WriteFixedFile(FileID ID, raw_ostream &OS) { 53 const RewriteBuffer *RewriteBuf = Rewrite.getRewriteBufferFor(ID); 54 if (!RewriteBuf) return true; 55 RewriteBuf->write(OS); 56 OS.flush(); 57 return false; 58 } 59 60 namespace { 61 62 class RewritesReceiver : public edit::EditsReceiver { 63 Rewriter &Rewrite; 64 65 public: 66 RewritesReceiver(Rewriter &Rewrite) : Rewrite(Rewrite) {} 67 68 void insert(SourceLocation loc, StringRef text) override { 69 Rewrite.InsertText(loc, text); 70 } 71 72 void replace(CharSourceRange range, StringRef text) override { 73 Rewrite.ReplaceText(range.getBegin(), Rewrite.getRangeSize(range), text); 74 } 75 }; 76 77 } // namespace 78 79 bool FixItRewriter::WriteFixedFiles( 80 std::vector<std::pair<std::string, std::string>> *RewrittenFiles) { 81 if (NumFailures > 0 && !FixItOpts->FixWhatYouCan) { 82 Diag(FullSourceLoc(), diag::warn_fixit_no_changes); 83 return true; 84 } 85 86 RewritesReceiver Rec(Rewrite); 87 Editor.applyRewrites(Rec); 88 89 if (FixItOpts->InPlace) { 90 // Overwriting open files on Windows is tricky, but the rewriter can do it 91 // for us. 92 Rewrite.overwriteChangedFiles(); 93 return false; 94 } 95 96 for (iterator I = buffer_begin(), E = buffer_end(); I != E; ++I) { 97 OptionalFileEntryRef Entry = 98 Rewrite.getSourceMgr().getFileEntryRefForID(I->first); 99 int fd; 100 std::string Filename = 101 FixItOpts->RewriteFilename(std::string(Entry->getName()), fd); 102 std::error_code EC; 103 std::unique_ptr<llvm::raw_fd_ostream> OS; 104 if (fd != -1) { 105 OS.reset(new llvm::raw_fd_ostream(fd, /*shouldClose=*/true)); 106 } else { 107 OS.reset(new llvm::raw_fd_ostream(Filename, EC, llvm::sys::fs::OF_None)); 108 } 109 if (EC) { 110 Diags.Report(clang::diag::err_fe_unable_to_open_output) << Filename 111 << EC.message(); 112 continue; 113 } 114 RewriteBuffer &RewriteBuf = I->second; 115 RewriteBuf.write(*OS); 116 OS->flush(); 117 118 if (RewrittenFiles) 119 RewrittenFiles->push_back( 120 std::make_pair(std::string(Entry->getName()), Filename)); 121 } 122 123 return false; 124 } 125 126 bool FixItRewriter::IncludeInDiagnosticCounts() const { 127 return Client ? Client->IncludeInDiagnosticCounts() : true; 128 } 129 130 void FixItRewriter::HandleDiagnostic(DiagnosticsEngine::Level DiagLevel, 131 const Diagnostic &Info) { 132 // Default implementation (Warnings/errors count). 133 DiagnosticConsumer::HandleDiagnostic(DiagLevel, Info); 134 135 if (!FixItOpts->Silent || 136 DiagLevel >= DiagnosticsEngine::Error || 137 (DiagLevel == DiagnosticsEngine::Note && !PrevDiagSilenced) || 138 (DiagLevel > DiagnosticsEngine::Note && Info.getNumFixItHints())) { 139 Client->HandleDiagnostic(DiagLevel, Info); 140 PrevDiagSilenced = false; 141 } else { 142 PrevDiagSilenced = true; 143 } 144 145 // Skip over any diagnostics that are ignored or notes. 146 if (DiagLevel <= DiagnosticsEngine::Note) 147 return; 148 // Skip over errors if we are only fixing warnings. 149 if (DiagLevel >= DiagnosticsEngine::Error && FixItOpts->FixOnlyWarnings) { 150 ++NumFailures; 151 return; 152 } 153 154 // Make sure that we can perform all of the modifications we 155 // in this diagnostic. 156 edit::Commit commit(Editor); 157 for (unsigned Idx = 0, Last = Info.getNumFixItHints(); 158 Idx < Last; ++Idx) { 159 const FixItHint &Hint = Info.getFixItHint(Idx); 160 161 if (Hint.CodeToInsert.empty()) { 162 if (Hint.InsertFromRange.isValid()) 163 commit.insertFromRange(Hint.RemoveRange.getBegin(), 164 Hint.InsertFromRange, /*afterToken=*/false, 165 Hint.BeforePreviousInsertions); 166 else 167 commit.remove(Hint.RemoveRange); 168 } else { 169 if (Hint.RemoveRange.isTokenRange() || 170 Hint.RemoveRange.getBegin() != Hint.RemoveRange.getEnd()) 171 commit.replace(Hint.RemoveRange, Hint.CodeToInsert); 172 else 173 commit.insert(Hint.RemoveRange.getBegin(), Hint.CodeToInsert, 174 /*afterToken=*/false, Hint.BeforePreviousInsertions); 175 } 176 } 177 bool CanRewrite = Info.getNumFixItHints() > 0 && commit.isCommitable(); 178 179 if (!CanRewrite) { 180 if (Info.getNumFixItHints() > 0) 181 Diag(Info.getLocation(), diag::note_fixit_in_macro); 182 183 // If this was an error, refuse to perform any rewriting. 184 if (DiagLevel >= DiagnosticsEngine::Error) { 185 if (++NumFailures == 1) 186 Diag(Info.getLocation(), diag::note_fixit_unfixed_error); 187 } 188 return; 189 } 190 191 if (!Editor.commit(commit)) { 192 ++NumFailures; 193 Diag(Info.getLocation(), diag::note_fixit_failed); 194 return; 195 } 196 197 Diag(Info.getLocation(), diag::note_fixit_applied); 198 } 199 200 /// Emit a diagnostic via the adapted diagnostic client. 201 void FixItRewriter::Diag(SourceLocation Loc, unsigned DiagID) { 202 // When producing this diagnostic, we temporarily bypass ourselves, 203 // and let the downstream client format the diagnostic. 204 Diags.setClient(Client, false); 205 Diags.Report(Loc, DiagID); 206 Diags.setClient(this, false); 207 } 208 209 FixItOptions::~FixItOptions() = default; 210