1 | //===-- CXLoadedDiagnostic.cpp - Handling of persisent diags ----*- C++ -*-===// |
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 | // Implements handling of persisent diagnostics. |
10 | // |
11 | //===----------------------------------------------------------------------===// |
12 | |
13 | #include "CXLoadedDiagnostic.h" |
14 | #include "CXFile.h" |
15 | #include "CXString.h" |
16 | #include "clang/Basic/Diagnostic.h" |
17 | #include "clang/Basic/FileManager.h" |
18 | #include "clang/Basic/LLVM.h" |
19 | #include "clang/Frontend/SerializedDiagnosticReader.h" |
20 | #include "clang/Frontend/SerializedDiagnostics.h" |
21 | #include "llvm/ADT/STLExtras.h" |
22 | #include "llvm/ADT/StringRef.h" |
23 | #include "llvm/ADT/Twine.h" |
24 | #include "llvm/Bitstream/BitstreamReader.h" |
25 | #include "llvm/Support/ErrorHandling.h" |
26 | |
27 | using namespace clang; |
28 | |
29 | //===----------------------------------------------------------------------===// |
30 | // Extend CXDiagnosticSetImpl which contains strings for diagnostics. |
31 | //===----------------------------------------------------------------------===// |
32 | |
33 | typedef llvm::DenseMap<unsigned, const char *> Strings; |
34 | |
35 | namespace { |
36 | class CXLoadedDiagnosticSetImpl : public CXDiagnosticSetImpl { |
37 | public: |
38 | CXLoadedDiagnosticSetImpl() : CXDiagnosticSetImpl(true), FakeFiles(FO) {} |
39 | ~CXLoadedDiagnosticSetImpl() override {} |
40 | |
41 | llvm::BumpPtrAllocator Alloc; |
42 | Strings Categories; |
43 | Strings WarningFlags; |
44 | Strings FileNames; |
45 | |
46 | FileSystemOptions FO; |
47 | FileManager FakeFiles; |
48 | llvm::DenseMap<unsigned, FileEntryRef> Files; |
49 | |
50 | /// Copy the string into our own allocator. |
51 | const char *copyString(StringRef Blob) { |
52 | char *mem = Alloc.Allocate<char>(Num: Blob.size() + 1); |
53 | memcpy(dest: mem, src: Blob.data(), n: Blob.size()); |
54 | mem[Blob.size()] = '\0'; |
55 | return mem; |
56 | } |
57 | }; |
58 | } // end anonymous namespace |
59 | |
60 | //===----------------------------------------------------------------------===// |
61 | // Cleanup. |
62 | //===----------------------------------------------------------------------===// |
63 | |
64 | CXLoadedDiagnostic::~CXLoadedDiagnostic() {} |
65 | |
66 | //===----------------------------------------------------------------------===// |
67 | // Public CXLoadedDiagnostic methods. |
68 | //===----------------------------------------------------------------------===// |
69 | |
70 | CXDiagnosticSeverity CXLoadedDiagnostic::getSeverity() const { |
71 | // FIXME: Fail more softly if the diagnostic level is unknown? |
72 | auto severityAsLevel = static_cast<serialized_diags::Level>(severity); |
73 | assert(severity == static_cast<unsigned>(severityAsLevel) && |
74 | "unknown serialized diagnostic level" ); |
75 | |
76 | switch (severityAsLevel) { |
77 | #define CASE(X) case serialized_diags::X: return CXDiagnostic_##X; |
78 | CASE(Ignored) |
79 | CASE(Note) |
80 | CASE(Warning) |
81 | CASE(Error) |
82 | CASE(Fatal) |
83 | #undef CASE |
84 | // The 'Remark' level isn't represented in the stable API. |
85 | case serialized_diags::Remark: return CXDiagnostic_Warning; |
86 | } |
87 | |
88 | llvm_unreachable("Invalid diagnostic level" ); |
89 | } |
90 | |
91 | static CXSourceLocation makeLocation(const CXLoadedDiagnostic::Location *DLoc) { |
92 | // The lowest bit of ptr_data[0] is always set to 1 to indicate this |
93 | // is a persistent diagnostic. |
94 | uintptr_t V = (uintptr_t) DLoc; |
95 | V |= 0x1; |
96 | CXSourceLocation Loc = { .ptr_data: { (void*) V, nullptr }, .int_data: 0 }; |
97 | return Loc; |
98 | } |
99 | |
100 | CXSourceLocation CXLoadedDiagnostic::getLocation() const { |
101 | // The lowest bit of ptr_data[0] is always set to 1 to indicate this |
102 | // is a persistent diagnostic. |
103 | return makeLocation(DLoc: &DiagLoc); |
104 | } |
105 | |
106 | CXString CXLoadedDiagnostic::getSpelling() const { |
107 | return cxstring::createRef(String: Spelling); |
108 | } |
109 | |
110 | CXString CXLoadedDiagnostic::getDiagnosticOption(CXString *Disable) const { |
111 | if (DiagOption.empty()) |
112 | return cxstring::createEmpty(); |
113 | |
114 | // FIXME: possibly refactor with logic in CXStoredDiagnostic. |
115 | if (Disable) |
116 | *Disable = cxstring::createDup(String: (Twine("-Wno-" ) + DiagOption).str()); |
117 | return cxstring::createDup(String: (Twine("-W" ) + DiagOption).str()); |
118 | } |
119 | |
120 | unsigned CXLoadedDiagnostic::getCategory() const { |
121 | return category; |
122 | } |
123 | |
124 | CXString CXLoadedDiagnostic::getCategoryText() const { |
125 | return cxstring::createDup(String: CategoryText); |
126 | } |
127 | |
128 | unsigned CXLoadedDiagnostic::getNumRanges() const { |
129 | return Ranges.size(); |
130 | } |
131 | |
132 | CXSourceRange CXLoadedDiagnostic::getRange(unsigned Range) const { |
133 | assert(Range < Ranges.size()); |
134 | return Ranges[Range]; |
135 | } |
136 | |
137 | unsigned CXLoadedDiagnostic::getNumFixIts() const { |
138 | return FixIts.size(); |
139 | } |
140 | |
141 | CXString CXLoadedDiagnostic::getFixIt(unsigned FixIt, |
142 | CXSourceRange *ReplacementRange) const { |
143 | assert(FixIt < FixIts.size()); |
144 | if (ReplacementRange) |
145 | *ReplacementRange = FixIts[FixIt].first; |
146 | return cxstring::createRef(String: FixIts[FixIt].second); |
147 | } |
148 | |
149 | void CXLoadedDiagnostic::decodeLocation(CXSourceLocation location, |
150 | CXFile *file, |
151 | unsigned int *line, |
152 | unsigned int *column, |
153 | unsigned int *offset) { |
154 | |
155 | |
156 | // CXSourceLocation consists of the following fields: |
157 | // |
158 | // void *ptr_data[2]; |
159 | // unsigned int_data; |
160 | // |
161 | // The lowest bit of ptr_data[0] is always set to 1 to indicate this |
162 | // is a persistent diagnostic. |
163 | // |
164 | // For now, do the unoptimized approach and store the data in a side |
165 | // data structure. We can optimize this case later. |
166 | |
167 | uintptr_t V = (uintptr_t) location.ptr_data[0]; |
168 | assert((V & 0x1) == 1); |
169 | V &= ~(uintptr_t)1; |
170 | |
171 | const Location &Loc = *((Location*)V); |
172 | |
173 | if (file) |
174 | *file = Loc.file; |
175 | if (line) |
176 | *line = Loc.line; |
177 | if (column) |
178 | *column = Loc.column; |
179 | if (offset) |
180 | *offset = Loc.offset; |
181 | } |
182 | |
183 | //===----------------------------------------------------------------------===// |
184 | // Deserialize diagnostics. |
185 | //===----------------------------------------------------------------------===// |
186 | |
187 | namespace { |
188 | class DiagLoader : serialized_diags::SerializedDiagnosticReader { |
189 | enum CXLoadDiag_Error *error; |
190 | CXString *errorString; |
191 | std::unique_ptr<CXLoadedDiagnosticSetImpl> TopDiags; |
192 | SmallVector<std::unique_ptr<CXLoadedDiagnostic>, 8> CurrentDiags; |
193 | |
194 | std::error_code reportBad(enum CXLoadDiag_Error code, llvm::StringRef err) { |
195 | if (error) |
196 | *error = code; |
197 | if (errorString) |
198 | *errorString = cxstring::createDup(String: err); |
199 | return serialized_diags::SDError::HandlerFailed; |
200 | } |
201 | |
202 | std::error_code reportInvalidFile(llvm::StringRef err) { |
203 | return reportBad(code: CXLoadDiag_InvalidFile, err); |
204 | } |
205 | |
206 | std::error_code readRange(const serialized_diags::Location &SDStart, |
207 | const serialized_diags::Location &SDEnd, |
208 | CXSourceRange &SR); |
209 | |
210 | std::error_code readLocation(const serialized_diags::Location &SDLoc, |
211 | CXLoadedDiagnostic::Location &LoadedLoc); |
212 | |
213 | protected: |
214 | std::error_code visitStartOfDiagnostic() override; |
215 | std::error_code visitEndOfDiagnostic() override; |
216 | |
217 | std::error_code visitCategoryRecord(unsigned ID, StringRef Name) override; |
218 | |
219 | std::error_code visitDiagFlagRecord(unsigned ID, StringRef Name) override; |
220 | |
221 | std::error_code visitDiagnosticRecord( |
222 | unsigned Severity, const serialized_diags::Location &Location, |
223 | unsigned Category, unsigned Flag, StringRef Message) override; |
224 | |
225 | std::error_code visitFilenameRecord(unsigned ID, unsigned Size, |
226 | unsigned Timestamp, |
227 | StringRef Name) override; |
228 | |
229 | std::error_code visitFixitRecord(const serialized_diags::Location &Start, |
230 | const serialized_diags::Location &End, |
231 | StringRef CodeToInsert) override; |
232 | |
233 | std::error_code |
234 | visitSourceRangeRecord(const serialized_diags::Location &Start, |
235 | const serialized_diags::Location &End) override; |
236 | |
237 | public: |
238 | DiagLoader(enum CXLoadDiag_Error *e, CXString *es) |
239 | : error(e), errorString(es) { |
240 | if (error) |
241 | *error = CXLoadDiag_None; |
242 | if (errorString) |
243 | *errorString = cxstring::createEmpty(); |
244 | } |
245 | |
246 | CXDiagnosticSet load(const char *file); |
247 | }; |
248 | } // end anonymous namespace |
249 | |
250 | CXDiagnosticSet DiagLoader::load(const char *file) { |
251 | TopDiags = std::make_unique<CXLoadedDiagnosticSetImpl>(); |
252 | |
253 | std::error_code EC = readDiagnostics(File: file); |
254 | if (EC) { |
255 | switch (EC.value()) { |
256 | case static_cast<int>(serialized_diags::SDError::HandlerFailed): |
257 | // We've already reported the problem. |
258 | break; |
259 | case static_cast<int>(serialized_diags::SDError::CouldNotLoad): |
260 | reportBad(code: CXLoadDiag_CannotLoad, err: EC.message()); |
261 | break; |
262 | default: |
263 | reportInvalidFile(err: EC.message()); |
264 | break; |
265 | } |
266 | return nullptr; |
267 | } |
268 | |
269 | return (CXDiagnosticSet)TopDiags.release(); |
270 | } |
271 | |
272 | std::error_code |
273 | DiagLoader::readLocation(const serialized_diags::Location &SDLoc, |
274 | CXLoadedDiagnostic::Location &LoadedLoc) { |
275 | unsigned FileID = SDLoc.FileID; |
276 | if (FileID == 0) |
277 | LoadedLoc.file = nullptr; |
278 | else { |
279 | auto It = TopDiags->Files.find(Val: FileID); |
280 | if (It == TopDiags->Files.end()) |
281 | return reportInvalidFile(err: "Corrupted file entry in source location" ); |
282 | LoadedLoc.file = cxfile::makeCXFile(FE: It->second); |
283 | } |
284 | LoadedLoc.line = SDLoc.Line; |
285 | LoadedLoc.column = SDLoc.Col; |
286 | LoadedLoc.offset = SDLoc.Offset; |
287 | return std::error_code(); |
288 | } |
289 | |
290 | std::error_code |
291 | DiagLoader::readRange(const serialized_diags::Location &SDStart, |
292 | const serialized_diags::Location &SDEnd, |
293 | CXSourceRange &SR) { |
294 | CXLoadedDiagnostic::Location *Start, *End; |
295 | Start = TopDiags->Alloc.Allocate<CXLoadedDiagnostic::Location>(); |
296 | End = TopDiags->Alloc.Allocate<CXLoadedDiagnostic::Location>(); |
297 | |
298 | std::error_code EC; |
299 | if ((EC = readLocation(SDLoc: SDStart, LoadedLoc&: *Start))) |
300 | return EC; |
301 | if ((EC = readLocation(SDLoc: SDEnd, LoadedLoc&: *End))) |
302 | return EC; |
303 | |
304 | CXSourceLocation startLoc = makeLocation(DLoc: Start); |
305 | CXSourceLocation endLoc = makeLocation(DLoc: End); |
306 | SR = clang_getRange(begin: startLoc, end: endLoc); |
307 | return std::error_code(); |
308 | } |
309 | |
310 | std::error_code DiagLoader::visitStartOfDiagnostic() { |
311 | CurrentDiags.push_back(Elt: std::make_unique<CXLoadedDiagnostic>()); |
312 | return std::error_code(); |
313 | } |
314 | |
315 | std::error_code DiagLoader::visitEndOfDiagnostic() { |
316 | auto D = CurrentDiags.pop_back_val(); |
317 | if (CurrentDiags.empty()) |
318 | TopDiags->appendDiagnostic(D: std::move(D)); |
319 | else |
320 | CurrentDiags.back()->getChildDiagnostics().appendDiagnostic(D: std::move(D)); |
321 | return std::error_code(); |
322 | } |
323 | |
324 | std::error_code DiagLoader::visitCategoryRecord(unsigned ID, StringRef Name) { |
325 | // FIXME: Why do we care about long strings? |
326 | if (Name.size() > 65536) |
327 | return reportInvalidFile(err: "Out-of-bounds string in category" ); |
328 | TopDiags->Categories[ID] = TopDiags->copyString(Blob: Name); |
329 | return std::error_code(); |
330 | } |
331 | |
332 | std::error_code DiagLoader::visitDiagFlagRecord(unsigned ID, StringRef Name) { |
333 | // FIXME: Why do we care about long strings? |
334 | if (Name.size() > 65536) |
335 | return reportInvalidFile(err: "Out-of-bounds string in warning flag" ); |
336 | TopDiags->WarningFlags[ID] = TopDiags->copyString(Blob: Name); |
337 | return std::error_code(); |
338 | } |
339 | |
340 | std::error_code DiagLoader::visitFilenameRecord(unsigned ID, unsigned Size, |
341 | unsigned Timestamp, |
342 | StringRef Name) { |
343 | // FIXME: Why do we care about long strings? |
344 | if (Name.size() > 65536) |
345 | return reportInvalidFile(err: "Out-of-bounds string in filename" ); |
346 | TopDiags->FileNames[ID] = TopDiags->copyString(Blob: Name); |
347 | TopDiags->Files.insert( |
348 | KV: {ID, TopDiags->FakeFiles.getVirtualFileRef(Filename: Name, Size, ModificationTime: Timestamp)}); |
349 | return std::error_code(); |
350 | } |
351 | |
352 | std::error_code |
353 | DiagLoader::visitSourceRangeRecord(const serialized_diags::Location &Start, |
354 | const serialized_diags::Location &End) { |
355 | CXSourceRange SR; |
356 | if (std::error_code EC = readRange(SDStart: Start, SDEnd: End, SR)) |
357 | return EC; |
358 | CurrentDiags.back()->Ranges.push_back(x: SR); |
359 | return std::error_code(); |
360 | } |
361 | |
362 | std::error_code |
363 | DiagLoader::visitFixitRecord(const serialized_diags::Location &Start, |
364 | const serialized_diags::Location &End, |
365 | StringRef CodeToInsert) { |
366 | CXSourceRange SR; |
367 | if (std::error_code EC = readRange(SDStart: Start, SDEnd: End, SR)) |
368 | return EC; |
369 | // FIXME: Why do we care about long strings? |
370 | if (CodeToInsert.size() > 65536) |
371 | return reportInvalidFile(err: "Out-of-bounds string in FIXIT" ); |
372 | CurrentDiags.back()->FixIts.push_back( |
373 | x: std::make_pair(x&: SR, y: TopDiags->copyString(Blob: CodeToInsert))); |
374 | return std::error_code(); |
375 | } |
376 | |
377 | std::error_code DiagLoader::visitDiagnosticRecord( |
378 | unsigned Severity, const serialized_diags::Location &Location, |
379 | unsigned Category, unsigned Flag, StringRef Message) { |
380 | CXLoadedDiagnostic &D = *CurrentDiags.back(); |
381 | D.severity = Severity; |
382 | if (std::error_code EC = readLocation(SDLoc: Location, LoadedLoc&: D.DiagLoc)) |
383 | return EC; |
384 | D.category = Category; |
385 | D.DiagOption = Flag ? TopDiags->WarningFlags[Flag] : "" ; |
386 | D.CategoryText = Category ? TopDiags->Categories[Category] : "" ; |
387 | D.Spelling = TopDiags->copyString(Blob: Message); |
388 | return std::error_code(); |
389 | } |
390 | |
391 | CXDiagnosticSet clang_loadDiagnostics(const char *file, |
392 | enum CXLoadDiag_Error *error, |
393 | CXString *errorString) { |
394 | DiagLoader L(error, errorString); |
395 | return L.load(file); |
396 | } |
397 | |