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