blob: bf1982eacdc433f93e0e16c4de8c6c613be40ba5 [file] [log] [blame]
//===--- 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/Defer.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;
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
// llvm::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;
Optional<unsigned> bufferID =
SM.getIDForBufferIdentifier(subconsumer.getInputFileName());
assert(bufferID.hasValue() && "consumer registered for unknown file");
CharSourceRange range = SM.getRangeForBuffer(bufferID.getValue());
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");
}
Optional<FileSpecificDiagnosticConsumer::Subconsumer *>
FileSpecificDiagnosticConsumer::subconsumerForLocation(SourceManager &SM,
SourceLoc loc) {
// Diagnostics with invalid locations always go to every consumer.
if (loc.isInvalid())
return None;
// 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())
.hasValue()) {
assert(llvm::none_of(Subconsumers, [&](const Subconsumer &subconsumer) {
return SM.getIDForBufferIdentifier(subconsumer.getInputFileName())
.hasValue();
}));
return None;
}
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 None;
}
void FileSpecificDiagnosticConsumer::handleDiagnostic(
SourceManager &SM, SourceLoc Loc, DiagnosticKind Kind,
StringRef FormatString, ArrayRef<DiagnosticArgument> FormatArgs,
const DiagnosticInfo &Info) {
HasAnErrorBeenConsumed |= Kind == DiagnosticKind::Error;
Optional<FileSpecificDiagnosticConsumer::Subconsumer *> subconsumer;
switch (Kind) {
case DiagnosticKind::Error:
case DiagnosticKind::Warning:
case DiagnosticKind::Remark:
subconsumer = subconsumerForLocation(SM, Loc);
SubconsumerForSubsequentNotes = subconsumer;
break;
case DiagnosticKind::Note:
subconsumer = SubconsumerForSubsequentNotes;
break;
}
if (subconsumer.hasValue()) {
subconsumer.getValue()->handleDiagnostic(SM, Loc, Kind, FormatString,
FormatArgs, Info);
return;
}
for (auto &subconsumer : Subconsumers)
subconsumer.handleDiagnostic(SM, Loc, Kind, FormatString, FormatArgs, Info);
}
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, SourceLoc Loc, DiagnosticKind Kind,
StringRef FormatString, ArrayRef<DiagnosticArgument> FormatArgs,
const DiagnosticInfo &Info) {
LLVM_DEBUG({
llvm::dbgs() << "NullDiagnosticConsumer received diagnostic: ";
DiagnosticEngine::formatDiagnosticText(llvm::dbgs(), FormatString,
FormatArgs);
llvm::dbgs() << "\n";
});
}
ForwardingDiagnosticConsumer::ForwardingDiagnosticConsumer(DiagnosticEngine &Target)
: TargetEngine(Target) {}
void ForwardingDiagnosticConsumer::handleDiagnostic(
SourceManager &SM, SourceLoc Loc, DiagnosticKind Kind,
StringRef FormatString, ArrayRef<DiagnosticArgument> FormatArgs,
const DiagnosticInfo &Info) {
LLVM_DEBUG({
llvm::dbgs() << "ForwardingDiagnosticConsumer received diagnostic: ";
DiagnosticEngine::formatDiagnosticText(llvm::dbgs(), FormatString,
FormatArgs);
llvm::dbgs() << "\n";
});
for (auto *C : TargetEngine.getConsumers()) {
C->handleDiagnostic(SM, Loc, Kind, FormatString, FormatArgs, Info);
}
}