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: 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: 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 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 91 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 97 CXString CXLoadedDiagnostic::getSpelling() const { 98 return cxstring::createRef(Spelling); 99 } 100 101 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 111 unsigned CXLoadedDiagnostic::getCategory() const { 112 return category; 113 } 114 115 CXString CXLoadedDiagnostic::getCategoryText() const { 116 return cxstring::createDup(CategoryText); 117 } 118 119 unsigned CXLoadedDiagnostic::getNumRanges() const { 120 return Ranges.size(); 121 } 122 123 CXSourceRange CXLoadedDiagnostic::getRange(unsigned Range) const { 124 assert(Range < Ranges.size()); 125 return Ranges[Range]; 126 } 127 128 unsigned CXLoadedDiagnostic::getNumFixIts() const { 129 return FixIts.size(); 130 } 131 132 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 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 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 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: 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 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 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 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 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 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 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 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 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" { 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