"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.LeakImplementation = undefined; var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }(); var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); /** * 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. */ var _errors = require("../errors.js"); var _environment = require("../environment.js"); var _index = require("../values/index.js"); var _index2 = require("../methods/index.js"); var _babelTypes = require("babel-types"); var t = _interopRequireWildcard(_babelTypes); var _babelTraverse = require("babel-traverse"); var _babelTraverse2 = _interopRequireDefault(_babelTraverse); var _invariant = require("../invariant.js"); var _invariant2 = _interopRequireDefault(_invariant); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } } function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } function visitName(path, state, name, read, write) { // Is the name bound to some local identifier? If so, we don't need to do anything if (path.scope.hasBinding(name, /*noGlobals*/true)) return; // Otherwise, let's record that there's an unbound identifier if (read) state.unboundReads.add(name); if (write) state.unboundWrites.add(name); } function ignorePath(path) { var parent = path.parent; return t.isLabeledStatement(parent) || t.isBreakStatement(parent) || t.isContinueStatement(parent); } var LeakedClosureRefVisitor = { ReferencedIdentifier: function ReferencedIdentifier(path, state) { if (ignorePath(path)) return; var innerName = path.node.name; if (innerName === "arguments") { return; } visitName(path, state, innerName, true, false); }, "AssignmentExpression|UpdateExpression": function AssignmentExpressionUpdateExpression(path, state) { var doesRead = path.node.operator !== "="; for (var name in path.getBindingIdentifiers()) { visitName(path, state, name, doesRead, true); } } }; function getLeakedFunctionInfo(value) { // TODO: This should really be cached on a per AST basis in case we have // many uses of the same closure. It should ideally share this cache // and data with ResidualHeapVisitor. (0, _invariant2.default)(value instanceof _index.ECMAScriptSourceFunctionValue); (0, _invariant2.default)(value.constructor === _index.ECMAScriptSourceFunctionValue); var functionInfo = { unboundReads: new Set(), unboundWrites: new Set() }; var formalParameters = value.$FormalParameters; (0, _invariant2.default)(formalParameters != null); var code = value.$ECMAScriptCode; (0, _invariant2.default)(code != null); (0, _babelTraverse2.default)(t.file(t.program([t.expressionStatement(t.functionExpression(null, formalParameters, code))])), LeakedClosureRefVisitor, null, functionInfo); return functionInfo; } var ObjectValueLeakingVisitor = function () { // ObjectValues to visit if they're reachable. function ObjectValueLeakingVisitor(objectsTrackedForLeaks) { _classCallCheck(this, ObjectValueLeakingVisitor); this.objectsTrackedForLeaks = objectsTrackedForLeaks; this.visitedValues = new Set(); } // Values that has been visited. _createClass(ObjectValueLeakingVisitor, [{ key: "mustVisit", value: function mustVisit(val) { if (val instanceof _index.ObjectValue) { // For Objects we only need to visit it if it is tracked // as a newly created object that might still be mutated. // Abstract values gets their arguments visited. if (!this.objectsTrackedForLeaks.has(val)) return false; } if (this.visitedValues.has(val)) return false; this.visitedValues.add(val); return true; } }, { key: "visitObjectProperty", value: function visitObjectProperty(binding) { var desc = binding.descriptor; if (desc === undefined) return; //deleted this.visitDescriptor(desc); } }, { key: "visitObjectProperties", value: function visitObjectProperties(obj, kind) { // visit symbol properties var _iteratorNormalCompletion = true; var _didIteratorError = false; var _iteratorError = undefined; try { for (var _iterator = obj.symbols[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { var _ref = _step.value; var _ref2 = _slicedToArray(_ref, 2); var propertyBindingValue = _ref2[1]; (0, _invariant2.default)(propertyBindingValue); this.visitObjectProperty(propertyBindingValue); } // visit string properties } catch (err) { _didIteratorError = true; _iteratorError = err; } finally { try { if (!_iteratorNormalCompletion && _iterator.return) { _iterator.return(); } } finally { if (_didIteratorError) { throw _iteratorError; } } } var _iteratorNormalCompletion2 = true; var _didIteratorError2 = false; var _iteratorError2 = undefined; try { for (var _iterator2 = obj.properties[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) { var _ref3 = _step2.value; var _ref4 = _slicedToArray(_ref3, 2); var _propertyBindingValue = _ref4[1]; (0, _invariant2.default)(_propertyBindingValue); this.visitObjectProperty(_propertyBindingValue); } // inject properties with computed names } catch (err) { _didIteratorError2 = true; _iteratorError2 = err; } finally { try { if (!_iteratorNormalCompletion2 && _iterator2.return) { _iterator2.return(); } } finally { if (_didIteratorError2) { throw _iteratorError2; } } } if (obj.unknownProperty !== undefined) { var desc = obj.unknownProperty.descriptor; if (desc !== undefined) { var val = desc.value; (0, _invariant2.default)(val instanceof _index.AbstractValue); this.visitObjectPropertiesWithComputedNames(val); } } // prototype this.visitObjectPrototype(obj); // if this object wasn't already leaked, we need mark it as leaked // so that any mutation and property access get tracked after this. if (!obj.isLeakedObject()) { obj.leak(); } } }, { key: "visitObjectPrototype", value: function visitObjectPrototype(obj) { var proto = obj.$Prototype; this.visitValue(proto); } }, { key: "visitObjectPropertiesWithComputedNames", value: function visitObjectPropertiesWithComputedNames(absVal) { (0, _invariant2.default)(absVal.args.length === 3); var cond = absVal.args[0]; (0, _invariant2.default)(cond instanceof _index.AbstractValue); if (cond.kind === "template for property name condition") { var P = cond.args[0]; (0, _invariant2.default)(P instanceof _index.AbstractValue); var V = absVal.args[1]; var earlier_props = absVal.args[2]; if (earlier_props instanceof _index.AbstractValue) this.visitObjectPropertiesWithComputedNames(earlier_props); this.visitValue(P); this.visitValue(V); } else { // conditional assignment this.visitValue(cond); var consequent = absVal.args[1]; (0, _invariant2.default)(consequent instanceof _index.AbstractValue); var alternate = absVal.args[2]; (0, _invariant2.default)(alternate instanceof _index.AbstractValue); this.visitObjectPropertiesWithComputedNames(consequent); this.visitObjectPropertiesWithComputedNames(alternate); } } }, { key: "visitDescriptor", value: function visitDescriptor(desc) { (0, _invariant2.default)(desc.value === undefined || desc.value instanceof _index.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); } }, { key: "visitDeclarativeEnvironmentRecordBinding", value: function visitDeclarativeEnvironmentRecordBinding(record, remainingLeakedBindings) { var bindings = record.bindings; var _iteratorNormalCompletion3 = true; var _didIteratorError3 = false; var _iteratorError3 = undefined; try { for (var _iterator3 = Object.keys(bindings)[Symbol.iterator](), _step3; !(_iteratorNormalCompletion3 = (_step3 = _iterator3.next()).done); _iteratorNormalCompletion3 = true) { var bindingName = _step3.value; var binding = bindings[bindingName]; // Check if this binding is referenced, and if so delete it from the set. var isRead = remainingLeakedBindings.unboundReads.delete(bindingName); var isWritten = remainingLeakedBindings.unboundWrites.delete(bindingName); if (isRead) { // If this binding can be read from the closure, its value has now leaked. var value = binding.value; if (value) { this.visitValue(value); } } if (isWritten || isRead) { // If this binding could have been mutated from the closure, then the // binding itself has now leaked, but not necessarily the value in it. // TODO: We could tag a leaked binding as read and/or write. That way // we don't have to leak values written to this binding if only writes // have leaked. We also don't have to leak reads from this binding // if it is only read from. (0, _environment.leakBinding)(binding); } } } catch (err) { _didIteratorError3 = true; _iteratorError3 = err; } finally { try { if (!_iteratorNormalCompletion3 && _iterator3.return) { _iterator3.return(); } } finally { if (_didIteratorError3) { throw _iteratorError3; } } } } }, { key: "visitValueMap", value: function visitValueMap(val) { var kind = val.getKind(); var entries = void 0; if (kind === "Map") { entries = val.$MapData; } else { (0, _invariant2.default)(kind === "WeakMap"); entries = val.$WeakMapData; } (0, _invariant2.default)(entries !== undefined); var len = entries.length; for (var i = 0; i < len; i++) { var entry = entries[i]; var key = entry.$Key; var value = entry.$Value; if (key === undefined || value === undefined) continue; this.visitValue(key); this.visitValue(value); } } }, { key: "visitValueSet", value: function visitValueSet(val) { var kind = val.getKind(); var entries = void 0; if (kind === "Set") { entries = val.$SetData; } else { (0, _invariant2.default)(kind === "WeakSet"); entries = val.$WeakSetData; } (0, _invariant2.default)(entries !== undefined); var len = entries.length; for (var i = 0; i < len; i++) { var entry = entries[i]; if (entry === undefined) continue; this.visitValue(entry); } } }, { key: "visitValueFunction", value: function visitValueFunction(val) { if (val.isLeakedObject()) { return; } this.visitObjectProperties(val); if (val instanceof _index.BoundFunctionValue) { this.visitValue(val.$BoundTargetFunction); this.visitValue(val.$BoundThis); var _iteratorNormalCompletion4 = true; var _didIteratorError4 = false; var _iteratorError4 = undefined; try { for (var _iterator4 = val.$BoundArguments[Symbol.iterator](), _step4; !(_iteratorNormalCompletion4 = (_step4 = _iterator4.next()).done); _iteratorNormalCompletion4 = true) { var boundArg = _step4.value; this.visitValue(boundArg); } } catch (err) { _didIteratorError4 = true; _iteratorError4 = err; } finally { try { if (!_iteratorNormalCompletion4 && _iterator4.return) { _iterator4.return(); } } finally { if (_didIteratorError4) { throw _iteratorError4; } } } return; } (0, _invariant2.default)(!(val instanceof _index.NativeFunctionValue), "all native function values should have already been created outside this pure function"); var remainingLeakedBindings = getLeakedFunctionInfo(val); var environment = val.$Environment.parent; while (environment) { var record = environment.environmentRecord; if (record instanceof _environment.ObjectEnvironmentRecord) { this.visitValue(record.object); continue; } (0, _invariant2.default)(!(record instanceof _environment.GlobalEnvironmentRecord), "we should never reach the global scope because it is never newly created in a pure function."); (0, _invariant2.default)(record instanceof _environment.DeclarativeEnvironmentRecord); this.visitDeclarativeEnvironmentRecordBinding(record, remainingLeakedBindings); if (record instanceof _environment.FunctionEnvironmentRecord) { // If this is a function environment, which is not tracked for leaks, // we can bail out because its bindings should not be mutated in a // pure function. var fn = record.$FunctionObject; if (!this.objectsTrackedForLeaks.has(fn)) { break; } } environment = environment.parent; } } }, { key: "visitValueObject", value: function visitValueObject(val) { if (val.isLeakedObject()) { return; } var kind = val.getKind(); this.visitObjectProperties(val, kind); switch (kind) { case "RegExp": case "Number": case "String": case "Boolean": case "ReactElement": case "ArrayBuffer": return; case "Date": var dateValue = val.$DateValue; (0, _invariant2.default)(dateValue !== undefined); this.visitValue(dateValue); return; case "Float32Array": case "Float64Array": case "Int8Array": case "Int16Array": case "Int32Array": case "Uint8Array": case "Uint16Array": case "Uint32Array": case "Uint8ClampedArray": case "DataView": var buf = val.$ViewedArrayBuffer; (0, _invariant2.default)(buf !== undefined); this.visitValue(buf); return; case "Map": case "WeakMap": this.visitValueMap(val); return; case "Set": case "WeakSet": this.visitValueSet(val); return; default: (0, _invariant2.default)(kind === "Object", "Object of kind " + kind + " is not supported in calls to abstract functions."); (0, _invariant2.default)(this.$ParameterMap === undefined, "Arguments object is not supported in calls to abstract functions."); return; } } }, { key: "visitValueProxy", value: function visitValueProxy(val) { this.visitValue(val.$ProxyTarget); this.visitValue(val.$ProxyHandler); } }, { key: "visitAbstractValue", value: function visitAbstractValue(val) { for (var i = 0, n = val.args.length; i < n; i++) { this.visitValue(val.args[i]); } } }, { key: "visitValue", value: function visitValue(val) { if (val instanceof _index.AbstractValue) { if (this.mustVisit(val)) this.visitAbstractValue(val); } else if (val.isIntrinsic()) { // All intrinsic values exist from the beginning of time... // ...except for a few that come into existance as templates for abstract objects (TODO #882). this.mustVisit(val); } else if (val instanceof _index.EmptyValue) { this.mustVisit(val); } else if (val instanceof _index.PrimitiveValue) { this.mustVisit(val); } else if (val instanceof _index.ProxyValue) { if (this.mustVisit(val)) this.visitValueProxy(val); } else if (val instanceof _index.FunctionValue) { (0, _invariant2.default)(val instanceof _index.FunctionValue); if (this.mustVisit(val)) this.visitValueFunction(val); } else { (0, _invariant2.default)(val instanceof _index.ObjectValue); if (val.originalConstructor !== undefined) { (0, _invariant2.default)(val instanceof _index.ObjectValue); if (this.mustVisit(val)) this.visitValueObject(val); } else { if (this.mustVisit(val)) this.visitValueObject(val); } } } }]); return ObjectValueLeakingVisitor; }(); function ensureFrozenValue(realm, value, loc) { // TODO: This should really check if it is recursively immutability. if (value instanceof _index.ObjectValue && !(0, _index2.TestIntegrityLevel)(realm, value, "frozen")) { var diag = new _errors.CompilerDiagnostic("Unfrozen object leaked before end of global code", loc || realm.currentLocation, "PP0017", "RecoverableError"); if (realm.handleError(diag) !== "Recover") throw new _errors.FatalError(); } } // Ensure that a value is immutable. If it is not, set all its properties to abstract values // and all reachable bindings to abstract values. var LeakImplementation = exports.LeakImplementation = function () { function LeakImplementation() { _classCallCheck(this, LeakImplementation); } _createClass(LeakImplementation, [{ key: "leakValue", value: function leakValue(realm, value, loc) { var objectsTrackedForLeaks = realm.createdObjectsTrackedForLeaks; if (objectsTrackedForLeaks === undefined) { // We're not tracking a pure function. That means that we would track // everything as leaked. We'll assume that any object argument // is invalid unless it's frozen. ensureFrozenValue(realm, value, loc); } else { // If we're tracking a pure function, we can assume that only newly // created objects and bindings, within it, are mutable. Any other // object can safely be assumed to be deeply immutable as far as this // pure function is concerned. However, any mutable object needs to // be tainted as possibly having changed to anything. var visitor = new ObjectValueLeakingVisitor(objectsTrackedForLeaks); visitor.visitValue(value); } } }]); return LeakImplementation; }(); //# sourceMappingURL=leak.js.map