/* * 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); } if (nargs == 0) { duk_push_string(ctx, ""); duk_push_string(ctx, ""); } else if (nargs == 1) { /* XXX: cover this with the generic >1 case? */ duk_push_string(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); 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); func = (duk_hcompfunc *) duk_known_hobject(ctx, -1); DUK_ASSERT(DUK_HOBJECT_IS_COMPFUNC((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(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_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(ctx, -2, DUK_STRIDX_INT_TARGET, DUK_PROPDESC_FLAGS_NONE); duk_dup_0(ctx); /* 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 ] */ 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(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_xdef_prop_stridx_thrower(ctx, -1, DUK_STRIDX_LC_ARGUMENTS); /* these non-standard properties are copied for convenience */ /* XXX: 'copy properties' API call? */ duk_push_string(ctx, "bound "); /* ES6 19.2.3.2. */ duk_get_prop_stridx(ctx, -3, DUK_STRIDX_NAME); if (!duk_is_string(ctx, -1)) { /* ES6 has requirement to check that .name of target is a string * (also must check for Symbol); if not, targetName should be the * empty string. ES6 19.2.3.2. */ duk_pop(ctx); duk_push_hstring_stridx(ctx, DUK_STRIDX_EMPTY_STRING); } duk_concat(ctx, 2); 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; } #endif /* DUK_USE_FUNCTION_BUILTIN */