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

lib: use const primordials in primordials.makeSafe and dependencies #36865

Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
235 changes: 129 additions & 106 deletions lib/internal/per_context/primordials.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@
// so that Node.js's builtin modules do not need to later look these up from
// the global proxy, which can be mutated by users.

const {
defineProperty: ReflectDefineProperty,
getOwnPropertyDescriptor: ReflectGetOwnPropertyDescriptor,
ownKeys: ReflectOwnKeys,
} = Reflect;

// TODO(joyeecheung): we can restrict access to these globals in builtin
// modules through the JS linter, for example: ban access such as `Object`
// (which falls back to a lookup in the global proxy) in favor of
Expand All @@ -19,159 +25,66 @@ const { bind, call } = Function.prototype;
const uncurryThis = bind.bind(call);
primordials.uncurryThis = uncurryThis;

function copyProps(src, dest) {
for (const key of Reflect.ownKeys(src)) {
if (!Reflect.getOwnPropertyDescriptor(dest, key)) {
Reflect.defineProperty(
dest,
key,
Reflect.getOwnPropertyDescriptor(src, key));
}
}
}

function getNewKey(key) {
return typeof key === 'symbol' ?
`Symbol${key.description[7].toUpperCase()}${key.description.slice(8)}` :
`${key[0].toUpperCase()}${key.slice(1)}`;
}

