mirror of
https://github.com/morluto/rea
synced 2026-10-27 22:45:19 -04:00
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
This commit is contained in:
@@ -0,0 +1,135 @@
|
||||
import * as t from "@babel/types";
|
||||
|
||||
import {
|
||||
isNullishJavaScriptSyntax,
|
||||
unwrapJavaScriptExpression,
|
||||
} from "./javascriptAstValues.js";
|
||||
import {
|
||||
isErasedJavaScriptDeclaration,
|
||||
isUnmodeledJavaScriptRuntimeScope,
|
||||
} from "./javascriptModuleDeclaration.js";
|
||||
import { semanticBindingAtRead } from "./javascriptSemanticMutationInitializers.js";
|
||||
import type { JavaScriptSemanticReference } from "./javascriptSemanticIr.js";
|
||||
import { SEMANTIC_EXPRESSION_DEPTH_LIMIT } from "./javascriptSemanticResourceLimits.js";
|
||||
import {
|
||||
isUnshadowedGlobal,
|
||||
resolveSemanticBindingState,
|
||||
semanticStableInitializer,
|
||||
type JavaScriptSemanticAnalysisState,
|
||||
} from "./javascriptSemanticState.js";
|
||||
import { traverseJavaScriptAst } from "./javascriptSemanticTraversal.js";
|
||||
import { javaScriptRuntimeContext } from "./javascriptSourceParser.js";
|
||||
|
||||
type Shape = "array" | "callback" | "unknown";
|
||||
const commonJsWrapperParameterNames = new Set([
|
||||
"exports",
|
||||
"require",
|
||||
"module",
|
||||
"__filename",
|
||||
"__dirname",
|
||||
]);
|
||||
|
||||
/** Bounded child-argument identity only; never recover named object contents. */
|
||||
export const createSemanticExpressionShapeResolver = (
|
||||
state: JavaScriptSemanticAnalysisState,
|
||||
references: readonly JavaScriptSemanticReference[],
|
||||
) => {
|
||||
const hoistedFunction = (node: t.Node): boolean => {
|
||||
const parent = state.parentsByNode.get(node);
|
||||
return (
|
||||
t.isFunctionDeclaration(node) &&
|
||||
(t.isProgram(parent) ||
|
||||
(t.isBlockStatement(parent) &&
|
||||
t.isFunction(state.parentsByNode.get(parent))))
|
||||
);
|
||||
};
|
||||
// Most modules have no named child arguments. Collect source-wide blockers
|
||||
// only when a query actually needs a named binding or ambient undefined.
|
||||
let blocked: boolean | undefined;
|
||||
const blockFunctionNames = new Set<string>();
|
||||
const namedProofBlocked = (): boolean => {
|
||||
if (blocked !== undefined) return blocked;
|
||||
blocked = references.some(({ name }) => name === "eval");
|
||||
if (!blocked)
|
||||
traverseJavaScriptAst(state.program, {
|
||||
enter(node) {
|
||||
blocked ||=
|
||||
isErasedJavaScriptDeclaration(node) ||
|
||||
isUnmodeledJavaScriptRuntimeScope(node);
|
||||
if (
|
||||
javaScriptRuntimeContext(state.program) !== "module" &&
|
||||
t.isFunctionDeclaration(node) &&
|
||||
node.id != null &&
|
||||
!hoistedFunction(node)
|
||||
)
|
||||
blockFunctionNames.add(node.id.name);
|
||||
},
|
||||
});
|
||||
return blocked;
|
||||
};
|
||||
const shape = (input: t.Node): Shape => {
|
||||
const seen = new Set<t.Node>();
|
||||
let node = input;
|
||||
let depth = 0;
|
||||
while (true) {
|
||||
const unwrapped = unwrapJavaScriptExpression(node);
|
||||
node = unwrapped.node;
|
||||
depth += unwrapped.depth;
|
||||
if (depth > SEMANTIC_EXPRESSION_DEPTH_LIMIT) {
|
||||
state.valueResourceLimits.add("expression-depth");
|
||||
return "unknown";
|
||||
}
|
||||
if (seen.has(node)) return "unknown";
|
||||
seen.add(node);
|
||||
if (t.isArrayExpression(node)) return "array";
|
||||
if (t.isFunction(node) && !("declare" in node && node.declare))
|
||||
return "callback";
|
||||
if (!t.isIdentifier(node)) return "unknown";
|
||||
const binding = resolveSemanticBindingState(state, node, node.name);
|
||||
if (binding === undefined || binding.referenceInitializers.length > 0)
|
||||
return "unknown";
|
||||
if (state.scopesById.get(binding.scopeId)?.kind === "program") {
|
||||
const context = javaScriptRuntimeContext(state.program);
|
||||
// Unknown scripts expose globals; wrapper parameters can alias arguments.
|
||||
// Neither environment has a proven stable local initializer here.
|
||||
if (
|
||||
context === "unknown" ||
|
||||
(context === "commonjs" &&
|
||||
commonJsWrapperParameterNames.has(binding.name))
|
||||
)
|
||||
return "unknown";
|
||||
}
|
||||
// Annex B may copy a block function into an outer var without an indexed
|
||||
// assignment. Keep same-named vars unknown rather than model that copy.
|
||||
if (
|
||||
namedProofBlocked() ||
|
||||
(binding.declarationKinds.has("var") &&
|
||||
blockFunctionNames.has(binding.name))
|
||||
)
|
||||
return "unknown";
|
||||
const initializer = semanticStableInitializer(binding, state);
|
||||
if (initializer === undefined || initializer.projection.length > 0)
|
||||
return "unknown";
|
||||
const read = semanticBindingAtRead(binding, node, state.parentsByNode);
|
||||
// Hoisted function declarations are the only accepted unordered source.
|
||||
if (
|
||||
!hoistedFunction(initializer.node) &&
|
||||
(read.status !== "ordered" || !read.hasDefiniteInitializer)
|
||||
)
|
||||
return "unknown";
|
||||
node = initializer.node;
|
||||
depth++;
|
||||
}
|
||||
};
|
||||
return {
|
||||
isArray: (node: t.Node) => shape(node) === "array",
|
||||
isCallback: (node: t.Node) => shape(node) === "callback",
|
||||
isNullish: (input: t.Node) => {
|
||||
const { node } = unwrapJavaScriptExpression(input);
|
||||
return (
|
||||
isNullishJavaScriptSyntax(node) ||
|
||||
(isUnshadowedGlobal(node, state, "undefined") && !namedProofBlocked())
|
||||
);
|
||||
},
|
||||
};
|
||||
};
|
||||
Reference in New Issue
Block a user