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/Frontend/SerializedDiagnosticPrinter.h"
19#include "llvm/ADT/StringRef.h"
20#include "llvm/ADT/Twine.h"
21#include "llvm/ADT/Optional.h"
22#include "clang/Basic/LLVM.h"
23#include "llvm/Support/ErrorHandling.h"
24#include "llvm/Bitcode/BitstreamReader.h"
25#include "llvm/Support/MemoryBuffer.h"
26#include <assert.h>
27
28using namespace clang;
29using namespace clang::cxstring;
30
31//===----------------------------------------------------------------------===//
32// Extend CXDiagnosticSetImpl which contains strings for diagnostics.
33//===----------------------------------------------------------------------===//
34
35typedef llvm::DenseMap<unsigned, llvm::StringRef> Strings;
36
37namespace {
38class CXLoadedDiagnosticSetImpl : public CXDiagnosticSetImpl {
39public:
40  CXLoadedDiagnosticSetImpl() : CXDiagnosticSetImpl(true), FakeFiles(FO) {}
41  virtual ~CXLoadedDiagnosticSetImpl() {}
42
43  llvm::StringRef makeString(const char *blob, unsigned blobLen);
44
45  llvm::BumpPtrAllocator Alloc;
46  Strings Categories;
47  Strings WarningFlags;
48  Strings FileNames;
49
50  FileSystemOptions FO;
51  FileManager FakeFiles;
52  llvm::DenseMap<unsigned, const FileEntry *> Files;
53};
54}
55
56llvm::StringRef CXLoadedDiagnosticSetImpl::makeString(const char *blob,
57                                                      unsigned bloblen) {
58  char *mem = Alloc.Allocate<char>(bloblen + 1);
59  memcpy(mem, blob, bloblen);
60  // Add a null terminator for those clients accessing the buffer
61  // like a c-string.
62  mem[bloblen] = '\0';
63  return llvm::StringRef(mem, bloblen);
64}
65
66//===----------------------------------------------------------------------===//
67// Cleanup.
68//===----------------------------------------------------------------------===//
69
70CXLoadedDiagnostic::~CXLoadedDiagnostic() {}
71
72//===----------------------------------------------------------------------===//
73// Public CXLoadedDiagnostic methods.
74//===----------------------------------------------------------------------===//
75
76CXDiagnosticSeverity CXLoadedDiagnostic::getSeverity() const {
77  // FIXME: possibly refactor with logic in CXStoredDiagnostic.
78  switch (severity) {
79    case DiagnosticsEngine::Ignored: return CXDiagnostic_Ignored;
80    case DiagnosticsEngine::Note:    return CXDiagnostic_Note;
81    case DiagnosticsEngine::Warning: return CXDiagnostic_Warning;
82    case DiagnosticsEngine::Error:   return CXDiagnostic_Error;
83    case DiagnosticsEngine::Fatal:   return CXDiagnostic_Fatal;
84  }
85
86  llvm_unreachable("Invalid diagnostic level");
87}
88
89static CXSourceLocation makeLocation(const CXLoadedDiagnostic::Location *DLoc) {
90  // The lowest bit of ptr_data[0] is always set to 1 to indicate this
91  // is a persistent diagnostic.
92  uintptr_t V = (uintptr_t) DLoc;
93  V |= 0x1;
94  CXSourceLocation Loc = { {  (void*) V, 0 }, 0 };
95  return Loc;
96}
97
98CXSourceLocation CXLoadedDiagnostic::getLocation() const {
99  // The lowest bit of ptr_data[0] is always set to 1 to indicate this
100  // is a persistent diagnostic.
101  return makeLocation(&DiagLoc);
102}
103
104CXString CXLoadedDiagnostic::getSpelling() const {
105  return cxstring::createCXString(Spelling, false);
106}
107
108CXString CXLoadedDiagnostic::getDiagnosticOption(CXString *Disable) const {
109  if (DiagOption.empty())
110    return createCXString("");
111
112  // FIXME: possibly refactor with logic in CXStoredDiagnostic.
113  if (Disable)
114    *Disable = createCXString((Twine("-Wno-") + DiagOption).str());
115  return createCXString((Twine("-W") + DiagOption).str());
116}
117
118unsigned CXLoadedDiagnostic::getCategory() const {
119  return category;
120}
121
122CXString CXLoadedDiagnostic::getCategoryText() const {
123  return cxstring::createCXString(CategoryText);
124}
125
126unsigned CXLoadedDiagnostic::getNumRanges() const {
127  return Ranges.size();
128}
129
130CXSourceRange CXLoadedDiagnostic::getRange(unsigned Range) const {
131  assert(Range < Ranges.size());
132  return Ranges[Range];
133}
134
135unsigned CXLoadedDiagnostic::getNumFixIts() const {
136  return FixIts.size();
137}
138
139CXString CXLoadedDiagnostic::getFixIt(unsigned FixIt,
140                                      CXSourceRange *ReplacementRange) const {
141  assert(FixIt < FixIts.size());
142  if (ReplacementRange)
143    *ReplacementRange = FixIts[FixIt].first;
144  return FixIts[FixIt].second;
145}
146
147void CXLoadedDiagnostic::decodeLocation(CXSourceLocation location,
148                                        CXFile *file,
149                                        unsigned int *line,
150                                        unsigned int *column,
151                                        unsigned int *offset) {
152
153
154  // CXSourceLocation consists of the following fields:
155  //
156  //   void *ptr_data[2];
157  //   unsigned int_data;
158  //
159  // The lowest bit of ptr_data[0] is always set to 1 to indicate this
160  // is a persistent diagnostic.
161  //
162  // For now, do the unoptimized approach and store the data in a side
163  // data structure.  We can optimize this case later.
164
165  uintptr_t V = (uintptr_t) location.ptr_data[0];
166  assert((V & 0x1) == 1);
167  V &= ~(uintptr_t)1;
168
169  const Location &Loc = *((Location*)V);
170
171  if (file)
172    *file = Loc.file;
173  if (line)
174    *line = Loc.line;
175  if (column)
176    *column = Loc.column;
177  if (offset)
178    *offset = Loc.offset;
179}
180
181//===----------------------------------------------------------------------===//
182// Deserialize diagnostics.
183//===----------------------------------------------------------------------===//
184
185enum { MaxSupportedVersion = 1 };
186typedef SmallVector<uint64_t, 64> RecordData;
187enum LoadResult { Failure = 1, Success = 0 };
188enum StreamResult { Read_EndOfStream,
189                    Read_BlockBegin,
190                    Read_Failure,
191                    Read_Record,
192                    Read_BlockEnd };
193
194namespace {
195class DiagLoader {
196  enum CXLoadDiag_Error *error;
197  CXString *errorString;
198
199  void reportBad(enum CXLoadDiag_Error code, llvm::StringRef err) {
200    if (error)
201      *error = code;
202    if (errorString)
203      *errorString = createCXString(err);
204  }
205
206  void reportInvalidFile(llvm::StringRef err) {
207    return reportBad(CXLoadDiag_InvalidFile, err);
208  }
209
210  LoadResult readMetaBlock(llvm::BitstreamCursor &Stream);
211
212  LoadResult readDiagnosticBlock(llvm::BitstreamCursor &Stream,
213                                 CXDiagnosticSetImpl &Diags,
214                                 CXLoadedDiagnosticSetImpl &TopDiags);
215
216  StreamResult readToNextRecordOrBlock(llvm::BitstreamCursor &Stream,
217                                       llvm::StringRef errorContext,
218                                       unsigned &BlockOrRecordID,
219                                       const bool atTopLevel = false);
220
221
222  LoadResult readString(CXLoadedDiagnosticSetImpl &TopDiags,
223                        Strings &strings, llvm::StringRef errorContext,
224                        RecordData &Record,
225                        const char *BlobStart,
226                        unsigned BlobLen,
227                        bool allowEmptyString = false);
228
229  LoadResult readString(CXLoadedDiagnosticSetImpl &TopDiags,
230                        llvm::StringRef &RetStr,
231                        llvm::StringRef errorContext,
232                        RecordData &Record,
233                        const char *BlobStart,
234                        unsigned BlobLen,
235                        bool allowEmptyString = false);
236
237  LoadResult readRange(CXLoadedDiagnosticSetImpl &TopDiags,
238                       RecordData &Record, unsigned RecStartIdx,
239                       CXSourceRange &SR);
240
241  LoadResult readLocation(CXLoadedDiagnosticSetImpl &TopDiags,
242                          RecordData &Record, unsigned &offset,
243                          CXLoadedDiagnostic::Location &Loc);
244
245public:
246  DiagLoader(enum CXLoadDiag_Error *e, CXString *es)
247    : error(e), errorString(es) {
248      if (error)
249        *error = CXLoadDiag_None;
250      if (errorString)
251        *errorString = createCXString("");
252    }
253
254  CXDiagnosticSet load(const char *file);
255};
256}
257
258CXDiagnosticSet DiagLoader::load(const char *file) {
259  // Open the diagnostics file.
260  std::string ErrStr;
261  FileSystemOptions FO;
262  FileManager FileMgr(FO);
263
264  OwningPtr<llvm::MemoryBuffer> Buffer;
265  Buffer.reset(FileMgr.getBufferForFile(file));
266
267  if (!Buffer) {
268    reportBad(CXLoadDiag_CannotLoad, ErrStr);
269    return 0;
270  }
271
272  llvm::BitstreamReader StreamFile;
273  StreamFile.init((const unsigned char *)Buffer->getBufferStart(),
274                  (const unsigned char *)Buffer->getBufferEnd());
275
276  llvm::BitstreamCursor Stream;
277  Stream.init(StreamFile);
278
279  // Sniff for the signature.
280  if (Stream.Read(8) != 'D' ||
281      Stream.Read(8) != 'I' ||
282      Stream.Read(8) != 'A' ||
283      Stream.Read(8) != 'G') {
284    reportBad(CXLoadDiag_InvalidFile,
285              "Bad header in diagnostics file");
286    return 0;
287  }
288
289  OwningPtr<CXLoadedDiagnosticSetImpl>
290    Diags(new CXLoadedDiagnosticSetImpl());
291
292  while (true) {
293    unsigned BlockID = 0;
294    StreamResult Res = readToNextRecordOrBlock(Stream, "Top-level",
295                                               BlockID, true);
296    switch (Res) {
297      case Read_EndOfStream:
298        return (CXDiagnosticSet) Diags.take();
299      case Read_Failure:
300        return 0;
301      case Read_Record:
302        llvm_unreachable("Top-level does not have records");
303      case Read_BlockEnd:
304        continue;
305      case Read_BlockBegin:
306        break;
307    }
308
309    switch (BlockID) {
310      case serialized_diags::BLOCK_META:
311        if (readMetaBlock(Stream))
312          return 0;
313        break;
314      case serialized_diags::BLOCK_DIAG:
315        if (readDiagnosticBlock(Stream, *Diags.get(), *Diags.get()))
316          return 0;
317        break;
318      default:
319        if (!Stream.SkipBlock()) {
320          reportInvalidFile("Malformed block at top-level of diagnostics file");
321          return 0;
322        }
323        break;
324    }
325  }
326}
327
328StreamResult DiagLoader::readToNextRecordOrBlock(llvm::BitstreamCursor &Stream,
329                                                 llvm::StringRef errorContext,
330                                                 unsigned &blockOrRecordID,
331                                                 const bool atTopLevel) {
332
333  blockOrRecordID = 0;
334
335  while (!Stream.AtEndOfStream()) {
336    unsigned Code = Stream.ReadCode();
337
338    // Handle the top-level specially.
339    if (atTopLevel) {
340      if (Code == llvm::bitc::ENTER_SUBBLOCK) {
341        unsigned BlockID = Stream.ReadSubBlockID();
342        if (BlockID == llvm::bitc::BLOCKINFO_BLOCK_ID) {
343          if (Stream.ReadBlockInfoBlock()) {
344            reportInvalidFile("Malformed BlockInfoBlock in diagnostics file");
345            return Read_Failure;
346          }
347          continue;
348        }
349        blockOrRecordID = BlockID;
350        return Read_BlockBegin;
351      }
352      reportInvalidFile("Only blocks can appear at the top of a "
353                        "diagnostic file");
354      return Read_Failure;
355    }
356
357    switch ((llvm::bitc::FixedAbbrevIDs)Code) {
358      case llvm::bitc::ENTER_SUBBLOCK:
359        blockOrRecordID = Stream.ReadSubBlockID();
360        return Read_BlockBegin;
361
362      case llvm::bitc::END_BLOCK:
363        if (Stream.ReadBlockEnd()) {
364          reportInvalidFile("Cannot read end of block");
365          return Read_Failure;
366        }
367        return Read_BlockEnd;
368
369      case llvm::bitc::DEFINE_ABBREV:
370        Stream.ReadAbbrevRecord();
371        continue;
372
373      case llvm::bitc::UNABBREV_RECORD:
374        reportInvalidFile("Diagnostics file should have no unabbreviated "
375                          "records");
376        return Read_Failure;
377
378      default:
379        // We found a record.
380        blockOrRecordID = Code;
381        return Read_Record;
382    }
383  }
384
385  if (atTopLevel)
386    return Read_EndOfStream;
387
388  reportInvalidFile(Twine("Premature end of diagnostics file within ").str() +
389                    errorContext.str());
390  return Read_Failure;
391}
392
393LoadResult DiagLoader::readMetaBlock(llvm::BitstreamCursor &Stream) {
394  if (Stream.EnterSubBlock(clang::serialized_diags::BLOCK_META)) {
395    reportInvalidFile("Malformed metadata block");
396    return Failure;
397  }
398
399  bool versionChecked = false;
400
401  while (true) {
402    unsigned blockOrCode = 0;
403    StreamResult Res = readToNextRecordOrBlock(Stream, "Metadata Block",
404                                               blockOrCode);
405
406    switch(Res) {
407      case Read_EndOfStream:
408        llvm_unreachable("EndOfStream handled by readToNextRecordOrBlock");
409      case Read_Failure:
410        return Failure;
411      case Read_Record:
412        break;
413      case Read_BlockBegin:
414        if (Stream.SkipBlock()) {
415          reportInvalidFile("Malformed metadata block");
416          return Failure;
417        }
418      case Read_BlockEnd:
419        if (!versionChecked) {
420          reportInvalidFile("Diagnostics file does not contain version"
421                            " information");
422          return Failure;
423        }
424        return Success;
425    }
426
427    RecordData Record;
428    const char *Blob;
429    unsigned BlobLen;
430    unsigned recordID = Stream.ReadRecord(blockOrCode, Record, &Blob, &BlobLen);
431
432    if (recordID == serialized_diags::RECORD_VERSION) {
433      if (Record.size() < 1) {
434        reportInvalidFile("malformed VERSION identifier in diagnostics file");
435        return Failure;
436      }
437      if (Record[0] > MaxSupportedVersion) {
438        reportInvalidFile("diagnosics file is a newer version than the one "
439                          "supported");
440        return Failure;
441      }
442      versionChecked = true;
443    }
444  }
445}
446
447LoadResult DiagLoader::readString(CXLoadedDiagnosticSetImpl &TopDiags,
448                                  llvm::StringRef &RetStr,
449                                  llvm::StringRef errorContext,
450                                  RecordData &Record,
451                                  const char *BlobStart,
452                                  unsigned BlobLen,
453                                  bool allowEmptyString) {
454
455  // Basic buffer overflow check.
456  if (BlobLen > 65536) {
457    reportInvalidFile(std::string("Out-of-bounds string in ") +
458                      std::string(errorContext));
459    return Failure;
460  }
461
462  if (allowEmptyString && Record.size() >= 1 && BlobLen == 0) {
463    RetStr = "";
464    return Success;
465  }
466
467  if (Record.size() < 1 || BlobLen == 0) {
468    reportInvalidFile(std::string("Corrupted ") + std::string(errorContext)
469                      + std::string(" entry"));
470    return Failure;
471  }
472
473  RetStr = TopDiags.makeString(BlobStart, BlobLen);
474  return Success;
475}
476
477LoadResult DiagLoader::readString(CXLoadedDiagnosticSetImpl &TopDiags,
478                                  Strings &strings,
479                                  llvm::StringRef errorContext,
480                                  RecordData &Record,
481                                  const char *BlobStart,
482                                  unsigned BlobLen,
483                                  bool allowEmptyString) {
484  llvm::StringRef RetStr;
485  if (readString(TopDiags, RetStr, errorContext, Record, BlobStart, BlobLen,
486                 allowEmptyString))
487    return Failure;
488  strings[Record[0]] = RetStr;
489  return Success;
490}
491
492LoadResult DiagLoader::readLocation(CXLoadedDiagnosticSetImpl &TopDiags,
493                                    RecordData &Record, unsigned &offset,
494                                    CXLoadedDiagnostic::Location &Loc) {
495  if (Record.size() < offset + 3) {
496    reportInvalidFile("Corrupted source location");
497    return Failure;
498  }
499
500  unsigned fileID = Record[offset++];
501  if (fileID == 0) {
502    // Sentinel value.
503    Loc.file = 0;
504    Loc.line = 0;
505    Loc.column = 0;
506    Loc.offset = 0;
507    return Success;
508  }
509
510  const FileEntry *FE = TopDiags.Files[fileID];
511  if (!FE) {
512    reportInvalidFile("Corrupted file entry in source location");
513    return Failure;
514  }
515  Loc.file = (void*) FE;
516  Loc.line = Record[offset++];
517  Loc.column = Record[offset++];
518  Loc.offset = Record[offset++];
519  return Success;
520}
521
522LoadResult DiagLoader::readRange(CXLoadedDiagnosticSetImpl &TopDiags,
523                                 RecordData &Record,
524                                 unsigned int RecStartIdx,
525                                 CXSourceRange &SR) {
526  CXLoadedDiagnostic::Location *Start, *End;
527  Start = TopDiags.Alloc.Allocate<CXLoadedDiagnostic::Location>();
528  End = TopDiags.Alloc.Allocate<CXLoadedDiagnostic::Location>();
529
530  if (readLocation(TopDiags, Record, RecStartIdx, *Start))
531    return Failure;
532  if (readLocation(TopDiags, Record, RecStartIdx, *End))
533    return Failure;
534
535  CXSourceLocation startLoc = makeLocation(Start);
536  CXSourceLocation endLoc = makeLocation(End);
537  SR = clang_getRange(startLoc, endLoc);
538  return Success;
539}
540
541LoadResult DiagLoader::readDiagnosticBlock(llvm::BitstreamCursor &Stream,
542                                           CXDiagnosticSetImpl &Diags,
543                                           CXLoadedDiagnosticSetImpl &TopDiags){
544
545  if (Stream.EnterSubBlock(clang::serialized_diags::BLOCK_DIAG)) {
546    reportInvalidFile("malformed diagnostic block");
547    return Failure;
548  }
549
550  OwningPtr<CXLoadedDiagnostic> D(new CXLoadedDiagnostic());
551  RecordData Record;
552
553  while (true) {
554    unsigned blockOrCode = 0;
555    StreamResult Res = readToNextRecordOrBlock(Stream, "Diagnostic Block",
556                                               blockOrCode);
557    switch (Res) {
558      case Read_EndOfStream:
559        llvm_unreachable("EndOfStream handled in readToNextRecordOrBlock");
560      case Read_Failure:
561        return Failure;
562      case Read_BlockBegin: {
563        // The only blocks we care about are subdiagnostics.
564        if (blockOrCode != serialized_diags::BLOCK_DIAG) {
565          if (!Stream.SkipBlock()) {
566            reportInvalidFile("Invalid subblock in Diagnostics block");
567            return Failure;
568          }
569        } else if (readDiagnosticBlock(Stream, D->getChildDiagnostics(),
570                                       TopDiags)) {
571          return Failure;
572        }
573
574        continue;
575      }
576      case Read_BlockEnd:
577        Diags.appendDiagnostic(D.take());
578        return Success;
579      case Read_Record:
580        break;
581    }
582
583    // Read the record.
584    Record.clear();
585    const char *BlobStart = 0;
586    unsigned BlobLen = 0;
587    unsigned recID = Stream.ReadRecord(blockOrCode, Record,
588                                       BlobStart, BlobLen);
589
590    if (recID < serialized_diags::RECORD_FIRST ||
591        recID > serialized_diags::RECORD_LAST)
592      continue;
593
594    switch ((serialized_diags::RecordIDs)recID) {
595      case serialized_diags::RECORD_VERSION:
596        continue;
597      case serialized_diags::RECORD_CATEGORY:
598        if (readString(TopDiags, TopDiags.Categories, "category", Record,
599                       BlobStart, BlobLen,
600                       /* allowEmptyString */ true))
601          return Failure;
602        continue;
603
604      case serialized_diags::RECORD_DIAG_FLAG:
605        if (readString(TopDiags, TopDiags.WarningFlags, "warning flag", Record,
606                       BlobStart, BlobLen))
607          return Failure;
608        continue;
609
610      case serialized_diags::RECORD_FILENAME: {
611        if (readString(TopDiags, TopDiags.FileNames, "filename", Record,
612                       BlobStart, BlobLen))
613          return Failure;
614
615        if (Record.size() < 3) {
616          reportInvalidFile("Invalid file entry");
617          return Failure;
618        }
619
620        const FileEntry *FE =
621          TopDiags.FakeFiles.getVirtualFile(TopDiags.FileNames[Record[0]],
622                                            /* size */ Record[1],
623                                            /* time */ Record[2]);
624
625        TopDiags.Files[Record[0]] = FE;
626        continue;
627      }
628
629      case serialized_diags::RECORD_SOURCE_RANGE: {
630        CXSourceRange SR;
631        if (readRange(TopDiags, Record, 0, SR))
632          return Failure;
633        D->Ranges.push_back(SR);
634        continue;
635      }
636
637      case serialized_diags::RECORD_FIXIT: {
638        CXSourceRange SR;
639        if (readRange(TopDiags, Record, 0, SR))
640          return Failure;
641        llvm::StringRef RetStr;
642        if (readString(TopDiags, RetStr, "FIXIT", Record, BlobStart, BlobLen,
643                       /* allowEmptyString */ true))
644          return Failure;
645        D->FixIts.push_back(std::make_pair(SR, createCXString(RetStr, false)));
646        continue;
647      }
648
649      case serialized_diags::RECORD_DIAG: {
650        D->severity = Record[0];
651        unsigned offset = 1;
652        if (readLocation(TopDiags, Record, offset, D->DiagLoc))
653          return Failure;
654        D->category = Record[offset++];
655        unsigned diagFlag = Record[offset++];
656        D->DiagOption = diagFlag ? TopDiags.WarningFlags[diagFlag] : "";
657        D->CategoryText = D->category ? TopDiags.Categories[D->category] : "";
658        D->Spelling = TopDiags.makeString(BlobStart, BlobLen);
659        continue;
660      }
661    }
662  }
663}
664
665extern "C" {
666CXDiagnosticSet clang_loadDiagnostics(const char *file,
667                                      enum CXLoadDiag_Error *error,
668                                      CXString *errorString) {
669  DiagLoader L(error, errorString);
670  return L.load(file);
671}
672} // end extern 'C'.
673