1 //===--- PlistDiagnostics.cpp - Plist Diagnostics for Paths -----*- C++ -*-===//
2 //
3 //                     The LLVM Compiler Infrastructure
4 //
5 // This file is distributed under the University of Illinois Open Source
6 // License. See LICENSE.TXT for details.
7 //
8 //===----------------------------------------------------------------------===//
9 //
10 //  This file defines the PlistDiagnostics object.
11 //
12 //===----------------------------------------------------------------------===//
13 
14 #include "clang/Basic/FileManager.h"
15 #include "clang/Basic/PlistSupport.h"
16 #include "clang/Basic/SourceManager.h"
17 #include "clang/Basic/Version.h"
18 #include "clang/Lex/Preprocessor.h"
19 #include "clang/Rewrite/Core/HTMLRewrite.h"
20 #include "clang/StaticAnalyzer/Core/AnalyzerOptions.h"
21 #include "clang/StaticAnalyzer/Core/BugReporter/PathDiagnostic.h"
22 #include "clang/StaticAnalyzer/Core/IssueHash.h"
23 #include "clang/StaticAnalyzer/Core/PathDiagnosticConsumers.h"
24 #include "llvm/ADT/Statistic.h"
25 #include "llvm/ADT/SmallVector.h"
26 #include "llvm/Support/Casting.h"
27 using namespace clang;
28 using namespace ento;
29 using namespace markup;
30 
31 namespace {
32   class PlistDiagnostics : public PathDiagnosticConsumer {
33     const std::string OutputFile;
34     const Preprocessor &PP;
35     AnalyzerOptions &AnOpts;
36     const bool SupportsCrossFileDiagnostics;
37   public:
38     PlistDiagnostics(AnalyzerOptions &AnalyzerOpts,
39                      const std::string& prefix,
40                      const Preprocessor &PP,
41                      bool supportsMultipleFiles);
42 
43     ~PlistDiagnostics() override {}
44 
45     void FlushDiagnosticsImpl(std::vector<const PathDiagnostic *> &Diags,
46                               FilesMade *filesMade) override;
47 
48     StringRef getName() const override {
49       return "PlistDiagnostics";
50     }
51 
52     PathGenerationScheme getGenerationScheme() const override {
53       return Extensive;
54     }
55     bool supportsLogicalOpControlFlow() const override { return true; }
56     bool supportsCrossFileDiagnostics() const override {
57       return SupportsCrossFileDiagnostics;
58     }
59   };
60 } // end anonymous namespace
61 
62 PlistDiagnostics::PlistDiagnostics(AnalyzerOptions &AnalyzerOpts,
63                                    const std::string& output,
64                                    const Preprocessor &PP,
65                                    bool supportsMultipleFiles)
66   : OutputFile(output), PP(PP), AnOpts(AnalyzerOpts),
67     SupportsCrossFileDiagnostics(supportsMultipleFiles) {}
68 
69 void ento::createPlistDiagnosticConsumer(AnalyzerOptions &AnalyzerOpts,
70                                          PathDiagnosticConsumers &C,
71                                          const std::string& s,
72                                          const Preprocessor &PP) {
73   C.push_back(new PlistDiagnostics(AnalyzerOpts, s, PP,
74                                    /*supportsMultipleFiles*/ false));
75 }
76 
77 void ento::createPlistMultiFileDiagnosticConsumer(AnalyzerOptions &AnalyzerOpts,
78                                                   PathDiagnosticConsumers &C,
79                                                   const std::string &s,
80                                                   const Preprocessor &PP) {
81   C.push_back(new PlistDiagnostics(AnalyzerOpts, s, PP,
82                                    /*supportsMultipleFiles*/ true));
83 }
84 
85 static void EmitRanges(raw_ostream &o,
86                        const ArrayRef<SourceRange> Ranges,
87                        const FIDMap& FM,
88                        const Preprocessor &PP,
89                        unsigned indent) {
90 
91   if (Ranges.empty())
92     return;
93 
94   Indent(o, indent) << "<key>ranges</key>\n";
95   Indent(o, indent) << "<array>\n";
96   ++indent;
97 
98   const SourceManager &SM = PP.getSourceManager();
99   const LangOptions &LangOpts = PP.getLangOpts();
100 
101   for (auto &R : Ranges)
102     EmitRange(o, SM,
103               Lexer::getAsCharRange(SM.getExpansionRange(R), SM, LangOpts),
104               FM, indent + 1);
105   --indent;
106   Indent(o, indent) << "</array>\n";
107 }
108 
109 static void EmitMessage(raw_ostream &o, StringRef Message, unsigned indent) {
110   // Output the text.
111   assert(!Message.empty());
112   Indent(o, indent) << "<key>extended_message</key>\n";
113   Indent(o, indent);
114   EmitString(o, Message) << '\n';
115 
116   // Output the short text.
117   // FIXME: Really use a short string.
118   Indent(o, indent) << "<key>message</key>\n";
119   Indent(o, indent);
120   EmitString(o, Message) << '\n';
121 }
122 
123 static void ReportControlFlow(raw_ostream &o,
124                               const PathDiagnosticControlFlowPiece& P,
125                               const FIDMap& FM,
126                               const Preprocessor &PP,
127                               unsigned indent) {
128 
129   const SourceManager &SM = PP.getSourceManager();
130   const LangOptions &LangOpts = PP.getLangOpts();
131 
132   Indent(o, indent) << "<dict>\n";
133   ++indent;
134 
135   Indent(o, indent) << "<key>kind</key><string>control</string>\n";
136 
137   // Emit edges.
138   Indent(o, indent) << "<key>edges</key>\n";
139   ++indent;
140   Indent(o, indent) << "<array>\n";
141   ++indent;
142   for (PathDiagnosticControlFlowPiece::const_iterator I=P.begin(), E=P.end();
143        I!=E; ++I) {
144     Indent(o, indent) << "<dict>\n";
145     ++indent;
146 
147     // Make the ranges of the start and end point self-consistent with adjacent edges
148     // by forcing to use only the beginning of the range.  This simplifies the layout
149     // logic for clients.
150     Indent(o, indent) << "<key>start</key>\n";
151     SourceRange StartEdge(
152         SM.getExpansionLoc(I->getStart().asRange().getBegin()));
153     EmitRange(o, SM, Lexer::getAsCharRange(StartEdge, SM, LangOpts), FM,
154               indent + 1);
155 
156     Indent(o, indent) << "<key>end</key>\n";
157     SourceRange EndEdge(SM.getExpansionLoc(I->getEnd().asRange().getBegin()));
158     EmitRange(o, SM, Lexer::getAsCharRange(EndEdge, SM, LangOpts), FM,
159               indent + 1);
160 
161     --indent;
162     Indent(o, indent) << "</dict>\n";
163   }
164   --indent;
165   Indent(o, indent) << "</array>\n";
166   --indent;
167 
168   // Output any helper text.
169   const auto &s = P.getString();
170   if (!s.empty()) {
171     Indent(o, indent) << "<key>alternate</key>";
172     EmitString(o, s) << '\n';
173   }
174 
175   --indent;
176   Indent(o, indent) << "</dict>\n";
177 }
178 
179 static void ReportEvent(raw_ostream &o,
180                         const PathDiagnosticEventPiece& P,
181                         const FIDMap& FM,
182                         const Preprocessor &PP,
183                         unsigned indent,
184                         unsigned depth,
185                         bool isKeyEvent = false) {
186 
187   const SourceManager &SM = PP.getSourceManager();
188 
189   Indent(o, indent) << "<dict>\n";
190   ++indent;
191 
192   Indent(o, indent) << "<key>kind</key><string>event</string>\n";
193 
194   if (isKeyEvent) {
195     Indent(o, indent) << "<key>key_event</key><true/>\n";
196   }
197 
198   // Output the location.
199   FullSourceLoc L = P.getLocation().asLocation();
200 
201   Indent(o, indent) << "<key>location</key>\n";
202   EmitLocation(o, SM, L, FM, indent);
203 
204   // Output the ranges (if any).
205   ArrayRef<SourceRange> Ranges = P.getRanges();
206   EmitRanges(o, Ranges, FM, PP, indent);
207 
208   // Output the call depth.
209   Indent(o, indent) << "<key>depth</key>";
210   EmitInteger(o, depth) << '\n';
211 
212   // Output the text.
213   EmitMessage(o, P.getString(), indent);
214 
215   // Finish up.
216   --indent;
217   Indent(o, indent); o << "</dict>\n";
218 }
219 
220 static void ReportPiece(raw_ostream &o,
221                         const PathDiagnosticPiece &P,
222                         const FIDMap& FM,
223                         const Preprocessor &PP,
224                         AnalyzerOptions &AnOpts,
225                         unsigned indent,
226                         unsigned depth,
227                         bool includeControlFlow,
228                         bool isKeyEvent = false);
229 
230 static void ReportCall(raw_ostream &o,
231                        const PathDiagnosticCallPiece &P,
232                        const FIDMap& FM,
233                        const Preprocessor &PP,
234                        AnalyzerOptions &AnOpts,
235                        unsigned indent,
236                        unsigned depth) {
237 
238   if (auto callEnter = P.getCallEnterEvent())
239     ReportPiece(o, *callEnter, FM, PP, AnOpts, indent, depth,
240                 /*includeControlFlow*/ true, P.isLastInMainSourceFile());
241 
242 
243   ++depth;
244 
245   if (auto callEnterWithinCaller = P.getCallEnterWithinCallerEvent())
246     ReportPiece(o, *callEnterWithinCaller, FM, PP, AnOpts, indent, depth,
247                 /*includeControlFlow*/ true);
248 
249   for (PathPieces::const_iterator I = P.path.begin(), E = P.path.end();I!=E;++I)
250     ReportPiece(o, **I, FM, PP, AnOpts, indent, depth,
251                 /*includeControlFlow*/ true);
252 
253   --depth;
254 
255   if (auto callExit = P.getCallExitEvent())
256     ReportPiece(o, *callExit, FM, PP, AnOpts, indent, depth,
257                 /*includeControlFlow*/ true);
258 }
259 
260 static void ReportMacro(raw_ostream &o,
261                         const PathDiagnosticMacroPiece& P,
262                         const FIDMap& FM,
263                         const Preprocessor &PP,
264                         AnalyzerOptions &AnOpts,
265                         unsigned indent,
266                         unsigned depth) {
267 
268   for (PathPieces::const_iterator I = P.subPieces.begin(), E=P.subPieces.end();
269        I!=E; ++I) {
270     ReportPiece(o, **I, FM, PP, AnOpts, indent, depth,
271                 /*includeControlFlow*/ false);
272   }
273 }
274 
275 static void ReportNote(raw_ostream &o, const PathDiagnosticNotePiece& P,
276                         const FIDMap& FM,
277                         const Preprocessor &PP,
278                         unsigned indent) {
279 
280   const SourceManager &SM = PP.getSourceManager();
281 
282   Indent(o, indent) << "<dict>\n";
283   ++indent;
284 
285   // Output the location.
286   FullSourceLoc L = P.getLocation().asLocation();
287 
288   Indent(o, indent) << "<key>location</key>\n";
289   EmitLocation(o, SM, L, FM, indent);
290 
291   // Output the ranges (if any).
292   ArrayRef<SourceRange> Ranges = P.getRanges();
293   EmitRanges(o, Ranges, FM, PP, indent);
294 
295   // Output the text.
296   EmitMessage(o, P.getString(), indent);
297 
298   // Finish up.
299   --indent;
300   Indent(o, indent); o << "</dict>\n";
301 }
302 
303 static void ReportDiag(raw_ostream &o,
304                        const PathDiagnosticPiece& P,
305                        const FIDMap& FM,
306                        const Preprocessor &PP,
307                        AnalyzerOptions &AnOpts) {
308   ReportPiece(o, P, FM, PP, AnOpts, /*indent*/ 4, /*depth*/ 0,
309               /*includeControlFlow*/ true);
310 }
311 
312 static void ReportPiece(raw_ostream &o,
313                         const PathDiagnosticPiece &P,
314                         const FIDMap& FM,
315                         const Preprocessor &PP,
316                         AnalyzerOptions &AnOpts,
317                         unsigned indent,
318                         unsigned depth,
319                         bool includeControlFlow,
320                         bool isKeyEvent) {
321   switch (P.getKind()) {
322     case PathDiagnosticPiece::ControlFlow:
323       if (includeControlFlow)
324         ReportControlFlow(o, cast<PathDiagnosticControlFlowPiece>(P), FM, PP,
325                           indent);
326       break;
327     case PathDiagnosticPiece::Call:
328       ReportCall(o, cast<PathDiagnosticCallPiece>(P), FM, PP, AnOpts, indent,
329                  depth);
330       break;
331     case PathDiagnosticPiece::Event:
332       ReportEvent(o, cast<PathDiagnosticEventPiece>(P), FM, PP, indent, depth,
333                   isKeyEvent);
334       break;
335     case PathDiagnosticPiece::Macro:
336       ReportMacro(o, cast<PathDiagnosticMacroPiece>(P), FM, PP, AnOpts, indent,
337                   depth);
338       break;
339     case PathDiagnosticPiece::Note:
340       ReportNote(o, cast<PathDiagnosticNotePiece>(P), FM, PP, indent);
341       break;
342   }
343 }
344 
345 /// Print coverage information to output stream {@code o}.
346 /// May modify the used list of files {@code Fids} by inserting new ones.
347 static void printCoverage(const PathDiagnostic *D,
348                           unsigned InputIndentLevel,
349                           SmallVectorImpl<FileID> &Fids,
350                           FIDMap &FM,
351                           llvm::raw_fd_ostream &o) {
352   unsigned IndentLevel = InputIndentLevel;
353 
354   Indent(o, IndentLevel) << "<key>ExecutedLines</key>\n";
355   Indent(o, IndentLevel) << "<dict>\n";
356   IndentLevel++;
357 
358   // Mapping from file IDs to executed lines.
359   const FilesToLineNumsMap &ExecutedLines = D->getExecutedLines();
360   for (auto I = ExecutedLines.begin(), E = ExecutedLines.end(); I != E; ++I) {
361     unsigned FileKey = AddFID(FM, Fids, I->first);
362     Indent(o, IndentLevel) << "<key>" << FileKey << "</key>\n";
363     Indent(o, IndentLevel) << "<array>\n";
364     IndentLevel++;
365     for (unsigned LineNo : I->second) {
366       Indent(o, IndentLevel);
367       EmitInteger(o, LineNo) << "\n";
368     }
369     IndentLevel--;
370     Indent(o, IndentLevel) << "</array>\n";
371   }
372   IndentLevel--;
373   Indent(o, IndentLevel) << "</dict>\n";
374 
375   assert(IndentLevel == InputIndentLevel);
376 }
377 
378 void PlistDiagnostics::FlushDiagnosticsImpl(
379                                     std::vector<const PathDiagnostic *> &Diags,
380                                     FilesMade *filesMade) {
381   // Build up a set of FIDs that we use by scanning the locations and
382   // ranges of the diagnostics.
383   FIDMap FM;
384   SmallVector<FileID, 10> Fids;
385   const SourceManager& SM = PP.getSourceManager();
386   const LangOptions &LangOpts = PP.getLangOpts();
387 
388   auto AddPieceFID = [&FM, &Fids, &SM](const PathDiagnosticPiece &Piece) {
389     AddFID(FM, Fids, SM, Piece.getLocation().asLocation());
390     ArrayRef<SourceRange> Ranges = Piece.getRanges();
391     for (const SourceRange &Range : Ranges) {
392       AddFID(FM, Fids, SM, Range.getBegin());
393       AddFID(FM, Fids, SM, Range.getEnd());
394     }
395   };
396 
397   for (const PathDiagnostic *D : Diags) {
398 
399     SmallVector<const PathPieces *, 5> WorkList;
400     WorkList.push_back(&D->path);
401 
402     while (!WorkList.empty()) {
403       const PathPieces &Path = *WorkList.pop_back_val();
404 
405       for (const auto &Iter : Path) {
406         const PathDiagnosticPiece &Piece = *Iter;
407         AddPieceFID(Piece);
408 
409         if (const PathDiagnosticCallPiece *Call =
410                 dyn_cast<PathDiagnosticCallPiece>(&Piece)) {
411           if (auto CallEnterWithin = Call->getCallEnterWithinCallerEvent())
412             AddPieceFID(*CallEnterWithin);
413 
414           if (auto CallEnterEvent = Call->getCallEnterEvent())
415             AddPieceFID(*CallEnterEvent);
416 
417           WorkList.push_back(&Call->path);
418         } else if (const PathDiagnosticMacroPiece *Macro =
419                        dyn_cast<PathDiagnosticMacroPiece>(&Piece)) {
420           WorkList.push_back(&Macro->subPieces);
421         }
422       }
423     }
424   }
425 
426   // Open the file.
427   std::error_code EC;
428   llvm::raw_fd_ostream o(OutputFile, EC, llvm::sys::fs::F_Text);
429   if (EC) {
430     llvm::errs() << "warning: could not create file: " << EC.message() << '\n';
431     return;
432   }
433 
434   EmitPlistHeader(o);
435 
436   // Write the root object: a <dict> containing...
437   //  - "clang_version", the string representation of clang version
438   //  - "files", an <array> mapping from FIDs to file names
439   //  - "diagnostics", an <array> containing the path diagnostics
440   o << "<dict>\n" <<
441        " <key>clang_version</key>\n";
442   EmitString(o, getClangFullVersion()) << '\n';
443   o << " <key>diagnostics</key>\n"
444        " <array>\n";
445 
446   for (std::vector<const PathDiagnostic*>::iterator DI=Diags.begin(),
447        DE = Diags.end(); DI!=DE; ++DI) {
448 
449     o << "  <dict>\n";
450 
451     const PathDiagnostic *D = *DI;
452     const PathPieces &Path = D->path;
453 
454     assert(std::is_partitioned(
455              Path.begin(), Path.end(),
456              [](const std::shared_ptr<PathDiagnosticPiece> &E)
457                { return E->getKind() == PathDiagnosticPiece::Note; }) &&
458            "PathDiagnostic is not partitioned so that notes precede the rest");
459 
460     PathPieces::const_iterator FirstNonNote = std::partition_point(
461         Path.begin(), Path.end(),
462         [](const std::shared_ptr<PathDiagnosticPiece> &E)
463           { return E->getKind() == PathDiagnosticPiece::Note; });
464 
465     PathPieces::const_iterator I = Path.begin();
466 
467     if (FirstNonNote != Path.begin()) {
468       o << "   <key>notes</key>\n"
469            "   <array>\n";
470 
471       for (; I != FirstNonNote; ++I)
472         ReportDiag(o, **I, FM, PP, AnOpts);
473 
474       o << "   </array>\n";
475     }
476 
477     o << "   <key>path</key>\n";
478 
479     o << "   <array>\n";
480 
481     for (PathPieces::const_iterator E = Path.end(); I != E; ++I)
482       ReportDiag(o, **I, FM, PP, AnOpts);
483 
484     o << "   </array>\n";
485 
486     // Output the bug type and bug category.
487     o << "   <key>description</key>";
488     EmitString(o, D->getShortDescription()) << '\n';
489     o << "   <key>category</key>";
490     EmitString(o, D->getCategory()) << '\n';
491     o << "   <key>type</key>";
492     EmitString(o, D->getBugType()) << '\n';
493     o << "   <key>check_name</key>";
494     EmitString(o, D->getCheckName()) << '\n';
495 
496     o << "   <!-- This hash is experimental and going to change! -->\n";
497     o << "   <key>issue_hash_content_of_line_in_context</key>";
498     PathDiagnosticLocation UPDLoc = D->getUniqueingLoc();
499     FullSourceLoc L(SM.getExpansionLoc(UPDLoc.isValid()
500                                             ? UPDLoc.asLocation()
501                                             : D->getLocation().asLocation()),
502                     SM);
503     const Decl *DeclWithIssue = D->getDeclWithIssue();
504     EmitString(o, GetIssueHash(SM, L, D->getCheckName(), D->getBugType(),
505                                DeclWithIssue, LangOpts))
506         << '\n';
507 
508     // Output information about the semantic context where
509     // the issue occurred.
510     if (const Decl *DeclWithIssue = D->getDeclWithIssue()) {
511       // FIXME: handle blocks, which have no name.
512       if (const NamedDecl *ND = dyn_cast<NamedDecl>(DeclWithIssue)) {
513         StringRef declKind;
514         switch (ND->getKind()) {
515           case Decl::CXXRecord:
516             declKind = "C++ class";
517             break;
518           case Decl::CXXMethod:
519             declKind = "C++ method";
520             break;
521           case Decl::ObjCMethod:
522             declKind = "Objective-C method";
523             break;
524           case Decl::Function:
525             declKind = "function";
526             break;
527           default:
528             break;
529         }
530         if (!declKind.empty()) {
531           const std::string &declName = ND->getDeclName().getAsString();
532           o << "  <key>issue_context_kind</key>";
533           EmitString(o, declKind) << '\n';
534           o << "  <key>issue_context</key>";
535           EmitString(o, declName) << '\n';
536         }
537 
538         // Output the bug hash for issue unique-ing. Currently, it's just an
539         // offset from the beginning of the function.
540         if (const Stmt *Body = DeclWithIssue->getBody()) {
541 
542           // If the bug uniqueing location exists, use it for the hash.
543           // For example, this ensures that two leaks reported on the same line
544           // will have different issue_hashes and that the hash will identify
545           // the leak location even after code is added between the allocation
546           // site and the end of scope (leak report location).
547           if (UPDLoc.isValid()) {
548             FullSourceLoc UFunL(
549                 SM.getExpansionLoc(
550                     D->getUniqueingDecl()->getBody()->getBeginLoc()),
551                 SM);
552             o << "  <key>issue_hash_function_offset</key><string>"
553               << L.getExpansionLineNumber() - UFunL.getExpansionLineNumber()
554               << "</string>\n";
555 
556           // Otherwise, use the location on which the bug is reported.
557           } else {
558             FullSourceLoc FunL(SM.getExpansionLoc(Body->getBeginLoc()), SM);
559             o << "  <key>issue_hash_function_offset</key><string>"
560               << L.getExpansionLineNumber() - FunL.getExpansionLineNumber()
561               << "</string>\n";
562           }
563 
564         }
565       }
566     }
567 
568     // Output the location of the bug.
569     o << "  <key>location</key>\n";
570     EmitLocation(o, SM, D->getLocation().asLocation(), FM, 2);
571 
572     // Output the diagnostic to the sub-diagnostic client, if any.
573     if (!filesMade->empty()) {
574       StringRef lastName;
575       PDFileEntry::ConsumerFiles *files = filesMade->getFiles(*D);
576       if (files) {
577         for (PDFileEntry::ConsumerFiles::const_iterator CI = files->begin(),
578                 CE = files->end(); CI != CE; ++CI) {
579           StringRef newName = CI->first;
580           if (newName != lastName) {
581             if (!lastName.empty()) {
582               o << "  </array>\n";
583             }
584             lastName = newName;
585             o <<  "  <key>" << lastName << "_files</key>\n";
586             o << "  <array>\n";
587           }
588           o << "   <string>" << CI->second << "</string>\n";
589         }
590         o << "  </array>\n";
591       }
592     }
593 
594     printCoverage(D, /*IndentLevel=*/2, Fids, FM, o);
595 
596     // Close up the entry.
597     o << "  </dict>\n";
598   }
599 
600   o << " </array>\n";
601 
602   o << " <key>files</key>\n"
603        " <array>\n";
604   for (FileID FID : Fids)
605     EmitString(o << "  ", SM.getFileEntryForID(FID)->getName()) << '\n';
606   o << " </array>\n";
607 
608   if (llvm::AreStatisticsEnabled() && AnOpts.shouldSerializeStats()) {
609     o << " <key>statistics</key>\n";
610     std::string stats;
611     llvm::raw_string_ostream os(stats);
612     llvm::PrintStatisticsJSON(os);
613     os.flush();
614     EmitString(o, html::EscapeText(stats)) << '\n';
615   }
616 
617   // Finish.
618   o << "</dict>\n</plist>";
619 }
620