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