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

assert.deepStrictEqual() fails to compare ES6 style object properties #11803

Closed
AnnaMag opened this issue Mar 11, 2017 · 20 comments
Closed

assert.deepStrictEqual() fails to compare ES6 style object properties #11803

AnnaMag opened this issue Mar 11, 2017 · 20 comments
Labels
assert Issues and PRs related to the assert subsystem. vm Issues and PRs related to the vm subsystem.

Comments

@AnnaMag
Copy link
Member

AnnaMag commented Mar 11, 2017

  • Version: v7.7.1
  • Platform: Mac OSX X86_64
  • Subsystem:

The following assertion fails when objects are property descriptors:

'use strict';
require('../common');
const assert = require('assert');

const x = {};

Object.defineProperty(x, 'prop', {
  get() {
    return 'foo';
  }
});

const y = {};

Object.defineProperty(y, 'prop', {
  get() {
    return 'foo';
  }
});

const descriptorx = Object.getOwnPropertyDescriptor(x, 'prop');
const descriptory = Object.getOwnPropertyDescriptor(y, 'prop');

assert.deepStrictEqual(x, y); // ok
assert.deepStrictEqual(descriptorx, descriptory); // throws an error

What seems to happen is that (in both cases) Object.getPrototypeOf(descriptor)
results in {}, which leads to the condition
https://github.com/nodejs/node/blob/master/lib/assert.js#L200
returning false. Same is true for assert.strictEqual()

@AnnaMag
Copy link
Member Author

AnnaMag commented Mar 11, 2017

With the changes in the new V8 API, accessor properties are not flattened anymore (patch hopefully coming soon as we are working on it with @fhinkel :)) and descriptors can have attributes that are functions (getters/setters). E.g. the following test will not throw with the new API:
https://github.com/nodejs/node/blob/master/test/known_issues/test-vm-getters.js
but current assertions do not work for deep object comparison with functions as attributes.
That is, this will not work:
https://github.com/nodejs/node/blob/master/test/known_issues/test-vm-getters.js#L19

I wrote a piece of code that fixed the issue:

const aKeys = Object.keys(descriptory);
const bKeys = Object.keys(descriptorx);

var keya, keyb, vala, valb, i;

if (aKeys.length !== bKeys.length)
  return false;

aKeys.sort();
bKeys.sort();

for (i = 0; i < aKeys.length; i++) {
  keya = aKeys[i];
  keyb = bKeys[i];
  if (keya === keyb) {
      if (typeof descriptory[keya] === 'function' &&
          typeof descriptorx[keyb] === 'function') {
              vala = descriptory[keya].toString();
              valb = descriptorx[keyb].toString();
              if (vala !== valb) {return false;}
              else continue;
       }

      if (descriptory[keya] !== descriptorx[keyb]) return false; //different values
    } else  return false; // keys are different
}

Happy to contribute it to assert.js. I was not able to figure out how to weave it into the existing code, though.

@AnnaMag
Copy link
Member Author

AnnaMag commented Mar 11, 2017

cc/ @fhinkel

@joyeecheung joyeecheung added the assert Issues and PRs related to the assert subsystem. label Mar 11, 2017
@addaleax
Copy link
Member

What seems to happen is that (in both cases) Object.getPrototypeOf(descriptor)
results in {}, which leads to the condition
https://github.com/nodejs/node/blob/master/lib/assert.js#L200
returning false.

Object.getPrototypeOf(descriptorx) === Object.getPrototypeOf(descriptory) yields true for me, so I don’t think that’s the problem.

You define two different functions as the get fields of the two descriptors, so I think this assert is correct here (descriptorx.get !== descriptory.get)?

@joyeecheung
Copy link
Member

I think this again raises the issue on whether we should add support for new ES features in assert...(see #11113). Similar to descriptors, the assertions don't work on Maps, Sets either..

Also related: last CTC minutes on this https://github.com/nodejs/CTC/blob/master/meetings/2017-02-01.md#assert-enforce-type-check-in-deepstrictequal-node10282

@addaleax
Copy link
Member

@AnnaMag If I understand your code correctly, you would say that assert.strictEqual(function a() {}, function a() {}) should not throw, even though those are distinct functions – correct?

@joyeecheung
Copy link
Member

(Also just noticed the comments above https://github.com/nodejs/node/blob/master/lib/assert.js#L200 are not correct...at least not until #10282 is merged, uh..)

@AnnaMag
Copy link
Member Author

AnnaMag commented Mar 11, 2017

@addaleax, correct... might not be in line with the overall design. What I am after is to have a way to test whether ES6 property descriptors work correctly in the vm.

After removing CopyProperties() and using the 5.5 V8 API, this test (among others)
https://github.com/nodejs/node/blob/master/test/known_issues/test-vm-getters.js
can be moved from /known_issues to /parallel.
With no assert in place it is tricky to show that the test does work :)

@joyeecheung
Copy link
Member

joyeecheung commented Mar 11, 2017

@AnnaMag @addaleax Ah yes, those are not the same functions. This should work:

function foo() { return 'foo'; }

const x = {};

Object.defineProperty(x, 'prop', {
  get: foo
});

const y = {};

Object.defineProperty(y, 'prop', {
  get: foo
});

@joyeecheung
Copy link
Member

joyeecheung commented Mar 11, 2017

@AnnaMag I think if the bug has been fixed, https://github.com/nodejs/node/blob/master/test/known_issues/test-vm-getters.js should pass now ? Because assert.strictEqual(result, descriptor) is equivalent to assert(result === descriptor)(assuming that is the desired behavior).

@AnnaMag
Copy link
Member Author

AnnaMag commented Mar 11, 2017

@joyeecheung, exactly, it should pass :) Yet it does not, which is the very thing that made me check the code to find out why:

assert.js:81
  throw new assert.AssertionError({
  ^
AssertionError: { get: [Function: get],
  set: undefined,
  enumerable: false,
  configurable: false } deepStrictEqual { get: [Function: get],
  set: undefined,
  enumerable: false,
  configurable: false }

This is the same for deepStrictEqual and strictEqual.
Object.getPrototypeOf() is {} and, as I mentioned above, nothing is in reality being checked (no comparison of the attributes) as the condition https://github.com/nodejs/node/blob/master/lib/assert.js#L200
returns false and nothing is executed after that.

What also happens is that even if it did work, it would not compare the values of the accessors (only attribute keys), as (as far as I understand it), comparing functions in JS requires a hack, e.g. .toString().
I might be wrong, so please feel free to correct me. Thanks for the input!

@addaleax
Copy link
Member

Object.getPrototypeOf() is {}

Ah – sorry, I think I understand how this relates to the vm module. Object.getPrototypeOf() is Object.prototype in both cases, but because we are comparing the results of Object.getOwnPropertyDescriptor() calls in two different contexts, the returned descriptor objects refer to different Object.prototypes.

Since the test is supposed to be a test for #2734, I think it would be okay to fix this up in the test itself (replacing result with Object.assign({}, result) in the assertion might work?).
But this is still an interesting point that I don’t think we considered in #11128.

@AnnaMag
Copy link
Member Author

AnnaMag commented Mar 11, 2017

@joyeecheung, the code snippet with common definition of foo does not seem to work for the current master (nor for my build).
Thanks @addaleax, makes sense.

Summing up, https://github.com/nodejs/node/blob/master/test/known_issues/test-vm-getters.js is not a valid test then. Is PR a good idea here?

@addaleax addaleax added the vm Issues and PRs related to the vm subsystem. label Mar 11, 2017
@addaleax
Copy link
Member

Is PR a good idea here?

Looks like it, yes… I’m not sure but I don’t think we’ll want to turn the prototype-equals-prototype check into a deep comparison, and that’s the only alternative I can think of right now. Maybe @fhinkel or @joyeecheung have better ideas but fixing up the test seems like the best option to me.

@AnnaMag
Copy link
Member Author

AnnaMag commented Mar 11, 2017

Very helpful @addaleax, thanks!

@joyeecheung
Copy link
Member

@addaleax Unfortunately, deep comparison of prototypes doesn't work with API that conforms to WebIDL(e.g. WHATWG URL), because the properties are defined as enumerable owned properties on the prototype, so if we enumerate through the prototype, for example, URL.prototype.href will be called without a valid context and throw.

@joyeecheung
Copy link
Member

joyeecheung commented Mar 12, 2017

@AnnaMag Since the strict assertions use === to check everything, if the objects are just bound to have properties that don't reference the same thing(e.g. in another vm), then strict assertions are probably not what the test is looking for..You can probably turn the assertions into something like this to avoid the extra prototype check:

assert.deepStrictEqual(Object.keys(result), Object.keys(descriptor));

for (const prop in Object.keys(result)) {
  assert.strictEqual(result[prop], descriptor[prop]);
}

This throws

AssertionError: [ 'value', 'writable', 'enumerable', 'configurable' ] deepStrictEqual [ 'get', 'set', 'enumerable', 'configurable' ]

in the master at the moment(which is exactly what #2734 is about..)

Also on comparing accessors: the assertions are testing the output of accessors, not the accessors themselves..at the moment they just care about the equality of the output, not how the output is obtained.

@AnnaMag
Copy link
Member Author

AnnaMag commented Mar 12, 2017

@joyeecheung, many thanks!

I was referring to the code snippet you gave as an example:

function foo() { return 'foo'; }

const x = {};

Object.defineProperty(x, 'prop', {
  get: foo
});

const y = {};

Object.defineProperty(y, 'prop', {
  get: foo
});

That should work on master, right?

@joyeecheung
Copy link
Member

@AnnaMag Yes, on master and 7.6.0, this passes:

'use strict';
const assert = require('assert');

function foo() { return 'foo'; }

const x = {};

Object.defineProperty(x, 'prop', {
  get: foo
});

const y = {};

Object.defineProperty(y, 'prop', {
  get: foo
});

const descriptorx = Object.getOwnPropertyDescriptor(x, 'prop');
const descriptory = Object.getOwnPropertyDescriptor(y, 'prop');

assert.deepStrictEqual(x, y); // ok
assert.deepStrictEqual(descriptorx, descriptory); // ok

@fhinkel
Copy link
Member

fhinkel commented Mar 14, 2017

Can we close this and you adjust the test accordingly?

@AnnaMag
Copy link
Member Author

AnnaMag commented Mar 14, 2017

I believe so. I will do a PR for the test.

fhinkel added a commit to fhinkel/node that referenced this issue Oct 23, 2017
Remove the CopyProperties() hack in the vm module, i.e., Contextify.
Use different V8 API methods, that allow interception of
DefineProperty() and do not flatten accessor descriptors to
property descriptors.

Move many known issues to test cases. Factor out the last test in
test-vm-context.js for
nodejs#10223
into its own file, test-vm-strict-assign.js.

Part of this CL is taken from a stalled PR by
https://github.com/AnnaMag
nodejs#13265

This PR requires a backport of
https://chromium.googlesource.com/v8/v8/+/37a3a15c3e52e2146e45f41c427f24414e4d7f6f

Refs: nodejs#6283
Refs: nodejs#15114
Refs: nodejs#13265

Fixes: nodejs#2734
Fixes: nodejs#10223
Fixes: nodejs#11803
Fixes: nodejs#11902
fhinkel added a commit to fhinkel/node that referenced this issue Oct 23, 2017
Remove the CopyProperties() hack in the vm module, i.e., Contextify.
Use different V8 API methods, that allow interception of
DefineProperty() and do not flatten accessor descriptors to
property descriptors.

Move many known issues to test cases. Factor out the last test in
test-vm-context.js for
nodejs#10223
into its own file, test-vm-strict-assign.js.

Part of this CL is taken from a stalled PR by
https://github.com/AnnaMag
nodejs#13265

This PR requires a backport of
https://chromium.googlesource.com/v8/v8/+/37a3a15c3e52e2146e45f41c427f24414e4d7f6f

PR-URL: nodejs#16293
Fixes: nodejs#2734
Fixes: nodejs#10223
Fixes: nodejs#11803
Fixes: nodejs#11902
Ref: nodejs#6283
Ref: nodejs#15114
Ref: nodejs#13265
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl>
Reviewed-By: Tobias Nießen <tniessen@tnie.de>
Reviewed-By: Anna Henningsen <anna@addaleax.net>
Reviewed-By: Timothy Gu <timothygu99@gmail.com>
Reviewed-By: Michaël Zasso <targos@protonmail.com>
addaleax pushed a commit to ayojs/ayo that referenced this issue Oct 26, 2017
Remove the CopyProperties() hack in the vm module, i.e., Contextify.
Use different V8 API methods, that allow interception of
DefineProperty() and do not flatten accessor descriptors to
property descriptors.

Move many known issues to test cases. Factor out the last test in
test-vm-context.js for
nodejs/node#10223
into its own file, test-vm-strict-assign.js.

Part of this CL is taken from a stalled PR by
https://github.com/AnnaMag
nodejs/node#13265

This PR requires a backport of
https://chromium.googlesource.com/v8/v8/+/37a3a15c3e52e2146e45f41c427f24414e4d7f6f

PR-URL: nodejs/node#16293
Fixes: nodejs/node#2734
Fixes: nodejs/node#10223
Fixes: nodejs/node#11803
Fixes: nodejs/node#11902
Ref: nodejs/node#6283
Ref: nodejs/node#15114
Ref: nodejs/node#13265
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl>
Reviewed-By: Tobias Nießen <tniessen@tnie.de>
Reviewed-By: Anna Henningsen <anna@addaleax.net>
Reviewed-By: Timothy Gu <timothygu99@gmail.com>
Reviewed-By: Michaël Zasso <targos@protonmail.com>
addaleax pushed a commit to ayojs/ayo that referenced this issue Dec 7, 2017
Remove the CopyProperties() hack in the vm module, i.e., Contextify.
Use different V8 API methods, that allow interception of
DefineProperty() and do not flatten accessor descriptors to
property descriptors.

Move many known issues to test cases. Factor out the last test in
test-vm-context.js for
nodejs/node#10223
into its own file, test-vm-strict-assign.js.

Part of this CL is taken from a stalled PR by
https://github.com/AnnaMag
nodejs/node#13265

This PR requires a backport of
https://chromium.googlesource.com/v8/v8/+/37a3a15c3e52e2146e45f41c427f24414e4d7f6f

PR-URL: nodejs/node#16293
Fixes: nodejs/node#2734
Fixes: nodejs/node#10223
Fixes: nodejs/node#11803
Fixes: nodejs/node#11902
Ref: nodejs/node#6283
Ref: nodejs/node#15114
Ref: nodejs/node#13265
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl>
Reviewed-By: Tobias Nießen <tniessen@tnie.de>
Reviewed-By: Anna Henningsen <anna@addaleax.net>
Reviewed-By: Timothy Gu <timothygu99@gmail.com>
Reviewed-By: Michaël Zasso <targos@protonmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
assert Issues and PRs related to the assert subsystem. vm Issues and PRs related to the vm subsystem.
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants