Browse Source

Add DUK_USE_NATIVE_STACK_CHECK macro support

* 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.
pull/1995/head
Sami Vaarala 6 years ago
parent
commit
3a67aacd05
  1. 2
      Makefile
  2. 22
      config/config-options/DUK_USE_NATIVE_STACK_CHECK.yaml
  3. 52
      examples/cmdline/duk_cmdline.c
  4. 1
      src-input/duk_js.h
  5. 15
      src-input/duk_js_call.c
  6. 14
      src-input/duk_numconv.c
  7. 1
      src-input/duk_regexp_compiler.c
  8. 1
      src-input/duk_regexp_executor.c
  9. 2
      src-input/duk_strings.h
  10. 2
      tests/ecmascript/test-dev-cont-native-reclimit.js
  11. 5
      util/makeduk_base.yaml

2
Makefile

@ -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
@ -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

22
config/config-options/DUK_USE_NATIVE_STACK_CHECK.yaml

@ -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
examples/cmdline/duk_cmdline.c

@ -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.
@ -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>
@ -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
src-input/duk_js.h

@ -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);

15
src-input/duk_js_call.c

@ -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
@ -59,7 +70,7 @@ 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;);
}
@ -67,6 +78,8 @@ DUK_LOCAL DUK_ALWAYS_INLINE void duk__call_c_recursion_limit_check(duk_hthread *
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.
*/

14
src-input/duk_numconv.c

@ -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;
@ -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
*
@ -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;
@ -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
src-input/duk_regexp_compiler.c

@ -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;);

1
src-input/duk_regexp_executor.c

@ -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;);

2
src-input/duk_strings.h

@ -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"

2
tests/ecmascript/test-dev-cont-native-reclimit.js

@ -9,7 +9,7 @@
---*/
/*===
RangeError: C call stack depth limit
RangeError: C stack depth limit
still here
===*/

5
util/makeduk_base.yaml

@ -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

Loading…
Cancel
Save