Skip to content

Commit cd08ff8

Browse files
committed
Allow yield from constructor calls
1 parent 37aaf9d commit cd08ff8

9 files changed

Lines changed: 279 additions & 320 deletions

File tree

src-input/duk_api_call.c

Lines changed: 11 additions & 198 deletions
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ DUK_EXTERNAL duk_int_t duk_pcall(duk_context *ctx, duk_idx_t nargs) {
125125
return DUK_EXEC_ERROR; /* unreachable */
126126
}
127127

128-
/* awkward; we assume there is space for this */
128+
/* Rely on the internal value stack reserve for these operations. */
129129
duk_push_undefined(ctx);
130130
duk_insert(ctx, idx_func + 1);
131131

@@ -230,205 +230,21 @@ DUK_EXTERNAL duk_int_t duk_safe_call(duk_context *ctx, duk_safe_call_function fu
230230
}
231231

232232
DUK_EXTERNAL void duk_new(duk_context *ctx, duk_idx_t nargs) {
233-
/*
234-
* There are two [[Construct]] operations in the specification:
235-
*
236-
* - E5 Section 13.2.2: for Function objects
237-
* - E5 Section 15.3.4.5.2: for "bound" Function objects
238-
*
239-
* The chain of bound functions is resolved in Section 15.3.4.5.2,
240-
* with arguments "piling up" until the [[Construct]] internal
241-
* method is called on the final, actual Function object. Note
242-
* that the "prototype" property is looked up *only* from the
243-
* final object, *before* calling the constructor.
244-
*
245-
* Since Duktape 2.2 bound functions are represented with the
246-
* duk_hboundfunc internal type, and bound function chains are
247-
* collapsed when a bound function is created. As a result, the
248-
* direct target of a duk_hboundfunc is always non-bound and the
249-
* this/argument lists have been resolved.
250-
*
251-
* When constructing new Array instances, an unnecessary object is
252-
* created and discarded now: the standard [[Construct]] creates an
253-
* object, and calls the Array constructor. The Array constructor
254-
* returns an Array instance, which is used as the result value for
255-
* the "new" operation; the object created before the Array constructor
256-
* call is discarded.
257-
*
258-
* This would be easy to fix, e.g. by knowing that the Array constructor
259-
* will always create a replacement object and skip creating the fallback
260-
* object in that case.
261-
*
262-
* Note: functions called via "new" need to know they are called as a
263-
* constructor. For instance, built-in constructors behave differently
264-
* depending on how they are called.
265-
*/
266-
267-
/* XXX: merge this with duk_js_call.c, as this function implements
268-
* core semantics (or perhaps merge the two files altogether).
269-
*/
270-
271233
duk_hthread *thr = (duk_hthread *) ctx;
272-
duk_hobject *proto;
273-
duk_hobject *cons;
274-
duk_hobject *fallback;
275-
duk_idx_t idx_cons;
276-
duk_small_uint_t call_flags;
234+
duk_idx_t idx_func;
277235

278236
DUK_ASSERT_CTX_VALID(ctx);
279237

280-
/* [... constructor arg1 ... argN] */
281-
282-
idx_cons = duk_require_normalize_index(ctx, -nargs - 1);
283-
284-
DUK_DDD(DUK_DDDPRINT("top=%ld, nargs=%ld, idx_cons=%ld",
285-
(long) duk_get_top(ctx), (long) nargs, (long) idx_cons));
286-
287-
/* XXX: code duplication */
288-
289-
/*
290-
* Figure out the final, non-bound constructor, to get "prototype"
291-
* property.
292-
*/
293-
294-
duk_dup(ctx, idx_cons);
295-
duk_resolve_nonbound_function(ctx);
296-
duk_require_callable(ctx, -1);
297-
cons = duk_get_hobject(ctx, -1);
298-
299-
/* Result is a lightfunc or a callable actual function. */
300-
DUK_ASSERT(cons == NULL || DUK_HOBJECT_IS_CALLABLE(cons));
301-
if (cons != NULL && !DUK_HOBJECT_HAS_CONSTRUCTABLE(cons)) {
302-
/* Check constructability from the final, non-bound object.
303-
* The constructable flag is 1:1 for the bound function and
304-
* its target so this should be sufficient. Lightfuncs are
305-
* always constructable.
306-
*/
307-
goto not_constructable;
308-
}
309-
310-
DUK_ASSERT(duk_is_callable(ctx, -1));
311-
DUK_ASSERT(duk_is_lightfunc(ctx, -1) ||
312-
(duk_get_hobject(ctx, -1) != NULL && !DUK_HOBJECT_HAS_BOUNDFUNC(duk_get_hobject(ctx, -1))));
313-
314-
/* [... constructor arg1 ... argN final_cons] */
315-
316-
/*
317-
* Create "fallback" object to be used as the object instance,
318-
* unless the constructor returns a replacement value.
319-
* Its internal prototype needs to be set based on "prototype"
320-
* property of the constructor.
321-
*/
322-
323-
duk_push_object(ctx); /* class Object, extensible */
324-
325-
/* [... constructor arg1 ... argN final_cons fallback] */
326-
327-
duk_get_prop_stridx_short(ctx, -2, DUK_STRIDX_PROTOTYPE);
328-
proto = duk_get_hobject(ctx, -1);
329-
if (!proto) {
330-
DUK_DDD(DUK_DDDPRINT("constructor has no 'prototype' property, or value not an object "
331-
"-> leave standard Object prototype as fallback prototype"));
332-
} else {
333-
DUK_DDD(DUK_DDDPRINT("constructor has 'prototype' property with object value "
334-
"-> set fallback prototype to that value: %!iO", (duk_heaphdr *) proto));
335-
fallback = duk_known_hobject(ctx, -2);
336-
DUK_ASSERT(fallback != NULL);
337-
DUK_HOBJECT_SET_PROTOTYPE_UPDREF(thr, fallback, proto);
338-
}
339-
duk_pop(ctx);
340-
341-
#if 0 /* XXX: smaller alternative */
342-
if (duk_is_object(ctx, -1)) {
343-
DUK_DDD(DUK_DDDPRINT("constructor has 'prototype' property with object value "
344-
"-> set fallback prototype to that value: %!iT", duk_get_tval(ctx, -1)));
345-
duk_set_prototype(ctx, -2);
346-
} else {
347-
DUK_DDD(DUK_DDDPRINT("constructor has no 'prototype' property, or value not an object "
348-
"-> leave standard Object prototype as fallback prototype"));
349-
duk_pop(ctx);
350-
}
351-
#endif
352-
353-
/* [... constructor arg1 ... argN final_cons fallback] */
354-
355-
/*
356-
* Manipulate value stack for the call.
357-
*/
358-
359-
duk_dup_top(ctx);
360-
duk_insert(ctx, idx_cons + 1); /* use fallback as 'this' value */
361-
duk_insert(ctx, idx_cons); /* also stash it before constructor,
362-
* in case we need it (as the fallback value)
363-
*/
364-
duk_pop(ctx); /* pop final_cons */
365-
366-
367-
/* [... fallback constructor fallback(this) arg1 ... argN];
368-
* Note: idx_cons points to first 'fallback', not 'constructor'.
369-
*/
370-
371-
DUK_DDD(DUK_DDDPRINT("before call, idx_cons+1 (constructor) -> %!T, idx_cons+2 (fallback/this) -> %!T, "
372-
"nargs=%ld, top=%ld",
373-
(duk_tval *) duk_get_tval(ctx, idx_cons + 1),
374-
(duk_tval *) duk_get_tval(ctx, idx_cons + 2),
375-
(long) nargs,
376-
(long) duk_get_top(ctx)));
377-
378-
/*
379-
* Call the constructor function (called in "constructor mode").
380-
*/
381-
382-
call_flags = DUK_CALL_FLAG_CONSTRUCTOR_CALL; /* not protected, respect reclimit, is a constructor call */
383-
384-
duk_handle_call_unprotected(thr, /* thread */
385-
nargs, /* num_stack_args */
386-
call_flags); /* call_flags */
387-
388-
/* [... fallback retval] */
389-
390-
DUK_DDD(DUK_DDDPRINT("constructor call finished, fallback=%!iT, retval=%!iT",
391-
(duk_tval *) duk_get_tval(ctx, -2),
392-
(duk_tval *) duk_get_tval(ctx, -1)));
393-
394-
/*
395-
* Determine whether to use the constructor return value as the created
396-
* object instance or not.
397-
*/
398-
399-
if (duk_check_type_mask(ctx, -1, DUK_TYPE_MASK_OBJECT |
400-
DUK_TYPE_MASK_BUFFER |
401-
DUK_TYPE_MASK_LIGHTFUNC)) {
402-
duk_remove_m2(ctx);
403-
} else {
404-
duk_pop(ctx);
238+
idx_func = duk_get_top(ctx) - nargs - 1;
239+
if (idx_func < 0 || nargs < 0) {
240+
/* note that we can't reliably pop anything here */
241+
DUK_ERROR_TYPE_INVALID_ARGS(thr);
405242
}
406243

407-
/*
408-
* Augment created errors upon creation (not when they are thrown or
409-
* rethrown). __FILE__ and __LINE__ are not desirable here; the call
410-
* stack reflects the caller which is correct.
411-
*/
412-
413-
#if defined(DUK_USE_AUGMENT_ERROR_CREATE)
414-
duk_hthread_sync_currpc(thr);
415-
duk_err_augment_error_create(thr, thr, NULL, 0, 1 /*noblame_fileline*/);
416-
#endif
417-
418-
/* [... retval] */
419-
420-
return;
244+
duk_push_object(ctx); /* default instance; internal proto updated by call handling */
245+
duk_insert(ctx, idx_func + 1);
421246

422-
not_constructable:
423-
#if defined(DUK_USE_VERBOSE_ERRORS)
424-
#if defined(DUK_USE_PARANOID_ERRORS)
425-
DUK_ERROR_FMT1(thr, DUK_ERR_TYPE_ERROR, "%s not constructable", duk_get_type_name(ctx, -1));
426-
#else
427-
DUK_ERROR_FMT1(thr, DUK_ERR_TYPE_ERROR, "%s not constructable", duk_push_string_readable(ctx, -1));
428-
#endif
429-
#else
430-
DUK_ERROR_TYPE(thr, "not constructable");
431-
#endif
247+
duk_handle_call_unprotected((duk_hthread *) ctx, nargs, DUK_CALL_FLAG_CONSTRUCTOR_CALL);
432248
}
433249

434250
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) {
447263
DUK_ASSERT_CTX_VALID(ctx);
448264

449265
/* For now, just use duk_safe_call() to wrap duk_new(). We can't
450-
* simply use a protected duk_handle_call() because there's post
451-
* processing which might throw. It should be possible to ensure
452-
* the post processing never throws (except in internal errors and
453-
* out of memory etc which are always allowed) and then remove this
454-
* wrapper.
266+
* simply use a protected duk_handle_call() because pushing the
267+
* default instance might throw.
455268
*/
456269

457270
rc = duk_safe_call(ctx, duk__pnew_helper, (void *) &nargs /*udata*/, nargs + 1 /*nargs*/, 1 /*nrets*/);

src-input/duk_api_stack.c

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3733,6 +3733,20 @@ DUK_EXTERNAL duk_bool_t duk_is_function(duk_context *ctx, duk_idx_t idx) {
37333733
DUK_HOBJECT_FLAG_BOUNDFUNC);
37343734
}
37353735

