| /* |
| * API calls related to general value stack manipulation: resizing the value |
| * stack, pushing and popping values, type checking and reading values, |
| * coercing values, etc. |
| * |
| * Also contains internal functions (such as duk_get_tval()), defined |
| * in duk_api_internal.h, with semantics similar to the public API. |
| */ |
| |
| /* XXX: repetition of stack pre-checks -> helper or macro or inline */ |
| /* XXX: shared api error strings, and perhaps even throw code for rare cases? */ |
| |
| #include "duk_internal.h" |
| |
| /* |
| * Forward declarations |
| */ |
| |
| DUK_LOCAL_DECL duk_idx_t duk__push_c_function_raw(duk_context *ctx, duk_c_function func, duk_idx_t nargs, duk_uint_t flags); |
| |
| /* |
| * Global state for working around missing variadic macros |
| */ |
| |
| #ifndef DUK_USE_VARIADIC_MACROS |
| DUK_EXTERNAL const char *duk_api_global_filename = NULL; |
| DUK_EXTERNAL duk_int_t duk_api_global_line = 0; |
| #endif |
| |
| /* |
| * Misc helpers |
| */ |
| |
| /* Check that there's room to push one value. */ |
| #if defined(DUK_USE_VALSTACK_UNSAFE) |
| /* Faster but value stack overruns are memory unsafe. */ |
| #define DUK__CHECK_SPACE() do { \ |
| DUK_ASSERT(!(thr->valstack_top >= thr->valstack_end)); \ |
| } while (0) |
| #else |
| #define DUK__CHECK_SPACE() do { \ |
| if (DUK_UNLIKELY(thr->valstack_top >= thr->valstack_end)) { \ |
| DUK_ERROR_API(thr, DUK_STR_PUSH_BEYOND_ALLOC_STACK); \ |
| } \ |
| } while (0) |
| #endif |
| |
| DUK_LOCAL_DECL duk_heaphdr *duk__get_tagged_heaphdr_raw(duk_context *ctx, duk_idx_t index, duk_uint_t tag); |
| |
| DUK_LOCAL duk_int_t duk__api_coerce_d2i(duk_context *ctx, duk_idx_t index, duk_bool_t require) { |
| duk_hthread *thr; |
| duk_tval *tv; |
| duk_small_int_t c; |
| duk_double_t d; |
| |
| thr = (duk_hthread *) ctx; |
| |
| tv = duk_get_tval(ctx, index); |
| if (tv == NULL) { |
| goto error_notnumber; |
| } |
| |
| /* |
| * Special cases like NaN and +/- Infinity are handled explicitly |
| * because a plain C coercion from double to int handles these cases |
| * in undesirable ways. For instance, NaN may coerce to INT_MIN |
| * (not zero), and INT_MAX + 1 may coerce to INT_MIN (not INT_MAX). |
| * |
| * This double-to-int coercion differs from ToInteger() because it |
| * has a finite range (ToInteger() allows e.g. +/- Infinity). It |
| * also differs from ToInt32() because the INT_MIN/INT_MAX clamping |
| * depends on the size of the int type on the platform. In particular, |
| * on platforms with a 64-bit int type, the full range is allowed. |
| */ |
| |
| #if defined(DUK_USE_FASTINT) |
| if (DUK_TVAL_IS_FASTINT(tv)) { |
| duk_int64_t t = DUK_TVAL_GET_FASTINT(tv); |
| #if (DUK_INT_MAX <= 0x7fffffffL) |
| /* Clamping only necessary for 32-bit ints. */ |
| if (t < DUK_INT_MIN) { |
| t = DUK_INT_MIN; |
| } else if (t > DUK_INT_MAX) { |
| t = DUK_INT_MAX; |
| } |
| #endif |
| return (duk_int_t) t; |
| } |
| #endif |
| |
| if (DUK_TVAL_IS_NUMBER(tv)) { |
| d = DUK_TVAL_GET_NUMBER(tv); |
| c = (duk_small_int_t) DUK_FPCLASSIFY(d); |
| if (c == DUK_FP_NAN) { |
| return 0; |
| } else if (d < (duk_double_t) DUK_INT_MIN) { |
| /* covers -Infinity */ |
| return DUK_INT_MIN; |
| } else if (d > (duk_double_t) DUK_INT_MAX) { |
| /* covers +Infinity */ |
| return DUK_INT_MAX; |
| } else { |
| /* coerce towards zero */ |
| return (duk_int_t) d; |
| } |
| } |
| |
| error_notnumber: |
| |
| if (require) { |
| DUK_ERROR_REQUIRE_TYPE_INDEX(thr, index, "number", DUK_STR_NOT_NUMBER); |
| /* not reachable */ |
| } |
| return 0; |
| } |
| |
| DUK_LOCAL duk_uint_t duk__api_coerce_d2ui(duk_context *ctx, duk_idx_t index, duk_bool_t require) { |
| duk_hthread *thr; |
| duk_tval *tv; |
| duk_small_int_t c; |
| duk_double_t d; |
| |
| /* Same as above but for unsigned int range. */ |
| |
| thr = (duk_hthread *) ctx; |
| |
| tv = duk_get_tval(ctx, index); |
| if (tv == NULL) { |
| goto error_notnumber; |
| } |
| |
| #if defined(DUK_USE_FASTINT) |
| if (DUK_TVAL_IS_FASTINT(tv)) { |
| duk_int64_t t = DUK_TVAL_GET_FASTINT(tv); |
| if (t < 0) { |
| t = 0; |
| } |
| #if (DUK_UINT_MAX <= 0xffffffffUL) |
| /* Clamping only necessary for 32-bit ints. */ |
| else if (t > DUK_UINT_MAX) { |
| t = DUK_UINT_MAX; |
| } |
| #endif |
| return (duk_uint_t) t; |
| } |
| #endif |
| |
| if (DUK_TVAL_IS_NUMBER(tv)) { |
| d = DUK_TVAL_GET_NUMBER(tv); |
| c = (duk_small_int_t) DUK_FPCLASSIFY(d); |
| if (c == DUK_FP_NAN) { |
| return 0; |
| } else if (d < 0.0) { |
| /* covers -Infinity */ |
| return (duk_uint_t) 0; |
| } else if (d > (duk_double_t) DUK_UINT_MAX) { |
| /* covers +Infinity */ |
| return (duk_uint_t) DUK_UINT_MAX; |
| } else { |
| /* coerce towards zero */ |
| return (duk_uint_t) d; |
| } |
| } |
| |
| error_notnumber: |
| |
| if (require) { |
| DUK_ERROR_REQUIRE_TYPE_INDEX(thr, index, "number", DUK_STR_NOT_NUMBER); |
| /* not reachable */ |
| } |
| return 0; |
| } |
| |
| /* |
| * Stack index validation/normalization and getting a stack duk_tval ptr. |
| * |
| * These are called by many API entrypoints so the implementations must be |
| * fast and "inlined". |
| * |
| * There's some repetition because of this; keep the functions in sync. |
| */ |
| |
| DUK_EXTERNAL duk_idx_t duk_normalize_index(duk_context *ctx, duk_idx_t index) { |
| duk_hthread *thr = (duk_hthread *) ctx; |
| duk_uidx_t vs_size; |
| duk_uidx_t uindex; |
| |
| DUK_ASSERT_CTX_VALID(ctx); |
| DUK_ASSERT(DUK_INVALID_INDEX < 0); |
| |
| /* Care must be taken to avoid pointer wrapping in the index |
| * validation. For instance, on a 32-bit platform with 8-byte |
| * duk_tval the index 0x20000000UL would wrap the memory space |
| * once. |
| */ |
| |
| /* Assume value stack sizes (in elements) fits into duk_idx_t. */ |
| DUK_ASSERT(thr->valstack_top >= thr->valstack_bottom); |
| vs_size = (duk_uidx_t) (thr->valstack_top - thr->valstack_bottom); |
| DUK_ASSERT_DISABLE(vs_size >= 0); /* unsigned */ |
| |
| if (index < 0) { |
| uindex = vs_size + (duk_uidx_t) index; |
| } else { |
| /* since index non-negative */ |
| DUK_ASSERT(index != DUK_INVALID_INDEX); |
| uindex = (duk_uidx_t) index; |
| } |
| |
| /* DUK_INVALID_INDEX won't be accepted as a valid index. */ |
| DUK_ASSERT(vs_size + (duk_uidx_t) DUK_INVALID_INDEX >= vs_size); |
| |
| if (DUK_LIKELY(uindex < vs_size)) { |
| return (duk_idx_t) uindex; |
| } |
| return DUK_INVALID_INDEX; |
| } |
| |
| DUK_EXTERNAL duk_idx_t duk_require_normalize_index(duk_context *ctx, duk_idx_t index) { |
| duk_hthread *thr = (duk_hthread *) ctx; |
| duk_uidx_t vs_size; |
| duk_uidx_t uindex; |
| |
| DUK_ASSERT_CTX_VALID(ctx); |
| DUK_ASSERT(DUK_INVALID_INDEX < 0); |
| |
| DUK_ASSERT(thr->valstack_top >= thr->valstack_bottom); |
| vs_size = (duk_uidx_t) (thr->valstack_top - thr->valstack_bottom); |
| DUK_ASSERT_DISABLE(vs_size >= 0); /* unsigned */ |
| |
| if (index < 0) { |
| uindex = vs_size + (duk_uidx_t) index; |
| } else { |
| DUK_ASSERT(index != DUK_INVALID_INDEX); |
| uindex = (duk_uidx_t) index; |
| } |
| |
| /* DUK_INVALID_INDEX won't be accepted as a valid index. */ |
| DUK_ASSERT(vs_size + (duk_uidx_t) DUK_INVALID_INDEX >= vs_size); |
| |
| if (DUK_LIKELY(uindex < vs_size)) { |
| return (duk_idx_t) uindex; |
| } |
| DUK_ERROR_API_INDEX(thr, index); |
| return 0; /* unreachable */ |
| } |
| |
| DUK_INTERNAL duk_tval *duk_get_tval(duk_context *ctx, duk_idx_t index) { |
| duk_hthread *thr = (duk_hthread *) ctx; |
| duk_uidx_t vs_size; |
| duk_uidx_t uindex; |
| |
| DUK_ASSERT_CTX_VALID(ctx); |
| DUK_ASSERT(DUK_INVALID_INDEX < 0); |
| |
| DUK_ASSERT(thr->valstack_top >= thr->valstack_bottom); |
| vs_size = (duk_uidx_t) (thr->valstack_top - thr->valstack_bottom); |
| DUK_ASSERT_DISABLE(vs_size >= 0); /* unsigned */ |
| |
| if (index < 0) { |
| uindex = vs_size + (duk_uidx_t) index; |
| } else { |
| DUK_ASSERT(index != DUK_INVALID_INDEX); |
| uindex = (duk_uidx_t) index; |
| } |
| |
| /* DUK_INVALID_INDEX won't be accepted as a valid index. */ |
| DUK_ASSERT(vs_size + (duk_uidx_t) DUK_INVALID_INDEX >= vs_size); |
| |
| if (DUK_LIKELY(uindex < vs_size)) { |
| return thr->valstack_bottom + uindex; |
| } |
| return NULL; |
| } |
| |
| DUK_INTERNAL duk_tval *duk_require_tval(duk_context *ctx, duk_idx_t index) { |
| duk_hthread *thr = (duk_hthread *) ctx; |
| duk_uidx_t vs_size; |
| duk_uidx_t uindex; |
| |
| DUK_ASSERT_CTX_VALID(ctx); |
| DUK_ASSERT(DUK_INVALID_INDEX < 0); |
| |
| DUK_ASSERT(thr->valstack_top >= thr->valstack_bottom); |
| vs_size = (duk_uidx_t) (thr->valstack_top - thr->valstack_bottom); |
| DUK_ASSERT_DISABLE(vs_size >= 0); /* unsigned */ |
| |
| /* Use unsigned arithmetic to optimize comparison. */ |
| if (index < 0) { |
| uindex = vs_size + (duk_uidx_t) index; |
| } else { |
| DUK_ASSERT(index != DUK_INVALID_INDEX); |
| uindex = (duk_uidx_t) index; |
| } |
| |
| /* DUK_INVALID_INDEX won't be accepted as a valid index. */ |
| DUK_ASSERT(vs_size + (duk_uidx_t) DUK_INVALID_INDEX >= vs_size); |
| |
| if (DUK_LIKELY(uindex < vs_size)) { |
| return thr->valstack_bottom + uindex; |
| } |
| DUK_ERROR_API_INDEX(thr, index); |
| return NULL; |
| } |
| |
| /* Non-critical. */ |
| DUK_EXTERNAL duk_bool_t duk_is_valid_index(duk_context *ctx, duk_idx_t index) { |
| DUK_ASSERT_CTX_VALID(ctx); |
| DUK_ASSERT(DUK_INVALID_INDEX < 0); |
| |
| return (duk_normalize_index(ctx, index) >= 0); |
| } |
| |
| /* Non-critical. */ |
| DUK_EXTERNAL void duk_require_valid_index(duk_context *ctx, duk_idx_t index) { |
| duk_hthread *thr = (duk_hthread *) ctx; |
| |
| DUK_ASSERT_CTX_VALID(ctx); |
| DUK_ASSERT(DUK_INVALID_INDEX < 0); |
| |
| if (duk_normalize_index(ctx, index) < 0) { |
| DUK_ERROR_API_INDEX(thr, index); |
| return; /* unreachable */ |
| } |
| } |
| |
| /* |
| * Value stack top handling |
| */ |
| |
| DUK_EXTERNAL duk_idx_t duk_get_top(duk_context *ctx) { |
| duk_hthread *thr = (duk_hthread *) ctx; |
| |
| DUK_ASSERT_CTX_VALID(ctx); |
| |
| return (duk_idx_t) (thr->valstack_top - thr->valstack_bottom); |
| } |
| |
| /* Set stack top within currently allocated range, but don't reallocate. |
| * This is performance critical especially for call handling, so whenever |
| * changing, profile and look at generated code. |
| */ |
| DUK_EXTERNAL void duk_set_top(duk_context *ctx, duk_idx_t index) { |
| duk_hthread *thr = (duk_hthread *) ctx; |
| duk_uidx_t vs_size; |
| duk_uidx_t vs_limit; |
| duk_uidx_t uindex; |
| duk_tval *tv; |
| |
| DUK_ASSERT_CTX_VALID(ctx); |
| DUK_ASSERT(DUK_INVALID_INDEX < 0); |
| |
| DUK_ASSERT(thr->valstack_top >= thr->valstack_bottom); |
| DUK_ASSERT(thr->valstack_end >= thr->valstack_bottom); |
| vs_size = (duk_uidx_t) (thr->valstack_top - thr->valstack_bottom); |
| vs_limit = (duk_uidx_t) (thr->valstack_end - thr->valstack_bottom); |
| |
| if (index < 0) { |
| /* Negative indices are always within allocated stack but |
| * must not go below zero index. |
| */ |
| uindex = vs_size + (duk_uidx_t) index; |
| } else { |
| /* Positive index can be higher than valstack top but must |
| * not go above allocated stack (equality is OK). |
| */ |
| uindex = (duk_uidx_t) index; |
| } |
| |
| /* DUK_INVALID_INDEX won't be accepted as a valid index. */ |
| DUK_ASSERT(vs_size + (duk_uidx_t) DUK_INVALID_INDEX >= vs_size); |
| DUK_ASSERT(vs_size + (duk_uidx_t) DUK_INVALID_INDEX >= vs_limit); |
| |
| #if defined(DUK_USE_VALSTACK_UNSAFE) |
| DUK_ASSERT(uindex <= vs_limit); |
| DUK_UNREF(vs_limit); |
| #else |
| if (DUK_UNLIKELY(uindex > vs_limit)) { |
| DUK_ERROR_API_INDEX(thr, index); |
| return; /* unreachable */ |
| } |
| #endif |
| DUK_ASSERT(uindex <= vs_limit); |
| |
| /* Handle change in value stack top. Respect value stack |
| * initialization policy: 'undefined' above top. Note that |
| * DECREF may cause a side effect that reallocates valstack, |
| * so must relookup after DECREF. |
| */ |
| |
| if (uindex >= vs_size) { |
| /* Stack size increases or stays the same. */ |
| #if defined(DUK_USE_ASSERTIONS) |
| duk_uidx_t count; |
| |
| count = uindex - vs_size; |
| while (count != 0) { |
| count--; |
| tv = thr->valstack_top + count; |
| DUK_ASSERT(DUK_TVAL_IS_UNDEFINED(tv)); |
| } |
| #endif |
| thr->valstack_top = thr->valstack_bottom + uindex; |
| } else { |
| /* Stack size decreases. */ |
| #if defined(DUK_USE_REFERENCE_COUNTING) |
| duk_uidx_t count; |
| |
| count = vs_size - uindex; |
| DUK_ASSERT(count > 0); |
| while (count > 0) { |
| count--; |
| tv = --thr->valstack_top; /* tv -> value just before prev top value; must relookup */ |
| DUK_ASSERT(tv >= thr->valstack_bottom); |
| DUK_TVAL_SET_UNDEFINED_UPDREF(thr, tv); /* side effects */ |
| } |
| #else /* DUK_USE_REFERENCE_COUNTING */ |
| duk_uidx_t count; |
| duk_tval *tv_end; |
| |
| count = vs_size - uindex; |
| tv = thr->valstack_top; |
| tv_end = tv - count; |
| DUK_ASSERT(tv > tv_end); |
| do { |
| tv--; |
| DUK_TVAL_SET_UNDEFINED(tv); |
| } while (tv != tv_end); |
| thr->valstack_top = tv_end; |
| #endif /* DUK_USE_REFERENCE_COUNTING */ |
| } |
| } |
| |
| DUK_EXTERNAL duk_idx_t duk_get_top_index(duk_context *ctx) { |
| duk_hthread *thr = (duk_hthread *) ctx; |
| duk_idx_t ret; |
| |
| DUK_ASSERT_CTX_VALID(ctx); |
| |
| ret = ((duk_idx_t) (thr->valstack_top - thr->valstack_bottom)) - 1; |
| if (DUK_UNLIKELY(ret < 0)) { |
| /* Return invalid index; if caller uses this without checking |
| * in another API call, the index won't map to a valid stack |
| * entry. |
| */ |
| return DUK_INVALID_INDEX; |
| } |
| return ret; |
| } |
| |
| DUK_EXTERNAL duk_idx_t duk_require_top_index(duk_context *ctx) { |
| duk_hthread *thr = (duk_hthread *) ctx; |
| duk_idx_t ret; |
| |
| DUK_ASSERT_CTX_VALID(ctx); |
| |
| ret = ((duk_idx_t) (thr->valstack_top - thr->valstack_bottom)) - 1; |
| if (DUK_UNLIKELY(ret < 0)) { |
| DUK_ERROR_API_INDEX(thr, -1); |
| return 0; /* unreachable */ |
| } |
| return ret; |
| } |
| |
| /* |
| * Value stack resizing. |
| * |
| * This resizing happens above the current "top": the value stack can be |
| * grown or shrunk, but the "top" is not affected. The value stack cannot |
| * be resized to a size below the current "top". |
| * |
| * The low level reallocation primitive must carefully recompute all value |
| * stack pointers, and must also work if ALL pointers are NULL. The resize |
| * is quite tricky because the valstack realloc may cause a mark-and-sweep, |
| * which may run finalizers. Running finalizers may resize the valstack |
| * recursively (the same value stack we're working on). So, after realloc |
| * returns, we know that the valstack "top" should still be the same (there |
| * should not be live values above the "top"), but its underlying size and |
| * pointer may have changed. |
| */ |
| |
| /* XXX: perhaps refactor this to allow caller to specify some parameters, or |
| * at least a 'compact' flag which skips any spare or round-up .. useful for |
| * emergency gc. |
| */ |
| |
| DUK_LOCAL duk_bool_t duk__resize_valstack(duk_context *ctx, duk_size_t new_size) { |
| duk_hthread *thr = (duk_hthread *) ctx; |
| duk_ptrdiff_t old_bottom_offset; |
| duk_ptrdiff_t old_top_offset; |
| duk_ptrdiff_t old_end_offset_post; |
| #ifdef DUK_USE_DEBUG |
| duk_ptrdiff_t old_end_offset_pre; |
| duk_tval *old_valstack_pre; |
| duk_tval *old_valstack_post; |
| #endif |
| duk_tval *new_valstack; |
| duk_size_t new_alloc_size; |
| duk_tval *p; |
| |
| DUK_ASSERT_CTX_VALID(ctx); |
| DUK_ASSERT(thr != NULL); |
| DUK_ASSERT(thr->valstack_bottom >= thr->valstack); |
| DUK_ASSERT(thr->valstack_top >= thr->valstack_bottom); |
| DUK_ASSERT(thr->valstack_end >= thr->valstack_top); |
| DUK_ASSERT((duk_size_t) (thr->valstack_top - thr->valstack) <= new_size); /* can't resize below 'top' */ |
| DUK_ASSERT(new_size <= thr->valstack_max); /* valstack limit caller has check, prevents wrapping */ |
| DUK_ASSERT(new_size <= DUK_SIZE_MAX / sizeof(duk_tval)); /* specific assert for wrapping */ |
| |
| /* get pointer offsets for tweaking below */ |
| old_bottom_offset = (((duk_uint8_t *) thr->valstack_bottom) - ((duk_uint8_t *) thr->valstack)); |
| old_top_offset = (((duk_uint8_t *) thr->valstack_top) - ((duk_uint8_t *) thr->valstack)); |
| #ifdef DUK_USE_DEBUG |
| old_end_offset_pre = (((duk_uint8_t *) thr->valstack_end) - ((duk_uint8_t *) thr->valstack)); /* not very useful, used for debugging */ |
| old_valstack_pre = thr->valstack; |
| #endif |
| |
| /* Allocate a new valstack. |
| * |
| * Note: cannot use a plain DUK_REALLOC() because a mark-and-sweep may |
| * invalidate the original thr->valstack base pointer inside the realloc |
| * process. See doc/memory-management.rst. |
| */ |
| |
| new_alloc_size = sizeof(duk_tval) * new_size; |
| new_valstack = (duk_tval *) DUK_REALLOC_INDIRECT(thr->heap, duk_hthread_get_valstack_ptr, (void *) thr, new_alloc_size); |
| if (!new_valstack) { |
| /* Because new_size != 0, if condition doesn't need to be |
| * (new_valstack != NULL || new_size == 0). |
| */ |
| DUK_ASSERT(new_size != 0); |
| DUK_D(DUK_DPRINT("failed to resize valstack to %lu entries (%lu bytes)", |
| (unsigned long) new_size, (unsigned long) new_alloc_size)); |
| return 0; |
| } |
| |
| /* Note: the realloc may have triggered a mark-and-sweep which may |
| * have resized our valstack internally. However, the mark-and-sweep |
| * MUST NOT leave the stack bottom/top in a different state. Particular |
| * assumptions and facts: |
| * |
| * - The thr->valstack pointer may be different after realloc, |
| * and the offset between thr->valstack_end <-> thr->valstack |
| * may have changed. |
| * - The offset between thr->valstack_bottom <-> thr->valstack |
| * and thr->valstack_top <-> thr->valstack MUST NOT have changed, |
| * because mark-and-sweep must adhere to a strict stack policy. |
| * In other words, logical bottom and top MUST NOT have changed. |
| * - All values above the top are unreachable but are initialized |
| * to UNDEFINED, up to the post-realloc valstack_end. |
| * - 'old_end_offset' must be computed after realloc to be correct. |
| */ |
| |
| DUK_ASSERT((((duk_uint8_t *) thr->valstack_bottom) - ((duk_uint8_t *) thr->valstack)) == old_bottom_offset); |
| DUK_ASSERT((((duk_uint8_t *) thr->valstack_top) - ((duk_uint8_t *) thr->valstack)) == old_top_offset); |
| |
| /* success, fixup pointers */ |
| old_end_offset_post = (((duk_uint8_t *) thr->valstack_end) - ((duk_uint8_t *) thr->valstack)); /* must be computed after realloc */ |
| #ifdef DUK_USE_DEBUG |
| old_valstack_post = thr->valstack; |
| #endif |
| thr->valstack = new_valstack; |
| thr->valstack_end = new_valstack + new_size; |
| #if !defined(DUK_USE_PREFER_SIZE) |
| thr->valstack_size = new_size; |
| #endif |
| thr->valstack_bottom = (duk_tval *) (void *) ((duk_uint8_t *) new_valstack + old_bottom_offset); |
| thr->valstack_top = (duk_tval *) (void *) ((duk_uint8_t *) new_valstack + old_top_offset); |
| |
| DUK_ASSERT(thr->valstack_bottom >= thr->valstack); |
| DUK_ASSERT(thr->valstack_top >= thr->valstack_bottom); |
| DUK_ASSERT(thr->valstack_end >= thr->valstack_top); |
| |
| /* useful for debugging */ |
| #ifdef DUK_USE_DEBUG |
| if (old_end_offset_pre != old_end_offset_post) { |
| DUK_D(DUK_DPRINT("valstack was resized during valstack_resize(), probably by mark-and-sweep; " |
| "end offset changed: %lu -> %lu", |
| (unsigned long) old_end_offset_pre, |
| (unsigned long) old_end_offset_post)); |
| } |
| if (old_valstack_pre != old_valstack_post) { |
| DUK_D(DUK_DPRINT("valstack pointer changed during valstack_resize(), probably by mark-and-sweep: %p -> %p", |
| (void *) old_valstack_pre, |
| (void *) old_valstack_post)); |
| } |
| #endif |
| |
| DUK_DD(DUK_DDPRINT("resized valstack to %lu elements (%lu bytes), bottom=%ld, top=%ld, " |
| "new pointers: start=%p end=%p bottom=%p top=%p", |
| (unsigned long) new_size, (unsigned long) new_alloc_size, |
| (long) (thr->valstack_bottom - thr->valstack), |
| (long) (thr->valstack_top - thr->valstack), |
| (void *) thr->valstack, (void *) thr->valstack_end, |
| (void *) thr->valstack_bottom, (void *) thr->valstack_top)); |
| |
| /* Init newly allocated slots (only). */ |
| p = (duk_tval *) (void *) ((duk_uint8_t *) thr->valstack + old_end_offset_post); |
| while (p < thr->valstack_end) { |
| /* Never executed if new size is smaller. */ |
| DUK_TVAL_SET_UNDEFINED(p); |
| p++; |
| } |
| |
| /* Assert for value stack initialization policy. */ |
| #if defined(DUK_USE_ASSERTIONS) |
| p = thr->valstack_top; |
| while (p < thr->valstack_end) { |
| DUK_ASSERT(DUK_TVAL_IS_UNDEFINED(p)); |
| p++; |
| } |
| #endif |
| |
| return 1; |
| } |
| |
| DUK_INTERNAL |
| duk_bool_t duk_valstack_resize_raw(duk_context *ctx, |
| duk_size_t min_new_size, |
| duk_small_uint_t flags) { |
| duk_hthread *thr = (duk_hthread *) ctx; |
| duk_size_t old_size; |
| duk_size_t new_size; |
| duk_bool_t is_shrink = 0; |
| duk_small_uint_t shrink_flag = (flags & DUK_VSRESIZE_FLAG_SHRINK); |
| duk_small_uint_t compact_flag = (flags & DUK_VSRESIZE_FLAG_COMPACT); |
| duk_small_uint_t throw_flag = (flags & DUK_VSRESIZE_FLAG_THROW); |
| |
| DUK_DDD(DUK_DDDPRINT("check valstack resize: min_new_size=%lu, curr_size=%ld, curr_top=%ld, " |
| "curr_bottom=%ld, shrink=%d, compact=%d, throw=%d", |
| (unsigned long) min_new_size, |
| (long) (thr->valstack_end - thr->valstack), |
| (long) (thr->valstack_top - thr->valstack), |
| (long) (thr->valstack_bottom - thr->valstack), |
| (int) shrink_flag, (int) compact_flag, (int) throw_flag)); |
| |
| DUK_ASSERT_CTX_VALID(ctx); |
| DUK_ASSERT(thr != NULL); |
| DUK_ASSERT(thr->valstack_bottom >= thr->valstack); |
| DUK_ASSERT(thr->valstack_top >= thr->valstack_bottom); |
| DUK_ASSERT(thr->valstack_end >= thr->valstack_top); |
| |
| #if defined(DUK_USE_PREFER_SIZE) |
| old_size = (duk_size_t) (thr->valstack_end - thr->valstack); |
| #else |
| DUK_ASSERT((duk_size_t) (thr->valstack_end - thr->valstack) == thr->valstack_size); |
| old_size = thr->valstack_size; |
| #endif |
| |
| if (min_new_size <= old_size) { |
| is_shrink = 1; |
| if (!shrink_flag || |
| old_size - min_new_size < DUK_VALSTACK_SHRINK_THRESHOLD) { |
| DUK_DDD(DUK_DDDPRINT("no need to grow or shrink valstack")); |
| return 1; |
| } |
| } |
| |
| new_size = min_new_size; |
| if (!compact_flag) { |
| if (is_shrink) { |
| /* shrink case; leave some spare */ |
| new_size += DUK_VALSTACK_SHRINK_SPARE; |
| } |
| |
| /* round up roughly to next 'grow step' */ |
| new_size = (new_size / DUK_VALSTACK_GROW_STEP + 1) * DUK_VALSTACK_GROW_STEP; |
| } |
| |
| DUK_DD(DUK_DDPRINT("want to %s valstack: %lu -> %lu elements (min_new_size %lu)", |
| (const char *) (new_size > old_size ? "grow" : "shrink"), |
| (unsigned long) old_size, (unsigned long) new_size, |
| (unsigned long) min_new_size)); |
| |
| if (new_size > thr->valstack_max) { |
| /* Note: may be triggered even if minimal new_size would not reach the limit, |
| * plan limit accordingly (taking DUK_VALSTACK_GROW_STEP into account). |
| */ |
| if (throw_flag) { |
| DUK_ERROR_RANGE(thr, DUK_STR_VALSTACK_LIMIT); |
| } else { |
| return 0; |
| } |
| } |
| |
| /* |
| * When resizing the valstack, a mark-and-sweep may be triggered for |
| * the allocation of the new valstack. If the mark-and-sweep needs |
| * to use our thread for something, it may cause *the same valstack* |
| * to be resized recursively. This happens e.g. when mark-and-sweep |
| * finalizers are called. This is taken into account carefully in |
| * duk__resize_valstack(). |
| * |
| * 'new_size' is known to be <= valstack_max, which ensures that |
| * size_t and pointer arithmetic won't wrap in duk__resize_valstack(). |
| */ |
| |
| if (!duk__resize_valstack(ctx, new_size)) { |
| if (is_shrink) { |
| DUK_DD(DUK_DDPRINT("valstack resize failed, but is a shrink, ignore")); |
| return 1; |
| } |
| |
| DUK_DD(DUK_DDPRINT("valstack resize failed")); |
| |
| if (throw_flag) { |
| DUK_ERROR_ALLOC_DEFMSG(thr); |
| } else { |
| return 0; |
| } |
| } |
| |
| DUK_DDD(DUK_DDDPRINT("valstack resize successful")); |
| return 1; |
| } |
| |
| DUK_EXTERNAL duk_bool_t duk_check_stack(duk_context *ctx, duk_idx_t extra) { |
| duk_hthread *thr = (duk_hthread *) ctx; |
| duk_size_t min_new_size; |
| |
| DUK_ASSERT_CTX_VALID(ctx); |
| DUK_ASSERT(thr != NULL); |
| |
| if (DUK_UNLIKELY(extra < 0)) { |
| /* Clamping to zero makes the API more robust to calling code |
| * calculation errors. |
| */ |
| extra = 0; |
| } |
| |
| min_new_size = (thr->valstack_top - thr->valstack) + extra + DUK_VALSTACK_INTERNAL_EXTRA; |
| return duk_valstack_resize_raw(ctx, |
| min_new_size, /* min_new_size */ |
| 0 /* no shrink */ | /* flags */ |
| 0 /* no compact */ | |
| 0 /* no throw */); |
| } |
| |
| DUK_EXTERNAL void duk_require_stack(duk_context *ctx, duk_idx_t extra) { |
| duk_hthread *thr = (duk_hthread *) ctx; |
| duk_size_t min_new_size; |
| |
| DUK_ASSERT_CTX_VALID(ctx); |
| DUK_ASSERT(thr != NULL); |
| |
| if (DUK_UNLIKELY(extra < 0)) { |
| /* Clamping to zero makes the API more robust to calling code |
| * calculation errors. |
| */ |
| extra = 0; |
| } |
| |
| min_new_size = (thr->valstack_top - thr->valstack) + extra + DUK_VALSTACK_INTERNAL_EXTRA; |
| (void) duk_valstack_resize_raw(ctx, |
| min_new_size, /* min_new_size */ |
| 0 /* no shrink */ | /* flags */ |
| 0 /* no compact */ | |
| DUK_VSRESIZE_FLAG_THROW); |
| } |
| |
| DUK_EXTERNAL duk_bool_t duk_check_stack_top(duk_context *ctx, duk_idx_t top) { |
| duk_size_t min_new_size; |
| |
| DUK_ASSERT_CTX_VALID(ctx); |
| |
| if (DUK_UNLIKELY(top < 0)) { |
| /* Clamping to zero makes the API more robust to calling code |
| * calculation errors. |
| */ |
| top = 0; |
| } |
| |
| min_new_size = top + DUK_VALSTACK_INTERNAL_EXTRA; |
| return duk_valstack_resize_raw(ctx, |
| min_new_size, /* min_new_size */ |
| 0 /* no shrink */ | /* flags */ |
| 0 /* no compact */ | |
| 0 /* no throw */); |
| } |
| |
| DUK_EXTERNAL void duk_require_stack_top(duk_context *ctx, duk_idx_t top) { |
| duk_size_t min_new_size; |
| |
| DUK_ASSERT_CTX_VALID(ctx); |
| |
| if (DUK_UNLIKELY(top < 0)) { |
| /* Clamping to zero makes the API more robust to calling code |
| * calculation errors. |
| */ |
| top = 0; |
| } |
| |
| min_new_size = top + DUK_VALSTACK_INTERNAL_EXTRA; |
| (void) duk_valstack_resize_raw(ctx, |
| min_new_size, /* min_new_size */ |
| 0 /* no shrink */ | /* flags */ |
| 0 /* no compact */ | |
| DUK_VSRESIZE_FLAG_THROW); |
| } |
| |
| /* |
| * Basic stack manipulation: swap, dup, insert, replace, etc |
| */ |
| |
| DUK_EXTERNAL void duk_swap(duk_context *ctx, duk_idx_t index1, duk_idx_t index2) { |
| duk_tval *tv1; |
| duk_tval *tv2; |
| duk_tval tv_tmp; |
| |
| DUK_ASSERT_CTX_VALID(ctx); |
| |
| tv1 = duk_require_tval(ctx, index1); |
| DUK_ASSERT(tv1 != NULL); |
| tv2 = duk_require_tval(ctx, index2); |
| DUK_ASSERT(tv2 != NULL); |
| |
| /* If tv1==tv2 this is a NOP, no check is needed */ |
| DUK_TVAL_SET_TVAL(&tv_tmp, tv1); |
| DUK_TVAL_SET_TVAL(tv1, tv2); |
| DUK_TVAL_SET_TVAL(tv2, &tv_tmp); |
| } |
| |
| DUK_EXTERNAL void duk_swap_top(duk_context *ctx, duk_idx_t index) { |
| DUK_ASSERT_CTX_VALID(ctx); |
| |
| duk_swap(ctx, index, -1); |
| } |
| |
| DUK_EXTERNAL void duk_dup(duk_context *ctx, duk_idx_t from_index) { |
| duk_hthread *thr; |
| duk_tval *tv_from; |
| duk_tval *tv_to; |
| |
| DUK_ASSERT_CTX_VALID(ctx); |
| thr = (duk_hthread *) ctx; |
| DUK__CHECK_SPACE(); |
| |
| tv_from = duk_require_tval(ctx, from_index); |
| tv_to = thr->valstack_top++; |
| DUK_ASSERT(tv_from != NULL); |
| DUK_ASSERT(tv_to != NULL); |
| DUK_TVAL_SET_TVAL(tv_to, tv_from); |
| DUK_TVAL_INCREF(thr, tv_to); /* no side effects */ |
| } |
| |
| DUK_EXTERNAL void duk_dup_top(duk_context *ctx) { |
| duk_hthread *thr; |
| duk_tval *tv_from; |
| duk_tval *tv_to; |
| |
| DUK_ASSERT_CTX_VALID(ctx); |
| thr = (duk_hthread *) ctx; |
| DUK__CHECK_SPACE(); |
| |
| if (thr->valstack_top - thr->valstack_bottom <= 0) { |
| DUK_ERROR_API_INDEX(thr, -1); |
| return; /* unreachable */ |
| } |
| tv_from = thr->valstack_top - 1; |
| tv_to = thr->valstack_top++; |
| DUK_ASSERT(tv_from != NULL); |
| DUK_ASSERT(tv_to != NULL); |
| DUK_TVAL_SET_TVAL(tv_to, tv_from); |
| DUK_TVAL_INCREF(thr, tv_to); /* no side effects */ |
| } |
| |
| DUK_EXTERNAL void duk_insert(duk_context *ctx, duk_idx_t to_index) { |
| duk_tval *p; |
| duk_tval *q; |
| duk_tval tv_tmp; |
| duk_size_t nbytes; |
| |
| DUK_ASSERT_CTX_VALID(ctx); |
| |
| p = duk_require_tval(ctx, to_index); |
| DUK_ASSERT(p != NULL); |
| q = duk_require_tval(ctx, -1); |
| DUK_ASSERT(q != NULL); |
| |
| DUK_ASSERT(q >= p); |
| |
| /* nbytes |
| * <---------> |
| * [ ... | p | x | x | q ] |
| * => [ ... | q | p | x | x ] |
| */ |
| |
| nbytes = (duk_size_t) (((duk_uint8_t *) q) - ((duk_uint8_t *) p)); /* Note: 'q' is top-1 */ |
| |
| DUK_DDD(DUK_DDDPRINT("duk_insert: to_index=%ld, p=%p, q=%p, nbytes=%lu", |
| (long) to_index, (void *) p, (void *) q, (unsigned long) nbytes)); |
| |
| /* No net refcount changes. */ |
| |
| if (nbytes > 0) { |
| DUK_TVAL_SET_TVAL(&tv_tmp, q); |
| DUK_ASSERT(nbytes > 0); |
| DUK_MEMMOVE((void *) (p + 1), (const void *) p, (size_t) nbytes); |
| DUK_TVAL_SET_TVAL(p, &tv_tmp); |
| } else { |
| /* nop: insert top to top */ |
| DUK_ASSERT(nbytes == 0); |
| DUK_ASSERT(p == q); |
| } |
| } |
| |
| DUK_EXTERNAL void duk_replace(duk_context *ctx, duk_idx_t to_index) { |
| duk_hthread *thr = (duk_hthread *) ctx; |
| duk_tval *tv1; |
| duk_tval *tv2; |
| duk_tval tv_tmp; |
| |
| DUK_ASSERT_CTX_VALID(ctx); |
| |
| tv1 = duk_require_tval(ctx, -1); |
| DUK_ASSERT(tv1 != NULL); |
| tv2 = duk_require_tval(ctx, to_index); |
| DUK_ASSERT(tv2 != NULL); |
| |
| /* For tv1 == tv2, both pointing to stack top, the end result |
| * is same as duk_pop(ctx). |
| */ |
| DUK_TVAL_SET_TVAL(&tv_tmp, tv2); |
| DUK_TVAL_SET_TVAL(tv2, tv1); |
| DUK_TVAL_SET_UNDEFINED(tv1); |
| thr->valstack_top--; |
| DUK_TVAL_DECREF(thr, &tv_tmp); /* side effects */ |
| } |
| |
| DUK_EXTERNAL void duk_copy(duk_context *ctx, duk_idx_t from_index, duk_idx_t to_index) { |
| duk_hthread *thr = (duk_hthread *) ctx; |
| duk_tval *tv1; |
| duk_tval *tv2; |
| |
| DUK_ASSERT_CTX_VALID(ctx); |
| DUK_UNREF(thr); /* w/o refcounting */ |
| |
| tv1 = duk_require_tval(ctx, from_index); |
| DUK_ASSERT(tv1 != NULL); |
| tv2 = duk_require_tval(ctx, to_index); |
| DUK_ASSERT(tv2 != NULL); |
| |
| /* For tv1 == tv2, this is a no-op (no explicit check needed). */ |
| DUK_TVAL_SET_TVAL_UPDREF(thr, tv2, tv1); /* side effects */ |
| } |
| |
| DUK_EXTERNAL void duk_remove(duk_context *ctx, duk_idx_t index) { |
| duk_hthread *thr = (duk_hthread *) ctx; |
| duk_tval *p; |
| duk_tval *q; |
| #ifdef DUK_USE_REFERENCE_COUNTING |
| duk_tval tv_tmp; |
| #endif |
| duk_size_t nbytes; |
| |
| DUK_ASSERT_CTX_VALID(ctx); |
| |
| p = duk_require_tval(ctx, index); |
| DUK_ASSERT(p != NULL); |
| q = duk_require_tval(ctx, -1); |
| DUK_ASSERT(q != NULL); |
| |
| DUK_ASSERT(q >= p); |
| |
| /* nbytes zero size case |
| * <---------> |
| * [ ... | p | x | x | q ] [ ... | p==q ] |
| * => [ ... | x | x | q ] [ ... ] |
| */ |
| |
| #ifdef DUK_USE_REFERENCE_COUNTING |
| /* use a temp: decref only when valstack reachable values are correct */ |
| DUK_TVAL_SET_TVAL(&tv_tmp, p); |
| #endif |
| |
| nbytes = (duk_size_t) (((duk_uint8_t *) q) - ((duk_uint8_t *) p)); /* Note: 'q' is top-1 */ |
| DUK_MEMMOVE((void *) p, (const void *) (p + 1), (size_t) nbytes); /* zero size not an issue: pointers are valid */ |
| |
| DUK_TVAL_SET_UNDEFINED(q); |
| thr->valstack_top--; |
| |
| #ifdef DUK_USE_REFERENCE_COUNTING |
| DUK_TVAL_DECREF(thr, &tv_tmp); /* side effects */ |
| #endif |
| } |
| |
| /* |
| * Stack slice primitives |
| */ |
| |
| DUK_EXTERNAL void duk_xcopymove_raw(duk_context *to_ctx, duk_context *from_ctx, duk_idx_t count, duk_bool_t is_copy) { |
| duk_hthread *to_thr = (duk_hthread *) to_ctx; |
| duk_hthread *from_thr = (duk_hthread *) from_ctx; |
| void *src; |
| duk_size_t nbytes; |
| duk_tval *p; |
| duk_tval *q; |
| |
| /* XXX: several pointer comparison issues here */ |
| |
| DUK_ASSERT_CTX_VALID(to_ctx); |
| DUK_ASSERT_CTX_VALID(from_ctx); |
| DUK_ASSERT(to_ctx != NULL); |
| DUK_ASSERT(from_ctx != NULL); |
| |
| if (to_ctx == from_ctx) { |
| DUK_ERROR_API(to_thr, DUK_STR_INVALID_CONTEXT); |
| return; |
| } |
| if ((count < 0) || |
| (count > (duk_idx_t) to_thr->valstack_max)) { |
| /* Maximum value check ensures 'nbytes' won't wrap below. */ |
| DUK_ERROR_API(to_thr, DUK_STR_INVALID_COUNT); |
| return; |
| } |
| |
| nbytes = sizeof(duk_tval) * count; |
| if (nbytes == 0) { |
| return; |
| } |
| DUK_ASSERT(to_thr->valstack_top <= to_thr->valstack_end); |
| if ((duk_size_t) ((duk_uint8_t *) to_thr->valstack_end - (duk_uint8_t *) to_thr->valstack_top) < nbytes) { |
| DUK_ERROR_API(to_thr, DUK_STR_PUSH_BEYOND_ALLOC_STACK); |
| } |
| src = (void *) ((duk_uint8_t *) from_thr->valstack_top - nbytes); |
| if (src < (void *) from_thr->valstack_bottom) { |
| DUK_ERROR_API(to_thr, DUK_STR_INVALID_COUNT); |
| } |
| |
| /* copy values (no overlap even if to_ctx == from_ctx; that's not |
| * allowed now anyway) |
| */ |
| DUK_ASSERT(nbytes > 0); |
| DUK_MEMCPY((void *) to_thr->valstack_top, (const void *) src, (size_t) nbytes); |
| |
| p = to_thr->valstack_top; |
| to_thr->valstack_top = (duk_tval *) (void *) (((duk_uint8_t *) p) + nbytes); |
| |
| if (is_copy) { |
| /* Incref copies, keep originals. */ |
| q = to_thr->valstack_top; |
| while (p < q) { |
| DUK_TVAL_INCREF(to_thr, p); /* no side effects */ |
| p++; |
| } |
| } else { |
| /* No net refcount change. */ |
| p = from_thr->valstack_top; |
| q = (duk_tval *) (void *) (((duk_uint8_t *) p) - nbytes); |
| from_thr->valstack_top = q; |
| |
| while (p > q) { |
| p--; |
| DUK_TVAL_SET_UNDEFINED(p); |
| /* XXX: fast primitive to set a bunch of values to UNDEFINED */ |
| } |
| } |
| } |
| |
| /* |
| * Get/require |
| */ |
| |
| DUK_EXTERNAL void duk_require_undefined(duk_context *ctx, duk_idx_t index) { |
| duk_hthread *thr = (duk_hthread *) ctx; |
| duk_tval *tv; |
| |
| DUK_ASSERT_CTX_VALID(ctx); |
| |
| tv = duk_get_tval(ctx, index); |
| if (tv && DUK_TVAL_IS_UNDEFINED(tv)) { |
| return; |
| } |
| DUK_ERROR_REQUIRE_TYPE_INDEX(thr, index, "undefined", DUK_STR_NOT_UNDEFINED); |
| return; /* not reachable */ |
| } |
| |
| DUK_EXTERNAL void duk_require_null(duk_context *ctx, duk_idx_t index) { |
| duk_hthread *thr = (duk_hthread *) ctx; |
| duk_tval *tv; |
| |
| DUK_ASSERT_CTX_VALID(ctx); |
| |
| tv = duk_get_tval(ctx, index); |
| if (tv && DUK_TVAL_IS_NULL(tv)) { |
| return; |
| } |
| DUK_ERROR_REQUIRE_TYPE_INDEX(thr, index, "null", DUK_STR_NOT_NULL); |
| return; /* not reachable */ |
| } |
| |
| DUK_EXTERNAL duk_bool_t duk_get_boolean(duk_context *ctx, duk_idx_t index) { |
| duk_bool_t ret = 0; /* default: false */ |
| duk_tval *tv; |
| |
| DUK_ASSERT_CTX_VALID(ctx); |
| |
| tv = duk_get_tval(ctx, index); |
| if (tv && DUK_TVAL_IS_BOOLEAN(tv)) { |
| ret = DUK_TVAL_GET_BOOLEAN(tv); |
| } |
| |
| DUK_ASSERT(ret == 0 || ret == 1); |
| return ret; |
| } |
| |
| DUK_EXTERNAL duk_bool_t duk_require_boolean(duk_context *ctx, duk_idx_t index) { |
| duk_hthread *thr = (duk_hthread *) ctx; |
| duk_tval *tv; |
| |
| DUK_ASSERT_CTX_VALID(ctx); |
| |
| tv = duk_get_tval(ctx, index); |
| if (tv && DUK_TVAL_IS_BOOLEAN(tv)) { |
| duk_bool_t ret = DUK_TVAL_GET_BOOLEAN(tv); |
| DUK_ASSERT(ret == 0 || ret == 1); |
| return ret; |
| } |
| DUK_ERROR_REQUIRE_TYPE_INDEX(thr, index, "boolean", DUK_STR_NOT_BOOLEAN); |
| return 0; /* not reachable */ |
| } |
| |
| DUK_EXTERNAL duk_double_t duk_get_number(duk_context *ctx, duk_idx_t index) { |
| duk_double_union ret; |
| duk_tval *tv; |
| |
| DUK_ASSERT_CTX_VALID(ctx); |
| |
| ret.d = DUK_DOUBLE_NAN; /* default: NaN */ |
| tv = duk_get_tval(ctx, index); |
| if (tv && DUK_TVAL_IS_NUMBER(tv)) { |
| ret.d = DUK_TVAL_GET_NUMBER(tv); |
| } |
| |
| /* |
| * Number should already be in NaN-normalized form, but let's |
| * normalize anyway. |
| */ |
| |
| DUK_DBLUNION_NORMALIZE_NAN_CHECK(&ret); |
| return ret.d; |
| } |
| |
| DUK_EXTERNAL duk_double_t duk_require_number(duk_context *ctx, duk_idx_t index) { |
| duk_hthread *thr = (duk_hthread *) ctx; |
| duk_tval *tv; |
| |
| DUK_ASSERT_CTX_VALID(ctx); |
| |
| tv = duk_get_tval(ctx, index); |
| if (tv && DUK_TVAL_IS_NUMBER(tv)) { |
| duk_double_union ret; |
| ret.d = DUK_TVAL_GET_NUMBER(tv); |
| |
| /* |
| * Number should already be in NaN-normalized form, |
| * but let's normalize anyway. |
| */ |
| |
| DUK_DBLUNION_NORMALIZE_NAN_CHECK(&ret); |
| return ret.d; |
| } |
| DUK_ERROR_REQUIRE_TYPE_INDEX(thr, index, "number", DUK_STR_NOT_NUMBER); |
| return DUK_DOUBLE_NAN; /* not reachable */ |
| } |
| |
| DUK_EXTERNAL duk_int_t duk_get_int(duk_context *ctx, duk_idx_t index) { |
| /* Custom coercion for API */ |
| DUK_ASSERT_CTX_VALID(ctx); |
| return (duk_int_t) duk__api_coerce_d2i(ctx, index, 0 /*require*/); |
| } |
| |
| DUK_EXTERNAL duk_uint_t duk_get_uint(duk_context *ctx, duk_idx_t index) { |
| /* Custom coercion for API */ |
| DUK_ASSERT_CTX_VALID(ctx); |
| return (duk_uint_t) duk__api_coerce_d2ui(ctx, index, 0 /*require*/); |
| } |
| |
| DUK_EXTERNAL duk_int_t duk_require_int(duk_context *ctx, duk_idx_t index) { |
| /* Custom coercion for API */ |
| DUK_ASSERT_CTX_VALID(ctx); |
| return (duk_int_t) duk__api_coerce_d2i(ctx, index, 1 /*require*/); |
| } |
| |
| DUK_EXTERNAL duk_uint_t duk_require_uint(duk_context *ctx, duk_idx_t index) { |
| /* Custom coercion for API */ |
| DUK_ASSERT_CTX_VALID(ctx); |
| return (duk_uint_t) duk__api_coerce_d2ui(ctx, index, 1 /*require*/); |
| } |
| |
| DUK_EXTERNAL const char *duk_get_lstring(duk_context *ctx, duk_idx_t index, duk_size_t *out_len) { |
| const char *ret; |
| duk_tval *tv; |
| |
| DUK_ASSERT_CTX_VALID(ctx); |
| |
| /* default: NULL, length 0 */ |
| ret = NULL; |
| if (out_len) { |
| *out_len = 0; |
| } |
| |
| tv = duk_get_tval(ctx, index); |
| if (tv && DUK_TVAL_IS_STRING(tv)) { |
| /* Here we rely on duk_hstring instances always being zero |
| * terminated even if the actual string is not. |
| */ |
| duk_hstring *h = DUK_TVAL_GET_STRING(tv); |
| DUK_ASSERT(h != NULL); |
| ret = (const char *) DUK_HSTRING_GET_DATA(h); |
| if (out_len) { |
| *out_len = DUK_HSTRING_GET_BYTELEN(h); |
| } |
| } |
| |
| return ret; |
| } |
| |
| DUK_EXTERNAL const char *duk_require_lstring(duk_context *ctx, duk_idx_t index, duk_size_t *out_len) { |
| duk_hthread *thr = (duk_hthread *) ctx; |
| const char *ret; |
| |
| DUK_ASSERT_CTX_VALID(ctx); |
| |
| /* Note: this check relies on the fact that even a zero-size string |
| * has a non-NULL pointer. |
| */ |
| ret = duk_get_lstring(ctx, index, out_len); |
| if (ret) { |
| return ret; |
| } |
| DUK_ERROR_REQUIRE_TYPE_INDEX(thr, index, "string", DUK_STR_NOT_STRING); |
| return NULL; /* not reachable */ |
| } |
| |
| DUK_EXTERNAL const char *duk_get_string(duk_context *ctx, duk_idx_t index) { |
| DUK_ASSERT_CTX_VALID(ctx); |
| |
| return duk_get_lstring(ctx, index, NULL); |
| } |
| |
| DUK_EXTERNAL const char *duk_require_string(duk_context *ctx, duk_idx_t index) { |
| DUK_ASSERT_CTX_VALID(ctx); |
| |
| return duk_require_lstring(ctx, index, NULL); |
| } |
| |
| DUK_EXTERNAL void *duk_get_pointer(duk_context *ctx, duk_idx_t index) { |
| duk_tval *tv; |
| |
| DUK_ASSERT_CTX_VALID(ctx); |
| |
| tv = duk_get_tval(ctx, index); |
| if (tv && DUK_TVAL_IS_POINTER(tv)) { |
| void *p = DUK_TVAL_GET_POINTER(tv); /* may be NULL */ |
| return (void *) p; |
| } |
| |
| return NULL; |
| } |
| |
| DUK_EXTERNAL void *duk_require_pointer(duk_context *ctx, duk_idx_t index) { |
| duk_hthread *thr = (duk_hthread *) ctx; |
| duk_tval *tv; |
| |
| DUK_ASSERT_CTX_VALID(ctx); |
| |
| /* Note: here we must be wary of the fact that a pointer may be |
| * valid and be a NULL. |
| */ |
| tv = duk_get_tval(ctx, index); |
| if (tv && DUK_TVAL_IS_POINTER(tv)) { |
| void *p = DUK_TVAL_GET_POINTER(tv); /* may be NULL */ |
| return (void *) p; |
| } |
| DUK_ERROR_REQUIRE_TYPE_INDEX(thr, index, "pointer", DUK_STR_NOT_POINTER); |
| return NULL; /* not reachable */ |
| } |
| |
| #if 0 /*unused*/ |
| DUK_INTERNAL void *duk_get_voidptr(duk_context *ctx, duk_idx_t index) { |
| duk_tval *tv; |
| |
| DUK_ASSERT_CTX_VALID(ctx); |
| |
| tv = duk_get_tval(ctx, index); |
| if (tv && DUK_TVAL_IS_HEAP_ALLOCATED(tv)) { |
| duk_heaphdr *h = DUK_TVAL_GET_HEAPHDR(tv); |
| DUK_ASSERT(h != NULL); |
| return (void *) h; |
| } |
| |
| return NULL; |
| } |
| #endif |
| |
| DUK_LOCAL void *duk__get_buffer_helper(duk_context *ctx, duk_idx_t index, duk_size_t *out_size, duk_bool_t throw_flag) { |
| duk_hthread *thr = (duk_hthread *) ctx; |
| duk_tval *tv; |
| |
| DUK_ASSERT_CTX_VALID(ctx); |
| DUK_UNREF(thr); |
| |
| if (out_size != NULL) { |
| *out_size = 0; |
| } |
| |
| tv = duk_get_tval(ctx, index); |
| if (tv && DUK_TVAL_IS_BUFFER(tv)) { |
| duk_hbuffer *h = DUK_TVAL_GET_BUFFER(tv); |
| DUK_ASSERT(h != NULL); |
| if (out_size) { |
| *out_size = DUK_HBUFFER_GET_SIZE(h); |
| } |
| return (void *) DUK_HBUFFER_GET_DATA_PTR(thr->heap, h); /* may be NULL (but only if size is 0) */ |
| } |
| |
| if (throw_flag) { |
| DUK_ERROR_REQUIRE_TYPE_INDEX(thr, index, "buffer", DUK_STR_NOT_BUFFER); |
| } |
| return NULL; |
| } |
| |
| DUK_EXTERNAL void *duk_get_buffer(duk_context *ctx, duk_idx_t index, duk_size_t *out_size) { |
| return duk__get_buffer_helper(ctx, index, out_size, 0 /*throw_flag*/); |
| } |
| |
| DUK_EXTERNAL void *duk_require_buffer(duk_context *ctx, duk_idx_t index, duk_size_t *out_size) { |
| return duk__get_buffer_helper(ctx, index, out_size, 1 /*throw_flag*/); |
| } |
| |
| DUK_LOCAL void *duk__get_buffer_data_helper(duk_context *ctx, duk_idx_t index, duk_size_t *out_size, duk_bool_t throw_flag) { |
| duk_hthread *thr = (duk_hthread *) ctx; |
| duk_tval *tv; |
| |
| DUK_ASSERT_CTX_VALID(ctx); |
| DUK_UNREF(thr); |
| |
| if (out_size != NULL) { |
| *out_size = 0; |
| } |
| |
| tv = duk_get_tval(ctx, index); |
| if (tv == NULL) { |
| goto fail; |
| } |
| |
| if (DUK_TVAL_IS_BUFFER(tv)) { |
| duk_hbuffer *h = DUK_TVAL_GET_BUFFER(tv); |
| DUK_ASSERT(h != NULL); |
| if (out_size) { |
| *out_size = DUK_HBUFFER_GET_SIZE(h); |
| } |
| return (void *) DUK_HBUFFER_GET_DATA_PTR(thr->heap, h); /* may be NULL (but only if size is 0) */ |
| } else if (DUK_TVAL_IS_OBJECT(tv)) { |
| duk_hobject *h = DUK_TVAL_GET_OBJECT(tv); |
| DUK_ASSERT(h != NULL); |
| if (DUK_HOBJECT_IS_BUFFEROBJECT(h)) { |
| /* XXX: this is probably a useful shared helper: for a |
| * duk_hbufferobject, get a validated buffer pointer/length. |
| */ |
| duk_hbufferobject *h_bufobj = (duk_hbufferobject *) h; |
| DUK_ASSERT_HBUFFEROBJECT_VALID(h_bufobj); |
| |
| if (h_bufobj->buf != NULL && |
| DUK_HBUFFEROBJECT_VALID_SLICE(h_bufobj)) { |
| duk_uint8_t *p; |
| |
| p = (duk_uint8_t *) DUK_HBUFFER_GET_DATA_PTR(thr->heap, h_bufobj->buf); |
| if (out_size != NULL) { |
| *out_size = (duk_size_t) h_bufobj->length; |
| } |
| return (void *) (p + h_bufobj->offset); |
| } |
| /* if slice not fully valid, treat as error */ |
| } |
| } |
| |
| fail: |
| if (throw_flag) { |
| DUK_ERROR_REQUIRE_TYPE_INDEX(thr, index, "buffer", DUK_STR_NOT_BUFFER); |
| } |
| return NULL; |
| } |
| |
| DUK_EXTERNAL void *duk_get_buffer_data(duk_context *ctx, duk_idx_t index, duk_size_t *out_size) { |
| return duk__get_buffer_data_helper(ctx, index, out_size, 0 /*throw_flag*/); |
| } |
| |
| DUK_EXTERNAL void *duk_require_buffer_data(duk_context *ctx, duk_idx_t index, duk_size_t *out_size) { |
| return duk__get_buffer_data_helper(ctx, index, out_size, 1 /*throw_flag*/); |
| } |
| |
| /* Raw helper for getting a value from the stack, checking its tag. |
| * The tag cannot be a number because numbers don't have an internal |
| * tag in the packed representation. |
| */ |
| |
| DUK_LOCAL duk_heaphdr *duk__get_tagged_heaphdr_raw(duk_context *ctx, duk_idx_t index, duk_uint_t tag) { |
| duk_tval *tv; |
| |
| DUK_ASSERT_CTX_VALID(ctx); |
| |
| tv = duk_get_tval(ctx, index); |
| if (tv && (DUK_TVAL_GET_TAG(tv) == tag)) { |
| duk_heaphdr *ret; |
| ret = DUK_TVAL_GET_HEAPHDR(tv); |
| DUK_ASSERT(ret != NULL); /* tagged null pointers should never occur */ |
| return ret; |
| } |
| |
| return (duk_heaphdr *) NULL; |
| } |
| |
| DUK_INTERNAL duk_hstring *duk_get_hstring(duk_context *ctx, duk_idx_t index) { |
| return (duk_hstring *) duk__get_tagged_heaphdr_raw(ctx, index, DUK_TAG_STRING); |
| } |
| |
| DUK_INTERNAL duk_hstring *duk_require_hstring(duk_context *ctx, duk_idx_t index) { |
| duk_heaphdr *h; |
| h = duk__get_tagged_heaphdr_raw(ctx, index, DUK_TAG_STRING); |
| if (h == NULL) { |
| DUK_ERROR_REQUIRE_TYPE_INDEX(ctx, index, "string", DUK_STR_NOT_STRING); |
| } |
| return (duk_hstring *) h; |
| } |
| |
| DUK_INTERNAL duk_hobject *duk_get_hobject(duk_context *ctx, duk_idx_t index) { |
| return (duk_hobject *) duk__get_tagged_heaphdr_raw(ctx, index, DUK_TAG_OBJECT); |
| } |
| |
| DUK_INTERNAL duk_hobject *duk_require_hobject(duk_context *ctx, duk_idx_t index) { |
| duk_heaphdr *h; |
| h = duk__get_tagged_heaphdr_raw(ctx, index, DUK_TAG_OBJECT); |
| if (h == NULL) { |
| DUK_ERROR_REQUIRE_TYPE_INDEX(ctx, index, "object", DUK_STR_NOT_OBJECT); |
| } |
| return (duk_hobject *) h; |
| } |
| |
| DUK_INTERNAL duk_hbuffer *duk_get_hbuffer(duk_context *ctx, duk_idx_t index) { |
| return (duk_hbuffer *) duk__get_tagged_heaphdr_raw(ctx, index, DUK_TAG_BUFFER); |
| } |
| |
| DUK_INTERNAL duk_hbuffer *duk_require_hbuffer(duk_context *ctx, duk_idx_t index) { |
| duk_heaphdr *h; |
| h = duk__get_tagged_heaphdr_raw(ctx, index, DUK_TAG_BUFFER); |
| if (h == NULL) { |
| DUK_ERROR_REQUIRE_TYPE_INDEX(ctx, index, "buffer", DUK_STR_NOT_BUFFER); |
| } |
| return (duk_hbuffer *) h; |
| } |
| |
| DUK_INTERNAL duk_hthread *duk_get_hthread(duk_context *ctx, duk_idx_t index) { |
| duk_hobject *h = (duk_hobject *) duk__get_tagged_heaphdr_raw(ctx, index, DUK_TAG_OBJECT); |
| if (h != NULL && !DUK_HOBJECT_IS_THREAD(h)) { |
| h = NULL; |
| } |
| return (duk_hthread *) h; |
| } |
| |
| DUK_INTERNAL duk_hthread *duk_require_hthread(duk_context *ctx, duk_idx_t index) { |
| duk_hthread *thr = (duk_hthread *) ctx; |
| duk_hobject *h = (duk_hobject *) duk__get_tagged_heaphdr_raw(ctx, index, DUK_TAG_OBJECT); |
| if (!(h != NULL && DUK_HOBJECT_IS_THREAD(h))) { |
| DUK_ERROR_REQUIRE_TYPE_INDEX(thr, index, "thread", DUK_STR_NOT_THREAD); |
| } |
| return (duk_hthread *) h; |
| } |
| |
| DUK_INTERNAL duk_hcompiledfunction *duk_get_hcompiledfunction(duk_context *ctx, duk_idx_t index) { |
| duk_hobject *h = (duk_hobject *) duk__get_tagged_heaphdr_raw(ctx, index, DUK_TAG_OBJECT); |
| if (h != NULL && !DUK_HOBJECT_IS_COMPILEDFUNCTION(h)) { |
| h = NULL; |
| } |
| return (duk_hcompiledfunction *) h; |
| } |
| |
| DUK_INTERNAL duk_hcompiledfunction *duk_require_hcompiledfunction(duk_context *ctx, duk_idx_t index) { |
| duk_hthread *thr = (duk_hthread *) ctx; |
| duk_hobject *h = (duk_hobject *) duk__get_tagged_heaphdr_raw(ctx, index, DUK_TAG_OBJECT); |
| if (!(h != NULL && DUK_HOBJECT_IS_COMPILEDFUNCTION(h))) { |
| DUK_ERROR_REQUIRE_TYPE_INDEX(thr, index, "compiledfunction", DUK_STR_NOT_COMPILEDFUNCTION); |
| } |
| return (duk_hcompiledfunction *) h; |
| } |
| |
| DUK_INTERNAL duk_hnativefunction *duk_get_hnativefunction(duk_context *ctx, duk_idx_t index) { |
| duk_hobject *h = (duk_hobject *) duk__get_tagged_heaphdr_raw(ctx, index, DUK_TAG_OBJECT); |
| if (h != NULL && !DUK_HOBJECT_IS_NATIVEFUNCTION(h)) { |
| h = NULL; |
| } |
| return (duk_hnativefunction *) h; |
| } |
| |
| DUK_INTERNAL duk_hnativefunction *duk_require_hnativefunction(duk_context *ctx, duk_idx_t index) { |
| duk_hthread *thr = (duk_hthread *) ctx; |
| duk_hobject *h = (duk_hobject *) duk__get_tagged_heaphdr_raw(ctx, index, DUK_TAG_OBJECT); |
| if (!(h != NULL && DUK_HOBJECT_IS_NATIVEFUNCTION(h))) { |
| DUK_ERROR_REQUIRE_TYPE_INDEX(thr, index, "nativefunction", DUK_STR_NOT_NATIVEFUNCTION); |
| } |
| return (duk_hnativefunction *) h; |
| } |
| |
| DUK_EXTERNAL duk_c_function duk_get_c_function(duk_context *ctx, duk_idx_t index) { |
| duk_tval *tv; |
| duk_hobject *h; |
| duk_hnativefunction *f; |
| |
| DUK_ASSERT_CTX_VALID(ctx); |
| |
| tv = duk_get_tval(ctx, index); |
| if (!tv) { |
| return NULL; |
| } |
| if (!DUK_TVAL_IS_OBJECT(tv)) { |
| return NULL; |
| } |
| h = DUK_TVAL_GET_OBJECT(tv); |
| DUK_ASSERT(h != NULL); |
| |
| if (!DUK_HOBJECT_IS_NATIVEFUNCTION(h)) { |
| return NULL; |
| } |
| DUK_ASSERT(DUK_HOBJECT_HAS_NATIVEFUNCTION(h)); |
| f = (duk_hnativefunction *) h; |
| |
| return f->func; |
| } |
| |
| DUK_EXTERNAL duk_c_function duk_require_c_function(duk_context *ctx, duk_idx_t index) { |
| duk_hthread *thr = (duk_hthread *) ctx; |
| duk_c_function ret; |
| |
| DUK_ASSERT_CTX_VALID(ctx); |
| |
| ret = duk_get_c_function(ctx, index); |
| if (!ret) { |
| DUK_ERROR_REQUIRE_TYPE_INDEX(thr, index, "nativefunction", DUK_STR_NOT_NATIVEFUNCTION); |
| } |
| return ret; |
| } |
| |
| DUK_EXTERNAL void duk_require_function(duk_context *ctx, duk_idx_t index) { |
| if (!duk_is_function(ctx, index)) { |
| DUK_ERROR_REQUIRE_TYPE_INDEX((duk_hthread *) ctx, index, "function", DUK_STR_NOT_FUNCTION); |
| } |
| } |
| |
| DUK_EXTERNAL duk_context *duk_get_context(duk_context *ctx, duk_idx_t index) { |
| DUK_ASSERT_CTX_VALID(ctx); |
| |
| return (duk_context *) duk_get_hthread(ctx, index); |
| } |
| |
| DUK_EXTERNAL duk_context *duk_require_context(duk_context *ctx, duk_idx_t index) { |
| DUK_ASSERT_CTX_VALID(ctx); |
| |
| return (duk_context *) duk_require_hthread(ctx, index); |
| } |
| |
| DUK_EXTERNAL void *duk_get_heapptr(duk_context *ctx, duk_idx_t index) { |
| duk_tval *tv; |
| void *ret; |
| |
| DUK_ASSERT_CTX_VALID(ctx); |
| |
| tv = duk_get_tval(ctx, index); |
| if (tv && DUK_TVAL_IS_HEAP_ALLOCATED(tv)) { |
| ret = (void *) DUK_TVAL_GET_HEAPHDR(tv); |
| DUK_ASSERT(ret != NULL); |
| return ret; |
| } |
| |
| return (void *) NULL; |
| } |
| |
| DUK_EXTERNAL void *duk_require_heapptr(duk_context *ctx, duk_idx_t index) { |
| duk_hthread *thr = (duk_hthread *) ctx; |
| duk_tval *tv; |
| void *ret; |
| |
| DUK_ASSERT_CTX_VALID(ctx); |
| |
| tv = duk_require_tval(ctx, index); |
| DUK_ASSERT(tv != NULL); |
| if (DUK_TVAL_IS_HEAP_ALLOCATED(tv)) { |
| ret = (void *) DUK_TVAL_GET_HEAPHDR(tv); |
| DUK_ASSERT(ret != NULL); |
| return ret; |
| } |
| |
| DUK_ERROR_REQUIRE_TYPE_INDEX(thr, index, "heapobject", DUK_STR_UNEXPECTED_TYPE); |
| return (void *) NULL; /* not reachable */ |
| } |
| |
| #if 0 |
| /* This would be pointless: we'd return NULL for both lightfuncs and |
| * unexpected types. |
| */ |
| DUK_INTERNAL duk_hobject *duk_get_hobject_or_lfunc(duk_context *ctx, duk_idx_t index) { |
| } |
| #endif |
| |
| /* Useful for internal call sites where we either expect an object (function) |
| * or a lightfunc. Accepts an object (returned as is) or a lightfunc (coerced |
| * to an object). Return value is NULL if value is neither an object nor a |
| * lightfunc. |
| */ |
| DUK_INTERNAL duk_hobject *duk_get_hobject_or_lfunc_coerce(duk_context *ctx, duk_idx_t index) { |
| duk_tval *tv; |
| |
| DUK_ASSERT_CTX_VALID(ctx); |
| |
| tv = duk_require_tval(ctx, index); |
| DUK_ASSERT(tv != NULL); |
| if (DUK_TVAL_IS_OBJECT(tv)) { |
| return DUK_TVAL_GET_OBJECT(tv); |
| } else if (DUK_TVAL_IS_LIGHTFUNC(tv)) { |
| duk_to_object(ctx, index); |
| return duk_require_hobject(ctx, index); |
| } |
| |
| return NULL; |
| } |
| |
| /* Useful for internal call sites where we either expect an object (function) |
| * or a lightfunc. Returns NULL for a lightfunc. |
| */ |
| DUK_INTERNAL duk_hobject *duk_require_hobject_or_lfunc(duk_context *ctx, duk_idx_t index) { |
| duk_hthread *thr = (duk_hthread *) ctx; |
| duk_tval *tv; |
| |
| DUK_ASSERT_CTX_VALID(ctx); |
| |
| tv = duk_require_tval(ctx, index); |
| DUK_ASSERT(tv != NULL); |
| if (DUK_TVAL_IS_OBJECT(tv)) { |
| return DUK_TVAL_GET_OBJECT(tv); |
| } else if (DUK_TVAL_IS_LIGHTFUNC(tv)) { |
| return NULL; |
| } |
| DUK_ERROR_REQUIRE_TYPE_INDEX(thr, index, "object", DUK_STR_NOT_OBJECT); |
| return NULL; /* not reachable */ |
| } |
| |
| /* Useful for internal call sites where we either expect an object (function) |
| * or a lightfunc. Accepts an object (returned as is) or a lightfunc (coerced |
| * to an object). Return value is never NULL. |
| */ |
| DUK_INTERNAL duk_hobject *duk_require_hobject_or_lfunc_coerce(duk_context *ctx, duk_idx_t index) { |
| duk_hthread *thr = (duk_hthread *) ctx; |
| duk_tval *tv; |
| |
| DUK_ASSERT_CTX_VALID(ctx); |
| |
| tv = duk_require_tval(ctx, index); |
| if (DUK_TVAL_IS_OBJECT(tv)) { |
| return DUK_TVAL_GET_OBJECT(tv); |
| } else if (DUK_TVAL_IS_LIGHTFUNC(tv)) { |
| duk_to_object(ctx, index); |
| return duk_require_hobject(ctx, index); |
| } |
| DUK_ERROR_REQUIRE_TYPE_INDEX(thr, index, "object", DUK_STR_NOT_OBJECT); |
| return NULL; /* not reachable */ |
| } |
| |
| DUK_INTERNAL duk_hobject *duk_get_hobject_with_class(duk_context *ctx, duk_idx_t index, duk_small_uint_t classnum) { |
| duk_hobject *h; |
| |
| DUK_ASSERT_CTX_VALID(ctx); |
| DUK_ASSERT_DISABLE(classnum >= 0); /* unsigned */ |
| DUK_ASSERT(classnum <= DUK_HOBJECT_CLASS_MAX); |
| |
| h = (duk_hobject *) duk__get_tagged_heaphdr_raw(ctx, index, DUK_TAG_OBJECT); |
| if (h != NULL && DUK_HOBJECT_GET_CLASS_NUMBER(h) != classnum) { |
| h = NULL; |
| } |
| return h; |
| } |
| |
| DUK_INTERNAL duk_hobject *duk_require_hobject_with_class(duk_context *ctx, duk_idx_t index, duk_small_uint_t classnum) { |
| duk_hthread *thr; |
| duk_hobject *h; |
| |
| DUK_ASSERT_CTX_VALID(ctx); |
| DUK_ASSERT_DISABLE(classnum >= 0); /* unsigned */ |
| DUK_ASSERT(classnum <= DUK_HOBJECT_CLASS_MAX); |
| thr = (duk_hthread *) ctx; |
| |
| h = (duk_hobject *) duk__get_tagged_heaphdr_raw(ctx, index, DUK_TAG_OBJECT); |
| if (!(h != NULL && DUK_HOBJECT_GET_CLASS_NUMBER(h) == classnum)) { |
| duk_hstring *h_class; |
| h_class = DUK_HTHREAD_GET_STRING(thr, DUK_HOBJECT_CLASS_NUMBER_TO_STRIDX(classnum)); |
| DUK_UNREF(h_class); |
| |
| DUK_ERROR_REQUIRE_TYPE_INDEX(thr, index, (const char *) DUK_HSTRING_GET_DATA(h_class), DUK_STR_UNEXPECTED_TYPE); |
| } |
| return h; |
| } |
| |
| DUK_EXTERNAL duk_size_t duk_get_length(duk_context *ctx, duk_idx_t index) { |
| duk_tval *tv; |
| |
| DUK_ASSERT_CTX_VALID(ctx); |
| |
| tv = duk_get_tval(ctx, index); |
| if (!tv) { |
| return 0; |
| } |
| |
| switch (DUK_TVAL_GET_TAG(tv)) { |
| case DUK_TAG_UNDEFINED: |
| case DUK_TAG_NULL: |
| case DUK_TAG_BOOLEAN: |
| case DUK_TAG_POINTER: |
| return 0; |
| case DUK_TAG_STRING: { |
| duk_hstring *h = DUK_TVAL_GET_STRING(tv); |
| DUK_ASSERT(h != NULL); |
| return (duk_size_t) DUK_HSTRING_GET_CHARLEN(h); |
| } |
| case DUK_TAG_OBJECT: { |
| duk_hobject *h = DUK_TVAL_GET_OBJECT(tv); |
| DUK_ASSERT(h != NULL); |
| return (duk_size_t) duk_hobject_get_length((duk_hthread *) ctx, h); |
| } |
| case DUK_TAG_BUFFER: { |
| duk_hbuffer *h = DUK_TVAL_GET_BUFFER(tv); |
| DUK_ASSERT(h != NULL); |
| return (duk_size_t) DUK_HBUFFER_GET_SIZE(h); |
| } |
| case DUK_TAG_LIGHTFUNC: { |
| duk_small_uint_t lf_flags; |
| lf_flags = DUK_TVAL_GET_LIGHTFUNC_FLAGS(tv); |
| return (duk_size_t) DUK_LFUNC_FLAGS_GET_LENGTH(lf_flags); |
| } |
| #if defined(DUK_USE_FASTINT) |
| case DUK_TAG_FASTINT: |
| #endif |
| default: |
| /* number */ |
| DUK_ASSERT(!DUK_TVAL_IS_UNUSED(tv)); |
| DUK_ASSERT(DUK_TVAL_IS_NUMBER(tv)); |
| return 0; |
| } |
| |
| DUK_UNREACHABLE(); |
| } |
| |
| DUK_INTERNAL void duk_set_length(duk_context *ctx, duk_idx_t index, duk_size_t length) { |
| duk_hthread *thr = (duk_hthread *) ctx; |
| duk_hobject *h; |
| |
| DUK_ASSERT_CTX_VALID(ctx); |
| |
| h = duk_get_hobject(ctx, index); |
| if (!h) { |
| return; |
| } |
| |
| duk_hobject_set_length(thr, h, (duk_uint32_t) length); /* XXX: typing */ |
| } |
| |
| /* |
| * Conversions and coercions |
| * |
| * The conversion/coercions are in-place operations on the value stack. |
| * Some operations are implemented here directly, while others call a |
| * helper in duk_js_ops.c after validating arguments. |
| */ |
| |
| /* E5 Section 8.12.8 */ |
| |
| DUK_LOCAL duk_bool_t duk__defaultvalue_coerce_attempt(duk_context *ctx, duk_idx_t index, duk_small_int_t func_stridx) { |
| if (duk_get_prop_stridx(ctx, index, func_stridx)) { |
| /* [ ... func ] */ |
| if (duk_is_callable(ctx, -1)) { |
| duk_dup(ctx, index); /* -> [ ... func this ] */ |
| duk_call_method(ctx, 0); /* -> [ ... retval ] */ |
| if (duk_is_primitive(ctx, -1)) { |
| duk_replace(ctx, index); |
| return 1; |
| } |
| /* [ ... retval ]; popped below */ |
| } |
| } |
| duk_pop(ctx); /* [ ... func/retval ] -> [ ... ] */ |
| return 0; |
| } |
| |
| DUK_EXTERNAL void duk_to_defaultvalue(duk_context *ctx, duk_idx_t index, duk_int_t hint) { |
| duk_hthread *thr = (duk_hthread *) ctx; |
| duk_hobject *obj; |
| /* inline initializer for coercers[] is not allowed by old compilers like BCC */ |
| duk_small_int_t coercers[2]; |
| |
| DUK_ASSERT_CTX_VALID(ctx); |
| DUK_ASSERT(thr != NULL); |
| |
| coercers[0] = DUK_STRIDX_VALUE_OF; |
| coercers[1] = DUK_STRIDX_TO_STRING; |
| |
| index = duk_require_normalize_index(ctx, index); |
| obj = duk_require_hobject_or_lfunc(ctx, index); |
| |
| if (hint == DUK_HINT_NONE) { |
| if (obj != NULL && DUK_HOBJECT_GET_CLASS_NUMBER(obj) == DUK_HOBJECT_CLASS_DATE) { |
| hint = DUK_HINT_STRING; |
| } else { |
| hint = DUK_HINT_NUMBER; |
| } |
| } |
| |
| if (hint == DUK_HINT_STRING) { |
| coercers[0] = DUK_STRIDX_TO_STRING; |
| coercers[1] = DUK_STRIDX_VALUE_OF; |
| } |
| |
| if (duk__defaultvalue_coerce_attempt(ctx, index, coercers[0])) { |
| return; |
| } |
| |
| if (duk__defaultvalue_coerce_attempt(ctx, index, coercers[1])) { |
| return; |
| } |
| |
| DUK_ERROR_TYPE(thr, DUK_STR_DEFAULTVALUE_COERCE_FAILED); |
| } |
| |
| DUK_EXTERNAL void duk_to_undefined(duk_context *ctx, duk_idx_t index) { |
| duk_hthread *thr = (duk_hthread *) ctx; |
| duk_tval *tv; |
| |
| DUK_ASSERT_CTX_VALID(ctx); |
| DUK_UNREF(thr); |
| |
| tv = duk_require_tval(ctx, index); |
| DUK_ASSERT(tv != NULL); |
| DUK_TVAL_SET_UNDEFINED_UPDREF(thr, tv); /* side effects */ |
| } |
| |
| DUK_EXTERNAL void duk_to_null(duk_context *ctx, duk_idx_t index) { |
| duk_hthread *thr = (duk_hthread *) ctx; |
| duk_tval *tv; |
| |
| DUK_ASSERT_CTX_VALID(ctx); |
| DUK_UNREF(thr); |
| |
| tv = duk_require_tval(ctx, index); |
| DUK_ASSERT(tv != NULL); |
| DUK_TVAL_SET_NULL_UPDREF(thr, tv); /* side effects */ |
| } |
| |
| /* E5 Section 9.1 */ |
| DUK_EXTERNAL void duk_to_primitive(duk_context *ctx, duk_idx_t index, duk_int_t hint) { |
| DUK_ASSERT_CTX_VALID(ctx); |
| DUK_ASSERT(hint == DUK_HINT_NONE || hint == DUK_HINT_NUMBER || hint == DUK_HINT_STRING); |
| |
| index = duk_require_normalize_index(ctx, index); |
| |
| if (!duk_check_type_mask(ctx, index, DUK_TYPE_MASK_OBJECT | |
| DUK_TYPE_MASK_LIGHTFUNC)) { |
| /* everything except object stay as is */ |
| return; |
| } |
| duk_to_defaultvalue(ctx, index, hint); |
| } |
| |
| /* E5 Section 9.2 */ |
| DUK_EXTERNAL duk_bool_t duk_to_boolean(duk_context *ctx, duk_idx_t index) { |
| duk_hthread *thr = (duk_hthread *) ctx; |
| duk_tval *tv; |
| duk_bool_t val; |
| |
| DUK_ASSERT_CTX_VALID(ctx); |
| DUK_UNREF(thr); |
| |
| index = duk_require_normalize_index(ctx, index); |
| |
| tv = duk_require_tval(ctx, index); |
| DUK_ASSERT(tv != NULL); |
| |
| val = duk_js_toboolean(tv); |
| DUK_ASSERT(val == 0 || val == 1); |
| |
| /* Note: no need to re-lookup tv, conversion is side effect free */ |
| DUK_ASSERT(tv != NULL); |
| DUK_TVAL_SET_BOOLEAN_UPDREF(thr, tv, val); /* side effects */ |
| return val; |
| } |
| |
| DUK_EXTERNAL duk_double_t duk_to_number(duk_context *ctx, duk_idx_t index) { |
| duk_hthread *thr = (duk_hthread *) ctx; |
| duk_tval *tv; |
| duk_double_t d; |
| |
| DUK_ASSERT_CTX_VALID(ctx); |
| |
| tv = duk_require_tval(ctx, index); |
| DUK_ASSERT(tv != NULL); |
| /* XXX: fastint? */ |
| d = duk_js_tonumber(thr, tv); |
| |
| /* Note: need to re-lookup because ToNumber() may have side effects */ |
| tv = duk_require_tval(ctx, index); |
| DUK_TVAL_SET_NUMBER_UPDREF(thr, tv, d); /* side effects */ |
| return d; |
| } |
| |
| /* XXX: combine all the integer conversions: they share everything |
| * but the helper function for coercion. |
| */ |
| |
| typedef duk_double_t (*duk__toint_coercer)(duk_hthread *thr, duk_tval *tv); |
| |
| DUK_LOCAL duk_double_t duk__to_int_uint_helper(duk_context *ctx, duk_idx_t index, duk__toint_coercer coerce_func) { |
| duk_hthread *thr = (duk_hthread *) ctx; |
| duk_tval *tv; |
| duk_double_t d; |
| |
| DUK_ASSERT_CTX_VALID(ctx); |
| |
| tv = duk_require_tval(ctx, index); |
| DUK_ASSERT(tv != NULL); |
| d = coerce_func(thr, tv); |
| |
| /* XXX: fastint? */ |
| |
| /* Relookup in case coerce_func() has side effects, e.g. ends up coercing an object */ |
| tv = duk_require_tval(ctx, index); |
| DUK_TVAL_SET_NUMBER_UPDREF(thr, tv, d); /* side effects */ |
| return d; |
| } |
| |
| DUK_EXTERNAL duk_int_t duk_to_int(duk_context *ctx, duk_idx_t index) { |
| /* Value coercion (in stack): ToInteger(), E5 Section 9.4 |
| * API return value coercion: custom |
| */ |
| DUK_ASSERT_CTX_VALID(ctx); |
| (void) duk__to_int_uint_helper(ctx, index, duk_js_tointeger); |
| return (duk_int_t) duk__api_coerce_d2i(ctx, index, 0 /*require*/); |
| } |
| |
| DUK_EXTERNAL duk_uint_t duk_to_uint(duk_context *ctx, duk_idx_t index) { |
| /* Value coercion (in stack): ToInteger(), E5 Section 9.4 |
| * API return value coercion: custom |
| */ |
| DUK_ASSERT_CTX_VALID(ctx); |
| (void) duk__to_int_uint_helper(ctx, index, duk_js_tointeger); |
| return (duk_uint_t) duk__api_coerce_d2ui(ctx, index, 0 /*require*/); |
| } |
| |
| DUK_EXTERNAL duk_int32_t duk_to_int32(duk_context *ctx, duk_idx_t index) { |
| duk_hthread *thr = (duk_hthread *) ctx; |
| duk_tval *tv; |
| duk_int32_t ret; |
| |
| DUK_ASSERT_CTX_VALID(ctx); |
| |
| tv = duk_require_tval(ctx, index); |
| DUK_ASSERT(tv != NULL); |
| ret = duk_js_toint32(thr, tv); |
| |
| /* Relookup in case coerce_func() has side effects, e.g. ends up coercing an object */ |
| tv = duk_require_tval(ctx, index); |
| DUK_TVAL_SET_FASTINT_I32_UPDREF(thr, tv, ret); /* side effects */ |
| return ret; |
| } |
| |
| DUK_EXTERNAL duk_uint32_t duk_to_uint32(duk_context *ctx, duk_idx_t index) { |
| duk_hthread *thr = (duk_hthread *) ctx; |
| duk_tval *tv; |
| duk_uint32_t ret; |
| |
| DUK_ASSERT_CTX_VALID(ctx); |
| |
| tv = duk_require_tval(ctx, index); |
| DUK_ASSERT(tv != NULL); |
| ret = duk_js_touint32(thr, tv); |
| |
| /* Relookup in case coerce_func() has side effects, e.g. ends up coercing an object */ |
| tv = duk_require_tval(ctx, index); |
| DUK_TVAL_SET_FASTINT_U32_UPDREF(thr, tv, ret); /* side effects */ |
| return ret; |
| } |
| |
| DUK_EXTERNAL duk_uint16_t duk_to_uint16(duk_context *ctx, duk_idx_t index) { |
| duk_hthread *thr = (duk_hthread *) ctx; |
| duk_tval *tv; |
| duk_uint16_t ret; |
| |
| DUK_ASSERT_CTX_VALID(ctx); |
| |
| tv = duk_require_tval(ctx, index); |
| DUK_ASSERT(tv != NULL); |
| ret = duk_js_touint16(thr, tv); |
| |
| /* Relookup in case coerce_func() has side effects, e.g. ends up coercing an object */ |
| tv = duk_require_tval(ctx, index); |
| DUK_TVAL_SET_FASTINT_U32_UPDREF(thr, tv, ret); /* side effects */ |
| return ret; |
| } |
| |
| #if defined(DUK_USE_BUFFEROBJECT_SUPPORT) |
| /* Special coercion for Uint8ClampedArray. */ |
| DUK_INTERNAL duk_uint8_t duk_to_uint8clamped(duk_context *ctx, duk_idx_t index) { |
| duk_double_t d; |
| duk_double_t t; |
| duk_uint8_t ret; |
| |
| /* XXX: Simplify this algorithm, should be possible to come up with |
| * a shorter and faster algorithm by inspecting IEEE representation |
| * directly. |
| */ |
| |
| d = duk_to_number(ctx, index); |
| if (d <= 0.0) { |
| return 0; |
| } else if (d >= 255) { |
| return 255; |
| } else if (DUK_ISNAN(d)) { |
| /* Avoid NaN-to-integer coercion as it is compiler specific. */ |
| return 0; |
| } |
| |
| t = d - DUK_FLOOR(d); |
| if (t == 0.5) { |
| /* Exact halfway, round to even. */ |
| ret = (duk_uint8_t) d; |
| ret = (ret + 1) & 0xfe; /* Example: d=3.5, t=0.5 -> ret = (3 + 1) & 0xfe = 4 & 0xfe = 4 |
| * Example: d=4.5, t=0.5 -> ret = (4 + 1) & 0xfe = 5 & 0xfe = 4 |
| */ |
| } else { |
| /* Not halfway, round to nearest. */ |
| ret = (duk_uint8_t) (d + 0.5); |
| } |
| return ret; |
| } |
| #endif /* DUK_USE_BUFFEROBJECT_SUPPORT */ |
| |
| DUK_EXTERNAL const char *duk_to_lstring(duk_context *ctx, duk_idx_t index, duk_size_t *out_len) { |
| DUK_ASSERT_CTX_VALID(ctx); |
| |
| (void) duk_to_string(ctx, index); |
| return duk_require_lstring(ctx, index, out_len); |
| } |
| |
| DUK_LOCAL duk_ret_t duk__safe_to_string_raw(duk_context *ctx) { |
| DUK_ASSERT_CTX_VALID(ctx); |
| |
| duk_to_string(ctx, -1); |
| return 1; |
| } |
| |
| DUK_EXTERNAL const char *duk_safe_to_lstring(duk_context *ctx, duk_idx_t index, duk_size_t *out_len) { |
| DUK_ASSERT_CTX_VALID(ctx); |
| |
| index = duk_require_normalize_index(ctx, index); |
| |
| /* We intentionally ignore the duk_safe_call() return value and only |
| * check the output type. This way we don't also need to check that |
| * the returned value is indeed a string in the success case. |
| */ |
| |
| duk_dup(ctx, index); |
| (void) duk_safe_call(ctx, duk__safe_to_string_raw, 1 /*nargs*/, 1 /*nrets*/); |
| if (!duk_is_string(ctx, -1)) { |
| /* Error: try coercing error to string once. */ |
| (void) duk_safe_call(ctx, duk__safe_to_string_raw, 1 /*nargs*/, 1 /*nrets*/); |
| if (!duk_is_string(ctx, -1)) { |
| /* Double error */ |
| duk_pop(ctx); |
| duk_push_hstring_stridx(ctx, DUK_STRIDX_UC_ERROR); |
| } else { |
| ; |
| } |
| } else { |
| ; |
| } |
| DUK_ASSERT(duk_is_string(ctx, -1)); |
| DUK_ASSERT(duk_get_string(ctx, -1) != NULL); |
| |
| duk_replace(ctx, index); |
| return duk_get_lstring(ctx, index, out_len); |
| } |
| |
| #if defined(DUK_USE_DEBUGGER_SUPPORT) /* only needed by debugger for now */ |
| DUK_INTERNAL duk_hstring *duk_safe_to_hstring(duk_context *ctx, duk_idx_t index) { |
| (void) duk_safe_to_string(ctx, index); |
| DUK_ASSERT(duk_is_string(ctx, index)); |
| DUK_ASSERT(duk_get_hstring(ctx, index) != NULL); |
| return duk_get_hstring(ctx, index); |
| } |
| #endif |
| |
| /* Coerce top into Object.prototype.toString() output. */ |
| DUK_INTERNAL void duk_to_object_class_string_top(duk_context *ctx) { |
| duk_hthread *thr; |
| duk_uint_t typemask; |
| duk_hstring *h_strclass; |
| |
| DUK_ASSERT_CTX_VALID(ctx); |
| thr = (duk_hthread *) ctx; |
| DUK_UNREF(thr); |
| |
| typemask = duk_get_type_mask(ctx, -1); |
| if (typemask & DUK_TYPE_MASK_UNDEFINED) { |
| h_strclass = DUK_HTHREAD_STRING_UC_UNDEFINED(thr); |
| } else if (typemask & DUK_TYPE_MASK_NULL) { |
| h_strclass = DUK_HTHREAD_STRING_UC_NULL(thr); |
| } else { |
| duk_hobject *h_obj; |
| |
| duk_to_object(ctx, -1); |
| h_obj = duk_get_hobject(ctx, -1); |
| DUK_ASSERT(h_obj != NULL); |
| |
| h_strclass = DUK_HOBJECT_GET_CLASS_STRING(thr->heap, h_obj); |
| } |
| DUK_ASSERT(h_strclass != NULL); |
| |
| duk_pop(ctx); |
| duk_push_sprintf(ctx, "[object %s]", (const char *) DUK_HSTRING_GET_DATA(h_strclass)); |
| } |
| |
| #if !defined(DUK_USE_PARANOID_ERRORS) |
| DUK_INTERNAL void duk_push_hobject_class_string(duk_context *ctx, duk_hobject *h) { |
| duk_hthread *thr; |
| duk_hstring *h_strclass; |
| |
| DUK_ASSERT_CTX_VALID(ctx); |
| DUK_ASSERT(h != NULL); |
| thr = (duk_hthread *) ctx; |
| DUK_UNREF(thr); |
| |
| h_strclass = DUK_HOBJECT_GET_CLASS_STRING(thr->heap, h); |
| DUK_ASSERT(h_strclass != NULL); |
| duk_push_sprintf(ctx, "[object %s]", (const char *) DUK_HSTRING_GET_DATA(h_strclass)); |
| } |
| #endif /* !DUK_USE_PARANOID_ERRORS */ |
| |
| /* XXX: other variants like uint, u32 etc */ |
| DUK_INTERNAL duk_int_t duk_to_int_clamped_raw(duk_context *ctx, duk_idx_t index, duk_int_t minval, duk_int_t maxval, duk_bool_t *out_clamped) { |
| duk_hthread *thr = (duk_hthread *) ctx; |
| duk_tval *tv; |
| duk_tval tv_tmp; |
| duk_double_t d, dmin, dmax; |
| duk_int_t res; |
| duk_bool_t clamped = 0; |
| |
| DUK_ASSERT_CTX_VALID(ctx); |
| |
| tv = duk_require_tval(ctx, index); |
| DUK_ASSERT(tv != NULL); |
| d = duk_js_tointeger(thr, tv); /* E5 Section 9.4, ToInteger() */ |
| |
| dmin = (duk_double_t) minval; |
| dmax = (duk_double_t) maxval; |
| |
| if (d < dmin) { |
| clamped = 1; |
| res = minval; |
| d = dmin; |
| } else if (d > dmax) { |
| clamped = 1; |
| res = maxval; |
| d = dmax; |
| } else { |
| res = (duk_int_t) d; |
| } |
| DUK_UNREF(d); /* SCANBUILD: with suitable dmin/dmax limits 'd' is unused */ |
| /* 'd' and 'res' agree here */ |
| |
| /* Relookup in case duk_js_tointeger() ends up e.g. coercing an object. */ |
| tv = duk_get_tval(ctx, index); |
| DUK_ASSERT(tv != NULL); /* not popped by side effect */ |
| DUK_TVAL_SET_TVAL(&tv_tmp, tv); |
| #if defined(DUK_USE_FASTINT) |
| #if (DUK_INT_MAX <= 0x7fffffffL) |
| DUK_TVAL_SET_FASTINT_I32(tv, res); |
| #else |
| /* Clamping needed if duk_int_t is 64 bits. */ |
| if (res >= DUK_FASTINT_MIN && res <= DUK_FASTINT_MAX) { |
| DUK_TVAL_SET_FASTINT(tv, res); |
| } else { |
| DUK_TVAL_SET_NUMBER(tv, d); |
| } |
| #endif |
| #else |
| DUK_TVAL_SET_NUMBER(tv, d); /* no need to incref */ |
| #endif |
| DUK_TVAL_DECREF(thr, &tv_tmp); /* side effects */ |
| |
| if (out_clamped) { |
| *out_clamped = clamped; |
| } else { |
| /* coerced value is updated to value stack even when RangeError thrown */ |
| if (clamped) { |
| DUK_ERROR_RANGE(thr, DUK_STR_NUMBER_OUTSIDE_RANGE); |
| } |
| } |
| |
| return res; |
| } |
| |
| DUK_INTERNAL duk_int_t duk_to_int_clamped(duk_context *ctx, duk_idx_t index, duk_idx_t minval, duk_idx_t maxval) { |
| duk_bool_t dummy; |
| return duk_to_int_clamped_raw(ctx, index, minval, maxval, &dummy); |
| } |
| |
| DUK_INTERNAL duk_int_t duk_to_int_check_range(duk_context *ctx, duk_idx_t index, duk_int_t minval, duk_int_t maxval) { |
| return duk_to_int_clamped_raw(ctx, index, minval, maxval, NULL); /* out_clamped==NULL -> RangeError if outside range */ |
| } |
| |
| DUK_EXTERNAL const char *duk_to_string(duk_context *ctx, duk_idx_t index) { |
| duk_hthread *thr = (duk_hthread *) ctx; |
| duk_tval *tv; |
| |
| DUK_ASSERT_CTX_VALID(ctx); |
| DUK_UNREF(thr); |
| |
| index = duk_require_normalize_index(ctx, index); |
| |
| tv = duk_require_tval(ctx, index); |
| DUK_ASSERT(tv != NULL); |
| |
| switch (DUK_TVAL_GET_TAG(tv)) { |
| case DUK_TAG_UNDEFINED: { |
| duk_push_hstring_stridx(ctx, DUK_STRIDX_LC_UNDEFINED); |
| break; |
| } |
| case DUK_TAG_NULL: { |
| duk_push_hstring_stridx(ctx, DUK_STRIDX_LC_NULL); |
| break; |
| } |
| case DUK_TAG_BOOLEAN: { |
| if (DUK_TVAL_GET_BOOLEAN(tv)) { |
| duk_push_hstring_stridx(ctx, DUK_STRIDX_TRUE); |
| } else { |
| duk_push_hstring_stridx(ctx, DUK_STRIDX_FALSE); |
| } |
| break; |
| } |
| case DUK_TAG_STRING: { |
| /* nop */ |
| goto skip_replace; |
| } |
| case DUK_TAG_OBJECT: { |
| duk_to_primitive(ctx, index, DUK_HINT_STRING); |
| return duk_to_string(ctx, index); /* Note: recursive call */ |
| } |
| case DUK_TAG_BUFFER: { |
| duk_hbuffer *h = DUK_TVAL_GET_BUFFER(tv); |
| |
| /* Note: this allows creation of internal strings. */ |
| |
| DUK_ASSERT(h != NULL); |
| duk_push_lstring(ctx, |
| (const char *) DUK_HBUFFER_GET_DATA_PTR(thr->heap, h), |
| (duk_size_t) DUK_HBUFFER_GET_SIZE(h)); |
| break; |
| } |
| case DUK_TAG_POINTER: { |
| void *ptr = DUK_TVAL_GET_POINTER(tv); |
| if (ptr != NULL) { |
| duk_push_sprintf(ctx, DUK_STR_FMT_PTR, (void *) ptr); |
| } else { |
| /* Represent a null pointer as 'null' to be consistent with |
| * the JX format variant. Native '%p' format for a NULL |
| * pointer may be e.g. '(nil)'. |
| */ |
| duk_push_hstring_stridx(ctx, DUK_STRIDX_LC_NULL); |
| } |
| break; |
| } |
| case DUK_TAG_LIGHTFUNC: { |
| /* Should match Function.prototype.toString() */ |
| duk_push_lightfunc_tostring(ctx, tv); |
| break; |
| } |
| #if defined(DUK_USE_FASTINT) |
| case DUK_TAG_FASTINT: |
| #endif |
| default: { |
| /* number */ |
| DUK_ASSERT(!DUK_TVAL_IS_UNUSED(tv)); |
| DUK_ASSERT(DUK_TVAL_IS_NUMBER(tv)); |
| duk_push_tval(ctx, tv); |
| duk_numconv_stringify(ctx, |
| 10 /*radix*/, |
| 0 /*precision:shortest*/, |
| 0 /*force_exponential*/); |
| break; |
| } |
| } |
| |
| duk_replace(ctx, index); |
| |
| skip_replace: |
| return duk_require_string(ctx, index); |
| } |
| |
| DUK_INTERNAL duk_hstring *duk_to_hstring(duk_context *ctx, duk_idx_t index) { |
| duk_hstring *ret; |
| DUK_ASSERT_CTX_VALID(ctx); |
| duk_to_string(ctx, index); |
| ret = duk_get_hstring(ctx, index); |
| DUK_ASSERT(ret != NULL); |
| return ret; |
| } |
| |
| DUK_EXTERNAL void *duk_to_buffer_raw(duk_context *ctx, duk_idx_t index, duk_size_t *out_size, duk_uint_t mode) { |
| duk_hthread *thr = (duk_hthread *) ctx; |
| duk_hbuffer *h_buf; |
| const duk_uint8_t *src_data; |
| duk_size_t src_size; |
| duk_uint8_t *dst_data; |
| |
| DUK_ASSERT_CTX_VALID(ctx); |
| DUK_UNREF(thr); |
| |
| index = duk_require_normalize_index(ctx, index); |
| |
| h_buf = duk_get_hbuffer(ctx, index); |
| if (h_buf != NULL) { |
| /* Buffer is kept as is, with the fixed/dynamic nature of the |
| * buffer only changed if requested. An external buffer |
| * is converted into a non-external dynamic buffer in a |
| * duk_to_dynamic_buffer() call. |
| */ |
| duk_uint_t tmp; |
| duk_uint8_t *tmp_ptr; |
| |
| tmp_ptr = (duk_uint8_t *) DUK_HBUFFER_GET_DATA_PTR(thr->heap, h_buf); |
| src_data = (const duk_uint8_t *) tmp_ptr; |
| src_size = DUK_HBUFFER_GET_SIZE(h_buf); |
| |
| tmp = (DUK_HBUFFER_HAS_DYNAMIC(h_buf) ? DUK_BUF_MODE_DYNAMIC : DUK_BUF_MODE_FIXED); |
| if ((tmp == mode && !DUK_HBUFFER_HAS_EXTERNAL(h_buf)) || |
| mode == DUK_BUF_MODE_DONTCARE) { |
| /* Note: src_data may be NULL if input is a zero-size |
| * dynamic buffer. |
| */ |
| dst_data = tmp_ptr; |
| goto skip_copy; |
| } |
| } else { |
| /* Non-buffer value is first ToString() coerced, then converted |
| * to a buffer (fixed buffer is used unless a dynamic buffer is |
| * explicitly requested). |
| */ |
| |
| src_data = (const duk_uint8_t *) duk_to_lstring(ctx, index, &src_size); |
| } |
| |
| dst_data = (duk_uint8_t *) duk_push_buffer(ctx, src_size, (mode == DUK_BUF_MODE_DYNAMIC) /*dynamic*/); |
| if (DUK_LIKELY(src_size > 0)) { |
| /* When src_size == 0, src_data may be NULL (if source |
| * buffer is dynamic), and dst_data may be NULL (if |
| * target buffer is dynamic). Avoid zero-size memcpy() |
| * with an invalid pointer. |
| */ |
| DUK_MEMCPY((void *) dst_data, (const void *) src_data, (size_t) src_size); |
| } |
| duk_replace(ctx, index); |
| skip_copy: |
| |
| if (out_size) { |
| *out_size = src_size; |
| } |
| return dst_data; |
| } |
| |
| DUK_EXTERNAL void *duk_to_pointer(duk_context *ctx, duk_idx_t index) { |
| duk_tval *tv; |
| void *res; |
| |
| DUK_ASSERT_CTX_VALID(ctx); |
| |
| index = duk_require_normalize_index(ctx, index); |
| |
| tv = duk_require_tval(ctx, index); |
| DUK_ASSERT(tv != NULL); |
| |
| switch (DUK_TVAL_GET_TAG(tv)) { |
| case DUK_TAG_UNDEFINED: |
| case DUK_TAG_NULL: |
| case DUK_TAG_BOOLEAN: |
| res = NULL; |
| break; |
| case DUK_TAG_POINTER: |
| res = DUK_TVAL_GET_POINTER(tv); |
| break; |
| case DUK_TAG_STRING: |
| case DUK_TAG_OBJECT: |
| case DUK_TAG_BUFFER: |
| /* Heap allocated: return heap pointer which is NOT useful |
| * for the caller, except for debugging. |
| */ |
| res = (void *) DUK_TVAL_GET_HEAPHDR(tv); |
| break; |
| case DUK_TAG_LIGHTFUNC: |
| /* Function pointers do not always cast correctly to void * |
| * (depends on memory and segmentation model for instance), |
| * so they coerce to NULL. |
| */ |
| res = NULL; |
| break; |
| #if defined(DUK_USE_FASTINT) |
| case DUK_TAG_FASTINT: |
| #endif |
| default: |
| /* number */ |
| DUK_ASSERT(!DUK_TVAL_IS_UNUSED(tv)); |
| DUK_ASSERT(DUK_TVAL_IS_NUMBER(tv)); |
| res = NULL; |
| break; |
| } |
| |
| duk_push_pointer(ctx, res); |
| duk_replace(ctx, index); |
| return res; |
| } |
| |
| DUK_EXTERNAL void duk_to_object(duk_context *ctx, duk_idx_t index) { |
| duk_hthread *thr = (duk_hthread *) ctx; |
| duk_tval *tv; |
| duk_uint_t flags = 0; /* shared flags for a subset of types */ |
| duk_small_int_t proto = 0; |
| |
| DUK_ASSERT_CTX_VALID(ctx); |
| |
| index = duk_require_normalize_index(ctx, index); |
| |
| tv = duk_require_tval(ctx, index); |
| DUK_ASSERT(tv != NULL); |
| |
| switch (DUK_TVAL_GET_TAG(tv)) { |
| case DUK_TAG_UNDEFINED: |
| case DUK_TAG_NULL: { |
| DUK_ERROR_TYPE(thr, DUK_STR_NOT_OBJECT_COERCIBLE); |
| break; |
| } |
| case DUK_TAG_BOOLEAN: { |
| flags = DUK_HOBJECT_FLAG_EXTENSIBLE | |
| DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_BOOLEAN); |
| proto = DUK_BIDX_BOOLEAN_PROTOTYPE; |
| goto create_object; |
| } |
| case DUK_TAG_STRING: { |
| flags = DUK_HOBJECT_FLAG_EXTENSIBLE | |
| DUK_HOBJECT_FLAG_EXOTIC_STRINGOBJ | |
| DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_STRING); |
| proto = DUK_BIDX_STRING_PROTOTYPE; |
| goto create_object; |
| } |
| case DUK_TAG_OBJECT: { |
| /* nop */ |
| break; |
| } |
| case DUK_TAG_BUFFER: { |
| /* A plain buffer coerces to a Duktape.Buffer because it's the |
| * object counterpart of the plain buffer value. But it might |
| * still make more sense to produce an ArrayBuffer here? |
| */ |
| |
| duk_hbufferobject *h_bufobj; |
| duk_hbuffer *h_val; |
| |
| h_val = DUK_TVAL_GET_BUFFER(tv); |
| DUK_ASSERT(h_val != NULL); |
| |
| h_bufobj = duk_push_bufferobject_raw(ctx, |
| DUK_HOBJECT_FLAG_EXTENSIBLE | |
| DUK_HOBJECT_FLAG_BUFFEROBJECT | |
| DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_BUFFER), |
| DUK_BIDX_BUFFER_PROTOTYPE); |
| DUK_ASSERT(h_bufobj != NULL); |
| DUK_ASSERT(DUK_HOBJECT_HAS_EXTENSIBLE((duk_hobject *) h_bufobj)); |
| DUK_ASSERT(DUK_HOBJECT_IS_BUFFEROBJECT((duk_hobject *) h_bufobj)); |
| |
| h_bufobj->buf = h_val; |
| DUK_HBUFFER_INCREF(thr, h_val); |
| DUK_ASSERT(h_bufobj->offset == 0); |
| h_bufobj->length = (duk_uint_t) DUK_HBUFFER_GET_SIZE(h_val); |
| DUK_ASSERT(h_bufobj->shift == 0); |
| DUK_ASSERT(h_bufobj->elem_type == DUK_HBUFFEROBJECT_ELEM_UINT8); |
| |
| DUK_ASSERT_HBUFFEROBJECT_VALID(h_bufobj); |
| goto replace_value; |
| } |
| case DUK_TAG_POINTER: { |
| flags = DUK_HOBJECT_FLAG_EXTENSIBLE | |
| DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_POINTER); |
| proto = DUK_BIDX_POINTER_PROTOTYPE; |
| goto create_object; |
| } |
| case DUK_TAG_LIGHTFUNC: { |
| /* Lightfunc coerces to a Function instance with concrete |
| * properties. Since 'length' is virtual for Duktape/C |
| * functions, don't need to define that. |
| * |
| * The result is made extensible to mimic what happens to |
| * strings: |
| * > Object.isExtensible(Object('foo')) |
| * true |
| */ |
| duk_small_uint_t lf_flags; |
| duk_idx_t nargs; |
| duk_small_uint_t lf_len; |
| duk_c_function func; |
| duk_hnativefunction *nf; |
| |
| DUK_TVAL_GET_LIGHTFUNC(tv, func, lf_flags); |
| |
| nargs = (duk_idx_t) DUK_LFUNC_FLAGS_GET_NARGS(lf_flags); |
| if (nargs == DUK_LFUNC_NARGS_VARARGS) { |
| nargs = (duk_idx_t) DUK_VARARGS; |
| } |
| flags = DUK_HOBJECT_FLAG_EXTENSIBLE | |
| DUK_HOBJECT_FLAG_CONSTRUCTABLE | |
| DUK_HOBJECT_FLAG_NATIVEFUNCTION | |
| DUK_HOBJECT_FLAG_NEWENV | |
| DUK_HOBJECT_FLAG_STRICT | |
| DUK_HOBJECT_FLAG_NOTAIL | |
| /* DUK_HOBJECT_FLAG_EXOTIC_DUKFUNC: omitted here intentionally */ |
| DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_FUNCTION); |
| (void) duk__push_c_function_raw(ctx, func, nargs, flags); |
| |
| lf_len = DUK_LFUNC_FLAGS_GET_LENGTH(lf_flags); |
| if ((duk_idx_t) lf_len != nargs) { |
| /* Explicit length is only needed if it differs from 'nargs'. */ |
| duk_push_int(ctx, (duk_int_t) lf_len); |
| duk_xdef_prop_stridx(ctx, -2, DUK_STRIDX_LENGTH, DUK_PROPDESC_FLAGS_NONE); |
| } |
| duk_push_lightfunc_name(ctx, tv); |
| duk_xdef_prop_stridx(ctx, -2, DUK_STRIDX_NAME, DUK_PROPDESC_FLAGS_NONE); |
| |
| nf = duk_get_hnativefunction(ctx, -1); |
| DUK_ASSERT(nf != NULL); |
| nf->magic = (duk_int16_t) DUK_LFUNC_FLAGS_GET_MAGIC(lf_flags); |
| |
| /* Enable DUKFUNC exotic behavior once properties are set up. */ |
| DUK_HOBJECT_SET_EXOTIC_DUKFUNC((duk_hobject *) nf); |
| goto replace_value; |
| } |
| #if defined(DUK_USE_FASTINT) |
| case DUK_TAG_FASTINT: |
| #endif |
| default: { |
| DUK_ASSERT(!DUK_TVAL_IS_UNUSED(tv)); |
| DUK_ASSERT(DUK_TVAL_IS_NUMBER(tv)); |
| flags = DUK_HOBJECT_FLAG_EXTENSIBLE | |
| DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_NUMBER); |
| proto = DUK_BIDX_NUMBER_PROTOTYPE; |
| goto create_object; |
| } |
| } |
| return; |
| |
| create_object: |
| (void) duk_push_object_helper(ctx, flags, proto); |
| |
| /* Note: Boolean prototype's internal value property is not writable, |
| * but duk_xdef_prop_stridx() disregards the write protection. Boolean |
| * instances are immutable. |
| * |
| * String and buffer special behaviors are already enabled which is not |
| * ideal, but a write to the internal value is not affected by them. |
| */ |
| duk_dup(ctx, index); |
| duk_xdef_prop_stridx(ctx, -2, DUK_STRIDX_INT_VALUE, DUK_PROPDESC_FLAGS_NONE); |
| |
| replace_value: |
| duk_replace(ctx, index); |
| } |
| |
| /* |
| * Type checking |
| */ |
| |
| DUK_LOCAL duk_bool_t duk__tag_check(duk_context *ctx, duk_idx_t index, duk_small_uint_t tag) { |
| duk_tval *tv; |
| |
| tv = duk_get_tval(ctx, index); |
| if (!tv) { |
| return 0; |
| } |
| return (DUK_TVAL_GET_TAG(tv) == tag); |
| } |
| |
| DUK_LOCAL duk_bool_t duk__obj_flag_any_default_false(duk_context *ctx, duk_idx_t index, duk_uint_t flag_mask) { |
| duk_hobject *obj; |
| |
| DUK_ASSERT_CTX_VALID(ctx); |
| |
| obj = duk_get_hobject(ctx, index); |
| if (obj) { |
| return (DUK_HEAPHDR_CHECK_FLAG_BITS((duk_heaphdr *) obj, flag_mask) ? 1 : 0); |
| } |
| return 0; |
| } |
| |
| DUK_EXTERNAL duk_int_t duk_get_type(duk_context *ctx, duk_idx_t index) { |
| duk_tval *tv; |
| |
| DUK_ASSERT_CTX_VALID(ctx); |
| |
| tv = duk_get_tval(ctx, index); |
| if (!tv) { |
| return DUK_TYPE_NONE; |
| } |
| switch (DUK_TVAL_GET_TAG(tv)) { |
| case DUK_TAG_UNDEFINED: |
| return DUK_TYPE_UNDEFINED; |
| case DUK_TAG_NULL: |
| return DUK_TYPE_NULL; |
| case DUK_TAG_BOOLEAN: |
| return DUK_TYPE_BOOLEAN; |
| case DUK_TAG_STRING: |
| return DUK_TYPE_STRING; |
| case DUK_TAG_OBJECT: |
| return DUK_TYPE_OBJECT; |
| case DUK_TAG_BUFFER: |
| return DUK_TYPE_BUFFER; |
| case DUK_TAG_POINTER: |
| return DUK_TYPE_POINTER; |
| case DUK_TAG_LIGHTFUNC: |
| return DUK_TYPE_LIGHTFUNC; |
| #if defined(DUK_USE_FASTINT) |
| case DUK_TAG_FASTINT: |
| #endif |
| default: |
| /* Note: number has no explicit tag (in 8-byte representation) */ |
| DUK_ASSERT(!DUK_TVAL_IS_UNUSED(tv)); |
| DUK_ASSERT(DUK_TVAL_IS_NUMBER(tv)); |
| return DUK_TYPE_NUMBER; |
| } |
| DUK_UNREACHABLE(); |
| } |
| |
| #if defined(DUK_USE_VERBOSE_ERRORS) && defined(DUK_USE_PARANOID_ERRORS) |
| DUK_LOCAL const char *duk__type_names[] = { |
| "none", |
| "undefined", |
| "null", |
| "boolean", |
| "number", |
| "string", |
| "object", |
| "buffer", |
| "pointer", |
| "lightfunc" |
| }; |
| |
| DUK_INTERNAL const char *duk_get_type_name(duk_context *ctx, duk_idx_t index) { |
| duk_int_t type_tag; |
| |
| type_tag = duk_get_type(ctx, index); |
| DUK_ASSERT(type_tag >= DUK_TYPE_MIN && type_tag <= DUK_TYPE_MAX); |
| DUK_ASSERT(DUK_TYPE_MIN == 0 && sizeof(duk__type_names) / sizeof(const char *) == DUK_TYPE_MAX + 1); |
| |
| return duk__type_names[type_tag]; |
| } |
| #endif |
| |
| DUK_EXTERNAL duk_bool_t duk_check_type(duk_context *ctx, duk_idx_t index, duk_int_t type) { |
| DUK_ASSERT_CTX_VALID(ctx); |
| |
| return (duk_get_type(ctx, index) == type) ? 1 : 0; |
| } |
| |
| DUK_EXTERNAL duk_uint_t duk_get_type_mask(duk_context *ctx, duk_idx_t index) { |
| duk_tval *tv; |
| |
| DUK_ASSERT_CTX_VALID(ctx); |
| |
| tv = duk_get_tval(ctx, index); |
| if (!tv) { |
| return DUK_TYPE_MASK_NONE; |
| } |
| switch (DUK_TVAL_GET_TAG(tv)) { |
| case DUK_TAG_UNDEFINED: |
| return DUK_TYPE_MASK_UNDEFINED; |
| case DUK_TAG_NULL: |
| return DUK_TYPE_MASK_NULL; |
| case DUK_TAG_BOOLEAN: |
| return DUK_TYPE_MASK_BOOLEAN; |
| case DUK_TAG_STRING: |
| return DUK_TYPE_MASK_STRING; |
| case DUK_TAG_OBJECT: |
| return DUK_TYPE_MASK_OBJECT; |
| case DUK_TAG_BUFFER: |
| return DUK_TYPE_MASK_BUFFER; |
| case DUK_TAG_POINTER: |
| return DUK_TYPE_MASK_POINTER; |
| case DUK_TAG_LIGHTFUNC: |
| return DUK_TYPE_MASK_LIGHTFUNC; |
| #if defined(DUK_USE_FASTINT) |
| case DUK_TAG_FASTINT: |
| #endif |
| default: |
| /* Note: number has no explicit tag (in 8-byte representation) */ |
| DUK_ASSERT(!DUK_TVAL_IS_UNUSED(tv)); |
| DUK_ASSERT(DUK_TVAL_IS_NUMBER(tv)); |
| return DUK_TYPE_MASK_NUMBER; |
| } |
| DUK_UNREACHABLE(); |
| } |
| |
| DUK_EXTERNAL duk_bool_t duk_check_type_mask(duk_context *ctx, duk_idx_t index, duk_uint_t mask) { |
| duk_hthread *thr = (duk_hthread *) ctx; |
| |
| DUK_ASSERT_CTX_VALID(ctx); |
| |
| if (duk_get_type_mask(ctx, index) & mask) { |
| return 1; |
| } |
| if (mask & DUK_TYPE_MASK_THROW) { |
| DUK_ERROR_TYPE(thr, DUK_STR_UNEXPECTED_TYPE); |
| DUK_UNREACHABLE(); |
| } |
| return 0; |
| } |
| |
| DUK_EXTERNAL duk_bool_t duk_is_undefined(duk_context *ctx, duk_idx_t index) { |
| DUK_ASSERT_CTX_VALID(ctx); |
| return duk__tag_check(ctx, index, DUK_TAG_UNDEFINED); |
| } |
| |
| DUK_EXTERNAL duk_bool_t duk_is_null(duk_context *ctx, duk_idx_t index) { |
| DUK_ASSERT_CTX_VALID(ctx); |
| return duk__tag_check(ctx, index, DUK_TAG_NULL); |
| } |
| |
| DUK_EXTERNAL duk_bool_t duk_is_null_or_undefined(duk_context *ctx, duk_idx_t index) { |
| duk_tval *tv; |
| duk_small_uint_t tag; |
| |
| DUK_ASSERT_CTX_VALID(ctx); |
| |
| tv = duk_get_tval(ctx, index); |
| if (!tv) { |
| return 0; |
| } |
| tag = DUK_TVAL_GET_TAG(tv); |
| return (tag == DUK_TAG_UNDEFINED) || (tag == DUK_TAG_NULL); |
| } |
| |
| DUK_EXTERNAL duk_bool_t duk_is_boolean(duk_context *ctx, duk_idx_t index) { |
| DUK_ASSERT_CTX_VALID(ctx); |
| return duk__tag_check(ctx, index, DUK_TAG_BOOLEAN); |
| } |
| |
| DUK_EXTERNAL duk_bool_t duk_is_number(duk_context *ctx, duk_idx_t index) { |
| duk_tval *tv; |
| |
| DUK_ASSERT_CTX_VALID(ctx); |
| |
| /* |
| * Number is special because it doesn't have a specific |
| * tag in the 8-byte representation. |
| */ |
| |
| /* XXX: shorter version for 12-byte representation? */ |
| |
| tv = duk_get_tval(ctx, index); |
| if (!tv) { |
| return 0; |
| } |
| return DUK_TVAL_IS_NUMBER(tv); |
| } |
| |
| DUK_EXTERNAL duk_bool_t duk_is_nan(duk_context *ctx, duk_idx_t index) { |
| /* XXX: This will now return false for non-numbers, even though they would |
| * coerce to NaN (as a general rule). In particular, duk_get_number() |
| * returns a NaN for non-numbers, so should this function also return |
| * true for non-numbers? |
| */ |
| |
| duk_tval *tv; |
| |
| DUK_ASSERT_CTX_VALID(ctx); |
| |
| tv = duk_get_tval(ctx, index); |
| if (!tv || !DUK_TVAL_IS_NUMBER(tv)) { |
| return 0; |
| } |
| return DUK_ISNAN(DUK_TVAL_GET_NUMBER(tv)); |
| } |
| |
| DUK_EXTERNAL duk_bool_t duk_is_string(duk_context *ctx, duk_idx_t index) { |
| DUK_ASSERT_CTX_VALID(ctx); |
| return duk__tag_check(ctx, index, DUK_TAG_STRING); |
| } |
| |
| DUK_EXTERNAL duk_bool_t duk_is_object(duk_context *ctx, duk_idx_t index) { |
| DUK_ASSERT_CTX_VALID(ctx); |
| return duk__tag_check(ctx, index, DUK_TAG_OBJECT); |
| } |
| |
| DUK_EXTERNAL duk_bool_t duk_is_buffer(duk_context *ctx, duk_idx_t index) { |
| DUK_ASSERT_CTX_VALID(ctx); |
| return duk__tag_check(ctx, index, DUK_TAG_BUFFER); |
| } |
| |
| DUK_EXTERNAL duk_bool_t duk_is_pointer(duk_context *ctx, duk_idx_t index) { |
| DUK_ASSERT_CTX_VALID(ctx); |
| return duk__tag_check(ctx, index, DUK_TAG_POINTER); |
| } |
| |
| DUK_EXTERNAL duk_bool_t duk_is_lightfunc(duk_context *ctx, duk_idx_t index) { |
| DUK_ASSERT_CTX_VALID(ctx); |
| return duk__tag_check(ctx, index, DUK_TAG_LIGHTFUNC); |
| } |
| |
| DUK_EXTERNAL duk_bool_t duk_is_array(duk_context *ctx, duk_idx_t index) { |
| duk_hobject *obj; |
| |
| DUK_ASSERT_CTX_VALID(ctx); |
| |
| obj = duk_get_hobject(ctx, index); |
| if (obj) { |
| return (DUK_HOBJECT_GET_CLASS_NUMBER(obj) == DUK_HOBJECT_CLASS_ARRAY ? 1 : 0); |
| } |
| return 0; |
| } |
| |
| DUK_EXTERNAL duk_bool_t duk_is_function(duk_context *ctx, duk_idx_t index) { |
| duk_tval *tv; |
| |
| DUK_ASSERT_CTX_VALID(ctx); |
| |
| tv = duk_get_tval(ctx, index); |
| if (tv && DUK_TVAL_IS_LIGHTFUNC(tv)) { |
| return 1; |
| } |
| return duk__obj_flag_any_default_false(ctx, |
| index, |
| DUK_HOBJECT_FLAG_COMPILEDFUNCTION | |
| DUK_HOBJECT_FLAG_NATIVEFUNCTION | |
| DUK_HOBJECT_FLAG_BOUND); |
| } |
| |
| DUK_EXTERNAL duk_bool_t duk_is_c_function(duk_context *ctx, duk_idx_t index) { |
| DUK_ASSERT_CTX_VALID(ctx); |
| return duk__obj_flag_any_default_false(ctx, |
| index, |
| DUK_HOBJECT_FLAG_NATIVEFUNCTION); |
| } |
| |
| DUK_EXTERNAL duk_bool_t duk_is_ecmascript_function(duk_context *ctx, duk_idx_t index) { |
| DUK_ASSERT_CTX_VALID(ctx); |
| return duk__obj_flag_any_default_false(ctx, |
| index, |
| DUK_HOBJECT_FLAG_COMPILEDFUNCTION); |
| } |
| |
| DUK_EXTERNAL duk_bool_t duk_is_bound_function(duk_context *ctx, duk_idx_t index) { |
| DUK_ASSERT_CTX_VALID(ctx); |
| return duk__obj_flag_any_default_false(ctx, |
| index, |
| DUK_HOBJECT_FLAG_BOUND); |
| } |
| |
| DUK_EXTERNAL duk_bool_t duk_is_thread(duk_context *ctx, duk_idx_t index) { |
| DUK_ASSERT_CTX_VALID(ctx); |
| return duk__obj_flag_any_default_false(ctx, |
| index, |
| DUK_HOBJECT_FLAG_THREAD); |
| } |
| |
| DUK_EXTERNAL duk_bool_t duk_is_fixed_buffer(duk_context *ctx, duk_idx_t index) { |
| duk_tval *tv; |
| |
| DUK_ASSERT_CTX_VALID(ctx); |
| |
| tv = duk_get_tval(ctx, index); |
| if (tv && DUK_TVAL_IS_BUFFER(tv)) { |
| duk_hbuffer *h = DUK_TVAL_GET_BUFFER(tv); |
| DUK_ASSERT(h != NULL); |
| return (DUK_HBUFFER_HAS_DYNAMIC(h) ? 0 : 1); |
| } |
| return 0; |
| } |
| |
| DUK_EXTERNAL duk_bool_t duk_is_dynamic_buffer(duk_context *ctx, duk_idx_t index) { |
| duk_tval *tv; |
| |
| DUK_ASSERT_CTX_VALID(ctx); |
| |
| tv = duk_get_tval(ctx, index); |
| if (tv && DUK_TVAL_IS_BUFFER(tv)) { |
| duk_hbuffer *h = DUK_TVAL_GET_BUFFER(tv); |
| DUK_ASSERT(h != NULL); |
| return (DUK_HBUFFER_HAS_DYNAMIC(h) && !DUK_HBUFFER_HAS_EXTERNAL(h) ? 1 : 0); |
| } |
| return 0; |
| } |
| |
| DUK_EXTERNAL duk_bool_t duk_is_external_buffer(duk_context *ctx, duk_idx_t index) { |
| duk_tval *tv; |
| |
| DUK_ASSERT_CTX_VALID(ctx); |
| |
| tv = duk_get_tval(ctx, index); |
| if (tv && DUK_TVAL_IS_BUFFER(tv)) { |
| duk_hbuffer *h = DUK_TVAL_GET_BUFFER(tv); |
| DUK_ASSERT(h != NULL); |
| return (DUK_HBUFFER_HAS_DYNAMIC(h) && DUK_HBUFFER_HAS_EXTERNAL(h) ? 1 : 0); |
| } |
| return 0; |
| } |
| |
| DUK_EXTERNAL duk_errcode_t duk_get_error_code(duk_context *ctx, duk_idx_t index) { |
| duk_hthread *thr = (duk_hthread *) ctx; |
| duk_hobject *h; |
| duk_uint_t sanity; |
| |
| DUK_ASSERT_CTX_VALID(ctx); |
| |
| h = duk_get_hobject(ctx, index); |
| |
| sanity = DUK_HOBJECT_PROTOTYPE_CHAIN_SANITY; |
| do { |
| if (!h) { |
| return DUK_ERR_NONE; |
| } |
| if (h == thr->builtins[DUK_BIDX_EVAL_ERROR_PROTOTYPE]) { |
| return DUK_ERR_EVAL_ERROR; |
| } |
| if (h == thr->builtins[DUK_BIDX_RANGE_ERROR_PROTOTYPE]) { |
| return DUK_ERR_RANGE_ERROR; |
| } |
| if (h == thr->builtins[DUK_BIDX_REFERENCE_ERROR_PROTOTYPE]) { |
| return DUK_ERR_REFERENCE_ERROR; |
| } |
| if (h == thr->builtins[DUK_BIDX_SYNTAX_ERROR_PROTOTYPE]) { |
| return DUK_ERR_SYNTAX_ERROR; |
| } |
| if (h == thr->builtins[DUK_BIDX_TYPE_ERROR_PROTOTYPE]) { |
| return DUK_ERR_TYPE_ERROR; |
| } |
| if (h == thr->builtins[DUK_BIDX_URI_ERROR_PROTOTYPE]) { |
| return DUK_ERR_URI_ERROR; |
| } |
| if (h == thr->builtins[DUK_BIDX_ERROR_PROTOTYPE]) { |
| return DUK_ERR_ERROR; |
| } |
| |
| h = DUK_HOBJECT_GET_PROTOTYPE(thr->heap, h); |
| } while (--sanity > 0); |
| |
| return DUK_ERR_NONE; |
| } |
| |
| /* |
| * Pushers |
| */ |
| |
| DUK_INTERNAL void duk_push_tval(duk_context *ctx, duk_tval *tv) { |
| duk_hthread *thr; |
| duk_tval *tv_slot; |
| |
| DUK_ASSERT_CTX_VALID(ctx); |
| DUK_ASSERT(tv != NULL); |
| thr = (duk_hthread *) ctx; |
| DUK__CHECK_SPACE(); |
| tv_slot = thr->valstack_top++; |
| DUK_TVAL_SET_TVAL(tv_slot, tv); |
| DUK_TVAL_INCREF(thr, tv); /* no side effects */ |
| } |
| |
| DUK_EXTERNAL void duk_push_undefined(duk_context *ctx) { |
| duk_hthread *thr; |
| |
| DUK_ASSERT_CTX_VALID(ctx); |
| thr = (duk_hthread *) ctx; |
| DUK__CHECK_SPACE(); |
| |
| /* Because value stack init policy is 'undefined above top', |
| * we don't need to write, just assert. |
| */ |
| thr->valstack_top++; |
| DUK_ASSERT(DUK_TVAL_IS_UNDEFINED(thr->valstack_top - 1)); |
| } |
| |
| DUK_EXTERNAL void duk_push_null(duk_context *ctx) { |
| duk_hthread *thr; |
| duk_tval *tv_slot; |
| |
| DUK_ASSERT_CTX_VALID(ctx); |
| thr = (duk_hthread *) ctx; |
| DUK__CHECK_SPACE(); |
| tv_slot = thr->valstack_top++; |
| DUK_TVAL_SET_NULL(tv_slot); |
| } |
| |
| DUK_EXTERNAL void duk_push_boolean(duk_context *ctx, duk_bool_t val) { |
| duk_hthread *thr; |
| duk_tval *tv_slot; |
| duk_small_int_t b; |
| |
| DUK_ASSERT_CTX_VALID(ctx); |
| thr = (duk_hthread *) ctx; |
| DUK__CHECK_SPACE(); |
| b = (val ? 1 : 0); /* ensure value is 1 or 0 (not other non-zero) */ |
| tv_slot = thr->valstack_top++; |
| DUK_TVAL_SET_BOOLEAN(tv_slot, b); |
| } |
| |
| DUK_EXTERNAL void duk_push_true(duk_context *ctx) { |
| duk_hthread *thr; |
| duk_tval *tv_slot; |
| |
| DUK_ASSERT_CTX_VALID(ctx); |
| thr = (duk_hthread *) ctx; |
| DUK__CHECK_SPACE(); |
| tv_slot = thr->valstack_top++; |
| DUK_TVAL_SET_BOOLEAN_TRUE(tv_slot); |
| } |
| |
| DUK_EXTERNAL void duk_push_false(duk_context *ctx) { |
| duk_hthread *thr; |
| duk_tval *tv_slot; |
| |
| DUK_ASSERT_CTX_VALID(ctx); |
| thr = (duk_hthread *) ctx; |
| DUK__CHECK_SPACE(); |
| tv_slot = thr->valstack_top++; |
| DUK_TVAL_SET_BOOLEAN_FALSE(tv_slot); |
| } |
| |
| /* normalize NaN which may not match our canonical internal NaN */ |
| DUK_EXTERNAL void duk_push_number(duk_context *ctx, duk_double_t val) { |
| duk_hthread *thr; |
| duk_tval *tv_slot; |
| duk_double_union du; |
| |
| DUK_ASSERT_CTX_VALID(ctx); |
| thr = (duk_hthread *) ctx; |
| DUK__CHECK_SPACE(); |
| du.d = val; |
| DUK_DBLUNION_NORMALIZE_NAN_CHECK(&du); |
| tv_slot = thr->valstack_top++; |
| DUK_TVAL_SET_NUMBER(tv_slot, du.d); |
| } |
| |
| DUK_EXTERNAL void duk_push_int(duk_context *ctx, duk_int_t val) { |
| #if defined(DUK_USE_FASTINT) |
| duk_hthread *thr; |
| duk_tval *tv_slot; |
| |
| DUK_ASSERT_CTX_VALID(ctx); |
| thr = (duk_hthread *) ctx; |
| DUK__CHECK_SPACE(); |
| tv_slot = thr->valstack_top++; |
| #if DUK_INT_MAX <= 0x7fffffffL |
| DUK_TVAL_SET_FASTINT_I32(tv_slot, (duk_int32_t) val); |
| #else |
| if (val >= DUK_FASTINT_MIN && val <= DUK_FASTINT_MAX) { |
| DUK_TVAL_SET_FASTINT(tv_slot, (duk_int64_t) val); |
| } else { |
| duk_double_t = (duk_double_t) val; |
| DUK_TVAL_SET_NUMBER(tv_slot, d); |
| } |
| #endif |
| #else /* DUK_USE_FASTINT */ |
| duk_hthread *thr; |
| duk_tval *tv_slot; |
| duk_double_t d; |
| |
| DUK_ASSERT_CTX_VALID(ctx); |
| thr = (duk_hthread *) ctx; |
| DUK__CHECK_SPACE(); |
| d = (duk_double_t) val; |
| tv_slot = thr->valstack_top++; |
| DUK_TVAL_SET_NUMBER(tv_slot, d); |
| #endif /* DUK_USE_FASTINT */ |
| } |
| |
| DUK_EXTERNAL void duk_push_uint(duk_context *ctx, duk_uint_t val) { |
| #if defined(DUK_USE_FASTINT) |
| duk_hthread *thr; |
| duk_tval *tv_slot; |
| |
| DUK_ASSERT_CTX_VALID(ctx); |
| thr = (duk_hthread *) ctx; |
| DUK__CHECK_SPACE(); |
| tv_slot = thr->valstack_top++; |
| #if DUK_UINT_MAX <= 0xffffffffUL |
| DUK_TVAL_SET_FASTINT_U32(tv_slot, (duk_uint32_t) val); |
| #else |
| if (val <= DUK_FASTINT_MAX) { /* val is unsigned so >= 0 */ |
| /* XXX: take advantage of val being unsigned, no need to mask */ |
| DUK_TVAL_SET_FASTINT(tv_slot, (duk_int64_t) val); |
| } else { |
| duk_double_t = (duk_double_t) val; |
| DUK_TVAL_SET_NUMBER(tv_slot, d); |
| } |
| #endif |
| #else /* DUK_USE_FASTINT */ |
| duk_hthread *thr; |
| duk_tval *tv_slot; |
| duk_double_t d; |
| |
| DUK_ASSERT_CTX_VALID(ctx); |
| thr = (duk_hthread *) ctx; |
| DUK__CHECK_SPACE(); |
| d = (duk_double_t) val; |
| tv_slot = thr->valstack_top++; |
| DUK_TVAL_SET_NUMBER(tv_slot, d); |
| #endif /* DUK_USE_FASTINT */ |
| } |
| |
| DUK_EXTERNAL void duk_push_nan(duk_context *ctx) { |
| duk_hthread *thr; |
| duk_tval *tv_slot; |
| duk_double_union du; |
| |
| DUK_ASSERT_CTX_VALID(ctx); |
| thr = (duk_hthread *) ctx; |
| DUK__CHECK_SPACE(); |
| DUK_DBLUNION_SET_NAN(&du); |
| DUK_ASSERT(DUK_DBLUNION_IS_NORMALIZED(&du)); |
| tv_slot = thr->valstack_top++; |
| DUK_TVAL_SET_NUMBER(tv_slot, du.d); |
| } |
| |
| DUK_EXTERNAL const char *duk_push_lstring(duk_context *ctx, const char *str, duk_size_t len) { |
| duk_hthread *thr = (duk_hthread *) ctx; |
| duk_hstring *h; |
| duk_tval *tv_slot; |
| |
| DUK_ASSERT_CTX_VALID(ctx); |
| |
| /* check stack before interning (avoid hanging temp) */ |
| if (thr->valstack_top >= thr->valstack_end) { |
| DUK_ERROR_API(thr, DUK_STR_PUSH_BEYOND_ALLOC_STACK); |
| } |
| |
| /* NULL with zero length represents an empty string; NULL with higher |
| * length is also now trated like an empty string although it is |
| * a bit dubious. This is unlike duk_push_string() which pushes a |
| * 'null' if the input string is a NULL. |
| */ |
| if (!str) { |
| len = 0; |
| } |
| |
| /* Check for maximum string length */ |
| if (len > DUK_HSTRING_MAX_BYTELEN) { |
| DUK_ERROR_RANGE(thr, DUK_STR_STRING_TOO_LONG); |
| } |
| |
| h = duk_heap_string_intern_checked(thr, (const duk_uint8_t *) str, (duk_uint32_t) len); |
| DUK_ASSERT(h != NULL); |
| |
| tv_slot = thr->valstack_top++; |
| DUK_TVAL_SET_STRING(tv_slot, h); |
| DUK_HSTRING_INCREF(thr, h); /* no side effects */ |
| |
| return (const char *) DUK_HSTRING_GET_DATA(h); |
| } |
| |
| DUK_EXTERNAL const char *duk_push_string(duk_context *ctx, const char *str) { |
| DUK_ASSERT_CTX_VALID(ctx); |
| |
| if (str) { |
| return duk_push_lstring(ctx, str, DUK_STRLEN(str)); |
| } else { |
| duk_push_null(ctx); |
| return NULL; |
| } |
| } |
| |
| #ifdef DUK_USE_FILE_IO |
| /* This is a bit clunky because it is ANSI C portable. Should perhaps |
| * relocate to another file because this is potentially platform |
| * dependent. |
| */ |
| DUK_EXTERNAL const char *duk_push_string_file_raw(duk_context *ctx, const char *path, duk_uint_t flags) { |
| duk_hthread *thr = (duk_hthread *) ctx; |
| duk_file *f = NULL; |
| char *buf; |
| long sz; /* ANSI C typing */ |
| |
| DUK_ASSERT_CTX_VALID(ctx); |
| |
| if (!path) { |
| goto fail; |
| } |
| f = DUK_FOPEN(path, "rb"); |
| if (!f) { |
| goto fail; |
| } |
| if (DUK_FSEEK(f, 0, SEEK_END) < 0) { |
| goto fail; |
| } |
| sz = DUK_FTELL(f); |
| if (sz < 0) { |
| goto fail; |
| } |
| if (DUK_FSEEK(f, 0, SEEK_SET) < 0) { |
| goto fail; |
| } |
| buf = (char *) duk_push_fixed_buffer(ctx, (duk_size_t) sz); |
| DUK_ASSERT(buf != NULL); |
| if ((duk_size_t) DUK_FREAD(buf, 1, (size_t) sz, f) != (duk_size_t) sz) { |
| goto fail; |
| } |
| (void) DUK_FCLOSE(f); /* ignore fclose() error */ |
| f = NULL; |
| return duk_to_string(ctx, -1); |
| |
| fail: |
| if (f) { |
| DUK_FCLOSE(f); |
| } |
| |
| if (flags != 0) { |
| DUK_ASSERT(flags == DUK_STRING_PUSH_SAFE); /* only flag now */ |
| duk_push_undefined(ctx); |
| } else { |
| /* XXX: string not shared because it is conditional */ |
| DUK_ERROR_TYPE(thr, "read file error"); |
| } |
| return NULL; |
| } |
| #else |
| DUK_EXTERNAL const char *duk_push_string_file_raw(duk_context *ctx, const char *path, duk_uint_t flags) { |
| duk_hthread *thr = (duk_hthread *) ctx; |
| DUK_ASSERT_CTX_VALID(ctx); |
| DUK_UNREF(path); |
| |
| if (flags != 0) { |
| DUK_ASSERT(flags == DUK_STRING_PUSH_SAFE); /* only flag now */ |
| duk_push_undefined(ctx); |
| } else { |
| /* XXX: string not shared because it is conditional */ |
| DUK_ERROR_UNSUPPORTED(thr, "file I/O disabled"); |
| } |
| return NULL; |
| } |
| #endif /* DUK_USE_FILE_IO */ |
| |
| DUK_EXTERNAL void duk_push_pointer(duk_context *ctx, void *val) { |
| duk_hthread *thr; |
| duk_tval *tv_slot; |
| |
| DUK_ASSERT_CTX_VALID(ctx); |
| thr = (duk_hthread *) ctx; |
| DUK__CHECK_SPACE(); |
| tv_slot = thr->valstack_top++; |
| DUK_TVAL_SET_POINTER(tv_slot, val); |
| } |
| |
| DUK_LOCAL void duk__push_this_helper(duk_context *ctx, duk_small_uint_t check_object_coercible) { |
| duk_hthread *thr; |
| duk_tval *tv_slot; |
| |
| DUK_ASSERT_CTX_VALID(ctx); |
| DUK_ASSERT_DISABLE(thr->callstack_top >= 0); /* avoid warning (unsigned) */ |
| thr = (duk_hthread *) ctx; |
| DUK_ASSERT(thr->callstack_top <= thr->callstack_size); |
| DUK__CHECK_SPACE(); |
| |
| DUK_ASSERT(DUK_TVAL_IS_UNDEFINED(thr->valstack_top)); /* because of valstack init policy */ |
| tv_slot = thr->valstack_top++; |
| |
| if (DUK_UNLIKELY(thr->callstack_top == 0)) { |
| if (check_object_coercible) { |
| goto type_error; |
| } |
| /* 'undefined' already on stack top */ |
| } else { |
| duk_tval *tv; |
| |
| /* 'this' binding is just before current activation's bottom */ |
| DUK_ASSERT(thr->valstack_bottom > thr->valstack); |
| tv = thr->valstack_bottom - 1; |
| if (check_object_coercible && |
| (DUK_TVAL_IS_UNDEFINED(tv) || DUK_TVAL_IS_NULL(tv))) { |
| /* XXX: better macro for DUK_TVAL_IS_UNDEFINED_OR_NULL(tv) */ |
| goto type_error; |
| } |
| |
| DUK_TVAL_SET_TVAL(tv_slot, tv); |
| DUK_TVAL_INCREF(thr, tv); |
| } |
| return; |
| |
| type_error: |
| DUK_ERROR_TYPE(thr, DUK_STR_NOT_OBJECT_COERCIBLE); |
| } |
| |
| DUK_EXTERNAL void duk_push_this(duk_context *ctx) { |
| DUK_ASSERT_CTX_VALID(ctx); |
| |
| duk__push_this_helper(ctx, 0 /*check_object_coercible*/); |
| } |
| |
| DUK_INTERNAL void duk_push_this_check_object_coercible(duk_context *ctx) { |
| DUK_ASSERT_CTX_VALID(ctx); |
| |
| duk__push_this_helper(ctx, 1 /*check_object_coercible*/); |
| } |
| |
| DUK_INTERNAL duk_hobject *duk_push_this_coercible_to_object(duk_context *ctx) { |
| duk_hobject *h; |
| |
| DUK_ASSERT_CTX_VALID(ctx); |
| |
| duk__push_this_helper(ctx, 1 /*check_object_coercible*/); |
| duk_to_object(ctx, -1); |
| h = duk_get_hobject(ctx, -1); |
| DUK_ASSERT(h != NULL); |
| return h; |
| } |
| |
| DUK_INTERNAL duk_hstring *duk_push_this_coercible_to_string(duk_context *ctx) { |
| duk_hstring *h; |
| |
| DUK_ASSERT_CTX_VALID(ctx); |
| |
| duk__push_this_helper(ctx, 1 /*check_object_coercible*/); |
| duk_to_string(ctx, -1); |
| h = duk_get_hstring(ctx, -1); |
| DUK_ASSERT(h != NULL); |
| return h; |
| } |
| |
| DUK_INTERNAL duk_tval *duk_get_borrowed_this_tval(duk_context *ctx) { |
| duk_hthread *thr; |
| |
| DUK_ASSERT(ctx != NULL); |
| thr = (duk_hthread *) ctx; |
| |
| DUK_ASSERT(thr->callstack_top > 0); /* caller required to know */ |
| DUK_ASSERT(thr->valstack_bottom > thr->valstack); /* consequence of above */ |
| DUK_ASSERT(thr->valstack_bottom - 1 >= thr->valstack); /* 'this' binding exists */ |
| |
| return thr->valstack_bottom - 1; |
| } |
| |
| DUK_EXTERNAL void duk_push_current_function(duk_context *ctx) { |
| duk_hthread *thr = (duk_hthread *) ctx; |
| duk_activation *act; |
| |
| DUK_ASSERT_CTX_VALID(ctx); |
| DUK_ASSERT(thr != NULL); |
| DUK_ASSERT_DISABLE(thr->callstack_top >= 0); |
| DUK_ASSERT(thr->callstack_top <= thr->callstack_size); |
| |
| act = duk_hthread_get_current_activation(thr); |
| if (act) { |
| duk_push_tval(ctx, &act->tv_func); |
| } else { |
| duk_push_undefined(ctx); |
| } |
| } |
| |
| DUK_EXTERNAL void duk_push_current_thread(duk_context *ctx) { |
| duk_hthread *thr = (duk_hthread *) ctx; |
| |
| DUK_ASSERT_CTX_VALID(ctx); |
| DUK_ASSERT(thr != NULL); |
| |
| if (thr->heap->curr_thread) { |
| duk_push_hobject(ctx, (duk_hobject *) thr->heap->curr_thread); |
| } else { |
| duk_push_undefined(ctx); |
| } |
| } |
| |
| DUK_EXTERNAL void duk_push_global_object(duk_context *ctx) { |
| DUK_ASSERT_CTX_VALID(ctx); |
| |
| duk_push_hobject_bidx(ctx, DUK_BIDX_GLOBAL); |
| } |
| |
| /* XXX: size optimize */ |
| DUK_LOCAL void duk__push_stash(duk_context *ctx) { |
| DUK_ASSERT_CTX_VALID(ctx); |
| if (!duk_get_prop_stridx(ctx, -1, DUK_STRIDX_INT_VALUE)) { |
| DUK_DDD(DUK_DDDPRINT("creating heap/global/thread stash on first use")); |
| duk_pop(ctx); |
| duk_push_object_internal(ctx); |
| duk_dup_top(ctx); |
| duk_xdef_prop_stridx(ctx, -3, DUK_STRIDX_INT_VALUE, DUK_PROPDESC_FLAGS_C); /* [ ... parent stash stash ] -> [ ... parent stash ] */ |
| } |
| duk_remove(ctx, -2); |
| } |
| |
| DUK_EXTERNAL void duk_push_heap_stash(duk_context *ctx) { |
| duk_hthread *thr = (duk_hthread *) ctx; |
| duk_heap *heap; |
| DUK_ASSERT_CTX_VALID(ctx); |
| heap = thr->heap; |
| DUK_ASSERT(heap->heap_object != NULL); |
| duk_push_hobject(ctx, heap->heap_object); |
| duk__push_stash(ctx); |
| } |
| |
| DUK_EXTERNAL void duk_push_global_stash(duk_context *ctx) { |
| DUK_ASSERT_CTX_VALID(ctx); |
| duk_push_global_object(ctx); |
| duk__push_stash(ctx); |
| } |
| |
| DUK_EXTERNAL void duk_push_thread_stash(duk_context *ctx, duk_context *target_ctx) { |
| duk_hthread *thr = (duk_hthread *) ctx; |
| DUK_ASSERT_CTX_VALID(ctx); |
| if (!target_ctx) { |
| DUK_ERROR_API(thr, DUK_STR_INVALID_CALL_ARGS); |
| return; /* not reached */ |
| } |
| duk_push_hobject(ctx, (duk_hobject *) target_ctx); |
| duk__push_stash(ctx); |
| } |
| |
| /* XXX: duk_ssize_t would be useful here */ |
| DUK_LOCAL duk_int_t duk__try_push_vsprintf(duk_context *ctx, void *buf, duk_size_t sz, const char *fmt, va_list ap) { |
| duk_int_t len; |
| |
| DUK_ASSERT_CTX_VALID(ctx); |
| DUK_UNREF(ctx); |
| |
| /* NUL terminator handling doesn't matter here */ |
| len = DUK_VSNPRINTF((char *) buf, sz, fmt, ap); |
| if (len < (duk_int_t) sz) { |
| /* Return value of 'sz' or more indicates output was (potentially) |
| * truncated. |
| */ |
| return (duk_int_t) len; |
| } |
| return -1; |
| } |
| |
| DUK_EXTERNAL const char *duk_push_vsprintf(duk_context *ctx, const char *fmt, va_list ap) { |
| duk_hthread *thr = (duk_hthread *) ctx; |
| duk_uint8_t stack_buf[DUK_PUSH_SPRINTF_INITIAL_SIZE]; |
| duk_size_t sz = DUK_PUSH_SPRINTF_INITIAL_SIZE; |
| duk_bool_t pushed_buf = 0; |
| void *buf; |
| duk_int_t len; /* XXX: duk_ssize_t */ |
| const char *res; |
| |
| DUK_ASSERT_CTX_VALID(ctx); |
| |
| /* special handling of fmt==NULL */ |
| if (!fmt) { |
| duk_hstring *h_str; |
| duk_push_hstring_stridx(ctx, DUK_STRIDX_EMPTY_STRING); |
| h_str = DUK_HTHREAD_STRING_EMPTY_STRING(thr); /* rely on interning, must be this string */ |
| return (const char *) DUK_HSTRING_GET_DATA(h_str); |
| } |
| |
| /* initial estimate based on format string */ |
| sz = DUK_STRLEN(fmt) + 16; /* format plus something to avoid just missing */ |
| if (sz < DUK_PUSH_SPRINTF_INITIAL_SIZE) { |
| sz = DUK_PUSH_SPRINTF_INITIAL_SIZE; |
| } |
| DUK_ASSERT(sz > 0); |
| |
| /* Try to make do with a stack buffer to avoid allocating a temporary buffer. |
| * This works 99% of the time which is quite nice. |
| */ |
| for (;;) { |
| va_list ap_copy; /* copied so that 'ap' can be reused */ |
| |
| if (sz <= sizeof(stack_buf)) { |
| buf = stack_buf; |
| } else if (!pushed_buf) { |
| pushed_buf = 1; |
| buf = duk_push_dynamic_buffer(ctx, sz); |
| } else { |
| buf = duk_resize_buffer(ctx, -1, sz); |
| } |
| DUK_ASSERT(buf != NULL); |
| |
| DUK_VA_COPY(ap_copy, ap); |
| len = duk__try_push_vsprintf(ctx, buf, sz, fmt, ap_copy); |
| va_end(ap_copy); |
| if (len >= 0) { |
| break; |
| } |
| |
| /* failed, resize and try again */ |
| sz = sz * 2; |
| if (sz >= DUK_PUSH_SPRINTF_SANITY_LIMIT) { |
| DUK_ERROR_API(thr, DUK_STR_SPRINTF_TOO_LONG); |
| } |
| } |
| |
| /* Cannot use duk_to_string() on the buffer because it is usually |
| * larger than 'len'. Also, 'buf' is usually a stack buffer. |
| */ |
| res = duk_push_lstring(ctx, (const char *) buf, (duk_size_t) len); /* [ buf? res ] */ |
| if (pushed_buf) { |
| duk_remove(ctx, -2); |
| } |
| return res; |
| } |
| |
| DUK_EXTERNAL const char *duk_push_sprintf(duk_context *ctx, const char *fmt, ...) { |
| va_list ap; |
| const char *ret; |
| |
| DUK_ASSERT_CTX_VALID(ctx); |
| |
| /* allow fmt==NULL */ |
| va_start(ap, fmt); |
| ret = duk_push_vsprintf(ctx, fmt, ap); |
| va_end(ap); |
| |
| return ret; |
| } |
| |
| DUK_INTERNAL duk_idx_t duk_push_object_helper(duk_context *ctx, duk_uint_t hobject_flags_and_class, duk_small_int_t prototype_bidx) { |
| duk_hthread *thr = (duk_hthread *) ctx; |
| duk_tval *tv_slot; |
| duk_hobject *h; |
| duk_idx_t ret; |
| |
| DUK_ASSERT_CTX_VALID(ctx); |
| DUK_ASSERT(prototype_bidx == -1 || |
| (prototype_bidx >= 0 && prototype_bidx < DUK_NUM_BUILTINS)); |
| |
| /* check stack first */ |
| if (thr->valstack_top >= thr->valstack_end) { |
| DUK_ERROR_API(thr, DUK_STR_PUSH_BEYOND_ALLOC_STACK); |
| } |
| |
| h = duk_hobject_alloc(thr->heap, hobject_flags_and_class); |
| if (!h) { |
| DUK_ERROR_ALLOC_DEFMSG(thr); |
| } |
| |
| DUK_DDD(DUK_DDDPRINT("created object with flags: 0x%08lx", (unsigned long) h->hdr.h_flags)); |
| |
| tv_slot = thr->valstack_top; |
| DUK_TVAL_SET_OBJECT(tv_slot, h); |
| DUK_HOBJECT_INCREF(thr, h); /* no side effects */ |
| ret = (duk_idx_t) (thr->valstack_top - thr->valstack_bottom); |
| thr->valstack_top++; |
| |
| /* object is now reachable */ |
| |
| if (prototype_bidx >= 0) { |
| DUK_HOBJECT_SET_PROTOTYPE_UPDREF(thr, h, thr->builtins[prototype_bidx]); |
| } else { |
| DUK_ASSERT(prototype_bidx == -1); |
| DUK_ASSERT(DUK_HOBJECT_GET_PROTOTYPE(thr->heap, h) == NULL); |
| } |
| |
| return ret; |
| } |
| |
| DUK_INTERNAL duk_idx_t duk_push_object_helper_proto(duk_context *ctx, duk_uint_t hobject_flags_and_class, duk_hobject *proto) { |
| duk_hthread *thr = (duk_hthread *) ctx; |
| duk_idx_t ret; |
| duk_hobject *h; |
| |
| DUK_ASSERT_CTX_VALID(ctx); |
| |
| ret = duk_push_object_helper(ctx, hobject_flags_and_class, -1); |
| h = duk_get_hobject(ctx, -1); |
| DUK_ASSERT(h != NULL); |
| DUK_ASSERT(DUK_HOBJECT_GET_PROTOTYPE(thr->heap, h) == NULL); |
| DUK_HOBJECT_SET_PROTOTYPE_UPDREF(thr, h, proto); |
| return ret; |
| } |
| |
| DUK_EXTERNAL duk_idx_t duk_push_object(duk_context *ctx) { |
| DUK_ASSERT_CTX_VALID(ctx); |
| |
| return duk_push_object_helper(ctx, |
| DUK_HOBJECT_FLAG_EXTENSIBLE | |
| DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_OBJECT), |
| DUK_BIDX_OBJECT_PROTOTYPE); |
| } |
| |
| DUK_EXTERNAL duk_idx_t duk_push_array(duk_context *ctx) { |
| duk_hthread *thr = (duk_hthread *) ctx; |
| duk_hobject *obj; |
| duk_idx_t ret; |
| |
| DUK_ASSERT_CTX_VALID(ctx); |
| |
| ret = duk_push_object_helper(ctx, |
| DUK_HOBJECT_FLAG_EXTENSIBLE | |
| DUK_HOBJECT_FLAG_ARRAY_PART | |
| DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_ARRAY), |
| DUK_BIDX_ARRAY_PROTOTYPE); |
| |
| obj = duk_require_hobject(ctx, ret); |
| |
| /* |
| * An array must have a 'length' property (E5 Section 15.4.5.2). |
| * The special array behavior flag must only be enabled once the |
| * length property has been added. |
| * |
| * The internal property must be a number (and preferably a |
| * fastint if fastint support is enabled). |
| */ |
| |
| duk_push_int(ctx, 0); |
| #if defined(DUK_USE_FASTINT) |
| DUK_ASSERT(DUK_TVAL_IS_FASTINT(duk_require_tval(ctx, -1))); |
| #endif |
| |
| duk_hobject_define_property_internal(thr, |
| obj, |
| DUK_HTHREAD_STRING_LENGTH(thr), |
| DUK_PROPDESC_FLAGS_W); |
| DUK_HOBJECT_SET_EXOTIC_ARRAY(obj); |
| |
| return ret; |
| } |
| |
| DUK_EXTERNAL duk_idx_t duk_push_thread_raw(duk_context *ctx, duk_uint_t flags) { |
| duk_hthread *thr = (duk_hthread *) ctx; |
| duk_hthread *obj; |
| duk_idx_t ret; |
| duk_tval *tv_slot; |
| |
| DUK_ASSERT_CTX_VALID(ctx); |
| |
| /* check stack first */ |
| if (thr->valstack_top >= thr->valstack_end) { |
| DUK_ERROR_API(thr, DUK_STR_PUSH_BEYOND_ALLOC_STACK); |
| } |
| |
| obj = duk_hthread_alloc(thr->heap, |
| DUK_HOBJECT_FLAG_EXTENSIBLE | |
| DUK_HOBJECT_FLAG_THREAD | |
| DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_THREAD)); |
| if (!obj) { |
| DUK_ERROR_ALLOC_DEFMSG(thr); |
| } |
| obj->state = DUK_HTHREAD_STATE_INACTIVE; |
| #if defined(DUK_USE_ROM_STRINGS) |
| /* Nothing to initialize, strs[] is in ROM. */ |
| #else |
| #if defined(DUK_USE_HEAPPTR16) |
| obj->strs16 = thr->strs16; |
| #else |
| obj->strs = thr->strs; |
| #endif |
| #endif |
| DUK_DDD(DUK_DDDPRINT("created thread object with flags: 0x%08lx", (unsigned long) obj->obj.hdr.h_flags)); |
| |
| /* make the new thread reachable */ |
| tv_slot = thr->valstack_top; |
| DUK_TVAL_SET_OBJECT(tv_slot, (duk_hobject *) obj); |
| DUK_HTHREAD_INCREF(thr, obj); |
| ret = (duk_idx_t) (thr->valstack_top - thr->valstack_bottom); |
| thr->valstack_top++; |
| |
| /* important to do this *after* pushing, to make the thread reachable for gc */ |
| if (!duk_hthread_init_stacks(thr->heap, obj)) { |
| DUK_ERROR_ALLOC_DEFMSG(thr); |
| } |
| |
| /* initialize built-ins - either by copying or creating new ones */ |
| if (flags & DUK_THREAD_NEW_GLOBAL_ENV) { |
| duk_hthread_create_builtin_objects(obj); |
| } else { |
| duk_hthread_copy_builtin_objects(thr, obj); |
| } |
| |
| /* default prototype (Note: 'obj' must be reachable) */ |
| DUK_HOBJECT_SET_PROTOTYPE_UPDREF(thr, (duk_hobject *) obj, obj->builtins[DUK_BIDX_THREAD_PROTOTYPE]); |
| |
| /* Initial stack size satisfies the stack spare constraints so there |
| * is no need to require stack here. |
| */ |
| DUK_ASSERT(DUK_VALSTACK_INITIAL_SIZE >= |
| DUK_VALSTACK_API_ENTRY_MINIMUM + DUK_VALSTACK_INTERNAL_EXTRA); |
| |
| return ret; |
| } |
| |
| DUK_INTERNAL duk_idx_t duk_push_compiledfunction(duk_context *ctx) { |
| duk_hthread *thr = (duk_hthread *) ctx; |
| duk_hcompiledfunction *obj; |
| duk_idx_t ret; |
| duk_tval *tv_slot; |
| |
| DUK_ASSERT_CTX_VALID(ctx); |
| |
| /* check stack first */ |
| if (thr->valstack_top >= thr->valstack_end) { |
| DUK_ERROR_API(thr, DUK_STR_PUSH_BEYOND_ALLOC_STACK); |
| } |
| |
| /* Template functions are not strictly constructable (they don't |
| * have a "prototype" property for instance), so leave the |
| * DUK_HOBJECT_FLAG_CONSRUCTABLE flag cleared here. |
| */ |
| |
| obj = duk_hcompiledfunction_alloc(thr->heap, |
| DUK_HOBJECT_FLAG_EXTENSIBLE | |
| DUK_HOBJECT_FLAG_COMPILEDFUNCTION | |
| DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_FUNCTION)); |
| if (!obj) { |
| DUK_ERROR_ALLOC_DEFMSG(thr); |
| } |
| |
| DUK_DDD(DUK_DDDPRINT("created compiled function object with flags: 0x%08lx", (unsigned long) obj->obj.hdr.h_flags)); |
| |
| tv_slot = thr->valstack_top; |
| DUK_TVAL_SET_OBJECT(tv_slot, (duk_hobject *) obj); |
| DUK_HOBJECT_INCREF(thr, obj); |
| ret = (duk_idx_t) (thr->valstack_top - thr->valstack_bottom); |
| thr->valstack_top++; |
| |
| /* default prototype (Note: 'obj' must be reachable) */ |
| DUK_HOBJECT_SET_PROTOTYPE_UPDREF(thr, (duk_hobject *) obj, thr->builtins[DUK_BIDX_FUNCTION_PROTOTYPE]); |
| |
| return ret; |
| } |
| |
| DUK_LOCAL duk_idx_t duk__push_c_function_raw(duk_context *ctx, duk_c_function func, duk_idx_t nargs, duk_uint_t flags) { |
| duk_hthread *thr = (duk_hthread *) ctx; |
| duk_hnativefunction *obj; |
| duk_idx_t ret; |
| duk_tval *tv_slot; |
| duk_int16_t func_nargs; |
| |
| DUK_ASSERT_CTX_VALID(ctx); |
| |
| /* check stack first */ |
| if (thr->valstack_top >= thr->valstack_end) { |
| DUK_ERROR_API(thr, DUK_STR_PUSH_BEYOND_ALLOC_STACK); |
| } |
| if (func == NULL) { |
| goto api_error; |
| } |
| if (nargs >= 0 && nargs < DUK_HNATIVEFUNCTION_NARGS_MAX) { |
| func_nargs = (duk_int16_t) nargs; |
| } else if (nargs == DUK_VARARGS) { |
| func_nargs = DUK_HNATIVEFUNCTION_NARGS_VARARGS; |
| } else { |
| goto api_error; |
| } |
| |
| obj = duk_hnativefunction_alloc(thr->heap, flags); |
| if (!obj) { |
| DUK_ERROR_ALLOC_DEFMSG(thr); |
| } |
| |
| obj->func = func; |
| obj->nargs = func_nargs; |
| |
| DUK_DDD(DUK_DDDPRINT("created native function object with flags: 0x%08lx, nargs=%ld", |
| (unsigned long) obj->obj.hdr.h_flags, (long) obj->nargs)); |
| |
| tv_slot = thr->valstack_top; |
| DUK_TVAL_SET_OBJECT(tv_slot, (duk_hobject *) obj); |
| DUK_HOBJECT_INCREF(thr, obj); |
| ret = (duk_idx_t) (thr->valstack_top - thr->valstack_bottom); |
| thr->valstack_top++; |
| |
| /* default prototype (Note: 'obj' must be reachable) */ |
| DUK_HOBJECT_SET_PROTOTYPE_UPDREF(thr, (duk_hobject *) obj, thr->builtins[DUK_BIDX_FUNCTION_PROTOTYPE]); |
| |
| return ret; |
| |
| api_error: |
| DUK_ERROR_API(thr, DUK_STR_INVALID_CALL_ARGS); |
| return 0; /* not reached */ |
| } |
| |
| DUK_EXTERNAL duk_idx_t duk_push_c_function(duk_context *ctx, duk_c_function func, duk_int_t nargs) { |
| duk_uint_t flags; |
| |
| DUK_ASSERT_CTX_VALID(ctx); |
| |
| flags = DUK_HOBJECT_FLAG_EXTENSIBLE | |
| DUK_HOBJECT_FLAG_CONSTRUCTABLE | |
| DUK_HOBJECT_FLAG_NATIVEFUNCTION | |
| DUK_HOBJECT_FLAG_NEWENV | |
| DUK_HOBJECT_FLAG_STRICT | |
| DUK_HOBJECT_FLAG_NOTAIL | |
| DUK_HOBJECT_FLAG_EXOTIC_DUKFUNC | |
| DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_FUNCTION); |
| |
| return duk__push_c_function_raw(ctx, func, nargs, flags); |
| } |
| |
| DUK_INTERNAL void duk_push_c_function_noexotic(duk_context *ctx, duk_c_function func, duk_int_t nargs) { |
| duk_uint_t flags; |
| |
| DUK_ASSERT_CTX_VALID(ctx); |
| |
| flags = DUK_HOBJECT_FLAG_EXTENSIBLE | |
| DUK_HOBJECT_FLAG_CONSTRUCTABLE | |
| DUK_HOBJECT_FLAG_NATIVEFUNCTION | |
| DUK_HOBJECT_FLAG_NEWENV | |
| DUK_HOBJECT_FLAG_STRICT | |
| DUK_HOBJECT_FLAG_NOTAIL | |
| DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_FUNCTION); |
| |
| (void) duk__push_c_function_raw(ctx, func, nargs, flags); |
| } |
| |
| DUK_INTERNAL void duk_push_c_function_noconstruct_noexotic(duk_context *ctx, duk_c_function func, duk_int_t nargs) { |
| duk_uint_t flags; |
| |
| DUK_ASSERT_CTX_VALID(ctx); |
| |
| flags = DUK_HOBJECT_FLAG_EXTENSIBLE | |
| DUK_HOBJECT_FLAG_NATIVEFUNCTION | |
| DUK_HOBJECT_FLAG_NEWENV | |
| DUK_HOBJECT_FLAG_STRICT | |
| DUK_HOBJECT_FLAG_NOTAIL | |
| DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_FUNCTION); |
| |
| (void) duk__push_c_function_raw(ctx, func, nargs, flags); |
| } |
| |
| DUK_EXTERNAL duk_idx_t duk_push_c_lightfunc(duk_context *ctx, duk_c_function func, duk_idx_t nargs, duk_idx_t length, duk_int_t magic) { |
| duk_hthread *thr = (duk_hthread *) ctx; |
| duk_tval tv_tmp; |
| duk_small_uint_t lf_flags; |
| |
| DUK_ASSERT_CTX_VALID(ctx); |
| |
| /* check stack first */ |
| if (thr->valstack_top >= thr->valstack_end) { |
| DUK_ERROR_API(thr, DUK_STR_PUSH_BEYOND_ALLOC_STACK); |
| } |
| |
| if (nargs >= DUK_LFUNC_NARGS_MIN && nargs <= DUK_LFUNC_NARGS_MAX) { |
| /* as is */ |
| } else if (nargs == DUK_VARARGS) { |
| nargs = DUK_LFUNC_NARGS_VARARGS; |
| } else { |
| goto api_error; |
| } |
| if (!(length >= DUK_LFUNC_LENGTH_MIN && length <= DUK_LFUNC_LENGTH_MAX)) { |
| goto api_error; |
| } |
| if (!(magic >= DUK_LFUNC_MAGIC_MIN && magic <= DUK_LFUNC_MAGIC_MAX)) { |
| goto api_error; |
| } |
| |
| lf_flags = DUK_LFUNC_FLAGS_PACK(magic, length, nargs); |
| DUK_TVAL_SET_LIGHTFUNC(&tv_tmp, func, lf_flags); |
| duk_push_tval(ctx, &tv_tmp); /* XXX: direct valstack write */ |
| DUK_ASSERT(thr->valstack_top != thr->valstack_bottom); |
| return ((duk_idx_t) (thr->valstack_top - thr->valstack_bottom)) - 1; |
| |
| api_error: |
| DUK_ERROR_API(thr, DUK_STR_INVALID_CALL_ARGS); |
| return 0; /* not reached */ |
| } |
| |
| DUK_INTERNAL duk_hbufferobject *duk_push_bufferobject_raw(duk_context *ctx, duk_uint_t hobject_flags_and_class, duk_small_int_t prototype_bidx) { |
| duk_hthread *thr = (duk_hthread *) ctx; |
| duk_hbufferobject *obj; |
| duk_tval *tv_slot; |
| |
| DUK_ASSERT(ctx != NULL); |
| DUK_ASSERT(prototype_bidx >= 0); |
| |
| /* check stack first */ |
| if (thr->valstack_top >= thr->valstack_end) { |
| DUK_ERROR_API(thr, DUK_STR_PUSH_BEYOND_ALLOC_STACK); |
| } |
| |
| obj = duk_hbufferobject_alloc(thr->heap, hobject_flags_and_class); |
| if (!obj) { |
| DUK_ERROR_ALLOC_DEFMSG(thr); |
| } |
| |
| DUK_HOBJECT_SET_PROTOTYPE_UPDREF(thr, (duk_hobject *) obj, thr->builtins[prototype_bidx]); |
| DUK_ASSERT_HBUFFEROBJECT_VALID(obj); |
| |
| tv_slot = thr->valstack_top; |
| DUK_TVAL_SET_OBJECT(tv_slot, (duk_hobject *) obj); |
| DUK_HOBJECT_INCREF(thr, obj); |
| thr->valstack_top++; |
| |
| return obj; |
| } |
| |
| /* XXX: There's quite a bit of overlap with buffer creation handling in |
| * duk_bi_buffer.c. Look for overlap and refactor. |
| */ |
| #define DUK__PACK_ARGS(classnum,protobidx,elemtype,elemshift,isview) \ |
| (((classnum) << 24) | ((protobidx) << 16) | ((elemtype) << 8) | ((elemshift) << 4) | (isview)) |
| |
| #if defined(DUK_USE_BUFFEROBJECT_SUPPORT) |
| static const duk_uint32_t duk__bufobj_flags_lookup[] = { |
| DUK__PACK_ARGS(DUK_HOBJECT_CLASS_BUFFER, DUK_BIDX_BUFFER_PROTOTYPE, DUK_HBUFFEROBJECT_ELEM_UINT8, 0, 0), /* DUK_BUFOBJ_DUKTAPE_BUFFER */ |
| DUK__PACK_ARGS(DUK_HOBJECT_CLASS_BUFFER, DUK_BIDX_NODEJS_BUFFER_PROTOTYPE, DUK_HBUFFEROBJECT_ELEM_UINT8, 0, 0), /* DUK_BUFOBJ_NODEJS_BUFFER */ |
| DUK__PACK_ARGS(DUK_HOBJECT_CLASS_ARRAYBUFFER, DUK_BIDX_ARRAYBUFFER_PROTOTYPE, DUK_HBUFFEROBJECT_ELEM_UINT8, 0, 0), /* DUK_BUFOBJ_ARRAYBUFFER */ |
| DUK__PACK_ARGS(DUK_HOBJECT_CLASS_DATAVIEW, DUK_BIDX_DATAVIEW_PROTOTYPE, DUK_HBUFFEROBJECT_ELEM_UINT8, 0, 1), /* DUK_BUFOBJ_DATAVIEW */ |
| DUK__PACK_ARGS(DUK_HOBJECT_CLASS_INT8ARRAY, DUK_BIDX_INT8ARRAY_PROTOTYPE, DUK_HBUFFEROBJECT_ELEM_INT8, 0, 1), /* DUK_BUFOBJ_INT8ARRAY */ |
| DUK__PACK_ARGS(DUK_HOBJECT_CLASS_UINT8ARRAY, DUK_BIDX_UINT8ARRAY_PROTOTYPE, DUK_HBUFFEROBJECT_ELEM_UINT8, 0, 1), /* DUK_BUFOBJ_UINT8ARRAY */ |
| DUK__PACK_ARGS(DUK_HOBJECT_CLASS_UINT8CLAMPEDARRAY, DUK_BIDX_UINT8CLAMPEDARRAY_PROTOTYPE, DUK_HBUFFEROBJECT_ELEM_UINT8CLAMPED, 0, 1), /* DUK_BUFOBJ_UINT8CLAMPEDARRAY */ |
| DUK__PACK_ARGS(DUK_HOBJECT_CLASS_INT16ARRAY, DUK_BIDX_INT16ARRAY_PROTOTYPE, DUK_HBUFFEROBJECT_ELEM_INT16, 1, 1), /* DUK_BUFOBJ_INT16ARRAY */ |
| DUK__PACK_ARGS(DUK_HOBJECT_CLASS_UINT16ARRAY, DUK_BIDX_UINT16ARRAY_PROTOTYPE, DUK_HBUFFEROBJECT_ELEM_UINT16, 1, 1), /* DUK_BUFOBJ_UINT16ARRAY */ |
| DUK__PACK_ARGS(DUK_HOBJECT_CLASS_INT32ARRAY, DUK_BIDX_INT32ARRAY_PROTOTYPE, DUK_HBUFFEROBJECT_ELEM_INT32, 2, 1), /* DUK_BUFOBJ_INT32ARRAY */ |
| DUK__PACK_ARGS(DUK_HOBJECT_CLASS_UINT32ARRAY, DUK_BIDX_UINT32ARRAY_PROTOTYPE, DUK_HBUFFEROBJECT_ELEM_UINT32, 2, 1), /* DUK_BUFOBJ_UINT32ARRAY */ |
| DUK__PACK_ARGS(DUK_HOBJECT_CLASS_FLOAT32ARRAY, DUK_BIDX_FLOAT32ARRAY_PROTOTYPE, DUK_HBUFFEROBJECT_ELEM_FLOAT32, 2, 1), /* DUK_BUFOBJ_FLOAT32ARRAY */ |
| DUK__PACK_ARGS(DUK_HOBJECT_CLASS_FLOAT64ARRAY, DUK_BIDX_FLOAT64ARRAY_PROTOTYPE, DUK_HBUFFEROBJECT_ELEM_FLOAT64, 3, 1) /* DUK_BUFOBJ_FLOAT64ARRAY */ |
| }; |
| #else /* DUK_USE_BUFFEROBJECT_SUPPORT */ |
| /* Only allow Duktape.Buffer when support disabled. */ |
| static const duk_uint32_t duk__bufobj_flags_lookup[] = { |
| DUK__PACK_ARGS(DUK_HOBJECT_CLASS_BUFFER, DUK_BIDX_BUFFER_PROTOTYPE, DUK_HBUFFEROBJECT_ELEM_UINT8, 0, 0) /* DUK_BUFOBJ_DUKTAPE_BUFFER */ |
| }; |
| #endif /* DUK_USE_BUFFEROBJECT_SUPPORT */ |
| #undef DUK__PACK_ARGS |
| |
| DUK_EXTERNAL void duk_push_buffer_object(duk_context *ctx, duk_idx_t idx_buffer, duk_size_t byte_offset, duk_size_t byte_length, duk_uint_t flags) { |
| duk_hthread *thr; |
| duk_hbufferobject *h_bufobj; |
| duk_hbuffer *h_val; |
| duk_uint32_t tmp; |
| duk_uint_t classnum; |
| duk_uint_t protobidx; |
| duk_uint_t lookupidx; |
| duk_uint_t uint_offset, uint_length, uint_added; |
| |
| DUK_ASSERT_CTX_VALID(ctx); |
| thr = (duk_hthread *) ctx; |
| DUK_UNREF(thr); |
| |
| /* The underlying types for offset/length in duk_hbufferobject is |
| * duk_uint_t; make sure argument values fit and that offset + length |
| * does not wrap. |
| */ |
| uint_offset = (duk_uint_t) byte_offset; |
| uint_length = (duk_uint_t) byte_length; |
| if (sizeof(duk_size_t) != sizeof(duk_uint_t)) { |
| if ((duk_size_t) uint_offset != byte_offset || (duk_size_t) uint_length != byte_length) { |
| goto range_error; |
| } |
| } |
| uint_added = uint_offset + uint_length; |
| if (uint_added < uint_offset) { |
| goto range_error; |
| } |
| DUK_ASSERT(uint_added >= uint_offset && uint_added >= uint_length); |
| |
| DUK_ASSERT_DISABLE(flags >= 0); /* flags is unsigned */ |
| lookupidx = flags & 0x0f; /* 4 low bits */ |
| if (lookupidx >= sizeof(duk__bufobj_flags_lookup) / sizeof(duk_uint32_t)) { |
| goto arg_error; |
| } |
| tmp = duk__bufobj_flags_lookup[lookupidx]; |
| classnum = tmp >> 24; |
| protobidx = (tmp >> 16) & 0xff; |
| |
| h_val = duk_require_hbuffer(ctx, idx_buffer); |
| DUK_ASSERT(h_val != NULL); |
| |
| h_bufobj = duk_push_bufferobject_raw(ctx, |
| DUK_HOBJECT_FLAG_EXTENSIBLE | |
| DUK_HOBJECT_FLAG_BUFFEROBJECT | |
| DUK_HOBJECT_CLASS_AS_FLAGS(classnum), |
| protobidx); |
| DUK_ASSERT(h_bufobj != NULL); |
| |
| h_bufobj->buf = h_val; |
| DUK_HBUFFER_INCREF(thr, h_val); |
| h_bufobj->offset = uint_offset; |
| h_bufobj->length = uint_length; |
| h_bufobj->shift = (tmp >> 4) & 0x0f; |
| h_bufobj->elem_type = (tmp >> 8) & 0xff; |
| h_bufobj->is_view = tmp & 0x0f; |
| DUK_ASSERT_HBUFFEROBJECT_VALID(h_bufobj); |
| |
| #if defined(DUK_USE_BUFFEROBJECT_SUPPORT) |
| /* TypedArray views need an automatic ArrayBuffer which must be |
| * provided as .buffer property of the view. Just create a new |
| * ArrayBuffer sharing the same underlying buffer. |
| */ |
| if (flags & DUK_BUFOBJ_CREATE_ARRBUF) { |
| h_bufobj = duk_push_bufferobject_raw(ctx, |
| DUK_HOBJECT_FLAG_EXTENSIBLE | |
| DUK_HOBJECT_FLAG_BUFFEROBJECT | |
| DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_ARRAYBUFFER), |
| DUK_BIDX_ARRAYBUFFER_PROTOTYPE); |
| |
| DUK_ASSERT(h_bufobj != NULL); |
| |
| h_bufobj->buf = h_val; |
| DUK_HBUFFER_INCREF(thr, h_val); |
| h_bufobj->offset = uint_offset; |
| h_bufobj->length = uint_length; |
| DUK_ASSERT(h_bufobj->shift == 0); |
| h_bufobj->elem_type = DUK_HBUFFEROBJECT_ELEM_UINT8; |
| DUK_ASSERT(h_bufobj->is_view == 0); |
| DUK_ASSERT_HBUFFEROBJECT_VALID(h_bufobj); |
| |
| duk_xdef_prop_stridx(ctx, -2, DUK_STRIDX_LC_BUFFER, DUK_PROPDESC_FLAGS_NONE); |
| duk_compact(ctx, -1); |
| } |
| #endif /* DUK_USE_BUFFEROBJECT_SUPPORT */ |
| |
| return; |
| |
| range_error: |
| DUK_ERROR_RANGE(thr, DUK_STR_INVALID_CALL_ARGS); |
| return; /* not reached */ |
| |
| arg_error: |
| DUK_ERROR_TYPE(thr, DUK_STR_INVALID_CALL_ARGS); |
| return; /* not reached */ |
| } |
| |
| DUK_EXTERNAL duk_idx_t duk_push_error_object_va_raw(duk_context *ctx, duk_errcode_t err_code, const char *filename, duk_int_t line, const char *fmt, va_list ap) { |
| duk_hthread *thr = (duk_hthread *) ctx; |
| duk_idx_t ret; |
| duk_hobject *proto; |
| #ifdef DUK_USE_AUGMENT_ERROR_CREATE |
| duk_bool_t noblame_fileline; |
| #endif |
| |
| DUK_ASSERT_CTX_VALID(ctx); |
| DUK_ASSERT(thr != NULL); |
| DUK_UNREF(filename); |
| DUK_UNREF(line); |
| |
| /* Error code also packs a tracedata related flag. */ |
| #ifdef DUK_USE_AUGMENT_ERROR_CREATE |
| noblame_fileline = err_code & DUK_ERRCODE_FLAG_NOBLAME_FILELINE; |
| #endif |
| err_code = err_code & (~DUK_ERRCODE_FLAG_NOBLAME_FILELINE); |
| |
| /* error gets its 'name' from the prototype */ |
| proto = duk_error_prototype_from_code(thr, err_code); |
| ret = duk_push_object_helper_proto(ctx, |
| DUK_HOBJECT_FLAG_EXTENSIBLE | |
| DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_ERROR), |
| proto); |
| |
| /* ... and its 'message' from an instance property */ |
| if (fmt) { |
| duk_push_vsprintf(ctx, fmt, ap); |
| duk_xdef_prop_stridx(ctx, -2, DUK_STRIDX_MESSAGE, DUK_PROPDESC_FLAGS_WC); |
| } else { |
| /* If no explicit message given, put error code into message field |
| * (as a number). This is not fully in keeping with the Ecmascript |
| * error model because messages are supposed to be strings (Error |
| * constructors use ToString() on their argument). However, it's |
| * probably more useful than having a separate 'code' property. |
| */ |
| duk_push_int(ctx, err_code); |
| duk_xdef_prop_stridx(ctx, -2, DUK_STRIDX_MESSAGE, DUK_PROPDESC_FLAGS_WC); |
| } |
| |
| /* XXX: .code = err_code disabled, not sure if useful */ |
| |
| /* Creation time error augmentation */ |
| #ifdef DUK_USE_AUGMENT_ERROR_CREATE |
| /* filename may be NULL in which case file/line is not recorded */ |
| duk_err_augment_error_create(thr, thr, filename, line, noblame_fileline); /* may throw an error */ |
| #endif |
| |
| return ret; |
| } |
| |
| DUK_EXTERNAL duk_idx_t duk_push_error_object_raw(duk_context *ctx, duk_errcode_t err_code, const char *filename, duk_int_t line, const char *fmt, ...) { |
| va_list ap; |
| duk_idx_t ret; |
| |
| DUK_ASSERT_CTX_VALID(ctx); |
| |
| va_start(ap, fmt); |
| ret = duk_push_error_object_va_raw(ctx, err_code, filename, line, fmt, ap); |
| va_end(ap); |
| return ret; |
| } |
| |
| #if !defined(DUK_USE_VARIADIC_MACROS) |
| DUK_EXTERNAL duk_idx_t duk_push_error_object_stash(duk_context *ctx, duk_errcode_t err_code, const char *fmt, ...) { |
| const char *filename = duk_api_global_filename; |
| duk_int_t line = duk_api_global_line; |
| va_list ap; |
| duk_idx_t ret; |
| |
| DUK_ASSERT_CTX_VALID(ctx); |
| |
| duk_api_global_filename = NULL; |
| duk_api_global_line = 0; |
| va_start(ap, fmt); |
| ret = duk_push_error_object_va_raw(ctx, err_code, filename, line, fmt, ap); |
| va_end(ap); |
| return ret; |
| } |
| #endif /* DUK_USE_VARIADIC_MACROS */ |
| |
| DUK_EXTERNAL void *duk_push_buffer_raw(duk_context *ctx, duk_size_t size, duk_small_uint_t flags) { |
| duk_hthread *thr = (duk_hthread *) ctx; |
| duk_tval *tv_slot; |
| duk_hbuffer *h; |
| void *buf_data; |
| |
| DUK_ASSERT_CTX_VALID(ctx); |
| |
| /* check stack first */ |
| if (thr->valstack_top >= thr->valstack_end) { |
| DUK_ERROR_API(thr, DUK_STR_PUSH_BEYOND_ALLOC_STACK); |
| } |
| |
| /* Check for maximum buffer length. */ |
| if (size > DUK_HBUFFER_MAX_BYTELEN) { |
| DUK_ERROR_RANGE(thr, DUK_STR_BUFFER_TOO_LONG); |
| } |
| |
| h = duk_hbuffer_alloc(thr->heap, size, flags, &buf_data); |
| if (!h) { |
| DUK_ERROR_ALLOC_DEFMSG(thr); |
| } |
| |
| tv_slot = thr->valstack_top; |
| DUK_TVAL_SET_BUFFER(tv_slot, h); |
| DUK_HBUFFER_INCREF(thr, h); |
| thr->valstack_top++; |
| |
| return (void *) buf_data; |
| } |
| |
| DUK_EXTERNAL duk_idx_t duk_push_heapptr(duk_context *ctx, void *ptr) { |
| duk_hthread *thr = (duk_hthread *) ctx; |
| duk_idx_t ret; |
| |
| DUK_ASSERT_CTX_VALID(ctx); |
| |
| ret = (duk_idx_t) (thr->valstack_top - thr->valstack_bottom); |
| |
| if (ptr == NULL) { |
| goto push_undefined; |
| } |
| |
| switch ((int) DUK_HEAPHDR_GET_TYPE((duk_heaphdr *) ptr)) { |
| case DUK_HTYPE_STRING: |
| duk_push_hstring(ctx, (duk_hstring *) ptr); |
| break; |
| case DUK_HTYPE_OBJECT: |
| duk_push_hobject(ctx, (duk_hobject *) ptr); |
| break; |
| case DUK_HTYPE_BUFFER: |
| duk_push_hbuffer(ctx, (duk_hbuffer *) ptr); |
| break; |
| default: |
| goto push_undefined; |
| } |
| return ret; |
| |
| push_undefined: |
| duk_push_undefined(ctx); |
| return ret; |
| } |
| |
| DUK_INTERNAL duk_idx_t duk_push_object_internal(duk_context *ctx) { |
| return duk_push_object_helper(ctx, |
| DUK_HOBJECT_FLAG_EXTENSIBLE | |
| DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_OBJECT), |
| -1); /* no prototype */ |
| } |
| |
| DUK_INTERNAL void duk_push_hstring(duk_context *ctx, duk_hstring *h) { |
| duk_tval tv; |
| DUK_ASSERT_CTX_VALID(ctx); |
| DUK_ASSERT(h != NULL); |
| DUK_TVAL_SET_STRING(&tv, h); |
| duk_push_tval(ctx, &tv); |
| } |
| |
| DUK_INTERNAL void duk_push_hstring_stridx(duk_context *ctx, duk_small_int_t stridx) { |
| duk_hthread *thr = (duk_hthread *) ctx; |
| DUK_UNREF(thr); |
| DUK_ASSERT(stridx >= 0 && stridx < DUK_HEAP_NUM_STRINGS); |
| duk_push_hstring(ctx, DUK_HTHREAD_GET_STRING(thr, stridx)); |
| } |
| |
| DUK_INTERNAL void duk_push_hobject(duk_context *ctx, duk_hobject *h) { |
| duk_tval tv; |
| DUK_ASSERT_CTX_VALID(ctx); |
| DUK_ASSERT(h != NULL); |
| DUK_TVAL_SET_OBJECT(&tv, h); |
| duk_push_tval(ctx, &tv); |
| } |
| |
| DUK_INTERNAL void duk_push_hbuffer(duk_context *ctx, duk_hbuffer *h) { |
| duk_tval tv; |
| DUK_ASSERT_CTX_VALID(ctx); |
| DUK_ASSERT(h != NULL); |
| DUK_TVAL_SET_BUFFER(&tv, h); |
| duk_push_tval(ctx, &tv); |
| } |
| |
| DUK_INTERNAL void duk_push_hobject_bidx(duk_context *ctx, duk_small_int_t builtin_idx) { |
| duk_hthread *thr = (duk_hthread *) ctx; |
| DUK_ASSERT_CTX_VALID(ctx); |
| DUK_ASSERT(thr != NULL); |
| DUK_ASSERT(builtin_idx >= 0 && builtin_idx < DUK_NUM_BUILTINS); |
| DUK_ASSERT(thr->builtins[builtin_idx] != NULL); |
| duk_push_hobject(ctx, thr->builtins[builtin_idx]); |
| } |
| |
| /* |
| * Poppers |
| */ |
| |
| DUK_EXTERNAL void duk_pop_n(duk_context *ctx, duk_idx_t count) { |
| duk_hthread *thr = (duk_hthread *) ctx; |
| duk_tval *tv; |
| |
| DUK_ASSERT_CTX_VALID(ctx); |
| |
| if (DUK_UNLIKELY(count < 0)) { |
| DUK_ERROR_API(thr, DUK_STR_INVALID_COUNT); |
| return; |
| } |
| |
| DUK_ASSERT(thr->valstack_top >= thr->valstack_bottom); |
| if (DUK_UNLIKELY((duk_size_t) (thr->valstack_top - thr->valstack_bottom) < (duk_size_t) count)) { |
| DUK_ERROR_API(thr, DUK_STR_POP_TOO_MANY); |
| } |
| |
| /* |
| * Must be very careful here, every DECREF may cause reallocation |
| * of our valstack. |
| */ |
| |
| /* XXX: inlined DECREF macro would be nice here: no NULL check, |
| * refzero queueing but no refzero algorithm run (= no pointer |
| * instability), inline code. |
| */ |
| |
| /* XXX: optimize loops */ |
| |
| #if defined(DUK_USE_REFERENCE_COUNTING) |
| while (count > 0) { |
| count--; |
| tv = --thr->valstack_top; /* tv points to element just below prev top */ |
| DUK_ASSERT(tv >= thr->valstack_bottom); |
| DUK_TVAL_SET_UNDEFINED_UPDREF(thr, tv); /* side effects */ |
| } |
| #else |
| tv = thr->valstack_top; |
| while (count > 0) { |
| count--; |
| tv--; |
| DUK_ASSERT(tv >= thr->valstack_bottom); |
| DUK_TVAL_SET_UNDEFINED(tv); |
| } |
| thr->valstack_top = tv; |
| #endif |
| |
| DUK_ASSERT(thr->valstack_top >= thr->valstack_bottom); |
| } |
| |
| /* Popping one element is called so often that when footprint is not an issue, |
| * compile a specialized function for it. |
| */ |
| #if defined(DUK_USE_PREFER_SIZE) |
| DUK_EXTERNAL void duk_pop(duk_context *ctx) { |
| DUK_ASSERT_CTX_VALID(ctx); |
| duk_pop_n(ctx, 1); |
| } |
| #else |
| DUK_EXTERNAL void duk_pop(duk_context *ctx) { |
| duk_hthread *thr = (duk_hthread *) ctx; |
| duk_tval *tv; |
| DUK_ASSERT_CTX_VALID(ctx); |
| |
| DUK_ASSERT(thr->valstack_top >= thr->valstack_bottom); |
| if (DUK_UNLIKELY(thr->valstack_top == thr->valstack_bottom)) { |
| DUK_ERROR_API(thr, DUK_STR_POP_TOO_MANY); |
| } |
| |
| tv = --thr->valstack_top; /* tv points to element just below prev top */ |
| DUK_ASSERT(tv >= thr->valstack_bottom); |
| #ifdef DUK_USE_REFERENCE_COUNTING |
| DUK_TVAL_SET_UNDEFINED_UPDREF(thr, tv); /* side effects */ |
| #else |
| DUK_TVAL_SET_UNDEFINED(tv); |
| #endif |
| DUK_ASSERT(thr->valstack_top >= thr->valstack_bottom); |
| } |
| #endif /* !DUK_USE_PREFER_SIZE */ |
| |
| DUK_EXTERNAL void duk_pop_2(duk_context *ctx) { |
| DUK_ASSERT_CTX_VALID(ctx); |
| duk_pop_n(ctx, 2); |
| } |
| |
| DUK_EXTERNAL void duk_pop_3(duk_context *ctx) { |
| DUK_ASSERT_CTX_VALID(ctx); |
| duk_pop_n(ctx, 3); |
| } |
| |
| /* |
| * Error throwing |
| */ |
| |
| DUK_EXTERNAL void duk_throw(duk_context *ctx) { |
| duk_hthread *thr = (duk_hthread *) ctx; |
| |
| DUK_ASSERT(thr->valstack_bottom >= thr->valstack); |
| DUK_ASSERT(thr->valstack_top >= thr->valstack_bottom); |
| DUK_ASSERT(thr->valstack_end >= thr->valstack_top); |
| |
| if (thr->valstack_top == thr->valstack_bottom) { |
| DUK_ERROR_API(thr, DUK_STR_INVALID_CALL_ARGS); |
| } |
| |
| /* Errors are augmented when they are created, not when they are |
| * thrown or re-thrown. The current error handler, however, runs |
| * just before an error is thrown. |
| */ |
| |
| /* Sync so that augmentation sees up-to-date activations, NULL |
| * thr->ptr_curr_pc so that it's not used if side effects occur |
| * in augmentation or longjmp handling. |
| */ |
| duk_hthread_sync_and_null_currpc(thr); |
| |
| #if defined(DUK_USE_AUGMENT_ERROR_THROW) |
| DUK_DDD(DUK_DDDPRINT("THROW ERROR (API): %!dT (before throw augment)", (duk_tval *) duk_get_tval(ctx, -1))); |
| duk_err_augment_error_throw(thr); |
| #endif |
| DUK_DDD(DUK_DDDPRINT("THROW ERROR (API): %!dT (after throw augment)", (duk_tval *) duk_get_tval(ctx, -1))); |
| |
| duk_err_setup_heap_ljstate(thr, DUK_LJ_TYPE_THROW); |
| |
| /* thr->heap->lj.jmpbuf_ptr is checked by duk_err_longjmp() so we don't |
| * need to check that here. If the value is NULL, a panic occurs because |
| * we can't return. |
| */ |
| |
| duk_err_longjmp(thr); |
| DUK_UNREACHABLE(); |
| } |
| |
| DUK_EXTERNAL void duk_fatal(duk_context *ctx, duk_errcode_t err_code, const char *err_msg) { |
| duk_hthread *thr = (duk_hthread *) ctx; |
| |
| DUK_ASSERT_CTX_VALID(ctx); |
| DUK_ASSERT(thr != NULL); |
| DUK_ASSERT(thr->heap != NULL); |
| DUK_ASSERT(thr->heap->fatal_func != NULL); |
| |
| DUK_D(DUK_DPRINT("fatal error occurred, code %ld, message %s", |
| (long) err_code, (const char *) err_msg)); |
| |
| /* fatal_func should be noreturn, but noreturn declarations on function |
| * pointers has a very spotty support apparently so it's not currently |
| * done. |
| */ |
| thr->heap->fatal_func(ctx, err_code, err_msg); |
| |
| DUK_PANIC(DUK_ERR_API_ERROR, "fatal handler returned"); |
| } |
| |
| DUK_EXTERNAL void duk_error_va_raw(duk_context *ctx, duk_errcode_t err_code, const char *filename, duk_int_t line, const char *fmt, va_list ap) { |
| DUK_ASSERT_CTX_VALID(ctx); |
| |
| duk_push_error_object_va_raw(ctx, err_code, filename, line, fmt, ap); |
| duk_throw(ctx); |
| } |
| |
| DUK_EXTERNAL void duk_error_raw(duk_context *ctx, duk_errcode_t err_code, const char *filename, duk_int_t line, const char *fmt, ...) { |
| va_list ap; |
| |
| DUK_ASSERT_CTX_VALID(ctx); |
| |
| va_start(ap, fmt); |
| duk_push_error_object_va_raw(ctx, err_code, filename, line, fmt, ap); |
| va_end(ap); |
| duk_throw(ctx); |
| } |
| |
| #if !defined(DUK_USE_VARIADIC_MACROS) |
| DUK_EXTERNAL void duk_error_stash(duk_context *ctx, duk_errcode_t err_code, const char *fmt, ...) { |
| const char *filename; |
| duk_int_t line; |
| va_list ap; |
| |
| DUK_ASSERT_CTX_VALID(ctx); |
| |
| filename = duk_api_global_filename; |
| line = duk_api_global_line; |
| duk_api_global_filename = NULL; |
| duk_api_global_line = 0; |
| |
| va_start(ap, fmt); |
| duk_push_error_object_va_raw(ctx, err_code, filename, line, fmt, ap); |
| va_end(ap); |
| duk_throw(ctx); |
| } |
| #endif /* DUK_USE_VARIADIC_MACROS */ |
| |
| /* |
| * Comparison |
| */ |
| |
| DUK_EXTERNAL duk_bool_t duk_equals(duk_context *ctx, duk_idx_t index1, duk_idx_t index2) { |
| duk_hthread *thr = (duk_hthread *) ctx; |
| duk_tval *tv1, *tv2; |
| |
| DUK_ASSERT_CTX_VALID(ctx); |
| |
| tv1 = duk_get_tval(ctx, index1); |
| tv2 = duk_get_tval(ctx, index2); |
| if ((tv1 == NULL) || (tv2 == NULL)) { |
| return 0; |
| } |
| |
| /* Coercion may be needed, the helper handles that by pushing the |
| * tagged values to the stack. |
| */ |
| return duk_js_equals(thr, tv1, tv2); |
| } |
| |
| DUK_EXTERNAL duk_bool_t duk_strict_equals(duk_context *ctx, duk_idx_t index1, duk_idx_t index2) { |
| duk_tval *tv1, *tv2; |
| |
| DUK_ASSERT_CTX_VALID(ctx); |
| |
| tv1 = duk_get_tval(ctx, index1); |
| tv2 = duk_get_tval(ctx, index2); |
| if ((tv1 == NULL) || (tv2 == NULL)) { |
| return 0; |
| } |
| |
| /* No coercions or other side effects, so safe */ |
| return duk_js_strict_equals(tv1, tv2); |
| } |
| |
| /* |
| * instanceof |
| */ |
| |
| DUK_EXTERNAL duk_bool_t duk_instanceof(duk_context *ctx, duk_idx_t index1, duk_idx_t index2) { |
| duk_tval *tv1, *tv2; |
| |
| DUK_ASSERT_CTX_VALID(ctx); |
| |
| /* Index validation is strict, which differs from duk_equals(). |
| * The strict behavior mimics how instanceof itself works, e.g. |
| * it is a TypeError if rval is not a -callable- object. It would |
| * be somewhat inconsistent if rval would be allowed to be |
| * non-existent without a TypeError. |
| */ |
| tv1 = duk_require_tval(ctx, index1); |
| DUK_ASSERT(tv1 != NULL); |
| tv2 = duk_require_tval(ctx, index2); |
| DUK_ASSERT(tv2 != NULL); |
| |
| return duk_js_instanceof((duk_hthread *) ctx, tv1, tv2); |
| } |
| |
| /* |
| * Lightfunc |
| */ |
| |
| DUK_INTERNAL void duk_push_lightfunc_name(duk_context *ctx, duk_tval *tv) { |
| duk_c_function func; |
| |
| DUK_ASSERT(DUK_TVAL_IS_LIGHTFUNC(tv)); |
| |
| /* Lightfunc name, includes Duktape/C native function pointer, which |
| * can often be used to locate the function from a symbol table. |
| * The name also includes the 16-bit duk_tval flags field because it |
| * includes the magic value. Because a single native function often |
| * provides different functionality depending on the magic value, it |
| * seems reasonably to include it in the name. |
| * |
| * On the other hand, a complicated name increases string table |
| * pressure in low memory environments (but only when function name |
| * is accessed). |
| */ |
| |
| func = DUK_TVAL_GET_LIGHTFUNC_FUNCPTR(tv); |
| duk_push_sprintf(ctx, "light_"); |
| duk_push_string_funcptr(ctx, (duk_uint8_t *) &func, sizeof(func)); |
| duk_push_sprintf(ctx, "_%04x", (unsigned int) DUK_TVAL_GET_LIGHTFUNC_FLAGS(tv)); |
| duk_concat(ctx, 3); |
| } |
| |
| DUK_INTERNAL void duk_push_lightfunc_tostring(duk_context *ctx, duk_tval *tv) { |
| DUK_ASSERT(DUK_TVAL_IS_LIGHTFUNC(tv)); |
| |
| duk_push_string(ctx, "function "); |
| duk_push_lightfunc_name(ctx, tv); |
| duk_push_string(ctx, "() {\"light\"}"); |
| duk_concat(ctx, 3); |
| } |
| |
| /* |
| * Function pointers |
| * |
| * Printing function pointers is non-portable, so we do that by hex printing |
| * bytes from memory. |
| */ |
| |
| DUK_INTERNAL void duk_push_string_funcptr(duk_context *ctx, duk_uint8_t *ptr, duk_size_t sz) { |
| duk_uint8_t buf[32 * 2]; |
| duk_uint8_t *p, *q; |
| duk_small_uint_t i; |
| duk_small_uint_t t; |
| |
| DUK_ASSERT(sz <= 32); /* sanity limit for function pointer size */ |
| |
| p = buf; |
| #if defined(DUK_USE_INTEGER_LE) |
| q = ptr + sz; |
| #else |
| q = ptr; |
| #endif |
| for (i = 0; i < sz; i++) { |
| #if defined(DUK_USE_INTEGER_LE) |
| t = *(--q); |
| #else |
| t = *(q++); |
| #endif |
| *p++ = duk_lc_digits[t >> 4]; |
| *p++ = duk_lc_digits[t & 0x0f]; |
| } |
| |
| duk_push_lstring(ctx, (const char *) buf, sz * 2); |
| } |
| |
| #if !defined(DUK_USE_PARANOID_ERRORS) |
| /* |
| * Push readable string summarizing duk_tval. The operation is side effect |
| * free and will only throw from internal errors (e.g. out of memory). |
| * This is used by e.g. property access code to summarize a key/base safely, |
| * and is not intended to be fast (but small and safe). |
| */ |
| |
| #define DUK__READABLE_STRING_MAXCHARS 32 |
| |
| /* String sanitizer which escapes ASCII control characters and a few other |
| * ASCII characters, passes Unicode as is, and replaces invalid UTF-8 with |
| * question marks. No errors are thrown for any input string, except in out |
| * of memory situations. |
| */ |
| DUK_LOCAL void duk__push_hstring_readable_unicode(duk_context *ctx, duk_hstring *h_input) { |
| duk_hthread *thr; |
| const duk_uint8_t *p, *p_start, *p_end; |
| duk_uint8_t buf[DUK_UNICODE_MAX_XUTF8_LENGTH * DUK__READABLE_STRING_MAXCHARS + |
| 2 /*quotes*/ + 3 /*periods*/]; |
| duk_uint8_t *q; |
| duk_ucodepoint_t cp; |
| duk_small_uint_t nchars; |
| |
| DUK_ASSERT_CTX_VALID(ctx); |
| DUK_ASSERT(h_input != NULL); |
| thr = (duk_hthread *) ctx; |
| |
| p_start = (const duk_uint8_t *) DUK_HSTRING_GET_DATA(h_input); |
| p_end = p_start + DUK_HSTRING_GET_BYTELEN(h_input); |
| p = p_start; |
| q = buf; |
| |
| nchars = 0; |
| *q++ = (duk_uint8_t) DUK_ASC_SINGLEQUOTE; |
| for (;;) { |
| if (p >= p_end) { |
| break; |
| } |
| if (nchars == DUK__READABLE_STRING_MAXCHARS) { |
| *q++ = (duk_uint8_t) DUK_ASC_PERIOD; |
| *q++ = (duk_uint8_t) DUK_ASC_PERIOD; |
| *q++ = (duk_uint8_t) DUK_ASC_PERIOD; |
| break; |
| } |
| if (duk_unicode_decode_xutf8(thr, &p, p_start, p_end, &cp)) { |
| if (cp < 0x20 || cp == 0x7f || cp == DUK_ASC_SINGLEQUOTE || cp == DUK_ASC_BACKSLASH) { |
| DUK_ASSERT(DUK_UNICODE_MAX_XUTF8_LENGTH >= 4); /* estimate is valid */ |
| DUK_ASSERT((cp >> 4) <= 0x0f); |
| *q++ = (duk_uint8_t) DUK_ASC_BACKSLASH; |
| *q++ = (duk_uint8_t) DUK_ASC_LC_X; |
| *q++ = (duk_uint8_t) duk_lc_digits[cp >> 4]; |
| *q++ = (duk_uint8_t) duk_lc_digits[cp & 0x0f]; |
| } else { |
| q += duk_unicode_encode_xutf8(cp, q); |
| } |
| } else { |
| p++; /* advance manually */ |
| *q++ = (duk_uint8_t) DUK_ASC_QUESTION; |
| } |
| nchars++; |
| } |
| *q++ = (duk_uint8_t) DUK_ASC_SINGLEQUOTE; |
| |
| duk_push_lstring(ctx, (const char *) buf, (duk_size_t) (q - buf)); |
| } |
| |
| DUK_INTERNAL const char *duk_push_string_tval_readable(duk_context *ctx, duk_tval *tv) { |
| duk_hthread *thr; |
| |
| DUK_ASSERT_CTX_VALID(ctx); |
| thr = (duk_hthread *) ctx; |
| DUK_UNREF(thr); |
| |
| if (tv == NULL) { |
| duk_push_string(ctx, "none"); |
| } else { |
| switch (DUK_TVAL_GET_TAG(tv)) { |
| case DUK_TAG_STRING: { |
| duk__push_hstring_readable_unicode(ctx, DUK_TVAL_GET_STRING(tv)); |
| break; |
| } |
| case DUK_TAG_OBJECT: { |
| duk_hobject *h = DUK_TVAL_GET_OBJECT(tv); |
| DUK_ASSERT(h != NULL); |
| duk_push_hobject_class_string(ctx, h); |
| break; |
| } |
| case DUK_TAG_BUFFER: { |
| /* XXX: Hex encoded, length limited buffer summary here? */ |
| duk_hbuffer *h = DUK_TVAL_GET_BUFFER(tv); |
| DUK_ASSERT(h != NULL); |
| duk_push_sprintf(ctx, "[buffer:%ld]", (long) DUK_HBUFFER_GET_SIZE(h)); |
| break; |
| } |
| case DUK_TAG_POINTER: { |
| /* Surround with parentheses like in JX, ensures NULL pointer |
| * is distinguishable from null value ("(null)" vs "null"). |
| */ |
| duk_push_tval(ctx, tv); |
| duk_push_sprintf(ctx, "(%s)", duk_to_string(ctx, -1)); |
| duk_remove(ctx, -2); |
| break; |
| } |
| default: { |
| duk_push_tval(ctx, tv); |
| break; |
| } |
| } |
| } |
| |
| return duk_to_string(ctx, -1); |
| } |
| |
| DUK_INTERNAL const char *duk_push_string_readable(duk_context *ctx, duk_idx_t index) { |
| DUK_ASSERT_CTX_VALID(ctx); |
| return duk_push_string_tval_readable(ctx, duk_get_tval(ctx, index)); |
| } |
| #endif /* !DUK_USE_PARANOID_ERRORS */ |
| |
| #undef DUK__CHECK_SPACE |
| #undef DUK__PACK_ARGS |
| #undef DUK__READABLE_STRING_MAXCHARS |