[PATCH v3 4/6] jscript: Support nested scopes for functions defined inside.
Paul Gofman
pgofman at codeweavers.com
Tue Jun 15 10:07:37 CDT 2021
Signed-off-by: Paul Gofman <pgofman at codeweavers.com>
---
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
More information about the wine-devel
mailing list