diff --git a/.circleci/config.yml b/.circleci/config.yml index 56a167513e..dba5acc72b 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -59,7 +59,7 @@ jobs: yarn test-react yarn test-sourcemaps yarn test-std-in - yarn test-test262 --expectedCounts 11944,5641,0 --statusFile ~/artifacts/test262-status.txt --timeout 120 --cpuScale 0.25 --verbose + yarn test-test262 --expectedCounts 11944,5641,0 --statusFile ~/artifacts/test262-status.txt --timeout 140 --cpuScale 0.25 --verbose #yarn test-test262-new --statusFile ~/artifacts/test262-new-status.txt --timeout 120 --verbose - store_artifacts: path: ~/artifacts/ diff --git a/scripts/test-runner.js b/scripts/test-runner.js index 1627ed6f25..a6bc66c44e 100644 --- a/scripts/test-runner.js +++ b/scripts/test-runner.js @@ -47,6 +47,7 @@ let execSpec; let JSONTokenizer = require("../lib/utils/JSONTokenizer.js").default; let { Linter } = require("eslint"); let lintConfig = require("./lint-config"); +let TextPrinter = require("../lib/utils/TextPrinter.js").TextPrinter; function transformWithBabel(code, plugins, presets) { return babel.transform(code, { @@ -431,6 +432,7 @@ function runTest(name, code, options: PrepackOptions, args) { return Promise.resolve(false); } else { let codeIterations = []; + let irIterations = []; let markersToFind = []; for (let [positive, marker] of [[true, "// does contain:"], [false, "// does not contain:"]]) { for (let i = code.indexOf(marker); i >= 0; i = code.indexOf(marker, i + 1)) { @@ -507,6 +509,14 @@ function runTest(name, code, options: PrepackOptions, args) { options.errorHandler = getErrorHandlerWithWarningCapture(diagnosticOutput, args.verbose); } + let ir = ""; + if (args.ir) + options.onExecute = (realm, optimizedFunctions) => { + new TextPrinter(line => { + ir += line + "\n"; + }).print(realm, optimizedFunctions); + }; + let serialized = prepackSources([{ filePath: name, fileContents: code, sourceMapContents: "" }], options); if (serialized.statistics && serialized.statistics.delayedValues > 0) anyDelayedValues = true; if (!serialized) { @@ -583,7 +593,11 @@ function runTest(name, code, options: PrepackOptions, args) { if (!execSpec && options.lazyObjectsRuntime !== undefined) { codeToRun = augmentCodeWithLazyObjectSupport(codeToRun, args.lazyObjectsRuntime); } - if (args.verbose) console.log(codeToRun); + if (args.verbose) { + if (args.ir) console.log(`=== ir\n${ir}\n`); + console.log(`=== generated code\n${codeToRun}\n`); + } + irIterations.push(unescapeUniqueSuffix(ir, options.uniqueSuffix)); codeIterations.push(unescapeUniqueSuffix(codeToRun, options.uniqueSuffix)); if (args.es5) { codeToRun = transformWithBabel( @@ -660,6 +674,10 @@ function runTest(name, code, options: PrepackOptions, args) { console.error(chalk.underline("output of inspect() on original code")); console.error(expected); for (let ii = 0; ii < codeIterations.length; ii++) { + if (args.ir) { + console.error(chalk.underline(`ir in iteration ${ii}`)); + console.error(irIterations[ii]); + } console.error(chalk.underline(`generated code in iteration ${ii}`)); console.error(codeIterations[ii]); } @@ -829,6 +847,7 @@ class ProgramArgs { noLazySupport: boolean; fast: boolean; cpuprofilePath: string; + ir: boolean; constructor( debugNames: boolean, debugScopes: boolean, @@ -839,7 +858,8 @@ class ProgramArgs { lazyObjectsRuntime: string, noLazySupport: boolean, fast: boolean, - cpuProfilePath: string + cpuProfilePath: string, + ir: boolean ) { this.debugNames = debugNames; this.debugScopes = debugScopes; @@ -851,6 +871,7 @@ class ProgramArgs { this.noLazySupport = noLazySupport; this.fast = fast; this.cpuprofilePath = cpuProfilePath; + this.ir = ir; } } @@ -885,7 +906,7 @@ function usage(): string { return ( `Usage: ${process.argv[0]} ${process.argv[1]} ` + EOL + - `[--debugNames] [--debugScopes] [--es5] [--fast] [--noLazySupport] [--verbose] [--filter ] [--outOfProcessRuntime ] ` + `[--debugNames] [--debugScopes] [--es5] [--fast] [--noLazySupport] [--verbose] [--ir] [--filter ] [--outOfProcessRuntime ] ` ); } @@ -913,6 +934,7 @@ function argsParse(): ProgramArgs { // to run tests. If not a separate node context used. lazyObjectsRuntime: LAZY_OBJECTS_RUNTIME_NAME, noLazySupport: false, + ir: false, fast: false, cpuprofilePath: "", }, @@ -949,6 +971,9 @@ function argsParse(): ProgramArgs { if (typeof parsedArgs.cpuprofilePath !== "string") { throw new ArgsParseError("cpuprofilePath must be a string"); } + if (typeof parsedArgs.ir !== "boolean") { + throw new ArgsParseError("ir must be a string"); + } let programArgs = new ProgramArgs( parsedArgs.debugNames, parsedArgs.debugScopes, @@ -959,7 +984,8 @@ function argsParse(): ProgramArgs { parsedArgs.lazyObjectsRuntime, parsedArgs.noLazySupport, parsedArgs.fast, - parsedArgs.cpuprofilePath + parsedArgs.cpuprofilePath, + parsedArgs.ir ); return programArgs; } diff --git a/src/descriptors.js b/src/descriptors.js new file mode 100644 index 0000000000..0e6d769af0 --- /dev/null +++ b/src/descriptors.js @@ -0,0 +1,136 @@ +/** + * 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 invariant from "./invariant.js"; +import type { AbstractValue, UndefinedValue, Value } from "./values/index.js"; +import { CompilerDiagnostic, FatalError } from "./errors.js"; +import type { CallableObjectValue } from "./types.js"; +import type { Realm } from "./realm.js"; + +export class Descriptor { + constructor() { + invariant(this.constructor !== Descriptor, "Descriptor is an abstract base class"); + } + throwIfNotConcrete(realm: Realm): PropertyDescriptor { + let error = new CompilerDiagnostic( + "only known descriptors supported", + realm.currentLocation, + "PP0042", + "FatalError" + ); + realm.handleError(error); + throw new FatalError(); + } + mightHaveBeenDeleted(): boolean { + invariant(false, "should have been overridden by subclass"); + } +} + +export type DescriptorInitializer = {| + writable?: boolean, + enumerable?: boolean, + configurable?: boolean, + + value?: Value, + + get?: UndefinedValue | CallableObjectValue | AbstractValue, + set?: UndefinedValue | CallableObjectValue | AbstractValue, +|}; + +// Normal descriptors are returned just like spec descriptors +export class PropertyDescriptor extends Descriptor { + writable: void | boolean; + enumerable: void | boolean; + configurable: void | boolean; + + // If value instanceof EmptyValue, then this descriptor indicates that the + // corresponding property has been deleted. + value: void | Value; + + get: void | UndefinedValue | CallableObjectValue | AbstractValue; + set: void | UndefinedValue | CallableObjectValue | AbstractValue; + + constructor(desc: DescriptorInitializer | PropertyDescriptor) { + super(); + this.writable = desc.writable; + this.enumerable = desc.enumerable; + this.configurable = desc.configurable; + this.value = desc.value; + this.get = desc.get; + this.set = desc.set; + } + + throwIfNotConcrete(realm: Realm): PropertyDescriptor { + return this; + } + mightHaveBeenDeleted(): boolean { + if (this.value === undefined) return false; + return this.value.mightHaveBeenDeleted(); + } +} + +// Only internal properties (those starting with $ / where internalSlot of owning property binding is true) will ever have array values. +export class InternalSlotDescriptor extends Descriptor { + value: void | Value | Array; + + constructor(value?: void | Value | Array) { + super(); + this.value = Array.isArray(value) ? value.slice(0) : value; + } + + mightHaveBeenDeleted(): boolean { + return false; + } +} + +// Only used if the result of a join of two descriptors is not a data descriptor with identical attribute values. +// When present, any update to the property must produce effects that are the join of updating both descriptors, +// using joinCondition as the condition of the join. +export class AbstractJoinedDescriptor extends Descriptor { + joinCondition: AbstractValue; + // An undefined descriptor means it might be empty in this branch. + descriptor1: void | Descriptor; + descriptor2: void | Descriptor; + + constructor(joinCondition: AbstractValue, descriptor1?: Descriptor, descriptor2?: Descriptor) { + super(); + this.joinCondition = joinCondition; + this.descriptor1 = descriptor1; + this.descriptor2 = descriptor2; + } + mightHaveBeenDeleted(): boolean { + if (!this.descriptor1 || this.descriptor1.mightHaveBeenDeleted()) { + return true; + } + if (!this.descriptor2 || this.descriptor2.mightHaveBeenDeleted()) { + return true; + } + return false; + } +} + +export function cloneDescriptor(d: void | PropertyDescriptor): void | PropertyDescriptor { + if (d === undefined) return undefined; + return new PropertyDescriptor(d); +} + +// does not check if the contents of value properties are the same +export function equalDescriptors(d1: PropertyDescriptor, d2: PropertyDescriptor): boolean { + if (d1.writable !== d2.writable) return false; + if (d1.enumerable !== d2.enumerable) return false; + if (d1.configurable !== d2.configurable) return false; + if (d1.value !== undefined) { + if (d2.value === undefined) return false; + } + if (d1.get !== d2.get) return false; + if (d1.set !== d2.set) return false; + return true; +} diff --git a/src/environment.js b/src/environment.js index 81d2a93e00..441f025e5b 100644 --- a/src/environment.js +++ b/src/environment.js @@ -48,6 +48,7 @@ import PrimitiveValue from "./values/PrimitiveValue.js"; import { createOperationDescriptor } from "./utils/generator.js"; import { SourceMapConsumer, type NullableMappedPosition } from "source-map"; +import { PropertyDescriptor } from "./descriptors.js"; function deriveGetBinding(realm: Realm, binding: Binding) { let types = TypesDomain.topVal; @@ -448,12 +449,17 @@ export class ObjectEnvironmentRecord extends EnvironmentRecord { // 4. Return ? DefinePropertyOrThrow(bindings, N, PropertyDescriptor{[[Value]]: undefined, [[Writable]]: true, [[Enumerable]]: true, [[Configurable]]: configValue}). return new BooleanValue( realm, - Properties.DefinePropertyOrThrow(realm, bindings, N, { - value: realm.intrinsics.undefined, - writable: true, - enumerable: true, - configurable: configValue, - }) + Properties.DefinePropertyOrThrow( + realm, + bindings, + N, + new PropertyDescriptor({ + value: realm.intrinsics.undefined, + writable: true, + enumerable: true, + configurable: configValue, + }) + ) ); } @@ -898,7 +904,8 @@ export class GlobalEnvironmentRecord extends EnvironmentRecord { // 5. If existingProp is undefined, return false. if (!existingProp) return false; - Properties.ThrowIfMightHaveBeenDeleted(existingProp.value); + Properties.ThrowIfMightHaveBeenDeleted(existingProp); + existingProp = existingProp.throwIfNotConcrete(globalObject.$Realm); // 6. If existingProp.[[Configurable]] is true, return false. if (existingProp.configurable) return false; @@ -948,7 +955,8 @@ export class GlobalEnvironmentRecord extends EnvironmentRecord { // 5. If existingProp is undefined, return ? IsExtensible(globalObject). if (!existingProp) return IsExtensible(realm, globalObject); - Properties.ThrowIfMightHaveBeenDeleted(existingProp.value); + Properties.ThrowIfMightHaveBeenDeleted(existingProp); + existingProp = existingProp.throwIfNotConcrete(globalObject.$Realm); // 6. If existingProp.[[Configurable]] is true, return true. if (existingProp.configurable) return true; @@ -1015,17 +1023,20 @@ export class GlobalEnvironmentRecord extends EnvironmentRecord { // 4. Let existingProp be ? globalObject.[[GetOwnProperty]](N). let existingProp = globalObject.$GetOwnProperty(N); + if (existingProp) { + existingProp = existingProp.throwIfNotConcrete(globalObject.$Realm); + } // 5. If existingProp is undefined or existingProp.[[Configurable]] is true, then let desc; if (!existingProp || existingProp.configurable) { // a. Let desc be the PropertyDescriptor{[[Value]]: V, [[Writable]]: true, [[Enumerable]]: true, [[Configurable]]: D}. - desc = { value: V, writable: true, enumerable: true, configurable: D }; + desc = new PropertyDescriptor({ value: V, writable: true, enumerable: true, configurable: D }); } else { // 6. Else, - Properties.ThrowIfMightHaveBeenDeleted(existingProp.value); + Properties.ThrowIfMightHaveBeenDeleted(existingProp); // a. Let desc be the PropertyDescriptor{[[Value]]: V }. - desc = { value: V }; + desc = new PropertyDescriptor({ value: V }); } // 7. Perform ? DefinePropertyOrThrow(globalObject, N, desc). diff --git a/src/evaluators/ForInStatement.js b/src/evaluators/ForInStatement.js index 2ca2310fc0..d6be3752a4 100644 --- a/src/evaluators/ForInStatement.js +++ b/src/evaluators/ForInStatement.js @@ -155,7 +155,7 @@ function emitResidualLoopIfSafe( if (key.object.unknownProperty === key) { targetObject = key.object; invariant(desc !== undefined); - let sourceValue = desc.value; + let sourceValue = desc.throwIfNotConcrete(realm).value; if (sourceValue instanceof AbstractValue) { // because sourceValue was written to key.object.unknownProperty it must be that let cond = sourceValue.args[0]; diff --git a/src/evaluators/FunctionDeclaration.js b/src/evaluators/FunctionDeclaration.js index 69dc81d509..e78f09a753 100644 --- a/src/evaluators/FunctionDeclaration.js +++ b/src/evaluators/FunctionDeclaration.js @@ -16,6 +16,7 @@ import { MakeConstructor } from "../methods/construct.js"; import { Create, Functions, Properties } from "../singletons.js"; import { StringValue } from "../values/index.js"; import IsStrict from "../utils/strict.js"; +import { PropertyDescriptor } from "../descriptors.js"; import type { BabelNodeFunctionDeclaration } from "@babel/types"; // ECMA262 14.1.20 @@ -44,11 +45,16 @@ export default function( let prototype = Create.ObjectCreate(realm, realm.intrinsics.GeneratorPrototype); // 5. Perform DefinePropertyOrThrow(F, "prototype", PropertyDescriptor{[[Value]]: prototype, [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: false}). - Properties.DefinePropertyOrThrow(realm, F, "prototype", { - value: prototype, - writable: true, - configurable: false, - }); + Properties.DefinePropertyOrThrow( + realm, + F, + "prototype", + new PropertyDescriptor({ + value: prototype, + writable: true, + configurable: false, + }) + ); // 6. Perform SetFunctionName(F, name). Functions.SetFunctionName(realm, F, name); diff --git a/src/evaluators/FunctionExpression.js b/src/evaluators/FunctionExpression.js index f0482d5653..75b4bef921 100644 --- a/src/evaluators/FunctionExpression.js +++ b/src/evaluators/FunctionExpression.js @@ -18,6 +18,7 @@ import { StringValue } from "../values/index.js"; import IsStrict from "../utils/strict.js"; import type { BabelNodeFunctionExpression } from "@babel/types"; import invariant from "../invariant.js"; +import { PropertyDescriptor } from "../descriptors.js"; export default function( ast: BabelNodeFunctionExpression, @@ -57,12 +58,17 @@ export default function( prototype.originalConstructor = closure; // 9. Perform DefinePropertyOrThrow(closure, "prototype", PropertyDescriptor{[[Value]]: prototype, [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: false}). - Properties.DefinePropertyOrThrow(realm, closure, "prototype", { - value: prototype, - writable: true, - enumerable: false, - configurable: false, - }); + Properties.DefinePropertyOrThrow( + realm, + closure, + "prototype", + new PropertyDescriptor({ + value: prototype, + writable: true, + enumerable: false, + configurable: false, + }) + ); // 10. Perform SetFunctionName(closure, name). Functions.SetFunctionName(realm, closure, new StringValue(realm, name)); @@ -126,12 +132,17 @@ export default function( prototype.originalConstructor = closure; // 5. Perform DefinePropertyOrThrow(closure, "prototype", PropertyDescriptor{[[Value]]: prototype, [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: false}). - Properties.DefinePropertyOrThrow(realm, closure, "prototype", { - value: prototype, - writable: true, - enumerable: false, - configurable: false, - }); + Properties.DefinePropertyOrThrow( + realm, + closure, + "prototype", + new PropertyDescriptor({ + value: prototype, + writable: true, + enumerable: false, + configurable: false, + }) + ); // 6. Return closure. return closure; diff --git a/src/evaluators/JSXElement.js b/src/evaluators/JSXElement.js index e3ae20e488..2b18d8fc65 100644 --- a/src/evaluators/JSXElement.js +++ b/src/evaluators/JSXElement.js @@ -195,10 +195,10 @@ function evaluateJSXChildren( return array; } -function isObjectEmpty(object: ObjectValue) { +function isObjectEmpty(realm: Realm, object: ObjectValue) { let propertyCount = 0; for (let [, binding] of object.properties) { - if (binding && binding.descriptor && binding.descriptor.enumerable) { + if (binding && binding.descriptor && binding.descriptor.throwIfNotConcrete(realm).enumerable) { propertyCount++; } } @@ -235,7 +235,7 @@ function evaluateJSXAttributes( if (spreadValue instanceof ObjectValue && !spreadValue.isPartialObject()) { for (let [spreadPropKey, binding] of spreadValue.properties) { - if (binding && binding.descriptor && binding.descriptor.enumerable) { + if (binding && binding.descriptor && binding.descriptor.throwIfNotConcrete(realm).enumerable) { setConfigProperty(spreadPropKey, Get(realm, spreadValue, spreadPropKey)); } } @@ -249,7 +249,7 @@ function evaluateJSXAttributes( if (hasNoPartialKeyOrRef(realm, spreadValue)) { safeAbstractSpreadCount++; } - if (!isObjectEmpty(config)) { + if (!isObjectEmpty(realm, config)) { abstractPropsArgs.push(config); } abstractPropsArgs.push(spreadValue); diff --git a/src/intrinsics/dom/global.js b/src/intrinsics/dom/global.js index a33b4f1262..5860b274b6 100644 --- a/src/intrinsics/dom/global.js +++ b/src/intrinsics/dom/global.js @@ -17,92 +17,117 @@ import initializeConsole from "../common/console.js"; import { FatalError } from "../../errors.js"; import invariant from "../../invariant.js"; import { TypesDomain, ValuesDomain } from "../../domains/index.js"; +import { PropertyDescriptor } from "../../descriptors.js"; export default function(realm: Realm): void { let global = realm.$GlobalObject; if (!realm.isCompatibleWith(realm.MOBILE_JSC_VERSION) && !realm.isCompatibleWith("mobile")) - global.$DefineOwnProperty("console", { - value: initializeConsole(realm), + global.$DefineOwnProperty( + "console", + new PropertyDescriptor({ + value: initializeConsole(realm), + writable: true, + enumerable: false, + configurable: true, + }) + ); + + global.$DefineOwnProperty( + "self", + new PropertyDescriptor({ + value: global, writable: true, - enumerable: false, + enumerable: true, configurable: true, - }); + }) + ); - global.$DefineOwnProperty("self", { - value: global, - writable: true, - enumerable: true, - configurable: true, - }); - - global.$DefineOwnProperty("window", { - value: global, - writable: true, - enumerable: true, - configurable: true, - }); + global.$DefineOwnProperty( + "window", + new PropertyDescriptor({ + value: global, + writable: true, + enumerable: true, + configurable: true, + }) + ); - global.$DefineOwnProperty("document", { - value: initializeDocument(realm), - writable: true, - enumerable: false, - configurable: true, - }); + global.$DefineOwnProperty( + "document", + new PropertyDescriptor({ + value: initializeDocument(realm), + writable: true, + enumerable: false, + configurable: true, + }) + ); - global.$DefineOwnProperty("setTimeout", { - value: new NativeFunctionValue(realm, "global.setTimeout", "", 2, (context, args) => { - let callback = args[0].throwIfNotConcrete(); - if (!(callback instanceof FunctionValue)) - throw realm.createErrorThrowCompletion(realm.intrinsics.TypeError, "callback arguments must be function"); - if (!realm.useAbstractInterpretation) throw new FatalError("TODO #1003: implement global.setTimeout"); - invariant(realm.generator !== undefined); - let generator = realm.generator; - return generator.emitCallAndCaptureResult(TypesDomain.topVal, ValuesDomain.topVal, "global.setTimeout", args); - }), - writable: true, - enumerable: true, - configurable: true, - }); + global.$DefineOwnProperty( + "setTimeout", + new PropertyDescriptor({ + value: new NativeFunctionValue(realm, "global.setTimeout", "", 2, (context, args) => { + let callback = args[0].throwIfNotConcrete(); + if (!(callback instanceof FunctionValue)) + throw realm.createErrorThrowCompletion(realm.intrinsics.TypeError, "callback arguments must be function"); + if (!realm.useAbstractInterpretation) throw new FatalError("TODO #1003: implement global.setTimeout"); + invariant(realm.generator !== undefined); + let generator = realm.generator; + return generator.emitCallAndCaptureResult(TypesDomain.topVal, ValuesDomain.topVal, "global.setTimeout", args); + }), + writable: true, + enumerable: true, + configurable: true, + }) + ); - global.$DefineOwnProperty("clearTimeout", { - value: new NativeFunctionValue(realm, "global.clearTimeout", "", 2, (context, args) => { - if (!realm.useAbstractInterpretation) throw new FatalError("TODO #1003: implement global.clearTimeout"); - invariant(realm.generator !== undefined); - let generator = realm.generator; - generator.emitCall("global.clearTimeout", args); - return realm.intrinsics.undefined; - }), - writable: true, - enumerable: true, - configurable: true, - }); + global.$DefineOwnProperty( + "clearTimeout", + new PropertyDescriptor({ + value: new NativeFunctionValue(realm, "global.clearTimeout", "", 2, (context, args) => { + if (!realm.useAbstractInterpretation) throw new FatalError("TODO #1003: implement global.clearTimeout"); + invariant(realm.generator !== undefined); + let generator = realm.generator; + generator.emitCall("global.clearTimeout", args); + return realm.intrinsics.undefined; + }), + writable: true, + enumerable: true, + configurable: true, + }) + ); - global.$DefineOwnProperty("setInterval", { - value: new NativeFunctionValue(realm, "global.setInterval", "", 2, (context, args) => { - if (!realm.useAbstractInterpretation) throw new FatalError("TODO #1003: implement global.setInterval"); - let callback = args[0].throwIfNotConcrete(); - if (!(callback instanceof FunctionValue)) - throw realm.createErrorThrowCompletion(realm.intrinsics.TypeError, "callback arguments must be function"); - invariant(realm.generator !== undefined); - let generator = realm.generator; - return generator.emitCallAndCaptureResult(TypesDomain.topVal, ValuesDomain.topVal, "global.setInterval", args); - }), - writable: true, - enumerable: true, - configurable: true, - }); + global.$DefineOwnProperty( + "setInterval", + new PropertyDescriptor({ + value: new NativeFunctionValue(realm, "global.setInterval", "", 2, (context, args) => { + if (!realm.useAbstractInterpretation) throw new FatalError("TODO #1003: implement global.setInterval"); + let callback = args[0].throwIfNotConcrete(); + if (!(callback instanceof FunctionValue)) + throw realm.createErrorThrowCompletion(realm.intrinsics.TypeError, "callback arguments must be function"); + invariant(realm.generator !== undefined); + let generator = realm.generator; + return generator.emitCallAndCaptureResult(TypesDomain.topVal, ValuesDomain.topVal, "global.setInterval", args); + }), + writable: true, + enumerable: true, + configurable: true, + }) + ); - global.$DefineOwnProperty("clearInterval", { - value: new NativeFunctionValue(realm, "global.clearInterval", "", 2, (context, args) => { - if (!realm.useAbstractInterpretation) throw new FatalError("TODO #1003: implement global.clearInterval"); - invariant(realm.generator !== undefined); - let generator = realm.generator; - generator.emitCall("global.clearInterval", args); - return realm.intrinsics.undefined; - }), - writable: true, - enumerable: true, - configurable: true, - }); + global.$DefineOwnProperty( + "clearInterval", + new PropertyDescriptor({ + value: new NativeFunctionValue(realm, "global.clearInterval", "", 2, (context, args) => { + if (!realm.useAbstractInterpretation) throw new FatalError("TODO #1003: implement global.clearInterval"); + invariant(realm.generator !== undefined); + let generator = realm.generator; + generator.emitCall("global.clearInterval", args); + return realm.intrinsics.undefined; + }), + writable: true, + enumerable: true, + configurable: true, + }) + ); } diff --git a/src/intrinsics/ecma262/ArrayPrototype.js b/src/intrinsics/ecma262/ArrayPrototype.js index 71758d4bfd..8e582d11f0 100644 --- a/src/intrinsics/ecma262/ArrayPrototype.js +++ b/src/intrinsics/ecma262/ArrayPrototype.js @@ -1749,7 +1749,7 @@ export default function(realm: Realm, obj: ObjectValue): void { let elem = O.$GetOwnProperty(i.toString()); // b.If elem is undefined, return true. if (elem === undefined) return true; - Properties.ThrowIfMightHaveBeenDeleted(elem.value); + Properties.ThrowIfMightHaveBeenDeleted(elem); } // 2.Return false. return false; @@ -1782,8 +1782,8 @@ export default function(realm: Realm, obj: ObjectValue): void { for (let j = 0; j < len; j++) { // is a data property whose [[Configurable]] attribute is false. let prop = O.$GetOwnProperty(j.toString()); - if (prop !== undefined && !prop.configurable) { - Properties.ThrowIfMightHaveBeenDeleted(prop.value); + if (prop !== undefined && !prop.throwIfNotConcrete(realm).configurable) { + Properties.ThrowIfMightHaveBeenDeleted(prop); throw Error( "Implementation defined behavior : Array is sparse and it's prototype has some numbered properties" ); @@ -1795,8 +1795,8 @@ export default function(realm: Realm, obj: ObjectValue): void { for (let j = 0; j < len; j++) { //is a data property whose [[writable]] attribute is false. let prop = O.$GetOwnProperty(j.toString()); - if (prop !== undefined && !prop.writable) { - Properties.ThrowIfMightHaveBeenDeleted(prop.value); + if (prop !== undefined && !prop.throwIfNotConcrete(realm).writable) { + Properties.ThrowIfMightHaveBeenDeleted(prop); throw Error("Implementation defined behavior : property " + j.toString() + "is non writable : "); } } diff --git a/src/intrinsics/ecma262/Error.js b/src/intrinsics/ecma262/Error.js index a435d04f24..fcb7936d4c 100644 --- a/src/intrinsics/ecma262/Error.js +++ b/src/intrinsics/ecma262/Error.js @@ -23,6 +23,7 @@ import { Get } from "../../methods/index.js"; import { Create, Properties, To } from "../../singletons.js"; import invariant from "../../invariant.js"; import type { BabelNodeSourceLocation } from "@babel/types"; +import { PropertyDescriptor } from "../../descriptors.js"; export default function(realm: Realm): NativeFunctionValue { return build("Error", realm, false); @@ -141,12 +142,12 @@ export function build(name: string, realm: Realm, inheritError?: boolean = true) let msg = message.getType() === StringValue ? message : To.ToStringValue(realm, message); // b. Let msgDesc be the PropertyDescriptor{[[Value]]: msg, [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: true}. - let msgDesc = { + let msgDesc = new PropertyDescriptor({ value: msg, writable: true, enumerable: false, configurable: true, - }; + }); // c. Perform ! DefinePropertyOrThrow(O, "message", msgDesc). Properties.DefinePropertyOrThrow(realm, O, "message", msgDesc); @@ -155,12 +156,12 @@ export function build(name: string, realm: Realm, inheritError?: boolean = true) } // Build a text description of the stack. - let stackDesc = { + let stackDesc = new PropertyDescriptor({ value: buildStack(realm, O), enumerable: false, configurable: true, writable: true, - }; + }); Properties.DefinePropertyOrThrow(realm, O, "stack", stackDesc); // 4. Return O. diff --git a/src/intrinsics/ecma262/FunctionPrototype.js b/src/intrinsics/ecma262/FunctionPrototype.js index 118b492652..cceec0d38a 100644 --- a/src/intrinsics/ecma262/FunctionPrototype.js +++ b/src/intrinsics/ecma262/FunctionPrototype.js @@ -29,6 +29,7 @@ import { IsCallable } from "../../methods/is.js"; import { HasOwnProperty, HasSomeCompatibleType } from "../../methods/has.js"; import { OrdinaryHasInstance } from "../../methods/abstract.js"; import invariant from "../../invariant.js"; +import { PropertyDescriptor } from "../../descriptors.js"; export default function(realm: Realm, obj: ObjectValue): void { // ECMA262 19.2.3 @@ -129,12 +130,17 @@ export default function(realm: Realm, obj: ObjectValue): void { } // 8. Perform ! DefinePropertyOrThrow(F, "length", PropertyDescriptor {[[Value]]: L, [[Writable]]: false, [[Enumerable]]: false, [[Configurable]]: true}). - Properties.DefinePropertyOrThrow(realm, F, "length", { - value: new NumberValue(realm, L), - writable: false, - enumerable: false, - configurable: true, - }); + Properties.DefinePropertyOrThrow( + realm, + F, + "length", + new PropertyDescriptor({ + value: new NumberValue(realm, L), + writable: false, + enumerable: false, + configurable: true, + }) + ); // 9. Let targetName be ? Get(Target, "name"). let targetName = Get(realm, Target, new StringValue(realm, "name")); diff --git a/src/intrinsics/ecma262/JSON.js b/src/intrinsics/ecma262/JSON.js index f34c6dcf35..bd74f15cd6 100644 --- a/src/intrinsics/ecma262/JSON.js +++ b/src/intrinsics/ecma262/JSON.js @@ -292,9 +292,10 @@ function InternalCloneObject(realm: Realm, val: ObjectValue): ObjectValue { let clone = Create.ObjectCreate(realm, realm.intrinsics.ObjectPrototype); for (let [key, binding] of val.properties) { if (binding === undefined || binding.descriptor === undefined) continue; // deleted - invariant(binding.descriptor !== undefined); - let value = binding.descriptor.value; - Properties.ThrowIfMightHaveBeenDeleted(value); + let desc = binding.descriptor; + invariant(desc !== undefined); + Properties.ThrowIfMightHaveBeenDeleted(desc); + let value = desc.throwIfNotConcrete(realm).value; if (value === undefined) { AbstractValue.reportIntrospectionError(val, key); // cannot handle accessors throw new FatalError(); diff --git a/src/intrinsics/ecma262/MapPrototype.js b/src/intrinsics/ecma262/MapPrototype.js index a009291b6a..389f603281 100644 --- a/src/intrinsics/ecma262/MapPrototype.js +++ b/src/intrinsics/ecma262/MapPrototype.js @@ -14,6 +14,7 @@ import { NumberValue, StringValue, NativeFunctionValue, ObjectValue } from "../. import { Call, CreateMapIterator, IsCallable, SameValueZeroPartial } from "../../methods/index.js"; import { Properties } from "../../singletons.js"; import invariant from "../../invariant.js"; +import { PropertyDescriptor } from "../../descriptors.js"; export default function(realm: Realm, obj: ObjectValue): void { // ECMA262 23.1.3.1 @@ -257,39 +258,42 @@ export default function(realm: Realm, obj: ObjectValue): void { }); // ECMA262 23.1.3.10 - obj.$DefineOwnProperty("size", { - configurable: true, - get: new NativeFunctionValue(realm, undefined, "get size", 0, context => { - // 1. Let M be the this value. - let M = context.throwIfNotConcrete(); - - // 2. If Type(M) is not Object, throw a TypeError exception. - if (!(M instanceof ObjectValue)) { - throw realm.createErrorThrowCompletion(realm.intrinsics.TypeError); - } - - // 3. If M does not have a [[MapData]] internal slot, throw a TypeError exception. - if (!M.$MapData) { - throw realm.createErrorThrowCompletion(realm.intrinsics.TypeError); - } - - // 4. Let entries be the List that is the value of M's [[MapData]] internal slot. - let entries = M.$MapData; - invariant(entries !== undefined); - - // 5. Let count be 0. - let count = 0; - - // 6. For each Record {[[Key]], [[Value]]} p that is an element of entries - for (let p of entries) { - // a. If p.[[Key]] is not empty, set count to count+1. - if (p.$Key !== undefined) count++; - } - - // 7. Return count. - return new NumberValue(realm, count); - }), - }); + obj.$DefineOwnProperty( + "size", + new PropertyDescriptor({ + configurable: true, + get: new NativeFunctionValue(realm, undefined, "get size", 0, context => { + // 1. Let M be the this value. + let M = context.throwIfNotConcrete(); + + // 2. If Type(M) is not Object, throw a TypeError exception. + if (!(M instanceof ObjectValue)) { + throw realm.createErrorThrowCompletion(realm.intrinsics.TypeError); + } + + // 3. If M does not have a [[MapData]] internal slot, throw a TypeError exception. + if (!M.$MapData) { + throw realm.createErrorThrowCompletion(realm.intrinsics.TypeError); + } + + // 4. Let entries be the List that is the value of M's [[MapData]] internal slot. + let entries = M.$MapData; + invariant(entries !== undefined); + + // 5. Let count be 0. + let count = 0; + + // 6. For each Record {[[Key]], [[Value]]} p that is an element of entries + for (let p of entries) { + // a. If p.[[Key]] is not empty, set count to count+1. + if (p.$Key !== undefined) count++; + } + + // 7. Return count. + return new NumberValue(realm, count); + }), + }) + ); // ECMA262 23.1.3.11 obj.defineNativeMethod("values", 0, context => { @@ -302,9 +306,9 @@ export default function(realm: Realm, obj: ObjectValue): void { // ECMA262 23.1.3.12 let entriesPropertyDescriptor = obj.$GetOwnProperty("entries"); - invariant(entriesPropertyDescriptor); - Properties.ThrowIfMightHaveBeenDeleted(entriesPropertyDescriptor.value); - obj.defineNativeProperty(realm.intrinsics.SymbolIterator, undefined, entriesPropertyDescriptor); + invariant(entriesPropertyDescriptor instanceof PropertyDescriptor); + Properties.ThrowIfMightHaveBeenDeleted(entriesPropertyDescriptor); + obj.$DefineOwnProperty(realm.intrinsics.SymbolIterator, entriesPropertyDescriptor); // ECMA262 23.1.3.13 obj.defineNativeProperty(realm.intrinsics.SymbolToStringTag, new StringValue(realm, "Map"), { writable: false }); diff --git a/src/intrinsics/ecma262/Object.js b/src/intrinsics/ecma262/Object.js index 37fd3792af..6c8b74dffe 100644 --- a/src/intrinsics/ecma262/Object.js +++ b/src/intrinsics/ecma262/Object.js @@ -40,6 +40,7 @@ import { import { Create, Leak, Properties as Props, To } from "../../singletons.js"; import { createOperationDescriptor } from "../../utils/generator.js"; import invariant from "../../invariant.js"; +import { PropertyDescriptor } from "../../descriptors.js"; function snapshotToObjectAndRemoveProperties( to: ObjectValue | AbstractObjectValue, @@ -96,8 +97,8 @@ function copyKeys(realm: Realm, keys, from, to): void { let desc = from.$GetOwnProperty(nextKey); // ii. If desc is not undefined and desc.[[Enumerable]] is true, then - if (desc && desc.enumerable) { - Props.ThrowIfMightHaveBeenDeleted(desc.value); + if (desc && desc.throwIfNotConcrete(realm).enumerable) { + Props.ThrowIfMightHaveBeenDeleted(desc); // 1. Let propValue be ? Get(from, nextKey). let propValue = Get(realm, from, nextKey); @@ -346,35 +347,37 @@ export default function(realm: Realm): NativeFunctionValue { // 3. Let desc be ? obj.[[GetOwnProperty]](key). let desc = obj.$GetOwnProperty(key); - let getterFunc = desc && desc.get; // If we are returning a descriptor with a NativeFunctionValue // and it has no intrinsic name, then we create a temporal as this // can only be done at runtime - if ( - getterFunc instanceof NativeFunctionValue && - getterFunc.intrinsicName === undefined && - realm.useAbstractInterpretation - ) { - invariant(P instanceof Value); - // this will create a property descriptor at runtime - let result = AbstractValue.createTemporalFromBuildFunction( - realm, - ObjectValue, - [getOwnPropertyDescriptor, obj, P], - createOperationDescriptor("OBJECT_PROTO_GET_OWN_PROPERTY_DESCRIPTOR") - ); - invariant(result instanceof AbstractObjectValue); - result.makeSimple(); - let get = Get(realm, result, "get"); - let set = Get(realm, result, "set"); - invariant(get instanceof AbstractValue); - invariant(set instanceof AbstractValue); - desc = { - get, - set, - enumerable: false, - configurable: true, - }; + if (desc instanceof PropertyDescriptor) { + let getterFunc = desc.get; + if ( + getterFunc instanceof NativeFunctionValue && + getterFunc.intrinsicName === undefined && + realm.useAbstractInterpretation + ) { + invariant(P instanceof Value); + // this will create a property descriptor at runtime + let result = AbstractValue.createTemporalFromBuildFunction( + realm, + ObjectValue, + [getOwnPropertyDescriptor, obj, P], + createOperationDescriptor("OBJECT_PROTO_GET_OWN_PROPERTY_DESCRIPTOR") + ); + invariant(result instanceof AbstractObjectValue); + result.makeSimple(); + let get = Get(realm, result, "get"); + let set = Get(realm, result, "set"); + invariant(get instanceof AbstractValue); + invariant(set instanceof AbstractValue); + desc = new PropertyDescriptor({ + get, + set, + enumerable: false, + configurable: true, + }); + } } // 4. Return FromPropertyDescriptor(desc). @@ -404,7 +407,7 @@ export default function(realm: Realm): NativeFunctionValue { for (let key of ownKeys) { // a. Let desc be ? obj.[[GetOwnProperty]](key). let desc = obj.$GetOwnProperty(key); - if (desc !== undefined) Props.ThrowIfMightHaveBeenDeleted(desc.value); + if (desc !== undefined) Props.ThrowIfMightHaveBeenDeleted(desc); // b. Let descriptor be ! FromPropertyDescriptor(desc). let descriptor = Props.FromPropertyDescriptor(realm, desc); diff --git a/src/intrinsics/ecma262/ObjectPrototype.js b/src/intrinsics/ecma262/ObjectPrototype.js index df5be3b5dc..47451d76af 100644 --- a/src/intrinsics/ecma262/ObjectPrototype.js +++ b/src/intrinsics/ecma262/ObjectPrototype.js @@ -26,6 +26,7 @@ import { FatalError } from "../../errors.js"; import invariant from "../../invariant.js"; import { TypesDomain, ValuesDomain } from "../../domains/index.js"; import { createOperationDescriptor } from "../../utils/generator.js"; +import { PropertyDescriptor } from "../../descriptors.js"; export default function(realm: Realm, obj: ObjectValue): void { // ECMA262 19.1.3.2 @@ -100,7 +101,8 @@ export default function(realm: Realm, obj: ObjectValue): void { // 4. If desc is undefined, return false. if (!desc) return realm.intrinsics.false; - Properties.ThrowIfMightHaveBeenDeleted(desc.value); + Properties.ThrowIfMightHaveBeenDeleted(desc); + desc = desc.throwIfNotConcrete(realm); // 5. Return the value of desc.[[Enumerable]]. return desc.enumerable === undefined ? realm.intrinsics.undefined : new BooleanValue(realm, desc.enumerable); @@ -124,38 +126,41 @@ export default function(realm: Realm, obj: ObjectValue): void { return To.ToObject(realm, context); }); - obj.$DefineOwnProperty("__proto__", { - // B.2.2.1.1 - get: new NativeFunctionValue(realm, undefined, "get __proto__", 0, context => { - // 1. Let O be ? ToObject(this value). - let O = To.ToObject(realm, context); - - // 2. Return ? O.[[GetPrototypeOf]](). - return O.$GetPrototypeOf(); - }), - - // B.2.2.1.2 - set: new NativeFunctionValue(realm, undefined, "set __proto__", 1, (context, [proto]) => { - // 1. Let O be ? RequireObjectCoercible(this value). - let O = RequireObjectCoercible(realm, context); - - // 2. If Type(proto) is neither Object nor Null, return undefined. - if (!HasSomeCompatibleType(proto, ObjectValue, NullValue)) return realm.intrinsics.undefined; - - // 3. If Type(O) is not Object, return undefined. - if (!O.mightBeObject()) return realm.intrinsics.undefined; - O = O.throwIfNotConcreteObject(); - - // 4. Let status be ? O.[[SetPrototypeOf]](proto). - let status = O.$SetPrototypeOf(((proto.throwIfNotConcrete(): any): ObjectValue | NullValue)); - - // 5. If status is false, throw a TypeError exception. - if (!status) { - throw realm.createErrorThrowCompletion(realm.intrinsics.TypeError, "couldn't set proto"); - } - - // 6. Return undefined. - return realm.intrinsics.undefined; - }), - }); + obj.$DefineOwnProperty( + "__proto__", + new PropertyDescriptor({ + // B.2.2.1.1 + get: new NativeFunctionValue(realm, undefined, "get __proto__", 0, context => { + // 1. Let O be ? ToObject(this value). + let O = To.ToObject(realm, context); + + // 2. Return ? O.[[GetPrototypeOf]](). + return O.$GetPrototypeOf(); + }), + + // B.2.2.1.2 + set: new NativeFunctionValue(realm, undefined, "set __proto__", 1, (context, [proto]) => { + // 1. Let O be ? RequireObjectCoercible(this value). + let O = RequireObjectCoercible(realm, context); + + // 2. If Type(proto) is neither Object nor Null, return undefined. + if (!HasSomeCompatibleType(proto, ObjectValue, NullValue)) return realm.intrinsics.undefined; + + // 3. If Type(O) is not Object, return undefined. + if (!O.mightBeObject()) return realm.intrinsics.undefined; + O = O.throwIfNotConcreteObject(); + + // 4. Let status be ? O.[[SetPrototypeOf]](proto). + let status = O.$SetPrototypeOf(((proto.throwIfNotConcrete(): any): ObjectValue | NullValue)); + + // 5. If status is false, throw a TypeError exception. + if (!status) { + throw realm.createErrorThrowCompletion(realm.intrinsics.TypeError, "couldn't set proto"); + } + + // 6. Return undefined. + return realm.intrinsics.undefined; + }), + }) + ); } diff --git a/src/intrinsics/ecma262/SetPrototype.js b/src/intrinsics/ecma262/SetPrototype.js index d54e8db5a8..a43f5c0f5a 100644 --- a/src/intrinsics/ecma262/SetPrototype.js +++ b/src/intrinsics/ecma262/SetPrototype.js @@ -14,6 +14,7 @@ import { NativeFunctionValue, ObjectValue, StringValue, NumberValue } from "../. import { Call, CreateSetIterator, IsCallable, SameValueZeroPartial } from "../../methods/index.js"; import { Properties } from "../../singletons.js"; import invariant from "../../invariant.js"; +import { PropertyDescriptor } from "../../descriptors.js"; export default function(realm: Realm, obj: ObjectValue): void { // ECMA262 23.2.3.1 @@ -200,38 +201,41 @@ export default function(realm: Realm, obj: ObjectValue): void { }); // ECMA262 23.2.3.9 get Set.prototype.size - obj.$DefineOwnProperty("size", { - get: new NativeFunctionValue(realm, undefined, "get size", 0, context => { - // 1. Let S be the this value. - let S = context.throwIfNotConcrete(); - - // 2. If Type(S) is not Object, throw a TypeError exception. - if (!(S instanceof ObjectValue)) { - throw realm.createErrorThrowCompletion(realm.intrinsics.TypeError); - } - - // 3. If S does not have a [[SetData]] internal slot, throw a TypeError exception. - if (!S.$SetData) { - throw realm.createErrorThrowCompletion(realm.intrinsics.TypeError); - } - - // 4. Let entries be the List that is the value of S's [[SetData]] internal slot. - let entries = S.$SetData; - - // 5. Let count be 0. - let count = 0; - - // 6. For each e that is an element of entries - for (let e of entries) { - // a. If e is not empty, set count to count+1. - if (e) count++; - } - - // 7. Return count. - return new NumberValue(realm, count); - }), - configurable: true, - }); + obj.$DefineOwnProperty( + "size", + new PropertyDescriptor({ + get: new NativeFunctionValue(realm, undefined, "get size", 0, context => { + // 1. Let S be the this value. + let S = context.throwIfNotConcrete(); + + // 2. If Type(S) is not Object, throw a TypeError exception. + if (!(S instanceof ObjectValue)) { + throw realm.createErrorThrowCompletion(realm.intrinsics.TypeError); + } + + // 3. If S does not have a [[SetData]] internal slot, throw a TypeError exception. + if (!S.$SetData) { + throw realm.createErrorThrowCompletion(realm.intrinsics.TypeError); + } + + // 4. Let entries be the List that is the value of S's [[SetData]] internal slot. + let entries = S.$SetData; + + // 5. Let count be 0. + let count = 0; + + // 6. For each e that is an element of entries + for (let e of entries) { + // a. If e is not empty, set count to count+1. + if (e) count++; + } + + // 7. Return count. + return new NumberValue(realm, count); + }), + configurable: true, + }) + ); // ECMA262 23.2.3.10 obj.defineNativeMethod("values", 0, context => { @@ -244,12 +248,12 @@ export default function(realm: Realm, obj: ObjectValue): void { // ECMA262 23.2.3.8 let valuesPropertyDescriptor = obj.$GetOwnProperty("values"); - invariant(valuesPropertyDescriptor); - Properties.ThrowIfMightHaveBeenDeleted(valuesPropertyDescriptor.value); - obj.defineNativeProperty("keys", undefined, valuesPropertyDescriptor); + invariant(valuesPropertyDescriptor instanceof PropertyDescriptor); + Properties.ThrowIfMightHaveBeenDeleted(valuesPropertyDescriptor); + obj.$DefineOwnProperty("keys", valuesPropertyDescriptor); // ECMA262 23.2.3.11 - obj.defineNativeProperty(realm.intrinsics.SymbolIterator, undefined, valuesPropertyDescriptor); + obj.$DefineOwnProperty(realm.intrinsics.SymbolIterator, valuesPropertyDescriptor); // ECMA262 23.2.3.12 Set.prototype [ @@toStringTag ] obj.defineNativeProperty(realm.intrinsics.SymbolToStringTag, new StringValue(realm, "Set"), { writable: false }); diff --git a/src/intrinsics/ecma262/ThrowTypeError.js b/src/intrinsics/ecma262/ThrowTypeError.js index bee8226826..e51d639301 100644 --- a/src/intrinsics/ecma262/ThrowTypeError.js +++ b/src/intrinsics/ecma262/ThrowTypeError.js @@ -11,6 +11,7 @@ import type { Realm } from "../../realm.js"; import { NativeFunctionValue } from "../../values/index.js"; +import { PropertyDescriptor } from "../../descriptors.js"; export default function(realm: Realm): NativeFunctionValue { // ECMA262 9.2.7.1 @@ -22,12 +23,15 @@ export default function(realm: Realm): NativeFunctionValue { func.setExtensible(false); // ECMA262 9.2.7.1 - func.$DefineOwnProperty("length", { - value: realm.intrinsics.zero, - writable: false, - configurable: false, - enumerable: false, - }); + func.$DefineOwnProperty( + "length", + new PropertyDescriptor({ + value: realm.intrinsics.zero, + writable: false, + configurable: false, + enumerable: false, + }) + ); return func; } diff --git a/src/intrinsics/ecma262/global.js b/src/intrinsics/ecma262/global.js index ba7f06c41e..bfe49ecf18 100644 --- a/src/intrinsics/ecma262/global.js +++ b/src/intrinsics/ecma262/global.js @@ -11,24 +11,31 @@ import type { Realm } from "../../realm.js"; import invariant from "../../invariant.js"; +import { PropertyDescriptor } from "../../descriptors.js"; export default function(realm: Realm): void { let global = realm.$GlobalObject; - global.$DefineOwnProperty("global", { - value: global, - writable: true, - enumerable: false, - configurable: true, - }); + global.$DefineOwnProperty( + "global", + new PropertyDescriptor({ + value: global, + writable: true, + enumerable: false, + configurable: true, + }) + ); for (let name of ["undefined", "NaN", "Infinity"]) { - global.$DefineOwnProperty(name, { - value: realm.intrinsics[name], - writable: false, - enumerable: false, - configurable: false, - }); + global.$DefineOwnProperty( + name, + new PropertyDescriptor({ + value: realm.intrinsics[name], + writable: false, + enumerable: false, + configurable: false, + }) + ); } let typeNames = [ "String", @@ -69,12 +76,15 @@ export default function(realm: Realm): void { for (let name of typeNames) { // need to check if the property exists (it may not due to --compatibility) if (realm.intrinsics[name]) { - global.$DefineOwnProperty(name, { - value: realm.intrinsics[name], - writable: true, - enumerable: false, - configurable: true, - }); + global.$DefineOwnProperty( + name, + new PropertyDescriptor({ + value: realm.intrinsics[name], + writable: true, + enumerable: false, + configurable: true, + }) + ); } else { invariant( name === "Symbol" || name === "Promise" || name === "WeakSet" || name === "Proxy" || name === "Reflect" @@ -94,11 +104,14 @@ export default function(realm: Realm): void { "encodeURIComponent", "decodeURIComponent", ]) { - global.$DefineOwnProperty(name, { - value: realm.intrinsics[name], - writable: true, - enumerable: false, - configurable: true, - }); + global.$DefineOwnProperty( + name, + new PropertyDescriptor({ + value: realm.intrinsics[name], + writable: true, + enumerable: false, + configurable: true, + }) + ); } } diff --git a/src/intrinsics/fb-www/fb-mocks.js b/src/intrinsics/fb-www/fb-mocks.js index 744a228ba7..80c848f037 100644 --- a/src/intrinsics/fb-www/fb-mocks.js +++ b/src/intrinsics/fb-www/fb-mocks.js @@ -27,6 +27,7 @@ import invariant from "../../invariant.js"; import { Properties } from "../../singletons.js"; import { forEachArrayValue } from "../../react/utils.js"; import { createOperationDescriptor } from "../../utils/generator.js"; +import { PropertyDescriptor } from "../../descriptors.js"; const fbMagicGlobalFunctions = [ "asset", @@ -74,8 +75,7 @@ function createBabelHelpers(realm: Realm, global: ObjectValue | AbstractObjectVa return superClass; }); - babelHelpersValue.$DefineOwnProperty("inherits", { - value: inheritsValue, + babelHelpersValue.defineNativeProperty("inherits", inheritsValue, { writable: false, enumerable: false, configurable: true, @@ -92,7 +92,7 @@ function createBabelHelpers(realm: Realm, global: ObjectValue | AbstractObjectVa let newObject = Create.ObjectCreate(realm, realm.intrinsics.ObjectPrototype); for (let [propName, binding] of obj.properties) { if (!removeKeys.has(propName)) { - if (binding && binding.descriptor && binding.descriptor.enumerable) { + if (binding && binding.descriptor && binding.descriptor.throwIfNotConcrete(realm).enumerable) { let value = Get(realm, obj, propName); Properties.Set(realm, newObject, propName, value, true); } @@ -134,8 +134,7 @@ function createBabelHelpers(realm: Realm, global: ObjectValue | AbstractObjectVa } } ); - babelHelpersValue.$DefineOwnProperty("objectWithoutProperties", { - value: objectWithoutPropertiesValue, + babelHelpersValue.defineNativeProperty("objectWithoutProperties", objectWithoutPropertiesValue, { writable: false, enumerable: false, configurable: true, @@ -154,8 +153,7 @@ function createBabelHelpers(realm: Realm, global: ObjectValue | AbstractObjectVa return strings; } ); - babelHelpersValue.$DefineOwnProperty("taggedTemplateLiteralLoose", { - value: taggedTemplateLiteralLooseValue, + babelHelpersValue.defineNativeProperty("taggedTemplateLiteralLoose", taggedTemplateLiteralLooseValue, { writable: false, enumerable: false, configurable: true, @@ -163,15 +161,13 @@ function createBabelHelpers(realm: Realm, global: ObjectValue | AbstractObjectVa taggedTemplateLiteralLooseValue.intrinsicName = `babelHelpers.taggedTemplateLiteralLoose`; //babelHelpers.extends & babelHelpers._extends - babelHelpersValue.$DefineOwnProperty("extends", { - value: objectAssign, + babelHelpersValue.defineNativeProperty("extends", objectAssign, { writable: true, enumerable: true, configurable: true, }); - babelHelpersValue.$DefineOwnProperty("_extends", { - value: objectAssign, + babelHelpersValue.defineNativeProperty("_extends", objectAssign, { writable: true, enumerable: true, configurable: true, @@ -180,51 +176,59 @@ function createBabelHelpers(realm: Realm, global: ObjectValue | AbstractObjectVa //babelHelpers.bind let functionBind = Get(realm, realm.intrinsics.FunctionPrototype, "bind"); - babelHelpersValue.$DefineOwnProperty("bind", { - value: functionBind, + babelHelpersValue.defineNativeProperty("bind", functionBind, { writable: true, enumerable: true, configurable: true, }); - global.$DefineOwnProperty("babelHelpers", { - value: babelHelpersValue, - writable: true, - enumerable: true, - configurable: true, - }); + global.$DefineOwnProperty( + "babelHelpers", + new PropertyDescriptor({ + value: babelHelpersValue, + writable: true, + enumerable: true, + configurable: true, + }) + ); babelHelpersValue.refuseSerialization = false; } function createMagicGlobalFunction(realm: Realm, global: ObjectValue | AbstractObjectValue, functionName: string) { - global.$DefineOwnProperty(functionName, { - value: new NativeFunctionValue(realm, functionName, functionName, 0, (context, args) => { - let val = AbstractValue.createTemporalFromBuildFunction( - realm, - FunctionValue, - [new StringValue(realm, functionName), ...args], - createOperationDescriptor("FB_MOCKS_MAGIC_GLOBAL_FUNCTION"), - { skipInvariant: true, isPure: true } - ); - invariant(val instanceof AbstractValue); - return val; - }), - writable: true, - enumerable: false, - configurable: true, - }); + global.$DefineOwnProperty( + functionName, + new PropertyDescriptor({ + value: new NativeFunctionValue(realm, functionName, functionName, 0, (context, args) => { + let val = AbstractValue.createTemporalFromBuildFunction( + realm, + FunctionValue, + [new StringValue(realm, functionName), ...args], + createOperationDescriptor("FB_MOCKS_MAGIC_GLOBAL_FUNCTION"), + { skipInvariant: true, isPure: true } + ); + invariant(val instanceof AbstractValue); + return val; + }), + writable: true, + enumerable: false, + configurable: true, + }) + ); } function createMagicGlobalObject(realm: Realm, global: ObjectValue | AbstractObjectValue, objectName: string) { let globalObject = AbstractValue.createAbstractObject(realm, objectName); globalObject.kind = "resolved"; - global.$DefineOwnProperty(objectName, { - value: globalObject, - writable: true, - enumerable: false, - configurable: true, - }); + global.$DefineOwnProperty( + objectName, + new PropertyDescriptor({ + value: globalObject, + writable: true, + enumerable: false, + configurable: true, + }) + ); } function createBootloader(realm: Realm, global: ObjectValue | AbstractObjectValue) { @@ -245,24 +249,30 @@ function createBootloader(realm: Realm, global: ObjectValue | AbstractObjectValu Properties.Set(realm, bootloader, "loadModules", loadModules, false); - global.$DefineOwnProperty("Bootloader", { - value: bootloader, - writable: true, - enumerable: false, - configurable: true, - }); + global.$DefineOwnProperty( + "Bootloader", + new PropertyDescriptor({ + value: bootloader, + writable: true, + enumerable: false, + configurable: true, + }) + ); return AbstractValue.createAbstractObject(realm, "Bootloader", bootloader); } export function createFbMocks(realm: Realm, global: ObjectValue | AbstractObjectValue): void { - global.$DefineOwnProperty("__DEV__", { - // TODO: we'll likely want to make this configurable from the outside. - value: realm.intrinsics.false, - writable: true, - enumerable: false, - configurable: true, - }); + global.$DefineOwnProperty( + "__DEV__", + new PropertyDescriptor({ + // TODO: we'll likely want to make this configurable from the outside. + value: realm.intrinsics.false, + writable: true, + enumerable: false, + configurable: true, + }) + ); createBabelHelpers(realm, global); diff --git a/src/intrinsics/fb-www/global.js b/src/intrinsics/fb-www/global.js index e785bd4bee..e093d67e2f 100644 --- a/src/intrinsics/fb-www/global.js +++ b/src/intrinsics/fb-www/global.js @@ -21,6 +21,7 @@ import { FatalError } from "../../errors"; import { Get } from "../../methods/index.js"; import invariant from "../../invariant"; import { createDefaultPropsHelper } from "../../react/utils.js"; +import { PropertyDescriptor } from "../../descriptors.js"; export default function(realm: Realm): void { let global = realm.$GlobalObject; @@ -42,84 +43,93 @@ export default function(realm: Realm): void { let moduleExportsValue = AbstractValue.createAbstractObject(realm, "module.exports"); moduleExportsValue.kind = "resolved"; - moduleValue.$DefineOwnProperty("exports", { - value: moduleExportsValue, - writable: true, - enumerable: false, - configurable: true, - }); - global.$DefineOwnProperty("module", { - value: moduleValue, - writable: true, - enumerable: false, - configurable: true, - }); + moduleValue.$DefineOwnProperty( + "exports", + new PropertyDescriptor({ + value: moduleExportsValue, + writable: true, + enumerable: false, + configurable: true, + }) + ); + global.$DefineOwnProperty( + "module", + new PropertyDescriptor({ + value: moduleValue, + writable: true, + enumerable: false, + configurable: true, + }) + ); // apply require() mock - global.$DefineOwnProperty("require", { - value: new NativeFunctionValue(realm, "require", "require", 0, (context, [requireNameVal]) => { - invariant(requireNameVal instanceof StringValue); - let requireNameValValue = requireNameVal.value; + global.$DefineOwnProperty( + "require", + new PropertyDescriptor({ + value: new NativeFunctionValue(realm, "require", "require", 0, (context, [requireNameVal]) => { + invariant(requireNameVal instanceof StringValue); + let requireNameValValue = requireNameVal.value; - if (requireNameValValue === "react" || requireNameValValue === "React") { - if (realm.fbLibraries.react === undefined) { - let react = createMockReact(realm, requireNameValValue); - realm.fbLibraries.react = react; - return react; - } - return realm.fbLibraries.react; - } else if (requireNameValValue === "react-dom" || requireNameValValue === "ReactDOM") { - if (realm.fbLibraries.reactDom === undefined) { - let reactDom = createMockReactDOM(realm, requireNameValValue); - realm.fbLibraries.reactDom = reactDom; - return reactDom; - } - return realm.fbLibraries.reactDom; - } else if (requireNameValValue === "react-dom/server" || requireNameValValue === "ReactDOMServer") { - if (realm.fbLibraries.reactDomServer === undefined) { - let reactDomServer = createMockReactDOMServer(realm, requireNameValValue); - realm.fbLibraries.reactDomServer = reactDomServer; - return reactDomServer; - } - return realm.fbLibraries.reactDomServer; - } else if (requireNameValValue === "react-native" || requireNameValValue === "ReactNative") { - if (realm.fbLibraries.reactNative === undefined) { - let reactNative = createMockReactNative(realm, requireNameValValue); - realm.fbLibraries.reactNative = reactNative; - return reactNative; - } - return realm.fbLibraries.reactNative; - } else if (requireNameValValue === "react-relay" || requireNameValValue === "RelayModern") { - if (realm.fbLibraries.reactRelay === undefined) { - let reactRelay = createMockReactRelay(realm, requireNameValValue); - realm.fbLibraries.reactRelay = reactRelay; - return reactRelay; - } - return realm.fbLibraries.reactRelay; - } else if (requireNameValValue === "prop-types" || requireNameValValue === "PropTypes") { - if (realm.fbLibraries.react === undefined) { - throw new FatalError("unable to require PropTypes due to React not being referenced in scope"); - } - let propTypes = Get(realm, realm.fbLibraries.react, "PropTypes"); - invariant(propTypes instanceof ObjectValue); - return propTypes; - } else { - let requireVal; - - if (realm.fbLibraries.other.has(requireNameValValue)) { - requireVal = realm.fbLibraries.other.get(requireNameValValue); + if (requireNameValValue === "react" || requireNameValValue === "React") { + if (realm.fbLibraries.react === undefined) { + let react = createMockReact(realm, requireNameValValue); + realm.fbLibraries.react = react; + return react; + } + return realm.fbLibraries.react; + } else if (requireNameValValue === "react-dom" || requireNameValValue === "ReactDOM") { + if (realm.fbLibraries.reactDom === undefined) { + let reactDom = createMockReactDOM(realm, requireNameValValue); + realm.fbLibraries.reactDom = reactDom; + return reactDom; + } + return realm.fbLibraries.reactDom; + } else if (requireNameValValue === "react-dom/server" || requireNameValValue === "ReactDOMServer") { + if (realm.fbLibraries.reactDomServer === undefined) { + let reactDomServer = createMockReactDOMServer(realm, requireNameValValue); + realm.fbLibraries.reactDomServer = reactDomServer; + return reactDomServer; + } + return realm.fbLibraries.reactDomServer; + } else if (requireNameValValue === "react-native" || requireNameValValue === "ReactNative") { + if (realm.fbLibraries.reactNative === undefined) { + let reactNative = createMockReactNative(realm, requireNameValValue); + realm.fbLibraries.reactNative = reactNative; + return reactNative; + } + return realm.fbLibraries.reactNative; + } else if (requireNameValValue === "react-relay" || requireNameValValue === "RelayModern") { + if (realm.fbLibraries.reactRelay === undefined) { + let reactRelay = createMockReactRelay(realm, requireNameValValue); + realm.fbLibraries.reactRelay = reactRelay; + return reactRelay; + } + return realm.fbLibraries.reactRelay; + } else if (requireNameValValue === "prop-types" || requireNameValValue === "PropTypes") { + if (realm.fbLibraries.react === undefined) { + throw new FatalError("unable to require PropTypes due to React not being referenced in scope"); + } + let propTypes = Get(realm, realm.fbLibraries.react, "PropTypes"); + invariant(propTypes instanceof ObjectValue); + return propTypes; } else { - requireVal = createAbstract(realm, "function", `require("${requireNameValValue}")`); - realm.fbLibraries.other.set(requireNameValValue, requireVal); + let requireVal; + + if (realm.fbLibraries.other.has(requireNameValValue)) { + requireVal = realm.fbLibraries.other.get(requireNameValValue); + } else { + requireVal = createAbstract(realm, "function", `require("${requireNameValValue}")`); + realm.fbLibraries.other.set(requireNameValValue, requireVal); + } + invariant(requireVal instanceof AbstractValue); + return requireVal; } - invariant(requireVal instanceof AbstractValue); - return requireVal; - } - }), - writable: true, - enumerable: false, - configurable: true, - }); + }), + writable: true, + enumerable: false, + configurable: true, + }) + ); if (realm.isCompatibleWith("fb-www")) { createFbMocks(realm, global); diff --git a/src/intrinsics/fb-www/utils.js b/src/intrinsics/fb-www/utils.js index e2f72fe48a..6a88539bcb 100644 --- a/src/intrinsics/fb-www/utils.js +++ b/src/intrinsics/fb-www/utils.js @@ -52,8 +52,7 @@ export function addMockFunctionToObject( ): void { let funcValue = new NativeFunctionValue(realm, undefined, funcName, 0, (context, args) => func(funcValue, args)); - obj.$DefineOwnProperty(funcName, { - value: funcValue, + obj.defineNativeProperty(funcName, funcValue, { writable: false, enumerable: false, configurable: true, diff --git a/src/intrinsics/index.js b/src/intrinsics/index.js index 704a3ebd87..5b83c850b8 100644 --- a/src/intrinsics/index.js +++ b/src/intrinsics/index.js @@ -158,6 +158,7 @@ import initializeThrowTypeError from "./ecma262/ThrowTypeError.js"; import initialize__IntrospectionError from "./prepack/__IntrospectionError.js"; import initialize__IntrospectionErrorPrototype from "./prepack/__IntrospectionErrorPrototype.js"; +import { PropertyDescriptor } from "../descriptors.js"; export function initialize(i: Intrinsics, realm: Realm): Intrinsics { i.undefined = new UndefinedValue(realm); @@ -401,26 +402,35 @@ export function initialize(i: Intrinsics, realm: Realm): Intrinsics { let fn = i[name]; let proto = i[`${name}Prototype`]; - proto.$DefineOwnProperty("constructor", { - value: fn, - writable: true, - enumerable: false, - configurable: true, - }); + proto.$DefineOwnProperty( + "constructor", + new PropertyDescriptor({ + value: fn, + writable: true, + enumerable: false, + configurable: true, + }) + ); - fn.$DefineOwnProperty("prototype", { - value: proto, - writable: false, - enumerable: false, - configurable: false, - }); + fn.$DefineOwnProperty( + "prototype", + new PropertyDescriptor({ + value: proto, + writable: false, + enumerable: false, + configurable: false, + }) + ); - fn.$DefineOwnProperty("constructor", { - value: i.Function, - writable: true, - enumerable: false, - configurable: true, - }); + fn.$DefineOwnProperty( + "constructor", + new PropertyDescriptor({ + value: i.Function, + writable: true, + enumerable: false, + configurable: true, + }) + ); } // @@ -430,31 +440,43 @@ export function initialize(i: Intrinsics, realm: Realm): Intrinsics { initializeGenerator(realm, i.Generator); i.GeneratorFunction = initializeGeneratorFunction(realm); - i.Generator.$DefineOwnProperty("prototype", { - value: i.GeneratorPrototype, - writable: false, - enumerable: false, - configurable: true, - }); - i.GeneratorPrototype.$DefineOwnProperty("constructor", { - value: i.Generator, - writable: false, - enumerable: false, - configurable: true, - }); - - i.GeneratorFunction.$DefineOwnProperty("prototype", { - value: i.Generator, - writable: false, - enumerable: false, - configurable: false, - }); - i.Generator.$DefineOwnProperty("constructor", { - value: i.GeneratorFunction, - writable: false, - enumerable: false, - configurable: true, - }); + i.Generator.$DefineOwnProperty( + "prototype", + new PropertyDescriptor({ + value: i.GeneratorPrototype, + writable: false, + enumerable: false, + configurable: true, + }) + ); + i.GeneratorPrototype.$DefineOwnProperty( + "constructor", + new PropertyDescriptor({ + value: i.Generator, + writable: false, + enumerable: false, + configurable: true, + }) + ); + + i.GeneratorFunction.$DefineOwnProperty( + "prototype", + new PropertyDescriptor({ + value: i.Generator, + writable: false, + enumerable: false, + configurable: false, + }) + ); + i.Generator.$DefineOwnProperty( + "constructor", + new PropertyDescriptor({ + value: i.GeneratorFunction, + writable: false, + enumerable: false, + configurable: true, + }) + ); // i.isNaN = initializeIsNaN(realm); diff --git a/src/intrinsics/prepack/global.js b/src/intrinsics/prepack/global.js index ba543ae0ce..c881f77bc7 100644 --- a/src/intrinsics/prepack/global.js +++ b/src/intrinsics/prepack/global.js @@ -34,6 +34,7 @@ import { CompilerDiagnostic, FatalError } from "../../errors.js"; import * as t from "@babel/types"; import { createOperationDescriptor, type OperationDescriptor } from "../../utils/generator.js"; import { createAndValidateArgModel } from "../../utils/ShapeInformation"; +import { PropertyDescriptor } from "../../descriptors.js"; export function createAbstractFunction(realm: Realm, ...additionalValues: Array): NativeFunctionValue { return new NativeFunctionValue( @@ -61,15 +62,18 @@ export function createAbstractFunction(realm: Realm, ...additionalValues: Array< export default function(realm: Realm): void { let global = realm.$GlobalObject; - global.$DefineOwnProperty("__dump", { - value: new NativeFunctionValue(realm, "global.__dump", "__dump", 0, (context, args) => { - console.log("dump", args.map(arg => arg.serialize())); - return context; - }), - writable: true, - enumerable: false, - configurable: true, - }); + global.$DefineOwnProperty( + "__dump", + new PropertyDescriptor({ + value: new NativeFunctionValue(realm, "global.__dump", "__dump", 0, (context, args) => { + console.log("dump", args.map(arg => arg.serialize())); + return context; + }), + writable: true, + enumerable: false, + configurable: true, + }) + ); // Helper function to model values that are obtained from the environment, // and whose concrete values are not known at Prepack-time. @@ -86,33 +90,45 @@ export default function(realm: Realm): void { // the abstract value's name. // If the abstract value gets somehow embedded in the final heap, // it will be referred to by the supplied name in the generated code. - global.$DefineOwnProperty("__abstract", { - value: createAbstractFunction(realm), - writable: true, - enumerable: false, - configurable: true, - }); - - global.$DefineOwnProperty("__abstractOrNull", { - value: createAbstractFunction(realm, realm.intrinsics.null), - writable: true, - enumerable: false, - configurable: true, - }); - - global.$DefineOwnProperty("__abstractOrNullOrUndefined", { - value: createAbstractFunction(realm, realm.intrinsics.null, realm.intrinsics.undefined), - writable: true, - enumerable: false, - configurable: true, - }); - - global.$DefineOwnProperty("__abstractOrUndefined", { - value: createAbstractFunction(realm, realm.intrinsics.undefined), - writable: true, - enumerable: false, - configurable: true, - }); + global.$DefineOwnProperty( + "__abstract", + new PropertyDescriptor({ + value: createAbstractFunction(realm), + writable: true, + enumerable: false, + configurable: true, + }) + ); + + global.$DefineOwnProperty( + "__abstractOrNull", + new PropertyDescriptor({ + value: createAbstractFunction(realm, realm.intrinsics.null), + writable: true, + enumerable: false, + configurable: true, + }) + ); + + global.$DefineOwnProperty( + "__abstractOrNullOrUndefined", + new PropertyDescriptor({ + value: createAbstractFunction(realm, realm.intrinsics.null, realm.intrinsics.undefined), + writable: true, + enumerable: false, + configurable: true, + }) + ); + + global.$DefineOwnProperty( + "__abstractOrUndefined", + new PropertyDescriptor({ + value: createAbstractFunction(realm, realm.intrinsics.undefined), + writable: true, + enumerable: false, + configurable: true, + }) + ); // Allows dynamically registering optimized functions. // WARNING: these functions will get exposed at global scope and called there. @@ -120,156 +136,180 @@ export default function(realm: Realm): void { // that is not subsequently applied, the function will not be registered // (because prepack won't have a correct value for the FunctionValue itself) // If we encounter an invalid input, we will emit a warning and not optimize the function - global.$DefineOwnProperty("__optimize", { - value: new NativeFunctionValue(realm, "global.__optimize", "__optimize", 1, (context, [value, argModelString]) => { - let argModel; - if (argModelString !== undefined) { - argModel = createAndValidateArgModel(realm, argModelString); - } - if (value instanceof ECMAScriptSourceFunctionValue || value instanceof AbstractValue) { - let currentArgModel = realm.optimizedFunctions.get(value); - // Verify that if there is an existing argModel, that it is the same as the new one. - if (currentArgModel) { - let currentString = argModelString instanceof StringValue ? argModelString.value : argModelString; - if (JSON.stringify(currentArgModel) !== currentString) { - let argModelError = new CompilerDiagnostic( - "__optimize called twice with different argModelStrings", - realm.currentLocation, - "PP1008", - "Warning" + global.$DefineOwnProperty( + "__optimize", + new PropertyDescriptor({ + value: new NativeFunctionValue( + realm, + "global.__optimize", + "__optimize", + 1, + (context, [value, argModelString]) => { + let argModel; + if (argModelString !== undefined) { + argModel = createAndValidateArgModel(realm, argModelString); + } + if (value instanceof ECMAScriptSourceFunctionValue || value instanceof AbstractValue) { + let currentArgModel = realm.optimizedFunctions.get(value); + // Verify that if there is an existing argModel, that it is the same as the new one. + if (currentArgModel) { + let currentString = argModelString instanceof StringValue ? argModelString.value : argModelString; + if (JSON.stringify(currentArgModel) !== currentString) { + let argModelError = new CompilerDiagnostic( + "__optimize called twice with different argModelStrings", + realm.currentLocation, + "PP1008", + "Warning" + ); + if (realm.handleError(argModelError) !== "Recover") throw new FatalError(); + else return realm.intrinsics.undefined; + } + } + realm.optimizedFunctions.set(value, argModel); + } else { + let location = value.expressionLocation + ? `${value.expressionLocation.start.line}:${value.expressionLocation.start.column} ` + + `${value.expressionLocation.end.line}:${value.expressionLocation.end.line}` + : "location unknown"; + let result = realm.handleError( + new CompilerDiagnostic( + `Optimized Function Value ${location} is an not a function or react element`, + realm.currentLocation, + "PP0033", + "Warning" + ) ); - if (realm.handleError(argModelError) !== "Recover") throw new FatalError(); + if (result !== "Recover") throw new FatalError(); else return realm.intrinsics.undefined; } + return value; } - realm.optimizedFunctions.set(value, argModel); - } else { - let location = value.expressionLocation - ? `${value.expressionLocation.start.line}:${value.expressionLocation.start.column} ` + - `${value.expressionLocation.end.line}:${value.expressionLocation.end.line}` - : "location unknown"; - let result = realm.handleError( - new CompilerDiagnostic( - `Optimized Function Value ${location} is an not a function or react element`, - realm.currentLocation, - "PP0033", - "Warning" - ) - ); - if (result !== "Recover") throw new FatalError(); - else return realm.intrinsics.undefined; - } - return value; - }), - writable: true, - enumerable: false, - configurable: true, - }); - - if (realm.react.enabled) { - global.$DefineOwnProperty("__reactComponentTrees", { - value: new ObjectValue( - realm, - realm.intrinsics.ObjectPrototype, - "__reactComponentTrees", - /* refuseSerialization */ true ), writable: true, enumerable: false, configurable: true, - }); + }) + ); + + if (realm.react.enabled) { + global.$DefineOwnProperty( + "__reactComponentTrees", + new PropertyDescriptor({ + value: new ObjectValue( + realm, + realm.intrinsics.ObjectPrototype, + "__reactComponentTrees", + /* refuseSerialization */ true + ), + writable: true, + enumerable: false, + configurable: true, + }) + ); let reactComponentRootUid = 0; - global.$DefineOwnProperty("__optimizeReactComponentTree", { + global.$DefineOwnProperty( + "__optimizeReactComponentTree", + new PropertyDescriptor({ + value: new NativeFunctionValue( + realm, + "global.__optimizeReactComponentTree", + "__optimizeReactComponentTree", + 0, + (context, [component, config]) => { + let hasValidComponent = + component instanceof ECMAScriptSourceFunctionValue || valueIsKnownReactAbstraction(realm, component); + let hasValidConfig = + config instanceof ObjectValue || config === realm.intrinsics.undefined || config === undefined; + + if (!hasValidComponent || !hasValidConfig) { + let diagnostic = new CompilerDiagnostic( + "__optimizeReactComponentTree(rootComponent, config) has been called with invalid arguments", + realm.currentLocation, + "PP0024", + "FatalError" + ); + realm.handleError(diagnostic); + if (realm.handleError(diagnostic) === "Fail") throw new FatalError(); + } + let reactComponentTree = new ObjectValue(realm, realm.intrinsics.ObjectPrototype); + reactComponentTree.$Set("rootComponent", component, reactComponentTree); + reactComponentTree.$Set("config", config || realm.intrinsics.undefined, reactComponentTree); + + realm.assignToGlobal( + t.memberExpression( + t.memberExpression(t.identifier("global"), t.identifier("__reactComponentTrees")), + t.identifier("" + reactComponentRootUid++) + ), + reactComponentTree + ); + return component; + } + ), + writable: true, + enumerable: false, + configurable: true, + }) + ); + } + + global.$DefineOwnProperty( + "__evaluatePureFunction", + new PropertyDescriptor({ value: new NativeFunctionValue( realm, - "global.__optimizeReactComponentTree", - "__optimizeReactComponentTree", + "global.__evaluatePureFunction", + "__evaluatePureFunction", 0, - (context, [component, config]) => { - let hasValidComponent = - component instanceof ECMAScriptSourceFunctionValue || valueIsKnownReactAbstraction(realm, component); - let hasValidConfig = - config instanceof ObjectValue || config === realm.intrinsics.undefined || config === undefined; - - if (!hasValidComponent || !hasValidConfig) { - let diagnostic = new CompilerDiagnostic( - "__optimizeReactComponentTree(rootComponent, config) has been called with invalid arguments", - realm.currentLocation, - "PP0024", - "FatalError" - ); - realm.handleError(diagnostic); - if (realm.handleError(diagnostic) === "Fail") throw new FatalError(); - } - let reactComponentTree = new ObjectValue(realm, realm.intrinsics.ObjectPrototype); - reactComponentTree.$Set("rootComponent", component, reactComponentTree); - reactComponentTree.$Set("config", config || realm.intrinsics.undefined, reactComponentTree); - - realm.assignToGlobal( - t.memberExpression( - t.memberExpression(t.identifier("global"), t.identifier("__reactComponentTrees")), - t.identifier("" + reactComponentRootUid++) - ), - reactComponentTree + (context, [functionValue]) => { + invariant(functionValue instanceof ECMAScriptSourceFunctionValue); + invariant(typeof functionValue.$Call === "function"); + let functionCall: Function = functionValue.$Call; + return realm.evaluatePure( + () => functionCall(realm.intrinsics.undefined, []), + /*bubbles*/ true, + /*reportSideEffectFunc*/ null ); - return component; } ), writable: true, enumerable: false, configurable: true, - }); - } - - global.$DefineOwnProperty("__evaluatePureFunction", { - value: new NativeFunctionValue( - realm, - "global.__evaluatePureFunction", - "__evaluatePureFunction", - 0, - (context, [functionValue]) => { - invariant(functionValue instanceof ECMAScriptSourceFunctionValue); - invariant(typeof functionValue.$Call === "function"); - let functionCall: Function = functionValue.$Call; - return realm.evaluatePure( - () => functionCall(realm.intrinsics.undefined, []), - /*bubbles*/ true, - /*reportSideEffectFunc*/ null - ); - } - ), - writable: true, - enumerable: false, - configurable: true, - }); + }) + ); // Maps from initialized moduleId to exports object // NB: Changes to this shouldn't ever be serialized - global.$DefineOwnProperty("__initializedModules", { - value: new ObjectValue( - realm, - realm.intrinsics.ObjectPrototype, - "__initializedModules", - /* refuseSerialization */ true - ), - writable: true, - enumerable: false, - configurable: true, - }); + global.$DefineOwnProperty( + "__initializedModules", + new PropertyDescriptor({ + value: new ObjectValue( + realm, + realm.intrinsics.ObjectPrototype, + "__initializedModules", + /* refuseSerialization */ true + ), + writable: true, + enumerable: false, + configurable: true, + }) + ); // Set of property bindings whose invariant got checked // NB: Changes to this shouldn't ever be serialized - global.$DefineOwnProperty("__checkedBindings", { - value: new ObjectValue( - realm, - realm.intrinsics.ObjectPrototype, - "__checkedBindings", - /* refuseSerialization */ true - ), - writable: true, - enumerable: false, - configurable: true, - }); + global.$DefineOwnProperty( + "__checkedBindings", + new PropertyDescriptor({ + value: new ObjectValue( + realm, + realm.intrinsics.ObjectPrototype, + "__checkedBindings", + /* refuseSerialization */ true + ), + writable: true, + enumerable: false, + configurable: true, + }) + ); // Helper function used to instantiate a residual function function createNativeFunctionForResidualCall(unsafe: boolean): NativeFunctionValue { @@ -328,29 +368,32 @@ export default function(realm: Realm): void { // Helper function that specifies a dynamic invariant that cannot be evaluated at prepack time, and needs code to // be injected into the serialized output. - global.$DefineOwnProperty("__assume", { - value: createNativeFunctionForResidualInjection( - "__assume", - ([c, s]): void => { - if (!c.mightBeTrue()) { - let error = new CompilerDiagnostic( - `Assumed condition cannot hold`, - realm.currentLocation, - "PP0040", - "FatalError" - ); - realm.handleError(error); - throw new FatalError(); - } - Path.pushAndRefine(c); - }, - createOperationDescriptor("ASSUME_CALL"), - 2 - ), - writable: true, - enumerable: false, - configurable: true, - }); + global.$DefineOwnProperty( + "__assume", + new PropertyDescriptor({ + value: createNativeFunctionForResidualInjection( + "__assume", + ([c, s]): void => { + if (!c.mightBeTrue()) { + let error = new CompilerDiagnostic( + `Assumed condition cannot hold`, + realm.currentLocation, + "PP0040", + "FatalError" + ); + realm.handleError(error); + throw new FatalError(); + } + Path.pushAndRefine(c); + }, + createOperationDescriptor("ASSUME_CALL"), + 2 + ), + writable: true, + enumerable: false, + configurable: true, + }) + ); // Helper function that identifies a computation that must remain part of the residual program and cannot be partially evaluated, // e.g. because it contains a loop over abstract values. @@ -358,190 +401,226 @@ export default function(realm: Realm): void { // that is computed by invoking function(arg0, arg1, ...) in the residual program and // where typeNameOrTemplate either either 'string', 'boolean', 'number', 'object', or an actual object defining known properties. // The function must not have side effects, and it must not access any state (besides the supplied arguments). - global.$DefineOwnProperty("__residual", { - value: createNativeFunctionForResidualCall(false), - writable: true, - enumerable: false, - configurable: true, - }); + global.$DefineOwnProperty( + "__residual", + new PropertyDescriptor({ + value: createNativeFunctionForResidualCall(false), + writable: true, + enumerable: false, + configurable: true, + }) + ); // Helper function that identifies a variant of the residual function that has implicit dependencies. This version of residual will infer the dependencies // and rewrite the function body to do the same thing as the original residual function. - global.$DefineOwnProperty("__residual_unsafe", { - value: createNativeFunctionForResidualCall(true), - writable: true, - enumerable: false, - configurable: true, - }); + global.$DefineOwnProperty( + "__residual_unsafe", + new PropertyDescriptor({ + value: createNativeFunctionForResidualCall(true), + writable: true, + enumerable: false, + configurable: true, + }) + ); // Internal helper function for tests. // __isAbstract(value) checks if a given value is abstract. - global.$DefineOwnProperty("__isAbstract", { - value: new NativeFunctionValue(realm, "global.__isAbstract", "__isAbstract", 1, (context, [value]) => { - return new BooleanValue(realm, value instanceof AbstractValue); - }), - writable: true, - enumerable: false, - configurable: true, - }); + global.$DefineOwnProperty( + "__isAbstract", + new PropertyDescriptor({ + value: new NativeFunctionValue(realm, "global.__isAbstract", "__isAbstract", 1, (context, [value]) => { + return new BooleanValue(realm, value instanceof AbstractValue); + }), + writable: true, + enumerable: false, + configurable: true, + }) + ); // __makePartial(object) marks an (abstract) object as partial. - global.$DefineOwnProperty("__makePartial", { - value: new NativeFunctionValue(realm, "global.__makePartial", "__makePartial", 1, (context, [object]) => { - if (object instanceof AbstractObjectValue || object instanceof ObjectValue) { - object.makePartial(); - return object; - } - throw realm.createErrorThrowCompletion(realm.intrinsics.TypeError, "not an (abstract) object"); - }), - writable: true, - enumerable: false, - configurable: true, - }); - - global.$DefineOwnProperty("__makeFinal", { - value: new NativeFunctionValue(realm, "global.__makeFinal", "__makeFinal", 1, (context, [object]) => { - if (object instanceof ObjectValue || (object instanceof AbstractObjectValue && !object.values.isTop())) { - object.makeFinal(); - return object; - } - throw realm.createErrorThrowCompletion( - realm.intrinsics.TypeError, - "not an object or abstract object value (non-top)" - ); - }), - writable: true, - enumerable: false, - configurable: true, - }); + global.$DefineOwnProperty( + "__makePartial", + new PropertyDescriptor({ + value: new NativeFunctionValue(realm, "global.__makePartial", "__makePartial", 1, (context, [object]) => { + if (object instanceof AbstractObjectValue || object instanceof ObjectValue) { + object.makePartial(); + return object; + } + throw realm.createErrorThrowCompletion(realm.intrinsics.TypeError, "not an (abstract) object"); + }), + writable: true, + enumerable: false, + configurable: true, + }) + ); + + global.$DefineOwnProperty( + "__makeFinal", + new PropertyDescriptor({ + value: new NativeFunctionValue(realm, "global.__makeFinal", "__makeFinal", 1, (context, [object]) => { + if (object instanceof ObjectValue || (object instanceof AbstractObjectValue && !object.values.isTop())) { + object.makeFinal(); + return object; + } + throw realm.createErrorThrowCompletion( + realm.intrinsics.TypeError, + "not an object or abstract object value (non-top)" + ); + }), + writable: true, + enumerable: false, + configurable: true, + }) + ); // __makeSimple(object) marks an (abstract) object as one that has no getters or setters. - global.$DefineOwnProperty("__makeSimple", { - value: new NativeFunctionValue(realm, "global.__makeSimple", "__makeSimple", 1, (context, [object, option]) => { - if (object instanceof AbstractObjectValue || object instanceof ObjectValue) { - object.makeSimple(option); - return object; - } - throw realm.createErrorThrowCompletion(realm.intrinsics.TypeError, "not an (abstract) object"); - }), - writable: true, - enumerable: false, - configurable: true, - }); + global.$DefineOwnProperty( + "__makeSimple", + new PropertyDescriptor({ + value: new NativeFunctionValue(realm, "global.__makeSimple", "__makeSimple", 1, (context, [object, option]) => { + if (object instanceof AbstractObjectValue || object instanceof ObjectValue) { + object.makeSimple(option); + return object; + } + throw realm.createErrorThrowCompletion(realm.intrinsics.TypeError, "not an (abstract) object"); + }), + writable: true, + enumerable: false, + configurable: true, + }) + ); // Helper function that emits a check whether a given object property has a particular value. - global.$DefineOwnProperty("__assumeDataProperty", { - value: new NativeFunctionValue( - realm, - "global.__assumeDataProperty", - "__assumeDataProperty", - 3, - (context, [object, propertyName, value, invariantOptions]) => { - if (!realm.useAbstractInterpretation) { - throw realm.createErrorThrowCompletion(realm.intrinsics.TypeError, "realm is not partial"); - } + global.$DefineOwnProperty( + "__assumeDataProperty", + new PropertyDescriptor({ + value: new NativeFunctionValue( + realm, + "global.__assumeDataProperty", + "__assumeDataProperty", + 3, + (context, [object, propertyName, value, invariantOptions]) => { + if (!realm.useAbstractInterpretation) { + throw realm.createErrorThrowCompletion(realm.intrinsics.TypeError, "realm is not partial"); + } - if (object instanceof AbstractObjectValue || object instanceof ObjectValue) { - let generator = realm.generator; - invariant(generator); - - let key = To.ToStringPartial(realm, propertyName); - - if (realm.emitConcreteModel) { - generator.emitConcreteModel(key, value); - } else if (realm.invariantLevel >= 1) { - let invariantOptionString = invariantOptions - ? To.ToStringPartial(realm, invariantOptions) - : "FULL_INVARIANT"; - switch (invariantOptionString) { - // checks (!property in object || object.property === undefined) - case "VALUE_DEFINED_INVARIANT": - generator.emitPropertyInvariant(object, key, value.mightBeUndefined() ? "PRESENT" : "DEFINED"); - break; - case "SKIP_INVARIANT": - break; - case "FULL_INVARIANT": - generator.emitFullInvariant((object: any), key, value); - break; - default: - invariant(false, "Invalid invariantOption " + invariantOptionString); + if (object instanceof AbstractObjectValue || object instanceof ObjectValue) { + let generator = realm.generator; + invariant(generator); + + let key = To.ToStringPartial(realm, propertyName); + + if (realm.emitConcreteModel) { + generator.emitConcreteModel(key, value); + } else if (realm.invariantLevel >= 1) { + let invariantOptionString = invariantOptions + ? To.ToStringPartial(realm, invariantOptions) + : "FULL_INVARIANT"; + switch (invariantOptionString) { + // checks (!property in object || object.property === undefined) + case "VALUE_DEFINED_INVARIANT": + generator.emitPropertyInvariant(object, key, value.mightBeUndefined() ? "PRESENT" : "DEFINED"); + break; + case "SKIP_INVARIANT": + break; + case "FULL_INVARIANT": + generator.emitFullInvariant((object: any), key, value); + break; + default: + invariant(false, "Invalid invariantOption " + invariantOptionString); + } + if (!realm.neverCheckProperty(object, key)) realm.markPropertyAsChecked(object, key); } - if (!realm.neverCheckProperty(object, key)) realm.markPropertyAsChecked(object, key); + realm.generator = undefined; // don't emit code during the following $Set call + // casting to due to Flow workaround above + (object: any).$Set(key, value, object); + realm.generator = generator; + if (object.intrinsicName) realm.rebuildObjectProperty(object, key, value, object.intrinsicName); + return context.$Realm.intrinsics.undefined; } - realm.generator = undefined; // don't emit code during the following $Set call - // casting to due to Flow workaround above - (object: any).$Set(key, value, object); - realm.generator = generator; - if (object.intrinsicName) realm.rebuildObjectProperty(object, key, value, object.intrinsicName); - return context.$Realm.intrinsics.undefined; - } - throw realm.createErrorThrowCompletion(realm.intrinsics.TypeError, "not an (abstract) object"); - } - ), - writable: true, - enumerable: false, - configurable: true, - }); - - global.$DefineOwnProperty("__IntrospectionError", { - value: realm.intrinsics.__IntrospectionError, - writable: true, - enumerable: false, - configurable: true, - }); - - global.$DefineOwnProperty("__isIntegral", { - value: new NativeFunctionValue(realm, "global.__isIntegral", "__isIntegral", 1, (context, [value]) => { - return new BooleanValue(realm, value instanceof IntegralValue); - }), - writable: true, - enumerable: false, - configurable: true, - }); - - global.$DefineOwnProperty("__describe", { - value: new NativeFunctionValue(realm, "global.__describe", "__describe", 1, (context, [value]) => { - return new StringValue(realm, describeValue(value)); - }), - writable: true, - enumerable: false, - configurable: true, - }); - - global.$DefineOwnProperty("__fatal", { - value: new NativeFunctionValue(realm, "global.__fatal", "__fatal", 0, (context, []) => { - throw new FatalError(); - }), - writable: true, - enumerable: false, - configurable: true, - }); - - global.$DefineOwnProperty("__eagerlyRequireModuleDependencies", { - value: new NativeFunctionValue( - realm, - "global.__eagerlyRequireModuleDependencies", - "__eagerlyRequireModuleDependencies", - 1, - (context, [functionValue]) => { - if (!IsCallable(realm, functionValue) || !(functionValue instanceof FunctionValue)) - throw realm.createErrorThrowCompletion(realm.intrinsics.TypeError, "argument must be callable function"); - let functionCall: void | ((thisArgument: Value, argumentsList: Array) => Value) = functionValue.$Call; - if (typeof functionCall !== "function") { - throw realm.createErrorThrowCompletion(realm.intrinsics.TypeError, "argument must be directly callable"); + throw realm.createErrorThrowCompletion(realm.intrinsics.TypeError, "not an (abstract) object"); } - let old = realm.eagerlyRequireModuleDependencies; - realm.eagerlyRequireModuleDependencies = true; - try { - return functionCall(realm.intrinsics.undefined, []); - } finally { - realm.eagerlyRequireModuleDependencies = old; + ), + writable: true, + enumerable: false, + configurable: true, + }) + ); + + global.$DefineOwnProperty( + "__IntrospectionError", + new PropertyDescriptor({ + value: realm.intrinsics.__IntrospectionError, + writable: true, + enumerable: false, + configurable: true, + }) + ); + + global.$DefineOwnProperty( + "__isIntegral", + new PropertyDescriptor({ + value: new NativeFunctionValue(realm, "global.__isIntegral", "__isIntegral", 1, (context, [value]) => { + return new BooleanValue(realm, value instanceof IntegralValue); + }), + writable: true, + enumerable: false, + configurable: true, + }) + ); + + global.$DefineOwnProperty( + "__describe", + new PropertyDescriptor({ + value: new NativeFunctionValue(realm, "global.__describe", "__describe", 1, (context, [value]) => { + return new StringValue(realm, describeValue(value)); + }), + writable: true, + enumerable: false, + configurable: true, + }) + ); + + global.$DefineOwnProperty( + "__fatal", + new PropertyDescriptor({ + value: new NativeFunctionValue(realm, "global.__fatal", "__fatal", 0, (context, []) => { + throw new FatalError(); + }), + writable: true, + enumerable: false, + configurable: true, + }) + ); + + global.$DefineOwnProperty( + "__eagerlyRequireModuleDependencies", + new PropertyDescriptor({ + value: new NativeFunctionValue( + realm, + "global.__eagerlyRequireModuleDependencies", + "__eagerlyRequireModuleDependencies", + 1, + (context, [functionValue]) => { + if (!IsCallable(realm, functionValue) || !(functionValue instanceof FunctionValue)) + throw realm.createErrorThrowCompletion(realm.intrinsics.TypeError, "argument must be callable function"); + let functionCall: void | ((thisArgument: Value, argumentsList: Array) => Value) = functionValue.$Call; + if (typeof functionCall !== "function") { + throw realm.createErrorThrowCompletion(realm.intrinsics.TypeError, "argument must be directly callable"); + } + let old = realm.eagerlyRequireModuleDependencies; + realm.eagerlyRequireModuleDependencies = true; + try { + return functionCall(realm.intrinsics.undefined, []); + } finally { + realm.eagerlyRequireModuleDependencies = old; + } } - } - ), - writable: true, - enumerable: false, - configurable: true, - }); + ), + writable: true, + enumerable: false, + configurable: true, + }) + ); } diff --git a/src/intrinsics/react-native/global.js b/src/intrinsics/react-native/global.js index 87a91c9ae5..ff9eb5e101 100644 --- a/src/intrinsics/react-native/global.js +++ b/src/intrinsics/react-native/global.js @@ -12,17 +12,21 @@ import type { Realm } from "../../realm.js"; import initializeConsole from "../common/console.js"; +import { PropertyDescriptor } from "../../descriptors.js"; export default function(realm: Realm): void { let global = realm.$GlobalObject; if (!realm.isCompatibleWith(realm.MOBILE_JSC_VERSION) && !realm.isCompatibleWith("mobile")) - global.$DefineOwnProperty("console", { - value: initializeConsole(realm), - writable: true, - enumerable: false, - configurable: true, - }); + global.$DefineOwnProperty( + "console", + new PropertyDescriptor({ + value: initializeConsole(realm), + writable: true, + enumerable: false, + configurable: true, + }) + ); for (let name of [ "document", @@ -58,11 +62,14 @@ export default function(realm: Realm): void { "FileReader", "XMLHttpRequest", ]) { - global.$DefineOwnProperty(name, { - value: realm.intrinsics.undefined, - writable: true, - enumerable: false, - configurable: true, - }); + global.$DefineOwnProperty( + name, + new PropertyDescriptor({ + value: realm.intrinsics.undefined, + writable: true, + enumerable: false, + configurable: true, + }) + ); } } diff --git a/src/methods/construct.js b/src/methods/construct.js index 979f6dc276..854094b33b 100644 --- a/src/methods/construct.js +++ b/src/methods/construct.js @@ -25,6 +25,7 @@ import { HasSomeCompatibleType } from "./has.js"; import { Create, Properties } from "../singletons.js"; import invariant from "../invariant.js"; import type { BabelNodeClassMethod } from "@babel/types"; +import { PropertyDescriptor } from "../descriptors.js"; // ECMA262 9.2.8 export function MakeConstructor( @@ -56,21 +57,31 @@ export function MakeConstructor( prototype.originalConstructor = F; // b. Perform ! DefinePropertyOrThrow(prototype, "constructor", PropertyDescriptor{[[Value]]: F, [[Writable]]: writablePrototype, [[Enumerable]]: false, [[Configurable]]: true }). - Properties.DefinePropertyOrThrow(realm, prototype, "constructor", { - value: F, - writable: writablePrototype, - enumerable: false, - configurable: true, - }); + Properties.DefinePropertyOrThrow( + realm, + prototype, + "constructor", + new PropertyDescriptor({ + value: F, + writable: writablePrototype, + enumerable: false, + configurable: true, + }) + ); } // 6. Perform ! DefinePropertyOrThrow(F, "prototype", PropertyDescriptor{[[Value]]: prototype, [[Writable]]: writablePrototype, [[Enumerable]]: false, [[Configurable]]: false}). - Properties.DefinePropertyOrThrow(realm, F, "prototype", { - value: prototype, - writable: writablePrototype, - enumerable: false, - configurable: false, - }); + Properties.DefinePropertyOrThrow( + realm, + F, + "prototype", + new PropertyDescriptor({ + value: prototype, + writable: writablePrototype, + enumerable: false, + configurable: false, + }) + ); // 7. Return NormalCompletion(undefined). return realm.intrinsics.undefined; diff --git a/src/methods/create.js b/src/methods/create.js index 8586ab3b0a..97f2ee2254 100644 --- a/src/methods/create.js +++ b/src/methods/create.js @@ -38,6 +38,7 @@ import invariant from "../invariant.js"; import parse from "../utils/parse.js"; import traverseFast from "../utils/traverse-fast.js"; import type { BabelNodeIdentifier, BabelNodeLVal, BabelNodeFunctionDeclaration } from "@babel/types"; +import { PropertyDescriptor } from "../descriptors.js"; const allElementTypes = ["Undefined", "Null", "Boolean", "String", "Symbol", "Number", "Object"]; @@ -69,12 +70,17 @@ export class CreateImplementation { let length = value.value.length; // 10. Perform ! DefinePropertyOrThrow(S, "length", PropertyDescriptor{[[Value]]: length, [[Writable]]: false, [[Enumerable]]: false, [[Configurable]]: false }). - Properties.DefinePropertyOrThrow(realm, S, "length", { - value: new NumberValue(realm, length), - writable: false, - enumerable: false, - configurable: false, - }); + Properties.DefinePropertyOrThrow( + realm, + S, + "length", + new PropertyDescriptor({ + value: new NumberValue(realm, length), + writable: false, + enumerable: false, + configurable: false, + }) + ); // 11. Return S. return S; @@ -307,12 +313,17 @@ export class CreateImplementation { A.setExtensible(true); // 10. Perform ! OrdinaryDefineOwnProperty(A, "length", PropertyDescriptor{[[Value]]: length, [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: false}). - Properties.OrdinaryDefineOwnProperty(realm, A, "length", { - value: new NumberValue(realm, length), - writable: true, - enumerable: false, - configurable: false, - }); + Properties.OrdinaryDefineOwnProperty( + realm, + A, + "length", + new PropertyDescriptor({ + value: new NumberValue(realm, length), + writable: true, + enumerable: false, + configurable: false, + }) + ); // 11. Return A. return A; @@ -358,12 +369,17 @@ export class CreateImplementation { // 4. Perform DefinePropertyOrThrow(obj, "length", PropertyDescriptor{[[Value]]: len, // [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: true}). - Properties.DefinePropertyOrThrow(realm, obj, "length", { - value: new NumberValue(realm, len), - writable: true, - enumerable: false, - configurable: true, - }); + Properties.DefinePropertyOrThrow( + realm, + obj, + "length", + new PropertyDescriptor({ + value: new NumberValue(realm, len), + writable: true, + enumerable: false, + configurable: true, + }) + ); // 5. Let index be 0. let index = 0; @@ -382,21 +398,31 @@ export class CreateImplementation { // 7. Perform ! DefinePropertyOrThrow(obj, @@iterator, PropertyDescriptor {[[Value]]: // %ArrayProto_values%, [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: true}). - Properties.DefinePropertyOrThrow(realm, obj, realm.intrinsics.SymbolIterator, { - value: realm.intrinsics.ArrayProto_values, - writable: true, - enumerable: false, - configurable: true, - }); + Properties.DefinePropertyOrThrow( + realm, + obj, + realm.intrinsics.SymbolIterator, + new PropertyDescriptor({ + value: realm.intrinsics.ArrayProto_values, + writable: true, + enumerable: false, + configurable: true, + }) + ); // 8. Perform ! DefinePropertyOrThrow(obj, "callee", PropertyDescriptor {[[Get]]: // %ThrowTypeError%, [[Set]]: %ThrowTypeError%, [[Enumerable]]: false, [[Configurable]]: false}). - Properties.DefinePropertyOrThrow(realm, obj, "callee", { - get: realm.intrinsics.ThrowTypeError, - set: realm.intrinsics.ThrowTypeError, - enumerable: false, - configurable: false, - }); + Properties.DefinePropertyOrThrow( + realm, + obj, + "callee", + new PropertyDescriptor({ + get: realm.intrinsics.ThrowTypeError, + set: realm.intrinsics.ThrowTypeError, + enumerable: false, + configurable: false, + }) + ); // 10. Return obj. return obj; @@ -442,7 +468,7 @@ export class CreateImplementation { obj.setExtensible(true); // 12. Let map be ObjectCreate(null). - let map = new ObjectValue(realm); + let map: ObjectValue = new ObjectValue(realm); // 13. Set the [[ParameterMap]] internal slot of obj to map. obj.$ParameterMap = map; @@ -473,12 +499,17 @@ export class CreateImplementation { // 18. Perform DefinePropertyOrThrow(obj, "length", PropertyDescriptor{[[Value]]: len, // [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: true}). - Properties.DefinePropertyOrThrow(realm, obj, "length", { - value: new NumberValue(realm, len), - writable: true, - enumerable: false, - configurable: true, - }); + Properties.DefinePropertyOrThrow( + realm, + obj, + "length", + new PropertyDescriptor({ + value: new NumberValue(realm, len), + writable: true, + enumerable: false, + configurable: true, + }) + ); // 19. Let mappedNames be an empty List. let mappedNames = []; @@ -506,12 +537,15 @@ export class CreateImplementation { // 3. Perform map.[[DefineOwnProperty]](! ToString(index), PropertyDescriptor{[[Set]]: p, [[Get]]: g, // [[Enumerable]]: false, [[Configurable]]: true}). - map.$DefineOwnProperty(new StringValue(realm, index + ""), { - set: p, - get: g, - enumerable: false, - configurable: true, - }); + map.$DefineOwnProperty( + new StringValue(realm, index + ""), + new PropertyDescriptor({ + set: p, + get: g, + enumerable: false, + configurable: true, + }) + ); } } @@ -521,21 +555,31 @@ export class CreateImplementation { // 22. Perform ! DefinePropertyOrThrow(obj, @@iterator, PropertyDescriptor {[[Value]]: // %ArrayProto_values%, [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: true}). - Properties.DefinePropertyOrThrow(realm, obj, realm.intrinsics.SymbolIterator, { - value: realm.intrinsics.ArrayProto_values, - writable: true, - enumerable: false, - configurable: true, - }); + Properties.DefinePropertyOrThrow( + realm, + obj, + realm.intrinsics.SymbolIterator, + new PropertyDescriptor({ + value: realm.intrinsics.ArrayProto_values, + writable: true, + enumerable: false, + configurable: true, + }) + ); // 23. Perform ! DefinePropertyOrThrow(obj, "callee", PropertyDescriptor {[[Value]]: // func, [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: true}). - Properties.DefinePropertyOrThrow(realm, obj, "callee", { - value: func, - writable: true, - enumerable: false, - configurable: true, - }); + Properties.DefinePropertyOrThrow( + realm, + obj, + "callee", + new PropertyDescriptor({ + value: func, + writable: true, + enumerable: false, + configurable: true, + }) + ); // 24. Return obj. return obj; @@ -549,12 +593,12 @@ export class CreateImplementation { invariant(IsPropertyKey(realm, P), "Not a property key"); // 3. Let newDesc be the PropertyDescriptor{[[Value]]: V, [[Writable]]: true, [[Enumerable]]: true, [[Configurable]]: true}. - let newDesc = { + let newDesc = new PropertyDescriptor({ value: V, writable: true, enumerable: true, configurable: true, - }; + }); // 4. Return ? O.[[DefineOwnProperty]](P, newDesc). return O.$DefineOwnProperty(P, newDesc); @@ -601,7 +645,7 @@ export class CreateImplementation { let desc = from.$GetOwnProperty(nextKey); // If desc is not undefined and desc.[[Enumerable]] is true, then - if (desc !== undefined && desc.enumerable === true) { + if (desc !== undefined && desc.throwIfNotConcrete(realm).enumerable === true) { // Let propValue be ? Get(from, nextKey). let propValue = Get(realm, from, nextKey); // Perform ! CreateDataProperty(target, nextKey, propValue). @@ -624,12 +668,12 @@ export class CreateImplementation { invariant(IsPropertyKey(realm, P), "Not a property key"); // 3. Let newDesc be the PropertyDescriptor{[[Value]]: V, [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: true}. - let newDesc = { + let newDesc = new PropertyDescriptor({ value: V, writable: true, enumerable: false, configurable: true, - }; + }); // 4. Return ? O.[[DefineOwnProperty]](P, newDesc). return O.$DefineOwnProperty(P, newDesc); @@ -903,12 +947,17 @@ export class CreateImplementation { prototype.originalConstructor = F; // b. Perform DefinePropertyOrThrow(F, "prototype", PropertyDescriptor{[[Value]]: prototype, [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: false}). - Properties.DefinePropertyOrThrow(realm, F, "prototype", { - value: prototype, - writable: true, - enumerable: false, - configurable: false, - }); + Properties.DefinePropertyOrThrow( + realm, + F, + "prototype", + new PropertyDescriptor({ + value: prototype, + writable: true, + enumerable: false, + configurable: false, + }) + ); } else { // 28. Else, perform MakeConstructor(F). MakeConstructor(realm, F); diff --git a/src/methods/descriptor.js b/src/methods/descriptor.js deleted file mode 100644 index fc41ba5a75..0000000000 --- a/src/methods/descriptor.js +++ /dev/null @@ -1,68 +0,0 @@ -/** - * 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 strict-local */ - -import type { Descriptor } from "../types.js"; - -export function cloneDescriptor(d: void | Descriptor): void | Descriptor { - if (d === undefined) return undefined; - let clone = {}; - copyDescriptor(d, clone); - return clone; -} - -export function clearDescriptor(d: Descriptor): void { - delete d.writable; - delete d.enumerable; - delete d.configurable; - delete d.value; - delete d.get; - delete d.set; -} - -export function copyDescriptor(from: Descriptor, to: Descriptor): void { - if (from.hasOwnProperty("writable")) to.writable = from.writable; - if (from.hasOwnProperty("enumerable")) to.enumerable = from.enumerable; - if (from.hasOwnProperty("configurable")) to.configurable = from.configurable; - if (from.hasOwnProperty("value")) { - if (from.value instanceof Array) to.value = from.value.slice(); - else to.value = from.value; - } - if (from.hasOwnProperty("get")) to.get = from.get; - if (from.hasOwnProperty("set")) to.set = from.set; -} - -// does not check if the contents of value properties are the same -export function equalDescriptors(d1: Descriptor, d2: Descriptor): boolean { - if (d1.hasOwnProperty("writable")) { - if (!d2.hasOwnProperty("writable")) return false; - if (d1.writable !== d2.writable) return false; - } - if (d1.hasOwnProperty("enumerable")) { - if (!d2.hasOwnProperty("enumerable")) return false; - if (d1.enumerable !== d2.enumerable) return false; - } - if (d1.hasOwnProperty("configurable")) { - if (!d2.hasOwnProperty("configurable")) return false; - if (d1.configurable !== d2.configurable) return false; - } - if (d1.hasOwnProperty("value")) { - if (!d2.hasOwnProperty("value")) return false; - } - if (d1.hasOwnProperty("get")) { - if (!d2.hasOwnProperty("get")) return false; - if (d1.get !== d2.get) return false; - } - if (d1.hasOwnProperty("set")) { - if (!d2.hasOwnProperty("set")) return false; - if (d1.set !== d2.set) return false; - } - return true; -} diff --git a/src/methods/function.js b/src/methods/function.js index 18f92dea60..af518ecc0d 100644 --- a/src/methods/function.js +++ b/src/methods/function.js @@ -68,6 +68,7 @@ import type { BabelNodeWithStatement, } from "@babel/types"; import * as t from "@babel/types"; +import { PropertyDescriptor } from "../descriptors.js"; function InternalCall( realm: Realm, @@ -734,12 +735,17 @@ export class FunctionImplementation { } // 6. Return ! DefinePropertyOrThrow(F, "name", PropertyDescriptor{[[Value]]: name, [[Writable]]: false, [[Enumerable]]: false, [[Configurable]]: true}). - return Properties.DefinePropertyOrThrow(realm, F, "name", { - value: name, - enumerable: false, - writable: false, - configurable: true, - }); + return Properties.DefinePropertyOrThrow( + realm, + F, + "name", + new PropertyDescriptor({ + value: name, + enumerable: false, + writable: false, + configurable: true, + }) + ); } // ECMA262 9.2.3 @@ -770,22 +776,32 @@ export class FunctionImplementation { } // 3. Perform ! DefinePropertyOrThrow(F, "length", PropertyDescriptor{[[Value]]: len, [[Writable]]: false, [[Enumerable]]: false, [[Configurable]]: true}). - Properties.DefinePropertyOrThrow(realm, F, "length", { - value: new NumberValue(realm, len), - writable: false, - enumerable: false, - configurable: true, - }); + Properties.DefinePropertyOrThrow( + realm, + F, + "length", + new PropertyDescriptor({ + value: new NumberValue(realm, len), + writable: false, + enumerable: false, + configurable: true, + }) + ); // 4. Let Strict be the value of the [[Strict]] internal slot of F. let Strict = F.$Strict; if (!Strict) { - Properties.DefinePropertyOrThrow(realm, F, "caller", { - value: new UndefinedValue(realm), - writable: true, - enumerable: false, - configurable: true, - }); + Properties.DefinePropertyOrThrow( + realm, + F, + "caller", + new PropertyDescriptor({ + value: new UndefinedValue(realm), + writable: true, + enumerable: false, + configurable: true, + }) + ); } // 5. Set the [[Environment]] internal slot of F to the value of Scope. @@ -838,12 +854,12 @@ export class FunctionImplementation { let thrower = realm.intrinsics.ThrowTypeError; invariant(thrower); - let desc = { + let desc = new PropertyDescriptor({ get: thrower, set: thrower, enumerable: false, configurable: true, - }; + }); // 3. Perform ! DefinePropertyOrThrow(F, "caller", PropertyDescriptor {[[Get]]: thrower, [[Set]]: thrower, [[Enumerable]]: false, [[Configurable]]: true}). Properties.DefinePropertyOrThrow(realm, F, "caller", desc); // 4. Return ! DefinePropertyOrThrow(F, "arguments", PropertyDescriptor {[[Get]]: thrower, [[Set]]: thrower, [[Enumerable]]: false, [[Configurable]]: true}). @@ -1200,12 +1216,17 @@ export class FunctionImplementation { // Note: "arguments" ***MUST NOT*** be set if the function is in strict mode or is an arrow, method, constructor, or generator function. // See 16.2 "Forbidden Extensions" if (!Strict && kind === "normal") { - Properties.DefinePropertyOrThrow(realm, F, "arguments", { - value: realm.intrinsics.undefined, - enumerable: false, - writable: true, - configurable: true, - }); + Properties.DefinePropertyOrThrow( + realm, + F, + "arguments", + new PropertyDescriptor({ + value: realm.intrinsics.undefined, + enumerable: false, + writable: true, + configurable: true, + }) + ); } // 5. Return FunctionInitialize(F, kind, ParameterList, Body, Scope). diff --git a/src/methods/get.js b/src/methods/get.js index da4bd14e15..c3f3ce8f3d 100644 --- a/src/methods/get.js +++ b/src/methods/get.js @@ -42,6 +42,7 @@ import { Create, Environment, Join, Leak, Path, To } from "../singletons.js"; import invariant from "../invariant.js"; import type { BabelNodeTemplateLiteral } from "@babel/types"; import { createOperationDescriptor } from "../utils/generator.js"; +import { PropertyDescriptor, AbstractJoinedDescriptor } from "../descriptors.js"; // ECMA262 7.3.22 export function GetFunctionRealm(realm: Realm, obj: ObjectValue): Realm { @@ -97,7 +98,10 @@ export function OrdinaryGet( let prop = O.unknownProperty; if (prop !== undefined && prop.descriptor !== undefined && O.$GetOwnProperty(P) === undefined) { let desc = prop.descriptor; - invariant(desc !== undefined); + invariant( + desc instanceof PropertyDescriptor, + "unknown properties are only created with Set and have equal descriptors" + ); let val = desc.value; invariant(val instanceof AbstractValue); let propValue; @@ -129,7 +133,7 @@ export function OrdinaryGet( // 2. Let desc be ? O.[[GetOwnProperty]](P). let desc = O.$GetOwnProperty(P); - if (desc === undefined || desc.joinCondition === undefined) return OrdinaryGetHelper(); + if (desc === undefined || !(desc instanceof AbstractJoinedDescriptor)) return OrdinaryGetHelper(); // joined descriptors need special treatment let joinCondition = desc.joinCondition; @@ -422,6 +426,10 @@ export function OrdinaryGetPartial( if (prop !== undefined) { let desc = prop.descriptor; if (desc !== undefined) { + invariant( + desc instanceof PropertyDescriptor, + "unknown properties are only created with Set and have equal descriptors" + ); let val = desc.value; invariant(val instanceof AbstractValue); if (val.kind === "widened numeric property") { @@ -437,6 +445,7 @@ export function OrdinaryGetPartial( for (let [key, propertyBinding] of O.properties) { let desc = propertyBinding.descriptor; if (desc === undefined) continue; // deleted + desc = desc.throwIfNotConcrete(realm); // TODO: Join descriptor values based on condition invariant(desc.value !== undefined); // otherwise this is not simple let val = desc.value; invariant(val instanceof Value); @@ -717,23 +726,29 @@ export function GetTemplateObject(realm: Realm, templateLiteral: BabelNodeTempla let cookedValue = new StringValue(realm, cookedStrings[index]); // c. Call template.[[DefineOwnProperty]](prop, PropertyDescriptor{[[Value]]: cookedValue, [[Writable]]: false, [[Enumerable]]: true, [[Configurable]]: false}). - template.$DefineOwnProperty(prop, { - value: cookedValue, - writable: false, - enumerable: true, - configurable: false, - }); + template.$DefineOwnProperty( + prop, + new PropertyDescriptor({ + value: cookedValue, + writable: false, + enumerable: true, + configurable: false, + }) + ); // d. Let rawValue be the String value rawStrings[index]. let rawValue = new StringValue(realm, rawStrings[index]); // e. Call rawObj.[[DefineOwnProperty]](prop, PropertyDescriptor{[[Value]]: rawValue, [[Writable]]: false, [[Enumerable]]: true, [[Configurable]]: false}). - rawObj.$DefineOwnProperty(prop, { - value: rawValue, - writable: false, - enumerable: true, - configurable: false, - }); + rawObj.$DefineOwnProperty( + prop, + new PropertyDescriptor({ + value: rawValue, + writable: false, + enumerable: true, + configurable: false, + }) + ); // f. Let index be index+1. index = index + 1; @@ -743,12 +758,15 @@ export function GetTemplateObject(realm: Realm, templateLiteral: BabelNodeTempla SetIntegrityLevel(realm, rawObj, "frozen"); // 12. Call template.[[DefineOwnProperty]]("raw", PropertyDescriptor{[[Value]]: rawObj, [[Writable]]: false, [[Enumerable]]: false, [[Configurable]]: false}). - template.$DefineOwnProperty("raw", { - value: rawObj, - writable: false, - enumerable: false, - configurable: false, - }); + template.$DefineOwnProperty( + "raw", + new PropertyDescriptor({ + value: rawObj, + writable: false, + enumerable: false, + configurable: false, + }) + ); // 13. Perform SetIntegrityLevel(template, "frozen"). SetIntegrityLevel(realm, template, "frozen"); @@ -781,7 +799,7 @@ export function GetFromArrayWithWidenedNumericProperty( if (prototypeBinding !== undefined) { let descriptor = prototypeBinding.descriptor; // ensure we are accessing a built-in native function - if (descriptor !== undefined && descriptor.value instanceof NativeFunctionValue) { + if (descriptor instanceof PropertyDescriptor && descriptor.value instanceof NativeFunctionValue) { return descriptor.value; } } diff --git a/src/methods/has.js b/src/methods/has.js index 3bf1aa789c..d803ef52f9 100644 --- a/src/methods/has.js +++ b/src/methods/has.js @@ -82,7 +82,7 @@ export function HasOwnProperty(realm: Realm, O: ObjectValue | AbstractObjectValu // 4. If desc is undefined, return false. if (desc === undefined) return false; - Properties.ThrowIfMightHaveBeenDeleted(desc.value); + Properties.ThrowIfMightHaveBeenDeleted(desc); // 5. Return true. return true; @@ -98,7 +98,7 @@ export function OrdinaryHasProperty(realm: Realm, O: ObjectValue, P: PropertyKey // 3. If hasOwn is not undefined, return true. if (hasOwn !== undefined) { - Properties.ThrowIfMightHaveBeenDeleted(hasOwn.value); + Properties.ThrowIfMightHaveBeenDeleted(hasOwn); return true; } diff --git a/src/methods/index.js b/src/methods/index.js index d3249c1ed3..58a9fd7594 100644 --- a/src/methods/index.js +++ b/src/methods/index.js @@ -13,7 +13,6 @@ export * from "./abstract.js"; export * from "./call.js"; export * from "./construct.js"; export * from "./date.js"; -export * from "./descriptor.js"; export * from "./get.js"; export * from "./has.js"; export * from "./hash.js"; diff --git a/src/methods/integrity.js b/src/methods/integrity.js index 3a7497f522..4401ea19be 100644 --- a/src/methods/integrity.js +++ b/src/methods/integrity.js @@ -15,6 +15,7 @@ import { IsExtensible, IsDataDescriptor, IsAccessorDescriptor } from "./index.js import { Properties } from "../singletons.js"; import { FatalError } from "../errors.js"; import invariant from "../invariant.js"; +import { PropertyDescriptor } from "../descriptors.js"; type IntegrityLevels = "sealed" | "frozen"; @@ -54,9 +55,14 @@ export function SetIntegrityLevel(realm: Realm, O: ObjectValue, level: Integrity // a. Repeat for each element k of keys, for (let k of keys) { // i. Perform ? DefinePropertyOrThrow(O, k, PropertyDescriptor{[[Configurable]]: false}). - Properties.DefinePropertyOrThrow(realm, O, k, { - configurable: false, - }); + Properties.DefinePropertyOrThrow( + realm, + O, + k, + new PropertyDescriptor({ + configurable: false, + }) + ); } } else if (level === "frozen") { // 7. Else level is "frozen", @@ -67,17 +73,17 @@ export function SetIntegrityLevel(realm: Realm, O: ObjectValue, level: Integrity // ii. If currentDesc is not undefined, then if (currentDesc) { - Properties.ThrowIfMightHaveBeenDeleted(currentDesc.value); + Properties.ThrowIfMightHaveBeenDeleted(currentDesc); let desc; // 1. If IsAccessorDescriptor(currentDesc) is true, then if (IsAccessorDescriptor(realm, currentDesc)) { // a. Let desc be the PropertyDescriptor{[[Configurable]]: false}. - desc = { configurable: false }; + desc = new PropertyDescriptor({ configurable: false }); } else { // 2. Else, // b. Let desc be the PropertyDescriptor { [[Configurable]]: false, [[Writable]]: false }. - desc = { configurable: false, writable: false }; + desc = new PropertyDescriptor({ configurable: false, writable: false }); } // 3. Perform ? DefinePropertyOrThrow(O, k, desc). @@ -116,7 +122,8 @@ export function TestIntegrityLevel(realm: Realm, O: ObjectValue, level: Integrit // b. If currentDesc is not undefined, then if (currentDesc) { - Properties.ThrowIfMightHaveBeenDeleted(currentDesc.value); + Properties.ThrowIfMightHaveBeenDeleted(currentDesc); + currentDesc = currentDesc.throwIfNotConcrete(realm); // i. If currentDesc.[[Configurable]] is true, return false. if (currentDesc.configurable === true) return false; diff --git a/src/methods/is.js b/src/methods/is.js index e34d781a39..12e8398e3a 100644 --- a/src/methods/is.js +++ b/src/methods/is.js @@ -31,6 +31,7 @@ import { Value } from "../values/index.js"; import invariant from "../invariant.js"; import { HasName, HasCompatibleType } from "./has.js"; import type { BabelNodeExpression, BabelNodeCallExpression, BabelNodeLVal, BabelNodeClassMethod } from "@babel/types"; +import { PropertyDescriptor } from "../descriptors.js"; // ECMA262 22.1.3.1.1 export function IsConcatSpreadable(realm: Realm, _O: Value): boolean { @@ -51,7 +52,7 @@ export function IsConcatSpreadable(realm: Realm, _O: Value): boolean { } // ECMA262 6.2.4.3 -export function IsGenericDescriptor(realm: Realm, Desc: ?Descriptor): boolean { +function IsGenericDescriptorInternal(realm: Realm, Desc: ?Descriptor): boolean { // 1. If Desc is undefined, return false. if (!Desc) return false; @@ -63,29 +64,47 @@ export function IsGenericDescriptor(realm: Realm, Desc: ?Descriptor): boolean { } // ECMA262 6.2.4.1 -export function IsAccessorDescriptor(realm: Realm, Desc: ?Descriptor): boolean { +function IsAccessorDescriptorInternal(realm: Realm, Desc: ?Descriptor): boolean { // 1. If Desc is undefined, return false. if (!Desc) return false; // 2. If both Desc.[[Get]] and Desc.[[Set]] are absent, return false. - if (!("get" in Desc) && !("set" in Desc)) return false; + Desc = Desc.throwIfNotConcrete(realm); + if (Desc.get === undefined && Desc.set === undefined) return false; // 3. Return true. return true; } // ECMA262 6.2.4.2 -export function IsDataDescriptor(realm: Realm, Desc: ?Descriptor): boolean { +function IsDataDescriptorInternal(realm: Realm, Desc: ?Descriptor): boolean { // If Desc is undefined, return false. if (!Desc) return false; // If both Desc.[[Value]] and Desc.[[Writable]] are absent, return false. - if (!("value" in Desc) && !("writable" in Desc)) return false; + Desc = Desc.throwIfNotConcrete(realm); + if (Desc.value === undefined && Desc.writable === undefined) return false; // Return true. return true; } +// Flow wrappers that provide refinements using Predicate Functions. +// These wrappers also assert that the type is PropertyDescriptor so that if this returns +// true, then Flow can refine that the type of Desc as PropertyDescriptor. + +export function IsGenericDescriptor(realm: Realm, Desc: ?Descriptor): boolean %checks { + return IsGenericDescriptorInternal(realm, Desc) && Desc instanceof PropertyDescriptor; +} + +export function IsAccessorDescriptor(realm: Realm, Desc: ?Descriptor): boolean %checks { + return IsAccessorDescriptorInternal(realm, Desc) && Desc instanceof PropertyDescriptor; +} + +export function IsDataDescriptor(realm: Realm, Desc: ?Descriptor): boolean %checks { + return IsDataDescriptorInternal(realm, Desc) && Desc instanceof PropertyDescriptor; +} + // ECMA262 9.1.3.1 export function OrdinaryIsExtensible(realm: Realm, O: ObjectValue): boolean { // 1. Return the value of the [[Extensible]] internal slot of O. diff --git a/src/methods/join.js b/src/methods/join.js index 18712122a2..ff58040eab 100644 --- a/src/methods/join.js +++ b/src/methods/join.js @@ -13,6 +13,13 @@ import type { Binding } from "../environment.js"; import type { Bindings, BindingEntry, PropertyBindings, CreatedObjects, Realm } from "../realm.js"; import { construct_empty_effects, Effects } from "../realm.js"; import type { Descriptor, PropertyBinding } from "../types.js"; +import { + cloneDescriptor, + equalDescriptors, + PropertyDescriptor, + AbstractJoinedDescriptor, + InternalSlotDescriptor, +} from "../descriptors.js"; import { AbruptCompletion, @@ -26,7 +33,7 @@ import { ReturnCompletion, ThrowCompletion, } from "../completions.js"; -import { cloneDescriptor, equalDescriptors, IsDataDescriptor, StrictEqualityComparison } from "../methods/index.js"; +import { IsDataDescriptor, StrictEqualityComparison } from "../methods/index.js"; import { Path } from "../singletons.js"; import { Generator } from "../utils/generator.js"; import { AbstractValue, ConcreteValue, EmptyValue, Value } from "../values/index.js"; @@ -366,7 +373,7 @@ export class JoinImplementation { if (c2.has(b.object)) return d2; // no join if (b.descriptor !== undefined && m1.has(b)) { // property was deleted - d1 = cloneDescriptor(b.descriptor); + d1 = cloneDescriptor(b.descriptor.throwIfNotConcrete(realm)); invariant(d1 !== undefined); d1.value = realm.intrinsics.empty; } else { @@ -378,7 +385,7 @@ export class JoinImplementation { if (c1.has(b.object)) return d1; // no join if (b.descriptor !== undefined && m2.has(b)) { // property was deleted - d2 = cloneDescriptor(b.descriptor); + d2 = cloneDescriptor(b.descriptor.throwIfNotConcrete(realm)); invariant(d2 !== undefined); d2.value = realm.intrinsics.empty; } else { @@ -403,69 +410,79 @@ export class JoinImplementation { let clone_with_abstract_value = (d: Descriptor) => { invariant(d === d1 || d === d2); if (!IsDataDescriptor(realm, d)) { - let d3: Descriptor = {}; - d3.joinCondition = joinCondition; - return d3; + return new AbstractJoinedDescriptor(joinCondition); } - let dc = cloneDescriptor(d); - invariant(dc !== undefined); - let dcValue = dc.value; - if (Array.isArray(dcValue)) { - invariant(dcValue.length > 0); - let elem0 = dcValue[0]; - if (elem0 instanceof Value) { - dc.value = dcValue.map(e => { - return d === d1 - ? getAbstractValue((e: any), realm.intrinsics.empty) - : getAbstractValue(realm.intrinsics.empty, (e: any)); - }); - } else { - dc.value = dcValue.map(e => { - let { $Key: key1, $Value: val1 } = (e: any); - let key3 = - d === d1 - ? getAbstractValue(key1, realm.intrinsics.empty) - : getAbstractValue(realm.intrinsics.empty, key1); - let val3 = - d === d1 - ? getAbstractValue(val1, realm.intrinsics.empty) - : getAbstractValue(realm.intrinsics.empty, val1); - return { $Key: key3, $Value: val3 }; - }); + let dc; + let dcValue; + if (d instanceof InternalSlotDescriptor) { + dc = new InternalSlotDescriptor(d.value); + dcValue = dc.value; + if (Array.isArray(dcValue)) { + invariant(dcValue.length > 0); + let elem0 = dcValue[0]; + if (elem0 instanceof Value) { + dc.value = dcValue.map(e => { + return d === d1 + ? getAbstractValue((e: any), realm.intrinsics.empty) + : getAbstractValue(realm.intrinsics.empty, (e: any)); + }); + } else { + dc.value = dcValue.map(e => { + let { $Key: key1, $Value: val1 } = (e: any); + let key3 = + d === d1 + ? getAbstractValue(key1, realm.intrinsics.empty) + : getAbstractValue(realm.intrinsics.empty, key1); + let val3 = + d === d1 + ? getAbstractValue(val1, realm.intrinsics.empty) + : getAbstractValue(realm.intrinsics.empty, val1); + return { $Key: key3, $Value: val3 }; + }); + } } } else { - invariant(dcValue === undefined || dcValue instanceof Value); - dc.value = - d === d1 - ? getAbstractValue(dcValue, realm.intrinsics.empty) - : getAbstractValue(realm.intrinsics.empty, dcValue); + dc = cloneDescriptor(d.throwIfNotConcrete(realm)); + invariant(dc !== undefined); + dcValue = dc.value; } + invariant(dcValue === undefined || dcValue instanceof Value); + dc.value = + d === d1 + ? getAbstractValue(dcValue, realm.intrinsics.empty) + : getAbstractValue(realm.intrinsics.empty, dcValue); return dc; }; if (d1 === undefined) { if (d2 === undefined) return undefined; // d2 is a new property created in only one branch, join with empty let d3 = clone_with_abstract_value(d2); - if (!IsDataDescriptor(realm, d2)) d3.descriptor2 = d2; + if (d3 instanceof AbstractJoinedDescriptor) d3.descriptor2 = d2; return d3; } else if (d2 === undefined) { invariant(d1 !== undefined); // d1 is a new property created in only one branch, join with empty let d3 = clone_with_abstract_value(d1); - if (!IsDataDescriptor(realm, d1)) d3.descriptor1 = d1; + if (d3 instanceof AbstractJoinedDescriptor) d3.descriptor1 = d1; return d3; } else { - if (equalDescriptors(d1, d2) && IsDataDescriptor(realm, d1)) { + if ( + d1 instanceof PropertyDescriptor && + d2 instanceof PropertyDescriptor && + equalDescriptors(d1, d2) && + IsDataDescriptor(realm, d1) + ) { let dc = cloneDescriptor(d1); invariant(dc !== undefined); - dc.value = this.joinValues(realm, d1.value, d2.value, getAbstractValue); + let dcValue = this.joinValues(realm, d1.value, d2.value, getAbstractValue); + invariant(dcValue instanceof Value); + dc.value = dcValue; return dc; } - let d3: Descriptor = {}; - d3.joinCondition = joinCondition; - d3.descriptor1 = d1; - d3.descriptor2 = d2; - return d3; + if (d1 instanceof InternalSlotDescriptor && d2 instanceof InternalSlotDescriptor) { + return new InternalSlotDescriptor(this.joinValues(realm, d1.value, d2.value, getAbstractValue)); + } + return new AbstractJoinedDescriptor(joinCondition, d1, d2); } } diff --git a/src/methods/own.js b/src/methods/own.js index fa36b70588..e634bf48d9 100644 --- a/src/methods/own.js +++ b/src/methods/own.js @@ -100,8 +100,8 @@ export function EnumerableOwnProperties( let desc = O.$GetOwnProperty(key); // ii. If desc is not undefined and desc.[[Enumerable]] is true, then - if (desc && desc.enumerable) { - Properties.ThrowIfMightHaveBeenDeleted(desc.value); + if (desc && desc.throwIfNotConcrete(realm).enumerable) { + Properties.ThrowIfMightHaveBeenDeleted(desc); // 1. If kind is "key", append key to properties. if (kind === "key") { diff --git a/src/methods/properties.js b/src/methods/properties.js index a9d9201218..8eaf6b4a61 100644 --- a/src/methods/properties.js +++ b/src/methods/properties.js @@ -32,8 +32,6 @@ import { CompilerDiagnostic, FatalError } from "../errors.js"; import invariant from "../invariant.js"; import { Call, - cloneDescriptor, - equalDescriptors, Get, GetGlobalObject, GetThisValue, @@ -53,6 +51,7 @@ import { Create, Environment, Functions, Leak, Join, Path, To } from "../singlet import IsStrict from "../utils/strict.js"; import { createOperationDescriptor } from "../utils/generator.js"; import { TypesDomain, ValuesDomain } from "../domains/index.js"; +import { cloneDescriptor, equalDescriptors, PropertyDescriptor, AbstractJoinedDescriptor } from "../descriptors.js"; function StringKey(key: PropertyKeyValue): string { if (key instanceof StringValue) key = key.value; @@ -123,12 +122,12 @@ function InternalUpdatedProperty(realm: Realm, O: ObjectValue, P: PropertyKeyVal generator.emitPropertyDelete(O, P); } } else { - let descValue = desc.value || realm.intrinsics.undefined; - invariant(descValue instanceof Value); + desc = desc.throwIfNotConcrete(realm); if (oldDesc === undefined) { // The property is being created if (O === realm.$GlobalObject) { if (IsDataDescriptor(realm, desc)) { + let descValue = desc.value || realm.intrinsics.undefined; if (isValidIdentifier(P) && !desc.configurable && desc.enumerable && desc.writable) { generator.emitGlobalDeclaration(P, descValue); } else if (desc.configurable && desc.enumerable && desc.writable) { @@ -141,14 +140,18 @@ function InternalUpdatedProperty(realm: Realm, O: ObjectValue, P: PropertyKeyVal } } else { if (IsDataDescriptor(realm, desc) && desc.configurable && desc.enumerable && desc.writable) { + let descValue = desc.value || realm.intrinsics.undefined; generator.emitPropertyAssignment(O, P, descValue); } else { generator.emitDefineProperty(O, P, desc); } } } else { + invariant(oldDesc instanceof PropertyDescriptor); // The property is being modified if (equalDescriptors(desc, oldDesc)) { + invariant(IsDataDescriptor(realm, desc)); + let descValue = desc.value || realm.intrinsics.undefined; // only the value is being modified if (O === realm.$GlobalObject) { generator.emitGlobalAssignment(P, descValue); @@ -163,6 +166,16 @@ function InternalUpdatedProperty(realm: Realm, O: ObjectValue, P: PropertyKeyVal } function leakDescriptor(realm: Realm, desc: Descriptor) { + if (desc instanceof AbstractJoinedDescriptor) { + if (desc.descriptor1) { + leakDescriptor(realm, desc.descriptor1); + } + if (desc.descriptor2) { + leakDescriptor(realm, desc.descriptor2); + } + } + invariant(desc instanceof PropertyDescriptor); + if (desc.value) { if (desc.value instanceof Value) Leak.value(realm, desc.value); else if (desc.value !== undefined) { @@ -187,14 +200,7 @@ function parentPermitsChildPropertyCreation(realm: Realm, O: ObjectValue, P: Pro } let ownDesc = O.$GetOwnProperty(P); - let ownDescValue = !ownDesc - ? realm.intrinsics.undefined - : ownDesc.value === undefined - ? realm.intrinsics.undefined - : ownDesc.value; - invariant(ownDescValue instanceof Value); - - if (!ownDesc || ownDescValue.mightHaveBeenDeleted()) { + if (!ownDesc || ownDesc.mightHaveBeenDeleted()) { // O might not object, so first ask its parent let parent = O.$GetPrototypeOf(); if (!(parent instanceof NullValue)) { @@ -268,15 +274,9 @@ export class PropertiesImplementation { // 2. Let ownDesc be ? O.[[GetOwnProperty]](P). let ownDesc = O.$GetOwnProperty(P); - let ownDescValue = !ownDesc - ? realm.intrinsics.undefined - : ownDesc.value === undefined - ? realm.intrinsics.undefined - : ownDesc.value; - invariant(ownDescValue instanceof Value); // 3. If ownDesc is undefined (or might be), then - if (!ownDesc || ownDescValue.mightHaveBeenDeleted()) { + if (!ownDesc || ownDesc.mightHaveBeenDeleted()) { // a. Let parent be ? O.[[GetPrototypeOf]](). let parent = O.$GetPrototypeOf(); @@ -290,9 +290,17 @@ export class PropertiesImplementation { // But since we don't know if O has its own property P, the parent might // actually have a say. Give up, unless the parent would be OK with it. if (!parentPermitsChildPropertyCreation(realm, parent, P)) { - invariant(ownDescValue instanceof AbstractValue); - AbstractValue.reportIntrospectionError(ownDescValue); - throw new FatalError(); + // TODO: Join the effects depending on if the property was deleted or not. + let error = new CompilerDiagnostic( + "assignment might or might not invoke a setter", + realm.currentLocation, + "PP0043", + "RecoverableError" + ); + if (realm.handleError(error) !== "Recover") { + throw new FatalError(); + } + // If we recover, we assume that the parent would've been fine creating the property. } // Since the parent is OK with us creating a local property for O // we can carry on as if there were no parent. @@ -300,17 +308,17 @@ export class PropertiesImplementation { // i. Let ownDesc be the PropertyDescriptor{[[Value]]: undefined, [[Writable]]: true, [[Enumerable]]: true, [[Configurable]]: true}. if (!ownDesc) - ownDesc = ({ + ownDesc = new PropertyDescriptor({ value: realm.intrinsics.undefined, writable: true, enumerable: true, configurable: true, - }: any); + }); } // joined descriptors need special treatment - let joinCondition = ownDesc.joinCondition; - if (joinCondition !== undefined) { + if (ownDesc instanceof AbstractJoinedDescriptor) { + let joinCondition = ownDesc.joinCondition; let descriptor2 = ownDesc.descriptor2; ownDesc = ownDesc.descriptor1; let e1 = Path.withCondition(joinCondition, () => { @@ -354,18 +362,25 @@ export class PropertiesImplementation { function OrdinarySetHelper(): boolean { invariant(ownDesc !== undefined); - invariant(ownDescValue instanceof Value); // 4. If IsDataDescriptor(ownDesc) is true, then if (IsDataDescriptor(realm, ownDesc)) { // a. If ownDesc.[[Writable]] is false, return false. if (!ownDesc.writable && !weakDeletion) { // The write will fail if the property actually exists - if (ownDescValue.mightHaveBeenDeleted()) { + if (ownDesc.value && ownDesc.value.mightHaveBeenDeleted()) { // But maybe it does not and thus would succeed. // Since we don't know what will happen, give up for now. - invariant(ownDescValue instanceof AbstractValue); - AbstractValue.reportIntrospectionError(ownDescValue); - throw new FatalError(); + // TODO: Join the effects depending on if the property was deleted or not. + let error = new CompilerDiagnostic( + "assignment might or might not invoke a setter", + realm.currentLocation, + "PP0043", + "RecoverableError" + ); + if (realm.handleError(error) !== "Recover") { + throw new FatalError(); + } + // If we recover we assume that the property was there. } return false; } @@ -376,7 +391,7 @@ export class PropertiesImplementation { // c. Let existingDescriptor be ? Receiver.[[GetOwnProperty]](P). let existingDescriptor = Receiver.$GetOwnProperty(P); - if (existingDescriptor !== undefined) { + if (existingDescriptor instanceof AbstractJoinedDescriptor) { if (existingDescriptor.descriptor1 === ownDesc) existingDescriptor = ownDesc; else if (existingDescriptor.descriptor2 === ownDesc) existingDescriptor = ownDesc; } @@ -410,7 +425,7 @@ export class PropertiesImplementation { } // iii. Let valueDesc be the PropertyDescriptor{[[Value]]: V}. - let valueDesc = { value: V }; + let valueDesc = new PropertyDescriptor({ value: V }); // iv. Return ? Receiver.[[DefineOwnProperty]](P, valueDesc). if (weakDeletion || existingDescValue.mightHaveBeenDeleted()) { @@ -420,7 +435,7 @@ export class PropertiesImplementation { // change the attributes of the property, so we can reuse the existing // descriptor. valueDesc = existingDescriptor; - valueDesc.value = V; + valueDesc.throwIfNotConcrete(realm).value = V; } return Receiver.$DefineOwnProperty(P, valueDesc); } else { @@ -435,7 +450,7 @@ export class PropertiesImplementation { invariant(IsAccessorDescriptor(realm, ownDesc), "expected accessor"); // 6. Let setter be ownDesc.[[Set]]. - let setter = "set" in ownDesc ? ownDesc.set : undefined; + let setter = ownDesc.set; // 7. If setter is undefined, return false. if (!setter || setter instanceof UndefinedValue) return false; @@ -569,16 +584,20 @@ export class PropertiesImplementation { ]); newVal = AbstractValue.createFromConditionalOp(realm, cond, V, sentinel); } - prop.descriptor = { + prop.descriptor = new PropertyDescriptor({ writable: true, enumerable: true, configurable: true, value: newVal, - }; + }); } else { + invariant( + desc instanceof PropertyDescriptor, + "unknown properties are only created with Set and have equal descriptors" + ); // join V with current value of O.unknownProperty. I.e. weak update. let oldVal = desc.value; - invariant(oldVal instanceof Value); + invariant(oldVal); let newVal = oldVal; if (!(V instanceof UndefinedValue)) { if (isWidenedValue(P)) { @@ -604,9 +623,11 @@ export class PropertiesImplementation { continue; } let oldVal = realm.intrinsics.empty; - if (propertyBinding.descriptor && propertyBinding.descriptor.value) { - oldVal = propertyBinding.descriptor.value; - invariant(oldVal instanceof Value); // otherwise this is not simple + if (propertyBinding.descriptor) { + let d = propertyBinding.descriptor.throwIfNotConcrete(realm); + if (d.value) { + oldVal = d.value; + } } let cond = AbstractValue.createFromBinaryOp(realm, "===", P, new StringValue(realm, key)); let newVal = AbstractValue.createFromConditionalOp(realm, cond, V, oldVal); @@ -622,7 +643,7 @@ export class PropertiesImplementation { // 1. If Desc is undefined, return undefined. if (!Desc) return realm.intrinsics.undefined; - if (Desc.joinCondition) { + if (Desc instanceof AbstractJoinedDescriptor) { return AbstractValue.createFromConditionalOp( realm, Desc.joinCondition, @@ -630,6 +651,7 @@ export class PropertiesImplementation { this.FromPropertyDescriptor(realm, Desc.descriptor2) ); } + invariant(Desc instanceof PropertyDescriptor); // 2. Let obj be ObjectCreate(%ObjectPrototype%). let obj = Create.ObjectCreate(realm, realm.intrinsics.ObjectPrototype); @@ -640,44 +662,38 @@ export class PropertiesImplementation { // 4. If Desc has a [[Value]] field, then let success = true; - if ("value" in Desc) { - invariant(Desc.value instanceof Value); + if (Desc.value !== undefined) { // a. Perform CreateDataProperty(obj, "value", Desc.[[Value]]). success = Create.CreateDataProperty(realm, obj, "value", Desc.value) && success; } // 5. If Desc has a [[Writable]] field, then - if ("writable" in Desc) { - invariant(Desc.writable !== undefined); + if (Desc.writable !== undefined) { // a. Perform CreateDataProperty(obj, "writable", Desc.[[Writable]]). success = Create.CreateDataProperty(realm, obj, "writable", new BooleanValue(realm, Desc.writable)) && success; } // 6. If Desc has a [[Get]] field, then - if ("get" in Desc) { - invariant(Desc.get !== undefined); + if (Desc.get !== undefined) { // a. Perform CreateDataProperty(obj, "get", Desc.[[Get]]). success = Create.CreateDataProperty(realm, obj, "get", Desc.get) && success; } // 7. If Desc has a [[Set]] field, then - if ("set" in Desc) { - invariant(Desc.set !== undefined); + if (Desc.set !== undefined) { // a. Perform CreateDataProperty(obj, "set", Desc.[[Set]]). success = Create.CreateDataProperty(realm, obj, "set", Desc.set) && success; } // 8. If Desc has an [[Enumerable]] field, then - if ("enumerable" in Desc) { - invariant(Desc.enumerable !== undefined); + if (Desc.enumerable !== undefined) { // a. Perform CreateDataProperty(obj, "enumerable", Desc.[[Enumerable]]). success = Create.CreateDataProperty(realm, obj, "enumerable", new BooleanValue(realm, Desc.enumerable)) && success; } // 9. If Desc has a [[Configurable]] field, then - if ("configurable" in Desc) { - invariant(Desc.configurable !== undefined); + if (Desc.configurable !== undefined) { // a. Perform CreateDataProperty(obj, "configurable", Desc.[[Configurable]]). success = Create.CreateDataProperty(realm, obj, "configurable", new BooleanValue(realm, Desc.configurable)) && success; @@ -709,6 +725,8 @@ export class PropertiesImplementation { return true; } + desc = desc.throwIfNotConcrete(realm); + // 4. If desc.[[Configurable]] is true, then if (desc.configurable) { ensureIsNotFinal(realm, O, P); @@ -765,8 +783,9 @@ export class PropertiesImplementation { } // ECMA262 6.2.4.6 - CompletePropertyDescriptor(realm: Realm, Desc: Descriptor): Descriptor { + CompletePropertyDescriptor(realm: Realm, _Desc: Descriptor): Descriptor { // 1. Assert: Desc is a Property Descriptor. + let Desc = _Desc.throwIfNotConcrete(realm); // 2. Let like be Record{[[Value]]: undefined, [[Writable]]: false, [[Get]]: undefined, [[Set]]: undefined, [[Enumerable]]: false, [[Configurable]]: false}. let like = { @@ -781,22 +800,22 @@ export class PropertiesImplementation { // 3. If either IsGenericDescriptor(Desc) or IsDataDescriptor(Desc) is true, then if (IsGenericDescriptor(realm, Desc) || IsDataDescriptor(realm, Desc)) { // a. If Desc does not have a [[Value]] field, set Desc.[[Value]] to like.[[Value]]. - if (!("value" in Desc)) Desc.value = like.value; + if (Desc.value === undefined) Desc.value = like.value; // b. If Desc does not have a [[Writable]] field, set Desc.[[Writable]] to like.[[Writable]]. - if (!("writable" in Desc)) Desc.writable = like.writable; + if (Desc.writable === undefined) Desc.writable = like.writable; } else { // 4. Else, // a. If Desc does not have a [[Get]] field, set Desc.[[Get]] to like.[[Get]]. - if (!("get" in Desc)) Desc.get = like.get; + if (Desc.get === undefined) Desc.get = like.get; // b. If Desc does not have a [[Set]] field, set Desc.[[Set]] to like.[[Set]]. - if (!("set" in Desc)) Desc.set = like.set; + if (Desc.set === undefined) Desc.set = like.set; } // 5. If Desc does not have an [[Enumerable]] field, set Desc.[[Enumerable]] to like.[[Enumerable]]. - if (!("enumerable" in Desc)) Desc.enumerable = like.enumerable; + if (Desc.enumerable === undefined) Desc.enumerable = like.enumerable; // 6. If Desc does not have a [[Configurable]] field, set Desc.[[Configurable]] to like.[[Configurable]]. - if (!("configurable" in Desc)) Desc.configurable = like.configurable; + if (Desc.configurable === undefined) Desc.configurable = like.configurable; // 7. Return Desc. return Desc; @@ -814,16 +833,19 @@ export class PropertiesImplementation { O: void | ObjectValue, P: void | PropertyKeyValue, extensible: boolean, - Desc: Descriptor, - current: ?Descriptor + _Desc: Descriptor, + _current: ?Descriptor ): boolean { + let Desc = _Desc; + let current = _current; + // 1. Assert: If O is not undefined, then IsPropertyKey(P) is true. if (O !== undefined) { invariant(P !== undefined); invariant(IsPropertyKey(realm, P)); } - if (current && current.joinCondition !== undefined) { + if (current instanceof AbstractJoinedDescriptor) { let jc = current.joinCondition; if (Path.implies(jc)) current = current.descriptor1; else if (!AbstractValue.createFromUnaryOp(realm, "!", jc, true).mightNotBeTrue()) current = current.descriptor2; @@ -842,7 +864,7 @@ export class PropertiesImplementation { if (!realm.ignoreLeakLogic && O.mightBeLeakedObject()) { leakDescriptor(realm, Desc); if (realm.generator !== undefined) { - realm.generator.emitDefineProperty(O, StringKey(P), Desc); + realm.generator.emitDefineProperty(O, StringKey(P), Desc.throwIfNotConcrete(realm)); } return true; } @@ -856,12 +878,17 @@ export class PropertiesImplementation { // to its default value. if (O !== undefined) { invariant(P !== undefined); - InternalSetProperty(realm, O, P, { - value: "value" in Desc ? Desc.value : realm.intrinsics.undefined, - writable: "writable" in Desc ? Desc.writable : false, - enumerable: "enumerable" in Desc ? Desc.enumerable : false, - configurable: "configurable" in Desc ? Desc.configurable : false, - }); + InternalSetProperty( + realm, + O, + P, + new PropertyDescriptor({ + value: Desc.value !== undefined ? Desc.value : realm.intrinsics.undefined, + writable: Desc.writable !== undefined ? Desc.writable : false, + enumerable: Desc.enumerable !== undefined ? Desc.enumerable : false, + configurable: Desc.configurable !== undefined ? Desc.configurable : false, + }) + ); InternalUpdatedProperty(realm, O, P, undefined); } } else { @@ -872,12 +899,18 @@ export class PropertiesImplementation { // default value. if (O !== undefined) { invariant(P !== undefined); - InternalSetProperty(realm, O, P, { - get: "get" in Desc ? Desc.get : realm.intrinsics.undefined, - set: "set" in Desc ? Desc.set : realm.intrinsics.undefined, - enumerable: "enumerable" in Desc ? Desc.enumerable : false, - configurable: "configurable" in Desc ? Desc.configurable : false, - }); + Desc = Desc.throwIfNotConcrete(realm); + InternalSetProperty( + realm, + O, + P, + new PropertyDescriptor({ + get: Desc.get !== undefined ? Desc.get : realm.intrinsics.undefined, + set: Desc.set !== undefined ? Desc.set : realm.intrinsics.undefined, + enumerable: Desc.enumerable !== undefined ? Desc.enumerable : false, + configurable: Desc.configurable !== undefined ? Desc.configurable : false, + }) + ); InternalUpdatedProperty(realm, O, P, undefined); } } @@ -886,18 +919,31 @@ export class PropertiesImplementation { return true; } + current = current.throwIfNotConcrete(realm); + Desc = Desc.throwIfNotConcrete(realm); + // 3. Return true, if every field in Desc is absent. - if (!Object.keys(Desc).length) return true; + let allAbsent = true; + for (let field in Desc) { + if ((Desc: any)[field] !== undefined) { + allAbsent = false; + break; + } + } + if (allAbsent) return true; // 4. Return true, if every field in Desc also occurs in current and the value of every field in Desc is the // same value as the corresponding field in current when compared using the SameValue algorithm. let identical = true; for (let field in Desc) { - if (!(field in current)) { + if ((Desc: any)[field] === undefined) { + continue; + } + if ((current: any)[field] === undefined) { identical = false; } else { - let dval = InternalDescriptorPropertyToValue(realm, Desc[field]); - let cval = InternalDescriptorPropertyToValue(realm, current[field]); + let dval = InternalDescriptorPropertyToValue(realm, (Desc: any)[field]); + let cval = InternalDescriptorPropertyToValue(realm, (current: any)[field]); if (dval instanceof ConcreteValue && cval instanceof ConcreteValue) identical = SameValue(realm, dval, cval); else { identical = dval === cval; @@ -924,11 +970,14 @@ export class PropertiesImplementation { if (Desc.configurable) return false; // b. Return false, if the [[Enumerable]] field of Desc is present and the [[Enumerable]] fields of current and Desc are the Boolean negation of each other. - if ("enumerable" in Desc && Desc.enumerable !== current.enumerable) { + if (Desc.enumerable !== undefined && Desc.enumerable !== current.enumerable) { return false; } } + current = current.throwIfNotConcrete(realm); + Desc = Desc.throwIfNotConcrete(realm); + if (O !== undefined && P !== undefined) { ensureIsNotFinal(realm, O, P); if (!realm.ignoreLeakLogic && O.mightBeLeakedObject()) { @@ -957,8 +1006,8 @@ export class PropertiesImplementation { // Preserve the existing values of the converted property's [[Configurable]] and [[Enumerable]] attributes and set the rest of the property's attributes to their default values. if (O !== undefined) { invariant(P !== undefined); - delete current.writable; - delete current.value; + current.writable = undefined; + current.value = undefined; current.get = realm.intrinsics.undefined; current.set = realm.intrinsics.undefined; } @@ -967,8 +1016,8 @@ export class PropertiesImplementation { // i. If O is not undefined, convert the property named P of object O from an accessor property to a data property. Preserve the existing values of the converted property's [[Configurable]] and [[Enumerable]] attributes and set the rest of the property's attributes to their default values. if (O !== undefined) { invariant(P !== undefined); - delete current.get; - delete current.set; + current.get = undefined; + current.set = undefined; current.writable = false; current.value = realm.intrinsics.undefined; } @@ -1010,8 +1059,8 @@ export class PropertiesImplementation { // If the property might have been deleted, we need to ensure that either // the new descriptor overrides any existing values, or always results in // the default value. - let unknownEnumerable = !("enumerable" in Desc) && !!current.enumerable; - let unknownWritable = !("writable" in Desc) && !!current.writable; + let unknownEnumerable = Desc.enumerable === undefined && !!current.enumerable; + let unknownWritable = Desc.writable === undefined && !!current.writable; if (unknownEnumerable || unknownWritable) { let error = new CompilerDiagnostic( "unknown descriptor attributes on deleted property", @@ -1046,7 +1095,11 @@ export class PropertiesImplementation { // a. For each field of Desc that is present, set the corresponding attribute of the property named P of // object O to the value of the field. - for (let field in Desc) current[field] = Desc[field]; + for (let field in Desc) { + if ((Desc: any)[field] !== undefined) { + (current: any)[field] = (Desc: any)[field]; + } + } InternalUpdatedProperty(realm, O, P, oldDesc); } @@ -1092,8 +1145,8 @@ export class PropertiesImplementation { let propDesc = props.$GetOwnProperty(nextKey); // b. If propDesc is not undefined and propDesc.[[Enumerable]] is true, then - if (propDesc && propDesc.enumerable) { - this.ThrowIfMightHaveBeenDeleted(propDesc.value); + if (propDesc && propDesc.throwIfNotConcrete(realm).enumerable) { + this.ThrowIfMightHaveBeenDeleted(propDesc); // i. Let descObj be ? Get(props, nextKey). let descObj = Get(realm, props, nextKey); @@ -1239,7 +1292,9 @@ export class PropertiesImplementation { } // ECMA262 9.4.2.4 - ArraySetLength(realm: Realm, A: ArrayValue, Desc: Descriptor): boolean { + ArraySetLength(realm: Realm, A: ArrayValue, _Desc: Descriptor): boolean { + let Desc = _Desc.throwIfNotConcrete(realm); + // 1. If the [[Value]] field of Desc is absent, then let DescValue = Desc.value; if (!DescValue) { @@ -1249,7 +1304,7 @@ export class PropertiesImplementation { invariant(DescValue instanceof Value); // 2. Let newLenDesc be a copy of Desc. - let newLenDesc = Object.assign({}, Desc); + let newLenDesc = new PropertyDescriptor(Desc); // 3. Let newLen be ? ToUint32(Desc.[[Value]]). let newLen = To.ToUint32(realm, DescValue); @@ -1274,6 +1329,7 @@ export class PropertiesImplementation { oldLenDesc !== undefined && !IsAccessorDescriptor(realm, oldLenDesc), "cannot be undefined or an accessor descriptor" ); + oldLenDesc = oldLenDesc.throwIfNotConcrete(realm); // 9. Let oldLen be oldLenDesc.[[Value]]. let oldLen = oldLenDesc.value; @@ -1293,7 +1349,7 @@ export class PropertiesImplementation { // 12. If newLenDesc.[[Writable]] is absent or has the value true, let newWritable be true. let newWritable; - if (!("writable" in newLenDesc) || newLenDesc.writable === true) { + if (newLenDesc.writable === undefined || newLenDesc.writable === true) { newWritable = true; } else { // 13. Else, @@ -1348,9 +1404,14 @@ export class PropertiesImplementation { // 17. If newWritable is false, then if (!newWritable) { // a. Return OrdinaryDefineOwnProperty(A, "length", PropertyDescriptor{[[Writable]]: false}). This call will always return true. - return this.OrdinaryDefineOwnProperty(realm, A, "length", { - writable: false, - }); + return this.OrdinaryDefineOwnProperty( + realm, + A, + "length", + new PropertyDescriptor({ + writable: false, + }) + ); } // 18. Return true. @@ -1382,7 +1443,7 @@ export class PropertiesImplementation { { isPure: true } ); // TODO: We can't be sure what the descriptor will be, but the value will be abstract. - return { configurable: true, enumerable: true, value: absVal, writable: true }; + return new PropertyDescriptor({ configurable: true, enumerable: true, value: absVal, writable: true }); } // 1. Assert: IsPropertyKey(P) is true. @@ -1443,7 +1504,7 @@ export class PropertiesImplementation { } else { absVal = createAbstractPropertyValue(Value); } - return { configurable: true, enumerable: true, value: absVal, writable: true }; + return new PropertyDescriptor({ configurable: true, enumerable: true, value: absVal, writable: true }); } else { invariant(P instanceof SymbolValue); // Simple objects don't have symbol properties @@ -1490,18 +1551,17 @@ export class PropertiesImplementation { } // 3. Let D be a newly created Property Descriptor with no fields. - let D = {}; + let D = new PropertyDescriptor({}); // 4. Let X be O's own property whose key is P. let X = existingBinding.descriptor; invariant(X !== undefined); - if (X.joinCondition !== undefined) { - D.joinCondition = X.joinCondition; - D.descriptor1 = X.descriptor1; - D.descriptor2 = X.descriptor2; - return D; + if (X instanceof AbstractJoinedDescriptor) { + return new AbstractJoinedDescriptor(X.joinCondition, X.descriptor1, X.descriptor2); } + invariant(X instanceof PropertyDescriptor); + // 5. If X is a data property, then if (IsDataDescriptor(realm, X)) { let value = X.value; @@ -1536,12 +1596,17 @@ export class PropertiesImplementation { realm.markPropertyAsChecked(O, P); realmGenerator.emitFullInvariant(O, P, value); } - InternalSetProperty(realm, O, P, { - value: value, - writable: "writable" in X ? X.writable : false, - enumerable: "enumerable" in X ? X.enumerable : false, - configurable: "configurable" in X ? X.configurable : false, - }); + InternalSetProperty( + realm, + O, + P, + new PropertyDescriptor({ + value: value, + writable: X.writable !== undefined ? X.writable : false, + enumerable: X.enumerable !== undefined ? X.enumerable : false, + configurable: X.configurable !== undefined ? X.configurable : false, + }) + ); } } else if (realm.invariantLevel >= 1 && value instanceof Value && !(value instanceof AbstractValue)) { let realmGenerator = realm.generator; @@ -1678,8 +1743,8 @@ export class PropertiesImplementation { // Omit non-enumerable properties. let desc = obj.$GetOwnProperty(key); - if (desc && !desc.enumerable) { - this.ThrowIfMightHaveBeenDeleted(desc.value); + if (desc && !desc.throwIfNotConcrete(realm).enumerable) { + this.ThrowIfMightHaveBeenDeleted(desc); index += 1; visited.add(key.value); continue; @@ -1699,10 +1764,20 @@ export class PropertiesImplementation { return iterator; } - ThrowIfMightHaveBeenDeleted( - value: void | Value | Array | Array<{ $Key: void | Value, $Value: void | Value }> - ): void { - if (!(value instanceof Value)) return; + ThrowIfMightHaveBeenDeleted(desc: Descriptor): void { + if (desc instanceof AbstractJoinedDescriptor) { + if (desc.descriptor1) { + this.ThrowIfMightHaveBeenDeleted(desc.descriptor1); + } + if (desc.descriptor2) { + this.ThrowIfMightHaveBeenDeleted(desc.descriptor2); + } + } + invariant(desc instanceof PropertyDescriptor, "internal slots should never assert using this"); + let value = desc.value; + if (value === undefined) { + return; + } if (!value.mightHaveBeenDeleted()) return; invariant(value instanceof AbstractValue); // real empty values should never get here let v = value.$Realm.simplifyAndRefineAbstractValue(value); @@ -1742,7 +1817,12 @@ export class PropertiesImplementation { methodDef.$Closure.$HasComputedName = !!MethodDefinition.computed; // 4. Let desc be the Property Descriptor{[[Value]]: methodDef.[[closure]], [[Writable]]: true, [[Enumerable]]: enumerable, [[Configurable]]: true}. - let desc: Descriptor = { value: methodDef.$Closure, writable: true, enumerable: enumerable, configurable: true }; + let desc: Descriptor = new PropertyDescriptor({ + value: methodDef.$Closure, + writable: true, + enumerable: enumerable, + configurable: true, + }); // 5. Return DefinePropertyOrThrow(object, methodDef.[[key]], desc). return this.DefinePropertyOrThrow(realm, object, methodDef.$Key, desc); @@ -1784,7 +1864,12 @@ export class PropertiesImplementation { Functions.SetFunctionName(realm, closure, propKey); // 10. Let desc be the Property Descriptor{[[Value]]: closure, [[Writable]]: true, [[Enumerable]]: enumerable, [[Configurable]]: true}. - let desc: Descriptor = { value: closure, writable: true, enumerable: enumerable, configurable: true }; + let desc: Descriptor = new PropertyDescriptor({ + value: closure, + writable: true, + enumerable: enumerable, + configurable: true, + }); // 11. Return DefinePropertyOrThrow(object, propKey, desc). return this.DefinePropertyOrThrow(realm, object, propKey, desc); @@ -1823,11 +1908,11 @@ export class PropertiesImplementation { closure.$HasComputedName = !!MethodDefinition.computed; // 9. Let desc be the PropertyDescriptor{[[Get]]: closure, [[Enumerable]]: enumerable, [[Configurable]]: true}. - let desc = { + let desc = new PropertyDescriptor({ get: closure, enumerable: true, configurable: true, - }; + }); // 10. Return ? DefinePropertyOrThrow(object, propKey, desc). return this.DefinePropertyOrThrow(realm, object, propKey, desc); @@ -1864,11 +1949,11 @@ export class PropertiesImplementation { closure.$HasComputedName = !!MethodDefinition.computed; // 8. Let desc be the PropertyDescriptor{[[Set]]: closure, [[Enumerable]]: enumerable, [[Configurable]]: true}. - let desc = { + let desc = new PropertyDescriptor({ set: closure, enumerable: true, configurable: true, - }; + }); // 9. Return ? DefinePropertyOrThrow(object, propKey, desc). return this.DefinePropertyOrThrow(realm, object, propKey, desc); @@ -1894,7 +1979,7 @@ export class PropertiesImplementation { keyArray = keyArray.filter(x => { let pb = O.properties.get(x); if (!pb || pb.descriptor === undefined) return false; - let pv = pb.descriptor.value; + let pv = pb.descriptor.throwIfNotConcrete(realm).value; if (pv === undefined) return true; invariant(pv instanceof Value); if (!pv.mightHaveBeenDeleted()) return true; diff --git a/src/methods/regexp.js b/src/methods/regexp.js index a37dee14cd..0aa704d344 100644 --- a/src/methods/regexp.js +++ b/src/methods/regexp.js @@ -17,6 +17,7 @@ import { IsCallable } from "./is.js"; import { Call } from "./call.js"; import { HasCompatibleType, HasSomeCompatibleType } from "./has.js"; import { Create, Properties, To } from "../singletons.js"; +import { PropertyDescriptor } from "../descriptors.js"; // ECMA262 21.2.3.2.3 export function RegExpCreate(realm: Realm, P: ?Value, F: ?Value): ObjectValue { @@ -39,11 +40,16 @@ export function RegExpAlloc(realm: Realm, newTarget: ObjectValue): ObjectValue { // 2. Perform ! DefinePropertyOrThrow(obj, "lastIndex", PropertyDescriptor {[[Writable]]: true, // [[Enumerable]]: false, [[Configurable]]: false}). - Properties.DefinePropertyOrThrow(realm, obj, "lastIndex", { - writable: true, - enumerable: false, - configurable: false, - }); + Properties.DefinePropertyOrThrow( + realm, + obj, + "lastIndex", + new PropertyDescriptor({ + writable: true, + enumerable: false, + configurable: false, + }) + ); // 3. Return obj. return obj; diff --git a/src/methods/to.js b/src/methods/to.js index d4f6c1b7f7..e2fc078892 100644 --- a/src/methods/to.js +++ b/src/methods/to.js @@ -36,6 +36,7 @@ import { } from "../values/index.js"; import invariant from "../invariant.js"; import { createOperationDescriptor } from "../utils/generator.js"; +import { PropertyDescriptor } from "../descriptors.js"; type ElementConvType = { Int8: (Realm, numberOrValue) => number, @@ -278,7 +279,7 @@ export class ToImplementation { } // 2. Let desc be a new Property Descriptor that initially has no fields. - let desc: Descriptor = {}; + let desc = new PropertyDescriptor({}); // 3. Let hasEnumerable be ? HasProperty(Obj, "enumerable"). let hasEnumerable = HasProperty(realm, Obj, "enumerable"); @@ -367,7 +368,7 @@ export class ToImplementation { // 15. If either desc.[[Get]] or desc.[[Set]] is present, then if (desc.get || desc.set) { // a. If either desc.[[Value]] or desc.[[Writable]] is present, throw a TypeError exception. - if ("value" in desc || "writable" in desc) { + if (desc.value !== undefined || desc.writable !== undefined) { throw realm.createErrorThrowCompletion(realm.intrinsics.TypeError); } } diff --git a/src/methods/widen.js b/src/methods/widen.js index 18379c17fc..08e1434eba 100644 --- a/src/methods/widen.js +++ b/src/methods/widen.js @@ -14,14 +14,16 @@ import { FatalError } from "../errors.js"; import type { Bindings, BindingEntry, EvaluationResult, PropertyBindings, CreatedObjects, Realm } from "../realm.js"; import { Effects } from "../realm.js"; import type { Descriptor, PropertyBinding } from "../types.js"; +import { cloneDescriptor, equalDescriptors, PropertyDescriptor } from "../descriptors.js"; import { AbruptCompletion, JoinedNormalAndAbruptCompletions, SimpleNormalCompletion } from "../completions.js"; import { Reference } from "../environment.js"; -import { cloneDescriptor, equalDescriptors, IsDataDescriptor, StrictEqualityComparison } from "./index.js"; +import { IsDataDescriptor, StrictEqualityComparison } from "./index.js"; import { Generator, createOperationDescriptor } from "../utils/generator.js"; import { AbstractValue, ArrayValue, EmptyValue, Value, StringValue } from "../values/index.js"; import invariant from "../invariant.js"; +import { InternalSlotDescriptor } from "../descriptors.js"; export class WidenImplementation { _widenArrays( @@ -212,17 +214,18 @@ export class WidenImplementation { if (d1 === undefined && d2 === undefined) return undefined; // If the PropertyBinding object has been freshly allocated do not widen (that happens in AbstractObjectValue) if (d1 === undefined) { + invariant(d2 !== undefined); if (c2.has(b.object)) return d2; // no widen if (b.descriptor !== undefined && m1.has(b)) { // property was present in (n-1)th iteration and deleted in nth iteration - d1 = cloneDescriptor(b.descriptor); + d1 = cloneDescriptor(b.descriptor.throwIfNotConcrete(realm)); invariant(d1 !== undefined); d1.value = realm.intrinsics.empty; } else { // no write to property in nth iteration, use the value from the (n-1)th iteration d1 = b.descriptor; if (d1 === undefined) { - d1 = cloneDescriptor(d2); + d1 = cloneDescriptor(d2.throwIfNotConcrete(realm)); invariant(d1 !== undefined); d1.value = realm.intrinsics.empty; } @@ -232,7 +235,7 @@ export class WidenImplementation { if (c1.has(b.object)) return d1; // no widen if (m2.has(b)) { // property was present in nth iteration and deleted in (n+1)th iteration - d2 = cloneDescriptor(d1); + d2 = cloneDescriptor(d1.throwIfNotConcrete(realm)); invariant(d2 !== undefined); d2.value = realm.intrinsics.empty; } else { @@ -277,8 +280,7 @@ export class WidenImplementation { // before the loop commences, otherwise the memberExpression will result in an undefined value. let generator = realm.generator; invariant(generator !== undefined); - let initVal = (b.descriptor && b.descriptor.value) || realm.intrinsics.empty; - if (!(initVal instanceof Value)) throw new FatalError("todo: handle internal properties"); + let initVal = (b.descriptor && b.descriptor.throwIfNotConcrete(realm).value) || realm.intrinsics.empty; if (!(initVal instanceof EmptyValue)) { if (key === "length" && b.object instanceof ArrayValue) { // do nothing, the array length will already be initialized @@ -311,7 +313,8 @@ export class WidenImplementation { return this.widenMaps(m1, m2, widen); } - widenDescriptors(realm: Realm, d1: void | Descriptor, d2: Descriptor): void | Descriptor { + widenDescriptors(realm: Realm, d1: void | Descriptor, d2: Descriptor): void | PropertyDescriptor { + d2 = d2.throwIfNotConcrete(realm); if (d1 === undefined) { // d2 is a property written to only in the (n+1)th iteration if (!IsDataDescriptor(realm, d2)) return d2; // accessor properties need not be widened. @@ -319,9 +322,12 @@ export class WidenImplementation { invariant(dc !== undefined); let d2value = dc.value; invariant(d2value !== undefined); // because IsDataDescriptor is true for d2/dc - dc.value = this.widenValues(realm, d2value, d2value); + let dcValue = this.widenValues(realm, d2value, d2value); + invariant(dcValue instanceof Value); + dc.value = dcValue; return dc; } else { + d1 = d1.throwIfNotConcrete(realm); if (equalDescriptors(d1, d2)) { if (!IsDataDescriptor(realm, d1)) return d1; // identical accessor properties need not be widened. // equalDescriptors plus IsDataDescriptor guarantee that both have value props and if you have a value prop is value is defined. @@ -331,7 +337,9 @@ export class WidenImplementation { invariant(d1value !== undefined); let d2value = d2.value; invariant(d2value !== undefined); - dc.value = this.widenValues(realm, d1value, d2value); + let dcValue = this.widenValues(realm, d1value, d2value); + invariant(dcValue instanceof Value); + dc.value = dcValue; return dc; } //todo: #1174 if we get here, the loop body contains a call to create a property and different iterations @@ -397,7 +405,23 @@ export class WidenImplementation { c2: CreatedObjects ): boolean { let containsPropertyBinding = (d1: void | Descriptor, d2: void | Descriptor) => { - let [v1, v2] = [d1 && d1.value, d2 && d2.value]; + let v1, v2; + if (d1 instanceof InternalSlotDescriptor || d2 instanceof InternalSlotDescriptor) { + if (d1 !== undefined) { + invariant(d1 instanceof InternalSlotDescriptor); + v1 = d1.value; + } + if (d2 !== undefined) { + invariant(d2 instanceof InternalSlotDescriptor); + v2 = d2.value; + } + } + if (d1 instanceof PropertyDescriptor) { + v1 = d1.value; + } + if (d2 instanceof PropertyDescriptor) { + v2 = d2.value; + } if (v1 === undefined) { return v2 === undefined; } diff --git a/src/options.js b/src/options.js index e0c10f034b..3deecfea8f 100644 --- a/src/options.js +++ b/src/options.js @@ -76,8 +76,4 @@ export type SerializerOptions = { heapGraphFormat?: "DotLanguage" | "VISJS", }; -export type PartialEvaluatorOptions = { - sourceMaps?: boolean, -}; - export const defaultOptions = {}; diff --git a/src/prepack-cli.js b/src/prepack-cli.js index 225610e059..f391d63657 100644 --- a/src/prepack-cli.js +++ b/src/prepack-cli.js @@ -21,6 +21,7 @@ import { InvariantModeValues, } from "./options.js"; import { type SerializedResult } from "./serializer/types.js"; +import { TextPrinter } from "./utils/TextPrinter.js"; import { prepackStdin, prepackFileSync } from "./prepack-node.js"; import type { BabelNodeSourceLocation } from "@babel/types"; import fs from "fs"; @@ -67,6 +68,7 @@ function run( --logStatistics Log statistics to console --statsFile The name of the output file where statistics will be written to. --heapGraphFilePath The name of the output file where heap graph will be written to. + --dumpIRFilePath The name of the output file where the intermediate representation will be written to. --inlineExpressions When generating code, tells prepack to avoid naming expressions when they are only used once, and instead inline them where they are used. --invariantLevel 0: no invariants (default); 1: checks for abstract values; 2: checks for accessed built-ins; 3: internal consistency @@ -94,6 +96,7 @@ function run( let debugIdentifiers: void | Array; let lazyObjectsRuntime: string; let heapGraphFilePath: void | string; + let dumpIRFilePath: void | string; let debugInFilePath: string; let debugOutFilePath: string; let reactOutput: ReactOutputTypes = "create-element"; @@ -230,6 +233,10 @@ function run( heapGraphFilePath = args.shift(); // do not include this in reproArguments needed by --repro[OnFatalError/Unconditionally], as path is likely not portable between environments break; + case "dumpIRFilePath": + dumpIRFilePath = args.shift(); + // do not include this in reproArguments needed by --repro[OnFatalError/Unconditionally], as path is likely not portable between environments + break; case "reactOutput": arg = args.shift(); if (!ReactOutputValues.includes(arg)) { @@ -306,6 +313,7 @@ function run( "--check [start[, number]]", "--lazyObjectsRuntime lazyObjectsRuntimeName", "--heapGraphFilePath heapGraphFilePath", + "--dumpIRFilePath dumpIRFilePath", "--reactOutput " + ReactOutputValues.join(" | "), "--repro reprofile.zip", "--cpuprofile name.cpuprofile", @@ -355,6 +363,16 @@ function run( flags ); if (heapGraphFilePath !== undefined) resolvedOptions.heapGraphFormat = "DotLanguage"; + if (dumpIRFilePath !== undefined) { + resolvedOptions.onExecute = (realm, optimizedFunctions) => { + let text = ""; + new TextPrinter(line => { + text += line + "\n"; + }).print(realm, optimizedFunctions); + invariant(dumpIRFilePath !== undefined); + fs.writeFileSync(dumpIRFilePath, text); + }; + } if (lazyObjectsRuntime !== undefined && (resolvedOptions.delayInitializations || resolvedOptions.inlineExpressions)) { console.error("lazy objects feature is incompatible with delayInitializations and inlineExpressions options"); process.exit(1); diff --git a/src/prepack-options.js b/src/prepack-options.js index 75d8fd8dd1..fbd51d035a 100644 --- a/src/prepack-options.js +++ b/src/prepack-options.js @@ -11,7 +11,9 @@ import type { ErrorHandler } from "./errors.js"; import type { SerializerOptions, RealmOptions, Compatibility, ReactOutputTypes, InvariantModeTypes } from "./options"; -import { Realm } from "./realm.js"; +import { type Realm } from "./realm.js"; +import { type Generator } from "./utils/generator.js"; +import { type FunctionValue } from "./values/index.js"; import type { DebuggerConfigArguments, DebugReproArguments } from "./types"; import type { BabelNodeFile } from "@babel/types"; @@ -58,6 +60,7 @@ export type PrepackOptions = {| debuggerConfigArgs?: DebuggerConfigArguments, debugReproArgs?: DebugReproArguments, onParse?: BabelNodeFile => void, + onExecute?: (Realm, Map) => void, arrayNestedOptimizedFunctionsEnabled?: boolean, |}; diff --git a/src/prepack-standalone.js b/src/prepack-standalone.js index a7e445459b..0ee8c4416f 100644 --- a/src/prepack-standalone.js +++ b/src/prepack-standalone.js @@ -62,7 +62,7 @@ export function prepackSources( return { code: "", map: undefined }; } else { let serializer = new Serializer(realm, getSerializerOptions(options)); - let serialized = serializer.init(sourceFileCollection, options.sourceMaps, options.onParse); + let serialized = serializer.init(sourceFileCollection, options.sourceMaps, options.onParse, options.onExecute); //Turn off the debugger if there is one if (realm.debuggerInstance) { diff --git a/src/react/components.js b/src/react/components.js index 01e1d234bc..603603345c 100644 --- a/src/react/components.js +++ b/src/react/components.js @@ -38,6 +38,7 @@ import type { ClassComponentMetadata, ReactComponentTreeConfig } from "../types. import type { ReactEvaluatedNode } from "../serializer/types.js"; import { FatalError } from "../errors.js"; import { type ComponentModel, ShapeInformation } from "../utils/ShapeInformation.js"; +import { PropertyDescriptor } from "../descriptors.js"; const lifecycleMethods = new Set([ "componentWillUnmount", @@ -231,9 +232,12 @@ export function createClassInstanceForFirstRenderOnly( newState.makeFinal(); for (let [key, binding] of stateToUpdate.properties) { - if (binding && binding.descriptor && binding.descriptor.enumerable) { - let value = getProperty(realm, stateToUpdate, key); - hardModifyReactObjectPropertyBinding(realm, newState, key, value); + if (binding && binding.descriptor) { + invariant(binding.descriptor instanceof PropertyDescriptor); + if (binding.descriptor.enumerable) { + let value = getProperty(realm, stateToUpdate, key); + hardModifyReactObjectPropertyBinding(realm, newState, key, value); + } } } diff --git a/src/react/elements.js b/src/react/elements.js index 512167a5c2..586a8c68e8 100644 --- a/src/react/elements.js +++ b/src/react/elements.js @@ -35,6 +35,7 @@ import { import { computeBinary } from "../evaluators/BinaryExpression.js"; import { CompilerDiagnostic, FatalError } from "../errors.js"; import { createOperationDescriptor } from "../utils/generator.js"; +import { PropertyDescriptor } from "../descriptors.js"; function createPropsObject( realm: Realm, @@ -110,8 +111,11 @@ function createPropsObject( const applyProperties = () => { if (config instanceof ObjectValue) { for (let [propKey, binding] of config.properties) { - if (binding && binding.descriptor && binding.descriptor.enumerable) { - setProp(propKey, Get(realm, config, propKey)); + if (binding && binding.descriptor) { + invariant(binding.descriptor instanceof PropertyDescriptor); + if (binding.descriptor.enumerable) { + setProp(propKey, Get(realm, config, propKey)); + } } } } @@ -138,19 +142,27 @@ function createPropsObject( // first see if we can apply all the defaultProps without needing the helper if (defaultProps instanceof ObjectValue && !defaultProps.isPartialObject()) { for (let [propName, binding] of defaultProps.properties) { - if (binding.descriptor !== undefined && binding.descriptor.value !== realm.intrinsics.undefined) { - // see if we have this on our props object - let propBinding = props.properties.get(propName); - // if the binding exists and value is abstract, it might be undefined - // so in that case we need the helper, otherwise we can continue - if ( - propBinding !== undefined && - !(propBinding.descriptor && propBinding.descriptor.value instanceof AbstractValue) - ) { - defaultPropsEvaluated++; - // if the value we have is undefined, we can apply the defaultProp - if (propBinding.descriptor && propBinding.descriptor.value === realm.intrinsics.undefined) { - hardModifyReactObjectPropertyBinding(realm, props, propName, Get(realm, defaultProps, propName)); + if (binding.descriptor !== undefined) { + invariant(binding.descriptor instanceof PropertyDescriptor); + if (binding.descriptor.value !== realm.intrinsics.undefined) { + // see if we have this on our props object + let propBinding = props.properties.get(propName); + // if the binding exists and value is abstract, it might be undefined + // so in that case we need the helper, otherwise we can continue + if ( + propBinding !== undefined && + !( + propBinding.descriptor instanceof PropertyDescriptor && + propBinding.descriptor.value instanceof AbstractValue + ) + ) { + defaultPropsEvaluated++; + // if the value we have is undefined, we can apply the defaultProp + if (propBinding.descriptor) { + invariant(propBinding.descriptor instanceof PropertyDescriptor); + if (propBinding.descriptor.value === realm.intrinsics.undefined) + hardModifyReactObjectPropertyBinding(realm, props, propName, Get(realm, defaultProps, propName)); + } } } } @@ -168,9 +180,12 @@ function createPropsObject( // as the helper function applies defaultProps on values that are undefined or do not // exist for (let [propName, binding] of props.properties) { - if (binding.descriptor !== undefined && binding.descriptor.value === realm.intrinsics.undefined) { - invariant(defaultProps instanceof AbstractObjectValue || defaultProps instanceof ObjectValue); - hardModifyReactObjectPropertyBinding(realm, props, propName, Get(realm, defaultProps, propName)); + if (binding.descriptor !== undefined) { + invariant(binding.descriptor instanceof PropertyDescriptor); + if (binding.descriptor.value === realm.intrinsics.undefined) { + invariant(defaultProps instanceof AbstractObjectValue || defaultProps instanceof ObjectValue); + hardModifyReactObjectPropertyBinding(realm, props, propName, Get(realm, defaultProps, propName)); + } } } // if we have children and they are abstract, they might be undefined at runtime @@ -218,8 +233,9 @@ function createPropsObject( invariant(false, "TODO: we need to eventually support this"); } else if (defaultProps instanceof ObjectValue) { for (let [propKey, binding] of defaultProps.properties) { - if (binding && binding.descriptor && binding.descriptor.enumerable) { - if (Get(realm, props, propKey) === realm.intrinsics.undefined) { + if (binding && binding.descriptor) { + invariant(binding.descriptor instanceof PropertyDescriptor); + if (binding.descriptor.enumerable && Get(realm, props, propKey) === realm.intrinsics.undefined) { setProp(propKey, Get(realm, defaultProps, propKey)); } } @@ -348,8 +364,9 @@ export function cloneReactElement( if (defaultProps instanceof ObjectValue) { for (let [propKey, binding] of defaultProps.properties) { - if (binding && binding.descriptor && binding.descriptor.enumerable) { - if (Get(realm, props, propKey) === realm.intrinsics.undefined) { + if (binding && binding.descriptor) { + invariant(binding.descriptor instanceof PropertyDescriptor); + if (binding.descriptor.enumerable && Get(realm, props, propKey) === realm.intrinsics.undefined) { setProp(propKey, Get(realm, defaultProps, propKey)); } } diff --git a/src/react/reconcilation.js b/src/react/reconcilation.js index 73a8ea9416..e51432db7d 100644 --- a/src/react/reconcilation.js +++ b/src/react/reconcilation.js @@ -77,6 +77,7 @@ 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 { PropertyDescriptor } from "../descriptors.js"; type ComponentResolutionStrategy = | "NORMAL" @@ -127,6 +128,7 @@ function setContextCurrentValue(contextObject: ObjectValue | AbstractObjectValue let binding = contextObject.properties.get("currentValue"); if (binding && binding.descriptor) { + invariant(binding.descriptor instanceof PropertyDescriptor); binding.descriptor.value = value; } else { invariant(false, "setContextCurrentValue failed to set the currentValue"); @@ -1477,15 +1479,18 @@ export class Reconciler { this._findReactComponentTrees(props, evaluatedNode, treatFunctionsAs, componentType, context, branchStatus); } else { for (let [propName, binding] of value.properties) { - if (binding && binding.descriptor && binding.descriptor.enumerable) { - this._findReactComponentTrees( - getProperty(this.realm, value, propName), - evaluatedNode, - treatFunctionsAs, - componentType, - context, - branchStatus - ); + if (binding && binding.descriptor) { + invariant(binding.descriptor instanceof PropertyDescriptor); + if (binding.descriptor.enumerable) { + this._findReactComponentTrees( + getProperty(this.realm, value, propName), + evaluatedNode, + treatFunctionsAs, + componentType, + context, + branchStatus + ); + } } } } diff --git a/src/react/utils.js b/src/react/utils.js index 3b98743eed..e80db65da3 100644 --- a/src/react/utils.js +++ b/src/react/utils.js @@ -31,7 +31,7 @@ import { } from "../values/index.js"; import { TemporalObjectAssignEntry } from "../utils/generator.js"; import type { Descriptor, ReactComponentTreeConfig, ReactHint, PropertyBinding } from "../types.js"; -import { Get, cloneDescriptor } from "../methods/index.js"; +import { Get, IsDataDescriptor } from "../methods/index.js"; import { computeBinary } from "../evaluators/BinaryExpression.js"; import type { AdditionalFunctionEffects, ReactEvaluatedNode } from "../serializer/types.js"; import invariant from "../invariant.js"; @@ -40,6 +40,7 @@ import traverse from "@babel/traverse"; import * as t from "@babel/types"; import type { BabelNodeStatement } from "@babel/types"; import { CompilerDiagnostic, FatalError } from "../errors.js"; +import { cloneDescriptor, PropertyDescriptor } from "../descriptors.js"; export type ReactSymbolTypes = | "react.element" @@ -109,6 +110,7 @@ export function getReactSymbol(symbolKey: ReactSymbolTypes, realm: Realm): Symbo let SymbolForDescriptor = SymbolFor.descriptor; if (SymbolForDescriptor !== undefined) { + invariant(SymbolForDescriptor instanceof PropertyDescriptor); let SymbolForValue = SymbolForDescriptor.value; if (SymbolForValue instanceof ObjectValue && typeof SymbolForValue.$Call === "function") { reactSymbol = SymbolForValue.$Call(realm.intrinsics.Symbol, [new StringValue(realm, symbolKey)]); @@ -243,6 +245,7 @@ export function forEachArrayValue( let elementProperty = array.properties.get("" + i); let elementPropertyDescriptor = elementProperty && elementProperty.descriptor; if (elementPropertyDescriptor) { + invariant(elementPropertyDescriptor instanceof PropertyDescriptor); let elementValue = elementPropertyDescriptor.value; if (elementValue instanceof Value) { mapFunc(elementValue, i); @@ -266,6 +269,7 @@ export function mapArrayValue( let elementProperty = array.properties.get("" + i); let elementPropertyDescriptor = elementProperty && elementProperty.descriptor; if (elementPropertyDescriptor) { + invariant(elementPropertyDescriptor instanceof PropertyDescriptor); let elementValue = elementPropertyDescriptor.value; if (elementValue instanceof Value) { let newElement = mapFunc(elementValue, elementPropertyDescriptor); @@ -294,7 +298,7 @@ export function convertSimpleClassComponentToFunctionalComponent( ): void { let prototype = complexComponentType.properties.get("prototype"); invariant(prototype); - invariant(prototype.descriptor); + invariant(prototype.descriptor instanceof PropertyDescriptor); prototype.descriptor.configurable = true; Properties.DeletePropertyOrThrow(realm, complexComponentType, "prototype"); @@ -349,7 +353,10 @@ function createBinding(descriptor: void | Descriptor, key: string | SymbolValue, function cloneProperties(realm: Realm, properties: Map, object: ObjectValue): Map { let newProperties = new Map(); for (let [propertyName, { descriptor }] of properties) { - newProperties.set(propertyName, createBinding(cloneDescriptor(descriptor), propertyName, object)); + newProperties.set( + propertyName, + createBinding(cloneDescriptor(descriptor.throwIfNotConcrete(realm)), propertyName, object) + ); } return newProperties; } @@ -357,7 +364,7 @@ function cloneProperties(realm: Realm, properties: Map, object: Obj function cloneSymbols(realm: Realm, symbols: Map, object: ObjectValue): Map { let newSymbols = new Map(); for (let [symbol, { descriptor }] of symbols) { - newSymbols.set(symbol, createBinding(cloneDescriptor(descriptor), symbol, object)); + newSymbols.set(symbol, createBinding(cloneDescriptor(descriptor.throwIfNotConcrete(realm)), symbol, object)); } return newSymbols; } @@ -482,7 +489,7 @@ export function convertFunctionalComponentToComplexClassComponent( export function normalizeFunctionalComponentParamaters(func: ECMAScriptSourceFunctionValue): void { // fix the length as we may change the arguments let lengthProperty = GetDescriptorForProperty(func, "length"); - invariant(lengthProperty); + invariant(lengthProperty instanceof PropertyDescriptor); lengthProperty.writable = false; lengthProperty.enumerable = false; lengthProperty.configurable = true; @@ -637,6 +644,7 @@ export function getProperty( if (!descriptor) { return realm.intrinsics.undefined; } + invariant(descriptor instanceof PropertyDescriptor); let value = descriptor.value; if (value === undefined) { AbstractValue.reportIntrospectionError(object, `react/utils/getProperty unsupported getter/setter property`); @@ -912,11 +920,14 @@ export function cloneProps(realm: Realm, props: ObjectValue, newChildren?: Value let clonedProps = new ObjectValue(realm, realm.intrinsics.ObjectPrototype); for (let [propName, binding] of props.properties) { - if (binding && binding.descriptor && binding.descriptor.enumerable) { - if (newChildren !== undefined && propName === "children") { - Properties.Set(realm, clonedProps, propName, newChildren, true); - } else { - Properties.Set(realm, clonedProps, propName, getProperty(realm, props, propName), true); + if (binding && binding.descriptor) { + invariant(binding.descriptor instanceof PropertyDescriptor); + if (binding.descriptor.enumerable) { + if (newChildren !== undefined && propName === "children") { + Properties.Set(realm, clonedProps, propName, newChildren, true); + } else { + Properties.Set(realm, clonedProps, propName, getProperty(realm, props, propName), true); + } } } } @@ -999,20 +1010,19 @@ export function hardModifyReactObjectPropertyBinding( if (binding === undefined) { binding = { object, - descriptor: { + descriptor: new PropertyDescriptor({ configurable: true, enumerable: true, value: undefined, writable: true, - }, + }), key: propName, }; } let descriptor = binding.descriptor; - invariant(descriptor !== undefined); - let newDescriptor = Object.assign({}, descriptor, { - value, - }); + invariant(descriptor instanceof PropertyDescriptor && IsDataDescriptor(realm, descriptor)); + let newDescriptor = new PropertyDescriptor(descriptor); + newDescriptor.value = value; let newBinding = Object.assign({}, binding, { descriptor: newDescriptor, }); diff --git a/src/realm.js b/src/realm.js index 0fb60375f3..3d2a77f043 100644 --- a/src/realm.js +++ b/src/realm.js @@ -54,7 +54,7 @@ import { DeclarativeEnvironmentRecord, } from "./environment.js"; import type { Binding } from "./environment.js"; -import { cloneDescriptor, Construct } from "./methods/index.js"; +import { Construct } from "./methods/index.js"; import { AbruptCompletion, Completion, @@ -81,6 +81,12 @@ import { Widen, } from "./singletons.js"; import type { ReactSymbolTypes } from "./react/utils.js"; +import { + cloneDescriptor, + AbstractJoinedDescriptor, + InternalSlotDescriptor, + PropertyDescriptor, +} from "./descriptors.js"; import type { BabelNode, BabelNodeSourceLocation, BabelNodeLVal } from "@babel/types"; export type BindingEntry = { hasLeaked: boolean, value: void | Value }; export type Bindings = Map; @@ -641,7 +647,7 @@ export class Realm { invariant(globalObject instanceof ObjectValue); let binding = globalObject.properties.get("__checkedBindings"); invariant(binding !== undefined); - let checkedBindingsObject = binding.descriptor && binding.descriptor.value; + let checkedBindingsObject = binding.descriptor && binding.descriptor.throwIfNotConcrete(this).value; invariant(checkedBindingsObject instanceof ObjectValue); return checkedBindingsObject; } @@ -662,7 +668,7 @@ export class Realm { let id = `__propertyHasBeenChecked__${objectId}:${P}`; let binding = this._getCheckedBindings().properties.get(id); if (binding === undefined) return false; - let value = binding.descriptor && binding.descriptor.value; + let value = binding.descriptor && binding.descriptor.throwIfNotConcrete(this).value; return value instanceof Value && !value.mightNotBeTrue(); } @@ -1080,7 +1086,7 @@ export class Realm { if (newlyCreatedObjects.has(key.object) || key.object.refuseSerialization) { return; } - let value = val && val.value; + let value = val && val.throwIfNotConcrete(this).value; if (value instanceof AbstractValue) { invariant(value.operationDescriptor !== undefined); let tval = gen.deriveAbstract( @@ -1102,7 +1108,7 @@ export class Realm { let path = key.pathNode; let tval = tvalFor.get(key); invariant(val !== undefined); - let value = val.value; + let value = val.throwIfNotConcrete(this).value; invariant(value instanceof Value); let keyKey = key.key; if (typeof keyKey === "string") { @@ -1516,7 +1522,20 @@ export class Realm { } this.callReportPropertyAccess(binding); if (this.modifiedProperties !== undefined && !this.modifiedProperties.has(binding)) { - this.modifiedProperties.set(binding, cloneDescriptor(binding.descriptor)); + let clone; + let desc = binding.descriptor; + if (desc === undefined) { + clone = undefined; + } else if (desc instanceof AbstractJoinedDescriptor) { + clone = new AbstractJoinedDescriptor(desc.joinCondition, desc.descriptor1, desc.descriptor2); + } else if (desc instanceof PropertyDescriptor) { + clone = cloneDescriptor(desc); + } else if (desc instanceof InternalSlotDescriptor) { + clone = new InternalSlotDescriptor(desc.value); + } else { + invariant(false, "unknown descriptor"); + } + this.modifiedProperties.set(binding, clone); } } @@ -1604,8 +1623,9 @@ export class Realm { for (let [key, binding] of template.properties) { if (binding === undefined || binding.descriptor === undefined) continue; // deleted invariant(binding.descriptor !== undefined); - let value = binding.descriptor.value; - Properties.ThrowIfMightHaveBeenDeleted(value); + let desc = binding.descriptor.throwIfNotConcrete(this); + let value = desc.value; + Properties.ThrowIfMightHaveBeenDeleted(desc); if (value === undefined) { AbstractValue.reportIntrospectionError(abstractValue, key); throw new FatalError(); diff --git a/src/serializer/LoggingTracer.js b/src/serializer/LoggingTracer.js index 3f63119ead..79b1d5d91f 100644 --- a/src/serializer/LoggingTracer.js +++ b/src/serializer/LoggingTracer.js @@ -33,7 +33,7 @@ function describeValue(realm: Realm, v: Value): string { if (v instanceof NumberValue || v instanceof BooleanValue) return v.value.toString(); if (v instanceof UndefinedValue) return "undefined"; if (v instanceof NullValue) return "null"; - if (v instanceof StringValue) return `"${v.value}"`; // TODO: proper escaping + if (v instanceof StringValue) return JSON.stringify(v.value); if (v instanceof FunctionValue) return To.ToStringPartial(realm, Get(realm, v, "name")) || "(anonymous function)"; if (v instanceof ObjectValue) return "(some object)"; if (v instanceof AbstractValue) return "(some abstract value)"; diff --git a/src/serializer/ResidualHeapSerializer.js b/src/serializer/ResidualHeapSerializer.js index afc6f45995..6176cdd7e6 100644 --- a/src/serializer/ResidualHeapSerializer.js +++ b/src/serializer/ResidualHeapSerializer.js @@ -86,6 +86,7 @@ import { type Replacement, getReplacement } from "./ResidualFunctionInstantiator import { describeValue } from "../utils.js"; import { getAsPropertyNameExpression } from "../utils/babelhelpers.js"; import { ResidualOperationSerializer } from "./ResidualOperationSerializer.js"; +import { PropertyDescriptor, AbstractJoinedDescriptor } from "../descriptors.js"; function commentStatement(text: string) { let s = t.emptyStatement(); @@ -317,7 +318,8 @@ export class ResidualHeapSerializer { // TODO #2259: Make deduplication in the face of leaking work for custom accessors let isCertainlyLeaked = !obj.mightNotBeLeakedObject(); let shouldDropAsAssignedProp = (descriptor: Descriptor | void) => - isCertainlyLeaked && (descriptor !== undefined && (descriptor.get === undefined && descriptor.set === undefined)); + isCertainlyLeaked && + (descriptor instanceof PropertyDescriptor && (descriptor.get === undefined && descriptor.set === undefined)); // inject properties for (let [key, propertyBinding] of properties) { @@ -348,14 +350,11 @@ export class ResidualHeapSerializer { if (obj.unknownProperty !== undefined) { let desc = obj.unknownProperty.descriptor; if (desc !== undefined) { - let val = desc.value; - invariant(val instanceof AbstractValue); let semaphore = this._acquireOneObjectSemaphore(obj); this.emitter.emitNowOrAfterWaitingForDependencies( - this._getNestedValuesFromAbstract(val, [obj]), + this._getNestedValuesFromAbstractDescriptor(desc, [obj]), () => { - invariant(val instanceof AbstractValue); - this._emitPropertiesWithComputedNames(obj, val); + this._emitPropertiesWithComputedNamesDescriptor(obj, desc); if (semaphore !== undefined) semaphore.releaseOne(); }, this.emitter.getBody() @@ -444,6 +443,22 @@ export class ResidualHeapSerializer { } } + _getNestedValuesFromAbstractDescriptor(desc: void | Descriptor, values: Array): Array { + if (desc === undefined) return values; + if (desc instanceof PropertyDescriptor) { + let val = desc.value; + invariant(val instanceof AbstractValue); + return this._getNestedValuesFromAbstract(val, values); + } else if (desc instanceof AbstractJoinedDescriptor) { + values.push(desc.joinCondition); + this._getNestedValuesFromAbstractDescriptor(desc.descriptor1, values); + this._getNestedValuesFromAbstractDescriptor(desc.descriptor2, values); + return values; + } else { + invariant(false, "unknown descriptor"); + } + } + _getNestedValuesFromAbstract(absVal: AbstractValue, values: Array): Array { if (absVal.kind === "widened property") return values; if (absVal.kind === "template for prototype member expression") return values; @@ -477,6 +492,60 @@ export class ResidualHeapSerializer { return values; } + _emitPropertiesWithComputedNamesDescriptor(obj: ObjectValue, desc: void | Descriptor): void { + if (desc === undefined) return; + if (desc instanceof PropertyDescriptor) { + let val = desc.value; + invariant(val instanceof AbstractValue); + this._emitPropertiesWithComputedNames(obj, val); + } else if (desc instanceof AbstractJoinedDescriptor) { + let serializedCond = this.serializeValue(desc.joinCondition); + + let valuesToProcess = new Set(); + let consequentStatement; + let alternateStatement; + + if (desc.descriptor1) { + let oldBody = this.emitter.beginEmitting( + "consequent", + { + type: "ConditionalAssignmentBranch", + parentBody: undefined, + entries: [], + done: false, + }, + /*isChild*/ true + ); + this._emitPropertiesWithComputedNamesDescriptor(obj, desc.descriptor1); + let consequentBody = this.emitter.endEmitting("consequent", oldBody, valuesToProcess, /*isChild*/ true); + consequentStatement = t.blockStatement(consequentBody.entries); + } + if (desc.descriptor2) { + let oldBody = this.emitter.beginEmitting( + "alternate", + { + type: "ConditionalAssignmentBranch", + parentBody: undefined, + entries: [], + done: false, + }, + /*isChild*/ true + ); + this._emitPropertiesWithComputedNamesDescriptor(obj, desc.descriptor2); + let alternateBody = this.emitter.endEmitting("alternate", oldBody, valuesToProcess, /*isChild*/ true); + alternateStatement = t.blockStatement(alternateBody.entries); + } + if (consequentStatement) { + this.emitter.emit(t.ifStatement(serializedCond, consequentStatement, alternateStatement)); + } else if (alternateStatement) { + this.emitter.emit(t.ifStatement(t.unaryExpression("!", serializedCond), alternateStatement)); + } + this.emitter.processValues(valuesToProcess); + } else { + invariant(false, "unknown descriptor"); + } + } + _emitPropertiesWithComputedNames(obj: ObjectValue, absVal: AbstractValue): void { if (absVal.kind === "widened property") return; if (absVal.kind === "template for prototype member expression") return; @@ -577,7 +646,7 @@ export class ResidualHeapSerializer { key: string | SymbolValue | AbstractValue, desc: Descriptor ): BabelNodeStatement { - if (desc.joinCondition) { + if (desc instanceof AbstractJoinedDescriptor) { let cond = this.serializeValue(desc.joinCondition); invariant(cond !== undefined); let trueBody; @@ -603,6 +672,7 @@ export class ResidualHeapSerializer { if (falseBody) return t.ifStatement(t.unaryExpression("!", cond), falseBody); invariant(false); } + invariant(desc instanceof PropertyDescriptor); if (locationFunction !== undefined && this._canEmbedProperty(val, key, desc)) { let descValue = desc.value; invariant(descValue instanceof Value); @@ -630,8 +700,8 @@ export class ResidualHeapSerializer { let descriptorsKey = []; for (let boolKey of boolKeys) { - if (boolKey in desc) { - let b = desc[boolKey]; + if ((desc: any)[boolKey] !== undefined) { + let b: boolean = (desc: any)[boolKey]; invariant(b !== undefined); descProps.push(t.objectProperty(t.identifier(boolKey), t.booleanLiteral(b))); descriptorsKey.push(`${boolKey}:${b.toString()}`); @@ -650,8 +720,8 @@ export class ResidualHeapSerializer { invariant(descriptorId !== undefined); for (let descKey of valKeys) { - if (descKey in desc) { - let descValue = desc[descKey]; + if ((desc: any)[descKey] !== undefined) { + let descValue: Value = (desc: any)[descKey]; invariant(descValue instanceof Value); if (descValue instanceof UndefinedValue) { this.serializeValue(descValue); @@ -1172,13 +1242,24 @@ export class ResidualHeapSerializer { } } - _getDescriptorValues(desc: Descriptor): Array { - if (desc.joinCondition !== undefined) return [desc.joinCondition]; - invariant(desc.value === undefined || desc.value instanceof Value); - if (desc.value !== undefined) return [desc.value]; - invariant(desc.get !== undefined); - invariant(desc.set !== undefined); - return [desc.get, desc.set]; + _getDescriptorValues(desc: void | Descriptor): Array { + if (desc === undefined) { + return []; + } else if (desc instanceof PropertyDescriptor) { + invariant(desc.value === undefined || desc.value instanceof Value); + if (desc.value !== undefined) return [desc.value]; + invariant(desc.get !== undefined); + invariant(desc.set !== undefined); + return [desc.get, desc.set]; + } else if (desc instanceof AbstractJoinedDescriptor) { + return [ + desc.joinCondition, + ...this._getDescriptorValues(desc.descriptor1), + ...this._getDescriptorValues(desc.descriptor2), + ]; + } else { + invariant(false, "unknown descriptor"); + } } _deleteProperty(location: BabelNodeLVal): void { @@ -1251,6 +1332,7 @@ export class ResidualHeapSerializer { if (propertyBinding !== undefined) { let descriptor = propertyBinding.descriptor; // "descriptor === undefined" means this array item has been deleted. + invariant(descriptor === undefined || descriptor instanceof PropertyDescriptor); if ( descriptor !== undefined && descriptor.value !== undefined && @@ -1695,7 +1777,7 @@ export class ResidualHeapSerializer { // Checks whether a property can be defined via simple assignment, or using object literal syntax. _canEmbedProperty(obj: ObjectValue, key: string | SymbolValue | AbstractValue, prop: Descriptor): boolean { - if (prop.joinCondition !== undefined) return false; + if (!(prop instanceof PropertyDescriptor)) return false; let targetDescriptor = this.residualHeapInspector.getTargetIntegrityDescriptor(obj); @@ -1744,7 +1826,8 @@ export class ResidualHeapSerializer { // TODO #2259: Make deduplication in the face of leaking work for custom accessors let shouldDropAsAssignedProp = (descriptor: Descriptor | void) => - isCertainlyLeaked && (descriptor !== undefined && (descriptor.get === undefined && descriptor.set === undefined)); + isCertainlyLeaked && + (descriptor instanceof PropertyDescriptor && (descriptor.get === undefined && descriptor.set === undefined)); if (val.temporalAlias !== undefined) { return t.objectExpression(props); @@ -1757,7 +1840,8 @@ export class ResidualHeapSerializer { if (propertyBinding.pathNode !== undefined) continue; // written to inside loop let descriptor = propertyBinding.descriptor; - if (descriptor === undefined || descriptor.value === undefined) continue; // deleted + if (descriptor === undefined || !(descriptor instanceof PropertyDescriptor) || descriptor.value === undefined) + continue; // deleted let serializedKey = getAsPropertyNameExpression(key); if (this._canEmbedProperty(val, key, descriptor)) { diff --git a/src/serializer/ResidualHeapVisitor.js b/src/serializer/ResidualHeapVisitor.js index 3eaddbeb8e..e2089e1cde 100644 --- a/src/serializer/ResidualHeapVisitor.js +++ b/src/serializer/ResidualHeapVisitor.js @@ -67,6 +67,7 @@ import { createPathConditions, Environment, To } from "../singletons.js"; import { isReactElement, isReactPropsObject, valueIsReactLibraryObject } from "../react/utils.js"; import { ResidualReactElementVisitor } from "./ResidualReactElementVisitor.js"; import { GeneratorDAG } from "./GeneratorDAG.js"; +import { PropertyDescriptor, AbstractJoinedDescriptor } from "../descriptors.js"; type BindingState = {| capturedBindings: Set, @@ -321,22 +322,17 @@ export class ResidualHeapVisitor { if ( !(obj instanceof ArrayValue) && !obj.mightNotBeLeakedObject() && - (descriptor !== undefined && (descriptor.get === undefined && descriptor.set === undefined)) - ) + (descriptor instanceof PropertyDescriptor && (descriptor.get === undefined && descriptor.set === undefined)) + ) { continue; + } - invariant(propertyBindingValue); this.visitObjectProperty(propertyBindingValue); } // inject properties with computed names if (obj.unknownProperty !== undefined) { - let desc = obj.unknownProperty.descriptor; - if (desc !== undefined) { - let val = desc.value; - invariant(val instanceof AbstractValue); - this.visitObjectPropertiesWithComputedNames(val); - } + this.visitObjectPropertiesWithComputedNamesDescriptor(obj.unknownProperty.descriptor); } // prototype @@ -376,6 +372,22 @@ export class ResidualHeapVisitor { } } + visitObjectPropertiesWithComputedNamesDescriptor(desc: void | Descriptor): void { + if (desc !== undefined) { + if (desc instanceof PropertyDescriptor) { + let val = desc.value; + invariant(val instanceof AbstractValue); + this.visitObjectPropertiesWithComputedNames(val); + } else if (desc instanceof AbstractJoinedDescriptor) { + this.visitValue(desc.joinCondition); + this.visitObjectPropertiesWithComputedNamesDescriptor(desc.descriptor1); + this.visitObjectPropertiesWithComputedNamesDescriptor(desc.descriptor2); + } else { + invariant(false, "unknown descriptor"); + } + } + } + visitObjectPropertiesWithComputedNames(absVal: AbstractValue): void { if (absVal.kind === "widened property") return; if (absVal.kind === "template for prototype member expression") return; @@ -407,17 +419,19 @@ export class ResidualHeapVisitor { } } - visitDescriptor(desc: Descriptor): void { - invariant(desc.value === undefined || desc.value instanceof Value); - if (desc.joinCondition !== undefined) { + visitDescriptor(desc: void | Descriptor): void { + if (desc === undefined) { + } else if (desc instanceof PropertyDescriptor) { + if (desc.value !== undefined) desc.value = this.visitEquivalentValue(desc.value); + if (desc.get !== undefined) this.visitValue(desc.get); + if (desc.set !== undefined) this.visitValue(desc.set); + } else if (desc instanceof AbstractJoinedDescriptor) { desc.joinCondition = this.visitEquivalentValue(desc.joinCondition); if (desc.descriptor1 !== undefined) this.visitDescriptor(desc.descriptor1); if (desc.descriptor2 !== undefined) this.visitDescriptor(desc.descriptor2); - return; + } else { + invariant(false, "unknown descriptor"); } - if (desc.value !== undefined) desc.value = this.visitEquivalentValue(desc.value); - if (desc.get !== undefined) this.visitValue(desc.get); - if (desc.set !== undefined) this.visitValue(desc.set); } visitValueArray(val: ObjectValue): void { diff --git a/src/serializer/ResidualOperationSerializer.js b/src/serializer/ResidualOperationSerializer.js index f47f0fd4c0..5b18648e30 100644 --- a/src/serializer/ResidualOperationSerializer.js +++ b/src/serializer/ResidualOperationSerializer.js @@ -739,15 +739,15 @@ export class ResidualOperationSerializer { } _serializeDefineProperty( - { object, desc }: OperationDescriptorData, + { object, descriptor }: OperationDescriptorData, [propName]: Array, context?: SerializationContext ): BabelNodeStatement { let propString = ((propName: any): BabelNodeStringLiteral).value; invariant(object !== undefined); - invariant(desc !== undefined); + invariant(descriptor !== undefined); invariant(context !== undefined); - return context.emitDefinePropertyBody(object, propString, desc); + return context.emitDefinePropertyBody(object, propString, descriptor); } _serializeFBMocksMagicGlobalFunction( diff --git a/src/serializer/functions.js b/src/serializer/functions.js index 71100d6a87..24b232a26e 100644 --- a/src/serializer/functions.js +++ b/src/serializer/functions.js @@ -37,6 +37,7 @@ import { handleReportedSideEffect } from "./utils.js"; import type { ArgModel } from "../types.js"; import { optionalStringOfLocation } from "../utils/babelhelpers"; import { Properties, Utils } from "../singletons.js"; +import { PropertyDescriptor } from "../descriptors.js"; type AdditionalFunctionEntry = { value: ECMAScriptSourceFunctionValue | AbstractValue, @@ -54,7 +55,6 @@ export class Functions { } realm: Realm; - // maps back from FunctionValue to the expression string moduleTracer: ModuleTracer; writeEffects: WriteEffects; _noopFunction: void | ECMAScriptSourceFunctionValue; @@ -120,9 +120,9 @@ export class Functions { for (let funcId of Properties.GetOwnPropertyKeysArray(realm, globalRecordedAdditionalFunctionsMap, true, false)) { let property = globalRecordedAdditionalFunctionsMap.properties.get(funcId); if (property) { - let value = property.descriptor && property.descriptor.value; + invariant(property.descriptor instanceof PropertyDescriptor); + let value = property.descriptor.value; invariant(value !== undefined); - invariant(value instanceof Value); let entry = this._optimizedFunctionEntryOfValue(value); if (entry) recordedAdditionalFunctions.push(entry); } @@ -175,7 +175,7 @@ export class Functions { } } - getDeclaringOptimizedFunction(functionValue: ECMAScriptSourceFunctionValue) { + getDeclaringOptimizedFunction(functionValue: ECMAScriptSourceFunctionValue): void | FunctionValue { for (let [optimizedFunctionValue, additionalEffects] of this.writeEffects) { // CreatedObjects is all objects created by this optimized function but not // nested optimized functions. @@ -383,7 +383,7 @@ export class Functions { if (!location) return; // happens only when accessing an additional function property if (pbs.has(pb) && !conflicts.has(location)) { let originalLocation = - pb.descriptor && pb.descriptor.value && !Array.isArray(pb.descriptor.value) + pb.descriptor instanceof PropertyDescriptor && pb.descriptor.value && !Array.isArray(pb.descriptor.value) ? pb.descriptor.value.expressionLocation : undefined; let keyString = pb.key instanceof Value ? pb.key.toDisplayString() : pb.key; diff --git a/src/serializer/serializer.js b/src/serializer/serializer.js index 4bcc5c2139..81dc9df646 100644 --- a/src/serializer/serializer.js +++ b/src/serializer/serializer.js @@ -36,8 +36,9 @@ import { ResidualHeapRefCounter } from "./ResidualHeapRefCounter"; import { ResidualHeapGraphGenerator } from "./ResidualHeapGraphGenerator"; import { Referentializer } from "./Referentializer.js"; import { Get } from "../methods/index.js"; -import { ObjectValue, Value } from "../values/index.js"; +import { ObjectValue, Value, FunctionValue } from "../values/index.js"; import { Properties } from "../singletons.js"; +import { PropertyDescriptor } from "../descriptors.js"; export class Serializer { constructor(realm: Realm, serializerOptions: SerializerOptions = {}) { @@ -115,7 +116,7 @@ export class Serializer { for (let name of Properties.GetOwnPropertyKeysArray(realm, output, false, false)) { let property = output.properties.get(name); if (!property) continue; - let value = property.descriptor && property.descriptor.value; + let value = property.descriptor instanceof PropertyDescriptor && property.descriptor.value; if (!(value instanceof Value)) continue; generator.emitGlobalDeclaration(name, value); } @@ -125,7 +126,8 @@ export class Serializer { init( sourceFileCollection: SourceFileCollection, sourceMaps?: boolean = false, - onParse?: BabelNodeFile => void + onParse?: BabelNodeFile => void, + onExecute?: (Realm, Map) => void ): void | SerializedResult { let realmStatistics = this.realm.statistics; invariant(realmStatistics instanceof SerializerStatistics, "serialization requires SerializerStatistics"); @@ -158,6 +160,15 @@ export class Serializer { }); } + statistics.dumpIR.measure(() => { + if (onExecute !== undefined) { + let optimizedFunctions = new Map(); + for (let [functionValue, additionalFunctionEffects] of this.functions.writeEffects) + optimizedFunctions.set(functionValue, additionalFunctionEffects.generator); + onExecute(this.realm, optimizedFunctions); + } + }); + statistics.processCollectedNestedOptimizedFunctions.measure(() => this.functions.processCollectedNestedOptimizedFunctions(environmentRecordIdAfterGlobalCode) ); diff --git a/src/serializer/statistics.js b/src/serializer/statistics.js index 1295c09a23..230ecb0231 100644 --- a/src/serializer/statistics.js +++ b/src/serializer/statistics.js @@ -34,6 +34,7 @@ export class SerializerStatistics extends RealmStatistics { this.referenceCounts = new PerformanceTracker(getTime, getMemory); this.serializePass = new PerformanceTracker(getTime, getMemory); this.babelGenerate = new PerformanceTracker(getTime, getMemory); + this.dumpIR = new PerformanceTracker(getTime, getMemory); } resetBeforePass(): void { @@ -100,6 +101,7 @@ export class SerializerStatistics extends RealmStatistics { referenceCounts: PerformanceTracker; serializePass: PerformanceTracker; babelGenerate: PerformanceTracker; + dumpIR: PerformanceTracker; log(): void { super.log(); @@ -136,7 +138,7 @@ export class SerializerStatistics extends RealmStatistics { this.optimizeReactComponentTreeRoots )} optimizing react component tree roots, ${format( this.checkThatFunctionsAreIndependent - )} evaluating functions to optimize` + )} evaluating functions to optimize, ${format(this.dumpIR)} dumping IR` ); console.log( `${format(this.deepTraversal)} visiting residual heap, ${format( diff --git a/src/serializer/utils.js b/src/serializer/utils.js index 39164c1d79..c0e275c20b 100644 --- a/src/serializer/utils.js +++ b/src/serializer/utils.js @@ -30,6 +30,7 @@ import type { AdditionalFunctionEffects } from "./types"; import type { Binding } from "../environment.js"; import type { BabelNodeSourceLocation } from "@babel/types"; import { optionalStringOfLocation } from "../utils/babelhelpers.js"; +import { PropertyDescriptor } from "../descriptors.js"; /** * Get index property list length by searching array properties list for the max index key value plus 1. @@ -126,6 +127,7 @@ export function withDescriptorValue( func: Function ): void { if (descriptor !== undefined) { + invariant(descriptor instanceof PropertyDescriptor); // TODO: Handle joined descriptors. if (descriptor.value !== undefined) { func(propertyNameOrSymbol, descriptor.value, "value"); } else { @@ -142,8 +144,17 @@ export function withDescriptorValue( export const ClassPropertiesToIgnore: Set = new Set(["arguments", "name", "caller"]); export function canIgnoreClassLengthProperty(val: ObjectValue, desc: void | Descriptor, logger: Logger): boolean { - if (desc && desc.value === undefined) { - logger.logError(val, "Functions with length accessor properties are not supported in residual heap."); + if (desc) { + if (desc instanceof PropertyDescriptor) { + if (desc.value === undefined) { + logger.logError(val, "Functions with length accessor properties are not supported in residual heap."); + } + } else { + logger.logError( + val, + "Functions with length properties with different attributes are not supported in residual heap." + ); + } } return true; } @@ -170,6 +181,11 @@ export function getObjectPrototypeMetadata( if (_constructor.descriptor === undefined) { throw new FatalError("TODO #1024: implement object prototype serialization with deleted constructor"); } + if (!(_constructor.descriptor instanceof PropertyDescriptor)) { + throw new FatalError( + "TODO #1024: implement object prototype serialization with multiple constructor attributes" + ); + } let classFunc = _constructor.descriptor.value; if (classFunc instanceof ECMAScriptSourceFunctionValue) { constructor = classFunc; diff --git a/src/types.js b/src/types.js index 0485237908..47f2304db6 100644 --- a/src/types.js +++ b/src/types.js @@ -29,6 +29,7 @@ import type { } from "./values/index.js"; import { Value } from "./values/index.js"; import { Completion } from "./completions.js"; +import type { Descriptor as DescriptorClass } from "./descriptors.js"; import { EnvironmentRecord, LexicalEnvironment, Reference } from "./environment.js"; import { ObjectValue } from "./values/index.js"; import type { @@ -134,26 +135,7 @@ export type DataBlock = Uint8Array; // -export type Descriptor = { - writable?: boolean, - enumerable?: boolean, - configurable?: boolean, - - // If value instanceof EmptyValue, then this descriptor indicates that the - // corresponding property has been deleted. - // Only internal properties (those starting with $ / where internalSlot of owning property binding is true) will ever have array values. - value?: Value | Array, - - get?: UndefinedValue | CallableObjectValue | AbstractValue, - set?: UndefinedValue | CallableObjectValue | AbstractValue, - - // Only used if the result of a join of two descriptors is not a data descriptor with identical attribute values. - // When present, any update to the property must produce effects that are the join of updating both desriptors, - // using joinCondition as the condition of the join. - joinCondition?: AbstractValue, - descriptor1?: Descriptor, - descriptor2?: Descriptor, -}; +export type Descriptor = DescriptorClass; export type FunctionBodyAstNode = { // Function body ast node will have uniqueOrderedTag after being interpreted. @@ -399,6 +381,10 @@ export class PathConditions { return 0; } + getAssumedConditions(): Set { + return new Set(); + } + refineBaseConditons(realm: Realm, depth?: number = 0): void {} } @@ -478,9 +464,7 @@ export type PropertiesType = { // ECMA262 13.7.5.15 EnumerateObjectProperties(realm: Realm, O: ObjectValue): ObjectValue, - ThrowIfMightHaveBeenDeleted( - value: void | Value | Array | Array<{ $Key: void | Value, $Value: void | Value }> - ): void, + ThrowIfMightHaveBeenDeleted(desc: Descriptor): void, ThrowIfInternalSlotNotWritable(realm: Realm, object: T, key: string): T, diff --git a/src/utils/HeapInspector.js b/src/utils/HeapInspector.js index b0ad75268e..181285c2d7 100644 --- a/src/utils/HeapInspector.js +++ b/src/utils/HeapInspector.js @@ -29,9 +29,36 @@ import { import { To } from "../singletons.js"; import invariant from "../invariant.js"; import { Logger } from "./logger.js"; +import { PropertyDescriptor, AbstractJoinedDescriptor } from "../descriptors.js"; type TargetIntegrityCommand = "freeze" | "seal" | "preventExtensions" | ""; +function hasAnyConfigurable(desc: void | Descriptor): boolean { + if (!desc) { + return false; + } + if (desc instanceof PropertyDescriptor) { + return !!desc.configurable; + } + if (desc instanceof AbstractJoinedDescriptor) { + return hasAnyConfigurable(desc.descriptor1) || hasAnyConfigurable(desc.descriptor2); + } + invariant(false, "internal slots aren't covered here"); +} + +function hasAnyWritable(desc: void | Descriptor): boolean { + if (!desc) { + return false; + } + if (desc instanceof PropertyDescriptor) { + return desc.value !== undefined && !!desc.writable; + } + if (desc instanceof AbstractJoinedDescriptor) { + return hasAnyWritable(desc.descriptor1) || hasAnyWritable(desc.descriptor2); + } + invariant(false, "internal slots aren't covered here"); +} + export class HeapInspector { constructor(realm: Realm, logger: Logger) { this.realm = realm; @@ -65,8 +92,8 @@ export class HeapInspector { for (let propertyBinding of val.properties.values()) { let desc = propertyBinding.descriptor; if (desc === undefined) continue; //deleted - if (desc.configurable) anyConfigurable = true; - else if (desc.value !== undefined && desc.writable) anyWritable = true; + if (hasAnyConfigurable(desc)) anyConfigurable = true; + else if (hasAnyWritable(desc)) anyWritable = true; } command = anyConfigurable ? "preventExtensions" : anyWritable ? "seal" : "freeze"; } @@ -135,6 +162,12 @@ export class HeapInspector { } _canIgnoreProperty(val: ObjectValue, key: string, desc: Descriptor): boolean { + if (!(desc instanceof PropertyDescriptor)) { + // If we have a joined descriptor, there is at least one variant that isn't the same as + // the target descriptor. Since the two descriptors won't be equal. + return false; + } + let targetDescriptor = this.getTargetIntegrityDescriptor(val); if (IsArray(this.realm, val)) { @@ -242,6 +275,7 @@ export class HeapInspector { if (prototypeBinding === undefined) return undefined; let prototypeDesc = prototypeBinding.descriptor; if (prototypeDesc === undefined) return undefined; + invariant(prototypeDesc instanceof PropertyDescriptor); invariant(prototypeDesc.value === undefined || prototypeDesc.value instanceof Value); return prototypeDesc.value; } diff --git a/src/utils/TextPrinter.js b/src/utils/TextPrinter.js new file mode 100644 index 0000000000..ef0451b681 --- /dev/null +++ b/src/utils/TextPrinter.js @@ -0,0 +1,666 @@ +/** + * 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 { + Completion, + SimpleNormalCompletion, + ThrowCompletion, + JoinedNormalAndAbruptCompletions, +} from "../completions.js"; +import type { Realm, Effects } from "../realm.js"; +import type { Descriptor, PropertyBinding } from "../types.js"; +import { PropertyDescriptor, InternalSlotDescriptor, AbstractJoinedDescriptor } from "../descriptors.js"; +import { + EnvironmentRecord, + type Binding, + type LexicalEnvironment, + DeclarativeEnvironmentRecord, + FunctionEnvironmentRecord, + ObjectEnvironmentRecord, + GlobalEnvironmentRecord, +} from "../environment.js"; +import { + PrimitiveValue, + AbstractValue, + ObjectValue, + FunctionValue, + ECMAScriptSourceFunctionValue, + NativeFunctionValue, + BoundFunctionValue, + SymbolValue, + ProxyValue, + Value, + UndefinedValue, +} from "../values/index.js"; +import invariant from "../invariant.js"; +import { + Printer, + Generator, + type OperationDescriptorType, + type CustomGeneratorEntryType, + type OperationDescriptorData, +} from "./generator.js"; +import * as t from "@babel/types"; + +const indent = " "; + +export class TextPrinter implements Printer { + constructor( + printLine: string => void, + abstractValueIds?: Map = new Map(), + symbolIds?: Map = new Map() + ) { + this._printLine = printLine; + this._abstractValueIds = abstractValueIds; + this._symbolIds = symbolIds; + this._indent = ""; + this._objects = new Set(); + this._propertyBindings = new Set(); + this._environmentRecords = new Set(); + this._bindings = new Set(); + this._lexicalEnvironments = new Set(); + this._symbols = new Set(); + } + + _printLine: string => void; + _abstractValueIds: Map; + _symbolIds: Map; + _indent: string; + _objects: Set; + _propertyBindings: Set; + _environmentRecords: Set; + _bindings: Set; + _lexicalEnvironments: Set; + _symbols: Set; + + _nest(): void { + this._indent += indent; + } + _unnest(): void { + this._indent = this._indent.substring(0, this._indent.length - indent.length); + } + + _print(text: string): void { + this._printLine(this._indent + text); + } + + _printDefinition(id: string, constructorName: string, args: Array) { + this._print(`* ${id} = ${constructorName}(${args.join(", ")})`); + } + + printGeneratorEntry( + declared: void | AbstractValue | ObjectValue, + type: OperationDescriptorType | CustomGeneratorEntryType, + args: Array, + data: OperationDescriptorData, + metadata: { isPure: boolean, mutatesOnly: void | Array } + ): void { + switch (type) { + case "DO_WHILE": + invariant(data.value !== undefined); + this._print(`do while ${this.describeValue(data.value)}`); + this._nest(); + const generator = data.generator; + invariant(generator !== undefined); + this.printGenerator(generator, "body"); + this._unnest(); + break; + case "JOIN_GENERATORS": + invariant(args.length === 1); + this._print(`if ${this.describeValue(args[0])}`); + this._nest(); + const generators = data.generators; + invariant(generators !== undefined && generators.length === 2); + this.printGenerator(generators[0], "then"); + this.printGenerator(generators[1], "else"); + this._unnest(); + break; + default: + let text; + if (declared !== undefined) { + invariant(declared.intrinsicName !== undefined); + text = `${this.describeExpression(declared.intrinsicName)} := `; + } else { + text = ""; + } + text += type; + + const dataTexts = []; + if (data.unaryOperator !== undefined) dataTexts.push(`unary ${data.unaryOperator}`); // used by UNARY_EXPRESSION + if (data.binaryOperator !== undefined) dataTexts.push(`binary ${data.binaryOperator}`); // used by BINARY_EXPRESSION + if (data.logicalOperator !== undefined) dataTexts.push(`logical ${data.logicalOperator}`); // used by LOGICAL_EXPRESSION + if (data.incrementor !== undefined) dataTexts.push(`incrementor ${data.incrementor}`); // used by UPDATE_INCREMENTOR + if (data.prefix !== undefined) dataTexts.push("prefix"); // used by UNARY_EXPRESSION + if (data.binding !== undefined) dataTexts.push(`binding ${this.describeBinding(data.binding)}`); // used by GET_BINDING + if (data.propertyBinding !== undefined) + dataTexts.push(`property binding ${this.describePropertyBinding(data.propertyBinding)}`); // used by LOGICAL_PROPERTY_ASSIGNMENT + if (data.object !== undefined) dataTexts.push(`object ${this.describeValue(data.object)}`); // used by DEFINE_PROPERTY + if (data.descriptor !== undefined) dataTexts.push(`desc ${this.describeDescriptor(data.descriptor)}`); // used by DEFINE_PROPERTY + if (data.value !== undefined) dataTexts.push(`value ${this.describeValue(data.value)}`); // used by DO_WHILE, CONDITIONAL_PROPERTY_ASSIGNMENT, LOGICAL_PROPERTY_ASSIGNMENT, LOCAL_ASSIGNMENT, CONDITIONAL_THROW, EMIT_PROPERTY_ASSIGNMENT + if (data.id !== undefined) dataTexts.push(`id ${this.describeExpression(data.id)}`); // used by IDENTIFIER + if (data.thisArg !== undefined) dataTexts.push(`this arg ${this.describeBaseValue(data.thisArg)}`); // used by CALL_BAILOUT + if (data.propRef !== undefined) dataTexts.push(`prop ref ${this.describeKey(data.propRef)}`); // used by CALL_BAILOUT, and then only if string + if (data.state !== undefined) dataTexts.push(`state ${data.state}`); // used by PROPERTY_INVARIANT + if (data.usesThis !== undefined) dataTexts.push(`usesThis`); // used by FOR_STATEMENT_FUNC + if (data.path !== undefined) dataTexts.push(`path ${this.describeValue(data.path)}`); // used by PROPERTY_ASSIGNMENT, CONDITIONAL_PROPERTY_ASSIGNMENT + if (data.callFunctionRef !== undefined) + dataTexts.push(`call function ref ${this.describeExpression(data.callFunctionRef)}`); // used by EMIT_CALL and EMIT_CALL_AND_CAPTURE_RESULT + if (data.templateSource !== undefined) + dataTexts.push(`template source ${this.describeExpression(data.templateSource)}`); // used by ABSTRACT_FROM_TEMPLATE + if (data.propertyGetter !== undefined) dataTexts.push(`property getter ${data.propertyGetter}`); // used by ABSTRACT_OBJECT_GET + + // TODO: + // appendLastToInvariantOperationDescriptor?: OperationDescriptor, // used by INVARIANT + // concreteComparisons?: Array, // used by FULL_INVARIANT_ABSTRACT + // boundName?: BabelNodeIdentifier, // used by FOR_IN + // lh?: BabelNodeVariableDeclaration, // used by FOR_IN + // quasis?: Array, // used by REACT_SSR_TEMPLATE_LITERAL + // typeComparisons?: Set, // used by FULL_INVARIANT_ABSTRACT + // violationConditionOperationDescriptor?: OperationDescriptor, // used by INVARIANT + if (dataTexts.length > 0) text += `<${dataTexts.join("; ")}>`; + + if (args.length > 0) text += `(${this.describeValues(args)})`; + + const metadataTexts = []; + if (metadata.isPure) metadataTexts.push("isPure"); + if (metadata.mutatesOnly !== undefined && metadata.mutatesOnly.length > 0) + metadataTexts.push(`mutates only: [${this.describeValues(metadata.mutatesOnly)}]`); + if (metadataTexts.length > 0) text += `[${metadataTexts.join("; ")}]`; + + this._print(text); + break; + } + } + + printGenerator(generator: Generator, label?: string = "(entry point)"): void { + this._print(`${label}: ${JSON.stringify(generator.getName())}`); + this._nest(); + if (generator.pathConditions.getLength() > 0) + this._print( + `path conditions ${this.describeValues(Array.from(generator.pathConditions.getAssumedConditions()))}` + ); + generator.print(this); + this._unnest(); + } + + print(realm: Realm, optimizedFunctions: Map): void { + const realmGenerator = realm.generator; + if (realmGenerator !== undefined) this.printGenerator(realmGenerator); + for (const [functionValue, generator] of optimizedFunctions) { + const effectsToApply = generator.effectsToApply; + invariant(effectsToApply !== undefined); + this._print(`=== optimized function ${this.describeValue(functionValue)}`); + realm.withEffectsAppliedInGlobalEnv(effects => { + const nestedPrinter = new TextPrinter(this._printLine, this._abstractValueIds, this._symbolIds); + nestedPrinter.printEffects(effects, generator); + return nestedPrinter; // not needed, but withEffectsAppliedInGlobalEnv has an unmotivated invariant that the result must not be undefined + }, effectsToApply); + } + } + + describeCompletion(result: Completion): string { + const args = []; + if (result instanceof SimpleNormalCompletion) args.push(`value ${this.describeValue(result.value)}`); + else if (result instanceof ThrowCompletion) args.push(`value ${this.describeValue(result.value)}`); + else { + invariant(result instanceof JoinedNormalAndAbruptCompletions); + args.push(`join condition ${this.describeValue(result.joinCondition)}`); + args.push(`consequent ${this.describeCompletion(result.consequent)}`); + args.push(`alternate ${this.describeCompletion(result.alternate)}`); + if (result.composedWith !== undefined) args.push(`composed with ${this.describeCompletion(result.composedWith)}`); + } + return `${result.constructor.name}(${args.join(", ")})`; + } + + printEffects(effects: Effects, generator?: Generator): void { + this._nest(); + this.printGenerator(generator || effects.generator); + // skip effects.generator + if (effects.modifiedProperties.size > 0) + this._print( + `modified property bindings: [${Array.from(effects.modifiedProperties.keys()) + .map(propertyBinding => this.describePropertyBinding(propertyBinding)) + .join(", ")}]` + ); + if (effects.modifiedBindings.size > 0) + this._print( + `modified bindings: [${Array.from(effects.modifiedBindings.keys()) + .map(binding => this.describeBinding(binding)) + .join(", ")}]` + ); + if (effects.createdObjects.size > 0) + this._print( + `created objects: [${Array.from(effects.createdObjects) + .map(object => this.describeValue(object)) + .join(", ")}]` + ); + if (!(effects.result instanceof UndefinedValue)) this._print(`result: ${this.describeCompletion(effects.result)}`); + this._unnest(); + } + + describeExpression(expression: string): string { + if (t.isValidIdentifier(expression)) return expression; + else return "@" + JSON.stringify(expression); + } + + describeValues(values: Array): string { + return values.map(value => this.describeValue(value)).join(", "); + } + + abstractValueName(value: AbstractValue): string { + const id = this._abstractValueIds.get(value); + invariant(id !== undefined); + return `value#${id}`; + } + + printAbstractValue(value: AbstractValue): void { + invariant(value.intrinsicName === undefined); + let kind = value.kind; + // TODO: I'd expect kind to be defined in this situation; however, it's not defined for test ForInStatement4.js + // invariant(kind !== undefined); + if (kind === undefined) kind = "(no kind)"; + this._printDefinition( + this.abstractValueName(value), + this.describeExpression(kind), + value.args.map(arg => this.describeValue(arg)) + ); + } + + objectValueName(value: ObjectValue): string { + invariant(this._objects.has(value)); + let name; + if (value instanceof FunctionValue) name = "func"; + else if (value instanceof ProxyValue) name = "proxy"; + else name = "object"; + return `${name}#${value.getHash()}`; + } + + printObjectValue(value: ObjectValue): void { + const args = []; + + if (value.temporalAlias !== undefined) args.push(`temporalAlias ${this.describeValue(value.temporalAlias)}`); + + if (value instanceof FunctionValue) { + if (value instanceof NativeFunctionValue) { + // TODO: This shouldn't happen; all native function values should be intrinsics + } else if (value instanceof BoundFunctionValue) { + args.push(`$BoundTargetFunction ${this.describeValue(value.$BoundTargetFunction)}`); + args.push(`$BoundThis ${this.describeValue(value.$BoundThis)}`); + args.push(`$BoundArguments [${this.describeValues(value.$BoundArguments)}]`); + } else { + invariant(value instanceof ECMAScriptSourceFunctionValue); + args.push(`$ConstructorKind ${value.$ConstructorKind}`); + args.push(`$ThisMode ${value.$ThisMode}`); + args.push(`$FunctionKind ${value.$FunctionKind}`); + if (value.$HomeObject !== undefined) args.push(`$HomeObject ${this.describeValue(value.$HomeObject)}`); + + // TODO: $Strict should always be defined according to its flow type signature, however, there are some tests where it's not + if (value.$Strict) args.push(`$Strict`); + args.push(`$FormalParameters ${value.$FormalParameters.length}`); + // TODO: pretty-print $ECMAScriptCode + + // TODO: $Environment should always be defined according to its flow type signature, however, it's not in test ConcreteModel2.js + if (value.$Environment) args.push(`$Environment ${this.describeLexicalEnvironment(value.$Environment)}`); + } + } else if (value instanceof ProxyValue) { + args.push(`$ProxyTarget ${this.describeValue(value.$ProxyTarget)}`); + args.push(`$ProxyHandler ${this.describeValue(value.$ProxyHandler)}`); + } else { + const kind = value.getKind(); + if (kind !== "Object") args.push(`kind ${kind}`); + switch (kind) { + case "RegExp": + const originalSource = value.$OriginalSource; + invariant(originalSource !== undefined); + args.push(`$OriginalSource ${originalSource}`); + const originalFlags = value.$OriginalFlags; + invariant(originalFlags !== undefined); + args.push(`$OriginalFlags ${originalFlags}`); + break; + case "Number": + const numberData = value.$NumberData; + invariant(numberData !== undefined); + args.push(`$NumberData ${this.describeValue(numberData)}`); + break; + case "String": + const stringData = value.$StringData; + invariant(stringData !== undefined); + args.push(`$StringData ${this.describeValue(stringData)}`); + break; + case "Boolean": + const booleanData = value.$BooleanData; + invariant(booleanData !== undefined); + args.push(`$BooleanData ${this.describeValue(booleanData)}`); + break; + case "Date": + const dateValue = value.$DateValue; + invariant(dateValue !== undefined); + args.push(`$DateValue ${this.describeValue(dateValue)}`); + break; + case "ArrayBuffer": + const len = value.$ArrayBufferByteLength; + invariant(len !== undefined); + args.push(`$ArrayBufferByteLength ${len}`); + const db = value.$ArrayBufferData; + invariant(db !== undefined); + if (db !== null) args.push(`$ArrayBufferData [${db.join(", ")}]`); + break; + case "Float32Array": + case "Float64Array": + case "Int8Array": + case "Int16Array": + case "Int32Array": + case "Uint8Array": + case "Uint16Array": + case "Uint32Array": + case "Uint8ClampedArray": + case "DataView": + const buf = value.$ViewedArrayBuffer; + invariant(buf !== undefined); + args.push(`$ViewedArrayBuffer ${this.describeValue(buf)}`); + break; + case "Map": + const mapDataEntries = value.$MapData; + invariant(mapDataEntries !== undefined); + args.push(`$MapData [${this.describeMapEntries(mapDataEntries)}]`); + break; + case "WeakMap": + const weakMapDataEntries = value.$WeakMapData; + invariant(weakMapDataEntries !== undefined); + args.push(`$WeakMapData [${this.describeMapEntries(weakMapDataEntries)}]`); + break; + case "Set": + const setDataEntries = value.$SetData; + invariant(setDataEntries !== undefined); + args.push(`$SetData [${this.describeSetEntries(setDataEntries)}]`); + break; + case "WeakSet": + const weakSetDataEntries = value.$WeakSetData; + invariant(weakSetDataEntries !== undefined); + args.push(`$WeakSetData [${this.describeSetEntries(weakSetDataEntries)}]`); + break; + case "ReactElement": + case "Object": + case "Array": + break; + default: + invariant(false); + } + } + + // properties + if (value.properties.size > 0) { + args.push( + `properties [${Array.from(value.properties.keys()) + .map(key => this.describeKey(key)) + .join(", ")}]` + ); + } + + // symbols + if (value.symbols.size > 0) { + args.push( + `symbols [${Array.from(value.symbols.keys()) + .map(key => this.describeKey(key)) + .join(", ")}]` + ); + } + + const unknownProperty = value.unknownProperty; + if (unknownProperty !== undefined) args.push(`unknown property`); + + if (value.$Prototype !== undefined) args.push(`$Prototype ${this.describeValue(value.$Prototype)}`); + + this._printDefinition(this.objectValueName(value), value.constructor.name, args); + + // jull pull on property bindings to get them emitting + for (const propertyBinding of value.properties.values()) this.describePropertyBinding(propertyBinding); + for (const propertyBinding of value.symbols.values()) this.describePropertyBinding(propertyBinding); + if (unknownProperty !== undefined) this.describePropertyBinding(unknownProperty); + } + + describeValue(value: Value): string { + if (value.intrinsicName !== undefined) return this.describeExpression(value.intrinsicName); + if (value instanceof SymbolValue) return this.describeSymbol(value); + if (value instanceof PrimitiveValue) return value.toDisplayString(); + if (value instanceof ObjectValue) { + if (!this._objects.has(value)) { + this._objects.add(value); + this.printObjectValue(value); + } + return this.objectValueName(value); + } else { + invariant(value instanceof AbstractValue, value.constructor.name); + if (!this._abstractValueIds.has(value)) { + this._abstractValueIds.set(value, this._abstractValueIds.size); + this.printAbstractValue(value); + } + return this.abstractValueName(value); + } + } + + describeMapEntries(entries: Array<{ $Key: void | Value, $Value: void | Value }>): string { + return entries + .map(entry => { + const args = []; + if (entry.$Key !== undefined) args.push(`$Key ${this.describeValue(entry.$Key)}`); + if (entry.$Value !== undefined) args.push(`$Value ${this.describeValue(entry.$Value)}`); + return `{${args.join(", ")}}`; + }) + .join(", "); + } + + describeSetEntries(entries: Array): string { + return entries.map(entry => (entry === undefined ? "(undefined)" : this.describeValue(entry))).join(", "); + } + + describeDescriptor(desc: Descriptor): string { + if (desc instanceof PropertyDescriptor) return this.describePropertyDescriptor(desc); + else if (desc instanceof InternalSlotDescriptor) return this.describeInternalSlotDescriptor(desc); + else { + invariant(desc instanceof AbstractJoinedDescriptor, desc.constructor.name); + return this.describeAbstractJoinedDescriptor(desc); + } + } + + describePropertyDescriptor(desc: PropertyDescriptor): string { + const args = []; + if (desc.writable) args.push("writable"); + if (desc.enumerable) args.push("enumerable"); + if (desc.configurable) args.push("configurable"); + if (desc.value !== undefined) args.push(`value ${this.describeValue(desc.value)}`); + if (desc.get !== undefined) args.push(`get ${this.describeValue(desc.get)}`); + if (desc.set !== undefined) args.push(`set ${this.describeValue(desc.set)}`); + return `PropertyDescriptor(${args.join(", ")})`; + } + + describeInternalSlotDescriptor(desc: InternalSlotDescriptor): string { + const args = []; + if (desc.value instanceof Value) args.push(`value ${this.describeValue(desc.value)}`); + else if (Array.isArray(desc.value)) args.push(`some array`); // TODO + return `InternalSlotDescriptor(${args.join(", ")})`; + } + + describeAbstractJoinedDescriptor(desc: AbstractJoinedDescriptor): string { + const args = []; + args.push(`join condition ${this.describeValue(desc.joinCondition)}`); + if (desc.descriptor1 !== undefined) args.push(`descriptor1 ${this.describeDescriptor(desc.descriptor1)}`); + if (desc.descriptor2 !== undefined) args.push(`descriptor2 ${this.describeDescriptor(desc.descriptor2)}`); + return `AbstractJoinedDescriptor(${args.join(", ")})`; + } + + bindingName(binding: Binding): string { + invariant(this._bindings.has(binding)); + return `${this.describeEnvironmentRecord(binding.environment)}.${this.describeExpression(binding.name)}`; + } + + printBinding(binding: Binding): void { + const args = []; + if (binding.isGlobal) args.push("is global"); + if (binding.mightHaveBeenCaptured) args.push("might have been captured"); + if (binding.initialized) args.push("initialized"); + if (binding.mutable) args.push("mutable"); + if (binding.deletable) args.push("deletable"); + if (binding.strict) args.push("strict"); + if (binding.hasLeaked) args.push("has leaked"); + if (binding.value !== undefined) args.push(`value ${this.describeValue(binding.value)})`); + if (binding.phiNode !== undefined) args.push(`phi node ${this.describeValue(binding.phiNode)}`); + this._printDefinition(this.bindingName(binding), "Binding", args); + } + + describeBinding(binding: Binding): string { + if (!this._bindings.has(binding)) { + this._bindings.add(binding); + this.printBinding(binding); + } + return this.bindingName(binding); + } + + describeKey(key: void | string | Value): string { + if (key === undefined) return "(undefined)"; + else if (typeof key === "string") return this.describeExpression(key); + else { + invariant(key instanceof Value); + return this.describeValue(key); + } + } + + propertyBindingName(propertyBinding: PropertyBinding): string { + return `${this.describeValue(propertyBinding.object)}.${this.describeKey(propertyBinding.key)}`; + } + + printPropertyBinding(propertyBinding: PropertyBinding): void { + const args = []; + if (propertyBinding.internalSlot) args.push("internal slot"); + if (propertyBinding.descriptor !== undefined) + args.push(`descriptor ${this.describeDescriptor(propertyBinding.descriptor)}`); + if (propertyBinding.pathNode !== undefined) args.push(`path node ${this.describeValue(propertyBinding.pathNode)}`); + this._printDefinition(this.propertyBindingName(propertyBinding), "PropertyBinding", args); + } + + describePropertyBinding(propertyBinding: PropertyBinding): string { + if (!this._propertyBindings.has(propertyBinding)) { + this._propertyBindings.add(propertyBinding); + this.printPropertyBinding(propertyBinding); + } + return this.propertyBindingName(propertyBinding); + } + + environmentRecordName(environment: EnvironmentRecord): string { + invariant(this._environmentRecords.has(environment)); + let name; + if (environment instanceof DeclarativeEnvironmentRecord) { + name = environment instanceof FunctionEnvironmentRecord ? "funEnv" : "declEnv"; + } else if (environment instanceof ObjectEnvironmentRecord) { + name = "objEnv"; + } else { + invariant(environment instanceof GlobalEnvironmentRecord); + name = "globEnv"; + } + return `${name}#${environment.id}`; + } + + printEnvironmentRecord(environment: EnvironmentRecord): void { + const args = []; + if (environment instanceof DeclarativeEnvironmentRecord) { + if (environment instanceof FunctionEnvironmentRecord) { + args.push(`$ThisBindingStatus ${environment.$ThisBindingStatus}`); + // TODO: $ThisValue should always be defined according to its flow type signature, however, it's not for test ObjectAssign9.js + if (environment.$ThisValue !== undefined) args.push(`$ThisValue ${this.describeValue(environment.$ThisValue)}`); + if (environment.$HomeObject !== undefined) + args.push(`$HomeObject ${this.describeValue(environment.$HomeObject)}`); + args.push(`$FunctionObject ${this.describeValue(environment.$FunctionObject)}`); + } + if (environment.$NewTarget !== undefined) args.push(`$NewTarget ${this.describeValue(environment.$NewTarget)}`); + if (environment.frozen) args.push("frozen"); + const bindings = Object.keys(environment.bindings); + if (bindings.length > 0) + args.push( + `bindings [${Object.keys(environment.bindings) + .map(key => this.describeKey(key)) + .join(", ")}]` + ); + } else if (environment instanceof ObjectEnvironmentRecord) { + args.push(`object ${this.describeValue(environment.object)}`); + if (environment.withEnvironment) args.push("with environment"); + } else if (environment instanceof GlobalEnvironmentRecord) { + args.push(`$DeclarativeRecord ${this.describeEnvironmentRecord(environment.$DeclarativeRecord)}`); + args.push(`$ObjectRecord ${this.describeEnvironmentRecord(environment.$DeclarativeRecord)}`); + if (environment.$VarNames.length > 0) + args.push(`$VarNames [${environment.$VarNames.map(varName => this.describeExpression(varName)).join(", ")}]`); + args.push(`$GlobalThisValue ${this.describeValue(environment.$GlobalThisValue)}`); + } + this._printDefinition(this.environmentRecordName(environment), environment.constructor.name, args); + + // pull on bindings to get them emitted + if (environment instanceof DeclarativeEnvironmentRecord) + for (const bindingName in environment.bindings) this.describeBinding(environment.bindings[bindingName]); + } + + describeEnvironmentRecord(environment: EnvironmentRecord): string { + if (!this._environmentRecords.has(environment)) { + this._environmentRecords.add(environment); + this.printEnvironmentRecord(environment); + } + return this.environmentRecordName(environment); + } + + describeBaseValue(value: void | EnvironmentRecord | Value): string { + if (value === undefined) return "(undefined)"; + else if (value instanceof Value) return this.describeValue(value); + invariant(value instanceof EnvironmentRecord); + return this.describeEnvironmentRecord(value); + } + + lexicalEnvironmentName(environment: LexicalEnvironment): string { + invariant(this._lexicalEnvironments.has(environment)); + return `lexEnv#${environment._uid}`; + } + + printLexicalEnvironment(environment: LexicalEnvironment): void { + const args = []; + if (environment.destroyed) args.push("destroyed"); + if (environment.parent !== null) args.push(`parent ${this.describeLexicalEnvironment(environment.parent)}`); + args.push(`environment record ${this.describeEnvironmentRecord(environment.environmentRecord)}`); + this._printDefinition(this.lexicalEnvironmentName(environment), "LexicalEnvironment", args); + } + + describeLexicalEnvironment(environment: LexicalEnvironment): string { + if (!this._lexicalEnvironments.has(environment)) { + this._lexicalEnvironments.add(environment); + this.printLexicalEnvironment(environment); + } + return this.lexicalEnvironmentName(environment); + } + + symbolName(symbol: SymbolValue): string { + const id = this._symbolIds.get(symbol); + invariant(id !== undefined); + return `symbol#${id}`; + } + + printSymbol(symbol: SymbolValue): void { + const args = []; + if (symbol.$Description) args.push(`$Description ${this.describeValue(symbol.$Description)}`); + this._printDefinition(this.symbolName(symbol), "Symbol", args); + } + + describeSymbol(symbol: SymbolValue): string { + if (!this._symbolIds.has(symbol)) { + this._symbolIds.set(symbol, this._symbolIds.size); + } + if (!this._symbols.has(symbol)) { + this._symbols.add(symbol); + this.printSymbol(symbol); + } + return this.symbolName(symbol); + } +} diff --git a/src/utils/generator.js b/src/utils/generator.js index 371513ee32..7c480a5285 100644 --- a/src/utils/generator.js +++ b/src/utils/generator.js @@ -56,6 +56,7 @@ import { concretize, Join, Utils } from "../singletons.js"; import type { SerializerOptions } from "../options.js"; import type { PathConditions, ShapeInformationInterface } from "../types.js"; import { PreludeGenerator } from "./PreludeGenerator.js"; +import { PropertyDescriptor } from "../descriptors.js"; export type OperationDescriptorType = | "ABSTRACT_FROM_TEMPLATE" @@ -147,7 +148,7 @@ export type OperationDescriptorData = { boundName?: BabelNodeIdentifier, // used by FOR_IN callFunctionRef?: string, // used by EMIT_CALL and EMIT_CALL_AND_CAPTURE_RESULT concreteComparisons?: Array, // used by FULL_INVARIANT_ABSTRACT - desc?: Descriptor, // used by DEFINE_PROPERTY + descriptor?: Descriptor, // used by DEFINE_PROPERTY generator?: Generator, // used by DO_WHILE generators?: Array, // used by JOIN_GENERATORS id?: string, // used by IDENTIFIER @@ -225,6 +226,19 @@ export type VisitEntryCallbacks = {| visitBindingAssignment: (Binding, Value) => Value, |}; +export type CustomGeneratorEntryType = "MODIFIED_PROPERTY" | "MODIFIED_BINDING" | "RETURN" | "BINDING_ASSIGNMENT"; + +export interface Printer { + printGeneratorEntry( + declared: void | AbstractValue | ObjectValue, + type: OperationDescriptorType | CustomGeneratorEntryType, + args: Array, + data: OperationDescriptorData, + metadata: { isPure: boolean, mutatesOnly: void | Array } + ): void; + printGenerator(generator: Generator, label?: string): void; +} + export class GeneratorEntry { constructor(realm: Realm) { // We increment the index of every TemporalOperationEntry created. @@ -236,6 +250,10 @@ export class GeneratorEntry { this.index = realm.temporalEntryCounter++; } + print(printer: Printer): void { + invariant(false, "GeneratorEntry is an abstract base class"); + } + visit(callbacks: VisitEntryCallbacks, containingGenerator: Generator): boolean { invariant(false, "GeneratorEntry is an abstract base class"); } @@ -263,7 +281,6 @@ export type TemporalOperationEntryArgs = { declared?: AbstractValue | ObjectValue, args: Array, operationDescriptor: OperationDescriptor, - dependencies?: Array, isPure?: boolean, mutatesOnly?: Array, }; @@ -284,10 +301,17 @@ export class TemporalOperationEntry extends GeneratorEntry { declared: void | AbstractValue | ObjectValue; args: Array; operationDescriptor: OperationDescriptor; - dependencies: void | Array; isPure: void | boolean; mutatesOnly: void | Array; + print(printer: Printer): void { + const operationDescriptor = this.operationDescriptor; + printer.printGeneratorEntry(this.declared, operationDescriptor.type, this.args, this.operationDescriptor.data, { + isPure: !!this.isPure, + mutatesOnly: this.mutatesOnly, + }); + } + toDisplayJson(depth: number): DisplayResult { if (depth <= 0) return `TemporalOperation${this.index}`; let obj = { type: "TemporalOperation", ...this }; @@ -345,8 +369,9 @@ export class TemporalOperationEntry extends GeneratorEntry { } } } - if (this.dependencies) - for (let dependency of this.dependencies) callbacks.visitGenerator(dependency, containingGenerator); + let dependencies = this.getDependencies(); + if (dependencies !== undefined) + for (let dependency of dependencies) callbacks.visitGenerator(dependency, containingGenerator); return true; } } @@ -393,7 +418,19 @@ export class TemporalOperationEntry extends GeneratorEntry { } getDependencies(): void | Array { - return this.dependencies; + const operationDescriptor = this.operationDescriptor; + switch (operationDescriptor.type) { + case "DO_WHILE": + let generator = operationDescriptor.data.generator; + invariant(generator !== undefined); + return [generator]; + case "JOIN_GENERATORS": + let generators = operationDescriptor.data.generators; + invariant(generators !== undefined); + return generators; + default: + return undefined; + } } } @@ -445,6 +482,16 @@ class ModifiedPropertyEntry extends GeneratorEntry { propertyBinding: PropertyBinding; newDescriptor: void | Descriptor; + print(printer: Printer): void { + printer.printGeneratorEntry( + undefined, + "MODIFIED_PROPERTY", + [], + { descriptor: this.newDescriptor, propertyBinding: this.propertyBinding }, + { isPure: false, mutatesOnly: undefined } + ); + } + toDisplayString(): string { let propertyKey = this.propertyBinding.key; let propertyKeyString = propertyKey instanceof Value ? propertyKey.toDisplayString() : propertyKey; @@ -488,6 +535,16 @@ class ModifiedBindingEntry extends GeneratorEntry { containingGenerator: Generator; modifiedBinding: Binding; + print(printer: Printer): void { + printer.printGeneratorEntry( + undefined, + "MODIFIED_BINDING", + [], + { binding: this.modifiedBinding, value: this.modifiedBinding.value }, + { isPure: false, mutatesOnly: undefined } + ); + } + toDisplayString(): string { return `[ModifiedBinding ${this.modifiedBinding.name}]`; } @@ -520,6 +577,10 @@ class ReturnValueEntry extends GeneratorEntry { returnValue: Value; containingGenerator: Generator; + print(printer: Printer): void { + printer.printGeneratorEntry(undefined, "RETURN", [this.returnValue], {}, { isPure: false, mutatesOnly: undefined }); + } + toDisplayString(): string { return `[Return ${this.returnValue.toDisplayString()}]`; } @@ -552,6 +613,16 @@ class BindingAssignmentEntry extends GeneratorEntry { binding: Binding; value: Value; + print(printer: Printer): void { + printer.printGeneratorEntry( + undefined, + "BINDING_ASSIGNMENT", + [this.value], + { binding: this.binding }, + { isPure: false, mutatesOnly: undefined } + ); + } + toDisplayString(): string { return `[BindingAssignment ${this.binding.name} = ${this.value.toDisplayString()}]`; } @@ -592,6 +663,10 @@ export class Generator { _name: string; pathConditions: PathConditions; + print(printer: Printer): void { + for (let entry of this._entries) entry.print(printer); + } + toDisplayString(): string { return Utils.jsonToDisplayString(this, 2); } @@ -663,16 +738,16 @@ export class Generator { emitPropertyModification(propertyBinding: PropertyBinding): void { invariant(this.effectsToApply !== undefined); let desc = propertyBinding.descriptor; - if (desc !== undefined) { + if (desc !== undefined && desc instanceof PropertyDescriptor) { let value = desc.value; if (value instanceof AbstractValue) { if (value.kind === "conditional") { let [c, x, y] = value.args; if (c instanceof AbstractValue && c.kind === "template for property name condition") { - let ydesc = Object.assign({}, desc, { value: y }); + let ydesc = new PropertyDescriptor(Object.assign({}, (desc: any), { value: y })); let yprop = Object.assign({}, propertyBinding, { descriptor: ydesc }); this.emitPropertyModification(yprop); - let xdesc = Object.assign({}, desc, { value: x }); + let xdesc = new PropertyDescriptor(Object.assign({}, (desc: any), { value: x })); let key = c.args[0]; invariant(key instanceof AbstractValue); let xprop = Object.assign({}, propertyBinding, { key, descriptor: xdesc }); @@ -758,14 +833,14 @@ export class Generator { }); } - emitDefineProperty(object: ObjectValue, key: string, desc: Descriptor, isDescChanged: boolean = true): void { + emitDefineProperty(object: ObjectValue, key: string, desc: PropertyDescriptor, isDescChanged: boolean = true): void { if (object.refuseSerialization) return; if (desc.enumerable && desc.configurable && desc.writable && desc.value && !isDescChanged) { let descValue = desc.value; invariant(descValue instanceof Value); this.emitPropertyAssignment(object, key, descValue); } else { - desc = Object.assign({}, desc); + desc = new PropertyDescriptor(desc); let descValue = desc.value || object.$Realm.intrinsics.undefined; invariant(descValue instanceof Value); this._addEntry({ @@ -776,7 +851,7 @@ export class Generator { desc.get || object.$Realm.intrinsics.undefined, desc.set || object.$Realm.intrinsics.undefined, ], - operationDescriptor: createOperationDescriptor("DEFINE_PROPERTY", { object, desc }), + operationDescriptor: createOperationDescriptor("DEFINE_PROPERTY", { object, descriptor: desc }), }); } } @@ -811,7 +886,6 @@ export class Generator { this._addEntry({ args: [], operationDescriptor: createOperationDescriptor("DO_WHILE", { generator: body, value: test }), - dependencies: [body], }); } @@ -1136,7 +1210,6 @@ export class Generator { this._addEntry({ args: [joinCondition], operationDescriptor: createOperationDescriptor("JOIN_GENERATORS", { generators }), - dependencies: generators, }); } } diff --git a/src/utils/leak.js b/src/utils/leak.js index be3395af78..2e7a4a5b6e 100644 --- a/src/utils/leak.js +++ b/src/utils/leak.js @@ -41,6 +41,7 @@ import invariant from "../invariant.js"; import { HeapInspector } from "../utils/HeapInspector.js"; import { Logger } from "../utils/logger.js"; import { isReactElement } from "../react/utils.js"; +import { PropertyDescriptor, AbstractJoinedDescriptor } from "../descriptors.js"; type LeakedFunctionInfo = { unboundReads: Set, @@ -137,6 +138,7 @@ function materializeObject(realm: Realm, object: ObjectValue, getCachingHeapInsp // If it indeed means deleted binding, should we initialize descriptor with a deleted value? if (generator !== undefined) generator.emitPropertyDelete(object, name); } else { + invariant(descriptor instanceof PropertyDescriptor); // TODO: Deal with joined descriptors. let value = descriptor.value; invariant( value === undefined || value instanceof Value, @@ -216,11 +218,7 @@ class ObjectValueLeakingVisitor { // inject properties with computed names if (obj.unknownProperty !== undefined) { let desc = obj.unknownProperty.descriptor; - if (desc !== undefined) { - let val = desc.value; - invariant(val instanceof AbstractValue); - this.visitObjectPropertiesWithComputedNames(val); - } + this.visitObjectPropertiesWithComputedNamesDescriptor(desc); } // prototype @@ -258,6 +256,22 @@ class ObjectValueLeakingVisitor { this.visitValue(proto); } + visitObjectPropertiesWithComputedNamesDescriptor(desc: void | Descriptor): void { + if (desc !== undefined) { + if (desc instanceof PropertyDescriptor) { + let val = desc.value; + invariant(val instanceof AbstractValue); + this.visitObjectPropertiesWithComputedNames(val); + } else if (desc instanceof AbstractJoinedDescriptor) { + this.visitValue(desc.joinCondition); + this.visitObjectPropertiesWithComputedNamesDescriptor(desc.descriptor1); + this.visitObjectPropertiesWithComputedNamesDescriptor(desc.descriptor2); + } else { + invariant(false, "unknown descriptor"); + } + } + } + visitObjectPropertiesWithComputedNames(absVal: AbstractValue): void { if (absVal.kind === "widened property") return; if (absVal.kind === "template for prototype member expression") return; @@ -289,11 +303,19 @@ class ObjectValueLeakingVisitor { } } - visitDescriptor(desc: Descriptor): void { - invariant(desc.value === undefined || desc.value instanceof Value); - if (desc.value !== undefined) this.visitValue(desc.value); - if (desc.get !== undefined) this.visitValue(desc.get); - if (desc.set !== undefined) this.visitValue(desc.set); + visitDescriptor(desc: void | Descriptor): void { + if (desc === undefined) { + } else if (desc instanceof PropertyDescriptor) { + if (desc.value !== undefined) this.visitValue(desc.value); + if (desc.get !== undefined) this.visitValue(desc.get); + if (desc.set !== undefined) this.visitValue(desc.set); + } else if (desc instanceof AbstractJoinedDescriptor) { + this.visitValue(desc.joinCondition); + if (desc.descriptor1 !== undefined) this.visitDescriptor(desc.descriptor1); + if (desc.descriptor2 !== undefined) this.visitDescriptor(desc.descriptor2); + } else { + invariant(false, "unknown descriptor"); + } } visitDeclarativeEnvironmentRecordBinding( diff --git a/src/utils/logger.js b/src/utils/logger.js index fa25bfe2d8..92042222e3 100644 --- a/src/utils/logger.js +++ b/src/utils/logger.js @@ -16,6 +16,7 @@ import { Completion, ThrowCompletion } from "../completions.js"; import { ObjectValue, StringValue, Value } from "../values/index.js"; import { To } from "../singletons.js"; import invariant from "../invariant.js"; +import { PropertyDescriptor } from "../descriptors.js"; export class Logger { constructor(realm: Realm, internalDebug: boolean) { @@ -94,7 +95,10 @@ export class Logger { } catch (err) { let message = object.properties.get("message"); console.error( - message && message.descriptor && message.descriptor.value instanceof StringValue + message && + message.descriptor && + message.descriptor instanceof PropertyDescriptor && + message.descriptor.value instanceof StringValue ? message.descriptor.value.value : "(no message available)" ); diff --git a/src/utils/modules.js b/src/utils/modules.js index be1b20afbc..4dda1c5841 100644 --- a/src/utils/modules.js +++ b/src/utils/modules.js @@ -37,6 +37,7 @@ import type { import invariant from "../invariant.js"; import { Logger } from "./logger.js"; import { SerializerStatistics } from "../serializer/statistics.js"; +import { PropertyDescriptor } from "../descriptors.js"; function downgradeErrorsToWarnings(realm: Realm, f: () => any) { let savedHandler = realm.errorHandler; @@ -238,9 +239,11 @@ export class Modules { for (let moduleId of globalInitializedModulesMap.properties.keys()) { let property = globalInitializedModulesMap.properties.get(moduleId); invariant(property); - let moduleValue = property.descriptor && property.descriptor.value; - if (moduleValue instanceof Value && !moduleValue.mightHaveBeenDeleted()) { - this.initializedModules.set(moduleId, moduleValue); + if (property.descriptor instanceof PropertyDescriptor) { + let moduleValue = property.descriptor && property.descriptor.value; + if (moduleValue instanceof Value && !moduleValue.mightHaveBeenDeleted()) { + this.initializedModules.set(moduleId, moduleValue); + } } } this.getStatistics().initializedModules = this.initializedModules.size; diff --git a/src/utils/native-to-interp.js b/src/utils/native-to-interp.js index 31a5564fd2..980ef76967 100644 --- a/src/utils/native-to-interp.js +++ b/src/utils/native-to-interp.js @@ -13,6 +13,7 @@ import type { Realm } from "../realm.js"; import { FatalError } from "../errors.js"; import { Value, StringValue, NumberValue, ObjectValue } from "../values/index.js"; import { Create } from "../singletons.js"; +import { PropertyDescriptor } from "../descriptors.js"; export default function convert(realm: Realm, val: any): Value { if (typeof val === "number") { @@ -33,12 +34,15 @@ export default function convert(realm: Realm, val: any): Value { let obj = new ObjectValue(realm, realm.intrinsics.ObjectPrototype); for (let key in val) { - obj.$DefineOwnProperty(key, { - enumerable: true, - writable: true, - configurable: true, - value: convert(realm, val[key]), - }); + obj.$DefineOwnProperty( + key, + new PropertyDescriptor({ + enumerable: true, + writable: true, + configurable: true, + value: convert(realm, val[key]), + }) + ); } return obj; diff --git a/src/utils/paths.js b/src/utils/paths.js index b72428d1a7..12ad17c92c 100644 --- a/src/utils/paths.js +++ b/src/utils/paths.js @@ -79,6 +79,10 @@ export class PathConditionsImplementation extends PathConditions { return this._assumedConditions.size; } + getAssumedConditions(): Set { + return this._assumedConditions; + } + refineBaseConditons(realm: Realm): void { if (realm.abstractValueImpliesMax > 0) return; let refine = (condition: AbstractValue) => { diff --git a/src/values/AbstractObjectValue.js b/src/values/AbstractObjectValue.js index bf9dbf8129..d722ba27a2 100644 --- a/src/values/AbstractObjectValue.js +++ b/src/values/AbstractObjectValue.js @@ -25,12 +25,13 @@ import { Value, } from "./index.js"; import { TypesDomain, ValuesDomain } from "../domains/index.js"; -import { IsDataDescriptor, cloneDescriptor, equalDescriptors } from "../methods/index.js"; +import { IsDataDescriptor } from "../methods/index.js"; import { Leak, Join, Widen } from "../singletons.js"; import invariant from "../invariant.js"; import { createOperationDescriptor, type OperationDescriptor } from "../utils/generator.js"; import { construct_empty_effects } from "../realm.js"; import { SimpleNormalCompletion } from "../completions.js"; +import { cloneDescriptor, equalDescriptors, PropertyDescriptor } from "../descriptors.js"; export default class AbstractObjectValue extends AbstractValue { constructor( @@ -326,11 +327,17 @@ export default class AbstractObjectValue extends AbstractValue { invariant(ob2 instanceof ObjectValue); let d1 = ob1.$GetOwnProperty(P); let d2 = ob2.$GetOwnProperty(P); - if (d1 === undefined || d2 === undefined || !equalDescriptors(d1, d2)) { + if (d1 === undefined || d2 === undefined) { // We do not handle the case where different loop iterations result in different kinds of propperties AbstractValue.reportIntrospectionError(this, P); throw new FatalError(); } + d1 = d1.throwIfNotConcrete(this.$Realm); + d2 = d2.throwIfNotConcrete(this.$Realm); + if (!equalDescriptors(d1, d2)) { + AbstractValue.reportIntrospectionError(this, P); + throw new FatalError(); + } let desc = cloneDescriptor(d1); invariant(desc !== undefined); if (IsDataDescriptor(this.$Realm, desc)) { @@ -340,7 +347,9 @@ export default class AbstractObjectValue extends AbstractValue { invariant(d1Value instanceof Value); let d2Value = d2.value; invariant(d2Value instanceof Value); - desc.value = Widen.widenValues(this.$Realm, d1Value, d2Value); + let dValue = Widen.widenValues(this.$Realm, d1Value, d2Value); + invariant(dValue instanceof Value); + desc.value = dValue; } else { // In this case equalDescriptors guarantees exact equality betwee d1 and d2. // Inlining the accessors will eventually bring in data properties if the accessors have loop variant behavior @@ -366,7 +375,7 @@ export default class AbstractObjectValue extends AbstractValue { } // ECMA262 9.1.6 - $DefineOwnProperty(_P: PropertyKeyValue, Desc: Descriptor): boolean { + $DefineOwnProperty(_P: PropertyKeyValue, _Desc: Descriptor): boolean { let P = _P; if (P instanceof StringValue) P = P.value; if (this.values.isTop()) { @@ -378,10 +387,11 @@ export default class AbstractObjectValue extends AbstractValue { if (elements.size === 1) { for (let cv of elements) { invariant(cv instanceof ObjectValue); - return cv.$DefineOwnProperty(P, Desc); + return cv.$DefineOwnProperty(P, _Desc); } invariant(false); } else { + let Desc = _Desc.throwIfNotConcrete(this.$Realm); if (!IsDataDescriptor(this.$Realm, Desc)) { AbstractValue.reportIntrospectionError(this, P); throw new FatalError(); @@ -395,13 +405,21 @@ export default class AbstractObjectValue extends AbstractValue { break; } } - let desc = { - value: "value" in Desc ? Desc.value : this.$Realm.intrinsics.undefined, - writable: "writable" in Desc ? Desc.writable : firstExistingDesc ? firstExistingDesc.writable : false, - enumerable: "enumerable" in Desc ? Desc.enumerable : firstExistingDesc ? firstExistingDesc.enumerable : false, + if (firstExistingDesc) { + firstExistingDesc = firstExistingDesc.throwIfNotConcrete(this.$Realm); + } + let desc = new PropertyDescriptor({ + value: Desc.value !== undefined ? Desc.value : this.$Realm.intrinsics.undefined, + writable: Desc.writable !== undefined ? Desc.writable : firstExistingDesc ? firstExistingDesc.writable : false, + enumerable: + Desc.enumerable !== undefined ? Desc.enumerable : firstExistingDesc ? firstExistingDesc.enumerable : false, configurable: - "configurable" in Desc ? Desc.configurable : firstExistingDesc ? firstExistingDesc.configurable : false, - }; + Desc.configurable !== undefined + ? Desc.configurable + : firstExistingDesc + ? firstExistingDesc.configurable + : false, + }); let newVal = desc.value; if (this.kind === "conditional") { // this is the join of two concrete/abstract objects @@ -412,9 +430,19 @@ export default class AbstractObjectValue extends AbstractValue { invariant(ob2 instanceof ObjectValue || ob2 instanceof AbstractObjectValue); let d1 = ob1.$GetOwnProperty(P); let d2 = ob2.$GetOwnProperty(P); - if ((d1 !== undefined && !equalDescriptors(d1, desc)) || (d2 !== undefined && !equalDescriptors(d2, desc))) { - AbstractValue.reportIntrospectionError(this, P); - throw new FatalError(); + if (d1 !== undefined) { + d1 = d1.throwIfNotConcrete(this.$Realm); + if (!equalDescriptors(d1, desc)) { + AbstractValue.reportIntrospectionError(this, P); + throw new FatalError(); + } + } + if (d2 !== undefined) { + d2 = d2.throwIfNotConcrete(this.$Realm); + if (!equalDescriptors(d2, desc)) { + AbstractValue.reportIntrospectionError(this, P); + throw new FatalError(); + } } let oldVal1 = d1 === undefined || d1.value === undefined ? this.$Realm.intrinsics.empty : d1.value; let oldVal2 = d2 === undefined || d2.value === undefined ? this.$Realm.intrinsics.empty : d2.value; @@ -438,9 +466,12 @@ export default class AbstractObjectValue extends AbstractValue { for (let cv of elements) { invariant(cv instanceof ObjectValue); let d = cv.$GetOwnProperty(P); - if (d !== undefined && !equalDescriptors(d, desc)) { - AbstractValue.reportIntrospectionError(this, P); - throw new FatalError(); + if (d !== undefined) { + d = d.throwIfNotConcrete(this.$Realm); + if (!equalDescriptors(d, desc)) { + AbstractValue.reportIntrospectionError(this, P); + throw new FatalError(); + } } let dval = d === undefined || d.value === undefined ? this.$Realm.intrinsics.empty : d.value; invariant(dval instanceof Value); @@ -957,14 +988,18 @@ export default class AbstractObjectValue extends AbstractValue { let result1 = true; let result2 = true; if (d1 !== undefined) { + d1 = d1.throwIfNotConcrete(this.$Realm); let newDesc1 = cloneDescriptor(d1); invariant(newDesc1); + newDesc1 = newDesc1.throwIfNotConcrete(this.$Realm); newDesc1.value = newVal1; result1 = ob1.$DefineOwnProperty(P, newDesc1); } if (d2 !== undefined) { + d2 = d2.throwIfNotConcrete(this.$Realm); let newDesc2 = cloneDescriptor(d2); invariant(newDesc2); + newDesc2 = newDesc2.throwIfNotConcrete(this.$Realm); newDesc2.value = newVal2; result2 = ob2.$DefineOwnProperty(P, newDesc2); } diff --git a/src/values/ArgumentsExotic.js b/src/values/ArgumentsExotic.js index 49c4b44de7..5b2a19bc2c 100644 --- a/src/values/ArgumentsExotic.js +++ b/src/values/ArgumentsExotic.js @@ -18,6 +18,7 @@ import { SameValuePartial } from "../methods/abstract.js"; import { Get, OrdinaryGet } from "../methods/get.js"; import { Properties } from "../singletons.js"; import invariant from "../invariant.js"; +import { PropertyDescriptor } from "../descriptors.js"; export default class ArgumentsExotic extends ObjectValue { constructor(realm: Realm, intrinsicName?: string) { @@ -36,7 +37,8 @@ export default class ArgumentsExotic extends ObjectValue { // 3. If desc is undefined, return desc. if (desc === undefined) return undefined; - Properties.ThrowIfMightHaveBeenDeleted(desc.value); + Properties.ThrowIfMightHaveBeenDeleted(desc); + desc = desc.throwIfNotConcrete(this.$Realm); // 4. Let map be args.[[ParameterMap]]. let map = args.$ParameterMap; @@ -56,7 +58,9 @@ export default class ArgumentsExotic extends ObjectValue { } // ECMA262 9.4.4.2 - $DefineOwnProperty(P: PropertyKeyValue, Desc: Descriptor): boolean { + $DefineOwnProperty(P: PropertyKeyValue, _Desc: Descriptor): boolean { + let Desc = _Desc.throwIfNotConcrete(this.$Realm); + // 1. Let args be the arguments object. let args = this; @@ -75,7 +79,7 @@ export default class ArgumentsExotic extends ObjectValue { // a. If Desc.[[Value]] is not present and Desc.[[Writable]] is present and its value is false, then if (Desc.value === undefined && Desc.writable === false) { // i. Let newArgDesc be a copy of Desc. - newArgDesc = Object.assign({}, Desc); + newArgDesc = new PropertyDescriptor(Desc); // ii. Set newArgDesc.[[Value]] to Get(map, P). newArgDesc.value = Get(this.$Realm, map, P); diff --git a/src/values/ArrayValue.js b/src/values/ArrayValue.js index e7007ad4c5..720baa3482 100644 --- a/src/values/ArrayValue.js +++ b/src/values/ArrayValue.js @@ -25,6 +25,7 @@ import { Leak, Properties, To, Utils } from "../singletons.js"; import { type OperationDescriptor } from "../utils/generator.js"; import invariant from "../invariant.js"; import { NestedOptimizedFunctionSideEffect } from "../errors.js"; +import { PropertyDescriptor } from "../descriptors.js"; type PossibleNestedOptimizedFunctions = [ { func: BoundFunctionValue | ECMAScriptSourceFunctionValue, thisValue: Value }, @@ -98,9 +99,9 @@ function createArrayWithWidenedNumericProperty( // Add unknownProperty so we manually handle this object property access abstractArrayValue.unknownProperty = { key: undefined, - descriptor: { + descriptor: new PropertyDescriptor({ value: AbstractValue.createFromType(realm, Value, "widened numeric property"), - }, + }), object: abstractArrayValue, }; return abstractArrayValue; @@ -149,7 +150,8 @@ export default class ArrayValue extends ObjectValue { oldLenDesc !== undefined && !IsAccessorDescriptor(this.$Realm, oldLenDesc), "cannot be undefined or an accessor descriptor" ); - Properties.ThrowIfMightHaveBeenDeleted(oldLenDesc.value); + Properties.ThrowIfMightHaveBeenDeleted(oldLenDesc); + oldLenDesc = oldLenDesc.throwIfNotConcrete(this.$Realm); // c. Let oldLen be oldLenDesc.[[Value]]. let oldLen = oldLenDesc.value; @@ -211,8 +213,7 @@ export default class ArrayValue extends ObjectValue { if (obj instanceof ArrayValue && obj.intrinsicName) { const prop = obj.unknownProperty; if (prop !== undefined && prop.descriptor !== undefined) { - const desc = prop.descriptor; - + const desc = prop.descriptor.throwIfNotConcrete(obj.$Realm); return desc.value instanceof AbstractValue && desc.value.kind === "widened numeric property"; } } diff --git a/src/values/FunctionValue.js b/src/values/FunctionValue.js index 2c603ecc26..cdc9fb3979 100644 --- a/src/values/FunctionValue.js +++ b/src/values/FunctionValue.js @@ -45,7 +45,7 @@ export default class FunctionValue extends ObjectValue { invariant(binding); let desc = binding.descriptor; invariant(desc); - let value = desc.value; + let value = desc.throwIfNotConcrete(this.$Realm).value; if (!(value instanceof NumberValue)) return undefined; return value.value; } diff --git a/src/values/IntegerIndexedExotic.js b/src/values/IntegerIndexedExotic.js index 2c764909ba..5a7b8e8356 100644 --- a/src/values/IntegerIndexedExotic.js +++ b/src/values/IntegerIndexedExotic.js @@ -18,6 +18,7 @@ import { OrdinaryHasProperty } from "../methods/has.js"; import { IntegerIndexedElementSet, IntegerIndexedElementGet } from "../methods/typedarray.js"; import { Properties, To } from "../singletons.js"; import invariant from "../invariant.js"; +import { PropertyDescriptor } from "../descriptors.js"; export default class IntegerIndexedExotic extends ObjectValue { constructor(realm: Realm, intrinsicName?: string) { @@ -51,12 +52,12 @@ export default class IntegerIndexedExotic extends ObjectValue { if (value instanceof UndefinedValue) return undefined; // iii. Return a PropertyDescriptor{[[Value]]: value, [[Writable]]: true, [[Enumerable]]: true, [[Configurable]]: false}. - return { + return new PropertyDescriptor({ value: value, writable: true, enumerable: true, configurable: false, - }; + }); } } // 4. Return OrdinaryGetOwnProperty(O, P). @@ -118,7 +119,7 @@ export default class IntegerIndexedExotic extends ObjectValue { } // ECMA262 9.4.5.3 - $DefineOwnProperty(P: PropertyKeyValue, Desc: Descriptor): boolean { + $DefineOwnProperty(P: PropertyKeyValue, _Desc: Descriptor): boolean { let O = this; // 1. Assert: IsPropertyKey(P) is true. @@ -153,6 +154,8 @@ export default class IntegerIndexedExotic extends ObjectValue { // v. If numericIndex ≥ length, return false. if (numericIndex >= length) return false; + let Desc = _Desc.throwIfNotConcrete(this.$Realm); + // vi. If IsAccessorDescriptor(Desc) is true, return false. if (IsAccessorDescriptor(this.$Realm, Desc) === true) return false; @@ -181,7 +184,7 @@ export default class IntegerIndexedExotic extends ObjectValue { } // 4. Return ! OrdinaryDefineOwnProperty(O, P, Desc). - return Properties.OrdinaryDefineOwnProperty(this.$Realm, O, P, Desc); + return Properties.OrdinaryDefineOwnProperty(this.$Realm, O, P, _Desc); } // ECMA262 9.4.5.4 diff --git a/src/values/NativeFunctionValue.js b/src/values/NativeFunctionValue.js index cf12b9b9c9..c3c94610c0 100644 --- a/src/values/NativeFunctionValue.js +++ b/src/values/NativeFunctionValue.js @@ -25,6 +25,7 @@ import { import type { PromiseCapability } from "../types.js"; import { ReturnCompletion } from "../completions.js"; import { Functions } from "../singletons.js"; +import { PropertyDescriptor } from "../descriptors.js"; export type NativeFunctionCallback = ( context: UndefinedValue | NullValue | ObjectValue | AbstractObjectValue, args: Array, @@ -64,12 +65,15 @@ export default class NativeFunctionValue extends ECMAScriptFunctionValue { this.callback = callback; this.length = length; - this.$DefineOwnProperty("length", { - value: new NumberValue(realm, length), - writable: false, - configurable: true, - enumerable: false, - }); + this.$DefineOwnProperty( + "length", + new PropertyDescriptor({ + value: new NumberValue(realm, length), + writable: false, + configurable: true, + enumerable: false, + }) + ); if (name !== undefined && name !== "") { if (name instanceof SymbolValue) { @@ -77,12 +81,15 @@ export default class NativeFunctionValue extends ECMAScriptFunctionValue { } else { this.name = name; } - this.$DefineOwnProperty("name", { - value: new StringValue(realm, this.name), - writable: false, - configurable: true, - enumerable: false, - }); + this.$DefineOwnProperty( + "name", + new PropertyDescriptor({ + value: new StringValue(realm, this.name), + writable: false, + configurable: true, + enumerable: false, + }) + ); } else { this.name = "native"; } diff --git a/src/values/ObjectValue.js b/src/values/ObjectValue.js index e912926898..7725605419 100644 --- a/src/values/ObjectValue.js +++ b/src/values/ObjectValue.js @@ -14,7 +14,6 @@ import { ValuesDomain } from "../domains/index.js"; import { FatalError } from "../errors.js"; import type { DataBlock, - Descriptor, IterationKind, ObjectKind, PromiseReaction, @@ -51,6 +50,7 @@ import { Properties } from "../singletons.js"; import invariant from "../invariant.js"; import type { typeAnnotation } from "@babel/types"; import { createOperationDescriptor } from "../utils/generator.js"; +import { Descriptor, PropertyDescriptor, type DescriptorInitializer, InternalSlotDescriptor } from "../descriptors.js"; export default class ObjectValue extends ConcreteValue { constructor( @@ -126,6 +126,7 @@ export default class ObjectValue extends ConcreteValue { configurable: true, get: function() { let binding = this[propBindingName]; + invariant(binding === undefined || binding.descriptor instanceof InternalSlotDescriptor); return binding === undefined ? undefined : binding.descriptor.value; }, set: function(v) { @@ -150,7 +151,7 @@ export default class ObjectValue extends ConcreteValue { ); let binding = this[propBindingName]; if (binding === undefined) { - let desc = { writeable: true, value: undefined }; + let desc = new InternalSlotDescriptor(undefined); this[propBindingName] = binding = { descriptor: desc, object: this, @@ -454,7 +455,7 @@ export default class ObjectValue extends ConcreteValue { name: SymbolValue | string, length: number, callback: NativeFunctionCallback, - desc?: Descriptor = {} + desc?: DescriptorInitializer ): Value { let intrinsicName; if (typeof name === "string") { @@ -469,18 +470,21 @@ export default class ObjectValue extends ConcreteValue { return fnValue; } - defineNativeProperty(name: SymbolValue | string, value?: Value | Array, desc?: Descriptor = {}): void { + defineNativeProperty(name: SymbolValue | string, value?: Value | Array, desc?: DescriptorInitializer): void { invariant(!value || value instanceof Value); - this.$DefineOwnProperty(name, { - value, - writable: true, - enumerable: false, - configurable: true, - ...desc, - }); + this.$DefineOwnProperty( + name, + new PropertyDescriptor({ + value, + writable: true, + enumerable: false, + configurable: true, + ...desc, + }) + ); } - defineNativeGetter(name: SymbolValue | string, callback: NativeFunctionCallback, desc?: Descriptor = {}): void { + defineNativeGetter(name: SymbolValue | string, callback: NativeFunctionCallback, desc?: DescriptorInitializer): void { let intrinsicName, funcName; if (typeof name === "string") { funcName = `get ${name}`; @@ -496,24 +500,30 @@ export default class ObjectValue extends ConcreteValue { } let func = new NativeFunctionValue(this.$Realm, intrinsicName, funcName, 0, callback); - this.$DefineOwnProperty(name, { - get: func, - set: this.$Realm.intrinsics.undefined, - enumerable: false, - configurable: true, - ...desc, - }); + this.$DefineOwnProperty( + name, + new PropertyDescriptor({ + get: func, + set: this.$Realm.intrinsics.undefined, + enumerable: false, + configurable: true, + ...desc, + }) + ); } - defineNativeConstant(name: SymbolValue | string, value?: Value | Array, desc?: Descriptor = {}): void { + defineNativeConstant(name: SymbolValue | string, value?: Value | Array, desc?: DescriptorInitializer): void { invariant(!value || value instanceof Value); - this.$DefineOwnProperty(name, { - value, - writable: false, - enumerable: false, - configurable: false, - ...desc, - }); + this.$DefineOwnProperty( + name, + new PropertyDescriptor({ + value, + writable: false, + enumerable: false, + configurable: false, + ...desc, + }) + ); } // Note that internal properties will not be copied to the snapshot, nor will they be removed. @@ -558,8 +568,8 @@ export default class ObjectValue extends ConcreteValue { let desc = from.$GetOwnProperty(nextKey); // ii. If desc is not undefined and desc.[[Enumerable]] is true, then - if (desc && desc.enumerable) { - Properties.ThrowIfMightHaveBeenDeleted(desc.value); + if (desc && desc.throwIfNotConcrete(this.$Realm).enumerable) { + Properties.ThrowIfMightHaveBeenDeleted(desc); // 1. Let propValue be ? Get(from, nextKey). let propValue = Get(this.$Realm, from, nextKey); @@ -576,7 +586,8 @@ export default class ObjectValue extends ConcreteValue { for (let [key, propertyBinding] of this.properties) { let desc = propertyBinding.descriptor; if (desc === undefined) continue; // deleted - Properties.ThrowIfMightHaveBeenDeleted(desc.value); + Properties.ThrowIfMightHaveBeenDeleted(desc); + desc = desc.throwIfNotConcrete(this.$Realm); let serializedDesc: any = { enumerable: desc.enumerable, configurable: desc.configurable }; if (desc.value) { serializedDesc.writable = desc.writable; @@ -656,7 +667,11 @@ export default class ObjectValue extends ConcreteValue { try { this.$Realm.invariantLevel = 0; let desc = this.$GetOwnProperty(P); - return desc !== undefined && desc.value instanceof Value ? desc.value : this.$Realm.intrinsics.undefined; + if (desc === undefined) { + return this.$Realm.intrinsics.undefined; + } + desc = desc.throwIfNotConcrete(this.$Realm); + return desc.value ? desc.value : this.$Realm.intrinsics.undefined; } finally { this.$Realm.invariantLevel = savedInvariantLevel; } diff --git a/src/values/ProxyValue.js b/src/values/ProxyValue.js index 477d7d314c..0380d114e5 100644 --- a/src/values/ProxyValue.js +++ b/src/values/ProxyValue.js @@ -304,7 +304,8 @@ export default class ProxyValue extends ObjectValue { if (trapResultObj instanceof UndefinedValue) { // a. If targetDesc is undefined, return undefined. if (!targetDesc) return undefined; - Properties.ThrowIfMightHaveBeenDeleted(targetDesc.value); + Properties.ThrowIfMightHaveBeenDeleted(targetDesc); + targetDesc = targetDesc.throwIfNotConcrete(realm); // b. If targetDesc.[[Configurable]] is false, throw a TypeError exception. if (!targetDesc.configurable) { @@ -344,9 +345,10 @@ export default class ProxyValue extends ObjectValue { } // 17. If resultDesc.[[Configurable]] is false, then + resultDesc = resultDesc.throwIfNotConcrete(realm); if (!resultDesc.configurable) { // a. If targetDesc is undefined or targetDesc.[[Configurable]] is true, then - if (!targetDesc || targetDesc.configurable) { + if (!targetDesc || targetDesc.throwIfNotConcrete(realm).configurable) { // i. Throw a TypeError exception. throw realm.createErrorThrowCompletion(realm.intrinsics.TypeError); } @@ -407,7 +409,7 @@ export default class ProxyValue extends ObjectValue { // 13. If Desc has a [[Configurable]] field and if Desc.[[Configurable]] is false, then let settingConfigFalse; - if ("configurable" in Desc && !Desc.configurable) { + if (Desc.throwIfNotConcrete(realm).configurable === false) { // a. Let settingConfigFalse be true. settingConfigFalse = true; } else { @@ -428,7 +430,7 @@ export default class ProxyValue extends ObjectValue { } } else { // 16. Else targetDesc is not undefined, - Properties.ThrowIfMightHaveBeenDeleted(targetDesc.value); + Properties.ThrowIfMightHaveBeenDeleted(targetDesc); // a. If IsCompatiblePropertyDescriptor(extensibleTarget, Desc, targetDesc) is false, throw a TypeError exception. if (!Properties.IsCompatiblePropertyDescriptor(realm, extensibleTarget, Desc, targetDesc)) { @@ -436,7 +438,7 @@ export default class ProxyValue extends ObjectValue { } // b. If settingConfigFalse is true and targetDesc.[[Configurable]] is true, throw a TypeError exception. - if (settingConfigFalse && targetDesc.configurable) { + if (settingConfigFalse && targetDesc.throwIfNotConcrete(realm).configurable) { throw realm.createErrorThrowCompletion(realm.intrinsics.TypeError); } } @@ -489,7 +491,8 @@ export default class ProxyValue extends ObjectValue { // b. If targetDesc is not undefined, then if (targetDesc) { - Properties.ThrowIfMightHaveBeenDeleted(targetDesc.value); + Properties.ThrowIfMightHaveBeenDeleted(targetDesc); + targetDesc = targetDesc.throwIfNotConcrete(realm); // i. If targetDesc.[[Configurable]] is false, throw a TypeError exception. if (!targetDesc.configurable) { @@ -553,7 +556,7 @@ export default class ProxyValue extends ObjectValue { // 10. If targetDesc is not undefined, then if (targetDesc) { - Properties.ThrowIfMightHaveBeenDeleted(targetDesc.value); + Properties.ThrowIfMightHaveBeenDeleted(targetDesc); // a. If IsDataDescriptor(targetDesc) is true and targetDesc.[[Configurable]] is false and targetDesc.[[Writable]] is false, then if (IsDataDescriptor(realm, targetDesc) && targetDesc.configurable === false && targetDesc.writable === false) { @@ -628,7 +631,7 @@ export default class ProxyValue extends ObjectValue { // 11. If targetDesc is not undefined, then if (targetDesc) { - Properties.ThrowIfMightHaveBeenDeleted(targetDesc.value); + Properties.ThrowIfMightHaveBeenDeleted(targetDesc); // a. If IsDataDescriptor(targetDesc) is true and targetDesc.[[Configurable]] is false and targetDesc.[[Writable]] is false, then if (IsDataDescriptor(realm, targetDesc) && !targetDesc.configurable && !targetDesc.writable) { @@ -699,7 +702,8 @@ export default class ProxyValue extends ObjectValue { // 11. If targetDesc is undefined, return true. if (!targetDesc) return true; - Properties.ThrowIfMightHaveBeenDeleted(targetDesc.value); + Properties.ThrowIfMightHaveBeenDeleted(targetDesc); + targetDesc = targetDesc.throwIfNotConcrete(realm); // 12. If targetDesc.[[Configurable]] is false, throw a TypeError exception. if (!targetDesc.configurable) { @@ -768,10 +772,10 @@ export default class ProxyValue extends ObjectValue { for (let key of targetKeys) { // a. Let desc be ? target.[[GetOwnProperty]](key). let desc = target.$GetOwnProperty(key); - if (desc) Properties.ThrowIfMightHaveBeenDeleted(desc.value); + if (desc) Properties.ThrowIfMightHaveBeenDeleted(desc); // b. If desc is not undefined and desc.[[Configurable]] is false, then - if (desc && desc.configurable === false) { + if (desc && desc.throwIfNotConcrete(realm).configurable === false) { // i. Append key as an element of targetNonconfigurableKeys. targetNonconfigurableKeys.push(key); } else { diff --git a/src/values/StringExotic.js b/src/values/StringExotic.js index 5b6287d1e5..3d0f1e65b6 100644 --- a/src/values/StringExotic.js +++ b/src/values/StringExotic.js @@ -15,6 +15,7 @@ import { ObjectValue, NumberValue, StringValue } from "./index.js"; import { IsInteger, IsArrayIndex } from "../methods/is.js"; import { Properties, To } from "../singletons.js"; import invariant from "../invariant.js"; +import { PropertyDescriptor } from "../descriptors.js"; export default class StringExotic extends ObjectValue { constructor(realm: Realm, intrinsicName?: string) { @@ -30,7 +31,7 @@ export default class StringExotic extends ObjectValue { // 3. If desc is not undefined, return desc. if (desc !== undefined) { - Properties.ThrowIfMightHaveBeenDeleted(desc.value); + Properties.ThrowIfMightHaveBeenDeleted(desc); return desc; } @@ -67,12 +68,12 @@ export default class StringExotic extends ObjectValue { let resultStr = new StringValue(this.$Realm, str.value.charAt(index)); // 13. Return a PropertyDescriptor{[[Value]]: resultStr, [[Writable]]: false, [[Enumerable]]: true, [[Configurable]]: false}. - return { + return new PropertyDescriptor({ value: resultStr, writable: false, enumerable: true, configurable: false, - }; + }); } // ECMA262 9.4.3.2 diff --git a/src/values/StringValue.js b/src/values/StringValue.js index cb47a11a8d..c7d7d5b0a4 100644 --- a/src/values/StringValue.js +++ b/src/values/StringValue.js @@ -42,7 +42,6 @@ export default class StringValue extends PrimitiveValue { } toDisplayString(): string { - // TODO: proper escaping - return `"${this.value}"`; + return JSON.stringify(this.value); } } diff --git a/test/serializer/abstract/PutValue7.js b/test/serializer/abstract/PutValue7.js index 295fefd7a8..46ea0584d7 100644 --- a/test/serializer/abstract/PutValue7.js +++ b/test/serializer/abstract/PutValue7.js @@ -1,3 +1,4 @@ +// throws introspection error let x = global.__abstract ? __abstract("boolean", "true") : true; let ob1 = {}; Object.defineProperty(ob1, "p", { writable: false, value: 1 });