blob: 5904ec5549ccd196cd7cbfa20a080b76e135fb83 [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.
part of dart.core;
// Exceptions are thrown either by the VM or from Dart code.
/// A marker interface implemented by all core library exceptions.
///
/// An [Exception] is intended to convey information to the user about a failure,
/// so that the error can be addressed programmatically. It is intended to be
/// caught, and it should contain useful data fields.
///
/// Creating instances of [Exception] directly with `Exception("message")`
/// is discouraged in library code since it doesn't give users a precise
/// type they can catch. It may be reasonable to use instances of this
/// class in tests or during development.
abstract class Exception {
factory Exception([var message]) => _Exception(message);
}
/// Default implementation of [Exception] which carries a message.
class _Exception implements Exception {
final dynamic message;
_Exception([this.message]);
String toString() {
Object? message = this.message;
if (message == null) return "Exception";
return "Exception: $message";
}
}
/// Exception thrown when a string or some other data does not have an expected
/// format and cannot be parsed or processed.
class FormatException implements Exception {
/// A message describing the format error.
final String message;
/// The actual source input which caused the error.
///
/// This is usually a [String], but can be other types too.
/// If it is a string, parts of it may be included in the [toString] message.
///
/// The source is `null` if omitted or unknown.
final dynamic source;
/// The offset in [source] where the error was detected.
///
/// A zero-based offset into the source that marks the format error causing
/// this exception to be created. If `source` is a string, this should be a
/// string index in the range `0 <= offset <= source.length`.
///
/// If input is a string, the [toString] method may represent this offset as
/// a line and character position. The offset should be inside the string,
/// or at the end of the string.
///
/// May be omitted. If present, [source] should also be present if possible.
final int? offset;
/// Creates a new `FormatException` with an optional error [message].
///
/// Optionally also supply the actual [source] with the incorrect format,
/// and the [offset] in the format where a problem was detected.
@pragma("vm:entry-point")
const FormatException([this.message = "", this.source, this.offset]);
/// Returns a description of the format exception.
///
/// The description always contains the [message].
///
/// If [source] is present and is a string, the description will contain
/// (at least a part of) the source.
/// If [offset] is also provided, the part of the source included will
/// contain that offset, and the offset will be marked.
///
/// If the source is a string and it contains a line break before offset,
/// only the line containing offset will be included, and its line number
/// will also be part of the description. Line and character offsets are
/// 1-based.
String toString() {
String report = "FormatException";
Object? message = this.message;
if (message != null && "" != message) {
report = "$report: $message";
}
int? offset = this.offset;
Object? source = this.source;
if (source is String) {
if (offset != null && (offset < 0 || offset > source.length)) {
offset = null;
}
// Source is string and offset is null or valid.
if (offset == null) {
if (source.length > 78) {
source = source.substring(0, 75) + "...";
}
return "$report\n$source";
}
int lineNum = 1;
int lineStart = 0;
bool previousCharWasCR = false;
for (int i = 0; i < offset; i++) {
int char = source.codeUnitAt(i);
if (char == 0x0a) {
if (lineStart != i || !previousCharWasCR) {
lineNum++;
}
lineStart = i + 1;
previousCharWasCR = false;
} else if (char == 0x0d) {
lineNum++;
lineStart = i + 1;
previousCharWasCR = true;
}
}
if (lineNum > 1) {
report += " (at line $lineNum, character ${offset - lineStart + 1})\n";
} else {
report += " (at character ${offset + 1})\n";
}
int lineEnd = source.length;
for (int i = offset; i < source.length; i++) {
int char = source.codeUnitAt(i);
if (char == 0x0a || char == 0x0d) {
lineEnd = i;
break;
}
}
int length = lineEnd - lineStart;
int start = lineStart;
int end = lineEnd;
String prefix = "";
String postfix = "";
if (length > 78) {
// Can't show entire line. Try to anchor at the nearest end, if
// one is within reach.
int index = offset - lineStart;
if (index < 75) {
end = start + 75;
postfix = "...";
} else if (end - offset < 75) {
start = end - 75;
prefix = "...";
} else {
// Neither end is near, just pick an area around the offset.
start = offset - 36;
end = offset + 36;
prefix = postfix = "...";
}
}
String slice = source.substring(start, end);
int markOffset = offset - start + prefix.length;
return "$report$prefix$slice$postfix\n${" " * markOffset}^\n";
} else {
// The source is not a string.
if (offset != null) {
report += " (at offset $offset)";
}
return report;
}
}
}
// Exception thrown when doing integer division with a zero divisor.
// TODO(30743): Should be removed, and division by zero should just throw an
// [ArgumentError].
class IntegerDivisionByZeroException implements Exception {
@pragma("vm:entry-point")
const IntegerDivisionByZeroException();
String toString() => "IntegerDivisionByZeroException";
}