DUK_INTERNAL void duk_regexp_create_instance(duk_hthread *thr) { duk_context *ctx = (duk_context *) thr; duk_hobject *h; duk_hstring *h_bc; duk_small_int_t re_flags; /* [ ... escape_source bytecode ] */ h_bc = duk_require_hstring(ctx, -1); DUK_ASSERT(h_bc != NULL); DUK_ASSERT(DUK_HSTRING_GET_BYTELEN(h_bc) >= 1); /* always at least the header */ DUK_ASSERT(DUK_HSTRING_GET_CHARLEN(h_bc) >= 1); DUK_ASSERT((duk_small_int_t) DUK_HSTRING_GET_DATA(h_bc)[0] < 0x80); /* flags always encodes to 1 byte */ re_flags = (duk_small_int_t) DUK_HSTRING_GET_DATA(h_bc)[0]; /* [ ... escaped_source bytecode ] */ duk_push_object(ctx); h = duk_known_hobject(ctx, -1); duk_insert(ctx, -3); /* [ ... regexp_object escaped_source bytecode ] */ DUK_HOBJECT_SET_CLASS_NUMBER(h, DUK_HOBJECT_CLASS_REGEXP); DUK_HOBJECT_SET_PROTOTYPE_UPDREF(thr, h, thr->builtins[DUK_BIDX_REGEXP_PROTOTYPE]); duk_xdef_prop_stridx(ctx, -3, DUK_STRIDX_INT_BYTECODE, DUK_PROPDESC_FLAGS_NONE); /* [ ... regexp_object escaped_source ] */ duk_xdef_prop_stridx(ctx, -2, DUK_STRIDX_SOURCE, DUK_PROPDESC_FLAGS_NONE); /* [ ... regexp_object ] */ duk_push_boolean(ctx, (re_flags & DUK_RE_FLAG_GLOBAL)); duk_xdef_prop_stridx(ctx, -2, DUK_STRIDX_GLOBAL, DUK_PROPDESC_FLAGS_NONE); duk_push_boolean(ctx, (re_flags & DUK_RE_FLAG_IGNORE_CASE)); duk_xdef_prop_stridx(ctx, -2, DUK_STRIDX_IGNORE_CASE, DUK_PROPDESC_FLAGS_NONE); duk_push_boolean(ctx, (re_flags & DUK_RE_FLAG_MULTILINE)); duk_xdef_prop_stridx(ctx, -2, DUK_STRIDX_MULTILINE, DUK_PROPDESC_FLAGS_NONE); duk_push_int(ctx, 0); duk_xdef_prop_stridx(ctx, -2, DUK_STRIDX_LAST_INDEX, DUK_PROPDESC_FLAGS_W); /* [ ... regexp_object ] */ }
DUK_INTERNAL duk_ret_t duk_bi_duktape_object_enc(duk_context *ctx) { duk_hthread *thr = (duk_hthread *) ctx; duk_hstring *h_str; DUK_UNREF(thr); /* Vararg function: must be careful to check/require arguments. * The JSON helpers accept invalid indices and treat them like * non-existent optional parameters. */ h_str = duk_require_hstring(ctx, 0); duk_require_valid_index(ctx, 1); if (h_str == DUK_HTHREAD_STRING_HEX(thr)) { duk_set_top(ctx, 2); duk_hex_encode(ctx, 1); DUK_ASSERT_TOP(ctx, 2); } else if (h_str == DUK_HTHREAD_STRING_BASE64(thr)) { duk_set_top(ctx, 2); duk_base64_encode(ctx, 1); DUK_ASSERT_TOP(ctx, 2); #ifdef DUK_USE_JX } else if (h_str == DUK_HTHREAD_STRING_JX(thr)) { duk_bi_json_stringify_helper(ctx, 1 /*idx_value*/, 2 /*idx_replacer*/, 3 /*idx_space*/, DUK_JSON_FLAG_EXT_CUSTOM | DUK_JSON_FLAG_ASCII_ONLY | DUK_JSON_FLAG_AVOID_KEY_QUOTES /*flags*/); #endif #ifdef DUK_USE_JC } else if (h_str == DUK_HTHREAD_STRING_JC(thr)) { duk_bi_json_stringify_helper(ctx, 1 /*idx_value*/, 2 /*idx_replacer*/, 3 /*idx_space*/, DUK_JSON_FLAG_EXT_COMPATIBLE | DUK_JSON_FLAG_ASCII_ONLY /*flags*/); #endif } else { return DUK_RET_TYPE_ERROR; } return 1; }
duk_ret_t duk_bi_duk_object_dec(duk_context *ctx) { duk_hthread *thr = (duk_hthread *) ctx; duk_hstring *h_str; /* Vararg function: must be careful to check/require arguments. * The JSON helpers accept invalid indices and treat them like * non-existent optional parameters. */ h_str = duk_require_hstring(ctx, 0); duk_require_valid_index(ctx, 1); if (h_str == DUK_HTHREAD_STRING_HEX(thr)) { duk_set_top(ctx, 2); duk_hex_decode(ctx, 1); DUK_ASSERT_TOP(ctx, 2); } else if (h_str == DUK_HTHREAD_STRING_BASE64(thr)) { duk_set_top(ctx, 2); duk_base64_decode(ctx, 1); DUK_ASSERT_TOP(ctx, 2); #ifdef DUK_USE_JSONX } else if (h_str == DUK_HTHREAD_STRING_JSONX(thr)) { duk_bi_json_parse_helper(ctx, 1 /*idx_value*/, 2 /*idx_replacer*/, DUK_JSON_FLAG_EXT_CUSTOM /*flags*/); #endif #ifdef DUK_USE_JSONC } else if (h_str == DUK_HTHREAD_STRING_JSONC(thr)) { duk_bi_json_parse_helper(ctx, 1 /*idx_value*/, 2 /*idx_replacer*/, DUK_JSON_FLAG_EXT_COMPATIBLE /*flags*/); #endif } else { return DUK_RET_TYPE_ERROR; } return 1; }
DUK_INTERNAL duk_ret_t duk_bi_function_constructor(duk_context *ctx) { duk_hthread *thr = (duk_hthread *) ctx; duk_hstring *h_sourcecode; duk_idx_t nargs; duk_idx_t i; duk_small_uint_t comp_flags; duk_hcompiledfunction *func; duk_hobject *outer_lex_env; duk_hobject *outer_var_env; /* normal and constructor calls have identical semantics */ nargs = duk_get_top(ctx); for (i = 0; i < nargs; i++) { duk_to_string(ctx, i); } if (nargs == 0) { duk_push_string(ctx, ""); duk_push_string(ctx, ""); } else if (nargs == 1) { /* XXX: cover this with the generic >1 case? */ duk_push_string(ctx, ""); } else { duk_insert(ctx, 0); /* [ arg1 ... argN-1 body] -> [body arg1 ... argN-1] */ duk_push_string(ctx, ","); duk_insert(ctx, 1); duk_join(ctx, nargs - 1); } /* [ body formals ], formals is comma separated list that needs to be parsed */ DUK_ASSERT_TOP(ctx, 2); /* XXX: this placeholder is not always correct, but use for now. * It will fail in corner cases; see test-dev-func-cons-args.js. */ duk_push_string(ctx, "function("); duk_dup(ctx, 1); duk_push_string(ctx, "){"); duk_dup(ctx, 0); duk_push_string(ctx, "}"); duk_concat(ctx, 5); /* [ body formals source ] */ DUK_ASSERT_TOP(ctx, 3); /* strictness is not inherited, intentional */ comp_flags = DUK_JS_COMPILE_FLAG_FUNCEXPR; duk_push_hstring_stridx(ctx, DUK_STRIDX_COMPILE); /* XXX: copy from caller? */ /* XXX: ignored now */ h_sourcecode = duk_require_hstring(ctx, -2); duk_js_compile(thr, (const duk_uint8_t *) DUK_HSTRING_GET_DATA(h_sourcecode), (duk_size_t) DUK_HSTRING_GET_BYTELEN(h_sourcecode), comp_flags); func = (duk_hcompiledfunction *) duk_get_hobject(ctx, -1); DUK_ASSERT(func != NULL); DUK_ASSERT(DUK_HOBJECT_IS_COMPILEDFUNCTION((duk_hobject *) func)); /* [ body formals source template ] */ /* only outer_lex_env matters, as functions always get a new * variable declaration environment. */ outer_lex_env = thr->builtins[DUK_BIDX_GLOBAL_ENV]; outer_var_env = thr->builtins[DUK_BIDX_GLOBAL_ENV]; duk_js_push_closure(thr, func, outer_var_env, outer_lex_env, 1 /*add_auto_proto*/); /* [ body formals source template closure ] */ return 1; }
DUK_INTERNAL void duk_regexp_compile(duk_hthread *thr) { duk_context *ctx = (duk_context *) thr; duk_re_compiler_ctx re_ctx; duk_lexer_point lex_point; duk_hstring *h_pattern; duk_hstring *h_flags; duk__re_disjunction_info ign_disj; DUK_ASSERT(thr != NULL); DUK_ASSERT(ctx != NULL); /* * Args validation */ /* TypeError if fails */ h_pattern = duk_require_hstring(ctx, -2); h_flags = duk_require_hstring(ctx, -1); /* * Create normalized 'source' property (E5 Section 15.10.3). */ /* [ ... pattern flags ] */ duk__create_escaped_source(thr, -2); /* [ ... pattern flags escaped_source ] */ /* * Init compilation context */ /* [ ... pattern flags escaped_source buffer ] */ DUK_MEMZERO(&re_ctx, sizeof(re_ctx)); DUK_LEXER_INITCTX(&re_ctx.lex); /* duplicate zeroing, expect for (possible) NULL inits */ re_ctx.thr = thr; re_ctx.lex.thr = thr; re_ctx.lex.input = DUK_HSTRING_GET_DATA(h_pattern); re_ctx.lex.input_length = DUK_HSTRING_GET_BYTELEN(h_pattern); re_ctx.lex.token_limit = DUK_RE_COMPILE_TOKEN_LIMIT; re_ctx.recursion_limit = DUK_USE_REGEXP_COMPILER_RECLIMIT; re_ctx.re_flags = duk__parse_regexp_flags(thr, h_flags); DUK_BW_INIT_PUSHBUF(thr, &re_ctx.bw, DUK__RE_INITIAL_BUFSIZE); DUK_DD(DUK_DDPRINT("regexp compiler ctx initialized, flags=0x%08lx, recursion_limit=%ld", (unsigned long) re_ctx.re_flags, (long) re_ctx.recursion_limit)); /* * Init lexer */ lex_point.offset = 0; /* expensive init, just want to fill window */ lex_point.line = 1; DUK_LEXER_SETPOINT(&re_ctx.lex, &lex_point); /* * Compilation */ DUK_DD(DUK_DDPRINT("starting regexp compilation")); duk__append_u32(&re_ctx, DUK_REOP_SAVE); duk__append_u32(&re_ctx, 0); duk__parse_disjunction(&re_ctx, 1 /*expect_eof*/, &ign_disj); duk__append_u32(&re_ctx, DUK_REOP_SAVE); duk__append_u32(&re_ctx, 1); duk__append_u32(&re_ctx, DUK_REOP_MATCH); /* * Check for invalid backreferences; note that it is NOT an error * to back-reference a capture group which has not yet been introduced * in the pattern (as in /\1(foo)/); in fact, the backreference will * always match! It IS an error to back-reference a capture group * which will never be introduced in the pattern. Thus, we can check * for such references only after parsing is complete. */ if (re_ctx.highest_backref > re_ctx.captures) { DUK_ERROR_SYNTAX(thr, DUK_STR_INVALID_BACKREFS); } /* * Emit compiled regexp header: flags, ncaptures * (insertion order inverted on purpose) */ duk__insert_u32(&re_ctx, 0, (re_ctx.captures + 1) * 2); duk__insert_u32(&re_ctx, 0, re_ctx.re_flags); /* [ ... pattern flags escaped_source buffer ] */ DUK_BW_COMPACT(thr, &re_ctx.bw); duk_to_string(ctx, -1); /* coerce to string */ /* [ ... pattern flags escaped_source bytecode ] */ /* * Finalize stack */ duk_remove(ctx, -4); /* -> [ ... flags escaped_source bytecode ] */ duk_remove(ctx, -3); /* -> [ ... escaped_source bytecode ] */ DUK_DD(DUK_DDPRINT("regexp compilation successful, bytecode: %!T, escaped source: %!T", (duk_tval *) duk_get_tval(ctx, -1), (duk_tval *) duk_get_tval(ctx, -2))); }
/* Shared helper for providing .source, .global, .multiline, etc getters. */ DUK_INTERNAL duk_ret_t duk_bi_regexp_prototype_shared_getter(duk_hthread *thr) { duk_hstring *h_bc; duk_small_uint_t re_flags; duk_hobject *h; duk_int_t magic; DUK_ASSERT_TOP(thr, 0); duk_push_this(thr); h = duk_require_hobject(thr, -1); magic = duk_get_current_magic(thr); if (DUK_HOBJECT_GET_CLASS_NUMBER(h) == DUK_HOBJECT_CLASS_REGEXP) { duk_get_prop_stridx_short(thr, 0, DUK_STRIDX_INT_SOURCE); duk_get_prop_stridx_short(thr, 0, DUK_STRIDX_INT_BYTECODE); h_bc = duk_require_hstring(thr, -1); re_flags = (duk_small_uint_t) DUK_HSTRING_GET_DATA(h_bc)[0]; /* Safe even if h_bc length is 0 (= NUL) */ duk_pop(thr); } else if (h == thr->builtins[DUK_BIDX_REGEXP_PROTOTYPE]) { /* In ES2015 and ES2016 a TypeError would be thrown here. * However, this had real world issues so ES2017 draft * allows RegExp.prototype specifically, returning '(?:)' * for .source and undefined for all flags. */ if (magic != 16 /* .source */) { return 0; } duk_push_literal(thr, "(?:)"); /* .source handled by switch-case */ re_flags = 0; } else { DUK_DCERROR_TYPE_INVALID_ARGS(thr); } /* [ regexp source ] */ switch (magic) { case 0: { /* global */ duk_push_boolean(thr, (re_flags & DUK_RE_FLAG_GLOBAL)); break; } case 1: { /* ignoreCase */ duk_push_boolean(thr, (re_flags & DUK_RE_FLAG_IGNORE_CASE)); break; } case 2: { /* multiline */ duk_push_boolean(thr, (re_flags & DUK_RE_FLAG_MULTILINE)); break; } #if 0 /* Don't provide until implemented to avoid interfering with feature * detection in user code. */ case 3: /* sticky */ case 4: { /* unicode */ duk_push_false(thr); break; } #endif default: { /* source */ /* leave 'source' on top */ break; } } return 1; }
/* Object.defineProperty() equivalent C binding. */ DUK_EXTERNAL void duk_def_prop(duk_context *ctx, duk_idx_t obj_index, duk_uint_t flags) { duk_hthread *thr = (duk_hthread *) ctx; duk_idx_t idx_base; duk_hobject *obj; duk_hstring *key; duk_idx_t idx_value; duk_hobject *get; duk_hobject *set; duk_uint_t is_data_desc; duk_uint_t is_acc_desc; DUK_ASSERT_CTX_VALID(ctx); obj = duk_require_hobject(ctx, obj_index); is_data_desc = flags & (DUK_DEFPROP_HAVE_VALUE | DUK_DEFPROP_HAVE_WRITABLE); is_acc_desc = flags & (DUK_DEFPROP_HAVE_GETTER | DUK_DEFPROP_HAVE_SETTER); if (is_data_desc && is_acc_desc) { /* "Have" flags must not be conflicting so that they would * apply to both a plain property and an accessor at the same * time. */ goto fail_invalid_desc; } idx_base = duk_get_top_index(ctx); if (flags & DUK_DEFPROP_HAVE_SETTER) { duk_require_type_mask(ctx, idx_base, DUK_TYPE_MASK_UNDEFINED | DUK_TYPE_MASK_OBJECT | DUK_TYPE_MASK_LIGHTFUNC); set = duk_get_hobject_or_lfunc_coerce(ctx, idx_base); if (set != NULL && !DUK_HOBJECT_IS_CALLABLE(set)) { goto fail_not_callable; } idx_base--; } else { set = NULL; } if (flags & DUK_DEFPROP_HAVE_GETTER) { duk_require_type_mask(ctx, idx_base, DUK_TYPE_MASK_UNDEFINED | DUK_TYPE_MASK_OBJECT | DUK_TYPE_MASK_LIGHTFUNC); get = duk_get_hobject_or_lfunc_coerce(ctx, idx_base); if (get != NULL && !DUK_HOBJECT_IS_CALLABLE(get)) { goto fail_not_callable; } idx_base--; } else { get = NULL; } if (flags & DUK_DEFPROP_HAVE_VALUE) { idx_value = idx_base; idx_base--; } else { idx_value = (duk_idx_t) -1; } key = duk_require_hstring(ctx, idx_base); duk_require_valid_index(ctx, idx_base); duk_hobject_define_property_helper(ctx, flags /*defprop_flags*/, obj, key, idx_value, get, set); /* Clean up stack */ duk_set_top(ctx, idx_base); /* [ ... obj ... ] */ return; fail_invalid_desc: DUK_ERROR(thr, DUK_ERR_TYPE_ERROR, DUK_STR_INVALID_DESCRIPTOR); return; fail_not_callable: DUK_ERROR(thr, DUK_ERR_TYPE_ERROR, DUK_STR_NOT_CALLABLE); return; }
DUK_INTERNAL duk_ret_t duk_bi_object_constructor_define_property(duk_context *ctx) { duk_hobject *obj; duk_hstring *key; duk_hobject *get; duk_hobject *set; duk_idx_t idx_value; duk_uint_t defprop_flags; DUK_ASSERT(ctx != NULL); DUK_DDD(DUK_DDDPRINT("Object.defineProperty(): ctx=%p obj=%!T key=%!T desc=%!T", (void *) ctx, (duk_tval *) duk_get_tval(ctx, 0), (duk_tval *) duk_get_tval(ctx, 1), (duk_tval *) duk_get_tval(ctx, 2))); /* [ obj key desc ] */ /* Lightfuncs are currently supported by coercing to a temporary * Function object; changes will be allowed (the coerced value is * extensible) but will be lost. */ obj = duk_require_hobject_or_lfunc_coerce(ctx, 0); (void) duk_to_string(ctx, 1); key = duk_require_hstring(ctx, 1); (void) duk_require_hobject(ctx, 2); DUK_ASSERT(obj != NULL); DUK_ASSERT(key != NULL); DUK_ASSERT(duk_get_hobject(ctx, 2) != NULL); /* * Validate and convert argument property descriptor (an Ecmascript * object) into a set of defprop_flags and possibly property value, * getter, and/or setter values on the value stack. * * Lightfunc set/get values are coerced to full Functions. */ duk_hobject_prepare_property_descriptor(ctx, 2 /*idx_desc*/, &defprop_flags, &idx_value, &get, &set); /* * Use Object.defineProperty() helper for the actual operation. */ duk_hobject_define_property_helper(ctx, defprop_flags, obj, key, idx_value, get, set); /* Ignore the normalize/validate helper outputs on the value stack, * they're popped automatically. */ /* * Return target object. */ duk_push_hobject(ctx, obj); return 1; }
void duk_regexp_compile(duk_hthread *thr) { duk_context *ctx = (duk_context *) thr; duk_re_compiler_ctx re_ctx; duk_lexer_point lex_point; duk_hstring *h_pattern; duk_hstring *h_flags; duk_hbuffer_dynamic *h_buffer; DUK_ASSERT(thr != NULL); DUK_ASSERT(ctx != NULL); /* * Args validation */ /* TypeError if fails */ h_pattern = duk_require_hstring(ctx, -2); h_flags = duk_require_hstring(ctx, -1); /* * Create normalized 'source' property (E5 Section 15.10.3). */ /* [ ... pattern flags ] */ create_escaped_source(thr, -2); /* [ ... pattern flags escaped_source ] */ /* * Init compilation context */ duk_push_dynamic_buffer(ctx, 0); h_buffer = (duk_hbuffer_dynamic *) duk_require_hbuffer(ctx, -1); DUK_ASSERT(DUK_HBUFFER_HAS_DYNAMIC(h_buffer)); /* [ ... pattern flags escaped_source buffer ] */ DUK_MEMSET(&re_ctx, 0, sizeof(re_ctx)); DUK_LEXER_INITCTX(&re_ctx.lex); /* duplicate zeroing, expect for (possible) NULL inits */ re_ctx.thr = thr; re_ctx.lex.thr = thr; re_ctx.lex.input = DUK_HSTRING_GET_DATA(h_pattern); re_ctx.lex.input_length = DUK_HSTRING_GET_BYTELEN(h_pattern); re_ctx.buf = h_buffer; re_ctx.recursion_limit = DUK_RE_COMPILE_RECURSION_LIMIT; re_ctx.re_flags = parse_regexp_flags(thr, h_flags); DUK_DDPRINT("regexp compiler ctx initialized, flags=0x%08x, recursion_limit=%d", (unsigned int) re_ctx.re_flags, (int) re_ctx.recursion_limit); /* * Init lexer */ lex_point.offset = 0; /* expensive init, just want to fill window */ lex_point.line = 1; DUK_LEXER_SETPOINT(&re_ctx.lex, &lex_point); /* * Compilation */ DUK_DPRINT("starting regexp compilation"); append_u32(&re_ctx, DUK_REOP_SAVE); append_u32(&re_ctx, 0); (void) parse_disjunction(&re_ctx, 1); /* 1 = expect eof */ append_u32(&re_ctx, DUK_REOP_SAVE); append_u32(&re_ctx, 1); append_u32(&re_ctx, DUK_REOP_MATCH); DUK_DPRINT("regexp bytecode size (before header) is %d bytes", (int) DUK_HBUFFER_GET_SIZE(re_ctx.buf)); /* * Check for invalid backreferences; note that it is NOT an error * to back-reference a capture group which has not yet been introduced * in the pattern (as in /\1(foo)/); in fact, the backreference will * always match! It IS an error to back-reference a capture group * which will never be introduced in the pattern. Thus, we can check * for such references only after parsing is complete. */ if (re_ctx.highest_backref > re_ctx.captures) { DUK_ERROR(thr, DUK_ERR_SYNTAX_ERROR, "invalid backreference(s)"); } /* * Emit compiled regexp header: flags, ncaptures * (insertion order inverted on purpose) */ insert_u32(&re_ctx, 0, (re_ctx.captures + 1) * 2); insert_u32(&re_ctx, 0, re_ctx.re_flags); DUK_DPRINT("regexp bytecode size (after header) is %d bytes", (int) DUK_HBUFFER_GET_SIZE(re_ctx.buf)); DUK_DDDPRINT("compiled regexp: %!xO", re_ctx.buf); /* [ ... pattern flags escaped_source buffer ] */ duk_to_string(ctx, -1); /* coerce to string */ /* [ ... pattern flags escaped_source bytecode ] */ /* * Finalize stack */ duk_remove(ctx, -4); /* -> [ ... flags escaped_source bytecode ] */ duk_remove(ctx, -3); /* -> [ ... escaped_source bytecode ] */ DUK_DPRINT("regexp compilation successful, bytecode: %!T, escaped source: %!T", duk_get_tval(ctx, -1), duk_get_tval(ctx, -2)); }