Skip to content

Fix the iOS on-device debugger crashing at breakpoints (issue #5333) - #5536

Merged
shai-almog merged 37 commits into
masterfrom
fix/ios-debugger-5333
Aug 8, 2026
Merged

Fix the iOS on-device debugger crashing at breakpoints (issue #5333)#5536
shai-almog merged 37 commits into
masterfrom
fix/ios-debugger-5333

Conversation

@shai-almog

Copy link
Copy Markdown
Collaborator

Closes the outstanding items on #5333.

The crash

A breakpoint in an event listener took the app down with We had a signal 11. Three independent wild-pointer paths converged on handleGetLocals, which is what NetBeans calls the moment a breakpoint hits.

1. The locals side-table described the wrong storage. It listed one row per declared local but only one address per JVM slot, picked last-writer-wins out of a HashSet. A slot reused by an int and a reference in disjoint scopes — routine in any listener — therefore had a reference row addressing a four-byte JAVA_INT:

JAVA_OBJECT obj = *(JAVA_OBJECT*)addrs[v->slot];   // 8 bytes off a 4-byte int
if (... obj->__codenameOneParentClsReference == &class__java_lang_String)  // SIGSEGV

There is now one address per row, so a row's type code and the storage it points at cannot disagree. Rows are sorted rather than hash-ordered, so the emitted table also stops varying between builds of the same input — which is why this reproduced on some breakpoints and not others.

2. Frames inherited a dangling locals pointer. callStackFrameInfo / callStackLocalsAddresses were written only by methods carrying a side-table, and never cleared. A native, eliminated or barebone frame inherited whatever the previous occupant of that call depth left — including an address array pointing into a C frame that had already returned. CN1_DEBUG_FRAME_ENTER clears both before every push and compiles away in release builds.

3. Nothing validated a reference before dereferencing it. Every reference the debugger handles is untrusted: the IDE echoes back objectIDs it was handed earlier, and a local slot holds whatever the branch that never ran left there. cn1_debugger_objects.c resolves a candidate through an exact classId -> clazz registry, reading it via vm_read_overwrite so an unmapped page returns an error instead of a signal. Every deref site goes through it — locals, object class/fields/string, invoke receiver and args, array length/values, and references handed back to the IDE.

The rest of the report

  • Threads. CMD_GET_THREADS was a device stub replying empty and the proxy never sent it, so AllThreads could only report threads that had already hit a breakpoint. It now enumerates ParparVM's thread registry. Suspension is tracked per thread rather than by a single "last suspended" field, and Thread.Name resolves the real name off the java.lang.Thread instance, so the panel shows EDT and main.
  • Deferred breakpoints. ClassPrepare (event kind 8) was accepted and never fired, so IntelliJ and NetBeans left every deferred breakpoint unarmed — the "breakpoint in a listener never stops" half of the report. Matching classes are replayed at registration, which is complete for ParparVM since the whole class set is linked in before a debugger can attach.
  • Scoped locals. Locals carry their declaring scope, so one is listed only on the lines it is live for. This also fixes a -g local being shown as vN: the placeholder a store opcode synthesises no longer keeps the class file's own entry out of the set that holds them.
  • Capabilities. CapabilitiesNew reports canRequestVMDeathEvent, which the proxy does support. The rest stay false rather than having the IDE offer redefinition or frame popping and then fail.
  • Diagnostics. Breakpoint arming logs the resolved Class.method:line, and warns when a request maps to no method or to a line outside the method's line table. It used to be accepted in silence, which is why this thread ran as long as it did.
  • IDE configuration. NetBeans gets Build for iOS On-Device Debug and Start iOS Debug Proxy project actions — the configuration the reporter had to build by hand. IntelliJ was missing the build half and gains a run config for it. The build goal forces the onDeviceDebug hint itself, so the properties-file editing that started this issue is not needed.

Tests

66 tests, in the places the behaviour actually lives:

OnDeviceDebugFrameTableTest (10) the emitted C: one address per row, each addressing its own slot and kind, disjoint scopes, determinism
DebugFramePushCoverageTest (3) every frame push clears the debug side-channel first
DebuggerObjectValidationTest (8) compiles and runs the shipped C validation policy
DebuggerLocalScopeTest (5) compiles and runs the shipped scope predicate
JdwpThreadListTest (6) / DeviceConnectionThreadListTest (5) thread enumeration and its wire decode
JdwpClassPrepareTest (7) pattern matching, replay, suspend policy
JdwpVariableScopeTest (6) live ranges over JDWP, and the capabilities reply
Cn1AppArchetypeCertificateWizardTest (+1) the IDE bindings are actually shipped

Each guard was checked against the pre-fix code rather than assumed. Reverting the per-row table fails four frame-table tests with exactly the per-slot symptom; removing one CN1_DEBUG_FRAME_ENTER names the file and line; reverting the validation to a raw deref fails five tests, four of them with exit 139 — the reported signal 11, reproduced and then prevented.

cn1_debugger_objects.c is plain C rather than part of the Objective-C file precisely so these rules can be compiled and run on a host. The tests are macOS-only, since vm_read_overwrite is Darwin-only and iOS debugging is a macOS activity regardless.

Verification limits

cn1_debugger.m and cn1_debugger_objects.c type-check against the real iOS SDK (xcrun --sdk iphonesimulator clang -fsyntax-only, only three pre-existing deprecation warnings), the frame macro compiles in both build modes, and SpotBugs is at zero on ByteCodeTranslator.

This has not been through an on-device debugging session. The device-side changes are verified by type-check and by the extracted policy unit under test, not by running against a tethered iPhone — worth doing before telling the reporter it's fixed.

Two things are deliberately left, and documented rather than papered over: two locals of the same type sharing a slot still collapse to one entry (reuse across different types, the case that caused the crash, is fully distinguished), and a local compiled without -g has no scope so it is listed for the whole method.

🤖 Generated with Claude Code

A breakpoint in an event listener took the app down with "We had a
signal 11". Three independent wild-pointer paths converged on
handleGetLocals, which is what NetBeans calls the moment a breakpoint
hits:

- The locals side-table listed one row per declared local but only one
  address per JVM *slot*, picked last-writer-wins out of a HashSet. A
  slot reused by an int and a reference in disjoint scopes -- routine in
  any listener -- therefore had a reference row addressing a four-byte
  JAVA_INT. The runtime read eight bytes off it and dereferenced the
  result. There is now one address per row, so a row's type code and the
  storage it points at cannot disagree, and the rows are sorted rather
  than hash-ordered so the emitted table stops varying between builds.

- callStackFrameInfo / callStackLocalsAddresses were written only by
  methods carrying a side-table, and never cleared. A native, eliminated
  or barebone frame inherited the previous occupant of that call depth,
  including a locals-address array pointing into a C frame that had
  already returned. CN1_DEBUG_FRAME_ENTER clears both before every push
  and compiles away in release builds.

- Nothing validated a reference before dereferencing it, though every
  one is untrusted: the IDE echoes back objectIDs it was handed earlier,
  and a local slot holds whatever the branch that never ran left there.
  cn1_debugger_objects.c resolves a candidate through an exact
  classId -> clazz registry, reading it via vm_read_overwrite so an
  unmapped page returns an error instead of a signal. Every deref site
  goes through it.

Also addresses the rest of what the issue reported:

- Threads. CMD_GET_THREADS was a device stub replying empty and the
  proxy never sent it, so AllThreads could only report threads that had
  already hit a breakpoint. It now enumerates ParparVM's thread
  registry; suspension is tracked per thread rather than by a single
  "last suspended" field, and Thread.Name resolves the real name off the
  java.lang.Thread instance.

- ClassPrepare (event kind 8) was accepted and never fired, so IntelliJ
  and NetBeans left every deferred breakpoint unarmed -- the "breakpoint
  in a listener never stops" half of the report. Matching classes are
  now replayed at registration, which is complete for ParparVM since the
  whole class set is linked in before a debugger can attach.

- Locals carry their declaring scope, so a local is listed only on the
  lines it is live for. This also fixes a -g local being shown as "vN":
  the placeholder a store opcode synthesises no longer keeps the class
  file's own entry out of the set that holds them.

- CapabilitiesNew reports canRequestVMDeathEvent, which the proxy does
  support. The rest stay false rather than having the IDE offer
  redefinition or frame popping and then fail.

- Breakpoint arming logs the resolved Class.method:line, and warns when
  a request maps to no method or to a line outside the method's line
  table. It used to be accepted in silence.

- NetBeans gets "Build for iOS On-Device Debug" and "Start iOS Debug
  Proxy" project actions, the configuration the reporter had to build by
  hand; IntelliJ was missing the build half and gains a run config for
  it. The build goal forces the onDeviceDebug hint itself, so the
  properties-file editing that started this issue is not needed.

Tested by 66 tests. The translator's table and the frame-push coverage
are asserted on emitted C; the runtime's validation and scope rules are
compiled and run on the host through cn1_debugger_objects.c, which is
plain C for that reason. Each guard was checked against the pre-fix code
rather than assumed -- reverting the validation makes four of them exit
139, which is the reported signal 11 reproduced.

Not verified on a device: the native changes are checked by type-check
against the iOS SDK and by the extracted policy unit under test, not by
an on-device session.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@shai-almog shai-almog linked an issue Aug 7, 2026 that may be closed by this pull request
Both carried a truncated header that predates the copyright gate, so
modifying them tripped it.

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: 3fe98d8fbc

ℹ️ 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/BytecodeMethod.java Outdated
@github-actions

github-actions Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

✅ Continuous Quality Report

Test & Coverage

Static Analysis

  • SpotBugs [Report archive]
    • ByteCodeTranslator: 0 findings (no issues)
    • android: 0 findings (no issues)
    • codenameone-maven-plugin: 0 findings (no issues)
    • core-unittests: 0 findings (no issues)
    • ios: 0 findings (no issues)
  • PMD: 0 findings (no issues) [Report archive]
  • Checkstyle: 0 findings (no issues) [Report archive]

Generated automatically by the PR CI workflow.

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

ℹ️ 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_debugger_objects.c
Comment thread maven/cn1-debug-proxy/src/main/java/com/codename1/debug/proxy/JdwpServer.java Outdated
@github-actions

github-actions Bot commented Aug 7, 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: No alerts found (report)
  • Paragraph capitalization: No paragraph capitalization issues (report)
  • LanguageTool: No grammar matches (report)
  • Image references: No unused images detected (report)

shai-almog and others added 3 commits August 8, 2026 06:42
The frame side-table is emitted during code generation and the symbol
table the IDE reads is written after every class has been generated,
with optimize() in between -- and a scope is resolved by walking the
instruction list that pass rewrites. It preserves the label and
line-number entries the walk depends on, so the two agree either way
today; nothing states that, and them disagreeing would not fail loudly,
it would just drop locals from the IDE's variables view.

Snapshotting in computeRawMethodPlans, next to the other plans that must
be read off the raw list, removes the dependence and costs one walk per
method rather than one per method per consumer.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Vale flagged five contractions and a wordiness in the sections this PR
added, and LanguageTool the same 'whether or not'.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
handleGetLocals and handleGetStack both built their reply into a
malloc'd buffer without checking it, while holding the suspended
thread's mutex. A null there would crash the app, which is the failure
mode this whole change is about, and the other handlers in the file
already check. Both now reply empty and unlock.

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

github-actions Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Cloudflare Preview

@github-actions

github-actions Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

✅ ByteCodeTranslator Quality Report

Test & Coverage

  • Tests: 492 total, 0 failed, 54 skipped

Benchmark Results

  • Execution Time: 25725 ms

  • Hotspots (Top 20 sampled methods):

    • 18.40% com.codename1.tools.translator.Parser.addToConstantPool (390 samples)
    • 9.67% java.util.ArrayList.indexOf (205 samples)
    • 3.68% com.codename1.tools.translator.ByteCodeClass.hasDeclaredMethod (78 samples)
    • 3.44% java.lang.StringBuilder.append (73 samples)
    • 3.21% com.codename1.tools.translator.BytecodeMethod.optimize (68 samples)
    • 3.11% com.codename1.tools.translator.Parser.cn1EnsureSubclassIndex (66 samples)
    • 2.74% com.codename1.tools.translator.Parser.classIndex (58 samples)
    • 2.50% com.codename1.tools.translator.ByteCodeClass.fillVirtualMethodTable (53 samples)
    • 2.03% org.objectweb.asm.tree.analysis.Analyzer.analyze (43 samples)
    • 1.60% com.codename1.tools.translator.bytecodes.Invoke.resolveDirectTarget (34 samples)
    • 1.37% com.codename1.tools.translator.BytecodeMethod.equals (29 samples)
    • 1.23% java.lang.System.identityHashCode (26 samples)
    • 1.23% org.objectweb.asm.tree.analysis.Analyzer.findSubroutine (26 samples)
    • 1.18% java.lang.Object.hashCode (25 samples)
    • 1.18% com.codename1.tools.translator.BytecodeMethod.appendCMethodPrefix (25 samples)
    • 1.13% java.util.HashMap.hash (24 samples)
    • 1.13% org.objectweb.asm.ClassReader.readCode (24 samples)
    • 1.04% java.lang.String.equals (22 samples)
    • 1.04% com.codename1.tools.translator.bytecodes.Invoke.findMethodUp (22 samples)
    • 0.85% java.lang.StringCoding.encode (18 samples)
  • ⚠️ Coverage report not generated.

Static Analysis

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

Generated automatically by the PR CI workflow.

shai-almog and others added 2 commits August 8, 2026 07:00
Tagged ints (P2, and the one that mattered). Integer.valueOf() returns
(v << 1) | 1 on every 64-bit target, which is every shipping iOS build.
Those references are deliberately odd, so the alignment guard in front of
the header read discarded all of them -- boxed Integers in locals,
fields, object arrays, invocation arguments and receivers would all have
read as null. They are now recognised before any read is attempted, and
report Integer's class; CMD_GET_OBJECT_FIELDS serves the value from the
tag rather than computing a field address from a value.

End-of-method locals (P2). javac closes a method-wide scope with a label
that no line number follows. Mapping it to the last line seen made the
exclusive end land on that line, hiding "this" and every parameter at
exactly the breakpoint most likely to be set -- the return or the closing
brace. Those labels now produce an open-ended scope.

Stale suspend state (P2). The thread snapshot is only as fresh as the
last refresh, and a thread is normally enumerated while running and
stopped a moment later. Status and SuspendCount therefore kept reporting
the thread the IDE had just stopped at as running. Suspend and resume
events now update the snapshot.

Phantom dead threads (P2). Event-derived ids accumulate for the session,
and unioning them into every answer kept a row in the IDE for every
worker that had ever hit a breakpoint. They are now a fallback for a
device that cannot answer, not an addition to one that can, and an
authoritative list prunes them. That distinction also corrected a test of
mine that modelled an empty list as "cannot enumerate" when it actually
means "nothing to show".

Each is covered: the tagged-int paths and the misalignment case they
reinterpret in DebuggerObjectValidationTest, the scope in
OnDeviceDebugFrameTableTest (reproducing the pre-fix behaviour fails it),
and both thread behaviours in JdwpThreadListTest.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
CN1_UNTAG_INT only exists when CN1_TAGGED_ACTIVE is set, so the value
accessor broke -DCN1_DISABLE_TAGGED_INT -- a documented opt-out -- and
any 32-bit target. Guarded; both configurations compile.

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: 40bdfb4ee0

ℹ️ 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_debugger.m Outdated
Comment thread maven/cn1-debug-proxy/src/main/java/com/codename1/debug/proxy/JdwpServer.java Outdated
@shai-almog

shai-almog commented Aug 8, 2026

Copy link
Copy Markdown
Collaborator Author

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

Benchmark Results

Detailed Performance Metrics

Metric Duration
SIMD kernel backend SSE2 (x64) / NEON (arm64) native kernels
SIMD int-add (64K x300) java 63ms / native 5ms = 12.6x speedup
SIMD float-mul (64K x300) java 62ms / native 5ms = 12.4x speedup
SIMD kernel correctness PASS (native result == scalar reference)
Base64 native bridge unavailable (CN1 + SIMD + image benchmarks only)
Base64 payload size 8192 bytes
Base64 benchmark iterations 6000
Base64 SIMD byte path gated to scalar (CPU autovectorizes scalar; explicit SIMD not beneficial here)
Base64 CN1 encode 220.000 ms
Base64 CN1 decode 138.000 ms
Base64 SIMD encode 101.000 ms
Base64 encode ratio (SIMD/CN1) 0.459x (54.1% faster)
Base64 SIMD decode 102.000 ms
Base64 decode ratio (SIMD/CN1) 0.739x (26.1% faster)
Image encode benchmark iterations 100
Image createMask (SIMD off) 23.000 ms
Image createMask (SIMD on) 19.000 ms
Image createMask ratio (SIMD on/off) 0.826x (17.4% faster)
Image applyMask (SIMD off) 56.000 ms
Image applyMask (SIMD on) 173.000 ms
Image applyMask ratio (SIMD on/off) 3.089x (208.9% slower)
Image modifyAlpha (SIMD off) 58.000 ms
Image modifyAlpha (SIMD on) 34.000 ms
Image modifyAlpha ratio (SIMD on/off) 0.586x (41.4% faster)
Image modifyAlpha removeColor (SIMD off) 49.000 ms
Image modifyAlpha removeColor (SIMD on) 29.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off) 0.592x (40.8% faster)

@shai-almog

shai-almog commented Aug 8, 2026

Copy link
Copy Markdown
Collaborator Author

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

Benchmark Results

Detailed Performance Metrics

Metric Duration
SIMD kernel backend SSE2 (x64) / NEON (arm64) native kernels
SIMD int-add (64K x300) java 54ms / native 4ms = 13.5x speedup
SIMD float-mul (64K x300) java 49ms / native 4ms = 12.2x speedup
SIMD kernel correctness PASS (native result == scalar reference)
Base64 native bridge unavailable (CN1 + SIMD + image benchmarks only)
Base64 payload size 8192 bytes
Base64 benchmark iterations 6000
Base64 SIMD byte path gated to scalar (CPU autovectorizes scalar; explicit SIMD not beneficial here)
Base64 CN1 encode 152.000 ms
Base64 CN1 decode 102.000 ms
Base64 SIMD encode 81.000 ms
Base64 encode ratio (SIMD/CN1) 0.533x (46.7% faster)
Base64 SIMD decode 88.000 ms
Base64 decode ratio (SIMD/CN1) 0.863x (13.7% faster)
Image encode benchmark iterations 100
Image createMask (SIMD off) 23.000 ms
Image createMask (SIMD on) 17.000 ms
Image createMask ratio (SIMD on/off) 0.739x (26.1% faster)
Image applyMask (SIMD off) 43.000 ms
Image applyMask (SIMD on) 164.000 ms
Image applyMask ratio (SIMD on/off) 3.814x (281.4% slower)
Image modifyAlpha (SIMD off) 40.000 ms
Image modifyAlpha (SIMD on) 29.000 ms
Image modifyAlpha ratio (SIMD on/off) 0.725x (27.5% faster)
Image modifyAlpha removeColor (SIMD off) 43.000 ms
Image modifyAlpha removeColor (SIMD on) 29.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off) 0.674x (32.6% faster)

