DUK_EXTERNAL void duk_call(duk_context *ctx, duk_idx_t nargs) { duk_hthread *thr = (duk_hthread *) ctx; duk_small_uint_t call_flags; duk_idx_t idx_func; DUK_ASSERT_CTX_VALID(ctx); DUK_ASSERT(thr != NULL); idx_func = duk_get_top(ctx) - nargs - 1; if (idx_func < 0 || nargs < 0) { /* note that we can't reliably pop anything here */ DUK_ERROR(thr, DUK_ERR_API_ERROR, DUK_STR_INVALID_CALL_ARGS); } /* XXX: awkward; we assume there is space for this, overwrite * directly instead? */ duk_push_undefined(ctx); duk_insert(ctx, idx_func + 1); call_flags = 0; /* not protected, respect reclimit, not constructor */ duk_handle_call_unprotected(thr, /* thread */ nargs, /* num_stack_args */ call_flags); /* call_flags */ }
DUK_INTERNAL duk_hstring *duk_heap_string_intern_checked(duk_hthread *thr, const duk_uint8_t *str, duk_uint32_t blen) { duk_hstring *res = duk_heap_string_intern(thr->heap, str, blen); if (!res) { DUK_ERROR(thr, DUK_ERR_ALLOC_ERROR, "failed to intern string"); } return res; }
/* FIXME: unused now, remove */ duk_hobject *duk_hobject_alloc_checked(duk_hthread *thr, int hobject_flags) { duk_hobject *res = duk_hobject_alloc(thr->heap, hobject_flags); if (!res) { DUK_ERROR(thr, DUK_ERR_ALLOC_ERROR, "failed to allocate an object"); } return res; }
static duk_double_t duk__push_this_number_plain(duk_context *ctx) { duk_hobject *h; /* Number built-in accepts a plain number or a Number object (whose * internal value is operated on). Other types cause TypeError. */ duk_push_this(ctx); if (duk_is_number(ctx, -1)) { DUK_DDD(DUK_DDDPRINT("plain number value: %!T", (duk_tval *) duk_get_tval(ctx, -1))); goto done; } h = duk_get_hobject(ctx, -1); if (!h || (DUK_HOBJECT_GET_CLASS_NUMBER(h) != DUK_HOBJECT_CLASS_NUMBER)) { DUK_DDD(DUK_DDDPRINT("unacceptable this value: %!T", (duk_tval *) duk_get_tval(ctx, -1))); DUK_ERROR((duk_hthread *) ctx, DUK_ERR_TYPE_ERROR, "expected a number"); } duk_get_prop_stridx(ctx, -1, DUK_STRIDX_INT_VALUE); DUK_ASSERT(duk_is_number(ctx, -1)); DUK_DDD(DUK_DDDPRINT("number object: %!T, internal value: %!T", (duk_tval *) duk_get_tval(ctx, -2), (duk_tval *) duk_get_tval(ctx, -1))); duk_remove(ctx, -2); done: return duk_get_number(ctx, -1); }
DUK_EXTERNAL duk_int_t duk_get_magic(duk_context *ctx, duk_idx_t index) { duk_hthread *thr = (duk_hthread *) ctx; duk_tval *tv; duk_hobject *h; DUK_ASSERT_CTX_VALID(ctx); tv = duk_require_tval(ctx, index); if (DUK_TVAL_IS_OBJECT(tv)) { h = DUK_TVAL_GET_OBJECT(tv); DUK_ASSERT(h != NULL); if (!DUK_HOBJECT_HAS_NATIVEFUNCTION(h)) { goto type_error; } return (duk_int_t) ((duk_hnativefunction *) h)->magic; } else if (DUK_TVAL_IS_LIGHTFUNC(tv)) { duk_small_uint_t lf_flags = DUK_TVAL_GET_LIGHTFUNC_FLAGS(tv); return (duk_int_t) DUK_LFUNC_FLAGS_GET_MAGIC(lf_flags); } /* fall through */ type_error: DUK_ERROR(thr, DUK_ERR_TYPE_ERROR, DUK_STR_UNEXPECTED_TYPE); return 0; }
void duk_hthread_catchstack_grow(duk_hthread *thr) { duk_size_t old_size; duk_size_t new_size; DUK_ASSERT(thr != NULL); DUK_ASSERT_DISABLE(thr->catchstack_top); /* avoid warning (unsigned) */ DUK_ASSERT(thr->catchstack_size >= thr->catchstack_top); if (thr->catchstack_top < thr->catchstack_size) { return; } old_size = thr->catchstack_size; new_size = old_size + DUK_CATCHSTACK_GROW_STEP; /* this is a bit approximate (errors out before max is reached); this is OK */ if (new_size >= thr->catchstack_max) { DUK_ERROR(thr, DUK_ERR_RANGE_ERROR, "catchstack limit"); } DUK_DDPRINT("growing catchstack %d -> %d", old_size, new_size); /* * Note: must use indirect variant of DUK_REALLOC() because underlying * pointer may be changed by mark-and-sweep. */ thr->catchstack = (duk_catcher *) DUK_REALLOC_INDIRECT_CHECKED(thr, duk_hthread_get_catchstack_ptr, (void *) thr, sizeof(duk_catcher) * new_size); thr->catchstack_size = new_size; /* note: any entries above the catchstack top are garbage and not zeroed */ }
DUK_INTERNAL duk_hstring *duk_heap_string_intern_u32_checked(duk_hthread *thr, duk_uint32_t val) { duk_hstring *res = duk_heap_string_intern_u32(thr->heap, val); if (!res) { DUK_ERROR(thr, DUK_ERR_ALLOC_ERROR, "failed to intern string"); } return res; }
DUK_EXTERNAL void *duk_steal_buffer(duk_context *ctx, duk_idx_t index, duk_size_t *out_size) { duk_hthread *thr = (duk_hthread *) ctx; duk_hbuffer_dynamic *h; void *ptr; duk_size_t sz; DUK_ASSERT(ctx != NULL); h = (duk_hbuffer_dynamic *) duk_require_hbuffer(ctx, index); DUK_ASSERT(h != NULL); if (!DUK_HBUFFER_HAS_DYNAMIC(h)) { DUK_ERROR(thr, DUK_ERR_TYPE_ERROR, DUK_STR_BUFFER_NOT_DYNAMIC); } /* Forget the previous allocation, setting size to 0 and alloc to * NULL. Caller is responsible for freeing the previous allocation. * Getting the allocation and clearing it is done in the same API * call to avoid any chance of a realloc. */ ptr = DUK_HBUFFER_DYNAMIC_GET_DATA_PTR(thr->heap, h); sz = DUK_HBUFFER_DYNAMIC_GET_SIZE(h); if (out_size) { *out_size = sz; } DUK_HBUFFER_DYNAMIC_SET_DATA_PTR_NULL(thr->heap, h); DUK_HBUFFER_DYNAMIC_SET_SIZE(h, 0); DUK_HBUFFER_DYNAMIC_SET_ALLOC_SIZE(h, 0); return ptr; }
DUK_LOCAL duk_uint32_t duk__push_this_obj_len_u32_limited(duk_context *ctx) { /* Range limited to [0, 0x7fffffff] range, i.e. range that can be * represented with duk_int32_t. Use this when the method doesn't * handle the full 32-bit unsigned range correctly. */ duk_uint32_t ret = duk__push_this_obj_len_u32(ctx); if (DUK_UNLIKELY(ret >= 0x80000000UL)) { DUK_ERROR((duk_hthread *) ctx, DUK_ERR_INTERNAL_ERROR, DUK_STR_ARRAY_LENGTH_OVER_2G); } return ret; }
/* Push 'this' binding, check that it is a Date object; then push the * internal time value. At the end, stack is: [ ... this timeval ]. * Returns the time value. Local time adjustment is done if requested. */ static double push_this_and_get_timeval_tzoffset(duk_context *ctx, int flags, int *out_tzoffset) { duk_hthread *thr = (duk_hthread *) ctx; duk_hobject *h; double d; int tzoffset = 0; duk_push_this(ctx); h = duk_get_hobject(ctx, -1); /* FIXME: getter with class check, useful in built-ins */ if (h == NULL || DUK_HOBJECT_GET_CLASS_NUMBER(h) != DUK_HOBJECT_CLASS_DATE) { DUK_ERROR(thr, DUK_ERR_TYPE_ERROR, "expected Date"); } duk_get_prop_stridx(ctx, -1, DUK_STRIDX_INT_VALUE); d = duk_to_number(ctx, -1); duk_pop(ctx); if (DUK_ISNAN(d)) { if (flags & FLAG_NAN_TO_ZERO) { d = 0.0; } if (flags & FLAG_NAN_TO_RANGE_ERROR) { DUK_ERROR(thr, DUK_ERR_RANGE_ERROR, "Invalid Date"); } } /* if no NaN handling flag, may still be NaN here, but not Inf */ DUK_ASSERT(!DUK_ISINF(d)); if (flags & FLAG_LOCALTIME) { /* Note: DST adjustment is determined using UTC time. * If 'd' is NaN, tzoffset will be 0. */ tzoffset = GET_LOCAL_TZOFFSET(d); /* seconds */ d += tzoffset * 1000; } if (out_tzoffset) { *out_tzoffset = tzoffset; } /* [ ... this ] */ return d; }
/* * CheckObjectCoercible() (E5 Section 9.10) * * Note: no API equivalent now. */ #if 0 /* unused */ DUK_INTERNAL void duk_js_checkobjectcoercible(duk_hthread *thr, duk_tval *tv_x) { duk_small_uint_t tag = DUK_TVAL_GET_TAG(tv_x); /* Note: this must match ToObject() behavior */ if (tag == DUK_TAG_UNDEFINED || tag == DUK_TAG_NULL || tag == DUK_TAG_POINTER || tag == DUK_TAG_BUFFER) { DUK_ERROR(thr, DUK_ERR_TYPE_ERROR, "not object coercible"); } }
DUK_INTERNAL void duk_hthread_catchstack_grow(duk_hthread *thr) { duk_catcher *new_ptr; duk_size_t old_size; duk_size_t new_size; DUK_ASSERT(thr != NULL); DUK_ASSERT_DISABLE(thr->catchstack_top); /* avoid warning (unsigned) */ DUK_ASSERT(thr->catchstack_size >= thr->catchstack_top); if (thr->catchstack_top < thr->catchstack_size) { return; } old_size = thr->catchstack_size; new_size = old_size + DUK_CATCHSTACK_GROW_STEP; /* this is a bit approximate (errors out before max is reached); this is OK */ if (new_size >= thr->catchstack_max) { DUK_ERROR(thr, DUK_ERR_RANGE_ERROR, DUK_STR_CATCHSTACK_LIMIT); } DUK_DD(DUK_DDPRINT("growing catchstack %ld -> %ld", (long) old_size, (long) new_size)); /* * Note: must use indirect variant of DUK_REALLOC() because underlying * pointer may be changed by mark-and-sweep. */ DUK_ASSERT(new_size > 0); new_ptr = (duk_catcher *) DUK_REALLOC_INDIRECT(thr->heap, duk_hthread_get_catchstack_ptr, (void *) thr, sizeof(duk_catcher) * new_size); if (!new_ptr) { /* No need for a NULL/zero-size check because new_size > 0) */ DUK_ERROR(thr, DUK_ERR_ALLOC_ERROR, DUK_STR_REALLOC_FAILED); } thr->catchstack = new_ptr; thr->catchstack_size = new_size; /* note: any entries above the catchstack top are garbage and not zeroed */ }
/* Get current Ecmascript time (= UNIX/Posix time, but in milliseconds). */ static double get_now_timeval_gettimeofday(duk_context *ctx) { duk_hthread *thr = (duk_hthread *) ctx; struct timeval tv; double d; if (gettimeofday(&tv, NULL) != 0) { DUK_ERROR(thr, DUK_ERR_INTERNAL_ERROR, "gettimeofday failed"); } d = ((double) tv.tv_sec) * 1000.0 + ((double) (tv.tv_usec / 1000)); DUK_ASSERT(floor(d) == d); /* no fractions */ return d; }
/* Get current Ecmascript time (= UNIX/Posix time, but in milliseconds). */ DUK_INTERNAL duk_double_t duk_bi_date_get_now_gettimeofday(duk_context *ctx) { duk_hthread *thr = (duk_hthread *) ctx; struct timeval tv; duk_double_t d; if (gettimeofday(&tv, NULL) != 0) { DUK_ERROR(thr, DUK_ERR_INTERNAL_ERROR, "gettimeofday failed"); } d = ((duk_double_t) tv.tv_sec) * 1000.0 + ((duk_double_t) (tv.tv_usec / 1000)); DUK_ASSERT(DUK_FLOOR(d) == d); /* no fractions */ return d; }
DUK_LOCAL duk_uint32_t duk__parse_regexp_flags(duk_hthread *thr, duk_hstring *h) { const duk_uint8_t *p; const duk_uint8_t *p_end; duk_uint32_t flags = 0; p = DUK_HSTRING_GET_DATA(h); p_end = p + DUK_HSTRING_GET_BYTELEN(h); /* Note: can be safely scanned as bytes (undecoded) */ while (p < p_end) { duk_uint8_t c = *p++; switch ((int) c) { case (int) 'g': { if (flags & DUK_RE_FLAG_GLOBAL) { goto error; } flags |= DUK_RE_FLAG_GLOBAL; break; } case (int) 'i': { if (flags & DUK_RE_FLAG_IGNORE_CASE) { goto error; } flags |= DUK_RE_FLAG_IGNORE_CASE; break; } case (int) 'm': { if (flags & DUK_RE_FLAG_MULTILINE) { goto error; } flags |= DUK_RE_FLAG_MULTILINE; break; } default: { goto error; } } } return flags; error: DUK_ERROR(thr, DUK_ERR_SYNTAX_ERROR, DUK_STR_INVALID_REGEXP_FLAGS); return 0; /* never here */ }
void *duk_heap_mem_realloc_indirect_checked(duk_hthread *thr, duk_mem_getptr cb, void *ud, size_t newsize, const char *filename, int line) { #else void *duk_heap_mem_realloc_indirect_checked(duk_hthread *thr, duk_mem_getptr cb, void *ud, size_t newsize) { #endif void *res; DUK_ASSERT(thr != NULL); DUK_ASSERT_DISABLE(newsize >= 0); res = DUK_REALLOC_INDIRECT(thr->heap, cb, ud, newsize); if (!res) { #ifdef DUK_USE_VERBOSE_ERRORS DUK_ERROR_RAW(filename, line, thr, DUK_ERR_ALLOC_ERROR, "memory realloc failed"); #else DUK_ERROR(thr, DUK_ERR_ALLOC_ERROR, "memory realloc failed"); #endif } return res; }
void *duk_resize_buffer(duk_context *ctx, duk_idx_t index, duk_size_t new_size) { duk_hthread *thr = (duk_hthread *) ctx; duk_hbuffer_dynamic *h; DUK_ASSERT(ctx != NULL); h = (duk_hbuffer_dynamic *) duk_require_hbuffer(ctx, index); DUK_ASSERT(h != NULL); if (!DUK_HBUFFER_HAS_DYNAMIC(h)) { DUK_ERROR(thr, DUK_ERR_TYPE_ERROR, "buffer is not dynamic"); } /* maximum size check is handled by callee */ duk_hbuffer_resize(thr, h, new_size, new_size); /* snug */ return DUK_HBUFFER_DYNAMIC_GET_CURR_DATA_PTR(h); }
void *duk_heap_mem_alloc_checked(duk_hthread *thr, size_t size, const char *filename, int line) { #else void *duk_heap_mem_alloc_checked(duk_hthread *thr, size_t size) { #endif void *res; DUK_ASSERT(thr != NULL); DUK_ASSERT_DISABLE(size >= 0); res = DUK_ALLOC(thr->heap, size); if (!res) { #ifdef DUK_USE_VERBOSE_ERRORS DUK_ERROR_RAW(filename, line, thr, DUK_ERR_ALLOC_ERROR, "memory alloc failed"); #else DUK_ERROR(thr, DUK_ERR_ALLOC_ERROR, "memory alloc failed"); #endif } return res; }
DUK_EXTERNAL void duk_call_method(duk_context *ctx, duk_idx_t nargs) { duk_hthread *thr = (duk_hthread *) ctx; duk_small_uint_t call_flags; duk_idx_t idx_func; DUK_ASSERT_CTX_VALID(ctx); DUK_ASSERT(thr != NULL); idx_func = duk_get_top(ctx) - nargs - 2; /* must work for nargs <= 0 */ if (idx_func < 0 || nargs < 0) { /* note that we can't reliably pop anything here */ DUK_ERROR(thr, DUK_ERR_API_ERROR, DUK_STR_INVALID_CALL_ARGS); } call_flags = 0; /* not protected, respect reclimit, not constructor */ duk_handle_call_unprotected(thr, /* thread */ nargs, /* num_stack_args */ call_flags); /* call_flags */ }
DUK_EXTERNAL duk_int_t duk_safe_call(duk_context *ctx, duk_safe_call_function func, duk_idx_t nargs, duk_idx_t nrets) { duk_hthread *thr = (duk_hthread *) ctx; duk_int_t rc; DUK_ASSERT_CTX_VALID(ctx); DUK_ASSERT(thr != NULL); if (duk_get_top(ctx) < nargs || nrets < 0) { /* See comments in duk_pcall(). */ DUK_ERROR(thr, DUK_ERR_API_ERROR, DUK_STR_INVALID_CALL_ARGS); return DUK_EXEC_ERROR; /* unreachable */ } rc = duk_handle_safe_call(thr, /* thread */ func, /* func */ nargs, /* num_stack_args */ nrets); /* num_stack_res */ return rc; }
DUK_EXTERNAL void duk_load_function(duk_context *ctx) { duk_hthread *thr; duk_uint8_t *p_buf, *p, *p_end; duk_size_t sz; DUK_ASSERT(ctx != NULL); thr = (duk_hthread *) ctx; DUK_UNREF(ctx); p_buf = (duk_uint8_t *) duk_require_buffer(ctx, -1, &sz); DUK_ASSERT(p_buf != NULL); /* The caller is responsible for being sure that bytecode being loaded * is valid and trusted. Invalid bytecode can cause memory unsafe * behavior directly during loading or later during bytecode execution * (instruction validation would be quite complex to implement). * * This signature check is the only sanity check for detecting * accidental invalid inputs. The initial 0xFF byte ensures no * ordinary string will be accepted by accident. */ p = p_buf; p_end = p_buf + sz; if (sz < 2 || p[0] != DUK__SER_MARKER || p[1] != DUK__SER_VERSION) { goto format_error; } p += 2; p = duk__load_func(ctx, p, p_end); if (p == NULL) { goto format_error; } duk_remove(ctx, -2); /* [ ... buf func ] -> [ ... func ] */ return; format_error: DUK_ERROR(thr, DUK_ERR_TYPE_ERROR, DUK_STR_DECODE_FAILED); }
DUK_EXTERNAL duk_int_t duk_pcall(duk_context *ctx, duk_idx_t nargs) { duk_hthread *thr = (duk_hthread *) ctx; duk_small_uint_t call_flags; duk_idx_t idx_func; duk_int_t rc; DUK_ASSERT_CTX_VALID(ctx); DUK_ASSERT(thr != NULL); idx_func = duk_get_top(ctx) - nargs - 1; /* must work for nargs <= 0 */ if (idx_func < 0 || nargs < 0) { /* We can't reliably pop anything here because the stack input * shape is incorrect. So we throw an error; if the caller has * no catch point for this, a fatal error will occur. Another * alternative would be to just return an error. But then the * stack would be in an unknown state which might cause some * very hard to diagnose problems later on. Also note that even * if we did not throw an error here, the underlying call handler * might STILL throw an out-of-memory error or some other internal * fatal error. */ DUK_ERROR(thr, DUK_ERR_API_ERROR, DUK_STR_INVALID_CALL_ARGS); return DUK_EXEC_ERROR; /* unreachable */ } /* awkward; we assume there is space for this */ duk_push_undefined(ctx); duk_insert(ctx, idx_func + 1); call_flags = 0; /* respect reclimit, not constructor */ rc = duk_handle_call_protected(thr, /* thread */ nargs, /* num_stack_args */ call_flags); /* call_flags */ return rc; }
DUK_EXTERNAL duk_int_t duk_pcall_method(duk_context *ctx, duk_idx_t nargs) { duk_hthread *thr = (duk_hthread *) ctx; duk_small_uint_t call_flags; duk_idx_t idx_func; duk_int_t rc; DUK_ASSERT_CTX_VALID(ctx); DUK_ASSERT(thr != NULL); idx_func = duk_get_top(ctx) - nargs - 2; /* must work for nargs <= 0 */ if (idx_func < 0 || nargs < 0) { /* See comments in duk_pcall(). */ DUK_ERROR(thr, DUK_ERR_API_ERROR, DUK_STR_INVALID_CALL_ARGS); return DUK_EXEC_ERROR; /* unreachable */ } call_flags = 0; /* respect reclimit, not constructor */ rc = duk_handle_call_protected(thr, /* thread */ nargs, /* num_stack_args */ call_flags); /* call_flags */ return rc; }
/* Helper which can be called both directly and with duk_safe_call(). */ DUK_LOCAL duk_ret_t duk__do_compile(duk_context *ctx) { duk_hthread *thr = (duk_hthread *) ctx; duk__compile_raw_args *comp_args; duk_uint_t flags; duk_small_uint_t comp_flags; duk_hcompiledfunction *h_templ; /* Note: strictness is not inherited from the current Duktape/C * context. Otherwise it would not be possible to compile * non-strict code inside a Duktape/C activation (which is * always strict now). See api-testcases/test-eval-strictness.c * for discussion. */ /* [ ... source? filename &comp_args ] (depends on flags) */ comp_args = (duk__compile_raw_args *) duk_require_pointer(ctx, -1); flags = comp_args->flags; duk_pop(ctx); /* [ ... source? filename ] */ if (!comp_args->src_buffer) { duk_hstring *h_sourcecode; h_sourcecode = duk_get_hstring(ctx, -2); if ((flags & DUK_COMPILE_NOSOURCE) || /* args incorrect */ (h_sourcecode == NULL)) { /* e.g. duk_push_file_string_raw() pushed undefined */ /* XXX: when this error is caused by a nonexistent * file given to duk_peval_file() or similar, the * error message is not the best possible. */ DUK_ERROR(thr, DUK_ERR_API_ERROR, DUK_STR_NO_SOURCECODE); } DUK_ASSERT(h_sourcecode != NULL); comp_args->src_buffer = (const duk_uint8_t *) DUK_HSTRING_GET_DATA(h_sourcecode); comp_args->src_length = (duk_size_t) DUK_HSTRING_GET_BYTELEN(h_sourcecode); } DUK_ASSERT(comp_args->src_buffer != NULL); /* XXX: unnecessary translation of flags */ comp_flags = 0; if (flags & DUK_COMPILE_EVAL) { comp_flags |= DUK_JS_COMPILE_FLAG_EVAL; } if (flags & DUK_COMPILE_FUNCTION) { comp_flags |= DUK_JS_COMPILE_FLAG_EVAL | DUK_JS_COMPILE_FLAG_FUNCEXPR; } if (flags & DUK_COMPILE_STRICT) { comp_flags |= DUK_JS_COMPILE_FLAG_STRICT; } /* [ ... source? filename ] */ duk_js_compile(thr, comp_args->src_buffer, comp_args->src_length, comp_flags); /* [ ... source? func_template ] */ if (flags & DUK_COMPILE_NOSOURCE) { ; } else { duk_remove(ctx, -2); } /* [ ... func_template ] */ h_templ = (duk_hcompiledfunction *) duk_get_hobject(ctx, -1); DUK_ASSERT(h_templ != NULL); duk_js_push_closure(thr, h_templ, thr->builtins[DUK_BIDX_GLOBAL_ENV], thr->builtins[DUK_BIDX_GLOBAL_ENV]); duk_remove(ctx, -2); /* -> [ ... closure ] */ /* [ ... closure ] */ return 1; }
DUK_INTERNAL void duk_regexp_compile(duk_hthread *thr) { duk_context *ctx = (duk_context *) thr; duk_re_compiler_ctx re_ctx; duk_lexer_point lex_point; duk_hstring *h_pattern; duk_hstring *h_flags; duk__re_disjunction_info ign_disj; DUK_ASSERT(thr != NULL); DUK_ASSERT(ctx != NULL); /* * Args validation */ /* TypeError if fails */ h_pattern = duk_require_hstring(ctx, -2); h_flags = duk_require_hstring(ctx, -1); /* * Create normalized 'source' property (E5 Section 15.10.3). */ /* [ ... pattern flags ] */ duk__create_escaped_source(thr, -2); /* [ ... pattern flags escaped_source ] */ /* * Init compilation context */ /* [ ... pattern flags escaped_source buffer ] */ DUK_MEMZERO(&re_ctx, sizeof(re_ctx)); DUK_LEXER_INITCTX(&re_ctx.lex); /* duplicate zeroing, expect for (possible) NULL inits */ re_ctx.thr = thr; re_ctx.lex.thr = thr; re_ctx.lex.input = DUK_HSTRING_GET_DATA(h_pattern); re_ctx.lex.input_length = DUK_HSTRING_GET_BYTELEN(h_pattern); re_ctx.lex.token_limit = DUK_RE_COMPILE_TOKEN_LIMIT; re_ctx.recursion_limit = DUK_USE_REGEXP_COMPILER_RECLIMIT; re_ctx.re_flags = duk__parse_regexp_flags(thr, h_flags); DUK_BW_INIT_PUSHBUF(thr, &re_ctx.bw, DUK__RE_INITIAL_BUFSIZE); DUK_DD(DUK_DDPRINT("regexp compiler ctx initialized, flags=0x%08lx, recursion_limit=%ld", (unsigned long) re_ctx.re_flags, (long) re_ctx.recursion_limit)); /* * Init lexer */ lex_point.offset = 0; /* expensive init, just want to fill window */ lex_point.line = 1; DUK_LEXER_SETPOINT(&re_ctx.lex, &lex_point); /* * Compilation */ DUK_D(DUK_DPRINT("starting regexp compilation")); duk__append_u32(&re_ctx, DUK_REOP_SAVE); duk__append_u32(&re_ctx, 0); duk__parse_disjunction(&re_ctx, 1 /*expect_eof*/, &ign_disj); duk__append_u32(&re_ctx, DUK_REOP_SAVE); duk__append_u32(&re_ctx, 1); duk__append_u32(&re_ctx, DUK_REOP_MATCH); /* * Check for invalid backreferences; note that it is NOT an error * to back-reference a capture group which has not yet been introduced * in the pattern (as in /\1(foo)/); in fact, the backreference will * always match! It IS an error to back-reference a capture group * which will never be introduced in the pattern. Thus, we can check * for such references only after parsing is complete. */ if (re_ctx.highest_backref > re_ctx.captures) { DUK_ERROR(thr, DUK_ERR_SYNTAX_ERROR, DUK_STR_INVALID_BACKREFS); } /* * Emit compiled regexp header: flags, ncaptures * (insertion order inverted on purpose) */ duk__insert_u32(&re_ctx, 0, (re_ctx.captures + 1) * 2); duk__insert_u32(&re_ctx, 0, re_ctx.re_flags); /* [ ... pattern flags escaped_source buffer ] */ DUK_BW_COMPACT(thr, &re_ctx.bw); duk_to_string(ctx, -1); /* coerce to string */ /* [ ... pattern flags escaped_source bytecode ] */ /* * Finalize stack */ duk_remove(ctx, -4); /* -> [ ... flags escaped_source bytecode ] */ duk_remove(ctx, -3); /* -> [ ... escaped_source bytecode ] */ DUK_D(DUK_DPRINT("regexp compilation successful, bytecode: %!T, escaped source: %!T", (duk_tval *) duk_get_tval(ctx, -1), (duk_tval *) duk_get_tval(ctx, -2))); }
DUK_INTERNAL duk_bool_t duk_js_instanceof(duk_hthread *thr, duk_tval *tv_x, duk_tval *tv_y) { duk_context *ctx = (duk_context *) thr; duk_hobject *func; duk_hobject *val; duk_hobject *proto; duk_uint_t sanity; /* * Get the values onto the stack first. It would be possible to cover * some normal cases without resorting to the value stack. * * The right hand side could be a light function (as they generally * behave like objects). Light functions never have a 'prototype' * property so E5.1 Section 15.3.5.3 step 3 always throws a TypeError. * Using duk_require_hobject() is thus correct (except for error msg). */ duk_push_tval(ctx, tv_x); duk_push_tval(ctx, tv_y); func = duk_require_hobject(ctx, -1); /* * For bound objects, [[HasInstance]] just calls the target function * [[HasInstance]]. If that is again a bound object, repeat until * we find a non-bound Function object. */ /* XXX: this bound function resolution also happens elsewhere, * move into a shared helper. */ sanity = DUK_HOBJECT_BOUND_CHAIN_SANITY; do { /* check func supports [[HasInstance]] (this is checked for every function * in the bound chain, including the final one) */ if (!DUK_HOBJECT_IS_CALLABLE(func)) { /* * Note: of native Ecmascript objects, only Function instances * have a [[HasInstance]] internal property. Custom objects might * also have it, but not in current implementation. * * XXX: add a separate flag, DUK_HOBJECT_FLAG_ALLOW_INSTANCEOF? */ DUK_ERROR(thr, DUK_ERR_TYPE_ERROR, "invalid instanceof rval"); } if (!DUK_HOBJECT_HAS_BOUND(func)) { break; } /* [ ... lval rval ] */ duk_get_prop_stridx(ctx, -1, DUK_STRIDX_INT_TARGET); /* -> [ ... lval rval new_rval ] */ duk_replace(ctx, -1); /* -> [ ... lval new_rval ] */ func = duk_require_hobject(ctx, -1); /* func support for [[HasInstance]] checked in the beginning of the loop */ } while (--sanity > 0); if (sanity == 0) { DUK_ERROR(thr, DUK_ERR_INTERNAL_ERROR, DUK_STR_BOUND_CHAIN_LIMIT); } /* * 'func' is now a non-bound object which supports [[HasInstance]] * (which here just means DUK_HOBJECT_FLAG_CALLABLE). Move on * to execute E5 Section 15.3.5.3. */ DUK_ASSERT(!DUK_HOBJECT_HAS_BOUND(func)); DUK_ASSERT(DUK_HOBJECT_IS_CALLABLE(func)); /* [ ... lval rval(func) ] */ /* Handle lightfuncs through object coercion for now. */ /* XXX: direct implementation */ val = duk_get_hobject_or_lfunc_coerce(ctx, -2); if (!val) { goto pop_and_false; } duk_get_prop_stridx(ctx, -1, DUK_STRIDX_PROTOTYPE); /* -> [ ... lval rval rval.prototype ] */ proto = duk_require_hobject(ctx, -1); duk_pop(ctx); /* -> [ ... lval rval ] */ sanity = DUK_HOBJECT_PROTOTYPE_CHAIN_SANITY; do { /* * Note: prototype chain is followed BEFORE first comparison. This * means that the instanceof lval is never itself compared to the * rval.prototype property. This is apparently intentional, see E5 * Section 15.3.5.3, step 4.a. * * Also note: * * js> (function() {}) instanceof Function * true * js> Function instanceof Function * true * * For the latter, h_proto will be Function.prototype, which is the * built-in Function prototype. Because Function.[[Prototype]] is * also the built-in Function prototype, the result is true. */ val = DUK_HOBJECT_GET_PROTOTYPE(thr->heap, val); if (!val) { goto pop_and_false; } else if (val == proto) { goto pop_and_true; } /* follow prototype chain */ } while (--sanity > 0); if (sanity == 0) { DUK_ERROR(thr, DUK_ERR_INTERNAL_ERROR, DUK_STR_PROTOTYPE_CHAIN_LIMIT); } DUK_UNREACHABLE(); pop_and_false: duk_pop_2(ctx); return 0; pop_and_true: duk_pop_2(ctx); return 1; }
DUK_INTERNAL duk_uint_fast32_t duk_heap_strcache_offset_char2byte(duk_hthread *thr, duk_hstring *h, duk_uint_fast32_t char_offset) { duk_heap *heap; duk_strcache *sce; duk_uint_fast32_t byte_offset; duk_small_int_t i; duk_bool_t use_cache; duk_uint_fast32_t dist_start, dist_end, dist_sce; duk_uint8_t *p_start; duk_uint8_t *p_end; duk_uint8_t *p_found; if (char_offset > DUK_HSTRING_GET_CHARLEN(h)) { goto error; } /* * For ASCII strings, the answer is simple. */ if (DUK_HSTRING_IS_ASCII(h)) { /* clen == blen -> pure ascii */ return char_offset; } /* * For non-ASCII strings, we need to scan forwards or backwards * from some starting point. The starting point may be the start * or end of the string, or some cached midpoint in the string * cache. * * For "short" strings we simply scan without checking or updating * the cache. For longer strings we check and update the cache as * necessary, inserting a new cache entry if none exists. */ DUK_DDD(DUK_DDDPRINT("non-ascii string %p, char_offset=%ld, clen=%ld, blen=%ld", (void *) h, (long) char_offset, (long) DUK_HSTRING_GET_CHARLEN(h), (long) DUK_HSTRING_GET_BYTELEN(h))); heap = thr->heap; sce = NULL; use_cache = (DUK_HSTRING_GET_CHARLEN(h) > DUK_HEAP_STRINGCACHE_NOCACHE_LIMIT); if (use_cache) { #ifdef DUK_USE_DDDPRINT DUK_DDD(DUK_DDDPRINT("stringcache before char2byte (using cache):")); for (i = 0; i < DUK_HEAP_STRCACHE_SIZE; i++) { duk_strcache *c = heap->strcache + i; DUK_DDD(DUK_DDDPRINT(" [%ld] -> h=%p, cidx=%ld, bidx=%ld", (long) i, (void *) c->h, (long) c->cidx, (long) c->bidx)); } #endif for (i = 0; i < DUK_HEAP_STRCACHE_SIZE; i++) { duk_strcache *c = heap->strcache + i; if (c->h == h) { sce = c; break; } } } /* * Scan from shortest distance: * - start of string * - end of string * - cache entry (if exists) */ DUK_ASSERT(DUK_HSTRING_GET_CHARLEN(h) >= char_offset); dist_start = char_offset; dist_end = DUK_HSTRING_GET_CHARLEN(h) - char_offset; dist_sce = 0; DUK_UNREF(dist_sce); /* initialize for debug prints, needed if sce==NULL */ p_start = (duk_uint8_t *) DUK_HSTRING_GET_DATA(h); p_end = (duk_uint8_t *) (p_start + DUK_HSTRING_GET_BYTELEN(h)); p_found = NULL; if (sce) { if (char_offset >= sce->cidx) { dist_sce = char_offset - sce->cidx; if ((dist_sce <= dist_start) && (dist_sce <= dist_end)) { DUK_DDD(DUK_DDDPRINT("non-ascii string, use_cache=%ld, sce=%p:%ld:%ld, " "dist_start=%ld, dist_end=%ld, dist_sce=%ld => " "scan forwards from sce", (long) use_cache, (void *) (sce ? sce->h : NULL), (sce ? (long) sce->cidx : (long) -1), (sce ? (long) sce->bidx : (long) -1), (long) dist_start, (long) dist_end, (long) dist_sce)); p_found = duk__scan_forwards(p_start + sce->bidx, p_end, dist_sce); goto scan_done; } } else { dist_sce = sce->cidx - char_offset; if ((dist_sce <= dist_start) && (dist_sce <= dist_end)) { DUK_DDD(DUK_DDDPRINT("non-ascii string, use_cache=%ld, sce=%p:%ld:%ld, " "dist_start=%ld, dist_end=%ld, dist_sce=%ld => " "scan backwards from sce", (long) use_cache, (void *) (sce ? sce->h : NULL), (sce ? (long) sce->cidx : (long) -1), (sce ? (long) sce->bidx : (long) -1), (long) dist_start, (long) dist_end, (long) dist_sce)); p_found = duk__scan_backwards(p_start + sce->bidx, p_start, dist_sce); goto scan_done; } } } /* no sce, or sce scan not best */ if (dist_start <= dist_end) { DUK_DDD(DUK_DDDPRINT("non-ascii string, use_cache=%ld, sce=%p:%ld:%ld, " "dist_start=%ld, dist_end=%ld, dist_sce=%ld => " "scan forwards from string start", (long) use_cache, (void *) (sce ? sce->h : NULL), (sce ? (long) sce->cidx : (long) -1), (sce ? (long) sce->bidx : (long) -1), (long) dist_start, (long) dist_end, (long) dist_sce)); p_found = duk__scan_forwards(p_start, p_end, dist_start); } else { DUK_DDD(DUK_DDDPRINT("non-ascii string, use_cache=%ld, sce=%p:%ld:%ld, " "dist_start=%ld, dist_end=%ld, dist_sce=%ld => " "scan backwards from string end", (long) use_cache, (void *) (sce ? sce->h : NULL), (sce ? (long) sce->cidx : (long) -1), (sce ? (long) sce->bidx : (long) -1), (long) dist_start, (long) dist_end, (long) dist_sce)); p_found = duk__scan_backwards(p_end, p_start, dist_end); } scan_done: if (!p_found) { /* Scan error: this shouldn't normally happen; it could happen if * string is not valid UTF-8 data, and clen/blen are not consistent * with the scanning algorithm. */ goto error; } DUK_ASSERT(p_found >= p_start); DUK_ASSERT(p_found <= p_end); /* may be equal */ byte_offset = (duk_uint32_t) (p_found - p_start); DUK_DDD(DUK_DDDPRINT("-> string %p, cidx %ld -> bidx %ld", (void *) h, (long) char_offset, (long) byte_offset)); /* * Update cache entry (allocating if necessary), and move the * cache entry to the first place (in an "LRU" policy). */ if (use_cache) { /* update entry, allocating if necessary */ if (!sce) { sce = heap->strcache + DUK_HEAP_STRCACHE_SIZE - 1; /* take last entry */ sce->h = h; } DUK_ASSERT(sce != NULL); sce->bidx = (duk_uint32_t) (p_found - p_start); sce->cidx = (duk_uint32_t) char_offset; /* LRU: move our entry to first */ if (sce > &heap->strcache[0]) { /* * A C * B A * C <- sce ==> B * D D */ duk_strcache tmp; tmp = *sce; DUK_MEMMOVE((void *) (&heap->strcache[1]), (void *) (&heap->strcache[0]), (size_t) (((char *) sce) - ((char *) &heap->strcache[0]))); heap->strcache[0] = tmp; /* 'sce' points to the wrong entry here, but is no longer used */ } #ifdef DUK_USE_DDDPRINT DUK_DDD(DUK_DDDPRINT("stringcache after char2byte (using cache):")); for (i = 0; i < DUK_HEAP_STRCACHE_SIZE; i++) { duk_strcache *c = heap->strcache + i; DUK_DDD(DUK_DDDPRINT(" [%ld] -> h=%p, cidx=%ld, bidx=%ld", (long) i, (void *) c->h, (long) c->cidx, (long) c->bidx)); } #endif } return byte_offset; error: DUK_ERROR(thr, DUK_ERR_INTERNAL_ERROR, "string scan error"); return 0; }
void duk_hbuffer_resize(duk_hthread *thr, duk_hbuffer_dynamic *buf, size_t new_size, size_t new_usable_size) { size_t new_alloc_size; void *res; DUK_ASSERT(thr != NULL); DUK_ASSERT(buf != NULL); DUK_ASSERT(new_usable_size >= new_size); DUK_ASSERT(DUK_HBUFFER_HAS_DYNAMIC(buf)); /* * Maximum size check * * XXX: check against usable size? */ if (new_size > DUK_HBUFFER_MAX_BYTELEN) { DUK_ERROR(thr, DUK_ERR_RANGE_ERROR, "buffer too long"); } /* * Note: use indirect realloc variant just in case mark-and-sweep * (finalizers) might resize this same buffer during garbage * collection. */ /* FIXME: maybe remove safety NUL term for buffers? */ new_alloc_size = new_usable_size + 1; /* +1 for safety nul term */ res = DUK_REALLOC_INDIRECT(thr->heap, duk_hbuffer_get_dynalloc_ptr, (void *) buf, new_alloc_size); if (res) { DUK_DDDPRINT("resized dynamic buffer %p:%d:%d -> %p:%d:%d", buf->curr_alloc, buf->size, buf->usable_size, res, new_size, new_usable_size); /* * The entire allocated buffer area, regardless of actual used size, * is kept zeroed in resizes for simplicity. If the buffer is grown, * zero the new part (the safety NUL byte is re-zeroed every time). * Another policy would be to ensure data is zeroed as the used part * is extended (with one safety NUL byte) this is much more simple, * and not a big deal because the spart part is relatively small. */ if (new_alloc_size > buf->usable_size) { /* When new_usable_size == old_usable_size, one byte will * be rezeroed (the safety NUL byte). */ DUK_ASSERT(new_alloc_size - buf->usable_size > 0); #ifdef DUK_USE_ZERO_BUFFER_DATA DUK_MEMZERO((void *) ((char *) res + buf->usable_size), new_alloc_size - buf->usable_size); #endif } buf->size = new_size; buf->usable_size = new_usable_size; buf->curr_alloc = res; } else { DUK_ERROR(thr, DUK_ERR_ALLOC_ERROR, "failed to resize buffer from %d:%d to %d:%d", buf->size, buf->usable_size, new_size, new_usable_size); } DUK_ASSERT(res != NULL); }
DUK_EXTERNAL void duk_new(duk_context *ctx, duk_idx_t nargs) { /* * There are two [[Construct]] operations in the specification: * * - E5 Section 13.2.2: for Function objects * - E5 Section 15.3.4.5.2: for "bound" Function objects * * The chain of bound functions is resolved in Section 15.3.4.5.2, * with arguments "piling up" until the [[Construct]] internal * method is called on the final, actual Function object. Note * that the "prototype" property is looked up *only* from the * final object, *before* calling the constructor. * * Currently we follow the bound function chain here to get the * "prototype" property value from the final, non-bound function. * However, we let duk_handle_call() handle the argument "piling" * when the constructor is called. The bound function chain is * thus now processed twice. * * When constructing new Array instances, an unnecessary object is * created and discarded now: the standard [[Construct]] creates an * object, and calls the Array constructor. The Array constructor * returns an Array instance, which is used as the result value for * the "new" operation; the object created before the Array constructor * call is discarded. * * This would be easy to fix, e.g. by knowing that the Array constructor * will always create a replacement object and skip creating the fallback * object in that case. * * Note: functions called via "new" need to know they are called as a * constructor. For instance, built-in constructors behave differently * depending on how they are called. */ /* XXX: merge this with duk_js_call.c, as this function implements * core semantics (or perhaps merge the two files altogether). */ duk_hthread *thr = (duk_hthread *) ctx; duk_hobject *proto; duk_hobject *cons; duk_hobject *fallback; duk_idx_t idx_cons; duk_small_uint_t call_flags; DUK_ASSERT_CTX_VALID(ctx); /* [... constructor arg1 ... argN] */ idx_cons = duk_require_normalize_index(ctx, -nargs - 1); DUK_DDD(DUK_DDDPRINT("top=%ld, nargs=%ld, idx_cons=%ld", (long) duk_get_top(ctx), (long) nargs, (long) idx_cons)); /* XXX: code duplication */ /* * Figure out the final, non-bound constructor, to get "prototype" * property. */ duk_dup(ctx, idx_cons); for (;;) { cons = duk_get_hobject(ctx, -1); if (cons == NULL || !DUK_HOBJECT_HAS_CONSTRUCTABLE(cons)) { /* Checking constructability from anything else than the * initial constructor is not strictly necessary, but a * nice sanity check. */ goto not_constructable; } if (!DUK_HOBJECT_HAS_BOUND(cons)) { break; } duk_get_prop_stridx(ctx, -1, DUK_STRIDX_INT_TARGET); /* -> [... cons target] */ duk_remove(ctx, -2); /* -> [... target] */ } DUK_ASSERT(cons != NULL && !DUK_HOBJECT_HAS_BOUND(cons)); /* [... constructor arg1 ... argN final_cons] */ /* * Create "fallback" object to be used as the object instance, * unless the constructor returns a replacement value. * Its internal prototype needs to be set based on "prototype" * property of the constructor. */ duk_push_object(ctx); /* class Object, extensible */ /* [... constructor arg1 ... argN final_cons fallback] */ duk_get_prop_stridx(ctx, -2, DUK_STRIDX_PROTOTYPE); proto = duk_get_hobject(ctx, -1); if (!proto) { DUK_DDD(DUK_DDDPRINT("constructor has no 'prototype' property, or value not an object " "-> leave standard Object prototype as fallback prototype")); } else { DUK_DDD(DUK_DDDPRINT("constructor has 'prototype' property with object value " "-> set fallback prototype to that value: %!iO", (duk_heaphdr *) proto)); fallback = duk_get_hobject(ctx, -2); DUK_ASSERT(fallback != NULL); DUK_HOBJECT_SET_PROTOTYPE_UPDREF(thr, fallback, proto); } duk_pop(ctx); /* [... constructor arg1 ... argN final_cons fallback] */ /* * Manipulate callstack for the call. */ duk_dup_top(ctx); duk_insert(ctx, idx_cons + 1); /* use fallback as 'this' value */ duk_insert(ctx, idx_cons); /* also stash it before constructor, * in case we need it (as the fallback value) */ duk_pop(ctx); /* pop final_cons */ /* [... fallback constructor fallback(this) arg1 ... argN]; * Note: idx_cons points to first 'fallback', not 'constructor'. */ DUK_DDD(DUK_DDDPRINT("before call, idx_cons+1 (constructor) -> %!T, idx_cons+2 (fallback/this) -> %!T, " "nargs=%ld, top=%ld", (duk_tval *) duk_get_tval(ctx, idx_cons + 1), (duk_tval *) duk_get_tval(ctx, idx_cons + 2), (long) nargs, (long) duk_get_top(ctx))); /* * Call the constructor function (called in "constructor mode"). */ call_flags = DUK_CALL_FLAG_CONSTRUCTOR_CALL; /* not protected, respect reclimit, is a constructor call */ duk_handle_call_unprotected(thr, /* thread */ nargs, /* num_stack_args */ call_flags); /* call_flags */ /* [... fallback retval] */ DUK_DDD(DUK_DDDPRINT("constructor call finished, fallback=%!iT, retval=%!iT", (duk_tval *) duk_get_tval(ctx, -2), (duk_tval *) duk_get_tval(ctx, -1))); /* * Determine whether to use the constructor return value as the created * object instance or not. */ if (duk_is_object(ctx, -1)) { duk_remove(ctx, -2); } else { duk_pop(ctx); } /* * Augment created errors upon creation (not when they are thrown or * rethrown). __FILE__ and __LINE__ are not desirable here; the call * stack reflects the caller which is correct. */ #ifdef DUK_USE_AUGMENT_ERROR_CREATE duk_hthread_sync_currpc(thr); duk_err_augment_error_create(thr, thr, NULL, 0, 1 /*noblame_fileline*/); #endif /* [... retval] */ return; not_constructable: DUK_ERROR(thr, DUK_ERR_TYPE_ERROR, DUK_STR_NOT_CONSTRUCTABLE); }
/* Object.defineProperty() equivalent C binding. */ DUK_EXTERNAL void duk_def_prop(duk_context *ctx, duk_idx_t obj_index, duk_uint_t flags) { duk_hthread *thr = (duk_hthread *) ctx; duk_idx_t idx_base; duk_hobject *obj; duk_hstring *key; duk_idx_t idx_value; duk_hobject *get; duk_hobject *set; duk_uint_t is_data_desc; duk_uint_t is_acc_desc; DUK_ASSERT_CTX_VALID(ctx); obj = duk_require_hobject(ctx, obj_index); is_data_desc = flags & (DUK_DEFPROP_HAVE_VALUE | DUK_DEFPROP_HAVE_WRITABLE); is_acc_desc = flags & (DUK_DEFPROP_HAVE_GETTER | DUK_DEFPROP_HAVE_SETTER); if (is_data_desc && is_acc_desc) { /* "Have" flags must not be conflicting so that they would * apply to both a plain property and an accessor at the same * time. */ goto fail_invalid_desc; } idx_base = duk_get_top_index(ctx); if (flags & DUK_DEFPROP_HAVE_SETTER) { duk_require_type_mask(ctx, idx_base, DUK_TYPE_MASK_UNDEFINED | DUK_TYPE_MASK_OBJECT | DUK_TYPE_MASK_LIGHTFUNC); set = duk_get_hobject_or_lfunc_coerce(ctx, idx_base); if (set != NULL && !DUK_HOBJECT_IS_CALLABLE(set)) { goto fail_not_callable; } idx_base--; } else { set = NULL; } if (flags & DUK_DEFPROP_HAVE_GETTER) { duk_require_type_mask(ctx, idx_base, DUK_TYPE_MASK_UNDEFINED | DUK_TYPE_MASK_OBJECT | DUK_TYPE_MASK_LIGHTFUNC); get = duk_get_hobject_or_lfunc_coerce(ctx, idx_base); if (get != NULL && !DUK_HOBJECT_IS_CALLABLE(get)) { goto fail_not_callable; } idx_base--; } else { get = NULL; } if (flags & DUK_DEFPROP_HAVE_VALUE) { idx_value = idx_base; idx_base--; } else { idx_value = (duk_idx_t) -1; } key = duk_require_hstring(ctx, idx_base); duk_require_valid_index(ctx, idx_base); duk_hobject_define_property_helper(ctx, flags /*defprop_flags*/, obj, key, idx_value, get, set); /* Clean up stack */ duk_set_top(ctx, idx_base); /* [ ... obj ... ] */ return; fail_invalid_desc: DUK_ERROR(thr, DUK_ERR_TYPE_ERROR, DUK_STR_INVALID_DESCRIPTOR); return; fail_not_callable: DUK_ERROR(thr, DUK_ERR_TYPE_ERROR, DUK_STR_NOT_CALLABLE); return; }