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