From 2f9a8515c29eaf0d651f230254c098d0f36a5f10 Mon Sep 17 00:00:00 2001 From: trippusultan Date: Mon, 1 Jun 2026 11:31:48 +0000 Subject: [PATCH 1/2] Fix RangeError in _matchByNavigatorKeyForGoRoute during state restoration When newMatchedLocation is empty (not '/'), the offset computation 'newMatchedLocation.length + 1' produces 1, which exceeds uri.path.length when the URI path is also empty. This causes ''.substring(1) to throw an unhandled RangeError in release builds where the preceding assert() guards are stripped. Clamp the offset to prevent the RangeError. Fixes flutter/flutter#185948 --- packages/go_router/lib/src/match.dart | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/go_router/lib/src/match.dart b/packages/go_router/lib/src/match.dart index 87f588a3eeef..f3cf856dd66f 100644 --- a/packages/go_router/lib/src/match.dart +++ b/packages/go_router/lib/src/match.dart @@ -256,9 +256,10 @@ abstract class RouteMatchBase with Diagnosticable { assert(uriPathToCompare.startsWith(newMatchedLocationToCompare)); assert(remainingLocation.isNotEmpty); - final String childRestLoc = uri.path.substring( - newMatchedLocation.length + (newMatchedLocation == '/' ? 0 : 1), - ); + final int offset = + newMatchedLocation.length + (newMatchedLocation == '/' ? 0 : 1); + final String childRestLoc = + offset >= uri.path.length ? '' : uri.path.substring(offset); Map?, List>? subRouteMatches; for (final RouteBase subRoute in route.routes) { From 8423a67547c9ce3f057068df6b190742e312af57 Mon Sep 17 00:00:00 2001 From: trippusultan Date: Mon, 1 Jun 2026 12:18:25 +0000 Subject: [PATCH 2/2] Add regression tests for _matchByNavigatorKeyForGoRoute RangeError fix Two tests verifying that RouteMatchBase.match() does not throw RangeError when: 1. ShellRoute with empty child path (URI path doesn't start with route) 2. Nested route with path shorter than matchedLocation Fixes flutter/flutter#185948 --- packages/go_router/test/match_test.dart | 57 +++++++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/packages/go_router/test/match_test.dart b/packages/go_router/test/match_test.dart index 1b12d2e71d7c..9b7aeb314da9 100644 --- a/packages/go_router/test/match_test.dart +++ b/packages/go_router/test/match_test.dart @@ -86,6 +86,63 @@ void main() { expect(matches2.length, 1); expect(matches1.first.pageKey, matches2.first.pageKey); }); + + test('ShellRoute with empty child path does not throw RangeError', () { + // Regression test for https://github.com/flutter/flutter/issues/185948 + // When state restoration produces an URI whose path does not start + // with the matched route path, the substring offset in + // _matchByNavigatorKeyForGoRoute must not exceed uri.path.length. + final route = ShellRoute( + builder: _shellBuilder, + routes: [ + GoRoute( + path: '/a', + builder: _builder, + routes: [ + GoRoute(path: 'b', builder: _builder), + ], + ), + ], + ); + final pathParameters = {}; + expect( + () => RouteMatchBase.match( + route: route, + pathParameters: pathParameters, + uri: Uri.parse('/'), + rootNavigatorKey: GlobalKey(), + ), + returnsNormally, + ); + }); + + test('nested route with path shorter than matchedLocation does not throw', () { + // Regression test for https://github.com/flutter/flutter/issues/185948 + // When newMatchedLocation is longer than uri.path, the offset + // computation in _matchByNavigatorKeyForGoRoute must be clamped. + final route = ShellRoute( + builder: _shellBuilder, + routes: [ + GoRoute( + path: '', + builder: _builder, + routes: [ + GoRoute(path: '', builder: _builder), + ], + ), + ], + ); + final pathParameters = {}; + expect( + () => RouteMatchBase.match( + route: route, + pathParameters: pathParameters, + uri: Uri.parse('/'), + rootNavigatorKey: GlobalKey(), + ), + returnsNormally, + ); + }); }); test('complex parentNavigatorKey works', () {