1 //===-- CXLoadedDiagnostic.cpp - Handling of persisent diags ----*- C++ -*-===// 2 // 3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 4 // See https://llvm.org/LICENSE.txt for license information. 5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 6 // 7 //===----------------------------------------------------------------------===// 8 // 9 // Implements handling of persisent diagnostics. 10 // 11 //===----------------------------------------------------------------------===// 12 13 #include "CXLoadedDiagnostic.h" 14 #include "CXString.h" 15 #include "clang/Basic/Diagnostic.h" 16 #include "clang/Basic/FileManager.h" 17 #include "clang/Basic/LLVM.h" 18 #include "clang/Frontend/SerializedDiagnosticReader.h" 19 #include "clang/Frontend/SerializedDiagnostics.h" 20 #include "llvm/ADT/STLExtras.h" 21 #include "llvm/ADT/StringRef.h" 22 #include "llvm/ADT/Twine.h" 23 #include "llvm/Bitstream/BitstreamReader.h" 24 #include "llvm/Support/ErrorHandling.h" 25 26 using namespace clang; 27 28 //===----------------------------------------------------------------------===// 29 // Extend CXDiagnosticSetImpl which contains strings for diagnostics. 30 //===----------------------------------------------------------------------===// 31 32 typedef llvm::DenseMap<unsigned, const char *> Strings; 33 34 namespace { 35 class CXLoadedDiagnosticSetImpl : public CXDiagnosticSetImpl { 36 public: 37 CXLoadedDiagnosticSetImpl() : CXDiagnosticSetImpl(true), FakeFiles(FO) {} 38 ~CXLoadedDiagnosticSetImpl() override {} 39 40 llvm::BumpPtrAllocator Alloc; 41 Strings Categories; 42 Strings WarningFlags; 43 Strings FileNames; 44 45 FileSystemOptions FO; 46 FileManager FakeFiles; 47 llvm::DenseMap<unsigned, const FileEntry *> Files; 48 49 /// Copy the string into our own allocator. 50 const char *copyString(StringRef Blob) { 51 char *mem = Alloc.Allocate<char>(Blob.size() + 1); 52 memcpy(mem, Blob.data(), Blob.size()); 53 mem[Blob.size()] = '\0'; 54 return mem; 55 } 56 }; 57 } // end anonymous namespace 58 59 //===----------------------------------------------------------------------===// 60 // Cleanup. 61 //===----------------------------------------------------------------------===// 62 63 CXLoadedDiagnostic::~CXLoadedDiagnostic() {} 64 65 //===----------------------------------------------------------------------===// 66 // Public CXLoadedDiagnostic methods. 67 //===----------------------------------------------------------------------===// 68 69 CXDiagnosticSeverity CXLoadedDiagnostic::getSeverity() const { 70 // FIXME: Fail more softly if the diagnostic level is unknown? 71 auto severityAsLevel = static_cast<serialized_diags::Level>(severity); 72 assert(severity == static_cast<unsigned>(severityAsLevel) && 73 "unknown serialized diagnostic level"); 74 75 switch (severityAsLevel) { 76 #define CASE(X) case serialized_diags::X: return CXDiagnostic_##X; 77 CASE(Ignored) 78 CASE(Note) 79 CASE(Warning) 80 CASE(Error) 81 CASE(Fatal) 82 #undef CASE 83 // The 'Remark' level isn't represented in the stable API. 84 case serialized_diags::Remark: return CXDiagnostic_Warning; 85 } 86 87 llvm_unreachable("Invalid diagnostic level"); 88 } 89 90 static 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, nullptr }, 0 }; 96 return Loc; 97 } 98 99 CXSourceLocation 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 105 CXString CXLoadedDiagnostic::getSpelling() const { 106 return cxstring::createRef(Spelling); 107 } 108 109 CXString CXLoadedDiagnostic::getDiagnosticOption(CXString *Disable) const { 110 if (DiagOption.empty()) 111 return cxstring::createEmpty(); 112 113 // FIXME: possibly refactor with logic in CXStoredDiagnostic. 114 if (Disable) 115 *Disable = cxstring::createDup((Twine("-Wno-") + DiagOption).str()); 116 return cxstring::createDup((Twine("-W") + DiagOption).str()); 117 } 118 119 unsigned CXLoadedDiagnostic::getCategory() const { 120 return category; 121 } 122 123 CXString CXLoadedDiagnostic::getCategoryText() const { 124 return cxstring::createDup(CategoryText); 125 } 126 127 unsigned CXLoadedDiagnostic::getNumRanges() const { 128 return Ranges.size(); 129 } 130 131 CXSourceRange CXLoadedDiagnostic::getRange(unsigned Range) const { 132 assert(Range < Ranges.size()); 133 return Ranges[Range]; 134 } 135 136 unsigned CXLoadedDiagnostic::getNumFixIts() const { 137 return FixIts.size(); 138 } 139 140 CXString CXLoadedDiagnostic::getFixIt(unsigned FixIt, 141 CXSourceRange *ReplacementRange) const { 142 assert(FixIt < FixIts.size()); 143 if (ReplacementRange) 144 *ReplacementRange = FixIts[FixIt].first; 145 return cxstring::createRef(FixIts[FixIt].second); 146 } 147 148 void CXLoadedDiagnostic::decodeLocation(CXSourceLocation location, 149 CXFile *file, 150 unsigned int *line, 151 unsigned int *column, 152 unsigned int *offset) { 153 154 155 // CXSourceLocation consists of the following fields: 156 // 157 // void *ptr_data[2]; 158 // unsigned int_data; 159 // 160 // The lowest bit of ptr_data[0] is always set to 1 to indicate this 161 // is a persistent diagnostic. 162 // 163 // For now, do the unoptimized approach and store the data in a side 164 // data structure. We can optimize this case later. 165 166 uintptr_t V = (uintptr_t) location.ptr_data[0]; 167 assert((V & 0x1) == 1); 168 V &= ~(uintptr_t)1; 169 170 const Location &Loc = *((Location*)V); 171 172 if (file) 173 *file = Loc.file; 174 if (line) 175 *line = Loc.line; 176 if (column) 177 *column = Loc.column; 178 if (offset) 179 *offset = Loc.offset; 180 } 181 182 //===----------------------------------------------------------------------===// 183 // Deserialize diagnostics. 184 //===----------------------------------------------------------------------===// 185 186 namespace { 187 class DiagLoader : serialized_diags::SerializedDiagnosticReader { 188 enum CXLoadDiag_Error *error; 189 CXString *errorString; 190 std::unique_ptr<CXLoadedDiagnosticSetImpl> TopDiags; 191 SmallVector<std::unique_ptr<CXLoadedDiagnostic>, 8> CurrentDiags; 192 193 std::error_code reportBad(enum CXLoadDiag_Error code, llvm::StringRef err) { 194 if (error) 195 *error = code; 196 if (errorString) 197 *errorString = cxstring::createDup(err); 198 return serialized_diags::SDError::HandlerFailed; 199 } 200 201 std::error_code reportInvalidFile(llvm::StringRef err) { 202 return reportBad(CXLoadDiag_InvalidFile, err); 203 } 204 205 std::error_code readRange(const serialized_diags::Location &SDStart, 206 const serialized_diags::Location &SDEnd, 207 CXSourceRange &SR); 208 209 std::error_code readLocation(const serialized_diags::Location &SDLoc, 210 CXLoadedDiagnostic::Location &LoadedLoc); 211 212 protected: 213 std::error_code visitStartOfDiagnostic() override; 214 std::error_code visitEndOfDiagnostic() override; 215 216 std::error_code visitCategoryRecord(unsigned ID, StringRef Name) override; 217 218 std::error_code visitDiagFlagRecord(unsigned ID, StringRef Name) override; 219 220 std::error_code visitDiagnosticRecord( 221 unsigned Severity, const serialized_diags::Location &Location, 222 unsigned Category, unsigned Flag, StringRef Message) override; 223 224 std::error_code visitFilenameRecord(unsigned ID, unsigned Size, 225 unsigned Timestamp, 226 StringRef Name) override; 227 228 std::error_code visitFixitRecord(const serialized_diags::Location &Start, 229 const serialized_diags::Location &End, 230 StringRef CodeToInsert) override; 231 232 std::error_code 233 visitSourceRangeRecord(const serialized_diags::Location &Start, 234 const serialized_diags::Location &End) override; 235 236 public: 237 DiagLoader(enum CXLoadDiag_Error *e, CXString *es) 238 : SerializedDiagnosticReader(), error(e), errorString(es) { 239 if (error) 240 *error = CXLoadDiag_None; 241 if (errorString) 242 *errorString = cxstring::createEmpty(); 243 } 244 245 CXDiagnosticSet load(const char *file); 246 }; 247 } // end anonymous namespace 248 249 CXDiagnosticSet DiagLoader::load(const char *file) { 250 TopDiags = std::make_unique<CXLoadedDiagnosticSetImpl>(); 251 252 std::error_code EC = readDiagnostics(file); 253 if (EC) { 254 switch (EC.value()) { 255 case static_cast<int>(serialized_diags::SDError::HandlerFailed): 256 // We've already reported the problem. 257 break; 258 case static_cast<int>(serialized_diags::SDError::CouldNotLoad): 259 reportBad(CXLoadDiag_CannotLoad, EC.message()); 260 break; 261 default: 262 reportInvalidFile(EC.message()); 263 break; 264 } 265 return nullptr; 266 } 267 268 return (CXDiagnosticSet)TopDiags.release(); 269 } 270 271 std::error_code 272 DiagLoader::readLocation(const serialized_diags::Location &SDLoc, 273 CXLoadedDiagnostic::Location &LoadedLoc) { 274 unsigned FileID = SDLoc.FileID; 275 if (FileID == 0) 276 LoadedLoc.file = nullptr; 277 else { 278 LoadedLoc.file = const_cast<FileEntry *>(TopDiags->Files[FileID]); 279 if (!LoadedLoc.file) 280 return reportInvalidFile("Corrupted file entry in source location"); 281 } 282 LoadedLoc.line = SDLoc.Line; 283 LoadedLoc.column = SDLoc.Col; 284 LoadedLoc.offset = SDLoc.Offset; 285 return std::error_code(); 286 } 287 288 std::error_code 289 DiagLoader::readRange(const serialized_diags::Location &SDStart, 290 const serialized_diags::Location &SDEnd, 291 CXSourceRange &SR) { 292 CXLoadedDiagnostic::Location *Start, *End; 293 Start = TopDiags->Alloc.Allocate<CXLoadedDiagnostic::Location>(); 294 End = TopDiags->Alloc.Allocate<CXLoadedDiagnostic::Location>(); 295 296 std::error_code EC; 297 if ((EC = readLocation(SDStart, *Start))) 298 return EC; 299 if ((EC = readLocation(SDEnd, *End))) 300 return EC; 301 302 CXSourceLocation startLoc = makeLocation(Start); 303 CXSourceLocation endLoc = makeLocation(End); 304 SR = clang_getRange(startLoc, endLoc); 305 return std::error_code(); 306 } 307 308 std::error_code DiagLoader::visitStartOfDiagnostic() { 309 CurrentDiags.push_back(std::make_unique<CXLoadedDiagnostic>()); 310 return std::error_code(); 311 } 312 313 std::error_code DiagLoader::visitEndOfDiagnostic() { 314 auto D = CurrentDiags.pop_back_val(); 315 if (CurrentDiags.empty()) 316 TopDiags->appendDiagnostic(std::move(D)); 317 else 318 CurrentDiags.back()->getChildDiagnostics().appendDiagnostic(std::move(D)); 319 return std::error_code(); 320 } 321 322 std::error_code DiagLoader::visitCategoryRecord(unsigned ID, StringRef Name) { 323 // FIXME: Why do we care about long strings? 324 if (Name.size() > 65536) 325 return reportInvalidFile("Out-of-bounds string in category"); 326 TopDiags->Categories[ID] = TopDiags->copyString(Name); 327 return std::error_code(); 328 } 329 330 std::error_code DiagLoader::visitDiagFlagRecord(unsigned ID, StringRef Name) { 331 // FIXME: Why do we care about long strings? 332 if (Name.size() > 65536) 333 return reportInvalidFile("Out-of-bounds string in warning flag"); 334 TopDiags->WarningFlags[ID] = TopDiags->copyString(Name); 335 return std::error_code(); 336 } 337 338 std::error_code DiagLoader::visitFilenameRecord(unsigned ID, unsigned Size, 339 unsigned Timestamp, 340 StringRef Name) { 341 // FIXME: Why do we care about long strings? 342 if (Name.size() > 65536) 343 return reportInvalidFile("Out-of-bounds string in filename"); 344 TopDiags->FileNames[ID] = TopDiags->copyString(Name); 345 TopDiags->Files[ID] = 346 TopDiags->FakeFiles.getVirtualFile(Name, Size, Timestamp); 347 return std::error_code(); 348 } 349 350 std::error_code 351 DiagLoader::visitSourceRangeRecord(const serialized_diags::Location &Start, 352 const serialized_diags::Location &End) { 353 CXSourceRange SR; 354 if (std::error_code EC = readRange(Start, End, SR)) 355 return EC; 356 CurrentDiags.back()->Ranges.push_back(SR); 357 return std::error_code(); 358 } 359 360 std::error_code 361 DiagLoader::visitFixitRecord(const serialized_diags::Location &Start, 362 const serialized_diags::Location &End, 363 StringRef CodeToInsert) { 364 CXSourceRange SR; 365 if (std::error_code EC = readRange(Start, End, SR)) 366 return EC; 367 // FIXME: Why do we care about long strings? 368 if (CodeToInsert.size() > 65536) 369 return reportInvalidFile("Out-of-bounds string in FIXIT"); 370 CurrentDiags.back()->FixIts.push_back( 371 std::make_pair(SR, TopDiags->copyString(CodeToInsert))); 372 return std::error_code(); 373 } 374 375 std::error_code DiagLoader::visitDiagnosticRecord( 376 unsigned Severity, const serialized_diags::Location &Location, 377 unsigned Category, unsigned Flag, StringRef Message) { 378 CXLoadedDiagnostic &D = *CurrentDiags.back(); 379 D.severity = Severity; 380 if (std::error_code EC = readLocation(Location, D.DiagLoc)) 381 return EC; 382 D.category = Category; 383 D.DiagOption = Flag ? TopDiags->WarningFlags[Flag] : ""; 384 D.CategoryText = Category ? TopDiags->Categories[Category] : ""; 385 D.Spelling = TopDiags->copyString(Message); 386 return std::error_code(); 387 } 388 389 CXDiagnosticSet clang_loadDiagnostics(const char *file, 390 enum CXLoadDiag_Error *error, 391 CXString *errorString) { 392 DiagLoader L(error, errorString); 393 return L.load(file); 394 } 395