Handle abstract toString casting in pure additionalFunctions

Summary:
Release notes: none

When attempting to do `String(props.title)` on an abstract value, it currently throws a FatalError: `PP0001: This operation is not yet supported on abstract value`. Ideally, when we're in pure evaluation we can mark this as dirty too like we do other abstract values. I've added a test case to show this failing.
Closes https://github.com/facebook/prepack/pull/1361

Reviewed By: sebmarkbage

Differential Revision: D6849468

Pulled By: trueadm

fbshipit-source-id: c75d2f8c869e1f892ae0a330c3c2bd55ca2a9e6e
This commit is contained in:
Dominic Gannaway 2018-01-30 13:59:52 -08:00 committed by Facebook Github Bot
parent 7ab0690c26
commit 5fa081bcbe
6 changed files with 132 additions and 35 deletions

View File

@ -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");
});

View File

@ -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<BabelNodeExpression | BabelNodeSpreadElement>);
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<BabelNodeExpression | BabelNodeSpreadElement>);
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);
}
}

View File

@ -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();

View File

@ -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; };

View File

@ -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 (
<div>{String(props.title)}</div>
);
}
App.getTrials = function(renderer, Root) {
renderer.update(<Root />);
return [['simple render', renderer.toJSON()]];
};
if (this.__registerReactComponentRoot) {
__registerReactComponentRoot(App);
}
module.exports = App;

View File

@ -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;
}