@shai-almog

shai-almog commented Aug 8, 2026

Copy link
Copy Markdown
Collaborator Author

Compared 151 screenshots: 151 matched.

Native Android coverage

  • 📊 Line coverage: 8.09% (7868/97198 lines covered) [HTML preview] (artifact android-coverage-report, jacocoAndroidReport/html/index.html)
    • Other counters: instruction 8.07% (41634/515700), branch 2.88% (1402/48723), complexity 3.18% (1663/52272), method 4.90% (1355/27642), class 9.97% (367/3680)
    • Lowest covered classes
      • kotlin.collections.kotlin.collections.ArraysKt___ArraysKt – 0.00% (0/6367 lines covered)
      • kotlin.collections.unsigned.kotlin.collections.unsigned.UArraysKt___UArraysKt – 0.00% (0/2384 lines covered)
      • org.jacoco.agent.rt.internal_0e20598.asm.org.jacoco.agent.rt.internal_0e20598.asm.ClassReader – 0.00% (0/1524 lines covered)
      • kotlin.collections.kotlin.collections.CollectionsKt___CollectionsKt – 0.00% (0/1187 lines covered)
      • org.jacoco.agent.rt.internal_0e20598.asm.org.jacoco.agent.rt.internal_0e20598.asm.MethodWriter – 0.00% (0/922 lines covered)
      • kotlin.sequences.kotlin.sequences.SequencesKt___SequencesKt – 0.00% (0/736 lines covered)
      • com.google.common.cache.com.google.common.cache.LocalCache$Segment – 0.00% (0/726 lines covered)
      • okio.okio.Buffer – 0.00% (0/687 lines covered)
      • kotlin.text.kotlin.text.StringsKt___StringsKt – 0.00% (0/625 lines covered)
      • org.jacoco.agent.rt.internal_0e20598.asm.org.jacoco.agent.rt.internal_0e20598.asm.Frame – 0.00% (0/570 lines covered)

