/* * Identifier access and function closure handling. * * Provides the primitives for slow path identifier accesses: GETVAR, * PUTVAR, DELVAR, etc. The fast path, direct register accesses, should * be used for most identifier accesses. Consequently, these slow path * primitives should be optimized for maximum compactness. * * ECMAScript environment records (declarative and object) are represented * as internal objects with control keys. Environment records have a * parent record ("outer environment reference") which is represented by * the implicit prototype for technical reasons (in other words, it is a * convenient field). The prototype chain is not followed in the ordinary * sense for variable lookups. * * See identifier-handling.rst for more details on the identifier algorithms * and the internal representation. See function-objects.rst for details on * what function templates and instances are expected to look like. * * Care must be taken to avoid duk_tval pointer invalidation caused by * e.g. value stack or object resizing. * * TODO: properties for function instances could be initialized much more * efficiently by creating a property allocation for a certain size and * filling in keys and values directly (and INCREFing both with "bulk incref" * primitives. * * XXX: duk_hobject_getprop() and duk_hobject_putprop() calls are a bit * awkward (especially because they follow the prototype chain); rework * if "raw" own property helpers are added. */ #include "duk_internal.h" /* * Local result type for duk__get_identifier_reference() lookup. */ typedef struct { duk_hobject *env; duk_hobject *holder; /* for object-bound identifiers */ duk_tval *value; /* for register-bound and declarative env identifiers */ duk_uint_t attrs; /* property attributes for identifier (relevant if value != NULL) */ duk_bool_t has_this; /* for object-bound identifiers: provide 'this' binding */ } duk__id_lookup_result; /* * Create a new function object based on a "template function" which contains * compiled bytecode, constants, etc, but lacks a lexical environment. * * ECMAScript requires that each created closure is a separate object, with * its own set of editable properties. However, structured property values * (such as the formal arguments list and the variable map) are shared. * Also the bytecode, constants, and inner functions are shared. * * See E5 Section 13.2 for detailed requirements on the function objects; * there are no similar requirements for function "templates" which are an * implementation dependent internal feature. Also see function-objects.rst * for a discussion on the function instance properties provided by this * implementation. * * Notes: * * * Order of internal properties should match frequency of use, since the * properties will be linearly scanned on lookup (functions usually don't * have enough properties to warrant a hash part). * * * The created closure is independent of its template; they do share the * same 'data' buffer object, but the template object itself can be freed * even if the closure object remains reachable. */ DUK_LOCAL void duk__inc_data_inner_refcounts(duk_hthread *thr, duk_hcompfunc *f) { duk_tval *tv, *tv_end; duk_hobject **funcs, **funcs_end; DUK_UNREF(thr); /* If function creation fails due to out-of-memory, the data buffer * pointer may be NULL in some cases. That's actually possible for * GC code, but shouldn't be possible here because the incomplete * function will be unwound from the value stack and never instantiated. */ DUK_ASSERT(DUK_HCOMPFUNC_GET_DATA(thr->heap, f) != NULL); tv = DUK_HCOMPFUNC_GET_CONSTS_BASE(thr->heap, f); tv_end = DUK_HCOMPFUNC_GET_CONSTS_END(thr->heap, f); while (tv < tv_end) { DUK_TVAL_INCREF(thr, tv); tv++; } funcs = DUK_HCOMPFUNC_GET_FUNCS_BASE(thr->heap, f); funcs_end = DUK_HCOMPFUNC_GET_FUNCS_END(thr->heap, f); while (funcs < funcs_end) { DUK_HEAPHDR_INCREF(thr, (duk_heaphdr *) *funcs); funcs++; } } /* Push a new closure on the stack. * * Note: if fun_temp has NEWENV, i.e. a new lexical and variable declaration * is created when the function is called, only outer_lex_env matters * (outer_var_env is ignored and may or may not be same as outer_lex_env). */ DUK_LOCAL const duk_uint16_t duk__closure_copy_proplist[] = { /* order: most frequent to least frequent */ DUK_STRIDX_INT_VARMAP, DUK_STRIDX_INT_FORMALS, #if defined(DUK_USE_PC2LINE) DUK_STRIDX_INT_PC2LINE, #endif #if defined(DUK_USE_FUNC_FILENAME_PROPERTY) DUK_STRIDX_FILE_NAME, #endif #if defined(DUK_USE_NONSTD_FUNC_SOURCE_PROPERTY) DUK_STRIDX_INT_SOURCE #endif }; DUK_INTERNAL void duk_js_push_closure(duk_hthread *thr, duk_hcompfunc *fun_temp, duk_hobject *outer_var_env, duk_hobject *outer_lex_env, duk_bool_t add_auto_proto) { duk_hcompfunc *fun_clos; duk_small_uint_t i; duk_uint_t len_value; DUK_ASSERT(fun_temp != NULL); DUK_ASSERT(DUK_HCOMPFUNC_GET_DATA(thr->heap, fun_temp) != NULL); DUK_ASSERT(DUK_HCOMPFUNC_GET_FUNCS(thr->heap, fun_temp) != NULL); DUK_ASSERT(DUK_HCOMPFUNC_GET_BYTECODE(thr->heap, fun_temp) != NULL); DUK_ASSERT(outer_var_env != NULL); DUK_ASSERT(outer_lex_env != NULL); DUK_UNREF(len_value); 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]); duk_push_hobject(thr, &fun_temp->obj); /* -> [ ... closure template ] */ DUK_ASSERT(DUK_HOBJECT_IS_COMPFUNC((duk_hobject *) fun_clos)); DUK_ASSERT(DUK_HCOMPFUNC_GET_DATA(thr->heap, fun_clos) == NULL); DUK_ASSERT(DUK_HCOMPFUNC_GET_FUNCS(thr->heap, fun_clos) == NULL); DUK_ASSERT(DUK_HCOMPFUNC_GET_BYTECODE(thr->heap, fun_clos) == NULL); DUK_HCOMPFUNC_SET_DATA(thr->heap, fun_clos, DUK_HCOMPFUNC_GET_DATA(thr->heap, fun_temp)); DUK_HCOMPFUNC_SET_FUNCS(thr->heap, fun_clos, DUK_HCOMPFUNC_GET_FUNCS(thr->heap, fun_temp)); DUK_HCOMPFUNC_SET_BYTECODE(thr->heap, fun_clos, DUK_HCOMPFUNC_GET_BYTECODE(thr->heap, fun_temp)); /* Note: all references inside 'data' need to get their refcounts * upped too. This is the case because refcounts are decreased * through every function referencing 'data' independently. */ DUK_HBUFFER_INCREF(thr, DUK_HCOMPFUNC_GET_DATA(thr->heap, fun_clos)); duk__inc_data_inner_refcounts(thr, fun_temp); fun_clos->nregs = fun_temp->nregs; fun_clos->nargs = fun_temp->nargs; #if defined(DUK_USE_DEBUGGER_SUPPORT) fun_clos->start_line = fun_temp->start_line; fun_clos->end_line = fun_temp->end_line; #endif DUK_ASSERT(DUK_HCOMPFUNC_GET_DATA(thr->heap, fun_clos) != NULL); DUK_ASSERT(DUK_HCOMPFUNC_GET_FUNCS(thr->heap, fun_clos) != NULL); DUK_ASSERT(DUK_HCOMPFUNC_GET_BYTECODE(thr->heap, fun_clos) != NULL); /* XXX: Could also copy from template, but there's no way to have any * other value here now (used code has no access to the template). * Prototype is set by duk_push_hcompfunc(). */ DUK_ASSERT(DUK_HOBJECT_GET_PROTOTYPE(thr->heap, &fun_clos->obj) == thr->builtins[DUK_BIDX_FUNCTION_PROTOTYPE]); #if 0 DUK_HOBJECT_SET_PROTOTYPE_UPDREF(thr, &fun_clos->obj, thr->builtins[DUK_BIDX_FUNCTION_PROTOTYPE]); #endif /* Copy duk_hobject flags as is from the template using a mask. * Leave out duk_heaphdr owned flags just in case (e.g. if there's * some GC flag or similar). Some flags can then be adjusted * separately if necessary. */ /* DUK_HEAPHDR_SET_FLAGS() masks changes to non-duk_heaphdr flags only. */ DUK_HEAPHDR_SET_FLAGS((duk_heaphdr *) fun_clos, DUK_HEAPHDR_GET_FLAGS_RAW((duk_heaphdr *) fun_temp)); DUK_DD(DUK_DDPRINT("fun_temp heaphdr flags: 0x%08lx, fun_clos heaphdr flags: 0x%08lx", (unsigned long) DUK_HEAPHDR_GET_FLAGS_RAW((duk_heaphdr *) fun_temp), (unsigned long) DUK_HEAPHDR_GET_FLAGS_RAW((duk_heaphdr *) fun_clos))); DUK_ASSERT(DUK_HOBJECT_HAS_EXTENSIBLE(&fun_clos->obj)); DUK_ASSERT(!DUK_HOBJECT_HAS_BOUNDFUNC(&fun_clos->obj)); DUK_ASSERT(DUK_HOBJECT_HAS_COMPFUNC(&fun_clos->obj)); DUK_ASSERT(!DUK_HOBJECT_HAS_NATFUNC(&fun_clos->obj)); DUK_ASSERT(!DUK_HOBJECT_IS_THREAD(&fun_clos->obj)); /* DUK_HOBJECT_FLAG_ARRAY_PART: don't care */ /* DUK_HOBJECT_FLAG_NEWENV: handled below */ DUK_ASSERT(!DUK_HOBJECT_HAS_EXOTIC_ARRAY(&fun_clos->obj)); DUK_ASSERT(!DUK_HOBJECT_HAS_EXOTIC_STRINGOBJ(&fun_clos->obj)); DUK_ASSERT(!DUK_HOBJECT_HAS_EXOTIC_ARGUMENTS(&fun_clos->obj)); if (!DUK_HOBJECT_HAS_CONSTRUCTABLE(&fun_clos->obj)) { /* If the template is not constructable don't add an automatic * .prototype property. This is the case for e.g. ES2015 object * literal getters/setters and method definitions. */ add_auto_proto = 0; } /* * Setup environment record properties based on the template and * its flags. * * If DUK_HOBJECT_HAS_NEWENV(fun_temp) is true, the environment * records represent identifiers "outside" the function; the * "inner" environment records are created on demand. Otherwise, * the environment records are those that will be directly used * (e.g. for declarations). * * _Lexenv is always set; _Varenv defaults to _Lexenv if missing, * so _Varenv is only set if _Lexenv != _Varenv. * * This is relatively complex, see doc/identifier-handling.rst. */ if (DUK_HOBJECT_HAS_NEWENV(&fun_clos->obj)) { #if defined(DUK_USE_FUNC_NAME_PROPERTY) if (DUK_HOBJECT_HAS_NAMEBINDING(&fun_clos->obj)) { duk_hobject *proto; duk_hdecenv *new_env; /* * Named function expression, name needs to be bound * in an intermediate environment record. The "outer" * lexical/variable environment will thus be: * * a) { funcname: , __prototype: outer_lex_env } * b) { funcname: , __prototype: } (if outer_lex_env missing) */ if (outer_lex_env) { proto = outer_lex_env; } else { proto = thr->builtins[DUK_BIDX_GLOBAL_ENV]; } /* -> [ ... closure template env ] */ new_env = duk_hdecenv_alloc(thr, DUK_HOBJECT_FLAG_EXTENSIBLE | DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_DECENV)); DUK_ASSERT(new_env != NULL); duk_push_hobject(thr, (duk_hobject *) new_env); DUK_ASSERT(DUK_HOBJECT_GET_PROTOTYPE(thr->heap, (duk_hobject *) new_env) == NULL); DUK_HOBJECT_SET_PROTOTYPE(thr->heap, (duk_hobject *) new_env, proto); DUK_HOBJECT_INCREF_ALLOWNULL(thr, proto); DUK_ASSERT(new_env->thread == NULL); /* Closed. */ DUK_ASSERT(new_env->varmap == NULL); /* It's important that duk_xdef_prop() is a 'raw define' so that any * properties in an ancestor are never an issue (they should never be * e.g. non-writable, but just in case). * * Because template objects are not visible to user code, the case * where .name is missing shouldn't happen in practice. It it does, * the name 'undefined' gets bound and maps to the closure (which is * a bit odd, but safe). */ (void) duk_get_prop_stridx_short(thr, -2, DUK_STRIDX_NAME); /* -> [ ... closure template env funcname ] */ duk_dup_m4(thr); /* -> [ ... closure template env funcname closure ] */ duk_xdef_prop(thr, -3, DUK_PROPDESC_FLAGS_NONE); /* -> [ ... closure template env ] */ /* env[funcname] = closure */ /* [ ... closure template env ] */ DUK_HCOMPFUNC_SET_LEXENV(thr->heap, fun_clos, (duk_hobject *) new_env); DUK_HCOMPFUNC_SET_VARENV(thr->heap, fun_clos, (duk_hobject *) new_env); DUK_HOBJECT_INCREF(thr, (duk_hobject *) new_env); DUK_HOBJECT_INCREF(thr, (duk_hobject *) new_env); duk_pop_unsafe(thr); /* [ ... closure template ] */ } else #endif /* DUK_USE_FUNC_NAME_PROPERTY */ { /* * Other cases (function declaration, anonymous function expression, * strict direct eval code). The "outer" environment will be whatever * the caller gave us. */ DUK_HCOMPFUNC_SET_LEXENV(thr->heap, fun_clos, outer_lex_env); DUK_HCOMPFUNC_SET_VARENV(thr->heap, fun_clos, outer_lex_env); DUK_HOBJECT_INCREF(thr, outer_lex_env); DUK_HOBJECT_INCREF(thr, outer_lex_env); /* [ ... closure template ] */ } } else { /* * Function gets no new environment when called. This is the * case for global code, indirect eval code, and non-strict * direct eval code. There is no direct correspondence to the * E5 specification, as global/eval code is not exposed as a * function. */ DUK_ASSERT(!DUK_HOBJECT_HAS_NAMEBINDING(&fun_temp->obj)); DUK_HCOMPFUNC_SET_LEXENV(thr->heap, fun_clos, outer_lex_env); DUK_HCOMPFUNC_SET_VARENV(thr->heap, fun_clos, outer_var_env); DUK_HOBJECT_INCREF(thr, outer_lex_env); /* NULLs not allowed; asserted on entry */ DUK_HOBJECT_INCREF(thr, outer_var_env); } DUK_DDD(DUK_DDDPRINT("closure varenv -> %!ipO, lexenv -> %!ipO", (duk_heaphdr *) fun_clos->var_env, (duk_heaphdr *) fun_clos->lex_env)); /* Call handling assumes this for all callable closures. */ DUK_ASSERT(DUK_HCOMPFUNC_GET_LEXENV(thr->heap, fun_clos) != NULL); DUK_ASSERT(DUK_HCOMPFUNC_GET_VARENV(thr->heap, fun_clos) != NULL); /* * Copy some internal properties directly * * The properties will be non-writable and non-enumerable, but * configurable. */ /* [ ... closure template ] */ DUK_DDD(DUK_DDDPRINT("copying properties: closure=%!iT, template=%!iT", (duk_tval *) duk_get_tval(thr, -2), (duk_tval *) duk_get_tval(thr, -1))); for (i = 0; i < (duk_small_uint_t) (sizeof(duk__closure_copy_proplist) / sizeof(duk_uint16_t)); i++) { duk_small_int_t stridx = (duk_small_int_t) duk__closure_copy_proplist[i]; if (duk_get_prop_stridx_short(thr, -1, stridx)) { /* [ ... closure template val ] */ DUK_DDD(DUK_DDDPRINT("copying property, stridx=%ld -> found", (long) stridx)); duk_xdef_prop_stridx_short(thr, -3, stridx, DUK_PROPDESC_FLAGS_C); } else { DUK_DDD(DUK_DDDPRINT("copying property, stridx=%ld -> not found", (long) stridx)); duk_pop_unsafe(thr); } } /* * "length" maps to number of formals (E5 Section 13.2) for function * declarations/expressions (non-bound functions). Note that 'nargs' * is NOT necessarily equal to the number of arguments. Use length * of _Formals; if missing, assume nargs matches .length. */ /* [ ... closure template ] */ /* XXX: these lookups should be just own property lookups instead of * looking up the inheritance chain. */ if (duk_get_prop_stridx_short(thr, -1, DUK_STRIDX_INT_FORMALS)) { /* [ ... closure template formals ] */ len_value = (duk_uint_t) duk_get_length(thr, -1); /* could access duk_harray directly, not important */ DUK_DD(DUK_DDPRINT("closure length from _Formals -> %ld", (long) len_value)); } else { len_value = fun_temp->nargs; DUK_DD(DUK_DDPRINT("closure length defaulted from nargs -> %ld", (long) len_value)); } duk_pop_unsafe(thr); duk_push_uint(thr, len_value); /* [ ... closure template len_value ] */ duk_xdef_prop_stridx_short(thr, -3, DUK_STRIDX_LENGTH, DUK_PROPDESC_FLAGS_C); /* * "prototype" is, by default, a fresh object with the "constructor" * property. * * Note that this creates a circular reference for every function * instance (closure) which prevents refcount-based collection of * function instances. * * XXX: Try to avoid creating the default prototype object, because * many functions are not used as constructors and the default * prototype is unnecessary. Perhaps it could be created on-demand * when it is first accessed? */ /* [ ... closure template ] */ if (add_auto_proto) { duk_push_object(thr); /* -> [ ... closure template newobj ] */ duk_dup_m3(thr); /* -> [ ... closure template newobj closure ] */ duk_xdef_prop_stridx_short(thr, -2, DUK_STRIDX_CONSTRUCTOR, DUK_PROPDESC_FLAGS_WC); /* -> [ ... closure template newobj ] */ duk_compact(thr, -1); /* compact the prototype */ duk_xdef_prop_stridx_short(thr, -3, DUK_STRIDX_PROTOTYPE, DUK_PROPDESC_FLAGS_W); /* -> [ ... closure template ] */ } /* * "arguments" and "caller" must be mapped to throwers for strict * mode and bound functions (E5 Section 15.3.5). * * XXX: This is expensive to have for every strict function instance. * Try to implement as virtual properties or on-demand created properties. */ /* [ ... closure template ] */ if (DUK_HOBJECT_HAS_STRICT(&fun_clos->obj)) { duk_xdef_prop_stridx_thrower(thr, -2, DUK_STRIDX_CALLER); duk_xdef_prop_stridx_thrower(thr, -2, DUK_STRIDX_LC_ARGUMENTS); } else { #if defined(DUK_USE_NONSTD_FUNC_CALLER_PROPERTY) DUK_DDD(DUK_DDDPRINT("function is non-strict and non-standard 'caller' property in use, add initial 'null' value")); duk_push_null(thr); duk_xdef_prop_stridx_short(thr, -3, DUK_STRIDX_CALLER, DUK_PROPDESC_FLAGS_NONE); #else DUK_DDD(DUK_DDDPRINT("function is non-strict and non-standard 'caller' property not used")); #endif } /* * "name" used to be non-standard but is now defined by ES2015. * In ES2015/ES2016 the .name property is configurable. */ /* [ ... closure template ] */ #if defined(DUK_USE_FUNC_NAME_PROPERTY) /* XXX: Look for own property only; doesn't matter much because * templates are bare objects. */ if (duk_get_prop_stridx_short(thr, -1, DUK_STRIDX_NAME)) { /* [ ... closure template name ] */ DUK_ASSERT(duk_is_string(thr, -1)); DUK_DD(DUK_DDPRINT("setting function instance name to %!T", duk_get_tval(thr, -1))); duk_xdef_prop_stridx_short(thr, -3, DUK_STRIDX_NAME, DUK_PROPDESC_FLAGS_C); /* -> [ ... closure template ] */ } else { /* Anonymous functions don't have a .name in ES2015, so don't set * it on the instance either. The instance will then inherit * it from Function.prototype.name. */ DUK_DD(DUK_DDPRINT("not setting function instance .name")); duk_pop_unsafe(thr); } #endif /* * Compact the closure, in most cases no properties will be added later. * Also, without this the closures end up having unused property slots * (e.g. in Duktape 0.9.0, 8 slots would be allocated and only 7 used). * A better future solution would be to allocate the closure directly * to correct size (and setup the properties directly without going * through the API). */ duk_compact(thr, -2); /* * Some assertions (E5 Section 13.2). */ DUK_ASSERT(DUK_HOBJECT_GET_CLASS_NUMBER(&fun_clos->obj) == DUK_HOBJECT_CLASS_FUNCTION); DUK_ASSERT(DUK_HOBJECT_GET_PROTOTYPE(thr->heap, &fun_clos->obj) == thr->builtins[DUK_BIDX_FUNCTION_PROTOTYPE]); DUK_ASSERT(DUK_HOBJECT_HAS_EXTENSIBLE(&fun_clos->obj)); DUK_ASSERT(duk_has_prop_stridx(thr, -2, DUK_STRIDX_LENGTH) != 0); DUK_ASSERT(add_auto_proto == 0 || duk_has_prop_stridx(thr, -2, DUK_STRIDX_PROTOTYPE) != 0); /* May be missing .name */ DUK_ASSERT(!DUK_HOBJECT_HAS_STRICT(&fun_clos->obj) || duk_has_prop_stridx(thr, -2, DUK_STRIDX_CALLER) != 0); DUK_ASSERT(!DUK_HOBJECT_HAS_STRICT(&fun_clos->obj) || duk_has_prop_stridx(thr, -2, DUK_STRIDX_LC_ARGUMENTS) != 0); /* * Finish */ /* [ ... closure template ] */ DUK_DDD(DUK_DDDPRINT("created function instance: template=%!iT -> closure=%!iT", (duk_tval *) duk_get_tval(thr, -1), (duk_tval *) duk_get_tval(thr, -2))); duk_pop_unsafe(thr); /* [ ... closure ] */ } /* * Delayed activation environment record initialization (for functions * with NEWENV). * * The non-delayed initialization is handled by duk_handle_call(). */ /* shared helper */ DUK_INTERNAL duk_hobject *duk_create_activation_environment_record(duk_hthread *thr, duk_hobject *func, duk_size_t bottom_byteoff) { duk_hdecenv *env; duk_hobject *parent; duk_hcompfunc *f; DUK_ASSERT(thr != NULL); DUK_ASSERT(func != NULL); f = (duk_hcompfunc *) func; parent = DUK_HCOMPFUNC_GET_LEXENV(thr->heap, f); if (!parent) { parent = thr->builtins[DUK_BIDX_GLOBAL_ENV]; } env = duk_hdecenv_alloc(thr, DUK_HOBJECT_FLAG_EXTENSIBLE | DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_DECENV)); DUK_ASSERT(env != NULL); duk_push_hobject(thr, (duk_hobject *) env); DUK_ASSERT(DUK_HOBJECT_GET_PROTOTYPE(thr->heap, (duk_hobject *) env) == NULL); DUK_HOBJECT_SET_PROTOTYPE(thr->heap, (duk_hobject *) env, parent); DUK_HOBJECT_INCREF_ALLOWNULL(thr, parent); /* parent env is the prototype */ /* open scope information, for compiled functions only */ DUK_ASSERT(env->thread == NULL); DUK_ASSERT(env->varmap == NULL); DUK_ASSERT(env->regbase_byteoff == 0); if (DUK_HOBJECT_IS_COMPFUNC(func)) { duk_hobject *varmap; duk_tval *tv; tv = duk_hobject_find_existing_entry_tval_ptr(thr->heap, func, DUK_HTHREAD_STRING_INT_VARMAP(thr)); if (tv != NULL && DUK_TVAL_IS_OBJECT(tv)) { DUK_ASSERT(DUK_TVAL_IS_OBJECT(tv)); varmap = DUK_TVAL_GET_OBJECT(tv); DUK_ASSERT(varmap != NULL); env->varmap = varmap; DUK_HOBJECT_INCREF(thr, varmap); env->thread = thr; DUK_HTHREAD_INCREF(thr, thr); env->regbase_byteoff = bottom_byteoff; } else { /* If function has no _Varmap, leave the environment closed. */ DUK_ASSERT(env->thread == NULL); DUK_ASSERT(env->varmap == NULL); DUK_ASSERT(env->regbase_byteoff == 0); } } return (duk_hobject *) env; } DUK_INTERNAL void duk_js_init_activation_environment_records_delayed(duk_hthread *thr, duk_activation *act) { duk_hobject *func; duk_hobject *env; DUK_ASSERT(thr != NULL); func = DUK_ACT_GET_FUNC(act); DUK_ASSERT(func != NULL); DUK_ASSERT(!DUK_HOBJECT_HAS_BOUNDFUNC(func)); /* bound functions are never in act 'func' */ /* * Delayed initialization only occurs for 'NEWENV' functions. */ DUK_ASSERT(DUK_HOBJECT_HAS_NEWENV(func)); DUK_ASSERT(act->lex_env == NULL); DUK_ASSERT(act->var_env == NULL); env = duk_create_activation_environment_record(thr, func, act->bottom_byteoff); DUK_ASSERT(env != NULL); /* 'act' is a stable pointer, so still OK. */ DUK_DDD(DUK_DDDPRINT("created delayed fresh env: %!ipO", (duk_heaphdr *) env)); #if defined(DUK_USE_DEBUG_LEVEL) && (DUK_USE_DEBUG_LEVEL >= 2) { duk_hobject *p = env; while (p) { DUK_DDD(DUK_DDDPRINT(" -> %!ipO", (duk_heaphdr *) p)); p = DUK_HOBJECT_GET_PROTOTYPE(thr->heap, p); } } #endif act->lex_env = env; act->var_env = env; DUK_HOBJECT_INCREF(thr, env); /* XXX: incref by count (here 2 times) */ DUK_HOBJECT_INCREF(thr, env); duk_pop_unsafe(thr); } /* * Closing environment records. * * The environment record MUST be closed with the thread where its activation * is; i.e. if 'env' is open, 'thr' must match env->thread, and the regbase * and varmap must still be valid. On entry, 'env' must be reachable. */ DUK_INTERNAL void duk_js_close_environment_record(duk_hthread *thr, duk_hobject *env) { duk_uint_fast32_t i; duk_hobject *varmap; duk_hstring *key; duk_tval *tv; duk_uint_t regnum; DUK_ASSERT(thr != NULL); DUK_ASSERT(env != NULL); if (DUK_UNLIKELY(!DUK_HOBJECT_IS_DECENV(env))) { DUK_DDD(DUK_DDDPRINT("env not a declarative record: %!iO", (duk_heaphdr *) env)); return; } varmap = ((duk_hdecenv *) env)->varmap; if (varmap == NULL) { DUK_DDD(DUK_DDDPRINT("env already closed: %!iO", (duk_heaphdr *) env)); return; } DUK_ASSERT(((duk_hdecenv *) env)->thread != NULL); DUK_ASSERT_HDECENV_VALID((duk_hdecenv *) env); DUK_DDD(DUK_DDDPRINT("closing env: %!iO", (duk_heaphdr *) env)); DUK_DDD(DUK_DDDPRINT("varmap: %!O", (duk_heaphdr *) varmap)); /* Env must be closed in the same thread as where it runs. */ DUK_ASSERT(((duk_hdecenv *) env)->thread == thr); /* XXX: additional conditions when to close variables? we don't want to do it * unless the environment may have "escaped" (referenced in a function closure). * With delayed environments, the existence is probably good enough of a check. */ /* Note: we rely on the _Varmap having a bunch of nice properties, like: * - being compacted and unmodified during this process * - not containing an array part * - having correct value types */ DUK_DDD(DUK_DDDPRINT("copying bound register values, %ld bound regs", (long) DUK_HOBJECT_GET_ENEXT(varmap))); /* Copy over current variable values from value stack to the * environment record. The scope object is empty but may * inherit from another scope which has conflicting names. */ /* XXX: Do this using a once allocated entry area, no side effects. * Hash part would need special treatment however (maybe copy, and * then realloc with hash part if large enough). */ for (i = 0; i < (duk_uint_fast32_t) DUK_HOBJECT_GET_ENEXT(varmap); i++) { duk_size_t regbase_byteoff; 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 */ tv = DUK_HOBJECT_E_GET_VALUE_TVAL_PTR(thr->heap, varmap, i); DUK_ASSERT(DUK_TVAL_IS_NUMBER(tv)); DUK_ASSERT(DUK_TVAL_GET_NUMBER(tv) <= (duk_double_t) DUK_UINT32_MAX); /* limits */ #if defined(DUK_USE_FASTINT) DUK_ASSERT(DUK_TVAL_IS_FASTINT(tv)); regnum = (duk_uint_t) DUK_TVAL_GET_FASTINT_U32(tv); #else regnum = (duk_uint_t) DUK_TVAL_GET_NUMBER(tv); #endif regbase_byteoff = ((duk_hdecenv *) env)->regbase_byteoff; 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. * Property is writable, but not deletable (not configurable * in terms of property attributes). */ 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", (duk_heaphdr *) key, (long) regnum, (duk_tval *) duk_get_tval(thr, -1))); duk_hobject_define_property_internal(thr, env, key, DUK_PROPDESC_FLAGS_WE); } /* NULL atomically to avoid inconsistent state + side effects. */ DUK_HOBJECT_DECREF_NORZ(thr, ((duk_hdecenv *) env)->thread); DUK_HOBJECT_DECREF_NORZ(thr, ((duk_hdecenv *) env)->varmap); ((duk_hdecenv *) env)->thread = NULL; ((duk_hdecenv *) env)->varmap = NULL; DUK_DDD(DUK_DDDPRINT("env after closing: %!O", (duk_heaphdr *) env)); } /* * GETIDREF: a GetIdentifierReference-like helper. * * Provides a parent traversing lookup and a single level lookup * (for HasBinding). * * Instead of returning the value, returns a bunch of values allowing * the caller to read, write, or delete the binding. Value pointers * are duk_tval pointers which can be mutated directly as long as * refcounts are properly updated. Note that any operation which may * reallocate valstacks or compact objects may invalidate the returned * duk_tval (but not object) pointers, so caller must be very careful. * * If starting environment record 'env' is given, 'act' is ignored. * However, if 'env' is NULL, the caller may identify, in 'act', an * activation which hasn't had its declarative environment initialized * yet. The activation registers are then looked up, and its parent * traversed normally. * * The 'out' structure values are only valid if the function returns * success (non-zero). */ /* lookup name from an open declarative record's registers */ DUK_LOCAL duk_bool_t duk__getid_open_decl_env_regs(duk_hthread *thr, duk_hstring *name, duk_hdecenv *env, duk__id_lookup_result *out) { duk_tval *tv; duk_size_t reg_rel; DUK_ASSERT(thr != NULL); DUK_ASSERT(name != NULL); DUK_ASSERT(env != NULL); DUK_ASSERT(out != NULL); DUK_ASSERT(DUK_HOBJECT_IS_DECENV((duk_hobject *) env)); DUK_ASSERT_HDECENV_VALID(env); if (env->thread == NULL) { /* already closed */ return 0; } DUK_ASSERT(env->varmap != NULL); tv = duk_hobject_find_existing_entry_tval_ptr(thr->heap, env->varmap, name); if (DUK_UNLIKELY(tv == NULL)) { return 0; } DUK_ASSERT(DUK_TVAL_IS_NUMBER(tv)); DUK_ASSERT(DUK_TVAL_GET_NUMBER(tv) <= (duk_double_t) DUK_UINT32_MAX); /* limits */ #if defined(DUK_USE_FASTINT) DUK_ASSERT(DUK_TVAL_IS_FASTINT(tv)); reg_rel = (duk_size_t) DUK_TVAL_GET_FASTINT_U32(tv); #else reg_rel = (duk_size_t) DUK_TVAL_GET_NUMBER(tv); #endif DUK_ASSERT_DISABLE(reg_rel >= 0); /* unsigned */ tv = (duk_tval *) (void *) ((duk_uint8_t *) env->thread->valstack + env->regbase_byteoff + sizeof(duk_tval) * reg_rel); DUK_ASSERT(tv >= env->thread->valstack && tv < env->thread->valstack_end); /* XXX: more accurate? */ out->value = tv; out->attrs = DUK_PROPDESC_FLAGS_W; /* registers are mutable, non-deletable */ out->env = (duk_hobject *) env; out->holder = NULL; out->has_this = 0; return 1; } /* lookup name from current activation record's functions' registers */ DUK_LOCAL duk_bool_t duk__getid_activation_regs(duk_hthread *thr, duk_hstring *name, duk_activation *act, duk__id_lookup_result *out) { duk_tval *tv; duk_hobject *func; duk_hobject *varmap; duk_size_t reg_rel; DUK_ASSERT(thr != NULL); DUK_ASSERT(name != NULL); DUK_ASSERT(act != NULL); DUK_ASSERT(out != NULL); func = DUK_ACT_GET_FUNC(act); DUK_ASSERT(func != NULL); DUK_ASSERT(DUK_HOBJECT_HAS_NEWENV(func)); if (!DUK_HOBJECT_IS_COMPFUNC(func)) { return 0; } /* XXX: move varmap to duk_hcompfunc struct field. */ tv = duk_hobject_find_existing_entry_tval_ptr(thr->heap, func, DUK_HTHREAD_STRING_INT_VARMAP(thr)); if (!tv) { return 0; } DUK_ASSERT(DUK_TVAL_IS_OBJECT(tv)); varmap = DUK_TVAL_GET_OBJECT(tv); DUK_ASSERT(varmap != NULL); tv = duk_hobject_find_existing_entry_tval_ptr(thr->heap, varmap, name); if (!tv) { return 0; } DUK_ASSERT(DUK_TVAL_IS_NUMBER(tv)); reg_rel = (duk_size_t) DUK_TVAL_GET_NUMBER(tv); DUK_ASSERT_DISABLE(reg_rel >= 0); DUK_ASSERT(reg_rel < ((duk_hcompfunc *) func)->nregs); tv = (duk_tval *) (void *) ((duk_uint8_t *) thr->valstack + act->bottom_byteoff); tv += reg_rel; out->value = tv; out->attrs = DUK_PROPDESC_FLAGS_W; /* registers are mutable, non-deletable */ out->env = NULL; out->holder = NULL; out->has_this = 0; return 1; } DUK_LOCAL duk_bool_t duk__get_identifier_reference(duk_hthread *thr, duk_hobject *env, duk_hstring *name, duk_activation *act, duk_bool_t parents, duk__id_lookup_result *out) { duk_tval *tv; duk_uint_t sanity; DUK_ASSERT(thr != NULL); DUK_ASSERT(env != NULL || act != NULL); DUK_ASSERT(name != NULL); DUK_ASSERT(out != NULL); DUK_ASSERT(!env || DUK_HOBJECT_IS_ENV(env)); DUK_ASSERT(!env || !DUK_HOBJECT_HAS_ARRAY_PART(env)); /* * Conceptually, we look for the identifier binding by starting from * 'env' and following to chain of environment records (represented * by the prototype chain). * * If 'env' is NULL, the current activation does not yet have an * allocated declarative environment record; this should be treated * exactly as if the environment record existed but had no bindings * other than register bindings. * * Note: we assume that with the DUK_HOBJECT_FLAG_NEWENV cleared * the environment will always be initialized immediately; hence * a NULL 'env' should only happen with the flag set. This is the * case for: (1) function calls, and (2) strict, direct eval calls. */ if (env == NULL && act != NULL) { duk_hobject *func; duk_hcompfunc *f; DUK_DDD(DUK_DDDPRINT("duk__get_identifier_reference: env is NULL, activation is non-NULL -> " "delayed env case, look up activation regs first")); /* * Try registers */ if (duk__getid_activation_regs(thr, name, act, out)) { DUK_DDD(DUK_DDDPRINT("duk__get_identifier_reference successful: " "name=%!O -> value=%!T, attrs=%ld, has_this=%ld, env=%!O, holder=%!O " "(found from register bindings when env=NULL)", (duk_heaphdr *) name, (duk_tval *) out->value, (long) out->attrs, (long) out->has_this, (duk_heaphdr *) out->env, (duk_heaphdr *) out->holder)); return 1; } DUK_DDD(DUK_DDDPRINT("not found in current activation regs")); /* * Not found in registers, proceed to the parent record. * Here we need to determine what the parent would be, * if 'env' was not NULL (i.e. same logic as when initializing * the record). * * Note that environment initialization is only deferred when * DUK_HOBJECT_HAS_NEWENV is set, and this only happens for: * - Function code * - Strict eval code * * We only need to check _Lexenv here; _Varenv exists only if it * differs from _Lexenv (and thus _Lexenv will also be present). */ if (!parents) { DUK_DDD(DUK_DDDPRINT("duk__get_identifier_reference failed, no parent traversal " "(not found from register bindings when env=NULL)")); goto fail_not_found; } func = DUK_ACT_GET_FUNC(act); DUK_ASSERT(func != NULL); DUK_ASSERT(DUK_HOBJECT_HAS_NEWENV(func)); f = (duk_hcompfunc *) func; env = DUK_HCOMPFUNC_GET_LEXENV(thr->heap, f); if (!env) { env = thr->builtins[DUK_BIDX_GLOBAL_ENV]; } DUK_DDD(DUK_DDDPRINT("continue lookup from env: %!iO", (duk_heaphdr *) env)); } /* * Prototype walking starting from 'env'. * * ('act' is not needed anywhere here.) */ sanity = DUK_HOBJECT_PROTOTYPE_CHAIN_SANITY; while (env != NULL) { duk_small_uint_t cl; duk_uint_t attrs; DUK_DDD(DUK_DDDPRINT("duk__get_identifier_reference, name=%!O, considering env=%p -> %!iO", (duk_heaphdr *) name, (void *) env, (duk_heaphdr *) env)); DUK_ASSERT(env != NULL); DUK_ASSERT(DUK_HOBJECT_IS_ENV(env)); DUK_ASSERT(!DUK_HOBJECT_HAS_ARRAY_PART(env)); cl = DUK_HOBJECT_GET_CLASS_NUMBER(env); DUK_ASSERT(cl == DUK_HOBJECT_CLASS_OBJENV || cl == DUK_HOBJECT_CLASS_DECENV); if (cl == DUK_HOBJECT_CLASS_DECENV) { /* * Declarative environment record. * * Identifiers can never be stored in ancestors and are * always plain values, so we can use an internal helper * and access the value directly with an duk_tval ptr. * * A closed environment is only indicated by it missing * the "book-keeping" properties required for accessing * register-bound variables. */ DUK_ASSERT_HDECENV_VALID((duk_hdecenv *) env); if (duk__getid_open_decl_env_regs(thr, name, (duk_hdecenv *) env, out)) { DUK_DDD(DUK_DDDPRINT("duk__get_identifier_reference successful: " "name=%!O -> value=%!T, attrs=%ld, has_this=%ld, env=%!O, holder=%!O " "(declarative environment record, scope open, found in regs)", (duk_heaphdr *) name, (duk_tval *) out->value, (long) out->attrs, (long) out->has_this, (duk_heaphdr *) out->env, (duk_heaphdr *) out->holder)); return 1; } tv = duk_hobject_find_existing_entry_tval_ptr_and_attrs(thr->heap, env, name, &attrs); if (tv) { out->value = tv; out->attrs = attrs; out->env = env; out->holder = env; out->has_this = 0; DUK_DDD(DUK_DDDPRINT("duk__get_identifier_reference successful: " "name=%!O -> value=%!T, attrs=%ld, has_this=%ld, env=%!O, holder=%!O " "(declarative environment record, found in properties)", (duk_heaphdr *) name, (duk_tval *) out->value, (long) out->attrs, (long) out->has_this, (duk_heaphdr *) out->env, (duk_heaphdr *) out->holder)); return 1; } } else { /* * Object environment record. * * Binding (target) object is an external, uncontrolled object. * Identifier may be bound in an ancestor property, and may be * an accessor. Target can also be a Proxy which we must support * here. */ /* XXX: we could save space by using _Target OR _This. If _Target, assume * this binding is undefined. If _This, assumes this binding is _This, and * target is also _This. One property would then be enough. */ duk_hobject *target; duk_bool_t found; DUK_ASSERT(cl == DUK_HOBJECT_CLASS_OBJENV); DUK_ASSERT_HOBJENV_VALID((duk_hobjenv *) env); target = ((duk_hobjenv *) env)->target; DUK_ASSERT(target != NULL); /* Target may be a Proxy or property may be an accessor, so we must * use an actual, Proxy-aware hasprop check here. * * out->holder is NOT set to the actual duk_hobject where the * property is found, but rather the object binding target object. */ #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); DUK_TVAL_SET_STRING(&tv_name, name); DUK_TVAL_SET_OBJECT(&tv_target_tmp, target); found = duk_hobject_hasprop(thr, &tv_target_tmp, &tv_name); } else #endif /* DUK_USE_ES6_PROXY */ { /* XXX: duk_hobject_hasprop() would be correct for * non-Proxy objects too, but it is about ~20-25% * slower at present so separate code paths for * Proxy and non-Proxy now. */ found = duk_hobject_hasprop_raw(thr, target, name); } if (found) { out->value = NULL; /* can't get value, may be accessor */ out->attrs = 0; /* irrelevant when out->value == NULL */ out->env = env; out->holder = target; out->has_this = ((duk_hobjenv *) env)->has_this; DUK_DDD(DUK_DDDPRINT("duk__get_identifier_reference successful: " "name=%!O -> value=%!T, attrs=%ld, has_this=%ld, env=%!O, holder=%!O " "(object environment record)", (duk_heaphdr *) name, (duk_tval *) out->value, (long) out->attrs, (long) out->has_this, (duk_heaphdr *) out->env, (duk_heaphdr *) out->holder)); return 1; } } if (!parents) { DUK_DDD(DUK_DDDPRINT("duk__get_identifier_reference failed, no parent traversal " "(not found from first traversed env)")); goto fail_not_found; } if (DUK_UNLIKELY(sanity-- == 0)) { DUK_ERROR_RANGE(thr, DUK_STR_PROTOTYPE_CHAIN_LIMIT); DUK_WO_NORETURN(return 0;); } env = DUK_HOBJECT_GET_PROTOTYPE(thr->heap, env); } /* * Not found (even in global object) */ fail_not_found: return 0; } /* * HASVAR: check identifier binding from a given environment record * without traversing its parents. * * This primitive is not exposed to user code as such, but is used * internally for e.g. declaration binding instantiation. * * See E5 Sections: * 10.2.1.1.1 HasBinding(N) * 10.2.1.2.1 HasBinding(N) * * Note: strictness has no bearing on this check. Hence we don't take * a 'strict' parameter. */ #if 0 /*unused*/ DUK_INTERNAL duk_bool_t duk_js_hasvar_envrec(duk_hthread *thr, duk_hobject *env, duk_hstring *name) { duk__id_lookup_result ref; duk_bool_t parents; DUK_DDD(DUK_DDDPRINT("hasvar: thr=%p, env=%p, name=%!O " "(env -> %!dO)", (void *) thr, (void *) env, (duk_heaphdr *) name, (duk_heaphdr *) env)); DUK_ASSERT(thr != NULL); DUK_ASSERT(env != NULL); DUK_ASSERT(name != NULL); DUK_ASSERT_REFCOUNT_NONZERO_HEAPHDR(env); DUK_ASSERT_REFCOUNT_NONZERO_HEAPHDR(name); DUK_ASSERT(DUK_HOBJECT_IS_ENV(env)); DUK_ASSERT(!DUK_HOBJECT_HAS_ARRAY_PART(env)); /* lookup results is ignored */ parents = 0; return duk__get_identifier_reference(thr, env, name, NULL, parents, &ref); } #endif /* * GETVAR * * See E5 Sections: * 11.1.2 Identifier Reference * 10.3.1 Identifier Resolution * 11.13.1 Simple Assignment [example of where the Reference is GetValue'd] * 8.7.1 GetValue (V) * 8.12.1 [[GetOwnProperty]] (P) * 8.12.2 [[GetProperty]] (P) * 8.12.3 [[Get]] (P) * * If 'throw' is true, always leaves two values on top of stack: [val this]. * * If 'throw' is false, returns 0 if identifier cannot be resolved, and the * stack will be unaffected in this case. If identifier is resolved, returns * 1 and leaves [val this] on top of stack. * * Note: the 'strict' flag of a reference returned by GetIdentifierReference * is ignored by GetValue. Hence we don't take a 'strict' parameter. * * The 'throw' flag is needed for implementing 'typeof' for an unreferenced * identifier. An unreference identifier in other contexts generates a * ReferenceError. */ DUK_LOCAL duk_bool_t duk__getvar_helper(duk_hthread *thr, duk_hobject *env, duk_activation *act, duk_hstring *name, duk_bool_t throw_flag) { duk__id_lookup_result ref; duk_tval tv_tmp_obj; duk_tval tv_tmp_key; duk_bool_t parents; DUK_DDD(DUK_DDDPRINT("getvar: thr=%p, env=%p, act=%p, name=%!O " "(env -> %!dO)", (void *) thr, (void *) env, (void *) act, (duk_heaphdr *) name, (duk_heaphdr *) env)); DUK_ASSERT(thr != NULL); DUK_ASSERT(name != NULL); /* env and act may be NULL */ DUK_STATS_INC(thr->heap, stats_getvar_all); DUK_ASSERT_REFCOUNT_NONZERO_HEAPHDR(env); DUK_ASSERT_REFCOUNT_NONZERO_HEAPHDR(name); parents = 1; /* follow parent chain */ if (duk__get_identifier_reference(thr, env, name, act, parents, &ref)) { if (ref.value) { duk_push_tval(thr, ref.value); duk_push_undefined(thr); } else { DUK_ASSERT(ref.holder != NULL); /* ref.holder is safe across the getprop call (even * with side effects) because 'env' is reachable and * ref.holder is a direct heap pointer. */ DUK_TVAL_SET_OBJECT(&tv_tmp_obj, ref.holder); DUK_TVAL_SET_STRING(&tv_tmp_key, name); (void) duk_hobject_getprop(thr, &tv_tmp_obj, &tv_tmp_key); /* [value] */ if (ref.has_this) { duk_push_hobject(thr, ref.holder); } else { duk_push_undefined(thr); } /* [value this] */ } return 1; } else { if (throw_flag) { DUK_ERROR_FMT1(thr, DUK_ERR_REFERENCE_ERROR, "identifier '%s' undefined", (const char *) DUK_HSTRING_GET_DATA(name)); DUK_WO_NORETURN(return 0;); } return 0; } } DUK_INTERNAL duk_bool_t duk_js_getvar_envrec(duk_hthread *thr, duk_hobject *env, duk_hstring *name, duk_bool_t throw_flag) { return duk__getvar_helper(thr, env, NULL, name, throw_flag); } DUK_INTERNAL duk_bool_t duk_js_getvar_activation(duk_hthread *thr, duk_activation *act, duk_hstring *name, duk_bool_t throw_flag) { DUK_ASSERT(act != NULL); return duk__getvar_helper(thr, act->lex_env, act, name, throw_flag); } /* * PUTVAR * * See E5 Sections: * 11.1.2 Identifier Reference * 10.3.1 Identifier Resolution * 11.13.1 Simple Assignment [example of where the Reference is PutValue'd] * 8.7.2 PutValue (V,W) [see especially step 3.b, undefined -> automatic global in non-strict mode] * 8.12.4 [[CanPut]] (P) * 8.12.5 [[Put]] (P) * * Note: may invalidate any valstack (or object) duk_tval pointers because * putting a value may reallocate any object or any valstack. Caller beware. */ DUK_LOCAL void duk__putvar_helper(duk_hthread *thr, duk_hobject *env, duk_activation *act, duk_hstring *name, duk_tval *val, duk_bool_t strict) { duk__id_lookup_result ref; duk_tval tv_tmp_obj; duk_tval tv_tmp_key; duk_bool_t parents; DUK_STATS_INC(thr->heap, stats_putvar_all); DUK_DDD(DUK_DDDPRINT("putvar: thr=%p, env=%p, act=%p, name=%!O, val=%p, strict=%ld " "(env -> %!dO, val -> %!T)", (void *) thr, (void *) env, (void *) act, (duk_heaphdr *) name, (void *) val, (long) strict, (duk_heaphdr *) env, (duk_tval *) val)); DUK_ASSERT(thr != NULL); DUK_ASSERT(name != NULL); DUK_ASSERT(val != NULL); /* env and act may be NULL */ DUK_ASSERT_REFCOUNT_NONZERO_HEAPHDR(env); DUK_ASSERT_REFCOUNT_NONZERO_HEAPHDR(name); DUK_ASSERT_REFCOUNT_NONZERO_TVAL(val); /* * In strict mode E5 protects 'eval' and 'arguments' from being * assigned to (or even declared anywhere). Attempt to do so * should result in a compile time SyntaxError. See the internal * design documentation for details. * * Thus, we should never come here, run-time, for strict code, * and name 'eval' or 'arguments'. */ DUK_ASSERT(!strict || (name != DUK_HTHREAD_STRING_EVAL(thr) && name != DUK_HTHREAD_STRING_LC_ARGUMENTS(thr))); /* * Lookup variable and update in-place if found. */ parents = 1; /* follow parent chain */ if (duk__get_identifier_reference(thr, env, name, act, parents, &ref)) { if (ref.value && (ref.attrs & DUK_PROPDESC_FLAG_WRITABLE)) { /* Update duk_tval in-place if pointer provided and the * property is writable. If the property is not writable * (immutable binding), use duk_hobject_putprop() which * will respect mutability. */ duk_tval *tv_val; tv_val = ref.value; DUK_ASSERT(tv_val != NULL); DUK_TVAL_SET_TVAL_UPDREF(thr, tv_val, val); /* side effects */ /* ref.value invalidated here */ } else { DUK_ASSERT(ref.holder != NULL); DUK_TVAL_SET_OBJECT(&tv_tmp_obj, ref.holder); DUK_TVAL_SET_STRING(&tv_tmp_key, name); (void) duk_hobject_putprop(thr, &tv_tmp_obj, &tv_tmp_key, val, strict); /* ref.value invalidated here */ } return; } /* * Not found: write to global object (non-strict) or ReferenceError * (strict); see E5 Section 8.7.2, step 3. */ if (strict) { DUK_DDD(DUK_DDDPRINT("identifier binding not found, strict => reference error")); DUK_ERROR_FMT1(thr, DUK_ERR_REFERENCE_ERROR, "identifier '%s' undefined", (const char *) DUK_HSTRING_GET_DATA(name)); DUK_WO_NORETURN(return;); } DUK_DDD(DUK_DDDPRINT("identifier binding not found, not strict => set to global")); DUK_TVAL_SET_OBJECT(&tv_tmp_obj, thr->builtins[DUK_BIDX_GLOBAL]); DUK_TVAL_SET_STRING(&tv_tmp_key, name); (void) duk_hobject_putprop(thr, &tv_tmp_obj, &tv_tmp_key, val, 0); /* 0 = no throw */ /* NB: 'val' may be invalidated here because put_value may realloc valstack, * caller beware. */ } DUK_INTERNAL void duk_js_putvar_envrec(duk_hthread *thr, duk_hobject *env, duk_hstring *name, duk_tval *val, duk_bool_t strict) { duk__putvar_helper(thr, env, NULL, name, val, strict); } DUK_INTERNAL void duk_js_putvar_activation(duk_hthread *thr, duk_activation *act, duk_hstring *name, duk_tval *val, duk_bool_t strict) { DUK_ASSERT(act != NULL); duk__putvar_helper(thr, act->lex_env, act, name, val, strict); } /* * DELVAR * * See E5 Sections: * 11.4.1 The delete operator * 10.2.1.1.5 DeleteBinding (N) [declarative environment record] * 10.2.1.2.5 DeleteBinding (N) [object environment record] * * Variable bindings established inside eval() are deletable (configurable), * other bindings are not, including variables declared in global level. * Registers are always non-deletable, and the deletion of other bindings * is controlled by the configurable flag. * * For strict mode code, the 'delete' operator should fail with a compile * time SyntaxError if applied to identifiers. Hence, no strict mode * run-time deletion of identifiers should ever happen. This function * should never be called from strict mode code! */ DUK_LOCAL duk_bool_t duk__delvar_helper(duk_hthread *thr, duk_hobject *env, duk_activation *act, duk_hstring *name) { duk__id_lookup_result ref; duk_bool_t parents; DUK_DDD(DUK_DDDPRINT("delvar: thr=%p, env=%p, act=%p, name=%!O " "(env -> %!dO)", (void *) thr, (void *) env, (void *) act, (duk_heaphdr *) name, (duk_heaphdr *) env)); DUK_ASSERT(thr != NULL); DUK_ASSERT(name != NULL); /* env and act may be NULL */ DUK_ASSERT_REFCOUNT_NONZERO_HEAPHDR(name); parents = 1; /* follow parent chain */ if (duk__get_identifier_reference(thr, env, name, act, parents, &ref)) { if (ref.value && !(ref.attrs & DUK_PROPDESC_FLAG_CONFIGURABLE)) { /* Identifier found in registers (always non-deletable) * or declarative environment record and non-configurable. */ return 0; } DUK_ASSERT(ref.holder != NULL); return duk_hobject_delprop_raw(thr, ref.holder, name, 0); } /* * Not found (even in global object). * * In non-strict mode this is a silent SUCCESS (!), see E5 Section 11.4.1, * step 3.b. In strict mode this case is a compile time SyntaxError so * we should not come here. */ DUK_DDD(DUK_DDDPRINT("identifier to be deleted not found: name=%!O " "(treated as silent success)", (duk_heaphdr *) name)); return 1; } #if 0 /*unused*/ DUK_INTERNAL duk_bool_t duk_js_delvar_envrec(duk_hthread *thr, duk_hobject *env, duk_hstring *name) { return duk__delvar_helper(thr, env, NULL, name); } #endif DUK_INTERNAL duk_bool_t duk_js_delvar_activation(duk_hthread *thr, duk_activation *act, duk_hstring *name) { DUK_ASSERT(act != NULL); return duk__delvar_helper(thr, act->lex_env, act, name); } /* * DECLVAR * * See E5 Sections: * 10.4.3 Entering Function Code * 10.5 Declaration Binding Instantion * 12.2 Variable Statement * 11.1.2 Identifier Reference * 10.3.1 Identifier Resolution * * Variable declaration behavior is mainly discussed in Section 10.5, * and is not discussed in the execution semantics (Sections 11-13). * * Conceptually declarations happen when code (global, eval, function) * is entered, before any user code is executed. In practice, register- * bound identifiers are 'declared' automatically (by virtue of being * allocated to registers with the initial value 'undefined'). Other * identifiers are declared in the function prologue with this primitive. * * Since non-register bindings eventually back to an internal object's * properties, the 'prop_flags' argument is used to specify binding * type: * * - Immutable binding: set DUK_PROPDESC_FLAG_WRITABLE to false * - Non-deletable binding: set DUK_PROPDESC_FLAG_CONFIGURABLE to false * - The flag DUK_PROPDESC_FLAG_ENUMERABLE should be set, although it * doesn't really matter for internal objects * * All bindings are non-deletable mutable bindings except: * * - Declarations in eval code (mutable, deletable) * - 'arguments' binding in strict function code (immutable) * - Function name binding of a function expression (immutable) * * Declarations may go to declarative environment records (always * so for functions), but may also go to object environment records * (e.g. global code). The global object environment has special * behavior when re-declaring a function (but not a variable); see * E5.1 specification, Section 10.5, step 5.e. * * Declarations always go to the 'top-most' environment record, i.e. * we never check the record chain. It's not an error even if a * property (even an immutable or non-deletable one) of the same name * already exists. * * If a declared variable already exists, its value needs to be updated * (if possible). Returns 1 if a PUTVAR needs to be done by the caller; * otherwise returns 0. */ DUK_LOCAL duk_bool_t duk__declvar_helper(duk_hthread *thr, duk_hobject *env, duk_hstring *name, duk_tval *val, duk_small_uint_t prop_flags, duk_bool_t is_func_decl) { duk_hobject *holder; duk_bool_t parents; duk__id_lookup_result ref; duk_tval *tv; DUK_DDD(DUK_DDDPRINT("declvar: thr=%p, env=%p, name=%!O, val=%!T, prop_flags=0x%08lx, is_func_decl=%ld " "(env -> %!iO)", (void *) thr, (void *) env, (duk_heaphdr *) name, (duk_tval *) val, (unsigned long) prop_flags, (unsigned int) is_func_decl, (duk_heaphdr *) env)); DUK_ASSERT(thr != NULL); DUK_ASSERT(env != NULL); DUK_ASSERT(name != NULL); DUK_ASSERT(val != NULL); /* Note: in strict mode the compiler should reject explicit * declaration of 'eval' or 'arguments'. However, internal * bytecode may declare 'arguments' in the function prologue. * We don't bother checking (or asserting) for these now. */ /* Note: val is a stable duk_tval pointer. The caller makes * a value copy into its stack frame, so 'tv_val' is not subject * to side effects here. */ /* * Check whether already declared. * * We need to check whether the binding exists in the environment * without walking its parents. However, we still need to check * register-bound identifiers and the prototype chain of an object * environment target object. */ parents = 0; /* just check 'env' */ if (duk__get_identifier_reference(thr, env, name, NULL, parents, &ref)) { duk_int_t e_idx; duk_int_t h_idx; duk_small_uint_t flags; /* * Variable already declared, ignore re-declaration. * The only exception is the updated behavior of E5.1 for * global function declarations, E5.1 Section 10.5, step 5.e. * This behavior does not apply to global variable declarations. */ if (!(is_func_decl && env == thr->builtins[DUK_BIDX_GLOBAL_ENV])) { DUK_DDD(DUK_DDDPRINT("re-declare a binding, ignoring")); return 1; /* 1 -> needs a PUTVAR */ } /* * Special behavior in E5.1. * * Note that even though parents == 0, the conflicting property * may be an inherited property (currently our global object's * prototype is Object.prototype). Step 5.e first operates on * the existing property (which is potentially in an ancestor) * and then defines a new property in the global object (and * never modifies the ancestor). * * Also note that this logic would become even more complicated * if the conflicting property might be a virtual one. Object * prototype has no virtual properties, though. * * XXX: this is now very awkward, rework. */ DUK_DDD(DUK_DDDPRINT("re-declare a function binding in global object, " "updated E5.1 processing")); DUK_ASSERT(ref.holder != NULL); holder = ref.holder; /* holder will be set to the target object, not the actual object * where the property was found (see duk__get_identifier_reference()). */ DUK_ASSERT(DUK_HOBJECT_GET_CLASS_NUMBER(holder) == DUK_HOBJECT_CLASS_GLOBAL); DUK_ASSERT(!DUK_HOBJECT_HAS_EXOTIC_ARRAY(holder)); /* global object doesn't have array part */ /* XXX: use a helper for prototype traversal; no loop check here */ /* must be found: was found earlier, and cannot be inherited */ for (;;) { DUK_ASSERT(holder != NULL); if (duk_hobject_find_existing_entry(thr->heap, holder, name, &e_idx, &h_idx)) { DUK_ASSERT(e_idx >= 0); break; } /* SCANBUILD: NULL pointer dereference, doesn't actually trigger, * asserted above. */ holder = DUK_HOBJECT_GET_PROTOTYPE(thr->heap, holder); } DUK_ASSERT(holder != NULL); DUK_ASSERT(e_idx >= 0); /* SCANBUILD: scan-build produces a NULL pointer dereference warning * below; it never actually triggers because holder is actually never * NULL. */ /* ref.holder is global object, holder is the object with the * conflicting property. */ flags = DUK_HOBJECT_E_GET_FLAGS(thr->heap, holder, e_idx); if (!(flags & DUK_PROPDESC_FLAG_CONFIGURABLE)) { if (flags & DUK_PROPDESC_FLAG_ACCESSOR) { DUK_DDD(DUK_DDDPRINT("existing property is a non-configurable " "accessor -> reject")); goto fail_existing_attributes; } if (!((flags & DUK_PROPDESC_FLAG_WRITABLE) && (flags & DUK_PROPDESC_FLAG_ENUMERABLE))) { DUK_DDD(DUK_DDDPRINT("existing property is a non-configurable " "plain property which is not writable and " "enumerable -> reject")); goto fail_existing_attributes; } DUK_DDD(DUK_DDDPRINT("existing property is not configurable but " "is plain, enumerable, and writable -> " "allow redeclaration")); } if (holder == ref.holder) { /* XXX: if duk_hobject_define_property_internal() was updated * to handle a pre-existing accessor property, this would be * a simple call (like for the ancestor case). */ DUK_DDD(DUK_DDDPRINT("redefine, offending property in global object itself")); if (flags & DUK_PROPDESC_FLAG_ACCESSOR) { duk_hobject *tmp; tmp = DUK_HOBJECT_E_GET_VALUE_GETTER(thr->heap, holder, e_idx); DUK_HOBJECT_E_SET_VALUE_GETTER(thr->heap, holder, e_idx, NULL); DUK_HOBJECT_DECREF_ALLOWNULL(thr, tmp); DUK_UNREF(tmp); tmp = DUK_HOBJECT_E_GET_VALUE_SETTER(thr->heap, holder, e_idx); DUK_HOBJECT_E_SET_VALUE_SETTER(thr->heap, holder, e_idx, NULL); DUK_HOBJECT_DECREF_ALLOWNULL(thr, tmp); DUK_UNREF(tmp); } else { tv = DUK_HOBJECT_E_GET_VALUE_TVAL_PTR(thr->heap, holder, e_idx); DUK_TVAL_SET_UNDEFINED_UPDREF(thr, tv); } /* Here val would be potentially invalid if we didn't make * a value copy at the caller. */ tv = DUK_HOBJECT_E_GET_VALUE_TVAL_PTR(thr->heap, holder, e_idx); DUK_TVAL_SET_TVAL(tv, val); DUK_TVAL_INCREF(thr, tv); DUK_HOBJECT_E_SET_FLAGS(thr->heap, holder, e_idx, prop_flags); DUK_DDD(DUK_DDDPRINT("updated global binding, final result: " "value -> %!T, prop_flags=0x%08lx", (duk_tval *) DUK_HOBJECT_E_GET_VALUE_TVAL_PTR(thr->heap, holder, e_idx), (unsigned long) prop_flags)); } else { DUK_DDD(DUK_DDDPRINT("redefine, offending property in ancestor")); DUK_ASSERT(ref.holder == thr->builtins[DUK_BIDX_GLOBAL]); duk_push_tval(thr, val); duk_hobject_define_property_internal(thr, ref.holder, name, prop_flags); } return 0; } /* * Not found (in registers or record objects). Declare * to current variable environment. */ /* * Get holder object */ if (DUK_HOBJECT_IS_DECENV(env)) { DUK_ASSERT_HDECENV_VALID((duk_hdecenv *) env); holder = env; } else { DUK_ASSERT_HOBJENV_VALID((duk_hobjenv *) env); holder = ((duk_hobjenv *) env)->target; DUK_ASSERT(holder != NULL); } /* * Define new property * * Note: this may fail if the holder is not extensible. */ /* XXX: this is awkward as we use an internal method which doesn't handle * extensibility etc correctly. Basically we'd want to do a [[DefineOwnProperty]] * or Object.defineProperty() here. */ if (!DUK_HOBJECT_HAS_EXTENSIBLE(holder)) { goto fail_not_extensible; } duk_push_hobject(thr, holder); duk_push_hstring(thr, name); duk_push_tval(thr, val); duk_xdef_prop(thr, -3, prop_flags); /* [holder name val] -> [holder] */ duk_pop_unsafe(thr); return 0; fail_existing_attributes: fail_not_extensible: DUK_ERROR_TYPE(thr, "declaration failed"); DUK_WO_NORETURN(return 0;); } DUK_INTERNAL duk_bool_t duk_js_declvar_activation(duk_hthread *thr, duk_activation *act, duk_hstring *name, duk_tval *val, duk_small_uint_t prop_flags, duk_bool_t is_func_decl) { duk_hobject *env; duk_tval tv_val_copy; DUK_ASSERT(act != NULL); /* * Make a value copy of the input val. This ensures that * side effects cannot invalidate the pointer. */ DUK_TVAL_SET_TVAL(&tv_val_copy, val); val = &tv_val_copy; /* * Delayed env creation check */ if (!act->var_env) { DUK_ASSERT(act->lex_env == NULL); duk_js_init_activation_environment_records_delayed(thr, act); /* 'act' is a stable pointer, so still OK. */ } DUK_ASSERT(act->lex_env != NULL); DUK_ASSERT(act->var_env != NULL); env = act->var_env; DUK_ASSERT(env != NULL); DUK_ASSERT(DUK_HOBJECT_IS_ENV(env)); return duk__declvar_helper(thr, env, name, val, prop_flags, is_func_decl); }