Skip to content

Allows early continue/break to error - #475

Open
LemonInTheDark wants to merge 6 commits into
SpaceManiac:masterfrom
LemonInTheDark:i-come-from-scientists-and-athiests-and-white-men-who-would-kill-god
Open

Allows early continue/break to error#475
LemonInTheDark wants to merge 6 commits into
SpaceManiac:masterfrom
LemonInTheDark:i-come-from-scientists-and-athiests-and-white-men-who-would-kill-god

Conversation

@LemonInTheDark

@LemonInTheDark LemonInTheDark commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Explanation of control flow parsing because it took me a bit to get

The idea here is to walk the code tree, mark blocks that return/continue/break, and then infect up to parent blocks.

We need to ensure that we only "elevate" a control flow case if it ALWAYS happens when we are executed. We use fuzzy to do this, we're fuzzy if we are unsure, unfuzzy if we aren't.

There's two types of evaluating (and so two patterns)

One type, switches/if blocks, we assume all cases are true, and negate them as we go through all our cases. If all cases have something, we do, if not we don't. If we sometimes don't execute (our case doesn't pass, there's no else), then we're fuzzy and so don't matter to our parent.

The other type is evaluating flat blocks. for these, we just run through them, and if we ever terminate early, mark ourselves dirty.

Fuzzy cases are not allowed to make their parent true, because they do not reflect all possibilities.

What I changed

What I did here was:

  • fix fuzzy carrying up into flat blocks, which doesn't make any sense.
  • remove fuzzy setting from break/continue, this was needed because return was handled special by flat block merging, but it isn't anymore so...
  • guaranteed loops (do whiles, for iterator loops with constant args) will pass returns up to their parent
  • added a bunch of tests to try and catch this stuff

God I hope there's no false positives

I did a survey of some ss13 repos (goon, monkey's eris fork, monkey, tg, bubber, paradise, nebula) and didn't find any false positives.
I did find 2 early continues this catches (an obvious one on monke and the divine light of ifdefs confusing people on goon)

The humble false negative

We don't handle labels correctly, which could lead to continues/breaks being relevant to their parents. I'm going to ignore this because it's stupid and I don't want to have to think about it.

The idea here is to walk the code tree, mark blocks that
return/continue/break, and then infect up to parent blocks.

We need to ensure that we only "elevate" a control flow case if it
ALWAYS happens when we are executed. We use fuzzy to do this, we're
fuzzy if we are unsure, unfuzzy if we aren't.

There's two types of evaluating (and so two patterns)

One type, switches/if blocks, we assume all cases are true, and negate
them as we go through all our cases. If all cases have something, we do,
if not we don't. If we sometimes don't execute (our case doesn't pass,
there's no else), then we're fuzzy and so don't matter to our parent.

The other type is evaluating flat blocks. for these, we just run through
them, and if we ever terminate early, mark ourselves dirty.

Fuzzy cases are not allowed to make their parent true, because they
do not reflect all possibilities.

What I did here was:
- fix fuzzy carrying up into flat blocks, which doesn't make any sense.
- remove fuzzy setting from break/continue, this was needed because
  return was handled special by flat block merging, but it isn't anymore
  so...
- added a bunch of tests to try and catch this stuff

I did a survey of some ss13 repos (goon, monkey's eris fork, monkey, tg,
bubber) and didn't find any false positives.
I did find 2 early continues this catches, and one wild one involving a
`for(var/i in const to const)` case which it catches
@LemonInTheDark LemonInTheDark changed the title Mucks with control flow parsing somewhat Allows early continue/break to error Aug 3, 2026
LemonInTheDark and others added 3 commits August 3, 2026 20:23
Guarenteed loops, blocks we are sure will run, should pass their returns
up the chain so for(var/i in 1 to 2) {return} ... marks correctly. They
however, should not pass up control flow stuff that's scoped TO THAT
LOOP like continues and breaks.

I will note it's possible this will produce very rare false negatives with labeled
break/continues but that shit's evil and I'm not interested in engaging
with it
@SpaceManiac

Copy link
Copy Markdown
Owner

This is a false positive:

/proc/test()
    for(var/i in 1 to 2)
        if(prob(100))
            continue
        return
    return

Says the second return is unreachable, but it's not.

Goon has a detection like this: https://github.com/goonstation/goonstation/blob/1f7b567faba9790b8b8500575e88d5b9bf2be803/code/modules/sound/managed_positional_sound.dm#L99-L111

@LemonInTheDark

Copy link
Copy Markdown
Contributor Author

fuck you're right

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants