1 //===- PartialDiagnostic.h - Diagnostic "closures" --------------*- 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 /// \file 10 /// Implements a partial diagnostic that can be emitted anwyhere 11 /// in a DiagnosticBuilder stream. 12 // 13 //===----------------------------------------------------------------------===// 14 15 #ifndef LLVM_CLANG_BASIC_PARTIALDIAGNOSTIC_H 16 #define LLVM_CLANG_BASIC_PARTIALDIAGNOSTIC_H 17 18 #include "clang/Basic/Diagnostic.h" 19 #include "clang/Basic/PartialDiagnostic.h" 20 #include "clang/Basic/LLVM.h" 21 #include "clang/Basic/SourceLocation.h" 22 #include "llvm/ADT/SmallVector.h" 23 #include "llvm/ADT/StringRef.h" 24 #include <cassert> 25 #include <cstdint> 26 #include <string> 27 #include <type_traits> 28 #include <utility> 29 30 namespace clang { 31 32 class DeclContext; 33 class IdentifierInfo; 34 35 class PartialDiagnostic { 36 public: 37 enum { 38 // The MaxArguments and MaxFixItHints member enum values from 39 // DiagnosticsEngine are private but DiagnosticsEngine declares 40 // PartialDiagnostic a friend. These enum values are redeclared 41 // here so that the nested Storage class below can access them. 42 MaxArguments = DiagnosticsEngine::MaxArguments 43 }; 44 45 struct Storage { 46 enum { 47 /// The maximum number of arguments we can hold. We 48 /// currently only support up to 10 arguments (%0-%9). 49 /// 50 /// A single diagnostic with more than that almost certainly has to 51 /// be simplified anyway. 52 MaxArguments = PartialDiagnostic::MaxArguments 53 }; 54 55 /// The number of entries in Arguments. 56 unsigned char NumDiagArgs = 0; 57 58 /// Specifies for each argument whether it is in DiagArgumentsStr 59 /// or in DiagArguments. 60 unsigned char DiagArgumentsKind[MaxArguments]; 61 62 /// The values for the various substitution positions. 63 /// 64 /// This is used when the argument is not an std::string. The specific value 65 /// is mangled into an intptr_t and the interpretation depends on exactly 66 /// what sort of argument kind it is. 67 intptr_t DiagArgumentsVal[MaxArguments]; 68 69 /// The values for the various substitution positions that have 70 /// string arguments. 71 std::string DiagArgumentsStr[MaxArguments]; 72 73 /// The list of ranges added to this diagnostic. 74 SmallVector<CharSourceRange, 8> DiagRanges; 75 76 /// If valid, provides a hint with some code to insert, remove, or 77 /// modify at a particular position. 78 SmallVector<FixItHint, 6> FixItHints; 79 80 Storage() = default; 81 }; 82 83 /// An allocator for Storage objects, which uses a small cache to 84 /// objects, used to reduce malloc()/free() traffic for partial diagnostics. 85 class StorageAllocator { 86 static const unsigned NumCached = 16; 87 Storage Cached[NumCached]; 88 Storage *FreeList[NumCached]; 89 unsigned NumFreeListEntries; 90 91 public: 92 StorageAllocator(); 93 ~StorageAllocator(); 94 95 /// Allocate new storage. Allocate()96 Storage *Allocate() { 97 if (NumFreeListEntries == 0) 98 return new Storage; 99 100 Storage *Result = FreeList[--NumFreeListEntries]; 101 Result->NumDiagArgs = 0; 102 Result->DiagRanges.clear(); 103 Result->FixItHints.clear(); 104 return Result; 105 } 106 107 /// Free the given storage object. Deallocate(Storage * S)108 void Deallocate(Storage *S) { 109 if (S >= Cached && S <= Cached + NumCached) { 110 FreeList[NumFreeListEntries++] = S; 111 return; 112 } 113 114 delete S; 115 } 116 }; 117 118 private: 119 // NOTE: Sema assumes that PartialDiagnostic is location-invariant 120 // in the sense that its bits can be safely memcpy'ed and destructed 121 // in the new location. 122 123 /// The diagnostic ID. 124 mutable unsigned DiagID = 0; 125 126 /// Storage for args and ranges. 127 mutable Storage *DiagStorage = nullptr; 128 129 /// Allocator used to allocate storage for this diagnostic. 130 StorageAllocator *Allocator = nullptr; 131 132 /// Retrieve storage for this particular diagnostic. getStorage()133 Storage *getStorage() const { 134 if (DiagStorage) 135 return DiagStorage; 136 137 if (Allocator) 138 DiagStorage = Allocator->Allocate(); 139 else { 140 assert(Allocator != reinterpret_cast<StorageAllocator *>(~uintptr_t(0))); 141 DiagStorage = new Storage; 142 } 143 return DiagStorage; 144 } 145 freeStorage()146 void freeStorage() { 147 if (!DiagStorage) 148 return; 149 150 // The hot path for PartialDiagnostic is when we just used it to wrap an ID 151 // (typically so we have the flexibility of passing a more complex 152 // diagnostic into the callee, but that does not commonly occur). 153 // 154 // Split this out into a slow function for silly compilers (*cough*) which 155 // can't do decent partial inlining. 156 freeStorageSlow(); 157 } 158 freeStorageSlow()159 void freeStorageSlow() { 160 if (Allocator) 161 Allocator->Deallocate(DiagStorage); 162 else if (Allocator != reinterpret_cast<StorageAllocator *>(~uintptr_t(0))) 163 delete DiagStorage; 164 DiagStorage = nullptr; 165 } 166 AddSourceRange(const CharSourceRange & R)167 void AddSourceRange(const CharSourceRange &R) const { 168 if (!DiagStorage) 169 DiagStorage = getStorage(); 170 171 DiagStorage->DiagRanges.push_back(R); 172 } 173 AddFixItHint(const FixItHint & Hint)174 void AddFixItHint(const FixItHint &Hint) const { 175 if (Hint.isNull()) 176 return; 177 178 if (!DiagStorage) 179 DiagStorage = getStorage(); 180 181 DiagStorage->FixItHints.push_back(Hint); 182 } 183 184 public: 185 struct NullDiagnostic {}; 186 187 /// Create a null partial diagnostic, which cannot carry a payload, 188 /// and only exists to be swapped with a real partial diagnostic. PartialDiagnostic(NullDiagnostic)189 PartialDiagnostic(NullDiagnostic) {} 190 PartialDiagnostic(unsigned DiagID,StorageAllocator & Allocator)191 PartialDiagnostic(unsigned DiagID, StorageAllocator &Allocator) 192 : DiagID(DiagID), Allocator(&Allocator) {} 193 PartialDiagnostic(const PartialDiagnostic & Other)194 PartialDiagnostic(const PartialDiagnostic &Other) 195 : DiagID(Other.DiagID), Allocator(Other.Allocator) { 196 if (Other.DiagStorage) { 197 DiagStorage = getStorage(); 198 *DiagStorage = *Other.DiagStorage; 199 } 200 } 201 PartialDiagnostic(PartialDiagnostic && Other)202 PartialDiagnostic(PartialDiagnostic &&Other) 203 : DiagID(Other.DiagID), DiagStorage(Other.DiagStorage), 204 Allocator(Other.Allocator) { 205 Other.DiagStorage = nullptr; 206 } 207 PartialDiagnostic(const PartialDiagnostic & Other,Storage * DiagStorage)208 PartialDiagnostic(const PartialDiagnostic &Other, Storage *DiagStorage) 209 : DiagID(Other.DiagID), DiagStorage(DiagStorage), 210 Allocator(reinterpret_cast<StorageAllocator *>(~uintptr_t(0))) { 211 if (Other.DiagStorage) 212 *this->DiagStorage = *Other.DiagStorage; 213 } 214 PartialDiagnostic(const Diagnostic & Other,StorageAllocator & Allocator)215 PartialDiagnostic(const Diagnostic &Other, StorageAllocator &Allocator) 216 : DiagID(Other.getID()), Allocator(&Allocator) { 217 // Copy arguments. 218 for (unsigned I = 0, N = Other.getNumArgs(); I != N; ++I) { 219 if (Other.getArgKind(I) == DiagnosticsEngine::ak_std_string) 220 AddString(Other.getArgStdStr(I)); 221 else 222 AddTaggedVal(Other.getRawArg(I), Other.getArgKind(I)); 223 } 224 225 // Copy source ranges. 226 for (unsigned I = 0, N = Other.getNumRanges(); I != N; ++I) 227 AddSourceRange(Other.getRange(I)); 228 229 // Copy fix-its. 230 for (unsigned I = 0, N = Other.getNumFixItHints(); I != N; ++I) 231 AddFixItHint(Other.getFixItHint(I)); 232 } 233 234 PartialDiagnostic &operator=(const PartialDiagnostic &Other) { 235 DiagID = Other.DiagID; 236 if (Other.DiagStorage) { 237 if (!DiagStorage) 238 DiagStorage = getStorage(); 239 240 *DiagStorage = *Other.DiagStorage; 241 } else { 242 freeStorage(); 243 } 244 245 return *this; 246 } 247 248 PartialDiagnostic &operator=(PartialDiagnostic &&Other) { 249 freeStorage(); 250 251 DiagID = Other.DiagID; 252 DiagStorage = Other.DiagStorage; 253 Allocator = Other.Allocator; 254 255 Other.DiagStorage = nullptr; 256 return *this; 257 } 258 ~PartialDiagnostic()259 ~PartialDiagnostic() { 260 freeStorage(); 261 } 262 swap(PartialDiagnostic & PD)263 void swap(PartialDiagnostic &PD) { 264 std::swap(DiagID, PD.DiagID); 265 std::swap(DiagStorage, PD.DiagStorage); 266 std::swap(Allocator, PD.Allocator); 267 } 268 getDiagID()269 unsigned getDiagID() const { return DiagID; } 270 AddTaggedVal(intptr_t V,DiagnosticsEngine::ArgumentKind Kind)271 void AddTaggedVal(intptr_t V, DiagnosticsEngine::ArgumentKind Kind) const { 272 if (!DiagStorage) 273 DiagStorage = getStorage(); 274 275 assert(DiagStorage->NumDiagArgs < Storage::MaxArguments && 276 "Too many arguments to diagnostic!"); 277 DiagStorage->DiagArgumentsKind[DiagStorage->NumDiagArgs] = Kind; 278 DiagStorage->DiagArgumentsVal[DiagStorage->NumDiagArgs++] = V; 279 } 280 AddString(StringRef V)281 void AddString(StringRef V) const { 282 if (!DiagStorage) 283 DiagStorage = getStorage(); 284 285 assert(DiagStorage->NumDiagArgs < Storage::MaxArguments && 286 "Too many arguments to diagnostic!"); 287 DiagStorage->DiagArgumentsKind[DiagStorage->NumDiagArgs] 288 = DiagnosticsEngine::ak_std_string; 289 DiagStorage->DiagArgumentsStr[DiagStorage->NumDiagArgs++] = V; 290 } 291 Emit(const DiagnosticBuilder & DB)292 void Emit(const DiagnosticBuilder &DB) const { 293 if (!DiagStorage) 294 return; 295 296 // Add all arguments. 297 for (unsigned i = 0, e = DiagStorage->NumDiagArgs; i != e; ++i) { 298 if ((DiagnosticsEngine::ArgumentKind)DiagStorage->DiagArgumentsKind[i] 299 == DiagnosticsEngine::ak_std_string) 300 DB.AddString(DiagStorage->DiagArgumentsStr[i]); 301 else 302 DB.AddTaggedVal(DiagStorage->DiagArgumentsVal[i], 303 (DiagnosticsEngine::ArgumentKind)DiagStorage->DiagArgumentsKind[i]); 304 } 305 306 // Add all ranges. 307 for (const CharSourceRange &Range : DiagStorage->DiagRanges) 308 DB.AddSourceRange(Range); 309 310 // Add all fix-its. 311 for (const FixItHint &Fix : DiagStorage->FixItHints) 312 DB.AddFixItHint(Fix); 313 } 314 EmitToString(DiagnosticsEngine & Diags,SmallVectorImpl<char> & Buf)315 void EmitToString(DiagnosticsEngine &Diags, 316 SmallVectorImpl<char> &Buf) const { 317 // FIXME: It should be possible to render a diagnostic to a string without 318 // messing with the state of the diagnostics engine. 319 DiagnosticBuilder DB(Diags.Report(getDiagID())); 320 Emit(DB); 321 DB.FlushCounts(); 322 Diagnostic(&Diags).FormatDiagnostic(Buf); 323 DB.Clear(); 324 Diags.Clear(); 325 } 326 327 /// Clear out this partial diagnostic, giving it a new diagnostic ID 328 /// and removing all of its arguments, ranges, and fix-it hints. 329 void Reset(unsigned DiagID = 0) { 330 this->DiagID = DiagID; 331 freeStorage(); 332 } 333 hasStorage()334 bool hasStorage() const { return DiagStorage != nullptr; } 335 336 /// Retrieve the string argument at the given index. getStringArg(unsigned I)337 StringRef getStringArg(unsigned I) { 338 assert(DiagStorage && "No diagnostic storage?"); 339 assert(I < DiagStorage->NumDiagArgs && "Not enough diagnostic args"); 340 assert(DiagStorage->DiagArgumentsKind[I] 341 == DiagnosticsEngine::ak_std_string && "Not a string arg"); 342 return DiagStorage->DiagArgumentsStr[I]; 343 } 344 345 friend const PartialDiagnostic &operator<<(const PartialDiagnostic &PD, 346 unsigned I) { 347 PD.AddTaggedVal(I, DiagnosticsEngine::ak_uint); 348 return PD; 349 } 350 351 friend const PartialDiagnostic &operator<<(const PartialDiagnostic &PD, 352 int I) { 353 PD.AddTaggedVal(I, DiagnosticsEngine::ak_sint); 354 return PD; 355 } 356 357 friend inline const PartialDiagnostic &operator<<(const PartialDiagnostic &PD, 358 const char *S) { 359 PD.AddTaggedVal(reinterpret_cast<intptr_t>(S), 360 DiagnosticsEngine::ak_c_string); 361 return PD; 362 } 363 364 friend inline const PartialDiagnostic &operator<<(const PartialDiagnostic &PD, 365 StringRef S) { 366 367 PD.AddString(S); 368 return PD; 369 } 370 371 friend inline const PartialDiagnostic &operator<<(const PartialDiagnostic &PD, 372 const IdentifierInfo *II) { 373 PD.AddTaggedVal(reinterpret_cast<intptr_t>(II), 374 DiagnosticsEngine::ak_identifierinfo); 375 return PD; 376 } 377 378 // Adds a DeclContext to the diagnostic. The enable_if template magic is here 379 // so that we only match those arguments that are (statically) DeclContexts; 380 // other arguments that derive from DeclContext (e.g., RecordDecls) will not 381 // match. 382 template<typename T> 383 friend inline 384 typename std::enable_if<std::is_same<T, DeclContext>::value, 385 const PartialDiagnostic &>::type 386 operator<<(const PartialDiagnostic &PD, T *DC) { 387 PD.AddTaggedVal(reinterpret_cast<intptr_t>(DC), 388 DiagnosticsEngine::ak_declcontext); 389 return PD; 390 } 391 392 friend inline const PartialDiagnostic &operator<<(const PartialDiagnostic &PD, 393 SourceRange R) { 394 PD.AddSourceRange(CharSourceRange::getTokenRange(R)); 395 return PD; 396 } 397 398 friend inline const PartialDiagnostic &operator<<(const PartialDiagnostic &PD, 399 const CharSourceRange &R) { 400 PD.AddSourceRange(R); 401 return PD; 402 } 403 404 friend const PartialDiagnostic &operator<<(const PartialDiagnostic &PD, 405 const FixItHint &Hint) { 406 PD.AddFixItHint(Hint); 407 return PD; 408 } 409 }; 410 411 inline const DiagnosticBuilder &operator<<(const DiagnosticBuilder &DB, 412 const PartialDiagnostic &PD) { 413 PD.Emit(DB); 414 return DB; 415 } 416 417 /// A partial diagnostic along with the source location where this 418 /// diagnostic occurs. 419 using PartialDiagnosticAt = std::pair<SourceLocation, PartialDiagnostic>; 420 421 } // namespace clang 422 423 #endif // LLVM_CLANG_BASIC_PARTIALDIAGNOSTIC_H 424