/* Shared helper to provide toString() and valueOf(). Checks 'this', gets * the primitive value to stack top, and optionally coerces with ToString(). */ DUK_INTERNAL duk_ret_t duk_bi_boolean_prototype_tostring_shared(duk_context *ctx) { duk_tval *tv; duk_hobject *h; duk_small_int_t coerce_tostring = duk_get_current_magic(ctx); /* XXX: there is room to use a shared helper here, many built-ins * check the 'this' type, and if it's an object, check its class, * then get its internal value, etc. */ duk_push_this(ctx); tv = duk_get_tval(ctx, -1); DUK_ASSERT(tv != NULL); if (DUK_TVAL_IS_BOOLEAN(tv)) { goto type_ok; } else if (DUK_TVAL_IS_OBJECT(tv)) { h = DUK_TVAL_GET_OBJECT(tv); DUK_ASSERT(h != NULL); if (DUK_HOBJECT_GET_CLASS_NUMBER(h) == DUK_HOBJECT_CLASS_BOOLEAN) { duk_get_prop_stridx(ctx, -1, DUK_STRIDX_INT_VALUE); DUK_ASSERT(duk_is_boolean(ctx, -1)); goto type_ok; } } return DUK_RET_TYPE_ERROR; type_ok: if (coerce_tostring) { duk_to_string(ctx, -1); } return 1; }
DUK_EXTERNAL duk_int_t duk_get_magic(duk_context *ctx, duk_idx_t idx) { duk_hthread *thr = (duk_hthread *) ctx; duk_tval *tv; duk_hobject *h; DUK_ASSERT_CTX_VALID(ctx); tv = duk_require_tval(ctx, idx); if (DUK_TVAL_IS_OBJECT(tv)) { h = DUK_TVAL_GET_OBJECT(tv); DUK_ASSERT(h != NULL); if (!DUK_HOBJECT_HAS_NATFUNC(h)) { goto type_error; } return (duk_int_t) ((duk_hnatfunc *) h)->magic; } else if (DUK_TVAL_IS_LIGHTFUNC(tv)) { duk_small_uint_t lf_flags = DUK_TVAL_GET_LIGHTFUNC_FLAGS(tv); return (duk_int_t) DUK_LFUNC_FLAGS_GET_MAGIC(lf_flags); } /* fall through */ type_error: DUK_ERROR_TYPE(thr, DUK_STR_UNEXPECTED_TYPE); return 0; }
int duk_bi_pointer_prototype_tostring_shared(duk_context *ctx) { duk_tval *tv; int to_string = duk_get_magic(ctx); duk_push_this(ctx); tv = duk_require_tval(ctx, -1); DUK_ASSERT(tv != NULL); if (DUK_TVAL_IS_POINTER(tv)) { /* nop */ } else if (DUK_TVAL_IS_OBJECT(tv)) { duk_hobject *h = DUK_TVAL_GET_OBJECT(tv); DUK_ASSERT(h != NULL); /* Must be a "pointer object", i.e. class "Pointer" */ if (DUK_HOBJECT_GET_CLASS_NUMBER(h) != DUK_HOBJECT_CLASS_POINTER) { goto type_error; } duk_get_prop_stridx(ctx, -1, DUK_STRIDX_INT_VALUE); } else { goto type_error; } if (to_string) { duk_to_string(ctx, -1); } return 1; type_error: return DUK_RET_TYPE_ERROR; }
DUK_INTERNAL duk_ret_t duk_bi_string_prototype_to_string(duk_context *ctx) { duk_tval *tv; duk_push_this(ctx); tv = duk_require_tval(ctx, -1); DUK_ASSERT(tv != NULL); if (DUK_TVAL_IS_STRING(tv)) { /* return as is */ return 1; } else if (DUK_TVAL_IS_OBJECT(tv)) { duk_hobject *h = DUK_TVAL_GET_OBJECT(tv); DUK_ASSERT(h != NULL); /* Must be a "string object", i.e. class "String" */ if (DUK_HOBJECT_GET_CLASS_NUMBER(h) != DUK_HOBJECT_CLASS_STRING) { goto type_error; } duk_get_prop_stridx(ctx, -1, DUK_STRIDX_INT_VALUE); DUK_ASSERT(duk_is_string(ctx, -1)); return 1; } else { goto type_error; } /* never here, but fall through */ type_error: DUK_DCERROR_TYPE_INVALID_ARGS((duk_hthread *) ctx); }
/* Resolve a bound function on value stack top to a non-bound target * (leave other values as is). */ DUK_INTERNAL void duk_resolve_nonbound_function(duk_context *ctx) { duk_tval *tv; tv = DUK_GET_TVAL_NEGIDX(ctx, -1); if (DUK_TVAL_IS_OBJECT(tv)) { duk_hobject *h; h = DUK_TVAL_GET_OBJECT(tv); DUK_ASSERT(h != NULL); if (DUK_HOBJECT_HAS_BOUNDFUNC(h)) { duk_push_tval(ctx, &((duk_hboundfunc *) h)->target); duk_replace(ctx, -2); #if 0 DUK_TVAL_SET_TVAL(tv, &((duk_hboundfunc *) h)->target); DUK_TVAL_INCREF(thr, tv); DUK_HOBJECT_DECREF_NORZ(thr, h); #endif /* Rely on Function.prototype.bind() on never creating a bound * function whose target is not proper. This is now safe * because the target is not even an internal property but a * struct member. */ DUK_ASSERT(duk_is_lightfunc(ctx, -1) || duk_is_callable(ctx, -1)); } } /* Lightfuncs cannot be bound but are always callable and * constructable. */ }
/* * IsCallable() (E5 Section 9.11) * * XXX: API equivalent is a separate implementation now, and this has * currently no callers. */ #if 0 /* unused */ DUK_INTERNAL duk_bool_t duk_js_iscallable(duk_tval *tv_x) { duk_hobject *obj; if (!DUK_TVAL_IS_OBJECT(tv_x)) { return 0; } obj = DUK_TVAL_GET_OBJECT(tv_x); DUK_ASSERT(obj != NULL); return DUK_HOBJECT_IS_CALLABLE(obj); }
int duk_builtin_object_constructor_create(duk_context *ctx) { duk_hthread *thr = (duk_hthread *) ctx; duk_tval *tv; duk_hobject *proto = NULL; duk_hobject *h; DUK_ASSERT_TOP(ctx, 2); tv = duk_get_tval(ctx, 0); DUK_ASSERT(tv != NULL); if (DUK_TVAL_IS_NULL(tv)) { ; } else if (DUK_TVAL_IS_OBJECT(tv)) { proto = DUK_TVAL_GET_OBJECT(tv); DUK_ASSERT(proto != NULL); } else { return DUK_RET_TYPE_ERROR; } /* FIXME: direct helper to create with specific prototype */ (void) duk_push_object_helper(ctx, DUK_HOBJECT_FLAG_EXTENSIBLE | DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_OBJECT), -1); h = duk_get_hobject(ctx, -1); DUK_ASSERT(h != NULL); DUK_ASSERT(h->prototype == NULL); DUK_HOBJECT_SET_PROTOTYPE(thr, h, proto); if (!duk_is_undefined(ctx, 1)) { /* [ O Properties obj ] */ /* Use original function. No need to get it explicitly, * just call the helper. */ duk_replace(ctx, 0); /* [ obj Properties ] */ return duk_hobject_object_define_properties(ctx); } /* [ O Properties obj ] */ return 1; }
DUK_LOCAL duk_uint8_t *duk__dump_varmap(duk_hthread *thr, duk_uint8_t *p, duk_bufwriter_ctx *bw_ctx, duk_hobject *func) { duk_tval *tv; tv = duk_hobject_find_existing_entry_tval_ptr(thr->heap, (duk_hobject *) func, DUK_HTHREAD_STRING_INT_VARMAP(thr)); if (tv != NULL && DUK_TVAL_IS_OBJECT(tv)) { duk_hobject *h; duk_uint_fast32_t i; h = DUK_TVAL_GET_OBJECT(tv); DUK_ASSERT(h != NULL); /* We know _Varmap only has own properties so walk property * table directly. We also know _Varmap is dense and all * values are numbers; assert for these. GC and finalizers * shouldn't affect _Varmap so side effects should be fine. */ for (i = 0; i < (duk_uint_fast32_t) DUK_HOBJECT_GET_ENEXT(h); i++) { duk_hstring *key; duk_tval *tv_val; duk_uint32_t val; key = DUK_HOBJECT_E_GET_KEY(thr->heap, h, i); DUK_ASSERT(key != NULL); /* _Varmap is dense */ DUK_ASSERT(!DUK_HOBJECT_E_SLOT_IS_ACCESSOR(thr->heap, h, i)); tv_val = DUK_HOBJECT_E_GET_VALUE_TVAL_PTR(thr->heap, h, i); DUK_ASSERT(tv_val != NULL); DUK_ASSERT(DUK_TVAL_IS_NUMBER(tv_val)); /* known to be number; in fact an integer */ #if defined(DUK_USE_FASTINT) DUK_ASSERT(DUK_TVAL_IS_FASTINT(tv_val)); DUK_ASSERT(DUK_TVAL_GET_FASTINT(tv_val) == (duk_int64_t) DUK_TVAL_GET_FASTINT_U32(tv_val)); /* known to be 32-bit */ val = DUK_TVAL_GET_FASTINT_U32(tv_val); #else val = (duk_uint32_t) DUK_TVAL_GET_NUMBER(tv_val); #endif DUK_ASSERT(DUK_HSTRING_MAX_BYTELEN <= 0x7fffffffUL); /* ensures no overflow */ p = DUK_BW_ENSURE_RAW(thr, bw_ctx, 4 + DUK_HSTRING_GET_BYTELEN(key) + 4, p); p = duk__dump_hstring_raw(p, key); DUK_RAW_WRITE_U32_BE(p, val); } } p = DUK_BW_ENSURE_RAW(thr, bw_ctx, 4, p); DUK_RAW_WRITE_U32_BE(p, 0); /* end of _Varmap */ return p; }
DUK_INTERNAL duk_ret_t duk_bi_object_constructor_create(duk_context *ctx) { duk_tval *tv; duk_hobject *proto = NULL; DUK_ASSERT_TOP(ctx, 2); tv = duk_get_tval(ctx, 0); DUK_ASSERT(tv != NULL); if (DUK_TVAL_IS_NULL(tv)) { ; } else if (DUK_TVAL_IS_OBJECT(tv)) { proto = DUK_TVAL_GET_OBJECT(tv); DUK_ASSERT(proto != NULL); } else { return DUK_RET_TYPE_ERROR; } (void) duk_push_object_helper_proto(ctx, DUK_HOBJECT_FLAG_EXTENSIBLE | DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_OBJECT), proto); if (!duk_is_undefined(ctx, 1)) { /* [ O Properties obj ] */ /* Use original function. No need to get it explicitly, * just call the helper. */ duk_replace(ctx, 0); /* [ obj Properties ] */ return duk_hobject_object_define_properties(ctx); } /* [ O Properties obj ] */ return 1; }
DUK_LOCAL duk_uint8_t *duk__dump_formals(duk_hthread *thr, duk_uint8_t *p, duk_bufwriter_ctx *bw_ctx, duk_hobject *func) { duk_tval *tv; tv = duk_hobject_find_existing_entry_tval_ptr(thr->heap, (duk_hobject *) func, DUK_HTHREAD_STRING_INT_FORMALS(thr)); if (tv != NULL && DUK_TVAL_IS_OBJECT(tv)) { duk_hobject *h; duk_uint_fast32_t i; h = DUK_TVAL_GET_OBJECT(tv); DUK_ASSERT(h != NULL); /* We know _Formals is dense and all entries will be in the * array part. GC and finalizers shouldn't affect _Formals * so side effects should be fine. */ for (i = 0; i < (duk_uint_fast32_t) DUK_HOBJECT_GET_ASIZE(h); i++) { duk_tval *tv_val; duk_hstring *varname; tv_val = DUK_HOBJECT_A_GET_VALUE_PTR(thr->heap, h, i); DUK_ASSERT(tv_val != NULL); if (DUK_TVAL_IS_STRING(tv_val)) { /* Array is dense and contains only strings, but ASIZE may * be larger than used part and there are UNUSED entries. */ varname = DUK_TVAL_GET_STRING(tv_val); DUK_ASSERT(varname != NULL); DUK_ASSERT(DUK_HSTRING_MAX_BYTELEN <= 0x7fffffffUL); /* ensures no overflow */ p = DUK_BW_ENSURE_RAW(thr, bw_ctx, 4 + DUK_HSTRING_GET_BYTELEN(varname), p); p = duk__dump_hstring_raw(p, varname); } } } p = DUK_BW_ENSURE_RAW(thr, bw_ctx, 4, p); DUK_RAW_WRITE_U32_BE(p, 0); /* end of _Formals */ return p; }
DUK_INTERNAL duk_ret_t duk_bi_function_prototype_to_string(duk_context *ctx) { duk_tval *tv; /* * E5 Section 15.3.4.2 places few requirements on the output of * this function: * * - The result is an implementation dependent representation * of the function; in particular * * - The result must follow the syntax of a FunctionDeclaration. * In particular, the function must have a name (even in the * case of an anonymous function or a function with an empty * name). * * - Note in particular that the output does NOT need to compile * into anything useful. */ /* XXX: faster internal way to get this */ duk_push_this(ctx); tv = duk_get_tval(ctx, -1); DUK_ASSERT(tv != NULL); if (DUK_TVAL_IS_OBJECT(tv)) { duk_hobject *obj = DUK_TVAL_GET_OBJECT(tv); const char *func_name; /* Function name: missing/undefined is mapped to empty string, * otherwise coerce to string. */ /* XXX: currently no handling for non-allowed identifier characters, * e.g. a '{' in the function name. */ duk_get_prop_stridx(ctx, -1, DUK_STRIDX_NAME); if (duk_is_undefined(ctx, -1)) { func_name = ""; } else { func_name = duk_to_string(ctx, -1); DUK_ASSERT(func_name != NULL); } /* Indicate function type in the function body using a dummy * directive. */ if (DUK_HOBJECT_HAS_COMPILEDFUNCTION(obj)) { duk_push_sprintf(ctx, "function %s() {\"ecmascript\"}", (const char *) func_name); } else if (DUK_HOBJECT_HAS_NATIVEFUNCTION(obj)) { duk_push_sprintf(ctx, "function %s() {\"native\"}", (const char *) func_name); } else if (DUK_HOBJECT_HAS_BOUND(obj)) { duk_push_sprintf(ctx, "function %s() {\"bound\"}", (const char *) func_name); } else { goto type_error; } } else if (DUK_TVAL_IS_LIGHTFUNC(tv)) { duk_push_lightfunc_tostring(ctx, tv); } else { goto type_error; } return 1; type_error: return DUK_RET_TYPE_ERROR; }
DUK_EXTERNAL void duk_new(duk_context *ctx, duk_idx_t nargs) { /* * There are two [[Construct]] operations in the specification: * * - E5 Section 13.2.2: for Function objects * - E5 Section 15.3.4.5.2: for "bound" Function objects * * The chain of bound functions is resolved in Section 15.3.4.5.2, * with arguments "piling up" until the [[Construct]] internal * method is called on the final, actual Function object. Note * that the "prototype" property is looked up *only* from the * final object, *before* calling the constructor. * * Currently we follow the bound function chain here to get the * "prototype" property value from the final, non-bound function. * However, we let duk_handle_call() handle the argument "piling" * when the constructor is called. The bound function chain is * thus now processed twice. * * When constructing new Array instances, an unnecessary object is * created and discarded now: the standard [[Construct]] creates an * object, and calls the Array constructor. The Array constructor * returns an Array instance, which is used as the result value for * the "new" operation; the object created before the Array constructor * call is discarded. * * This would be easy to fix, e.g. by knowing that the Array constructor * will always create a replacement object and skip creating the fallback * object in that case. * * Note: functions called via "new" need to know they are called as a * constructor. For instance, built-in constructors behave differently * depending on how they are called. */ /* XXX: merge this with duk_js_call.c, as this function implements * core semantics (or perhaps merge the two files altogether). */ duk_hthread *thr = (duk_hthread *) ctx; duk_hobject *proto; duk_hobject *cons; duk_hobject *fallback; duk_idx_t idx_cons; duk_small_uint_t call_flags; DUK_ASSERT_CTX_VALID(ctx); /* [... constructor arg1 ... argN] */ idx_cons = duk_require_normalize_index(ctx, -nargs - 1); DUK_DDD(DUK_DDDPRINT("top=%ld, nargs=%ld, idx_cons=%ld", (long) duk_get_top(ctx), (long) nargs, (long) idx_cons)); /* XXX: code duplication */ /* * Figure out the final, non-bound constructor, to get "prototype" * property. */ duk_dup(ctx, idx_cons); for (;;) { duk_tval *tv; tv = DUK_GET_TVAL_NEGIDX(ctx, -1); DUK_ASSERT(tv != NULL); if (DUK_TVAL_IS_OBJECT(tv)) { cons = DUK_TVAL_GET_OBJECT(tv); DUK_ASSERT(cons != NULL); if (!DUK_HOBJECT_IS_CALLABLE(cons) || !DUK_HOBJECT_HAS_CONSTRUCTABLE(cons)) { /* Checking callability of the immediate target * is important, same for constructability. * Checking it for functions down the bound * function chain is not strictly necessary * because .bind() should normally reject them. * But it's good to check anyway because it's * technically possible to edit the bound function * chain via internal keys. */ goto not_constructable; } if (!DUK_HOBJECT_HAS_BOUNDFUNC(cons)) { break; } } else if (DUK_TVAL_IS_LIGHTFUNC(tv)) { /* Lightfuncs cannot be bound. */ break; } else { /* Anything else is not constructable. */ goto not_constructable; } duk_get_prop_stridx_short(ctx, -1, DUK_STRIDX_INT_TARGET); /* -> [... cons target] */ duk_remove_m2(ctx); /* -> [... target] */ } DUK_ASSERT(duk_is_callable(ctx, -1)); DUK_ASSERT(duk_is_lightfunc(ctx, -1) || (duk_get_hobject(ctx, -1) != NULL && !DUK_HOBJECT_HAS_BOUNDFUNC(duk_get_hobject(ctx, -1)))); /* [... constructor arg1 ... argN final_cons] */ /* * Create "fallback" object to be used as the object instance, * unless the constructor returns a replacement value. * Its internal prototype needs to be set based on "prototype" * property of the constructor. */ duk_push_object(ctx); /* class Object, extensible */ /* [... constructor arg1 ... argN final_cons fallback] */ duk_get_prop_stridx_short(ctx, -2, DUK_STRIDX_PROTOTYPE); proto = duk_get_hobject(ctx, -1); if (!proto) { DUK_DDD(DUK_DDDPRINT("constructor has no 'prototype' property, or value not an object " "-> leave standard Object prototype as fallback prototype")); } else { DUK_DDD(DUK_DDDPRINT("constructor has 'prototype' property with object value " "-> set fallback prototype to that value: %!iO", (duk_heaphdr *) proto)); fallback = duk_get_hobject(ctx, -2); DUK_ASSERT(fallback != NULL); DUK_HOBJECT_SET_PROTOTYPE_UPDREF(thr, fallback, proto); } duk_pop(ctx); /* [... constructor arg1 ... argN final_cons fallback] */ /* * Manipulate callstack for the call. */ duk_dup_top(ctx); duk_insert(ctx, idx_cons + 1); /* use fallback as 'this' value */ duk_insert(ctx, idx_cons); /* also stash it before constructor, * in case we need it (as the fallback value) */ duk_pop(ctx); /* pop final_cons */ /* [... fallback constructor fallback(this) arg1 ... argN]; * Note: idx_cons points to first 'fallback', not 'constructor'. */ DUK_DDD(DUK_DDDPRINT("before call, idx_cons+1 (constructor) -> %!T, idx_cons+2 (fallback/this) -> %!T, " "nargs=%ld, top=%ld", (duk_tval *) duk_get_tval(ctx, idx_cons + 1), (duk_tval *) duk_get_tval(ctx, idx_cons + 2), (long) nargs, (long) duk_get_top(ctx))); /* * Call the constructor function (called in "constructor mode"). */ call_flags = DUK_CALL_FLAG_CONSTRUCTOR_CALL; /* not protected, respect reclimit, is a constructor call */ duk_handle_call_unprotected(thr, /* thread */ nargs, /* num_stack_args */ call_flags); /* call_flags */ /* [... fallback retval] */ DUK_DDD(DUK_DDDPRINT("constructor call finished, fallback=%!iT, retval=%!iT", (duk_tval *) duk_get_tval(ctx, -2), (duk_tval *) duk_get_tval(ctx, -1))); /* * Determine whether to use the constructor return value as the created * object instance or not. */ if (duk_check_type_mask(ctx, -1, DUK_TYPE_MASK_OBJECT | DUK_TYPE_MASK_BUFFER | DUK_TYPE_MASK_LIGHTFUNC)) { duk_remove_m2(ctx); } else { duk_pop(ctx); } /* * Augment created errors upon creation (not when they are thrown or * rethrown). __FILE__ and __LINE__ are not desirable here; the call * stack reflects the caller which is correct. */ #if defined(DUK_USE_AUGMENT_ERROR_CREATE) duk_hthread_sync_currpc(thr); duk_err_augment_error_create(thr, thr, NULL, 0, 1 /*noblame_fileline*/); #endif /* [... retval] */ return; not_constructable: #if defined(DUK_USE_VERBOSE_ERRORS) #if defined(DUK_USE_PARANOID_ERRORS) DUK_ERROR_FMT1(thr, DUK_ERR_TYPE_ERROR, "%s not constructable", duk_get_type_name(ctx, -1)); #else DUK_ERROR_FMT1(thr, DUK_ERR_TYPE_ERROR, "%s not constructable", duk_push_string_readable(ctx, -1)); #endif #else DUK_ERROR_TYPE(thr, "not constructable"); #endif }
DUK_INTERNAL duk_bool_t duk_js_equals_helper(duk_hthread *thr, duk_tval *tv_x, duk_tval *tv_y, duk_small_int_t flags) { duk_context *ctx = (duk_context *) thr; duk_tval *tv_tmp; /* If flags != 0 (strict or SameValue), thr can be NULL. For loose * equals comparison it must be != NULL. */ DUK_ASSERT(flags != 0 || thr != NULL); /* * Same type? * * Note: since number values have no explicit tag in the 8-byte * representation, need the awkward if + switch. */ #if defined(DUK_USE_FASTINT) if (DUK_TVAL_IS_FASTINT(tv_x) && DUK_TVAL_IS_FASTINT(tv_y)) { if (DUK_TVAL_GET_FASTINT(tv_x) == DUK_TVAL_GET_FASTINT(tv_y)) { return 1; } else { return 0; } } else #endif if (DUK_TVAL_IS_NUMBER(tv_x) && DUK_TVAL_IS_NUMBER(tv_y)) { /* Catches both doubles and cases where only one argument is a fastint */ if (DUK_UNLIKELY((flags & DUK_EQUALS_FLAG_SAMEVALUE) != 0)) { /* SameValue */ return duk__js_samevalue_number(DUK_TVAL_GET_NUMBER(tv_x), DUK_TVAL_GET_NUMBER(tv_y)); } else { /* equals and strict equals */ return duk__js_equals_number(DUK_TVAL_GET_NUMBER(tv_x), DUK_TVAL_GET_NUMBER(tv_y)); } } else if (DUK_TVAL_GET_TAG(tv_x) == DUK_TVAL_GET_TAG(tv_y)) { switch (DUK_TVAL_GET_TAG(tv_x)) { case DUK_TAG_UNDEFINED: case DUK_TAG_NULL: { return 1; } case DUK_TAG_BOOLEAN: { return DUK_TVAL_GET_BOOLEAN(tv_x) == DUK_TVAL_GET_BOOLEAN(tv_y); } case DUK_TAG_POINTER: { return DUK_TVAL_GET_POINTER(tv_x) == DUK_TVAL_GET_POINTER(tv_y); } case DUK_TAG_STRING: case DUK_TAG_OBJECT: { /* heap pointer comparison suffices */ return DUK_TVAL_GET_HEAPHDR(tv_x) == DUK_TVAL_GET_HEAPHDR(tv_y); } case DUK_TAG_BUFFER: { if ((flags & (DUK_EQUALS_FLAG_STRICT | DUK_EQUALS_FLAG_SAMEVALUE)) != 0) { /* heap pointer comparison suffices */ return DUK_TVAL_GET_HEAPHDR(tv_x) == DUK_TVAL_GET_HEAPHDR(tv_y); } else { /* non-strict equality for buffers compares contents */ duk_hbuffer *h_x = DUK_TVAL_GET_BUFFER(tv_x); duk_hbuffer *h_y = DUK_TVAL_GET_BUFFER(tv_y); duk_size_t len_x = DUK_HBUFFER_GET_SIZE(h_x); duk_size_t len_y = DUK_HBUFFER_GET_SIZE(h_y); void *buf_x; void *buf_y; if (len_x != len_y) { return 0; } buf_x = (void *) DUK_HBUFFER_GET_DATA_PTR(thr->heap, h_x); buf_y = (void *) DUK_HBUFFER_GET_DATA_PTR(thr->heap, h_y); /* if len_x == len_y == 0, buf_x and/or buf_y may * be NULL, but that's OK. */ DUK_ASSERT(len_x == len_y); DUK_ASSERT(len_x == 0 || buf_x != NULL); DUK_ASSERT(len_y == 0 || buf_y != NULL); return (DUK_MEMCMP(buf_x, buf_y, len_x) == 0) ? 1 : 0; } } case DUK_TAG_LIGHTFUNC: { /* At least 'magic' has a significant impact on function * identity. */ duk_small_uint_t lf_flags_x; duk_small_uint_t lf_flags_y; duk_c_function func_x; duk_c_function func_y; DUK_TVAL_GET_LIGHTFUNC(tv_x, func_x, lf_flags_x); DUK_TVAL_GET_LIGHTFUNC(tv_y, func_y, lf_flags_y); return ((func_x == func_y) && (lf_flags_x == lf_flags_y)) ? 1 : 0; } #if defined(DUK_USE_FASTINT) case DUK_TAG_FASTINT: #endif default: { DUK_ASSERT(DUK_TVAL_IS_NUMBER(tv_x)); DUK_ASSERT(DUK_TVAL_IS_NUMBER(tv_y)); DUK_UNREACHABLE(); return 0; } } } if ((flags & (DUK_EQUALS_FLAG_STRICT | DUK_EQUALS_FLAG_SAMEVALUE)) != 0) { return 0; } DUK_ASSERT(flags == 0); /* non-strict equality from here on */ /* * Types are different; various cases for non-strict comparison * * Since comparison is symmetric, we use a "swap trick" to reduce * code size. */ /* Undefined/null are considered equal (e.g. "null == undefined" -> true). */ if ((DUK_TVAL_IS_UNDEFINED(tv_x) && DUK_TVAL_IS_NULL(tv_y)) || (DUK_TVAL_IS_NULL(tv_x) && DUK_TVAL_IS_UNDEFINED(tv_y))) { return 1; } /* Number/string-or-buffer -> coerce string to number (e.g. "'1.5' == 1.5" -> true). */ if (DUK_TVAL_IS_NUMBER(tv_x) && (DUK_TVAL_IS_STRING(tv_y) || DUK_TVAL_IS_BUFFER(tv_y))) { /* the next 'if' is guaranteed to match after swap */ tv_tmp = tv_x; tv_x = tv_y; tv_y = tv_tmp; } if ((DUK_TVAL_IS_STRING(tv_x) || DUK_TVAL_IS_BUFFER(tv_x)) && DUK_TVAL_IS_NUMBER(tv_y)) { /* XXX: this is possible without resorting to the value stack */ duk_double_t d1, d2; d2 = DUK_TVAL_GET_NUMBER(tv_y); duk_push_tval(ctx, tv_x); duk_to_string(ctx, -1); /* buffer values are coerced first to string here */ duk_to_number(ctx, -1); d1 = duk_require_number(ctx, -1); duk_pop(ctx); return duk__js_equals_number(d1, d2); } /* Buffer/string -> compare contents. */ if (DUK_TVAL_IS_BUFFER(tv_x) && DUK_TVAL_IS_STRING(tv_y)) { tv_tmp = tv_x; tv_x = tv_y; tv_y = tv_tmp; } if (DUK_TVAL_IS_STRING(tv_x) && DUK_TVAL_IS_BUFFER(tv_y)) { duk_hstring *h_x = DUK_TVAL_GET_STRING(tv_x); duk_hbuffer *h_y = DUK_TVAL_GET_BUFFER(tv_y); duk_size_t len_x = DUK_HSTRING_GET_BYTELEN(h_x); duk_size_t len_y = DUK_HBUFFER_GET_SIZE(h_y); void *buf_x; void *buf_y; if (len_x != len_y) { return 0; } buf_x = (void *) DUK_HSTRING_GET_DATA(h_x); buf_y = (void *) DUK_HBUFFER_GET_DATA_PTR(thr->heap, h_y); /* if len_x == len_y == 0, buf_x and/or buf_y may * be NULL, but that's OK. */ DUK_ASSERT(len_x == len_y); DUK_ASSERT(len_x == 0 || buf_x != NULL); DUK_ASSERT(len_y == 0 || buf_y != NULL); return (DUK_MEMCMP(buf_x, buf_y, len_x) == 0) ? 1 : 0; } /* Boolean/any -> coerce boolean to number and try again. If boolean is * compared to a pointer, the final comparison after coercion now always * yields false (as pointer vs. number compares to false), but this is * not special cased. */ if (DUK_TVAL_IS_BOOLEAN(tv_x)) { tv_tmp = tv_x; tv_x = tv_y; tv_y = tv_tmp; } if (DUK_TVAL_IS_BOOLEAN(tv_y)) { /* ToNumber(bool) is +1.0 or 0.0. Tagged boolean value is always 0 or 1. */ duk_bool_t rc; DUK_ASSERT(DUK_TVAL_GET_BOOLEAN(tv_y) == 0 || DUK_TVAL_GET_BOOLEAN(tv_y) == 1); duk_push_tval(ctx, tv_x); duk_push_int(ctx, DUK_TVAL_GET_BOOLEAN(tv_y)); rc = duk_js_equals_helper(thr, duk_get_tval(ctx, -2), duk_get_tval(ctx, -1), 0 /*flags:nonstrict*/); duk_pop_2(ctx); return rc; } /* String-number-buffer/object -> coerce object to primitive (apparently without hint), then try again. */ if ((DUK_TVAL_IS_STRING(tv_x) || DUK_TVAL_IS_NUMBER(tv_x) || DUK_TVAL_IS_BUFFER(tv_x)) && DUK_TVAL_IS_OBJECT(tv_y)) { tv_tmp = tv_x; tv_x = tv_y; tv_y = tv_tmp; } if (DUK_TVAL_IS_OBJECT(tv_x) && (DUK_TVAL_IS_STRING(tv_y) || DUK_TVAL_IS_NUMBER(tv_y) || DUK_TVAL_IS_BUFFER(tv_y))) { duk_bool_t rc; duk_push_tval(ctx, tv_x); duk_push_tval(ctx, tv_y); duk_to_primitive(ctx, -2, DUK_HINT_NONE); /* apparently no hint? */ rc = duk_js_equals_helper(thr, duk_get_tval(ctx, -2), duk_get_tval(ctx, -1), 0 /*flags:nonstrict*/); duk_pop_2(ctx); return rc; } /* Nothing worked -> not equal. */ return 0; }
int duk_bi_function_prototype_to_string(duk_context *ctx) { duk_tval *tv; /* * E5 Section 15.3.4.2 places few requirements on the output of * this function: * * - The result is an implementation dependent representation * of the function; in particular * * - The result must follow the syntax of a FunctionDeclaration. * In particular, the function must have a name (even in the * case of an anonymous function or a function with an empty * name). * * - Note in particular that the output does NOT need to compile * into anything useful. */ /* FIXME: faster internal way to get this */ duk_push_this(ctx); tv = duk_get_tval(ctx, -1); DUK_ASSERT(tv != NULL); if (DUK_TVAL_IS_OBJECT(tv)) { duk_hobject *obj = DUK_TVAL_GET_OBJECT(tv); const char *func_name = "anonymous"; /* FIXME: rework, it would be nice to avoid C formatting functions to * ensure there are no Unicode issues. */ duk_get_prop_stridx(ctx, -1, DUK_STRIDX_NAME); if (!duk_is_undefined(ctx, -1)) { func_name = duk_to_string(ctx, -1); DUK_ASSERT(func_name != NULL); if (func_name[0] == (char) 0) { func_name = "empty"; } } if (DUK_HOBJECT_HAS_COMPILEDFUNCTION(obj)) { /* FIXME: actual source, if available */ duk_push_sprintf(ctx, "function %s() {/* source code */}", func_name); } else if (DUK_HOBJECT_HAS_NATIVEFUNCTION(obj)) { duk_push_sprintf(ctx, "function %s() {/* native code */}", func_name); } else if (DUK_HOBJECT_HAS_BOUND(obj)) { duk_push_sprintf(ctx, "function %s() {/* bound */}", func_name); } else { goto type_error; } } else { goto type_error; } return 1; type_error: return DUK_RET_TYPE_ERROR; }
duk_ret_t duk_bi_thread_resume(duk_context *ctx) { duk_hthread *thr = (duk_hthread *) ctx; duk_hthread *thr_resume; duk_tval tv_tmp; duk_tval *tv; duk_hobject *func; duk_small_int_t is_error; DUK_DDDPRINT("Duktape.Thread.resume(): thread=%!T, value=%!T, is_error=%!T", duk_get_tval(ctx, 0), duk_get_tval(ctx, 1), duk_get_tval(ctx, 2)); DUK_ASSERT(thr->state == DUK_HTHREAD_STATE_RUNNING); DUK_ASSERT(thr->heap->curr_thread == thr); thr_resume = duk_require_hthread(ctx, 0); is_error = (duk_small_int_t) duk_to_boolean(ctx, 2); duk_set_top(ctx, 2); /* [ thread value ] */ /* * Thread state and calling context checks */ if (thr->callstack_top < 2) { DUK_DDPRINT("resume state invalid: callstack should contain at least 2 entries (caller and Duktape.Thread.resume)"); goto state_error; } DUK_ASSERT((thr->callstack + thr->callstack_top - 1)->func != NULL); /* us */ DUK_ASSERT(DUK_HOBJECT_IS_NATIVEFUNCTION((thr->callstack + thr->callstack_top - 1)->func)); DUK_ASSERT((thr->callstack + thr->callstack_top - 2)->func != NULL); /* caller */ if (!DUK_HOBJECT_IS_COMPILEDFUNCTION((thr->callstack + thr->callstack_top - 2)->func)) { DUK_DDPRINT("resume state invalid: caller must be Ecmascript code"); goto state_error; } /* Note: there is no requirement that: 'thr->callstack_preventcount == 1' * like for yield. */ if (thr_resume->state != DUK_HTHREAD_STATE_INACTIVE && thr_resume->state != DUK_HTHREAD_STATE_YIELDED) { DUK_DDPRINT("resume state invalid: target thread must be INACTIVE or YIELDED"); goto state_error; } DUK_ASSERT(thr_resume->state == DUK_HTHREAD_STATE_INACTIVE || thr_resume->state == DUK_HTHREAD_STATE_YIELDED); /* Further state-dependent pre-checks */ if (thr_resume->state == DUK_HTHREAD_STATE_YIELDED) { /* no pre-checks now, assume a previous yield() has left things in * tip-top shape (longjmp handler will assert for these). */ } else { DUK_ASSERT(thr_resume->state == DUK_HTHREAD_STATE_INACTIVE); if ((thr_resume->callstack_top != 0) || (thr_resume->valstack_top - thr_resume->valstack != 1)) { goto state_invalid_initial; } tv = &thr_resume->valstack_top[-1]; DUK_ASSERT(tv >= thr_resume->valstack && tv < thr_resume->valstack_top); if (!DUK_TVAL_IS_OBJECT(tv)) { goto state_invalid_initial; } func = DUK_TVAL_GET_OBJECT(tv); DUK_ASSERT(func != NULL); if (!DUK_HOBJECT_IS_COMPILEDFUNCTION(func)) { /* Note: cannot be a bound function either right now, * this would be easy to relax though. */ goto state_invalid_initial; } } /* * The error object has been augmented with a traceback and other * info from its creation point -- usually another thread. The * error handler is called here right before throwing, but it also * runs in the resumer's thread. It might be nice to get a traceback * from the resumee but this is not the case now. */ #if defined(DUK_USE_AUGMENT_ERROR_THROW) if (is_error) { DUK_ASSERT_TOP(ctx, 2); /* value (error) is at stack top */ duk_err_augment_error_throw(thr); /* in resumer's context */ } #endif #ifdef DUK_USE_DEBUG /* debug logging */ if (is_error) { DUK_DDDPRINT("RESUME ERROR: thread=%!T, value=%!T", duk_get_tval(ctx, 0), duk_get_tval(ctx, 1)); } else if (thr_resume->state == DUK_HTHREAD_STATE_YIELDED) { DUK_DDDPRINT("RESUME NORMAL: thread=%!T, value=%!T", duk_get_tval(ctx, 0), duk_get_tval(ctx, 1)); } else { DUK_DDDPRINT("RESUME INITIAL: thread=%!T, value=%!T", duk_get_tval(ctx, 0), duk_get_tval(ctx, 1)); } #endif thr->heap->lj.type = DUK_LJ_TYPE_RESUME; /* lj value2: thread */ DUK_ASSERT(thr->valstack_bottom < thr->valstack_top); DUK_TVAL_SET_TVAL(&tv_tmp, &thr->heap->lj.value2); DUK_TVAL_SET_TVAL(&thr->heap->lj.value2, &thr->valstack_bottom[0]); DUK_TVAL_INCREF(thr, &thr->heap->lj.value2); DUK_TVAL_DECREF(thr, &tv_tmp); /* lj value1: value */ DUK_ASSERT(thr->valstack_bottom + 1 < thr->valstack_top); DUK_TVAL_SET_TVAL(&tv_tmp, &thr->heap->lj.value1); DUK_TVAL_SET_TVAL(&thr->heap->lj.value1, &thr->valstack_bottom[1]); DUK_TVAL_INCREF(thr, &thr->heap->lj.value1); DUK_TVAL_DECREF(thr, &tv_tmp); thr->heap->lj.iserror = is_error; DUK_ASSERT(thr->heap->lj.jmpbuf_ptr != NULL); /* call is from executor, so we know we have a jmpbuf */ duk_err_longjmp(thr); /* execution resumes in bytecode executor */ return 0; /* never here */ state_invalid_initial: DUK_ERROR(thr, DUK_ERR_TYPE_ERROR, "invalid initial thread state/stack"); return 0; /* never here */ state_error: DUK_ERROR(thr, DUK_ERR_TYPE_ERROR, "invalid state for resume"); return 0; /* never here */ }
static duk_uint8_t *duk__load_func(duk_context *ctx, duk_uint8_t *p, duk_uint8_t *p_end) { duk_hthread *thr; duk_hcompiledfunction *h_fun; duk_hbuffer *h_data; duk_size_t data_size; duk_uint32_t count_instr, count_const, count_funcs; duk_uint32_t n; duk_uint32_t tmp32; duk_small_uint_t const_type; duk_uint8_t *fun_data; duk_uint8_t *q; duk_idx_t idx_base; duk_tval *tv; duk_uarridx_t arr_idx; /* XXX: There's some overlap with duk_js_closure() here, but * seems difficult to share code. Ensure that the final function * looks the same as created by duk_js_closure(). */ DUK_ASSERT(ctx != NULL); thr = (duk_hthread *) ctx; DUK_DD(DUK_DDPRINT("loading function, p=%p, p_end=%p", (void *) p, (void *) p_end)); DUK__ASSERT_LEFT(3 * 4); count_instr = DUK_RAW_READ_U32_BE(p); count_const = DUK_RAW_READ_U32_BE(p); count_funcs = DUK_RAW_READ_U32_BE(p); data_size = sizeof(duk_tval) * count_const + sizeof(duk_hobject *) * count_funcs + sizeof(duk_instr_t) * count_instr; DUK_DD(DUK_DDPRINT("instr=%ld, const=%ld, funcs=%ld, data_size=%ld", (long) count_instr, (long) count_const, (long) count_const, (long) data_size)); /* Value stack is used to ensure reachability of constants and * inner functions being loaded. Require enough space to handle * large functions correctly. */ duk_require_stack(ctx, 2 + count_const + count_funcs); idx_base = duk_get_top(ctx); /* Push function object, init flags etc. This must match * duk_js_push_closure() quite carefully. */ duk_push_compiledfunction(ctx); h_fun = duk_get_hcompiledfunction(ctx, -1); DUK_ASSERT(h_fun != NULL); DUK_ASSERT(DUK_HOBJECT_IS_COMPILEDFUNCTION((duk_hobject *) h_fun)); DUK_ASSERT(DUK_HCOMPILEDFUNCTION_GET_DATA(thr->heap, h_fun) == NULL); DUK_ASSERT(DUK_HCOMPILEDFUNCTION_GET_FUNCS(thr->heap, h_fun) == NULL); DUK_ASSERT(DUK_HCOMPILEDFUNCTION_GET_BYTECODE(thr->heap, h_fun) == NULL); h_fun->nregs = DUK_RAW_READ_U16_BE(p); h_fun->nargs = DUK_RAW_READ_U16_BE(p); #if defined(DUK_USE_DEBUGGER_SUPPORT) h_fun->start_line = DUK_RAW_READ_U32_BE(p); h_fun->end_line = DUK_RAW_READ_U32_BE(p); #else p += 8; /* skip line info */ #endif /* duk_hcompiledfunction flags; quite version specific */ tmp32 = DUK_RAW_READ_U32_BE(p); DUK_HEAPHDR_SET_FLAGS((duk_heaphdr *) h_fun, tmp32); /* standard prototype */ DUK_HOBJECT_SET_PROTOTYPE_UPDREF(thr, &h_fun->obj, thr->builtins[DUK_BIDX_FUNCTION_PROTOTYPE]); /* assert just a few critical flags */ DUK_ASSERT(DUK_HEAPHDR_GET_TYPE((duk_heaphdr *) h_fun) == DUK_HTYPE_OBJECT); DUK_ASSERT(!DUK_HOBJECT_HAS_BOUND(&h_fun->obj)); DUK_ASSERT(DUK_HOBJECT_HAS_COMPILEDFUNCTION(&h_fun->obj)); DUK_ASSERT(!DUK_HOBJECT_HAS_NATIVEFUNCTION(&h_fun->obj)); DUK_ASSERT(!DUK_HOBJECT_HAS_THREAD(&h_fun->obj)); DUK_ASSERT(!DUK_HOBJECT_HAS_EXOTIC_ARRAY(&h_fun->obj)); DUK_ASSERT(!DUK_HOBJECT_HAS_EXOTIC_STRINGOBJ(&h_fun->obj)); DUK_ASSERT(!DUK_HOBJECT_HAS_EXOTIC_ARGUMENTS(&h_fun->obj)); /* Create function 'data' buffer but don't attach it yet. */ fun_data = (duk_uint8_t *) duk_push_fixed_buffer(ctx, data_size); DUK_ASSERT(fun_data != NULL); /* Load bytecode instructions. */ DUK_ASSERT(sizeof(duk_instr_t) == 4); DUK__ASSERT_LEFT(count_instr * sizeof(duk_instr_t)); #if defined(DUK_USE_INTEGER_BE) q = fun_data + sizeof(duk_tval) * count_const + sizeof(duk_hobject *) * count_funcs; DUK_MEMCPY((void *) q, (const void *) p, sizeof(duk_instr_t) * count_instr); p += sizeof(duk_instr_t) * count_instr; #else q = fun_data + sizeof(duk_tval) * count_const + sizeof(duk_hobject *) * count_funcs; for (n = count_instr; n > 0; n--) { *((duk_instr_t *) (void *) q) = DUK_RAW_READ_U32_BE(p); q += sizeof(duk_instr_t); } #endif /* Load constants onto value stack but don't yet copy to buffer. */ for (n = count_const; n > 0; n--) { DUK__ASSERT_LEFT(1); const_type = DUK_RAW_READ_U8(p); switch (const_type) { case DUK__SER_STRING: { p = duk__load_string_raw(ctx, p); break; } case DUK__SER_NUMBER: { /* Important to do a fastint check so that constants are * properly read back as fastints. */ duk_tval tv_tmp; duk_double_t val; DUK__ASSERT_LEFT(8); val = DUK_RAW_READ_DOUBLE_BE(p); DUK_TVAL_SET_NUMBER_CHKFAST(&tv_tmp, val); duk_push_tval(ctx, &tv_tmp); break; } default: { goto format_error; } } } /* Load inner functions to value stack, but don't yet copy to buffer. */ for (n = count_funcs; n > 0; n--) { p = duk__load_func(ctx, p, p_end); if (p == NULL) { goto format_error; } } /* With constants and inner functions on value stack, we can now * atomically finish the function 'data' buffer, bump refcounts, * etc. * * Here we take advantage of the value stack being just a duk_tval * array: we can just memcpy() the constants as long as we incref * them afterwards. */ h_data = (duk_hbuffer *) duk_get_hbuffer(ctx, idx_base + 1); DUK_ASSERT(h_data != NULL); DUK_ASSERT(!DUK_HBUFFER_HAS_DYNAMIC(h_data)); DUK_HCOMPILEDFUNCTION_SET_DATA(thr->heap, h_fun, h_data); DUK_HBUFFER_INCREF(thr, h_data); tv = duk_get_tval(ctx, idx_base + 2); /* may be NULL if no constants or inner funcs */ DUK_ASSERT((count_const == 0 && count_funcs == 0) || tv != NULL); q = fun_data; if (count_const > 0) { /* Explicit zero size check to avoid NULL 'tv'. */ DUK_MEMCPY((void *) q, (const void *) tv, sizeof(duk_tval) * count_const); for (n = count_const; n > 0; n--) { DUK_TVAL_INCREF_FAST(thr, (duk_tval *) (void *) q); /* no side effects */ q += sizeof(duk_tval); } tv += count_const; } DUK_HCOMPILEDFUNCTION_SET_FUNCS(thr->heap, h_fun, (duk_hobject **) (void *) q); for (n = count_funcs; n > 0; n--) { duk_hobject *h_obj; DUK_ASSERT(DUK_TVAL_IS_OBJECT(tv)); h_obj = DUK_TVAL_GET_OBJECT(tv); DUK_ASSERT(h_obj != NULL); tv++; DUK_HOBJECT_INCREF(thr, h_obj); *((duk_hobject **) (void *) q) = h_obj; q += sizeof(duk_hobject *); } DUK_HCOMPILEDFUNCTION_SET_BYTECODE(thr->heap, h_fun, (duk_instr_t *) (void *) q); /* The function object is now reachable and refcounts are fine, * so we can pop off all the temporaries. */ DUK_DDD(DUK_DDDPRINT("function is reachable, reset top; func: %!iT", duk_get_tval(ctx, idx_base))); duk_set_top(ctx, idx_base + 1); /* Setup function properties. */ tmp32 = DUK_RAW_READ_U32_BE(p); duk_push_u32(ctx, tmp32); duk_xdef_prop_stridx(ctx, -2, DUK_STRIDX_LENGTH, DUK_PROPDESC_FLAGS_NONE); p = duk__load_string_raw(ctx, p); if (DUK_HOBJECT_HAS_NAMEBINDING((duk_hobject *) h_fun)) { /* Original function instance/template had NAMEBINDING. * Must create a lexical environment on loading to allow * recursive functions like 'function foo() { foo(); }'. */ duk_hobject *proto; proto = thr->builtins[DUK_BIDX_GLOBAL_ENV]; (void) duk_push_object_helper_proto(ctx, DUK_HOBJECT_FLAG_EXTENSIBLE | DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_DECENV), proto); duk_dup(ctx, -2); /* -> [ func funcname env funcname ] */ duk_dup(ctx, idx_base); /* -> [ func funcname env funcname func ] */ duk_xdef_prop(ctx, -3, DUK_PROPDESC_FLAGS_NONE); /* -> [ func funcname env ] */ duk_xdef_prop_stridx(ctx, idx_base, DUK_STRIDX_INT_LEXENV, DUK_PROPDESC_FLAGS_WC); /* since closure has NEWENV, never define DUK_STRIDX_INT_VARENV, as it * will be ignored anyway */ } duk_xdef_prop_stridx(ctx, -2, DUK_STRIDX_NAME, DUK_PROPDESC_FLAGS_NONE); p = duk__load_string_raw(ctx, p); duk_xdef_prop_stridx(ctx, -2, DUK_STRIDX_FILE_NAME, DUK_PROPDESC_FLAGS_WC); duk_push_object(ctx); duk_dup(ctx, -2); duk_xdef_prop_stridx(ctx, -2, DUK_STRIDX_CONSTRUCTOR, DUK_PROPDESC_FLAGS_WC); /* func.prototype.constructor = func */ duk_compact(ctx, -1); duk_xdef_prop_stridx(ctx, -2, DUK_STRIDX_PROTOTYPE, DUK_PROPDESC_FLAGS_W); p = duk__load_buffer_raw(ctx, p); duk_xdef_prop_stridx(ctx, -2, DUK_STRIDX_INT_PC2LINE, DUK_PROPDESC_FLAGS_WC); duk_push_object(ctx); /* _Varmap */ for (;;) { /* XXX: awkward */ p = duk__load_string_raw(ctx, p); if (duk_get_length(ctx, -1) == 0) { duk_pop(ctx); break; } tmp32 = DUK_RAW_READ_U32_BE(p); duk_push_u32(ctx, tmp32); duk_put_prop(ctx, -3); } duk_compact(ctx, -1); duk_xdef_prop_stridx(ctx, -2, DUK_STRIDX_INT_VARMAP, DUK_PROPDESC_FLAGS_NONE); duk_push_array(ctx); /* _Formals */ for (arr_idx = 0; ; arr_idx++) { /* XXX: awkward */ p = duk__load_string_raw(ctx, p); if (duk_get_length(ctx, -1) == 0) { duk_pop(ctx); break; } duk_put_prop_index(ctx, -2, arr_idx); } duk_compact(ctx, -1); duk_xdef_prop_stridx(ctx, -2, DUK_STRIDX_INT_FORMALS, DUK_PROPDESC_FLAGS_NONE); /* Return with final function pushed on stack top. */ DUK_DD(DUK_DDPRINT("final loaded function: %!iT", duk_get_tval(ctx, -1))); DUK_ASSERT_TOP(ctx, idx_base + 1); return p; format_error: return NULL; }
DUK_LOCAL void duk__add_traceback(duk_hthread *thr, duk_hthread *thr_callstack, const char *c_filename, duk_int_t c_line, duk_bool_t noblame_fileline) { duk_context *ctx = (duk_context *) thr; duk_small_uint_t depth; duk_int_t i, i_min; duk_int_t arr_size; duk_harray *a; duk_tval *tv; duk_hstring *s; duk_uint32_t u32; duk_double_t d; DUK_ASSERT(thr != NULL); DUK_ASSERT(thr_callstack != NULL); DUK_ASSERT(ctx != NULL); /* [ ... error ] */ /* * The traceback format is pretty arcane in an attempt to keep it compact * and cheap to create. It may change arbitrarily from version to version. * It should be decoded/accessed through version specific accessors only. * * See doc/error-objects.rst. */ DUK_DDD(DUK_DDDPRINT("adding traceback to object: %!T", (duk_tval *) duk_get_tval(ctx, -1))); /* Preallocate array to correct size, so that we can just write out * the _Tracedata values into the array part. */ depth = DUK_USE_TRACEBACK_DEPTH; arr_size = (duk_int_t) (thr_callstack->callstack_top <= depth ? thr_callstack->callstack_top : depth) * 2; if (thr->compile_ctx != NULL && thr->compile_ctx->h_filename != NULL) { arr_size += 2; } if (c_filename) { /* We need the C filename to be interned before getting the * array part pointer to avoid any GC interference while the * array part is populated. */ duk_push_string(ctx, c_filename); arr_size += 2; } DUK_D(DUK_DPRINT("preallocated _Tracedata to %ld items", (long) arr_size)); a = duk_push_harray_with_size(ctx, (duk_uint32_t) arr_size); /* XXX: call which returns array part pointer directly */ DUK_ASSERT(a != NULL); tv = DUK_HOBJECT_A_GET_BASE(thr->heap, (duk_hobject *) a); DUK_ASSERT(tv != NULL || arr_size == 0); /* Compiler SyntaxErrors (and other errors) come first, and are * blamed by default (not flagged "noblame"). */ if (thr->compile_ctx != NULL && thr->compile_ctx->h_filename != NULL) { s = thr->compile_ctx->h_filename; DUK_TVAL_SET_STRING(tv, s); DUK_HSTRING_INCREF(thr, s); tv++; u32 = (duk_uint32_t) thr->compile_ctx->curr_token.start_line; /* (flags<<32) + (line), flags = 0 */ DUK_TVAL_SET_U32(tv, u32); tv++; } /* Filename/line from C macros (__FILE__, __LINE__) are added as an * entry with a special format: (string, number). The number contains * the line and flags. */ /* [ ... error c_filename? arr ] */ if (c_filename) { DUK_ASSERT(DUK_TVAL_IS_STRING(thr->valstack_top - 2)); s = DUK_TVAL_GET_STRING(thr->valstack_top - 2); /* interned c_filename */ DUK_ASSERT(s != NULL); DUK_TVAL_SET_STRING(tv, s); DUK_HSTRING_INCREF(thr, s); tv++; d = (noblame_fileline ? ((duk_double_t) DUK_TB_FLAG_NOBLAME_FILELINE) * DUK_DOUBLE_2TO32 : 0.0) + (duk_double_t) c_line; DUK_TVAL_SET_DOUBLE(tv, d); tv++; } /* traceback depth doesn't take into account the filename/line * special handling above (intentional) */ depth = DUK_USE_TRACEBACK_DEPTH; i_min = (thr_callstack->callstack_top > (duk_size_t) depth ? (duk_int_t) (thr_callstack->callstack_top - depth) : 0); DUK_ASSERT(i_min >= 0); /* [ ... error c_filename? arr ] */ DUK_ASSERT(thr_callstack->callstack_top <= DUK_INT_MAX); /* callstack limits */ for (i = (duk_int_t) (thr_callstack->callstack_top - 1); i >= i_min; i--) { duk_uint32_t pc; duk_tval *tv_src; /* * Note: each API operation potentially resizes the callstack, * so be careful to re-lookup after every operation. Currently * these is no issue because we don't store a temporary 'act' * pointer at all. (This would be a non-issue if we operated * directly on the array part.) */ /* [... arr] */ DUK_ASSERT_DISABLE(thr_callstack->callstack[i].pc >= 0); /* unsigned */ /* Add function object. */ tv_src = &(thr_callstack->callstack + i)->tv_func; /* object (function) or lightfunc */ DUK_ASSERT(DUK_TVAL_IS_OBJECT(tv_src) || DUK_TVAL_IS_LIGHTFUNC(tv_src)); DUK_TVAL_SET_TVAL(tv, tv_src); DUK_TVAL_INCREF(thr, tv); tv++; /* Add a number containing: pc, activation flags. * * PC points to next instruction, find offending PC. Note that * PC == 0 for native code. */ pc = duk_hthread_get_act_prev_pc(thr_callstack, thr_callstack->callstack + i); DUK_ASSERT_DISABLE(pc >= 0); /* unsigned */ DUK_ASSERT((duk_double_t) pc < DUK_DOUBLE_2TO32); /* assume PC is at most 32 bits and non-negative */ d = ((duk_double_t) thr_callstack->callstack[i].flags) * DUK_DOUBLE_2TO32 + (duk_double_t) pc; DUK_TVAL_SET_DOUBLE(tv, d); tv++; } DUK_ASSERT((duk_uint32_t) (tv - DUK_HOBJECT_A_GET_BASE(thr->heap, (duk_hobject *) a)) == a->length); DUK_ASSERT(a->length == (duk_uint32_t) arr_size); /* [ ... error c_filename? arr ] */ if (c_filename) { duk_remove(ctx, -2); } /* [ ... error arr ] */ duk_xdef_prop_stridx_wec(ctx, -2, DUK_STRIDX_INT_TRACEDATA); /* -> [ ... error ] */ }