Repository: gren-lang/compiler
Found against: gren 0.6.6, node 22
Summary
Generate.Node.sandwich wraps the whole generated program in a try that
catches everything and calls console.error(e). It never sets an exit status,
so a program that died reports success:
$ node app
Error: Cannot perform mod 0. Division by zero error.
at _Debug_crash (…)
…
$ echo $?
0
Anything that decides whether a run worked by looking at the exit status — a
shell script, make, CI, a supervisor, a test harness, && — is told the
program succeeded. A crash in production is invisible to the thing whose job is
to notice it.
Reproduction
module Main exposing (main)
import Init
import Math
import Node
import Stream
import Task exposing (Task)
main : Node.SimpleProgram a
main =
Node.defineSimpleProgram
(\env -> Node.endSimpleProgram (emit env))
emit : Node.Environment -> Task Never {}
emit env =
Stream.writeLineAsBytes (String.fromInt (Math.modBy 0 7)) env.stdout
|> Task.map (\_ -> {})
|> Task.onError (\_ -> Task.succeed {})
$ gren make Main --output=app
$ node app > /dev/null 2>&1 ; echo $?
0
Any other crash reaches the same place: Debug.todo, a failed Debug.log on a
cyclic value, a RangeError from deep non-tail recursion.
Cause
compiler/src/Generate/Node.hs:
sandwich :: Name.Name -> B.Builder -> B.Builder
sandwich moduleName javascript =
let name = Name.toBuilder moduleName
in [r|#!/usr/bin/env node
try {
|]
<> javascript
<> …
<> [r|.init({});
}
catch (e)
{
console.error(e);
}
|]
The catch is what turns an uncaught exception — which node would otherwise
report and exit 1 for — into a printed message and a clean exit.
Repository:
gren-lang/compilerFound against:
gren0.6.6, node 22Summary
Generate.Node.sandwichwraps the whole generated program in atrythatcatches everything and calls
console.error(e). It never sets an exit status,so a program that died reports success:
Anything that decides whether a run worked by looking at the exit status — a
shell script,
make, CI, a supervisor, a test harness,&&— is told theprogram succeeded. A crash in production is invisible to the thing whose job is
to notice it.
Reproduction
Any other crash reaches the same place:
Debug.todo, a failedDebug.logon acyclic value, a
RangeErrorfrom deep non-tail recursion.Cause
compiler/src/Generate/Node.hs:The
catchis what turns an uncaught exception — which node would otherwisereport and exit
1for — into a printed message and a clean exit.