Browse Source

Fix StackOverflow when a NaN offset is set on ScrollViewer (#21558)

ScrollViewer.Offset and ScrollContentPresenter.Offset form a two-way coerced
binding whose convergence is decided with the default equality comparer. Because
NaN != NaN, a NaN offset is reported "changed" on every pass, so the coerce/raise
cycle never converges and recurses until the stack overflows -- an unrecoverable
crash (on Mono/AOT targets such as Android it surfaces as a native SIGSEGV).

CoerceOffset's Clamp passed NaN straight through (NaN < min and NaN > max are
both false), so a NaN component survived coercion. Sanitize it to the lower
bound so the coerce always converges, regardless of where the NaN came from.

Fixes #21444

Co-authored-by: Julien Lebosquain <julien@lebosquain.net>
pull/21448/head
Nicholas Lachapelle 4 months ago
committed by GitHub
parent
commit
fcbd5d037a
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 8
      src/Avalonia.Controls/ScrollViewer.cs
  2. 19
      tests/Avalonia.Controls.UnitTests/ScrollViewerTests.cs

8
src/Avalonia.Controls/ScrollViewer.cs

@ -696,6 +696,14 @@ namespace Avalonia.Controls
private static double Clamp(double value, double min, double max)
{
// A NaN offset must never survive. Offset is two-way coerced between the ScrollViewer and its
// ScrollContentPresenter; because NaN != NaN, a NaN offset never compares equal, so the
// coerce/raise cycle never converges and recurses until it overflows the stack (see #21444).
if (double.IsNaN(value))
{
return min;
}
return (value < min) ? min : (value > max) ? max : value;
}

19
tests/Avalonia.Controls.UnitTests/ScrollViewerTests.cs

@ -44,6 +44,25 @@ namespace Avalonia.Controls.UnitTests
Assert.Equal(new Vector(10, 10), target.Offset);
}
[Fact]
public void Setting_Offset_To_NaN_Does_Not_Cause_Infinite_Coerce_Recursion()
{
var target = new ScrollViewer
{
Template = new FuncControlTemplate<ScrollViewer>(CreateTemplate),
Content = "Foo",
Extent = new Size(100, 100),
Viewport = new Size(10, 10),
};
InitializeScrollViewer(target);
target.Offset = new Vector(0, double.NaN);
Assert.False(double.IsNaN(target.Offset.X));
Assert.False(double.IsNaN(target.Offset.Y));
}
[Fact]
public void Test_ScrollToHome()
{

Loading…
Cancel
Save