• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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:
CXLoadedDiagnosticSetImpl()37   CXLoadedDiagnosticSetImpl() : CXDiagnosticSetImpl(true), FakeFiles(FO) {}
~CXLoadedDiagnosticSetImpl()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.
copyString(StringRef Blob)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 
~CXLoadedDiagnostic()63 CXLoadedDiagnostic::~CXLoadedDiagnostic() {}
64 
65 //===----------------------------------------------------------------------===//
66 // Public CXLoadedDiagnostic methods.
67 //===----------------------------------------------------------------------===//
68 
getSeverity() const69 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 
makeLocation(const CXLoadedDiagnostic::Location * DLoc)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 
getLocation() const91 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 
getSpelling() const97 CXString CXLoadedDiagnostic::getSpelling() const {
98   return cxstring::createRef(Spelling);
99 }
100 
getDiagnosticOption(CXString * Disable) const101 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 
getCategory() const111 unsigned CXLoadedDiagnostic::getCategory() const {
112   return category;
113 }
114 
getCategoryText() const115 CXString CXLoadedDiagnostic::getCategoryText() const {
116   return cxstring::createDup(CategoryText);
117 }
118 
getNumRanges() const119 unsigned CXLoadedDiagnostic::getNumRanges() const {
120   return Ranges.size();
121 }
122 
getRange(unsigned Range) const123 CXSourceRange CXLoadedDiagnostic::getRange(unsigned Range) const {
124   assert(Range < Ranges.size());
125   return Ranges[Range];
126 }
127 
getNumFixIts() const128 unsigned CXLoadedDiagnostic::getNumFixIts() const {
129   return FixIts.size();
130 }
131 
getFixIt(unsigned FixIt,CXSourceRange * ReplacementRange) const132 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 
decodeLocation(CXSourceLocation location,CXFile * file,unsigned int * line,unsigned int * column,unsigned int * offset)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 
reportBad(enum CXLoadDiag_Error code,llvm::StringRef err)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 
reportInvalidFile(llvm::StringRef err)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:
DiagLoader(enum CXLoadDiag_Error * e,CXString * es)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 
load(const char * file)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 
readToNextRecordOrBlock(llvm::BitstreamCursor & Stream,llvm::StringRef errorContext,unsigned & blockOrRecordID,bool atTopLevel)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 
readMetaBlock(llvm::BitstreamCursor & Stream)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 
readString(CXLoadedDiagnosticSetImpl & TopDiags,const char * & RetStr,llvm::StringRef errorContext,RecordData & Record,StringRef Blob,bool allowEmptyString)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 
readString(CXLoadedDiagnosticSetImpl & TopDiags,Strings & strings,llvm::StringRef errorContext,RecordData & Record,StringRef Blob,bool allowEmptyString)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 
readLocation(CXLoadedDiagnosticSetImpl & TopDiags,RecordData & Record,unsigned & offset,CXLoadedDiagnostic::Location & Loc)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 
readRange(CXLoadedDiagnosticSetImpl & TopDiags,RecordData & Record,unsigned int RecStartIdx,CXSourceRange & SR)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 
readDiagnosticBlock(llvm::BitstreamCursor & Stream,CXDiagnosticSetImpl & Diags,CXLoadedDiagnosticSetImpl & TopDiags)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" {
clang_loadDiagnostics(const char * file,enum CXLoadDiag_Error * error,CXString * errorString)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