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