132 lines
4.2 KiB
C
132 lines
4.2 KiB
C
/* process stack overflow must be reported, not swallowed.
|
|
*
|
|
* HAK_STACK_PUSH() detects the overflow, sets HAK_ESTKOVRFLW and stores -1 in
|
|
* hak->abort_req; the interpreter loop then takes "goto oops" and answers a
|
|
* failure. That path is only reachable while abort_req is a signed type - when
|
|
* it was hak_uint8_t the -1 became 255, the "abort_req < 0" test could never
|
|
* be true, and the overflow fell through to the ordinary "abort_req > 0" break
|
|
* instead. hak_execute() then answered success and the script simply stopped
|
|
* running with nothing reported.
|
|
*
|
|
* This is checked here rather than in t/ as a script because neither script
|
|
* harness can express it: run.sh fails a test as soon as the output contains
|
|
* an ERROR: line, and err.sh insists the error be reported at the line its
|
|
* ##ERROR: marker sits on, whereas a stack overflow carries no source
|
|
* location and is reported at [0,0]. Only a C test can assert on the value
|
|
* hak_execute() answers, which is the part that regressed.
|
|
*
|
|
* The nesting depth is what drives the operand stack: evaluating
|
|
* (+ 1 (+ 1 ... 0)) has to hold every pending left operand at once. The
|
|
* process stack size is set explicitly here because there is no single
|
|
* default to rely on - HAK_DFL_PROCSTK_SIZE is 5000, bin/hak asks for 600,
|
|
* and exec.c clamps whatever it is given up to a floor of 192. */
|
|
|
|
#include <hak.h>
|
|
#include "tap.h"
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
|
|
/* slots per nesting level are an implementation detail, so keep a wide margin
|
|
* on both sides of the limit rather than probing for the exact threshold */
|
|
#define STK_SLOTS 192
|
|
#define DEEP_DEPTH 2000
|
|
#define SHALLOW_DEPTH 10
|
|
|
|
static int on_cnode (hak_t* hak, hak_cnode_t* obj)
|
|
{
|
|
return hak_compile(hak, obj, 0);
|
|
}
|
|
|
|
/* build "x := (+ 1 (+ 1 ... 0))" nested to the given depth */
|
|
static char* make_src (int depth)
|
|
{
|
|
hak_oow_t capa;
|
|
char* buf;
|
|
char* p;
|
|
int i;
|
|
|
|
capa = (hak_oow_t)depth * 8 + 64;
|
|
buf = (char*)malloc(capa);
|
|
if (!buf) return HAK_NULL;
|
|
|
|
p = buf;
|
|
memcpy(p, "x := ", 5); p += 5;
|
|
for (i = 0; i < depth; i++) { memcpy(p, "(+ 1 ", 5); p += 5; }
|
|
*p++ = '0';
|
|
for (i = 0; i < depth; i++) *p++ = ')';
|
|
*p++ = '\n';
|
|
*p = '\0';
|
|
|
|
return buf;
|
|
}
|
|
|
|
/* returns 0 if the run completed, -1 if the fixture itself could not be built */
|
|
static int run_at_depth (int depth, int expect_overflow)
|
|
{
|
|
hak_t* hak;
|
|
hak_bitmask_t trait;
|
|
hak_oow_t stksize;
|
|
hak_oop_t retv;
|
|
char* src;
|
|
int errnum;
|
|
int rc = -1;
|
|
|
|
src = make_src(depth);
|
|
OK (src != HAK_NULL, "source built");
|
|
if (!src) return -1;
|
|
|
|
hak = hak_openstd(0, HAK_NULL);
|
|
OK (hak != HAK_NULL, "instantiation");
|
|
if (!hak) goto done;
|
|
|
|
hak_getoption(hak, HAK_OPT_TRAIT, &trait);
|
|
trait |= HAK_TRAIT_LANG_ENABLE_EOL;
|
|
hak_setoption(hak, HAK_OPT_TRAIT, &trait);
|
|
|
|
stksize = STK_SLOTS;
|
|
OK (hak_setoption(hak, HAK_OPT_PROCSTK_SIZE, &stksize) == 0, "process stack size");
|
|
|
|
OK (hak_ignite(hak, 0) == 0, "ignition");
|
|
OK (hak_addbuiltinprims(hak) == 0, "builtin primitives");
|
|
OK (hak_attachcciostdwithbcstr(hak, HAK_NULL) == 0, "source input stream");
|
|
OK (hak_attachudiostdwithbcstr(hak, "", "") == 0, "user data streams");
|
|
OK (hak_beginfeed(hak, on_cnode) == 0, "begin feed");
|
|
OK (hak_feedbchars(hak, src, strlen(src)) == 0, "feed the script");
|
|
OK (hak_endfeed(hak) == 0, "end feed");
|
|
|
|
retv = hak_execute(hak);
|
|
errnum = (int)hak_geterrnum(hak);
|
|
|
|
if (expect_overflow)
|
|
{
|
|
printf("# depth=%d retv=%s errnum=%d (%s)\n", depth,
|
|
retv? "value": "HAK_NULL", errnum,
|
|
retv? "-": hak_geterrbmsg(hak));
|
|
/* the assertion that regressed: a swallowed overflow answers a value */
|
|
OK (retv == HAK_NULL, "an overflowing script fails rather than answering a value");
|
|
OK (errnum == HAK_ESTKOVRFLW, "the failure is reported as HAK_ESTKOVRFLW");
|
|
}
|
|
else
|
|
{
|
|
if (!retv) printf("# depth=%d unexpected failure: [%d] %s\n", depth, errnum, hak_geterrbmsg(hak));
|
|
OK (retv != HAK_NULL, "a script within the stack limit still runs");
|
|
}
|
|
|
|
rc = 0;
|
|
hak_close(hak);
|
|
|
|
done:
|
|
free(src);
|
|
return rc;
|
|
}
|
|
|
|
int main (int argc, char* argv[])
|
|
{
|
|
no_plan();
|
|
/* the control comes first: if this one failed the deep case would pass
|
|
* for the wrong reason, since any failure at all answers HAK_NULL */
|
|
run_at_depth(SHALLOW_DEPTH, 0);
|
|
run_at_depth(DEEP_DEPTH, 1);
|
|
return exit_status();
|
|
}
|