Skip to content
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

New integrity level #15

Open
Jack-Works opened this issue Mar 20, 2022 · 16 comments
Open

New integrity level #15

Jack-Works opened this issue Mar 20, 2022 · 16 comments

Comments

@Jack-Works
Copy link
Member

Jack-Works commented Mar 20, 2022

Follow-on of discussion in #13, I organized ideas with a new "new integrity level" that freeze the internal state of an object. What do you think?

(cc @erights, @ljharb, @phoddie, @syg)

stabilized state (temporally name)

A new integrity level of object:

  • Its internal state is immutable (NOT recursive).
  • This does not mean it is frozen (in the meaning of Object.freeze)
  • Stabilize an object might throw!
  • Some objects may be refused to be stabilized (e.g: when an ArrayBuffer is used by WebAssembly as memory?)

difference between harden

  • harden is recursive, stabilize is not.
  • harden care about the property descriptors, stabilize only care about the meaningful fields (and internal slots) that play roles in its data structure.

New APIs:

  • isStable(x)
  • stablilzed(x)

Terms

  • 🌟 `born stabilized: the value is already stabilized after creation
  • ✅: can be stabilized after creation
  • ❌: cannot be stabilized
  • ❓: to be decided

ECMAScript defined:

Data type Status Description
Primitive (1, true) 🌟 Born stabilized.
Boxed primitive (new Number(1)) 🌟 Born stabilized.
Array Make its all array index properties unconfigurable and non-writable.
Refuse to add new array index properties.
ArrayBuffer Refuse to modify data inside [[ArrayBufferData]] (but allowing detach to transfer).
Refuse to modify [[ArrayBufferByteLength]] (refuse to expand/shrink size).
Date Refuse to modify [[DateValue]].
Errors (EvalError, ...) To be decided.
FinalizationRegistry & WeakRef To be decided.
Function 🌟❓ Can you change it anyway? Make length and name unconfigurable?
globalThis To be decided. Refuse to define new global variable? What about DOM with ID?
Iterators (ArrayIterator, ...) To be decided.
Map & Set Refuse to modify [[MapData]] or [[SetData]] field.
Promise Refuse to be stabilized.
It's either already stabilized (fulfilled/rejected), or useless after hardened (pending).
Proxy ➡️ Passthrough. It will invoke the [[Get]] trap with Symbol.harden.
RegExp Make its lastIndex property non-writable.
Refuse to compile().
ShadowRealm To be decided. Possible options:
❌ Refuse to be stabilized
✅ Refuse to compile new code (by evaluate, eval, Function or importValue)
TypedArrays/DataView Refuse to be stabilized.
It is a view, you must freeze the underlying buffer with the buffer directly.
WeakMap & WeakSet Refuse to modify [[WeakMapData]] or [[WeakSetData]] field.

Userland objects:

Symbol.stabilize protocol:

class T {
    #data = new Set()
    #hardened = false
    ;[Symbol.stabilize]() {
        harden(this.#data)
        this.#hardened = true
    }
    add(x) {
        this.#data.add(x)
    }
}

Extra APIs:

Those methods are useful but not directly related to the new integrity level, and not possible to make a unified protocol for all objects. I list them case-by-case.

@ljharb
Copy link
Member

ljharb commented Mar 20, 2022

I would expect such an integrity level to apply to everything - preventing a promise from ever fulfilling, preventing any changes to a Weak Map/Set, etc.

Functions and Errors have no internal slots that change, so i'd expect it to either be a noop for these objects, or better, for them to be born "hardened".

Your list is missing Dates, which would become immutable, also hardened RegExps would have to fail to be changeable via .compile.

For Arrays, none of that info is internal slots, so I would not expect hardening an Array to have any effect - you can freeze an array for that.

@Jack-Works
Copy link
Member Author

@ljharb thanks! I've added the missing Date to the table. I have adopted the word born hardened.

The internal state I referred does not the same as an internal slot. It means a necessary part of a data structure to make the data it refers to mutable.

For example, in RegExp, I choose to harden the lastIndex property (it is not an internal slot). Therefore I choose the same approach for Array (make numeric index keys immutable and refuse to add new numeric keys but still allow define other own properties like arr.foo).

harden(arr)
arr[0] = a // error
arr.length = 0 // error
arr.own = a // OK, define new own property

For Functions, maybe we can harden the length property, that's the only changeable part of a Function.
For Errors, maybe we can harden the message, stack (wait it is not standardized), and cause property.

It's technically ok to harden Promise and WeakMap/WeakSet but I'm doubting if it is really useful for people...

@ljharb
Copy link
Member

ljharb commented Mar 21, 2022

It is very useful to prevent people from clearing a weak map or weak set, or from changing or removing mappings.

I don’t think we need a new integrity level for anything to do with non-exotic properties - function length is configurable, and can easily be made not so (also, the name is the same as the length). Array length might be a special case for arrays, though. In other words, for most properties, i don’t think it would be appropriate to harden them - that’s already handled by existing APIs.

@Jack-Works
Copy link
Member Author

It is very useful to prevent people from clearing a weak map or weak set, or from changing or removing mappings.

Oh, I only think of adding to the weak map/set. That's reasonable, I have updated the table.

I don’t think we need a new integrity level for anything to do with non-exotic properties

Then what about RegExp.lastIndex? It's a normal property, but also affects the result of matching on the object.

@ljharb
Copy link
Member

ljharb commented Mar 21, 2022

That's already possible to make immutable:

var r = /a/g;
r.lastIndex; // 0
r.exec('aaaa')
r.lastIndex; // 1
Object.defineProperty(r, 'lastIndex', { configurable: false, writable: false });
r.exec('aaaa') // throws TypeError

and surprisingly, if you continue with the same code, the internal slots for source and flags are also now immutable:

Object.freeze(r);
r.source; // 'a'
r.flags; // 'g'
r.compile('b', 'gi'); // throws TypeError

because compile assigns to lastIndex.

However, making lastIndex immutable makes a global regex largely useless - but if you leave lastIndex mutable, then the source and flags can be changed out from under you at any time.

Thus, having a way to make these internal slots immutable WITHOUT making lastIndex immutable is actually quite valuable, and currently impossible.

@Jack-Works
Copy link
Member Author

Hmm, this RegExp case is very interesting, but that will be strange if harden a Map make the map immutable, but harden a RegExp its lastIndex still might change.

@ljharb
Copy link
Member

ljharb commented Mar 21, 2022

It wouldn’t make the Map’s properties immutable, just its internal storage.

@Jack-Works
Copy link
Member Author

Yeah, I know. Map's own properties do not interact with all built-in methods on Map (e.g. has()), but lastIndex interacts with the built-in methods (exec()).

@ljharb
Copy link
Member

ljharb commented Mar 21, 2022

Right - but it's not internal state, it's just a normal property, so there's no need for a new way to lock it down.

@Jack-Works
Copy link
Member Author

I have updated the term to stabilize to disambiguate with harden which SES people heavily use.

@codehag
Copy link

codehag commented Mar 29, 2022

I am struggling a bit to fully understand what the impact here will be, and have similar concerns to @ljharb . A new integrity level, I would expect to be applied broadly -- but at the moment I only really understand this in relation to map/set and weakmap/set and similar collections. Can you elaborate a bit more about what you intend to communicate with "new integrity level"? I also have some concerns about something so broad without a clear motivation, it may make more sense to scope this. I am discussing this with my team currently, but as it is on the agenda for tomorrow I am trying to give early feedback so this isn't fully thought through.

@Jack-Works
Copy link
Member Author

A new integrity level, I would expect to be applied broadly

I'll expect host data structures (and user-land objects) to opt into the new protocol. Since we only have a few data structures in the language (Map, Set, Date, ...), it's a challenge to clearly define what is it and how it should behave.

@Jack-Works
Copy link
Member Author

Conclusion:

  • Committee does not prefer the symbol based protocol but likes to have a case-by-case review for built-ins
  • Will discuss what does it mean for some special object (like lastIndex of RegExp)
  • Try to make a unified proposal to add them (e.g. {Map,Set,Date,ArrayBuffer}.prototype.freeze()) in a consistent way.
  • SES prefers in-place freeze other than returning a new immutable copy (because that needs to construct a new object tree in harden)

I may change the conclusion above after I review the meeting notes.

@ghost
Copy link

ghost commented Aug 1, 2022

It is very useful to prevent people from clearing a weak map or weak set, or from changing or removing mappings.

Re: clearing, WeakMap doesn't expose a WeakMap#clear, like Map does via Map#clear.
Freezing a WeakMap/WeakSet sounds like it would prevent GC of its stored data, because the lists become immutable. If not, this is observable via WeakRef.

@ghost
Copy link

ghost commented Aug 1, 2022

It doesn't seem that the conclusion is to expose stabilize? If I'm mistaken, and that it is, is it intended to also freeze the internal data of items exposed from the web platform, such as those represented via WebIDL?

@Jack-Works
Copy link
Member Author

I revisited this issue and found the discussion very interesting. @erights talked about stabilize to me this autumn but with very different semantics, and also the note above conflicts with the immutable ArrayBuffer is designed. I wonder if Mark Miller has anything new to share since 2022.

SES prefers in-place freeze rather than returning a new immutable copy (because that needs to construct a new object tree in harden)

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

No branches or pull requests

3 participants