Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Create constructor function .prototype property on-demand #1873

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions src-input/duk_api_stack.c
Original file line number Diff line number Diff line change
Expand Up @@ -5138,6 +5138,10 @@ DUK_LOCAL duk_idx_t duk__push_c_function_raw(duk_hthread *thr, duk_c_function fu

DUK_ASSERT_BIDX_VALID(proto_bidx);
DUK_HOBJECT_SET_PROTOTYPE_INIT_INCREF(thr, (duk_hobject *) obj, thr->builtins[proto_bidx]);

if (flags & DUK_HOBJECT_FLAG_CONSTRUCTABLE) {
DUK_STATS_INC(thr->heap, stats_object_proto_potential);
}
return ret;

api_error:
Expand Down
2 changes: 2 additions & 0 deletions src-input/duk_heap.h
Original file line number Diff line number Diff line change
Expand Up @@ -605,6 +605,8 @@ struct duk_heap {
duk_int_t stats_strtab_litcache_pin;
duk_int_t stats_object_realloc_props;
duk_int_t stats_object_abandon_array;
duk_int_t stats_object_proto_potential;
duk_int_t stats_object_proto_create;
duk_int_t stats_getownpropdesc_count;
duk_int_t stats_getownpropdesc_hit;
duk_int_t stats_getownpropdesc_miss;
Expand Down
6 changes: 4 additions & 2 deletions src-input/duk_heap_markandsweep.c
Original file line number Diff line number Diff line change
Expand Up @@ -1125,8 +1125,10 @@ DUK_LOCAL void duk__dump_stats(duk_heap *heap) {
(long) heap->stats_strtab_resize_check, (long) heap->stats_strtab_resize_grow,
(long) heap->stats_strtab_resize_shrink, (long) heap->stats_strtab_litcache_hit,
(long) heap->stats_strtab_litcache_miss, (long) heap->stats_strtab_litcache_pin));
DUK_D(DUK_DPRINT("stats object: realloc_props=%ld, abandon_array=%ld",
(long) heap->stats_object_realloc_props, (long) heap->stats_object_abandon_array));
DUK_D(DUK_DPRINT("stats object: realloc_props=%ld, abandon_array=%ld, "
"proto_potential=%ld, proto_create=%ld",
(long) heap->stats_object_realloc_props, (long) heap->stats_object_abandon_array,
(long) heap->stats_object_proto_potential, (long) heap->stats_object_proto_create));
DUK_D(DUK_DPRINT("stats getownpropdesc: count=%ld, hit=%ld, miss=%ld",
(long) heap->stats_getownpropdesc_count, (long) heap->stats_getownpropdesc_hit,
(long) heap->stats_getownpropdesc_miss));
Expand Down
11 changes: 11 additions & 0 deletions src-input/duk_hobject_enum.c
Original file line number Diff line number Diff line change
Expand Up @@ -477,6 +477,17 @@ DUK_INTERNAL void duk_hobject_enumerator_create(duk_hthread *thr, duk_small_uint
/* [enum_target res] */
}

/*
* On-demand created .prototype property.
*/

/* FIXME: helper for condition */
if ((enum_flags & DUK_ENUM_INCLUDE_NONENUMERABLE) &&
DUK_HOBJECT_HAS_CONSTRUCTABLE(curr) &&
!DUK_HOBJECT_HAS_NATFUNC(curr)) {
duk__add_enum_key_stridx(thr, DUK_STRIDX_PROTOTYPE);
}

/* Sort enumerated keys according to ES2015 requirements for
* the "inheritance level" just processed. This is far from
* optimal, ES2015 semantics could be achieved more efficiently
Expand Down
93 changes: 93 additions & 0 deletions src-input/duk_hobject_props.c
Original file line number Diff line number Diff line change
Expand Up @@ -351,6 +351,84 @@ DUK_LOCAL duk_bool_t duk__abandon_array_slow_check_required(duk_uint32_t arr_idx
return (arr_idx > DUK_USE_HOBJECT_ARRAY_FAST_RESIZE_LIMIT * ((old_size + 7) >> 3));
}

/*
* On-demand .prototype for functions
*
* The automatic .prototype property of function objects is writable, but
* not enumerable or configurable. We create the property on-demand when
* its presence matters:
*
* - On a direct property access with the property key "prototype".
* - Object.defineProperty().
* - Object.getOwnPropertyKeys() or other enumeration which also includes
* non-enumerable keys. In these cases only the string "prototype"
* must be returned, the property itself doesn't (yet) need to be created.
*
* Because .prototype is non-configurable, it cannot be deleted. So it should
* be safe to add the property if it is missing and then never delete it.
*/

/* XXX: separate flag for virtual .prototype, not just any constructable
* function lacking the property?
*/
/* XXX: DUK_DEFPROP_FORCE vs. deletion of .prototype */
/* XXX: enumeration order */

DUK_INTERNAL duk_bool_t duk_hobject_ondemand_proto_check(duk_hthread *thr, duk_hobject *obj) {
duk_int_t e_idx;
duk_int_t h_idx;
duk_hstring **keyptr;
duk_bool_t rc;

/* Automatic .prototype property only applies to constructable
* functions, e.g. function expressions, not e.g. object literal
* getters/setters, arrow functions, etc.
*/
if (!DUK_HOBJECT_HAS_CONSTRUCTABLE(obj)) {
return 0;
}

/* While adding automatic .prototype objects for Duktape/C functions
* would be nice, it'd be an incompatible change in 2.x so skip that
* for now.
*/
if (DUK_HOBJECT_HAS_NATFUNC(obj)) {
return 0;
}

/* Assumption: caller has already checked that the property doesn't
* exist.
*/
DUK_STATS_INC(thr->heap, stats_object_proto_create);
DUK_D(DUK_DPRINT("create .prototype on-demand for %!O", obj));
duk_push_hobject(thr, obj);
duk_push_object(thr); /* -> [ ... func proto ] */
duk_dup_m2(thr); /* -> [ ... func proto func ] */
duk_xdef_prop_stridx(thr, -2, DUK_STRIDX_CONSTRUCTOR, DUK_PROPDESC_FLAGS_WC | DUK_DEFPROP_FORCE); /* -> [ ... func proto ] */
duk_compact(thr, -1); /* compact the prototype */

/* When we add the .prototype property we don't want to do a property
* descriptor lookup because it'd cause infinite recursion. For now,
* define the property using a different name and then rename it in
* place.
*/
duk_xdef_prop_stridx(thr, -2, DUK_STRIDX_INT_TARGET, DUK_PROPDESC_FLAGS_W | DUK_DEFPROP_FORCE); /* -> [ ... func ] */

rc = duk_hobject_find_existing_entry(thr->heap, obj, DUK_HTHREAD_STRING_INT_TARGET(thr), &e_idx, &h_idx);
DUK_UNREF(rc);
DUK_ASSERT(rc != 0);
DUK_ASSERT(e_idx >= 0);
keyptr = DUK_HOBJECT_E_GET_KEY_PTR(thr->heap, obj, e_idx);
DUK_ASSERT(keyptr != NULL);
DUK_ASSERT(*keyptr == DUK_HTHREAD_STRING_INT_TARGET(thr));
*keyptr = DUK_HTHREAD_STRING_PROTOTYPE(thr);
DUK_HSTRING_INCREF(thr, DUK_HTHREAD_STRING_PROTOTYPE(thr));
DUK_HSTRING_DECREF(thr, DUK_HTHREAD_STRING_INT_TARGET(thr));

duk_pop_unsafe(thr);
return 1;
}

/*
* Proxy helpers
*/
Expand Down Expand Up @@ -1753,6 +1831,21 @@ DUK_LOCAL duk_bool_t duk__get_own_propdesc_raw(duk_hthread *thr, duk_hobject *ob
* Not found as a concrete property, check for virtual properties.
*/

/* If object has an on-demand .prototype property, create it now and
* retry via recursion.
*/
/* XXX: faster check for key, e.g. by adding DUK_HSTRING_IS_PROTOTYPE */
/* XXX: enable "has virtual properties" for at least constructable functions,
* so that we can avoid this check in the fast path?
*/
if (DUK_UNLIKELY(key == DUK_HTHREAD_STRING_PROTOTYPE(thr))) {
DUK_D(DUK_DPRINT("ondemand .property, missing -> create?"));
if (duk_hobject_ondemand_proto_check(thr, obj)) {
DUK_D(DUK_DPRINT("ondemand .property, retry own propdesc lookup"));
return duk__get_own_propdesc_raw(thr, obj, key, arr_idx, out_desc, flags);
}
}

if (!DUK_HOBJECT_HAS_VIRTUAL_PROPERTIES(obj)) {
/* Quick skip. */
goto prop_not_found;
Expand Down
1 change: 1 addition & 0 deletions src-input/duk_hstring.h
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
/* With lowmem builds the high 16 bits of duk_heaphdr are used for other
* purposes, so this leaves 7 duk_heaphdr flags and 9 duk_hstring flags.
*/
/* FIXME: eval_or_arguments -> FLAG_PROTOTYPE */
#define DUK_HSTRING_FLAG_ASCII DUK_HEAPHDR_USER_FLAG(0) /* string is ASCII, clen == blen */
#define DUK_HSTRING_FLAG_ARRIDX DUK_HEAPHDR_USER_FLAG(1) /* string is a valid array index */
#define DUK_HSTRING_FLAG_SYMBOL DUK_HEAPHDR_USER_FLAG(2) /* string is a symbol (invalid utf-8) */
Expand Down
5 changes: 5 additions & 0 deletions src-input/duk_js_var.c
Original file line number Diff line number Diff line change
Expand Up @@ -395,11 +395,14 @@ void duk_js_push_closure(duk_hthread *thr,
/* [ ... closure template ] */

if (add_auto_proto) {
DUK_STATS_INC(thr->heap, stats_object_proto_potential);
#if 0 /* FIXME: testing */
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 ] */
#endif
}

/*
Expand Down Expand Up @@ -470,7 +473,9 @@ void duk_js_push_closure(duk_hthread *thr,
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);
#if 0 /* FIXME: has check triggers creation of .prototype! */
DUK_ASSERT(add_auto_proto == 0 || duk_has_prop_stridx(thr, -2, DUK_STRIDX_PROTOTYPE) != 0);
#endif
/* May be missing .name */
DUK_ASSERT(!DUK_HOBJECT_HAS_STRICT(&fun_clos->obj) ||
duk_has_prop_stridx(thr, -2, DUK_STRIDX_CALLER) != 0);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/*---
{
"custom": true
}
---*/

/*===
- Object.defineProperty() and .prototype
TypeError
number 321
number 123
TypeError
done
===*/

// Because the .prototype is intended to be writable, but configurable or
// enumerable, Object.defineProperty() should not allow modification of
// any attributes except: (1) to disable writable, (2) to update the value.
print('- Object.defineProperty() and .prototype');
try {
// This must fail because the (actually non-existent) .prototype is
// not configurable.
var X = function () {};
Object.defineProperty(X, 'prototype', { enumerable: true });
print('never here');
} catch (e) {
print(e.name);
}
try {
// This must succeed because the .prototype is writable.
var X = function () {};
Object.defineProperty(X, 'prototype', { value: 321 });
print(typeof X.prototype, X.prototype);
} catch (e) {
print(e);
}
try {
// Defining all attributes which are compatible with the virtualized
// existing ones -> must succeed.
var X = function () {};
Object.defineProperty(X, 'prototype', {
value: 123,
writable: true,
enumerable: false,
configurable: false
});
print(typeof X.prototype, X.prototype);
} catch (e) {
print(e);
}
try {
// Defining all attributes which are incompatible with the virtualized
// existing ones -> must fail.
var X = function () {};
Object.defineProperty(X, 'prototype', {
value: 123,
writable: true,
enumerable: true, // incompatible
configurable: false
});
print('never here');
} catch (e) {
print(e.name);
}

print('done');
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/*---
{
"custom": true
}
---*/

/*===
- the virtualized .prototype property appears in Object.getOwnPropertyNames()
fileName
length
prototype
- enumeration position
0 fileName
1 length
2 foo
3 prototype
[object Object]
0 fileName
1 length
2 foo
3 prototype
4 bar
- the virtualized .prototype property does not appear in for-in enumeration
before
[object Object]
after
done
===*/

print('- the virtualized .prototype property appears in Object.getOwnPropertyNames()');
var X = function () {};
Object.getOwnPropertyNames(X).forEach(function (v) {
print(v);
});

// This behavior is liable to change without notice: the .prototype property
// does not have a stable order: its apparent position changes when the property
// is actually created. This is not ideal and may be fixed later.
print('- enumeration position');
var X = function () {};
X.foo = 123;
Object.getOwnPropertyNames(X).forEach(function (v, i) {
print(i, v);
});
print(X.prototype); // creates property
X.bar = 321;
Object.getOwnPropertyNames(X).forEach(function (v, i) {
print(i, v);
});

print('- the virtualized .prototype property does not appear in for-in enumeration');
print('before');
var X = function () {};
for (var k in X) {
print(k);
}
print(X.prototype); // creates property
print('after');
for (var k in X) {
print(k);
}

print('done');
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/*---
{
"custom": true
}
---*/

/*===
- Object.getOwnPropertyDescriptor() spawns .prototype
object
object
true
false
false
done
===*/

print('- Object.getOwnPropertyDescriptor() spawns .prototype');
var X = function () {};
var pd = Object.getOwnPropertyDescriptor(X, 'prototype');
print(typeof pd);
print(typeof pd.value);
print(pd.writable);
print(pd.enumerable);
print(pd.configurable);

print('done');
23 changes: 23 additions & 0 deletions tests/ecmascript/test-dev-ondemand-constructor-prototype-misc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/*---
{
"custom": true
}
---*/

print('- non-constructable built-ins have no .prototype');
var X = Math.cos;
print('prototype' in X);
print(X.prototype);
print('prototype' in X);

print('- non-constructable functions like object getter/setters have no .prototype');
var tmp = {
get X() {},
};
X = Object.getOwnPropertyDescriptor(tmp, 'X').get;
print(typeof X);
print('prototype' in X);
print(X.prototype);
print('prototype' in X);

print('done');
Loading