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