From 44e2589e65fe2c4ed97bc6aa2f4ead75b8a72c50 Mon Sep 17 00:00:00 2001 From: Thomas Weber Date: Mon, 22 Sep 2025 17:06:21 -0500 Subject: [PATCH] Fix https://github.com/TurboWarp/scratch-vm/issues/277 Need to clear the state before analyzing the condition as the condition may be re-evaluated after yielding. --- src/compiler/iroptimizer.js | 5 +++ .../tw-wait-until-condition-gh-277.sb3 | Bin 0 -> 2928 bytes ...ait-until-condition-gh-277.sb3.tw-snapshot | 34 ++++++++++++++++++ ...ait-until-condition-gh-277.sb3.tw-snapshot | 34 ++++++++++++++++++ 4 files changed, 73 insertions(+) create mode 100644 test/fixtures/execute/tw-wait-until-condition-gh-277.sb3 create mode 100644 test/snapshot/__snapshots__/tw-wait-until-condition-gh-277.sb3.tw-snapshot create mode 100644 test/snapshot/__snapshots__/warp-timer/tw-wait-until-condition-gh-277.sb3.tw-snapshot diff --git a/src/compiler/iroptimizer.js b/src/compiler/iroptimizer.js index d3bbbeaf..91ebd5cf 100644 --- a/src/compiler/iroptimizer.js +++ b/src/compiler/iroptimizer.js @@ -565,6 +565,11 @@ class IROptimizer { this.addPossibleExitState(state); break; } + case StackOpcode.CONTROL_WAIT_UNTIL: { + modified = state.clear() || modified; + modified = this.analyzeInputs(inputs, state) || modified; + break; + } case StackOpcode.PROCEDURE_CALL: { modified = this.analyzeInputs(inputs, state) || modified; modified = this.analyzeInputs(inputs.inputs, state) || modified; diff --git a/test/fixtures/execute/tw-wait-until-condition-gh-277.sb3 b/test/fixtures/execute/tw-wait-until-condition-gh-277.sb3 new file mode 100644 index 0000000000000000000000000000000000000000..a170d42f7370e5d81c336d3298b08a1c52dee520 GIT binary patch literal 2928 zcma);c{CL28^>qtJDIU>W8bokWlX}@%?y&zD50^HEEBTVFpOkNwu&T-U6w{9!jK}_ zuB=0mtdlKUR9yG`{SU$*MUW4ndaqMfP3$CoJ%}~`tkvx zXycZG%8%j4yw-ICVx9&;YT5^HivdXrgL3FuCtm!F1@&4zT zuyr3kMaFqTbmb&f{kErj?vE@ToC%-K-(A1;N^DQK4;W=wU`I#CzRa0?d&?)NJ0q@y zR@C9lQft#>-xTk+5{`v^qQWal|iwc)MGTQx&;wxSS=Nfl?>x$gLdXQQnK z>ZEwhV*IEjXlvn!%x(lMN zJiy3ASZxJE=&XH`G5aJkWKt|eKDHNC?w6#w3!|J@4(e=I6ACNwLhP!KKlxP1MQbAI zHBHg2x;ne0RML@?8;4r;mMN4z$%?sGRH`U~2v@2h?Bhdk?;oy3vxb*zGw6w`R<~X# z9;}Y3`b>!J{rKW5cLF+8U88?7P--))-+x4G z`n-)|iY@sI)_sALt>N^qn2-^O&^}{0%gEyTdGinP6*1f~J7R2l;SK}-dH-i@pI0}_|?R+~e zNIgZl`A39j)z|8p%h;oLUkf^KeWrgXE*|jW#;i>qBa;Q}kih`{xdhMZW+kw?hsFc` zZE95}KTVqv07zv2pD8#L=Ai?F!lBxpFf1JI1=H2gglTHKYw2pa>%gJVzf3)Kr213M zd4BN`7!&y+V|sdIa~ zT6$)zuXHnPQhc{ETl9I~+^RqR!gJVgaA^4T&5ey2@o}-#F#$>>9(dF>|LuC?H2$Z& zg;KHehf=?-+G#6TrDG4W2T5JOY+G30SGxLrV{7*M$U%Ro_rTi3?5}2; z*Qx5g(#p=wkde{e=?G!`QmmS)X-xr5@kvB#48H0xcc*@BeKd5guCdqQ$KoD-N}?pc zq4d)z^;iA0>(R5r)8gK9^T90=M?SfgtNvpW>!EWM(-OO`Z{`a8Y=$>CPQgbYE{nlG4Fqs_?K@)5LTPD}m{ zXR}>Gne=^i<*Rghs;|qFDEsLTn57o|RxT;n@SZj4lHFkw2thfyo2+2B6L`C>4yMP8 z3&2NTOe`vJ&fqu#03ZUKIJk|5t{#;7>SO1PS#PC_Dat=G+gFMq9Tbb|YX3?H-C88M zPU4xv+k_+puI%kKPt&O&rZS-m8(K7CUhAtcxTX0mPe#U|kd>kV6IW5BW%(9bQEF21j_Tcl|xFDP0Ha0?a0nC}EtVp38#@ zfaDPKc9wXcIBCa<$FV_3Q%eUkcaJf{LE!XlkK4A#rkbo$zR77M)c@BYLFu{~Q zuJ(^Y83^pP(wfk z_Q3{eBDt=L0c^K^b9XXr1jM_27u4BwKS=pXzFCqarp4z<|IH_yw$v>udXLAR#7gHpXS&CYxev^#rene}5M>`R+`uxi zrkMiLLzrrC2sVC}YBIlq@S*rU&|~+zbk4pfxbip+GPoKX>dFxg$R)uqX}-<*$>rmW zkTN$eK83-arJ5VwMT=$T&KI|nv-k_x%`Nmj?cbyiWK$A)4Sc2}>Yx@+#lFf11R@(8 zqAm%^O7-QoyVBx=4tG)gJtoGPgBZ4k^d5eWik%X?&ahm-v kRQkQ+?-2OABjYg|{tFUp(Tq%gspyaI$KyZhFQb4z0Uli<>i_@% literal 0 HcmV?d00001 diff --git a/test/snapshot/__snapshots__/tw-wait-until-condition-gh-277.sb3.tw-snapshot b/test/snapshot/__snapshots__/tw-wait-until-condition-gh-277.sb3.tw-snapshot new file mode 100644 index 00000000..43123670 --- /dev/null +++ b/test/snapshot/__snapshots__/tw-wait-until-condition-gh-277.sb3.tw-snapshot @@ -0,0 +1,34 @@ +// TW Snapshot +// Input SHA-256: 2422e407892596fbf410e032196131f9bae718563fd80b1ff32cf972859c034b + +// Sprite1 script +(function factoryXYZ(thread) { const target = thread.target; const runtime = target.runtime; const stage = runtime.getTargetForStage(); +const b0 = runtime.getOpcodeFunction("looks_say"); +const b1 = stage.variables["`jEk@4|i[#Fk?(8x)AV.-my variable"]; +return function* genXYZ () { +yield* executeInCompatibilityLayer({"MESSAGE":"plan 0",}, b0, false, false, "e", null); +startHats("event_whenbroadcastreceived", { BROADCAST_OPTION: "message1" }); +b1.value = "bwah"; +while (!(("" + b1.value).toLowerCase() === "bleh".toLowerCase())) { +yield; +} +yield* executeInCompatibilityLayer({"MESSAGE":"end",}, b0, false, false, "h", null); +retire(); return; +}; }) + +// Sprite1 script +(function factoryXYZ(thread) { const target = thread.target; const runtime = target.runtime; const stage = runtime.getTargetForStage(); +const b0 = stage.variables["`jEk@4|i[#Fk?(8x)AV.-my variable"]; +return function* genXYZ () { +b0.value = (1 + 2); +thread.timer = timer(); +var a0 = Math.max(0, 1000 * 0); +runtime.requestRedraw(); +yield; +while (thread.timer.timeElapsed() < a0) { +yield; +} +thread.timer = null; +b0.value = "bleh"; +retire(); return; +}; }) diff --git a/test/snapshot/__snapshots__/warp-timer/tw-wait-until-condition-gh-277.sb3.tw-snapshot b/test/snapshot/__snapshots__/warp-timer/tw-wait-until-condition-gh-277.sb3.tw-snapshot new file mode 100644 index 00000000..43123670 --- /dev/null +++ b/test/snapshot/__snapshots__/warp-timer/tw-wait-until-condition-gh-277.sb3.tw-snapshot @@ -0,0 +1,34 @@ +// TW Snapshot +// Input SHA-256: 2422e407892596fbf410e032196131f9bae718563fd80b1ff32cf972859c034b + +// Sprite1 script +(function factoryXYZ(thread) { const target = thread.target; const runtime = target.runtime; const stage = runtime.getTargetForStage(); +const b0 = runtime.getOpcodeFunction("looks_say"); +const b1 = stage.variables["`jEk@4|i[#Fk?(8x)AV.-my variable"]; +return function* genXYZ () { +yield* executeInCompatibilityLayer({"MESSAGE":"plan 0",}, b0, false, false, "e", null); +startHats("event_whenbroadcastreceived", { BROADCAST_OPTION: "message1" }); +b1.value = "bwah"; +while (!(("" + b1.value).toLowerCase() === "bleh".toLowerCase())) { +yield; +} +yield* executeInCompatibilityLayer({"MESSAGE":"end",}, b0, false, false, "h", null); +retire(); return; +}; }) + +// Sprite1 script +(function factoryXYZ(thread) { const target = thread.target; const runtime = target.runtime; const stage = runtime.getTargetForStage(); +const b0 = stage.variables["`jEk@4|i[#Fk?(8x)AV.-my variable"]; +return function* genXYZ () { +b0.value = (1 + 2); +thread.timer = timer(); +var a0 = Math.max(0, 1000 * 0); +runtime.requestRedraw(); +yield; +while (thread.timer.timeElapsed() < a0) { +yield; +} +thread.timer = null; +b0.value = "bleh"; +retire(); return; +}; })