Files
rea/src/domain/javascript/electronStaticAnalysisValues.ts
T
星闌 de9371b85d fix(javascript): preserve effective child-process stdio options (#4336)
* fix(javascript): preserve effective child-process stdio options

* fix(javascript): retain proven callbacks and primitive stdio keys

* fix(javascript): preserve argv shapes and nullish spreads

* fix(javascript): bound callable proofs and retain exposed binding uncertainty

* test(javascript): keep callback acceptance within transport budget

* test(javascript): inspect application coverage for callback proof limits

* fix(javascript): keep spread command argument slots uncertain

* fix(javascript): bound mapped argument content proofs

* fix(javascript): distinguish erased declarations from runtime shadows

* fix(javascript): prove runtime module context before binding isolation

* fix(javascript): share erased declaration and opaque scope proofs

* fix(javascript): keep lowered enum scopes opaque

* fix(javascript): narrow stdio proofs and consolidate regressions

* perf(javascript): reuse lexical references for child shapes

* fix(javascript): retain CommonJS child argument context

* fix(javascript): keep Annex B affected child arguments unknown

* fix(javascript): recognize value import-equals runtime modules

* fix(javascript): retain exact stdio through module and primitive syntax

* fix(javascript): bound inline spread property resolution

* fix(javascript): distinguish copied methods from getters
2026-10-17 15:51:55 +08:00

168 lines
5.0 KiB
TypeScript

import * as t from "@babel/types";
import { compareUnicodeCodePoints } from "../unicodeCodePointOrder.js";
import type { ElectronStaticValue } from "./electronStaticAnalysisTypes.js";
import {
readExactJavaScriptLiteral,
semanticStaticPropertyKey,
semanticStaticPropertyName,
} from "./javascriptAstValues.js";
import { sourceSlice } from "./javascriptStaticAnalysisHelpers.js";
import type { JavaScriptFindingContext } from "./javascriptStaticAnalysisState.js";
/** Resolve a callee only when its root has a proven Electron module identity. */
export const electronCalleePath = (
callee: t.Node,
context: JavaScriptFindingContext,
): readonly string[] | undefined => {
const members: string[] = [];
let root = callee;
while (t.isMemberExpression(root) || t.isOptionalMemberExpression(root)) {
const member = semanticStaticPropertyName(root.property, root.computed);
if (member === "" || !t.isNode(root.object)) return undefined;
members.push(member);
root = root.object;
}
const exported = context.electronBindings?.get(root.start ?? -1);
return exported === undefined
? undefined
: [...exported, ...members.toReversed()];
};
/** Preserve one literal value or the exact inert expression. */
export const electronStaticValue = (
source: string,
node: t.Node | null | undefined,
): ElectronStaticValue => {
const literal = literalValue(node);
return literal.found
? { status: "literal", value: literal.value, expression: null }
: {
status: "dynamic",
value: null,
expression: boundedExpression(source, node),
};
};
/** Return an actionable expression without evaluating it. */
export const boundedExpression = (
source: string,
node: t.Node | null | undefined,
): string => {
if (node === null || node === undefined) return "[missing-expression]";
const expression = sourceSlice(source, node).trim();
return expression === "" ? `[${node.type}]` : expression;
};
type PresentHandlerKind =
| "inline-function"
| "identifier"
| "member-expression"
| "dynamic-expression";
/** Classify a handler argument while retaining its exact source range. */
export function handlerKind(node: t.Node): PresentHandlerKind;
export function handlerKind(node: null | undefined): "missing";
export function handlerKind(
node: t.Node | null | undefined,
): PresentHandlerKind | "missing" {
if (node === null || node === undefined) return "missing";
if (
t.isArrowFunctionExpression(node) ||
t.isFunctionExpression(node) ||
t.isFunctionDeclaration(node)
)
return "inline-function";
if (t.isIdentifier(node)) return "identifier";
if (t.isMemberExpression(node) || t.isOptionalMemberExpression(node))
return "member-expression";
return "dynamic-expression";
}
/** Collect statically visible dotted keys from one literal contextBridge API object. */
export const collectContextBridgeMembers = (
node: t.Node | null | undefined,
): {
readonly status: "object-literal" | "dynamic" | "missing";
readonly members: readonly string[];
readonly unknown: number;
} => {
if (node === null || node === undefined)
return {
status: "missing",
members: [],
unknown: 0,
};
if (!t.isObjectExpression(node))
return {
status: "dynamic",
members: [],
unknown: 1,
};
const state: { members: string[]; unknown: number } = {
members: [],
unknown: 0,
};
collectMembersAt(node, [], state);
const members = [...new Set(state.members)].sort(compareUnicodeCodePoints);
return {
status: "object-literal",
members,
unknown: state.unknown,
};
};
const collectMembersAt = (
object: t.ObjectExpression,
prefix: readonly string[],
state: { members: string[]; unknown: number },
): void => {
if (prefix.length >= 8) {
state.unknown += 1;
return;
}
for (const property of object.properties) {
if (t.isSpreadElement(property)) {
state.unknown += 1;
continue;
}
const name = semanticStaticPropertyKey(property.key, property.computed);
if (name === null) {
state.unknown += 1;
continue;
}
if (
t.isObjectProperty(property) &&
!property.computed &&
!property.shorthand &&
name === "__proto__"
)
continue;
const segments = [...prefix, name];
const path = segments.join(".");
state.members.push(path);
if (t.isObjectProperty(property) && t.isObjectExpression(property.value))
collectMembersAt(property.value, segments, state);
}
};
const literalValue = (
node: t.Node | null | undefined,
):
| { readonly found: true; readonly value: string | number | boolean | null }
| { readonly found: false } => {
if (node === null || node === undefined) return { found: false };
const literal = readExactJavaScriptLiteral(node);
if (literal.found) return literal;
if (
t.isUnaryExpression(node, { operator: "-" }) &&
t.isNumericLiteral(node.argument)
) {
const value = -node.argument.value;
return Number.isFinite(value) && !Object.is(value, -0)
? { found: true, value }
: { found: false };
}
return { found: false };
};