blob: d676db2da230c552b5def2c5ef3d3b4d80bed182 [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::ConsumerPair> consumers) {
llvm::StringSet<> seenFiles;
for (const auto &consumerPair : consumers) {
if (consumerPair.first.empty()) {
// We can handle multiple consumers 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(consumerPair.first).second;
if (!isUnique)
return true;
}
return false;
}
FileSpecificDiagnosticConsumer::FileSpecificDiagnosticConsumer(
SmallVectorImpl<ConsumerPair> &consumers)
: SubConsumers(std::move(consumers)) {
assert(!SubConsumers.empty() &&
"don't waste time handling diagnostics that will never get emitted");
assert(!hasDuplicateFileNames(SubConsumers) &&
"having multiple consumers 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 ConsumerPair &pair : SubConsumers) {
if (pair.first.empty())
continue;
Optional<unsigned> bufferID = SM.getIDForBufferIdentifier(pair.first);
assert(bufferID.hasValue() && "consumer registered for unknown file");
CharSourceRange range = SM.getRangeForBuffer(bufferID.getValue());
ConsumersOrderedByRange.emplace_back(
ConsumerSpecificInformation(range, pair.second.get()));
}
// 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(),
[](const ConsumerSpecificInformation &left,
const ConsumerSpecificInformation &right) -> bool {
auto compare = std::less<const char *>();
return compare(getRawLoc(left.range.getEnd()).getPointer(),
getRawLoc(right.range.getEnd()).getPointer());
});
// 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 ConsumerSpecificInformation &left,
const ConsumerSpecificInformation &right) {
return left.range.overlaps(right.range);
}) &&
"overlapping ranges despite having distinct files");
}
Optional<FileSpecificDiagnosticConsumer::ConsumerSpecificInformation *>
FileSpecificDiagnosticConsumer::consumerSpecificInformationForLocation(
SourceManager &SM, SourceLoc loc) const {
// 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()->first).hasValue()) {
assert(llvm::none_of(SubConsumers, [&](const ConsumerPair &pair) {
return SM.getIDForBufferIdentifier(pair.first).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 ConsumerSpecificInformation *possiblyContainingRangeIter =
std::lower_bound(
ConsumersOrderedByRange.begin(), ConsumersOrderedByRange.end(), loc,
[](const ConsumerSpecificInformation &entry, SourceLoc loc) -> bool {
auto compare = std::less<const char *>();
return compare(getRawLoc(entry.range.getEnd()).getPointer(),
getRawLoc(loc).getPointer());
});
if (possiblyContainingRangeIter != ConsumersOrderedByRange.end() &&
possiblyContainingRangeIter->range.contains(loc)) {
return const_cast<ConsumerSpecificInformation *>(
possiblyContainingRangeIter);
}
return None;
}
void FileSpecificDiagnosticConsumer::handleDiagnostic(
SourceManager &SM, SourceLoc Loc, DiagnosticKind Kind,
StringRef FormatString, ArrayRef<DiagnosticArgument> FormatArgs,
const DiagnosticInfo &Info) {
HasAnErrorBeenConsumed |= Kind == DiagnosticKind::Error;
Optional<ConsumerSpecificInformation *> consumerSpecificInfo;
switch (Kind) {
case DiagnosticKind::Error:
case DiagnosticKind::Warning:
case DiagnosticKind::Remark:
consumerSpecificInfo = consumerSpecificInformationForLocation(SM, Loc);
ConsumerSpecificInfoForSubsequentNotes = consumerSpecificInfo;
break;
case DiagnosticKind::Note:
consumerSpecificInfo = ConsumerSpecificInfoForSubsequentNotes;
break;
}
if (!consumerSpecificInfo.hasValue()) {
for (auto &subConsumer : SubConsumers) {
if (subConsumer.second) {
subConsumer.second->handleDiagnostic(SM, Loc, Kind, FormatString,
FormatArgs, Info);
}
}
return;
}
if (!consumerSpecificInfo.getValue()->consumer)
return; // Suppress non-primary diagnostic in batch mode.
consumerSpecificInfo.getValue()->consumer->handleDiagnostic(
SM, Loc, Kind, FormatString, FormatArgs, Info);
consumerSpecificInfo.getValue()->hasAnErrorBeenEmitted |=
Kind == DiagnosticKind::Error;
}
bool FileSpecificDiagnosticConsumer::finishProcessing(SourceManager &SM) {
addNonSpecificErrors(SM);
// 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.second && subConsumer.second->finishProcessing(SM);
return hadError;
}
static void produceNonSpecificError(
FileSpecificDiagnosticConsumer::ConsumerSpecificInformation &consumerInfo,
SourceManager &SM) {
Diagnostic diagnostic(
diag::error_compilation_stopped_by_errors_in_other_files);
// Stolen from DiagnosticEngine::emitDiagnostic
DiagnosticInfo Info;
Info.ID = diagnostic.getID();
consumerInfo.consumer->handleDiagnostic(
SM, consumerInfo.range.getStart(), DiagnosticKind::Error,
DiagnosticEngine::diagnosticStringFor(diagnostic.getID()), {}, Info);
}
void FileSpecificDiagnosticConsumer::addNonSpecificErrors(SourceManager &SM) {
if (!HasAnErrorBeenConsumed)
return;
for (auto &info : ConsumersOrderedByRange) {
if (!info.hasAnErrorBeenEmitted && info.consumer) {
produceNonSpecificError(info, SM);
info.hasAnErrorBeenEmitted = true;
}
}
}
void NullDiagnosticConsumer::handleDiagnostic(
SourceManager &SM, SourceLoc Loc, DiagnosticKind Kind,
StringRef FormatString, ArrayRef<DiagnosticArgument> FormatArgs,
const DiagnosticInfo &Info) {
DEBUG({
llvm::dbgs() << "NullDiagnosticConsumer received diagnostic: ";
DiagnosticEngine::formatDiagnosticText(llvm::dbgs(), FormatString,
FormatArgs);
llvm::dbgs() << "\n";
});
}