Make Object.assign() safe to use with simple partial sources

Summary:
Fixes https://github.com/facebook/prepack/issues/1462.

Supersedes my attempts in https://github.com/facebook/prepack/pull/1459, https://github.com/facebook/prepack/pull/1460, and https://github.com/facebook/prepack/pull/1468.
The git history reflects a lot of back-and-forth so it might make more sense to look at the final code.

There are two separate fixes here:

* If we have a partial simple source and then a normal source, we shouldn't attempt to `Set` any further keys after the first partial. Instead we should emit residual `Object.assign` calls. If we set the keys, we end up with a wrong key order (a key gets set earlier than it should have). Key order (AFAIK) isn't mandated by the spec but tends to be relied on in product code because all engines preserve it in the order of assignments.

* It is not safe to mutate either a partial source or the target (when some sources are partial) in Prepack land after the `Object.assign` call. This is because Prepack will serialize all those mutations (as the object final state) right into the residual `Object.assign` call. But this is wrong because they haven't happened yet.

The second issue is thorny. In many cases sources (and the target) don't get mutated later. So we'd like to keep supporting `Object.assign` with partials when we don't touch them later.

To solve it, I introduced new `obj.makeFinal()` and `obj.isFinalObject()` methods. If an object is marked as "final", we cannot make changes to it that would change its emitted form. This is a way for us to say "let's try to continue as far as we can, but if anything tries to touch it, fatal".

It doesn't mean the object is "frozen" (technically we didn't freeze it), but it means Prepack can't deal with further mutations to it because Prepack has already "used" its current snapshot and it would be unsafe to change it.

I'm not sure I'm checking `isFinalObject()` in all places where it's necessary. If there is a way to do this with stronger guarantees please let me know. I want to still highlight that this fixes multiple existing issues on master so it's a step in a safer duration, even though I'm wary that if the concept is not fully baked, it might give a false sense of security.

Here's how we're using this new infra. When we emit a residual `Object.assign()` call because some sources are partial (and simple), I mark all sources and the target as "final". So if they don't get mutated, everything is fine. If they get mutated later, we get a fatal when they do. In the future Prepack might support this better, but the fatal makes it clear which cases it currently doesn't handle (and would produce wrong output for).

There might also be other cases where making an object as "final" lets us continue further, although I don't know Prepack codebase well enough to tell for sure. So I'm hoping it is useful as a generic concept outside of the `Object.assign()` use case.

The pure mode is treated a bit differently. In the pure mode we can get away with "leaking" final values whenever we need to write to them. Then they're treated like any other "leaked" object.

I added regression tests for all existing cases I could find, both in pure and impure mode.
Closes https://github.com/facebook/prepack/pull/1469

Reviewed By: trueadm

Differential Revision: D7046641

Pulled By: gaearon

fbshipit-source-id: 472188eeabe28dcce36ab026ecbcbc4c8e83176b
This commit is contained in:
Dan Abramov 2018-02-21 13:17:15 -08:00 committed by Facebook Github Bot
parent bfc005a15b
commit bd432f6316
33 changed files with 693 additions and 14 deletions

View File

