import * as t from "@babel/types"; import { javascriptBootstrapAt } from "./javascriptBootstrap.js"; import { proveJavaScriptBootstrapIntegrity } from "./javascriptBootstrapIntegrity.js"; import { semanticMemberChain, semanticStaticPropertyKey, unwrapJavaScriptExpression, } from "./javascriptAstValues.js"; import { collectJavaScriptIdentityWritesSteps } from "./javascriptElectronMemberWrites.js"; import { semanticBindingAtRead } from "./javascriptSemanticMutationInitializers.js"; import { SEMANTIC_ALIAS_SELECTION_LIMIT } from "./javascriptSemanticResourceLimits.js"; import { createSemanticModuleInterop } from "./javascriptModuleInterop.js"; import { semanticRequireOrigin, type JavaScriptRequireOrigin, } from "./javascriptSemanticRequireOrigin.js"; import { resolveSemanticBindingState, semanticResolutionBlocked, type JavaScriptSemanticAnalysisState, } from "./javascriptSemanticState.js"; import { argumentValue, moduleFactory, modulePropertyName, } from "./javascriptStaticAnalysisHelpers.js"; import { completeSemanticSteps, traverseJavaScriptAst, } from "./javascriptSemanticTraversal.js"; /** Loader bindings and literal externals proven for one standalone bootstrap. */ interface BootstrapExternals { /** The runtime loader and every factory parameter it is passed as. */ readonly loaderBindingIds: ReadonlySet; /** Loader bindings with the canonical n.n getter installed before dispatch. */ readonly interopGetterBindingIds: ReadonlySet; /** Table keys whose factory only re-exports one unshadowed literal require; null when a key repeats. */ readonly externals: ReadonlyMap; } const proofs = new WeakMap< JavaScriptSemanticAnalysisState, WeakMap >(); /** * Recover a literal require reached through a proven Webpack bootstrap. * * Webpack emits externals as `{45840: e => { e.exports = require("electron") }}` * and consumers call the runtime loader, `n(45840)`, instead of `require`. The * origin is accepted only when the loader binding is the bootstrap's own loader * or a factory parameter that the bootstrap proves it passes the loader as, and * the keyed factory consists solely of that re-export. */ export const semanticBundledRequireOrigin = ( node: t.Node | null | undefined, state: JavaScriptSemanticAnalysisState, ): JavaScriptRequireOrigin | undefined => { const chain = semanticMemberChain(node); const current = chain?.root; if ( chain?.members.length === 0 && t.isCallExpression(current) && t.isMemberExpression(current.callee) && semanticStaticPropertyKey( current.callee.property, current.callee.computed, ) === "n" && t.isIdentifier(current.callee.object) && current.arguments.length === 1 ) { // Webpack's `n.n(module)` returns its default-export getter. For a // CommonJS external, invoking that getter yields the exact exports object; // bind its identity to the proven registry result while retaining the // nested loader call as the source of provenance. const nested = semanticBundledRequireOrigin(current.arguments[0], state); const loaderBinding = resolveSemanticBindingState( state, current.callee.object, current.callee.object.name, ); const proof = nested === undefined || loaderBinding === undefined ? undefined : bootstrapProofForLoaderCall( nested.call, loaderBinding.bindingId, state, ); const nestedCallee = nested?.call.callee; const loaderBindingId = loaderBinding?.bindingId; if ( nested !== undefined && nested.origin.importedPath.length === 0 && t.isIdentifier(nestedCallee) && loaderBindingId !== undefined && loaderBindingId === resolveSemanticBindingState(state, nestedCallee, nestedCallee.name) ?.bindingId && proof?.loaderBindingIds.has(loaderBindingId) === true && proof.interopGetterBindingIds.has(loaderBindingId) ) return nested; } if ( chain === undefined || !t.isCallExpression(current) || !t.isIdentifier(current.callee) || current.arguments.length !== 1 ) return undefined; const key = argumentValue(current.arguments[0]); if (key === undefined) return undefined; const loader = current.callee; if (semanticResolutionBlocked(state, loader, loader.name)) return undefined; const binding = resolveSemanticBindingState(state, loader, loader.name); if ( binding === undefined || binding.definitions.length !== 1 || binding.definitions[0]?.kind === "assignment" ) return undefined; for ( let ancestor = state.parentsByNode.get(current); ancestor !== undefined; ancestor = state.parentsByNode.get(ancestor) ) { if (!t.isCallExpression(ancestor)) continue; const proof = bootstrapExternals(ancestor, state); if (proof === null || !proof.loaderBindingIds.has(binding.bindingId)) continue; const specifier = proof.externals.get(key); return typeof specifier === "string" ? { origin: { specifier, importedPath: chain.members, resolutionMode: "require", }, call: current, } : undefined; } return undefined; }; /** Prove the source-local n.n getter and reject visible replacements. */ const canonicalWebpackInteropBindings = ( loaderBindingIds: ReadonlySet, bootstrapBody: t.BlockStatement, state: JavaScriptSemanticAnalysisState, ): ReadonlySet => { // The shared identity-write owner covers assignment, delete, destructuring, // and Object/Reflect mutators; any write that may select `n` counts. const writes = new Set(); const loaderMayOwnReference = (root: t.Node): boolean => { const pending = [root]; const seen = new Set(); while (pending.length > 0) { const reference = pending.pop(); if (reference === undefined || seen.has(reference)) continue; seen.add(reference); if (seen.size > SEMANTIC_ALIAS_SELECTION_LIMIT) { state.valueResourceLimits.add("alias-selections"); return true; } const node = unwrapJavaScriptExpression(reference).node; if (!t.isIdentifier(node)) continue; const binding = resolveSemanticBindingState(state, node, node.name); if (binding === undefined) continue; if (loaderBindingIds.has(binding.bindingId)) return true; const selected = semanticBindingAtRead( binding, node, state.parentsByNode, ).binding; for (const source of [ ...selected.initializers, ...selected.referenceInitializers, ]) if (source.projection.length === 0) pending.push(source.node); } return false; }; const interop = createSemanticModuleInterop(state); completeSemanticSteps( collectJavaScriptIdentityWritesSteps( state.program, (root, kind) => { return kind === "member" && loaderMayOwnReference(root) ? [["loader"]] : []; }, interop.intrinsicInvocation, interop.staticString, interop.isFreshObject, (node, path) => { if (path.length < 2 || path[1] === "n" || path[1] === null) writes.add(node); }, ), ); const proven = new Set(); const [write] = writes; if ( writes.size === 1 && t.isAssignmentExpression(write) && isInstalledBeforeEntryDispatch( write, bootstrapBody, loaderBindingIds, state, ) && isCanonicalGetter(write, state) ) for (const bindingId of loaderBindingIds) proven.add(bindingId); return proven; }; const isCanonicalGetter = ( write: t.AssignmentExpression, state: JavaScriptSemanticAnalysisState, ): boolean => { if ( !t.isArrowFunctionExpression(write.right) || write.right.params.length !== 1 || !t.isIdentifier(write.right.params[0]) ) return false; const parameter = write.right.params[0]; const parameterBinding = resolveSemanticBindingState( state, parameter, parameter.name, ); const body = write.right.body; return ( parameterBinding !== undefined && t.isConditionalExpression(body) && t.isLogicalExpression(body.test, { operator: "&&" }) && sameBinding(body.test.left, parameterBinding.bindingId, state) && t.isMemberExpression(body.test.right) && semanticStaticPropertyKey( body.test.right.property, body.test.right.computed, ) === "__esModule" && sameBinding(body.test.right.object, parameterBinding.bindingId, state) && getterReturnsDefault(body.consequent, parameterBinding.bindingId, state) && getterReturnsModule(body.alternate, parameterBinding.bindingId, state) ); }; const isInstalledBeforeEntryDispatch = ( write: t.AssignmentExpression, body: t.BlockStatement, loaderBindingIds: ReadonlySet, state: JavaScriptSemanticAnalysisState, ): boolean => { const statement = state.parentsByNode.get(write); if (!t.isExpressionStatement(statement)) return false; const writeIndex = body.body.indexOf(statement); if (writeIndex < 0) return false; let entryIndex = -1; for (let index = 0; index < body.body.length; index += 1) { const topLevel = body.body[index]; if (topLevel === undefined) continue; traverseJavaScriptAst(topLevel, { enter: (node, _parent, ancestors) => { if ( entryIndex < 0 && t.isCallExpression(node) && t.isIdentifier(node.callee) && ancestors().every((ancestor) => !t.isFunction(ancestor)) && loaderBindingIds.has( resolveSemanticBindingState(state, node.callee, node.callee.name) ?.bindingId ?? "", ) ) entryIndex = index; }, }); if (entryIndex >= 0) break; } return entryIndex > writeIndex; }; const bootstrapProofForLoaderCall = ( call: t.CallExpression, loaderBindingId: string, state: JavaScriptSemanticAnalysisState, ): BootstrapExternals | undefined => { for ( let ancestor = state.parentsByNode.get(call); ancestor !== undefined; ancestor = state.parentsByNode.get(ancestor) ) { if (!t.isCallExpression(ancestor)) continue; const proof = bootstrapExternals(ancestor, state); if (proof?.loaderBindingIds.has(loaderBindingId)) return proof; } return undefined; }; const sameBinding = ( node: t.Node, bindingId: string, state: JavaScriptSemanticAnalysisState, ): boolean => t.isIdentifier(node) && resolveSemanticBindingState(state, node, node.name)?.bindingId === bindingId; const getterReturnsDefault = ( node: t.Node, bindingId: string, state: JavaScriptSemanticAnalysisState, ): boolean => t.isArrowFunctionExpression(node) && node.params.length === 0 && t.isMemberExpression(node.body) && semanticStaticPropertyKey(node.body.property, node.body.computed) === "default" && sameBinding(node.body.object, bindingId, state); const getterReturnsModule = ( node: t.Node, bindingId: string, state: JavaScriptSemanticAnalysisState, ): boolean => t.isArrowFunctionExpression(node) && node.params.length === 0 && sameBinding(node.body, bindingId, state); const bootstrapExternals = ( call: t.CallExpression, state: JavaScriptSemanticAnalysisState, ): BootstrapExternals | null => { let byCall = proofs.get(state); if (byCall === undefined) { byCall = new WeakMap(); proofs.set(state, byCall); } const cached = byCall.get(call); if (cached !== undefined) return cached; const proof = proveBootstrapExternals(call, state); byCall.set(call, proof); return proof; }; const proveBootstrapExternals = ( call: t.CallExpression, state: JavaScriptSemanticAnalysisState, ): BootstrapExternals | null => { const bootstrap = javascriptBootstrapAt(call, state); // A replaced loader no longer dispatches the declared table. if ( bootstrap === undefined || !singleDefinition(state, bootstrap.loaderBindingId, "function") ) return null; const loaderBindingIds = new Set([bootstrap.loaderBindingId]); const factories = bootstrap.table.properties.flatMap((property) => { const factory = moduleFactory(property); return factory === undefined ? [] : [{ property, factory }]; }); for (const { factory } of factories) { // The bootstrap proves `table[key](module, module.exports, loader)`. const loaderParameter = factory.params[2]; const bindingId = t.isIdentifier(loaderParameter) ? resolveSemanticBindingState( state, loaderParameter, loaderParameter.name, )?.bindingId : undefined; if ( bindingId !== undefined && singleDefinition(state, bindingId, "parameter") ) loaderBindingIds.add(bindingId); } const integrity = proveJavaScriptBootstrapIntegrity( call, bootstrap, loaderBindingIds, state, ); if (integrity === undefined) return null; const externals = new Map(); for (const { property, factory } of factories) { const key = modulePropertyName(property); const specifier = integrity.replacedKeys.has(key) ? undefined : externalSpecifier(factory, state); if (externals.has(key)) externals.set(key, null); else externals.set(key, specifier ?? null); } return { loaderBindingIds, interopGetterBindingIds: canonicalWebpackInteropBindings( loaderBindingIds, bootstrap.body, state, ), externals, }; }; /** * Accept only `module.exports = require("specifier")` as the whole body of an * ordinary factory; generator, async and accessor factories do not run it. */ const externalSpecifier = ( factory: t.FunctionExpression | t.ArrowFunctionExpression | t.ObjectMethod, state: JavaScriptSemanticAnalysisState, ): string | undefined => { const moduleParameter = factory.params[0]; const names = factory.params.map((parameter) => t.isIdentifier(parameter) ? parameter.name : null, ); if ( !t.isIdentifier(moduleParameter) || // A duplicated name binds the later argument, not the module object. new Set(names).size !== names.length || factory.generator || factory.async || (t.isObjectMethod(factory) && factory.kind !== "method") ) return undefined; let expression: t.Node | undefined; if (t.isBlockStatement(factory.body)) { const [statement] = factory.body.body; if (factory.body.body.length !== 1 || !t.isExpressionStatement(statement)) return undefined; expression = statement.expression; } else expression = factory.body; if ( !t.isAssignmentExpression(expression, { operator: "=" }) || !t.isMemberExpression(expression.left) || !t.isIdentifier(expression.left.object) || semanticStaticPropertyKey( expression.left.property, expression.left.computed, ) !== "exports" ) return undefined; const moduleBinding = resolveSemanticBindingState( state, moduleParameter, moduleParameter.name, ); const target = resolveSemanticBindingState( state, expression.left.object, expression.left.object.name, ); if ( moduleBinding === undefined || target?.bindingId !== moduleBinding.bindingId ) return undefined; const required = semanticRequireOrigin(expression.right, state)?.origin; return required !== undefined && required.importedPath.length === 0 ? required.specifier : undefined; }; const singleDefinition = ( state: JavaScriptSemanticAnalysisState, bindingId: string, kind: "function" | "parameter", ): boolean => { const definitions = state.bindingsById.get(bindingId)?.definitions; return definitions?.length === 1 && definitions[0]?.kind === kind; };