[PATCH 1/2] jscript: Handle detached scope objects 'this' in Object.toString.
Gabriel Ivăncescu
gabrielopcode at gmail.com
Mon May 16 10:39:29 CDT 2022
On 16/05/2022 14:51, Jacek Caban wrote:
> Hi Gabriel,
>
> On 5/12/22 19:09, Gabriel Ivăncescu wrote:
>> Hi Jacek,
>>
>> After analyzing the spec and the current code, I think my patches are
>> correct.
>>
>> So, the patch you attached this time fails because 'window' is not a
>> JS object (proxy), so Object.toString returns [object Object] since
>> it's a non-JS object, but it *is* passed to it properly as 'window',
>> so I don't think there's any problem? It seems it's unrelated. With
>> proxies this is fixed, actually. It's the same as
>> Object.prototype.toString.call(window) which returns [object Object]
>> right now.
>>
>> What happens in that test is that 'this' is replaced with the global
>> object (window), this.f assigns to the window, so this.f() uses window
>> as 'this', which is correct and works as expected.
>>
>> I've been reading the spec, which seems to differ quite a bit between
>> ES3 and ES5 in this matter, but it basically has similar end result
>> for us:
>>
>> 3rd Edition 10.1.6: This mentions the 'Activation Object', which in
>> our case is the "abstract" scope, whether detached or not. It mentions:
>>
>> `The activation object is purely a specification mechanism. It is
>> impossible for an ECMAScript program to access the activation object.
>> It can access members of the activation object, but not the activation
>> object itself.`
>
>
> Yes, exactly, this is why fixing it in toString does not look right, it
> should never reach this function in the first place.
>
Right, sorry, I was referring to the v2 patches I sent, which don't use
it anymore. I should have been more explicit.
>
>> ...which is what I mentioned earlier that the scope object itself
>> should never be accessible. exec_source and the part of the patch I
>> moved mentions 11.2.3.7, which says:
>>
>> `If Result(6) is an activation object, Result(7) is null. Otherwise,
>> Result(7) is the same as Result(6).`
>>
>> this means that the way it's done (checking for scope object, or NONE
>> class), it is pretty much correct and follows the spec. The GLOBAL
>> check is due to native jscript (not ES5) using two objects for the
>> "global", so it has to return the host object instead, if any, instead
>> of the js global. This might be a quirk on Windows implementation
>> rather than something in the spec, but anyway it's how it works right
>> now.
>>
>> This is why my patch adds the ES5 workaround for this; once the global
>> object *is* both 'window' and the js global, this is cleanly removed.
>>
>>
>>
>> ES5 is different. 11.2.3.6 and 11.2.3.7 basically say that:
>>
>> If the evaluated MemberExpression is a "PropertyReference", use its
>> base as 'this'. Otherwise, set it to 'undefined'.
>>
>> (there's also a case where base is an "Environment Record", where it
>> uses the "ImplicitThisValue" of the record, but in all cases it's
>> 'undefined', so it's the same thing, except for 'with' statements,
>> which are handled already IIRC).
>>
>> So what does this mean? As far as I can see, a "PropertyReference" is
>> when you do a call like:
>>
>> a.foo()
>>
>> instead of
>>
>> foo()
>>
>> Both are handled right now in interp_call_member, via exprval_call,
>> but the ref type is different. For the former, we have EXPRVAL_IDREF,
>> and for latter we have EXPRVAL_STACK_REF.
>>
>> This actually does the right thing: EXPRVAL_STACK_REF is clearly not a
>> "PropertyReference", while EXPRVAL_IDREF is clearly a
>> "PropertyReference", and I'm not sure about EXPRVAL_JSVAL. Anyway, the
>> former passes NULL to disp_call_value, which is correct for a
>> non-PropertyReference. It is fixed by resent patch 2/2, since in ES5
>> it treats it as undefined instead of null, but otherwise works as
>> intended (null is pretty much same as undefined for almost all
>> functions, except for rare ones like Object.toString).
>>
>> The latter uses disp_call which ends up calling it with proper 'this'.
>> In your patch, this is actually 'window' which is correct.
>>
>>
>> With that said, I think the patches are correct as-is. They follow
>> exactly what the spec says, except that maybe it could be moved to
>> interpreter instead of call itself, but I don't think it's worth it.
>> The code currently *does* seem to handle the spec properly, it just
>> needs some minor tweaks. Do you have other suggestions?
>
>
> Note that all of above describe caller side of things, while your
> patches modify function implementation. I think that this should be
> handled before it leaves the interpreter, as per spec.
>
>
>> Perhaps do you think it's better to handle this in exprval_call itself
>> somehow? Because that's where we *truly* have distinction between
>> PropertyReference and other kind of refs. This would need extending
>> disp_call, though, I don't think it's worth it as I said.
>
>
> Yes, I expect that it would be better, that seems to be the actual
> source of those problems. It should be as simple as adding an explicit
> jsthis argument to disp_call (although in this case it may be fine to
> ignore it for non-jsdisp cases), we already have it in disp_call_value.
> We could even consider passing jsthis as jsval to *disp_call* functions.
>
Hmm, I think we have to handle the "non-jsdisp" case since it's also
triggered when the ctx doesn't match, even if it's a jsdisp. But I found
an easier way, without extending disp_call at all: since we already
check for a scope object (or "activation object" as called in spec), we
know it's a jsdisp without builtins and so we can propget without
overhead, and then disp_call_value, without having to add DISPID_THIS
handling in disp_call...
This matches what EXPRVAL_STACK_REF does (without the propget) which is
in the same function so, I think, it fits.
One thing to note is that we'll have to keep the GLOBAL case in
exec_source, since this now would have wider reach and fail some
existing tests (e.g. `var t = dispexFunc; t = t(false);` test).
>
> Also, particular test aside, the case of 'this.f()' is still something
> that may matter and I'd rather make sure we don't break it. Note that,
> unlike activation objects, 'this' builtin global objects can be exposed
> to scripts when host doesn't provide one.
>
>
> In general, it would be good to include all tests instead of dropping
> ones that don't match your solution. For cases like 'this.f()' it's fine
> to have it with todo_wine for now.
>
>
> Thanks,
>
> Jacek
>
Alright, I'll keep the test then.
Thanks,
Gabriel
More information about the wine-devel
mailing list