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