mirror of
https://github.com/morluto/rea
synced 2026-10-27 06:33:32 -04:00
fix(javascript): isolate source analysis and retain bounded partial evidence (#1651)
* refactor(javascript): validate transferable file projections * test(javascript): consume decoded module and static facts * fix(javascript): isolate source analysis and retain bounded partial evidence * test(javascript): verify cleanup retention and repeated source paths
This commit is contained in:
@@ -0,0 +1,582 @@
|
||||
import { z } from "zod";
|
||||
import { digestSchema } from "../../domain/digests.js";
|
||||
import type { JavaScriptStaticAnalysis } from "../../domain/javascript/javascriptStaticAnalysisTypes.js";
|
||||
import type {
|
||||
JavaScriptSemanticProperty,
|
||||
JavaScriptSemanticValue,
|
||||
} from "../../domain/javascript/javascriptSemanticValueTypes.js";
|
||||
import {
|
||||
javaScriptSemanticNodeSchema,
|
||||
javaScriptSemanticRelationSchema,
|
||||
javaScriptSemanticUnknownSchema,
|
||||
javaScriptSemanticFingerprintSchema,
|
||||
javaScriptSemanticEvidenceContextSchema,
|
||||
} from "../../domain/javascript/javascriptSemanticGraphSchemas.js";
|
||||
|
||||
const range =
|
||||
javaScriptSemanticNodeSchema.shape.identity.shape.source_range.unwrap();
|
||||
const count = z.number().int().nonnegative();
|
||||
const strings = z.array(z.string());
|
||||
const pathContext = z.enum(["module-specifier", "filesystem-expression"]);
|
||||
const findingContext = {
|
||||
module_key: z.string().nullable(),
|
||||
module_runtime: z.string().optional(),
|
||||
location: range,
|
||||
};
|
||||
const withRuntime = <Value extends { module_runtime?: string | undefined }>(
|
||||
value: Value,
|
||||
) => {
|
||||
const { module_runtime, ...rest } = value;
|
||||
return {
|
||||
...rest,
|
||||
...(module_runtime === undefined ? {} : { module_runtime }),
|
||||
};
|
||||
};
|
||||
const staticValue = z.discriminatedUnion("status", [
|
||||
z.strictObject({
|
||||
status: z.literal("literal"),
|
||||
value: z.union([z.string(), z.number(), z.boolean(), z.null()]),
|
||||
expression: z.null(),
|
||||
}),
|
||||
z.strictObject({
|
||||
status: z.literal("dynamic"),
|
||||
value: z.null(),
|
||||
expression: z.string(),
|
||||
}),
|
||||
]);
|
||||
const nullHandler = { handler_kind: z.null(), handler_location: z.null() };
|
||||
const missingHandler = {
|
||||
handler_kind: z.literal("missing"),
|
||||
handler_location: z.null(),
|
||||
};
|
||||
const locatedHandler = {
|
||||
handler_kind: z.enum([
|
||||
"inline-function",
|
||||
"identifier",
|
||||
"member-expression",
|
||||
"dynamic-expression",
|
||||
]),
|
||||
handler_location: range,
|
||||
};
|
||||
const ipcChannel = <Shape extends z.ZodRawShape>(shape: Shape) =>
|
||||
z.union([
|
||||
z.strictObject({
|
||||
...findingContext,
|
||||
...shape,
|
||||
channel: z.string(),
|
||||
channel_expression: z.null(),
|
||||
}),
|
||||
z.strictObject({
|
||||
...findingContext,
|
||||
...shape,
|
||||
channel: z.null(),
|
||||
channel_expression: z.string(),
|
||||
}),
|
||||
]);
|
||||
const ipc = z
|
||||
.union([
|
||||
ipcChannel({
|
||||
side: z.literal("renderer"),
|
||||
operation: z.enum(["send", "send-sync", "post-message", "send-to-host"]),
|
||||
mode: z.literal("send"),
|
||||
...nullHandler,
|
||||
}),
|
||||
ipcChannel({
|
||||
side: z.literal("renderer"),
|
||||
operation: z.literal("invoke"),
|
||||
mode: z.literal("invoke"),
|
||||
...nullHandler,
|
||||
}),
|
||||
ipcChannel({
|
||||
side: z.enum(["renderer", "main"]),
|
||||
operation: z.enum(["on", "once"]),
|
||||
mode: z.literal("listen"),
|
||||
...missingHandler,
|
||||
}),
|
||||
ipcChannel({
|
||||
side: z.enum(["renderer", "main"]),
|
||||
operation: z.enum(["on", "once"]),
|
||||
mode: z.literal("listen"),
|
||||
...locatedHandler,
|
||||
}),
|
||||
ipcChannel({
|
||||
side: z.literal("main"),
|
||||
operation: z.enum(["handle", "handle-once"]),
|
||||
mode: z.literal("handle"),
|
||||
...missingHandler,
|
||||
}),
|
||||
ipcChannel({
|
||||
side: z.literal("main"),
|
||||
operation: z.enum(["handle", "handle-once"]),
|
||||
mode: z.literal("handle"),
|
||||
...locatedHandler,
|
||||
}),
|
||||
])
|
||||
.transform((value) => {
|
||||
const { module_runtime, ...rest } = value;
|
||||
return {
|
||||
...rest,
|
||||
...(module_runtime === undefined ? {} : { module_runtime }),
|
||||
};
|
||||
});
|
||||
const windowShape = {
|
||||
...findingContext,
|
||||
options_status: z.enum(["object-literal", "dynamic", "missing"]),
|
||||
web_preferences_status: z.enum(["object-literal", "dynamic", "missing"]),
|
||||
web_preferences: z.array(
|
||||
z.strictObject({ name: z.string(), value: staticValue }),
|
||||
),
|
||||
};
|
||||
const bridgeShape = {
|
||||
...findingContext,
|
||||
world: z.enum(["main", "isolated"]),
|
||||
world_id: staticValue.nullable(),
|
||||
api_status: z.enum(["object-literal", "dynamic", "missing"]),
|
||||
members: strings,
|
||||
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",
|
||||
]),
|
||||
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(),
|
||||
}),
|
||||
),
|
||||
});
|
||||
const referenceShape = {
|
||||
...findingContext,
|
||||
kind: z.enum([
|
||||
"static-import",
|
||||
"dynamic-import",
|
||||
"require",
|
||||
"worker",
|
||||
"service-worker",
|
||||
]),
|
||||
};
|
||||
|
||||
/** Exact producer facts recoverable before a later semantic worker failure. */
|
||||
export const javaScriptWorkerStaticAnalysisSchema = z
|
||||
.strictObject({
|
||||
parse_status: z.enum(["complete", "partial", "failed"]),
|
||||
parse_error_count: count,
|
||||
visited_ast_nodes: count,
|
||||
references: z.array(
|
||||
z.union([
|
||||
z
|
||||
.strictObject({
|
||||
...referenceShape,
|
||||
specifier: z.string(),
|
||||
expression: z.null(),
|
||||
})
|
||||
.transform(withRuntime),
|
||||
z
|
||||
.strictObject({
|
||||
...referenceShape,
|
||||
specifier: z.null(),
|
||||
expression: z.string(),
|
||||
})
|
||||
.transform(withRuntime),
|
||||
]),
|
||||
),
|
||||
endpoints: z.array(
|
||||
z.strictObject({
|
||||
...findingContext,
|
||||
kind: z.enum(["route", "network"]),
|
||||
value: z.string(),
|
||||
mechanism: z.string(),
|
||||
}),
|
||||
),
|
||||
storage: z.array(
|
||||
z.strictObject({
|
||||
...findingContext,
|
||||
kind: z.enum([
|
||||
"local-storage",
|
||||
"session-storage",
|
||||
"indexed-db",
|
||||
"cache-storage",
|
||||
"sqlite",
|
||||
]),
|
||||
name: z.string().nullable(),
|
||||
mechanism: z.string(),
|
||||
}),
|
||||
),
|
||||
bundler_registrations: z.array(
|
||||
z.strictObject({
|
||||
bundler: z.enum(["webpack", "rspack", "esbuild"]),
|
||||
runtime: z.string(),
|
||||
chunk_keys: strings,
|
||||
unknown_chunk_keys: count,
|
||||
runtime_require_name: z.string().nullable(),
|
||||
runtime_module_cache_status: z.enum(["observed", "not-observed"]),
|
||||
entry_module_keys: strings,
|
||||
unknown_entry_module_keys: count,
|
||||
async_chunk_keys: strings,
|
||||
unknown_async_chunk_keys: count,
|
||||
modules: z.array(
|
||||
z.strictObject({
|
||||
module_key: z.string(),
|
||||
factory_require_name: z.string().nullable(),
|
||||
source_sha256: digestSchema,
|
||||
exports: strings,
|
||||
location: range,
|
||||
structural_fingerprint_sha256: digestSchema,
|
||||
structural_fingerprint_algorithm: z.literal("babel-ast-v1"),
|
||||
}),
|
||||
),
|
||||
location: range,
|
||||
}),
|
||||
),
|
||||
role_paths: z.array(
|
||||
z.strictObject({
|
||||
...findingContext,
|
||||
role: z.enum(["preload", "renderer"]),
|
||||
path: z.string(),
|
||||
resolution_context: pathContext,
|
||||
mechanism: z.string(),
|
||||
}),
|
||||
),
|
||||
source_map_urls: z.array(
|
||||
z.strictObject({ declared_url: z.string(), location: range }),
|
||||
),
|
||||
vendors: strings,
|
||||
electron,
|
||||
limitations: strings,
|
||||
})
|
||||
.transform((analysis) => ({
|
||||
...analysis,
|
||||
endpoints: analysis.endpoints.map(withRuntime),
|
||||
storage: analysis.storage.map(withRuntime),
|
||||
role_paths: analysis.role_paths.map(withRuntime),
|
||||
electron: {
|
||||
...analysis.electron,
|
||||
sender_validations: analysis.electron.sender_validations.map(withRuntime),
|
||||
native_addon_bindings: analysis.electron.native_addon_bindings.map(
|
||||
(value) => {
|
||||
const { namespace_access, dynamic_member_access, ...rest } =
|
||||
withRuntime(value);
|
||||
return {
|
||||
...rest,
|
||||
...(namespace_access === undefined ? {} : { namespace_access }),
|
||||
...(dynamic_member_access === undefined
|
||||
? {}
|
||||
: { dynamic_member_access }),
|
||||
};
|
||||
},
|
||||
),
|
||||
},
|
||||
})) satisfies z.ZodType<JavaScriptStaticAnalysis>;
|
||||
|
||||
const resourceLimit = z.enum([
|
||||
"primitive-candidates",
|
||||
"primitive-bytes",
|
||||
"expression-depth",
|
||||
]);
|
||||
const primitive = z.union([z.string(), z.number(), z.boolean(), z.null()]);
|
||||
const property: z.ZodType<JavaScriptSemanticProperty> = z.lazy(() =>
|
||||
z.strictObject({
|
||||
name: z.string(),
|
||||
value: semanticValue,
|
||||
presence: z.enum(["present", "absent", "unknown-coverage"]),
|
||||
}),
|
||||
);
|
||||
const semanticValue: z.ZodType<JavaScriptSemanticValue> = z.lazy(() =>
|
||||
z
|
||||
.discriminatedUnion("status", [
|
||||
z.strictObject({ status: z.literal("literal"), value: primitive }),
|
||||
z.strictObject({
|
||||
status: z.literal("union"),
|
||||
values: z.array(primitive),
|
||||
}),
|
||||
z.discriminatedUnion("unknownProperties", [
|
||||
z.strictObject({
|
||||
status: z.literal("object"),
|
||||
properties: z.array(property),
|
||||
unknownProperties: z.literal(false),
|
||||
omittedProperties: z.literal(0),
|
||||
}),
|
||||
z.strictObject({
|
||||
status: z.literal("object"),
|
||||
properties: z.array(property),
|
||||
unknownProperties: z.literal(true),
|
||||
omittedProperties: count.nullable(),
|
||||
}),
|
||||
]),
|
||||
z.discriminatedUnion("unknownItems", [
|
||||
z.strictObject({
|
||||
status: z.literal("array"),
|
||||
items: z.array(property),
|
||||
unknownItems: z.literal(false),
|
||||
omittedItems: z.literal(0),
|
||||
}),
|
||||
z.strictObject({
|
||||
status: z.literal("array"),
|
||||
items: z.array(property),
|
||||
unknownItems: z.literal(true),
|
||||
omittedItems: count.nullable(),
|
||||
}),
|
||||
]),
|
||||
z.strictObject({
|
||||
status: z.enum(["unknown", "ambiguous", "cycle"]),
|
||||
reason: z.string(),
|
||||
resourceLimit: resourceLimit.optional(),
|
||||
}),
|
||||
])
|
||||
.transform((value) => {
|
||||
if (
|
||||
value.status === "literal" ||
|
||||
value.status === "union" ||
|
||||
value.status === "object" ||
|
||||
value.status === "array"
|
||||
)
|
||||
return value;
|
||||
const { resourceLimit: limit, ...rest } = value;
|
||||
return {
|
||||
...rest,
|
||||
...(limit === undefined ? {} : { resourceLimit: limit }),
|
||||
};
|
||||
}),
|
||||
);
|
||||
const origin = z.strictObject({ specifier: z.string(), importedPath: strings });
|
||||
const provenance = z.discriminatedUnion("status", [
|
||||
z.strictObject({
|
||||
status: z.literal("module"),
|
||||
origins: z.tuple([origin]),
|
||||
reason: z.null(),
|
||||
}),
|
||||
z.strictObject({
|
||||
status: z.literal("local"),
|
||||
origins: z.tuple([]),
|
||||
reason: z.null(),
|
||||
}),
|
||||
z.strictObject({
|
||||
status: z.literal("ambiguous"),
|
||||
origins: z.array(origin),
|
||||
reason: z.string(),
|
||||
}),
|
||||
z.strictObject({
|
||||
status: z.enum(["unknown", "cycle"]),
|
||||
origins: z.tuple([]),
|
||||
reason: z.string(),
|
||||
}),
|
||||
]);
|
||||
const coverage = z
|
||||
.discriminatedUnion("status", [
|
||||
z.strictObject({
|
||||
status: z.literal("complete"),
|
||||
omittedCount: z.literal(0),
|
||||
resourceLimits: z.array(resourceLimit).optional(),
|
||||
}),
|
||||
z.strictObject({
|
||||
status: z.literal("partial"),
|
||||
omittedCount: z.union([z.literal(0), z.null()]),
|
||||
resourceLimits: z.array(resourceLimit).optional(),
|
||||
}),
|
||||
z.strictObject({
|
||||
status: z.literal("failed"),
|
||||
omittedCount: z.null(),
|
||||
resourceLimits: z.array(resourceLimit).optional(),
|
||||
}),
|
||||
])
|
||||
.transform((value) => {
|
||||
const { resourceLimits, ...rest } = value;
|
||||
return {
|
||||
...rest,
|
||||
...(resourceLimits === undefined ? {} : { resourceLimits }),
|
||||
};
|
||||
});
|
||||
const moduleLink = z.strictObject({
|
||||
kind: z.enum(["import", "require", "export", "re-export", "commonjs-export"]),
|
||||
specifier: z.string().nullable(),
|
||||
importedName: z.string().nullable(),
|
||||
localName: z.string().nullable(),
|
||||
exportedName: z.string().nullable(),
|
||||
callableId: z.string().nullable(),
|
||||
location: range,
|
||||
});
|
||||
const definitionKind = z.enum([
|
||||
"import",
|
||||
"variable",
|
||||
"parameter",
|
||||
"function",
|
||||
"class",
|
||||
"catch",
|
||||
"assignment",
|
||||
]);
|
||||
const scope = z.strictObject({
|
||||
scopeId: z.string(),
|
||||
parentScopeId: z.string().nullable(),
|
||||
kind: z.enum([
|
||||
"program",
|
||||
"function",
|
||||
"block",
|
||||
"static-block",
|
||||
"class",
|
||||
"catch",
|
||||
]),
|
||||
location: range,
|
||||
bindingsComplete: z.boolean(),
|
||||
bindingIds: strings,
|
||||
});
|
||||
const binding = z.strictObject({
|
||||
bindingId: z.string(),
|
||||
scopeId: z.string(),
|
||||
name: z.string(),
|
||||
kind: definitionKind,
|
||||
mutable: z.boolean(),
|
||||
definitions: z.array(
|
||||
z.strictObject({ kind: definitionKind, location: range }),
|
||||
),
|
||||
value: semanticValue,
|
||||
provenance,
|
||||
});
|
||||
const callable = z.strictObject({
|
||||
callableId: z.string(),
|
||||
kind: z.enum(["function", "class", "method"]),
|
||||
name: z.string().nullable(),
|
||||
containerScopeId: z.string(),
|
||||
bodyScopeId: z.string().nullable(),
|
||||
location: range,
|
||||
returnSites: z.array(
|
||||
z.strictObject({
|
||||
returnSiteId: z.string(),
|
||||
location: range,
|
||||
identityReferenceLocation: range.nullable(),
|
||||
value: semanticValue,
|
||||
}),
|
||||
),
|
||||
returnCoverage: z.strictObject({
|
||||
retainedCount: count,
|
||||
status: z.enum(["complete", "partial"]),
|
||||
omittedCount: z.union([z.literal(0), z.null()]),
|
||||
}),
|
||||
});
|
||||
|
||||
/** Validate each transferred graph fact independently of its emitting realm. */
|
||||
export const javaScriptWorkerGraphRecordSchema = z.discriminatedUnion("kind", [
|
||||
z.strictObject({
|
||||
kind: z.literal("evidence-context"),
|
||||
value: javaScriptSemanticEvidenceContextSchema,
|
||||
}),
|
||||
z.strictObject({
|
||||
kind: z.literal("node"),
|
||||
value: javaScriptSemanticNodeSchema,
|
||||
}),
|
||||
z.strictObject({
|
||||
kind: z.literal("relation"),
|
||||
value: javaScriptSemanticRelationSchema,
|
||||
}),
|
||||
z.strictObject({
|
||||
kind: z.literal("unknown"),
|
||||
value: javaScriptSemanticUnknownSchema,
|
||||
}),
|
||||
z.strictObject({
|
||||
kind: z.literal("fingerprint"),
|
||||
value: javaScriptSemanticFingerprintSchema,
|
||||
}),
|
||||
]);
|
||||
|
||||
/** Portable per-file header, before any graph binding or immutable proof. */
|
||||
export const javaScriptWorkerProjectionHeaderSchema = z.strictObject({
|
||||
roots: z.array(javaScriptSemanticNodeSchema.shape.node_id),
|
||||
truncated: z.boolean(),
|
||||
});
|
||||
|
||||
/** Module facts are transferred individually to avoid a whole-IR JSON string. */
|
||||
export const javaScriptWorkerModuleRecordSchema = z.discriminatedUnion("kind", [
|
||||
z.strictObject({ kind: z.literal("scope"), value: scope }),
|
||||
z.strictObject({ kind: z.literal("binding"), value: binding }),
|
||||
z.strictObject({ kind: z.literal("callable"), value: callable }),
|
||||
z.strictObject({ kind: z.literal("module-link"), value: moduleLink }),
|
||||
]);
|
||||
|
||||
/** Coverage and limitations are independent of module collection sizes. */
|
||||
export const javaScriptWorkerModuleHeaderSchema = z.strictObject({
|
||||
coverage,
|
||||
limitations: strings,
|
||||
});
|
||||
Reference in New Issue
Block a user