Skip to content

Commit

Permalink
Add DUK_USE_NATIVE_STACK_CHECK macro support
Browse files Browse the repository at this point in the history
* Add DUK_USE_NATIVE_STACK_CHECK config option.

* Add internal duk_native_stack_check() helper which checks and
  throws if DUK_USE_NATIVE_STACK_CHECK() indicates we're out of
  stack space.

* Add initial call sites for duk_native_stack_check().  These are
  in addition to existing recursion limit checks.

* Add stack check example to cmdline example and 'duk' build on
  Linux.  Disabled by default for now.
  • Loading branch information
svaarala committed Jul 1, 2019
1 parent 4ca1b3b commit 3a67aac
Show file tree
Hide file tree
Showing 11 changed files with 111 additions and 6 deletions.
2 changes: 2 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ ifdef SYSTEMROOT # Windows
# Skip fancy (linenoise)
else
CCOPTS_SHARED += -DDUK_CMDLINE_FANCY
#CCOPTS_SHARED += -DDUK_CMDLINE_PTHREAD_STACK_CHECK
endif
CCOPTS_SHARED += -DDUK_CMDLINE_ALLOC_LOGGING
CCOPTS_SHARED += -DDUK_CMDLINE_ALLOC_TORTURE
Expand Down Expand Up @@ -191,6 +192,7 @@ CCOPTS_DUKLOW += -DDUK_ALLOC_POOL_TRACK_WASTE # quite fast, but not free so dis
ifdef SYSTEMROOT # Windows
CCLIBS = -lm -lws2_32
else
#CCLIBS = -lm -lpthread
CCLIBS = -lm
endif

Expand Down
22 changes: 22 additions & 0 deletions config/config-options/DUK_USE_NATIVE_STACK_CHECK.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
define: DUK_USE_NATIVE_STACK_CHECK
introduced: 2.4.0
default: false
tags:
- portability
- execution
description: >
Provide a macro hook to check for available native stack space for the
currently executing native thread. The macro must evaluate to zero if
there is enough stack space available and non-zero otherwise; a RangeError
will then be thrown.
The definition of "enough space" depends on the target platform and the
compiler because the size of native stack frames cannot be easily known
in advance. As a relatively safe estimate, one can check for 8kB of
available stack.
Duktape doesn't call this macro for every internal native call. The macro
is called in code paths that are involved in potentially unlimited
recursion (such as making Ecmascript/native function calls, invoking
getters and Proxy traps, and resolving Proxy chains) and code paths
requiring a lot of stack space temporarily.
52 changes: 51 additions & 1 deletion examples/cmdline/duk_cmdline.c
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
/*
* Command line execution tool. Useful for test cases and manual testing.
* Also demonstrates some basic integration techniques.
*
* Optional features:
* Optional features include:
*
* - To enable print()/alert() bindings, define DUK_CMDLINE_PRINTALERT_SUPPORT
* and add extras/print-alert/duk_print_alert.c to compilation.
Expand Down Expand Up @@ -46,6 +47,11 @@
#endif
#endif

#if defined(DUK_CMDLINE_PTHREAD_STACK_CHECK)
#define _GNU_SOURCE
#include <pthread.h>
#endif

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
Expand Down Expand Up @@ -1563,3 +1569,47 @@ int main(int argc, char *argv[]) {
fflush(stderr);
exit(1);
}

/* Example of how a native stack check can be implemented in a platform
* specific manner for DUK_USE_NATIVE_STACK_CHECK(). This example is for
* (Linux) pthreads, and rejects further native recursion if less than
* 16kB stack is left (conservative).
*/
#if defined(DUK_CMDLINE_PTHREAD_STACK_CHECK)
int duk_cmdline_stack_check(void) {
pthread_attr_t attr;
void *stackaddr;
size_t stacksize;
char *ptr;
char *ptr_base;
ptrdiff_t remain;

(void) pthread_getattr_np(pthread_self(), &attr);
(void) pthread_attr_getstack(&attr, &stackaddr, &stacksize);
ptr = (char *) &stacksize; /* Rough estimate of current stack pointer. */
ptr_base = (char *) stackaddr;
remain = ptr - ptr_base;

/* HIGH ADDR ----- stackaddr + stacksize
* |
* | stack growth direction
* v -- ptr, approximate used size
*
* LOW ADDR ----- ptr_base, end of stack (lowest address)
*/

#if 0
fprintf(stderr, "STACK CHECK: stackaddr=%p, stacksize=%ld, ptr=%p, remain=%ld\n",
stackaddr, (long) stacksize, (void *) ptr, (long) remain);
fflush(stderr);
#endif
if (remain < 16384) {
return 1;
}
return 0; /* 0: no error, != 0: throw RangeError */
}
#else
int duk_cmdline_stack_check(void) {
return 0;
}
#endif
1 change: 1 addition & 0 deletions src-input/duk_js.h
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ DUK_INTERNAL_DECL void duk_js_push_closure(duk_hthread *thr,
duk_bool_t add_auto_proto);

