mirror of
https://github.com/facebookarchive/prepack.git
synced 2024-10-26 15:20:18 +03:00
Serialize independent functions
Summary: Currently works for all test cases except `noconflict-existantobject.js`. There are a couple things that are dubious in the current implementation that I will address as I iterate on it: * We don't visit things correctly in the visitor or the serializer -- we should be visiting only: * generator entries in effects * Bindings, PropertyBindings in effects not relating to CreatedObjects * There is currently no way to emit to the main body after the main generator has been serialized -- we may need to if a value is only reachable from one of the additional functions. * More thought needs to go into how this interacts with * speculative initialization * require optimization * inlining Closes https://github.com/facebook/prepack/pull/912 Differential Revision: D5859741 Pulled By: cblappert fbshipit-source-id: 9fe7bd4429ad53629582f9fb7154a28971159554
This commit is contained in:
parent
8d7390f7d5
commit
cd3821ab7e
@ -36,7 +36,7 @@
|
||||
"test-sourcemaps": "babel-node scripts/generate-sourcemaps-test.js && bash < scripts/test-sourcemaps.sh",
|
||||
"test-test262": "babel-node scripts/test262-runner.js",
|
||||
"test-test262-nightly": "NIGHTLY_BUILD=true babel-node -- scripts/test262-runner.js",
|
||||
"test-internal": "babel-node --stack_size=10000 --stack_trace_limit=200 --max_old_space_size=8192 scripts/test-internal.js",
|
||||
"test-internal": "babel-node --stack_size=10000 --stack_trace_limit=200 --max_old_space_size=16384 scripts/test-internal.js",
|
||||
"test-error-handler": "babel-node scripts/test-error-handler.js",
|
||||
"test-error-handler-with-coverage": "./node_modules/.bin/istanbul cover ./lib/test-error-handler.js --dir coverage.error && ./node_modules/.bin/remap-istanbul -i coverage.error/coverage.json -o coverage-sourcemapped.error -t html",
|
||||
"test-node-cli-mode": "bash < scripts/test-node-cli-mode.sh",
|
||||
|
@ -60,7 +60,19 @@ function runTest(name: string, code: string): boolean {
|
||||
let modelCode = fs.existsSync(modelName) ? fs.readFileSync(modelName, "utf8") : undefined;
|
||||
let sourceMap = fs.existsSync(sourceMapName) ? fs.readFileSync(sourceMapName, "utf8") : undefined;
|
||||
let sources = [];
|
||||
if (modelCode) sources.push({ filePath: modelName, fileContents: modelCode });
|
||||
let additionalFunctions;
|
||||
if (modelCode) {
|
||||
/* allows specifying additional functions by a comment of the form:
|
||||
// additional function: additional1, additional2
|
||||
*/
|
||||
let marker = "// additional functions:";
|
||||
if (modelCode.includes(marker)) {
|
||||
let i = modelCode.indexOf(marker);
|
||||
let value = modelCode.substring(i + marker.length, modelCode.indexOf("\n", i));
|
||||
additionalFunctions = value.split(",").map(funcStr => funcStr.trim());
|
||||
}
|
||||
sources.push({ filePath: modelName, fileContents: modelCode });
|
||||
}
|
||||
sources.push({ filePath: name, fileContents: sourceCode, sourceMapContents: sourceMap });
|
||||
|
||||
let options = {
|
||||
@ -72,6 +84,7 @@ function runTest(name: string, code: string): boolean {
|
||||
serialize: true,
|
||||
initializeMoreModules: !modelCode,
|
||||
sourceMaps: !!sourceMap,
|
||||
additionalFunctions: additionalFunctions,
|
||||
};
|
||||
if (name.endsWith("/bundle.js~"))
|
||||
(options: any).additionalFunctions = [
|
||||
|
@ -119,8 +119,9 @@ export default function(realm: Realm): void {
|
||||
});
|
||||
|
||||
// Maps from initialized moduleId to exports object
|
||||
// NB: Changes to this shouldn't ever be serialized
|
||||
global.$DefineOwnProperty("__initializedModules", {
|
||||
value: new ObjectValue(realm),
|
||||
value: new ObjectValue(realm, realm.intrinsics.ObjectPrototype, "__initializedModules", true),
|
||||
writable: true,
|
||||
enumerable: false,
|
||||
configurable: true,
|
||||
|
@ -330,7 +330,11 @@ export class Realm {
|
||||
return this.evaluateForEffects(() => env.evaluateAbstractCompletion(ast, strictCode), state);
|
||||
}
|
||||
|
||||
evaluateNodeForEffectsInGlobalEnv(node: BabelNode, state?: any) {
|
||||
evaluateAndRevertInGlobalEnv(func: () => void): void {
|
||||
this.wrapInGlobalEnv(() => this.evaluateForEffects(func));
|
||||
}
|
||||
|
||||
evaluateNodeForEffectsInGlobalEnv(node: BabelNode, state?: any): Effects {
|
||||
return this.wrapInGlobalEnv(() => this.evaluateNodeForEffects(node, false, this.$GlobalEnv, state));
|
||||
}
|
||||
|
||||
|
@ -172,7 +172,11 @@ export class ResidualFunctions {
|
||||
return scope;
|
||||
}
|
||||
|
||||
_referentialize(unbound: Names, instances: Array<FunctionInstance>): void {
|
||||
_referentialize(
|
||||
unbound: Names,
|
||||
instances: Array<FunctionInstance>,
|
||||
shouldReferentializeInstanceFn: FunctionInstance => boolean
|
||||
): void {
|
||||
for (let instance of instances) {
|
||||
let serializedBindings = instance.serializedBindings;
|
||||
|
||||
@ -182,6 +186,9 @@ export class ResidualFunctions {
|
||||
if (serializedBinding.modified) {
|
||||
// Initialize captured scope at function call instead of globally
|
||||
if (!serializedBinding.referentialized) {
|
||||
if (!shouldReferentializeInstanceFn(instance)) {
|
||||
throw new FatalError("TODO: implement referentialization for prepacked functions");
|
||||
}
|
||||
let scope = this._getSerializedBindingScopeInstance(serializedBinding);
|
||||
let capturedScope = "__captured" + scope.name;
|
||||
// Save the serialized value for initialization at the top of
|
||||
@ -229,7 +236,9 @@ export class ResidualFunctions {
|
||||
];
|
||||
}
|
||||
|
||||
spliceFunctions(): ResidualFunctionsResult {
|
||||
spliceFunctions(
|
||||
rewrittenAdditionalFunctions: Map<FunctionValue, Array<BabelNodeStatement>>
|
||||
): ResidualFunctionsResult {
|
||||
this.residualFunctionInitializers.scrubFunctionInitializers();
|
||||
|
||||
let functionBodies = new Map();
|
||||
@ -252,7 +261,11 @@ export class ResidualFunctions {
|
||||
for (let [funcBody, instances] of functionEntries) {
|
||||
let functionInfo = this.residualFunctionInfos.get(funcBody);
|
||||
invariant(functionInfo);
|
||||
this._referentialize(functionInfo.unbound, instances);
|
||||
this._referentialize(
|
||||
functionInfo.unbound,
|
||||
instances,
|
||||
instance => !rewrittenAdditionalFunctions.has(instance.functionValue)
|
||||
);
|
||||
}
|
||||
|
||||
for (let [funcBody, instances] of functionEntries) {
|
||||
@ -270,32 +283,69 @@ export class ResidualFunctions {
|
||||
|
||||
let define = (instance, funcId, funcNode) => {
|
||||
let { functionValue } = instance;
|
||||
let body;
|
||||
let addToBody;
|
||||
if (t.isFunctionExpression(funcNode)) {
|
||||
funcNodes.set(functionValue, ((funcNode: any): BabelNodeFunctionExpression));
|
||||
body = this.prelude;
|
||||
addToBody = elem => {
|
||||
// Let additional functions override the prelude to be their own body.
|
||||
// In this case, prepend to that body to simulate a "prelude" in the function
|
||||
if (instance.preludeOverride) instance.preludeOverride.unshift(elem);
|
||||
else this.prelude.push(elem);
|
||||
};
|
||||
} else {
|
||||
invariant(t.isCallExpression(funcNode)); // .bind call
|
||||
body = getFunctionBody(instance);
|
||||
addToBody = elem => getFunctionBody(instance).push(elem);
|
||||
}
|
||||
let declaration = t.variableDeclaration("var", [t.variableDeclarator(funcId, funcNode)]);
|
||||
body.push(declaration);
|
||||
addToBody(declaration);
|
||||
let prototypeId = this.functionPrototypes.get(functionValue);
|
||||
if (prototypeId !== undefined) {
|
||||
let id = this.locationService.getLocation(functionValue);
|
||||
invariant(id !== undefined);
|
||||
body.push(
|
||||
addToBody(
|
||||
t.variableDeclaration("var", [
|
||||
t.variableDeclarator(prototypeId, t.memberExpression(id, t.identifier("prototype"))),
|
||||
])
|
||||
);
|
||||
}
|
||||
};
|
||||
// Split instances into normal or additional functions (whose bodies have been rewritten)
|
||||
let normalInstances = [];
|
||||
let additionalFunctionInstances = [];
|
||||
for (let instance of instances) {
|
||||
if (rewrittenAdditionalFunctions.has(instance.functionValue)) additionalFunctionInstances.push(instance);
|
||||
else normalInstances.push(instance);
|
||||
}
|
||||
|
||||
if (shouldInline || instances.length === 1 || usesArguments) {
|
||||
this.statistics.functionClones += instances.length - 1;
|
||||
let rewrittenBody = rewrittenAdditionalFunctions.get(instances[0].functionValue);
|
||||
|
||||
for (let instance of instances) {
|
||||
// rewritten functions shouldn't have references fixed up because the body,
|
||||
// consists of serialized code. For simplicity we emit their instances in a naive way
|
||||
if (rewrittenBody) {
|
||||
let functionBody = t.blockStatement(rewrittenBody);
|
||||
this.statistics.functionClones += additionalFunctionInstances.length - 1;
|
||||
|
||||
for (let instance of additionalFunctionInstances) {
|
||||
let { functionValue } = instance;
|
||||
let id = this.locationService.getLocation(functionValue);
|
||||
invariant(id !== undefined);
|
||||
let funcParams = params.slice();
|
||||
let funcNode = t.functionExpression(null, funcParams, functionBody);
|
||||
|
||||
if (functionValue.$Strict) {
|
||||
strictFunctionBodies.push(funcNode);
|
||||
} else {
|
||||
unstrictFunctionBodies.push(funcNode);
|
||||
}
|
||||
|
||||
define(instance, id, funcNode);
|
||||
}
|
||||
}
|
||||
if (normalInstances.length === 0) continue;
|
||||
if (shouldInline || normalInstances.length === 1 || usesArguments) {
|
||||
this.statistics.functionClones += normalInstances.length - 1;
|
||||
|
||||
for (let instance of normalInstances) {
|
||||
let { functionValue, serializedBindings, scopeInstances } = instance;
|
||||
let id = this.locationService.getLocation(functionValue);
|
||||
invariant(id !== undefined);
|
||||
@ -331,7 +381,7 @@ export class ResidualFunctions {
|
||||
define(instance, id, funcNode);
|
||||
}
|
||||
} else {
|
||||
let suffix = instances[0].functionValue.__originalName || "";
|
||||
let suffix = normalInstances[0].functionValue.__originalName || "";
|
||||
let factoryId = t.identifier(this.factoryNameGenerator.generate(suffix));
|
||||
|
||||
// filter included variables to only include those that are different
|
||||
@ -341,13 +391,13 @@ export class ResidualFunctions {
|
||||
let isDifferent = false;
|
||||
let lastBinding;
|
||||
|
||||
if (instances[0].serializedBindings[name].modified) {
|
||||
if (normalInstances[0].serializedBindings[name].modified) {
|
||||
// Must modify for traversal
|
||||
sameSerializedBindings[name] = instances[0].serializedBindings[name];
|
||||
sameSerializedBindings[name] = normalInstances[0].serializedBindings[name];
|
||||
continue;
|
||||
}
|
||||
|
||||
for (let { serializedBindings } of instances) {
|
||||
for (let { serializedBindings } of normalInstances) {
|
||||
let serializedBinding = serializedBindings[name];
|
||||
|
||||
invariant(!serializedBinding.modified);
|
||||
@ -374,7 +424,7 @@ export class ResidualFunctions {
|
||||
}
|
||||
|
||||
let scopeInitialization = [];
|
||||
for (let scope of instances[0].scopeInstances) {
|
||||
for (let scope of normalInstances[0].scopeInstances) {
|
||||
factoryParams.push(t.identifier(scope.name));
|
||||
scopeInitialization = scopeInitialization.concat(this._getReferentializedScopeInitialization(scope));
|
||||
}
|
||||
@ -388,7 +438,7 @@ export class ResidualFunctions {
|
||||
((t.cloneDeep(funcBody): any): BabelNodeBlockStatement)
|
||||
);
|
||||
|
||||
if (instances[0].functionValue.$Strict) {
|
||||
if (normalInstances[0].functionValue.$Strict) {
|
||||
strictFunctionBodies.push(factoryNode);
|
||||
} else {
|
||||
unstrictFunctionBodies.push(factoryNode);
|
||||
@ -405,10 +455,10 @@ export class ResidualFunctions {
|
||||
modified,
|
||||
requireReturns: this.requireReturns,
|
||||
requireStatistics,
|
||||
isRequire: this.modules.getIsRequire(factoryParams, instances.map(instance => instance.functionValue)),
|
||||
isRequire: this.modules.getIsRequire(factoryParams, normalInstances.map(instance => instance.functionValue)),
|
||||
});
|
||||
|
||||
for (let instance of instances) {
|
||||
for (let instance of normalInstances) {
|
||||
let { functionValue, serializedBindings, insertionPoint } = instance;
|
||||
let functionId = this.locationService.getLocation(functionValue);
|
||||
invariant(functionId !== undefined);
|
||||
|
@ -54,6 +54,7 @@ import { voidExpression, emptyExpression, constructorExpression, protoExpression
|
||||
import { Emitter } from "./Emitter.js";
|
||||
import { ResidualHeapValueIdentifiers } from "./ResidualHeapValueIdentifiers.js";
|
||||
import { commonAncestorOf, getSuggestedArrayLiteralLength } from "./utils.js";
|
||||
import type { Effects } from "../realm.js";
|
||||
|
||||
export class ResidualHeapSerializer {
|
||||
constructor(
|
||||
@ -67,6 +68,7 @@ export class ResidualHeapSerializer {
|
||||
residualFunctionInfos: Map<BabelNodeBlockStatement, FunctionInfo>,
|
||||
delayInitializations: boolean,
|
||||
referencedDeclaredValues: Set<AbstractValue>,
|
||||
additionalFunctionValuesAndEffects: Map<FunctionValue, Effects> | void,
|
||||
statistics: SerializerStatistics
|
||||
) {
|
||||
this.realm = realm;
|
||||
@ -102,7 +104,7 @@ export class ResidualHeapSerializer {
|
||||
getLocation: value => this.residualHeapValueIdentifiers.getIdentifierAndIncrementReferenceCountOptional(value),
|
||||
createLocation: () => {
|
||||
let location = t.identifier(this.valueNameGenerator.generate("initialized"));
|
||||
this.mainBody.push(t.variableDeclaration("var", [t.variableDeclarator(location)]));
|
||||
this.currentFunctionBody.push(t.variableDeclaration("var", [t.variableDeclarator(location)]));
|
||||
return location;
|
||||
},
|
||||
},
|
||||
@ -114,6 +116,7 @@ export class ResidualHeapSerializer {
|
||||
);
|
||||
this.emitter = new Emitter(this.residualFunctions, delayInitializations);
|
||||
this.mainBody = this.emitter.getBody();
|
||||
this.currentFunctionBody = this.mainBody;
|
||||
this.residualHeapInspector = residualHeapInspector;
|
||||
this.residualValues = residualValues;
|
||||
this.residualFunctionBindings = residualFunctionBindings;
|
||||
@ -121,6 +124,8 @@ export class ResidualHeapSerializer {
|
||||
this.delayInitializations = delayInitializations;
|
||||
this.referencedDeclaredValues = referencedDeclaredValues;
|
||||
this.activeGeneratorBodies = new Map();
|
||||
this.additionalFunctionValuesAndEffects = additionalFunctionValuesAndEffects;
|
||||
this.additionalFunctionValueNestedFunctions = new Set();
|
||||
}
|
||||
|
||||
emitter: Emitter;
|
||||
@ -130,6 +135,9 @@ export class ResidualHeapSerializer {
|
||||
prelude: Array<BabelNodeStatement>;
|
||||
body: Array<BabelNodeStatement>;
|
||||
mainBody: Array<BabelNodeStatement>;
|
||||
// if we're in an additional function we need to access both mainBody and the
|
||||
// additional function's body which will be currentFunctionBody.
|
||||
currentFunctionBody: Array<BabelNodeStatement>;
|
||||
realm: Realm;
|
||||
preludeGenerator: PreludeGenerator;
|
||||
generator: Generator;
|
||||
@ -155,6 +163,10 @@ export class ResidualHeapSerializer {
|
||||
delayInitializations: boolean;
|
||||
referencedDeclaredValues: Set<AbstractValue>;
|
||||
activeGeneratorBodies: Map<Generator, Array<BabelNodeStatement>>;
|
||||
additionalFunctionValuesAndEffects: Map<FunctionValue, Effects> | void;
|
||||
// function values nested in additional functions can't delay initializations
|
||||
// TODO: revisit this and fix additional functions to be capable of delaying initializations
|
||||
additionalFunctionValueNestedFunctions: Set<FunctionValue>;
|
||||
|
||||
// Configures all mutable aspects of an object, in particular:
|
||||
// symbols, properties, prototype.
|
||||
@ -325,32 +337,36 @@ export class ResidualHeapSerializer {
|
||||
_emitProperty(
|
||||
val: ObjectValue,
|
||||
key: string | SymbolValue,
|
||||
desc: Descriptor,
|
||||
cleanupDummyProperty: boolean = false
|
||||
desc: Descriptor | void,
|
||||
deleteIfMightHaveBeenDeleted: boolean = false
|
||||
): void {
|
||||
if (this._canEmbedProperty(val, key, desc)) {
|
||||
// Location for the property to be assigned to
|
||||
let locationFunction = () => {
|
||||
let serializedKey =
|
||||
key instanceof SymbolValue ? this.serializeValue(key) : this.generator.getAsPropertyNameExpression(key);
|
||||
let computed = key instanceof SymbolValue || !t.isIdentifier(serializedKey);
|
||||
return t.memberExpression(
|
||||
this.residualHeapValueIdentifiers.getIdentifierAndIncrementReferenceCount(val),
|
||||
serializedKey,
|
||||
computed
|
||||
);
|
||||
};
|
||||
if (desc === undefined) {
|
||||
this._deleteProperty(locationFunction());
|
||||
} else if (this._canEmbedProperty(val, key, desc)) {
|
||||
let descValue = desc.value;
|
||||
invariant(descValue instanceof Value);
|
||||
invariant(!this.emitter.getReasonToWaitForDependencies([descValue, val]), "precondition of _emitProperty");
|
||||
let mightHaveBeenDeleted = descValue.mightHaveBeenDeleted();
|
||||
// The only case we do not need to remove the dummy property is array index property.
|
||||
this._assignProperty(
|
||||
() => {
|
||||
let serializedKey =
|
||||
key instanceof SymbolValue ? this.serializeValue(key) : this.generator.getAsPropertyNameExpression(key);
|
||||
let computed = key instanceof SymbolValue || !t.isIdentifier(serializedKey);
|
||||
return t.memberExpression(
|
||||
this.residualHeapValueIdentifiers.getIdentifierAndIncrementReferenceCount(val),
|
||||
serializedKey,
|
||||
computed
|
||||
);
|
||||
},
|
||||
locationFunction,
|
||||
() => {
|
||||
invariant(descValue instanceof Value);
|
||||
return this.serializeValue(descValue);
|
||||
},
|
||||
mightHaveBeenDeleted,
|
||||
cleanupDummyProperty
|
||||
deleteIfMightHaveBeenDeleted
|
||||
);
|
||||
} else {
|
||||
this.emitter.emit(this.emitDefinePropertyBody(val, key, desc));
|
||||
@ -463,7 +479,7 @@ export class ResidualHeapSerializer {
|
||||
if (scope === this.realm.generator) {
|
||||
// This value is used from the main generator scope. This means that we need to emit the value and its
|
||||
// initialization code into the main body, and cannot delay initialization.
|
||||
return { body: this.mainBody };
|
||||
return { body: this.currentFunctionBody };
|
||||
}
|
||||
generators.push(scope);
|
||||
}
|
||||
@ -472,16 +488,28 @@ export class ResidualHeapSerializer {
|
||||
if (generators.length === 0) {
|
||||
// This value is only referenced from residual functions.
|
||||
invariant(functionValues.length > 0);
|
||||
if (this.delayInitializations) {
|
||||
let additionalFunctionValuesAndEffects = this.additionalFunctionValuesAndEffects;
|
||||
let numAdditionalFunctionReferences = 0;
|
||||
// Make sure we don't delay things referenced by additional functions or nested functions
|
||||
if (additionalFunctionValuesAndEffects) {
|
||||
// flow forces me to do this
|
||||
let additionalFuncValuesAndEffects = additionalFunctionValuesAndEffects;
|
||||
numAdditionalFunctionReferences = functionValues.filter(
|
||||
funcValue =>
|
||||
additionalFuncValuesAndEffects.has(funcValue) || this.additionalFunctionValueNestedFunctions.has(funcValue)
|
||||
).length;
|
||||
}
|
||||
|
||||
if (numAdditionalFunctionReferences > 0 || !this.delayInitializations) {
|
||||
// We can just emit it into the current function body.
|
||||
return { body: this.currentFunctionBody };
|
||||
} else {
|
||||
// We can delay the initialization, and move it into a conditional code block in the residual functions!
|
||||
let body = this.residualFunctions.residualFunctionInitializers.registerValueOnlyReferencedByResidualFunctions(
|
||||
functionValues,
|
||||
val
|
||||
);
|
||||
return { body, usedOnlyByResidualFunctions: true };
|
||||
} else {
|
||||
// We can just emit it into the main body which will get executed unconditionally.
|
||||
return { body: this.mainBody };
|
||||
}
|
||||
}
|
||||
|
||||
@ -489,12 +517,14 @@ export class ResidualHeapSerializer {
|
||||
// We can emit the initialization of this value into the body associated with their common ancestor.
|
||||
let commonAncestor = Array.from(scopes).reduce((x, y) => commonAncestorOf(x, y), generators[0]);
|
||||
invariant(commonAncestor instanceof Generator); // every scope is either the root, or a descendant
|
||||
let body = commonAncestor === this.generator ? this.mainBody : this.activeGeneratorBodies.get(commonAncestor);
|
||||
let body =
|
||||
commonAncestor === this.generator ? this.currentFunctionBody : this.activeGeneratorBodies.get(commonAncestor);
|
||||
invariant(body !== undefined);
|
||||
return { body: body };
|
||||
}
|
||||
|
||||
serializeValue(val: Value, referenceOnly?: boolean, bindingType?: BabelVariableKind): BabelNodeExpression {
|
||||
invariant(!val.refuseSerialization);
|
||||
let scopes = this.residualValues.get(val);
|
||||
invariant(scopes !== undefined);
|
||||
|
||||
@ -556,7 +586,7 @@ export class ResidualHeapSerializer {
|
||||
return this.preludeGenerator.convertStringToMember(intrinsicName);
|
||||
} else {
|
||||
// The intrinsic conceptually exists ahead of time.
|
||||
invariant(this.emitter.getBody() === this.mainBody);
|
||||
invariant(this.emitter.getBody() === this.currentFunctionBody);
|
||||
return this.preludeGenerator.memoizeReference(intrinsicName);
|
||||
}
|
||||
}
|
||||
@ -568,11 +598,18 @@ export class ResidualHeapSerializer {
|
||||
return [desc.get, desc.set];
|
||||
}
|
||||
|
||||
_deleteProperty(location: BabelNodeLVal) {
|
||||
invariant(location.type === "MemberExpression");
|
||||
this.emitter.emit(
|
||||
t.expressionStatement(t.unaryExpression("delete", ((location: any): BabelNodeMemberExpression), true))
|
||||
);
|
||||
}
|
||||
|
||||
_assignProperty(
|
||||
locationFn: () => BabelNodeLVal,
|
||||
valueFn: () => BabelNodeExpression,
|
||||
mightHaveBeenDeleted: boolean,
|
||||
cleanupDummyProperty: boolean = false
|
||||
deleteIfMightHaveBeenDeleted: boolean = false
|
||||
) {
|
||||
let location = locationFn();
|
||||
let value = valueFn();
|
||||
@ -580,7 +617,7 @@ export class ResidualHeapSerializer {
|
||||
if (mightHaveBeenDeleted) {
|
||||
let condition = t.binaryExpression("!==", value, this.serializeValue(this.realm.intrinsics.empty));
|
||||
let deletion = null;
|
||||
if (cleanupDummyProperty) {
|
||||
if (deleteIfMightHaveBeenDeleted) {
|
||||
invariant(location.type === "MemberExpression");
|
||||
deletion = t.expressionStatement(
|
||||
t.unaryExpression("delete", ((location: any): BabelNodeMemberExpression), true)
|
||||
@ -820,6 +857,7 @@ export class ResidualHeapSerializer {
|
||||
}
|
||||
|
||||
invariant(!(val instanceof NativeFunctionValue), "all native function values should be intrinsics");
|
||||
invariant(val instanceof ECMAScriptSourceFunctionValue);
|
||||
|
||||
let residualBindings = this.residualFunctionBindings.get(val);
|
||||
invariant(residualBindings);
|
||||
@ -832,6 +870,7 @@ export class ResidualHeapSerializer {
|
||||
scopeInstances: new Set(),
|
||||
};
|
||||
|
||||
if (this.currentFunctionBody !== this.mainBody) instance.preludeOverride = this.currentFunctionBody;
|
||||
let delayed = 1;
|
||||
let undelay = () => {
|
||||
if (--delayed === 0) {
|
||||
@ -1099,19 +1138,23 @@ export class ResidualHeapSerializer {
|
||||
}
|
||||
}
|
||||
|
||||
_withGeneratorScope(generator: Generator, callback: (Array<BabelNodeStatement>) => void): Array<BabelNodeStatement> {
|
||||
let newBody = [];
|
||||
let oldBody = this.emitter.beginEmitting(generator, newBody);
|
||||
this.activeGeneratorBodies.set(generator, newBody);
|
||||
callback(newBody);
|
||||
this.activeGeneratorBodies.delete(generator);
|
||||
return this.emitter.endEmitting(generator, oldBody);
|
||||
}
|
||||
|
||||
_getContext(): SerializationContext {
|
||||
// TODO #482: Values serialized by nested generators would currently only get defined
|
||||
// along the code of the nested generator; their definitions need to get hoisted
|
||||
// or repeated so that they are accessible and defined from all using scopes
|
||||
let context = {
|
||||
serializeValue: this.serializeValue.bind(this),
|
||||
serializeGenerator: (generator: Generator) => {
|
||||
let newBody = [];
|
||||
let oldBody = this.emitter.beginEmitting(generator, newBody);
|
||||
this.activeGeneratorBodies.set(generator, newBody);
|
||||
generator.serialize(context);
|
||||
this.activeGeneratorBodies.delete(generator);
|
||||
return this.emitter.endEmitting(generator, oldBody);
|
||||
serializeGenerator: (generator: Generator): Array<BabelNodeStatement> => {
|
||||
return this._withGeneratorScope(generator, () => generator.serialize(context));
|
||||
},
|
||||
emit: (statement: BabelNodeStatement) => {
|
||||
this.emitter.emit(statement);
|
||||
@ -1127,6 +1170,17 @@ export class ResidualHeapSerializer {
|
||||
return context;
|
||||
}
|
||||
|
||||
_serializeAdditionalFunction(generator: Generator, postGeneratorCallback: () => void) {
|
||||
let context = this._getContext();
|
||||
return this._withGeneratorScope(generator, newBody => {
|
||||
let oldCurBody = this.currentFunctionBody;
|
||||
this.currentFunctionBody = newBody;
|
||||
generator.serialize(context);
|
||||
if (postGeneratorCallback) postGeneratorCallback();
|
||||
this.currentFunctionBody = oldCurBody;
|
||||
});
|
||||
}
|
||||
|
||||
_shouldBeWrapped(body: Array<any>) {
|
||||
for (let i = 0; i < body.length; i++) {
|
||||
let item = body[i];
|
||||
@ -1154,6 +1208,73 @@ export class ResidualHeapSerializer {
|
||||
return false;
|
||||
}
|
||||
|
||||
processAdditionalFunctionValues(): Map<FunctionValue, Array<BabelNodeStatement>> {
|
||||
let rewrittenAdditionalFunctions: Map<FunctionValue, Array<BabelNodeStatement>> = new Map();
|
||||
let shouldEmitLog = !this.residualHeapValueIdentifiers.collectValToRefCountOnly;
|
||||
let processAdditionalFunctionValuesFn = () => {
|
||||
let additionalFVEffects = this.additionalFunctionValuesAndEffects;
|
||||
if (additionalFVEffects) {
|
||||
for (let [additionalFunctionValue, effects] of additionalFVEffects.entries()) {
|
||||
let [
|
||||
result,
|
||||
generator,
|
||||
modifiedBindings,
|
||||
modifiedProperties: Map<PropertyBinding, void | Descriptor>,
|
||||
createdObjects,
|
||||
] = effects;
|
||||
let nestedFunctions = new Set([...createdObjects].filter(object => object instanceof FunctionValue));
|
||||
// result -- ignore TODO: return the result from the function somehow
|
||||
// Generator -- visit all entries
|
||||
// Bindings -- only need to serialize bindings if they're captured by some nested function ??
|
||||
// -- need to apply them and maybe need to revisit functions in ancestors to make sure
|
||||
// -- we don't overwrite anything they capture
|
||||
// -- TODO: deal with these properly
|
||||
// PropertyBindings -- visit any property bindings that aren't to createdobjects
|
||||
// CreatedObjects -- should take care of itself
|
||||
this.realm.applyEffects([
|
||||
result,
|
||||
new Generator(this.realm),
|
||||
modifiedBindings,
|
||||
modifiedProperties,
|
||||
createdObjects,
|
||||
]);
|
||||
// Allows us to emit function declarations etc. inside of this additional
|
||||
// function instead of adding them at global scope
|
||||
// TODO: make sure this generator isn't getting mutated oddly
|
||||
this.additionalFunctionValueNestedFunctions = ((nestedFunctions: any): Set<FunctionValue>);
|
||||
let serializePropertiesAndBindings = () => {
|
||||
for (let propertyBinding of modifiedProperties.keys()) {
|
||||
let binding: PropertyBinding = ((propertyBinding: any): PropertyBinding);
|
||||
let object = binding.object;
|
||||
if (object instanceof ObjectValue && createdObjects.has(object)) continue;
|
||||
if (object.refuseSerialization) continue;
|
||||
if (object.isIntrinsic()) continue;
|
||||
invariant(object instanceof ObjectValue);
|
||||
this._emitProperty(object, binding.key, binding.descriptor, true);
|
||||
}
|
||||
};
|
||||
let body = this._serializeAdditionalFunction(generator, serializePropertiesAndBindings);
|
||||
invariant(additionalFunctionValue instanceof ECMAScriptSourceFunctionValue);
|
||||
rewrittenAdditionalFunctions.set(additionalFunctionValue, body);
|
||||
// re-resolve initialized modules to include things from additional functions
|
||||
this.modules.resolveInitializedModules();
|
||||
if (shouldEmitLog && this.modules.moduleIds.size > 0)
|
||||
console.log(
|
||||
`=== ${this.modules.initializedModules.size} of ${this.modules.moduleIds
|
||||
.size} modules initialized after additional function ${additionalFunctionValue.intrinsicName
|
||||
? additionalFunctionValue.intrinsicName
|
||||
: ""}`
|
||||
);
|
||||
// These don't restore themselves properly otherwise.
|
||||
this.realm.restoreBindings(modifiedBindings);
|
||||
this.realm.restoreProperties(modifiedProperties);
|
||||
}
|
||||
}
|
||||
};
|
||||
this.realm.evaluateAndRevertInGlobalEnv(processAdditionalFunctionValuesFn);
|
||||
return rewrittenAdditionalFunctions;
|
||||
}
|
||||
|
||||
serialize(): BabelNodeFile {
|
||||
this.generator.serialize(this._getContext());
|
||||
invariant(this.emitter._declaredAbstractValues.size <= this.preludeGenerator.derivedIds.size);
|
||||
@ -1163,12 +1284,20 @@ export class ResidualHeapSerializer {
|
||||
// TODO #20: add timers
|
||||
|
||||
// TODO #21: add event listeners
|
||||
|
||||
for (let [moduleId, moduleValue] of this.modules.initializedModules)
|
||||
this.requireReturns.set(moduleId, this.serializeValue(moduleValue));
|
||||
|
||||
// Make sure additional functions get serialized.
|
||||
let rewrittenAdditionalFunctions = this.processAdditionalFunctionValues();
|
||||
|
||||
this.modules.resolveInitializedModules();
|
||||
|
||||
this.emitter.finalize();
|
||||
|
||||
let { unstrictFunctionBodies, strictFunctionBodies, requireStatistics } = this.residualFunctions.spliceFunctions();
|
||||
let { unstrictFunctionBodies, strictFunctionBodies, requireStatistics } = this.residualFunctions.spliceFunctions(
|
||||
rewrittenAdditionalFunctions
|
||||
);
|
||||
if (requireStatistics.replaced > 0 && !this.residualHeapValueIdentifiers.collectValToRefCountOnly) {
|
||||
console.log(
|
||||
`=== ${this.modules.initializedModules.size} of ${this.modules.moduleIds
|
||||
|
@ -12,7 +12,8 @@
|
||||
import { GlobalEnvironmentRecord, DeclarativeEnvironmentRecord } from "../environment.js";
|
||||
import { FatalError } from "../errors.js";
|
||||
import { Realm } from "../realm.js";
|
||||
import type { Descriptor } from "../types.js";
|
||||
import type { Effects } from "../realm.js";
|
||||
import type { Descriptor, PropertyBinding } from "../types.js";
|
||||
import { IsUnresolvableReference, ToLength, ResolveBinding, HashSet, IsArray, Get } from "../methods/index.js";
|
||||
import {
|
||||
BoundFunctionValue,
|
||||
@ -24,6 +25,7 @@ import {
|
||||
FunctionValue,
|
||||
Value,
|
||||
ObjectValue,
|
||||
AbstractObjectValue,
|
||||
NativeFunctionValue,
|
||||
} from "../values/index.js";
|
||||
import { describeLocation } from "../intrinsics/ecma262/Error.js";
|
||||
@ -49,7 +51,12 @@ export type Scope = FunctionValue | Generator;
|
||||
TODO #680: Figure out minimal set of values that need to be kept alive for WeakSet and WeakMap instances.
|
||||
*/
|
||||
export class ResidualHeapVisitor {
|
||||
constructor(realm: Realm, logger: Logger, modules: Modules) {
|
||||
constructor(
|
||||
realm: Realm,
|
||||
logger: Logger,
|
||||
modules: Modules,
|
||||
additionalFunctionValuesAndEffects: Map<FunctionValue, Effects>
|
||||
) {
|
||||
invariant(realm.useAbstractInterpretation);
|
||||
this.realm = realm;
|
||||
this.logger = logger;
|
||||
@ -62,10 +69,11 @@ export class ResidualHeapVisitor {
|
||||
this.values = new Map();
|
||||
let generator = this.realm.generator;
|
||||
invariant(generator);
|
||||
this.scope = this.realmGenerator = generator;
|
||||
this.scope = this.commonScope = generator;
|
||||
this.inspector = new ResidualHeapInspector(realm, logger);
|
||||
this.referencedDeclaredValues = new Set();
|
||||
this.delayedVisitGeneratorEntries = [];
|
||||
this.additionalFunctionValuesAndEffects = additionalFunctionValuesAndEffects;
|
||||
this.equivalenceSet = new HashSet();
|
||||
}
|
||||
|
||||
@ -78,11 +86,13 @@ export class ResidualHeapVisitor {
|
||||
functionInfos: Map<BabelNodeBlockStatement, FunctionInfo>;
|
||||
functionBindings: Map<FunctionValue, VisitedBindings>;
|
||||
scope: Scope;
|
||||
realmGenerator: Generator;
|
||||
// Either the realm's generator or the FunctionValue of an additional function to serialize
|
||||
commonScope: Scope;
|
||||
values: Map<Value, Set<Scope>>;
|
||||
inspector: ResidualHeapInspector;
|
||||
referencedDeclaredValues: Set<AbstractValue>;
|
||||
delayedVisitGeneratorEntries: Array<{| generator: Generator, entry: GeneratorEntry |}>;
|
||||
delayedVisitGeneratorEntries: Array<{| commonScope: Scope, generator: Generator, entry: GeneratorEntry |}>;
|
||||
additionalFunctionValuesAndEffects: Map<FunctionValue, Effects>;
|
||||
equivalenceSet: HashSet<AbstractValue>;
|
||||
|
||||
_withScope(scope: Scope, f: () => void) {
|
||||
@ -92,6 +102,15 @@ export class ResidualHeapVisitor {
|
||||
this.scope = oldScope;
|
||||
}
|
||||
|
||||
visitObjectProperty(binding: PropertyBinding) {
|
||||
let desc = binding.descriptor;
|
||||
if (desc === undefined) return; //deleted
|
||||
let obj = binding.object;
|
||||
if (obj instanceof AbstractObjectValue || !this.inspector.canIgnoreProperty(obj, binding.key)) {
|
||||
this.visitDescriptor(desc);
|
||||
}
|
||||
}
|
||||
|
||||
visitObjectProperties(obj: ObjectValue): void {
|
||||
// visit properties
|
||||
for (let [symbol, propertyBinding] of obj.symbols) {
|
||||
@ -103,13 +122,9 @@ export class ResidualHeapVisitor {
|
||||
}
|
||||
|
||||
// visit properties
|
||||
for (let [key, propertyBinding] of obj.properties) {
|
||||
for (let propertyBinding of obj.properties.values()) {
|
||||
invariant(propertyBinding);
|
||||
let desc = propertyBinding.descriptor;
|
||||
if (desc === undefined) continue; //deleted
|
||||
if (!this.inspector.canIgnoreProperty(obj, key)) {
|
||||
this.visitDescriptor(desc);
|
||||
}
|
||||
this.visitObjectProperty(propertyBinding);
|
||||
}
|
||||
|
||||
// inject properties with computed names
|
||||
@ -321,7 +336,7 @@ export class ResidualHeapVisitor {
|
||||
let reference = this.logger.tryQuery(
|
||||
() => ResolveBinding(this.realm, innerName, doesNotMatter, val.$Environment),
|
||||
undefined,
|
||||
false /* The only reason `ResolveBinding` might fail is because the global object is partial. But in that case, we know that we are dealing with the global scope. */
|
||||
false /* The only reason `ResolveBinding` might fail is because the global object is partial. But in that case, we know that we are dealing with the common scope. */
|
||||
);
|
||||
if (
|
||||
reference === undefined ||
|
||||
@ -438,12 +453,13 @@ export class ResidualHeapVisitor {
|
||||
}
|
||||
|
||||
visitValue(val: Value): void {
|
||||
invariant(!val.refuseSerialization);
|
||||
if (val instanceof AbstractValue) {
|
||||
if (this._mark(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._withScope(this.realmGenerator, () => {
|
||||
this._withScope(this.commonScope, () => {
|
||||
this._mark(val);
|
||||
});
|
||||
} else if (val instanceof EmptyValue) {
|
||||
@ -456,8 +472,8 @@ export class ResidualHeapVisitor {
|
||||
} else if (val instanceof ProxyValue) {
|
||||
if (this._mark(val)) this.visitValueProxy(val);
|
||||
} else if (val instanceof FunctionValue) {
|
||||
// Function declarations should get hoisted in the global code so that instances only get allocated once
|
||||
this._withScope(this.realmGenerator, () => {
|
||||
// Function declarations should get hoisted in common scope so that instances only get allocated once
|
||||
this._withScope(this.commonScope, () => {
|
||||
invariant(val instanceof FunctionValue);
|
||||
if (this._mark(val)) this.visitValueFunction(val);
|
||||
});
|
||||
@ -467,9 +483,9 @@ export class ResidualHeapVisitor {
|
||||
invariant(val instanceof ObjectValue);
|
||||
|
||||
// Prototypes are reachable via function declarations, and those get hoisted, so we need to move
|
||||
// prototype initialization to the global code as well.
|
||||
// prototype initialization to the common scope code as well.
|
||||
if (val.originalConstructor !== undefined) {
|
||||
this._withScope(this.realmGenerator, () => {
|
||||
this._withScope(this.commonScope, () => {
|
||||
invariant(val instanceof ObjectValue);
|
||||
if (this._mark(val)) this.visitValueObject(val);
|
||||
});
|
||||
@ -490,7 +506,7 @@ export class ResidualHeapVisitor {
|
||||
return binding;
|
||||
}
|
||||
|
||||
createGeneratorVisitCallbacks(generator: Generator): VisitEntryCallbacks {
|
||||
createGeneratorVisitCallbacks(generator: Generator, commonScope: Scope): VisitEntryCallbacks {
|
||||
return {
|
||||
visitValues: (values: Array<Value>) => {
|
||||
for (let i = 0, n = values.length; i < n; i++) values[i] = this.visitEquivalentValue(values[i]);
|
||||
@ -503,30 +519,84 @@ export class ResidualHeapVisitor {
|
||||
this.referencedDeclaredValues.add(value);
|
||||
},
|
||||
recordDelayedEntry: (entry: GeneratorEntry) => {
|
||||
this.delayedVisitGeneratorEntries.push({ generator, entry });
|
||||
this.delayedVisitGeneratorEntries.push({ commonScope, generator, entry });
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
visitGenerator(generator: Generator): void {
|
||||
this._withScope(generator, () => {
|
||||
generator.visit(this.createGeneratorVisitCallbacks(generator));
|
||||
generator.visit(this.createGeneratorVisitCallbacks(generator, this.commonScope));
|
||||
});
|
||||
}
|
||||
|
||||
visitRoots(): void {
|
||||
this.visitGenerator(this.realmGenerator);
|
||||
for (let [, moduleValue] of this.modules.initializedModules) this.visitValue(moduleValue);
|
||||
visitAdditionalFunctionEffects() {
|
||||
for (let [functionValue, effects] of this.additionalFunctionValuesAndEffects.entries()) {
|
||||
let [
|
||||
result,
|
||||
generator,
|
||||
modifiedBindings,
|
||||
modifiedProperties: Map<PropertyBinding, void | Descriptor>,
|
||||
createdObjects,
|
||||
] = effects;
|
||||
// Need to do this fixup because otherwise we will skip over this function's
|
||||
// generator in the _getTarget scope lookup
|
||||
generator.parent = functionValue.parent;
|
||||
functionValue.parent = generator;
|
||||
// result -- ignore TODO: return the result from the function somehow
|
||||
// Generator -- visit all entries
|
||||
// Bindings -- (modifications to named variables) only need to serialize bindings if they're
|
||||
// captured by a residual function
|
||||
// -- need to apply them and maybe need to revisit functions in ancestors to make sure
|
||||
// we don't overwrite anything they capture
|
||||
// -- TODO: deal with these properly
|
||||
// PropertyBindings -- (property modifications) visit any property bindings to pre-existing objects
|
||||
// CreatedObjects -- should take care of itself
|
||||
this.realm.applyEffects([
|
||||
result,
|
||||
new Generator(this.realm),
|
||||
modifiedBindings,
|
||||
modifiedProperties,
|
||||
createdObjects,
|
||||
]);
|
||||
// Allows us to emit function declarations etc. inside of this additional
|
||||
// function instead of adding them at global scope
|
||||
this.commonScope = functionValue;
|
||||
let visitPropertiesAndBindings = () => {
|
||||
for (let propertyBinding of modifiedProperties.keys()) {
|
||||
let binding: PropertyBinding = ((propertyBinding: any): PropertyBinding);
|
||||
let object = binding.object;
|
||||
if (object instanceof ObjectValue && createdObjects.has(object)) continue; // Created Object's binding
|
||||
if (object.refuseSerialization) continue; // modification to internal state
|
||||
if (object.intrinsicName === "global") continue; // Avoid double-counting
|
||||
this.visitObjectProperty(binding);
|
||||
}
|
||||
};
|
||||
this.visitGenerator(generator);
|
||||
this._withScope(generator, visitPropertiesAndBindings);
|
||||
this.realm.restoreBindings(modifiedBindings);
|
||||
this.realm.restoreProperties(modifiedProperties);
|
||||
}
|
||||
// Do a fixpoint over all pure generator entries to make sure that we visit
|
||||
// arguments of only BodyEntries that are required by some other residual value
|
||||
let oldDelayedEntries = [];
|
||||
while (oldDelayedEntries.length !== this.delayedVisitGeneratorEntries.length) {
|
||||
oldDelayedEntries = this.delayedVisitGeneratorEntries;
|
||||
this.delayedVisitGeneratorEntries = [];
|
||||
for (let { generator, entry } of oldDelayedEntries)
|
||||
this._withScope(generator, () => {
|
||||
generator.visitEntry(entry, this.createGeneratorVisitCallbacks(generator));
|
||||
for (let { commonScope, generator: entryGenerator, entry } of oldDelayedEntries) {
|
||||
this.commonScope = commonScope;
|
||||
this._withScope(entryGenerator, () => {
|
||||
entryGenerator.visitEntry(entry, this.createGeneratorVisitCallbacks(entryGenerator, commonScope));
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
visitRoots(): void {
|
||||
let generator = this.realm.generator;
|
||||
invariant(generator);
|
||||
this.visitGenerator(generator);
|
||||
for (let moduleValue of this.modules.initializedModules.values()) this.visitValue(moduleValue);
|
||||
this.realm.evaluateAndRevertInGlobalEnv(this.visitAdditionalFunctionEffects.bind(this));
|
||||
}
|
||||
}
|
||||
|
@ -26,11 +26,15 @@ export class Functions {
|
||||
this.realm = realm;
|
||||
this.functions = functions;
|
||||
this.moduleTracer = moduleTracer;
|
||||
this.writeEffects = new Map();
|
||||
this.nameToFunctionValue = new Map();
|
||||
}
|
||||
|
||||
realm: Realm;
|
||||
functions: ?Array<string>;
|
||||
moduleTracer: ModuleTracer;
|
||||
writeEffects: Map<string, Effects>;
|
||||
nameToFunctionValue: Map<string, FunctionValue>;
|
||||
|
||||
checkThatFunctionsAreIndependent() {
|
||||
let functions = this.functions;
|
||||
@ -59,24 +63,24 @@ export class Functions {
|
||||
this.realm.handleError(error);
|
||||
throw new FatalError();
|
||||
}
|
||||
this.nameToFunctionValue.set(fname, fun);
|
||||
let call = t.callExpression(fnameAst, []);
|
||||
calls.push([fname, call]);
|
||||
}
|
||||
|
||||
// Get write effects of the functions
|
||||
let writeEffects: Map<string, Effects> = new Map();
|
||||
for (let [fname, call] of calls) {
|
||||
// This may throw a FatalError if there is an unrecoverable error in the called function
|
||||
// When that happens we cannot prepack the bundle.
|
||||
// There may also be warnings reported for errors that happen inside imported modules that can be postponed.
|
||||
let e = this.realm.evaluateNodeForEffectsInGlobalEnv(call, this.moduleTracer);
|
||||
writeEffects.set(fname, e);
|
||||
this.writeEffects.set(fname, e);
|
||||
}
|
||||
|
||||
// check that functions are independent
|
||||
let conflicts: Map<BabelNodeSourceLocation, CompilerDiagnostic> = new Map();
|
||||
for (let [fname1, call1] of calls) {
|
||||
let e1 = writeEffects.get(fname1);
|
||||
let e1 = this.writeEffects.get(fname1);
|
||||
invariant(e1 !== undefined);
|
||||
if (e1[0] instanceof Completion) {
|
||||
let error = new CompilerDiagnostic(
|
||||
@ -100,6 +104,16 @@ export class Functions {
|
||||
}
|
||||
}
|
||||
|
||||
getAdditionalFunctionValuesToEffects(): Map<FunctionValue, Effects> {
|
||||
let functionValueToEffects = new Map();
|
||||
for (let [functionString, effects] of this.writeEffects.entries()) {
|
||||
let funcValue = this.nameToFunctionValue.get(functionString);
|
||||
invariant(funcValue);
|
||||
functionValueToEffects.set(funcValue, effects);
|
||||
}
|
||||
return functionValueToEffects;
|
||||
}
|
||||
|
||||
reportWriteConflicts(
|
||||
fname: string,
|
||||
conflicts: Map<BabelNodeSourceLocation, CompilerDiagnostic>,
|
||||
|
@ -308,6 +308,7 @@ export class Modules {
|
||||
moduleTracer: ModuleTracer;
|
||||
|
||||
resolveInitializedModules(): void {
|
||||
this.initializedModules.clear();
|
||||
let globalInitializedModulesMap = this._getGlobalProperty("__initializedModules");
|
||||
invariant(globalInitializedModulesMap instanceof ObjectValue);
|
||||
for (let moduleId of globalInitializedModulesMap.getOwnPropertyKeysArray()) {
|
||||
@ -468,7 +469,9 @@ export class Modules {
|
||||
// Check for escaping property assignments, if none escape, we got an existing object
|
||||
let escapes = false;
|
||||
for (let [binding] of properties) {
|
||||
if (!createdObjects.has(binding.object)) escapes = true;
|
||||
let object = binding.object;
|
||||
invariant(object instanceof ObjectValue);
|
||||
if (!createdObjects.has(object)) escapes = true;
|
||||
}
|
||||
if (escapes) return undefined;
|
||||
|
||||
|
@ -109,6 +109,7 @@ export class Serializer {
|
||||
if (this.options.additionalFunctions) {
|
||||
this.functions.checkThatFunctionsAreIndependent();
|
||||
}
|
||||
|
||||
if (this.options.initializeMoreModules) {
|
||||
if (timingStats !== undefined) timingStats.initializeMoreModulesTime = Date.now();
|
||||
this.modules.initializeMoreModules();
|
||||
@ -117,10 +118,15 @@ export class Serializer {
|
||||
timingStats.initializeMoreModulesTime = Date.now() - timingStats.initializeMoreModulesTime;
|
||||
}
|
||||
|
||||
let additionalFunctionValuesAndEffects = this.functions.getAdditionalFunctionValuesToEffects();
|
||||
//Deep traversal of the heap to identify the necessary scope of residual functions
|
||||
|
||||
if (timingStats !== undefined) timingStats.deepTraversalTime = Date.now();
|
||||
let residualHeapVisitor = new ResidualHeapVisitor(this.realm, this.logger, this.modules);
|
||||
let residualHeapVisitor = new ResidualHeapVisitor(
|
||||
this.realm,
|
||||
this.logger,
|
||||
this.modules,
|
||||
additionalFunctionValuesAndEffects
|
||||
);
|
||||
residualHeapVisitor.visitRoots();
|
||||
if (this.logger.hasErrors()) return undefined;
|
||||
if (timingStats !== undefined) timingStats.deepTraversalTime = Date.now() - timingStats.deepTraversalTime;
|
||||
@ -128,6 +134,7 @@ export class Serializer {
|
||||
// Phase 2: Let's serialize the heap and generate code.
|
||||
// Serialize for the first time in order to gather reference counts
|
||||
let residualHeapValueIdentifiers = new ResidualHeapValueIdentifiers();
|
||||
|
||||
if (this.options.inlineExpressions) {
|
||||
if (timingStats !== undefined) timingStats.referenceCountsTime = Date.now();
|
||||
residualHeapValueIdentifiers.initPass1();
|
||||
@ -142,6 +149,7 @@ export class Serializer {
|
||||
residualHeapVisitor.functionInfos,
|
||||
!!this.options.delayInitializations,
|
||||
residualHeapVisitor.referencedDeclaredValues,
|
||||
additionalFunctionValuesAndEffects,
|
||||
this.statistics
|
||||
).serialize();
|
||||
if (this.logger.hasErrors()) return undefined;
|
||||
@ -151,7 +159,6 @@ export class Serializer {
|
||||
|
||||
// Serialize for a second time, using reference counts to minimize number of generated identifiers
|
||||
if (timingStats !== undefined) timingStats.serializePassTime = Date.now();
|
||||
|
||||
let residualHeapSerializer = new ResidualHeapSerializer(
|
||||
this.realm,
|
||||
this.logger,
|
||||
@ -163,8 +170,10 @@ export class Serializer {
|
||||
residualHeapVisitor.functionInfos,
|
||||
!!this.options.delayInitializations,
|
||||
residualHeapVisitor.referencedDeclaredValues,
|
||||
additionalFunctionValuesAndEffects,
|
||||
this.statistics
|
||||
);
|
||||
|
||||
let ast = residualHeapSerializer.serialize();
|
||||
let generated = generate(ast, { sourceMaps: sourceMaps }, (code: any));
|
||||
if (timingStats !== undefined) {
|
||||
|
@ -23,6 +23,8 @@ export type FunctionInstance = {
|
||||
serializedBindings: SerializedBindings,
|
||||
functionValue: ECMAScriptSourceFunctionValue,
|
||||
insertionPoint?: BodyReference,
|
||||
// Optional place to put the function declaration
|
||||
preludeOverride?: Array<BabelNodeStatement>,
|
||||
scopeInstances: Set<ScopeBinding>,
|
||||
};
|
||||
|
||||
|
@ -134,7 +134,8 @@ export class Generator {
|
||||
});
|
||||
}
|
||||
|
||||
emitPropertyAssignment(object: Value, key: string, value: Value) {
|
||||
emitPropertyAssignment(object: ObjectValue, key: string, value: Value) {
|
||||
if (object.refuseSerialization) return;
|
||||
let propName = this.getAsPropertyNameExpression(key);
|
||||
this.body.push({
|
||||
args: [object, value],
|
||||
@ -146,6 +147,7 @@ export class Generator {
|
||||
}
|
||||
|
||||
emitDefineProperty(object: ObjectValue, key: string, desc: Descriptor) {
|
||||
if (object.refuseSerialization) return;
|
||||
if (desc.enumerable && desc.configurable && desc.writable && desc.value) {
|
||||
let descValue = desc.value;
|
||||
invariant(descValue instanceof Value);
|
||||
@ -164,7 +166,8 @@ export class Generator {
|
||||
}
|
||||
}
|
||||
|
||||
emitPropertyDelete(object: Value, key: string) {
|
||||
emitPropertyDelete(object: ObjectValue, key: string) {
|
||||
if (object.refuseSerialization) return;
|
||||
let propName = this.getAsPropertyNameExpression(key);
|
||||
this.body.push({
|
||||
args: [object],
|
||||
|
@ -53,7 +53,12 @@ import {
|
||||
import invariant from "../invariant.js";
|
||||
|
||||
export default class ObjectValue extends ConcreteValue {
|
||||
constructor(realm: Realm, proto?: ObjectValue | NullValue, intrinsicName?: string) {
|
||||
constructor(
|
||||
realm: Realm,
|
||||
proto?: ObjectValue | NullValue,
|
||||
intrinsicName?: string,
|
||||
refuseSerialization: boolean = false
|
||||
) {
|
||||
super(realm, intrinsicName);
|
||||
realm.recordNewObject(this);
|
||||
if (realm.useAbstractInterpretation) this.setupBindings();
|
||||
@ -63,6 +68,7 @@ export default class ObjectValue extends ConcreteValue {
|
||||
this._isSimple = realm.intrinsics.false;
|
||||
this.properties = new Map();
|
||||
this.symbols = new Map();
|
||||
this.refuseSerialization = refuseSerialization;
|
||||
}
|
||||
|
||||
static trackedProperties = [
|
||||
@ -232,6 +238,10 @@ export default class ObjectValue extends ConcreteValue {
|
||||
return this.hashValue;
|
||||
}
|
||||
|
||||
// We track some internal state as properties on the global object, these should
|
||||
// never be serialized.
|
||||
refuseSerialization: boolean;
|
||||
|
||||
mightBeFalse(): boolean {
|
||||
return false;
|
||||
}
|
||||
|
@ -0,0 +1,29 @@
|
||||
// additional functions
|
||||
// does not contain:x = 5;
|
||||
// does not contain:y = 10;
|
||||
// add at runtime: global.a = {}; global.b = {};
|
||||
if (global.__abstract) {
|
||||
global.a = __abstract({}, "global.a");
|
||||
global.b = __abstract({}, "global.b");
|
||||
}
|
||||
if (global.__makeSimple) {
|
||||
__makeSimple(global.a);
|
||||
__makeSimple(global.b);
|
||||
}
|
||||
var z = 42;
|
||||
|
||||
function additional1() {
|
||||
global.a.foo = z + "foo";
|
||||
var x = 5;
|
||||
}
|
||||
|
||||
function additional2() {
|
||||
global.b.bar = z + "bar";
|
||||
var y = 10;
|
||||
}
|
||||
|
||||
inspect = function() {
|
||||
additional2();
|
||||
additional1();
|
||||
return JSON.stringify(global.a) + JSON.stringify(global.b);
|
||||
}
|
26
test/serializer/additional-functions/capture-local.js
Normal file
26
test/serializer/additional-functions/capture-local.js
Normal file
@ -0,0 +1,26 @@
|
||||
// additional functions
|
||||
// does not contain:x = 5;
|
||||
// does not contain:y = 10;
|
||||
|
||||
function additional1() {
|
||||
var z = { foo: 5 };
|
||||
global.x = function nested1() { return z };
|
||||
var x = 5;
|
||||
}
|
||||
|
||||
function additional2() {
|
||||
global.y = function nested2() { return 6; };
|
||||
var y = 10;
|
||||
}
|
||||
|
||||
inspect = function inspect() {
|
||||
additional1();
|
||||
additional2();
|
||||
let x_fun = global.x;
|
||||
let x = global.x();
|
||||
let y = global.y;
|
||||
additional1();
|
||||
additional2();
|
||||
|
||||
return '' + x.foo + y() + (x_fun === global.x) + (global.x() === x) + global.x().foo + global.y() + (global.y === y);
|
||||
}
|
25
test/serializer/additional-functions/create-local.js
Normal file
25
test/serializer/additional-functions/create-local.js
Normal file
@ -0,0 +1,25 @@
|
||||
// additional functions
|
||||
// does not contain:x = 5;
|
||||
// does not contain:y = 10;
|
||||
|
||||
function additional1() {
|
||||
var z = { foo: 5 };
|
||||
global.x = z;
|
||||
var x = 5;
|
||||
}
|
||||
|
||||
function additional2() {
|
||||
var z = { bar: 6 };
|
||||
global.y = z;
|
||||
var y = 10;
|
||||
}
|
||||
|
||||
inspect = function() {
|
||||
additional1();
|
||||
additional2();
|
||||
let x = global.x;
|
||||
let y = global.y;
|
||||
additional1();
|
||||
|
||||
return '' + x.foo + y.bar + (global.x === x) + global.x.foo;
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
// additional functions
|
||||
// does not contain:x = 5;
|
||||
// does not contain:y = 10;
|
||||
|
||||
global.foo = { x: 100, y: 200 };
|
||||
var z = 42;
|
||||
|
||||
function additional1() {
|
||||
let local = {};
|
||||
local.x = global.foo.x;
|
||||
local.foo = z + local.x;
|
||||
delete local.x;
|
||||
var x = 5;
|
||||
global.foo.x = local;
|
||||
}
|
||||
|
||||
function additional2() {
|
||||
let local = {};
|
||||
local.y = global.foo.y;
|
||||
local.bar = z + local.y;
|
||||
delete local.y;
|
||||
var y = 10;
|
||||
global.foo.y = local;
|
||||
}
|
||||
|
||||
inspect = function() {
|
||||
additional2();
|
||||
additional1();
|
||||
let foo = global.foo;
|
||||
return "" + JSON.stringify(foo.x) + JSON.stringify(foo.y);
|
||||
}
|
27
test/serializer/additional-functions/createdobject.js
Normal file
27
test/serializer/additional-functions/createdobject.js
Normal file
@ -0,0 +1,27 @@
|
||||
// additional functions
|
||||
// does not contain:x = 5;
|
||||
// does not contain:y = 10;
|
||||
let toCapture1 = {};
|
||||
|
||||
function additional1() {
|
||||
toCapture1 = 5;
|
||||
var x = 5;
|
||||
x = 10;
|
||||
global.x = x;
|
||||
}
|
||||
|
||||
function additional2() {
|
||||
var y = 10;
|
||||
y = 5;
|
||||
global.y = y;
|
||||
}
|
||||
|
||||
inspect = function inspect() {
|
||||
let z = toCapture1;
|
||||
additional1();
|
||||
let z2 = toCapture1;
|
||||
additional2();
|
||||
|
||||
return "PASS";
|
||||
//return '' + JSON.stringify(z) + JSON.stringify(z2) + JSON.stringify(toCapture1);
|
||||
}
|
37
test/serializer/additional-functions/modifiedobjects.js
Normal file
37
test/serializer/additional-functions/modifiedobjects.js
Normal file
@ -0,0 +1,37 @@
|
||||
// additional functions
|
||||
// does not contain:x = 5;
|
||||
// does not contain:y = 10;
|
||||
let toCapture1 = {};
|
||||
let toCapture2 = 5;
|
||||
let toCapture3 = {};
|
||||
Object.defineProperty(global, "foo", { configurable: true, enumerable: false, value: 42 });
|
||||
Object.defineProperty(global, "bar", { configurable: true, enumerable: false, get: function () { return 43; } });
|
||||
|
||||
function additional1() {
|
||||
toCapture1 = 5;
|
||||
toCapture2 = undefined;
|
||||
global.foo += 1;
|
||||
foo += 1;
|
||||
var x = 5;
|
||||
x = 10;
|
||||
global.x = x;
|
||||
}
|
||||
|
||||
function additional2() {
|
||||
var y = 10;
|
||||
y = 5;
|
||||
toCapture3 = y;
|
||||
global.y = y;
|
||||
}
|
||||
|
||||
inspect = function inspect() {
|
||||
let z = toCapture1;
|
||||
let x = toCapture2;
|
||||
let y = toCapture3;
|
||||
additional1();
|
||||
let z2 = toCapture1;
|
||||
additional2();
|
||||
|
||||
return 'PASS';
|
||||
//return '' + z + z2 + x + y + toCapture1 + toCapture2 + toCapture3 + foo + bar + (foo === bar);
|
||||
}
|
29
test/serializer/additional-functions/nested_function.js
Normal file
29
test/serializer/additional-functions/nested_function.js
Normal file
@ -0,0 +1,29 @@
|
||||
// additional functions
|
||||
// does not contain:var y = 5;
|
||||
// does not contain:var y = 10;
|
||||
|
||||
function additional1() {
|
||||
var x2 = { foo: 5 };
|
||||
foo = function() { return x2; }
|
||||
var y = 5;
|
||||
}
|
||||
|
||||
function produceObject() {
|
||||
return { bar: 5 };
|
||||
}
|
||||
|
||||
function additional2() {
|
||||
let x1 = produceObject();
|
||||
bar = function() { return x1; }
|
||||
}
|
||||
|
||||
inspect = function() {
|
||||
additional1();
|
||||
additional2();
|
||||
let bar1 = bar;
|
||||
let foo1 = foo;
|
||||
additional1();
|
||||
additional2();
|
||||
|
||||
return ' ' + JSON.stringify(bar()) + foo().foo + bar1() + foo1().foo + (bar1 === bar) + (bar1() === bar()) + (foo1() === foo());
|
||||
}
|
23
test/serializer/additional-functions/noconflict-captures.js
Normal file
23
test/serializer/additional-functions/noconflict-captures.js
Normal file
@ -0,0 +1,23 @@
|
||||
// additional functions
|
||||
// does not contain:x = 5;
|
||||
// does not contain:y = 10;
|
||||
|
||||
global.a = "";
|
||||
global.b = "";
|
||||
var z = 42;
|
||||
|
||||
function additional1() {
|
||||
global.a = z + "foo";
|
||||
var x = 5;
|
||||
}
|
||||
|
||||
function additional2() {
|
||||
global.b = z + "bar";
|
||||
var y = 10;
|
||||
}
|
||||
|
||||
inspect = function() {
|
||||
additional2();
|
||||
additional1();
|
||||
return global.a + global.b;
|
||||
}
|
23
test/serializer/additional-functions/noconflict-captures2.js
Normal file
23
test/serializer/additional-functions/noconflict-captures2.js
Normal file
@ -0,0 +1,23 @@
|
||||
// additional functions
|
||||
// does not contain:x = 5;
|
||||
// does not contain:y = 10;
|
||||
|
||||
global.a = "";
|
||||
global.b = "";
|
||||
var z = global.__abstract ? __abstract("number", "(42)") : 42;
|
||||
|
||||
function additional1() {
|
||||
global.a = z + "foo";
|
||||
var x = 5;
|
||||
}
|
||||
|
||||
function additional2() {
|
||||
global.b = z + "bar";
|
||||
var y = 10;
|
||||
}
|
||||
|
||||
inspect = function() {
|
||||
additional2();
|
||||
additional1();
|
||||
return global.a + global.b;
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
// additional functions
|
||||
// does not contain:x = 5;
|
||||
// does not contain:y = 10;
|
||||
|
||||
global.c = {};
|
||||
|
||||
function additional1() {
|
||||
global.c.foo = 5;
|
||||
var x = 5;
|
||||
}
|
||||
|
||||
function additional2() {
|
||||
global.c.bar = 2;
|
||||
var y = 10;
|
||||
}
|
||||
|
||||
inspect = function() {
|
||||
additional2();
|
||||
additional1();
|
||||
return global.b + global.c.foo + global.c.bar;
|
||||
}
|
22
test/serializer/additional-functions/noopfunc.js
Normal file
22
test/serializer/additional-functions/noopfunc.js
Normal file
@ -0,0 +1,22 @@
|
||||
// additional functions
|
||||
// does not contain:x = 5;
|
||||
// does not contain:y = 10;
|
||||
|
||||
global.a = "";
|
||||
global.b = "";
|
||||
var z = 42;
|
||||
|
||||
function additional1() {
|
||||
var x = 5;
|
||||
}
|
||||
|
||||
function additional2() {
|
||||
global.b = z + "bar";
|
||||
var y = 10;
|
||||
}
|
||||
|
||||
inspect = function() {
|
||||
additional2();
|
||||
additional1();
|
||||
return global.a + global.b;
|
||||
}
|
25
test/serializer/additional-functions/property-deletion.js
Normal file
25
test/serializer/additional-functions/property-deletion.js
Normal file
@ -0,0 +1,25 @@
|
||||
// additional functions
|
||||
// does not contain:x = 5;
|
||||
// does not contain:y = 10;
|
||||
|
||||
global.foo = { x: 100, y: 200};
|
||||
var z = 42;
|
||||
|
||||
function additional1() {
|
||||
global.foo.foo = z + global.foo.x;
|
||||
delete global.foo.x;
|
||||
var x = 5;
|
||||
}
|
||||
|
||||
function additional2() {
|
||||
global.foo.bar = z + global.foo.y;
|
||||
delete global.foo.y
|
||||
var y = 10;
|
||||
}
|
||||
|
||||
inspect = function() {
|
||||
additional2();
|
||||
additional1();
|
||||
let foo = global.foo;
|
||||
return "" + foo.x + foo.y + foo.foo + foo.bar;
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
// additional functions
|
||||
// does not contain:x = 5;
|
||||
// does not contain:y = 10;
|
||||
|
||||
global.a = {};
|
||||
global.b = {};
|
||||
var z = 42;
|
||||
|
||||
function additional1() {
|
||||
global.a.foo = z + "foo";
|
||||
var x = 5;
|
||||
}
|
||||
|
||||
function additional2() {
|
||||
global.b.bar = z + "bar";
|
||||
var y = 10;
|
||||
}
|
||||
|
||||
inspect = function() {
|
||||
additional2();
|
||||
additional1();
|
||||
return JSON.stringify(global.a) + JSON.stringify(global.b);
|
||||
}
|
135
test/serializer/additional-functions/require_opt.js
Normal file
135
test/serializer/additional-functions/require_opt.js
Normal file
@ -0,0 +1,135 @@
|
||||
// additional functions
|
||||
// does not contain:var y = 5;
|
||||
// does not contain:var y = 10;
|
||||
var modules = Object.create(null);
|
||||
|
||||
__d = define;
|
||||
function require(moduleId) {
|
||||
var moduleIdReallyIsNumber = moduleId;
|
||||
var module = modules[moduleIdReallyIsNumber];
|
||||
return module && module.isInitialized ? module.exports : guardedLoadModule(moduleIdReallyIsNumber, module);
|
||||
}
|
||||
|
||||
function define(factory, moduleId, dependencyMap) {
|
||||
if (moduleId in modules) {
|
||||
return;
|
||||
}
|
||||
modules[moduleId] = {
|
||||
dependencyMap: dependencyMap,
|
||||
exports: undefined,
|
||||
factory: factory,
|
||||
hasError: false,
|
||||
isInitialized: false
|
||||
};
|
||||
|
||||
var _verboseName = arguments[3];
|
||||
if (_verboseName) {
|
||||
modules[moduleId].verboseName = _verboseName;
|
||||
verboseNamesToModuleIds[_verboseName] = moduleId;
|
||||
}
|
||||
}
|
||||
|
||||
var inGuard = false;
|
||||
function guardedLoadModule(moduleId, module) {
|
||||
if (!inGuard && global.ErrorUtils) {
|
||||
inGuard = true;
|
||||
var returnValue = void 0;
|
||||
try {
|
||||
returnValue = loadModuleImplementation(moduleId, module);
|
||||
} catch (e) {
|
||||
global.ErrorUtils.reportFatalError(e);
|
||||
}
|
||||
inGuard = false;
|
||||
return returnValue;
|
||||
} else {
|
||||
return loadModuleImplementation(moduleId, module);
|
||||
}
|
||||
}
|
||||
|
||||
function loadModuleImplementation(moduleId, module) {
|
||||
var nativeRequire = global.nativeRequire;
|
||||
if (!module && nativeRequire) {
|
||||
nativeRequire(moduleId);
|
||||
module = modules[moduleId];
|
||||
}
|
||||
|
||||
if (!module) {
|
||||
throw unknownModuleError(moduleId);
|
||||
}
|
||||
|
||||
if (module.hasError) {
|
||||
throw moduleThrewError(moduleId);
|
||||
}
|
||||
|
||||
module.isInitialized = true;
|
||||
var exports = module.exports = {};
|
||||
var _module = module,
|
||||
factory = _module.factory,
|
||||
dependencyMap = _module.dependencyMap;
|
||||
try {
|
||||
|
||||
var _moduleObject = { exports: exports };
|
||||
|
||||
factory(global, require, _moduleObject, exports, dependencyMap);
|
||||
|
||||
module.factory = undefined;
|
||||
|
||||
return module.exports = _moduleObject.exports;
|
||||
} catch (e) {
|
||||
module.hasError = true;
|
||||
module.isInitialized = false;
|
||||
module.exports = undefined;
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
function unknownModuleError(id) {
|
||||
var message = 'Requiring unknown module "' + id + '".';
|
||||
return Error(message);
|
||||
}
|
||||
|
||||
function moduleThrewError(id) {
|
||||
return Error('Requiring module "' + id + '", which threw an exception.');
|
||||
}
|
||||
|
||||
// === End require code ===
|
||||
|
||||
define(function(global, require, module, exports) {
|
||||
let exportval = { foo: " hello " };
|
||||
module.exports = exportval;
|
||||
}, 0, null);
|
||||
|
||||
define(function(global, require, module, exports) {
|
||||
var x1 = require(0);
|
||||
var y = require(2);
|
||||
module.exports = {
|
||||
bar: " goodbye",
|
||||
foo2: x1.foo,
|
||||
baz: y.baz
|
||||
};
|
||||
}, 1, null);
|
||||
|
||||
define(function(global, require, module, exports) {
|
||||
module.exports = { baz: " foo " };
|
||||
}, 2, null);
|
||||
|
||||
function additional1() {
|
||||
var x2 = require(0);
|
||||
global.foo = function() { return x2; }
|
||||
var y = 5;
|
||||
}
|
||||
|
||||
function additional2() {
|
||||
//global.bar = function() { return require(0).baz + "bar"; }
|
||||
global.bar = function() { return 5; }
|
||||
}
|
||||
|
||||
inspect = function() {
|
||||
additional1();
|
||||
additional2();
|
||||
|
||||
let requireequal = require(0) === global.foo();
|
||||
let uninitialized = modules[1].exports === undefined &&
|
||||
require(1).bar === " goodbye";
|
||||
return ' ' + requireequal + uninitialized + global.bar();
|
||||
}
|
@ -1,14 +1,18 @@
|
||||
// additional functions
|
||||
// does not contain:x = 5;
|
||||
// does not contain:y = 10;
|
||||
|
||||
global.a = "";
|
||||
global.b = "";
|
||||
|
||||
function additional1() {
|
||||
global.a = "foo";
|
||||
var x = 5;
|
||||
}
|
||||
|
||||
function additional2() {
|
||||
global.b = "bar";
|
||||
var y = 10;
|
||||
}
|
||||
|
||||
inspect = function() {
|
||||
|
@ -1,4 +1,4 @@
|
||||
Object.defineProperty(global, "foo", { configurable: true, enumerable: false, value: 42 });
|
||||
Object.defineProperty(global, "foo", { configurable: true, enumerable: false, value: 41 });
|
||||
Object.defineProperty(global, "bar", { configurable: true, enumerable: false, get: function () { return 43; } });
|
||||
|
||||
inspect = function() { global.foo += 1; return foo === bar; }
|
||||
|
Loading…
Reference in New Issue
Block a user