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