/* Native stack exhaustion must return an error, unwind, and permit reuse. * Parse/open on the main thread, then execute the same rtx on smaller stacks. */ #include #include #include #include #include #include "tap.h" #if defined(_WIN32) #define TEST_CSTACK #elif defined(__HAIKU__) && defined(HAVE_FIND_THREAD) && defined(HAVE_GET_THREAD_INFO) #define TEST_CSTACK #elif defined(__APPLE__) && defined(HAVE_PTHREAD) && defined(HAVE_PTHREAD_GET_STACKADDR_NP) && defined(HAVE_PTHREAD_GET_STACKSIZE_NP) #define TEST_CSTACK #elif defined(__OpenBSD__) && defined(HAVE_PTHREAD) && defined(HAVE_PTHREAD_STACKSEG_NP) #define TEST_CSTACK #elif defined(__linux__) && defined(HAVE_PTHREAD) && defined(HAVE_PTHREAD_GETATTR_NP) && defined(HAVE_PTHREAD_ATTR_GETSTACK) && defined(HAVE_PTHREAD_ATTR_GETGUARDSIZE) #define TEST_CSTACK #elif (defined(__FreeBSD__) || defined(__NetBSD__)) && defined(HAVE_PTHREAD) && defined(HAVE_PTHREAD_ATTR_GET_NP) && defined(HAVE_PTHREAD_ATTR_GETSTACK) && defined(HAVE_PTHREAD_ATTR_GETGUARDSIZE) #define TEST_CSTACK #endif #if defined(TEST_CSTACK) # if defined(_WIN32) # include # else # include # include # include # if !defined(MAP_ANONYMOUS) # define MAP_ANONYMOUS MAP_ANON # endif # endif #endif #if defined(TEST_CSTACK) static int reenter (hawk_rtx_t* rtx, const hawk_fnc_info_t* fi) { hawk_val_t* v; (void)fi; v = hawk_rtx_callwithbcstr(rtx, "callback", HAWK_NULL, 0); if (!v) return -1; hawk_rtx_setretval(rtx, v); hawk_rtx_refdownval(rtx, v); return 0; } struct job_t { hawk_rtx_t* rtx; const char* name; /* NULL exercises hawk_rtx_loop() */ int require_native; int passed; }; static void* exhaust (void* data) { struct job_t* job = (struct job_t*)data; hawk_val_t* v; v = job->name? hawk_rtx_callwithbcstr(job->rtx, job->name, HAWK_NULL, 0): hawk_rtx_loop(job->rtx); job->passed = !v && hawk_rtx_geterrnum(job->rtx) == HAWK_ESTACK && (!job->require_native || strstr(hawk_rtx_geterrbmsg(job->rtx), "native C stack limit reached") != HAWK_NULL); if (!job->passed) printf("# unexpected result: %s\n", hawk_rtx_geterrbmsg(job->rtx)); if (v) hawk_rtx_refdownval(job->rtx, v); return HAWK_NULL; } #if defined(_WIN32) static DWORD WINAPI thread_main (LPVOID data) { exhaust(data); return 0; } #endif static int on_stack (struct job_t* job, size_t size) { #if defined(_WIN32) HANDLE thread = CreateThread(HAWK_NULL, size, thread_main, job, STACK_SIZE_PARAM_IS_A_RESERVATION, HAWK_NULL); DWORD n; if (!thread) return -1; n = WaitForSingleObject(thread, INFINITE); CloseHandle(thread); return n == WAIT_OBJECT_0? 0: -1; #elif defined(__HAIKU__) pthread_attr_t attr; pthread_t thread; int n, started = 0; n = pthread_attr_init(&attr); if (n == 0) { /* On Haiku, passing an existing mmap() area to pthread_attr_setstack() * prevents the thread entry point from running. Let the kernel allocate * an exact-sized stack instead. * * [NOTE] * This branch may work on other platforms that support pthread_attr_setstacksize. * In posix specification, the given size to pthread_attr_setstacksize * is the minimum size. so the actual stack may be larger than requested. * If the exact-sized stack is allocated, the test becomes less deterministic */ n = pthread_attr_setstacksize(&attr, size); if (n == 0) { n = pthread_create(&thread, &attr, exhaust, job); started = (n == 0); } pthread_attr_destroy(&attr); if (n == 0) n = pthread_join(thread, HAWK_NULL); } return started && n == 0? 0: -1; #else pthread_attr_t attr; pthread_t thread; void* stack; long page = sysconf(_SC_PAGESIZE); int n, started = 0; if (page <= 0) return -1; /* Supply the exact allocation: pthreads may otherwise reuse a cached, * larger stack and turn the small-stack regression into a false pass. */ stack = mmap(HAWK_NULL, size + 2 * page, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); if (stack == MAP_FAILED) return -1; if (mprotect((char*)stack + page, size, PROT_READ | PROT_WRITE) != 0) { munmap(stack, size + 2 * page); return -1; } n = pthread_attr_init(&attr); if (n == 0) { n = pthread_attr_setstack(&attr, (char*)stack + page, size); if (n == 0) { n = pthread_create(&thread, &attr, exhaust, job); started = (n == 0); } pthread_attr_destroy(&attr); if (n == 0) n = pthread_join(thread, HAWK_NULL); } /* A failed join cannot prove that the worker stopped using this stack. */ if (!started || n == 0) munmap(stack, size + 2 * page); return n == 0? 0: -1; #endif } static void check_reuse (hawk_rtx_t* rtx) { hawk_val_t* v; hawk_int_t n = 0; v = hawk_rtx_callwithbcstr(rtx, "answer", HAWK_NULL, 0); OK(v && hawk_rtx_valtoint(rtx, v, &n) == 0 && n == 42, "rtx reusable on main thread after exhaustion"); if (v) hawk_rtx_refdownval(rtx, v); } #endif int main (void) { #if defined(TEST_CSTACK) static const char prefix[] = "@pragma stack_limit 1048576\n" "function direct() { @local s; s = sprintf(\"value-%d\", 1); return 1 + direct(); }\n" "function mutual() { return other(); }\n" "function other() { return mutual(); }\n" "function callback() { return host_reenter(); }\n" "function answer() { return 42; }\n" "BEGIN { direct(); }\n" "function expression() { @local n; n = 1; return n"; static const char* names[] = { "direct", "mutual", "callback", "expression", HAWK_NULL }; static const size_t sizes[] = { 128 * 1024, 256 * 1024, 512 * 1024 }; hawk_t* hawk; hawk_rtx_t* rtx = HAWK_NULL; hawk_parsestd_t in[2]; hawk_fnc_bspec_t spec; hawk_oow_t unlimited = 0; struct job_t job; char* src; size_t i, j, len; int n; no_plan(); hawk = hawk_openstd(0, HAWK_NULL); OK(hawk != HAWK_NULL, "open interpreter"); if (!hawk) return exit_status(); /* Make depth/value-stack limits incapable of masking native exhaustion. */ hawk_setopt(hawk, HAWK_OPT_DEPTH_RECURS_RUN, &unlimited); memset(&spec, 0, sizeof(spec)); spec.impl = reenter; OK(hawk_addfncwithbcstr(hawk, "host_reenter", &spec) != HAWK_NULL, "register host callback"); src = (char*)malloc(sizeof(prefix) + 12000); OK(src != HAWK_NULL, "allocate expression source"); if (!src) goto done; strcpy(src, prefix); len = strlen(src); /* Not constant-foldable; evaluates recursively without function calls. */ for (i = 0; i < 5000; i++) { src[len++] = '+'; src[len++] = 'n'; } strcpy(src + len, "; }\n"); memset(in, 0, sizeof(in)); in[0].type = HAWK_PARSESTD_BCS; in[0].u.bcs.ptr = src; in[0].u.bcs.len = strlen(src); in[1].type = HAWK_PARSESTD_NULL; n = hawk_parsestd(hawk, in, HAWK_NULL); OK(n >= 0, "parse recursive functions and long expression"); free(src); if (n < 0) { printf("# %s\n", hawk_geterrbmsg(hawk)); goto done; } rtx = hawk_rtx_openstd(hawk, 0, HAWK_T("t-011"), HAWK_NULL, HAWK_NULL, HAWK_NULL); OK(rtx != HAWK_NULL, "open runtime on main thread"); if (!rtx) goto done; job.rtx = rtx; job.name = "direct"; job.require_native = 0; job.passed = 0; exhaust(&job); OK(job.passed, "stack exhaustion on main thread"); check_reuse(rtx); job.require_native = 1; for (i = 0; i < sizeof(sizes) / sizeof(sizes[0]); i++) { for (j = 0; j < sizeof(names) / sizeof(names[0]); j++) { job.name = names[j]; job.passed = 0; printf("# stack=%lu entry=%s\n", (unsigned long)sizes[i], job.name? job.name: "BEGIN"); n = on_stack(&job, sizes[i]); OK(n == 0, "execute on bounded worker stack"); OK(n == 0 && job.passed, "native stack exhausted gracefully"); check_reuse(rtx); } } /* An entry with less than the reserve must refuse even trivial work. */ job.name = "answer"; job.passed = 0; OK(on_stack(&job, 64 * 1024) == 0 && job.passed, "reject entry without stack headroom"); check_reuse(rtx); done: if (rtx) hawk_rtx_close(rtx); hawk_close(hawk); return exit_status(); #else static char src[] = "function direct() { return 1 + direct(); }\n" "function answer() { return 42; }\n"; hawk_t* hawk; hawk_rtx_t* rtx = HAWK_NULL; hawk_parsestd_t in[2]; hawk_val_t* v; hawk_oow_t limit = 32; hawk_int_t answer = 0; int n; no_plan(); hawk = hawk_openstd(0, HAWK_NULL); OK(hawk != HAWK_NULL, "open interpreter for logical-depth fallback"); if (!hawk) return exit_status(); OK(hawk_setopt(hawk, HAWK_OPT_DEPTH_RECURS_RUN, &limit) == 0, "set logical recursion limit"); memset(in, 0, sizeof(in)); in[0].type = HAWK_PARSESTD_BCS; in[0].u.bcs.ptr = src; in[0].u.bcs.len = sizeof(src) - 1; in[1].type = HAWK_PARSESTD_NULL; n = hawk_parsestd(hawk, in, HAWK_NULL); OK(n >= 0, "parse fallback test functions"); if (n < 0) goto fallback_done; rtx = hawk_rtx_openstd(hawk, 0, HAWK_T("t-011"), HAWK_NULL, HAWK_NULL, HAWK_NULL); OK(rtx != HAWK_NULL, "open runtime for logical-depth fallback"); if (!rtx) goto fallback_done; v = hawk_rtx_callwithbcstr(rtx, "direct", HAWK_NULL, 0); OK(!v && hawk_rtx_geterrnum(rtx) == HAWK_EBLKNST, "logical recursion limit stops recursive evaluation"); if (v) hawk_rtx_refdownval(rtx, v); v = hawk_rtx_callwithbcstr(rtx, "answer", HAWK_NULL, 0); OK(v && hawk_rtx_valtoint(rtx, v, &answer) == 0 && answer == 42, "runtime reusable after logical-depth exhaustion"); if (v) hawk_rtx_refdownval(rtx, v); fallback_done: if (rtx) hawk_rtx_close(rtx); hawk_close(hawk); return exit_status(); #endif }