diff --git a/src/evaluators.js b/src/evaluators.js index d7fd73b..b24f641 100644 --- a/src/evaluators.js +++ b/src/evaluators.js @@ -190,7 +190,7 @@ export function createSafeEvaluatorWhichTakesEndowments(safeEvaluatorFactory) { * A safe version of the native Function which relies on * the safety of evalEvaluator for confinement. */ -export function createFunctionEvaluator(unsafeRec, safeEval) { +export function createFunctionEvaluator(unsafeRec, safeEval, realmGlobal) { const { unsafeFunction, unsafeGlobal } = unsafeRec; const safeFunction = function Function(...params) { @@ -247,8 +247,24 @@ export function createFunctionEvaluator(unsafeRec, safeEval) { } const src = `(function(${functionParams}){\n${functionBody}\n})`; - - return safeEval(src); + const isStrict = !!/^\s*['|"]use strict['|"]/.exec(functionBody); + const fn = safeEval(src); + if (isStrict) { + return fn; + } + // we fix the `this` binding in Function(). + // note: In non-strict mode, if `this` is not object + // it will be wrapped by Object(). Like 1 becomes Object(1) which is Number(1) + // Should we simulate this? It seems like no one will rely on this. + const bindThis = `(function (globalThis, f) { + function f2() { + return Reflect.apply(f, this === undefined ? globalThis : this, arguments); + } + f2.toString = () => f.toString(); + return f2; +})`; + const fnWithThis = safeEval(bindThis)(realmGlobal, fn); + return fnWithThis; }; // Ensure that Function from any compartment in a root realm can be used diff --git a/src/realm.js b/src/realm.js index 6f03266..182df44 100644 --- a/src/realm.js +++ b/src/realm.js @@ -65,7 +65,7 @@ function createRealmRec(unsafeRec, transforms, sloppyGlobals) { const safeEvalWhichTakesEndowments = createSafeEvaluatorWhichTakesEndowments( safeEvaluatorFactory ); - const safeFunction = createFunctionEvaluator(unsafeRec, safeEval); + const safeFunction = createFunctionEvaluator(unsafeRec, safeEval, safeGlobal); setDefaultBindings(safeGlobal, safeEval, safeFunction); diff --git a/test/realm/test-confinement.js b/test/realm/test-confinement.js index 9ddf5a2..db0c038 100644 --- a/test/realm/test-confinement.js +++ b/test/realm/test-confinement.js @@ -1,22 +1,32 @@ import test from 'tape'; import Realm from '../../src/realm'; +test('non-strict mode this binding in Function constructor', t => { + t.plan(1); + + const r = Realm.makeRootRealm(); + t.equal(r.evaluate('(new Function("return this"))()'), r.global); +}); + test('confinement evaluation strict mode', t => { t.plan(2); const r = Realm.makeRootRealm(); t.equal(r.evaluate('(function() { return this })()'), undefined); - t.equal(r.evaluate('(new Function("return this"))()'), undefined); + t.equal( + r.evaluate(`(new Function('"use strict"; return this'))()`), + undefined + ); }); test('constructor this binding', t => { const r = Realm.makeRootRealm(); const F = r.evaluate('(new Function("return this"))'); - t.equal(F(), undefined); + t.equal(F(), r.global); t.equal(F.call(8), 8); - t.equal(F.call(undefined), undefined); + t.equal(F.call(undefined), r.global); t.equal(Reflect.apply(F, 8, []), 8); const x = { F };