[PATCH v9 0/4] MR11452: jscript: Support Unicode characters in identifiers.
Fix The Heroic Legend of America: Second Revolutionary War (SteamID: 2340720) failing to start. The game has a line of `var i、unit, dataList;` in one of its .js file. The `、`(\u3001, category Po) is a punctuation mark used in CJK languages. This patch adds support for Unicode characters in identifiers according ECMA-262 3rd edition 7.6. <ZWNJ> (\u200c) and <ZWJ> (\u200d) are not allowed so it's not using ES5. However, other Unicode categories other than those specified in the spec are supported in IdentifierPart according tests so it's not fully ECMA-262 compliant. This non-compliant behavior is needed for the game, as demonstrated by the \u3001 in the Po category. Using GetStringTypeW() can cover most of the Unicode letters supported in IdentifierPart other than those in the 'Mc' and 'No' categories, which have to be included by using a generated Unicode range table. Note that make_unicode uses Unicode 17.0.0 to generate the allowed Unicode characters ranges and some characters got moved to different categories compared to Unicode 2.1 used by ECMA-262. However, I don't think it's worth adding another Unicode data file. We can adjust it when it's needed by real-world applications. Conditional compilation identifiers also support some Unicode characters. For example, the following code is valid. ```js @cc_on @set @π = 3.14; WScript.Echo(@π); ``` However, conditional compilation identifiers do not support Unicode escape sequences. So the previous is_identifier_first_char() and is_identifier_char() were wrong as well. With that said, I doubt any application would use Unicode escape sequences for conditional compilation identifiers. So let's use the same parser helpers for standard identifiers for now. -- v9: mshtml/tests: Add ES5 identifier tests. jscript/tests: Add ES3 identifier tests. jscript: Support Unicode characters in identifiers. https://gitlab.winehq.org/wine/wine/-/merge_requests/11452
From: Zhiyi Zhang <zzhang@codeweavers.com> Move finding a keyword in the keyword table from parsing keywords, e.g., advancing ctx->ptr and setting ctx->implicit_nl_semicolon. This allows the find_keyword() helper to be reused in the latter patch. --- dlls/jscript/lex.c | 58 +++++++++++++++++++++++++++++++++------------- 1 file changed, 42 insertions(+), 16 deletions(-) diff --git a/dlls/jscript/lex.c b/dlls/jscript/lex.c index ee29bff1e37..8e81bb4d5c3 100644 --- a/dlls/jscript/lex.c +++ b/dlls/jscript/lex.c @@ -32,7 +32,7 @@ WINE_DEFAULT_DEBUG_CHANNEL(jscript); -static const struct { +static const struct keyword { const WCHAR *word; int token; BOOL no_nl; @@ -90,25 +90,38 @@ 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) +static int compare_keyword(const WCHAR *ptr, const WCHAR *end, const WCHAR *word) { - const WCHAR *p1 = ctx->ptr; + const WCHAR *p1 = ptr; const WCHAR *p2 = word; - while(p1 < ctx->end && *p2) { + while(p1 < end && *p2) { if(*p1 != *p2) return *p1 - *p2; p1++; p2++; } - if(*p2 || (p1 < ctx->end && is_identifier_char(*p1))) + if(*p2) + return -1; + else if(p1 < end && is_identifier_char(*p1)) return 1; + else + return 0; +} - if(lval) - *lval = word; - ctx->ptr = p1; - return 0; +static int check_keyword(parser_ctx_t *ctx, const WCHAR *word, const WCHAR **lval) +{ + int ret; + + ret = compare_keyword(ctx->ptr, ctx->end, word); + if(!ret) { + if(lval) + *lval = word; + ctx->ptr += lstrlenW(word); + } + + return ret; } /* ECMA-262 3rd Edition 7.3 */ @@ -131,23 +144,21 @@ int hex_to_int(WCHAR c) return -1; } -static int check_keywords(parser_ctx_t *ctx, const WCHAR **lval) +static const struct keyword * find_keyword(parser_ctx_t *ctx, const WCHAR *ptr, const WCHAR *end) { 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); + r = compare_keyword(ptr, end, keywords[i].word); 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; + return NULL; } - ctx->implicit_nl_semicolon = keywords[i].no_nl; - return keywords[i].token; + return &keywords[i]; } if(r > 0) @@ -156,7 +167,22 @@ static int check_keywords(parser_ctx_t *ctx, const WCHAR **lval) max = i-1; } - return 0; + return NULL; +} + +static int check_keywords(parser_ctx_t *ctx, const WCHAR **lval) +{ + const struct keyword *keyword; + + keyword = find_keyword(ctx, ctx->ptr, ctx->end); + if(!keyword) + return 0; + + if(lval) + *lval = keyword->word; + ctx->ptr += lstrlenW(keyword->word); + ctx->implicit_nl_semicolon = keyword->no_nl; + return keyword->token; } static BOOL skip_html_comment(parser_ctx_t *ctx) -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/11452
From: Zhiyi Zhang <zzhang@codeweavers.com> Fix The Heroic Legend of America: Second Revolutionary War (SteamID: 2340720) failing to start. The game has a line of `var i、unit, dataList;` in one of its .js file. The `、`(\u3001, category 'Po') is a punctuation mark used in CJK languages. This patch adds support for Unicode characters in identifiers according ECMA-262 5th edition 7.6. Other Unicode categories other than those specified in the spec are supported in IdentifierPart according tests so it's not fully ECMA-262 compliant. This non-compliant behavior is needed for the game, as demonstrated by the \u3001 in the 'Po' category. Using GetStringTypeW() can cover most of the Unicode letters supported in IdentifierPart except those in the 'Mc' and 'No' categories, which are not handled by this patch. We can add support for 'Mc' and 'No' later by including a Unicode character table if it's needed by a real-world application. Conditional compilation identifiers also support some Unicode characters. For example, the following code is valid. ```js @cc_on @set @π = 3.14; WScript.Echo(@π); ``` However, conditional compilation identifiers do not support Unicode escape sequences. So the previous is_identifier_first_char() and is_identifier_char() were wrong as well. With that said, I doubt any application would use Unicode escape sequences for conditional compilation identifiers. So let's use the same parser helpers for standard identifiers for now. --- dlls/jscript/json.c | 4 +- dlls/jscript/lex.c | 156 +++++++++++++++++++++++++++++------------- dlls/jscript/parser.h | 4 +- 3 files changed, 113 insertions(+), 51 deletions(-) diff --git a/dlls/jscript/json.c b/dlls/jscript/json.c index 093bd5bf386..8477a3d2865 100644 --- a/dlls/jscript/json.c +++ b/dlls/jscript/json.c @@ -51,7 +51,7 @@ static BOOL is_keyword(json_parse_ctx_t *ctx, const WCHAR *keyword) if(!ctx->ptr[i] || keyword[i] != ctx->ptr[i]) return FALSE; } - if(is_identifier_char(ctx->ptr[i])) + if(is_identifier_char(ctx->ctx, ctx->ptr[i])) return FALSE; ctx->ptr += i; return TRUE; @@ -325,7 +325,7 @@ static HRESULT parse_json_value(json_parse_ctx_t *ctx, jsval_t *r) if(*ctx->ptr == '0' && ctx->ptr + 1 < ctx->end && is_digit(ctx->ptr[1])) break; - hres = parse_decimal(&ctx->ptr, ctx->end, &n); + hres = parse_decimal(ctx->ctx, &ctx->ptr, ctx->end, &n); if(FAILED(hres)) break; diff --git a/dlls/jscript/lex.c b/dlls/jscript/lex.c index 8e81bb4d5c3..ffcadb9ffce 100644 --- a/dlls/jscript/lex.c +++ b/dlls/jscript/lex.c @@ -80,17 +80,35 @@ static int lex_error(parser_ctx_t *ctx, HRESULT hres) } /* ECMA-262 3rd Edition 7.6 */ -BOOL is_identifier_char(WCHAR c) +static BOOL is_identifier_first_char(WCHAR c) { - return iswalnum(c) || c == '$' || c == '_' || c == '\\'; + return iswalpha(c) || c == '$' || c == '_' || c == '\\'; } -static BOOL is_identifier_first_char(WCHAR c) +/* More Unicode categories are allowed in IdentifierPart than those specified in ECMA-262 5th + * edition according to tests. Categories "Mc" and "No" are not supported currently. */ +BOOL is_identifier_char(const script_ctx_t *ctx, WCHAR c) { - return iswalpha(c) || c == '$' || c == '_' || c == '\\'; + WORD c1_type = 0, c3_type = 0; + + if(iswalnum(c) || c == '$' || c == '_' || c == '\\') + return TRUE; + + if(c <= 0x7f) + return FALSE; + + if(ctx->version >= SCRIPTLANGUAGEVERSION_ES5 && (c == 0x200c || c == 0x200d)) + return TRUE; + + GetStringTypeW(CT_CTYPE1, &c, 1, &c1_type); + if(c1_type & C1_PUNCT) + return TRUE; + + GetStringTypeW(CT_CTYPE3, &c, 1, &c3_type); + return c3_type & C3_NONSPACING || c3_type & C3_SYMBOL; } -static int compare_keyword(const WCHAR *ptr, const WCHAR *end, const WCHAR *word) +static int compare_keyword(const parser_ctx_t *ctx, const WCHAR *ptr, const WCHAR *end, const WCHAR *word) { const WCHAR *p1 = ptr; const WCHAR *p2 = word; @@ -104,7 +122,7 @@ static int compare_keyword(const WCHAR *ptr, const WCHAR *end, const WCHAR *word if(*p2) return -1; - else if(p1 < end && is_identifier_char(*p1)) + else if(p1 < end && is_identifier_char(ctx->script, *p1)) return 1; else return 0; @@ -114,7 +132,7 @@ static int check_keyword(parser_ctx_t *ctx, const WCHAR *word, const WCHAR **lva { int ret; - ret = compare_keyword(ctx->ptr, ctx->end, word); + ret = compare_keyword(ctx, ctx->ptr, ctx->end, word); if(!ret) { if(lval) *lval = word; @@ -151,7 +169,7 @@ static const struct keyword * find_keyword(parser_ctx_t *ctx, const WCHAR *ptr, while(min <= max) { i = (min+max)/2; - r = compare_keyword(ptr, end, keywords[i].word); + r = compare_keyword(ctx, ptr, end, keywords[i].word); if(!r) { if(ctx->script->version < keywords[i].min_version) { TRACE("ignoring keyword %s in incompatible mode\n", @@ -170,21 +188,6 @@ static const struct keyword * find_keyword(parser_ctx_t *ctx, const WCHAR *ptr, return NULL; } -static int check_keywords(parser_ctx_t *ctx, const WCHAR **lval) -{ - const struct keyword *keyword; - - keyword = find_keyword(ctx, ctx->ptr, ctx->end); - if(!keyword) - return 0; - - if(lval) - *lval = keyword->word; - ctx->ptr += lstrlenW(keyword->word); - ctx->implicit_nl_semicolon = keyword->no_nl; - return keyword->token; -} - static BOOL skip_html_comment(parser_ctx_t *ctx) { if(!ctx->is_html || ctx->ptr+3 >= ctx->end || @@ -214,7 +217,7 @@ static BOOL skip_comment(parser_ctx_t *ctx) switch(ctx->ptr[1]) { case '*': ctx->ptr += 2; - if(ctx->ptr+2 < ctx->end && *ctx->ptr == '@' && is_identifier_char(ctx->ptr[1])) + if(ctx->ptr+2 < ctx->end && *ctx->ptr == '@' && is_identifier_char(ctx->script, ctx->ptr[1])) return FALSE; while(ctx->ptr+1 < ctx->end && (ctx->ptr[0] != '*' || ctx->ptr[1] != '/')) ctx->ptr++; @@ -228,7 +231,7 @@ static BOOL skip_comment(parser_ctx_t *ctx) break; case '/': ctx->ptr += 2; - if(ctx->ptr+2 < ctx->end && *ctx->ptr == '@' && is_identifier_char(ctx->ptr[1])) + if(ctx->ptr+2 < ctx->end && *ctx->ptr == '@' && is_identifier_char(ctx->script, ctx->ptr[1])) return FALSE; while(ctx->ptr < ctx->end && !is_endline(*ctx->ptr)) ctx->ptr++; @@ -344,22 +347,90 @@ BOOL unescape(WCHAR *str, size_t *len) return TRUE; } +static BOOL unescape_identifier(const parser_ctx_t *ctx, WCHAR *dst, const WCHAR *src, int *len) +{ + const WCHAR *p, *end = src + *len; + WCHAR *pd, c; + int i; + + p = src; + pd = dst; + while(p < end) { + if(*p != '\\') { + *pd++ = *p++; + continue; + } + + if(++p == end) + return FALSE; + + if(*p != 'u' || p + 4 >= end) + return FALSE; + + i = hex_to_int(*++p); + if(i == -1) + return FALSE; + c = i << 12; + + i = hex_to_int(*++p); + if(i == -1) + return FALSE; + c += i << 8; + + i = hex_to_int(*++p); + if(i == -1) + return FALSE; + c += i << 4; + + i = hex_to_int(*++p); + if(i == -1) + return FALSE; + c += i; + + if(pd == dst && !is_identifier_first_char(c)) + return FALSE; + else if(!is_identifier_char(ctx->script, c)) + return FALSE; + + *pd++ = c; + p++; + } + + *len = pd - dst; + return TRUE; +} + static int parse_identifier(parser_ctx_t *ctx, const WCHAR **ret) { const WCHAR *ptr = ctx->ptr++; + const struct keyword *keyword; WCHAR *wstr; int len; - while(ctx->ptr < ctx->end && is_identifier_char(*ctx->ptr)) + while(ctx->ptr < ctx->end && is_identifier_char(ctx->script, *ctx->ptr)) ctx->ptr++; len = ctx->ptr-ptr; *ret = wstr = parser_alloc(ctx, (len+1)*sizeof(WCHAR)); - memcpy(wstr, ptr, len*sizeof(WCHAR)); + if(!unescape_identifier(ctx, wstr, ptr, &len)) { + WARN("unescape identifier failed\n"); + return lex_error(ctx, E_FAIL); + } wstr[len] = 0; - /* FIXME: unescape */ + keyword = find_keyword(ctx, wstr, wstr+len); + if(keyword) { + /* Escaped keywords are not allowed in < ES5 */ + if(ctx->script->version < SCRIPTLANGUAGEVERSION_ES5 && len != ctx->ptr-ptr) { + WARN("unexpected keyword %s\n", wine_dbgstr_w(wstr)); + return lex_error(ctx, E_FAIL); + } + + ctx->implicit_nl_semicolon = keyword->no_nl; + return keyword->token; + } + return tIdentifier; } @@ -420,7 +491,7 @@ literal_t *new_boolean_literal(parser_ctx_t *ctx, BOOL bval) return ret; } -HRESULT parse_decimal(const WCHAR **iter, const WCHAR *end, double *ret) +HRESULT parse_decimal(const script_ctx_t *ctx, const WCHAR **iter, const WCHAR *end, double *ret) { const WCHAR *ptr = *iter; LONGLONG d = 0, hlp; @@ -486,7 +557,7 @@ HRESULT parse_decimal(const WCHAR **iter, const WCHAR *end, double *ret) else exp += e; } - if(is_identifier_char(*ptr)) { + if(is_identifier_char(ctx, *ptr)) { WARN("wrong char after zero\n"); return JS_E_MISSING_SEMICOLON; } @@ -516,7 +587,7 @@ static BOOL parse_numeric_literal(parser_ctx_t *ctx, double *ret) ctx->ptr++; } - if(ctx->ptr < ctx->end && is_identifier_char(*ctx->ptr)) { + if(ctx->ptr < ctx->end && is_identifier_char(ctx->script, *ctx->ptr)) { WARN("unexpected identifier char\n"); lex_error(ctx, JS_E_MISSING_SEMICOLON); return FALSE; @@ -543,7 +614,7 @@ static BOOL parse_numeric_literal(parser_ctx_t *ctx, double *ret) }while(++ctx->ptr < ctx->end && is_digit(*ctx->ptr)); /* FIXME: Do we need it here? */ - if(ctx->ptr < ctx->end && (is_identifier_char(*ctx->ptr) || *ctx->ptr == '.')) { + if(ctx->ptr < ctx->end && (is_identifier_char(ctx->script, *ctx->ptr) || *ctx->ptr == '.')) { WARN("wrong char after octal literal: '%c'\n", *ctx->ptr); lex_error(ctx, JS_E_MISSING_SEMICOLON); return FALSE; @@ -553,14 +624,14 @@ static BOOL parse_numeric_literal(parser_ctx_t *ctx, double *ret) return TRUE; } - if(is_identifier_char(*ctx->ptr)) { + if(is_identifier_char(ctx->script, *ctx->ptr)) { WARN("wrong char after zero\n"); lex_error(ctx, JS_E_MISSING_SEMICOLON); return FALSE; } } - hres = parse_decimal(&ctx->ptr, ctx->end, ret); + hres = parse_decimal(ctx->script, &ctx->ptr, ctx->end, ret); if(FAILED(hres)) { lex_error(ctx, hres); return FALSE; @@ -585,13 +656,8 @@ static int next_token(parser_ctx_t *ctx, unsigned *loc, void *lval) ctx->implicit_nl_semicolon = FALSE; } - if(iswalpha(*ctx->ptr)) { - int ret = check_keywords(ctx, lval); - if(ret) - return ret; - + if(is_identifier_first_char(*ctx->ptr)) return parse_identifier(ctx, lval); - } if(is_digit(*ctx->ptr)) { double n; @@ -620,7 +686,7 @@ static int next_token(parser_ctx_t *ctx, unsigned *loc, void *lval) if(ctx->ptr+1 < ctx->end && is_digit(ctx->ptr[1])) { double n; HRESULT hres; - hres = parse_decimal(&ctx->ptr, ctx->end, &n); + hres = parse_decimal(ctx->script, &ctx->ptr, ctx->end, &n); if(FAILED(hres)) { lex_error(ctx, hres); return -1; @@ -821,10 +887,6 @@ static int next_token(parser_ctx_t *ctx, unsigned *loc, void *lval) case '\'': return parse_string_literal(ctx, lval, *ctx->ptr); - case '_': - case '$': - return parse_identifier(ctx, lval); - case '@': return '@'; } @@ -925,7 +987,7 @@ static BOOL parse_cc_identifier(parser_ctx_t *ctx, const WCHAR **ret, unsigned * } *ret = ctx->ptr; - while(++ctx->ptr < ctx->end && is_identifier_char(*ctx->ptr)); + while(++ctx->ptr < ctx->end && is_identifier_char(ctx->script, *ctx->ptr)); *ret_len = ctx->ptr - *ret; return TRUE; } @@ -1107,7 +1169,7 @@ static int cc_token(parser_ctx_t *ctx, void *lval) 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])) + while(ctx->ptr+id_len < ctx->end && is_identifier_char(ctx->script, ctx->ptr[id_len])) id_len++; if(!id_len) return '@'; diff --git a/dlls/jscript/parser.h b/dlls/jscript/parser.h index 406bedc6607..6e76df9249a 100644 --- a/dlls/jscript/parser.h +++ b/dlls/jscript/parser.h @@ -66,9 +66,9 @@ static inline void *parser_alloc_tmp(parser_ctx_t *ctx, DWORD size) } int hex_to_int(WCHAR); -BOOL is_identifier_char(WCHAR); +BOOL is_identifier_char(const script_ctx_t*,WCHAR); BOOL unescape(WCHAR*,size_t*); -HRESULT parse_decimal(const WCHAR**,const WCHAR*,double*); +HRESULT parse_decimal(const script_ctx_t*,const WCHAR**,const WCHAR*,double*); typedef enum { LT_DOUBLE, -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/11452
From: Zhiyi Zhang <zzhang@codeweavers.com> --- dlls/jscript/tests/lang.js | 122 +++++++++++++++++++++++++++++++++++++ 1 file changed, 122 insertions(+) diff --git a/dlls/jscript/tests/lang.js b/dlls/jscript/tests/lang.js index 89f52298fb3..73fe0a06d57 100644 --- a/dlls/jscript/tests/lang.js +++ b/dlls/jscript/tests/lang.js @@ -2162,3 +2162,125 @@ function test_es5_keywords() { ok(tmp === true, "Expected exception for 'const c1 = 1;'"); } test_es5_keywords(); + +function test_identifers() { + valid_tests = ['$', + '_', + 'a$', + 'a_', + // Unicode categories + 'A', // Lu: Letter, Uppercase + '_A', + 'a', // Ll: Letter, Lowercase + '_a', + '\\u01c5', // Lt: Letter, Titlecase + '_\\u01c5', + '_\\u0300', // Mn: Mark, Non-Spacing + '_\\u20dd', // Me: Mark, Enclosing + '_1', // Nd: Number, Decimal Digit + '\\u2160', // Nl: Number, Letter + '_\\u2160', + '\\u02b0', // Lm: Letter, Modifier + '_\\u02b0', + '\\u04c0', // Lo: Letter, Other + '_\\u04c0', + '_\\u203f', // Pc: Punctuation, Connector + '_\\u301c', // Pd: Punctuation, Dash + '_\\u0f3a', // Ps: Punctuation, Open + '_\\u0f3b', // Pe: Punctuation, Close + '_\\u00ab', // Pi: Punctuation, Initial quote + '_\\u2e02', + '_\\u00bb', // Pf: Punctuation, Final quote + '_\\u2e0a', + '_\\u00a1', // Po: Punctuation, Other + '_\\u3001', + '_\\u00ac', // Sm: Symbol, Math + '_\\u2044', + '_\\u00a2', // Sc: Symbol, Currency + '_\\u20a0', + '_\\u00a8', // Sk: Symbol, Modifier + '_\\u02c2', + '_\\u00a6', // So: Symbol, Other + '_\\u0482']; + invalid_tests = ['v\\u0061r', + // Unicode categories + '\\u0300', // Mn: Mark, Non-Spacing + '\\u093e', // Mc: Mark, Spacing Combining + '\\u20dd', // Me: Mark, Enclosing + '1', // Nd: Number, Decimal Digit + '\\u00b2', // No: Number, Other + '\\u2460', + '\\u009f', // Cc: Other, Control + '_\\u009f', + '\\u200c', // Cf: Other, Format + '_\\u200c', + '\\ud800', // Cs: Other, Surrogate + '_\\ud800', + '\\ue000', // Co: Other, Private Use + '_\\ue000', + '\\u203f', // Pc: Punctuation, Connector + '\\u301c', // Pd: Punctuation, Dash + '\\u0f3a', // Ps: Punctuation, Open + '\\u0f3b', // Pe: Punctuation, Close + '\\u00ab', // Pi: Punctuation, Initial quote + '\\u2e02', + '\\u00bb', // Pf: Punctuation, Final quote + '\\u2e0a', + '\\u00a1', // Po: Punctuation, Other + '\\u3001', + '\\u00ac', // Sm: Symbol, Math + '\\u2044', + '\\u00a2', // Sc: Symbol, Currency + '\\u20a0', + '\\u00a8', // Sk: Symbol, Modifier + '\\u02c2', + '\\u00a6', // So: Symbol, Other + '\\u0482']; + + var \u0061 = 1; + ok(\u0061 === 1, "\u0061 != 1"); + ok(a === 1, "a != 1"); + + var b = 1; + ok(b === 1, "b != 1"); + ok(\u0062 === 1, "\u0062 != 1"); + + var m\u0079 = 1; + ok(m\u0079 === 1, "m\u0079 != 1"); + ok(my === 1, "my != 1"); + + tmp = false + try { + eval('\\u0076ar c = 1;'); + } + catch(e) { + tmp = true + } + ok(tmp === true, 'Expected exception for \\u0076ar c = 1;'); + + tmp = false + try { + eval('v\\u0061r d = 1;'); + } + catch(e) { + tmp = true + } + ok(tmp === true, 'Expected exception for v\\u0061r d = 1;'); + + for(i=0; i<valid_tests.length; i++) { + eval('var ' + valid_tests[i] + ' = 1;'); + eval('ok(' + valid_tests[i] + ' === 1, "' + valid_tests[i] + ' != 1");'); + } + + for(i=0; i<invalid_tests.length; i++) { + tmp = false + try { + eval('var ' + invalid_tests[i] + ' = 1;'); + } + catch(e) { + tmp = true + } + ok(tmp === true, 'Expected exception for var ' + invalid_tests[i] + ' = 1;'); + } +} +test_identifers(); -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/11452
From: Zhiyi Zhang <zzhang@codeweavers.com> Compared to ES3, escaped keywords are allowed in ES5. In addition, 0x200c <ZWNJ> and 0x200d <ZWJ> are legal Unicode characters for IdentifierPart in ES5. --- dlls/mshtml/tests/es5.js | 113 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 113 insertions(+) diff --git a/dlls/mshtml/tests/es5.js b/dlls/mshtml/tests/es5.js index 969a891c08d..466bb930dd6 100644 --- a/dlls/mshtml/tests/es5.js +++ b/dlls/mshtml/tests/es5.js @@ -562,6 +562,119 @@ sync_test("member_expression_keywords", function() { ok(w.label === 4, "w.label = " + w.label); }); +sync_test("identifiers", function() { + valid_tests = ['$', + '_', + 'a$', + 'a_', + // Unicode categories + 'A', // Lu: Letter, Uppercase + '_A', + 'a', // Ll: Letter, Lowercase + '_a', + '\\u01c5', // Lt: Letter, Titlecase + '_\\u01c5', + '_\\u0300', // Mn: Mark, Non-Spacing + '_\\u20dd', // Me: Mark, Enclosing + '_1', // Nd: Number, Decimal Digit + '\\u2160', // Nl: Number, Letter + '_\\u2160', + '_\\u200c', // Cf: Other, Format + '_\\u200d', + '\\u02b0', // Lm: Letter, Modifier + '_\\u02b0', + '\\u04c0', // Lo: Letter, Other + '_\\u04c0', + '_\\u203f', // Pc: Punctuation, Connector + '_\\u301c', // Pd: Punctuation, Dash + '_\\u0f3a', // Ps: Punctuation, Open + '_\\u0f3b', // Pe: Punctuation, Close + '_\\u00ab', // Pi: Punctuation, Initial quote + '_\\u2e02', + '_\\u00bb', // Pf: Punctuation, Final quote + '_\\u2e0a', + '_\\u00a1', // Po: Punctuation, Other + '_\\u3001', + '_\\u00ac', // Sm: Symbol, Math + '_\\u2044', + '_\\u00a2', // Sc: Symbol, Currency + '_\\u20a0', + '_\\u00a8', // Sk: Symbol, Modifier + '_\\u02c2', + '_\\u00a6', // So: Symbol, Other + '_\\u0482']; + invalid_tests = ['v\\u0061r', + // Unicode categories + '\\u0300', // Mn: Mark, Non-Spacing + '\\u093e', // Mc: Mark, Spacing Combining + '\\u20dd', // Me: Mark, Enclosing + '1', // Nd: Number, Decimal Digit + '\\u00b2', // No: Number, Other + '\\u2460', + '\\u009f', // Cc: Other, Control + '_\\u009f', + '\\u200c', // Cf: Other, Format + '\\u200d', + '\\u0600', + '_\\u0600', + '\\ud800', // Cs: Other, Surrogate + '_\\ud800', + '\\ue000', // Co: Other, Private Use + '_\\ue000', + '\\u203f', // Pc: Punctuation, Connector + '\\u301c', // Pd: Punctuation, Dash + '\\u0f3a', // Ps: Punctuation, Open + '\\u0f3b', // Pe: Punctuation, Close + '\\u00ab', // Pi: Punctuation, Initial quote + '\\u2e02', + '\\u00bb', // Pf: Punctuation, Final quote + '\\u2e0a', + '\\u00a1', // Po: Punctuation, Other + '\\u3001', + '\\u00ac', // Sm: Symbol, Math + '\\u2044', + '\\u00a2', // Sc: Symbol, Currency + '\\u20a0', + '\\u00a8', // Sk: Symbol, Modifier + '\\u02c2', + '\\u00a6', // So: Symbol, Other + '\\u0482']; + + var \u0061 = 1; + ok(\u0061 === 1, "\u0061 != 1"); + ok(a === 1, "a != 1"); + + var b = 1; + ok(b === 1, "b != 1"); + ok(\u0062 === 1, "\u0062 != 1"); + + var m\u0079 = 1; + ok(m\u0079 === 1, "m\u0079 != 1"); + ok(my === 1, "my != 1"); + + \u0076ar c = 1; + ok(c === 1, "c != 1"); + + v\u0061r d = 1; + ok(d === 1, "d != 1"); + + for(i=0; i<valid_tests.length; i++) { + eval('var ' + valid_tests[i] + ' = 1;'); + eval('ok(' + valid_tests[i] + ' === 1, "' + valid_tests[i] + ' != 1");'); + } + + for(i=0; i<invalid_tests.length; i++) { + tmp = false + try { + eval('var ' + invalid_tests[i] + ' = 1;'); + } + catch(e) { + tmp = true + } + ok(tmp === true, 'Expected exception for var ' + invalid_tests[i] + ' = 1;'); + } +}); + function test_own_data_prop_desc(obj, prop, expected_writable, expected_enumerable, expected_configurable) { var desc = Object.getOwnPropertyDescriptor(obj, prop); -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/11452
v9: Remove the Unicode character table for Mc and No categories. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/11452#note_148419
participants (2)
-
Zhiyi Zhang -
Zhiyi Zhang (@zhiyi)