tmedwards / sugarcube-2

SugarCube is a free (gratis and libre) story format for Twine/Twee.
https://www.motoslave.net/sugarcube/2/
BSD 2-Clause "Simplified" License
177 stars 41 forks source link

Widget shenanigans? #255

Closed tmedwards closed 2 months ago

tmedwards commented 1 year ago

Investigate the following widget shenanigans.

Shenanigan 1

From the Twine Games Discord. Just see the source.

Source

https://discord.com/channels/389867840406159362/389868418855075840/1130873190936232007

Example Twee

:: Start
<<caller "first">>second<</caller>>

:: User scripts [script]
Macro.add("macro_container", {
  tags: null,
  handler: function () {
    $(this.output).wiki(`args0: ${this.args[0]}` + "\n" +
        `contents: ${this.payload[0].contents}`)
  }
})

:: User widgets [widget]
<<widget "widget_container" container>>
args0: _args[0]
contents: _contents
<</widget>>

<<widget "caller" container>>
Caller contents: _contents
<hr>
Macro_container:
<<macro_container _args[0]>>_contents<</macro_container>>
<hr>
Widget_container:
<<widget_container _args[0]>>_contents<</widget_container>>
<hr>
Caller contents: _contents
<</widget>>

Shenanigan 2

From the Twine Games Discord. See the source, but reproduced here.

Btw, if you swap the order of the macro and widget call, it makes an interesting error:

<<caller>>: error within widget code (Error: cannot execute macro <<macro_container>>: Scripting.parse last index is non-zero at start)

Source

https://discord.com/channels/389867840406159362/389868418855075840/1130877699829805106

Example Twee

:: Start
<<caller "first">>second<</caller>>

:: User scripts [script]
Macro.add("macro_container", {
  tags: null,
  handler: function () {
    $(this.output).wiki(`args0: ${this.args[0]}` + "\n" +
        `contents: ${this.payload[0].contents}`)
  }
})

:: User widgets [widget]
<<widget "widget_container" container>>
args0: _args[0]
contents: _contents
<</widget>>

<<widget "caller" container>>
Caller contents: _contents
<hr>
Widget_container:
<<widget_container _args[0]>>_contents<</widget_container>>
<hr>
Macro_container:
<<macro_container _args[0]>>_contents<</macro_container>>
<hr>
Caller contents: _contents
<</widget>>
tmedwards commented 1 year ago

This is due to recursion.

E.g., minimal working example:

<<widget "callee" container>>
_contents
<</widget>>

<<widget "caller" container>>
<<callee>>_contents<</callee>>
<</widget>>

<<caller>>fnord<</caller>>

What happens is that during the call to <<callee>> it attempts to render the value of its _contents variable. The problem is that value is the string '_contents' that it received from <<caller>>. Meaning that it renders its _contents yielding _contents, which renders its _contents yielding _contents, which…. Eventually, something fails.

The fix is that all _contents variables within a container widget's body should be replaced with the value of its _contents variable before rendering or something in that general vicinity.

tmedwards commented 1 year ago

This is being addressed by a documentation update that warns users against this circumstance.

Resolved by PR #263.