jscript: Support block scope variables.

Signed-off-by: Paul Gofman <pgofman@codeweavers.com>
Signed-off-by: Jacek Caban <jacek@codeweavers.com>
Signed-off-by: Alexandre Julliard <julliard@winehq.org>
This commit is contained in:
Paul Gofman 2021-06-18 15:50:18 +03:00 committed by Alexandre Julliard
parent a35901137e
commit 308c8468b4
5 changed files with 285 additions and 71 deletions

View File

@ -65,14 +65,13 @@ typedef struct _compiler_ctx_t {
struct
{
struct wine_rb_tree locals;
unsigned int locals_cnt;
unsigned int *ref_index;
}
*local_scopes;
unsigned local_scope_count;
unsigned local_scope_size;
struct wine_rb_tree locals;
unsigned locals_cnt;
statement_ctx_t *stat_ctx;
function_code_t *func;
@ -137,6 +136,12 @@ static void dump_code(compiler_ctx_t *ctx, unsigned off)
static HRESULT compile_expression(compiler_ctx_t*,expression_t*,BOOL);
static HRESULT compile_statement(compiler_ctx_t*,statement_ctx_t*,statement_t*);
static int function_local_cmp(const void *key, const struct wine_rb_entry *entry)
{
function_local_t *local = WINE_RB_ENTRY_VALUE(entry, function_local_t, entry);
return wcscmp(key, local->name);
}
static BOOL alloc_local_scope(compiler_ctx_t *ctx, unsigned int *scope_index)
{
unsigned int scope, new_size;
@ -154,6 +159,7 @@ static BOOL alloc_local_scope(compiler_ctx_t *ctx, unsigned int *scope_index)
ctx->local_scopes[scope].locals_cnt = 0;
ctx->local_scopes[scope].ref_index = scope_index;
wine_rb_init(&ctx->local_scopes[scope].locals, function_local_cmp);
*scope_index = scope;
return TRUE;
@ -485,10 +491,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;
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;
@ -1157,18 +1172,33 @@ 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(FAILED(hres = push_instr_uint(ctx, OP_push_block_scope, block->scope_index)))
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;
}
@ -1184,8 +1214,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");
@ -1595,7 +1623,7 @@ 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))
if(!push_instr(ctx, OP_push_with_scope))
return E_OUTOFMEMORY;
hres = compile_statement(ctx, &stat_ctx, stat->statement);
@ -1833,7 +1861,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);
@ -1892,15 +1920,9 @@ static HRESULT compile_statement(compiler_ctx_t *ctx, statement_ctx_t *stat_ctx,
return hres;
}
static int function_local_cmp(const void *key, const struct wine_rb_entry *entry)
static inline function_local_t *find_local(compiler_ctx_t *ctx, const WCHAR *name, unsigned int scope)
{
function_local_t *local = WINE_RB_ENTRY_VALUE(entry, function_local_t, entry);
return wcscmp(key, local->name);
}
static inline function_local_t *find_local(compiler_ctx_t *ctx, const WCHAR *name)
{
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;
}
@ -1914,8 +1936,7 @@ static BOOL alloc_local(compiler_ctx_t *ctx, BSTR name, int ref, unsigned int sc
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;
}
@ -1924,7 +1945,7 @@ static BOOL alloc_variable(compiler_ctx_t *ctx, const WCHAR *name, unsigned int
{
BSTR ident;
if(find_local(ctx, name))
if(find_local(ctx, name, scope))
return TRUE;
ident = compiler_alloc_bstr(ctx, name);
@ -2416,8 +2437,6 @@ 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 = 0;
if (!alloc_local_scope(ctx, &scope))
return E_OUTOFMEMORY;
@ -2456,7 +2475,7 @@ 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, 0))
if(!find_local(ctx, func->params[i], 0) && !alloc_local(ctx, func->params[i], -i-1, 0))
return E_OUTOFMEMORY;
}
@ -2464,30 +2483,35 @@ static HRESULT compile_function(compiler_ctx_t *ctx, source_elements_t *source,
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)
@ -2495,7 +2519,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;
@ -2518,7 +2542,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)

View File

