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