✅ Native Android screenshot tests passed.

Native Android coverage

  • 📊 Line coverage: 8.09% (7868/97198 lines covered) [HTML preview] (artifact android-coverage-report, jacocoAndroidReport/html/index.html)
    • Other counters: instruction 8.07% (41634/515700), branch 2.88% (1402/48723), complexity 3.18% (1663/52272), method 4.90% (1355/27642), class 9.97% (367/3680)
    • Lowest covered classes
      • kotlin.collections.kotlin.collections.ArraysKt___ArraysKt – 0.00% (0/6367 lines covered)
      • kotlin.collections.unsigned.kotlin.collections.unsigned.UArraysKt___UArraysKt – 0.00% (0/2384 lines covered)
      • org.jacoco.agent.rt.internal_0e20598.asm.org.jacoco.agent.rt.internal_0e20598.asm.ClassReader – 0.00% (0/1524 lines covered)
      • kotlin.collections.kotlin.collections.CollectionsKt___CollectionsKt – 0.00% (0/1187 lines covered)
      • org.jacoco.agent.rt.internal_0e20598.asm.org.jacoco.agent.rt.internal_0e20598.asm.MethodWriter – 0.00% (0/922 lines covered)
      • kotlin.sequences.kotlin.sequences.SequencesKt___SequencesKt – 0.00% (0/736 lines covered)
      • com.google.common.cache.com.google.common.cache.LocalCache$Segment – 0.00% (0/726 lines covered)
      • okio.okio.Buffer – 0.00% (0/687 lines covered)
      • kotlin.text.kotlin.text.StringsKt___StringsKt – 0.00% (0/625 lines covered)
      • org.jacoco.agent.rt.internal_0e20598.asm.org.jacoco.agent.rt.internal_0e20598.asm.Frame – 0.00% (0/570 lines covered)

