Steps to reproduce
This is a low-frequency crash observed in production via Crashlytics. It occurs while dragging a text selection handle when the underlying RenderEditable is momentarily in a degenerate layout state (zero preferred line height, or a non-invertible transform that makes globalToLocal return non-finite coordinates).
In our data it is concentrated on foldable devices (Samsung Galaxy Z Flip 5 / SM-F731N), which strongly suggests the trigger is a layout transition (fold/unfold) happening mid-drag of a selection handle, transiently producing preferredLineHeight == 0 and/or a degenerate transform.
We do not have a deterministic minimal repro (it's a timing/layout race), but the root cause is unambiguous from the source — see below.
Expected results
Dragging a selection handle while the editable is in a transient degenerate layout state should be a no-op (or clamp), not throw.
Actual results
Fatal (uncaught-in-callback) exception:
Unsupported operation: Infinity or NaN toInt
reported through GestureRecognizer.invokeCallback → FlutterError.reportError.
Stack trace (symbolicated):
TextSelectionOverlay._getHandleDy (package:flutter/src/widgets/text_selection.dart)
TextSelectionOverlay._handleSelectionStartHandleDragUpdate (text_selection.dart:915)
SelectionOverlay._handleStartHandleDragUpdate (text_selection.dart:1296)
DragGestureRecognizer._checkUpdate.<fn> (monodrag.dart:871)
GestureRecognizer.invokeCallback (recognizer.dart:345)
DragGestureRecognizer._checkUpdate (monodrag.dart:871)
DragGestureRecognizer.handleEvent (monodrag.dart:713)
PointerRouter._dispatch (pointer_router.dart:97)
...
(The same applies to _handleSelectionEndHandleDragUpdate.)
Root cause
_getHandleDy in packages/flutter/lib/src/widgets/text_selection.dart (unchanged on master as of June 2026):
double _getHandleDy(double dragDy, double handleDy) {
final double distanceDragged = dragDy - handleDy;
final int dragDirection = distanceDragged < 0.0 ? -1 : 1;
final int linesDragged =
dragDirection * (distanceDragged.abs() / renderObject.preferredLineHeight).floor();
return handleDy + linesDragged * renderObject.preferredLineHeight;
}
(distanceDragged.abs() / renderObject.preferredLineHeight) becomes:
Infinity when renderObject.preferredLineHeight == 0 (division by zero with a positive numerator), or
NaN when distanceDragged is non-finite — which happens when renderObject.globalToLocal(...) is fed through a non-invertible transform (a degenerate/zero-size render object during a layout transition), producing NaN/Infinity coordinates at the two call sites (text_selection.dart:782-785 and :915-918).
.floor() returns int, so on a non-finite double it throws UnsupportedError('Infinity or NaN toInt').
Proposed fix
Guard against a non-finite result before calling .floor():
double _getHandleDy(double dragDy, double handleDy) {
final double distanceDragged = dragDy - handleDy;
final double lineHeight = renderObject.preferredLineHeight;
// A degenerate layout (e.g. a foldable mid fold/unfold) can momentarily make
// preferredLineHeight 0, or make globalToLocal return non-finite coordinates.
// Without this guard the division below is Infinity/NaN and .floor() throws
// "Unsupported operation: Infinity or NaN toInt".
if (lineHeight <= 0.0 || !distanceDragged.isFinite) {
return handleDy;
}
final int dragDirection = distanceDragged < 0.0 ? -1 : 1;
final int linesDragged = dragDirection * (distanceDragged.abs() / lineHeight).floor();
return handleDy + linesDragged * lineHeight;
}
I'm happy to send a PR with this guard if it's welcome.
Flutter / Dart version
Flutter 3.38.9 • channel stable
Framework • revision 67323de285 • 2026-01-28
Engine • hash 5eb06b7ad5bb8cbc22c5230264c7a00ceac7674b (revision 587c18f873)
Dart SDK 3.10.x
Affected platform: Android (observed on Samsung Galaxy Z Flip 5 / SM-F731N). The vulnerable code is platform-agnostic; the path is just easiest to hit on foldables.
Confirmed the same unguarded _getHandleDy is present on master, so this is not fixed in any release after 3.38.9.
Steps to reproduce
This is a low-frequency crash observed in production via Crashlytics. It occurs while dragging a text selection handle when the underlying
RenderEditableis momentarily in a degenerate layout state (zero preferred line height, or a non-invertible transform that makesglobalToLocalreturn non-finite coordinates).In our data it is concentrated on foldable devices (Samsung Galaxy Z Flip 5 /
SM-F731N), which strongly suggests the trigger is a layout transition (fold/unfold) happening mid-drag of a selection handle, transiently producingpreferredLineHeight == 0and/or a degenerate transform.We do not have a deterministic minimal repro (it's a timing/layout race), but the root cause is unambiguous from the source — see below.
Expected results
Dragging a selection handle while the editable is in a transient degenerate layout state should be a no-op (or clamp), not throw.
Actual results
Fatal (uncaught-in-callback) exception:
reported through
GestureRecognizer.invokeCallback→FlutterError.reportError.Stack trace (symbolicated):
(The same applies to
_handleSelectionEndHandleDragUpdate.)Root cause
_getHandleDyinpackages/flutter/lib/src/widgets/text_selection.dart(unchanged onmasteras of June 2026):(distanceDragged.abs() / renderObject.preferredLineHeight)becomes:InfinitywhenrenderObject.preferredLineHeight == 0(division by zero with a positive numerator), orNaNwhendistanceDraggedis non-finite — which happens whenrenderObject.globalToLocal(...)is fed through a non-invertible transform (a degenerate/zero-size render object during a layout transition), producingNaN/Infinitycoordinates at the two call sites (text_selection.dart:782-785and:915-918)..floor()returnsint, so on a non-finite double it throwsUnsupportedError('Infinity or NaN toInt').Proposed fix
Guard against a non-finite result before calling
.floor():I'm happy to send a PR with this guard if it's welcome.
Flutter / Dart version
Affected platform: Android (observed on Samsung Galaxy Z Flip 5 /
SM-F731N). The vulnerable code is platform-agnostic; the path is just easiest to hit on foldables.Confirmed the same unguarded
_getHandleDyis present onmaster, so this is not fixed in any release after 3.38.9.