From c80f082827b1965a9182c0d53036eac6c77ee36c Mon Sep 17 00:00:00 2001 From: John-David Dalton Date: Fri, 16 Sep 2022 13:52:27 -0700 Subject: [PATCH] fix: @W-11771403 support proxies that have get traps but not getOwnPropertyDescriptor traps (#392) --- packages/near-membrane-base/src/membrane.ts | 61 ++++++++++++++++----- test/membrane/get-trap.spec.js | 23 ++++++++ test/membrane/ownKeys-trap.spec.js | 30 ++++++++++ test/membrane/ownKeys.spec.js | 28 ---------- 4 files changed, 100 insertions(+), 42 deletions(-) create mode 100644 test/membrane/get-trap.spec.js create mode 100644 test/membrane/ownKeys-trap.spec.js delete mode 100644 test/membrane/ownKeys.spec.js diff --git a/packages/near-membrane-base/src/membrane.ts b/packages/near-membrane-base/src/membrane.ts index 775a5004..f21e9eea 100644 --- a/packages/near-membrane-base/src/membrane.ts +++ b/packages/near-membrane-base/src/membrane.ts @@ -2623,14 +2623,23 @@ export function createMembraneMarshall( } else { result = localValue; } - } else if ( - key === SymbolToStringTag && - foreignTargetTraits & TargetTraits.IsObject - ) { - let toStringTag; + } else { + const transferableReceiver = + proxy === receiver + ? foreignTargetPointer + : // Inline getTransferableValue(). + (typeof receiver === 'object' && receiver !== null) || + typeof receiver === 'function' + ? getTransferablePointer(receiver) + : receiver; + let pointerOrPrimitive: PointerOrPrimitive; try { - toStringTag = - foreignCallableGetToStringTagOfTarget(foreignTargetPointer); + pointerOrPrimitive = foreignCallableGet( + foreignTargetPointer, + foreignTargetTraits, + key, + transferableReceiver + ); } catch (error: any) { const errorToThrow = selectedTarget ?? error; selectedTarget = undefined; @@ -2639,12 +2648,37 @@ export function createMembraneMarshall( } throw errorToThrow; } - // The default language toStringTag is "Object". If we - // receive "Object" we return `undefined` to let the - // language resolve it naturally without projecting a - // value. - if (toStringTag !== 'Object') { - result = toStringTag; + if (typeof pointerOrPrimitive === 'function') { + pointerOrPrimitive(); + result = selectedTarget; + selectedTarget = undefined; + } else { + result = pointerOrPrimitive; + } + if ( + result === undefined && + key === SymbolToStringTag && + foreignTargetTraits & TargetTraits.IsObject + ) { + let toStringTag; + try { + toStringTag = + foreignCallableGetToStringTagOfTarget(foreignTargetPointer); + } catch (error: any) { + const errorToThrow = selectedTarget ?? error; + selectedTarget = undefined; + if (LOCKER_DEBUG_MODE_INSTRUMENTATION_FLAG) { + activity!.error(errorToThrow); + } + throw errorToThrow; + } + // The default language toStringTag is "Object". If we + // receive "Object" we return `undefined` to let the + // language resolve it naturally without projecting a + // value. + if (toStringTag !== 'Object') { + result = toStringTag; + } } } if (LOCKER_DEBUG_MODE_INSTRUMENTATION_FLAG) { @@ -2699,7 +2733,6 @@ export function createMembraneMarshall( ); if (safeDesc) { const { get: getter, value: localValue } = safeDesc; - if (getter) { if (safeDesc.foreign) { const foreignGetterPointer = getTransferablePointer(getter); diff --git a/test/membrane/get-trap.spec.js b/test/membrane/get-trap.spec.js new file mode 100644 index 00000000..ad48ebb1 --- /dev/null +++ b/test/membrane/get-trap.spec.js @@ -0,0 +1,23 @@ +import createVirtualEnvironment from '@locker/near-membrane-dom'; + +describe('get trap', () => { + it('does retrieve values when missing a getOwnPropertyDescriptor trap', () => { + const ve = createVirtualEnvironment(window); + const get = ve.evaluate('(object, key) => object[key]'); + const proxiedObject = new Proxy( + {}, + { + get() { + return 1; + }, + } + ); + const proxiedTypedArray = new Proxy(new Uint8Array(), { + get() { + return 1; + }, + }); + expect(get(proxiedObject, 'a')).toBe(1); + expect(get(proxiedTypedArray, 'a')).toBe(1); + }); +}); diff --git a/test/membrane/ownKeys-trap.spec.js b/test/membrane/ownKeys-trap.spec.js new file mode 100644 index 00000000..065f0cc5 --- /dev/null +++ b/test/membrane/ownKeys-trap.spec.js @@ -0,0 +1,30 @@ +import createVirtualEnvironment from '@locker/near-membrane-dom'; + +let exported; +function exportData(arg) { + exported = arg; +} + +describe('ownKeys trap', () => { + it('does not throw ownKeys trap invariant for classes or strict mode functions', () => { + const envOne = createVirtualEnvironment(window, { + endowments: Object.getOwnPropertyDescriptors({ exportData }), + }); + envOne.evaluate(` + exportData([ + class Foo {}, + function() {'use strict'} + ]); + `); + const envTwo = createVirtualEnvironment(window, { + endowments: Object.getOwnPropertyDescriptors({ exportData, imported: exported }), + }); + envTwo.evaluate(` + exportData(imported.map(Reflect.ownKeys)); + `); + const matcher = jasmine.arrayWithExactContents(['length', 'name', 'prototype']); + exported.forEach((ownKeys) => { + expect(ownKeys).toEqual(matcher); + }); + }); +}); diff --git a/test/membrane/ownKeys.spec.js b/test/membrane/ownKeys.spec.js deleted file mode 100644 index 21fde0f6..00000000 --- a/test/membrane/ownKeys.spec.js +++ /dev/null @@ -1,28 +0,0 @@ -import createVirtualEnvironment from '@locker/near-membrane-dom'; - -let exported; -function exportData(arg) { - exported = arg; -} - -it('does not throw ownKeys trap invariant for classes or strict mode functions', () => { - const envOne = createVirtualEnvironment(window, { - endowments: Object.getOwnPropertyDescriptors({ exportData }), - }); - envOne.evaluate(` - exportData([ - class Foo {}, - function() {'use strict'} - ]); - `); - const envTwo = createVirtualEnvironment(window, { - endowments: Object.getOwnPropertyDescriptors({ exportData, imported: exported }), - }); - envTwo.evaluate(` - exportData(imported.map(Reflect.ownKeys)); - `); - const matcher = jasmine.arrayWithExactContents(['length', 'name', 'prototype']); - exported.forEach((ownKeys) => { - expect(ownKeys).toEqual(matcher); - }); -});