|
|
|
/*
|
|
|
|
* 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.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#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_harray *formals;
|
|
|
|
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);
|
|
|
|
|
|
|
|
DUK_STATS_INC(thr->heap, stats_envrec_pushclosure);
|
|
|
|
|
|
|
|
fun_clos = duk_push_hcompfunc(thr);
|
|
|
|
DUK_ASSERT(fun_clos != NULL);
|
|
|
|
DUK_ASSERT(duk_hobject_get_proto_raw(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_proto_raw(thr->heap, &fun_clos->obj) == thr->builtins[DUK_BIDX_FUNCTION_PROTOTYPE]);
|
|
|
|
#if 0
|
|
|
|
duk_hobject_set_proto_raw_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_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: <func>, __prototype: outer_lex_env }
|
|
|
|
* b) { funcname: <func>, __prototype: <globalenv> } (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_HEAPHDR_HTYPE_AS_FLAGS(DUK_HTYPE_DECENV));
|
|
|
|
DUK_ASSERT(new_env != NULL);
|
|
|
|
duk_push_hobject(thr, (duk_hobject *) new_env);
|
|
|
|
|
|
|
|
DUK_ASSERT(duk_hobject_get_proto_raw(thr->heap, (duk_hobject *) new_env) == NULL);
|
|
|
|
duk_hobject_set_proto_raw(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_known(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.
|
|
|
|
*
|
|
|
|
* Function templates are bare objects, so inheritance of internal
|
|
|
|
* Symbols is not an issue here even when using ordinary property
|
|
|
|
* reads. The function instance created is not bare, so internal
|
|
|
|
* Symbols must be defined without inheritance checks.
|
|
|
|
*/
|
|
|
|
|
|
|
|
/* [ ... 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_xget_owndataprop_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_known(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 ] */
|
|
|
|
|
|
|
|
formals = duk_hobject_get_formals(thr, (duk_hobject *) fun_temp);
|
|
|
|
if (formals) {
|
|
|
|
len_value = (duk_uint_t) DUK_HARRAY_GET_LENGTH(formals);
|
|
|
|
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_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 in ES5.1 (E5 Section 15.3.5). This is
|
|
|
|
* no longer required in ES2015+ for any functions; instead .arguments
|
|
|
|
* and .caller are throwing accessors in Function.prototype for all
|
|
|
|
* function types.
|
|
|
|
*
|
|
|
|
* V8 provides .caller and .arguments own properties (null) for
|
|
|
|
* non-strict functions. We omit these and rely on the throwing
|
|
|
|
* accessors to reduce function size. Both behaviors are compliant.
|
|
|
|
*/
|
|
|
|
|
|
|
|
/* [ ... closure template ] */
|
|
|
|
|
|
|
|
#if defined(DUK_USE_NONSTD_FUNC_CALLER_PROPERTY)
|
|
|
|
if (!DUK_HOBJECT_HAS_STRICT(&fun_clos->obj)) {
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#if 0 /* V8 provides immutable null value .caller and .arguments for non-strict functions. */
|
|
|
|
if (DUK_HOBJECT_HAS_STRICT(&fun_clos->obj)) {
|
|
|
|
;
|
|
|
|
} else {
|
|
|
|
duk_push_null(thr);
|
|
|
|
duk_xdef_prop_stridx_short(thr, -3, DUK_STRIDX_CALLER, DUK_PROPDESC_FLAGS_NONE);
|
|
|
|
duk_push_null(thr);
|
|
|
|
duk_xdef_prop_stridx_short(thr, -3, DUK_STRIDX_LC_ARGUMENTS, DUK_PROPDESC_FLAGS_NONE);
|
|
|
|
}
|
|
|
|
#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_known(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.
|
|
|
|
*/
|
|
|
|
|
|
|
|
DUK_ASSERT(DUK_HOBJECT_GET_HTYPE(&fun_clos->obj) == DUK_HTYPE_COMPFUNC);
|
|
|
|
DUK_ASSERT(duk_hobject_get_proto_raw(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_has_prop_stridx(thr, -2, DUK_STRIDX_CALLER) != 0);
|
|
|
|
DUK_ASSERT(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_known(thr);
|
|
|
|
|
|
|
|
/* [ ... closure ] */
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Delayed activation environment record initialization (for functions
|
|
|
|
* with NEWENV).
|
|
|
|
*
|
|
|
|
* 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. Or by having an internal
|
|
|
|
* duk_prop_setown custom algorithm which just ignores
|
|
|
|
* inheritance.
|
|
|
|
*/
|
|
|
|
duk_push_undefined(thr);
|
|
|
|
DUK_DDD(DUK_DDDPRINT("preallocate env entry for key %!O", key));
|
|
|
|
(void) duk_prop_defown_strkey(thr,
|
|
|
|
env,
|
|
|
|
key,
|
|
|
|
duk_get_top_index_known(thr),
|
|
|
|
DUK_DEFPROP_ATTR_WE | DUK_DEFPROP_HAVE_VALUE);
|
|
|
|
duk_pop_known(thr);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* 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);
|
|
|
|
|
|
|
|
DUK_STATS_INC(thr->heap, stats_envrec_create);
|
|
|
|
|
|
|
|
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_HEAPHDR_HTYPE_AS_FLAGS(DUK_HTYPE_DECENV));
|
|
|
|
DUK_ASSERT(env != NULL);
|
|
|
|
duk_push_hobject(thr, (duk_hobject *) env);
|
|
|
|
|
|
|
|
DUK_ASSERT(duk_hobject_get_proto_raw(thr->heap, (duk_hobject *) env) == NULL);
|
|
|
|
duk_hobject_set_proto_raw(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;
|
|
|
|
|
|
|
|
varmap = duk_hobject_get_varmap(thr, func);
|
|
|
|
if (varmap != NULL) {
|
|
|
|
env->varmap = varmap;
|
|
|
|
DUK_HOBJECT_INCREF(thr, varmap);
|
|
|
|
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);
|
|
|
|
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);
|
First round of lightfunc changes
A lot of changes to add preliminary lightfunc support:
* Add LIGHTFUNC tagged type to duk_tval.h and API.
* Internal changes for preliminary to support lightfuncs in call handling
and other operations (FIXMEs left in obvious places where support is
still missing after this commit)
* Preliminary Ecmascript and API testcases for lightfuncs
Detailed notes:
* Because magic is signed, reading it back involves sign extension which is
quite verbose to do in C. Use macros for reading the magic value and other
bit fields encoded in the flags.
* Function.prototype.bind(): the 'length' property of a bound function now
comes out wrong. We could simply look up the virtual 'length' property
even if h_target is NULL: no extra code and binding is relatively rare in
hot paths. Rewrite more cleanly in any case.
* The use flag DUK_USE_LIGHTFUNC_BUILTINS controls the forced lightfunc
conversion of built-ins. This results in non-compliant built-ins but
significant memory savings in very memory poor environments.
* Reject eval(), Thread.yield/resume as lightfuncs. These functions have
current assertions that they must be called as fully fledged functions.
* Lightfuncs are serialized like ordinary functions for JSON, JX, and JC
by this diff.
* Add 'magic' to activation for lightfuncs. It will be needed for lightweight
functions: we don't have the duk_tval related to the lightfunc, so we must
copy the magic value to the activation when a call is made.
* When lightfuncs are used as property lookup base values, continue property
lookup from the Function.prototype object. This is necessary to allow e.g.
``func.call()`` and ``func.apply()`` to be used.
* Call handling had to be reworked for lightfuncs, especially how bound
function chains are handled. This is a relatively large change but is
necessary to support lightweight functions properly in bound function
resolution.
The current solution is not ideal. The bytecode executor will first try an
ecma-to-ecma call setup which resolves the bound function chain first. If
the final, unbound function is not viable (a native function) the call setup
returns with an error code. The caller will then perform a normal call.
Although bound function resolution has already been done, the normal call
handling code will re-do it (and detect there is nothing to do).
This situation could be avoided by decoupling bound function handling and
effective this binding computation from the actual call setup. The caller
could then to do this prestep first, and only then decide whether to use an
ecma-to-ecma call or an ordinary heavyweight call.
Remove duk__find_nonbound_function as unused.
* Use indirect magic to allow LIGHTFUNCs for Date. Most of the built-in
functions not directly eligible as lightfuncs are the Date built-in methods,
whose magic values contain too much information to fit into the 8-bit magic
of a LIGHTFUNC value.
To work around this, add an array (duk__date_magics[]) containing the
actual control flags needed by the built-ins, and make the Date built-in
magic value an index into this table. With this change Date built-ins are
successfully converted to lightfuncs.
Testcase fixes:
- Whitespace fixes
- Print error for indirect eval error to make diagnosis easier
- Fix error string to match errmsg updated in this branch
11 years ago
|
|
|
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);
|
|
|
|
|
|
|
|
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. */
|
|
|
|
|
|
|
|
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_proto_raw(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_known(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_HDECENV_ASSERT_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);
|
|
|
|
|
|
|
|
/* 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",
|
|
|
|
(duk_heaphdr *) key,
|
|
|
|
(long) regnum,
|
|
|
|
(duk_tval *) duk_get_tval(thr, -1)));
|
|
|
|
|
|
|
|
(void) duk_prop_defown_strkey(thr,
|
|
|
|
env,
|
|
|
|
key,
|
|
|
|
duk_get_top_index_known(thr),
|
|
|
|
DUK_DEFPROP_ATTR_WE | DUK_DEFPROP_HAVE_VALUE);
|
|
|
|
duk_pop_known(thr);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* 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_HDECENV_ASSERT_VALID(env);
|
|
|
|
|
|
|
|
if (env->thread == NULL) {
|
|
|
|
/* already closed */
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
DUK_ASSERT(env->varmap != NULL);
|
|
|
|
|
|
|
|
tv = duk_hobject_find_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);
|
|
|
|
|
First round of lightfunc changes
A lot of changes to add preliminary lightfunc support:
* Add LIGHTFUNC tagged type to duk_tval.h and API.
* Internal changes for preliminary to support lightfuncs in call handling
and other operations (FIXMEs left in obvious places where support is
still missing after this commit)
* Preliminary Ecmascript and API testcases for lightfuncs
Detailed notes:
* Because magic is signed, reading it back involves sign extension which is
quite verbose to do in C. Use macros for reading the magic value and other
bit fields encoded in the flags.
* Function.prototype.bind(): the 'length' property of a bound function now
comes out wrong. We could simply look up the virtual 'length' property
even if h_target is NULL: no extra code and binding is relatively rare in
hot paths. Rewrite more cleanly in any case.
* The use flag DUK_USE_LIGHTFUNC_BUILTINS controls the forced lightfunc
conversion of built-ins. This results in non-compliant built-ins but
significant memory savings in very memory poor environments.
* Reject eval(), Thread.yield/resume as lightfuncs. These functions have
current assertions that they must be called as fully fledged functions.
* Lightfuncs are serialized like ordinary functions for JSON, JX, and JC
by this diff.
* Add 'magic' to activation for lightfuncs. It will be needed for lightweight
functions: we don't have the duk_tval related to the lightfunc, so we must
copy the magic value to the activation when a call is made.
* When lightfuncs are used as property lookup base values, continue property
lookup from the Function.prototype object. This is necessary to allow e.g.
``func.call()`` and ``func.apply()`` to be used.
* Call handling had to be reworked for lightfuncs, especially how bound
function chains are handled. This is a relatively large change but is
necessary to support lightweight functions properly in bound function
resolution.
The current solution is not ideal. The bytecode executor will first try an
ecma-to-ecma call setup which resolves the bound function chain first. If
the final, unbound function is not viable (a native function) the call setup
returns with an error code. The caller will then perform a normal call.
Although bound function resolution has already been done, the normal call
handling code will re-do it (and detect there is nothing to do).
This situation could be avoided by decoupling bound function handling and
effective this binding computation from the actual call setup. The caller
could then to do this prestep first, and only then decide whether to use an
ecma-to-ecma call or an ordinary heavyweight call.
Remove duk__find_nonbound_function as unused.
* Use indirect magic to allow LIGHTFUNCs for Date. Most of the built-in
functions not directly eligible as lightfuncs are the Date built-in methods,
whose magic values contain too much information to fit into the 8-bit magic
of a LIGHTFUNC value.
To work around this, add an array (duk__date_magics[]) containing the
actual control flags needed by the built-ins, and make the Date built-in
magic value an index into this table. With this change Date built-ins are
successfully converted to lightfuncs.
Testcase fixes:
- Whitespace fixes
- Print error for indirect eval error to make diagnosis easier
- Fix error string to match errmsg updated in this branch
11 years ago
|
|
|
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? */
|
|
|
|
varmap = duk_hobject_get_varmap(thr, func);
|
|
|
|
if (!varmap) {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
tv = duk_hobject_find_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 == NULL || DUK_HOBJECT_IS_ENV(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;
|
|
|
|
}
|
|
|
|
|
First round of lightfunc changes
A lot of changes to add preliminary lightfunc support:
* Add LIGHTFUNC tagged type to duk_tval.h and API.
* Internal changes for preliminary to support lightfuncs in call handling
and other operations (FIXMEs left in obvious places where support is
still missing after this commit)
* Preliminary Ecmascript and API testcases for lightfuncs
Detailed notes:
* Because magic is signed, reading it back involves sign extension which is
quite verbose to do in C. Use macros for reading the magic value and other
bit fields encoded in the flags.
* Function.prototype.bind(): the 'length' property of a bound function now
comes out wrong. We could simply look up the virtual 'length' property
even if h_target is NULL: no extra code and binding is relatively rare in
hot paths. Rewrite more cleanly in any case.
* The use flag DUK_USE_LIGHTFUNC_BUILTINS controls the forced lightfunc
conversion of built-ins. This results in non-compliant built-ins but
significant memory savings in very memory poor environments.
* Reject eval(), Thread.yield/resume as lightfuncs. These functions have
current assertions that they must be called as fully fledged functions.
* Lightfuncs are serialized like ordinary functions for JSON, JX, and JC
by this diff.
* Add 'magic' to activation for lightfuncs. It will be needed for lightweight
functions: we don't have the duk_tval related to the lightfunc, so we must
copy the magic value to the activation when a call is made.
* When lightfuncs are used as property lookup base values, continue property
lookup from the Function.prototype object. This is necessary to allow e.g.
``func.call()`` and ``func.apply()`` to be used.
* Call handling had to be reworked for lightfuncs, especially how bound
function chains are handled. This is a relatively large change but is
necessary to support lightweight functions properly in bound function
resolution.
The current solution is not ideal. The bytecode executor will first try an
ecma-to-ecma call setup which resolves the bound function chain first. If
the final, unbound function is not viable (a native function) the call setup
returns with an error code. The caller will then perform a normal call.
Although bound function resolution has already been done, the normal call
handling code will re-do it (and detect there is nothing to do).
This situation could be avoided by decoupling bound function handling and
effective this binding computation from the actual call setup. The caller
could then to do this prestep first, and only then decide whether to use an
ecma-to-ecma call or an ordinary heavyweight call.
Remove duk__find_nonbound_function as unused.
* Use indirect magic to allow LIGHTFUNCs for Date. Most of the built-in
functions not directly eligible as lightfuncs are the Date built-in methods,
whose magic values contain too much information to fit into the 8-bit magic
of a LIGHTFUNC value.
To work around this, add an array (duk__date_magics[]) containing the
actual control flags needed by the built-ins, and make the Date built-in
magic value an index into this table. With this change Date built-ins are
successfully converted to lightfuncs.
Testcase fixes:
- Whitespace fixes
- Print error for indirect eval error to make diagnosis easier
- Fix error string to match errmsg updated in this branch
11 years ago
|
|
|
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 htype;
|
|
|
|
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));
|
|
|
|
|
|
|
|
htype = DUK_HOBJECT_GET_HTYPE(env);
|
|
|
|
DUK_ASSERT(htype == DUK_HTYPE_OBJENV || htype == DUK_HTYPE_DECENV);
|
|
|
|
if (htype == DUK_HTYPE_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_HDECENV_ASSERT_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_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(htype == DUK_HTYPE_OBJENV);
|
|
|
|
DUK_HOBJENV_ASSERT_VALID((duk_hobjenv *) env);
|
|
|
|
|
|
|
|
target = ((duk_hobjenv *) env)->target;
|
|
|
|
DUK_ASSERT(target != NULL);
|
|
|
|
|
|
|
|
/* Target may be a Proxy (or inherit from one), 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 (1) {
|
|
|
|
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_prop_has(thr, &tv_target_tmp, &tv_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_PROTO_SANITY(thr);
|
|
|
|
DUK_WO_NORETURN(return 0;);
|
|
|
|
}
|
|
|
|
env = duk_hobject_get_proto_raw(thr->heap, env);
|
Use DUK_HOBJECT_FLAG_HAVE_FINALIZER for checks
One bottleneck in refzero and mark-and-sweep handling is checking whether an
object has an own or inherited _Finalizer property. This check walked the
prototype chain and did a property lookup for every object. Because a
finalizer is usually not present, the prototype chain would almost always be
walked to completion.
Improve this behavior by:
* Adding a DUK_HOBJECT_FLAG_HAVE_FINALIZER flag. The flag is set when the
object has an own _Finalizer property with a callable value, and cleared
otherwise. The flag is *only* set by duk_set_finalizer(), so any other
means of changing the internal _Finalizer property will leave the flag out
of sync (which causes a finalizer run to be skipped).
* Adding duk_hobject_has_finalizer_fast() which checks for finalizer existence
by walking the prototype chain, but only checking the flag, not the property
table.
* Use the fast finalizer check in refzero and mark-and-sweep.
Out-of sync cases:
* If the flag is set but there is no actual finalizer, the object will go
through finalizer processing when garbage collecting. This is harmless:
the finalizer call will fail and the object will be garbage collected, but
with some potential delay (especially for mark-and-sweep).
* If the flag is cleared but there is an actual finalizer, the finalizer will
be ignored.
Related changes:
* When duk_dump_function() is called, zero DUK_HOBJECT_FLAG_HAVE_FINALIZER on
serialization, so it won't be set when the function is loaded back. If this
is not done, the loaded function will (harmlessly) go through finalizer
processing when garbage collected.
* Update debugger artificial properties to include "have_finalizer" flag.
Other changes:
* A few DUK_UNLIKELY() attributes for prototype sanity limits which are
almost never hit.
8 years ago
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* 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));
|
|
|
|
|
|
|
|
/* 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_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_idx_t top_idx;
|
|
|
|
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.
|
|
|
|
*/
|
|
|
|
|
|
|
|
top_idx = thr->valstack_top - thr->valstack_bottom;
|
|
|
|
duk_push_hobject(thr, ref.holder);
|
|
|
|
(void) duk_prop_getvalue_strkey_outidx(thr, top_idx, name, top_idx);
|
|
|
|
|
|
|
|
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_val;
|
|
|
|
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);
|
|
|
|
|
|
|
|
DUK_TVAL_SET_TVAL(&tv_tmp_val, val); /* Stabilize. */
|
|
|
|
val = NULL;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* 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_prop_putvalue_inidx()
|
|
|
|
* 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, &tv_tmp_val); /* side effects */
|
|
|
|
|
|
|
|
/* ref.value invalidated here */
|
|
|
|
} else {
|
|
|
|
DUK_ASSERT(ref.holder != NULL);
|
|
|
|
|
|
|
|
duk_push_hobject(thr, ref.holder);
|
|
|
|
duk_push_tval_unsafe(thr, &tv_tmp_val);
|
|
|
|
DUK_TVAL_SET_STRING(&tv_tmp_key, name);
|
|
|
|
(void) duk_prop_putvalue_inidx(thr, duk_get_top(thr) - 2, &tv_tmp_key, duk_get_top(thr) - 1, strict);
|
|
|
|
duk_pop_2_known(thr);
|
|
|
|
|
|
|
|
/* 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_push_hobject(thr, thr->builtins[DUK_BIDX_GLOBAL]);
|
|
|
|
duk_push_tval_unsafe(thr, &tv_tmp_val);
|
|
|
|
DUK_TVAL_SET_STRING(&tv_tmp_key, name);
|
|
|
|
(void) duk_prop_putvalue_inidx(thr, duk_get_top(thr) - 2, &tv_tmp_key, duk_get_top(thr) - 1, 0 /* no throw */);
|
|
|
|
duk_pop_2_known(thr);
|
|
|
|
|
|
|
|
/* 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_prop_delete_obj_strkey(thr, ref.holder, name, 0 /*delprop_flags*/); /* no throw */
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* 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_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_small_int_t attrs;
|
|
|
|
duk_small_uint_t uattrs;
|
|
|
|
duk_bool_t do_full_write;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* 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 */
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* ES5.1 added special processing for redeclaring globals.
|
|
|
|
* ES2015+ changed the processing to ignore global object
|
|
|
|
* inherited properties.
|
|
|
|
*
|
|
|
|
* Implementation fuses CanDeclareGlobalFunction() and
|
|
|
|
* CreateGlobalFunctionBinding() which is not 100% correct,
|
|
|
|
* but closer to ES2015+ than ES5.1 now.
|
|
|
|
*/
|
|
|
|
|
|
|
|
DUK_DDD(DUK_DDDPRINT("re-declare a function binding in global object"));
|
|
|
|
|
|
|
|
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_HTYPE(holder) == DUK_HTYPE_GLOBAL);
|
|
|
|
DUK_ASSERT(!DUK_HOBJECT_HAS_EXOTIC_ARRAY(holder));
|
|
|
|
|
|
|
|
attrs = duk_prop_getownattr_obj_strkey(thr, holder, name);
|
|
|
|
if (attrs >= 0) {
|
|
|
|
uattrs = (duk_small_uint_t) attrs;
|
|
|
|
if (uattrs & DUK_PROPDESC_FLAG_CONFIGURABLE) {
|
|
|
|
do_full_write = 1;
|
|
|
|
} else {
|
|
|
|
if (uattrs & DUK_PROPDESC_FLAG_ACCESSOR) {
|
|
|
|
goto fail_existing_attributes;
|
|
|
|
}
|
|
|
|
if (!((uattrs & DUK_PROPDESC_FLAG_WRITABLE) && (uattrs & DUK_PROPDESC_FLAG_ENUMERABLE))) {
|
|
|
|
goto fail_existing_attributes;
|
|
|
|
}
|
|
|
|
do_full_write = 0;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if (duk_js_isextensible(thr, holder)) {
|
|
|
|
do_full_write = 1;
|
|
|
|
} else {
|
|
|
|
goto fail_not_extensible;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
duk_push_tval(thr, val);
|
|
|
|
(void) duk_prop_defown_strkey(thr,
|
|
|
|
ref.holder,
|
|
|
|
name,
|
|
|
|
duk_get_top_index_known(thr),
|
|
|
|
prop_flags | (do_full_write ? DUK_DEFPROP_HAVE_WEC : 0) | DUK_DEFPROP_HAVE_VALUE);
|
|
|
|
duk_pop_known(thr);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Not found (in registers or record objects). Declare
|
|
|
|
* to current variable environment.
|
|
|
|
*/
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Get holder object
|
|
|
|
*/
|
|
|
|
|
|
|
|
if (DUK_HOBJECT_IS_DECENV(env)) {
|
|
|
|
DUK_HDECENV_ASSERT_VALID((duk_hdecenv *) env);
|
|
|
|
holder = env;
|
|
|
|
} else {
|
|
|
|
DUK_HOBJENV_ASSERT_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_known(thr);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
fail_internal_error:
|
|
|
|
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);
|
|
|
|
}
|