Browse Source

Allow yield from constructor calls

pull/1523/head
Sami Vaarala 8 years ago
parent
commit
cd08ff8624
  1. 209
      src-input/duk_api_call.c
  2. 23
      src-input/duk_api_stack.c
  3. 2
      src-input/duk_bi_error.c
  4. 5
      src-input/duk_error.h
  5. 51
      src-input/duk_error_augment.c
  6. 1
      src-input/duk_js.h
  7. 257
      src-input/duk_js_call.c
  8. 49
      src-input/duk_js_executor.c
  9. 2
      src-input/duktape.h.in

209
src-input/duk_api_call.c

@ -125,7 +125,7 @@ DUK_EXTERNAL duk_int_t duk_pcall(duk_context *ctx, duk_idx_t nargs) {
return DUK_EXEC_ERROR; /* unreachable */
}
/* awkward; we assume there is space for this */
/* Rely on the internal value stack reserve for these operations. */
duk_push_undefined(ctx);
duk_insert(ctx, idx_func + 1);
@ -230,205 +230,21 @@ DUK_EXTERNAL duk_int_t duk_safe_call(duk_context *ctx, duk_safe_call_function fu
}
DUK_EXTERNAL void duk_new(duk_context *ctx, duk_idx_t nargs) {
/*
* There are two [[Construct]] operations in the specification:
*
* - E5 Section 13.2.2: for Function objects
* - E5 Section 15.3.4.5.2: for "bound" Function objects
*
* The chain of bound functions is resolved in Section 15.3.4.5.2,
* with arguments "piling up" until the [[Construct]] internal
* method is called on the final, actual Function object. Note
* that the "prototype" property is looked up *only* from the
* final object, *before* calling the constructor.
*
* Since Duktape 2.2 bound functions are represented with the
* duk_hboundfunc internal type, and bound function chains are
* collapsed when a bound function is created. As a result, the
* direct target of a duk_hboundfunc is always non-bound and the
* this/argument lists have been resolved.
*
* When constructing new Array instances, an unnecessary object is
* created and discarded now: the standard [[Construct]] creates an
* object, and calls the Array constructor. The Array constructor
* returns an Array instance, which is used as the result value for
* the "new" operation; the object created before the Array constructor
* call is discarded.
*
* This would be easy to fix, e.g. by knowing that the Array constructor
* will always create a replacement object and skip creating the fallback
* object in that case.
*
* Note: functions called via "new" need to know they are called as a
* constructor. For instance, built-in constructors behave differently
* depending on how they are called.
*/
/* XXX: merge this with duk_js_call.c, as this function implements
* core semantics (or perhaps merge the two files altogether).
*/
duk_hthread *thr = (duk_hthread *) ctx;
duk_hobject *proto;
duk_hobject *cons;
duk_hobject *fallback;
duk_idx_t idx_cons;
duk_small_uint_t call_flags;
duk_idx_t idx_func;
DUK_ASSERT_CTX_VALID(ctx);
/* [... constructor arg1 ... argN] */
idx_cons = duk_require_normalize_index(ctx, -nargs - 1);
DUK_DDD(DUK_DDDPRINT("top=%ld, nargs=%ld, idx_cons=%ld",
(long) duk_get_top(ctx), (long) nargs, (long) idx_cons));
/* XXX: code duplication */
/*
* Figure out the final, non-bound constructor, to get "prototype"
* property.
*/
duk_dup(ctx, idx_cons);
duk_resolve_nonbound_function(ctx);
duk_require_callable(ctx, -1);
cons = duk_get_hobject(ctx, -1);
/* Result is a lightfunc or a callable actual function. */
DUK_ASSERT(cons == NULL || DUK_HOBJECT_IS_CALLABLE(cons));
if (cons != NULL && !DUK_HOBJECT_HAS_CONSTRUCTABLE(cons)) {
/* Check constructability from the final, non-bound object.
* The constructable flag is 1:1 for the bound function and
* its target so this should be sufficient. Lightfuncs are
* always constructable.
*/
goto not_constructable;
}
DUK_ASSERT(duk_is_callable(ctx, -1));
DUK_ASSERT(duk_is_lightfunc(ctx, -1) ||
(duk_get_hobject(ctx, -1) != NULL && !DUK_HOBJECT_HAS_BOUNDFUNC(duk_get_hobject(ctx, -1))));
/* [... constructor arg1 ... argN final_cons] */
/*
* Create "fallback" object to be used as the object instance,
* unless the constructor returns a replacement value.
* Its internal prototype needs to be set based on "prototype"
* property of the constructor.
*/
duk_push_object(ctx); /* class Object, extensible */
/* [... constructor arg1 ... argN final_cons fallback] */
duk_get_prop_stridx_short(ctx, -2, DUK_STRIDX_PROTOTYPE);
proto = duk_get_hobject(ctx, -1);
if (!proto) {
DUK_DDD(DUK_DDDPRINT("constructor has no 'prototype' property, or value not an object "
"-> leave standard Object prototype as fallback prototype"));
} else {
DUK_DDD(DUK_DDDPRINT("constructor has 'prototype' property with object value "
"-> set fallback prototype to that value: %!iO", (duk_heaphdr *) proto));
fallback = duk_known_hobject(ctx, -2);
DUK_ASSERT(fallback != NULL);
DUK_HOBJECT_SET_PROTOTYPE_UPDREF(thr, fallback, proto);
}
duk_pop(ctx);
#if 0 /* XXX: smaller alternative */
if (duk_is_object(ctx, -1)) {
DUK_DDD(DUK_DDDPRINT("constructor has 'prototype' property with object value "
"-> set fallback prototype to that value: %!iT", duk_get_tval(ctx, -1)));
duk_set_prototype(ctx, -2);
} else {
DUK_DDD(DUK_DDDPRINT("constructor has no 'prototype' property, or value not an object "
"-> leave standard Object prototype as fallback prototype"));
duk_pop(ctx);
}
#endif
/* [... constructor arg1 ... argN final_cons fallback] */
/*
* Manipulate value stack for the call.
*/
duk_dup_top(ctx);
duk_insert(ctx, idx_cons + 1); /* use fallback as 'this' value */
duk_insert(ctx, idx_cons); /* also stash it before constructor,
* in case we need it (as the fallback value)
*/
duk_pop(ctx); /* pop final_cons */
/* [... fallback constructor fallback(this) arg1 ... argN];
* Note: idx_cons points to first 'fallback', not 'constructor'.
*/
DUK_DDD(DUK_DDDPRINT("before call, idx_cons+1 (constructor) -> %!T, idx_cons+2 (fallback/this) -> %!T, "
"nargs=%ld, top=%ld",
(duk_tval *) duk_get_tval(ctx, idx_cons + 1),
(duk_tval *) duk_get_tval(ctx, idx_cons + 2),
(long) nargs,
(long) duk_get_top(ctx)));
/*
* Call the constructor function (called in "constructor mode").
*/
call_flags = DUK_CALL_FLAG_CONSTRUCTOR_CALL; /* not protected, respect reclimit, is a constructor call */
duk_handle_call_unprotected(thr, /* thread */
nargs, /* num_stack_args */
call_flags); /* call_flags */
/* [... fallback retval] */
DUK_DDD(DUK_DDDPRINT("constructor call finished, fallback=%!iT, retval=%!iT",
(duk_tval *) duk_get_tval(ctx, -2),
(duk_tval *) duk_get_tval(ctx, -1)));
/*
* Determine whether to use the constructor return value as the created
* object instance or not.
*/
if (duk_check_type_mask(ctx, -1, DUK_TYPE_MASK_OBJECT |
DUK_TYPE_MASK_BUFFER |
DUK_TYPE_MASK_LIGHTFUNC)) {
duk_remove_m2(ctx);
} else {
duk_pop(ctx);
idx_func = duk_get_top(ctx) - nargs - 1;
if (idx_func < 0 || nargs < 0) {
/* note that we can't reliably pop anything here */
DUK_ERROR_TYPE_INVALID_ARGS(thr);
}
/*
* Augment created errors upon creation (not when they are thrown or
* rethrown). __FILE__ and __LINE__ are not desirable here; the call
* stack reflects the caller which is correct.
*/
#if defined(DUK_USE_AUGMENT_ERROR_CREATE)
duk_hthread_sync_currpc(thr);
duk_err_augment_error_create(thr, thr, NULL, 0, 1 /*noblame_fileline*/);
#endif
/* [... retval] */
return;
duk_push_object(ctx); /* default instance; internal proto updated by call handling */
duk_insert(ctx, idx_func + 1);
not_constructable:
#if defined(DUK_USE_VERBOSE_ERRORS)
#if defined(DUK_USE_PARANOID_ERRORS)
DUK_ERROR_FMT1(thr, DUK_ERR_TYPE_ERROR, "%s not constructable", duk_get_type_name(ctx, -1));
#else
DUK_ERROR_FMT1(thr, DUK_ERR_TYPE_ERROR, "%s not constructable", duk_push_string_readable(ctx, -1));
#endif
#else
DUK_ERROR_TYPE(thr, "not constructable");
#endif
duk_handle_call_unprotected((duk_hthread *) ctx, nargs, DUK_CALL_FLAG_CONSTRUCTOR_CALL);
}
DUK_LOCAL duk_ret_t duk__pnew_helper(duk_context *ctx, void *udata) {
@ -447,11 +263,8 @@ DUK_EXTERNAL duk_int_t duk_pnew(duk_context *ctx, duk_idx_t nargs) {
DUK_ASSERT_CTX_VALID(ctx);
/* For now, just use duk_safe_call() to wrap duk_new(). We can't
* simply use a protected duk_handle_call() because there's post
* processing which might throw. It should be possible to ensure
* the post processing never throws (except in internal errors and
* out of memory etc which are always allowed) and then remove this
* wrapper.
* simply use a protected duk_handle_call() because pushing the
* default instance might throw.
*/
rc = duk_safe_call(ctx, duk__pnew_helper, (void *) &nargs /*udata*/, nargs + 1 /*nargs*/, 1 /*nrets*/);

23
src-input/duk_api_stack.c

@ -3733,6 +3733,20 @@ DUK_EXTERNAL duk_bool_t duk_is_function(duk_context *ctx, duk_idx_t idx) {
DUK_HOBJECT_FLAG_BOUNDFUNC);
}
DUK_EXTERNAL duk_bool_t duk_is_constructable(duk_context *ctx, duk_idx_t idx) {
duk_tval *tv;
DUK_ASSERT_CTX_VALID(ctx);
tv = duk_get_tval_or_unused(ctx, idx);
if (DUK_TVAL_IS_LIGHTFUNC(tv)) {
return 1;
}
return duk__obj_flag_any_default_false(ctx,
idx,
DUK_HOBJECT_FLAG_CONSTRUCTABLE);
}
DUK_EXTERNAL duk_bool_t duk_is_c_function(duk_context *ctx, duk_idx_t idx) {
DUK_ASSERT_CTX_VALID(ctx);
return duk__obj_flag_any_default_false(ctx,
@ -4866,7 +4880,7 @@ DUK_EXTERNAL duk_idx_t duk_push_error_object_va_raw(duk_context *ctx, duk_errcod
duk_hthread *thr = (duk_hthread *) ctx;
duk_hobject *proto;
#if defined(DUK_USE_AUGMENT_ERROR_CREATE)
duk_bool_t noblame_fileline;
duk_small_uint_t augment_flags;
#endif
DUK_ASSERT_CTX_VALID(ctx);
@ -4876,7 +4890,10 @@ DUK_EXTERNAL duk_idx_t duk_push_error_object_va_raw(duk_context *ctx, duk_errcod
/* Error code also packs a tracedata related flag. */
#if defined(DUK_USE_AUGMENT_ERROR_CREATE)
noblame_fileline = err_code & DUK_ERRCODE_FLAG_NOBLAME_FILELINE;
augment_flags = 0;
if (err_code & DUK_ERRCODE_FLAG_NOBLAME_FILELINE) {
augment_flags = DUK_AUGMENT_FLAG_NOBLAME_FILELINE;
}
#endif
err_code = err_code & (~DUK_ERRCODE_FLAG_NOBLAME_FILELINE);
@ -4908,7 +4925,7 @@ DUK_EXTERNAL duk_idx_t duk_push_error_object_va_raw(duk_context *ctx, duk_errcod
/* Creation time error augmentation */
#if defined(DUK_USE_AUGMENT_ERROR_CREATE)
/* filename may be NULL in which case file/line is not recorded */
duk_err_augment_error_create(thr, thr, filename, line, noblame_fileline); /* may throw an error */
duk_err_augment_error_create(thr, thr, filename, line, augment_flags); /* may throw an error */
#endif
return duk_get_top_index_unsafe(ctx);

2
src-input/duk_bi_error.c

@ -39,7 +39,7 @@ DUK_INTERNAL duk_ret_t duk_bi_error_constructor_shared(duk_context *ctx) {
#if defined(DUK_USE_AUGMENT_ERROR_CREATE)
if (!duk_is_constructor_call(ctx)) {
duk_err_augment_error_create(thr, thr, NULL, 0, 1 /*noblame_fileline*/);
duk_err_augment_error_create(thr, thr, NULL, 0, DUK_AUGMENT_FLAG_NOBLAME_FILELINE);
}
#endif

5
src-input/duk_error.h

@ -455,8 +455,11 @@ DUK_NORETURN(DUK_INTERNAL_DECL void duk_err_create_and_throw(duk_hthread *thr, d
DUK_NORETURN(DUK_INTERNAL_DECL void duk_error_throw_from_negative_rc(duk_hthread *thr, duk_ret_t rc));
#define DUK_AUGMENT_FLAG_NOBLAME_FILELINE (1U << 0) /* if set, don't blame C file/line for .fileName and .lineNumber */
#define DUK_AUGMENT_FLAG_SKIP_ONE (1U << 1) /* if set, skip topmost activation in traceback construction */
#if defined(DUK_USE_AUGMENT_ERROR_CREATE)
DUK_INTERNAL_DECL void duk_err_augment_error_create(duk_hthread *thr, duk_hthread *thr_callstack, const char *filename, duk_int_t line, duk_bool_t noblame_fileline);
DUK_INTERNAL_DECL void duk_err_augment_error_create(duk_hthread *thr, duk_hthread *thr_callstack, const char *filename, duk_int_t line, duk_small_uint_t flags);
#endif
#if defined(DUK_USE_AUGMENT_ERROR_THROW)
DUK_INTERNAL_DECL void duk_err_augment_error_throw(duk_hthread *thr);

51
src-input/duk_error_augment.c

@ -141,10 +141,10 @@ DUK_LOCAL void duk__err_augment_user(duk_hthread *thr, duk_small_uint_t stridx_c
*/
#if defined(DUK_USE_TRACEBACKS)
DUK_LOCAL void duk__add_traceback(duk_hthread *thr, duk_hthread *thr_callstack, const char *c_filename, duk_int_t c_line, duk_bool_t noblame_fileline) {
DUK_LOCAL void duk__add_traceback(duk_hthread *thr, duk_hthread *thr_callstack, const char *c_filename, duk_int_t c_line, duk_small_uint_t flags) {
duk_context *ctx = (duk_context *) thr;
duk_activation *act;
duk_small_uint_t depth;
duk_int_t depth;
duk_int_t arr_size;
duk_tval *tv;
duk_hstring *s;
@ -171,8 +171,20 @@ DUK_LOCAL void duk__add_traceback(duk_hthread *thr, duk_hthread *thr_callstack,
/* Preallocate array to correct size, so that we can just write out
* the _Tracedata values into the array part.
*/
act = thr->callstack_curr;
depth = DUK_USE_TRACEBACK_DEPTH;
arr_size = (duk_int_t) (thr_callstack->callstack_top <= depth ? thr_callstack->callstack_top : depth) * 2;
DUK_ASSERT(thr_callstack->callstack_top <= DUK_INT_MAX); /* callstack limits */
if (depth > (duk_int_t) thr_callstack->callstack_top) {
depth = (duk_int_t) thr_callstack->callstack_top;
}
if (depth > 0) {
if (flags & DUK_AUGMENT_FLAG_SKIP_ONE) {
DUK_ASSERT(act != NULL);
act = act->parent;
depth--;
}
}
arr_size = depth * 2;
if (thr->compile_ctx != NULL && thr->compile_ctx->h_filename != NULL) {
arr_size += 2;
}
@ -219,7 +231,7 @@ DUK_LOCAL void duk__add_traceback(duk_hthread *thr, duk_hthread *thr_callstack,
DUK_HSTRING_INCREF(thr, s);
tv++;
d = (noblame_fileline ? ((duk_double_t) DUK_TB_FLAG_NOBLAME_FILELINE) * DUK_DOUBLE_2TO32 : 0.0) +
d = ((flags & DUK_AUGMENT_FLAG_NOBLAME_FILELINE) ? ((duk_double_t) DUK_TB_FLAG_NOBLAME_FILELINE) * DUK_DOUBLE_2TO32 : 0.0) +
(duk_double_t) c_line;
DUK_TVAL_SET_DOUBLE(tv, d);
tv++;
@ -228,12 +240,7 @@ DUK_LOCAL void duk__add_traceback(duk_hthread *thr, duk_hthread *thr_callstack,
/* Traceback depth doesn't take into account the filename/line
* special handling above (intentional).
*/
DUK_ASSERT(thr_callstack->callstack_top <= DUK_INT_MAX); /* callstack limits */
depth = DUK_USE_TRACEBACK_DEPTH;
if (depth > thr_callstack->callstack_top) {
depth = (duk_small_uint_t) thr_callstack->callstack_top;
}
for (act = thr_callstack->callstack_curr; depth-- > 0; act = act->parent) {
for (; depth-- > 0; act = act->parent) {
duk_uint32_t pc;
duk_tval *tv_src;
@ -289,7 +296,7 @@ DUK_LOCAL void duk__add_traceback(duk_hthread *thr, duk_hthread *thr_callstack,
*/
#if defined(DUK_USE_AUGMENT_ERROR_CREATE) && !defined(DUK_USE_TRACEBACKS)
DUK_LOCAL void duk__add_fileline(duk_hthread *thr, duk_hthread *thr_callstack, const char *c_filename, duk_int_t c_line, duk_bool_t noblame_fileline) {
DUK_LOCAL void duk__add_fileline(duk_hthread *thr, duk_hthread *thr_callstack, const char *c_filename, duk_int_t c_line, duk_small_uint_t flags) {
duk_context *ctx;
#if defined(DUK_USE_ASSERTIONS)
duk_int_t entry_top;
@ -314,7 +321,7 @@ DUK_LOCAL void duk__add_fileline(duk_hthread *thr, duk_hthread *thr_callstack, c
*/
duk_push_uint(ctx, (duk_uint_t) thr->compile_ctx->curr_token.start_line);
duk_push_hstring(ctx, thr->compile_ctx->h_filename);
} else if (c_filename && !noblame_fileline) {
} else if (c_filename && (flags & DUK_AUGMENT_FLAG_NOBLAME_FILELINE) == 0) {
/* C call site gets blamed next, unless flagged not to do so.
* XXX: file/line is disabled in minimal builds, so disable this
* too when appropriate.
@ -447,7 +454,7 @@ DUK_LOCAL void duk__add_compiler_error_line(duk_hthread *thr) {
*/
#if defined(DUK_USE_AUGMENT_ERROR_CREATE)
DUK_LOCAL void duk__err_augment_builtin_create(duk_hthread *thr, duk_hthread *thr_callstack, const char *c_filename, duk_int_t c_line, duk_small_int_t noblame_fileline, duk_hobject *obj) {
DUK_LOCAL void duk__err_augment_builtin_create(duk_hthread *thr, duk_hthread *thr_callstack, const char *c_filename, duk_int_t c_line, duk_hobject *obj, duk_small_uint_t flags) {
duk_context *ctx = (duk_context *) thr;
#if defined(DUK_USE_ASSERTIONS)
duk_int_t entry_top;
@ -471,13 +478,13 @@ DUK_LOCAL void duk__err_augment_builtin_create(duk_hthread *thr, duk_hthread *th
if (duk_hobject_hasprop_raw(thr, obj, DUK_HTHREAD_STRING_INT_TRACEDATA(thr))) {
DUK_DDD(DUK_DDDPRINT("error value already has a '_Tracedata' property, not modifying it"));
} else {
duk__add_traceback(thr, thr_callstack, c_filename, c_line, noblame_fileline);
duk__add_traceback(thr, thr_callstack, c_filename, c_line, flags);
}
#else
/* Without tracebacks the concrete .fileName and .lineNumber need
* to be added directly.
*/
duk__add_fileline(thr, thr_callstack, c_filename, c_line, noblame_fileline);
duk__add_fileline(thr, thr_callstack, c_filename, c_line, flags);
#endif
#if defined(DUK_USE_ASSERTIONS)
@ -495,16 +502,14 @@ DUK_LOCAL void duk__err_augment_builtin_create(duk_hthread *thr, duk_hthread *th
* thr_callstack: thread which should be used for generating callstack etc.
* c_filename: C __FILE__ related to the error
* c_line: C __LINE__ related to the error
* noblame_fileline: if true, don't fileName/line as error source, otherwise use traceback
* (needed because user code filename/line are reported but internal ones
* are not)
*
* XXX: rename noblame_fileline to flags field; combine it to some existing
* field (there are only a few call sites so this may not be worth it).
* flags & DUK_AUGMENT_FLAG_NOBLAME_FILELINE:
* if true, don't fileName/line as error source, otherwise use traceback
* (needed because user code filename/line are reported but internal ones
* are not)
*/
#if defined(DUK_USE_AUGMENT_ERROR_CREATE)
DUK_INTERNAL void duk_err_augment_error_create(duk_hthread *thr, duk_hthread *thr_callstack, const char *c_filename, duk_int_t c_line, duk_bool_t noblame_fileline) {
DUK_INTERNAL void duk_err_augment_error_create(duk_hthread *thr, duk_hthread *thr_callstack, const char *c_filename, duk_int_t c_line, duk_small_uint_t flags) {
duk_context *ctx = (duk_context *) thr;
duk_hobject *obj;
@ -541,7 +546,7 @@ DUK_INTERNAL void duk_err_augment_error_create(duk_hthread *thr, duk_hthread *th
}
if (DUK_HOBJECT_HAS_EXTENSIBLE(obj)) {
DUK_DDD(DUK_DDDPRINT("error meets criteria, built-in augment"));
duk__err_augment_builtin_create(thr, thr_callstack, c_filename, c_line, noblame_fileline, obj);
duk__err_augment_builtin_create(thr, thr_callstack, c_filename, c_line, obj, flags);
} else {
DUK_DDD(DUK_DDDPRINT("error does not meet criteria, no built-in augment"));
}

1
src-input/duk_js.h

@ -97,6 +97,7 @@ DUK_INTERNAL_DECL duk_int_t duk_handle_call_protected(duk_hthread *thr, duk_idx_
DUK_INTERNAL_DECL void duk_handle_call_unprotected(duk_hthread *thr, duk_idx_t num_stack_args, 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 duk_bool_t duk_handle_ecma_call_setup(duk_hthread *thr, duk_idx_t num_stack_args, duk_small_uint_t call_flags);
DUK_INTERNAL_DECL void duk_call_construct_postprocess(duk_context *ctx);
/* bytecode execution */
DUK_INTERNAL_DECL void duk_js_execute_bytecode(duk_hthread *exec_thr);

257
src-input/duk_js_call.c

@ -482,6 +482,96 @@ DUK_LOCAL void duk__handle_createargs_for_call(duk_hthread *thr,
/* [ ... arg1 ... argN envobj ] */
}
/*
* Helpers for constructor call handling.
*
* There are two [[Construct]] operations in the specification:
*
* - E5 Section 13.2.2: for Function objects
* - E5 Section 15.3.4.5.2: for "bound" Function objects
*
* The chain of bound functions is resolved in Section 15.3.4.5.2,
* with arguments "piling up" until the [[Construct]] internal
* method is called on the final, actual Function object. Note
* that the "prototype" property is looked up *only* from the
* final object, *before* calling the constructor.
*
* Since Duktape 2.2 bound functions are represented with the
* duk_hboundfunc internal type, and bound function chains are
* collapsed when a bound function is created. As a result, the
* direct target of a duk_hboundfunc is always non-bound and the
* this/argument lists have been resolved.
*
* When constructing new Array instances, an unnecessary object is
* created and discarded now: the standard [[Construct]] creates an
* object, and calls the Array constructor. The Array constructor
* returns an Array instance, which is used as the result value for
* the "new" operation; the object created before the Array constructor
* call is discarded.
*
* This would be easy to fix, e.g. by knowing that the Array constructor
* will always create a replacement object and skip creating the fallback
* object in that case.
*/
/* Update default instance prototype for constructor call. */
DUK_LOCAL void duk__update_default_instance_proto(duk_context *ctx, duk_idx_t idx_func) {
duk_hthread *thr = (duk_hthread *) ctx;
duk_hobject *proto;
duk_hobject *fallback;
DUK_ASSERT(duk_is_constructable(ctx, idx_func));
duk_get_prop_stridx_short(ctx, idx_func, DUK_STRIDX_PROTOTYPE);
proto = duk_get_hobject(ctx, -1);
if (!proto) {
DUK_DDD(DUK_DDDPRINT("constructor has no 'prototype' property, or value not an object "
"-> leave standard Object prototype as fallback prototype"));
} else {
DUK_DDD(DUK_DDDPRINT("constructor has 'prototype' property with object value "
"-> set fallback prototype to that value: %!iO", (duk_heaphdr *) proto));
/* Original fallback (default instance) is untouched when
* resolving bound functions etc.
*/
fallback = duk_known_hobject(ctx, idx_func + 1);
DUK_ASSERT(fallback != NULL);
DUK_HOBJECT_SET_PROTOTYPE_UPDREF(thr, fallback, proto);
}
duk_pop(ctx);
}
/* Postprocess: return value special handling, error augmentation. */
DUK_INTERNAL void duk_call_construct_postprocess(duk_context *ctx) {
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.
*/
if (!duk_check_type_mask(ctx, -1, DUK_TYPE_MASK_OBJECT |
DUK_TYPE_MASK_BUFFER |
DUK_TYPE_MASK_LIGHTFUNC)) {
/* XXX: direct value stack access */
duk_pop(ctx);
duk_push_this(ctx);
}
DUK_UNREF(thr);
#if defined(DUK_USE_AUGMENT_ERROR_CREATE)
/* Augment created errors upon creation, not when they are thrown or
* rethrown. __FILE__ and __LINE__ are not desirable here; the call
* stack reflects the caller which is correct. Skip topmost, unwound
* activation when creating a traceback. If thr->ptr_curr_pc was !=
* NULL we'd need to sync the current PC so that the traceback comes
* out right; however it is always synced here so just assert for it.
*/
DUK_ASSERT(thr->ptr_curr_pc == NULL);
duk_err_augment_error_create(thr, thr, NULL, 0, DUK_AUGMENT_FLAG_NOBLAME_FILELINE |
DUK_AUGMENT_FLAG_SKIP_ONE);
#endif
}
/*
* Helper for handling a bound function when a call is being made.
*
@ -876,7 +966,7 @@ DUK_LOCAL DUK_INLINE void duk__coerce_nonstrict_this_binding(duk_hthread *thr, d
}
}
DUK_LOCAL DUK_ALWAYS_INLINE duk_bool_t duk__resolve_target_fastpath_check(duk_context *ctx, duk_idx_t idx_func, duk_tval *out_tv_func, duk_hobject **out_func) {
DUK_LOCAL DUK_ALWAYS_INLINE duk_bool_t duk__resolve_target_fastpath_check(duk_context *ctx, duk_idx_t idx_func, duk_tval *out_tv_func, duk_hobject **out_func, duk_small_uint_t call_flags) {
#if defined(DUK_USE_PREFER_SIZE)
DUK_UNREF(ctx);
DUK_UNREF(idx_func);
@ -886,6 +976,10 @@ DUK_LOCAL DUK_ALWAYS_INLINE duk_bool_t duk__resolve_target_fastpath_check(duk_co
duk_tval *tv_func;
duk_hobject *func;
if (call_flags & DUK_CALL_FLAG_CONSTRUCTOR_CALL) {
return 0;
}
tv_func = DUK_GET_TVAL_POSIDX(ctx, idx_func);
DUK_ASSERT(tv_func != NULL);
@ -951,6 +1045,16 @@ 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_CONSTRUCTOR_CALL) {
/* Check for constructability and update
* default instance prototype.
*/
if (!DUK_HOBJECT_HAS_CONSTRUCTABLE(func)) {
goto not_constructable;
}
duk__update_default_instance_proto(ctx, idx_func);
}
break;
}
@ -975,7 +1079,10 @@ DUK_LOCAL duk_hobject *duk__resolve_target_func_and_this_binding(duk_context *ct
}
/* Retry loop. */
} else if (DUK_TVAL_IS_LIGHTFUNC(tv_func)) {
/* Lightfuncs are strict, so no 'this' coercion. */
/* Lightfuncs are strict, so no 'this' coercion.
* Lightfuncs are also always constructable, so no
* constructability check.
*/
DUK_TVAL_SET_TVAL(out_tv_func, tv_func);
func = NULL;
break;
@ -1003,6 +1110,8 @@ DUK_LOCAL duk_hobject *duk__resolve_target_func_and_this_binding(duk_context *ct
DUK_ASSERT(func == NULL || !DUK_HOBJECT_HAS_BOUNDFUNC(func));
DUK_ASSERT(func == NULL || (DUK_HOBJECT_IS_COMPFUNC(func) ||
DUK_HOBJECT_IS_NATFUNC(func)));
DUK_ASSERT(func == NULL || (DUK_HOBJECT_HAS_CONSTRUCTABLE(func) ||
(call_flags & DUK_CALL_FLAG_CONSTRUCTOR_CALL) == 0));
}
#endif
@ -1010,10 +1119,27 @@ DUK_LOCAL duk_hobject *duk__resolve_target_func_and_this_binding(duk_context *ct
not_callable_error:
DUK_ASSERT(tv_func != NULL);
#if defined(DUK_USE_VERBOSE_ERRORS)
#if defined(DUK_USE_PARANOID_ERRORS)
DUK_ERROR_TYPE(thr, DUK_STR_NOT_CALLABLE);
DUK_ERROR_FMT1(thr, DUK_ERR_TYPE_ERROR, "%s not callable", duk_get_type_name(ctx, -1));
#else
DUK_ERROR_FMT1(thr, DUK_ERR_TYPE_ERROR, "%s not callable", duk_push_string_tval_readable(ctx, tv_func));
#endif
#else
DUK_ERROR_TYPE(thr, DUK_STR_NOT_CALLABLE);
#endif
DUK_UNREACHABLE();
return NULL; /* never executed */
not_constructable:
#if defined(DUK_USE_VERBOSE_ERRORS)
#if defined(DUK_USE_PARANOID_ERRORS)
DUK_ERROR_FMT1(thr, DUK_ERR_TYPE_ERROR, "%s not constructable", duk_get_type_name(ctx, -1));
#else
DUK_ERROR_FMT1(thr, DUK_ERR_TYPE_ERROR, "%s not constructable", duk_push_string_readable(ctx, -1));
#endif
#else
DUK_ERROR_TYPE(thr, DUK_STR_NOT_CONSTRUCTABLE);
#endif
DUK_UNREACHABLE();
return NULL; /* never executed */
@ -1462,7 +1588,7 @@ DUK_LOCAL void duk__handle_call_inner(duk_hthread *thr,
* we must resolve a possible bound function first.
*/
if (duk__resolve_target_fastpath_check(ctx, idx_func, &tv_func_copy, &func)) {
if (duk__resolve_target_fastpath_check(ctx, idx_func, &tv_func_copy, &func, call_flags)) {
DUK_DDD(DUK_DDDPRINT("fast path target resolve"));
} else {
DUK_DDD(DUK_DDDPRINT("slow path target resolve"));
@ -1474,6 +1600,14 @@ DUK_LOCAL void duk__handle_call_inner(duk_hthread *thr,
DUK_ASSERT(func == NULL || (DUK_HOBJECT_IS_COMPFUNC(func) ||
DUK_HOBJECT_IS_NATFUNC(func)));
if (call_flags & DUK_CALL_FLAG_CONSTRUCTOR_CALL) {
/* Update default instance internal prototype based on the
* .prototype property of the final, non-bound target
* function.
*/
duk__update_default_instance_proto(ctx, idx_func);
}
/* [ ... func this arg1 ... argN ] */
/*
@ -1710,9 +1844,6 @@ DUK_LOCAL void duk__handle_call_inner(duk_hthread *thr,
* Ecmascript call
*/
duk_tval *tv_ret;
duk_tval *tv_funret;
DUK_ASSERT(func != NULL);
DUK_ASSERT(DUK_HOBJECT_HAS_COMPFUNC(func));
act->curr_pc = DUK_HCOMPFUNC_GET_CODE_BASE(thr->heap, (duk_hcompfunc *) func);
@ -1742,42 +1873,11 @@ DUK_LOCAL void duk__handle_call_inner(duk_hthread *thr,
DUK_DDD(DUK_DDDPRINT("entering bytecode execution"));
duk_js_execute_bytecode(thr);
DUK_DDD(DUK_DDDPRINT("returned from bytecode execution"));
/* Unwind. */
DUK_ASSERT(thr->callstack_curr != NULL);
DUK_ASSERT(thr->callstack_curr->parent == entry_act);
DUK_ASSERT(thr->callstack_top == entry_callstack_top + 1);
duk_hthread_activation_unwind_norz(thr);
DUK_ASSERT(thr->callstack_curr == entry_act);
DUK_ASSERT(thr->callstack_top == entry_callstack_top);
thr->valstack_bottom = (duk_tval *) (void *) ((duk_uint8_t *) thr->valstack + entry_valstack_bottom_byteoff);
/* keep current valstack_top */
DUK_ASSERT(thr->valstack_bottom >= thr->valstack);
DUK_ASSERT(thr->valstack_top >= thr->valstack_bottom);
DUK_ASSERT(thr->valstack_end >= thr->valstack_top);
DUK_ASSERT(thr->valstack_top - thr->valstack_bottom >= idx_func + 1);
/* Return value handling. */
/* [ ... func this (crud) retval ] */
tv_ret = thr->valstack_bottom + idx_func;
tv_funret = thr->valstack_top - 1;
#if defined(DUK_USE_FASTINT)
/* Explicit check for fastint downgrade. */
DUK_TVAL_CHKFAST_INPLACE_FAST(tv_funret);
#endif
DUK_TVAL_SET_TVAL_UPDREF(thr, tv_ret, tv_funret); /* side effects */
} else {
/*
* Native call.
*/
duk_tval *tv_ret;
duk_tval *tv_funret;
thr->valstack_bottom = thr->valstack_bottom + idx_func + 2;
/* keep current valstack_top */
DUK_ASSERT(thr->valstack_bottom >= thr->valstack);
@ -1799,48 +1899,61 @@ DUK_LOCAL void duk__handle_call_inner(duk_hthread *thr,
/* Automatic error throwing, retval check. */
if (rc < 0) {
if (rc == 0) {
DUK_ASSERT(thr->valstack < thr->valstack_end);
DUK_ASSERT(DUK_TVAL_IS_UNDEFINED(thr->valstack_top));
thr->valstack_top++;
} else if (rc == 1) {
;
} else if (rc < 0) {
duk_error_throw_from_negative_rc(thr, rc);
DUK_UNREACHABLE();
} else if (rc > 1) {
} else {
DUK_ERROR_TYPE(thr, "c function returned invalid rc");
}
DUK_ASSERT(rc == 0 || rc == 1);
}
DUK_ASSERT(thr->ptr_curr_pc == NULL);
/* Unwind. */
/* Constructor call post processing. */
DUK_ASSERT(thr->callstack_curr != NULL);
DUK_ASSERT(thr->callstack_curr->parent == entry_act);
DUK_ASSERT(thr->callstack_top == entry_callstack_top + 1);
duk_hthread_activation_unwind_norz(thr);
DUK_ASSERT(thr->callstack_curr == entry_act);
DUK_ASSERT(thr->callstack_top == entry_callstack_top);
if (call_flags & DUK_CALL_FLAG_CONSTRUCTOR_CALL) {
duk_call_construct_postprocess(ctx);
}
thr->valstack_bottom = (duk_tval *) (void *) ((duk_uint8_t *) thr->valstack + entry_valstack_bottom_byteoff);
/* keep current valstack_top */
DUK_ASSERT(thr->valstack_bottom >= thr->valstack);
DUK_ASSERT(thr->valstack_top >= thr->valstack_bottom);
DUK_ASSERT(thr->valstack_end >= thr->valstack_top);
DUK_ASSERT(thr->valstack_top - thr->valstack_bottom >= idx_func + 1);
/* Unwind. */
DUK_ASSERT(thr->callstack_curr != NULL);
DUK_ASSERT(thr->callstack_curr->parent == entry_act);
DUK_ASSERT(thr->callstack_top == entry_callstack_top + 1);
duk_hthread_activation_unwind_norz(thr);
DUK_ASSERT(thr->callstack_curr == entry_act);
DUK_ASSERT(thr->callstack_top == entry_callstack_top);
thr->valstack_bottom = (duk_tval *) (void *) ((duk_uint8_t *) thr->valstack + entry_valstack_bottom_byteoff);
/* keep current valstack_top */
DUK_ASSERT(thr->valstack_bottom >= thr->valstack);
DUK_ASSERT(thr->valstack_top >= thr->valstack_bottom);
DUK_ASSERT(thr->valstack_end >= thr->valstack_top);
DUK_ASSERT(thr->valstack_top - thr->valstack_bottom >= idx_func + 1);
/* Return value handling. */
/* Return value handling. */
/* [ ... func this (crud) retval ] */
{
duk_tval *tv_ret;
duk_tval *tv_funret;
/* XXX: should this happen in the callee's activation or after unwinding? */
tv_ret = thr->valstack_bottom + idx_func;
if (rc == 0) {
DUK_TVAL_SET_UNDEFINED_UPDREF(thr, tv_ret); /* side effects */
} else {
/* [ ... func this (crud) retval ] */
tv_funret = thr->valstack_top - 1;
tv_funret = thr->valstack_top - 1;
#if defined(DUK_USE_FASTINT)
/* Explicit check for fastint downgrade. */
DUK_TVAL_CHKFAST_INPLACE_FAST(tv_funret);
/* Explicit check for fastint downgrade. */
DUK_TVAL_CHKFAST_INPLACE_FAST(tv_funret);
#endif
DUK_TVAL_SET_TVAL_UPDREF(thr, tv_ret, tv_funret); /* side effects */
}
DUK_TVAL_SET_TVAL_UPDREF(thr, tv_ret, tv_funret); /* side effects */
}
duk_set_top(ctx, idx_func + 1); /* XXX: unnecessary, handle in adjust */
duk_set_top(ctx, idx_func + 1);
/* [ ... retval ] */
@ -2598,7 +2711,7 @@ DUK_INTERNAL duk_bool_t duk_handle_ecma_call_setup(duk_hthread *thr,
* a plain function call.
*/
if (duk__resolve_target_fastpath_check(ctx, idx_func, &tv_func_ignore, &func)) {
if (duk__resolve_target_fastpath_check(ctx, idx_func, &tv_func_ignore, &func, call_flags)) {
DUK_DDD(DUK_DDDPRINT("fast path target resolve"));
} else {
DUK_DDD(DUK_DDDPRINT("slow path target resolve"));
@ -2615,6 +2728,10 @@ DUK_INTERNAL duk_bool_t duk_handle_ecma_call_setup(duk_hthread *thr,
DUK_ASSERT(!DUK_HOBJECT_HAS_BOUNDFUNC(func));
DUK_ASSERT(DUK_HOBJECT_IS_COMPFUNC(func));
if (call_flags & DUK_CALL_FLAG_CONSTRUCTOR_CALL) {
duk__update_default_instance_proto(ctx, idx_func);
}
nargs = ((duk_hcompfunc *) func)->nargs;
nregs = ((duk_hcompfunc *) func)->nregs;
DUK_ASSERT(nregs >= 0);
@ -2741,6 +2858,9 @@ DUK_INTERNAL duk_bool_t duk_handle_ecma_call_setup(duk_hthread *thr,
act->flags = (DUK_HOBJECT_HAS_STRICT(func) ?
DUK_ACT_FLAG_STRICT | DUK_ACT_FLAG_TAILCALLED :
DUK_ACT_FLAG_TAILCALLED);
if (call_flags & DUK_CALL_FLAG_CONSTRUCTOR_CALL) {
act->flags |= DUK_ACT_FLAG_CONSTRUCT;
}
DUK_ASSERT(DUK_ACT_GET_FUNC(act) == func); /* already updated */
DUK_ASSERT(act->var_env == NULL);
@ -2813,6 +2933,9 @@ DUK_INTERNAL duk_bool_t duk_handle_ecma_call_setup(duk_hthread *thr,
act->flags = (DUK_HOBJECT_HAS_STRICT(func) ?
DUK_ACT_FLAG_STRICT :
0);
if (call_flags & DUK_CALL_FLAG_CONSTRUCTOR_CALL) {
act->flags |= DUK_ACT_FLAG_CONSTRUCT;
}
act->func = func;
act->var_env = NULL;
act->lex_env = NULL;

49
src-input/duk_js_executor.c

@ -42,7 +42,7 @@ DUK_LOCAL_DECL void duk__js_execute_bytecode_inner(duk_hthread *entry_thread, du
} while (0)
/* XXX: candidate of being an internal shared API call */
#if !defined(DUK_USE_EXEC_PREFER_SIZE)
#if 0 /* unused */
DUK_LOCAL void duk__push_tvals_incref_only(duk_hthread *thr, duk_tval *tv_src, duk_small_uint_fast_t count) {
duk_tval *tv_dst;
duk_size_t copy_size;
@ -1602,7 +1602,8 @@ DUK_LOCAL duk_small_uint_t duk__handle_return(duk_hthread *thr, duk_activation *
}
if (act == entry_act) {
/* Return to the bytecode executor caller who will unwind stacks.
/* Return to the bytecode executor caller who will unwind stacks
* and handle constructor post-processing.
* Return value is already on the stack top: [ ... retval ].
*/
@ -1614,7 +1615,6 @@ DUK_LOCAL duk_small_uint_t duk__handle_return(duk_hthread *thr, duk_activation *
/* There is a caller; it MUST be an Ecmascript caller (otherwise it would
* match entry_act check).
*/
DUK_DDD(DUK_DDDPRINT("return to Ecmascript caller, retval_byteoff=%ld, lj_value1=%!T",
(long) (thr->callstack_curr->parent->retval_byteoff),
(duk_tval *) &thr->heap->lj.value1));
@ -1623,6 +1623,10 @@ 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 (thr->callstack_curr->flags & DUK_ACT_FLAG_CONSTRUCT) {
duk_call_construct_postprocess((duk_context *) thr); /* side effects */
}
tv1 = (duk_tval *) (void *) ((duk_uint8_t *) thr->valstack + thr->callstack_curr->parent->retval_byteoff);
DUK_ASSERT(thr->valstack_top - 1 >= thr->valstack_bottom);
tv2 = thr->valstack_top - 1;
@ -1630,6 +1634,7 @@ DUK_LOCAL duk_small_uint_t duk__handle_return(duk_hthread *thr, duk_activation *
/* Catch stack unwind happens inline in callstack unwind. */
duk_hthread_activation_unwind_norz(thr);
duk__reconfig_valstack_ecma_return(thr);
DUK_DD(DUK_DDPRINT("-> return not intercepted, restart execution in caller"));
@ -2446,7 +2451,7 @@ DUK_LOCAL DUK__NOINLINE_PERF duk_small_uint_t duk__handle_op_endfin(duk_hthread
duk_small_uint_t cont_type;
duk_small_uint_t ret_result;
DUK_ASSERT(thr->ptr_curr_pc == NULL);
DUK_ASSERT(thr->callstack_top >= 1);
act = thr->callstack_curr;
DUK_ASSERT(act != NULL);
@ -4358,6 +4363,7 @@ DUK_LOCAL DUK_NOINLINE DUK_HOT void duk__js_execute_bytecode_inner(duk_hthread *
* for potential out-of-memory situations which will then \
* propagate out of the executor longjmp handler. \
*/ \
DUK_ASSERT(thr->ptr_curr_pc == NULL); \
ret_result = duk__handle_return(thr, entry_act); \
if (ret_result == DUK__RETHAND_RESTART) { \
goto restart_execution; \
@ -4743,14 +4749,10 @@ DUK_LOCAL DUK_NOINLINE DUK_HOT void duk__js_execute_bytecode_inner(duk_hthread *
duk_context *ctx = (duk_context *) thr;
duk_small_uint_fast_t a = DUK_DEC_A(ins);
duk_small_uint_fast_t bc = DUK_DEC_BC(ins);
#if defined(DUK_USE_EXEC_PREFER_SIZE)
#if !defined(DUK_USE_EXEC_FUN_LOCAL)
duk_hcompfunc *fun;
#endif
#else
duk_small_uint_fast_t count;
duk_tval *tv_src;
#endif
duk_idx_t num_stack_args;
/* A -> num args (N)
* BC -> target register and start reg: constructor, arg1, ..., argN
@ -4765,13 +4767,18 @@ DUK_LOCAL DUK_NOINLINE DUK_HOT void duk__js_execute_bytecode_inner(duk_hthread *
* when it augments the created error.
*/
#if defined(DUK_USE_EXEC_PREFER_SIZE)
/* This alternative relies on our being allowed to trash anything
* above 'bc' so we can just reuse the argument registers which
* means smaller value stack use. Footprint is a bit smaller.
*/
duk_set_top(ctx, (duk_idx_t) (bc + a + 1));
duk_new(ctx, (duk_idx_t) a); /* [... constructor arg1 ... argN] -> [retval] */
duk_push_object(ctx); /* default instance; internal proto updated by call handling */
duk_insert(ctx, bc + 1);
if (duk_handle_ecma_call_setup(thr, a, DUK_CALL_FLAG_CONSTRUCTOR_CALL)) {
/* curr_pc synced by duk_handle_ecma_call_setup() */
goto restart_execution;
}
/* Recompute argument count: bound function handling may have shifted. */
num_stack_args = duk_get_top(ctx) - (bc + 2);
DUK_DDD(DUK_DDDPRINT("recomputed arg count: %ld\n", (long) num_stack_args));
duk_handle_call_unprotected(thr, num_stack_args, DUK_CALL_FLAG_CONSTRUCTOR_CALL /*call_flags*/);
/* The return value is already in its correct place at the stack,
* i.e. it has replaced the 'constructor' at index bc. Just reset
@ -4782,18 +4789,6 @@ DUK_LOCAL DUK_NOINLINE DUK_HOT void duk__js_execute_bytecode_inner(duk_hthread *
fun = DUK__FUN();
#endif
duk_set_top(ctx, (duk_idx_t) fun->nregs);
#else /* DUK_USE_EXEC_PREFER_SIZE */
/* Faster alternative is to duplicate the values to avoid a resize.
* This depends on the relative size between the value stack and
* the argument count, though.
*/
count = a + 1;
duk_require_stack(ctx, count);
tv_src = DUK_GET_TVAL_POSIDX(ctx, bc);
duk__push_tvals_incref_only(thr, tv_src, count);
duk_new(ctx, (duk_idx_t) a); /* [... constructor arg1 ... argN] -> [retval] */
duk_replace(ctx, bc);
#endif /* DUK_USE_EXEC_PREFER_SIZE */
/* When debugger is enabled, we need to recheck the activation
* status after returning. This is now handled by call handling

2
src-input/duktape.h.in

@ -622,6 +622,8 @@ DUK_EXTERNAL_DECL duk_bool_t duk_is_thread(duk_context *ctx, duk_idx_t idx);
#define duk_is_callable(ctx,idx) \
duk_is_function((ctx), (idx))
DUK_EXTERNAL_DECL duk_bool_t duk_is_constructable(duk_context *ctx, duk_idx_t idx);
DUK_EXTERNAL_DECL duk_bool_t duk_is_dynamic_buffer(duk_context *ctx, duk_idx_t idx);
DUK_EXTERNAL_DECL duk_bool_t duk_is_fixed_buffer(duk_context *ctx, duk_idx_t idx);
DUK_EXTERNAL_DECL duk_bool_t duk_is_external_buffer(duk_context *ctx, duk_idx_t idx);

Loading…
Cancel
Save