mirror of
https://github.com/morluto/rea.git
synced 2026-11-11 01:13:39 +00:00
fix(javascript): qualify network receiver evidence
This commit is contained in:
1 parent
a293738c66
commit
25b69bb803
9 files changed
+323
-60
No files matched your search
@@ -0,0 +1,115 @@
|
||||
import { completeSemanticSteps } from "./javascriptSemanticTraversal.js";
|
||||
import {
|
||||
semanticStaticPropertyName,
|
||||
unwrapJavaScriptExpression,
|
||||
} from "./javascriptAstValues.js";
|
||||
import { semanticBindingAtRead } from "./javascriptSemanticMutationInitializers.js";
|
||||
import { semanticOpaqueGlobalMayHaveChanged } from "./javascriptSemanticOpaqueEffects.js";
|
||||
import {
|
||||
resolveSemanticBindingState,
|
||||
semanticStableInitializer,
|
||||
type JavaScriptBindingIndex,
|
||||
type JavaScriptSemanticAnalysisState,
|
||||
} from "./javascriptSemanticState.js";
|
||||
import * as t from "@babel/types";
|
||||
import { isUnmodifiedGlobalReference } from "./javascriptSemanticAmbientWrites.js";
|
||||
|
||||
export const isFetchCall = (
|
||||
node: t.Node,
|
||||
bindings: JavaScriptSemanticAnalysisState,
|
||||
): boolean =>
|
||||
(t.isCallExpression(node) || t.isOptionalCallExpression(node)) &&
|
||||
isUnmodifiedGlobalReference(node.callee, bindings, "fetch");
|
||||
|
||||
/** Browser request identity requires an intact intrinsic, not a method spelling. */
|
||||
export const isBrowserNetworkRequest = (
|
||||
node: t.CallExpression | t.OptionalCallExpression | t.NewExpression,
|
||||
bindings: JavaScriptSemanticAnalysisState,
|
||||
): boolean =>
|
||||
isFetchCall(node, bindings) ||
|
||||
(t.isNewExpression(node) &&
|
||||
(isUnmodifiedGlobalReference(node.callee, bindings, "WebSocket") ||
|
||||
isUnmodifiedGlobalReference(node.callee, bindings, "EventSource"))) ||
|
||||
((t.isCallExpression(node) || t.isOptionalCallExpression(node)) &&
|
||||
isUnmodifiedGlobalReference(node.callee, bindings, [
|
||||
"navigator",
|
||||
"sendBeacon",
|
||||
]));
|
||||
|
||||
const isXhrOpenRequest = (
|
||||
node: t.Node,
|
||||
bindings: JavaScriptSemanticAnalysisState,
|
||||
index?: Pick<JavaScriptBindingIndex, "referenceMayHaveChangedAt">,
|
||||
): boolean | undefined => {
|
||||
if (!t.isCallExpression(node) && !t.isOptionalCallExpression(node))
|
||||
return false;
|
||||
const callee = unwrapJavaScriptExpression(node.callee).node;
|
||||
if (
|
||||
(!t.isMemberExpression(callee) && !t.isOptionalMemberExpression(callee)) ||
|
||||
semanticStaticPropertyName(callee.property, callee.computed) !== "open"
|
||||
)
|
||||
return false;
|
||||
if (
|
||||
["XMLHttpRequest", "Object"].some((owner) =>
|
||||
["open", "__proto__"].some(
|
||||
(member) =>
|
||||
bindings.ambientWrites.covers([
|
||||
"global",
|
||||
owner,
|
||||
"prototype",
|
||||
member,
|
||||
]) ||
|
||||
bindings.ambientEscapes.covers([
|
||||
"global",
|
||||
owner,
|
||||
"prototype",
|
||||
member,
|
||||
]),
|
||||
),
|
||||
)
|
||||
)
|
||||
return false;
|
||||
let receiver = unwrapJavaScriptExpression(callee.object).node;
|
||||
const seen = new Set<string>();
|
||||
while (t.isIdentifier(receiver)) {
|
||||
if (
|
||||
index !== undefined &&
|
||||
(index.referenceMayHaveChangedAt(node, receiver, ["open"]) ||
|
||||
index.referenceMayHaveChangedAt(node, receiver, ["__proto__"]))
|
||||
)
|
||||
return false;
|
||||
const binding = resolveSemanticBindingState(
|
||||
bindings,
|
||||
receiver,
|
||||
receiver.name,
|
||||
);
|
||||
if (binding === undefined || seen.has(binding.bindingId)) return false;
|
||||
seen.add(binding.bindingId);
|
||||
const initializer = semanticStableInitializer(binding, bindings);
|
||||
if (
|
||||
initializer === undefined ||
|
||||
initializer.projection.length !== 0 ||
|
||||
!semanticBindingAtRead(binding, receiver, bindings.parentsByNode)
|
||||
.hasDefiniteInitializer
|
||||
)
|
||||
return false;
|
||||
receiver = unwrapJavaScriptExpression(initializer.node).node;
|
||||
}
|
||||
if (
|
||||
!t.isNewExpression(receiver) ||
|
||||
!isUnmodifiedGlobalReference(receiver.callee, bindings, "XMLHttpRequest") ||
|
||||
semanticOpaqueGlobalMayHaveChanged(callee, bindings)
|
||||
)
|
||||
return false;
|
||||
return index === undefined && seen.size > 0 ? undefined : true;
|
||||
};
|
||||
|
||||
/** Resolve once through the shared mutation index when lexical proof needs it. */
|
||||
export const createXhrOpenRequestQuery =
|
||||
(index: JavaScriptBindingIndex): ((node: t.Node) => boolean) =>
|
||||
(node) => {
|
||||
const proof = isXhrOpenRequest(node, index.bindings);
|
||||
if (proof !== undefined) return proof;
|
||||
completeSemanticSteps(index.indexMemberMutationFactsSteps());
|
||||
return isXhrOpenRequest(node, index.bindings, index) === true;
|
||||
};
|
||||
@@ -1,3 +1,4 @@
|
||||
import { createXhrOpenRequestQuery } from "./javascriptNetworkRequests.js";
|
||||
import { uniqueSortedBy } from "../canonicalOrdering.js";
|
||||
import * as t from "@babel/types";
|
||||
import { JavaScriptRouteScan } from "./javascriptRouteScan.js";
|
||||
@@ -114,6 +115,7 @@ export function* analyzeParsedJavaScriptStaticSourceSteps(
|
||||
bindings,
|
||||
);
|
||||
const findings: JavaScriptFindingContext = {
|
||||
xhrOpenRequest: createXhrOpenRequestQuery(index),
|
||||
pathIntrinsics: createStaticPathIntrinsicQuery(index),
|
||||
browserWindowReceiver: (node, site, suffix) =>
|
||||
resolveElectronBrowserWindowReceiver(node, site, suffix, findings, index),
|
||||
|
||||
@@ -357,8 +357,8 @@ it.each(expressionWrappers)(
|
||||
)
|
||||
.toEqual([
|
||||
["local-window", "candidate"],
|
||||
["/local-db", null],
|
||||
["/local-cache", null],
|
||||
["/local-db", "candidate"],
|
||||
["/local-cache", "candidate"],
|
||||
["changed-window", "candidate"],
|
||||
["uncertain-window", "candidate"],
|
||||
]);
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
import {
|
||||
isBrowserNetworkRequest,
|
||||
isFetchCall,
|
||||
} from "./javascriptNetworkRequests.js";
|
||||
import * as t from "@babel/types";
|
||||
import { isIntrinsicMapLookup } from "./javascriptSemanticMapOperations.js";
|
||||
import { traverseJavaScriptValueAst } from "./javascriptSemanticTraversal.js";
|
||||
@@ -491,13 +495,6 @@ export const indexVitePreloadDependencyTable = (
|
||||
return escapedOrMutated ? dependencies.map(() => undefined) : dependencies;
|
||||
};
|
||||
|
||||
const isFetchCall = (
|
||||
node: t.Node,
|
||||
bindings: JavaScriptSemanticAnalysisState,
|
||||
): boolean =>
|
||||
(t.isCallExpression(node) || t.isOptionalCallExpression(node)) &&
|
||||
isUnmodifiedGlobalReference(node.callee, bindings, "fetch");
|
||||
|
||||
const requestConstructorUrl = (
|
||||
node: t.Node,
|
||||
bindings: JavaScriptSemanticAnalysisState,
|
||||
@@ -591,7 +588,10 @@ const inspectLiteralEndpointCall = (
|
||||
kind: "network",
|
||||
value: endpoint.value,
|
||||
mechanism: `call:${name}`,
|
||||
...(endpoint.kind === "candidate" ? { interpretation: "candidate" } : {}),
|
||||
...(endpoint.kind === "candidate" &&
|
||||
context.xhrOpenRequest?.(node) !== true
|
||||
? { interpretation: "candidate" }
|
||||
: {}),
|
||||
});
|
||||
};
|
||||
|
||||
@@ -611,7 +611,7 @@ const isKnownNetworkRequest = (
|
||||
bindings: JavaScriptSemanticAnalysisState,
|
||||
context: FindingContext,
|
||||
): boolean => {
|
||||
if (isFetchCall(node, bindings)) return true;
|
||||
if (isBrowserNetworkRequest(node, bindings)) return true;
|
||||
const callee = unwrapJavaScriptExpression(node.callee).node;
|
||||
if (
|
||||
(t.isCallExpression(node) || t.isOptionalCallExpression(node)) &&
|
||||
@@ -620,19 +620,7 @@ const isKnownNetworkRequest = (
|
||||
context.rendererLoad?.(node.callee, node) === true
|
||||
)
|
||||
return true;
|
||||
if (
|
||||
t.isNewExpression(node) &&
|
||||
(isUnmodifiedGlobalReference(node.callee, bindings, "WebSocket") ||
|
||||
isUnmodifiedGlobalReference(node.callee, bindings, "EventSource"))
|
||||
)
|
||||
return true;
|
||||
return (
|
||||
(t.isCallExpression(node) || t.isOptionalCallExpression(node)) &&
|
||||
isUnmodifiedGlobalReference(node.callee, bindings, [
|
||||
"navigator",
|
||||
"sendBeacon",
|
||||
])
|
||||
);
|
||||
return false;
|
||||
};
|
||||
|
||||
/** Finish queued targets after shared member-mutation facts are indexed. */
|
||||
|
||||
@@ -675,10 +675,10 @@ export const endpointArgument = (
|
||||
callee: t.Node,
|
||||
receiverFact?: JavaScriptOpenReceiverFact,
|
||||
): JavaScriptEndpointArgument | undefined => {
|
||||
const literal = (
|
||||
const candidate = (
|
||||
value: string | undefined,
|
||||
): JavaScriptEndpointArgument | undefined =>
|
||||
value === undefined ? undefined : { kind: "endpoint", value };
|
||||
value === undefined ? undefined : { kind: "candidate", value };
|
||||
if (receiverFact === "window") return undefined;
|
||||
if (
|
||||
name === "fetch" ||
|
||||
@@ -687,17 +687,11 @@ export const endpointArgument = (
|
||||
name === "EventSource" ||
|
||||
name.endsWith(".EventSource")
|
||||
)
|
||||
return literal(stringValue(args[0]));
|
||||
return candidate(stringValue(args[0]));
|
||||
if (name.endsWith(".open")) {
|
||||
const value = xhrOpenUrl(name, args[0], args[1], receiverFact);
|
||||
if (value === undefined) return undefined;
|
||||
return {
|
||||
kind:
|
||||
stringValue(args[0]) !== undefined || isRequestUrlReference(value)
|
||||
? "endpoint"
|
||||
: "candidate",
|
||||
value,
|
||||
};
|
||||
return { kind: "candidate", value };
|
||||
}
|
||||
const method = ["get", "post", "put", "patch", "delete", "request"].find(
|
||||
(candidate) => name === candidate || name.endsWith(`.${candidate}`),
|
||||
@@ -717,17 +711,10 @@ export const endpointArgument = (
|
||||
receiverFact === "extension-state")))
|
||||
)
|
||||
return undefined;
|
||||
return {
|
||||
kind:
|
||||
method !== "get" && method !== "delete" && isRequestUrlReference(value)
|
||||
? "endpoint"
|
||||
: "candidate",
|
||||
value,
|
||||
};
|
||||
return { kind: "candidate", value };
|
||||
}
|
||||
// Beacon URLs are endpoint observations, retaining the upstream API support.
|
||||
if (name === "sendBeacon" || name.endsWith(".sendBeacon"))
|
||||
return literal(stringValue(args[0]));
|
||||
return candidate(stringValue(args[0]));
|
||||
if (name === "loadURL" || name.endsWith(".loadURL")) {
|
||||
const value = stringValue(args[0]);
|
||||
return value === undefined ? undefined : { kind: "candidate", value };
|
||||
|
||||
@@ -110,6 +110,7 @@ export interface JavaScriptFindingContext {
|
||||
) =>
|
||||
| { readonly owner: t.NewExpression; readonly path: readonly string[] }
|
||||
| undefined;
|
||||
readonly xhrOpenRequest?: (node: t.Node) => boolean;
|
||||
readonly rendererLoad?: (callee: t.Node, site: t.Node) => boolean;
|
||||
readonly rpcScan: JavaScriptRpcScan;
|
||||
/** Shared bounded argv/nullish argument shapes for optional-slot signatures. */
|
||||
|
||||
Reference in new issue
Block a user