-
Notifications
You must be signed in to change notification settings - Fork 62
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
polyfilling or virtualizing unbox
#254
Comments
I don’t think it can be pollyfilled on its own without symbols-as-weakMap-keys, and adding
Unbox doesn’t go on Record.prototype. Box is a separate immutable type. let r = #{ a: 1, b: Box(function(){}) }
let f = r.b.unbox(); |
The virtualizing question is still valid. For example, to make a compartment specific version of const compartmentMap = new WeakMap();
const OriginalBox = globalThis.Box;
globalThis.Box = function Box(target) {
let wrappedTarget = compartmentMap.get(target);
if (!wrappedTarget) {
wrappedTarget = Object.freeze({target});
compartmentMap.set(target, wrappedTarget);
}
return OriginalBox(wrappedTarget);
};
globalThis.Box.unbox = function unbox(box) {
const wrappedTarget = OriginalBox.unbox(box);
if (compartmentMap.get(wrappedTarget.target) !== wrappedTarget) {
throw new TypeError();
}
return wrappedTarget.target;
}; This would replicate the proposed realm semantics where the box for a given target created in one compartment isn't equal to the one created in another compartment, preventing unboxing unless you have access to the other compartment's cc @erights |
The additional wiring to follow the inert constructor pattern: defineProperties(GlobalThis.Box, {
prototype: {
value: OriginalBox.prototype
}
});
defineProperties(OriginalBox.prototype, {
constructor: {
value: function InertBox(target) {
throw TypeError('Try boxing with some globalThis.Box');
}
}
}); Then box = anyGlobal.Box(target);
box instanceof anyOtherGlobal.Box; // true |
Right, I only wrote the relevant code, not the rest of plumbing. @nicolo-ribaudo can we re-open to discuss Compartment virtualization of Following up on #257 (review), I think we can make This would make boxes transparent for serialization, while preserving virtualization. Adapting the previous code (and now allowing primitives to follow latest changes): // Global taming
const OriginalBox = globalThis.Box;
defineProperties(OriginalBox.prototype, {
constructor: {
value: function InertBox(target) {
throw TypeError('Try boxing with some globalThis.Box');
}
}
});
// Unique Box per compartment
const compartmentMap = new WeakMap();
function unwrap(wrappedTarget) {
if (compartmentMap.get(wrappedTarget.target) !== wrappedTarget) {
throw new TypeError();
}
return wrappedTarget.target;
}
function unbox(box) {
const maybeWrappedTarget = OriginalBox.unbox(box);
if (Object(maybeWrappedTarget) === maybeWrappedTarget) {
return unwrap(maybeWrappedTarget);
}
return maybeWrappedTarget;
}
function toString() {
const target = unwrap(this);
// Perform https://tc39.es/ecma262/#sec-ordinarytoprimitive
for (const methodName of ['toString', 'valueOf']) {
const method = target[methodName];
if (typeof method === 'function') {
const result = method.call(target);
if (Object(result) !== result) {
return result;
}
}
}
throw new TypeError();
}
function toJSON(key) {
const target = unwrap(this);
const targetToJSON = target.toJSON;
if (typeof targetToJSON === 'function') {
return targetToJSON.call(target, key);
}
return target;
}
function Box(target) {
if (Object(target) !== target) {
return OriginalBox(target);
}
let wrappedTarget = compartmentMap.get(target);
if (!wrappedTarget) {
wrappedTarget = Object.freeze({
target,
toString,
toJSON,
});
compartmentMap.set(target, wrappedTarget);
}
return OriginalBox(wrappedTarget);
}
globalThis.Box = Box;
defineProperties(globalThis.Box, {
prototype: {
value: OriginalBox.prototype
},
unbox: {
value: unbox
}
}); |
Are we saying |
At least I know understand that there is a difference between polyfilling and virtualizing. Still getting used to the idea of compartments. |
No, it would still use the
Would it make sense to perform: replacer, auto-unbox, recurse if box? It'd be similar to the way object properties are recursed, but in place instead of nested properties. Regardless, I believe that's a separate question from virtualization. As long as a |
Could the virtualisation replace and install its own implementation of These are the current stringify semantics that we landed on previously: https://github.com/tc39/proposal-record-tuple/pull/241/files - which was |
Yeah, preventing the second const c = new Compartment();
const box = c.evaluate(`Box({})`);
JSON.stringify({
foo: {
toJSON: () => box
}
});
That would be extremely heavy handed and hard to keep up to date. IMO, we should say if |
The other aspect is existing compartment implementations, if one compartment is given access to the original Box and passes a Box instance onto a compartment that has not been given original Box, it could still unbox it using |
The only existing implementation (SES shim) does not pass things on the global it doesn't know about, like
That would be a powerful endowment. The start compartment is considered powerful and it has to be mindful about what it gives out to compartment it creates. Untrusted code is not supposed to run there. |
Ugh I think I wasn't thinking clearly when I wrote the |
So, for safety, |
If we want to preserve Compartment virtualization of Box, no operation other than an explicit If |
The alternative is to somehow have an unboxing hook which is current compartment sensitive, which would be a significant departure from current patterns. Last option is to give up on Box virtualization (which would permit |
@nicolo-ribaudo, let's discuss virtualization impact here instead of #232. One last option is to have have It'd require replacing (or removing) |
If |
I suppose. If the per-compartment @erights did express concern about the semantics of auto-unboxing, and why JSON stringifying would be special. I know one concern was the bad experience developers had with |
This whole discussion is distressing. We need a simple theory of what kind of thing a box is supposed to be. Is it transparent or opaque? If it is opaque, then it should never be dereferenced transparently. If it is transparent, then it should always be, including across realms. Also, if it is transparent, why is it needed? Why cannot R&T just contain objects directly? Each of these, taken by themselves, have good answers. However, we must answer them so we have an overall coherent story. What is a box about? How should we understand it in terms of hard lines between what it makes possible and what it makes impossible? My overall general sense of box is towards it being opaque, where for each box, only its associated |
I'm still very unconvinced that one realm's Box should not be able to dereference another realm's Box - that's not the way internal slots work in the language, and template literals' per-realm state is an anomaly that i don't think we should repeat. |
My understanding was the opposite, and that any new way to mangle object graphs cross-realms should not be allowed, or at least should be explicit. Also to be pedantic, you can unbox another realm's box, if it contains a primitive. And you can always unbox another realm's box, even if it contains and object, if you have access to that other realm's |
My understanding of the issue is that this is perhaps less about what we want and more about what we have. What has been stated is that there are existing applications that are currently using realms combined with a Making cross-realm access to the contents of a Box opt-in then these existing applications will by default get the security mechanism they would need to remain secure. |
If the choices here are between preserving one and breaking one of the following invariants:
My intuition is that the first invariant is relied upon FAR, far more than the second, given how long iframes have been a thing, and this leads to my belief that we should weigh preserving the first invariant over the second. If we're unwilling to do that, then I think there's a third option, which is that we don't provide Box at all. |
Moved the Realm discussion to its own issue (#260), I'd like to keep the topic here on virtualization. |
Totally fine to move it to its own issue; but I don't think the topics are really separable, if virtualization depends on the "separate object graphs" thing that conflicts with cross-realm access. |
FWIW I agree these are inseparable, but separate issue is fine if we keep this in mind. |
You're right, there is a similar problem:
However the overall object graph is still shared between compartments, and we could decide that an explicit "containsObject" predicate is sufficient to control the sharing of objects between compartments (basically assume that a box primitive carries the same authority as its content). In my mind, the cross-realm restriction is a much stronger boundary which we can't break, unlike the compartment one. |
We removed boxes from the proposal, since it was stalled because of them. I'm closing this issue, but we'll keep track of it if we'll bring them up again as a follow on proposal. |
An interesting aspect here is how to polyfill or virtualize unbox? can it be replace? can it be patched? I'm still confused about how unbox gets into the record without breaking the invariants of a record. Obviously, that will not be the case if you have to use
Record.unbox(someRecord)
instead of the dot notation on the record.The text was updated successfully, but these errors were encountered: