CXLoadedDiagnostic.cpp revision e97ac9e684aecb5fc3fb9f86da09b8bb9dc31ff4
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 225 LoadResult readString(CXLoadedDiagnosticSetImpl &TopDiags, 226 llvm::StringRef &RetStr, 227 llvm::StringRef errorContext, 228 RecordData &Record, 229 const char *BlobStart, 230 unsigned BlobLen); 231 232 LoadResult readRange(CXLoadedDiagnosticSetImpl &TopDiags, 233 RecordData &Record, unsigned RecStartIdx, 234 CXSourceRange &SR); 235 236 LoadResult readLocation(CXLoadedDiagnosticSetImpl &TopDiags, 237 RecordData &Record, unsigned &offset, 238 CXLoadedDiagnostic::Location &Loc); 239 240public: 241 DiagLoader(enum CXLoadDiag_Error *e, CXString *es) 242 : error(e), errorString(es) { 243 if (error) 244 *error = CXLoadDiag_None; 245 if (errorString) 246 *errorString = createCXString(""); 247 } 248 249 CXDiagnosticSet load(const char *file); 250}; 251} 252 253CXDiagnosticSet DiagLoader::load(const char *file) { 254 // Open the diagnostics file. 255 std::string ErrStr; 256 FileSystemOptions FO; 257 FileManager FileMgr(FO); 258 259 llvm::OwningPtr<llvm::MemoryBuffer> Buffer; 260 Buffer.reset(FileMgr.getBufferForFile(file)); 261 262 if (!Buffer) { 263 reportBad(CXLoadDiag_CannotLoad, ErrStr); 264 return 0; 265 } 266 267 llvm::BitstreamReader StreamFile; 268 StreamFile.init((const unsigned char *)Buffer->getBufferStart(), 269 (const unsigned char *)Buffer->getBufferEnd()); 270 271 llvm::BitstreamCursor Stream; 272 Stream.init(StreamFile); 273 274 // Sniff for the signature. 275 if (Stream.Read(8) != 'D' || 276 Stream.Read(8) != 'I' || 277 Stream.Read(8) != 'A' || 278 Stream.Read(8) != 'G') { 279 reportBad(CXLoadDiag_InvalidFile, 280 "Bad header in diagnostics file"); 281 return 0; 282 } 283 284 llvm::OwningPtr<CXLoadedDiagnosticSetImpl> 285 Diags(new CXLoadedDiagnosticSetImpl()); 286 287 while (true) { 288 unsigned BlockID = 0; 289 StreamResult Res = readToNextRecordOrBlock(Stream, "Top-level", 290 BlockID, true); 291 switch (Res) { 292 case Read_EndOfStream: 293 return (CXDiagnosticSet) Diags.take(); 294 case Read_Failure: 295 return 0; 296 case Read_Record: 297 llvm_unreachable("Top-level does not have records"); 298 return 0; 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 450 // Basic buffer overflow check. 451 if (BlobLen > 65536) { 452 reportInvalidFile(std::string("Out-of-bounds string in ") + 453 std::string(errorContext)); 454 return Failure; 455 } 456 457 if (Record.size() < 1 || BlobLen == 0) { 458 reportInvalidFile(std::string("Corrupted ") + std::string(errorContext) 459 + std::string(" entry")); 460 return Failure; 461 } 462 463 RetStr = TopDiags.makeString(BlobStart, BlobLen); 464 return Success; 465} 466 467LoadResult DiagLoader::readString(CXLoadedDiagnosticSetImpl &TopDiags, 468 Strings &strings, 469 llvm::StringRef errorContext, 470 RecordData &Record, 471 const char *BlobStart, 472 unsigned BlobLen) { 473 llvm::StringRef RetStr; 474 if (readString(TopDiags, RetStr, errorContext, Record, BlobStart, BlobLen)) 475 return Failure; 476 strings[Record[0]] = RetStr; 477 return Success; 478} 479 480LoadResult DiagLoader::readLocation(CXLoadedDiagnosticSetImpl &TopDiags, 481 RecordData &Record, unsigned &offset, 482 CXLoadedDiagnostic::Location &Loc) { 483 if (Record.size() < offset + 3) { 484 reportInvalidFile("Corrupted source location"); 485 return Failure; 486 } 487 488 unsigned fileID = Record[offset++]; 489 if (fileID == 0) { 490 // Sentinel value. 491 Loc.file = 0; 492 Loc.line = 0; 493 Loc.column = 0; 494 Loc.offset = 0; 495 return Success; 496 } 497 498 const FileEntry *FE = TopDiags.Files[fileID]; 499 if (!FE) { 500 reportInvalidFile("Corrupted file entry in source location"); 501 return Failure; 502 } 503 Loc.file = (void*) FE; 504 Loc.line = Record[offset++]; 505 Loc.column = Record[offset++]; 506 Loc.offset = Record[offset++]; 507 return Success; 508} 509 510LoadResult DiagLoader::readRange(CXLoadedDiagnosticSetImpl &TopDiags, 511 RecordData &Record, 512 unsigned int RecStartIdx, 513 CXSourceRange &SR) { 514 CXLoadedDiagnostic::Location *Start, *End; 515 Start = TopDiags.Alloc.Allocate<CXLoadedDiagnostic::Location>(); 516 End = TopDiags.Alloc.Allocate<CXLoadedDiagnostic::Location>(); 517 518 if (readLocation(TopDiags, Record, RecStartIdx, *Start)) 519 return Failure; 520 if (readLocation(TopDiags, Record, RecStartIdx, *End)) 521 return Failure; 522 523 CXSourceLocation startLoc = makeLocation(Start); 524 CXSourceLocation endLoc = makeLocation(End); 525 SR = clang_getRange(startLoc, endLoc); 526 return Success; 527} 528 529LoadResult DiagLoader::readDiagnosticBlock(llvm::BitstreamCursor &Stream, 530 CXDiagnosticSetImpl &Diags, 531 CXLoadedDiagnosticSetImpl &TopDiags){ 532 533 if (Stream.EnterSubBlock(clang::serialized_diags::BLOCK_DIAG)) { 534 reportInvalidFile("malformed diagnostic block"); 535 return Failure; 536 } 537 538 llvm::OwningPtr<CXLoadedDiagnostic> D(new CXLoadedDiagnostic()); 539 RecordData Record; 540 541 while (true) { 542 unsigned blockOrCode = 0; 543 StreamResult Res = readToNextRecordOrBlock(Stream, "Diagnostic Block", 544 blockOrCode); 545 switch (Res) { 546 case Read_EndOfStream: 547 llvm_unreachable("EndOfStream handled in readToNextRecordOrBlock"); 548 return Failure; 549 case Read_Failure: 550 return Failure; 551 case Read_BlockBegin: { 552 // The only blocks we care about are subdiagnostics. 553 if (blockOrCode != serialized_diags::BLOCK_DIAG) { 554 if (!Stream.SkipBlock()) { 555 reportInvalidFile("Invalid subblock in Diagnostics block"); 556 return Failure; 557 } 558 } else if (readDiagnosticBlock(Stream, D->getChildDiagnostics(), 559 TopDiags)) { 560 return Failure; 561 } 562 563 continue; 564 } 565 case Read_BlockEnd: 566 Diags.appendDiagnostic(D.take()); 567 return Success; 568 case Read_Record: 569 break; 570 } 571 572 // Read the record. 573 Record.clear(); 574 const char *BlobStart = 0; 575 unsigned BlobLen = 0; 576 unsigned recID = Stream.ReadRecord(blockOrCode, Record, 577 BlobStart, BlobLen); 578 579 if (recID < serialized_diags::RECORD_FIRST || 580 recID > serialized_diags::RECORD_LAST) 581 continue; 582 583 switch ((serialized_diags::RecordIDs)recID) { 584 case serialized_diags::RECORD_VERSION: 585 continue; 586 case serialized_diags::RECORD_CATEGORY: 587 if (readString(TopDiags, TopDiags.Categories, "category", Record, 588 BlobStart, BlobLen)) 589 return Failure; 590 continue; 591 592 case serialized_diags::RECORD_DIAG_FLAG: 593 if (readString(TopDiags, TopDiags.WarningFlags, "warning flag", Record, 594 BlobStart, BlobLen)) 595 return Failure; 596 continue; 597 598 case serialized_diags::RECORD_FILENAME: { 599 if (readString(TopDiags, TopDiags.FileNames, "filename", Record, 600 BlobStart, BlobLen)) 601 return Failure; 602 603 if (Record.size() < 3) { 604 reportInvalidFile("Invalid file entry"); 605 return Failure; 606 } 607 608 const FileEntry *FE = 609 TopDiags.FakeFiles.getVirtualFile(TopDiags.FileNames[Record[0]], 610 /* size */ Record[1], 611 /* time */ Record[2]); 612 613 TopDiags.Files[Record[0]] = FE; 614 continue; 615 } 616 617 case serialized_diags::RECORD_SOURCE_RANGE: { 618 CXSourceRange SR; 619 if (readRange(TopDiags, Record, 0, SR)) 620 return Failure; 621 D->Ranges.push_back(SR); 622 continue; 623 } 624 625 case serialized_diags::RECORD_FIXIT: { 626 CXSourceRange SR; 627 if (readRange(TopDiags, Record, 0, SR)) 628 return Failure; 629 llvm::StringRef RetStr; 630 if (readString(TopDiags, RetStr, "FIXIT", Record, BlobStart, BlobLen)) 631 return Failure; 632 D->FixIts.push_back(std::make_pair(SR, createCXString(RetStr, false))); 633 continue; 634 } 635 636 case serialized_diags::RECORD_DIAG: { 637 D->severity = Record[0]; 638 unsigned offset = 1; 639 if (readLocation(TopDiags, Record, offset, D->DiagLoc)) 640 return Failure; 641 D->category = Record[offset++]; 642 unsigned diagFlag = Record[offset++]; 643 D->DiagOption = diagFlag ? TopDiags.WarningFlags[diagFlag] : ""; 644 D->Spelling = TopDiags.makeString(BlobStart, BlobLen); 645 continue; 646 } 647 } 648 } 649} 650 651extern "C" { 652CXDiagnosticSet clang_loadDiagnostics(const char *file, 653 enum CXLoadDiag_Error *error, 654 CXString *errorString) { 655 DiagLoader L(error, errorString); 656 return L.load(file); 657} 658} // end extern 'C'. 659