Skip to content

Device runtime: run pushed Codename One apps on a phone - #5561

Open
shai-almog wants to merge 130 commits into
masterfrom
device-runtime
Open

Device runtime: run pushed Codename One apps on a phone#5561
shai-almog wants to merge 130 commits into
masterfrom
device-runtime

Conversation

@shai-almog

@shai-almog shai-almog commented Aug 17, 2026

Copy link
Copy Markdown
Collaborator

Adds a device runtime: install one app on a phone, then push a project to it
from your IDE and watch it run natively in seconds. A third way to run a
Codename One app, alongside the simulator and a cloud device build.

Pushed classes are interpreted on the device against the framework already
compiled into it. Nothing is built, signed or installed between edits — the
edit-run loop measured 2.8 seconds end to end.

Try it

# install ~/cn1-device-runtime.apk on a phone (11MB, no native libs, any arch)
cd scripts/devruntime-ide-project
mvn -Ppush-lan package

The desktop finds the phone on the local network, shows a six-digit pairing
code you type once, and from then on it is edit-and-run. --device <address>
is there for networks that block a scan.

What is here

CodenameOne/src/com/codename1/interp/ the interpreter
Ports/{Android,iOSPort} per-platform linkers, iOS native bridge
vm/ByteCodeTranslator bundle writer, lambda desugaring, DevicePush tool
scripts/cn1-device-runtime/ the runtime app itself
scripts/devruntime-ide-project/ the project you open in an IDE
scripts/devruntime-probes/ 20 programs that found the defects worth knowing about
docs/developer-guide/Device-Runtime.asciidoc how and why

Decisions worth reviewing

Shims are generated over the whole API, never curated. A hand-maintained
list is a promise that applications only subclass what somebody anticipated, and
its failure mode is not an error message but an override that is silently never
called. The generator fails the build rather than pruning what will not compile
— a compile-and-drop loop once silently ate Interp_ui_Form.

Native-heavy subsystems are excluded from the shim set (ai, ar,
camera, surfaces, car, health, …). A shim is a compiled reference to the
class it extends, which is exactly what the build scans to decide what to link,
so generating the full API pulled 300MB of ML Kit, ARCore and CameraX natives
into an app that calls none of them. Cost: those types cannot be subclassed by
pushed code; calling them degrades to isSupported() == false, which is the
runtime's existing contract for a cn1lib without its native half.

iOS keeps shims rather than runtime vtable synthesis. Synthesis would make
14 more types extensible on iOS only, and Android cannot follow — so the usable
capability, the intersection, does not move. InterpHostVtableSynthesisIntegrationTest
stays for the day that changes.

synchronized uses the real object monitor, not a private lock table, which
is what makes wait/notify work.

Framework fixes that fell out

  • AndroidImplementation.getHostOrIP() returned dummy0's IPv6 link-local
    instead of a usable IPv4 — affects any caller.
  • CodenameOneImplementation.getResourceAsStream gained a local-resource hook,
    so a pushed program's theme.res is found by Resources.openLayered, which
    never passes through Display.

Verification

4798 core · 506 translator · 52 interpreter · SpotBugs 0 · 20-program device
battery green on an Android emulator and the iOS simulator, including a
four-file, three-package app entered through Lifecycle rather than main.

Every probe exists because something plausible turned out not to work; the
README records which defect each was written for.

Review rounds

Eleven findings from codex, all real, all fixed and each answered on its thread.
The two that mattered most:

  • Pairing handed out a bearer token. The peer id travelled in the clear on
    every push and never rotated, so one captured frame authorised arbitrary code
    on that phone forever. v2 is gone rather than deprecated. v3 derives a 256-bit
    secret on both ends from (typed code, peerId, deviceId) — never transmitted —
    and every connection answers a fresh challenge whose MAC covers the bundle.
    Authentication happens before the approval prompt, so nobody can raise dialogs
    on a stranger's phone until they tap Approve to stop them. What it still does
    not defeat is a passive observer of the pairing exchange itself, and the docs
    say so.
  • A failed class initializer left the class looking initialized, so later
    reads returned whatever half of it had been assigned. Four states and an owning
    thread now, per JLS 12.4.2.

Shipping it

.github/workflows/device-runtime-store.yml runs Mondays and on demand,
uploading to Play internal testing and TestFlight. It does not promote to
production and does not submit for review — a weekly automatic release would
put unread builds in front of the public and queue an iOS review every week
whether anything changed or not. Promotion stays one deliberate command.

Without credentials the job names the missing secrets and stops rather than
publishing half a release; none exist yet, so today it is a no-op that says so.

Listing text is in fastlane's layout (scripts/cn1-device-runtime/fastlane/) so
supply and deliver consume it directly, with store/privacy.md for both
stores' data forms and store/README.md for the secrets, the pre-submission
checklist and the review-risk assessment.

The compliance point that matters: this app runs code it did not ship with,
which is Guideline 2.5.2 — permitted for tools that develop or test code, and
only while the source is "completely viewable and editable by the user". The
runtime refuses to load a bundle whose sources it lacks, and shows them under
View source. Removing that screen makes the app unsubmittable, which is why
the code says so where the screen is defined.

Not done

NativeLookup stubbing covers the Java half of a cn1lib; the native half
reports unsupported. Resource push covers theme.res, CSS and images.

Screenshots for both stores, the Play content rating questionnaire, Apple's
privacy manifest and the console listings themselves are human steps, listed in
store/README.md.

🤖 Generated with Claude Code

@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: ddc43de0d2

ℹ️ 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 thread .github/workflows/device-runtime-store.yml Outdated
Comment thread CodenameOne/src/com/codename1/interp/InterpRuntime.java Outdated
Comment thread vm/ByteCodeTranslator/src/com/codename1/tools/translator/InterpBundleWriter.java Outdated
shai-almog and others added 3 commits August 17, 2026 15:49
Codename One apps run two ways today: the JavaSE simulator, which is not a
device, or a cloud device build, which costs minutes per iteration. This adds a
third: install one app on a phone, and from then on push a project to it from an
IDE and watch it run natively in seconds.

The app is not a shell around a compiled build. Pushed classes are interpreted
on the device against the framework already compiled into it, so nothing is
built, signed or installed between edits.

How the pieces fit
------------------

com.codename1.interp is the interpreter: one interpreted frame per real frame,
so Display.invokeAndBlock and every blocking idiom built on it still work. A
per-thread fuel counter bounds runaway code, and the budget is per entry into
the interpreter rather than per session -- measuring it per session kills every
callback that arrives later than the budget, which in an application whose whole
life is callbacks is every button press.

Interpreted classes reach the framework through InterpLinker: invoke thunks on
iOS, reflection on Android. A linker must dispatch on the receiver's class, not
the call site's declared type -- list.add(x) names java.util.List, and resolving
from there finds AbstractList.add, whose body throws.

Extending a framework class needs an object the framework accepts, which neither
platform can define at run time. Generated shims provide it: every public,
non-final, constructible class and every public interface the device exposes,
derived by scanning the framework jar and codenameone-java-runtime rather than
curated. A hand-maintained list is a promise that applications only subclass
what somebody anticipated, and its failure mode is not an error but an override
that is silently never called.

Lambdas and method references are rewritten into real classes when the bundle is
written, since neither target has a runtime invokedynamic. Enums are answered by
the interpreter, java.lang.Enum having no shim and needing none.

Store compliance is built in rather than bolted on: the runtime refuses to load
a bundle whose sources it cannot show, and shows them.

Verified
--------

4798 core tests, 506 translator tests, 43 interpreter tests, SpotBugs at zero,
and a 20-program device battery (scripts/devruntime-probes) passing on both an
Android emulator and the iOS simulator -- including a four-file, three-package
application entered through Lifecycle rather than main.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Listing text in fastlane's layout so supply and deliver can consume it, a
privacy statement for both stores' data forms, and a scheduled workflow.

The weekly job uploads to Play internal testing and TestFlight. It does not
promote to production and does not submit for App Store review, which is a
decision rather than an omission: a weekly automatic release would put unread
builds in front of the public and queue an iOS review every week whether or not
anything changed. Promotion stays one command, taken deliberately.

