1 /*
2 * Object built-ins
3 */
4
5 #include "duk_internal.h"
6
duk_bi_object_constructor(duk_context * ctx)7 DUK_INTERNAL duk_ret_t duk_bi_object_constructor(duk_context *ctx) {
8 if (!duk_is_constructor_call(ctx) &&
9 !duk_is_null_or_undefined(ctx, 0)) {
10 duk_to_object(ctx, 0);
11 return 1;
12 }
13
14 if (duk_is_object(ctx, 0)) {
15 return 1;
16 }
17
18 /* Pointer and buffer primitive values are treated like other
19 * primitives values which have a fully fledged object counterpart:
20 * promote to an object value. Lightfuncs are coerced with
21 * ToObject() even they could also be returned as is.
22 */
23 if (duk_check_type_mask(ctx, 0, DUK_TYPE_MASK_STRING |
24 DUK_TYPE_MASK_BOOLEAN |
25 DUK_TYPE_MASK_NUMBER |
26 DUK_TYPE_MASK_POINTER |
27 DUK_TYPE_MASK_BUFFER |
28 DUK_TYPE_MASK_LIGHTFUNC)) {
29 duk_to_object(ctx, 0);
30 return 1;
31 }
32
33 duk_push_object_helper(ctx,
34 DUK_HOBJECT_FLAG_EXTENSIBLE |
35 DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_OBJECT),
36 DUK_BIDX_OBJECT_PROTOTYPE);
37 return 1;
38 }
39
40 /* Shared helper to implement Object.getPrototypeOf and the ES6
41 * Object.prototype.__proto__ getter.
42 *
43 * http://www.ecma-international.org/ecma-262/6.0/index.html#sec-get-object.prototype.__proto__
44 */
duk_bi_object_getprototype_shared(duk_context * ctx)45 DUK_INTERNAL duk_ret_t duk_bi_object_getprototype_shared(duk_context *ctx) {
46 duk_hthread *thr = (duk_hthread *) ctx;
47 duk_hobject *h;
48 duk_hobject *proto;
49
50 DUK_UNREF(thr);
51
52 /* magic: 0=getter call, 1=Object.getPrototypeOf */
53 if (duk_get_current_magic(ctx) == 0) {
54 duk_push_this_coercible_to_object(ctx);
55 duk_insert(ctx, 0);
56 }
57
58 h = duk_require_hobject_or_lfunc(ctx, 0);
59 /* h is NULL for lightfunc */
60
61 /* XXX: should the API call handle this directly, i.e. attempt
62 * to duk_push_hobject(ctx, null) would push a null instead?
63 * (On the other hand 'undefined' would be just as logical, but
64 * not wanted here.)
65 */
66
67 if (h == NULL) {
68 duk_push_hobject_bidx(ctx, DUK_BIDX_FUNCTION_PROTOTYPE);
69 } else {
70 proto = DUK_HOBJECT_GET_PROTOTYPE(thr->heap, h);
71 if (proto) {
72 duk_push_hobject(ctx, proto);
73 } else {
74 duk_push_null(ctx);
75 }
76 }
77 return 1;
78 }
79
80 /* Shared helper to implement ES6 Object.setPrototypeOf and
81 * Object.prototype.__proto__ setter.
82 *
83 * http://www.ecma-international.org/ecma-262/6.0/index.html#sec-get-object.prototype.__proto__
84 * http://www.ecma-international.org/ecma-262/6.0/index.html#sec-object.setprototypeof
85 */
duk_bi_object_setprototype_shared(duk_context * ctx)86 DUK_INTERNAL duk_ret_t duk_bi_object_setprototype_shared(duk_context *ctx) {
87 duk_hthread *thr = (duk_hthread *) ctx;
88 duk_hobject *h_obj;
89 duk_hobject *h_new_proto;
90 duk_hobject *h_curr;
91 duk_ret_t ret_success = 1; /* retval for success path */
92
93 /* Preliminaries for __proto__ and setPrototypeOf (E6 19.1.2.18 steps 1-4);
94 * magic: 0=setter call, 1=Object.setPrototypeOf
95 */
96 if (duk_get_current_magic(ctx) == 0) {
97 duk_push_this_check_object_coercible(ctx);
98 duk_insert(ctx, 0);
99 if (!duk_check_type_mask(ctx, 1, DUK_TYPE_MASK_NULL | DUK_TYPE_MASK_OBJECT)) {
100 return 0;
101 }
102
103 /* __proto__ setter returns 'undefined' on success unlike the
104 * setPrototypeOf() call which returns the target object.
105 */
106 ret_success = 0;
107 } else {
108 duk_require_object_coercible(ctx, 0);
109 duk_require_type_mask(ctx, 1, DUK_TYPE_MASK_NULL | DUK_TYPE_MASK_OBJECT);
110 }
111
112 h_new_proto = duk_get_hobject(ctx, 1);
113 /* h_new_proto may be NULL */
114 if (duk_is_lightfunc(ctx, 0)) {
115 if (h_new_proto == thr->builtins[DUK_BIDX_FUNCTION_PROTOTYPE]) {
116 goto skip;
117 }
118 goto fail_nonextensible;
119 }
120 h_obj = duk_get_hobject(ctx, 0);
121 if (!h_obj) {
122 goto skip;
123 }
124 DUK_ASSERT(h_obj != NULL);
125
126 /* [[SetPrototypeOf]] standard behavior, E6 9.1.2 */
127 /* TODO: implement Proxy object support here */
128
129 if (h_new_proto == DUK_HOBJECT_GET_PROTOTYPE(thr->heap, h_obj)) {
130 goto skip;
131 }
132 if (!DUK_HOBJECT_HAS_EXTENSIBLE(h_obj)) {
133 goto fail_nonextensible;
134 }
135 for (h_curr = h_new_proto; h_curr != NULL; h_curr = DUK_HOBJECT_GET_PROTOTYPE(thr->heap, h_curr)) {
136 /* Loop prevention */
137 if (h_curr == h_obj) {
138 goto fail_loop;
139 }
140 }
141 DUK_HOBJECT_SET_PROTOTYPE_UPDREF(thr, h_obj, h_new_proto);
142 /* fall thru */
143
144 skip:
145 duk_set_top(ctx, 1);
146 return ret_success;
147
148 fail_nonextensible:
149 fail_loop:
150 return DUK_RET_TYPE_ERROR;
151 }
152
duk_bi_object_constructor_get_own_property_descriptor(duk_context * ctx)153 DUK_INTERNAL duk_ret_t duk_bi_object_constructor_get_own_property_descriptor(duk_context *ctx) {
154 /* XXX: no need for indirect call */
155 return duk_hobject_object_get_own_property_descriptor(ctx);
156 }
157
duk_bi_object_constructor_create(duk_context * ctx)158 DUK_INTERNAL duk_ret_t duk_bi_object_constructor_create(duk_context *ctx) {
159 duk_tval *tv;
160 duk_hobject *proto = NULL;
161
162 DUK_ASSERT_TOP(ctx, 2);
163
164 tv = duk_get_tval(ctx, 0);
165 DUK_ASSERT(tv != NULL);
166 if (DUK_TVAL_IS_NULL(tv)) {
167 ;
168 } else if (DUK_TVAL_IS_OBJECT(tv)) {
169 proto = DUK_TVAL_GET_OBJECT(tv);
170 DUK_ASSERT(proto != NULL);
171 } else {
172 return DUK_RET_TYPE_ERROR;
173 }
174
175 (void) duk_push_object_helper_proto(ctx,
176 DUK_HOBJECT_FLAG_EXTENSIBLE |
177 DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_OBJECT),
178 proto);
179
180 if (!duk_is_undefined(ctx, 1)) {
181 /* [ O Properties obj ] */
182
183 duk_replace(ctx, 0);
184
185 /* [ obj Properties ] */
186
187 /* Just call the "original" Object.defineProperties() to
188 * finish up.
189 */
190
191 return duk_bi_object_constructor_define_properties(ctx);
192 }
193
194 /* [ O Properties obj ] */
195
196 return 1;
197 }
198
duk_bi_object_constructor_define_property(duk_context * ctx)199 DUK_INTERNAL duk_ret_t duk_bi_object_constructor_define_property(duk_context *ctx) {
200 duk_hobject *obj;
201 duk_hstring *key;
202 duk_hobject *get;
203 duk_hobject *set;
204 duk_idx_t idx_value;
205 duk_uint_t defprop_flags;
206
207 DUK_ASSERT(ctx != NULL);
208
209 DUK_DDD(DUK_DDDPRINT("Object.defineProperty(): ctx=%p obj=%!T key=%!T desc=%!T",
210 (void *) ctx,
211 (duk_tval *) duk_get_tval(ctx, 0),
212 (duk_tval *) duk_get_tval(ctx, 1),
213 (duk_tval *) duk_get_tval(ctx, 2)));
214
215 /* [ obj key desc ] */
216
217 /* Lightfuncs are currently supported by coercing to a temporary
218 * Function object; changes will be allowed (the coerced value is
219 * extensible) but will be lost.
220 */
221 obj = duk_require_hobject_or_lfunc_coerce(ctx, 0);
222 (void) duk_to_string(ctx, 1);
223 key = duk_require_hstring(ctx, 1);
224 (void) duk_require_hobject(ctx, 2);
225
226 DUK_ASSERT(obj != NULL);
227 DUK_ASSERT(key != NULL);
228 DUK_ASSERT(duk_get_hobject(ctx, 2) != NULL);
229
230 /*
231 * Validate and convert argument property descriptor (an Ecmascript
232 * object) into a set of defprop_flags and possibly property value,
233 * getter, and/or setter values on the value stack.
234 *
235 * Lightfunc set/get values are coerced to full Functions.
236 */
237
238 duk_hobject_prepare_property_descriptor(ctx,
239 2 /*idx_desc*/,
240 &defprop_flags,
241 &idx_value,
242 &get,
243 &set);
244
245 /*
246 * Use Object.defineProperty() helper for the actual operation.
247 */
248
249 duk_hobject_define_property_helper(ctx,
250 defprop_flags,
251 obj,
252 key,
253 idx_value,
254 get,
255 set);
256
257 /* Ignore the normalize/validate helper outputs on the value stack,
258 * they're popped automatically.
259 */
260
261 /*
262 * Return target object.
263 */
264
265 duk_push_hobject(ctx, obj);
266 return 1;
267 }
268
duk_bi_object_constructor_define_properties(duk_context * ctx)269 DUK_INTERNAL duk_ret_t duk_bi_object_constructor_define_properties(duk_context *ctx) {
270 duk_small_uint_t pass;
271 duk_uint_t defprop_flags;
272 duk_hobject *obj;
273 duk_idx_t idx_value;
274 duk_hobject *get;
275 duk_hobject *set;
276
277 /* Lightfunc handling by ToObject() coercion. */
278 obj = duk_require_hobject_or_lfunc_coerce(ctx, 0); /* target */
279 DUK_ASSERT(obj != NULL);
280
281 duk_to_object(ctx, 1); /* properties object */
282
283 DUK_DDD(DUK_DDDPRINT("target=%!iT, properties=%!iT",
284 (duk_tval *) duk_get_tval(ctx, 0),
285 (duk_tval *) duk_get_tval(ctx, 1)));
286
287 /*
288 * Two pass approach to processing the property descriptors.
289 * On first pass validate and normalize all descriptors before
290 * any changes are made to the target object. On second pass
291 * make the actual modifications to the target object.
292 *
293 * Right now we'll just use the same normalize/validate helper
294 * on both passes, ignoring its outputs on the first pass.
295 */
296
297 for (pass = 0; pass < 2; pass++) {
298 duk_set_top(ctx, 2); /* -> [ hobject props ] */
299 duk_enum(ctx, 1, DUK_ENUM_OWN_PROPERTIES_ONLY /*enum_flags*/);
300
301 for (;;) {
302 duk_hstring *key;
303
304 /* [ hobject props enum(props) ] */
305
306 duk_set_top(ctx, 3);
307
308 if (!duk_next(ctx, 2, 1 /*get_value*/)) {
309 break;
310 }
311
312 DUK_DDD(DUK_DDDPRINT("-> key=%!iT, desc=%!iT",
313 (duk_tval *) duk_get_tval(ctx, -2),
314 (duk_tval *) duk_get_tval(ctx, -1)));
315
316 /* [ hobject props enum(props) key desc ] */
317
318 duk_hobject_prepare_property_descriptor(ctx,
319 4 /*idx_desc*/,
320 &defprop_flags,
321 &idx_value,
322 &get,
323 &set);
324
325 /* [ hobject props enum(props) key desc value? getter? setter? ] */
326
327 if (pass == 0) {
328 continue;
329 }
330
331 key = duk_get_hstring(ctx, 3);
332 DUK_ASSERT(key != NULL);
333
334 duk_hobject_define_property_helper(ctx,
335 defprop_flags,
336 obj,
337 key,
338 idx_value,
339 get,
340 set);
341 }
342 }
343
344 /*
345 * Return target object
346 */
347
348 duk_dup(ctx, 0);
349 return 1;
350 }
351
duk_bi_object_constructor_seal_freeze_shared(duk_context * ctx)352 DUK_INTERNAL duk_ret_t duk_bi_object_constructor_seal_freeze_shared(duk_context *ctx) {
353 duk_hthread *thr = (duk_hthread *) ctx;
354 duk_hobject *h;
355 duk_bool_t is_freeze;
356
357 h = duk_require_hobject_or_lfunc(ctx, 0);
358 if (!h) {
359 /* Lightfunc, always success. */
360 return 1;
361 }
362
363 is_freeze = (duk_bool_t) duk_get_current_magic(ctx);
364 duk_hobject_object_seal_freeze_helper(thr, h, is_freeze);
365
366 /* Sealed and frozen objects cannot gain any more properties,
367 * so this is a good time to compact them.
368 */
369 duk_hobject_compact_props(thr, h);
370
371 return 1;
372 }
373
duk_bi_object_constructor_prevent_extensions(duk_context * ctx)374 DUK_INTERNAL duk_ret_t duk_bi_object_constructor_prevent_extensions(duk_context *ctx) {
375 duk_hthread *thr = (duk_hthread *) ctx;
376 duk_hobject *h;
377
378 h = duk_require_hobject_or_lfunc(ctx, 0);
379 if (!h) {
380 /* Lightfunc, always success. */
381 return 1;
382 }
383 DUK_ASSERT(h != NULL);
384
385 DUK_HOBJECT_CLEAR_EXTENSIBLE(h);
386
387 /* A non-extensible object cannot gain any more properties,
388 * so this is a good time to compact.
389 */
390 duk_hobject_compact_props(thr, h);
391
392 return 1;
393 }
394
duk_bi_object_constructor_is_sealed_frozen_shared(duk_context * ctx)395 DUK_INTERNAL duk_ret_t duk_bi_object_constructor_is_sealed_frozen_shared(duk_context *ctx) {
396 duk_hobject *h;
397 duk_bool_t is_frozen;
398 duk_bool_t rc;
399
400 h = duk_require_hobject_or_lfunc(ctx, 0);
401 if (!h) {
402 duk_push_true(ctx); /* frozen and sealed */
403 } else {
404 is_frozen = duk_get_current_magic(ctx);
405 rc = duk_hobject_object_is_sealed_frozen_helper((duk_hthread *) ctx, h, is_frozen /*is_frozen*/);
406 duk_push_boolean(ctx, rc);
407 }
408 return 1;
409 }
410
duk_bi_object_constructor_is_extensible(duk_context * ctx)411 DUK_INTERNAL duk_ret_t duk_bi_object_constructor_is_extensible(duk_context *ctx) {
412 duk_hobject *h;
413
414 h = duk_require_hobject_or_lfunc(ctx, 0);
415 if (!h) {
416 duk_push_false(ctx);
417 } else {
418 duk_push_boolean(ctx, DUK_HOBJECT_HAS_EXTENSIBLE(h));
419 }
420 return 1;
421 }
422
423 /* Shared helper for Object.getOwnPropertyNames() and Object.keys().
424 * Magic: 0=getOwnPropertyNames, 1=Object.keys.
425 */
duk_bi_object_constructor_keys_shared(duk_context * ctx)426 DUK_INTERNAL duk_ret_t duk_bi_object_constructor_keys_shared(duk_context *ctx) {
427 duk_hthread *thr = (duk_hthread *) ctx;
428 duk_hobject *obj;
429 #if defined(DUK_USE_ES6_PROXY)
430 duk_hobject *h_proxy_target;
431 duk_hobject *h_proxy_handler;
432 duk_hobject *h_trap_result;
433 duk_uarridx_t i, len, idx;
434 #endif
435 duk_small_uint_t enum_flags;
436
437 DUK_ASSERT_TOP(ctx, 1);
438 DUK_UNREF(thr);
439
440 obj = duk_require_hobject_or_lfunc_coerce(ctx, 0);
441 DUK_ASSERT(obj != NULL);
442 DUK_UNREF(obj);
443
444 #if defined(DUK_USE_ES6_PROXY)
445 if (DUK_LIKELY(!duk_hobject_proxy_check(thr,
446 obj,
447 &h_proxy_target,
448 &h_proxy_handler))) {
449 goto skip_proxy;
450 }
451
452 duk_push_hobject(ctx, h_proxy_handler);
453 if (!duk_get_prop_stridx(ctx, -1, DUK_STRIDX_OWN_KEYS)) {
454 /* Careful with reachability here: don't pop 'obj' before pushing
455 * proxy target.
456 */
457 DUK_DDD(DUK_DDDPRINT("no ownKeys trap, get keys of target instead"));
458 duk_pop_2(ctx);
459 duk_push_hobject(ctx, h_proxy_target);
460 duk_replace(ctx, 0);
461 DUK_ASSERT_TOP(ctx, 1);
462 goto skip_proxy;
463 }
464
465 /* [ obj handler trap ] */
466 duk_insert(ctx, -2);
467 duk_push_hobject(ctx, h_proxy_target); /* -> [ obj trap handler target ] */
468 duk_call_method(ctx, 1 /*nargs*/); /* -> [ obj trap_result ] */
469 h_trap_result = duk_require_hobject(ctx, -1);
470 DUK_UNREF(h_trap_result);
471
472 len = (duk_uarridx_t) duk_get_length(ctx, -1);
473 idx = 0;
474 duk_push_array(ctx);
475 for (i = 0; i < len; i++) {
476 /* [ obj trap_result res_arr ] */
477 if (duk_get_prop_index(ctx, -2, i) && duk_is_string(ctx, -1)) {
478 /* XXX: for Object.keys() we should check enumerability of key */
479 /* [ obj trap_result res_arr propname ] */
480 duk_put_prop_index(ctx, -2, idx);
481 idx++;
482 } else {
483 duk_pop(ctx);
484 }
485 }
486
487 /* XXX: missing trap result validation for non-configurable target keys
488 * (must be present), for non-extensible target all target keys must be
489 * present and no extra keys can be present.
490 * http://www.ecma-international.org/ecma-262/6.0/#sec-proxy-object-internal-methods-and-internal-slots-ownpropertykeys
491 */
492
493 /* XXX: for Object.keys() the [[OwnPropertyKeys]] result (trap result)
494 * should be filtered so that only enumerable keys remain. Enumerability
495 * should be checked with [[GetOwnProperty]] on the original object
496 * (i.e., the proxy in this case). If the proxy has a getOwnPropertyDescriptor
497 * trap, it should be triggered for every property. If the proxy doesn't have
498 * the trap, enumerability should be checked against the target object instead.
499 * We don't do any of this now, so Object.keys() and Object.getOwnPropertyNames()
500 * return the same result now for proxy traps. We still do clean up the trap
501 * result, so that Object.keys() and Object.getOwnPropertyNames() will return a
502 * clean array of strings without gaps.
503 */
504 return 1;
505
506 skip_proxy:
507 #endif /* DUK_USE_ES6_PROXY */
508
509 DUK_ASSERT_TOP(ctx, 1);
510
511 if (duk_get_current_magic(ctx)) {
512 /* Object.keys */
513 enum_flags = DUK_ENUM_OWN_PROPERTIES_ONLY |
514 DUK_ENUM_NO_PROXY_BEHAVIOR;
515 } else {
516 /* Object.getOwnPropertyNames */
517 enum_flags = DUK_ENUM_INCLUDE_NONENUMERABLE |
518 DUK_ENUM_OWN_PROPERTIES_ONLY |
519 DUK_ENUM_NO_PROXY_BEHAVIOR;
520 }
521
522 return duk_hobject_get_enumerated_keys(ctx, enum_flags);
523 }
524
duk_bi_object_prototype_to_string(duk_context * ctx)525 DUK_INTERNAL duk_ret_t duk_bi_object_prototype_to_string(duk_context *ctx) {
526 duk_push_this(ctx);
527 duk_to_object_class_string_top(ctx);
528 return 1;
529 }
530
duk_bi_object_prototype_to_locale_string(duk_context * ctx)531 DUK_INTERNAL duk_ret_t duk_bi_object_prototype_to_locale_string(duk_context *ctx) {
532 DUK_ASSERT_TOP(ctx, 0);
533 (void) duk_push_this_coercible_to_object(ctx);
534 duk_get_prop_stridx(ctx, 0, DUK_STRIDX_TO_STRING);
535 if (!duk_is_callable(ctx, 1)) {
536 return DUK_RET_TYPE_ERROR;
537 }
538 duk_dup(ctx, 0); /* -> [ O toString O ] */
539 duk_call_method(ctx, 0); /* XXX: call method tail call? */
540 return 1;
541 }
542
duk_bi_object_prototype_value_of(duk_context * ctx)543 DUK_INTERNAL duk_ret_t duk_bi_object_prototype_value_of(duk_context *ctx) {
544 (void) duk_push_this_coercible_to_object(ctx);
545 return 1;
546 }
547
duk_bi_object_prototype_is_prototype_of(duk_context * ctx)548 DUK_INTERNAL duk_ret_t duk_bi_object_prototype_is_prototype_of(duk_context *ctx) {
549 duk_hthread *thr = (duk_hthread *) ctx;
550 duk_hobject *h_v;
551 duk_hobject *h_obj;
552
553 DUK_ASSERT_TOP(ctx, 1);
554
555 h_v = duk_get_hobject(ctx, 0);
556 if (!h_v) {
557 duk_push_false(ctx); /* XXX: tail call: return duk_push_false(ctx) */
558 return 1;
559 }
560
561 h_obj = duk_push_this_coercible_to_object(ctx);
562 DUK_ASSERT(h_obj != NULL);
563
564 /* E5.1 Section 15.2.4.6, step 3.a, lookup proto once before compare.
565 * Prototype loops should cause an error to be thrown.
566 */
567 duk_push_boolean(ctx, duk_hobject_prototype_chain_contains(thr, DUK_HOBJECT_GET_PROTOTYPE(thr->heap, h_v), h_obj, 0 /*ignore_loop*/));
568 return 1;
569 }
570
duk_bi_object_prototype_has_own_property(duk_context * ctx)571 DUK_INTERNAL duk_ret_t duk_bi_object_prototype_has_own_property(duk_context *ctx) {
572 return duk_hobject_object_ownprop_helper(ctx, 0 /*required_desc_flags*/);
573 }
574
duk_bi_object_prototype_property_is_enumerable(duk_context * ctx)575 DUK_INTERNAL duk_ret_t duk_bi_object_prototype_property_is_enumerable(duk_context *ctx) {
576 return duk_hobject_object_ownprop_helper(ctx, DUK_PROPDESC_FLAG_ENUMERABLE /*required_desc_flags*/);
577 }
578