Skip to content
This repository has been archived by the owner on Jan 25, 2022. It is now read-only.

Use a class instead of creating a new object with closures each time #2

Closed
domenic opened this issue Feb 13, 2016 · 18 comments
Closed

Comments

@domenic
Copy link
Member

domenic commented Feb 13, 2016

ES prefers using classes to encapsulate methods that are reused, instead of re-creating new closures every time a given type of object is created. So, it should be new WeakRef(...) with WeakRef.prototype.get and WeakRef.prototype.clear().

@bterlson
Copy link
Member

The proposal suggests that the factory should go on the System object, but I still prefer new System.WeakRef() to System.makeWeakRef.

@dtribble
Copy link
Contributor

I'll add this to the open questions and plan to add a section to the document to describe the motivation and whatever change/resolution we come to here.

ES prefers using classes to encapsulate methods that are reused, instead of re-creating new closures every time a given type of object is created. So, it should be new WeakRef(...) withWeakRef.prototype.get and WeakRef.prototype.clear().
The proposal suggests that the factory should go on the System object, but I still prefer new System.WeakRef() to System.makeWeakRef.

We would also prefer to use the standard pattern. After some
discussion, we reluctantly proposed a different pattern because the
class pattern provides inappropriate access. If we can find something
that is closer without being confusing or if some other effort locks
down the standard pattern, that would be ideal.

Here's the issue: Say Bob does not have access to System.WeakRef
because Bob should not be able to read arbitrary gc-droppage channels.
But Bob might be given access to a particular weak reference

    const wr = new System.WeakRef(....);
    bob(wr);

if the caller judges it safe enough to enable Bob to observe when this
particular target is condemned. Bob could then do

    const WR = Object.getPrototypeOf(wr).constructor;

to obtain the weak reference constructor. There are simple variations
on the class pattern that would deny this access. But as Alan Kay says
"Similar things should be made either the same or very different."
Since we can't follow the class pattern, we thought it clearer to
propose just the minimal objects.

However, regarding:

re-creating new closures every time a given type of object is created

that was definitely not the intent; whatever the final there should be
only one allocation for each WeakRef. I'll separately revise the current proposal so it doesn't suggest that. Hopefully can chime in and help with an alternative construction before I get to that :).

@domenic
Copy link
Member Author

domenic commented Feb 13, 2016

Can't systems that want to restrict access... just restrict access? Virtualize or null out System.WeakRef.constructor.

@domenic
Copy link
Member Author

domenic commented Feb 13, 2016

I also think we should put this on the global, not on a namespace object. There is no practical difference and the former fits better with the language.

@dtribble
Copy link
Contributor

Can't systems that want to restrict access... just restrict access? Virtualize or null out System.WeakRef.constructor.

That's an example of "it's mostly the class pattern but not quite" that can be confusing. For example, if the constructor is null'd, is "new WeakRef" still well-defined? (That's a rhetorical question, btw.) So further exploration around other intrinsics might yield an existing object to match the specifics of (@erights suggested %MapIterator% as a possibility).

@domenic
Copy link
Member Author

domenic commented Feb 13, 2016

That sounds like an acceptable sacrifice for such systems to make in order to allow the rest of the ecosystem to interface with this through standard JavaScript patterns.

@erights
Copy link
Contributor

erights commented Feb 13, 2016

Yes, in ES6 there are several unnamed intrinsics, the ones listed in the "Intrinsic Name" column of https://tc39.github.io/ecma262/#sec-well-known-intrinsic-objects but where their "Global Name" is blank. Many of these are prototypes for which there is no corresponding constructor, thereby establishing precedent as a pattern for providing constructor-less vtables of methods for instances to inherit from. Let us indeed use %MapIteratorPrototype% https://tc39.github.io/ecma262/#sec-%mapiteratorprototype%-object as an example. (I think this is what Dean meant rather than %MapIterator%.)

new Map()[Symbol.iterator]().constructor === Object;
true

Likewise, we should revise the weakref proposal to be more ecma-spec-like by:

  • putting the internal state of a weakref instance in [[name]]ed slots, i.e., [[Target]], [[Holdings]], etc.
  • defining an unnamed intrinsic %WeakRefPrototype% that lacks a .constructor property
  • defining the .get and .clear methods as builtin methods on %WeakRefPrototype% that access these internal slots directly.

This does raise another cross realm issue we need to be careful of: what if a %WeakRefPrototype%.get method in one realm is applied to a weakref instance from another realm? Unlike cross-realm weakly pointing, this is a potential cross realm leakage issue that I have not yet thought about. My first reaction is this one is ok, but I am not yet convinced there's no security danger lurking there.

Nevertheless, this new cross-realm question should not block the intra-realm transition from the current spec language to one in terms of %WeakRefPrototype%.

@dtribble
Copy link
Contributor

The proposed revision approach sounds good to me. I expect I'll need a few iterations to correctly achieve it in the doc. @domenic @bterlson would that approach address your concerns for this issue?

@erights
Copy link
Contributor

erights commented Feb 13, 2016

