Skip to content

Commit 0cd182d

Browse files
authored
Merge pull request #14900 from jrose-apple/mux
Wire up FileSpecificDiagnosticConsumer for serialized diagnostics
2 parents a16d8a7 + de74836 commit 0cd182d

12 files changed

+869
-27
lines changed

include/swift/AST/DiagnosticConsumer.h

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
#ifndef SWIFT_BASIC_DIAGNOSTICCONSUMER_H
2020
#define SWIFT_BASIC_DIAGNOSTICCONSUMER_H
2121

22+
#include "swift/Basic/LLVM.h"
2223
#include "swift/Basic/SourceLoc.h"
2324
#include "llvm/Support/SourceMgr.h"
2425

@@ -111,6 +112,75 @@ class NullDiagnosticConsumer : public DiagnosticConsumer {
111112
ArrayRef<DiagnosticArgument> FormatArgs,
112113
const DiagnosticInfo &Info) override;
113114
};
115+
116+
/// \brief DiagnosticConsumer that funnels diagnostics in certain files to
117+
/// particular sub-consumers.
118+
///
119+
/// The intended use case for such a consumer is "batch mode" compilations,
120+
/// where we want to record diagnostics for each file as if they were compiled
121+
/// separately. This is important for incremental builds, so that if a file has
122+
/// warnings but doesn't get recompiled in the next build, the warnings persist.
123+
///
124+
/// Diagnostics that are not in one of the special files are emitted into every
125+
/// sub-consumer. This is necessary to deal with, for example, diagnostics in a
126+
/// bridging header imported from Objective-C, which isn't really about the
127+
/// current file.
128+
class FileSpecificDiagnosticConsumer : public DiagnosticConsumer {
129+
public:
130+
/// A diagnostic consumer, along with the name of the buffer that it should
131+
/// be associated with. An empty string means that a consumer is not
132+
/// associated with any particular buffer, and should only receive diagnostics
133+
/// that are not in any of the other consumers' files.
134+
using ConsumerPair =
135+
std::pair<std::string, std::unique_ptr<DiagnosticConsumer>>;
136+
137+
private:
138+
/// All consumers owned by this FileSpecificDiagnosticConsumer.
139+
const SmallVector<ConsumerPair, 4> SubConsumers;
140+
141+
using ConsumersOrderedByRangeEntry =
142+
std::pair<CharSourceRange, DiagnosticConsumer *>;
143+
144+
/// The consumers owned by this FileSpecificDiagnosticConsumer, sorted by
145+
/// the end locations of each file so that a lookup by position can be done
146+
/// using binary search.
147+
///
148+
/// Generated and cached when the first diagnostic with a location is emitted.
149+
/// This allows diagnostics to be emitted before files are actually opened,
150+
/// as long as they don't have source locations.
151+
///
152+
/// \see #consumerForLocation
153+
SmallVector<ConsumersOrderedByRangeEntry, 4> ConsumersOrderedByRange;
154+
155+
/// Indicates which consumer to send Note diagnostics too.
156+
///
157+
/// Notes are always considered attached to the error, warning, or remark
158+
/// that was most recently emitted.
159+
///
160+
/// If null, Note diagnostics are sent to every consumer.
161+
DiagnosticConsumer *ConsumerForSubsequentNotes = nullptr;
162+
163+
public:
164+
/// Takes ownership of the DiagnosticConsumers specified in \p consumers.
165+
///
166+
/// There must not be two consumers for the same file (i.e., having the same
167+
/// buffer name).
168+
explicit FileSpecificDiagnosticConsumer(
169+
SmallVectorImpl<ConsumerPair> &consumers);
170+
171+
void handleDiagnostic(SourceManager &SM, SourceLoc Loc,
172+
DiagnosticKind Kind,
173+
StringRef FormatString,
174+
ArrayRef<DiagnosticArgument> FormatArgs,
175+
const DiagnosticInfo &Info) override;
176+
177+
bool finishProcessing() override;
178+
179+
private:
180+
void computeConsumersOrderedByRange(SourceManager &SM);
181+
DiagnosticConsumer *consumerForLocation(SourceManager &SM,
182+
SourceLoc loc) const;
183+
};
114184

115185
} // end namespace swift
116186

include/swift/Frontend/InputFile.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,10 @@ class InputFile {
9191
std::string loadedModuleTracePath() const {
9292
return getPrimarySpecificPaths().SupplementaryOutputs.LoadedModuleTracePath;
9393
}
94+
std::string serializedDiagnosticsPath() const {
95+
return getPrimarySpecificPaths().SupplementaryOutputs
96+
.SerializedDiagnosticsPath;
97+
}
9498
};
9599
} // namespace swift
96100

