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