/* call handling */
DUK_INTERNAL_DECL void duk_native_stack_check(duk_hthread *thr);
DUK_INTERNAL_DECL duk_int_t duk_handle_call_unprotected(duk_hthread *thr, duk_idx_t idx_func, duk_small_uint_t call_flags);
DUK_INTERNAL_DECL duk_int_t duk_handle_call_unprotected_nargs(duk_hthread *thr, duk_idx_t nargs, duk_small_uint_t call_flags);
DUK_INTERNAL_DECL duk_int_t duk_handle_safe_call(duk_hthread *thr, duk_safe_call_function func, void *udata, duk_idx_t num_stack_args, duk_idx_t num_stack_res);
Expand Down
15 changes: 14 additions & 1 deletion src-input/duk_js_call.c
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,17 @@
* Limit check helpers.
*/

/* Check native stack space if DUK_USE_NATIVE_STACK_CHECK() defined. */
DUK_INTERNAL void duk_native_stack_check(duk_hthread *thr) {
#if defined(DUK_USE_NATIVE_STACK_CHECK)
if (DUK_USE_NATIVE_STACK_CHECK() != 0) {
DUK_ERROR_RANGE(thr, DUK_STR_NATIVE_STACK_LIMIT);
}
#else
DUK_UNREF(thr);
#endif
}

