mirror of
https://github.com/bendtherules/ask262.git
synced 2026-08-19 00:31:06 +00:00
Merge commit '6e92d2345e8231a44556ce7d416451f5f3e6c398' as 'engine262'
This commit is contained in:
@@ -0,0 +1,631 @@
|
||||
import type { Protocol } from 'devtools-protocol';
|
||||
import {
|
||||
DynamicParsedCodeRecord, type GCMarker, surroundingAgent,
|
||||
} from './host-defined/engine.mts';
|
||||
import { ExecutionContext } from './execution-context/ExecutionContext.mts';
|
||||
import {
|
||||
Value, JSStringValue, ObjectValue, UndefinedValue, NullValue, type PropertyKeyValue,
|
||||
SymbolValue,
|
||||
} from './value.mts';
|
||||
import { Q } from './completion.mts';
|
||||
import type { ParseNode } from './parser/ParseNode.mts';
|
||||
import type {
|
||||
Evaluator, EvaluatorNextType, ValueEvaluator, YieldEvaluator,
|
||||
} from './evaluator.mts';
|
||||
import type { ErrorObject } from './intrinsics/Error.mts';
|
||||
import {
|
||||
Call,
|
||||
isFunctionObject,
|
||||
isBuiltinFunctionObject,
|
||||
isECMAScriptFunctionObject,
|
||||
} from '#self';
|
||||
|
||||
export const kInternal = Symbol('kInternal');
|
||||
|
||||
export class JSStringMap<V> implements Map<JSStringValue, V> {
|
||||
#map = new Map<string, V>();
|
||||
|
||||
clear() {
|
||||
this.#map.clear();
|
||||
}
|
||||
|
||||
delete(key: JSStringValue | string) {
|
||||
if (key instanceof JSStringValue) {
|
||||
key = key.stringValue();
|
||||
}
|
||||
return this.#map.delete(key);
|
||||
}
|
||||
|
||||
forEach(callbackfn: (value: V, key: JSStringValue, map: Map<JSStringValue, V>) => void, thisArg?: JSStringMap<V>) {
|
||||
this.#map.forEach((value, key) => Reflect.apply(callbackfn, thisArg, [value, typeof key === 'string' ? Value(key) : key, this]));
|
||||
}
|
||||
|
||||
get(key: JSStringValue | string) {
|
||||
if (key instanceof JSStringValue) {
|
||||
key = key.stringValue();
|
||||
}
|
||||
return this.#map.get(key);
|
||||
}
|
||||
|
||||
has(key: JSStringValue | string) {
|
||||
if (key instanceof JSStringValue) {
|
||||
key = key.stringValue();
|
||||
}
|
||||
return this.#map.has(key);
|
||||
}
|
||||
|
||||
set(key: JSStringValue | string, value: V): this {
|
||||
if (key instanceof JSStringValue) {
|
||||
key = key.stringValue();
|
||||
}
|
||||
this.#map.set(key, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
get size() {
|
||||
return this.#map.size;
|
||||
}
|
||||
|
||||
* entries() {
|
||||
for (const [key, value] of this.#map.entries()) {
|
||||
yield [Value(key), value] as [JSStringValue, V];
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
* keys() {
|
||||
for (const key of this.#map.keys()) {
|
||||
yield Value(key);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
values() {
|
||||
return this.#map.values();
|
||||
}
|
||||
|
||||
declare [Symbol.iterator]: () => MapIterator<[JSStringValue, V]>;
|
||||
|
||||
declare [Symbol.toStringTag]: string;
|
||||
|
||||
static {
|
||||
JSStringMap.prototype[Symbol.toStringTag] = 'JSStringMap';
|
||||
JSStringMap.prototype[Symbol.iterator] = JSStringMap.prototype.entries;
|
||||
}
|
||||
|
||||
mark(m: GCMarker) {
|
||||
for (const [k, v] of this.#map.entries()) {
|
||||
m(k);
|
||||
m(v);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class PropertyKeyMap<V> implements Map<PropertyKeyValue, V> {
|
||||
#map = new Map<string | SymbolValue, V>();
|
||||
|
||||
clear() {
|
||||
this.#map.clear();
|
||||
}
|
||||
|
||||
delete(key: PropertyKeyValue | string) {
|
||||
if (key instanceof JSStringValue) {
|
||||
key = key.stringValue();
|
||||
}
|
||||
return this.#map.delete(key);
|
||||
}
|
||||
|
||||
forEach(callbackfn: (value: V, key: PropertyKeyValue, map: Map<PropertyKeyValue, V>) => void, thisArg?: PropertyKeyMap<V>) {
|
||||
this.#map.forEach((value, key) => Reflect.apply(callbackfn, thisArg, [value, typeof key === 'string' ? Value(key) : key, this]));
|
||||
}
|
||||
|
||||
get(key: PropertyKeyValue | string) {
|
||||
if (key instanceof JSStringValue) {
|
||||
key = key.stringValue();
|
||||
}
|
||||
return this.#map.get(key);
|
||||
}
|
||||
|
||||
has(key: PropertyKeyValue | string) {
|
||||
if (key instanceof JSStringValue) {
|
||||
key = key.stringValue();
|
||||
}
|
||||
return this.#map.has(key);
|
||||
}
|
||||
|
||||
set(key: PropertyKeyValue | string, value: V): this {
|
||||
if (key instanceof JSStringValue) {
|
||||
key = key.stringValue();
|
||||
}
|
||||
this.#map.set(key, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
get size() {
|
||||
return this.#map.size;
|
||||
}
|
||||
|
||||
* entries() {
|
||||
for (const [key, value] of this.#map.entries()) {
|
||||
if (typeof key === 'string') {
|
||||
yield [Value(key), value] as [JSStringValue, V];
|
||||
} else {
|
||||
yield [key, value] as [SymbolValue, V];
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
* keys() {
|
||||
for (const key of this.#map.keys()) {
|
||||
if (typeof key === 'string') {
|
||||
yield Value(key);
|
||||
} else {
|
||||
yield key;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
* values() {
|
||||
for (const value of this.#map.values()) {
|
||||
yield value;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
declare [Symbol.iterator]: () => MapIterator<[PropertyKeyValue, V]>;
|
||||
|
||||
declare [Symbol.toStringTag]: string;
|
||||
|
||||
static {
|
||||
PropertyKeyMap.prototype[Symbol.toStringTag] = 'PropertyKeyMap';
|
||||
PropertyKeyMap.prototype[Symbol.iterator] = PropertyKeyMap.prototype.entries;
|
||||
}
|
||||
|
||||
mark(m: GCMarker) {
|
||||
for (const [k, v] of this.#map.entries()) {
|
||||
m(k);
|
||||
m(v);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class JSStringSet {
|
||||
#set = new Set<string>();
|
||||
|
||||
constructor(value?: Iterable<JSStringValue | string>) {
|
||||
if (value) {
|
||||
for (const item of value) {
|
||||
this.add(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
add(value: JSStringValue | string): this {
|
||||
this.#set.add(typeof value === 'string' ? value : value.stringValue());
|
||||
return this;
|
||||
}
|
||||
|
||||
clear(): void {
|
||||
this.#set.clear();
|
||||
}
|
||||
|
||||
delete(value: JSStringValue | string): boolean {
|
||||
return this.#set.delete(typeof value === 'string' ? value : value.stringValue());
|
||||
}
|
||||
|
||||
forEach(callbackfn: (value: JSStringValue, value2: JSStringValue, set: Set<JSStringValue>) => void, thisArg?: JSStringSet): void {
|
||||
for (const value of this.#set) {
|
||||
Reflect.apply(callbackfn, thisArg, [Value(value), Value(value), this]);
|
||||
}
|
||||
}
|
||||
|
||||
has(value: JSStringValue | NullValue | string): boolean {
|
||||
if (value instanceof NullValue) {
|
||||
return false;
|
||||
}
|
||||
return this.#set.has(typeof value === 'string' ? value : value.stringValue());
|
||||
}
|
||||
|
||||
get size() {
|
||||
return this.#set.size;
|
||||
}
|
||||
|
||||
* entries(): SetIterator<[JSStringValue, JSStringValue]> {
|
||||
for (const value of this.#set) {
|
||||
yield [Value(value), Value(value)];
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
declare keys: () => SetIterator<JSStringValue>;
|
||||
|
||||
* values() {
|
||||
for (const value of this.#set) {
|
||||
yield Value(value);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
declare [Symbol.iterator]: () => SetIterator<JSStringValue>;
|
||||
|
||||
declare [Symbol.toStringTag]: string;
|
||||
|
||||
static {
|
||||
JSStringSet.prototype[Symbol.toStringTag] = 'JSStringSet';
|
||||
JSStringSet.prototype[Symbol.iterator] = JSStringSet.prototype.values;
|
||||
JSStringSet.prototype.keys = JSStringSet.prototype.values;
|
||||
}
|
||||
|
||||
mark(_m: GCMarker) { }
|
||||
}
|
||||
|
||||
export class OutOfRange extends RangeError {
|
||||
/* node:coverage disable */
|
||||
declare cause: unknown;
|
||||
|
||||
detail: unknown;
|
||||
|
||||
constructor(fn: string, detail: unknown) {
|
||||
super(`${fn}() argument out of range`, { cause: detail });
|
||||
this.detail = detail;
|
||||
}
|
||||
}
|
||||
/* node:coverage enable */
|
||||
|
||||
export function skipDebugger<T>(iterator: Evaluator<T>, maxSteps = Infinity): T {
|
||||
let steps = 0;
|
||||
while (true) {
|
||||
const { done, value } = iterator.next({ type: 'debugger-resume', value: undefined });
|
||||
if (done) {
|
||||
return value;
|
||||
}
|
||||
/* node:coverage ignore next 4 */
|
||||
steps += 1;
|
||||
if (steps > maxSteps) {
|
||||
throw new RangeError('Max steps exceeded');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function* resume(context: ExecutionContext, completion: EvaluatorNextType): YieldEvaluator {
|
||||
let result;
|
||||
while (true) {
|
||||
result = context.codeEvaluationState!.next(completion);
|
||||
if (result.done) {
|
||||
return result.value;
|
||||
}
|
||||
const { value } = result;
|
||||
if (value.type === 'debugger' || value.type === 'potential-debugger') {
|
||||
completion = yield value;
|
||||
} else if (value.type === 'await' || value.type === 'async-generator-yield') {
|
||||
return Value.undefined;
|
||||
} else if (value.type === 'yield') {
|
||||
return value.value;
|
||||
} else {
|
||||
unreachable(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class CallSite {
|
||||
context: ExecutionContext;
|
||||
|
||||
lastNode: ParseNode | null = null;
|
||||
|
||||
nextNode: ParseNode | null = null;
|
||||
|
||||
lastCallNode: ParseNode.CallExpression | null = null;
|
||||
|
||||
inheritedLastCallNode: ParseNode.CallExpression | null = null;
|
||||
|
||||
constructCall = false;
|
||||
|
||||
constructor(context: ExecutionContext) {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
clone(context = this.context) {
|
||||
const c = new CallSite(context);
|
||||
c.lastNode = this.lastNode;
|
||||
c.lastCallNode = this.lastCallNode;
|
||||
c.inheritedLastCallNode = this.inheritedLastCallNode;
|
||||
c.constructCall = this.constructCall;
|
||||
return c;
|
||||
}
|
||||
|
||||
isTopLevel() {
|
||||
return this.context.Function === Value.null;
|
||||
}
|
||||
|
||||
isConstructCall() {
|
||||
return this.constructCall;
|
||||
}
|
||||
|
||||
isAsync() {
|
||||
if (!(this.context.Function instanceof NullValue) && isECMAScriptFunctionObject(this.context.Function) && this.context.Function.ECMAScriptCode) {
|
||||
const code = this.context.Function.ECMAScriptCode;
|
||||
return code.type === 'AsyncBody' || code.type === 'AsyncGeneratorBody';
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
isNative() {
|
||||
return isBuiltinFunctionObject(this.context.Function);
|
||||
}
|
||||
|
||||
getFunctionName(): string | null {
|
||||
if (isFunctionObject(this.context.Function)) {
|
||||
const name = this.context.Function.properties.get('name');
|
||||
if (name && name.Value && name.Value instanceof JSStringValue) {
|
||||
return name.Value.stringValue();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
getSpecifier() {
|
||||
if (this.context.HostDefined?.scriptId && surroundingAgent.parsedSources.get(this.context.HostDefined.scriptId) instanceof DynamicParsedCodeRecord) {
|
||||
return null;
|
||||
}
|
||||
if (!(this.context.ScriptOrModule instanceof NullValue)) {
|
||||
return this.context.ScriptOrModule.HostDefined.specifier;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
getScriptId() {
|
||||
const context = this.context.HostDefined?.scriptId;
|
||||
if (context) {
|
||||
return context;
|
||||
}
|
||||
if (!(this.context.ScriptOrModule instanceof NullValue)) {
|
||||
return this.context.ScriptOrModule.HostDefined.scriptId;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
setLocation(node: ParseNode) {
|
||||
this.lastNode = node;
|
||||
}
|
||||
|
||||
setNextLocation(node: ParseNode) {
|
||||
this.nextNode = node;
|
||||
}
|
||||
|
||||
setCallLocation(node: ParseNode.CallExpression | null) {
|
||||
this.lastCallNode = node;
|
||||
}
|
||||
|
||||
get lineNumber() {
|
||||
if (this.lastNode) {
|
||||
return this.lastNode.location.start.line;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
get columnNumber() {
|
||||
if (this.lastNode) {
|
||||
return this.lastNode.location.start.column;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
loc() {
|
||||
if (this.isNative()) {
|
||||
return 'native';
|
||||
}
|
||||
let out = '';
|
||||
const specifier = this.getSpecifier();
|
||||
if (specifier) {
|
||||
out += specifier;
|
||||
} else {
|
||||
out += '<anonymous>';
|
||||
}
|
||||
if (this.lineNumber !== null) {
|
||||
out += `:${this.lineNumber}`;
|
||||
if (this.columnNumber !== null) {
|
||||
out += `:${this.columnNumber}`;
|
||||
}
|
||||
}
|
||||
return out.trim();
|
||||
}
|
||||
|
||||
toString() {
|
||||
const isAsync = this.isAsync();
|
||||
const functionName = this.getFunctionName();
|
||||
const isConstructCall = this.isConstructCall();
|
||||
const isMethodCall = !isConstructCall && !this.isTopLevel();
|
||||
|
||||
let visualFunctionName;
|
||||
if (this.inheritedLastCallNode?.CallExpression.type === 'IdentifierReference') {
|
||||
visualFunctionName = this.inheritedLastCallNode.CallExpression.name;
|
||||
}
|
||||
if (visualFunctionName === functionName) {
|
||||
visualFunctionName = undefined;
|
||||
}
|
||||
|
||||
let string = isAsync ? 'async ' : '';
|
||||
|
||||
if (isConstructCall) {
|
||||
string += 'new ';
|
||||
}
|
||||
|
||||
if (isMethodCall || isConstructCall) {
|
||||
if (functionName) {
|
||||
string += functionName;
|
||||
} else {
|
||||
string += '<anonymous>';
|
||||
}
|
||||
if (visualFunctionName) {
|
||||
string += ` (as ${visualFunctionName})`;
|
||||
}
|
||||
} else if (functionName) {
|
||||
string += functionName;
|
||||
if (visualFunctionName) {
|
||||
string += ` (as ${visualFunctionName})`;
|
||||
}
|
||||
} else {
|
||||
return `${string}${this.loc()}`;
|
||||
}
|
||||
|
||||
return `${string} (${this.loc()})`;
|
||||
}
|
||||
|
||||
toCallFrame(): Protocol.Runtime.CallFrame | undefined {
|
||||
const source = this.getScriptId();
|
||||
if (source === undefined || source === null) {
|
||||
return undefined;
|
||||
}
|
||||
return {
|
||||
columnNumber: (this.columnNumber || 1) - 1,
|
||||
lineNumber: (this.lineNumber || 1) - 1,
|
||||
functionName: this.getFunctionName() || '<anonymous>',
|
||||
scriptId: source,
|
||||
url: this.getSpecifier() || '<anonymous>',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export class CallFrame {
|
||||
columnNumber: number | undefined;
|
||||
|
||||
lineNumber: number | undefined;
|
||||
|
||||
functionName: string | undefined;
|
||||
|
||||
scriptId: string | undefined;
|
||||
|
||||
url: string | undefined;
|
||||
|
||||
toCallFrame(): Protocol.Runtime.CallFrame | undefined {
|
||||
if (!this.scriptId) {
|
||||
return undefined;
|
||||
}
|
||||
return {
|
||||
columnNumber: (this.columnNumber || 1) - 1,
|
||||
lineNumber: (this.lineNumber || 1) - 1,
|
||||
functionName: this.functionName || '<anonymous>',
|
||||
scriptId: this.scriptId,
|
||||
url: this.url || '<anonymous>',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export const kAsyncContext = Symbol('kAsyncContext');
|
||||
|
||||
function captureAsyncStack(stack: CallSite[]) {
|
||||
let promise = stack[0].context.promiseCapability!.Promise;
|
||||
for (let i = 0; i < 10; i += 1) {
|
||||
if (promise.PromiseFulfillReactions!.length !== 1) {
|
||||
return;
|
||||
}
|
||||
const [reaction] = promise.PromiseFulfillReactions!;
|
||||
if (reaction.Handler && reaction.Handler.Callback[kAsyncContext]) {
|
||||
const asyncContext = reaction.Handler.Callback[kAsyncContext];
|
||||
stack.push(asyncContext.callSite.clone());
|
||||
if ('PromiseState' in asyncContext.promiseCapability!.Promise) {
|
||||
promise = asyncContext.promiseCapability!.Promise;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
} else if (!(reaction.Capability instanceof UndefinedValue)) {
|
||||
if ('PromiseState' in reaction.Capability.Promise) {
|
||||
promise = reaction.Capability.Promise;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function getHostDefinedErrorStack(O: Value): (CallSite | CallFrame)[] | undefined {
|
||||
if (O instanceof ObjectValue && 'HostDefinedErrorStack' in O && isArray((O as ErrorObject).HostDefinedErrorStack)) {
|
||||
return (O as ErrorObject).HostDefinedErrorStack as (CallSite | CallFrame)[];
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export function getCurrentStack(excludeGlobalStack = true) {
|
||||
const stack: CallSite[] = [];
|
||||
for (let i = surroundingAgent.executionContextStack.length - (excludeGlobalStack ? 2 : 1); i >= 0; i -= 1) {
|
||||
const e = surroundingAgent.executionContextStack[i];
|
||||
if (e.VariableEnvironment === undefined && e.Function === Value.null) {
|
||||
break;
|
||||
}
|
||||
const clone = e.callSite.clone();
|
||||
const parent = stack[stack.length - 1];
|
||||
if (parent && !parent.context.poppedForTailCall) {
|
||||
parent.inheritedLastCallNode = clone.lastCallNode;
|
||||
}
|
||||
stack.push(clone);
|
||||
if (e.callSite.isAsync()) {
|
||||
i -= 1; // skip original execution context which has no useful information.
|
||||
}
|
||||
}
|
||||
|
||||
if (stack.length > 0 && stack[0].context.promiseCapability) {
|
||||
captureAsyncStack(stack);
|
||||
}
|
||||
return stack;
|
||||
}
|
||||
|
||||
export function captureStack() {
|
||||
const stack = getCurrentStack();
|
||||
|
||||
let nativeStack: string | undefined;
|
||||
if (surroundingAgent.hostDefinedOptions.errorStackAttachNativeStack) {
|
||||
const origStackTraceLimit = Error.stackTraceLimit;
|
||||
Error.stackTraceLimit = 12;
|
||||
try {
|
||||
nativeStack = new Error().stack;
|
||||
} finally {
|
||||
Error.stackTraceLimit = origStackTraceLimit;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
stack,
|
||||
nativeStack,
|
||||
};
|
||||
}
|
||||
|
||||
export function* callSiteToErrorString(O: ErrorObject, stack: readonly CallSite[], nativeStack?: string): ValueEvaluator<JSStringValue> {
|
||||
const errorString = (Q(yield* Call(surroundingAgent.intrinsic('%Error.prototype.toString%'), O)) as JSStringValue).stringValue();
|
||||
const errorStack = callSiteToErrorStack(stack, nativeStack);
|
||||
return Value(errorString + errorStack);
|
||||
}
|
||||
|
||||
export function callSiteToErrorStack(stack: readonly CallSite[], nativeStack: string | undefined) {
|
||||
let errorString = '';
|
||||
stack.forEach((s) => {
|
||||
errorString = `${errorString}\n at ${s.toString()}`;
|
||||
});
|
||||
if (typeof nativeStack === 'string') {
|
||||
errorString = `${errorString}\n <NATIVE>\n${nativeStack.split('\n').slice(6).join('\n')}`;
|
||||
}
|
||||
return errorString;
|
||||
}
|
||||
|
||||
export function callable<Class extends object>(
|
||||
onCalled = (target: Class, _thisArg: unknown, args: unknown[]) => Reflect.construct(target as new (...args: unknown[]) => unknown, args),
|
||||
) {
|
||||
const handler: ProxyHandler<Class> = Object.freeze({
|
||||
__proto__: null,
|
||||
apply: onCalled,
|
||||
});
|
||||
return function decorator(classValue: Class, _classContext: ClassDecoratorContext<Class & (new (...args: readonly unknown[]) => unknown)>) {
|
||||
return new Proxy(classValue, handler);
|
||||
};
|
||||
}
|
||||
|
||||
export type Mutable<T> = {
|
||||
-readonly [P in keyof T]: T[P];
|
||||
}
|
||||
|
||||
export const isArray: (arg: unknown) => arg is readonly unknown[] = Array.isArray;
|
||||
export function unreachable(_: never): never {
|
||||
throw new Error('Unreachable');
|
||||
}
|
||||
export function __ts_cast__<T>(_value: unknown): asserts _value is T { }
|
||||
Reference in New Issue
Block a user