From: Paul Gofman Subject: [PATCH v3 4/6] jscript: Support nested scopes for functions defined inside. Message-Id: <20210615150739.150691-4-pgofman@codeweavers.com> Date: Tue, 15 Jun 2021 18:07:37 +0300 In-Reply-To: <20210615150739.150691-1-pgofman@codeweavers.com> References: <20210615150739.150691-1-pgofman@codeweavers.com> Signed-off-by: Paul Gofman --- v2: - detach variable object when leaving scope which is still reference (fixes test by Jacek from patch 5); - add more tests for binding function defined and referenced in various scopes; - allocate function variable in the scope where the function is defined; - keep detached function variable both in local scope object and function base scope object. v3: - put the outer scope functions to variable_obj first in detach_variable_object(): fixes some test failures in patch 6; - remove unrelated tests from "declaration_let" test. dlls/jscript/compile.c | 39 +++++++++- dlls/jscript/engine.c | 154 +++++++++++++++++++++++++++------------ dlls/jscript/engine.h | 2 + dlls/jscript/parser.h | 1 + dlls/jscript/parser.y | 1 + dlls/mshtml/tests/es5.js | 49 +++++++++++++ 6 files changed, 195 insertions(+), 51 deletions(-) diff --git a/dlls/jscript/compile.c b/dlls/jscript/compile.c index 9ac3d221ff0..dd970ef8678 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,15 +1950,27 @@ 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; - return alloc_variable(ctx, expr->identifier, 0) ? S_OK : E_OUTOFMEMORY; + return alloc_variable(ctx, expr->identifier, stat_ctx ? stat_ctx->scope_index : 0) ? S_OK : E_OUTOFMEMORY; } static HRESULT visit_expression(compiler_ctx_t *ctx, expression_t *expr) @@ -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; } @@ -2437,6 +2463,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; @@ -2520,6 +2547,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)) @@ -2541,10 +2569,13 @@ 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); + local_ref_t *local_ref = lookup_local(func, func->funcs[i].name, func->funcs[i].scope_index); + 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) diff --git a/dlls/jscript/engine.c b/dlls/jscript/engine.c index 877d67afb31..13beaf5acd3 100644 --- a/dlls/jscript/engine.c +++ b/dlls/jscript/engine.c @@ -565,14 +565,45 @@ HRESULT jsval_strict_equal(jsval_t lval, jsval_t rval, BOOL *ret) return S_OK; } +static HRESULT detach_scope_chain(script_ctx_t *ctx, call_frame_t *frame, scope_chain_t *scope) +{ + unsigned int i, index; + HRESULT hres; + + if (scope == frame->base_scope) + return S_OK; + + if (FAILED(hres = detach_scope_chain(ctx, frame, scope->next))) + return hres; + + if (!scope->frame) + return S_OK; + + assert(scope->frame == frame); + scope->frame = NULL; + index = scope->scope_index; + + for(i = 0; i < frame->function->local_scopes[index].locals_cnt; i++) + { + WCHAR *name = frame->function->local_scopes[index].locals[i].name; + int ref = frame->function->local_scopes[index].locals[i].ref; + + if (FAILED(hres = jsdisp_propput_name(scope->jsobj, name, ctx->stack[local_off(frame, ref)]))) + return hres; + if (frame->function->variables[ref].func_id != -1 + && FAILED(hres = jsdisp_propput_name(frame->variable_obj, name, ctx->stack[local_off(frame, ref)]))) + return hres; + } + return S_OK; +} + /* * Transfers local variables from stack to variable object. * It's slow, so we want to avoid it as much as possible. */ static HRESULT detach_variable_object(script_ctx_t *ctx, call_frame_t *frame, BOOL from_release) { - unsigned int i, index; - scope_chain_t *scope; + unsigned int i; HRESULT hres; if(!frame->base_scope) @@ -580,47 +611,30 @@ static HRESULT detach_variable_object(script_ctx_t *ctx, call_frame_t *frame, BO 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(frame->base_scope->frame) { - if (!scope->frame) - continue; + TRACE("detaching %p\n", frame); - assert(scope->frame == frame); - scope->frame = NULL; + assert(frame == frame->base_scope->frame); + assert(frame->variable_obj == frame->base_scope->jsobj); - 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(!from_release && !frame->arguments_obj) { + hres = setup_arguments_object(ctx, frame); if(FAILED(hres)) return hres; } - } - if(!frame->base_scope->frame) - return S_OK; + frame->base_scope->frame = NULL; - TRACE("detaching %p\n", frame); - - assert(frame == frame->base_scope->frame); - assert(frame->variable_obj == frame->base_scope->jsobj); - - if(!from_release && !frame->arguments_obj) { - hres = setup_arguments_object(ctx, frame); - if(FAILED(hres)) - return hres; + 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; + } } - frame->base_scope->frame = NULL; - - 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; - } + detach_scope_chain(ctx, frame, ctx->call_ctx->scope); return S_OK; } @@ -863,6 +877,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,32 +897,57 @@ 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; - assert(dispex); + detached_vars = !(frame->base_scope && frame->base_scope->frame); - if (frame->base_scope && frame->base_scope->frame) + if (scope_block && !detached_vars) { + assert(dispex); 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++) + for(i = 0; i < frame->function->local_scopes[scope_index].locals_cnt; i++) + { + WCHAR *name = frame->function->local_scopes[scope_index].locals[i].name; + int ref = frame->function->local_scopes[scope_index].locals[i].ref; + jsdisp_t *func_obj; + jsval_t val; + + if (frame->function->variables[ref].func_id != -1) { - off = local_off(frame, frame->function->local_scopes[scope_index].locals[i].ref); - jsval_release(ctx->stack[off]); - ctx->stack[off] = jsval_undefined(); + TRACE("function %s %d\n", debugstr_w(name), i); + + if (FAILED(hres = create_source_function(ctx, frame->bytecode, frame->function->funcs + + frame->function->variables[ref].func_id, ctx->call_ctx->scope, &func_obj))) + return hres; + val = jsval_obj(func_obj); + if (detached_vars) + { + hres = jsdisp_propput_name(frame->variable_obj, name, jsval_obj(func_obj)); + if(FAILED(hres)) + return hres; + } } - } - else - { - for(i = 0; i < frame->function->local_scopes[scope_index].locals_cnt; i++) + else { - hres = jsdisp_propput_name(dispex, frame->function->local_scopes[scope_index].locals[i].name, - jsval_undefined()); + val = jsval_undefined(); + } + + if (detached_vars) + { + hres = jsdisp_propput_name(dispex, name, val); if(FAILED(hres)) return hres; } + else + { + off = local_off(frame, ref); + jsval_release(ctx->stack[off]); + ctx->stack[off] = val; + } } return S_OK; @@ -918,6 +958,12 @@ static HRESULT interp_pop_scope(script_ctx_t *ctx) { TRACE("\n"); + if(ctx->call_ctx->scope->ref > 1) { + HRESULT hres = detach_variable_object(ctx, ctx->call_ctx, FALSE); + if(FAILED(hres)) + ERR("Failed to detach variable object: %08x\n", hres); + } + scope_pop(&ctx->call_ctx->scope); return S_OK; } @@ -3112,7 +3158,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 +3214,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 +3250,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 f14577c7d1f..b74b503e858 100644 --- a/dlls/jscript/parser.y +++ b/dlls/jscript/parser.y @@ -1416,6 +1416,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 f4710348739..2fb115556e5 100644 --- a/dlls/mshtml/tests/es5.js +++ b/dlls/mshtml/tests/es5.js @@ -1266,6 +1266,55 @@ 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 != 2"); + b = 3; + ok(b == 3, "func3: b != 3"); + 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(); + } + } + { + let c = 4; + let d = 1; + + func3(1, 6); + } } ok(a == 3, "a != 3"); -- 2.31.1