/* * Copyright 2008 Jacek Caban for CodeWeavers * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA */ #include #include #include "jscript.h" #include "activscp.h" #include "objsafe.h" #include "engine.h" #include "parser.h" #include "parser.tab.h" #include "wine/debug.h" WINE_DEFAULT_DEBUG_CHANNEL(jscript); static const struct { const WCHAR *word; int token; BOOL no_nl; unsigned min_version; } keywords[] = { {L"break", kBREAK, TRUE}, {L"case", kCASE}, {L"catch", kCATCH}, {L"const", kCONST}, {L"continue", kCONTINUE, TRUE}, {L"default", kDEFAULT}, {L"delete", kDELETE}, {L"do", kDO}, {L"else", kELSE}, {L"false", kFALSE}, {L"finally", kFINALLY}, {L"for", kFOR}, {L"function", kFUNCTION}, {L"get", kGET, FALSE, SCRIPTLANGUAGEVERSION_ES5}, {L"if", kIF}, {L"in", kIN}, {L"instanceof", kINSTANCEOF}, {L"let", kLET, FALSE, SCRIPTLANGUAGEVERSION_ES5}, {L"new", kNEW}, {L"null", kNULL}, {L"return", kRETURN, TRUE}, {L"set", kSET, FALSE, SCRIPTLANGUAGEVERSION_ES5}, {L"switch", kSWITCH}, {L"this", kTHIS}, {L"throw", kTHROW}, {L"true", kTRUE}, {L"try", kTRY}, {L"typeof", kTYPEOF}, {L"var", kVAR}, {L"void", kVOID}, {L"while", kWHILE}, {L"with", kWITH} }; static int lex_error(parser_ctx_t *ctx, HRESULT hres) { ctx->hres = hres; ctx->lexer_error = TRUE; return -1; } /* ECMA-262 3rd Edition 7.6 */ BOOL is_identifier_char(WCHAR c) { return iswalnum(c) || c == '$' || c == '_' || c == '\\'; } static BOOL is_identifier_first_char(WCHAR c) { return iswalpha(c) || c == '$' || c == '_' || c == '\\'; } static int check_keyword(parser_ctx_t *ctx, const WCHAR *word, const WCHAR **lval) { const WCHAR *p1 = ctx->ptr; const WCHAR *p2 = word; while(p1 < ctx->end && *p2) { if(*p1 != *p2) return *p1 - *p2; p1++; p2++; } if(*p2 || (p1 < ctx->end && is_identifier_char(*p1))) return 1; if(lval) *lval = word; ctx->ptr = p1; return 0; } /* ECMA-262 3rd Edition 7.3 */ static BOOL is_endline(WCHAR c) { return c == '\n' || c == '\r' || c == 0x2028 || c == 0x2029; } static int hex_to_int(WCHAR c) { if('0' <= c && c <= '9') return c-'0'; if('a' <= c && c <= 'f') return c-'a'+10; if('A' <= c && c <= 'F') return c-'A'+10; return -1; } static int check_keywords(parser_ctx_t *ctx, const WCHAR **lval) { int min = 0, max = ARRAY_SIZE(keywords)-1, r, i; while(min <= max) { i = (min+max)/2; r = check_keyword(ctx, keywords[i].word, lval); if(!r) { if(ctx->script->version < keywords[i].min_version) { TRACE("ignoring keyword %s in incompatible mode\n", debugstr_w(keywords[i].word)); ctx->ptr -= lstrlenW(keywords[i].word); return 0; } ctx->implicit_nl_semicolon = keywords[i].no_nl; return keywords[i].token; } if(r > 0) min = i+1; else max = i-1; } return 0; } static BOOL skip_html_comment(parser_ctx_t *ctx) { if(!ctx->is_html || ctx->ptr+3 >= ctx->end || memcmp(ctx->ptr, L" */ ctx->ptr++; if(ctx->is_html && ctx->nl && ctx->ptr < ctx->end && *ctx->ptr == '>') { ctx->ptr++; return tHTMLCOMMENT; } return tDEC; case '=': /* -= */ ctx->ptr++; *(int*)lval = EXPR_ASSIGNSUB; return tAssignOper; } } return '-'; case '*': if(++ctx->ptr < ctx->end && *ctx->ptr == '=') { /* *= */ ctx->ptr++; *(int*)lval = EXPR_ASSIGNMUL; return tAssignOper; } return '*'; case '%': if(++ctx->ptr < ctx->end && *ctx->ptr == '=') { /* %= */ ctx->ptr++; *(int*)lval = EXPR_ASSIGNMOD; return tAssignOper; } return '%'; case '&': if(++ctx->ptr < ctx->end) { switch(*ctx->ptr) { case '=': /* &= */ ctx->ptr++; *(int*)lval = EXPR_ASSIGNAND; return tAssignOper; case '&': /* && */ ctx->ptr++; return tANDAND; } } return '&'; case '|': if(++ctx->ptr < ctx->end) { switch(*ctx->ptr) { case '=': /* |= */ ctx->ptr++; *(int*)lval = EXPR_ASSIGNOR; return tAssignOper; case '|': /* || */ ctx->ptr++; return tOROR; } } return '|'; case '^': if(++ctx->ptr < ctx->end && *ctx->ptr == '=') { /* ^= */ ctx->ptr++; *(int*)lval = EXPR_ASSIGNXOR; return tAssignOper; } return '^'; case '!': if(++ctx->ptr < ctx->end && *ctx->ptr == '=') { /* != */ if(++ctx->ptr < ctx->end && *ctx->ptr == '=') { /* !== */ ctx->ptr++; *(int*)lval = EXPR_NOTEQEQ; return tEqOper; } *(int*)lval = EXPR_NOTEQ; return tEqOper; } return '!'; case '=': if(++ctx->ptr < ctx->end && *ctx->ptr == '=') { /* == */ if(++ctx->ptr < ctx->end && *ctx->ptr == '=') { /* === */ ctx->ptr++; *(int*)lval = EXPR_EQEQ; return tEqOper; } *(int*)lval = EXPR_EQ; return tEqOper; } return '='; case '/': if(++ctx->ptr < ctx->end) { if(*ctx->ptr == '=') { /* /= */ ctx->ptr++; *(int*)lval = EXPR_ASSIGNDIV; return kDIVEQ; } } return '/'; case ':': if(++ctx->ptr < ctx->end && *ctx->ptr == ':') { ctx->ptr++; return kDCOL; } return ':'; case '\"': case '\'': return parse_string_literal(ctx, lval, *ctx->ptr); case '_': case '$': return parse_identifier(ctx, lval); case '@': return '@'; } WARN("unexpected char '%c' %d\n", *ctx->ptr, *ctx->ptr); return 0; } struct _cc_var_t { ccval_t val; struct _cc_var_t *next; unsigned name_len; WCHAR name[0]; }; void release_cc(cc_ctx_t *cc) { cc_var_t *iter, *next; for(iter = cc->vars; iter; iter = next) { next = iter->next; heap_free(iter); } heap_free(cc); } static BOOL new_cc_var(cc_ctx_t *cc, const WCHAR *name, int len, ccval_t v) { cc_var_t *new_v; if(len == -1) len = lstrlenW(name); new_v = heap_alloc(sizeof(cc_var_t) + (len+1)*sizeof(WCHAR)); if(!new_v) return FALSE; new_v->val = v; memcpy(new_v->name, name, (len+1)*sizeof(WCHAR)); new_v->name_len = len; new_v->next = cc->vars; cc->vars = new_v; return TRUE; } static cc_var_t *find_cc_var(cc_ctx_t *cc, const WCHAR *name, unsigned name_len) { cc_var_t *iter; for(iter = cc->vars; iter; iter = iter->next) { if(iter->name_len == name_len && !memcmp(iter->name, name, name_len*sizeof(WCHAR))) return iter; } return NULL; } static BOOL init_cc(parser_ctx_t *ctx) { cc_ctx_t *cc; if(ctx->script->cc) return TRUE; cc = heap_alloc(sizeof(cc_ctx_t)); if(!cc) { lex_error(ctx, E_OUTOFMEMORY); return FALSE; } cc->vars = NULL; if(!new_cc_var(cc, L"_jscript", -1, ccval_bool(TRUE)) || !new_cc_var(cc, sizeof(void*) == 8 ? L"_win64" : L"_win32", -1, ccval_bool(TRUE)) || !new_cc_var(cc, sizeof(void*) == 8 ? L"_amd64" : L"_x86", -1, ccval_bool(TRUE)) || !new_cc_var(cc, L"_jscript_version", -1, ccval_num(JSCRIPT_MAJOR_VERSION + (DOUBLE)JSCRIPT_MINOR_VERSION/10.0)) || !new_cc_var(cc, L"_jscript_build", -1, ccval_num(JSCRIPT_BUILD_VERSION))) { release_cc(cc); lex_error(ctx, E_OUTOFMEMORY); return FALSE; } ctx->script->cc = cc; return TRUE; } static BOOL parse_cc_identifier(parser_ctx_t *ctx, const WCHAR **ret, unsigned *ret_len) { if(*ctx->ptr != '@') { lex_error(ctx, JS_E_EXPECTED_AT); return FALSE; } if(!is_identifier_first_char(*++ctx->ptr)) { lex_error(ctx, JS_E_EXPECTED_IDENTIFIER); return FALSE; } *ret = ctx->ptr; while(++ctx->ptr < ctx->end && is_identifier_char(*ctx->ptr)); *ret_len = ctx->ptr - *ret; return TRUE; } int try_parse_ccval(parser_ctx_t *ctx, ccval_t *r) { if(!skip_spaces(ctx)) return -1; if(is_digit(*ctx->ptr)) { double n; if(!parse_numeric_literal(ctx, &n)) return -1; *r = ccval_num(n); return 1; } if(*ctx->ptr == '@') { const WCHAR *ident; unsigned ident_len; cc_var_t *cc_var; if(!parse_cc_identifier(ctx, &ident, &ident_len)) return -1; cc_var = find_cc_var(ctx->script->cc, ident, ident_len); *r = cc_var ? cc_var->val : ccval_num(NAN); return 1; } if(!check_keyword(ctx, L"true", NULL)) { *r = ccval_bool(TRUE); return 1; } if(!check_keyword(ctx, L"false", NULL)) { *r = ccval_bool(FALSE); return 1; } return 0; } static int skip_code(parser_ctx_t *ctx, BOOL exec_else) { int if_depth = 1; const WCHAR *ptr; while(1) { ptr = wcschr(ctx->ptr, '@'); if(!ptr) { WARN("No @end\n"); return lex_error(ctx, JS_E_EXPECTED_CCEND); } ctx->ptr = ptr+1; if(!check_keyword(ctx, L"end", NULL)) { if(--if_depth) continue; return 0; } if(exec_else && !check_keyword(ctx, L"elif", NULL)) { if(if_depth > 1) continue; if(!skip_spaces(ctx) || *ctx->ptr != '(') return lex_error(ctx, JS_E_MISSING_LBRACKET); if(!parse_cc_expr(ctx)) return -1; if(!get_ccbool(ctx->ccval)) continue; /* skip block of code */ /* continue parsing */ ctx->cc_if_depth++; return 0; } if(exec_else && !check_keyword(ctx, L"else", NULL)) { if(if_depth > 1) continue; /* parse else block */ ctx->cc_if_depth++; return 0; } if(!check_keyword(ctx, L"if", NULL)) { if_depth++; continue; } ctx->ptr++; } } static int cc_token(parser_ctx_t *ctx, void *lval) { unsigned id_len = 0; cc_var_t *var; ctx->ptr++; if(!check_keyword(ctx, L"cc_on", NULL)) return init_cc(ctx) ? 0 : -1; if(!check_keyword(ctx, L"set", NULL)) { const WCHAR *ident; unsigned ident_len; cc_var_t *var; if(!init_cc(ctx)) return -1; if(!skip_spaces(ctx)) return lex_error(ctx, JS_E_EXPECTED_AT); if(!parse_cc_identifier(ctx, &ident, &ident_len)) return -1; if(!skip_spaces(ctx) || *ctx->ptr != '=') return lex_error(ctx, JS_E_EXPECTED_ASSIGN); ctx->ptr++; if(!parse_cc_expr(ctx)) { WARN("parsing CC expression failed\n"); return -1; } var = find_cc_var(ctx->script->cc, ident, ident_len); if(var) { var->val = ctx->ccval; }else { if(!new_cc_var(ctx->script->cc, ident, ident_len, ctx->ccval)) return lex_error(ctx, E_OUTOFMEMORY); } return 0; } if(!check_keyword(ctx, L"if", NULL)) { if(!init_cc(ctx)) return -1; if(!skip_spaces(ctx) || *ctx->ptr != '(') return lex_error(ctx, JS_E_MISSING_LBRACKET); if(!parse_cc_expr(ctx)) return -1; if(get_ccbool(ctx->ccval)) { /* continue parsing block inside if */ ctx->cc_if_depth++; return 0; } return skip_code(ctx, TRUE); } if(!check_keyword(ctx, L"elif", NULL) || !check_keyword(ctx, L"else", NULL)) { if(!ctx->cc_if_depth) return lex_error(ctx, JS_E_SYNTAX); return skip_code(ctx, FALSE); } if(!check_keyword(ctx, L"end", NULL)) { if(!ctx->cc_if_depth) return lex_error(ctx, JS_E_SYNTAX); ctx->cc_if_depth--; return 0; } if(!ctx->script->cc) return lex_error(ctx, JS_E_DISABLED_CC); while(ctx->ptr+id_len < ctx->end && is_identifier_char(ctx->ptr[id_len])) id_len++; if(!id_len) return '@'; TRACE("var %s\n", debugstr_wn(ctx->ptr, id_len)); var = find_cc_var(ctx->script->cc, ctx->ptr, id_len); ctx->ptr += id_len; if(!var || var->val.is_num) { *(literal_t**)lval = new_double_literal(ctx, var ? var->val.u.n : NAN); return tNumericLiteral; } *(literal_t**)lval = new_boolean_literal(ctx, var->val.u.b); return tBooleanLiteral; } int parser_lex(void *lval, unsigned *loc, parser_ctx_t *ctx) { int ret; ctx->nl = ctx->ptr == ctx->begin; do { ret = next_token(ctx, loc, lval); } while(ret == '@' && !(ret = cc_token(ctx, lval))); return ret; } literal_t *parse_regexp(parser_ctx_t *ctx) { const WCHAR *re, *flags_ptr; BOOL in_class = FALSE; DWORD re_len, flags; literal_t *ret; TRACE("\n"); while(*--ctx->ptr != '/'); /* Simple regexp pre-parser; '/' if used in char class does not terminate regexp literal */ re = ++ctx->ptr; while(ctx->ptr < ctx->end) { if(*ctx->ptr == '\\') { if(++ctx->ptr == ctx->end) break; }else if(in_class) { if(*ctx->ptr == '\n') break; if(*ctx->ptr == ']') in_class = FALSE; }else { if(*ctx->ptr == '/') break; if(*ctx->ptr == '[') in_class = TRUE; } ctx->ptr++; } if(ctx->ptr == ctx->end || *ctx->ptr != '/') { WARN("pre-parsing failed\n"); ctx->hres = JS_E_SYNTAX; return NULL; } re_len = ctx->ptr-re; flags_ptr = ++ctx->ptr; while(ctx->ptr < ctx->end && iswalnum(*ctx->ptr)) ctx->ptr++; ctx->hres = parse_regexp_flags(flags_ptr, ctx->ptr-flags_ptr, &flags); if(FAILED(ctx->hres)) return NULL; ret = parser_alloc(ctx, sizeof(literal_t)); ret->type = LT_REGEXP; ret->u.regexp.str = compiler_alloc_string_len(ctx->compiler, re, re_len); ret->u.regexp.flags = flags; return ret; }