Browse Source

Merge pull request #1421 from svaarala/func-call-direct-handling

Direct handling of .call(), .apply(), etc
pull/1522/head
Sami Vaarala 8 years ago
committed by GitHub
parent
commit
f209463718
  1. 9
      RELEASES.rst
  2. 2
      doc/debugger.rst
  3. 8
      doc/release-notes-v2-2.rst
  4. 4
      doc/testcase-known-issues.yaml
  5. 29
      src-input/builtins.yaml
  6. 1
      src-input/duk_api_internal.h
  7. 32
      src-input/duk_api_stack.c
  8. 157
      src-input/duk_bi_function.c
  9. 4
      src-input/duk_debugger.c
  10. 7
      src-input/duk_hobject.h
  11. 31
      src-input/duk_hthread_builtins.c
  12. 154
      src-input/duk_js_call.c
  13. 91
      tests/ecmascript/test-bi-function-call-apply-bind-combinations.js
  14. 33
      tests/ecmascript/test-bi-function-proto-apply-hugeargs.js
  15. 30
      tests/ecmascript/test-bi-function-proto-apply-tail.js
  16. 36
      tests/ecmascript/test-bi-function-proto-call-hugeargs.js
  17. 30
      tests/ecmascript/test-bi-function-proto-call-tail.js
  18. 30
      tests/ecmascript/test-bi-reflect-apply-tail.js
  19. 40
      tests/ecmascript/test-dev-call-apply-not-in-callstack.js
  20. 111
      tests/ecmascript/test-dev-func-call-apply-missing-args.js
  21. 84
      tests/ecmascript/test-dev-func-call-apply-no-native-stack.js
  22. 17
      tests/ecmascript/test-dev-yield-after-callapply.js
  23. 30
      tests/perf/test-call-apply.js
  24. 29
      tests/perf/test-call-call.js
  25. 9
      tools/genbuiltins.py
  26. 7
      website/guide/coroutines.html

9
RELEASES.rst

@ -2848,6 +2848,15 @@ Planned
this change also allows .name and .length to be overridden using
duk_def_prop() or Object.defineProperty() (GH-1493, GH-1494, GH-1515)
* Handle Function.prototype.call(), Function.prototype.apply(), and
Reflect.apply() inline in call handling; as a side effect .call() and
.apply() no longer appear in the call stack (tracebacks etc) (GH-1421)
* Function.prototype.call(), Function.prototype.apply(), and Reflect.apply()
can be used in tailcalls (e.g. 'return func.call(null, 123);'), don't grow
the native C stack when doing an Ecmascript-to-Ecmascript call, and no
longer prevent coroutine yielding (GH-1421)
* Add duk_push_proxy() API call which allows a Proxy to be created from C
code (GH-1500, GH-837)

2
doc/debugger.rst

@ -2400,6 +2400,8 @@ The following list describes artificial keys included in Duktape 1.5.0, see
+---------------------------------+---------------------------+---------------------------------------------------------+
| ``exotic_proxyobj`` | ``duk_hobject`` | DUK_HOBJECT_FLAG_EXOTIC_PROXYOBJ |
+---------------------------------+---------------------------+---------------------------------------------------------+
| ``special_call`` | ``duk_hobject`` | DUK_HOBJECT_FLAG_SPECIAL_CALL |
+---------------------------------+---------------------------+---------------------------------------------------------+
| ``class_number`` | ``duk_hobject`` | Duktape internal class number (same as object dvalue). |
+---------------------------------+---------------------------+---------------------------------------------------------+
| ``class_name`` | ``duk_hobject`` | String class name, e.g. ``"ArrayBuffer"``. |

8
doc/release-notes-v2-2.rst

@ -20,6 +20,14 @@ from Duktape v2.1.x. Note the following:
very low memory targets. If you're using a pool allocator, you may need to
measure and adjust pool sizes/counts.
* Function.prototype.call(), Function.prototype.apply(), and Reflect.apply()
are now handled inline in call handling. As a result, when functions are
called via .call()/.apply() the .call()/.apply() is not part of the call
stack and is absent in e.g. tracebacks. .call()/.apply() no longer prevents
a yield, doesn't consume native stack for Ecmascript-to-Ecmascript calls,
and can now be used in tailcall positions, e.g. in
'return func.call(null, 1, 2);'.
* Functions pushed using duk_push_c_function() and duk_push_c_lightfunc() now
inherit from an intermediate prototype (func -> %NativeFunctionPrototype%
-> Function.prototype) which provides ``.name`` and ``.length`` getters.

4
doc/testcase-known-issues.yaml

@ -60,10 +60,6 @@
test: "test-dev-func-cons-args.js"
knownissue: "corner cases for 'new Function()' when arguments and code are given as strings"
# Moved
-
test: "test-dev-yield-after-callapply.js"
knownissue: "yield() not allowed when function called via Function.prototype.(call|apply)()"
# Moved
-
test: "test-lex-unterminated-hex-uni-escape.js"
knownissue: "unterminated hex escapes should be parsed leniently, e.g. '\\uX' -> 'uX' but Duktape now refuses to parse them"

29
src-input/builtins.yaml