@ -301,6 +301,27 @@ ReactStatistics {
}
`;
exports[`Test React (JSX) Functional component folding Simple with Object.assign #3 1`] = `
ReactStatistics {
"inlinedComponents": 0,
"optimizedTrees": 1,
}
`;
exports[`Test React (JSX) Functional component folding Simple with Object.assign #4 1`] = `
ReactStatistics {
"inlinedComponents": 0,
"optimizedTrees": 1,
}
`;
exports[`Test React (JSX) Functional component folding Simple with Object.assign #5 1`] = `
ReactStatistics {
"inlinedComponents": 0,
"optimizedTrees": 1,
}
`;
exports[`Test React (JSX) Functional component folding Simple with Object.assign 1`] = `
ReactStatistics {
"inlinedComponents": 1,
@ -679,6 +700,27 @@ ReactStatistics {
}
`;
exports[`Test React (create-element) Functional component folding Simple with Object.assign #3 1`] = `
ReactStatistics {
"inlinedComponents": 0,
"optimizedTrees": 1,
}
`;
exports[`Test React (create-element) Functional component folding Simple with Object.assign #4 1`] = `
ReactStatistics {
"inlinedComponents": 0,
"optimizedTrees": 1,
}
`;
exports[`Test React (create-element) Functional component folding Simple with Object.assign #5 1`] = `
ReactStatistics {
"inlinedComponents": 0,
"optimizedTrees": 1,
}
`;
exports[`Test React (create-element) Functional component folding Simple with Object.assign 1`] = `
ReactStatistics {
"inlinedComponents": 1,

View File

@ -220,6 +220,18 @@ function runTestSuite(outputJsx) {
await runTest(directory, "simple-assign2.js");
});
it("Simple with Object.assign #3", async () => {
await runTest(directory, "simple-assign3.js");
});
it("Simple with Object.assign #4", async () => {
await runTest(directory, "simple-assign4.js");
});
it("Simple with Object.assign #5", async () => {
await runTest(directory, "simple-assign5.js");
});
it("Circular reference", async () => {
await runTest(directory, "circular-reference.js");
});

View File

@ -14,6 +14,7 @@ import { Realm } from "../../realm.js";
import { NativeFunctionValue } from "../../values/index.js";
import {
AbstractValue,
AbstractObjectValue,
ObjectValue,
NullValue,
UndefinedValue,
@ -88,6 +89,13 @@ export default function(realm: Realm): NativeFunctionValue {
throw new FatalError();
}
to_must_be_partial = true;
// Make this temporally not partial
// so that we can call frm.$OwnPropertyKeys below.
frm.makeNotPartial();
}
if (to_must_be_partial) {
// Generate a residual Object.assign call that copies the
// partial properties that we don't know about.
AbstractValue.createTemporalFromBuildFunction(
@ -99,14 +107,19 @@ export default function(realm: Realm): NativeFunctionValue {
}
);
to_must_be_partial = true;
frm.makeNotPartial();
if (frm instanceof ObjectValue || frm instanceof AbstractObjectValue) {
// At this point any further mutations to the source would be unsafe
// because the Object.assign() call operates on the snapshot of the
// object at this point in time. We can't mutate that snapshot.
frm.makeFinal();
}
}
// ii. Let keys be ? from.[[OwnPropertyKeys]]().
keys = frm.$OwnPropertyKeys();
if (frm_was_partial) frm.makePartial();
}
if (to_must_be_partial) {
// Only OK if to is an empty object because nextSource might have
// properties at runtime that will overwrite current properties in to.
@ -116,6 +129,12 @@ export default function(realm: Realm): NativeFunctionValue {
AbstractValue.reportIntrospectionError(nextSource);
throw new FatalError();
}
// If `to` is going to be a partial, we are emitting Object.assign()
// calls for each argument. At this point we should not be trying to
// assign keys below because that will change the order of the keys on
// the resulting object (i.e. the keys assigned later would already be
// on the serialized version from the heap).
continue;
}
invariant(frm, "from required");
@ -152,6 +171,13 @@ export default function(realm: Realm): NativeFunctionValue {
// We already established above that `to` is simple,
// so set the `_isSimple` flag.
to.makeSimple();
if (to instanceof ObjectValue || to instanceof AbstractObjectValue) {
// At this point any further mutations to the target would be unsafe
// because the Object.assign() call operates on the snapshot of the
// object at this point in time. We can't mutate that snapshot.
to.makeFinal();
}
}
return to;
});

View File

@ -28,7 +28,7 @@ import {
} from "../values/index.js";
import { EvalPropertyName } from "../evaluators/ObjectExpression";
import { EnvironmentRecord, Reference } from "../environment.js";
import { FatalError } from "../errors.js";
import { CompilerDiagnostic, FatalError } from "../errors.js";
import invariant from "../invariant.js";
import {
Call,
@ -108,6 +108,7 @@ function InternalUpdatedProperty(realm: Realm, O: ObjectValue, P: PropertyKeyVal
if (P instanceof SymbolValue) return;
if (P instanceof StringValue) P = P.value;
invariant(!O.isLeakedObject()); // leaked objects are never updated
invariant(!O.isFinalObject()); // final objects are never updated
invariant(typeof P === "string");
let propertyBinding = InternalGetPropertiesMap(O, P).get(P);
invariant(propertyBinding !== undefined); // The callers ensure this
@ -206,9 +207,35 @@ function parentPermitsChildPropertyCreation(realm: Realm, O: ObjectValue, P: Pro
return false;
}
function ensureIsNotFinal(realm: Realm, O: ObjectValue, P: void | PropertyKeyValue) {
if (!O.isFinalObject()) {
return;
}
if (realm.isInPureScope()) {
// It's not safe to write to this object anymore because it's already
// been used in a way that serializes its final state. We can, however,
// leak it if we're in pure scope, and continue to emit assignments.
Leak.leakValue(realm, O);
if (O.isLeakedObject()) {
return;
}
}
// We can't continue because this object is already in its final state.
let error = new CompilerDiagnostic(
"Mutating an object with unknown properties, after some of those " +
"properties have already been used, is not yet supported.",
realm.currentLocation,
"PP0023",
"FatalError"
);
realm.handleError(error);
throw new FatalError();
}
export class PropertiesImplementation {
// ECMA262 9.1.9.1
OrdinarySet(realm: Realm, O: ObjectValue, P: PropertyKeyValue, V: Value, Receiver: Value): boolean {
ensureIsNotFinal(realm, O, P);
if (!realm.ignoreLeakLogic && O.isLeakedObject()) {
Leak.leakValue(realm, V);
if (realm.generator) {
@ -480,6 +507,7 @@ export class PropertiesImplementation {
// 3. If desc is undefined, return true.
if (!desc) {
ensureIsNotFinal(realm, O, P);
if (!realm.ignoreLeakLogic && O.isLeakedObject()) {
if (realm.generator) {
realm.generator.emitPropertyDelete(O, StringKey(P));
@ -490,6 +518,7 @@ export class PropertiesImplementation {
// 4. If desc.[[Configurable]] is true, then
if (desc.configurable) {
ensureIsNotFinal(realm, O, P);
if (O.isLeakedObject()) {
if (realm.generator) {
realm.generator.emitPropertyDelete(O, StringKey(P));
@ -607,12 +636,15 @@ export class PropertiesImplementation {
// b. Assert: extensible is true.
invariant(extensible === true, "expected extensible to be true");
if (O !== undefined && !realm.ignoreLeakLogic && O.isLeakedObject() && P !== undefined) {
leakDescriptor(realm, Desc);
if (realm.generator) {
realm.generator.emitDefineProperty(O, StringKey(P), Desc);
if (O !== undefined && P !== undefined) {
ensureIsNotFinal(realm, O, P);
if (!realm.ignoreLeakLogic && O.isLeakedObject()) {
leakDescriptor(realm, Desc);
if (realm.generator) {
realm.generator.emitDefineProperty(O, StringKey(P), Desc);
}
return true;
}
return true;
}
// c. If IsGenericDescriptor(Desc) is true or IsDataDescriptor(Desc) is true, then
@ -692,12 +724,15 @@ export class PropertiesImplementation {
}
}
if (O !== undefined && !realm.ignoreLeakLogic && O.isLeakedObject() && P !== undefined) {
leakDescriptor(realm, Desc);
if (realm.generator) {
realm.generator.emitDefineProperty(O, StringKey(P), Desc);
if (O !== undefined && P !== undefined) {
ensureIsNotFinal(realm, O, P);
if (!realm.ignoreLeakLogic && O.isLeakedObject()) {
leakDescriptor(realm, Desc);
if (realm.generator) {
realm.generator.emitDefineProperty(O, StringKey(P), Desc);
}
return true;
}
return true;
}
let oldDesc = current;
@ -1221,6 +1256,7 @@ export class PropertiesImplementation {
// ECMA262 9.1.2.1
OrdinarySetPrototypeOf(realm: Realm, O: ObjectValue, V: ObjectValue | NullValue): boolean {
ensureIsNotFinal(realm, O);
if (!realm.ignoreLeakLogic && O.isLeakedObject()) {
throw new FatalError();
}

View File

@ -81,6 +81,25 @@ export default class AbstractObjectValue extends AbstractValue {
return result;
}
isFinalObject(): boolean {
if (this.values.isTop()) return false;
let result;
for (let element of this.values.getElements()) {
invariant(element instanceof ObjectValue);
if (result === undefined) {
result = element.isFinalObject();
} else if (result !== element.isFinalObject()) {
AbstractValue.reportIntrospectionError(this);
throw new FatalError();
}
}
if (result === undefined) {
AbstractValue.reportIntrospectionError(this);
throw new FatalError();
}
return result;
}
mightBeFalse(): boolean {
return false;
}
@ -128,6 +147,17 @@ export default class AbstractObjectValue extends AbstractValue {
this.cachedIsSimpleObject = true;
}
makeFinal(): void {
if (this.values.isTop()) {
AbstractValue.reportIntrospectionError(this);
throw new FatalError();
}
for (let element of this.values.getElements()) {
invariant(element instanceof ObjectValue);
element.makeFinal();
}
}
throwIfNotObject(): AbstractObjectValue {
return this;
}

View File

@ -76,6 +76,7 @@ export default class ObjectValue extends ConcreteValue {
this._isPartial = realm.intrinsics.false;
this._hasLeaked = realm.intrinsics.false;
this._isSimple = realm.intrinsics.false;
this._isFinal = realm.intrinsics.false;
this.properties = new Map();
this.symbols = new Map();
this.refuseSerialization = refuseSerialization;
@ -86,6 +87,7 @@ export default class ObjectValue extends ConcreteValue {
"_isPartial",
"_hasLeaked",
"_isSimple",
"_isFinal",
"$ArrayIteratorNextIndex",
"$DateValue",
"$Extensible",
@ -244,6 +246,10 @@ export default class ObjectValue extends ConcreteValue {
// to return AbstractValue for unknown properties.
_isSimple: BooleanValue;
// If true, it is not safe to perform any more mutations that would change
// the object's serialized form.
_isFinal: AbstractValue | BooleanValue;
isTemplate: void | true;
properties: Map<string, PropertyBinding>;
@ -301,10 +307,24 @@ export default class ObjectValue extends ConcreteValue {
this._isSimple = this.$Realm.intrinsics.true;
}
makeFinal(): void {
this._isFinal = this.$Realm.intrinsics.true;
}
isPartialObject(): boolean {
return this._isPartial.value;
}
isFinalObject(): boolean {
if (this._isFinal instanceof BooleanValue) {
return this._isFinal.value;
}
if (this._isFinal === undefined) {
return false;
}
return true;
}
leak(): void {
this._hasLeaked = this.$Realm.intrinsics.true;
}

View File

@ -0,0 +1,14 @@
// recover-from-errors
// expected errors: [{"location":{"start":{"line":8,"column":7},"end":{"line":8,"column":16},"identifierName":"copyOfObj","source":"test/error-handler/object-assign10.js"},"severity":"FatalError","errorCode":"PP0023"}]
var obj = global.__abstract && global.__makePartial && global.__makeSimple ? __makeSimple(__makePartial(__abstract({foo: __abstract('number')}, "({foo:1})"))) : {foo:1};
var copyOfObj = {};
Object.assign(copyOfObj, obj);
delete copyOfObj.foo;
// Demonstrates the issue we would get
// if this hadn't been marked as an error.
inspect = function() {
return JSON.stringify(copyOfObj);
}

View File

@ -0,0 +1,23 @@
// recover-from-errors
// expected errors: [{"location":{"start":{"line":17,"column":22},"end":{"line":17,"column":27},"identifierName":"proto","source":"test/error-handler/object-assign11.js"},"severity":"FatalError","errorCode":"PP0023"}]
var obj = global.__abstract && global.__makePartial && global.__makeSimple ? __makeSimple(__makePartial(__abstract({}, "({foo:1})"))) : {foo:1};
var copyOfObj = {};
var y = 0;
Object.assign(copyOfObj, obj);
var proto = {};
Object.defineProperty(proto, 'foo', {
enumerable: true,
set() {
y = 42;
}
});
copyOfObj.__proto__ = proto;
// Demonstrates the issue we would get
// if this hadn't been marked as an error.
inspect = function() {
return JSON.stringify(y);
}

View File

@ -0,0 +1,15 @@
// recover-from-errors
// expected errors: [{"location":{"start":{"line":9,"column":8},"end":{"line":9,"column":9},"source":"test/error-handler/object-assign3.js"},"severity":"FatalError","errorCode":"PP0023"}]
var obj = global.__abstract && global.__makePartial && global.__makeSimple ? __makeSimple(__makePartial(__abstract({}, "({foo:1})"))) : {foo:1};
var x = {};
var y = {};
Object.assign(x, obj, y, {bar: 2});
y.foo = 2;
// Demonstrates the issue we would get
// if this hadn't been marked as an error.
inspect = function() {
return JSON.stringify(x);
}

View File

@ -0,0 +1,22 @@
// abstract effects
// recover-from-errors
// expected errors: [{"location":{"start":{"line":15,"column":16},"end":{"line":15,"column":18},"source":"test/error-handler/object-assign4.js"},"severity":"FatalError","errorCode":"PP0023"}]
var __evaluatePureFunction = this.__evaluatePureFunction || (f => f());
var obj = global.__abstract && global.__makePartial && global.__makeSimple ? __makeSimple(__makePartial(__abstract({}, "({foo:1})"))) : {foo:1};
// Intentionally allocate outside the pure scope.
var copyOfObj = {};
__evaluatePureFunction(() => {
Object.assign(copyOfObj, obj);
// Normally at this point we would leak it,
// but we can't because it was created outside the pure scope.
copyOfObj.x = 10;
});
// Demonstrates the issue we would get
// if this hadn't been marked as an error.
inspect = function() {
return JSON.stringify(copyOfObj);
}

View File

@ -0,0 +1,15 @@
// recover-from-errors
// expected errors: [{"location":{"start":{"line":9,"column":14},"end":{"line":9,"column":16},"source":"test/error-handler/object-assign5.js"},"severity":"FatalError","errorCode":"PP0023"}]
var obj = global.__abstract && global.__makePartial && global.__makeSimple ? __makeSimple(__makePartial(__abstract({}, "({foo:1})"))) : {foo:1};
var copyOfObj = Object.assign({}, obj);
var copyOfCopyOfObj = Object.assign({}, copyOfObj);
copyOfObj.x = 10;
// Demonstrates the issue we would get
// if this hadn't been marked as an error.
inspect = function() {
return JSON.stringify(copyOfCopyOfObj);
}

View File

@ -0,0 +1,14 @@
// recover-from-errors
// expected errors: [{"location":{"start":{"line":8,"column":14},"end":{"line":8,"column":16},"source":"test/error-handler/object-assign6.js"},"severity":"FatalError","errorCode":"PP0023"}]
var obj = global.__abstract && global.__makePartial && global.__makeSimple ? __makeSimple(__makePartial(__abstract({}, "({foo:1})"))) : {foo:1};
var copyOfObj = {};
Object.assign(copyOfObj, obj);
copyOfObj.x = 10;
// Demonstrates the issue we would get
// if this hadn't been marked as an error.
inspect = function() {
return JSON.stringify(copyOfObj);
}

View File

@ -0,0 +1,14 @@
// recover-from-errors
// expected errors: [{"location":{"start":{"line":8,"column":16},"end":{"line":8,"column":17},"source":"test/error-handler/object-assign7.js"},"severity":"FatalError","errorCode":"PP0023"}]
var obj = global.__abstract && global.__makePartial && global.__makeSimple ? __makeSimple(__makePartial(__abstract({foo: __abstract('number')}, "({foo:1})"))) : {foo:1};
var copyOfObj = {};
Object.assign(copyOfObj, obj);
copyOfObj.foo = 2;
// Demonstrates the issue we would get
// if this hadn't been marked as an error.
inspect = function() {
return JSON.stringify(copyOfObj);
}

View File

@ -0,0 +1,19 @@
// recover-from-errors
// expected errors: [{"location":{"start":{"line":9,"column":14},"end":{"line":9,"column":18},"source":"test/error-handler/object-assign8.js"},"severity":"FatalError","errorCode":"PP0023"}]
var obj = global.__abstract && global.__makePartial && global.__makeSimple ? __makeSimple(__makePartial(__abstract({}, "({foo:1})"))) : {foo:1};
var copyOfObj = {};
Object.assign(copyOfObj, obj);
Object.defineProperty(copyOfObj, 'x', {
enumerable: true,
get() {
return 10;
},
});
// Demonstrates the issue we would get
// if this hadn't been marked as an error.
inspect = function() {
return JSON.stringify(copyOfObj);
}

View File

@ -0,0 +1,20 @@
// recover-from-errors
// expected errors: [{"location":{"start":{"line":10,"column":14},"end":{"line":10,"column":18},"source":"test/error-handler/object-assign9.js"},"severity":"FatalError","errorCode":"PP0023"}]
var obj = global.__abstract && global.__makePartial && global.__makeSimple ? __makeSimple(__makePartial(__abstract({}, "({foo:1})"))) : {foo:1};
var copyOfObj = {};
var y = 0;
Object.assign(copyOfObj, obj);
Object.defineProperty(copyOfObj, 'foo', {
enumerable: true,
set() {
y = 42;
}
});
// Demonstrates the issue we would get
// if this hadn't been marked as an error.
inspect = function() {
return JSON.stringify(y);
}

View File

@ -0,0 +1,24 @@
var React = require('react');
this['React'] = React;
function App(props) {
var obj1 = Object.assign({}, props, {x: 20});
var obj2 = Object.assign({}, obj1);
return (
<div>
{obj1.x}
{obj2.x}
</div>
);
}
App.getTrials = function(renderer, Root) {
renderer.update(<Root x={10} />);
return [['simple render with object assign', renderer.toJSON()]];
};
if (this.__registerReactComponentRoot) {
__registerReactComponentRoot(App);
}
module.exports = App;

View File

@ -0,0 +1,22 @@
var React = require('react');
this['React'] = React;
function App(props) {
var obj = Object.assign({}, {x: 20}, props);
return (
<div>
{obj.x}
</div>
);
}
App.getTrials = function(renderer, Root) {
renderer.update(<Root x={10} />);
return [['simple render with object assign', renderer.toJSON()]];
};
if (this.__registerReactComponentRoot) {
__registerReactComponentRoot(App);
}
module.exports = App;

View File

@ -0,0 +1,26 @@
var React = require('react');
this['React'] = React;
function App(props) {
var obj1 = {};
var obj2 = {};
Object.assign(obj1, props, obj2, {x: 20});
obj2.foo = 2;
return (
<div>
{obj1.x}
{obj1.foo}
</div>
);
}
App.getTrials = function(renderer, Root) {
renderer.update(<Root x={10} />);
return [['simple render with object assign', renderer.toJSON()]];
};
if (this.__registerReactComponentRoot) {
__registerReactComponentRoot(App);
}
module.exports = App;

View File

@ -0,0 +1,15 @@
// abstract effects
var __evaluatePureFunction = this.__evaluatePureFunction || (f => f());
var obj = global.__abstract && global.__makePartial && global.__makeSimple ? __makeSimple(__makePartial(__abstract({}, "({foo:1})"))) : {foo:1};
var copyOfCopyOfObj;
__evaluatePureFunction(() => {
var copyOfObj = Object.assign({}, obj);
copyOfCopyOfObj = Object.assign({}, copyOfObj);
copyOfObj.x = 10;
});
inspect = function() {
return JSON.stringify(copyOfCopyOfObj);
}

View File

@ -0,0 +1,15 @@
// abstract effects
var __evaluatePureFunction = this.__evaluatePureFunction || (f => f());
var obj = global.__abstract && global.__makePartial && global.__makeSimple ? __makeSimple(__makePartial(__abstract({}, "({foo:1})"))) : {foo:1};
var copyOfObj;
__evaluatePureFunction(() => {
copyOfObj = {};
Object.assign(copyOfObj, obj);
copyOfObj.x = 10;
});
inspect = function() {
return JSON.stringify(copyOfObj);
}

View File

@ -0,0 +1,15 @@
// abstract effects
var __evaluatePureFunction = this.__evaluatePureFunction || (f => f());
var obj = global.__abstract && global.__makePartial && global.__makeSimple ? __makeSimple(__makePartial(__abstract({foo: __abstract('number')}, "({foo:1})"))) : {foo:1};
var copyOfObj;
__evaluatePureFunction(() => {
copyOfObj = {};
Object.assign(copyOfObj, obj);
copyOfObj.foo = 2;
});
inspect = function() {
return JSON.stringify(copyOfObj);
}

View File

@ -0,0 +1,20 @@
// abstract effects
var __evaluatePureFunction = this.__evaluatePureFunction || (f => f());
var obj = global.__abstract && global.__makePartial && global.__makeSimple ? __makeSimple(__makePartial(__abstract({}, "({foo:1})"))) : {foo:1};
var copyOfObj;
__evaluatePureFunction(() => {
copyOfObj = {};
Object.assign(copyOfObj, obj);
Object.defineProperty(copyOfObj, 'x', {
enumerable: true,
get() {
return 10;
},
});
});
inspect = function() {
return JSON.stringify(copyOfObj);
}

View File

@ -0,0 +1,15 @@
// abstract effects
var __evaluatePureFunction = this.__evaluatePureFunction || (f => f());
var obj = global.__abstract && global.__makePartial && global.__makeSimple ? __makeSimple(__makePartial(__abstract({foo: __abstract('number')}, "({foo:1})"))) : {foo:1};
var copyOfObj;
__evaluatePureFunction(() => {
copyOfObj = {};
Object.assign(copyOfObj, obj);
copyOfObj.foo = 2;
});
inspect = function() {
return JSON.stringify(copyOfObj);
}

View File

@ -0,0 +1,22 @@
// abstract effects
var __evaluatePureFunction = this.__evaluatePureFunction || (f => f());
var obj = global.__abstract && global.__makePartial && global.__makeSimple ? __makeSimple(__makePartial(__abstract({foo: __abstract('number')}, "({foo:1})"))) : {foo:1};
var copyOfObj;
var y;
__evaluatePureFunction(() => {
copyOfObj = {};
y = 0;
Object.assign(copyOfObj, obj);
Object.defineProperty(copyOfObj, 'foo', {
enumerable: true,
set() {
y = 42;
}
});
});
inspect = function() {
return JSON.stringify(y);
}

View File

@ -0,0 +1,15 @@
// abstract effects
var __evaluatePureFunction = this.__evaluatePureFunction || (f => f());
var obj = global.__abstract && global.__makePartial && global.__makeSimple ? __makeSimple(__makePartial(__abstract({foo: __abstract('number')}, "({foo:1})"))) : {foo:1};
var copyOfObj;
__evaluatePureFunction(() => {
copyOfObj = {};
Object.assign(copyOfObj, obj);
delete copyOfObj.foo;
});
inspect = function() {
return JSON.stringify(copyOfObj);
}

View File

@ -0,0 +1,26 @@
// abstract effects
var __evaluatePureFunction = this.__evaluatePureFunction || (f => f());
var obj = global.__abstract && global.__makePartial && global.__makeSimple ? __makeSimple(__makePartial(__abstract({}, "({foo:1})"))) : {foo:1};
var copyOfObj;
var y;
__evaluatePureFunction(() => {
copyOfObj = {};
y = 0;
Object.assign(copyOfObj, obj);
var proto = {};
Object.defineProperty(proto, 'foo', {
enumerable: true,
set() {
y = 42;
}
});
copyOfObj.__proto__ = proto;
});
inspect = function() {
return JSON.stringify(y);
}

View File

@ -0,0 +1,6 @@
var obj = global.__abstract && global.__makePartial && global.__makeSimple ? __makeSimple(__makePartial(__abstract({}, "({foo:1})"))) : {foo:1};
var copyOfObj = Object.assign({}, obj, {foo: 2});
inspect = function() {
return JSON.stringify(copyOfObj);
}

View File

@ -0,0 +1,13 @@
// abstract effects
var __evaluatePureFunction = this.__evaluatePureFunction || (f => f());
var obj = global.__abstract && global.__makePartial && global.__makeSimple ? __makeSimple(__makePartial(__abstract({}, "({foo:1})"))) : {foo:1};
var copyOfObj;
__evaluatePureFunction(() => {
copyOfObj = Object.assign({}, obj, {foo: 2});
});
inspect = function() {
return JSON.stringify(copyOfObj);
}

View File

@ -1,6 +1,6 @@
var obj = global.__abstract && global.__makePartial && global.__makeSimple ? __makeSimple(__makePartial(__abstract({}, "({foo:1})"))) : {foo:1};
var copyOfObj = Object.assign({}, obj);
inspect = function() {
inspect = function() {
return JSON.stringify(copyOfObj);
}

View File

@ -0,0 +1,13 @@
// abstract effects
var __evaluatePureFunction = this.__evaluatePureFunction || (f => f());
var obj = global.__abstract && global.__makePartial && global.__makeSimple ? __makeSimple(__makePartial(__abstract({}, "({foo:1})"))) : {foo:1};
var copyOfObj;
__evaluatePureFunction(() => {
copyOfObj = Object.assign({}, obj, {foo: 2});
});
inspect = function() {
return JSON.stringify(copyOfObj);
}

View File

@ -0,0 +1,13 @@
// abstract effects
var __evaluatePureFunction = this.__evaluatePureFunction || (f => f());
var obj = global.__abstract && global.__makePartial && global.__makeSimple ? __makeSimple(__makePartial(__abstract({}, "({foo:1})"))) : {foo:1};
var copyOfObj;
__evaluatePureFunction(() => {
copyOfObj = Object.assign({}, {foo: 2}, obj);
});
inspect = function() {
return JSON.stringify(copyOfObj);
}

View File

@ -0,0 +1,16 @@
// abstract effects
var __evaluatePureFunction = this.__evaluatePureFunction || (f => f());
var obj = global.__abstract && global.__makePartial && global.__makeSimple ? __makeSimple(__makePartial(__abstract({}, "({foo:1})"))) : {foo:1};
var x;
__evaluatePureFunction(() => {
x = {};
var y = {};
Object.assign(x, obj, y, {bar: 2});
y.foo = 2;
});
inspect = function() {
return JSON.stringify(x);
}

View File

@ -0,0 +1,81 @@
// abstract effects
var __evaluatePureFunction = this.__evaluatePureFunction || (f => f());
__evaluatePureFunction(() => {
let dims = global.__abstract ? __abstract({
window: undefined,
screen: undefined,
windowPhysicalPixels: __abstract({
width: __abstract("number", "/* windowPhysicalPixels.width = */ 1"),
height: __abstract("number", "/* windowPhysicalPixels.height = */ 1"),
scale: __abstract("number", "/* windowPhysicalPixels.scale = */ 2"),
fontScale: __abstract("number", "/* windowPhysicalPixels.fontScale = */4"),
}),
screenPhysicalPixels: __abstract({
width: __abstract("number", "/* screenPhysicalPixels.width = */ 1"),
height: __abstract("number", "/* screenPhysicalPixels.height = */1"),
scale: __abstract("number", "/* screenPhysicalPixels.scale = */ 2"),
fontScale: __abstract("number", "/*screenPhysicalPixels.fontScale = */4"),
}),
}, `({
window: undefined,
screen: undefined,
windowPhysicalPixels: {
width: 1,
height: 1,
scale: 2,
fontScale: 4
},
screenPhysicalPixels: {
width: 1,
height: 1,
scale: 2,
fontScale: 4
}
})`) :
{
window: undefined,
screen: undefined,
windowPhysicalPixels: {
width: 1,
height: 1,
scale: 2,
fontScale: 4
},
screenPhysicalPixels: {
width: 1,
height: 1,
scale: 2,
fontScale: 4
}
};
dims = JSON.parse(JSON.stringify(dims));
// Note that the object returned by JSON.parse will never have getters, and it only has well-behaved properties.
// Prepack already has some magic built-in to preserve the "template shape" when cloning an object via parse/stringify.
let windowPhysicalPixels = dims.windowPhysicalPixels;
dims.window = {
width: windowPhysicalPixels.width / windowPhysicalPixels.scale,
height: windowPhysicalPixels.height / windowPhysicalPixels.scale,
scale: windowPhysicalPixels.scale,
fontScale: windowPhysicalPixels.fontScale,
};
let screenPhysicalPixels = dims.screenPhysicalPixels;
dims.screen = {
width: screenPhysicalPixels.width / screenPhysicalPixels.scale,
height: screenPhysicalPixels.height / screenPhysicalPixels.scale,
scale: screenPhysicalPixels.scale,
fontScale: screenPhysicalPixels.fontScale,
};
delete dims.screenPhysicalPixels;
delete dims.windowPhysicalPixels;
let dimensions = {};
Object.assign(dimensions, dims);
dimensions.extra = 'hello';
inspect = function() { return Object.keys(dimensions).join(','); }
});