int duk_bi_function_prototype_apply(duk_context *ctx) { unsigned int len; unsigned int i; /* FIXME: stack checks */ DUK_ASSERT_TOP(ctx, 2); /* not a vararg function */ duk_push_this(ctx); if (!duk_is_callable(ctx, -1)) { DUK_DDD(DUK_DDDPRINT("func is not callable")); goto type_error; } duk_insert(ctx, 0); DUK_ASSERT_TOP(ctx, 3); DUK_DDD(DUK_DDDPRINT("func=%!iT, thisArg=%!iT, argArray=%!iT", duk_get_tval(ctx, 0), duk_get_tval(ctx, 1), duk_get_tval(ctx, 2))); /* [ func thisArg argArray ] */ if (duk_is_null_or_undefined(ctx, 2)) { DUK_DDD(DUK_DDDPRINT("argArray is null/undefined, no args")); len = 0; } else if (!duk_is_object(ctx, 2)) { goto type_error; } else { DUK_DDD(DUK_DDDPRINT("argArray is an object")); /* FIXME: make this an internal helper */ duk_get_prop_stridx(ctx, 2, DUK_STRIDX_LENGTH); len = duk_to_uint32(ctx, -1); duk_pop(ctx); duk_require_stack(ctx, len); /* FIXME: more? */ DUK_DDD(DUK_DDDPRINT("argArray length is %d", len)); for (i = 0; i < len; i++) { duk_get_prop_index(ctx, 2, i); } } duk_remove(ctx, 2); DUK_ASSERT_TOP(ctx, 2 + len); /* [ func thisArg arg1 ... argN ] */ DUK_DDD(DUK_DDDPRINT("apply, func=%!iT, thisArg=%!iT, len=%d", duk_get_tval(ctx, 0), duk_get_tval(ctx, 1), len)); duk_call_method(ctx, len); return 1; type_error: return DUK_RET_TYPE_ERROR; }
DUK_INTERNAL duk_ret_t duk_bi_object_prototype_to_locale_string(duk_context *ctx) { DUK_ASSERT_TOP(ctx, 0); (void) duk_push_this_coercible_to_object(ctx); duk_get_prop_stridx(ctx, 0, DUK_STRIDX_TO_STRING); if (!duk_is_callable(ctx, 1)) { return DUK_RET_TYPE_ERROR; } duk_dup(ctx, 0); /* -> [ O toString O ] */ duk_call_method(ctx, 0); /* XXX: call method tailcall? */ return 1; }
/* Constructor */ DUK_INTERNAL duk_ret_t duk_bi_logger_constructor(duk_context *ctx) { duk_hthread *thr = (duk_hthread *) ctx; duk_idx_t nargs; /* Calling as a non-constructor is not meaningful. */ if (!duk_is_constructor_call(ctx)) { return DUK_RET_TYPE_ERROR; } nargs = duk_get_top(ctx); duk_set_top(ctx, 1); duk_push_this(ctx); /* [ name this ] */ if (nargs == 0) { /* Automatic defaulting of logger name from caller. This would * work poorly with tail calls, but constructor calls are currently * never tail calls, so tail calls are not an issue now. */ if (thr->callstack_top >= 2) { duk_activation *act_caller = thr->callstack + thr->callstack_top - 2; duk_hobject *func_caller; func_caller = DUK_ACT_GET_FUNC(act_caller); if (func_caller) { /* Stripping the filename might be a good idea * ("/foo/bar/quux.js" -> logger name "quux"), * but now used verbatim. */ duk_push_hobject(ctx, func_caller); duk_get_prop_stridx(ctx, -1, DUK_STRIDX_FILE_NAME); duk_replace(ctx, 0); } } } /* the stack is unbalanced here on purpose; we only rely on the * initial two values: [ name this ]. */ if (duk_is_string(ctx, 0)) { duk_dup(ctx, 0); duk_put_prop_stridx(ctx, 1, DUK_STRIDX_LC_N); } else { /* don't set 'n' at all, inherited value is used as name */ } duk_compact(ctx, 1); return 0; /* keep default instance */ }
DUK_INTERNAL duk_bool_t duk_get_prop_stridx_boolean(duk_context *ctx, duk_idx_t obj_index, duk_small_int_t stridx, duk_bool_t *out_has_prop) { duk_bool_t rc; DUK_ASSERT_CTX_VALID(ctx); DUK_ASSERT_DISABLE(stridx >= 0); DUK_ASSERT(stridx < DUK_HEAP_NUM_STRINGS); rc = duk_get_prop_stridx(ctx, obj_index, stridx); if (out_has_prop) { *out_has_prop = rc; } rc = duk_to_boolean(ctx, -1); DUK_ASSERT(rc == 0 || rc == 1); duk_pop(ctx); return rc; }
/* Default function to format objects. Tries to use toLogString() but falls * back to toString(). Any errors are propagated out without catching. */ DUK_INTERNAL duk_ret_t duk_bi_logger_prototype_fmt(duk_context *ctx) { if (duk_get_prop_stridx(ctx, 0, DUK_STRIDX_TO_LOG_STRING)) { /* [ arg toLogString ] */ duk_dup(ctx, 0); duk_call_method(ctx, 0); /* [ arg result ] */ return 1; } /* [ arg undefined ] */ duk_pop(ctx); duk_to_string(ctx, 0); return 1; }
duk_ret_t duk_bi_duktape_object_fin(duk_context *ctx) { (void) duk_require_hobject(ctx, 0); if (duk_get_top(ctx) >= 2) { /* Set: currently a finalizer is disabled by setting it to * undefined; this does not remove the property at the moment. * The value could be type checked to be either a function * or something else; if something else, the property could * be deleted. */ duk_set_top(ctx, 2); (void) duk_put_prop_stridx(ctx, 0, DUK_STRIDX_INT_FINALIZER); return 0; } else { /* Get. */ DUK_ASSERT(duk_get_top(ctx) == 1); duk_get_prop_stridx(ctx, 0, DUK_STRIDX_INT_FINALIZER); return 1; } }
/* Push 'this' binding, check that it is a Date object; then push the * internal time value. At the end, stack is: [ ... this timeval ]. * Returns the time value. Local time adjustment is done if requested. */ static double push_this_and_get_timeval_tzoffset(duk_context *ctx, int flags, int *out_tzoffset) { duk_hthread *thr = (duk_hthread *) ctx; duk_hobject *h; double d; int tzoffset = 0; duk_push_this(ctx); h = duk_get_hobject(ctx, -1); /* FIXME: getter with class check, useful in built-ins */ if (h == NULL || DUK_HOBJECT_GET_CLASS_NUMBER(h) != DUK_HOBJECT_CLASS_DATE) { DUK_ERROR(thr, DUK_ERR_TYPE_ERROR, "expected Date"); } duk_get_prop_stridx(ctx, -1, DUK_STRIDX_INT_VALUE); d = duk_to_number(ctx, -1); duk_pop(ctx); if (DUK_ISNAN(d)) { if (flags & FLAG_NAN_TO_ZERO) { d = 0.0; } if (flags & FLAG_NAN_TO_RANGE_ERROR) { DUK_ERROR(thr, DUK_ERR_RANGE_ERROR, "Invalid Date"); } } /* if no NaN handling flag, may still be NaN here, but not Inf */ DUK_ASSERT(!DUK_ISINF(d)); if (flags & FLAG_LOCALTIME) { /* Note: DST adjustment is determined using UTC time. * If 'd' is NaN, tzoffset will be 0. */ tzoffset = GET_LOCAL_TZOFFSET(d); /* seconds */ d += tzoffset * 1000; } if (out_tzoffset) { *out_tzoffset = tzoffset; } /* [ ... this ] */ return d; }
static int _finalize_helper(duk_context *ctx) { DUK_ASSERT(ctx != NULL); DUK_DDDPRINT("protected finalization helper running"); /* [... obj] */ duk_get_prop_stridx(ctx, -1, DUK_STRIDX_INT_FINALIZER); /* -> [... obj finalizer] */ if (!duk_is_callable(ctx, -1)) { DUK_DDDPRINT("-> no finalizer or finalizer not callable"); return 0; } duk_dup(ctx, -2); /* -> [... obj finalizer obj] */ DUK_DDDPRINT("-> finalizer found, calling finalizer"); duk_call(ctx, 1); /* -> [... obj retval] */ DUK_DDDPRINT("finalizer finished successfully"); return 0; /* Note: we rely on duk_safe_call() to fix up the stack for the caller, * so we don't need to pop stuff here. There is no return value; * caller determines rescued status based on object refcount. */ }
int duk_builtin_date_prototype_to_json(duk_context *ctx) { /* Note: toJSON() is a generic function which works even if 'this' * is not a Date. The sole argument is ignored. */ duk_push_this(ctx); duk_to_object(ctx, -1); duk_dup_top(ctx); duk_to_primitive(ctx, -1, DUK_HINT_NUMBER); if (duk_is_number(ctx, -1)) { double d = duk_get_number(ctx, -1); if (!DUK_ISFINITE(d)) { duk_push_null(ctx); return 1; } } duk_pop(ctx); duk_get_prop_stridx(ctx, -1, DUK_STRIDX_TO_ISO_STRING); duk_dup(ctx, -2); /* -> [ O toIsoString O ] */ duk_call_method(ctx, 0); return 1; }
/* XXX: the implementation now assumes "chained" bound functions, * whereas "collapsed" bound functions (where there is ever only * one bound function which directly points to a non-bound, final * function) would require a "collapsing" implementation which * merges argument lists etc here. */ DUK_INTERNAL duk_ret_t duk_bi_function_prototype_bind(duk_context *ctx) { duk_hobject *h_bound; duk_hobject *h_target; duk_idx_t nargs; duk_idx_t i; /* vararg function, careful arg handling (e.g. thisArg may not be present) */ nargs = duk_get_top(ctx); /* = 1 + arg count */ if (nargs == 0) { duk_push_undefined(ctx); nargs++; } DUK_ASSERT(nargs >= 1); duk_push_this(ctx); if (!duk_is_callable(ctx, -1)) { DUK_DDD(DUK_DDDPRINT("func is not callable")); goto type_error; } /* [ thisArg arg1 ... argN func ] (thisArg+args == nargs total) */ DUK_ASSERT_TOP(ctx, nargs + 1); /* create bound function object */ duk_push_object_helper(ctx, DUK_HOBJECT_FLAG_EXTENSIBLE | DUK_HOBJECT_FLAG_BOUND | DUK_HOBJECT_FLAG_CONSTRUCTABLE | DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_FUNCTION), DUK_BIDX_FUNCTION_PROTOTYPE); h_bound = duk_get_hobject(ctx, -1); DUK_ASSERT(h_bound != NULL); /* [ thisArg arg1 ... argN func boundFunc ] */ duk_dup(ctx, -2); /* func */ duk_xdef_prop_stridx(ctx, -2, DUK_STRIDX_INT_TARGET, DUK_PROPDESC_FLAGS_NONE); duk_dup(ctx, 0); /* thisArg */ duk_xdef_prop_stridx(ctx, -2, DUK_STRIDX_INT_THIS, DUK_PROPDESC_FLAGS_NONE); duk_push_array(ctx); /* [ thisArg arg1 ... argN func boundFunc argArray ] */ for (i = 0; i < nargs - 1; i++) { duk_dup(ctx, 1 + i); duk_put_prop_index(ctx, -2, i); } duk_xdef_prop_stridx(ctx, -2, DUK_STRIDX_INT_ARGS, DUK_PROPDESC_FLAGS_NONE); /* [ thisArg arg1 ... argN func boundFunc ] */ /* bound function 'length' property is interesting */ h_target = duk_get_hobject(ctx, -2); if (h_target == NULL || /* lightfunc */ DUK_HOBJECT_GET_CLASS_NUMBER(h_target) == DUK_HOBJECT_CLASS_FUNCTION) { /* For lightfuncs, simply read the virtual property. */ duk_int_t tmp; duk_get_prop_stridx(ctx, -2, DUK_STRIDX_LENGTH); tmp = duk_to_int(ctx, -1) - (nargs - 1); /* step 15.a */ duk_pop(ctx); duk_push_int(ctx, (tmp < 0 ? 0 : tmp)); } else { duk_push_int(ctx, 0); } duk_xdef_prop_stridx(ctx, -2, DUK_STRIDX_LENGTH, DUK_PROPDESC_FLAGS_NONE); /* attrs in E5 Section 15.3.5.1 */ /* caller and arguments must use the same thrower, [[ThrowTypeError]] */ duk_xdef_prop_stridx_thrower(ctx, -1, DUK_STRIDX_CALLER, DUK_PROPDESC_FLAGS_NONE); duk_xdef_prop_stridx_thrower(ctx, -1, DUK_STRIDX_LC_ARGUMENTS, DUK_PROPDESC_FLAGS_NONE); /* these non-standard properties are copied for convenience */ /* XXX: 'copy properties' API call? */ duk_get_prop_stridx(ctx, -2, DUK_STRIDX_NAME); duk_xdef_prop_stridx(ctx, -2, DUK_STRIDX_NAME, DUK_PROPDESC_FLAGS_WC); duk_get_prop_stridx(ctx, -2, DUK_STRIDX_FILE_NAME); duk_xdef_prop_stridx(ctx, -2, DUK_STRIDX_FILE_NAME, DUK_PROPDESC_FLAGS_WC); /* The 'strict' flag is copied to get the special [[Get]] of E5.1 * Section 15.3.5.4 to apply when a 'caller' value is a strict bound * function. Not sure if this is correct, because the specification * is a bit ambiguous on this point but it would make sense. */ if (h_target == NULL) { /* Lightfuncs are always strict. */ DUK_HOBJECT_SET_STRICT(h_bound); } else if (DUK_HOBJECT_HAS_STRICT(h_target)) { DUK_HOBJECT_SET_STRICT(h_bound); } DUK_DDD(DUK_DDDPRINT("created bound function: %!iT", (duk_tval *) duk_get_tval(ctx, -1))); return 1; type_error: return DUK_RET_TYPE_ERROR; }
DUK_LOCAL duk_ret_t duk__error_getter_helper(duk_context *ctx, duk_small_int_t output_type) { duk_hthread *thr = (duk_hthread *) ctx; duk_idx_t idx_td; duk_small_int_t i; /* traceback depth fits into 16 bits */ duk_small_int_t t; /* stack type fits into 16 bits */ duk_small_int_t count_func = 0; /* traceback depth ensures fits into 16 bits */ const char *str_tailcall = " tailcall"; const char *str_strict = " strict"; const char *str_construct = " construct"; const char *str_prevyield = " preventsyield"; const char *str_directeval = " directeval"; const char *str_empty = ""; DUK_ASSERT_TOP(ctx, 0); /* fixed arg count */ DUK_UNREF(thr); duk_push_this(ctx); duk_get_prop_stridx(ctx, -1, DUK_STRIDX_INT_TRACEDATA); idx_td = duk_get_top_index(ctx); duk_push_hstring_stridx(ctx, DUK_STRIDX_NEWLINE_4SPACE); duk_push_this(ctx); /* [ ... this tracedata sep this ] */ /* XXX: skip null filename? */ if (duk_check_type(ctx, idx_td, DUK_TYPE_OBJECT)) { /* Current tracedata contains 2 entries per callstack entry. */ for (i = 0; ; i += 2) { duk_int_t pc; duk_int_t line; duk_int_t flags; duk_double_t d; const char *funcname; const char *filename; duk_hobject *h_func; duk_hstring *h_name; duk_require_stack(ctx, 5); duk_get_prop_index(ctx, idx_td, i); duk_get_prop_index(ctx, idx_td, i + 1); d = duk_to_number(ctx, -1); pc = (duk_int_t) DUK_FMOD(d, DUK_DOUBLE_2TO32); flags = (duk_int_t) DUK_FLOOR(d / DUK_DOUBLE_2TO32); t = (duk_small_int_t) duk_get_type(ctx, -2); if (t == DUK_TYPE_OBJECT || t == DUK_TYPE_LIGHTFUNC) { /* * Ecmascript/native function call or lightfunc call */ count_func++; /* [ ... v1(func) v2(pc+flags) ] */ h_func = duk_get_hobject(ctx, -2); /* NULL for lightfunc */ duk_get_prop_stridx(ctx, -2, DUK_STRIDX_NAME); duk_get_prop_stridx(ctx, -3, DUK_STRIDX_FILE_NAME); #if defined(DUK_USE_PC2LINE) line = duk_hobject_pc2line_query(ctx, -4, (duk_uint_fast32_t) pc); #else line = 0; #endif /* [ ... v1 v2 name filename ] */ /* When looking for .fileName/.lineNumber, blame first * function which has a .fileName. */ if (duk_is_string(ctx, -1)) { if (output_type == DUK__OUTPUT_TYPE_FILENAME) { return 1; } else if (output_type == DUK__OUTPUT_TYPE_LINENUMBER) { duk_push_int(ctx, line); return 1; } } /* XXX: Change 'anon' handling here too, to use empty string for anonymous functions? */ /* XXX: Could be improved by coercing to a readable duk_tval (especially string escaping) */ h_name = duk_get_hstring(ctx, -2); /* may be NULL */ funcname = (h_name == NULL || h_name == DUK_HTHREAD_STRING_EMPTY_STRING(thr)) ? "[anon]" : (const char *) DUK_HSTRING_GET_DATA(h_name); filename = duk_get_string(ctx, -1); filename = filename ? filename : ""; DUK_ASSERT(funcname != NULL); DUK_ASSERT(filename != NULL); if (h_func == NULL) { duk_push_sprintf(ctx, "at %s light%s%s%s%s%s", (const char *) funcname, (const char *) ((flags & DUK_ACT_FLAG_STRICT) ? str_strict : str_empty), (const char *) ((flags & DUK_ACT_FLAG_TAILCALLED) ? str_tailcall : str_empty), (const char *) ((flags & DUK_ACT_FLAG_CONSTRUCT) ? str_construct : str_empty), (const char *) ((flags & DUK_ACT_FLAG_DIRECT_EVAL) ? str_directeval : str_empty), (const char *) ((flags & DUK_ACT_FLAG_PREVENT_YIELD) ? str_prevyield : str_empty)); } else if (DUK_HOBJECT_HAS_NATFUNC(h_func)) { duk_push_sprintf(ctx, "at %s (%s) native%s%s%s%s%s", (const char *) funcname, (const char *) filename, (const char *) ((flags & DUK_ACT_FLAG_STRICT) ? str_strict : str_empty), (const char *) ((flags & DUK_ACT_FLAG_TAILCALLED) ? str_tailcall : str_empty), (const char *) ((flags & DUK_ACT_FLAG_CONSTRUCT) ? str_construct : str_empty), (const char *) ((flags & DUK_ACT_FLAG_DIRECT_EVAL) ? str_directeval : str_empty), (const char *) ((flags & DUK_ACT_FLAG_PREVENT_YIELD) ? str_prevyield : str_empty)); } else { duk_push_sprintf(ctx, "at %s (%s:%ld)%s%s%s%s%s", (const char *) funcname, (const char *) filename, (long) line, (const char *) ((flags & DUK_ACT_FLAG_STRICT) ? str_strict : str_empty), (const char *) ((flags & DUK_ACT_FLAG_TAILCALLED) ? str_tailcall : str_empty), (const char *) ((flags & DUK_ACT_FLAG_CONSTRUCT) ? str_construct : str_empty), (const char *) ((flags & DUK_ACT_FLAG_DIRECT_EVAL) ? str_directeval : str_empty), (const char *) ((flags & DUK_ACT_FLAG_PREVENT_YIELD) ? str_prevyield : str_empty)); } duk_replace(ctx, -5); /* [ ... v1 v2 name filename str ] -> [ ... str v2 name filename ] */ duk_pop_n(ctx, 3); /* -> [ ... str ] */ } else if (t == DUK_TYPE_STRING) { /* * __FILE__ / __LINE__ entry, here 'pc' is line number directly. * Sometimes __FILE__ / __LINE__ is reported as the source for * the error (fileName, lineNumber), sometimes not. */ /* [ ... v1(filename) v2(line+flags) ] */ /* When looking for .fileName/.lineNumber, blame compilation * or C call site unless flagged not to do so. */ if (!(flags & DUK_TB_FLAG_NOBLAME_FILELINE)) { if (output_type == DUK__OUTPUT_TYPE_FILENAME) { duk_pop(ctx); return 1; } else if (output_type == DUK__OUTPUT_TYPE_LINENUMBER) { duk_push_int(ctx, pc); return 1; } } duk_push_sprintf(ctx, "at [anon] (%s:%ld) internal", (const char *) duk_get_string(ctx, -2), (long) pc); duk_replace(ctx, -3); /* [ ... v1 v2 str ] -> [ ... str v2 ] */ duk_pop(ctx); /* -> [ ... str ] */ } else { /* unknown, ignore */ duk_pop_2(ctx); break; } } if (count_func >= DUK_USE_TRACEBACK_DEPTH) { /* Possibly truncated; there is no explicit truncation * marker so this is the best we can do. */ duk_push_hstring_stridx(ctx, DUK_STRIDX_BRACKETED_ELLIPSIS); } } /* [ ... this tracedata sep this str1 ... strN ] */ if (output_type != DUK__OUTPUT_TYPE_TRACEBACK) { return 0; } else { /* The 'this' after 'sep' will get ToString() coerced by * duk_join() automatically. We don't want to do that * coercion when providing .fileName or .lineNumber (GH-254). */ duk_join(ctx, duk_get_top(ctx) - (idx_td + 2) /*count, not including sep*/); return 1; } }
int duk_builtin_duk_object_get_finalizer(duk_context *ctx) { (void) duk_require_hobject(ctx, 0); duk_get_prop_stridx(ctx, 0, DUK_STRIDX_INT_FINALIZER); return 1; }
/* FIXME: the implementation now assumes "chained" bound functions, * whereas "collapsed" bound functions (where there is ever only * one bound function which directly points to a non-bound, final * function) would require a "collapsing" implementation which * merges argument lists etc here. */ int duk_bi_function_prototype_bind(duk_context *ctx) { duk_hobject *h_target; int nargs; int i; /* FIXME: stack checks */ /* vararg function, careful arg handling (e.g. thisArg may not be present) */ nargs = duk_get_top(ctx); /* = 1 + arg count */ if (nargs == 0) { duk_push_undefined(ctx); nargs++; } DUK_ASSERT(nargs >= 1); duk_push_this(ctx); if (!duk_is_callable(ctx, -1)) { DUK_DDD(DUK_DDDPRINT("func is not callable")); goto type_error; } /* [ thisArg arg1 ... argN func ] (thisArg+args == nargs total) */ DUK_ASSERT_TOP(ctx, nargs + 1); /* create bound function object */ duk_push_object_helper(ctx, DUK_HOBJECT_FLAG_EXTENSIBLE | DUK_HOBJECT_FLAG_BOUND | DUK_HOBJECT_FLAG_CONSTRUCTABLE | DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_FUNCTION), DUK_BIDX_FUNCTION_PROTOTYPE); /* FIXME: check hobject flags (e.g. strict) */ /* [ thisArg arg1 ... argN func boundFunc ] */ duk_dup(ctx, -2); /* func */ duk_def_prop_stridx(ctx, -2, DUK_STRIDX_INT_TARGET, DUK_PROPDESC_FLAGS_NONE); duk_dup(ctx, 0); /* thisArg */ duk_def_prop_stridx(ctx, -2, DUK_STRIDX_INT_THIS, DUK_PROPDESC_FLAGS_NONE); duk_push_array(ctx); /* [ thisArg arg1 ... argN func boundFunc argArray ] */ for (i = 0; i < nargs - 1; i++) { duk_dup(ctx, 1 + i); duk_put_prop_index(ctx, -2, i); } duk_def_prop_stridx(ctx, -2, DUK_STRIDX_INT_ARGS, DUK_PROPDESC_FLAGS_NONE); /* [ thisArg arg1 ... argN func boundFunc ] */ /* bound function 'length' property is interesting */ h_target = duk_get_hobject(ctx, -2); DUK_ASSERT(h_target != NULL); if (DUK_HOBJECT_GET_CLASS_NUMBER(h_target) == DUK_HOBJECT_CLASS_FUNCTION) { int tmp; duk_get_prop_stridx(ctx, -2, DUK_STRIDX_LENGTH); tmp = duk_to_int(ctx, -1) - (nargs - 1); /* step 15.a */ duk_pop(ctx); duk_push_int(ctx, (tmp < 0 ? 0 : tmp)); } else { duk_push_int(ctx, 0); } duk_def_prop_stridx(ctx, -2, DUK_STRIDX_LENGTH, DUK_PROPDESC_FLAGS_NONE); /* attrs in E5 Section 15.3.5.1 */ /* caller and arguments must use the same thrower, [[ThrowTypeError]] */ duk_def_prop_stridx_thrower(ctx, -1, DUK_STRIDX_CALLER, DUK_PROPDESC_FLAGS_NONE); duk_def_prop_stridx_thrower(ctx, -1, DUK_STRIDX_LC_ARGUMENTS, DUK_PROPDESC_FLAGS_NONE); /* these non-standard properties are copied for convenience */ /* FIXME: 'copy properties' API call? */ duk_get_prop_stridx(ctx, -2, DUK_STRIDX_NAME); duk_def_prop_stridx(ctx, -2, DUK_STRIDX_NAME, DUK_PROPDESC_FLAGS_WC); duk_get_prop_stridx(ctx, -2, DUK_STRIDX_FILE_NAME); duk_def_prop_stridx(ctx, -2, DUK_STRIDX_FILE_NAME, DUK_PROPDESC_FLAGS_WC); DUK_DDD(DUK_DDDPRINT("created bound function: %!iT", duk_get_tval(ctx, -1))); return 1; type_error: return DUK_RET_TYPE_ERROR; }
DUK_INTERNAL duk_ret_t duk_bi_regexp_prototype_to_string(duk_context *ctx) { duk_hstring *h_bc; duk_small_int_t re_flags; #if 0 /* A little tricky string approach to provide the flags string. * This depends on the specific flag values in duk_regexp.h, * which needs to be asserted for. In practice this doesn't * produce more compact code than the easier approach in use. */ const char *flag_strings = "gim\0gi\0gm\0g\0"; duk_uint8_t flag_offsets[8] = { (duk_uint8_t) 3, /* flags: "" */ (duk_uint8_t) 10, /* flags: "g" */ (duk_uint8_t) 5, /* flags: "i" */ (duk_uint8_t) 4, /* flags: "gi" */ (duk_uint8_t) 2, /* flags: "m" */ (duk_uint8_t) 7, /* flags: "gm" */ (duk_uint8_t) 1, /* flags: "im" */ (duk_uint8_t) 0, /* flags: "gim" */ }; DUK_ASSERT(DUK_RE_FLAG_GLOBAL == 1); DUK_ASSERT(DUK_RE_FLAG_IGNORE_CASE == 2); DUK_ASSERT(DUK_RE_FLAG_MULTILINE == 4); #endif duk__get_this_regexp(ctx); /* [ regexp ] */ duk_get_prop_stridx(ctx, 0, DUK_STRIDX_SOURCE); duk_get_prop_stridx(ctx, 0, DUK_STRIDX_INT_BYTECODE); h_bc = duk_get_hstring(ctx, -1); DUK_ASSERT(h_bc != NULL); DUK_ASSERT(DUK_HSTRING_GET_BYTELEN(h_bc) >= 1); DUK_ASSERT(DUK_HSTRING_GET_CHARLEN(h_bc) >= 1); DUK_ASSERT(DUK_HSTRING_GET_DATA(h_bc)[0] < 0x80); re_flags = (duk_small_int_t) DUK_HSTRING_GET_DATA(h_bc)[0]; /* [ regexp source bytecode ] */ #if 1 /* This is a cleaner approach and also produces smaller code than * the other alternative. Use duk_require_string() for format * safety (although the source property should always exist). */ duk_push_sprintf(ctx, "/%s/%s%s%s", (const char *) duk_require_string(ctx, -2), /* require to be safe */ (re_flags & DUK_RE_FLAG_GLOBAL) ? "g" : "", (re_flags & DUK_RE_FLAG_IGNORE_CASE) ? "i" : "", (re_flags & DUK_RE_FLAG_MULTILINE) ? "m" : ""); #else /* This should not be necessary because no-one should tamper with the * regexp bytecode, but is prudent to avoid potential segfaults if that * were to happen for some reason. */ re_flags &= 0x07; DUK_ASSERT(re_flags >= 0 && re_flags <= 7); /* three flags */ duk_push_sprintf(ctx, "/%s/%s", (const char *) duk_require_string(ctx, -2), (const char *) (flag_strings + flag_offsets[re_flags])); #endif return 1; }
DUK_LOCAL void duk__add_fileline(duk_hthread *thr, duk_hthread *thr_callstack, const char *c_filename, duk_int_t c_line, duk_bool_t noblame_fileline) { duk_context *ctx; #if defined(DUK_USE_ASSERTIONS) duk_int_t entry_top; #endif ctx = (duk_context *) thr; #if defined(DUK_USE_ASSERTIONS) entry_top = duk_get_top(ctx); #endif /* * If tracebacks are disabled, 'fileName' and 'lineNumber' are added * as plain own properties. Since Error.prototype has accessors of * the same name, we need to define own properties directly (cannot * just use e.g. duk_put_prop_stridx). Existing properties are not * overwritten in case they already exist. */ if (thr->compile_ctx != NULL && thr->compile_ctx->h_filename != NULL) { /* Compiler SyntaxError (or other error) gets the primary blame. * Currently no flag to prevent blaming. */ duk_push_uint(ctx, (duk_uint_t) thr->compile_ctx->curr_token.start_line); duk_push_hstring(ctx, thr->compile_ctx->h_filename); } else if (c_filename && !noblame_fileline) { /* C call site gets blamed next, unless flagged not to do so. * XXX: file/line is disabled in minimal builds, so disable this * too when appropriate. */ duk_push_int(ctx, c_line); duk_push_string(ctx, c_filename); } else { /* Finally, blame the innermost callstack entry which has a * .fileName property. */ duk_small_uint_t depth; duk_int_t i, i_min; duk_uint32_t ecma_line; 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); 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_activation *act; duk_hobject *func; duk_uint32_t pc; act = thr_callstack->callstack + i; DUK_ASSERT(act >= thr_callstack->callstack && act < thr_callstack->callstack + thr_callstack->callstack_size); func = DUK_ACT_GET_FUNC(act); if (func == NULL) { /* Lightfunc, not blamed now. */ continue; } /* PC points to next instruction, find offending PC, * PC == 0 for native code. */ pc = duk_hthread_get_act_prev_pc(thr, act); /* thr argument only used for thr->heap, so specific thread doesn't matter */ 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 */ act = NULL; /* invalidated by pushes, so get out of the way */ duk_push_hobject(ctx, func); /* [ ... error func ] */ duk_get_prop_stridx(ctx, -1, DUK_STRIDX_FILE_NAME); if (!duk_is_string(ctx, -1)) { duk_pop_2(ctx); continue; } /* [ ... error func fileName ] */ ecma_line = 0; #if defined(DUK_USE_PC2LINE) if (DUK_HOBJECT_IS_COMPILEDFUNCTION(func)) { ecma_line = duk_hobject_pc2line_query(ctx, -2, (duk_uint_fast32_t) pc); } else { /* Native function, no relevant lineNumber. */ } #endif /* DUK_USE_PC2LINE */ duk_push_u32(ctx, ecma_line); /* [ ... error func fileName lineNumber ] */ duk_replace(ctx, -3); /* [ ... error lineNumber fileName ] */ goto define_props; } /* No activation matches, use undefined for both .fileName and * .lineNumber (matches what we do with a _Tracedata based * no-match lookup. */ duk_push_undefined(ctx); duk_push_undefined(ctx); } define_props: /* [ ... error lineNumber fileName ] */ #if defined(DUK_USE_ASSERTIONS) DUK_ASSERT(duk_get_top(ctx) == entry_top + 2); #endif duk_xdef_prop_stridx(ctx, -3, DUK_STRIDX_FILE_NAME, DUK_PROPDESC_FLAGS_WC | DUK_PROPDESC_FLAG_NO_OVERWRITE); duk_xdef_prop_stridx(ctx, -2, DUK_STRIDX_LINE_NUMBER, DUK_PROPDESC_FLAGS_WC | DUK_PROPDESC_FLAG_NO_OVERWRITE); }
void duk_hobject_enumerator_create(duk_context *ctx, duk_small_uint_t enum_flags) { duk_hthread *thr = (duk_hthread *) ctx; duk_hobject *enum_target; duk_hobject *curr; duk_hobject *res; #if defined(DUK_USE_ES6_PROXY) duk_hobject *h_proxy_target; duk_hobject *h_proxy_handler; duk_hobject *h_trap_result; #endif duk_uint_fast32_t i, len; /* used for array, stack, and entry indices */ DUK_ASSERT(ctx != NULL); DUK_DDD(DUK_DDDPRINT("create enumerator, stack top: %ld", (long) duk_get_top(ctx))); enum_target = duk_require_hobject(ctx, -1); DUK_ASSERT(enum_target != NULL); duk_push_object_internal(ctx); res = duk_require_hobject(ctx, -1); DUK_DDD(DUK_DDDPRINT("created internal object")); /* [enum_target res] */ /* Target must be stored so that we can recheck whether or not * keys still exist when we enumerate. This is not done if the * enumeration result comes from a proxy trap as there is no * real object to check against. */ duk_push_hobject(ctx, enum_target); duk_put_prop_stridx(ctx, -2, DUK_STRIDX_INT_TARGET); /* Initialize index so that we skip internal control keys. */ duk_push_int(ctx, DUK__ENUM_START_INDEX); duk_put_prop_stridx(ctx, -2, DUK_STRIDX_INT_NEXT); /* * Proxy object handling */ #if defined(DUK_USE_ES6_PROXY) if (DUK_LIKELY((enum_flags & DUK_ENUM_NO_PROXY_BEHAVIOR) != 0)) { goto skip_proxy; } if (DUK_LIKELY(!duk_hobject_proxy_check(thr, enum_target, &h_proxy_target, &h_proxy_handler))) { goto skip_proxy; } DUK_DDD(DUK_DDDPRINT("proxy enumeration")); duk_push_hobject(ctx, h_proxy_handler); if (!duk_get_prop_stridx(ctx, -1, DUK_STRIDX_ENUMERATE)) { /* No need to replace the 'enum_target' value in stack, only the * enum_target reference. This also ensures that the original * enum target is reachable, which keeps the proxy and the proxy * target reachable. We do need to replace the internal _target. */ DUK_DDD(DUK_DDDPRINT("no enumerate trap, enumerate proxy target instead")); DUK_DDD(DUK_DDDPRINT("h_proxy_target=%!O", (duk_heaphdr *) h_proxy_target)); enum_target = h_proxy_target; duk_push_hobject(ctx, enum_target); /* -> [ ... enum_target res handler undefined target ] */ duk_put_prop_stridx(ctx, -4, DUK_STRIDX_INT_TARGET); duk_pop_2(ctx); /* -> [ ... enum_target res ] */ goto skip_proxy; } /* [ ... enum_target res handler trap ] */ duk_insert(ctx, -2); duk_push_hobject(ctx, h_proxy_target); /* -> [ ... enum_target res trap handler target ] */ duk_call_method(ctx, 1 /*nargs*/); /* -> [ ... enum_target res trap_result ] */ h_trap_result = duk_require_hobject(ctx, -1); DUK_UNREF(h_trap_result); /* Copy trap result keys into the enumerator object. */ len = (duk_uint_fast32_t) duk_get_length(ctx, -1); for (i = 0; i < len; i++) { /* XXX: not sure what the correct semantic details are here, * e.g. handling of missing values (gaps), handling of non-array * trap results, etc. * * For keys, we simply skip non-string keys which seems to be * consistent with how e.g. Object.keys() will process proxy trap * results (ES6 draft, Section 19.1.2.14). */ if (duk_get_prop_index(ctx, -1, i) && duk_is_string(ctx, -1)) { /* [ ... enum_target res trap_result val ] */ duk_push_true(ctx); /* [ ... enum_target res trap_result val true ] */ duk_put_prop(ctx, -4); } else { duk_pop(ctx); } } /* [ ... enum_target res trap_result ] */ duk_pop(ctx); duk_remove(ctx, -2); /* [ ... res ] */ /* The internal _target property is kept pointing to the original * enumeration target (the proxy object), so that the enumerator * 'next' operation can read property values if so requested. The * fact that the _target is a proxy disables key existence check * during enumeration. */ DUK_DDD(DUK_DDDPRINT("proxy enumeration, final res: %!O", (duk_heaphdr *) res)); goto compact_and_return; skip_proxy: #endif /* DUK_USE_ES6_PROXY */ curr = enum_target; while (curr) { /* * Virtual properties. * * String and buffer indices are virtual and always enumerable, * 'length' is virtual and non-enumerable. Array and arguments * object props have special behavior but are concrete. */ if (DUK_HOBJECT_HAS_EXOTIC_STRINGOBJ(curr) || DUK_HOBJECT_HAS_EXOTIC_BUFFEROBJ(curr)) { /* String and buffer enumeration behavior is identical now, * so use shared handler. */ if (DUK_HOBJECT_HAS_EXOTIC_STRINGOBJ(curr)) { duk_hstring *h_val; h_val = duk_hobject_get_internal_value_string(thr->heap, curr); DUK_ASSERT(h_val != NULL); /* string objects must not created without internal value */ len = (duk_uint_fast32_t) DUK_HSTRING_GET_CHARLEN(h_val); } else { duk_hbuffer *h_val; DUK_ASSERT(DUK_HOBJECT_HAS_EXOTIC_BUFFEROBJ(curr)); h_val = duk_hobject_get_internal_value_buffer(thr->heap, curr); DUK_ASSERT(h_val != NULL); /* buffer objects must not created without internal value */ len = (duk_uint_fast32_t) DUK_HBUFFER_GET_SIZE(h_val); } for (i = 0; i < len; i++) { duk_hstring *k; k = duk_heap_string_intern_u32_checked(thr, i); DUK_ASSERT(k); duk_push_hstring(ctx, k); duk_push_true(ctx); /* [enum_target res key true] */ duk_put_prop(ctx, -3); /* [enum_target res] */ } /* 'length' property is not enumerable, but is included if * non-enumerable properties are requested. */ if (enum_flags & DUK_ENUM_INCLUDE_NONENUMERABLE) { duk_push_hstring_stridx(ctx, DUK_STRIDX_LENGTH); duk_push_true(ctx); duk_put_prop(ctx, -3); } } else if (DUK_HOBJECT_HAS_EXOTIC_DUKFUNC(curr)) { if (enum_flags & DUK_ENUM_INCLUDE_NONENUMERABLE) { duk_push_hstring_stridx(ctx, DUK_STRIDX_LENGTH); duk_push_true(ctx); duk_put_prop(ctx, -3); } } /* * Array part * * Note: ordering between array and entry part must match 'abandon array' * behavior in duk_hobject_props.c: key order after an array is abandoned * must be the same. */ for (i = 0; i < (duk_uint_fast32_t) curr->a_size; i++) { duk_hstring *k; duk_tval *tv; tv = DUK_HOBJECT_A_GET_VALUE_PTR(curr, i); if (DUK_TVAL_IS_UNDEFINED_UNUSED(tv)) { continue; } k = duk_heap_string_intern_u32_checked(thr, i); DUK_ASSERT(k); duk_push_hstring(ctx, k); duk_push_true(ctx); /* [enum_target res key true] */ duk_put_prop(ctx, -3); /* [enum_target res] */ } /* * Entries part */ for (i = 0; i < (duk_uint_fast32_t) curr->e_next; i++) { duk_hstring *k; k = DUK_HOBJECT_E_GET_KEY(curr, i); if (!k) { continue; } if (!DUK_HOBJECT_E_SLOT_IS_ENUMERABLE(curr, i) && !(enum_flags & DUK_ENUM_INCLUDE_NONENUMERABLE)) { continue; } if (DUK_HSTRING_HAS_INTERNAL(k) && !(enum_flags & DUK_ENUM_INCLUDE_INTERNAL)) { continue; } if ((enum_flags & DUK_ENUM_ARRAY_INDICES_ONLY) && (DUK_HSTRING_GET_ARRIDX_SLOW(k) == DUK_HSTRING_NO_ARRAY_INDEX)) { continue; } DUK_ASSERT(DUK_HOBJECT_E_SLOT_IS_ACCESSOR(curr, i) || !DUK_TVAL_IS_UNDEFINED_UNUSED(&DUK_HOBJECT_E_GET_VALUE_PTR(curr, i)->v)); duk_push_hstring(ctx, k); duk_push_true(ctx); /* [enum_target res key true] */ duk_put_prop(ctx, -3); /* [enum_target res] */ } if (enum_flags & DUK_ENUM_OWN_PROPERTIES_ONLY) { break; } curr = curr->prototype; } /* [enum_target res] */ duk_remove(ctx, -2); /* [res] */ if ((enum_flags & (DUK_ENUM_ARRAY_INDICES_ONLY | DUK_ENUM_SORT_ARRAY_INDICES)) == (DUK_ENUM_ARRAY_INDICES_ONLY | DUK_ENUM_SORT_ARRAY_INDICES)) { /* * Some E5/E5.1 algorithms require that array indices are iterated * in a strictly ascending order. This is the case for e.g. * Array.prototype.forEach() and JSON.stringify() PropertyList * handling. * * To ensure this property for arrays with an array part (and * arbitrary objects too, since e.g. forEach() can be applied * to an array), the caller can request that we sort the keys * here. */ /* XXX: avoid this at least when enum_target is an Array, it has an * array part, and no ancestor properties were included? Not worth * it for JSON, but maybe worth it for forEach(). */ /* XXX: may need a 'length' filter for forEach() */ DUK_DDD(DUK_DDDPRINT("sort array indices by caller request")); duk__sort_array_indices(res); } #if defined(DUK_USE_ES6_PROXY) compact_and_return: #endif /* compact; no need to seal because object is internal */ duk_hobject_compact_props(thr, res); DUK_DDD(DUK_DDDPRINT("created enumerator object: %!iT", (duk_tval *) duk_get_tval(ctx, -1))); }
/* XXX: these could be implemented as macros calling an internal function * directly. * XXX: same issue as with Duktape.fin: there's no way to delete the property * now (just set it to undefined). */ DUK_EXTERNAL void duk_get_finalizer(duk_context *ctx, duk_idx_t index) { DUK_ASSERT_CTX_VALID(ctx); duk_get_prop_stridx(ctx, index, DUK_STRIDX_INT_FINALIZER); }
void duk_err_augment_error(duk_hthread *thr, duk_hthread *thr_callstack, int err_index, const char *filename, int line, int noblame_fileline) { duk_context *ctx = (duk_context *) thr; duk_hobject *obj; DUK_ASSERT(thr != NULL); DUK_ASSERT(thr_callstack != NULL); DUK_ASSERT(ctx != NULL); err_index = duk_require_normalize_index(ctx, err_index); /* * Criteria for augmenting: * * - augmentation enabled in build (naturally) * - error value is an extensible object * - error value internal prototype chain contains the built-in * Error prototype object (i.e. 'val instanceof Error') */ obj = duk_require_hobject(ctx, err_index); if (!obj) { DUK_DDDPRINT("error value not an object, not augmented"); return; } if (!DUK_HOBJECT_HAS_EXTENSIBLE(obj)) { DUK_DDDPRINT("error value not extensible, not augmented"); return; } if (!duk_hobject_prototype_chain_contains(thr, obj, thr->builtins[DUK_BIDX_ERROR_PROTOTYPE])) { DUK_DDDPRINT("error value not inherited from Error, not augmented"); return; } /* Yes, augment error. */ #ifdef DUK_USE_TRACEBACKS /* * If tracebacks are enabled, the 'tracedata' property is the only * thing we need: 'fileName' and 'lineNumber' are virtual properties * which use 'tracedata'. */ if (duk_hobject_hasprop_raw(thr, obj, DUK_HTHREAD_STRING_TRACEDATA(thr))) { DUK_DDDPRINT("error value already has a 'traceback' property, not modifying it"); } else { add_traceback(thr, thr_callstack, obj, err_index, filename, line, noblame_fileline); } #else /* * If tracebacks are disabled, 'fileName' and 'lineNumber' are added * as plain own properties. Since Error.prototype has accessors of * the same name, we need to define own properties directly (cannot * just use e.g. duk_put_prop_stridx). Existing properties are not * overwritten in case they already exist. */ if (filename && !noblame_fileline) { /* FIXME: file/line is disabled in minimal builds, so disable this too * when appropriate. */ duk_push_string(ctx, filename); duk_def_prop_stridx(ctx, err_index, DUK_STRIDX_FILE_NAME, DUK_PROPDESC_FLAGS_WC | DUK_PROPDESC_FLAG_NO_OVERWRITE); duk_push_int(ctx, line); duk_def_prop_stridx(ctx, err_index, DUK_STRIDX_LINE_NUMBER, DUK_PROPDESC_FLAGS_WC | DUK_PROPDESC_FLAG_NO_OVERWRITE); } else if (thr_callstack->callstack_top > 0) { duk_activation *act; duk_hobject *func; duk_hbuffer *pc2line; act = thr_callstack->callstack + thr_callstack->callstack_top - 1; DUK_ASSERT(act >= thr_callstack->callstack && act < thr_callstack->callstack + thr_callstack->callstack_size); func = act->func; if (func) { int pc; duk_uint32_t line; /* PC points to next instruction, find offending PC. Note that * PC == 0 for native code. */ pc = act->pc; if (pc > 0) { pc--; } DUK_ASSERT(pc >= 0 && (double) pc < DUK_DOUBLE_2TO32); /* assume PC is at most 32 bits and non-negative */ act = NULL; /* invalidated by pushes, so get out of the way */ duk_push_hobject(ctx, func); duk_get_prop_stridx(ctx, -1, DUK_STRIDX_FILE_NAME); duk_def_prop_stridx(ctx, err_index, DUK_STRIDX_FILE_NAME, DUK_PROPDESC_FLAGS_WC | DUK_PROPDESC_FLAG_NO_OVERWRITE); if (DUK_HOBJECT_IS_COMPILEDFUNCTION(func)) { #if 0 duk_push_number(ctx, pc); duk_def_prop_stridx(ctx, err_index, DUK_STRIDX_PC, DUK_PROPDESC_FLAGS_WC | DUK_PROPDESC_FLAGS_NO_OVERWRITE); #endif duk_get_prop_stridx(ctx, -1, DUK_STRIDX_INT_PC2LINE); if (duk_is_buffer(ctx, -1)) { pc2line = duk_get_hbuffer(ctx, -1); DUK_ASSERT(pc2line != NULL); DUK_ASSERT(!DUK_HBUFFER_HAS_DYNAMIC(pc2line)); line = duk_hobject_pc2line_query((duk_hbuffer_fixed *) pc2line, (duk_uint_fast32_t) pc); duk_push_number(ctx, (double) line); /* FIXME: u32 */ duk_def_prop_stridx(ctx, err_index, DUK_STRIDX_LINE_NUMBER, DUK_PROPDESC_FLAGS_WC | DUK_PROPDESC_FLAG_NO_OVERWRITE); } duk_pop(ctx); } else { /* Native function, no relevant lineNumber. */ } duk_pop(ctx); } } #endif /* DUK_USE_TRACEBACKS */ }
int duk_bi_buffer_constructor(duk_context *ctx) { duk_size_t buf_size; duk_small_int_t buf_dynamic; duk_uint8_t *buf_data; const duk_uint8_t *src_data; duk_hobject *h_obj; /* * Constructor arguments are currently somewhat compatible with * (keep it that way if possible): * * http://nodejs.org/api/buffer.html * */ buf_dynamic = duk_get_boolean(ctx, 1); /* default to false */ switch (duk_get_type(ctx, 0)) { case DUK_TYPE_NUMBER: /* new buffer of specified size */ buf_size = (duk_size_t) duk_to_int(ctx, 0); (void) duk_push_buffer(ctx, buf_size, buf_dynamic); break; case DUK_TYPE_BUFFER: /* return input buffer, converted to a Buffer object if called as a * constructor (no change if called as a function). */ duk_set_top(ctx, 1); break; case DUK_TYPE_STRING: /* new buffer with string contents */ src_data = (const duk_uint8_t *) duk_get_lstring(ctx, 0, &buf_size); DUK_ASSERT(src_data != NULL); /* even for zero-length string */ buf_data = (duk_uint8_t *) duk_push_buffer(ctx, buf_size, buf_dynamic); DUK_MEMCPY((void *) buf_data, (const void *) src_data, (size_t) buf_size); break; case DUK_TYPE_OBJECT: /* Buffer object: get the plain buffer inside. If called as as * constructor, a new Buffer object pointing to the same plain * buffer is created below. */ h_obj = duk_get_hobject(ctx, 0); DUK_ASSERT(h_obj != NULL); if (DUK_HOBJECT_GET_CLASS_NUMBER(h_obj) != DUK_HOBJECT_CLASS_BUFFER) { return DUK_RET_TYPE_ERROR; } duk_get_prop_stridx(ctx, 0, DUK_STRIDX_INT_VALUE); DUK_ASSERT(duk_is_buffer(ctx, -1)); break; case DUK_TYPE_NONE: default: return DUK_RET_TYPE_ERROR; } /* stack is unbalanced, but: [ <something> buf ] */ if (duk_is_constructor_call(ctx)) { duk_push_object_helper(ctx, DUK_HOBJECT_FLAG_EXTENSIBLE | DUK_HOBJECT_FLAG_EXOTIC_BUFFEROBJ | DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_BUFFER), DUK_BIDX_BUFFER_PROTOTYPE); /* Buffer object internal value is immutable */ duk_dup(ctx, -2); duk_def_prop_stridx(ctx, -2, DUK_STRIDX_INT_VALUE, DUK_PROPDESC_FLAGS_NONE); } /* Note: unbalanced stack on purpose */ return 1; }
DUK_INTERNAL duk_ret_t duk_bi_string_prototype_replace(duk_context *ctx) { duk_hthread *thr = (duk_hthread *) ctx; duk_hstring *h_input; duk_hstring *h_match; duk_hstring *h_search; duk_hobject *h_re; duk_bufwriter_ctx bw_alloc; duk_bufwriter_ctx *bw; #ifdef DUK_USE_REGEXP_SUPPORT duk_bool_t is_regexp; duk_bool_t is_global; #endif duk_bool_t is_repl_func; duk_uint32_t match_start_coff, match_start_boff; #ifdef DUK_USE_REGEXP_SUPPORT duk_int_t match_caps; #endif duk_uint32_t prev_match_end_boff; const duk_uint8_t *r_start, *r_end, *r; /* repl string scan */ duk_size_t tmp_sz; DUK_ASSERT_TOP(ctx, 2); h_input = duk_push_this_coercible_to_string(ctx); DUK_ASSERT(h_input != NULL); bw = &bw_alloc; DUK_BW_INIT_PUSHBUF(thr, bw, DUK_HSTRING_GET_BYTELEN(h_input)); /* input size is good output starting point */ DUK_ASSERT_TOP(ctx, 4); /* stack[0] = search value * stack[1] = replace value * stack[2] = input string * stack[3] = result buffer */ h_re = duk_get_hobject_with_class(ctx, 0, DUK_HOBJECT_CLASS_REGEXP); if (h_re) { #ifdef DUK_USE_REGEXP_SUPPORT is_regexp = 1; is_global = duk_get_prop_stridx_boolean(ctx, 0, DUK_STRIDX_GLOBAL, NULL); if (is_global) { /* start match from beginning */ duk_push_int(ctx, 0); duk_put_prop_stridx(ctx, 0, DUK_STRIDX_LAST_INDEX); } #else /* DUK_USE_REGEXP_SUPPORT */ return DUK_RET_UNSUPPORTED_ERROR; #endif /* DUK_USE_REGEXP_SUPPORT */ } else { duk_to_string(ctx, 0); #ifdef DUK_USE_REGEXP_SUPPORT is_regexp = 0; is_global = 0; #endif } if (duk_is_function(ctx, 1)) { is_repl_func = 1; r_start = NULL; r_end = NULL; } else { duk_hstring *h_repl; is_repl_func = 0; h_repl = duk_to_hstring(ctx, 1); DUK_ASSERT(h_repl != NULL); r_start = DUK_HSTRING_GET_DATA(h_repl); r_end = r_start + DUK_HSTRING_GET_BYTELEN(h_repl); } prev_match_end_boff = 0; for (;;) { /* * If matching with a regexp: * - non-global RegExp: lastIndex not touched on a match, zeroed * on a non-match * - global RegExp: on match, lastIndex will be updated by regexp * executor to point to next char after the matching part (so that * characters in the matching part are not matched again) * * If matching with a string: * - always non-global match, find first occurrence * * We need: * - The character offset of start-of-match for the replacer function * - The byte offsets for start-of-match and end-of-match to implement * the replacement values $&, $`, and $', and to copy non-matching * input string portions (including header and trailer) verbatim. * * NOTE: the E5.1 specification is a bit vague how the RegExp should * behave in the replacement process; e.g. is matching done first for * all matches (in the global RegExp case) before any replacer calls * are made? See: test-bi-string-proto-replace.js for discussion. */ DUK_ASSERT_TOP(ctx, 4); #ifdef DUK_USE_REGEXP_SUPPORT if (is_regexp) { duk_dup(ctx, 0); duk_dup(ctx, 2); duk_regexp_match(thr); /* [ ... regexp input ] -> [ res_obj ] */ if (!duk_is_object(ctx, -1)) { duk_pop(ctx); break; } duk_get_prop_stridx(ctx, -1, DUK_STRIDX_INDEX); DUK_ASSERT(duk_is_number(ctx, -1)); match_start_coff = duk_get_int(ctx, -1); duk_pop(ctx); duk_get_prop_index(ctx, -1, 0); DUK_ASSERT(duk_is_string(ctx, -1)); h_match = duk_get_hstring(ctx, -1); DUK_ASSERT(h_match != NULL); duk_pop(ctx); /* h_match is borrowed, remains reachable through match_obj */ if (DUK_HSTRING_GET_BYTELEN(h_match) == 0) { /* This should be equivalent to match() algorithm step 8.f.iii.2: * detect an empty match and allow it, but don't allow it twice. */ duk_uint32_t last_index; duk_get_prop_stridx(ctx, 0, DUK_STRIDX_LAST_INDEX); last_index = (duk_uint32_t) duk_get_uint(ctx, -1); DUK_DDD(DUK_DDDPRINT("empty match, bump lastIndex: %ld -> %ld", (long) last_index, (long) (last_index + 1))); duk_pop(ctx); duk_push_int(ctx, last_index + 1); duk_put_prop_stridx(ctx, 0, DUK_STRIDX_LAST_INDEX); } DUK_ASSERT(duk_get_length(ctx, -1) <= DUK_INT_MAX); /* string limits */ match_caps = (duk_int_t) duk_get_length(ctx, -1); } else { #else /* DUK_USE_REGEXP_SUPPORT */ { /* unconditionally */ #endif /* DUK_USE_REGEXP_SUPPORT */ const duk_uint8_t *p_start, *p_end, *p; /* input string scan */ const duk_uint8_t *q_start; /* match string */ duk_size_t q_blen; #ifdef DUK_USE_REGEXP_SUPPORT DUK_ASSERT(!is_global); /* single match always */ #endif p_start = DUK_HSTRING_GET_DATA(h_input); p_end = p_start + DUK_HSTRING_GET_BYTELEN(h_input); p = p_start; h_search = duk_get_hstring(ctx, 0); DUK_ASSERT(h_search != NULL); q_start = DUK_HSTRING_GET_DATA(h_search); q_blen = (duk_size_t) DUK_HSTRING_GET_BYTELEN(h_search); p_end -= q_blen; /* ensure full memcmp() fits in while */ match_start_coff = 0; while (p <= p_end) { DUK_ASSERT(p + q_blen <= DUK_HSTRING_GET_DATA(h_input) + DUK_HSTRING_GET_BYTELEN(h_input)); if (DUK_MEMCMP((void *) p, (void *) q_start, (size_t) q_blen) == 0) { duk_dup(ctx, 0); h_match = duk_get_hstring(ctx, -1); DUK_ASSERT(h_match != NULL); #ifdef DUK_USE_REGEXP_SUPPORT match_caps = 0; #endif goto found; } /* track utf-8 non-continuation bytes */ if ((p[0] & 0xc0) != 0x80) { match_start_coff++; } p++; } /* not found */ break; } found: /* stack[0] = search value * stack[1] = replace value * stack[2] = input string * stack[3] = result buffer * stack[4] = regexp match OR match string */ match_start_boff = duk_heap_strcache_offset_char2byte(thr, h_input, match_start_coff); tmp_sz = (duk_size_t) (match_start_boff - prev_match_end_boff); DUK_BW_WRITE_ENSURE_BYTES(thr, bw, DUK_HSTRING_GET_DATA(h_input) + prev_match_end_boff, tmp_sz); prev_match_end_boff = match_start_boff + DUK_HSTRING_GET_BYTELEN(h_match); if (is_repl_func) { duk_idx_t idx_args; duk_hstring *h_repl; /* regexp res_obj is at index 4 */ duk_dup(ctx, 1); idx_args = duk_get_top(ctx); #ifdef DUK_USE_REGEXP_SUPPORT if (is_regexp) { duk_int_t idx; duk_require_stack(ctx, match_caps + 2); for (idx = 0; idx < match_caps; idx++) { /* match followed by capture(s) */ duk_get_prop_index(ctx, 4, idx); } } else { #else /* DUK_USE_REGEXP_SUPPORT */ { /* unconditionally */ #endif /* DUK_USE_REGEXP_SUPPORT */ /* match == search string, by definition */ duk_dup(ctx, 0); } duk_push_int(ctx, match_start_coff); duk_dup(ctx, 2); /* [ ... replacer match [captures] match_char_offset input ] */ duk_call(ctx, duk_get_top(ctx) - idx_args); h_repl = duk_to_hstring(ctx, -1); /* -> [ ... repl_value ] */ DUK_ASSERT(h_repl != NULL); DUK_BW_WRITE_ENSURE_HSTRING(thr, bw, h_repl); duk_pop(ctx); /* repl_value */ } else { r = r_start; while (r < r_end) { duk_int_t ch1; duk_int_t ch2; #ifdef DUK_USE_REGEXP_SUPPORT duk_int_t ch3; #endif duk_size_t left; ch1 = *r++; if (ch1 != DUK_ASC_DOLLAR) { goto repl_write; } left = r_end - r; if (left <= 0) { goto repl_write; } ch2 = r[0]; switch ((int) ch2) { case DUK_ASC_DOLLAR: { ch1 = (1 << 8) + DUK_ASC_DOLLAR; goto repl_write; } case DUK_ASC_AMP: { DUK_BW_WRITE_ENSURE_HSTRING(thr, bw, h_match); r++; continue; } case DUK_ASC_GRAVE: { tmp_sz = (duk_size_t) match_start_boff; DUK_BW_WRITE_ENSURE_BYTES(thr, bw, DUK_HSTRING_GET_DATA(h_input), tmp_sz); r++; continue; } case DUK_ASC_SINGLEQUOTE: { duk_uint32_t match_end_boff; /* Use match charlen instead of bytelen, just in case the input and * match codepoint encodings would have different lengths. */ match_end_boff = duk_heap_strcache_offset_char2byte(thr, h_input, match_start_coff + DUK_HSTRING_GET_CHARLEN(h_match)); tmp_sz = (duk_size_t) (DUK_HSTRING_GET_BYTELEN(h_input) - match_end_boff); DUK_BW_WRITE_ENSURE_BYTES(thr, bw, DUK_HSTRING_GET_DATA(h_input) + match_end_boff, tmp_sz); r++; continue; } default: { #ifdef DUK_USE_REGEXP_SUPPORT duk_int_t capnum, captmp, capadv; /* XXX: optional check, match_caps is zero if no regexp, * so dollar will be interpreted literally anyway. */ if (!is_regexp) { goto repl_write; } if (!(ch2 >= DUK_ASC_0 && ch2 <= DUK_ASC_9)) { goto repl_write; } capnum = ch2 - DUK_ASC_0; capadv = 1; if (left >= 2) { ch3 = r[1]; if (ch3 >= DUK_ASC_0 && ch3 <= DUK_ASC_9) { captmp = capnum * 10 + (ch3 - DUK_ASC_0); if (captmp < match_caps) { capnum = captmp; capadv = 2; } } } if (capnum > 0 && capnum < match_caps) { DUK_ASSERT(is_regexp != 0); /* match_caps == 0 without regexps */ /* regexp res_obj is at offset 4 */ duk_get_prop_index(ctx, 4, (duk_uarridx_t) capnum); if (duk_is_string(ctx, -1)) { duk_hstring *h_tmp_str; h_tmp_str = duk_get_hstring(ctx, -1); DUK_ASSERT(h_tmp_str != NULL); DUK_BW_WRITE_ENSURE_HSTRING(thr, bw, h_tmp_str); } else { /* undefined -> skip (replaced with empty) */ } duk_pop(ctx); r += capadv; continue; } else { goto repl_write; } #else /* DUK_USE_REGEXP_SUPPORT */ goto repl_write; /* unconditionally */ #endif /* DUK_USE_REGEXP_SUPPORT */ } /* default case */ } /* switch (ch2) */ repl_write: /* ch1 = (r_increment << 8) + byte */ DUK_BW_WRITE_ENSURE_U8(thr, bw, (duk_uint8_t) (ch1 & 0xff)); r += ch1 >> 8; } /* while repl */ } /* if (is_repl_func) */ duk_pop(ctx); /* pop regexp res_obj or match string */ #ifdef DUK_USE_REGEXP_SUPPORT if (!is_global) { #else { /* unconditionally; is_global==0 */ #endif break; } } /* trailer */ tmp_sz = (duk_size_t) (DUK_HSTRING_GET_BYTELEN(h_input) - prev_match_end_boff); DUK_BW_WRITE_ENSURE_BYTES(thr, bw, DUK_HSTRING_GET_DATA(h_input) + prev_match_end_boff, tmp_sz); DUK_ASSERT_TOP(ctx, 4); DUK_BW_COMPACT(thr, bw); duk_to_string(ctx, -1); return 1; } /* * split() */ /* XXX: very messy now, but works; clean up, remove unused variables (nomimally * used so compiler doesn't complain). */ DUK_INTERNAL duk_ret_t duk_bi_string_prototype_split(duk_context *ctx) { duk_hthread *thr = (duk_hthread *) ctx; duk_hstring *h_input; duk_hstring *h_sep; duk_uint32_t limit; duk_uint32_t arr_idx; #ifdef DUK_USE_REGEXP_SUPPORT duk_bool_t is_regexp; #endif duk_bool_t matched; /* set to 1 if any match exists (needed for empty input special case) */ duk_uint32_t prev_match_end_coff, prev_match_end_boff; duk_uint32_t match_start_boff, match_start_coff; duk_uint32_t match_end_boff, match_end_coff; DUK_UNREF(thr); h_input = duk_push_this_coercible_to_string(ctx); DUK_ASSERT(h_input != NULL); duk_push_array(ctx); if (duk_is_undefined(ctx, 1)) { limit = 0xffffffffUL; } else { limit = duk_to_uint32(ctx, 1); } if (limit == 0) { return 1; } /* If the separator is a RegExp, make a "clone" of it. The specification * algorithm calls [[Match]] directly for specific indices; we emulate this * by tweaking lastIndex and using a "force global" variant of duk_regexp_match() * which will use global-style matching even when the RegExp itself is non-global. */ if (duk_is_undefined(ctx, 0)) { /* The spec algorithm first does "R = ToString(separator)" before checking * whether separator is undefined. Since this is side effect free, we can * skip the ToString() here. */ duk_dup(ctx, 2); duk_put_prop_index(ctx, 3, 0); return 1; } else if (duk_get_hobject_with_class(ctx, 0, DUK_HOBJECT_CLASS_REGEXP) != NULL) { #ifdef DUK_USE_REGEXP_SUPPORT duk_push_hobject_bidx(ctx, DUK_BIDX_REGEXP_CONSTRUCTOR); duk_dup(ctx, 0); duk_new(ctx, 1); /* [ ... RegExp val ] -> [ ... res ] */ duk_replace(ctx, 0); /* lastIndex is initialized to zero by new RegExp() */ is_regexp = 1; #else return DUK_RET_UNSUPPORTED_ERROR; #endif } else { duk_to_string(ctx, 0); #ifdef DUK_USE_REGEXP_SUPPORT is_regexp = 0; #endif } /* stack[0] = separator (string or regexp) * stack[1] = limit * stack[2] = input string * stack[3] = result array */ prev_match_end_boff = 0; prev_match_end_coff = 0; arr_idx = 0; matched = 0; for (;;) { /* * The specification uses RegExp [[Match]] to attempt match at specific * offsets. We don't have such a primitive, so we use an actual RegExp * and tweak lastIndex. Since the RegExp may be non-global, we use a * special variant which forces global-like behavior for matching. */ DUK_ASSERT_TOP(ctx, 4); #ifdef DUK_USE_REGEXP_SUPPORT if (is_regexp) { duk_dup(ctx, 0); duk_dup(ctx, 2); duk_regexp_match_force_global(thr); /* [ ... regexp input ] -> [ res_obj ] */ if (!duk_is_object(ctx, -1)) { duk_pop(ctx); break; } matched = 1; duk_get_prop_stridx(ctx, -1, DUK_STRIDX_INDEX); DUK_ASSERT(duk_is_number(ctx, -1)); match_start_coff = duk_get_int(ctx, -1); match_start_boff = duk_heap_strcache_offset_char2byte(thr, h_input, match_start_coff); duk_pop(ctx); if (match_start_coff == DUK_HSTRING_GET_CHARLEN(h_input)) { /* don't allow an empty match at the end of the string */ duk_pop(ctx); break; } duk_get_prop_stridx(ctx, 0, DUK_STRIDX_LAST_INDEX); DUK_ASSERT(duk_is_number(ctx, -1)); match_end_coff = duk_get_int(ctx, -1); match_end_boff = duk_heap_strcache_offset_char2byte(thr, h_input, match_end_coff); duk_pop(ctx); /* empty match -> bump and continue */ if (prev_match_end_boff == match_end_boff) { duk_push_int(ctx, match_end_coff + 1); duk_put_prop_stridx(ctx, 0, DUK_STRIDX_LAST_INDEX); duk_pop(ctx); continue; } } else { #else /* DUK_USE_REGEXP_SUPPORT */ { /* unconditionally */ #endif /* DUK_USE_REGEXP_SUPPORT */ const duk_uint8_t *p_start, *p_end, *p; /* input string scan */ const duk_uint8_t *q_start; /* match string */ duk_size_t q_blen, q_clen; p_start = DUK_HSTRING_GET_DATA(h_input); p_end = p_start + DUK_HSTRING_GET_BYTELEN(h_input); p = p_start + prev_match_end_boff; h_sep = duk_get_hstring(ctx, 0); DUK_ASSERT(h_sep != NULL); q_start = DUK_HSTRING_GET_DATA(h_sep); q_blen = (duk_size_t) DUK_HSTRING_GET_BYTELEN(h_sep); q_clen = (duk_size_t) DUK_HSTRING_GET_CHARLEN(h_sep); p_end -= q_blen; /* ensure full memcmp() fits in while */ match_start_coff = prev_match_end_coff; if (q_blen == 0) { /* Handle empty separator case: it will always match, and always * triggers the check in step 13.c.iii initially. Note that we * must skip to either end of string or start of first codepoint, * skipping over any continuation bytes! * * Don't allow an empty string to match at the end of the input. */ matched = 1; /* empty separator can always match */ match_start_coff++; p++; while (p < p_end) { if ((p[0] & 0xc0) != 0x80) { goto found; } p++; } goto not_found; } DUK_ASSERT(q_blen > 0 && q_clen > 0); while (p <= p_end) { DUK_ASSERT(p + q_blen <= DUK_HSTRING_GET_DATA(h_input) + DUK_HSTRING_GET_BYTELEN(h_input)); DUK_ASSERT(q_blen > 0); /* no issues with empty memcmp() */ if (DUK_MEMCMP((void *) p, (void *) q_start, (duk_size_t) q_blen) == 0) { /* never an empty match, so step 13.c.iii can't be triggered */ goto found; } /* track utf-8 non-continuation bytes */ if ((p[0] & 0xc0) != 0x80) { match_start_coff++; } p++; } not_found: /* not found */ break; found: matched = 1; match_start_boff = (duk_uint32_t) (p - p_start); match_end_coff = (duk_uint32_t) (match_start_coff + q_clen); /* constrained by string length */ match_end_boff = (duk_uint32_t) (match_start_boff + q_blen); /* ditto */ /* empty match (may happen with empty separator) -> bump and continue */ if (prev_match_end_boff == match_end_boff) { prev_match_end_boff++; prev_match_end_coff++; continue; } } /* if (is_regexp) */ /* stack[0] = separator (string or regexp) * stack[1] = limit * stack[2] = input string * stack[3] = result array * stack[4] = regexp res_obj (if is_regexp) */ DUK_DDD(DUK_DDDPRINT("split; match_start b=%ld,c=%ld, match_end b=%ld,c=%ld, prev_end b=%ld,c=%ld", (long) match_start_boff, (long) match_start_coff, (long) match_end_boff, (long) match_end_coff, (long) prev_match_end_boff, (long) prev_match_end_coff)); duk_push_lstring(ctx, (const char *) (DUK_HSTRING_GET_DATA(h_input) + prev_match_end_boff), (duk_size_t) (match_start_boff - prev_match_end_boff)); duk_put_prop_index(ctx, 3, arr_idx); arr_idx++; if (arr_idx >= limit) { goto hit_limit; } #ifdef DUK_USE_REGEXP_SUPPORT if (is_regexp) { duk_size_t i, len; len = duk_get_length(ctx, 4); for (i = 1; i < len; i++) { DUK_ASSERT(i <= DUK_UARRIDX_MAX); /* cannot have >4G captures */ duk_get_prop_index(ctx, 4, (duk_uarridx_t) i); duk_put_prop_index(ctx, 3, arr_idx); arr_idx++; if (arr_idx >= limit) { goto hit_limit; } } duk_pop(ctx); /* lastIndex already set up for next match */ } else { #else /* DUK_USE_REGEXP_SUPPORT */ { /* unconditionally */ #endif /* DUK_USE_REGEXP_SUPPORT */ /* no action */ } prev_match_end_boff = match_end_boff; prev_match_end_coff = match_end_coff; continue; } /* for */ /* Combined step 11 (empty string special case) and 14-15. */ DUK_DDD(DUK_DDDPRINT("split trailer; prev_end b=%ld,c=%ld", (long) prev_match_end_boff, (long) prev_match_end_coff)); if (DUK_HSTRING_GET_CHARLEN(h_input) > 0 || !matched) { /* Add trailer if: * a) non-empty input * b) empty input and no (zero size) match found (step 11) */ duk_push_lstring(ctx, (const char *) DUK_HSTRING_GET_DATA(h_input) + prev_match_end_boff, (duk_size_t) (DUK_HSTRING_GET_BYTELEN(h_input) - prev_match_end_boff)); duk_put_prop_index(ctx, 3, arr_idx); /* No arr_idx update or limit check */ } return 1; hit_limit: #ifdef DUK_USE_REGEXP_SUPPORT if (is_regexp) { duk_pop(ctx); } #endif return 1; } /* * Various */ #ifdef DUK_USE_REGEXP_SUPPORT DUK_LOCAL void duk__to_regexp_helper(duk_context *ctx, duk_idx_t index, duk_bool_t force_new) { duk_hobject *h; /* Shared helper for match() steps 3-4, search() steps 3-4. */ DUK_ASSERT(index >= 0); if (force_new) { goto do_new; } h = duk_get_hobject_with_class(ctx, index, DUK_HOBJECT_CLASS_REGEXP); if (!h) { goto do_new; } return; do_new: duk_push_hobject_bidx(ctx, DUK_BIDX_REGEXP_CONSTRUCTOR); duk_dup(ctx, index); duk_new(ctx, 1); /* [ ... RegExp val ] -> [ ... res ] */ duk_replace(ctx, index); } #endif /* DUK_USE_REGEXP_SUPPORT */ #ifdef DUK_USE_REGEXP_SUPPORT DUK_INTERNAL duk_ret_t duk_bi_string_prototype_search(duk_context *ctx) { duk_hthread *thr = (duk_hthread *) ctx; /* Easiest way to implement the search required by the specification * is to do a RegExp test() with lastIndex forced to zero. To avoid * side effects on the argument, "clone" the RegExp if a RegExp was * given as input. * * The global flag of the RegExp should be ignored; setting lastIndex * to zero (which happens when "cloning" the RegExp) should have an * equivalent effect. */ DUK_ASSERT_TOP(ctx, 1); (void) duk_push_this_coercible_to_string(ctx); /* at index 1 */ duk__to_regexp_helper(ctx, 0 /*index*/, 1 /*force_new*/); /* stack[0] = regexp * stack[1] = string */ /* Avoid using RegExp.prototype methods, as they're writable and * configurable and may have been changed. */ duk_dup(ctx, 0); duk_dup(ctx, 1); /* [ ... re_obj input ] */ duk_regexp_match(thr); /* -> [ ... res_obj ] */ if (!duk_is_object(ctx, -1)) { duk_push_int(ctx, -1); return 1; } duk_get_prop_stridx(ctx, -1, DUK_STRIDX_INDEX); DUK_ASSERT(duk_is_number(ctx, -1)); return 1; } #else /* DUK_USE_REGEXP_SUPPORT */ DUK_INTERNAL duk_ret_t duk_bi_string_prototype_search(duk_context *ctx) { DUK_UNREF(ctx); return DUK_RET_UNSUPPORTED_ERROR; }
DUK_INTERNAL duk_ret_t duk_bi_string_prototype_match(duk_context *ctx) { duk_hthread *thr = (duk_hthread *) ctx; duk_bool_t global; duk_int_t prev_last_index; duk_int_t this_index; duk_int_t arr_idx; DUK_ASSERT_TOP(ctx, 1); (void) duk_push_this_coercible_to_string(ctx); duk__to_regexp_helper(ctx, 0 /*index*/, 0 /*force_new*/); global = duk_get_prop_stridx_boolean(ctx, 0, DUK_STRIDX_GLOBAL, NULL); DUK_ASSERT_TOP(ctx, 2); /* stack[0] = regexp * stack[1] = string */ if (!global) { duk_regexp_match(thr); /* -> [ res_obj ] */ return 1; /* return 'res_obj' */ } /* Global case is more complex. */ /* [ regexp string ] */ duk_push_int(ctx, 0); duk_put_prop_stridx(ctx, 0, DUK_STRIDX_LAST_INDEX); duk_push_array(ctx); /* [ regexp string res_arr ] */ prev_last_index = 0; arr_idx = 0; for (;;) { DUK_ASSERT_TOP(ctx, 3); duk_dup(ctx, 0); duk_dup(ctx, 1); duk_regexp_match(thr); /* -> [ ... regexp string ] -> [ ... res_obj ] */ if (!duk_is_object(ctx, -1)) { duk_pop(ctx); break; } duk_get_prop_stridx(ctx, 0, DUK_STRIDX_LAST_INDEX); DUK_ASSERT(duk_is_number(ctx, -1)); this_index = duk_get_int(ctx, -1); duk_pop(ctx); if (this_index == prev_last_index) { this_index++; duk_push_int(ctx, this_index); duk_put_prop_stridx(ctx, 0, DUK_STRIDX_LAST_INDEX); } prev_last_index = this_index; duk_get_prop_index(ctx, -1, 0); /* match string */ duk_put_prop_index(ctx, 2, arr_idx); arr_idx++; duk_pop(ctx); /* res_obj */ } if (arr_idx == 0) { duk_push_null(ctx); } return 1; /* return 'res_arr' or 'null' */ }
DUK_INTERNAL duk_bool_t duk_js_instanceof(duk_hthread *thr, duk_tval *tv_x, duk_tval *tv_y) { duk_context *ctx = (duk_context *) thr; duk_hobject *func; duk_hobject *val; duk_hobject *proto; duk_uint_t sanity; /* * Get the values onto the stack first. It would be possible to cover * some normal cases without resorting to the value stack. * * The right hand side could be a light function (as they generally * behave like objects). Light functions never have a 'prototype' * property so E5.1 Section 15.3.5.3 step 3 always throws a TypeError. * Using duk_require_hobject() is thus correct (except for error msg). */ duk_push_tval(ctx, tv_x); duk_push_tval(ctx, tv_y); func = duk_require_hobject(ctx, -1); /* * For bound objects, [[HasInstance]] just calls the target function * [[HasInstance]]. If that is again a bound object, repeat until * we find a non-bound Function object. */ /* XXX: this bound function resolution also happens elsewhere, * move into a shared helper. */ sanity = DUK_HOBJECT_BOUND_CHAIN_SANITY; do { /* check func supports [[HasInstance]] (this is checked for every function * in the bound chain, including the final one) */ if (!DUK_HOBJECT_IS_CALLABLE(func)) { /* * Note: of native Ecmascript objects, only Function instances * have a [[HasInstance]] internal property. Custom objects might * also have it, but not in current implementation. * * XXX: add a separate flag, DUK_HOBJECT_FLAG_ALLOW_INSTANCEOF? */ DUK_ERROR(thr, DUK_ERR_TYPE_ERROR, "invalid instanceof rval"); } if (!DUK_HOBJECT_HAS_BOUND(func)) { break; } /* [ ... lval rval ] */ duk_get_prop_stridx(ctx, -1, DUK_STRIDX_INT_TARGET); /* -> [ ... lval rval new_rval ] */ duk_replace(ctx, -1); /* -> [ ... lval new_rval ] */ func = duk_require_hobject(ctx, -1); /* func support for [[HasInstance]] checked in the beginning of the loop */ } while (--sanity > 0); if (sanity == 0) { DUK_ERROR(thr, DUK_ERR_INTERNAL_ERROR, DUK_STR_BOUND_CHAIN_LIMIT); } /* * 'func' is now a non-bound object which supports [[HasInstance]] * (which here just means DUK_HOBJECT_FLAG_CALLABLE). Move on * to execute E5 Section 15.3.5.3. */ DUK_ASSERT(!DUK_HOBJECT_HAS_BOUND(func)); DUK_ASSERT(DUK_HOBJECT_IS_CALLABLE(func)); /* [ ... lval rval(func) ] */ /* Handle lightfuncs through object coercion for now. */ /* XXX: direct implementation */ val = duk_get_hobject_or_lfunc_coerce(ctx, -2); if (!val) { goto pop_and_false; } duk_get_prop_stridx(ctx, -1, DUK_STRIDX_PROTOTYPE); /* -> [ ... lval rval rval.prototype ] */ proto = duk_require_hobject(ctx, -1); duk_pop(ctx); /* -> [ ... lval rval ] */ sanity = DUK_HOBJECT_PROTOTYPE_CHAIN_SANITY; do { /* * Note: prototype chain is followed BEFORE first comparison. This * means that the instanceof lval is never itself compared to the * rval.prototype property. This is apparently intentional, see E5 * Section 15.3.5.3, step 4.a. * * Also note: * * js> (function() {}) instanceof Function * true * js> Function instanceof Function * true * * For the latter, h_proto will be Function.prototype, which is the * built-in Function prototype. Because Function.[[Prototype]] is * also the built-in Function prototype, the result is true. */ val = DUK_HOBJECT_GET_PROTOTYPE(thr->heap, val); if (!val) { goto pop_and_false; } else if (val == proto) { goto pop_and_true; } /* follow prototype chain */ } while (--sanity > 0); if (sanity == 0) { DUK_ERROR(thr, DUK_ERR_INTERNAL_ERROR, DUK_STR_PROTOTYPE_CHAIN_LIMIT); } DUK_UNREACHABLE(); pop_and_false: duk_pop_2(ctx); return 0; pop_and_true: duk_pop_2(ctx); return 1; }
/* XXX: much to improve (code size) */ DUK_INTERNAL duk_ret_t duk_bi_regexp_constructor(duk_context *ctx) { duk_hthread *thr = (duk_hthread *) ctx; duk_hobject *h_pattern; DUK_ASSERT_TOP(ctx, 2); h_pattern = duk_get_hobject(ctx, 0); if (!duk_is_constructor_call(ctx) && h_pattern != NULL && DUK_HOBJECT_GET_CLASS_NUMBER(h_pattern) == DUK_HOBJECT_CLASS_REGEXP && duk_is_undefined(ctx, 1)) { /* Called as a function, pattern has [[Class]] "RegExp" and * flags is undefined -> return object as is. */ duk_dup(ctx, 0); return 1; } /* Else functionality is identical for function call and constructor * call. */ if (h_pattern != NULL && DUK_HOBJECT_GET_CLASS_NUMBER(h_pattern) == DUK_HOBJECT_CLASS_REGEXP) { if (duk_is_undefined(ctx, 1)) { duk_bool_t flag_g, flag_i, flag_m; duk_get_prop_stridx(ctx, 0, DUK_STRIDX_SOURCE); flag_g = duk_get_prop_stridx_boolean(ctx, 0, DUK_STRIDX_GLOBAL, NULL); flag_i = duk_get_prop_stridx_boolean(ctx, 0, DUK_STRIDX_IGNORE_CASE, NULL); flag_m = duk_get_prop_stridx_boolean(ctx, 0, DUK_STRIDX_MULTILINE, NULL); duk_push_sprintf(ctx, "%s%s%s", (const char *) (flag_g ? "g" : ""), (const char *) (flag_i ? "i" : ""), (const char *) (flag_m ? "m" : "")); /* [ ... pattern flags ] */ } else { return DUK_RET_TYPE_ERROR; } } else { if (duk_is_undefined(ctx, 0)) { duk_push_string(ctx, ""); } else { duk_dup(ctx, 0); duk_to_string(ctx, -1); } if (duk_is_undefined(ctx, 1)) { duk_push_string(ctx, ""); } else { duk_dup(ctx, 1); duk_to_string(ctx, -1); } /* [ ... pattern flags ] */ } DUK_DDD(DUK_DDDPRINT("RegExp constructor/function call, pattern=%!T, flags=%!T", (duk_tval *) duk_get_tval(ctx, -2), (duk_tval *) duk_get_tval(ctx, -1))); /* [ ... pattern flags ] */ duk_regexp_compile(thr); /* [ ... bytecode escaped_source ] */ duk_regexp_create_instance(thr); /* [ ... RegExp ] */ return 1; }
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; }
static void duk__err_augment_builtin_throw(duk_hthread *thr, duk_hthread *thr_callstack, const char *filename, duk_int_t line, duk_small_int_t noblame_fileline, duk_hobject *obj) { duk_context *ctx = (duk_context *) thr; #ifdef DUK_USE_ASSERTIONS duk_int_t entry_top; #endif #ifdef DUK_USE_ASSERTIONS entry_top = duk_get_top(ctx); #endif DUK_ASSERT(obj != NULL); DUK_UNREF(obj); /* unreferenced w/o tracebacks */ DUK_UNREF(ctx); /* unreferenced w/ tracebacks */ #ifdef DUK_USE_TRACEBACKS /* * If tracebacks are enabled, the 'tracedata' property is the only * thing we need: 'fileName' and 'lineNumber' are virtual properties * which use 'tracedata'. */ if (duk_hobject_hasprop_raw(thr, obj, DUK_HTHREAD_STRING_TRACEDATA(thr))) { DUK_DDD(DUK_DDDPRINT("error value already has a 'tracedata' property, not modifying it")); } else { duk__add_traceback(thr, thr_callstack, filename, line, noblame_fileline); } #else /* * If tracebacks are disabled, 'fileName' and 'lineNumber' are added * as plain own properties. Since Error.prototype has accessors of * the same name, we need to define own properties directly (cannot * just use e.g. duk_put_prop_stridx). Existing properties are not * overwritten in case they already exist. */ if (filename && !noblame_fileline) { /* XXX: file/line is disabled in minimal builds, so disable this too * when appropriate. */ duk_push_string(ctx, filename); duk_def_prop_stridx(ctx, -2, DUK_STRIDX_FILE_NAME, DUK_PROPDESC_FLAGS_WC | DUK_PROPDESC_FLAG_NO_OVERWRITE); duk_push_int(ctx, line); duk_def_prop_stridx(ctx, -2, DUK_STRIDX_LINE_NUMBER, DUK_PROPDESC_FLAGS_WC | DUK_PROPDESC_FLAG_NO_OVERWRITE); } else if (thr_callstack->callstack_top > 0) { duk_activation *act; duk_hobject *func; act = thr_callstack->callstack + thr_callstack->callstack_top - 1; DUK_ASSERT(act >= thr_callstack->callstack && act < thr_callstack->callstack + thr_callstack->callstack_size); func = act->func; if (func) { duk_uint32_t pc; duk_uint32_t line; /* PC points to next instruction, find offending PC. Note that * PC == 0 for native code. */ pc = act->pc; if (pc > 0) { pc--; } 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 */ act = NULL; /* invalidated by pushes, so get out of the way */ duk_push_hobject(ctx, func); /* [ ... error func ] */ duk_get_prop_stridx(ctx, -1, DUK_STRIDX_FILE_NAME); duk_def_prop_stridx(ctx, -3, DUK_STRIDX_FILE_NAME, DUK_PROPDESC_FLAGS_WC | DUK_PROPDESC_FLAG_NO_OVERWRITE); if (DUK_HOBJECT_IS_COMPILEDFUNCTION(func)) { #if 0 duk_push_number(ctx, pc); duk_def_prop_stridx(ctx, -3, DUK_STRIDX_PC, DUK_PROPDESC_FLAGS_WC | DUK_PROPDESC_FLAGS_NO_OVERWRITE); #endif line = duk_hobject_pc2line_query(ctx, -1, (duk_uint_fast32_t) pc); if (line > 0) { duk_push_u32(ctx, (duk_uint32_t) line); /* -> [ ... error func line ] */ duk_def_prop_stridx(ctx, -3, DUK_STRIDX_LINE_NUMBER, DUK_PROPDESC_FLAGS_WC | DUK_PROPDESC_FLAG_NO_OVERWRITE); } } else { /* Native function, no relevant lineNumber. */ } duk_pop(ctx); } } #endif /* DUK_USE_TRACEBACKS */ #ifdef DUK_USE_ASSERTIONS DUK_ASSERT(duk_get_top(ctx) == entry_top); #endif }
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; }
/* * Returns non-zero if a key and/or value was enumerated, and: * * [enum] -> [key] (get_value == 0) * [enum] -> [key value] (get_value == 1) * * Returns zero without pushing anything on the stack otherwise. */ duk_bool_t duk_hobject_enumerator_next(duk_context *ctx, duk_bool_t get_value) { duk_hthread *thr = (duk_hthread *) ctx; duk_hobject *e; duk_hobject *enum_target; duk_hstring *res = NULL; duk_uint_fast32_t idx; duk_bool_t check_existence; DUK_ASSERT(ctx != NULL); /* [... enum] */ e = duk_require_hobject(ctx, -1); /* XXX use get tval ptr, more efficient */ duk_get_prop_stridx(ctx, -1, DUK_STRIDX_INT_NEXT); idx = (duk_uint_fast32_t) duk_require_uint(ctx, -1); duk_pop(ctx); DUK_DDD(DUK_DDDPRINT("enumeration: index is: %ld", (long) idx)); /* Enumeration keys are checked against the enumeration target (to see * that they still exist). In the proxy enumeration case _target will * be the proxy, and checking key existence against the proxy is not * required (or sensible, as the keys may be fully virtual). */ duk_get_prop_stridx(ctx, -1, DUK_STRIDX_INT_TARGET); enum_target = duk_require_hobject(ctx, -1); DUK_ASSERT(enum_target != NULL); #if defined(DUK_USE_ES6_PROXY) check_existence = (!DUK_HOBJECT_HAS_EXOTIC_PROXYOBJ(enum_target)); #else check_existence = 1; #endif duk_pop(ctx); /* still reachable */ DUK_DDD(DUK_DDDPRINT("getting next enum value, enum_target=%!iO, enumerator=%!iT", (duk_heaphdr *) enum_target, (duk_tval *) duk_get_tval(ctx, -1))); /* no array part */ for (;;) { duk_hstring *k; if (idx >= e->e_next) { DUK_DDD(DUK_DDDPRINT("enumeration: ran out of elements")); break; } /* we know these because enum objects are internally created */ k = DUK_HOBJECT_E_GET_KEY(e, idx); DUK_ASSERT(k != NULL); DUK_ASSERT(!DUK_HOBJECT_E_SLOT_IS_ACCESSOR(e, idx)); DUK_ASSERT(!DUK_TVAL_IS_UNDEFINED_UNUSED(&DUK_HOBJECT_E_GET_VALUE(e, idx).v)); idx++; /* recheck that the property still exists */ if (check_existence && !duk_hobject_hasprop_raw(thr, enum_target, k)) { DUK_DDD(DUK_DDDPRINT("property deleted during enumeration, skip")); continue; } DUK_DDD(DUK_DDDPRINT("enumeration: found element, key: %!O", (duk_heaphdr *) k)); res = k; break; } DUK_DDD(DUK_DDDPRINT("enumeration: updating next index to %ld", (long) idx)); duk_push_number(ctx, (double) idx); duk_put_prop_stridx(ctx, -2, DUK_STRIDX_INT_NEXT); /* [... enum] */ if (res) { duk_push_hstring(ctx, res); if (get_value) { duk_push_hobject(ctx, enum_target); duk_dup(ctx, -2); /* -> [... enum key enum_target key] */ duk_get_prop(ctx, -2); /* -> [... enum key enum_target val] */ duk_remove(ctx, -2); /* -> [... enum key val] */ duk_remove(ctx, -3); /* -> [... key val] */ } else { duk_remove(ctx, -2); /* -> [... key] */ } return 1; } else { duk_pop(ctx); /* -> [...] */ return 0; } }
/* XXX: much to improve (code size) */ DUK_INTERNAL duk_ret_t duk_bi_regexp_constructor(duk_hthread *thr) { duk_hobject *h_pattern; DUK_ASSERT_TOP(thr, 2); h_pattern = duk_get_hobject(thr, 0); if (!duk_is_constructor_call(thr) && h_pattern != NULL && DUK_HOBJECT_GET_CLASS_NUMBER(h_pattern) == DUK_HOBJECT_CLASS_REGEXP && duk_is_undefined(thr, 1)) { /* Called as a function, pattern has [[Class]] "RegExp" and * flags is undefined -> return object as is. */ /* XXX: ES2015 has a NewTarget SameValue() check which is not * yet implemented. */ duk_dup_0(thr); return 1; } /* Else functionality is identical for function call and constructor * call. */ if (h_pattern != NULL && DUK_HOBJECT_GET_CLASS_NUMBER(h_pattern) == DUK_HOBJECT_CLASS_REGEXP) { duk_get_prop_stridx_short(thr, 0, DUK_STRIDX_SOURCE); if (duk_is_undefined(thr, 1)) { /* In ES5 one would need to read the flags individually; * in ES2015 just read .flags. */ duk_get_prop_stridx(thr, 0, DUK_STRIDX_FLAGS); } else { /* In ES2015 allowed; overrides argument RegExp flags. */ duk_dup_1(thr); } } else { if (duk_is_undefined(thr, 0)) { duk_push_hstring_empty(thr); } else { duk_dup_0(thr); duk_to_string(thr, -1); /* Rejects Symbols. */ } if (duk_is_undefined(thr, 1)) { duk_push_hstring_empty(thr); } else { duk_dup_1(thr); duk_to_string(thr, -1); /* Rejects Symbols. */ } /* [ ... pattern flags ] */ } DUK_DDD(DUK_DDDPRINT("RegExp constructor/function call, pattern=%!T, flags=%!T", (duk_tval *) duk_get_tval(thr, -2), (duk_tval *) duk_get_tval(thr, -1))); /* [ ... pattern flags ] (both uncoerced) */ duk_to_string(thr, -2); duk_to_string(thr, -1); duk_regexp_compile(thr); /* [ ... bytecode escaped_source ] */ duk_regexp_create_instance(thr); /* [ ... RegExp ] */ return 1; }
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 (;;) { cons = duk_get_hobject(ctx, -1); if (cons == NULL || !DUK_HOBJECT_HAS_CONSTRUCTABLE(cons)) { /* Checking constructability from anything else than the * initial constructor is not strictly necessary, but a * nice sanity check. */ goto not_constructable; } if (!DUK_HOBJECT_HAS_BOUNDFUNC(cons)) { break; } duk_get_prop_stridx(ctx, -1, DUK_STRIDX_INT_TARGET); /* -> [... cons target] */ duk_remove(ctx, -2); /* -> [... target] */ } DUK_ASSERT(cons != NULL && !DUK_HOBJECT_HAS_BOUNDFUNC(cons)); /* [... 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(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_is_object(ctx, -1)) { duk_remove(ctx, -2); } 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. */ #ifdef 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 }
/* Shared helper for Object.getOwnPropertyNames() and Object.keys(). * Magic: 0=getOwnPropertyNames, 1=Object.keys. */ DUK_INTERNAL duk_ret_t duk_bi_object_constructor_keys_shared(duk_context *ctx) { duk_hthread *thr = (duk_hthread *) ctx; duk_hobject *obj; #if defined(DUK_USE_ES6_PROXY) duk_hobject *h_proxy_target; duk_hobject *h_proxy_handler; duk_hobject *h_trap_result; duk_uarridx_t i, len, idx; #endif duk_small_uint_t enum_flags; DUK_ASSERT_TOP(ctx, 1); obj = duk_require_hobject_or_lfunc_coerce(ctx, 0); DUK_ASSERT(obj != NULL); DUK_UNREF(obj); #if defined(DUK_USE_ES6_PROXY) if (DUK_LIKELY(!duk_hobject_proxy_check(thr, obj, &h_proxy_target, &h_proxy_handler))) { goto skip_proxy; } duk_push_hobject(ctx, h_proxy_handler); if (!duk_get_prop_stridx(ctx, -1, DUK_STRIDX_OWN_KEYS)) { /* Careful with reachability here: don't pop 'obj' before pushing * proxy target. */ DUK_DDD(DUK_DDDPRINT("no ownKeys trap, get keys of target instead")); duk_pop_2(ctx); duk_push_hobject(ctx, h_proxy_target); duk_replace(ctx, 0); DUK_ASSERT_TOP(ctx, 1); goto skip_proxy; } /* [ obj handler trap ] */ duk_insert(ctx, -2); duk_push_hobject(ctx, h_proxy_target); /* -> [ obj trap handler target ] */ duk_call_method(ctx, 1 /*nargs*/); /* -> [ obj trap_result ] */ h_trap_result = duk_require_hobject(ctx, -1); DUK_UNREF(h_trap_result); len = (duk_uarridx_t) duk_get_length(ctx, -1); idx = 0; duk_push_array(ctx); for (i = 0; i < len; i++) { /* [ obj trap_result res_arr ] */ if (duk_get_prop_index(ctx, -2, i) && duk_is_string(ctx, -1)) { /* XXX: for Object.keys() we should check enumerability of key */ /* [ obj trap_result res_arr propname ] */ duk_put_prop_index(ctx, -2, idx); idx++; } else { duk_pop(ctx); } } /* XXX: for Object.keys() the [[OwnPropertyKeys]] result (trap result) * should be filtered so that only enumerable keys remain. Enumerability * should be checked with [[GetOwnProperty]] on the original object * (i.e., the proxy in this case). If the proxy has a getOwnPropertyDescriptor * trap, it should be triggered for every property. If the proxy doesn't have * the trap, enumerability should be checked against the target object instead. * We don't do any of this now, so Object.keys() and Object.getOwnPropertyNames() * return the same result now for proxy traps. We still do clean up the trap * result, so that Object.keys() and Object.getOwnPropertyNames() will return a * clean array of strings without gaps. */ return 1; skip_proxy: #endif /* DUK_USE_ES6_PROXY */ DUK_ASSERT_TOP(ctx, 1); if (duk_get_current_magic(ctx)) { /* Object.keys */ enum_flags = DUK_ENUM_OWN_PROPERTIES_ONLY | DUK_ENUM_NO_PROXY_BEHAVIOR; } else { /* Object.getOwnPropertyNames */ enum_flags = DUK_ENUM_INCLUDE_NONENUMERABLE | DUK_ENUM_OWN_PROPERTIES_ONLY | DUK_ENUM_NO_PROXY_BEHAVIOR; } return duk_hobject_get_enumerated_keys(ctx, enum_flags); }