tc39/proposal-compartments

Consider hooks for Array templatedness

Closed this issue · 20 comments

koto commented

https://github.com/tc39/proposal-array-is-template-object introduces Array(.prototype?).isTemplateObject. Compartments may need a hook in order to enable virtualizing the isTemplateObject brand check.

Originally posted by @koto in tc39/proposal-array-is-template-object#10 (comment)

koto commented

From @erights:

The compartments hooks are plausible. Let's work on what Compartment hooks would look like to address these concerns. It would not be an internal slot though, as that would not enable a per-compartment difference.

To my understanding, you just need a distinguisher. Wouldn't it be sufficient to store a compartment in the [[TemplateObjectCompartment]] slot of the Array object when creating the template object and then call the compartment hook like so:

isTemplateHook(array maybeTemplateObject, boolean fromSameCompartment)?

FWIW, it's not entirely clear that templatedness needs a compartments hook, as Compartments seem to be about enabling virtualization of host behavior (which Array.isTemplateObject is not).

This is a good direction.

Why the boolean flag? A compartment should be able to detect the templatedness of its own templates. It should not be able to detect the templatedness of other templates.

As a templatedness test, I wouldn't name it *Hook. Just compartment.isTemplate(maybeTemplate) is a good predicate for checking compartment-relative templatedness.

Why the internal slot? The spec already has the semantic state we need --- each realm's internal set of its own template objects. Without any compatibility risk, we can make that a per-compartment set rather than a per-realm set. Then, the query would just be checking membership in that compartment's internal set.

Progress!

hmm is there a summary of why this is needed? 👀

koto commented

@erights

Why the boolean flag? A compartment should be able to detect the templatedness of its own templates. It should not be able to detect the templatedness of other templates.

The templatedness check (without compartments) is realm-agnostic (tc39/proposal-array-is-template-object#10) i.e. another Realm templates pass it, hence the new [[TemplateObject]] instead of reusing [[TemplateMap]] for the check. I agree moving the [[TemplateMap]] to the compartments from realms makes sense, but the regular (i.e. not compartmentalized) isTemplateObjectis not checking the [[TemplateMap]], so it doesn't change much?

I wanted to preserve this semantic for code running inside a Compartment. If this is not a requirement (and the code running inside a compartment will always be more restricted), then the signature could be changed. What is the expected behavior we're looking for in the following cases (for the Array.[prototype].isTemplateObject running inside a compartment)?

  1. a cross-realm template
  2. same-realm template
    a. created outside of compartment
    b. created in a different compartment
    c. created in the same compartment

IIUC 1, 2a and 2b should fail unconditionally, and 2c should look in the compartment-relative[[TemplateMap]], and additionally consult the configurable isTemplate? Do I get it right, that compartment.isTemplate is called implicitly for 2c?

If this is the case, we would need to know that the code executes in a compartment. It's not currently spelled out, but I guess compartments will store that in a running execution context so we could branch on that?

is there a summary of why this is needed?

@Jack-Works, see tc39/proposal-array-is-template-object#10 (comment) and the rest of that issue. The summary is: to be able to virtualize isTemplateObject check in a compartment.

koto commented

@erights, what do you think about the above? Rereading #42 (comment) it seems like you propose to have a compartment-relative method for checking templatedness (that consults a [[TemplateMap]] that would move to Compartments), but I'm not sure how this solves the problem if Array.isTemplateObject exists, and can be called from within the compartment. Hence the idea to enrich the compartment with a hook to be invoked that might add extra steps to the templatedness check. It could be user-controllable, or could just be boolean to fail on templates from outside the compartment (based on [[TemplateMap]])

I would rather not block isTemplateObject on compartments, the scopes of these proposals are different.

I don't understand. If the API remains Array.isTemplateObject, how can it be compartment-relative? The only options I see is that the API be rooted in something which is itself compartment relative. The only such objects are

  • The eval function
  • The Function constructor
  • The compartment's globalThis, i.e., the compartment's global object.
  • The Compartment constructor
  • The compartment instance that represents the current compartment

The API possibilities would be, respectively, something like

  • eval.isTemplateObject which I've suggested before. This would not need to wait on compartments themselves advancing
  • Function.isTemplateObject which is technically similar, but would be bizarrely surprising.
  • isTemplateObject i.e., globalThis.isTemplateObject
  • Compartment.isTemplateObject seems to be about the wrong compartments, in that the Compartment constructor is about making compartments relative to this compartment
  • compartment.isTemplateObject where compartment is the compartment instance representing the compartment of interest.

How is the Function constructor compartment-relative in a way that Array is not?

Why couldn't there also be one Array per compartment? since function () {}.constructor and [].constructor and function () {} instanceof Function and [] instanceof Array all share similar syntax-derived challenges, it seems like they'd both be in the same bucket.

@ljharb I believe there could be a per-compartment Array, but there has been no need so far. It’s generally better to have very few per-Compartment specializations to keep them cheap.

That makes sense, but that doesn't seem like a strong argument to me against putting isTemplateObject on an unideal object.

@ljharb No argument intentionally implied.

The price of having a Function constructor per compartment is that Function.prototype.constructor has to point at a dummy function constructor. For the evaluators we have no other choice. Having Array.prototype.constructor point at a dummy array constructor, just so each compartment can have its own Array constructor, seems way too complicated.

Those statements don’t mesh for me - i understand there’s no other choice for Function, but i don’t think it’s “way too complicated” for Array to do the thing Function is already doing - and since the solution for compartments is thus straightforward, even if not ideal, I’m not hearing a compelling argument to put isTemplateObject on “not Array”.

koto commented

I think we should strive to define a shape that is most understandable for the developers, and it does sound like Array is the most idiomatic container for the check. Per-compartment Array allows the compartments to avoid forgery across the boundaries they enact - and it looks like a simple solution (from the user's perspective).

Regarding the complexity: The compartments design introduces complexity to address their use cases. If that use case also covers virtualizing the templatedness checks, isn't it expected that there is some non-zero spec overhead to it? In this case, the proposed resolution doesn't appear to be that complex - what am I missing?

What you're missing is that this would be a substantial complexity cost to Compartments in order to support an esoteric feature that I'm not convinced is worth the complexity cost regardless. Right now Compartments treat a very small number of global constructors in this manner. Adding one more is a huge percentage increase. And it would establish a bad precedent, leading to more pollution of this namespace.

To be clear, the complexity cost is not on the difficulty of implementing or speccing compartments. It is on the understanding burden of using them correctly. Complexity as experienced by our users. Every deviation of Compartments from normal JS that they need to be aware of is an additional burden.

koto commented

The whole purpose of this ticket is to figure out how to support per-compartment templatedness checks, which you argued are required for isTemplateObject (or at least that's how I understand tc39/proposal-array-is-template-object#10 (comment)). We've explored how this could be achieved. It you believe that, for compartment users, there is not enough value in compartment-granularity of templatedness, do I get correctly that this concern goes away and is no longer blocking tc39/proposal-array-is-template-object#10, especially in the light of tc39/proposal-array-is-template-object#10 (comment)?

No of course not. I don't know what you misunderstood. Let's arrange a verbal conversation by email.

This proposal is now removing all other host hooks to focus on the module loaders, and I believe all compartments within a Realm share the same Array instance so they don't have to suffer from identity discontinuity.