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