diff --git a/src-input/duk_bi_object.c b/src-input/duk_bi_object.c index 9751fe9512..f1b602ef33 100644 --- a/src-input/duk_bi_object.c +++ b/src-input/duk_bi_object.c @@ -690,7 +690,7 @@ DUK_INTERNAL duk_ret_t duk_bi_object_constructor_keys_shared(duk_hthread *thr) { DUK_ASSERT(magic >= 0 && magic < (duk_int_t) (sizeof(duk__object_keys_enum_flags) / sizeof(duk_small_uint_t))); enum_flags = duk__object_keys_enum_flags[magic]; - duk_proxy_ownkeys_postprocess(thr, h_proxy_target, enum_flags); + duk_proxy_ownkeys_postprocess(thr, (duk_hproxy *) obj, enum_flags); return 1; skip_proxy: diff --git a/src-input/duk_bi_protos.h b/src-input/duk_bi_protos.h index 7c1488d4c8..595aeea357 100644 --- a/src-input/duk_bi_protos.h +++ b/src-input/duk_bi_protos.h @@ -75,7 +75,7 @@ void duk_bi_json_stringify_helper(duk_hthread *thr, DUK_INTERNAL_DECL duk_ret_t duk_textdecoder_decode_utf8_nodejs(duk_hthread *thr); #if defined(DUK_USE_ES6_PROXY) -DUK_INTERNAL_DECL void duk_proxy_ownkeys_postprocess(duk_hthread *thr, duk_hobject *h_proxy_target, duk_uint_t flags); +DUK_INTERNAL_DECL void duk_proxy_ownkeys_postprocess(duk_hthread *thr, duk_hproxy *h_proxy, duk_uint_t flags); #endif #endif /* DUK_BUILTIN_PROTOS_H_INCLUDED */ diff --git a/src-input/duk_bi_proxy.c b/src-input/duk_bi_proxy.c index a98a5b49a0..bb47da4153 100644 --- a/src-input/duk_bi_proxy.c +++ b/src-input/duk_bi_proxy.c @@ -9,12 +9,12 @@ * array of valid result keys (strings or symbols). TypeError for invalid * values. Flags are shared with duk_enum(). */ -DUK_INTERNAL void duk_proxy_ownkeys_postprocess(duk_hthread *thr, duk_hobject *h_proxy_target, duk_uint_t flags) { +DUK_INTERNAL void duk_proxy_ownkeys_postprocess(duk_hthread *thr, duk_hproxy *h_proxy, duk_uint_t flags) { duk_uarridx_t i, len, idx; duk_propdesc desc; - DUK_ASSERT_CTX_VALID(thr); - DUK_ASSERT(h_proxy_target != NULL); + DUK_ASSERT_CTX_VALID(ctx); + DUK_ASSERT(h_proxy != NULL); len = (duk_uarridx_t) duk_get_length(thr, -1); idx = 0; @@ -32,11 +32,13 @@ DUK_INTERNAL void duk_proxy_ownkeys_postprocess(duk_hthread *thr, duk_hobject *h } if (!(flags & DUK_ENUM_INCLUDE_NONENUMERABLE)) { - /* No support for 'getOwnPropertyDescriptor' trap yet, - * so check enumerability always from target object - * descriptor. + /* Check enumerability from Proxy so that a possible + * getOwnPropertyDescriptor trap can be invoked. It + * would be tempting to cache the trap function but + * it may be removed as a side effect of a previous + * call so we must look it up every time. */ - if (duk_hobject_get_own_propdesc(thr, h_proxy_target, duk_known_hstring(thr, -1), &desc, 0 /*flags*/)) { + if (duk_hobject_get_own_propdesc(thr, (duk_hobject *) h_proxy, duk_known_hstring(thr, -1), &desc, 0 /*flags*/)) { if ((desc.flags & DUK_PROPDESC_FLAG_ENUMERABLE) == 0) { DUK_DDD(DUK_DDDPRINT("ignore non-enumerable property: %!T", duk_get_tval(thr, -1))); goto skip_key; diff --git a/src-input/duk_hobject.h b/src-input/duk_hobject.h index 6a8d255192..f9dc369903 100644 --- a/src-input/duk_hobject.h +++ b/src-input/duk_hobject.h @@ -748,8 +748,8 @@ union duk_propvalue { struct duk_propdesc { /* read-only values 'lifted' for ease of use */ duk_small_uint_t flags; - duk_hobject *get; - duk_hobject *set; + duk_hobject *get; /* borrowed */ + duk_hobject *set; /* borrowed */ /* for updating (all are set to < 0 for virtual properties) */ duk_int_t e_idx; /* prop index in 'entry part', < 0 if not there */ diff --git a/src-input/duk_hobject_enum.c b/src-input/duk_hobject_enum.c index 9f9bcb9125..9f4ceb62cd 100644 --- a/src-input/duk_hobject_enum.c +++ b/src-input/duk_hobject_enum.c @@ -268,7 +268,8 @@ DUK_INTERNAL void duk_hobject_enumerator_create(duk_hthread *thr, duk_small_uint h_trap_result = duk_require_hobject(thr, -1); DUK_UNREF(h_trap_result); - duk_proxy_ownkeys_postprocess(thr, h_proxy_target, enum_flags); + /* FIXME: enum_target vs. h_proxy_target? */ + duk_proxy_ownkeys_postprocess(thr, (duk_hproxy *) enum_target, enum_flags); /* -> [ ... enum_target res trap_result keys_array ] */ /* Copy cleaned up trap result keys into the enumerator object. */ diff --git a/src-input/duk_hobject_props.c b/src-input/duk_hobject_props.c index 2883623ac4..ad03b978ed 100644 --- a/src-input/duk_hobject_props.c +++ b/src-input/duk_hobject_props.c @@ -1595,6 +1595,8 @@ DUK_LOCAL void duk__check_arguments_map_for_delete(duk_hthread *thr, duk_hobject DUK_ASSERT_VALSTACK_SPACE(thr, DUK__VALSTACK_SPACE); + /* FIXME: find_existing_entry, removes propdesc usage */ + if (!duk_hobject_get_own_propdesc(thr, obj, DUK_HTHREAD_STRING_INT_MAP(thr), temp_desc, DUK_GETDESC_FLAG_PUSH_VALUE)) { DUK_DDD(DUK_DDDPRINT("arguments: key not mapped, no exotic delete behavior")); return; @@ -1686,6 +1688,66 @@ DUK_LOCAL duk_bool_t duk__get_own_propdesc_raw(duk_hthread *thr, duk_hobject *ob out_desc->a_idx = -1; #endif + /* FIXME: property descriptor helpers would need to be reworked + * so that both object and duk_propdesc outputs are supported. + */ +#if defined(DUK_USE_ES6_PROXY) + if (DUK_UNLIKELY(DUK_HOBJECT_IS_PROXY(obj))) { + duk_hproxy *h_proxy; + duk_hobject *h_target; + duk_tval tv_key; + + DUK_TVAL_SET_STRING(&tv_key, key); + if (duk__proxy_check_prop(thr, obj, DUK_STRIDX_GET_OWN_PROPERTY_DESCRIPTOR, &tv_key, &h_target)) { + duk_push_hobject(thr, h_target); /* target */ + duk_push_hstring(thr, key); /* P */ + duk_call_method(thr, 2 /*nargs*/); + + /* XXX: At present out_desc->{get,set} are borrowed so they're + * provided as NULL even when the descriptor has them. + */ + + /* FIXME: validate descriptor, convert it to internal form. + * At present out_desc->get etc are borrowed so value, get, + * set must be scrubbed, leaving only attributes behind which + * is fine for now. + */ + /* FIXME: arguments special */ + + out_desc->get = NULL; + out_desc->set = NULL; + out_desc->e_idx = -1; + out_desc->h_idx = -1; + out_desc->a_idx = -1; + out_desc->flags = 0; + if (duk_get_prop_stridx_boolean(thr, -1, DUK_STRIDX_WRITABLE, NULL)) { + out_desc->flags |= DUK_PROPDESC_FLAG_WRITABLE; + } + if (duk_get_prop_stridx_boolean(thr, -1, DUK_STRIDX_ENUMERABLE, NULL)) { + out_desc->flags |= DUK_PROPDESC_FLAG_ENUMERABLE; + } + if (duk_get_prop_stridx_boolean(thr, -1, DUK_STRIDX_CONFIGURABLE, NULL)) { + out_desc->flags |= DUK_PROPDESC_FLAG_CONFIGURABLE; + } + out_desc->flags |= DUK_PROPDESC_FLAG_VIRTUAL; + + if (flags & DUK_GETDESC_FLAG_PUSH_VALUE) { + duk_get_prop_stridx(thr, -1, DUK_STRIDX_VALUE); + duk_remove_m2(thr); + } else { + duk_pop(thr); + } + + return 1; + } + + DUK_D(DUK_DPRINT("getting own property descriptor for Proxy")); + h_proxy = (duk_hproxy *) obj; + DUK_ASSERT(h_proxy->target != NULL); + obj = h_proxy->target; + } +#endif /* DUK_USE_ES6_PROXY */ + /* * Try entries part first because it's the common case. * @@ -3870,6 +3932,7 @@ DUK_INTERNAL duk_bool_t duk_hobject_putprop(duk_hthread *thr, duk_tval *tv_obj, goto fail_not_writable; } #endif + /* FIXME: add explicit virtual property safety check */ /* Although there are writable virtual properties (e.g. plain buffer * and buffer object number indices), they are handled before we come @@ -4974,6 +5037,7 @@ void duk_hobject_prepare_property_descriptor(duk_hthread *thr, idx_value = duk_get_top_index(thr); } + /* FIXME: some overlap here */ if (duk_get_prop_stridx(thr, idx_in, DUK_STRIDX_WRITABLE)) { is_data_desc = 1; if (duk_to_boolean(thr, -1)) { @@ -5116,6 +5180,10 @@ duk_bool_t duk_hobject_define_property_helper(duk_hthread *thr, DUK_ASSERT_VALSTACK_SPACE(thr, DUK__VALSTACK_SPACE); + /* FIXME: maybe add defineProperty trap at the same time, so that only + * non-Proxy objects come here? + */ + /* All the flags fit in 16 bits, so will fit into duk_bool_t. */ has_writable = (defprop_flags & DUK_DEFPROP_HAVE_WRITABLE); @@ -5151,6 +5219,21 @@ duk_bool_t duk_hobject_define_property_helper(duk_hthread *thr, (long) has_set, (void *) set, (duk_heaphdr *) set, (long) arr_idx, (long) throw_flag)); + /* + * Proxy objects. + */ + +#if defined(DUK_USE_ES6_PROXY) + if (DUK_UNLIKELY(DUK_HOBJECT_IS_PROXY(obj))) { + duk_hproxy *h_proxy; + + h_proxy = (duk_hproxy *) obj; + obj = h_proxy->target; + DUK_ASSERT(obj != NULL); + DUK_ASSERT(!DUK_HOBJECT_IS_PROXY(obj)); + } +#endif /* DUK_USE_ES6_PROXY */ + /* * Array exotic behaviors can be implemented at this point. The local variables * are essentially a 'value copy' of the input descriptor (Desc), which is modified diff --git a/tests/ecmascript/test-bi-array-proto-reverse.js b/tests/ecmascript/test-bi-array-proto-reverse.js index cb94a0b06c..30470bed66 100644 --- a/tests/ecmascript/test-bi-array-proto-reverse.js +++ b/tests/ecmascript/test-bi-array-proto-reverse.js @@ -1,27 +1,6 @@ // XXX: shared test utils -function formatValue(v) { - if (typeof v === 'function') { - return 'function'; - } - return typeof(v) + ':' + String(v); -} - -function printDescriptor(obj, key) { - var pd = Object.getOwnPropertyDescriptor(obj, key); - if (!pd) { - print('key=' + key, 'nonexistent'); - return; - } - - print('key=' + key, - 'value=' + formatValue(pd.value), - 'writable=' + formatValue(pd.writable), - 'enumerable=' + formatValue(pd.enumerable), - 'configurable=' + formatValue(pd.configurable), - 'get=' + formatValue(pd.get), - 'set=' + formatValue(pd.set)); -} +/*@include util-object.js@*/ function dumpValue(x) { var i, n, clipped; @@ -157,14 +136,14 @@ try { /*=== attributes true true false false false true false true -key=0 value=undefined:undefined writable=undefined:undefined enumerable=boolean:false configurable=boolean:true get=function set=function -key=1 value=undefined:undefined writable=undefined:undefined enumerable=boolean:false configurable=boolean:true get=function set=function -key=2 nonexistent -key=3 nonexistent -key=4 nonexistent -key=5 value=undefined:undefined writable=undefined:undefined enumerable=boolean:true configurable=boolean:true get=function set=function -key=6 nonexistent -key=7 value=undefined:undefined writable=undefined:undefined enumerable=boolean:true configurable=boolean:true get=function set=function +key: 0, desc: get=function,set=function,enumerable=false,configurable=true +key: 1, desc: get=function,set=function,enumerable=false,configurable=true +key: 2, desc: none +key: 3, desc: none +key: 4, desc: none +key: 5, desc: get=function,set=function,enumerable=true,configurable=true +key: 6, desc: none +key: 7, desc: get=function,set=function,enumerable=true,configurable=true get 0 get 7 set 0 val7 @@ -172,18 +151,18 @@ set 7 val0 get 1 get 5 true false true false false false true true -key=0 value=undefined:undefined writable=undefined:undefined enumerable=boolean:false configurable=boolean:true get=function set=function -key=1 nonexistent -key=2 value=string:val5 writable=boolean:true enumerable=boolean:true configurable=boolean:true get=undefined:undefined set=undefined:undefined -key=3 nonexistent -key=4 nonexistent -key=5 nonexistent -key=6 value=string:val1 writable=boolean:true enumerable=boolean:true configurable=boolean:true get=undefined:undefined set=undefined:undefined -key=7 value=undefined:undefined writable=undefined:undefined enumerable=boolean:true configurable=boolean:true get=function set=function -key=0 value=string:foo writable=boolean:true enumerable=boolean:false configurable=boolean:true get=undefined:undefined set=undefined:undefined -key=1 nonexistent -key=0 value=string:foo writable=boolean:true enumerable=boolean:false configurable=boolean:true get=undefined:undefined set=undefined:undefined -key=1 nonexistent +key: 0, desc: get=function,set=function,enumerable=false,configurable=true +key: 1, desc: none +key: 2, desc: value="val5",writable=true,enumerable=true,configurable=true +key: 3, desc: none +key: 4, desc: none +key: 5, desc: none +key: 6, desc: value="val1",writable=true,enumerable=true,configurable=true +key: 7, desc: get=function,set=function,enumerable=true,configurable=true +key: 0, desc: value="foo",writable=true,enumerable=false,configurable=true +key: 1, desc: none +key: 0, desc: value="foo",writable=true,enumerable=false,configurable=true +key: 1, desc: none ===*/ print('attributes'); @@ -245,7 +224,7 @@ function attributesTest() { t.hasOwnProperty(6), t.hasOwnProperty(7)); for (i = 0; i < 8; i++) { - printDescriptor(t, String(i)); + printKeyPropDesc(t, String(i)); } t.reverse(); @@ -256,7 +235,7 @@ function attributesTest() { t.hasOwnProperty(6), t.hasOwnProperty(7)); for (i = 0; i < 8; i++) { - printDescriptor(t, String(i)); + printKeyPropDesc(t, String(i)); } // Here, '0' is not enumerable, but when it gets swapped to position 1, @@ -269,11 +248,11 @@ function attributesTest() { '0': { value: 'foo', writable: true, enumerable: false, configurable: true }, 'length': { value: 2 } }); - printDescriptor(t, '0'); - printDescriptor(t, '1'); + printKeyPropDesc(t, '0'); + printKeyPropDesc(t, '1'); [].reverse(); - printDescriptor(t, '0'); - printDescriptor(t, '1'); + printKeyPropDesc(t, '0'); + printKeyPropDesc(t, '1'); } try { diff --git a/tests/ecmascript/test-bi-proxy-defineproperty.js b/tests/ecmascript/test-bi-proxy-defineproperty.js new file mode 100644 index 0000000000..7ad6f3eb7b --- /dev/null +++ b/tests/ecmascript/test-bi-proxy-defineproperty.js @@ -0,0 +1,40 @@ +/* + * Proxy (ES2015) 'defineProperty'. + */ + +/*@include util-object.js@*/ + +/*=== +value=123,writable=false,enumerable=false,configurable=true +get=function,set=function,enumerable=false,configurable=true +===*/ + +function passThroughTest() { + var T = {}; + var P = new Proxy(T, {}); + var pd; + + Object.defineProperty(P, 'foo', { + value: 123, + writable: false, + enumerable: false, + configurable: true + }); + Object.defineProperty(P, 'bar', { + get: function getter() {}, + set: function setter() {}, + enumerable: false, + configurable: true + }); + + pd = Object.getOwnPropertyDescriptor(T, 'foo'); + printPropDesc(pd); + pd = Object.getOwnPropertyDescriptor(T, 'bar'); + printPropDesc(pd); +} + +try { + passThroughTest(); +} catch (e) { + print(e.stack || e); +} diff --git a/tests/ecmascript/test-bi-proxy-getownpropertydescriptor.js b/tests/ecmascript/test-bi-proxy-getownpropertydescriptor.js new file mode 100644 index 0000000000..afbb3b4110 --- /dev/null +++ b/tests/ecmascript/test-bi-proxy-getownpropertydescriptor.js @@ -0,0 +1,80 @@ +/* + * Proxy (ES2015) 'getOwnPropertyDescriptor'. + */ + +/*@include util-object.js@*/ + +/*=== +value=123,writable=true,enumerable=false,configurable=true +get=function,enumerable=true,configurable=false +set=function,enumerable=false,configurable=false +get=function,set=function,enumerable=true,configurable=true +value=123,writable=true,enumerable=false,configurable=true +get=function,enumerable=true,configurable=false +set=function,enumerable=false,configurable=false +get=function,set=function,enumerable=true,configurable=true +===*/ + +function passThroughTest() { + var T = {}; + var P = new Proxy(T, {}); + var pd; + + Object.defineProperty(T, 'foo', { value: 123, writable: true, enumerable: false, configurable: true }); + Object.defineProperty(T, 'bar', { get: function getter() {}, enumerable: true, configurable: false }); + Object.defineProperty(T, 'quux', { set: function setter() {}, enumerable: false, configurable: false }); + Object.defineProperty(T, 'baz', { get: function getter() {}, set: function setter() {}, enumerable: true, configurable: true }); + + pd = Object.getOwnPropertyDescriptor(T, 'foo'); + printPropDesc(pd); + pd = Object.getOwnPropertyDescriptor(T, 'bar'); + printPropDesc(pd); + pd = Object.getOwnPropertyDescriptor(T, 'quux'); + printPropDesc(pd); + pd = Object.getOwnPropertyDescriptor(T, 'baz'); + printPropDesc(pd); + + // As of Duktape 2.2 getOwnPropertyDescriptor() properly passes through + // to the target object. + pd = Object.getOwnPropertyDescriptor(P, 'foo'); + printPropDesc(pd); + pd = Object.getOwnPropertyDescriptor(P, 'bar'); + printPropDesc(pd); + pd = Object.getOwnPropertyDescriptor(P, 'quux'); + printPropDesc(pd); + pd = Object.getOwnPropertyDescriptor(P, 'baz'); + printPropDesc(pd); +} + +try { + passThroughTest(); +} catch (e) { + print(e.stack || e); +} + +/*=== +===*/ + +function miscTest() { + var O = { foo: 123 }; + var P = new Proxy(O, {}); + + printPropDesc(Object.getOwnPropertyDescriptor(O, 'foo')); + printPropDesc(Object.getOwnPropertyDescriptor(P, 'foo')); + + var O = { foo: 123 }; + var P = new Proxy(O, { + getOwnPropertyDescriptor: function () { + return { value: 321, writable: false, enumerable: false, configurable: true }; + } + }); + + printPropDesc(Object.getOwnPropertyDescriptor(O, 'foo')); + printPropDesc(Object.getOwnPropertyDescriptor(P, 'foo')); +} + +try { + miscTest(); +} catch (e) { + print(e.stack || e); +} diff --git a/tests/ecmascript/test-bi-proxy-virtualized-enum.js b/tests/ecmascript/test-bi-proxy-virtualized-enum.js new file mode 100644 index 0000000000..ec7cfb89d7 --- /dev/null +++ b/tests/ecmascript/test-bi-proxy-virtualized-enum.js @@ -0,0 +1,62 @@ +/* + * Example of virtualizing enumeration using a Proxy. + */ + +/*=== +ownKeys trap: true +getOwnPropertyDescriptor trap: true true string 0 +getOwnPropertyDescriptor trap: true true string 1 +getOwnPropertyDescriptor trap: true true string 2 +getOwnPropertyDescriptor trap: true true string 3 +getOwnPropertyDescriptor trap: true true string 4 +getOwnPropertyDescriptor trap: true true string noSuch +getOwnPropertyDescriptor trap: true true string length +0 +1 +2 +3 +4 +noSuch +===*/ + +function test() { + var target = [ 'foo', 'bar', 'quux' ]; + var handler = { + ownKeys: function () { + // The ownKeys result set is validated against the Proxy to ensure + // enumerated keys are enumerable; unless a getOwnPropertyDescriptor + // trap exists these checks will be made against the target which + // means non-existent properties won't be enumerated.ยง + print('ownKeys trap:', this === handler); + return [ '0', '1', '2', '3', '4', 'noSuch', 'length' ] + }, + getOwnPropertyDescriptor: function (targ, key) { + // Cannot report 'length' as enumerable (that would violate + // getOwnPropertyDescriptor trap constraints). However, + // Duktape 2.2 (at least) won't enforce that constraint yet. + print('getOwnPropertyDescriptor trap:', this === handler, targ === target, typeof key, key); + if (key === 'length') { + return Object.getOwnPropertyDescriptor(target, key); + } + return { + value: target[key], + enumerable: true, + writable: true, + configurable: true + }; + } + }; + var proxy = new Proxy(target, handler); + + // Without 'getOwnPropertyDescriptor' trap the only keys visible would be + // '0', '1', and '2' which backing in the target object. + for (var k in proxy) { + print(k); + } +} + +try { + test(); +} catch (e) { + print(e.stack || e); +} diff --git a/tests/ecmascript/test-dev-array-property-attrs.js b/tests/ecmascript/test-dev-array-property-attrs.js index 006b823006..3fba640c17 100644 --- a/tests/ecmascript/test-dev-array-property-attrs.js +++ b/tests/ecmascript/test-dev-array-property-attrs.js @@ -3,31 +3,29 @@ * and without an internal "array part". */ -function printDesc(desc) { - print(desc.writable, desc.enumerable, desc.configurable); -} +/*@include util-object.js@*/ /*=== -true true true -true true true -true true true -true true true -true true true -true true true -true true true +value=1,writable=true,enumerable=true,configurable=true +value=2,writable=true,enumerable=true,configurable=true +value=3,writable=true,enumerable=true,configurable=true +value=1,writable=true,enumerable=true,configurable=true +value=2,writable=true,enumerable=true,configurable=true +value=3,writable=true,enumerable=true,configurable=true +value=4,writable=true,enumerable=true,configurable=true ===*/ /* array is initially dense (array part exists) */ a = [1,2,3]; -printDesc(Object.getOwnPropertyDescriptor(a, '0')); -printDesc(Object.getOwnPropertyDescriptor(a, '1')); -printDesc(Object.getOwnPropertyDescriptor(a, '2')); +printPropDesc(Object.getOwnPropertyDescriptor(a, '0')); +printPropDesc(Object.getOwnPropertyDescriptor(a, '1')); +printPropDesc(Object.getOwnPropertyDescriptor(a, '2')); /* force array to be sparse (array part is abandoned) */ a[10000] = 4; -printDesc(Object.getOwnPropertyDescriptor(a, '0')); -printDesc(Object.getOwnPropertyDescriptor(a, '1')); -printDesc(Object.getOwnPropertyDescriptor(a, '2')); -printDesc(Object.getOwnPropertyDescriptor(a, '10000')); +printPropDesc(Object.getOwnPropertyDescriptor(a, '0')); +printPropDesc(Object.getOwnPropertyDescriptor(a, '1')); +printPropDesc(Object.getOwnPropertyDescriptor(a, '2')); +printPropDesc(Object.getOwnPropertyDescriptor(a, '10000')); diff --git a/tests/ecmascript/util-object.js b/tests/ecmascript/util-object.js index 0441f6129f..9fbc834759 100644 --- a/tests/ecmascript/util-object.js +++ b/tests/ecmascript/util-object.js @@ -47,3 +47,50 @@ function getValuePublicType(val) { function getValueInternalTag(val) { return getDuktapeInfoField(val, 1, 'itag'); } + +// String convert a descriptor object. +function propDescToString(pd) { + function fmt(v) { + if (typeof v === 'function') { + return 'function'; + } + return Duktape.enc('jx', v); + } + + if (!pd) { + return 'none'; + } + + var res = []; + if (pd.value !== void 0) { + res.push('value=' + fmt(pd.value)); + } + if (pd.get !== void 0) { + res.push('get=' + fmt(pd.get)); + } + if (pd.set !== void 0) { + res.push('set=' + fmt(pd.set)); + } + if (pd.writable !== void 0) { + res.push('writable=' + fmt(pd.writable)); + } + if (pd.enumerable !== void 0) { + res.push('enumerable=' + fmt(pd.enumerable)); + } + if (pd.configurable !== void 0) { + res.push('configurable=' + fmt(pd.configurable)); + } + + return res.join(','); +} + +// Print a descriptor object. +function printPropDesc(pd) { + print(propDescToString(pd)); +} + +// Print a key and a related property descriptor. +function printKeyPropDesc(obj, key) { + var pd = Object.getOwnPropertyDescriptor(obj, key); + print('key: ' + key + ', desc: ' + propDescToString(pd)); +}