@ -20,6 +20,8 @@
# - native: native function name
# - callable: true
# - constructable: true/false, depending on function
# - special_call: true/false, used for .call(), .apply(), etc which
# need special casing in runtime call handling
# - To disable an object without removing its metadata, you can
# use 'disable: true'.
# - If the object is dependent on Duktape configuration, you can make the
@ -41,9 +43,9 @@
# + e: enumerable
# + c: configurable
# + a: accessor (determined automatically, not necessary to give explicitly)
# - autoLightfunc: if true (which is default), function property may be
# - auto_lightfunc: if true (which is default), function property may be
# automatically converted to a lightfunc if other automatic lightfunc
# conversion criteria are met; use "autoLightfunc: false" to prevent
# conversion criteria are met; use "auto_lightfunc: false" to prevent
# lightfunc conversion
# - May also have feature tags like "es6: true" to indicate property
# is related to a specific standard
@ -108,6 +110,7 @@
# - varargs: if true, function is vararg (nargs = DUK_VARARGS)
# - magic: magic value for function (see below)
# - name: optional, provides .name property (non-writable, non-enumerable, non-configurable)
# - special_call: recognized in shorthand
# - Accessor (setter/getter) shorthand:
# - type: accessor
# - getter: native function name
@ -413,7 +416,7 @@ objects:
type: function
native: duk_bi_global_object_eval
length: 1
autoLightfunc: false # automatic lightfunc conversion clashes with internal implementation
auto_lightfunc: false # automatic lightfunc conversion clashes with internal implementation
present_if: DUK_USE_GLOBAL_BUILTIN
- key: "parseInt"
value:
@ -764,14 +767,19 @@ objects:
type: function
native: duk_bi_function_prototype_apply
length: 2
magic: 0
magic: 1 # see duk_js_call.c
special_call: true
auto_lightfunc: false # automatic lightfunc conversion clashes with internal implementation
present_if: DUK_USE_FUNCTION_BUILTIN
- key: "call"
value:
type: function
native: duk_bi_function_prototype_call
length: 1
magic: 0 # see duk_js_call.c
varargs: true
special_call: true
auto_lightfunc: false # automatic lightfunc conversion clashes with internal implementation
present_if: DUK_USE_FUNCTION_BUILTIN
- key: "bind"
value:
@ -2807,7 +2815,7 @@ objects:
native: duk_bi_thread_yield
length: 2
duktape: true
autoLightfunc: false # automatic lightfunc conversion clashes with internal implementation
auto_lightfunc: false # automatic lightfunc conversion clashes with internal implementation
present_if: DUK_USE_COROUTINE_SUPPORT
- key: "resume"
value:
@ -2815,7 +2823,7 @@ objects:
native: duk_bi_thread_resume
length: 3
duktape: true
autoLightfunc: false # automatic lightfunc conversion clashes with internal implementation
auto_lightfunc: false # automatic lightfunc conversion clashes with internal implementation
present_if: DUK_USE_COROUTINE_SUPPORT
- key: "current"
value:
@ -2981,16 +2989,17 @@ objects:
- key: "apply"
value:
type: function
native: duk_bi_function_prototype_apply
native: duk_bi_reflect_apply
length: 3
magic: 1
magic: 2 # see duk_js_call.c
special_call: true
auto_lightfunc: false # automatic lightfunc conversion clashes with internal implementation
es6: true
- key: "construct"
value:
type: function
native: duk_bi_function_prototype_apply
native: duk_bi_reflect_construct
length: 2
magic: 2
varargs: true
es6: true
- key: "defineProperty"

1
src-input/duk_api_internal.h

