Skip to content

Flutter: port ordinary apps unmodified (language, dart:io/convert, pub packages, plugin stubs) - #6055

Merged
shai-almog merged 36 commits into
masterfrom
flutter-language-completeness
Oct 22, 2026
Merged

shai-almog merged 36 commits into
masterfrom
flutter-language-completeness

Conversation

@shai-almog

Copy link
Copy Markdown
Collaborator

Ordinary Flutter apps should now import and build with no edits. The approach is hybrid: pure-Dart pub packages are transpiled from source, and native plugins get Java stub packs.

Language (dart-transpiler)

  • Added: throw expressions, async*/await for, mixin application classes, and redirecting constructors and factories.
  • Added: function-typed parameters, mixin on clauses, generator and generic closures, super assignment, and >>>.
  • Added: dart:convert, dynamic dispatch, arrow factories, and mixed list literals.
  • Callback parameters are typed from the receiver's type arguments.
  • When an app class has the same name as a stub, the app class wins.

Runtime (dart-runtime)

  • A dart:io subset: File, Directory, Platform, HttpClient, and WebSocket.
  • Fuller Streams.

Import (build-engine)

  • Packages listed in package_config.json are transpiled from the pub cache.
  • Only packages that are native and have no stub are reported as unsupported.

Plugin stubs (flutter-runtime)

  • Each one runs on the matching Codename One API: shared_preferences, path_provider, cross_file, share_plus, package_info_plus, device_info_plus, connectivity_plus, geolocator, image_picker, app_links, flutter_local_notifications, firebase_core/auth/analytics/messaging, and google_maps_flutter.
  • The generated entry point forwards push, notification, and deep-link callbacks.

Widgets

  • PopScope and WillPopScope can block a back navigation.
  • StreamBuilder is new, and the FutureBuilder, StreamBuilder, and Dismissible callbacks are typed.

Acceptance

  • Two apps in scripts/flutter-apps:
    • pet_adoption: dio, shared_preferences, provider.
    • field_notes: firebase, notifications, share, connectivity.
  • run-acceptance.sh imports each app, transpiles it, and compiles the result.
  • The new flutter-acceptance.yml runs it in CI.

Testing

  • dart-runtime: 200 tests pass.
  • flutter-runtime: 731 tests pass.
  • dart-transpiler: 142 tests pass, including the new behavior cases.
  • Importer: 24 tests pass.
  • Both acceptance apps pass locally.
  • SpotBugs did not run on build-engine locally; pr.yml covers it.

🤖 Generated with Claude Code

shai-almog and others added 6 commits October 19, 2026 19:19
…on classes, redirecting constructors, function-typed params, mixin on-clauses

Real Flutter apps lean on these constructs (bloc's async* event handlers, `mixin X on State<W>`, `: this(...)` constructors), and each one used to fail the build with E01xx/E03xx.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s, super assignment, >>> operator

Also fixes two pre-existing emitter bugs real code hits: a factory in a
generic class referenced the class's type variable from a static method,
and a class declaring factory constructors could not be implemented
(the shape of every freezed model).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ed list literals

dart:convert (json, utf8, ascii, latin1, base64, LineSplitter) in dart-runtime
with a stub, and a Dyn runtime that dispatches member reads, writes, calls and
indexing on dynamic receivers -- maps, lists, strings, numbers, functions, and
transpiled classes through generated DynamicObject members.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
File, Directory and FileSystemEntity over FileSystemStorage (an in-memory file
system before Codename One starts), Platform from Display.getPlatformName(),
HttpClient/HttpClientRequest/HttpClientResponse over ConnectionRequest,
HttpHeaders, ContentType, HttpStatus and WebSocket over
com.codename1.io.WebSocket. Stream gains its factories, terminal operations,
derived streams and transform(); the dart:convert codecs are
StreamTransformers. Future.then types an untyped callback parameter from the
future's value type.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ceptance app

Stub packs (flutter-runtime com.codename1.flutter.plugins.*, META-INF/dart/plugin_*.dart):
bloc/flutter_bloc, equatable, json_annotation, shared_preferences, path_provider, dio,
go_router (with MaterialApp.router) and cached_network_image, each with a headless
behavior test. The importer lists them as covered and transpiles other pure-Dart pub
packages from their sources.

Transpiler: stub extension methods, witness inference from a typed selector parameter
(context.select), explicit Java type arguments on generic stub calls, an empty {} read
as a Set where a Set is expected, Icons style-variant fallback, and javac-accurate
reachability after try/catch and if/else. Runtime: the full Material icon set,
FilledButton, Provider create blocks, DateTime.parse/toIso8601String.

scripts/flutter-apps/pet_adoption (bloc, go_router, dio, shared_preferences, equatable,
json_annotation, cached_network_image) imports, transpiles and compiles unmodified via
scripts/flutter-apps/Acceptance.java.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… CI job

Plugin stub packs backed by Codename One APIs: cross_file, share_plus,
package_info_plus, device_info_plus, connectivity_plus, geolocator,
image_picker, app_links, flutter_local_notifications, firebase_core/auth/
analytics/messaging and google_maps_flutter. The importer marks them covered,
and the generated entry point forwards push, local-notification and deep-link
callbacks only when the app depends on the matching plugin.

Widget fidelity: PopScope and WillPopScope veto Navigator.maybePop and the
toolbar back command. StreamBuilder is new. FutureBuilder and StreamBuilder
builders are typed, and Dismissible callbacks are typed too.

Transpiler:
- A user enum or class that shares a stub's name wins.
- Explicit type arguments on non-generic Java methods are cast.
- Callback parameters take the receiver's type arguments, so a closure
  passed to stream.listen gets typed parameters.

A second acceptance app, field_notes (firebase, notifications, share,
connectivity, PopScope, StreamBuilder), joins pet_adoption.
scripts/flutter-apps/run-acceptance.sh runs both, and
flutter-acceptance.yml runs it in CI.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@shai-almog

Copy link
Copy Markdown
Collaborator Author

@codex review

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 19, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review 🔄 Running since 2026-10-21T14:05:04.656272Z 03a5de8 Manual request
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f0322cca0c

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

if (path.startsWith("file:")) {
return path;
}
return path.startsWith("/") ? "file://" + path : path;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Resolve relative paths against Directory.current

For every on-device File or Directory created with a relative path such as File('notes.json'), this passes the raw name directly to FileSystemStorage. The platform implementations interpret that relative to the process working directory (and Android commonly ends up at an unwritable location), while Dart resolves it against Directory.current, which this runtime initializes to the app home. Consequently ordinary relative file reads and writes can fail or access the wrong location; qualify these paths with the current/app-home directory before calling FileSystemStorage.

Useful? React with 👍 / 👎.

}

