Skip to content

Fix null check crash in MultiscriptsLayoutDelegate intrinsic layout#132

Open
passte-llc wants to merge 1 commit into
simpleclub:mainfrom
passte-llc:fix/multiscripts-null-check
Open

Fix null check crash in MultiscriptsLayoutDelegate intrinsic layout#132
passte-llc wants to merge 1 commit into
simpleclub:mainfrom
passte-llc:fix/multiscripts-null-check

Conversation

@passte-llc

Copy link
Copy Markdown

Summary

Fixes #1.

MultiscriptsLayoutDelegate.performHorizontalIntrinsicLayout and
performVerticalIntrinsicLayout (lib/src/render/layout/multiscripts.dart)
use a non-null assertion (childrenWidths[_ScriptPos.base]!,
childrenHeights[_ScriptPos.base]!, childrenBaselines[_ScriptPos.base]!)
on map lookups that are not guaranteed to succeed during intrinsic-size
layout probing. When the base script's size/baseline hasn't been recorded
in the map yet at the time intrinsic layout runs, the assertion throws
Null check operator used on a null value (_CastError) inside
performLayout(). Because Flutter's rendering pipeline does not catch
layout exceptions, this crashes the entire widget subtree and freezes the
UI — there is no graceful degradation.

Reproduction

We hit this consistently in production with expressions that mix subscript
characters inside \mathrm{}, e.g.:

\mathrm{CL_r = CL_{tot} \times f_e}

rendered via Math.tex(...) inside a layout that triggers intrinsic width
computation (e.g. content that may need to wrap). Issue #1 reports the same
stack trace (multiscripts.dart:136) triggered via \displaystyle{...}
inside a RichText-hosted Math.tex widget, with a workaround of removing
displaystyle — but the underlying null-check panic in
MultiscriptsLayoutDelegate was never fixed upstream.

Fix

Replace the three non-null assertions on _ScriptPos.base lookups with a
?? 0.0 fallback:

  • performHorizontalIntrinsicLayout: childrenWidths[_ScriptPos.base] ?? 0.0
  • performVerticalIntrinsicLayout: childrenHeights[_ScriptPos.base] ?? 0.0
    and childrenBaselines[_ScriptPos.base] ?? 0.0

When the base entry is present (the expected/common case), behavior is
byte-for-byte identical to before — ?? 0.0 is a no-op unless the map
lookup is null. When the base entry is absent (the previously-crashing
case), layout now degrades gracefully instead of throwing: the widget
continues to render (with a fallback intrinsic size of 0.0 for the
missing entry) instead of freezing the whole app.

Backward compatibility

  • No public API / signature changes.
  • No behavior change in the non-crashing path.
  • Only affects intrinsic-size probing; final paint layout is untouched.

Testing

  • dart analyze lib/src/render/layout/multiscripts.dart: no issues.
  • Verified against the exact expression from our production repro
    (\mathrm{CL_r = CL_{tot} \times f_e}) rendered inside a layout that
    exercises intrinsic width computation — previously threw
    Null check operator used on a null value synchronously on
    performLayout(), now renders without throwing. We have been shipping
    this exact 3-line patch in a production Flutter app (iOS, App Store)
    since 2026-05 with no regressions in math rendering.

performHorizontalIntrinsicLayout and performVerticalIntrinsicLayout used
non-null assertions on childrenWidths/childrenHeights/childrenBaselines
lookups for _ScriptPos.base that are not guaranteed to succeed during
intrinsic-size probing. When the base entry is missing, the assertion
throws 'Null check operator used on a null value' inside performLayout(),
which freezes the whole widget subtree.

Replace the three assertions with a '?? 0.0' fallback: identical behavior
when the entry is present, graceful degradation instead of a crash when
it is absent.

Fixes simpleclub#1

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@CLAassistant

Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

@coderabbitai

coderabbitai Bot commented Jul 6, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This change modifies MultiscriptsLayoutDelegate in multiscripts.dart to prevent a null check crash during intrinsic layout. Force-unwrapped map lookups for _ScriptPos.base in horizontal and vertical intrinsic layout methods are replaced with null-coalescing fallbacks defaulting to 0.0.

Changes

Intrinsic Layout Null-Safety Fix

Layer / File(s) Summary
Fallback for missing base script position
lib/src/render/layout/multiscripts.dart
Replaces force-unwrapped childrenWidths[_ScriptPos.base], childrenHeights[_ScriptPos.base], and childrenBaselines[_ScriptPos.base] lookups with ?? 0.0 fallbacks in performHorizontalIntrinsicLayout and performVerticalIntrinsicLayout, adding explanatory comments to avoid a null check crash during intrinsic-size probing.

Estimated code review effort: 1 (Trivial) | ~5 minutes

Related issues: Fixes a _CastError/null check operator crash ("Null check operator used on a null value") thrown during performLayout() in MultiscriptsLayoutDelegate.performHorizontalIntrinsicLayout when intrinsic layout is triggered (e.g., via RichText).

Suggested labels: bug, fix

Suggested reviewers: znjameswu

🐰 A tiny hop through code so tight,
No more null checks crashing in the night,
Base scripts missing? Zero will do,
Intrinsic layouts now see it through!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: preventing a null-check crash in MultiscriptsLayoutDelegate intrinsic layout.
Description check ✅ Passed The description directly matches the code change and explains the crash fix and its impact.
Linked Issues check ✅ Passed The patch addresses issue #1 by replacing the failing base-script null assertions in intrinsic layout with safe fallbacks.
Out of Scope Changes check ✅ Passed The changes stay confined to the reported crash fix in multiscripts.dart and add no unrelated behavior or API changes.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
lib/src/render/layout/multiscripts.dart (1)

136-142: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider adding a regression test.

This fixes a reported crash (Null check operator used on a null value at Line 136). Since base is a required, always-present child in Multiscripts's widget tree, the missing map entry is specific to CustomLayout's intrinsic-probing pass — worth a targeted golden/unit test (e.g. an intrinsic-width query on a nested subscript/superscript expression) to guard against regressing back to the unsafe ! lookup.

Also applies to: 184-192

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/src/render/layout/multiscripts.dart` around lines 136 - 142, Add a
regression test for Multiscripts to cover the intrinsic-probing path that can
omit _ScriptPos.base from childrenWidths. Use the Multiscripts/CustomLayout flow
exercised by nested subscript/superscript expressions and assert an
intrinsic-width query no longer throws the null check exception. Place the test
alongside the Multiscripts layout logic so it protects the baseSize lookup from
regressing back to an unsafe ! access.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@lib/src/render/layout/multiscripts.dart`:
- Around line 136-142: Add a regression test for Multiscripts to cover the
intrinsic-probing path that can omit _ScriptPos.base from childrenWidths. Use
the Multiscripts/CustomLayout flow exercised by nested subscript/superscript
expressions and assert an intrinsic-width query no longer throws the null check
exception. Place the test alongside the Multiscripts layout logic so it protects
the baseSize lookup from regressing back to an unsafe ! access.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 6539cd66-1895-411e-b696-b9f8e933a0bc

📥 Commits

Reviewing files that changed from the base of the PR and between 75a6f61 and 036bb03.

📒 Files selected for processing (1)
  • lib/src/render/layout/multiscripts.dart

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

Null check operator used on a null value

2 participants