CXLoadedDiagnostic.cpp revision 7530c034c0c71a64c5a9173206d9742ae847af8b
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
122unsigned CXLoadedDiagnostic::getNumRanges() const {
123  return Ranges.size();
124}
125
126CXSourceRange CXLoadedDiagnostic::getRange(unsigned Range) const {
127  assert(Range < Ranges.size());
128  return Ranges[Range];
129}
130
131unsigned CXLoadedDiagnostic::getNumFixIts() const {
132  return FixIts.size();
133}
134
135CXString CXLoadedDiagnostic::getFixIt(unsigned FixIt,
136                                      CXSourceRange *ReplacementRange) const {
137  assert(FixIt < FixIts.size());
138  if (ReplacementRange)
139    *ReplacementRange = FixIts[FixIt].first;
140  return FixIts[FixIt].second;
141}
142
143void CXLoadedDiagnostic::decodeLocation(CXSourceLocation location,
144                                        CXFile *file,
145                                        unsigned int *line,
146                                        unsigned int *column,
147                                        unsigned int *offset) {
148
149
150  // CXSourceLocation consists of the following fields:
151  //
152  //   void *ptr_data[2];
153  //   unsigned int_data;
154  //
155  // The lowest bit of ptr_data[0] is always set to 1 to indicate this
156  // is a persistent diagnostic.
157  //
158  // For now, do the unoptimized approach and store the data in a side
159  // data structure.  We can optimize this case later.
160
161  uintptr_t V = (uintptr_t) location.ptr_data[0];
162  assert((V & 0x1) == 1);
163  V &= ~(uintptr_t)1;
164
165  const Location &Loc = *((Location*)V);
166
167  if (file)
168    *file = Loc.file;
169  if (line)
170    *line = Loc.line;
171  if (column)
172    *column = Loc.column;
173  if (offset)
174    *offset = Loc.offset;
175}
176
177//===----------------------------------------------------------------------===//
178// Deserialize diagnostics.
179//===----------------------------------------------------------------------===//
180
181enum { MaxSupportedVersion = 1 };
182typedef SmallVector<uint64_t, 64> RecordData;
183enum LoadResult { Failure = 1, Success = 0 };
184enum StreamResult { Read_EndOfStream,
185                    Read_BlockBegin,
186                    Read_Failure,
187                    Read_Record,
188                    Read_BlockEnd };
189
190namespace {
191class DiagLoader {
192  enum CXLoadDiag_Error *error;
193  CXString *errorString;
194
195  void reportBad(enum CXLoadDiag_Error code, llvm::StringRef err) {
196    if (error)
197      *error = code;
198    if (errorString)
199      *errorString = createCXString(err);
200  }
201
202  void reportInvalidFile(llvm::StringRef err) {
203    return reportBad(CXLoadDiag_InvalidFile, err);
204  }
205
206  LoadResult readMetaBlock(llvm::BitstreamCursor &Stream);
207
208  LoadResult readDiagnosticBlock(llvm::BitstreamCursor &Stream,
209                                 CXDiagnosticSetImpl &Diags,
210                                 CXLoadedDiagnosticSetImpl &TopDiags);
211
212  StreamResult readToNextRecordOrBlock(llvm::BitstreamCursor &Stream,
213                                       llvm::StringRef errorContext,
214                                       unsigned &BlockOrRecordID,
215                                       const bool atTopLevel = false);
216
217
218  LoadResult readString(CXLoadedDiagnosticSetImpl &TopDiags,
219                        Strings &strings, llvm::StringRef errorContext,
220                        RecordData &Record,
221                        const char *BlobStart,
222                        unsigned BlobLen,
223                        bool allowEmptyString = false);
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                        bool allowEmptyString = false);
232
233  LoadResult readRange(CXLoadedDiagnosticSetImpl &TopDiags,
234                       RecordData &Record, unsigned RecStartIdx,
235                       CXSourceRange &SR);
236
237  LoadResult readLocation(CXLoadedDiagnosticSetImpl &TopDiags,
238                          RecordData &Record, unsigned &offset,
239                          CXLoadedDiagnostic::Location &Loc);
240
241public:
242  DiagLoader(enum CXLoadDiag_Error *e, CXString *es)
243    : error(e), errorString(es) {
244      if (error)
245        *error = CXLoadDiag_None;
246      if (errorString)
247        *errorString = createCXString("");
248    }
249
250  CXDiagnosticSet load(const char *file);
251};
252}
253
254CXDiagnosticSet DiagLoader::load(const char *file) {
255  // Open the diagnostics file.
256  std::string ErrStr;
257  FileSystemOptions FO;
258  FileManager FileMgr(FO);
259
260  llvm::OwningPtr<llvm::MemoryBuffer> Buffer;
261  Buffer.reset(FileMgr.getBufferForFile(file));
262
263  if (!Buffer) {
264    reportBad(CXLoadDiag_CannotLoad, ErrStr);
265    return 0;
266  }
267
268  llvm::BitstreamReader StreamFile;
269  StreamFile.init((const unsigned char *)Buffer->getBufferStart(),
270                  (const unsigned char *)Buffer->getBufferEnd());
271
272  llvm::BitstreamCursor Stream;
273  Stream.init(StreamFile);
274
275  // Sniff for the signature.
276  if (Stream.Read(8) != 'D' ||
277      Stream.Read(8) != 'I' ||
278      Stream.Read(8) != 'A' ||
279      Stream.Read(8) != 'G') {
280    reportBad(CXLoadDiag_InvalidFile,
281              "Bad header in diagnostics file");
282    return 0;
283  }
284
285  llvm::OwningPtr<CXLoadedDiagnosticSetImpl>
286    Diags(new CXLoadedDiagnosticSetImpl());
287
288  while (true) {
289    unsigned BlockID = 0;
290    StreamResult Res = readToNextRecordOrBlock(Stream, "Top-level",
291                                               BlockID, true);
292    switch (Res) {
293      case Read_EndOfStream:
294        return (CXDiagnosticSet) Diags.take();
295      case Read_Failure:
296        return 0;
297      case Read_Record:
298        llvm_unreachable("Top-level does not have records");
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                                  bool allowEmptyString) {
450
451  // Basic buffer overflow check.
452  if (BlobLen > 65536) {
453    reportInvalidFile(std::string("Out-of-bounds string in ") +
454                      std::string(errorContext));
455    return Failure;
456  }
457
458  if (allowEmptyString && Record.size() >= 1 && BlobLen == 0) {
459    RetStr = "";
460    return Success;
461  }
462
463  if (Record.size() < 1 || BlobLen == 0) {
464    reportInvalidFile(std::string("Corrupted ") + std::string(errorContext)
465                      + std::string(" entry"));
466    return Failure;
467  }
468
469  RetStr = TopDiags.makeString(BlobStart, BlobLen);
470  return Success;
471}
472
473LoadResult DiagLoader::readString(CXLoadedDiagnosticSetImpl &TopDiags,
474                                  Strings &strings,
475                                  llvm::StringRef errorContext,
476                                  RecordData &Record,
477                                  const char *BlobStart,
478                                  unsigned BlobLen,
479                                  bool allowEmptyString) {
480  llvm::StringRef RetStr;
481  if (readString(TopDiags, RetStr, errorContext, Record, BlobStart, BlobLen,
482                 allowEmptyString))
483    return Failure;
484  strings[Record[0]] = RetStr;
485  return Success;
486}
487
488LoadResult DiagLoader::readLocation(CXLoadedDiagnosticSetImpl &TopDiags,
489                                    RecordData &Record, unsigned &offset,
490                                    CXLoadedDiagnostic::Location &Loc) {
491  if (Record.size() < offset + 3) {
492    reportInvalidFile("Corrupted source location");
493    return Failure;
494  }
495
496  unsigned fileID = Record[offset++];
497  if (fileID == 0) {
498    // Sentinel value.
499    Loc.file = 0;
500    Loc.line = 0;
501    Loc.column = 0;
502    Loc.offset = 0;
503    return Success;
504  }
505
506  const FileEntry *FE = TopDiags.Files[fileID];
507  if (!FE) {
508    reportInvalidFile("Corrupted file entry in source location");
509    return Failure;
510  }
511  Loc.file = (void*) FE;
512  Loc.line = Record[offset++];
513  Loc.column = Record[offset++];
514  Loc.offset = Record[offset++];
515  return Success;
516}
517
518LoadResult DiagLoader::readRange(CXLoadedDiagnosticSetImpl &TopDiags,
519                                 RecordData &Record,
520                                 unsigned int RecStartIdx,
521                                 CXSourceRange &SR) {
522  CXLoadedDiagnostic::Location *Start, *End;
523  Start = TopDiags.Alloc.Allocate<CXLoadedDiagnostic::Location>();
524  End = TopDiags.Alloc.Allocate<CXLoadedDiagnostic::Location>();
525
526  if (readLocation(TopDiags, Record, RecStartIdx, *Start))
527    return Failure;
528  if (readLocation(TopDiags, Record, RecStartIdx, *End))
529    return Failure;
530
531  CXSourceLocation startLoc = makeLocation(Start);
532  CXSourceLocation endLoc = makeLocation(End);
533  SR = clang_getRange(startLoc, endLoc);
534  return Success;
535}
536
537LoadResult DiagLoader::readDiagnosticBlock(llvm::BitstreamCursor &Stream,
538                                           CXDiagnosticSetImpl &Diags,
539                                           CXLoadedDiagnosticSetImpl &TopDiags){
540
541  if (Stream.EnterSubBlock(clang::serialized_diags::BLOCK_DIAG)) {
542    reportInvalidFile("malformed diagnostic block");
543    return Failure;
544  }
545
546  llvm::OwningPtr<CXLoadedDiagnostic> D(new CXLoadedDiagnostic());
547  RecordData Record;
548
549  while (true) {
550    unsigned blockOrCode = 0;
551    StreamResult Res = readToNextRecordOrBlock(Stream, "Diagnostic Block",
552                                               blockOrCode);
553    switch (Res) {
554      case Read_EndOfStream:
555        llvm_unreachable("EndOfStream handled in readToNextRecordOrBlock");
556      case Read_Failure:
557        return Failure;
558      case Read_BlockBegin: {
559        // The only blocks we care about are subdiagnostics.
560        if (blockOrCode != serialized_diags::BLOCK_DIAG) {
561          if (!Stream.SkipBlock()) {
562            reportInvalidFile("Invalid subblock in Diagnostics block");
563            return Failure;
564          }
565        } else if (readDiagnosticBlock(Stream, D->getChildDiagnostics(),
566                                       TopDiags)) {
567          return Failure;
568        }
569
570        continue;
571      }
572      case Read_BlockEnd:
573        Diags.appendDiagnostic(D.take());
574        return Success;
575      case Read_Record:
576        break;
577    }
578
579    // Read the record.
580    Record.clear();
581    const char *BlobStart = 0;
582    unsigned BlobLen = 0;
583    unsigned recID = Stream.ReadRecord(blockOrCode, Record,
584                                       BlobStart, BlobLen);
585
586    if (recID < serialized_diags::RECORD_FIRST ||
587        recID > serialized_diags::RECORD_LAST)
588      continue;
589
590    switch ((serialized_diags::RecordIDs)recID) {
591      case serialized_diags::RECORD_VERSION:
592        continue;
593      case serialized_diags::RECORD_CATEGORY:
594        if (readString(TopDiags, TopDiags.Categories, "category", Record,
595                       BlobStart, BlobLen,
596                       /* allowEmptyString */ true))
597          return Failure;
598        continue;
599
600      case serialized_diags::RECORD_DIAG_FLAG:
601        if (readString(TopDiags, TopDiags.WarningFlags, "warning flag", Record,
602                       BlobStart, BlobLen))
603          return Failure;
604        continue;
605
606      case serialized_diags::RECORD_FILENAME: {
607        if (readString(TopDiags, TopDiags.FileNames, "filename", Record,
608                       BlobStart, BlobLen))
609          return Failure;
610
611        if (Record.size() < 3) {
612          reportInvalidFile("Invalid file entry");
613          return Failure;
614        }
615
616        const FileEntry *FE =
617          TopDiags.FakeFiles.getVirtualFile(TopDiags.FileNames[Record[0]],
618                                            /* size */ Record[1],
619                                            /* time */ Record[2]);
620
621        TopDiags.Files[Record[0]] = FE;
622        continue;
623      }
624
625      case serialized_diags::RECORD_SOURCE_RANGE: {
626        CXSourceRange SR;
627        if (readRange(TopDiags, Record, 0, SR))
628          return Failure;
629        D->Ranges.push_back(SR);
630        continue;
631      }
632
633      case serialized_diags::RECORD_FIXIT: {
634        CXSourceRange SR;
635        if (readRange(TopDiags, Record, 0, SR))
636          return Failure;
637        llvm::StringRef RetStr;
638        if (readString(TopDiags, RetStr, "FIXIT", Record, BlobStart, BlobLen,
639                       /* allowEmptyString */ true))
640          return Failure;
641        D->FixIts.push_back(std::make_pair(SR, createCXString(RetStr, false)));
642        continue;
643      }
644
645      case serialized_diags::RECORD_DIAG: {
646        D->severity = Record[0];
647        unsigned offset = 1;
648        if (readLocation(TopDiags, Record, offset, D->DiagLoc))
649          return Failure;
650        D->category = Record[offset++];
651        unsigned diagFlag = Record[offset++];
652        D->DiagOption = diagFlag ? TopDiags.WarningFlags[diagFlag] : "";
653        D->Spelling = TopDiags.makeString(BlobStart, BlobLen);
654        continue;
655      }
656    }
657  }
658}
659
660extern "C" {
661CXDiagnosticSet clang_loadDiagnostics(const char *file,
662                                      enum CXLoadDiag_Error *error,
663                                      CXString *errorString) {
664  DiagLoader L(error, errorString);
665  return L.load(file);
666}
667} // end extern 'C'.
668