diff --git a/src/serializer/ResidualHeapSerializer.js b/src/serializer/ResidualHeapSerializer.js index 9c7305341..e21d37f1e 100644 --- a/src/serializer/ResidualHeapSerializer.js +++ b/src/serializer/ResidualHeapSerializer.js @@ -324,11 +324,20 @@ export class ResidualHeapSerializer { ); } + // TODO #2259: Make deduplication in the face of leaking work for custom accessors + let isCertainlyLeaked = !obj.mightNotBeHavocedObject(); + let shouldDropAsAssignedProp = (descriptor: Descriptor | void) => + isCertainlyLeaked && (descriptor !== undefined && (descriptor.get === undefined && descriptor.set === undefined)); + // inject properties for (let [key, propertyBinding] of properties) { invariant(propertyBinding); + if (propertyBinding.pathNode !== undefined) continue; // Property is assigned to inside loop let desc = propertyBinding.descriptor; + + if (shouldDropAsAssignedProp(desc)) continue; + if (desc === undefined) continue; //deleted if (this.residualHeapInspector.canIgnoreProperty(obj, key)) continue; invariant(desc !== undefined); @@ -1694,13 +1703,25 @@ export class ResidualHeapSerializer { let remainingProperties = new Map(val.properties); const dummyProperties = new Set(); let props = []; + let isCertainlyLeaked = !val.mightNotBeHavocedObject(); + + // 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)); + if (val.temporalAlias !== undefined) { return t.objectExpression(props); } else { for (let [key, propertyBinding] of val.properties) { + if (propertyBinding.descriptor !== undefined && shouldDropAsAssignedProp(propertyBinding.descriptor)) { + remainingProperties.delete(key); + continue; + } + if (propertyBinding.pathNode !== undefined) continue; // written to inside loop let descriptor = propertyBinding.descriptor; if (descriptor === undefined || descriptor.value === undefined) continue; // deleted + let serializedKey = getAsPropertyNameExpression(key); if (this._canEmbedProperty(val, key, descriptor)) { let propValue = descriptor.value; @@ -1727,6 +1748,7 @@ export class ResidualHeapSerializer { } } } + this._emitObjectProperties( val, remainingProperties, @@ -1734,6 +1756,7 @@ export class ResidualHeapSerializer { dummyProperties, skipPrototype ); + return t.objectExpression(props); } diff --git a/src/serializer/ResidualHeapVisitor.js b/src/serializer/ResidualHeapVisitor.js index 696a0d4d6..a33ed620e 100644 --- a/src/serializer/ResidualHeapVisitor.js +++ b/src/serializer/ResidualHeapVisitor.js @@ -287,6 +287,7 @@ export class ResidualHeapVisitor { } visitObjectProperties(obj: ObjectValue, kind?: ObjectKind): void { + // In non-instant render mode, properties of leaked objects are generated via assignments let { skipPrototype, constructor } = getObjectPrototypeMetadata(this.realm, obj); if (obj.temporalAlias !== undefined) return; @@ -313,6 +314,15 @@ export class ResidualHeapVisitor { continue; } if (propertyBindingValue.pathNode !== undefined) continue; // property is written to inside a loop + + // Leaked object. Properties are set via assignments + // TODO #2259: Make deduplication in the face of leaking work for custom accessors + if ( + !obj.mightNotBeHavocedObject() && + (descriptor !== undefined && (descriptor.get === undefined && descriptor.set === undefined)) + ) + continue; + invariant(propertyBindingValue); this.visitObjectProperty(propertyBindingValue); } diff --git a/test/serializer/optimized-functions/ConditionallyLeakedBinding.js b/test/serializer/optimized-functions/ConditionallyLeakedBinding.js new file mode 100644 index 000000000..0046581c1 --- /dev/null +++ b/test/serializer/optimized-functions/ConditionallyLeakedBinding.js @@ -0,0 +1,22 @@ +(function() { + function f(c, g) { + let x = 23; + let y = 0; + if (c) { + x = Date.now(); + function h() { + y = x; + x++; + } + g(h); + return x - y; + } else { + x = Date.now(); + return x - y; + } + } + global.__optimize && __optimize(f); + global.inspect = function() { + return f(true, g => g()); + }; +})(); diff --git a/test/serializer/optimized-functions/ConditionallyLeakedObject1.js b/test/serializer/optimized-functions/ConditionallyLeakedObject1.js new file mode 100644 index 000000000..31c3a19a5 --- /dev/null +++ b/test/serializer/optimized-functions/ConditionallyLeakedObject1.js @@ -0,0 +1,17 @@ +(function() { + function f(g, c) { + let o = { foo: 42 }; + if (c) { + g(o); + } else { + o.foo = 2; + } + + return o; + } + + global.__optimize && __optimize(f); + inspect = function() { + return JSON.stringify(f(o => o, false)); + }; +})(); diff --git a/test/serializer/optimized-functions/ConditionallyLeakedObject2.js b/test/serializer/optimized-functions/ConditionallyLeakedObject2.js new file mode 100644 index 000000000..da8f6d212 --- /dev/null +++ b/test/serializer/optimized-functions/ConditionallyLeakedObject2.js @@ -0,0 +1,17 @@ +(function() { + function f(g, c) { + let o = { foo: 42 }; + if (c) { + g(o); + } else { + o.x = 1; + } + + return o; + } + + global.__optimize && __optimize(f); + inspect = function() { + return JSON.stringify(f(o => o, false)); + }; +})(); diff --git a/test/serializer/optimized-functions/LeakObjectWithSetter.js b/test/serializer/optimized-functions/LeakObjectWithSetter.js new file mode 100644 index 000000000..827c69191 --- /dev/null +++ b/test/serializer/optimized-functions/LeakObjectWithSetter.js @@ -0,0 +1,17 @@ +(function() { + function f(g) { + let o = { + foo: 1, + set x(v) { + this.foo += 1; + }, + }; + g(o); + return o; + } + + global.__optimize && __optimize(f); + inspect = () => { + JSON.stringify(f(o => o)); + }; +})(); diff --git a/test/serializer/optimized-functions/LeakedCustomObjectInMultipleScopes.js b/test/serializer/optimized-functions/LeakedCustomObjectInMultipleScopes.js new file mode 100644 index 000000000..bddf39f6e --- /dev/null +++ b/test/serializer/optimized-functions/LeakedCustomObjectInMultipleScopes.js @@ -0,0 +1,17 @@ +function f(g, c) { + let o = { foo: {} }; + o.__proto__ = {}; + + if (c) { + g(o); + } else { + o.foo = { bar: 5 }; + g(o); + } + return o; +} + +global.__optimize && __optimize(f); +inspect = function() { + return f(o => o); +}; diff --git a/test/serializer/optimized-functions/LeakedObjectCodeDuplication.js b/test/serializer/optimized-functions/LeakedObjectCodeDuplication.js new file mode 100644 index 000000000..796229239 --- /dev/null +++ b/test/serializer/optimized-functions/LeakedObjectCodeDuplication.js @@ -0,0 +1,12 @@ +// Copies of 42:1 +function f(g) { + var o = {}; + o.foo = 42; + g(o); + return o; +} + +global.__optimize && __optimize(f); +inspect = function() { + return f(o => o); +};