1 | //=- ClangDiagnosticsEmitter.cpp - Generate Clang diagnostics tables -*- 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 | // These tablegen backends emit Clang diagnostics tables. |
10 | // |
11 | //===----------------------------------------------------------------------===// |
12 | |
13 | #include "TableGenBackends.h" |
14 | #include "llvm/ADT/DenseSet.h" |
15 | #include "llvm/ADT/PointerUnion.h" |
16 | #include "llvm/ADT/STLExtras.h" |
17 | #include "llvm/ADT/SmallPtrSet.h" |
18 | #include "llvm/ADT/SmallString.h" |
19 | #include "llvm/ADT/SmallVector.h" |
20 | #include "llvm/ADT/StringMap.h" |
21 | #include "llvm/ADT/StringSwitch.h" |
22 | #include "llvm/ADT/Twine.h" |
23 | #include "llvm/Support/Casting.h" |
24 | #include "llvm/TableGen/Error.h" |
25 | #include "llvm/TableGen/Record.h" |
26 | #include "llvm/TableGen/StringToOffsetTable.h" |
27 | #include "llvm/TableGen/TableGenBackend.h" |
28 | #include <algorithm> |
29 | #include <cctype> |
30 | #include <functional> |
31 | #include <map> |
32 | #include <optional> |
33 | #include <set> |
34 | using namespace llvm; |
35 | |
36 | //===----------------------------------------------------------------------===// |
37 | // Diagnostic category computation code. |
38 | //===----------------------------------------------------------------------===// |
39 | |
40 | namespace { |
41 | class DiagGroupParentMap { |
42 | RecordKeeper &Records; |
43 | std::map<const Record*, std::vector<Record*> > Mapping; |
44 | public: |
45 | DiagGroupParentMap(RecordKeeper &records) : Records(records) { |
46 | std::vector<Record*> DiagGroups |
47 | = Records.getAllDerivedDefinitions(ClassName: "DiagGroup" ); |
48 | for (unsigned i = 0, e = DiagGroups.size(); i != e; ++i) { |
49 | std::vector<Record*> SubGroups = |
50 | DiagGroups[i]->getValueAsListOfDefs(FieldName: "SubGroups" ); |
51 | for (unsigned j = 0, e = SubGroups.size(); j != e; ++j) |
52 | Mapping[SubGroups[j]].push_back(x: DiagGroups[i]); |
53 | } |
54 | } |
55 | |
56 | const std::vector<Record*> &getParents(const Record *Group) { |
57 | return Mapping[Group]; |
58 | } |
59 | }; |
60 | } // end anonymous namespace. |
61 | |
62 | static std::string |
63 | getCategoryFromDiagGroup(const Record *Group, |
64 | DiagGroupParentMap &DiagGroupParents) { |
65 | // If the DiagGroup has a category, return it. |
66 | std::string CatName = std::string(Group->getValueAsString(FieldName: "CategoryName" )); |
67 | if (!CatName.empty()) return CatName; |
68 | |
69 | // The diag group may the subgroup of one or more other diagnostic groups, |
70 | // check these for a category as well. |
71 | const std::vector<Record*> &Parents = DiagGroupParents.getParents(Group); |
72 | for (unsigned i = 0, e = Parents.size(); i != e; ++i) { |
73 | CatName = getCategoryFromDiagGroup(Group: Parents[i], DiagGroupParents); |
74 | if (!CatName.empty()) return CatName; |
75 | } |
76 | return "" ; |
77 | } |
78 | |
79 | /// getDiagnosticCategory - Return the category that the specified diagnostic |
80 | /// lives in. |
81 | static std::string getDiagnosticCategory(const Record *R, |
82 | DiagGroupParentMap &DiagGroupParents) { |
83 | // If the diagnostic is in a group, and that group has a category, use it. |
84 | if (DefInit *Group = dyn_cast<DefInit>(Val: R->getValueInit(FieldName: "Group" ))) { |
85 | // Check the diagnostic's diag group for a category. |
86 | std::string CatName = getCategoryFromDiagGroup(Group: Group->getDef(), |
87 | DiagGroupParents); |
88 | if (!CatName.empty()) return CatName; |
89 | } |
90 | |
91 | // If the diagnostic itself has a category, get it. |
92 | return std::string(R->getValueAsString(FieldName: "CategoryName" )); |
93 | } |
94 | |
95 | namespace { |
96 | class DiagCategoryIDMap { |
97 | RecordKeeper &Records; |
98 | StringMap<unsigned> CategoryIDs; |
99 | std::vector<std::string> CategoryStrings; |
100 | public: |
101 | DiagCategoryIDMap(RecordKeeper &records) : Records(records) { |
102 | DiagGroupParentMap ParentInfo(Records); |
103 | |
104 | // The zero'th category is "". |
105 | CategoryStrings.push_back(x: "" ); |
106 | CategoryIDs["" ] = 0; |
107 | |
108 | std::vector<Record*> Diags = |
109 | Records.getAllDerivedDefinitions(ClassName: "Diagnostic" ); |
110 | for (unsigned i = 0, e = Diags.size(); i != e; ++i) { |
111 | std::string Category = getDiagnosticCategory(R: Diags[i], DiagGroupParents&: ParentInfo); |
112 | if (Category.empty()) continue; // Skip diags with no category. |
113 | |
114 | unsigned &ID = CategoryIDs[Category]; |
115 | if (ID != 0) continue; // Already seen. |
116 | |
117 | ID = CategoryStrings.size(); |
118 | CategoryStrings.push_back(x: Category); |
119 | } |
120 | } |
121 | |
122 | unsigned getID(StringRef CategoryString) { |
123 | return CategoryIDs[CategoryString]; |
124 | } |
125 | |
126 | typedef std::vector<std::string>::const_iterator const_iterator; |
127 | const_iterator begin() const { return CategoryStrings.begin(); } |
128 | const_iterator end() const { return CategoryStrings.end(); } |
129 | }; |
130 | |
131 | struct GroupInfo { |
132 | llvm::StringRef GroupName; |
133 | std::vector<const Record*> DiagsInGroup; |
134 | std::vector<std::string> SubGroups; |
135 | unsigned IDNo = 0; |
136 | |
137 | llvm::SmallVector<const Record *, 1> Defs; |
138 | |
139 | GroupInfo() = default; |
140 | }; |
141 | } // end anonymous namespace. |
142 | |
143 | static bool beforeThanCompare(const Record *LHS, const Record *RHS) { |
144 | assert(!LHS->getLoc().empty() && !RHS->getLoc().empty()); |
145 | return |
146 | LHS->getLoc().front().getPointer() < RHS->getLoc().front().getPointer(); |
147 | } |
148 | |
149 | static bool diagGroupBeforeByName(const Record *LHS, const Record *RHS) { |
150 | return LHS->getValueAsString(FieldName: "GroupName" ) < |
151 | RHS->getValueAsString(FieldName: "GroupName" ); |
152 | } |
153 | |
154 | /// Invert the 1-[0/1] mapping of diags to group into a one to many |
155 | /// mapping of groups to diags in the group. |
156 | static void groupDiagnostics(const std::vector<Record*> &Diags, |
157 | const std::vector<Record*> &DiagGroups, |
158 | std::map<std::string, GroupInfo> &DiagsInGroup) { |
159 | |
160 | for (unsigned i = 0, e = Diags.size(); i != e; ++i) { |
161 | const Record *R = Diags[i]; |
162 | DefInit *DI = dyn_cast<DefInit>(Val: R->getValueInit(FieldName: "Group" )); |
163 | if (!DI) |
164 | continue; |
165 | assert(R->getValueAsDef("Class" )->getName() != "CLASS_NOTE" && |
166 | "Note can't be in a DiagGroup" ); |
167 | std::string GroupName = |
168 | std::string(DI->getDef()->getValueAsString(FieldName: "GroupName" )); |
169 | DiagsInGroup[GroupName].DiagsInGroup.push_back(x: R); |
170 | } |
171 | |
172 | // Add all DiagGroup's to the DiagsInGroup list to make sure we pick up empty |
173 | // groups (these are warnings that GCC supports that clang never produces). |
174 | for (unsigned i = 0, e = DiagGroups.size(); i != e; ++i) { |
175 | Record *Group = DiagGroups[i]; |
176 | GroupInfo &GI = |
177 | DiagsInGroup[std::string(Group->getValueAsString(FieldName: "GroupName" ))]; |
178 | GI.GroupName = Group->getName(); |
179 | GI.Defs.push_back(Elt: Group); |
180 | |
181 | std::vector<Record*> SubGroups = Group->getValueAsListOfDefs(FieldName: "SubGroups" ); |
182 | for (unsigned j = 0, e = SubGroups.size(); j != e; ++j) |
183 | GI.SubGroups.push_back( |
184 | x: std::string(SubGroups[j]->getValueAsString(FieldName: "GroupName" ))); |
185 | } |
186 | |
187 | // Assign unique ID numbers to the groups. |
188 | unsigned IDNo = 0; |
189 | for (std::map<std::string, GroupInfo>::iterator |
190 | I = DiagsInGroup.begin(), E = DiagsInGroup.end(); I != E; ++I, ++IDNo) |
191 | I->second.IDNo = IDNo; |
192 | |
193 | // Warn if the same group is defined more than once (including implicitly). |
194 | for (auto &Group : DiagsInGroup) { |
195 | if (Group.second.Defs.size() == 1 && |
196 | (!Group.second.Defs.front()->isAnonymous() || |
197 | Group.second.DiagsInGroup.size() <= 1)) |
198 | continue; |
199 | |
200 | bool First = true; |
201 | for (const Record *Def : Group.second.Defs) { |
202 | // Skip implicit definitions from diagnostics; we'll report those |
203 | // separately below. |
204 | bool IsImplicit = false; |
205 | for (const Record *Diag : Group.second.DiagsInGroup) { |
206 | if (cast<DefInit>(Val: Diag->getValueInit(FieldName: "Group" ))->getDef() == Def) { |
207 | IsImplicit = true; |
208 | break; |
209 | } |
210 | } |
211 | if (IsImplicit) |
212 | continue; |
213 | |
214 | llvm::SMLoc Loc = Def->getLoc().front(); |
215 | if (First) { |
216 | SrcMgr.PrintMessage(Loc, Kind: SourceMgr::DK_Error, |
217 | Msg: Twine("group '" ) + Group.first + |
218 | "' is defined more than once" ); |
219 | First = false; |
220 | } else { |
221 | SrcMgr.PrintMessage(Loc, Kind: SourceMgr::DK_Note, Msg: "also defined here" ); |
222 | } |
223 | } |
224 | |
225 | for (const Record *Diag : Group.second.DiagsInGroup) { |
226 | if (!cast<DefInit>(Val: Diag->getValueInit(FieldName: "Group" ))->getDef()->isAnonymous()) |
227 | continue; |
228 | |
229 | llvm::SMLoc Loc = Diag->getLoc().front(); |
230 | if (First) { |
231 | SrcMgr.PrintMessage(Loc, Kind: SourceMgr::DK_Error, |
232 | Msg: Twine("group '" ) + Group.first + |
233 | "' is implicitly defined more than once" ); |
234 | First = false; |
235 | } else { |
236 | SrcMgr.PrintMessage(Loc, Kind: SourceMgr::DK_Note, |
237 | Msg: "also implicitly defined here" ); |
238 | } |
239 | } |
240 | } |
241 | } |
242 | |
243 | //===----------------------------------------------------------------------===// |
244 | // Infer members of -Wpedantic. |
245 | //===----------------------------------------------------------------------===// |
246 | |
247 | typedef std::vector<const Record *> RecordVec; |
248 | typedef llvm::DenseSet<const Record *> RecordSet; |
249 | typedef llvm::PointerUnion<RecordVec*, RecordSet*> VecOrSet; |
250 | |
251 | namespace { |
252 | class InferPedantic { |
253 | typedef llvm::DenseMap<const Record *, |
254 | std::pair<unsigned, std::optional<unsigned>>> |
255 | GMap; |
256 | |
257 | DiagGroupParentMap &DiagGroupParents; |
258 | const std::vector<Record*> &Diags; |
259 | const std::vector<Record*> DiagGroups; |
260 | std::map<std::string, GroupInfo> &DiagsInGroup; |
261 | llvm::DenseSet<const Record*> DiagsSet; |
262 | GMap GroupCount; |
263 | public: |
264 | InferPedantic(DiagGroupParentMap &DiagGroupParents, |
265 | const std::vector<Record*> &Diags, |
266 | const std::vector<Record*> &DiagGroups, |
267 | std::map<std::string, GroupInfo> &DiagsInGroup) |
268 | : DiagGroupParents(DiagGroupParents), |
269 | Diags(Diags), |
270 | DiagGroups(DiagGroups), |
271 | DiagsInGroup(DiagsInGroup) {} |
272 | |
273 | /// Compute the set of diagnostics and groups that are immediately |
274 | /// in -Wpedantic. |
275 | void compute(VecOrSet DiagsInPedantic, |
276 | VecOrSet GroupsInPedantic); |
277 | |
278 | private: |
279 | /// Determine whether a group is a subgroup of another group. |
280 | bool isSubGroupOfGroup(const Record *Group, |
281 | llvm::StringRef RootGroupName); |
282 | |
283 | /// Determine if the diagnostic is an extension. |
284 | bool isExtension(const Record *Diag); |
285 | |
286 | /// Determine if the diagnostic is off by default. |
287 | bool isOffByDefault(const Record *Diag); |
288 | |
289 | /// Increment the count for a group, and transitively marked |
290 | /// parent groups when appropriate. |
291 | void markGroup(const Record *Group); |
292 | |
293 | /// Return true if the diagnostic is in a pedantic group. |
294 | bool groupInPedantic(const Record *Group, bool increment = false); |
295 | }; |
296 | } // end anonymous namespace |
297 | |
298 | bool InferPedantic::isSubGroupOfGroup(const Record *Group, |
299 | llvm::StringRef GName) { |
300 | const std::string &GroupName = |
301 | std::string(Group->getValueAsString(FieldName: "GroupName" )); |
302 | if (GName == GroupName) |
303 | return true; |
304 | |
305 | const std::vector<Record*> &Parents = DiagGroupParents.getParents(Group); |
306 | for (unsigned i = 0, e = Parents.size(); i != e; ++i) |
307 | if (isSubGroupOfGroup(Group: Parents[i], GName)) |
308 | return true; |
309 | |
310 | return false; |
311 | } |
312 | |
313 | /// Determine if the diagnostic is an extension. |
314 | bool InferPedantic::isExtension(const Record *Diag) { |
315 | const std::string &ClsName = |
316 | std::string(Diag->getValueAsDef(FieldName: "Class" )->getName()); |
317 | return ClsName == "CLASS_EXTENSION" ; |
318 | } |
319 | |
320 | bool InferPedantic::isOffByDefault(const Record *Diag) { |
321 | const std::string &DefSeverity = std::string( |
322 | Diag->getValueAsDef(FieldName: "DefaultSeverity" )->getValueAsString(FieldName: "Name" )); |
323 | return DefSeverity == "Ignored" ; |
324 | } |
325 | |
326 | bool InferPedantic::groupInPedantic(const Record *Group, bool increment) { |
327 | GMap::mapped_type &V = GroupCount[Group]; |
328 | // Lazily compute the threshold value for the group count. |
329 | if (!V.second) { |
330 | const GroupInfo &GI = |
331 | DiagsInGroup[std::string(Group->getValueAsString(FieldName: "GroupName" ))]; |
332 | V.second = GI.SubGroups.size() + GI.DiagsInGroup.size(); |
333 | } |
334 | |
335 | if (increment) |
336 | ++V.first; |
337 | |
338 | // Consider a group in -Wpendatic IFF if has at least one diagnostic |
339 | // or subgroup AND all of those diagnostics and subgroups are covered |
340 | // by -Wpedantic via our computation. |
341 | return V.first != 0 && V.first == *V.second; |
342 | } |
343 | |
344 | void InferPedantic::markGroup(const Record *Group) { |
345 | // If all the diagnostics and subgroups have been marked as being |
346 | // covered by -Wpedantic, increment the count of parent groups. Once the |
347 | // group's count is equal to the number of subgroups and diagnostics in |
348 | // that group, we can safely add this group to -Wpedantic. |
349 | if (groupInPedantic(Group, /* increment */ true)) { |
350 | const std::vector<Record*> &Parents = DiagGroupParents.getParents(Group); |
351 | for (unsigned i = 0, e = Parents.size(); i != e; ++i) |
352 | markGroup(Group: Parents[i]); |
353 | } |
354 | } |
355 | |
356 | void InferPedantic::compute(VecOrSet DiagsInPedantic, |
357 | VecOrSet GroupsInPedantic) { |
358 | // All extensions that are not on by default are implicitly in the |
359 | // "pedantic" group. For those that aren't explicitly included in -Wpedantic, |
360 | // mark them for consideration to be included in -Wpedantic directly. |
361 | for (unsigned i = 0, e = Diags.size(); i != e; ++i) { |
362 | Record *R = Diags[i]; |
363 | if (isExtension(Diag: R) && isOffByDefault(Diag: R)) { |
364 | DiagsSet.insert(V: R); |
365 | if (DefInit *Group = dyn_cast<DefInit>(Val: R->getValueInit(FieldName: "Group" ))) { |
366 | const Record *GroupRec = Group->getDef(); |
367 | if (!isSubGroupOfGroup(Group: GroupRec, GName: "pedantic" )) { |
368 | markGroup(Group: GroupRec); |
369 | } |
370 | } |
371 | } |
372 | } |
373 | |
374 | // Compute the set of diagnostics that are directly in -Wpedantic. We |
375 | // march through Diags a second time to ensure the results are emitted |
376 | // in deterministic order. |
377 | for (unsigned i = 0, e = Diags.size(); i != e; ++i) { |
378 | Record *R = Diags[i]; |
379 | if (!DiagsSet.count(V: R)) |
380 | continue; |
381 | // Check if the group is implicitly in -Wpedantic. If so, |
382 | // the diagnostic should not be directly included in the -Wpedantic |
383 | // diagnostic group. |
384 | if (DefInit *Group = dyn_cast<DefInit>(Val: R->getValueInit(FieldName: "Group" ))) |
385 | if (groupInPedantic(Group: Group->getDef())) |
386 | continue; |
387 | |
388 | // The diagnostic is not included in a group that is (transitively) in |
389 | // -Wpedantic. Include it in -Wpedantic directly. |
390 | if (RecordVec *V = DiagsInPedantic.dyn_cast<RecordVec*>()) |
391 | V->push_back(x: R); |
392 | else { |
393 | DiagsInPedantic.get<RecordSet*>()->insert(V: R); |
394 | } |
395 | } |
396 | |
397 | if (!GroupsInPedantic) |
398 | return; |
399 | |
400 | // Compute the set of groups that are directly in -Wpedantic. We |
401 | // march through the groups to ensure the results are emitted |
402 | /// in a deterministc order. |
403 | for (unsigned i = 0, ei = DiagGroups.size(); i != ei; ++i) { |
404 | Record *Group = DiagGroups[i]; |
405 | if (!groupInPedantic(Group)) |
406 | continue; |
407 | |
408 | const std::vector<Record*> &Parents = DiagGroupParents.getParents(Group); |
409 | bool AllParentsInPedantic = |
410 | llvm::all_of(Range: Parents, P: [&](Record *R) { return groupInPedantic(Group: R); }); |
411 | // If all the parents are in -Wpedantic, this means that this diagnostic |
412 | // group will be indirectly included by -Wpedantic already. In that |
413 | // case, do not add it directly to -Wpedantic. If the group has no |
414 | // parents, obviously it should go into -Wpedantic. |
415 | if (Parents.size() > 0 && AllParentsInPedantic) |
416 | continue; |
417 | |
418 | if (RecordVec *V = GroupsInPedantic.dyn_cast<RecordVec*>()) |
419 | V->push_back(x: Group); |
420 | else { |
421 | GroupsInPedantic.get<RecordSet*>()->insert(V: Group); |
422 | } |
423 | } |
424 | } |
425 | |
426 | namespace { |
427 | enum PieceKind { |
428 | MultiPieceClass, |
429 | TextPieceClass, |
430 | PlaceholderPieceClass, |
431 | SelectPieceClass, |
432 | PluralPieceClass, |
433 | DiffPieceClass, |
434 | SubstitutionPieceClass, |
435 | }; |
436 | |
437 | enum ModifierType { |
438 | MT_Unknown, |
439 | MT_Placeholder, |
440 | MT_Select, |
441 | MT_Sub, |
442 | MT_Plural, |
443 | MT_Diff, |
444 | MT_Ordinal, |
445 | MT_S, |
446 | MT_Q, |
447 | MT_ObjCClass, |
448 | MT_ObjCInstance, |
449 | }; |
450 | |
451 | static StringRef getModifierName(ModifierType MT) { |
452 | switch (MT) { |
453 | case MT_Select: |
454 | return "select" ; |
455 | case MT_Sub: |
456 | return "sub" ; |
457 | case MT_Diff: |
458 | return "diff" ; |
459 | case MT_Plural: |
460 | return "plural" ; |
461 | case MT_Ordinal: |
462 | return "ordinal" ; |
463 | case MT_S: |
464 | return "s" ; |
465 | case MT_Q: |
466 | return "q" ; |
467 | case MT_Placeholder: |
468 | return "" ; |
469 | case MT_ObjCClass: |
470 | return "objcclass" ; |
471 | case MT_ObjCInstance: |
472 | return "objcinstance" ; |
473 | case MT_Unknown: |
474 | llvm_unreachable("invalid modifier type" ); |
475 | } |
476 | // Unhandled case |
477 | llvm_unreachable("invalid modifier type" ); |
478 | } |
479 | |
480 | struct Piece { |
481 | // This type and its derived classes are move-only. |
482 | Piece(PieceKind Kind) : ClassKind(Kind) {} |
483 | Piece(Piece const &O) = delete; |
484 | Piece &operator=(Piece const &) = delete; |
485 | virtual ~Piece() {} |
486 | |
487 | PieceKind getPieceClass() const { return ClassKind; } |
488 | static bool classof(const Piece *) { return true; } |
489 | |
490 | private: |
491 | PieceKind ClassKind; |
492 | }; |
493 | |
494 | struct MultiPiece : Piece { |
495 | MultiPiece() : Piece(MultiPieceClass) {} |
496 | MultiPiece(std::vector<Piece *> Pieces) |
497 | : Piece(MultiPieceClass), Pieces(std::move(Pieces)) {} |
498 | |
499 | std::vector<Piece *> Pieces; |
500 | |
501 | static bool classof(const Piece *P) { |
502 | return P->getPieceClass() == MultiPieceClass; |
503 | } |
504 | }; |
505 | |
506 | struct TextPiece : Piece { |
507 | StringRef Role; |
508 | std::string Text; |
509 | TextPiece(StringRef Text, StringRef Role = "" ) |
510 | : Piece(TextPieceClass), Role(Role), Text(Text.str()) {} |
511 | |
512 | static bool classof(const Piece *P) { |
513 | return P->getPieceClass() == TextPieceClass; |
514 | } |
515 | }; |
516 | |
517 | struct PlaceholderPiece : Piece { |
518 | ModifierType Kind; |
519 | int Index; |
520 | PlaceholderPiece(ModifierType Kind, int Index) |
521 | : Piece(PlaceholderPieceClass), Kind(Kind), Index(Index) {} |
522 | |
523 | static bool classof(const Piece *P) { |
524 | return P->getPieceClass() == PlaceholderPieceClass; |
525 | } |
526 | }; |
527 | |
528 | struct SelectPiece : Piece { |
529 | protected: |
530 | SelectPiece(PieceKind Kind, ModifierType ModKind) |
531 | : Piece(Kind), ModKind(ModKind) {} |
532 | |
533 | public: |
534 | SelectPiece(ModifierType ModKind) : SelectPiece(SelectPieceClass, ModKind) {} |
535 | |
536 | ModifierType ModKind; |
537 | std::vector<Piece *> Options; |
538 | int Index = 0; |
539 | |
540 | static bool classof(const Piece *P) { |
541 | return P->getPieceClass() == SelectPieceClass || |
542 | P->getPieceClass() == PluralPieceClass; |
543 | } |
544 | }; |
545 | |
546 | struct PluralPiece : SelectPiece { |
547 | PluralPiece() : SelectPiece(PluralPieceClass, MT_Plural) {} |
548 | |
549 | std::vector<Piece *> OptionPrefixes; |
550 | int Index = 0; |
551 | |
552 | static bool classof(const Piece *P) { |
553 | return P->getPieceClass() == PluralPieceClass; |
554 | } |
555 | }; |
556 | |
557 | struct DiffPiece : Piece { |
558 | DiffPiece() : Piece(DiffPieceClass) {} |
559 | |
560 | Piece *Parts[4] = {}; |
561 | int Indexes[2] = {}; |
562 | |
563 | static bool classof(const Piece *P) { |
564 | return P->getPieceClass() == DiffPieceClass; |
565 | } |
566 | }; |
567 | |
568 | struct SubstitutionPiece : Piece { |
569 | SubstitutionPiece() : Piece(SubstitutionPieceClass) {} |
570 | |
571 | std::string Name; |
572 | std::vector<int> Modifiers; |
573 | |
574 | static bool classof(const Piece *P) { |
575 | return P->getPieceClass() == SubstitutionPieceClass; |
576 | } |
577 | }; |
578 | |
579 | /// Diagnostic text, parsed into pieces. |
580 | |
581 | |
582 | struct DiagnosticTextBuilder { |
583 | DiagnosticTextBuilder(DiagnosticTextBuilder const &) = delete; |
584 | DiagnosticTextBuilder &operator=(DiagnosticTextBuilder const &) = delete; |
585 | |
586 | DiagnosticTextBuilder(RecordKeeper &Records) { |
587 | // Build up the list of substitution records. |
588 | for (auto *S : Records.getAllDerivedDefinitions(ClassName: "TextSubstitution" )) { |
589 | EvaluatingRecordGuard Guard(&EvaluatingRecord, S); |
590 | Substitutions.try_emplace( |
591 | Key: S->getName(), Args: DiagText(*this, S->getValueAsString(FieldName: "Substitution" ))); |
592 | } |
593 | |
594 | // Check that no diagnostic definitions have the same name as a |
595 | // substitution. |
596 | for (Record *Diag : Records.getAllDerivedDefinitions(ClassName: "Diagnostic" )) { |
597 | StringRef Name = Diag->getName(); |
598 | if (Substitutions.count(Key: Name)) |
599 | llvm::PrintFatalError( |
600 | ErrorLoc: Diag->getLoc(), |
601 | Msg: "Diagnostic '" + Name + |
602 | "' has same name as TextSubstitution definition" ); |
603 | } |
604 | } |
605 | |
606 | std::vector<std::string> buildForDocumentation(StringRef Role, |
607 | const Record *R); |
608 | std::string buildForDefinition(const Record *R); |
609 | |
610 | Piece *getSubstitution(SubstitutionPiece *S) const { |
611 | auto It = Substitutions.find(Key: S->Name); |
612 | if (It == Substitutions.end()) |
613 | PrintFatalError(Msg: "Failed to find substitution with name: " + S->Name); |
614 | return It->second.Root; |
615 | } |
616 | |
617 | [[noreturn]] void PrintFatalError(llvm::Twine const &Msg) const { |
618 | assert(EvaluatingRecord && "not evaluating a record?" ); |
619 | llvm::PrintFatalError(ErrorLoc: EvaluatingRecord->getLoc(), Msg); |
620 | } |
621 | |
622 | private: |
623 | struct DiagText { |
624 | DiagnosticTextBuilder &Builder; |
625 | std::vector<Piece *> AllocatedPieces; |
626 | Piece *Root = nullptr; |
627 | |
628 | template <class T, class... Args> T *New(Args &&... args) { |
629 | static_assert(std::is_base_of<Piece, T>::value, "must be piece" ); |
630 | T *Mem = new T(std::forward<Args>(args)...); |
631 | AllocatedPieces.push_back(Mem); |
632 | return Mem; |
633 | } |
634 | |
635 | DiagText(DiagnosticTextBuilder &Builder, StringRef Text) |
636 | : Builder(Builder), Root(parseDiagText(Text, Stop: StopAt::End)) {} |
637 | |
638 | enum class StopAt { |
639 | // Parse until the end of the string. |
640 | End, |
641 | // Additionally stop if we hit a non-nested '|' or '}'. |
642 | PipeOrCloseBrace, |
643 | // Additionally stop if we hit a non-nested '$'. |
644 | Dollar, |
645 | }; |
646 | |
647 | Piece *parseDiagText(StringRef &Text, StopAt Stop); |
648 | int parseModifier(StringRef &) const; |
649 | |
650 | public: |
651 | DiagText(DiagText &&O) noexcept |
652 | : Builder(O.Builder), AllocatedPieces(std::move(O.AllocatedPieces)), |
653 | Root(O.Root) { |
654 | O.Root = nullptr; |
655 | } |
656 | // The move assignment operator is defined as deleted pending further |
657 | // motivation. |
658 | DiagText &operator=(DiagText &&) = delete; |
659 | |
660 | // The copy constrcutor and copy assignment operator is defined as deleted |
661 | // pending further motivation. |
662 | DiagText(const DiagText &) = delete; |
663 | DiagText &operator=(const DiagText &) = delete; |
664 | |
665 | ~DiagText() { |
666 | for (Piece *P : AllocatedPieces) |
667 | delete P; |
668 | } |
669 | }; |
670 | |
671 | private: |
672 | const Record *EvaluatingRecord = nullptr; |
673 | struct EvaluatingRecordGuard { |
674 | EvaluatingRecordGuard(const Record **Dest, const Record *New) |
675 | : Dest(Dest), Old(*Dest) { |
676 | *Dest = New; |
677 | } |
678 | ~EvaluatingRecordGuard() { *Dest = Old; } |
679 | const Record **Dest; |
680 | const Record *Old; |
681 | }; |
682 | |
683 | StringMap<DiagText> Substitutions; |
684 | }; |
685 | |
686 | template <class Derived> struct DiagTextVisitor { |
687 | using ModifierMappingsType = std::optional<std::vector<int>>; |
688 | |
689 | private: |
690 | Derived &getDerived() { return static_cast<Derived &>(*this); } |
691 | |
692 | public: |
693 | std::vector<int> |
694 | getSubstitutionMappings(SubstitutionPiece *P, |
695 | const ModifierMappingsType &Mappings) const { |
696 | std::vector<int> NewMappings; |
697 | for (int Idx : P->Modifiers) |
698 | NewMappings.push_back(mapIndex(Idx, Mappings)); |
699 | return NewMappings; |
700 | } |
701 | |
702 | struct SubstitutionContext { |
703 | SubstitutionContext(DiagTextVisitor &Visitor, SubstitutionPiece *P) |
704 | : Visitor(Visitor) { |
705 | Substitution = Visitor.Builder.getSubstitution(P); |
706 | OldMappings = std::move(Visitor.ModifierMappings); |
707 | std::vector<int> NewMappings = |
708 | Visitor.getSubstitutionMappings(P, OldMappings); |
709 | Visitor.ModifierMappings = std::move(NewMappings); |
710 | } |
711 | |
712 | ~SubstitutionContext() { |
713 | Visitor.ModifierMappings = std::move(OldMappings); |
714 | } |
715 | |
716 | private: |
717 | DiagTextVisitor &Visitor; |
718 | std::optional<std::vector<int>> OldMappings; |
719 | |
720 | public: |
721 | Piece *Substitution; |
722 | }; |
723 | |
724 | public: |
725 | DiagTextVisitor(DiagnosticTextBuilder &Builder) : Builder(Builder) {} |
726 | |
727 | void Visit(Piece *P) { |
728 | switch (P->getPieceClass()) { |
729 | #define CASE(T) \ |
730 | case T##PieceClass: \ |
731 | return getDerived().Visit##T(static_cast<T##Piece *>(P)) |
732 | CASE(Multi); |
733 | CASE(Text); |
734 | CASE(Placeholder); |
735 | CASE(Select); |
736 | CASE(Plural); |
737 | CASE(Diff); |
738 | CASE(Substitution); |
739 | #undef CASE |
740 | } |
741 | } |
742 | |
743 | void VisitSubstitution(SubstitutionPiece *P) { |
744 | SubstitutionContext Guard(*this, P); |
745 | Visit(P: Guard.Substitution); |
746 | } |
747 | |
748 | int mapIndex(int Idx, |
749 | ModifierMappingsType const &ModifierMappings) const { |
750 | if (!ModifierMappings) |
751 | return Idx; |
752 | if (ModifierMappings->size() <= static_cast<unsigned>(Idx)) |
753 | Builder.PrintFatalError(Msg: "Modifier value '" + std::to_string(val: Idx) + |
754 | "' is not valid for this mapping (has " + |
755 | std::to_string(val: ModifierMappings->size()) + |
756 | " mappings)" ); |
757 | return (*ModifierMappings)[Idx]; |
758 | } |
759 | |
760 | int mapIndex(int Idx) const { |
761 | return mapIndex(Idx, ModifierMappings); |
762 | } |
763 | |
764 | protected: |
765 | DiagnosticTextBuilder &Builder; |
766 | ModifierMappingsType ModifierMappings; |
767 | }; |
768 | |
769 | void escapeRST(StringRef Str, std::string &Out) { |
770 | for (auto K : Str) { |
771 | if (StringRef("`*|_[]\\" ).count(C: K)) |
772 | Out.push_back(c: '\\'); |
773 | Out.push_back(c: K); |
774 | } |
775 | } |
776 | |
777 | template <typename It> void padToSameLength(It Begin, It End) { |
778 | size_t Width = 0; |
779 | for (It I = Begin; I != End; ++I) |
780 | Width = std::max(Width, I->size()); |
781 | for (It I = Begin; I != End; ++I) |
782 | (*I) += std::string(Width - I->size(), ' '); |
783 | } |
784 | |
785 | template <typename It> void makeTableRows(It Begin, It End) { |
786 | if (Begin == End) |
787 | return; |
788 | padToSameLength(Begin, End); |
789 | for (It I = Begin; I != End; ++I) |
790 | *I = "|" + *I + "|" ; |
791 | } |
792 | |
793 | void makeRowSeparator(std::string &Str) { |
794 | for (char &K : Str) |
795 | K = (K == '|' ? '+' : '-'); |
796 | } |
797 | |
798 | struct DiagTextDocPrinter : DiagTextVisitor<DiagTextDocPrinter> { |
799 | using BaseTy = DiagTextVisitor<DiagTextDocPrinter>; |
800 | DiagTextDocPrinter(DiagnosticTextBuilder &Builder, |
801 | std::vector<std::string> &RST) |
802 | : BaseTy(Builder), RST(RST) {} |
803 | |
804 | void gatherNodes( |
805 | Piece *OrigP, const ModifierMappingsType &CurrentMappings, |
806 | std::vector<std::pair<Piece *, ModifierMappingsType>> &Pieces) const { |
807 | if (auto *Sub = dyn_cast<SubstitutionPiece>(Val: OrigP)) { |
808 | ModifierMappingsType NewMappings = |
809 | getSubstitutionMappings(P: Sub, Mappings: CurrentMappings); |
810 | return gatherNodes(OrigP: Builder.getSubstitution(S: Sub), CurrentMappings: NewMappings, Pieces); |
811 | } |
812 | if (auto *MD = dyn_cast<MultiPiece>(Val: OrigP)) { |
813 | for (Piece *Node : MD->Pieces) |
814 | gatherNodes(OrigP: Node, CurrentMappings, Pieces); |
815 | return; |
816 | } |
817 | Pieces.push_back(x: std::make_pair(x&: OrigP, y: CurrentMappings)); |
818 | } |
819 | |
820 | void VisitMulti(MultiPiece *P) { |
821 | if (P->Pieces.empty()) { |
822 | RST.push_back(x: "" ); |
823 | return; |
824 | } |
825 | |
826 | if (P->Pieces.size() == 1) |
827 | return Visit(P: P->Pieces[0]); |
828 | |
829 | // Flatten the list of nodes, replacing any substitution pieces with the |
830 | // recursively flattened substituted node. |
831 | std::vector<std::pair<Piece *, ModifierMappingsType>> Pieces; |
832 | gatherNodes(OrigP: P, CurrentMappings: ModifierMappings, Pieces); |
833 | |
834 | std::string EmptyLinePrefix; |
835 | size_t Start = RST.size(); |
836 | bool HasMultipleLines = true; |
837 | for (const std::pair<Piece *, ModifierMappingsType> &NodePair : Pieces) { |
838 | std::vector<std::string> Lines; |
839 | DiagTextDocPrinter Visitor{Builder, Lines}; |
840 | Visitor.ModifierMappings = NodePair.second; |
841 | Visitor.Visit(P: NodePair.first); |
842 | |
843 | if (Lines.empty()) |
844 | continue; |
845 | |
846 | // We need a vertical separator if either this or the previous piece is a |
847 | // multi-line piece, or this is the last piece. |
848 | const char *Separator = (Lines.size() > 1 || HasMultipleLines) ? "|" : "" ; |
849 | HasMultipleLines = Lines.size() > 1; |
850 | |
851 | if (Start + Lines.size() > RST.size()) |
852 | RST.resize(new_size: Start + Lines.size(), x: EmptyLinePrefix); |
853 | |
854 | padToSameLength(Begin: Lines.begin(), End: Lines.end()); |
855 | for (size_t I = 0; I != Lines.size(); ++I) |
856 | RST[Start + I] += Separator + Lines[I]; |
857 | std::string Empty(Lines[0].size(), ' '); |
858 | for (size_t I = Start + Lines.size(); I != RST.size(); ++I) |
859 | RST[I] += Separator + Empty; |
860 | EmptyLinePrefix += Separator + Empty; |
861 | } |
862 | for (size_t I = Start; I != RST.size(); ++I) |
863 | RST[I] += "|" ; |
864 | EmptyLinePrefix += "|" ; |
865 | |
866 | makeRowSeparator(Str&: EmptyLinePrefix); |
867 | RST.insert(position: RST.begin() + Start, x: EmptyLinePrefix); |
868 | RST.insert(position: RST.end(), x: EmptyLinePrefix); |
869 | } |
870 | |
871 | void VisitText(TextPiece *P) { |
872 | RST.push_back(x: "" ); |
873 | auto &S = RST.back(); |
874 | |
875 | StringRef T = P->Text; |
876 | while (T.consume_front(Prefix: " " )) |
877 | RST.back() += " |nbsp| " ; |
878 | |
879 | std::string Suffix; |
880 | while (T.consume_back(Suffix: " " )) |
881 | Suffix += " |nbsp| " ; |
882 | |
883 | if (!T.empty()) { |
884 | S += ':'; |
885 | S += P->Role; |
886 | S += ":`" ; |
887 | escapeRST(Str: T, Out&: S); |
888 | S += '`'; |
889 | } |
890 | |
891 | S += Suffix; |
892 | } |
893 | |
894 | void VisitPlaceholder(PlaceholderPiece *P) { |
895 | RST.push_back(x: std::string(":placeholder:`" ) + |
896 | char('A' + mapIndex(Idx: P->Index)) + "`" ); |
897 | } |
898 | |
899 | void VisitSelect(SelectPiece *P) { |
900 | std::vector<size_t> SeparatorIndexes; |
901 | SeparatorIndexes.push_back(x: RST.size()); |
902 | RST.emplace_back(); |
903 | for (auto *O : P->Options) { |
904 | Visit(P: O); |
905 | SeparatorIndexes.push_back(x: RST.size()); |
906 | RST.emplace_back(); |
907 | } |
908 | |
909 | makeTableRows(Begin: RST.begin() + SeparatorIndexes.front(), |
910 | End: RST.begin() + SeparatorIndexes.back() + 1); |
911 | for (size_t I : SeparatorIndexes) |
912 | makeRowSeparator(Str&: RST[I]); |
913 | } |
914 | |
915 | void VisitPlural(PluralPiece *P) { VisitSelect(P); } |
916 | |
917 | void VisitDiff(DiffPiece *P) { |
918 | // Render %diff{a $ b $ c|d}e,f as %select{a %e b %f c|d}. |
919 | PlaceholderPiece E(MT_Placeholder, P->Indexes[0]); |
920 | PlaceholderPiece F(MT_Placeholder, P->Indexes[1]); |
921 | |
922 | MultiPiece FirstOption; |
923 | FirstOption.Pieces.push_back(x: P->Parts[0]); |
924 | FirstOption.Pieces.push_back(x: &E); |
925 | FirstOption.Pieces.push_back(x: P->Parts[1]); |
926 | FirstOption.Pieces.push_back(x: &F); |
927 | FirstOption.Pieces.push_back(x: P->Parts[2]); |
928 | |
929 | SelectPiece Select(MT_Diff); |
930 | Select.Options.push_back(x: &FirstOption); |
931 | Select.Options.push_back(x: P->Parts[3]); |
932 | |
933 | VisitSelect(P: &Select); |
934 | } |
935 | |
936 | std::vector<std::string> &RST; |
937 | }; |
938 | |
939 | struct DiagTextPrinter : DiagTextVisitor<DiagTextPrinter> { |
940 | public: |
941 | using BaseTy = DiagTextVisitor<DiagTextPrinter>; |
942 | DiagTextPrinter(DiagnosticTextBuilder &Builder, std::string &Result) |
943 | : BaseTy(Builder), Result(Result) {} |
944 | |
945 | void VisitMulti(MultiPiece *P) { |
946 | for (auto *Child : P->Pieces) |
947 | Visit(P: Child); |
948 | } |
949 | void VisitText(TextPiece *P) { Result += P->Text; } |
950 | void VisitPlaceholder(PlaceholderPiece *P) { |
951 | Result += "%" ; |
952 | Result += getModifierName(MT: P->Kind); |
953 | addInt(Val: mapIndex(Idx: P->Index)); |
954 | } |
955 | void VisitSelect(SelectPiece *P) { |
956 | Result += "%" ; |
957 | Result += getModifierName(MT: P->ModKind); |
958 | if (P->ModKind == MT_Select) { |
959 | Result += "{" ; |
960 | for (auto *D : P->Options) { |
961 | Visit(P: D); |
962 | Result += '|'; |
963 | } |
964 | if (!P->Options.empty()) |
965 | Result.erase(position: --Result.end()); |
966 | Result += '}'; |
967 | } |
968 | addInt(Val: mapIndex(Idx: P->Index)); |
969 | } |
970 | |
971 | void VisitPlural(PluralPiece *P) { |
972 | Result += "%plural{" ; |
973 | assert(P->Options.size() == P->OptionPrefixes.size()); |
974 | for (unsigned I = 0, End = P->Options.size(); I < End; ++I) { |
975 | if (P->OptionPrefixes[I]) |
976 | Visit(P: P->OptionPrefixes[I]); |
977 | Visit(P: P->Options[I]); |
978 | Result += "|" ; |
979 | } |
980 | if (!P->Options.empty()) |
981 | Result.erase(position: --Result.end()); |
982 | Result += '}'; |
983 | addInt(Val: mapIndex(Idx: P->Index)); |
984 | } |
985 | |
986 | void VisitDiff(DiffPiece *P) { |
987 | Result += "%diff{" ; |
988 | Visit(P: P->Parts[0]); |
989 | Result += "$" ; |
990 | Visit(P: P->Parts[1]); |
991 | Result += "$" ; |
992 | Visit(P: P->Parts[2]); |
993 | Result += "|" ; |
994 | Visit(P: P->Parts[3]); |
995 | Result += "}" ; |
996 | addInt(Val: mapIndex(Idx: P->Indexes[0])); |
997 | Result += "," ; |
998 | addInt(Val: mapIndex(Idx: P->Indexes[1])); |
999 | } |
1000 | |
1001 | void addInt(int Val) { Result += std::to_string(val: Val); } |
1002 | |
1003 | std::string &Result; |
1004 | }; |
1005 | |
1006 | int DiagnosticTextBuilder::DiagText::parseModifier(StringRef &Text) const { |
1007 | if (Text.empty() || !isdigit(Text[0])) |
1008 | Builder.PrintFatalError(Msg: "expected modifier in diagnostic" ); |
1009 | int Val = 0; |
1010 | do { |
1011 | Val *= 10; |
1012 | Val += Text[0] - '0'; |
1013 | Text = Text.drop_front(); |
1014 | } while (!Text.empty() && isdigit(Text[0])); |
1015 | return Val; |
1016 | } |
1017 | |
1018 | Piece *DiagnosticTextBuilder::DiagText::parseDiagText(StringRef &Text, |
1019 | StopAt Stop) { |
1020 | std::vector<Piece *> Parsed; |
1021 | |
1022 | constexpr llvm::StringLiteral StopSets[] = {"%" , "%|}" , "%|}$" }; |
1023 | llvm::StringRef StopSet = StopSets[static_cast<int>(Stop)]; |
1024 | |
1025 | while (!Text.empty()) { |
1026 | size_t End = (size_t)-2; |
1027 | do |
1028 | End = Text.find_first_of(Chars: StopSet, From: End + 2); |
1029 | while ( |
1030 | End < Text.size() - 1 && Text[End] == '%' && |
1031 | (Text[End + 1] == '%' || Text[End + 1] == '|' || Text[End + 1] == '$')); |
1032 | |
1033 | if (End) { |
1034 | Parsed.push_back(x: New<TextPiece>(args: Text.slice(Start: 0, End), args: "diagtext" )); |
1035 | Text = Text.slice(Start: End, End: StringRef::npos); |
1036 | if (Text.empty()) |
1037 | break; |
1038 | } |
1039 | |
1040 | if (Text[0] == '|' || Text[0] == '}' || Text[0] == '$') |
1041 | break; |
1042 | |
1043 | // Drop the '%'. |
1044 | Text = Text.drop_front(); |
1045 | |
1046 | // Extract the (optional) modifier. |
1047 | size_t ModLength = Text.find_first_of(Chars: "0123456789{" ); |
1048 | StringRef Modifier = Text.slice(Start: 0, End: ModLength); |
1049 | Text = Text.slice(Start: ModLength, End: StringRef::npos); |
1050 | ModifierType ModType = llvm::StringSwitch<ModifierType>{Modifier} |
1051 | .Case(S: "select" , Value: MT_Select) |
1052 | .Case(S: "sub" , Value: MT_Sub) |
1053 | .Case(S: "diff" , Value: MT_Diff) |
1054 | .Case(S: "plural" , Value: MT_Plural) |
1055 | .Case(S: "s" , Value: MT_S) |
1056 | .Case(S: "ordinal" , Value: MT_Ordinal) |
1057 | .Case(S: "q" , Value: MT_Q) |
1058 | .Case(S: "objcclass" , Value: MT_ObjCClass) |
1059 | .Case(S: "objcinstance" , Value: MT_ObjCInstance) |
1060 | .Case(S: "" , Value: MT_Placeholder) |
1061 | .Default(Value: MT_Unknown); |
1062 | |
1063 | auto ExpectAndConsume = [&](StringRef Prefix) { |
1064 | if (!Text.consume_front(Prefix)) |
1065 | Builder.PrintFatalError(Msg: "expected '" + Prefix + "' while parsing %" + |
1066 | Modifier); |
1067 | }; |
1068 | |
1069 | switch (ModType) { |
1070 | case MT_Unknown: |
1071 | Builder.PrintFatalError(Msg: "Unknown modifier type: " + Modifier); |
1072 | case MT_Select: { |
1073 | SelectPiece *Select = New<SelectPiece>(args: MT_Select); |
1074 | do { |
1075 | Text = Text.drop_front(); // '{' or '|' |
1076 | Select->Options.push_back( |
1077 | x: parseDiagText(Text, Stop: StopAt::PipeOrCloseBrace)); |
1078 | assert(!Text.empty() && "malformed %select" ); |
1079 | } while (Text.front() == '|'); |
1080 | ExpectAndConsume("}" ); |
1081 | Select->Index = parseModifier(Text); |
1082 | Parsed.push_back(x: Select); |
1083 | continue; |
1084 | } |
1085 | case MT_Plural: { |
1086 | PluralPiece *Plural = New<PluralPiece>(); |
1087 | do { |
1088 | Text = Text.drop_front(); // '{' or '|' |
1089 | size_t End = Text.find_first_of(C: ':'); |
1090 | if (End == StringRef::npos) |
1091 | Builder.PrintFatalError(Msg: "expected ':' while parsing %plural" ); |
1092 | ++End; |
1093 | assert(!Text.empty()); |
1094 | Plural->OptionPrefixes.push_back( |
1095 | x: New<TextPiece>(args: Text.slice(Start: 0, End), args: "diagtext" )); |
1096 | Text = Text.slice(Start: End, End: StringRef::npos); |
1097 | Plural->Options.push_back( |
1098 | x: parseDiagText(Text, Stop: StopAt::PipeOrCloseBrace)); |
1099 | assert(!Text.empty() && "malformed %plural" ); |
1100 | } while (Text.front() == '|'); |
1101 | ExpectAndConsume("}" ); |
1102 | Plural->Index = parseModifier(Text); |
1103 | Parsed.push_back(x: Plural); |
1104 | continue; |
1105 | } |
1106 | case MT_Sub: { |
1107 | SubstitutionPiece *Sub = New<SubstitutionPiece>(); |
1108 | ExpectAndConsume("{" ); |
1109 | size_t NameSize = Text.find_first_of(C: '}'); |
1110 | assert(NameSize != size_t(-1) && "failed to find the end of the name" ); |
1111 | assert(NameSize != 0 && "empty name?" ); |
1112 | Sub->Name = Text.substr(Start: 0, N: NameSize).str(); |
1113 | Text = Text.drop_front(N: NameSize); |
1114 | ExpectAndConsume("}" ); |
1115 | if (!Text.empty()) { |
1116 | while (true) { |
1117 | if (!isdigit(Text[0])) |
1118 | break; |
1119 | Sub->Modifiers.push_back(x: parseModifier(Text)); |
1120 | if (!Text.consume_front(Prefix: "," )) |
1121 | break; |
1122 | assert(!Text.empty() && isdigit(Text[0]) && |
1123 | "expected another modifier" ); |
1124 | } |
1125 | } |
1126 | Parsed.push_back(x: Sub); |
1127 | continue; |
1128 | } |
1129 | case MT_Diff: { |
1130 | DiffPiece *Diff = New<DiffPiece>(); |
1131 | ExpectAndConsume("{" ); |
1132 | Diff->Parts[0] = parseDiagText(Text, Stop: StopAt::Dollar); |
1133 | ExpectAndConsume("$" ); |
1134 | Diff->Parts[1] = parseDiagText(Text, Stop: StopAt::Dollar); |
1135 | ExpectAndConsume("$" ); |
1136 | Diff->Parts[2] = parseDiagText(Text, Stop: StopAt::PipeOrCloseBrace); |
1137 | ExpectAndConsume("|" ); |
1138 | Diff->Parts[3] = parseDiagText(Text, Stop: StopAt::PipeOrCloseBrace); |
1139 | ExpectAndConsume("}" ); |
1140 | Diff->Indexes[0] = parseModifier(Text); |
1141 | ExpectAndConsume("," ); |
1142 | Diff->Indexes[1] = parseModifier(Text); |
1143 | Parsed.push_back(x: Diff); |
1144 | continue; |
1145 | } |
1146 | case MT_S: { |
1147 | SelectPiece *Select = New<SelectPiece>(args&: ModType); |
1148 | Select->Options.push_back(x: New<TextPiece>(args: "" )); |
1149 | Select->Options.push_back(x: New<TextPiece>(args: "s" , args: "diagtext" )); |
1150 | Select->Index = parseModifier(Text); |
1151 | Parsed.push_back(x: Select); |
1152 | continue; |
1153 | } |
1154 | case MT_Q: |
1155 | case MT_Placeholder: |
1156 | case MT_ObjCClass: |
1157 | case MT_ObjCInstance: |
1158 | case MT_Ordinal: { |
1159 | Parsed.push_back(x: New<PlaceholderPiece>(args&: ModType, args: parseModifier(Text))); |
1160 | continue; |
1161 | } |
1162 | } |
1163 | } |
1164 | |
1165 | return New<MultiPiece>(args&: Parsed); |
1166 | } |
1167 | |
1168 | std::vector<std::string> |
1169 | DiagnosticTextBuilder::buildForDocumentation(StringRef Severity, |
1170 | const Record *R) { |
1171 | EvaluatingRecordGuard Guard(&EvaluatingRecord, R); |
1172 | StringRef Text = R->getValueAsString(FieldName: "Summary" ); |
1173 | |
1174 | DiagText D(*this, Text); |
1175 | TextPiece *Prefix = D.New<TextPiece>(args&: Severity, args&: Severity); |
1176 | Prefix->Text += ": " ; |
1177 | auto *MP = dyn_cast<MultiPiece>(Val: D.Root); |
1178 | if (!MP) { |
1179 | MP = D.New<MultiPiece>(); |
1180 | MP->Pieces.push_back(x: D.Root); |
1181 | D.Root = MP; |
1182 | } |
1183 | MP->Pieces.insert(position: MP->Pieces.begin(), x: Prefix); |
1184 | std::vector<std::string> Result; |
1185 | DiagTextDocPrinter{*this, Result}.Visit(P: D.Root); |
1186 | return Result; |
1187 | } |
1188 | |
1189 | std::string DiagnosticTextBuilder::buildForDefinition(const Record *R) { |
1190 | EvaluatingRecordGuard Guard(&EvaluatingRecord, R); |
1191 | StringRef Text = R->getValueAsString(FieldName: "Summary" ); |
1192 | DiagText D(*this, Text); |
1193 | std::string Result; |
1194 | DiagTextPrinter{*this, Result}.Visit(P: D.Root); |
1195 | return Result; |
1196 | } |
1197 | |
1198 | } // namespace |
1199 | |
1200 | //===----------------------------------------------------------------------===// |
1201 | // Warning Tables (.inc file) generation. |
1202 | //===----------------------------------------------------------------------===// |
1203 | |
1204 | static bool isError(const Record &Diag) { |
1205 | const std::string &ClsName = |
1206 | std::string(Diag.getValueAsDef(FieldName: "Class" )->getName()); |
1207 | return ClsName == "CLASS_ERROR" ; |
1208 | } |
1209 | |
1210 | static bool (const Record &Diag) { |
1211 | const std::string &ClsName = |
1212 | std::string(Diag.getValueAsDef(FieldName: "Class" )->getName()); |
1213 | return ClsName == "CLASS_REMARK" ; |
1214 | } |
1215 | |
1216 | // Presumes the text has been split at the first whitespace or hyphen. |
1217 | static bool isExemptAtStart(StringRef Text) { |
1218 | // Fast path, the first character is lowercase or not alphanumeric. |
1219 | if (Text.empty() || isLower(C: Text[0]) || !isAlnum(C: Text[0])) |
1220 | return true; |
1221 | |
1222 | // If the text is all uppercase (or numbers, +, or _), then we assume it's an |
1223 | // acronym and that's allowed. This covers cases like ISO, C23, C++14, and |
1224 | // OBJECT_MODE. However, if there's only a single letter other than "C", we |
1225 | // do not exempt it so that we catch a case like "A really bad idea" while |
1226 | // still allowing a case like "C does not allow...". |
1227 | if (llvm::all_of(Range&: Text, P: [](char C) { |
1228 | return isUpper(C) || isDigit(C) || C == '+' || C == '_'; |
1229 | })) |
1230 | return Text.size() > 1 || Text[0] == 'C'; |
1231 | |
1232 | // Otherwise, there are a few other exemptions. |
1233 | return StringSwitch<bool>(Text) |
1234 | .Case(S: "AddressSanitizer" , Value: true) |
1235 | .Case(S: "CFString" , Value: true) |
1236 | .Case(S: "Clang" , Value: true) |
1237 | .Case(S: "Fuchsia" , Value: true) |
1238 | .Case(S: "GNUstep" , Value: true) |
1239 | .Case(S: "IBOutletCollection" , Value: true) |
1240 | .Case(S: "Microsoft" , Value: true) |
1241 | .Case(S: "Neon" , Value: true) |
1242 | .StartsWith(S: "NSInvocation" , Value: true) // NSInvocation, NSInvocation's |
1243 | .Case(S: "Objective" , Value: true) // Objective-C (hyphen is a word boundary) |
1244 | .Case(S: "OpenACC" , Value: true) |
1245 | .Case(S: "OpenCL" , Value: true) |
1246 | .Case(S: "OpenMP" , Value: true) |
1247 | .Case(S: "Pascal" , Value: true) |
1248 | .Case(S: "Swift" , Value: true) |
1249 | .Case(S: "Unicode" , Value: true) |
1250 | .Case(S: "Vulkan" , Value: true) |
1251 | .Case(S: "WebAssembly" , Value: true) |
1252 | .Default(Value: false); |
1253 | } |
1254 | |
1255 | // Does not presume the text has been split at all. |
1256 | static bool isExemptAtEnd(StringRef Text) { |
1257 | // Rather than come up with a list of characters that are allowed, we go the |
1258 | // other way and look only for characters that are not allowed. |
1259 | switch (Text.back()) { |
1260 | default: |
1261 | return true; |
1262 | case '?': |
1263 | // Explicitly allowed to support "; did you mean?". |
1264 | return true; |
1265 | case '.': |
1266 | case '!': |
1267 | return false; |
1268 | } |
1269 | } |
1270 | |
1271 | static void verifyDiagnosticWording(const Record &Diag) { |
1272 | StringRef FullDiagText = Diag.getValueAsString(FieldName: "Summary" ); |
1273 | |
1274 | auto DiagnoseStart = [&](StringRef Text) { |
1275 | // Verify that the text does not start with a capital letter, except for |
1276 | // special cases that are exempt like ISO and C++. Find the first word |
1277 | // by looking for a word breaking character. |
1278 | char Separators[] = {' ', '-', ',', '}'}; |
1279 | auto Iter = std::find_first_of( |
1280 | first1: Text.begin(), last1: Text.end(), first2: std::begin(arr&: Separators), last2: std::end(arr&: Separators)); |
1281 | |
1282 | StringRef First = Text.substr(Start: 0, N: Iter - Text.begin()); |
1283 | if (!isExemptAtStart(Text: First)) { |
1284 | PrintError(Rec: &Diag, |
1285 | Msg: "Diagnostics should not start with a capital letter; '" + |
1286 | First + "' is invalid" ); |
1287 | } |
1288 | }; |
1289 | |
1290 | auto DiagnoseEnd = [&](StringRef Text) { |
1291 | // Verify that the text does not end with punctuation like '.' or '!'. |
1292 | if (!isExemptAtEnd(Text)) { |
1293 | PrintError(Rec: &Diag, Msg: "Diagnostics should not end with punctuation; '" + |
1294 | Text.substr(Start: Text.size() - 1, N: 1) + "' is invalid" ); |
1295 | } |
1296 | }; |
1297 | |
1298 | // If the diagnostic starts with %select, look through it to see whether any |
1299 | // of the options will cause a problem. |
1300 | if (FullDiagText.starts_with(Prefix: "%select{" )) { |
1301 | // Do a balanced delimiter scan from the start of the text to find the |
1302 | // closing '}', skipping intermediary {} pairs. |
1303 | |
1304 | size_t BraceCount = 1; |
1305 | constexpr size_t PercentSelectBraceLen = sizeof("%select{" ) - 1; |
1306 | auto Iter = FullDiagText.begin() + PercentSelectBraceLen; |
1307 | for (auto End = FullDiagText.end(); Iter != End; ++Iter) { |
1308 | char Ch = *Iter; |
1309 | if (Ch == '{') |
1310 | ++BraceCount; |
1311 | else if (Ch == '}') |
1312 | --BraceCount; |
1313 | if (!BraceCount) |
1314 | break; |
1315 | } |
1316 | // Defending against a malformed diagnostic string. |
1317 | if (BraceCount != 0) |
1318 | return; |
1319 | |
1320 | StringRef SelectText = |
1321 | FullDiagText.substr(Start: PercentSelectBraceLen, N: Iter - FullDiagText.begin() - |
1322 | PercentSelectBraceLen); |
1323 | SmallVector<StringRef, 4> SelectPieces; |
1324 | SelectText.split(A&: SelectPieces, Separator: '|'); |
1325 | |
1326 | // Walk over all of the individual pieces of select text to see if any of |
1327 | // them start with an invalid character. If any of the select pieces is |
1328 | // empty, we need to look at the first word after the %select to see |
1329 | // whether that is invalid or not. If all of the pieces are fine, then we |
1330 | // don't need to check anything else about the start of the diagnostic. |
1331 | bool CheckSecondWord = false; |
1332 | for (StringRef Piece : SelectPieces) { |
1333 | if (Piece.empty()) |
1334 | CheckSecondWord = true; |
1335 | else |
1336 | DiagnoseStart(Piece); |
1337 | } |
1338 | |
1339 | if (CheckSecondWord) { |
1340 | // There was an empty select piece, so we need to check the second |
1341 | // word. This catches situations like '%select{|fine}0 Not okay'. Add |
1342 | // two to account for the closing curly brace and the number after it. |
1343 | StringRef AfterSelect = |
1344 | FullDiagText.substr(Start: Iter - FullDiagText.begin() + 2).ltrim(); |
1345 | DiagnoseStart(AfterSelect); |
1346 | } |
1347 | } else { |
1348 | // If the start of the diagnostic is not %select, we can check the first |
1349 | // word and be done with it. |
1350 | DiagnoseStart(FullDiagText); |
1351 | } |
1352 | |
1353 | // If the last character in the diagnostic is a number preceded by a }, scan |
1354 | // backwards to see if this is for a %select{...}0. If it is, we need to look |
1355 | // at each piece to see whether it ends in punctuation or not. |
1356 | bool StillNeedToDiagEnd = true; |
1357 | if (isDigit(C: FullDiagText.back()) && *(FullDiagText.end() - 2) == '}') { |
1358 | // Scan backwards to find the opening curly brace. |
1359 | size_t BraceCount = 1; |
1360 | auto Iter = FullDiagText.end() - sizeof("}0" ); |
1361 | for (auto End = FullDiagText.begin(); Iter != End; --Iter) { |
1362 | char Ch = *Iter; |
1363 | if (Ch == '}') |
1364 | ++BraceCount; |
1365 | else if (Ch == '{') |
1366 | --BraceCount; |
1367 | if (!BraceCount) |
1368 | break; |
1369 | } |
1370 | // Defending against a malformed diagnostic string. |
1371 | if (BraceCount != 0) |
1372 | return; |
1373 | |
1374 | // Continue the backwards scan to find the word before the '{' to see if it |
1375 | // is 'select'. |
1376 | constexpr size_t SelectLen = sizeof("select" ) - 1; |
1377 | bool IsSelect = |
1378 | (FullDiagText.substr(Start: Iter - SelectLen - FullDiagText.begin(), |
1379 | N: SelectLen) == "select" ); |
1380 | if (IsSelect) { |
1381 | // Gather the content between the {} for the select in question so we can |
1382 | // split it into pieces. |
1383 | StillNeedToDiagEnd = false; // No longer need to handle the end. |
1384 | StringRef SelectText = |
1385 | FullDiagText.substr(Start: Iter - FullDiagText.begin() + /*{*/ 1, |
1386 | N: FullDiagText.end() - Iter - /*pos before }0*/ 3); |
1387 | SmallVector<StringRef, 4> SelectPieces; |
1388 | SelectText.split(A&: SelectPieces, Separator: '|'); |
1389 | for (StringRef Piece : SelectPieces) { |
1390 | // Not worrying about a situation like: "this is bar. %select{foo|}0". |
1391 | if (!Piece.empty()) |
1392 | DiagnoseEnd(Piece); |
1393 | } |
1394 | } |
1395 | } |
1396 | |
1397 | // If we didn't already cover the diagnostic because of a %select, handle it |
1398 | // now. |
1399 | if (StillNeedToDiagEnd) |
1400 | DiagnoseEnd(FullDiagText); |
1401 | |
1402 | // FIXME: This could also be improved by looking for instances of clang or |
1403 | // gcc in the diagnostic and recommend Clang or GCC instead. However, this |
1404 | // runs into odd situations like [[clang::warn_unused_result]], |
1405 | // #pragma clang, or --unwindlib=libgcc. |
1406 | } |
1407 | |
1408 | /// ClangDiagsDefsEmitter - The top-level class emits .def files containing |
1409 | /// declarations of Clang diagnostics. |
1410 | void clang::EmitClangDiagsDefs(RecordKeeper &Records, raw_ostream &OS, |
1411 | const std::string &Component) { |
1412 | // Write the #if guard |
1413 | if (!Component.empty()) { |
1414 | std::string ComponentName = StringRef(Component).upper(); |
1415 | OS << "#ifdef " << ComponentName << "START\n" ; |
1416 | OS << "__" << ComponentName << "START = DIAG_START_" << ComponentName |
1417 | << ",\n" ; |
1418 | OS << "#undef " << ComponentName << "START\n" ; |
1419 | OS << "#endif\n\n" ; |
1420 | } |
1421 | |
1422 | DiagnosticTextBuilder DiagTextBuilder(Records); |
1423 | |
1424 | std::vector<Record *> Diags = Records.getAllDerivedDefinitions(ClassName: "Diagnostic" ); |
1425 | |
1426 | std::vector<Record*> DiagGroups |
1427 | = Records.getAllDerivedDefinitions(ClassName: "DiagGroup" ); |
1428 | |
1429 | std::map<std::string, GroupInfo> DiagsInGroup; |
1430 | groupDiagnostics(Diags, DiagGroups, DiagsInGroup); |
1431 | |
1432 | DiagCategoryIDMap CategoryIDs(Records); |
1433 | DiagGroupParentMap DGParentMap(Records); |
1434 | |
1435 | // Compute the set of diagnostics that are in -Wpedantic. |
1436 | RecordSet DiagsInPedantic; |
1437 | InferPedantic inferPedantic(DGParentMap, Diags, DiagGroups, DiagsInGroup); |
1438 | inferPedantic.compute(DiagsInPedantic: &DiagsInPedantic, GroupsInPedantic: (RecordVec*)nullptr); |
1439 | |
1440 | for (unsigned i = 0, e = Diags.size(); i != e; ++i) { |
1441 | const Record &R = *Diags[i]; |
1442 | |
1443 | // Check if this is an error that is accidentally in a warning |
1444 | // group. |
1445 | if (isError(Diag: R)) { |
1446 | if (DefInit *Group = dyn_cast<DefInit>(Val: R.getValueInit(FieldName: "Group" ))) { |
1447 | const Record *GroupRec = Group->getDef(); |
1448 | const std::string &GroupName = |
1449 | std::string(GroupRec->getValueAsString(FieldName: "GroupName" )); |
1450 | PrintFatalError(ErrorLoc: R.getLoc(), Msg: "Error " + R.getName() + |
1451 | " cannot be in a warning group [" + GroupName + "]" ); |
1452 | } |
1453 | } |
1454 | |
1455 | // Check that all remarks have an associated diagnostic group. |
1456 | if (isRemark(Diag: R)) { |
1457 | if (!isa<DefInit>(Val: R.getValueInit(FieldName: "Group" ))) { |
1458 | PrintFatalError(ErrorLoc: R.getLoc(), Msg: "Error " + R.getName() + |
1459 | " not in any diagnostic group" ); |
1460 | } |
1461 | } |
1462 | |
1463 | // Filter by component. |
1464 | if (!Component.empty() && Component != R.getValueAsString(FieldName: "Component" )) |
1465 | continue; |
1466 | |
1467 | // Validate diagnostic wording for common issues. |
1468 | verifyDiagnosticWording(Diag: R); |
1469 | |
1470 | OS << "DIAG(" << R.getName() << ", " ; |
1471 | OS << R.getValueAsDef(FieldName: "Class" )->getName(); |
1472 | OS << ", (unsigned)diag::Severity::" |
1473 | << R.getValueAsDef(FieldName: "DefaultSeverity" )->getValueAsString(FieldName: "Name" ); |
1474 | |
1475 | // Description string. |
1476 | OS << ", \"" ; |
1477 | OS.write_escaped(Str: DiagTextBuilder.buildForDefinition(R: &R)) << '"'; |
1478 | |
1479 | // Warning group associated with the diagnostic. This is stored as an index |
1480 | // into the alphabetically sorted warning group table. |
1481 | if (DefInit *DI = dyn_cast<DefInit>(Val: R.getValueInit(FieldName: "Group" ))) { |
1482 | std::map<std::string, GroupInfo>::iterator I = DiagsInGroup.find( |
1483 | x: std::string(DI->getDef()->getValueAsString(FieldName: "GroupName" ))); |
1484 | assert(I != DiagsInGroup.end()); |
1485 | OS << ", " << I->second.IDNo; |
1486 | } else if (DiagsInPedantic.count(V: &R)) { |
1487 | std::map<std::string, GroupInfo>::iterator I = |
1488 | DiagsInGroup.find(x: "pedantic" ); |
1489 | assert(I != DiagsInGroup.end() && "pedantic group not defined" ); |
1490 | OS << ", " << I->second.IDNo; |
1491 | } else { |
1492 | OS << ", 0" ; |
1493 | } |
1494 | |
1495 | // SFINAE response. |
1496 | OS << ", " << R.getValueAsDef(FieldName: "SFINAE" )->getName(); |
1497 | |
1498 | // Default warning has no Werror bit. |
1499 | if (R.getValueAsBit(FieldName: "WarningNoWerror" )) |
1500 | OS << ", true" ; |
1501 | else |
1502 | OS << ", false" ; |
1503 | |
1504 | if (R.getValueAsBit(FieldName: "ShowInSystemHeader" )) |
1505 | OS << ", true" ; |
1506 | else |
1507 | OS << ", false" ; |
1508 | |
1509 | if (R.getValueAsBit(FieldName: "ShowInSystemMacro" )) |
1510 | OS << ", true" ; |
1511 | else |
1512 | OS << ", false" ; |
1513 | |
1514 | if (R.getValueAsBit(FieldName: "Deferrable" )) |
1515 | OS << ", true" ; |
1516 | else |
1517 | OS << ", false" ; |
1518 | |
1519 | // Category number. |
1520 | OS << ", " << CategoryIDs.getID(CategoryString: getDiagnosticCategory(R: &R, DiagGroupParents&: DGParentMap)); |
1521 | OS << ")\n" ; |
1522 | } |
1523 | } |
1524 | |
1525 | //===----------------------------------------------------------------------===// |
1526 | // Warning Group Tables generation |
1527 | //===----------------------------------------------------------------------===// |
1528 | |
1529 | static std::string getDiagCategoryEnum(llvm::StringRef name) { |
1530 | if (name.empty()) |
1531 | return "DiagCat_None" ; |
1532 | SmallString<256> enumName = llvm::StringRef("DiagCat_" ); |
1533 | for (llvm::StringRef::iterator I = name.begin(), E = name.end(); I != E; ++I) |
1534 | enumName += isalnum(*I) ? *I : '_'; |
1535 | return std::string(enumName); |
1536 | } |
1537 | |
1538 | /// Emit the array of diagnostic subgroups. |
1539 | /// |
1540 | /// The array of diagnostic subgroups contains for each group a list of its |
1541 | /// subgroups. The individual lists are separated by '-1'. Groups with no |
1542 | /// subgroups are skipped. |
1543 | /// |
1544 | /// \code |
1545 | /// static const int16_t DiagSubGroups[] = { |
1546 | /// /* Empty */ -1, |
1547 | /// /* DiagSubGroup0 */ 142, -1, |
1548 | /// /* DiagSubGroup13 */ 265, 322, 399, -1 |
1549 | /// } |
1550 | /// \endcode |
1551 | /// |
1552 | static void emitDiagSubGroups(std::map<std::string, GroupInfo> &DiagsInGroup, |
1553 | RecordVec &GroupsInPedantic, raw_ostream &OS) { |
1554 | OS << "static const int16_t DiagSubGroups[] = {\n" |
1555 | << " /* Empty */ -1,\n" ; |
1556 | for (auto const &I : DiagsInGroup) { |
1557 | const bool IsPedantic = I.first == "pedantic" ; |
1558 | |
1559 | const std::vector<std::string> &SubGroups = I.second.SubGroups; |
1560 | if (!SubGroups.empty() || (IsPedantic && !GroupsInPedantic.empty())) { |
1561 | OS << " /* DiagSubGroup" << I.second.IDNo << " */ " ; |
1562 | for (auto const &SubGroup : SubGroups) { |
1563 | std::map<std::string, GroupInfo>::const_iterator RI = |
1564 | DiagsInGroup.find(x: SubGroup); |
1565 | assert(RI != DiagsInGroup.end() && "Referenced without existing?" ); |
1566 | OS << RI->second.IDNo << ", " ; |
1567 | } |
1568 | // Emit the groups implicitly in "pedantic". |
1569 | if (IsPedantic) { |
1570 | for (auto const &Group : GroupsInPedantic) { |
1571 | const std::string &GroupName = |
1572 | std::string(Group->getValueAsString(FieldName: "GroupName" )); |
1573 | std::map<std::string, GroupInfo>::const_iterator RI = |
1574 | DiagsInGroup.find(x: GroupName); |
1575 | assert(RI != DiagsInGroup.end() && "Referenced without existing?" ); |
1576 | OS << RI->second.IDNo << ", " ; |
1577 | } |
1578 | } |
1579 | |
1580 | OS << "-1,\n" ; |
1581 | } |
1582 | } |
1583 | OS << "};\n\n" ; |
1584 | } |
1585 | |
1586 | /// Emit the list of diagnostic arrays. |
1587 | /// |
1588 | /// This data structure is a large array that contains itself arrays of varying |
1589 | /// size. Each array represents a list of diagnostics. The different arrays are |
1590 | /// separated by the value '-1'. |
1591 | /// |
1592 | /// \code |
1593 | /// static const int16_t DiagArrays[] = { |
1594 | /// /* Empty */ -1, |
1595 | /// /* DiagArray1 */ diag::warn_pragma_message, |
1596 | /// -1, |
1597 | /// /* DiagArray2 */ diag::warn_abs_too_small, |
1598 | /// diag::warn_unsigned_abs, |
1599 | /// diag::warn_wrong_absolute_value_type, |
1600 | /// -1 |
1601 | /// }; |
1602 | /// \endcode |
1603 | /// |
1604 | static void emitDiagArrays(std::map<std::string, GroupInfo> &DiagsInGroup, |
1605 | RecordVec &DiagsInPedantic, raw_ostream &OS) { |
1606 | OS << "static const int16_t DiagArrays[] = {\n" |
1607 | << " /* Empty */ -1,\n" ; |
1608 | for (auto const &I : DiagsInGroup) { |
1609 | const bool IsPedantic = I.first == "pedantic" ; |
1610 | |
1611 | const std::vector<const Record *> &V = I.second.DiagsInGroup; |
1612 | if (!V.empty() || (IsPedantic && !DiagsInPedantic.empty())) { |
1613 | OS << " /* DiagArray" << I.second.IDNo << " */ " ; |
1614 | for (auto *Record : V) |
1615 | OS << "diag::" << Record->getName() << ", " ; |
1616 | // Emit the diagnostics implicitly in "pedantic". |
1617 | if (IsPedantic) { |
1618 | for (auto const &Diag : DiagsInPedantic) |
1619 | OS << "diag::" << Diag->getName() << ", " ; |
1620 | } |
1621 | OS << "-1,\n" ; |
1622 | } |
1623 | } |
1624 | OS << "};\n\n" ; |
1625 | } |
1626 | |
1627 | /// Emit a list of group names. |
1628 | /// |
1629 | /// This creates a long string which by itself contains a list of pascal style |
1630 | /// strings, which consist of a length byte directly followed by the string. |
1631 | /// |
1632 | /// \code |
1633 | /// static const char DiagGroupNames[] = { |
1634 | /// \000\020#pragma-messages\t#warnings\020CFString-literal" |
1635 | /// }; |
1636 | /// \endcode |
1637 | static void emitDiagGroupNames(StringToOffsetTable &GroupNames, |
1638 | raw_ostream &OS) { |
1639 | OS << "static const char DiagGroupNames[] = {\n" ; |
1640 | GroupNames.EmitString(O&: OS); |
1641 | OS << "};\n\n" ; |
1642 | } |
1643 | |
1644 | /// Emit diagnostic arrays and related data structures. |
1645 | /// |
1646 | /// This creates the actual diagnostic array, an array of diagnostic subgroups |
1647 | /// and an array of subgroup names. |
1648 | /// |
1649 | /// \code |
1650 | /// #ifdef GET_DIAG_ARRAYS |
1651 | /// static const int16_t DiagArrays[]; |
1652 | /// static const int16_t DiagSubGroups[]; |
1653 | /// static const char DiagGroupNames[]; |
1654 | /// #endif |
1655 | /// \endcode |
1656 | static void emitAllDiagArrays(std::map<std::string, GroupInfo> &DiagsInGroup, |
1657 | RecordVec &DiagsInPedantic, |
1658 | RecordVec &GroupsInPedantic, |
1659 | StringToOffsetTable &GroupNames, |
1660 | raw_ostream &OS) { |
1661 | OS << "\n#ifdef GET_DIAG_ARRAYS\n" ; |
1662 | emitDiagArrays(DiagsInGroup, DiagsInPedantic, OS); |
1663 | emitDiagSubGroups(DiagsInGroup, GroupsInPedantic, OS); |
1664 | emitDiagGroupNames(GroupNames, OS); |
1665 | OS << "#endif // GET_DIAG_ARRAYS\n\n" ; |
1666 | } |
1667 | |
1668 | /// Emit diagnostic table. |
1669 | /// |
1670 | /// The table is sorted by the name of the diagnostic group. Each element |
1671 | /// consists of the name of the diagnostic group (given as offset in the |
1672 | /// group name table), a reference to a list of diagnostics (optional) and a |
1673 | /// reference to a set of subgroups (optional). |
1674 | /// |
1675 | /// \code |
1676 | /// #ifdef GET_DIAG_TABLE |
1677 | /// {/* abi */ 159, /* DiagArray11 */ 19, /* Empty */ 0}, |
1678 | /// {/* aggregate-return */ 180, /* Empty */ 0, /* Empty */ 0}, |
1679 | /// {/* all */ 197, /* Empty */ 0, /* DiagSubGroup13 */ 3}, |
1680 | /// {/* deprecated */ 1981,/* DiagArray1 */ 348, /* DiagSubGroup3 */ 9}, |
1681 | /// #endif |
1682 | /// \endcode |
1683 | static void emitDiagTable(std::map<std::string, GroupInfo> &DiagsInGroup, |
1684 | RecordVec &DiagsInPedantic, |
1685 | RecordVec &GroupsInPedantic, |
1686 | StringToOffsetTable &GroupNames, raw_ostream &OS) { |
1687 | unsigned MaxLen = 0; |
1688 | |
1689 | for (auto const &I: DiagsInGroup) |
1690 | MaxLen = std::max(a: MaxLen, b: (unsigned)I.first.size()); |
1691 | |
1692 | OS << "\n#ifdef DIAG_ENTRY\n" ; |
1693 | unsigned SubGroupIndex = 1, DiagArrayIndex = 1; |
1694 | for (auto const &I: DiagsInGroup) { |
1695 | // Group option string. |
1696 | OS << "DIAG_ENTRY(" ; |
1697 | OS << I.second.GroupName << " /* " ; |
1698 | |
1699 | if (I.first.find_first_not_of(s: "abcdefghijklmnopqrstuvwxyz" |
1700 | "ABCDEFGHIJKLMNOPQRSTUVWXYZ" |
1701 | "0123456789!@#$%^*-+=:?" ) != |
1702 | std::string::npos) |
1703 | PrintFatalError(Msg: "Invalid character in diagnostic group '" + I.first + |
1704 | "'" ); |
1705 | OS << I.first << " */, " ; |
1706 | // Store a pascal-style length byte at the beginning of the string. |
1707 | std::string Name = char(I.first.size()) + I.first; |
1708 | OS << GroupNames.GetOrAddStringOffset(Str: Name, appendZero: false) << ", " ; |
1709 | |
1710 | // Special handling for 'pedantic'. |
1711 | const bool IsPedantic = I.first == "pedantic" ; |
1712 | |
1713 | // Diagnostics in the group. |
1714 | const std::vector<const Record *> &V = I.second.DiagsInGroup; |
1715 | const bool hasDiags = |
1716 | !V.empty() || (IsPedantic && !DiagsInPedantic.empty()); |
1717 | if (hasDiags) { |
1718 | OS << "/* DiagArray" << I.second.IDNo << " */ " << DiagArrayIndex |
1719 | << ", " ; |
1720 | if (IsPedantic) |
1721 | DiagArrayIndex += DiagsInPedantic.size(); |
1722 | DiagArrayIndex += V.size() + 1; |
1723 | } else { |
1724 | OS << "0, " ; |
1725 | } |
1726 | |
1727 | // Subgroups. |
1728 | const std::vector<std::string> &SubGroups = I.second.SubGroups; |
1729 | const bool hasSubGroups = |
1730 | !SubGroups.empty() || (IsPedantic && !GroupsInPedantic.empty()); |
1731 | if (hasSubGroups) { |
1732 | OS << "/* DiagSubGroup" << I.second.IDNo << " */ " << SubGroupIndex |
1733 | << ", " ; |
1734 | if (IsPedantic) |
1735 | SubGroupIndex += GroupsInPedantic.size(); |
1736 | SubGroupIndex += SubGroups.size() + 1; |
1737 | } else { |
1738 | OS << "0, " ; |
1739 | } |
1740 | |
1741 | std::string Documentation = I.second.Defs.back() |
1742 | ->getValue(Name: "Documentation" ) |
1743 | ->getValue() |
1744 | ->getAsUnquotedString(); |
1745 | |
1746 | OS << "R\"(" << StringRef(Documentation).trim() << ")\"" ; |
1747 | |
1748 | OS << ")\n" ; |
1749 | } |
1750 | OS << "#endif // DIAG_ENTRY\n\n" ; |
1751 | } |
1752 | |
1753 | /// Emit the table of diagnostic categories. |
1754 | /// |
1755 | /// The table has the form of macro calls that have two parameters. The |
1756 | /// category's name as well as an enum that represents the category. The |
1757 | /// table can be used by defining the macro 'CATEGORY' and including this |
1758 | /// table right after. |
1759 | /// |
1760 | /// \code |
1761 | /// #ifdef GET_CATEGORY_TABLE |
1762 | /// CATEGORY("Semantic Issue", DiagCat_Semantic_Issue) |
1763 | /// CATEGORY("Lambda Issue", DiagCat_Lambda_Issue) |
1764 | /// #endif |
1765 | /// \endcode |
1766 | static void emitCategoryTable(RecordKeeper &Records, raw_ostream &OS) { |
1767 | DiagCategoryIDMap CategoriesByID(Records); |
1768 | OS << "\n#ifdef GET_CATEGORY_TABLE\n" ; |
1769 | for (auto const &C : CategoriesByID) |
1770 | OS << "CATEGORY(\"" << C << "\", " << getDiagCategoryEnum(name: C) << ")\n" ; |
1771 | OS << "#endif // GET_CATEGORY_TABLE\n\n" ; |
1772 | } |
1773 | |
1774 | void clang::EmitClangDiagGroups(RecordKeeper &Records, raw_ostream &OS) { |
1775 | // Compute a mapping from a DiagGroup to all of its parents. |
1776 | DiagGroupParentMap DGParentMap(Records); |
1777 | |
1778 | std::vector<Record *> Diags = Records.getAllDerivedDefinitions(ClassName: "Diagnostic" ); |
1779 | |
1780 | std::vector<Record *> DiagGroups = |
1781 | Records.getAllDerivedDefinitions(ClassName: "DiagGroup" ); |
1782 | |
1783 | std::map<std::string, GroupInfo> DiagsInGroup; |
1784 | groupDiagnostics(Diags, DiagGroups, DiagsInGroup); |
1785 | |
1786 | // All extensions are implicitly in the "pedantic" group. Record the |
1787 | // implicit set of groups in the "pedantic" group, and use this information |
1788 | // later when emitting the group information for Pedantic. |
1789 | RecordVec DiagsInPedantic; |
1790 | RecordVec GroupsInPedantic; |
1791 | InferPedantic inferPedantic(DGParentMap, Diags, DiagGroups, DiagsInGroup); |
1792 | inferPedantic.compute(DiagsInPedantic: &DiagsInPedantic, GroupsInPedantic: &GroupsInPedantic); |
1793 | |
1794 | StringToOffsetTable GroupNames; |
1795 | for (std::map<std::string, GroupInfo>::const_iterator |
1796 | I = DiagsInGroup.begin(), |
1797 | E = DiagsInGroup.end(); |
1798 | I != E; ++I) { |
1799 | // Store a pascal-style length byte at the beginning of the string. |
1800 | std::string Name = char(I->first.size()) + I->first; |
1801 | GroupNames.GetOrAddStringOffset(Str: Name, appendZero: false); |
1802 | } |
1803 | |
1804 | emitAllDiagArrays(DiagsInGroup, DiagsInPedantic, GroupsInPedantic, GroupNames, |
1805 | OS); |
1806 | emitDiagTable(DiagsInGroup, DiagsInPedantic, GroupsInPedantic, GroupNames, |
1807 | OS); |
1808 | emitCategoryTable(Records, OS); |
1809 | } |
1810 | |
1811 | //===----------------------------------------------------------------------===// |
1812 | // Diagnostic name index generation |
1813 | //===----------------------------------------------------------------------===// |
1814 | |
1815 | namespace { |
1816 | struct RecordIndexElement |
1817 | { |
1818 | RecordIndexElement() {} |
1819 | explicit RecordIndexElement(Record const &R) |
1820 | : Name(std::string(R.getName())) {} |
1821 | |
1822 | std::string Name; |
1823 | }; |
1824 | } // end anonymous namespace. |
1825 | |
1826 | void clang::EmitClangDiagsIndexName(RecordKeeper &Records, raw_ostream &OS) { |
1827 | const std::vector<Record*> &Diags = |
1828 | Records.getAllDerivedDefinitions(ClassName: "Diagnostic" ); |
1829 | |
1830 | std::vector<RecordIndexElement> Index; |
1831 | Index.reserve(n: Diags.size()); |
1832 | for (unsigned i = 0, e = Diags.size(); i != e; ++i) { |
1833 | const Record &R = *(Diags[i]); |
1834 | Index.push_back(x: RecordIndexElement(R)); |
1835 | } |
1836 | |
1837 | llvm::sort(C&: Index, |
1838 | Comp: [](const RecordIndexElement &Lhs, const RecordIndexElement &Rhs) { |
1839 | return Lhs.Name < Rhs.Name; |
1840 | }); |
1841 | |
1842 | for (unsigned i = 0, e = Index.size(); i != e; ++i) { |
1843 | const RecordIndexElement &R = Index[i]; |
1844 | |
1845 | OS << "DIAG_NAME_INDEX(" << R.Name << ")\n" ; |
1846 | } |
1847 | } |
1848 | |
1849 | //===----------------------------------------------------------------------===// |
1850 | // Diagnostic documentation generation |
1851 | //===----------------------------------------------------------------------===// |
1852 | |
1853 | namespace docs { |
1854 | namespace { |
1855 | |
1856 | bool (const Record *DiagGroup, |
1857 | const std::map<std::string, GroupInfo> &DiagsInGroup) { |
1858 | bool = false, = false; |
1859 | |
1860 | std::function<void(StringRef)> Visit = [&](StringRef GroupName) { |
1861 | auto &GroupInfo = DiagsInGroup.find(x: std::string(GroupName))->second; |
1862 | for (const Record *Diag : GroupInfo.DiagsInGroup) |
1863 | (isRemark(Diag: *Diag) ? AnyRemarks : AnyNonRemarks) = true; |
1864 | for (const auto &Name : GroupInfo.SubGroups) |
1865 | Visit(Name); |
1866 | }; |
1867 | Visit(DiagGroup->getValueAsString(FieldName: "GroupName" )); |
1868 | |
1869 | if (AnyRemarks && AnyNonRemarks) |
1870 | PrintFatalError( |
1871 | ErrorLoc: DiagGroup->getLoc(), |
1872 | Msg: "Diagnostic group contains both remark and non-remark diagnostics" ); |
1873 | return AnyRemarks; |
1874 | } |
1875 | |
1876 | std::string getDefaultSeverity(const Record *Diag) { |
1877 | return std::string( |
1878 | Diag->getValueAsDef(FieldName: "DefaultSeverity" )->getValueAsString(FieldName: "Name" )); |
1879 | } |
1880 | |
1881 | std::set<std::string> |
1882 | getDefaultSeverities(const Record *DiagGroup, |
1883 | const std::map<std::string, GroupInfo> &DiagsInGroup) { |
1884 | std::set<std::string> States; |
1885 | |
1886 | std::function<void(StringRef)> Visit = [&](StringRef GroupName) { |
1887 | auto &GroupInfo = DiagsInGroup.find(x: std::string(GroupName))->second; |
1888 | for (const Record *Diag : GroupInfo.DiagsInGroup) |
1889 | States.insert(x: getDefaultSeverity(Diag)); |
1890 | for (const auto &Name : GroupInfo.SubGroups) |
1891 | Visit(Name); |
1892 | }; |
1893 | Visit(DiagGroup->getValueAsString(FieldName: "GroupName" )); |
1894 | return States; |
1895 | } |
1896 | |
1897 | void (StringRef Str, raw_ostream &OS, char Kind = '-') { |
1898 | OS << Str << "\n" << std::string(Str.size(), Kind) << "\n" ; |
1899 | } |
1900 | |
1901 | void writeDiagnosticText(DiagnosticTextBuilder &Builder, const Record *R, |
1902 | StringRef Role, raw_ostream &OS) { |
1903 | StringRef Text = R->getValueAsString(FieldName: "Summary" ); |
1904 | if (Text == "%0" ) |
1905 | OS << "The text of this diagnostic is not controlled by Clang.\n\n" ; |
1906 | else { |
1907 | std::vector<std::string> Out = Builder.buildForDocumentation(Severity: Role, R); |
1908 | for (auto &Line : Out) |
1909 | OS << Line << "\n" ; |
1910 | OS << "\n" ; |
1911 | } |
1912 | } |
1913 | |
1914 | } // namespace |
1915 | } // namespace docs |
1916 | |
1917 | void clang::EmitClangDiagDocs(RecordKeeper &Records, raw_ostream &OS) { |
1918 | using namespace docs; |
1919 | |
1920 | // Get the documentation introduction paragraph. |
1921 | const Record *Documentation = Records.getDef(Name: "GlobalDocumentation" ); |
1922 | if (!Documentation) { |
1923 | PrintFatalError(Msg: "The Documentation top-level definition is missing, " |
1924 | "no documentation will be generated." ); |
1925 | return; |
1926 | } |
1927 | |
1928 | OS << Documentation->getValueAsString(FieldName: "Intro" ) << "\n" ; |
1929 | |
1930 | DiagnosticTextBuilder Builder(Records); |
1931 | |
1932 | std::vector<Record*> Diags = |
1933 | Records.getAllDerivedDefinitions(ClassName: "Diagnostic" ); |
1934 | |
1935 | std::vector<Record*> DiagGroups = |
1936 | Records.getAllDerivedDefinitions(ClassName: "DiagGroup" ); |
1937 | llvm::sort(C&: DiagGroups, Comp: diagGroupBeforeByName); |
1938 | |
1939 | DiagGroupParentMap DGParentMap(Records); |
1940 | |
1941 | std::map<std::string, GroupInfo> DiagsInGroup; |
1942 | groupDiagnostics(Diags, DiagGroups, DiagsInGroup); |
1943 | |
1944 | // Compute the set of diagnostics that are in -Wpedantic. |
1945 | { |
1946 | RecordSet DiagsInPedanticSet; |
1947 | RecordSet GroupsInPedanticSet; |
1948 | InferPedantic inferPedantic(DGParentMap, Diags, DiagGroups, DiagsInGroup); |
1949 | inferPedantic.compute(DiagsInPedantic: &DiagsInPedanticSet, GroupsInPedantic: &GroupsInPedanticSet); |
1950 | auto &PedDiags = DiagsInGroup["pedantic" ]; |
1951 | // Put the diagnostics into a deterministic order. |
1952 | RecordVec DiagsInPedantic(DiagsInPedanticSet.begin(), |
1953 | DiagsInPedanticSet.end()); |
1954 | RecordVec GroupsInPedantic(GroupsInPedanticSet.begin(), |
1955 | GroupsInPedanticSet.end()); |
1956 | llvm::sort(C&: DiagsInPedantic, Comp: beforeThanCompare); |
1957 | llvm::sort(C&: GroupsInPedantic, Comp: beforeThanCompare); |
1958 | PedDiags.DiagsInGroup.insert(position: PedDiags.DiagsInGroup.end(), |
1959 | first: DiagsInPedantic.begin(), |
1960 | last: DiagsInPedantic.end()); |
1961 | for (auto *Group : GroupsInPedantic) |
1962 | PedDiags.SubGroups.push_back( |
1963 | x: std::string(Group->getValueAsString(FieldName: "GroupName" ))); |
1964 | } |
1965 | |
1966 | // FIXME: Write diagnostic categories and link to diagnostic groups in each. |
1967 | |
1968 | // Write out the diagnostic groups. |
1969 | for (const Record *G : DiagGroups) { |
1970 | bool = isRemarkGroup(DiagGroup: G, DiagsInGroup); |
1971 | auto &GroupInfo = |
1972 | DiagsInGroup[std::string(G->getValueAsString(FieldName: "GroupName" ))]; |
1973 | bool IsSynonym = GroupInfo.DiagsInGroup.empty() && |
1974 | GroupInfo.SubGroups.size() == 1; |
1975 | |
1976 | writeHeader(Str: ((IsRemarkGroup ? "-R" : "-W" ) + |
1977 | G->getValueAsString(FieldName: "GroupName" )).str(), |
1978 | OS); |
1979 | |
1980 | if (!IsSynonym) { |
1981 | // FIXME: Ideally, all the diagnostics in a group should have the same |
1982 | // default state, but that is not currently the case. |
1983 | auto DefaultSeverities = getDefaultSeverities(DiagGroup: G, DiagsInGroup); |
1984 | if (!DefaultSeverities.empty() && !DefaultSeverities.count(x: "Ignored" )) { |
1985 | bool AnyNonErrors = DefaultSeverities.count(x: "Warning" ) || |
1986 | DefaultSeverities.count(x: "Remark" ); |
1987 | if (!AnyNonErrors) |
1988 | OS << "This diagnostic is an error by default, but the flag ``-Wno-" |
1989 | << G->getValueAsString(FieldName: "GroupName" ) << "`` can be used to disable " |
1990 | << "the error.\n\n" ; |
1991 | else |
1992 | OS << "This diagnostic is enabled by default.\n\n" ; |
1993 | } else if (DefaultSeverities.size() > 1) { |
1994 | OS << "Some of the diagnostics controlled by this flag are enabled " |
1995 | << "by default.\n\n" ; |
1996 | } |
1997 | } |
1998 | |
1999 | if (!GroupInfo.SubGroups.empty()) { |
2000 | if (IsSynonym) |
2001 | OS << "Synonym for " ; |
2002 | else if (GroupInfo.DiagsInGroup.empty()) |
2003 | OS << "Controls " ; |
2004 | else |
2005 | OS << "Also controls " ; |
2006 | |
2007 | bool First = true; |
2008 | llvm::sort(C&: GroupInfo.SubGroups); |
2009 | for (const auto &Name : GroupInfo.SubGroups) { |
2010 | if (!First) OS << ", " ; |
2011 | OS << "`" << (IsRemarkGroup ? "-R" : "-W" ) << Name << "`_" ; |
2012 | First = false; |
2013 | } |
2014 | OS << ".\n\n" ; |
2015 | } |
2016 | |
2017 | if (!GroupInfo.DiagsInGroup.empty()) { |
2018 | OS << "**Diagnostic text:**\n\n" ; |
2019 | for (const Record *D : GroupInfo.DiagsInGroup) { |
2020 | auto Severity = getDefaultSeverity(Diag: D); |
2021 | Severity[0] = tolower(c: Severity[0]); |
2022 | if (Severity == "ignored" ) |
2023 | Severity = IsRemarkGroup ? "remark" : "warning" ; |
2024 | |
2025 | writeDiagnosticText(Builder, R: D, Role: Severity, OS); |
2026 | } |
2027 | } |
2028 | |
2029 | auto Doc = G->getValueAsString(FieldName: "Documentation" ); |
2030 | if (!Doc.empty()) |
2031 | OS << Doc; |
2032 | else if (GroupInfo.SubGroups.empty() && GroupInfo.DiagsInGroup.empty()) |
2033 | OS << "This diagnostic flag exists for GCC compatibility, and has no " |
2034 | "effect in Clang.\n" ; |
2035 | OS << "\n" ; |
2036 | } |
2037 | } |
2038 | |