-
Notifications
You must be signed in to change notification settings - Fork 10.4k
/
Copy pathDiagnosticConsumer.cpp
279 lines (248 loc) · 11 KB
/
DiagnosticConsumer.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
//===--- DiagnosticConsumer.cpp - Diagnostic Consumer Impl ----------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
//
// This file implements the DiagnosticConsumer class.
//
//===----------------------------------------------------------------------===//
#define DEBUG_TYPE "swift-ast"
#include "swift/AST/DiagnosticConsumer.h"
#include "swift/AST/DiagnosticEngine.h"
#include "swift/AST/DiagnosticsFrontend.h"
#include "swift/Basic/Assertions.h"
#include "swift/Basic/Defer.h"
#include "swift/Basic/Range.h"
#include "swift/Basic/SourceManager.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/ADT/StringSet.h"
#include "llvm/Support/Debug.h"
#include "llvm/Support/raw_ostream.h"
using namespace swift;
DiagnosticConsumer::~DiagnosticConsumer() = default;
DiagnosticInfo::FixIt::FixIt(CharSourceRange R, StringRef Str,
ArrayRef<DiagnosticArgument> Args) : Range(R) {
// FIXME: Defer text formatting to later in the pipeline.
llvm::raw_string_ostream OS(Text);
DiagnosticEngine::formatDiagnosticText(OS, Str, Args,
DiagnosticFormatOptions::
formatForFixIts());
}
llvm::SMLoc DiagnosticConsumer::getRawLoc(SourceLoc loc) {
return loc.Value;
}
LLVM_ATTRIBUTE_UNUSED
static bool hasDuplicateFileNames(
ArrayRef<FileSpecificDiagnosticConsumer::Subconsumer> subconsumers) {
llvm::StringSet<> seenFiles;
for (const auto &subconsumer : subconsumers) {
if (subconsumer.getInputFileName().empty()) {
// We can handle multiple subconsumers that aren't associated with any
// file, because they only collect diagnostics that aren't in any of the
// special files. This isn't an important use case to support, but also
// SmallSet doesn't handle empty strings anyway!
continue;
}
bool isUnique = seenFiles.insert(subconsumer.getInputFileName()).second;
if (!isUnique)
return true;
}
return false;
}
std::unique_ptr<DiagnosticConsumer>
FileSpecificDiagnosticConsumer::consolidateSubconsumers(
SmallVectorImpl<Subconsumer> &subconsumers) {
if (subconsumers.empty())
return nullptr;
if (subconsumers.size() == 1)
return std::move(subconsumers.front()).consumer;
// Cannot use return
// std::make_unique<FileSpecificDiagnosticConsumer>(subconsumers); because
// the constructor is private.
return std::unique_ptr<DiagnosticConsumer>(
new FileSpecificDiagnosticConsumer(subconsumers));
}
FileSpecificDiagnosticConsumer::FileSpecificDiagnosticConsumer(
SmallVectorImpl<Subconsumer> &subconsumers)
: Subconsumers(std::move(subconsumers)) {
assert(!Subconsumers.empty() &&
"don't waste time handling diagnostics that will never get emitted");
assert(!hasDuplicateFileNames(Subconsumers) &&
"having multiple subconsumers for the same file is not implemented");
}
void FileSpecificDiagnosticConsumer::computeConsumersOrderedByRange(
SourceManager &SM) {
// Look up each file's source range and add it to the "map" (to be sorted).
for (const unsigned subconsumerIndex: indices(Subconsumers)) {
const Subconsumer &subconsumer = Subconsumers[subconsumerIndex];
if (subconsumer.getInputFileName().empty())
continue;
std::optional<unsigned> bufferID =
SM.getIDForBufferIdentifier(subconsumer.getInputFileName());
assert(bufferID.has_value() && "consumer registered for unknown file");
CharSourceRange range = SM.getRangeForBuffer(bufferID.value());
ConsumersOrderedByRange.emplace_back(
ConsumerAndRange(range, subconsumerIndex));
}
// Sort the "map" by buffer /end/ location, for use with std::lower_bound
// later. (Sorting by start location would produce the same sort, since the
// ranges must not be overlapping, but since we need to check end locations
// later it's consistent to sort by that here.)
std::sort(ConsumersOrderedByRange.begin(), ConsumersOrderedByRange.end());
// Check that the ranges are non-overlapping. If the files really are all
// distinct, this should be trivially true, but if it's ever not we might end
// up mis-filing diagnostics.
assert(ConsumersOrderedByRange.end() ==
std::adjacent_find(ConsumersOrderedByRange.begin(),
ConsumersOrderedByRange.end(),
[](const ConsumerAndRange &left,
const ConsumerAndRange &right) {
return left.overlaps(right);
}) &&
"overlapping ranges despite having distinct files");
}
std::optional<FileSpecificDiagnosticConsumer::Subconsumer *>
FileSpecificDiagnosticConsumer::subconsumerForLocation(SourceManager &SM,
SourceLoc loc) {
// Diagnostics with invalid locations always go to every consumer.
if (loc.isInvalid())
return std::nullopt;
// What if a there's a FileSpecificDiagnosticConsumer but there are no
// subconsumers in it? (This situation occurs for the fix-its
// FileSpecificDiagnosticConsumer.) In such a case, bail out now.
if (Subconsumers.empty())
return std::nullopt;
// This map is generated on first use and cached, to allow the
// FileSpecificDiagnosticConsumer to be set up before the source files are
// actually loaded.
if (ConsumersOrderedByRange.empty()) {
// It's possible to get here while a bridging header PCH is being
// attached-to, if there's some sort of AST-reader warning or error, which
// happens before CompilerInstance::setUpInputs(), at which point _no_
// source buffers are loaded in yet. In that case we return None, rather
// than trying to build a nonsensical map (and actually crashing since we
// can't find buffers for the inputs).
assert(!Subconsumers.empty());
if (!SM.getIDForBufferIdentifier(Subconsumers.begin()->getInputFileName())
.has_value()) {
assert(llvm::none_of(Subconsumers, [&](const Subconsumer &subconsumer) {
return SM.getIDForBufferIdentifier(subconsumer.getInputFileName())
.has_value();
}));
return std::nullopt;
}
auto *mutableThis = const_cast<FileSpecificDiagnosticConsumer*>(this);
mutableThis->computeConsumersOrderedByRange(SM);
}
// This std::lower_bound call is doing a binary search for the first range
// that /might/ contain 'loc'. Specifically, since the ranges are sorted
// by end location, it's looking for the first range where the end location
// is greater than or equal to 'loc'.
const ConsumerAndRange *possiblyContainingRangeIter = std::lower_bound(
ConsumersOrderedByRange.begin(), ConsumersOrderedByRange.end(), loc,
[](const ConsumerAndRange &entry, SourceLoc loc) -> bool {
return entry.endsAfter(loc);
});
if (possiblyContainingRangeIter != ConsumersOrderedByRange.end() &&
possiblyContainingRangeIter->contains(loc)) {
auto *consumerAndRangeForLocation =
const_cast<ConsumerAndRange *>(possiblyContainingRangeIter);
return &(*this)[*consumerAndRangeForLocation];
}
return std::nullopt;
}
void FileSpecificDiagnosticConsumer::handleDiagnostic(
SourceManager &SM, const DiagnosticInfo &Info) {
HasAnErrorBeenConsumed |= Info.Kind == DiagnosticKind::Error;
auto subconsumer = findSubconsumer(SM, Info);
if (subconsumer) {
subconsumer.value()->handleDiagnostic(SM, Info);
return;
}
// Last resort: spray it everywhere
for (auto &subconsumer : Subconsumers)
subconsumer.handleDiagnostic(SM, Info);
}
std::optional<FileSpecificDiagnosticConsumer::Subconsumer *>
FileSpecificDiagnosticConsumer::findSubconsumer(SourceManager &SM,
const DiagnosticInfo &Info) {
// Ensure that a note goes to the same place as the preceding non-note.
switch (Info.Kind) {
case DiagnosticKind::Error:
case DiagnosticKind::Warning:
case DiagnosticKind::Remark: {
auto subconsumer = findSubconsumerForNonNote(SM, Info);
SubconsumerForSubsequentNotes = subconsumer;
return subconsumer;
}
case DiagnosticKind::Note:
return SubconsumerForSubsequentNotes;
}
llvm_unreachable("covered switch");
}
std::optional<FileSpecificDiagnosticConsumer::Subconsumer *>
FileSpecificDiagnosticConsumer::findSubconsumerForNonNote(
SourceManager &SM, const DiagnosticInfo &Info) {
const auto subconsumer = subconsumerForLocation(SM, Info.Loc);
if (!subconsumer)
return std::nullopt; // No place to put it; might be in an imported module
if ((*subconsumer)->getConsumer())
return subconsumer; // A primary file with a .dia file
// Try to put it in the responsible primary input
if (Info.BufferIndirectlyCausingDiagnostic.isInvalid())
return std::nullopt;
const auto currentPrimarySubconsumer =
subconsumerForLocation(SM, Info.BufferIndirectlyCausingDiagnostic);
assert(!currentPrimarySubconsumer ||
(*currentPrimarySubconsumer)->getConsumer() &&
"current primary must have a .dia file");
return currentPrimarySubconsumer;
}
bool FileSpecificDiagnosticConsumer::finishProcessing() {
tellSubconsumersToInformDriverOfIncompleteBatchModeCompilation();
// Deliberately don't use std::any_of here because we don't want early-exit
// behavior.
bool hadError = false;
for (auto &subconsumer : Subconsumers)
hadError |= subconsumer.getConsumer() &&
subconsumer.getConsumer()->finishProcessing();
return hadError;
}
void FileSpecificDiagnosticConsumer::
tellSubconsumersToInformDriverOfIncompleteBatchModeCompilation() {
if (!HasAnErrorBeenConsumed)
return;
for (auto &info : ConsumersOrderedByRange)
(*this)[info].informDriverOfIncompleteBatchModeCompilation();
}
void NullDiagnosticConsumer::handleDiagnostic(SourceManager &SM,
const DiagnosticInfo &Info) {
LLVM_DEBUG({
llvm::dbgs() << "NullDiagnosticConsumer received diagnostic: ";
DiagnosticEngine::formatDiagnosticText(llvm::dbgs(), Info.FormatString,
Info.FormatArgs);
llvm::dbgs() << "\n";
});
}
ForwardingDiagnosticConsumer::ForwardingDiagnosticConsumer(DiagnosticEngine &Target)
: TargetEngine(Target) {}
void ForwardingDiagnosticConsumer::handleDiagnostic(
SourceManager &SM, const DiagnosticInfo &Info) {
LLVM_DEBUG({
llvm::dbgs() << "ForwardingDiagnosticConsumer received diagnostic: ";
DiagnosticEngine::formatDiagnosticText(llvm::dbgs(), Info.FormatString,
Info.FormatArgs);
llvm::dbgs() << "\n";
});
for (auto *C : TargetEngine.getConsumers()) {
C->handleDiagnostic(SM, Info);
}
}