Browse Source

Preallocate env record property table

This avoids a possible out-of-memory when the env is closed in unwind.
Unwind code, which may happen due to an error throw, must not throw.
pull/2106/head
Sami Vaarala 6 years ago
parent
commit
391f600aea
  1. 5
      src-input/duk_heap.h
  2. 6
      src-input/duk_heap_markandsweep.c
  3. 3
      src-input/duk_js_call.c
  4. 44
      src-input/duk_js_var.c

5
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
};

6
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 */

3
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);
}
}

44
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",

Loading…
Cancel
Save