1 | //===--- PlistDiagnostics.cpp - Plist Diagnostics for Paths -----*- 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 | // This file defines the PlistDiagnostics object. |
10 | // |
11 | //===----------------------------------------------------------------------===// |
12 | |
13 | #include "clang/Analysis/IssueHash.h" |
14 | #include "clang/Analysis/MacroExpansionContext.h" |
15 | #include "clang/Analysis/PathDiagnostic.h" |
16 | #include "clang/Basic/PlistSupport.h" |
17 | #include "clang/Basic/SourceManager.h" |
18 | #include "clang/Basic/Version.h" |
19 | #include "clang/CrossTU/CrossTranslationUnit.h" |
20 | #include "clang/Frontend/ASTUnit.h" |
21 | #include "clang/Lex/Preprocessor.h" |
22 | #include "clang/Lex/TokenConcatenation.h" |
23 | #include "clang/Rewrite/Core/HTMLRewrite.h" |
24 | #include "clang/StaticAnalyzer/Core/PathDiagnosticConsumers.h" |
25 | #include "llvm/ADT/SmallVector.h" |
26 | #include "llvm/ADT/Statistic.h" |
27 | #include <memory> |
28 | #include <optional> |
29 | |
30 | using namespace clang; |
31 | using namespace ento; |
32 | using namespace markup; |
33 | |
34 | //===----------------------------------------------------------------------===// |
35 | // Declarations of helper classes and functions for emitting bug reports in |
36 | // plist format. |
37 | //===----------------------------------------------------------------------===// |
38 | |
39 | namespace { |
40 | class PlistDiagnostics : public PathDiagnosticConsumer { |
41 | PathDiagnosticConsumerOptions DiagOpts; |
42 | const std::string OutputFile; |
43 | const Preprocessor &PP; |
44 | const cross_tu::CrossTranslationUnitContext &CTU; |
45 | const MacroExpansionContext &MacroExpansions; |
46 | const bool SupportsCrossFileDiagnostics; |
47 | |
48 | void printBugPath(llvm::raw_ostream &o, const FIDMap &FM, |
49 | const PathPieces &Path); |
50 | |
51 | public: |
52 | PlistDiagnostics(PathDiagnosticConsumerOptions DiagOpts, |
53 | const std::string &OutputFile, const Preprocessor &PP, |
54 | const cross_tu::CrossTranslationUnitContext &CTU, |
55 | const MacroExpansionContext &MacroExpansions, |
56 | bool supportsMultipleFiles); |
57 | |
58 | ~PlistDiagnostics() override {} |
59 | |
60 | void FlushDiagnosticsImpl(std::vector<const PathDiagnostic *> &Diags, |
61 | FilesMade *filesMade) override; |
62 | |
63 | StringRef getName() const override { |
64 | return "PlistDiagnostics"; |
65 | } |
66 | |
67 | PathGenerationScheme getGenerationScheme() const override { |
68 | return Extensive; |
69 | } |
70 | bool supportsLogicalOpControlFlow() const override { return true; } |
71 | bool supportsCrossFileDiagnostics() const override { |
72 | return SupportsCrossFileDiagnostics; |
73 | } |
74 | }; |
75 | } // end anonymous namespace |
76 | |
77 | namespace { |
78 | |
79 | /// A helper class for emitting a single report. |
80 | class PlistPrinter { |
81 | const FIDMap& FM; |
82 | const Preprocessor &PP; |
83 | const cross_tu::CrossTranslationUnitContext &CTU; |
84 | const MacroExpansionContext &MacroExpansions; |
85 | llvm::SmallVector<const PathDiagnosticMacroPiece *, 0> MacroPieces; |
86 | |
87 | public: |
88 | PlistPrinter(const FIDMap &FM, const Preprocessor &PP, |
89 | const cross_tu::CrossTranslationUnitContext &CTU, |
90 | const MacroExpansionContext &MacroExpansions) |
91 | : FM(FM), PP(PP), CTU(CTU), MacroExpansions(MacroExpansions) {} |
92 | |
93 | void ReportDiag(raw_ostream &o, const PathDiagnosticPiece& P) { |
94 | ReportPiece(o, P, /*indent*/ 4, /*depth*/ 0, /*includeControlFlow*/ true); |
95 | } |
96 | |
97 | /// Print the expansions of the collected macro pieces. |
98 | /// |
99 | /// Each time ReportDiag is called on a PathDiagnosticMacroPiece (or, if one |
100 | /// is found through a call piece, etc), it's subpieces are reported, and the |
101 | /// piece itself is collected. Call this function after the entire bugpath |
102 | /// was reported. |
103 | void ReportMacroExpansions(raw_ostream &o, unsigned indent); |
104 | |
105 | private: |
106 | void ReportPiece(raw_ostream &o, const PathDiagnosticPiece &P, |
107 | unsigned indent, unsigned depth, bool includeControlFlow, |
108 | bool isKeyEvent = false) { |
109 | switch (P.getKind()) { |
110 | case PathDiagnosticPiece::ControlFlow: |
111 | if (includeControlFlow) |
112 | ReportControlFlow(o, P: cast<PathDiagnosticControlFlowPiece>(Val: P), indent); |
113 | break; |
114 | case PathDiagnosticPiece::Call: |
115 | ReportCall(o, P: cast<PathDiagnosticCallPiece>(Val: P), indent, |
116 | depth); |
117 | break; |
118 | case PathDiagnosticPiece::Event: |
119 | ReportEvent(o, P: cast<PathDiagnosticEventPiece>(Val: P), indent, depth, |
120 | isKeyEvent); |
121 | break; |
122 | case PathDiagnosticPiece::Macro: |
123 | ReportMacroSubPieces(o, P: cast<PathDiagnosticMacroPiece>(Val: P), indent, |
124 | depth); |
125 | break; |
126 | case PathDiagnosticPiece::Note: |
127 | ReportNote(o, P: cast<PathDiagnosticNotePiece>(Val: P), indent); |
128 | break; |
129 | case PathDiagnosticPiece::PopUp: |
130 | ReportPopUp(o, P: cast<PathDiagnosticPopUpPiece>(Val: P), indent); |
131 | break; |
132 | } |
133 | } |
134 | |
135 | void EmitRanges(raw_ostream &o, const ArrayRef<SourceRange> Ranges, |
136 | unsigned indent); |
137 | void EmitMessage(raw_ostream &o, StringRef Message, unsigned indent); |
138 | void EmitFixits(raw_ostream &o, ArrayRef<FixItHint> fixits, unsigned indent); |
139 | |
140 | void ReportControlFlow(raw_ostream &o, |
141 | const PathDiagnosticControlFlowPiece& P, |
142 | unsigned indent); |
143 | void ReportEvent(raw_ostream &o, const PathDiagnosticEventPiece& P, |
144 | unsigned indent, unsigned depth, bool isKeyEvent = false); |
145 | void ReportCall(raw_ostream &o, const PathDiagnosticCallPiece &P, |
146 | unsigned indent, unsigned depth); |
147 | void ReportMacroSubPieces(raw_ostream &o, const PathDiagnosticMacroPiece& P, |
148 | unsigned indent, unsigned depth); |
149 | void ReportNote(raw_ostream &o, const PathDiagnosticNotePiece& P, |
150 | unsigned indent); |
151 | |
152 | void ReportPopUp(raw_ostream &o, const PathDiagnosticPopUpPiece &P, |
153 | unsigned indent); |
154 | }; |
155 | |
156 | } // end of anonymous namespace |
157 | |
158 | /// Print coverage information to output stream @c o. |
159 | /// May modify the used list of files @c Fids by inserting new ones. |
160 | static void printCoverage(const PathDiagnostic *D, |
161 | unsigned InputIndentLevel, |
162 | SmallVectorImpl<FileID> &Fids, |
163 | FIDMap &FM, |
164 | llvm::raw_fd_ostream &o); |
165 | |
166 | static std::optional<StringRef> getExpandedMacro( |
167 | SourceLocation MacroLoc, const cross_tu::CrossTranslationUnitContext &CTU, |
168 | const MacroExpansionContext &MacroExpansions, const SourceManager &SM); |
169 | |
170 | //===----------------------------------------------------------------------===// |
171 | // Methods of PlistPrinter. |
172 | //===----------------------------------------------------------------------===// |
173 | |
174 | void PlistPrinter::EmitRanges(raw_ostream &o, |
175 | const ArrayRef<SourceRange> Ranges, |
176 | unsigned indent) { |
177 | |
178 | if (Ranges.empty()) |
179 | return; |
180 | |
181 | Indent(o, indent) << "<key>ranges</key>\n"; |
182 | Indent(o, indent) << "<array>\n"; |
183 | ++indent; |
184 | |
185 | const SourceManager &SM = PP.getSourceManager(); |
186 | const LangOptions &LangOpts = PP.getLangOpts(); |
187 | |
188 | for (auto &R : Ranges) |
189 | EmitRange(o, SM, |
190 | R: Lexer::getAsCharRange(Range: SM.getExpansionRange(Range: R), SM, LangOpts), |
191 | FM, indent: indent + 1); |
192 | --indent; |
193 | Indent(o, indent) << "</array>\n"; |
194 | } |
195 | |
196 | void PlistPrinter::EmitMessage(raw_ostream &o, StringRef Message, |
197 | unsigned indent) { |
198 | // Output the text. |
199 | assert(!Message.empty()); |
200 | Indent(o, indent) << "<key>extended_message</key>\n"; |
201 | Indent(o, indent); |
202 | EmitString(o, s: Message) << '\n'; |
203 | |
204 | // Output the short text. |
205 | // FIXME: Really use a short string. |
206 | Indent(o, indent) << "<key>message</key>\n"; |
207 | Indent(o, indent); |
208 | EmitString(o, s: Message) << '\n'; |
209 | } |
210 | |
211 | void PlistPrinter::EmitFixits(raw_ostream &o, ArrayRef<FixItHint> fixits, |
212 | unsigned indent) { |
213 | if (fixits.size() == 0) |
214 | return; |
215 | |
216 | const SourceManager &SM = PP.getSourceManager(); |
217 | const LangOptions &LangOpts = PP.getLangOpts(); |
218 | |
219 | Indent(o, indent) << "<key>fixits</key>\n"; |
220 | Indent(o, indent) << "<array>\n"; |
221 | for (const auto &fixit : fixits) { |
222 | assert(!fixit.isNull()); |
223 | // FIXME: Add support for InsertFromRange and BeforePreviousInsertion. |
224 | assert(!fixit.InsertFromRange.isValid() && "Not implemented yet!"); |
225 | assert(!fixit.BeforePreviousInsertions && "Not implemented yet!"); |
226 | Indent(o, indent) << " <dict>\n"; |
227 | Indent(o, indent) << " <key>remove_range</key>\n"; |
228 | EmitRange(o, SM, R: Lexer::getAsCharRange(Range: fixit.RemoveRange, SM, LangOpts), |
229 | FM, indent: indent + 2); |
230 | Indent(o, indent) << " <key>insert_string</key>"; |
231 | EmitString(o, s: fixit.CodeToInsert); |
232 | o << "\n"; |
233 | Indent(o, indent) << " </dict>\n"; |
234 | } |
235 | Indent(o, indent) << "</array>\n"; |
236 | } |
237 | |
238 | void PlistPrinter::ReportControlFlow(raw_ostream &o, |
239 | const PathDiagnosticControlFlowPiece& P, |
240 | unsigned indent) { |
241 | |
242 | const SourceManager &SM = PP.getSourceManager(); |
243 | const LangOptions &LangOpts = PP.getLangOpts(); |
244 | |
245 | Indent(o, indent) << "<dict>\n"; |
246 | ++indent; |
247 | |
248 | Indent(o, indent) << "<key>kind</key><string>control</string>\n"; |
249 | |
250 | // Emit edges. |
251 | Indent(o, indent) << "<key>edges</key>\n"; |
252 | ++indent; |
253 | Indent(o, indent) << "<array>\n"; |
254 | ++indent; |
255 | for (PathDiagnosticControlFlowPiece::const_iterator I=P.begin(), E=P.end(); |
256 | I!=E; ++I) { |
257 | Indent(o, indent) << "<dict>\n"; |
258 | ++indent; |
259 | |
260 | // Make the ranges of the start and end point self-consistent with adjacent edges |
261 | // by forcing to use only the beginning of the range. This simplifies the layout |
262 | // logic for clients. |
263 | Indent(o, indent) << "<key>start</key>\n"; |
264 | SourceRange StartEdge( |
265 | SM.getExpansionLoc(Loc: I->getStart().asRange().getBegin())); |
266 | EmitRange(o, SM, R: Lexer::getAsCharRange(Range: StartEdge, SM, LangOpts), FM, |
267 | indent: indent + 1); |
268 | |
269 | Indent(o, indent) << "<key>end</key>\n"; |
270 | SourceRange EndEdge(SM.getExpansionLoc(Loc: I->getEnd().asRange().getBegin())); |
271 | EmitRange(o, SM, R: Lexer::getAsCharRange(Range: EndEdge, SM, LangOpts), FM, |
272 | indent: indent + 1); |
273 | |
274 | --indent; |
275 | Indent(o, indent) << "</dict>\n"; |
276 | } |
277 | --indent; |
278 | Indent(o, indent) << "</array>\n"; |
279 | --indent; |
280 | |
281 | // Output any helper text. |
282 | const auto &s = P.getString(); |
283 | if (!s.empty()) { |
284 | Indent(o, indent) << "<key>alternate</key>"; |
285 | EmitString(o, s) << '\n'; |
286 | } |
287 | |
288 | assert(P.getFixits().size() == 0 && |
289 | "Fixits on constrol flow pieces are not implemented yet!"); |
290 | |
291 | --indent; |
292 | Indent(o, indent) << "</dict>\n"; |
293 | } |
294 | |
295 | void PlistPrinter::ReportEvent(raw_ostream &o, const PathDiagnosticEventPiece& P, |
296 | unsigned indent, unsigned depth, |
297 | bool isKeyEvent) { |
298 | |
299 | const SourceManager &SM = PP.getSourceManager(); |
300 | |
301 | Indent(o, indent) << "<dict>\n"; |
302 | ++indent; |
303 | |
304 | Indent(o, indent) << "<key>kind</key><string>event</string>\n"; |
305 | |
306 | if (isKeyEvent) { |
307 | Indent(o, indent) << "<key>key_event</key><true/>\n"; |
308 | } |
309 | |
310 | // Output the location. |
311 | FullSourceLoc L = P.getLocation().asLocation(); |
312 | |
313 | Indent(o, indent) << "<key>location</key>\n"; |
314 | EmitLocation(o, SM, L, FM, indent); |
315 | |
316 | // Output the ranges (if any). |
317 | ArrayRef<SourceRange> Ranges = P.getRanges(); |
318 | EmitRanges(o, Ranges, indent); |
319 | |
320 | // Output the call depth. |
321 | Indent(o, indent) << "<key>depth</key>"; |
322 | EmitInteger(o, value: depth) << '\n'; |
323 | |
324 | // Output the text. |
325 | EmitMessage(o, Message: P.getString(), indent); |
326 | |
327 | // Output the fixits. |
328 | EmitFixits(o, fixits: P.getFixits(), indent); |
329 | |
330 | // Finish up. |
331 | --indent; |
332 | Indent(o, indent); o << "</dict>\n"; |
333 | } |
334 | |
335 | void PlistPrinter::ReportCall(raw_ostream &o, const PathDiagnosticCallPiece &P, |
336 | unsigned indent, |
337 | unsigned depth) { |
338 | |
339 | if (auto callEnter = P.getCallEnterEvent()) |
340 | ReportPiece(o, P: *callEnter, indent, depth, /*includeControlFlow*/ true, |
341 | isKeyEvent: P.isLastInMainSourceFile()); |
342 | |
343 | |
344 | ++depth; |
345 | |
346 | if (auto callEnterWithinCaller = P.getCallEnterWithinCallerEvent()) |
347 | ReportPiece(o, P: *callEnterWithinCaller, indent, depth, |
348 | /*includeControlFlow*/ true); |
349 | |
350 | for (PathPieces::const_iterator I = P.path.begin(), E = P.path.end();I!=E;++I) |
351 | ReportPiece(o, P: **I, indent, depth, /*includeControlFlow*/ true); |
352 | |
353 | --depth; |
354 | |
355 | if (auto callExit = P.getCallExitEvent()) |
356 | ReportPiece(o, P: *callExit, indent, depth, /*includeControlFlow*/ true); |
357 | |
358 | assert(P.getFixits().size() == 0 && |
359 | "Fixits on call pieces are not implemented yet!"); |
360 | } |
361 | |
362 | void PlistPrinter::ReportMacroSubPieces(raw_ostream &o, |
363 | const PathDiagnosticMacroPiece& P, |
364 | unsigned indent, unsigned depth) { |
365 | MacroPieces.push_back(Elt: &P); |
366 | |
367 | for (const auto &SubPiece : P.subPieces) { |
368 | ReportPiece(o, P: *SubPiece, indent, depth, /*includeControlFlow*/ false); |
369 | } |
370 | |
371 | assert(P.getFixits().size() == 0 && |
372 | "Fixits on constrol flow pieces are not implemented yet!"); |
373 | } |
374 | |
375 | void PlistPrinter::ReportMacroExpansions(raw_ostream &o, unsigned indent) { |
376 | |
377 | for (const PathDiagnosticMacroPiece *P : MacroPieces) { |
378 | const SourceManager &SM = PP.getSourceManager(); |
379 | |
380 | SourceLocation MacroExpansionLoc = |
381 | P->getLocation().asLocation().getExpansionLoc(); |
382 | |
383 | const std::optional<StringRef> MacroName = |
384 | MacroExpansions.getOriginalText(MacroExpansionLoc); |
385 | const std::optional<StringRef> ExpansionText = |
386 | getExpandedMacro(MacroLoc: MacroExpansionLoc, CTU, MacroExpansions, SM); |
387 | |
388 | if (!MacroName || !ExpansionText) |
389 | continue; |
390 | |
391 | Indent(o, indent) << "<dict>\n"; |
392 | ++indent; |
393 | |
394 | // Output the location. |
395 | FullSourceLoc L = P->getLocation().asLocation(); |
396 | |
397 | Indent(o, indent) << "<key>location</key>\n"; |
398 | EmitLocation(o, SM, L, FM, indent); |
399 | |
400 | // Output the ranges (if any). |
401 | ArrayRef<SourceRange> Ranges = P->getRanges(); |
402 | EmitRanges(o, Ranges, indent); |
403 | |
404 | // Output the macro name. |
405 | Indent(o, indent) << "<key>name</key>"; |
406 | EmitString(o, s: *MacroName) << '\n'; |
407 | |
408 | // Output what it expands into. |
409 | Indent(o, indent) << "<key>expansion</key>"; |
410 | EmitString(o, s: *ExpansionText) << '\n'; |
411 | |
412 | // Finish up. |
413 | --indent; |
414 | Indent(o, indent); |
415 | o << "</dict>\n"; |
416 | } |
417 | } |
418 | |
419 | void PlistPrinter::ReportNote(raw_ostream &o, const PathDiagnosticNotePiece& P, |
420 | unsigned indent) { |
421 | |
422 | const SourceManager &SM = PP.getSourceManager(); |
423 | |
424 | Indent(o, indent) << "<dict>\n"; |
425 | ++indent; |
426 | |
427 | // Output the location. |
428 | FullSourceLoc L = P.getLocation().asLocation(); |
429 | |
430 | Indent(o, indent) << "<key>location</key>\n"; |
431 | EmitLocation(o, SM, L, FM, indent); |
432 | |
433 | // Output the ranges (if any). |
434 | ArrayRef<SourceRange> Ranges = P.getRanges(); |
435 | EmitRanges(o, Ranges, indent); |
436 | |
437 | // Output the text. |
438 | EmitMessage(o, Message: P.getString(), indent); |
439 | |
440 | // Output the fixits. |
441 | EmitFixits(o, fixits: P.getFixits(), indent); |
442 | |
443 | // Finish up. |
444 | --indent; |
445 | Indent(o, indent); o << "</dict>\n"; |
446 | } |
447 | |
448 | void PlistPrinter::ReportPopUp(raw_ostream &o, |
449 | const PathDiagnosticPopUpPiece &P, |
450 | unsigned indent) { |
451 | const SourceManager &SM = PP.getSourceManager(); |
452 | |
453 | Indent(o, indent) << "<dict>\n"; |
454 | ++indent; |
455 | |
456 | Indent(o, indent) << "<key>kind</key><string>pop-up</string>\n"; |
457 | |
458 | // Output the location. |
459 | FullSourceLoc L = P.getLocation().asLocation(); |
460 | |
461 | Indent(o, indent) << "<key>location</key>\n"; |
462 | EmitLocation(o, SM, L, FM, indent); |
463 | |
464 | // Output the ranges (if any). |
465 | ArrayRef<SourceRange> Ranges = P.getRanges(); |
466 | EmitRanges(o, Ranges, indent); |
467 | |
468 | // Output the text. |
469 | EmitMessage(o, Message: P.getString(), indent); |
470 | |
471 | assert(P.getFixits().size() == 0 && |
472 | "Fixits on pop-up pieces are not implemented yet!"); |
473 | |
474 | // Finish up. |
475 | --indent; |
476 | Indent(o, indent) << "</dict>\n"; |
477 | } |
478 | |
479 | //===----------------------------------------------------------------------===// |
480 | // Static function definitions. |
481 | //===----------------------------------------------------------------------===// |
482 | |
483 | /// Print coverage information to output stream @c o. |
484 | /// May modify the used list of files @c Fids by inserting new ones. |
485 | static void printCoverage(const PathDiagnostic *D, |
486 | unsigned InputIndentLevel, |
487 | SmallVectorImpl<FileID> &Fids, |
488 | FIDMap &FM, |
489 | llvm::raw_fd_ostream &o) { |
490 | unsigned IndentLevel = InputIndentLevel; |
491 | |
492 | Indent(o, indent: IndentLevel) << "<key>ExecutedLines</key>\n"; |
493 | Indent(o, indent: IndentLevel) << "<dict>\n"; |
494 | IndentLevel++; |
495 | |
496 | // Mapping from file IDs to executed lines. |
497 | const FilesToLineNumsMap &ExecutedLines = D->getExecutedLines(); |
498 | for (const auto &[FID, Lines] : ExecutedLines) { |
499 | unsigned FileKey = AddFID(FIDs&: FM, V&: Fids, FID); |
500 | Indent(o, indent: IndentLevel) << "<key>"<< FileKey << "</key>\n"; |
501 | Indent(o, indent: IndentLevel) << "<array>\n"; |
502 | IndentLevel++; |
503 | for (unsigned LineNo : Lines) { |
504 | Indent(o, indent: IndentLevel); |
505 | EmitInteger(o, value: LineNo) << "\n"; |
506 | } |
507 | IndentLevel--; |
508 | Indent(o, indent: IndentLevel) << "</array>\n"; |
509 | } |
510 | IndentLevel--; |
511 | Indent(o, indent: IndentLevel) << "</dict>\n"; |
512 | |
513 | assert(IndentLevel == InputIndentLevel); |
514 | } |
515 | |
516 | //===----------------------------------------------------------------------===// |
517 | // Methods of PlistDiagnostics. |
518 | //===----------------------------------------------------------------------===// |
519 | |
520 | PlistDiagnostics::PlistDiagnostics( |
521 | PathDiagnosticConsumerOptions DiagOpts, const std::string &output, |
522 | const Preprocessor &PP, const cross_tu::CrossTranslationUnitContext &CTU, |
523 | const MacroExpansionContext &MacroExpansions, bool supportsMultipleFiles) |
524 | : DiagOpts(std::move(DiagOpts)), OutputFile(output), PP(PP), CTU(CTU), |
525 | MacroExpansions(MacroExpansions), |
526 | SupportsCrossFileDiagnostics(supportsMultipleFiles) { |
527 | // FIXME: Will be used by a later planned change. |
528 | (void)this->CTU; |
529 | } |
530 | |
531 | void ento::createPlistDiagnosticConsumer( |
532 | PathDiagnosticConsumerOptions DiagOpts, PathDiagnosticConsumers &C, |
533 | const std::string &OutputFile, const Preprocessor &PP, |
534 | const cross_tu::CrossTranslationUnitContext &CTU, |
535 | const MacroExpansionContext &MacroExpansions) { |
536 | |
537 | // TODO: Emit an error here. |
538 | if (OutputFile.empty()) |
539 | return; |
540 | |
541 | C.push_back(x: std::make_unique<PlistDiagnostics>( |
542 | args&: DiagOpts, args: OutputFile, args: PP, args: CTU, args: MacroExpansions, |
543 | /*supportsMultipleFiles=*/args: false)); |
544 | createTextMinimalPathDiagnosticConsumer(Diagopts: std::move(DiagOpts), C, Prefix: OutputFile, |
545 | PP, CTU, MacroExpansions); |
546 | } |
547 | |
548 | void ento::createPlistMultiFileDiagnosticConsumer( |
549 | PathDiagnosticConsumerOptions DiagOpts, PathDiagnosticConsumers &C, |
550 | const std::string &OutputFile, const Preprocessor &PP, |
551 | const cross_tu::CrossTranslationUnitContext &CTU, |
552 | const MacroExpansionContext &MacroExpansions) { |
553 | |
554 | // TODO: Emit an error here. |
555 | if (OutputFile.empty()) |
556 | return; |
557 | |
558 | C.push_back(x: std::make_unique<PlistDiagnostics>( |
559 | args&: DiagOpts, args: OutputFile, args: PP, args: CTU, args: MacroExpansions, |
560 | /*supportsMultipleFiles=*/args: true)); |
561 | createTextMinimalPathDiagnosticConsumer(Diagopts: std::move(DiagOpts), C, Prefix: OutputFile, |
562 | PP, CTU, MacroExpansions); |
563 | } |
564 | |
565 | void PlistDiagnostics::printBugPath(llvm::raw_ostream &o, const FIDMap &FM, |
566 | const PathPieces &Path) { |
567 | PlistPrinter Printer(FM, PP, CTU, MacroExpansions); |
568 | assert(std::is_partitioned(Path.begin(), Path.end(), |
569 | [](const PathDiagnosticPieceRef &E) { |
570 | return E->getKind() == PathDiagnosticPiece::Note; |
571 | }) && |
572 | "PathDiagnostic is not partitioned so that notes precede the rest"); |
573 | |
574 | PathPieces::const_iterator FirstNonNote = |
575 | llvm::partition_point(Range: Path, P: [](const PathDiagnosticPieceRef &E) { |
576 | return E->getKind() == PathDiagnosticPiece::Note; |
577 | }); |
578 | |
579 | PathPieces::const_iterator I = Path.begin(); |
580 | |
581 | if (FirstNonNote != Path.begin()) { |
582 | o << " <key>notes</key>\n" |
583 | " <array>\n"; |
584 | |
585 | for (; I != FirstNonNote; ++I) |
586 | Printer.ReportDiag(o, P: **I); |
587 | |
588 | o << " </array>\n"; |
589 | } |
590 | |
591 | o << " <key>path</key>\n"; |
592 | |
593 | o << " <array>\n"; |
594 | |
595 | for (const auto &Piece : llvm::make_range(x: I, y: Path.end())) |
596 | Printer.ReportDiag(o, P: *Piece); |
597 | |
598 | o << " </array>\n"; |
599 | |
600 | if (!DiagOpts.ShouldDisplayMacroExpansions) |
601 | return; |
602 | |
603 | o << " <key>macro_expansions</key>\n" |
604 | " <array>\n"; |
605 | Printer.ReportMacroExpansions(o, /* indent */ 4); |
606 | o << " </array>\n"; |
607 | } |
608 | |
609 | void PlistDiagnostics::FlushDiagnosticsImpl( |
610 | std::vector<const PathDiagnostic *> &Diags, |
611 | FilesMade *filesMade) { |
612 | // Build up a set of FIDs that we use by scanning the locations and |
613 | // ranges of the diagnostics. |
614 | FIDMap FM; |
615 | SmallVector<FileID, 10> Fids; |
616 | const SourceManager& SM = PP.getSourceManager(); |
617 | const LangOptions &LangOpts = PP.getLangOpts(); |
618 | |
619 | auto AddPieceFID = [&FM, &Fids, &SM](const PathDiagnosticPiece &Piece) { |
620 | AddFID(FIDs&: FM, V&: Fids, SM, L: Piece.getLocation().asLocation()); |
621 | ArrayRef<SourceRange> Ranges = Piece.getRanges(); |
622 | for (const SourceRange &Range : Ranges) { |
623 | AddFID(FIDs&: FM, V&: Fids, SM, L: Range.getBegin()); |
624 | AddFID(FIDs&: FM, V&: Fids, SM, L: Range.getEnd()); |
625 | } |
626 | }; |
627 | |
628 | for (const PathDiagnostic *D : Diags) { |
629 | |
630 | SmallVector<const PathPieces *, 5> WorkList; |
631 | WorkList.push_back(Elt: &D->path); |
632 | |
633 | while (!WorkList.empty()) { |
634 | const PathPieces &Path = *WorkList.pop_back_val(); |
635 | |
636 | for (const auto &Iter : Path) { |
637 | const PathDiagnosticPiece &Piece = *Iter; |
638 | AddPieceFID(Piece); |
639 | |
640 | if (const PathDiagnosticCallPiece *Call = |
641 | dyn_cast<PathDiagnosticCallPiece>(Val: &Piece)) { |
642 | if (auto CallEnterWithin = Call->getCallEnterWithinCallerEvent()) |
643 | AddPieceFID(*CallEnterWithin); |
644 | |
645 | if (auto CallEnterEvent = Call->getCallEnterEvent()) |
646 | AddPieceFID(*CallEnterEvent); |
647 | |
648 | WorkList.push_back(Elt: &Call->path); |
649 | } else if (const PathDiagnosticMacroPiece *Macro = |
650 | dyn_cast<PathDiagnosticMacroPiece>(Val: &Piece)) { |
651 | WorkList.push_back(Elt: &Macro->subPieces); |
652 | } |
653 | } |
654 | } |
655 | } |
656 | |
657 | // Open the file. |
658 | std::error_code EC; |
659 | llvm::raw_fd_ostream o(OutputFile, EC, llvm::sys::fs::OF_TextWithCRLF); |
660 | if (EC) { |
661 | llvm::errs() << "warning: could not create file: "<< EC.message() << '\n'; |
662 | return; |
663 | } |
664 | |
665 | EmitPlistHeader(o); |
666 | |
667 | // Write the root object: a <dict> containing... |
668 | // - "clang_version", the string representation of clang version |
669 | // - "files", an <array> mapping from FIDs to file names |
670 | // - "diagnostics", an <array> containing the path diagnostics |
671 | o << "<dict>\n"<< |
672 | " <key>clang_version</key>\n"; |
673 | EmitString(o, s: getClangFullVersion()) << '\n'; |
674 | o << " <key>diagnostics</key>\n" |
675 | " <array>\n"; |
676 | |
677 | for (std::vector<const PathDiagnostic*>::iterator DI=Diags.begin(), |
678 | DE = Diags.end(); DI!=DE; ++DI) { |
679 | |
680 | o << " <dict>\n"; |
681 | |
682 | const PathDiagnostic *D = *DI; |
683 | printBugPath(o, FM, Path: D->path); |
684 | |
685 | // Output the bug type and bug category. |
686 | o << " <key>description</key>"; |
687 | EmitString(o, s: D->getShortDescription()) << '\n'; |
688 | o << " <key>category</key>"; |
689 | EmitString(o, s: D->getCategory()) << '\n'; |
690 | o << " <key>type</key>"; |
691 | EmitString(o, s: D->getBugType()) << '\n'; |
692 | o << " <key>check_name</key>"; |
693 | EmitString(o, s: D->getCheckerName()) << '\n'; |
694 | |
695 | o << " <!-- This hash is experimental and going to change! -->\n"; |
696 | o << " <key>issue_hash_content_of_line_in_context</key>"; |
697 | PathDiagnosticLocation UPDLoc = D->getUniqueingLoc(); |
698 | FullSourceLoc L(SM.getExpansionLoc(Loc: UPDLoc.isValid() |
699 | ? UPDLoc.asLocation() |
700 | : D->getLocation().asLocation()), |
701 | SM); |
702 | const Decl *DeclWithIssue = D->getDeclWithIssue(); |
703 | EmitString(o, s: getIssueHash(IssueLoc: L, CheckerName: D->getCheckerName(), WarningMessage: D->getBugType(), |
704 | IssueDecl: DeclWithIssue, LangOpts)) |
705 | << '\n'; |
706 | |
707 | // Output information about the semantic context where |
708 | // the issue occurred. |
709 | if (const Decl *DeclWithIssue = D->getDeclWithIssue()) { |
710 | // FIXME: handle blocks, which have no name. |
711 | if (const NamedDecl *ND = dyn_cast<NamedDecl>(Val: DeclWithIssue)) { |
712 | StringRef declKind; |
713 | switch (ND->getKind()) { |
714 | case Decl::CXXRecord: |
715 | declKind = "C++ class"; |
716 | break; |
717 | case Decl::CXXMethod: |
718 | declKind = "C++ method"; |
719 | break; |
720 | case Decl::ObjCMethod: |
721 | declKind = "Objective-C method"; |
722 | break; |
723 | case Decl::Function: |
724 | declKind = "function"; |
725 | break; |
726 | default: |
727 | break; |
728 | } |
729 | if (!declKind.empty()) { |
730 | const std::string &declName = ND->getDeclName().getAsString(); |
731 | o << " <key>issue_context_kind</key>"; |
732 | EmitString(o, s: declKind) << '\n'; |
733 | o << " <key>issue_context</key>"; |
734 | EmitString(o, s: declName) << '\n'; |
735 | } |
736 | |
737 | // Output the bug hash for issue unique-ing. Currently, it's just an |
738 | // offset from the beginning of the function. |
739 | if (const Stmt *Body = DeclWithIssue->getBody()) { |
740 | |
741 | // If the bug uniqueing location exists, use it for the hash. |
742 | // For example, this ensures that two leaks reported on the same line |
743 | // will have different issue_hashes and that the hash will identify |
744 | // the leak location even after code is added between the allocation |
745 | // site and the end of scope (leak report location). |
746 | if (UPDLoc.isValid()) { |
747 | FullSourceLoc UFunL( |
748 | SM.getExpansionLoc( |
749 | Loc: D->getUniqueingDecl()->getBody()->getBeginLoc()), |
750 | SM); |
751 | o << " <key>issue_hash_function_offset</key><string>" |
752 | << L.getExpansionLineNumber() - UFunL.getExpansionLineNumber() |
753 | << "</string>\n"; |
754 | |
755 | // Otherwise, use the location on which the bug is reported. |
756 | } else { |
757 | FullSourceLoc FunL(SM.getExpansionLoc(Loc: Body->getBeginLoc()), SM); |
758 | o << " <key>issue_hash_function_offset</key><string>" |
759 | << L.getExpansionLineNumber() - FunL.getExpansionLineNumber() |
760 | << "</string>\n"; |
761 | } |
762 | |
763 | } |
764 | } |
765 | } |
766 | |
767 | // Output the location of the bug. |
768 | o << " <key>location</key>\n"; |
769 | EmitLocation(o, SM, L: D->getLocation().asLocation(), FM, indent: 2); |
770 | |
771 | // Output the diagnostic to the sub-diagnostic client, if any. |
772 | if (!filesMade->empty()) { |
773 | StringRef lastName; |
774 | PDFileEntry::ConsumerFiles *files = filesMade->getFiles(PD: *D); |
775 | if (files) { |
776 | for (PDFileEntry::ConsumerFiles::const_iterator CI = files->begin(), |
777 | CE = files->end(); CI != CE; ++CI) { |
778 | StringRef newName = CI->first; |
779 | if (newName != lastName) { |
780 | if (!lastName.empty()) { |
781 | o << " </array>\n"; |
782 | } |
783 | lastName = newName; |
784 | o << " <key>"<< lastName << "_files</key>\n"; |
785 | o << " <array>\n"; |
786 | } |
787 | o << " <string>"<< CI->second << "</string>\n"; |
788 | } |
789 | o << " </array>\n"; |
790 | } |
791 | } |
792 | |
793 | printCoverage(D, /*IndentLevel=*/InputIndentLevel: 2, Fids, FM, o); |
794 | |
795 | // Close up the entry. |
796 | o << " </dict>\n"; |
797 | } |
798 | |
799 | o << " </array>\n"; |
800 | |
801 | o << " <key>files</key>\n" |
802 | " <array>\n"; |
803 | for (FileID FID : Fids) |
804 | EmitString(o&: o << " ", s: SM.getFileEntryRefForID(FID)->getName()) << '\n'; |
805 | o << " </array>\n"; |
806 | |
807 | if (llvm::AreStatisticsEnabled() && DiagOpts.ShouldSerializeStats) { |
808 | o << " <key>statistics</key>\n"; |
809 | std::string stats; |
810 | llvm::raw_string_ostream os(stats); |
811 | llvm::PrintStatisticsJSON(OS&: os); |
812 | EmitString(o, s: html::EscapeText(s: stats)) << '\n'; |
813 | } |
814 | |
815 | // Finish. |
816 | o << "</dict>\n</plist>\n"; |
817 | } |
818 | |
819 | //===----------------------------------------------------------------------===// |
820 | // Definitions of helper functions and methods for expanding macros. |
821 | //===----------------------------------------------------------------------===// |
822 | |
823 | static std::optional<StringRef> |
824 | getExpandedMacro(SourceLocation MacroExpansionLoc, |
825 | const cross_tu::CrossTranslationUnitContext &CTU, |
826 | const MacroExpansionContext &MacroExpansions, |
827 | const SourceManager &SM) { |
828 | if (auto CTUMacroExpCtx = |
829 | CTU.getMacroExpansionContextForSourceLocation(ToLoc: MacroExpansionLoc)) { |
830 | return CTUMacroExpCtx->getExpandedText(MacroExpansionLoc); |
831 | } |
832 | return MacroExpansions.getExpandedText(MacroExpansionLoc); |
833 | } |
834 |
Definitions
- PlistDiagnostics
- ~PlistDiagnostics
- getName
- getGenerationScheme
- supportsLogicalOpControlFlow
- supportsCrossFileDiagnostics
- PlistPrinter
- PlistPrinter
- ReportDiag
- ReportPiece
- EmitRanges
- EmitMessage
- EmitFixits
- ReportControlFlow
- ReportEvent
- ReportCall
- ReportMacroSubPieces
- ReportMacroExpansions
- ReportNote
- ReportPopUp
- printCoverage
- PlistDiagnostics
- createPlistDiagnosticConsumer
- createPlistMultiFileDiagnosticConsumer
- printBugPath
- FlushDiagnosticsImpl
Update your C++ knowledge – Modern C++11/14/17 Training
Find out more