Browse Source

Merge pull request #1995 from svaarala/native-stack-space-check-macro

Add DUK_USE_NATIVE_STACK_CHECK application hook and initial call sites
pull/2135/head
Sami Vaarala 5 years ago
committed by GitHub
parent
commit
45b8e0e8ee
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      Makefile
  2. 5
      RELEASES.rst
  3. 22
      config/config-options/DUK_USE_NATIVE_STACK_CHECK.yaml
  4. 52
      examples/cmdline/duk_cmdline.c
  5. 1
      src-input/duk_js.h
  6. 15
      src-input/duk_js_call.c
  7. 14
      src-input/duk_numconv.c
  8. 1
      src-input/duk_regexp_compiler.c
  9. 1
      src-input/duk_regexp_executor.c
  10. 2
      src-input/duk_strings.h
  11. 2
      tests/ecmascript/test-dev-cont-native-reclimit.js
  12. 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

5
RELEASES.rst

@ -3410,6 +3410,11 @@ Planned
* Remove arguments.caller for strict argument objects to match revised
ES2017 behavior (GH-2009)
* Add DUK_USE_NATIVE_STACK_CHECK() macro config option (disabled by default)
for a platform specific stack space check in recursive and stack heavy
code paths; this is more accurate than the default fixed recursion limit
(GH-1995)
* When using Proxy wrapping in console extra, don't return a fake NOP
function for console.toJSON to avoid confusing JX serialization of the
console object (GH-2052, GH-2054, GH-2055)

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