1 | //===--- LogDiagnosticPrinter.cpp - Log Diagnostic Printer ----------------===// |
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 | #include "clang/Frontend/LogDiagnosticPrinter.h" |
10 | #include "clang/Basic/DiagnosticOptions.h" |
11 | #include "clang/Basic/PlistSupport.h" |
12 | #include "clang/Basic/SourceManager.h" |
13 | #include "llvm/Support/ErrorHandling.h" |
14 | #include "llvm/Support/raw_ostream.h" |
15 | using namespace clang; |
16 | using namespace markup; |
17 | |
18 | LogDiagnosticPrinter::LogDiagnosticPrinter( |
19 | raw_ostream &os, DiagnosticOptions &, |
20 | std::unique_ptr<raw_ostream> StreamOwner) |
21 | : OS(os), StreamOwner(std::move(StreamOwner)), LangOpts(nullptr) {} |
22 | |
23 | static StringRef getLevelName(DiagnosticsEngine::Level Level) { |
24 | switch (Level) { |
25 | case DiagnosticsEngine::Ignored: return "ignored" ; |
26 | case DiagnosticsEngine::Remark: return "remark" ; |
27 | case DiagnosticsEngine::Note: return "note" ; |
28 | case DiagnosticsEngine::Warning: return "warning" ; |
29 | case DiagnosticsEngine::Error: return "error" ; |
30 | case DiagnosticsEngine::Fatal: return "fatal error" ; |
31 | } |
32 | llvm_unreachable("Invalid DiagnosticsEngine level!" ); |
33 | } |
34 | |
35 | void |
36 | LogDiagnosticPrinter::EmitDiagEntry(llvm::raw_ostream &OS, |
37 | const LogDiagnosticPrinter::DiagEntry &DE) { |
38 | OS << " <dict>\n" ; |
39 | OS << " <key>level</key>\n" |
40 | << " " ; |
41 | EmitString(o&: OS, s: getLevelName(Level: DE.DiagnosticLevel)) << '\n'; |
42 | if (!DE.Filename.empty()) { |
43 | OS << " <key>filename</key>\n" |
44 | << " " ; |
45 | EmitString(o&: OS, s: DE.Filename) << '\n'; |
46 | } |
47 | if (DE.Line != 0) { |
48 | OS << " <key>line</key>\n" |
49 | << " " ; |
50 | EmitInteger(o&: OS, value: DE.Line) << '\n'; |
51 | } |
52 | if (DE.Column != 0) { |
53 | OS << " <key>column</key>\n" |
54 | << " " ; |
55 | EmitInteger(o&: OS, value: DE.Column) << '\n'; |
56 | } |
57 | if (!DE.Message.empty()) { |
58 | OS << " <key>message</key>\n" |
59 | << " " ; |
60 | EmitString(o&: OS, s: DE.Message) << '\n'; |
61 | } |
62 | OS << " <key>ID</key>\n" |
63 | << " " ; |
64 | EmitInteger(o&: OS, value: DE.DiagnosticID) << '\n'; |
65 | if (!DE.WarningOption.empty()) { |
66 | OS << " <key>WarningOption</key>\n" |
67 | << " " ; |
68 | EmitString(o&: OS, s: DE.WarningOption) << '\n'; |
69 | } |
70 | OS << " </dict>\n" ; |
71 | } |
72 | |
73 | void LogDiagnosticPrinter::EndSourceFile() { |
74 | // We emit all the diagnostics in EndSourceFile. However, we don't emit any |
75 | // entry if no diagnostics were present. |
76 | // |
77 | // Note that DiagnosticConsumer has no "end-of-compilation" callback, so we |
78 | // will miss any diagnostics which are emitted after and outside the |
79 | // translation unit processing. |
80 | if (Entries.empty()) |
81 | return; |
82 | |
83 | // Write to a temporary string to ensure atomic write of diagnostic object. |
84 | SmallString<512> Msg; |
85 | llvm::raw_svector_ostream OS(Msg); |
86 | |
87 | OS << "<dict>\n" ; |
88 | if (!MainFilename.empty()) { |
89 | OS << " <key>main-file</key>\n" |
90 | << " " ; |
91 | EmitString(o&: OS, s: MainFilename) << '\n'; |
92 | } |
93 | if (!DwarfDebugFlags.empty()) { |
94 | OS << " <key>dwarf-debug-flags</key>\n" |
95 | << " " ; |
96 | EmitString(o&: OS, s: DwarfDebugFlags) << '\n'; |
97 | } |
98 | OS << " <key>diagnostics</key>\n" ; |
99 | OS << " <array>\n" ; |
100 | for (auto &DE : Entries) |
101 | EmitDiagEntry(OS, DE); |
102 | OS << " </array>\n" ; |
103 | OS << "</dict>\n" ; |
104 | |
105 | this->OS << OS.str(); |
106 | } |
107 | |
108 | void LogDiagnosticPrinter::HandleDiagnostic(DiagnosticsEngine::Level Level, |
109 | const Diagnostic &Info) { |
110 | // Default implementation (Warnings/errors count). |
111 | DiagnosticConsumer::HandleDiagnostic(DiagLevel: Level, Info); |
112 | |
113 | // Initialize the main file name, if we haven't already fetched it. |
114 | if (MainFilename.empty() && Info.hasSourceManager()) { |
115 | const SourceManager &SM = Info.getSourceManager(); |
116 | FileID FID = SM.getMainFileID(); |
117 | if (FID.isValid()) { |
118 | if (OptionalFileEntryRef FE = SM.getFileEntryRefForID(FID)) |
119 | MainFilename = std::string(FE->getName()); |
120 | } |
121 | } |
122 | |
123 | // Create the diag entry. |
124 | DiagEntry DE; |
125 | DE.DiagnosticID = Info.getID(); |
126 | DE.DiagnosticLevel = Level; |
127 | |
128 | DE.WarningOption = |
129 | std::string(Info.getDiags()->getDiagnosticIDs()->getWarningOptionForDiag( |
130 | DiagID: DE.DiagnosticID)); |
131 | |
132 | // Format the message. |
133 | SmallString<100> MessageStr; |
134 | Info.FormatDiagnostic(OutStr&: MessageStr); |
135 | DE.Message = std::string(MessageStr); |
136 | |
137 | // Set the location information. |
138 | DE.Filename = "" ; |
139 | DE.Line = DE.Column = 0; |
140 | if (Info.getLocation().isValid() && Info.hasSourceManager()) { |
141 | const SourceManager &SM = Info.getSourceManager(); |
142 | PresumedLoc PLoc = SM.getPresumedLoc(Loc: Info.getLocation()); |
143 | |
144 | if (PLoc.isInvalid()) { |
145 | // At least print the file name if available: |
146 | FileID FID = SM.getFileID(SpellingLoc: Info.getLocation()); |
147 | if (FID.isValid()) { |
148 | if (OptionalFileEntryRef FE = SM.getFileEntryRefForID(FID)) |
149 | DE.Filename = std::string(FE->getName()); |
150 | } |
151 | } else { |
152 | DE.Filename = PLoc.getFilename(); |
153 | DE.Line = PLoc.getLine(); |
154 | DE.Column = PLoc.getColumn(); |
155 | } |
156 | } |
157 | |
158 | // Record the diagnostic entry. |
159 | Entries.push_back(Elt: DE); |
160 | } |
161 | |