cosmopolitan/third_party/duktape/duk_bi_function.c

454 lines
14 KiB
C

/*
* Function built-ins
*/
#include "third_party/duktape/duk_internal.h"
/* Needed even when Function built-in is disabled. */
DUK_INTERNAL duk_ret_t duk_bi_function_prototype(duk_hthread *thr) {
/* ignore arguments, return undefined (E5 Section 15.3.4) */
DUK_UNREF(thr);
return 0;
}
#if defined(DUK_USE_FUNCTION_BUILTIN)
DUK_INTERNAL duk_ret_t duk_bi_function_constructor(duk_hthread *thr) {
duk_hstring *h_sourcecode;
duk_idx_t nargs;
duk_idx_t i;
duk_small_uint_t comp_flags;
duk_hcompfunc *func;
duk_hobject *outer_lex_env;
duk_hobject *outer_var_env;
/* normal and constructor calls have identical semantics */
nargs = duk_get_top(thr);
for (i = 0; i < nargs; i++) {
duk_to_string(thr, i); /* Rejects Symbols during coercion. */
}
if (nargs == 0) {
duk_push_hstring_empty(thr);
duk_push_hstring_empty(thr);
} else if (nargs == 1) {
/* XXX: cover this with the generic >1 case? */
duk_push_hstring_empty(thr);
} else {
duk_insert(thr, 0); /* [ arg1 ... argN-1 body] -> [body arg1 ... argN-1] */
duk_push_literal(thr, ",");
duk_insert(thr, 1);
duk_join(thr, nargs - 1);
}
/* [ body formals ], formals is comma separated list that needs to be parsed */
DUK_ASSERT_TOP(thr, 2);
/* XXX: this placeholder is not always correct, but use for now.
* It will fail in corner cases; see test-dev-func-cons-args.js.
*/
duk_push_literal(thr, "function(");
duk_dup_1(thr);
duk_push_literal(thr, "){");
duk_dup_0(thr);
duk_push_literal(thr, "\n}"); /* Newline is important to handle trailing // comment. */
duk_concat(thr, 5);
/* [ body formals source ] */
DUK_ASSERT_TOP(thr, 3);
/* strictness is not inherited, intentional */
comp_flags = DUK_COMPILE_FUNCEXPR;
duk_push_hstring_stridx(thr, DUK_STRIDX_COMPILE); /* XXX: copy from caller? */ /* XXX: ignored now */
h_sourcecode = duk_require_hstring(thr, -2); /* no symbol check needed; -2 is concat'd code */
duk_js_compile(thr,
(const duk_uint8_t *) DUK_HSTRING_GET_DATA(h_sourcecode),
(duk_size_t) DUK_HSTRING_GET_BYTELEN(h_sourcecode),
comp_flags);
/* Force .name to 'anonymous' (ES2015). */
duk_push_literal(thr, "anonymous");
duk_xdef_prop_stridx_short(thr, -2, DUK_STRIDX_NAME, DUK_PROPDESC_FLAGS_C);
func = (duk_hcompfunc *) duk_known_hobject(thr, -1);
DUK_ASSERT(DUK_HOBJECT_IS_COMPFUNC((duk_hobject *) func));
DUK_ASSERT(DUK_HOBJECT_HAS_CONSTRUCTABLE((duk_hobject *) func));
/* [ body formals source template ] */
/* only outer_lex_env matters, as functions always get a new
* variable declaration environment.
*/
outer_lex_env = thr->builtins[DUK_BIDX_GLOBAL_ENV];
outer_var_env = thr->builtins[DUK_BIDX_GLOBAL_ENV];
duk_js_push_closure(thr, func, outer_var_env, outer_lex_env, 1 /*add_auto_proto*/);
/* [ body formals source template closure ] */
return 1;
}
#endif /* DUK_USE_FUNCTION_BUILTIN */
#if defined(DUK_USE_FUNCTION_BUILTIN)
DUK_INTERNAL duk_ret_t duk_bi_function_prototype_to_string(duk_hthread *thr) {
duk_tval *tv;
/*
* E5 Section 15.3.4.2 places few requirements on the output of
* this function: the result is implementation dependent, must
* follow FunctionDeclaration syntax (in particular, must have a
* name even for anonymous functions or functions with empty name).
* The output does NOT need to compile into anything useful.
*
* E6 Section 19.2.3.5 changes the requirements completely: the
* result must either eval() to a functionally equivalent object
* OR eval() to a SyntaxError.
*
* We opt for the SyntaxError approach for now, with a syntax that
* mimics V8's native function syntax:
*
* 'function cos() { [native code] }'
*
* but extended with [ecmascript code], [bound code], and
* [lightfunc code].
*/
duk_push_this(thr);
tv = DUK_GET_TVAL_NEGIDX(thr, -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. No handling for invalid identifier
* characters or e.g. '{' in the function name. This doesn't
* really matter as long as a SyntaxError results. Technically
* if the name contained a suitable prefix followed by '//' it
* might cause the result to parse without error.
*/
duk_get_prop_stridx_short(thr, -1, DUK_STRIDX_NAME);
if (duk_is_undefined(thr, -1)) {
func_name = "";
} else {
func_name = duk_to_string(thr, -1);
DUK_ASSERT(func_name != NULL);
}
if (DUK_HOBJECT_IS_COMPFUNC(obj)) {
duk_push_sprintf(thr, "function %s() { [ecmascript code] }", (const char *) func_name);
} else if (DUK_HOBJECT_IS_NATFUNC(obj)) {
duk_push_sprintf(thr, "function %s() { [native code] }", (const char *) func_name);
} else if (DUK_HOBJECT_IS_BOUNDFUNC(obj)) {
duk_push_sprintf(thr, "function %s() { [bound code] }", (const char *) func_name);
} else {
goto type_error;
}
} else if (DUK_TVAL_IS_LIGHTFUNC(tv)) {
duk_push_lightfunc_tostring(thr, tv);
} else {
goto type_error;
}
return 1;
type_error:
DUK_DCERROR_TYPE_INVALID_ARGS(thr);
}
#endif
/* Always present because the native function pointer is needed in call
* handling.
*/
DUK_INTERNAL duk_ret_t duk_bi_function_prototype_call(duk_hthread *thr) {
/* .call() is dealt with in call handling by simulating its
* effects so this function is actually never called.
*/
DUK_UNREF(thr);
return DUK_RET_TYPE_ERROR;
}
DUK_INTERNAL duk_ret_t duk_bi_function_prototype_apply(duk_hthread *thr) {
/* Like .call(), never actually called. */
DUK_UNREF(thr);
return DUK_RET_TYPE_ERROR;
}
DUK_INTERNAL duk_ret_t duk_bi_reflect_apply(duk_hthread *thr) {
/* Like .call(), never actually called. */
DUK_UNREF(thr);
return DUK_RET_TYPE_ERROR;
}
DUK_INTERNAL duk_ret_t duk_bi_reflect_construct(duk_hthread *thr) {
/* Like .call(), never actually called. */
DUK_UNREF(thr);
return DUK_RET_TYPE_ERROR;
}
#if defined(DUK_USE_FUNCTION_BUILTIN)
/* Create a bound function which points to a target function which may
* be bound or non-bound. If the target is bound, the argument lists
* and 'this' binding of the functions are merged and the resulting
* function points directly to the non-bound target.
*/
DUK_INTERNAL duk_ret_t duk_bi_function_prototype_bind(duk_hthread *thr) {
duk_hboundfunc *h_bound;
duk_idx_t nargs; /* bound args, not counting 'this' binding */
duk_idx_t bound_nargs;
duk_int_t bound_len;
duk_tval *tv_prevbound;
duk_idx_t n_prevbound;
duk_tval *tv_res;
duk_tval *tv_tmp;
/* XXX: C API call, e.g. duk_push_bound_function(thr, target_idx, nargs); */
/* Vararg function, careful arg handling, e.g. thisArg may not
* be present.
*/
nargs = duk_get_top(thr) - 1; /* actual args, not counting 'this' binding */
if (nargs < 0) {
nargs++;
duk_push_undefined(thr);
}
DUK_ASSERT(nargs >= 0);
/* Limit 'nargs' for bound functions to guarantee arithmetic
* below will never wrap.
*/
if (nargs > (duk_idx_t) DUK_HBOUNDFUNC_MAX_ARGS) {
DUK_DCERROR_RANGE_INVALID_COUNT(thr);
}
duk_push_this(thr);
duk_require_callable(thr, -1);
/* [ thisArg arg1 ... argN func ] (thisArg+args == nargs+1 total) */
DUK_ASSERT_TOP(thr, nargs + 2);
/* Create bound function object. */
h_bound = duk_push_hboundfunc(thr);
DUK_ASSERT(DUK_TVAL_IS_UNDEFINED(&h_bound->target));
DUK_ASSERT(DUK_TVAL_IS_UNDEFINED(&h_bound->this_binding));
DUK_ASSERT(h_bound->args == NULL);
DUK_ASSERT(h_bound->nargs == 0);
DUK_ASSERT(DUK_HOBJECT_GET_PROTOTYPE(thr->heap, (duk_hobject *) h_bound) == NULL);
/* [ thisArg arg1 ... argN func boundFunc ] */
/* If the target is a bound function, argument lists must be
* merged. The 'this' binding closest to the target function
* wins because in call handling the 'this' gets replaced over
* and over again until we call the non-bound function.
*/
tv_prevbound = NULL;
n_prevbound = 0;
tv_tmp = DUK_GET_TVAL_POSIDX(thr, 0);
DUK_TVAL_SET_TVAL(&h_bound->this_binding, tv_tmp);
tv_tmp = DUK_GET_TVAL_NEGIDX(thr, -2);
DUK_TVAL_SET_TVAL(&h_bound->target, tv_tmp);
if (DUK_TVAL_IS_OBJECT(tv_tmp)) {
duk_hobject *h_target;
duk_hobject *bound_proto;
h_target = DUK_TVAL_GET_OBJECT(tv_tmp);
DUK_ASSERT(DUK_HOBJECT_IS_CALLABLE(h_target));
/* Internal prototype must be copied from the target.
* For lightfuncs Function.prototype is used and is already
* in place.
*/
bound_proto = DUK_HOBJECT_GET_PROTOTYPE(thr->heap, h_target);
DUK_HOBJECT_SET_PROTOTYPE_INIT_INCREF(thr, (duk_hobject *) h_bound, bound_proto);
/* 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.
*/
/* Strictness is inherited from target. */
if (DUK_HOBJECT_HAS_STRICT(h_target)) {
DUK_HOBJECT_SET_STRICT((duk_hobject *) h_bound);
}
if (DUK_HOBJECT_HAS_BOUNDFUNC(h_target)) {
duk_hboundfunc *h_boundtarget;
h_boundtarget = (duk_hboundfunc *) (void *) h_target;
/* The final function should always be non-bound, unless
* there's a bug in the internals. Assert for it.
*/
DUK_ASSERT(DUK_TVAL_IS_LIGHTFUNC(&h_boundtarget->target) ||
(DUK_TVAL_IS_OBJECT(&h_boundtarget->target) &&
DUK_HOBJECT_IS_CALLABLE(DUK_TVAL_GET_OBJECT(&h_boundtarget->target)) &&
!DUK_HOBJECT_IS_BOUNDFUNC(DUK_TVAL_GET_OBJECT(&h_boundtarget->target))));
DUK_TVAL_SET_TVAL(&h_bound->target, &h_boundtarget->target);
DUK_TVAL_SET_TVAL(&h_bound->this_binding, &h_boundtarget->this_binding);
tv_prevbound = h_boundtarget->args;
n_prevbound = h_boundtarget->nargs;
}
} else {
/* Lightfuncs are always strict. */
duk_hobject *bound_proto;
DUK_ASSERT(DUK_TVAL_IS_LIGHTFUNC(tv_tmp));
DUK_HOBJECT_SET_STRICT((duk_hobject *) h_bound);
bound_proto = thr->builtins[DUK_BIDX_FUNCTION_PROTOTYPE];
DUK_HOBJECT_SET_PROTOTYPE_INIT_INCREF(thr, (duk_hobject *) h_bound, bound_proto);
}
DUK_TVAL_INCREF(thr, &h_bound->target); /* old values undefined, no decref needed */
DUK_TVAL_INCREF(thr, &h_bound->this_binding);
bound_nargs = n_prevbound + nargs;
if (bound_nargs > (duk_idx_t) DUK_HBOUNDFUNC_MAX_ARGS) {
DUK_DCERROR_RANGE_INVALID_COUNT(thr);
}
tv_res = (duk_tval *) DUK_ALLOC_CHECKED(thr, ((duk_size_t) bound_nargs) * sizeof(duk_tval));
DUK_ASSERT(tv_res != NULL || bound_nargs == 0);
DUK_ASSERT(h_bound->args == NULL);
DUK_ASSERT(h_bound->nargs == 0);
h_bound->args = tv_res;
h_bound->nargs = bound_nargs;
DUK_ASSERT(n_prevbound >= 0);
duk_copy_tvals_incref(thr, tv_res, tv_prevbound, (duk_size_t) n_prevbound);
DUK_ASSERT(nargs >= 0);
duk_copy_tvals_incref(thr, tv_res + n_prevbound, DUK_GET_TVAL_POSIDX(thr, 1), (duk_size_t) nargs);
/* [ thisArg arg1 ... argN func boundFunc ] */
/* Bound function 'length' property is interesting.
* For lightfuncs, simply read the virtual property.
*/
duk_get_prop_stridx_short(thr, -2, DUK_STRIDX_LENGTH);
bound_len = duk_get_int(thr, -1); /* ES2015: no coercion */
if (bound_len < nargs) {
bound_len = 0;
} else {
bound_len -= nargs;
}
if (sizeof(duk_int_t) > 4 && bound_len > (duk_int_t) DUK_UINT32_MAX) {
bound_len = (duk_int_t) DUK_UINT32_MAX;
}
duk_pop(thr);
DUK_ASSERT(bound_len >= 0);
tv_tmp = thr->valstack_top++;
DUK_ASSERT(DUK_TVAL_IS_UNDEFINED(tv_tmp));
DUK_ASSERT(!DUK_TVAL_NEEDS_REFCOUNT_UPDATE(tv_tmp));
DUK_TVAL_SET_U32(tv_tmp, (duk_uint32_t) bound_len); /* in-place update, fastint */
duk_xdef_prop_stridx_short(thr, -2, DUK_STRIDX_LENGTH, DUK_PROPDESC_FLAGS_C); /* attrs in E6 Section 9.2.4 */
/* XXX: could these be virtual? */
/* Caller and arguments must use the same thrower, [[ThrowTypeError]]. */
duk_xdef_prop_stridx_thrower(thr, -1, DUK_STRIDX_CALLER);
duk_xdef_prop_stridx_thrower(thr, -1, DUK_STRIDX_LC_ARGUMENTS);
/* Function name and fileName (non-standard). */
duk_push_literal(thr, "bound "); /* ES2015 19.2.3.2. */
duk_get_prop_stridx(thr, -3, DUK_STRIDX_NAME);
if (!duk_is_string_notsymbol(thr, -1)) {
/* ES2015 has requirement to check that .name of target is a string
* (also must check for Symbol); if not, targetName should be the
* empty string. ES2015 19.2.3.2.
*/
duk_pop(thr);
duk_push_hstring_empty(thr);
}
duk_concat(thr, 2);
duk_xdef_prop_stridx_short(thr, -2, DUK_STRIDX_NAME, DUK_PROPDESC_FLAGS_C);
#if defined(DUK_USE_FUNC_FILENAME_PROPERTY)
duk_get_prop_stridx_short(thr, -2, DUK_STRIDX_FILE_NAME);
duk_xdef_prop_stridx_short(thr, -2, DUK_STRIDX_FILE_NAME, DUK_PROPDESC_FLAGS_C);
#endif
DUK_DDD(DUK_DDDPRINT("created bound function: %!iT", (duk_tval *) duk_get_tval(thr, -1)));
return 1;
}
#endif /* DUK_USE_FUNCTION_BUILTIN */
/* %NativeFunctionPrototype% .length getter. */
DUK_INTERNAL duk_ret_t duk_bi_native_function_length(duk_hthread *thr) {
duk_tval *tv;
duk_hnatfunc *h;
duk_int16_t func_nargs;
tv = duk_get_borrowed_this_tval(thr);
DUK_ASSERT(tv != NULL);
if (DUK_TVAL_IS_OBJECT(tv)) {
h = (duk_hnatfunc *) DUK_TVAL_GET_OBJECT(tv);
DUK_ASSERT(h != NULL);
if (!DUK_HOBJECT_IS_NATFUNC((duk_hobject *) h)) {
goto fail_type;
}
func_nargs = h->nargs;
duk_push_int(thr, func_nargs == DUK_HNATFUNC_NARGS_VARARGS ? 0 : func_nargs);
} else if (DUK_TVAL_IS_LIGHTFUNC(tv)) {
duk_small_uint_t lf_flags;
duk_small_uint_t lf_len;
lf_flags = DUK_TVAL_GET_LIGHTFUNC_FLAGS(tv);
lf_len = DUK_LFUNC_FLAGS_GET_LENGTH(lf_flags);
duk_push_uint(thr, lf_len);
} else {
goto fail_type;
}
return 1;
fail_type:
DUK_DCERROR_TYPE_INVALID_ARGS(thr);
}
/* %NativeFunctionPrototype% .name getter. */
DUK_INTERNAL duk_ret_t duk_bi_native_function_name(duk_hthread *thr) {
duk_tval *tv;
duk_hnatfunc *h;
tv = duk_get_borrowed_this_tval(thr);
DUK_ASSERT(tv != NULL);
if (DUK_TVAL_IS_OBJECT(tv)) {
h = (duk_hnatfunc *) DUK_TVAL_GET_OBJECT(tv);
DUK_ASSERT(h != NULL);
if (!DUK_HOBJECT_IS_NATFUNC((duk_hobject *) h)) {
goto fail_type;
}
#if 0
duk_push_hnatfunc_name(thr, h);
#endif
duk_push_hstring_empty(thr);
} else if (DUK_TVAL_IS_LIGHTFUNC(tv)) {
duk_push_lightfunc_name(thr, tv);
} else {
goto fail_type;
}
return 1;
fail_type:
DUK_DCERROR_TYPE_INVALID_ARGS(thr);
}
#if defined(DUK_USE_SYMBOL_BUILTIN)
DUK_INTERNAL duk_ret_t duk_bi_function_prototype_hasinstance(duk_hthread *thr) {
/* This binding: RHS, stack index 0: LHS. */
duk_bool_t ret;
ret = duk_js_instanceof_ordinary(thr, DUK_GET_TVAL_POSIDX(thr, 0), DUK_GET_THIS_TVAL_PTR(thr));
duk_push_boolean(thr, ret);
return 1;
}
#endif /* DUK_USE_SYMBOL_BUILTIN */