Benchmark Results

Detailed Performance Metrics

Metric Duration
SIMD kernel backend scalar fallback (no native SIMD)
SIMD int-add (64K x300) java 73ms / native 135ms = 0.5x speedup
SIMD float-mul (64K x300) java 56ms / native 61ms = 0.9x speedup
SIMD kernel correctness PASS (native result == scalar reference)
Base64 payload size 8192 bytes
Base64 benchmark iterations 6000
Base64 SIMD byte path gated to scalar (CPU autovectorizes scalar; explicit SIMD not beneficial here)
Base64 CN1 encode 52.000 ms
Base64 CN1 decode 54.000 ms
Base64 native encode 331.000 ms
Base64 encode ratio (CN1/native) 0.157x (84.3% faster)
Base64 native decode 242.000 ms
Base64 decode ratio (CN1/native) 0.223x (77.7% faster)
Image encode benchmark status skipped (SIMD unsupported)

@shai-almog

shai-almog commented Aug 8, 2026

Copy link
Copy Markdown
Collaborator Author

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

Benchmark Results

Detailed Performance Metrics

Metric Duration
SIMD kernel backend SSE2 (x64) / NEON (arm64) native kernels
SIMD int-add (64K x300) java 53ms / native 3ms = 17.6x speedup
SIMD float-mul (64K x300) java 54ms / native 3ms = 18.0x speedup
SIMD kernel correctness PASS (native result == scalar reference)
Base64 native bridge unavailable (CN1 + SIMD + image benchmarks only)
Base64 payload size 8192 bytes
Base64 benchmark iterations 6000
Base64 SIMD byte path gated to scalar (CPU autovectorizes scalar; explicit SIMD not beneficial here)
Base64 CN1 encode 246.000 ms
Base64 CN1 decode 128.000 ms
Base64 SIMD encode 64.000 ms
Base64 encode ratio (SIMD/CN1) 0.260x (74.0% faster)
Base64 SIMD decode 63.000 ms
Base64 decode ratio (SIMD/CN1) 0.492x (50.8% faster)
Image encode benchmark iterations 100
Image createMask (SIMD off) 12.000 ms
Image createMask (SIMD on) 7.000 ms
Image createMask ratio (SIMD on/off) 0.583x (41.7% faster)
Image applyMask (SIMD off) 23.000 ms
Image applyMask (SIMD on) 18.000 ms
Image applyMask ratio (SIMD on/off) 0.783x (21.7% faster)
Image modifyAlpha (SIMD off) 15.000 ms
Image modifyAlpha (SIMD on) 10.000 ms
Image modifyAlpha ratio (SIMD on/off) 0.667x (33.3% faster)
Image modifyAlpha removeColor (SIMD off) 21.000 ms
Image modifyAlpha removeColor (SIMD on) 11.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off) 0.524x (47.6% faster)

