Files
2023-08-01 13:49:46 +02:00

548 lines
24 KiB
JavaScript

"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