Description
Steps to reproduce
A RenderBox that has no baseline returns null from computeDryBaseline.
The documented contract explicitly allows this:
The implementation must return a value that represents the distance from the
top of the box to the first baseline of the box's contents, for the given
constraints, ornullif the [RenderBox] has no baselines.
Wrap such a box in any proxy box and ask for a dry baseline:
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter_test/flutter_test.dart';
class NoBaseline extends SingleChildRenderObjectWidget {
const NoBaseline({super.key});
@override
RenderObject createRenderObject(BuildContext context) => RenderNoBaseline();
}
class RenderNoBaseline extends RenderBox {
@override
void performLayout() => size = constraints.constrain(const Size(40, 20));
@override
Size computeDryLayout(BoxConstraints constraints) =>
constraints.constrain(const Size(40, 20));
@override
double? computeDryBaseline(BoxConstraints constraints, TextBaseline baseline) =>
null; // documented: null means "no baselines"
}
void main() {
testWidgets('proxy box over a child with no baseline', (tester) async {
await tester.pumpWidget(MaterialApp(home: Scaffold(
body: IntrinsicWidth(
child: Text.rich(TextSpan(children: [
WidgetSpan(
alignment: PlaceholderAlignment.baseline,
baseline: TextBaseline.alphabetic,
child: Semantics(container: true, child: const NoBaseline()),
),
])),
),
)));
expect(tester.takeException(), isNull);
});
}Expected results
No exception. The child reported that it has no baseline, which is a valid
answer, so the proxy should report the same.
Actual results
The RenderSemanticsAnnotations class does not implement "computeDryBaseline".
#2 RenderBox.computeDryBaseline (box.dart:2184)
#3 RenderProxyBoxMixin.computeDryBaseline (proxy_box.dart:106)
#4 RenderBox._computeDryBaseline (box.dart:2140)
Cause
RenderProxyBoxMixin.computeDryBaseline collapses two different cases into one
??:
double? computeDryBaseline(covariant BoxConstraints constraints, TextBaseline baseline) {
final double? result = child?.getDryBaseline(constraints, baseline);
return result ?? super.computeDryBaseline(constraints, baseline);
}result is null both when there is no child and when the child answered "I
have no baseline". Only the first case should fall through to
super.computeDryBaseline, which exists to assert that the subclass did not
implement the method. In the second case the proxy throws even though
everything behaved correctly.
There is a second effect that makes this hard to trace. The throw happens
inside RenderBox._computeDryBaseline, after it has set
_computingThisDryBaseline and the library level renderBoxDoingDryBaseline.
The exception escapes past the cleanup, so if any caller catches it, those
flags stay set and every later RenderBox.size read in the same frame trips
'renderBoxDoingDryBaseline == null'. The reported failure then has no
dry baseline frames in its stack at all, because the dry pass has already
unwound. That is what Sub6Resources/flutter_html#1510 looks like from the
outside.
Proposed fix
@override
@protected
double? computeDryBaseline(covariant BoxConstraints constraints, TextBaseline baseline) {
final RenderBox? child = this.child;
return child == null
? super.computeDryBaseline(constraints, baseline)
: child.getDryBaseline(constraints, baseline);
}I verified this on 3.41.2:
- the reproduction above passes
Sub6Resources/flutter_html#1510, which is the same bug reached through a
real package, passes- reverting the change brings both failures back
flutter test test/rendering/passes, 813 tests including the regression
test below
Regression test, in the style of the existing file:
test('computeDryBaseline returns null when the child reports no baseline', () {
final RenderBox child = RenderBoxWithoutBaseline();
final RenderProxyBox parent = RenderProxyBox(child);
const BoxConstraints constraints = BoxConstraints(maxWidth: 100, maxHeight: 100);
layout(parent, constraints: constraints);
// Returning null from computeDryBaseline is the documented way for a box to
// say it has no baselines. The proxy must forward that, not treat it as an
// unimplemented method.
expect(parent.getDryBaseline(constraints, TextBaseline.alphabetic), isNull);
});
class RenderBoxWithoutBaseline extends RenderBox {
@override
void performLayout() => size = constraints.constrain(const Size(40, 20));
@override
Size computeDryLayout(BoxConstraints constraints) =>
constraints.constrain(const Size(40, 20));
@override
double? computeDryBaseline(BoxConstraints constraints, TextBaseline baseline) => null;
}Without the fix that test fails with
The RenderProxyBox class does not implement "computeDryBaseline".
I have this ready as a PR against master if that is the preferred next step.
Flutter version
Reproduced on stable 3.41.2, macOS arm64, Dart 3.11.0. The checkout is at the
release commit itself:
$ git describe --tags --exact-match HEAD
3.41.2
$ git rev-parse --short HEAD
90673a4eef2 ("Bump engine version for 3.41.2 stable release (#182559)")
flutter doctor reports the channel as [user-branch] only because the local
clone's remote is not named origin; the framework source is the unmodified
3.41.2 stable tag.
Activity
suryaKommana2662 commentedon Jul 20, 2026
@Yusufihsangorgel Thanks for the report. I was able to reproduce this issue locally using the provided widget test.
The initial failure matches the reported behavior:
The stack trace points to
RenderProxyBoxMixin.computeDryBaseline, with the call sequence including:After the initial rendering exception, I also observed additional layout and semantics assertions (such as
RenderBox was not laid outand subsequent semantics-related assertions). Based on the provided reproduction and my local verification, the issue is reproducible. Routing this toteam-frameworkfor further investigation.Flutter Doctor Output
Yusufihsangorgel commentedon Jul 20, 2026
Thanks for confirming. Fix is up in #189723, with a regression test based on this reproduction.
Return null from RenderProxyBoxMixin.computeDryBaseline when the chil…
github-actions commentedon Aug 13, 2026
This thread has been automatically locked since there has not been any recent activity after it was closed. If you are still experiencing a similar issue, please open a new bug, including the output of
flutter doctor -vand a minimal reproduction of the issue.