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