/* * QuickJS Javascript Engine * * Copyright (c) 2017-2021 Fabrice Bellard * Copyright (c) 2017-2021 Charlie Gordon * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ #include "libc/assert.h" #include "libc/runtime/runtime.h" #include "libc/str/str.h" #include "third_party/quickjs/internal.h" #include "third_party/quickjs/libregexp.h" asm(".ident\t\"\\n\\n\ QuickJS (MIT License)\\n\ Copyright (c) 2017-2021 Fabrice Bellard\\n\ Copyright (c) 2017-2021 Charlie Gordon\""); asm(".include \"libc/disclaimer.inc\""); static __exception int js_parse_assign_expr(JSParseState *); static __exception int js_parse_assign_expr2(JSParseState *, int); static __exception int js_parse_expr(JSParseState *); static __exception int js_parse_function_decl(JSParseState *, JSParseFunctionEnum, JSFunctionKindEnum, JSAtom, const uint8_t *, int); static __exception int js_parse_function_decl2(JSParseState *, JSParseFunctionEnum, JSFunctionKindEnum, JSAtom, const uint8_t *, int, JSParseExportEnum, JSFunctionDef **); static __exception int js_parse_postfix_expr(JSParseState *, int); static __exception int js_parse_unary(JSParseState *, int); void js_parse_init(JSContext *ctx, JSParseState *s, const char *input, size_t input_len, const char *filename) { bzero(s, sizeof(*s)); s->ctx = ctx; s->filename = filename; s->line_num = 1; s->buf_ptr = (const uint8_t *)input; s->buf_end = s->buf_ptr + input_len; s->token.val = ' '; s->token.line_num = 1; } /* Note: all the fields are already sealed except length */ static int seal_template_obj(JSContext *ctx, JSValueConst obj) { JSObject *p; JSShapeProperty *prs; p = JS_VALUE_GET_OBJ(obj); prs = find_own_property1(p, JS_ATOM_length); if (prs) { if (js_update_property_flags(ctx, p, &prs, prs->flags & ~(JS_PROP_CONFIGURABLE | JS_PROP_WRITABLE))) return -1; } p->extensible = FALSE; return 0; } static __exception int emit_push_const(JSParseState *s, JSValueConst val, BOOL as_atom) { int idx; if (JS_VALUE_GET_TAG(val) == JS_TAG_STRING && as_atom) { JSAtom atom; /* warning: JS_NewAtomStr frees the string value */ JS_DupValue(s->ctx, val); atom = JS_NewAtomStr(s->ctx, JS_VALUE_GET_STRING(val)); if (atom != JS_ATOM_NULL && !__JS_AtomIsTaggedInt(atom)) { emit_op(s, OP_push_atom_value); emit_u32(s, atom); return 0; } } idx = cpool_add(s, JS_DupValue(s->ctx, val)); if (idx < 0) return -1; emit_op(s, OP_push_const); emit_u32(s, idx); return 0; } int js_parse_template_part(JSParseState *s, const uint8_t *p) { uint32_t c; StringBuffer b_s, *b = &b_s; /* p points to the first byte of the template part */ if (string_buffer_init(s->ctx, b, 32)) goto fail; for(;;) { if (p >= s->buf_end) goto unexpected_eof; c = *p++; if (c == '`') { /* template end part */ break; } if (c == '$' && *p == '{') { /* template start or middle part */ p++; break; } if (c == '\\') { if (string_buffer_putc8(b, c)) goto fail; if (p >= s->buf_end) goto unexpected_eof; c = *p++; } /* newline sequences are normalized as single '\n' bytes */ if (c == '\r') { if (*p == '\n') p++; c = '\n'; } if (c == '\n') { s->line_num++; } else if (c >= 0x80) { const uint8_t *p_next; c = unicode_from_utf8(p - 1, UTF8_CHAR_LEN_MAX, &p_next); if (c > 0x10FFFF) { js_parse_error(s, "invalid UTF-8 sequence"); goto fail; } p = p_next; } if (string_buffer_putc(b, c)) goto fail; } s->token.val = TOK_TEMPLATE; s->token.u.str.sep = c; s->token.u.str.str = string_buffer_end(b); s->buf_ptr = p; return 0; unexpected_eof: js_parse_error(s, "unexpected end of string"); fail: string_buffer_free(b); return -1; } int js_parse_string(JSParseState *s, int sep, BOOL do_throw, const uint8_t *p, JSToken *token, const uint8_t **pp) { int ret; uint32_t c; StringBuffer b_s, *b = &b_s; /* string */ if (string_buffer_init(s->ctx, b, 32)) goto fail; for(;;) { if (p >= s->buf_end) goto invalid_char; c = *p; if (c < 0x20) { if (!s->cur_func) { if (do_throw) js_parse_error(s, "invalid character in a JSON string"); goto fail; } if (sep == '`') { if (c == '\r') { if (p[1] == '\n') p++; c = '\n'; } /* do not update s->line_num */ } else if (c == '\n' || c == '\r') goto invalid_char; } p++; if (c == sep) break; if (c == '$' && *p == '{' && sep == '`') { /* template start or middle part */ p++; break; } if (c == '\\') { c = *p; /* XXX: need a specific JSON case to avoid accepting invalid escapes */ switch(c) { case '\0': if (p >= s->buf_end) goto invalid_char; p++; break; case '\'': case '\"': case '\\': p++; break; case '\r': /* accept DOS and MAC newline sequences */ if (p[1] == '\n') { p++; } /* fall thru */ case '\n': /* ignore escaped newline sequence */ p++; if (sep != '`') s->line_num++; continue; default: if (c >= '0' && c <= '9') { if (!s->cur_func) goto invalid_escape; /* JSON case */ if (!(s->cur_func->js_mode & JS_MODE_STRICT) && sep != '`') goto parse_escape; if (c == '0' && !(p[1] >= '0' && p[1] <= '9')) { p++; c = '\0'; } else { if (c >= '8' || sep == '`') { /* Note: according to ES2021, \8 and \9 are not accepted in strict mode or in templates. */ goto invalid_escape; } else { if (do_throw) js_parse_error(s, "octal escape sequences are not allowed in strict mode"); } goto fail; } } else if (c >= 0x80) { const uint8_t *p_next; c = unicode_from_utf8(p, UTF8_CHAR_LEN_MAX, &p_next); if (c > 0x10FFFF) { goto invalid_utf8; } p = p_next; /* LS or PS are skipped */ if (c == CP_LS || c == CP_PS) continue; } else { parse_escape: ret = lre_parse_escape(&p, TRUE); if (ret == -1) { invalid_escape: if (do_throw) js_parse_error(s, "malformed escape sequence in string literal"); goto fail; } else if (ret < 0) { /* ignore the '\' (could output a warning) */ p++; } else { c = ret; } } break; } } else if (c >= 0x80) { const uint8_t *p_next; c = unicode_from_utf8(p - 1, UTF8_CHAR_LEN_MAX, &p_next); if (c > 0x10FFFF) goto invalid_utf8; p = p_next; } if (string_buffer_putc(b, c)) goto fail; } token->val = TOK_STRING; token->u.str.sep = c; token->u.str.str = string_buffer_end(b); *pp = p; return 0; invalid_utf8: if (do_throw) js_parse_error(s, "invalid UTF-8 sequence"); goto fail; invalid_char: if (do_throw) js_parse_error(s, "unexpected end of string"); fail: string_buffer_free(b); return -1; } int js_parse_expect(JSParseState *s, int tok) { if (s->token.val != tok) { /* XXX: dump token correctly in all cases */ return js_parse_error(s, "expecting '%c'", tok); } return next_token(s); } static int js_parse_expect_semi(JSParseState *s) { if (s->token.val != ';') { /* automatic insertion of ';' */ if (s->token.val == TOK_EOF || s->token.val == '}' || s->got_lf) { return 0; } return js_parse_error(s, "expecting '%c'", ';'); } return next_token(s); } static __exception int js_parse_statement_or_decl(JSParseState *s, int decl_mask); static __exception int js_parse_statement(JSParseState *s) { return js_parse_statement_or_decl(s, 0); } static int get_first_lexical_var(JSFunctionDef *fd, int scope) { while (scope >= 0) { int scope_idx = fd->scopes[scope].first; if (scope_idx >= 0) return scope_idx; scope = fd->scopes[scope].parent; } return -1; } static void pop_scope(JSParseState *s) { if (s->cur_func) { /* disable scoped variables */ JSFunctionDef *fd = s->cur_func; int scope = fd->scope_level; emit_op(s, OP_leave_scope); emit_u16(s, scope); fd->scope_level = fd->scopes[scope].parent; fd->scope_first = get_first_lexical_var(fd, fd->scope_level); } } static __exception int js_parse_block(JSParseState *s) { if (js_parse_expect(s, '{')) return -1; if (s->token.val != '}') { push_scope(s); for(;;) { if (js_parse_statement_or_decl(s, DECL_MASK_ALL)) return -1; if (s->token.val == '}') break; } pop_scope(s); } if (next_token(s)) return -1; return 0; } static __exception int js_parse_template(JSParseState *s, int call, int *argc) { JSContext *ctx = s->ctx; JSValue raw_array, template_object; JSToken cooked; int depth, ret; raw_array = JS_UNDEFINED; /* avoid warning */ template_object = JS_UNDEFINED; /* avoid warning */ if (call) { /* Create a template object: an array of cooked strings */ /* Create an array of raw strings and store it to the raw property */ template_object = JS_NewArray(ctx); if (JS_IsException(template_object)) return -1; // pool_idx = s->cur_func->cpool_count; ret = emit_push_const(s, template_object, 0); JS_FreeValue(ctx, template_object); if (ret) return -1; raw_array = JS_NewArray(ctx); if (JS_IsException(raw_array)) return -1; if (JS_DefinePropertyValue(ctx, template_object, JS_ATOM_raw, raw_array, JS_PROP_THROW) < 0) { return -1; } } depth = 0; while (s->token.val == TOK_TEMPLATE) { const uint8_t *p = s->token.ptr + 1; cooked = s->token; if (call) { if (JS_DefinePropertyValueUint32(ctx, raw_array, depth, JS_DupValue(ctx, s->token.u.str.str), JS_PROP_ENUMERABLE | JS_PROP_THROW) < 0) { return -1; } /* re-parse the string with escape sequences but do not throw a syntax error if it contains invalid sequences */ if (js_parse_string(s, '`', FALSE, p, &cooked, &p)) { cooked.u.str.str = JS_UNDEFINED; } if (JS_DefinePropertyValueUint32(ctx, template_object, depth, cooked.u.str.str, JS_PROP_ENUMERABLE | JS_PROP_THROW) < 0) { return -1; } } else { JSString *str; /* re-parse the string with escape sequences and throw a syntax error if it contains invalid sequences */ JS_FreeValue(ctx, s->token.u.str.str); s->token.u.str.str = JS_UNDEFINED; if (js_parse_string(s, '`', TRUE, p, &cooked, &p)) return -1; str = JS_VALUE_GET_STRING(cooked.u.str.str); if (str->len != 0 || depth == 0) { ret = emit_push_const(s, cooked.u.str.str, 1); JS_FreeValue(s->ctx, cooked.u.str.str); if (ret) return -1; if (depth == 0) { if (s->token.u.str.sep == '`') goto done1; emit_op(s, OP_get_field2); emit_atom(s, JS_ATOM_concat); } depth++; } else { JS_FreeValue(s->ctx, cooked.u.str.str); } } if (s->token.u.str.sep == '`') goto done; if (next_token(s)) return -1; if (js_parse_expr(s)) return -1; depth++; if (s->token.val != '}') { return js_parse_error(s, "expected '}' after template expression"); } /* XXX: should convert to string at this stage? */ free_token(s, &s->token); /* Resume TOK_TEMPLATE parsing (s->token.line_num and * s->token.ptr are OK) */ s->got_lf = FALSE; s->last_line_num = s->token.line_num; if (js_parse_template_part(s, s->buf_ptr)) return -1; } return js_parse_expect(s, TOK_TEMPLATE); done: if (call) { /* Seal the objects */ seal_template_obj(ctx, raw_array); seal_template_obj(ctx, template_object); *argc = depth + 1; } else { emit_op(s, OP_call_method); emit_u16(s, depth - 1); } done1: return next_token(s); } static inline BOOL token_is_pseudo_keyword(JSParseState *s, JSAtom atom) { return s->token.val == TOK_IDENT && s->token.u.ident.atom == atom && !s->token.u.ident.has_escape; } static int js_parse_get_pos(JSParseState *s, JSParsePos *sp) { sp->last_line_num = s->last_line_num; sp->line_num = s->token.line_num; sp->ptr = s->token.ptr; sp->got_lf = s->got_lf; return 0; } static __exception int js_parse_seek_token(JSParseState *s, const JSParsePos *sp) { s->token.line_num = sp->last_line_num; s->line_num = sp->line_num; s->buf_ptr = sp->ptr; s->got_lf = sp->got_lf; return next_token(s); } /* test if the current token is a let keyword. Use simplistic look-ahead scanner */ static int is_let(JSParseState *s, int decl_mask) { int res = FALSE; if (token_is_pseudo_keyword(s, JS_ATOM_let)) { #if 1 JSParsePos pos; js_parse_get_pos(s, &pos); for (;;) { if (next_token(s)) { res = -1; break; } if (s->token.val == '[') { /* let [ is a syntax restriction: it never introduces an ExpressionStatement */ res = TRUE; break; } if (s->token.val == '{' || (s->token.val == TOK_IDENT && !s->token.u.ident.is_reserved) || s->token.val == TOK_LET || s->token.val == TOK_YIELD || s->token.val == TOK_AWAIT) { /* Check for possible ASI if not scanning for Declaration */ /* XXX: should also check that `{` introduces a BindingPattern, but Firefox does not and rejects eval("let=1;let\n{if(1)2;}") */ if (s->last_line_num == s->token.line_num || (decl_mask & DECL_MASK_OTHER)) { res = TRUE; break; } break; } break; } if (js_parse_seek_token(s, &pos)) { res = -1; } #else int tok = peek_token(s, TRUE); if (tok == '{' || tok == TOK_IDENT || peek_token(s, FALSE) == '[') { res = TRUE; } #endif } return res; } static int peek_token(JSParseState *s, BOOL no_line_terminator) { const uint8_t *p = s->buf_ptr; return simple_next_token(&p, no_line_terminator); } /* test if the current token is a label. Use simplistic look-ahead scanner */ static BOOL is_label(JSParseState *s) { return (s->token.val == TOK_IDENT && !s->token.u.ident.is_reserved && peek_token(s, FALSE) == ':'); } static BOOL token_is_ident(int tok) { /* Accept keywords and reserved words as property names */ return (tok == TOK_IDENT || (tok >= TOK_FIRST_KEYWORD && tok <= TOK_LAST_KEYWORD)); } /* if the property is an expression, name = JS_ATOM_NULL */ static int __exception js_parse_property_name(JSParseState *s, JSAtom *pname, BOOL allow_method, BOOL allow_var, BOOL allow_private) { int is_private = 0; BOOL is_non_reserved_ident; JSAtom name; int prop_type; prop_type = PROP_TYPE_IDENT; if (allow_method) { if (token_is_pseudo_keyword(s, JS_ATOM_get) || token_is_pseudo_keyword(s, JS_ATOM_set)) { /* get x(), set x() */ name = JS_DupAtom(s->ctx, s->token.u.ident.atom); if (next_token(s)) goto fail1; if (s->token.val == ':' || s->token.val == ',' || s->token.val == '}' || s->token.val == '(') { is_non_reserved_ident = TRUE; goto ident_found; } prop_type = PROP_TYPE_GET + (name == JS_ATOM_set); JS_FreeAtom(s->ctx, name); } else if (s->token.val == '*') { if (next_token(s)) goto fail; prop_type = PROP_TYPE_STAR; } else if (token_is_pseudo_keyword(s, JS_ATOM_async) && peek_token(s, TRUE) != '\n') { name = JS_DupAtom(s->ctx, s->token.u.ident.atom); if (next_token(s)) goto fail1; if (s->token.val == ':' || s->token.val == ',' || s->token.val == '}' || s->token.val == '(') { is_non_reserved_ident = TRUE; goto ident_found; } JS_FreeAtom(s->ctx, name); if (s->token.val == '*') { if (next_token(s)) goto fail; prop_type = PROP_TYPE_ASYNC_STAR; } else { prop_type = PROP_TYPE_ASYNC; } } } if (token_is_ident(s->token.val)) { /* variable can only be a non-reserved identifier */ is_non_reserved_ident = (s->token.val == TOK_IDENT && !s->token.u.ident.is_reserved); /* keywords and reserved words have a valid atom */ name = JS_DupAtom(s->ctx, s->token.u.ident.atom); if (next_token(s)) goto fail1; ident_found: if (is_non_reserved_ident && prop_type == PROP_TYPE_IDENT && allow_var) { if (!(s->token.val == ':' || (s->token.val == '(' && allow_method))) { prop_type = PROP_TYPE_VAR; } } } else if (s->token.val == TOK_STRING) { name = JS_ValueToAtom(s->ctx, s->token.u.str.str); if (name == JS_ATOM_NULL) goto fail; if (next_token(s)) goto fail1; } else if (s->token.val == TOK_NUMBER) { JSValue val; val = s->token.u.num.val; #ifdef CONFIG_BIGNUM if (JS_VALUE_GET_TAG(val) == JS_TAG_BIG_FLOAT) { JSBigFloat *p = JS_VALUE_GET_PTR(val); val = s->ctx->rt->bigfloat_ops. mul_pow10_to_float64(s->ctx, &p->num, s->token.u.num.exponent); if (JS_IsException(val)) goto fail; name = JS_ValueToAtom(s->ctx, val); JS_FreeValue(s->ctx, val); } else #endif { name = JS_ValueToAtom(s->ctx, val); } if (name == JS_ATOM_NULL) goto fail; if (next_token(s)) goto fail1; } else if (s->token.val == '[') { if (next_token(s)) goto fail; if (js_parse_expr(s)) goto fail; if (js_parse_expect(s, ']')) goto fail; name = JS_ATOM_NULL; } else if (s->token.val == TOK_PRIVATE_NAME && allow_private) { name = JS_DupAtom(s->ctx, s->token.u.ident.atom); if (next_token(s)) goto fail1; is_private = PROP_TYPE_PRIVATE; } else { goto invalid_prop; } if (prop_type != PROP_TYPE_IDENT && prop_type != PROP_TYPE_VAR && s->token.val != '(') { JS_FreeAtom(s->ctx, name); invalid_prop: js_parse_error(s, "invalid property name"); goto fail; } *pname = name; return prop_type | is_private; fail1: JS_FreeAtom(s->ctx, name); fail: *pname = JS_ATOM_NULL; return -1; } static void set_object_name_computed(JSParseState *s) { JSFunctionDef *fd = s->cur_func; int opcode; opcode = get_prev_opcode(fd); if (opcode == OP_set_name) { /* XXX: should free atom after OP_set_name? */ fd->byte_code.size = fd->last_opcode_pos; fd->last_opcode_pos = -1; emit_op(s, OP_set_name_computed); } else if (opcode == OP_set_class_name) { int define_class_pos; define_class_pos = fd->last_opcode_pos + 1 - get_u32(fd->byte_code.buf + fd->last_opcode_pos + 1); assert(fd->byte_code.buf[define_class_pos] == OP_define_class); fd->byte_code.buf[define_class_pos] = OP_define_class_computed; fd->last_opcode_pos = -1; } } static void set_object_name(JSParseState *s, JSAtom name) { JSFunctionDef *fd = s->cur_func; int opcode; opcode = get_prev_opcode(fd); if (opcode == OP_set_name) { /* XXX: should free atom after OP_set_name? */ fd->byte_code.size = fd->last_opcode_pos; fd->last_opcode_pos = -1; emit_op(s, OP_set_name); emit_atom(s, name); } else if (opcode == OP_set_class_name) { int define_class_pos; JSAtom atom; define_class_pos = fd->last_opcode_pos + 1 - get_u32(fd->byte_code.buf + fd->last_opcode_pos + 1); assert(fd->byte_code.buf[define_class_pos] == OP_define_class); /* for consistency we free the previous atom which is JS_ATOM_empty_string */ atom = get_u32(fd->byte_code.buf + define_class_pos + 1); JS_FreeAtom(s->ctx, atom); put_u32(fd->byte_code.buf + define_class_pos + 1, JS_DupAtom(s->ctx, name)); fd->last_opcode_pos = -1; } } static __exception int js_parse_object_literal(JSParseState *s) { JSAtom name = JS_ATOM_NULL; const uint8_t *start_ptr; int start_line, prop_type; BOOL has_proto; if (next_token(s)) goto fail; /* XXX: add an initial length that will be patched back */ emit_op(s, OP_object); has_proto = FALSE; while (s->token.val != '}') { /* specific case for getter/setter */ start_ptr = s->token.ptr; start_line = s->token.line_num; if (s->token.val == TOK_ELLIPSIS) { if (next_token(s)) return -1; if (js_parse_assign_expr(s)) return -1; emit_op(s, OP_null); /* dummy excludeList */ emit_op(s, OP_copy_data_properties); emit_u8(s, 2 | (1 << 2) | (0 << 5)); emit_op(s, OP_drop); /* pop excludeList */ emit_op(s, OP_drop); /* pop src object */ goto next; } prop_type = js_parse_property_name(s, &name, TRUE, TRUE, FALSE); if (prop_type < 0) goto fail; if (prop_type == PROP_TYPE_VAR) { /* shortcut for x: x */ emit_op(s, OP_scope_get_var); emit_atom(s, name); emit_u16(s, s->cur_func->scope_level); emit_op(s, OP_define_field); emit_atom(s, name); } else if (s->token.val == '(') { BOOL is_getset = (prop_type == PROP_TYPE_GET || prop_type == PROP_TYPE_SET); JSParseFunctionEnum func_type; JSFunctionKindEnum func_kind; int op_flags; func_kind = JS_FUNC_NORMAL; if (is_getset) { func_type = JS_PARSE_FUNC_GETTER + prop_type - PROP_TYPE_GET; } else { func_type = JS_PARSE_FUNC_METHOD; if (prop_type == PROP_TYPE_STAR) func_kind = JS_FUNC_GENERATOR; else if (prop_type == PROP_TYPE_ASYNC) func_kind = JS_FUNC_ASYNC; else if (prop_type == PROP_TYPE_ASYNC_STAR) func_kind = JS_FUNC_ASYNC_GENERATOR; } if (js_parse_function_decl(s, func_type, func_kind, JS_ATOM_NULL, start_ptr, start_line)) goto fail; if (name == JS_ATOM_NULL) { emit_op(s, OP_define_method_computed); } else { emit_op(s, OP_define_method); emit_atom(s, name); } if (is_getset) { op_flags = OP_DEFINE_METHOD_GETTER + prop_type - PROP_TYPE_GET; } else { op_flags = OP_DEFINE_METHOD_METHOD; } emit_u8(s, op_flags | OP_DEFINE_METHOD_ENUMERABLE); } else { if (js_parse_expect(s, ':')) goto fail; if (js_parse_assign_expr(s)) goto fail; if (name == JS_ATOM_NULL) { set_object_name_computed(s); emit_op(s, OP_define_array_el); emit_op(s, OP_drop); } else if (name == JS_ATOM___proto__) { if (has_proto) { js_parse_error(s, "duplicate __proto__ property name"); goto fail; } emit_op(s, OP_set_proto); has_proto = TRUE; } else { set_object_name(s, name); emit_op(s, OP_define_field); emit_atom(s, name); } } JS_FreeAtom(s->ctx, name); next: name = JS_ATOM_NULL; if (s->token.val != ',') break; if (next_token(s)) goto fail; } if (js_parse_expect(s, '}')) goto fail; return 0; fail: JS_FreeAtom(s->ctx, name); return -1; } /* return TRUE if a regexp literal is allowed after this token */ static BOOL is_regexp_allowed(int tok) { switch (tok) { case TOK_NUMBER: case TOK_STRING: case TOK_REGEXP: case TOK_DEC: case TOK_INC: case TOK_NULL: case TOK_FALSE: case TOK_TRUE: case TOK_THIS: case ')': case ']': case '}': /* XXX: regexp may occur after */ case TOK_IDENT: return FALSE; default: return TRUE; } } static __exception int js_parse_regexp(JSParseState *s) { const uint8_t *p; BOOL in_class; StringBuffer b_s, *b = &b_s; StringBuffer b2_s, *b2 = &b2_s; uint32_t c; p = s->buf_ptr; p++; in_class = FALSE; if (string_buffer_init(s->ctx, b, 32)) return -1; if (string_buffer_init(s->ctx, b2, 1)) goto fail; for(;;) { if (p >= s->buf_end) { eof_error: js_parse_error(s, "unexpected end of regexp"); goto fail; } c = *p++; if (c == '\n' || c == '\r') { goto eol_error; } else if (c == '/') { if (!in_class) break; } else if (c == '[') { in_class = TRUE; } else if (c == ']') { /* XXX: incorrect as the first character in a class */ in_class = FALSE; } else if (c == '\\') { if (string_buffer_putc8(b, c)) goto fail; c = *p++; if (c == '\n' || c == '\r') goto eol_error; else if (c == '\0' && p >= s->buf_end) goto eof_error; else if (c >= 0x80) { const uint8_t *p_next; c = unicode_from_utf8(p - 1, UTF8_CHAR_LEN_MAX, &p_next); if (c > 0x10FFFF) { goto invalid_utf8; } p = p_next; if (c == CP_LS || c == CP_PS) goto eol_error; } } else if (c >= 0x80) { const uint8_t *p_next; c = unicode_from_utf8(p - 1, UTF8_CHAR_LEN_MAX, &p_next); if (c > 0x10FFFF) { invalid_utf8: js_parse_error(s, "invalid UTF-8 sequence"); goto fail; } p = p_next; /* LS or PS are considered as line terminator */ if (c == CP_LS || c == CP_PS) { eol_error: js_parse_error(s, "unexpected line terminator in regexp"); goto fail; } } if (string_buffer_putc(b, c)) goto fail; } /* flags */ for(;;) { const uint8_t *p_next = p; c = *p_next++; if (c >= 0x80) { c = unicode_from_utf8(p, UTF8_CHAR_LEN_MAX, &p_next); if (c > 0x10FFFF) { goto invalid_utf8; } } if (!lre_js_is_ident_next(c)) break; if (string_buffer_putc(b2, c)) goto fail; p = p_next; } s->token.val = TOK_REGEXP; s->token.u.regexp.body = string_buffer_end(b); s->token.u.regexp.flags = string_buffer_end(b2); s->buf_ptr = p; return 0; fail: string_buffer_free(b); string_buffer_free(b2); return -1; } /* XXX: improve speed with early bailout */ /* XXX: no longer works if regexps are present. Could use previous regexp parsing heuristics to handle most cases */ static int js_parse_skip_parens_token(JSParseState *s, int *pbits, BOOL no_line_terminator) { char state[256]; size_t level = 0; JSParsePos pos; int last_tok, tok = TOK_EOF; int c, tok_len, bits = 0; /* protect from underflow */ state[level++] = 0; js_parse_get_pos(s, &pos); last_tok = 0; for (;;) { switch(s->token.val) { case '(': case '[': case '{': if (level >= sizeof(state)) goto done; state[level++] = s->token.val; break; case ')': if (state[--level] != '(') goto done; break; case ']': if (state[--level] != '[') goto done; break; case '}': c = state[--level]; if (c == '`') { /* continue the parsing of the template */ free_token(s, &s->token); /* Resume TOK_TEMPLATE parsing (s->token.line_num and * s->token.ptr are OK) */ s->got_lf = FALSE; s->last_line_num = s->token.line_num; if (js_parse_template_part(s, s->buf_ptr)) goto done; goto handle_template; } else if (c != '{') { goto done; } break; case TOK_TEMPLATE: handle_template: if (s->token.u.str.sep != '`') { /* '${' inside the template : closing '}' and continue parsing the template */ if (level >= sizeof(state)) goto done; state[level++] = '`'; } break; case TOK_EOF: goto done; case ';': if (level == 2) { bits |= SKIP_HAS_SEMI; } break; case TOK_ELLIPSIS: if (level == 2) { bits |= SKIP_HAS_ELLIPSIS; } break; case '=': bits |= SKIP_HAS_ASSIGNMENT; break; case TOK_DIV_ASSIGN: tok_len = 2; goto parse_regexp; case '/': tok_len = 1; parse_regexp: if (is_regexp_allowed(last_tok)) { s->buf_ptr -= tok_len; if (js_parse_regexp(s)) { /* XXX: should clear the exception */ goto done; } } break; } /* last_tok is only used to recognize regexps */ if (s->token.val == TOK_IDENT && (token_is_pseudo_keyword(s, JS_ATOM_of) || token_is_pseudo_keyword(s, JS_ATOM_yield))) { last_tok = TOK_OF; } else { last_tok = s->token.val; } if (next_token(s)) { /* XXX: should clear the exception generated by next_token() */ break; } if (level <= 1) { tok = s->token.val; if (token_is_pseudo_keyword(s, JS_ATOM_of)) tok = TOK_OF; if (no_line_terminator && s->last_line_num != s->token.line_num) tok = '\n'; break; } } done: if (pbits) { *pbits = bits; } if (js_parse_seek_token(s, &pos)) return -1; return tok; } static int js_parse_check_duplicate_parameter(JSParseState *s, JSAtom name) { /* Check for duplicate parameter names */ JSFunctionDef *fd = s->cur_func; int i; for (i = 0; i < fd->arg_count; i++) { if (fd->args[i].var_name == name) goto duplicate; } for (i = 0; i < fd->var_count; i++) { if (fd->vars[i].var_name == name) goto duplicate; } return 0; duplicate: return js_parse_error(s, "duplicate parameter names not allowed in this context"); } static JSAtom js_parse_destructuring_var(JSParseState *s, int tok, int is_arg) { JSAtom name; if (!(s->token.val == TOK_IDENT && !s->token.u.ident.is_reserved) || ((s->cur_func->js_mode & JS_MODE_STRICT) && (s->token.u.ident.atom == JS_ATOM_eval || s->token.u.ident.atom == JS_ATOM_arguments))) { js_parse_error(s, "invalid destructuring target"); return JS_ATOM_NULL; } name = JS_DupAtom(s->ctx, s->token.u.ident.atom); if (is_arg && js_parse_check_duplicate_parameter(s, name)) goto fail; if (next_token(s)) goto fail; return name; fail: JS_FreeAtom(s->ctx, name); return JS_ATOM_NULL; } static __exception int js_parse_expr_paren(JSParseState *s) { if (js_parse_expect(s, '(')) return -1; if (js_parse_expr(s)) return -1; if (js_parse_expect(s, ')')) return -1; return 0; } static int js_parse_error_reserved_identifier(JSParseState *s) { char buf1[ATOM_GET_STR_BUF_SIZE]; return js_parse_error(s, "'%s' is a reserved identifier", JS_AtomGetStr(s->ctx, buf1, sizeof(buf1), s->token.u.ident.atom)); } static __exception int js_parse_left_hand_side_expr(JSParseState *s) { return js_parse_postfix_expr(s, PF_POSTFIX_CALL); } /* add a private field variable in the current scope */ static int add_private_class_field(JSParseState *s, JSFunctionDef *fd, JSAtom name, JSVarKindEnum var_kind) { JSContext *ctx = s->ctx; JSVarDef *vd; int idx; idx = add_scope_var(ctx, fd, name, var_kind); if (idx < 0) return idx; vd = &fd->vars[idx]; vd->is_lexical = 1; vd->is_const = 1; return idx; } static BOOL js_is_live_code(JSParseState *s) { switch (get_prev_opcode(s->cur_func)) { case OP_tail_call: case OP_tail_call_method: case OP_return: case OP_return_undef: case OP_return_async: case OP_throw: case OP_throw_error: case OP_goto: #if SHORT_OPCODES case OP_goto8: case OP_goto16: #endif case OP_ret: return FALSE; default: return TRUE; } } /* return label or -1 if dead code */ static int emit_goto(JSParseState *s, int opcode, int label) { if (js_is_live_code(s)) { if (label < 0) label = new_label(s); emit_op(s, opcode); emit_u32(s, label); s->cur_func->label_slots[label].ref_count++; return label; } return -1; } /* create a function to initialize class fields */ static JSFunctionDef *js_parse_function_class_fields_init(JSParseState *s) { JSFunctionDef *fd; fd = js_new_function_def(s->ctx, s->cur_func, FALSE, FALSE, s->filename, 0); if (!fd) return NULL; fd->func_name = JS_ATOM_NULL; fd->has_prototype = FALSE; fd->has_home_object = TRUE; fd->has_arguments_binding = FALSE; fd->has_this_binding = TRUE; fd->is_derived_class_constructor = FALSE; fd->new_target_allowed = TRUE; fd->super_call_allowed = FALSE; fd->super_allowed = fd->has_home_object; fd->arguments_allowed = FALSE; fd->func_kind = JS_FUNC_NORMAL; fd->func_type = JS_PARSE_FUNC_METHOD; return fd; } static __exception int emit_class_init_start(JSParseState *s, ClassFieldsDef *cf) { int label_add_brand; cf->fields_init_fd = js_parse_function_class_fields_init(s); if (!cf->fields_init_fd) return -1; s->cur_func = cf->fields_init_fd; /* XXX: would be better to add the code only if needed, maybe in a later pass */ emit_op(s, OP_push_false); /* will be patched later */ cf->brand_push_pos = cf->fields_init_fd->last_opcode_pos; label_add_brand = emit_goto(s, OP_if_false, -1); emit_op(s, OP_scope_get_var); emit_atom(s, JS_ATOM_this); emit_u16(s, 0); emit_op(s, OP_scope_get_var); emit_atom(s, JS_ATOM_home_object); emit_u16(s, 0); emit_op(s, OP_add_brand); emit_label(s, label_add_brand); s->cur_func = s->cur_func->parent; return 0; } static __exception int add_brand(JSParseState *s, ClassFieldsDef *cf) { if (!cf->has_brand) { /* define the brand field in 'this' of the initializer */ if (!cf->fields_init_fd) { if (emit_class_init_start(s, cf)) return -1; } /* patch the start of the function to enable the OP_add_brand code */ cf->fields_init_fd->byte_code.buf[cf->brand_push_pos] = OP_push_true; cf->has_brand = TRUE; } return 0; } static JSAtom js_atom_concat_num(JSContext *ctx, JSAtom name, uint32_t n) { char buf[16]; snprintf(buf, sizeof(buf), "%u", n); return js_atom_concat_str(ctx, name, buf); } /* XXX: could generate specific bytecode */ static __exception int js_parse_class_default_ctor(JSParseState *s, BOOL has_super, JSFunctionDef **pfd) { JSParsePos pos; const char *str; int ret, line_num; JSParseFunctionEnum func_type; const uint8_t *saved_buf_end; js_parse_get_pos(s, &pos); if (has_super) { /* spec change: no argument evaluation */ str = "(){super(...arguments);}"; func_type = JS_PARSE_FUNC_DERIVED_CLASS_CONSTRUCTOR; } else { str = "(){}"; func_type = JS_PARSE_FUNC_CLASS_CONSTRUCTOR; } line_num = s->token.line_num; saved_buf_end = s->buf_end; s->buf_ptr = (uint8_t *)str; s->buf_end = (uint8_t *)(str + strlen(str)); ret = next_token(s); if (!ret) { ret = js_parse_function_decl2(s, func_type, JS_FUNC_NORMAL, JS_ATOM_NULL, (uint8_t *)str, line_num, JS_PARSE_EXPORT_NONE, pfd); } s->buf_end = saved_buf_end; ret |= js_parse_seek_token(s, &pos); return ret; } static void emit_class_init_end(JSParseState *s, ClassFieldsDef *cf) { int cpool_idx; s->cur_func = cf->fields_init_fd; emit_op(s, OP_return_undef); s->cur_func = s->cur_func->parent; cpool_idx = cpool_add(s, JS_NULL); cf->fields_init_fd->parent_cpool_idx = cpool_idx; emit_op(s, OP_fclosure); emit_u32(s, cpool_idx); emit_op(s, OP_set_home_object); } static __exception int js_parse_class(JSParseState *s, BOOL is_class_expr, JSParseExportEnum export_flag) { JSContext *ctx = s->ctx; JSFunctionDef *fd = s->cur_func; JSAtom name = JS_ATOM_NULL, class_name = JS_ATOM_NULL, class_name1; JSAtom class_var_name = JS_ATOM_NULL; JSFunctionDef *method_fd, *ctor_fd; int saved_js_mode, class_name_var_idx, prop_type, ctor_cpool_offset; int class_flags = 0, i, define_class_offset; BOOL is_static, is_private; const uint8_t *class_start_ptr = s->token.ptr; const uint8_t *start_ptr; ClassFieldsDef class_fields[2]; /* classes are parsed and executed in strict mode */ saved_js_mode = fd->js_mode; fd->js_mode |= JS_MODE_STRICT; if (next_token(s)) goto fail; if (s->token.val == TOK_IDENT) { if (s->token.u.ident.is_reserved) { js_parse_error_reserved_identifier(s); goto fail; } class_name = JS_DupAtom(ctx, s->token.u.ident.atom); if (next_token(s)) goto fail; } else if (!is_class_expr && export_flag != JS_PARSE_EXPORT_DEFAULT) { js_parse_error(s, "class statement requires a name"); goto fail; } if (!is_class_expr) { if (class_name == JS_ATOM_NULL) class_var_name = JS_ATOM__default_; /* export default */ else class_var_name = class_name; class_var_name = JS_DupAtom(ctx, class_var_name); } push_scope(s); if (s->token.val == TOK_EXTENDS) { class_flags = JS_DEFINE_CLASS_HAS_HERITAGE; if (next_token(s)) goto fail; if (js_parse_left_hand_side_expr(s)) goto fail; } else { emit_op(s, OP_undefined); } /* add a 'const' definition for the class name */ if (class_name != JS_ATOM_NULL) { class_name_var_idx = define_var(s, fd, class_name, JS_VAR_DEF_CONST); if (class_name_var_idx < 0) goto fail; } if (js_parse_expect(s, '{')) goto fail; /* this scope contains the private fields */ push_scope(s); emit_op(s, OP_push_const); ctor_cpool_offset = fd->byte_code.size; emit_u32(s, 0); /* will be patched at the end of the class parsing */ if (class_name == JS_ATOM_NULL) { if (class_var_name != JS_ATOM_NULL) class_name1 = JS_ATOM_default; else class_name1 = JS_ATOM_empty_string; } else { class_name1 = class_name; } emit_op(s, OP_define_class); emit_atom(s, class_name1); emit_u8(s, class_flags); define_class_offset = fd->last_opcode_pos; for(i = 0; i < 2; i++) { ClassFieldsDef *cf = &class_fields[i]; cf->fields_init_fd = NULL; cf->computed_fields_count = 0; cf->has_brand = FALSE; } ctor_fd = NULL; while (s->token.val != '}') { if (s->token.val == ';') { if (next_token(s)) goto fail; continue; } is_static = (s->token.val == TOK_STATIC); prop_type = -1; if (is_static) { if (next_token(s)) goto fail; /* allow "static" field name */ if (s->token.val == ';' || s->token.val == '=') { is_static = FALSE; name = JS_DupAtom(ctx, JS_ATOM_static); prop_type = PROP_TYPE_IDENT; } } if (is_static) emit_op(s, OP_swap); start_ptr = s->token.ptr; if (prop_type < 0) { prop_type = js_parse_property_name(s, &name, TRUE, FALSE, TRUE); if (prop_type < 0) goto fail; } is_private = prop_type & PROP_TYPE_PRIVATE; prop_type &= ~PROP_TYPE_PRIVATE; if ((name == JS_ATOM_constructor && !is_static && prop_type != PROP_TYPE_IDENT) || (name == JS_ATOM_prototype && is_static) || name == JS_ATOM_hash_constructor) { js_parse_error(s, "invalid method name"); goto fail; } if (prop_type == PROP_TYPE_GET || prop_type == PROP_TYPE_SET) { BOOL is_set = prop_type - PROP_TYPE_GET; JSFunctionDef *method_fd; if (is_private) { int idx, var_kind; idx = find_private_class_field(ctx, fd, name, fd->scope_level); if (idx >= 0) { var_kind = fd->vars[idx].var_kind; if (var_kind == JS_VAR_PRIVATE_FIELD || var_kind == JS_VAR_PRIVATE_METHOD || var_kind == JS_VAR_PRIVATE_GETTER_SETTER || var_kind == (JS_VAR_PRIVATE_GETTER + is_set)) { goto private_field_already_defined; } fd->vars[idx].var_kind = JS_VAR_PRIVATE_GETTER_SETTER; } else { if (add_private_class_field(s, fd, name, JS_VAR_PRIVATE_GETTER + is_set) < 0) goto fail; } if (add_brand(s, &class_fields[is_static]) < 0) goto fail; } if (js_parse_function_decl2(s, JS_PARSE_FUNC_GETTER + is_set, JS_FUNC_NORMAL, JS_ATOM_NULL, start_ptr, s->token.line_num, JS_PARSE_EXPORT_NONE, &method_fd)) goto fail; if (is_private) { method_fd->need_home_object = TRUE; /* needed for brand check */ emit_op(s, OP_set_home_object); /* XXX: missing function name */ emit_op(s, OP_scope_put_var_init); if (is_set) { JSAtom setter_name; int ret; setter_name = get_private_setter_name(ctx, name); if (setter_name == JS_ATOM_NULL) goto fail; emit_atom(s, setter_name); ret = add_private_class_field(s, fd, setter_name, JS_VAR_PRIVATE_SETTER); JS_FreeAtom(ctx, setter_name); if (ret < 0) goto fail; } else { emit_atom(s, name); } emit_u16(s, s->cur_func->scope_level); } else { if (name == JS_ATOM_NULL) { emit_op(s, OP_define_method_computed); } else { emit_op(s, OP_define_method); emit_atom(s, name); } emit_u8(s, OP_DEFINE_METHOD_GETTER + is_set); } } else if (prop_type == PROP_TYPE_IDENT && s->token.val != '(') { ClassFieldsDef *cf = &class_fields[is_static]; JSAtom field_var_name = JS_ATOM_NULL; /* class field */ /* XXX: spec: not consistent with method name checks */ if (name == JS_ATOM_constructor || name == JS_ATOM_prototype) { js_parse_error(s, "invalid field name"); goto fail; } if (is_private) { if (find_private_class_field(ctx, fd, name, fd->scope_level) >= 0) { goto private_field_already_defined; } if (add_private_class_field(s, fd, name, JS_VAR_PRIVATE_FIELD) < 0) goto fail; emit_op(s, OP_private_symbol); emit_atom(s, name); emit_op(s, OP_scope_put_var_init); emit_atom(s, name); emit_u16(s, s->cur_func->scope_level); } if (!cf->fields_init_fd) { if (emit_class_init_start(s, cf)) goto fail; } if (name == JS_ATOM_NULL ) { /* save the computed field name into a variable */ field_var_name = js_atom_concat_num(ctx, JS_ATOM_computed_field + is_static, cf->computed_fields_count); if (field_var_name == JS_ATOM_NULL) goto fail; if (define_var(s, fd, field_var_name, JS_VAR_DEF_CONST) < 0) { JS_FreeAtom(ctx, field_var_name); goto fail; } emit_op(s, OP_to_propkey); emit_op(s, OP_scope_put_var_init); emit_atom(s, field_var_name); emit_u16(s, s->cur_func->scope_level); } s->cur_func = cf->fields_init_fd; emit_op(s, OP_scope_get_var); emit_atom(s, JS_ATOM_this); emit_u16(s, 0); if (name == JS_ATOM_NULL) { emit_op(s, OP_scope_get_var); emit_atom(s, field_var_name); emit_u16(s, s->cur_func->scope_level); cf->computed_fields_count++; JS_FreeAtom(ctx, field_var_name); } else if (is_private) { emit_op(s, OP_scope_get_var); emit_atom(s, name); emit_u16(s, s->cur_func->scope_level); } if (s->token.val == '=') { if (next_token(s)) goto fail; if (js_parse_assign_expr(s)) goto fail; } else { emit_op(s, OP_undefined); } if (is_private) { set_object_name_computed(s); emit_op(s, OP_define_private_field); } else if (name == JS_ATOM_NULL) { set_object_name_computed(s); emit_op(s, OP_define_array_el); emit_op(s, OP_drop); } else { set_object_name(s, name); emit_op(s, OP_define_field); emit_atom(s, name); } s->cur_func = s->cur_func->parent; if (js_parse_expect_semi(s)) goto fail; } else { JSParseFunctionEnum func_type; JSFunctionKindEnum func_kind; func_type = JS_PARSE_FUNC_METHOD; func_kind = JS_FUNC_NORMAL; if (prop_type == PROP_TYPE_STAR) { func_kind = JS_FUNC_GENERATOR; } else if (prop_type == PROP_TYPE_ASYNC) { func_kind = JS_FUNC_ASYNC; } else if (prop_type == PROP_TYPE_ASYNC_STAR) { func_kind = JS_FUNC_ASYNC_GENERATOR; } else if (name == JS_ATOM_constructor && !is_static) { if (ctor_fd) { js_parse_error(s, "property constructor appears more than once"); goto fail; } if (class_flags & JS_DEFINE_CLASS_HAS_HERITAGE) func_type = JS_PARSE_FUNC_DERIVED_CLASS_CONSTRUCTOR; else func_type = JS_PARSE_FUNC_CLASS_CONSTRUCTOR; } if (is_private) { if (add_brand(s, &class_fields[is_static]) < 0) goto fail; } if (js_parse_function_decl2(s, func_type, func_kind, JS_ATOM_NULL, start_ptr, s->token.line_num, JS_PARSE_EXPORT_NONE, &method_fd)) goto fail; if (func_type == JS_PARSE_FUNC_DERIVED_CLASS_CONSTRUCTOR || func_type == JS_PARSE_FUNC_CLASS_CONSTRUCTOR) { ctor_fd = method_fd; } else if (is_private) { method_fd->need_home_object = TRUE; /* needed for brand check */ if (find_private_class_field(ctx, fd, name, fd->scope_level) >= 0) { private_field_already_defined: js_parse_error(s, "private class field is already defined"); goto fail; } if (add_private_class_field(s, fd, name, JS_VAR_PRIVATE_METHOD) < 0) goto fail; emit_op(s, OP_set_home_object); emit_op(s, OP_set_name); emit_atom(s, name); emit_op(s, OP_scope_put_var_init); emit_atom(s, name); emit_u16(s, s->cur_func->scope_level); } else { if (name == JS_ATOM_NULL) { emit_op(s, OP_define_method_computed); } else { emit_op(s, OP_define_method); emit_atom(s, name); } emit_u8(s, OP_DEFINE_METHOD_METHOD); } } if (is_static) emit_op(s, OP_swap); JS_FreeAtom(ctx, name); name = JS_ATOM_NULL; } if (s->token.val != '}') { js_parse_error(s, "expecting '%c'", '}'); goto fail; } if (!ctor_fd) { if (js_parse_class_default_ctor(s, class_flags & JS_DEFINE_CLASS_HAS_HERITAGE, &ctor_fd)) goto fail; } /* patch the constant pool index for the constructor */ put_u32(fd->byte_code.buf + ctor_cpool_offset, ctor_fd->parent_cpool_idx); /* store the class source code in the constructor. */ if (!(fd->js_mode & JS_MODE_STRIP)) { js_free(ctx, ctor_fd->source); ctor_fd->source_len = s->buf_ptr - class_start_ptr; ctor_fd->source = js_strndup(ctx, (const char *)class_start_ptr, ctor_fd->source_len); if (!ctor_fd->source) goto fail; } /* consume the '}' */ if (next_token(s)) goto fail; /* store the function to initialize the fields to that it can be referenced by the constructor */ { ClassFieldsDef *cf = &class_fields[0]; int var_idx; var_idx = define_var(s, fd, JS_ATOM_class_fields_init, JS_VAR_DEF_CONST); if (var_idx < 0) goto fail; if (cf->fields_init_fd) { emit_class_init_end(s, cf); } else { emit_op(s, OP_undefined); } emit_op(s, OP_scope_put_var_init); emit_atom(s, JS_ATOM_class_fields_init); emit_u16(s, s->cur_func->scope_level); } /* drop the prototype */ emit_op(s, OP_drop); /* initialize the static fields */ if (class_fields[1].fields_init_fd != NULL) { ClassFieldsDef *cf = &class_fields[1]; emit_op(s, OP_dup); emit_class_init_end(s, cf); emit_op(s, OP_call_method); emit_u16(s, 0); emit_op(s, OP_drop); } if (class_name != JS_ATOM_NULL) { /* store the class name in the scoped class name variable (it is independent from the class statement variable definition) */ emit_op(s, OP_dup); emit_op(s, OP_scope_put_var_init); emit_atom(s, class_name); emit_u16(s, fd->scope_level); } pop_scope(s); pop_scope(s); /* the class statements have a block level scope */ if (class_var_name != JS_ATOM_NULL) { if (define_var(s, fd, class_var_name, JS_VAR_DEF_LET) < 0) goto fail; emit_op(s, OP_scope_put_var_init); emit_atom(s, class_var_name); emit_u16(s, fd->scope_level); } else { if (class_name == JS_ATOM_NULL) { /* cannot use OP_set_name because the name of the class must be defined before the static initializers are executed */ emit_op(s, OP_set_class_name); emit_u32(s, fd->last_opcode_pos + 1 - define_class_offset); } } if (export_flag != JS_PARSE_EXPORT_NONE) { if (!add_export_entry(s, fd->module, class_var_name, export_flag == JS_PARSE_EXPORT_NAMED ? class_var_name : JS_ATOM_default, JS_EXPORT_TYPE_LOCAL)) goto fail; } JS_FreeAtom(ctx, class_name); JS_FreeAtom(ctx, class_var_name); fd->js_mode = saved_js_mode; return 0; fail: JS_FreeAtom(ctx, name); JS_FreeAtom(ctx, class_name); JS_FreeAtom(ctx, class_var_name); fd->js_mode = saved_js_mode; return -1; } static __exception int get_lvalue(JSParseState *s, int *popcode, int *pscope, JSAtom *pname, int *plabel, int *pdepth, BOOL keep, int tok) { JSFunctionDef *fd; int opcode, scope, label, depth; JSAtom name; /* we check the last opcode to get the lvalue type */ fd = s->cur_func; scope = 0; name = JS_ATOM_NULL; label = -1; depth = 0; switch(opcode = get_prev_opcode(fd)) { case OP_scope_get_var: name = get_u32(fd->byte_code.buf + fd->last_opcode_pos + 1); scope = get_u16(fd->byte_code.buf + fd->last_opcode_pos + 5); if ((name == JS_ATOM_arguments || name == JS_ATOM_eval) && (fd->js_mode & JS_MODE_STRICT)) { return js_parse_error(s, "invalid lvalue in strict mode"); } if (name == JS_ATOM_this || name == JS_ATOM_new_target) goto invalid_lvalue; depth = 2; /* will generate OP_get_ref_value */ break; case OP_get_field: name = get_u32(fd->byte_code.buf + fd->last_opcode_pos + 1); depth = 1; break; case OP_scope_get_private_field: name = get_u32(fd->byte_code.buf + fd->last_opcode_pos + 1); scope = get_u16(fd->byte_code.buf + fd->last_opcode_pos + 5); depth = 1; break; case OP_get_array_el: depth = 2; break; case OP_get_super_value: depth = 3; break; default: invalid_lvalue: if (tok == TOK_FOR) { return js_parse_error(s, "invalid for in/of left hand-side"); } else if (tok == TOK_INC || tok == TOK_DEC) { return js_parse_error(s, "invalid increment/decrement operand"); } else if (tok == '[' || tok == '{') { return js_parse_error(s, "invalid destructuring target"); } else { return js_parse_error(s, "invalid assignment left-hand side"); } } /* remove the last opcode */ fd->byte_code.size = fd->last_opcode_pos; fd->last_opcode_pos = -1; if (keep) { /* get the value but keep the object/fields on the stack */ switch(opcode) { case OP_scope_get_var: label = new_label(s); emit_op(s, OP_scope_make_ref); emit_atom(s, name); emit_u32(s, label); emit_u16(s, scope); update_label(fd, label, 1); emit_op(s, OP_get_ref_value); opcode = OP_get_ref_value; break; case OP_get_field: emit_op(s, OP_get_field2); emit_atom(s, name); break; case OP_scope_get_private_field: emit_op(s, OP_scope_get_private_field2); emit_atom(s, name); emit_u16(s, scope); break; case OP_get_array_el: /* XXX: replace by a single opcode ? */ emit_op(s, OP_to_propkey2); emit_op(s, OP_dup2); emit_op(s, OP_get_array_el); break; case OP_get_super_value: emit_op(s, OP_to_propkey); emit_op(s, OP_dup3); emit_op(s, OP_get_super_value); break; default: abort(); } } else { switch(opcode) { case OP_scope_get_var: label = new_label(s); emit_op(s, OP_scope_make_ref); emit_atom(s, name); emit_u32(s, label); emit_u16(s, scope); update_label(fd, label, 1); opcode = OP_get_ref_value; break; case OP_get_array_el: emit_op(s, OP_to_propkey2); break; case OP_get_super_value: emit_op(s, OP_to_propkey); break; } } *popcode = opcode; *pscope = scope; /* name has refcount for OP_get_field and OP_get_ref_value, and JS_ATOM_NULL for other opcodes */ *pname = name; *plabel = label; if (pdepth) *pdepth = depth; return 0; } static __exception int js_define_var(JSParseState *s, JSAtom name, int tok) { JSFunctionDef *fd = s->cur_func; JSVarDefEnum var_def_type; if (name == JS_ATOM_yield && fd->func_kind == JS_FUNC_GENERATOR) { return js_parse_error(s, "yield is a reserved identifier"); } if ((name == JS_ATOM_arguments || name == JS_ATOM_eval) && (fd->js_mode & JS_MODE_STRICT)) { return js_parse_error(s, "invalid variable name in strict mode"); } if ((name == JS_ATOM_let || name == JS_ATOM_undefined) && (tok == TOK_LET || tok == TOK_CONST)) { return js_parse_error(s, "invalid lexical variable name"); } switch(tok) { case TOK_LET: var_def_type = JS_VAR_DEF_LET; break; case TOK_CONST: var_def_type = JS_VAR_DEF_CONST; break; case TOK_VAR: var_def_type = JS_VAR_DEF_VAR; break; case TOK_CATCH: var_def_type = JS_VAR_DEF_CATCH; break; default: abort(); } if (define_var(s, fd, name, var_def_type) < 0) return -1; return 0; } /* name has a live reference. 'is_let' is only used with opcode = OP_scope_get_var which is never generated by get_lvalue(). */ static void put_lvalue(JSParseState *s, int opcode, int scope, JSAtom name, int label, PutLValueEnum special, BOOL is_let) { switch(opcode) { case OP_get_field: case OP_scope_get_private_field: /* depth = 1 */ switch(special) { case PUT_LVALUE_NOKEEP: case PUT_LVALUE_NOKEEP_DEPTH: break; case PUT_LVALUE_KEEP_TOP: emit_op(s, OP_insert2); /* obj v -> v obj v */ break; case PUT_LVALUE_KEEP_SECOND: emit_op(s, OP_perm3); /* obj v0 v -> v0 obj v */ break; case PUT_LVALUE_NOKEEP_BOTTOM: emit_op(s, OP_swap); break; default: abort(); } break; case OP_get_array_el: case OP_get_ref_value: /* depth = 2 */ if (opcode == OP_get_ref_value) { JS_FreeAtom(s->ctx, name); emit_label(s, label); } switch(special) { case PUT_LVALUE_NOKEEP: emit_op(s, OP_nop); /* will trigger optimization */ break; case PUT_LVALUE_NOKEEP_DEPTH: break; case PUT_LVALUE_KEEP_TOP: emit_op(s, OP_insert3); /* obj prop v -> v obj prop v */ break; case PUT_LVALUE_KEEP_SECOND: emit_op(s, OP_perm4); /* obj prop v0 v -> v0 obj prop v */ break; case PUT_LVALUE_NOKEEP_BOTTOM: emit_op(s, OP_rot3l); break; default: abort(); } break; case OP_get_super_value: /* depth = 3 */ switch(special) { case PUT_LVALUE_NOKEEP: case PUT_LVALUE_NOKEEP_DEPTH: break; case PUT_LVALUE_KEEP_TOP: emit_op(s, OP_insert4); /* this obj prop v -> v this obj prop v */ break; case PUT_LVALUE_KEEP_SECOND: emit_op(s, OP_perm5); /* this obj prop v0 v -> v0 this obj prop v */ break; case PUT_LVALUE_NOKEEP_BOTTOM: emit_op(s, OP_rot4l); break; default: abort(); } break; default: break; } switch(opcode) { case OP_scope_get_var: /* val -- */ assert(special == PUT_LVALUE_NOKEEP || special == PUT_LVALUE_NOKEEP_DEPTH); emit_op(s, is_let ? OP_scope_put_var_init : OP_scope_put_var); emit_u32(s, name); /* has refcount */ emit_u16(s, scope); break; case OP_get_field: emit_op(s, OP_put_field); emit_u32(s, name); /* name has refcount */ break; case OP_scope_get_private_field: emit_op(s, OP_scope_put_private_field); emit_u32(s, name); /* name has refcount */ emit_u16(s, scope); break; case OP_get_array_el: emit_op(s, OP_put_array_el); break; case OP_get_ref_value: emit_op(s, OP_put_ref_value); break; case OP_get_super_value: emit_op(s, OP_put_super_value); break; default: abort(); } } static void js_emit_spread_code(JSParseState *s, int depth) { int label_rest_next, label_rest_done; /* XXX: could check if enum object is an actual array and optimize slice extraction. enumeration record and target array are in a different order from OP_append case. */ /* enum_rec xxx -- enum_rec xxx array 0 */ emit_op(s, OP_array_from); emit_u16(s, 0); emit_op(s, OP_push_i32); emit_u32(s, 0); emit_label(s, label_rest_next = new_label(s)); emit_op(s, OP_for_of_next); emit_u8(s, 2 + depth); label_rest_done = emit_goto(s, OP_if_true, -1); /* array idx val -- array idx */ emit_op(s, OP_define_array_el); emit_op(s, OP_inc); emit_goto(s, OP_goto, label_rest_next); emit_label(s, label_rest_done); /* enum_rec xxx array idx undef -- enum_rec xxx array */ emit_op(s, OP_drop); emit_op(s, OP_drop); } static void push_break_entry(JSFunctionDef *fd, BlockEnv *be, JSAtom label_name, int label_break, int label_cont, int drop_count) { be->prev = fd->top_break; fd->top_break = be; be->label_name = label_name; be->label_break = label_break; be->label_cont = label_cont; be->drop_count = drop_count; be->label_finally = -1; be->scope_level = fd->scope_level; be->has_iterator = FALSE; } static void pop_break_entry(JSFunctionDef *fd) { BlockEnv *be; be = fd->top_break; fd->top_break = be->prev; } /* Return -1 if error, 0 if no initializer, 1 if an initializer is present at the top level. */ static int js_parse_destructuring_element(JSParseState *s, int tok, int is_arg, int hasval, int has_ellipsis, BOOL allow_initializer) { int label_parse, label_assign, label_done, label_lvalue, depth_lvalue; int start_addr, assign_addr; JSAtom prop_name, var_name; int opcode, scope, tok1, skip_bits; BOOL has_initializer; if (has_ellipsis < 0) { /* pre-parse destructuration target for spread detection */ js_parse_skip_parens_token(s, &skip_bits, FALSE); has_ellipsis = skip_bits & SKIP_HAS_ELLIPSIS; } label_parse = new_label(s); label_assign = new_label(s); start_addr = s->cur_func->byte_code.size; if (hasval) { /* consume value from the stack */ emit_op(s, OP_dup); emit_op(s, OP_undefined); emit_op(s, OP_strict_eq); emit_goto(s, OP_if_true, label_parse); emit_label(s, label_assign); } else { emit_goto(s, OP_goto, label_parse); emit_label(s, label_assign); /* leave value on the stack */ emit_op(s, OP_dup); } assign_addr = s->cur_func->byte_code.size; if (s->token.val == '{') { if (next_token(s)) return -1; /* throw an exception if the value cannot be converted to an object */ emit_op(s, OP_to_object); if (has_ellipsis) { /* add excludeList on stack just below src object */ emit_op(s, OP_object); emit_op(s, OP_swap); } while (s->token.val != '}') { int prop_type; if (s->token.val == TOK_ELLIPSIS) { if (!has_ellipsis) { JS_ThrowInternalError(s->ctx, "unexpected ellipsis token"); return -1; } if (next_token(s)) return -1; if (tok) { var_name = js_parse_destructuring_var(s, tok, is_arg); if (var_name == JS_ATOM_NULL) return -1; opcode = OP_scope_get_var; scope = s->cur_func->scope_level; label_lvalue = -1; depth_lvalue = 0; } else { if (js_parse_left_hand_side_expr(s)) return -1; if (get_lvalue(s, &opcode, &scope, &var_name, &label_lvalue, &depth_lvalue, FALSE, '{')) return -1; } if (s->token.val != '}') { js_parse_error(s, "assignment rest property must be last"); goto var_error; } emit_op(s, OP_object); /* target */ emit_op(s, OP_copy_data_properties); emit_u8(s, 0 | ((depth_lvalue + 1) << 2) | ((depth_lvalue + 2) << 5)); goto set_val; } prop_type = js_parse_property_name(s, &prop_name, FALSE, TRUE, FALSE); if (prop_type < 0) return -1; var_name = JS_ATOM_NULL; opcode = OP_scope_get_var; scope = s->cur_func->scope_level; label_lvalue = -1; depth_lvalue = 0; if (prop_type == PROP_TYPE_IDENT) { if (next_token(s)) goto prop_error; if ((s->token.val == '[' || s->token.val == '{') && ((tok1 = js_parse_skip_parens_token(s, &skip_bits, FALSE)) == ',' || tok1 == '=' || tok1 == '}')) { if (prop_name == JS_ATOM_NULL) { /* computed property name on stack */ if (has_ellipsis) { /* define the property in excludeList */ emit_op(s, OP_to_propkey); /* avoid calling ToString twice */ emit_op(s, OP_perm3); /* TOS: src excludeList prop */ emit_op(s, OP_null); /* TOS: src excludeList prop null */ emit_op(s, OP_define_array_el); /* TOS: src excludeList prop */ emit_op(s, OP_perm3); /* TOS: excludeList src prop */ } /* get the computed property from the source object */ emit_op(s, OP_get_array_el2); } else { /* named property */ if (has_ellipsis) { /* define the property in excludeList */ emit_op(s, OP_swap); /* TOS: src excludeList */ emit_op(s, OP_null); /* TOS: src excludeList null */ emit_op(s, OP_define_field); /* TOS: src excludeList */ emit_atom(s, prop_name); emit_op(s, OP_swap); /* TOS: excludeList src */ } /* get the named property from the source object */ emit_op(s, OP_get_field2); emit_u32(s, prop_name); } if (js_parse_destructuring_element(s, tok, is_arg, TRUE, -1, TRUE) < 0) return -1; if (s->token.val == '}') break; /* accept a trailing comma before the '}' */ if (js_parse_expect(s, ',')) return -1; continue; } if (prop_name == JS_ATOM_NULL) { emit_op(s, OP_to_propkey2); if (has_ellipsis) { /* define the property in excludeList */ emit_op(s, OP_perm3); emit_op(s, OP_null); emit_op(s, OP_define_array_el); emit_op(s, OP_perm3); } /* source prop -- source source prop */ emit_op(s, OP_dup1); } else { if (has_ellipsis) { /* define the property in excludeList */ emit_op(s, OP_swap); emit_op(s, OP_null); emit_op(s, OP_define_field); emit_atom(s, prop_name); emit_op(s, OP_swap); } /* source -- source source */ emit_op(s, OP_dup); } if (tok) { var_name = js_parse_destructuring_var(s, tok, is_arg); if (var_name == JS_ATOM_NULL) goto prop_error; } else { if (js_parse_left_hand_side_expr(s)) goto prop_error; lvalue: if (get_lvalue(s, &opcode, &scope, &var_name, &label_lvalue, &depth_lvalue, FALSE, '{')) goto prop_error; /* swap ref and lvalue object if any */ if (prop_name == JS_ATOM_NULL) { switch(depth_lvalue) { case 1: /* source prop x -> x source prop */ emit_op(s, OP_rot3r); break; case 2: /* source prop x y -> x y source prop */ emit_op(s, OP_swap2); /* t p2 s p1 */ break; case 3: /* source prop x y z -> x y z source prop */ emit_op(s, OP_rot5l); emit_op(s, OP_rot5l); break; } } else { switch(depth_lvalue) { case 1: /* source x -> x source */ emit_op(s, OP_swap); break; case 2: /* source x y -> x y source */ emit_op(s, OP_rot3l); break; case 3: /* source x y z -> x y z source */ emit_op(s, OP_rot4l); break; } } } if (prop_name == JS_ATOM_NULL) { /* computed property name on stack */ /* XXX: should have OP_get_array_el2x with depth */ /* source prop -- val */ emit_op(s, OP_get_array_el); } else { /* named property */ /* XXX: should have OP_get_field2x with depth */ /* source -- val */ emit_op(s, OP_get_field); emit_u32(s, prop_name); } } else { /* prop_type = PROP_TYPE_VAR, cannot be a computed property */ if (is_arg && js_parse_check_duplicate_parameter(s, prop_name)) goto prop_error; if ((s->cur_func->js_mode & JS_MODE_STRICT) && (prop_name == JS_ATOM_eval || prop_name == JS_ATOM_arguments)) { js_parse_error(s, "invalid destructuring target"); goto prop_error; } if (has_ellipsis) { /* define the property in excludeList */ emit_op(s, OP_swap); emit_op(s, OP_null); emit_op(s, OP_define_field); emit_atom(s, prop_name); emit_op(s, OP_swap); } if (!tok || tok == TOK_VAR) { /* generate reference */ /* source -- source source */ emit_op(s, OP_dup); emit_op(s, OP_scope_get_var); emit_atom(s, prop_name); emit_u16(s, s->cur_func->scope_level); goto lvalue; } var_name = JS_DupAtom(s->ctx, prop_name); /* source -- source val */ emit_op(s, OP_get_field2); emit_u32(s, prop_name); } set_val: if (tok) { if (js_define_var(s, var_name, tok)) goto var_error; scope = s->cur_func->scope_level; } if (s->token.val == '=') { /* handle optional default value */ int label_hasval; emit_op(s, OP_dup); emit_op(s, OP_undefined); emit_op(s, OP_strict_eq); label_hasval = emit_goto(s, OP_if_false, -1); if (next_token(s)) goto var_error; emit_op(s, OP_drop); if (js_parse_assign_expr(s)) goto var_error; if (opcode == OP_scope_get_var || opcode == OP_get_ref_value) set_object_name(s, var_name); emit_label(s, label_hasval); } /* store value into lvalue object */ put_lvalue(s, opcode, scope, var_name, label_lvalue, PUT_LVALUE_NOKEEP_DEPTH, (tok == TOK_CONST || tok == TOK_LET)); if (s->token.val == '}') break; /* accept a trailing comma before the '}' */ if (js_parse_expect(s, ',')) return -1; } /* drop the source object */ emit_op(s, OP_drop); if (has_ellipsis) { emit_op(s, OP_drop); /* pop excludeList */ } if (next_token(s)) return -1; } else if (s->token.val == '[') { BOOL has_spread; int enum_depth; BlockEnv block_env; if (next_token(s)) return -1; /* the block environment is only needed in generators in case 'yield' triggers a 'return' */ push_break_entry(s->cur_func, &block_env, JS_ATOM_NULL, -1, -1, 2); block_env.has_iterator = TRUE; emit_op(s, OP_for_of_start); has_spread = FALSE; while (s->token.val != ']') { /* get the next value */ if (s->token.val == TOK_ELLIPSIS) { if (next_token(s)) return -1; if (s->token.val == ',' || s->token.val == ']') return js_parse_error(s, "missing binding pattern..."); has_spread = TRUE; } if (s->token.val == ',') { /* do nothing, skip the value, has_spread is false */ emit_op(s, OP_for_of_next); emit_u8(s, 0); emit_op(s, OP_drop); emit_op(s, OP_drop); } else if ((s->token.val == '[' || s->token.val == '{') && ((tok1 = js_parse_skip_parens_token(s, &skip_bits, FALSE)) == ',' || tok1 == '=' || tok1 == ']')) { if (has_spread) { if (tok1 == '=') return js_parse_error(s, "rest element cannot have a default value"); js_emit_spread_code(s, 0); } else { emit_op(s, OP_for_of_next); emit_u8(s, 0); emit_op(s, OP_drop); } if (js_parse_destructuring_element(s, tok, is_arg, TRUE, skip_bits & SKIP_HAS_ELLIPSIS, TRUE) < 0) return -1; } else { var_name = JS_ATOM_NULL; enum_depth = 0; if (tok) { var_name = js_parse_destructuring_var(s, tok, is_arg); if (var_name == JS_ATOM_NULL) goto var_error; if (js_define_var(s, var_name, tok)) goto var_error; opcode = OP_scope_get_var; scope = s->cur_func->scope_level; } else { if (js_parse_left_hand_side_expr(s)) return -1; if (get_lvalue(s, &opcode, &scope, &var_name, &label_lvalue, &enum_depth, FALSE, '[')) { return -1; } } if (has_spread) { js_emit_spread_code(s, enum_depth); } else { emit_op(s, OP_for_of_next); emit_u8(s, enum_depth); emit_op(s, OP_drop); } if (s->token.val == '=' && !has_spread) { /* handle optional default value */ int label_hasval; emit_op(s, OP_dup); emit_op(s, OP_undefined); emit_op(s, OP_strict_eq); label_hasval = emit_goto(s, OP_if_false, -1); if (next_token(s)) goto var_error; emit_op(s, OP_drop); if (js_parse_assign_expr(s)) goto var_error; if (opcode == OP_scope_get_var || opcode == OP_get_ref_value) set_object_name(s, var_name); emit_label(s, label_hasval); } /* store value into lvalue object */ put_lvalue(s, opcode, scope, var_name, label_lvalue, PUT_LVALUE_NOKEEP_DEPTH, (tok == TOK_CONST || tok == TOK_LET)); } if (s->token.val == ']') break; if (has_spread) return js_parse_error(s, "rest element must be the last one"); /* accept a trailing comma before the ']' */ if (js_parse_expect(s, ',')) return -1; } /* close iterator object: if completed, enum_obj has been replaced by undefined */ emit_op(s, OP_iterator_close); pop_break_entry(s->cur_func); if (next_token(s)) return -1; } else { return js_parse_error(s, "invalid assignment syntax"); } if (s->token.val == '=' && allow_initializer) { label_done = emit_goto(s, OP_goto, -1); if (next_token(s)) return -1; emit_label(s, label_parse); if (hasval) emit_op(s, OP_drop); if (js_parse_assign_expr(s)) return -1; emit_goto(s, OP_goto, label_assign); emit_label(s, label_done); has_initializer = TRUE; } else { /* normally hasval is true except if js_parse_skip_parens_token() was wrong in the parsing */ // assert(hasval); if (!hasval) { js_parse_error(s, "too complicated destructuring expression"); return -1; } /* remove test and decrement label ref count */ memset(s->cur_func->byte_code.buf + start_addr, OP_nop, assign_addr - start_addr); s->cur_func->label_slots[label_parse].ref_count--; has_initializer = FALSE; } return has_initializer; prop_error: JS_FreeAtom(s->ctx, prop_name); var_error: JS_FreeAtom(s->ctx, var_name); return -1; } static __exception int js_parse_array_literal(JSParseState *s) { uint32_t idx; BOOL need_length; if (next_token(s)) return -1; /* small regular arrays are created on the stack */ idx = 0; while (s->token.val != ']' && idx < 32) { if (s->token.val == ',' || s->token.val == TOK_ELLIPSIS) break; if (js_parse_assign_expr(s)) return -1; idx++; /* accept trailing comma */ if (s->token.val == ',') { if (next_token(s)) return -1; } else if (s->token.val != ']') goto done; } emit_op(s, OP_array_from); emit_u16(s, idx); /* larger arrays and holes are handled with explicit indices */ need_length = FALSE; while (s->token.val != ']' && idx < 0x7fffffff) { if (s->token.val == TOK_ELLIPSIS) break; need_length = TRUE; if (s->token.val != ',') { if (js_parse_assign_expr(s)) return -1; emit_op(s, OP_define_field); emit_u32(s, __JS_AtomFromUInt32(idx)); need_length = FALSE; } idx++; /* accept trailing comma */ if (s->token.val == ',') { if (next_token(s)) return -1; } } if (s->token.val == ']') { if (need_length) { /* Set the length: Cannot use OP_define_field because length is not configurable */ emit_op(s, OP_dup); emit_op(s, OP_push_i32); emit_u32(s, idx); emit_op(s, OP_put_field); emit_atom(s, JS_ATOM_length); } goto done; } /* huge arrays and spread elements require a dynamic index on the stack */ emit_op(s, OP_push_i32); emit_u32(s, idx); /* stack has array, index */ while (s->token.val != ']') { if (s->token.val == TOK_ELLIPSIS) { if (next_token(s)) return -1; if (js_parse_assign_expr(s)) return -1; #if 1 emit_op(s, OP_append); #else int label_next, label_done; label_next = new_label(s); label_done = new_label(s); /* enumerate object */ emit_op(s, OP_for_of_start); emit_op(s, OP_rot5l); emit_op(s, OP_rot5l); emit_label(s, label_next); /* on stack: enum_rec array idx */ emit_op(s, OP_for_of_next); emit_u8(s, 2); emit_goto(s, OP_if_true, label_done); /* append element */ /* enum_rec array idx val -> enum_rec array new_idx */ emit_op(s, OP_define_array_el); emit_op(s, OP_inc); emit_goto(s, OP_goto, label_next); emit_label(s, label_done); /* close enumeration */ emit_op(s, OP_drop); /* drop undef val */ emit_op(s, OP_nip1); /* drop enum_rec */ emit_op(s, OP_nip1); emit_op(s, OP_nip1); #endif } else { need_length = TRUE; if (s->token.val != ',') { if (js_parse_assign_expr(s)) return -1; /* a idx val */ emit_op(s, OP_define_array_el); need_length = FALSE; } emit_op(s, OP_inc); } if (s->token.val != ',') break; if (next_token(s)) return -1; } if (need_length) { /* Set the length: cannot use OP_define_field because length is not configurable */ emit_op(s, OP_dup1); /* array length - array array length */ emit_op(s, OP_put_field); emit_atom(s, JS_ATOM_length); } else { emit_op(s, OP_drop); /* array length - array */ } done: return js_parse_expect(s, ']'); } /* XXX: remove */ static BOOL has_with_scope(JSFunctionDef *s, int scope_level) { /* check if scope chain contains a with statement */ while (s) { int scope_idx = s->scopes[scope_level].first; while (scope_idx >= 0) { JSVarDef *vd = &s->vars[scope_idx]; if (vd->var_name == JS_ATOM__with_) return TRUE; scope_idx = vd->scope_next; } /* check parent scopes */ scope_level = s->parent_scope_level; s = s->parent; } return FALSE; } static void optional_chain_test(JSParseState *s, int *poptional_chaining_label, int drop_count) { int label_next, i; if (*poptional_chaining_label < 0) *poptional_chaining_label = new_label(s); /* XXX: could be more efficient with a specific opcode */ emit_op(s, OP_dup); emit_op(s, OP_is_undefined_or_null); label_next = emit_goto(s, OP_if_false, -1); for(i = 0; i < drop_count; i++) emit_op(s, OP_drop); emit_op(s, OP_undefined); emit_goto(s, OP_goto, *poptional_chaining_label); emit_label(s, label_next); } /* initialize the class fields, called by the constructor. Note: super() can be called in an arrow function, so and can be variable references */ static void emit_class_field_init(JSParseState *s) { int label_next; emit_op(s, OP_scope_get_var); emit_atom(s, JS_ATOM_class_fields_init); emit_u16(s, s->cur_func->scope_level); /* no need to call the class field initializer if not defined */ emit_op(s, OP_dup); label_next = emit_goto(s, OP_if_false, -1); emit_op(s, OP_scope_get_var); emit_atom(s, JS_ATOM_this); emit_u16(s, 0); emit_op(s, OP_swap); emit_op(s, OP_call_method); emit_u16(s, 0); emit_label(s, label_next); emit_op(s, OP_drop); } /* allowed parse_flags: PF_POSTFIX_CALL, PF_ARROW_FUNC */ static __exception int js_parse_postfix_expr(JSParseState *s, int parse_flags) { FuncCallType call_type; int optional_chaining_label; BOOL accept_lparen = (parse_flags & PF_POSTFIX_CALL) != 0; call_type = FUNC_CALL_NORMAL; switch(s->token.val) { case TOK_NUMBER: { JSValue val; val = s->token.u.num.val; if (JS_VALUE_GET_TAG(val) == JS_TAG_INT) { emit_op(s, OP_push_i32); emit_u32(s, JS_VALUE_GET_INT(val)); } else #ifdef CONFIG_BIGNUM if (JS_VALUE_GET_TAG(val) == JS_TAG_BIG_FLOAT) { slimb_t e; int ret; /* need a runtime conversion */ /* XXX: could add a cache and/or do it once at the start of the function */ if (emit_push_const(s, val, 0) < 0) return -1; e = s->token.u.num.exponent; if (e == (int32_t)e) { emit_op(s, OP_push_i32); emit_u32(s, e); } else { val = JS_NewBigInt64_1(s->ctx, e); if (JS_IsException(val)) return -1; ret = emit_push_const(s, val, 0); JS_FreeValue(s->ctx, val); if (ret < 0) return -1; } emit_op(s, OP_mul_pow10); } else #endif { if (emit_push_const(s, val, 0) < 0) return -1; } } if (next_token(s)) return -1; break; case TOK_TEMPLATE: if (js_parse_template(s, 0, NULL)) return -1; break; case TOK_STRING: if (emit_push_const(s, s->token.u.str.str, 1)) return -1; if (next_token(s)) return -1; break; case TOK_DIV_ASSIGN: s->buf_ptr -= 2; goto parse_regexp; case '/': s->buf_ptr--; parse_regexp: { JSValue str; int ret, backtrace_flags; if (!s->ctx->compile_regexp) return js_parse_error(s, "RegExp are not supported"); /* the previous token is '/' or '/=', so no need to free */ if (js_parse_regexp(s)) return -1; ret = emit_push_const(s, s->token.u.regexp.body, 0); str = s->ctx->compile_regexp(s->ctx, s->token.u.regexp.body, s->token.u.regexp.flags); if (JS_IsException(str)) { /* add the line number info */ backtrace_flags = 0; if (s->cur_func && s->cur_func->backtrace_barrier) backtrace_flags = JS_BACKTRACE_FLAG_SINGLE_LEVEL; build_backtrace(s->ctx, s->ctx->rt->current_exception, s->filename, s->token.line_num, backtrace_flags); return -1; } ret = emit_push_const(s, str, 0); JS_FreeValue(s->ctx, str); if (ret) return -1; /* we use a specific opcode to be sure the correct function is called (otherwise the bytecode would have to be verified by the RegExp constructor) */ emit_op(s, OP_regexp); if (next_token(s)) return -1; } break; case '(': if ((parse_flags & PF_ARROW_FUNC) && js_parse_skip_parens_token(s, NULL, TRUE) == TOK_ARROW) { if (js_parse_function_decl(s, JS_PARSE_FUNC_ARROW, JS_FUNC_NORMAL, JS_ATOM_NULL, s->token.ptr, s->token.line_num)) return -1; } else { if (js_parse_expr_paren(s)) return -1; } break; case TOK_FUNCTION: if (js_parse_function_decl(s, JS_PARSE_FUNC_EXPR, JS_FUNC_NORMAL, JS_ATOM_NULL, s->token.ptr, s->token.line_num)) return -1; break; case TOK_CLASS: if (js_parse_class(s, TRUE, JS_PARSE_EXPORT_NONE)) return -1; break; case TOK_NULL: if (next_token(s)) return -1; emit_op(s, OP_null); break; case TOK_THIS: if (next_token(s)) return -1; emit_op(s, OP_scope_get_var); emit_atom(s, JS_ATOM_this); emit_u16(s, 0); break; case TOK_FALSE: if (next_token(s)) return -1; emit_op(s, OP_push_false); break; case TOK_TRUE: if (next_token(s)) return -1; emit_op(s, OP_push_true); break; case TOK_IDENT: { JSAtom name; if (s->token.u.ident.is_reserved) { return js_parse_error_reserved_identifier(s); } if ((parse_flags & PF_ARROW_FUNC) && peek_token(s, TRUE) == TOK_ARROW) { if (js_parse_function_decl(s, JS_PARSE_FUNC_ARROW, JS_FUNC_NORMAL, JS_ATOM_NULL, s->token.ptr, s->token.line_num)) return -1; } else if (token_is_pseudo_keyword(s, JS_ATOM_async) && peek_token(s, TRUE) != '\n') { const uint8_t *source_ptr; int source_line_num; source_ptr = s->token.ptr; source_line_num = s->token.line_num; if (next_token(s)) return -1; if (s->token.val == TOK_FUNCTION) { if (js_parse_function_decl(s, JS_PARSE_FUNC_EXPR, JS_FUNC_ASYNC, JS_ATOM_NULL, source_ptr, source_line_num)) return -1; } else if ((parse_flags & PF_ARROW_FUNC) && ((s->token.val == '(' && js_parse_skip_parens_token(s, NULL, TRUE) == TOK_ARROW) || (s->token.val == TOK_IDENT && !s->token.u.ident.is_reserved && peek_token(s, TRUE) == TOK_ARROW))) { if (js_parse_function_decl(s, JS_PARSE_FUNC_ARROW, JS_FUNC_ASYNC, JS_ATOM_NULL, source_ptr, source_line_num)) return -1; } else { name = JS_DupAtom(s->ctx, JS_ATOM_async); goto do_get_var; } } else { if (s->token.u.ident.atom == JS_ATOM_arguments && !s->cur_func->arguments_allowed) { js_parse_error(s, "'arguments' identifier is not allowed in class field initializer"); return -1; } name = JS_DupAtom(s->ctx, s->token.u.ident.atom); if (next_token(s)) /* update line number before emitting code */ return -1; do_get_var: emit_op(s, OP_scope_get_var); emit_u32(s, name); emit_u16(s, s->cur_func->scope_level); } } break; case '{': case '[': { int skip_bits; if (js_parse_skip_parens_token(s, &skip_bits, FALSE) == '=') { if (js_parse_destructuring_element(s, 0, 0, FALSE, skip_bits & SKIP_HAS_ELLIPSIS, TRUE) < 0) return -1; } else { if (s->token.val == '{') { if (js_parse_object_literal(s)) return -1; } else { if (js_parse_array_literal(s)) return -1; } } } break; case TOK_NEW: if (next_token(s)) return -1; if (s->token.val == '.') { if (next_token(s)) return -1; if (!token_is_pseudo_keyword(s, JS_ATOM_target)) return js_parse_error(s, "expecting target"); if (!s->cur_func->new_target_allowed) return js_parse_error(s, "new.target only allowed within functions"); if (next_token(s)) return -1; emit_op(s, OP_scope_get_var); emit_atom(s, JS_ATOM_new_target); emit_u16(s, 0); } else { if (js_parse_postfix_expr(s, 0)) return -1; accept_lparen = TRUE; if (s->token.val != '(') { /* new operator on an object */ emit_op(s, OP_dup); emit_op(s, OP_call_constructor); emit_u16(s, 0); } else { call_type = FUNC_CALL_NEW; } } break; case TOK_SUPER: if (next_token(s)) return -1; if (s->token.val == '(') { if (!s->cur_func->super_call_allowed) return js_parse_error(s, "super() is only valid in a derived class constructor"); call_type = FUNC_CALL_SUPER_CTOR; } else if (s->token.val == '.' || s->token.val == '[') { if (!s->cur_func->super_allowed) return js_parse_error(s, "'super' is only valid in a method"); emit_op(s, OP_scope_get_var); emit_atom(s, JS_ATOM_this); emit_u16(s, 0); emit_op(s, OP_scope_get_var); emit_atom(s, JS_ATOM_home_object); emit_u16(s, 0); emit_op(s, OP_get_super); } else { return js_parse_error(s, "invalid use of 'super'"); } break; case TOK_IMPORT: if (next_token(s)) return -1; if (s->token.val == '.') { if (next_token(s)) return -1; if (!token_is_pseudo_keyword(s, JS_ATOM_meta)) return js_parse_error(s, "meta expected"); if (!s->is_module) return js_parse_error(s, "import.meta only valid in module code"); if (next_token(s)) return -1; emit_op(s, OP_special_object); emit_u8(s, OP_SPECIAL_OBJECT_IMPORT_META); } else { if (js_parse_expect(s, '(')) return -1; if (!accept_lparen) return js_parse_error(s, "invalid use of 'import()'"); if (js_parse_assign_expr(s)) return -1; if (js_parse_expect(s, ')')) return -1; emit_op(s, OP_import); } break; default: return js_parse_error(s, "unexpected token in expression: '%.*s'", (int)(s->buf_ptr - s->token.ptr), s->token.ptr); } optional_chaining_label = -1; for(;;) { JSFunctionDef *fd = s->cur_func; BOOL has_optional_chain = FALSE; if (s->token.val == TOK_QUESTION_MARK_DOT) { /* optional chaining */ if (next_token(s)) return -1; has_optional_chain = TRUE; if (s->token.val == '(' && accept_lparen) { goto parse_func_call; } else if (s->token.val == '[') { goto parse_array_access; } else { goto parse_property; } } else if (s->token.val == TOK_TEMPLATE && call_type == FUNC_CALL_NORMAL) { if (optional_chaining_label >= 0) { return js_parse_error(s, "template literal cannot appear in an optional chain"); } call_type = FUNC_CALL_TEMPLATE; goto parse_func_call2; } else if (s->token.val == '(' && accept_lparen) { int opcode, arg_count, drop_count; /* function call */ parse_func_call: if (next_token(s)) return -1; if (call_type == FUNC_CALL_NORMAL) { parse_func_call2: switch(opcode = get_prev_opcode(fd)) { case OP_get_field: /* keep the object on the stack */ fd->byte_code.buf[fd->last_opcode_pos] = OP_get_field2; drop_count = 2; break; case OP_scope_get_private_field: /* keep the object on the stack */ fd->byte_code.buf[fd->last_opcode_pos] = OP_scope_get_private_field2; drop_count = 2; break; case OP_get_array_el: /* keep the object on the stack */ fd->byte_code.buf[fd->last_opcode_pos] = OP_get_array_el2; drop_count = 2; break; case OP_scope_get_var: { JSAtom name; int scope; name = get_u32(fd->byte_code.buf + fd->last_opcode_pos + 1); scope = get_u16(fd->byte_code.buf + fd->last_opcode_pos + 5); if (name == JS_ATOM_eval && call_type == FUNC_CALL_NORMAL && !has_optional_chain) { /* direct 'eval' */ opcode = OP_eval; } else { /* verify if function name resolves to a simple get_loc/get_arg: a function call inside a `with` statement can resolve to a method call of the `with` context object */ /* XXX: always generate the OP_scope_get_ref and remove it in variable resolution pass ? */ if (has_with_scope(fd, scope)) { opcode = OP_scope_get_ref; fd->byte_code.buf[fd->last_opcode_pos] = opcode; } } drop_count = 1; } break; case OP_get_super_value: fd->byte_code.buf[fd->last_opcode_pos] = OP_get_array_el; /* on stack: this func_obj */ opcode = OP_get_array_el; drop_count = 2; break; default: opcode = OP_invalid; drop_count = 1; break; } if (has_optional_chain) { optional_chain_test(s, &optional_chaining_label, drop_count); } } else { opcode = OP_invalid; } if (call_type == FUNC_CALL_TEMPLATE) { if (js_parse_template(s, 1, &arg_count)) return -1; goto emit_func_call; } else if (call_type == FUNC_CALL_SUPER_CTOR) { emit_op(s, OP_scope_get_var); emit_atom(s, JS_ATOM_this_active_func); emit_u16(s, 0); emit_op(s, OP_get_super); emit_op(s, OP_scope_get_var); emit_atom(s, JS_ATOM_new_target); emit_u16(s, 0); } else if (call_type == FUNC_CALL_NEW) { emit_op(s, OP_dup); /* new.target = function */ } /* parse arguments */ arg_count = 0; while (s->token.val != ')') { if (arg_count >= 65535) { return js_parse_error(s, "Too many call arguments"); } if (s->token.val == TOK_ELLIPSIS) break; if (js_parse_assign_expr(s)) return -1; arg_count++; if (s->token.val == ')') break; /* accept a trailing comma before the ')' */ if (js_parse_expect(s, ',')) return -1; } if (s->token.val == TOK_ELLIPSIS) { emit_op(s, OP_array_from); emit_u16(s, arg_count); emit_op(s, OP_push_i32); emit_u32(s, arg_count); /* on stack: array idx */ while (s->token.val != ')') { if (s->token.val == TOK_ELLIPSIS) { if (next_token(s)) return -1; if (js_parse_assign_expr(s)) return -1; #if 1 /* XXX: could pass is_last indicator? */ emit_op(s, OP_append); #else int label_next, label_done; label_next = new_label(s); label_done = new_label(s); /* push enumerate object below array/idx pair */ emit_op(s, OP_for_of_start); emit_op(s, OP_rot5l); emit_op(s, OP_rot5l); emit_label(s, label_next); /* on stack: enum_rec array idx */ emit_op(s, OP_for_of_next); emit_u8(s, 2); emit_goto(s, OP_if_true, label_done); /* append element */ /* enum_rec array idx val -> enum_rec array new_idx */ emit_op(s, OP_define_array_el); emit_op(s, OP_inc); emit_goto(s, OP_goto, label_next); emit_label(s, label_done); /* close enumeration, drop enum_rec and idx */ emit_op(s, OP_drop); /* drop undef */ emit_op(s, OP_nip1); /* drop enum_rec */ emit_op(s, OP_nip1); emit_op(s, OP_nip1); #endif } else { if (js_parse_assign_expr(s)) return -1; /* array idx val */ emit_op(s, OP_define_array_el); emit_op(s, OP_inc); } if (s->token.val == ')') break; /* accept a trailing comma before the ')' */ if (js_parse_expect(s, ',')) return -1; } if (next_token(s)) return -1; /* drop the index */ emit_op(s, OP_drop); /* apply function call */ switch(opcode) { case OP_get_field: case OP_scope_get_private_field: case OP_get_array_el: case OP_scope_get_ref: /* obj func array -> func obj array */ emit_op(s, OP_perm3); emit_op(s, OP_apply); emit_u16(s, call_type == FUNC_CALL_NEW); break; case OP_eval: emit_op(s, OP_apply_eval); emit_u16(s, fd->scope_level); fd->has_eval_call = TRUE; break; default: if (call_type == FUNC_CALL_SUPER_CTOR) { emit_op(s, OP_apply); emit_u16(s, 1); /* set the 'this' value */ emit_op(s, OP_dup); emit_op(s, OP_scope_put_var_init); emit_atom(s, JS_ATOM_this); emit_u16(s, 0); emit_class_field_init(s); } else if (call_type == FUNC_CALL_NEW) { /* obj func array -> func obj array */ emit_op(s, OP_perm3); emit_op(s, OP_apply); emit_u16(s, 1); } else { /* func array -> func undef array */ emit_op(s, OP_undefined); emit_op(s, OP_swap); emit_op(s, OP_apply); emit_u16(s, 0); } break; } } else { if (next_token(s)) return -1; emit_func_call: switch(opcode) { case OP_get_field: case OP_scope_get_private_field: case OP_get_array_el: case OP_scope_get_ref: emit_op(s, OP_call_method); emit_u16(s, arg_count); break; case OP_eval: emit_op(s, OP_eval); emit_u16(s, arg_count); emit_u16(s, fd->scope_level); fd->has_eval_call = TRUE; break; default: if (call_type == FUNC_CALL_SUPER_CTOR) { emit_op(s, OP_call_constructor); emit_u16(s, arg_count); /* set the 'this' value */ emit_op(s, OP_dup); emit_op(s, OP_scope_put_var_init); emit_atom(s, JS_ATOM_this); emit_u16(s, 0); emit_class_field_init(s); } else if (call_type == FUNC_CALL_NEW) { emit_op(s, OP_call_constructor); emit_u16(s, arg_count); } else { emit_op(s, OP_call); emit_u16(s, arg_count); } break; } } call_type = FUNC_CALL_NORMAL; } else if (s->token.val == '.') { if (next_token(s)) return -1; parse_property: if (s->token.val == TOK_PRIVATE_NAME) { /* private class field */ if (get_prev_opcode(fd) == OP_get_super) { return js_parse_error(s, "private class field forbidden after super"); } if (has_optional_chain) { optional_chain_test(s, &optional_chaining_label, 1); } emit_op(s, OP_scope_get_private_field); emit_atom(s, s->token.u.ident.atom); emit_u16(s, s->cur_func->scope_level); } else { if (!token_is_ident(s->token.val)) { return js_parse_error(s, "expecting field name"); } if (get_prev_opcode(fd) == OP_get_super) { JSValue val; int ret; val = JS_AtomToValue(s->ctx, s->token.u.ident.atom); ret = emit_push_const(s, val, 1); JS_FreeValue(s->ctx, val); if (ret) return -1; emit_op(s, OP_get_super_value); } else { if (has_optional_chain) { optional_chain_test(s, &optional_chaining_label, 1); } emit_op(s, OP_get_field); emit_atom(s, s->token.u.ident.atom); } } if (next_token(s)) return -1; } else if (s->token.val == '[') { int prev_op; parse_array_access: prev_op = get_prev_opcode(fd); if (has_optional_chain) { optional_chain_test(s, &optional_chaining_label, 1); } if (next_token(s)) return -1; if (js_parse_expr(s)) return -1; if (js_parse_expect(s, ']')) return -1; if (prev_op == OP_get_super) { emit_op(s, OP_get_super_value); } else { emit_op(s, OP_get_array_el); } } else { break; } } if (optional_chaining_label >= 0) emit_label(s, optional_chaining_label); return 0; } static int js_unsupported_keyword(JSParseState *s, JSAtom atom) { char buf[ATOM_GET_STR_BUF_SIZE]; return js_parse_error(s, "unsupported keyword: %s", JS_AtomGetStr(s->ctx, buf, sizeof(buf), atom)); } /* XXX: handle IteratorClose when exiting the loop before the enumeration is done */ static __exception int js_parse_for_in_of(JSParseState *s, int label_name, BOOL is_async) { JSContext *ctx = s->ctx; JSFunctionDef *fd = s->cur_func; JSAtom var_name; BOOL has_initializer, is_for_of, has_destructuring; int tok, tok1, opcode, scope, block_scope_level; int label_next, label_expr, label_cont, label_body, label_break; int pos_next, pos_expr; BlockEnv break_entry; has_initializer = FALSE; has_destructuring = FALSE; is_for_of = FALSE; block_scope_level = fd->scope_level; label_cont = new_label(s); label_body = new_label(s); label_break = new_label(s); label_next = new_label(s); /* create scope for the lexical variables declared in the enumeration expressions. XXX: Not completely correct because of weird capturing semantics in `for (i of o) a.push(function(){return i})` */ push_scope(s); /* local for_in scope starts here so individual elements can be closed in statement. */ push_break_entry(s->cur_func, &break_entry, label_name, label_break, label_cont, 1); break_entry.scope_level = block_scope_level; label_expr = emit_goto(s, OP_goto, -1); pos_next = s->cur_func->byte_code.size; emit_label(s, label_next); tok = s->token.val; switch (is_let(s, DECL_MASK_OTHER)) { case TRUE: tok = TOK_LET; break; case FALSE: break; default: return -1; } if (tok == TOK_VAR || tok == TOK_LET || tok == TOK_CONST) { if (next_token(s)) return -1; if (!(s->token.val == TOK_IDENT && !s->token.u.ident.is_reserved)) { if (s->token.val == '[' || s->token.val == '{') { if (js_parse_destructuring_element(s, tok, 0, TRUE, -1, FALSE) < 0) return -1; has_destructuring = TRUE; } else { return js_parse_error(s, "variable name expected"); } var_name = JS_ATOM_NULL; } else { var_name = JS_DupAtom(ctx, s->token.u.ident.atom); if (next_token(s)) { JS_FreeAtom(s->ctx, var_name); return -1; } if (js_define_var(s, var_name, tok)) { JS_FreeAtom(s->ctx, var_name); return -1; } emit_op(s, (tok == TOK_CONST || tok == TOK_LET) ? OP_scope_put_var_init : OP_scope_put_var); emit_atom(s, var_name); emit_u16(s, fd->scope_level); } } else { int skip_bits; if ((s->token.val == '[' || s->token.val == '{') && ((tok1 = js_parse_skip_parens_token(s, &skip_bits, FALSE)) == TOK_IN || tok1 == TOK_OF)) { if (js_parse_destructuring_element(s, 0, 0, TRUE, skip_bits & SKIP_HAS_ELLIPSIS, TRUE) < 0) return -1; } else { int lvalue_label; if (js_parse_left_hand_side_expr(s)) return -1; if (get_lvalue(s, &opcode, &scope, &var_name, &lvalue_label, NULL, FALSE, TOK_FOR)) return -1; put_lvalue(s, opcode, scope, var_name, lvalue_label, PUT_LVALUE_NOKEEP_BOTTOM, FALSE); } var_name = JS_ATOM_NULL; } emit_goto(s, OP_goto, label_body); pos_expr = s->cur_func->byte_code.size; emit_label(s, label_expr); if (s->token.val == '=') { /* XXX: potential scoping issue if inside `with` statement */ has_initializer = TRUE; /* parse and evaluate initializer prior to evaluating the object (only used with "for in" with a non lexical variable in non strict mode */ if (next_token(s) || js_parse_assign_expr2(s, 0)) { JS_FreeAtom(ctx, var_name); return -1; } if (var_name != JS_ATOM_NULL) { emit_op(s, OP_scope_put_var); emit_atom(s, var_name); emit_u16(s, fd->scope_level); } } JS_FreeAtom(ctx, var_name); if (token_is_pseudo_keyword(s, JS_ATOM_of)) { break_entry.has_iterator = is_for_of = TRUE; break_entry.drop_count += 2; if (has_initializer) goto initializer_error; } else if (s->token.val == TOK_IN) { if (is_async) return js_parse_error(s, "'for await' loop should be used with 'of'"); if (has_initializer && (tok != TOK_VAR || (fd->js_mode & JS_MODE_STRICT) || has_destructuring)) { initializer_error: return js_parse_error(s, "a declaration in the head of a for-%s loop can't have an initializer", is_for_of ? "of" : "in"); } } else { return js_parse_error(s, "expected 'of' or 'in' in for control expression"); } if (next_token(s)) return -1; if (is_for_of) { if (js_parse_assign_expr(s)) return -1; } else { if (js_parse_expr(s)) return -1; } /* close the scope after having evaluated the expression so that the TDZ values are in the closures */ close_scopes(s, s->cur_func->scope_level, block_scope_level); if (is_for_of) { if (is_async) emit_op(s, OP_for_await_of_start); else emit_op(s, OP_for_of_start); /* on stack: enum_rec */ } else { emit_op(s, OP_for_in_start); /* on stack: enum_obj */ } emit_goto(s, OP_goto, label_cont); if (js_parse_expect(s, ')')) return -1; if (OPTIMIZE) { /* move the `next` code here */ DynBuf *bc = &s->cur_func->byte_code; int chunk_size = pos_expr - pos_next; int offset = bc->size - pos_next; int i; dbuf_realloc(bc, bc->size + chunk_size); dbuf_put(bc, bc->buf + pos_next, chunk_size); memset(bc->buf + pos_next, OP_nop, chunk_size); /* `next` part ends with a goto */ s->cur_func->last_opcode_pos = bc->size - 5; /* relocate labels */ for (i = label_cont; i < s->cur_func->label_count; i++) { LabelSlot *ls = &s->cur_func->label_slots[i]; if (ls->pos >= pos_next && ls->pos < pos_expr) ls->pos += offset; } } emit_label(s, label_body); if (js_parse_statement(s)) return -1; close_scopes(s, s->cur_func->scope_level, block_scope_level); emit_label(s, label_cont); if (is_for_of) { if (is_async) { /* call the next method */ /* stack: iter_obj next catch_offset */ emit_op(s, OP_dup3); emit_op(s, OP_drop); emit_op(s, OP_call_method); emit_u16(s, 0); /* get the result of the promise */ emit_op(s, OP_await); /* unwrap the value and done values */ emit_op(s, OP_iterator_get_value_done); } else { emit_op(s, OP_for_of_next); emit_u8(s, 0); } } else { emit_op(s, OP_for_in_next); } /* on stack: enum_rec / enum_obj value bool */ emit_goto(s, OP_if_false, label_next); /* drop the undefined value from for_xx_next */ emit_op(s, OP_drop); emit_label(s, label_break); if (is_for_of) { /* close and drop enum_rec */ emit_op(s, OP_iterator_close); } else { emit_op(s, OP_drop); } pop_break_entry(s->cur_func); pop_scope(s); return 0; } /* allowed parse_flags: PF_IN_ACCEPTED */ static __exception int js_parse_var(JSParseState *s, int parse_flags, int tok, BOOL export_flag) { JSContext *ctx = s->ctx; JSFunctionDef *fd = s->cur_func; JSAtom name = JS_ATOM_NULL; for (;;) { if (s->token.val == TOK_IDENT) { if (s->token.u.ident.is_reserved) { return js_parse_error_reserved_identifier(s); } name = JS_DupAtom(ctx, s->token.u.ident.atom); if (name == JS_ATOM_let && (tok == TOK_LET || tok == TOK_CONST)) { js_parse_error(s, "'let' is not a valid lexical identifier"); goto var_error; } if (next_token(s)) goto var_error; if (js_define_var(s, name, tok)) goto var_error; if (export_flag) { if (!add_export_entry(s, s->cur_func->module, name, name, JS_EXPORT_TYPE_LOCAL)) goto var_error; } if (s->token.val == '=') { if (next_token(s)) goto var_error; if (tok == TOK_VAR) { /* Must make a reference for proper `with` semantics */ int opcode, scope, label; JSAtom name1; emit_op(s, OP_scope_get_var); emit_atom(s, name); emit_u16(s, fd->scope_level); if (get_lvalue(s, &opcode, &scope, &name1, &label, NULL, FALSE, '=') < 0) goto var_error; if (js_parse_assign_expr2(s, parse_flags)) { JS_FreeAtom(ctx, name1); goto var_error; } set_object_name(s, name); put_lvalue(s, opcode, scope, name1, label, PUT_LVALUE_NOKEEP, FALSE); } else { if (js_parse_assign_expr2(s, parse_flags)) goto var_error; set_object_name(s, name); emit_op(s, (tok == TOK_CONST || tok == TOK_LET) ? OP_scope_put_var_init : OP_scope_put_var); emit_atom(s, name); emit_u16(s, fd->scope_level); } } else { if (tok == TOK_CONST) { js_parse_error(s, "missing initializer for const variable"); goto var_error; } if (tok == TOK_LET) { /* initialize lexical variable upon entering its scope */ emit_op(s, OP_undefined); emit_op(s, OP_scope_put_var_init); emit_atom(s, name); emit_u16(s, fd->scope_level); } } JS_FreeAtom(ctx, name); } else { int skip_bits; if ((s->token.val == '[' || s->token.val == '{') && js_parse_skip_parens_token(s, &skip_bits, FALSE) == '=') { emit_op(s, OP_undefined); if (js_parse_destructuring_element(s, tok, 0, TRUE, skip_bits & SKIP_HAS_ELLIPSIS, TRUE) < 0) return -1; } else { return js_parse_error(s, "variable name expected"); } } if (s->token.val != ',') break; if (next_token(s)) return -1; } return 0; var_error: JS_FreeAtom(ctx, name); return -1; } /* execute the finally blocks before return */ static void emit_return(JSParseState *s, BOOL hasval) { BlockEnv *top; int drop_count; drop_count = 0; top = s->cur_func->top_break; while (top != NULL) { /* XXX: emit the appropriate OP_leave_scope opcodes? Probably not required as all local variables will be closed upon returning from JS_CallInternal, but not in the same order. */ if (top->has_iterator) { /* with 'yield', the exact number of OP_drop to emit is unknown, so we use a specific operation to look for the catch offset */ if (!hasval) { emit_op(s, OP_undefined); hasval = TRUE; } emit_op(s, OP_iterator_close_return); if (s->cur_func->func_kind == JS_FUNC_ASYNC_GENERATOR) { int label_next, label_next2; emit_op(s, OP_drop); /* catch offset */ emit_op(s, OP_drop); /* next */ emit_op(s, OP_get_field2); emit_atom(s, JS_ATOM_return); /* stack: iter_obj return_func */ emit_op(s, OP_dup); emit_op(s, OP_is_undefined_or_null); label_next = emit_goto(s, OP_if_true, -1); emit_op(s, OP_call_method); emit_u16(s, 0); emit_op(s, OP_iterator_check_object); emit_op(s, OP_await); label_next2 = emit_goto(s, OP_goto, -1); emit_label(s, label_next); emit_op(s, OP_drop); emit_label(s, label_next2); emit_op(s, OP_drop); } else { emit_op(s, OP_iterator_close); } drop_count = -3; } drop_count += top->drop_count; if (top->label_finally != -1) { while(drop_count) { /* must keep the stack top if hasval */ emit_op(s, hasval ? OP_nip : OP_drop); drop_count--; } if (!hasval) { /* must push return value to keep same stack size */ emit_op(s, OP_undefined); hasval = TRUE; } emit_goto(s, OP_gosub, top->label_finally); } top = top->prev; } if (s->cur_func->is_derived_class_constructor) { int label_return; /* 'this' can be uninitialized, so it may be accessed only if the derived class constructor does not return an object */ if (hasval) { emit_op(s, OP_check_ctor_return); label_return = emit_goto(s, OP_if_false, -1); emit_op(s, OP_drop); } else { label_return = -1; } /* XXX: if this is not initialized, should throw the ReferenceError in the caller realm */ emit_op(s, OP_scope_get_var); emit_atom(s, JS_ATOM_this); emit_u16(s, 0); emit_label(s, label_return); emit_op(s, OP_return); } else if (s->cur_func->func_kind != JS_FUNC_NORMAL) { if (!hasval) { emit_op(s, OP_undefined); } else if (s->cur_func->func_kind == JS_FUNC_ASYNC_GENERATOR) { emit_op(s, OP_await); } emit_op(s, OP_return_async); } else { emit_op(s, hasval ? OP_return : OP_return_undef); } } static void set_eval_ret_undefined(JSParseState *s) { if (s->cur_func->eval_ret_idx >= 0) { emit_op(s, OP_undefined); emit_op(s, OP_put_loc); emit_u16(s, s->cur_func->eval_ret_idx); } } /* allowed parse_flags: PF_IN_ACCEPTED */ static __exception int js_parse_expr2(JSParseState *s, int parse_flags) { BOOL comma = FALSE; for(;;) { if (js_parse_assign_expr2(s, parse_flags)) return -1; if (comma) { /* prevent get_lvalue from using the last expression as an lvalue. This also prevents the conversion of of get_var to get_ref for method lookup in function call inside `with` statement. */ s->cur_func->last_opcode_pos = -1; } if (s->token.val != ',') break; comma = TRUE; if (next_token(s)) return -1; emit_op(s, OP_drop); } return 0; } static __exception int emit_break(JSParseState *s, JSAtom name, int is_cont) { BlockEnv *top; int i, scope_level; scope_level = s->cur_func->scope_level; top = s->cur_func->top_break; while (top != NULL) { close_scopes(s, scope_level, top->scope_level); scope_level = top->scope_level; if (is_cont && top->label_cont != -1 && (name == JS_ATOM_NULL || top->label_name == name)) { /* continue stays inside the same block */ emit_goto(s, OP_goto, top->label_cont); return 0; } if (!is_cont && top->label_break != -1 && (name == JS_ATOM_NULL || top->label_name == name)) { emit_goto(s, OP_goto, top->label_break); return 0; } i = 0; if (top->has_iterator) { emit_op(s, OP_iterator_close); i += 3; } for(; i < top->drop_count; i++) emit_op(s, OP_drop); if (top->label_finally != -1) { /* must push dummy value to keep same stack depth */ emit_op(s, OP_undefined); emit_goto(s, OP_gosub, top->label_finally); emit_op(s, OP_drop); } top = top->prev; } if (name == JS_ATOM_NULL) { if (is_cont) return js_parse_error(s, "continue must be inside loop"); else return js_parse_error(s, "break must be inside loop or switch"); } else { return js_parse_error(s, "break/continue label not found"); } } static int js_parse_statement_or_decl(JSParseState *s, int decl_mask) { JSContext *ctx = s->ctx; JSAtom label_name; int tok; /* specific label handling */ /* XXX: support multiple labels on loop statements */ label_name = JS_ATOM_NULL; if (is_label(s)) { BlockEnv *be; label_name = JS_DupAtom(ctx, s->token.u.ident.atom); for (be = s->cur_func->top_break; be; be = be->prev) { if (be->label_name == label_name) { js_parse_error(s, "duplicate label name"); goto fail; } } if (next_token(s)) goto fail; if (js_parse_expect(s, ':')) goto fail; if (s->token.val != TOK_FOR && s->token.val != TOK_DO && s->token.val != TOK_WHILE) { /* labelled regular statement */ int label_break, mask; BlockEnv break_entry; label_break = new_label(s); push_break_entry(s->cur_func, &break_entry, label_name, label_break, -1, 0); if (!(s->cur_func->js_mode & JS_MODE_STRICT) && (decl_mask & DECL_MASK_FUNC_WITH_LABEL)) { mask = DECL_MASK_FUNC | DECL_MASK_FUNC_WITH_LABEL; } else { mask = 0; } if (js_parse_statement_or_decl(s, mask)) goto fail; emit_label(s, label_break); pop_break_entry(s->cur_func); goto done; } } switch(tok = s->token.val) { case '{': if (js_parse_block(s)) goto fail; break; case TOK_RETURN: if (s->cur_func->is_eval) { js_parse_error(s, "return not in a function"); goto fail; } if (next_token(s)) goto fail; if (s->token.val != ';' && s->token.val != '}' && !s->got_lf) { if (js_parse_expr(s)) goto fail; emit_return(s, TRUE); } else { emit_return(s, FALSE); } if (js_parse_expect_semi(s)) goto fail; break; case TOK_THROW: if (next_token(s)) goto fail; if (s->got_lf) { js_parse_error(s, "line terminator not allowed after throw"); goto fail; } if (js_parse_expr(s)) goto fail; emit_op(s, OP_throw); if (js_parse_expect_semi(s)) goto fail; break; case TOK_LET: case TOK_CONST: haslet: if (!(decl_mask & DECL_MASK_OTHER)) { js_parse_error(s, "lexical declarations can't appear in single-statement context"); goto fail; } /* fall thru */ case TOK_VAR: if (next_token(s)) goto fail; if (js_parse_var(s, TRUE, tok, FALSE)) goto fail; if (js_parse_expect_semi(s)) goto fail; break; case TOK_IF: { int label1, label2, mask; if (next_token(s)) goto fail; /* create a new scope for `let f;if(1) function f(){}` */ push_scope(s); set_eval_ret_undefined(s); if (js_parse_expr_paren(s)) goto fail; label1 = emit_goto(s, OP_if_false, -1); if (s->cur_func->js_mode & JS_MODE_STRICT) mask = 0; else mask = DECL_MASK_FUNC; /* Annex B.3.4 */ if (js_parse_statement_or_decl(s, mask)) goto fail; if (s->token.val == TOK_ELSE) { label2 = emit_goto(s, OP_goto, -1); if (next_token(s)) goto fail; emit_label(s, label1); if (js_parse_statement_or_decl(s, mask)) goto fail; label1 = label2; } emit_label(s, label1); pop_scope(s); } break; case TOK_WHILE: { int label_cont, label_break; BlockEnv break_entry; label_cont = new_label(s); label_break = new_label(s); push_break_entry(s->cur_func, &break_entry, label_name, label_break, label_cont, 0); if (next_token(s)) goto fail; set_eval_ret_undefined(s); emit_label(s, label_cont); if (js_parse_expr_paren(s)) goto fail; emit_goto(s, OP_if_false, label_break); if (js_parse_statement(s)) goto fail; emit_goto(s, OP_goto, label_cont); emit_label(s, label_break); pop_break_entry(s->cur_func); } break; case TOK_DO: { int label_cont, label_break, label1; BlockEnv break_entry; label_cont = new_label(s); label_break = new_label(s); label1 = new_label(s); push_break_entry(s->cur_func, &break_entry, label_name, label_break, label_cont, 0); if (next_token(s)) goto fail; emit_label(s, label1); set_eval_ret_undefined(s); if (js_parse_statement(s)) goto fail; emit_label(s, label_cont); if (js_parse_expect(s, TOK_WHILE)) goto fail; if (js_parse_expr_paren(s)) goto fail; /* Insert semicolon if missing */ if (s->token.val == ';') { if (next_token(s)) goto fail; } emit_goto(s, OP_if_true, label1); emit_label(s, label_break); pop_break_entry(s->cur_func); } break; case TOK_FOR: { int label_cont, label_break, label_body, label_test; int pos_cont, pos_body, block_scope_level; BlockEnv break_entry; int tok, bits; BOOL is_async; if (next_token(s)) goto fail; set_eval_ret_undefined(s); bits = 0; is_async = FALSE; if (s->token.val == '(') { js_parse_skip_parens_token(s, &bits, FALSE); } else if (s->token.val == TOK_AWAIT) { if (!(s->cur_func->func_kind & JS_FUNC_ASYNC)) { js_parse_error(s, "for await is only valid in asynchronous functions"); goto fail; } is_async = TRUE; if (next_token(s)) goto fail; } if (js_parse_expect(s, '(')) goto fail; if (!(bits & SKIP_HAS_SEMI)) { /* parse for/in or for/of */ if (js_parse_for_in_of(s, label_name, is_async)) goto fail; break; } block_scope_level = s->cur_func->scope_level; /* create scope for the lexical variables declared in the initial, test and increment expressions */ push_scope(s); /* initial expression */ tok = s->token.val; if (tok != ';') { switch (is_let(s, DECL_MASK_OTHER)) { case TRUE: tok = TOK_LET; break; case FALSE: break; default: goto fail; } if (tok == TOK_VAR || tok == TOK_LET || tok == TOK_CONST) { if (next_token(s)) goto fail; if (js_parse_var(s, FALSE, tok, FALSE)) goto fail; } else { if (js_parse_expr2(s, FALSE)) goto fail; emit_op(s, OP_drop); } /* close the closures before the first iteration */ close_scopes(s, s->cur_func->scope_level, block_scope_level); } if (js_parse_expect(s, ';')) goto fail; label_test = new_label(s); label_cont = new_label(s); label_body = new_label(s); label_break = new_label(s); push_break_entry(s->cur_func, &break_entry, label_name, label_break, label_cont, 0); /* test expression */ if (s->token.val == ';') { /* no test expression */ label_test = label_body; } else { emit_label(s, label_test); if (js_parse_expr(s)) goto fail; emit_goto(s, OP_if_false, label_break); } if (js_parse_expect(s, ';')) goto fail; if (s->token.val == ')') { /* no end expression */ break_entry.label_cont = label_cont = label_test; pos_cont = 0; /* avoid warning */ } else { /* skip the end expression */ emit_goto(s, OP_goto, label_body); pos_cont = s->cur_func->byte_code.size; emit_label(s, label_cont); if (js_parse_expr(s)) goto fail; emit_op(s, OP_drop); if (label_test != label_body) emit_goto(s, OP_goto, label_test); } if (js_parse_expect(s, ')')) goto fail; pos_body = s->cur_func->byte_code.size; emit_label(s, label_body); if (js_parse_statement(s)) goto fail; /* close the closures before the next iteration */ /* XXX: check continue case */ close_scopes(s, s->cur_func->scope_level, block_scope_level); if (OPTIMIZE && label_test != label_body && label_cont != label_test) { /* move the increment code here */ DynBuf *bc = &s->cur_func->byte_code; int chunk_size = pos_body - pos_cont; int offset = bc->size - pos_cont; int i; dbuf_realloc(bc, bc->size + chunk_size); dbuf_put(bc, bc->buf + pos_cont, chunk_size); memset(bc->buf + pos_cont, OP_nop, chunk_size); /* increment part ends with a goto */ s->cur_func->last_opcode_pos = bc->size - 5; /* relocate labels */ for (i = label_cont; i < s->cur_func->label_count; i++) { LabelSlot *ls = &s->cur_func->label_slots[i]; if (ls->pos >= pos_cont && ls->pos < pos_body) ls->pos += offset; } } else { emit_goto(s, OP_goto, label_cont); } emit_label(s, label_break); pop_break_entry(s->cur_func); pop_scope(s); } break; case TOK_BREAK: case TOK_CONTINUE: { int is_cont = s->token.val - TOK_BREAK; int label; if (next_token(s)) goto fail; if (!s->got_lf && s->token.val == TOK_IDENT && !s->token.u.ident.is_reserved) label = s->token.u.ident.atom; else label = JS_ATOM_NULL; if (emit_break(s, label, is_cont)) goto fail; if (label != JS_ATOM_NULL) { if (next_token(s)) goto fail; } if (js_parse_expect_semi(s)) goto fail; } break; case TOK_SWITCH: { int label_case, label_break, label1; int default_label_pos; BlockEnv break_entry; if (next_token(s)) goto fail; set_eval_ret_undefined(s); if (js_parse_expr_paren(s)) goto fail; push_scope(s); label_break = new_label(s); push_break_entry(s->cur_func, &break_entry, label_name, label_break, -1, 1); if (js_parse_expect(s, '{')) goto fail; default_label_pos = -1; label_case = -1; while (s->token.val != '}') { if (s->token.val == TOK_CASE) { label1 = -1; if (label_case >= 0) { /* skip the case if needed */ label1 = emit_goto(s, OP_goto, -1); } emit_label(s, label_case); label_case = -1; for (;;) { /* parse a sequence of case clauses */ if (next_token(s)) goto fail; emit_op(s, OP_dup); if (js_parse_expr(s)) goto fail; if (js_parse_expect(s, ':')) goto fail; emit_op(s, OP_strict_eq); if (s->token.val == TOK_CASE) { label1 = emit_goto(s, OP_if_true, label1); } else { label_case = emit_goto(s, OP_if_false, -1); emit_label(s, label1); break; } } } else if (s->token.val == TOK_DEFAULT) { if (next_token(s)) goto fail; if (js_parse_expect(s, ':')) goto fail; if (default_label_pos >= 0) { js_parse_error(s, "duplicate default"); goto fail; } if (label_case < 0) { /* falling thru direct from switch expression */ label_case = emit_goto(s, OP_goto, -1); } /* Emit a dummy label opcode. Label will be patched after the end of the switch body. Do not use emit_label(s, 0) because it would clobber label 0 address, preventing proper optimizer operation. */ emit_op(s, OP_label); emit_u32(s, 0); default_label_pos = s->cur_func->byte_code.size - 4; } else { if (label_case < 0) { /* falling thru direct from switch expression */ js_parse_error(s, "invalid switch statement"); goto fail; } if (js_parse_statement_or_decl(s, DECL_MASK_ALL)) goto fail; } } if (js_parse_expect(s, '}')) goto fail; if (default_label_pos >= 0) { /* Ugly patch for the the `default` label, shameful and risky */ put_u32(s->cur_func->byte_code.buf + default_label_pos, label_case); s->cur_func->label_slots[label_case].pos = default_label_pos + 4; } else { emit_label(s, label_case); } emit_label(s, label_break); emit_op(s, OP_drop); /* drop the switch expression */ pop_break_entry(s->cur_func); pop_scope(s); } break; case TOK_TRY: { int label_catch, label_catch2, label_finally, label_end; JSAtom name; BlockEnv block_env; set_eval_ret_undefined(s); if (next_token(s)) goto fail; label_catch = new_label(s); label_catch2 = new_label(s); label_finally = new_label(s); label_end = new_label(s); emit_goto(s, OP_catch, label_catch); push_break_entry(s->cur_func, &block_env, JS_ATOM_NULL, -1, -1, 1); block_env.label_finally = label_finally; if (js_parse_block(s)) goto fail; pop_break_entry(s->cur_func); if (js_is_live_code(s)) { /* drop the catch offset */ emit_op(s, OP_drop); /* must push dummy value to keep same stack size */ emit_op(s, OP_undefined); emit_goto(s, OP_gosub, label_finally); emit_op(s, OP_drop); emit_goto(s, OP_goto, label_end); } if (s->token.val == TOK_CATCH) { if (next_token(s)) goto fail; push_scope(s); /* catch variable */ emit_label(s, label_catch); if (s->token.val == '{') { /* support optional-catch-binding feature */ emit_op(s, OP_drop); /* pop the exception object */ } else { if (js_parse_expect(s, '(')) goto fail; if (!(s->token.val == TOK_IDENT && !s->token.u.ident.is_reserved)) { if (s->token.val == '[' || s->token.val == '{') { /* XXX: TOK_LET is not completely correct */ if (js_parse_destructuring_element(s, TOK_LET, 0, TRUE, -1, TRUE) < 0) goto fail; } else { js_parse_error(s, "identifier expected"); goto fail; } } else { name = JS_DupAtom(ctx, s->token.u.ident.atom); if (next_token(s) || js_define_var(s, name, TOK_CATCH) < 0) { JS_FreeAtom(ctx, name); goto fail; } /* store the exception value in the catch variable */ emit_op(s, OP_scope_put_var); emit_u32(s, name); emit_u16(s, s->cur_func->scope_level); } if (js_parse_expect(s, ')')) goto fail; } /* XXX: should keep the address to nop it out if there is no finally block */ emit_goto(s, OP_catch, label_catch2); push_scope(s); /* catch block */ push_break_entry(s->cur_func, &block_env, JS_ATOM_NULL, -1, -1, 1); block_env.label_finally = label_finally; if (js_parse_block(s)) goto fail; pop_break_entry(s->cur_func); pop_scope(s); /* catch block */ pop_scope(s); /* catch variable */ if (js_is_live_code(s)) { /* drop the catch2 offset */ emit_op(s, OP_drop); /* XXX: should keep the address to nop it out if there is no finally block */ /* must push dummy value to keep same stack size */ emit_op(s, OP_undefined); emit_goto(s, OP_gosub, label_finally); emit_op(s, OP_drop); emit_goto(s, OP_goto, label_end); } /* catch exceptions thrown in the catch block to execute the * finally clause and rethrow the exception */ emit_label(s, label_catch2); /* catch value is at TOS, no need to push undefined */ emit_goto(s, OP_gosub, label_finally); emit_op(s, OP_throw); } else if (s->token.val == TOK_FINALLY) { /* finally without catch : execute the finally clause * and rethrow the exception */ emit_label(s, label_catch); /* catch value is at TOS, no need to push undefined */ emit_goto(s, OP_gosub, label_finally); emit_op(s, OP_throw); } else { js_parse_error(s, "expecting catch or finally"); goto fail; } emit_label(s, label_finally); if (s->token.val == TOK_FINALLY) { int saved_eval_ret_idx = 0; /* avoid warning */ if (next_token(s)) goto fail; /* on the stack: ret_value gosub_ret_value */ push_break_entry(s->cur_func, &block_env, JS_ATOM_NULL, -1, -1, 2); if (s->cur_func->eval_ret_idx >= 0) { /* 'finally' updates eval_ret only if not a normal termination */ saved_eval_ret_idx = add_var(s->ctx, s->cur_func, JS_ATOM__ret_); if (saved_eval_ret_idx < 0) goto fail; emit_op(s, OP_get_loc); emit_u16(s, s->cur_func->eval_ret_idx); emit_op(s, OP_put_loc); emit_u16(s, saved_eval_ret_idx); set_eval_ret_undefined(s); } if (js_parse_block(s)) goto fail; if (s->cur_func->eval_ret_idx >= 0) { emit_op(s, OP_get_loc); emit_u16(s, saved_eval_ret_idx); emit_op(s, OP_put_loc); emit_u16(s, s->cur_func->eval_ret_idx); } pop_break_entry(s->cur_func); } emit_op(s, OP_ret); emit_label(s, label_end); } break; case ';': /* empty statement */ if (next_token(s)) goto fail; break; case TOK_WITH: if (s->cur_func->js_mode & JS_MODE_STRICT) { js_parse_error(s, "invalid keyword: with"); goto fail; } else { int with_idx; if (next_token(s)) goto fail; if (js_parse_expr_paren(s)) goto fail; push_scope(s); with_idx = define_var(s, s->cur_func, JS_ATOM__with_, JS_VAR_DEF_WITH); if (with_idx < 0) goto fail; emit_op(s, OP_to_object); emit_op(s, OP_put_loc); emit_u16(s, with_idx); set_eval_ret_undefined(s); if (js_parse_statement(s)) goto fail; /* Popping scope drops lexical context for the with object variable */ pop_scope(s); } break; case TOK_FUNCTION: /* ES6 Annex B.3.2 and B.3.3 semantics */ if (!(decl_mask & DECL_MASK_FUNC)) goto func_decl_error; if (!(decl_mask & DECL_MASK_OTHER) && peek_token(s, FALSE) == '*') goto func_decl_error; goto parse_func_var; case TOK_IDENT: if (s->token.u.ident.is_reserved) { js_parse_error_reserved_identifier(s); goto fail; } /* Determine if `let` introduces a Declaration or an ExpressionStatement */ switch (is_let(s, decl_mask)) { case TRUE: tok = TOK_LET; goto haslet; case FALSE: break; default: goto fail; } if (token_is_pseudo_keyword(s, JS_ATOM_async) && peek_token(s, TRUE) == TOK_FUNCTION) { if (!(decl_mask & DECL_MASK_OTHER)) { func_decl_error: js_parse_error(s, "function declarations can't appear in single-statement context"); goto fail; } parse_func_var: if (js_parse_function_decl(s, JS_PARSE_FUNC_VAR, JS_FUNC_NORMAL, JS_ATOM_NULL, s->token.ptr, s->token.line_num)) goto fail; break; } goto hasexpr; case TOK_CLASS: if (!(decl_mask & DECL_MASK_OTHER)) { js_parse_error(s, "class declarations can't appear in single-statement context"); goto fail; } if (js_parse_class(s, FALSE, JS_PARSE_EXPORT_NONE)) return -1; break; case TOK_DEBUGGER: /* currently no debugger, so just skip the keyword */ if (next_token(s)) goto fail; if (js_parse_expect_semi(s)) goto fail; break; case TOK_ENUM: case TOK_EXPORT: case TOK_EXTENDS: js_unsupported_keyword(s, s->token.u.ident.atom); goto fail; default: hasexpr: if (js_parse_expr(s)) goto fail; if (s->cur_func->eval_ret_idx >= 0) { /* store the expression value so that it can be returned by eval() */ emit_op(s, OP_put_loc); emit_u16(s, s->cur_func->eval_ret_idx); } else { emit_op(s, OP_drop); /* drop the result */ } if (js_parse_expect_semi(s)) goto fail; break; } done: JS_FreeAtom(ctx, label_name); return 0; fail: JS_FreeAtom(ctx, label_name); return -1; } static __exception int js_parse_delete(JSParseState *s) { JSFunctionDef *fd = s->cur_func; JSAtom name; int opcode; if (next_token(s)) return -1; if (js_parse_unary(s, PF_POW_FORBIDDEN)) return -1; switch(opcode = get_prev_opcode(fd)) { case OP_get_field: { JSValue val; int ret; name = get_u32(fd->byte_code.buf + fd->last_opcode_pos + 1); fd->byte_code.size = fd->last_opcode_pos; fd->last_opcode_pos = -1; val = JS_AtomToValue(s->ctx, name); ret = emit_push_const(s, val, 1); JS_FreeValue(s->ctx, val); JS_FreeAtom(s->ctx, name); if (ret) return ret; } goto do_delete; case OP_get_array_el: fd->byte_code.size = fd->last_opcode_pos; fd->last_opcode_pos = -1; do_delete: emit_op(s, OP_delete); break; case OP_scope_get_var: /* 'delete this': this is not a reference */ name = get_u32(fd->byte_code.buf + fd->last_opcode_pos + 1); if (name == JS_ATOM_this || name == JS_ATOM_new_target) goto ret_true; if (fd->js_mode & JS_MODE_STRICT) { return js_parse_error(s, "cannot delete a direct reference in strict mode"); } else { fd->byte_code.buf[fd->last_opcode_pos] = OP_scope_delete_var; } break; case OP_scope_get_private_field: return js_parse_error(s, "cannot delete a private class field"); case OP_get_super_value: emit_op(s, OP_throw_error); emit_atom(s, JS_ATOM_NULL); emit_u8(s, JS_THROW_ERROR_DELETE_SUPER); break; default: ret_true: emit_op(s, OP_drop); emit_op(s, OP_push_true); break; } return 0; } /* allowed parse_flags: PF_ARROW_FUNC, PF_POW_ALLOWED, PF_POW_FORBIDDEN */ static __exception int js_parse_unary(JSParseState *s, int parse_flags) { int op; switch(s->token.val) { case '+': case '-': case '!': case '~': case TOK_VOID: op = s->token.val; if (next_token(s)) return -1; if (js_parse_unary(s, PF_POW_FORBIDDEN)) return -1; switch(op) { case '-': emit_op(s, OP_neg); break; case '+': emit_op(s, OP_plus); break; case '!': emit_op(s, OP_lnot); break; case '~': emit_op(s, OP_not); break; case TOK_VOID: emit_op(s, OP_drop); emit_op(s, OP_undefined); break; default: abort(); } parse_flags = 0; break; case TOK_DEC: case TOK_INC: { int opcode, op, scope, label; JSAtom name; op = s->token.val; if (next_token(s)) return -1; if (js_parse_unary(s, 0)) return -1; if (get_lvalue(s, &opcode, &scope, &name, &label, NULL, TRUE, op)) return -1; emit_op(s, OP_dec + op - TOK_DEC); put_lvalue(s, opcode, scope, name, label, PUT_LVALUE_KEEP_TOP, FALSE); } break; case TOK_TYPEOF: { JSFunctionDef *fd; if (next_token(s)) return -1; if (js_parse_unary(s, PF_POW_FORBIDDEN)) return -1; /* reference access should not return an exception, so we patch the get_var */ fd = s->cur_func; if (get_prev_opcode(fd) == OP_scope_get_var) { fd->byte_code.buf[fd->last_opcode_pos] = OP_scope_get_var_undef; } emit_op(s, OP_typeof); parse_flags = 0; } break; case TOK_DELETE: if (js_parse_delete(s)) return -1; parse_flags = 0; break; case TOK_AWAIT: if (!(s->cur_func->func_kind & JS_FUNC_ASYNC)) return js_parse_error(s, "unexpected 'await' keyword"); if (!s->cur_func->in_function_body) return js_parse_error(s, "await in default expression"); if (next_token(s)) return -1; if (js_parse_unary(s, PF_POW_FORBIDDEN)) return -1; emit_op(s, OP_await); parse_flags = 0; break; default: if (js_parse_postfix_expr(s, (parse_flags & PF_ARROW_FUNC) | PF_POSTFIX_CALL)) return -1; if (!s->got_lf && (s->token.val == TOK_DEC || s->token.val == TOK_INC)) { int opcode, op, scope, label; JSAtom name; op = s->token.val; if (get_lvalue(s, &opcode, &scope, &name, &label, NULL, TRUE, op)) return -1; emit_op(s, OP_post_dec + op - TOK_DEC); put_lvalue(s, opcode, scope, name, label, PUT_LVALUE_KEEP_SECOND, FALSE); if (next_token(s)) return -1; } break; } if (parse_flags & (PF_POW_ALLOWED | PF_POW_FORBIDDEN)) { #ifdef CONFIG_BIGNUM if (s->token.val == TOK_POW || s->token.val == TOK_MATH_POW) { /* Extended exponentiation syntax rules: we extend the ES7 grammar in order to have more intuitive semantics: -2**2 evaluates to -4. */ if (!(s->cur_func->js_mode & JS_MODE_MATH)) { if (parse_flags & PF_POW_FORBIDDEN) { JS_ThrowSyntaxError(s->ctx, "unparenthesized unary expression can't appear on the left-hand side of '**'"); return -1; } } if (next_token(s)) return -1; if (js_parse_unary(s, PF_POW_ALLOWED)) return -1; emit_op(s, OP_pow); } #else if (s->token.val == TOK_POW) { /* Strict ES7 exponentiation syntax rules: To solve conficting semantics between different implementations regarding the precedence of prefix operators and the postifx exponential, ES7 specifies that -2**2 is a syntax error. */ if (parse_flags & PF_POW_FORBIDDEN) { JS_ThrowSyntaxError(s->ctx, "unparenthesized unary expression can't appear on the left-hand side of '**'"); return -1; } if (next_token(s)) return -1; if (js_parse_unary(s, PF_POW_ALLOWED)) return -1; emit_op(s, OP_pow); } #endif } return 0; } /* allowed parse_flags: PF_ARROW_FUNC, PF_IN_ACCEPTED */ static __exception int js_parse_expr_binary(JSParseState *s, int level, int parse_flags) { int op, opcode; if (level == 0) { return js_parse_unary(s, (parse_flags & PF_ARROW_FUNC) | PF_POW_ALLOWED); } if (js_parse_expr_binary(s, level - 1, parse_flags)) return -1; for(;;) { op = s->token.val; switch(level) { case 1: switch(op) { case '*': opcode = OP_mul; break; case '/': opcode = OP_div; break; case '%': #ifdef CONFIG_BIGNUM if (s->cur_func->js_mode & JS_MODE_MATH) opcode = OP_math_mod; else #endif opcode = OP_mod; break; default: return 0; } break; case 2: switch(op) { case '+': opcode = OP_add; break; case '-': opcode = OP_sub; break; default: return 0; } break; case 3: switch(op) { case TOK_SHL: opcode = OP_shl; break; case TOK_SAR: opcode = OP_sar; break; case TOK_SHR: opcode = OP_shr; break; default: return 0; } break; case 4: switch(op) { case '<': opcode = OP_lt; break; case '>': opcode = OP_gt; break; case TOK_LTE: opcode = OP_lte; break; case TOK_GTE: opcode = OP_gte; break; case TOK_INSTANCEOF: opcode = OP_instanceof; break; case TOK_IN: if (parse_flags & PF_IN_ACCEPTED) { opcode = OP_in; } else { return 0; } break; default: return 0; } break; case 5: switch(op) { case TOK_EQ: opcode = OP_eq; break; case TOK_NEQ: opcode = OP_neq; break; case TOK_STRICT_EQ: opcode = OP_strict_eq; break; case TOK_STRICT_NEQ: opcode = OP_strict_neq; break; default: return 0; } break; case 6: switch(op) { case '&': opcode = OP_and; break; default: return 0; } break; case 7: switch(op) { case '^': opcode = OP_xor; break; default: return 0; } break; case 8: switch(op) { case '|': opcode = OP_or; break; default: return 0; } break; default: abort(); } if (next_token(s)) return -1; if (js_parse_expr_binary(s, level - 1, parse_flags & ~PF_ARROW_FUNC)) return -1; emit_op(s, opcode); } return 0; } /* allowed parse_flags: PF_ARROW_FUNC, PF_IN_ACCEPTED */ static __exception int js_parse_logical_and_or(JSParseState *s, int op, int parse_flags) { int label1; if (op == TOK_LAND) { if (js_parse_expr_binary(s, 8, parse_flags)) return -1; } else { if (js_parse_logical_and_or(s, TOK_LAND, parse_flags)) return -1; } if (s->token.val == op) { label1 = new_label(s); for(;;) { if (next_token(s)) return -1; emit_op(s, OP_dup); emit_goto(s, op == TOK_LAND ? OP_if_false : OP_if_true, label1); emit_op(s, OP_drop); if (op == TOK_LAND) { if (js_parse_expr_binary(s, 8, parse_flags & ~PF_ARROW_FUNC)) return -1; } else { if (js_parse_logical_and_or(s, TOK_LAND, parse_flags & ~PF_ARROW_FUNC)) return -1; } if (s->token.val != op) { if (s->token.val == TOK_DOUBLE_QUESTION_MARK) return js_parse_error(s, "cannot mix ?? with && or ||"); break; } } emit_label(s, label1); } return 0; } static __exception int js_parse_coalesce_expr(JSParseState *s, int parse_flags) { int label1; if (js_parse_logical_and_or(s, TOK_LOR, parse_flags)) return -1; if (s->token.val == TOK_DOUBLE_QUESTION_MARK) { label1 = new_label(s); for(;;) { if (next_token(s)) return -1; emit_op(s, OP_dup); emit_op(s, OP_is_undefined_or_null); emit_goto(s, OP_if_false, label1); emit_op(s, OP_drop); if (js_parse_expr_binary(s, 8, parse_flags & ~PF_ARROW_FUNC)) return -1; if (s->token.val != TOK_DOUBLE_QUESTION_MARK) break; } emit_label(s, label1); } return 0; } /* allowed parse_flags: PF_ARROW_FUNC, PF_IN_ACCEPTED */ static __exception int js_parse_cond_expr(JSParseState *s, int parse_flags) { int label1, label2; if (js_parse_coalesce_expr(s, parse_flags)) return -1; if (s->token.val == '?') { if (next_token(s)) return -1; label1 = emit_goto(s, OP_if_false, -1); if (js_parse_assign_expr(s)) return -1; if (js_parse_expect(s, ':')) return -1; label2 = emit_goto(s, OP_goto, -1); emit_label(s, label1); if (js_parse_assign_expr2(s, parse_flags & PF_IN_ACCEPTED)) return -1; emit_label(s, label2); } return 0; } /* allowed parse_flags: PF_IN_ACCEPTED */ static __exception int js_parse_assign_expr2(JSParseState *s, int parse_flags) { int opcode, op, scope; JSAtom name0 = JS_ATOM_NULL; JSAtom name; if (s->token.val == TOK_YIELD) { BOOL is_star = FALSE, is_async; if (!(s->cur_func->func_kind & JS_FUNC_GENERATOR)) return js_parse_error(s, "unexpected 'yield' keyword"); if (!s->cur_func->in_function_body) return js_parse_error(s, "yield in default expression"); if (next_token(s)) return -1; /* XXX: is there a better method to detect 'yield' without parameters ? */ if (s->token.val != ';' && s->token.val != ')' && s->token.val != ']' && s->token.val != '}' && s->token.val != ',' && s->token.val != ':' && !s->got_lf) { if (s->token.val == '*') { is_star = TRUE; if (next_token(s)) return -1; } if (js_parse_assign_expr2(s, parse_flags)) return -1; } else { emit_op(s, OP_undefined); } is_async = (s->cur_func->func_kind == JS_FUNC_ASYNC_GENERATOR); if (is_star) { int label_loop, label_return, label_next; int label_return1, label_yield, label_throw, label_throw1; int label_throw2; label_loop = new_label(s); label_yield = new_label(s); emit_op(s, is_async ? OP_for_await_of_start : OP_for_of_start); /* remove the catch offset (XXX: could avoid pushing back undefined) */ emit_op(s, OP_drop); emit_op(s, OP_undefined); emit_op(s, OP_undefined); /* initial value */ emit_label(s, label_loop); emit_op(s, OP_iterator_next); if (is_async) emit_op(s, OP_await); emit_op(s, OP_iterator_check_object); emit_op(s, OP_get_field2); emit_atom(s, JS_ATOM_done); label_next = emit_goto(s, OP_if_true, -1); /* end of loop */ emit_label(s, label_yield); if (is_async) { /* OP_async_yield_star takes the value as parameter */ emit_op(s, OP_get_field); emit_atom(s, JS_ATOM_value); emit_op(s, OP_await); emit_op(s, OP_async_yield_star); } else { /* OP_yield_star takes (value, done) as parameter */ emit_op(s, OP_yield_star); } emit_op(s, OP_dup); label_return = emit_goto(s, OP_if_true, -1); emit_op(s, OP_drop); emit_goto(s, OP_goto, label_loop); emit_label(s, label_return); emit_op(s, OP_push_i32); emit_u32(s, 2); emit_op(s, OP_strict_eq); label_throw = emit_goto(s, OP_if_true, -1); /* return handling */ if (is_async) emit_op(s, OP_await); emit_op(s, OP_iterator_call); emit_u8(s, 0); label_return1 = emit_goto(s, OP_if_true, -1); if (is_async) emit_op(s, OP_await); emit_op(s, OP_iterator_check_object); emit_op(s, OP_get_field2); emit_atom(s, JS_ATOM_done); emit_goto(s, OP_if_false, label_yield); emit_op(s, OP_get_field); emit_atom(s, JS_ATOM_value); emit_label(s, label_return1); emit_op(s, OP_nip); emit_op(s, OP_nip); emit_op(s, OP_nip); emit_return(s, TRUE); /* throw handling */ emit_label(s, label_throw); emit_op(s, OP_iterator_call); emit_u8(s, 1); label_throw1 = emit_goto(s, OP_if_true, -1); if (is_async) emit_op(s, OP_await); emit_op(s, OP_iterator_check_object); emit_op(s, OP_get_field2); emit_atom(s, JS_ATOM_done); emit_goto(s, OP_if_false, label_yield); emit_goto(s, OP_goto, label_next); /* close the iterator and throw a type error exception */ emit_label(s, label_throw1); emit_op(s, OP_iterator_call); emit_u8(s, 2); label_throw2 = emit_goto(s, OP_if_true, -1); if (is_async) emit_op(s, OP_await); emit_label(s, label_throw2); emit_op(s, OP_throw_error); emit_atom(s, JS_ATOM_NULL); emit_u8(s, JS_THROW_ERROR_ITERATOR_THROW); emit_label(s, label_next); emit_op(s, OP_get_field); emit_atom(s, JS_ATOM_value); emit_op(s, OP_nip); /* keep the value associated with done = true */ emit_op(s, OP_nip); emit_op(s, OP_nip); } else { int label_next; if (is_async) emit_op(s, OP_await); emit_op(s, OP_yield); label_next = emit_goto(s, OP_if_false, -1); emit_return(s, TRUE); emit_label(s, label_next); } return 0; } if (s->token.val == TOK_IDENT) { /* name0 is used to check for OP_set_name pattern, not duplicated */ name0 = s->token.u.ident.atom; } if (js_parse_cond_expr(s, parse_flags | PF_ARROW_FUNC)) return -1; op = s->token.val; if (op == '=' || (op >= TOK_MUL_ASSIGN && op <= TOK_POW_ASSIGN)) { int label; if (next_token(s)) return -1; if (get_lvalue(s, &opcode, &scope, &name, &label, NULL, (op != '='), op) < 0) return -1; if (js_parse_assign_expr2(s, parse_flags)) { JS_FreeAtom(s->ctx, name); return -1; } if (op == '=') { if (opcode == OP_get_ref_value && name == name0) { set_object_name(s, name); } } else { static const uint8_t assign_opcodes[] = { OP_mul, OP_div, OP_mod, OP_add, OP_sub, OP_shl, OP_sar, OP_shr, OP_and, OP_xor, OP_or, #ifdef CONFIG_BIGNUM OP_pow, #endif OP_pow, }; op = assign_opcodes[op - TOK_MUL_ASSIGN]; #ifdef CONFIG_BIGNUM if (s->cur_func->js_mode & JS_MODE_MATH) { if (op == OP_mod) op = OP_math_mod; } #endif emit_op(s, op); } put_lvalue(s, opcode, scope, name, label, PUT_LVALUE_KEEP_TOP, FALSE); } else if (op >= TOK_LAND_ASSIGN && op <= TOK_DOUBLE_QUESTION_MARK_ASSIGN) { int label, label1, depth_lvalue, label2; if (next_token(s)) return -1; if (get_lvalue(s, &opcode, &scope, &name, &label, &depth_lvalue, TRUE, op) < 0) return -1; emit_op(s, OP_dup); if (op == TOK_DOUBLE_QUESTION_MARK_ASSIGN) emit_op(s, OP_is_undefined_or_null); label1 = emit_goto(s, op == TOK_LOR_ASSIGN ? OP_if_true : OP_if_false, -1); emit_op(s, OP_drop); if (js_parse_assign_expr2(s, parse_flags)) { JS_FreeAtom(s->ctx, name); return -1; } if (opcode == OP_get_ref_value && name == name0) { set_object_name(s, name); } switch(depth_lvalue) { case 1: emit_op(s, OP_insert2); break; case 2: emit_op(s, OP_insert3); break; case 3: emit_op(s, OP_insert4); break; default: abort(); } /* XXX: we disable the OP_put_ref_value optimization by not using put_lvalue() otherwise depth_lvalue is not correct */ put_lvalue(s, opcode, scope, name, label, PUT_LVALUE_NOKEEP_DEPTH, FALSE); label2 = emit_goto(s, OP_goto, -1); emit_label(s, label1); /* remove the lvalue stack entries */ while (depth_lvalue != 0) { emit_op(s, OP_nip); depth_lvalue--; } emit_label(s, label2); } return 0; } static __exception int js_parse_assign_expr(JSParseState *s) { return js_parse_assign_expr2(s, PF_IN_ACCEPTED); } static __exception int js_parse_expr(JSParseState *s) { return js_parse_expr2(s, PF_IN_ACCEPTED); } static __exception JSAtom js_parse_from_clause(JSParseState *s) { JSAtom module_name; if (!token_is_pseudo_keyword(s, JS_ATOM_from)) { js_parse_error(s, "from clause expected"); return JS_ATOM_NULL; } if (next_token(s)) return JS_ATOM_NULL; if (s->token.val != TOK_STRING) { js_parse_error(s, "string expected"); return JS_ATOM_NULL; } module_name = JS_ValueToAtom(s->ctx, s->token.u.str.str); if (module_name == JS_ATOM_NULL) return JS_ATOM_NULL; if (next_token(s)) { JS_FreeAtom(s->ctx, module_name); return JS_ATOM_NULL; } return module_name; } static int add_import(JSParseState *s, JSModuleDef *m, JSAtom local_name, JSAtom import_name) { JSContext *ctx = s->ctx; int i, var_idx; JSImportEntry *mi; BOOL is_local; if (local_name == JS_ATOM_arguments || local_name == JS_ATOM_eval) return js_parse_error(s, "invalid import binding"); if (local_name != JS_ATOM_default) { for (i = 0; i < s->cur_func->closure_var_count; i++) { if (s->cur_func->closure_var[i].var_name == local_name) return js_parse_error(s, "duplicate import binding"); } } is_local = (import_name == JS_ATOM__star_); var_idx = add_closure_var(ctx, s->cur_func, is_local, FALSE, m->import_entries_count, local_name, TRUE, TRUE, FALSE); if (var_idx < 0) return -1; if (js_resize_array(ctx, (void **)&m->import_entries, sizeof(JSImportEntry), &m->import_entries_size, m->import_entries_count + 1)) return -1; mi = &m->import_entries[m->import_entries_count++]; mi->import_name = JS_DupAtom(ctx, import_name); mi->var_idx = var_idx; return 0; } static int add_req_module_entry(JSContext *ctx, JSModuleDef *m, JSAtom module_name) { JSReqModuleEntry *rme; int i; /* no need to add the module request if it is already present */ for(i = 0; i < m->req_module_entries_count; i++) { rme = &m->req_module_entries[i]; if (rme->module_name == module_name) return i; } if (js_resize_array(ctx, (void **)&m->req_module_entries, sizeof(JSReqModuleEntry), &m->req_module_entries_size, m->req_module_entries_count + 1)) return -1; rme = &m->req_module_entries[m->req_module_entries_count++]; rme->module_name = JS_DupAtom(ctx, module_name); rme->module = NULL; return i; } static __exception int js_parse_import(JSParseState *s) { JSContext *ctx = s->ctx; JSModuleDef *m = s->cur_func->module; JSAtom local_name, import_name, module_name; int first_import, i, idx; if (next_token(s)) return -1; first_import = m->import_entries_count; if (s->token.val == TOK_STRING) { module_name = JS_ValueToAtom(ctx, s->token.u.str.str); if (module_name == JS_ATOM_NULL) return -1; if (next_token(s)) { JS_FreeAtom(ctx, module_name); return -1; } } else { if (s->token.val == TOK_IDENT) { if (s->token.u.ident.is_reserved) { return js_parse_error_reserved_identifier(s); } /* "default" import */ local_name = JS_DupAtom(ctx, s->token.u.ident.atom); import_name = JS_ATOM_default; if (next_token(s)) goto fail; if (add_import(s, m, local_name, import_name)) goto fail; JS_FreeAtom(ctx, local_name); if (s->token.val != ',') goto end_import_clause; if (next_token(s)) return -1; } if (s->token.val == '*') { /* name space import */ if (next_token(s)) return -1; if (!token_is_pseudo_keyword(s, JS_ATOM_as)) return js_parse_error(s, "expecting 'as'"); if (next_token(s)) return -1; if (!token_is_ident(s->token.val)) { js_parse_error(s, "identifier expected"); return -1; } local_name = JS_DupAtom(ctx, s->token.u.ident.atom); import_name = JS_ATOM__star_; if (next_token(s)) goto fail; if (add_import(s, m, local_name, import_name)) goto fail; JS_FreeAtom(ctx, local_name); } else if (s->token.val == '{') { if (next_token(s)) return -1; while (s->token.val != '}') { if (!token_is_ident(s->token.val)) { js_parse_error(s, "identifier expected"); return -1; } import_name = JS_DupAtom(ctx, s->token.u.ident.atom); local_name = JS_ATOM_NULL; if (next_token(s)) goto fail; if (token_is_pseudo_keyword(s, JS_ATOM_as)) { if (next_token(s)) goto fail; if (!token_is_ident(s->token.val)) { js_parse_error(s, "identifier expected"); goto fail; } local_name = JS_DupAtom(ctx, s->token.u.ident.atom); if (next_token(s)) { fail: JS_FreeAtom(ctx, local_name); JS_FreeAtom(ctx, import_name); return -1; } } else { local_name = JS_DupAtom(ctx, import_name); } if (add_import(s, m, local_name, import_name)) goto fail; JS_FreeAtom(ctx, local_name); JS_FreeAtom(ctx, import_name); if (s->token.val != ',') break; if (next_token(s)) return -1; } if (js_parse_expect(s, '}')) return -1; } end_import_clause: module_name = js_parse_from_clause(s); if (module_name == JS_ATOM_NULL) return -1; } idx = add_req_module_entry(ctx, m, module_name); JS_FreeAtom(ctx, module_name); if (idx < 0) return -1; for(i = first_import; i < m->import_entries_count; i++) m->import_entries[i].req_module_idx = idx; return js_parse_expect_semi(s); } static int add_star_export_entry(JSContext *ctx, JSModuleDef *m, int req_module_idx) { JSStarExportEntry *se; if (js_resize_array(ctx, (void **)&m->star_export_entries, sizeof(JSStarExportEntry), &m->star_export_entries_size, m->star_export_entries_count + 1)) return -1; se = &m->star_export_entries[m->star_export_entries_count++]; se->req_module_idx = req_module_idx; return 0; } int js_parse_export(JSParseState *s) { JSContext *ctx = s->ctx; JSModuleDef *m = s->cur_func->module; JSAtom local_name, export_name; int first_export, idx, i, tok; JSAtom module_name; JSExportEntry *me; if (next_token(s)) return -1; tok = s->token.val; if (tok == TOK_CLASS) { return js_parse_class(s, FALSE, JS_PARSE_EXPORT_NAMED); } else if (tok == TOK_FUNCTION || (token_is_pseudo_keyword(s, JS_ATOM_async) && peek_token(s, TRUE) == TOK_FUNCTION)) { return js_parse_function_decl2(s, JS_PARSE_FUNC_STATEMENT, JS_FUNC_NORMAL, JS_ATOM_NULL, s->token.ptr, s->token.line_num, JS_PARSE_EXPORT_NAMED, NULL); } if (next_token(s)) return -1; switch(tok) { case '{': first_export = m->export_entries_count; while (s->token.val != '}') { if (!token_is_ident(s->token.val)) { js_parse_error(s, "identifier expected"); return -1; } local_name = JS_DupAtom(ctx, s->token.u.ident.atom); export_name = JS_ATOM_NULL; if (next_token(s)) goto fail; if (token_is_pseudo_keyword(s, JS_ATOM_as)) { if (next_token(s)) goto fail; if (!token_is_ident(s->token.val)) { js_parse_error(s, "identifier expected"); goto fail; } export_name = JS_DupAtom(ctx, s->token.u.ident.atom); if (next_token(s)) { fail: JS_FreeAtom(ctx, local_name); fail1: JS_FreeAtom(ctx, export_name); return -1; } } else { export_name = JS_DupAtom(ctx, local_name); } me = add_export_entry(s, m, local_name, export_name, JS_EXPORT_TYPE_LOCAL); JS_FreeAtom(ctx, local_name); JS_FreeAtom(ctx, export_name); if (!me) return -1; if (s->token.val != ',') break; if (next_token(s)) return -1; } if (js_parse_expect(s, '}')) return -1; if (token_is_pseudo_keyword(s, JS_ATOM_from)) { module_name = js_parse_from_clause(s); if (module_name == JS_ATOM_NULL) return -1; idx = add_req_module_entry(ctx, m, module_name); JS_FreeAtom(ctx, module_name); if (idx < 0) return -1; for(i = first_export; i < m->export_entries_count; i++) { me = &m->export_entries[i]; me->export_type = JS_EXPORT_TYPE_INDIRECT; me->u.req_module_idx = idx; } } break; case '*': if (token_is_pseudo_keyword(s, JS_ATOM_as)) { /* export ns from */ if (next_token(s)) return -1; if (!token_is_ident(s->token.val)) { js_parse_error(s, "identifier expected"); return -1; } export_name = JS_DupAtom(ctx, s->token.u.ident.atom); if (next_token(s)) goto fail1; module_name = js_parse_from_clause(s); if (module_name == JS_ATOM_NULL) goto fail1; idx = add_req_module_entry(ctx, m, module_name); JS_FreeAtom(ctx, module_name); if (idx < 0) goto fail1; me = add_export_entry(s, m, JS_ATOM__star_, export_name, JS_EXPORT_TYPE_INDIRECT); JS_FreeAtom(ctx, export_name); if (!me) return -1; me->u.req_module_idx = idx; } else { module_name = js_parse_from_clause(s); if (module_name == JS_ATOM_NULL) return -1; idx = add_req_module_entry(ctx, m, module_name); JS_FreeAtom(ctx, module_name); if (idx < 0) return -1; if (add_star_export_entry(ctx, m, idx) < 0) return -1; } break; case TOK_DEFAULT: if (s->token.val == TOK_CLASS) { return js_parse_class(s, FALSE, JS_PARSE_EXPORT_DEFAULT); } else if (s->token.val == TOK_FUNCTION || (token_is_pseudo_keyword(s, JS_ATOM_async) && peek_token(s, TRUE) == TOK_FUNCTION)) { return js_parse_function_decl2(s, JS_PARSE_FUNC_STATEMENT, JS_FUNC_NORMAL, JS_ATOM_NULL, s->token.ptr, s->token.line_num, JS_PARSE_EXPORT_DEFAULT, NULL); } else { if (js_parse_assign_expr(s)) return -1; } /* set the name of anonymous functions */ set_object_name(s, JS_ATOM_default); /* store the value in the _default_ global variable and export it */ local_name = JS_ATOM__default_; if (define_var(s, s->cur_func, local_name, JS_VAR_DEF_LET) < 0) return -1; emit_op(s, OP_scope_put_var_init); emit_atom(s, local_name); emit_u16(s, 0); if (!add_export_entry(s, m, local_name, JS_ATOM_default, JS_EXPORT_TYPE_LOCAL)) return -1; break; case TOK_VAR: case TOK_LET: case TOK_CONST: return js_parse_var(s, TRUE, tok, TRUE); default: return js_parse_error(s, "invalid export syntax"); } return js_parse_expect_semi(s); } static __exception int js_parse_source_element(JSParseState *s) { JSFunctionDef *fd = s->cur_func; int tok; if (s->token.val == TOK_FUNCTION || (token_is_pseudo_keyword(s, JS_ATOM_async) && peek_token(s, TRUE) == TOK_FUNCTION)) { if (js_parse_function_decl(s, JS_PARSE_FUNC_STATEMENT, JS_FUNC_NORMAL, JS_ATOM_NULL, s->token.ptr, s->token.line_num)) return -1; } else if (s->token.val == TOK_EXPORT && fd->module) { if (js_parse_export(s)) return -1; } else if (s->token.val == TOK_IMPORT && fd->module && ((tok = peek_token(s, FALSE)) != '(' && tok != '.')) { /* the peek_token is needed to avoid confusion with ImportCall (dynamic import) or import.meta */ if (js_parse_import(s)) return -1; } else { if (js_parse_statement_or_decl(s, DECL_MASK_ALL)) return -1; } return 0; } static __exception int js_parse_directives(JSParseState *s) { char str[20]; JSParsePos pos; BOOL has_semi; if (s->token.val != TOK_STRING) return 0; js_parse_get_pos(s, &pos); while(s->token.val == TOK_STRING) { /* Copy actual source string representation */ snprintf(str, sizeof str, "%.*s", (int)(s->buf_ptr - s->token.ptr - 2), s->token.ptr + 1); if (next_token(s)) return -1; has_semi = FALSE; switch (s->token.val) { case ';': if (next_token(s)) return -1; has_semi = TRUE; break; case '}': case TOK_EOF: has_semi = TRUE; break; case TOK_NUMBER: case TOK_STRING: case TOK_TEMPLATE: case TOK_IDENT: case TOK_REGEXP: case TOK_DEC: case TOK_INC: case TOK_NULL: case TOK_FALSE: case TOK_TRUE: case TOK_IF: case TOK_RETURN: case TOK_VAR: case TOK_THIS: case TOK_DELETE: case TOK_TYPEOF: case TOK_NEW: case TOK_DO: case TOK_WHILE: case TOK_FOR: case TOK_SWITCH: case TOK_THROW: case TOK_TRY: case TOK_FUNCTION: case TOK_DEBUGGER: case TOK_WITH: case TOK_CLASS: case TOK_CONST: case TOK_ENUM: case TOK_EXPORT: case TOK_IMPORT: case TOK_SUPER: case TOK_INTERFACE: case TOK_LET: case TOK_PACKAGE: case TOK_PRIVATE: case TOK_PROTECTED: case TOK_PUBLIC: case TOK_STATIC: /* automatic insertion of ';' */ if (s->got_lf) has_semi = TRUE; break; default: break; } if (!has_semi) break; if (!strcmp(str, "use strict")) { s->cur_func->has_use_strict = TRUE; s->cur_func->js_mode |= JS_MODE_STRICT; } #if !defined(DUMP_BYTECODE) || !(DUMP_BYTECODE & 8) else if (!strcmp(str, "use strip")) { s->cur_func->js_mode |= JS_MODE_STRIP; } #endif #ifdef CONFIG_BIGNUM else if (s->ctx->bignum_ext && !strcmp(str, "use math")) { s->cur_func->js_mode |= JS_MODE_MATH; } #endif } return js_parse_seek_token(s, &pos); } static int js_parse_function_check_names(JSParseState *s, JSFunctionDef *fd, JSAtom func_name) { JSAtom name; int i, idx; if (fd->js_mode & JS_MODE_STRICT) { if (!fd->has_simple_parameter_list && fd->has_use_strict) { return js_parse_error(s, "\"use strict\" not allowed in function with default or destructuring parameter"); } if (func_name == JS_ATOM_eval || func_name == JS_ATOM_arguments) { return js_parse_error(s, "invalid function name in strict code"); } for (idx = 0; idx < fd->arg_count; idx++) { name = fd->args[idx].var_name; if (name == JS_ATOM_eval || name == JS_ATOM_arguments) { return js_parse_error(s, "invalid argument name in strict code"); } } } /* check async_generator case */ if ((fd->js_mode & JS_MODE_STRICT) || !fd->has_simple_parameter_list || (fd->func_type == JS_PARSE_FUNC_METHOD && fd->func_kind == JS_FUNC_ASYNC) || fd->func_type == JS_PARSE_FUNC_ARROW || fd->func_type == JS_PARSE_FUNC_METHOD) { for (idx = 0; idx < fd->arg_count; idx++) { name = fd->args[idx].var_name; if (name != JS_ATOM_NULL) { for (i = 0; i < idx; i++) { if (fd->args[i].var_name == name) goto duplicate; } /* Check if argument name duplicates a destructuring parameter */ /* XXX: should have a flag for such variables */ for (i = 0; i < fd->var_count; i++) { if (fd->vars[i].var_name == name && fd->vars[i].scope_level == 0) goto duplicate; } } } } return 0; duplicate: return js_parse_error(s, "duplicate argument names not allowed in this context"); } static int add_arg(JSContext *ctx, JSFunctionDef *fd, JSAtom name) { JSVarDef *vd; /* the local variable indexes are currently stored on 16 bits */ if (fd->arg_count >= JS_MAX_LOCAL_VARS) { JS_ThrowInternalError(ctx, "too many arguments"); return -1; } if (js_resize_array(ctx, (void **)&fd->args, sizeof(fd->args[0]), &fd->arg_size, fd->arg_count + 1)) return -1; vd = &fd->args[fd->arg_count++]; bzero(vd, sizeof(*vd)); vd->var_name = JS_DupAtom(ctx, name); vd->func_pool_idx = -1; return fd->arg_count - 1; } /* func_name must be JS_ATOM_NULL for JS_PARSE_FUNC_STATEMENT and JS_PARSE_FUNC_EXPR, JS_PARSE_FUNC_ARROW and JS_PARSE_FUNC_VAR */ static __exception int js_parse_function_decl2(JSParseState *s, JSParseFunctionEnum func_type, JSFunctionKindEnum func_kind, JSAtom func_name, const uint8_t *ptr, int function_line_num, JSParseExportEnum export_flag, JSFunctionDef **pfd) { JSContext *ctx = s->ctx; JSFunctionDef *fd = s->cur_func; BOOL is_expr; int func_idx, lexical_func_idx = -1; BOOL has_opt_arg; BOOL create_func_var = FALSE; is_expr = (func_type != JS_PARSE_FUNC_STATEMENT && func_type != JS_PARSE_FUNC_VAR); if (func_type == JS_PARSE_FUNC_STATEMENT || func_type == JS_PARSE_FUNC_VAR || func_type == JS_PARSE_FUNC_EXPR) { if (func_kind == JS_FUNC_NORMAL && token_is_pseudo_keyword(s, JS_ATOM_async) && peek_token(s, TRUE) != '\n') { if (next_token(s)) return -1; func_kind = JS_FUNC_ASYNC; } if (next_token(s)) return -1; if (s->token.val == '*') { if (next_token(s)) return -1; func_kind |= JS_FUNC_GENERATOR; } if (s->token.val == TOK_IDENT) { if (s->token.u.ident.is_reserved || (s->token.u.ident.atom == JS_ATOM_yield && func_type == JS_PARSE_FUNC_EXPR && (func_kind & JS_FUNC_GENERATOR)) || (s->token.u.ident.atom == JS_ATOM_await && func_type == JS_PARSE_FUNC_EXPR && (func_kind & JS_FUNC_ASYNC))) { return js_parse_error_reserved_identifier(s); } } if (s->token.val == TOK_IDENT || (((s->token.val == TOK_YIELD && !(fd->js_mode & JS_MODE_STRICT)) || (s->token.val == TOK_AWAIT && !s->is_module)) && func_type == JS_PARSE_FUNC_EXPR)) { func_name = JS_DupAtom(ctx, s->token.u.ident.atom); if (next_token(s)) { JS_FreeAtom(ctx, func_name); return -1; } } else { if (func_type != JS_PARSE_FUNC_EXPR && export_flag != JS_PARSE_EXPORT_DEFAULT) { return js_parse_error(s, "function name expected"); } } } else if (func_type != JS_PARSE_FUNC_ARROW) { func_name = JS_DupAtom(ctx, func_name); } if (fd->is_eval && fd->eval_type == JS_EVAL_TYPE_MODULE && (func_type == JS_PARSE_FUNC_STATEMENT || func_type == JS_PARSE_FUNC_VAR)) { JSGlobalVar *hf; hf = find_global_var(fd, func_name); /* XXX: should check scope chain */ if (hf && hf->scope_level == fd->scope_level) { js_parse_error(s, "invalid redefinition of global identifier in module code"); JS_FreeAtom(ctx, func_name); return -1; } } if (func_type == JS_PARSE_FUNC_VAR) { if (!(fd->js_mode & JS_MODE_STRICT) && func_kind == JS_FUNC_NORMAL && find_lexical_decl(ctx, fd, func_name, fd->scope_first, FALSE) < 0 && !((func_idx = find_var(ctx, fd, func_name)) >= 0 && (func_idx & ARGUMENT_VAR_OFFSET)) && !(func_name == JS_ATOM_arguments && fd->has_arguments_binding)) { create_func_var = TRUE; } /* Create the lexical name here so that the function closure contains it */ if (fd->is_eval && (fd->eval_type == JS_EVAL_TYPE_GLOBAL || fd->eval_type == JS_EVAL_TYPE_MODULE) && fd->scope_level == fd->body_scope) { /* avoid creating a lexical variable in the global scope. XXX: check annex B */ JSGlobalVar *hf; hf = find_global_var(fd, func_name); /* XXX: should check scope chain */ if (hf && hf->scope_level == fd->scope_level) { js_parse_error(s, "invalid redefinition of global identifier"); JS_FreeAtom(ctx, func_name); return -1; } } else { /* Always create a lexical name, fail if at the same scope as existing name */ /* Lexical variable will be initialized upon entering scope */ lexical_func_idx = define_var(s, fd, func_name, func_kind != JS_FUNC_NORMAL ? JS_VAR_DEF_NEW_FUNCTION_DECL : JS_VAR_DEF_FUNCTION_DECL); if (lexical_func_idx < 0) { JS_FreeAtom(ctx, func_name); return -1; } } } fd = js_new_function_def(ctx, fd, FALSE, is_expr, s->filename, function_line_num); if (!fd) { JS_FreeAtom(ctx, func_name); return -1; } if (pfd) *pfd = fd; s->cur_func = fd; fd->func_name = func_name; /* XXX: test !fd->is_generator is always false */ fd->has_prototype = (func_type == JS_PARSE_FUNC_STATEMENT || func_type == JS_PARSE_FUNC_VAR || func_type == JS_PARSE_FUNC_EXPR) && func_kind == JS_FUNC_NORMAL; fd->has_home_object = (func_type == JS_PARSE_FUNC_METHOD || func_type == JS_PARSE_FUNC_GETTER || func_type == JS_PARSE_FUNC_SETTER || func_type == JS_PARSE_FUNC_CLASS_CONSTRUCTOR || func_type == JS_PARSE_FUNC_DERIVED_CLASS_CONSTRUCTOR); fd->has_arguments_binding = (func_type != JS_PARSE_FUNC_ARROW); fd->has_this_binding = fd->has_arguments_binding; fd->is_derived_class_constructor = (func_type == JS_PARSE_FUNC_DERIVED_CLASS_CONSTRUCTOR); if (func_type == JS_PARSE_FUNC_ARROW) { fd->new_target_allowed = fd->parent->new_target_allowed; fd->super_call_allowed = fd->parent->super_call_allowed; fd->super_allowed = fd->parent->super_allowed; fd->arguments_allowed = fd->parent->arguments_allowed; } else { fd->new_target_allowed = TRUE; fd->super_call_allowed = fd->is_derived_class_constructor; fd->super_allowed = fd->has_home_object; fd->arguments_allowed = TRUE; } /* fd->in_function_body == FALSE prevents yield/await during the parsing of the arguments in generator/async functions. They are parsed as regular identifiers for other function kinds. */ fd->func_kind = func_kind; fd->func_type = func_type; if (func_type == JS_PARSE_FUNC_CLASS_CONSTRUCTOR || func_type == JS_PARSE_FUNC_DERIVED_CLASS_CONSTRUCTOR) { /* error if not invoked as a constructor */ emit_op(s, OP_check_ctor); } if (func_type == JS_PARSE_FUNC_CLASS_CONSTRUCTOR) { emit_class_field_init(s); } /* parse arguments */ fd->has_simple_parameter_list = TRUE; fd->has_parameter_expressions = FALSE; has_opt_arg = FALSE; if (func_type == JS_PARSE_FUNC_ARROW && s->token.val == TOK_IDENT) { JSAtom name; if (s->token.u.ident.is_reserved) { js_parse_error_reserved_identifier(s); goto fail; } name = s->token.u.ident.atom; if (add_arg(ctx, fd, name) < 0) goto fail; fd->defined_arg_count = 1; } else { if (s->token.val == '(') { int skip_bits; /* if there is an '=' inside the parameter list, we consider there is a parameter expression inside */ js_parse_skip_parens_token(s, &skip_bits, FALSE); if (skip_bits & SKIP_HAS_ASSIGNMENT) fd->has_parameter_expressions = TRUE; if (next_token(s)) goto fail; } else { if (js_parse_expect(s, '(')) goto fail; } if (fd->has_parameter_expressions) { fd->scope_level = -1; /* force no parent scope */ if (push_scope(s) < 0) return -1; } while (s->token.val != ')') { JSAtom name; BOOL rest = FALSE; int idx, has_initializer; if (s->token.val == TOK_ELLIPSIS) { fd->has_simple_parameter_list = FALSE; rest = TRUE; if (next_token(s)) goto fail; } if (s->token.val == '[' || s->token.val == '{') { fd->has_simple_parameter_list = FALSE; if (rest) { emit_op(s, OP_rest); emit_u16(s, fd->arg_count); } else { /* unnamed arg for destructuring */ idx = add_arg(ctx, fd, JS_ATOM_NULL); emit_op(s, OP_get_arg); emit_u16(s, idx); } has_initializer = js_parse_destructuring_element(s, fd->has_parameter_expressions ? TOK_LET : TOK_VAR, 1, TRUE, -1, TRUE); if (has_initializer < 0) goto fail; if (has_initializer) has_opt_arg = TRUE; if (!has_opt_arg) fd->defined_arg_count++; } else if (s->token.val == TOK_IDENT) { if (s->token.u.ident.is_reserved) { js_parse_error_reserved_identifier(s); goto fail; } name = s->token.u.ident.atom; if (name == JS_ATOM_yield && fd->func_kind == JS_FUNC_GENERATOR) { js_parse_error_reserved_identifier(s); goto fail; } if (fd->has_parameter_expressions) { if (define_var(s, fd, name, JS_VAR_DEF_LET) < 0) goto fail; } /* XXX: could avoid allocating an argument if rest is true */ idx = add_arg(ctx, fd, name); if (idx < 0) goto fail; if (next_token(s)) goto fail; if (rest) { emit_op(s, OP_rest); emit_u16(s, idx); if (fd->has_parameter_expressions) { emit_op(s, OP_dup); emit_op(s, OP_scope_put_var_init); emit_atom(s, name); emit_u16(s, fd->scope_level); } emit_op(s, OP_put_arg); emit_u16(s, idx); fd->has_simple_parameter_list = FALSE; has_opt_arg = TRUE; } else if (s->token.val == '=') { int label; fd->has_simple_parameter_list = FALSE; has_opt_arg = TRUE; if (next_token(s)) goto fail; label = new_label(s); emit_op(s, OP_get_arg); emit_u16(s, idx); emit_op(s, OP_dup); emit_op(s, OP_undefined); emit_op(s, OP_strict_eq); emit_goto(s, OP_if_false, label); emit_op(s, OP_drop); if (js_parse_assign_expr(s)) goto fail; set_object_name(s, name); emit_op(s, OP_dup); emit_op(s, OP_put_arg); emit_u16(s, idx); emit_label(s, label); emit_op(s, OP_scope_put_var_init); emit_atom(s, name); emit_u16(s, fd->scope_level); } else { if (!has_opt_arg) { fd->defined_arg_count++; } if (fd->has_parameter_expressions) { /* copy the argument to the argument scope */ emit_op(s, OP_get_arg); emit_u16(s, idx); emit_op(s, OP_scope_put_var_init); emit_atom(s, name); emit_u16(s, fd->scope_level); } } } else { js_parse_error(s, "missing formal parameter"); goto fail; } if (rest && s->token.val != ')') { js_parse_expect(s, ')'); goto fail; } if (s->token.val == ')') break; if (js_parse_expect(s, ',')) goto fail; } if ((func_type == JS_PARSE_FUNC_GETTER && fd->arg_count != 0) || (func_type == JS_PARSE_FUNC_SETTER && fd->arg_count != 1)) { js_parse_error(s, "invalid number of arguments for getter or setter"); goto fail; } } if (fd->has_parameter_expressions) { int idx; /* Copy the variables in the argument scope to the variable scope (see FunctionDeclarationInstantiation() in spec). The normal arguments are already present, so no need to copy them. */ idx = fd->scopes[fd->scope_level].first; while (idx >= 0) { JSVarDef *vd = &fd->vars[idx]; if (vd->scope_level != fd->scope_level) break; if (find_var(ctx, fd, vd->var_name) < 0) { if (add_var(ctx, fd, vd->var_name) < 0) goto fail; vd = &fd->vars[idx]; /* fd->vars may have been reallocated */ emit_op(s, OP_scope_get_var); emit_atom(s, vd->var_name); emit_u16(s, fd->scope_level); emit_op(s, OP_scope_put_var); emit_atom(s, vd->var_name); emit_u16(s, 0); } idx = vd->scope_next; } /* the argument scope has no parent, hence we don't use pop_scope(s) */ emit_op(s, OP_leave_scope); emit_u16(s, fd->scope_level); /* set the variable scope as the current scope */ fd->scope_level = 0; fd->scope_first = fd->scopes[fd->scope_level].first; } if (next_token(s)) goto fail; /* generator function: yield after the parameters are evaluated */ if (func_kind == JS_FUNC_GENERATOR || func_kind == JS_FUNC_ASYNC_GENERATOR) emit_op(s, OP_initial_yield); /* in generators, yield expression is forbidden during the parsing of the arguments */ fd->in_function_body = TRUE; push_scope(s); /* enter body scope */ fd->body_scope = fd->scope_level; if (s->token.val == TOK_ARROW) { if (next_token(s)) goto fail; if (s->token.val != '{') { if (js_parse_function_check_names(s, fd, func_name)) goto fail; if (js_parse_assign_expr(s)) goto fail; if (func_kind != JS_FUNC_NORMAL) emit_op(s, OP_return_async); else emit_op(s, OP_return); if (!(fd->js_mode & JS_MODE_STRIP)) { /* save the function source code */ /* the end of the function source code is after the last token of the function source stored into s->last_ptr */ fd->source_len = s->last_ptr - ptr; fd->source = js_strndup(ctx, (const char *)ptr, fd->source_len); if (!fd->source) goto fail; } goto done; } } if (js_parse_expect(s, '{')) goto fail; if (js_parse_directives(s)) goto fail; /* in strict_mode, check function and argument names */ if (js_parse_function_check_names(s, fd, func_name)) goto fail; while (s->token.val != '}') { if (js_parse_source_element(s)) goto fail; } if (!(fd->js_mode & JS_MODE_STRIP)) { /* save the function source code */ fd->source_len = s->buf_ptr - ptr; fd->source = js_strndup(ctx, (const char *)ptr, fd->source_len); if (!fd->source) goto fail; } if (next_token(s)) { /* consume the '}' */ goto fail; } /* in case there is no return, add one */ if (js_is_live_code(s)) { emit_return(s, FALSE); } done: s->cur_func = fd->parent; /* create the function object */ { int idx; JSAtom func_name = fd->func_name; /* the real object will be set at the end of the compilation */ idx = cpool_add(s, JS_NULL); fd->parent_cpool_idx = idx; if (is_expr) { /* for constructors, no code needs to be generated here */ if (func_type != JS_PARSE_FUNC_CLASS_CONSTRUCTOR && func_type != JS_PARSE_FUNC_DERIVED_CLASS_CONSTRUCTOR) { /* OP_fclosure creates the function object from the bytecode and adds the scope information */ emit_op(s, OP_fclosure); emit_u32(s, idx); if (func_name == JS_ATOM_NULL) { emit_op(s, OP_set_name); emit_u32(s, JS_ATOM_NULL); } } } else if (func_type == JS_PARSE_FUNC_VAR) { emit_op(s, OP_fclosure); emit_u32(s, idx); if (create_func_var) { if (s->cur_func->is_global_var) { JSGlobalVar *hf; /* the global variable must be defined at the start of the function */ hf = add_global_var(ctx, s->cur_func, func_name); if (!hf) goto fail; /* it is considered as defined at the top level (needed for annex B.3.3.4 and B.3.3.5 checks) */ hf->scope_level = 0; hf->force_init = ((s->cur_func->js_mode & JS_MODE_STRICT) != 0); /* store directly into global var, bypass lexical scope */ emit_op(s, OP_dup); emit_op(s, OP_scope_put_var); emit_atom(s, func_name); emit_u16(s, 0); } else { /* do not call define_var to bypass lexical scope check */ func_idx = find_var(ctx, s->cur_func, func_name); if (func_idx < 0) { func_idx = add_var(ctx, s->cur_func, func_name); if (func_idx < 0) goto fail; } /* store directly into local var, bypass lexical catch scope */ emit_op(s, OP_dup); emit_op(s, OP_scope_put_var); emit_atom(s, func_name); emit_u16(s, 0); } } if (lexical_func_idx >= 0) { /* lexical variable will be initialized upon entering scope */ s->cur_func->vars[lexical_func_idx].func_pool_idx = idx; emit_op(s, OP_drop); } else { /* store function object into its lexical name */ /* XXX: could use OP_put_loc directly */ emit_op(s, OP_scope_put_var_init); emit_atom(s, func_name); emit_u16(s, s->cur_func->scope_level); } } else { if (!s->cur_func->is_global_var) { int var_idx = define_var(s, s->cur_func, func_name, JS_VAR_DEF_VAR); if (var_idx < 0) goto fail; /* the variable will be assigned at the top of the function */ if (var_idx & ARGUMENT_VAR_OFFSET) { s->cur_func->args[var_idx - ARGUMENT_VAR_OFFSET].func_pool_idx = idx; } else { s->cur_func->vars[var_idx].func_pool_idx = idx; } } else { JSAtom func_var_name; JSGlobalVar *hf; if (func_name == JS_ATOM_NULL) func_var_name = JS_ATOM__default_; /* export default */ else func_var_name = func_name; /* the variable will be assigned at the top of the function */ hf = add_global_var(ctx, s->cur_func, func_var_name); if (!hf) goto fail; hf->cpool_idx = idx; if (export_flag != JS_PARSE_EXPORT_NONE) { if (!add_export_entry(s, s->cur_func->module, func_var_name, export_flag == JS_PARSE_EXPORT_NAMED ? func_var_name : JS_ATOM_default, JS_EXPORT_TYPE_LOCAL)) goto fail; } } } } return 0; fail: s->cur_func = fd->parent; js_free_function_def(ctx, fd); if (pfd) *pfd = NULL; return -1; } static __exception int js_parse_function_decl(JSParseState *s, JSParseFunctionEnum func_type, JSFunctionKindEnum func_kind, JSAtom func_name, const uint8_t *ptr, int function_line_num) { return js_parse_function_decl2(s, func_type, func_kind, func_name, ptr, function_line_num, JS_PARSE_EXPORT_NONE, NULL); } int js_parse_program(JSParseState *s) { JSFunctionDef *fd = s->cur_func; int idx; if (next_token(s)) return -1; if (js_parse_directives(s)) return -1; fd->is_global_var = (fd->eval_type == JS_EVAL_TYPE_GLOBAL) || (fd->eval_type == JS_EVAL_TYPE_MODULE) || !(fd->js_mode & JS_MODE_STRICT); if (!s->is_module) { /* hidden variable for the return value */ fd->eval_ret_idx = idx = add_var(s->ctx, fd, JS_ATOM__ret_); if (idx < 0) return -1; } while (s->token.val != TOK_EOF) { if (js_parse_source_element(s)) return -1; } if (!s->is_module) { /* return the value of the hidden variable eval_ret_idx */ emit_op(s, OP_get_loc); emit_u16(s, fd->eval_ret_idx); emit_op(s, OP_return); } else { emit_op(s, OP_return_undef); } return 0; }