3736+
DUK_EXTERNAL duk_bool_t duk_is_constructable(duk_context *ctx, duk_idx_t idx) {
3737+
duk_tval *tv;
3738+
3739+
DUK_ASSERT_CTX_VALID(ctx);
3740+
3741+
tv = duk_get_tval_or_unused(ctx, idx);
3742+
if (DUK_TVAL_IS_LIGHTFUNC(tv)) {
3743+
return 1;
3744+
}
3745+
return duk__obj_flag_any_default_false(ctx,
3746+
idx,
3747+
DUK_HOBJECT_FLAG_CONSTRUCTABLE);
3748+
}
3749+
37363750
DUK_EXTERNAL duk_bool_t duk_is_c_function(duk_context *ctx, duk_idx_t idx) {
37373751
DUK_ASSERT_CTX_VALID(ctx);
37383752
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
48664880
duk_hthread *thr = (duk_hthread *) ctx;
48674881
duk_hobject *proto;
48684882
#if defined(DUK_USE_AUGMENT_ERROR_CREATE)
4869-
duk_bool_t noblame_fileline;
4883+
duk_small_uint_t augment_flags;
48704884
#endif
48714885

48724886
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
48764890

48774891
/* Error code also packs a tracedata related flag. */
48784892
#if defined(DUK_USE_AUGMENT_ERROR_CREATE)
4879-
noblame_fileline = err_code & DUK_ERRCODE_FLAG_NOBLAME_FILELINE;
4893+
augment_flags = 0;
4894+
if (err_code & DUK_ERRCODE_FLAG_NOBLAME_FILELINE) {
4895+
augment_flags = DUK_AUGMENT_FLAG_NOBLAME_FILELINE;
4896+
}
48804897
#endif
48814898
err_code = err_code & (~DUK_ERRCODE_FLAG_NOBLAME_FILELINE);
48824899

@@ -4908,7 +4925,7 @@ DUK_EXTERNAL duk_idx_t duk_push_error_object_va_raw(duk_context *ctx, duk_errcod
49084925
/* Creation time error augmentation */
49094926
#if defined(DUK_USE_AUGMENT_ERROR_CREATE)
49104927
/* filename may be NULL in which case file/line is not recorded */
4911-
duk_err_augment_error_create(thr, thr, filename, line, noblame_fileline); /* may throw an error */
4928+
duk_err_augment_error_create(thr, thr, filename, line, augment_flags); /* may throw an error */
49124929
#endif
49134930

49144931
return duk_get_top_index_unsafe(ctx);

src-input/duk_bi_error.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ DUK_INTERNAL duk_ret_t duk_bi_error_constructor_shared(duk_context *ctx) {
3939

4040
#if defined(DUK_USE_AUGMENT_ERROR_CREATE)
4141
if (!duk_is_constructor_call(ctx)) {
42-
duk_err_augment_error_create(thr, thr, NULL, 0, 1 /*noblame_fileline*/);
42+
duk_err_augment_error_create(thr, thr, NULL, 0, DUK_AUGMENT_FLAG_NOBLAME_FILELINE);
4343
}
4444
#endif
4545

src-input/duk_error.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -455,8 +455,11 @@ DUK_NORETURN(DUK_INTERNAL_DECL void duk_err_create_and_throw(duk_hthread *thr, d
455455

456456
DUK_NORETURN(DUK_INTERNAL_DECL void duk_error_throw_from_negative_rc(duk_hthread *thr, duk_ret_t rc));
457457

458+
#define DUK_AUGMENT_FLAG_NOBLAME_FILELINE (1U << 0) /* if set, don't blame C file/line for .fileName and .lineNumber */
459+
#define DUK_AUGMENT_FLAG_SKIP_ONE (1U << 1) /* if set, skip topmost activation in traceback construction */
460+
458461
#if defined(DUK_USE_AUGMENT_ERROR_CREATE)
459-
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);
462+
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);
460463
#endif
461464
#if defined(DUK_USE_AUGMENT_ERROR_THROW)
462465
DUK_INTERNAL_DECL void duk_err_augment_error_throw(duk_hthread *thr);

0 commit comments

Comments
 (0)