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
25using namespace clang;
26using namespace clang::cxloc;
27using namespace clang::cxdiag;
28using namespace llvm;
29
30CXDiagnosticSetImpl::~CXDiagnosticSetImpl() {}
31
32void
33CXDiagnosticSetImpl::appendDiagnostic(std::unique_ptr<CXDiagnosticImpl> D) {
34 Diagnostics.push_back(x: std::move(D));
35}
36
37CXDiagnosticImpl::~CXDiagnosticImpl() {}
38
39namespace {
40class CXDiagnosticCustomNoteImpl : public CXDiagnosticImpl {
41 std::string Message;
42 CXSourceLocation Loc;
43public:
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
82class CXDiagnosticRenderer : public DiagnosticNoteRenderer {
83public:
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
149CXDiagnosticSetImpl *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//-----------------------------------------------------------------------------
199unsigned 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
209CXDiagnostic 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
226CXDiagnosticSet 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
236void clang_disposeDiagnostic(CXDiagnostic Diagnostic) {
237 // No-op. Kept as a legacy API. CXDiagnostics are now managed
238 // by the enclosing CXDiagnosticSet.
239}
240
241CXString 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
361unsigned clang_defaultDiagnosticDisplayOptions() {
362 return CXDiagnostic_DisplaySourceLocation | CXDiagnostic_DisplayColumn |
363 CXDiagnostic_DisplayOption;
364}
365
366enum CXDiagnosticSeverity clang_getDiagnosticSeverity(CXDiagnostic Diag) {
367 if (CXDiagnosticImpl *D = static_cast<CXDiagnosticImpl*>(Diag))
368 return D->getSeverity();
369 return CXDiagnostic_Ignored;
370}
371
372CXSourceLocation clang_getDiagnosticLocation(CXDiagnostic Diag) {
373 if (CXDiagnosticImpl *D = static_cast<CXDiagnosticImpl*>(Diag))
374 return D->getLocation();
375 return clang_getNullLocation();
376}
377
378CXString clang_getDiagnosticSpelling(CXDiagnostic Diag) {
379 if (CXDiagnosticImpl *D = static_cast<CXDiagnosticImpl *>(Diag))
380 return D->getSpelling();
381 return cxstring::createEmpty();
382}
383
384CXString 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
394unsigned clang_getDiagnosticCategory(CXDiagnostic Diag) {
395 if (CXDiagnosticImpl *D = static_cast<CXDiagnosticImpl *>(Diag))
396 return D->getCategory();
397 return 0;
398}
399
400CXString clang_getDiagnosticCategoryName(unsigned Category) {
401 // Kept for backward compatibility.
402 return cxstring::createRef(String: DiagnosticIDs::getCategoryNameFromID(CategoryID: Category));
403}
404
405CXString clang_getDiagnosticCategoryText(CXDiagnostic Diag) {
406 if (CXDiagnosticImpl *D = static_cast<CXDiagnosticImpl *>(Diag))
407 return D->getCategoryText();
408 return cxstring::createEmpty();
409}
410
411unsigned clang_getDiagnosticNumRanges(CXDiagnostic Diag) {
412 if (CXDiagnosticImpl *D = static_cast<CXDiagnosticImpl *>(Diag))
413 return D->getNumRanges();
414 return 0;
415}
416
417CXSourceRange 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
424unsigned clang_getDiagnosticNumFixIts(CXDiagnostic Diag) {
425 if (CXDiagnosticImpl *D = static_cast<CXDiagnosticImpl *>(Diag))
426 return D->getNumFixIts();
427 return 0;
428}
429
430CXString 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
441void clang_disposeDiagnosticSet(CXDiagnosticSet Diags) {
442 if (CXDiagnosticSetImpl *D = static_cast<CXDiagnosticSetImpl *>(Diags)) {
443 if (D->isExternallyManaged())
444 delete D;
445 }
446}
447
448CXDiagnostic 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
456CXDiagnosticSet 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
464unsigned clang_getNumDiagnosticsInSet(CXDiagnosticSet Diags) {
465 if (CXDiagnosticSetImpl *D = static_cast<CXDiagnosticSetImpl*>(Diags))
466 return D->getNumDiagnostics();
467 return 0;
468}
469