Reflect.defineProperty + FromPropertyDescriptor & ToPropertyDescriptor

Darien Valentine valentinium at gmail.com
Tue Sep 4 07:32:37 UTC 2018


The following instrinsic functions invoke the ToPropertyDescriptor internal
operation to convert JS-object property descriptors into "Property
Descriptors" (spec-internal object type):

- `Reflect.defineProperty`
- `Object.create`
- `Object.defineProperty`
- `Object.defineProperties`
- a `Proxy` instance if its handler includes `getOwnPropertyDescriptor`

The ToPropertyDescriptor operation uses HasProperty to check for and the
presence of the following properties of a JS object property descriptor in
the order enumerable, configurable, value, writable, get, set.

If any two incompatible properties are found to be “had properties,” an
error is thrown.

It seems unfortunate that this works:

```js
function breakEveryPropertyDescriptor() {
  Object.prototype.value = undefined;
  Object.prototype.get = undefined;
}
```

But this has been mentioned before. I found this prior discussion:

https://esdiscuss.org/topic/topropertydescriptor-hasproperty-hasownproperty

As explained there, one can address this, if sufficiently paranoid, by only
ever passing in null-prototype objects (or objects with a prototype you
control) as JS-object property descriptors.

It gets a bit more complex. FromPropertyDescriptor produces JS-object PDs
which also inherit from %ObjectPrototype%. Naturally you can perform the
own-property check yourself — but it has a consequence I find particularly
interesting:

```
const obj1 = {};
const obj2 = new Proxy({}, Reflect);

breakEveryPropertyDescriptor();

const pd = Object.assign(Object.create(null), { value: 1 });

try {
  Reflect.defineProperty(obj1, 'foo', pd);
  console.log('successful on obj1');
} catch {
  console.log('unsuccessful on obj1');
}

try {
  Reflect.defineProperty(obj2, 'foo', pd);
  console.log('successful on obj2');
} catch {
  console.log('unsuccessful on obj2');
}
```

In the Proxy case, the input JS PD object is converted to an internal
Property Descriptor (`{ value: 1 }`) and then back to a fresh JS object
(now with %ObjectPrototype%) — and then back to an internal Property
Descriptor again. But it doesn’t successfully round trip! At this the
result of HasProperty(Obj, "get") changes from what it would have been if
the PD had not passed through Reflect.defineProperty, and the descriptor
has become `{ value: 1, get: undefined }`.

My understanding was that, in theory, using `Reflect` as your handler
object should mean the behavior of all the trapped operations should be the
same as it would have been for an ordinary object. This may be an incorrect
assumption on my part (are there other examples like this?), but it does
seem desirable for that to be true.

I’m curious about whether changes to these algorithms to alter this
behavior in some way would be web-safe. The obvious solution would be
switching HasProperty to HasOwnProperty in ToPropertyDescriptor and/or
creating objects that inherit from null in FromPropertyDescriptor. But I’m
guessing those changes would not be safe. In any case, I wanted to share
the non-parity edge case with Reflect.defineProperty in case this was not
already a known quirk.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20180904/40d24a50/attachment.html>


More information about the es-discuss mailing list