@shai-almog

shai-almog commented Aug 8, 2026

Copy link
Copy Markdown
Collaborator Author

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

@shai-almog

shai-almog commented Aug 8, 2026

Copy link
Copy Markdown
Collaborator Author

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

shai-almog and others added 2 commits August 8, 2026 07:36
Both said On-Device-Debugging.adoc; the file is .asciidoc.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Same pre-gate truncation as the two debugger files.

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: 06ac1bd30b

ℹ️ 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 and others added 2 commits August 8, 2026 07:48
Claiming canRequestVMDeathEvent while ignoring VM_DEATH in
EventRequest.Set meant an IDE got a request id back that nothing would
ever match: onVmDeath emitted only the spec's automatic event, id 0.
A debugger waiting on its own id would wait for a session end that had
already happened.

Registered requests are now tracked and matched, in one composite with
the automatic event, at the strongest suspend policy any of them asked
for -- a weaker one would leave a debugger that asked to suspend running
on. Cleared on EventRequest.Clear and on detach.

The test client had to learn to split a composite into its events; it
read only the first, which is why this looked correct at first.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Suspend state was published after the event that announces it. The proxy
can have a thread-list request in flight, and its reply is built from
that flag, so the wire order could be BP_HIT followed by a snapshot
taken while the thread still read as running -- putting the IDE back to
showing the thread it had just stopped at as running. The flag is now
set before the event goes out, and sampled under the mutex that
publishes it, so any snapshot taken after the event sees the suspension.

The debugger's own listener appeared in the thread list. It is a raw
pthread, but servicing CMD_GET_STRING calls toNSString with
getThreadLocalData(), which registers whatever thread asks into
allThreads and never unregisters it. That left a permanent synthetic
entry with no java.lang.Thread behind it -- and it only showed up once
the IDE resolved a thread name, so it was a side effect of the feature
this branch added. The listener now registers itself up front and
enumeration skips it.

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: 4a654c366c

ℹ️ 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_debugger.m Outdated
Comment thread Ports/iOSPort/nativeSources/cn1_debugger_objects.c
Racing VM thread-state init. My own previous fix -- registering the
listener's ThreadLocalData at thread start so enumeration could skip it
-- created this: getThreadLocalData() initialises threadIdKey,
threadKeyCounter and the allThreads array with no synchronisation, and
on a non-wait launch the listener spawns just as the main thread makes
its first call. Competing TLS keys or duplicate thread ids were possible.
The pointer is now captured lazily at the one place the listener really
needs a Java context, servicing CMD_GET_STRING, by which time a proxy
and an IDE are attached and the VM initialised long ago. No call is made
any earlier than the code already made one.

Accepting a class header as proof of liveness. A registered class word
survives reclamation -- full-page BiBOP reset does not clear class words,
and a legacy object reaches free() with its header intact -- so an
objectID the IDE held across a resume passed validation and was read.
Every reference handed to the proxy is now recorded, the set is dropped
on every resume path, and an id arriving from the wire must be one
issued since the last resume or it is refused.

That bounds the reported window rather than proving liveness, and the
code says so: objects shown to the IDE come from live frames, fields and
arrays and so are reachable, but the concurrent collector can still run
while a thread is parked. Closing it completely means rooting issued ids
until the IDE disposes them, which belongs in the collector, not here.
Worth weighing against the starting point -- before this branch, wire
ids were dereferenced with no validation at all.

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: b9cfea00de

ℹ️ 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_debugger.m Outdated
Comment thread Ports/iOSPort/nativeSources/cn1_debugger_objects.c Outdated
Both are consequences of the table I added for the liveness finding.

A per-thread resume cleared the whole table. With two threads parked and
the IDE resuming or stepping only one, ids already handed out for the
other -- still stopped, still being inspected -- were invalidated, so its
locals, fields and arrays started reporting unavailable. The set is now
kept while any thread remains suspended and dropped when the last one
wakes, which still denies ids to an app that has run on.

Overflow was silent. Past the fixed 4096 entries the table stopped
recording but callers kept sending those references, so every one after
that was visible to the IDE and impossible to expand -- reachable by
inspecting a single large object array. The table now grows, and
recording reports failure so a reference that cannot be recorded is
reported as null instead: one the IDE never sees beats one it sees and
cannot use.

Covered in DebuggerObjectValidationTest; pinning the old fixed table
fails both new cases at exactly the 4096 boundary.

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: 6319dd476f

ℹ️ 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 maven/cn1-debug-proxy/src/main/java/com/codename1/debug/proxy/JdwpServer.java Outdated
Count was parsed for its width and discarded, so a ClassPrepare request
asking for one event got one per matching class and stayed armed
afterwards. The count is retained now: the first N-1 matches are
swallowed, the Nth is reported, and the request is deleted, which is
what the spec asks for.

Thread names were cached for the life of the thread. Pools rename their
workers per task, so a name resolved on the first query mislabelled the
thread from then on. The cache is dropped on every authoritative thread
list rather than pruned to live ids, so a rename shows up on the next
refresh; resolution costs a field read and a string read per thread the
IDE actually asks about.

Thread naming had no test of its resolution path at all -- it needs a
device to answer three round trips, and the existing thread tests drive
the proxy's listener callbacks directly. Added one that speaks the wire
protocol through a stand-in device, covering the resolution, the rename,
and the fallback when a thread has no java.lang.Thread yet.

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: 1be85b6223

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

Hints were read only out of codenameone_settings.properties. A hint
passed with -D was accepted by Maven, echoed in its own property dump,
and then ignored: the builder asked request.getArg(...) and got the
file's value or the default.