include/swift/Frontend/SerializedDiagnosticConsumer.h

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,13 +29,14 @@ namespace swift {
2929
class DiagnosticConsumer;
3030

3131
namespace serialized_diagnostics {
32-
/// \brief Create a DiagnosticConsumer that serializes diagnostics to a
33-
/// file.
32+
/// Create a DiagnosticConsumer that serializes diagnostics to a file, using
33+
/// Clang's serialized diagnostics format.
3434
///
35-
/// \param serializedDiagnosticsPath the file path to write the diagnostics.
35+
/// \param outputPath the file path to write the diagnostics to.
3636
///
3737
/// \returns A new diagnostic consumer that serializes diagnostics.
38-
DiagnosticConsumer *createConsumer(llvm::StringRef serializedDiagnosticsPath);
38+
std::unique_ptr<DiagnosticConsumer>
39+
createConsumer(llvm::StringRef outputPath);
3940
}
4041
}
4142

lib/AST/DiagnosticConsumer.cpp

Lines changed: 159 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,172 @@
1414
//
1515
//===----------------------------------------------------------------------===//
1616

17-
#define DEBUG_TYPE "swift-basic"
17+
#define DEBUG_TYPE "swift-ast"
1818
#include "swift/AST/DiagnosticConsumer.h"
1919
#include "swift/AST/DiagnosticEngine.h"
20+
#include "swift/Basic/Defer.h"
21+
#include "swift/Basic/SourceManager.h"
2022
#include "llvm/ADT/StringRef.h"
23+
#include "llvm/ADT/StringSet.h"
2124
#include "llvm/Support/Debug.h"
2225
#include "llvm/Support/raw_ostream.h"
2326
using namespace swift;
2427

25-
DiagnosticConsumer::~DiagnosticConsumer() { }
28+
DiagnosticConsumer::~DiagnosticConsumer() = default;
29+
30+
llvm::SMLoc DiagnosticConsumer::getRawLoc(SourceLoc loc) {
31+
return loc.Value;
32+
}
33+
34+
LLVM_ATTRIBUTE_UNUSED
35+
static bool hasDuplicateFileNames(
36+
ArrayRef<FileSpecificDiagnosticConsumer::ConsumerPair> consumers) {
37+
llvm::StringSet<> seenFiles;
38+
for (const auto &consumerPair : consumers) {
39+
if (consumerPair.first.empty()) {
40+
// We can handle multiple consumers that aren't associated with any file,
41+
// because they only collect diagnostics that aren't in any of the special
42+
// files. This isn't an important use case to support, but also SmallSet
43+
// doesn't handle empty strings anyway!
44+
continue;
45+
}
46+
47+
bool isUnique = seenFiles.insert(consumerPair.first).second;
48+
if (!isUnique)
49+
return true;
50+
}
51+
return false;
52+
}
53+
54+
FileSpecificDiagnosticConsumer::FileSpecificDiagnosticConsumer(
55+
SmallVectorImpl<ConsumerPair> &consumers)
56+
: SubConsumers(std::move(consumers)) {
57+
assert(!SubConsumers.empty() &&
58+
"don't waste time handling diagnostics that will never get emitted");
59+
assert(!hasDuplicateFileNames(SubConsumers) &&
60+
"having multiple consumers for the same file is not implemented");
61+
}
62+
63+
void FileSpecificDiagnosticConsumer::computeConsumersOrderedByRange(
64+
SourceManager &SM) {
65+
// Look up each file's source range and add it to the "map" (to be sorted).
66+
for (const ConsumerPair &pair : SubConsumers) {
67+
if (pair.first.empty())
68+
continue;
69+
70+
Optional<unsigned> bufferID = SM.getIDForBufferIdentifier(pair.first);
71+
assert(bufferID.hasValue() && "consumer registered for unknown file");
72+
CharSourceRange range = SM.getRangeForBuffer(bufferID.getValue());
73+
ConsumersOrderedByRange.emplace_back(range, pair.second.get());
74+
}
75+
76+
// Sort the "map" by buffer /end/ location, for use with std::lower_bound
77+
// later. (Sorting by start location would produce the same sort, since the
78+
// ranges must not be overlapping, but since we need to check end locations
79+
// later it's consistent to sort by that here.)
80+
std::sort(ConsumersOrderedByRange.begin(), ConsumersOrderedByRange.end(),
81+
[](const ConsumersOrderedByRangeEntry &left,
82+
const ConsumersOrderedByRangeEntry &right) -> bool {
83+
auto compare = std::less<const char *>();
84+
return compare(getRawLoc(left.first.getEnd()).getPointer(),
85+
getRawLoc(right.first.getEnd()).getPointer());
86+
});
87+
88+
// Check that the ranges are non-overlapping. If the files really are all
89+
// distinct, this should be trivially true, but if it's ever not we might end
90+
// up mis-filing diagnostics.
91+
assert(ConsumersOrderedByRange.end() ==
92+
std::adjacent_find(ConsumersOrderedByRange.begin(),
93+
ConsumersOrderedByRange.end(),
94+
[](const ConsumersOrderedByRangeEntry &left,
95+
const ConsumersOrderedByRangeEntry &right) {
96+
return left.first.overlaps(right.first);
97+
}) &&
98+
"overlapping ranges despite having distinct files");
99+
}
100+
101+
DiagnosticConsumer *
102+
FileSpecificDiagnosticConsumer::consumerForLocation(SourceManager &SM,
103+
SourceLoc loc) const {
104+
// If there's only one consumer, we'll use it no matter what, because...
105+
// - ...all diagnostics within the file will go to that consumer.
106+
// - ...all diagnostics not within the file will not be claimed by any
107+
// consumer, and so will go to all (one) consumers.
108+
if (SubConsumers.size() == 1)
109+
return SubConsumers.front().second.get();
110+
111+
// Diagnostics with invalid locations always go to every consumer.
112+
if (loc.isInvalid())
113+
return nullptr;
114+
115+
// This map is generated on first use and cached, to allow the
116+
// FileSpecificDiagnosticConsumer to be set up before the source files are
117+
// actually loaded.
118+
if (ConsumersOrderedByRange.empty()) {
119+
auto *mutableThis = const_cast<FileSpecificDiagnosticConsumer*>(this);
120+
mutableThis->computeConsumersOrderedByRange(SM);
121+
}
122+
123+
// This std::lower_bound call is doing a binary search for the first range
124+
// that /might/ contain 'loc'. Specifically, since the ranges are sorted
125+
// by end location, it's looking for the first range where the end location
126+
// is greater than or equal to 'loc'.
127+
auto possiblyContainingRangeIter =
128+
std::lower_bound(ConsumersOrderedByRange.begin(),
129+
ConsumersOrderedByRange.end(),
130+
loc,
131+
[](const ConsumersOrderedByRangeEntry &entry,
132+
SourceLoc loc) -> bool {
133+
auto compare = std::less<const char *>();
134+
return compare(getRawLoc(entry.first.getEnd()).getPointer(),
135+
getRawLoc(loc).getPointer());
136+
});
137+
138+
if (possiblyContainingRangeIter != ConsumersOrderedByRange.end() &&
139+
possiblyContainingRangeIter->first.contains(loc)) {
140+
return possiblyContainingRangeIter->second;
141+
}
142+
143+
return nullptr;
144+
}
145+
146+
void FileSpecificDiagnosticConsumer::handleDiagnostic(
147+
SourceManager &SM, SourceLoc Loc, DiagnosticKind Kind,
148+
StringRef FormatString, ArrayRef<DiagnosticArgument> FormatArgs,
149+
const DiagnosticInfo &Info) {
150+
151+
DiagnosticConsumer *specificConsumer;
152+
switch (Kind) {
153+
case DiagnosticKind::Error:
154+
case DiagnosticKind::Warning:
155+
case DiagnosticKind::Remark:
156+
specificConsumer = consumerForLocation(SM, Loc);
157+
ConsumerForSubsequentNotes = specificConsumer;
158+
break;
159+
case DiagnosticKind::Note:
160+
specificConsumer = ConsumerForSubsequentNotes;
161+
break;
162+
}
163+
164+
if (specificConsumer) {
165+
specificConsumer->handleDiagnostic(SM, Loc, Kind, FormatString, FormatArgs,
166+
Info);
167+
} else {
168+
for (auto &subConsumer : SubConsumers) {
169+
subConsumer.second->handleDiagnostic(SM, Loc, Kind, FormatString,
170+
FormatArgs, Info);
171+
}
172+
}
173+
}
174+
175+
bool FileSpecificDiagnosticConsumer::finishProcessing() {
176+
// Deliberately don't use std::any_of here because we don't want early-exit
177+
// behavior.
178+
bool hadError = false;
179+
for (auto &subConsumer : SubConsumers)
180+
hadError |= subConsumer.second->finishProcessing();
181+
return hadError;
182+
}
26183

