DUK_INTERNAL duk_uint_t duk_hbufferobject_clamp_bytelength(duk_hbufferobject *h_bufobj, duk_uint_t len) { duk_uint_t buf_size; duk_uint_t buf_avail; DUK_ASSERT(h_bufobj != NULL); DUK_ASSERT(h_bufobj->buf != NULL); buf_size = DUK_HBUFFER_GET_SIZE(h_bufobj->buf); if (h_bufobj->offset > buf_size) { /* Slice starting point is beyond current length. */ return 0; } buf_avail = buf_size - h_bufobj->offset; return buf_avail >= len ? len : buf_avail; }
DUK_LOCAL duk_uint8_t *duk__dump_buffer_prop(duk_hthread *thr, duk_uint8_t *p, duk_bufwriter_ctx *bw_ctx, duk_hobject *func, duk_small_uint_t stridx) { duk_tval *tv; tv = duk_hobject_find_existing_entry_tval_ptr(thr->heap, (duk_hobject *) func, DUK_HTHREAD_GET_STRING(thr, stridx)); if (tv != NULL && DUK_TVAL_IS_BUFFER(tv)) { duk_hbuffer *h_buf; h_buf = DUK_TVAL_GET_BUFFER(tv); DUK_ASSERT(h_buf != NULL); DUK_ASSERT(DUK_HBUFFER_MAX_BYTELEN <= 0x7fffffffUL); /* ensures no overflow */ p = DUK_BW_ENSURE_RAW(thr, bw_ctx, 4 + DUK_HBUFFER_GET_SIZE(h_buf), p); p = duk__dump_hbuffer_raw(thr, p, h_buf); } else { p = DUK_BW_ENSURE_RAW(thr, bw_ctx, 4, p); DUK_RAW_WRITE_U32_BE(p, 0); } return p; }
DUK_LOCAL duk_uint8_t *duk__dump_hbuffer_raw(duk_hthread *thr, duk_uint8_t *p, duk_hbuffer *h) { duk_size_t len; duk_uint32_t tmp32; DUK_ASSERT(thr != NULL); DUK_ASSERT(h != NULL); DUK_UNREF(thr); len = DUK_HBUFFER_GET_SIZE(h); DUK_ASSERT(len <= 0xffffffffUL); /* buffer limits */ tmp32 = (duk_uint32_t) len; DUK_RAW_WRITE_U32_BE(p, tmp32); DUK_MEMCPY((void *) p, (const void *) DUK_HBUFFER_GET_DATA_PTR(thr->heap, h), len); p += len; return p; }
void duk_to_fixed_buffer(duk_context *ctx, duk_idx_t index) { duk_hbuffer_dynamic *h_src; duk_uint8_t *data; duk_size_t size; index = duk_require_normalize_index(ctx, index); h_src = (duk_hbuffer_dynamic *) duk_require_hbuffer(ctx, index); DUK_ASSERT(h_src != NULL); if (!DUK_HBUFFER_HAS_DYNAMIC(h_src)) { return; } size = DUK_HBUFFER_GET_SIZE(h_src); data = (duk_uint8_t *) duk_push_fixed_buffer(ctx, size); if (size > 0U) { DUK_ASSERT(data != NULL); DUK_MEMCPY(data, DUK_HBUFFER_DYNAMIC_GET_CURR_DATA_PTR(h_src), size); } duk_replace(ctx, index); }
duk_bool_t duk_js_toboolean(duk_tval *tv) { switch (DUK_TVAL_GET_TAG(tv)) { case DUK_TAG_UNDEFINED: case DUK_TAG_NULL: return 0; case DUK_TAG_BOOLEAN: return DUK_TVAL_GET_BOOLEAN(tv); case DUK_TAG_STRING: { duk_hstring *h = DUK_TVAL_GET_STRING(tv); DUK_ASSERT(h != NULL); return (h->blen > 0 ? 1 : 0); } case DUK_TAG_OBJECT: { return 1; } case DUK_TAG_BUFFER: { /* mimic semantics for strings */ duk_hbuffer *h = DUK_TVAL_GET_BUFFER(tv); DUK_ASSERT(h != NULL); return (DUK_HBUFFER_GET_SIZE(h) > 0 ? 1 : 0); } case DUK_TAG_POINTER: { void *p = DUK_TVAL_GET_POINTER(tv); return (p != NULL ? 1 : 0); } default: { /* number */ int c; DUK_ASSERT(DUK_TVAL_IS_NUMBER(tv)); c = DUK_FPCLASSIFY(DUK_TVAL_GET_NUMBER(tv)); if (c == DUK_FP_ZERO || c == DUK_FP_NAN) { return 0; } else { return 1; } } } DUK_UNREACHABLE(); }
void duk_hbuffer_insert_bytes(duk_hthread *thr, duk_hbuffer_dynamic *buf, size_t offset, duk_uint8_t *data, size_t length) { char *p; /* XXX: allow inserts with offset > curr_size? i.e., insert zeroes automatically? */ DUK_ASSERT(thr != NULL); DUK_ASSERT(buf != NULL); DUK_ASSERT(DUK_HBUFFER_HAS_DYNAMIC(buf)); DUK_ASSERT_DISABLE(offset >= 0); /* unsigned, so always true */ DUK_ASSERT(offset <= DUK_HBUFFER_GET_SIZE(buf)); /* equality is OK (= append) */ DUK_ASSERT(data != NULL); DUK_ASSERT_DISABLE(length >= 0); /* unsigned, so always true */ if (length == 0) { return; } if (DUK_HBUFFER_DYNAMIC_GET_SPARE_SIZE(buf) < length) { duk_hbuffer_resize(thr, buf, DUK_HBUFFER_GET_SIZE(buf), duk__add_spare(DUK_HBUFFER_GET_SIZE(buf) + length)); } DUK_ASSERT(DUK_HBUFFER_DYNAMIC_GET_SPARE_SIZE(buf) >= length); p = (char *) DUK_HBUFFER_DYNAMIC_GET_CURR_DATA_PTR(buf); if (offset < DUK_HBUFFER_GET_SIZE(buf)) { /* not an append */ DUK_ASSERT(DUK_HBUFFER_GET_SIZE(buf) - offset > 0); /* not a zero byte memmove */ DUK_MEMMOVE((void *) (p + offset + length), (void *) (p + offset), DUK_HBUFFER_GET_SIZE(buf) - offset); } DUK_MEMCPY((void *) (p + offset), data, length); buf->size += length; }
/* Raw helper to extract internal information / statistics about a value. * The return values are version specific and must not expose anything * that would lead to security issues (e.g. exposing compiled function * 'data' buffer might be an issue). Currently only counts and sizes and * such are given so there should not be a security impact. */ duk_ret_t duk_bi_duktape_object_info(duk_context *ctx) { duk_tval *tv; duk_heaphdr *h; duk_int_t i, n; tv = duk_get_tval(ctx, 0); DUK_ASSERT(tv != NULL); /* because arg count is 1 */ duk_push_array(ctx); /* -> [ val arr ] */ /* type tag (public) */ duk_push_int(ctx, duk_get_type(ctx, 0)); /* address */ if (DUK_TVAL_IS_HEAP_ALLOCATED(tv)) { h = DUK_TVAL_GET_HEAPHDR(tv); duk_push_pointer(ctx, (void *) h); } else { goto done; } DUK_ASSERT(h != NULL); /* refcount */ #ifdef DUK_USE_REFERENCE_COUNTING duk_push_int(ctx, DUK_HEAPHDR_GET_REFCOUNT(h)); #else duk_push_undefined(ctx); #endif /* heaphdr size and additional allocation size, followed by * type specific stuff (with varying value count) */ switch ((duk_small_int_t) DUK_HEAPHDR_GET_TYPE(h)) { case DUK_HTYPE_STRING: { duk_hstring *h_str = (duk_hstring *) h; duk_push_int(ctx, (int) (sizeof(duk_hstring) + DUK_HSTRING_GET_BYTELEN(h_str) + 1)); break; } case DUK_HTYPE_OBJECT: { duk_hobject *h_obj = (duk_hobject *) h; duk_int_t hdr_size; if (DUK_HOBJECT_IS_COMPILEDFUNCTION(h_obj)) { hdr_size = (duk_int_t) sizeof(duk_hcompiledfunction); } else if (DUK_HOBJECT_IS_NATIVEFUNCTION(h_obj)) { hdr_size = (duk_int_t) sizeof(duk_hnativefunction); } else if (DUK_HOBJECT_IS_THREAD(h_obj)) { hdr_size = (duk_int_t) sizeof(duk_hthread); } else { hdr_size = (duk_int_t) sizeof(duk_hobject); } duk_push_int(ctx, (int) hdr_size); duk_push_int(ctx, (int) DUK_HOBJECT_E_ALLOC_SIZE(h_obj)); duk_push_int(ctx, (int) h_obj->e_size); duk_push_int(ctx, (int) h_obj->e_used); duk_push_int(ctx, (int) h_obj->a_size); duk_push_int(ctx, (int) h_obj->h_size); if (DUK_HOBJECT_IS_COMPILEDFUNCTION(h_obj)) { duk_hbuffer *h_data = ((duk_hcompiledfunction *) h_obj)->data; if (h_data) { duk_push_int(ctx, DUK_HBUFFER_GET_SIZE(h_data)); } else { duk_push_int(ctx, 0); } } break; } case DUK_HTYPE_BUFFER: { duk_hbuffer *h_buf = (duk_hbuffer *) h; if (DUK_HBUFFER_HAS_DYNAMIC(h_buf)) { /* XXX: when usable_size == 0, dynamic buf ptr may now be NULL, in which case * the second allocation does not exist. */ duk_hbuffer_dynamic *h_dyn = (duk_hbuffer_dynamic *) h; duk_push_int(ctx, (int) (sizeof(duk_hbuffer_dynamic))); duk_push_int(ctx, (int) (DUK_HBUFFER_DYNAMIC_GET_ALLOC_SIZE(h_dyn))); } else { duk_push_int(ctx, (int) (sizeof(duk_hbuffer_fixed) + DUK_HBUFFER_GET_SIZE(h_buf) + 1)); } break; } } done: /* set values into ret array */ /* FIXME: primitive to make array from valstack slice */ n = duk_get_top(ctx); for (i = 2; i < n; i++) { duk_dup(ctx, i); duk_put_prop_index(ctx, 1, i - 2); } duk_dup(ctx, 1); return 1; }
void duk_hobject_enumerator_create(duk_context *ctx, duk_small_uint_t enum_flags) { duk_hthread *thr = (duk_hthread *) ctx; duk_hobject *enum_target; duk_hobject *curr; duk_hobject *res; #if defined(DUK_USE_ES6_PROXY) duk_hobject *h_proxy_target; duk_hobject *h_proxy_handler; duk_hobject *h_trap_result; #endif duk_uint_fast32_t i, len; /* used for array, stack, and entry indices */ DUK_ASSERT(ctx != NULL); DUK_DDD(DUK_DDDPRINT("create enumerator, stack top: %ld", (long) duk_get_top(ctx))); enum_target = duk_require_hobject(ctx, -1); DUK_ASSERT(enum_target != NULL); duk_push_object_internal(ctx); res = duk_require_hobject(ctx, -1); DUK_DDD(DUK_DDDPRINT("created internal object")); /* [enum_target res] */ /* Target must be stored so that we can recheck whether or not * keys still exist when we enumerate. This is not done if the * enumeration result comes from a proxy trap as there is no * real object to check against. */ duk_push_hobject(ctx, enum_target); duk_put_prop_stridx(ctx, -2, DUK_STRIDX_INT_TARGET); /* Initialize index so that we skip internal control keys. */ duk_push_int(ctx, DUK__ENUM_START_INDEX); duk_put_prop_stridx(ctx, -2, DUK_STRIDX_INT_NEXT); /* * Proxy object handling */ #if defined(DUK_USE_ES6_PROXY) if (DUK_LIKELY((enum_flags & DUK_ENUM_NO_PROXY_BEHAVIOR) != 0)) { goto skip_proxy; } if (DUK_LIKELY(!duk_hobject_proxy_check(thr, enum_target, &h_proxy_target, &h_proxy_handler))) { goto skip_proxy; } DUK_DDD(DUK_DDDPRINT("proxy enumeration")); duk_push_hobject(ctx, h_proxy_handler); if (!duk_get_prop_stridx(ctx, -1, DUK_STRIDX_ENUMERATE)) { /* No need to replace the 'enum_target' value in stack, only the * enum_target reference. This also ensures that the original * enum target is reachable, which keeps the proxy and the proxy * target reachable. We do need to replace the internal _target. */ DUK_DDD(DUK_DDDPRINT("no enumerate trap, enumerate proxy target instead")); DUK_DDD(DUK_DDDPRINT("h_proxy_target=%!O", (duk_heaphdr *) h_proxy_target)); enum_target = h_proxy_target; duk_push_hobject(ctx, enum_target); /* -> [ ... enum_target res handler undefined target ] */ duk_put_prop_stridx(ctx, -4, DUK_STRIDX_INT_TARGET); duk_pop_2(ctx); /* -> [ ... enum_target res ] */ goto skip_proxy; } /* [ ... enum_target res handler trap ] */ duk_insert(ctx, -2); duk_push_hobject(ctx, h_proxy_target); /* -> [ ... enum_target res trap handler target ] */ duk_call_method(ctx, 1 /*nargs*/); /* -> [ ... enum_target res trap_result ] */ h_trap_result = duk_require_hobject(ctx, -1); DUK_UNREF(h_trap_result); /* Copy trap result keys into the enumerator object. */ len = (duk_uint_fast32_t) duk_get_length(ctx, -1); for (i = 0; i < len; i++) { /* XXX: not sure what the correct semantic details are here, * e.g. handling of missing values (gaps), handling of non-array * trap results, etc. * * For keys, we simply skip non-string keys which seems to be * consistent with how e.g. Object.keys() will process proxy trap * results (ES6 draft, Section 19.1.2.14). */ if (duk_get_prop_index(ctx, -1, i) && duk_is_string(ctx, -1)) { /* [ ... enum_target res trap_result val ] */ duk_push_true(ctx); /* [ ... enum_target res trap_result val true ] */ duk_put_prop(ctx, -4); } else { duk_pop(ctx); } } /* [ ... enum_target res trap_result ] */ duk_pop(ctx); duk_remove(ctx, -2); /* [ ... res ] */ /* The internal _target property is kept pointing to the original * enumeration target (the proxy object), so that the enumerator * 'next' operation can read property values if so requested. The * fact that the _target is a proxy disables key existence check * during enumeration. */ DUK_DDD(DUK_DDDPRINT("proxy enumeration, final res: %!O", (duk_heaphdr *) res)); goto compact_and_return; skip_proxy: #endif /* DUK_USE_ES6_PROXY */ curr = enum_target; while (curr) { /* * Virtual properties. * * String and buffer indices are virtual and always enumerable, * 'length' is virtual and non-enumerable. Array and arguments * object props have special behavior but are concrete. */ if (DUK_HOBJECT_HAS_EXOTIC_STRINGOBJ(curr) || DUK_HOBJECT_HAS_EXOTIC_BUFFEROBJ(curr)) { /* String and buffer enumeration behavior is identical now, * so use shared handler. */ if (DUK_HOBJECT_HAS_EXOTIC_STRINGOBJ(curr)) { duk_hstring *h_val; h_val = duk_hobject_get_internal_value_string(thr->heap, curr); DUK_ASSERT(h_val != NULL); /* string objects must not created without internal value */ len = (duk_uint_fast32_t) DUK_HSTRING_GET_CHARLEN(h_val); } else { duk_hbuffer *h_val; DUK_ASSERT(DUK_HOBJECT_HAS_EXOTIC_BUFFEROBJ(curr)); h_val = duk_hobject_get_internal_value_buffer(thr->heap, curr); DUK_ASSERT(h_val != NULL); /* buffer objects must not created without internal value */ len = (duk_uint_fast32_t) DUK_HBUFFER_GET_SIZE(h_val); } for (i = 0; i < len; i++) { duk_hstring *k; k = duk_heap_string_intern_u32_checked(thr, i); DUK_ASSERT(k); duk_push_hstring(ctx, k); duk_push_true(ctx); /* [enum_target res key true] */ duk_put_prop(ctx, -3); /* [enum_target res] */ } /* 'length' property is not enumerable, but is included if * non-enumerable properties are requested. */ if (enum_flags & DUK_ENUM_INCLUDE_NONENUMERABLE) { duk_push_hstring_stridx(ctx, DUK_STRIDX_LENGTH); duk_push_true(ctx); duk_put_prop(ctx, -3); } } else if (DUK_HOBJECT_HAS_EXOTIC_DUKFUNC(curr)) { if (enum_flags & DUK_ENUM_INCLUDE_NONENUMERABLE) { duk_push_hstring_stridx(ctx, DUK_STRIDX_LENGTH); duk_push_true(ctx); duk_put_prop(ctx, -3); } } /* * Array part * * Note: ordering between array and entry part must match 'abandon array' * behavior in duk_hobject_props.c: key order after an array is abandoned * must be the same. */ for (i = 0; i < (duk_uint_fast32_t) curr->a_size; i++) { duk_hstring *k; duk_tval *tv; tv = DUK_HOBJECT_A_GET_VALUE_PTR(curr, i); if (DUK_TVAL_IS_UNDEFINED_UNUSED(tv)) { continue; } k = duk_heap_string_intern_u32_checked(thr, i); DUK_ASSERT(k); duk_push_hstring(ctx, k); duk_push_true(ctx); /* [enum_target res key true] */ duk_put_prop(ctx, -3); /* [enum_target res] */ } /* * Entries part */ for (i = 0; i < (duk_uint_fast32_t) curr->e_next; i++) { duk_hstring *k; k = DUK_HOBJECT_E_GET_KEY(curr, i); if (!k) { continue; } if (!DUK_HOBJECT_E_SLOT_IS_ENUMERABLE(curr, i) && !(enum_flags & DUK_ENUM_INCLUDE_NONENUMERABLE)) { continue; } if (DUK_HSTRING_HAS_INTERNAL(k) && !(enum_flags & DUK_ENUM_INCLUDE_INTERNAL)) { continue; } if ((enum_flags & DUK_ENUM_ARRAY_INDICES_ONLY) && (DUK_HSTRING_GET_ARRIDX_SLOW(k) == DUK_HSTRING_NO_ARRAY_INDEX)) { continue; } DUK_ASSERT(DUK_HOBJECT_E_SLOT_IS_ACCESSOR(curr, i) || !DUK_TVAL_IS_UNDEFINED_UNUSED(&DUK_HOBJECT_E_GET_VALUE_PTR(curr, i)->v)); duk_push_hstring(ctx, k); duk_push_true(ctx); /* [enum_target res key true] */ duk_put_prop(ctx, -3); /* [enum_target res] */ } if (enum_flags & DUK_ENUM_OWN_PROPERTIES_ONLY) { break; } curr = curr->prototype; } /* [enum_target res] */ duk_remove(ctx, -2); /* [res] */ if ((enum_flags & (DUK_ENUM_ARRAY_INDICES_ONLY | DUK_ENUM_SORT_ARRAY_INDICES)) == (DUK_ENUM_ARRAY_INDICES_ONLY | DUK_ENUM_SORT_ARRAY_INDICES)) { /* * Some E5/E5.1 algorithms require that array indices are iterated * in a strictly ascending order. This is the case for e.g. * Array.prototype.forEach() and JSON.stringify() PropertyList * handling. * * To ensure this property for arrays with an array part (and * arbitrary objects too, since e.g. forEach() can be applied * to an array), the caller can request that we sort the keys * here. */ /* XXX: avoid this at least when enum_target is an Array, it has an * array part, and no ancestor properties were included? Not worth * it for JSON, but maybe worth it for forEach(). */ /* XXX: may need a 'length' filter for forEach() */ DUK_DDD(DUK_DDDPRINT("sort array indices by caller request")); duk__sort_array_indices(res); } #if defined(DUK_USE_ES6_PROXY) compact_and_return: #endif /* compact; no need to seal because object is internal */ duk_hobject_compact_props(thr, res); DUK_DDD(DUK_DDDPRINT("created enumerator object: %!iT", (duk_tval *) duk_get_tval(ctx, -1))); }
void duk_hbuffer_insert_slice(duk_hthread *thr, duk_hbuffer_dynamic *buf, size_t dst_offset, size_t src_offset, size_t length) { char *p; size_t src_end_offset; /* source end (exclusive) in initial buffer */ size_t len; DUK_ASSERT(thr != NULL); DUK_ASSERT(buf != NULL); DUK_ASSERT(DUK_HBUFFER_HAS_DYNAMIC(buf)); DUK_ASSERT_DISABLE(dst_offset >= 0); /* always true */ DUK_ASSERT(dst_offset <= DUK_HBUFFER_GET_SIZE(buf)); /* allow equality */ DUK_ASSERT_DISABLE(src_offset >= 0); /* always true */ DUK_ASSERT(src_offset <= DUK_HBUFFER_GET_SIZE(buf)); /* allow equality */ DUK_ASSERT_DISABLE(length >= 0); /* always true */ DUK_ASSERT(src_offset + length <= DUK_HBUFFER_GET_SIZE(buf)); /* allow equality */ if (length == 0) { return; } if (DUK_HBUFFER_DYNAMIC_GET_SPARE_SIZE(buf) < length) { duk_hbuffer_resize(thr, buf, DUK_HBUFFER_GET_SIZE(buf), duk__add_spare(DUK_HBUFFER_GET_SIZE(buf) + length)); } DUK_ASSERT(DUK_HBUFFER_DYNAMIC_GET_SPARE_SIZE(buf) >= length); p = (char *) DUK_HBUFFER_DYNAMIC_GET_CURR_DATA_PTR(buf); /* * src_offset and dst_offset refer to the state of the buffer * before any changes are made. This must be taken into account * when moving data around; in particular, the source data may * "straddle" the dst_offset, so the insert may need to be handled * in two pieces. */ src_end_offset = src_offset + length; /* create a hole for the insert */ len = DUK_HBUFFER_GET_SIZE(buf) - dst_offset; if (len > 0) { DUK_MEMMOVE(p + dst_offset + length, p + dst_offset, len); } if (src_offset < dst_offset) { if (src_end_offset <= dst_offset) { /* entire source is before 'dst_offset' */ DUK_MEMCPY(p + dst_offset, p + src_offset, length); } else { /* part of the source is before 'dst_offset'; straddles */ len = dst_offset - src_offset; DUK_ASSERT(len >= 1 && len < length); DUK_ASSERT(length - len >= 1); DUK_MEMCPY(p + dst_offset, p + src_offset, len); DUK_MEMCPY(p + dst_offset + len, p + src_offset + length + len, /* take above memmove() into account */ length - len); } } else { /* entire source is after 'dst_offset' */ DUK_MEMCPY(p + dst_offset, p + src_offset + length, /* take above memmove() into account */ length); } buf->size += length; }
DUK_INTERNAL duk_bool_t duk_js_equals_helper(duk_hthread *thr, duk_tval *tv_x, duk_tval *tv_y, duk_small_int_t flags) { duk_context *ctx = (duk_context *) thr; duk_tval *tv_tmp; /* If flags != 0 (strict or SameValue), thr can be NULL. For loose * equals comparison it must be != NULL. */ DUK_ASSERT(flags != 0 || thr != NULL); /* * Same type? * * Note: since number values have no explicit tag in the 8-byte * representation, need the awkward if + switch. */ #if defined(DUK_USE_FASTINT) if (DUK_TVAL_IS_FASTINT(tv_x) && DUK_TVAL_IS_FASTINT(tv_y)) { if (DUK_TVAL_GET_FASTINT(tv_x) == DUK_TVAL_GET_FASTINT(tv_y)) { return 1; } else { return 0; } } else #endif if (DUK_TVAL_IS_NUMBER(tv_x) && DUK_TVAL_IS_NUMBER(tv_y)) { /* Catches both doubles and cases where only one argument is a fastint */ if (DUK_UNLIKELY((flags & DUK_EQUALS_FLAG_SAMEVALUE) != 0)) { /* SameValue */ return duk__js_samevalue_number(DUK_TVAL_GET_NUMBER(tv_x), DUK_TVAL_GET_NUMBER(tv_y)); } else { /* equals and strict equals */ return duk__js_equals_number(DUK_TVAL_GET_NUMBER(tv_x), DUK_TVAL_GET_NUMBER(tv_y)); } } else if (DUK_TVAL_GET_TAG(tv_x) == DUK_TVAL_GET_TAG(tv_y)) { switch (DUK_TVAL_GET_TAG(tv_x)) { case DUK_TAG_UNDEFINED: case DUK_TAG_NULL: { return 1; } case DUK_TAG_BOOLEAN: { return DUK_TVAL_GET_BOOLEAN(tv_x) == DUK_TVAL_GET_BOOLEAN(tv_y); } case DUK_TAG_POINTER: { return DUK_TVAL_GET_POINTER(tv_x) == DUK_TVAL_GET_POINTER(tv_y); } case DUK_TAG_STRING: case DUK_TAG_OBJECT: { /* heap pointer comparison suffices */ return DUK_TVAL_GET_HEAPHDR(tv_x) == DUK_TVAL_GET_HEAPHDR(tv_y); } case DUK_TAG_BUFFER: { if ((flags & (DUK_EQUALS_FLAG_STRICT | DUK_EQUALS_FLAG_SAMEVALUE)) != 0) { /* heap pointer comparison suffices */ return DUK_TVAL_GET_HEAPHDR(tv_x) == DUK_TVAL_GET_HEAPHDR(tv_y); } else { /* non-strict equality for buffers compares contents */ duk_hbuffer *h_x = DUK_TVAL_GET_BUFFER(tv_x); duk_hbuffer *h_y = DUK_TVAL_GET_BUFFER(tv_y); duk_size_t len_x = DUK_HBUFFER_GET_SIZE(h_x); duk_size_t len_y = DUK_HBUFFER_GET_SIZE(h_y); void *buf_x; void *buf_y; if (len_x != len_y) { return 0; } buf_x = (void *) DUK_HBUFFER_GET_DATA_PTR(thr->heap, h_x); buf_y = (void *) DUK_HBUFFER_GET_DATA_PTR(thr->heap, h_y); /* if len_x == len_y == 0, buf_x and/or buf_y may * be NULL, but that's OK. */ DUK_ASSERT(len_x == len_y); DUK_ASSERT(len_x == 0 || buf_x != NULL); DUK_ASSERT(len_y == 0 || buf_y != NULL); return (DUK_MEMCMP(buf_x, buf_y, len_x) == 0) ? 1 : 0; } } case DUK_TAG_LIGHTFUNC: { /* At least 'magic' has a significant impact on function * identity. */ duk_small_uint_t lf_flags_x; duk_small_uint_t lf_flags_y; duk_c_function func_x; duk_c_function func_y; DUK_TVAL_GET_LIGHTFUNC(tv_x, func_x, lf_flags_x); DUK_TVAL_GET_LIGHTFUNC(tv_y, func_y, lf_flags_y); return ((func_x == func_y) && (lf_flags_x == lf_flags_y)) ? 1 : 0; } #if defined(DUK_USE_FASTINT) case DUK_TAG_FASTINT: #endif default: { DUK_ASSERT(DUK_TVAL_IS_NUMBER(tv_x)); DUK_ASSERT(DUK_TVAL_IS_NUMBER(tv_y)); DUK_UNREACHABLE(); return 0; } } } if ((flags & (DUK_EQUALS_FLAG_STRICT | DUK_EQUALS_FLAG_SAMEVALUE)) != 0) { return 0; } DUK_ASSERT(flags == 0); /* non-strict equality from here on */ /* * Types are different; various cases for non-strict comparison * * Since comparison is symmetric, we use a "swap trick" to reduce * code size. */ /* Undefined/null are considered equal (e.g. "null == undefined" -> true). */ if ((DUK_TVAL_IS_UNDEFINED(tv_x) && DUK_TVAL_IS_NULL(tv_y)) || (DUK_TVAL_IS_NULL(tv_x) && DUK_TVAL_IS_UNDEFINED(tv_y))) { return 1; } /* Number/string-or-buffer -> coerce string to number (e.g. "'1.5' == 1.5" -> true). */ if (DUK_TVAL_IS_NUMBER(tv_x) && (DUK_TVAL_IS_STRING(tv_y) || DUK_TVAL_IS_BUFFER(tv_y))) { /* the next 'if' is guaranteed to match after swap */ tv_tmp = tv_x; tv_x = tv_y; tv_y = tv_tmp; } if ((DUK_TVAL_IS_STRING(tv_x) || DUK_TVAL_IS_BUFFER(tv_x)) && DUK_TVAL_IS_NUMBER(tv_y)) { /* XXX: this is possible without resorting to the value stack */ duk_double_t d1, d2; d2 = DUK_TVAL_GET_NUMBER(tv_y); duk_push_tval(ctx, tv_x); duk_to_string(ctx, -1); /* buffer values are coerced first to string here */ duk_to_number(ctx, -1); d1 = duk_require_number(ctx, -1); duk_pop(ctx); return duk__js_equals_number(d1, d2); } /* Buffer/string -> compare contents. */ if (DUK_TVAL_IS_BUFFER(tv_x) && DUK_TVAL_IS_STRING(tv_y)) { tv_tmp = tv_x; tv_x = tv_y; tv_y = tv_tmp; } if (DUK_TVAL_IS_STRING(tv_x) && DUK_TVAL_IS_BUFFER(tv_y)) { duk_hstring *h_x = DUK_TVAL_GET_STRING(tv_x); duk_hbuffer *h_y = DUK_TVAL_GET_BUFFER(tv_y); duk_size_t len_x = DUK_HSTRING_GET_BYTELEN(h_x); duk_size_t len_y = DUK_HBUFFER_GET_SIZE(h_y); void *buf_x; void *buf_y; if (len_x != len_y) { return 0; } buf_x = (void *) DUK_HSTRING_GET_DATA(h_x); buf_y = (void *) DUK_HBUFFER_GET_DATA_PTR(thr->heap, h_y); /* if len_x == len_y == 0, buf_x and/or buf_y may * be NULL, but that's OK. */ DUK_ASSERT(len_x == len_y); DUK_ASSERT(len_x == 0 || buf_x != NULL); DUK_ASSERT(len_y == 0 || buf_y != NULL); return (DUK_MEMCMP(buf_x, buf_y, len_x) == 0) ? 1 : 0; } /* Boolean/any -> coerce boolean to number and try again. If boolean is * compared to a pointer, the final comparison after coercion now always * yields false (as pointer vs. number compares to false), but this is * not special cased. */ if (DUK_TVAL_IS_BOOLEAN(tv_x)) { tv_tmp = tv_x; tv_x = tv_y; tv_y = tv_tmp; } if (DUK_TVAL_IS_BOOLEAN(tv_y)) { /* ToNumber(bool) is +1.0 or 0.0. Tagged boolean value is always 0 or 1. */ duk_bool_t rc; DUK_ASSERT(DUK_TVAL_GET_BOOLEAN(tv_y) == 0 || DUK_TVAL_GET_BOOLEAN(tv_y) == 1); duk_push_tval(ctx, tv_x); duk_push_int(ctx, DUK_TVAL_GET_BOOLEAN(tv_y)); rc = duk_js_equals_helper(thr, duk_get_tval(ctx, -2), duk_get_tval(ctx, -1), 0 /*flags:nonstrict*/); duk_pop_2(ctx); return rc; } /* String-number-buffer/object -> coerce object to primitive (apparently without hint), then try again. */ if ((DUK_TVAL_IS_STRING(tv_x) || DUK_TVAL_IS_NUMBER(tv_x) || DUK_TVAL_IS_BUFFER(tv_x)) && DUK_TVAL_IS_OBJECT(tv_y)) { tv_tmp = tv_x; tv_x = tv_y; tv_y = tv_tmp; } if (DUK_TVAL_IS_OBJECT(tv_x) && (DUK_TVAL_IS_STRING(tv_y) || DUK_TVAL_IS_NUMBER(tv_y) || DUK_TVAL_IS_BUFFER(tv_y))) { duk_bool_t rc; duk_push_tval(ctx, tv_x); duk_push_tval(ctx, tv_y); duk_to_primitive(ctx, -2, DUK_HINT_NONE); /* apparently no hint? */ rc = duk_js_equals_helper(thr, duk_get_tval(ctx, -2), duk_get_tval(ctx, -1), 0 /*flags:nonstrict*/); duk_pop_2(ctx); return rc; } /* Nothing worked -> not equal. */ return 0; }
/* Raw helper to extract internal information / statistics about a value. * The return values are version specific and must not expose anything * that would lead to security issues (e.g. exposing compiled function * 'data' buffer might be an issue). Currently only counts and sizes and * such are given so there should not be a security impact. */ DUK_INTERNAL duk_ret_t duk_bi_duktape_object_info(duk_context *ctx) { duk_hthread *thr = (duk_hthread *) ctx; duk_tval *tv; duk_heaphdr *h; duk_int_t i, n; DUK_UNREF(thr); /* result array */ duk_push_array(ctx); /* -> [ val arr ] */ /* type tag (public) */ duk_push_int(ctx, duk_get_type(ctx, 0)); /* address */ tv = duk_get_tval(ctx, 0); DUK_ASSERT(tv != NULL); /* because arg count is 1 */ if (DUK_TVAL_IS_HEAP_ALLOCATED(tv)) { h = DUK_TVAL_GET_HEAPHDR(tv); duk_push_pointer(ctx, (void *) h); } else { /* internal type tag */ duk_push_int(ctx, (duk_int_t) DUK_TVAL_GET_TAG(tv)); goto done; } DUK_ASSERT(h != NULL); /* refcount */ #ifdef DUK_USE_REFERENCE_COUNTING duk_push_size_t(ctx, DUK_HEAPHDR_GET_REFCOUNT(h)); #else duk_push_undefined(ctx); #endif /* heaphdr size and additional allocation size, followed by * type specific stuff (with varying value count) */ switch ((duk_small_int_t) DUK_HEAPHDR_GET_TYPE(h)) { case DUK_HTYPE_STRING: { duk_hstring *h_str = (duk_hstring *) h; duk_push_uint(ctx, (duk_uint_t) (sizeof(duk_hstring) + DUK_HSTRING_GET_BYTELEN(h_str) + 1)); break; } case DUK_HTYPE_OBJECT: { duk_hobject *h_obj = (duk_hobject *) h; duk_small_uint_t hdr_size; if (DUK_HOBJECT_IS_COMPILEDFUNCTION(h_obj)) { hdr_size = (duk_small_uint_t) sizeof(duk_hcompiledfunction); } else if (DUK_HOBJECT_IS_NATIVEFUNCTION(h_obj)) { hdr_size = (duk_small_uint_t) sizeof(duk_hnativefunction); } else if (DUK_HOBJECT_IS_THREAD(h_obj)) { hdr_size = (duk_small_uint_t) sizeof(duk_hthread); #if defined(DUK_USE_BUFFEROBJECT_SUPPORT) } else if (DUK_HOBJECT_IS_BUFFEROBJECT(h_obj)) { hdr_size = (duk_small_uint_t) sizeof(duk_hbufferobject); #endif } else { hdr_size = (duk_small_uint_t) sizeof(duk_hobject); } duk_push_uint(ctx, (duk_uint_t) hdr_size); duk_push_uint(ctx, (duk_uint_t) DUK_HOBJECT_P_ALLOC_SIZE(h_obj)); duk_push_uint(ctx, (duk_uint_t) DUK_HOBJECT_GET_ESIZE(h_obj)); /* Note: e_next indicates the number of gc-reachable entries * in the entry part, and also indicates the index where the * next new property would be inserted. It does *not* indicate * the number of non-NULL keys present in the object. That * value could be counted separately but requires a pass through * the key list. */ duk_push_uint(ctx, (duk_uint_t) DUK_HOBJECT_GET_ENEXT(h_obj)); duk_push_uint(ctx, (duk_uint_t) DUK_HOBJECT_GET_ASIZE(h_obj)); duk_push_uint(ctx, (duk_uint_t) DUK_HOBJECT_GET_HSIZE(h_obj)); if (DUK_HOBJECT_IS_COMPILEDFUNCTION(h_obj)) { duk_hbuffer *h_data = (duk_hbuffer *) DUK_HCOMPILEDFUNCTION_GET_DATA(thr->heap, (duk_hcompiledfunction *) h_obj); if (h_data) { duk_push_uint(ctx, (duk_uint_t) DUK_HBUFFER_GET_SIZE(h_data)); } else { duk_push_uint(ctx, 0); } } break; } case DUK_HTYPE_BUFFER: { duk_hbuffer *h_buf = (duk_hbuffer *) h; if (DUK_HBUFFER_HAS_DYNAMIC(h_buf)) { if (DUK_HBUFFER_HAS_EXTERNAL(h_buf)) { duk_push_uint(ctx, (duk_uint_t) (sizeof(duk_hbuffer_external))); } else { /* When alloc_size == 0 the second allocation may not * actually exist. */ duk_push_uint(ctx, (duk_uint_t) (sizeof(duk_hbuffer_dynamic))); } duk_push_uint(ctx, (duk_uint_t) (DUK_HBUFFER_GET_SIZE(h_buf))); } else { duk_push_uint(ctx, (duk_uint_t) (sizeof(duk_hbuffer_fixed) + DUK_HBUFFER_GET_SIZE(h_buf) + 1)); } break; } } done: /* set values into ret array */ /* XXX: primitive to make array from valstack slice */ n = duk_get_top(ctx); for (i = 2; i < n; i++) { duk_dup(ctx, i); duk_put_prop_index(ctx, 1, i - 2); } duk_dup(ctx, 1); return 1; }
void duk_hobject_enumerator_create(duk_context *ctx, int enum_flags) { duk_hthread *thr = (duk_hthread *) ctx; duk_hobject *target; duk_hobject *curr; duk_hobject *res; DUK_ASSERT(ctx != NULL); DUK_DDDPRINT("create enumerator, stack top: %d", duk_get_top(ctx)); target = duk_require_hobject(ctx, -1); DUK_ASSERT(target != NULL); duk_push_object_internal(ctx); DUK_DDDPRINT("created internal object"); /* [target res] */ duk_push_hstring_stridx(ctx, DUK_STRIDX_INT_TARGET); duk_push_hobject(ctx, target); duk_put_prop(ctx, -3); /* initialize index so that we skip internal control keys */ duk_push_hstring_stridx(ctx, DUK_STRIDX_INT_NEXT); duk_push_int(ctx, DUK__ENUM_START_INDEX); duk_put_prop(ctx, -3); curr = target; while (curr) { duk_uint32_t i, len; /* * Virtual properties. * * String and buffer indices are virtual and always enumerable, * 'length' is virtual and non-enumerable. Array and arguments * object props have special behavior but are concrete. */ if (DUK_HOBJECT_HAS_SPECIAL_STRINGOBJ(curr) || DUK_HOBJECT_HAS_SPECIAL_BUFFEROBJ(curr)) { /* String and buffer enumeration behavior is identical now, * so use shared handler. */ if (DUK_HOBJECT_HAS_SPECIAL_STRINGOBJ(curr)) { duk_hstring *h_val; h_val = duk_hobject_get_internal_value_string(thr->heap, curr); DUK_ASSERT(h_val != NULL); /* string objects must not created without internal value */ len = DUK_HSTRING_GET_CHARLEN(h_val); } else { duk_hbuffer *h_val; DUK_ASSERT(DUK_HOBJECT_HAS_SPECIAL_BUFFEROBJ(curr)); h_val = duk_hobject_get_internal_value_buffer(thr->heap, curr); DUK_ASSERT(h_val != NULL); /* buffer objects must not created without internal value */ len = DUK_HBUFFER_GET_SIZE(h_val); } /* FIXME: type for 'i' to match string max len (duk_uint32_t) */ for (i = 0; i < len; i++) { duk_hstring *k; k = duk_heap_string_intern_u32_checked(thr, i); DUK_ASSERT(k); duk_push_hstring(ctx, k); duk_push_true(ctx); /* [target res key true] */ duk_put_prop(ctx, -3); /* [target res] */ } /* 'length' property is not enumerable, but is included if * non-enumerable properties are requested. */ if (enum_flags & DUK_ENUM_INCLUDE_NONENUMERABLE) { duk_push_hstring_stridx(ctx, DUK_STRIDX_LENGTH); duk_push_true(ctx); duk_put_prop(ctx, -3); } } else if (DUK_HOBJECT_HAS_SPECIAL_DUKFUNC(curr)) { if (enum_flags & DUK_ENUM_INCLUDE_NONENUMERABLE) { duk_push_hstring_stridx(ctx, DUK_STRIDX_LENGTH); duk_push_true(ctx); duk_put_prop(ctx, -3); } } /* * Array part * * Note: ordering between array and entry part must match 'abandon array' * behavior in duk_hobject_props.c: key order after an array is abandoned * must be the same. */ for (i = 0; i < curr->a_size; i++) { duk_hstring *k; duk_tval *tv; tv = DUK_HOBJECT_A_GET_VALUE_PTR(curr, i); if (DUK_TVAL_IS_UNDEFINED_UNUSED(tv)) { continue; } k = duk_heap_string_intern_u32_checked(thr, i); DUK_ASSERT(k); duk_push_hstring(ctx, k); duk_push_true(ctx); /* [target res key true] */ duk_put_prop(ctx, -3); /* [target res] */ } /* * Entries part */ for (i = 0; i < curr->e_used; i++) { duk_hstring *k; k = DUK_HOBJECT_E_GET_KEY(curr, i); if (!k) { continue; } if (!DUK_HOBJECT_E_SLOT_IS_ENUMERABLE(curr, i) && !(enum_flags & DUK_ENUM_INCLUDE_NONENUMERABLE)) { continue; } if (DUK_HSTRING_HAS_INTERNAL(k) && !(enum_flags & DUK_ENUM_INCLUDE_INTERNAL)) { continue; } if ((enum_flags & DUK_ENUM_ARRAY_INDICES_ONLY) && (DUK_HSTRING_GET_ARRIDX_SLOW(k) == DUK_HSTRING_NO_ARRAY_INDEX)) { continue; } DUK_ASSERT(DUK_HOBJECT_E_SLOT_IS_ACCESSOR(curr, i) || !DUK_TVAL_IS_UNDEFINED_UNUSED(&DUK_HOBJECT_E_GET_VALUE_PTR(curr, i)->v)); duk_push_hstring(ctx, k); duk_push_true(ctx); /* [target res key true] */ duk_put_prop(ctx, -3); /* [target res] */ } if (enum_flags & DUK_ENUM_OWN_PROPERTIES_ONLY) { break; } curr = curr->prototype; } /* [target res] */ duk_remove(ctx, -2); res = duk_require_hobject(ctx, -1); /* [res] */ if ((enum_flags & (DUK_ENUM_ARRAY_INDICES_ONLY | DUK_ENUM_SORT_ARRAY_INDICES)) == (DUK_ENUM_ARRAY_INDICES_ONLY | DUK_ENUM_SORT_ARRAY_INDICES)) { /* * Some E5/E5.1 algorithms require that array indices are iterated * in a strictly ascending order. This is the case for e.g. * Array.prototype.forEach() and JSON.stringify() PropertyList * handling. * * To ensure this property for arrays with an array part (and * arbitrary objects too, since e.g. forEach() can be applied * to an array), the caller can request that we sort the keys * here. */ /* FIXME: avoid this at least when target is an Array, it has an * array part, and no ancestor properties were included? Not worth * it for JSON, but maybe worth it for forEach(). */ /* FIXME: may need a 'length' filter for forEach() */ DUK_DDDPRINT("sort array indices by caller request"); duk__sort_array_indices(res); } /* compact; no need to seal because object is internal */ duk_hobject_compact_props(thr, res); DUK_DDDPRINT("created enumerator object: %!iT", duk_get_tval(ctx, -1)); }
void duk_regexp_compile(duk_hthread *thr) { duk_context *ctx = (duk_context *) thr; duk_re_compiler_ctx re_ctx; duk_lexer_point lex_point; duk_hstring *h_pattern; duk_hstring *h_flags; duk_hbuffer_dynamic *h_buffer; DUK_ASSERT(thr != NULL); DUK_ASSERT(ctx != NULL); /* * Args validation */ /* TypeError if fails */ h_pattern = duk_require_hstring(ctx, -2); h_flags = duk_require_hstring(ctx, -1); /* * Create normalized 'source' property (E5 Section 15.10.3). */ /* [ ... pattern flags ] */ create_escaped_source(thr, -2); /* [ ... pattern flags escaped_source ] */ /* * Init compilation context */ duk_push_dynamic_buffer(ctx, 0); h_buffer = (duk_hbuffer_dynamic *) duk_require_hbuffer(ctx, -1); DUK_ASSERT(DUK_HBUFFER_HAS_DYNAMIC(h_buffer)); /* [ ... pattern flags escaped_source buffer ] */ DUK_MEMSET(&re_ctx, 0, sizeof(re_ctx)); DUK_LEXER_INITCTX(&re_ctx.lex); /* duplicate zeroing, expect for (possible) NULL inits */ re_ctx.thr = thr; re_ctx.lex.thr = thr; re_ctx.lex.input = DUK_HSTRING_GET_DATA(h_pattern); re_ctx.lex.input_length = DUK_HSTRING_GET_BYTELEN(h_pattern); re_ctx.buf = h_buffer; re_ctx.recursion_limit = DUK_RE_COMPILE_RECURSION_LIMIT; re_ctx.re_flags = parse_regexp_flags(thr, h_flags); DUK_DDPRINT("regexp compiler ctx initialized, flags=0x%08x, recursion_limit=%d", (unsigned int) re_ctx.re_flags, (int) re_ctx.recursion_limit); /* * Init lexer */ lex_point.offset = 0; /* expensive init, just want to fill window */ lex_point.line = 1; DUK_LEXER_SETPOINT(&re_ctx.lex, &lex_point); /* * Compilation */ DUK_DPRINT("starting regexp compilation"); append_u32(&re_ctx, DUK_REOP_SAVE); append_u32(&re_ctx, 0); (void) parse_disjunction(&re_ctx, 1); /* 1 = expect eof */ append_u32(&re_ctx, DUK_REOP_SAVE); append_u32(&re_ctx, 1); append_u32(&re_ctx, DUK_REOP_MATCH); DUK_DPRINT("regexp bytecode size (before header) is %d bytes", (int) DUK_HBUFFER_GET_SIZE(re_ctx.buf)); /* * Check for invalid backreferences; note that it is NOT an error * to back-reference a capture group which has not yet been introduced * in the pattern (as in /\1(foo)/); in fact, the backreference will * always match! It IS an error to back-reference a capture group * which will never be introduced in the pattern. Thus, we can check * for such references only after parsing is complete. */ if (re_ctx.highest_backref > re_ctx.captures) { DUK_ERROR(thr, DUK_ERR_SYNTAX_ERROR, "invalid backreference(s)"); } /* * Emit compiled regexp header: flags, ncaptures * (insertion order inverted on purpose) */ insert_u32(&re_ctx, 0, (re_ctx.captures + 1) * 2); insert_u32(&re_ctx, 0, re_ctx.re_flags); DUK_DPRINT("regexp bytecode size (after header) is %d bytes", (int) DUK_HBUFFER_GET_SIZE(re_ctx.buf)); DUK_DDDPRINT("compiled regexp: %!xO", re_ctx.buf); /* [ ... pattern flags escaped_source buffer ] */ duk_to_string(ctx, -1); /* coerce to string */ /* [ ... pattern flags escaped_source bytecode ] */ /* * Finalize stack */ duk_remove(ctx, -4); /* -> [ ... flags escaped_source bytecode ] */ duk_remove(ctx, -3); /* -> [ ... escaped_source bytecode ] */ DUK_DPRINT("regexp compilation successful, bytecode: %!T, escaped source: %!T", duk_get_tval(ctx, -1), duk_get_tval(ctx, -2)); }