Skip to content

TextSelectionOverlay._getHandleDy crashes with "Unsupported operation: Infinity or NaN toInt" when dragging a selection handle (preferredLineHeight == 0 / non-finite drag position) #187644

Description

@tonystarup

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.invokeCallbackFlutterError.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.

Metadata

Metadata

Labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions