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(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(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>(*SD, LangOpts);
103    CXStoredDiagnostic &CD = *Owner;
104    CurrentSet->appendDiagnostic(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(Loc.getManager(), LangOpts, Loc);
120    else
121      L = clang_getNullLocation();
122    CurrentSet->appendDiagnostic(
123        std::make_unique<CXDiagnosticCustomNoteImpl>(Message, 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(Loc.getManager(), LangOpts, Loc);
138    else
139      L = clang_getNullLocation();
140    CurrentSet->appendDiagnostic(
141        std::make_unique<CXDiagnosticCustomNoteImpl>(Message, 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(*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(Unit)) {
201    LOG_BAD_TU(Unit);
202    return 0;
203  }
204  if (!cxtu::getASTUnit(Unit))
205    return 0;
206  return lazyCreateDiags(Unit, /*checkIfChanged=*/true)->getNumDiagnostics();
207}
208
209CXDiagnostic clang_getDiagnostic(CXTranslationUnit Unit, unsigned Index) {
210  if (cxtu::isNotUsableTU(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(Index);
224}
225
226CXDiagnosticSet clang_getDiagnosticSetFromTU(CXTranslationUnit Unit) {
227  if (cxtu::isNotUsableTU(Unit)) {
228    LOG_BAD_TU(Unit);
229    return nullptr;
230  }
231  if (!cxtu::getASTUnit(Unit))
232    return nullptr;
233  return static_cast<CXDiagnostic>(lazyCreateDiags(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(clang_getDiagnosticLocation(Diagnostic),
256                              &File, &Line, &Column, nullptr);
257    if (File) {
258      CXString FName = clang_getFileName(File);
259      Out << clang_getCString(FName) << ":" << Line << ":";
260      clang_disposeString(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, I);
270
271          unsigned StartLine, StartColumn, EndLine, EndColumn;
272          clang_getSpellingLocation(clang_getRangeStart(Range),
273                                    &StartFile, &StartLine, &StartColumn,
274                                    nullptr);
275          clang_getSpellingLocation(clang_getRangeEnd(Range),
276                                    &EndFile, &EndLine, &EndColumn, 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(Text))
304    Out << clang_getCString(Text);
305  else
306    Out << "<no diagnostic text>";
307  clang_disposeString(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(Diagnostic, nullptr);
316      if (const char *OptionText = clang_getCString(OptionName)) {
317        if (OptionText[0]) {
318          Out << " [" << OptionText;
319          NeedBracket = false;
320          NeedComma = true;
321        }
322      }
323      clang_disposeString(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(CategoryName);
346          NeedBracket = false;
347          NeedComma = true;
348          clang_disposeString(CategoryName);
349        }
350      }
351    }
352
353    (void) NeedComma; // Silence dead store warning.
354    if (!NeedBracket)
355      Out << "]";
356  }
357
358  return cxstring::createDup(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(DiagnosticIDs::getCategoryNameFromID(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(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