See this comment for a revised version.
The override mistake is a problem introduced with ES5's ability to mark properties as non-writable. When a property on the prototype of an object is marked non-writable, it makes the property unaddable on the object.
A notable example of this is if we freeze (which marks all properties as non-writable) on Object.prototype
.
Object.freeze(Object.prototype);
When we do this, it becomes impossible to set toString()
on any objects that don't already have a .toString()
.
e.g. the following fails:
"use strict";
Object.freeze(Object.prototype);
const newObject = {};
newObject.toString = () => "Some string"; // TypeError
This is of particular concern to the SES proposal, which needs to freeze all builtins in order to guarantee security properties.
In this proposal we propose a new descriptor member called overridable
,
this member may only be present when writable
is set to false
.
When overridable
is present, non-writable properties on the prototype will be ignored when attempting to set them.
For example the following will print 10
:
"use strict";
const proto = {};
Object.defineProperty(proto, 'example', { value: 5, writable: false, overridable: true });
const object = { __proto__: proto };
object.example = 10;
console.log(object.example);
For SES, we need to be able to able to freeze entire objects, but without creating the problems of the override mistake.
As such a new function will be provided called Object.harden
, this method acts just like Object.freeze
,
except it sets overridable: true
on all descriptors (that are configurable).
For example, the following will just work now:
"use strict";
Object.harden(Object.prototype);
const object = {};
object.toString = () => "Hello world!";
console.log(object.toString()); // Hello world!
QUESTION: Should this simply be an option to freeze
rather than a new method?
If we expose overridable: boolean
as a new property on descriptors returned from Object.getOwnPropertyDescriptor(s)
,
will existing code break?
If so, should we add a new method to get overridable
to Object
(e.g. Object.getIsOverridable(object, name)
), or should we expose overridable
only when it is set to true
.
The short answer is no. Basically you're forced to return
false
from theset
trap (as per the invariants of[[Set]]
). Although strictly speaking you can actually still use defineProperty within the set trap to set the value. It's just that in strict mode the assignment will still result in an error (despite being been set).e.g.:
It is somewhat surprising to me that the invariant of
[[Set]](Prop, Value, Receiver)
requires a return offalse
in such a case, regardless of whether or not theReceiver
is different to the target or not.I understand the concern of adding a new descriptor member. Strictly speaking because the override mistake is actually a problem with the definition of "ordinary objects" (by way of [
OrdinarySetWithOwnPropertyDescriptor
]), we don't actually need to add it to the object meta-model. We only need to find a way for ordinary objects to mark their properties as overridable.As a suggestion we could add a new slot
[[OverridableProperties]]
which is a set of all own properties which may be overridable.OrdinarySetWithOwnPropertyDescriptor
would check it's passed property as to whether it is in this set before deciding to ignore[[Writable]]
on inherited descriptors.I'd imagine some API like
Object.setOverridable(object, property)
that adds the property to the[[OverridableProperties]]
list. For non-ordinary objects this would either do nothing or throw an error (TODO: Investigate side-channels introduced by callingObject.setOverridable
).We'd need to take care to ensure there's no way to add a property to
[[OverridableProperties]]
if the property isn't configurable. We'd also need to take care to ensure all relevant builtin non-ordinary objects have this slot (e.g.%Object.prototype%
).