diff --git a/packages/vm/src/edge-vm.ts b/packages/vm/src/edge-vm.ts index 491b50b2..08e43ef1 100644 --- a/packages/vm/src/edge-vm.ts +++ b/packages/vm/src/edge-vm.ts @@ -117,13 +117,31 @@ 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) - return runInContext( + const patchPrototype = !patchedPrototypes.has(item) + ? '' + : `Object.assign(globalThis.${item}.prototype, { + get constructor() { + return proxy; + } + })` + + 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 +155,22 @@ function patchInstanceOf(item: string, ctx: any) { } return Reflect.get(target, prop, receiver); + }, + construct(target, args, newTarget) { + const value = Reflect.construct(target, args, newTarget) + Object.defineProperty(value, 'constructor', { + value: proxy, + writable: true, + configurable: true, + enumerable: false, + }) + return value; } }) + + globalThis.${item} = proxy; + ${patchPrototype} + })() `, 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() diff --git a/packages/vm/tests/integration/error.test.ts b/packages/vm/tests/integration/error.test.ts index f2e210e5..24a4886f 100644 --- a/packages/vm/tests/integration/error.test.ts +++ b/packages/vm/tests/integration/error.test.ts @@ -12,7 +12,7 @@ test('Error maintains a stack trace', async () => { vm.evaluate(`(${fn})()`) expect(log).toHaveBeenCalledTimes(1) - expect(log.mock.lastCall[0]).toMatch(/^Error: hello, world!\s+at fn/m) + expect(log.mock.lastCall[0]).toMatch(/^Error: hello, world!\s+at Object/m) }) test('additional error properties', async () => {