1//===--- TextDiagnosticPrinter.cpp - 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// This diagnostic client prints out their diagnostic messages.
10//
11//===----------------------------------------------------------------------===//
12
13#include "clang/Frontend/TextDiagnosticPrinter.h"
14#include "clang/Basic/DiagnosticOptions.h"
15#include "clang/Basic/SourceManager.h"
16#include "clang/Frontend/TextDiagnostic.h"
17#include "clang/Lex/Lexer.h"
18#include "llvm/ADT/SmallString.h"
19#include "llvm/Support/ErrorHandling.h"
20#include "llvm/Support/raw_ostream.h"
21#include <algorithm>
22using namespace clang;
23
24TextDiagnosticPrinter::TextDiagnosticPrinter(raw_ostream &os,
25                                             DiagnosticOptions *diags,
26                                             bool _OwnsOutputStream)
27  : OS(os), DiagOpts(diags),
28    OwnsOutputStream(_OwnsOutputStream) {
29}
30
31TextDiagnosticPrinter::~TextDiagnosticPrinter() {
32  if (OwnsOutputStream)
33    delete &OS;
34}
35
36void TextDiagnosticPrinter::BeginSourceFile(const LangOptions &LO,
37                                            const Preprocessor *PP) {
38  // Build the TextDiagnostic utility.
39  TextDiag.reset(new TextDiagnostic(OS, LO, &*DiagOpts));
40}
41
42void TextDiagnosticPrinter::EndSourceFile() {
43  TextDiag.reset();
44}
45
46/// Print any diagnostic option information to a raw_ostream.
47///
48/// This implements all of the logic for adding diagnostic options to a message
49/// (via OS). Each relevant option is comma separated and all are enclosed in
50/// the standard bracketing: " [...]".
51static void printDiagnosticOptions(raw_ostream &OS,
52                                   DiagnosticsEngine::Level Level,
53                                   const Diagnostic &Info,
54                                   const DiagnosticOptions &DiagOpts) {
55  bool Started = false;
56  if (DiagOpts.ShowOptionNames) {
57    // Handle special cases for non-warnings early.
58    if (Info.getID() == diag::fatal_too_many_errors) {
59      OS << " [-ferror-limit=]";
60      return;
61    }
62
63    // The code below is somewhat fragile because we are essentially trying to
64    // report to the user what happened by inferring what the diagnostic engine
65    // did. Eventually it might make more sense to have the diagnostic engine
66    // include some "why" information in the diagnostic.
67
68    // If this is a warning which has been mapped to an error by the user (as
69    // inferred by checking whether the default mapping is to an error) then
70    // flag it as such. Note that diagnostics could also have been mapped by a
71    // pragma, but we don't currently have a way to distinguish this.
72    if (Level == DiagnosticsEngine::Error &&
73        DiagnosticIDs::isBuiltinWarningOrExtension(Info.getID()) &&
74        !DiagnosticIDs::isDefaultMappingAsError(Info.getID())) {
75      OS << " [-Werror";
76      Started = true;
77    }
78
79    StringRef Opt = DiagnosticIDs::getWarningOptionForDiag(Info.getID());
80    if (!Opt.empty()) {
81      OS << (Started ? "," : " [")
82         << (Level == DiagnosticsEngine::Remark ? "-R" : "-W") << Opt;
83      StringRef OptValue = Info.getDiags()->getFlagValue();
84      if (!OptValue.empty())
85        OS << "=" << OptValue;
86      Started = true;
87    }
88  }
89
90  // If the user wants to see category information, include it too.
91  if (DiagOpts.ShowCategories) {
92    unsigned DiagCategory =
93      DiagnosticIDs::getCategoryNumberForDiag(Info.getID());
94    if (DiagCategory) {
95      OS << (Started ? "," : " [");
96      Started = true;
97      if (DiagOpts.ShowCategories == 1)
98        OS << DiagCategory;
99      else {
100        assert(DiagOpts.ShowCategories == 2 && "Invalid ShowCategories value");
101        OS << DiagnosticIDs::getCategoryNameFromID(DiagCategory);
102      }
103    }
104  }
105  if (Started)
106    OS << ']';
107}
108
109void TextDiagnosticPrinter::HandleDiagnostic(DiagnosticsEngine::Level Level,
110                                             const Diagnostic &Info) {
111  // Default implementation (Warnings/errors count).
112  DiagnosticConsumer::HandleDiagnostic(Level, Info);
113
114  // Render the diagnostic message into a temporary buffer eagerly. We'll use
115  // this later as we print out the diagnostic to the terminal.
116  SmallString<100> OutStr;
117  Info.FormatDiagnostic(OutStr);
118
119  llvm::raw_svector_ostream DiagMessageStream(OutStr);
120  printDiagnosticOptions(DiagMessageStream, Level, Info, *DiagOpts);
121
122  // Keeps track of the starting position of the location
123  // information (e.g., "foo.c:10:4:") that precedes the error
124  // message. We use this information to determine how long the
125  // file+line+column number prefix is.
126  uint64_t StartOfLocationInfo = OS.tell();
127
128  if (!Prefix.empty())
129    OS << Prefix << ": ";
130
131  // Use a dedicated, simpler path for diagnostics without a valid location.
132  // This is important as if the location is missing, we may be emitting
133  // diagnostics in a context that lacks language options, a source manager, or
134  // other infrastructure necessary when emitting more rich diagnostics.
135  if (!Info.getLocation().isValid()) {
136    TextDiagnostic::printDiagnosticLevel(OS, Level, DiagOpts->ShowColors,
137                                         DiagOpts->CLFallbackMode);
138    TextDiagnostic::printDiagnosticMessage(OS, Level, DiagMessageStream.str(),
139                                           OS.tell() - StartOfLocationInfo,
140                                           DiagOpts->MessageLength,
141                                           DiagOpts->ShowColors);
142    OS.flush();
143    return;
144  }
145
146  // Assert that the rest of our infrastructure is setup properly.
147  assert(DiagOpts && "Unexpected diagnostic without options set");
148  assert(Info.hasSourceManager() &&
149         "Unexpected diagnostic with no source manager");
150  assert(TextDiag && "Unexpected diagnostic outside source file processing");
151
152  TextDiag->emitDiagnostic(
153      FullSourceLoc(Info.getLocation(), Info.getSourceManager()), Level,
154      DiagMessageStream.str(), Info.getRanges(), Info.getFixItHints());
155
156  OS.flush();
157}
158