mirror of
https://github.com/morluto/rea.git
synced 2026-10-26 17:35:11 +00:00
fix(javascript): distinguish deferred bootstrap installer writes
This commit is contained in:
1 parent
bfa60d596e
commit
9fb63ad066
3 files changed
+120
-2
No files matched your search
@@ -380,7 +380,12 @@ parameter does not change the recovered names. Global property-definition
|
||||
intrinsics require lexical and visible-write proof. Computed writes through a
|
||||
standalone loader's exposed module/cache table use the shared value owner; an
|
||||
unknown key invalidates the original-slot proof rather than implying a fresh
|
||||
chunk installation. Export names describe recognized declarations, not a proof
|
||||
chunk installation. A function stored on a standalone loader member does not
|
||||
write the table merely by being defined: the bootstrap owner excludes its
|
||||
deferred writes only when that member is unread and the loader namespace stays
|
||||
confined. Calls, member aliases, dynamic member reads, namespace escapes, or
|
||||
receiver-based access retain uncertainty about those writes. Export names
|
||||
describe recognized declarations, not a proof
|
||||
of final runtime property lifetime or arbitrary helper behavior.
|
||||
|
||||
Each recovered bundle module retains the exact factory-source digest. A complete
|
||||
|
||||
@@ -246,6 +246,38 @@ describe("Webpack externals with a tampered runtime", () => {
|
||||
});
|
||||
|
||||
describe("Webpack externals with a tampered loader or module object", () => {
|
||||
it.each([
|
||||
["invoked", "n.i(runtimeChunks);"],
|
||||
["read through an alias", "const install=n.i;install(runtimeChunks);"],
|
||||
["read through a dynamic key", "n[runtimeMember](runtimeChunks);"],
|
||||
["exposed with its namespace", "publish(n);"],
|
||||
[
|
||||
"reachable through a helper receiver",
|
||||
"n.run=function(){this.i(runtimeChunks)};n.run();",
|
||||
],
|
||||
])(
|
||||
"keeps an unknown chunk installation unresolved when %s",
|
||||
(_name, invoke) => {
|
||||
expectUnresolved(
|
||||
bootstrap(
|
||||
EXTERNAL,
|
||||
CONSUMER,
|
||||
`n.m=m;n.i=e=>{for(var k in e)n.m[k]=e[k]};${invoke}n(100);`,
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
it("does not treat a called assignment value as an unused installer", () => {
|
||||
expectUnresolved(
|
||||
bootstrap(
|
||||
EXTERNAL,
|
||||
CONSUMER,
|
||||
"n.m=m;(n.i=e=>{for(var k in e)n.m[k]=e[k]})(runtimeChunks);n(100);",
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
it.each([
|
||||
[
|
||||
"cached exports rewritten through the runtime exposure",
|
||||
|
||||
@@ -438,6 +438,12 @@ const referencesAreContained = (
|
||||
[...tracked.keys()].flatMap((id) => state.bindingsById.get(id)?.name ?? []),
|
||||
);
|
||||
const replacedKeys = new Set<string>();
|
||||
const unreadDefinitions = unreadRuntimeDefinitions(
|
||||
call,
|
||||
shape,
|
||||
loaderIds,
|
||||
state,
|
||||
);
|
||||
let contained = true;
|
||||
traverseJavaScriptAst(call, {
|
||||
skip: () => !contained,
|
||||
@@ -452,12 +458,87 @@ const referencesAreContained = (
|
||||
const use: Use = { node, parent, ancestors, shape, loaderIds, state };
|
||||
if (role !== "loader") contained = containedUse(role, use);
|
||||
else if (exposedTableUse(use) === "escaped") contained = false;
|
||||
else contained = exposedSlotWrite(use, replacedKeys);
|
||||
else {
|
||||
const activation = [...ancestors]
|
||||
.reverse()
|
||||
.find((ancestor) => t.isFunction(ancestor));
|
||||
contained =
|
||||
(activation !== undefined && unreadDefinitions.has(activation)) ||
|
||||
exposedSlotWrite(use, replacedKeys);
|
||||
}
|
||||
},
|
||||
});
|
||||
return contained ? { replacedKeys } : undefined;
|
||||
};
|
||||
|
||||
/**
|
||||
* A function stored on the confined runtime cannot execute until its member is
|
||||
* read. Keep this proof local to the bootstrap's existing loader/table def-use
|
||||
* boundary: aliases, dynamic member reads and escapes invalidate it. Merely
|
||||
* defining Webpack's chunk installer does not install any chunks.
|
||||
*/
|
||||
const unreadRuntimeDefinitions = (
|
||||
call: t.CallExpression,
|
||||
shape: LoaderShape,
|
||||
loaderIds: ReadonlySet<string>,
|
||||
state: JavaScriptSemanticAnalysisState,
|
||||
): ReadonlySet<t.Node> => {
|
||||
const definitions = new Map<t.Node, string>();
|
||||
const readKeys = new Set<string>();
|
||||
let confined = true;
|
||||
traverseJavaScriptAst(call, {
|
||||
enter: (node, parent, readAncestors) => {
|
||||
// A helper invoked as `loader.helper()` can obtain the namespace through
|
||||
// its receiver instead of a tracked loader identifier.
|
||||
if (t.isThisExpression(node)) confined = false;
|
||||
if (!t.isIdentifier(node) || parent === null) return;
|
||||
const ancestors = readAncestors();
|
||||
if (
|
||||
!t.isReferenced(node, parent, ancestors.at(-2)) ||
|
||||
!loaderIds.has(bindingOf(state, node) ?? "")
|
||||
)
|
||||
return;
|
||||
if (
|
||||
t.isCallExpression(parent) &&
|
||||
(parent.callee === node ||
|
||||
(parent === shape.dispatch && parent.arguments.at(-1) === node))
|
||||
)
|
||||
return;
|
||||
if (!t.isMemberExpression(parent) || parent.object !== node) {
|
||||
confined = false;
|
||||
return;
|
||||
}
|
||||
const key = semanticStaticPropertyKey(parent.property, parent.computed);
|
||||
if (key === null) {
|
||||
confined = false;
|
||||
return;
|
||||
}
|
||||
const assignment = ancestors.at(-2);
|
||||
if (
|
||||
t.isAssignmentExpression(assignment, { operator: "=" }) &&
|
||||
assignment.left === parent
|
||||
) {
|
||||
if (
|
||||
key !== "m" &&
|
||||
key !== "c" &&
|
||||
t.isFunction(assignment.right) &&
|
||||
t.isExpressionStatement(ancestors.at(-3))
|
||||
)
|
||||
definitions.set(assignment.right, key);
|
||||
return;
|
||||
}
|
||||
readKeys.add(key);
|
||||
},
|
||||
});
|
||||
return new Set(
|
||||
confined
|
||||
? [...definitions]
|
||||
.filter(([, key]) => !readKeys.has(key))
|
||||
.map(([node]) => node)
|
||||
: [],
|
||||
);
|
||||
};
|
||||
|
||||
interface Use {
|
||||
readonly node: t.Identifier;
|
||||
readonly parent: t.Node;
|
||||
|
||||
Reference in new issue
Block a user