ID:2390064
 
Resolved
Very old bug: The continue keyword in a do-while loop skipped the condition.
BYOND Version:511
Operating System:Windows 7 Ultimate 64-bit
Web Browser:Firefox 61.0
Applies to:Dream Maker
Status: Resolved (512.1445)

This issue has been resolved.
Descriptive Problem Summary:

Maybe not a bug(?), but certainly unexpected behavior. If a 'continue' is encountered during a do-while loop, the while condition is not checked, leading to infinite loop hazards. Neither Javascript nor C++ (the first two other languages I could think of with do-while loops) exhibit this behavior.

Code Snippet (if applicable) to Reproduce Problem:
This is toy code, simplified from an example which was causing a real server crash due to a sleeping infinite loop which consumed unbounded memory:

/client/verb/Good()
src << "before"
var/keep_going = TRUE
while(keep_going)
keep_going = FALSE
continue
src << "after"

/client/verb/Bad()
src << "before"
var/keep_going = TRUE
do
keep_going = FALSE
continue
while(keep_going)
src << "after"


Expected Results:

Both verbs print "before" and "after" immediately.

Actual Results:

"Good" works, but "Bad" triggers infinite loop detection and never prints "after".

Does the problem occur:
Every time? Or how often? Every time.
In other games? In a test project.
In other user accounts? Unknown.
On other computers? Unknown.

When does the problem NOT occur?

Ordinary 'while' loops work fine.

Did the problem NOT occur in any earlier versions? If so, what was the last version that worked? (Visit http://www.byond.com/download/build to download old versions for testing.)

Tested on 511.1385 and 512.1442, both exhibit this behavior.

Workarounds:

Replace the do-while loop with an ordinary while loop.
DM has no forward progress guarantee, unlike C++.

The continue statement is expected to move the interpreter to the entry point of the current loop. As the continue statement is a backward jump, one would reasonably expect that it would not jump forward.

Internally, a do while and a while loop are the same basic structure, but the position and logical value of the conditional jump is reversed.

while([condition])
[body]


<entry point> //continue jumps to entry point
[condition] (implicit push)
lnot pop cndjmp exit
[body]
<exit point> //break jumps to exit point


do
[body]
while([condition])


<entry point> //continue jumps to entry point
[body]
[condition] (implicit push)
pop cndjmp entry
<exit point> //break jumps to exit point


Essentially, when you are in a do while loop, the condition cannot be checked after a continue, because it is ahead of the interpreter, and continue is by definition a backward, unconditional jump.

The only solution to this bug is to implement a progress guarantee in DM, which means that every execution path that does not result in progress should be undefined. This makes writing DM harder, and it invalidates an awful lot of patterns that have become ostensibly axiomatic in DM.

Even then, making the DM compiler balk at every potentially infinite loop would probably be more effort than it is worth, and piss right in the cornflakes of an awful lot of people for some time.
I actually think it wouldn't cause too much fuss to change continue to jump to the while() condition. The various jump points are collected along the way and don't get filled in until the loop ends, so this is a relatively simple fix. I'm not aware of any code that currently expects the continue to skip the condition.
Lummox JR resolved issue with message:
Very old bug: The continue keyword in a do-while loop skipped the condition.