-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Possible solution to override mistake without a new integrity trait #4
Comments
To clarify, this is unilaterally fixing the override mistake, just with mitigations for some observed in-the-wild reliance of the override mistake currently silently failing in sloppy mode? |
Correct.
Correct.
Incorrect. This fixes an assumption made in both sloppy and strict modes.
With the new override behavior, this code would change. Since the new built-ins don't have special behavior in |
If the override mistake can be fixed unilaterally that is ideal, but if it's not web compatible, we can still fix it without changing the object model I've suggested in the past. To summarize and simplify that suggestion, we would just have a new slot on ordinary objects, have a way to freeze and set that slot to true, and then I called it harden, but it could just be an argument to freeze: const o = Object.freeze({ x: 10 }, true);
// o is an object { [[AllowOverride]]: true, x: 10 }
const o2 = { __proto__: o };
// o has slot [[AllowOverride]] so the override mistake is disabled.
o2.x = 12; As this doesn't add any proxy traps it should be very simple to implement (just a bit on ordinary objects so it's extremely lightweight). |
@Jamesernator adding a new slot is effectively the equivalent of the overidable integrity trait we're suggesting. Why do you believe it wouldn't require a proxy trap? |
Because it adds no more power than if ordinary objects just had a private field, it would essentially just change the current check:
to:
Freezing would essentially be: function freezeWithOverride(o) {
// Prevent a communication channel if the object is already frozen
if (Object.isFrozen(o)) {
return;
}
if (#allowOverride in o) {
o.#allowOverride = true;
}
Object.freeze(o);
} |
The problem is that having no traps prevents a proxy the opportunity to reflect the state of the real target on its shadow target. Imagine the following simplified pattern that is common in membranes: const shadowToReal= new WeakMap();
const realToProxy = new WeakMap();
// Traps of the handler lookup the real target from the shadow in shadowToReal
// and reflect the shape of the real target onto the shadow as necessary to respect
// object invariants and according to any distortion that the membrane wants to apply.
const membraneHandler = {...};
const getProxyForTarget = (target) => {
let proxy = realToProxy.get(target);
if (proxy) return proxy;
// Approximation of creating a bare shadow object of the same "kind"
const shadow = typeof target === 'function' ?
function() {} :
Array.isArray(target) ?
[] :
{ __proto__: null };
shadowToReal.set(shadow, target);
proxy = new Proxy(shadow, membraneHandler);
realToProxy.set(target, proxy);
return proxy;
}; Without "is overridable" and "make overridable" proxy traps, the following can't work: const someTarget = {};
const proxy = getProxyForTarget(someTarget);
freezeWithOverride(someTarget);
stampPrivateField(proxy); // This should fail
const someOtherTarget = {};
const otherProxy = getProxyForTarget(someOtherTarget);
freezeWithOverride(otherProxy); // If distortions allow
stampPrivateField(someOtherTarget); // Should now fail |
I wasn't suggesting that the trap for preventing private field stamping can be removed. That is separate as it is actually a fundamental capability for objects. Unlike the override mistake, there is no proxy you can write to emulate such behaviour. Though yes |
Ugh sorry I got my overrides mixed up. But I believe the same argument holds for the assign override mistake. I'll follow up with some examples. |
Example: const someTarget = { foo: 3 };
const proxy = getProxyForTarget(someTarget);
freezeWithOverride(someTarget);
const someDerivedObject = { __proto__: proxy };
someDerivedObject.foo = 4; // This should work if distortions allow
const someOtherTarget = { foo: 3};
const otherProxy = getProxyForTarget(someOtherTarget);
freezeWithOverride(otherProxy); // If distortions allow
const someOtherDerivedObject = { __proto__: someOtherTarget };
someOtherDerivedObject.foo = 4; // This should work
I think this is where I disagree. Allowing assignment override is an intrinsic aspect of the object (as denoted by the private slot you're adding to described that aspect). I don't see how you would "expose it through the membrane" without making it part of the MOP traps a proxy handler implement. Assignment does not currently trigger user code except for proxy traps, and we should not expand that unfortunate exception. Built-in functions do not do any slot checking on arguments, only on receivers, which is why membranes have to make sure to properly map the receiver when reflecting to the real target. The only intrinsic that brand checks its argument is |
Based on the bug found in tc39/ecma262#1320 (comment):
o.foo = v
o
with{ value: v, enumerable: true, writable: true, configurable: true }
(like it's defining a new property without extending the parent)delete
statement when runninggetRawTag(new Uint8Array(0))
.Object.prototype.toString()
special cases with typed arrays,DataView
,ArrayBuffer
,Map
,WeakMap
,Set
,WeakSet
,Promise
, etc.value[Symbol.toStringTag] = undefined
, we must fix various builtins to still return the expected[object Foo]
values.isTypedArray
,isArrayBuffer
, etc, functions directly, and thegetTag
function indirectly by ensuring a few feature checks don't fail and cause a incomplete impl ofgetTag
to be used.The text was updated successfully, but these errors were encountered: