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:
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: 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
makeLocation(const CXLoadedDiagnostic::Location * DLoc)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
getLocation() const99 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
getSpelling() const105 CXString CXLoadedDiagnostic::getSpelling() const {
106 return cxstring::createRef(Spelling);
107 }
108
getDiagnosticOption(CXString * Disable) const109 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
getCategory() const119 unsigned CXLoadedDiagnostic::getCategory() const {
120 return category;
121 }
122
getCategoryText() const123 CXString CXLoadedDiagnostic::getCategoryText() const {
124 return cxstring::createDup(CategoryText);
125 }
126
getNumRanges() const127 unsigned CXLoadedDiagnostic::getNumRanges() const {
128 return Ranges.size();
129 }
130
getRange(unsigned Range) const131 CXSourceRange CXLoadedDiagnostic::getRange(unsigned Range) const {
132 assert(Range < Ranges.size());
133 return Ranges[Range];
134 }
135
getNumFixIts() const136 unsigned CXLoadedDiagnostic::getNumFixIts() const {
137 return FixIts.size();
138 }
139
getFixIt(unsigned FixIt,CXSourceRange * ReplacementRange) const140 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
decodeLocation(CXSourceLocation location,CXFile * file,unsigned int * line,unsigned int * column,unsigned int * offset)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
reportBad(enum CXLoadDiag_Error code,llvm::StringRef err)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
reportInvalidFile(llvm::StringRef err)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:
DiagLoader(enum CXLoadDiag_Error * e,CXString * es)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
load(const char * file)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
readToNextRecordOrBlock(llvm::BitstreamCursor & Stream,llvm::StringRef errorContext,unsigned & blockOrRecordID,bool atTopLevel)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
readMetaBlock(llvm::BitstreamCursor & Stream)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
readString(CXLoadedDiagnosticSetImpl & TopDiags,const char * & RetStr,llvm::StringRef errorContext,RecordData & Record,StringRef Blob,bool allowEmptyString)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
readString(CXLoadedDiagnosticSetImpl & TopDiags,Strings & strings,llvm::StringRef errorContext,RecordData & Record,StringRef Blob,bool allowEmptyString)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
readLocation(CXLoadedDiagnosticSetImpl & TopDiags,RecordData & Record,unsigned & offset,CXLoadedDiagnostic::Location & Loc)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
readRange(CXLoadedDiagnosticSetImpl & TopDiags,RecordData & Record,unsigned int RecStartIdx,CXSourceRange & SR)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
readDiagnosticBlock(llvm::BitstreamCursor & Stream,CXDiagnosticSetImpl & Diags,CXLoadedDiagnosticSetImpl & TopDiags)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" {
clang_loadDiagnostics(const char * file,enum CXLoadDiag_Error * error,CXString * errorString)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