[PATCH v2 4/5] jscript: Support nested scopes for functions defined inside.

Paul Gofman pgofman at codeweavers.com
Tue Jun 15 05:04:16 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.

 dlls/jscript/compile.c   | 40 ++++++++++++++++--
 dlls/jscript/engine.c    | 84 +++++++++++++++++++++++++++++--------
 dlls/jscript/engine.h    |  2 +
 dlls/jscript/parser.h    |  1 +
 dlls/jscript/parser.y    |  1 +
 dlls/mshtml/tests/es5.js | 89 +++++++++++++++++++++++++++++++++++++++-
 6 files changed, 194 insertions(+), 23 deletions(-)

diff --git a/dlls/jscript/compile.c b/dlls/jscript/compile.c
index 1227a27ddcc..21edc650090 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;
     }
@@ -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,10 +2568,14 @@ 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);
+            if (!local_ref)
+                ERR("namke %s, scope_index %d.\n", debugstr_w(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..b77ac416c5c 100644
--- a/dlls/jscript/engine.c
+++ b/dlls/jscript/engine.c
@@ -592,9 +592,13 @@ static HRESULT detach_variable_object(script_ctx_t *ctx, call_frame_t *frame, BO
 
         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))
+            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;
         }
     }
@@ -863,6 +867,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 +887,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 +948,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 +3148,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 +3204,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 +3240,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 4f590f9b8fd..a40ff05dbbb 100644
--- a/dlls/mshtml/tests/es5.js
+++ b/dlls/mshtml/tests/es5.js
@@ -1236,6 +1236,22 @@ sync_test("declaration_let", function() {
     ok(a === undefined, "a is not undefined");
     var a = 3;
 
+    func3(98, 98);
+
+    function func3(a1, a2)
+    {
+        ok(a1 == 96 && a2 == 96, "func3 scope 0 ver 1: a1 " + a1 + ", a2 " + a2);
+    }
+
+    func3(98, 98);
+
+    function func3(a1, a2)
+    {
+        ok(a1 == 98 && a2 == 98, "func3 scope 0 ver 2: a1 " + a1 + ", a2 " + a2);
+    }
+
+    func3(98, 98);
+
     {
         let a = 2;
         let b
@@ -1266,15 +1282,84 @@ sync_test("declaration_let", function() {
             ok(b == 1, "func2: b != 1");
         }
         func2();
-    }
 
+        function func3(a1, a2)
+        {
+            ok(a1 == 99 && a2 == 99, "func3 scope 1: a1 " + a1 + ", a2 " + a2);
+            ok(b == 4, "func3 scope 1: b " + b);
+        }
+
+        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();
+            }
+            func3(99, 99);
+        }
+        expect_exception(function() {f4();});
+
+        {
+            let d = 3;
+            let c = 4;
+
+            func3(99, 99);
+        }
+    }
+    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");
+
+    func3(1, 6);
+
+    {
+        function func3(a1, a2)
+        {
+            ok(a1 == 97 && a2 == 97, "func3 scope 2: a1 " + a1 + ", a2 " + a2);
+            ok(a == 3, "func3 scope 1: a " + a);
+        }
+    }
+    func3(97, 97);
 
     ok(a == 3, "a != 3");
 });
-- 
2.31.1




More information about the wine-devel mailing list