/* Allow headroom for calls during error augmentation (see GH-191).
* We allow space for 10 additional recursions, with one extra
* for, e.g. a print() call at the deepest level, and an extra
Expand All @@ -59,14 +70,16 @@ DUK_LOCAL DUK_NOINLINE void duk__call_c_recursion_limit_check_slowpath(duk_hthre
#endif

DUK_D(DUK_DPRINT("call prevented because C recursion limit reached"));
DUK_ERROR_RANGE(thr, DUK_STR_C_CALLSTACK_LIMIT);
DUK_ERROR_RANGE(thr, DUK_STR_NATIVE_STACK_LIMIT);
DUK_WO_NORETURN(return;);
}

DUK_LOCAL DUK_ALWAYS_INLINE void duk__call_c_recursion_limit_check(duk_hthread *thr) {
DUK_ASSERT(thr->heap->call_recursion_depth >= 0);
DUK_ASSERT(thr->heap->call_recursion_depth <= thr->heap->call_recursion_limit);

duk_native_stack_check(thr);

/* This check is forcibly inlined because it's very cheap and almost
* always passes. The slow path is forcibly noinline.
*/
Expand Down
14 changes: 12 additions & 2 deletions src-input/duk_numconv.c
Original file line number Diff line number Diff line change
Expand Up @@ -1537,7 +1537,7 @@ DUK_LOCAL void duk__dragon4_ctx_to_double(duk__numconv_stringify_ctx *nc_ctx, du
* Output: [ string ]
*/

DUK_INTERNAL void duk_numconv_stringify(duk_hthread *thr, duk_small_int_t radix, duk_small_int_t digits, duk_small_uint_t flags) {
DUK_LOCAL DUK_NOINLINE void duk__numconv_stringify_raw(duk_hthread *thr, duk_small_int_t radix, duk_small_int_t digits, duk_small_uint_t flags) {
duk_double_t x;
duk_small_int_t c;
duk_small_int_t neg;
Expand Down Expand Up @@ -1730,6 +1730,11 @@ DUK_INTERNAL void duk_numconv_stringify(duk_hthread *thr, duk_small_int_t radix,
duk__dragon4_convert_and_push(nc_ctx, thr, radix, digits, flags, neg);
}

DUK_INTERNAL void duk_numconv_stringify(duk_hthread *thr, duk_small_int_t radix, duk_small_int_t digits, duk_small_uint_t flags) {
duk_native_stack_check(thr);
duk__numconv_stringify_raw(thr, radix, digits, flags);
}

/*
* Exposed string-to-number API
*
Expand All @@ -1740,7 +1745,7 @@ DUK_INTERNAL void duk_numconv_stringify(duk_hthread *thr, duk_small_int_t radix,
* fails due to an internal error, an InternalError is thrown.
*/

DUK_INTERNAL void duk_numconv_parse(duk_hthread *thr, duk_small_int_t radix, duk_small_uint_t flags) {
DUK_LOCAL DUK_NOINLINE void duk__numconv_parse_raw(duk_hthread *thr, duk_small_int_t radix, duk_small_uint_t flags) {
duk__numconv_stringify_ctx nc_ctx_alloc; /* large context; around 2kB now */
duk__numconv_stringify_ctx *nc_ctx = &nc_ctx_alloc;
duk_double_t res;
Expand Down Expand Up @@ -2268,3 +2273,8 @@ DUK_INTERNAL void duk_numconv_parse(duk_hthread *thr, duk_small_int_t radix, duk
DUK_ERROR_RANGE(thr, "exponent too large");
DUK_WO_NORETURN(return;);
}

DUK_INTERNAL void duk_numconv_parse(duk_hthread *thr, duk_small_int_t radix, duk_small_uint_t flags) {
duk_native_stack_check(thr);
duk__numconv_parse_raw(thr, radix, flags);
}
1 change: 1 addition & 0 deletions src-input/duk_regexp_compiler.c
Original file line number Diff line number Diff line change
Expand Up @@ -522,6 +522,7 @@ DUK_LOCAL void duk__parse_disjunction(duk_re_compiler_ctx *re_ctx, duk_bool_t ex

DUK_ASSERT(out_atom_info != NULL);

duk_native_stack_check(re_ctx->thr);
if (re_ctx->recursion_depth >= re_ctx->recursion_limit) {
DUK_ERROR_RANGE(re_ctx->thr, DUK_STR_REGEXP_COMPILER_RECURSION_LIMIT);
DUK_WO_NORETURN(return;);
Expand Down
1 change: 1 addition & 0 deletions src-input/duk_regexp_executor.c
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@ DUK_LOCAL duk_codepoint_t duk__inp_get_prev_cp(duk_re_matcher_ctx *re_ctx, const
*/

DUK_LOCAL const duk_uint8_t *duk__match_regexp(duk_re_matcher_ctx *re_ctx, const duk_uint8_t *pc, const duk_uint8_t *sp) {
duk_native_stack_check(re_ctx->thr);
if (re_ctx->recursion_depth >= re_ctx->recursion_limit) {
DUK_ERROR_RANGE(re_ctx->thr, DUK_STR_REGEXP_EXECUTOR_RECURSION_LIMIT);
DUK_WO_NORETURN(return NULL;);
Expand Down
2 changes: 1 addition & 1 deletion src-input/duk_strings.h
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@
#define DUK_STR_CALLSTACK_LIMIT "callstack limit"
#define DUK_STR_PROTOTYPE_CHAIN_LIMIT "prototype chain limit"
#define DUK_STR_BOUND_CHAIN_LIMIT "function call bound chain limit"
#define DUK_STR_C_CALLSTACK_LIMIT "C call stack depth limit"
#define DUK_STR_NATIVE_STACK_LIMIT "C stack depth limit"
#define DUK_STR_COMPILER_RECURSION_LIMIT "compiler recursion limit"
#define DUK_STR_BYTECODE_LIMIT "bytecode limit"
#define DUK_STR_REG_LIMIT "register limit"
Expand Down
2 changes: 1 addition & 1 deletion tests/ecmascript/test-dev-cont-native-reclimit.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
---*/

/*===
RangeError: C call stack depth limit
RangeError: C stack depth limit
still here
===*/

Expand Down
5 changes: 5 additions & 0 deletions util/makeduk_base.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@ DUK_USE_FATAL_HANDLER:
verbatim: "#define DUK_USE_FATAL_HANDLER(udata,msg) do { const char *fatal_msg = (msg); fprintf(stderr, \"*** FATAL ERROR: %s\\n\", fatal_msg ? fatal_msg : \"no message\"); fflush(stderr); *((volatile unsigned int *) 0) = (unsigned int) 0xdeadbeefUL; abort(); } while(0)"
DUK_USE_SELF_TESTS: true

#DUK_USE_NATIVE_STACK_CHECK:
# verbatim: "int duk_cmdline_stack_check(void);\n#define DUK_USE_NATIVE_STACK_CHECK() duk_cmdline_stack_check()"
#DUK_USE_NATIVE_CALL_RECLIMIT: 10000000
#DUK_USE_CALLSTACK_LIMIT: 10000000

#DUK_USE_ASSERTIONS: true
#DUK_USE_GC_TORTURE: true
#DUK_USE_SHUFFLE_TORTURE: true
Expand Down

0 comments on commit 3a67aac

Please sign in to comment.