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