CXLoadedDiagnostic.cpp revision dc66adb40ee871b4c578ba615f45d82476c7119e
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" 26using namespace clang; 27using namespace clang::cxstring; 28 29//===----------------------------------------------------------------------===// 30// Extend CXDiagnosticSetImpl which contains strings for diagnostics. 31//===----------------------------------------------------------------------===// 32 33typedef llvm::DenseMap<unsigned, llvm::StringRef> Strings; 34 35namespace { 36class CXLoadedDiagnosticSetImpl : public CXDiagnosticSetImpl { 37public: 38 CXLoadedDiagnosticSetImpl() : CXDiagnosticSetImpl(true), FakeFiles(FO) {} 39 virtual ~CXLoadedDiagnosticSetImpl() {} 40 41 llvm::BumpPtrAllocator Alloc; 42 Strings Categories; 43 Strings WarningFlags; 44 Strings FileNames; 45 46 FileSystemOptions FO; 47 FileManager FakeFiles; 48 llvm::DenseMap<unsigned, const FileEntry *> Files; 49 50 llvm::StringRef makeString(StringRef Blob) { 51 char *mem = Alloc.Allocate<char>(Blob.size() + 1); 52 memcpy(mem, Blob.data(), Blob.size()); 53 // Add a null terminator for those clients accessing the buffer 54 // like a c-string. 55 mem[Blob.size()] = '\0'; 56 return llvm::StringRef(mem, Blob.size()); 57 } 58}; 59} 60 61//===----------------------------------------------------------------------===// 62// Cleanup. 63//===----------------------------------------------------------------------===// 64 65CXLoadedDiagnostic::~CXLoadedDiagnostic() {} 66 67//===----------------------------------------------------------------------===// 68// Public CXLoadedDiagnostic methods. 69//===----------------------------------------------------------------------===// 70 71CXDiagnosticSeverity CXLoadedDiagnostic::getSeverity() const { 72 // FIXME: possibly refactor with logic in CXStoredDiagnostic. 73 switch (severity) { 74 case DiagnosticsEngine::Ignored: return CXDiagnostic_Ignored; 75 case DiagnosticsEngine::Note: return CXDiagnostic_Note; 76 case DiagnosticsEngine::Warning: return CXDiagnostic_Warning; 77 case DiagnosticsEngine::Error: return CXDiagnostic_Error; 78 case DiagnosticsEngine::Fatal: return CXDiagnostic_Fatal; 79 } 80 81 llvm_unreachable("Invalid diagnostic level"); 82} 83 84static CXSourceLocation makeLocation(const CXLoadedDiagnostic::Location *DLoc) { 85 // The lowest bit of ptr_data[0] is always set to 1 to indicate this 86 // is a persistent diagnostic. 87 uintptr_t V = (uintptr_t) DLoc; 88 V |= 0x1; 89 CXSourceLocation Loc = { { (void*) V, 0 }, 0 }; 90 return Loc; 91} 92 93CXSourceLocation CXLoadedDiagnostic::getLocation() const { 94 // The lowest bit of ptr_data[0] is always set to 1 to indicate this 95 // is a persistent diagnostic. 96 return makeLocation(&DiagLoc); 97} 98 99CXString CXLoadedDiagnostic::getSpelling() const { 100 return cxstring::createCXString(Spelling, false); 101} 102 103CXString CXLoadedDiagnostic::getDiagnosticOption(CXString *Disable) const { 104 if (DiagOption.empty()) 105 return cxstring::createEmpty(); 106 107 // FIXME: possibly refactor with logic in CXStoredDiagnostic. 108 if (Disable) 109 *Disable = createCXString((Twine("-Wno-") + DiagOption).str()); 110 return createCXString((Twine("-W") + DiagOption).str()); 111} 112 113unsigned CXLoadedDiagnostic::getCategory() const { 114 return category; 115} 116 117CXString CXLoadedDiagnostic::getCategoryText() const { 118 return cxstring::createCXString(CategoryText); 119} 120 121unsigned CXLoadedDiagnostic::getNumRanges() const { 122 return Ranges.size(); 123} 124 125CXSourceRange CXLoadedDiagnostic::getRange(unsigned Range) const { 126 assert(Range < Ranges.size()); 127 return Ranges[Range]; 128} 129 130unsigned CXLoadedDiagnostic::getNumFixIts() const { 131 return FixIts.size(); 132} 133 134CXString CXLoadedDiagnostic::getFixIt(unsigned FixIt, 135 CXSourceRange *ReplacementRange) const { 136 assert(FixIt < FixIts.size()); 137 if (ReplacementRange) 138 *ReplacementRange = FixIts[FixIt].first; 139 return FixIts[FixIt].second; 140} 141 142void CXLoadedDiagnostic::decodeLocation(CXSourceLocation location, 143 CXFile *file, 144 unsigned int *line, 145 unsigned int *column, 146 unsigned int *offset) { 147 148 149 // CXSourceLocation consists of the following fields: 150 // 151 // void *ptr_data[2]; 152 // unsigned int_data; 153 // 154 // The lowest bit of ptr_data[0] is always set to 1 to indicate this 155 // is a persistent diagnostic. 156 // 157 // For now, do the unoptimized approach and store the data in a side 158 // data structure. We can optimize this case later. 159 160 uintptr_t V = (uintptr_t) location.ptr_data[0]; 161 assert((V & 0x1) == 1); 162 V &= ~(uintptr_t)1; 163 164 const Location &Loc = *((Location*)V); 165 166 if (file) 167 *file = Loc.file; 168 if (line) 169 *line = Loc.line; 170 if (column) 171 *column = Loc.column; 172 if (offset) 173 *offset = Loc.offset; 174} 175 176//===----------------------------------------------------------------------===// 177// Deserialize diagnostics. 178//===----------------------------------------------------------------------===// 179 180enum { MaxSupportedVersion = 1 }; 181typedef SmallVector<uint64_t, 64> RecordData; 182enum LoadResult { Failure = 1, Success = 0 }; 183enum StreamResult { Read_EndOfStream, 184 Read_BlockBegin, 185 Read_Failure, 186 Read_Record, 187 Read_BlockEnd }; 188 189namespace { 190class DiagLoader { 191 enum CXLoadDiag_Error *error; 192 CXString *errorString; 193 194 void reportBad(enum CXLoadDiag_Error code, llvm::StringRef err) { 195 if (error) 196 *error = code; 197 if (errorString) 198 *errorString = createCXString(err); 199 } 200 201 void reportInvalidFile(llvm::StringRef err) { 202 return reportBad(CXLoadDiag_InvalidFile, err); 203 } 204 205 LoadResult readMetaBlock(llvm::BitstreamCursor &Stream); 206 207 LoadResult readDiagnosticBlock(llvm::BitstreamCursor &Stream, 208 CXDiagnosticSetImpl &Diags, 209 CXLoadedDiagnosticSetImpl &TopDiags); 210 211 StreamResult readToNextRecordOrBlock(llvm::BitstreamCursor &Stream, 212 llvm::StringRef errorContext, 213 unsigned &BlockOrRecordID, 214 bool atTopLevel = false); 215 216 217 LoadResult readString(CXLoadedDiagnosticSetImpl &TopDiags, 218 Strings &strings, llvm::StringRef errorContext, 219 RecordData &Record, 220 StringRef Blob, 221 bool allowEmptyString = false); 222 223 LoadResult readString(CXLoadedDiagnosticSetImpl &TopDiags, 224 llvm::StringRef &RetStr, 225 llvm::StringRef errorContext, 226 RecordData &Record, 227 StringRef Blob, 228 bool allowEmptyString = false); 229 230 LoadResult readRange(CXLoadedDiagnosticSetImpl &TopDiags, 231 RecordData &Record, unsigned RecStartIdx, 232 CXSourceRange &SR); 233 234 LoadResult readLocation(CXLoadedDiagnosticSetImpl &TopDiags, 235 RecordData &Record, unsigned &offset, 236 CXLoadedDiagnostic::Location &Loc); 237 238public: 239 DiagLoader(enum CXLoadDiag_Error *e, CXString *es) 240 : error(e), errorString(es) { 241 if (error) 242 *error = CXLoadDiag_None; 243 if (errorString) 244 *errorString = cxstring::createEmpty(); 245 } 246 247 CXDiagnosticSet load(const char *file); 248}; 249} 250 251CXDiagnosticSet DiagLoader::load(const char *file) { 252 // Open the diagnostics file. 253 std::string ErrStr; 254 FileSystemOptions FO; 255 FileManager FileMgr(FO); 256 257 OwningPtr<llvm::MemoryBuffer> Buffer; 258 Buffer.reset(FileMgr.getBufferForFile(file)); 259 260 if (!Buffer) { 261 reportBad(CXLoadDiag_CannotLoad, ErrStr); 262 return 0; 263 } 264 265 llvm::BitstreamReader StreamFile; 266 StreamFile.init((const unsigned char *)Buffer->getBufferStart(), 267 (const unsigned char *)Buffer->getBufferEnd()); 268 269 llvm::BitstreamCursor Stream; 270 Stream.init(StreamFile); 271 272 // Sniff for the signature. 273 if (Stream.Read(8) != 'D' || 274 Stream.Read(8) != 'I' || 275 Stream.Read(8) != 'A' || 276 Stream.Read(8) != 'G') { 277 reportBad(CXLoadDiag_InvalidFile, 278 "Bad header in diagnostics file"); 279 return 0; 280 } 281 282 OwningPtr<CXLoadedDiagnosticSetImpl> Diags(new CXLoadedDiagnosticSetImpl()); 283 284 while (true) { 285 unsigned BlockID = 0; 286 StreamResult Res = readToNextRecordOrBlock(Stream, "Top-level", 287 BlockID, true); 288 switch (Res) { 289 case Read_EndOfStream: 290 return (CXDiagnosticSet) Diags.take(); 291 case Read_Failure: 292 return 0; 293 case Read_Record: 294 llvm_unreachable("Top-level does not have records"); 295 case Read_BlockEnd: 296 continue; 297 case Read_BlockBegin: 298 break; 299 } 300 301 switch (BlockID) { 302 case serialized_diags::BLOCK_META: 303 if (readMetaBlock(Stream)) 304 return 0; 305 break; 306 case serialized_diags::BLOCK_DIAG: 307 if (readDiagnosticBlock(Stream, *Diags.get(), *Diags.get())) 308 return 0; 309 break; 310 default: 311 if (!Stream.SkipBlock()) { 312 reportInvalidFile("Malformed block at top-level of diagnostics file"); 313 return 0; 314 } 315 break; 316 } 317 } 318} 319 320StreamResult DiagLoader::readToNextRecordOrBlock(llvm::BitstreamCursor &Stream, 321 llvm::StringRef errorContext, 322 unsigned &blockOrRecordID, 323 bool atTopLevel) { 324 325 blockOrRecordID = 0; 326 327 while (!Stream.AtEndOfStream()) { 328 unsigned Code = Stream.ReadCode(); 329 330 // Handle the top-level specially. 331 if (atTopLevel) { 332 if (Code == llvm::bitc::ENTER_SUBBLOCK) { 333 unsigned BlockID = Stream.ReadSubBlockID(); 334 if (BlockID == llvm::bitc::BLOCKINFO_BLOCK_ID) { 335 if (Stream.ReadBlockInfoBlock()) { 336 reportInvalidFile("Malformed BlockInfoBlock in diagnostics file"); 337 return Read_Failure; 338 } 339 continue; 340 } 341 blockOrRecordID = BlockID; 342 return Read_BlockBegin; 343 } 344 reportInvalidFile("Only blocks can appear at the top of a " 345 "diagnostic file"); 346 return Read_Failure; 347 } 348 349 switch ((llvm::bitc::FixedAbbrevIDs)Code) { 350 case llvm::bitc::ENTER_SUBBLOCK: 351 blockOrRecordID = Stream.ReadSubBlockID(); 352 return Read_BlockBegin; 353 354 case llvm::bitc::END_BLOCK: 355 if (Stream.ReadBlockEnd()) { 356 reportInvalidFile("Cannot read end of block"); 357 return Read_Failure; 358 } 359 return Read_BlockEnd; 360 361 case llvm::bitc::DEFINE_ABBREV: 362 Stream.ReadAbbrevRecord(); 363 continue; 364 365 case llvm::bitc::UNABBREV_RECORD: 366 reportInvalidFile("Diagnostics file should have no unabbreviated " 367 "records"); 368 return Read_Failure; 369 370 default: 371 // We found a record. 372 blockOrRecordID = Code; 373 return Read_Record; 374 } 375 } 376 377 if (atTopLevel) 378 return Read_EndOfStream; 379 380 reportInvalidFile(Twine("Premature end of diagnostics file within ").str() + 381 errorContext.str()); 382 return Read_Failure; 383} 384 385LoadResult DiagLoader::readMetaBlock(llvm::BitstreamCursor &Stream) { 386 if (Stream.EnterSubBlock(clang::serialized_diags::BLOCK_META)) { 387 reportInvalidFile("Malformed metadata block"); 388 return Failure; 389 } 390 391 bool versionChecked = false; 392 393 while (true) { 394 unsigned blockOrCode = 0; 395 StreamResult Res = readToNextRecordOrBlock(Stream, "Metadata Block", 396 blockOrCode); 397 398 switch(Res) { 399 case Read_EndOfStream: 400 llvm_unreachable("EndOfStream handled by readToNextRecordOrBlock"); 401 case Read_Failure: 402 return Failure; 403 case Read_Record: 404 break; 405 case Read_BlockBegin: 406 if (Stream.SkipBlock()) { 407 reportInvalidFile("Malformed metadata block"); 408 return Failure; 409 } 410 case Read_BlockEnd: 411 if (!versionChecked) { 412 reportInvalidFile("Diagnostics file does not contain version" 413 " information"); 414 return Failure; 415 } 416 return Success; 417 } 418 419 RecordData Record; 420 unsigned recordID = Stream.readRecord(blockOrCode, Record); 421 422 if (recordID == serialized_diags::RECORD_VERSION) { 423 if (Record.size() < 1) { 424 reportInvalidFile("malformed VERSION identifier in diagnostics file"); 425 return Failure; 426 } 427 if (Record[0] > MaxSupportedVersion) { 428 reportInvalidFile("diagnosics file is a newer version than the one " 429 "supported"); 430 return Failure; 431 } 432 versionChecked = true; 433 } 434 } 435} 436 437LoadResult DiagLoader::readString(CXLoadedDiagnosticSetImpl &TopDiags, 438 llvm::StringRef &RetStr, 439 llvm::StringRef errorContext, 440 RecordData &Record, 441 StringRef Blob, 442 bool allowEmptyString) { 443 444 // Basic buffer overflow check. 445 if (Blob.size() > 65536) { 446 reportInvalidFile(std::string("Out-of-bounds string in ") + 447 std::string(errorContext)); 448 return Failure; 449 } 450 451 if (allowEmptyString && Record.size() >= 1 && Blob.size() == 0) { 452 RetStr = ""; 453 return Success; 454 } 455 456 if (Record.size() < 1 || Blob.size() == 0) { 457 reportInvalidFile(std::string("Corrupted ") + std::string(errorContext) 458 + std::string(" entry")); 459 return Failure; 460 } 461 462 RetStr = TopDiags.makeString(Blob); 463 return Success; 464} 465 466LoadResult DiagLoader::readString(CXLoadedDiagnosticSetImpl &TopDiags, 467 Strings &strings, 468 llvm::StringRef errorContext, 469 RecordData &Record, 470 StringRef Blob, 471 bool allowEmptyString) { 472 llvm::StringRef RetStr; 473 if (readString(TopDiags, RetStr, errorContext, Record, Blob, 474 allowEmptyString)) 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 = const_cast<FileEntry *>(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 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 case Read_Failure: 549 return Failure; 550 case Read_BlockBegin: { 551 // The only blocks we care about are subdiagnostics. 552 if (blockOrCode != serialized_diags::BLOCK_DIAG) { 553 if (!Stream.SkipBlock()) { 554 reportInvalidFile("Invalid subblock in Diagnostics block"); 555 return Failure; 556 } 557 } else if (readDiagnosticBlock(Stream, D->getChildDiagnostics(), 558 TopDiags)) { 559 return Failure; 560 } 561 562 continue; 563 } 564 case Read_BlockEnd: 565 Diags.appendDiagnostic(D.take()); 566 return Success; 567 case Read_Record: 568 break; 569 } 570 571 // Read the record. 572 Record.clear(); 573 StringRef Blob; 574 unsigned recID = Stream.readRecord(blockOrCode, Record, &Blob); 575 576 if (recID < serialized_diags::RECORD_FIRST || 577 recID > serialized_diags::RECORD_LAST) 578 continue; 579 580 switch ((serialized_diags::RecordIDs)recID) { 581 case serialized_diags::RECORD_VERSION: 582 continue; 583 case serialized_diags::RECORD_CATEGORY: 584 if (readString(TopDiags, TopDiags.Categories, "category", Record, 585 Blob, /* allowEmptyString */ true)) 586 return Failure; 587 continue; 588 589 case serialized_diags::RECORD_DIAG_FLAG: 590 if (readString(TopDiags, TopDiags.WarningFlags, "warning flag", Record, 591 Blob)) 592 return Failure; 593 continue; 594 595 case serialized_diags::RECORD_FILENAME: { 596 if (readString(TopDiags, TopDiags.FileNames, "filename", Record, 597 Blob)) 598 return Failure; 599 600 if (Record.size() < 3) { 601 reportInvalidFile("Invalid file entry"); 602 return Failure; 603 } 604 605 const FileEntry *FE = 606 TopDiags.FakeFiles.getVirtualFile(TopDiags.FileNames[Record[0]], 607 /* size */ Record[1], 608 /* time */ Record[2]); 609 610 TopDiags.Files[Record[0]] = FE; 611 continue; 612 } 613 614 case serialized_diags::RECORD_SOURCE_RANGE: { 615 CXSourceRange SR; 616 if (readRange(TopDiags, Record, 0, SR)) 617 return Failure; 618 D->Ranges.push_back(SR); 619 continue; 620 } 621 622 case serialized_diags::RECORD_FIXIT: { 623 CXSourceRange SR; 624 if (readRange(TopDiags, Record, 0, SR)) 625 return Failure; 626 llvm::StringRef RetStr; 627 if (readString(TopDiags, RetStr, "FIXIT", Record, Blob, 628 /* allowEmptyString */ true)) 629 return Failure; 630 D->FixIts.push_back(std::make_pair(SR, createCXString(RetStr, false))); 631 continue; 632 } 633 634 case serialized_diags::RECORD_DIAG: { 635 D->severity = Record[0]; 636 unsigned offset = 1; 637 if (readLocation(TopDiags, Record, offset, D->DiagLoc)) 638 return Failure; 639 D->category = Record[offset++]; 640 unsigned diagFlag = Record[offset++]; 641 D->DiagOption = diagFlag ? TopDiags.WarningFlags[diagFlag] : ""; 642 D->CategoryText = D->category ? TopDiags.Categories[D->category] : ""; 643 D->Spelling = TopDiags.makeString(Blob); 644 continue; 645 } 646 } 647 } 648} 649 650extern "C" { 651CXDiagnosticSet clang_loadDiagnostics(const char *file, 652 enum CXLoadDiag_Error *error, 653 CXString *errorString) { 654 DiagLoader L(error, errorString); 655 return L.load(file); 656} 657} // end extern 'C'. 658