Some of these fixes are subtle (like the first patch) and very annoying to debug. Although the first patch looks like a hack, surprisingly, it's how the spec itself says it is! It's not even an IE quirk, but a special case in the spec.
For example, the variable name (which holds the builtin eval func) **does** matter: if it's called something other than 'eval', it gets treated differently (as if indirect), and this is verified by the tests + the spec's wording (so Microsoft's implementation follows it).
Most of the patches other than the first 2 are pretty small so they're in same MR.
From: Gabriel Ivăncescu gabrielopcode@gmail.com
Signed-off-by: Gabriel Ivăncescu gabrielopcode@gmail.com --- dlls/jscript/compile.c | 5 +++- dlls/jscript/engine.c | 47 +++++++++++++++++++++++++++++ dlls/jscript/engine.h | 1 + dlls/jscript/function.c | 6 ++++ dlls/jscript/global.c | 9 ++++-- dlls/jscript/jscript.h | 2 ++ dlls/jscript/tests/api.js | 42 ++++++++++++++++++++++++++ dlls/mshtml/tests/documentmode.js | 49 +++++++++++++++++++++++++++++++ 8 files changed, 158 insertions(+), 3 deletions(-)
diff --git a/dlls/jscript/compile.c b/dlls/jscript/compile.c index 9d7944fcf72..f81757cd93c 100644 --- a/dlls/jscript/compile.c +++ b/dlls/jscript/compile.c @@ -727,7 +727,10 @@ static HRESULT compile_call_expression(compiler_ctx_t *ctx, call_expression_t *e HRESULT hres;
if(is_memberid_expr(expr->expression->type)) { - op = OP_call_member; + if(expr->expression->type == EXPR_IDENT && !wcscmp(((identifier_expression_t*)expr->expression)->identifier, L"eval")) + op = OP_call_member_eval; + else + op = OP_call_member; extra_args = 2; hres = compile_memberid_expression(ctx, expr->expression, 0); }else { diff --git a/dlls/jscript/engine.c b/dlls/jscript/engine.c index b0557065e69..9d465f84388 100644 --- a/dlls/jscript/engine.c +++ b/dlls/jscript/engine.c @@ -1459,6 +1459,53 @@ static HRESULT interp_call_member(script_ctx_t *ctx) argn, stack_args(ctx, argn), do_ret ? &ctx->acc : NULL); }
+/* ECMA-262 5th Edition 15.1.2.1.1 */ +static HRESULT interp_call_member_eval(script_ctx_t *ctx) +{ + const unsigned argn = get_op_uint(ctx, 0); + const int do_ret = get_op_int(ctx, 1); + BOOL is_eval, free_v = FALSE; + HRESULT hres = S_OK; + jsdisp_t *jsdisp; + exprval_t ref; + jsval_t v; + + TRACE("%d %d\n", argn, do_ret); + + if(!stack_topn_exprval(ctx, argn, &ref)) + return ref.u.hres; + + clear_acc(ctx); + switch(ref.type) { + case EXPRVAL_STACK_REF: + v = ctx->stack[ref.u.off]; + break; + case EXPRVAL_IDREF: { + hres = disp_propget(ctx, ref.u.idref.disp, ref.u.idref.id, &v); + free_v = TRUE; + break; + } + case EXPRVAL_JSVAL: + v = ref.u.val; + break; + default: + assert(0); + } + if(FAILED(hres)) + return hres; + + is_eval = (is_object_instance(v) && (jsdisp = to_jsdisp(get_object(v))) && jsdisp->ctx == ctx && is_builtin_eval_func(jsdisp)); + if(free_v) + jsval_release(v); + + if(is_eval) + return builtin_eval(ctx, ctx->call_ctx, DISPATCH_METHOD | DISPATCH_JSCRIPT_CALLEREXECSSOURCE, + argn, stack_args(ctx, argn), do_ret ? &ctx->acc : NULL); + + return exprval_call(ctx, &ref, DISPATCH_METHOD | DISPATCH_JSCRIPT_CALLEREXECSSOURCE, + argn, stack_args(ctx, argn), do_ret ? &ctx->acc : NULL); +} + /* ECMA-262 3rd Edition 11.1.1 */ static HRESULT interp_this(script_ctx_t *ctx) { diff --git a/dlls/jscript/engine.h b/dlls/jscript/engine.h index f8046cbaafb..63faed2a8fc 100644 --- a/dlls/jscript/engine.h +++ b/dlls/jscript/engine.h @@ -26,6 +26,7 @@ X(bneg, 1, 0,0) \ X(call, 1, ARG_UINT, ARG_UINT) \ X(call_member,1, ARG_UINT, ARG_UINT) \ + X(call_member_eval, 1, ARG_UINT, ARG_UINT) \ X(carray, 1, ARG_UINT, 0) \ X(carray_set, 1, ARG_UINT, 0) \ X(case, 0, ARG_ADDR, 0) \ diff --git a/dlls/jscript/function.c b/dlls/jscript/function.c index 5f210a04050..018fd3955db 100644 --- a/dlls/jscript/function.c +++ b/dlls/jscript/function.c @@ -1140,6 +1140,12 @@ static HRESULT FunctionProt_value(script_ctx_t *ctx, jsval_t vthis, WORD flags, return E_NOTIMPL; }
+BOOL is_builtin_eval_func(jsdisp_t *jsdisp) +{ + return is_class(jsdisp, JSCLASS_FUNCTION) && function_from_jsdisp(jsdisp)->vtbl == &NativeFunctionVtbl && + ((NativeFunction*)function_from_jsdisp(jsdisp))->proc == JSGlobal_eval; +} + HRESULT init_function_constr(script_ctx_t *ctx, jsdisp_t *object_prototype) { NativeFunction *prot, *constr; diff --git a/dlls/jscript/global.c b/dlls/jscript/global.c index 9dd969aa334..997b2542a9e 100644 --- a/dlls/jscript/global.c +++ b/dlls/jscript/global.c @@ -130,10 +130,9 @@ static HRESULT JSGlobal_escape(script_ctx_t *ctx, jsval_t vthis, WORD flags, uns }
/* ECMA-262 3rd Edition 15.1.2.1 */ -HRESULT JSGlobal_eval(script_ctx_t *ctx, jsval_t vthis, WORD flags, unsigned argc, jsval_t *argv, +HRESULT builtin_eval(script_ctx_t *ctx, call_frame_t *frame, WORD flags, unsigned argc, jsval_t *argv, jsval_t *r) { - call_frame_t *frame = ctx->call_ctx; DWORD exec_flags = EXEC_EVAL; bytecode_t *code; const WCHAR *src; @@ -174,6 +173,12 @@ HRESULT JSGlobal_eval(script_ctx_t *ctx, jsval_t vthis, WORD flags, unsigned arg return hres; }
+HRESULT JSGlobal_eval(script_ctx_t *ctx, jsval_t vthis, WORD flags, unsigned argc, jsval_t *argv, + jsval_t *r) +{ + return builtin_eval(ctx, ctx->version < SCRIPTLANGUAGEVERSION_ES5 ? ctx->call_ctx : NULL, flags, argc, argv, r); +} + static HRESULT JSGlobal_isNaN(script_ctx_t *ctx, jsval_t vthis, WORD flags, unsigned argc, jsval_t *argv, jsval_t *r) { diff --git a/dlls/jscript/jscript.h b/dlls/jscript/jscript.h index 06c446b0c76..e91aa6dfabd 100644 --- a/dlls/jscript/jscript.h +++ b/dlls/jscript/jscript.h @@ -464,6 +464,8 @@ BOOL bool_obj_value(jsdisp_t*) DECLSPEC_HIDDEN; unsigned array_get_length(jsdisp_t*) DECLSPEC_HIDDEN; HRESULT localize_number(script_ctx_t*,DOUBLE,BOOL,jsstr_t**) DECLSPEC_HIDDEN;
+BOOL is_builtin_eval_func(jsdisp_t*) DECLSPEC_HIDDEN; +HRESULT builtin_eval(script_ctx_t*,struct _call_frame_t*,WORD,unsigned,jsval_t*,jsval_t*) DECLSPEC_HIDDEN; HRESULT JSGlobal_eval(script_ctx_t*,jsval_t,WORD,unsigned,jsval_t*,jsval_t*) DECLSPEC_HIDDEN; HRESULT Object_get_proto_(script_ctx_t*,jsval_t,WORD,unsigned,jsval_t*,jsval_t*) DECLSPEC_HIDDEN; HRESULT Object_set_proto_(script_ctx_t*,jsval_t,WORD,unsigned,jsval_t*,jsval_t*) DECLSPEC_HIDDEN; diff --git a/dlls/jscript/tests/api.js b/dlls/jscript/tests/api.js index a3b2bbcbba5..34295719f10 100644 --- a/dlls/jscript/tests/api.js +++ b/dlls/jscript/tests/api.js @@ -2446,6 +2446,48 @@ ok(bool.toLocaleString() === bool.toString(), "bool.toLocaleString() = " + bool. tmp = Object.prototype.valueOf.call(nullDisp); ok(tmp === nullDisp, "nullDisp.valueOf != nullDisp");
+(function(global) { + var i, code = "this.foobar = 1234"; + function context() {} + + var direct = [ + function() { eval(code); }, + function() { (eval)(code); }, + function() { (function(eval) { eval(code); }).call(this, eval); }, + function() { eval("eval(" + code + ")"); } + ]; + + for(i = 0; i < direct.length; i++) { + ok(!("foobar" in context), "direct[" + i + "] has foobar"); + direct[i].call(context); + ok(context.foobar === 1234, "direct[" + i + "] context foobar = " + context.foobar); + delete context.foobar; + } + + var indirect = [ + function() { (true, eval)(code); }, + function() { (eval, eval)(code); }, + function() { (true ? eval : false)(code); }, + function() { [eval][0](code); }, + function() { eval.call(this, code); }, + function() { var f; (f = eval)(code); }, + function() { var f = eval; f(code); }, + function() { (function(f) { f(code); }).call(this, eval); }, + function() { (function(f) { return f; }).call(this, eval)(code); }, + function() { (function() { arguments[0](code) }).call(this, eval); }, + function() { eval("eval")(code); } + ]; + + for(i = 0; i < indirect.length; i++) { + ok(!("foobar" in context), "indirect[" + i + "] has foobar before call"); + ok(!("foobar" in global), "indirect[" + i + "] has global foobar before call"); + indirect[i].call(context); + ok(context.foobar === 1234, "indirect[" + i + "] context foobar = " + context.foobar); + ok(!("foobar" in global), "indirect[" + i + "] has global foobar"); + delete context.foobar; + } +})(this); + ok(ActiveXObject instanceof Function, "ActiveXObject is not instance of Function"); ok(ActiveXObject.prototype instanceof Object, "ActiveXObject.prototype is not instance of Object");
diff --git a/dlls/mshtml/tests/documentmode.js b/dlls/mshtml/tests/documentmode.js index 1a9f36927d0..2872d2d4c80 100644 --- a/dlls/mshtml/tests/documentmode.js +++ b/dlls/mshtml/tests/documentmode.js @@ -756,6 +756,55 @@ sync_test("JS objs", function() { test_parses("if(false) { o.if; }", v >= 9); });
+sync_test("eval", function() { + var i, code = "this.foobar = 1234", v = document.documentMode; + function context() {} + + var direct = [ + function() { eval(code); }, + function() { (eval)(code); }, + function() { (function(eval) { eval(code); }).call(this, eval); }, + function() { eval("eval(" + code + ")"); } + ]; + + for(i = 0; i < direct.length; i++) { + ok(!("foobar" in context), "direct[" + i + "] has foobar"); + direct[i].call(context); + ok(context.foobar === 1234, "direct[" + i + "] context foobar = " + context.foobar); + delete context.foobar; + } + + var indirect = [ + function() { (true, eval)(code); }, + function() { (eval, eval)(code); }, + function() { (true ? eval : false)(code); }, + function() { [eval][0](code); }, + function() { eval.call(this, code); }, + function() { var f; (f = eval)(code); }, + function() { var f = eval; f(code); }, + function() { (function(f) { f(code); }).call(this, eval); }, + function() { (function(f) { return f; }).call(this, eval)(code); }, + function() { (function() { arguments[0](code) }).call(this, eval); }, + function() { window.eval(code); }, + function() { window["eval"](code); }, + function() { eval("eval")(code); } + ]; + + for(i = 0; i < indirect.length; i++) { + ok(!("foobar" in context), "indirect[" + i + "] has foobar before call"); + ok(!("foobar" in window), "indirect[" + i + "] has global foobar before call"); + indirect[i].call(context); + if(v < 9) { + ok(!("foobar" in window), "indirect[" + i + "] has global foobar"); + }else { + ok(!("foobar" in context), "indirect[" + i + "] has foobar"); + ok(window.foobar === 1234, "indirect[" + i + "] global foobar = " + context.foobar); + delete window.foobar; + } + delete context.foobar; + } +}); + sync_test("for..in", function() { var v = document.documentMode, found = 0, r;
From: Gabriel Ivăncescu gabrielopcode@gmail.com
Signed-off-by: Gabriel Ivăncescu gabrielopcode@gmail.com --- dlls/jscript/compile.c | 16 ++++++++-- dlls/jscript/engine.c | 6 +++- dlls/jscript/engine.h | 4 ++- dlls/jscript/function.c | 61 ++++++++++++++++++++++++++++++++++++-- dlls/jscript/tests/lang.js | 10 +++++++ 5 files changed, 90 insertions(+), 7 deletions(-)
diff --git a/dlls/jscript/compile.c b/dlls/jscript/compile.c index f81757cd93c..9107d696f29 100644 --- a/dlls/jscript/compile.c +++ b/dlls/jscript/compile.c @@ -2525,6 +2525,7 @@ static HRESULT compile_function(compiler_ctx_t *ctx, statement_t *source, functi
func->bytecode = ctx->code; func->local_ref = INVALID_LOCAL_REF; + func->shadowed_param_cnt = 0; func->scope_index = 0; ctx->func_head = ctx->func_tail = NULL; ctx->from_eval = from_eval; @@ -2566,9 +2567,18 @@ static HRESULT compile_function(compiler_ctx_t *ctx, statement_t *source, functi } }
- for(i = 0; i < func->param_cnt; i++) { - if(!find_local(ctx, func->params[i], 0) && !alloc_local(ctx, func->params[i], -i-1, 0)) - return E_OUTOFMEMORY; + func->shadowed_params_idx = compiler_alloc(ctx->code, func->param_cnt * sizeof(*func->shadowed_params_idx)); + if(!func->shadowed_params_idx) + return E_OUTOFMEMORY; + + for(i = func->param_cnt; i--;) { + if(find_local(ctx, func->params[i], 0)) + func->shadowed_params_idx[i] = func->shadowed_param_cnt++; + else { + func->shadowed_params_idx[i] = ~0; + if(!alloc_local(ctx, func->params[i], -i-1, 0)) + return E_OUTOFMEMORY; + } }
hres = visit_block_statement(ctx, NULL, source); diff --git a/dlls/jscript/engine.c b/dlls/jscript/engine.c index 9d465f84388..846518d88c5 100644 --- a/dlls/jscript/engine.c +++ b/dlls/jscript/engine.c @@ -696,7 +696,11 @@ 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); - return detach_scope_chain(ctx, frame, ctx->call_ctx->scope); + hres = detach_scope_chain(ctx, frame, ctx->call_ctx->scope); + if(FAILED(hres)) + return hres; + + return fill_shadowed_params(ctx, frame); }
static BOOL lookup_global_members(script_ctx_t *ctx, BSTR identifier, exprval_t *ret) diff --git a/dlls/jscript/engine.h b/dlls/jscript/engine.h index 63faed2a8fc..34bd3e0e81f 100644 --- a/dlls/jscript/engine.h +++ b/dlls/jscript/engine.h @@ -172,8 +172,9 @@ typedef struct _function_code_t { int func_id; /* -1 if not a function */ } *variables;
- unsigned param_cnt; + unsigned param_cnt, shadowed_param_cnt; BSTR *params; + unsigned *shadowed_params_idx; /* index for each param into arguments object's shadowed_params, ~0 if not shadowed */
local_ref_scopes_t *local_scopes; unsigned local_scope_count; @@ -305,3 +306,4 @@ HRESULT exec_source(script_ctx_t*,DWORD,bytecode_t*,function_code_t*,scope_chain HRESULT create_source_function(script_ctx_t*,bytecode_t*,function_code_t*,scope_chain_t*,jsdisp_t**) DECLSPEC_HIDDEN; HRESULT setup_arguments_object(script_ctx_t*,call_frame_t*) DECLSPEC_HIDDEN; void detach_arguments_object(jsdisp_t*) DECLSPEC_HIDDEN; +HRESULT fill_shadowed_params(script_ctx_t*,call_frame_t*) DECLSPEC_HIDDEN; diff --git a/dlls/jscript/function.c b/dlls/jscript/function.c index 018fd3955db..a4c2d2f09d5 100644 --- a/dlls/jscript/function.c +++ b/dlls/jscript/function.c @@ -67,6 +67,7 @@ typedef struct { jsdisp_t jsdisp; InterpretedFunction *function; jsval_t *buf; + jsval_t *shadowed_params; call_frame_t *frame; unsigned argc; } ArgumentsInstance; @@ -114,6 +115,13 @@ static void Arguments_destructor(jsdisp_t *jsdisp) free(arguments->buf); }
+ if(arguments->shadowed_params) { + unsigned i, cnt = arguments->frame->function->shadowed_param_cnt; + for(i = 0; i < cnt; i++) + jsval_release(arguments->shadowed_params[i]); + free(arguments->shadowed_params); + } + if(arguments->function) jsdisp_release(&arguments->function->function.dispex); free(arguments); @@ -131,6 +139,8 @@ static jsval_t *get_argument_ref(ArgumentsInstance *arguments, unsigned idx) return arguments->buf + idx; if(arguments->frame->base_scope->frame || idx >= arguments->frame->function->param_cnt) return arguments->jsdisp.ctx->stack + arguments->frame->arguments_off + idx; + if(arguments->shadowed_params && arguments->frame->function->shadowed_params_idx[idx] != ~0) + return arguments->shadowed_params + arguments->frame->function->shadowed_params_idx[idx]; return NULL; }
@@ -144,7 +154,6 @@ static HRESULT Arguments_idx_get(jsdisp_t *jsdisp, unsigned idx, jsval_t *r) if((ref = get_argument_ref(arguments, idx))) return jsval_copy(*ref, r);
- /* FIXME: Accessing by name won't work for duplicated argument names */ return jsdisp_propget_name(arguments->frame->base_scope->jsobj, arguments->function->func_code->params[idx], r); } @@ -168,7 +177,6 @@ static HRESULT Arguments_idx_put(jsdisp_t *jsdisp, unsigned idx, jsval_t val) return S_OK; }
- /* FIXME: Accessing by name won't work for duplicated argument names */ return jsdisp_propput_name(arguments->frame->base_scope->jsobj, arguments->function->func_code->params[idx], val); } @@ -187,6 +195,15 @@ static HRESULT Arguments_gc_traverse(struct gc_ctx *gc_ctx, enum gc_traverse_op } }
+ if(arguments->shadowed_params) { + unsigned cnt = arguments->frame->function->shadowed_param_cnt; + for(i = 0; i < cnt; i++) { + hres = gc_process_linked_val(gc_ctx, op, jsdisp, &arguments->shadowed_params[i]); + if(FAILED(hres)) + return hres; + } + } + return gc_process_linked_obj(gc_ctx, op, jsdisp, &arguments->function->function.dispex, (void**)&arguments->function); }
@@ -204,6 +221,7 @@ static const builtin_info_t Arguments_info = {
HRESULT setup_arguments_object(script_ctx_t *ctx, call_frame_t *frame) { + unsigned i, shadowed_param_cnt; ArgumentsInstance *args; HRESULT hres;
@@ -211,8 +229,15 @@ HRESULT setup_arguments_object(script_ctx_t *ctx, call_frame_t *frame) if(!args) return E_OUTOFMEMORY;
+ shadowed_param_cnt = frame->function->shadowed_param_cnt; + if(shadowed_param_cnt && !(args->shadowed_params = malloc(shadowed_param_cnt * sizeof(*args->shadowed_params)))) { + free(args); + return E_OUTOFMEMORY; + } + hres = init_dispex_from_constr(&args->jsdisp, ctx, &Arguments_info, ctx->object_constr); if(FAILED(hres)) { + free(args->shadowed_params); free(args); return hres; } @@ -220,6 +245,8 @@ HRESULT setup_arguments_object(script_ctx_t *ctx, call_frame_t *frame) args->function = (InterpretedFunction*)function_from_jsdisp(jsdisp_addref(frame->function_instance)); args->argc = frame->argc; args->frame = frame; + for(i = 0; i < shadowed_param_cnt; i++) + args->shadowed_params[i] = jsval_undefined();
hres = jsdisp_define_data_property(&args->jsdisp, L"length", PROPF_WRITABLE | PROPF_CONFIGURABLE, jsval_number(args->argc)); @@ -242,11 +269,13 @@ void detach_arguments_object(jsdisp_t *args_disp) ArgumentsInstance *arguments = arguments_from_jsdisp(args_disp); call_frame_t *frame = arguments->frame; const BOOL on_stack = frame->base_scope->frame == frame; + jsval_t *shadowed_params = arguments->shadowed_params; HRESULT hres;
/* Reset arguments value to cut the reference cycle. Note that since all activation contexts have * their own arguments property, it's impossible to use prototype's one during name lookup */ jsdisp_propput_name(frame->base_scope->jsobj, L"arguments", jsval_undefined()); + arguments->shadowed_params = NULL; arguments->frame = NULL;
/* Don't bother coppying arguments if call frame holds the last reference. */ @@ -256,8 +285,11 @@ void detach_arguments_object(jsdisp_t *args_disp) int i;
for(i = 0; i < arguments->argc ; i++) { + hres = S_OK; if(on_stack || i >= frame->function->param_cnt) hres = jsval_copy(arguments->jsdisp.ctx->stack[frame->arguments_off + i], arguments->buf+i); + else if(frame->function->shadowed_params_idx[i] != ~0) + arguments->buf[i] = shadowed_params[frame->function->shadowed_params_idx[i]]; else hres = jsdisp_propget_name(frame->base_scope->jsobj, frame->function->params[i], arguments->buf+i); if(FAILED(hres)) @@ -267,11 +299,36 @@ void detach_arguments_object(jsdisp_t *args_disp) ERR("out of memory\n"); arguments->argc = 0; } + }else if(shadowed_params) { + unsigned i, cnt = frame->function->shadowed_param_cnt; + for(i = 0; i < cnt; i++) + jsval_release(shadowed_params[i]); } + free(shadowed_params);
jsdisp_release(frame->arguments_obj); }
+HRESULT fill_shadowed_params(script_ctx_t *ctx, call_frame_t *frame) +{ + ArgumentsInstance *arguments = arguments_from_jsdisp(frame->arguments_obj); + HRESULT hres; + unsigned i; + + if(!frame->function->shadowed_param_cnt || !frame->arguments_obj) + return S_OK; + + for(i = 0; i < frame->function->param_cnt; i++) { + if(frame->function->shadowed_params_idx[i] == ~0) + continue; + hres = jsval_copy(ctx->stack[frame->arguments_off + i], arguments->shadowed_params + arguments->frame->function->shadowed_params_idx[i]); + if(FAILED(hres)) + return hres; + } + + return S_OK; +} + HRESULT Function_invoke(jsdisp_t *func_this, jsval_t vthis, WORD flags, unsigned argc, jsval_t *argv, jsval_t *r) { FunctionInstance *function; diff --git a/dlls/jscript/tests/lang.js b/dlls/jscript/tests/lang.js index 5c201d5bf26..adb5be67c8e 100644 --- a/dlls/jscript/tests/lang.js +++ b/dlls/jscript/tests/lang.js @@ -306,6 +306,16 @@ argumentsTest(); ok(arguments === 1, "arguments = " + arguments); })();
+// duplicated argument names are shadowed by the last argument with the same name +(function(a, a, b, c) { + ok(a === 2, "function(a, a, b, c) a = " + a); + ok(b === 3, "function(a, a, b, c) b = " + b); + ok(c === 4, "function(a, a, b, c) c = " + c); + a = 42; + ok(arguments[0] === 1, "function(a, a, b, c) arguments[0] = " + arguments[0]); + ok(arguments[1] === 42, "function(a, a, b, c) arguments[1] = " + arguments[1]); +})(1, 2, 3, 4); + (function callAsExprTest() { ok(callAsExprTest.arguments === null, "callAsExprTest.arguments = " + callAsExprTest.arguments); })(1,2);
From: Gabriel Ivăncescu gabrielopcode@gmail.com
Signed-off-by: Gabriel Ivăncescu gabrielopcode@gmail.com --- dlls/jscript/function.c | 49 ++++++++++++++++++++++++++++++- dlls/jscript/tests/api.js | 1 + dlls/mshtml/tests/documentmode.js | 21 +++++++++++++ dlls/mshtml/tests/es5.js | 12 ++++++++ 4 files changed, 82 insertions(+), 1 deletion(-)
diff --git a/dlls/jscript/function.c b/dlls/jscript/function.c index a4c2d2f09d5..3910b0d3a1b 100644 --- a/dlls/jscript/function.c +++ b/dlls/jscript/function.c @@ -346,6 +346,26 @@ HRESULT Function_invoke(jsdisp_t *func_this, jsval_t vthis, WORD flags, unsigned return function->vtbl->call(function->dispex.ctx, function, vthis, flags, argc, argv, r); }
+static HRESULT Function_get_caller(script_ctx_t *ctx, jsdisp_t *jsthis, jsval_t *r) +{ + FunctionInstance *function = function_from_jsdisp(jsthis); + call_frame_t *frame; + + TRACE("%p\n", jsthis); + + for(frame = ctx->call_ctx; frame; frame = frame->prev_frame) { + if(frame->function_instance == &function->dispex) { + if(!frame->prev_frame || !frame->prev_frame->function_instance) + break; + *r = jsval_obj(jsdisp_addref(frame->prev_frame->function_instance)); + return S_OK; + } + } + + *r = jsval_null(); + return S_OK; +} + static HRESULT Function_get_length(script_ctx_t *ctx, jsdisp_t *jsthis, jsval_t *r) { TRACE("%p\n", jsthis); @@ -641,6 +661,7 @@ static const builtin_prop_t Function_props[] = { {L"arguments", NULL, 0, Function_get_arguments}, {L"bind", Function_bind, PROPF_METHOD|PROPF_ES5|1}, {L"call", Function_call, PROPF_METHOD|1}, + {L"caller", NULL, PROPF_HTML, Function_get_caller}, {L"length", NULL, 0, Function_get_length}, {L"toString", Function_toString, PROPF_METHOD} }; @@ -660,6 +681,7 @@ static const builtin_info_t Function_info = {
static const builtin_prop_t FunctionInst_props[] = { {L"arguments", NULL, 0, Function_get_arguments}, + {L"caller", NULL, PROPF_HTML, Function_get_caller}, {L"length", NULL, 0, Function_get_length} };
@@ -845,6 +867,7 @@ static HRESULT InterpretedFunction_set_prototype(script_ctx_t *ctx, jsdisp_t *js
static const builtin_prop_t InterpretedFunction_props[] = { {L"arguments", NULL, 0, Function_get_arguments}, + {L"caller", NULL, PROPF_HTML, Function_get_caller}, {L"length", NULL, 0, Function_get_length}, {L"prototype", NULL, 0, InterpretedFunction_get_prototype, InterpretedFunction_set_prototype} }; @@ -965,6 +988,30 @@ HRESULT create_source_function(script_ctx_t *ctx, bytecode_t *code, function_cod return S_OK; }
+static HRESULT BindFunction_get_caller(script_ctx_t *ctx, jsdisp_t *jsthis, jsval_t *r) +{ + return JS_E_INVALID_ACTION; +} + +static const builtin_prop_t BindFunction_props[] = { + {L"arguments", NULL, 0, Function_get_arguments}, + {L"caller", NULL, 0, BindFunction_get_caller}, + {L"length", NULL, 0, Function_get_length} +}; + +static const builtin_info_t BindFunction_info = { + JSCLASS_FUNCTION, + Function_value, + ARRAY_SIZE(BindFunction_props), + BindFunction_props, + Function_destructor, + NULL, + NULL, + NULL, + NULL, + Function_gc_traverse +}; + static HRESULT BindFunction_call(script_ctx_t *ctx, FunctionInstance *func, jsval_t vthis, unsigned flags, unsigned argc, jsval_t *argv, jsval_t *r) { @@ -1051,7 +1098,7 @@ static HRESULT create_bind_function(script_ctx_t *ctx, FunctionInstance *target, BindFunction *function; HRESULT hres;
- hres = create_function(ctx, NULL, &BindFunctionVtbl, FIELD_OFFSET(BindFunction, args[argc]), PROPF_METHOD, + hres = create_function(ctx, &BindFunction_info, &BindFunctionVtbl, FIELD_OFFSET(BindFunction, args[argc]), PROPF_METHOD, FALSE, NULL, (void**)&function); if(FAILED(hres)) return hres; diff --git a/dlls/jscript/tests/api.js b/dlls/jscript/tests/api.js index 34295719f10..78bfd164b90 100644 --- a/dlls/jscript/tests/api.js +++ b/dlls/jscript/tests/api.js @@ -286,6 +286,7 @@ ok(unescape(escape(tmp)) === tmp, "unescape(escape('" + tmp + "')) = " + unescap ok(Object.prototype.hasOwnProperty('toString'), "Object.prototype.hasOwnProperty('toString') is false"); ok(Object.prototype.hasOwnProperty('isPrototypeOf'), "Object.prototype.hasOwnProperty('isPrototypeOf') is false"); ok(Function.prototype.hasOwnProperty('call'), "Function.prototype.hasOwnProperty('call') is false"); +ok(!Function.prototype.hasOwnProperty('caller'), "Function.prototype.hasOwnProperty('caller') is true");
Object(); new Object(); diff --git a/dlls/mshtml/tests/documentmode.js b/dlls/mshtml/tests/documentmode.js index 2872d2d4c80..77cbae6dd92 100644 --- a/dlls/mshtml/tests/documentmode.js +++ b/dlls/mshtml/tests/documentmode.js @@ -838,6 +838,27 @@ sync_test("for..in", function() { ok(found === 1, "ondragstart enumerated " + found + " times in document after set to empty string"); });
+sync_test("function caller", function() { + ok(Function.prototype.hasOwnProperty("caller"), "caller not prop of Function.prototype"); + + function test_caller(expected_caller, stop) { + ok(test_caller.caller === expected_caller, "caller = " + test_caller.caller); + if(stop) return; + function nested() { + ok(nested.caller === test_caller, "nested caller = " + nested.caller); + test_caller(nested, true); + ok(test_caller.caller === expected_caller, "caller within nested = " + test_caller.caller); + } + nested(); + ok(test_caller.caller === expected_caller, "caller after nested = " + test_caller.caller); + } + ok(test_caller.hasOwnProperty("caller"), "caller not prop of test_caller"); + ok(test_caller.caller === null, "test_caller.caller = " + test_caller.caller); + + function f1() { test_caller(f1); } f1(); + function f2() { test_caller(f2); } f2(); +}); + sync_test("elem_by_id", function() { document.body.innerHTML = '<form id="testid" name="testname"></form>'; var v = document.documentMode, found, i; diff --git a/dlls/mshtml/tests/es5.js b/dlls/mshtml/tests/es5.js index 1ed3a64bcba..43723a68d00 100644 --- a/dlls/mshtml/tests/es5.js +++ b/dlls/mshtml/tests/es5.js @@ -18,6 +18,7 @@
var E_INVALIDARG = 0x80070057; var JS_E_PROP_DESC_MISMATCH = 0x800a01bd; +var JS_E_INVALID_ACTION = 0x800a01bd; var JS_E_NUMBER_EXPECTED = 0x800a1389; var JS_E_FUNCTION_EXPECTED = 0x800a138a; var JS_E_DATE_EXPECTED = 0x800a138e; @@ -525,11 +526,14 @@ sync_test("getOwnPropertyDescriptor", function() { (function() { test_own_data_prop_desc(arguments, "length", true, false, true); test_own_data_prop_desc(arguments, "callee", true, false, true); + ok(!("caller" in arguments), "caller in arguments"); })();
test_own_data_prop_desc(String, "prototype", false, false, false); test_own_data_prop_desc(function(){}, "prototype", true, false, false); + test_own_data_prop_desc(function(){}, "caller", false, false, false); test_own_data_prop_desc(Function, "prototype", false, false, false); + test_own_data_prop_desc(Function.prototype, "caller", false, false, false); test_own_data_prop_desc(String.prototype, "constructor", true, false, true);
try { @@ -1136,6 +1140,14 @@ sync_test("bind", function() { r = f.call(o2); ok(r === 1, "r = " + r);
+ try { + f.caller; + ok(false, "expected exception getting f.caller"); + }catch(ex) { + var n = ex.number >>> 0; + ok(n === JS_E_INVALID_ACTION, "f.caller threw " + n); + } + f = (function() { ok(this === o, "this != o"); ok(arguments.length === 1, "arguments.length = " + arguments.length);
From: Gabriel Ivăncescu gabrielopcode@gmail.com
Signed-off-by: Gabriel Ivăncescu gabrielopcode@gmail.com --- dlls/jscript/function.c | 7 ++++++- dlls/mshtml/tests/es5.js | 9 +++++++++ 2 files changed, 15 insertions(+), 1 deletion(-)
diff --git a/dlls/jscript/function.c b/dlls/jscript/function.c index 3910b0d3a1b..a95aa5077fd 100644 --- a/dlls/jscript/function.c +++ b/dlls/jscript/function.c @@ -988,13 +988,18 @@ HRESULT create_source_function(script_ctx_t *ctx, bytecode_t *code, function_cod return S_OK; }
+static HRESULT BindFunction_get_arguments(script_ctx_t *ctx, jsdisp_t *jsthis, jsval_t *r) +{ + return JS_E_INVALID_ACTION; +} + static HRESULT BindFunction_get_caller(script_ctx_t *ctx, jsdisp_t *jsthis, jsval_t *r) { return JS_E_INVALID_ACTION; }
static const builtin_prop_t BindFunction_props[] = { - {L"arguments", NULL, 0, Function_get_arguments}, + {L"arguments", NULL, 0, BindFunction_get_arguments}, {L"caller", NULL, 0, BindFunction_get_caller}, {L"length", NULL, 0, Function_get_length} }; diff --git a/dlls/mshtml/tests/es5.js b/dlls/mshtml/tests/es5.js index 43723a68d00..fb9b710c70c 100644 --- a/dlls/mshtml/tests/es5.js +++ b/dlls/mshtml/tests/es5.js @@ -532,8 +532,10 @@ sync_test("getOwnPropertyDescriptor", function() { test_own_data_prop_desc(String, "prototype", false, false, false); test_own_data_prop_desc(function(){}, "prototype", true, false, false); test_own_data_prop_desc(function(){}, "caller", false, false, false); + test_own_data_prop_desc(function(){}, "arguments", false, false, false); test_own_data_prop_desc(Function, "prototype", false, false, false); test_own_data_prop_desc(Function.prototype, "caller", false, false, false); + test_own_data_prop_desc(Function.prototype, "arguments", false, false, false); test_own_data_prop_desc(String.prototype, "constructor", true, false, true);
try { @@ -1140,6 +1142,13 @@ sync_test("bind", function() { r = f.call(o2); ok(r === 1, "r = " + r);
+ try { + f.arguments; + ok(false, "expected exception getting f.arguments"); + }catch(ex) { + var n = ex.number >>> 0; + ok(n === JS_E_INVALID_ACTION, "f.arguments threw " + n); + } try { f.caller; ok(false, "expected exception getting f.caller");
From: Gabriel Ivăncescu gabrielopcode@gmail.com
Signed-off-by: Gabriel Ivăncescu gabrielopcode@gmail.com --- dlls/jscript/function.c | 2 +- dlls/jscript/tests/api.js | 1 + 2 files changed, 2 insertions(+), 1 deletion(-)
diff --git a/dlls/jscript/function.c b/dlls/jscript/function.c index a95aa5077fd..e240f7325e4 100644 --- a/dlls/jscript/function.c +++ b/dlls/jscript/function.c @@ -658,7 +658,7 @@ static HRESULT Function_gc_traverse(struct gc_ctx *gc_ctx, enum gc_traverse_op o
static const builtin_prop_t Function_props[] = { {L"apply", Function_apply, PROPF_METHOD|2}, - {L"arguments", NULL, 0, Function_get_arguments}, + {L"arguments", NULL, PROPF_HTML, Function_get_arguments}, {L"bind", Function_bind, PROPF_METHOD|PROPF_ES5|1}, {L"call", Function_call, PROPF_METHOD|1}, {L"caller", NULL, PROPF_HTML, Function_get_caller}, diff --git a/dlls/jscript/tests/api.js b/dlls/jscript/tests/api.js index 78bfd164b90..5cefdbec232 100644 --- a/dlls/jscript/tests/api.js +++ b/dlls/jscript/tests/api.js @@ -287,6 +287,7 @@ ok(Object.prototype.hasOwnProperty('toString'), "Object.prototype.hasOwnProperty ok(Object.prototype.hasOwnProperty('isPrototypeOf'), "Object.prototype.hasOwnProperty('isPrototypeOf') is false"); ok(Function.prototype.hasOwnProperty('call'), "Function.prototype.hasOwnProperty('call') is false"); ok(!Function.prototype.hasOwnProperty('caller'), "Function.prototype.hasOwnProperty('caller') is true"); +ok(!Function.prototype.hasOwnProperty('arguments'), "Function.prototype.hasOwnProperty('arguments') is true");
Object(); new Object();
From: Gabriel Ivăncescu gabrielopcode@gmail.com
Signed-off-by: Gabriel Ivăncescu gabrielopcode@gmail.com --- dlls/jscript/number.c | 2 +- dlls/jscript/tests/api.js | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-)
diff --git a/dlls/jscript/number.c b/dlls/jscript/number.c index a5ab775a5d5..00456daaf53 100644 --- a/dlls/jscript/number.c +++ b/dlls/jscript/number.c @@ -525,7 +525,7 @@ static HRESULT Number_toPrecision(script_ctx_t *ctx, jsval_t vthis, WORD flags, if(FAILED(hres)) return hres;
- if(argc) { + if(argc && (ctx->version < 2 || !is_undefined(argv[0]))) { hres = to_int32(ctx, argv[0], &prec); if(FAILED(hres)) return hres; diff --git a/dlls/jscript/tests/api.js b/dlls/jscript/tests/api.js index 5cefdbec232..d73fb2fe6b3 100644 --- a/dlls/jscript/tests/api.js +++ b/dlls/jscript/tests/api.js @@ -1432,6 +1432,10 @@ tmp = (new Number(1.182e30)).toPrecision(5); ok(tmp == "1.1820e+30", "num(1.182e30)).toPrecision(5) = " + tmp); tmp = (new Number(1.123)).toPrecision(); ok(tmp == "1.123", "num(1.123).toPrecision() = " + tmp); +if(invokeVersion >= 2) { + tmp = (new Number(1.123)).toPrecision(undefined); + ok(tmp == "1.123", "num(1.123).toPrecision(undefined) = " + tmp); +}
ok(Number() === 0, "Number() = " + Number()); ok(Number(false) === 0, "Number(false) = " + Number(false)); @@ -2706,6 +2710,8 @@ testException(function() {Number.prototype.toLocaleString.call(null);}, "E_NOT_N testException(function() {(new Number(3)).toString(1);}, "E_INVALID_CALL_ARG"); testException(function() {(new Number(3)).toFixed(21);}, "E_FRACTION_DIGITS_OUT_OF_RANGE"); testException(function() {(new Number(1)).toPrecision(0);}, "E_PRECISION_OUT_OF_RANGE"); +if(invokeVersion < 2) + testException(function() {(new Number(1)).toPrecision(undefined);}, "E_PRECISION_OUT_OF_RANGE"); testException(function() {not_existing_variable.something();}, "E_UNDEFINED"); testException(function() {date();}, "E_NOT_FUNC"); testException(function() {arr();}, "E_NOT_FUNC");
From: Gabriel Ivăncescu gabrielopcode@gmail.com
Signed-off-by: Gabriel Ivăncescu gabrielopcode@gmail.com --- dlls/jscript/number.c | 2 +- dlls/jscript/tests/api.js | 1 + dlls/mshtml/tests/es5.js | 3 +++ 3 files changed, 5 insertions(+), 1 deletion(-)
diff --git a/dlls/jscript/number.c b/dlls/jscript/number.c index 00456daaf53..27c3d5ecfee 100644 --- a/dlls/jscript/number.c +++ b/dlls/jscript/number.c @@ -239,7 +239,7 @@ static HRESULT Number_toString(script_ctx_t *ctx, jsval_t vthis, WORD flags, uns if(FAILED(hres)) return hres;
- if(argc) { + if(argc && (ctx->version < SCRIPTLANGUAGEVERSION_ES5 || !is_undefined(argv[0]))) { hres = to_int32(ctx, argv[0], &radix); if(FAILED(hres)) return hres; diff --git a/dlls/jscript/tests/api.js b/dlls/jscript/tests/api.js index d73fb2fe6b3..4005107b009 100644 --- a/dlls/jscript/tests/api.js +++ b/dlls/jscript/tests/api.js @@ -2708,6 +2708,7 @@ testException(function() {Number.prototype.toFixed.call(arr);}, "E_NOT_NUM"); testException(function() {Number.prototype.toLocaleString.call(arr);}, "E_NOT_NUM"); testException(function() {Number.prototype.toLocaleString.call(null);}, "E_NOT_NUM"); testException(function() {(new Number(3)).toString(1);}, "E_INVALID_CALL_ARG"); +testException(function() {(new Number(3)).toString(undefined);}, "E_INVALID_CALL_ARG"); testException(function() {(new Number(3)).toFixed(21);}, "E_FRACTION_DIGITS_OUT_OF_RANGE"); testException(function() {(new Number(1)).toPrecision(0);}, "E_PRECISION_OUT_OF_RANGE"); if(invokeVersion < 2) diff --git a/dlls/mshtml/tests/es5.js b/dlls/mshtml/tests/es5.js index fb9b710c70c..e86ab1dbe77 100644 --- a/dlls/mshtml/tests/es5.js +++ b/dlls/mshtml/tests/es5.js @@ -1127,6 +1127,9 @@ sync_test("toString", function() { obj = Object.create(Number.prototype); tmp = Object.prototype.toString.call(obj); ok(tmp === "[object Object]", "toString.call(Object.create(Number.prototype)) = " + tmp); + + tmp = (new Number(303)).toString(undefined); + ok(tmp === "303", "Number 303 toString(undefined) = " + tmp); });
sync_test("bind", function() {
Jacek Caban (@jacek) commented about dlls/jscript/compile.c:
HRESULT hres; if(is_memberid_expr(expr->expression->type)) {
op = OP_call_member;
if(expr->expression->type == EXPR_IDENT && !wcscmp(((identifier_expression_t*)expr->expression)->identifier, L"eval"))
op = OP_call_member_eval;
I think it would be cleaner to have something like `OP_call_eval`, which knows the identifier anyway, and skip `compile_memberid_expression` entirely in this case.
Jacek Caban (@jacek) commented about dlls/jscript/engine.c:
+/* ECMA-262 5th Edition 15.1.2.1.1 */ +static HRESULT interp_call_member_eval(script_ctx_t *ctx) +{
- const unsigned argn = get_op_uint(ctx, 0);
- const int do_ret = get_op_int(ctx, 1);
- BOOL is_eval, free_v = FALSE;
- HRESULT hres = S_OK;
- jsdisp_t *jsdisp;
- exprval_t ref;
- jsval_t v;
- TRACE("%d %d\n", argn, do_ret);
- if(!stack_topn_exprval(ctx, argn, &ref))
return ref.u.hres;
This could be just `identifier_eval`.
Jacek Caban (@jacek) commented about dlls/jscript/engine.c:
- clear_acc(ctx);
- switch(ref.type) {
case EXPRVAL_STACK_REF:
v = ctx->stack[ref.u.off];
break;
case EXPRVAL_IDREF: {
hres = disp_propget(ctx, ref.u.idref.disp, ref.u.idref.id, &v);
free_v = TRUE;
break;
}
case EXPRVAL_JSVAL:
v = ref.u.val;
break;
default:
assert(0);
- }
That's `exprval_propget`.
Jacek Caban (@jacek) commented about dlls/jscript/tests/api.js:
tmp = Object.prototype.valueOf.call(nullDisp); ok(tmp === nullDisp, "nullDisp.valueOf != nullDisp");
+(function(global) {
- var i, code = "this.foobar = 1234";
- function context() {}
- var direct = [
function() { eval(code); },
function() { (eval)(code); },
function() { (function(eval) { eval(code); }).call(this, eval); },
function() { eval("eval(" + code + ")"); }
- ];
- for(i = 0; i < direct.length; i++) {
ok(!("foobar" in context), "direct[" + i + "] has foobar");
Is there any reason to use a function for the context? Could we just use something like `context = {};` here and not have to worry about cleaning it?
Jacek Caban (@jacek) commented about dlls/jscript/engine.h:
int func_id; /* -1 if not a function */ } *variables;
- unsigned param_cnt;
- unsigned param_cnt, shadowed_param_cnt; BSTR *params;
- unsigned *shadowed_params_idx; /* index for each param into arguments object's shadowed_params, ~0 if not shadowed */
That all seems a workaround. The actual problem is that arguments object stores arguments from detached scopes in variable object, but it doesn't have to. It could just allocate an array and store them there. It may need extra care to make sure that lookup by name keeps working correctly, but it should be doable unless I'm missing something.
On Fri Jun 2 13:51:50 2023 +0000, Jacek Caban wrote:
That all seems a workaround. The actual problem is that arguments object stores arguments from detached scopes in variable object, but it doesn't have to. It could just allocate an array and store them there. It may need extra care to make sure that lookup by name keeps working correctly, but it should be doable unless I'm missing something.
Yeah that's the problem, the name lookup. It would need special casing where it's done, which I think is more work than just special casing it into the arguments object itself (since it's specific to it, anyway).
BTW is it just detached scopes? Doesn't it apply also for accessing arguments' variables via name (as "local vars")? Modifying an arg syncs it with the arguments object.
On Fri Jun 2 14:20:08 2023 +0000, Gabriel Ivăncescu wrote:
Yeah that's the problem, the name lookup. It would need special casing where it's done, which I think is more work than just special casing it into the arguments object itself (since it's specific to it, anyway). BTW is it just detached scopes? Doesn't it apply also for accessing arguments' variables via name (as "local vars")? Modifying an arg syncs it with the arguments object.
I'm not sure what you mean, as long as arguments are on the stack, both accessing by name and via arguments object operate on stack.
On Fri Jun 2 14:44:06 2023 +0000, Jacek Caban wrote:
I'm not sure what you mean, as long as arguments are on the stack, both accessing by name and via arguments object operate on stack.
Oh, I misunderstood how the code worked, now I get it, thanks. I'll see if I can simplify it, then.