mirror of
https://github.com/facebookarchive/prepack.git
synced 2024-10-27 15:47:30 +03:00
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:
parent
7ab0690c26
commit
5fa081bcbe
@ -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");
|
||||
});
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
|
@ -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; };
|
||||
|
||||
|
20
test/react/functional-components/simple-6.js
Normal file
20
test/react/functional-components/simple-6.js
Normal 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;
|
22
test/serializer/pure-functions/CastStringOnUnknown.js
Normal file
22
test/serializer/pure-functions/CastStringOnUnknown.js
Normal 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;
|
||||
}
|
Loading…
Reference in New Issue
Block a user