This is the complaint that opened issue #5333. The reporter set
-Dcodename1.arg.ios.onDeviceDebug=true exactly as documented and got
"No cn1-symbols.txt found ... the translator only emits it when
-Dcodename1.arg.ios.onDeviceDebug=true is set". It was never user error.
Reproduced here against the local ios-source path: with the hint on the
command line the translator was still invoked with
-Dcn1.onDeviceDebug=false, so no symbol table was emitted and the debug
listener was not linked.

It also means cn1:buildIosOnDeviceDebug could not do what it claims. That
goal sets the hint as a property on a nested Maven invocation, which hits
the same wall -- so the IDE actions this branch added, and the guide text
saying the goal forces the hint for one build, were both wrong.

Maven's user properties -- the values actually passed with -D or set by
an invoking build, not the whole system property table -- are now
overlaid on the settings file, so an unrelated JVM property cannot become
a build hint.

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

if (atomic_compare_exchange_strong(&g_susInit, &expected, 1)) {

P1 Badge Wait until suspend primitives are initialized

When two threads first call susForThread() concurrently—for example, a breakpoint lands while the listener handles its first thread-list request—the winner publishes g_susInit = 1 before initializing the mutexes and condition variables. The loser therefore returns immediately and can lock or signal a partially initialized sus_state, causing undefined behavior, crashes, or missed wakeups; use pthread_once or a distinct completed state that other callers wait for.


return &g_sus[((uint64_t)threadId) & (SUS_TABLE_SIZE - 1)];

P1 Badge Store suspend state by the full thread ID

After the process has allocated more than 1024 cumulative threads, the monotonic thread IDs can alias here even though no more than 1024 threads are alive simultaneously—for example, a long-lived thread 1 and a later worker 1025 use the same slot. Suspending the worker then also makes thread 1 appear suspended and exposes the worker's tsd as thread 1's stack; resuming either ID can wake the wrong waiter or leave one parked indefinitely. The table needs to key and verify the complete ID rather than masking it directly.

ℹ️ 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_debugger.m
Comment thread Ports/iOSPort/nativeSources/cn1_debugger.m Outdated
Building the sample app for the iOS simulator failed to compile: the
watchOS slice's generated classes call cn1_debugger_register_class from
their constructors, and there it resolves against an older copy of
cn1_debugger.h that predates the function. cn1_debugger_objects.c is not
in that source set either, so it would not have linked even with a
declaration.

Generated code is compiled in more contexts than the iOS port's own
headers reach, so a hook it calls cannot depend on them. Declared in
cn1_globals.h, which every generated file includes, and given a weak
no-op in cn1_globals.m alongside cn1_debugger_check and
cn1_debugger_mark_issued_roots -- the same arrangement those already use
for the same reason. A target that links the debugger runtime gets the
real one.

The test pins the contract for all three hooks rather than this instance
alone, since the next one added would fail the same way.

Found by building for the simulator. Nothing in the host tests reaches
it: they compile the debugger unit against the working tree in
isolation, never as the translator emits calls into it.

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: f6acda51d9

ℹ️ 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_debugger.m
Building the sample app with on-device debugging on failed the watch
target: cn1_debugger.m draws the "waiting for debugger" overlay with
UIKit, which does not exist on watchOS, and the watch slice compiles the
same file. So enabling the feature on any project that has a watch
target did not build at all.

The overlay is the only UIKit in the file. It is compiled out for
watchOS, leaving no-op install and dismiss entry points -- the watch app
has no debugger UI to show, while the rest of the runtime, the listener,
the wire protocol and the object validation, is plain C and unaffected.

Verified by syntax-checking the file against both SDKs.

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

case 3: { // ThreadOnly (objectID)
if (off + 8 > p.length) { badModifier = true; break; }
off += 8; break;

P2 Badge Preserve ThreadOnly on class-prepare requests

When a client adds a valid ThreadOnly modifier to a CLASS_PREPARE request, this branch consumes the thread ID without retaining it, and replayClassPrepare() subsequently emits every matching preparation with either lastSuspendedThread or dummy thread 1. Thread-filtered requests therefore receive events attributed to a thread they explicitly excluded; retain and apply the requested thread ID, or reject this unsupported modifier rather than silently broadening the request.

ℹ️ About Codex in GitHub

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

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

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 maven/cn1-debug-proxy/src/main/java/com/codename1/debug/proxy/JdwpServer.java Outdated
ParparVM thread ids are small integers and this proxy hands them to the
IDE as thread ids unchanged. jdb asks ObjectReference.ReferenceType on
them while drawing the Threads panel, and those arrived here as 1, 3, 5
rather than heap addresses. Forwarding them to the device as object
pointers is wrong twice: they address nothing, and an odd one cannot be
told apart from a tagged int -- so every odd-numbered thread rendered as
a java.lang.Integer.

Object queries now resolve a known thread id to the java.lang.Thread
behind it. Heap references are aligned addresses far above the thread-id
range, so nothing else is ambiguous.

Found by attaching jdb to the app running in the simulator: the thread
list came back as
  (java.lang.Integer)0x1  Thread-1
  0x2                     GC Thread
  (java.lang.Integer)0x3  EDT
with the pattern following odd ids exactly.

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

if (g_proxyFd >= 0) {
uint8_t* payload = (uint8_t*)malloc(4 + lineLen);
if (payload) {
uint32_t lenBE = htonl((uint32_t)lineLen);
memcpy(payload, &lenBE, 4);
if (lineLen > 0) memcpy(payload + 4, line, lineLen);
sendEvent(cap->evtCode, payload, 4 + (uint32_t)lineLen);

P2 Badge Suppress SIGPIPE on the debugger socket

When the proxy disconnects while the debug build is still printing, the new stdout/stderr capture threads can reach this sendEvent() call with a stale connected descriptor. The debugger socket is not configured with SO_NOSIGPIPE, while the iOS runtime explicitly installs a SIGPIPE handler; therefore send() can raise a signal instead of returning the EPIPE that sendAll() expects, potentially crashing or throwing on the raw capture pthread. Configure the socket to suppress SIGPIPE, as the repository's normal iOS socket implementation does, before forwarding output.

ℹ️ 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_debugger.m
A debugger resolves a frame against the line table it was handed for
that frame's method and treats an index the table cannot describe as
corruption rather than as missing detail. jdb raises

  InternalError: Location with invalid code index

out of `where` and the session ends -- so a single frame the proxy
described badly took down the whole stack view, not just its own row.

Two ways the proxy produced such a frame:

Method.LineTable answered "success, zero entries" for a method it had no
lines for. That reads as an authoritative empty table, which makes every
location in the method unresolvable. It now answers ABSENT_INFORMATION,
which is what the spec reserves for this and what debuggers already
handle -- the frame simply shows no line number.

ThreadReference.Frames passed the device's line through unchanged. It is
normally a line the table has, since ParparVM's code index is the source
line, but not for a line the optimiser moved or for 0 from a frame that
has not reached a tracked line. Frames now snap to the nearest tracked
line at or before the reported one, naming the last line known to have
started, and a frame with no line yet shows the method's first.

Declining an unimplemented command is also no longer silent. A debugger
reports "Unexpected JDWP Error: 100" without naming what it asked for,
so neither side of the connection could tell which command was missing;
the proxy now logs the command set and command once per pair.

Found with jdb attached to an app in the simulator: a breakpoint in
Form.show() stopped and printed three frames, including a lambda, before
`where` threw on the fourth.

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: 11f7787933

ℹ️ 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 maven/cn1-debug-proxy/src/main/java/com/codename1/debug/proxy/JdwpServer.java Outdated
Comment thread maven/cn1-debug-proxy/src/main/java/com/codename1/debug/proxy/JdwpServer.java Outdated
Review findings, in the order they bite a session:

Suspension-table init raced its own publication. ensureSusInit set the
"initialised" flag before constructing the mutexes, so a caller that lost
the race returned early and locked a slot that did not exist yet. It
needs the IDE's first AllThreads to race a thread reaching its first
breakpoint -- the opening seconds of a session. Now pthread_once.

An invocation could arrive before the target parked and hang the
session. suspendCurrent waited before testing for queued work, and a
condition variable does not queue signals, so an invocation published in
that window had already been signalled: the listener waited for a result
nobody was running. The predicate is now tested before each wait.

Invocation results were rooted after the target went inactive. A thunk
returning or throwing a fresh object left the only reference to it in
the result struct, which the collector does not scan; the ID handed over
could already name reclaimed storage. Rooted on the target thread before
it goes inactive.

Descendant IDs died on every thread-list refresh. A refresh re-issues
only top-level Thread objects, so a field the IDE had expanded off one
carried no claim and was dropped -- while still reachable from a live
thread. Claims now carry a generation and a parent link, and a refresh
reconciles instead of clearing, keeping whatever hangs off a thread it
still advertises.

A thread that stopped while the thread list was in flight vanished from
it. The device samples at request time, so the reply predates the stop,
and rebuilding from it dropped the thread the IDE had just been told
about. Suspensions and list requests are now ordered against each other:
a suspension newer than the request survives the snapshot, an older one
absent from it is still a thread that died.

ClassMatch modifiers were combined disjunctively. JDWP ANDs them, so
"com.example.*" plus "*Test" asks for the test classes in that package;
we replayed everything matching either.

A malformed thread-list frame could take the proxy down. count * 17
overflows to a negative for a large count, turning the bounds check into
a pass, and the allocation that followed threw OutOfMemoryError. Checked
by division now -- and the same defect was in the stack, locals and
object-fields frames, which are fixed too.

Found by running it against the simulator:

ReferenceType.SignatureWithGeneric was unimplemented. jdb asks for it
while caching a reference type, which it first does when rendering an
array, so printing a frame holding one threw and took the rest of that
locals listing with it.

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: 4f63a22fc2

ℹ️ 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_debugger_objects.c
shai-almog and others added 2 commits August 8, 2026 17:18
A debugger may ask about a thread ID as an object -- jdb does it while
drawing its thread panel -- so resolving such a query to the thread's
java.lang.Thread was right, but recognising which IDs were threads by
their small numeric range was not.

Heap references are aligned and therefore even, and the earlier reasoning
stopped there. Tagged ints are not references at all: ParparVM encodes
int v as (v << 1) | 1, so they are odd and occupy exactly the small
values thread IDs use. Integer 0 encodes to 1, which was also thread 1,
and expanding that boxed Integer in the IDE read the thread object
instead.

Thread IDs now travel tagged into the top of the ID space and shifted
even: above any address the process can hold, and never odd, so no
tagged int can equal one. The mapping is applied at each boundary --
AllThreads, the thread group's children, breakpoint and step events,
every ThreadReference and StackFrame command, and the thread argument of
both invocation forms.

Separately, a VM_DEATH request with a Count above one now stays silent.
A VM dies once, so such a request is asking never to be told; the count
was parsed and then dropped, and the death reported anyway.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The device reports an array by naming its component class and setting a
flag, so an array and its component type shared one reference-type ID.
The type the IDE had been told was an array then answered with the
component's own signature -- one with no leading '['. A debugger reads
the component type back out by removing the first character, so it does
not detect the missing bracket, it just eats the 'L' and parses what is
left: printing an array local ended in

  IllegalArgumentException: Invalid JNI signature character 'j'

the 'j' being the start of "java/lang/...". Array reference types are
now tagged into their own ID range carrying the component class, and
report the component's signature with a '[' in front.

From review:

A hold outlived the session that set it. An IDE detaching between
HoldEvents and ReleaseEvents left the flag and the queue in place, so
the next attach queued its own VM_START instead of sending it and a
later release delivered events carrying the previous IDE's request ids.
Cleared in teardown.

A descendant reached through two threads kept only the most recently
recorded parent. When that thread went away the id went with it, while
the IDE was still displaying the same id under the thread that remained.
Parents are a set now, like the owners, with the same overflow rule.

A thread-list request that timed out could have its reply applied to the
next request. The reply describes the list as of the request it answers,
so using it later judged suspensions against the wrong epoch and rebuilt
from a list older than the one asked for. Abandoned replies are counted
and discarded on arrival.

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: 3bebcc3963

ℹ️ 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 maven/cn1-debug-proxy/src/main/java/com/codename1/debug/proxy/JdwpServer.java Outdated
Comment thread maven/cn1-debug-proxy/src/main/java/com/codename1/debug/proxy/JdwpServer.java Outdated
The stack view no longer stops at a frame whose method carries no line
information, which is worth stating because such frames are common --
synthetic lambda bodies and Thread.runImpl both appear in an ordinary
EDT stack.

The Error 100 troubleshooting entry promised the proxy logs the command
it declined. It does now, so the entry shows what that looks like.

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: e4ac02cc22

ℹ️ 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 maven/cn1-debug-proxy/src/main/java/com/codename1/debug/proxy/JdwpServer.java Outdated
shai-almog and others added 3 commits August 8, 2026 17:45
Two events name a thread the proxy has to invent rather than one it
learned from the device, and both still wrote it raw: VM_START, and a
CLASS_PREPARE replayed to an IDE that registered the request. Every
ThreadReference handler now decodes what it is given, so a raw value
came back as a different device thread -- raw 1 as thread 0 -- and a
small raw value is exactly what a tagged int looks like, which is the
collision the namespace exists to prevent.

Also stop inferring "the device answered" from "the request is no longer
outstanding". Those were the same thing until a timeout began clearing
the pending flag; since then a device that never answers, or a send that
threw, read as a successful refresh. Receipt is tracked separately now.

That second change is not observable today: the fallback it guards adds
threads from knownThreads, which only ever gains an id alongside
deviceThreads and is pruned to a subset of it on every snapshot, so the
loop cannot contribute one. The accompanying test says so rather than
implying it covers a live defect -- it passes with or without the fix.
The flag is still worth correcting, because it now means what its name
says and is the right guard if that invariant changes.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The runtime gives every array its own class, made up as needed for the
element type, so those classes carry no entry in the symbol table --
that table describes what the translator compiled. Reporting an array's
own class ID left the proxy with nothing to resolve, so it fell back to
Object and every array in the IDE read as Object[] whatever it held. A
single is-array flag could not have said otherwise: it has no room for
the depth, so int[][] was unrepresentable either way.

The device now walks arrayType down to the first class that is not
itself an array -- the one the symbol table knows -- counting the hops,
and sends that component with the depth alongside the flag. The proxy
carries the depth in the reference-type ID and repeats the '[' that many
times. The walk is bounded rather than open, since the chain is read out
of the target's memory and a corrupt link must not spin.

The reply grows a sixth byte; proxies reading four or five are
unaffected, and a device that sends neither is read as one dimension,
which is all such a device could have meant.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The two describe the same object-class reply, and reading them in
separate synchronized blocks let a second object's answer land in
between -- pairing one object's array flag with another's depth.

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: 7937a1bdc3

ℹ️ 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_debugger.m Outdated
Comment thread Ports/iOSPort/nativeSources/cn1_debugger_objects.c Outdated
Comment thread Ports/iOSPort/nativeSources/cn1_debugger_objects.c
shai-almog and others added 2 commits August 8, 2026 18:10
Suspension state was indexed by a hash of the thread ID into a fixed
table, and the runtime hands out IDs from a counter that only ever
climbs. Once an app has created as many threads as the table has slots,
two live threads share one: either thread's suspend flag and frame
pointer overwrote the other's, so a stack or locals request could answer
about the wrong thread and resuming one woke the other. The slot now
records which thread owns it and is found by probing, so a collision
moves the newcomer along instead of overwriting. A table with nothing
free reclaims a slot that is idle -- one still in its initial state
carries nothing, and its owner claims a fresh slot next time it asks --
and failing even that returns the hashed slot rather than nothing, since
every caller dereferences the result.

The array depth was counted rather than read. The translator points
arrayType straight at the scalar for every depth and puts the real one
in dimensions, so following the link and counting called int[][]
one-dimensional and the IDE showed it as int[]. The depth now comes from
the class; a chain is still walked in case a layout ever nests.

A resume dropped descendants of live threads. An object reached through
a Thread object carries no owner of its own and a generation stamp from
before the last refresh, so judged on its own account it looks
unclaimed. The refresh knew to follow the parent links; the resume
rebuild did not, and dropped the ID the moment any thread ran again --
while the IDE was still showing it under a thread the current list
advertises. Both paths share the reachability pass now.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Making the slot table probe for its owner put a process-wide lock and a
scan of the table on the path cn1_debugger_check takes -- which runs at
every source line of every method, and is the whole cost the debugger
adds to a running app. Correct, and far too expensive to reach a device.

The thread's own slot is remembered in thread-local storage, so the
common case is a pointer compare and no lock at all. That holds only
because a claimed slot never changes hands, so slots are no longer
reclaimed: a full table falls back to the shared hashed slot, which is
where every thread started before any of this. Reclaiming would let a
slot change owner underneath the thread holding it -- the very defect
being fixed -- and would cost the fast path.

Only a caller running on the thread it is asking about may use the
cache; the listener asks about other threads and takes the slow path,
which is not hot.

Verified on the simulator with the rebuilt binary: breakpoint hit,
locals read in the stopped frame, app still running.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@shai-almog
shai-almog merged commit 3a746af into master Aug 8, 2026
49 checks passed
@shai-almog
shai-almog deleted the fix/ios-debugger-5333 branch August 8, 2026 14:04
shai-almog added a commit that referenced this pull request Aug 8, 2026
The packaging job started failing on master, and the failure was not a flake:
the last green run mentions GoogleSignIn zero times and the first red one 517.
#5536 fixed build-hint plumbing, so ios.gplus.clientId -- which
ios-packaging.yml has passed for months -- finally reached IPhoneBuilder for
the first time and switched on the pod. The coverage that workflow describes,
compiling GoogleConnectImpl.m, had never actually run, and the moment it did it
took the build down: GoogleSignIn 5.x vendors a binary framework whose arm64
slice is device-only, so it cannot link into an arm64 simulator build, which is
every simulator build on an Apple Silicon machine.

7.1 ships as a source pod, so there is no prebuilt slice to mismatch. Its API
is different in the three places this port touches: the client id rides on a
GIDConfiguration, sign-in reports to a completion block rather than a delegate,
and the token hangs off the user rather than an authentication object. The
GIDSignInDelegate conformance and its two callbacks are gone with it, and the
pre-configuration in initGoogleConnect goes with them -- there is nothing to
set up ahead of a call that carries everything. The GPPSignIn branches are
untouched.

Verified by building the sample the way the packaging job does, with the same
dependency arguments: the workspace compiles and links for the arm64 simulator
with the pod in it. What that does not cover is the sign-in flow itself, which
needs real Google credentials and is no better covered today than before.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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.

Problems debugging in ios side

1 participant