diff --git a/scripts/test-react.js b/scripts/test-react.js index 6d76254c8..0361edc80 100644 --- a/scripts/test-react.js +++ b/scripts/test-react.js @@ -142,6 +142,10 @@ function runTestSuite(outputJsx) { await runTest(directory, "simple-5.js"); }); + it("Simple 6", async () => { + await runTest(directory, "simple-6.js"); + }); + it("Simple children", async () => { await runTest(directory, "simple-children.js"); }); diff --git a/src/evaluators/CallExpression.js b/src/evaluators/CallExpression.js index ed611e8a0..0b75b51a4 100644 --- a/src/evaluators/CallExpression.js +++ b/src/evaluators/CallExpression.js @@ -98,6 +98,76 @@ function callBothFunctionsAndJoinTheirEffects( return completion; } +function generateRuntimeCall( + ref: Value | Reference, + func: Value, + ast: BabelNodeCallExpression, + strictCode: boolean, + env: LexicalEnvironment, + realm: Realm +) { + let args = [func]; + let [thisArg, propName] = ref instanceof Reference ? [ref.base, ref.referencedName] : []; + if (thisArg instanceof Value) args = [thisArg]; + if (propName !== undefined && typeof propName !== "string") args.push(propName); + args = args.concat(ArgumentListEvaluation(realm, strictCode, env, ast.arguments)); + for (let arg of args) { + if (arg !== func) { + Leak.leakValue(realm, arg, ast.loc); + } + } + return AbstractValue.createTemporalFromBuildFunction(realm, Value, args, nodes => { + let callFunc; + let argStart = 1; + if (thisArg instanceof Value) { + if (typeof propName === "string") { + callFunc = t.memberExpression(nodes[0], t.identifier(propName), !t.isValidIdentifier(propName)); + } else { + callFunc = t.memberExpression(nodes[0], nodes[1], true); + argStart = 2; + } + } else { + callFunc = nodes[0]; + } + let fun_args = ((nodes.slice(argStart): any): Array); + return t.callExpression(callFunc, fun_args); + }); +} + +function tryToEvaluateCallOrLeaveAsAbstract( + ref: Value | Reference, + func: Value, + ast: BabelNodeCallExpression, + strictCode: boolean, + env: LexicalEnvironment, + realm: Realm, + thisValue: Value, + tailCall: boolean +): Value { + let effects; + try { + effects = realm.evaluateForEffects(() => + EvaluateDirectCall(realm, strictCode, env, ref, func, thisValue, ast.arguments, tailCall) + ); + } catch (error) { + if (error instanceof FatalError) { + return realm.evaluateWithPossibleThrowCompletion( + () => generateRuntimeCall(ref, func, ast, strictCode, env, realm), + TypesDomain.topVal, + ValuesDomain.topVal + ); + } else { + throw error; + } + } + realm.applyEffects(effects); + let completion = effects[0]; + // return or throw completion + if (completion instanceof AbruptCompletion) throw completion; + invariant(completion instanceof Value); + return completion; +} + function EvaluateCall( ref: Value | Reference, func: Value, @@ -106,35 +176,6 @@ function EvaluateCall( env: LexicalEnvironment, realm: Realm ): Value { - function generateRuntimeCall() { - let args = [func]; - let [thisArg, propName] = ref instanceof Reference ? [ref.base, ref.referencedName] : []; - if (thisArg instanceof Value) args = [thisArg]; - if (propName !== undefined && typeof propName !== "string") args.push(propName); - args = args.concat(ArgumentListEvaluation(realm, strictCode, env, ast.arguments)); - for (let arg of args) { - if (arg !== func) { - Leak.leakValue(realm, arg, ast.loc); - } - } - return AbstractValue.createTemporalFromBuildFunction(realm, Value, args, nodes => { - let callFunc; - let argStart = 1; - if (thisArg instanceof Value) { - if (typeof propName === "string") { - callFunc = t.memberExpression(nodes[0], t.identifier(propName), !t.isValidIdentifier(propName)); - } else { - callFunc = t.memberExpression(nodes[0], nodes[1], true); - argStart = 2; - } - } else { - callFunc = nodes[0]; - } - let fun_args = ((nodes.slice(argStart): any): Array); - return t.callExpression(callFunc, fun_args); - }); - } - if (func instanceof AbstractValue) { let loc = ast.callee.type === "MemberExpression" ? ast.callee.property.loc : ast.callee.loc; if (!Value.isTypeCompatibleWith(func.getType(), FunctionValue)) { @@ -151,9 +192,13 @@ function EvaluateCall( } if (realm.isInPureScope()) { // In pure functions we allow abstract functions to throw, which this might. - return realm.evaluateWithPossibleThrowCompletion(generateRuntimeCall, TypesDomain.topVal, ValuesDomain.topVal); + return realm.evaluateWithPossibleThrowCompletion( + () => generateRuntimeCall(ref, func, ast, strictCode, env, realm), + TypesDomain.topVal, + ValuesDomain.topVal + ); } - return generateRuntimeCall(); + return generateRuntimeCall(ref, func, ast, strictCode, env, realm); } invariant(func instanceof ConcreteValue); @@ -181,7 +226,7 @@ function EvaluateCall( let error = new CompilerDiagnostic("eval argument must be a known value", loc, "PP0006", "RecoverableError"); if (realm.handleError(error) === "Fail") throw new FatalError(); // Assume that it is a safe eval with no visible heap changes or abrupt control flow. - return generateRuntimeCall(); + return generateRuntimeCall(ref, func, ast, strictCode, env, realm); } return Functions.PerformEval(realm, evalText, evalRealm, strictCaller, true); } @@ -217,5 +262,9 @@ function EvaluateCall( let tailCall = IsInTailPosition(realm, thisCall); // 8. Return ? EvaluateDirectCall(func, thisValue, Arguments, tailCall). - return EvaluateDirectCall(realm, strictCode, env, ref, func, thisValue, ast.arguments, tailCall); + if (realm.isInPureScope()) { + return tryToEvaluateCallOrLeaveAsAbstract(ref, func, ast, strictCode, env, realm, thisValue, tailCall); + } else { + return EvaluateDirectCall(realm, strictCode, env, ref, func, thisValue, ast.arguments, tailCall); + } } diff --git a/src/serializer/ResidualHeapSerializer.js b/src/serializer/ResidualHeapSerializer.js index b9b91c792..dfceecb30 100644 --- a/src/serializer/ResidualHeapSerializer.js +++ b/src/serializer/ResidualHeapSerializer.js @@ -1737,7 +1737,6 @@ export class ResidualHeapSerializer { invariant(this.emitter._declaredAbstractValues.size <= this.preludeGenerator.derivedIds.size); this.postGeneratorSerialization(); - Array.prototype.push.apply(this.prelude, this.preludeGenerator.prelude); // TODO #20: add timers @@ -1749,6 +1748,8 @@ export class ResidualHeapSerializer { // Make sure additional functions get serialized. let rewrittenAdditionalFunctions = this.processAdditionalFunctionValues(); + Array.prototype.push.apply(this.prelude, this.preludeGenerator.prelude); + this.modules.resolveInitializedModules(); this.emitter.finalize(); diff --git a/test/error-handler/try-and-call-abstract-function.js b/test/error-handler/try-and-call-abstract-function.js index c7c113399..13d776c65 100644 --- a/test/error-handler/try-and-call-abstract-function.js +++ b/test/error-handler/try-and-call-abstract-function.js @@ -1,6 +1,7 @@ // additional functions // abstract effects -// expected errors: [{location: {"start":{"line":26,"column":11},"end":{"line":26,"column":21},"identifierName":"abstractFn","source":"test/error-handler/try-and-call-abstract-function.js"}, errorCode: "PP0021", severity: "RecoverableError", message: "Possible throw inside try/catch is not yet supported"}] +// expected errors: [{location: {"start":{"line":27,"column":11},"end":{"line":27,"column":21},"identifierName":"abstractFn","source":"test/error-handler/try-and-call-abstract-function.js"}, errorCode: "PP0021", severity: "RecoverableError", message: "Possible throw inside try/catch is not yet supported"}] +// recover-from-errors let abstractFn = global.__abstract ? __abstract('function', '(function() { return true; })') : function() { return true; }; diff --git a/test/react/functional-components/simple-6.js b/test/react/functional-components/simple-6.js new file mode 100644 index 000000000..857f99c17 --- /dev/null +++ b/test/react/functional-components/simple-6.js @@ -0,0 +1,20 @@ +var React = require('react'); +// the JSX transform converts to React, so we need to add it back in +this['React'] = React; + +function App(props) { + return ( +
{String(props.title)}
+ ); +} + +App.getTrials = function(renderer, Root) { + renderer.update(); + return [['simple render', renderer.toJSON()]]; +}; + +if (this.__registerReactComponentRoot) { + __registerReactComponentRoot(App); +} + +module.exports = App; \ No newline at end of file diff --git a/test/serializer/pure-functions/CastStringOnUnknown.js b/test/serializer/pure-functions/CastStringOnUnknown.js new file mode 100644 index 000000000..9ca70ddee --- /dev/null +++ b/test/serializer/pure-functions/CastStringOnUnknown.js @@ -0,0 +1,22 @@ +// additional functions +// abstract effects + +let obj1 = global.__abstract ? __abstract('object', '({get foo() { return "bar"; }})') : {get foo() { return "bar"; }}; +let obj2 = global.__abstract ? __abstract('object', '({foo:{bar:"baz"}})') : {foo:{bar:"baz"}}; +if (global.__makeSimple) { + __makeSimple(obj2); +} + +function additional1() { + return String(obj1.foo); +} + +function additional2() { + return String(obj2.foo.bar); +} + +inspect = function() { + let ret1 = additional1(); + let ret2 = additional2(); + return ret1 + ret2; +}