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