From 7ab7fd37e6ccd5dbf8685eecb1e3cad477114e85 Mon Sep 17 00:00:00 2001 From: Gal Schlezinger Date: Thu, 28 Nov 2024 15:22:21 +0200 Subject: [PATCH] fix ({}).constructor===Object this resolves #899 with a caveat that an object that was created outside of the VM will obviously won't satisfy this requirement. However, it will still be `instaceof Object` which is interesting. I am not sure it'll be easy to solve. --- packages/vm/src/edge-vm.ts | 35 ++++++++++++++++++++++++++-- packages/vm/tests/instanceof.test.ts | 29 +++++++++++++++++++++++ 2 files changed, 62 insertions(+), 2 deletions(-) diff --git a/packages/vm/src/edge-vm.ts b/packages/vm/src/edge-vm.ts index de430e82..2ad14536 100644 --- a/packages/vm/src/edge-vm.ts +++ b/packages/vm/src/edge-vm.ts @@ -117,13 +117,25 @@ const transferableConstructors = [ 'TypeError', ] as const -function patchInstanceOf(item: string, ctx: any) { +const patchedPrototypes = new Set<(typeof transferableConstructors)[number]>([ + 'Array', + 'Object', + 'RegExp', +]) + +function patchInstanceOf( + item: (typeof transferableConstructors)[number], + ctx: any, +) { // @ts-ignore ctx[Symbol.for(`node:${item}`)] = eval(item) + const shouldPatchPrototype = patchedPrototypes.has(item) + return runInContext( ` - globalThis.${item} = new Proxy(${item}, { + (() => { + const proxy = new Proxy(${item}, { get(target, prop, receiver) { if (prop === Symbol.hasInstance && receiver === globalThis.${item}) { const nodeTarget = globalThis[Symbol.for('node:${item}')]; @@ -137,8 +149,27 @@ function patchInstanceOf(item: string, ctx: any) { } return Reflect.get(target, prop, receiver); + }, + construct(target, args, newTarget) { + return Object.assign( + Reflect.construct(target, args, newTarget), + { constructor: proxy } + ); } }) + + globalThis.${item} = proxy; + + ${ + !shouldPatchPrototype + ? '' + : `Object.assign(globalThis.${item}.prototype, { + get constructor() { + return proxy; + } + })` + } + })() `, ctx, ) diff --git a/packages/vm/tests/instanceof.test.ts b/packages/vm/tests/instanceof.test.ts index 2ec4390b..8e3b5347 100644 --- a/packages/vm/tests/instanceof.test.ts +++ b/packages/vm/tests/instanceof.test.ts @@ -91,6 +91,35 @@ it('handles prototype chain correctly', () => { }) }) +describe('.constructor ===', () => { + describe('created inside the vm', () => { + test('new Object().constructor === Object', () => { + const vm = new EdgeVM() + expect(vm.evaluate(`new Object().constructor === Object`)).toBe(true) + }) + test('({}).constructor === Object', () => { + const vm = new EdgeVM() + expect(vm.evaluate(`({}).constructor === Object`)).toBe(true) + }) + test('new Array().constructor === Array', () => { + const vm = new EdgeVM() + expect(vm.evaluate(`new Array().constructor === Array`)).toBe(true) + }) + test('[].constructor === Array', () => { + const vm = new EdgeVM() + expect(vm.evaluate(`[].constructor === Array`)).toBe(true) + }) + test('new RegExp("").constructor === Array', () => { + const vm = new EdgeVM() + expect(vm.evaluate(`new RegExp("").constructor === RegExp`)).toBe(true) + }) + test('[].constructor === Array', () => { + const vm = new EdgeVM() + expect(vm.evaluate(`/./.constructor === RegExp`)).toBe(true) + }) + }) +}) + describe('instanceof overriding', () => { test('binary array created outside of the VM is `instanceof` Object inside the VM', () => { const vm = new EdgeVM()