Starting with the default loader, System is emerging as the place to put authority bearing primordial objects, so that one who wishes to virtualize the authority provided to a confined program knows that these are the dangerous tools that need such special treatment. We already have several candidates, scattered through other proposals in progress, for inclusion on System.

  • System.loader - the default loader
  • System.makeWeakRef
  • System.getStack(err) -> stacktrace-object
  • System.getStackString(err) -> the stacktrace rendered into a line-per-frame string.
  • System.getSourceMap(template) -> where in the original sources did the fragments of the template come from?
  • System.global - a standard way to name the (possibly virtual) global object

If anything like the current zones proposal were to go forward, many things would have to change. But anything like its "Zone" object would also go on System.

getSourceMap is arguable, as no authority is provided, so I'm not sure System is the best place for it. However, since getStack must be on System, and since a source remapping needs to virtualize these together, System may be the best place.

Leaving aside getSourceMap, the general issue is closely analogous to the difference between user-mode and system mode instructions. Anyone who has not yet read the classic
https://www.princeton.edu/~rblee/ELE572Papers/Fall04Readings/secureOS/popek_virtualizable.pdf
should. Essentially, in a cleanly virtualizable architecture, user-mode instructions allow completely unrestricted ability to do internal computation, but not to affect or be affected by the world outside that computation. Everything else must trap, so that the trap handler can emulate another behavior. A great example is an instruction which returns the time in system mode and is a noop, rather than trapping, in user mode. Since this provides the user-mode program the ability to sense whether it is in user-mode, the architecture is not cleanly virtualizable. Obviously, we are not in a position to approach this degree of virtualizability. But the architectural principles in this paper are right on target.

See the discussion at whatwg/loader#34 which concluded that the Loader constructor should be completely authority free -- all of a loader's ability to cause i/o should come from its constructor parameters -- and so the Loader constructor should go on Reflect. By contrast, the default loader needs to go on System.

@domenic
Copy link
Member Author

domenic commented Feb 14, 2016

I don't agree with this general direction. All of the proposals you mention are still nonstandard and one of my personal criteria for moving most of them forward will involve moving them to other more conventional objects (global, Error, etc.). I don't think this idea has as much support as you think it does.

And I think that breaking the X.prototype.constructor === X link (for APIs where X is meaningful, like WeakRef but unlike %MapIteratorPrototype%) should be confined to environments that need to do so, and not affect the language spec.

@dtribble
Copy link
Contributor

I updated the proposal to use a prototype. That seems like several steps in the right direction, regardless.

@allenwb
Copy link
Member

allenwb commented Feb 15, 2016

@domenic

don't agree with this general direction...

I'm with Domenic here. I don't think we should be turning System into a namespace object that we dump also sorts of new intrinsics. Either make them globals or make them exports from a built-in module.
Of those two alternatives, I prefer the module approach. The only thing we loose with that approach is the ability for sloppy mode "scripts" to directly reference them. That actually doesn't bother me a lot. And such scripts could still use the default loader (ie, System) to imperatively access them.

@zenparsing
Copy link
Member

Either make them globals or make them exports from a built-in module.

Agree as well.

@erights
Copy link
Contributor

erights commented Feb 15, 2016

At tc39/ecmascript_simd#323 I write:

For a tremendous number of the new apis that have been on the table recently, I would desperately love to see them provided via built in standard modules rather than new globals. Many of us would. A particularly vivid example is the SIMD API. Today, proposed as rooted in globals, it adds a tremendous number of new objects to the population of primordials. Since it raises none of the security concerns of weakrefs, let us explore, first, how SIMD may be provided via module rather than new globals.

Once we understand what that looks like in the absence of security constraints, let's revisit it for weakrefs. If it makes sense, I would certainly prefer to see this rooted in a module rather than a global.

@dtribble
Copy link
Contributor

I too would prefer a module, assuming it can address the security requirements. Weak references are intended for use by library and framework creators, and shouldn't appear in most application code. Thus, even independent of security concerns, they shouldn't be in a global namespace.

@zenparsing
Copy link
Member

At the March meeting, there seemed to be a positive reaction to the idea of exposing the API via a WeakRef constructor. The challenge with that approach, if I remember correctly, is that we may not want to expose the WeakMap constructor to code that is given a reference to a WeakRef instance.

As a solution to that problem, we briefly discussed the idea of a meta API on superclasses which informs subclasses that they should not attach a "constructor" property to the newly created prototype object.

Has any more thought been given to this issue? It seems like it may be a blocker for the surface API of this proposal.

@erights
Copy link
Contributor

erights commented Apr 15, 2016

I had an informative side conversation at that tc39 meeting, but I'm not sure with whom. The suggestion which emerged is:

Introduce a new symbol, say @@override (or Symbol.override). When evaluating the class expression or definition class B extends A {...}, at some stage in the initialization of B, check if A has an @@override method. If so, call A[@@override](B). Thus,

An annotation on A such as @constructorless can both remove A's .constructor property and add an @@override method to A that removes its argument's .constructor property. One can imagine other @@override methods that install yet other @@override methods on their argument class.

Open questions:

  • Need better names for @@override symbol and for the @constructorless annotation.
  • At what stage of the initialization of B does it call A[@@override](B) ?

@littledan
Copy link
Member

The current proposal is based on a class.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

7 participants