Files
rea/src/domain/javascript/javascriptBootstrap.ts
T
hhang 1ae9000587 fix(javascript): exclude TypeScript this annotations from runtime parameters (#2761)
* fix(javascript): exclude TypeScript this annotations from runtime parameters

* test(javascript): assert exact typed argument bindings

* test(apple): keep dyld slice regression portable

* fix(process): report no remaining time after a provider deadline fires

Node timers can fire while performance.now() is still a fraction of a
millisecond short of the monotonic deadline. ProviderStartupDeadline then
reported interruption "timeout" and an aborted signal while remainingMs()
returned ceil(0.x) = 1, so polling loops guarded by remainingMs() > 0
(Hopper and Ghidra startup) could run past an elapsed deadline, and the
real-timer test "aborts a bounded wait when its real monotonic deadline
expires" failed intermittently in CI (expected 1 to be 0).

Once the deadline's own timer has classified a timeout, report zero
remaining time. A deterministic regression test fires the timer with the
monotonic clock 0.4 ms short of the deadline.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(ci): restore descriptor and fixture validation

* test(ci): retain verification coverage within lint limits

* fix(test): share admitted Ghidra profile installation fixtures

* fix(ci): isolate timing report fixtures from workflow state

---------

Co-authored-by: hhang <169027046+xu91102@users.noreply.github.com>
Co-authored-by: Kaoru0822 <208281322+kaoru0822-kitauji@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: morluto <76467478+morluto@users.noreply.github.com>
2026-10-14 07:18:50 +08:00

154 lines
5.0 KiB
TypeScript

import * as t from "@babel/types";
import {
semanticStaticPropertyKey,
runtimeFunctionParameters,
} from "./javascriptAstValues.js";
import { moduleFactory } from "./javascriptStaticAnalysisHelpers.js";
import { traverseJavaScriptAst } from "./javascriptSemanticTraversal.js";
import {
resolveSemanticBindingState,
semanticStableInitializer,
type JavaScriptSemanticAnalysisState,
} from "./javascriptSemanticState.js";
/** A literal module table dispatched by a standalone CommonJS-style runtime. */
interface JavaScriptBootstrap {
readonly body: t.BlockStatement;
readonly table: t.ObjectExpression;
readonly loaderName: string;
readonly loaderBindingId: string;
readonly tableBindingId: string;
}
/** Recognize syntax, never invoke a bootstrap or infer it from variable names. */
export const javascriptBootstrapAt = (
call: t.CallExpression,
state: JavaScriptSemanticAnalysisState,
): JavaScriptBootstrap | undefined => {
if (
(!t.isFunctionExpression(call.callee) &&
!t.isArrowFunctionExpression(call.callee)) ||
!t.isBlockStatement(call.callee.body) ||
runtimeFunctionParameters(call.callee).length !== 0 ||
call.arguments.length !== 0
)
return undefined;
const body = call.callee.body;
const tables = new Map<string, t.ObjectExpression>();
for (const statement of body.body) {
if (!t.isVariableDeclaration(statement)) continue;
for (const declaration of statement.declarations) {
if (
t.isIdentifier(declaration.id) &&
t.isObjectExpression(declaration.init) &&
declaration.init.properties.length > 0 &&
declaration.init.properties.every(
(property) => moduleFactory(property) !== undefined,
)
) {
const binding = resolveSemanticBindingState(
state,
declaration.id,
declaration.id.name,
);
if (
binding !== undefined &&
semanticStableInitializer(binding, state)?.node === declaration.init
)
tables.set(binding.bindingId, declaration.init);
}
}
}
if (tables.size === 0) return undefined;
for (const loader of body.body) {
if (
!t.isFunctionDeclaration(loader) ||
loader.id === null ||
loader.id === undefined ||
runtimeFunctionParameters(loader).length !== 1
)
continue;
const parameter = runtimeFunctionParameters(loader)[0];
if (!t.isIdentifier(parameter)) continue;
const loaderName = loader.id.name;
const loaderBindingId = bindingId(state, loader.id);
const parameterBindingId = bindingId(state, parameter);
if (loaderBindingId === undefined || parameterBindingId === undefined)
continue;
let tableBindingId: string | undefined;
traverseJavaScriptAst(loader.body, {
enter: (node, _parent, ancestors) => {
if (
!t.isCallExpression(node) ||
ancestors().some((node) => t.isFunction(node))
)
return;
const candidate = dispatchedTableBinding(
node,
loaderBindingId,
parameterBindingId,
state,
);
if (candidate !== undefined && tables.has(candidate))
tableBindingId = candidate;
},
});
const table =
tableBindingId === undefined ? undefined : tables.get(tableBindingId);
if (table !== undefined && tableBindingId !== undefined)
return { body, table, loaderName, loaderBindingId, tableBindingId };
}
return undefined;
};
const dispatchedTableBinding = (
call: t.CallExpression,
loaderBindingId: string,
keyBindingId: string,
state: JavaScriptSemanticAnalysisState,
): string | undefined => {
const member = call.callee;
const viaCall =
t.isMemberExpression(member) &&
semanticStaticPropertyKey(member.property, member.computed) === "call";
const dispatch =
viaCall && t.isMemberExpression(member) ? member.object : member;
if (
!t.isMemberExpression(dispatch) ||
!dispatch.computed ||
!t.isIdentifier(dispatch.object) ||
!t.isIdentifier(dispatch.property) ||
bindingId(state, dispatch.property) !== keyBindingId
)
return undefined;
const args = viaCall ? call.arguments.slice(1) : call.arguments;
const [module, exports, require] = args;
if (
args.length !== 3 ||
!t.isIdentifier(module) ||
bindingId(state, module) === undefined ||
!t.isMemberExpression(exports) ||
!t.isIdentifier(exports.object) ||
bindingId(state, exports.object) !== bindingId(state, module) ||
semanticStaticPropertyKey(exports.property, exports.computed) !==
"exports" ||
!t.isIdentifier(require) ||
bindingId(state, require) !== loaderBindingId
)
return undefined;
if (
viaCall &&
(!t.isNode(call.arguments[0]) ||
!t.isNodesEquivalent(call.arguments[0], exports))
)
return undefined;
return bindingId(state, dispatch.object);
};
const bindingId = (
state: JavaScriptSemanticAnalysisState,
identifier: t.Identifier,
): string | undefined =>
resolveSemanticBindingState(state, identifier, identifier.name)?.bindingId;