210 lines
7.5 KiB
C
210 lines
7.5 KiB
C
|
|
/**
|
||
|
|
* @file value_pool.c
|
||
|
|
* @brief Tests that creating a name inside a scope costs the value pool nothing.
|
||
|
|
*
|
||
|
|
* This is the defect a Breakout written against the interpreter died of after
|
||
|
|
* twenty-five seconds, and nothing in either corpus could have found it: the
|
||
|
|
* pool holds 4096 values, so it takes *thousands* of scope entries to see, and
|
||
|
|
* nothing else here runs that long.
|
||
|
|
*
|
||
|
|
* The cause was that scope exit returns the variable *slot* and not its storage.
|
||
|
|
* akbasic_runtime_prev_environment() marks the variable unused,
|
||
|
|
* akbasic_runtime_new_variable() memsets the slot it hands back -- clearing
|
||
|
|
* `values` -- and akbasic_variable_init() therefore drew fresh slots for a
|
||
|
|
* variable whose old ones were still counted. Every GOSUB that created a local
|
||
|
|
* leaked, with no diagnostic until the pool ran dry on whichever line happened
|
||
|
|
* to be unlucky.
|
||
|
|
*
|
||
|
|
* A scalar now lives in the variable itself, so the pool is not involved at all.
|
||
|
|
* Two of the counts below are past the old 4096 ceiling on purpose -- under it,
|
||
|
|
* they would pass against the broken interpreter too. The third takes the
|
||
|
|
* opposite approach and asks for the whole pool after only a few scopes, which
|
||
|
|
* is cheaper and sharper: one leaked slot and it has nowhere to go.
|
||
|
|
*/
|
||
|
|
|
||
|
|
#include <string.h>
|
||
|
|
|
||
|
|
#include <akbasic/error.h>
|
||
|
|
#include <akbasic/runtime.h>
|
||
|
|
|
||
|
|
#include "harness.h"
|
||
|
|
#include "testutil.h"
|
||
|
|
|
||
|
|
/** @brief Run a program to completion, bounded so a hang fails rather than waits. */
|
||
|
|
static akerr_ErrorContext AKERR_NOIGNORE *run_program(const char *source)
|
||
|
|
{
|
||
|
|
PREPARE_ERROR(errctx);
|
||
|
|
|
||
|
|
PASS(errctx, harness_start(NULL));
|
||
|
|
PASS(errctx, akbasic_runtime_load(&HARNESS_RUNTIME, source));
|
||
|
|
PASS(errctx, akbasic_runtime_start(&HARNESS_RUNTIME, AKBASIC_MODE_RUN));
|
||
|
|
PASS(errctx, akbasic_runtime_run(&HARNESS_RUNTIME, 2000000));
|
||
|
|
SUCCEED_RETURN(errctx);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @brief 6,000 GOSUBs, each creating a local, cost nothing.
|
||
|
|
*
|
||
|
|
* TODO.md section 6 item 30's reduction, unchanged. It used to fail at
|
||
|
|
* `LOC# = 1` on the 4091st call with "Array of 1 elements does not fit in the
|
||
|
|
* 0 remaining value slots" -- naming the line that was unlucky rather than the
|
||
|
|
* line that was wrong, which is most of why it cost an evening to find.
|
||
|
|
*/
|
||
|
|
static void test_scoped_scalar_costs_nothing(void)
|
||
|
|
{
|
||
|
|
TEST_REQUIRE_OK(run_program("10 X# = 0\n"
|
||
|
|
"20 FOR T# = 1 TO 6000\n"
|
||
|
|
"30 GOSUB SUBA\n"
|
||
|
|
"40 NEXT T#\n"
|
||
|
|
"50 PRINT \"OK \" + X#\n"
|
||
|
|
"60 END\n"
|
||
|
|
"70 LABEL SUBA\n"
|
||
|
|
"80 LOC# = 1\n"
|
||
|
|
"90 X# = X# + LOC#\n"
|
||
|
|
"100 RETURN\n"));
|
||
|
|
TEST_REQUIRE_STR(HARNESS_OUTPUT, "OK 6000\n");
|
||
|
|
harness_stop();
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @brief A `FOR` counter is a name like any other, and costs nothing either.
|
||
|
|
*
|
||
|
|