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