blob: c889753ce404ac6002af310261716fcec27ceb89 [file] [log] [blame]
// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
// @dart = 2.10
library dart2js.diagnostic_listener;
import '../../compiler.dart' as api;
import '../compiler.dart' show Compiler;
import '../elements/entities.dart';
import '../io/source_information.dart';
import '../options.dart';
import '../ssa/nodes.dart' show HInstruction;
import 'messages.dart';
import 'source_span.dart' show SourceSpan;
import 'spannable.dart';
class DiagnosticReporter {
final Compiler _compiler;
CompilerOptions get options => _compiler.options;
Entity _currentElement;
bool _hasCrashed = false;
/// `true` if the last diagnostic was filtered, in which case the
/// accompanying info message should be filtered as well.
bool _lastDiagnosticWasFiltered = false;
/// Map containing information about the warnings and hints that have been
/// suppressed for each library.
final Map<Uri, SuppressionInfo> _suppressedWarnings = {};
DiagnosticReporter(this._compiler);
Entity get currentElement => _currentElement;
DiagnosticMessage createMessage(Spannable spannable, MessageKind messageKind,
[Map<String, String> arguments = const {}]) {
SourceSpan span = spanFromSpannable(spannable);
MessageTemplate template = MessageTemplate.TEMPLATES[messageKind];
Message message = template.message(arguments, options);
return DiagnosticMessage(span, spannable, message);
}
void reportError(DiagnosticMessage message,
[List<DiagnosticMessage> infos = const <DiagnosticMessage>[]]) {
_reportDiagnosticInternal(message, infos, api.Diagnostic.ERROR);
}
void reportErrorMessage(Spannable spannable, MessageKind messageKind,
[Map<String, String> arguments = const {}]) {
reportError(createMessage(spannable, messageKind, arguments));
}
void reportWarning(DiagnosticMessage message,
[List<DiagnosticMessage> infos = const <DiagnosticMessage>[]]) {
_reportDiagnosticInternal(message, infos, api.Diagnostic.WARNING);
}
void reportWarningMessage(Spannable spannable, MessageKind messageKind,
[Map<String, String> arguments = const {}]) {
reportWarning(createMessage(spannable, messageKind, arguments));
}
void reportHint(DiagnosticMessage message,
[List<DiagnosticMessage> infos = const <DiagnosticMessage>[]]) {
_reportDiagnosticInternal(message, infos, api.Diagnostic.HINT);
}
void reportHintMessage(Spannable spannable, MessageKind messageKind,
[Map<String, String> arguments = const {}]) {
reportHint(createMessage(spannable, messageKind, arguments));
}
void reportInfo(DiagnosticMessage message,
[List<DiagnosticMessage> infos = const <DiagnosticMessage>[]]) {
_reportDiagnosticInternal(message, infos, api.Diagnostic.INFO);
}
void reportInfoMessage(Spannable node, MessageKind errorCode,
[Map<String, String> arguments = const {}]) {
reportInfo(createMessage(node, errorCode, arguments));
}
void _reportDiagnosticInternal(DiagnosticMessage message,
List<DiagnosticMessage> infos, api.Diagnostic kind) {
if (!options.showAllPackageWarnings &&
message.spannable != NO_LOCATION_SPANNABLE) {
switch (kind) {
case api.Diagnostic.WARNING:
case api.Diagnostic.HINT:
Entity element = _elementFromSpannable(message.spannable);
if (!_compiler.inUserCode(element)) {
Uri uri = _compiler.getCanonicalUri(element);
if (options.showPackageWarningsFor(uri)) {
_reportDiagnostic(message, infos, kind);
return;
}
SuppressionInfo info =
_suppressedWarnings.putIfAbsent(uri, () => SuppressionInfo());
if (kind == api.Diagnostic.WARNING) {
info.warnings++;
} else {
info.hints++;
}
_lastDiagnosticWasFiltered = true;
return;
}
break;
case api.Diagnostic.INFO:
if (_lastDiagnosticWasFiltered) {
return;
}
break;
}
}
_lastDiagnosticWasFiltered = false;
_reportDiagnostic(message, infos, kind);
}
void _reportDiagnostic(DiagnosticMessage message,
List<DiagnosticMessage> infos, api.Diagnostic kind) {
_compiler.reportDiagnostic(message, infos, kind);
if (kind == api.Diagnostic.ERROR ||
kind == api.Diagnostic.CRASH ||
(options.fatalWarnings && kind == api.Diagnostic.WARNING)) {
_compiler.fatalDiagnosticReported(message, infos, kind);
}
}
/// Returns `true` if a crash, an error or a fatal warning has been reported.
bool get hasReportedError => _compiler.compilationFailed;
/// Set current element of this reporter to [element]. This is used for
/// creating [SourceSpan] in [spanFromSpannable]. That is,
/// [withCurrentElement] performs an operation, [f], returning the return
/// value from [f]. If an error occurs then report it as having occurred
/// during compilation of [element]. Can be nested.
dynamic withCurrentElement(Entity element, dynamic f()) {
Entity old = currentElement;
_currentElement = element;
try {
return f();
} on SpannableAssertionFailure catch (ex) {
if (!_hasCrashed) {
_reportAssertionFailure(ex);
_pleaseReportCrash();
}
_hasCrashed = true;
rethrow;
} on StackOverflowError {
// We cannot report anything useful in this case, because we
// do not have enough stack space.
rethrow;
} catch (ex) {
if (_hasCrashed) rethrow;
try {
_unhandledExceptionOnElement(element);
} catch (doubleFault) {
// Ignoring exceptions in exception handling.
}
rethrow;
} finally {
_currentElement = old;
}
}
void _reportAssertionFailure(SpannableAssertionFailure ex) {
String message =
(ex.message != null) ? tryToString(ex.message) : tryToString(ex);
_reportDiagnosticInternal(
createMessage(ex.node, MessageKind.GENERIC, {'text': message}),
const <DiagnosticMessage>[],
api.Diagnostic.CRASH);
}
/// Using [frontendStrategy] to compute a [SourceSpan] from spannable using
/// the [currentElement] as context.
SourceSpan _spanFromStrategy(Spannable spannable) {
SourceSpan span;
if (_compiler.phase == Compiler.PHASE_COMPILING) {
span = _compiler.backendStrategy
.spanFromSpannable(spannable, currentElement);
} else {
span = _compiler.frontendStrategy
.spanFromSpannable(spannable, currentElement);
}
if (span != null) return span;
throw 'No error location.';
}
/// Creates a [SourceSpan] for [node] in scope of the current element.
///
/// If [node] is a [Node] we assert in checked mode that the corresponding
/// tokens can be found within the tokens of the current element.
SourceSpan spanFromSpannable(Spannable spannable) {
if (spannable == CURRENT_ELEMENT_SPANNABLE) {
spannable = currentElement;
} else if (spannable == NO_LOCATION_SPANNABLE) {
if (currentElement == null) return null;
spannable = currentElement;
}
if (spannable is SourceSpan) {
return spannable;
} else if (spannable is HInstruction) {
Entity element = spannable.sourceElement;
if (element == null) element = currentElement;
SourceInformation position = spannable.sourceInformation;
if (position != null) return position.sourceSpan;
return _spanFromStrategy(element);
} else {
return _spanFromStrategy(spannable);
}
}
dynamic internalError(Spannable spannable, reason) {
String message = tryToString(reason);
_reportDiagnosticInternal(
createMessage(spannable, MessageKind.GENERIC, {'text': message}),
const <DiagnosticMessage>[],
api.Diagnostic.CRASH);
throw 'Internal Error: $message';
}
void _unhandledExceptionOnElement(Entity element) {
if (_hasCrashed) return;
_hasCrashed = true;
_reportDiagnostic(createMessage(element, MessageKind.COMPILER_CRASHED),
const <DiagnosticMessage>[], api.Diagnostic.CRASH);
_pleaseReportCrash();
}
void _pleaseReportCrash() {
print(MessageTemplate.TEMPLATES[MessageKind.PLEASE_REPORT_THE_CRASH]
.message({'buildId': _compiler.options.buildId}, options));
}
/// Finds the approximate [Element] for [node]. [currentElement] is used as
/// the default value.
Entity _elementFromSpannable(Spannable node) {
Entity element;
if (node is Entity) {
element = node;
} else if (node is HInstruction) {
element = node.sourceElement;
}
return element ?? currentElement;
}
void log(message) {
Message msg = MessageTemplate.TEMPLATES[MessageKind.GENERIC]
.message({'text': '$message'}, options);
_reportDiagnostic(DiagnosticMessage(null, null, msg),
const <DiagnosticMessage>[], api.Diagnostic.VERBOSE_INFO);
}
String tryToString(object) {
try {
return object.toString();
} catch (_) {
return '<exception in toString()>';
}
}
Future onError(Uri uri, error, StackTrace stackTrace) {
try {
if (!_hasCrashed) {
_hasCrashed = true;
if (error is SpannableAssertionFailure) {
_reportAssertionFailure(error);
} else {
_reportDiagnostic(
createMessage(
SourceSpan(uri, 0, 0), MessageKind.COMPILER_CRASHED),
const <DiagnosticMessage>[],
api.Diagnostic.CRASH);
}
_pleaseReportCrash();
}
} catch (doubleFault) {
// Ignoring exceptions in exception handling.
}
return Future.error(error, stackTrace);
}
/// Called when an [exception] is thrown from user-provided code, like from
/// the input provider or diagnostics handler.
void onCrashInUserCode(String message, exception, stackTrace) {
_hasCrashed = true;
print('$message: ${tryToString(exception)}');
print(tryToString(stackTrace));
}
void reportSuppressedMessagesSummary() {
if (!options.showAllPackageWarnings && !options.suppressWarnings) {
_suppressedWarnings.forEach((Uri uri, SuppressionInfo info) {
MessageKind kind = MessageKind.HIDDEN_WARNINGS_HINTS;
if (info.warnings == 0) {
kind = MessageKind.HIDDEN_HINTS;
} else if (info.hints == 0) {
kind = MessageKind.HIDDEN_WARNINGS;
}
MessageTemplate template = MessageTemplate.TEMPLATES[kind];
Message message = template.message({
'warnings': info.warnings.toString(),
'hints': info.hints.toString(),
'uri': uri.toString(),
}, options);
_reportDiagnostic(DiagnosticMessage(null, null, message),
const <DiagnosticMessage>[], api.Diagnostic.HINT);
});
}
}
}
class DiagnosticMessage {
final SourceSpan sourceSpan;
final Spannable spannable;
final Message message;
DiagnosticMessage(this.sourceSpan, this.spannable, this.message);
}
/// Information about suppressed warnings and hints for a given library.
class SuppressionInfo {
int warnings = 0;
int hints = 0;
}