@ -205,30 +205,45 @@ 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) {
DISPID id;
BSTR name;
HRESULT hres;
HRESULT hres = E_FAIL;
/* Got stack reference in deoptimized code. Need to convert it back to variable object reference. */
assert(off < frame->variables_off + frame->function->var_cnt);
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);
if(FAILED(hres)) {
r->type = EXPRVAL_INVALID;
r->u.hres = hres;
return FALSE;
if (off >= frame->variables_off)
{
name = frame->function->variables[off - frame->variables_off].name;
scope = frame->scope;
}
else
{
name = frame->function->params[off - frame->arguments_off];
scope = frame->base_scope;
}
*stack_top_ref(ctx, n+1) = jsval_obj(jsdisp_addref(frame->base_scope->jsobj));
while (1)
{
if (scope->jsobj && SUCCEEDED(hres = jsdisp_get_id(scope->jsobj, name, 0, &id)))
break;
if (scope == frame->base_scope)
{
r->type = EXPRVAL_INVALID;
r->u.hres = hres;
return FALSE;
}
scope = scope->next;
}
*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;
}
@ -401,11 +416,13 @@ static HRESULT scope_push(scope_chain_t *scope, jsdisp_t *jsobj, IDispatch *obj,
new_scope->ref = 1;
IDispatch_AddRef(obj);
if (obj)
IDispatch_AddRef(obj);
new_scope->jsobj = jsobj;
new_scope->obj = obj;
new_scope->frame = NULL;
new_scope->next = scope ? scope_addref(scope) : NULL;
new_scope->scope_index = 0;
*ret = new_scope;
return S_OK;
@ -428,7 +445,8 @@ void scope_release(scope_chain_t *scope)
if(scope->next)
scope_release(scope->next);
IDispatch_Release(scope->obj);
if (scope->obj)
IDispatch_Release(scope->obj);
heap_free(scope);
}
@ -553,13 +571,59 @@ HRESULT jsval_strict_equal(jsval_t lval, jsval_t rval, BOOL *ret)
return S_OK;
}
static HRESULT detach_scope(script_ctx_t *ctx, call_frame_t *frame, scope_chain_t *scope)
{
unsigned int i, index;
HRESULT hres;
if (!scope->frame)
return S_OK;
assert(scope->frame == frame);
scope->frame = NULL;
if (!scope->jsobj)
{
assert(!scope->obj);
if (FAILED(hres = create_object(ctx, NULL, &scope->jsobj)))
return hres;
scope->obj = to_disp(scope->jsobj);
}
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;
}
return S_OK;
}
static HRESULT detach_scope_chain(script_ctx_t *ctx, call_frame_t *frame, scope_chain_t *scope)
{
HRESULT hres;
while (1)
{
if ((hres = detach_scope(ctx, frame, scope)))
return hres;
if (scope == frame->base_scope)
break;
scope = scope->next;
}
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 i;
HRESULT hres;
if(!frame->base_scope || !frame->base_scope->frame)
@ -576,15 +640,8 @@ static HRESULT detach_variable_object(script_ctx_t *ctx, call_frame_t *frame, BO
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;
}
return S_OK;
TRACE("detaching scope chain %p, frame %p.\n", ctx->call_ctx->scope, frame);
return detach_scope_chain(ctx, frame, ctx->call_ctx->scope);
}
static BOOL lookup_global_members(script_ctx_t *ctx, BSTR identifier, exprval_t *ret)
@ -627,10 +684,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 +704,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;
@ -670,6 +727,10 @@ static HRESULT identifier_eval(script_ctx_t *ctx, BSTR identifier, exprval_t *re
return S_OK;
}
}
if (!scope->jsobj && !scope->obj)
continue;
if(scope->jsobj)
hres = jsdisp_get_id(scope->jsobj, identifier, fdexNameImplicit, &id);
else
@ -820,8 +881,54 @@ static HRESULT interp_forin(script_ctx_t *ctx)
return S_OK;
}
static HRESULT scope_init_locals(script_ctx_t *ctx)
{
call_frame_t *frame = ctx->call_ctx;
unsigned int i, off, index;
scope_chain_t *scope;
BOOL detached_vars;
HRESULT hres;
scope = frame->scope;
index = scope->scope_index;
detached_vars = !(frame->base_scope && frame->base_scope->frame);
if (!detached_vars)
{
assert(frame->base_scope->frame == frame);
frame->scope->frame = ctx->call_ctx;
}
else if (!scope->jsobj)
{
assert(!scope->obj);
if (FAILED(hres = create_object(ctx, NULL, &scope->jsobj)))
return hres;
scope->obj = to_disp(scope->jsobj);
}
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;
jsval_t val = jsval_undefined();
if (detached_vars)
{
if (FAILED(hres = jsdisp_propput_name(scope->jsobj, name, val)))
return hres;
}
else
{
off = local_off(frame, ref);
jsval_release(ctx->stack[off]);
ctx->stack[off] = val;
}
}
return S_OK;
}
/* ECMA-262 3rd Edition 12.10 */
static HRESULT interp_push_scope(script_ctx_t *ctx)
static HRESULT interp_push_with_scope(script_ctx_t *ctx)
{
IDispatch *disp;
jsval_t v;
@ -840,6 +947,25 @@ static HRESULT interp_push_scope(script_ctx_t *ctx)
return hres;
}
/* ECMA-262 10th Edition 13.3.1 */
static HRESULT interp_push_block_scope(script_ctx_t *ctx)
{
unsigned int scope_index = get_op_uint(ctx, 0);
call_frame_t *frame = ctx->call_ctx;
HRESULT hres;
TRACE("scope_index %u.\n", scope_index);
hres = scope_push(ctx->call_ctx->scope, NULL, NULL, &frame->scope);
if (FAILED(hres) || !scope_index)
return hres;
frame->scope->scope_index = scope_index;
return scope_init_locals(ctx);
}
/* ECMA-262 3rd Edition 12.10 */
static HRESULT interp_pop_scope(script_ctx_t *ctx)
{

View File

@ -75,7 +75,8 @@
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_block_scope, 1, ARG_UINT, 0) \
X(push_with_scope, 1, 0,0) \
X(regexp, 1, ARG_STR, ARG_UINT) \
X(rshift, 1, 0,0) \
X(rshift2, 1, 0,0) \
@ -180,7 +181,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 +223,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;

View File

@ -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;
}

View File

@ -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");
});