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: 37 CXLoadedDiagnosticSetImpl() : CXDiagnosticSetImpl(true), FakeFiles(FO) {} 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. 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 63 CXLoadedDiagnostic::~CXLoadedDiagnostic() {} 64 65 //===----------------------------------------------------------------------===// 66 // Public CXLoadedDiagnostic methods. 67 //===----------------------------------------------------------------------===// 68 69 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 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 99 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 105 CXString CXLoadedDiagnostic::getSpelling() const { 106 return cxstring::createRef(Spelling); 107 } 108 109 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 119 unsigned CXLoadedDiagnostic::getCategory() const { 120 return category; 121 } 122 123 CXString CXLoadedDiagnostic::getCategoryText() const { 124 return cxstring::createDup(CategoryText); 125 } 126 127 unsigned CXLoadedDiagnostic::getNumRanges() const { 128 return Ranges.size(); 129 } 130 131 CXSourceRange CXLoadedDiagnostic::getRange(unsigned Range) const { 132 assert(Range < Ranges.size()); 133 return Ranges[Range]; 134 } 135 136 unsigned CXLoadedDiagnostic::getNumFixIts() const { 137 return FixIts.size(); 138 } 139 140 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 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 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 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: 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 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 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 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 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 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 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 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 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" { 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