mirror of
https://github.com/morluto/rea.git
synced 2026-11-10 16:57:19 +00:00
fix(javascript): trace wrapped Electron IPC paths in large bundles (#1874)
Co-authored-by: David <17119984+fengdwx@users.noreply.github.com>
This commit is contained in:
1 parent
d93fbda390
commit
87b9793c53
46 files changed
+3213
-355
No files matched your search
@@ -20,6 +20,7 @@ import {
|
||||
export const JAVASCRIPT_ANALYSIS_TRANSFER_RECORD_BYTES = 8 * 1024 * 1024;
|
||||
const FRAME_BYTES = JAVASCRIPT_ANALYSIS_TRANSFER_RECORD_BYTES;
|
||||
const TRANSFER_BYTES = 512 * 1024 * 1024;
|
||||
const MAX_TRANSFER_BYTES = 2 * 1024 * 1024 * 1024;
|
||||
const BATCH_BYTES = 1024 * 1024;
|
||||
const recordSchema = z.discriminatedUnion("kind", [
|
||||
z.strictObject({
|
||||
@@ -36,7 +37,7 @@ const recordSchema = z.discriminatedUnion("kind", [
|
||||
|
||||
/** Complete-file coordinates authenticated before adopting any worker facts. */
|
||||
export const javaScriptAnalysisTransferDescriptorSchema = z.strictObject({
|
||||
bytes: z.number().int().nonnegative().max(TRANSFER_BYTES),
|
||||
bytes: z.number().int().nonnegative().max(MAX_TRANSFER_BYTES),
|
||||
sha256: digestSchema,
|
||||
records: z.number().int().nonnegative(),
|
||||
module_bytes: z.number().int().nonnegative(),
|
||||
@@ -52,11 +53,24 @@ export const javaScriptTransferRetentionBytes = (
|
||||
records: number,
|
||||
): number => bytes * 8 + records * 64;
|
||||
|
||||
const encodeTransferRecord = (input: unknown, previousBytes: number) => {
|
||||
/** Larger streamed files require corresponding parent admission capacity. */
|
||||
export const javaScriptTransferByteBudget = (
|
||||
resultBudgetBytes: number,
|
||||
): number =>
|
||||
Math.min(
|
||||
MAX_TRANSFER_BYTES,
|
||||
Math.max(TRANSFER_BYTES, Math.floor(resultBudgetBytes / 8)),
|
||||
);
|
||||
|
||||
const encodeTransferRecord = (
|
||||
input: unknown,
|
||||
previousBytes: number,
|
||||
transferBytes = TRANSFER_BYTES,
|
||||
) => {
|
||||
const record = recordSchema.parse(input);
|
||||
const line = `${JSON.stringify(record)}\n`;
|
||||
const size = Buffer.byteLength(line);
|
||||
if (size > FRAME_BYTES || previousBytes + size > TRANSFER_BYTES)
|
||||
if (size > FRAME_BYTES || previousBytes + size > transferBytes)
|
||||
throw new AnalysisResourceConstraintError(
|
||||
"analyze_javascript_application",
|
||||
"transport",
|
||||
@@ -65,7 +79,7 @@ const encodeTransferRecord = (input: unknown, previousBytes: number) => {
|
||||
: "Complete JavaScript file facts exceed the transfer byte budget",
|
||||
{
|
||||
transfer_record_bytes: FRAME_BYTES,
|
||||
transfer_file_bytes: TRANSFER_BYTES,
|
||||
transfer_file_bytes: transferBytes,
|
||||
record_bytes: size,
|
||||
transfer_bytes_at_least: previousBytes + size,
|
||||
},
|
||||
@@ -108,7 +122,16 @@ export const writeJavaScriptAnalysisTransfer = async (
|
||||
path: string,
|
||||
module: JavaScriptModuleSemanticIr,
|
||||
projection: JavaScriptSemanticFileProjection,
|
||||
transferBytes = TRANSFER_BYTES,
|
||||
): Promise<Descriptor> => {
|
||||
if (
|
||||
!Number.isSafeInteger(transferBytes) ||
|
||||
transferBytes < 1 ||
|
||||
transferBytes > MAX_TRANSFER_BYTES
|
||||
)
|
||||
throw new RangeError(
|
||||
"JavaScript transfer requires a valid complete-file byte budget",
|
||||
);
|
||||
const output = await open(path, "wx", 0o600);
|
||||
const digest = createHash("sha256");
|
||||
let bytes = 0;
|
||||
@@ -129,7 +152,11 @@ export const writeJavaScriptAnalysisTransfer = async (
|
||||
batchBytes = 0;
|
||||
};
|
||||
const append = async (input: unknown): Promise<void> => {
|
||||
const { line, size, kind } = encodeTransferRecord(input, bytes);
|
||||
const { line, size, kind } = encodeTransferRecord(
|
||||
input,
|
||||
bytes,
|
||||
transferBytes,
|
||||
);
|
||||
bytes += size;
|
||||
records += 1;
|
||||
if (
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
import { z } from "zod";
|
||||
import { jsonValueSchema } from "../../domain/jsonValue.js";
|
||||
import { digestSchema } from "../../domain/digests.js";
|
||||
import { JAVASCRIPT_ANALYSIS_RESOURCE_DEFAULTS } from "../../domain/javascript/javascriptAnalysisResourceControls.js";
|
||||
import {
|
||||
JAVASCRIPT_ANALYSIS_RESOURCE_DEFAULTS,
|
||||
JAVASCRIPT_SEMANTIC_NODE_MAXIMUM,
|
||||
} from "../../domain/javascript/javascriptAnalysisResourceControls.js";
|
||||
import { javaScriptAnalysisTransferDescriptorSchema } from "./JavaScriptAnalysisTransfer.js";
|
||||
|
||||
/** Independent worker execution budget; source length is not an admission rule. */
|
||||
@@ -14,7 +17,7 @@ export const JAVASCRIPT_ANALYSIS_WORKER_DEFAULTS = {
|
||||
/** Caller-selected artifact identity, separate from the decoded source-text digest. */
|
||||
export const javaScriptWorkerRequestSchema = z.strictObject({
|
||||
id: z.uuid(),
|
||||
node_budget: z.number().int().min(0).max(20_000),
|
||||
node_budget: z.number().int().min(0).max(JAVASCRIPT_SEMANTIC_NODE_MAXIMUM),
|
||||
text_sha256: digestSchema,
|
||||
file: z.strictObject({
|
||||
path: z.string(),
|
||||
|
||||
@@ -23,13 +23,19 @@ const findingContext = {
|
||||
module_runtime: z.string().optional(),
|
||||
location: range,
|
||||
};
|
||||
const withRuntime = <Value extends { module_runtime?: string | undefined }>(
|
||||
const withRuntime = <
|
||||
Value extends {
|
||||
module_runtime?: string | undefined;
|
||||
resolution_basis?: "wrapper-substitution" | undefined;
|
||||
},
|
||||
>(
|
||||
value: Value,
|
||||
) => {
|
||||
const { module_runtime, ...rest } = value;
|
||||
const { module_runtime, resolution_basis, ...rest } = value;
|
||||
return {
|
||||
...rest,
|
||||
...(module_runtime === undefined ? {} : { module_runtime }),
|
||||
...(resolution_basis === undefined ? {} : { resolution_basis }),
|
||||
};
|
||||
};
|
||||
const staticValue = z.discriminatedUnion("status", [
|
||||
@@ -62,12 +68,14 @@ const ipcChannel = <Shape extends z.ZodRawShape>(shape: Shape) =>
|
||||
z.union([
|
||||
z.strictObject({
|
||||
...findingContext,
|
||||
resolution_basis: z.literal("wrapper-substitution").optional(),
|
||||
...shape,
|
||||
channel: z.string(),
|
||||
channel_expression: z.null(),
|
||||
}),
|
||||
z.strictObject({
|
||||
...findingContext,
|
||||
resolution_basis: z.literal("wrapper-substitution").optional(),
|
||||
...shape,
|
||||
channel: z.null(),
|
||||
channel_expression: z.string(),
|
||||
@@ -113,10 +121,11 @@ const ipc = z
|
||||
}),
|
||||
])
|
||||
.transform((value) => {
|
||||
const { module_runtime, ...rest } = value;
|
||||
const { module_runtime, resolution_basis, ...rest } = value;
|
||||
return {
|
||||
...rest,
|
||||
...(module_runtime === undefined ? {} : { module_runtime }),
|
||||
...(resolution_basis === undefined ? {} : { resolution_basis }),
|
||||
};
|
||||
});
|
||||
const windowShape = {
|
||||
@@ -128,6 +137,7 @@ const windowShape = {
|
||||
),
|
||||
};
|
||||
const bridgeShape = {
|
||||
resolution_basis: z.literal("wrapper-substitution").optional(),
|
||||
...findingContext,
|
||||
world: z.enum(["main", "isolated"]),
|
||||
world_id: staticValue.nullable(),
|
||||
@@ -136,99 +146,134 @@ const bridgeShape = {
|
||||
unknown_members: count,
|
||||
};
|
||||
const utilityShape = { ...findingContext, service_name: z.string().nullable() };
|
||||
const electron = z.strictObject({
|
||||
browser_windows: z.array(
|
||||
z.union([
|
||||
z
|
||||
.strictObject({
|
||||
...windowShape,
|
||||
preload_path: z.string(),
|
||||
preload_resolution_context: pathContext,
|
||||
})
|
||||
.transform(withRuntime),
|
||||
z
|
||||
.strictObject({
|
||||
...windowShape,
|
||||
preload_path: z.null(),
|
||||
preload_resolution_context: z.null(),
|
||||
})
|
||||
.transform(withRuntime),
|
||||
]),
|
||||
),
|
||||
context_bridge_apis: z.array(
|
||||
z.union([
|
||||
z
|
||||
.strictObject({
|
||||
...bridgeShape,
|
||||
api_key: z.string(),
|
||||
api_key_expression: z.null(),
|
||||
})
|
||||
.transform(withRuntime),
|
||||
z
|
||||
.strictObject({
|
||||
...bridgeShape,
|
||||
api_key: z.null(),
|
||||
api_key_expression: z.string(),
|
||||
})
|
||||
.transform(withRuntime),
|
||||
z
|
||||
.strictObject({
|
||||
...bridgeShape,
|
||||
api_key: z.null(),
|
||||
api_key_expression: z.null(),
|
||||
})
|
||||
.transform(withRuntime),
|
||||
]),
|
||||
),
|
||||
ipc: z.array(ipc),
|
||||
sender_validations: z.array(
|
||||
z.strictObject({
|
||||
...findingContext,
|
||||
subject: z.enum([
|
||||
"sender-url",
|
||||
"sender-origin",
|
||||
"sender-frame",
|
||||
"sender-id",
|
||||
"frame-id",
|
||||
"process-id",
|
||||
const electron = z
|
||||
.strictObject({
|
||||
call_paths: z
|
||||
.array(
|
||||
z.strictObject({
|
||||
entry_kind: z.enum(["bridge-member", "ipc-handler"]),
|
||||
entry: z.string(),
|
||||
channel: z.string().nullable(),
|
||||
terminal: z.string(),
|
||||
resolution: z.literal("candidate"),
|
||||
steps: z.array(
|
||||
z.strictObject({
|
||||
kind: z.enum([
|
||||
"exposure",
|
||||
"factory",
|
||||
"function",
|
||||
"call",
|
||||
"ipc",
|
||||
"terminal",
|
||||
]),
|
||||
label: z.string(),
|
||||
location: range,
|
||||
module_key: z.string().nullable(),
|
||||
module_runtime: z.string().nullable(),
|
||||
}),
|
||||
),
|
||||
conditions: z.array(
|
||||
z.strictObject({ expression: z.string(), location: range }),
|
||||
),
|
||||
}),
|
||||
)
|
||||
.optional(),
|
||||
browser_windows: z.array(
|
||||
z.union([
|
||||
z
|
||||
.strictObject({
|
||||
...windowShape,
|
||||
preload_path: z.string(),
|
||||
preload_resolution_context: pathContext,
|
||||
})
|
||||
.transform(withRuntime),
|
||||
z
|
||||
.strictObject({
|
||||
...windowShape,
|
||||
preload_path: z.null(),
|
||||
preload_resolution_context: z.null(),
|
||||
})
|
||||
.transform(withRuntime),
|
||||
]),
|
||||
mechanism: z.string(),
|
||||
expected: staticValue,
|
||||
enforcement: z.literal("unknown"),
|
||||
}),
|
||||
),
|
||||
utility_processes: z.array(
|
||||
z.union([
|
||||
z
|
||||
.strictObject({
|
||||
...utilityShape,
|
||||
module_path: z.string(),
|
||||
module_resolution_context: pathContext,
|
||||
module_expression: z.null(),
|
||||
})
|
||||
.transform(withRuntime),
|
||||
z
|
||||
.strictObject({
|
||||
...utilityShape,
|
||||
module_path: z.null(),
|
||||
module_resolution_context: z.null(),
|
||||
module_expression: z.string(),
|
||||
})
|
||||
.transform(withRuntime),
|
||||
]),
|
||||
),
|
||||
native_addon_bindings: z.array(
|
||||
z.strictObject({
|
||||
...findingContext,
|
||||
specifier: z.string(),
|
||||
binding_kind: z.enum(["import", "require", "re-export"]),
|
||||
module_kind: z.enum(["import", "require"]),
|
||||
members: strings,
|
||||
namespace_access: z.boolean().optional(),
|
||||
dynamic_member_access: z.boolean().optional(),
|
||||
}),
|
||||
),
|
||||
});
|
||||
),
|
||||
context_bridge_apis: z.array(
|
||||
z.union([
|
||||
z
|
||||
.strictObject({
|
||||
...bridgeShape,
|
||||
api_key: z.string(),
|
||||
api_key_expression: z.null(),
|
||||
})
|
||||
.transform(withRuntime),
|
||||
z
|
||||
.strictObject({
|
||||
...bridgeShape,
|
||||
api_key: z.null(),
|
||||
api_key_expression: z.string(),
|
||||
})
|
||||
.transform(withRuntime),
|
||||
z
|
||||
.strictObject({
|
||||
...bridgeShape,
|
||||
api_key: z.null(),
|
||||
api_key_expression: z.null(),
|
||||
})
|
||||
.transform(withRuntime),
|
||||
]),
|
||||
),
|
||||
ipc: z.array(ipc),
|
||||
sender_validations: z.array(
|
||||
z.strictObject({
|
||||
...findingContext,
|
||||
subject: z.enum([
|
||||
"sender-url",
|
||||
"sender-origin",
|
||||
"sender-frame",
|
||||
"sender-id",
|
||||
"frame-id",
|
||||
"process-id",
|
||||
]),
|
||||
mechanism: z.string(),
|
||||
expected: staticValue,
|
||||
enforcement: z.literal("unknown"),
|
||||
}),
|
||||
),
|
||||
utility_processes: z.array(
|
||||
z.union([
|
||||
z
|
||||
.strictObject({
|
||||
...utilityShape,
|
||||
module_path: z.string(),
|
||||
module_resolution_context: pathContext,
|
||||
module_expression: z.null(),
|
||||
})
|
||||
.transform(withRuntime),
|
||||
z
|
||||
.strictObject({
|
||||
...utilityShape,
|
||||
module_path: z.null(),
|
||||
module_resolution_context: z.null(),
|
||||
module_expression: z.string(),
|
||||
})
|
||||
.transform(withRuntime),
|
||||
]),
|
||||
),
|
||||
native_addon_bindings: z.array(
|
||||
z.strictObject({
|
||||
...findingContext,
|
||||
specifier: z.string(),
|
||||
binding_kind: z.enum(["import", "require", "re-export"]),
|
||||
module_kind: z.enum(["import", "require"]),
|
||||
members: strings,
|
||||
namespace_access: z.boolean().optional(),
|
||||
dynamic_member_access: z.boolean().optional(),
|
||||
}),
|
||||
),
|
||||
})
|
||||
.transform(({ call_paths, ...rest }) => ({
|
||||
...rest,
|
||||
...(call_paths === undefined ? {} : { call_paths }),
|
||||
}));
|
||||
const referenceShape = {
|
||||
...findingContext,
|
||||
kind: z.enum([
|
||||
|
||||
@@ -9,7 +9,10 @@ import type { JavaScriptArtifactFile } from "../../domain/javascript/javascriptA
|
||||
import type { JavaScriptStaticAnalysis } from "../../domain/javascript/javascriptStaticAnalysisTypes.js";
|
||||
import type { JavaScriptModuleSemanticIr } from "../../domain/javascript/javascriptModuleSemanticIr.js";
|
||||
import type { JavaScriptSemanticFileProjection } from "../../domain/javascript/javascriptSemanticFileProjection.js";
|
||||
import { writeJavaScriptAnalysisTransfer } from "./JavaScriptAnalysisTransfer.js";
|
||||
import {
|
||||
javaScriptTransferByteBudget,
|
||||
writeJavaScriptAnalysisTransfer,
|
||||
} from "./JavaScriptAnalysisTransfer.js";
|
||||
import { javaScriptWorkerStaticAnalysisSchema } from "./JavaScriptAnalysisWorkerSchemas.js";
|
||||
import {
|
||||
JAVASCRIPT_ANALYSIS_WORKER_DEFAULTS as DEFAULTS,
|
||||
@@ -95,6 +98,7 @@ const analyzeJob = async (
|
||||
requests: AsyncIterator<string>,
|
||||
): Promise<void> => {
|
||||
try {
|
||||
let resultBudgetBytes = 0;
|
||||
const source = await readFile(join(root, `${request.id}.source`), "utf8");
|
||||
if (
|
||||
createHash("sha256").update(source).digest("hex") !== request.text_sha256
|
||||
@@ -148,7 +152,8 @@ const analyzeJob = async (
|
||||
throw new TypeError(
|
||||
"JavaScript worker admission names a different source job",
|
||||
);
|
||||
return admission.semantic_result_budget_bytes;
|
||||
resultBudgetBytes = admission.semantic_result_budget_bytes;
|
||||
return resultBudgetBytes;
|
||||
},
|
||||
},
|
||||
);
|
||||
@@ -160,6 +165,7 @@ const analyzeJob = async (
|
||||
join(root, `${request.id}.records`),
|
||||
result.module,
|
||||
result.projection,
|
||||
javaScriptTransferByteBudget(resultBudgetBytes),
|
||||
);
|
||||
await send({ kind: "complete", id: request.id, descriptor });
|
||||
} catch (cause: unknown) {
|
||||
|
||||
Reference in new issue
Block a user