@ -277,6 +277,7 @@ DUK_INTERNAL_DECL void duk_xdef_prop_stridx_builtin(duk_context *ctx, duk_idx_t
DUK_INTERNAL_DECL void duk_xdef_prop_stridx_thrower(duk_context *ctx, duk_idx_t obj_idx, duk_small_uint_t stridx); /* [] -> [] */
DUK_INTERNAL_DECL void duk_pack(duk_context *ctx, duk_idx_t count);
DUK_INTERNAL_DECL duk_idx_t duk_unpack_array_like(duk_context *ctx, duk_idx_t idx);
#if 0
DUK_INTERNAL_DECL void duk_unpack(duk_context *ctx);
#endif

32
src-input/duk_api_stack.c

@ -5497,6 +5497,38 @@ DUK_INTERNAL void duk_pack(duk_context *ctx, duk_idx_t count) {
thr->valstack_top = tv_dst + 1;
}
DUK_INTERNAL duk_idx_t duk_unpack_array_like(duk_context *ctx, duk_idx_t idx) {
duk_uint_t mask;
duk_idx_t len;
duk_idx_t i;
idx = duk_require_normalize_index(ctx, idx);
mask = duk_get_type_mask(ctx, idx);
if (mask & (DUK_TYPE_MASK_NULL | DUK_TYPE_MASK_UNDEFINED)) {
len = 0;
} else if (mask & DUK_TYPE_MASK_OBJECT) {
/* XXX: ToUint32() coercion is used in ES5.1 for
* Function.prototype.call() and .apply(). ES2015
* updates this to ToLength() which is not implemented yet.
*/
/* XXX: direct array handling */
duk_get_prop_stridx(ctx, idx, DUK_STRIDX_LENGTH);
len = (duk_idx_t) duk_to_uint32(ctx, -1); /* ToUint32() coercion required */
duk_pop(ctx);
duk_require_stack(ctx, len);
for (i = 0; i < len; i++) {
duk_get_prop_index(ctx, idx, i);
}
} else {
DUK_ERROR_TYPE_INVALID_ARGS((duk_hthread *) ctx);
}
DUK_ASSERT(len >= 0);
return len;
}
#if 0
/* XXX: unpack to position? */
DUK_INTERNAL void duk_unpack(duk_context *ctx) {

157
src-input/duk_bi_function.c

@ -164,143 +164,52 @@ DUK_INTERNAL duk_ret_t duk_bi_function_prototype_to_string(duk_context *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()
/* Always present because the native function pointer is needed in call
* handling.
*/
DUK_INTERNAL duk_ret_t duk_bi_function_prototype_call(duk_context *ctx) {
/* .call() is dealt with in call handling by simulating its
* effects so this function is actually never called.
*/
DUK_UNREF(ctx);
return DUK_RET_TYPE_ERROR;
}
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);
}
duk_set_top(ctx, 2); /* chop off extra arguments: [ constructor argArray ] */
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;
DUK_INTERNAL duk_ret_t duk_bi_function_prototype_apply(duk_context *ctx) {
/* Like .call(), never actually called. */
DUK_UNREF(ctx);
return DUK_RET_TYPE_ERROR;
}
type_error:
DUK_DCERROR_TYPE_INVALID_ARGS((duk_hthread *) ctx);
DUK_INTERNAL duk_ret_t duk_bi_reflect_apply(duk_context *ctx) {
/* Like .call(), never actually called. */
DUK_UNREF(ctx);
return DUK_RET_TYPE_ERROR;
}
#endif
#if defined(DUK_USE_FUNCTION_BUILTIN)
DUK_INTERNAL duk_ret_t duk_bi_function_prototype_call(duk_context *ctx) {
DUK_INTERNAL duk_ret_t duk_bi_reflect_construct(duk_context *ctx) {
duk_idx_t nargs;
duk_idx_t len;
/* Step 1 is not necessary because duk_call_method() will take
* care of it.
*/
duk_require_constructable(ctx, 0);
/* vararg function, thisArg needs special handling */
nargs = duk_get_top(ctx); /* = 1 + arg count */
if (nargs == 0) {
duk_push_undefined(ctx);
nargs++;
nargs = duk_get_top(ctx);
if (nargs < 2) {
DUK_DCERROR_TYPE_INVALID_ARGS((duk_hthread *) ctx);
}
DUK_ASSERT(nargs >= 1);
/* [ thisArg arg1 ... argN ] */
duk_push_this(ctx); /* 'func' in the algorithm */
duk_insert(ctx, 0);
if (nargs >= 3 && !duk_strict_equals(ctx, 0, 2)) {
/* XXX: [[Construct]] newTarget currently unsupported */
DUK_ERROR_UNSUPPORTED((duk_hthread *) ctx);
}
duk_set_top(ctx, 2);
/* [ func thisArg arg1 ... argN ] */
len = duk_unpack_array_like(ctx, 1 /*idx_args*/);
duk_remove(ctx, 1);
DUK_ASSERT_TOP(ctx, len + 1);
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);
duk_new(ctx, len);
return 1;
}
#endif /* DUK_USE_FUNCTION_BUILTIN */
#if defined(DUK_USE_FUNCTION_BUILTIN)
/* Create a bound function which points to a target function which may

4
src-input/duk_debugger.c

@ -1918,7 +1918,8 @@ DUK_LOCAL const char * const duk__debug_getinfo_hobject_keys[] = {
"exotic_array",
"exotic_stringobj",
"exotic_arguments",
"exotic_proxyobj"
"exotic_proxyobj",
"special_call"
/* NULL not needed here */
};
DUK_LOCAL duk_uint_t duk__debug_getinfo_hobject_masks[] = {
@ -1940,6 +1941,7 @@ DUK_LOCAL duk_uint_t duk__debug_getinfo_hobject_masks[] = {
DUK_HOBJECT_FLAG_EXOTIC_STRINGOBJ,
DUK_HOBJECT_FLAG_EXOTIC_ARGUMENTS,
DUK_HOBJECT_FLAG_EXOTIC_PROXYOBJ,
DUK_HOBJECT_FLAG_SPECIAL_CALL,
0 /* terminator */
};
DUK_LOCAL const char * const duk__debug_getinfo_hbuffer_keys[] = {

7
src-input/duk_hobject.h

@ -56,8 +56,8 @@
#define DUK_HOBJECT_FLAG_EXOTIC_ARRAY DUK_HEAPHDR_USER_FLAG(15) /* 'Array' object, array length and index exotic behavior */
#define DUK_HOBJECT_FLAG_EXOTIC_STRINGOBJ DUK_HEAPHDR_USER_FLAG(16) /* 'String' object, array index exotic behavior */
#define DUK_HOBJECT_FLAG_EXOTIC_ARGUMENTS DUK_HEAPHDR_USER_FLAG(17) /* 'Arguments' object and has arguments exotic behavior (non-strict callee) */
#define DUK_HOBJECT_FLAG_UNUSED18 DUK_HEAPHDR_USER_FLAG(18) /* unused */
#define DUK_HOBJECT_FLAG_EXOTIC_PROXYOBJ DUK_HEAPHDR_USER_FLAG(19) /* 'Proxy' object */
#define DUK_HOBJECT_FLAG_EXOTIC_PROXYOBJ DUK_HEAPHDR_USER_FLAG(18) /* 'Proxy' object */
#define DUK_HOBJECT_FLAG_SPECIAL_CALL DUK_HEAPHDR_USER_FLAG(19) /* special casing in call behavior, for .call(), .apply(), etc. */
#define DUK_HOBJECT_FLAG_CLASS_BASE DUK_HEAPHDR_USER_FLAG_NUMBER(20)
#define DUK_HOBJECT_FLAG_CLASS_BITS 5
@ -214,6 +214,7 @@
#define DUK_HOBJECT_HAS_EXOTIC_STRINGOBJ(h) DUK_HEAPHDR_CHECK_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_EXOTIC_STRINGOBJ)
#define DUK_HOBJECT_HAS_EXOTIC_ARGUMENTS(h) DUK_HEAPHDR_CHECK_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_EXOTIC_ARGUMENTS)
#define DUK_HOBJECT_HAS_EXOTIC_PROXYOBJ(h) DUK_HEAPHDR_CHECK_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_EXOTIC_PROXYOBJ)
#define DUK_HOBJECT_HAS_SPECIAL_CALL(h) DUK_HEAPHDR_CHECK_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_SPECIAL_CALL)
#define DUK_HOBJECT_SET_EXTENSIBLE(h) DUK_HEAPHDR_SET_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_EXTENSIBLE)
#define DUK_HOBJECT_SET_CONSTRUCTABLE(h) DUK_HEAPHDR_SET_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_CONSTRUCTABLE)
@ -233,6 +234,7 @@
#define DUK_HOBJECT_SET_EXOTIC_STRINGOBJ(h) DUK_HEAPHDR_SET_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_EXOTIC_STRINGOBJ)
#define DUK_HOBJECT_SET_EXOTIC_ARGUMENTS(h) DUK_HEAPHDR_SET_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_EXOTIC_ARGUMENTS)
#define DUK_HOBJECT_SET_EXOTIC_PROXYOBJ(h) DUK_HEAPHDR_SET_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_EXOTIC_PROXYOBJ)
#define DUK_HOBJECT_SET_SPECIAL_CALL(h) DUK_HEAPHDR_SET_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_SPECIAL_CALL)
#define DUK_HOBJECT_CLEAR_EXTENSIBLE(h) DUK_HEAPHDR_CLEAR_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_EXTENSIBLE)
#define DUK_HOBJECT_CLEAR_CONSTRUCTABLE(h) DUK_HEAPHDR_CLEAR_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_CONSTRUCTABLE)
@ -252,6 +254,7 @@
#define DUK_HOBJECT_CLEAR_EXOTIC_STRINGOBJ(h) DUK_HEAPHDR_CLEAR_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_EXOTIC_STRINGOBJ)
#define DUK_HOBJECT_CLEAR_EXOTIC_ARGUMENTS(h) DUK_HEAPHDR_CLEAR_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_EXOTIC_ARGUMENTS)
#define DUK_HOBJECT_CLEAR_EXOTIC_PROXYOBJ(h) DUK_HEAPHDR_CLEAR_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_EXOTIC_PROXYOBJ)
#define DUK_HOBJECT_CLEAR_SPECIAL_CALL(h) DUK_HEAPHDR_CLEAR_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_SPECIAL_CALL)
/* Object can/cannot use FASTREFS, i.e. has no strong reference fields beyond
* duk_hobject base header. This is used just for asserts so doesn't need to

31
src-input/duk_hthread_builtins.c

@ -582,14 +582,22 @@ DUK_INTERNAL void duk_hthread_create_builtin_objects(duk_hthread *thr) {
(c_length <= DUK_LFUNC_LENGTH_MAX) &&
(magic >= DUK_LFUNC_MAGIC_MIN && magic <= DUK_LFUNC_MAGIC_MAX);
if (h_key == DUK_HTHREAD_STRING_EVAL(thr) ||
h_key == DUK_HTHREAD_STRING_YIELD(thr) ||
h_key == DUK_HTHREAD_STRING_RESUME(thr)) {
/* These functions have trouble working as lightfuncs.
* Some of them have specific asserts and some may have
* additional properties (e.g. 'require.id' may be written).
*/
DUK_D(DUK_DPRINT("reject as lightfunc: key=%!O, i=%d, j=%d", (duk_heaphdr *) h_key, (int) i, (int) j));
/* These functions have trouble working as lightfuncs.
* Some of them have specific asserts and some may have
* additional properties (e.g. 'require.id' may be written).
*/
if (c_func == duk_bi_global_object_eval) {
lightfunc_eligible = 0;
}
#if defined(DUK_USE_COROUTINE_SUPPORT)
if (c_func == duk_bi_thread_yield ||
c_func == duk_bi_thread_resume) {
lightfunc_eligible = 0;
}
#endif
if (c_func == duk_bi_function_prototype_call ||
c_func == duk_bi_function_prototype_apply ||
c_func == duk_bi_reflect_apply) {
lightfunc_eligible = 0;
}
@ -612,6 +620,13 @@ DUK_INTERNAL void duk_hthread_create_builtin_objects(duk_hthread *thr) {
h_func = duk_known_hnatfunc(ctx, -1);
DUK_UNREF(h_func);
/* Special call handling, not described in init data. */
if (c_func == duk_bi_function_prototype_call ||
c_func == duk_bi_function_prototype_apply ||
c_func == duk_bi_reflect_apply) {
DUK_HOBJECT_SET_SPECIAL_CALL((duk_hobject *) h_func);
}
/* Currently all built-in native functions are strict.
* This doesn't matter for many functions, but e.g.
* String.prototype.charAt (and other string functions)

154
src-input/duk_js_call.c

@ -497,23 +497,18 @@ DUK_LOCAL void duk__handle_createargs_for_call(duk_hthread *thr,
DUK_LOCAL void duk__handle_bound_chain_for_call(duk_hthread *thr,
duk_idx_t idx_func,
duk_idx_t *p_num_stack_args, /* may be changed by call */
duk_bool_t is_constructor_call) {
duk_context *ctx = (duk_context *) thr;
duk_idx_t num_stack_args;
duk_tval *tv_func;
duk_hobject *func;
duk_idx_t len;
DUK_ASSERT(thr != NULL);
DUK_ASSERT(p_num_stack_args != NULL);
/* On entry, item at idx_func is a bound, non-lightweight function,
* but we don't rely on that below.
*/
num_stack_args = *p_num_stack_args;
tv_func = duk_require_tval(ctx, idx_func);
DUK_ASSERT(tv_func != NULL);
@ -531,8 +526,8 @@ DUK_LOCAL void duk__handle_bound_chain_for_call(duk_hthread *thr,
len = h_bound->nargs;
DUK_ASSERT(len == 0 || tv_args != NULL);
DUK_DDD(DUK_DDDPRINT("bound function encountered, ptr=%p, num_stack_args=%ld: %!T",
(void *) DUK_TVAL_GET_OBJECT(tv_func), (long) num_stack_args, tv_func));
DUK_DDD(DUK_DDDPRINT("bound function encountered, ptr=%p: %!T",
(void *) DUK_TVAL_GET_OBJECT(tv_func), tv_func));
/* [ ... func this arg1 ... argN ] */
@ -551,15 +546,14 @@ DUK_LOCAL void duk__handle_bound_chain_for_call(duk_hthread *thr,
tv_gap = duk_create_gap(ctx, idx_func + 2, len);
duk_copy_tvals_incref(thr, tv_gap, tv_args, len);
num_stack_args += len; /* must be updated to work properly (e.g. creation of 'arguments') */
/* [ ... func this <bound args> arg1 ... argN ] */
duk_push_tval(ctx, &h_bound->target);
duk_replace(ctx, idx_func); /* replace in stack */
DUK_DDD(DUK_DDDPRINT("bound function handled, num_stack_args=%ld, idx_func=%ld, curr func=%!T",
(long) num_stack_args, (long) idx_func, duk_get_tval(ctx, idx_func)));
DUK_DDD(DUK_DDDPRINT("bound function handled, idx_func=%ld, curr func=%!T",
(long) idx_func, duk_get_tval(ctx, idx_func)));
}
} else if (DUK_TVAL_IS_LIGHTFUNC(tv_func)) {
/* Lightweight function: never bound, so terminate. */
@ -582,9 +576,111 @@ DUK_LOCAL void duk__handle_bound_chain_for_call(duk_hthread *thr,
DUK_HOBJECT_HAS_NATFUNC(func));
}
#endif
}
/*
* Helper for inline handling of .call() and .apply().
*/
DUK_LOCAL void duk__handle_callapply_for_call(duk_hthread *thr, duk_idx_t idx_func, duk_hobject *func) {
duk_context *ctx = (duk_context *) thr;
#if defined(DUK_USE_ASSERTIONS)
duk_c_function natfunc;
#endif
#if defined(DUK_USE_ASSERTIONS)
natfunc = ((duk_hnatfunc *) func)->func;
DUK_ASSERT(natfunc != NULL);
#endif
/* write back */
*p_num_stack_args = num_stack_args;
/* Handle .call() and .apply() based on them having the
* DUK_HOBJECT_FLAG_SPECIAL_CALL flag; their magic value
* is used for switch-case.
*
* NOTE: duk_unpack_array_like() reserves value stack space
* for the result values (unlike most other value stack calls).
*/
switch (((duk_hnatfunc *) func)->magic) {
case 0: { /* 0=Function.prototype.call() */
/* Value stack:
* idx_func + 0: Function.prototype.call()
* idx_func + 1: this binding for .call (target function)
* idx_func + 2: 1st argument to .call, desired 'this' binding
* idx_func + 3: 2nd argument to .call, desired 1st argument for ultimate target
* ...
*
* Remove idx_func + 0 to get:
* idx_func + 0: target function
* idx_func + 1: this binding
* idx_func + 2: call arguments
* ...
*/
DUK_ASSERT(natfunc == duk_bi_function_prototype_call);
while (duk_get_top(ctx) < idx_func + 3) {
duk_push_undefined(ctx);
}
duk_remove(ctx, idx_func);
break;
}
case 1: { /* 1=Function.prototype.apply() */
/* Value stack:
* idx_func + 0: Function.prototype.apply()
* idx_func + 1: this binding for .apply (target function)
* idx_func + 2: 1st argument to .apply, desired 'this' binding
* idx_func + 3: 2nd argument to .apply, argArray
* [anything after this MUST be ignored]
*
* Remove idx_func + 0 and unpack the argArray to get:
* idx_func + 0: target function
* idx_func + 1: this binding
* idx_func + 2: call arguments
* ...
*/
DUK_ASSERT(natfunc == duk_bi_function_prototype_apply);
while (duk_get_top(ctx) < idx_func + 3) {
duk_push_undefined(ctx);
}
while (duk_get_top(ctx) > idx_func + 4) {
duk_pop(ctx);
}
duk_remove(ctx, idx_func);
if (duk_is_valid_index(ctx, idx_func + 2)) {
(void) duk_unpack_array_like(ctx, idx_func + 2);
duk_remove(ctx, idx_func + 2);
}
break;
}
default: { /* 2=Reflect.apply() */
/* Value stack:
* idx_func + 0: Reflect.apply()
* idx_func + 1: this binding for .apply (ignored, usually Reflect)
* idx_func + 2: 1st argument to .apply, target function
* idx_func + 3: 2nd argument to .apply, desired 'this' binding
* idx_func + 4: 3rd argument to .apply, argArray
* [anything after this MUST be ignored]
*
* Remove idx_func + 0 and idx_func + 1, and unpack the argArray to get:
* idx_func + 0: target function
* idx_func + 1: this binding
* idx_func + 2: call arguments
* ...
*/
DUK_ASSERT(natfunc == duk_bi_reflect_apply);
while (duk_get_top(ctx) < idx_func + 4) {
duk_push_undefined(ctx);
}
while (duk_get_top(ctx) > idx_func + 5) {
duk_pop(ctx);
}
duk_remove(ctx, idx_func);
duk_remove(ctx, idx_func);
if (duk_is_valid_index(ctx, idx_func + 2)) {
(void) duk_unpack_array_like(ctx, idx_func + 2);
duk_remove(ctx, idx_func + 2);
}
break;
}
}
}
/*
@ -784,6 +880,7 @@ DUK_LOCAL void duk__coerce_effective_this_binding(duk_hthread *thr,
* Shared helper for non-bound func lookup.
*
* Returns duk_hobject * to the final non-bound function (NULL for lightfunc).
* Also handles .call() and .apply() inline.
*/
DUK_LOCAL duk_hobject *duk__nonbound_func_lookup(duk_context *ctx,
@ -802,11 +899,19 @@ DUK_LOCAL duk_hobject *duk__nonbound_func_lookup(duk_context *ctx,
if (DUK_TVAL_IS_OBJECT(tv_func)) {
func = DUK_TVAL_GET_OBJECT(tv_func);
if (!DUK_HOBJECT_IS_CALLABLE(func)) {
if (DUK_UNLIKELY(!DUK_HOBJECT_IS_CALLABLE(func))) {
goto not_callable_error;
}
if (DUK_LIKELY(!DUK_HOBJECT_HAS_BOUNDFUNC(func) && !DUK_HOBJECT_HAS_SPECIAL_CALL(func))) {
/* Common case, so test for using a single bitfield test. */
break;
}
if (DUK_HOBJECT_HAS_BOUNDFUNC(func)) {
duk__handle_bound_chain_for_call(thr, idx_func, out_num_stack_args, call_flags & DUK_CALL_FLAG_CONSTRUCTOR_CALL);
DUK_ASSERT(!DUK_HOBJECT_HAS_SPECIAL_CALL(func));
DUK_ASSERT(!DUK_HOBJECT_IS_NATFUNC(func));
duk__handle_bound_chain_for_call(thr, idx_func, call_flags & DUK_CALL_FLAG_CONSTRUCTOR_CALL);
/* The final object may be a normal function or a lightfunc.
* We need to re-lookup tv_func because it may have changed
@ -815,16 +920,28 @@ DUK_LOCAL duk_hobject *duk__nonbound_func_lookup(duk_context *ctx,
*/
DUK_ASSERT(DUK_TVAL_IS_OBJECT(duk_require_tval(ctx, idx_func)) ||
DUK_TVAL_IS_LIGHTFUNC(duk_require_tval(ctx, idx_func)));
continue;
} else {
DUK_ASSERT(DUK_HOBJECT_HAS_SPECIAL_CALL(func));
DUK_ASSERT(DUK_HOBJECT_IS_NATFUNC(func));
duk__handle_callapply_for_call(thr, idx_func, func);
}
/* Retry loop. */
} else if (DUK_TVAL_IS_LIGHTFUNC(tv_func)) {
func = NULL;
break;
} else {
goto not_callable_error;
}
break;
}
/* Recompute num_stack_args.
* XXX: the whole num_stack_args tracking could be removed
* from the call site.
*/
DUK_ASSERT(duk_get_top(ctx) >= idx_func + 2);
*out_num_stack_args = duk_get_top(ctx) - (idx_func + 2);
DUK_ASSERT((DUK_TVAL_IS_OBJECT(tv_func) && DUK_HOBJECT_IS_CALLABLE(DUK_TVAL_GET_OBJECT(tv_func))) ||
DUK_TVAL_IS_LIGHTFUNC(tv_func));
DUK_ASSERT(func == NULL || !DUK_HOBJECT_HAS_BOUNDFUNC(func));
@ -897,9 +1014,7 @@ DUK_LOCAL void duk__adjust_valstack_and_top(duk_hthread *thr,
(void) duk_valstack_resize_raw((duk_context *) thr,
vs_min_size,
DUK_VSRESIZE_FLAG_SHRINK | /* flags */
0 /* no compact */ |
DUK_VSRESIZE_FLAG_THROW);
DUK_VSRESIZE_FLAG_THROW); /* flags: no shrink or compact */
if (!adjusted_top) {
if (nregs >= 0) {
@ -1220,6 +1335,7 @@ DUK_INTERNAL void duk_handle_call_unprotected(duk_hthread *thr,
duk__handle_call_inner(thr, num_stack_args, call_flags, idx_func);
}
/* XXX: idx_func and num_stack_args are redundant */
DUK_LOCAL void duk__handle_call_inner(duk_hthread *thr,
duk_idx_t num_stack_args,
duk_small_uint_t call_flags,

91
tests/ecmascript/test-bi-function-call-apply-bind-combinations.js

@ -0,0 +1,91 @@
/*
* Test that .bind(), .call(), and .apply() can be used in various
* combinations.
*/
/*===
[object global] 1 2 3 undefined undefined undefined undefined undefined undefined undefined
arguments: 3 1 2 3 undefined undefined undefined undefined undefined undefined undefined undefined undefined
mythis 101 102 undefined undefined undefined undefined undefined undefined undefined undefined
arguments: 2 101 102 undefined undefined undefined undefined undefined undefined undefined undefined undefined undefined
mythis 101 102 103 undefined undefined undefined undefined undefined undefined undefined
arguments: 3 101 102 103 undefined undefined undefined undefined undefined undefined undefined undefined undefined
mythis 101 102 103 104 undefined undefined undefined undefined undefined undefined
arguments: 4 101 102 103 104 undefined undefined undefined undefined undefined undefined undefined undefined
mythis 101 102 103 104 105 undefined undefined undefined undefined undefined
arguments: 5 101 102 103 104 105 undefined undefined undefined undefined undefined undefined undefined
mythis 101 102 103 104 105 106 undefined undefined undefined undefined
arguments: 6 101 102 103 104 105 106 undefined undefined undefined undefined undefined undefined
mythis 101 102 103 104 105 106 107 undefined undefined undefined
arguments: 7 101 102 103 104 105 106 107 undefined undefined undefined undefined undefined
boundcallthis 201 202 303 304 undefined undefined undefined undefined undefined undefined
arguments: 4 201 202 303 304 undefined undefined undefined undefined undefined undefined undefined undefined
boundcallthis 201 202 303 304 undefined undefined undefined undefined undefined undefined
arguments: 4 201 202 303 304 undefined undefined undefined undefined undefined undefined undefined undefined
mythis 101 202 203 304 305 undefined undefined undefined undefined undefined
arguments: 5 101 202 203 304 305 undefined undefined undefined undefined undefined undefined undefined
mythis 101 202 203 304 305 undefined undefined undefined undefined undefined
arguments: 5 101 202 203 304 305 undefined undefined undefined undefined undefined undefined undefined
boundapplythis 201 202 undefined undefined undefined undefined undefined undefined undefined undefined
arguments: 2 201 202 undefined undefined undefined undefined undefined undefined undefined undefined undefined undefined
boundapplythis 201 202 undefined undefined undefined undefined undefined undefined undefined undefined
arguments: 2 201 202 undefined undefined undefined undefined undefined undefined undefined undefined undefined undefined
mythis 101 202 203 undefined undefined undefined undefined undefined undefined undefined
arguments: 3 101 202 203 undefined undefined undefined undefined undefined undefined undefined undefined undefined
mythis 101 202 203 undefined undefined undefined undefined undefined undefined undefined
arguments: 3 101 202 203 undefined undefined undefined undefined undefined undefined undefined undefined undefined
===*/
function test() {
var f0 = function f0(a, b, c, d, e, f, g, h, i, j) {
print(this, a, b, c, d, e, f, g, h, i, j);
print('arguments:', arguments.length,
arguments[0], arguments[1], arguments[2], arguments[3],
arguments[4], arguments[5], arguments[6], arguments[7],
arguments[8], arguments[9], arguments[10], arguments[11]);
};
// Direct call.
f0(1, 2, 3);
// Bound call.
var f1 = f0.bind('mythis', 101);
f1(102);
// Bound call, call via .call().
f1.call('callthis', 102, 103);
// Bound call, call via .call(); but use .apply() to call .call().
f1.call.apply(f1, [ 'callthis', 102, 103, 104 ]);
// Bound call, call via .apply().
f1.apply('applythis', [ 102, 103, 104, 105 ]);
// Bound call, call via .apply(); but use .call() to call .apply().
f1.apply.call(f1, 'applythis', [ 102, 103, 104, 105, 106 ]);
// Bound call, call via .apply(); but use .apply() to call .apply().
f1.apply.apply(f1, [ 'applythis', [ 102, 103, 104, 105, 106, 107 ] ]);
// Use .bind() on .call().
var f2 = f0.call.bind(f0, 'boundcallthis', 201, 202);
f2(303, 304);
f2.call('ignored', 303, 304);
var f3 = f1.call.bind(f1, 'boundcallthis', 202, 203);
f3(304, 305);
f3.apply('ignored', [ 304, 305 ]);
// Use .bind() on .apply().
var f4 = f0.apply.bind(f0, 'boundapplythis', [ 201, 202 ]);
f4(303, 304);
f4.call('ignored', 303, 304); // these args get ignored because they don't affect [ 201, 202 ]
var f5 = f1.apply.bind(f1, 'boundapplythis', [ 202, 203 ]);
f5(304, 305);
f5.apply('ignored', [ 304, 305 ]); // same here, [ 304, 305 ] is ignored by .apply()
}
try {
test();
} catch (e) {
print(e.stack || e);
}

33
tests/ecmascript/test-bi-function-proto-apply-hugeargs.js

@ -0,0 +1,33 @@
/*===
f0 called
applythis arg-0 arg-1 arg-2
500000
arg-499998
arg-499999
undefined
done
===*/
function test() {
function f0(a, b, c) {
print('f0 called');
print(this, a, b, c);
print(arguments.length);
print(arguments[499998]);
print(arguments[499999]);
print(arguments[500000]);
}
var args = [];
while (args.length < 500000) {
args.push('arg-' + args.length);
}
f0.apply('applythis', args);
print('done');
}
try {
test();
} catch (e) {
print(e.stack || e);
}

30
tests/ecmascript/test-bi-function-proto-apply-tail.js

@ -0,0 +1,30 @@
/*===
0
100000
200000
300000
400000
500000
600000
700000
800000
900000
1000000
done
===*/
function test(count) {
if ((count % 1e5) == 0) {
print(count);
}
if (count < 1e6) {
return test.apply(null, [ count + 1 ]);
}
return 'done';
}
try {
print(test(0));
} catch (e) {
print(e.stack || e);
}

36
tests/ecmascript/test-bi-function-proto-call-hugeargs.js

@ -0,0 +1,36 @@
/*===
f0 called
callthis arg-0 arg-1 arg-2
500000
arg-499998
arg-499999
undefined
done
===*/
function test() {
function f0(a, b, c) {
print('f0 called');
print(this, a, b, c);
print(arguments.length);
print(arguments[499998]);
print(arguments[499999]);
print(arguments[500000]);
}
var args = [];
while (args.length < 500000) {
args.push('arg-' + args.length);
}
args.unshift('callthis');
// Call .call() via .apply() to ensure huge argument count makes it.
f0.call.apply(f0, args);
print('done');
}
try {
test();
} catch (e) {
print(e.stack || e);
}

30
tests/ecmascript/test-bi-function-proto-call-tail.js

@ -0,0 +1,30 @@
/*===
0
100000
200000
300000
400000
500000
600000
700000
800000
900000
1000000
done
===*/
function test(count) {
if ((count % 1e5) == 0) {
print(count);
}
if (count < 1e6) {
return test.call(null, count + 1);
}
return 'done';
}
try {
print(test(0));
} catch (e) {
print(e.stack || e);
}

30
tests/ecmascript/test-bi-reflect-apply-tail.js

@ -0,0 +1,30 @@
/*===
0
100000
200000
300000
400000
500000
600000
700000
800000
900000
1000000
done
===*/
function test(count) {
if ((count % 1e5) == 0) {
print(count);
}
if (count < 1e6) {
return Reflect.apply(test, null, [ count + 1 ]);
}
return 'done';
}
try {
print(test(0));
} catch (e) {
print(e.stack || e);
}

40
tests/ecmascript/test-dev-call-apply-not-in-callstack.js

@ -0,0 +1,40 @@
/*
* Since Duktape 2.2 .call() and .apply() no longer visible in call stack.
*/
/*===
f0 called
-1 act
-2 f0
-3 f1
-4 f2
-5 f3
-6 test
-7 global
===*/
function test() {
var f0 = function f0() {
print('f0 called');
for (var i = -1; ; i--) {
var act = Duktape.act(i);
if (!act) { break; }
print(i, act.function.name);
}
};
var f1 = function f1() {
Reflect.apply(f0, 'dummy');
}
var f2 = function f2() {
f1.call('dummy');
}
var f3 = function f3() {
f2.apply('dummy');
}
f3();
}
try {
test();
} catch (e) {
print(e.stack || e);
}

111
tests/ecmascript/test-dev-func-call-apply-missing-args.js

@ -0,0 +1,111 @@
/*
* Some tests for .call() and .apply() with missing arguments.
*/
/*===
- Function.prototype.call()
undefined undefined undefined undefined
mythis undefined undefined undefined
mythis arg undefined undefined
- Function.prototype.apply()
undefined undefined undefined undefined
mythis undefined undefined undefined
mythis undefined undefined undefined
mythis undefined undefined undefined
mythis undefined undefined undefined
mythis arg undefined undefined
mythis undefined undefined undefined
mythis foo bar undefined
mythis FOO BAR undefined
TypeError
- Reflect.apply()
TypeError
TypeError
undefined undefined undefined undefined
mythis undefined undefined undefined
mythis undefined undefined undefined
mythis undefined undefined undefined
mythis undefined undefined undefined
mythis arg undefined undefined
mythis undefined undefined undefined
mythis foo bar undefined
mythis FOO BAR undefined
TypeError
done
===*/
function test() {
function func(a,b,c) {
'use strict'; // avoid 'this' coercion
print(this, a, b, c);
}
var dummyFunc = function dummy(a,b) {}; // length: 2
dummyFunc[0] = 'FOO';
dummyFunc[1] = 'BAR';
dummyFunc[2] = 'QUUX';
// For .call() there are no required arguments.
// This binding will be 'undefined'.
print('- Function.prototype.call()');
func.call();
func.call('mythis');
func.call('mythis', 'arg');
// For .apply() there are similarly no required arguments.
// argArray can be null/undefined and is treated like empty
// array. Array-like object is accepted (.length is respected);
// other types cause a TypeError. A function is an acceptable
// input because it's an object and even has a .length!
print('- Function.prototype.apply()');
func.apply();
func.apply('mythis');
func.apply('mythis', void 0);
func.apply('mythis', null);
func.apply('mythis', []);
func.apply('mythis', [ 'arg' ]);
func.apply('mythis', {}); // no .length -> same as []
func.apply('mythis', { length: 2, 0: 'foo', 1: 'bar', 2: 'quux-ignored' });
func.apply('mythis', dummyFunc);
try {
func.apply('mythis', 123);
} catch (e) {
print(e.name);
}
// Reflect.apply() requires a callable first argument,
// but is otherwise similar to Function.prototype.apply().
print('- Reflect.apply()');
try {
Reflect.apply();
} catch (e) {
print(e.name);
}
try {
Reflect.apply(123);
} catch (e) {
print(e.name);
}
Reflect.apply(func);
Reflect.apply(func, 'mythis');
Reflect.apply(func, 'mythis', void 0);
Reflect.apply(func, 'mythis', null);
Reflect.apply(func, 'mythis', []);
Reflect.apply(func, 'mythis', [ 'arg' ]);
Reflect.apply(func, 'mythis', {});
Reflect.apply(func, 'mythis', { length: 2, 0: 'foo', 1: 'bar', 2: 'quux-ignored' });
Reflect.apply(func, 'mythis', dummyFunc);
try {
Reflect.apply(func, 'mythis', 123);
} catch (e) {
print(e.name);
}
print('done');
}
try {
test();
} catch (e) {
print(e.stack || e);
}

84
tests/ecmascript/test-dev-func-call-apply-no-native-stack.js

@ -0,0 +1,84 @@
/*
* Demonstrate that in Duktape 2.2 .call() and .apply() no longer
* consume native call stack when the resolved call is an Ecma-to-Ecma
* call.
*
* Can't test against a specific limit but the native stack limit is
* ~500 calls while the default call stack limit is ~10000 calls.
*/
/*===
RangeError
true
RangeError
true
RangeError
true
RangeError
true
done
===*/
function test() {
var count;
function f1() {
count++;
f1(); // not a tail call
void count;
}
function f2() {
count++;
f2.call(); // not a tail call
void count;
}
function f3() {
count++;
f3.apply(); // not a tail call
void count;
}
function f4() {
count++;
Reflect.apply(f4); // not a tail call
void count;
}
try {
count = 0;
f1();
} catch (e) {
print(e.name);
}
print(count > 2000);
try {
count = 0;
f2();
} catch (e) {
print(e.name);
}
print(count > 2000);
try {
count = 0;
f3();
} catch (e) {
print(e.name);
}
print(count > 2000);
try {
count = 0;
f4();
} catch (e) {
print(e.name);
}
print(count > 2000);
print('done');
}
try {
test();
} catch (e) {
print(e.stack || e);
}

17
tests/ecmascript/test-dev-yield-after-callapply.js

@ -10,10 +10,11 @@ var res;
/*===
123
123
123
===*/
/* Calling via Function.prototype.call() or Function.prototype.apply()
* currently prevents a yield.
* no longer prevents a yield in Duktape 2.2.
*/
function innerfunc() {
@ -21,15 +22,17 @@ function innerfunc() {
}
function coroutine1() {
// This is a native call so the current (naive) handling prevents a later yield
innerfunc.call();
}
function coroutine2() {
// Same here
innerfunc.apply();
}
function coroutine3() {
Reflect.apply(innerfunc);
}
try {
thread = new Duktape.Thread(coroutine1);
res = Duktape.Thread.resume(thread, 0);
@ -45,3 +48,11 @@ try {
} catch (e) {
print(e.name);
}
try {
thread = new Duktape.Thread(coroutine3);
res = Duktape.Thread.resume(thread, 0);
print(res);
} catch (e) {
print(e.name);
}

30
tests/perf/test-call-apply.js

@ -0,0 +1,30 @@
if (typeof print !== 'function') { print = console.log; }
function target(x) {
}
function test() {
var f = target;
var i;
var args = [ 123 ];
for (i = 0; i < 1e6; i++) {
target.apply(null, args);
target.apply(null, args);
target.apply(null, args);
target.apply(null, args);
target.apply(null, args);
target.apply(null, args);
target.apply(null, args);
target.apply(null, args);
target.apply(null, args);
target.apply(null, args);
}
}
try {
test();
} catch (e) {
print(e.stack || e);
throw e;
}

29
tests/perf/test-call-call.js

@ -0,0 +1,29 @@
if (typeof print !== 'function') { print = console.log; }
function target(x) {
}
function test() {
var f = target;
var i;
for (i = 0; i < 1e6; i++) {
target.call(null, 123);
target.call(null, 123);
target.call(null, 123);
target.call(null, 123);
target.call(null, 123);
target.call(null, 123);
target.call(null, 123);
target.call(null, 123);
target.call(null, 123);
target.call(null, 123);
}
}
try {
test();
} catch (e) {
print(e.stack || e);
throw e;
}

9
tools/genbuiltins.py

@ -425,6 +425,7 @@ def metadata_normalize_shorthand(meta):
obj['class'] = 'Function'
obj['callable'] = True
obj['constructable'] = val.get('constructable', False)
obj['special_call'] = val.get('special_call', False)
fun_name = val.get('name', funprop['key'])
props.append({ 'key': 'length', 'value': val['length'], 'attributes': 'c' }) # Configurable in ES2015
props.append({ 'key': 'name', 'value': fun_name, 'attributes': 'c' }) # Configurable in ES2015
@ -442,6 +443,7 @@ def metadata_normalize_shorthand(meta):
obj['class'] = 'Function'
obj['callable'] = True
obj['constructable'] = False
assert(obj.get('special_call', False) == False)
# Shorthand accessors are minimal and have no .length or .name
# right now. Use longhand if these matter.
#props.append({ 'key': 'length', 'value': length, 'attributes': 'c' })
@ -512,7 +514,7 @@ def metadata_normalize_shorthand(meta):
def clonePropShared(prop):
res = {}
for k in [ 'key', 'attributes', 'autoLightfunc' ]:
for k in [ 'key', 'attributes', 'auto_lightfunc' ]:
if prop.has_key(k):
res[k] = prop[k]
return res
@ -745,7 +747,7 @@ def metadata_convert_lightfuncs(meta):
if p2['key'] not in [ 'length', 'name' ]:
reasons.append('nonallowed-property')
if not p.get('autoLightfunc', True):
if not p.get('auto_lightfunc', True):
logger.debug('Automatic lightfunc conversion rejected for key %s, explicitly requested in metadata' % p['key'])
reasons.append('no-auto-lightfunc')
@ -1712,6 +1714,7 @@ def gen_ramobj_initdata_for_object(meta, be, bi, string_to_stridx, natfunc_name_
be.bits(1, 1) # flag: constructable
else:
be.bits(0, 1) # flag: not constructable
# DUK_HOBJECT_FLAG_SPECIAL_CALL is handled at runtime without init data.
# Convert signed magic to 16-bit unsigned for encoding
magic = resolve_magic(bi.get('magic'), objid_to_bidx) & 0xffff
@ -2751,6 +2754,8 @@ def rom_emit_objects(genc, meta, bi_str_map):
flags.append('DUK_HOBJECT_FLAG_CONSTRUCTABLE')
if obj.get('class') == 'Array':
flags.append('DUK_HOBJECT_FLAG_EXOTIC_ARRAY')
if obj.get('special_call', False):
flags.append('DUK_HOBJECT_FLAG_SPECIAL_CALL')
flags.append('DUK_HOBJECT_CLASS_AS_FLAGS(%d)' % class_to_number(obj['class'])) # XXX: use constant, not number
refcount = 1 # refcount is faked to be always 1

7
website/guide/coroutines.html

@ -42,9 +42,14 @@ they are present anywhere in the yielding coroutine's call stack:</p>
<li>a getter/setter call</li>
<li>a proxy trap call</li>
<li>an <code>eval()</code> call</li>
<li><code>Function.prototype.call()</code> or <code>Function.prototype.apply()</code></li>
<li>a finalizer call</li>
</ul>
<div class="note">
Since Duktape 2.2 <code>Function.prototype.call()</code>,
<code>Function.prototype.apply()</code>, and <code>Reflect.apply()</code>
no longer prevent a yield.
</div>
<p>See <a href="http://wiki.duktape.org/HowtoCoroutines.html">How to use coroutines</a>
for examples.</p>

Loading…
Cancel
Save