mirror of https://github.com/svaarala/duktape.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
421 lines
13 KiB
421 lines
13 KiB
/*
|
|
* Function built-ins
|
|
*/
|
|
|
|
#include "duk_internal.h"
|
|
|
|
/* Needed even when Function built-in is disabled. */
|
|
DUK_INTERNAL duk_ret_t duk_bi_function_prototype(duk_context *ctx) {
|
|
/* ignore arguments, return undefined (E5 Section 15.3.4) */
|
|
DUK_UNREF(ctx);
|
|
return 0;
|
|
}
|
|
|
|
#if defined(DUK_USE_FUNCTION_BUILTIN)
|
|
DUK_INTERNAL duk_ret_t duk_bi_function_constructor(duk_context *ctx) {
|
|
duk_hthread *thr = (duk_hthread *) ctx;
|
|
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(ctx);
|
|
for (i = 0; i < nargs; i++) {
|
|
duk_to_string(ctx, i); /* Rejects Symbols during coercion. */
|
|
}
|
|
|
|
if (nargs == 0) {
|
|
duk_push_hstring_empty(ctx);
|
|
duk_push_hstring_empty(ctx);
|
|
} else if (nargs == 1) {
|
|
/* XXX: cover this with the generic >1 case? */
|
|
duk_push_hstring_empty(ctx);
|
|
} else {
|
|
duk_insert(ctx, 0); /* [ arg1 ... argN-1 body] -> [body arg1 ... argN-1] */
|
|
duk_push_string(ctx, ",");
|
|
duk_insert(ctx, 1);
|
|
duk_join(ctx, nargs - 1);
|
|
}
|
|
|
|
/* [ body formals ], formals is comma separated list that needs to be parsed */
|
|
|
|
DUK_ASSERT_TOP(ctx, 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_string(ctx, "function(");
|
|
duk_dup_1(ctx);
|
|
duk_push_string(ctx, "){");
|
|
duk_dup_0(ctx);
|
|
duk_push_string(ctx, "}");
|
|
duk_concat(ctx, 5);
|
|
|
|
/* [ body formals source ] */
|
|
|
|
DUK_ASSERT_TOP(ctx, 3);
|
|
|
|
/* strictness is not inherited, intentional */
|
|
comp_flags = DUK_JS_COMPILE_FLAG_FUNCEXPR;
|
|
|
|
duk_push_hstring_stridx(ctx, DUK_STRIDX_COMPILE); /* XXX: copy from caller? */ /* XXX: ignored now */
|
|
h_sourcecode = duk_require_hstring(ctx, -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_string(ctx, "anonymous");
|
|
duk_xdef_prop_stridx_short(ctx, -2, DUK_STRIDX_NAME, DUK_PROPDESC_FLAGS_C);
|
|
|
|
func = (duk_hcompfunc *) duk_known_hobject(ctx, -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_context *ctx) {
|
|
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(ctx);
|
|
tv = DUK_GET_TVAL_NEGIDX(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. 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(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);
|
|
}
|
|
|
|
if (DUK_HOBJECT_IS_COMPFUNC(obj)) {
|
|
duk_push_sprintf(ctx, "function %s() { [ecmascript code] }", (const char *) func_name);
|
|
} else if (DUK_HOBJECT_IS_NATFUNC(obj)) {
|
|
duk_push_sprintf(ctx, "function %s() { [native code] }", (const char *) func_name);
|
|
} else if (DUK_HOBJECT_IS_BOUNDFUNC(obj)) {
|
|
duk_push_sprintf(ctx, "function %s() { [bound code] }", (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:
|
|
DUK_DCERROR_TYPE_INVALID_ARGS((duk_hthread *) ctx);
|
|
}
|
|
#endif
|
|
|
|
#if defined(DUK_USE_FUNCTION_BUILTIN) || defined(DUK_USE_REFLECT_BUILTIN)
|
|
DUK_INTERNAL duk_ret_t duk_bi_function_prototype_apply(duk_context *ctx) {
|
|
/*
|
|
* magic = 0: Function.prototype.apply()
|
|
* magic = 1: Reflect.apply()
|
|
* magic = 2: Reflect.construct()
|
|
*/
|
|
|
|
duk_idx_t idx_args;
|
|
duk_idx_t len;
|
|
duk_idx_t i;
|
|
duk_int_t magic;
|
|
duk_idx_t nargs;
|
|
duk_uint_t mask;
|
|
|
|
magic = duk_get_current_magic(ctx);
|
|
switch (magic) {
|
|
case 0: /* Function.prototype.apply() */
|
|
DUK_ASSERT_TOP(ctx, 2); /* not a vararg function */
|
|
duk_push_this(ctx);
|
|
duk_insert(ctx, 0);
|
|
/* Fall through intentionally for shared handling. */
|
|
case 1: /* Reflect.apply(); Function.prototype.apply() after 'this' fixup. */
|
|
DUK_ASSERT_TOP(ctx, 3); /* not a vararg function */
|
|
idx_args = 2;
|
|
duk_require_callable(ctx, 0);
|
|
break;
|
|
default: /* Reflect.construct() */
|
|
DUK_ASSERT(magic == 2);
|
|
duk_require_constructable(ctx, 0);
|
|
nargs = duk_get_top(ctx);
|
|
if (nargs < 2) {
|
|
DUK_DCERROR_TYPE_INVALID_ARGS((duk_hthread *) ctx);
|
|
}
|
|
if (nargs >= 3 && !duk_strict_equals(ctx, 0, 2)) {
|
|
/* XXX: [[Construct]] newTarget currently unsupported */
|
|
DUK_ERROR_UNSUPPORTED((duk_hthread *) ctx);
|
|
}
|
|
idx_args = 1;
|
|
break;
|
|
}
|
|
|
|
if (magic != 2) {
|
|
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)));
|
|
} else {
|
|
/* thisArg is not applicable for Reflect.construct(). */
|
|
DUK_DDD(DUK_DDDPRINT("func=%!iT, argArray=%!iT",
|
|
(duk_tval *) duk_get_tval(ctx, 0),
|
|
(duk_tval *) duk_get_tval(ctx, 1)));
|
|
}
|
|
|
|
/* [ func thisArg? argArray ] */
|
|
|
|
mask = duk_get_type_mask(ctx, idx_args);
|
|
if (mask & (DUK_TYPE_MASK_NULL | DUK_TYPE_MASK_UNDEFINED)) {
|
|
DUK_DDD(DUK_DDDPRINT("argArray is null/undefined, no args"));
|
|
len = 0;
|
|
} else if (mask & DUK_TYPE_MASK_OBJECT) {
|
|
DUK_DDD(DUK_DDDPRINT("argArray is an object"));
|
|
|
|
/* XXX: make this an internal helper */
|
|
DUK_ASSERT(idx_args >= 0 && idx_args <= 0x7fffL); /* short variants would work, but avoid shifting */
|
|
duk_get_prop_stridx(ctx, idx_args, 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, idx_args, i);
|
|
}
|
|
} else {
|
|
goto type_error;
|
|
}
|
|
duk_remove(ctx, idx_args);
|
|
DUK_ASSERT_TOP(ctx, idx_args + len);
|
|
|
|
/* [ func thisArg? arg1 ... argN ] */
|
|
|
|
if (magic != 2) {
|
|
/* Function.prototype.apply() or Reflect.apply() */
|
|
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);
|
|
} else {
|
|
/* Reflect.construct() */
|
|
DUK_DDD(DUK_DDDPRINT("construct, func=%!iT, len=%ld",
|
|
(duk_tval *) duk_get_tval(ctx, 0),
|
|
(long) len));
|
|
duk_new(ctx, len);
|
|
}
|
|
return 1;
|
|
|
|
type_error:
|
|
DUK_DCERROR_TYPE_INVALID_ARGS((duk_hthread *) ctx);
|
|
}
|
|
#endif
|
|
|
|
#if defined(DUK_USE_FUNCTION_BUILTIN)
|
|
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;
|
|
}
|
|
#endif /* DUK_USE_FUNCTION_BUILTIN */
|
|
|
|
#if defined(DUK_USE_FUNCTION_BUILTIN)
|
|
/* 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_hthread *thr = (duk_hthread *) 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);
|
|
duk_require_callable(ctx, -1);
|
|
|
|
/* [ thisArg arg1 ... argN func ] (thisArg+args == nargs total) */
|
|
DUK_ASSERT_TOP(ctx, nargs + 1);
|
|
|
|
/* create bound function object */
|
|
h_bound = duk_push_object_helper(ctx,
|
|
DUK_HOBJECT_FLAG_EXTENSIBLE |
|
|
DUK_HOBJECT_FLAG_BOUNDFUNC |
|
|
DUK_HOBJECT_FLAG_CONSTRUCTABLE |
|
|
DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_FUNCTION),
|
|
DUK_BIDX_FUNCTION_PROTOTYPE);
|
|
DUK_ASSERT(h_bound != NULL);
|
|
|
|
/* [ thisArg arg1 ... argN func boundFunc ] */
|
|
duk_dup_m2(ctx); /* func */
|
|
duk_xdef_prop_stridx_short(ctx, -2, DUK_STRIDX_INT_TARGET, DUK_PROPDESC_FLAGS_NONE);
|
|
|
|
duk_dup_0(ctx); /* thisArg */
|
|
duk_xdef_prop_stridx_short(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_short(ctx, -2, DUK_STRIDX_INT_ARGS, DUK_PROPDESC_FLAGS_NONE);
|
|
|
|
/* [ thisArg arg1 ... argN func boundFunc ] */
|
|
|
|
h_target = duk_get_hobject(ctx, -2);
|
|
|
|
/* internal prototype must be copied from the target */
|
|
if (h_target != NULL) {
|
|
/* For lightfuncs Function.prototype is used and is already in place. */
|
|
DUK_HOBJECT_SET_PROTOTYPE_UPDREF(thr, h_bound, DUK_HOBJECT_GET_PROTOTYPE(thr->heap, h_target));
|
|
}
|
|
|
|
/* bound function 'length' property is interesting */
|
|
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_short(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_short(ctx, -2, DUK_STRIDX_LENGTH, DUK_PROPDESC_FLAGS_C); /* attrs in E6 Section 9.2.4 */
|
|
|
|
/* caller and arguments must use the same thrower, [[ThrowTypeError]] */
|
|
duk_xdef_prop_stridx_thrower(ctx, -1, DUK_STRIDX_CALLER);
|
|
duk_xdef_prop_stridx_thrower(ctx, -1, DUK_STRIDX_LC_ARGUMENTS);
|
|
|
|
/* XXX: 'copy properties' API call? */
|
|
#if defined(DUK_USE_FUNC_NAME_PROPERTY)
|
|
duk_push_string(ctx, "bound "); /* ES2015 19.2.3.2. */
|
|
duk_get_prop_stridx_short(ctx, -3, DUK_STRIDX_NAME);
|
|
if (!duk_is_string_notsymbol(ctx, -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(ctx);
|
|
duk_push_hstring_empty(ctx);
|
|
}
|
|
duk_concat(ctx, 2);
|
|
duk_xdef_prop_stridx_short(ctx, -2, DUK_STRIDX_NAME, DUK_PROPDESC_FLAGS_C);
|
|
#endif
|
|
#if defined(DUK_USE_FUNC_FILENAME_PROPERTY)
|
|
duk_get_prop_stridx_short(ctx, -2, DUK_STRIDX_FILE_NAME);
|
|
duk_xdef_prop_stridx_short(ctx, -2, DUK_STRIDX_FILE_NAME, DUK_PROPDESC_FLAGS_C);
|
|
#endif
|
|
|
|
/* 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;
|
|
}
|
|
#endif /* DUK_USE_FUNCTION_BUILTIN */
|
|
|