A rule for the bug that formatted a disk

falls-into-subroutine. The code above a label ends without going anywhere and
the label is one something CALLs, so execution walks into the subroutine,
reaches its RET, and returns to whatever the Stack happens to hold - because
nobody called, there is no caller, and it goes somewhere nobody named.

It is worth a rule because the symptom is nowhere near the cause and changes
with the Stack. In CosmOS's monitor it was usually a byte that does not decode,
in the middle of newLine; once it was inside sbfsFormat, and the machine
formatted the disk it had booted from.

Two exemptions, and both had to exist or the rule would have reported well
written code:

A TAIL CALL IS THE SAME SHAPE AND IS FINE. Falling out of one subroutine into
another means the RET returns to the outer caller, which is real. So it only
fires when nothing since the last branch or return was a call target either -
which is the linter's usual trade of precision for being worth reading.

AND osExit NEVER RETURNS. It is how a loaded program gives the machine back, and
every program here ends with it and then writes its helpers underneath. Without
that, twelve well written programs were reported. It is the one name from the
system this tool knows, and the comment says why it is there.

Also SRET, which stopsFallthrough did not list. It returns from a handler
exactly as RET returns from a call, and leaving it out is a gap in every rule
that asks what reaches an instruction. Load bearing rather than tidy: without it
cosmos.asm reports a handler ending in SRET as falling into the routine written
under it.

A first pass over the file collects call targets, because a subroutine is very
often called from further down than it is written.

The corpus reports none of it, which is the point rather than a disappointment,
and the Test Manual now says so - a baseline entry that is absent is otherwise
indistinguishable from a rule that never runs. Checked against the version of
cosmos.asm from before the fix, where it names the line.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E2JrLzFvuFX9fgi1LDRjrW
This commit is contained in:
Anachronaut
2026-09-01 15:43:25 -04:00
co-authored by Claude Opus 5
parent 66be42d7bb
commit c1b3c4c156
3 changed files with 160 additions and 6 deletions
+41 -4
View File
@@ -116,6 +116,27 @@ printf '%s\n' \
' INIA 0d9' \
' CALL somewhere' \
' INIA 0d9' \
' RET' \
' ; ---- Walking into a subroutine instead of calling it ----' \
'walksInto:' \
' INIA 0d1' \
'somewhere:' \
' RET' \
' ; ---- And a tail call, which is the same shape and is fine ----' \
'tailCaller:' \
' INIA 0d2' \
'alsoCalled:' \
' RET' \
' ; ---- And osExit, after which nothing runs ----' \
'stopsHere:' \
' SWI osExit' \
'calledOnly:' \
' RET' \
'theCallsThemselves:' \
' CALL tailCaller' \
' CALL alsoCalled' \
' CALL calledOnly' \
' CALL stopsHere' \
> "$fixture"
"$LINT" "$fixture" > "$output" 2>&1
@@ -162,6 +183,13 @@ expect 53 "carry is already known to be clear"
expect 56 "BRC is always taken because carry is known set"
expect 60 "BRC is never taken because carry is known clear"
expect 61 "BNC is always taken because carry is known clear"
# ---- Walking into a subroutine instead of calling it ----
#
# The code above ends without going anywhere and the label below is one something CALLs, so
# execution reaches a RET that has no caller and returns to whatever the Stack held. That is
# what formatted a disk: CosmOS's monitor had nothing at the end of its command list, so an
# unrecognised word walked into sayPrompt and its RET went into the filesystem's formatter.
expect 78 "execution walks into a subroutine nothing here called"
# ---- And a claim does not survive a call ----
#
@@ -173,14 +201,23 @@ expect 61 "BNC is always taken because carry is known clear"
#
# THE LINE NUMBERS ARE THE REPEATS, not the label above them. Checking the label instead
# passes for free, because a label is never warned about by anything.
# ---- The two shapes that look the same and are not ----
#
# 82 is a TAIL CALL: the run above it began at tailCaller, which is itself called, so the RET
# at the bottom returns to that caller and everything is where it meant to be. Falling out of
# one subroutine into another is an ordinary thing to do and must stay quiet.
#
# 86 follows osExit, which is how a program gives the machine back and never returns. Every
# well written program on this machine ends that way and then writes its helpers underneath,
# so a rule that did not know it would report the entire repository.
quiet=0
for line in 70 73; do
for line in 70 73 82 86; do
if grep -q "^${fixture}:${line}: style:" "$output"; then
FAIL=$((FAIL + 1)); MISSING+=("line $line should be quiet across a CALL")
printf " [%sFAIL%s] line %-3s no claim survives a CALL\n" "$RED" "$RESET" "$line"
FAIL=$((FAIL + 1)); MISSING+=("line $line should have stayed quiet")
printf " [%sFAIL%s] line %-3s should have stayed quiet\n" "$RED" "$RESET" "$line"
else
PASS=$((PASS + 1)); quiet=$((quiet + 1))
printf " [%sok %s] line %-3s no claim survives a CALL\n" "$GREEN" "$RESET" "$line"
printf " [%sok %s] line %-3s stays quiet\n" "$GREEN" "$RESET" "$line"
fi
done