[PATCH 3/4] jscript: Support block scope variables.

Paul Gofman pgofman at codeweavers.com
Fri Jun 11 12:46:25 CDT 2021


Signed-off-by: Paul Gofman <pgofman at 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");
 });
-- 
2.31.1




More information about the wine-devel mailing list