mirror of
https://github.com/morluto/rea
synced 2026-10-26 14:25:49 -04:00
fix(javascript): refuse Webpack loaders that can fall off the end (#6007)
* fix(javascript): refuse Webpack loaders that can fall off the end
The bootstrap integrity proof accepted any loader whose explicit returns
all yielded the module's exports. A loader with no returns, or with only
the cache-hit return, passed vacuously even though its miss path returns
undefined, so a bundled `n(45840).BrowserWindow` became an exact Electron
window.
Require that the loader body cannot complete normally before the proof
checks its returns, so the bundled-call identity stays unresolved while
the literal require("electron") reference is preserved.
Fixes #5958
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* test(javascript): cover finally completion in Webpack loader proofs
Group the loader completion-path cases in their own suite and add
focused finally cases: a completing finally keeps a falling-through
catch unproven, a finally returning another value stays unresolved, and
a finally returning the exports proves the loader.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* test(javascript): keep IPC checks for unresolved Webpack loader cases
Share the unresolved-identity assertion so the completion-path cases
also reject directly attributed IPC observations.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -30,11 +30,12 @@ interface LoaderShape {
|
||||
|
||||
/**
|
||||
* Prove that `loader(key)` returns the exports of the factory declared at
|
||||
* `table[key]`: the loader returns only the dispatched module's exports or a
|
||||
* cache entry it created for that module, the table has only literal keys,
|
||||
* and the table and cache are referenced only by the loader or through the
|
||||
* runtime's `loader.m`/`loader.c` exposure. Literal-key writes through that
|
||||
* exposure replace their slot. Returns undefined when any of this is unproven.
|
||||
* `table[key]`: every normal completion of the loader returns the dispatched
|
||||
* module's exports or a cache entry it created for that module, the table has
|
||||
* only literal keys, and the table and cache are referenced only by the loader
|
||||
* or through the runtime's `loader.m`/`loader.c` exposure. Literal-key writes
|
||||
* through that exposure replace their slot. Returns undefined when any of this
|
||||
* is unproven.
|
||||
*/
|
||||
export const proveJavaScriptBootstrapIntegrity = (
|
||||
call: t.CallExpression,
|
||||
@@ -74,12 +75,14 @@ const loaderShape = (
|
||||
const parameterId = t.isIdentifier(parameter)
|
||||
? bindingOf(state, parameter)
|
||||
: undefined;
|
||||
// A rewritten key would dispatch a different slot than the caller named.
|
||||
// A rewritten key would dispatch a different slot than the caller named,
|
||||
// and falling off the end returns undefined instead of the exports.
|
||||
if (
|
||||
parameterId === undefined ||
|
||||
state.bindingsById.get(parameterId)?.definitions.length !== 1 ||
|
||||
loader.async ||
|
||||
loader.generator
|
||||
loader.generator ||
|
||||
completesNormally(loader.body)
|
||||
)
|
||||
return undefined;
|
||||
const dispatches = new Set<t.Node>();
|
||||
@@ -129,6 +132,35 @@ const loaderShape = (
|
||||
: undefined;
|
||||
};
|
||||
|
||||
/**
|
||||
* Whether control may reach the end of `statement`. Loops, switches and labels
|
||||
* can absorb a `break`, so they are assumed to complete.
|
||||
*/
|
||||
const completesNormally = (statement: t.Statement): boolean => {
|
||||
if (t.isReturnStatement(statement) || t.isThrowStatement(statement))
|
||||
return false;
|
||||
if (t.isBlockStatement(statement))
|
||||
return statement.body.every(completesNormally);
|
||||
if (t.isIfStatement(statement))
|
||||
return (
|
||||
statement.alternate === null ||
|
||||
statement.alternate === undefined ||
|
||||
completesNormally(statement.consequent) ||
|
||||
completesNormally(statement.alternate)
|
||||
);
|
||||
if (t.isTryStatement(statement))
|
||||
return (
|
||||
(statement.finalizer === null ||
|
||||
statement.finalizer === undefined ||
|
||||
completesNormally(statement.finalizer)) &&
|
||||
(completesNormally(statement.block) ||
|
||||
(statement.handler !== null &&
|
||||
statement.handler !== undefined &&
|
||||
completesNormally(statement.handler.body)))
|
||||
);
|
||||
return true;
|
||||
};
|
||||
|
||||
/** `table[key](module, ...)` or `table[key].call(thisArg, module, ...)`. */
|
||||
const tableDispatch = (
|
||||
call: t.CallExpression,
|
||||
|
||||
Reference in New Issue
Block a user