Browse Source

Merge pull request #1601 from svaarala/add-call-proxy-trap

Add 'apply' and 'construct' Proxy traps with a few limitations
pull/1603/head
Sami Vaarala 7 years ago
committed by GitHub
parent
commit
7384fab911
  1. 2
      RELEASES.rst
  2. 2
      doc/debugger.rst
  3. 3
      doc/testcase-known-issues.yaml
  4. 5
      src-input/builtins.yaml
  5. 31
      src-input/duk_api_stack.c
  6. 2
      src-input/duk_debugger.c
  7. 1
      src-input/duk_heap_alloc.c
  8. 37
      src-input/duk_hobject.h
  9. 3
      src-input/duk_hthread.h
  10. 14
      src-input/duk_js.h
  11. 268
      src-input/duk_js_call.c
  12. 8
      src-input/duk_js_executor.c
  13. 2
      src-input/duk_js_var.c
  14. 2
      src-input/duk_strings.h
  15. 8
      src-input/strings.yaml
  16. 4
      tests/api/test-dump-load-basic.c
  17. 14
      tests/api/test-is-calls.c
  18. 66
      tests/ecmascript/test-bi-proxy-apply-yield.js
  19. 165
      tests/ecmascript/test-bi-proxy-apply.js
  20. 48
      tests/ecmascript/test-bi-proxy-construct-invariants.js
  21. 43
      tests/ecmascript/test-bi-proxy-construct-newtarget.js
  22. 46
      tests/ecmascript/test-bi-proxy-construct-prototype-lookup.js
  23. 66
      tests/ecmascript/test-bi-proxy-construct-yield.js
  24. 164
      tests/ecmascript/test-bi-proxy-construct.js
  25. 287
      tests/ecmascript/test-dev-tailcall-constructor-normal-mixing.js
  26. 9
      tests/knownissues/test-bi-proxy-construct-newtarget-1.txt
  27. 42
      tests/perf/test-call-proxy-apply-1.js
  28. 38
      tests/perf/test-call-proxy-pass-1.js
  29. 8
      tools/genbuiltins.py

2
RELEASES.rst

@ -2879,6 +2879,8 @@ Planned
* Add duk_push_proxy() API call which allows a Proxy to be created from C
code (GH-1500, GH-837)
* Add support for Proxy 'apply' and 'construct' traps (GH-1601)
* Add minimal new.target support, evaluates to undefined for non-constructor
calls and final non-bound constructor function in constructor calls;
explicit newTarget not yet supported and handling of new.target in eval()

2
doc/debugger.rst

@ -2368,6 +2368,8 @@ The following list describes artificial keys included in Duktape 1.5.0, see
+---------------------------------+---------------------------+---------------------------------------------------------+
| ``constructable`` | ``duk_hobject`` | DUK_HOBJECT_FLAG_CONSTRUCTABLE |
+---------------------------------+---------------------------+---------------------------------------------------------+
| ``callable`` | ``duk_hobject`` | DUK_HOBJECT_FLAG_CALLABLE |
+---------------------------------+---------------------------+---------------------------------------------------------+
| ``bound`` | ``duk_hobject`` | DUK_HOBJECT_FLAG_BOUND |
+---------------------------------+---------------------------+---------------------------------------------------------+
| ``compfunc`` | ``duk_hobject`` | DUK_HOBJECT_FLAG_COMPFUNC |

3
doc/testcase-known-issues.yaml

@ -123,6 +123,9 @@
-
test: "test-expr-newtarget-eval-code.js"
knownissue: "new.target eval handling limitations"
-
test: "test-bi-proxy-construct-newtarget.js"
knownissue: "for Proxy without 'construct' trap, new.target evaluates to Proxy target rather than Proxy in target constructor"
# Ecmascript testcases that need special options or environment to work

5
src-input/builtins.yaml

@ -18,7 +18,7 @@
# - nargs: nargs (ignored if varargs true); if missing, default from 'length' property
# - magic: see below
# - native: native function name
# - callable: true
# - callable: true/false, depending on function
# - constructable: true/false, depending on function
# - special_call: true/false, used for .call(), .apply(), etc which
# need special casing in runtime call handling
@ -103,6 +103,7 @@
# - Native function shorthand; alternative to defining a function as a
# native object and referencing it using an ID:
# - type: function (implicitly callable)
# - callable: default is true
# - constructable: default is false
# - native: native function name
# - length: function .length (optional, defaults to 0)
@ -113,6 +114,8 @@
# - special_call: recognized in shorthand
# - Accessor (setter/getter) shorthand:
# - type: accessor
# - callable: default is true
# - constructable: default is false
# - getter: native function name
# - setter: native function name
# - getter_magic: magic value for getter

31
src-input/duk_api_stack.c

@ -3376,6 +3376,7 @@ DUK_LOCAL void duk__push_func_from_lightfunc(duk_context *ctx, duk_c_function fu
flags = DUK_HOBJECT_FLAG_EXTENSIBLE |
DUK_HOBJECT_FLAG_CONSTRUCTABLE |
DUK_HOBJECT_FLAG_CALLABLE |
DUK_HOBJECT_FLAG_FASTREFS |
DUK_HOBJECT_FLAG_NATFUNC |
DUK_HOBJECT_FLAG_NEWENV |
@ -3875,9 +3876,7 @@ DUK_EXTERNAL duk_bool_t duk_is_function(duk_context *ctx, duk_idx_t idx) {
}
return duk__obj_flag_any_default_false(ctx,
idx,
DUK_HOBJECT_FLAG_COMPFUNC |
DUK_HOBJECT_FLAG_NATFUNC |
DUK_HOBJECT_FLAG_BOUNDFUNC);
DUK_HOBJECT_FLAG_CALLABLE);
}
DUK_EXTERNAL duk_bool_t duk_is_constructable(duk_context *ctx, duk_idx_t idx) {
@ -4704,6 +4703,7 @@ DUK_INTERNAL duk_hcompfunc *duk_push_hcompfunc(duk_context *ctx) {
obj = duk_hcompfunc_alloc(thr,
DUK_HOBJECT_FLAG_EXTENSIBLE |
DUK_HOBJECT_FLAG_CALLABLE |
DUK_HOBJECT_FLAG_COMPFUNC |
DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_FUNCTION));
if (DUK_UNLIKELY(obj == NULL)) {
@ -4734,6 +4734,7 @@ DUK_INTERNAL duk_hboundfunc *duk_push_hboundfunc(duk_context *ctx) {
DUK_HOBJECT_FLAG_EXTENSIBLE |
DUK_HOBJECT_FLAG_BOUNDFUNC |
DUK_HOBJECT_FLAG_CONSTRUCTABLE |
DUK_HOBJECT_FLAG_CALLABLE |
DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_FUNCTION));
if (!obj) {
DUK_ERROR_ALLOC_FAILED(thr);
@ -4804,6 +4805,7 @@ DUK_EXTERNAL duk_idx_t duk_push_c_function(duk_context *ctx, duk_c_function func
flags = DUK_HOBJECT_FLAG_EXTENSIBLE |
DUK_HOBJECT_FLAG_CONSTRUCTABLE |
DUK_HOBJECT_FLAG_CALLABLE |
DUK_HOBJECT_FLAG_FASTREFS |
DUK_HOBJECT_FLAG_NATFUNC |
DUK_HOBJECT_FLAG_NEWENV |
@ -4824,6 +4826,7 @@ DUK_INTERNAL void duk_push_c_function_builtin(duk_context *ctx, duk_c_function f
flags = DUK_HOBJECT_FLAG_EXTENSIBLE |
DUK_HOBJECT_FLAG_CONSTRUCTABLE |
DUK_HOBJECT_FLAG_CALLABLE |
DUK_HOBJECT_FLAG_FASTREFS |
DUK_HOBJECT_FLAG_NATFUNC |
DUK_HOBJECT_FLAG_NEWENV |
@ -4841,6 +4844,7 @@ DUK_INTERNAL void duk_push_c_function_builtin_noconstruct(duk_context *ctx, duk_
DUK_ASSERT_CTX_VALID(ctx);
flags = DUK_HOBJECT_FLAG_EXTENSIBLE |
DUK_HOBJECT_FLAG_CALLABLE |
DUK_HOBJECT_FLAG_FASTREFS |
DUK_HOBJECT_FLAG_NATFUNC |
DUK_HOBJECT_FLAG_NEWENV |
@ -5197,13 +5201,22 @@ DUK_EXTERNAL duk_idx_t duk_push_proxy(duk_context *ctx) {
}
/* XXX: Proxy object currently has no prototype, so ToPrimitive()
* coercion fails which is a bit confusing. No callable check/handling
* in the current Proxy subset.
* coercion fails which is a bit confusing.
*/
flags = DUK_HOBJECT_FLAG_EXTENSIBLE |
DUK_HOBJECT_FLAG_EXOTIC_PROXYOBJ |
DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_OBJECT);
/* CALLABLE and CONSTRUCTABLE flags are copied from the (initial)
* target, see ES2015 Sections 9.5.15 and 9.5.13.
*/
flags = DUK_HEAPHDR_GET_FLAGS((duk_heaphdr *) h_target) &
(DUK_HOBJECT_FLAG_CALLABLE | DUK_HOBJECT_FLAG_CONSTRUCTABLE);
flags |= DUK_HOBJECT_FLAG_EXTENSIBLE |
DUK_HOBJECT_FLAG_EXOTIC_PROXYOBJ;
if (flags & DUK_HOBJECT_FLAG_CALLABLE) {
flags |= DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_FUNCTION) |
DUK_HOBJECT_FLAG_SPECIAL_CALL;
} else {
flags |= DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_OBJECT);
}
h_proxy = duk_hproxy_alloc(thr, flags);
DUK_ASSERT(h_proxy != NULL);
@ -5827,6 +5840,7 @@ DUK_INTERNAL duk_idx_t duk_unpack_array_like(duk_context *ctx, duk_idx_t idx) {
duk_tval *tv;
thr = (duk_hthread *) ctx;
DUK_UNREF(thr);
tv = duk_require_tval(ctx, idx);
if (DUK_LIKELY(DUK_TVAL_IS_OBJECT(tv))) {
@ -5836,6 +5850,7 @@ DUK_INTERNAL duk_idx_t duk_unpack_array_like(duk_context *ctx, duk_idx_t idx) {
h = DUK_TVAL_GET_OBJECT(tv);
DUK_ASSERT(h != NULL);
DUK_UNREF(h);
#if defined(DUK_USE_ARRAY_FASTPATH) /* close enough */
if (DUK_LIKELY(DUK_HOBJECT_IS_ARRAY(h) &&

2
src-input/duk_debugger.c

@ -1901,6 +1901,7 @@ DUK_LOCAL duk_uint_t duk__debug_getinfo_hstring_masks[] = {
DUK_LOCAL const char * const duk__debug_getinfo_hobject_keys[] = {
"extensible",
"constructable",
"callable",
"boundfunc",
"compfunc",
"natfunc",
@ -1923,6 +1924,7 @@ DUK_LOCAL const char * const duk__debug_getinfo_hobject_keys[] = {
DUK_LOCAL duk_uint_t duk__debug_getinfo_hobject_masks[] = {
DUK_HOBJECT_FLAG_EXTENSIBLE,
DUK_HOBJECT_FLAG_CONSTRUCTABLE,
DUK_HOBJECT_FLAG_CALLABLE,
DUK_HOBJECT_FLAG_BOUNDFUNC,
DUK_HOBJECT_FLAG_COMPFUNC,
DUK_HOBJECT_FLAG_NATFUNC,

1
src-input/duk_heap_alloc.c

@ -180,6 +180,7 @@ DUK_INTERNAL void duk_heap_free_freelists(duk_heap *heap) {
#if defined(DUK_USE_CACHE_CATCHER)
count_cat = duk__heap_free_catcher_freelist(heap);
#endif
DUK_UNREF(heap);
DUK_UNREF(count_act);
DUK_UNREF(count_cat);

37
src-input/duk_hobject.h

@ -41,7 +41,8 @@
*/
#define DUK_HOBJECT_FLAG_EXTENSIBLE DUK_HEAPHDR_USER_FLAG(0) /* object is extensible */
#define DUK_HOBJECT_FLAG_CONSTRUCTABLE DUK_HEAPHDR_USER_FLAG(1) /* object is constructable */
#define DUK_HOBJECT_FLAG_BOUNDFUNC DUK_HEAPHDR_USER_FLAG(2) /* object established using Function.prototype.bind() */
#define DUK_HOBJECT_FLAG_CALLABLE DUK_HEAPHDR_USER_FLAG(2) /* object is callable */
#define DUK_HOBJECT_FLAG_BOUNDFUNC DUK_HEAPHDR_USER_FLAG(3) /* object established using Function.prototype.bind() */
#define DUK_HOBJECT_FLAG_COMPFUNC DUK_HEAPHDR_USER_FLAG(4) /* object is a compiled function (duk_hcompfunc) */
#define DUK_HOBJECT_FLAG_NATFUNC DUK_HEAPHDR_USER_FLAG(5) /* object is a native function (duk_hnatfunc) */
#define DUK_HOBJECT_FLAG_BUFOBJ DUK_HEAPHDR_USER_FLAG(6) /* object is a buffer object (duk_hbufobj) (always exotic) */
@ -52,7 +53,7 @@
#define DUK_HOBJECT_FLAG_NEWENV DUK_HEAPHDR_USER_FLAG(11) /* function: create new environment when called (see duk_hcompfunc) */
#define DUK_HOBJECT_FLAG_NAMEBINDING DUK_HEAPHDR_USER_FLAG(12) /* function: create binding for func name (function templates only, used for named function expressions) */
#define DUK_HOBJECT_FLAG_CREATEARGS DUK_HEAPHDR_USER_FLAG(13) /* function: create an arguments object on function call */
#define DUK_HOBJECT_FLAG_HAVE_FINALIZER DUK_HEAPHDR_USER_FLAG(14) /* object has a callable finalizer property */
#define DUK_HOBJECT_FLAG_HAVE_FINALIZER DUK_HEAPHDR_USER_FLAG(14) /* object has a callable (own) finalizer property */
#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) */
@ -164,9 +165,17 @@
#define DUK_HOBJECT_IS_BOUNDFUNC(h) DUK_HEAPHDR_CHECK_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_BOUNDFUNC)
#define DUK_HOBJECT_IS_COMPFUNC(h) DUK_HEAPHDR_CHECK_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_COMPFUNC)
#define DUK_HOBJECT_IS_NATFUNC(h) DUK_HEAPHDR_CHECK_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_NATFUNC)
#if defined(DUK_USE_BUFFEROBJECT_SUPPORT)
#define DUK_HOBJECT_IS_BUFOBJ(h) DUK_HEAPHDR_CHECK_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_BUFOBJ)
#else
#define DUK_HOBJECT_IS_BUFOBJ(h) 0
#endif
#define DUK_HOBJECT_IS_THREAD(h) (DUK_HOBJECT_GET_CLASS_NUMBER((h)) == DUK_HOBJECT_CLASS_THREAD)
#if defined(DUK_USE_ES6_PROXY)
#define DUK_HOBJECT_IS_PROXY(h) DUK_HOBJECT_HAS_EXOTIC_PROXYOBJ((h))
#else
#define DUK_HOBJECT_IS_PROXY(h) 0
#endif
#define DUK_HOBJECT_IS_NONBOUND_FUNCTION(h) DUK_HEAPHDR_CHECK_FLAG_BITS(&(h)->hdr, \
DUK_HOBJECT_FLAG_COMPFUNC | \
@ -177,10 +186,7 @@
DUK_HOBJECT_FLAG_COMPFUNC | \
DUK_HOBJECT_FLAG_NATFUNC)
#define DUK_HOBJECT_IS_CALLABLE(h) DUK_HEAPHDR_CHECK_FLAG_BITS(&(h)->hdr, \
DUK_HOBJECT_FLAG_BOUNDFUNC | \
DUK_HOBJECT_FLAG_COMPFUNC | \
DUK_HOBJECT_FLAG_NATFUNC)
#define DUK_HOBJECT_IS_CALLABLE(h) DUK_HOBJECT_HAS_CALLABLE((h))
/* Object has any exotic behavior(s). */
#define DUK_HOBJECT_EXOTIC_BEHAVIOR_FLAGS (DUK_HOBJECT_FLAG_EXOTIC_ARRAY | \
@ -198,10 +204,15 @@
#define DUK_HOBJECT_HAS_EXTENSIBLE(h) DUK_HEAPHDR_CHECK_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_EXTENSIBLE)
#define DUK_HOBJECT_HAS_CONSTRUCTABLE(h) DUK_HEAPHDR_CHECK_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_CONSTRUCTABLE)
#define DUK_HOBJECT_HAS_CALLABLE(h) DUK_HEAPHDR_CHECK_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_CALLABLE)
#define DUK_HOBJECT_HAS_BOUNDFUNC(h) DUK_HEAPHDR_CHECK_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_BOUNDFUNC)
#define DUK_HOBJECT_HAS_COMPFUNC(h) DUK_HEAPHDR_CHECK_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_COMPFUNC)
#define DUK_HOBJECT_HAS_NATFUNC(h) DUK_HEAPHDR_CHECK_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_NATFUNC)
#if defined(DUK_USE_BUFFEROBJECT_SUPPORT)
#define DUK_HOBJECT_HAS_BUFOBJ(h) DUK_HEAPHDR_CHECK_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_BUFOBJ)
#else
#define DUK_HOBJECT_HAS_BUFOBJ(h) 0
#endif
#define DUK_HOBJECT_HAS_FASTREFS(h) DUK_HEAPHDR_CHECK_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_FASTREFS)
#define DUK_HOBJECT_HAS_ARRAY_PART(h) DUK_HEAPHDR_CHECK_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_ARRAY_PART)
#define DUK_HOBJECT_HAS_STRICT(h) DUK_HEAPHDR_CHECK_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_STRICT)
@ -213,15 +224,22 @@
#define DUK_HOBJECT_HAS_EXOTIC_ARRAY(h) DUK_HEAPHDR_CHECK_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_EXOTIC_ARRAY)
#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)
#if defined(DUK_USE_ES6_PROXY)
#define DUK_HOBJECT_HAS_EXOTIC_PROXYOBJ(h) DUK_HEAPHDR_CHECK_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_EXOTIC_PROXYOBJ)
#else
#define DUK_HOBJECT_HAS_EXOTIC_PROXYOBJ(h) 0
#endif
#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)
#define DUK_HOBJECT_SET_CALLABLE(h) DUK_HEAPHDR_SET_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_CALLABLE)
#define DUK_HOBJECT_SET_BOUNDFUNC(h) DUK_HEAPHDR_SET_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_BOUNDFUNC)
#define DUK_HOBJECT_SET_COMPFUNC(h) DUK_HEAPHDR_SET_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_COMPFUNC)
#define DUK_HOBJECT_SET_NATFUNC(h) DUK_HEAPHDR_SET_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_NATFUNC)
#if defined(DUK_USE_BUFFEROBJECT_SUPPORT)
#define DUK_HOBJECT_SET_BUFOBJ(h) DUK_HEAPHDR_SET_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_BUFOBJ)
#endif
#define DUK_HOBJECT_SET_FASTREFS(h) DUK_HEAPHDR_SET_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_FASTREFS)
#define DUK_HOBJECT_SET_ARRAY_PART(h) DUK_HEAPHDR_SET_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_ARRAY_PART)
#define DUK_HOBJECT_SET_STRICT(h) DUK_HEAPHDR_SET_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_STRICT)
@ -233,15 +251,20 @@
#define DUK_HOBJECT_SET_EXOTIC_ARRAY(h) DUK_HEAPHDR_SET_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_EXOTIC_ARRAY)
#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)
#if defined(DUK_USE_ES6_PROXY)
#define DUK_HOBJECT_SET_EXOTIC_PROXYOBJ(h) DUK_HEAPHDR_SET_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_EXOTIC_PROXYOBJ)
#endif
#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)
#define DUK_HOBJECT_CLEAR_CALLABLE(h) DUK_HEAPHDR_CLEAR_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_CALLABLE)
#define DUK_HOBJECT_CLEAR_BOUNDFUNC(h) DUK_HEAPHDR_CLEAR_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_BOUNDFUNC)
#define DUK_HOBJECT_CLEAR_COMPFUNC(h) DUK_HEAPHDR_CLEAR_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_COMPFUNC)
#define DUK_HOBJECT_CLEAR_NATFUNC(h) DUK_HEAPHDR_CLEAR_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_NATFUNC)
#if defined(DUK_USE_BUFFEROBJECT_SUPPORT)
#define DUK_HOBJECT_CLEAR_BUFOBJ(h) DUK_HEAPHDR_CLEAR_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_BUFOBJ)
#endif
#define DUK_HOBJECT_CLEAR_FASTREFS(h) DUK_HEAPHDR_CLEAR_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_FASTREFS)
#define DUK_HOBJECT_CLEAR_ARRAY_PART(h) DUK_HEAPHDR_CLEAR_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_ARRAY_PART)
#define DUK_HOBJECT_CLEAR_STRICT(h) DUK_HEAPHDR_CLEAR_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_STRICT)
@ -253,7 +276,9 @@
#define DUK_HOBJECT_CLEAR_EXOTIC_ARRAY(h) DUK_HEAPHDR_CLEAR_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_EXOTIC_ARRAY)
#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)
#if defined(DUK_USE_ES6_PROXY)
#define DUK_HOBJECT_CLEAR_EXOTIC_PROXYOBJ(h) DUK_HEAPHDR_CLEAR_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_EXOTIC_PROXYOBJ)
#endif
#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

3
src-input/duk_hthread.h

@ -35,7 +35,8 @@
#define DUK_ACT_FLAG_CONSTRUCT (1 << 2) /* function executes as a constructor (called via "new") */
#define DUK_ACT_FLAG_PREVENT_YIELD (1 << 3) /* activation prevents yield (native call or "new") */
#define DUK_ACT_FLAG_DIRECT_EVAL (1 << 4) /* activation is a direct eval call */
#define DUK_ACT_FLAG_BREAKPOINT_ACTIVE (1 << 5) /* activation has active breakpoint(s) */
#define DUK_ACT_FLAG_CONSTRUCT_PROXY (1 << 5) /* activation is for Proxy 'construct' call, special return value handling */
#define DUK_ACT_FLAG_BREAKPOINT_ACTIVE (1 << 6) /* activation has active breakpoint(s) */
#define DUK_ACT_GET_FUNC(act) ((act)->func)

14
src-input/duk_js.h

@ -6,11 +6,13 @@
#define DUK_JS_H_INCLUDED
/* Flags for call handling. Lowest flags must match bytecode DUK_BC_CALL_FLAG_xxx 1:1. */
#define DUK_CALL_FLAG_TAILCALL (1 << 0) /* setup for a tail call */
#define DUK_CALL_FLAG_CONSTRUCT (1 << 1) /* constructor call (i.e. called as 'new Foo()') */
#define DUK_CALL_FLAG_CALLED_AS_EVAL (1 << 2) /* call was made using the identifier 'eval' */
#define DUK_CALL_FLAG_ALLOW_ECMATOECMA (1 << 3) /* ecma-to-ecma call with executor reuse is possible */
#define DUK_CALL_FLAG_DIRECT_EVAL (1 << 4) /* call is a direct eval call */
#define DUK_CALL_FLAG_TAILCALL (1 << 0) /* setup for a tail call */
#define DUK_CALL_FLAG_CONSTRUCT (1 << 1) /* constructor call (i.e. called as 'new Foo()') */
#define DUK_CALL_FLAG_CALLED_AS_EVAL (1 << 2) /* call was made using the identifier 'eval' */
#define DUK_CALL_FLAG_ALLOW_ECMATOECMA (1 << 3) /* ecma-to-ecma call with executor reuse is possible */
#define DUK_CALL_FLAG_DIRECT_EVAL (1 << 4) /* call is a direct eval call */
#define DUK_CALL_FLAG_CONSTRUCT_PROXY (1 << 5) /* handled via 'construct' proxy trap, check return value invariant(s) */
#define DUK_CALL_FLAG_DEFAULT_INSTANCE_UPDATED (1 << 6) /* prototype of 'default instance' updated, temporary flag in call handling */
/* Flags for duk_js_equals_helper(). */
#define DUK_EQUALS_FLAG_SAMEVALUE (1 << 0) /* use SameValue instead of non-strict equality */
@ -97,7 +99,7 @@ DUK_INTERNAL_DECL void duk_js_push_closure(duk_hthread *thr,
DUK_INTERNAL_DECL duk_int_t duk_handle_call_unprotected(duk_hthread *thr, duk_idx_t idx_func, duk_small_uint_t call_flags);
DUK_INTERNAL_DECL duk_int_t duk_handle_call_unprotected_nargs(duk_hthread *thr, duk_idx_t nargs, duk_small_uint_t call_flags);
DUK_INTERNAL_DECL duk_int_t duk_handle_safe_call(duk_hthread *thr, duk_safe_call_function func, void *udata, duk_idx_t num_stack_args, duk_idx_t num_stack_res);
DUK_INTERNAL_DECL void duk_call_construct_postprocess(duk_context *ctx);
DUK_INTERNAL_DECL void duk_call_construct_postprocess(duk_context *ctx, duk_small_uint_t proxy_invariant);
/* bytecode execution */
DUK_INTERNAL_DECL void duk_js_execute_bytecode(duk_hthread *exec_thr);

268
src-input/duk_js_call.c

@ -491,17 +491,27 @@ DUK_LOCAL void duk__update_default_instance_proto(duk_context *ctx, duk_idx_t id
}
/* Postprocess: return value special handling, error augmentation. */
DUK_INTERNAL void duk_call_construct_postprocess(duk_context *ctx) {
DUK_INTERNAL void duk_call_construct_postprocess(duk_context *ctx, duk_small_uint_t proxy_invariant) {
duk_hthread *thr = (duk_hthread *) ctx;
/* Use either fallback (default instance) or retval depending
* on retval type. Needs to be called before unwind because
* the default instance is read from the current (immutable)
* 'this' binding.
*
* For Proxy 'construct' calls the return value must be an
* Object (we accept object-like values like buffers and
* lightfuncs too). If not, TypeError.
*/
if (!duk_check_type_mask(ctx, -1, DUK_TYPE_MASK_OBJECT |
DUK_TYPE_MASK_BUFFER |
DUK_TYPE_MASK_LIGHTFUNC)) {
if (duk_check_type_mask(ctx, -1, DUK_TYPE_MASK_OBJECT |
DUK_TYPE_MASK_BUFFER |
DUK_TYPE_MASK_LIGHTFUNC)) {
DUK_DDD(DUK_DDDPRINT("replacement value"));
} else {
if (DUK_UNLIKELY(proxy_invariant)) {
/* Proxy 'construct' return value invariant violated. */
DUK_ERROR_TYPE_INVALID_TRAP_RESULT(thr);
}
/* XXX: direct value stack access */
duk_pop(ctx);
duk_push_this(ctx);
@ -829,6 +839,118 @@ DUK_LOCAL duk_bool_t duk__handle_specialfuncs_for_call(duk_hthread *thr, duk_idx
return 0; /* keep resolving */
}
/*
* Helper for Proxy handling.
*/
#if defined(DUK_USE_ES6_PROXY)
DUK_LOCAL void duk__handle_proxy_for_call(duk_hthread *thr, duk_idx_t idx_func, duk_hproxy *h_proxy, duk_small_uint_t *call_flags) {
duk_context *ctx = (duk_context *) thr;
duk_bool_t rc;
/* Value stack:
* idx_func + 0: Proxy object
* idx_func + 1: this binding for call
* idx_func + 2: 1st argument for call
* idx_func + 3: 2nd argument for call
* ...
*
* If Proxy doesn't have a trap for the call ('apply' or 'construct'),
* replace Proxy object with target object.
*
* If we're dealing with a normal call and the Proxy has an 'apply'
* trap, manipulate value stack to:
*
* idx_func + 0: trap
* idx_func + 1: Proxy's handler
* idx_func + 2: Proxy's target
* idx_func + 3: this binding for call (from idx_func + 1)
* idx_func + 4: call arguments packed to an array
*
* If we're dealing with a constructor call and the Proxy has a
* 'construct' trap, manipulate value stack to:
*
* idx_func + 0: trap
* idx_func + 1: Proxy's handler
* idx_func + 2: Proxy's target
* idx_func + 3: call arguments packed to an array
* idx_func + 4: newTarget == Proxy object here
*
* As we don't yet have proper newTarget support, the newTarget at
* idx_func + 3 is just the original constructor being called, i.e.
* the Proxy object (not the target). Note that the default instance
* (original 'this' binding) is dropped and ignored.
*/
duk_push_hobject(ctx, h_proxy->handler);
rc = duk_get_prop_stridx_short(ctx, -1, (*call_flags & DUK_CALL_FLAG_CONSTRUCT) ? DUK_STRIDX_CONSTRUCT : DUK_STRIDX_APPLY);
if (rc == 0) {
/* Not found, continue to target. If this is a construct
* call, update default instance prototype using the Proxy,
* not the target.
*/
if (*call_flags & DUK_CALL_FLAG_CONSTRUCT) {
if (!(*call_flags & DUK_CALL_FLAG_DEFAULT_INSTANCE_UPDATED)) {
*call_flags |= DUK_CALL_FLAG_DEFAULT_INSTANCE_UPDATED;
duk__update_default_instance_proto(ctx, idx_func);
}
}
duk_pop_2(ctx);
duk_push_hobject(ctx, h_proxy->target);
duk_replace(ctx, idx_func);
return;
}
/* Here we must be careful not to replace idx_func while
* h_proxy is still needed, otherwise h_proxy may become
* dangling. This could be improved e.g. using a
* duk_pack_slice() with a freeform slice.
*/
/* Here:
* idx_func + 0: Proxy object
* idx_func + 1: this binding for call
* idx_func + 2: 1st argument for call
* idx_func + 3: 2nd argument for call
* ...
* idx_func + N: handler
* idx_func + N + 1: trap
*/
duk_insert(ctx, idx_func + 1);
duk_insert(ctx, idx_func + 2);
duk_push_hobject(ctx, h_proxy->target);
duk_insert(ctx, idx_func + 3);
duk_pack(ctx, duk_get_top(ctx) - (idx_func + 5));
/* Here:
* idx_func + 0: Proxy object
* idx_func + 1: trap
* idx_func + 2: Proxy's handler
* idx_func + 3: Proxy's target
* idx_func + 4: this binding for call
* idx_func + 5: arguments array
*/
DUK_ASSERT(duk_get_top(ctx) == idx_func + 6);
if (*call_flags & DUK_CALL_FLAG_CONSTRUCT) {
*call_flags |= DUK_CALL_FLAG_CONSTRUCT_PROXY; /* Enable 'construct' trap return invariant check. */
*call_flags &= ~(DUK_CALL_FLAG_CONSTRUCT); /* Resume as non-constructor call to the trap. */
/* 'apply' args: target, thisArg, argArray
* 'construct' args: target, argArray, newTarget
*/
duk_remove(ctx, idx_func + 4);
duk_push_hobject(ctx, (duk_hobject *) h_proxy);
}
/* Finalize value stack layout by removing Proxy reference. */
duk_remove(ctx, idx_func);
h_proxy = NULL; /* invalidated */
DUK_ASSERT(duk_get_top(ctx) == idx_func + 5);
}
#endif /* DUK_USE_ES6_PROXY */
/*
* Helper for setting up var_env and lex_env of an activation,
* assuming it does NOT have the DUK_HOBJECT_FLAG_NEWENV flag.
@ -952,6 +1074,10 @@ DUK_LOCAL void duk__update_func_caller_prop(duk_hthread *thr, duk_hobject *func)
* call and the effective 'this' binding. Resolves bound functions and
* applies .call(), .apply(), and .construct() inline.
*
* Proxy traps are also handled inline so that if the target is a Proxy with
* a 'call' or 'construct' trap, the trap handler is called with a modified
* argument list.
*
* Once the bound function / .call() / .apply() / .construct() sequence has
* been resolved, the value at idx_func + 1 may need coercion described in
* E5 Section 10.4.3.
@ -1018,18 +1144,19 @@ DUK_LOCAL DUK_ALWAYS_INLINE duk_bool_t duk__resolve_target_fastpath_check(duk_co
DUK_UNREF(ctx);
DUK_UNREF(idx_func);
DUK_UNREF(out_func);
DUK_UNREF(call_flags);
#else /* DUK_USE_PREFER_SIZE */
duk_tval *tv_func;
duk_hobject *func;
if (call_flags & DUK_CALL_FLAG_CONSTRUCT) {
if (DUK_UNLIKELY(call_flags & DUK_CALL_FLAG_CONSTRUCT)) {
return 0;
}
tv_func = DUK_GET_TVAL_POSIDX(ctx, idx_func);
DUK_ASSERT(tv_func != NULL);
if (DUK_TVAL_IS_OBJECT(tv_func)) {
if (DUK_LIKELY(DUK_TVAL_IS_OBJECT(tv_func))) {
func = DUK_TVAL_GET_OBJECT(tv_func);
if (DUK_HOBJECT_IS_CALLABLE(func) &&
!DUK_HOBJECT_HAS_BOUNDFUNC(func) &&
@ -1075,10 +1202,20 @@ DUK_LOCAL duk_hobject *duk__resolve_target_func_and_this_binding(duk_context *ct
if (DUK_TVAL_IS_OBJECT(tv_func)) {
func = DUK_TVAL_GET_OBJECT(tv_func);
if (DUK_UNLIKELY(!DUK_HOBJECT_IS_CALLABLE(func))) {
goto not_callable;
if (*call_flags & DUK_CALL_FLAG_CONSTRUCT) {
if (DUK_UNLIKELY(!DUK_HOBJECT_HAS_CONSTRUCTABLE(func))) {
goto not_constructable;
}
} else {
if (DUK_UNLIKELY(!DUK_HOBJECT_IS_CALLABLE(func))) {
goto not_callable;
}
}
if (DUK_LIKELY(!DUK_HOBJECT_HAS_BOUNDFUNC(func) && !DUK_HOBJECT_HAS_SPECIAL_CALL(func))) {
if (DUK_LIKELY(!DUK_HOBJECT_HAS_BOUNDFUNC(func) &&
!DUK_HOBJECT_HAS_SPECIAL_CALL(func) &&
!DUK_HOBJECT_HAS_EXOTIC_PROXYOBJ(func))) {
/* Common case, so test for using a single bitfield test.
* Break out to handle this coercion etc.
*/
@ -1091,34 +1228,52 @@ DUK_LOCAL duk_hobject *duk__resolve_target_func_and_this_binding(duk_context *ct
DUK_ASSERT(!DUK_HOBJECT_HAS_SPECIAL_CALL(func));
DUK_ASSERT(!DUK_HOBJECT_IS_NATFUNC(func));
/* Callable/constructable flags are the same
* for the bound function and its target, so
* we don't need to check them here, we can
* check them from the target only.
*/
duk__handle_bound_chain_for_call(thr, idx_func, *call_flags & DUK_CALL_FLAG_CONSTRUCT);
DUK_ASSERT(DUK_TVAL_IS_OBJECT(duk_require_tval(ctx, idx_func)) ||
DUK_TVAL_IS_LIGHTFUNC(duk_require_tval(ctx, idx_func)));
} else {
DUK_ASSERT(DUK_HOBJECT_HAS_SPECIAL_CALL(func));
DUK_ASSERT(DUK_HOBJECT_IS_NATFUNC(func));
DUK_ASSERT(!DUK_HOBJECT_HAS_CONSTRUCTABLE(func));
if (*call_flags & DUK_CALL_FLAG_CONSTRUCT) {
/* None of the special calls (Function.prototype.apply(), eval,
* etc) can be called as a constructor.
#if defined(DUK_USE_ES6_PROXY)
if (DUK_HOBJECT_HAS_EXOTIC_PROXYOBJ(func)) {
/* If no trap, resume processing from Proxy trap.
* If trap exists, helper converts call into a trap
* call; this may change a constructor call into a
* normal (non-constructor) trap call. We must
* continue processing even when a trap is found as
* the trap may be bound.
*/
goto not_constructable;
duk__handle_proxy_for_call(thr, idx_func, (duk_hproxy *) func, call_flags);
}
if (duk__handle_specialfuncs_for_call(thr, idx_func, func, call_flags, first) != 0) {
/* Encountered native eval call, break out to handle
* this coercion etc.
*/
break;
else
#endif
{
DUK_ASSERT(DUK_HOBJECT_IS_NATFUNC(func));
DUK_ASSERT(DUK_HOBJECT_HAS_CALLABLE(func));
DUK_ASSERT(!DUK_HOBJECT_HAS_CONSTRUCTABLE(func));
/* Constructable check already done above. */
if (duk__handle_specialfuncs_for_call(thr, idx_func, func, call_flags, first) != 0) {
/* Encountered native eval call, normal call
* context. Break out, handle this coercion etc.
*/
break;
}
}
}
/* Retry loop. */
} else if (DUK_TVAL_IS_LIGHTFUNC(tv_func)) {
/* Lightfuncs are strict, so no 'this' coercion.
* Lightfuncs are also always constructable, so no
* constructability check. Finally, specialfuncs
* cannot currently be lightfuncs.
/* Lightfuncs are:
* - Always strict, so no 'this' coercion.
* - Always callable.
* - Always constructable.
* - Never specialfuncs.
*/
func = NULL;
goto finished;
@ -1136,15 +1291,11 @@ DUK_LOCAL duk_hobject *duk__resolve_target_func_and_this_binding(duk_context *ct
*/
duk__coerce_nonstrict_this_binding(ctx, idx_func + 1);
}
if (*call_flags & DUK_CALL_FLAG_CONSTRUCT) {
/* Check for constructability and update
* default instance prototype.
*/
if (!DUK_HOBJECT_HAS_CONSTRUCTABLE(func)) {
goto not_constructable;
if (!(*call_flags & DUK_CALL_FLAG_DEFAULT_INSTANCE_UPDATED)) {
*call_flags |= DUK_CALL_FLAG_DEFAULT_INSTANCE_UPDATED;
duk__update_default_instance_proto(ctx, idx_func);
}
duk__update_default_instance_proto(ctx, idx_func);
}
finished:
@ -1283,6 +1434,7 @@ DUK_LOCAL duk_small_uint_t duk__call_setup_act_attempt_tailcall(duk_hthread *thr
duk_activation *act;
duk_tval *tv1, *tv2;
duk_idx_t idx_args;
duk_small_uint_t flags1, flags2;
DUK_UNREF(entry_valstack_end_byteoff);
@ -1298,19 +1450,37 @@ DUK_LOCAL duk_small_uint_t duk__call_setup_act_attempt_tailcall(duk_hthread *thr
if (func == NULL || !DUK_HOBJECT_IS_COMPFUNC(func)) {
DUK_DDD(DUK_DDDPRINT("tail call prevented by target not being ecma function"));
return 0;
} else if (act->flags & DUK_ACT_FLAG_PREVENT_YIELD) {
}
if (act->flags & DUK_ACT_FLAG_PREVENT_YIELD) {
DUK_DDD(DUK_DDDPRINT("tail call prevented by current activation having DUK_ACT_FLAG_PREVENT_YIELD"));
return 0;
} else if (((act->flags & DUK_ACT_FLAG_CONSTRUCT) && !(call_flags & DUK_CALL_FLAG_CONSTRUCT)) ||
(!(act->flags & DUK_ACT_FLAG_CONSTRUCT) && (call_flags & DUK_CALL_FLAG_CONSTRUCT))) {
/* Cannot tailcall if mixing normal and constructor
* calls. Current function and potential tailcall
* must have same return value handling (normal or
* constructor special handling).
*/
}
/* Tailcall is only allowed if current and candidate
* function have identical return value handling. There
* are three possible return value handling cases:
* 1. Normal function call, no special return value handling.
* 2. Constructor call, return value replacement object check.
* 3. Proxy 'construct' trap call, return value invariant check.
*/
flags1 = ((act->flags & DUK_ACT_FLAG_CONSTRUCT) ? 1 : 0)
#if defined(DUK_USE_ES6_PROXY)
| ((act->flags & DUK_ACT_FLAG_CONSTRUCT_PROXY) ? 2 : 0)
#endif
;
flags2 = ((call_flags & DUK_CALL_FLAG_CONSTRUCT) ? 1 : 0)
#if defined(DUK_USE_ES6_PROXY)
| ((call_flags & DUK_CALL_FLAG_CONSTRUCT_PROXY) ? 2 : 0);
#endif
;
if (flags1 != flags2) {
DUK_DDD(DUK_DDDPRINT("tail call prevented by incompatible return value handling"));
return 0;
} else if (DUK_HOBJECT_HAS_NOTAIL(func)) {
}
DUK_ASSERT(((act->flags & DUK_ACT_FLAG_CONSTRUCT) && (call_flags & DUK_CALL_FLAG_CONSTRUCT)) ||
(!(act->flags & DUK_ACT_FLAG_CONSTRUCT) && !(call_flags & DUK_CALL_FLAG_CONSTRUCT)));
DUK_ASSERT(((act->flags & DUK_ACT_FLAG_CONSTRUCT_PROXY) && (call_flags & DUK_CALL_FLAG_CONSTRUCT_PROXY)) ||
(!(act->flags & DUK_ACT_FLAG_CONSTRUCT_PROXY) && !(call_flags & DUK_CALL_FLAG_CONSTRUCT_PROXY)));
if (DUK_HOBJECT_HAS_NOTAIL(func)) {
/* See: test-bug-tailcall-preventyield-assert.c. */
DUK_DDD(DUK_DDDPRINT("tail call prevented by function having a notail flag"));
return 0;
@ -1375,6 +1545,11 @@ DUK_LOCAL duk_small_uint_t duk__call_setup_act_attempt_tailcall(duk_hthread *thr
if (call_flags & DUK_CALL_FLAG_CONSTRUCT) {
act->flags |= DUK_ACT_FLAG_CONSTRUCT;
}
#if defined(DUK_USE_ES6_PROXY)
if (call_flags & DUK_CALL_FLAG_CONSTRUCT_PROXY) {
act->flags |= DUK_ACT_FLAG_CONSTRUCT_PROXY;
}
#endif
DUK_ASSERT(DUK_ACT_GET_FUNC(act) == func); /* already updated */
DUK_ASSERT(act->var_env == NULL);
@ -1490,6 +1665,11 @@ DUK_LOCAL void duk__call_setup_act_not_tailcall(duk_hthread *thr,
if (call_flags & DUK_CALL_FLAG_CONSTRUCT) {
act->flags |= DUK_ACT_FLAG_CONSTRUCT;
}
#if defined(DUK_USE_ES6_PROXY)
if (call_flags & DUK_CALL_FLAG_CONSTRUCT_PROXY) {
act->flags |= DUK_ACT_FLAG_CONSTRUCT_PROXY;
}
#endif
if (call_flags & DUK_CALL_FLAG_DIRECT_EVAL) {
act->flags |= DUK_ACT_FLAG_DIRECT_EVAL;
}
@ -2032,9 +2212,15 @@ DUK_LOCAL duk_int_t duk__handle_call_raw(duk_hthread *thr,
* Constructor call post processing.
*/
#if defined(DUK_USE_ES6_PROXY)
if (call_flags & (DUK_CALL_FLAG_CONSTRUCT | DUK_CALL_FLAG_CONSTRUCT_PROXY)) {
duk_call_construct_postprocess(ctx, call_flags & DUK_CALL_FLAG_CONSTRUCT_PROXY);
}
#else
if (call_flags & DUK_CALL_FLAG_CONSTRUCT) {
duk_call_construct_postprocess(ctx);
duk_call_construct_postprocess(ctx, 0);
}
#endif
/*
* Unwind, restore valstack bottom and other book-keeping.

8
src-input/duk_js_executor.c

@ -1620,9 +1620,15 @@ DUK_LOCAL duk_small_uint_t duk__handle_return(duk_hthread *thr, duk_activation *
DUK_ASSERT(thr->callstack_curr->parent != NULL);
DUK_ASSERT(DUK_HOBJECT_IS_COMPFUNC(DUK_ACT_GET_FUNC(thr->callstack_curr->parent))); /* must be ecmascript */
#if defined(DUK_USE_ES6_PROXY)
if (thr->callstack_curr->flags & (DUK_ACT_FLAG_CONSTRUCT | DUK_ACT_FLAG_CONSTRUCT_PROXY)) {
duk_call_construct_postprocess((duk_context *) thr, thr->callstack_curr->flags & DUK_ACT_FLAG_CONSTRUCT_PROXY); /* side effects */
}
#else
if (thr->callstack_curr->flags & DUK_ACT_FLAG_CONSTRUCT) {
duk_call_construct_postprocess((duk_context *) thr); /* side effects */
duk_call_construct_postprocess((duk_context *) thr, 0); /* side effects */
}
#endif
tv1 = (duk_tval *) (void *) ((duk_uint8_t *) thr->valstack + thr->callstack_curr->parent->retval_byteoff);
DUK_ASSERT(thr->valstack_top - 1 >= thr->valstack_bottom);

2
src-input/duk_js_var.c

@ -838,7 +838,6 @@ duk_bool_t duk__get_identifier_reference(duk_hthread *thr,
duk_bool_t parents,
duk__id_lookup_result *out) {
duk_tval *tv;
duk_tval tv_name;
duk_uint_t sanity;
DUK_ASSERT(thr != NULL);
@ -1018,6 +1017,7 @@ duk_bool_t duk__get_identifier_reference(duk_hthread *thr,
#if defined(DUK_USE_ES6_PROXY)
if (DUK_UNLIKELY(DUK_HOBJECT_IS_PROXY(target))) {
duk_tval tv_name;
duk_tval tv_target_tmp;
DUK_ASSERT(name != NULL);

2
src-input/duk_strings.h

@ -76,7 +76,6 @@
#define DUK_STR_CYCLIC_INPUT "cyclic input"
/* Object property access */
#define DUK_STR_PROXY_REVOKED "proxy revoked"
#define DUK_STR_INVALID_BASE "invalid base value"
#define DUK_STR_STRICT_CALLER_READ "cannot read strict 'caller'"
#define DUK_STR_PROXY_REJECTED "proxy rejected"
@ -85,6 +84,7 @@
#define DUK_STR_INVALID_DESCRIPTOR "invalid descriptor"
/* Proxy */
#define DUK_STR_PROXY_REVOKED "proxy revoked"
#define DUK_STR_INVALID_TRAP_RESULT "invalid trap result"
/* Variables */

8
src-input/strings.yaml

@ -442,10 +442,10 @@ strings:
# es6: true
- str: "ownKeys"
es6: true
#- str: "apply"
# es6: true
#- str: "construct"
# es6: true
- str: "apply"
es6: true
- str: "construct"
es6: true
- str: "Reflect"
es6: true

4
tests/api/test-dump-load-basic.c

@ -40,7 +40,7 @@
/*===
*** test_basic (duk_safe_call)
dump result type: 7
ff000000000e00000003000000010009000000000001000000011800088000000007000001ad000103030000049800040500000505ab8001070480020804000502b00005040000020503000103b0000100000000009d00000000057072696e74000000000568656c6c6f0140091eb851eb851f000000030000000000000000000300020000000100000001180c0980010002340002009d0000009e000000020000000561646465720000000f66616b6546696c656e616d652e6a730000000d03000000010000000c000000000000000178000000000000000179000000010000000000000002000000017800000001790000000000000006676c6f62616c0000000f66616b6546696c656e616d652e6a730000000e0e000000010000000c00000000000000000000000000
ff000000000e000000030000000100090000000000010000000118000a8000000007000001ad000103030000049800040500000505ab8001070480020804000502b00005040000020503000103b0000100000000009d00000000057072696e74000000000568656c6c6f0140091eb851eb851f000000030000000000000000000300020000000100000001180c0b80010002340002009d0000009e000000020000000561646465720000000f66616b6546696c656e616d652e6a730000000d03000000010000000c000000000000000178000000000000000179000000010000000000000002000000017800000001790000000000000006676c6f62616c0000000f66616b6546696c656e616d652e6a730000000e0e000000010000000c00000000000000000000000000
load result type: 6
hello 3 3.14
call result type: 1
@ -93,7 +93,7 @@ static duk_ret_t test_basic(duk_context *ctx, void *udata) {
/*===
*** test_mandel (duk_safe_call)
Mandelbrot source length: 884
ff000000005a000000100000000000130000000000010000001e180c0980804c0004801c010480640204000000a180005302800005028000030401030e28000e00328000030280004d0200030e7a7ffffa0201030e40000e0e3e010e083a000100a180003e028000070280000404001000c100100d0000040e28000e0032800003028000360200040e7a7ffffa0200040e40020e0e3e030e073a000200a18000290280000802800005048000090480000a040004060302050e28000e0032800003028000200200050e7a7ffffa0209090b3c0a0a0c3c0c0b0e34050e0e2a000e00308000060209030e3d0a0e0e3c080e0a340c0b0e38070e09347ffff20202050e2a000e0030800002020006060380000d0207050e2a000e003080000202000806038000080209050e2a000e003080000202000a060380000302000b0603800001027fffe002000200a2000d0f000c0d0e6e00061000000e01b07fffca02000100a2000d0ead000d11000e0d106e000f1203001001b0000e01b07fffb302000000a20000009e014004000000000000013ff400000000000001400800000000000001400000000000000000000000012301401000000000000000000000012e01401400000000000000000000012c01402400000000000000000000012d00000000013d00000000047075736800000000057072696e7400000000046a6f696e000000000000000000000000066d616e64656c000000096d616e64656c2e6a73000000285a000000020000001400000013000000210000002880201001008004244108910080844a04201080000000017700000000000000016800000001000000046974657200000002000000016900000003000000016a00000004000000016b000000050000000163000000060000000278300000000700000002793000000008000000027878000000090000000279790000000a000000037878320000000b000000037979320000000c000000046c696e650000000d0000000000000000
ff000000005a000000100000000000130000000000010000001e180c0b80804c0004801c010480640204000000a180005302800005028000030401030e28000e00328000030280004d0200030e7a7ffffa0201030e40000e0e3e010e083a000100a180003e028000070280000404001000c100100d0000040e28000e0032800003028000360200040e7a7ffffa0200040e40020e0e3e030e073a000200a18000290280000802800005048000090480000a040004060302050e28000e0032800003028000200200050e7a7ffffa0209090b3c0a0a0c3c0c0b0e34050e0e2a000e00308000060209030e3d0a0e0e3c080e0a340c0b0e38070e09347ffff20202050e2a000e0030800002020006060380000d0207050e2a000e003080000202000806038000080209050e2a000e003080000202000a060380000302000b0603800001027fffe002000200a2000d0f000c0d0e6e00061000000e01b07fffca02000100a2000d0ead000d11000e0d106e000f1203001001b0000e01b07fffb302000000a20000009e014004000000000000013ff400000000000001400800000000000001400000000000000000000000012301401000000000000000000000012e01401400000000000000000000012c01402400000000000000000000012d00000000013d00000000047075736800000000057072696e7400000000046a6f696e000000000000000000000000066d616e64656c000000096d616e64656c2e6a73000000285a000000020000001400000013000000210000002880201001008004244108910080844a04201080000000017700000000000000016800000001000000046974657200000002000000016900000003000000016a00000004000000016b000000050000000163000000060000000278300000000700000002793000000008000000027878000000090000000279790000000a000000037878320000000b000000037979320000000c000000046c696e650000000d0000000000000000
..........................,,,,,,,,,,,,,,,,,,,,,,,,,.........................
....................,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,...................
................,,,,,,,,,,,,,,,,,,,,,,,,,,,---=----,,,,,,,,,,...............

14
tests/api/test-is-calls.c

@ -22,7 +22,10 @@
19: valididx=1 und=0 null=0 noru=0 bool=0 num=0 nan=0 str=0 obj=0 arr=0 fun=0 cfun=0 efun=0 bfun=0 call=0 construct=0 thr=0 buf=1 dyn=1 fix=0 ext=0 ptr=0 prim=0 objcoerc=1
20: valididx=1 und=0 null=0 noru=0 bool=0 num=0 nan=0 str=0 obj=0 arr=0 fun=0 cfun=0 efun=0 bfun=0 call=0 construct=0 thr=0 buf=1 dyn=0 fix=0 ext=1 ptr=0 prim=0 objcoerc=1
21: valididx=1 und=0 null=0 noru=0 bool=0 num=0 nan=0 str=0 obj=0 arr=0 fun=0 cfun=0 efun=0 bfun=0 call=0 construct=0 thr=0 buf=0 dyn=0 fix=0 ext=0 ptr=1 prim=1 objcoerc=1
22: valididx=0 und=0 null=0 noru=0 bool=0 num=0 nan=0 str=0 obj=0 arr=0 fun=0 cfun=0 efun=0 bfun=0 call=0 construct=0 thr=0 buf=0 dyn=0 fix=0 ext=0 ptr=0 prim=0 objcoerc=0
22: valididx=1 und=0 null=0 noru=0 bool=0 num=0 nan=0 str=0 obj=1 arr=0 fun=0 cfun=0 efun=0 bfun=0 call=0 construct=0 thr=0 buf=0 dyn=0 fix=0 ext=0 ptr=0 prim=0 objcoerc=1
23: valididx=1 und=0 null=0 noru=0 bool=0 num=0 nan=0 str=0 obj=1 arr=0 fun=1 cfun=0 efun=0 bfun=0 call=1 construct=0 thr=0 buf=0 dyn=0 fix=0 ext=0 ptr=0 prim=0 objcoerc=1
24: valididx=1 und=0 null=0 noru=0 bool=0 num=0 nan=0 str=0 obj=1 arr=0 fun=1 cfun=0 efun=0 bfun=0 call=1 construct=1 thr=0 buf=0 dyn=0 fix=0 ext=0 ptr=0 prim=0 objcoerc=1
25: valididx=0 und=0 null=0 noru=0 bool=0 num=0 nan=0 str=0 obj=0 arr=0 fun=0 cfun=0 efun=0 bfun=0 call=0 construct=0 thr=0 buf=0 dyn=0 fix=0 ext=0 ptr=0 prim=0 objcoerc=0
==> rc=0, result='undefined'
===*/
@ -107,6 +110,15 @@ static duk_ret_t test_1(duk_context *ctx, void *udata) {
/* 21 */
duk_push_pointer(ctx, (void *) 0xf00);
/* 22 */
duk_eval_string(ctx, "new Proxy({}, {})"); /* non-callable proxy */
/* 23 */
duk_eval_string(ctx, "new Proxy(Math.sin, {})"); /* callable but not constructable proxy */
/* 24 */
duk_eval_string(ctx, "new Proxy(function dummy() {}, {})"); /* callable and constructable proxy */
/*
* call checkers for each
*

66
tests/ecmascript/test-bi-proxy-apply-yield.js

@ -0,0 +1,66 @@
/*
* Yielding from a Proxy 'apply' trap.
*/
/*===
myFunction yielding
1001
myFunction resumed
myThread yielding result 1
234
trap yielding
2001
trap resumed
myFunction yielding
1001
myFunction resumed
myThread yielding result 2
234
myThread done
thread-done
===*/
function myFunction() {
print('myFunction yielding');
Duktape.Thread.yield(1001);
print('myFunction resumed');
return 234;
}
function myThread() {
var proxy = new Proxy(myFunction, {});
var x = proxy();
print('myThread yielding result 1');
Duktape.Thread.yield(x);
proxy = new Proxy(myFunction, {
apply: function (targ, argArray, newTarget) {
print('trap yielding');
Duktape.Thread.yield(2001);
print('trap resumed');
return myFunction();
}
});
x = proxy();
print('myThread yielding result 2');
Duktape.Thread.yield(x);
print('myThread done');
return 'thread-done';
}
function test() {
var t = new Duktape.Thread(myThread);
print(Duktape.Thread.resume(t, 3001));
print(Duktape.Thread.resume(t, 3002));
print(Duktape.Thread.resume(t, 3003));
print(Duktape.Thread.resume(t, 3004));
print(Duktape.Thread.resume(t, 3005));
print(Duktape.Thread.resume(t, 3006));
}
try {
test();
} catch (e) {
print(e.stack || e);
}

165
tests/ecmascript/test-bi-proxy-apply.js

@ -0,0 +1,165 @@
/*
* Proxy (ES2015) 'apply'.
*/
/*===
- target not callable
object
TypeError
- target callable, trap non-callable
function
TypeError
- target callable, proxy callable
function
apply trap
true
true
undefined
object 2 123 234 undefined
VALUE
- .call(), .apply(), Reflect.apply()
apply trap
true
true
myThisCall
object 4 123 234 345
VALUE
apply trap
true
true
myThisApply
object 2 123 234 undefined
VALUE
apply trap
true
true
myThisReflectApply
object 2 123 234 undefined
VALUE
- bound function as trap
function
bound apply trap
true
123 234
VALUE
- passthrough
function
myFunction called
object [object global]
foo bar quux undefined undefined
123
===*/
function myFunction(a,b,c,d,e) {
print('myFunction called');
print(typeof this, this);
print(a, b, c, d, e);
return 123;
}
function test() {
var target;
var handler;
var proxy;
// If the (initial) target is not callable, the Proxy is not callable
// even if it has a callable 'apply' trap.
print('- target not callable');
target = {};
handler = {
apply: function myApply(targ, thisArg, argArray) {
print('apply trap');
print(this === handler);
print(targ === target);
print(thisArg);
print(typeof argArray, argArray.length, argArray[0], argArray[1], argArray[2]);
return 'VALUE';
}
};
proxy = new Proxy(target, handler);
print(typeof proxy);
try {
print(proxy(123));
} catch (e) {
print(e.name);
}
// The Proxy may be callable but a trap non-callable; if so, TypeError.
// Firefox ignores both 'null' and 'undefined', specification only seems
// to ignore missing/undefined.
print('- target callable, trap non-callable');
target = myFunction;
handler = {
apply: null
};
proxy = new Proxy(target, handler);
print(typeof proxy);
try {
print(proxy(123, 234));
} catch (e) {
print(e.name);
}
// If the (initial) target is callable, the Proxy is callable which also
// makes typeof return 'function'.
print('- target callable, proxy callable');
target = myFunction;
handler = {
apply: function myApply(targ, thisArg, argArray) {
print('apply trap');
print(this === handler);
print(targ === target);
print(thisArg);
print(typeof argArray, argArray.length, argArray[0], argArray[1], argArray[2]);
return 'VALUE';
}
};
proxy = new Proxy(target, handler);
print(typeof proxy);
try {
print(proxy(123, 234));
} catch (e) {
print(e.name);
}
// Trap is also called via .call(), .apply(), and Reflect.apply();
print('- .call(), .apply(), Reflect.apply()');
print(proxy.call('myThisCall', 123, 234, 345, 456));
print(proxy.apply('myThisApply', [ 123, 234 ]));
print(Reflect.apply(proxy, 'myThisReflectApply', [ 123, 234 ]));
// Bound function as trap.
print('- bound function as trap');
target = myFunction;
function myApply(targ, thisArg, argArray) {
// Two args are bound, so actual 'targ' argument ends at argArray.
print('bound apply trap');
print(argArray === target);
print(targ, thisArg); // 123, 234
return 'VALUE';
}
handler = {
apply: myApply.bind('boundThis', 123, 234)
};
proxy = new Proxy(target, handler);
print(typeof proxy);
try {
print(proxy(123, 234));
} catch (e) {
print(e.name);
}
// Passthrough case: Proxy without 'apply' trap.
print('- passthrough');
handler = {};
target = myFunction;
proxy = new Proxy(target, handler);
print(typeof proxy);
print(proxy('foo', 'bar', 'quux'));
}
try {
test();
} catch (e) {
print(e.stack || e);
}

48
tests/ecmascript/test-bi-proxy-construct-invariants.js

@ -0,0 +1,48 @@
/*
* Proxy 'construct' trap return value invariant: return value must be an
* Object or a TypeError is thrown.
*/
/*===
bar-trap
TypeError
===*/
function test() {
var proxy;
function MyConstructor(a, b, c) {
this.foo = 'bar';
}
// Proxy 'construct' must return an object.
proxy = new Proxy(MyConstructor, {
construct: function (targ, argArray, newTarget) {
return { foo: 'bar-trap' };
}
});
try {
print(new proxy().foo);
} catch (e) {
print(e.stack || e);
}
// If the return value is not an object, TypeError.
proxy = new Proxy(MyConstructor, {
construct: function (targ, argArray, newTarget) {
return 'xyz';
}
});
try {
print(new proxy());
} catch (e) {
print(e.name);
//print(e.stack || e);
}
}
try {
test();
} catch (e) {
print(e.stack || e);
}

43
tests/ecmascript/test-bi-proxy-construct-newtarget.js

@ -0,0 +1,43 @@
/*
* When calling a constructor via a Proxy object which doesn't have a
* 'construct' trap, the target function's new.target should evaluate
* to the Proxy (original target of constructor call) and not the Proxy's
* target object.
*
* In Duktape 2.2 new.target will evaluate to the target object instead.
*/
/*===
function
true
false
function
false
true
===*/
var myProxy;
function MyConstructor() {
print(typeof new.target);
print(new.target === MyConstructor);
print(new.target === myProxy);
}
function test() {
// No 'construct' trap, just call through.
myProxy = new Proxy(MyConstructor, {});
// Direct constructor call, new.target should be MyConstructor.
print('- direct call');
new MyConstructor();
// Call via Proxy without capturing trap.
new myProxy();
}
try {
test();
} catch (e) {
print(e.stack || e);
}

46
tests/ecmascript/test-bi-proxy-construct-prototype-lookup.js

@ -0,0 +1,46 @@
/*
* Constructor .prototype lookup when calling through a Proxy without
* 'construct' trap. The .prototype should be looked up via the Proxy
* and not the target, allowing the Proxy to capture the lookup.
*/
/*===
- direct call
bar-prototype
- call via proxy
get trap: prototype
bar-replaced
===*/
function MyConstructor() {
}
MyConstructor.prototype.foo = 'bar-prototype';
function test() {
var replaced = { foo: 'bar-replaced' };
var proxy = new Proxy(MyConstructor, {
get: function get(targ, prop, recv) {
print('get trap:', prop);
if (prop === 'prototype') {
return replaced;
}
return targ[prop];
}
});
var tmp;
print('- direct call');
tmp = new MyConstructor();
print(tmp.foo);
print('- call via proxy');
tmp = new proxy();
print(tmp.foo);
}
try {
test();
} catch (e) {
print(e.stack || e);
}

66
tests/ecmascript/test-bi-proxy-construct-yield.js

@ -0,0 +1,66 @@
/*
* Yielding from a Proxy 'construct' trap.
*/
/*===
MyConstructor yielding
1001
MyConstructor resumed
myThread yielding result 1
234
trap yielding
2001
trap resumed
MyConstructor yielding
1001
MyConstructor resumed
myThread yielding result 2
234
myThread done
thread-done
===*/
function MyConstructor() {
print('MyConstructor yielding');
Duktape.Thread.yield(1001);
print('MyConstructor resumed');
return { foo: 234 };
}
function myThread() {
var proxy = new Proxy(MyConstructor, {});
var x = new proxy();
print('myThread yielding result 1');
Duktape.Thread.yield(x.foo);
proxy = new Proxy(MyConstructor, {
construct: function (targ, argArray, newTarget) {
print('trap yielding');
Duktape.Thread.yield(2001);
print('trap resumed');
return new MyConstructor();
}
});
x = new proxy();
print('myThread yielding result 2');
Duktape.Thread.yield(x.foo);
print('myThread done');
return 'thread-done';
}
function test() {
var t = new Duktape.Thread(myThread);
print(Duktape.Thread.resume(t, 3001));
print(Duktape.Thread.resume(t, 3002));
print(Duktape.Thread.resume(t, 3003));
print(Duktape.Thread.resume(t, 3004));
print(Duktape.Thread.resume(t, 3005));
print(Duktape.Thread.resume(t, 3006));
}
try {
test();
} catch (e) {
print(e.stack || e);
}

164
tests/ecmascript/test-bi-proxy-construct.js

@ -0,0 +1,164 @@
/*
* Proxy (ES2015) 'construct'.
*/
/*===
- target not callable
object
[object Object]
TypeError
- target not constructable
function
[object Function]
TypeError
- proxy constructable but trap not callable
function
[object Function]
TypeError
- proxy constructable but trap only callable
function
[object Function]
0
- basic case
function
[object Function]
construct trap
true
true
object 2 123 234 undefined
true
bar-trap
- Reflect.construct
construct trap
true
true
object 2 123 234 undefined
true
bar-trap
- passthrough
function
[object Function]
MyConstructor called
object [object Object]
function
123 234 undefined undefined undefined
bar
===*/
var globalProxyRef;
function MyConstructor(a, b, c, d, e) {
print('MyConstructor called');
print(typeof this, this);
print(typeof new.target); // exact new.target behavior covered by separate test(s)
print(a, b, c, d, e);
this.foo = 'bar';
}
function test() {
var target;
var handler;
var proxy;
// If the (initial) target is not callable, the Proxy is not constructable.
print('- target not callable');
target = {};
handler = {
construct: function myConstruct(targ, argArray, newTarget) {
print('construct trap');
print(this === handler);
print(targ === target);
print(typeof argArray, argArray.length, argArray[0], argArray[1], argArray[2]);
print(newTarget === proxy);
return { foo: 'bar-trap' };
}
};
proxy = new Proxy(target, handler);
globalProxyRef = proxy;
print(typeof proxy);
print(Object.prototype.toString.call(proxy));
try {
print(new proxy(123).foo);
} catch (e) {
print(e.name);
}
// Similarly, if the target is callable but not constructable, the Proxy
// is not constructable. However, typeof will still be a 'function' because
// the Proxy is callable.
print('- target not constructable');
target = Math.sin;
proxy = new Proxy(target, handler);
globalProxyRef = proxy;
print(typeof proxy);
print(Object.prototype.toString.call(proxy));
try {
print(new proxy(123).foo);
} catch (e) {
print(e.name);
}
// Proxy may be constructable but trap not callable.
// Firefox ignores a null construct trap; ES2015 and later seem to
// indicate only 'undefined' trap should be ignored.
print('- proxy constructable but trap not callable');
target = MyConstructor;
proxy = new Proxy(target, { construct: null });
globalProxyRef = proxy;
print(typeof proxy);
print(Object.prototype.toString.call(proxy));
try {
print(new proxy(123).foo);
} catch (e) {
print(e.name);
}
// Proxy may be constructable but trap only callable (not constructable),
// this is OK as the trap is called as a normal function. Trap is chosen
// carefully to be a non-constructable built-in which returns an object
// to satisfy the Proxy return value invariant.
print('- proxy constructable but trap only callable');
target = MyConstructor;
proxy = new Proxy(target, { construct: Array.prototype.map });
globalProxyRef = proxy;
print(typeof proxy);
print(Object.prototype.toString.call(proxy));
try {
print(new proxy(123).length); // return value is empty array, so 0
} catch (e) {
print(e.name);
}
// Basic case.
print('- basic case');
target = MyConstructor;
proxy = new Proxy(target, handler);
globalProxyRef = proxy;
print(typeof proxy);
print(Object.prototype.toString.call(proxy));
try {
print(new proxy(123, 234).foo);
} catch (e) {
print(e.name);
}
// Trap is also called via Reflect.construct().
print('- Reflect.construct');
print(Reflect.construct(proxy, [ 123, 234 ], proxy).foo);
// Passthrough case: Proxy without 'construct' trap.
print('- passthrough');
target = MyConstructor;
handler = {};
proxy = new Proxy(target, handler);
globalProxyRef = proxy;
print(typeof proxy);
print(Object.prototype.toString.call(proxy));
print(new proxy(123, 234).foo);
}
try {
test();
} catch (e) {
print(e.stack || e);
}

287
tests/ecmascript/test-dev-tailcall-constructor-normal-mixing.js

@ -2,6 +2,16 @@
* Tailcall requires that current and target function share the same return
* value handling; constructor calls have a special return value handling
* compared to normal function calls.
*
* In more detail there are three different return value handling mechanisms
* in ES2015:
*
* - Normal call, no special handling.
* - Constructor call, default instance vs. replacement instance handling
* - Proxy 'construct' trap call, return value invariant check
*
* A tailcall is only allowed (by Duktape) when the return value handling of
* the calling code and target code is identical.
*/
/*---
@ -10,7 +20,18 @@
}
---*/
function getDepth() {
var res = 0;
//console.trace();
for (var i = -1; Duktape.act(i); i--) {
res++;
}
// 'res' is number of callstack entries
return res - 2; // -1 for Duktape.act, -1 for getDepth()
}
/*===
non-proxy test
normal -> normal
- tailcall
normal -> normal, using call
@ -37,15 +58,8 @@ constructor -> normal call, using Reflect.apply
- not a tailcall
===*/
function getDepth() {
//console.trace();
for (var i = -1; Duktape.act(i); i--) {
}
return -i;
}
function test() {
var baseline;
function testNonProxy() {
var baseline = getDepth() + 2; // baseline: normal, non-tailcall call stack depth
function check() {
var d = getDepth() - 1; // account for check() call
@ -58,11 +72,6 @@ function test() {
}
}
// Baseline: normal, non-tailcall call stack depth.
function baseline_a() { baseline = getDepth(); }
function baseline_b() { baseline_a(); }
baseline_b();
// Normal -> normal tail call.
print('normal -> normal');
function nn_a() { check(); }
@ -122,7 +131,255 @@ function test() {
}
try {
test();
print('non-proxy test');
testNonProxy();
} catch (e) {
print(e.stack || e);
}
/*===
proxy construct test 1
proxy2 construct called
proxy1 construct called
relative depth: 1
bar-1-replaced
proxy3 construct called
proxy1 construct called
relative depth: 2
bar-1-replaced
proxy construct test 2
proxy1 construct called
relative depth: 2
bar-1-replaced
proxy construct test 3
proxy1 construct called
MyConstructor1 called
relative depth: 2
bar
===*/
// Constructor and Proxy 'construct' trap calls have different return value
// handling which prevents mixing them in tailcalls.
function testProxyConstruct1() {
var baseline = getDepth(); // baseline: function entry level
var proxy1, proxy2, proxy3;
// These are not actually called.
function MyConstructor1() {
print('MyConstructor1 called');
return { foo: 'bar-1' };
}
function MyConstructor2() {
print('MyConstructor2 called');
return { foo: 'bar-2' };
}
proxy1 = new Proxy(MyConstructor1, {
construct: function construct1(target, argArray, newTarget) {
print('proxy1 construct called');
print('relative depth:', getDepth() - baseline);
return { foo: 'bar-1-replaced' };
}
});
proxy2 = new Proxy(MyConstructor2, {
construct: function construct2(target, argArray, newTarget) {
print('proxy2 construct called');
// This is a tailcall: calling code (this function) has Proxy
// 'construct' return value handling, and so does the target.
return new proxy1(); // MyConstructor2 not called
}
});
proxy3 = new Proxy(MyConstructor2, {
construct: function construct3(target, argArray, newTarget) {
print('proxy3 construct called');
// For comparison only, avoid tailcall to see depth difference.
var ret = new proxy1(); // MyConstructor2 not called
void 1+1;
return ret;
}
});
// Tailcall: Proxy trap to Proxy trap.
print(new proxy2().foo);
// Not a tailcall, for comparison.
print(new proxy3().foo);
}
function testProxyConstruct2() {
var baseline = getDepth(); // baseline: function entry level
var proxy1;
// Not actually called.
function MyConstructor1() {
print(getDepth() - baseline);
print('MyConstructor1 called');
return { foo: 'bar' };
}
proxy1 = new Proxy(MyConstructor1, {
construct: function construct1(target, argArray, newTarget) {
print('proxy1 construct called');
print('relative depth:', getDepth() - baseline);
return { foo: 'bar-1-replaced' };
}
});
function MyConstructor2() {
// This is NOT a tailcall: calling code (this function) has normal
// constructor return value handling, target has Proxy 'construct'
// return value handling.
return new proxy1();
}
// Not a tailcall.
print(new MyConstructor2().foo);
}
function testProxyConstruct3() {
var baseline = getDepth(); // baseline: function entry level
var proxy1;
function MyConstructor1() {
print('MyConstructor1 called');
print('relative depth:', getDepth() - baseline);
return { foo: 'bar' };
}
proxy1 = new Proxy(MyConstructor1, {
construct: function construct1(target, argArray, newTarget) {
print('proxy1 construct called');
// Not a tailcall: calling context is a Proxy trap, target
// is a normal constructor call.
return new MyConstructor1();
}
});
// Not a tailcall
print(new proxy1().foo);
}
try {
print('proxy construct test 1');
testProxyConstruct1();
} catch (e) {
print(e.stack || e);
}
try {
print('proxy construct test 2');
testProxyConstruct2();
} catch (e) {
print(e.stack || e);
}
try {
print('proxy construct test 3');
testProxyConstruct3();
} catch (e) {
print(e.stack || e);
}
/*===
proxy apply test 1
- normal -> normal
caller1 called
target called
relative depth: 1
123
- proxy apply -> normal
apply trap 1 called
target called
relative depth: 1
123
- normal -> proxy apply
caller2 called
apply trap 2 called
relative depth: 1
234
apply trap 4 called
apply trap 3 called
relative depth: 1
234
===*/
// Function call and Proxy 'apply' trap have same return value behavior
// so they can be mixed.
function proxyApplyTest1() {
var baseline = getDepth(); // baseline: function entry level
var proxy1;
var proxy2;
function target() {
print('target called');
print('relative depth:', getDepth() - baseline);
return 123;
}
function caller1() {
print('caller1 called');
return target();
}
proxy1 = new Proxy(function dummy() {}, {
apply: function (targ, thisArg, argArray) {
print('apply trap 1 called');
return target();
}
});
proxy2 = new Proxy(function dummy() {}, {
apply: function (targ, thisArg, argArray) {
print('apply trap 2 called');
print('relative depth:', getDepth() - baseline);
return 234;
}
});
function caller2() {
print('caller2 called');
return proxy2();
}
proxy3 = new Proxy(function dummy() {}, {
apply: function (targ, thisArg, argArray) {
print('apply trap 3 called');
print('relative depth:', getDepth() - baseline);
return 234;
}
});
proxy4 = new Proxy(function dummy() {}, {
apply: function (targ, thisArg, argArray) {
print('apply trap 4 called');
return proxy3();
}
});
// Normal -> normal tailcall.
print('- normal -> normal');
print(caller1());
// Proxy apply -> normal tailcall.
print('- proxy apply -> normal');
print(proxy1());
// Normal -> Proxy apply tailcall.
print('- normal -> proxy apply');
print(caller2());
// Proxy apply -> Proxy apply tailcall.
print(proxy4());
}
try {
print('proxy apply test 1');
proxyApplyTest1();
} catch (e) {
print(e.stack || e);
}

9
tests/knownissues/test-bi-proxy-construct-newtarget-1.txt

@ -0,0 +1,9 @@
summary: new.target is target object rather than Proxy when calling via Proxy
---
- direct call
function
true
false
function
true
false

42
tests/perf/test-call-proxy-apply-1.js

@ -0,0 +1,42 @@
/*
* Basic Proxy 'apply' trap performance.
*/
if (typeof print !== 'function') { print = console.log; }
function test() {
var i;
function f_target() { return; } // ignored
var f = new Proxy(f_target, {
apply: function (targ, thisArg, argArray) {
return;
}
});
var t1 = Date.now();
for (i = 0; i < 1e6; i++) {
f(); f(); f(); f(); f(); f(); f(); f(); f(); f();
f(); f(); f(); f(); f(); f(); f(); f(); f(); f();
f(); f(); f(); f(); f(); f(); f(); f(); f(); f();
f(); f(); f(); f(); f(); f(); f(); f(); f(); f();
f(); f(); f(); f(); f(); f(); f(); f(); f(); f();
f(); f(); f(); f(); f(); f(); f(); f(); f(); f();
f(); f(); f(); f(); f(); f(); f(); f(); f(); f();
f(); f(); f(); f(); f(); f(); f(); f(); f(); f();
f(); f(); f(); f(); f(); f(); f(); f(); f(); f();
f(); f(); f(); f(); f(); f(); f(); f(); f(); f();
}
var t2 = Date.now();
print((1e6 * 100 / (t2 - t1)) + ' calls per millisecond');
}
try {
test();
} catch (e) {
print(e.stack || e);
throw e;
}

38
tests/perf/test-call-proxy-pass-1.js

@ -0,0 +1,38 @@
/*
* Basic Proxy passthrough call performance.
*/
if (typeof print !== 'function') { print = console.log; }
function test() {
var i;
function f_target() { return; }
var f = new Proxy(f_target, {});
var t1 = Date.now();
for (i = 0; i < 1e6; i++) {
f(); f(); f(); f(); f(); f(); f(); f(); f(); f();
f(); f(); f(); f(); f(); f(); f(); f(); f(); f();
f(); f(); f(); f(); f(); f(); f(); f(); f(); f();
f(); f(); f(); f(); f(); f(); f(); f(); f(); f();
f(); f(); f(); f(); f(); f(); f(); f(); f(); f();
f(); f(); f(); f(); f(); f(); f(); f(); f(); f();
f(); f(); f(); f(); f(); f(); f(); f(); f(); f();
f(); f(); f(); f(); f(); f(); f(); f(); f(); f();
f(); f(); f(); f(); f(); f(); f(); f(); f(); f();
f(); f(); f(); f(); f(); f(); f(); f(); f(); f();
}
var t2 = Date.now();
print((1e6 * 100 / (t2 - t1)) + ' calls per millisecond');
}
try {
test();
} catch (e) {
print(e.stack || e);
throw e;
}

8
tools/genbuiltins.py

@ -423,7 +423,7 @@ def metadata_normalize_shorthand(meta):
obj['magic'] = val.get('magic', 0)
obj['internal_prototype'] = 'bi_function_prototype'
obj['class'] = 'Function'
obj['callable'] = True
obj['callable'] = val.get('callable', True)
obj['constructable'] = val.get('constructable', False)
obj['special_call'] = val.get('special_call', False)
fun_name = val.get('name', funprop['key'])
@ -441,8 +441,8 @@ def metadata_normalize_shorthand(meta):
obj['magic'] = magic
obj['internal_prototype'] = 'bi_function_prototype'
obj['class'] = 'Function'
obj['callable'] = True
obj['constructable'] = False
obj['callable'] = val.get('callable', True)
obj['constructable'] = val.get('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.
@ -2750,6 +2750,8 @@ def rom_emit_objects(genc, meta, bi_str_map):
flags.append('DUK_HOBJECT_FLAG_NATFUNC')
flags.append('DUK_HOBJECT_FLAG_STRICT')
flags.append('DUK_HOBJECT_FLAG_NEWENV')
if obj.get('callable', False):
flags.append('DUK_HOBJECT_FLAG_CALLABLE')
if obj.get('constructable', False):
flags.append('DUK_HOBJECT_FLAG_CONSTRUCTABLE')
if obj.get('class') == 'Array':

Loading…
Cancel
Save