function copyAccessor(dest, prefix, key, { enumerable, get, set }) {
Reflect.defineProperty(dest, `${prefix}Get${key}`, {
ReflectDefineProperty(dest, `${prefix}Get${key}`, {
value: uncurryThis(get),
enumerable
});
if (set !== undefined) {
Reflect.defineProperty(dest, `${prefix}Set${key}`, {
ReflectDefineProperty(dest, `${prefix}Set${key}`, {
value: uncurryThis(set),
enumerable
});
}
}

function copyPropsRenamed(src, dest, prefix) {
for (const key of Reflect.ownKeys(src)) {
for (const key of ReflectOwnKeys(src)) {
const newKey = getNewKey(key);
const desc = Reflect.getOwnPropertyDescriptor(src, key);
const desc = ReflectGetOwnPropertyDescriptor(src, key);
if ('get' in desc) {
copyAccessor(dest, prefix, newKey, desc);
} else {
Reflect.defineProperty(dest, `${prefix}${newKey}`, desc);
ReflectDefineProperty(dest, `${prefix}${newKey}`, desc);
}
}
}

function copyPropsRenamedBound(src, dest, prefix) {
for (const key of Reflect.ownKeys(src)) {
for (const key of ReflectOwnKeys(src)) {
const newKey = getNewKey(key);
const desc = Reflect.getOwnPropertyDescriptor(src, key);
const desc = ReflectGetOwnPropertyDescriptor(src, key);
if ('get' in desc) {
copyAccessor(dest, prefix, newKey, desc);
} else {
if (typeof desc.value === 'function') {
desc.value = desc.value.bind(src);
}
Reflect.defineProperty(dest, `${prefix}${newKey}`, desc);
ReflectDefineProperty(dest, `${prefix}${newKey}`, desc);
}
}
}

function copyPrototype(src, dest, prefix) {
for (const key of Reflect.ownKeys(src)) {
for (const key of ReflectOwnKeys(src)) {
const newKey = getNewKey(key);
const desc = Reflect.getOwnPropertyDescriptor(src, key);
const desc = ReflectGetOwnPropertyDescriptor(src, key);
if ('get' in desc) {
copyAccessor(dest, prefix, newKey, desc);
} else {
if (typeof desc.value === 'function') {
desc.value = uncurryThis(desc.value);
}
Reflect.defineProperty(dest, `${prefix}${newKey}`, desc);
}
}
}

const createSafeIterator = (factory, next) => {
class SafeIterator {
constructor(iterable) {
this._iterator = factory(iterable);
}
next() {
return next(this._iterator);
}
[Symbol.iterator]() {
return this;
}
}
Object.setPrototypeOf(SafeIterator.prototype, null);
Object.freeze(SafeIterator.prototype);
Object.freeze(SafeIterator);
return SafeIterator;
};

function makeSafe(unsafe, safe) {
if (Symbol.iterator in unsafe.prototype) {
const dummy = new unsafe();
let next; // We can reuse the same `next` method.

for (const key of Reflect.ownKeys(unsafe.prototype)) {
if (!Reflect.getOwnPropertyDescriptor(safe.prototype, key)) {
const desc = Reflect.getOwnPropertyDescriptor(unsafe.prototype, key);
if (
typeof desc.value === 'function' &&
desc.value.length === 0 &&
Symbol.iterator in (desc.value.call(dummy) ?? {})
) {
const createIterator = uncurryThis(desc.value);
next ??= uncurryThis(createIterator(dummy).next);
const SafeIterator = createSafeIterator(createIterator, next);
desc.value = function() {
return new SafeIterator(this);
};
}
Reflect.defineProperty(safe.prototype, key, desc);
}
ReflectDefineProperty(dest, `${prefix}${newKey}`, desc);
}
} else {
copyProps(unsafe.prototype, safe.prototype);
}
copyProps(unsafe, safe);

Object.setPrototypeOf(safe.prototype, null);
Object.freeze(safe.prototype);
Object.freeze(safe);
return safe;
}
primordials.makeSafe = makeSafe;

// Subclass the constructors because we need to use their prototype
// methods later.
// Defining the `constructor` is necessary here to avoid the default
// constructor which uses the user-mutable `%ArrayIteratorPrototype%.next`.
primordials.SafeMap = makeSafe(
Map,
class SafeMap extends Map {
constructor(i) { super(i); } // eslint-disable-line no-useless-constructor
}
);
primordials.SafeWeakMap = makeSafe(
WeakMap,
class SafeWeakMap extends WeakMap {
constructor(i) { super(i); } // eslint-disable-line no-useless-constructor
}
);
primordials.SafeSet = makeSafe(
Set,
class SafeSet extends Set {
constructor(i) { super(i); } // eslint-disable-line no-useless-constructor
}
);
primordials.SafeWeakSet = makeSafe(
WeakSet,
class SafeWeakSet extends WeakSet {
constructor(i) { super(i); } // eslint-disable-line no-useless-constructor
}
);

// Create copies of the namespace objects
[
Expand Down Expand Up @@ -256,6 +169,41 @@ primordials.SafeWeakSet = makeSafe(
copyPrototype(original.prototype, primordials, `${name}Prototype`);
});

/* eslint-enable node-core/prefer-primordials */

const {
ArrayPrototypeForEach,
FunctionPrototypeCall,
ExE-Boss marked this conversation as resolved.
Show resolved Hide resolved
Map,
ObjectFreeze,
ObjectSetPrototypeOf,
Set,
SymbolIterator,
WeakMap,
WeakSet,
} = primordials;

// Because these functions are used by `makeSafe`, which is exposed
// on the `primordials` object, it's important to use const references
// to the primordials that they use:
const createSafeIterator = (factory, next) => {
class SafeIterator {
constructor(iterable) {
this._iterator = factory(iterable);
}
next() {
return next(this._iterator);
}
[SymbolIterator]() {
return this;
}
}
ObjectSetPrototypeOf(SafeIterator.prototype, null);
ObjectFreeze(SafeIterator.prototype);
ObjectFreeze(SafeIterator);
return SafeIterator;
};

primordials.SafeArrayIterator = createSafeIterator(
primordials.ArrayPrototypeSymbolIterator,
primordials.ArrayIteratorPrototypeNext
Expand All @@ -265,5 +213,80 @@ primordials.SafeStringIterator = createSafeIterator(
primordials.StringIteratorPrototypeNext
);

Object.setPrototypeOf(primordials, null);
Object.freeze(primordials);
const copyProps = (src, dest) => {
ArrayPrototypeForEach(ReflectOwnKeys(src), (key) => {
if (!ReflectGetOwnPropertyDescriptor(dest, key)) {
ReflectDefineProperty(
dest,
key,
ReflectGetOwnPropertyDescriptor(src, key));
}
});
};

const makeSafe = (unsafe, safe) => {
if (SymbolIterator in unsafe.prototype) {
const dummy = new unsafe();
let next; // We can reuse the same `next` method.

ArrayPrototypeForEach(ReflectOwnKeys(unsafe.prototype), (key) => {
if (!ReflectGetOwnPropertyDescriptor(safe.prototype, key)) {
const desc = ReflectGetOwnPropertyDescriptor(unsafe.prototype, key);
if (
typeof desc.value === 'function' &&
desc.value.length === 0 &&
SymbolIterator in (FunctionPrototypeCall(desc.value, dummy) ?? {})
) {
const createIterator = uncurryThis(desc.value);
next ??= uncurryThis(createIterator(dummy).next);
const SafeIterator = createSafeIterator(createIterator, next);
desc.value = function() {
return new SafeIterator(this);
};
}
ReflectDefineProperty(safe.prototype, key, desc);
}
});
} else {
copyProps(unsafe.prototype, safe.prototype);
}
copyProps(unsafe, safe);

ObjectSetPrototypeOf(safe.prototype, null);
ObjectFreeze(safe.prototype);
ObjectFreeze(safe);
return safe;
};
primordials.makeSafe = makeSafe;

// Subclass the constructors because we need to use their prototype
// methods later.
// Defining the `constructor` is necessary here to avoid the default
// constructor which uses the user-mutable `%ArrayIteratorPrototype%.next`.
primordials.SafeMap = makeSafe(
Map,
class SafeMap extends Map {
constructor(i) { super(i); } // eslint-disable-line no-useless-constructor
}
);
primordials.SafeWeakMap = makeSafe(
WeakMap,
class SafeWeakMap extends WeakMap {
constructor(i) { super(i); } // eslint-disable-line no-useless-constructor
}
);
primordials.SafeSet = makeSafe(
Set,
class SafeSet extends Set {
constructor(i) { super(i); } // eslint-disable-line no-useless-constructor
}
);
primordials.SafeWeakSet = makeSafe(
WeakSet,
class SafeWeakSet extends WeakSet {
constructor(i) { super(i); } // eslint-disable-line no-useless-constructor
}
);

ObjectSetPrototypeOf(primordials, null);
aduh95 marked this conversation as resolved.
Show resolved Hide resolved
ObjectFreeze(primordials);