Without publishing credentials the job reports which secrets are missing and
stops, rather than publishing half a release. None of them exist yet.

The review risk is written down rather than discovered later. This app runs code
it did not ship with, which is squarely Guideline 2.5.2 -- permitted for tools
that develop or test code, and only while the source stays viewable and editable
on the device. That is why the runtime refuses a bundle it cannot show the
source for. 4.7.2 is the sharper edge and the argument to make is that this is
point to point developer tooling rather than a mini-app platform.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ate every push

Three things the review asked for, and two defects found on the way.

The interpreter moves from com.codename1.interp to com.codename1.impl.interp.
It is an implementation detail of one app, not public API, and the impl
hierarchy is what keeps it out of the javadoc. Note the package name is not only
a Java name: ParparVM's dead-code pass recognises the runtime's own classes by
their C-mangled prefix, so Parser.isLoadBearingForInterp moved with it. Missing
that would have stubbed out InterpRuntime.run in an interp-host build, which
fails by succeeding -- every pushed program "runs" instantly and executes
nothing.

The ~1000 generated shims leave git. They are a mechanical function of the
framework jar, so the build generates them: a tools module builds the
generator, exec-maven-plugin runs it into target/generated-sources/shims, and
build-helper adds that as a source root.  scripts/generate-interp-shims.sh
keeps the three properties the build takes on faith -- every shim compiles, the
load-bearing ones exist, generating twice is identical -- and now asserts them
against a scratch tree instead of writing into src.

Pairing no longer hands out a bearer token. v2 authorised a push with a peer id
sent in the clear, so capturing one frame on a LAN meant pushing arbitrary code
to somebody's phone forever. v3 derives a 256-bit secret on both ends from the
typed code, the peer id and the device id -- never transmitted, 20k HMAC
iterations so grinding six digits costs something -- and every connection
answers a fresh challenge whose MAC covers the bundle. Authentication happens
before the approval prompt, so nobody can raise dialogs on a stranger's phone
until they tap Approve to stop them. What this still does not defeat is a
passive observer of the pairing exchange itself, which the docs now say plainly.
There are two implementations of the derivation, since ParparVM has no
javax.crypto; InterpPairingSecretTest runs both and compares.

Also fixed:

- A class initializer that threw left the class marked initialized, so later
  reads returned whatever half of it had been assigned. Four states and an
  owning thread now, per JLS 12.4.2.
- Sources were keyed by file name, so two Util.java in different packages
  collided and the runtime refused the program with "missing the source file
  Util.java" for a file it had been handed. Keyed by package now.
- The iOS release job resolved ExportOptions.plist relative to the generated
  Xcode project, which is not where it lives.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
check-copyright-headers gates every added source file, and the probes and the
IDE sample are ours -- not third-party, so the exclusions file (which is for
provenance, and rejects anything else) is the wrong place for them.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@chatgpt-codex-connector

Copy link
Copy Markdown

💡 Codex Review


P1 Badge Register the Android linker before checking support

On every Android launch this support check is false because no code installs the newly added InterpAndroidLinker: a repository-wide search finds InterpPlatform.register(...) only in IOSImplementation. Consequently DeviceRuntimeApp.init() returns without starting either transport and the Android runtime app cannot accept any pushed program; register an InterpAndroidLinker during Android port initialization.


final InterpRuntime rt = new InterpRuntime(bundle, InterpPlatform.getLinker(), factory);
factory.attach(rt);
runtime = rt;

P2 Badge Stop the previous program before replacing its runtime

When the normal “Push again … to replace it” workflow loads a second bundle, this assignment discards the service's reference to the previous runtime without requesting cancellation or invoking the previous Lifecycle.stop()/destroy(). Programs that registered global listeners, timers, network callbacks, or worker threads therefore continue executing alongside the replacement, and after this overwrite the service can no longer stop them.


if (!send(payload, port, peerId, false) && rejectedAsUnpaired()) {

P2 Badge Propagate failed LAN pushes as process failures

For an already-paired LAN push, send() returns false when the user denies approval, authentication fails, or the device rejects/runs the bundle unsuccessfully; unless the message contains “not paired,” this condition falls through and main() exits with status 0. The documented Maven push-lan profile therefore reports BUILD SUCCESS for a failed deployment, which also prevents scripts and IDE integrations from detecting the failure.


synchronized (found) {
if (found[0]) {
return;
}
found[0] = true;
foundAt[0] = candidate;
}
handle(is, os, false);

P2 Badge Validate a discovered peer before remembering its address

If any unrelated service happens to accept this port during the subnet sweep, the callback marks it as found before handle() validates the protocol magic. The sweep then persists that address, and subsequent dial attempts likewise treat a successful TCP connection as served even when the peer never sends a runtime frame, so discovery can remain stuck on the wrong machine; only publish found/foundAt after a valid handshake.

ℹ️ 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".

@github-actions

github-actions Bot commented Aug 17, 2026

Copy link
Copy Markdown
Contributor

✅ Continuous Quality Report

Test & Coverage

Static Analysis

Generated automatically by the PR CI workflow.

@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: 3118d715e7

ℹ️ 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 thread scripts/cn1-push.sh Outdated
Comment thread CodenameOne/src/com/codename1/impl/interp/InterpRuntime.java Outdated
Comment thread .github/workflows/device-runtime-store.yml Outdated
shai-almog and others added 2 commits August 17, 2026 21:15
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Three were real: a SecureRandom built per call (worse and slower than one
seeded once, and what it generates is the pairing code), two Files.createDirectories
calls on a getParent() that SpotBugs cannot prove non-null, and an
ExecutorService.submit whose Future was never going to be read -- execute()
says what the scan actually wants.

The other two are recorded in spotbugs-exclude.xml with their reasons: a
command-line tool exits, and a failure while enumerating this machine's
interfaces must be answered with 'no device found' rather than by killing the
push.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@chatgpt-codex-connector

Copy link
Copy Markdown

💡 Codex Review

ShimObjectFactory factory = new ShimObjectFactory();
final InterpRuntime rt = new InterpRuntime(bundle, InterpPlatform.getLinker(), factory);
factory.attach(rt);
runtime = rt;

P1 Badge Tear down the previous runtime before replacing it

When a second bundle is pushed, this assignment only drops the service's reference to the previous runtime; it neither requests cancellation nor invokes any lifecycle cleanup. Peers, background threads, timers, and framework listeners retain references to the old runtime, so the supposedly replaced application can continue executing and mutate the UI or shared resources while the new application runs. Add a runtime deactivation/cleanup path and call it before publishing the replacement.


if ("toString".equals(name) && args.length == 0) {
return io.toString();
}
return NOT_OBJECT_METHOD;

P2 Badge Route Object monitor methods to interpreted-object monitors

For a peerless interpreted object, calls inherited from Object are handled here, but wait, notify, and notifyAll fall through to NOT_OBJECT_METHOD and ultimately raise AbstractMethodError. Consequently ordinary code such as synchronized (lock) { lock.wait(); }, where lock is a pushed POJO, cannot use Java monitor coordination even though MONITORENTER successfully acquired that same InterpObject; dispatch these methods against the monitor used by the interpreter.


for (File f : kids) {
if (f.isDirectory()) {
addSourceTree(f);
} else if (f.getName().endsWith(".java")) {
String text = new String(Files.readAllBytes(f.toPath()), StandardCharsets.UTF_8);
addSource(sourceKey(packageOf(text), f.getName()), text);

P2 Badge Include Kotlin files in pushed source bundles

When compiled output contains Kotlin classes, even explicitly passing a Kotlin source directory to --source cannot produce a valid bundle because this traversal ignores every .kt file. The reader later requires the SourceFile entry (for example Foo.kt) for each carried class and rejects the bundle as missing source, so Kotlin Codename One applications cannot be pushed; collect Kotlin sources and ensure the default project discovery also includes src/main/kotlin.


if ("com/codename1/system/Lifecycle".equals(cn.superName)) {
lifecycle = cn.name;
}

P2 Badge Discover Lifecycle subclasses through the class hierarchy

Entry-point discovery recognizes only classes whose immediate superclass is Lifecycle. If an application class extends a project-defined base lifecycle, this either reports no entry point or selects the base class itself (often abstract) instead of the concrete application, even though InterpRuntime.extendsHost() can execute an indirect subclass once selected. Resolve the collected superclass graph and choose the concrete transitive Lifecycle subclass.

ℹ️ 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".

@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: d4d7ad9bda

ℹ️ 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 thread vm/ByteCodeTranslator/src/com/codename1/tools/translator/DevicePush.java Outdated
Comment thread vm/ByteCodeTranslator/src/com/codename1/tools/translator/DevicePush.java Outdated
Comment thread CodenameOne/src/com/codename1/impl/interp/InterpRuntime.java
Comment thread CodenameOne/src/com/codename1/impl/interp/InterpRuntime.java Outdated
@github-actions

Copy link
Copy Markdown
Contributor

Cloudflare Preview

Seven findings, all real.

The interpreter:

- A class literal for a pushed type puts an InterpClass on the stack, because
  there is no host class object to hand back -- and then the bytecode goes on
  calling java.lang.Class methods on it, which no linker can serve. The part of
  Class that means anything here (naming, identity, isInterface, isInstance,
  getSuperclass) is answered by the interpreter; anything else is refused by
  name rather than answered wrongly.
- `new Entry[1][]` names its component `[LEntry;`, not `Entry`, so the
  bundle-membership test missed it and asked the host loader for a class only
  the bundle has. It looks through the brackets now, and multianewarray builds
  the nested Object[] itself rather than delegating.
- JLS 12.4.1: initializing a class initializes the superinterfaces that declare
  a default method. Only those -- initializing all of them would run
  initializers Java never runs, which is as wrong as running them late.

The push tool:

- The Lifecycle entry point was chosen by direct superclass only, so a project
  whose app extends its own BaseApp entered BaseApp: an abstract class that was
  never meant to be instantiated. It walks the hierarchy now and takes the
  deepest concrete descendant.
- A subnet scan treated any host that accepted TCP on the port as the device,
  and then failed the push against it while the real device sat unqueried.
  There is a PING frame now; only an answer in our own protocol wins.
- cn1-push.sh still spoke v2, which nothing accepts any more. Its paired mode
  is gone rather than ported: it is a loopback helper, and pushing to a phone
  over Wi-Fi is DevicePush's job. A third copy of the derivation in a shell
  script would only drift from the two that have to agree.

The release workflow now checks every secret the job will consume, not the two
that name the store, so a half-configured store says so in preflight instead of
half an hour later in the signing step.

Co-Authored-By: Claude Opus 5 (1M context) <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: 1356d055bd

ℹ️ 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 thread CodenameOne/src/com/codename1/impl/interp/InterpRuntime.java Outdated
Comment thread CodenameOne/src/com/codename1/impl/interp/InterpRuntime.java
Comment thread CodenameOne/src/com/codename1/impl/interp/InterpRuntime.java Outdated
Comment thread vm/ByteCodeTranslator/src/com/codename1/tools/translator/DevicePush.java Outdated
Comment thread CodenameOne/src/com/codename1/impl/CodenameOneImplementation.java Fixed
CLDC11 keeps AbstractMethodError's constructors package-private, so the
framework cannot throw one with a message and the Ant leg would not compile.
IncompatibleClassChangeError carries the message, and a message naming the
method is worth more here than the exactly right type.

The three inline source blocks in the device runtime chapter move into
docs/demos and are included by tag, which is what the guide validator asks of
every other chapter.

Co-Authored-By: Claude Opus 5 (1M context) <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: 47fd057b7e

ℹ️ 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 thread CodenameOne/src/com/codename1/impl/interp/InterpObject.java
Comment thread CodenameOne/src/com/codename1/impl/interp/InterpRuntime.java
Comment thread CodenameOne/src/com/codename1/impl/interp/InterpRuntime.java Outdated
Comment thread CodenameOne/src/com/codename1/impl/interp/InterpRuntime.java Outdated
The interpreter's depth cap throws one, and it is the right type: ParparVM's
java.lang has StackOverflowError and so does every JVM the simulator runs on.
It was simply missing from this compile-time stub, so the Ant leg could not
compile the framework while the Maven leg could -- the two disagree because
only the Ant build puts CLDC11 on the bootclasspath.

Reproducing that locally needs the same -bootclasspath; compiling core and
CLDC11 together against a full JDK resolves java.lang from the JDK and reports
nothing.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@chatgpt-codex-connector

Copy link
Copy Markdown

💡 Codex Review

@Override
public void init(Object m) {
// NOTE: Do not explicitly set the PlayServices instance to anything other than

P1 Badge Register the Android linker during port initialization

On every Android runtime build, DeviceRuntimeService.isSupported() requires InterpPlatform.isAvailable(), but the Android initialization path never calls InterpPlatform.register(new InterpAndroidLinker()); a repository-wide search finds no other construction of InterpAndroidLinker, while IOSImplementation.init() performs the corresponding registration. Consequently startDialer() always returns false on Android with “no interpreter bindings,” so the newly added Android device runtime cannot accept any pushes.


f.pushRef(isInterpretedLeaf(comp)
? new Object[count]
: linker.newArray(comp.startsWith("[") ? comp : "L" + comp + ";", count));

P2 Badge Preserve interpreted array component types

When the leaf type is interpreted, allocating every reference array as a plain Object[] discards its runtime component type. For example, after Sub[] a = new Sub[1]; Base[] b = a;, storing new Base() through b must throw ArrayStoreException, but AASTORE later writes unconditionally into this Object[], so the invalid value is accepted and the array is silently corrupted. Retain component metadata or otherwise validate each store against the allocated array type.

ℹ️ 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".

@shai-almog

shai-almog commented Aug 17, 2026

Copy link
Copy Markdown
Collaborator Author

Compared 12 screenshots: 12 matched.
✅ JavaSE simulator integration screenshots matched stored baselines.

@github-actions

github-actions Bot commented Aug 17, 2026

Copy link
Copy Markdown
Contributor

Developer Guide build artifacts are available for download from this workflow run:

Developer Guide quality checks:

  • AsciiDoc linter: No issues found (report)
  • Vale: 3 alert(s) (3 errors, 0 warnings, 0 suggestions) (exit code 1) (report)
  • Paragraph capitalization: No paragraph capitalization issues (report)
  • LanguageTool: 1 advisory match(es) — top: MORFOLOGIK_RULE_EN_US (1) (report)
  • Image references: No unused images detected (report)

The chapter was written before the rebase brought in the vale gate and had
never been through it: 141 findings, all in this one file. The bulk is the
guide's house style of contractions. The rest is adverbs that carried no
weight, two sentences opening with 'So', and three quotations of literal text
-- a runtime message, a device dialog, Google Play's policy -- where moving the
period inside the quotes would misquote the source, so those carry a vale-skip
naming the reason.

Where an adverb was load-bearing, the sentence says the thing instead: 'which
silently dropped Runnable' is now 'which dropped Runnable with no diagnostic'.

Co-Authored-By: Claude Opus 5 (1M context) <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: 7a54e8f350

ℹ️ 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 thread Ports/iOSPort/src/com/codename1/impl/ios/InterpIOSSymbols.java Outdated
Comment thread CodenameOne/src/com/codename1/impl/interp/InterpRuntime.java Outdated
Comment thread CodenameOne/src/com/codename1/impl/interp/InterpRuntime.java Outdated
Comment thread CodenameOne/src/com/codename1/impl/interp/InterpRuntime.java
The rebase reconstruction rewrote the file with LF where master has CRLF, so a
63-line change showed up as a 14,000-line rewrite. That is not only noise:
CodeQL reports alerts for code a PR changed, and a whole-file diff re-reported
twelve alerts that master already has and this branch did not introduce.

Co-Authored-By: Claude Opus 5 (1M context) <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: f86bfb26ac

ℹ️ 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 thread scripts/cn1-push.sh Outdated
Comment thread scripts/cn1-push.sh Outdated
Comment thread CodenameOne/src/com/codename1/impl/interp/InterpRuntime.java
The chapter used British spellings in a US-English guide -- which the
cross-document coherency rule catches, not just the dictionary -- and a
vocabulary LanguageTool has never heard of. Spellings are now US; the
vocabulary (vtable, clazz, dex, desugar, devirtualize, supertype, cmake,
thebaselab) is in the accept list with a line saying what each one is.

Two sentences were rephrased rather than allowlisted: LanguageTool reads
'An interpreted X has to be an object...' as a typo for 'and' once the code
spans are stripped, and the rule is right that the sentence was hard to parse.

Co-Authored-By: Claude Opus 5 (1M context) <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: 79950c65f5

ℹ️ 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 thread CodenameOne/src/com/codename1/impl/interp/InterpRuntime.java
Comment thread CodenameOne/src/com/codename1/impl/interp/InterpRuntime.java
Comment thread CodenameOne/src/com/codename1/impl/interp/InterpRuntime.java
Codex reposted the `Object.clone()` thread. It's the same known
non-goal from prior rounds -- Codename One doesn't carry `Cloneable`,
and the device runtime inherits that stance -- so no behaviour
change lands here. What was missing was a written answer to the
finding so codex and the tree agree on the outcome.

Added a paragraph at the dispatch site in `InterpRuntime.hostCall`
that walks through why the fromHost hop unwraps a shim clone back
to the original wrapper, and why the fix is not a runtime intercept:
cloning a pushed object honestly needs a fresh InterpObject beside
a fresh peer wired to each other, and adding that on the device
runtime alone would put a Cloneable implementation on one target
that no other Codename One target ships.

Also a matching entry in the developer guide's known-deviations
section under `Device-Runtime.asciidoc`, so the answer sits with
the ArrayStoreException entry rather than only in the code.

Co-Authored-By: Claude Opus 4.7 (1M context) <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: eb2b3588d9

ℹ️ 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".

}
}

private volatile Failure lastFailure; //NOPMD AvoidUsingVolatile - written from another thread on purpose

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 Track failure associations without a single shared slot

When two threads enter this runtime concurrently, any throw on thread B overwrites thread A's failure record; if A then executes throw e from its catch block, toThrowable() no longer recognizes the rethrow and replaces the original interpreted stack with the rethrow site, while cross-thread reporting can lose A's stack entirely. Fresh evidence beyond the earlier single-thread rethrow issue is that failure identity is still stored in this one runtime-wide slot even though the adjacent ThreadState explicitly supports concurrent entries; retain associations by throwable identity (with thread-local state for rethrow detection) instead.

Useful? React with 👍 / 👎.

P2 from codex: `lastFailure` was a runtime-wide slot even though the
adjacent `ThreadState` already exists for exactly this reason. A
throw on the event thread would overwrite what the pusher thread
was about to rethrow, breaking rethrow-detection and losing cross-
thread stack reporting.

Failure identity now lives on `ThreadState.lastFailure` for the
rethrow-detection path, so per-thread throws don't stomp on each
other. Cross-thread lookup for `interpretedStackFor` and
`hostCallFor` uses a new `WeakHashMap` keyed by throwable identity
(Throwable inherits Object.hashCode/equals, so identity == equality
here). `Collections.synchronizedMap` makes both entry and lookup
safe from any thread, and weak keys mean a caught throwable can be
released without keeping the runtime alive.

Every write site (`toThrowable` and `hostCall`'s catch) routes
through a single `recordFailure(st, f)` helper that sets the
thread's slot and registers both `f.thrown` and (when distinct)
`f.original` in the map, so a caller catching either the wrapper
or the InterpObject can still ask for the interpreted stack.

Co-Authored-By: Claude Opus 4.7 (1M context) <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: 82db26feb0

ℹ️ 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 +110
private final java.util.Map failuresByThrown =
java.util.Collections.synchronizedMap(new java.util.WeakHashMap());

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 Use identity semantics for failure lookup

When a host throwable subclass overrides equals() or hashCode(), this WeakHashMap is not identity-keyed: recording a distinct but equal exception can replace another exception's failure metadata, so interpretedStackFor() and hostCallFor() return the wrong throw site, and a mutable hash can make the metadata unreachable altogether. Throwable subclasses are free to override these methods, so the cross-thread registry needs weak identity keys rather than relying on ordinary map equality.

Useful? React with 👍 / 👎.

Comment on lines +3937 to +3939
failuresByThrown.put(f.thrown, f);
if (f.original != f.thrown && f.original instanceof Throwable) { //NOPMD CompareObjectsWithEquals - identity is the point
failuresByThrown.put(f.original, 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 Break the failure value's strong reference to its weak key

Whenever a throwable is recorded, the WeakHashMap value is the Failure that strongly retains the same throwable in f.thrown (and sometimes f.original). The map therefore keeps its own weak key alive through the value, so completed exceptions are never reclaimed and a long-running device runtime accumulates every recorded failure. Store metadata that does not retain the key, or otherwise make both the identity key and back-references weak.

Useful? React with 👍 / 👎.

Two P2s on round 93's cross-thread failure registry.

Identity keys: `WeakHashMap` uses equals/hashCode, and a Throwable
subclass overriding either could collapse two distinct exceptions
to one map entry (or make an entry unreachable when a mutable hash
changed). Replaced with a plain `HashMap` keyed by
`IdentityWeakRef`: a `WeakReference` subclass whose `hashCode` is
captured `System.identityHashCode(referent)` at construction and
whose `equals` is `==` on the referents. Two exceptions that
`equals` each other now still get separate entries; a
`hashCode()` mutation after recording can't hide the entry.

No strong ref cycle: the previous `Failure` value strongly retained
its own key (`f.thrown`, `f.original`), so `WeakHashMap` kept its
own weak keys alive through the value and the map leaked every
exception. Split off `FailureInfo` -- just `stack` and `hostCall`
-- as the map's value; the full `Failure` with its throwable
references stays only on `ThreadState.lastFailure` for rethrow
detection. The reference queue drains on every put/get so cleared
entries don't accumulate either.

Co-Authored-By: Claude Opus 4.7 (1M context) <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: 878685f073

ℹ️ 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 +1644 to +1646
emitDispatch(w, m, "$runtime == null ? InterpRuntime.NOT_OVERRIDDEN\n"
+ " : $runtime.dispatch($interp, \"" + m.getName() + "\", \""
+ descriptorOf(params, ret) + "\", new Object[]{" + boxed + "})");

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 constructor-time overrides to interpreted subclasses

When a host superclass constructor invokes an overridable method implemented by the pushed subclass, this null guard reports NOT_OVERRIDDEN and runs the host superclass implementation instead, although Java dispatches to the subclass even before its fields are initialized. This affects an ordinary Form subclass overriding initGlobalToolbar(), because Form(Layout) invokes that protected hook during construction; the interpreted override is silently skipped, and an abstract constructor-time hook would instead produce AbstractMethodError. Make the interpreter/runtime context available to shim dispatch during the super(...) call rather than treating that window as unoverridden.

Useful? React with 👍 / 👎.

P2 from codex: a framework superclass constructor that invokes an
overridable method during initialisation -- Form(Layout) calls
initGlobalToolbar() -- reached the shim's override while `$runtime`
and `$interp` were still null (Java assigns subclass fields only
after super() returns). The old fall-through to super silently
skipped the interpreted override; an abstract constructor-time
hook would even hit AbstractMethodError.

The shim now captures its (runtime, interp) context onto a thread-
local stack in the arg-evaluation phase that precedes super(), and
the dispatch helper falls back to that context when the fields
aren't set yet. Concrete steps:

* `InterpRuntime` gains a thread-local `PENDING_SHIM_CONTEXT`
  stack, `push/popPendingContext` helpers, and
  `dispatchOrDeferred(rt, io, name, desc, args)` which uses the
  stack top when `rt == null`.
* Every override the generator emits (including toString / hashCode
  / equals) now routes through `dispatchOrDeferred` -- same number
  of lines, cleaner call, and it does the right thing during super().
* Each shim gets a `$captureCtx(rt, io) -> Void` helper. Every
  public constructor is a `this()` chain that pushes the context
  via `$captureCtx` in argument-evaluation (before super()); the
  private companion runs `super(...)`, assigns the fields, and
  pops. The chain shape stays uniform across zero-arg and
  primitive-first-arg constructors, so there is no per-first-type
  overload to emit.
* `generate-interp-shims.sh` still emits 842 class + 279 interface
  shims, all compiling and reproducible.

Co-Authored-By: Claude Opus 4.7 (1M context) <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: e31810d348

ℹ️ 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 +1091 to +1094
w.println(" super(" + call + ");");
w.println(" this.$runtime = runtime;");
w.println(" this.$interp = interp;");
w.println(" InterpRuntime.popPendingContext();");

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 Pop pending shim contexts when super construction throws

When a framework superclass constructor throws—either because its arguments are invalid or because a constructor-time interpreted override fails—execution never reaches this popPendingContext() call. The static ThreadLocal stack therefore retains the InterpRuntime and InterpObject for the lifetime of the framework thread, so repeated failed constructions or pushes on the long-lived EDT accumulate entire bundle graphs; the stale top entry can also be used by a later null-field deferred dispatch. Clean up the matching pending context on the exceptional return from peer construction as well as on this success path.

Useful? React with 👍 / 👎.

P2 from codex: a super() that threw -- invalid args, or a
constructor-time interpreted override that failed -- never reached
the constructor body's `popPendingContext()`, so the shim's
`$captureCtx` push was stranded on the thread-local for the rest
of the thread's life. Repeated failed constructions (or the EDT
retrying) accumulated whole bundle graphs there and later
null-field deferred dispatch could pick up the stale top entry.

`InterpRuntime` gains `pendingContextDepth()` and
`trimPendingContext(depth)` helpers, and the shim generator now
wraps each `create()` body in `try { ... } finally { trim to the
depth captured before the constructor call }`. The success path
still runs the constructor's own pop first and the trim becomes
a no-op; the exception path (constructor abrupt completion)
brings the stack back where it was so nothing accumulates.

Shims regenerate at 842 class + 279 interface, all compile,
double-run reproducible.

Co-Authored-By: Claude Opus 4.7 (1M context) <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: aff96d3b56

ℹ️ 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".

rt = (InterpRuntime) ctx[0];
io = (InterpObject) ctx[1];
}
return rt.dispatch(io, name, descriptor, args);

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 the partial peer before constructor-time dispatch

When a framework constructor invokes an interpreted override and that override calls an inherited host method on this—for example, Form.initGlobalToolbar() calling setToolbar()—this dispatch enters interpreted code while io.hostPeer is still null, because createPeer() assigns it only after the factory constructor returns. The inherited call therefore cannot run against the partially constructed shim and ends in the peerless fallback with an IncompatibleClassChangeError or null-target failure. Fresh evidence after the deferred-dispatch fix is that the pending context carries only the runtime and wrapper, not the actual shim receiver; expose that receiver as the temporary peer during super(...).

Useful? React with 👍 / 👎.

Comment on lines +1258 to +1260
Object thrown = it.getThrown();
int handler = findHandler(m, insn, thrown, false);
if (handler < 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 Prevent Throwable handlers from swallowing cancellation

When pushed code wraps a retry loop in catch (Throwable)—for example, for (;;) { try { workForever(); } catch (Throwable t) {} }—routing InterpCancelled into this handler lets the loop resume, and the still-set flag merely raises another catchable cancellation at a later checkpoint. Consequently the Stop request or EDT budget never unwinds the frame and the event thread can remain pegged indefinitely. Allow cleanup handlers to execute, but keep cancellation pending so a handler that completes normally cannot resume the program.

Useful? React with 👍 / 👎.

Comment on lines +271 to +272
if ("package".equals(token)) {
// Terminated by `;` (Java) or end-of-line (Kotlin, whose

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 Ignore backtick identifiers while scanning Kotlin packages

When a Kotlin file has a file annotation whose qualified name contains an escaped keyword before the real declaration, such as @file:com.foo.package.Ann followed by package real.pkg, this scanner treats the package text inside the backticks as the declaration and returns the remainder of the annotation line. The source is then stored under the wrong key, so requireSourcesFor() rejects the generated real/pkg/... classes as missing their source; package keywords should only be recognized outside backtick-escaped identifiers.

Useful? React with 👍 / 👎.

Three P2s from codex.

Constructor-time overrides can now reach inherited host methods on
`this`. Round 95's `dispatchOrDeferred` only carried (runtime,
interp) through the pending context, so an override that called
back into `super.setToolbar(...)` during `super()` reached hostCall
with `io.hostPeer == null` and ended in the peerless fallback with
IncompatibleClassChangeError. The dispatch helper now takes the
shim (`this` inside the generated override), binds it as
`io.hostPeer` when hostPeer is null, and sets `io.hostPeerOwner`
via `factory.peerClassName(shim)` so the host-side dispatch path
finds the right owner. `createPeer` assigns the same reference
back once the factory returns; the write is idempotent.

`InterpCancelled` no longer heartbeats through a retry loop. A
`for(;;) { try { work(); } catch (Throwable t) {} }` used to keep
catching every re-raised cancellation and running forever, because
the next checkpoint's throw was catchable by the same handler.
`ThreadState.cancelCaughtOnce` now flips once any handler matches
InterpCancelled; `findHandler` from then on only lets catch-all
(finally) entries match a subsequent InterpCancelled, so cleanup
runs but the retry loop unwinds. Cleared with `cancelRequested` at
the start of every `runMain`.

`packageOf` now skips backtick-escaped identifiers as opaque
tokens. A Kotlin annotation naming an escaped keyword before the
declaration -- `@file:com.foo.`package`.Ann` followed by `package
real.pkg` -- was reading the word inside the backticks as the
declaration; the source got the wrong key and the reader refused
the whole push. New assertion covers the case.

Shims regenerate at 842 class + 279 interface, all compile; 5274
tests pass, SpotBugs 0 on core-unittests and ByteCodeTranslator.

Co-Authored-By: Claude Opus 4.7 (1M context) <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: 6138a08007

ℹ️ 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 +1285 to +1286
if (thrown instanceof InterpCancelled) {
state().cancelCaughtOnce = 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 Reset cancellation handler state between interpreter entries

Fresh evidence after the catch-once change is that this flag is set when a cancellation traverses a typed cleanup handler but is reset only by runMain(). If that cancellation escapes a callback, a later callback on the same EDT starts with cancelCaughtOnce still true; its next budget cancellation therefore skips every typed catch (Throwable), including javac's try-with-resources cleanup handler, so resources acquired by later callbacks are not closed. Reset this state when a cancellation leaves a top-level interpreter entry while preserving it during that cancellation's unwind.

Useful? React with 👍 / 👎.

Cancellation state is now per-interpreter-entry, not per-thread
lifetime. Round 97 set `ThreadState.cancelCaughtOnce` when a
typed handler swallowed an `InterpCancelled` and only cleared it
in `runMain`. On the EDT that meant a Stop that traversed a
try-with-resources cleanup in one listener left the flag set for
every subsequent listener called on the same thread; the next
budget cancellation those saw skipped their own typed
`catch (Throwable)`, so the resource the callback opened was
never closed.

The reset moves to the fresh-entry branch of `invokeInterpreted`,
next to the runStart/fuel/hostCallDepth reset. A nested (non-fresh)
entry -- a callback invoked from inside another interpreted frame
-- shares the enclosing entry's cancellation unwind and does not
reset; a fresh entry that follows the unwind's return to depth 0
starts clean. `runMain`'s clear stays, harmless but redundant, so
the direct-run path never observes stale state either.

5274 tests pass, SpotBugs 0 on core-unittests.

Co-Authored-By: Claude Opus 4.7 (1M context) <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: efc37768b1

ℹ️ 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 +296 to +297
if (!Modifier.isStatic(mods) && !Modifier.isPrivate(mods)
&& !Modifier.isAbstract(mods)) {

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 Retain inherited abstract interface methods during lookup

When pushed code invokes a method through a host abstract class that merely inherits the method from an interface (for example, abstract class A implements I {} with a concrete B extends A), javac emits invokevirtual A.m, but this filter discards I.m because it is abstract. lookupMethod() then throws NoSuchMethodException instead of letting Method.invoke dispatch to B.m. The same filter exists in the inspected JavaSE linker, so abstract interface declarations must remain available for class-owner method resolution even though they need separate handling from competing defaults.

Useful? React with 👍 / 👎.

// pick one arbitrarily. The host will reject the mismatched slot
// -- but that mismatch is present in the pushed program, not
// introduced here.
return a;

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 aliases that satisfy sibling interfaces

After the superclass/subclass alias fix, an interpreted Both[] whose element type implements two unrelated host interfaces still fails when the same array is passed as both A[] and B[]. Neither interface descriptor is more specific, so this fallback chooses A[], assigns that one materialization to both arguments, and reflective invocation rejects it for the B[] parameter even though a real Both[] is valid for both. Preserve the interpreted component type or derive a materialization from the common peer type rather than arbitrarily choosing one sibling descriptor.

Useful? React with 👍 / 👎.

// line of perfectly ordinary Java, and reading it as the default
// package stored the source under a key the runtime never looks up --
// so the push was refused for missing source that had been supplied.
String code = stripComments(decodeUnicodeEscapes(text));

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 Avoid Java Unicode preprocessing for Kotlin package scans

When a Kotlin file has a pre-package file annotation containing a Unicode escape that produces a quote, such as @file:Suppress("\u0022 package fake") followed by package real, the escape is part of the Kotlin string value and does not terminate the literal lexically. Decoding it before blanking literals creates a synthetic quote delimiter, which can expose the fake package token or consume the real declaration; the source is then keyed under the wrong path and requireSourcesFor() rejects the push as missing source. Apply Java's pre-lexing Unicode expansion only to Java sources, or scan Kotlin literals before interpreting their escapes.

Useful? React with 👍 / 👎.

Three P2s from codex.

Class-owner resolution keeps abstract interface methods in the
candidate pool. `invokevirtual A.m` on `abstract class A implements
I {}` -- with a concrete B extends A -- resolves to I.m even when
I.m is abstract; reflection's Method.invoke dispatches virtually to
B.m. The old filter dropped abstracts before the maximally-specific
pass, so lookupMethod threw NoSuchMethodException and every push
that touched such a method died with "not on host". Concrete
defaults still win when both are present: `findInInterfaces`
partitions the candidates and runs the JVMS 5.4.3.3 pass on the
concrete pool when it is non-empty, falls back to the abstract pool
otherwise, and only raises IncompatibleClassChangeError for
concrete conflicts (all-abstract pools cannot conflict -- every
entry dispatches through the receiver). Android, JavaSE and the
test-only ReflectionInterpLinker are aligned.

Aliased arrays no longer collapse across unrelated sibling
descriptors. When the same interpreted `Both[]` (elements
implementing two disjoint host interfaces) landed in one A[] slot
and one B[] slot, the arbitrary A pick was assigned to both slots
and reflection rejected the A[] where a B[] was declared. Per-src
now clusters descriptors by covariance -- Component[] and Button[]
of the same Button[] src still share one Button[] dst -- and
materialises one array per cluster, keying by (src, descriptor).
Unrelated siblings form separate clusters and each slot receives
the array that satisfies its own declared type.
`moreSpecificElement` returns null on unrelated so the caller can
recognise the split rather than silently pick.

packageOf no longer runs Java's backslash-u preprocessing on
Kotlin sources. Kotlin does not preprocess -- an escape lives
inside the string, not the source -- so decoding it before
stripComments would turn a Kotlin annotation like
`@file:Suppress("... package fake")` into a synthetic quote that
swallows the real `package` line and refuses the push. The
overload takes the file name and gates decoding on `.java`; the
old single-arg entry delegates for back-compat. New test verifies
the Kotlin escape stays inert.

Shims regenerate at 843 class + 279 interface, all compile; 5275
tests pass, SpotBugs 0 on core-unittests and ByteCodeTranslator.

Co-Authored-By: Claude Opus 4.7 (1M context) <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: e031a4d69d

ℹ️ 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 +1178 to +1179
f.pushRef(isInterpretedLeaf(comp) || isClassLeaf(comp)
? new Object[count]

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 interpreted array component types

When Foo is interpreted, legal code such as Foo[] values = new Foo[1]; Object[] alias = values; alias[0] = "wrong"; must throw ArrayStoreException, but this allocation erases Foo[] to an untyped Object[], and the AASTORE path consequently accepts the string. Later reads can therefore return a value that is not a Foo and fail at an unrelated invocation; retain the interpreted component descriptor with the array and enforce it on stores rather than using an indistinguishable plain Object[].

Useful? React with 👍 / 👎.

Comment on lines +560 to +564
// back -- the array is the caller's now, and interpreted code
// reading an element converts on the way in.
toHostElements(result, new Vector());
}
return 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 Materialize typed arrays returned to host callbacks

When an interpreted override of a host method returning Component[] returns new MyButton[] where MyButton is interpreted, the result here is still the plain Object[] representation; converting its elements does not make the array assignable to Component[]. The generated shim then casts $r to the declared return type and throws ClassCastException, so return arrays need to be materialized from m's return descriptor at this host boundary just as argument arrays are materialized before host calls.

Useful? React with 👍 / 👎.

Two P2s from codex, plus the CI leg that only the Ant core catches.

Reference-tracked failures no longer depend on ReferenceQueue.
CLDC's `java.lang.ref` ships Reference and WeakReference but no
queue, so the Ant core (compiled under
`-bootclasspath Ports/CLDC11/dist/CLDC11.jar`) failed with three
"cannot find symbol" errors on the field, the constructor and the
argument to IdentityWeakRef -- the whole simulator screenshot leg
went red for lack of a class Maven happens to see. The map is
walked directly now: `IdentityWeakRef` takes only the referent,
and `sweepClearedEntries` iterates `failuresByThrown.keySet()`
under the map's monitor and removes any entry whose weak
`get()` has returned null. Sweep runs on every put -- exceptions
are rare relative to invocations, so the linear scan is cheap and
bounded by the small live set.

Return arrays materialise to their declared type at the
`invoke(InterpMethod, ...)` boundary. An interpreted override of
`Component[] getSelected()` returned the interpreter's plain
`Object[]`; the generated shim's implicit cast to `Component[]`
then failed because `[Ljava.lang.Object;` is not assignable to
`[Lcom.codename1.ui.Component;`. The host boundary now reads the
declared return descriptor and rebuilds through
`materializeTypedArray` when it names a reference-element array
whose leaf class is host-visible. Interpreted-only leaves stay
Object[] -- that is the same known deviation documented for
ArrayStoreException.

The `ArrayStoreException`-known-deviation code comment now points
at that same section of `Device-Runtime.asciidoc` from the
`ANEWARRAY` site so the review thread has an in-source landmark;
the developer-guide contractions and one adverb the doc-quality
gate flagged are rewritten to match the Microsoft style pass, and
"synthesising" is now the American "synthesizing" that
LanguageTool wanted.

5275 tests pass, SpotBugs 0 on core-unittests and BCT; CLDC-only
compile of the interp package under
`-bootclasspath Ports/CLDC11/dist/CLDC11.jar` also passes.

Co-Authored-By: Claude Opus 4.7 (1M context) <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: f67c2452c2

ℹ️ 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 thread Ports/iOSPort/nativeSources/cn1_interp_ios.m Outdated
An interpreted PUTFIELD into a compiled object wrote the reference straight
through the field offset, skipping both barriers the translator emits around
every generated reference setter.

Each omission is its own bug. Without CN1_WRITE_BARRIER a nursery value stored
into a heap object never gets promoted, so the nursery frees an object the host
object still points at. Without CN1_SATB_DELETE the reference being overwritten
is not handed to the collector, so a reference that was in the start-of-cycle
snapshot can be dropped by a thread the mark has already scanned and swept while
still live.

Both are silent at the store: it succeeds, and the damage surfaces a cycle later
in a mark walking a field that no longer points at a live object -- the shape
this collector's hardest bugs have taken before.

Both barriers now run in the same order the generated setter uses. Statics were
already covered: setStaticById dispatches through set_static_*, which pays them.
Reads need neither. Type-checked in both the nursery and no-nursery
configurations of the macros.

Co-Authored-By: Claude Opus 5 (1M context) <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: 9ec35be841

ℹ️ 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 +286 to +287
if (kind == K_OBJECT) {
return *(JAVA_OBJECT*)slot;

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 volatile semantics for iOS host fields

When interpreted code reads or writes a volatile instance field declared by a compiled host class on iOS, this raw pointer access bypasses the generated field accessors' atomic_load_explicit(..., memory_order_acquire) and atomic_store_explicit(..., memory_order_release). Since volatile fields are emitted as _Atomic storage but cn1_field_entry does not record volatility, concurrent host and interpreted threads can observe stale state or enter a C data race; carry the volatile flag in the field metadata and use atomic loads/stores for these slots (while retaining the reference write barriers).

Useful? React with 👍 / 👎.

One P2 from codex: preserve volatile semantics for iOS host fields
accessed from interpreted code.

The translator emits `volatile` instance fields as `_Atomic`
storage and generates host accessors that use
`atomic_load_explicit(..., memory_order_acquire)` and
`atomic_store_explicit(..., memory_order_release)`. Interpreted
GETFIELD/PUTFIELD reach the same slot through the generic
`cn1_reflect_field_for` table and dereferenced the pointer plainly,
so a host thread and an interpreted thread reading the same
volatile could tear the read, reorder it, or miss a happens-before
established by a host write.

`cn1_field_entry` now carries an `isVolatile` char, the translator
populates it from `ByteCodeField.isVolatile()` when it emits the
per-class table, and the two iOS interp accessors --
`getFieldById` and `setFieldById` -- dispatch through
`atomic_load_explicit(memory_order_acquire)` /
`atomic_store_explicit(memory_order_release)` on `_Atomic(T)*` for
each JVM type character when the flag is set, matching the
ordering the generated host accessors already use. The reference
write barriers (`CN1_WRITE_BARRIER`, `CN1_SATB_DELETE`) still fire
either way -- those are GC bookkeeping, not memory ordering.
`<stdatomic.h>` is now included by `cn1_interp_ios.m` for the
atomic intrinsics.

5275 tests pass, SpotBugs 0 on core-unittests and BCT.

Co-Authored-By: Claude Opus 4.7 (1M context) <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: 63547f227e

ℹ️ 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 thread scripts/cn1-push.sh Outdated
Comment on lines +52 to +54
CORE_JAR="$(ls "$REPO_ROOT"/.m2-local/com/codenameone/codenameone-core/*/codenameone-core-*.jar 2>/dev/null | head -1 || true)"
if [ -z "$CORE_JAR" ]; then
CORE_JAR="$(ls "$HOME"/.m2/repository/com/codenameone/codenameone-core/*/codenameone-core-*.jar 2>/dev/null | head -1 || 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 Select the binary core artifact deterministically

When the local Maven repository contains multiple core versions or attached source/Javadoc artifacts, this glob followed by head -1 can select the oldest version or a classified JAR (classified names commonly sort before the binary JAR). The subsequent javac invocation then either misses all Codename One classes or compiles against a stale API, so cn1-push.sh fails or packages against the wrong runtime; filter classifiers and select the intended/current project version explicitly.

Useful? React with 👍 / 👎.

Comment on lines +752 to +754
OutputStream out = Files.newOutputStream(f);
try {
p.store(out, "Codename One device runtime -- shared secrets, one per paired device");

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 Create the pairing-secret file with restrictive permissions

On the first pairing on a POSIX multi-user machine, Files.newOutputStream(f) creates this predictable file using the process umask (commonly resulting in mode 0644), writes the phone-authorizing secret, and only afterward changes it to 0600. Another local user watching ~/.codenameone can read the secret during that window and subsequently authenticate arbitrary pushes to the paired device; create the file or a replacement temporary file with owner-only permissions before writing any secret bytes.

Useful? React with 👍 / 👎.

Two P2s from codex, plus a PMD gate the last round failed.

The pairing secret file is now created owner-only from the start,
not chmodded to 0600 after the write. Files.newOutputStream(f)
opened the file with the process umask (0644 on most POSIX
defaults) and only later called setPosixFilePermissions; another
local user on the machine could read the phone-authorising secret
during that window and subsequently authenticate arbitrary pushes.
Write goes to a sibling temp file created with a FileAttribute
carrying "rw-------" (Windows falls back to the plain temp file,
whose default ACL is already user-only), and an ATOMIC_MOVE with
REPLACE_EXISTING renames it into place -- so the target ends up
owning the temp's mode and an existing file with laxer permissions
is replaced rather than reopened. AtomicMoveNotSupportedException
falls back to a plain move.

cn1-push.sh picks the intended core / parparvm jar rather than
whichever entry `ls | head -1` happens to land on. A local repo
that also has -sources / -javadoc / -bundle classifiers (they sort
before the plain jar) or older versions alongside the intended one
would previously feed javac an attached artifact or a stale API,
and the push either failed or packaged against the wrong runtime.
`pick_latest_artifact` walks the per-version directory in
`sort -V -r` order and returns the first `<prefix>-<v>.jar` that
exists, so classifiers are ignored by construction and the newest
version wins.

The Ant PMD gate flagged `hostThrown instanceof InterpCancelled`
inside a `catch (Throwable)` in `run()`. That arm was dead --
`InterpCancelled`'s constructor is package-private and every
creation site wraps it in `InterpThrowable`, which the catch above
handles -- so the check is deleted, with a comment recording why
no cancelCaughtOnce update is needed there.

5275 tests pass, SpotBugs 0 on core-unittests and BCT.

Co-Authored-By: Claude Opus 4.7 (1M context) <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: 679b765cc9

ℹ️ 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 +314 to +315
bits = (uint64_t)atomic_load_explicit(
(_Atomic(uint64_t)*)slot, memory_order_acquire);

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 Access volatile floats through their declared atomic types

When interpreted code accesses a compiled volatile float or volatile double field on iOS, the generated storage is _Atomic(JAVA_FLOAT) or _Atomic(JAVA_DOUBLE), but this code accesses it through _Atomic(uint32_t)* or _Atomic(uint64_t)*. Those are incompatible atomic object types under C's aliasing rules, so the load and matching store have undefined behavior and may be miscompiled despite having the correct size. Atomically load/store the declared floating type and use memcpy only between the resulting local value and its raw bits.

Useful? React with 👍 / 👎.

Comment on lines +2682 to +2685
if (merged != null) {
clusterReps.setElementAt(merged, c);
mergedInto = c;
break;

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 Coalesce covariance clusters connected by later descriptors

When the same pushed MyBoth[] is passed to a host signature ordered as f(A[], B[], HostBoth[]), where HostBoth implements the unrelated interfaces A and B, processing first creates separate A and B clusters and then merges HostBoth into only the first cluster because of this break. All three arguments subsequently select the HostBoth[] destination, but the unused B[] destination remains in hostArrayPairs and is mirrored back after the call, overwriting mutations made through the array the host actually received. Fresh evidence after the sibling-interface fix is that a later descriptor can bridge previously separate clusters; merge all now-compatible representatives or omit unused materializations from mirroring.

Useful? React with 👍 / 👎.

Comment on lines +4141 to +4143
Failure prev = st.lastFailure;
boolean rethrow = prev != null //NOPMD CompareObjectsWithEquals - identity is the point
&& (prev.thrown == t || prev.original == 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 Preserve rethrow metadata beyond the most recent failure

When a catch block handles another exception before rethrowing its original exception—for example, catch (Exception e) { try { riskyLog(); } catch (Exception ignored) {} throw e; }—the nested failure replaces lastFailure, so this identity check no longer recognizes e. The rethrow then records the current site instead of preserving the original interpreted stack. Fresh evidence after making this slot thread-local is that multiple failures on the same thread still overwrite one another; consult the existing identity-keyed failure registry rather than only the most recent slot.

Useful? React with 👍 / 👎.

Three P2s from codex.

Volatile float / double on iOS now round-trip through their
declared atomic types. Round 101 dispatched through
`_Atomic(uint32_t)*` / `_Atomic(uint64_t)*` for `F` / `D` fields,
but the storage the translator declares is `_Atomic(JAVA_FLOAT)`
/ `_Atomic(JAVA_DOUBLE)` -- an incompatible atomic object type
under C aliasing, so clang is free to miscompile the pair even
though the widths match. The load / store now use the declared
floating type; `memcpy` moves between the local value and the raw
bits the interpreter's long slot uses.

Sibling-interface array clusters coalesce transitively. Round 99
formed one cluster per unrelated descriptor and merged each new
candidate into the first cluster it met, but a later descriptor
that bridges two previously separate clusters -- `HostBoth[]`
after `A[]` and `B[]`, where `HostBoth` implements both -- left
the other cluster stranded. The stranded cluster still
materialised an array that never reached the host, and the finally
mirror wrote its contents back over the mutations the host had
made through the array it did receive. After the initial pass a
fixed-point loop coalesces any two cluster reps that
`moreSpecificElement` can merge, so every pair that shares a
common subtype ends up in a single cluster.

Rethrow detection consults the identity-keyed failure registry as
well as `lastFailure`. `catch (Exception e) { try { risky(); }
catch (Exception ignored) {} throw e; }` overwrote the slot with
`ignored` between the outer catch and the rethrow, and the
identity match against the slot alone then missed `e`, replacing
its recorded interpreted stack with a snapshot of the rethrow
site. `toThrowable` now falls back to `getFailureInfo(t)` on the
Throwable path: if the registry still holds an entry for `t`, the
recorded stack lives on and the wrapper (for the InterpObject
path) reuses it too.

5275 tests pass, SpotBugs 0 on core-unittests.

Co-Authored-By: Claude Opus 4.7 (1M context) <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: 47feded9ee

ℹ️ 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".

boolean rethrow = prev != null //NOPMD CompareObjectsWithEquals - identity is the point
&& (prev.thrown == t || prev.original == t);
String[] rethrowStack = rethrow ? prev.stack : null;
if (!rethrow && t instanceof Throwable) {

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 Retain rethrow stacks for interpreted exception objects

When the outer exception is declared in pushed code, the caught value is an InterpObject, not a Throwable, so this registry fallback is skipped. If its catch block handles another failure before throw e, that failure overwrites lastFailure; the rethrow then receives a new stack rooted at the rethrow site instead of the original throw site. Fresh evidence beyond the existing rethrow finding is that the new registry lookup is explicitly limited to host Throwable instances and recordFailure() cannot index the interpreted original. Track interpreted exception identities as well as host throwables.

Useful? React with 👍 / 👎.

One P2 from codex: the round-103 rethrow-registry fallback only
covered Throwable exceptions; an interpreted-only exception's
caught value is an InterpObject, so the registry lookup was
skipped and the outer rethrow after a nested catch still lost the
original stack.

`putFailureInfo` / `getFailureInfo` now take `Object` and the
`IdentityWeakRef` weak key accepts any Object identity.
`recordFailure` registers the `original` -- Throwable or
InterpObject -- alongside `thrown`, so a rethrow of an interpreted
exception whose `lastFailure` slot was overwritten by a nested
`catch (Exception ignored) {}` still resolves through the
registry to its recorded interpreted stack. Widening the key type
does not change what `interpretedStackFor(Throwable)` looks up --
its argument is still a Throwable -- but it does let the
InterpObject path in `toThrowable` reach the same registry.

5275 tests pass, SpotBugs 0 on core-unittests.

Co-Authored-By: Claude Opus 4.7 (1M context) <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: be1c8098e0

ℹ️ 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 +1054 to +1056
if (!loopback) {
reject = "this app requires a paired computer; upgrade the push tool";
} else {

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 Authenticate loopback push connections

When another Android app binds 127.0.0.1:18234 before this runtime starts, startListener() merely reports that the port is unavailable and the dialer connects to that app, but this branch then accepts its v1 bundle without pairing or approval because the configured address is loopback. Android apps share the device's TCP loopback namespace, so a local app that wins this predictable port can make the runtime execute an arbitrary bundle; require the authenticated protocol even on loopback or establish that the peer is specifically the USB tunnel rather than treating the address itself as authentication.

Useful? React with 👍 / 👎.

Comment on lines +579 to +581
Object[] typed = materializeTypedArray((Object[]) result, elem, null);
if (typed != null) {
return typed;

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 retained arrays when returning them to host code

Fresh evidence after the return-type materialization fix is that it returns a new array with no ongoing association to the interpreted source. If an override retains Component[] items = new PushedButton[...] in a field and returns it, the host receives this typed copy; a later host mutation changes only the copy, while interpreted code continues reading the original Object[], and later interpreted mutations are likewise invisible to the host. Preserve shared array identity or synchronize the retained representations beyond this one-time conversion.

Useful? React with 👍 / 👎.

// its budget expires. Nested (non-fresh) entries do NOT reset
// -- they participate in the same cancellation unwind as the
// enclosing entry.
st.cancelCaughtOnce = 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 Restore cancellation state after reentrant callbacks

Fresh evidence after resetting cancellation state per interpreter entry is that a fresh entry can also be a reentrant callback nested inside an outer entry's host call. If the outer cancellation has already traversed a typed handler, a short callback dispatched by that handler resets cancelCaughtOnce here and the enclosing state is never restored in the finally; the outer retry loop can consequently catch the same pending cancellation through catch (Throwable) again, allowing Stop or the EDT budget to be swallowed repeatedly. Save and restore this flag alongside the enclosing clock, fuel, and host-call depth.

Useful? React with 👍 / 👎.

One P1 and two P2s from codex.

Loopback v1 pushes are no longer accepted on a real device. Codex
noticed that Android apps share the TCP loopback namespace: another
local app can connect to this listener over 127.0.0.1 and would
otherwise get an unpaired v1 bundle run because `loopback == true`
was treated as authentication. The physical-presence argument only
holds inside the JavaSE simulator, where "loopback" really does
mean this same process's peer; on device (Android in particular),
require the paired v3 protocol. The v1 branch now gates on
`Display.getInstance().isSimulator()` as well as `loopback`, and
the comment on the enclosing block records why.

Cancellation-in-flight state is now saved and restored across a
reentrant fresh entry. Round 98 reset `cancelCaughtOnce` on every
fresh entry so a stale flag from a prior EDT callback couldn't
skip a later callback's typed cleanup; but a fresh entry can also
be a reentrant callback dispatched inside an outer entry's host
call. Resetting there and never restoring it let the outer retry
loop's `catch (Throwable)` swallow the same pending cancellation
each pass. `enclosingCancelCaughtOnce` is stashed alongside the
enclosing clock / fuel / hostCallDepth before the reset and put
back in the `finally` (and in the early-throw for a max-depth
overflow), so the outer state is preserved through the callback.

Returned typed arrays being one-time copies is now a documented
known deviation. Codex followed the round-100 fix with a
scenario -- an override retains `Component[] items = new
PushedButton[...]` in a field and hands the same reference back --
where a later host mutation lives in the returned copy alone and a
later interpreted write into the source stays invisible on the
host side. Closing it means carrying an alias registry through
every array opcode and every host boundary for a rare pattern
against the host contract (these accessors are expected to hand
back a snapshot, not an internal buffer). Recorded in
`Device-Runtime.asciidoc` alongside the ArrayStoreException and
`Object.clone()` deviations; the materialisation site now points
at that section.

5275 tests pass, SpotBugs 0 on core-unittests.

Co-Authored-By: Claude Opus 4.7 (1M context) <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: c9d23a7350

ℹ️ 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 +594 to +595
if (self == null || self.indexOf('.') < 0 || isLoopback(self)) {
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 Support IPv6-only discovery instead of rejecting it

On an IPv6-only Wi-Fi network, AndroidImplementation.getHostOrIP() deliberately falls back to a routable IPv6 address, but this condition rejects every address without a dot before attempting discovery. The device runtime then remains on the loopback default and cannot find the desktop, while getLocalAddress() similarly tells the UI that the device has no network address. Add an IPv6 discovery path or another way to dial the desktop rather than unconditionally refusing the only reachable address.

Useful? React with 👍 / 👎.

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.

2 participants