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