Unverified Commit 2a8dba4a authored by Michael Goderbauer's avatar Michael Goderbauer Committed by GitHub

Treat some exceptions as unhandled when a debugger is attached (#78649)

parent eb735167
...@@ -135,6 +135,7 @@ mixin AnimationLocalListenersMixin { ...@@ -135,6 +135,7 @@ mixin AnimationLocalListenersMixin {
/// If listeners are added or removed during this function, the modifications /// If listeners are added or removed during this function, the modifications
/// will not change which listeners are called during this iteration. /// will not change which listeners are called during this iteration.
@protected @protected
@pragma('vm:notify-debugger-on-exception')
void notifyListeners() { void notifyListeners() {
final List<VoidCallback> localListeners = List<VoidCallback>.from(_listeners); final List<VoidCallback> localListeners = List<VoidCallback>.from(_listeners);
for (final VoidCallback listener in localListeners) { for (final VoidCallback listener in localListeners) {
...@@ -223,6 +224,7 @@ mixin AnimationLocalStatusListenersMixin { ...@@ -223,6 +224,7 @@ mixin AnimationLocalStatusListenersMixin {
/// If listeners are added or removed during this function, the modifications /// If listeners are added or removed during this function, the modifications
/// will not change which listeners are called during this iteration. /// will not change which listeners are called during this iteration.
@protected @protected
@pragma('vm:notify-debugger-on-exception')
void notifyStatusListeners(AnimationStatus status) { void notifyStatusListeners(AnimationStatus status) {
final List<AnimationStatusListener> localListeners = List<AnimationStatusListener>.from(_statusListeners); final List<AnimationStatusListener> localListeners = List<AnimationStatusListener>.from(_statusListeners);
for (final AnimationStatusListener listener in localListeners) { for (final AnimationStatusListener listener in localListeners) {
......
...@@ -15,6 +15,7 @@ import 'stack_frame.dart'; ...@@ -15,6 +15,7 @@ import 'stack_frame.dart';
// late bool draconisAlive; // late bool draconisAlive;
// late bool draconisAmulet; // late bool draconisAmulet;
// late Diagnosticable draconis; // late Diagnosticable draconis;
// void methodThatMayThrow() { }
/// Signature for [FlutterError.onError] handler. /// Signature for [FlutterError.onError] handler.
typedef FlutterExceptionHandler = void Function(FlutterErrorDetails details); typedef FlutterExceptionHandler = void Function(FlutterErrorDetails details);
...@@ -876,9 +877,7 @@ class FlutterError extends Error with DiagnosticableTreeMixin implements Asserti ...@@ -876,9 +877,7 @@ class FlutterError extends Error with DiagnosticableTreeMixin implements Asserti
/// ///
/// Do not call [onError] directly, instead, call [reportError], which /// Do not call [onError] directly, instead, call [reportError], which
/// forwards to [onError] if it is not null. /// forwards to [onError] if it is not null.
static FlutterExceptionHandler? onError = _defaultErrorHandler; static FlutterExceptionHandler? onError = presentError;
static void _defaultErrorHandler(FlutterErrorDetails details) => presentError(details);
/// Called by the Flutter framework before attempting to parse a [StackTrace]. /// Called by the Flutter framework before attempting to parse a [StackTrace].
/// ///
...@@ -1101,6 +1100,31 @@ class FlutterError extends Error with DiagnosticableTreeMixin implements Asserti ...@@ -1101,6 +1100,31 @@ class FlutterError extends Error with DiagnosticableTreeMixin implements Asserti
} }
/// Calls [onError] with the given details, unless it is null. /// Calls [onError] with the given details, unless it is null.
///
/// {@tool snippet}
/// When calling this from a `catch` block consider annotating the method
/// containing the `catch` block with
/// `@pragma('vm:notify-debugger-on-exception')` to allow an attached debugger
/// to treat the exception as unhandled. This means instead of executing the
/// `catch` block, the debugger can break at the original source location from
/// which the exception was thrown.
///
/// ```dart
/// @pragma('vm:notify-debugger-on-exception')
/// void doSomething() {
/// try {
/// methodThatMayThrow();
/// } catch (exception, stack) {
/// FlutterError.reportError(FlutterErrorDetails(
/// exception: exception,
/// stack: stack,
/// library: 'example library',
/// context: ErrorDescription('while doing something'),
/// ));
/// }
/// }
/// ```
/// {@end-tool}
static void reportError(FlutterErrorDetails details) { static void reportError(FlutterErrorDetails details) {
assert(details != null); assert(details != null);
assert(details.exception != null); assert(details.exception != null);
......
...@@ -283,6 +283,7 @@ class ChangeNotifier implements Listenable { ...@@ -283,6 +283,7 @@ class ChangeNotifier implements Listenable {
/// See the discussion at [removeListener]. /// See the discussion at [removeListener].
@protected @protected
@visibleForTesting @visibleForTesting
@pragma('vm:notify-debugger-on-exception')
void notifyListeners() { void notifyListeners() {
assert(_debugAssertNotDisposed()); assert(_debugAssertNotDisposed());
if (_count == 0) if (_count == 0)
......
...@@ -390,6 +390,7 @@ mixin GestureBinding on BindingBase implements HitTestable, HitTestDispatcher, H ...@@ -390,6 +390,7 @@ mixin GestureBinding on BindingBase implements HitTestable, HitTestDispatcher, H
/// The `hitTestResult` argument may only be null for [PointerAddedEvent]s or /// The `hitTestResult` argument may only be null for [PointerAddedEvent]s or
/// [PointerRemovedEvent]s. /// [PointerRemovedEvent]s.
@override // from HitTestDispatcher @override // from HitTestDispatcher
@pragma('vm:notify-debugger-on-exception')
void dispatchEvent(PointerEvent event, HitTestResult? hitTestResult) { void dispatchEvent(PointerEvent event, HitTestResult? hitTestResult) {
assert(!locked); assert(!locked);
// No hit test information implies that this is a [PointerHoverEvent], // No hit test information implies that this is a [PointerHoverEvent],
......
...@@ -87,6 +87,7 @@ class PointerRouter { ...@@ -87,6 +87,7 @@ class PointerRouter {
throw UnsupportedError('debugGlobalRouteCount is not supported in release builds'); throw UnsupportedError('debugGlobalRouteCount is not supported in release builds');
} }
@pragma('vm:notify-debugger-on-exception')
void _dispatch(PointerEvent event, PointerRoute route, Matrix4? transform) { void _dispatch(PointerEvent event, PointerRoute route, Matrix4? transform) {
try { try {
event = event.transformed(transform); event = event.transformed(transform);
......
...@@ -2,7 +2,6 @@ ...@@ -2,7 +2,6 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'events.dart'; import 'events.dart';
...@@ -189,6 +188,7 @@ class PointerSignalResolver { ...@@ -189,6 +188,7 @@ class PointerSignalResolver {
/// ///
/// This is called by the [GestureBinding] after the framework has finished /// This is called by the [GestureBinding] after the framework has finished
/// dispatching the pointer signal event. /// dispatching the pointer signal event.
@pragma('vm:notify-debugger-on-exception')
void resolve(PointerSignalEvent event) { void resolve(PointerSignalEvent event) {
if (_firstRegisteredCallback == null) { if (_firstRegisteredCallback == null) {
assert(_currentEvent == null); assert(_currentEvent == null);
......
...@@ -165,6 +165,7 @@ abstract class GestureRecognizer extends GestureArenaMember with DiagnosticableT ...@@ -165,6 +165,7 @@ abstract class GestureRecognizer extends GestureArenaMember with DiagnosticableT
/// callback that returns a string describing useful debugging information, /// callback that returns a string describing useful debugging information,
/// e.g. the arguments passed to the callback. /// e.g. the arguments passed to the callback.
@protected @protected
@pragma('vm:notify-debugger-on-exception')
T? invokeCallback<T>(String name, RecognizerCallback<T> callback, { String Function()? debugReport }) { T? invokeCallback<T>(String name, RecognizerCallback<T> callback, { String Function()? debugReport }) {
assert(callback != null); assert(callback != null);
T? result; T? result;
......
...@@ -614,6 +614,7 @@ abstract class ImageStreamCompleter with Diagnosticable { ...@@ -614,6 +614,7 @@ abstract class ImageStreamCompleter with Diagnosticable {
/// Calls all the registered listeners to notify them of a new image. /// Calls all the registered listeners to notify them of a new image.
@protected @protected
@pragma('vm:notify-debugger-on-exception')
void setImage(ImageInfo image) { void setImage(ImageInfo image) {
_checkDisposed(); _checkDisposed();
_currentImage?.dispose(); _currentImage?.dispose();
...@@ -668,6 +669,7 @@ abstract class ImageStreamCompleter with Diagnosticable { ...@@ -668,6 +669,7 @@ abstract class ImageStreamCompleter with Diagnosticable {
/// ///
/// See [FlutterErrorDetails] for further details on these values. /// See [FlutterErrorDetails] for further details on these values.
@protected @protected
@pragma('vm:notify-debugger-on-exception')
void reportError({ void reportError({
DiagnosticsNode? context, DiagnosticsNode? context,
required Object exception, required Object exception,
......
...@@ -1617,6 +1617,7 @@ abstract class RenderObject extends AbstractNode with DiagnosticableTreeMixin im ...@@ -1617,6 +1617,7 @@ abstract class RenderObject extends AbstractNode with DiagnosticableTreeMixin im
owner!._nodesNeedingLayout.add(this); owner!._nodesNeedingLayout.add(this);
} }
@pragma('vm:notify-debugger-on-exception')
void _layoutWithoutResize() { void _layoutWithoutResize() {
assert(_relayoutBoundary == this); assert(_relayoutBoundary == this);
RenderObject? debugPreviousActiveLayout; RenderObject? debugPreviousActiveLayout;
...@@ -1671,6 +1672,7 @@ abstract class RenderObject extends AbstractNode with DiagnosticableTreeMixin im ...@@ -1671,6 +1672,7 @@ abstract class RenderObject extends AbstractNode with DiagnosticableTreeMixin im
/// children unconditionally. It is the [layout] method's responsibility (as /// children unconditionally. It is the [layout] method's responsibility (as
/// implemented here) to return early if the child does not need to do any /// implemented here) to return early if the child does not need to do any
/// work to update its layout information. /// work to update its layout information.
@pragma('vm:notify-debugger-on-exception')
void layout(Constraints constraints, { bool parentUsesSize = false }) { void layout(Constraints constraints, { bool parentUsesSize = false }) {
if (!kReleaseMode && debugProfileLayoutsEnabled) if (!kReleaseMode && debugProfileLayoutsEnabled)
Timeline.startSync('$runtimeType', arguments: timelineArgumentsIndicatingLandmarkEvent); Timeline.startSync('$runtimeType', arguments: timelineArgumentsIndicatingLandmarkEvent);
......
...@@ -280,6 +280,7 @@ mixin SchedulerBinding on BindingBase { ...@@ -280,6 +280,7 @@ mixin SchedulerBinding on BindingBase {
} }
} }
@pragma('vm:notify-debugger-on-exception')
void _executeTimingsCallbacks(List<FrameTiming> timings) { void _executeTimingsCallbacks(List<FrameTiming> timings) {
final List<TimingsCallback> clonedCallbacks = final List<TimingsCallback> clonedCallbacks =
List<TimingsCallback>.from(_timingsCallbacks); List<TimingsCallback>.from(_timingsCallbacks);
...@@ -450,6 +451,10 @@ mixin SchedulerBinding on BindingBase { ...@@ -450,6 +451,10 @@ mixin SchedulerBinding on BindingBase {
/// ///
/// Also returns false if there are no tasks remaining. /// Also returns false if there are no tasks remaining.
@visibleForTesting @visibleForTesting
// TODO(goderbauer): Add pragma (and enable test in
// break_on_framework_exceptions_test.dart) once debugger breaks on correct
// line, https://github.com/dart-lang/sdk/issues/45684
// @pragma('vm:notify-debugger-on-exception')
bool handleEventLoopCallback() { bool handleEventLoopCallback() {
if (_taskQueue.isEmpty || locked) if (_taskQueue.isEmpty || locked)
return false; return false;
...@@ -1133,6 +1138,7 @@ mixin SchedulerBinding on BindingBase { ...@@ -1133,6 +1138,7 @@ mixin SchedulerBinding on BindingBase {
// Wraps the callback in a try/catch and forwards any error to // Wraps the callback in a try/catch and forwards any error to
// [debugSchedulerExceptionHandler], if set. If not set, then simply prints // [debugSchedulerExceptionHandler], if set. If not set, then simply prints
// the error. // the error.
@pragma('vm:notify-debugger-on-exception')
void _invokeFrameCallback(FrameCallback callback, Duration timeStamp, [ StackTrace? callbackStack ]) { void _invokeFrameCallback(FrameCallback callback, Duration timeStamp, [ StackTrace? callbackStack ]) {
assert(callback != null); assert(callback != null);
assert(_FrameCallbackEntry.debugCurrentCallbackStack == null); assert(_FrameCallbackEntry.debugCurrentCallbackStack == null);
......
...@@ -272,6 +272,10 @@ class _DefaultBinaryMessenger extends BinaryMessenger { ...@@ -272,6 +272,10 @@ class _DefaultBinaryMessenger extends BinaryMessenger {
} }
@override @override
// TODO(goderbauer): Add pragma (and enable test in
// break_on_framework_exceptions_test.dart) when it works on async methods,
// https://github.com/dart-lang/sdk/issues/45673
// @pragma('vm:notify-debugger-on-exception')
Future<void> handlePlatformMessage( Future<void> handlePlatformMessage(
String channel, String channel,
ByteData? data, ByteData? data,
......
...@@ -198,6 +198,7 @@ abstract class Action<T extends Intent> with Diagnosticable { ...@@ -198,6 +198,7 @@ abstract class Action<T extends Intent> with Diagnosticable {
/// See the discussion at [removeActionListener]. /// See the discussion at [removeActionListener].
@protected @protected
@visibleForTesting @visibleForTesting
@pragma('vm:notify-debugger-on-exception')
void notifyActionListeners() { void notifyActionListeners() {
if (_listeners.isEmpty) { if (_listeners.isEmpty) {
return; return;
......
...@@ -1187,6 +1187,7 @@ class RenderObjectToWidgetElement<T extends RenderObject> extends RootRenderObje ...@@ -1187,6 +1187,7 @@ class RenderObjectToWidgetElement<T extends RenderObject> extends RootRenderObje
assert(_newWidget == null); assert(_newWidget == null);
} }
@pragma('vm:notify-debugger-on-exception')
void _rebuild() { void _rebuild() {
try { try {
_child = updateChild(_child, widget.child, _rootChildSlot); _child = updateChild(_child, widget.child, _rootChildSlot);
......
...@@ -1873,6 +1873,7 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien ...@@ -1873,6 +1873,7 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
} }
} }
@pragma('vm:notify-debugger-on-exception')
void _finalizeEditing(TextInputAction action, {required bool shouldUnfocus}) { void _finalizeEditing(TextInputAction action, {required bool shouldUnfocus}) {
// Take any actions necessary now that the user has completed editing. // Take any actions necessary now that the user has completed editing.
if (widget.onEditingComplete != null) { if (widget.onEditingComplete != null) {
...@@ -2126,6 +2127,7 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien ...@@ -2126,6 +2127,7 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
} }
} }
@pragma('vm:notify-debugger-on-exception')
void _handleSelectionChanged(TextSelection selection, SelectionChangedCause? cause) { void _handleSelectionChanged(TextSelection selection, SelectionChangedCause? cause) {
// We return early if the selection is not valid. This can happen when the // We return early if the selection is not valid. This can happen when the
// text of [EditableText] is updated at the same time as the selection is // text of [EditableText] is updated at the same time as the selection is
...@@ -2259,6 +2261,7 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien ...@@ -2259,6 +2261,7 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
_lastBottomViewInset = WidgetsBinding.instance!.window.viewInsets.bottom; _lastBottomViewInset = WidgetsBinding.instance!.window.viewInsets.bottom;
} }
@pragma('vm:notify-debugger-on-exception')
void _formatAndSetValue(TextEditingValue value, SelectionChangedCause? cause, {bool userInteraction = false}) { void _formatAndSetValue(TextEditingValue value, SelectionChangedCause? cause, {bool userInteraction = false}) {
// Only apply input formatters if the text has changed (including uncommited // Only apply input formatters if the text has changed (including uncommited
// text in the composing region), or when the user committed the composing // text in the composing region), or when the user committed the composing
......
...@@ -1596,6 +1596,7 @@ class FocusManager with DiagnosticableTreeMixin, ChangeNotifier { ...@@ -1596,6 +1596,7 @@ class FocusManager with DiagnosticableTreeMixin, ChangeNotifier {
/// [FocusManager] notifies. /// [FocusManager] notifies.
void removeHighlightModeListener(ValueChanged<FocusHighlightMode> listener) => _listeners.remove(listener); void removeHighlightModeListener(ValueChanged<FocusHighlightMode> listener) => _listeners.remove(listener);
@pragma('vm:notify-debugger-on-exception')
void _notifyHighlightModeListeners() { void _notifyHighlightModeListeners() {
if (_listeners.isEmpty) { if (_listeners.isEmpty) {
return; return;
......
...@@ -2505,6 +2505,7 @@ class BuildOwner { ...@@ -2505,6 +2505,7 @@ class BuildOwner {
/// [debugPrintBuildScope] to true. This is useful when debugging problems /// [debugPrintBuildScope] to true. This is useful when debugging problems
/// involving widgets not getting marked dirty, or getting marked dirty too /// involving widgets not getting marked dirty, or getting marked dirty too
/// often. /// often.
@pragma('vm:notify-debugger-on-exception')
void buildScope(Element context, [ VoidCallback? callback ]) { void buildScope(Element context, [ VoidCallback? callback ]) {
if (callback == null && _dirtyElements.isEmpty) if (callback == null && _dirtyElements.isEmpty)
return; return;
...@@ -2833,6 +2834,10 @@ class BuildOwner { ...@@ -2833,6 +2834,10 @@ class BuildOwner {
/// ///
/// After the current call stack unwinds, a microtask that notifies listeners /// After the current call stack unwinds, a microtask that notifies listeners
/// about changes to global keys will run. /// about changes to global keys will run.
// TODO(goderbauer): Add pragma (and enable test in
// break_on_framework_exceptions_test.dart) once debugger breaks on correct
// line, https://github.com/dart-lang/sdk/issues/45684
// @pragma('vm:notify-debugger-on-exception')
void finalizeTree() { void finalizeTree() {
Timeline.startSync('Finalize tree', arguments: timelineArgumentsIndicatingLandmarkEvent); Timeline.startSync('Finalize tree', arguments: timelineArgumentsIndicatingLandmarkEvent);
try { try {
...@@ -4579,6 +4584,7 @@ abstract class ComponentElement extends Element { ...@@ -4579,6 +4584,7 @@ abstract class ComponentElement extends Element {
/// Called automatically during [mount] to generate the first build, and by /// Called automatically during [mount] to generate the first build, and by
/// [rebuild] when the element needs updating. /// [rebuild] when the element needs updating.
@override @override
@pragma('vm:notify-debugger-on-exception')
void performRebuild() { void performRebuild() {
if (!kReleaseMode && debugProfileBuildsEnabled) if (!kReleaseMode && debugProfileBuildsEnabled)
Timeline.startSync('${widget.runtimeType}', arguments: timelineArgumentsIndicatingLandmarkEvent); Timeline.startSync('${widget.runtimeType}', arguments: timelineArgumentsIndicatingLandmarkEvent);
......
...@@ -115,6 +115,10 @@ class _LayoutBuilderElement<ConstraintType extends Constraints> extends RenderOb ...@@ -115,6 +115,10 @@ class _LayoutBuilderElement<ConstraintType extends Constraints> extends RenderOb
} }
void _layout(ConstraintType constraints) { void _layout(ConstraintType constraints) {
// TODO(goderbauer): When https://github.com/dart-lang/sdk/issues/45710 is
// fixed: refactor the anonymous closure below into a named one, apply the
// @pragma('vm:notify-debugger-on-exception') to it and enable the
// corresponding test in break_on_framework_exceptions_test.dart.
owner!.buildScope(this, () { owner!.buildScope(this, () {
Widget built; Widget built;
try { try {
......
...@@ -760,6 +760,7 @@ class _CallbackHookProvider<T> { ...@@ -760,6 +760,7 @@ class _CallbackHookProvider<T> {
/// Exceptions thrown by callbacks will be caught and reported using /// Exceptions thrown by callbacks will be caught and reported using
/// [FlutterError.reportError]. /// [FlutterError.reportError].
@protected @protected
@pragma('vm:notify-debugger-on-exception')
T invokeCallback(T defaultValue) { T invokeCallback(T defaultValue) {
if (_callbacks.isEmpty) if (_callbacks.isEmpty)
return defaultValue; return defaultValue;
...@@ -773,7 +774,7 @@ class _CallbackHookProvider<T> { ...@@ -773,7 +774,7 @@ class _CallbackHookProvider<T> {
context: ErrorDescription('while invoking the callback for $runtimeType'), context: ErrorDescription('while invoking the callback for $runtimeType'),
informationCollector: () sync* { informationCollector: () sync* {
yield DiagnosticsProperty<_CallbackHookProvider<T>>( yield DiagnosticsProperty<_CallbackHookProvider<T>>(
'The $runtimeType that invoked the callback was:', 'The $runtimeType that invoked the callback was',
this, this,
style: DiagnosticsTreeStyle.errorProperty, style: DiagnosticsTreeStyle.errorProperty,
); );
......
...@@ -446,6 +446,7 @@ class SliverChildBuilderDelegate extends SliverChildDelegate { ...@@ -446,6 +446,7 @@ class SliverChildBuilderDelegate extends SliverChildDelegate {
} }
@override @override
@pragma('vm:notify-debugger-on-exception')
Widget? build(BuildContext context, int index) { Widget? build(BuildContext context, int index) {
assert(builder != null); assert(builder != null);
if (index < 0 || (childCount != null && index >= childCount!)) if (index < 0 || (childCount != null && index >= childCount!))
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment