Skip to content

Commit

Permalink
vm: return all own names and symbols in property enumerator interceptor
Browse files Browse the repository at this point in the history
Property enumerator methods like `Object.getOwnPropertyNames`,
`Object.getOwnPropertySymbols`, and `Object.keys` all invokes the
named property enumerator interceptor. V8 will filter the result based
on the invoked enumerator variant. Fix the enumerator interceptor to
return all potential properties.

PR-URL: nodejs#54522
Refs: jsdom/jsdom#3688
Reviewed-By: Joyee Cheung <joyeec9h3@gmail.com>
  • Loading branch information
legendecas authored and tpoisseau committed Nov 21, 2024
1 parent d4e1b8d commit 689ac6c
Show file tree
Hide file tree
Showing 5 changed files with 88 additions and 24 deletions.
32 changes: 25 additions & 7 deletions src/node_contextify.cc
Original file line number Diff line number Diff line change
Expand Up @@ -769,19 +769,25 @@ Intercepted ContextifyContext::PropertyDeleterCallback(
// static
void ContextifyContext::PropertyEnumeratorCallback(
const PropertyCallbackInfo<Array>& args) {
// Named enumerator will be invoked on Object.keys,
// Object.getOwnPropertyNames, Object.getOwnPropertySymbols,
// Object.getOwnPropertyDescriptors, for...in, etc. operations.
// Named enumerator should return all own non-indices property names,
// including string properties and symbol properties. V8 will filter the
// result array to match the expected symbol-only, enumerable-only with
// NamedPropertyQueryCallback.
ContextifyContext* ctx = ContextifyContext::Get(args);

// Still initializing
if (IsStillInitializing(ctx)) return;

Local<Array> properties;
// Only get named properties, exclude symbols and indices.
// Only get own named properties, exclude indices.
if (!ctx->sandbox()
->GetPropertyNames(
ctx->context(),
KeyCollectionMode::kIncludePrototypes,
static_cast<PropertyFilter>(PropertyFilter::ONLY_ENUMERABLE |
PropertyFilter::SKIP_SYMBOLS),
KeyCollectionMode::kOwnOnly,
static_cast<PropertyFilter>(PropertyFilter::ALL_PROPERTIES),
IndexFilter::kSkipIndices)
.ToLocal(&properties))
return;
Expand All @@ -792,6 +798,12 @@ void ContextifyContext::PropertyEnumeratorCallback(
// static
void ContextifyContext::IndexedPropertyEnumeratorCallback(
const PropertyCallbackInfo<Array>& args) {
// Indexed enumerator will be invoked on Object.keys,
// Object.getOwnPropertyNames, Object.getOwnPropertyDescriptors, for...in,
// etc. operations. Indexed enumerator should return all own non-indices index
// properties. V8 will filter the result array to match the expected
// enumerable-only with IndexedPropertyQueryCallback.

Isolate* isolate = args.GetIsolate();
HandleScope scope(isolate);
ContextifyContext* ctx = ContextifyContext::Get(args);
Expand All @@ -802,9 +814,15 @@ void ContextifyContext::IndexedPropertyEnumeratorCallback(

Local<Array> properties;

// By default, GetPropertyNames returns string and number property names, and
// doesn't convert the numbers to strings.
if (!ctx->sandbox()->GetPropertyNames(context).ToLocal(&properties)) return;
// Only get own index properties.
if (!ctx->sandbox()
->GetPropertyNames(
context,
KeyCollectionMode::kOwnOnly,
static_cast<PropertyFilter>(PropertyFilter::SKIP_SYMBOLS),
IndexFilter::kIncludeIndices)
.ToLocal(&properties))
return;

std::vector<v8::Global<Value>> properties_vec;
if (FromV8Array(context, properties, &properties_vec).IsNothing()) {
Expand Down
56 changes: 54 additions & 2 deletions test/parallel/test-vm-global-property-enumerator.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
'use strict';
require('../common');
const globalNames = require('../common/globals');
const vm = require('vm');
const assert = require('assert');

Expand Down Expand Up @@ -39,11 +40,62 @@ const cases = [
key: 'value',
},
},
(() => {
const obj = {
__proto__: {
[Symbol.toStringTag]: 'proto',
},
};
Object.defineProperty(obj, '1', {
value: 'value',
enumerable: false,
configurable: true,
});
Object.defineProperty(obj, 'key', {
value: 'value',
enumerable: false,
configurable: true,
});
Object.defineProperty(obj, Symbol('symbol'), {
value: 'value',
enumerable: false,
configurable: true,
});
Object.defineProperty(obj, Symbol('symbol-enumerable'), {
value: 'value',
enumerable: true,
configurable: true,
});
return obj;
})(),
];

for (const [idx, obj] of cases.entries()) {
const ctx = vm.createContext(obj);
const globalObj = vm.runInContext('this', ctx);
const keys = Object.keys(globalObj);
assert.deepStrictEqual(keys, Object.keys(obj), `Case ${idx} failed`);
assert.deepStrictEqual(Object.keys(globalObj), Object.keys(obj), `Case ${idx} failed: Object.keys`);

const ownPropertyNamesInner = difference(Object.getOwnPropertyNames(globalObj), globalNames.intrinsics);
const ownPropertyNamesOuter = Object.getOwnPropertyNames(obj);
assert.deepStrictEqual(
ownPropertyNamesInner,
ownPropertyNamesOuter,
`Case ${idx} failed: Object.getOwnPropertyNames`
);

// FIXME(legendecas): globalThis[@@toStringTag] is unconditionally
// initialized to the sandbox's constructor name, even if it does not exist
// on the sandbox object. This may incorrectly initialize the prototype
// @@toStringTag on the globalThis as an own property, like
// Window.prototype[@@toStringTag] should be a property on the prototype.
assert.deepStrictEqual(
Object.getOwnPropertySymbols(globalObj).filter((it) => it !== Symbol.toStringTag),
Object.getOwnPropertySymbols(obj),
`Case ${idx} failed: Object.getOwnPropertySymbols`
);
}

function difference(arrA, arrB) {
const setB = new Set(arrB);
return arrA.filter((x) => !setB.has(x));
};
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,9 @@ Object.defineProperty(sandbox, sym2, { value: true });

const ctx = vm.createContext(sandbox);

// Sanity check
// Please uncomment these when the test is no longer broken
// assert.deepStrictEqual(Reflect.ownKeys(sandbox), ['a', 'b', sym1, sym2]);
// assert.deepStrictEqual(Object.getOwnPropertyNames(sandbox), ['a', 'b']);
// assert.deepStrictEqual(Object.getOwnPropertySymbols(sandbox), [sym1, sym2]);
assert.deepStrictEqual(Reflect.ownKeys(sandbox), ['a', 'b', sym1, sym2]);
assert.deepStrictEqual(Object.getOwnPropertyNames(sandbox), ['a', 'b']);
assert.deepStrictEqual(Object.getOwnPropertySymbols(sandbox), [sym1, sym2]);

const nativeKeys = vm.runInNewContext('Reflect.ownKeys(this);');
const ownKeys = vm.runInContext('Reflect.ownKeys(this);', ctx);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,9 @@ Object.defineProperty(sandbox, sym2, { value: true });

const ctx = vm.createContext(sandbox);

// Sanity check
// Please uncomment these when the test is no longer broken
// assert.deepStrictEqual(Reflect.ownKeys(sandbox), ['a', 'b', sym1, sym2]);
// assert.deepStrictEqual(Object.getOwnPropertyNames(sandbox), ['a', 'b']);
// assert.deepStrictEqual(Object.getOwnPropertySymbols(sandbox), [sym1, sym2]);
assert.deepStrictEqual(Reflect.ownKeys(sandbox), ['a', 'b', sym1, sym2]);
assert.deepStrictEqual(Object.getOwnPropertyNames(sandbox), ['a', 'b']);
assert.deepStrictEqual(Object.getOwnPropertySymbols(sandbox), [sym1, sym2]);

const nativeNames = vm.runInNewContext('Object.getOwnPropertyNames(this);');
const ownNames = vm.runInContext('Object.getOwnPropertyNames(this);', ctx);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,9 @@ Object.defineProperty(sandbox, sym2, { value: true });

const ctx = vm.createContext(sandbox);

// Sanity check
// Please uncomment these when the test is no longer broken
// assert.deepStrictEqual(Reflect.ownKeys(sandbox), ['a', 'b', sym1, sym2]);
// assert.deepStrictEqual(Object.getOwnPropertyNames(sandbox), ['a', 'b']);
// assert.deepStrictEqual(Object.getOwnPropertySymbols(sandbox), [sym1, sym2]);
assert.deepStrictEqual(Reflect.ownKeys(sandbox), ['a', 'b', sym1, sym2]);
assert.deepStrictEqual(Object.getOwnPropertyNames(sandbox), ['a', 'b']);
assert.deepStrictEqual(Object.getOwnPropertySymbols(sandbox), [sym1, sym2]);

const nativeSym = vm.runInNewContext('Object.getOwnPropertySymbols(this);');
const ownSym = vm.runInContext('Object.getOwnPropertySymbols(this);', ctx);
Expand Down

0 comments on commit 689ac6c

Please sign in to comment.