mirror of
https://github.com/bendtherules/ask262.git
synced 2026-08-18 21:31:46 +00:00
356 lines
12 KiB
TypeScript
356 lines
12 KiB
TypeScript
import type { Protocol } from 'devtools-protocol';
|
|
import type {
|
|
DebuggerContext,
|
|
DebuggerNamespace, HeapProfilerNamespace, ProfilerNamespace, RuntimeNamespace,
|
|
TargetNamespace,
|
|
} from './types.mts';
|
|
import { getParsedEvent } from './internal-utils.mts';
|
|
import { InspectorContext } from './context.mts';
|
|
import {
|
|
Call, NormalCompletion, ObjectValue, ParseScript, runJobQueue, ScriptRecord, surroundingAgent, ThrowCompletion, skipDebugger, Value, type FunctionObject,
|
|
ParseModule,
|
|
SourceTextModuleRecord, performDevtoolsEval,
|
|
ValueOfNormalCompletion,
|
|
JSStringValue,
|
|
evalQ,
|
|
Assert,
|
|
kInternal,
|
|
captureStack,
|
|
} from '#self';
|
|
|
|
export const Debugger: DebuggerNamespace = {
|
|
enable(_req, { onDebuggerAttached }) {
|
|
onDebuggerAttached();
|
|
return { debuggerId: 'debugger.0' };
|
|
},
|
|
getScriptSource({ scriptId }) {
|
|
const source = surroundingAgent.parsedSources.get(scriptId);
|
|
if (!source) {
|
|
throw new Error('Not found');
|
|
}
|
|
return { scriptSource: source.ECMAScriptCode.sourceText };
|
|
},
|
|
setAsyncCallStackDepth() { },
|
|
setBlackboxPatterns() { },
|
|
setBlackboxExecutionContexts() { },
|
|
|
|
// #region breakpoints
|
|
getPossibleBreakpoints() {
|
|
// getPossibleBreakpoints({ start, end, restrictToFunction }) {
|
|
return { locations: [] };
|
|
// return { locations: getBreakpointCandidates(start, end, restrictToFunction) };
|
|
},
|
|
removeBreakpoint({ breakpointId }) {
|
|
surroundingAgent?.removeBreakpoint(breakpointId);
|
|
},
|
|
// setBreakpoint({ location, condition }) { },
|
|
setBreakpointByUrl(req) {
|
|
return surroundingAgent?.addBreakpointByUrl(req);
|
|
},
|
|
// setBreakpointOnFunctionCall({ objectId, condition }) { },
|
|
setBreakpointsActive({ active }) {
|
|
surroundingAgent.breakpointsEnabled = active;
|
|
},
|
|
// setInstrumentationBreakpoint({ instrumentation }) { },
|
|
setPauseOnExceptions({ state }) {
|
|
if (surroundingAgent) {
|
|
surroundingAgent.pauseOnExceptions = state === 'none' ? undefined : state;
|
|
}
|
|
},
|
|
// #endregion
|
|
|
|
stepInto(_, { sendEvent }) {
|
|
sendEvent['Debugger.resumed']();
|
|
surroundingAgent.resumeEvaluate({ pauseAt: 'step-in' });
|
|
},
|
|
resume(_, { sendEvent }) {
|
|
sendEvent['Debugger.resumed']();
|
|
surroundingAgent.resumeEvaluate();
|
|
},
|
|
stepOver(_req, { sendEvent }) {
|
|
sendEvent['Debugger.resumed']();
|
|
surroundingAgent.resumeEvaluate({ pauseAt: 'step-over' });
|
|
},
|
|
stepOut(_req, { sendEvent }) {
|
|
sendEvent['Debugger.resumed']();
|
|
surroundingAgent.resumeEvaluate({ pauseAt: 'step-out' });
|
|
},
|
|
evaluateOnCallFrame(req, context) {
|
|
return evaluate({
|
|
...req,
|
|
uniqueContextId: context.context.getRealm(undefined)!.descriptor.uniqueId,
|
|
evalMode: context.context.evaluateMode,
|
|
}, context);
|
|
},
|
|
engine262_setEvaluateMode({ mode }, { context }) {
|
|
if (mode === 'module' || mode === 'script' || mode === 'console') {
|
|
context.evaluateMode = mode;
|
|
}
|
|
},
|
|
engine262_setFeatures() {
|
|
throw new Error('Method should not be implemented here.');
|
|
},
|
|
};
|
|
export const Profiler: ProfilerNamespace = {
|
|
enable() { },
|
|
};
|
|
export const Runtime: RuntimeNamespace = {
|
|
discardConsoleEntries() { },
|
|
enable() {},
|
|
compileScript(options, { context, sendEvent }) {
|
|
let parsed!: ScriptRecord | SourceTextModuleRecord | ObjectValue[];
|
|
let realm = context.getRealm(options.executionContextId);
|
|
if (!realm && !options.persistScript) {
|
|
realm = context.getAnyRealm();
|
|
}
|
|
if (!realm) {
|
|
return unsupportedError;
|
|
}
|
|
realm.realm.scope(() => {
|
|
if (context.evaluateMode === 'module') {
|
|
parsed = ParseModule(options.expression, realm.realm, { specifier: options.sourceURL, doNotTrackScriptId: !options.persistScript });
|
|
} else {
|
|
parsed = ParseScript(options.expression, realm.realm, { specifier: options.sourceURL, doNotTrackScriptId: !options.persistScript, [kInternal]: { allowAllPrivateNames: true } });
|
|
}
|
|
});
|
|
if (!parsed) {
|
|
throw new Error('No parsed result');
|
|
}
|
|
if (Array.isArray(parsed)) {
|
|
const e = context.createExceptionDetails(ThrowCompletion(parsed[0]), false);
|
|
// Note: it has to be this message to trigger devtools' line wrap.
|
|
e.exception!.description = 'SyntaxError: Unexpected end of input';
|
|
return { exceptionDetails: e };
|
|
}
|
|
if (options.persistScript) {
|
|
if (realm?.descriptor.id === undefined) {
|
|
throw new Error('No realm id found');
|
|
}
|
|
const event = getParsedEvent(parsed, parsed.HostDefined.scriptId!, realm.descriptor.id);
|
|
sendEvent['Debugger.scriptParsed'](event);
|
|
return { scriptId: event.scriptId };
|
|
}
|
|
return {};
|
|
},
|
|
callFunctionOn(options, { context }): Protocol.Runtime.CallFunctionOnResponse {
|
|
const realmDesc = context.getRealm(options.uniqueContextId || options.executionContextId) || context.getAnyRealm();
|
|
if (!realmDesc) {
|
|
throw new Error('No realm found');
|
|
}
|
|
const { Value: F } = realmDesc.realm.evaluateScript(`(${options.functionDeclaration})`, { doNotTrackScriptId: true }) as NormalCompletion<FunctionObject>;
|
|
const thisValue = options.objectId
|
|
? context.getObject(options.objectId)!
|
|
: Value.undefined;
|
|
const args = options.arguments?.map((a) => {
|
|
// TODO: revisit
|
|
if ('value' in a) {
|
|
return Value(a.value);
|
|
}
|
|
if (a.objectId) {
|
|
return context.getObject(a.objectId)!;
|
|
}
|
|
if ('unserializableValue' in a) {
|
|
throw new RangeError();
|
|
}
|
|
return Value.undefined;
|
|
});
|
|
return realmDesc.realm.scope((): Protocol.Runtime.CallFunctionOnResponse => {
|
|
const completion = evalQ((Q, X): Protocol.Runtime.CallFunctionOnResponse => {
|
|
const r = Q(skipDebugger(Call(F, thisValue, args || [])));
|
|
if (options.returnByValue) {
|
|
const value = X(Call(realmDesc.realm.Intrinsics['%JSON.stringify%'], Value.undefined, [r]));
|
|
if (value instanceof JSStringValue) {
|
|
const valueRealized = JSON.parse(value.stringValue());
|
|
return { result: { type: typeof value, value: valueRealized } };
|
|
}
|
|
}
|
|
return context.createEvaluationResult(r);
|
|
});
|
|
if (completion instanceof ThrowCompletion) {
|
|
return { result: { type: 'undefined' }, exceptionDetails: context.createExceptionDetails(completion, false) };
|
|
}
|
|
return completion.Value;
|
|
});
|
|
},
|
|
evaluate(options, context) {
|
|
return evaluate({
|
|
...options,
|
|
evalMode: context.context.evaluateMode,
|
|
uniqueContextId: options.uniqueContextId!,
|
|
}, context);
|
|
},
|
|
getExceptionDetails(req, { context }) {
|
|
const object = context.getObject(req.errorObjectId)!;
|
|
if (object instanceof ObjectValue) {
|
|
return {
|
|
exceptionDetails: context.createExceptionDetails(ThrowCompletion(object), false),
|
|
};
|
|
}
|
|
return {
|
|
exceptionDetails: {
|
|
text: 'unsupported', lineNumber: 0, columnNumber: 0, exceptionId: 0,
|
|
},
|
|
};
|
|
},
|
|
getHeapUsage() {
|
|
return {
|
|
usedSize: 0, totalSize: 0, backingStorageSize: 0, embedderHeapUsedSize: 0,
|
|
};
|
|
},
|
|
getIsolateId() {
|
|
return { id: 'isolate.0' };
|
|
},
|
|
getProperties(options, { context }) {
|
|
return context.getProperties(options);
|
|
},
|
|
globalLexicalScopeNames({ executionContextId }, { context }) {
|
|
const global = context.getRealm(executionContextId)?.realm.GlobalObject;
|
|
if (!global) {
|
|
return { names: [] };
|
|
}
|
|
const keys = skipDebugger(global.OwnPropertyKeys());
|
|
if (keys instanceof ThrowCompletion) {
|
|
return { names: [] };
|
|
}
|
|
return { names: ValueOfNormalCompletion(keys).map((k) => (k instanceof JSStringValue ? k.stringValue() : null!)).filter(Boolean) };
|
|
},
|
|
releaseObject(req, { context }) {
|
|
context.releaseObject(req.objectId);
|
|
},
|
|
releaseObjectGroup({ objectGroup }, { context }) {
|
|
context.releaseObjectGroup(objectGroup);
|
|
},
|
|
runIfWaitingForDebugger() { },
|
|
};
|
|
export const HeapProfiler: HeapProfilerNamespace = {
|
|
enable() { },
|
|
collectGarbage() { },
|
|
};
|
|
|
|
export const Target: TargetNamespace = {
|
|
setDiscoverTargets() { },
|
|
// @ts-expect-error no doc
|
|
setRemoteLocations() { },
|
|
};
|
|
|
|
const unsupportedError: Protocol.Runtime.EvaluateResponse = {
|
|
result: { type: 'undefined' },
|
|
exceptionDetails: {
|
|
text: 'unsupported', lineNumber: 0, columnNumber: 0, exceptionId: 0,
|
|
},
|
|
};
|
|
function evaluate(options: {
|
|
uniqueContextId: string,
|
|
expression: string,
|
|
evalMode: InspectorContext['evaluateMode'],
|
|
throwOnSideEffect?: boolean,
|
|
awaitPromise?: boolean,
|
|
callFrameId?: string,
|
|
}, _context: DebuggerContext): Protocol.Runtime.EvaluateResponse | Promise<Protocol.Runtime.EvaluateResponse> {
|
|
const { context } = _context;
|
|
const isPreview = options.throwOnSideEffect;
|
|
if (options.awaitPromise) {
|
|
return unsupportedError;
|
|
}
|
|
const realm = context.getRealm(options.uniqueContextId);
|
|
if (!realm) {
|
|
return unsupportedError;
|
|
}
|
|
|
|
const isCallOnFrame = typeof options.callFrameId === 'string';
|
|
let callOnFramePoppedLevel = 0;
|
|
const oldExecutionStack = [...surroundingAgent.executionContextStack];
|
|
if (isCallOnFrame) {
|
|
const frame = surroundingAgent.executionContextStack[options.callFrameId as `${number}`];
|
|
if (!frame) {
|
|
// eslint-disable-next-line no-console
|
|
console.error('Execution context not found: ', options.callFrameId);
|
|
return unsupportedError;
|
|
}
|
|
for (const currentFrame of [...surroundingAgent.executionContextStack].reverse()) {
|
|
if (currentFrame === frame) {
|
|
break;
|
|
}
|
|
callOnFramePoppedLevel += 1;
|
|
surroundingAgent.executionContextStack.pop(currentFrame);
|
|
}
|
|
}
|
|
const promise = new Promise<Protocol.Runtime.EvaluateResponse>((resolve) => {
|
|
let toBeEvaluated;
|
|
if (isPreview || options.evalMode === 'console' || isCallOnFrame) {
|
|
toBeEvaluated = performDevtoolsEval(options.expression, realm.realm, false, !!(isPreview || isCallOnFrame));
|
|
} else {
|
|
let parsed!: ScriptRecord | SourceTextModuleRecord | ObjectValue[];
|
|
const realm = context.getRealm(options.uniqueContextId);
|
|
realm?.realm.scope(() => {
|
|
if (options.evalMode === 'module') {
|
|
parsed = ParseModule(options.expression, realm.realm);
|
|
} else {
|
|
parsed = ParseScript(options.expression, realm.realm);
|
|
}
|
|
});
|
|
if (Array.isArray(parsed)) {
|
|
const e = context.createExceptionDetails(ThrowCompletion(parsed[0]), false);
|
|
resolve({ exceptionDetails: e, result: { type: 'undefined' } });
|
|
return;
|
|
}
|
|
toBeEvaluated = parsed;
|
|
}
|
|
|
|
const noDebuggerEvaluate = () => {
|
|
if (!('next' in toBeEvaluated)) {
|
|
throw new Assert.Error('Unexpected');
|
|
}
|
|
resolve(context.createEvaluationResult(skipDebugger(toBeEvaluated)));
|
|
};
|
|
if (isPreview) {
|
|
surroundingAgent.debugger_scopePreview(noDebuggerEvaluate);
|
|
return;
|
|
}
|
|
if (isCallOnFrame) {
|
|
noDebuggerEvaluate();
|
|
return;
|
|
}
|
|
|
|
const completion = realm.realm.evaluate(toBeEvaluated, (completion) => {
|
|
resolve(context.createEvaluationResult(completion));
|
|
runJobQueue();
|
|
});
|
|
if (completion) {
|
|
return;
|
|
}
|
|
surroundingAgent.resumeEvaluate();
|
|
});
|
|
promise.then(() => {
|
|
if (callOnFramePoppedLevel) {
|
|
Assert(oldExecutionStack.length - callOnFramePoppedLevel === surroundingAgent.executionContextStack.length);
|
|
for (const [newIndex, newStack] of surroundingAgent.executionContextStack.entries()) {
|
|
Assert(newStack === oldExecutionStack[newIndex]);
|
|
}
|
|
surroundingAgent.executionContextStack.length = 0;
|
|
for (const stack of oldExecutionStack) {
|
|
surroundingAgent.executionContextStack.push(stack);
|
|
}
|
|
}
|
|
}, (err): Protocol.Runtime.EvaluateResponse => {
|
|
const expr = surroundingAgent.runningExecutionContext.callSite.lastNode?.sourceText;
|
|
const frame = InspectorContext.callSiteToCallFrame(captureStack().stack);
|
|
_context.sendEvent['Runtime.exceptionThrown']({
|
|
timestamp: Date.now(),
|
|
exceptionDetails: {
|
|
stackTrace: frame.length ? { callFrames: frame } : undefined,
|
|
text: `engine262 error when evaluating the following node:\n\n ${expr}\n\n${err.constructor.name}: ${err.message}\n${err.stack.slice(err.stack.indexOf(err.message) + err.message.length + 1)}\n\nFrom now on, the engine262 VM state is broken, please press the reload button.`,
|
|
columnNumber: frame[0]?.columnNumber,
|
|
lineNumber: frame[0]?.lineNumber,
|
|
scriptId: frame[0]?.scriptId,
|
|
url: frame[0]?.url,
|
|
exceptionId: 0,
|
|
},
|
|
});
|
|
return {
|
|
result: { type: 'undefined' },
|
|
};
|
|
});
|
|
return promise;
|
|
}
|