diff --git a/src-input/duk_heap.h b/src-input/duk_heap.h index 4d7c887f..58cb3fa4 100644 --- a/src-input/duk_heap.h +++ b/src-input/duk_heap.h @@ -634,6 +634,11 @@ struct duk_heap { duk_int_t stats_putprop_proxy; duk_int_t stats_getvar_all; duk_int_t stats_putvar_all; + duk_int_t stats_envrec_delayedcreate; + duk_int_t stats_envrec_create; + duk_int_t stats_envrec_newenv; + duk_int_t stats_envrec_oldenv; + duk_int_t stats_envrec_pushclosure; #endif }; diff --git a/src-input/duk_heap_markandsweep.c b/src-input/duk_heap_markandsweep.c index abf5517e..1838ffc2 100644 --- a/src-input/duk_heap_markandsweep.c +++ b/src-input/duk_heap_markandsweep.c @@ -1177,6 +1177,12 @@ DUK_LOCAL void duk__dump_stats(duk_heap *heap) { (long) heap->stats_getvar_all)); DUK_D(DUK_DPRINT("stats putvar: all=%ld", (long) heap->stats_putvar_all)); + DUK_D(DUK_DPRINT("stats envrec: delayedcreate=%ld, create=%ld, newenv=%ld, oldenv=%ld, pushclosure=%ld", + (long) heap->stats_envrec_delayedcreate, + (long) heap->stats_envrec_create, + (long) heap->stats_envrec_newenv, + (long) heap->stats_envrec_oldenv, + (long) heap->stats_envrec_pushclosure)); } #endif /* DUK_USE_DEBUG */ diff --git a/src-input/duk_js_call.c b/src-input/duk_js_call.c index 55f6f909..8babec8b 100644 --- a/src-input/duk_js_call.c +++ b/src-input/duk_js_call.c @@ -1820,6 +1820,7 @@ DUK_LOCAL void duk__call_env_setup(duk_hthread *thr, duk_hobject *func, duk_acti if (DUK_LIKELY(func != NULL)) { if (DUK_LIKELY(DUK_HOBJECT_HAS_NEWENV(func))) { + DUK_STATS_INC(thr->heap, stats_envrec_newenv); if (DUK_LIKELY(!DUK_HOBJECT_HAS_CREATEARGS(func))) { /* Use a new environment but there's no 'arguments' object; * delayed environment initialization. This is the most @@ -1856,6 +1857,7 @@ DUK_LOCAL void duk__call_env_setup(duk_hthread *thr, duk_hobject *func, duk_acti DUK_ASSERT(!DUK_HOBJECT_HAS_CREATEARGS(func)); + DUK_STATS_INC(thr->heap, stats_envrec_oldenv); duk__handle_oldenv_for_call(thr, func, act); DUK_ASSERT(act->lex_env != NULL); @@ -1865,6 +1867,7 @@ DUK_LOCAL void duk__call_env_setup(duk_hthread *thr, duk_hobject *func, duk_acti /* Lightfuncs are always native functions and have "newenv". */ DUK_ASSERT(act->lex_env == NULL); DUK_ASSERT(act->var_env == NULL); + DUK_STATS_INC(thr->heap, stats_envrec_newenv); } } diff --git a/src-input/duk_js_var.c b/src-input/duk_js_var.c index fe7d871b..f7838687 100644 --- a/src-input/duk_js_var.c +++ b/src-input/duk_js_var.c @@ -138,6 +138,8 @@ void duk_js_push_closure(duk_hthread *thr, DUK_ASSERT(outer_lex_env != NULL); DUK_UNREF(len_value); + DUK_STATS_INC(thr->heap, stats_envrec_pushclosure); + fun_clos = duk_push_hcompfunc(thr); DUK_ASSERT(fun_clos != NULL); DUK_ASSERT(DUK_HOBJECT_GET_PROTOTYPE(thr->heap, (duk_hobject *) fun_clos) == thr->builtins[DUK_BIDX_FUNCTION_PROTOTYPE]); @@ -499,6 +501,29 @@ void duk_js_push_closure(duk_hthread *thr, * The non-delayed initialization is handled by duk_handle_call(). */ +DUK_LOCAL void duk__preallocate_env_entries(duk_hthread *thr, duk_hobject *varmap, duk_hobject *env) { + duk_uint_fast32_t i; + + for (i = 0; i < (duk_uint_fast32_t) DUK_HOBJECT_GET_ENEXT(varmap); i++) { + duk_hstring *key; + + key = DUK_HOBJECT_E_GET_KEY(thr->heap, varmap, i); + DUK_ASSERT(key != NULL); /* assume keys are compact in _Varmap */ + DUK_ASSERT(!DUK_HOBJECT_E_SLOT_IS_ACCESSOR(thr->heap, varmap, i)); /* assume plain values */ + + /* Predefine as 'undefined' to reserve a property slot. + * This makes the unwind process (where register values + * are copied to the env object) safe against throwing. + * + * XXX: This could be made much faster by creating the + * property table directly. + */ + duk_push_undefined(thr); + DUK_DDD(DUK_DDDPRINT("preallocate env entry for key %!O", key)); + duk_hobject_define_property_internal(thr, env, key, DUK_PROPDESC_FLAGS_WE); + } +} + /* shared helper */ DUK_INTERNAL duk_hobject *duk_create_activation_environment_record(duk_hthread *thr, @@ -511,6 +536,8 @@ duk_hobject *duk_create_activation_environment_record(duk_hthread *thr, DUK_ASSERT(thr != NULL); DUK_ASSERT(func != NULL); + DUK_STATS_INC(thr->heap, stats_envrec_create); + f = (duk_hcompfunc *) func; parent = DUK_HCOMPFUNC_GET_LEXENV(thr->heap, f); if (!parent) { @@ -546,6 +573,11 @@ duk_hobject *duk_create_activation_environment_record(duk_hthread *thr, env->thread = thr; DUK_HTHREAD_INCREF(thr, thr); env->regbase_byteoff = bottom_byteoff; + + /* Preallocate env property table to avoid potential + * for out-of-memory on unwind when the env is closed. + */ + duk__preallocate_env_entries(thr, varmap, (duk_hobject *) env); } else { /* If function has no _Varmap, leave the environment closed. */ DUK_ASSERT(env->thread == NULL); @@ -576,6 +608,8 @@ void duk_js_init_activation_environment_records_delayed(duk_hthread *thr, DUK_ASSERT(act->lex_env == NULL); DUK_ASSERT(act->var_env == NULL); + DUK_STATS_INC(thr->heap, stats_envrec_delayedcreate); + env = duk_create_activation_environment_record(thr, func, act->bottom_byteoff); DUK_ASSERT(env != NULL); /* 'act' is a stable pointer, so still OK. */ @@ -680,9 +714,17 @@ DUK_INTERNAL void duk_js_close_environment_record(duk_hthread *thr, duk_hobject DUK_ASSERT((duk_uint8_t *) thr->valstack + regbase_byteoff + sizeof(duk_tval) * regnum >= (duk_uint8_t *) thr->valstack); DUK_ASSERT((duk_uint8_t *) thr->valstack + regbase_byteoff + sizeof(duk_tval) * regnum < (duk_uint8_t *) thr->valstack_top); - /* If property already exists, overwrites silently. + /* Write register value into env as named properties. + * If property already exists, overwrites silently. * Property is writable, but not deletable (not configurable * in terms of property attributes). + * + * This property write must not throw because we're unwinding + * and unwind code is not allowed to throw at present. The + * call itself has no such guarantees, but we've preallocated + * entries for each property when the env was created, so no + * out-of-memory error should be possible. If this guarantee + * is not provided, problems like GH-476 may happen. */ duk_push_tval(thr, (duk_tval *) (void *) ((duk_uint8_t *) thr->valstack + regbase_byteoff + sizeof(duk_tval) * regnum)); DUK_DDD(DUK_DDDPRINT("closing identifier %!O -> reg %ld, value %!T",