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