-
-
Notifications
You must be signed in to change notification settings - Fork 17
Centralize loop guard injection into AST transformation #399
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -632,9 +632,71 @@ export const flock = { | |
| }, | ||
| }); | ||
| }, | ||
| injectLoopGuards(src) { | ||
| const ast = acorn.parse(src, { | ||
| ecmaVersion: "latest", | ||
| sourceType: "script", | ||
| allowAwaitOutsideFunction: true, | ||
| }); | ||
|
|
||
| // Check whether a BlockStatement already contains any await expression | ||
| // (at any nesting depth). Loops that already yield don't need an extra guard. | ||
| function bodyHasAwait(node) { | ||
| let found = false; | ||
| walk.simple(node, { | ||
| AwaitExpression() { | ||
| found = true; | ||
| }, | ||
| }); | ||
| return found; | ||
| } | ||
|
|
||
| // Collect positions (just before the closing '}') where we'll inject | ||
| const insertions = []; | ||
|
|
||
| function addGuardIfNeeded(loopBody) { | ||
| if ( | ||
| loopBody?.type === "BlockStatement" && | ||
| !bodyHasAwait(loopBody) | ||
| ) { | ||
| insertions.push(loopBody.end - 1); | ||
| } | ||
| } | ||
|
|
||
| walk.simple(ast, { | ||
| WhileStatement(node) { | ||
| addGuardIfNeeded(node.body); | ||
| }, | ||
| DoWhileStatement(node) { | ||
| addGuardIfNeeded(node.body); | ||
| }, | ||
| ForStatement(node) { | ||
| addGuardIfNeeded(node.body); | ||
| }, | ||
| ForInStatement(node) { | ||
| addGuardIfNeeded(node.body); | ||
| }, | ||
| ForOfStatement(node) { | ||
| addGuardIfNeeded(node.body); | ||
| }, | ||
| }); | ||
|
|
||
| // Apply from last to first so earlier positions stay valid | ||
| insertions.sort((a, b) => b - a); | ||
|
|
||
| let result = src; | ||
| for (const pos of insertions) { | ||
| result = | ||
| result.slice(0, pos) + | ||
| "await wait(0);\n" + | ||
|
Comment on lines
+689
to
+691
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: git ls-files | head -20Repository: flipcomputing/flock Length of output: 437 🏁 Script executed: fd -t f "flock.js" | head -5Repository: flipcomputing/flock Length of output: 71 🏁 Script executed: wc -l flock.jsRepository: flipcomputing/flock Length of output: 76 🏁 Script executed: sed -n '680,700p' flock.jsRepository: flipcomputing/flock Length of output: 875 🏁 Script executed: rg "wait" flock.js -A 2 -B 2 | head -50Repository: flipcomputing/flock Length of output: 2176 🏁 Script executed: rg "createWhitelist\|wait" flock.js -B 3 -A 5 | head -100Repository: flipcomputing/flock Length of output: 45 🏁 Script executed: rg "function wait\|const wait\|=.*wait" flock.js | head -20Repository: flipcomputing/flock Length of output: 45 🏁 Script executed: rg "wait" flock.js -B 5 -A 5 | grep -A 10 -B 10 "await wait"Repository: flipcomputing/flock Length of output: 756 🏁 Script executed: rg "function createWhitelist|createWhitelist" flock.js -B 2 -A 20Repository: flipcomputing/flock Length of output: 2343 🏁 Script executed: sed -n '900,1100p' flock.js | grep -A 50 "createWhitelist"Repository: flipcomputing/flock Length of output: 2747 🏁 Script executed: rg "return.*\{" flock.js -A 30 | grep -B 10 -A 30 "wait"Repository: flipcomputing/flock Length of output: 2170 🏁 Script executed: sed -n '900,1100p' flock.jsRepository: flipcomputing/flock Length of output: 11739 🏁 Script executed: sed -n '1000,1200p' flock.jsRepository: flipcomputing/flock Length of output: 11072 🏁 Script executed: rg "validateUserCodeAST" flock.js -B 3 -A 30Repository: flipcomputing/flock Length of output: 2926 🏁 Script executed: sed -n '500,700p' flock.js | grep -A 100 "REJECT_IDENTIFIERS"Repository: flipcomputing/flock Length of output: 7628 🏁 Script executed: rg "REJECT_IDENTIFIERS" flock.js -A 100 | head -150Repository: flipcomputing/flock Length of output: 6659 Use a hygienic helper name for the injected guard. The injected code calls bare Suggested fix- "await wait(0);\n" +
+ "await __flockLoopGuardWait__(0);\n" +Then expose 🤖 Prompt for AI Agents |
||
| result.slice(pos); | ||
|
Comment on lines
+666
to
+692
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🌐 Web query:
💡 Result: No. Only inject guards inside async-capable scopes. This code instruments loops anywhere in the AST, producing invalid JavaScript. For example, 🤖 Prompt for AI Agents |
||
| } | ||
| return result; | ||
| }, | ||
| async runCode(code) { | ||
| try { | ||
| flock.validateUserCodeAST(code); | ||
| const instrumentedCode = flock.injectLoopGuards(code); | ||
| await flock.disposeOldScene(); | ||
|
|
||
| // --- remove any existing iframe --- | ||
|
|
@@ -818,12 +880,12 @@ export const flock = { | |
| // Wrap user code to allow top-level await | ||
| /*const wrapped = | ||
| '(async () => {\n"use strict";\n' + | ||
| code + | ||
| instrumentedCode + | ||
| "\n})()\n//# sourceURL=user-code.js";*/ | ||
|
|
||
| const wrapped = | ||
| '(async function () {\n"use strict";\n' + | ||
| code + | ||
| instrumentedCode + | ||
| "\n}).call(undefined)\n//# sourceURL=user-code.js"; | ||
|
|
||
| // Evaluate in SES Compartment | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
Repository: flipcomputing/flock
Length of output: 354
🏁 Script executed:
# Read the full context of flock.js around the lines mentioned wc -l flock.jsRepository: flipcomputing/flock
Length of output: 76
🏁 Script executed:
Repository: flipcomputing/flock
Length of output: 4632
🏁 Script executed:
Repository: flipcomputing/flock
Length of output: 912
🏁 Script executed:
Repository: flipcomputing/flock
Length of output: 45
🏁 Script executed:
Repository: flipcomputing/flock
Length of output: 45
🏁 Script executed:
Repository: flipcomputing/flock
Length of output: 150
🏁 Script executed:
Repository: flipcomputing/flock
Length of output: 3397
🏁 Script executed:
Repository: flipcomputing/flock
Length of output: 1256
🏁 Script executed:
Repository: flipcomputing/flock
Length of output: 2029
🏁 Script executed:
Repository: flipcomputing/flock
Length of output: 3561
🏁 Script executed:
Repository: flipcomputing/flock
Length of output: 1266
🏁 Script executed:
Repository: flipcomputing/flock
Length of output: 132
🏁 Script executed:
Repository: flipcomputing/flock
Length of output: 45
🏁 Script executed:
Repository: flipcomputing/flock
Length of output: 149
Loop guards can be bypassed in three ways, reintroducing UI-blocking loops.
The current implementation has three bypass mechanisms that defeat the guard:
continuestatements skip the tail-inserted guard — Since the guard is injected just before the closing}, acontinuejumps to the next iteration without executing it.Single-statement loop bodies are never guarded — The code checks
loopBody?.type === "BlockStatement", so loops likewhile (true) foo();are never instrumented.bodyHasAwait()treats awaits in nested async functions as loop yields — Sincewalk.simple()traverses the entire AST, anasync function() { await x; }inside the loop body causes the guard to be skipped, even though the loop itself doesn't yield if the function is never called.All three reintroduce the "busy loop blocks the UI" failure mode. Normalizing eligible loop bodies to blocks and injecting the guard as the first statement of each iteration would close these gaps.
Also applies to: 684-692
🤖 Prompt for AI Agents