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