Object.freezing proxies should freeze or throw?
Raul-Sebastian Mihăilă
raul.mihaila at gmail.com
Sun Aug 7 13:49:11 UTC 2016
Would be a good idea to do the test for any kind of exotic objects
(including host defined ones), not only for proxies?
On Sun, Aug 7, 2016 at 4:33 PM, Tom Van Cutsem <tomvc.be at gmail.com> wrote:
> Good catch. This appears to be a genuine spec bug.
>
> First, I thought that the problem simply was due to sloppy-mode silent
> failures, as a call to Object.isFrozen revealed that the proxy is not
> actually frozen after the call to Object.freeze. When your script is run in
> strict mode, it fails:
>
> on Chrome: Uncaught TypeError: 'set' on proxy: trap returned falsish for
> property 'x'
> on Firefox: TypeError: proxy set handler returned false for property '"x"'
>
> However, this error can easily be avoided by adding a `return true` to the
> set trap. At this point, your example still runs in strict mode, which
> worried me.
>
> The culprit is not that the proxy's "x" property can still be updated, but
> rather that Object.freeze(proxy) doesn't actually freeze the proxy yet
> still returns without throwing. Thus, a shorter testcase of the problem is:
>
> "use strict";
> const target = Object.seal({x: 2});
> const proxy = new Proxy(target, {
> defineProperty() {
> return true;
> }
> });
> Object.freeze(proxy);
> console.log(Object.isFrozen(proxy)); // expected true, but is false
>
> Tracing through the ES2015 spec, the spec 'call stack' is roughly:
>
> 19.1.2.5 Object.freeze ( O )
> calls 7.3.14 SetIntegrityLevel (O, level = "frozen")
> calls 7.3.7 DefinePropertyOrThrow (O, P = "x", desc = {configurable:
> false, writable: false})
> calls 9.5.6 [[DefineOwnProperty]] (P, Desc)
> calls 9.1.6.2 IsCompatiblePropertyDescriptor (Extensible, Desc, Current)
> calls 9.1.6.3 ValidateAndApplyPropertyDescriptor (O, P, extensible,
> Desc, current)
> where O = undefined, P = undefined, extensible = false,
> Desc = {configurable: false, writable: false},
> current = { value: 2, writable: true, enumerable: true,
> configurable: false }
>
> The ValidateAndApplyPropertyDescriptor, when called via
> IsCompatiblePropertyDescriptor, essentially checks whether it is legal to
> update an existing property descriptor `current` to a new state described
> by `Desc`, without actually performing the update.
>
> Tracing through the details of that algorithm, it turns out the above
> property descriptors are indeed compatible. It is legal to update a data
> property {writable: true, configurable: false} to {writable: false,
> configurable: false}. IIRC, this is somewhat of an exception as it is the
> only state transition allowed on a non-configurable data property.
>
> Because IsCompatiblePropertyDescriptor returns true, the
> [[DefineOwnProperty]] call on the proxy completes successfully, signaling
> to SetIntegrityLevel that the property appears to be successfully updated
> to {configurable: false, writable: false} (even though it didn't in this
> case, as the proxy's "defineProperty" trap simply returned `true` without
> actually performing the update).
>
> The quick fix appears to be to change the SetIntegrityLevel algorithm as
> follows, by updating step 10:
>
> 10. If O is a Proxy, return TestIntegrityLevel(O, "frozen"), otherwise
> return true.
>
> With this change, the call to SetIntegrityLevel ends with a call to
> TestIntegrityLevel, which will notice that O is in fact not frozen and thus
> cause the call to Object.freeze to fail.
>
> To summarize: the error here is not with the proxy invariant checks (the
> proxy is still unfrozen), but rather that because of the exceptional
> allowed state transfer from writable,non-configurable to
> non-writable,non-configurable, SetIntegrityLevel fails to detect that the
> proxy object being frozen in fact remains unfrozen.
>
> Cheers,
> Tom
>
> 2016-08-06 21:09 GMT+02:00 Raul-Sebastian Mihăilă <raul.mihaila at gmail.com>
> :
>
>> Initially posted on github (https://github.com/tc39/ecma262/issues/652),
>> but I think the mailing list is more appropriate.
>>
>> Usually methods on Object that change some internal state of the
>> objects/properties of the objects either succeed or throw. However,
>> Object.freeze can fail to freeze proxies without throwing:
>>
>> ```js
>> const target = Object.seal({x: 2});
>> const proxy = new Proxy(target, {
>> defineProperty() {
>> return true;
>> },
>>
>> set(target, prop, value) {
>> target[prop] = value;
>> }
>> });
>>
>> Object.freeze(proxy);
>> console.log(proxy.x); // 2
>> proxy.x = 100;
>> console.log(proxy.x); // 100
>> ```
>>
>> Should this be allowed?
>>
>> _______________________________________________
>> es-discuss mailing list
>> es-discuss at mozilla.org
>> https://mail.mozilla.org/listinfo/es-discuss
>>
>>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20160807/5a26bf2a/attachment-0001.html>
More information about the es-discuss
mailing list