aran
Version:
JavaScript instrumenter
958 lines (936 loc) • 29.2 kB
JavaScript
// https://tc39.github.io/ecma262/#sec-function-instances
import {
hasDirectEvalCall,
hasReturnStatement,
hasUseStrictDirective,
} from "../query/index.mjs";
import {
EMPTY,
concatXX,
concat__,
concat___,
concat___XX,
findFirstIndex,
map,
mapIndex,
some,
tuple2,
NULL_SEQUENCE,
bindSequence,
callSequence___X,
flatSequence,
liftSequenceX,
liftSequenceXX__,
liftSequenceX_,
liftSequenceX_X__,
liftSequenceX__,
liftSequenceX___,
liftSequence_XXX_,
liftSequence_X_,
liftSequence_X__,
liftSequence__X,
liftSequence__X_,
mapSequence,
zeroSequence,
every,
filterOut,
callSequence____X,
liftSequenceXX,
findNarrow,
get0,
filter,
} from "../../util/index.mjs";
import { AranTypeError } from "../../error.mjs";
import {
makeDataDescriptor,
makeDataDescriptorExpression,
makeGetExpression,
} from "../intrinsic.mjs";
import {
makeApplyExpression,
makeExpressionEffect,
makeIntrinsicExpression,
makePrimitiveExpression,
makeSequenceExpression,
makeRoutineBlock,
makeConditionalExpression,
makeEffectStatement,
makeClosureExpression as makeClosureExpressionInner,
makeBlockStatement,
makeTreeRoutineBlock,
} from "../node.mjs";
import { extendEvalScope, transClosureBody } from "./body.mjs";
import { transExpression } from "./expression.mjs";
import { transPattern } from "./pattern.mjs";
import { incorporateRoutineBlock } from "../prelude/index.mjs";
import {
cacheWritable,
makeWriteCacheEffect,
makeReadCacheExpression,
} from "../cache.mjs";
import { makeNameExpression } from "../name.mjs";
import { forkMeta, nextMeta } from "../meta.mjs";
import {
extendClosureRoutine,
extendClosureVariable,
extendNormalRegularVariable,
extendProxyVariable,
extendResultRoutine,
extendStrict,
listWriteVariableEffect,
makeFinalizeResultExpression,
makeFinalizeResultOperation,
makeInitVariableOperation,
makeReadInputExpression,
} from "../scope/index.mjs";
import { hoist } from "../annotation/index.mjs";
/**
* @type {(
* node: import("estree-sentry").RestablePattern<import("../hash.d.ts").HashProp>,
* ) => boolean}
*/
const isLengthCutoffPattern = ({ type }) =>
type === "RestElement" || type === "AssignmentPattern";
/**
* @type {(
* kind: import("../closure.d.ts").Closure["type"],
* node: import("estree-sentry").Function<import("../hash.d.ts").HashProp>,
* ) => "none" | "plain" | "generator" | "async-generator" }
*/
const getPrototypeKind = (kind, node) => {
if (node.type === "ArrowFunctionExpression") {
return "none";
} else {
if (node.generator) {
return node.async ? "async-generator" : "generator";
} else {
if (kind === "constructor") {
return "plain";
} else if (kind === "plain") {
return node.async ? "none" : "plain";
} else if (kind === "method") {
return "none";
} else {
throw new AranTypeError(kind);
}
}
}
};
/**
* @type {(
* options: {
* closure: import("../closure.d.ts").Closure,
* self: import("../cache.d.ts").Cache,
* node: import("estree-sentry").Function<unknown>,
* },
* ) => import("../scope/routine/index.d.ts").Closure}
*/
const makeScopeClosure = ({ closure, self, node }) => {
if (node.type === "ArrowFunctionExpression") {
return { type: "arrow", asynchronous: node.async, generator: false };
} else {
switch (closure.type) {
case "constructor": {
return {
...closure,
asynchronous: false,
generator: false,
self,
};
}
case "method": {
return {
...closure,
asynchronous: node.async,
generator: node.generator,
};
}
case "plain": {
return {
type: "function",
asynchronous: node.async,
generator: node.generator,
};
}
default: {
throw new AranTypeError(closure);
}
}
}
};
/**
* @type {(
* hash: import("../hash.d.ts").Hash,
* meta: import("../meta.d.ts").Meta,
* options: {
* has_return_statement: boolean,
* },
* scope: import("../scope/index.d.ts").Scope,
* ) => import("../../util/sequence.d.ts").Sequence<
* import("../prelude/index.d.ts").MetaDeclarationPrelude,
* import("../scope/index.d.ts").Scope,
* >}
*/
const extendResultScope = (hash, meta, { has_return_statement }, scope) => {
if (has_return_statement) {
return extendResultRoutine(hash, meta, {}, scope);
} else {
return zeroSequence(scope);
}
};
/**
* @type {(
* binding: import("../annotation/hoisting.d.ts").FrameEntry,
* ) => binding is [
* import("estree-sentry").VariableName,
* ["function-self-sloppy" | "function-self-strict"],
* ]}
*/
const isCalleeBinding = (binding) => {
const { 1: kinds } = binding;
if (kinds.length === 1) {
const kind = kinds[0];
return kind === "function-self-sloppy" || kind === "function-self-strict";
} else {
return false;
}
};
/**
* @type {(
* bindings: import("../annotation/hoisting.d.ts").FrameEntry[],
* ) => {
* selection: null | [
* import("estree-sentry").VariableName,
* ["function-self-sloppy" | "function-self-strict"],
* ],
* remainder: import("../annotation/hoisting.d.ts").FrameEntry[],
* }}
*/
const extractCalleeFrame = (bindings) => ({
selection: findNarrow(bindings, isCalleeBinding),
remainder: filterOut(bindings, isCalleeBinding),
});
/**
* @type {(
* hash: import("../hash.d.ts").Hash,
* meta: import("../meta.d.ts").Meta,
* options: {
* callee: null | [
* import("estree-sentry").VariableName,
* ["function-self-sloppy" | "function-self-strict"],
* ],
* self: import("../cache.d.ts").WritableCache,
* },
* scope: import("../scope/index.d.ts").Scope,
* ) => import("../../util/sequence.d.ts").Sequence<
* never,
* import("../scope/index.d.ts").Scope,
* >}
*/
const extendCalleeScope = (hash, meta, { callee, self }, scope) => {
if (callee === null) {
return zeroSequence(scope);
} else {
const {
0: variable,
1: { 0: kind },
} = callee;
return extendProxyVariable(
hash,
meta,
{ bindings: [[variable, { proxy: self, kind }]] },
scope,
);
}
};
/**
* @type {(
* hash: import("../hash.d.ts").Hash,
* kind: (
* | "plain"
* | "generator"
* | "async-generator"
* ),
* self: import("../cache.d.ts").Cache,
* ) => import("../atom.d.ts").Expression}
*/
const makePrototypeExpression = (hash, kind, self) => {
switch (kind) {
case "plain": {
return makeApplyExpression(
makeIntrinsicExpression("Object.defineProperty", hash),
makeIntrinsicExpression("undefined", hash),
[
makeApplyExpression(
makeIntrinsicExpression("Object.create", hash),
makeIntrinsicExpression("undefined", hash),
[makeIntrinsicExpression("Object.prototype", hash)],
hash,
),
makePrimitiveExpression("constructor", hash),
makeDataDescriptorExpression(
{
value: makeReadCacheExpression(self, hash),
writable: true,
enumerable: false,
configurable: true,
},
hash,
),
],
hash,
);
}
case "generator": {
return makeApplyExpression(
makeIntrinsicExpression("Object.create", hash),
makeIntrinsicExpression("undefined", hash),
[
makeIntrinsicExpression(
"aran.GeneratorFunction.prototype.prototype",
hash,
),
],
hash,
);
}
case "async-generator": {
return makeApplyExpression(
makeIntrinsicExpression("Object.create", hash),
makeIntrinsicExpression("undefined", hash),
[
makeIntrinsicExpression(
"aran.AsyncGeneratorFunction.prototype.prototype",
hash,
),
],
hash,
);
}
default: {
throw new AranTypeError(kind);
}
}
};
// Two different scope frame:
// ==========================
// > function f (x = y) { var y; return x; }
// undefined
// > y
// Thrown:
// ReferenceError: y is not defined
// > f()
// Thrown:
// ReferenceError: y is not defined
// at f (repl:1:17)
/**
* @type {(
* node: import("estree-sentry").RestablePattern<import("../hash.d.ts").HashProp>,
* meta: import("../meta.d.ts").Meta,
* scope: import("../scope/index.d.ts").Scope,
* kind: "write" | "initialize",
* index: number,
* ) => import("../../util/sequence.d.ts").Sequence<
* import("../prelude/index.d.ts").BodyPrelude,
* [
* import("../../util/tree.d.ts").Tree<import("../atom.d.ts").Effect>,
* import("../scope/index.d.ts").Scope,
* ],
* >}
*/
const transParameter = (node, meta, scope, kind, index) => {
const { _hash: hash } = node;
if (node.type === "RestElement") {
return callSequence____X(
transPattern,
node.argument,
forkMeta((meta = nextMeta(meta))),
scope,
kind,
index === 0
? makeReadInputExpression(
hash,
forkMeta((meta = nextMeta(meta))),
scope,
{},
)
: liftSequence_X__(
makeApplyExpression,
makeIntrinsicExpression("Array.prototype.slice", hash),
makeReadInputExpression(
hash,
forkMeta((meta = nextMeta(meta))),
scope,
{},
),
[makePrimitiveExpression(index, hash)],
hash,
),
);
} else {
return callSequence____X(
transPattern,
node,
forkMeta((meta = nextMeta(meta))),
scope,
kind,
liftSequenceXX__(
makeConditionalExpression,
// Object.hasOwn guard to avoid accessing the prototype chain
liftSequence__X_(
makeApplyExpression,
makeIntrinsicExpression("Object.hasOwn", hash),
makeIntrinsicExpression("undefined", hash),
liftSequenceX_(
concat__,
makeReadInputExpression(
hash,
forkMeta((meta = nextMeta(meta))),
scope,
{},
),
makePrimitiveExpression(index, hash),
),
hash,
),
liftSequenceX__(
makeGetExpression,
makeReadInputExpression(
hash,
forkMeta((meta = nextMeta(meta))),
scope,
{},
),
makePrimitiveExpression(index, hash),
hash,
),
makeIntrinsicExpression("undefined", hash),
hash,
),
);
}
};
/**
* @type {(
* binding: import("../annotation/hoisting.d.ts").FrameEntry,
* ) => boolean}
*/
const isArgumentListBinding = ({ 1: kinds }) =>
kinds.length === 1 && kinds[0] === "arguments";
/**
* @type {(
* nodes: import("estree-sentry").RestablePattern<import("../hash.d.ts").HashProp>[],
* meta: import("../meta.d.ts").Meta,
* scope: import("../scope/index.d.ts").Scope,
* kind: "write" | "initialize",
* ) => import("../../util/sequence.d.ts").Sequence<
* import("../prelude/index.d.ts").BodyPrelude,
* [
* import("../../util/tree.d.ts").Tree<import("../atom.d.ts").Effect>,
* import("../scope/index.d.ts").Scope,
* ],
* >}
*/
const listParameterEffect = (nodes, meta, scope, kind) => {
/** @type {<X>(pair: [X, import("../scope/index.d.ts").Scope]) => X} */
const updateScope = (pair) => {
// eslint-disable-next-line local/no-impure
scope = pair[1];
return pair[0];
};
return liftSequenceX_(
tuple2,
flatSequence(
mapIndex(nodes.length, (index) =>
mapSequence(
transParameter(
nodes[index],
forkMeta((meta = nextMeta(meta))),
scope,
kind,
index,
),
updateScope,
),
),
),
scope,
);
};
/**
* @type {<X>(
* node: import("estree-sentry").RestablePattern<X>,
* ) => node is import("estree-sentry").VariableIdentifier<X>}
*/
const isIdentifier = (node) => node.type === "Identifier";
/**
* @type {(
* mode: "strict" | "sloppy",
* simple: boolean,
* callee: import("../cache.d.ts").Cache,
* hash: import("../hash.d.ts").Hash,
* ) => import("../atom.d.ts").Expression}
*/
const makeCalleeExpression = (mode, simple, callee, hash) => {
if (mode === "sloppy") {
return simple
? makeReadCacheExpression(callee, hash)
: makePrimitiveExpression(null, hash);
} else if (mode === "strict") {
return makePrimitiveExpression(null, hash);
} else {
throw new AranTypeError(mode);
}
};
/**
* @type {(
* kind: import("../../lang/syntax.d.ts").ClosureKind,
* asynchronous: boolean,
* body: import("../atom.d.ts").RoutineBlock,
* hash: import("../hash.d.ts").Hash,
* ) => import("../atom.d.ts").Expression}
*/
const makeClosureExpression = (kind, asynchronous, body, hash) => {
if (kind === "arrow" || kind === "function" || kind === "method") {
return makeClosureExpressionInner(
kind,
asynchronous,
makeRoutineBlock(
body.bindings,
null,
body.head === null
? body.body
: concatXX(
map(body.head, (node) => makeEffectStatement(node, hash)),
body.body,
),
body.tail,
hash,
),
hash,
);
} else if (kind === "generator") {
return makeClosureExpressionInner(kind, asynchronous, body, hash);
} else {
throw new AranTypeError(kind);
}
};
/**
* @type {(
* node: import("estree-sentry").Function<import("../hash.d.ts").HashProp>,
* closure: import("../closure.d.ts").Closure,
* ) => import("../../lang/syntax.d.ts").ClosureKind}
*/
const getClosureKind = (node, closure) => {
if (node.type === "ArrowFunctionExpression") {
return "arrow";
} else {
if (node.generator) {
return "generator";
} else {
switch (closure.type) {
case "constructor": {
return "function";
}
case "method": {
return "method";
}
case "plain": {
return "function";
}
default: {
throw new AranTypeError(closure);
}
}
}
}
};
// arguments must be in eval frame:
//
// (function () {
// eval("var arguments;");
// return arguments;
// })(); // [Arguments] {}
//
// parameter must be in eval frame:
//
// (function (x) {
// eval("var x;");
// return x;
// })(123); // 123
//
// callee must be out of eval frame:
//
// (function f() {
// eval("var f;");
// return f;
// })(); // undefined
/**
* @type {(
* binding: import("../annotation/hoisting.d.ts").Kind,
* ) => boolean}
*/
const isSimpleHeadEvalKind = (kind) =>
kind === "arguments" ||
kind === "param-simple" ||
kind === "var" ||
kind === "function-sloppy-away";
/**
* @type {(
* binding: import("../annotation/hoisting.d.ts").FrameEntry,
* ) => boolean}
*/
const isSimpleHeadEvalBinding = ({ 1: kinds }) =>
every(kinds, isSimpleHeadEvalKind);
/**
* @type {(
* node: import("estree-sentry").Function<import("../hash.d.ts").HashProp>,
* meta: import("../meta.d.ts").Meta,
* scope: import("../scope/index.d.ts").Scope,
* closure: import("../closure.d.ts").Closure,
* name: import("../name.d.ts").Name,
* ) => import("../../util/sequence.d.ts").Sequence<
* import("../prelude/index.d.ts").BodyPrelude,
* import("../atom.d.ts").Expression,
* >}
*/
export const transFunction = (node, meta, parent_scope, closure, name) => {
const { _hash: hash } = node;
const scope =
node.body.type === "BlockStatement" && hasUseStrictDirective(node.body.body)
? extendStrict(parent_scope)
: parent_scope;
const { selection: callee, remainder: bindings } = extractCalleeFrame(
hoist(hash, scope.annotation),
);
const has_argument_list = some(bindings, isArgumentListBinding);
const simple = every(node.params, isIdentifier);
const prototype_kind = getPrototypeKind(closure.type, node);
const has_return_statement =
node.body.type === "BlockStatement" && hasReturnStatement(node.body);
const has_body_direct_eval_call = hasDirectEvalCall(node.body);
const has_head_direct_eval_call = some(node.params, hasDirectEvalCall);
return bindSequence(
cacheWritable(forkMeta((meta = nextMeta(meta))), "aran.deadzone_symbol"),
(self) =>
liftSequenceX__(
makeSequenceExpression,
liftSequenceX_X__(
concat___XX,
liftSequence_X_(
makeWriteCacheEffect,
self,
liftSequence__X_(
makeClosureExpression,
getClosureKind(node, closure),
node.async,
incorporateRoutineBlock(
bindSequence(
callSequence___X(
extendNormalRegularVariable,
hash,
forkMeta((meta = nextMeta(meta))),
{
bindings:
simple && has_body_direct_eval_call
? filterOut(bindings, isSimpleHeadEvalBinding)
: bindings,
},
callSequence___X(
extendEvalScope,
hash,
forkMeta((meta = nextMeta(meta))),
{
variables: simple
? has_body_direct_eval_call
? map(
filter(bindings, isSimpleHeadEvalBinding),
get0,
)
: null
: has_head_direct_eval_call
? EMPTY
: null,
},
callSequence___X(
extendCalleeScope,
hash,
forkMeta((meta = nextMeta(meta))),
{ callee, self },
callSequence___X(
extendResultScope,
hash,
forkMeta((meta = nextMeta(meta))),
{ has_return_statement },
extendClosureRoutine(
hash,
forkMeta((meta = nextMeta(meta))),
makeScopeClosure({ closure, self, node }),
extendClosureVariable(scope),
),
),
),
),
),
(scope) => {
/** @type {<X>(pair: [X, import("../scope/index.d.ts").Scope]) => X} */
const updateScope = (pair) => {
// eslint-disable-next-line local/no-impure
scope = pair[1];
return pair[0];
};
return liftSequence_XXX_(
makeTreeRoutineBlock,
[],
liftSequenceXX(
concat__,
has_argument_list
? callSequence___X(
listWriteVariableEffect,
hash,
forkMeta((meta = nextMeta(meta))),
scope,
callSequence___X(
// init operation to prevent reporting this
// as a early syntax error in strict mode.
makeInitVariableOperation,
hash,
scope.mode,
/** @type {import("estree-sentry").VariableName} */ (
"arguments"
),
liftSequence__X_(
makeApplyExpression,
makeIntrinsicExpression(
"aran.toArgumentList",
hash,
),
makeIntrinsicExpression("undefined", hash),
liftSequenceX_(
concat__,
makeReadInputExpression(
hash,
forkMeta((meta = nextMeta(meta))),
scope,
{},
),
makeCalleeExpression(
scope.mode,
simple,
self,
hash,
),
),
hash,
),
),
)
: NULL_SEQUENCE,
mapSequence(
listParameterEffect(
node.params,
forkMeta((meta = nextMeta(meta))),
scope,
simple ? "write" : "initialize",
),
updateScope,
),
),
node.body.type === "BlockStatement"
? liftSequenceX_(
makeBlockStatement,
transClosureBody(
node.body,
forkMeta((meta = nextMeta(meta))),
scope,
// With simple parameter,
// the binding should
// not be initialized
// with undefined.
// Instead, it should
// keep its value.
//
// (((f) => {
// eval(`
// // { f: 123 }
// console.log({ f });
// { function f () {} }
// `);
// }) (123))
{
has_direct_eval_call: simple
? false
: hasDirectEvalCall(node.body),
has_return_statement,
},
),
hash,
)
: NULL_SEQUENCE,
callSequence___X(
makeFinalizeResultExpression,
hash,
forkMeta((meta = nextMeta(meta))),
scope,
liftSequenceX(
makeFinalizeResultOperation,
node.body.type === "BlockStatement"
? zeroSequence(null)
: transExpression(
node.body,
forkMeta((meta = nextMeta(meta))),
scope,
),
),
),
hash,
);
},
),
hash,
),
hash,
),
hash,
),
makeExpressionEffect(
makeApplyExpression(
makeIntrinsicExpression("Reflect.defineProperty", hash),
makeIntrinsicExpression("undefined", hash),
[
makeReadCacheExpression(self, hash),
makePrimitiveExpression("length", hash),
makeDataDescriptorExpression(
{
value: makePrimitiveExpression(
some(node.params, isLengthCutoffPattern)
? findFirstIndex(node.params, isLengthCutoffPattern)
: node.params.length,
hash,
),
writable: false,
enumerable: false,
configurable: true,
},
hash,
),
],
hash,
),
hash,
),
liftSequenceX_(
makeExpressionEffect,
liftSequence__X_(
makeApplyExpression,
makeIntrinsicExpression("Reflect.defineProperty", hash),
makeIntrinsicExpression("undefined", hash),
liftSequence__X(
concat___,
makeReadCacheExpression(self, hash),
makePrimitiveExpression("name", hash),
liftSequenceX_(
makeDataDescriptorExpression,
liftSequenceX___(
makeDataDescriptor,
makeNameExpression(
hash,
forkMeta((meta = nextMeta(meta))),
name,
),
false,
false,
true,
),
hash,
),
),
hash,
),
hash,
),
scope.mode === "sloppy" &&
node.type !== "ArrowFunctionExpression" &&
!node.generator &&
!node.async &&
closure.type === "plain"
? [
makeExpressionEffect(
makeApplyExpression(
makeIntrinsicExpression("Reflect.defineProperty", hash),
makeIntrinsicExpression("undefined", hash),
[
makeReadCacheExpression(self, hash),
makePrimitiveExpression("arguments", hash),
makeDataDescriptorExpression(
{
value: makePrimitiveExpression(null, hash),
writable: false,
enumerable: false,
configurable: true,
},
hash,
),
],
hash,
),
hash,
),
makeExpressionEffect(
makeApplyExpression(
makeIntrinsicExpression("Reflect.defineProperty", hash),
makeIntrinsicExpression("undefined", hash),
[
makeReadCacheExpression(self, hash),
makePrimitiveExpression("caller", hash),
makeDataDescriptorExpression(
{
value: makePrimitiveExpression(null, hash),
writable: false,
enumerable: false,
configurable: true,
},
hash,
),
],
hash,
),
hash,
),
]
: EMPTY,
prototype_kind !== "none"
? [
makeExpressionEffect(
makeApplyExpression(
makeIntrinsicExpression("Reflect.defineProperty", hash),
makeIntrinsicExpression("undefined", hash),
[
makeReadCacheExpression(self, hash),
makePrimitiveExpression("prototype", hash),
makeDataDescriptorExpression(
{
value: makePrototypeExpression(
hash,
prototype_kind,
self,
),
writable: closure.type !== "constructor",
enumerable: false,
configurable: false,
},
hash,
),
],
hash,
),
hash,
),
]
: EMPTY,
),
makeReadCacheExpression(self, hash),
hash,
),
);
};