🐛 keep toString always return original value for function in sandbox (#1785)

* fix: correct toString for functions

* Update src/sandbox/common.ts

* Update src/sandbox/common.ts

* handle getter in toString descriptor

* toString function predict

Co-authored-by: Kuitos <kuitos.lau@gmail.com>
This commit is contained in:
叡山电车 2021-11-04 17:19:36 +08:00 committed by GitHub
parent b86fffcdbe
commit e807cbcfb6
2 changed files with 39 additions and 0 deletions

View File

@ -59,4 +59,24 @@ describe('getTargetValue', () => {
const boundFn = getTargetValue(window, callableFunction);
expect(boundFn.prototype).toBe(callableFunction.prototype);
});
it("should work well while function's toString()'s return value keeps the same as the origin", () => {
function callableFunction1() {}
function callableFunction2() {}
function callableFunction3() {}
callableFunction2.toString = () => 'instance toString';
Object.defineProperty(callableFunction3, 'toString', {
get() {
return () => 'instance toString';
},
});
const boundFn1 = getTargetValue(window, callableFunction1);
const boundFn2 = getTargetValue(window, callableFunction2);
const boundFn3 = getTargetValue(window, callableFunction3);
expect(boundFn1.toString()).toBe(callableFunction1.toString());
expect(boundFn2.toString()).toBe(callableFunction2.toString());
expect(boundFn3.toString()).toBe(callableFunction3.toString());
});
});

View File

@ -51,6 +51,25 @@ export function getTargetValue(target: any, value: any): any {
Object.defineProperty(boundValue, 'prototype', { value: value.prototype, enumerable: false, writable: true });
}
// Some util, like `function isNative() { return typeof Ctor === 'function' && /native code/.test(Ctor.toString()) }` relies on the original `toString()` result
// but bound functions will always return "function() {[native code]}" for `toString`, which is misleading
if (typeof value.toString === 'function') {
const valueHasInstanceToString = value.hasOwnProperty('toString') && !boundValue.hasOwnProperty('toString');
const boundValueHasPrototypeToString = boundValue.toString === Function.prototype.toString;
if (valueHasInstanceToString || boundValueHasPrototypeToString) {
const originToStringDescriptor = Object.getOwnPropertyDescriptor(
valueHasInstanceToString ? value : Function.prototype,
'toString',
);
Object.defineProperty(boundValue, 'toString', {
...originToStringDescriptor,
...(originToStringDescriptor?.get ? null : { value: () => value.toString() }),
});
}
}
functionBoundedValueMap.set(value, boundValue);
return boundValue;
}