Signed-off-by: Paul Gofman pgofman@codeweavers.com --- dlls/jscript/compile.c | 5 +++++ dlls/jscript/lex.c | 2 ++ dlls/jscript/parser.h | 1 + dlls/jscript/parser.y | 29 ++++++++++++++++++++++++----- dlls/jscript/tests/lang.js | 25 +++++++++++++++++++++++++ dlls/mshtml/tests/es5.js | 17 +++++++++++++++++ 6 files changed, 74 insertions(+), 5 deletions(-)
diff --git a/dlls/jscript/compile.c b/dlls/jscript/compile.c index 9bb9ff13d14..2a17639641f 100644 --- a/dlls/jscript/compile.c +++ b/dlls/jscript/compile.c @@ -1138,6 +1138,11 @@ static HRESULT compile_variable_list(compiler_ctx_t *ctx, variable_declaration_t if(!iter->expr) continue;
+ if (iter->block_scope) + FIXME("Block scope variables are not supported.\n"); + if (iter->constant) + FIXME("Constant variables are not supported.\n"); + hres = emit_identifier_ref(ctx, iter->identifier, 0); if(FAILED(hres)) return hres; diff --git a/dlls/jscript/lex.c b/dlls/jscript/lex.c index efed1038a95..3dd609885e6 100644 --- a/dlls/jscript/lex.c +++ b/dlls/jscript/lex.c @@ -41,6 +41,7 @@ static const struct { {L"break", kBREAK, TRUE}, {L"case", kCASE}, {L"catch", kCATCH}, + {L"const", kCONST, FALSE}, {L"continue", kCONTINUE, TRUE}, {L"default", kDEFAULT}, {L"delete", kDELETE}, @@ -54,6 +55,7 @@ static const struct { {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}, diff --git a/dlls/jscript/parser.h b/dlls/jscript/parser.h index b88a52af242..c4dd0752ee9 100644 --- a/dlls/jscript/parser.h +++ b/dlls/jscript/parser.h @@ -95,6 +95,7 @@ literal_t *new_boolean_literal(parser_ctx_t*,BOOL) DECLSPEC_HIDDEN;
typedef struct _variable_declaration_t { const WCHAR *identifier; + BOOL block_scope, constant; expression_t *expr;
struct _variable_declaration_t *next; diff --git a/dlls/jscript/parser.y b/dlls/jscript/parser.y index f11119143b8..7d0630582e0 100644 --- a/dlls/jscript/parser.y +++ b/dlls/jscript/parser.y @@ -87,7 +87,7 @@ static variable_list_t *variable_list_add(parser_ctx_t*,variable_list_t*,variabl
static void *new_statement(parser_ctx_t*,statement_type_t,size_t,unsigned); static statement_t *new_block_statement(parser_ctx_t*,unsigned,statement_list_t*); -static statement_t *new_var_statement(parser_ctx_t*,unsigned,variable_list_t*); +static statement_t *new_var_statement(parser_ctx_t*,BOOL,BOOL,unsigned,variable_list_t*); static statement_t *new_expression_statement(parser_ctx_t*,unsigned,expression_t*); static statement_t *new_if_statement(parser_ctx_t*,unsigned,expression_t*,statement_t*,statement_t*); static statement_t *new_while_statement(parser_ctx_t*,unsigned,BOOL,expression_t*,statement_t*); @@ -168,8 +168,9 @@ static source_elements_t *source_elements_add_statement(source_elements_t*,state }
/* keywords */ -%token <identifier> kBREAK kCASE kCATCH kCONTINUE kDEFAULT kDELETE kDO kELSE kFUNCTION kIF kFINALLY kFOR kGET kIN kSET -%token <identifier> kINSTANCEOF kNEW kNULL kRETURN kSWITCH kTHIS kTHROW kTRUE kFALSE kTRY kTYPEOF kVAR kVOID kWHILE kWITH +%token <identifier> kBREAK kCASE kCATCH kCONST kCONTINUE kDEFAULT kDELETE kDO kELSE kFUNCTION kIF kFINALLY kFOR +%token <identifier> kGET kIN kLET kSET kINSTANCEOF kNEW kNULL kRETURN kSWITCH kTHIS kTHROW kTRUE kFALSE +%token <identifier> kTRY kTYPEOF kVAR kVOID kWHILE kWITH %token tANDAND tOROR tINC tDEC tHTMLCOMMENT kDIVEQ kDCOL
/* tokens */ @@ -182,6 +183,7 @@ static source_elements_t *source_elements_add_statement(source_elements_t*,state %type <source_elements> FunctionBody %type <statement> Statement %type <statement> Block +%type <statement> LexicalDeclaration %type <statement> VariableStatement %type <statement> EmptyStatement %type <statement> ExpressionStatement @@ -291,6 +293,7 @@ FormalParameterList_opt /* ECMA-262 3rd Edition 12 */ Statement : Block { $$ = $1; } + | LexicalDeclaration { $$ = $1; } | VariableStatement { $$ = $1; } | EmptyStatement { $$ = $1; } | FunctionExpression { $$ = new_expression_statement(ctx, @$, $1); } @@ -322,10 +325,17 @@ Block : '{' StatementList '}' { $$ = new_block_statement(ctx, @2, $2); } | '{' '}' { $$ = new_block_statement(ctx, @$, NULL); }
+/* ECMA-262 10th Edition 13.3.1, TODO: BindingList*/ +LexicalDeclaration + : kLET VariableDeclarationList semicolon_opt + { $$ = new_var_statement(ctx, TRUE, FALSE, @$, $2); } + | kCONST VariableDeclarationList semicolon_opt + { $$ = new_var_statement(ctx, TRUE, TRUE, @$, $2); } + /* ECMA-262 3rd Edition 12.2 */ VariableStatement : kVAR VariableDeclarationList semicolon_opt - { $$ = new_var_statement(ctx, @$, $2); } + { $$ = new_var_statement(ctx, FALSE, FALSE, @$, $2); }
/* ECMA-262 3rd Edition 12.2 */ VariableDeclarationList @@ -834,6 +844,7 @@ ReservedAsIdentifier : kBREAK { $$ = $1; } | kCASE { $$ = $1; } | kCATCH { $$ = $1; } + | kCONST { $$ = $1; } | kCONTINUE { $$ = $1; } | kDEFAULT { $$ = $1; } | kDELETE { $$ = $1; } @@ -847,6 +858,7 @@ ReservedAsIdentifier | kIF { $$ = $1; } | kIN { $$ = $1; } | kINSTANCEOF { $$ = $1; } + | kLET { $$ = $1; } | kNEW { $$ = $1; } | kNULL { $$ = $1; } | kRETURN { $$ = $1; } @@ -1144,8 +1156,10 @@ static variable_list_t *variable_list_add(parser_ctx_t *ctx, variable_list_t *li return list; }
-static statement_t *new_var_statement(parser_ctx_t *ctx, unsigned loc, variable_list_t *variable_list) +static statement_t *new_var_statement(parser_ctx_t *ctx, BOOL block_scope, BOOL constant, unsigned loc, + variable_list_t *variable_list) { + variable_declaration_t *var; var_statement_t *ret;
ret = new_statement(ctx, STAT_VAR, sizeof(*ret), loc); @@ -1153,6 +1167,11 @@ static statement_t *new_var_statement(parser_ctx_t *ctx, unsigned loc, variable_ return NULL;
ret->variable_list = variable_list->head; + for (var = ret->variable_list; var; var = var->next) + { + var->block_scope = block_scope; + var->constant = constant; + }
return &ret->stat; } diff --git a/dlls/jscript/tests/lang.js b/dlls/jscript/tests/lang.js index 9e6e6ad239f..2028d233ccf 100644 --- a/dlls/jscript/tests/lang.js +++ b/dlls/jscript/tests/lang.js @@ -2030,3 +2030,28 @@ Math = 6; ok(Math === 6, "NaN !== 6");
reportSuccess(); + +function test_es5_keywords() { + var let = 1 + var tmp + ok(let == 1, "let != 1"); + + tmp = false + try { + eval('var var = 1;'); + } + catch(e) { + tmp = true + } + ok(tmp === true, "Expected exception for 'var var = 1;'"); + + tmp = false + try { + eval('var const = 1;'); + } + catch(e) { + tmp = true + } + ok(tmp === true, "Expected exception for 'var const = 1;'"); +} +test_es5_keywords(); diff --git a/dlls/mshtml/tests/es5.js b/dlls/mshtml/tests/es5.js index 7e46848d40e..24906de5231 100644 --- a/dlls/mshtml/tests/es5.js +++ b/dlls/mshtml/tests/es5.js @@ -1207,3 +1207,20 @@ sync_test("head_setter", function() { document.head = ""; ok(typeof(document.head) === "object", "typeof(document.head) = " + typeof(document.head)); }); + + +sync_test("declaration_let", function() { + ok(a === undefined, "a is not undefined"); + var a = 3; + + { + let a = 2; + + ok(a == 2, "a != 2"); + + a = 4; + ok(a == 4, "a != 4"); + } + + todo_wine.ok(a == 3, "a != 3"); +});
Signed-off-by: Paul Gofman pgofman@codeweavers.com --- dlls/jscript/compile.c | 14 +++++++++----- dlls/jscript/engine.c | 10 +++++----- dlls/jscript/engine.h | 9 +++++++-- 3 files changed, 21 insertions(+), 12 deletions(-)
diff --git a/dlls/jscript/compile.c b/dlls/jscript/compile.c index 2a17639641f..46efd53cdb5 100644 --- a/dlls/jscript/compile.c +++ b/dlls/jscript/compile.c @@ -2379,10 +2379,14 @@ static HRESULT compile_function(compiler_ctx_t *ctx, source_elements_t *source, if(FAILED(hres)) return hres;
- func->locals = compiler_alloc(ctx->code, ctx->locals_cnt * sizeof(*func->locals)); - if(!func->locals) + func->local_scope_count = 1; + func->local_scopes = compiler_alloc(ctx->code, func->local_scope_count * sizeof(*func->local_scopes)); + if(!func->local_scopes) return E_OUTOFMEMORY; - func->locals_cnt = ctx->locals_cnt; + func->local_scopes[0].locals = compiler_alloc(ctx->code, ctx->locals_cnt * sizeof(*func->local_scopes[0].locals)); + if(!func->local_scopes[0].locals) + return E_OUTOFMEMORY; + func->local_scopes[0].locals_cnt = ctx->locals_cnt;
func->variables = compiler_alloc(ctx->code, func->var_cnt * sizeof(*func->variables)); if(!func->variables) @@ -2390,8 +2394,8 @@ static HRESULT compile_function(compiler_ctx_t *ctx, source_elements_t *source,
i = 0; WINE_RB_FOR_EACH_ENTRY(local, &ctx->locals, function_local_t, entry) { - func->locals[i].name = local->name; - func->locals[i].ref = local->ref; + func->local_scopes[0].locals[i].name = local->name; + func->local_scopes[0].locals[i].ref = local->ref; if(local->ref >= 0) { func->variables[local->ref].name = local->name; func->variables[local->ref].func_id = -1; diff --git a/dlls/jscript/engine.c b/dlls/jscript/engine.c index e0a6e10e98b..c6ca3a9c10d 100644 --- a/dlls/jscript/engine.c +++ b/dlls/jscript/engine.c @@ -578,13 +578,12 @@ static HRESULT detach_variable_object(script_ctx_t *ctx, call_frame_t *frame, BO
frame->base_scope->frame = NULL;
- for(i = 0; i < frame->function->locals_cnt; i++) { - hres = jsdisp_propput_name(frame->variable_obj, frame->function->locals[i].name, - ctx->stack[local_off(frame, frame->function->locals[i].ref)]); + for(i = 0; i < frame->function->local_scopes[0].locals_cnt; i++) { + hres = jsdisp_propput_name(frame->variable_obj, frame->function->local_scopes[0].locals[i].name, + ctx->stack[local_off(frame, frame->function->local_scopes[0].locals[i].ref)]); if(FAILED(hres)) return hres; } - return S_OK; }
@@ -630,7 +629,8 @@ static int __cdecl local_ref_cmp(const void *key, const void *ref)
local_ref_t *lookup_local(const function_code_t *function, const WCHAR *identifier) { - return bsearch(identifier, function->locals, function->locals_cnt, sizeof(*function->locals), local_ref_cmp); + return bsearch(identifier, function->local_scopes[0].locals, function->local_scopes[0].locals_cnt, + sizeof(*function->local_scopes[0].locals), local_ref_cmp); }
/* ECMA-262 3rd Edition 10.1.4 */ diff --git a/dlls/jscript/engine.h b/dlls/jscript/engine.h index 442e7334c41..a94f1f211e4 100644 --- a/dlls/jscript/engine.h +++ b/dlls/jscript/engine.h @@ -147,6 +147,11 @@ typedef struct {
#define INVALID_LOCAL_REF 0x7fffffff
+typedef struct { + unsigned locals_cnt; + local_ref_t *locals; +} local_ref_scopes_t; + typedef struct _function_code_t { BSTR name; int local_ref; @@ -168,8 +173,8 @@ typedef struct _function_code_t { unsigned param_cnt; BSTR *params;
- unsigned locals_cnt; - local_ref_t *locals; + local_ref_scopes_t *local_scopes; + unsigned local_scope_count;
bytecode_t *bytecode; } function_code_t;
Signed-off-by: Paul Gofman pgofman@codeweavers.com --- dlls/jscript/compile.c | 234 +++++++++++++++++++++++++++---------- dlls/jscript/engine.c | 105 ++++++++++++++--- dlls/jscript/engine.h | 5 +- dlls/jscript/parser.h | 2 + dlls/jscript/parser.y | 2 + dlls/jscript/tests/lang.js | 16 +++ dlls/mshtml/tests/es5.js | 56 ++++++++- 7 files changed, 341 insertions(+), 79 deletions(-)
diff --git a/dlls/jscript/compile.c b/dlls/jscript/compile.c index 46efd53cdb5..1227a27ddcc 100644 --- a/dlls/jscript/compile.c +++ b/dlls/jscript/compile.c @@ -39,6 +39,8 @@ typedef struct _statement_ctx_t {
const labelled_statement_t *labelled_stat;
+ unsigned int scope_index; + BOOL block_scope; struct _statement_ctx_t *next; } statement_ctx_t;
@@ -48,6 +50,8 @@ typedef struct { int ref; } function_local_t;
+#define MAX_SCOPE_COUNT 1024 + typedef struct _compiler_ctx_t { parser_ctx_t *parser; bytecode_t *code; @@ -61,8 +65,14 @@ typedef struct _compiler_ctx_t { unsigned labels_size; unsigned labels_cnt;
- struct wine_rb_tree locals; - unsigned locals_cnt; + struct + { + struct wine_rb_tree locals; + unsigned int locals_cnt; + unsigned int *ref_index; + } + *local_scopes; + unsigned local_scope_count;
statement_ctx_t *stat_ctx; function_code_t *func; @@ -253,6 +263,19 @@ static HRESULT push_instr_int(compiler_ctx_t *ctx, jsop_t op, LONG arg) return S_OK; }
+static HRESULT push_instr_uint_uint(compiler_ctx_t *ctx, jsop_t op, unsigned arg1, unsigned arg2) +{ + unsigned instr; + + instr = push_instr(ctx, op); + if(!instr) + return E_OUTOFMEMORY; + + instr_ptr(ctx, instr)->u.arg[0].uint = arg1; + instr_ptr(ctx, instr)->u.arg[1].uint = arg2; + return S_OK; +} + static HRESULT push_instr_str(compiler_ctx_t *ctx, jsop_t op, jsstr_t *str) { unsigned instr; @@ -439,10 +462,19 @@ static BOOL bind_local(compiler_ctx_t *ctx, const WCHAR *identifier, int *ret_re
for(iter = ctx->stat_ctx; iter; iter = iter->next) { if(iter->using_scope) - return FALSE; + { + if (!iter->block_scope) + return FALSE; + TRACE("iter->scope_index %d, ctx->func->local_scope_count %d.\n", iter->scope_index, ctx->func->local_scope_count); + if ((ref = lookup_local(ctx->func, identifier, iter->scope_index))) + { + *ret_ref = ref->ref; + return TRUE; + } + } }
- ref = lookup_local(ctx->func, identifier); + ref = lookup_local(ctx->func, identifier, 0); if(!ref) return FALSE;
@@ -1111,18 +1143,35 @@ static inline BOOL is_loop_statement(statement_type_t type) }
/* ECMA-262 3rd Edition 12.1 */ -static HRESULT compile_block_statement(compiler_ctx_t *ctx, statement_t *iter) +static HRESULT compile_block_statement(compiler_ctx_t *ctx, block_statement_t *block, statement_t *iter) { + statement_ctx_t stat_ctx = {0, TRUE}; + BOOL needs_scope; HRESULT hres;
+ needs_scope = block && block->scope_index; + if (needs_scope) + { + if(!push_instr(ctx, OP_new_obj)) + return E_OUTOFMEMORY; + if(FAILED(hres = push_instr_uint_uint(ctx, OP_push_scope, block->scope_index, TRUE))) + return hres; + + stat_ctx.scope_index = block->scope_index; + stat_ctx.block_scope = TRUE; + } + while(iter) { - hres = compile_statement(ctx, NULL, iter); + hres = compile_statement(ctx, needs_scope ? &stat_ctx : NULL, iter); if(FAILED(hres)) return hres;
iter = iter->next; }
+ if(needs_scope && !push_instr(ctx, OP_pop_scope)) + return E_OUTOFMEMORY; + return S_OK; }
@@ -1138,8 +1187,6 @@ static HRESULT compile_variable_list(compiler_ctx_t *ctx, variable_declaration_t if(!iter->expr) continue;
- if (iter->block_scope) - FIXME("Block scope variables are not supported.\n"); if (iter->constant) FIXME("Constant variables are not supported.\n");
@@ -1549,8 +1596,8 @@ static HRESULT compile_with_statement(compiler_ctx_t *ctx, with_statement_t *sta if(FAILED(hres)) return hres;
- if(!push_instr(ctx, OP_push_scope)) - return E_OUTOFMEMORY; + if(FAILED(hres = push_instr_uint_uint(ctx, OP_push_scope, stat->scope_index, FALSE))) + return hres;
hres = compile_statement(ctx, &stat_ctx, stat->statement); if(FAILED(hres)) @@ -1787,7 +1834,7 @@ static HRESULT compile_statement(compiler_ctx_t *ctx, statement_ctx_t *stat_ctx,
switch(stat->type) { case STAT_BLOCK: - hres = compile_block_statement(ctx, ((block_statement_t*)stat)->stat_list); + hres = compile_block_statement(ctx, (block_statement_t*)stat, ((block_statement_t*)stat)->stat_list); break; case STAT_BREAK: hres = compile_break_statement(ctx, (branch_statement_t*)stat); @@ -1852,13 +1899,13 @@ static int function_local_cmp(const void *key, const struct wine_rb_entry *entry return wcscmp(key, local->name); }
-static inline function_local_t *find_local(compiler_ctx_t *ctx, const WCHAR *name) +static inline function_local_t *find_local(compiler_ctx_t *ctx, const WCHAR *name, unsigned int scope) { - struct wine_rb_entry *entry = wine_rb_get(&ctx->locals, name); + struct wine_rb_entry *entry = wine_rb_get(&ctx->local_scopes[scope].locals, name); return entry ? WINE_RB_ENTRY_VALUE(entry, function_local_t, entry) : NULL; }
-static BOOL alloc_local(compiler_ctx_t *ctx, BSTR name, int ref) +static BOOL alloc_local(compiler_ctx_t *ctx, BSTR name, int ref, unsigned int scope) { function_local_t *local;
@@ -1868,23 +1915,23 @@ static BOOL alloc_local(compiler_ctx_t *ctx, BSTR name, int ref)
local->name = name; local->ref = ref; - wine_rb_put(&ctx->locals, name, &local->entry); - ctx->locals_cnt++; + wine_rb_put(&ctx->local_scopes[scope].locals, name, &local->entry); + ctx->local_scopes[scope].locals_cnt++; return TRUE; }
-static BOOL alloc_variable(compiler_ctx_t *ctx, const WCHAR *name) +static BOOL alloc_variable(compiler_ctx_t *ctx, const WCHAR *name, unsigned int scope) { BSTR ident;
- if(find_local(ctx, name)) + if(find_local(ctx, name, scope)) return TRUE;
ident = compiler_alloc_bstr(ctx, name); if(!ident) return FALSE;
- return alloc_local(ctx, ident, ctx->func->var_cnt++); + return alloc_local(ctx, ident, ctx->func->var_cnt++, scope); }
static HRESULT visit_function_expression(compiler_ctx_t *ctx, function_expression_t *expr) @@ -1897,7 +1944,7 @@ static HRESULT visit_function_expression(compiler_ctx_t *ctx, function_expressio if(!expr->is_statement && ctx->parser->script->version >= SCRIPTLANGUAGEVERSION_ES5) return S_OK;
- return alloc_variable(ctx, expr->identifier) ? S_OK : E_OUTOFMEMORY; + return alloc_variable(ctx, expr->identifier, 0) ? S_OK : E_OUTOFMEMORY; }
static HRESULT visit_expression(compiler_ctx_t *ctx, expression_t *expr) @@ -2033,11 +2080,18 @@ static HRESULT visit_expression(compiler_ctx_t *ctx, expression_t *expr) static HRESULT visit_variable_list(compiler_ctx_t *ctx, variable_declaration_t *list) { variable_declaration_t *iter; + statement_ctx_t *stat_ctx; HRESULT hres;
for(iter = list; iter; iter = iter->next) { - if(!alloc_variable(ctx, iter->identifier)) - return E_OUTOFMEMORY; + for (stat_ctx = ctx->stat_ctx; stat_ctx; stat_ctx = stat_ctx->next) + { + if (stat_ctx->block_scope) + break; + } + + if(!alloc_variable(ctx, iter->identifier, iter->block_scope && stat_ctx ? stat_ctx->scope_index : 0)) + return E_OUTOFMEMORY;
if(iter->expr) { hres = visit_expression(ctx, iter->expr); @@ -2049,30 +2103,67 @@ static HRESULT visit_variable_list(compiler_ctx_t *ctx, variable_declaration_t * return S_OK; }
-static HRESULT visit_statement(compiler_ctx_t*,statement_t*); +static HRESULT visit_statement(compiler_ctx_t*,statement_ctx_t *,statement_t*);
-static HRESULT visit_block_statement(compiler_ctx_t *ctx, statement_t *iter) +static HRESULT visit_block_statement(compiler_ctx_t *ctx, block_statement_t *block, statement_t *iter) { + statement_ctx_t stat_ctx = {0, TRUE}; + BOOL needs_scope; + unsigned int i; HRESULT hres;
+ needs_scope = block && ctx->parser->script->version >= SCRIPTLANGUAGEVERSION_ES5; + if (needs_scope) + { + if (ctx->local_scope_count > MAX_SCOPE_COUNT) + return E_OUTOFMEMORY; + + stat_ctx.scope_index = ctx->local_scope_count++; + stat_ctx.block_scope = TRUE; + ctx->local_scopes[stat_ctx.scope_index].locals_cnt = 0; + ctx->local_scopes[stat_ctx.scope_index].ref_index = &block->scope_index; + + wine_rb_init(&ctx->local_scopes[stat_ctx.scope_index].locals, function_local_cmp); + } + while(iter) { - hres = visit_statement(ctx, iter); + hres = visit_statement(ctx, needs_scope ? &stat_ctx : NULL, iter); if(FAILED(hres)) return hres;
iter = iter->next; }
+ if (!needs_scope) + return S_OK; + + if (ctx->local_scopes[stat_ctx.scope_index].locals_cnt) + { + block->scope_index = stat_ctx.scope_index; + } + else + { + --ctx->local_scope_count; + memmove(&ctx->local_scopes[stat_ctx.scope_index], &ctx->local_scopes[stat_ctx.scope_index + 1], + sizeof(*ctx->local_scopes) * (ctx->local_scope_count - stat_ctx.scope_index)); + for (i = stat_ctx.scope_index; i < ctx->local_scope_count; ++i) + --*ctx->local_scopes[i].ref_index; + } return S_OK; }
-static HRESULT visit_statement(compiler_ctx_t *ctx, statement_t *stat) +static HRESULT visit_statement(compiler_ctx_t *ctx, statement_ctx_t *stat_ctx, statement_t *stat) { HRESULT hres = S_OK;
+ if(stat_ctx) { + stat_ctx->next = ctx->stat_ctx; + ctx->stat_ctx = stat_ctx; + } + switch(stat->type) { case STAT_BLOCK: - hres = visit_block_statement(ctx, ((block_statement_t*)stat)->stat_list); + hres = visit_block_statement(ctx, (block_statement_t*)stat, ((block_statement_t*)stat)->stat_list); break; case STAT_BREAK: case STAT_CONTINUE: @@ -2110,7 +2201,7 @@ static HRESULT visit_statement(compiler_ctx_t *ctx, statement_t *stat) break; }
- hres = visit_statement(ctx, for_stat->statement); + hres = visit_statement(ctx, NULL, for_stat->statement); if(FAILED(hres)) break;
@@ -2137,7 +2228,7 @@ static HRESULT visit_statement(compiler_ctx_t *ctx, statement_t *stat) return hres; }
- hres = visit_statement(ctx, forin_stat->statement); + hres = visit_statement(ctx, NULL, forin_stat->statement); break; } case STAT_IF: { @@ -2147,16 +2238,16 @@ static HRESULT visit_statement(compiler_ctx_t *ctx, statement_t *stat) if(FAILED(hres)) return hres;
- hres = visit_statement(ctx, if_stat->if_stat); + hres = visit_statement(ctx, NULL, if_stat->if_stat); if(FAILED(hres)) return hres;
if(if_stat->else_stat) - hres = visit_statement(ctx, if_stat->else_stat); + hres = visit_statement(ctx, NULL, if_stat->else_stat); break; } case STAT_LABEL: - hres = visit_statement(ctx, ((labelled_statement_t*)stat)->statement); + hres = visit_statement(ctx, NULL, ((labelled_statement_t*)stat)->statement); break; case STAT_SWITCH: { switch_statement_t *switch_stat = (switch_statement_t*)stat; @@ -2180,7 +2271,7 @@ static HRESULT visit_statement(compiler_ctx_t *ctx, statement_t *stat) iter = iter->next; for(stat_iter = iter->stat; stat_iter && (!iter->next || iter->next->stat != stat_iter); stat_iter = stat_iter->next) { - hres = visit_statement(ctx, stat_iter); + hres = visit_statement(ctx, NULL, stat_iter); if(FAILED(hres)) return hres; } @@ -2190,18 +2281,18 @@ static HRESULT visit_statement(compiler_ctx_t *ctx, statement_t *stat) case STAT_TRY: { try_statement_t *try_stat = (try_statement_t*)stat;
- hres = visit_statement(ctx, try_stat->try_statement); + hres = visit_statement(ctx, NULL, try_stat->try_statement); if(FAILED(hres)) return hres;
if(try_stat->catch_block) { - hres = visit_statement(ctx, try_stat->catch_block->statement); + hres = visit_statement(ctx, NULL, try_stat->catch_block->statement); if(FAILED(hres)) return hres; }
if(try_stat->finally_statement) - hres = visit_statement(ctx, try_stat->finally_statement); + hres = visit_statement(ctx, NULL, try_stat->finally_statement); break; } case STAT_VAR: @@ -2214,22 +2305,35 @@ static HRESULT visit_statement(compiler_ctx_t *ctx, statement_t *stat) if(FAILED(hres)) return hres;
- hres = visit_statement(ctx, while_stat->statement); + hres = visit_statement(ctx, NULL, while_stat->statement); break; } case STAT_WITH: { with_statement_t *with_stat = (with_statement_t*)stat; + statement_ctx_t stat_ctx = {0, TRUE};
hres = visit_expression(ctx, with_stat->expr); if(FAILED(hres)) return hres;
- hres = visit_statement(ctx, with_stat->statement); + if (ctx->parser->script->version >= SCRIPTLANGUAGEVERSION_ES5) + { + stat_ctx.scope_index = with_stat->scope_index = ctx->local_scope_count++; + ctx->local_scopes[stat_ctx.scope_index].locals_cnt = 0; + ctx->local_scopes[stat_ctx.scope_index].ref_index = &with_stat->scope_index; + } + + hres = visit_statement(ctx, &stat_ctx, with_stat->statement); break; } DEFAULT_UNREACHABLE; }
+ if(stat_ctx) { + assert(ctx->stat_ctx == stat_ctx); + ctx->stat_ctx = stat_ctx->next; + } + return hres; }
@@ -2325,7 +2429,7 @@ static HRESULT compile_function(compiler_ctx_t *ctx, source_elements_t *source, { function_expression_t *iter; function_local_t *local; - unsigned off, i; + unsigned off, i, scope; HRESULT hres;
TRACE("\n"); @@ -2335,8 +2439,9 @@ static HRESULT compile_function(compiler_ctx_t *ctx, source_elements_t *source, ctx->func_head = ctx->func_tail = NULL; ctx->from_eval = from_eval; ctx->func = func; - ctx->locals_cnt = 0; - wine_rb_init(&ctx->locals, function_local_cmp); + ctx->local_scope_count = 1; + ctx->local_scopes[0].locals_cnt = 0; + wine_rb_init(&ctx->local_scopes[0].locals, function_local_cmp);
if(func_expr) { parameter_t *param_iter; @@ -2371,38 +2476,43 @@ static HRESULT compile_function(compiler_ctx_t *ctx, source_elements_t *source, }
for(i = 0; i < func->param_cnt; i++) { - if(!find_local(ctx, func->params[i]) && !alloc_local(ctx, func->params[i], -i-1)) + if(!find_local(ctx, func->params[i], 0) && !alloc_local(ctx, func->params[i], -i-1, 0)) return E_OUTOFMEMORY; }
- hres = visit_block_statement(ctx, source->statement); + hres = visit_block_statement(ctx, NULL, source->statement); if(FAILED(hres)) return hres;
- func->local_scope_count = 1; + func->local_scope_count = ctx->local_scope_count; func->local_scopes = compiler_alloc(ctx->code, func->local_scope_count * sizeof(*func->local_scopes)); if(!func->local_scopes) return E_OUTOFMEMORY; - func->local_scopes[0].locals = compiler_alloc(ctx->code, ctx->locals_cnt * sizeof(*func->local_scopes[0].locals)); - if(!func->local_scopes[0].locals) - return E_OUTOFMEMORY; - func->local_scopes[0].locals_cnt = ctx->locals_cnt;
func->variables = compiler_alloc(ctx->code, func->var_cnt * sizeof(*func->variables)); if(!func->variables) return E_OUTOFMEMORY;
- i = 0; - WINE_RB_FOR_EACH_ENTRY(local, &ctx->locals, function_local_t, entry) { - func->local_scopes[0].locals[i].name = local->name; - func->local_scopes[0].locals[i].ref = local->ref; - if(local->ref >= 0) { - func->variables[local->ref].name = local->name; - func->variables[local->ref].func_id = -1; + for (scope = 0; scope < func->local_scope_count; ++scope) + { + func->local_scopes[scope].locals = compiler_alloc(ctx->code, + ctx->local_scopes[scope].locals_cnt * sizeof(*func->local_scopes[scope].locals)); + if(!func->local_scopes[scope].locals) + return E_OUTOFMEMORY; + func->local_scopes[scope].locals_cnt = ctx->local_scopes[scope].locals_cnt; + + i = 0; + WINE_RB_FOR_EACH_ENTRY(local, &ctx->local_scopes[scope].locals, function_local_t, entry) { + func->local_scopes[scope].locals[i].name = local->name; + func->local_scopes[scope].locals[i].ref = local->ref; + if(local->ref >= 0) { + func->variables[local->ref].name = local->name; + func->variables[local->ref].func_id = -1; + } + i++; } - i++; + assert(i == ctx->local_scopes[scope].locals_cnt); } - assert(i == ctx->locals_cnt);
func->funcs = compiler_alloc(ctx->code, func->func_cnt * sizeof(*func->funcs)); if(!func->funcs) @@ -2410,7 +2520,7 @@ static HRESULT compile_function(compiler_ctx_t *ctx, source_elements_t *source, memset(func->funcs, 0, func->func_cnt * sizeof(*func->funcs));
off = ctx->code_off; - hres = compile_block_statement(ctx, source->statement); + hres = compile_block_statement(ctx, NULL, source->statement); if(FAILED(hres)) return hres;
@@ -2433,7 +2543,7 @@ static HRESULT compile_function(compiler_ctx_t *ctx, source_elements_t *source, TRACE("[%d] func %s\n", i, debugstr_w(func->funcs[i].name)); if((ctx->parser->script->version < SCRIPTLANGUAGEVERSION_ES5 || iter->is_statement) && func->funcs[i].name && !func->funcs[i].event_target) { - local_ref_t *local_ref = lookup_local(func, func->funcs[i].name); + local_ref_t *local_ref = lookup_local(func, func->funcs[i].name, 0); func->funcs[i].local_ref = local_ref->ref; TRACE("found ref %s %d for %s\n", debugstr_w(local_ref->name), local_ref->ref, debugstr_w(func->funcs[i].name)); if(local_ref->ref >= 0) @@ -2548,7 +2658,11 @@ HRESULT compile_script(script_ctx_t *ctx, const WCHAR *code, UINT64 source_conte }
heap_pool_init(&compiler.heap); - hres = compile_function(&compiler, compiler.parser->source, NULL, from_eval, &compiler.code->global_code); + if (!(compiler.local_scopes = heap_alloc(MAX_SCOPE_COUNT * sizeof(*compiler.local_scopes)))) + hres = E_OUTOFMEMORY; + else + hres = compile_function(&compiler, compiler.parser->source, NULL, from_eval, &compiler.code->global_code); + heap_free(compiler.local_scopes); heap_pool_free(&compiler.heap); parser_release(compiler.parser); if(FAILED(hres)) { diff --git a/dlls/jscript/engine.c b/dlls/jscript/engine.c index c6ca3a9c10d..877d67afb31 100644 --- a/dlls/jscript/engine.c +++ b/dlls/jscript/engine.c @@ -205,6 +205,7 @@ static BOOL stack_topn_exprval(script_ctx_t *ctx, unsigned n, exprval_t *r) switch(jsval_type(v)) { case JSV_NUMBER: { call_frame_t *frame = ctx->call_ctx; + scope_chain_t *scope; unsigned off = get_number(v);
if(!frame->base_scope->frame && off >= frame->arguments_off) { @@ -218,17 +219,26 @@ static BOOL stack_topn_exprval(script_ctx_t *ctx, unsigned n, exprval_t *r) name = off >= frame->variables_off ? frame->function->variables[off - frame->variables_off].name : frame->function->params[off - frame->arguments_off]; - hres = jsdisp_get_id(ctx->call_ctx->base_scope->jsobj, name, 0, &id); + scope = ctx->call_ctx->scope; + hres = E_FAIL; + do + { + if (scope->jsobj && SUCCEEDED(hres = jsdisp_get_id(scope->jsobj, name, 0, &id))) + break; + scope = scope->next; + } + while (scope != ctx->call_ctx->base_scope); + if(FAILED(hres)) { r->type = EXPRVAL_INVALID; r->u.hres = hres; return FALSE; }
- *stack_top_ref(ctx, n+1) = jsval_obj(jsdisp_addref(frame->base_scope->jsobj)); + *stack_top_ref(ctx, n+1) = jsval_obj(jsdisp_addref(scope->jsobj)); *stack_top_ref(ctx, n) = jsval_number(id); r->type = EXPRVAL_IDREF; - r->u.idref.disp = frame->base_scope->obj; + r->u.idref.disp = scope->obj; r->u.idref.id = id; return TRUE; } @@ -391,7 +401,8 @@ static inline void clear_acc(script_ctx_t *ctx) ctx->acc = jsval_undefined(); }
-static HRESULT scope_push(scope_chain_t *scope, jsdisp_t *jsobj, IDispatch *obj, scope_chain_t **ret) +static HRESULT scope_push(scope_chain_t *scope, jsdisp_t *jsobj, IDispatch *obj, + unsigned int scope_index, scope_chain_t **ret) { scope_chain_t *new_scope;
@@ -406,6 +417,7 @@ static HRESULT scope_push(scope_chain_t *scope, jsdisp_t *jsobj, IDispatch *obj, new_scope->obj = obj; new_scope->frame = NULL; new_scope->next = scope ? scope_addref(scope) : NULL; + new_scope->scope_index = scope_index;
*ret = new_scope; return S_OK; @@ -559,10 +571,35 @@ HRESULT jsval_strict_equal(jsval_t lval, jsval_t rval, BOOL *ret) */ static HRESULT detach_variable_object(script_ctx_t *ctx, call_frame_t *frame, BOOL from_release) { - unsigned i; + unsigned int i, index; + scope_chain_t *scope; HRESULT hres;
- if(!frame->base_scope || !frame->base_scope->frame) + if(!frame->base_scope) + return S_OK; + + TRACE("detaching scope chain %p, frame %p.\n", ctx->call_ctx->scope, frame); + + for (scope = ctx->call_ctx->scope; scope != frame->base_scope; scope = scope->next) + { + if (!scope->frame) + continue; + + assert(scope->frame == frame); + scope->frame = NULL; + + index = scope->scope_index; + + for(i = 0; i < frame->function->local_scopes[index].locals_cnt; i++) + { + hres = jsdisp_propput_name(scope->jsobj, frame->function->local_scopes[index].locals[i].name, + ctx->stack[local_off(frame, frame->function->local_scopes[index].locals[i].ref)]); + if(FAILED(hres)) + return hres; + } + } + + if(!frame->base_scope->frame) return S_OK;
TRACE("detaching %p\n", frame); @@ -627,10 +664,10 @@ static int __cdecl local_ref_cmp(const void *key, const void *ref) return wcscmp((const WCHAR*)key, ((const local_ref_t*)ref)->name); }
-local_ref_t *lookup_local(const function_code_t *function, const WCHAR *identifier) +local_ref_t *lookup_local(const function_code_t *function, const WCHAR *identifier, unsigned int scope) { - return bsearch(identifier, function->local_scopes[0].locals, function->local_scopes[0].locals_cnt, - sizeof(*function->local_scopes[0].locals), local_ref_cmp); + return bsearch(identifier, function->local_scopes[scope].locals, function->local_scopes[scope].locals_cnt, + sizeof(*function->local_scopes[scope].locals), local_ref_cmp); }
/* ECMA-262 3rd Edition 10.1.4 */ @@ -647,7 +684,7 @@ static HRESULT identifier_eval(script_ctx_t *ctx, BSTR identifier, exprval_t *re for(scope = ctx->call_ctx->scope; scope; scope = scope->next) { if(scope->frame) { function_code_t *func = scope->frame->function; - local_ref_t *ref = lookup_local(func, identifier); + local_ref_t *ref = lookup_local(func, identifier, scope->scope_index);
if(ref) { ret->type = EXPRVAL_STACK_REF; @@ -823,11 +860,17 @@ static HRESULT interp_forin(script_ctx_t *ctx) /* ECMA-262 3rd Edition 12.10 */ static HRESULT interp_push_scope(script_ctx_t *ctx) { + unsigned int scope_index = get_op_uint(ctx, 0); + BOOL scope_block = get_op_uint(ctx, 1); + call_frame_t *frame = ctx->call_ctx; + unsigned int off; + jsdisp_t *dispex; IDispatch *disp; - jsval_t v; + unsigned int i; HRESULT hres; + jsval_t v;
- TRACE("\n"); + TRACE("scope_index %u.\n", scope_index);
v = stack_pop(ctx); hres = to_object(ctx, v, &disp); @@ -835,9 +878,39 @@ static HRESULT interp_push_scope(script_ctx_t *ctx) if(FAILED(hres)) return hres;
- hres = scope_push(ctx->call_ctx->scope, to_jsdisp(disp), disp, &ctx->call_ctx->scope); + dispex = to_jsdisp(disp); + hres = scope_push(ctx->call_ctx->scope, dispex, disp, scope_index, &ctx->call_ctx->scope); IDispatch_Release(disp); - return hres; + + if (FAILED(hres) || !scope_block) + return hres; + + assert(dispex); + + if (frame->base_scope && frame->base_scope->frame) + { + assert(frame->base_scope->frame == frame); + frame->scope->frame = ctx->call_ctx; + + for(i = 0; i < frame->function->local_scopes[scope_index].locals_cnt; i++) + { + off = local_off(frame, frame->function->local_scopes[scope_index].locals[i].ref); + jsval_release(ctx->stack[off]); + ctx->stack[off] = jsval_undefined(); + } + } + else + { + for(i = 0; i < frame->function->local_scopes[scope_index].locals_cnt; i++) + { + hres = jsdisp_propput_name(dispex, frame->function->local_scopes[scope_index].locals[i].name, + jsval_undefined()); + if(FAILED(hres)) + return hres; + } + } + + return S_OK; }
/* ECMA-262 3rd Edition 12.10 */ @@ -1036,7 +1109,7 @@ static HRESULT interp_enter_catch(script_ctx_t *ctx) hres = jsdisp_propput_name(scope_obj, ident, v); jsval_release(v); if(SUCCEEDED(hres)) - hres = scope_push(ctx->call_ctx->scope, scope_obj, to_disp(scope_obj), &ctx->call_ctx->scope); + hres = scope_push(ctx->call_ctx->scope, scope_obj, to_disp(scope_obj), 0, &ctx->call_ctx->scope); jsdisp_release(scope_obj); return hres; } @@ -3032,7 +3105,7 @@ static HRESULT setup_scope(script_ctx_t *ctx, call_frame_t *frame, scope_chain_t
frame->pop_variables = i;
- hres = scope_push(scope_chain, variable_object, to_disp(variable_object), &scope); + hres = scope_push(scope_chain, variable_object, to_disp(variable_object), 0, &scope); if(FAILED(hres)) { stack_popn(ctx, ctx->stack_top - orig_stack); return hres; diff --git a/dlls/jscript/engine.h b/dlls/jscript/engine.h index a94f1f211e4..3656b32dd8d 100644 --- a/dlls/jscript/engine.h +++ b/dlls/jscript/engine.h @@ -75,7 +75,7 @@ X(preinc, 1, ARG_INT, 0) \ X(push_acc, 1, 0,0) \ X(push_except,1, ARG_ADDR, ARG_UINT) \ - X(push_scope, 1, 0,0) \ + X(push_scope, 1, ARG_UINT, ARG_UINT) \ X(regexp, 1, ARG_STR, ARG_UINT) \ X(rshift, 1, 0,0) \ X(rshift2, 1, 0,0) \ @@ -180,7 +180,7 @@ typedef struct _function_code_t { } function_code_t;
IDispatch *lookup_global_host(script_ctx_t*) DECLSPEC_HIDDEN; -local_ref_t *lookup_local(const function_code_t*,const WCHAR*) DECLSPEC_HIDDEN; +local_ref_t *lookup_local(const function_code_t*,const WCHAR*,unsigned int) DECLSPEC_HIDDEN;
struct _bytecode_t { LONG ref; @@ -222,6 +222,7 @@ typedef struct _scope_chain_t { LONG ref; jsdisp_t *jsobj; IDispatch *obj; + unsigned int scope_index; struct _call_frame_t *frame; struct _scope_chain_t *next; } scope_chain_t; diff --git a/dlls/jscript/parser.h b/dlls/jscript/parser.h index c4dd0752ee9..32bdc3b5186 100644 --- a/dlls/jscript/parser.h +++ b/dlls/jscript/parser.h @@ -129,6 +129,7 @@ struct _statement_t {
typedef struct { statement_t stat; + unsigned int scope_index; statement_t *stat_list; } block_statement_t;
@@ -184,6 +185,7 @@ typedef struct { statement_t stat; expression_t *expr; statement_t *statement; + unsigned int scope_index; } with_statement_t;
typedef struct { diff --git a/dlls/jscript/parser.y b/dlls/jscript/parser.y index 7d0630582e0..86bfbe2fd01 100644 --- a/dlls/jscript/parser.y +++ b/dlls/jscript/parser.y @@ -1123,6 +1123,7 @@ static statement_t *new_block_statement(parser_ctx_t *ctx, unsigned loc, stateme if(!ret) return NULL;
+ ret->scope_index = 0; ret->stat_list = list ? list->head : NULL;
return &ret->stat; @@ -1306,6 +1307,7 @@ static statement_t *new_with_statement(parser_ctx_t *ctx, unsigned loc, expressi
ret->expr = expr; ret->statement = statement; + ret->scope_index = 0;
return &ret->stat; } diff --git a/dlls/jscript/tests/lang.js b/dlls/jscript/tests/lang.js index 2028d233ccf..baff4fbaa73 100644 --- a/dlls/jscript/tests/lang.js +++ b/dlls/jscript/tests/lang.js @@ -1586,6 +1586,22 @@ tmp.testWith = true; with(tmp) ok(testWith === true, "testWith !== true");
+function withScopeTest() +{ + var a = 3; + with({a : 2}) + { + ok(a == 2, "withScopeTest: a != 2"); + function func() + { + ok(a == 3, "withScopeTest: func: a != 3"); + } + func(); + eval('ok(a == 2, "withScopeTest: eval: a != 2");'); + } +} +withScopeTest(); + if(false) { var varTest1 = true; } diff --git a/dlls/mshtml/tests/es5.js b/dlls/mshtml/tests/es5.js index 24906de5231..4f590f9b8fd 100644 --- a/dlls/mshtml/tests/es5.js +++ b/dlls/mshtml/tests/es5.js @@ -1210,17 +1210,71 @@ sync_test("head_setter", function() {
sync_test("declaration_let", function() { + ok(typeof(func) === "undefined", "typeof(func) = " + typeof(func)); + with(new Object()) { + var x = false && function func() {}; + } + ok(typeof(func) === "undefined", "typeof(func) = " + typeof(func)); + + function expect_exception(func, todo) { + try { + func(); + }catch(e) { + return; + } + if (typeof todo === 'undefined' || !todo) + ok(false, "expected exception"); + else + todo_wine.ok(false, "expected exception"); + } + + function call_func(f, expected_a) + { + f(2, expected_a); + } + ok(a === undefined, "a is not undefined"); var a = 3;
{ let a = 2; + let b + + ok(typeof b === 'undefined', "b is defined"); + ok(b === undefined, "b !== undefined");
ok(a == 2, "a != 2");
a = 4; ok(a == 4, "a != 4"); + + eval('ok(a == 4, "eval: a != 4"); b = a; a = 5;') + ok(b == 4, "b != 4"); + ok(a == 5, "a != 5"); + + function func1() + { + ok(typeof b === 'undefined', "func1: b is defined"); + ok(b === undefined, "func1: should produce exception"); + let b = 1; + } + expect_exception(func1, true); + + function func2() + { + let b = 1; + ok(b == 1, "func2: b != 1"); + } + func2(); + } + + if (0) + { + function func4() + { + } } + todo_wine.ok(typeof func4 === 'undefined', "func4 is defined");
- todo_wine.ok(a == 3, "a != 3"); + ok(a == 3, "a != 3"); });
Hi,
While running your changed tests, I think I found new failures. Being a bot and all I'm not very good at pattern recognition, so I might be wrong, but could you please double-check?
Full results can be found at: https://testbot.winehq.org/JobDetails.pl?Key=92366
Your paranoid android.
=== w8adm (32 bit report) ===
mshtml: events.c:1089: Test failed: unexpected call img_onerror events: Timeout
=== w10pro64_zh_CN (testbot log) ===
WineRunTask.pl:error: The previous 1 run(s) terminated abnormally
Signed-off-by: Paul Gofman pgofman@codeweavers.com --- dlls/jscript/compile.c | 34 +++++++++++++++++++++-- dlls/jscript/engine.c | 59 +++++++++++++++++++++++++++++++++++++-- dlls/jscript/engine.h | 2 ++ dlls/jscript/parser.h | 1 + dlls/jscript/parser.y | 1 + dlls/mshtml/tests/es5.js | 60 ++++++++++++++++++++++++++++++++++++++-- 6 files changed, 150 insertions(+), 7 deletions(-)
diff --git a/dlls/jscript/compile.c b/dlls/jscript/compile.c index 1227a27ddcc..68c6d75e868 100644 --- a/dlls/jscript/compile.c +++ b/dlls/jscript/compile.c @@ -41,6 +41,7 @@ typedef struct _statement_ctx_t {
unsigned int scope_index; BOOL block_scope; + BOOL scope_has_functions; struct _statement_ctx_t *next; } statement_ctx_t;
@@ -81,6 +82,7 @@ typedef struct _compiler_ctx_t {
function_expression_t *func_head; function_expression_t *func_tail; + function_expression_t *current_function_expr;
heap_pool_t heap; } compiler_ctx_t; @@ -950,6 +952,18 @@ static HRESULT compile_object_literal(compiler_ctx_t *ctx, property_value_expres
static HRESULT compile_function_expression(compiler_ctx_t *ctx, function_expression_t *expr, BOOL emit_ret) { + statement_ctx_t *stat_ctx; + + assert(ctx->current_function_expr); + + for(stat_ctx = ctx->stat_ctx; stat_ctx; stat_ctx = stat_ctx->next) + { + if(stat_ctx->using_scope) + break; + } + ctx->current_function_expr->scope_index = stat_ctx ? stat_ctx->scope_index : 0; + ctx->current_function_expr = ctx->current_function_expr->next; + return emit_ret ? push_instr_uint(ctx, OP_func, expr->func_id) : S_OK; }
@@ -1936,11 +1950,23 @@ static BOOL alloc_variable(compiler_ctx_t *ctx, const WCHAR *name, unsigned int
static HRESULT visit_function_expression(compiler_ctx_t *ctx, function_expression_t *expr) { + statement_ctx_t *stat_ctx; + expr->func_id = ctx->func->func_cnt++; ctx->func_tail = ctx->func_tail ? (ctx->func_tail->next = expr) : (ctx->func_head = expr);
if(!expr->identifier || expr->event_target) return S_OK; + + for (stat_ctx = ctx->stat_ctx; stat_ctx; stat_ctx = stat_ctx->next) + { + if (stat_ctx->block_scope) + { + stat_ctx->scope_has_functions = TRUE; + break; + } + } + if(!expr->is_statement && ctx->parser->script->version >= SCRIPTLANGUAGEVERSION_ES5) return S_OK;
@@ -2137,7 +2163,7 @@ static HRESULT visit_block_statement(compiler_ctx_t *ctx, block_statement_t *blo if (!needs_scope) return S_OK;
- if (ctx->local_scopes[stat_ctx.scope_index].locals_cnt) + if (ctx->local_scopes[stat_ctx.scope_index].locals_cnt || stat_ctx.scope_has_functions) { block->scope_index = stat_ctx.scope_index; } @@ -2436,6 +2462,7 @@ static HRESULT compile_function(compiler_ctx_t *ctx, source_elements_t *source,
func->bytecode = ctx->code; func->local_ref = INVALID_LOCAL_REF; + func->scope_index = 0; ctx->func_head = ctx->func_tail = NULL; ctx->from_eval = from_eval; ctx->func = func; @@ -2519,6 +2546,7 @@ static HRESULT compile_function(compiler_ctx_t *ctx, source_elements_t *source, return E_OUTOFMEMORY; memset(func->funcs, 0, func->func_cnt * sizeof(*func->funcs));
+ ctx->current_function_expr = ctx->func_head; off = ctx->code_off; hres = compile_block_statement(ctx, NULL, source->statement); if(FAILED(hres)) @@ -2540,7 +2568,9 @@ static HRESULT compile_function(compiler_ctx_t *ctx, source_elements_t *source, if(FAILED(hres)) return hres;
- TRACE("[%d] func %s\n", i, debugstr_w(func->funcs[i].name)); + func->funcs[i].scope_index = iter->scope_index; + + TRACE("[%d] func %s, scope_index %u\n", i, debugstr_w(func->funcs[i].name), iter->scope_index); if((ctx->parser->script->version < SCRIPTLANGUAGEVERSION_ES5 || iter->is_statement) && func->funcs[i].name && !func->funcs[i].event_target) { local_ref_t *local_ref = lookup_local(func, func->funcs[i].name, 0); diff --git a/dlls/jscript/engine.c b/dlls/jscript/engine.c index 877d67afb31..8e05c8449c6 100644 --- a/dlls/jscript/engine.c +++ b/dlls/jscript/engine.c @@ -863,6 +863,7 @@ static HRESULT interp_push_scope(script_ctx_t *ctx) unsigned int scope_index = get_op_uint(ctx, 0); BOOL scope_block = get_op_uint(ctx, 1); call_frame_t *frame = ctx->call_ctx; + BOOL detached_vars; unsigned int off; jsdisp_t *dispex; IDispatch *disp; @@ -882,12 +883,50 @@ static HRESULT interp_push_scope(script_ctx_t *ctx) hres = scope_push(ctx->call_ctx->scope, dispex, disp, scope_index, &ctx->call_ctx->scope); IDispatch_Release(disp);
- if (FAILED(hres) || !scope_block) + if (FAILED(hres) || !scope_index) return hres;
+ detached_vars = !(frame->base_scope && frame->base_scope->frame); + + for(i = 0; i < frame->function->func_cnt; i++) { + if(frame->function->funcs[i].local_ref != INVALID_LOCAL_REF + && frame->function->funcs[i].scope_index == scope_index) + { + jsdisp_t *func_obj; + unsigned off; + int ref; + + TRACE("%s %d\n", debugstr_w(frame->function->funcs[i].name), i); + + if (FAILED(hres = create_source_function(ctx, frame->bytecode, frame->function->funcs+i, + ctx->call_ctx->scope, &func_obj))) + return hres; + + ref = frame->function->funcs[i].local_ref; + + if (detached_vars) + { + assert(frame->variable_obj); + hres = jsdisp_propput_name(frame->variable_obj, frame->function->funcs[i].name, + jsval_obj(func_obj)); + if(FAILED(hres)) + return hres; + } + else + { + off = local_off(frame, ref); + jsval_release(ctx->stack[off]); + ctx->stack[off] = jsval_obj(func_obj); + } + } + } + + if (!scope_block) + return S_OK; + assert(dispex);
- if (frame->base_scope && frame->base_scope->frame) + if (!detached_vars) { assert(frame->base_scope->frame == frame); frame->scope->frame = ctx->call_ctx; @@ -3112,7 +3151,9 @@ static HRESULT setup_scope(script_ctx_t *ctx, call_frame_t *frame, scope_chain_t }
for(i = 0; i < frame->function->func_cnt; i++) { - if(frame->function->funcs[i].local_ref != INVALID_LOCAL_REF) { + if(frame->function->funcs[i].local_ref != INVALID_LOCAL_REF + && !frame->function->funcs[i].scope_index) + { jsdisp_t *func_obj; unsigned off;
@@ -3166,6 +3207,12 @@ HRESULT exec_source(script_ctx_t *ctx, DWORD flags, bytecode_t *bytecode, functi if(!function->funcs[i].event_target) continue;
+ if (function->funcs[i].scope_index) + { + /* TODO: Add tests and handle in interp_push_scope(). */ + FIXME("Event target with scope index are not properly handled.\n"); + } + hres = create_source_function(ctx, bytecode, function->funcs+i, scope, &func_obj); if(FAILED(hres)) return hres; @@ -3196,6 +3243,12 @@ HRESULT exec_source(script_ctx_t *ctx, DWORD flags, bytecode_t *bytecode, functi if(function->variables[i].func_id != -1) { jsdisp_t *func_obj;
+ if (function->funcs[function->variables[i].func_id].scope_index && flags & EXEC_EVAL) + { + /* TODO: Add tests and handle in interp_push_scope(). */ + FIXME("Functions with scope index inside eval() are not properly handled.\n"); + } + hres = create_source_function(ctx, bytecode, function->funcs+function->variables[i].func_id, scope, &func_obj); if(FAILED(hres)) goto fail; diff --git a/dlls/jscript/engine.h b/dlls/jscript/engine.h index 3656b32dd8d..c395ad502ff 100644 --- a/dlls/jscript/engine.h +++ b/dlls/jscript/engine.h @@ -176,6 +176,8 @@ typedef struct _function_code_t { local_ref_scopes_t *local_scopes; unsigned local_scope_count;
+ unsigned int scope_index; /* index of scope in the parent function where the function is defined */ + bytecode_t *bytecode; } function_code_t;
diff --git a/dlls/jscript/parser.h b/dlls/jscript/parser.h index 32bdc3b5186..df036d47fd4 100644 --- a/dlls/jscript/parser.h +++ b/dlls/jscript/parser.h @@ -306,6 +306,7 @@ typedef struct _function_expression_t { DWORD src_len; unsigned func_id; BOOL is_statement; + unsigned int scope_index;
struct _function_expression_t *next; /* for compiler */ } function_expression_t; diff --git a/dlls/jscript/parser.y b/dlls/jscript/parser.y index 86bfbe2fd01..6ab5ecf42db 100644 --- a/dlls/jscript/parser.y +++ b/dlls/jscript/parser.y @@ -1408,6 +1408,7 @@ static expression_t *new_function_expression(parser_ctx_t *ctx, const WCHAR *ide ret->src_str = src_str; ret->src_len = src_len; ret->is_statement = FALSE; + ret->scope_index = 0; ret->next = NULL;
return &ret->expr; diff --git a/dlls/mshtml/tests/es5.js b/dlls/mshtml/tests/es5.js index 4f590f9b8fd..cf086df0bb2 100644 --- a/dlls/mshtml/tests/es5.js +++ b/dlls/mshtml/tests/es5.js @@ -1266,15 +1266,71 @@ sync_test("declaration_let", function() { ok(b == 1, "func2: b != 1"); } func2(); - }
+ var w = 8; + with({w: 9}) + { + { + let c = 5 + + function func3(b, expected) + { + var b = 2 + + ok(typeof d === 'undefined', "d is defined"); + + ok(c == expected, "func3: c != expected"); + ok(w == 9, "w != 9") + ok(b == 2, "func3: b != 1"); + b = 3; + ok(b == 3, "func3: b != 1"); + ok(a == expected, "func3: a != expected"); + a = 6; + c = 6; + } + + let f3 = func3 + let f4 = function() + { + ok(a == 6, "f4: a != 6"); + } + + ok(a == 5, "tmp 2 a != 5"); + ok(c == 5, "c != 5"); + func3(1, 5) + ok(c == 6, "c != 6"); + call_func(func3, 6); + f3(1, 6) + ok(a == 6, "a != 6"); + ok(b == 4, "b != 4"); + ok(c == 6, "c != 6"); + + call_func(f4); + f4(); + } + } + expect_exception(function() {f4();}); + { + let d = 3; + let c = 4; + + func3(1, 6); + } + } + ok(typeof func3 === 'function', "func3 is not defined"); if (0) { function func4() { } } - todo_wine.ok(typeof func4 === 'undefined', "func4 is defined"); + ok(typeof func4 === 'undefined', "func4 is defined"); + + function func3(a1, a2) + { + ok(false, "second func3 called"); + } + func3(1, 6);
ok(a == 3, "a != 3"); });
Hi,
While running your changed tests, I think I found new failures. Being a bot and all I'm not very good at pattern recognition, so I might be wrong, but could you please double-check?
Full results can be found at: https://testbot.winehq.org/JobDetails.pl?Key=92367
Your paranoid android.
=== w8adm (32 bit report) ===
mshtml: htmldoc.c:3084: Test failed: Incorrect error code: -2146697211 htmldoc.c:3089: Test failed: Page address: L"http://test.winehq.org/tests/winehq_snapshot/" htmldoc.c:5861: Test failed: expected OnChanged_1012 htmldoc.c:5862: Test failed: expected Exec_HTTPEQUIV htmldoc.c:5864: Test failed: expected Exec_SETTITLE htmldoc.c:5905: Test failed: expected FireNavigateComplete2
=== w1064_tsign (64 bit report) ===
mshtml: htmldoc.c:2541: Test failed: unexpected call UpdateUI htmldoc.c:2853: Test failed: unexpected call Exec_UPDATECOMMANDS htmldoc.c:350: Test failed: expected Exec_SETTITLE htmldoc.c:2859: Test failed: unexpected call Exec_SETTITLE
Hi Paul,
On 6/11/21 7:46 PM, Paul Gofman wrote:
Signed-off-by: Paul Gofmanpgofman@codeweavers.com
dlls/jscript/compile.c | 34 +++++++++++++++++++++-- dlls/jscript/engine.c | 59 +++++++++++++++++++++++++++++++++++++-- dlls/jscript/engine.h | 2 ++ dlls/jscript/parser.h | 1 + dlls/jscript/parser.y | 1 + dlls/mshtml/tests/es5.js | 60 ++++++++++++++++++++++++++++++++++++++-- 6 files changed, 150 insertions(+), 7 deletions(-)
It would be nice to have tests for more interesting cases. I attached a test shows scope chain life time and it's failing for me with your patches.
While writing the test, I also noticed that 'let' inside 'for' statement doesn't work, for example: for(let i = ...; ...)
Thanks, Jacek
Hello Jacek,
On 6/14/21 21:17, Jacek Caban wrote:
Hi Paul,
On 6/11/21 7:46 PM, Paul Gofman wrote:
Signed-off-by: Paul Gofmanpgofman@codeweavers.com
dlls/jscript/compile.c | 34 +++++++++++++++++++++-- dlls/jscript/engine.c | 59 +++++++++++++++++++++++++++++++++++++-- dlls/jscript/engine.h | 2 ++ dlls/jscript/parser.h | 1 + dlls/jscript/parser.y | 1 + dlls/mshtml/tests/es5.js | 60 ++++++++++++++++++++++++++++++++++++++-- 6 files changed, 150 insertions(+), 7 deletions(-)
It would be nice to have tests for more interesting cases. I attached a test shows scope chain life time and it's failing for me with your patches.
Yes, thanks. It seems to me it is not exactly about the scope life time, looks like I need to detach scope when leaving it if it is still referenced (similar to leaving the function frame; currently it is not detached and while scope is alive the on-stack value is retrieved which is wrong). Executing detach_variable_object() on leaving the scope helps in this case. It can be optimized by detaching the scope being left only, but then it will need a neater tracking of deoptimized case in stack_topn_exprval() for local refs. I am not yet quite sure if such optimization worth it.
While writing the test, I also noticed that 'let' inside 'for' statement doesn't work, for example: for(let i = ...; ...)
Yes. The 'let' and 'var' in 'for' is a separate syntax rule in spec and the same is for 'var' in our implementation. I thought of it as a separate change which can be added later (the handling of let in for should be easy on top of these patches). Or do you think it should be included at once?
On 6/15/21 8:29 AM, Paul Gofman wrote:
Hello Jacek,
On 6/14/21 21:17, Jacek Caban wrote:
Hi Paul,
On 6/11/21 7:46 PM, Paul Gofman wrote:
Signed-off-by: Paul Gofmanpgofman@codeweavers.com
dlls/jscript/compile.c | 34 +++++++++++++++++++++-- dlls/jscript/engine.c | 59 +++++++++++++++++++++++++++++++++++++-- dlls/jscript/engine.h | 2 ++ dlls/jscript/parser.h | 1 + dlls/jscript/parser.y | 1 + dlls/mshtml/tests/es5.js | 60 ++++++++++++++++++++++++++++++++++++++-- 6 files changed, 150 insertions(+), 7 deletions(-)
It would be nice to have tests for more interesting cases. I attached a test shows scope chain life time and it's failing for me with your patches.
Yes, thanks. It seems to me it is not exactly about the scope life time, looks like I need to detach scope when leaving it if it is still referenced (similar to leaving the function frame; currently it is not detached and while scope is alive the on-stack value is retrieved which is wrong). Executing detach_variable_object() on leaving the scope helps in this case. It can be optimized by detaching the scope being left only, but then it will need a neater tracking of deoptimized case in stack_topn_exprval() for local refs. I am not yet quite sure if such optimization worth it.
The optimization would be ultimitely nice, but it's fine to have it less optimized if it makes things much easier for now.
While writing the test, I also noticed that 'let' inside 'for' statement doesn't work, for example: for(let i = ...; ...)
Yes. The 'let' and 'var' in 'for' is a separate syntax rule in spec and the same is for 'var' in our implementation. I thought of it as a separate change which can be added later (the handling of let in for should be easy on top of these patches). Or do you think it should be included at once?
It's fine to leave it for later.
Jacek
Hi,
While running your changed tests, I think I found new failures. Being a bot and all I'm not very good at pattern recognition, so I might be wrong, but could you please double-check?
Full results can be found at: https://testbot.winehq.org/JobDetails.pl?Key=92364
Your paranoid android.
=== w8adm (32 bit report) ===
mshtml: htmldoc.c:3084: Test failed: Incorrect error code: -2146697211 htmldoc.c:3089: Test failed: Page address: L"http://test.winehq.org/tests/winehq_snapshot/" htmldoc.c:5861: Test failed: expected OnChanged_1012 htmldoc.c:5862: Test failed: expected Exec_HTTPEQUIV htmldoc.c:5864: Test failed: expected Exec_SETTITLE htmldoc.c:5905: Test failed: expected FireNavigateComplete2
=== w10pro64_he (64 bit report) ===
mshtml: htmldoc.c:2541: Test failed: unexpected call UpdateUI htmldoc.c:2853: Test failed: unexpected call Exec_UPDATECOMMANDS htmldoc.c:350: Test failed: expected Exec_SETTITLE htmldoc.c:2859: Test failed: unexpected call Exec_SETTITLE
On 6/11/21 7:46 PM, Paul Gofman wrote:
diff --git a/dlls/jscript/lex.c b/dlls/jscript/lex.c index efed1038a95..3dd609885e6 100644 --- a/dlls/jscript/lex.c +++ b/dlls/jscript/lex.c @@ -41,6 +41,7 @@ static const struct { {L"break", kBREAK, TRUE}, {L"case", kCASE}, {L"catch", kCATCH},
- {L"const", kCONST, FALSE},
Your tests show pre-ES5 versions treat it as a reserved keyword, but it seems to me that it's not actually supported there.
Thanks, Jacek
On 6/14/21 21:22, Jacek Caban wrote:
On 6/11/21 7:46 PM, Paul Gofman wrote:
diff --git a/dlls/jscript/lex.c b/dlls/jscript/lex.c index efed1038a95..3dd609885e6 100644 --- a/dlls/jscript/lex.c +++ b/dlls/jscript/lex.c @@ -41,6 +41,7 @@ static const struct { {L"break", kBREAK, TRUE}, {L"case", kCASE}, {L"catch", kCATCH}, + {L"const", kCONST, FALSE},
Your tests show pre-ES5 versions treat it as a reserved keyword, but it seems to me that it's not actually supported there.
Yes, I will add a test for the actual 'const' usage in pre-ES5 and will fire an error on attempt to use it.