private void push(Object event) {
queue.addLast(event);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Preserve null events in StreamIterator

When an await for loop or any derived stream backed by StreamIterator receives a legitimate null event, ArrayDeque.addLast(null) throws. Stream.deliverData catches and logs that exception, so the event is silently dropped; if the source remains open, the pending moveNext() can wait forever. Nullable streams such as a controller emitting null or Stream.periodic with a null computation therefore produce incorrect results or hang, so the queue needs a non-null sentinel for null data.

Useful? React with 👍 / 👎.

Comment on lines +79 to +83
public Object decode(Object input, Boolean allowMalformed) {
if (kind == BASE64 || kind == BASE64_URL) {
return Uint8List.fromBytes(unbase64((String) input));
}
return text(bytesOf(input));

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Reject malformed UTF-8 unless explicitly allowed

For utf8.decode with the default allowMalformed: false, this delegates to Java's new String(..., "UTF-8"), which silently replaces malformed sequences with U+FFFD rather than throwing Dart's FormatException; the allowMalformed argument is ignored entirely. Inputs such as [0xff] therefore appear successfully decoded and can let corrupt protocol or file data pass validation.

Useful? React with 👍 / 👎.

Comment on lines +54 to +57
protected void firstBuild() {
// After Element.mount linked the parent, so create(context) can read the
// providers above it -- `create: (c) => Cubit(c.read<Repo>())`.
supplyCreated(widget());

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Honor Provider's default lazy creation

Whenever a Provider(create: ...) is mounted, firstBuild() invokes the factory immediately even though Provider.lazy defaults to true and its setter is otherwise unused. Providers that are never read will still start requests, allocate blocs/resources, or throw during construction, unlike the package contract where creation is deferred until the first lookup; only lazy: false should create during the initial build.

Useful? React with 👍 / 👎.

Comment on lines +103 to +105
// Dart rounds a seventh digit and beyond into the microseconds.
if (frac.length() > 6 && frac.charAt(6) >= '5') {
all++;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Truncate excess DateTime fractional digits

For timestamps with more than six fractional digits, Dart's parser truncates after microseconds, but this code rounds based on the seventh digit. Thus DateTime.parse('...00.1234567Z') becomes .123457Z instead of .123456Z, and .9999999 even rolls into the next second, causing parsed instants and equality/order comparisons to differ from Dart.

Useful? React with 👍 / 👎.

Comment on lines +71 to +75
if (dots >= 3) {
int last = full.lastIndexOf('.');
version = full.substring(0, last);
build = full.substring(last + 1);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Report the imported pubspec build number

For the normal Flutter version form 1.2.3+42, the importer sets AppVersion to the marketing part 1.2.3 and writes 42 to the native build-number hints. Since this code only invents a build number when AppVersion has at least three dots, PackageInfo.fromPlatform() returns an empty build number for virtually every imported app despite the installed Android/iOS build carrying 42, breaking version checks and diagnostics that use package_info_plus.

Useful? React with 👍 / 👎.

Comment on lines +65 to +66
this.active = req;
this.onCancel = cancelled;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Cancel every request attached to a CancelToken

When the same CancelToken is passed to multiple concurrent Dio requests, each attach overwrites the preceding request and callback. Calling cancel() therefore kills and completes only the most recently attached request, while the earlier requests continue running despite sharing the canceled token; retain all active request/callback pairs until they complete and cancel each of them.

Useful? React with 👍 / 👎.

Comment on lines +80 to +82
public Future<DartUri> getLatestLink() {
return getInitialLink();
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Return the most recently received app link

After the application receives a second deep link while running, appStarted() emits that link but leaves initial unchanged, and this implementation still returns the first launch link. Callers using getLatestLink() after a resume therefore navigate using stale data; track the most recently consumed AppArg separately from the one-shot initial link.

Useful? React with 👍 / 👎.

Comment on lines +99 to +101
synchronized (listeners) {
listeners.add(c);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Remove canceled Firebase auth stream controllers

Every call to authStateChanges(), idTokenChanges(), or userChanges() permanently adds a controller here, but no onCancel handler closes or removes it. In applications that recreate and cancel these subscriptions as widgets are mounted, the singleton retains one controller per old subscription for the lifetime of the process, and each auth update repeatedly scans them; remove the controller when its last listener cancels.

Useful? React with 👍 / 👎.

Comment on lines +1117 to +1118
if (c.superclass != null && !inherits) {
return false;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Emit dynamic members for models extending runtime classes

Any application class whose direct superclass is supplied by a runtime stub is excluded from DynamicObject, even when it is ordinary model data. For example, a Pet extends Equatable accessed through dynamic pet will throw NoSuchMethodError for pet.name, because Dyn.get has neither generated member dispatch nor reflection; restrict this optimization to known framework lifecycle classes rather than every non-program superclass.

Useful? React with 👍 / 👎.

@github-actions

github-actions Bot commented Oct 19, 2026 •

Copy link
Copy Markdown
Contributor

✅ Continuous Quality Report

Test & Coverage

Static Analysis

  • SpotBugs [Report archive]
    • ✅ ByteCodeTranslator: 0 findings (no issues)
    • ✅ android: 0 findings (no issues)
    • ✅ android-compat: 0 findings (no issues)
    • ✅ android-res-compiler: 0 findings (no issues)
    • ✅ backend: 0 findings (no issues)
    • ✅ backend-test: 0 findings (no issues)
    • ✅ build-engine: 0 findings (no issues)
    • ✅ build-hint-catalog: 0 findings (no issues)
    • ✅ build-hint-tools: 0 findings (no issues)
    • ✅ codenameone-gradle-plugin: 0 findings (no issues)
    • ✅ codenameone-maven-plugin: 0 findings (no issues)
    • ✅ core-unittests: 0 findings (no issues)
    • ✅ ios: 0 findings (no issues)
    • ✅ javac: 0 findings (no issues)
    • ✅ project-model: 0 findings (no issues)
  • ✅ PMD: 0 findings (no issues) [Report archive]
  • ✅ Checkstyle: 0 findings (no issues) [Report archive]

Generated automatically by the PR CI workflow.

@github-actions

github-actions Bot commented Oct 19, 2026 •

Copy link
Copy Markdown
Contributor

Flutter vs Codename One

No benchmark results were produced.

@shai-almog

shai-almog commented Oct 19, 2026 •

Copy link
Copy Markdown
Collaborator Author

Compared 178 screenshots: 178 matched.

Native Android coverage

  • 📊 Line coverage: 17.40% (30679/176345 lines covered) [HTML preview] (artifact android-coverage-report, jacocoAndroidReport/html/index.html)
    • Other counters: instruction 15.70% (144888/923101), branch 10.66% (10014/93896), complexity 8.22% (7450/90604), method 12.01% (5157/42930), class 20.74% (1059/5105)
    • Lowest covered classes
      • kotlin.collections.kotlin.collections.ArraysKt___ArraysKt – 0.00% (0/6367 lines covered)
      • kotlin.collections.unsigned.kotlin.collections.unsigned.UArraysKt___UArraysKt – 0.00% (0/2384 lines covered)
      • org.jacoco.agent.rt.internal_0e20598.asm.org.jacoco.agent.rt.internal_0e20598.asm.ClassReader – 0.00% (0/1524 lines covered)
      • org.jacoco.agent.rt.internal_0e20598.asm.org.jacoco.agent.rt.internal_0e20598.asm.MethodWriter – 0.00% (0/922 lines covered)
      • kotlin.sequences.kotlin.sequences.SequencesKt___SequencesKt – 0.00% (0/736 lines covered)
      • com.google.common.cache.com.google.common.cache.LocalCache$Segment – 0.00% (0/726 lines covered)
      • okio.okio.Buffer – 0.00% (0/687 lines covered)
      • kotlin.text.kotlin.text.StringsKt___StringsKt – 0.00% (0/625 lines covered)
      • com.codename1.androidcompat.rt.com.codename1.androidcompat.rt.TextXmlPullParser – 0.00% (0/623 lines covered)
      • com.codename1.androidcompat.androidx.constraintlayout.core.widgets.com.codename1.androidcompat.androidx.constraintlayout.core.widgets.Flow – 0.00% (0/576 lines covered)

✅ Native Android screenshot tests passed.

Native Android coverage

  • 📊 Line coverage: 17.40% (30679/176345 lines covered) [HTML preview] (artifact android-coverage-report, jacocoAndroidReport/html/index.html)
    • Other counters: instruction 15.70% (144888/923101), branch 10.66% (10014/93896), complexity 8.22% (7450/90604), method 12.01% (5157/42930), class 20.74% (1059/5105)
    • Lowest covered classes
      • kotlin.collections.kotlin.collections.ArraysKt___ArraysKt – 0.00% (0/6367 lines covered)
      • kotlin.collections.unsigned.kotlin.collections.unsigned.UArraysKt___UArraysKt – 0.00% (0/2384 lines covered)
      • org.jacoco.agent.rt.internal_0e20598.asm.org.jacoco.agent.rt.internal_0e20598.asm.ClassReader – 0.00% (0/1524 lines covered)
      • org.jacoco.agent.rt.internal_0e20598.asm.org.jacoco.agent.rt.internal_0e20598.asm.MethodWriter – 0.00% (0/922 lines covered)
      • kotlin.sequences.kotlin.sequences.SequencesKt___SequencesKt – 0.00% (0/736 lines covered)
      • com.google.common.cache.com.google.common.cache.LocalCache$Segment – 0.00% (0/726 lines covered)
      • okio.okio.Buffer – 0.00% (0/687 lines covered)
      • kotlin.text.kotlin.text.StringsKt___StringsKt – 0.00% (0/625 lines covered)
      • com.codename1.androidcompat.rt.com.codename1.androidcompat.rt.TextXmlPullParser – 0.00% (0/623 lines covered)
      • com.codename1.androidcompat.androidx.constraintlayout.core.widgets.com.codename1.androidcompat.androidx.constraintlayout.core.widgets.Flow – 0.00% (0/576 lines covered)

Benchmark Results

Detailed Performance Metrics

Metric Duration
SIMD kernel backend scalar fallback (no native SIMD)
SIMD int-add (64K x300) java 186ms / native 317ms = 0.5x speedup
SIMD float-mul (64K x300) java 190ms / native 242ms = 0.7x speedup
SIMD kernel correctness PASS (native result == scalar reference)
Base64 payload size 8192 bytes
Base64 benchmark iterations 6000
Base64 SIMD byte path gated to scalar (CPU autovectorizes scalar; explicit SIMD not beneficial here)
Base64 CN1 encode 61.000 ms
Base64 CN1 decode 68.000 ms
Base64 native encode 371.000 ms
Base64 encode ratio (CN1/native) 0.164x (83.6% faster)
Base64 native decode 234.000 ms
Base64 decode ratio (CN1/native) 0.291x (70.9% faster)
Image encode benchmark status skipped (SIMD unsupported)

@shai-almog

shai-almog commented Oct 19, 2026 •

Copy link
Copy Markdown
Collaborator Author

Compared 214 screenshots: 214 matched.
✅ JavaScript-port screenshot tests passed.

@github-actions

github-actions Bot commented Oct 19, 2026 •

Copy link
Copy Markdown
Contributor

✅ ByteCodeTranslator Quality Report

Test & Coverage

  • ✅ Tests: 879 total, 0 failed, 57 skipped

Benchmark Results

  • Execution Time: 6779 ms

  • Hotspots (Top 20 sampled methods):

    • 12.37% com.codename1.tools.translator.IteratorEscape.mangle (133 samples)
    • 6.33% com.codename1.tools.translator.BytecodeMethod.equals (68 samples)
    • 6.14% java.lang.System.identityHashCode (66 samples)
    • 4.65% java.lang.Object.hashCode (50 samples)
    • 3.81% java.lang.StringBuilder.append (41 samples)
    • 2.70% java.util.ArrayList.indexOf (29 samples)
    • 2.60% com.codename1.tools.translator.classfile.analysis.Frame.execute (28 samples)
    • 1.77% com.codename1.tools.translator.BytecodeMethod.updateInlinableFieldDependencies (19 samples)
    • 1.67% java.util.AbstractCollection.containsAll (18 samples)
    • 1.58% com.codename1.tools.translator.bytecodes.Invoke.resolveDirectTarget (17 samples)
    • 1.40% java.util.HashMap.hash (15 samples)
    • 1.30% com.codename1.tools.translator.ByteCodeClass.fillVirtualMethodTable (14 samples)
    • 1.12% com.codename1.tools.translator.JavascriptReachability.enqueueResolved (12 samples)
    • 1.12% java.lang.String.equals (12 samples)
    • 1.12% com.codename1.tools.translator.BytecodeMethod.optimize (12 samples)
    • 1.12% com.codename1.tools.translator.bytecodes.Invoke.findMethodUp (12 samples)
    • 1.12% com.codename1.tools.translator.ByteCodeClass.hasDeclaredMethod (12 samples)
    • 1.02% com.codename1.tools.translator.NativeTokenIndex.<init> (11 samples)
    • 1.02% com.codename1.tools.translator.IteratorEscape.walk (11 samples)
    • 1.02% com.codename1.tools.translator.Parser.cullMethods (11 samples)
  • ⚠️ Coverage report not generated.

Static Analysis

  • ✅ SpotBugs: no findings (report was not generated by the build).
  • ⚠️ PMD report not generated.
  • ⚠️ Checkstyle report not generated.

Generated automatically by the PR CI workflow.

…pile

- AsyncAndDependencyTest types its FutureBuilder builder (AsyncSnapshot<Object>).
- dart:io resolves relative paths against Directory.current.
- StreamIterator carries null events instead of dropping them.
- utf8.decode throws FormatException on malformed input unless allowMalformed.
- DateTime.parse truncates digits past microseconds, as the Dart VM does.
- Stream.fromFutures emits in completion order.
- Provider honours lazy: create runs on first lookup unless lazy is false.
- Equatable hashes through DartRuntime.hashOf, so 1 and 1.0 agree.
- The importer ignores import-like text inside triple-quoted strings.
- Geolocator shares one platform listener and removes it with the last subscriber.
- Local-notification payloads persist across process restarts.
- package_info_plus reports the pubspec build number from a resource the
  importer writes.
- CancelToken cancels every request it is attached to.
- AppLinks.getLatestLink returns the most recent link.
- FirebaseAuth drops change-stream controllers once they are cancelled.
- App classes extending runtime model classes such as Equatable get dynamic
  members; only widget, State, Element and RenderObject subclasses are skipped.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@shai-almog

Copy link
Copy Markdown
Collaborator Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 281d800c2f

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +287 to +290
if (to instanceof Future) {
// FutureOr<String?>: only an already-completed answer can steer a navigation
// that is happening now.
to = ((Future<?>) to).completedValue();

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Await asynchronous redirects before opening the route

When a redirect performs asynchronous work, such as reading an auth session, its Future is normally incomplete here, so completedValue() returns null and navigation immediately opens the unredirected route. Nothing subscribes to the future afterward, meaning even a later redirect result is permanently ignored and authentication guards can expose protected screens; defer route construction until the redirect future completes.

Useful? React with 👍 / 👎.

Comment on lines +461 to +464
collectDartSources(libDir, "", own);
for (String f : own) {
work.add(new String[] {null, f});
seen.add("\u0000" + f);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Traverse dependencies from the actual entry point

When lib/ contains a dormant Dart file that the application's entry point never imports, this seeds the dependency walk with that file anyway. If the file imports an unsupported platform package—for example, an unused experimental camera implementation—the importer records that package as reached and rejects an application that Flutter itself can build because the file is outside the real import graph; seed the walk from the selected entry point and follow only reachable directives.

Useful? React with 👍 / 👎.

Comment on lines +1022 to +1025
} catch (Throwable t) {
stopped[0] = true;
c.completeError(dart.runtime.DartRuntime.caught(t));
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Cancel terminal stream subscriptions when callbacks throw

When a callback used by forEach, any, every, fold, or another collect-based operation throws after the asynchronous subscription has been installed, this catch completes the result with an error but never cancels the subscription. Since stopped then suppresses every later event, an infinite or platform-backed source remains subscribed indefinitely without doing useful work; cancel holder[0] here as the stream-error path already does.

Useful? React with 👍 / 👎.

Comment on lines +160 to +165
if (cut > 0) {
sink.yield$(text(head));
}
}
if (carry.length > 0) {
sink.yield$(text(carry));

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Validate malformed UTF-8 in the streaming decoder

When stream.transform(utf8.decoder) receives malformed bytes, completed chunks and the final carry are passed directly to Java's string decoder, which substitutes U+FFFD rather than emitting Dart's FormatException. The new strict check in the direct decode path does not cover this bind path, so inputs such as a final [0xff] still pass silently; validate each completed prefix and the carry before decoding.

Useful? React with 👍 / 👎.

Comment on lines +313 to +324
} else if (c == '=') {
break;
} else {
throw new FormatException("Invalid character (at character " + (i + 1) + ")");
}
acc = (acc << 6) | v;
bits += 6;
if (bits >= 8) {
bits -= 8;
out[n++] = (byte) (acc >> bits);
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Reject invalid Base64 padding and trailing data

When an input has impossible length, misplaced padding, or characters after padding, encountering the first = simply stops decoding and any leftover bits are discarded. Consequently malformed values such as base64.decode('A') return empty bytes and base64.decode('QQ=garbage') returns truncated data instead of throwing FormatException, allowing corrupted encoded payloads to be accepted; validate the complete normalized quartet and padding structure.

Useful? React with 👍 / 👎.

Comment on lines +960 to +964
boolean pop = Boolean.TRUE.equals(ok) && !stack.isEmpty() && stack.get(stack.size() - 1) == top;
if (pop) {
pop(context, result);
PopGuards.notifyPopScopes(scopes, true, result);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Notify PopScope when another guard vetoes the pop

When a route contains a PopScope(canPop: true) alongside a WillPopScope that returns false or errors, mayPop completes false and this branch never invokes the PopScope callback. Flutter calls onPopInvokedWithResult for every attempted pop with didPop: false, so code that records the attempt or restores UI state misses it; notify the scopes on this veto path without double-notifying the existing canPop: false path.

Useful? React with 👍 / 👎.

Comment on lines +210 to +211
DartMap<String, Object> body = DartMap.<String, Object>of("idToken", user.idToken,
"returnSecureToken", Boolean.FALSE);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Refresh restored sessions before authenticated mutations

After a process restart, restore() reconstructs the current user with only a refresh token and leaves idToken null, but profile updates send that null token directly. The same direct use occurs for deletion and email verification, so these authenticated operations fail until application code happens to call getIdToken() first; refresh an absent or expired token before issuing each authenticated request.

Useful? React with 👍 / 👎.

Comment on lines +822 to +826
public final Stream<T> take(final long count) {
return derive(new Funcs.VoidFunc2<StreamIterator<T>, AsyncStar.Sink<T>>() {
@Override
public void call(StreamIterator<T> it, AsyncStar.Sink<T> s) {
for (long i = 0; i < count && it.next(); i++) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Reject negative stream counts before subscribing

When take is called with a negative count, Dart throws a RangeError, whereas this implementation silently returns an empty stream. skip similarly passes every event, and elementAt(-1) consumes the entire source before failing—hanging forever for an infinite stream—so validate these arguments before creating or listening to the derived operation.

Useful? React with 👍 / 👎.

Comment on lines +379 to +380
} else if (!p.equalsIgnoreCase(t)) {
return false;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Match route literals case-sensitively

With the default go_router behavior, literal path segments are case-sensitive, but this comparison accepts any casing. A route declared as /Admin therefore also handles /admin, and if both variants are declared the first matching route makes the other unreachable; compare literal segments exactly unless an explicit case-insensitive route option is added.

Useful? React with 👍 / 👎.

Comment on lines +107 to +109
long at = scheduledDate instanceof DateTime
? ((DateTime) scheduledDate).millisecondsSinceEpoch() : System.currentTimeMillis() + 10;
schedule(id, title, body, details, payload, at, LocalNotification.REPEAT_NONE);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve zonedSchedule recurrence requests

When callers pass matchDateTimeComponents to request a daily or weekly recurring notification, this unconditionally schedules REPEAT_NONE. The first notification may fire at the requested instant, but all subsequent occurrences are lost; map the supported date-component modes to the platform repeat interval instead of treating every zoned schedule as one-shot.

Useful? React with 👍 / 👎.

- Transpiler: fix gallery regressions (stub super calls, model-only dynamic
  members, implicit downcast of dynamic results).
- GoRouter: case-sensitive literal segments; async redirects awaited.
- dart:async/convert/io: cancel on collect failure, Dart equality in
  distinct/contains, range checks; strict UTF-8 and Base64 validation;
  HttpClientRequest.abort kills the in-flight request.
- Plugins: FCM opened flag, bloc released when explicit bloc dropped,
  dio ResponseType.stream returns a ResponseBody, PopScope notified once
  including WillPopScope vetoes, FirebaseAuth refreshes the ID token
  before account calls, zonedSchedule recurrence.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@shai-almog

Copy link
Copy Markdown
Collaborator Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 6091c28c71

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +358 to +359
} else if (len % 4 != 0) {
throw new FormatException("Invalid length, must be multiple of four (at character " + (len + 1) + ")");

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Allow valid unpadded Base64 input

When decoding common unpadded Base64 or Base64URL values such as QQ or JWT segments, this branch rejects every length that is not a multiple of four. Dart normalizes omitted padding for lengths modulo four of two or three—only a remainder of one is impossible—so valid tokens now fail with FormatException; validate the impossible remainder while decoding the valid leftover bits.

Useful? React with 👍 / 👎.

Comment on lines +64 to +66
Funcs.VoidFunc1<NotificationResponse> cb = onTap;
if (cb != null) {
Long id = null;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Preserve notification taps until initialization

On a cold launch caused by a local-notification tap, the Android launch flow invokes the generated LocalNotificationCallback before Lifecycle.run starts the Flutter app, so initialize() has not assigned onTap yet and this branch silently discards the notification ID. Because getNotificationAppLaunchDetails() also always returns null, an application launched from a killed state can never recover or route that tap; retain the pending response and deliver it after initialization.

Useful? React with 👍 / 👎.

if (contentType[0] == null) {
contentType[0] = ro.contentType;
}
final byte[] payload = encodeBody(ro, contentType);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Route request-encoding failures through Dio's future

When request data cannot be encoded—for example, a map contains an object that jsonEncode cannot serialize—encodeBody throws here while fetch() is still executing synchronously. The exception therefore escapes dio.post() instead of completing its returned future with a DioException, and error interceptors never run; catch body-encoding failures and send them through call.error.

Useful? React with 👍 / 👎.

Comment on lines +49 to +50
public File absolute() {
return this;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Resolve File.absolute against Directory.current

For a relative entity such as File('notes.json'), absolute() returns the same relative object even though subsequent I/O resolves it against Directory.current. Callers that persist, share, compare, or inspect file.absolute.path therefore still receive notes.json rather than an absolute path; construct the resolved path here and apply the same correction to Directory.absolute().

Useful? React with 👍 / 👎.

Comment on lines +941 to +942
if (test == null || dart.runtime.Dyn.invoke(test, e) == Boolean.TRUE) {
deliverError(onError, e);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Forward exceptions thrown by handleError

When the source emits an error and the supplied handleError callback itself throws, deliverError catches and merely logs that exception as if it came from a terminal listener. handleError is a stream transformer, so Dart emits the callback's thrown exception on the returned stream; here downstream retry or error handling sees neither the original nor replacement error. Invoke the handler under a transformer-specific try/catch and pass the thrown value to out.addError.

Useful? React with 👍 / 👎.

Comment on lines +1173 to +1174
String raw = p.named ? "(n == null ? null : n.idx(\"" + p.name + "\"))"
: "(a.length > " + pi + " ? a[" + pi + "] : null)";

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Apply optional defaults in dynamic method dispatch

When a method is invoked through a dynamic receiver with an omitted optional parameter, this dispatcher always supplies null instead of the parameter's declared default. Reference parameters therefore observe the wrong value, while primitive parameters such as int count = 1 fail during unboxing; emit p.defaultValue for absent positional and named arguments, while preserving an explicitly supplied null.

Useful? React with 👍 / 👎.

Comment on lines +185 to +189
if (fs.exists(path)) {
if (exclusive != null && exclusive.booleanValue()) {
throw new FileSystemException("Cannot create file, it already exists", path);
}
return;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Reject directory paths in File.create

When path already names a directory and exclusive is false, File.createSync() returns successfully because this check treats every existing entity as an existing file. Dart instead completes with a FileSystemException for this file/directory collision, so callers can believe a file was created and fail only during later I/O; verify that the existing entity is actually a file before returning.

Useful? React with 👍 / 👎.

Comment on lines +254 to +257
req.setFailSilently(true);
req.setReadResponseForErrors(true);
req.setFollowRedirects(followRedirects);
req.setDuplicateSupported(true);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Enforce HttpClientRequest.maxRedirects

When callers lower maxRedirects—especially to zero to inspect the first redirect—close() only forwards followRedirects and never uses the stored limit. The platform therefore follows its own redirect policy instead of failing after the requested number, which can send authenticated requests through an unexpected redirect chain; enforce the request's maxRedirects while handling redirects.

Useful? React with 👍 / 👎.

Comment on lines +910 to +913
while (it.next()) {
T v = it.current();
if (first || !equals.call(last, v).booleanValue()) {
s.yield$(v);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Keep distinct alive after equality callback errors

When a custom distinct(equals) callback throws for one event, the exception escapes this generator body, so AsyncStar.run emits the error and then closes the derived stream. Dart replaces only that data event with an error and continues processing later events without updating the previous value; catch the equality failure inside the loop, emit it as an error, and continue listening.

Useful? React with 👍 / 👎.

Comment on lines +99 to +103
sink.controller.onCancel(new Funcs.VoidFunc0() {
@Override
public void call() {
sink.cancelled = true;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Cancel upstream subscriptions when derived streams are canceled

When a listener cancels a derive-based stream such as source.distinct() while the source is idle, this hook only marks the sink canceled; the generator remains blocked in StreamIterator.next() and its upstream subscription is not canceled until another event arrives. A quiet platform source can therefore retain GPS, socket, or controller listeners indefinitely after cancellation; propagate cancellation to the active iterator/subscription immediately.

Useful? React with 👍 / 👎.

- Transpiler: dynamic dispatch applies declared defaults for omitted
  optional arguments (behavior case dart_dynamic_call_defaults).
- Streams: handleError emits what its handler throws; a throwing
  distinct(equals) errors one event and continues; cancelling a derived
  stream cancels its upstream and wakes a pending moveNext
  (behavior case dart_stream_transform_errors).
- dart:io: File/Directory.absolute resolve against Directory.current;
  File.create fails on a directory; HttpClientRequest enforces
  maxRedirects. Base64 strictness kept (matches the Dart VM), documented.
- Plugins: dio routes body-encoding failures through its future and
  detaches settled requests from a CancelToken; bloc reports failed async
  handlers to onError; a cold-launch notification tap is reported by
  getNotificationAppLaunchDetails.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@shai-almog

Copy link
Copy Markdown
Collaborator Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

if (!unchanged) {
log.warn(f.getName() + " was changed after an earlier import and was left as it is; "
+ "delete it and import again to regenerate it");
return;

P2 Badge Regenerate plugin callbacks after dependency changes

When a project is re-imported after adding or removing app_links, flutter_local_notifications, or firebase_messaging, the newly generated entry-point source differs from the previous generated file, but the presence of its original backup makes this branch classify that difference as a developer edit and return without writing it. Newly added plugins therefore never receive their lifecycle callbacks, while removed push dependencies can leave push enabled; compare against the previously generated template or otherwise distinguish generated changes from user edits.


if (plus >= 0 && plus < pubspec.version.length() - 1) {

P2 Badge Clear stale build numbers when pubspec drops the suffix

When a later import changes version: 1.2.3+42 to a version without a +build suffix, this branch is skipped and leaves the old Android/iOS build hints and cn1-flutter-build-number.txt in place. Native builds and PackageInfo.buildNumber therefore continue reporting 42; remove or reset all three generated values when the current pubspec has no build-number component.

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +109 to +111
return new DateTime(year, month, day, hour, minute, second, milli, micro);
}
DateTime u = utc(year, month, day, hour, minute, second, milli, micro);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Return null from tryParse for out-of-range dates

When a syntactically valid input lies outside Dart's DateTime range, such as 999999-01-01, these constructor calls throw ArgumentError from the range checks. DateTime.tryParse must return null for any value that DateTime.parse cannot produce, while parse should surface a FormatException; callers using tryParse on untrusted timestamps therefore unexpectedly throw instead of taking their invalid-input path.

Useful? React with 👍 / 👎.

Comment on lines +160 to +163
if (cut > 0) {
// Strict as decode() is: Java's decoder would substitute U+FFFD.
checkUtf8(head, head.length);
sink.yield$(text(head));

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Strip the UTF-8 BOM only at stream start

When a UTF-8 stream contains U+FEFF at the beginning of any later chunk, calling text(head) independently for every chunk strips that chunk's first EF BB BF sequence as though it were the stream BOM. For example, joining decoded chunks [0x41] and [0xef,0xbb,0xbf,0x42] yields AB instead of A\uFEFFB; track whether the decoder has processed its first code point rather than applying BOM removal to every chunk.

Useful? React with 👍 / 👎.

Comment on lines +70 to +71
if (fs.exists(path)) {
return;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Reject file collisions in Directory.create

When the requested path already exists as a regular file, this branch reports successful directory creation. Code can then continue with the returned Directory and fail only on a later listing or child write, whereas Dart's Directory.create/createSync throws FileSystemException for this collision; return early only when fs.isDirectory(path) is true.

Useful? React with 👍 / 👎.

Comment on lines +1147 to +1148
if (m.isStatic || m.isSetter || !m.typeParams.isEmpty() || m.name.startsWith("$")) {
continue;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Emit dynamic dispatch for declared setters

When a model exposes a property through an explicit setter rather than a mutable backing field, this exclusion leaves no corresponding $dset case. An assignment such as dynamic model = value; model.name = 'x'; therefore throws NoSuchMethodError even though the Dart class declares set name(...); generate setter dispatch instead of skipping these methods.

Useful? React with 👍 / 👎.

Comment on lines +1190 to +1195
String invoke = "this." + javaMethodName(m.name) + "(" + args + ")";
call.append(" case \"").append(m.name).append("\": ");
if (rt.is("void") || overridesVoid(c, m.name)) {
call.append(invoke).append("; return null;\n");
} else {
call.append("return ").append(invoke).append(";\n");

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Validate arguments before dynamic method dispatch

When a dynamic call supplies too many positional arguments or an unknown named argument, the generated case invokes the method using only its declared parameters and silently ignores the extras. Missing required nullable arguments are similarly converted to null and invoked; Dart instead raises NoSuchMethodError for each mismatched invocation, so validate the positional count and named-key set before executing the method.

Useful? React with 👍 / 👎.

if (q.length() == 0) {
return url;
}
return url + (url.indexOf('?') >= 0 ? "&" : "?") + q;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Insert query parameters before URL fragments

When a request path contains a fragment and also supplies queryParameters, this concatenates the query after #; for example, /items#section becomes /items#section?q=x. The query is then part of the fragment and is not sent to the server, so construct the query before the fragment while preserving the fragment at the end.

Useful? React with 👍 / 👎.

Comment on lines +1033 to +1035
if (toJson != null && !toJson.isStatic && toJson.params.isEmpty() && !c.isMixin
&& (toJson.returnType == null || !(toJson.returnType.is("int") || toJson.returnType.is("double")
|| toJson.returnType.is("bool") || toJson.returnType.is("void")))) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Dispatch primitive-returning toJson methods

When a class declares a valid zero-argument toJson returning int, double, or bool, this condition excludes it from JsonEncodable. jsonEncode(instance) consequently throws JsonUnsupportedObjectError instead of encoding the primitive returned by toJson; generate a boxed bridge or otherwise dispatch every non-void toJson regardless of its declared return type.

Useful? React with 👍 / 👎.

}
}
}
fs.delete(path);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Reject non-recursive deletion of non-empty directories

When Directory.deleteSync() is called without recursive: true on a directory containing children, this falls through to the storage deletion without checking that the directory is empty. The in-memory backend removes only the directory entry and leaves orphaned children, while platform deletion may silently fail, yet the method reports success in both cases; Dart instead throws FileSystemException, so reject a non-recursive delete before calling fs.delete when fs.list(path) is non-empty.

Useful? React with 👍 / 👎.

Comment on lines +69 to +70
public static FirebaseAuth instanceFor(FirebaseApp app, Object persistence) {
return instance;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Bind FirebaseAuth instances to their requested app

When an application calls FirebaseAuth.instanceFor for a secondary FirebaseApp, this returns the default singleton and discards the supplied app. The transport later reads only [DEFAULT]'s API key, and the user/session state is also shared, so authentication intended for a second Firebase project is sent to the wrong project and can expose the default project's signed-in user; maintain an auth instance and credentials namespace per app.

Useful? React with 👍 / 👎.

Comment on lines +219 to +224
if (name != null) {
body.put("displayName", name);
}
if (photo != null) {
body.put("photoUrl", photo);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Send profile-field deletions to Firebase

When updateDisplayName(null) or updatePhotoURL(null) is used to clear a profile field, these conditions omit the field from the REST request, which means the remote account retains its previous value. The success callback nevertheless clears the local User, leaving client and server state inconsistent; send Firebase's explicit delete attribute for null fields rather than treating null as no update.

Useful? React with 👍 / 👎.

- Dyn: hand-written sign; ParparVM's JavaAPI has no Math/Long.signum,
  so the Linux and Windows gallery builds failed to link.
- FirebaseMessaging no longer names com.codename1.push: any reference
  turns push on in an Android build, which then requires
  google-services.json even for apps without Firebase. The generated
  entry point supplies the push key only when firebase_messaging is used.
- StreamIterator: checking the queue and publishing the waiter is now
  atomic; an event pushed between the two from a timer thread was lost
  and an async* body hung (about 1 in 60 runs under load).
- Accept the gallery's JavaScript size growth for this PR
  (scripts/flutter-bench/baseline/pr/6055.json).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@shai-almog

Copy link
Copy Markdown
Collaborator Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f78e349063

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +132 to +133
public Future<RemoteMessage> getInitialMessage() {
return Future.value(null);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Preserve cold-start messages for getInitialMessage

When a notification tap launches a terminated app, pushReceived publishes the message to the broadcast OPENED stream before Dart code necessarily installs a listener, so that event can be dropped. Since getInitialMessage() then always returns null, the standard Firebase Messaging cold-start flow can never recover the message or route the user; retain the launch message and return it here.

Useful? React with 👍 / 👎.

Comment on lines +91 to +92
public Future<Object> reload() {
return Future.value(null);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Refresh account data in User.reload

When a user follows an email-verification link and the app calls await user.reload(), this completes successfully without fetching the updated account record. Because emailVerified is never assigned anywhere else in this implementation, it remains false permanently and common verification gates continue rejecting the user; reload the account through the authenticated Firebase lookup endpoint and update the local fields.

Useful? React with 👍 / 👎.

Comment on lines +248 to +251
((Future<Object>) to).then(answered, new Funcs.VoidFunc1<Object>() {
@Override
public void call(Object e) {
then.call(loc);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Do not navigate after failed async redirects

When an asynchronous redirect used as an auth guard completes with an error, this handler treats the failure as if no redirect were requested and continues to the original location. push() then constructs and displays that route, so a transient authentication or network failure can open guarded content instead of surfacing the routing error; propagate the error or route it through errorBuilder rather than continuing with loc.

Useful? React with 👍 / 👎.

return AsyncStar.asyncStar(new Funcs.VoidFunc1<AsyncStar.Sink<R>>() {
@Override
public void call(AsyncStar.Sink<R> s) {
final StreamIterator<T> it = new StreamIterator<T>(self);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Keep derived streams subscribed after source errors

When a source emits an error and later continues with data, every derive-based operator—including take, skip, expand, and asyncMap—reads it through StreamIterator, whose error path enqueues DONE and cancels its subscription. The generated stream therefore emits the first error and closes, dropping all later events, whereas Dart stream transformations forward source errors and remain subscribed; use a non-canceling error-forwarding subscription for these operators.

Useful? React with 👍 / 👎.

Comment on lines +65 to +66
snapshot = new AsyncSnapshot(ConnectionState.none, snapshot.data(), snapshot.error(),
snapshot.stackTrace());

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Clear stale errors when StreamBuilder changes streams

When a StreamBuilder replaces a stream after the old stream emitted an error, this carries that error and stack trace into both the disconnected and waiting snapshots for the new stream. Builders that prioritize snapshot.hasError therefore keep rendering the old failure instead of the new stream's loading state until it emits; preserve the last data but clear the old error when disconnecting and subscribing.

Useful? React with 👍 / 👎.

Comment on lines +501 to +502
case "toInt":
return Long.valueOf(dbl ? (long) d : n.longValue());

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Reject non-finite dynamic toInt calls

When a dynamic receiver contains double.nan or an infinity, this Java cast returns 0 or a saturated long instead of throwing Dart's UnsupportedError. Code that validates or converts calculated dynamic values can consequently accept a fabricated integer; route the double case through the existing checked DartRuntime.toInt implementation.

Useful? React with 👍 / 👎.

Comment on lines +449 to +450
while (i < s.length() && Character.isDigit(s.charAt(i))) {
i++;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Restrict JSON number digits to ASCII

When an otherwise numeric token contains Unicode decimal digits, such as 1٢, Character.isDigit consumes them and Long.parseLong accepts them, so this decoder returns 12. JSON permits only ASCII 0–9, and Dart's decoder rejects this document with FormatException; use an explicit ASCII digit predicate throughout number parsing.

Useful? React with 👍 / 👎.

BlocBase<?> b = explicitBloc();
if (b == null) {
Class<?> type = blocType();
Object found = context().providerValueOfType(type == null ? BlocBase.class : type, false);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Resubscribe when the provided bloc changes

When an ancestor BlocProvider replaces its bloc while the BlocBuilder, BlocListener, BlocConsumer, or BlocSelector widget itself remains in place, this non-listening lookup registers no provider dependency and didUpdateWidget sees no explicit-bloc change. The state therefore remains subscribed to the old, potentially disposed bloc and never observes the replacement; track provider dependency changes and release/rebind the subscription when the resolved bloc changes.

Useful? React with 👍 / 👎.

Comment on lines +307 to +308
Long t = times.get(key(path));
return t == null ? 0 : t.longValue();

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Fail lastModified for missing files

When File.lastModifiedSync() or lastModified() targets a missing file in the headless/in-memory backend, this returns zero and the public API reports the Unix epoch as a valid modification time. Dart instead throws or completes with a FileSystemException, so callers cannot distinguish a missing file from a real epoch-dated file; check that the file exists before returning its timestamp.

Useful? React with 👍 / 👎.

@shai-almog

shai-almog commented Oct 20, 2026 •

Copy link
Copy Markdown
Collaborator Author

Compared 236 screenshots: 236 matched.
✅ Native Apple Watch (watchOS, Core Graphics) screenshot tests passed.

@shai-almog

shai-almog commented Oct 20, 2026 •

Copy link
Copy Markdown
Collaborator Author

Compared 176 screenshots: 176 matched.
✅ Native iOS Metal screenshot tests passed.

Benchmark Results

  • VM Translation Time: 0 seconds
  • Compilation Time: 1533 seconds

Build and Run Timing

Metric Duration
Simulator Boot 63000 ms
Simulator Boot (Run) 1000 ms
App Install 24000 ms
App Launch 2000 ms
Test Execution 481000 ms

Detailed Performance Metrics

Metric Duration
SIMD kernel backend SSE2 (x64) / NEON (arm64) native kernels
SIMD int-add (64K x300) java 170ms / native 5ms = 34.0x speedup
SIMD float-mul (64K x300) java 227ms / native 8ms = 28.3x speedup
SIMD kernel correctness PASS (native result == scalar reference)
Base64 payload size 8192 bytes
Base64 benchmark iterations 6000
Base64 SIMD byte path active (NEON-accelerated)
Base64 CN1 encode 119.000 ms
Base64 CN1 decode 114.000 ms
Base64 native encode 1717.000 ms
Base64 encode ratio (CN1/native) 0.069x (93.1% faster)
Base64 native decode 2227.000 ms
Base64 decode ratio (CN1/native) 0.051x (94.9% faster)
Base64 SIMD encode 139.000 ms
Base64 encode ratio (SIMD/CN1) 1.168x (16.8% slower)
Base64 SIMD decode 255.000 ms
Base64 decode ratio (SIMD/CN1) 2.237x (123.7% slower)
Base64 encode ratio (SIMD/native) 0.081x (91.9% faster)
Base64 decode ratio (SIMD/native) 0.115x (88.5% faster)
Image encode benchmark iterations 100
Image createMask (SIMD off) 14.000 ms
Image createMask (SIMD on) 7.000 ms
Image createMask ratio (SIMD on/off) 0.500x (50.0% faster)
Image applyMask (SIMD off) 431.000 ms
Image applyMask (SIMD on) 65.000 ms
Image applyMask ratio (SIMD on/off) 0.151x (84.9% faster)
Image modifyAlpha (SIMD off) 75.000 ms
Image modifyAlpha (SIMD on) 103.000 ms
Image modifyAlpha ratio (SIMD on/off) 1.373x (37.3% slower)
Image modifyAlpha removeColor (SIMD off) 372.000 ms
Image modifyAlpha removeColor (SIMD on) 86.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off) 0.231x (76.9% faster)

shai-almog and others added 2 commits October 20, 2026 17:19
… a test-harness timeout

- Icons are static methods rather than 2,176 eagerly built fields, and dynamic
  dispatch switch cases are pruned to member names the program reaches through
  dynamic receivers, so ParparVM's dead-code removal can drop the rest.
- Derived streams (take/skip/expand/asyncMap...) forward source errors and stay
  subscribed; failed async GoRouter redirects show the error page; User.reload
  refreshes the account; getInitialMessage returns the cold-start push; bloc
  widgets rebind when the provided bloc changes; dynamic toInt/floor/ceil reject
  non-finite values; JSON numbers are ASCII digits; lastModified fails on a
  missing file.
- The behavior harness kills a generated program after a timeout instead of
  hanging the suite.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 1f5d6c0991

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

return;
}
@SuppressWarnings("unchecked")
StreamIterator<T> it = new StreamIterator<T>((Stream<T>) source);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Forward yield-star errors without terminating the generator

When an async* generator uses yield* on a stream that emits an error followed by more data, this plain StreamIterator subscribes with cancelOnError: true and makes next() throw on the error. AsyncStar.run then emits that error and closes the entire generator, losing both subsequent source events and statements after yield*. Delegation should forward error events and continue until the source finishes; use an error-forwarding iterator, as Stream.derive already does, rather than the terminating iterator used for await for.

Useful? React with 👍 / 👎.

Comment on lines +95 to +96
try {
while (it.next()) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Cancel the delegated subscription when its listener cancels

When a listener cancels an async* stream while yield* is waiting for an idle source, the sink only sets cancelled; this iterator is not registered with onCancel, so its pending next() never wakes and the finally block cannot cancel the upstream subscription. This leaves upstream resources subscribed and prevents generator cleanup from running indefinitely if the source emits nothing further (and leaves a non-daemon timer thread blocked in headless execution). Register the delegated iterator with the sink's cancellation hooks so cancellation releases the pending wait and unwinds the generator.

Useful? React with 👍 / 👎.

req.setUrl(ro.url());
req.setHttpMethod(ro.method);
req.setPost(payload != null);
req.setFailSilently(true);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Complete request futures on transport failures

With Codename One initialized, a DNS failure, TLS error, or socket timeout leaves the Dio request future pending indefinitely. ConnectionRequest.performOperationComplete() schedules postResponse() only after normal completion, while NetworkManager merely logs transport exceptions when failSilently is true, bypassing this request's handleException() override altogether. Consequently neither the error interceptors nor the request completer are reached. Settle the request through an exception path that actually runs instead of deferring failure handling to postResponse(); the same pattern also needs correction in FirebaseAuth.request() and HttpClientRequest.close().

Useful? React with 👍 / 👎.

if (files instanceof List) {
for (Object f : (List<?>) files) {
if (f instanceof XFile) {
path = ((XFile) f).path();

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Materialize in-memory XFiles before sharing them

When an app shares an export created with XFile.fromData(bytes, name: ...), XFile holds the bytes only in memory and defaults its path to an empty string. Extracting only path() here therefore passes an empty attachment path to Display.share, without ever writing or reading the export bytes, so the recipient cannot receive the generated file. The instance API has the same problem through ShareParams.firstFilePath(). Materialize data-backed XFiles into temporary files before passing their paths to the platform share sheet.

Useful? React with 👍 / 👎.

Comment on lines +169 to +171
} else if (v instanceof Enum) {
// Not encodable in Dart either without toJson; name it in the error.
throw new JsonUnsupportedObjectError(v, "Converting object to an encodable object failed");

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Apply custom JSON encoders to enum values

When jsonEncode or JsonEncoder receives an enum and a toEncodable callback, this branch throws before the callback can run. For example, jsonEncode(Status.ready, toEncodable: (_) => 'ready') should produce a JSON string but instead raises JsonUnsupportedObjectError; enums nested in maps or lists fail the same way. Since the transpiler emits Dart enums as Java enums, route these values through the existing custom-conversion fallback and reject them only when no usable conversion is available.

Useful? React with 👍 / 👎.

shai-almog and others added 3 commits October 21, 2026 14:14
yield* forwards source errors and releases the source on cancel; Dio,
FirebaseAuth and HttpClient settle transport failures instead of
leaving the request pending; in-memory XFiles are written out before
sharing; jsonEncode lets toEncodable/toJson encode enums.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The whole flutter-runtime jar, all 19 plugin packs, was merged into every
app. The Android builder's class scan then saw geolocator/maps/firebase
classes and added play-services-location plus location, audio and boot
permissions to apps that use none of them (+762 KB dex on the gallery).
Executor.unzip now drops every pack nothing outside the plugin tree
references, keeping transitive pack dependencies.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@shai-almog

Copy link
Copy Markdown
Collaborator Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 91ff218dff

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +201 to +203
if (samePath(location, initialLocation)) {
return;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Reevaluate redirects when navigating to the initial location

When the initial location redirects elsewhere, this early return prevents subsequent navigation back to that location from resolving its current destination. For example, if / initially redirects an unauthenticated user to /login, calling go('/') after signing in simply exposes the original login page again without invoking the redirect or rebuilding the route. It also leaves state describing the previous navigation. Resolve the requested location before deciding whether the existing base page can be reused.

Useful? React with 👍 / 👎.

Comment on lines +115 to +117
if (autoUncompress) {
r.headers().set(HttpHeaders.acceptEncodingHeader, "gzip");
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Decompress gzip responses when autoUncompress is enabled

On the JavaSE simulator, a server honoring the default Accept-Encoding: gzip header returns compressed bytes, but HttpClientRequest.readResponse() copies those bytes directly into the response without decompression. The inspected JavaSEPort.openInputStream() only buffers the underlying HTTP stream; gzip handling is provided separately by GZConnectionRequest. Consequently ordinary UTF-8 or JSON response decoding fails for gzip-enabled servers despite autoUncompress being true. Decode compressed responses when enabled, accounting for platforms that already decompress them.

Useful? React with 👍 / 👎.

Comment on lines +802 to +806
private <R> Stream<R> derive(final Funcs.VoidFunc2<StreamIterator<T>, AsyncStar.Sink<R>> body) {
final Stream<T> self = this;
return AsyncStar.asyncStar(new Funcs.VoidFunc1<AsyncStar.Sink<R>>() {
@Override
public void call(final AsyncStar.Sink<R> s) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Subscribe derived streams before returning from listen

For a live broadcast source, operators using derive, such as take, skip, and asyncMap, lose events emitted immediately after the downstream listener attaches. AsyncStar.asyncStar() schedules its body on a timer, and the upstream subscription is established only when that body eventually calls StreamIterator.next(). Thus controller.stream().take(1).listen(...); controller.add(value); on the EDT drops value because the controller still has no listener, potentially leaving the result waiting indefinitely. Establish and buffer the upstream subscription synchronously during downstream registration rather than deferring registration with generator execution.

Useful? React with 👍 / 👎.

Comment on lines +508 to +511
case "contains":
return Boolean.valueOf(s.contains(String.valueOf(arg(a, 0))));
case "startsWith":
return Boolean.valueOf(s.startsWith(String.valueOf(arg(a, 0))));

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Honor string search offsets on dynamic receivers

When a string is accessed through dynamic, these branches accept but discard the optional search offset. For example, dynamic s = 'abc'; s.startsWith('b', 1) returns false instead of true, while s.contains('a', 1) returns true instead of false; the indexOf branch below likewise always searches from zero. This silently changes parsing and matching results compared with statically typed calls. Forward the supplied offset to the existing range-checked DString overloads rather than ignoring it.

Useful? React with 👍 / 👎.

Comment on lines +974 to +976
// one onPopInvoked per request: didPop false for any refusal, a
// WillPopScope's included, as Flutter reports it
PopGuards.notifyPopScopes(scopes, pop, result);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Notify PopScope callbacks for explicit pops

When an application closes a page with Navigator.pop(context, result), its PopScope.onPopInvokedWithResult or onPopInvoked callback never runs: the only notification call is here inside maybePop, while the explicit pop path removes the page without notifying its scopes. Although explicit pops correctly bypass canPop, they still need to report the successful pop and its result, as the new PopScope contract promises. Put successful-pop notification in the actual pop path and avoid notifying it twice through maybePop, so callback-based cleanup and result handling also run for programmatic navigation.

Useful? React with 👍 / 👎.

Comment on lines +44 to +45
public dart.core.DartUri uri() {
return dart.core.DartUri.parse(path.startsWith("/") ? "file://" + path : path);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Escape filesystem paths before constructing their URIs

For filenames containing URI delimiters or literal percent escapes, parsing the raw path changes which file the URI represents. For example, File('/tmp/a#b').uri treats b as a fragment, so passing it to File.fromUri throws instead of recovering the original path; /tmp/a%20b instead round-trips to /tmp/a b, a different filename. The same implementation serves directories. Construct the URI from escaped filesystem path components so literal #, ?, and % characters remain part of the filename.

Useful? React with 👍 / 👎.

- Dyn: contains/startsWith/indexOf forward the optional offset.
- FileSystemEntity.uri escapes the path, so '#', '?' and '%' stay in names.
- Navigator: an explicit pop notifies PopScope (didPop true); maybePop
  reports only refusals, so a pop is never reported twice.
- GoRouter.go runs redirects even for the initial location.
- HttpClient gunzips gzip responses under autoUncompress; contentLength
  is the declared Content-Length (-1 if absent or decompressed).
- Derived streams (take/skip/asyncMap...) subscribe to the source inside
  listen(), so events right after listen are not lost.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@shai-almog

Copy link
Copy Markdown
Collaborator Author

@codex review

shai-almog and others added 5 commits October 21, 2026 18:34
AndroidGradleBuilder overrides unzip, so the prune in Executor.unzip never
ran on Android: every plugin pack reached the permission scan, and
FirebaseMessaging's com/codename1/push reference switched FCM on, failing
the gallery build on a missing google-services.json.

Drop pr/6055.json: the latest run's JavaScript and Linux sizes are within
tolerance of the current rows (JavaScript once #5976's overlay is
re-based, on branch fix-5976-flutter-baseline), so nothing needs
rebaselining.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
#5976 merged after the nightly fold had already moved javascript
gallery/code_bytes to 13,304,997, so its overlay's 'from' (13,238,731)
no longer matched and every baseline read failed: master's Performance
baselines check and every PR's Flutter benchmark gate. Its measured
value (13,338,532) is unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 2d83e24)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit e6b4a09)
…n app

- FirebaseMessaging no longer names com.codename1.push: topic calls go
  through a topicSink hook the import-generated entry point installs,
  as tokenSource already did. Pruning keeps the whole firebase pack for
  an auth-only app, and that reference turned on FCM (and required
  google-services.json) on Android. NoPushReferenceTest guards it.
- User keeps the FirebaseAuth that created or restored it, so a
  secondary app's token refresh, profile update, reload and delete use
  that app's key and session.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@shai-almog

shai-almog commented Oct 21, 2026 •

Copy link
Copy Markdown
Collaborator Author

Compared 179 screenshots: 179 matched.
✅ Native Mac screenshot tests passed.

Benchmark Results

  • VM Translation Time: 0 seconds
  • Compilation Time: 447 seconds

Detailed Performance Metrics

Metric Duration
SIMD kernel backend SSE2 (x64) / NEON (arm64) native kernels
SIMD int-add (64K x300) java 90ms / native 3ms = 30.0x speedup
SIMD float-mul (64K x300) java 76ms / native 3ms = 25.3x speedup
SIMD kernel correctness PASS (native result == scalar reference)
Base64 native bridge unavailable (CN1 + SIMD + image benchmarks only)
Base64 payload size 8192 bytes
Base64 benchmark iterations 6000
Base64 SIMD byte path active (NEON-accelerated)
Base64 CN1 encode 64.000 ms
Base64 CN1 decode 63.000 ms
Image encode benchmark iterations 100
Image createMask (SIMD off) 7.000 ms
Image createMask (SIMD on) 3.000 ms
Image createMask ratio (SIMD on/off) 0.429x (57.1% faster)
Image applyMask (SIMD off) 74.000 ms
Image applyMask (SIMD on) 78.000 ms
Image applyMask ratio (SIMD on/off) 1.054x (5.4% slower)
Image modifyAlpha (SIMD off) 68.000 ms
Image modifyAlpha (SIMD on) 48.000 ms
Image modifyAlpha ratio (SIMD on/off) 0.706x (29.4% faster)
Image modifyAlpha removeColor (SIMD off) 67.000 ms
Image modifyAlpha removeColor (SIMD on) 71.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off) 1.060x (6.0% slower)

ParparVM vs HotSpot (JDK 25): macos-arm64

The performance gate could not run: the gate's own unit tests (test_perf_gate.py) failed. See perf-build.log in this job's artifacts.

@shai-almog

shai-almog commented Oct 21, 2026 •

Copy link
Copy Markdown
Collaborator Author

Compared 176 screenshots: 176 matched.
✅ Native iOS Metal screenshot tests passed.

Benchmark Results

  • VM Translation Time: 0 seconds
  • Compilation Time: 1597 seconds

Build and Run Timing

Metric Duration
Simulator Boot 87000 ms
Simulator Boot (Run) 1000 ms
App Install 17000 ms
App Launch 2000 ms
Test Execution 495000 ms

Detailed Performance Metrics

Metric Duration
SIMD kernel backend SSE2 (x64) / NEON (arm64) native kernels
SIMD int-add (64K x300) java 71ms / native 4ms = 17.7x speedup
SIMD float-mul (64K x300) java 102ms / native 3ms = 34.0x speedup
SIMD kernel correctness PASS (native result == scalar reference)
Base64 payload size 8192 bytes
Base64 benchmark iterations 6000
Base64 SIMD byte path active (NEON-accelerated)
Base64 CN1 encode 63.000 ms
Base64 CN1 decode 88.000 ms
Base64 native encode 660.000 ms
Base64 encode ratio (CN1/native) 0.095x (90.5% faster)
Base64 native decode 403.000 ms
Base64 decode ratio (CN1/native) 0.218x (78.2% faster)
Base64 SIMD encode 73.000 ms
Base64 encode ratio (SIMD/CN1) 1.159x (15.9% slower)
Base64 SIMD decode 47.000 ms
Base64 decode ratio (SIMD/CN1) 0.534x (46.6% faster)
Base64 encode ratio (SIMD/native) 0.111x (88.9% faster)
Base64 decode ratio (SIMD/native) 0.117x (88.3% faster)
Image encode benchmark iterations 100
Image createMask (SIMD off) 6.000 ms
Image createMask (SIMD on) 1.000 ms
Image createMask ratio (SIMD on/off) 0.167x (83.3% faster)
Image applyMask (SIMD off) 86.000 ms
Image applyMask (SIMD on) 253.000 ms
Image applyMask ratio (SIMD on/off) 2.942x (194.2% slower)
Image modifyAlpha (SIMD off) 41.000 ms
Image modifyAlpha (SIMD on) 96.000 ms
Image modifyAlpha ratio (SIMD on/off) 2.341x (134.1% slower)
Image modifyAlpha removeColor (SIMD off) 144.000 ms
Image modifyAlpha removeColor (SIMD on) 167.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off) 1.160x (16.0% slower)

shai-almog and others added 6 commits October 22, 2026 04:27
Rows master had already moved made it unreadable; same repair as
3702c6c on parley-messaging, so the two branches merge cleanly.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Build time:
- Importer: a package importing an unsupported one is unsupported too
  ('depends on X'), so its files never reach the build.
- Generated $dcall/$dset convert arguments through DartRuntime.as
  (instanceof, Dart TypeError) instead of bare casts, which ParparVM does
  not check. Explicit setters and generic methods are now dispatched.
- Plugin pruning also reads library jars in libs/: a pack only a cn1lib
  calls is kept.

Runtime:
- AsyncStar.over is the shared transformer plumbing: LineSplitter and
  utf8/base64 bind subscribe inside listen() and forward source errors
  without ending the stream, as Stream.derive already did.
- Sink.onCancel and the cancel handler share one lock (a cancel between
  check and add left the hook unrun).
- Dyn join/padLeft/compareTo and base64.decode check their String casts.
- GoogleMap's camera poll stops after ~500 ms still and on unmount.
- ImagePicker's single-pick fallback propagates errors.
- HttpClientResponse.contentLength is -1 for any gzip body under
  autoUncompress, including one iOS already inflated.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Transpiler: a[i]++ through the index write (old value for postfix), dynamic
compound assignment evaluates receiver/key once, left operand pinned when the
right lifts statements, unary -/~ operator overloads, generic local functions
erased. Plugins: SharedPreferences mock values stay off Storage, held
cold-start pushes replayed to a late onMessageOpenedApp listener.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
bloc: close() completes live emitters (cancels forEach/onEach), a handler's
synchronous error goes to onError and is reported instead of thrown at add's
caller, on<E> handler and Emitter.forEach/onEach typed so block handlers and
emit.forEach bind statically. Transpiler: a block lambda with no value return
targeting a dynamic-returning function answers null. connectivity: events
delivered on the EDT, listener registered once Display is up. path_provider:
separate temp and cache subdirectories.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A block lambda emitted with a null tail is value-shaped, so then()/catchError
pick the value SAM (block-bodied then callbacks failed to compile). Local
sync*/async* functions lowered like generator closures, with their declared
element type. await for over a stream of unknown element type reads it as
Stream<Object>.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@shai-almog

shai-almog commented Oct 22, 2026 •

Copy link
Copy Markdown
Collaborator Author

Compared 193 screenshots: 193 matched.
Native Windows port (x64 / Intel-AMD): full hellocodenameone screenshot suite rendered offscreen with Direct2D/DirectWrite, plus the real benchmarks (base64 native/CN1/SIMD, image createMask/applyMask/modifyAlpha/PNG/JPEG, SSE2 SIMD kernels). Compared against the in-repo baseline in scripts/windows/screenshots.

Benchmark Results

Detailed Performance Metrics

Metric Duration
SIMD kernel backend SSE2 (x64) / NEON (arm64) native kernels
SIMD int-add (64K x300) java 73ms / native 4ms = 18.2x speedup
SIMD float-mul (64K x300) java 57ms / native 3ms = 19.0x speedup
SIMD kernel correctness PASS (native result == scalar reference)
Base64 native bridge unavailable (CN1 + SIMD + image benchmarks only)
Base64 payload size 8192 bytes
Base64 benchmark iterations 6000
Base64 SIMD byte path gated to scalar (CPU autovectorizes scalar; explicit SIMD not beneficial here)
Base64 CN1 encode 89.000 ms
Base64 CN1 decode 90.000 ms
Base64 SIMD encode 105.000 ms
Base64 encode ratio (SIMD/CN1) 1.180x (18.0% slower)
Base64 SIMD decode 98.000 ms
Base64 decode ratio (SIMD/CN1) 1.089x (8.9% slower)
Image encode benchmark iterations 100
Image createMask (SIMD off) 12.000 ms
Image createMask (SIMD on) 6.000 ms
Image createMask ratio (SIMD on/off) 0.500x (50.0% faster)
Image applyMask (SIMD off) 34.000 ms
Image applyMask (SIMD on) 42.000 ms
Image applyMask ratio (SIMD on/off) 1.235x (23.5% slower)
Image modifyAlpha (SIMD off) 43.000 ms
Image modifyAlpha (SIMD on) 49.000 ms
Image modifyAlpha ratio (SIMD on/off) 1.140x (14.0% slower)
Image modifyAlpha removeColor (SIMD off) 38.000 ms
Image modifyAlpha removeColor (SIMD on) 29.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off) 0.763x (23.7% faster)

ParparVM vs HotSpot (JDK 25): Windows x64

Runner CPU: AMD64 Family 25 Model 1 Stepping 1, AuthenticAMD (baseline windows-x64@amd64-family-25-model-1-authenticamd)

Ratios are ParparVM / JDK 25: below 1.00x ParparVM is faster (time) or smaller (RAM). Median of 5 interleaved, paired rounds; every run's output was verified. A ratio more than 20% (time) / 25% (RAM) above its baseline in vm/selfhost/perf-baseline/ is a regression and fails; one that far below is an improvement, reported but not failed (a row whose calibration runs were noisier carries a wider tolerance), and for RAM the change must also exceed 0.05x in absolute terms. Both run unpinned on all of the runner's CPUs, with their own default thread counts.

Benchmark Cores Time RAM Status
hello (WinHelloMain) 4 1.42x (base 1.22x, +16.5%) 0.73x (base 0.94x, -22.2%) ok
translator (self) 4 0.68x (base 0.60x, +13.2%) 0.56x (base 0.54x, +2.9%) ok
intArithmetic 4 1.16x (base 1.10x, +5.7%) 0.06x (base 0.06x, +4.6%) ok
longArithmetic 4 1.04x (base 1.08x, -3.6%) 0.05x (base 0.05x, +0.9%) ok
mathTranscendental 4 0.77x (base 0.79x, -2.2%) 0.06x (base 0.06x, -4.5%) ok
arraySequential 4 1.32x (base 1.36x, -3.2%) 0.39x (base 0.39x, +0.3%) ok
arrayRandom 4 1.05x (base 1.06x, -1.4%) 0.23x (base 0.23x, -0.1%) ok
objectAllocation 4 1.60x (base 1.56x, +2.9%) 0.42x (base 0.39x, +8.3%) ok
valueEscape 4 0.10x (base 0.10x, +0.3%) 0.04x (base 0.04x, +1.9%) ok
hashMapChurn 4 1.72x (base 1.80x, -4.5%) 0.09x (base 0.09x, +2.1%) ok
stringBuilding 4 1.32x (base 1.22x, +8.7%) 0.29x (base 0.32x, -8.8%) ok
recursion 4 1.49x (base 1.49x, -0.3%) 0.06x (base 0.06x, -1.2%) ok
quicksort 4 1.13x (base 1.13x, -0.1%) 0.12x (base 0.12x, -0.3%) ok

Result: no regression

@shai-almog

shai-almog commented Oct 22, 2026 •

Copy link
Copy Markdown
Collaborator Author

Compared 193 screenshots: 193 matched.
Native Windows port, REAL shipping pipeline: the hellocodenameone screenshot suite rendered by a binary CROSS-COMPILED on Linux (clang-cl + xwin, WebView2 linked) and RUN on a Windows x64 runner. Compared against the in-repo baseline in scripts/windows/screenshots.

Benchmark Results

Detailed Performance Metrics

Metric Duration
SIMD kernel backend SSE2 (x64) / NEON (arm64) native kernels
SIMD int-add (64K x300) java 44ms / native 3ms = 14.6x speedup
SIMD float-mul (64K x300) java 56ms / native 2ms = 28.0x speedup
SIMD kernel correctness PASS (native result == scalar reference)
Base64 native bridge unavailable (CN1 + SIMD + image benchmarks only)
Base64 payload size 8192 bytes
Base64 benchmark iterations 6000
Base64 SIMD byte path gated to scalar (CPU autovectorizes scalar; explicit SIMD not beneficial here)
Base64 CN1 encode 60.000 ms
Base64 CN1 decode 72.000 ms
Base64 SIMD encode 93.000 ms
Base64 encode ratio (SIMD/CN1) 1.550x (55.0% slower)
Base64 SIMD decode 81.000 ms
Base64 decode ratio (SIMD/CN1) 1.125x (12.5% slower)
Image encode benchmark iterations 100
Image createMask (SIMD off) 7.000 ms
Image createMask (SIMD on) 4.000 ms
Image createMask ratio (SIMD on/off) 0.571x (42.9% faster)
Image applyMask (SIMD off) 18.000 ms
Image applyMask (SIMD on) 20.000 ms
Image applyMask ratio (SIMD on/off) 1.111x (11.1% slower)
Image modifyAlpha (SIMD off) 27.000 ms
Image modifyAlpha (SIMD on) 15.000 ms
Image modifyAlpha ratio (SIMD on/off) 0.556x (44.4% faster)
Image modifyAlpha removeColor (SIMD off) 21.000 ms
Image modifyAlpha removeColor (SIMD on) 12.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off) 0.571x (42.9% faster)

@shai-almog

shai-almog commented Oct 22, 2026 •

Copy link
Copy Markdown
Collaborator Author

Compared 193 screenshots: 193 matched.
Native Linux port (x64), GTK3/Cairo/Pango, ParparVM bytecode-to-C (no JVM): the hellocodenameone screenshot suite rendered by a native ELF built + run on the GitHub x64 runner. Baseline: scripts/linux/screenshots.

ParparVM vs HotSpot (JDK 25): Linux x64

Runner CPU: AMD EPYC 9V45 96-Core Processor (baseline linux-x64@amd-epyc-9v45-96-core-processor)

Ratios are ParparVM / JDK 25: below 1.00x ParparVM is faster (time) or smaller (RAM). Median of 5 interleaved, paired rounds; every run's output was verified. A ratio more than 20% (time) / 25% (RAM) above its baseline in vm/selfhost/perf-baseline/ is a regression and fails; one that far below is an improvement, reported but not failed (a row whose calibration runs were noisier carries a wider tolerance), and for RAM the change must also exceed 0.05x in absolute terms. Both run unpinned on all of the runner's CPUs, with their own default thread counts.

Benchmark Cores Time RAM Status
hello (LinuxHelloMain) 4 0.90x (base 0.92x, -1.4%) 0.84x (base 0.84x, -0.5%) ok
translator (self) 4 0.67x (base 0.64x, +3.8%) 0.47x (base 0.45x, +3.8%) ok
intArithmetic 4 1.00x (base 1.00x, +0.0%) 0.04x (base 0.06x, -34.5%) ok
longArithmetic 4 1.00x (base 0.99x, +0.3%) 0.04x (base 0.07x, -51.2%) ok
mathTranscendental 4 1.20x (base 1.20x, -0.1%) 0.04x (base 0.06x, -32.3%) ok
arraySequential 4 2.83x (base 2.91x, -2.7%) 0.36x (base 0.36x, +0.3%) ok
arrayRandom 4 0.98x (base 1.04x, -5.7%) 0.20x (base 0.23x, -11.8%) ok
objectAllocation 4 1.58x (base 1.61x, -2.2%) 0.24x (base 0.24x, -1.5%) ok
valueEscape 4 0.10x (base 0.10x, +1.3%) 0.03x (base 0.04x, -34.1%) ok
hashMapChurn 4 1.34x (base 1.31x, +2.5%) 0.09x (base 0.09x, +3.7%) ok
stringBuilding 4 1.52x (base 1.58x, -3.3%) 0.26x (base 0.22x, +19.8%) ok
recursion 4 1.35x (base 1.34x, +0.8%) 0.04x (base 0.08x, -50.4%) ok
quicksort 4 1.05x (base 1.06x, -1.0%) 0.10x (base 0.11x, -13.0%) ok

Result: no regression

@shai-almog

shai-almog commented Oct 22, 2026 •

Copy link
Copy Markdown
Collaborator Author

Compared 193 screenshots: 193 matched.
Native Linux port (arm64), GTK3/Cairo/Pango, ParparVM bytecode-to-C (no JVM): the hellocodenameone screenshot suite rendered by a native ELF built + run on the GitHub arm64 runner. Baseline: scripts/linux/screenshots-arm.

ParparVM vs HotSpot (JDK 25): Linux arm64

Runner CPU: Neoverse-N2 (baseline linux-arm64@neoverse-n2)

Ratios are ParparVM / JDK 25: below 1.00x ParparVM is faster (time) or smaller (RAM). Median of 5 interleaved, paired rounds; every run's output was verified. A ratio more than 20% (time) / 25% (RAM) above its baseline in vm/selfhost/perf-baseline/ is a regression and fails; one that far below is an improvement, reported but not failed (a row whose calibration runs were noisier carries a wider tolerance), and for RAM the change must also exceed 0.05x in absolute terms. Both run unpinned on all of the runner's CPUs, with their own default thread counts.

Benchmark Cores Time RAM Status
hello (LinuxHelloMain) 4 0.93x (base 1.02x, -8.7%) 0.76x (base 0.93x, -18.1%) ok
translator (self) 4 0.71x (base 0.70x, +1.7%) 0.45x (base 0.51x, -11.5%) ok
intArithmetic 4 1.04x (base 1.04x, -0.0%) 0.03x (base 0.03x, -1.6%) ok
longArithmetic 4 0.79x (base 0.79x, +0.0%) 0.03x (base 0.03x, -1.2%) ok
mathTranscendental 4 1.10x (base 1.10x, +0.3%) 0.03x (base 0.03x, -0.3%) ok
arraySequential 4 0.35x (base 0.36x, -3.1%) 0.37x (base 0.37x, +0.1%) ok
arrayRandom 4 0.94x (base 0.94x, +0.1%) 0.20x (base 0.20x, -0.0%) ok
objectAllocation 4 1.93x (base 1.77x, +9.4%) 0.36x (base 0.38x, -5.3%) ok
valueEscape 4 0.51x (base 0.51x, -0.1%) 0.03x (base 0.03x, -0.1%) ok
hashMapChurn 4 0.86x (base 0.84x, +1.9%) 0.12x (base 0.12x, +0.2%) ok
stringBuilding 4 1.37x (base 1.57x, -12.5%) 0.27x (base 0.26x, +1.6%) ok
recursion 4 1.56x (base 1.43x, +8.9%) 0.03x (base 0.04x, -2.8%) ok
quicksort 4 1.00x (base 0.99x, +0.5%) 0.09x (base 0.09x, +0.5%) ok

Result: no regression

@shai-almog

shai-almog commented Oct 22, 2026 •

Copy link
Copy Markdown
Collaborator Author

Compared 193 screenshots: 193 matched.
Native Windows port (arm64 / Apple Silicon - Arm): full hellocodenameone screenshot suite rendered offscreen with Direct2D/DirectWrite, plus the real benchmarks (base64 native/CN1/SIMD, image createMask/applyMask/modifyAlpha/PNG/JPEG, NEON SIMD kernels). Compared against the in-repo baseline in scripts/windows/screenshots.

Benchmark Results

Detailed Performance Metrics

Metric Duration
SIMD kernel backend SSE2 (x64) / NEON (arm64) native kernels
SIMD int-add (64K x300) java 50ms / native 3ms = 16.6x speedup
SIMD float-mul (64K x300) java 49ms / native 2ms = 24.5x speedup
SIMD kernel correctness PASS (native result == scalar reference)
Base64 native bridge unavailable (CN1 + SIMD + image benchmarks only)
Base64 payload size 8192 bytes
Base64 benchmark iterations 6000
Base64 SIMD byte path gated to scalar (CPU autovectorizes scalar; explicit SIMD not beneficial here)
Base64 CN1 encode 59.000 ms
Base64 CN1 decode 71.000 ms
Base64 SIMD encode 77.000 ms
Base64 encode ratio (SIMD/CN1) 1.305x (30.5% slower)
Base64 SIMD decode 73.000 ms
Base64 decode ratio (SIMD/CN1) 1.028x (2.8% slower)
Image encode benchmark iterations 100
Image createMask (SIMD off) 5.000 ms
Image createMask (SIMD on) 3.000 ms
Image createMask ratio (SIMD on/off) 0.600x (40.0% faster)
Image applyMask (SIMD off) 20.000 ms
Image applyMask (SIMD on) 20.000 ms
Image applyMask ratio (SIMD on/off) 1.000x (0.0% slower)
Image modifyAlpha (SIMD off) 17.000 ms
Image modifyAlpha (SIMD on) 13.000 ms
Image modifyAlpha ratio (SIMD on/off) 0.765x (23.5% faster)
Image modifyAlpha removeColor (SIMD off) 21.000 ms
Image modifyAlpha removeColor (SIMD on) 13.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off) 0.619x (38.1% faster)

ParparVM vs HotSpot (JDK 25): Windows arm64

Runner CPU: ARMv8 (64-bit) Family 8 Model D49 Revision 0, MICROSOFT CORPORATION (baseline windows-arm64@armv8-64-bit-family-8-model-d49-microsoft-corporation)

Ratios are ParparVM / JDK 25: below 1.00x ParparVM is faster (time) or smaller (RAM). Median of 5 interleaved, paired rounds; every run's output was verified. A ratio more than 20% (time) / 25% (RAM) above its baseline in vm/selfhost/perf-baseline/ is a regression and fails; one that far below is an improvement, reported but not failed (a row whose calibration runs were noisier carries a wider tolerance), and for RAM the change must also exceed 0.05x in absolute terms. Both run unpinned on all of the runner's CPUs, with their own default thread counts.

Benchmark Cores Time RAM Status
hello (WinHelloMain) 4 1.02x (base 1.11x, -8.2%) 0.77x (base 0.75x, +2.4%) ok
translator (self) 4 0.84x (base 0.84x, +0.0%) 0.49x (base 0.51x, -3.3%) ok
intArithmetic 4 1.04x (base 1.04x, +0.0%) 0.06x (base 0.06x, +1.2%) ok
longArithmetic 4 0.79x (base 0.80x, -0.4%) 0.06x (base 0.06x, +1.6%) ok
mathTranscendental 4 0.72x (base 0.72x, -0.2%) 0.06x (base 0.06x, +0.2%) ok
arraySequential 4 0.36x (base 0.46x, -20.4%) 0.39x (base 0.39x, -0.0%) ok
arrayRandom 4 0.93x (base 0.94x, -0.3%) 0.23x (base 0.23x, +0.0%) ok
objectAllocation 4 1.76x (base 2.81x, -37.4%) 0.43x (base 0.43x, -0.8%) ok
valueEscape 4 0.76x (base 0.76x, +0.1%) 0.05x (base 0.05x, +1.2%) ok
hashMapChurn 4 0.99x (base 0.98x, +0.2%) 0.12x (base 0.12x, -0.3%) ok
stringBuilding 4 1.54x (base 1.43x, +8.0%) 0.27x (base 0.27x, -0.2%) ok
recursion 4 1.42x (base 1.46x, -2.8%) 0.06x (base 0.07x, -0.6%) ok
quicksort 4 1.00x (base 1.00x, +0.3%) 0.12x (base 0.12x, -0.2%) ok

Result: no regression

shai-almog and others added 2 commits October 22, 2026 13:39
Takes master's #5976 baseline overlays (repaired in #6059) over this branch's
deletion.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The runtime jar no longer carries plugin pack classes. Packs ship as sources
under META-INF/dart/java and the transpiler copies in only those the generated
code or the app's own Java names (transitively), sweeping any it no longer
selects; the mojo also drops their stale classes. The Executor and
AndroidGradleBuilder class-scan prune is removed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@shai-almog
shai-almog merged commit bced6dd into master Oct 22, 2026
28 of 73 checks passed
@shai-almog
shai-almog deleted the flutter-language-completeness branch October 22, 2026 12:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant