From: Paul Gofman Subject: [PATCH v3 3/6] jscript: Support block scope variables. Message-Id: <20210615150739.150691-3-pgofman@codeweavers.com> Date: Tue, 15 Jun 2021 18:07:36 +0300 In-Reply-To: <20210615150739.150691-1-pgofman@codeweavers.com> References: <20210615150739.150691-1-pgofman@codeweavers.com> Signed-off-by: Paul Gofman --- v3: - initialize new scope's locals tree in visit_statement( STAT_WITH case): fixes assertion in test from patch 6. dlls/jscript/compile.c | 235 +++++++++++++++++++++++++++---------- 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 | 48 +++++++- 7 files changed, 334 insertions(+), 79 deletions(-) diff --git a/dlls/jscript/compile.c b/dlls/jscript/compile.c index 46efd53cdb5..9ac3d221ff0 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,36 @@ 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; + wine_rb_init(&ctx->local_scopes[stat_ctx.scope_index].locals, function_local_cmp); + } + + 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 +2430,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 +2440,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 +2477,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 +2521,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 +2544,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 +2659,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 fd8ea7e7aa7..f14577c7d1f 100644 --- a/dlls/jscript/parser.y +++ b/dlls/jscript/parser.y @@ -1131,6 +1131,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; @@ -1314,6 +1315,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 0db86c89985..ad1217d6b60 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..f4710348739 100644 --- a/dlls/mshtml/tests/es5.js +++ b/dlls/mshtml/tests/es5.js @@ -1210,17 +1210,63 @@ 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(); } - todo_wine.ok(a == 3, "a != 3"); + ok(a == 3, "a != 3"); }); -- 2.31.1