Skip to content

Commit 1af5e29

Browse files
shai-almogclaude
andcommitted
js-port: protect runtime-constructed bridge names from ident minification
The screenshot runner (port.js) dispatches the suite's test lambdas by building their identifier at runtime via string concatenation, e.g. "cn1_..._Cn1ssDeviceRunner_lambda_" + methodName + "_" + i + "_" + sig so the FULL generated identifier (".._lambda_runNextTest_2_<sig>") never appears as a string literal -- only the 77-char stem does. The exact-match string-literal exclusion therefore missed these, the minifier renamed the lambda run methods to $M*, and the bridge's name lookup failed: `lambda2RunBridge:missingDispatch=1`. With no runnable dispatch the cooperative scheduler parked every thread (runnable=0) and the suite wedged ~28 tests in -> the javascript-screenshots hang/broken-pipe at 85a8e01. Fix: treat every scanned cn1_ string token as a PREFIX and protect any generated def that extends it at an identifier-segment boundary, so the constructed name keeps its canonical identifier. A length floor (16) keeps the generic construction roots "cn1_" (-> ___INIT__/___CLINIT__, already skipped) and "cn1_s_" (dispatch-ids in the _qX table, never a def) out of the prefix set -- "cn1_" as a prefix would match every def and disable all minification. Over-protecting only forgoes a little size; under-protecting breaks a name-resolved bridge. Validated: HelloCodenameOne JS bundle keeps all 30 Cn1ssDeviceRunner lambda identifiers verbatim while still minifying 12,836 generated functions (translated_app.js 11.93MB -> 7.69MB, -36%); the local Playwright screenshot run progresses past the prior wedge with sinceStepMs resetting (no parked scheduler) instead of hanging. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 85a8e01 commit 1af5e29

1 file changed

Lines changed: 39 additions & 0 deletions

File tree

vm/ByteCodeTranslator/src/com/codename1/tools/translator/JavascriptBundleWriter.java

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,45 @@ private static void minifyGeneratedIdentifiers(java.util.List<String> chunkStrin
265265
// override targets, by name) -- more reliable than scanning runtime JS text.
266266
excluded.addAll(JavascriptMethodGenerator.NATIVE_METHOD_IDENTIFIERS);
267267
defs.removeAll(excluded);
268+
// Prefix protection: some bridge names are CONSTRUCTED at runtime by string
269+
// concatenation, so the full identifier never appears as a literal -- only
270+
// its stem does. The screenshot runner (port.js) builds
271+
// "cn1_..._Cn1ssDeviceRunner_lambda_" + methodName + "_" + i + "_" + sig
272+
// so the scanned literal is the stem ".._lambda_" while the generated def is
273+
// ".._lambda_runNextTest_2_<sig>". Treat every scanned cn1_ string token as a
274+
// prefix and protect any def that extends it at an identifier-segment boundary
275+
// (the next char is '_', or the stem already ends in '_'), so the constructed
276+
// name still resolves after minification. Over-protecting only forgoes size;
277+
// under-protecting breaks a name-resolved bridge -> wedge.
278+
// Only class-qualified stems are eligible as prefixes. The generic
279+
// construction roots "cn1_" (4, completes to ___INIT__/___CLINIT__, already
280+
// skipped above) and "cn1_s_" (6, dispatch-ids resolved via the _qX table,
281+
// never a function def) are short and would over-match -- "cn1_" as a prefix
282+
// matches EVERY def and would disable all minification. A length floor keeps
283+
// those out while admitting genuine fully-qualified stems (the only real one,
284+
// the screenshot runner's lambda stem, is 77 chars).
285+
final int MIN_PREFIX_PROTECT_LEN = 16;
286+
if (!defs.isEmpty()) {
287+
java.util.List<String> prefixTokens = new java.util.ArrayList<String>();
288+
for (String t : stringTokens) {
289+
if (t.length() >= MIN_PREFIX_PROTECT_LEN) {
290+
prefixTokens.add(t);
291+
}
292+
}
293+
if (!prefixTokens.isEmpty()) {
294+
java.util.Iterator<String> it = defs.iterator();
295+
while (it.hasNext()) {
296+
String d = it.next();
297+
for (String t : prefixTokens) {
298+
if (d.length() > t.length() && d.startsWith(t)
299+
&& (t.endsWith("_") || d.charAt(t.length()) == '_')) {
300+
it.remove();
301+
break;
302+
}
303+
}
304+
}
305+
}
306+
}
268307
if (defs.isEmpty()) {
269308
return;
270309
}

0 commit comments

Comments
 (0)