From bf0ed5917441a744fe4b5bddb93883cfa193e837 Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Thu, 19 Jul 2018 15:34:29 -0700 Subject: [PATCH] Re-design of the buildNode logic for AbstractValue and GeneratorEntries (#2296) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Summary: Release notes: much of the buildNode and inline Babel logic has been moved to a dedicated ResidualOperationSerializer class Note: this PR is like *phase 1* of many phases to move around buildNodes and their logic. Furthermore, the ideas is to move all Babel coupled logic to the serialization phase and away from the generator and other generic areas. I've explained this more in detail below. This PR moves much of the Babel logic involving build nodes, that was coupled to inline closures through many parts of the Prepack code base, to its own class `ResidualOperationSerializer`. We now use a helper function `createOperationDescriptor` to create `OperationDescriptor`s. `OperationDescriptor`s exist on both `AbstractValue` and `GeneratorEntry`, and replaces the old `buildNode` and `_buildNode` properties and unifies all the logic. The `buildNode` field has now changed to `operationDescriptor` respectfully. We now have a `OperationDescriptorType` enum that tells the class which build node to use, along with some associated data that is needed to construct the build node (previously this data was captured in the closure). The idea is to remove the data/kind arguments from `createOperationDescriptor` in follow up PRs, but that will take quite a bit more work, thus why this is a phase 1 PR. In many cases, we now have no instances of importing Babel in many of the files and this unifies much (but not all) of the Babel node creation logic into a single file – allowing for easy extension in the future. This PR took forever to do and is very large, so there might be bits that I missed, but hopefully I haven't. All tests etc pass locally. Pull Request resolved: https://github.com/facebook/prepack/pull/2296 Differential Revision: D8923889 Pulled By: trueadm fbshipit-source-id: aa3a27efd97f10cc941159735d598199deb24f52 --- src/environment.js | 9 +- src/evaluators/BinaryExpression.js | 11 +- src/evaluators/CallExpression.js | 29 +- src/evaluators/ForStatement.js | 6 +- src/evaluators/NewExpression.js | 7 +- src/evaluators/UpdateExpression.js | 9 +- src/intrinsics/ecma262/Array.js | 4 +- src/intrinsics/ecma262/ArrayProto_toString.js | 9 +- src/intrinsics/ecma262/ArrayProto_values.js | 9 +- src/intrinsics/ecma262/ArrayPrototype.js | 181 +++- src/intrinsics/ecma262/Object.js | 20 +- src/intrinsics/ecma262/ObjectPrototype.js | 7 +- src/intrinsics/fb-www/fb-mocks.js | 16 +- src/intrinsics/fb-www/react-dom-mocks.js | 16 +- src/intrinsics/fb-www/react-mocks.js | 16 +- src/intrinsics/fb-www/react-native-mocks.js | 8 +- src/intrinsics/fb-www/relay-mocks.js | 13 +- src/intrinsics/prepack/global.js | 25 +- src/intrinsics/prepack/utils.js | 4 +- src/methods/arraybuffer.js | 6 +- src/methods/call.js | 31 +- src/methods/environment.js | 2 +- src/methods/function.js | 2 +- src/methods/get.js | 7 +- src/methods/json.js | 8 +- src/methods/promise.js | 12 +- src/methods/properties.js | 28 +- src/methods/to.js | 6 +- src/methods/typedarray.js | 10 +- src/methods/widen.js | 45 +- src/react/ReactEquivalenceSet.js | 2 +- src/react/branching.js | 5 +- src/react/components.js | 2 +- src/react/elements.js | 6 +- .../rendering.js | 23 +- src/react/jsx.js | 2 +- src/react/reconcilation.js | 9 +- src/realm.js | 103 +- src/serializer/Emitter.js | 2 +- .../ResidualFunctionInstantiator.js | 2 +- src/serializer/ResidualHeapSerializer.js | 33 +- src/serializer/ResidualOperationSerializer.js | 998 ++++++++++++++++++ .../ResidualReactElementSerializer.js | 2 +- src/serializer/utils.js | 2 +- src/types.js | 2 +- src/utils/HeapInspector.js | 2 +- src/utils/generator.js | 536 +++++----- src/utils/modules.js | 10 +- src/utils/parse.js | 2 +- src/utils/simplifier.js | 9 +- src/values/AbstractObjectValue.js | 52 +- src/values/AbstractValue.js | 113 +- src/values/ArgumentsExotic.js | 4 +- src/values/ArrayValue.js | 6 +- src/values/ECMAScriptSourceFunctionValue.js | 2 +- src/values/IntegerIndexedExotic.js | 4 +- src/values/ObjectValue.js | 29 +- src/values/StringExotic.js | 4 +- src/values/StringValue.js | 2 +- src/values/index.js | 2 +- 60 files changed, 1747 insertions(+), 779 deletions(-) create mode 100644 src/serializer/ResidualOperationSerializer.js diff --git a/src/environment.js b/src/environment.js index 99f73c52b..7b00e9b29 100644 --- a/src/environment.js +++ b/src/environment.js @@ -20,10 +20,11 @@ import type { } from "@babel/types"; import type { Realm } from "./realm.js"; import type { SourceFile, SourceMap, SourceType } from "./types.js"; +import * as t from "@babel/types"; import { AbruptCompletion, Completion, ThrowCompletion } from "./completions.js"; import { CompilerDiagnostic, FatalError } from "./errors.js"; -import { defaultOptions } from "./options"; +import { defaultOptions } from "./options.js"; import type { PartialEvaluatorOptions } from "./options"; import { ExecutionContext } from "./realm.js"; import { @@ -46,9 +47,9 @@ import invariant from "./invariant.js"; import traverseFast from "./utils/traverse-fast.js"; import { HasProperty, Get, IsExtensible, HasOwnProperty, IsDataDescriptor } from "./methods/index.js"; import { Environment, Havoc, Properties, To } from "./singletons.js"; -import * as t from "@babel/types"; import { TypesDomain, ValuesDomain } from "./domains/index.js"; -import PrimitiveValue from "./values/PrimitiveValue"; +import PrimitiveValue from "./values/PrimitiveValue.js"; +import { createOperationDescriptor } from "./utils/generator.js"; const sourceMap = require("source-map"); @@ -56,7 +57,7 @@ function deriveGetBinding(realm: Realm, binding: Binding) { let types = TypesDomain.topVal; let values = ValuesDomain.topVal; invariant(realm.generator !== undefined); - return realm.generator.deriveAbstract(types, values, [], (_, context) => context.serializeBinding(binding)); + return realm.generator.deriveAbstract(types, values, [], createOperationDescriptor("GET_BINDING", { binding })); } export function havocBinding(binding: Binding): void { diff --git a/src/evaluators/BinaryExpression.js b/src/evaluators/BinaryExpression.js index 19b337bd5..93558d987 100644 --- a/src/evaluators/BinaryExpression.js +++ b/src/evaluators/BinaryExpression.js @@ -27,13 +27,8 @@ import { } from "../values/index.js"; import { AbruptCompletion, PossiblyNormalCompletion, SimpleNormalCompletion } from "../completions.js"; import { Environment, Havoc, To } from "../singletons.js"; -import type { - BabelBinaryOperator, - BabelNodeBinaryExpression, - BabelNodeExpression, - BabelNodeSourceLocation, -} from "@babel/types"; -import * as t from "@babel/types"; +import type { BabelBinaryOperator, BabelNodeBinaryExpression, BabelNodeSourceLocation } from "@babel/types"; +import { createOperationDescriptor } from "../utils/generator.js"; import invariant from "../invariant.js"; export default function( @@ -251,7 +246,7 @@ export function computeBinary( realm, resultType, [lval, rval], - ([lnode, rnode]: Array) => t.binaryExpression(op, lnode, rnode) + createOperationDescriptor("BINARY_EXPRESSION", { op }) ), TypesDomain.topVal, ValuesDomain.topVal diff --git a/src/evaluators/CallExpression.js b/src/evaluators/CallExpression.js index c30ce9ebe..6f8376167 100644 --- a/src/evaluators/CallExpression.js +++ b/src/evaluators/CallExpression.js @@ -34,11 +34,10 @@ import { IsInTailPosition, SameValue, } from "../methods/index.js"; -import type { BabelNodeCallExpression, BabelNodeExpression, BabelNodeSpreadElement } from "@babel/types"; +import type { BabelNodeCallExpression } from "@babel/types"; import invariant from "../invariant.js"; -import * as t from "@babel/types"; -import SuperCall from "./SuperCall"; -import { memberExpressionHelper } from "../utils/babelhelpers.js"; +import SuperCall from "./SuperCall.js"; +import { createOperationDescriptor } from "../utils/generator.js"; export default function( ast: BabelNodeCallExpression, @@ -235,22 +234,12 @@ function generateRuntimeCall( } } let resultType = (func instanceof AbstractObjectValue ? func.functionResultType : undefined) || Value; - return AbstractValue.createTemporalFromBuildFunction(realm, resultType, args, nodes => { - let callFunc; - let argStart = 1; - if (thisArg instanceof Value) { - if (typeof propName === "string") { - callFunc = memberExpressionHelper(nodes[0], propName); - } else { - callFunc = memberExpressionHelper(nodes[0], nodes[1]); - argStart = 2; - } - } else { - callFunc = nodes[0]; - } - let fun_args = ((nodes.slice(argStart): any): Array); - return t.callExpression(callFunc, fun_args); - }); + return AbstractValue.createTemporalFromBuildFunction( + realm, + resultType, + args, + createOperationDescriptor("CALL_BAILOUT", { propRef: propName, thisArg }) + ); } function tryToEvaluateCallOrLeaveAsAbstract( diff --git a/src/evaluators/ForStatement.js b/src/evaluators/ForStatement.js index f1b4a8e52..70fc3888e 100644 --- a/src/evaluators/ForStatement.js +++ b/src/evaluators/ForStatement.js @@ -40,6 +40,7 @@ import invariant from "../invariant.js"; import * as t from "@babel/types"; import type { FunctionBodyAstNode } from "../types.js"; import type { BabelNodeExpression, BabelNodeForStatement, BabelNodeBlockStatement } from "@babel/types"; +import { createOperationDescriptor } from "../utils/generator.js"; type BailOutWrapperInfo = { usesArguments: boolean, @@ -455,10 +456,7 @@ function generateRuntimeForStatement( realm, Value, args, - ([func, thisExpr]) => - usesThis - ? t.callExpression(t.memberExpression(func, t.identifier("call")), [thisExpr]) - : t.callExpression(func, []) + createOperationDescriptor("FOR_STATEMENT_FUNC", { usesThis }) ); invariant(wrapperValue instanceof AbstractValue); return wrapperValue; diff --git a/src/evaluators/NewExpression.js b/src/evaluators/NewExpression.js index 32a30de7b..e2b2776b8 100644 --- a/src/evaluators/NewExpression.js +++ b/src/evaluators/NewExpression.js @@ -19,8 +19,8 @@ import { IsConstructor, ArgumentListEvaluation } from "../methods/index.js"; import { Construct } from "../methods/index.js"; import invariant from "../invariant.js"; import { FatalError } from "../errors.js"; -import * as t from "@babel/types"; -import { BabelNodeNewExpression, type BabelNodeExpression } from "@babel/types"; +import { BabelNodeNewExpression } from "@babel/types"; +import { createOperationDescriptor } from "../utils/generator.js"; export default function( ast: BabelNodeNewExpression, @@ -102,8 +102,7 @@ function tryToEvaluateConstructOrLeaveAsAbstract( realm, ObjectValue, [constructor, ...argsList], - ([constructorNode, ...argListNodes]: Array) => - t.newExpression(constructorNode, argListNodes) + createOperationDescriptor("NEW_EXPRESSION") ), TypesDomain.topVal, ValuesDomain.topVal diff --git a/src/evaluators/UpdateExpression.js b/src/evaluators/UpdateExpression.js index 4401afaf8..319ce8a20 100644 --- a/src/evaluators/UpdateExpression.js +++ b/src/evaluators/UpdateExpression.js @@ -18,8 +18,8 @@ import { AbstractValue, NumberValue, IntegralValue } from "../values/index.js"; import type { BabelNodeUpdateExpression } from "@babel/types"; import { Environment, Havoc, Properties, To } from "../singletons.js"; import invariant from "../invariant.js"; -import * as t from "@babel/types"; import { ValuesDomain, TypesDomain } from "../domains/index.js"; +import { createOperationDescriptor } from "../utils/generator.js"; export default function( ast: BabelNodeUpdateExpression, @@ -46,8 +46,11 @@ export default function( Havoc.value(realm, oldExpr); newAbstractValue = realm.evaluateWithPossibleThrowCompletion( () => - AbstractValue.createTemporalFromBuildFunction(realm, NumberValue, [oldExpr], ([oldValNode]) => - t.binaryExpression(op, oldValNode, t.numericLiteral(1)) + AbstractValue.createTemporalFromBuildFunction( + realm, + NumberValue, + [oldExpr], + createOperationDescriptor("UPDATE_INCREMENTOR", { op }) ), TypesDomain.topVal, ValuesDomain.topVal diff --git a/src/intrinsics/ecma262/Array.js b/src/intrinsics/ecma262/Array.js index fa65c1a84..4ca1a407b 100644 --- a/src/intrinsics/ecma262/Array.js +++ b/src/intrinsics/ecma262/Array.js @@ -31,10 +31,10 @@ import { IsConstructor, IsCallable, } from "../../methods/index.js"; -import * as t from "@babel/types"; import { GetIterator, IteratorClose, IteratorStep, IteratorValue } from "../../methods/iterator.js"; import { Create, Havoc, Properties, To } from "../../singletons.js"; import invariant from "../../invariant.js"; +import { createOperationDescriptor } from "../../utils/generator.js"; export default function(realm: Realm): NativeFunctionValue { let func = new NativeFunctionValue(realm, "Array", "Array", 1, (context, [...items], argCount, NewTarget) => { @@ -247,7 +247,7 @@ export default function(realm: Realm): NativeFunctionValue { return ArrayValue.createTemporalWithWidenedNumericProperty( realm, args, - ([methodNode, ..._args]) => t.callExpression(methodNode, ((_args: any): Array)), + createOperationDescriptor("UNKNOWN_ARRAY_METHOD_CALL"), { func: mapfn, thisVal: thisArg } ); } diff --git a/src/intrinsics/ecma262/ArrayProto_toString.js b/src/intrinsics/ecma262/ArrayProto_toString.js index 9fef26e0d..2568ec6b2 100644 --- a/src/intrinsics/ecma262/ArrayProto_toString.js +++ b/src/intrinsics/ecma262/ArrayProto_toString.js @@ -15,7 +15,7 @@ import { To } from "../../singletons.js"; import { Get } from "../../methods/get.js"; import { Call } from "../../methods/call.js"; import { IsCallable } from "../../methods/is.js"; -import * as t from "@babel/types"; +import { createOperationDescriptor } from "../../utils/generator.js"; export default function(realm: Realm): NativeFunctionValue { // ECMA262 22.1.3.30 @@ -36,8 +36,11 @@ export default function(realm: Realm): NativeFunctionValue { realm.isInPureScope() && array.$GetOwnProperty("toString") === undefined ) { - return AbstractValue.createTemporalFromBuildFunction(realm, StringValue, [array], ([objNode]) => - t.callExpression(t.memberExpression(objNode, t.identifier("toString")), []) + return AbstractValue.createTemporalFromBuildFunction( + realm, + StringValue, + [array], + createOperationDescriptor("UNKNOWN_ARRAY_METHOD_PROPERTY_CALL", { propName: "toString" }) ); } diff --git a/src/intrinsics/ecma262/ArrayProto_values.js b/src/intrinsics/ecma262/ArrayProto_values.js index f02ae70a4..2f4e3a88c 100644 --- a/src/intrinsics/ecma262/ArrayProto_values.js +++ b/src/intrinsics/ecma262/ArrayProto_values.js @@ -12,7 +12,7 @@ import type { Realm } from "../../realm.js"; import { AbstractValue, ArrayValue, NativeFunctionValue, Value } from "../../values/index.js"; import { Create, To } from "../../singletons.js"; -import * as t from "@babel/types"; +import { createOperationDescriptor } from "../../utils/generator.js"; export default function(realm: Realm): NativeFunctionValue { // ECMA262 22.1.3.30 @@ -28,8 +28,11 @@ export default function(realm: Realm): NativeFunctionValue { realm.isInPureScope() && O.$GetOwnProperty("values") === undefined ) { - return AbstractValue.createTemporalFromBuildFunction(realm, Value, [O], ([objNode]) => - t.callExpression(t.memberExpression(objNode, t.identifier("values")), []) + return AbstractValue.createTemporalFromBuildFunction( + realm, + Value, + [O], + createOperationDescriptor("UNKNOWN_ARRAY_METHOD_PROPERTY_CALL", { propName: "values" }) ); } diff --git a/src/intrinsics/ecma262/ArrayPrototype.js b/src/intrinsics/ecma262/ArrayPrototype.js index 64b5e538f..f1b561ad5 100644 --- a/src/intrinsics/ecma262/ArrayPrototype.js +++ b/src/intrinsics/ecma262/ArrayPrototype.js @@ -23,7 +23,6 @@ import { Value, } from "../../values/index.js"; import invariant from "../../invariant.js"; -import * as t from "@babel/types"; import { SameValueZeroPartial, AbstractRelationalComparison } from "../../methods/abstract.js"; import { StrictEqualityComparisonPartial, @@ -38,6 +37,7 @@ import { HasSomeCompatibleType, } from "../../methods/index.js"; import { Create, Join, Properties, To } from "../../singletons.js"; +import { createOperationDescriptor } from "../../utils/generator.js"; export default function(realm: Realm, obj: ObjectValue): void { // ECMA262 22.1.3.31 @@ -57,8 +57,10 @@ export default function(realm: Realm, obj: ObjectValue): void { O.$GetOwnProperty("concat") === undefined ) { let newArgs = [O, ...args]; - return ArrayValue.createTemporalWithWidenedNumericProperty(realm, newArgs, ([objNode, ..._args]) => - t.callExpression(t.memberExpression(objNode, t.identifier("concat")), ((_args: any): Array)) + return ArrayValue.createTemporalWithWidenedNumericProperty( + realm, + newArgs, + createOperationDescriptor("UNKNOWN_ARRAY_METHOD_PROPERTY_CALL", { propName: "concat" }) ); } @@ -161,8 +163,11 @@ export default function(realm: Realm, obj: ObjectValue): void { if (end) { args.push(end); } - AbstractValue.createTemporalFromBuildFunction(realm, BooleanValue, args, ([objNode, ..._args]) => - t.callExpression(t.memberExpression(objNode, t.identifier("copyWithin")), ((_args: any): Array)) + AbstractValue.createTemporalFromBuildFunction( + realm, + BooleanValue, + args, + createOperationDescriptor("UNKNOWN_ARRAY_METHOD_PROPERTY_CALL", { propName: "copyWithin" }) ); return O; } @@ -258,8 +263,11 @@ export default function(realm: Realm, obj: ObjectValue): void { realm.isInPureScope() && O.$GetOwnProperty("entries") === undefined ) { - return AbstractValue.createTemporalFromBuildFunction(realm, Value, [O], ([objNode]) => - t.callExpression(t.memberExpression(objNode, t.identifier("entries")), []) + return AbstractValue.createTemporalFromBuildFunction( + realm, + Value, + [O], + createOperationDescriptor("UNKNOWN_ARRAY_METHOD_PROPERTY_CALL", { propName: "entries" }) ); } @@ -284,8 +292,11 @@ export default function(realm: Realm, obj: ObjectValue): void { if (thisArg) { args.push(thisArg); } - return AbstractValue.createTemporalFromBuildFunction(realm, BooleanValue, args, ([objNode, ..._args]) => - t.callExpression(t.memberExpression(objNode, t.identifier("every")), ((_args: any): Array)) + return AbstractValue.createTemporalFromBuildFunction( + realm, + BooleanValue, + args, + createOperationDescriptor("UNKNOWN_ARRAY_METHOD_PROPERTY_CALL", { propName: "every" }) ); } @@ -351,8 +362,11 @@ export default function(realm: Realm, obj: ObjectValue): void { if (end) { args.push(end); } - AbstractValue.createTemporalFromBuildFunction(realm, Value, args, ([objNode, ..._args]) => - t.callExpression(t.memberExpression(objNode, t.identifier("fill")), ((_args: any): Array)) + AbstractValue.createTemporalFromBuildFunction( + realm, + Value, + args, + createOperationDescriptor("UNKNOWN_ARRAY_METHOD_PROPERTY_CALL", { propName: "fill" }) ); return O; } @@ -405,8 +419,7 @@ export default function(realm: Realm, obj: ObjectValue): void { return ArrayValue.createTemporalWithWidenedNumericProperty( realm, args, - ([objNode, ..._args]) => - t.callExpression(t.memberExpression(objNode, t.identifier("filter")), ((_args: any): Array)), + createOperationDescriptor("UNKNOWN_ARRAY_METHOD_PROPERTY_CALL", { propName: "filter" }), { func: callbackfn, thisVal: thisArg } ); } @@ -482,8 +495,11 @@ export default function(realm: Realm, obj: ObjectValue): void { if (thisArg) { args.push(thisArg); } - return AbstractValue.createTemporalFromBuildFunction(realm, Value, args, ([objNode, ..._args]) => - t.callExpression(t.memberExpression(objNode, t.identifier("find")), ((_args: any): Array)) + return AbstractValue.createTemporalFromBuildFunction( + realm, + Value, + args, + createOperationDescriptor("UNKNOWN_ARRAY_METHOD_PROPERTY_CALL", { propName: "find" }) ); } @@ -540,8 +556,11 @@ export default function(realm: Realm, obj: ObjectValue): void { if (thisArg) { args.push(thisArg); } - return AbstractValue.createTemporalFromBuildFunction(realm, NumberValue, args, ([objNode, ..._args]) => - t.callExpression(t.memberExpression(objNode, t.identifier("findIndex")), ((_args: any): Array)) + return AbstractValue.createTemporalFromBuildFunction( + realm, + NumberValue, + args, + createOperationDescriptor("UNKNOWN_ARRAY_METHOD_PROPERTY_CALL", { propName: "findIndex" }) ); } @@ -598,8 +617,11 @@ export default function(realm: Realm, obj: ObjectValue): void { if (thisArg) { args.push(thisArg); } - AbstractValue.createTemporalFromBuildFunction(realm, BooleanValue, args, ([objNode, ..._args]) => - t.callExpression(t.memberExpression(objNode, t.identifier("forEach")), ((_args: any): Array)) + AbstractValue.createTemporalFromBuildFunction( + realm, + BooleanValue, + args, + createOperationDescriptor("UNKNOWN_ARRAY_METHOD_PROPERTY_CALL", { propName: "forEach" }) ); return realm.intrinsics.undefined; } @@ -661,8 +683,11 @@ export default function(realm: Realm, obj: ObjectValue): void { if (fromIndex) { args.push(fromIndex); } - return AbstractValue.createTemporalFromBuildFunction(realm, BooleanValue, args, ([objNode, ..._args]) => - t.callExpression(t.memberExpression(objNode, t.identifier("includes")), ((_args: any): Array)) + return AbstractValue.createTemporalFromBuildFunction( + realm, + BooleanValue, + args, + createOperationDescriptor("UNKNOWN_ARRAY_METHOD_PROPERTY_CALL", { propName: "includes" }) ); } @@ -721,8 +746,11 @@ export default function(realm: Realm, obj: ObjectValue): void { if (fromIndex) { args.push(fromIndex); } - return AbstractValue.createTemporalFromBuildFunction(realm, NumberValue, args, ([objNode, ..._args]) => - t.callExpression(t.memberExpression(objNode, t.identifier("indexOf")), ((_args: any): Array)) + return AbstractValue.createTemporalFromBuildFunction( + realm, + NumberValue, + args, + createOperationDescriptor("UNKNOWN_ARRAY_METHOD_PROPERTY_CALL", { propName: "indexOf" }) ); } @@ -794,8 +822,11 @@ export default function(realm: Realm, obj: ObjectValue): void { if (separator) { args.push(separator); } - return AbstractValue.createTemporalFromBuildFunction(realm, StringValue, args, ([objNode, ..._args]) => - t.callExpression(t.memberExpression(objNode, t.identifier("join")), ((_args: any): Array)) + return AbstractValue.createTemporalFromBuildFunction( + realm, + StringValue, + args, + createOperationDescriptor("UNKNOWN_ARRAY_METHOD_PROPERTY_CALL", { propName: "join" }) ); } @@ -865,8 +896,11 @@ export default function(realm: Realm, obj: ObjectValue): void { realm.isInPureScope() && O.$GetOwnProperty("keys") === undefined ) { - return AbstractValue.createTemporalFromBuildFunction(realm, Value, [O], ([objNode]) => - t.callExpression(t.memberExpression(objNode, t.identifier("keys")), []) + return AbstractValue.createTemporalFromBuildFunction( + realm, + Value, + [O], + createOperationDescriptor("UNKNOWN_ARRAY_METHOD_PROPERTY_CALL", { propName: "keys" }) ); } @@ -891,8 +925,11 @@ export default function(realm: Realm, obj: ObjectValue): void { if (fromIndex) { args.push(fromIndex); } - return AbstractValue.createTemporalFromBuildFunction(realm, NumberValue, args, ([objNode, ..._args]) => - t.callExpression(t.memberExpression(objNode, t.identifier("lastIndexOf")), ((_args: any): Array)) + return AbstractValue.createTemporalFromBuildFunction( + realm, + NumberValue, + args, + createOperationDescriptor("UNKNOWN_ARRAY_METHOD_PROPERTY_CALL", { propName: "lastIndexOf" }) ); } @@ -958,8 +995,7 @@ export default function(realm: Realm, obj: ObjectValue): void { return ArrayValue.createTemporalWithWidenedNumericProperty( realm, args, - ([objNode, ..._args]) => - t.callExpression(t.memberExpression(objNode, t.identifier("map")), ((_args: any): Array)), + createOperationDescriptor("UNKNOWN_ARRAY_METHOD_PROPERTY_CALL", { propName: "map" }), { func: callbackfn, thisVal: thisArg } ); } @@ -1045,8 +1081,11 @@ export default function(realm: Realm, obj: ObjectValue): void { realm.isInPureScope() && O.$GetOwnProperty("pop") === undefined ) { - return AbstractValue.createTemporalFromBuildFunction(realm, Value, [O], ([objNode]) => - t.callExpression(t.memberExpression(objNode, t.identifier("pop")), []) + return AbstractValue.createTemporalFromBuildFunction( + realm, + Value, + [O], + createOperationDescriptor("UNKNOWN_ARRAY_METHOD_PROPERTY_CALL", { propName: "pop" }) ); } @@ -1095,8 +1134,11 @@ export default function(realm: Realm, obj: ObjectValue): void { realm.isInPureScope() && O.$GetOwnProperty("push") === undefined ) { - return AbstractValue.createTemporalFromBuildFunction(realm, NumberValue, [O, ...args], ([objNode, ..._args]) => - t.callExpression(t.memberExpression(objNode, t.identifier("push")), ((_args: any): Array)) + return AbstractValue.createTemporalFromBuildFunction( + realm, + NumberValue, + [O, ...args], + createOperationDescriptor("UNKNOWN_ARRAY_METHOD_PROPERTY_CALL", { propName: "push" }) ); } @@ -1150,8 +1192,11 @@ export default function(realm: Realm, obj: ObjectValue): void { if (initialValue) { args.push(initialValue); } - return AbstractValue.createTemporalFromBuildFunction(realm, Value, args, ([objNode, ..._args]) => - t.callExpression(t.memberExpression(objNode, t.identifier("reduce")), ((_args: any): Array)) + return AbstractValue.createTemporalFromBuildFunction( + realm, + Value, + args, + createOperationDescriptor("UNKNOWN_ARRAY_METHOD_PROPERTY_CALL", { propName: "reduce" }) ); } @@ -1254,8 +1299,11 @@ export default function(realm: Realm, obj: ObjectValue): void { if (initialValue) { args.push(initialValue); } - return AbstractValue.createTemporalFromBuildFunction(realm, Value, args, ([objNode, ..._args]) => - t.callExpression(t.memberExpression(objNode, t.identifier("reduceRight")), ((_args: any): Array)) + return AbstractValue.createTemporalFromBuildFunction( + realm, + Value, + args, + createOperationDescriptor("UNKNOWN_ARRAY_METHOD_PROPERTY_CALL", { propName: "reduceRight" }) ); } @@ -1352,8 +1400,11 @@ export default function(realm: Realm, obj: ObjectValue): void { realm.isInPureScope() && O.$GetOwnProperty("reverse") === undefined ) { - AbstractValue.createTemporalFromBuildFunction(realm, ArrayValue, [O], ([objNode]) => - t.callExpression(t.memberExpression(objNode, t.identifier("reverse")), []) + AbstractValue.createTemporalFromBuildFunction( + realm, + ArrayValue, + [O], + createOperationDescriptor("UNKNOWN_ARRAY_METHOD_PROPERTY_CALL", { propName: "reverse" }) ); return O; } @@ -1452,8 +1503,11 @@ export default function(realm: Realm, obj: ObjectValue): void { realm.isInPureScope() && O.$GetOwnProperty("shift") === undefined ) { - return AbstractValue.createTemporalFromBuildFunction(realm, Value, [O], ([objNode]) => - t.callExpression(t.memberExpression(objNode, t.identifier("shift")), []) + return AbstractValue.createTemporalFromBuildFunction( + realm, + Value, + [O], + createOperationDescriptor("UNKNOWN_ARRAY_METHOD_PROPERTY_CALL", { propName: "shift" }) ); } @@ -1524,8 +1578,10 @@ export default function(realm: Realm, obj: ObjectValue): void { O.$GetOwnProperty("slice") === undefined ) { let newArgs = [O, start, end]; - return ArrayValue.createTemporalWithWidenedNumericProperty(realm, newArgs, ([objNode, ..._args]) => - t.callExpression(t.memberExpression(objNode, t.identifier("slice")), ((_args: any): Array)) + return ArrayValue.createTemporalWithWidenedNumericProperty( + realm, + newArgs, + createOperationDescriptor("UNKNOWN_ARRAY_METHOD_PROPERTY_CALL", { propName: "slice" }) ); } @@ -1601,8 +1657,11 @@ export default function(realm: Realm, obj: ObjectValue): void { if (thisArg) { args.push(thisArg); } - return AbstractValue.createTemporalFromBuildFunction(realm, BooleanValue, args, ([objNode, ..._args]) => - t.callExpression(t.memberExpression(objNode, t.identifier("some")), ((_args: any): Array)) + return AbstractValue.createTemporalFromBuildFunction( + realm, + BooleanValue, + args, + createOperationDescriptor("UNKNOWN_ARRAY_METHOD_PROPERTY_CALL", { propName: "some" }) ); } @@ -1665,8 +1724,11 @@ export default function(realm: Realm, obj: ObjectValue): void { O.$GetOwnProperty("sort") === undefined ) { let args = [O, comparefn]; - AbstractValue.createTemporalFromBuildFunction(realm, Value, args, ([objNode, ..._args]) => - t.callExpression(t.memberExpression(objNode, t.identifier("sort")), ((_args: any): Array)) + AbstractValue.createTemporalFromBuildFunction( + realm, + Value, + args, + createOperationDescriptor("UNKNOWN_ARRAY_METHOD_PROPERTY_CALL", { propName: "sort" }) ); // context is returned instead of O at the end of this method // so we do the same here @@ -1854,8 +1916,11 @@ export default function(realm: Realm, obj: ObjectValue): void { if (items && items.length > 0) { args.push(...items); } - return AbstractValue.createTemporalFromBuildFunction(realm, ArrayValue, args, ([objNode, ..._args]) => - t.callExpression(t.memberExpression(objNode, t.identifier("splice")), ((_args: any): Array)) + return AbstractValue.createTemporalFromBuildFunction( + realm, + ArrayValue, + args, + createOperationDescriptor("UNKNOWN_ARRAY_METHOD_PROPERTY_CALL", { propName: "splice" }) ); } @@ -2052,8 +2117,11 @@ export default function(realm: Realm, obj: ObjectValue): void { realm.isInPureScope() && array.$GetOwnProperty("toLocaleString") === undefined ) { - return AbstractValue.createTemporalFromBuildFunction(realm, StringValue, [array], ([objNode]) => - t.callExpression(t.memberExpression(objNode, t.identifier("toLocaleString")), []) + return AbstractValue.createTemporalFromBuildFunction( + realm, + StringValue, + [array], + createOperationDescriptor("UNKNOWN_ARRAY_METHOD_PROPERTY_CALL", { propName: "toLocaleString" }) ); } @@ -2129,8 +2197,11 @@ export default function(realm: Realm, obj: ObjectValue): void { realm.isInPureScope() && O.$GetOwnProperty("unshift") === undefined ) { - return AbstractValue.createTemporalFromBuildFunction(realm, NumberValue, [O], ([objNode]) => - t.callExpression(t.memberExpression(objNode, t.identifier("unshift")), []) + return AbstractValue.createTemporalFromBuildFunction( + realm, + NumberValue, + [O], + createOperationDescriptor("UNKNOWN_ARRAY_METHOD_PROPERTY_CALL", { propName: "unshift" }) ); } diff --git a/src/intrinsics/ecma262/Object.js b/src/intrinsics/ecma262/Object.js index d451b3c56..2185b8097 100644 --- a/src/intrinsics/ecma262/Object.js +++ b/src/intrinsics/ecma262/Object.js @@ -38,7 +38,7 @@ import { HasSomeCompatibleType, } from "../../methods/index.js"; import { Create, Havoc, Properties as Props, To } from "../../singletons.js"; -import * as t from "@babel/types"; +import { createOperationDescriptor } from "../../utils/generator.js"; import invariant from "../../invariant.js"; function snapshotToObjectAndRemoveProperties( @@ -381,7 +381,7 @@ export default function(realm: Realm): NativeFunctionValue { realm, ObjectValue, [getOwnPropertyDescriptor, obj, P], - ([methodNode, objNode, keyNode]) => t.callExpression(methodNode, [objNode, keyNode]) + createOperationDescriptor("OBJECT_PROTO_GET_OWN_PROPERTY_DESCRIPTOR") ); invariant(result instanceof AbstractObjectValue); result.makeSimple(); @@ -500,12 +500,14 @@ export default function(realm: Realm): NativeFunctionValue { let array = ArrayValue.createTemporalWithWidenedNumericProperty( realm, [objectKeys, obj], - ([methodNode, objNode]) => t.callExpression(methodNode, [objNode]) + createOperationDescriptor("UNKNOWN_ARRAY_METHOD_CALL") ); return array; } else if (ArrayValue.isIntrinsicAndHasWidenedNumericProperty(obj)) { - return ArrayValue.createTemporalWithWidenedNumericProperty(realm, [objectKeys, obj], ([methodNode, objNode]) => - t.callExpression(methodNode, [objNode]) + return ArrayValue.createTemporalWithWidenedNumericProperty( + realm, + [objectKeys, obj], + createOperationDescriptor("UNKNOWN_ARRAY_METHOD_CALL") ); } @@ -529,14 +531,14 @@ export default function(realm: Realm): NativeFunctionValue { let array = ArrayValue.createTemporalWithWidenedNumericProperty( realm, [objectValues, obj], - ([methodNode, objNode]) => t.callExpression(methodNode, [objNode]) + createOperationDescriptor("UNKNOWN_ARRAY_METHOD_CALL") ); return array; } else if (ArrayValue.isIntrinsicAndHasWidenedNumericProperty(obj)) { return ArrayValue.createTemporalWithWidenedNumericProperty( realm, [objectValues, obj], - ([methodNode, objNode]) => t.callExpression(methodNode, [objNode]) + createOperationDescriptor("UNKNOWN_ARRAY_METHOD_CALL") ); } } @@ -561,14 +563,14 @@ export default function(realm: Realm): NativeFunctionValue { let array = ArrayValue.createTemporalWithWidenedNumericProperty( realm, [objectEntries, obj], - ([methodNode, objNode]) => t.callExpression(methodNode, [objNode]) + createOperationDescriptor("UNKNOWN_ARRAY_METHOD_CALL") ); return array; } else if (ArrayValue.isIntrinsicAndHasWidenedNumericProperty(obj)) { return ArrayValue.createTemporalWithWidenedNumericProperty( realm, [objectEntries, obj], - ([methodNode, objNode]) => t.callExpression(methodNode, [objNode]) + createOperationDescriptor("UNKNOWN_ARRAY_METHOD_CALL") ); } diff --git a/src/intrinsics/ecma262/ObjectPrototype.js b/src/intrinsics/ecma262/ObjectPrototype.js index 4053f66a6..bd6e06423 100644 --- a/src/intrinsics/ecma262/ObjectPrototype.js +++ b/src/intrinsics/ecma262/ObjectPrototype.js @@ -23,10 +23,9 @@ import { HasOwnProperty, HasSomeCompatibleType } from "../../methods/has.js"; import { Invoke } from "../../methods/call.js"; import { Properties, To } from "../../singletons.js"; import { FatalError } from "../../errors.js"; -import type { BabelNodeExpression } from "@babel/types"; -import * as t from "@babel/types"; import invariant from "../../invariant.js"; import { TypesDomain, ValuesDomain } from "../../domains/index.js"; +import { createOperationDescriptor } from "../../utils/generator.js"; export default function(realm: Realm, obj: ObjectValue): void { // ECMA262 19.1.3.2 @@ -54,9 +53,7 @@ export default function(realm: Realm, obj: ObjectValue): void { realm, BooleanValue, [ObjectPrototypeHasOwnPrototype, context, key], - ([methodNode, objectNode, nameNode]: Array) => { - return t.callExpression(t.memberExpression(methodNode, t.identifier("call")), [objectNode, nameNode]); - } + createOperationDescriptor("OBJECT_PROTO_HAS_OWN_PROPERTY") ), TypesDomain.topVal, ValuesDomain.topVal diff --git a/src/intrinsics/fb-www/fb-mocks.js b/src/intrinsics/fb-www/fb-mocks.js index 2a98ad4f3..40b0de602 100644 --- a/src/intrinsics/fb-www/fb-mocks.js +++ b/src/intrinsics/fb-www/fb-mocks.js @@ -23,10 +23,10 @@ import { } from "../../values/index.js"; import { Create } from "../../singletons.js"; import { Get } from "../../methods/index.js"; -import * as t from "@babel/types"; -import invariant from "../../invariant"; +import invariant from "../../invariant.js"; import { Properties } from "../../singletons.js"; import { forEachArrayValue } from "../../react/utils.js"; +import { createOperationDescriptor } from "../../utils/generator.js"; const fbMagicGlobalFunctions = [ "asset", @@ -117,9 +117,7 @@ function createBabelHelpers(realm: Realm, global: ObjectValue | AbstractObjectVa realm, ObjectValue, temporalArgs, - ([methodNode, objNode, propRemoveNode]) => { - return t.callExpression(methodNode, [objNode, propRemoveNode]); - }, + createOperationDescriptor("BABEL_HELPERS_OBJECT_WITHOUT_PROPERTIES"), temporalConfig ); invariant(value instanceof AbstractObjectValue); @@ -205,7 +203,7 @@ function createMagicGlobalFunction(realm: Realm, global: ObjectValue | AbstractO realm, FunctionValue, args, - _args => t.callExpression(t.identifier(functionName), ((_args: any): Array)), + createOperationDescriptor("FB_MOCKS_MAGIC_GLOBAL_FUNCTION", { propName: functionName }), { skipInvariant: true, isPure: true } ); invariant(val instanceof AbstractValue); @@ -238,11 +236,7 @@ function createBootloader(realm: Realm, global: ObjectValue | AbstractObjectValu realm, FunctionValue, args, - _args => - t.callExpression( - t.memberExpression(t.identifier("Bootloader"), t.identifier("loadModules")), - ((_args: any): Array) - ), + createOperationDescriptor("FB_MOCKS_BOOTLOADER_LOAD_MODULES"), { skipInvariant: true } ); invariant(val instanceof AbstractValue); diff --git a/src/intrinsics/fb-www/react-dom-mocks.js b/src/intrinsics/fb-www/react-dom-mocks.js index 847439a91..6481af4f3 100644 --- a/src/intrinsics/fb-www/react-dom-mocks.js +++ b/src/intrinsics/fb-www/react-dom-mocks.js @@ -12,10 +12,10 @@ import type { Realm } from "../../realm.js"; import { ObjectValue, AbstractObjectValue, AbstractValue, FunctionValue } from "../../values/index.js"; import { createReactHintObject, isReactElement } from "../../react/utils.js"; -import * as t from "@babel/types"; -import invariant from "../../invariant"; +import invariant from "../../invariant.js"; import { updateIntrinsicNames, addMockFunctionToObject } from "./utils.js"; import { renderToString } from "../../react/experimental-server-rendering/rendering.js"; +import { createOperationDescriptor } from "../../utils/generator.js"; export function createMockReactDOM(realm: Realm, reactDomRequireName: string): ObjectValue { let reactDomValue = new ObjectValue(realm, realm.intrinsics.ObjectPrototype); @@ -28,9 +28,7 @@ export function createMockReactDOM(realm: Realm, reactDomRequireName: string): O realm, FunctionValue, [funcVal, ...args], - ([renderNode, ..._args]) => { - return t.callExpression(renderNode, ((_args: any): Array)); - }, + createOperationDescriptor("REACT_TEMPORAL_FUNC"), { skipInvariant: true, isPure: true } ); invariant(reactDomMethod instanceof AbstractObjectValue); @@ -47,9 +45,7 @@ export function createMockReactDOM(realm: Realm, reactDomRequireName: string): O realm, ObjectValue, [funcVal, reactPortalValue, domNodeValue], - ([renderNode, ..._args]) => { - return t.callExpression(renderNode, ((_args: any): Array)); - }, + createOperationDescriptor("REACT_TEMPORAL_FUNC"), { skipInvariant: true, isPure: true } ); invariant(reactDomMethod instanceof AbstractObjectValue); @@ -78,9 +74,7 @@ export function createMockReactDOMServer(realm: Realm, requireName: string): Obj realm, FunctionValue, [funcVal, ...args], - ([renderNode, ..._args]) => { - return t.callExpression(renderNode, ((_args: any): Array)); - }, + createOperationDescriptor("REACT_TEMPORAL_FUNC"), { skipInvariant: true, isPure: true } ); invariant(reactDomMethod instanceof AbstractObjectValue); diff --git a/src/intrinsics/fb-www/react-mocks.js b/src/intrinsics/fb-www/react-mocks.js index 7226982f1..5547da700 100644 --- a/src/intrinsics/fb-www/react-mocks.js +++ b/src/intrinsics/fb-www/react-mocks.js @@ -26,9 +26,9 @@ import { Environment } from "../../singletons.js"; import { createReactHintObject, getReactSymbol } from "../../react/utils.js"; import { cloneReactElement, createReactElement } from "../../react/elements.js"; import { Properties, Create, To } from "../../singletons.js"; -import * as t from "@babel/types"; -import invariant from "../../invariant"; +import invariant from "../../invariant.js"; import { updateIntrinsicNames, addMockFunctionToObject } from "./utils.js"; +import { createOperationDescriptor } from "../../utils/generator.js"; // most of the code here was taken from https://github.com/facebook/react/blob/master/packages/react/src/ReactElement.js let reactCode = ` @@ -489,7 +489,7 @@ export function createMockReact(realm: Realm, reactRequireName: string): ObjectV realm, ObjectValue, [funcValue, defaultValue], - ([methodNode, defaultValueNode]) => t.callExpression(methodNode, [defaultValueNode]), + createOperationDescriptor("REACT_TEMPORAL_FUNC"), { skipInvariant: true, isPure: true } ); invariant(consumer instanceof AbstractObjectValue); @@ -499,7 +499,7 @@ export function createMockReact(realm: Realm, reactRequireName: string): ObjectV realm, ObjectValue, [consumer], - ([consumerNode]) => t.memberExpression(consumerNode, t.identifier("Provider")), + createOperationDescriptor("REACT_CREATE_CONTEXT_PROVIDER"), { skipInvariant: true, isPure: true } ); invariant(provider instanceof AbstractObjectValue); @@ -524,9 +524,7 @@ export function createMockReact(realm: Realm, reactRequireName: string): ObjectV realm, FunctionValue, [funcVal], - ([createRefNode]) => { - return t.callExpression(createRefNode, []); - }, + createOperationDescriptor("REACT_TEMPORAL_FUNC"), { skipInvariant: true, isPure: true } ); invariant(createRef instanceof AbstractObjectValue); @@ -538,9 +536,7 @@ export function createMockReact(realm: Realm, reactRequireName: string): ObjectV realm, FunctionValue, [funcVal, func], - ([forwardRefNode, funcNode]) => { - return t.callExpression(forwardRefNode, [funcNode]); - }, + createOperationDescriptor("REACT_TEMPORAL_FUNC"), { skipInvariant: true, isPure: true } ); invariant(forwardedRef instanceof AbstractObjectValue); diff --git a/src/intrinsics/fb-www/react-native-mocks.js b/src/intrinsics/fb-www/react-native-mocks.js index a764aa2da..3d92c248e 100644 --- a/src/intrinsics/fb-www/react-native-mocks.js +++ b/src/intrinsics/fb-www/react-native-mocks.js @@ -12,9 +12,9 @@ import type { Realm } from "../../realm.js"; import { AbstractValue, ECMAScriptSourceFunctionValue, ObjectValue, StringValue } from "../../values/index.js"; import { Environment } from "../../singletons.js"; -import invariant from "../../invariant"; -import * as t from "@babel/types"; +import invariant from "../../invariant.js"; import { parseExpression } from "@babel/parser"; +import { createOperationDescriptor } from "../../utils/generator.js"; let reactNativeCode = ` function createReactNative(React, reactNameRequireName) { @@ -1660,7 +1660,7 @@ export function createMockReactNative(realm: Realm, reactNativeRequireName: stri realm, StringValue, [], - () => t.stringLiteral("RCTView"), + createOperationDescriptor("REACT_NATIVE_STRING_LITERAL", { propName: "RCTView" }), { skipInvariant: true, isPure: true } ); invariant(RCTViewDerivedReference instanceof AbstractValue); @@ -1670,7 +1670,7 @@ export function createMockReactNative(realm: Realm, reactNativeRequireName: stri realm, StringValue, [], - () => t.stringLiteral("RCTText"), + createOperationDescriptor("REACT_NATIVE_STRING_LITERAL", { propName: "RCTText" }), { skipInvariant: true, isPure: true } ); invariant(RCTTextDerivedReference instanceof AbstractValue); diff --git a/src/intrinsics/fb-www/relay-mocks.js b/src/intrinsics/fb-www/relay-mocks.js index b7ff6fb29..a35883446 100644 --- a/src/intrinsics/fb-www/relay-mocks.js +++ b/src/intrinsics/fb-www/relay-mocks.js @@ -14,11 +14,11 @@ import { ObjectValue, FunctionValue, AbstractValue, ECMAScriptSourceFunctionValu import { Create, Environment } from "../../singletons.js"; import { createAbstract } from "../prepack/utils.js"; import { Get } from "../../methods/index.js"; -import * as t from "@babel/types"; -import invariant from "../../invariant"; +import invariant from "../../invariant.js"; import { createReactHintObject } from "../../react/utils.js"; import { parseExpression } from "@babel/parser"; import { addMockFunctionToObject } from "./utils.js"; +import { createOperationDescriptor } from "../../utils/generator.js"; let reactRelayCode = ` function createReactRelay(React) { @@ -113,14 +113,7 @@ function createReactRelayContainer( realm, FunctionValue, [reactRelay, ...args], - _args => { - let [reactRelayIdent, ...otherArgs] = _args; - - return t.callExpression( - t.memberExpression(reactRelayIdent, t.identifier(containerName)), - ((otherArgs: any): Array) - ); - }, + createOperationDescriptor("REACT_RELAY_MOCK_CONTAINER", { propName: containerName }), { skipInvariant: true, isPure: true } ); invariant(value instanceof AbstractValue); diff --git a/src/intrinsics/prepack/global.js b/src/intrinsics/prepack/global.js index a5efd96b6..7abc8c23e 100644 --- a/src/intrinsics/prepack/global.js +++ b/src/intrinsics/prepack/global.js @@ -25,13 +25,13 @@ import { } from "../../values/index.js"; import { To, Path } from "../../singletons.js"; import { ValuesDomain } from "../../domains/index.js"; -import * as t from "@babel/types"; -import type { BabelNodeExpression, BabelNodeSpreadElement } from "@babel/types"; import invariant from "../../invariant.js"; import { createAbstract, parseTypeNameOrTemplate } from "./utils.js"; import { describeValue } from "../../utils.js"; import { valueIsKnownReactAbstraction } from "../../react/utils.js"; import { CompilerDiagnostic, FatalError } from "../../errors.js"; +import * as t from "@babel/types"; +import { createOperationDescriptor, type OperationDescriptor } from "../../utils/generator.js"; export function createAbstractFunction(realm: Realm, ...additionalValues: Array): NativeFunctionValue { return new NativeFunctionValue( @@ -301,8 +301,11 @@ export default function(realm: Realm): void { invariant(f instanceof FunctionValue); f.isResidual = true; if (unsafe) f.isUnsafeResidual = true; - let result = AbstractValue.createTemporalFromBuildFunction(realm, type, [f].concat(args), nodes => - t.callExpression(nodes[0], ((nodes.slice(1): any): Array)) + let result = AbstractValue.createTemporalFromBuildFunction( + realm, + type, + [f].concat(args), + createOperationDescriptor("RESIDUAL_CALL") ); if (template) { invariant( @@ -312,7 +315,7 @@ export default function(realm: Realm): void { template.makePartial(); result.values = new ValuesDomain(new Set([template])); invariant(realm.generator); - realm.rebuildNestedProperties(result, result.getIdentifier().name); + realm.rebuildNestedProperties(result, result.getIdentifier()); } return result; } @@ -322,13 +325,13 @@ export default function(realm: Realm): void { function createNativeFunctionForResidualInjection( name: string, initializeAndValidateArgs: (Array) => void, - buildNode_: (Array) => BabelNodeStatement, + operationDescriptor: OperationDescriptor, numArgs: number ): NativeFunctionValue { return new NativeFunctionValue(realm, "global." + name, name, numArgs, (context, ciArgs) => { initializeAndValidateArgs(ciArgs); invariant(realm.generator !== undefined); - realm.generator.emitStatement(ciArgs, buildNode_); + realm.generator.emitStatement(ciArgs, operationDescriptor); return realm.intrinsics.undefined; }); } @@ -351,13 +354,7 @@ export default function(realm: Realm): void { } Path.pushAndRefine(c); }, - ([c, s]: Array) => { - let errorLiteral = s.type === "StringLiteral" ? s : t.stringLiteral("Assumption violated"); - return t.ifStatement( - t.unaryExpression("!", c), - t.blockStatement([t.throwStatement(t.newExpression(t.identifier("Error"), [errorLiteral]))]) - ); - }, + createOperationDescriptor("ASSUME_CALL"), 2 ), writable: true, diff --git a/src/intrinsics/prepack/utils.js b/src/intrinsics/prepack/utils.js index 932bc2411..27cbeae67 100644 --- a/src/intrinsics/prepack/utils.js +++ b/src/intrinsics/prepack/utils.js @@ -23,9 +23,9 @@ import buildExpressionTemplate from "../../utils/builder.js"; import { ValuesDomain } from "../../domains/index.js"; import { describeLocation } from "../ecma262/Error.js"; import { To } from "../../singletons.js"; -import AbstractObjectValue from "../../values/AbstractObjectValue"; +import AbstractObjectValue from "../../values/AbstractObjectValue.js"; import { CompilerDiagnostic, FatalError } from "../../errors.js"; -import { Utils } from "../../singletons"; +import { Utils } from "../../singletons.js"; import invariant from "../../invariant.js"; const throwTemplateSrc = "(function(){throw new global.Error('abstract value defined at ' + A);})()"; diff --git a/src/methods/arraybuffer.js b/src/methods/arraybuffer.js index fcec61b5d..bd480799a 100644 --- a/src/methods/arraybuffer.js +++ b/src/methods/arraybuffer.js @@ -12,9 +12,9 @@ import type { Realm } from "../realm.js"; import type { DataBlock, ElementType } from "../types.js"; import { Value, ObjectValue, NumberValue, EmptyValue, NullValue, UndefinedValue } from "../values/index.js"; -import { SpeciesConstructor } from "../methods/construct.js"; -import { IsConstructor } from "../methods/index.js"; -import { IsDetachedBuffer } from "../methods/is.js"; +import { SpeciesConstructor } from "./construct.js"; +import { IsConstructor } from "./index.js"; +import { IsDetachedBuffer } from "./is.js"; import { Create, Properties, To } from "../singletons.js"; import invariant from "../invariant.js"; import { ElementSize } from "../types.js"; diff --git a/src/methods/call.js b/src/methods/call.js index 3ad5e9a1f..714aedc7b 100644 --- a/src/methods/call.js +++ b/src/methods/call.js @@ -32,13 +32,13 @@ import { AbstractValue, } from "../values/index.js"; import { GetIterator, HasSomeCompatibleType, IsCallable, IsPropertyKey, IteratorStep, IteratorValue } from "./index.js"; -import { GeneratorStart } from "../methods/generator.js"; +import { GeneratorStart } from "./generator.js"; import { ReturnCompletion, AbruptCompletion, ThrowCompletion, ForkedAbruptCompletion } from "../completions.js"; -import { GetTemplateObject, GetV, GetThisValue } from "../methods/get.js"; +import { GetTemplateObject, GetV, GetThisValue } from "./get.js"; import { Create, Environment, Functions, Join, Havoc, To, Widen } from "../singletons.js"; import invariant from "../invariant.js"; +import { createOperationDescriptor } from "../utils/generator.js"; import type { BabelNodeExpression, BabelNodeSpreadElement, BabelNodeTemplateLiteral } from "@babel/types"; -import * as t from "@babel/types"; // ECMA262 12.3.6.1 export function ArgumentListEvaluation( @@ -528,10 +528,7 @@ export function EvaluateDirectCallWithArgList( realm, func.functionResultType || Value, [func].concat(argList), - (nodes: Array) => { - let fun_args = nodes.slice(1); - return t.callExpression(nodes[0], ((fun_args: any): Array)); - } + createOperationDescriptor("DIRECT_CALL_WITH_ARG_LIST") ); } func = func.throwIfNotConcrete(); @@ -594,16 +591,20 @@ export function Call(realm: Realm, F: Value, V: Value, argsList?: Array): } if (V === realm.intrinsics.undefined) { let fullArgs = [F].concat(argsList); - return AbstractValue.createTemporalFromBuildFunction(realm, Value, fullArgs, nodes => { - let fun_args = ((nodes.slice(1): any): Array); - return t.callExpression(nodes[0], fun_args); - }); + return AbstractValue.createTemporalFromBuildFunction( + realm, + Value, + fullArgs, + createOperationDescriptor("CALL_ABSTRACT_FUNC") + ); } else { let fullArgs = [F, V].concat(argsList); - return AbstractValue.createTemporalFromBuildFunction(realm, Value, fullArgs, nodes => { - let fun_args = ((nodes.slice(1): any): Array); - return t.callExpression(t.memberExpression(nodes[0], t.identifier("call")), fun_args); - }); + return AbstractValue.createTemporalFromBuildFunction( + realm, + Value, + fullArgs, + createOperationDescriptor("CALL_ABSTRACT_FUNC_THIS") + ); } } invariant(F instanceof ObjectValue); diff --git a/src/methods/environment.js b/src/methods/environment.js index 609d5d00e..df5f7b683 100644 --- a/src/methods/environment.js +++ b/src/methods/environment.js @@ -10,7 +10,6 @@ /* @flow */ import type { Realm } from "../realm.js"; -import * as t from "@babel/types"; import invariant from "../invariant.js"; import type { PropertyKeyValue } from "../types.js"; import { @@ -64,6 +63,7 @@ import type { BabelNodeLVal, BabelNodePattern, } from "@babel/types"; +import * as t from "@babel/types"; export class EnvironmentImplementation { // 2.6 RestBindingInitialization (please suggest an appropriate section name) diff --git a/src/methods/function.js b/src/methods/function.js index 8279b9e74..d34c8b72c 100644 --- a/src/methods/function.js +++ b/src/methods/function.js @@ -41,7 +41,6 @@ import traverseFast from "../utils/traverse-fast.js"; import invariant from "../invariant.js"; import parse from "../utils/parse.js"; import IsStrict from "../utils/strict.js"; -import * as t from "@babel/types"; import type { BabelNode, BabelNodeBlockStatement, @@ -62,6 +61,7 @@ import type { BabelNodeWhileStatement, BabelNodeWithStatement, } from "@babel/types"; +import * as t from "@babel/types"; function InternalCall( realm: Realm, diff --git a/src/methods/get.js b/src/methods/get.js index c8c838787..240d3d1f1 100644 --- a/src/methods/get.js +++ b/src/methods/get.js @@ -42,8 +42,7 @@ import { import { Create, Environment, Join, Path, To } from "../singletons.js"; import invariant from "../invariant.js"; import type { BabelNodeTemplateLiteral } from "@babel/types"; -import * as t from "@babel/types"; -import { memberExpressionHelper } from "../utils/babelhelpers.js"; +import { createOperationDescriptor } from "../utils/generator.js"; // ECMA262 7.3.22 export function GetFunctionRealm(realm: Realm, obj: ObjectValue): Realm { @@ -565,7 +564,7 @@ export function GetFromArrayWithWidenedNumericProperty(realm: Realm, arr: ArrayV realm, NumberValue, [arr], - ([o]) => t.memberExpression(o, t.identifier("length"), false), + createOperationDescriptor("UNKNOWN_ARRAY_LENGTH"), { skipInvariant: true, isPure: true } ); } @@ -583,7 +582,7 @@ export function GetFromArrayWithWidenedNumericProperty(realm: Realm, arr: ArrayV realm, Value, [arr, prop], - ([o, p]) => memberExpressionHelper(o, p), + createOperationDescriptor("UNKNOWN_ARRAY_GET_PARTIAL"), { skipInvariant: true, isPure: true, diff --git a/src/methods/json.js b/src/methods/json.js index 12bb97592..77dd393b9 100644 --- a/src/methods/json.js +++ b/src/methods/json.js @@ -12,10 +12,10 @@ import type { Realm } from "../realm.js"; import { Value, ObjectValue, NumberValue, UndefinedValue, StringValue } from "../values/index.js"; import { Create, To } from "../singletons.js"; -import { Get } from "../methods/get.js"; -import { Call } from "../methods/call.js"; -import { IsArray } from "../methods/is.js"; -import { EnumerableOwnProperties } from "../methods/own.js"; +import { Get } from "./get.js"; +import { Call } from "./call.js"; +import { IsArray } from "./is.js"; +import { EnumerableOwnProperties } from "./own.js"; import type { PropertyKeyValue } from "../types.js"; import invariant from "../invariant.js"; diff --git a/src/methods/promise.js b/src/methods/promise.js index 2442cdff5..edb2b4940 100644 --- a/src/methods/promise.js +++ b/src/methods/promise.js @@ -20,12 +20,12 @@ import { FunctionValue, type UndefinedValue, } from "../values/index.js"; -import { SameValue } from "../methods/abstract.js"; -import { Construct } from "../methods/construct.js"; -import { Get } from "../methods/get.js"; -import { Invoke, Call } from "../methods/call.js"; -import { IsCallable, IsConstructor, IsPromise } from "../methods/is.js"; -import { IteratorStep, IteratorValue } from "../methods/iterator.js"; +import { SameValue } from "./abstract.js"; +import { Construct } from "./construct.js"; +import { Get } from "./get.js"; +import { Invoke, Call } from "./call.js"; +import { IsCallable, IsConstructor, IsPromise } from "./is.js"; +import { IteratorStep, IteratorValue } from "./iterator.js"; import { Create, Properties } from "../singletons.js"; import invariant from "../invariant.js"; diff --git a/src/methods/properties.js b/src/methods/properties.js index 82ab0e97f..377a7c47e 100644 --- a/src/methods/properties.js +++ b/src/methods/properties.js @@ -26,7 +26,7 @@ import { UndefinedValue, Value, } from "../values/index.js"; -import { EvalPropertyName } from "../evaluators/ObjectExpression"; +import { EvalPropertyName } from "../evaluators/ObjectExpression.js"; import { EnvironmentRecord, Reference } from "../environment.js"; import { CompilerDiagnostic, FatalError } from "../errors.js"; import invariant from "../invariant.js"; @@ -45,12 +45,12 @@ import { MakeConstructor, SameValue, SameValuePartial, -} from "../methods/index.js"; +} from "./index.js"; import { type BabelNodeObjectMethod, type BabelNodeClassMethod, isValidIdentifier } from "@babel/types"; import type { LexicalEnvironment } from "../environment.js"; import { Create, Environment, Functions, Havoc, Join, Path, To } from "../singletons.js"; import IsStrict from "../utils/strict.js"; -import { memberExpressionHelper } from "../utils/babelhelpers.js"; +import { createOperationDescriptor } from "../utils/generator.js"; function StringKey(key: PropertyKeyValue): string { if (key instanceof StringValue) key = key.value; @@ -1180,11 +1180,16 @@ export class PropertiesImplementation { } invariant(realm.generator); + let propName = P; + if (P instanceof StringValue) { + propName = P.value; + } + invariant(typeof propName === "string"); let absVal = AbstractValue.createTemporalFromBuildFunction( realm, Value, [O._templateFor || O], - ([node]) => memberExpressionHelper(node, StringKey(P)), + createOperationDescriptor("ABSTRACT_PROPERTY", { propName }), { isPure: true } ); // TODO: We can't be sure what the descriptor will be, but the value will be abstract. @@ -1206,16 +1211,13 @@ export class PropertiesImplementation { invariant(realm.generator); let absVal; function createAbstractPropertyValue(type: typeof Value) { + invariant(typeof P === "string"); if (O.isTransitivelySimple()) { - invariant(typeof P === "string"); return AbstractValue.createFromBuildFunction( realm, type, [O._templateFor || O], - ([node]) => { - invariant(typeof P === "string"); - return memberExpressionHelper(node, P); - }, + createOperationDescriptor("ABSTRACT_PROPERTY", { propName: P }), { kind: AbstractValue.makeKind("property", P) } ); } else { @@ -1223,10 +1225,7 @@ export class PropertiesImplementation { realm, type, [O._templateFor || O], - ([node]) => { - invariant(typeof P === "string"); - return memberExpressionHelper(node, P); - }, + createOperationDescriptor("ABSTRACT_PROPERTY", { propName: P }), { skipInvariant: true, isPure: true } ); } @@ -1320,7 +1319,8 @@ export class PropertiesImplementation { if (value.kind !== "resolved") { let realmGenerator = realm.generator; invariant(realmGenerator); - value = realmGenerator.deriveAbstract(value.types, value.values, value.args, value.getBuildNode(), { + invariant(value.operationDescriptor); + value = realmGenerator.deriveAbstract(value.types, value.values, value.args, value.operationDescriptor, { isPure: true, kind: "resolved", // We can't emit the invariant here otherwise it'll assume the AbstractValue's type not the union type diff --git a/src/methods/to.js b/src/methods/to.js index da6cbb4b7..09c05e256 100644 --- a/src/methods/to.js +++ b/src/methods/to.js @@ -35,7 +35,7 @@ import { Value, } from "../values/index.js"; import invariant from "../invariant.js"; -import * as t from "@babel/types"; +import { createOperationDescriptor } from "../utils/generator.js"; type ElementConvType = { Int8: (Realm, numberOrValue) => number, @@ -746,9 +746,9 @@ export class ToImplementation { ToStringAbstract(realm: Realm, value: AbstractValue): AbstractValue { if (value.mightNotBeString()) { - // If the property is not a string we need to coerce it. - let coerceToString = ([p]) => t.binaryExpression("+", t.stringLiteral(""), p); let result; + // If the property is not a string we need to coerce it. + let coerceToString = createOperationDescriptor("COERCE_TO_STRING"); if (value.mightNotBeNumber() && !value.isSimpleObject()) { // If this might be a non-simple object, we need to coerce this at a // temporal point since it can have side-effects. diff --git a/src/methods/typedarray.js b/src/methods/typedarray.js index 3109e27c1..e59dee2fd 100644 --- a/src/methods/typedarray.js +++ b/src/methods/typedarray.js @@ -21,11 +21,11 @@ import { NumberValue, UndefinedValue, } from "../values/index.js"; -import { GetPrototypeFromConstructor } from "../methods/get.js"; -import { AllocateArrayBuffer } from "../methods/arraybuffer.js"; -import { IsDetachedBuffer, IsInteger } from "../methods/is.js"; -import { GetValueFromBuffer, SetValueInBuffer } from "../methods/arraybuffer.js"; -import { Construct, SpeciesConstructor } from "../methods/construct.js"; +import { GetPrototypeFromConstructor } from "./get.js"; +import { AllocateArrayBuffer } from "./arraybuffer.js"; +import { IsDetachedBuffer, IsInteger } from "./is.js"; +import { GetValueFromBuffer, SetValueInBuffer } from "./arraybuffer.js"; +import { Construct, SpeciesConstructor } from "./construct.js"; import { To } from "../singletons.js"; import invariant from "../invariant.js"; diff --git a/src/methods/widen.js b/src/methods/widen.js index e78cb3ade..29a259369 100644 --- a/src/methods/widen.js +++ b/src/methods/widen.js @@ -17,13 +17,11 @@ import type { Descriptor, PropertyBinding } from "../types.js"; import { AbruptCompletion, PossiblyNormalCompletion, SimpleNormalCompletion } from "../completions.js"; import { Reference } from "../environment.js"; -import { cloneDescriptor, equalDescriptors, IsDataDescriptor, StrictEqualityComparison } from "../methods/index.js"; -import { Generator } from "../utils/generator.js"; +import { cloneDescriptor, equalDescriptors, IsDataDescriptor, StrictEqualityComparison } from "./index.js"; +import { Generator, createOperationDescriptor } from "../utils/generator.js"; import { AbstractValue, ArrayValue, EmptyValue, Value } from "../values/index.js"; import invariant from "../invariant.js"; -import * as t from "@babel/types"; -import { memberExpressionHelper } from "../utils/babelhelpers.js"; export class WidenImplementation { _widenArrays( @@ -158,7 +156,7 @@ export class WidenImplementation { result.types, result.values, [b.value || realm.intrinsics.undefined], - ([n]) => n, + createOperationDescriptor("SINGLE_ARG"), { skipInvariant: true } ); b.phiNode = phiNode; @@ -167,7 +165,7 @@ export class WidenImplementation { invariant(phiNode.intrinsicName !== undefined); let phiName = phiNode.intrinsicName; result.intrinsicName = phiName; - result._buildNode = args => t.identifier(phiName); + result.operationDescriptor = createOperationDescriptor("WIDENED_IDENTIFIER", { id: phiName }); } invariant(result instanceof Value); let previousHasLeaked = b2.previousHasLeaked; @@ -253,7 +251,7 @@ export class WidenImplementation { let pathNode = b.pathNode; if (pathNode === undefined) { //Since properties already have mutable storage locations associated with them, we do not - //need phi nodes. What we need is an abstract value with a build node that results in a memberExpression + //need phi nodes. What we need is an abstract value with a operation descriptor that results in a memberExpression //that resolves to the storage location of the property. // For now, we only handle loop invariant properties @@ -264,14 +262,20 @@ export class WidenImplementation { (key instanceof AbstractValue && !(key.mightNotBeString() && key.mightNotBeNumber())) ) { if (typeof key === "string") { - pathNode = AbstractValue.createFromWidenedProperty(realm, rval, [b.object], ([o]) => - memberExpressionHelper(o, key) + pathNode = AbstractValue.createFromWidenedProperty( + realm, + rval, + [b.object], + createOperationDescriptor("WIDEN_PROPERTY", { propName: key }) ); } else { invariant(key instanceof AbstractValue); - pathNode = AbstractValue.createFromWidenedProperty(realm, rval, [b.object, key], ([o, p]) => { - return memberExpressionHelper(o, p); - }); + pathNode = AbstractValue.createFromWidenedProperty( + realm, + rval, + [b.object, key], + createOperationDescriptor("WIDEN_ABSTRACT_PROPERTY") + ); } // The value of the property at the start of the loop needs to be written to the property // before the loop commences, otherwise the memberExpression will result in an undefined value. @@ -283,14 +287,19 @@ export class WidenImplementation { if (key === "length" && b.object instanceof ArrayValue) { // do nothing, the array length will already be initialized } else if (typeof key === "string") { - generator.emitVoidExpression(rval.types, rval.values, [b.object, initVal], ([o, v]) => { - invariant(typeof key === "string"); - return t.assignmentExpression("=", memberExpressionHelper(o, key), v); - }); + generator.emitVoidExpression( + rval.types, + rval.values, + [b.object, initVal], + createOperationDescriptor("WIDEN_PROPERTY_ASSIGNMENT", { propName: key }) + ); } else { invariant(key instanceof AbstractValue); - generator.emitVoidExpression(rval.types, rval.values, [b.object, key, initVal], ([o, p, v]) => - t.assignmentExpression("=", memberExpressionHelper(o, p), v) + generator.emitVoidExpression( + rval.types, + rval.values, + [b.object, key, initVal], + createOperationDescriptor("WIDEN_ABSTRACT_PROPERTY_ASSIGNMENT") ); } } diff --git a/src/react/ReactEquivalenceSet.js b/src/react/ReactEquivalenceSet.js index 61382bf60..ac0962a23 100644 --- a/src/react/ReactEquivalenceSet.js +++ b/src/react/ReactEquivalenceSet.js @@ -22,7 +22,7 @@ import { Value, } from "../values/index.js"; import invariant from "../invariant.js"; -import { hardModifyReactObjectPropertyBinding, isReactElement, isReactPropsObject, getProperty } from "./utils"; +import { hardModifyReactObjectPropertyBinding, isReactElement, isReactPropsObject, getProperty } from "./utils.js"; import { ResidualReactElementVisitor } from "../serializer/ResidualReactElementVisitor.js"; export type ReactSetValueMapKey = Value | number | string; diff --git a/src/react/branching.js b/src/react/branching.js index 5d201c898..ba6448970 100644 --- a/src/react/branching.js +++ b/src/react/branching.js @@ -31,8 +31,9 @@ import { forEachArrayValue, getProperty, mapArrayValue, -} from "./utils"; +} from "./utils.js"; import { ExpectedBailOut } from "./errors.js"; +import { createOperationDescriptor } from "../utils/generator.js"; // Branch status is used for when Prepack returns an abstract value from a render // that results in a conditional path occuring. This can be problematic for reconcilation @@ -243,7 +244,7 @@ export function wrapReactElementInBranchOrReturnValue(realm: Realm, value: Value realm, ObjectValue, [cloneReactElement(realm, value, false)], - ([node]) => node, + createOperationDescriptor("SINGLE_ARG"), { isPure: true, skipInvariant: true } ); invariant(temporal instanceof AbstractObjectValue); diff --git a/src/react/components.js b/src/react/components.js index 84c5dae7f..6b572d7ab 100644 --- a/src/react/components.js +++ b/src/react/components.js @@ -28,7 +28,7 @@ import { getValueFromFunctionCall, hardModifyReactObjectPropertyBinding, valueIsClassComponent, -} from "./utils"; +} from "./utils.js"; import { ExpectedBailOut, SimpleClassBailOut } from "./errors.js"; import { Get, Construct } from "../methods/index.js"; import { Properties } from "../singletons.js"; diff --git a/src/react/elements.js b/src/react/elements.js index 764813e6f..b290b35dd 100644 --- a/src/react/elements.js +++ b/src/react/elements.js @@ -31,9 +31,9 @@ import { getProperty, hasNoPartialKeyOrRef, } from "./utils.js"; -import * as t from "@babel/types"; import { computeBinary } from "../evaluators/BinaryExpression.js"; import { CompilerDiagnostic, FatalError } from "../errors.js"; +import { createOperationDescriptor } from "../utils/generator.js"; function createPropsObject( realm: Realm, @@ -194,9 +194,7 @@ function createPropsObject( realm, ObjectValue, temporalArgs, - ([methodNode, ..._args]) => { - return t.callExpression(methodNode, ((_args: any): Array)); - }, + createOperationDescriptor("REACT_DEFAULT_PROPS_HELPER"), { skipInvariant: true } ); invariant(temporalTo instanceof AbstractObjectValue); diff --git a/src/react/experimental-server-rendering/rendering.js b/src/react/experimental-server-rendering/rendering.js index d90d7b537..afe35fdc4 100644 --- a/src/react/experimental-server-rendering/rendering.js +++ b/src/react/experimental-server-rendering/rendering.js @@ -68,13 +68,17 @@ import { // $FlowFixMe: flow complains that this isn't a module but it is, and it seems to load fine import hyphenateStyleName from "fbjs/lib/hyphenateStyleName"; import { To } from "../../singletons.js"; +import { createOperationDescriptor } from "../../utils/generator.js"; export type ReactNode = Array | string | AbstractValue | ArrayValue; function renderValueWithHelper(realm: Realm, value: Value, helper: ECMAScriptSourceFunctionValue): AbstractValue { // given we know nothing of this value, we need to escape the contents of it at runtime - let val = AbstractValue.createFromBuildFunction(realm, Value, [helper, value], ([helperNode, valueNode]) => - t.callExpression(helperNode, [valueNode]) + let val = AbstractValue.createFromBuildFunction( + realm, + Value, + [helper, value], + createOperationDescriptor("REACT_SSR_RENDER_VALUE_HELPER") ); invariant(val instanceof AbstractValue); return val; @@ -252,8 +256,11 @@ function renderReactNode(realm: Realm, reactNode: ReactNode): StringValue | Abst args.push(element); } } - let val = AbstractValue.createFromBuildFunction(realm, StringValue, args, valueNodes => - t.templateLiteral(((quasis: any): Array), valueNodes) + let val = AbstractValue.createFromBuildFunction( + realm, + StringValue, + args, + createOperationDescriptor("REACT_SSR_TEMPLATE_LITERAL", { quasis }) ); invariant(val instanceof AbstractValue); return val; @@ -490,13 +497,9 @@ export function renderToString( invariant(realm.generator); // create a single regex used for the escape functions // by hoisting it, it gets cached by the VM JITs - realm.generator.emitStatement([], () => - t.variableDeclaration("var", [t.variableDeclarator(t.identifier("matchHtmlRegExp"), t.regExpLiteral("[\"'&<>]"))]) - ); + realm.generator.emitStatement([], createOperationDescriptor("REACT_SSR_REGEX_CONSTANT")); invariant(realm.generator); - realm.generator.emitStatement([], () => - t.variableDeclaration("var", [t.variableDeclarator(t.identifier("previousWasTextNode"), t.booleanLiteral(false))]) - ); + realm.generator.emitStatement([], createOperationDescriptor("REACT_SSR_PREV_TEXT_NODE")); invariant(effects); realm.applyEffects(effects); invariant(effects.result instanceof SimpleNormalCompletion); diff --git a/src/react/jsx.js b/src/react/jsx.js index 0f4b4fde9..cecc80c9b 100644 --- a/src/react/jsx.js +++ b/src/react/jsx.js @@ -19,7 +19,7 @@ import type { BabelNodeStringLiteral, } from "@babel/types"; import invariant from "../invariant.js"; -import { isReactComponent } from "./utils"; +import { isReactComponent } from "./utils.js"; export function convertExpressionToJSXIdentifier( expr: BabelNodeExpression, diff --git a/src/react/reconcilation.js b/src/react/reconcilation.js index 8c3020aef..47172bb48 100644 --- a/src/react/reconcilation.js +++ b/src/react/reconcilation.js @@ -47,7 +47,7 @@ import { valueIsFactoryClassComponent, valueIsKnownReactAbstraction, valueIsLegacyCreateClassComponent, -} from "./utils"; +} from "./utils.js"; import { Get } from "../methods/index.js"; import invariant from "../invariant.js"; import { Properties } from "../singletons.js"; @@ -57,7 +57,6 @@ import { getValueWithBranchingLogicApplied, wrapReactElementInBranchOrReturnValue, } from "./branching.js"; -import * as t from "@babel/types"; import { Completion } from "../completions.js"; import { getInitialProps, @@ -79,6 +78,8 @@ import { import { Logger } from "../utils/logger.js"; import type { ClassComponentMetadata, ReactComponentTreeConfig, ReactHint } from "../types.js"; import { handleReportedSideEffect } from "../serializer/utils.js"; +import { createOperationDescriptor } from "../utils/generator.js"; +import * as t from "@babel/types"; type ComponentResolutionStrategy = | "NORMAL" @@ -900,9 +901,7 @@ export class Reconciler { this.realm, ObjectValue, [reactDomPortalFunc, resolvedReactPortalValue, domNodeValue], - ([renderNode, ..._args]) => { - return t.callExpression(renderNode, ((_args: any): Array)); - }, + createOperationDescriptor("REACT_TEMPORAL_FUNC"), { skipInvariant: true, isPure: true } ); } diff --git a/src/realm.js b/src/realm.js index 695ee2293..41c79b8f7 100644 --- a/src/realm.js +++ b/src/realm.js @@ -64,12 +64,15 @@ import { import type { Compatibility, RealmOptions, ReactOutputTypes, InvariantModeTypes } from "./options.js"; import invariant from "./invariant.js"; import seedrandom from "seedrandom"; -import { Generator, PreludeGenerator, type TemporalBuildNodeEntry } from "./utils/generator.js"; -import { emptyExpression, voidExpression } from "./utils/babelhelpers.js"; +import { + createOperationDescriptor, + Generator, + PreludeGenerator, + type TemporalBuildNodeEntry, +} from "./utils/generator.js"; import { Environment, Functions, Join, Properties, To, Widen, Path } from "./singletons.js"; import type { ReactSymbolTypes } from "./react/utils.js"; import type { BabelNode, BabelNodeSourceLocation, BabelNodeLVal, BabelNodeStatement } from "@babel/types"; -import * as t from "@babel/types"; import { Utils } from "./singletons.js"; export type BindingEntry = { @@ -1018,12 +1021,16 @@ export class Realm { // Generate code using effects2 because its expressions have not been widened away. const e2 = effects2; this._applyPropertiesToNewlyCreatedObjects(e2.modifiedProperties, e2.createdObjects); - this._emitPropertAssignments(e2.generator, e2.modifiedProperties, e2.createdObjects); + this._emitPropertyAssignments(e2.generator, e2.modifiedProperties, e2.createdObjects); this._emitLocalAssignments(e2.generator, e2.modifiedBindings, e2.createdObjects); invariant(test instanceof AbstractValue); - let cond = e2.generator.deriveAbstract(test.types, test.values, [test], ([n]) => n, { - skipInvariant: true, - }); + let cond = e2.generator.deriveAbstract( + test.types, + test.values, + [test], + createOperationDescriptor("SINGLE_ARG"), + { skipInvariant: true } + ); return [effects1, effects2, cond]; } effects1 = Widen.widenEffects(this, effects1, effects2); @@ -1109,10 +1116,8 @@ export class Realm { bindings.forEach((binding, key, map) => { let val = binding.value; if (val instanceof AbstractValue) { - invariant(val._buildNode !== undefined); - let tval = gen.deriveAbstract(val.types, val.values, [val], ([n]) => n, { - skipInvariant: true, - }); + invariant(val.operationDescriptor !== undefined); + let tval = gen.deriveAbstract(val.types, val.values, [val], createOperationDescriptor("SINGLE_ARG")); tvalFor.set(key, tval); } }); @@ -1122,36 +1127,18 @@ export class Realm { let phiNode = key.phiNode; let tval = tvalFor.get(key); invariant(tval !== undefined); - gen.emitStatement([tval], ([v]) => { - invariant(phiNode !== undefined); - let id = phiNode.buildNode([]); - return t.expressionStatement(t.assignmentExpression("=", (id: any), v)); - }); + gen.emitStatement([tval], createOperationDescriptor("LOCAL_ASSIGNMENT", { value: phiNode })); } if (val instanceof ObjectValue && newlyCreatedObjects.has(val)) { let phiNode = key.phiNode; - gen.emitStatement([val], ([v]) => { - invariant(phiNode !== undefined); - let id = phiNode.buildNode([]); - return t.expressionStatement(t.assignmentExpression("=", (id: any), v)); - }); + gen.emitStatement([val], createOperationDescriptor("LOCAL_ASSIGNMENT", { value: phiNode })); } }); } // populate the loop body generator with assignments that will update properties modified inside the loop - _emitPropertAssignments(gen: Generator, pbindings: PropertyBindings, newlyCreatedObjects: CreatedObjects): void { - function isSelfReferential(value: Value, pathNode: void | AbstractValue): boolean { - if (value === pathNode) return true; - if (value instanceof AbstractValue && pathNode !== undefined) { - for (let v of value.args) { - if (isSelfReferential(v, pathNode)) return true; - } - } - return false; - } - + _emitPropertyAssignments(gen: Generator, pbindings: PropertyBindings, newlyCreatedObjects: CreatedObjects): void { let tvalFor: Map = new Map(); pbindings.forEach((val, key, map) => { if (newlyCreatedObjects.has(key.object) || key.object.refuseSerialization) { @@ -1159,20 +1146,12 @@ export class Realm { } let value = val && val.value; if (value instanceof AbstractValue) { - invariant(value._buildNode !== undefined); + invariant(value.operationDescriptor !== undefined); let tval = gen.deriveAbstract( value.types, value.values, [key.object, value], - ([o, n]) => { - invariant(value instanceof Value); - if (typeof key.key === "string" && value.mightHaveBeenDeleted() && isSelfReferential(value, key.pathNode)) { - let inTest = t.binaryExpression("in", t.stringLiteral(key.key), o); - let addEmpty = t.conditionalExpression(inTest, n, emptyExpression); - n = t.logicalExpression("||", n, addEmpty); - } - return n; - }, + createOperationDescriptor("LOGICAL_PROPERTY_ASSIGNMENT", { binding: key, value }), { skipInvariant: true, } @@ -1189,27 +1168,13 @@ export class Realm { invariant(val !== undefined); let value = val.value; invariant(value instanceof Value); - let mightHaveBeenDeleted = value.mightHaveBeenDeleted(); - let mightBeUndefined = value.mightBeUndefined(); let keyKey = key.key; if (typeof keyKey === "string") { if (path !== undefined) { - gen.emitStatement([key.object, tval || value, this.intrinsics.empty], ([o, v, e]) => { - invariant(path !== undefined); - invariant(typeof keyKey === "string"); - let lh = path.buildNode([o, t.identifier(keyKey)]); - let r = t.expressionStatement(t.assignmentExpression("=", (lh: any), v)); - if (mightHaveBeenDeleted) { - // If v === __empty || (v === undefined && !(key.key in o)) then delete it - let emptyTest = t.binaryExpression("===", v, e); - let undefinedTest = t.binaryExpression("===", v, voidExpression); - let inTest = t.unaryExpression("!", t.binaryExpression("in", t.stringLiteral(keyKey), o)); - let guard = t.logicalExpression("||", emptyTest, t.logicalExpression("&&", undefinedTest, inTest)); - let deleteIt = t.expressionStatement(t.unaryExpression("delete", (lh: any))); - return t.ifStatement(mightBeUndefined ? emptyTest : guard, deleteIt, r); - } - return r; - }); + gen.emitStatement( + [key.object, tval || value, this.intrinsics.empty], + createOperationDescriptor("CONDITIONAL_PROPERTY_ASSIGNMENT", { binding: key, path, value }) + ); } else { // RH value was not widened, so it must have been a constant. We don't need to assign that inside the loop. // Note, however, that if the LH side is a property of an intrinsic object, then an assignment will @@ -1218,11 +1183,10 @@ export class Realm { } else { // TODO: What if keyKey is undefined? invariant(keyKey instanceof Value); - gen.emitStatement([key.object, keyKey, tval || value, this.intrinsics.empty], ([o, p, v, e]) => { - invariant(path !== undefined); - let lh = path.buildNode([o, p]); - return t.expressionStatement(t.assignmentExpression("=", (lh: any), v)); - }); + gen.emitStatement( + [key.object, keyKey, tval || value, this.intrinsics.empty], + createOperationDescriptor("PROPERTY_ASSIGNMENT", { path }) + ); } }); } @@ -1699,11 +1663,10 @@ export class Realm { propertyValue.intrinsicName = `${path}.${key}`; propertyValue.kind = "rebuiltProperty"; propertyValue.args = [object]; - propertyValue._buildNode = ([node]) => - t.isValidIdentifier(key) - ? t.memberExpression(node, t.identifier(key), false) - : t.memberExpression(node, t.stringLiteral(key), true); - this.rebuildNestedProperties(propertyValue, propertyValue.intrinsicName); + propertyValue.operationDescriptor = createOperationDescriptor("REBUILT_OBJECT", { propName: key }); + let intrinsicName = propertyValue.intrinsicName; + invariant(intrinsicName !== undefined); + this.rebuildNestedProperties(propertyValue, intrinsicName); } } diff --git a/src/serializer/Emitter.js b/src/serializer/Emitter.js index 52a742e9b..294c8a433 100644 --- a/src/serializer/Emitter.js +++ b/src/serializer/Emitter.js @@ -88,7 +88,7 @@ export class Emitter { onAbstractValueWithIdentifier: val => { // If the value hasn't been declared yet, then we should wait for it. if ( - derivedIds.has(val.getIdentifier().name) && + derivedIds.has(val.getIdentifier()) && !this.cannotDeclare() && !this.hasBeenDeclared(val) && (!this.emittingToAdditionalFunction() || referencedDeclaredValues.get(val) !== undefined) diff --git a/src/serializer/ResidualFunctionInstantiator.js b/src/serializer/ResidualFunctionInstantiator.js index 46e3802d8..a1729f330 100644 --- a/src/serializer/ResidualFunctionInstantiator.js +++ b/src/serializer/ResidualFunctionInstantiator.js @@ -37,7 +37,7 @@ import type { } from "@babel/types"; import type { FunctionBodyAstNode } from "../types.js"; import type { FactoryFunctionInfo } from "./types.js"; -import { nullExpression } from "../utils/babelhelpers.js"; +import { nullExpression } from "../utils/babelhelpers"; function canShareFunctionBody(duplicateFunctionInfo: FactoryFunctionInfo): boolean { if (duplicateFunctionInfo.anyContainingAdditionalFunction) { diff --git a/src/serializer/ResidualHeapSerializer.js b/src/serializer/ResidualHeapSerializer.js index b28bd0002..37040a883 100644 --- a/src/serializer/ResidualHeapSerializer.js +++ b/src/serializer/ResidualHeapSerializer.js @@ -42,7 +42,7 @@ import type { BabelNodeFunctionExpression, } from "@babel/types"; import { Generator, PreludeGenerator, NameGenerator } from "../utils/generator.js"; -import type { SerializationContext } from "../utils/generator.js"; +import type { OperationDescriptor, SerializationContext } from "../utils/generator.js"; import invariant from "../invariant.js"; import type { ResidualFunctionBinding, @@ -81,9 +81,10 @@ import type { Binding } from "../environment.js"; import { GlobalEnvironmentRecord, DeclarativeEnvironmentRecord, FunctionEnvironmentRecord } from "../environment.js"; import type { Referentializer } from "./Referentializer.js"; import { GeneratorDAG } from "./GeneratorDAG.js"; -import { type Replacement, getReplacement } from "./ResidualFunctionInstantiator"; +import { type Replacement, getReplacement } from "./ResidualFunctionInstantiator.js"; import { describeValue } from "../utils.js"; import { getAsPropertyNameExpression } from "../utils/babelhelpers.js"; +import { ResidualOperationSerializer } from "./ResidualOperationSerializer.js"; function commentStatement(text: string) { let s = t.emptyStatement(); @@ -142,6 +143,7 @@ export class ResidualHeapSerializer { let realmPreludeGenerator = this.realm.preludeGenerator; invariant(realmPreludeGenerator); this.preludeGenerator = realmPreludeGenerator; + this.residualOperationSerializer = new ResidualOperationSerializer(realm, realmPreludeGenerator); this.prelude = []; this._descriptors = new Map(); @@ -231,6 +233,7 @@ export class ResidualHeapSerializer { body: Array; mainBody: SerializedBody; realm: Realm; + residualOperationSerializer: ResidualOperationSerializer; preludeGenerator: PreludeGenerator; generator: Generator; _descriptors: Map; @@ -385,7 +388,9 @@ export class ResidualHeapSerializer { ? t.memberExpression(uid, protoExpression) : t.callExpression(this.preludeGenerator.memoizeReference("Object.getPrototypeOf"), [uid]); let condition = t.binaryExpression("!==", fetchedPrototype, serializedProto); - let consequent = this.generator.getErrorStatement(t.stringLiteral("unexpected prototype")); + let consequent = this.residualOperationSerializer.getErrorStatement( + t.stringLiteral("unexpected prototype") + ); this.emitter.emit(t.ifStatement(condition, consequent)); }, this.emitter.getBody() @@ -1911,7 +1916,8 @@ export class ResidualHeapSerializer { let prop = this.serializeValue(val.args[1]); return t.memberExpression(obj, prop, true); } - let serializedValue = val.buildNode(serializedArgs); + invariant(val.operationDescriptor !== undefined); + let serializedValue = this.residualOperationSerializer.serialize(val.operationDescriptor, serializedArgs); if (serializedValue.type === "Identifier") { let id = ((serializedValue: any): BabelNodeIdentifier); invariant( @@ -2123,13 +2129,30 @@ export class ResidualHeapSerializer { // along the code of the nested generator; their definitions need to get hoisted // or repeated so that they are accessible and defined from all using scopes let context = { + serializeOperationDescriptor: ( + operationDescriptor: OperationDescriptor, + nodes: Array, + _context: SerializationContext, + valuesToProcess: Set + ) => { + let serializedValue = this.residualOperationSerializer.serialize( + operationDescriptor, + nodes, + _context, + valuesToProcess + ); + + return ((serializedValue: any): BabelNodeStatement); + }, serializeValue: this.serializeValue.bind(this), serializeBinding: this.serializeBinding.bind(this), serializeGenerator: ( generator: Generator, valuesToProcess: Set ): Array => - this._withGeneratorScope("Generator", generator, valuesToProcess, () => generator.serialize(context)), + this._withGeneratorScope("Generator", generator, valuesToProcess, () => + generator.serialize(((context: any): SerializationContext)) + ), initGenerator: (generator: Generator) => { let activeGeneratorBody = this._getActiveBodyOfGenerator(generator); invariant(activeGeneratorBody === this.emitter.getBody(), "generator to init must be current emitter body"); diff --git a/src/serializer/ResidualOperationSerializer.js b/src/serializer/ResidualOperationSerializer.js new file mode 100644 index 000000000..077302387 --- /dev/null +++ b/src/serializer/ResidualOperationSerializer.js @@ -0,0 +1,998 @@ +/** + * Copyright (c) 2017-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +/* @flow */ + +import { Realm } from "../realm.js"; +import { + Generator, + PreludeGenerator, + type SerializationContext, + type OperationDescriptor, + type OperationDescriptorData, +} from "../utils/generator.js"; +import { + emptyExpression, + memberExpressionHelper, + nullExpression, + protoExpression, + voidExpression, +} from "../utils/babelhelpers.js"; +import invariant from "../invariant.js"; +import { type Binding } from "../environment.js"; +import * as t from "@babel/types"; +import { AbstractValue, EmptyValue, ObjectValue, Value } from "../values/index.js"; +import type { BabelNodeBlockStatement, BabelNodeExpression, BabelNodeSpreadElement } from "@babel/types"; +import { Utils } from "../singletons.js"; +import type { PropertyBinding } from "../types.js"; + +const labels = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + +function serializeBody( + generator: Generator, + context: SerializationContext, + valuesToProcess: Set +): BabelNodeBlockStatement { + let statements = context.serializeGenerator(generator, valuesToProcess); + if (statements.length === 1 && statements[0].type === "BlockStatement") return (statements[0]: any); + return t.blockStatement(statements); +} + +function isSelfReferential(value: Value, pathNode: void | AbstractValue): boolean { + if (value === pathNode) return true; + if (value instanceof AbstractValue && pathNode !== undefined) { + for (let v of value.args) { + if (isSelfReferential(v, pathNode)) return true; + } + } + return false; +} + +export class ResidualOperationSerializer { + constructor(realm: Realm, preludeGenerator: PreludeGenerator) { + this.realm = realm; + this.preludeGenerator = preludeGenerator; + } + realm: Realm; + preludeGenerator: PreludeGenerator; + + getErrorStatement(message: BabelNodeExpression): BabelNodeStatement { + if (this.realm.invariantMode === "throw") + return t.throwStatement(t.newExpression(this.preludeGenerator.memoizeReference("Error"), [message])); + else { + let targetReference = this.realm.invariantMode; + let args = [message]; + let i = targetReference.indexOf("+"); + if (i !== -1) { + let s = targetReference.substr(i + 1); + let x = Number.parseInt(s, 10); + args.push(isNaN(x) ? t.stringLiteral(s) : t.numericLiteral(x)); + targetReference = targetReference.substr(0, i); + } + return t.expressionStatement(t.callExpression(this.preludeGenerator.memoizeReference(targetReference), args)); + } + } + + serialize( + operationDescriptor: OperationDescriptor, + nodes: Array, + context?: SerializationContext, + valuesToProcess?: Set + ): BabelNodeStatement | BabelNodeExpression { + let { data, kind, type } = operationDescriptor; + let babelNode; + + switch (type) { + case "IDENTIFIER": + babelNode = this._serializeIdentifier(data); + break; + case "REBUILT_OBJECT": + babelNode = this._serializeRebuiltObject(data, nodes); + break; + case "FOR_IN": + babelNode = this._serializeForIn(data, nodes); + break; + case "DO_WHILE": + babelNode = this._serializeDoWhile(data, nodes, context, valuesToProcess); + break; + case "APPEND_GENERATOR": + babelNode = this._serializeAppendGenerator(data, nodes, context, valuesToProcess); + break; + case "JOIN_GENERATORS": + babelNode = this._serializeJoinGenerators(data, nodes, context, valuesToProcess); + break; + case "BINARY_EXPRESSION": + babelNode = this._serializeBinaryExpression(data, nodes); + break; + case "LOGICAL_EXPRESSION": + babelNode = this._serializeLogicalExpression(data, nodes); + break; + case "CONDITIONAL_EXPRESSION": + babelNode = this._serializeConditionalExpression(data, nodes); + break; + case "UNARY_EXPRESSION": + babelNode = this._serializeUnaryExpression(data, nodes); + break; + case "ABSTRACT_PROPERTY": + babelNode = this._serializeAbstractProperty(data, nodes); + break; + case "ABSTRACT_FROM_TEMPLATE": + babelNode = this._serializeAbstractFromTemplate(data, nodes); + break; + case "GLOBAL_ASSIGNMENT": + babelNode = this._serializeGlobalAssignment(data, nodes); + break; + case "GLOBAL_DELETE": + babelNode = this._serializeGlobalDelete(data); + break; + case "EMIT_PROPERTY_ASSIGNMENT": + babelNode = this._serializeEmitPropertyAssignment(data, nodes, context); + break; + case "PROPERTY_DELETE": + babelNode = this._serializePropertyDelete(data, nodes); + break; + case "THROW": + babelNode = this._serializeThrow(data, nodes); + break; + case "CONDITIONAL_THROW": + babelNode = this._serializeConditionalThrow(data, nodes, context); + break; + case "COERCE_TO_STRING": + babelNode = this._serializeCoerceToString(data, nodes); + break; + case "OBJECT_ASSIGN": + babelNode = this._serializeObjectAssign(data, nodes); + break; + case "SINGLE_ARG": + babelNode = this._serializeSingleArg(data, nodes); + break; + case "CALL_BAILOUT": + babelNode = this._serializeCallBailout(data, nodes); + break; + case "EMIT_CALL": + babelNode = this._serializeEmitCall(data, nodes); + break; + case "EMIT_CALL_AND_CAPTURE_RESULT": + babelNode = this._serializeEmitCallAndCaptureResults(data, nodes); + break; + case "CONCRETE_MODEL": + babelNode = this._serializeConcreteModel(data, nodes); + break; + case "NEW_EXPRESSION": + babelNode = this._serializeNewExpression(data, nodes); + break; + case "FOR_STATEMENT_FUNC": + babelNode = this._serializeForFunctionCall(data, nodes); + break; + case "GET_BINDING": + babelNode = this._serializeGetBinding(data, nodes, context); + break; + case "UNKNOWN_ARRAY_METHOD_CALL": + babelNode = this._serializeUnknownArrayMethodCall(data, nodes); + break; + case "UNKNOWN_ARRAY_METHOD_PROPERTY_CALL": + babelNode = this._serializeUnknownArrayMethodPropertyCall(data, nodes); + break; + case "UNKNOWN_ARRAY_LENGTH": + babelNode = this._serializeUnknownArrayLength(data, nodes); + break; + case "UNKNOWN_ARRAY_GET_PARTIAL": + babelNode = this._serializeUnknownArrayGetPartial(data, nodes); + break; + case "OBJECT_SET_PARTIAL": + babelNode = this._serializeObjectSetPartial(data, nodes); + break; + case "OBJECT_GET_PARTIAL": + babelNode = this._serializeObjectGetPartial(data, nodes); + break; + case "ABSTRACT_OBJECT_GET_PARTIAL": + babelNode = this._serializeAbstractObjectGetPartial(data, nodes); + break; + case "ABSTRACT_OBJECT_SET_PARTIAL": + babelNode = this._serializeAbstractObjectSetPartial(data, nodes); + break; + case "ABSTRACT_OBJECT_SET_PARTIAL_VALUE": + babelNode = this._serializeAbstractObjectSetPartialValue(data, nodes); + break; + case "ABSTRACT_OBJECT_GET_PROTO_OF": + babelNode = this._serializeAbstractObjectGetProtoOf(data, nodes); + break; + case "ABSTRACT_OBJECT_GET": + babelNode = this._serializeAbstractObjectGet(data, nodes); + break; + case "DEFINE_PROPERTY": + babelNode = this._serializeDefineProperty(data, nodes, context); + break; + case "OBJECT_PROTO_HAS_OWN_PROPERTY": + babelNode = this._serializeObjectProtoHasOwnProperty(data, nodes); + break; + case "OBJECT_PROTO_GET_OWN_PROPERTY_DESCRIPTOR": + babelNode = this._serializeObjectProtoGetOwnPropertyDescriptor(data, nodes); + break; + case "DIRECT_CALL_WITH_ARG_LIST": + babelNode = this._serializeDirectCallWithArgList(data, nodes); + break; + case "CALL_ABSTRACT_FUNC": + babelNode = this._serializeCallAbstractFunc(data, nodes); + break; + case "CALL_ABSTRACT_FUNC_THIS": + babelNode = this._serializeCallAbstractFuncThis(data, nodes); + break; + case "LOCAL_ASSIGNMENT": + babelNode = this._serializeLocalAssignment(data, nodes, context, valuesToProcess); + break; + case "LOGICAL_PROPERTY_ASSIGNMENT": + babelNode = this._serializeLogicalPropertyAssignment(data, nodes); + break; + case "CONDITIONAL_PROPERTY_ASSIGNMENT": + babelNode = this._serializeConditionalPropertyAssignment(data, nodes, context, valuesToProcess); + break; + case "PROPERTY_ASSIGNMENT": + babelNode = this._serializePropertyAssignment(data, nodes, context, valuesToProcess); + break; + case "UPDATE_INCREMENTOR": + babelNode = this._serializeUpdateIncrementor(data, nodes); + break; + case "CONSOLE_LOG": + babelNode = this._serializeConsoleLog(data, nodes); + break; + case "MODULES_REQUIRE": + babelNode = this._serializeModulesRequires(data); + break; + case "RESIDUAL_CALL": + babelNode = this._serializeResidualCall(data, nodes); + break; + case "ASSUME_CALL": + babelNode = this._serializeAssumeCall(data, nodes); + break; + case "CANNOT_BECOME_OBJECT": + babelNode = this._serializeCannotBecomeObject(data, nodes); + break; + case "WIDENED_IDENTIFIER": + babelNode = this._serializeIdentifier(data); + break; + case "WIDEN_PROPERTY": + babelNode = this._serializeWidenProperty(data, nodes); + break; + case "WIDEN_ABSTRACT_PROPERTY": + babelNode = this._serializeWidenAbstractProperty(data, nodes); + break; + case "WIDEN_PROPERTY_ASSIGNMENT": + babelNode = this._serializeWidenPropertyAssignment(data, nodes); + break; + case "WIDEN_ABSTRACT_PROPERTY_ASSIGNMENT": + babelNode = this._serializeWidenAbstractPropertyAssignment(data, nodes); + break; + + // Invariants + case "INVARIANT": + babelNode = this._serializeInvariant(data, nodes); + break; + case "DERIVED_ABSTRACT_INVARIANT": + babelNode = this._serializeDerivedAbstractInvariant(data, nodes); + break; + case "PROPERTY_INVARIANT": + babelNode = this._serializePropertyInvariant(data, nodes); + break; + case "INVARIANT_APPEND": + babelNode = this._serializeInvariantAppend(data, nodes); + break; + case "FULL_INVARIANT": + babelNode = this._serializeFullInvariant(data, nodes); + break; + case "FULL_INVARIANT_ABSTRACT": + babelNode = this._serializeFullInvariantAbstract(data, nodes); + break; + case "FULL_INVARIANT_FUNCTION": + babelNode = this._serializeFullInvariantFunction(data, nodes); + break; + + // React + case "REACT_DEFAULT_PROPS_HELPER": + babelNode = this._serializeReactDefaultPropsHelper(data, nodes); + break; + case "REACT_SSR_REGEX_CONSTANT": + return t.variableDeclaration("var", [ + t.variableDeclarator(t.identifier("matchHtmlRegExp"), t.regExpLiteral("[\"'&<>]")), + ]); + case "REACT_SSR_PREV_TEXT_NODE": + return t.variableDeclaration("var", [ + t.variableDeclarator(t.identifier("previousWasTextNode"), t.booleanLiteral(false)), + ]); + case "REACT_SSR_RENDER_VALUE_HELPER": + babelNode = this._serializeReactRenderValueHelper(data, nodes); + break; + case "REACT_SSR_TEMPLATE_LITERAL": + babelNode = this._serializeReactSSRTemplateLiteral(data, nodes); + break; + case "REACT_TEMPORAL_FUNC": + babelNode = this._serializeReactTemporalFunc(data, nodes); + break; + case "REACT_CREATE_CONTEXT_PROVIDER": + babelNode = this._serializeReactCreateContextProvider(data, nodes); + break; + case "REACT_NATIVE_STRING_LITERAL": + babelNode = this._serializeReactNativeStringLiteral(data); + break; + case "REACT_RELAY_MOCK_CONTAINER": + babelNode = this._serializeReactRelayMockContainer(data, nodes); + break; + + // FB Mocks + case "FB_MOCKS_BOOTLOADER_LOAD_MODULES": + babelNode = this._serializeFBMocksBootloaderLoadModules(data, nodes); + break; + case "FB_MOCKS_MAGIC_GLOBAL_FUNCTION": + babelNode = this._serializeFBMocksMagicGlobalFunction(data, nodes); + break; + + // Babel helpers + case "BABEL_HELPERS_OBJECT_WITHOUT_PROPERTIES": + babelNode = this._serializeBabelHelpersObjectWithoutProperties(data, nodes); + break; + default: + invariant(false, `operation descriptor "type" not recognized when serializing operation descriptor`); + } + + if (kind === "DERIVED") { + return this._serializeDerivedOperationDescriptor(data, ((babelNode: any): BabelNodeExpression)); + } else if (kind === "VOID") { + return this._serializeVoidOperationDescriptor(((babelNode: any): BabelNodeExpression)); + } + return babelNode; + } + + _serializeAppendGenerator( + { generator, propName: leadingComment }: OperationDescriptorData, + node: Array, + context?: SerializationContext, + valuesToProcess?: Set + ) { + invariant(context !== undefined); + invariant(generator !== undefined); + invariant(leadingComment !== undefined); + invariant(valuesToProcess !== undefined); + let statements = context.serializeGenerator(generator, valuesToProcess); + if (statements.length === 1) { + let statement = statements[0]; + if (leadingComment.length > 0) + statement.leadingComments = [({ type: "BlockComment", value: leadingComment }: any)]; + return statement; + } + let block = t.blockStatement(statements); + if (leadingComment.length > 0) { + block.leadingComments = [({ type: "BlockComment", value: leadingComment }: any)]; + } + return block; + } + + _serializeAssumeCall(data: OperationDescriptorData, [c, s]: Array) { + let errorLiteral = s.type === "StringLiteral" ? s : t.stringLiteral("Assumption violated"); + return t.ifStatement( + t.unaryExpression("!", c), + t.blockStatement([t.throwStatement(t.newExpression(t.identifier("Error"), [errorLiteral]))]) + ); + } + + _serializeWidenAbstractPropertyAssignment(data: OperationDescriptorData, [o, p, v]: Array) { + return t.assignmentExpression("=", memberExpressionHelper(o, p), v); + } + + _serializeWidenPropertyAssignment({ propName }: OperationDescriptorData, [o, v]: Array) { + invariant(typeof propName === "string"); + return t.assignmentExpression("=", memberExpressionHelper(o, propName), v); + } + + _serializeWidenAbstractProperty(data: OperationDescriptorData, [o, p]: Array) { + return memberExpressionHelper(o, p); + } + + _serializeWidenProperty({ propName }: OperationDescriptorData, [o]: Array) { + invariant(typeof propName === "string"); + return memberExpressionHelper(o, propName); + } + + _serializeAbstractObjectGet( + { propertyGetter, propName: P }: OperationDescriptorData, + [o]: Array + ) { + invariant(typeof P === "string"); + return propertyGetter !== undefined + ? t.callExpression(t.memberExpression(t.identifier("global"), t.identifier("__prop_" + propertyGetter)), [ + o, + t.stringLiteral(P), + ]) + : memberExpressionHelper(o, P); + } + + _serializeAbstractObjectGetProtoOf(data: OperationDescriptorData, [p]: Array) { + invariant(this.realm.preludeGenerator !== undefined); + let getPrototypeOf = this.realm.preludeGenerator.memoizeReference("Object.getPrototypeOf"); + return this.realm.isCompatibleWith(this.realm.MOBILE_JSC_VERSION) || this.realm.isCompatibleWith("mobile") + ? t.memberExpression(p, protoExpression) + : t.callExpression(getPrototypeOf, [p]); + } + + _serializeCannotBecomeObject(data: OperationDescriptorData, [n]: Array) { + let callFunc = t.identifier("global.__cannotBecomeObject"); + return t.callExpression(callFunc, [n]); + } + + _serializeResidualCall(data: OperationDescriptorData, nodes: Array) { + return t.callExpression(nodes[0], ((nodes.slice(1): any): Array)); + } + + _serializeModulesRequires({ propName }: OperationDescriptorData) { + invariant(propName !== undefined); + return t.callExpression(t.identifier("require"), [t.valueToNode(propName)]); + } + + _serializeConcreteModel({ propName }: OperationDescriptorData, [valueNode]: Array) { + invariant(propName !== undefined); + return t.expressionStatement( + t.assignmentExpression("=", this.preludeGenerator.globalReference(propName, false), valueNode) + ); + } + + _serializeConsoleLog({ propName }: OperationDescriptorData, nodes: Array) { + invariant(propName !== undefined); + return t.expressionStatement( + t.callExpression(t.memberExpression(t.identifier("console"), t.identifier(propName)), [...nodes]) + ); + } + + _serializeDoWhile( + { generator, value }: OperationDescriptorData, + nodes: Array, + context?: SerializationContext, + valuesToProcess?: Set + ) { + invariant(context !== undefined); + invariant(valuesToProcess !== undefined); + invariant(value !== undefined); + let testId = value.intrinsicName; + invariant(testId !== undefined); + invariant(generator !== undefined); + let statements = context.serializeGenerator(generator, valuesToProcess); + let block = t.blockStatement(statements); + return t.doWhileStatement(t.identifier(testId), block); + } + + _serializeForIn({ boundName, lh }: OperationDescriptorData, [obj, tgt, src]: Array) { + invariant(boundName !== undefined); + invariant(lh !== undefined); + return t.forInStatement( + lh, + obj, + t.blockStatement([ + t.expressionStatement( + t.assignmentExpression("=", memberExpressionHelper(tgt, boundName), memberExpressionHelper(src, boundName)) + ), + ]) + ); + } + + _serializeFullInvariant({ propName }: OperationDescriptorData, [objectNode, valueNode]: Array) { + invariant(propName !== undefined); + return t.binaryExpression("!==", memberExpressionHelper(objectNode, propName), valueNode); + } + + _serializeFullInvariantFunction({ propName }: OperationDescriptorData, [objectNode]: Array) { + invariant(typeof propName === "string"); + return t.binaryExpression( + "!==", + t.unaryExpression("typeof", memberExpressionHelper(objectNode, propName), true), + t.stringLiteral("function") + ); + } + + _serializeFullInvariantAbstract( + { concreteComparisons, typeComparisons }: OperationDescriptorData, + [valueNode]: Array + ) { + invariant(concreteComparisons !== undefined); + invariant(typeComparisons !== undefined); + // Create `object.property !== concreteValue` + let checks = concreteComparisons.map(concreteValue => + t.binaryExpression("!==", valueNode, t.valueToNode(concreteValue.serialize())) + ); + // Create `typeof object.property !== typeValue` + checks = checks.concat( + [...typeComparisons].map(typeValue => { + let typeString = Utils.typeToString(typeValue); + invariant(typeString !== undefined, typeValue); + return t.binaryExpression("!==", t.unaryExpression("typeof", valueNode, true), t.stringLiteral(typeString)); + }) + ); + return checks.reduce((expr, newCondition) => t.logicalExpression("&&", expr, newCondition)); + } + + _serializeInvariantAppend({ propName }: OperationDescriptorData, [objectNode]: Array) { + invariant(typeof propName === "string"); + return memberExpressionHelper(objectNode, propName); + } + + _serializePropertyInvariant({ propName, state }: OperationDescriptorData, [objectNode]: Array) { + invariant(state !== undefined); + invariant(typeof propName === "string"); + let n = t.callExpression( + t.memberExpression( + this.preludeGenerator.memoizeReference("Object.prototype.hasOwnProperty"), + t.identifier("call") + ), + [objectNode, t.stringLiteral(propName)] + ); + if (state !== "MISSING") { + n = t.unaryExpression("!", n, true); + if (state === "DEFINED") + n = t.logicalExpression( + "||", + n, + t.binaryExpression("===", memberExpressionHelper(objectNode, propName), t.valueToNode(undefined)) + ); + } + return n; + } + + _serializeUpdateIncrementor({ op }: OperationDescriptorData, [oldValNode]: Array) { + invariant(op !== undefined); + return t.binaryExpression(op, oldValNode, t.numericLiteral(1)); + } + + _serializeDerivedAbstractInvariant({ typeofString }: OperationDescriptorData, nodes: Array) { + invariant(typeofString !== undefined); + let condition = t.binaryExpression("!==", t.unaryExpression("typeof", nodes[0]), t.stringLiteral(typeofString)); + if (typeofString === "object") { + condition = t.logicalExpression( + "&&", + condition, + t.binaryExpression("!==", t.unaryExpression("typeof", nodes[0]), t.stringLiteral("function")) + ); + condition = t.logicalExpression("||", condition, t.binaryExpression("===", nodes[0], nullExpression)); + } + return condition; + } + + _serializeInvariant( + { appendLastToInvariantOperationDescriptor, violationConditionOperationDescriptor }: OperationDescriptorData, + nodes: Array + ) { + invariant(violationConditionOperationDescriptor !== undefined); + let messageComponents = [ + t.stringLiteral("Prepack model invariant violation ("), + t.numericLiteral(this.preludeGenerator.nextInvariantId++), + ]; + if (appendLastToInvariantOperationDescriptor) { + let last = nodes.pop(); + messageComponents.push(t.stringLiteral("): ")); + messageComponents.push(this.serialize(appendLastToInvariantOperationDescriptor, [last])); + } else { + messageComponents.push(t.stringLiteral(")")); + } + let throwString = messageComponents[0]; + for (let i = 1; i < messageComponents.length; i++) + throwString = t.binaryExpression("+", throwString, messageComponents[i]); + let condition = this.serialize(violationConditionOperationDescriptor, nodes); + let consequent = this.getErrorStatement(throwString); + return t.ifStatement(condition, consequent); + } + + _serializeReactRelayMockContainer( + { propName }: OperationDescriptorData, + [reactRelayIdent, ...otherArgs]: Array + ) { + invariant(typeof propName === "string"); + return t.callExpression( + t.memberExpression(reactRelayIdent, t.identifier(propName)), + ((otherArgs: any): Array) + ); + } + + _serializePropertyAssignment( + { path }: OperationDescriptorData, + [o, p, v, e]: Array, + context?: SerializationContext, + valuesToProcess?: Set + ) { + invariant(path instanceof AbstractValue); + invariant(path.operationDescriptor !== undefined); + let lh = this.serialize(path.operationDescriptor, [o, p], context, valuesToProcess); + return t.expressionStatement(t.assignmentExpression("=", (lh: any), v)); + } + + _serializeConditionalPropertyAssignment( + { binding: _binding, path, value }: OperationDescriptorData, + [o, v, e]: Array, + context?: SerializationContext, + valuesToProcess?: Set + ) { + invariant(value instanceof AbstractValue); + invariant(path instanceof AbstractValue); + invariant(_binding !== undefined); + let binding = ((_binding: any): PropertyBinding); + invariant(value !== undefined); + let keyKey = binding.key; + invariant(typeof keyKey === "string"); + let mightHaveBeenDeleted = value.mightHaveBeenDeleted(); + let mightBeUndefined = value.mightBeUndefined(); + invariant(path.operationDescriptor !== undefined); + let lh = this.serialize(path.operationDescriptor, [o, t.identifier(keyKey)], context, valuesToProcess); + let r = t.expressionStatement(t.assignmentExpression("=", (lh: any), v)); + if (mightHaveBeenDeleted) { + // If v === __empty || (v === undefined && !(key.key in o)) then delete it + let emptyTest = t.binaryExpression("===", v, e); + let undefinedTest = t.binaryExpression("===", v, voidExpression); + let inTest = t.unaryExpression("!", t.binaryExpression("in", t.stringLiteral(keyKey), o)); + let guard = t.logicalExpression("||", emptyTest, t.logicalExpression("&&", undefinedTest, inTest)); + let deleteIt = t.expressionStatement(t.unaryExpression("delete", (lh: any))); + return t.ifStatement(mightBeUndefined ? emptyTest : guard, deleteIt, r); + } + return r; + } + + _serializeLogicalPropertyAssignment( + { binding: _binding, value }: OperationDescriptorData, + [o, n]: Array + ) { + invariant(value instanceof Value); + invariant(_binding !== undefined); + let binding = ((_binding: any): PropertyBinding); + if (typeof binding.key === "string" && value.mightHaveBeenDeleted() && isSelfReferential(value, binding.pathNode)) { + let inTest = t.binaryExpression("in", t.stringLiteral(binding.key), o); + let addEmpty = t.conditionalExpression(inTest, n, emptyExpression); + n = t.logicalExpression("||", n, addEmpty); + } + return n; + } + + _serializeLocalAssignment( + { value }: OperationDescriptorData, + [v]: Array, + context?: SerializationContext, + valuesToProcess?: Set + ) { + invariant(value instanceof AbstractValue); + invariant(value.operationDescriptor !== undefined); + let id = this.serialize(value.operationDescriptor, [], context, valuesToProcess); + return t.expressionStatement(t.assignmentExpression("=", (id: any), v)); + } + + _serializeReactNativeStringLiteral({ propName }: OperationDescriptorData) { + invariant(typeof propName === "string"); + return t.stringLiteral(propName); + } + + _serializeReactCreateContextProvider(data: OperationDescriptorData, [consumerNode]: Array) { + return t.memberExpression(consumerNode, t.identifier("Provider")); + } + + _serializeReactTemporalFunc(data: OperationDescriptorData, [renderNode, ..._args]: Array) { + return t.callExpression(renderNode, ((_args: any): Array)); + } + + _serializeCallAbstractFunc(data: OperationDescriptorData, nodes: Array) { + let fun_args = ((nodes.slice(1): any): Array); + return t.callExpression(nodes[0], fun_args); + } + + _serializeCallAbstractFuncThis(data: OperationDescriptorData, nodes: Array) { + let fun_args = ((nodes.slice(1): any): Array); + return t.callExpression(t.memberExpression(nodes[0], t.identifier("call")), fun_args); + } + + _serializeDirectCallWithArgList(data: OperationDescriptorData, nodes: Array) { + let fun_args = nodes.slice(1); + return t.callExpression(nodes[0], ((fun_args: any): Array)); + } + + _serializeObjectProtoHasOwnProperty( + data: OperationDescriptorData, + [methodNode, objectNode, nameNode]: Array + ) { + return t.callExpression(t.memberExpression(methodNode, t.identifier("call")), [objectNode, nameNode]); + } + + _serializeRebuiltObject({ propName }: OperationDescriptorData, [node]: Array) { + invariant(typeof propName === "string"); + return t.isValidIdentifier(propName) + ? t.memberExpression(node, t.identifier(propName), false) + : t.memberExpression(node, t.stringLiteral(propName), true); + } + + _serializeGlobalDelete({ propName }: OperationDescriptorData) { + invariant(typeof propName === "string"); + return t.expressionStatement(t.unaryExpression("delete", this.preludeGenerator.globalReference(propName, false))); + } + + _serializeDefineProperty( + { object, propName, desc }: OperationDescriptorData, + args: Array, + context?: SerializationContext + ) { + invariant(object !== undefined); + invariant(propName !== undefined); + invariant(desc !== undefined); + invariant(context !== undefined); + return context.emitDefinePropertyBody(object, propName, desc); + } + + _serializeFBMocksMagicGlobalFunction({ propName }: OperationDescriptorData, args: Array) { + invariant(typeof propName === "string"); + return t.callExpression(t.identifier(propName), ((args: any): Array)); + } + + _serializeFBMocksBootloaderLoadModules(data: OperationDescriptorData, args: Array) { + return t.callExpression( + t.memberExpression(t.identifier("Bootloader"), t.identifier("loadModules")), + ((args: any): Array) + ); + } + + _serializeAbstractObjectSetPartial( + { propName }: OperationDescriptorData, + [objectNode, valueNode]: Array + ) { + invariant(typeof propName === "string"); + return t.expressionStatement(t.assignmentExpression("=", memberExpressionHelper(objectNode, propName), valueNode)); + } + + _serializeAbstractObjectSetPartialValue( + data: OperationDescriptorData, + [objectNode, keyNode, valueNode]: Array + ) { + return t.expressionStatement(t.assignmentExpression("=", memberExpressionHelper(objectNode, keyNode), valueNode)); + } + + _serializeUnknownArrayGetPartial(data: OperationDescriptorData, [o, p]: Array) { + return memberExpressionHelper(o, p); + } + + _serializeObjectGetPartial(data: OperationDescriptorData, [o, p]: Array) { + return memberExpressionHelper(o, p); + } + + _serializeAbstractObjectGetPartial(data: OperationDescriptorData, [o, p]: Array) { + return memberExpressionHelper(o, p); + } + + _serializeObjectSetPartial( + data: OperationDescriptorData, + [objectNode, keyNode, valueNode]: Array + ) { + return t.expressionStatement(t.assignmentExpression("=", memberExpressionHelper(objectNode, keyNode), valueNode)); + } + + _serializeIdentifier(data: OperationDescriptorData) { + invariant(typeof data.id === "string"); + return t.identifier(data.id); + } + + _serializeCoerceToString(data: OperationDescriptorData, [p]: Array) { + return t.binaryExpression("+", t.stringLiteral(""), p); + } + + _serializeBabelHelpersObjectWithoutProperties( + data: OperationDescriptorData, + [methodNode, objNode, propRemoveNode]: Array + ) { + return t.callExpression(methodNode, [objNode, propRemoveNode]); + } + + _serializeReactDefaultPropsHelper(data: OperationDescriptorData, [methodNode, ..._args]: Array) { + return t.callExpression(methodNode, ((_args: any): Array)); + } + + _serializeUnknownArrayMethodCall(data: OperationDescriptorData, [methodNode, ..._args]: Array) { + return t.callExpression(methodNode, ((_args: any): Array)); + } + + _serializeUnknownArrayLength(data: OperationDescriptorData, [o]: Array) { + return t.memberExpression(o, t.identifier("length"), false); + } + + _serializeUnknownArrayMethodPropertyCall( + { propName }: OperationDescriptorData, + [objNode, ..._args]: Array + ) { + invariant(typeof propName === "string"); + return t.callExpression(t.memberExpression(objNode, t.identifier(propName)), ((_args: any): Array)); + } + + _serializeThrow(data: OperationDescriptorData, [argument]: Array) { + return t.throwStatement(argument); + } + + _serializeConditionalThrow( + { value }: OperationDescriptorData, + nodes: Array, + context?: SerializationContext + ) { + invariant(value instanceof Value); + + function createStatement(val: Value) { + invariant(context !== undefined); + if (!(val instanceof AbstractValue) || val.kind !== "conditional") { + return t.throwStatement(context.serializeValue(val)); + } + let [cond, trueVal, falseVal] = val.args; + let condVal = context.serializeValue(cond); + let trueStat, falseStat; + if (trueVal instanceof EmptyValue) trueStat = t.blockStatement([]); + else trueStat = createStatement(trueVal); + if (falseVal instanceof EmptyValue) falseStat = t.blockStatement([]); + else falseStat = createStatement(falseVal); + return t.ifStatement(condVal, trueStat, falseStat); + } + return createStatement(value); + } + + _serializeReactSSRTemplateLiteral({ quasis }: OperationDescriptorData, valueNodes: Array) { + invariant(quasis !== undefined); + return t.templateLiteral(((quasis: any): Array), valueNodes); + } + + _serializeReactRenderValueHelper(data: OperationDescriptorData, [helperNode, valueNode]: Array) { + return t.callExpression(helperNode, [valueNode]); + } + + _serializePropertyDelete({ propName }: OperationDescriptorData, [objectNode]: Array) { + invariant(propName !== undefined); + return t.expressionStatement(t.unaryExpression("delete", memberExpressionHelper(objectNode, propName))); + } + + _serializeGetBinding( + { binding }: OperationDescriptorData, + nodes: Array, + context?: SerializationContext + ) { + invariant(binding !== undefined); + invariant(context !== undefined); + return context.serializeBinding(((binding: any): Binding)); + } + + _serializeForFunctionCall(data: OperationDescriptorData, [func, thisExpr]: Array) { + let { usesThis } = data; + return usesThis + ? t.callExpression(t.memberExpression(func, t.identifier("call")), [thisExpr]) + : t.callExpression(func, []); + } + + _serializeNewExpression( + data: OperationDescriptorData, + [constructorNode, ...argListNodes]: Array + ) { + return t.newExpression(constructorNode, argListNodes); + } + + _serializeEmitCall({ callTemplate }: OperationDescriptorData, nodes: Array) { + invariant(typeof callTemplate === "function"); + return t.expressionStatement(t.callExpression(callTemplate(), [...nodes])); + } + + _serializeEmitCallAndCaptureResults({ callTemplate }: OperationDescriptorData, nodes: Array) { + invariant(typeof callTemplate === "function"); + return t.callExpression(callTemplate(), ((nodes: any): Array)); + } + + _serializeObjectProtoGetOwnPropertyDescriptor( + data: OperationDescriptorData, + [funcNode, ...args]: Array + ) { + return t.callExpression(funcNode, ((args: any): Array)); + } + + _serializeCallBailout({ propRef, thisArg }: OperationDescriptorData, nodes: Array) { + let callFunc; + let argStart = 1; + if (thisArg instanceof Value) { + if (typeof propRef === "string") { + callFunc = memberExpressionHelper(nodes[0], propRef); + } else { + callFunc = memberExpressionHelper(nodes[0], nodes[1]); + argStart = 2; + } + } else { + callFunc = nodes[0]; + } + let fun_args = ((nodes.slice(argStart): any): Array); + return t.callExpression(callFunc, fun_args); + } + + _serializeJoinGenerators( + { generators }: OperationDescriptorData, + [cond]: Array, + context?: SerializationContext, + valuesToProcess?: Set + ): BabelNodeStatement { + invariant(context !== undefined); + invariant(valuesToProcess !== undefined); + invariant(generators !== undefined); + let [generator1, generator2] = generators; + let block1 = generator1.empty() ? null : serializeBody(generator1, context, valuesToProcess); + let block2 = generator2.empty() ? null : serializeBody(generator2, context, valuesToProcess); + if (block1) return t.ifStatement(cond, block1, block2); + invariant(block2); + return t.ifStatement(t.unaryExpression("!", cond), block2); + } + + _serializeEmitPropertyAssignment( + { propName, value }: OperationDescriptorData, + [objectNode, valueNode]: Array, + context?: SerializationContext + ) { + invariant(context !== undefined); + invariant(value instanceof Value); + invariant(typeof propName === "string"); + return context.getPropertyAssignmentStatement( + memberExpressionHelper(objectNode, propName), + value, + value.mightHaveBeenDeleted(), + /* deleteIfMightHaveBeenDeleted */ true + ); + } + + _serializeGlobalAssignment({ propName }: OperationDescriptorData, [valueNode]: Array) { + invariant(typeof propName === "string"); + return t.expressionStatement( + t.assignmentExpression("=", this.preludeGenerator.globalReference(propName, false), valueNode) + ); + } + + _serializeSingleArg(data: OperationDescriptorData, [o]: Array) { + return o; + } + + _serializeAbstractProperty({ propName }: OperationDescriptorData, [o]: Array) { + invariant(propName !== undefined); + return memberExpressionHelper(o, propName); + } + + _serializeUnaryExpression({ op, prefix }: OperationDescriptorData, [x, y]: Array) { + invariant(op !== undefined); + return t.unaryExpression(op, x, prefix); + } + + _serializeBinaryExpression({ op }: OperationDescriptorData, [x, y]: Array) { + invariant(op !== undefined); + return t.binaryExpression(op, x, y); + } + + _serializeLogicalExpression({ op }: OperationDescriptorData, [x, y]: Array) { + invariant(op !== undefined); + return t.logicalExpression(op, x, y); + } + + _serializeConditionalExpression(data: OperationDescriptorData, [c, x, y]: Array) { + return t.conditionalExpression(c, x, y); + } + + _serializeDerivedOperationDescriptor( + data: OperationDescriptorData, + babelNode: BabelNodeExpression + ): BabelNodeStatement { + invariant(typeof data.id === "string"); + return t.variableDeclaration("var", [t.variableDeclarator(t.identifier(data.id), babelNode)]); + } + + _serializeVoidOperationDescriptor(babelNode: BabelNodeExpression) { + return t.expressionStatement(babelNode); + } + + _serializeAbstractFromTemplate({ template }: OperationDescriptorData, nodes: Array) { + let generatorArgs = {}; + let i = 0; + for (let node of nodes) generatorArgs[labels.charAt(i++)] = node; + invariant(typeof template === "function"); + return template(this.preludeGenerator)(generatorArgs); + } + + _serializeObjectAssign(data: OperationDescriptorData, [targetNode, ...sourceNodes]: Array) { + return t.callExpression(this.preludeGenerator.memoizeReference("Object.assign"), [targetNode, ...sourceNodes]); + } +} diff --git a/src/serializer/ResidualReactElementSerializer.js b/src/serializer/ResidualReactElementSerializer.js index 2a4892e94..964f25ebc 100644 --- a/src/serializer/ResidualReactElementSerializer.js +++ b/src/serializer/ResidualReactElementSerializer.js @@ -18,7 +18,7 @@ import { AbstractValue, AbstractObjectValue, ObjectValue, SymbolValue, Value } f import { convertExpressionToJSXIdentifier, convertKeyValueToJSXAttribute } from "../react/jsx.js"; import { Logger } from "../utils/logger.js"; import invariant from "../invariant.js"; -import { FatalError } from "../errors"; +import { FatalError } from "../errors.js"; import { traverseReactElement } from "../react/elements.js"; import { canExcludeReactElementObjectProperty, getReactSymbol, getProperty } from "../react/utils.js"; import type { ReactOutputTypes } from "../options.js"; diff --git a/src/serializer/utils.js b/src/serializer/utils.js index 9aca6a173..46cc46d06 100644 --- a/src/serializer/utils.js +++ b/src/serializer/utils.js @@ -28,7 +28,7 @@ import { Logger } from "../utils/logger.js"; import { Generator } from "../utils/generator.js"; import type { AdditionalFunctionEffects } from "./types"; import type { Binding } from "../environment.js"; -import { getLocationFromValue } from "../react/utils"; +import { getLocationFromValue } from "../react/utils.js"; /** * Get index property list length by searching array properties list for the max index key value plus 1. diff --git a/src/types.js b/src/types.js index f42614278..c202dcf43 100644 --- a/src/types.js +++ b/src/types.js @@ -157,7 +157,7 @@ export type PropertyBinding = { descriptor?: Descriptor, object: ObjectValue, key: void | string | SymbolValue | AbstractValue, // where an abstract value must be of type String or Number or Symbol - // contains a build node that produces a member expression that resolves to this property binding (location) + // contains a operation descriptor that produces a member expression that resolves to this property binding (location) pathNode?: AbstractValue, internalSlot?: boolean, }; diff --git a/src/utils/HeapInspector.js b/src/utils/HeapInspector.js index 8811a18d5..b0ad75268 100644 --- a/src/utils/HeapInspector.js +++ b/src/utils/HeapInspector.js @@ -28,7 +28,7 @@ import { } from "../values/index.js"; import { To } from "../singletons.js"; import invariant from "../invariant.js"; -import { Logger } from "../utils/logger.js"; +import { Logger } from "./logger.js"; type TargetIntegrityCommand = "freeze" | "seal" | "preventExtensions" | ""; diff --git a/src/utils/generator.js b/src/utils/generator.js index 4349e742f..068036642 100644 --- a/src/utils/generator.js +++ b/src/utils/generator.js @@ -9,16 +9,21 @@ /* @flow */ -import type { Realm, Effects } from "../realm.js"; -import type { ConsoleMethodTypes, Descriptor, PropertyBinding, DisplayResult } from "../types.js"; -import type { Binding } from "../environment.js"; +import type { Effects, Realm } from "../realm.js"; +import type { + ConsoleMethodTypes, + Descriptor, + DisplayResult, + PropertyBinding, + SupportedGraphQLGetters, +} from "../types.js"; +import type { BaseValue, Binding, ReferenceName } from "../environment.js"; import { AbstractObjectValue, AbstractValue, type AbstractValueKind, BooleanValue, ConcreteValue, - EmptyValue, FunctionValue, NullValue, NumberValue, @@ -30,9 +35,7 @@ import { Value, } from "../values/index.js"; import { CompilerDiagnostic } from "../errors.js"; -import type { AbstractValueBuildNodeFunction } from "../values/AbstractValue.js"; import { TypesDomain, ValuesDomain } from "../domains/index.js"; -import * as t from "@babel/types"; import invariant from "../invariant.js"; import { AbruptCompletion, @@ -52,12 +55,159 @@ import type { BabelNodeBlockStatement, BabelNodeLVal, } from "@babel/types"; -import { nullExpression, memberExpressionHelper } from "./babelhelpers.js"; -import { Utils, concretize } from "../singletons.js"; +import { memberExpressionHelper } from "./babelhelpers.js"; +import { concretize, Utils } from "../singletons.js"; import type { SerializerOptions } from "../options.js"; +import * as t from "@babel/types"; + +export type OperationDescriptorType = + | "IDENTIFIER" + | "SINGLE_ARG" + | "REBUILT_OBJECT" + | "CONSOLE_LOG" + | "FOR_IN" + | "DO_WHILE" + | "CONCRETE_MODEL" + | "BINARY_EXPRESSION" + | "LOGICAL_EXPRESSION" + | "CONDITIONAL_EXPRESSION" + | "UNARY_EXPRESSION" + | "ABSTRACT_FROM_TEMPLATE" + | "GLOBAL_ASSIGNMENT" + | "GLOBAL_DELETE" + | "EMIT_PROPERTY_ASSIGNMENT" + | "ABSTRACT_PROPERTY" + | "JOIN_GENERATORS" + | "APPEND_GENERATOR" + | "DEFINE_PROPERTY" + | "PROPERTY_DELETE" + | "THROW" + | "CONDITIONAL_THROW" + | "COERCE_TO_STRING" + | "ABSTRACT_FROM_TEMPLATE" + | "FOR_STATEMENT_FUNC" + | "NEW_EXPRESSION" + | "OBJECT_ASSIGN" + | "OBJECT_SET_PARTIAL" + | "OBJECT_GET_PARTIAL" + | "OBJECT_PROTO_HAS_OWN_PROPERTY" + | "OBJECT_PROTO_GET_OWN_PROPERTY_DESCRIPTOR" + | "ABSTRACT_OBJECT_SET_PARTIAL" + | "ABSTRACT_OBJECT_SET_PARTIAL_VALUE" + | "ABSTRACT_OBJECT_GET_PARTIAL" + | "ABSTRACT_OBJECT_GET_PROTO_OF" + | "ABSTRACT_OBJECT_GET" + | "DIRECT_CALL_WITH_ARG_LIST" + | "CALL_ABSTRACT_FUNC" + | "CALL_ABSTRACT_FUNC_THIS" + | "CALL_BAILOUT" + | "EMIT_CALL" + | "EMIT_CALL_AND_CAPTURE_RESULT" + | "GET_BINDING" + | "LOCAL_ASSIGNMENT" + | "LOGICAL_PROPERTY_ASSIGNMENT" + | "CONDITIONAL_PROPERTY_ASSIGNMENT" + | "PROPERTY_ASSIGNMENT" + | "MODULES_REQUIRE" + | "RESIDUAL_CALL" + | "ASSUME_CALL" + | "CANNOT_BECOME_OBJECT" + | "UPDATE_INCREMENTOR" + | "WIDENED_IDENTIFIER" + | "WIDEN_PROPERTY" + | "WIDEN_ABSTRACT_PROPERTY" + | "WIDEN_PROPERTY_ASSIGNMENT" + | "WIDEN_ABSTRACT_PROPERTY_ASSIGNMENT" + | "INVARIANT" + | "INVARIANT_APPEND" + | "DERIVED_ABSTRACT_INVARIANT" + | "PROPERTY_INVARIANT" + | "FULL_INVARIANT" + | "FULL_INVARIANT_FUNCTION" + | "FULL_INVARIANT_ABSTRACT" + | "UNKNOWN_ARRAY_METHOD_CALL" + | "UNKNOWN_ARRAY_METHOD_PROPERTY_CALL" + | "UNKNOWN_ARRAY_LENGTH" + | "UNKNOWN_ARRAY_GET_PARTIAL" + | "BABEL_HELPERS_OBJECT_WITHOUT_PROPERTIES" + | "REACT_DEFAULT_PROPS_HELPER" + | "REACT_TEMPORAL_FUNC" + | "REACT_CREATE_CONTEXT_PROVIDER" + | "REACT_SSR_REGEX_CONSTANT" + | "REACT_SSR_PREV_TEXT_NODE" + | "REACT_SSR_RENDER_VALUE_HELPER" + | "REACT_SSR_TEMPLATE_LITERAL" + | "REACT_NATIVE_STRING_LITERAL" + | "REACT_RELAY_MOCK_CONTAINER" + | "FB_MOCKS_BOOTLOADER_LOAD_MODULES" + | "FB_MOCKS_MAGIC_GLOBAL_FUNCTION"; + +export type DerivedExpressionBuildNodeFunction = ( + Array, + SerializationContext, + Set +) => BabelNodeExpression; + +export type OperationDescriptor = { + data: OperationDescriptorData, + kind: void | ResidualBuildKind, + type: OperationDescriptorType, +}; + +// TODO: gradually remove all these, currently it's a random bag of values +// that should be in args or in other places rather than here. +export type OperationDescriptorData = { + appendLastToInvariantOperationDescriptor?: OperationDescriptor, + binding?: Binding | PropertyBinding, + boundName?: BabelNodeIdentifier, + callTemplate?: () => BabelNodeExpression, + concreteComparisons?: Array, + desc?: Descriptor, + generator?: Generator, + generators?: Array, + id?: string, + lh?: BabelNodeVariableDeclaration, + op?: any, // TODO: This is a union of Babel operators, refactor to not use "any" at some point + prefix?: boolean, + path?: Value, + propertyGetter?: SupportedGraphQLGetters, + propName?: string, + propRef?: ReferenceName | AbstractValue, + object?: ObjectValue, + quasis?: Array, + state?: "MISSING" | "PRESENT" | "DEFINED", + thisArg?: BaseValue | Value, + template?: PreludeGenerator => ({}) => BabelNodeExpression, + typeComparisons?: Set, + typeofString?: string, + usesThis?: boolean, + value?: Value, + violationConditionOperationDescriptor?: OperationDescriptor, +}; + +export type ResidualBuildKind = "DERIVED" | "VOID"; + +export function createOperationDescriptor( + type: OperationDescriptorType, + data?: OperationDescriptorData = {}, + kind?: ResidualBuildKind +): OperationDescriptor { + return { + data, + kind, + type, + }; +} + import type { ShapeInformationInterface } from "../types.js"; export type SerializationContext = {| + serializeOperationDescriptor: ( + OperationDescriptor, + Array, + SerializationContext, + Set + ) => BabelNodeStatement, serializeValue: Value => BabelNodeExpression, serializeBinding: Binding => BabelNodeIdentifier | BabelNodeMemberExpression, getPropertyAssignmentStatement: ( @@ -90,20 +240,6 @@ export type VisitEntryCallbacks = {| visitBindingAssignment: (Binding, Value) => Value, |}; -export type TemporalBuildNodeType = "OBJECT_ASSIGN"; - -export type DerivedExpressionBuildNodeFunction = ( - Array, - SerializationContext, - Set -) => BabelNodeExpression; - -export type GeneratorBuildNodeFunction = ( - Array, - SerializationContext, - Set -) => BabelNodeStatement; - export class GeneratorEntry { constructor(realm: Realm) { // We increment the index of every TemporalBuildNodeEntry created. @@ -141,12 +277,11 @@ export class GeneratorEntry { export type TemporalBuildNodeEntryArgs = { declared?: AbstractValue | ObjectValue, args: Array, - // If we're just trying to add roots for the serializer to notice, we don't need a buildNode. - buildNode?: GeneratorBuildNodeFunction, + // If we're just trying to add roots for the serializer to notice, we don't need an operationDescriptor. + operationDescriptor?: OperationDescriptor, dependencies?: Array, isPure?: boolean, mutatesOnly?: Array, - temporalType?: TemporalBuildNodeType, }; export class TemporalBuildNodeEntry extends GeneratorEntry { @@ -163,17 +298,16 @@ export class TemporalBuildNodeEntry extends GeneratorEntry { declared: void | AbstractValue | ObjectValue; args: Array; - // If we're just trying to add roots for the serializer to notice, we don't need a buildNode. - buildNode: void | GeneratorBuildNodeFunction; + // If we're just trying to add roots for the serializer to notice, we don't need an operationDescriptor. + operationDescriptor: void | OperationDescriptor; dependencies: void | Array; isPure: void | boolean; mutatesOnly: void | Array; - temporalType: void | TemporalBuildNodeType; toDisplayJson(depth: number): DisplayResult { if (depth <= 0) return `TemporalBuildNode${this.index}`; let obj = { type: "TemporalBuildNode", ...this }; - delete obj.buildNode; + delete obj.operationDescriptor; return Utils.verboseToDisplayJson(obj, depth); } @@ -213,9 +347,9 @@ export class TemporalBuildNodeEntry extends GeneratorEntry { } if (!omit) { let nodes = this.args.map((boundArg, i) => context.serializeValue(boundArg)); - if (this.buildNode) { + if (this.operationDescriptor !== undefined) { let valuesToProcess = new Set(); - let node = this.buildNode(nodes, context, valuesToProcess); + let node = context.serializeOperationDescriptor(this.operationDescriptor, nodes, context, valuesToProcess); if (node.type === "BlockStatement") { let block: BabelNodeBlockStatement = (node: any); let statements = block.body; @@ -476,16 +610,6 @@ class BindingAssignmentEntry extends GeneratorEntry { } } -function serializeBody( - generator: Generator, - context: SerializationContext, - valuesToProcess: Set -): BabelNodeBlockStatement { - let statements = context.serializeGenerator(generator, valuesToProcess); - if (statements.length === 1 && statements[0].type === "BlockStatement") return (statements[0]: any); - return t.blockStatement(statements); -} - export class Generator { constructor(realm: Realm, name: string, pathConditions: Array, effects?: Effects) { invariant(realm.useAbstractInterpretation); @@ -642,28 +766,21 @@ export class Generator { emitGlobalAssignment(key: string, value: Value): void { this._addEntry({ args: [value], - buildNode: ([valueNode]) => - t.expressionStatement( - t.assignmentExpression("=", this.preludeGenerator.globalReference(key, false), valueNode) - ), + operationDescriptor: createOperationDescriptor("GLOBAL_ASSIGNMENT", { propName: key }), }); } emitConcreteModel(key: string, value: Value): void { this._addEntry({ args: [concretize(this.realm, value)], - buildNode: ([valueNode]) => - t.expressionStatement( - t.assignmentExpression("=", this.preludeGenerator.globalReference(key, false), valueNode) - ), + operationDescriptor: createOperationDescriptor("CONCRETE_MODEL", { propName: key }), }); } emitGlobalDelete(key: string): void { this._addEntry({ args: [], - buildNode: ([]) => - t.expressionStatement(t.unaryExpression("delete", this.preludeGenerator.globalReference(key, false))), + operationDescriptor: createOperationDescriptor("GLOBAL_DELETE", { propName: key }), }); } @@ -675,13 +792,7 @@ export class Generator { if (object.refuseSerialization) return; this._addEntry({ args: [object, value], - buildNode: ([objectNode, valueNode], context) => - context.getPropertyAssignmentStatement( - memberExpressionHelper(objectNode, key), - value, - value.mightHaveBeenDeleted(), - /* deleteIfMightHaveBeenDeleted */ true - ), + operationDescriptor: createOperationDescriptor("EMIT_PROPERTY_ASSIGNMENT", { propName: key, value }), }); } @@ -702,7 +813,7 @@ export class Generator { desc.get || object.$Realm.intrinsics.undefined, desc.set || object.$Realm.intrinsics.undefined, ], - buildNode: (_, context: SerializationContext) => context.emitDefinePropertyBody(object, key, desc), + operationDescriptor: createOperationDescriptor("DEFINE_PROPERTY", { object, propName: key, desc }), }); } } @@ -711,59 +822,37 @@ export class Generator { if (object.refuseSerialization) return; this._addEntry({ args: [object], - buildNode: ([objectNode]) => - t.expressionStatement(t.unaryExpression("delete", memberExpressionHelper(objectNode, key))), + operationDescriptor: createOperationDescriptor("PROPERTY_DELETE", { propName: key }), }); } - emitCall(createCallee: () => BabelNodeExpression, args: Array): void { + emitCall(callTemplate: () => BabelNodeExpression, args: Array): void { this._addEntry({ args, - buildNode: values => t.expressionStatement(t.callExpression(createCallee(), [...values])), + operationDescriptor: createOperationDescriptor("EMIT_CALL", { callTemplate }), }); } emitConsoleLog(method: ConsoleMethodTypes, args: Array): void { - this.emitCall( - () => t.memberExpression(t.identifier("console"), t.identifier(method)), - args.map(v => (typeof v === "string" ? new StringValue(this.realm, v) : v)) - ); + this._addEntry({ + args: args.map(v => (typeof v === "string" ? new StringValue(this.realm, v) : v)), + operationDescriptor: createOperationDescriptor("CONSOLE_LOG", { propName: method }), + }); } // test must be a temporal value, which means that it must have a defined intrinsicName emitDoWhileStatement(test: AbstractValue, body: Generator): void { this._addEntry({ args: [], - buildNode: function([], context, valuesToProcess) { - let testId = test.intrinsicName; - invariant(testId !== undefined); - let statements = context.serializeGenerator(body, valuesToProcess); - let block = t.blockStatement(statements); - return t.doWhileStatement(t.identifier(testId), block); - }, + operationDescriptor: createOperationDescriptor("DO_WHILE", { generator: body, value: test }), dependencies: [body], }); } emitConditionalThrow(value: Value): void { - function createStatement(val: Value, context: SerializationContext) { - if (!(val instanceof AbstractValue) || val.kind !== "conditional") { - return t.throwStatement(context.serializeValue(val)); - } - let [cond, trueVal, falseVal] = val.args; - let condVal = context.serializeValue(cond); - let trueStat, falseStat; - if (trueVal instanceof EmptyValue) trueStat = t.blockStatement([]); - else trueStat = createStatement(trueVal, context); - if (falseVal instanceof EmptyValue) falseStat = t.blockStatement([]); - else falseStat = createStatement(falseVal, context); - return t.ifStatement(condVal, trueStat, falseStat); - } this._addEntry({ args: [value], - buildNode: function([argument], context: SerializationContext) { - return createStatement(value, context); - }, + operationDescriptor: createOperationDescriptor("CONDITIONAL_THROW", { value }), }); } @@ -784,7 +873,7 @@ export class Generator { emitThrow(value: Value): void { this._issueThrowCompilerDiagnostic(value); - this.emitStatement([value], ([argument]) => t.throwStatement(argument)); + this.emitStatement([value], createOperationDescriptor("THROW")); } // Checks the full set of possible concrete values as well as typeof @@ -793,8 +882,6 @@ export class Generator { // NB: if the type of the AbstractValue is top, skips the invariant emitFullInvariant(object: ObjectValue | AbstractObjectValue, key: string, value: Value): void { if (object.refuseSerialization) return; - let accessedPropertyOf = objectNode => memberExpressionHelper(objectNode, key); - let condition; if (value instanceof AbstractValue) { let isTop = false; let concreteComparisons = []; @@ -823,58 +910,27 @@ export class Generator { if (isTop) { return; } else { - condition = ([valueNode]) => { - // Create `object.property !== concreteValue` - let checks = concreteComparisons.map(concreteValue => - t.binaryExpression("!==", valueNode, t.valueToNode(concreteValue.serialize())) - ); - // Create `typeof object.property !== typeValue` - checks = checks.concat( - [...typeComparisons].map(typeValue => { - let typeString = Utils.typeToString(typeValue); - invariant(typeString !== undefined, typeValue); - return t.binaryExpression( - "!==", - t.unaryExpression("typeof", valueNode, true), - t.stringLiteral(typeString) - ); - }) - ); - return checks.reduce((expr, newCondition) => t.logicalExpression("&&", expr, newCondition)); - }; - this._emitInvariant([value, value], condition, valueNode => valueNode); + this._emitInvariant( + [value, value], + createOperationDescriptor("FULL_INVARIANT_ABSTRACT", { concreteComparisons, typeComparisons }), + createOperationDescriptor("INVARIANT_APPEND", { propName: key }) + ); } } else if (value instanceof FunctionValue) { // We do a special case for functions, // as we like to use concrete functions in the model to model abstract behaviors. // These concrete functions do not have the right identity. - condition = ([objectNode]) => - t.binaryExpression( - "!==", - t.unaryExpression("typeof", accessedPropertyOf(objectNode), true), - t.stringLiteral("function") - ); - this._emitInvariant([object, value, object], condition, objnode => accessedPropertyOf(objnode)); + this._emitInvariant( + [object, value, object], + createOperationDescriptor("FULL_INVARIANT_FUNCTION", { propName: key }), + createOperationDescriptor("INVARIANT_APPEND", { propName: key }) + ); } else { - condition = ([objectNode, valueNode]) => t.binaryExpression("!==", accessedPropertyOf(objectNode), valueNode); - this._emitInvariant([object, value, object], condition, objnode => accessedPropertyOf(objnode)); - } - } - - getErrorStatement(message: BabelNodeExpression): BabelNodeStatement { - if (this.realm.invariantMode === "throw") - return t.throwStatement(t.newExpression(this.preludeGenerator.memoizeReference("Error"), [message])); - else { - let targetReference = this.realm.invariantMode; - let args = [message]; - let i = targetReference.indexOf("+"); - if (i !== -1) { - let s = targetReference.substr(i + 1); - let x = Number.parseInt(s, 10); - args.push(isNaN(x) ? t.stringLiteral(s) : t.numericLiteral(x)); - targetReference = targetReference.substr(0, i); - } - return t.expressionStatement(t.callExpression(this.preludeGenerator.memoizeReference(targetReference), args)); + this._emitInvariant( + [object, value, object], + createOperationDescriptor("FULL_INVARIANT", { propName: key }), + createOperationDescriptor("INVARIANT_APPEND", { propName: key }) + ); } } @@ -884,72 +940,50 @@ export class Generator { state: "MISSING" | "PRESENT" | "DEFINED" ): void { if (object.refuseSerialization) return; - let accessedPropertyOf = (objectNode: BabelNodeExpression) => memberExpressionHelper(objectNode, key); - let condition = ([objectNode: BabelNodeExpression]) => { - let n = t.callExpression( - t.memberExpression( - this.preludeGenerator.memoizeReference("Object.prototype.hasOwnProperty"), - t.identifier("call") - ), - [objectNode, t.stringLiteral(key)] - ); - if (state !== "MISSING") { - n = t.unaryExpression("!", n, true); - if (state === "DEFINED") - n = t.logicalExpression( - "||", - n, - t.binaryExpression("===", accessedPropertyOf(objectNode), t.valueToNode(undefined)) - ); - } - return n; - }; - - this._emitInvariant([object, object], condition, objnode => accessedPropertyOf(objnode)); + this._emitInvariant( + [object, object], + createOperationDescriptor("PROPERTY_INVARIANT", { state, propName: key }), + createOperationDescriptor("INVARIANT_APPEND", { propName: key }) + ); } _emitInvariant( args: Array, - violationConditionFn: (Array) => BabelNodeExpression, - appendLastToInvariantFn?: BabelNodeExpression => BabelNodeExpression + violationConditionOperationDescriptor: OperationDescriptor, + appendLastToInvariantOperationDescriptor: OperationDescriptor ): void { invariant(this.realm.invariantLevel > 0); + let invariantOperationDescriptor = createOperationDescriptor("INVARIANT", { + appendLastToInvariantOperationDescriptor, + violationConditionOperationDescriptor, + }); this._addEntry({ args, - buildNode: (nodes: Array) => { - let messageComponents = [ - t.stringLiteral("Prepack model invariant violation ("), - t.numericLiteral(this.preludeGenerator.nextInvariantId++), - ]; - if (appendLastToInvariantFn) { - let last = nodes.pop(); - messageComponents.push(t.stringLiteral("): ")); - messageComponents.push(appendLastToInvariantFn(last)); - } else messageComponents.push(t.stringLiteral(")")); - let throwString = messageComponents[0]; - for (let i = 1; i < messageComponents.length; i++) - throwString = t.binaryExpression("+", throwString, messageComponents[i]); - let condition = violationConditionFn(nodes); - let consequent = this.getErrorStatement(throwString); - return t.ifStatement(condition, consequent); - }, + operationDescriptor: invariantOperationDescriptor, }); } emitCallAndCaptureResult( types: TypesDomain, values: ValuesDomain, - createCallee: () => BabelNodeExpression, + callTemplate: () => BabelNodeExpression, args: Array, kind?: AbstractValueKind ): AbstractValue { - return this.deriveAbstract(types, values, args, (nodes: any) => t.callExpression(createCallee(), nodes), { kind }); + return this.deriveAbstract( + types, + values, + args, + createOperationDescriptor("EMIT_CALL_AND_CAPTURE_RESULT", { callTemplate }), + { kind } + ); } - emitStatement(args: Array, buildNode_: (Array) => BabelNodeStatement): void { + emitStatement(args: Array, operationDescriptor: OperationDescriptor): void { + invariant(typeof operationDescriptor !== "function"); this._addEntry({ args, - buildNode: buildNode_, + operationDescriptor, }); } @@ -957,16 +991,12 @@ export class Generator { types: TypesDomain, values: ValuesDomain, args: Array, - buildNode_: AbstractValueBuildNodeFunction | BabelNodeExpression + operationDescriptor: OperationDescriptor ): UndefinedValue { + let voidOperationDescriptor = createOperationDescriptor(operationDescriptor.type, operationDescriptor.data, "VOID"); this._addEntry({ args, - buildNode: (nodes: Array) => - t.expressionStatement( - (buildNode_: any) instanceof Function - ? ((buildNode_: any): AbstractValueBuildNodeFunction)(nodes) - : ((buildNode_: any): BabelNodeExpression) - ), + operationDescriptor: voidOperationDescriptor, }); return this.realm.intrinsics.undefined; } @@ -981,49 +1011,31 @@ export class Generator { this._addEntry({ // duplicate args to ensure refcount > 1 args: [o, targetObject, sourceObject, targetObject, sourceObject], - buildNode: ([obj, tgt, src, obj1, tgt1, src1]) => { - return t.forInStatement( - lh, - obj, - t.blockStatement([ - t.expressionStatement( - t.assignmentExpression( - "=", - memberExpressionHelper(tgt, boundName), - memberExpressionHelper(src, boundName) - ) - ), - ]) - ); - }, + operationDescriptor: createOperationDescriptor("FOR_IN", { boundName, lh }), }); } deriveConcreteObject( buildValue: (intrinsicName: string) => ObjectValue, args: Array, - buildNode_: DerivedExpressionBuildNodeFunction | BabelNodeExpression, + operationDescriptor: OperationDescriptor, optionalArgs?: {| isPure?: boolean |} ): ConcreteValue { - invariant(buildNode_ instanceof Function || args.length === 0); - let id = t.identifier(this.preludeGenerator.nameGenerator.generate("derived")); - let value = buildValue(id.name); + let id = this.preludeGenerator.nameGenerator.generate("derived"); + let value = buildValue(id); value.intrinsicNameGenerated = true; value._isScopedTemplate = true; // because this object doesn't exist ahead of time, and the visitor would otherwise declare it in the common scope - this._addDerivedEntry(id.name, { + // Operation descriptors are immutable so we need create a new version to update properties + let derivedOperationDescriptor = createOperationDescriptor( + operationDescriptor.type, + Object.assign({}, operationDescriptor.data, { id }), + "DERIVED" + ); + this._addDerivedEntry(id, { isPure: optionalArgs ? optionalArgs.isPure : undefined, declared: value, args, - buildNode: (nodes: Array, context: SerializationContext, valuesToProcess) => { - return t.variableDeclaration("var", [ - t.variableDeclarator( - id, - (buildNode_: any) instanceof Function - ? ((buildNode_: any): DerivedExpressionBuildNodeFunction)(nodes, context, valuesToProcess) - : ((buildNode_: any): BabelNodeExpression) - ), - ]); - }, + operationDescriptor: derivedOperationDescriptor, }); return value; } @@ -1032,18 +1044,16 @@ export class Generator { types: TypesDomain, values: ValuesDomain, args: Array, - buildNode_: DerivedExpressionBuildNodeFunction | BabelNodeExpression, + operationDescriptor: OperationDescriptor, optionalArgs?: {| kind?: AbstractValueKind, isPure?: boolean, skipInvariant?: boolean, mutatesOnly?: Array, - temporalType?: TemporalBuildNodeType, shape?: void | ShapeInformationInterface, |} ): AbstractValue { - invariant(buildNode_ instanceof Function || args.length === 0); - let id = t.identifier(this.preludeGenerator.nameGenerator.generate("derived")); + let id = this.preludeGenerator.nameGenerator.generate("derived"); let options = {}; if (optionalArgs && optionalArgs.kind !== undefined) options.kind = optionalArgs.kind; if (optionalArgs && optionalArgs.shape !== undefined) options.shape = optionalArgs.shape; @@ -1054,28 +1064,24 @@ export class Generator { values, 1735003607742176 + this.realm.derivedIds.size, [], - id, + createOperationDescriptor("IDENTIFIER", { id }), options ); - this._addDerivedEntry(id.name, { + // Operation descriptor are immutable so we need create a new version to update properties + let derivedOperationDescriptor = createOperationDescriptor( + operationDescriptor.type, + Object.assign({}, operationDescriptor.data, { id }), + "DERIVED" + ); + this._addDerivedEntry(id, { isPure: optionalArgs ? optionalArgs.isPure : undefined, declared: res, args, - buildNode: (nodes: Array, context: SerializationContext, valuesToProcess) => { - return t.variableDeclaration("var", [ - t.variableDeclarator( - id, - (buildNode_: any) instanceof Function - ? ((buildNode_: any): DerivedExpressionBuildNodeFunction)(nodes, context, valuesToProcess) - : ((buildNode_: any): BabelNodeExpression) - ), - ]); - }, + operationDescriptor: derivedOperationDescriptor, mutatesOnly: optionalArgs ? optionalArgs.mutatesOnly : undefined, - temporalType: optionalArgs ? optionalArgs.temporalType : undefined, }); let type = types.getType(); - res.intrinsicName = id.name; + res.intrinsicName = id; if (optionalArgs && optionalArgs.skipInvariant) return res; let typeofString; if (type instanceof FunctionValue) typeofString = "function"; @@ -1092,24 +1098,8 @@ export class Generator { // should mean the model is wrong. this._emitInvariant( [res, res], - nodes => { - invariant(typeofString !== undefined); - let condition = t.binaryExpression( - "!==", - t.unaryExpression("typeof", nodes[0]), - t.stringLiteral(typeofString) - ); - if (typeofString === "object") { - condition = t.logicalExpression( - "&&", - condition, - t.binaryExpression("!==", t.unaryExpression("typeof", nodes[0]), t.stringLiteral("function")) - ); - condition = t.logicalExpression("||", condition, t.binaryExpression("===", nodes[0], nullExpression)); - } - return condition; - }, - node => node + createOperationDescriptor("DERIVED_ABSTRACT_INVARIANT", { typeofString }), + createOperationDescriptor("SINGLE_ARG") ); } @@ -1153,7 +1143,8 @@ export class Generator { _addEntry(entryArgs: TemporalBuildNodeEntryArgs): TemporalBuildNodeEntry { let entry; - if (entryArgs.temporalType === "OBJECT_ASSIGN") { + let operationDescriptor = entryArgs.operationDescriptor; + if (operationDescriptor && operationDescriptor.type === "OBJECT_ASSIGN") { entry = new TemporalObjectAssignEntry(this.realm, entryArgs); } else { entry = new TemporalBuildNodeEntry(this.realm, entryArgs); @@ -1179,20 +1170,10 @@ export class Generator { } else { this._addEntry({ args: [], - buildNode: function(args, context, valuesToProcess) { - let statements = context.serializeGenerator(other, valuesToProcess); - if (statements.length === 1) { - let statement = statements[0]; - if (leadingComment.length > 0) - statement.leadingComments = [({ type: "BlockComment", value: leadingComment }: any)]; - return statement; - } - let block = t.blockStatement(statements); - if (leadingComment.length > 0) - block.leadingComments = [({ type: "BlockComment", value: leadingComment }: any)]; - return block; - }, - dependencies: [other], + operationDescriptor: createOperationDescriptor("APPEND_GENERATOR", { + generator: other, + propName: leadingComment, + }), }); } } @@ -1200,16 +1181,11 @@ export class Generator { joinGenerators(joinCondition: AbstractValue, generator1: Generator, generator2: Generator): void { invariant(generator1 !== this && generator2 !== this && generator1 !== generator2); if (generator1.empty() && generator2.empty()) return; + let generators = [generator1, generator2]; this._addEntry({ args: [joinCondition], - buildNode: function([cond], context, valuesToProcess) { - let block1 = generator1.empty() ? null : serializeBody(generator1, context, valuesToProcess); - let block2 = generator2.empty() ? null : serializeBody(generator2, context, valuesToProcess); - if (block1) return t.ifStatement(cond, block1, block2); - invariant(block2); - return t.ifStatement(t.unaryExpression("!", cond), block2); - }, - dependencies: [generator1, generator2], + operationDescriptor: createOperationDescriptor("JOIN_GENERATORS", { generators }), + dependencies: generators, }); } } diff --git a/src/utils/modules.js b/src/utils/modules.js index d75f7b815..ae0b62186 100644 --- a/src/utils/modules.js +++ b/src/utils/modules.js @@ -39,6 +39,7 @@ import type { import invariant from "../invariant.js"; import { Logger } from "./logger.js"; import { SerializerStatistics } from "../serializer/statistics.js"; +import { createOperationDescriptor } from "./generator.js"; function downgradeErrorsToWarnings(realm: Realm, f: () => any) { let savedHandler = realm.errorHandler; @@ -243,8 +244,13 @@ export class ModuleTracer extends Tracer { ); } - result = AbstractValue.createTemporalFromBuildFunction(realm, Value, [], ([]) => - t.callExpression(t.identifier("require"), [t.valueToNode(moduleIdValue)]) + let propName = moduleIdValue + ""; + invariant(typeof propName === "string"); + result = AbstractValue.createTemporalFromBuildFunction( + realm, + Value, + [], + createOperationDescriptor("MODULES_REQUIRE", { propName }) ); } else { result = effects.result; diff --git a/src/utils/parse.js b/src/utils/parse.js index 74d1bd9ff..ff1635d60 100644 --- a/src/utils/parse.js +++ b/src/utils/parse.js @@ -15,7 +15,7 @@ import type { Realm } from "../realm.js"; import { ThrowCompletion } from "../completions.js"; import { StringValue } from "../values/index.js"; import { Construct } from "../methods/construct.js"; -import traverseFast from "../utils/traverse-fast.js"; +import traverseFast from "./traverse-fast.js"; import { parse } from "@babel/parser"; import type { BabelNodeFile } from "@babel/types"; diff --git a/src/utils/simplifier.js b/src/utils/simplifier.js index b415acae9..cf5d3b2e4 100644 --- a/src/utils/simplifier.js +++ b/src/utils/simplifier.js @@ -16,8 +16,8 @@ import invariant from "../invariant.js"; import { Realm } from "../realm.js"; import { AbstractValue, BooleanValue, ConcreteValue, Value } from "../values/index.js"; import { Path, To } from "../singletons.js"; -import EmptyValue from "../values/EmptyValue"; -import * as t from "@babel/types"; +import EmptyValue from "../values/EmptyValue.js"; +import { createOperationDescriptor } from "./generator.js"; export default function simplifyAndRefineAbstractValue( realm: Realm, @@ -128,10 +128,7 @@ function simplify(realm, value: Value, isCondition: boolean = false): Value { realm, BooleanValue, [xa], - ([n]) => { - let callFunc = t.identifier("global.__cannotBecomeObject"); - return t.callExpression(callFunc, [n]); - }, + createOperationDescriptor("CANNOT_BECOME_OBJECT"), { kind: "global.__cannotBecomeObject(A)" } ); } diff --git a/src/values/AbstractObjectValue.js b/src/values/AbstractObjectValue.js index 74d4a765b..fc2e7bcf2 100644 --- a/src/values/AbstractObjectValue.js +++ b/src/values/AbstractObjectValue.js @@ -23,8 +23,6 @@ import { StringValue, Value, } from "./index.js"; -import { protoExpression, memberExpressionHelper } from "../utils/babelhelpers.js"; -import type { AbstractValueBuildNodeFunction } from "./AbstractValue.js"; import { TypesDomain, ValuesDomain } from "../domains/index.js"; import { GetFromArrayWithWidenedNumericProperty, @@ -33,9 +31,8 @@ import { equalDescriptors, } from "../methods/index.js"; import { Havoc, Widen } from "../singletons.js"; -import type { BabelNodeExpression } from "@babel/types"; import invariant from "../invariant.js"; -import * as t from "@babel/types"; +import { createOperationDescriptor, type OperationDescriptor } from "../utils/generator.js"; export default class AbstractObjectValue extends AbstractValue { constructor( @@ -44,10 +41,10 @@ export default class AbstractObjectValue extends AbstractValue { values: ValuesDomain, hashValue: number, args: Array, - buildNode?: AbstractValueBuildNodeFunction | BabelNodeExpression, + operationDescriptor?: OperationDescriptor, optionalArgs?: {| kind?: AbstractValueKind, intrinsicName?: string, shape?: ShapeInformationInterface |} ) { - super(realm, types, values, hashValue, args, buildNode, optionalArgs); + super(realm, types, values, hashValue, args, operationDescriptor, optionalArgs); if (!values.isTop()) { for (let element of this.values.getElements()) invariant(element instanceof ObjectValue); } @@ -263,13 +260,12 @@ export default class AbstractObjectValue extends AbstractValue { } else if (this.kind === "explicit conversion to object") { let primitiveValue = this.args[0]; invariant(!Value.isTypeCompatibleWith(primitiveValue.getType(), PrimitiveValue)); - let result = AbstractValue.createFromBuildFunction(realm, ObjectValue, [primitiveValue], ([p]) => { - invariant(realm.preludeGenerator !== undefined); - let getPrototypeOf = realm.preludeGenerator.memoizeReference("Object.getPrototypeOf"); - return realm.isCompatibleWith(realm.MOBILE_JSC_VERSION) || realm.isCompatibleWith("mobile") - ? t.memberExpression(p, protoExpression) - : t.callExpression(getPrototypeOf, [p]); - }); + let result = AbstractValue.createFromBuildFunction( + realm, + ObjectValue, + [primitiveValue], + createOperationDescriptor("ABSTRACT_OBJECT_GET_PROTO_OF") + ); invariant(result instanceof AbstractObjectValue); return result; } else { @@ -523,15 +519,7 @@ export default class AbstractObjectValue extends AbstractValue { this.$Realm, type, [ob], - ([o]) => { - invariant(typeof P === "string"); - return propertyGetter !== undefined - ? t.callExpression(t.memberExpression(t.identifier("global"), t.identifier("__prop_" + propertyGetter)), [ - o, - t.stringLiteral(P), - ]) - : memberExpressionHelper(o, P); - }, + createOperationDescriptor("ABSTRACT_OBJECT_GET", { propertyGetter, propName: P }), { skipInvariant: true, isPure: true, @@ -635,7 +623,7 @@ export default class AbstractObjectValue extends AbstractValue { this.$Realm, Value, [this, P], - ([o, p]) => memberExpressionHelper(o, p), + createOperationDescriptor("ABSTRACT_OBJECT_GET_PARTIAL"), { skipInvariant: true, isPure: true } ); } @@ -655,7 +643,7 @@ export default class AbstractObjectValue extends AbstractValue { this.$Realm, Value, [Receiver, P], - ([o, p]) => memberExpressionHelper(o, p), + createOperationDescriptor("ABSTRACT_OBJECT_GET_PARTIAL"), { skipInvariant: true, isPure: true } ); } @@ -782,20 +770,14 @@ export default class AbstractObjectValue extends AbstractValue { P = P.value; } if (typeof P === "string") { - generator.emitStatement([Receiver, V], ([objectNode, valueNode]) => { - invariant(typeof P === "string"); - return t.expressionStatement( - t.assignmentExpression("=", memberExpressionHelper(objectNode, P), valueNode) - ); - }); + generator.emitStatement( + [Receiver, V], + createOperationDescriptor("ABSTRACT_OBJECT_SET_PARTIAL", { propName: P }) + ); } else { // Coercion can only have effects on anything reachable from the key. Havoc.value(this.$Realm, P); - generator.emitStatement([Receiver, P, V], ([objectNode, keyNode, valueNode]) => - t.expressionStatement( - t.assignmentExpression("=", memberExpressionHelper(objectNode, keyNode), valueNode) - ) - ); + generator.emitStatement([Receiver, P, V], createOperationDescriptor("ABSTRACT_OBJECT_SET_PARTIAL_VALUE")); } return this.$Realm.intrinsics.undefined; }, diff --git a/src/values/AbstractValue.js b/src/values/AbstractValue.js index f8b25a9e5..a799b5b91 100644 --- a/src/values/AbstractValue.js +++ b/src/values/AbstractValue.js @@ -12,14 +12,13 @@ import type { BabelBinaryOperator, BabelNodeExpression, - BabelNodeIdentifier, BabelNodeLogicalOperator, BabelNodeSourceLocation, BabelUnaryOperator, } from "@babel/types"; import { CompilerDiagnostic, FatalError } from "../errors.js"; import type { Realm } from "../realm.js"; -import { PreludeGenerator, type TemporalBuildNodeType } from "../utils/generator.js"; +import { createOperationDescriptor, PreludeGenerator, type OperationDescriptor } from "../utils/generator.js"; import type { PropertyKeyValue, ShapeInformationInterface } from "../types.js"; import buildExpressionTemplate from "../utils/builder.js"; @@ -40,10 +39,6 @@ import { hashString, hashBinary, hashCall, hashTernary, hashUnary } from "../met import { TypesDomain, ValuesDomain } from "../domains/index.js"; import invariant from "../invariant.js"; -import * as t from "@babel/types"; - -export type AbstractValueBuildNodeFunction = (Array) => BabelNodeExpression; - // In addition to the explicitly listed kinds, // all strings that start with `AbstractValueKindPrefix` are also legal kinds. export type AbstractValueKind = @@ -100,7 +95,7 @@ export default class AbstractValue extends Value { values: ValuesDomain, hashValue: number, args: Array, - buildNode?: AbstractValueBuildNodeFunction | BabelNodeExpression, + operationDescriptor?: OperationDescriptor, optionalArgs?: {| kind?: AbstractValueKind, intrinsicName?: string, shape?: ShapeInformationInterface |} ) { invariant(realm.useAbstractInterpretation); @@ -110,7 +105,7 @@ export default class AbstractValue extends Value { this.types = types; this.values = values; this.mightBeEmpty = false; - this._buildNode = buildNode; + this.operationDescriptor = operationDescriptor; this.args = args; this.hashValue = hashValue; this.kind = optionalArgs ? optionalArgs.kind : undefined; @@ -124,7 +119,7 @@ export default class AbstractValue extends Value { mightBeEmpty: boolean; args: Array; shape: void | ShapeInformationInterface; - _buildNode: void | AbstractValueBuildNodeFunction | BabelNodeExpression; + operationDescriptor: void | OperationDescriptor; toDisplayString(): string { return "[Abstract " + this.hashValue.toString() + "]"; @@ -133,9 +128,10 @@ export default class AbstractValue extends Value { addSourceLocationsTo(locations: Array, seenValues?: Set = new Set()): void { if (seenValues.has(this)) return; seenValues.add(this); - if (this._buildNode && !(this._buildNode instanceof Function)) { - if (this._buildNode.loc) locations.push(this._buildNode.loc); - } + // TODO: make this work again? + // if (this._buildNode && !(this._buildNode instanceof Function)) { + // if (this._buildNode.loc) locations.push(this._buildNode.loc); + // } for (let val of this.args) { if (val instanceof AbstractValue) val.addSourceLocationsTo(locations, seenValues); } @@ -174,13 +170,6 @@ export default class AbstractValue extends Value { add_args(this.args); } - buildNode(args: Array): BabelNodeExpression { - let buildNode = this.getBuildNode(); - return buildNode instanceof Function - ? ((buildNode: any): AbstractValueBuildNodeFunction)(args) - : ((buildNode: any): BabelNodeExpression); - } - equals(x: Value): boolean { if (x instanceof ConcreteValue) return false; let thisArgs = this.args; @@ -209,11 +198,6 @@ export default class AbstractValue extends Value { ); } - getBuildNode(): AbstractValueBuildNodeFunction | BabelNodeExpression { - invariant(this._buildNode); - return this._buildNode; - } - getHash(): number { return this.hashValue; } @@ -222,13 +206,16 @@ export default class AbstractValue extends Value { return this.types.getType(); } - getIdentifier(): BabelNodeIdentifier { + getIdentifier(): string { invariant(this.hasIdentifier()); - return ((this._buildNode: any): BabelNodeIdentifier); + invariant(this.operationDescriptor !== undefined); + let { id } = this.operationDescriptor.data; + invariant(id !== undefined); + return id; } hasIdentifier(): boolean { - return this._buildNode ? this._buildNode.type === "Identifier" : false; + return this.operationDescriptor ? this.operationDescriptor.type === "IDENTIFIER" : false; } _checkAbstractValueImpliesCounter(): void { @@ -608,9 +595,8 @@ export default class AbstractValue extends Value { ? ValuesDomain.topVal : ValuesDomain.binaryOp(realm, op, leftValues, rightValues); let [hash, args] = kind === undefined ? hashBinary(op, left, right) : hashCall(kind, left, right); - let result = new AbstractValue(realm, resultTypes, resultValues, hash, args, ([x, y]) => - t.binaryExpression(op, x, y) - ); + let operationDescriptor = createOperationDescriptor("BINARY_EXPRESSION", { op }); + let result = new AbstractValue(realm, resultTypes, resultValues, hash, args, operationDescriptor); result.kind = kind || op; result.expressionLocation = loc; if (doNotSimplify) return result; @@ -654,9 +640,8 @@ export default class AbstractValue extends Value { let Constructor = Value.isTypeCompatibleWith(resultTypes.getType(), ObjectValue) ? AbstractObjectValue : AbstractValue; - let result = new Constructor(realm, resultTypes, resultValues, hash, args, ([x, y]) => - t.logicalExpression(op, x, y) - ); + let operationDescriptor = createOperationDescriptor("LOGICAL_EXPRESSION", { op }); + let result = new Constructor(realm, resultTypes, resultValues, hash, args, operationDescriptor); result.kind = op; result.expressionLocation = loc; if (doNotSimplify) return result; @@ -686,9 +671,8 @@ export default class AbstractValue extends Value { let values = ValuesDomain.joinValues(realm, left, right); let [hash, args] = hashTernary(condition, left || realm.intrinsics.undefined, right || realm.intrinsics.undefined); let Constructor = Value.isTypeCompatibleWith(types.getType(), ObjectValue) ? AbstractObjectValue : AbstractValue; - let result = new Constructor(realm, types, values, hash, args, ([c, x, y]) => t.conditionalExpression(c, x, y), { - kind: "conditional", - }); + let operationDescriptor = createOperationDescriptor("CONDITIONAL_EXPRESSION"); + let result = new Constructor(realm, types, values, hash, args, operationDescriptor, { kind: "conditional" }); result.expressionLocation = loc; if (left) result.mightBeEmpty = left.mightHaveBeenDeleted(); if (right && !result.mightBeEmpty) result.mightBeEmpty = right.mightHaveBeenDeleted(); @@ -710,8 +694,14 @@ export default class AbstractValue extends Value { invariant(op !== "delete" && op !== "++" && op !== "--"); // The operation must be pure let resultTypes = TypesDomain.unaryOp(op, new TypesDomain(operand.getType())); let resultValues = ValuesDomain.unaryOp(realm, op, operand.values); - let result = new AbstractValue(realm, resultTypes, resultValues, hashUnary(op, operand), [operand], ([x]) => - t.unaryExpression(op, x, prefix) + let operationDescriptor = createOperationDescriptor("UNARY_EXPRESSION", { op, prefix }); + let result = new AbstractValue( + realm, + resultTypes, + resultValues, + hashUnary(op, operand), + [operand], + operationDescriptor ); result.kind = op; result.expressionLocation = loc; @@ -741,13 +731,8 @@ export default class AbstractValue extends Value { let Constructor = Value.isTypeCompatibleWith(resultType, ObjectValue) ? AbstractObjectValue : AbstractValue; let labels = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; invariant(labels.length >= operands.length); - let result = new Constructor(realm, resultTypes, resultValues, hash, operands, args => { - invariant(realm.preludeGenerator !== undefined); - let generatorArgs = {}; - let i = 0; - for (let arg of args) generatorArgs[labels.charAt(i++)] = arg; - return template(realm.preludeGenerator)(generatorArgs); - }); + let operationDescriptor = createOperationDescriptor("ABSTRACT_FROM_TEMPLATE", { template }); + let result = new Constructor(realm, resultTypes, resultValues, hash, operands, operationDescriptor); result.kind = kind; result.expressionLocation = loc || realm.currentLocation; return result; @@ -785,7 +770,6 @@ export default class AbstractValue extends Value { isPure?: boolean, skipInvariant?: boolean, mutatesOnly?: Array, - temporalType?: TemporalBuildNodeType, shape?: ShapeInformationInterface, |} ): AbstractValue { @@ -794,16 +778,16 @@ export default class AbstractValue extends Value { let types = temp.types; let values = temp.values; let args = temp.args; - let buildNode_ = temp.getBuildNode(); invariant(realm.generator !== undefined); - return realm.generator.deriveAbstract(types, values, args, buildNode_, optionalArgs); + invariant(temp.operationDescriptor !== undefined); + return realm.generator.deriveAbstract(types, values, args, temp.operationDescriptor, optionalArgs); } static createFromBuildFunction( realm: Realm, resultType: typeof Value, args: Array, - buildFunction: AbstractValueBuildNodeFunction, + operationDescriptor: OperationDescriptor, optionalArgs?: {| kind?: AbstractValueKind |} ): AbstractValue | UndefinedValue { let types = new TypesDomain(resultType); @@ -812,7 +796,7 @@ export default class AbstractValue extends Value { let kind = (optionalArgs && optionalArgs.kind) || "build function"; let hash; [hash, args] = hashCall(kind, ...args); - let result = new Constructor(realm, types, values, hash, args, buildFunction); + let result = new Constructor(realm, types, values, hash, args, operationDescriptor); result.kind = kind; return result; } @@ -821,13 +805,12 @@ export default class AbstractValue extends Value { realm: Realm, resultType: typeof Value, args: Array, - buildFunction: AbstractValueBuildNodeFunction, + operationDescriptor: OperationDescriptor, optionalArgs?: {| kind?: AbstractValueKind, isPure?: boolean, skipInvariant?: boolean, mutatesOnly?: Array, - temporalType?: TemporalBuildNodeType, shape?: void | ShapeInformationInterface, |} ): AbstractValue | UndefinedValue { @@ -835,14 +818,14 @@ export default class AbstractValue extends Value { let values = ValuesDomain.topVal; invariant(realm.generator !== undefined); if (resultType === UndefinedValue) { - return realm.generator.emitVoidExpression(types, values, args, buildFunction); + return realm.generator.emitVoidExpression(types, values, args, operationDescriptor); } else { - return realm.generator.deriveAbstract(types, values, args, buildFunction, optionalArgs); + return realm.generator.deriveAbstract(types, values, args, operationDescriptor, optionalArgs); } } // Creates a union of an abstract value with one or more concrete values. - // The build node for the abstract values becomes the build node for the union. + // The operation descriptor for the abstract values becomes the operation descriptor for the union. // Use this only to allow instrinsic abstract objects to be null and/or undefined. static createAbstractConcreteUnion(realm: Realm, ...elements: Array): AbstractValue { let concreteValues: Array = (elements.filter(e => e instanceof ConcreteValue): any); @@ -859,7 +842,7 @@ export default class AbstractValue extends Value { } let types = TypesDomain.topVal; let [hash, operands] = hashCall("abstractConcreteUnion", abstractValue, ...concreteValues); - let result = new AbstractValue(realm, types, values, hash, operands, nodes => nodes[0], { + let result = new AbstractValue(realm, types, values, hash, operands, createOperationDescriptor("SINGLE_ARG"), { kind: "abstractConcreteUnion", }); result.expressionLocation = realm.currentLocation; @@ -870,13 +853,13 @@ export default class AbstractValue extends Value { realm: Realm, resultTemplate: AbstractValue, args: Array, - buildFunction: AbstractValueBuildNodeFunction + operationDescriptor: OperationDescriptor ): AbstractValue { let types = resultTemplate.types; let values = resultTemplate.values; let [hash] = hashCall("widened property", ...args); let Constructor = Value.isTypeCompatibleWith(types.getType(), ObjectValue) ? AbstractObjectValue : AbstractValue; - let result = new Constructor(realm, types, values, hash, args, buildFunction); + let result = new Constructor(realm, types, values, hash, args, operationDescriptor); result.kind = "widened property"; result.mightBeEmpty = resultTemplate.mightBeEmpty; result.expressionLocation = resultTemplate.expressionLocation; @@ -909,11 +892,11 @@ export default class AbstractValue extends Value { let realmPreludeGenerator = realm.preludeGenerator; invariant(realmPreludeGenerator); - let id = t.identifier(name); let types = new TypesDomain(type); let values = ValuesDomain.topVal; let Constructor = Value.isTypeCompatibleWith(type, ObjectValue) ? AbstractObjectValue : AbstractValue; - let result = new Constructor(realm, types, values, 943586754858 + hashString(name), [], id); + let operationDescriptor = createOperationDescriptor("IDENTIFIER", { id: name }); + let result = new Constructor(realm, types, values, 943586754858 + hashString(name), [], operationDescriptor); result.kind = AbstractValue.makeKind("abstractCounted", (realm.objectCount++).toString()); // need not be an object, but must be unique result.expressionLocation = location; result.shape = shape; @@ -999,14 +982,8 @@ export default class AbstractValue extends Value { realm, ObjectValue, temporalArgs, - ([targetNode, ...sourceNodes]: Array) => { - return t.callExpression(preludeGenerator.memoizeReference("Object.assign"), [targetNode, ...sourceNodes]); - }, - { - skipInvariant: true, - mutatesOnly: [to], - temporalType: "OBJECT_ASSIGN", - } + createOperationDescriptor("OBJECT_ASSIGN"), + { skipInvariant: true, mutatesOnly: [to] } ); invariant(temporalTo instanceof AbstractObjectValue); if (to instanceof AbstractObjectValue) { diff --git a/src/values/ArgumentsExotic.js b/src/values/ArgumentsExotic.js index 3d9eee0e5..49c4b44de 100644 --- a/src/values/ArgumentsExotic.js +++ b/src/values/ArgumentsExotic.js @@ -11,13 +11,13 @@ import type { Realm } from "../realm.js"; import type { PropertyKeyValue, Descriptor } from "../types.js"; -import { ObjectValue, Value } from "../values/index.js"; +import { ObjectValue, Value } from "./index.js"; import { IsDataDescriptor, IsAccessorDescriptor } from "../methods/is.js"; import { HasOwnProperty } from "../methods/has.js"; import { SameValuePartial } from "../methods/abstract.js"; import { Get, OrdinaryGet } from "../methods/get.js"; import { Properties } from "../singletons.js"; -import invariant from "../invariant"; +import invariant from "../invariant.js"; export default class ArgumentsExotic extends ObjectValue { constructor(realm: Realm, intrinsicName?: string) { diff --git a/src/values/ArrayValue.js b/src/values/ArrayValue.js index 06bba9761..e450a01cf 100644 --- a/src/values/ArrayValue.js +++ b/src/values/ArrayValue.js @@ -14,8 +14,8 @@ import type { PropertyKeyValue, Descriptor, ObjectKind } from "../types.js"; import { AbstractValue, ObjectValue, StringValue, NumberValue, Value } from "./index.js"; import { IsAccessorDescriptor, IsPropertyKey, IsArrayIndex } from "../methods/is.js"; import { Properties, To } from "../singletons.js"; +import { type OperationDescriptor } from "../utils/generator.js"; import invariant from "../invariant.js"; -import type { BabelNodeExpression } from "@babel/types"; export default class ArrayValue extends ObjectValue { constructor(realm: Realm, intrinsicName?: string) { @@ -97,7 +97,7 @@ export default class ArrayValue extends ObjectValue { static createTemporalWithWidenedNumericProperty( realm: Realm, args: Array, - buildFunction: (Array) => BabelNodeExpression, + operationDescriptor: OperationDescriptor, reactArrayHint?: { func: Value, thisVal: Value } ): ArrayValue { invariant(realm.generator !== undefined); @@ -105,7 +105,7 @@ export default class ArrayValue extends ObjectValue { let value = realm.generator.deriveConcreteObject( intrinsicName => new ArrayValue(realm, intrinsicName), args, - buildFunction, + operationDescriptor, { isPure: true } ); diff --git a/src/values/ECMAScriptSourceFunctionValue.js b/src/values/ECMAScriptSourceFunctionValue.js index b7ccae523..94a99b5ad 100644 --- a/src/values/ECMAScriptSourceFunctionValue.js +++ b/src/values/ECMAScriptSourceFunctionValue.js @@ -14,7 +14,7 @@ import type { BabelNodeBlockStatement, BabelNodeSourceLocation, BabelNodeLVal } import type { FunctionBodyAstNode } from "../types.js"; import { ECMAScriptFunctionValue } from "./index.js"; import * as t from "@babel/types"; -import invariant from "../invariant"; +import invariant from "../invariant.js"; /* Non built-in ECMAScript function objects with source code */ export default class ECMAScriptSourceFunctionValue extends ECMAScriptFunctionValue { diff --git a/src/values/IntegerIndexedExotic.js b/src/values/IntegerIndexedExotic.js index 5bdf19d1f..cb67b2b8c 100644 --- a/src/values/IntegerIndexedExotic.js +++ b/src/values/IntegerIndexedExotic.js @@ -11,13 +11,13 @@ import type { Realm } from "../realm.js"; import type { PropertyKeyValue, Descriptor } from "../types.js"; -import { ObjectValue, NumberValue, StringValue, Value, UndefinedValue } from "../values/index.js"; +import { ObjectValue, NumberValue, StringValue, Value, UndefinedValue } from "./index.js"; import { IsInteger, IsArrayIndex, IsAccessorDescriptor, IsDetachedBuffer, IsPropertyKey } from "../methods/is.js"; import { OrdinaryGet } from "../methods/get.js"; import { OrdinaryHasProperty } from "../methods/has.js"; import { IntegerIndexedElementSet, IntegerIndexedElementGet } from "../methods/typedarray.js"; import { Properties, To } from "../singletons.js"; -import invariant from "../invariant"; +import invariant from "../invariant.js"; export default class IntegerIndexedExotic extends ObjectValue { constructor(realm: Realm, intrinsicName?: string) { diff --git a/src/values/ObjectValue.js b/src/values/ObjectValue.js index 6ac567983..5790b18d9 100644 --- a/src/values/ObjectValue.js +++ b/src/values/ObjectValue.js @@ -54,8 +54,7 @@ import { import { Havoc, Properties, To } from "../singletons.js"; import invariant from "../invariant.js"; import type { typeAnnotation } from "@babel/types"; -import * as t from "@babel/types"; -import { memberExpressionHelper } from "../utils/babelhelpers.js"; +import { createOperationDescriptor } from "../utils/generator.js"; function isWidenedValue(v: void | Value) { if (!(v instanceof AbstractValue)) return false; @@ -572,10 +571,14 @@ export default class ObjectValue extends ConcreteValue { if (realm.react.enabled && realm.react.reactProps.has(this)) { realm.react.reactProps.add(template); } - let result = AbstractValue.createTemporalFromBuildFunction(this.$Realm, ObjectValue, [template], ([x]) => x, { - skipInvariant: true, - isPure: true, - }); + let operationDescriptor = createOperationDescriptor("SINGLE_ARG"); + let result = AbstractValue.createTemporalFromBuildFunction( + this.$Realm, + ObjectValue, + [template], + operationDescriptor, + { skipInvariant: true, isPure: true } + ); invariant(result instanceof AbstractObjectValue); result.values = new ValuesDomain(template); return result; @@ -770,7 +773,7 @@ export default class ObjectValue extends ConcreteValue { this.$Realm, Value, [Receiver, P], - ([o, p]) => memberExpressionHelper(o, p), + createOperationDescriptor("OBJECT_GET_PARTIAL"), { skipInvariant: true, isPure: true } ); } else { @@ -798,7 +801,7 @@ export default class ObjectValue extends ConcreteValue { this.$Realm, Value, [this, P], - ([o, p]) => memberExpressionHelper(o, p), + createOperationDescriptor("OBJECT_GET_PARTIAL"), { skipInvariant: true, isPure: true } ); } @@ -808,7 +811,7 @@ export default class ObjectValue extends ConcreteValue { this.$Realm, Value, [this, P], - ([o, p]) => memberExpressionHelper(o, p), + createOperationDescriptor("OBJECT_GET_PARTIAL"), { skipInvariant: true, isPure: true } ); } @@ -861,7 +864,7 @@ export default class ObjectValue extends ConcreteValue { this.$Realm, absVal.getType(), [ob, propName], - ([o, p]) => memberExpressionHelper(o, p), + createOperationDescriptor("OBJECT_GET_PARTIAL"), { skipInvariant: true, isPure: true } ); } @@ -879,7 +882,7 @@ export default class ObjectValue extends ConcreteValue { this.$Realm, absVal.getType(), [ob, propName], - ([o, p]) => memberExpressionHelper(o, p), + createOperationDescriptor("OBJECT_GET_PARTIAL"), { skipInvariant: true, isPure: true } ); } else if (arg2.args.length === 3) { @@ -941,9 +944,7 @@ export default class ObjectValue extends ConcreteValue { let generator = this.$Realm.generator; invariant(generator); invariant(P instanceof AbstractValue); - generator.emitStatement([Receiver, P, V], ([objectNode, keyNode, valueNode]) => - t.expressionStatement(t.assignmentExpression("=", memberExpressionHelper(objectNode, keyNode), valueNode)) - ); + generator.emitStatement([Receiver, P, V], createOperationDescriptor("OBJECT_SET_PARTIAL")); return this.$Realm.intrinsics.undefined; }, TypesDomain.topVal, diff --git a/src/values/StringExotic.js b/src/values/StringExotic.js index aa1516b05..eedf6921e 100644 --- a/src/values/StringExotic.js +++ b/src/values/StringExotic.js @@ -11,10 +11,10 @@ import type { Realm } from "../realm.js"; import type { PropertyKeyValue, Descriptor } from "../types.js"; -import { ObjectValue, NumberValue, StringValue } from "../values/index.js"; +import { ObjectValue, NumberValue, StringValue } from "./index.js"; import { IsInteger, IsArrayIndex } from "../methods/is.js"; import { Properties, To } from "../singletons.js"; -import invariant from "../invariant"; +import invariant from "../invariant.js"; export default class StringExotic extends ObjectValue { constructor(realm: Realm, intrinsicName?: string) { diff --git a/src/values/StringValue.js b/src/values/StringValue.js index f1a4f3508..cb47a11a8 100644 --- a/src/values/StringValue.js +++ b/src/values/StringValue.js @@ -10,7 +10,7 @@ /* @flow strict-local */ import { hashString } from "../methods/index.js"; -import { PrimitiveValue, Value } from "../values/index.js"; +import { PrimitiveValue, Value } from "./index.js"; import type { Realm } from "../realm.js"; export default class StringValue extends PrimitiveValue { diff --git a/src/values/index.js b/src/values/index.js index 770374727..61cc2332e 100644 --- a/src/values/index.js +++ b/src/values/index.js @@ -37,6 +37,6 @@ export { default as BooleanValue } from "./BooleanValue.js"; export { default as StringValue } from "./StringValue.js"; export { default as SymbolValue } from "./SymbolValue.js"; -export { default as AbstractValue, AbstractValueBuildNodeFunction } from "./AbstractValue.js"; +export { default as AbstractValue } from "./AbstractValue.js"; export type { AbstractValueKind } from "./AbstractValue.js"; export { default as AbstractObjectValue } from "./AbstractObjectValue.js";