1 | //===- CIndexDiagnostic.cpp - Diagnostics C Interface ---------------------===// |
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 the diagnostic functions of the Clang C interface. |
10 | // |
11 | //===----------------------------------------------------------------------===// |
12 | |
13 | #include "CIndexDiagnostic.h" |
14 | #include "CIndexer.h" |
15 | #include "CXTranslationUnit.h" |
16 | #include "CXSourceLocation.h" |
17 | #include "CXString.h" |
18 | |
19 | #include "clang/Basic/DiagnosticOptions.h" |
20 | #include "clang/Frontend/ASTUnit.h" |
21 | #include "clang/Frontend/DiagnosticRenderer.h" |
22 | #include "llvm/ADT/SmallString.h" |
23 | #include "llvm/Support/raw_ostream.h" |
24 | |
25 | using namespace clang; |
26 | using namespace clang::cxloc; |
27 | using namespace clang::cxdiag; |
28 | using namespace llvm; |
29 | |
30 | CXDiagnosticSetImpl::~CXDiagnosticSetImpl() {} |
31 | |
32 | void |
33 | CXDiagnosticSetImpl::appendDiagnostic(std::unique_ptr<CXDiagnosticImpl> D) { |
34 | Diagnostics.push_back(x: std::move(D)); |
35 | } |
36 | |
37 | CXDiagnosticImpl::~CXDiagnosticImpl() {} |
38 | |
39 | namespace { |
40 | class CXDiagnosticCustomNoteImpl : public CXDiagnosticImpl { |
41 | std::string Message; |
42 | CXSourceLocation Loc; |
43 | public: |
44 | CXDiagnosticCustomNoteImpl(StringRef Msg, CXSourceLocation L) |
45 | : CXDiagnosticImpl(CustomNoteDiagnosticKind), Message(std::string(Msg)), |
46 | Loc(L) {} |
47 | |
48 | ~CXDiagnosticCustomNoteImpl() override {} |
49 | |
50 | CXDiagnosticSeverity getSeverity() const override { |
51 | return CXDiagnostic_Note; |
52 | } |
53 | |
54 | CXSourceLocation getLocation() const override { return Loc; } |
55 | |
56 | CXString getSpelling() const override { |
57 | return cxstring::createRef(String: Message.c_str()); |
58 | } |
59 | |
60 | CXString getDiagnosticOption(CXString *Disable) const override { |
61 | if (Disable) |
62 | *Disable = cxstring::createEmpty(); |
63 | return cxstring::createEmpty(); |
64 | } |
65 | |
66 | unsigned getCategory() const override { return 0; } |
67 | CXString getCategoryText() const override { return cxstring::createEmpty(); } |
68 | |
69 | unsigned getNumRanges() const override { return 0; } |
70 | CXSourceRange getRange(unsigned Range) const override { |
71 | return clang_getNullRange(); |
72 | } |
73 | unsigned getNumFixIts() const override { return 0; } |
74 | CXString getFixIt(unsigned FixIt, |
75 | CXSourceRange *ReplacementRange) const override { |
76 | if (ReplacementRange) |
77 | *ReplacementRange = clang_getNullRange(); |
78 | return cxstring::createEmpty(); |
79 | } |
80 | }; |
81 | |
82 | class CXDiagnosticRenderer : public DiagnosticNoteRenderer { |
83 | public: |
84 | CXDiagnosticRenderer(const LangOptions &LangOpts, |
85 | DiagnosticOptions *DiagOpts, |
86 | CXDiagnosticSetImpl *mainSet) |
87 | : DiagnosticNoteRenderer(LangOpts, DiagOpts), |
88 | CurrentSet(mainSet), MainSet(mainSet) {} |
89 | |
90 | ~CXDiagnosticRenderer() override {} |
91 | |
92 | void beginDiagnostic(DiagOrStoredDiag D, |
93 | DiagnosticsEngine::Level Level) override { |
94 | |
95 | const StoredDiagnostic *SD = D.dyn_cast<const StoredDiagnostic*>(); |
96 | if (!SD) |
97 | return; |
98 | |
99 | if (Level != DiagnosticsEngine::Note) |
100 | CurrentSet = MainSet; |
101 | |
102 | auto Owner = std::make_unique<CXStoredDiagnostic>(args: *SD, args: LangOpts); |
103 | CXStoredDiagnostic &CD = *Owner; |
104 | CurrentSet->appendDiagnostic(D: std::move(Owner)); |
105 | |
106 | if (Level != DiagnosticsEngine::Note) |
107 | CurrentSet = &CD.getChildDiagnostics(); |
108 | } |
109 | |
110 | void emitDiagnosticMessage(FullSourceLoc Loc, PresumedLoc PLoc, |
111 | DiagnosticsEngine::Level Level, StringRef Message, |
112 | ArrayRef<CharSourceRange> Ranges, |
113 | DiagOrStoredDiag D) override { |
114 | if (!D.isNull()) |
115 | return; |
116 | |
117 | CXSourceLocation L; |
118 | if (Loc.hasManager()) |
119 | L = translateSourceLocation(SM: Loc.getManager(), LangOpts, Loc); |
120 | else |
121 | L = clang_getNullLocation(); |
122 | CurrentSet->appendDiagnostic( |
123 | D: std::make_unique<CXDiagnosticCustomNoteImpl>(args&: Message, args&: L)); |
124 | } |
125 | |
126 | void emitDiagnosticLoc(FullSourceLoc Loc, PresumedLoc PLoc, |
127 | DiagnosticsEngine::Level Level, |
128 | ArrayRef<CharSourceRange> Ranges) override {} |
129 | |
130 | void emitCodeContext(FullSourceLoc Loc, DiagnosticsEngine::Level Level, |
131 | SmallVectorImpl<CharSourceRange> &Ranges, |
132 | ArrayRef<FixItHint> Hints) override {} |
133 | |
134 | void emitNote(FullSourceLoc Loc, StringRef Message) override { |
135 | CXSourceLocation L; |
136 | if (Loc.hasManager()) |
137 | L = translateSourceLocation(SM: Loc.getManager(), LangOpts, Loc); |
138 | else |
139 | L = clang_getNullLocation(); |
140 | CurrentSet->appendDiagnostic( |
141 | D: std::make_unique<CXDiagnosticCustomNoteImpl>(args&: Message, args&: L)); |
142 | } |
143 | |
144 | CXDiagnosticSetImpl *CurrentSet; |
145 | CXDiagnosticSetImpl *MainSet; |
146 | }; |
147 | } |
148 | |
149 | CXDiagnosticSetImpl *cxdiag::lazyCreateDiags(CXTranslationUnit TU, |
150 | bool checkIfChanged) { |
151 | ASTUnit *AU = cxtu::getASTUnit(TU); |
152 | |
153 | if (TU->Diagnostics && checkIfChanged) { |
154 | // In normal use, ASTUnit's diagnostics should not change unless we reparse. |
155 | // Currently they can only change by using the internal testing flag |
156 | // '-error-on-deserialized-decl' which will error during deserialization of |
157 | // a declaration. What will happen is: |
158 | // |
159 | // -c-index-test gets a CXTranslationUnit |
160 | // -checks the diagnostics, the diagnostics set is lazily created, |
161 | // no errors are reported |
162 | // -later does an operation, like annotation of tokens, that triggers |
163 | // -error-on-deserialized-decl, that will emit a diagnostic error, |
164 | // that ASTUnit will catch and add to its stored diagnostics vector. |
165 | // -c-index-test wants to check whether an error occurred after performing |
166 | // the operation but can only query the lazily created set. |
167 | // |
168 | // We check here if a new diagnostic was appended since the last time the |
169 | // diagnostic set was created, in which case we reset it. |
170 | |
171 | CXDiagnosticSetImpl * |
172 | Set = static_cast<CXDiagnosticSetImpl*>(TU->Diagnostics); |
173 | if (AU->stored_diag_size() != Set->getNumDiagnostics()) { |
174 | // Diagnostics in the ASTUnit were updated, reset the associated |
175 | // diagnostics. |
176 | delete Set; |
177 | TU->Diagnostics = nullptr; |
178 | } |
179 | } |
180 | |
181 | if (!TU->Diagnostics) { |
182 | CXDiagnosticSetImpl *Set = new CXDiagnosticSetImpl(); |
183 | TU->Diagnostics = Set; |
184 | IntrusiveRefCntPtr<DiagnosticOptions> DOpts = new DiagnosticOptions; |
185 | CXDiagnosticRenderer Renderer(AU->getASTContext().getLangOpts(), |
186 | &*DOpts, Set); |
187 | |
188 | for (ASTUnit::stored_diag_iterator it = AU->stored_diag_begin(), |
189 | ei = AU->stored_diag_end(); it != ei; ++it) { |
190 | Renderer.emitStoredDiagnostic(Diag&: *it); |
191 | } |
192 | } |
193 | return static_cast<CXDiagnosticSetImpl*>(TU->Diagnostics); |
194 | } |
195 | |
196 | //----------------------------------------------------------------------------- |
197 | // C Interface Routines |
198 | //----------------------------------------------------------------------------- |
199 | unsigned clang_getNumDiagnostics(CXTranslationUnit Unit) { |
200 | if (cxtu::isNotUsableTU(TU: Unit)) { |
201 | LOG_BAD_TU(Unit); |
202 | return 0; |
203 | } |
204 | if (!cxtu::getASTUnit(TU: Unit)) |
205 | return 0; |
206 | return lazyCreateDiags(TU: Unit, /*checkIfChanged=*/true)->getNumDiagnostics(); |
207 | } |
208 | |
209 | CXDiagnostic clang_getDiagnostic(CXTranslationUnit Unit, unsigned Index) { |
210 | if (cxtu::isNotUsableTU(TU: Unit)) { |
211 | LOG_BAD_TU(Unit); |
212 | return nullptr; |
213 | } |
214 | |
215 | CXDiagnosticSet D = clang_getDiagnosticSetFromTU(Unit); |
216 | if (!D) |
217 | return nullptr; |
218 | |
219 | CXDiagnosticSetImpl *Diags = static_cast<CXDiagnosticSetImpl*>(D); |
220 | if (Index >= Diags->getNumDiagnostics()) |
221 | return nullptr; |
222 | |
223 | return Diags->getDiagnostic(i: Index); |
224 | } |
225 | |
226 | CXDiagnosticSet clang_getDiagnosticSetFromTU(CXTranslationUnit Unit) { |
227 | if (cxtu::isNotUsableTU(TU: Unit)) { |
228 | LOG_BAD_TU(Unit); |
229 | return nullptr; |
230 | } |
231 | if (!cxtu::getASTUnit(TU: Unit)) |
232 | return nullptr; |
233 | return static_cast<CXDiagnostic>(lazyCreateDiags(TU: Unit)); |
234 | } |
235 | |
236 | void clang_disposeDiagnostic(CXDiagnostic Diagnostic) { |
237 | // No-op. Kept as a legacy API. CXDiagnostics are now managed |
238 | // by the enclosing CXDiagnosticSet. |
239 | } |
240 | |
241 | CXString clang_formatDiagnostic(CXDiagnostic Diagnostic, unsigned Options) { |
242 | if (!Diagnostic) |
243 | return cxstring::createEmpty(); |
244 | |
245 | CXDiagnosticSeverity Severity = clang_getDiagnosticSeverity(Diagnostic); |
246 | |
247 | SmallString<256> Str; |
248 | llvm::raw_svector_ostream Out(Str); |
249 | |
250 | if (Options & CXDiagnostic_DisplaySourceLocation) { |
251 | // Print source location (file:line), along with optional column |
252 | // and source ranges. |
253 | CXFile File; |
254 | unsigned Line, Column; |
255 | clang_getSpellingLocation(location: clang_getDiagnosticLocation(Diagnostic), |
256 | file: &File, line: &Line, column: &Column, offset: nullptr); |
257 | if (File) { |
258 | CXString FName = clang_getFileName(SFile: File); |
259 | Out << clang_getCString(string: FName) << ":" << Line << ":" ; |
260 | clang_disposeString(string: FName); |
261 | if (Options & CXDiagnostic_DisplayColumn) |
262 | Out << Column << ":" ; |
263 | |
264 | if (Options & CXDiagnostic_DisplaySourceRanges) { |
265 | unsigned N = clang_getDiagnosticNumRanges(Diagnostic); |
266 | bool PrintedRange = false; |
267 | for (unsigned I = 0; I != N; ++I) { |
268 | CXFile StartFile, EndFile; |
269 | CXSourceRange Range = clang_getDiagnosticRange(Diagnostic, Range: I); |
270 | |
271 | unsigned StartLine, StartColumn, EndLine, EndColumn; |
272 | clang_getSpellingLocation(location: clang_getRangeStart(range: Range), |
273 | file: &StartFile, line: &StartLine, column: &StartColumn, |
274 | offset: nullptr); |
275 | clang_getSpellingLocation(location: clang_getRangeEnd(range: Range), |
276 | file: &EndFile, line: &EndLine, column: &EndColumn, offset: nullptr); |
277 | |
278 | if (StartFile != EndFile || StartFile != File) |
279 | continue; |
280 | |
281 | Out << "{" << StartLine << ":" << StartColumn << "-" |
282 | << EndLine << ":" << EndColumn << "}" ; |
283 | PrintedRange = true; |
284 | } |
285 | if (PrintedRange) |
286 | Out << ":" ; |
287 | } |
288 | |
289 | Out << " " ; |
290 | } |
291 | } |
292 | |
293 | /* Print warning/error/etc. */ |
294 | switch (Severity) { |
295 | case CXDiagnostic_Ignored: llvm_unreachable("impossible" ); |
296 | case CXDiagnostic_Note: Out << "note: " ; break; |
297 | case CXDiagnostic_Warning: Out << "warning: " ; break; |
298 | case CXDiagnostic_Error: Out << "error: " ; break; |
299 | case CXDiagnostic_Fatal: Out << "fatal error: " ; break; |
300 | } |
301 | |
302 | CXString Text = clang_getDiagnosticSpelling(Diagnostic); |
303 | if (clang_getCString(string: Text)) |
304 | Out << clang_getCString(string: Text); |
305 | else |
306 | Out << "<no diagnostic text>" ; |
307 | clang_disposeString(string: Text); |
308 | |
309 | if (Options & (CXDiagnostic_DisplayOption | CXDiagnostic_DisplayCategoryId | |
310 | CXDiagnostic_DisplayCategoryName)) { |
311 | bool NeedBracket = true; |
312 | bool NeedComma = false; |
313 | |
314 | if (Options & CXDiagnostic_DisplayOption) { |
315 | CXString OptionName = clang_getDiagnosticOption(Diag: Diagnostic, Disable: nullptr); |
316 | if (const char *OptionText = clang_getCString(string: OptionName)) { |
317 | if (OptionText[0]) { |
318 | Out << " [" << OptionText; |
319 | NeedBracket = false; |
320 | NeedComma = true; |
321 | } |
322 | } |
323 | clang_disposeString(string: OptionName); |
324 | } |
325 | |
326 | if (Options & (CXDiagnostic_DisplayCategoryId | |
327 | CXDiagnostic_DisplayCategoryName)) { |
328 | if (unsigned CategoryID = clang_getDiagnosticCategory(Diagnostic)) { |
329 | if (Options & CXDiagnostic_DisplayCategoryId) { |
330 | if (NeedBracket) |
331 | Out << " [" ; |
332 | if (NeedComma) |
333 | Out << ", " ; |
334 | Out << CategoryID; |
335 | NeedBracket = false; |
336 | NeedComma = true; |
337 | } |
338 | |
339 | if (Options & CXDiagnostic_DisplayCategoryName) { |
340 | CXString CategoryName = clang_getDiagnosticCategoryText(Diagnostic); |
341 | if (NeedBracket) |
342 | Out << " [" ; |
343 | if (NeedComma) |
344 | Out << ", " ; |
345 | Out << clang_getCString(string: CategoryName); |
346 | NeedBracket = false; |
347 | NeedComma = true; |
348 | clang_disposeString(string: CategoryName); |
349 | } |
350 | } |
351 | } |
352 | |
353 | (void) NeedComma; // Silence dead store warning. |
354 | if (!NeedBracket) |
355 | Out << "]" ; |
356 | } |
357 | |
358 | return cxstring::createDup(String: Out.str()); |
359 | } |
360 | |
361 | unsigned clang_defaultDiagnosticDisplayOptions() { |
362 | return CXDiagnostic_DisplaySourceLocation | CXDiagnostic_DisplayColumn | |
363 | CXDiagnostic_DisplayOption; |
364 | } |
365 | |
366 | enum CXDiagnosticSeverity clang_getDiagnosticSeverity(CXDiagnostic Diag) { |
367 | if (CXDiagnosticImpl *D = static_cast<CXDiagnosticImpl*>(Diag)) |
368 | return D->getSeverity(); |
369 | return CXDiagnostic_Ignored; |
370 | } |
371 | |
372 | CXSourceLocation clang_getDiagnosticLocation(CXDiagnostic Diag) { |
373 | if (CXDiagnosticImpl *D = static_cast<CXDiagnosticImpl*>(Diag)) |
374 | return D->getLocation(); |
375 | return clang_getNullLocation(); |
376 | } |
377 | |
378 | CXString clang_getDiagnosticSpelling(CXDiagnostic Diag) { |
379 | if (CXDiagnosticImpl *D = static_cast<CXDiagnosticImpl *>(Diag)) |
380 | return D->getSpelling(); |
381 | return cxstring::createEmpty(); |
382 | } |
383 | |
384 | CXString clang_getDiagnosticOption(CXDiagnostic Diag, CXString *Disable) { |
385 | if (Disable) |
386 | *Disable = cxstring::createEmpty(); |
387 | |
388 | if (CXDiagnosticImpl *D = static_cast<CXDiagnosticImpl *>(Diag)) |
389 | return D->getDiagnosticOption(Disable); |
390 | |
391 | return cxstring::createEmpty(); |
392 | } |
393 | |
394 | unsigned clang_getDiagnosticCategory(CXDiagnostic Diag) { |
395 | if (CXDiagnosticImpl *D = static_cast<CXDiagnosticImpl *>(Diag)) |
396 | return D->getCategory(); |
397 | return 0; |
398 | } |
399 | |
400 | CXString clang_getDiagnosticCategoryName(unsigned Category) { |
401 | // Kept for backward compatibility. |
402 | return cxstring::createRef(String: DiagnosticIDs::getCategoryNameFromID(CategoryID: Category)); |
403 | } |
404 | |
405 | CXString clang_getDiagnosticCategoryText(CXDiagnostic Diag) { |
406 | if (CXDiagnosticImpl *D = static_cast<CXDiagnosticImpl *>(Diag)) |
407 | return D->getCategoryText(); |
408 | return cxstring::createEmpty(); |
409 | } |
410 | |
411 | unsigned clang_getDiagnosticNumRanges(CXDiagnostic Diag) { |
412 | if (CXDiagnosticImpl *D = static_cast<CXDiagnosticImpl *>(Diag)) |
413 | return D->getNumRanges(); |
414 | return 0; |
415 | } |
416 | |
417 | CXSourceRange clang_getDiagnosticRange(CXDiagnostic Diag, unsigned Range) { |
418 | CXDiagnosticImpl *D = static_cast<CXDiagnosticImpl *>(Diag); |
419 | if (!D || Range >= D->getNumRanges()) |
420 | return clang_getNullRange(); |
421 | return D->getRange(Range); |
422 | } |
423 | |
424 | unsigned clang_getDiagnosticNumFixIts(CXDiagnostic Diag) { |
425 | if (CXDiagnosticImpl *D = static_cast<CXDiagnosticImpl *>(Diag)) |
426 | return D->getNumFixIts(); |
427 | return 0; |
428 | } |
429 | |
430 | CXString clang_getDiagnosticFixIt(CXDiagnostic Diag, unsigned FixIt, |
431 | CXSourceRange *ReplacementRange) { |
432 | CXDiagnosticImpl *D = static_cast<CXDiagnosticImpl *>(Diag); |
433 | if (!D || FixIt >= D->getNumFixIts()) { |
434 | if (ReplacementRange) |
435 | *ReplacementRange = clang_getNullRange(); |
436 | return cxstring::createEmpty(); |
437 | } |
438 | return D->getFixIt(FixIt, ReplacementRange); |
439 | } |
440 | |
441 | void clang_disposeDiagnosticSet(CXDiagnosticSet Diags) { |
442 | if (CXDiagnosticSetImpl *D = static_cast<CXDiagnosticSetImpl *>(Diags)) { |
443 | if (D->isExternallyManaged()) |
444 | delete D; |
445 | } |
446 | } |
447 | |
448 | CXDiagnostic clang_getDiagnosticInSet(CXDiagnosticSet Diags, |
449 | unsigned Index) { |
450 | if (CXDiagnosticSetImpl *D = static_cast<CXDiagnosticSetImpl*>(Diags)) |
451 | if (Index < D->getNumDiagnostics()) |
452 | return D->getDiagnostic(i: Index); |
453 | return nullptr; |
454 | } |
455 | |
456 | CXDiagnosticSet clang_getChildDiagnostics(CXDiagnostic Diag) { |
457 | if (CXDiagnosticImpl *D = static_cast<CXDiagnosticImpl *>(Diag)) { |
458 | CXDiagnosticSetImpl &ChildDiags = D->getChildDiagnostics(); |
459 | return ChildDiags.empty() ? nullptr : (CXDiagnosticSet) &ChildDiags; |
460 | } |
461 | return nullptr; |
462 | } |
463 | |
464 | unsigned clang_getNumDiagnosticsInSet(CXDiagnosticSet Diags) { |
465 | if (CXDiagnosticSetImpl *D = static_cast<CXDiagnosticSetImpl*>(Diags)) |
466 | return D->getNumDiagnostics(); |
467 | return 0; |
468 | } |
469 | |