27184
void NullDiagnosticConsumer::handleDiagnostic(
28185
SourceManager &SM, SourceLoc Loc, DiagnosticKind Kind,

lib/Frontend/PrintingDiagnosticConsumer.cpp

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -63,10 +63,6 @@ namespace {
6363
};
6464
} // end anonymous namespace
6565

66-
llvm::SMLoc DiagnosticConsumer::getRawLoc(SourceLoc loc) {
67-
return loc.Value;
68-
}
69-
7066
void PrintingDiagnosticConsumer::handleDiagnostic(
7167
SourceManager &SM, SourceLoc Loc, DiagnosticKind Kind,
7268
StringRef FormatString, ArrayRef<DiagnosticArgument> FormatArgs,

lib/Frontend/SerializedDiagnosticConsumer.cpp

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -222,9 +222,10 @@ class SerializedDiagnosticConsumer : public DiagnosticConsumer {
222222
};
223223
} // end anonymous namespace
224224

225-
namespace swift { namespace serialized_diagnostics {
226-
DiagnosticConsumer *createConsumer(StringRef serializedDiagnosticsPath) {
227-
return new SerializedDiagnosticConsumer(serializedDiagnosticsPath);
225+
namespace swift {
226+
namespace serialized_diagnostics {
227+
std::unique_ptr<DiagnosticConsumer> createConsumer(StringRef outputPath) {
228+
return llvm::make_unique<SerializedDiagnosticConsumer>(outputPath);
228229
}
229230
} // namespace serialized_diagnostics
230231
} // namespace swift

0 commit comments

Comments
 (0)