DUK_INTERNAL duk_bool_t duk_js_in(duk_hthread *thr, duk_tval *tv_x, duk_tval *tv_y) { duk_context *ctx = (duk_context *) thr; duk_bool_t retval; /* * Get the values onto the stack first. It would be possible to cover * some normal cases without resorting to the value stack (e.g. if * lval is already a string). */ /* XXX: The ES5/5.1/6 specifications require that the key in 'key in obj' * must be string coerced before the internal HasProperty() algorithm is * invoked. A fast path skipping coercion could be safely implemented for * numbers (as number-to-string coercion has no side effects). For ES6 * proxy behavior, the trap 'key' argument must be in a string coerced * form (which is a shame). */ /* TypeError if rval is not an object (or lightfunc which should behave * like a Function instance). */ duk_push_tval(ctx, tv_x); duk_push_tval(ctx, tv_y); duk_require_type_mask(ctx, -1, DUK_TYPE_MASK_OBJECT | DUK_TYPE_MASK_LIGHTFUNC); duk_to_string(ctx, -2); /* coerce lval with ToString() */ retval = duk_hobject_hasprop(thr, duk_get_tval(ctx, -1), duk_get_tval(ctx, -2)); duk_pop_2(ctx); return retval; }
DUK_LOCAL duk_ret_t duk__error_setter_helper(duk_context *ctx, duk_small_uint_t stridx_key) { /* Attempt to write 'stack', 'fileName', 'lineNumber' works as if * user code called Object.defineProperty() to create an overriding * own property. This allows user code to overwrite .fileName etc * intuitively as e.g. "err.fileName = 'dummy'" as one might expect. * See https://github.com/svaarala/duktape/issues/387. */ DUK_ASSERT_TOP(ctx, 1); /* fixed arg count: value */ duk_push_this(ctx); duk_push_hstring_stridx(ctx, (duk_small_int_t) stridx_key); duk_dup_0(ctx); /* [ ... obj key value ] */ DUK_DD(DUK_DDPRINT("error setter: %!T %!T %!T", duk_get_tval(ctx, -3), duk_get_tval(ctx, -2), duk_get_tval(ctx, -1))); duk_def_prop(ctx, -3, DUK_DEFPROP_HAVE_VALUE | DUK_DEFPROP_HAVE_WRITABLE | DUK_DEFPROP_WRITABLE | DUK_DEFPROP_HAVE_ENUMERABLE | /*not enumerable*/ DUK_DEFPROP_HAVE_CONFIGURABLE | DUK_DEFPROP_CONFIGURABLE); return 0; }
DUK_LOCAL void duk__add_compiler_error_line(duk_hthread *thr) { duk_context *ctx; /* Append a "(line NNN)" to the "message" property of any error * thrown during compilation. Usually compilation errors are * SyntaxErrors but they can also be out-of-memory errors and * the like. */ /* [ ... error ] */ ctx = (duk_context *) thr; DUK_ASSERT(duk_is_object(ctx, -1)); if (!(thr->compile_ctx != NULL && thr->compile_ctx->h_filename != NULL)) { return; } DUK_DDD(DUK_DDDPRINT("compile error, before adding line info: %!T", (duk_tval *) duk_get_tval(ctx, -1))); if (duk_get_prop_stridx(ctx, -1, DUK_STRIDX_MESSAGE)) { duk_push_sprintf(ctx, " (line %ld)", (long) thr->compile_ctx->curr_token.start_line); duk_concat(ctx, 2); duk_put_prop_stridx(ctx, -2, DUK_STRIDX_MESSAGE); } else { duk_pop(ctx); } DUK_DDD(DUK_DDDPRINT("compile error, after adding line info: %!T", (duk_tval *) duk_get_tval(ctx, -1))); }
DUK_INTERNAL duk_ret_t duk_bi_function_prototype_call(duk_context *ctx) { duk_idx_t nargs; /* Step 1 is not necessary because duk_call_method() will take * care of it. */ /* vararg function, thisArg needs special handling */ nargs = duk_get_top(ctx); /* = 1 + arg count */ if (nargs == 0) { duk_push_undefined(ctx); nargs++; } DUK_ASSERT(nargs >= 1); /* [ thisArg arg1 ... argN ] */ duk_push_this(ctx); /* 'func' in the algorithm */ duk_insert(ctx, 0); /* [ func thisArg arg1 ... argN ] */ DUK_DDD(DUK_DDDPRINT("func=%!iT, thisArg=%!iT, argcount=%ld, top=%ld", (duk_tval *) duk_get_tval(ctx, 0), (duk_tval *) duk_get_tval(ctx, 1), (long) (nargs - 1), (long) duk_get_top(ctx))); duk_call_method(ctx, nargs - 1); return 1; }
DUK_INTERNAL duk_ret_t duk_bi_array_prototype_to_string(duk_context *ctx) { (void) duk_push_this_coercible_to_object(ctx); duk_get_prop_stridx(ctx, -1, DUK_STRIDX_JOIN); /* [ ... this func ] */ if (!duk_is_callable(ctx, -1)) { /* Fall back to the initial (original) Object.toString(). We don't * currently have pointers to the built-in functions, only the top * level global objects (like "Array") so this is now done in a bit * of a hacky manner. It would be cleaner to push the (original) * function and use duk_call_method(). */ /* XXX: 'this' will be ToObject() coerced twice, which is incorrect * but should have no visible side effects. */ DUK_DDD(DUK_DDDPRINT("this.join is not callable, fall back to (original) Object.toString")); duk_set_top(ctx, 0); return duk_bi_object_prototype_to_string(ctx); /* has access to 'this' binding */ } /* [ ... this func ] */ duk_insert(ctx, -2); /* [ ... func this ] */ DUK_DDD(DUK_DDDPRINT("calling: func=%!iT, this=%!iT", (duk_tval *) duk_get_tval(ctx, -2), (duk_tval *) duk_get_tval(ctx, -1))); duk_call_method(ctx, 0); return 1; }
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_INTERNAL duk_ret_t duk_bi_function_prototype_apply(duk_context *ctx) { duk_idx_t len; duk_idx_t i; DUK_ASSERT_TOP(ctx, 2); /* not a vararg function */ duk_push_this(ctx); if (!duk_is_callable(ctx, -1)) { DUK_DDD(DUK_DDDPRINT("func is not callable")); goto type_error; } duk_insert(ctx, 0); DUK_ASSERT_TOP(ctx, 3); DUK_DDD(DUK_DDDPRINT("func=%!iT, thisArg=%!iT, argArray=%!iT", (duk_tval *) duk_get_tval(ctx, 0), (duk_tval *) duk_get_tval(ctx, 1), (duk_tval *) duk_get_tval(ctx, 2))); /* [ func thisArg argArray ] */ if (duk_is_null_or_undefined(ctx, 2)) { DUK_DDD(DUK_DDDPRINT("argArray is null/undefined, no args")); len = 0; } else if (!duk_is_object(ctx, 2)) { goto type_error; } else { DUK_DDD(DUK_DDDPRINT("argArray is an object")); /* XXX: make this an internal helper */ duk_get_prop_stridx(ctx, 2, DUK_STRIDX_LENGTH); len = (duk_idx_t) duk_to_uint32(ctx, -1); /* ToUint32() coercion required */ duk_pop(ctx); duk_require_stack(ctx, len); DUK_DDD(DUK_DDDPRINT("argArray length is %ld", (long) len)); for (i = 0; i < len; i++) { duk_get_prop_index(ctx, 2, i); } } duk_remove(ctx, 2); DUK_ASSERT_TOP(ctx, 2 + len); /* [ func thisArg arg1 ... argN ] */ DUK_DDD(DUK_DDDPRINT("apply, func=%!iT, thisArg=%!iT, len=%ld", (duk_tval *) duk_get_tval(ctx, 0), (duk_tval *) duk_get_tval(ctx, 1), (long) len)); duk_call_method(ctx, len); return 1; type_error: return DUK_RET_TYPE_ERROR; }
/* Shared helper to provide toString() and valueOf(). Checks 'this', gets * the primitive value to stack top, and optionally coerces with ToString(). */ duk_ret_t duk_bi_boolean_prototype_tostring_shared(duk_context *ctx) { duk_tval *tv; duk_hobject *h; duk_small_int_t coerce_tostring = duk_get_magic(ctx); /* FIXME: there is room to use a shared helper here, many built-ins * check the 'this' type, and if it's an object, check its class, * then get its internal value, etc. */ duk_push_this(ctx); tv = duk_get_tval(ctx, -1); DUK_ASSERT(tv != NULL); if (DUK_TVAL_IS_BOOLEAN(tv)) { goto type_ok; } else if (DUK_TVAL_IS_OBJECT(tv)) { h = DUK_TVAL_GET_OBJECT(tv); DUK_ASSERT(h != NULL); if (DUK_HOBJECT_GET_CLASS_NUMBER(h) == DUK_HOBJECT_CLASS_BOOLEAN) { duk_get_prop_stridx(ctx, -1, DUK_STRIDX_INT_VALUE); DUK_ASSERT(duk_is_boolean(ctx, -1)); goto type_ok; } } return DUK_RET_TYPE_ERROR; type_ok: if (coerce_tostring) { duk_to_string(ctx, -1); } return 1; }
/* Prepare value stack for a method call through an object property. * May currently throw an error e.g. when getting the property. */ DUK_LOCAL void duk__call_prop_prep_stack(duk_context *ctx, duk_idx_t normalized_obj_idx, duk_idx_t nargs) { DUK_ASSERT_CTX_VALID(ctx); DUK_DDD(DUK_DDDPRINT("duk__call_prop_prep_stack, normalized_obj_idx=%ld, nargs=%ld, stacktop=%ld", (long) normalized_obj_idx, (long) nargs, (long) duk_get_top(ctx))); /* [... key arg1 ... argN] */ /* duplicate key */ duk_dup(ctx, -nargs - 1); /* Note: -nargs alone would fail for nargs == 0, this is OK */ duk_get_prop(ctx, normalized_obj_idx); DUK_DDD(DUK_DDDPRINT("func: %!T", (duk_tval *) duk_get_tval(ctx, -1))); /* [... key arg1 ... argN func] */ duk_replace(ctx, -nargs - 2); /* [... func arg1 ... argN] */ duk_dup(ctx, normalized_obj_idx); duk_insert(ctx, -nargs - 1); /* [... func this arg1 ... argN] */ }
DUK_INTERNAL duk_ret_t duk_bi_string_prototype_char_code_at(duk_context *ctx) { duk_hthread *thr = (duk_hthread *) ctx; duk_int_t pos; duk_hstring *h; duk_bool_t clamped; /* XXX: faster implementation */ DUK_DDD(DUK_DDDPRINT("arg=%!T", (duk_tval *) duk_get_tval(ctx, 0))); h = duk_push_this_coercible_to_string(ctx); DUK_ASSERT(h != NULL); pos = duk_to_int_clamped_raw(ctx, 0 /*index*/, 0 /*min(incl)*/, DUK_HSTRING_GET_CHARLEN(h) - 1 /*max(incl)*/, &clamped /*out_clamped*/); if (clamped) { duk_push_number(ctx, DUK_DOUBLE_NAN); return 1; } duk_push_u32(ctx, (duk_uint32_t) duk_hstring_char_code_at_raw(thr, h, pos)); return 1; }
DUK_EXTERNAL void duk_dump_function(duk_context *ctx) { duk_hthread *thr; duk_hcompiledfunction *func; duk_bufwriter_ctx bw_ctx_alloc; duk_bufwriter_ctx *bw_ctx = &bw_ctx_alloc; duk_uint8_t *p; DUK_ASSERT(ctx != NULL); thr = (duk_hthread *) ctx; /* Bound functions don't have all properties so we'd either need to * lookup the non-bound target function or reject bound functions. * For now, bound functions are rejected. */ func = duk_require_hcompiledfunction(ctx, -1); DUK_ASSERT(func != NULL); DUK_ASSERT(!DUK_HOBJECT_HAS_BOUND(&func->obj)); /* Estimating the result size beforehand would be costly, so * start with a reasonable size and extend as needed. */ DUK_BW_INIT_PUSHBUF(thr, bw_ctx, DUK__BYTECODE_INITIAL_ALLOC); p = DUK_BW_GET_PTR(thr, bw_ctx); *p++ = DUK__SER_MARKER; *p++ = DUK__SER_VERSION; p = duk__dump_func(ctx, func, bw_ctx, p); DUK_BW_SET_PTR(thr, bw_ctx, p); DUK_BW_COMPACT(thr, bw_ctx); DUK_DD(DUK_DDPRINT("serialized result: %!T", duk_get_tval(ctx, -1))); duk_remove(ctx, -2); /* [ ... func buf ] -> [ ... buf ] */ }
DUK_INTERNAL void duk_hobject_run_finalizer(duk_hthread *thr, duk_hobject *obj) { duk_context *ctx = (duk_context *) thr; duk_ret_t rc; #if defined(DUK_USE_ASSERTIONS) duk_idx_t entry_top; #endif DUK_DDD(DUK_DDDPRINT("running object finalizer for object: %p", (void *) obj)); DUK_ASSERT(thr != NULL); DUK_ASSERT(ctx != NULL); DUK_ASSERT(obj != NULL); DUK_ASSERT_VALSTACK_SPACE(thr, 1); #if defined(DUK_USE_ASSERTIONS) entry_top = duk_get_top(ctx); #endif /* * Get and call the finalizer. All of this must be wrapped * in a protected call, because even getting the finalizer * may trigger an error (getter may throw one, for instance). */ DUK_ASSERT(!DUK_HEAPHDR_HAS_READONLY((duk_heaphdr *) obj)); if (DUK_HEAPHDR_HAS_FINALIZED((duk_heaphdr *) obj)) { DUK_D(DUK_DPRINT("object already finalized, avoid running finalizer twice: %!O", obj)); return; } DUK_HEAPHDR_SET_FINALIZED((duk_heaphdr *) obj); /* ensure never re-entered until rescue cycle complete */ if (DUK_HOBJECT_HAS_EXOTIC_PROXYOBJ(obj)) { /* This shouldn't happen; call sites should avoid looking up * _Finalizer "through" a Proxy, but ignore if we come here * with a Proxy to avoid finalizer re-entry. */ DUK_D(DUK_DPRINT("object is a proxy, skip finalizer call")); return; } /* XXX: use a NULL error handler for the finalizer call? */ DUK_DDD(DUK_DDDPRINT("-> finalizer found, calling wrapped finalize helper")); duk_push_hobject(ctx, obj); /* this also increases refcount by one */ rc = duk_safe_call(ctx, duk__finalize_helper, NULL /*udata*/, 0 /*nargs*/, 1 /*nrets*/); /* -> [... obj retval/error] */ DUK_ASSERT_TOP(ctx, entry_top + 2); /* duk_safe_call discipline */ if (rc != DUK_EXEC_SUCCESS) { /* Note: we ask for one return value from duk_safe_call to get this * error debugging here. */ DUK_D(DUK_DPRINT("wrapped finalizer call failed for object %p (ignored); error: %!T", (void *) obj, (duk_tval *) duk_get_tval(ctx, -1))); } duk_pop_2(ctx); /* -> [...] */ DUK_ASSERT_TOP(ctx, entry_top); }
DUK_LOCAL duk_ret_t duk__refcount_fake_finalizer(duk_context *ctx) { DUK_UNREF(ctx); DUK_D(DUK_DPRINT("fake refcount torture finalizer executed")); #if 0 DUK_DD(DUK_DDPRINT("fake torture finalizer for: %!T", duk_get_tval(ctx, 0))); #endif /* Require a lot of stack to force a value stack grow/shrink. */ duk_require_stack(ctx, 100000); /* XXX: do something to force a callstack grow/shrink, perhaps * just a manual forced resize? */ return 0; }
int duk_builtin_duk_object_addr(duk_context *ctx) { duk_tval *tv; void *p; tv = duk_get_tval(ctx, 0); if (!tv || !DUK_TVAL_IS_HEAP_ALLOCATED(tv)) { return 0; /* undefined */ } p = (void *) DUK_TVAL_GET_HEAPHDR(tv); /* any heap allocated value (string, object, buffer) has a stable pointer */ duk_push_sprintf(ctx, "%p", p); return 1; }
int duk_builtin_object_constructor_create(duk_context *ctx) { duk_hthread *thr = (duk_hthread *) ctx; duk_tval *tv; duk_hobject *proto = NULL; duk_hobject *h; DUK_ASSERT_TOP(ctx, 2); tv = duk_get_tval(ctx, 0); DUK_ASSERT(tv != NULL); if (DUK_TVAL_IS_NULL(tv)) { ; } else if (DUK_TVAL_IS_OBJECT(tv)) { proto = DUK_TVAL_GET_OBJECT(tv); DUK_ASSERT(proto != NULL); } else { return DUK_RET_TYPE_ERROR; } /* FIXME: direct helper to create with specific prototype */ (void) duk_push_object_helper(ctx, DUK_HOBJECT_FLAG_EXTENSIBLE | DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_OBJECT), -1); h = duk_get_hobject(ctx, -1); DUK_ASSERT(h != NULL); DUK_ASSERT(h->prototype == NULL); DUK_HOBJECT_SET_PROTOTYPE(thr, h, proto); if (!duk_is_undefined(ctx, 1)) { /* [ O Properties obj ] */ /* Use original function. No need to get it explicitly, * just call the helper. */ duk_replace(ctx, 0); /* [ obj Properties ] */ return duk_hobject_object_define_properties(ctx); } /* [ O Properties obj ] */ return 1; }
int duk_builtin_duk_object_refc(duk_context *ctx) { #ifdef DUK_USE_REFERENCE_COUNTING duk_tval *tv = duk_get_tval(ctx, 0); duk_heaphdr *h; if (!tv) { return 0; } if (!DUK_TVAL_IS_HEAP_ALLOCATED(tv)) { return 0; } h = DUK_TVAL_GET_HEAPHDR(tv); duk_push_int(ctx, DUK_HEAPHDR_GET_REFCOUNT(h)); return 1; #else return 0; #endif }
void duk_hobject_run_finalizer(duk_hthread *thr, duk_hobject *obj) { duk_context *ctx = (duk_context *) thr; int rc; #ifdef DUK_USE_ASSERTIONS int entry_top; #endif DUK_DDDPRINT("running object finalizer for object: %p", (void *) obj); DUK_ASSERT(thr != NULL); DUK_ASSERT(ctx != NULL); DUK_ASSERT(obj != NULL); /* FIXME: assert stack space */ #ifdef DUK_USE_ASSERTIONS entry_top = duk_get_top(ctx); #endif /* * Get and call the finalizer. All of this must be wrapped * in a protected call, because even getting the finalizer * may trigger an error (getter may throw one, for instance). */ /* FIXME: use a NULL error handler for the finalizer call? */ DUK_DDDPRINT("-> finalizer found, calling wrapped finalize helper"); duk_push_hobject(ctx, obj); /* this also increases refcount by one */ rc = duk_safe_call(ctx, _finalize_helper, 0 /*nargs*/, 1 /*nrets*/, DUK_INVALID_INDEX); /* -> [... obj retval/error] */ DUK_ASSERT_TOP(ctx, entry_top + 2); /* duk_safe_call discipline */ if (rc != DUK_ERR_EXEC_SUCCESS) { /* Note: we ask for one return value from duk_safe_call to get this * error debugging here. */ DUK_DPRINT("wrapped finalizer call failed for object %p (ignored); error: %!T", (void *) obj, duk_get_tval(ctx, -1)); } duk_pop_2(ctx); /* -> [...] */ DUK_ASSERT_TOP(ctx, entry_top); }
/* Magic: 0=charCodeAt, 1=codePointAt */ DUK_INTERNAL duk_ret_t duk_bi_string_prototype_char_code_at(duk_context *ctx) { duk_hthread *thr = (duk_hthread *) ctx; duk_int_t pos; duk_hstring *h; duk_bool_t clamped; duk_uint32_t cp; duk_int_t magic; /* XXX: faster implementation */ DUK_DDD(DUK_DDDPRINT("arg=%!T", (duk_tval *) duk_get_tval(ctx, 0))); h = duk_push_this_coercible_to_string(ctx); DUK_ASSERT(h != NULL); pos = duk_to_int_clamped_raw(ctx, 0 /*index*/, 0 /*min(incl)*/, DUK_HSTRING_GET_CHARLEN(h) - 1 /*max(incl)*/, &clamped /*out_clamped*/); #if defined(DUK_USE_ES6) magic = duk_get_current_magic(ctx); #else DUK_ASSERT(duk_get_current_magic(ctx) == 0); magic = 0; #endif if (clamped) { /* For out-of-bounds indices .charCodeAt() returns NaN and * .codePointAt() returns undefined. */ if (magic != 0) { return 0; } duk_push_nan(ctx); } else { cp = (duk_uint32_t) duk_hstring_char_code_at_raw(thr, h, pos, (duk_bool_t) magic /*surrogate_aware*/); duk_push_u32(ctx, cp); } return 1; }
DUK_INTERNAL duk_ret_t duk_bi_object_constructor_create(duk_context *ctx) { duk_tval *tv; duk_hobject *proto = NULL; DUK_ASSERT_TOP(ctx, 2); tv = duk_get_tval(ctx, 0); DUK_ASSERT(tv != NULL); if (DUK_TVAL_IS_NULL(tv)) { ; } else if (DUK_TVAL_IS_OBJECT(tv)) { proto = DUK_TVAL_GET_OBJECT(tv); DUK_ASSERT(proto != NULL); } else { return DUK_RET_TYPE_ERROR; } (void) duk_push_object_helper_proto(ctx, DUK_HOBJECT_FLAG_EXTENSIBLE | DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_OBJECT), proto); if (!duk_is_undefined(ctx, 1)) { /* [ O Properties obj ] */ /* Use original function. No need to get it explicitly, * just call the helper. */ duk_replace(ctx, 0); /* [ obj Properties ] */ return duk_hobject_object_define_properties(ctx); } /* [ O Properties obj ] */ return 1; }
/* Raw helper to extract internal information / statistics about a value. * The return values are version specific and must not expose anything * that would lead to security issues (e.g. exposing compiled function * 'data' buffer might be an issue). Currently only counts and sizes and * such are given so there should not be a security impact. */ duk_ret_t duk_bi_duktape_object_info(duk_context *ctx) { duk_tval *tv; duk_heaphdr *h; duk_int_t i, n; tv = duk_get_tval(ctx, 0); DUK_ASSERT(tv != NULL); /* because arg count is 1 */ duk_push_array(ctx); /* -> [ val arr ] */ /* type tag (public) */ duk_push_int(ctx, duk_get_type(ctx, 0)); /* address */ if (DUK_TVAL_IS_HEAP_ALLOCATED(tv)) { h = DUK_TVAL_GET_HEAPHDR(tv); duk_push_pointer(ctx, (void *) h); } else { goto done; } DUK_ASSERT(h != NULL); /* refcount */ #ifdef DUK_USE_REFERENCE_COUNTING duk_push_int(ctx, DUK_HEAPHDR_GET_REFCOUNT(h)); #else duk_push_undefined(ctx); #endif /* heaphdr size and additional allocation size, followed by * type specific stuff (with varying value count) */ switch ((duk_small_int_t) DUK_HEAPHDR_GET_TYPE(h)) { case DUK_HTYPE_STRING: { duk_hstring *h_str = (duk_hstring *) h; duk_push_int(ctx, (int) (sizeof(duk_hstring) + DUK_HSTRING_GET_BYTELEN(h_str) + 1)); break; } case DUK_HTYPE_OBJECT: { duk_hobject *h_obj = (duk_hobject *) h; duk_int_t hdr_size; if (DUK_HOBJECT_IS_COMPILEDFUNCTION(h_obj)) { hdr_size = (duk_int_t) sizeof(duk_hcompiledfunction); } else if (DUK_HOBJECT_IS_NATIVEFUNCTION(h_obj)) { hdr_size = (duk_int_t) sizeof(duk_hnativefunction); } else if (DUK_HOBJECT_IS_THREAD(h_obj)) { hdr_size = (duk_int_t) sizeof(duk_hthread); } else { hdr_size = (duk_int_t) sizeof(duk_hobject); } duk_push_int(ctx, (int) hdr_size); duk_push_int(ctx, (int) DUK_HOBJECT_E_ALLOC_SIZE(h_obj)); duk_push_int(ctx, (int) h_obj->e_size); duk_push_int(ctx, (int) h_obj->e_used); duk_push_int(ctx, (int) h_obj->a_size); duk_push_int(ctx, (int) h_obj->h_size); if (DUK_HOBJECT_IS_COMPILEDFUNCTION(h_obj)) { duk_hbuffer *h_data = ((duk_hcompiledfunction *) h_obj)->data; if (h_data) { duk_push_int(ctx, DUK_HBUFFER_GET_SIZE(h_data)); } else { duk_push_int(ctx, 0); } } break; } case DUK_HTYPE_BUFFER: { duk_hbuffer *h_buf = (duk_hbuffer *) h; if (DUK_HBUFFER_HAS_DYNAMIC(h_buf)) { /* XXX: when usable_size == 0, dynamic buf ptr may now be NULL, in which case * the second allocation does not exist. */ duk_hbuffer_dynamic *h_dyn = (duk_hbuffer_dynamic *) h; duk_push_int(ctx, (int) (sizeof(duk_hbuffer_dynamic))); duk_push_int(ctx, (int) (DUK_HBUFFER_DYNAMIC_GET_ALLOC_SIZE(h_dyn))); } else { duk_push_int(ctx, (int) (sizeof(duk_hbuffer_fixed) + DUK_HBUFFER_GET_SIZE(h_buf) + 1)); } break; } } done: /* set values into ret array */ /* FIXME: primitive to make array from valstack slice */ n = duk_get_top(ctx); for (i = 2; i < n; i++) { duk_dup(ctx, i); duk_put_prop_index(ctx, 1, i - 2); } duk_dup(ctx, 1); return 1; }
DUK_LOCAL void duk__add_traceback(duk_hthread *thr, duk_hthread *thr_callstack, const char *c_filename, duk_int_t c_line, duk_bool_t noblame_fileline) { duk_context *ctx = (duk_context *) thr; duk_small_uint_t depth; duk_int_t i, i_min; duk_uarridx_t arr_idx; duk_double_t d; DUK_ASSERT(thr != NULL); DUK_ASSERT(thr_callstack != NULL); DUK_ASSERT(ctx != NULL); /* [ ... error ] */ /* * The traceback format is pretty arcane in an attempt to keep it compact * and cheap to create. It may change arbitrarily from version to version. * It should be decoded/accessed through version specific accessors only. * * See doc/error-objects.rst. */ DUK_DDD(DUK_DDDPRINT("adding traceback to object: %!T", (duk_tval *) duk_get_tval(ctx, -1))); duk_push_array(ctx); /* XXX: specify array size, as we know it */ arr_idx = 0; /* Compiler SyntaxErrors (and other errors) come first, and are * blamed by default (not flagged "noblame"). */ if (thr->compile_ctx != NULL && thr->compile_ctx->h_filename != NULL) { duk_push_hstring(ctx, thr->compile_ctx->h_filename); duk_xdef_prop_index_wec(ctx, -2, arr_idx); arr_idx++; duk_push_uint(ctx, (duk_uint_t) thr->compile_ctx->curr_token.start_line); /* (flags<<32) + (line), flags = 0 */ duk_xdef_prop_index_wec(ctx, -2, arr_idx); arr_idx++; } /* Filename/line from C macros (__FILE__, __LINE__) are added as an * entry with a special format: (string, number). The number contains * the line and flags. */ /* XXX: optimize: allocate an array part to the necessary size (upwards * estimate) and fill in the values directly into the array part; finally * update 'length'. */ /* XXX: using duk_put_prop_index() would cause obscure error cases when Array.prototype * has write-protected array index named properties. This was seen as DoubleErrors * in e.g. some test262 test cases. Using duk_xdef_prop_index() is better but heavier. * The best fix is to fill in the tracedata directly into the array part. There are * no side effect concerns if the array part is allocated directly and only INCREFs * happen after that. */ /* [ ... error arr ] */ if (c_filename) { duk_push_string(ctx, c_filename); duk_xdef_prop_index_wec(ctx, -2, arr_idx); arr_idx++; d = (noblame_fileline ? ((duk_double_t) DUK_TB_FLAG_NOBLAME_FILELINE) * DUK_DOUBLE_2TO32 : 0.0) + (duk_double_t) c_line; duk_push_number(ctx, d); duk_xdef_prop_index_wec(ctx, -2, arr_idx); arr_idx++; } /* traceback depth doesn't take into account the filename/line * special handling above (intentional) */ depth = DUK_USE_TRACEBACK_DEPTH; i_min = (thr_callstack->callstack_top > (duk_size_t) depth ? (duk_int_t) (thr_callstack->callstack_top - depth) : 0); DUK_ASSERT(i_min >= 0); /* [ ... error arr ] */ DUK_ASSERT(thr_callstack->callstack_top <= DUK_INT_MAX); /* callstack limits */ for (i = (duk_int_t) (thr_callstack->callstack_top - 1); i >= i_min; i--) { duk_uint32_t pc; /* * Note: each API operation potentially resizes the callstack, * so be careful to re-lookup after every operation. Currently * these is no issue because we don't store a temporary 'act' * pointer at all. (This would be a non-issue if we operated * directly on the array part.) */ /* [... arr] */ DUK_ASSERT_DISABLE(thr_callstack->callstack[i].pc >= 0); /* unsigned */ /* Add function object. */ duk_push_tval(ctx, &(thr_callstack->callstack + i)->tv_func); duk_xdef_prop_index_wec(ctx, -2, arr_idx); arr_idx++; /* Add a number containing: pc, activation flags. * * PC points to next instruction, find offending PC. Note that * PC == 0 for native code. */ pc = duk_hthread_get_act_prev_pc(thr_callstack, thr_callstack->callstack + i); DUK_ASSERT_DISABLE(pc >= 0); /* unsigned */ DUK_ASSERT((duk_double_t) pc < DUK_DOUBLE_2TO32); /* assume PC is at most 32 bits and non-negative */ d = ((duk_double_t) thr_callstack->callstack[i].flags) * DUK_DOUBLE_2TO32 + (duk_double_t) pc; duk_push_number(ctx, d); /* -> [... arr num] */ duk_xdef_prop_index_wec(ctx, -2, arr_idx); arr_idx++; } /* XXX: set with duk_hobject_set_length() when tracedata is filled directly */ duk_push_uint(ctx, (duk_uint_t) arr_idx); duk_xdef_prop_stridx(ctx, -2, DUK_STRIDX_LENGTH, DUK_PROPDESC_FLAGS_WC); /* [ ... error arr ] */ duk_xdef_prop_stridx_wec(ctx, -2, DUK_STRIDX_INT_TRACEDATA); /* -> [ ... error ] */ }
/* * Returns non-zero if a key and/or value was enumerated, and: * * [enum] -> [key] (get_value == 0) * [enum] -> [key value] (get_value == 1) * * Returns zero without pushing anything on the stack otherwise. */ duk_bool_t duk_hobject_enumerator_next(duk_context *ctx, duk_bool_t get_value) { duk_hthread *thr = (duk_hthread *) ctx; duk_hobject *e; duk_hobject *enum_target; duk_hstring *res = NULL; duk_uint_fast32_t idx; duk_bool_t check_existence; DUK_ASSERT(ctx != NULL); /* [... enum] */ e = duk_require_hobject(ctx, -1); /* XXX use get tval ptr, more efficient */ duk_get_prop_stridx(ctx, -1, DUK_STRIDX_INT_NEXT); idx = (duk_uint_fast32_t) duk_require_uint(ctx, -1); duk_pop(ctx); DUK_DDD(DUK_DDDPRINT("enumeration: index is: %ld", (long) idx)); /* Enumeration keys are checked against the enumeration target (to see * that they still exist). In the proxy enumeration case _target will * be the proxy, and checking key existence against the proxy is not * required (or sensible, as the keys may be fully virtual). */ duk_get_prop_stridx(ctx, -1, DUK_STRIDX_INT_TARGET); enum_target = duk_require_hobject(ctx, -1); DUK_ASSERT(enum_target != NULL); #if defined(DUK_USE_ES6_PROXY) check_existence = (!DUK_HOBJECT_HAS_EXOTIC_PROXYOBJ(enum_target)); #else check_existence = 1; #endif duk_pop(ctx); /* still reachable */ DUK_DDD(DUK_DDDPRINT("getting next enum value, enum_target=%!iO, enumerator=%!iT", (duk_heaphdr *) enum_target, (duk_tval *) duk_get_tval(ctx, -1))); /* no array part */ for (;;) { duk_hstring *k; if (idx >= e->e_next) { DUK_DDD(DUK_DDDPRINT("enumeration: ran out of elements")); break; } /* we know these because enum objects are internally created */ k = DUK_HOBJECT_E_GET_KEY(e, idx); DUK_ASSERT(k != NULL); DUK_ASSERT(!DUK_HOBJECT_E_SLOT_IS_ACCESSOR(e, idx)); DUK_ASSERT(!DUK_TVAL_IS_UNDEFINED_UNUSED(&DUK_HOBJECT_E_GET_VALUE(e, idx).v)); idx++; /* recheck that the property still exists */ if (check_existence && !duk_hobject_hasprop_raw(thr, enum_target, k)) { DUK_DDD(DUK_DDDPRINT("property deleted during enumeration, skip")); continue; } DUK_DDD(DUK_DDDPRINT("enumeration: found element, key: %!O", (duk_heaphdr *) k)); res = k; break; } DUK_DDD(DUK_DDDPRINT("enumeration: updating next index to %ld", (long) idx)); duk_push_number(ctx, (double) idx); duk_put_prop_stridx(ctx, -2, DUK_STRIDX_INT_NEXT); /* [... enum] */ if (res) { duk_push_hstring(ctx, res); if (get_value) { duk_push_hobject(ctx, enum_target); duk_dup(ctx, -2); /* -> [... enum key enum_target key] */ duk_get_prop(ctx, -2); /* -> [... enum key enum_target val] */ duk_remove(ctx, -2); /* -> [... enum key val] */ duk_remove(ctx, -3); /* -> [... key val] */ } else { duk_remove(ctx, -2); /* -> [... key] */ } return 1; } else { duk_pop(ctx); /* -> [...] */ return 0; } }
void duk_hobject_enumerator_create(duk_context *ctx, duk_small_uint_t enum_flags) { duk_hthread *thr = (duk_hthread *) ctx; duk_hobject *enum_target; duk_hobject *curr; duk_hobject *res; #if defined(DUK_USE_ES6_PROXY) duk_hobject *h_proxy_target; duk_hobject *h_proxy_handler; duk_hobject *h_trap_result; #endif duk_uint_fast32_t i, len; /* used for array, stack, and entry indices */ DUK_ASSERT(ctx != NULL); DUK_DDD(DUK_DDDPRINT("create enumerator, stack top: %ld", (long) duk_get_top(ctx))); enum_target = duk_require_hobject(ctx, -1); DUK_ASSERT(enum_target != NULL); duk_push_object_internal(ctx); res = duk_require_hobject(ctx, -1); DUK_DDD(DUK_DDDPRINT("created internal object")); /* [enum_target res] */ /* Target must be stored so that we can recheck whether or not * keys still exist when we enumerate. This is not done if the * enumeration result comes from a proxy trap as there is no * real object to check against. */ duk_push_hobject(ctx, enum_target); duk_put_prop_stridx(ctx, -2, DUK_STRIDX_INT_TARGET); /* Initialize index so that we skip internal control keys. */ duk_push_int(ctx, DUK__ENUM_START_INDEX); duk_put_prop_stridx(ctx, -2, DUK_STRIDX_INT_NEXT); /* * Proxy object handling */ #if defined(DUK_USE_ES6_PROXY) if (DUK_LIKELY((enum_flags & DUK_ENUM_NO_PROXY_BEHAVIOR) != 0)) { goto skip_proxy; } if (DUK_LIKELY(!duk_hobject_proxy_check(thr, enum_target, &h_proxy_target, &h_proxy_handler))) { goto skip_proxy; } DUK_DDD(DUK_DDDPRINT("proxy enumeration")); duk_push_hobject(ctx, h_proxy_handler); if (!duk_get_prop_stridx(ctx, -1, DUK_STRIDX_ENUMERATE)) { /* No need to replace the 'enum_target' value in stack, only the * enum_target reference. This also ensures that the original * enum target is reachable, which keeps the proxy and the proxy * target reachable. We do need to replace the internal _target. */ DUK_DDD(DUK_DDDPRINT("no enumerate trap, enumerate proxy target instead")); DUK_DDD(DUK_DDDPRINT("h_proxy_target=%!O", (duk_heaphdr *) h_proxy_target)); enum_target = h_proxy_target; duk_push_hobject(ctx, enum_target); /* -> [ ... enum_target res handler undefined target ] */ duk_put_prop_stridx(ctx, -4, DUK_STRIDX_INT_TARGET); duk_pop_2(ctx); /* -> [ ... enum_target res ] */ goto skip_proxy; } /* [ ... enum_target res handler trap ] */ duk_insert(ctx, -2); duk_push_hobject(ctx, h_proxy_target); /* -> [ ... enum_target res trap handler target ] */ duk_call_method(ctx, 1 /*nargs*/); /* -> [ ... enum_target res trap_result ] */ h_trap_result = duk_require_hobject(ctx, -1); DUK_UNREF(h_trap_result); /* Copy trap result keys into the enumerator object. */ len = (duk_uint_fast32_t) duk_get_length(ctx, -1); for (i = 0; i < len; i++) { /* XXX: not sure what the correct semantic details are here, * e.g. handling of missing values (gaps), handling of non-array * trap results, etc. * * For keys, we simply skip non-string keys which seems to be * consistent with how e.g. Object.keys() will process proxy trap * results (ES6 draft, Section 19.1.2.14). */ if (duk_get_prop_index(ctx, -1, i) && duk_is_string(ctx, -1)) { /* [ ... enum_target res trap_result val ] */ duk_push_true(ctx); /* [ ... enum_target res trap_result val true ] */ duk_put_prop(ctx, -4); } else { duk_pop(ctx); } } /* [ ... enum_target res trap_result ] */ duk_pop(ctx); duk_remove(ctx, -2); /* [ ... res ] */ /* The internal _target property is kept pointing to the original * enumeration target (the proxy object), so that the enumerator * 'next' operation can read property values if so requested. The * fact that the _target is a proxy disables key existence check * during enumeration. */ DUK_DDD(DUK_DDDPRINT("proxy enumeration, final res: %!O", (duk_heaphdr *) res)); goto compact_and_return; skip_proxy: #endif /* DUK_USE_ES6_PROXY */ curr = enum_target; while (curr) { /* * Virtual properties. * * String and buffer indices are virtual and always enumerable, * 'length' is virtual and non-enumerable. Array and arguments * object props have special behavior but are concrete. */ if (DUK_HOBJECT_HAS_EXOTIC_STRINGOBJ(curr) || DUK_HOBJECT_HAS_EXOTIC_BUFFEROBJ(curr)) { /* String and buffer enumeration behavior is identical now, * so use shared handler. */ if (DUK_HOBJECT_HAS_EXOTIC_STRINGOBJ(curr)) { duk_hstring *h_val; h_val = duk_hobject_get_internal_value_string(thr->heap, curr); DUK_ASSERT(h_val != NULL); /* string objects must not created without internal value */ len = (duk_uint_fast32_t) DUK_HSTRING_GET_CHARLEN(h_val); } else { duk_hbuffer *h_val; DUK_ASSERT(DUK_HOBJECT_HAS_EXOTIC_BUFFEROBJ(curr)); h_val = duk_hobject_get_internal_value_buffer(thr->heap, curr); DUK_ASSERT(h_val != NULL); /* buffer objects must not created without internal value */ len = (duk_uint_fast32_t) DUK_HBUFFER_GET_SIZE(h_val); } for (i = 0; i < len; i++) { duk_hstring *k; k = duk_heap_string_intern_u32_checked(thr, i); DUK_ASSERT(k); duk_push_hstring(ctx, k); duk_push_true(ctx); /* [enum_target res key true] */ duk_put_prop(ctx, -3); /* [enum_target res] */ } /* 'length' property is not enumerable, but is included if * non-enumerable properties are requested. */ if (enum_flags & DUK_ENUM_INCLUDE_NONENUMERABLE) { duk_push_hstring_stridx(ctx, DUK_STRIDX_LENGTH); duk_push_true(ctx); duk_put_prop(ctx, -3); } } else if (DUK_HOBJECT_HAS_EXOTIC_DUKFUNC(curr)) { if (enum_flags & DUK_ENUM_INCLUDE_NONENUMERABLE) { duk_push_hstring_stridx(ctx, DUK_STRIDX_LENGTH); duk_push_true(ctx); duk_put_prop(ctx, -3); } } /* * Array part * * Note: ordering between array and entry part must match 'abandon array' * behavior in duk_hobject_props.c: key order after an array is abandoned * must be the same. */ for (i = 0; i < (duk_uint_fast32_t) curr->a_size; i++) { duk_hstring *k; duk_tval *tv; tv = DUK_HOBJECT_A_GET_VALUE_PTR(curr, i); if (DUK_TVAL_IS_UNDEFINED_UNUSED(tv)) { continue; } k = duk_heap_string_intern_u32_checked(thr, i); DUK_ASSERT(k); duk_push_hstring(ctx, k); duk_push_true(ctx); /* [enum_target res key true] */ duk_put_prop(ctx, -3); /* [enum_target res] */ } /* * Entries part */ for (i = 0; i < (duk_uint_fast32_t) curr->e_next; i++) { duk_hstring *k; k = DUK_HOBJECT_E_GET_KEY(curr, i); if (!k) { continue; } if (!DUK_HOBJECT_E_SLOT_IS_ENUMERABLE(curr, i) && !(enum_flags & DUK_ENUM_INCLUDE_NONENUMERABLE)) { continue; } if (DUK_HSTRING_HAS_INTERNAL(k) && !(enum_flags & DUK_ENUM_INCLUDE_INTERNAL)) { continue; } if ((enum_flags & DUK_ENUM_ARRAY_INDICES_ONLY) && (DUK_HSTRING_GET_ARRIDX_SLOW(k) == DUK_HSTRING_NO_ARRAY_INDEX)) { continue; } DUK_ASSERT(DUK_HOBJECT_E_SLOT_IS_ACCESSOR(curr, i) || !DUK_TVAL_IS_UNDEFINED_UNUSED(&DUK_HOBJECT_E_GET_VALUE_PTR(curr, i)->v)); duk_push_hstring(ctx, k); duk_push_true(ctx); /* [enum_target res key true] */ duk_put_prop(ctx, -3); /* [enum_target res] */ } if (enum_flags & DUK_ENUM_OWN_PROPERTIES_ONLY) { break; } curr = curr->prototype; } /* [enum_target res] */ duk_remove(ctx, -2); /* [res] */ if ((enum_flags & (DUK_ENUM_ARRAY_INDICES_ONLY | DUK_ENUM_SORT_ARRAY_INDICES)) == (DUK_ENUM_ARRAY_INDICES_ONLY | DUK_ENUM_SORT_ARRAY_INDICES)) { /* * Some E5/E5.1 algorithms require that array indices are iterated * in a strictly ascending order. This is the case for e.g. * Array.prototype.forEach() and JSON.stringify() PropertyList * handling. * * To ensure this property for arrays with an array part (and * arbitrary objects too, since e.g. forEach() can be applied * to an array), the caller can request that we sort the keys * here. */ /* XXX: avoid this at least when enum_target is an Array, it has an * array part, and no ancestor properties were included? Not worth * it for JSON, but maybe worth it for forEach(). */ /* XXX: may need a 'length' filter for forEach() */ DUK_DDD(DUK_DDDPRINT("sort array indices by caller request")); duk__sort_array_indices(res); } #if defined(DUK_USE_ES6_PROXY) compact_and_return: #endif /* compact; no need to seal because object is internal */ duk_hobject_compact_props(thr, res); DUK_DDD(DUK_DDDPRINT("created enumerator object: %!iT", (duk_tval *) duk_get_tval(ctx, -1))); }
DUK_INTERNAL duk_ret_t duk_bi_function_prototype_to_string(duk_context *ctx) { duk_tval *tv; /* * E5 Section 15.3.4.2 places few requirements on the output of * this function: * * - The result is an implementation dependent representation * of the function; in particular * * - The result must follow the syntax of a FunctionDeclaration. * In particular, the function must have a name (even in the * case of an anonymous function or a function with an empty * name). * * - Note in particular that the output does NOT need to compile * into anything useful. */ /* XXX: faster internal way to get this */ duk_push_this(ctx); tv = duk_get_tval(ctx, -1); DUK_ASSERT(tv != NULL); if (DUK_TVAL_IS_OBJECT(tv)) { duk_hobject *obj = DUK_TVAL_GET_OBJECT(tv); const char *func_name; /* Function name: missing/undefined is mapped to empty string, * otherwise coerce to string. */ /* XXX: currently no handling for non-allowed identifier characters, * e.g. a '{' in the function name. */ duk_get_prop_stridx(ctx, -1, DUK_STRIDX_NAME); if (duk_is_undefined(ctx, -1)) { func_name = ""; } else { func_name = duk_to_string(ctx, -1); DUK_ASSERT(func_name != NULL); } /* Indicate function type in the function body using a dummy * directive. */ if (DUK_HOBJECT_HAS_COMPILEDFUNCTION(obj)) { duk_push_sprintf(ctx, "function %s() {\"ecmascript\"}", (const char *) func_name); } else if (DUK_HOBJECT_HAS_NATIVEFUNCTION(obj)) { duk_push_sprintf(ctx, "function %s() {\"native\"}", (const char *) func_name); } else if (DUK_HOBJECT_HAS_BOUND(obj)) { duk_push_sprintf(ctx, "function %s() {\"bound\"}", (const char *) func_name); } else { goto type_error; } } else if (DUK_TVAL_IS_LIGHTFUNC(tv)) { duk_push_lightfunc_tostring(ctx, tv); } else { goto type_error; } return 1; type_error: return DUK_RET_TYPE_ERROR; }
/* XXX: the implementation now assumes "chained" bound functions, * whereas "collapsed" bound functions (where there is ever only * one bound function which directly points to a non-bound, final * function) would require a "collapsing" implementation which * merges argument lists etc here. */ DUK_INTERNAL duk_ret_t duk_bi_function_prototype_bind(duk_context *ctx) { duk_hobject *h_bound; duk_hobject *h_target; duk_idx_t nargs; duk_idx_t i; /* vararg function, careful arg handling (e.g. thisArg may not be present) */ nargs = duk_get_top(ctx); /* = 1 + arg count */ if (nargs == 0) { duk_push_undefined(ctx); nargs++; } DUK_ASSERT(nargs >= 1); duk_push_this(ctx); if (!duk_is_callable(ctx, -1)) { DUK_DDD(DUK_DDDPRINT("func is not callable")); goto type_error; } /* [ thisArg arg1 ... argN func ] (thisArg+args == nargs total) */ DUK_ASSERT_TOP(ctx, nargs + 1); /* create bound function object */ duk_push_object_helper(ctx, DUK_HOBJECT_FLAG_EXTENSIBLE | DUK_HOBJECT_FLAG_BOUND | DUK_HOBJECT_FLAG_CONSTRUCTABLE | DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_FUNCTION), DUK_BIDX_FUNCTION_PROTOTYPE); h_bound = duk_get_hobject(ctx, -1); DUK_ASSERT(h_bound != NULL); /* [ thisArg arg1 ... argN func boundFunc ] */ duk_dup(ctx, -2); /* func */ duk_xdef_prop_stridx(ctx, -2, DUK_STRIDX_INT_TARGET, DUK_PROPDESC_FLAGS_NONE); duk_dup(ctx, 0); /* thisArg */ duk_xdef_prop_stridx(ctx, -2, DUK_STRIDX_INT_THIS, DUK_PROPDESC_FLAGS_NONE); duk_push_array(ctx); /* [ thisArg arg1 ... argN func boundFunc argArray ] */ for (i = 0; i < nargs - 1; i++) { duk_dup(ctx, 1 + i); duk_put_prop_index(ctx, -2, i); } duk_xdef_prop_stridx(ctx, -2, DUK_STRIDX_INT_ARGS, DUK_PROPDESC_FLAGS_NONE); /* [ thisArg arg1 ... argN func boundFunc ] */ /* bound function 'length' property is interesting */ h_target = duk_get_hobject(ctx, -2); if (h_target == NULL || /* lightfunc */ DUK_HOBJECT_GET_CLASS_NUMBER(h_target) == DUK_HOBJECT_CLASS_FUNCTION) { /* For lightfuncs, simply read the virtual property. */ duk_int_t tmp; duk_get_prop_stridx(ctx, -2, DUK_STRIDX_LENGTH); tmp = duk_to_int(ctx, -1) - (nargs - 1); /* step 15.a */ duk_pop(ctx); duk_push_int(ctx, (tmp < 0 ? 0 : tmp)); } else { duk_push_int(ctx, 0); } duk_xdef_prop_stridx(ctx, -2, DUK_STRIDX_LENGTH, DUK_PROPDESC_FLAGS_NONE); /* attrs in E5 Section 15.3.5.1 */ /* caller and arguments must use the same thrower, [[ThrowTypeError]] */ duk_xdef_prop_stridx_thrower(ctx, -1, DUK_STRIDX_CALLER, DUK_PROPDESC_FLAGS_NONE); duk_xdef_prop_stridx_thrower(ctx, -1, DUK_STRIDX_LC_ARGUMENTS, DUK_PROPDESC_FLAGS_NONE); /* these non-standard properties are copied for convenience */ /* XXX: 'copy properties' API call? */ duk_get_prop_stridx(ctx, -2, DUK_STRIDX_NAME); duk_xdef_prop_stridx(ctx, -2, DUK_STRIDX_NAME, DUK_PROPDESC_FLAGS_WC); duk_get_prop_stridx(ctx, -2, DUK_STRIDX_FILE_NAME); duk_xdef_prop_stridx(ctx, -2, DUK_STRIDX_FILE_NAME, DUK_PROPDESC_FLAGS_WC); /* The 'strict' flag is copied to get the special [[Get]] of E5.1 * Section 15.3.5.4 to apply when a 'caller' value is a strict bound * function. Not sure if this is correct, because the specification * is a bit ambiguous on this point but it would make sense. */ if (h_target == NULL) { /* Lightfuncs are always strict. */ DUK_HOBJECT_SET_STRICT(h_bound); } else if (DUK_HOBJECT_HAS_STRICT(h_target)) { DUK_HOBJECT_SET_STRICT(h_bound); } DUK_DDD(DUK_DDDPRINT("created bound function: %!iT", (duk_tval *) duk_get_tval(ctx, -1))); return 1; type_error: return DUK_RET_TYPE_ERROR; }
DUK_INTERNAL duk_bool_t duk_js_compare_helper(duk_hthread *thr, duk_tval *tv_x, duk_tval *tv_y, duk_small_int_t flags) { duk_context *ctx = (duk_context *) thr; duk_double_t d1, d2; duk_small_int_t c1, c2; duk_small_int_t s1, s2; duk_small_int_t rc; duk_bool_t retval; /* Fast path for fastints */ #if defined(DUK_USE_FASTINT) if (DUK_TVAL_IS_FASTINT(tv_x) && DUK_TVAL_IS_FASTINT(tv_y)) { duk_int64_t v1 = DUK_TVAL_GET_FASTINT(tv_x); duk_int64_t v2 = DUK_TVAL_GET_FASTINT(tv_y); if (v1 < v2) { /* 'lt is true' */ retval = 1; } else { retval = 0; } if (flags & DUK_COMPARE_FLAG_NEGATE) { retval ^= 1; } return retval; } #endif /* DUK_USE_FASTINT */ /* Fast path for numbers (one of which may be a fastint) */ #if 1 /* XXX: make fast paths optional for size minimization? */ if (DUK_TVAL_IS_NUMBER(tv_x) && DUK_TVAL_IS_NUMBER(tv_y)) { d1 = DUK_TVAL_GET_NUMBER(tv_x); d2 = DUK_TVAL_GET_NUMBER(tv_y); c1 = DUK_FPCLASSIFY(d1); c2 = DUK_FPCLASSIFY(d2); if (c1 == DUK_FP_NORMAL && c2 == DUK_FP_NORMAL) { /* XXX: this is a very narrow check, and doesn't cover * zeroes, subnormals, infinities, which compare normally. */ if (d1 < d2) { /* 'lt is true' */ retval = 1; } else { retval = 0; } if (flags & DUK_COMPARE_FLAG_NEGATE) { retval ^= 1; } return retval; } } #endif /* Slow path */ duk_push_tval(ctx, tv_x); duk_push_tval(ctx, tv_y); if (flags & DUK_COMPARE_FLAG_EVAL_LEFT_FIRST) { duk_to_primitive(ctx, -2, DUK_HINT_NUMBER); duk_to_primitive(ctx, -1, DUK_HINT_NUMBER); } else { duk_to_primitive(ctx, -1, DUK_HINT_NUMBER); duk_to_primitive(ctx, -2, DUK_HINT_NUMBER); } /* Note: reuse variables */ tv_x = duk_get_tval(ctx, -2); tv_y = duk_get_tval(ctx, -1); if (DUK_TVAL_IS_STRING(tv_x) && DUK_TVAL_IS_STRING(tv_y)) { duk_hstring *h1 = DUK_TVAL_GET_STRING(tv_x); duk_hstring *h2 = DUK_TVAL_GET_STRING(tv_y); DUK_ASSERT(h1 != NULL); DUK_ASSERT(h2 != NULL); rc = duk_js_string_compare(h1, h2); if (rc < 0) { goto lt_true; } else { goto lt_false; } } else { /* Ordering should not matter (E5 Section 11.8.5, step 3.a) but * preserve it just in case. */ if (flags & DUK_COMPARE_FLAG_EVAL_LEFT_FIRST) { d1 = duk_to_number(ctx, -2); d2 = duk_to_number(ctx, -1); } else { d2 = duk_to_number(ctx, -1); d1 = duk_to_number(ctx, -2); } c1 = (duk_small_int_t) DUK_FPCLASSIFY(d1); s1 = (duk_small_int_t) DUK_SIGNBIT(d1); c2 = (duk_small_int_t) DUK_FPCLASSIFY(d2); s2 = (duk_small_int_t) DUK_SIGNBIT(d2); if (c1 == DUK_FP_NAN || c2 == DUK_FP_NAN) { goto lt_undefined; } if (c1 == DUK_FP_ZERO && c2 == DUK_FP_ZERO) { /* For all combinations: +0 < +0, +0 < -0, -0 < +0, -0 < -0, * steps e, f, and g. */ goto lt_false; } if (d1 == d2) { goto lt_false; } if (c1 == DUK_FP_INFINITE && s1 == 0) { /* x == +Infinity */ goto lt_false; } if (c2 == DUK_FP_INFINITE && s2 == 0) { /* y == +Infinity */ goto lt_true; } if (c2 == DUK_FP_INFINITE && s2 != 0) { /* y == -Infinity */ goto lt_false; } if (c1 == DUK_FP_INFINITE && s1 != 0) { /* x == -Infinity */ goto lt_true; } if (d1 < d2) { goto lt_true; } goto lt_false; } lt_undefined: /* Note: undefined from Section 11.8.5 always results in false * return (see e.g. Section 11.8.3) - hence special treatment here. */ retval = 0; goto cleanup; lt_true: if (flags & DUK_COMPARE_FLAG_NEGATE) { retval = 0; goto cleanup; } else { retval = 1; goto cleanup; } /* never here */ lt_false: if (flags & DUK_COMPARE_FLAG_NEGATE) { retval = 1; goto cleanup; } else { retval = 0; goto cleanup; } /* never here */ cleanup: duk_pop_2(ctx); return retval; }
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_BOUNDFUNC(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_BOUNDFUNC(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: #if defined(DUK_USE_VERBOSE_ERRORS) #if defined(DUK_USE_PARANOID_ERRORS) DUK_ERROR_FMT1(thr, DUK_ERR_TYPE_ERROR, "%s not constructable", duk_get_type_name(ctx, -1)); #else DUK_ERROR_FMT1(thr, DUK_ERR_TYPE_ERROR, "%s not constructable", duk_push_string_readable(ctx, -1)); #endif #else DUK_ERROR_TYPE(thr, "not constructable"); #endif }
/* XXX: much to improve (code size) */ DUK_INTERNAL duk_ret_t duk_bi_regexp_constructor(duk_hthread *thr) { duk_hobject *h_pattern; DUK_ASSERT_TOP(thr, 2); h_pattern = duk_get_hobject(thr, 0); if (!duk_is_constructor_call(thr) && h_pattern != NULL && DUK_HOBJECT_GET_CLASS_NUMBER(h_pattern) == DUK_HOBJECT_CLASS_REGEXP && duk_is_undefined(thr, 1)) { /* Called as a function, pattern has [[Class]] "RegExp" and * flags is undefined -> return object as is. */ /* XXX: ES2015 has a NewTarget SameValue() check which is not * yet implemented. */ duk_dup_0(thr); return 1; } /* Else functionality is identical for function call and constructor * call. */ if (h_pattern != NULL && DUK_HOBJECT_GET_CLASS_NUMBER(h_pattern) == DUK_HOBJECT_CLASS_REGEXP) { duk_get_prop_stridx_short(thr, 0, DUK_STRIDX_SOURCE); if (duk_is_undefined(thr, 1)) { /* In ES5 one would need to read the flags individually; * in ES2015 just read .flags. */ duk_get_prop_stridx(thr, 0, DUK_STRIDX_FLAGS); } else { /* In ES2015 allowed; overrides argument RegExp flags. */ duk_dup_1(thr); } } else { if (duk_is_undefined(thr, 0)) { duk_push_hstring_empty(thr); } else { duk_dup_0(thr); duk_to_string(thr, -1); /* Rejects Symbols. */ } if (duk_is_undefined(thr, 1)) { duk_push_hstring_empty(thr); } else { duk_dup_1(thr); duk_to_string(thr, -1); /* Rejects Symbols. */ } /* [ ... pattern flags ] */ } DUK_DDD(DUK_DDDPRINT("RegExp constructor/function call, pattern=%!T, flags=%!T", (duk_tval *) duk_get_tval(thr, -2), (duk_tval *) duk_get_tval(thr, -1))); /* [ ... pattern flags ] (both uncoerced) */ duk_to_string(thr, -2); duk_to_string(thr, -1); duk_regexp_compile(thr); /* [ ... bytecode escaped_source ] */ duk_regexp_create_instance(thr); /* [ ... RegExp ] */ 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_DD(DUK_DDPRINT("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_SYNTAX(thr, 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_DD(DUK_DDPRINT("regexp compilation successful, bytecode: %!T, escaped source: %!T", (duk_tval *) duk_get_tval(ctx, -1), (duk_tval *) duk_get_tval(ctx, -2))); }
/* XXX: better place for this */ DUK_EXTERNAL void duk_set_global_object(duk_context *ctx) { duk_hthread *thr = (duk_hthread *) ctx; duk_hobject *h_glob; duk_hobject *h_prev_glob; duk_hobject *h_env; duk_hobject *h_prev_env; DUK_D(DUK_DPRINT("replace global object with: %!T", duk_get_tval(ctx, -1))); h_glob = duk_require_hobject(ctx, -1); DUK_ASSERT(h_glob != NULL); /* * Replace global object. */ h_prev_glob = thr->builtins[DUK_BIDX_GLOBAL]; DUK_UNREF(h_prev_glob); thr->builtins[DUK_BIDX_GLOBAL] = h_glob; DUK_HOBJECT_INCREF(thr, h_glob); DUK_HOBJECT_DECREF_ALLOWNULL(thr, h_prev_glob); /* side effects, in theory (referenced by global env) */ /* * Replace lexical environment for global scope * * Create a new object environment for the global lexical scope. * We can't just reset the _Target property of the current one, * because the lexical scope is shared by other threads with the * same (initial) built-ins. */ (void) duk_push_object_helper(ctx, DUK_HOBJECT_FLAG_EXTENSIBLE | DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_OBJENV), -1); /* no prototype, updated below */ duk_dup(ctx, -2); duk_dup(ctx, -3); /* [ ... new_glob new_env new_glob new_glob ] */ duk_xdef_prop_stridx(thr, -3, DUK_STRIDX_INT_TARGET, DUK_PROPDESC_FLAGS_NONE); duk_xdef_prop_stridx(thr, -2, DUK_STRIDX_INT_THIS, DUK_PROPDESC_FLAGS_NONE); /* [ ... new_glob new_env ] */ h_env = duk_get_hobject(ctx, -1); DUK_ASSERT(h_env != NULL); h_prev_env = thr->builtins[DUK_BIDX_GLOBAL_ENV]; thr->builtins[DUK_BIDX_GLOBAL_ENV] = h_env; DUK_HOBJECT_INCREF(thr, h_env); DUK_HOBJECT_DECREF_ALLOWNULL(thr, h_prev_env); /* side effects */ DUK_UNREF(h_env); /* without refcounts */ DUK_UNREF(h_prev_env); /* [ ... new_glob new_env ] */ duk_pop_2(ctx); /* [ ... ] */ }