CXLoadedDiagnostic.cpp revision ef8225444452a1486bd721f3285301fe84643b00
1//===-- CXLoadedDiagnostic.cpp - Handling of persisent diags ----*- C++ -*-===//
2//
3//                     The LLVM Compiler Infrastructure
4//
5// This file is distributed under the University of Illinois Open Source
6// License. See LICENSE.TXT for details.
7//
8//===----------------------------------------------------------------------===//
9//
10// Implements handling of persisent diagnostics.
11//
12//===----------------------------------------------------------------------===//
13
14#include "CXLoadedDiagnostic.h"
15#include "CXString.h"
16#include "clang/Basic/Diagnostic.h"
17#include "clang/Basic/FileManager.h"
18#include "clang/Basic/LLVM.h"
19#include "clang/Frontend/SerializedDiagnosticPrinter.h"
20#include "llvm/ADT/Optional.h"
21#include "llvm/ADT/StringRef.h"
22#include "llvm/ADT/Twine.h"
23#include "llvm/Bitcode/BitstreamReader.h"
24#include "llvm/Support/ErrorHandling.h"
25#include "llvm/Support/MemoryBuffer.h"
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  virtual ~CXLoadedDiagnosticSetImpl() {}
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  /// \brief 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}
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
186enum { MaxSupportedVersion = 2 };
187typedef SmallVector<uint64_t, 64> RecordData;
188enum LoadResult { Failure = 1, Success = 0 };
189enum StreamResult { Read_EndOfStream,
190                    Read_BlockBegin,
191                    Read_Failure,
192                    Read_Record,
193                    Read_BlockEnd };
194
195namespace {
196class DiagLoader {
197  enum CXLoadDiag_Error *error;
198  CXString *errorString;
199
200  void reportBad(enum CXLoadDiag_Error code, llvm::StringRef err) {
201    if (error)
202      *error = code;
203    if (errorString)
204      *errorString = cxstring::createDup(err);
205  }
206
207  void reportInvalidFile(llvm::StringRef err) {
208    return reportBad(CXLoadDiag_InvalidFile, err);
209  }
210
211  LoadResult readMetaBlock(llvm::BitstreamCursor &Stream);
212
213  LoadResult readDiagnosticBlock(llvm::BitstreamCursor &Stream,
214                                 CXDiagnosticSetImpl &Diags,
215                                 CXLoadedDiagnosticSetImpl &TopDiags);
216
217  StreamResult readToNextRecordOrBlock(llvm::BitstreamCursor &Stream,
218                                       llvm::StringRef errorContext,
219                                       unsigned &BlockOrRecordID,
220                                       bool atTopLevel = false);
221
222
223  LoadResult readString(CXLoadedDiagnosticSetImpl &TopDiags,
224                        Strings &strings, llvm::StringRef errorContext,
225                        RecordData &Record,
226                        StringRef Blob,
227                        bool allowEmptyString = false);
228
229  LoadResult readString(CXLoadedDiagnosticSetImpl &TopDiags,
230                        const char *&RetStr,
231                        llvm::StringRef errorContext,
232                        RecordData &Record,
233                        StringRef Blob,
234                        bool allowEmptyString = false);
235
236  LoadResult readRange(CXLoadedDiagnosticSetImpl &TopDiags,
237                       RecordData &Record, unsigned RecStartIdx,
238                       CXSourceRange &SR);
239
240  LoadResult readLocation(CXLoadedDiagnosticSetImpl &TopDiags,
241                          RecordData &Record, unsigned &offset,
242                          CXLoadedDiagnostic::Location &Loc);
243
244public:
245  DiagLoader(enum CXLoadDiag_Error *e, CXString *es)
246    : error(e), errorString(es) {
247      if (error)
248        *error = CXLoadDiag_None;
249      if (errorString)
250        *errorString = cxstring::createEmpty();
251    }
252
253  CXDiagnosticSet load(const char *file);
254};
255}
256
257CXDiagnosticSet DiagLoader::load(const char *file) {
258  // Open the diagnostics file.
259  std::string ErrStr;
260  FileSystemOptions FO;
261  FileManager FileMgr(FO);
262
263  std::unique_ptr<llvm::MemoryBuffer> Buffer;
264  Buffer.reset(FileMgr.getBufferForFile(file));
265
266  if (!Buffer) {
267    reportBad(CXLoadDiag_CannotLoad, ErrStr);
268    return nullptr;
269  }
270
271  llvm::BitstreamReader StreamFile;
272  StreamFile.init((const unsigned char *)Buffer->getBufferStart(),
273                  (const unsigned char *)Buffer->getBufferEnd());
274
275  llvm::BitstreamCursor Stream;
276  Stream.init(StreamFile);
277
278  // Sniff for the signature.
279  if (Stream.Read(8) != 'D' ||
280      Stream.Read(8) != 'I' ||
281      Stream.Read(8) != 'A' ||
282      Stream.Read(8) != 'G') {
283    reportBad(CXLoadDiag_InvalidFile,
284              "Bad header in diagnostics file");
285    return nullptr;
286  }
287
288  std::unique_ptr<CXLoadedDiagnosticSetImpl> Diags(
289      new CXLoadedDiagnosticSetImpl());
290
291  while (true) {
292    unsigned BlockID = 0;
293    StreamResult Res = readToNextRecordOrBlock(Stream, "Top-level",
294                                               BlockID, true);
295    switch (Res) {
296      case Read_EndOfStream:
297        return (CXDiagnosticSet)Diags.release();
298      case Read_Failure:
299        return nullptr;
300      case Read_Record:
301        llvm_unreachable("Top-level does not have records");
302      case Read_BlockEnd:
303        continue;
304      case Read_BlockBegin:
305        break;
306    }
307
308    switch (BlockID) {
309      case serialized_diags::BLOCK_META:
310        if (readMetaBlock(Stream))
311          return nullptr;
312        break;
313      case serialized_diags::BLOCK_DIAG:
314        if (readDiagnosticBlock(Stream, *Diags.get(), *Diags.get()))
315          return nullptr;
316        break;
317      default:
318        if (!Stream.SkipBlock()) {
319          reportInvalidFile("Malformed block at top-level of diagnostics file");
320          return nullptr;
321        }
322        break;
323    }
324  }
325}
326
327StreamResult DiagLoader::readToNextRecordOrBlock(llvm::BitstreamCursor &Stream,
328                                                 llvm::StringRef errorContext,
329                                                 unsigned &blockOrRecordID,
330                                                 bool atTopLevel) {
331
332  blockOrRecordID = 0;
333
334  while (!Stream.AtEndOfStream()) {
335    unsigned Code = Stream.ReadCode();
336
337    // Handle the top-level specially.
338    if (atTopLevel) {
339      if (Code == llvm::bitc::ENTER_SUBBLOCK) {
340        unsigned BlockID = Stream.ReadSubBlockID();
341        if (BlockID == llvm::bitc::BLOCKINFO_BLOCK_ID) {
342          if (Stream.ReadBlockInfoBlock()) {
343            reportInvalidFile("Malformed BlockInfoBlock in diagnostics file");
344            return Read_Failure;
345          }
346          continue;
347        }
348        blockOrRecordID = BlockID;
349        return Read_BlockBegin;
350      }
351      reportInvalidFile("Only blocks can appear at the top of a "
352                        "diagnostic file");
353      return Read_Failure;
354    }
355
356    switch ((llvm::bitc::FixedAbbrevIDs)Code) {
357      case llvm::bitc::ENTER_SUBBLOCK:
358        blockOrRecordID = Stream.ReadSubBlockID();
359        return Read_BlockBegin;
360
361      case llvm::bitc::END_BLOCK:
362        if (Stream.ReadBlockEnd()) {
363          reportInvalidFile("Cannot read end of block");
364          return Read_Failure;
365        }
366        return Read_BlockEnd;
367
368      case llvm::bitc::DEFINE_ABBREV:
369        Stream.ReadAbbrevRecord();
370        continue;
371
372      case llvm::bitc::UNABBREV_RECORD:
373        reportInvalidFile("Diagnostics file should have no unabbreviated "
374                          "records");
375        return Read_Failure;
376
377      default:
378        // We found a record.
379        blockOrRecordID = Code;
380        return Read_Record;
381    }
382  }
383
384  if (atTopLevel)
385    return Read_EndOfStream;
386
387  reportInvalidFile(Twine("Premature end of diagnostics file within ").str() +
388                    errorContext.str());
389  return Read_Failure;
390}
391
392LoadResult DiagLoader::readMetaBlock(llvm::BitstreamCursor &Stream) {
393  if (Stream.EnterSubBlock(clang::serialized_diags::BLOCK_META)) {
394    reportInvalidFile("Malformed metadata block");
395    return Failure;
396  }
397
398  bool versionChecked = false;
399
400  while (true) {
401    unsigned blockOrCode = 0;
402    StreamResult Res = readToNextRecordOrBlock(Stream, "Metadata Block",
403                                               blockOrCode);
404
405    switch(Res) {
406      case Read_EndOfStream:
407        llvm_unreachable("EndOfStream handled by readToNextRecordOrBlock");
408      case Read_Failure:
409        return Failure;
410      case Read_Record:
411        break;
412      case Read_BlockBegin:
413        if (Stream.SkipBlock()) {
414          reportInvalidFile("Malformed metadata block");
415          return Failure;
416        }
417      case Read_BlockEnd:
418        if (!versionChecked) {
419          reportInvalidFile("Diagnostics file does not contain version"
420                            " information");
421          return Failure;
422        }
423        return Success;
424    }
425
426    RecordData Record;
427    unsigned recordID = Stream.readRecord(blockOrCode, Record);
428
429    if (recordID == serialized_diags::RECORD_VERSION) {
430      if (Record.size() < 1) {
431        reportInvalidFile("malformed VERSION identifier in diagnostics file");
432        return Failure;
433      }
434      if (Record[0] > MaxSupportedVersion) {
435        reportInvalidFile("diagnostics file is a newer version than the one "
436                          "supported");
437        return Failure;
438      }
439      versionChecked = true;
440    }
441  }
442}
443
444LoadResult DiagLoader::readString(CXLoadedDiagnosticSetImpl &TopDiags,
445                                  const char *&RetStr,
446                                  llvm::StringRef errorContext,
447                                  RecordData &Record,
448                                  StringRef Blob,
449                                  bool allowEmptyString) {
450
451  // Basic buffer overflow check.
452  if (Blob.size() > 65536) {
453    reportInvalidFile(std::string("Out-of-bounds string in ") +
454                      std::string(errorContext));
455    return Failure;
456  }
457
458  if (allowEmptyString && Record.size() >= 1 && Blob.size() == 0) {
459    RetStr = "";
460    return Success;
461  }
462
463  if (Record.size() < 1 || Blob.size() == 0) {
464    reportInvalidFile(std::string("Corrupted ") + std::string(errorContext)
465                      + std::string(" entry"));
466    return Failure;
467  }
468
469  RetStr = TopDiags.copyString(Blob);
470  return Success;
471}
472
473LoadResult DiagLoader::readString(CXLoadedDiagnosticSetImpl &TopDiags,
474                                  Strings &strings,
475                                  llvm::StringRef errorContext,
476                                  RecordData &Record,
477                                  StringRef Blob,
478                                  bool allowEmptyString) {
479  const char *RetStr;
480  if (readString(TopDiags, RetStr, errorContext, Record, Blob,
481                 allowEmptyString))
482    return Failure;
483  strings[Record[0]] = RetStr;
484  return Success;
485}
486
487LoadResult DiagLoader::readLocation(CXLoadedDiagnosticSetImpl &TopDiags,
488                                    RecordData &Record, unsigned &offset,
489                                    CXLoadedDiagnostic::Location &Loc) {
490  if (Record.size() < offset + 3) {
491    reportInvalidFile("Corrupted source location");
492    return Failure;
493  }
494
495  unsigned fileID = Record[offset++];
496  if (fileID == 0) {
497    // Sentinel value.
498    Loc.file = nullptr;
499    Loc.line = 0;
500    Loc.column = 0;
501    Loc.offset = 0;
502    return Success;
503  }
504
505  const FileEntry *FE = TopDiags.Files[fileID];
506  if (!FE) {
507    reportInvalidFile("Corrupted file entry in source location");
508    return Failure;
509  }
510  Loc.file = const_cast<FileEntry *>(FE);
511  Loc.line = Record[offset++];
512  Loc.column = Record[offset++];
513  Loc.offset = Record[offset++];
514  return Success;
515}
516
517LoadResult DiagLoader::readRange(CXLoadedDiagnosticSetImpl &TopDiags,
518                                 RecordData &Record,
519                                 unsigned int RecStartIdx,
520                                 CXSourceRange &SR) {
521  CXLoadedDiagnostic::Location *Start, *End;
522  Start = TopDiags.Alloc.Allocate<CXLoadedDiagnostic::Location>();
523  End = TopDiags.Alloc.Allocate<CXLoadedDiagnostic::Location>();
524
525  if (readLocation(TopDiags, Record, RecStartIdx, *Start))
526    return Failure;
527  if (readLocation(TopDiags, Record, RecStartIdx, *End))
528    return Failure;
529
530  CXSourceLocation startLoc = makeLocation(Start);
531  CXSourceLocation endLoc = makeLocation(End);
532  SR = clang_getRange(startLoc, endLoc);
533  return Success;
534}
535
536LoadResult DiagLoader::readDiagnosticBlock(llvm::BitstreamCursor &Stream,
537                                           CXDiagnosticSetImpl &Diags,
538                                           CXLoadedDiagnosticSetImpl &TopDiags){
539
540  if (Stream.EnterSubBlock(clang::serialized_diags::BLOCK_DIAG)) {
541    reportInvalidFile("malformed diagnostic block");
542    return Failure;
543  }
544
545  std::unique_ptr<CXLoadedDiagnostic> D(new CXLoadedDiagnostic());
546  RecordData Record;
547
548  while (true) {
549    unsigned blockOrCode = 0;
550    StreamResult Res = readToNextRecordOrBlock(Stream, "Diagnostic Block",
551                                               blockOrCode);
552    switch (Res) {
553      case Read_EndOfStream:
554        llvm_unreachable("EndOfStream handled in readToNextRecordOrBlock");
555      case Read_Failure:
556        return Failure;
557      case Read_BlockBegin: {
558        // The only blocks we care about are subdiagnostics.
559        if (blockOrCode != serialized_diags::BLOCK_DIAG) {
560          if (!Stream.SkipBlock()) {
561            reportInvalidFile("Invalid subblock in Diagnostics block");
562            return Failure;
563          }
564        } else if (readDiagnosticBlock(Stream, D->getChildDiagnostics(),
565                                       TopDiags)) {
566          return Failure;
567        }
568
569        continue;
570      }
571      case Read_BlockEnd:
572        Diags.appendDiagnostic(D.release());
573        return Success;
574      case Read_Record:
575        break;
576    }
577
578    // Read the record.
579    Record.clear();
580    StringRef Blob;
581    unsigned recID = Stream.readRecord(blockOrCode, Record, &Blob);
582
583    if (recID < serialized_diags::RECORD_FIRST ||
584        recID > serialized_diags::RECORD_LAST)
585      continue;
586
587    switch ((serialized_diags::RecordIDs)recID) {
588      case serialized_diags::RECORD_VERSION:
589        continue;
590      case serialized_diags::RECORD_CATEGORY:
591        if (readString(TopDiags, TopDiags.Categories, "category", Record,
592                       Blob, /* allowEmptyString */ true))
593          return Failure;
594        continue;
595
596      case serialized_diags::RECORD_DIAG_FLAG:
597        if (readString(TopDiags, TopDiags.WarningFlags, "warning flag", Record,
598                       Blob))
599          return Failure;
600        continue;
601
602      case serialized_diags::RECORD_FILENAME: {
603        if (readString(TopDiags, TopDiags.FileNames, "filename", Record,
604                       Blob))
605          return Failure;
606
607        if (Record.size() < 3) {
608          reportInvalidFile("Invalid file entry");
609          return Failure;
610        }
611
612        const FileEntry *FE =
613          TopDiags.FakeFiles.getVirtualFile(TopDiags.FileNames[Record[0]],
614                                            /* size */ Record[1],
615                                            /* time */ Record[2]);
616
617        TopDiags.Files[Record[0]] = FE;
618        continue;
619      }
620
621      case serialized_diags::RECORD_SOURCE_RANGE: {
622        CXSourceRange SR;
623        if (readRange(TopDiags, Record, 0, SR))
624          return Failure;
625        D->Ranges.push_back(SR);
626        continue;
627      }
628
629      case serialized_diags::RECORD_FIXIT: {
630        CXSourceRange SR;
631        if (readRange(TopDiags, Record, 0, SR))
632          return Failure;
633        const char *RetStr;
634        if (readString(TopDiags, RetStr, "FIXIT", Record, Blob,
635                       /* allowEmptyString */ true))
636          return Failure;
637        D->FixIts.push_back(std::make_pair(SR, RetStr));
638        continue;
639      }
640
641      case serialized_diags::RECORD_DIAG: {
642        D->severity = Record[0];
643        unsigned offset = 1;
644        if (readLocation(TopDiags, Record, offset, D->DiagLoc))
645          return Failure;
646        D->category = Record[offset++];
647        unsigned diagFlag = Record[offset++];
648        D->DiagOption = diagFlag ? TopDiags.WarningFlags[diagFlag] : "";
649        D->CategoryText = D->category ? TopDiags.Categories[D->category] : "";
650        D->Spelling = TopDiags.copyString(Blob);
651        continue;
652      }
653    }
654  }
655}
656
657extern "C" {
658CXDiagnosticSet clang_loadDiagnostics(const char *file,
659                                      enum CXLoadDiag_Error *error,
660                                      CXString *errorString) {
661  DiagLoader L(error, errorString);
662  return L.load(file);
663}
664} // end extern 'C'.
665