Compare commits

...
Author SHA1 Message Date
Kaoru0822andClaude Opus 5.5 3b9b461924 fix(lint): see through type-only wrappers around bulk-append callees
rea/no-bulk-append-spread unwrapped only optional chains, so
`target.push!(...items)`, `(target.push as T)(...items)`, satisfies and
angle-bracket assertions reached the call with a non-member callee and
were not reported. Strip TypeScript wrapper and grouping nodes before
matching the method, and cover those forms and wrapped receivers.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-01 17:03:52 +00:00
Kaoru0822 7d5a459987 Merge remote-tracking branch 'origin/main' into fix/8545-bulk-append-overflow 2026-11-01 17:02:31 +00:00
Kaoru0822andClaude Opus 5.5 fb566c800c fix(analysis): append large inventories without exceeding call-argument limits
Spreading a data-sized collection into push, splice, Math.max/min or a
variadic helper turns each element into a call argument. Above roughly
125,000 elements V8 runs out of stack, so admitted inputs failed with
RangeError: managed/native verification exited with execution_failure,
import-reference-source with UNKNOWN, and DirectoryArtifactReader and
JavaScript export-binding selection threw.

Add appendAll in src/domain/arrayAppend.ts, which appends in place and in
order without expanding elements into arguments, and use it at every
production push/splice spread site. Replace the data-sized Math.max/min and
variadic loader-queue spreads with loops or array parameters. Add the
rea/no-bulk-append-spread lint rule so the pattern cannot return; the few
structurally bounded spreads keep disable comments naming their bound.

Fixes #8545

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-01 16:53:16 +00:00
83 changed files with 699 additions and 204 deletions

No files matched your search

+2
View File
@@ -76,12 +76,14 @@
{
"files": ["src/**/*.ts"],
"rules": {
"rea/no-bulk-append-spread": "error",
"typescript/no-misused-promises": "error"
}
},
{
"files": ["tests/**/*.ts", "src/**/*.test.ts", "src/**/*.fixture.ts"],
"rules": {
"rea/no-bulk-append-spread": "off",
"max-lines": "off",
"max-lines-per-function": [
"error",
+9
View File
@@ -172,6 +172,15 @@ operation names, schemas or types; display-only selection remains allowed.
`rea/no-fixture-root-cleanup` follows `const` fixture-root aliases into teardown
deletion; mutable bindings and mixed root arrays remain outside its scope.
`rea/no-bulk-append-spread` rejects spread arguments to `push`, `unshift` and
`splice` in production sources. Each element becomes a call argument, and V8
bounds argument count by stack size (about 125,000 on a default main-thread
stack), so a data-sized collection throws `RangeError`. Use `appendAll` from
`src/domain/arrayAppend.ts`, or name the structural bound in a disable comment.
The rule does not check receiver types or other variadic calls such as
`Math.max` and `String.fromCharCode`. Reproduce such limits in a forked test
project: worker threads default to a larger stack.
`rea/no-shared-module-mock` resolves Vitest import bindings before rejecting
module mocks in projects without per-file isolation. The test-project definitions
own isolation for Vitest and lint; the executable gate checks the resolved policy
+2
View File
@@ -1,6 +1,7 @@
import noBufferDeepEquality from "./no-buffer-deep-equality.mjs";
import cancellationCause from "./cancellation-cause.mjs";
import noBulkAppendSpread from "./rules/no-bulk-append-spread.mjs";
import noEvidenceJsonPreadmission from "./rules/no-evidence-json-preadmission.mjs";
import noFixtureRootCleanup from "./rules/no-fixture-root-cleanup.mjs";
import noPositionalToolContract from "./rules/no-positional-tool-contract.mjs";
@@ -11,6 +12,7 @@ export default {
rules: {
"no-buffer-deep-equality": noBufferDeepEquality,
"cancellation-cause": cancellationCause,
"no-bulk-append-spread": noBulkAppendSpread,
"no-evidence-json-preadmission": noEvidenceJsonPreadmission,
"no-fixture-root-cleanup": noFixtureRootCleanup,
"no-positional-tool-contract": noPositionalToolContract,
@@ -0,0 +1,3 @@
import type { RuleTester } from "oxlint/plugins-dev";
declare const rule: Parameters<RuleTester["run"]>[1];
export default rule;
@@ -0,0 +1,57 @@
const bulkMethods = new Set(["push", "unshift", "splice"]);
/** Syntax that leaves the evaluated callee or receiver unchanged. */
const transparent = new Set([
"ChainExpression",
"ParenthesizedExpression",
"TSAsExpression",
"TSNonNullExpression",
"TSSatisfiesExpression",
"TSTypeAssertion",
"TSInstantiationExpression",
]);
const unwrap = (node) => {
while (node && transparent.has(node.type)) node = node.expression;
return node;
};
const propertyName = (member) => {
if (!member.computed && member.property.type === "Identifier")
return member.property.name;
if (member.computed && member.property.type === "Literal")
return member.property.value;
return undefined;
};
export default {
meta: {
type: "problem",
docs: {
description:
"Append collections element-wise instead of spreading them into call arguments.",
},
schema: [],
messages: {
spread:
"Spreading into {{method}}() makes each element a call argument, and engines bound argument count by stack size, so a data-sized collection throws RangeError. Use appendAll from src/domain/arrayAppend.ts, or document the structural bound in a disable comment.",
},
},
create(context) {
return {
CallExpression(node) {
const callee = unwrap(node.callee);
if (callee?.type !== "MemberExpression") return;
const method = propertyName(callee);
if (!bulkMethods.has(method)) return;
for (const argument of node.arguments)
if (argument.type === "SpreadElement")
context.report({
node: argument,
messageId: "spread",
data: { method },
});
},
};
},
};
+2 -1
View File
@@ -30,6 +30,7 @@ import {
jadxXrefsSchema,
normalizeJadxText,
} from "./JadxProtocol.js";
import { appendAll } from "../domain/arrayAppend.js";
/** The adapter consumes tool payloads, never provider RPC details outside this layer. */
export interface JadxToolPort {
@@ -336,7 +337,7 @@ const searchClasses = async (
request.operation,
"JADX class pagination is incomplete",
);
matches.push(...page.items);
appendAll(matches, page.items);
offset = page.next_offset;
} while (offset < loaded.class_count);
const occurrences = new Map<string, number>();
+2 -1
View File
@@ -24,6 +24,7 @@ import {
import { androidSnapshotCopy } from "./AndroidTargetSnapshot.js";
import type { JadxSession } from "./JadxSession.js";
import type { JadxMcpTransport } from "./JadxMcpTransport.js";
import { appendAll } from "../domain/arrayAppend.js";
interface FailureContext {
readonly cause: unknown;
@@ -280,7 +281,7 @@ const snapshotCloseFailures = (
cause.outcome.kind === "failed" &&
cause.outcome.cause instanceof RegularFileCleanupFailure
)
failures.push(...snapshotCloseFailures(cause.outcome.cause));
appendAll(failures, snapshotCloseFailures(cause.outcome.cause));
return failures;
};
+2 -1
View File
@@ -70,6 +70,7 @@ import {
parseApktoolYml,
parseManifestPackage,
} from "./ApktoolDecodeOutput.js";
import { appendAll } from "../domain/arrayAppend.js";
const DECODE_MAX_OUTPUT_BYTES = 4 * 1024 * 1024;
const MANIFEST_MAX_BYTES = 512 * 1024;
@@ -656,7 +657,7 @@ export class ApktoolProvider {
const projectedStrings = projection.ok
? projection.value
: projection.error.partial;
limitations.push(...projectedStrings.limitations);
appendAll(limitations, projectedStrings.limitations);
const coverage =
projectedStrings.coverage === "partial" ||
manifestRead.truncated ||
@@ -11,6 +11,7 @@ import {
type RegularFileOperationOutcome,
} from "../filesystem/OwnedRegularFile.js";
import { retryRegularFileCleanupFailures } from "./RegularFileRead.js";
import { appendAll } from "../domain/arrayAppend.js";
/** Completion and exact staging owner retained when publication cleanup fails. */
export class ClientConfigurationPublicationFailure<
@@ -72,7 +73,7 @@ export const retryClientConfigurationCleanup = async (
let nested: unknown;
if (current instanceof RegularFileCleanupFailure) {
const retried = await retryRegularFileCleanupFailures(current, resources);
failures.push(...retried.failures);
appendAll(failures, retried.failures);
for (const lease of retried.cleanupLeases) cleanupLeases.add(lease);
if (retried.cleanup !== undefined) observations.push(retried.cleanup);
primary = retried.primary;
+3 -2
View File
@@ -10,6 +10,7 @@ import type {
DoctorHost,
DoctorProviderInspection,
} from "./Doctor.js";
import { appendAll } from "../domain/arrayAppend.js";
/** Ordered environment diagnostics consumed by the Doctor report assembler. */
export interface DoctorDiagnostics {
@@ -30,13 +31,13 @@ export const collectDoctorDiagnostics = async (
checks.push(hopperCheck(host, hopperPath));
const hopperTimeoutCheck = optionalHopperStartupTimeoutCheck(host);
if (hopperTimeoutCheck !== undefined) checks.push(hopperTimeoutCheck);
checks.push(...runtimeConfigurationChecks(host));
appendAll(checks, runtimeConfigurationChecks(host));
const hopperVersionCheck = await optionalHopperVersionCheck(host, hopperPath);
if (hopperVersionCheck !== undefined) checks.push(hopperVersionCheck);
const providerInspections = await host.providerInspections?.();
if (providerInspections !== undefined)
checks.push(...providerInspections.flatMap(providerDoctorChecks));
appendAll(checks, providerInspections.flatMap(providerDoctorChecks));
const ilspyCheck = await optionalIlspyCheck(host);
if (ilspyCheck !== undefined) checks.push(ilspyCheck);
@@ -37,6 +37,7 @@ import {
} from "./ReconstructionObligationOriginalCases.js";
import { obligationLedgerEvidenceLinks } from "./ReconstructionObligationLedgerProjection.js";
import { compareUnicodeCodePoints } from "../domain/unicodeCodePointOrder.js";
import { appendAll } from "../domain/arrayAppend.js";
type Diagnostic = ReconstructionObligation["diagnostics"][number];
type Binding = NonNullable<ReconstructionObligation["binding"]>;
@@ -210,13 +211,15 @@ const evaluateObligation = (
);
if (binding !== null)
evaluateBinding(candidate, binding, context, diagnostics);
diagnostics.push(
...originalCaseDiagnostics(candidate, originalCases, context.evidence),
appendAll(
diagnostics,
originalCaseDiagnostics(candidate, originalCases, context.evidence),
);
evaluateAuthority(candidate, originalCases, context, diagnostics);
evaluateDisposition(candidate, diagnostics);
diagnostics.push(
...contradictionDiagnostics(
appendAll(
diagnostics,
contradictionDiagnostics(
candidate.obligation_id,
context.contradictions,
context.evidence,
@@ -24,6 +24,7 @@ import type {
ReferenceSourceRead,
} from "../reference/ReferenceSourceReaderTypes.js";
import { typescriptEmittedSourceCandidates } from "./TypeScriptEmittedSourceCandidates.js";
import { appendAll } from "../domain/arrayAppend.js";
export interface ParsedReferenceSourceEntries {
readonly entries: HistoricalSourceGraphInput["entries"];
@@ -473,8 +474,9 @@ const resolveInternalSpecifier = (
// Retain present runtime files first; substitute only a missing counterpart.
// Counterparts cover explicit filenames, not directory/index resolution.
if (!directoryReference)
candidates.push(
...typescriptEmittedSourceCandidates(fromPath, normalized).map(
appendAll(
candidates,
typescriptEmittedSourceCandidates(fromPath, normalized).map(
({ path }) => path,
),
);
@@ -564,7 +566,7 @@ const appendFile = (
limitations: [...(parsed?.limitations ?? [])],
});
if (parsed === undefined) return;
output.limitations.push(...parsed.limitations);
appendAll(output.limitations, parsed.limitations);
for (const relationship of parsed.relationships) {
const { resolutionStrategy, resolutionBasePath, ...graphRelationship } =
relationship;
@@ -598,7 +600,7 @@ const appendFile = (
resolution: resolved.resolution,
});
}
output.parseFailures.push(...parsed.parse_failures);
appendAll(output.parseFailures, parsed.parse_failures);
};
const appendReadEntry = (
+4 -2
View File
@@ -70,6 +70,7 @@ import {
registrationWriteBlocker,
type SetupClient,
} from "./SupportedClients.js";
import { appendAll } from "../domain/arrayAppend.js";
export class ClientConfigurationCleanupRollbackFailure extends RegularFileCleanupFailure<string> {
constructor(
@@ -728,8 +729,9 @@ const registrationOwnershipBlocker = (
value: parsed.legacyServers[name],
paths: [client.configPath],
});
entries.push(
...shadowedClientServers(original, parsed, name).legacyServers.map(
appendAll(
entries,
shadowedClientServers(original, parsed, name).legacyServers.map(
(value) => ({ value, paths: [client.configPath] }),
),
);
+7 -5
View File
@@ -24,6 +24,7 @@ import {
type SkillFileSnapshot,
type SkillPublicationCleanupOwner,
} from "./SkillFilePublication.js";
import { appendAll } from "../domain/arrayAppend.js";
export const MANAGED_SKILL_FILES = [
"SKILL.md",
@@ -272,7 +273,7 @@ export const existingSkillDestinations = async (
try {
const pathIssues = await skillDirectoryIssues([destination]);
if (pathIssues.some(({ kind }) => kind === "unreadable")) {
issues.push(...pathIssues);
appendAll(issues, pathIssues);
continue;
}
if (pathIssues.length > 0) continue;
@@ -478,7 +479,7 @@ const inspectCanonicalSkill = async (
const { path: root } = destination;
const pathIssues = await skillDirectoryIssues([destination]);
if (pathIssues.length > 0) {
issues.push(...pathIssues);
appendAll(issues, pathIssues);
if (failureCause === undefined) failureCause = pathIssues;
installedDestination = true;
canonical = false;
@@ -521,7 +522,7 @@ const inspectCanonicalSkill = async (
if (clientIds !== undefined || rootHasFiles || rootIssues.length > 0) {
installedDestination ||= rootHasFiles;
canonical = canonical && rootCanonical;
issues.push(...rootIssues);
appendAll(issues, rootIssues);
if (!rootCanonical && rootIssues.length === 0)
issues.push({
client: destination.client,
@@ -695,8 +696,9 @@ export const installCanonicalSkill = async (
}
}
const cleanupOwners = [...publications, ...preparedCleanupOwners];
rollbackFailures.push(
...(await closeSkillPublications(cleanupOwners, resources)),
appendAll(
rollbackFailures,
await closeSkillPublications(cleanupOwners, resources),
);
return err(
new SetupSkillInstallError({
+10 -6
View File
@@ -67,6 +67,7 @@ import {
redirectedWriteBlocker,
type MsixPackageView,
} from "./MsixPackageStorage.js";
import { appendAll } from "../domain/arrayAppend.js";
interface ManagedPathStats {
readonly uid?: number;
@@ -253,7 +254,7 @@ const removeAll = async (
];
}
items.push(await host.removeSkill());
if (purgeData) items.push(...(await host.purgeData()));
if (purgeData) appendAll(items, await host.purgeData());
return items;
};
@@ -1175,16 +1176,18 @@ const purgeSelectedData = async (
// Resolve selections before a managed namespace removal can delete cwd.
// Keep the existing result and removal order after planning those facts.
items.unshift(
// oxlint-disable-next-line rea/no-bulk-append-spread -- one item per literal name.
...(await purgeManagedData(appendCallerPath(home, ".rea"), fileSystem, [
"cache",
"state",
])),
);
for (const directory of directories)
items.push(
...(await purgeManagedData(dirname(directory), fileSystem, [
appendAll(
items,
await purgeManagedData(dirname(directory), fileSystem, [
basename(directory),
])),
]),
);
return items;
};
@@ -1538,8 +1541,9 @@ const collapseUninstallItems = (
for (const next of items) {
const { name, actions, ...action } = next;
const group = grouped.get(name) ?? [];
group.push(
...(actions !== undefined && actions.length > 0 ? actions : [action]),
appendAll(
group,
actions !== undefined && actions.length > 0 ? actions : [action],
);
grouped.set(name, group);
}
@@ -43,6 +43,7 @@ import {
parseViewEvidence,
} from "./environmentSource.js";
import { prepareServedState } from "./environmentState.js";
import { appendAll } from "../../domain/arrayAppend.js";
/** Operation name the build command's own refusals report; each stage keeps its own. */
export const ENVIRONMENT_BUILD_OPERATION = "environment-build";
@@ -318,10 +319,8 @@ export const buildEnvironment = async (
name,
operation: ENVIRONMENT_EXPORT_OPERATION,
});
warnings.push(
...credentialScanWarnings(recorded.value),
...savedCredentialWarnings(capture.parameters),
);
appendAll(warnings, credentialScanWarnings(recorded.value));
appendAll(warnings, savedCredentialWarnings(capture.parameters));
const packageSource: EnvironmentPackageSource =
input.packageTarball === undefined
@@ -4,6 +4,7 @@ import { uniqueSorted } from "../../domain/canonicalOrdering.js";
import type { EvidenceIntegrityError } from "../../domain/evidenceErrors.js";
import type { Evidence } from "../../domain/evidence.js";
import { ok, type Result } from "../../domain/result.js";
import { appendAll } from "../../domain/arrayAppend.js";
/** Follow exact session references without exporting unrelated records or Unknowns. */
export const resolveInvestigationEvidenceClosure = (
@@ -30,7 +31,7 @@ export const resolveInvestigationEvidenceClosure = (
);
if (!evidence.ok) return evidence;
visited.add(evidenceId);
pending.push(...evidence.value.evidence_links);
appendAll(pending, evidence.value.evidence_links);
}
return ok(uniqueSorted([...visited]));
};
@@ -7,6 +7,7 @@ import {
addSemanticGraphUnknown,
} from "./JavaScriptSemanticGraphConstruction.js";
import { unknownSemanticEvidence } from "./JavaScriptSemanticGraphEvidence.js";
import { appendAll } from "../../domain/arrayAppend.js";
/** Connect retained reads to writes without claiming assignment values or order. */
export function* projectSemanticAssignments(
@@ -35,8 +36,9 @@ export function* projectSemanticAssignments(
);
// Compound assignments consume the previous value as well as the RHS.
if (flow.operator !== "=")
reads.push(
...readsWithin(flow.target).filter((node) =>
appendAll(
reads,
readsWithin(flow.target).filter((node) =>
sourceRangesEqual(node.identity.source_range, flow.target),
),
);
@@ -91,6 +91,7 @@ import type {
} from "../../domain/javascript/javascriptSemanticFileProjection.js";
import type { ElectronCallPath } from "../../domain/javascript/electronStaticAnalysisTypes.js";
import { projectElectronSemanticCallPaths } from "./JavaScriptSemanticGraphElectronProjection.js";
import { appendAll } from "../../domain/arrayAppend.js";
type ApplicationGraph = Pick<JavaScriptApplicationGraph, "graph_id" | "nodes">;
@@ -276,7 +277,7 @@ export const createJavaScriptSemanticGraphProjection = (
unknownCeiling,
unknownsBefore + fileUnknownCeiling,
);
fingerprints.push(...(yield* projectFileSteps(file, ir, state, paths)));
appendAll(fingerprints, yield* projectFileSteps(file, ir, state, paths));
// Nodes were dropped only if the budget actually blocked creation. A file
// that exactly fills its share ends with a zero budget without dropping
// anything, so the remaining budget alone cannot decide truncation.
@@ -453,7 +454,7 @@ export const createJavaScriptSemanticGraphProjection = (
for (const unknown of newUnknowns)
state.unknowns.set(unknown.unknown_id, unknown);
for (const root of projection.roots) state.roots.add(root);
fingerprints.push(...projection.fingerprints);
appendAll(fingerprints, projection.fingerprints);
if (projection.nodesTruncated) {
truncatedFiles += 1;
noteSemanticCeilingReached(
+11 -7
View File
@@ -16,6 +16,7 @@ import {
nativeCallClassificationSchema,
type NativeCallReference,
} from "../../domain/native/nativeCallReference.js";
import { appendAll } from "../../domain/arrayAppend.js";
/** Detailed or partial observations and the untyped fallback share one owner. */
export const NATIVE_CALL_ROUTE_SOURCES = {
@@ -140,8 +141,9 @@ export const readNativeCallRoutes = async (
outcome,
});
if (execution.ok) {
limitations.push(
...limitationsFrom(
appendAll(
limitations,
limitationsFrom(
NATIVE_CALL_ROUTE_SOURCES.references,
execution.value.provider,
execution.value.limitations,
@@ -168,14 +170,15 @@ export const readNativeCallRoutes = async (
// Missing detailed reference flags do not invalidate observed targetless
// calls. Keep their producing provider even if another provider supplies
// the fallback callee inventory.
unknowns.push(
...parsed.data.unresolved_calls.map((call) => ({
appendAll(
unknowns,
parsed.data.unresolved_calls.map((call) => ({
...call,
provider: execution.value.provider,
})),
);
const references = parsed.data.references;
routes.push(...observedCallRoutes(references, execution.value.provider));
appendAll(routes, observedCallRoutes(references, execution.value.provider));
if (
references.every(({ kind }) => kind.available) &&
(parsed.data.reference_kinds_available || references.length > 0)
@@ -189,8 +192,9 @@ export const readNativeCallRoutes = async (
options,
);
if (!callees.ok) return finish(err(callees.error));
limitations.push(
...limitationsFrom(
appendAll(
limitations,
limitationsFrom(
NATIVE_CALL_ROUTE_SOURCES.callees,
callees.value.provider,
callees.value.limitations,
@@ -11,6 +11,7 @@ import {
nativeInvestigationTraceSchema,
traceNativeInvestigationGraph,
joinInterfaceBuilderDispatch,
type NativeInvestigationGraph,
} from "../../domain/native/nativeInvestigationGraph.js";
import { nativeDispatchMetadataResultSchema } from "../../domain/native/objcSwiftMetadata.js";
import { inspectNativeDispatch } from "./NativeDispatchMetadataInspection.js";
@@ -24,6 +25,7 @@ import {
import { err, ok } from "../../domain/result.js";
import { AnalysisOutputError } from "../../domain/analysisErrorCore.js";
import { projectAnalysisError } from "../../domain/analysisErrorProjection.js";
import { appendAll } from "../../domain/arrayAppend.js";
type Input = z.output<typeof enhancedInputSchemas.trace_native_ui_action>;
@@ -91,7 +93,7 @@ export const traceNativeUiAction = async (
);
let graph = emptyGraph(targetSha256, metadata.provider);
const coverage = [];
const coverage: NativeInvestigationGraph["coverage"][number][] = [];
if (uiExecution.ok) {
const decoded = interfaceBuilderAnalysisSchema.safeParse(
uiExecution.value.result,
@@ -194,7 +196,7 @@ export const traceNativeUiAction = async (
}
}
if (selected.nodeId === null) {
coverage.push(...graph.coverage);
appendAll(coverage, graph.coverage);
const trace = {
target_sha256: graph.target_sha256,
provider: graph.provider,
@@ -458,7 +460,7 @@ const expandNativeCalls = async (input: {
"provider",
);
examined += 1;
sourceLimitations.push(...execution.limitations);
appendAll(sourceLimitations, execution.limitations);
if (
!execution.outcome.ok &&
(execution.outcome.error._tag === "AnalysisCancelledError" ||
+4 -2
View File
@@ -17,6 +17,7 @@ import {
type SourceLimitation,
} from "../SourceLimitations.js";
import { createValueTraceBudget } from "./NativeValueTraceBudget.js";
import { appendAll } from "../../domain/arrayAppend.js";
/** Compose bounded high-p-code def-use graphs across exactly referenced call destinations. */
export const traceNativeValues = async (
@@ -277,8 +278,9 @@ export const traceNativeValues = async (
),
),
);
sourceLimitations.push(
...targets.data.limitations.map((limitation) => ({
appendAll(
sourceLimitations,
targets.data.limitations.map((limitation) => ({
provider: resolved.value.provider,
operation: "resolve_native_call_targets" as const,
limitation,
+3 -2
View File
@@ -44,6 +44,7 @@ import type {
AnalysisPartialObservation,
} from "../domain/analysisErrorBase.js";
import { BinaryTargetError } from "../domain/configurationErrors.js";
import { appendAll } from "../domain/arrayAppend.js";
const RETAINED_METADATA_BYTES = 32 * 1024 * 1024;
/**
@@ -293,7 +294,7 @@ const scanDirectory = async (
if (coverage.examinedEntries !== undefined)
entries = coverage.examinedEntries;
if (coverage.status !== "complete") partial = true;
limitations.push(...coverage.limitations);
appendAll(limitations, coverage.limitations);
},
})) {
entries++;
@@ -318,7 +319,7 @@ const scanDirectory = async (
continue;
}
if (conventions === undefined) continue;
row.facts.push(...conventions.facts);
appendAll(row.facts, conventions.facts);
const managedRoot = conventions.unityManagedDirectory;
if (!probeSelected(entry)) continue;
if (budget.bytes >= budget.maximum) {
+2 -1
View File
@@ -35,6 +35,7 @@ import {
import { AnalysisCleanupLease } from "./AnalysisCleanupLease.js";
import { decodeDirectoryEntryName } from "../filesystem/decodeDirectoryEntryName.js";
import { resolveArtifactSourcePath } from "./ArtifactSelectedSource.js";
import { appendAll } from "../domain/arrayAppend.js";
export type DirectoryArtifactEntry = ArtifactEntry & {
readonly kind: "file" | "directory" | "symlink";
@@ -298,7 +299,7 @@ export class DirectoryArtifactReader implements ArtifactReader {
}
if (traversal.kind === "failed") throw traversal.cause;
childDirectories.reverse();
pending.push(...childDirectories);
appendAll(pending, childDirectories);
}
async #finishDirectory(
+7 -10
View File
@@ -14,6 +14,7 @@ import {
ArtifactReaderFailure,
artifactFixedLimitFailure,
} from "../ArtifactReader.js";
import { appendAll } from "../../domain/arrayAppend.js";
/** Exact trailer written by Bun's standalone module graph serializer. */
export const BUN_TRAILER = Buffer.from("\n---- Bun! ----\n");
@@ -502,8 +503,9 @@ const machOClaims = async (
if (!read.ok) break;
const { command: cmd, offset, size } = read.value;
if (cmd === LC_SEGMENT_64 || cmd === LC_SEGMENT)
claims.push(
...segmentClaims(
appendAll(
claims,
segmentClaims(
commands.subarray(offset, offset + size),
littleEndian,
cmd === LC_SEGMENT_64,
@@ -577,14 +579,9 @@ const fatClaims = async (
for (const slice of parsed.slices) {
abortIfNeeded(signal);
if (parsed.rangeIssues[slice.index] !== null) continue;
claims.push(
...(await machOClaims(
handle,
fileSize,
slice.offset,
slice.size,
signal,
)),
appendAll(
claims,
await machOClaims(handle, fileSize, slice.offset, slice.size, signal),
);
}
return claims;
+4 -3
View File
@@ -41,6 +41,7 @@ import {
observeNodeSeaFuseMatches,
type NodeSeaFuseObservation,
} from "./NodeSeaBlob.js";
import { appendAll } from "../../domain/arrayAppend.js";
const LC_SEGMENT = 0x1;
const LC_SEGMENT_64 = 0x19;
@@ -226,7 +227,7 @@ const locateFatMachO = async (
const found: BlobRange[] = [];
for (const [index, slice] of header.slices.entries()) {
if (slice === undefined || header.rangeIssues[index] != null) continue;
found.push(...(await locateThinMachO(carrier, slice.offset, slice.size)));
appendAll(found, await locateThinMachO(carrier, slice.offset, slice.size));
}
return found;
};
@@ -396,7 +397,7 @@ const locateElfNotes = async (
found.length > 0,
);
if (scanned === undefined) opaque = true;
else found.push(...scanned);
else appendAll(found, scanned);
}
// The runtime locates the blob through PT_NOTE segments. Injectors can
// leave stale or misaligned SHT_NOTE entries, so section notes are only a
@@ -444,7 +445,7 @@ const locateElfNotes = async (
found.length > 0,
);
if (scanned === undefined) opaque = true;
else found.push(...scanned);
else appendAll(found, scanned);
}
if (found.length > 0 && opaque)
throw formatFailure(NODE_SEA_ELF_TABLE_TRUNCATED);
+6 -5
View File
@@ -32,6 +32,7 @@ import {
XHTML_NAMESPACE,
} from "./CdpCaptureDomNamespaces.js";
import { compareUnicodeCodePoints } from "../domain/unicodeCodePointOrder.js";
import { appendAll } from "../domain/arrayAppend.js";
export type CapturedResource = Omit<
WebPageInspection["resources"][number],
@@ -403,8 +404,8 @@ export function* captureDom(
pendingMetadata,
completeness,
);
urls.push(...metadata.urls);
agentHints.push(...metadata.agentHints);
appendAll(urls, metadata.urls);
appendAll(agentHints, metadata.agentHints);
excludedUrls += metadata.excludedUrls;
unresolvedSvgNodes += metadata.unresolvedSvgNodes;
pendingMetadata.length = 0;
@@ -412,8 +413,8 @@ export function* captureDom(
}
if (pendingMetadata.length > 0) {
const metadata = yield* captureDomMetadata(pendingMetadata, completeness);
urls.push(...metadata.urls);
agentHints.push(...metadata.agentHints);
appendAll(urls, metadata.urls);
appendAll(agentHints, metadata.agentHints);
excludedUrls += metadata.excludedUrls;
unresolvedSvgNodes += metadata.unresolvedSvgNodes;
pendingMetadata.length = 0;
@@ -499,7 +500,7 @@ function* captureDomMetadata(
);
}
}
agentHints.push(...metadata.agentHints);
appendAll(agentHints, metadata.agentHints);
}
return { urls, agentHints, unresolvedSvgNodes, excludedUrls };
}
+2 -1
View File
@@ -53,6 +53,7 @@ import {
type CapturedScript,
type NetworkState,
} from "./CdpCaptureEventTypes.js";
import { appendAll } from "../domain/arrayAppend.js";
export const handleExecutionContextCreated = (
state: CdpCaptureEventsState,
@@ -434,7 +435,7 @@ export const handleResponseReceived = (
state.allowedOrigins,
);
state.responseMetadata.push(metadata.response);
state.agentHints.push(...metadata.agentHints);
appendAll(state.agentHints, metadata.agentHints);
};
export const handleLoadingFinished = (
+5 -3
View File
@@ -19,6 +19,7 @@ import {
scenarioCommandTimeoutMs,
withScenarioCommandDeadline,
} from "./ScenarioCommandDeadline.js";
import { appendAll } from "../domain/arrayAppend.js";
const breakpointResponse = z.object({ breakpointId: z.string() });
const scriptIdentifierResponse = z.object({ identifier: z.string() });
@@ -281,8 +282,9 @@ export class PlaywrightScenarioStorage {
): void {
for (const { origin, entries } of blocks) {
const seed = this.#seeds.get(origin) ?? { local: [], session: [] };
seed[kind].push(
...entries.map(({ name, value }): [string, string] => [
appendAll(
seed[kind],
entries.map(({ name, value }): [string, string] => [
name,
secrets.value(value),
]),
@@ -553,7 +555,7 @@ export class PlaywrightScenarioStorage {
if (origin !== undefined && origin !== "") return origin;
break;
}
pending.push(...(current.childFrames ?? []));
appendAll(pending, current.childFrames ?? []);
}
throw new Error(
"The browser did not establish the private storage seed frame's security origin.",
@@ -20,6 +20,7 @@ import {
WEB_NETWORK_CAPTURE_LIMITS,
webNetworkCapturePayloadSchema,
} from "../../domain/webNetworkCapture.js";
import { appendAll } from "../../domain/arrayAppend.js";
const replySchema = z.discriminatedUnion("ok", [
z.strictObject({
@@ -160,5 +161,5 @@ const excludeNormalizedNumbers = (
projection.numeric_literals = projection.numeric_literals.filter(
(number) => !excluded.has(number.pointer),
);
projection.redactions.push(...redaction.redactions);
appendAll(projection.redactions, redaction.redactions);
};
@@ -37,6 +37,7 @@ import {
type PreparedProtobufBody,
} from "./ProtobufFraming.js";
import type { ProtobufDecoding } from "../../../domain/protobufWire.js";
import { appendAll } from "../../../domain/arrayAppend.js";
const object = (
value: JsonValue | undefined,
@@ -361,7 +362,7 @@ export const prepareCaptureProtobuf = async (
selected_records: input.record_ordinals.length,
inspected_records: 0,
};
capture.limitations.push(...PROTOBUF_LIMITATIONS);
appendAll(capture.limitations, PROTOBUF_LIMITATIONS);
let incomplete = false;
const records: { record: WebNetworkCaptureRecord; bodies: PreparedBody[] }[] =
[];
+2 -1
View File
@@ -2,6 +2,7 @@ import { positiveDecimalIntegerSchema } from "./positiveDecimalInteger.js";
import { ConfigurationError } from "../domain/configurationErrors.js";
import { err, ok, type Result } from "../domain/result.js";
import { appendAll } from "../domain/arrayAppend.js";
/** Smallest phase budget. Zero is not a deadline, and Node's timer needs a positive delay. */
export const MCP_PHASE_BUDGET_FLOOR_MS = 1;
@@ -135,7 +136,7 @@ export const parseMcpStartupBudgets = (
const budgets = new Map<keyof McpStartupBudgets, McpPhaseBudget>();
for (const phase of PHASES) {
const parsed = parsePhaseBudget(phase, environment[phase.setting]);
if (!parsed.ok) issues.push(...parsed.error.settings);
if (!parsed.ok) appendAll(issues, parsed.error.settings);
else budgets.set(phase.key, parsed.value);
}
if (issues.length > 0)
+3 -2
View File
@@ -8,6 +8,7 @@ import {
schemaMaps,
schemaLiteralValues,
} from "./jsonSchemaKeywords.js";
import { appendAll } from "../domain/arrayAppend.js";
// Shared compact JSON Schema projection primitives; canonical validation stays separate.
@@ -407,7 +408,7 @@ const reducedItemSchema = (
for (const { schema, scope } of alternatives) {
const detected = detectedTypes(schema, references, scope);
if (detected === undefined || detected.length === 0) return {};
types.push(...detected);
appendAll(types, detected);
}
const unique = [...new Set(types)];
if (!unique.includes("array"))
@@ -417,7 +418,7 @@ const reducedItemSchema = (
for (const { schema, scope } of alternatives) {
const items = arrayItemAlternatives(schema, references, scope);
if (items === undefined) return { type: "array", items: {} };
nested.push(...items);
appendAll(nested, items);
}
return {
type: "array",
@@ -914,9 +914,9 @@ export class EnvironmentStateWriteSimulation {
readonly view: StateView;
}): void {
const ordinal = failed.record.ordinal;
const since = Math.min(
...failed.keys.map((key) => this.#lastCheck.get(key) ?? -1),
);
let since = Infinity;
for (const key of failed.keys)
since = Math.min(since, this.#lastCheck.get(key) ?? -1);
const blame = this.#blame.of({ ...failed, since });
for (const name of blame.confirmed)
this.#drafts.get(name)!.confirmed.push(ordinal);
@@ -5,6 +5,7 @@ import {
type SpellingSummary,
} from "./environmentManifestSpelling.js";
import type { RouteRule } from "./webNetworkRouteTemplates.js";
import { appendAll } from "../arrayAppend.js";
/** Opt-in template rules applied after the four exact format rules. */
export const TEMPLATE_RULES = [
@@ -275,10 +276,17 @@ const mergeSiblings = (
];
};
/** Segment count of the longest group; groups are as many as the observed tools. */
const longestParts = (groups: readonly ToolGroup[]): number => {
let longest = 0;
for (const group of groups) longest = Math.max(longest, group.parts.length);
return longest;
};
/** Merge groups that differ in exactly one identifier-like segment at index two or later. */
const mergeSiblingVariance = (groups: readonly ToolGroup[]): ToolGroup[] => {
let current = [...groups];
const longest = Math.max(0, ...current.map((group) => group.parts.length));
const longest = longestParts(current);
for (let position = 2; position < longest; position += 1) {
const buckets = new Map<string, ToolGroup[]>();
const output: ToolGroup[] = [];
@@ -300,7 +308,7 @@ const mergeSiblingVariance = (groups: readonly ToolGroup[]): ToolGroup[] => {
else bucket.push(group);
}
for (const bucket of buckets.values())
output.push(...mergeSiblings(bucket, position));
appendAll(output, mergeSiblings(bucket, position));
current = output;
}
return current;
@@ -372,7 +380,7 @@ const mergePairBucket = (
/** Merge groups that differ in two adjacent literals after a plural literal, such as `/repos/{owner}/{repo}/…`. */
const mergeSiblingPairs = (groups: readonly ToolGroup[]): ToolGroup[] => {
let current = [...groups];
const longest = Math.max(0, ...current.map((group) => group.parts.length));
const longest = longestParts(current);
for (let position = 2; position + 1 < longest; position += 1) {
const buckets = new Map<string, ToolGroup[]>();
const output: ToolGroup[] = [];
@@ -394,7 +402,7 @@ const mergeSiblingPairs = (groups: readonly ToolGroup[]): ToolGroup[] => {
else bucket.push(group);
}
for (const bucket of buckets.values())
output.push(...mergePairBucket(bucket, position));
appendAll(output, mergePairBucket(bucket, position));
current = output;
}
return current;
@@ -489,7 +497,7 @@ const sharedShape = (bucket: readonly ToolGroup[]): ToolGroup => {
/** Template literals at positions another tool templates by an opt-in rule after the same preceding parts, at any method or length. */
const mergePrefixVariance = (groups: readonly ToolGroup[]): ToolGroup[] => {
let current = [...groups];
const longest = Math.max(0, ...current.map((group) => group.parts.length));
const longest = longestParts(current);
for (let position = 0; position < longest; position += 1) {
const shared = sharedPositions(current, position);
if (shared.size === 0) continue;
@@ -45,6 +45,14 @@ export interface WriteHistory {
readonly unmodelledAt: readonly number[];
}
/** A target's last check or pending write, whichever ran later. */
const lastRecorded = (history: WriteHistory, key: string): number => {
let latest = history.checked.get(key) ?? -1;
for (const at of history.pending.get(key)?.at.values() ?? [])
latest = Math.max(latest, at);
return latest;
};
/** The tools a failed read's difference an unmodelled write may explain: it came after the tool's call on a target where its effect survives, and after every differing target's last check and pending writes. */
export const interference = (
history: WriteHistory,
@@ -58,12 +66,7 @@ export const interference = (
const after = (since: number) =>
history.unmodelledAt.some((index) => index > since);
const explained = (read.differing ?? []).every((key) =>
after(
Math.max(
history.checked.get(key) ?? -1,
...(history.pending.get(key)?.at.values() ?? []),
),
),
after(lastRecorded(history, key)),
);
return (tool) =>
explained &&
@@ -71,6 +71,7 @@ import {
type SlugSource,
type UpdateEvidence,
} from "./environmentStateWriteChanges.js";
import { appendAll } from "../arrayAppend.js";
/** What explaining a create reads: the observed entity, the call that wrote it and the entities it joined. */
export interface CreateEvidence {
@@ -604,12 +605,12 @@ export const explainUpdate = (
defaults.push({ member: field, value: evidence.after[field]! });
else unexplained.push(field);
}
rules.push(...pairedStamps(evidence, rules));
appendAll(rules, pairedStamps(evidence, rules));
// A member a reference reads names an entity, which any held one may be.
for (const rule of rules)
if (rule.rule === "reference") echoed.add(rule.member);
// Serving renews one UUID alike in every field, so the call must have.
unexplained.push(...unexplainedRenewals(rules, evidence));
appendAll(unexplained, unexplainedRenewals(rules, evidence));
const pinned = unreferenced(rules, pinnedEchoes(evidence.after, body));
const shaped = bodyOf(
Object.fromEntries(
@@ -16,6 +16,7 @@ import {
completeWithinViewCoverage,
pageViewCoverage,
} from "./analysisViewCoverage.js";
import { appendAll } from "../arrayAppend.js";
type Artifact = { readonly path: string; readonly sha256: string };
@@ -40,11 +41,10 @@ const parentFields = (
"native_value_flow is truncated; omitted observations remain unknown",
);
if (api === null) unknowns.push("native_api was not recorded");
else if (!api.available)
unknowns.push(
"native_api unavailable: " + api.reason,
...api.residual_unknowns,
);
else if (!api.available) {
unknowns.push("native_api unavailable: " + api.reason);
appendAll(unknowns, api.residual_unknowns);
}
return {
parent_evidence_id: parent.evidenceId,
parent_operation: "analyze_function" as const,
+1
View File
@@ -50,6 +50,7 @@ export const resolveTreePath = async (
hops += 1;
if (hops > MAX_SYMLINK_HOPS) return { kind: "absent" };
if (entry.target.startsWith("/")) return { kind: "escapes" };
// oxlint-disable-next-line rea/no-bulk-append-spread -- the tree view reads link text with readlink, which the host bounds by PATH_MAX.
pending.unshift(...segmentsOf(entry.target));
continue;
}
+6 -4
View File
@@ -21,6 +21,7 @@ import {
isLazyLoadCommand,
type TracedRootSlice,
} from "./dylibResolutionFindings.js";
import { appendAll } from "../arrayAppend.js";
/**
* Normalized path below the analyzed root: nonempty segments joined by single
@@ -654,7 +655,7 @@ const traceProcess = async (
context.byInstallName.set(slice.install_name, image);
created.push(image);
}
pending.push(...created.reverse());
appendAll(pending, created.reverse());
}
return edges;
};
@@ -790,8 +791,9 @@ export const traceDylibLoading = async (
environmentRoots.add(root);
roots.push({ image: root, architecture: slice.architecture });
rootSlices.push({ image: root, slice });
edges.push(
...(await traceProcess(
appendAll(
edges,
await traceProcess(
{
view,
root,
@@ -813,7 +815,7 @@ export const traceDylibLoading = async (
},
slice,
request.signal,
)),
),
);
}
}
+4 -2
View File
@@ -20,6 +20,7 @@ import {
nativeInvestigationNodeSchema,
nativeInvestigationTraceLimitsSchema,
} from "../native/nativeInvestigationGraph.js";
import { appendAll } from "../arrayAppend.js";
/** One compiled Interface Builder document projected from ibtool output. */
export const interfaceBuilderDocumentSchema = z.strictObject({
@@ -495,8 +496,9 @@ export const buildInterfaceBuilderAnalysis = (input: {
: hierarchyTruncated
? "partial"
: "complete";
numberLimitations.push(
...(numbers?.limitations() ?? []).map(
appendAll(
numberLimitations,
(numbers?.limitations() ?? []).map(
(note) => `${document.relativePath}: ${note}`,
),
);
+11
View File
@@ -0,0 +1,11 @@
import { expect, it } from "vitest";
import { appendAll } from "./arrayAppend.js";
it("appends in order like a spread, including an array appended to itself", () => {
const target = ["a", "b"];
appendAll(target, ["c", "d"]);
expect(target).toEqual(["a", "b", "c", "d"]);
appendAll(target, target);
expect(target).toEqual(["a", "b", "c", "d", "a", "b", "c", "d"]);
});
+14
View File
@@ -0,0 +1,14 @@
/**
* Append items in order, as `target.push(...items)` would, without turning
* each item into a call argument. Engines bound argument count by stack size,
* so spreading a data-sized collection throws `RangeError`. The count is read
* once, so appending an array to itself duplicates it like a spread.
*/
export const appendAll = <Item>(
target: Item[],
items: readonly NoInfer<Item>[],
): void => {
const count = items.length;
for (let index = 0; index < count; index += 1)
target.push(items[index] as Item);
};
+4 -2
View File
@@ -25,6 +25,7 @@ import { evidenceSchema, parseEvidence, type Evidence } from "./evidence.js";
import { offsetText } from "./firmware/espImageStructures.js";
import { jsonValueSchema } from "./jsonValue.js";
import { prefixedDigestSchema } from "./../domain/digests.js";
import { appendAll } from "./arrayAppend.js";
const textSchema = z.string().min(1);
const evidenceIdSchema = prefixedDigestSchema("ev");
@@ -549,8 +550,9 @@ const allUnexploredBranches = (
nextProbes: ArtifactInspectionResult["next_probes"],
): ArtifactInspectionResult["unexplored_branches"] => {
const branches: ArtifactInspectionResult["unexplored_branches"] = [];
branches.push(
...nextProbes.map((probe) =>
appendAll(
branches,
nextProbes.map((probe) =>
branch(
"format-specific-analysis-required",
`${probe.operation} was not run by provider-neutral artifact inspection.`,
+1
View File
@@ -69,6 +69,7 @@ export const percentDecodeUrlPath = (value: string): string | null => {
continue;
}
const character = String.fromCodePoint(value.codePointAt(index) ?? 0);
// oxlint-disable-next-line rea/no-bulk-append-spread -- one code point encodes to at most four UTF-8 bytes.
bytes.push(...UTF8_ENCODER.encode(character));
index += character.length;
}
+6 -5
View File
@@ -19,6 +19,7 @@ import {
validBasicCredentialUsername,
} from "./browserScenarioValues.js";
import type { JsonValue } from "./jsonValue.js";
import { appendAll } from "./arrayAppend.js";
export const browserScenarioInputSchema = z.strictObject({
browser: browserScenarioBrowserSchema.describe(
@@ -226,13 +227,13 @@ const validateRequestSteps = (
const validateStorage = (scenario: ScenarioShape): readonly string[] => {
const references: string[] = [];
scenario.storage.cookies.forEach((cookie) => {
references.push(...secretReferencesInValue(cookie.value));
references.push(...destinationSecretReferences(cookie.destination));
appendAll(references, secretReferencesInValue(cookie.value));
appendAll(references, destinationSecretReferences(cookie.destination));
});
for (const collection of ["local_storage", "session_storage"] as const)
scenario.storage[collection].forEach((storage) => {
for (const entry of storage.entries)
references.push(...secretReferencesInValue(entry.value));
appendAll(references, secretReferencesInValue(entry.value));
});
return references;
};
@@ -287,8 +288,8 @@ const validateBrowserScenario = (
validateRequestSteps(scenario, context);
validateSavedValues(scenario, context);
const references = scenario.actions.flatMap(actionSecretReferences);
references.push(...destinationSecretReferences(scenario.start_url));
references.push(...validateStorage(scenario));
appendAll(references, destinationSecretReferences(scenario.start_url));
appendAll(references, validateStorage(scenario));
validateSecretReferences(scenario, references, context);
};
+2 -1
View File
@@ -17,6 +17,7 @@ import {
} from "./functionDossierEvidence.js";
import { isCallerOnlyIncompleteCoverage } from "./functionCallCoverage.js";
import { compareUnicodeCodePoints } from "./unicodeCodePointOrder.js";
import { appendAll } from "./arrayAppend.js";
/** Minimal directed caller-to-callee adjacency for call-path search. */
class CallGraph {
@@ -271,7 +272,7 @@ const inspectSearch = ({
const coverage = snapshot.evidence.relationship_coverage;
for (const limitation of snapshot.limitations)
limitations.push(limitation);
limitations.push(...snapshot.unresolvedCallCaveats);
appendAll(limitations, snapshot.unresolvedCallCaveats);
if (coverage !== null) {
for (const limitation of relationshipCoverageLimitations(coverage))
limitations.push(limitation);
+3 -2
View File
@@ -19,6 +19,7 @@ import {
type EspBootSelection,
type EspOtaRecord,
} from "./espOtaData.js";
import { appendAll } from "../arrayAppend.js";
/** Where the file's first byte sits in flash, and who said so. */
export interface EspFlashOrigin {
@@ -224,9 +225,9 @@ export const planEspFlashLayout = (input: EspFlashPlanInput): EspFlashPlan => {
// what the file lacks instead of a missing path.
if (placed.fileOffset >= 0) regions.push(placed);
}
limitations.push(...overlaps(regions));
appendAll(limitations, overlaps(regions));
const boot = espBootPartitions(input.table.partitions);
limitations.push(...boot.limitations);
appendAll(limitations, boot.limitations);
return {
input,
regions,
+2 -1
View File
@@ -26,6 +26,7 @@ import {
u32,
type EspSignedContentDigests,
} from "./espImageStructures.js";
import { appendAll } from "../arrayAppend.js";
const SIGNATURE_SLOTS = 3;
@@ -177,7 +178,7 @@ export const deriveEspImage = (observed: EspImageObservations): EspImage => {
const segments = observed.segments.map((segment) =>
segmentFacts(observed, segment, chip),
);
limitations.push(...segmentLimitations(chipId, chip, segments));
appendAll(limitations, segmentLimitations(chipId, chip, segments));
const truncated = truncation(observed, segments, limitations);
const checksum = checksumFacts(observed, truncated, limitations);
const appendedSha256 = digestFacts(observed, limitations);
@@ -18,6 +18,7 @@ import {
resolveSemanticBindingState,
type JavaScriptBindingIndex,
} from "./javascriptSemanticState.js";
import { appendAll } from "../arrayAppend.js";
/** Static CommonJS export names. */
export interface StaticExports {
@@ -31,7 +32,7 @@ export const fingerprintJavaScriptAst = (node: t.Node): string => {
const tokens: string[] = [];
traverseJavaScriptAst(node, {
enter: (current) => {
tokens.push(...semanticTokens(current));
appendAll(tokens, semanticTokens(current));
return undefined;
},
});
@@ -17,6 +17,7 @@ import type {
JavaScriptRpcEndpoint,
JavaScriptRpcScan,
} from "./javascriptRpcEndpoint.js";
import { appendAll } from "../arrayAppend.js";
/**
* Find source-body string values owned by a GraphQL Source-shaped literal.
@@ -59,7 +60,7 @@ const graphqlSourceBodyLiterals = (
const current = pending.pop();
if (current === undefined || visited.has(current)) continue;
visited.add(current);
bodies.push(...bodyStrings(current));
appendAll(bodies, bodyStrings(current));
for (const property of current.properties) {
if (t.isSpreadElement(property)) {
const spread = unwrapJavaScriptExpression(property.argument).node;
@@ -79,7 +79,8 @@ export const semanticRuntimeExportValueSources = (
const values: t.Node[] = [];
if (t.isVariableDeclaration(node.declaration))
for (const declaration of node.declaration.declarations)
values.push(...semanticWriteTargets(declaration.id));
for (const target of semanticWriteTargets(declaration.id))
values.push(target);
else if (
(t.isFunctionDeclaration(node.declaration) ||
t.isClassDeclaration(node.declaration)) &&
@@ -12,6 +12,7 @@ import {
mappedRuntimePaths,
staticNodeRuntimeScope,
} from "./javascriptRuntimeStaticCandidates.js";
import { appendAll } from "../arrayAppend.js";
export interface StaticLoadStateProjection {
readonly states: readonly JavaScriptStaticLoadState[];
@@ -39,7 +40,7 @@ export const classifyStaticLoadStates = (
reconciliationComplete: options.reconciliationComplete,
},
);
states.push(...projection.states);
appendAll(states, projection.states);
}
return { states, omittedStates: 0 };
};
@@ -32,6 +32,7 @@ import {
semanticAliasSelectionChoices,
type SemanticAliasSelectionPath,
} from "./javascriptSemanticAliasSelections.js";
import { appendAll } from "../arrayAppend.js";
export interface SemanticCapturedSelection {
readonly at: t.Node;
@@ -143,8 +144,9 @@ const hasFreshProjectedReceiver = (
state.valueResourceLimits.add("alias-selections");
return false;
}
pending.push(
...sources.map((source) => ({
appendAll(
pending,
sources.map((source) => ({
node: source.node,
path: [...source.path, ...remaining],
})),
@@ -62,6 +62,7 @@ import {
isUnmodifiedGlobalReference,
isUnmodifiedImportMetaUrl,
} from "./javascriptSemanticAmbientWrites.js";
import { appendAll } from "../arrayAppend.js";
export type {
JavaScriptRequireLoaderFact,
@@ -413,8 +414,9 @@ const selectedRequireSourcesAt = (
return { loader: "unknown", sourceRelative: false };
}
if (t.isMemberExpression(node) || t.isOptionalMemberExpression(node)) {
pending.push(
...inheritLoaderSourceContext(current, [
appendAll(
pending,
inheritLoaderSourceContext(current, [
loaderMemberSource(current, node),
]),
);
@@ -498,8 +500,9 @@ const selectedRequireSourcesAt = (
node,
state.parentsByNode,
).binding;
pending.push(
...inheritLoaderSourceContext(
appendAll(
pending,
inheritLoaderSourceContext(
current,
projectedLoaderSources(
current,
@@ -522,8 +525,9 @@ const selectedRequireSourcesAt = (
facts.retainsOrigin &&
binding.directOrigins.length > 0
);
pending.push(
...inheritLoaderSourceContext(
appendAll(
pending,
inheritLoaderSourceContext(
current,
projectedLoaderSources(current, facts.sources),
),
@@ -536,7 +540,7 @@ const selectedRequireSourcesAt = (
}
const sources = loaderObjectPropertySources(current, node, state);
unknown ||= sources.length === 0;
pending.push(...inheritLoaderSourceContext(current, sources));
appendAll(pending, inheritLoaderSourceContext(current, sources));
} else if (t.isSequenceExpression(node)) {
const last = node.expressions.at(-1);
if (last !== undefined) pending.push({ ...current, node: last });
@@ -576,8 +580,9 @@ const selectedRequireSourcesAt = (
returned.unresolvedInvocation === true ||
!t.isArrowFunctionExpression(callee) ||
t.isBlockStatement(callee.body);
pending.push(
...inheritLoaderSourceContext(
appendAll(
pending,
inheritLoaderSourceContext(
current,
returned.references.map((reference) => ({
node: reference.node,
@@ -1117,29 +1122,30 @@ const hasCommonJsModuleSource = (
examined.add(identity);
const enqueueAt = (
depths: readonly number[],
...sources: ModuleObjectSource[]
sources: readonly ModuleObjectSource[],
) => {
pending.push(
...inheritLoaderSourceContext(next, sources).map((source) => ({
appendAll(
pending,
inheritLoaderSourceContext(next, sources).map((source) => ({
...source,
awaitDepths: depths,
})),
);
};
const enqueue = (...sources: ModuleObjectSource[]) => {
enqueueAt(awaitDepths, ...sources);
const enqueue = (sources: readonly ModuleObjectSource[]) => {
enqueueAt(awaitDepths, sources);
};
if (t.isAwaitExpression(value)) {
enqueueAt(
[...new Set([...awaitDepths, path.length])].sort(
(left, right) => left - right,
),
{ node: value.argument, path },
[{ node: value.argument, path }],
);
continue;
}
if (t.isMemberExpression(value) || t.isOptionalMemberExpression(value)) {
enqueue(loaderMemberSource({ ...next, path }, value));
enqueue([loaderMemberSource({ ...next, path }, value)]);
continue;
}
if (
@@ -1149,12 +1155,12 @@ const hasCommonJsModuleSource = (
enqueueAt(
// Awaiting a fresh container does not await one of its children.
awaitDepths.filter((depth) => depth !== path.length),
...loaderObjectPropertySources({ ...next, path }, value, state),
loaderObjectPropertySources({ ...next, path }, value, state),
);
continue;
}
if (t.isConditionalExpression(value) || t.isLogicalExpression(value)) {
enqueue(
enqueue([
{
node: t.isConditionalExpression(value)
? value.consequent
@@ -1167,16 +1173,16 @@ const hasCommonJsModuleSource = (
: value.right,
path,
},
);
]);
continue;
}
if (t.isSequenceExpression(value)) {
const last = value.expressions.at(-1);
if (last !== undefined) enqueue({ node: last, path });
if (last !== undefined) enqueue([{ node: last, path }]);
continue;
}
if (t.isAssignmentExpression(value)) {
enqueue(...assignmentValueSources(value).map((node) => ({ node, path })));
enqueue(assignmentValueSources(value).map((node) => ({ node, path })));
continue;
}
if (
@@ -1193,7 +1199,7 @@ const hasCommonJsModuleSource = (
);
if (returnedResult.unresolvedInvocation) return true;
enqueue(
...returnedResult.references.map((source) => ({
returnedResult.references.map((source) => ({
node: source.node,
path: [...source.projection, ...path],
capturedSelections: semanticShiftCapturedSelections(
@@ -1325,7 +1331,7 @@ const hasCommonJsModuleSource = (
eligibility = true;
}
enqueue(
...projectedLoaderSources({ ...next, node: value, path }, sources).map(
projectedLoaderSources({ ...next, node: value, path }, sources).map(
(source) => (eligibility ? { ...source, eligibility } : source),
),
);
@@ -35,6 +35,7 @@ import type {
JavaScriptBundlerRegistration,
} from "./javascriptStaticAnalysisTypes.js";
import { JAVASCRIPT_AST_FINGERPRINT_ALGORITHM } from "./javascriptStaticAnalysisTypes.js";
import { appendAll } from "../arrayAppend.js";
/** Inspect a bundler registration call and recover its module table. */
export const inspectBundlerRegistration = (
@@ -230,7 +231,7 @@ const recoverBundlerModules = (
);
if (recovered === null) continue;
modules.push(recovered.module);
asyncChunkKeys.push(...recovered.asyncChunkKeys);
appendAll(asyncChunkKeys, recovered.asyncChunkKeys);
unknownAsyncChunkKeys += recovered.unknownAsyncChunkKeys;
}
return {
@@ -45,6 +45,7 @@ import {
type SymbolicEnvironment,
type SymbolicFunction,
} from "./javascriptSymbolicValues.js";
import { appendAll } from "../arrayAppend.js";
interface PropertyWrite {
readonly path: readonly string[];
@@ -1238,10 +1239,10 @@ export class JavaScriptSymbolicBindings {
returnedValue,
depth + 1,
);
possible.push(...flow.values.map(guardCurrentPath));
appendAll(possible, flow.values.map(guardCurrentPath));
if (!flow.fallsThrough)
return { values: possible, fallsThrough: false };
continuationConditions.push(...(flow.conditions ?? []));
appendAll(continuationConditions, flow.conditions ?? []);
unresolvedFallthrough ||= flow.conditionUnresolved === true;
}
if (t.isIfStatement(statement)) {
@@ -1258,8 +1259,9 @@ export class JavaScriptSymbolicBindings {
depth + 1,
),
);
possible.push(
...flows.flatMap((flow, index) =>
appendAll(
possible,
flows.flatMap((flow, index) =>
flow.values.map((value) => {
const guarded = guardCurrentPath(value);
return truth === undefined
@@ -1274,7 +1276,7 @@ export class JavaScriptSymbolicBindings {
if (flows.every((flow) => !flow.fallsThrough))
return { values: possible, fallsThrough: false };
if (truth !== undefined) {
continuationConditions.push(...(flows[0]?.conditions ?? []));
appendAll(continuationConditions, flows[0]?.conditions ?? []);
unresolvedFallthrough ||= flows[0]?.conditionUnresolved === true;
} else {
const fallthrough = flows.flatMap((flow, index) =>
@@ -8,6 +8,7 @@ import type {
NativeSymbol,
PinvokeVerification,
} from "./managedNativeVerificationSchemas.js";
import { appendAll } from "../arrayAppend.js";
type PinvokeImport = ManagedNativeBoundaryInspection["pinvoke_imports"][number];
type VerifiedPinvoke = {
@@ -35,7 +36,7 @@ export const collectNativeSymbols = (
unsupported += 1;
} else {
accepted += 1;
symbols.push(...observation.symbols);
appendAll(symbols, observation.symbols);
}
}
return { symbols, accepted, unsupported, moduleFormatsByEvidenceId };
+2 -1
View File
@@ -21,6 +21,7 @@ import {
compareUnicodeCodePoints,
compositeKey,
} from "../unicodeCodePointOrder.js";
import { appendAll } from "../arrayAppend.js";
/** Targets admitted to the Apple byte reader; the reader validates slice support. */
export const APPLE_DISPATCH_TARGET_APPLICABILITY: TargetApplicability = {
@@ -600,7 +601,7 @@ export const inspectNativeDispatchMetadata = (
: Symbol();
const evidence = symbolEvidence(entry, sourceAddress);
const previous = implementations.get(key);
if (previous !== undefined) previous.evidence.push(...evidence);
if (previous !== undefined) appendAll(previous.evidence, evidence);
else
implementations.set(key, {
class_name: className,
+4 -2
View File
@@ -16,6 +16,7 @@ import {
processTraceIdentifierSchema,
type ProcessTraceSpecification,
} from "./processTraceSpecification.js";
import { appendAll } from "../arrayAppend.js";
const matchedEventSchema = z.strictObject({
event_id: processTraceIdentifierSchema,
@@ -458,8 +459,9 @@ const evaluatePartialOrder = (
const suffixFailure = boundaryFailure(trace, "suffix", language.suffix);
if (suffixFailure !== null) return suffixFailure;
const satisfied = ordering.satisfied;
satisfied.push(
...language.unordered_groups.map(
appendAll(
satisfied,
language.unordered_groups.map(
({ events }) => `unordered:${events.join(":")}`,
),
);
+6 -5
View File
@@ -17,6 +17,7 @@ import {
webBundleScriptAnalysisSchema,
type WebBundleScriptAnalysis,
} from "./webBundleScriptAnalysis.js";
import { appendAll } from "./arrayAppend.js";
export const buildCapturedWebBundleAnalysis = (
inspection: WebPageInspection,
@@ -219,11 +220,11 @@ const mergeScriptResults = (
accumulator.recoveredScripts.push(result.script_key);
if (result.widened_return_references)
accumulator.widenedScripts.push(result.script_key);
accumulator.edges.push(...result.observations.edges);
accumulator.routes.push(...result.observations.routes);
accumulator.endpoints.push(...result.observations.endpoints);
accumulator.webMcp.push(...result.observations.webmcp_declarations);
accumulator.inferences.push(...result.inferences);
appendAll(accumulator.edges, result.observations.edges);
appendAll(accumulator.routes, result.observations.routes);
appendAll(accumulator.endpoints, result.observations.endpoints);
appendAll(accumulator.webMcp, result.observations.webmcp_declarations);
appendAll(accumulator.inferences, result.inferences);
}
return accumulator;
};
+4 -2
View File
@@ -1,4 +1,5 @@
import { z } from "zod";
import { appendAll } from "./arrayAppend.js";
/**
* An array schema's own constraints, such as a minimum length, without its
@@ -22,8 +23,9 @@ export function* parseElementSteps<Element extends z.ZodType>(
const result = element.safeParse(value);
if (result.success) parsed.push(result.data);
else
issues.push(
...result.error.issues.map((issue) => ({
appendAll(
issues,
result.error.issues.map((issue) => ({
...issue,
path: [...path, index, ...issue.path],
})),
+1
View File
@@ -233,6 +233,7 @@ const resolveInside = async (
: target;
if (relativeTarget === null) return { kind: "escapes" };
if (absoluteTarget) resolved.length = 0;
// oxlint-disable-next-line rea/no-bulk-append-spread -- readlink text is bounded by the host's PATH_MAX.
pending.unshift(...symlinkTargetSegments(relativeTarget));
continue;
}
+1
View File
@@ -2313,6 +2313,7 @@ export const miStringField = (
)
return undefined;
const value = String.fromCodePoint(codePoint);
// oxlint-disable-next-line rea/no-bulk-append-spread -- one code point encodes to at most four UTF-8 bytes.
bytes.push(...Buffer.from(value, "utf8"));
index += value.length;
continue;
+5 -3
View File
@@ -9,6 +9,7 @@ import type { GhidraLanguageOverride } from "../config/ghidraLanguageOverride.js
import { ConfigurationError } from "../domain/configurationErrors.js";
import { RegularFileOperationCancelled } from "../filesystem/RegularFile.js";
import { readBoundedFileBytes } from "../process/BoundedFileBytes.js";
import { appendAll } from "../domain/arrayAppend.js";
/** One language definition and the compiler specs it declares. */
export interface GhidraLanguageDefinition {
@@ -125,12 +126,13 @@ export const readGhidraLanguageCatalog = (
}
for (const entry of entries.sort()) {
if (!entry.endsWith(".ldefs")) continue;
definitions.push(
...(await readLanguageDefinitionFile(
appendAll(
definitions,
await readLanguageDefinitionFile(
join(directory, entry),
resources,
signal,
)),
),
);
}
}
+25 -20
View File
@@ -126,6 +126,7 @@ import {
import { inspectWindowsNativeReadiness } from "../process/WindowsAuthority.js";
import { ghidraRawBinaryLanguage } from "./GhidraRawBinaryLanguage.js";
import { ghidraDolLanguage, ghidraDolSectionFile } from "./GhidraDolProfile.js";
import { appendAll } from "../domain/arrayAppend.js";
/** Production seam for exercising provider projection without a real process. */
export type GhidraProviderClientFactory = (
@@ -155,6 +156,15 @@ interface GhidraExecutionMetadata {
readonly relationshipCoverage?: RelationshipCoverage;
}
/** Replace a session limitation list with the outcome its handshake reported. */
const replaceLimitations = (
target: string[],
reported: readonly string[],
): void => {
target.length = 0;
appendAll(target, reported);
};
/** Build one AnalysisClient for an admitted Ghidra target and profile. */
export const createGhidraProviderClient = (input: {
readonly config: AppConfig;
@@ -553,10 +563,9 @@ export const createGhidraProviderClient = (input: {
info: GhidraSessionInfo,
): Promise<AnalysisError | undefined> => {
if (target.format === "pe") {
pdbLimitations.splice(
0,
pdbLimitations.length,
...ghidraPePdbLimitations(target, info.pdb),
replaceLimitations(
pdbLimitations,
ghidraPePdbLimitations(target, info.pdb),
);
}
if (project !== undefined) {
@@ -596,34 +605,30 @@ export const createGhidraProviderClient = (input: {
diagnostics: { reason: callingConventionReason },
}));
if (extensionFailure === undefined && info.analysis_seeds !== undefined)
seedLimitations.splice(
0,
seedLimitations.length,
...ghidraSeedLimitations(seeds, info.analysis_seeds),
replaceLimitations(
seedLimitations,
ghidraSeedLimitations(seeds, info.analysis_seeds),
);
if (extensionFailure === undefined)
softFloatLimitations.splice(
0,
softFloatLimitations.length,
...ghidraMipsSoftFloatLimitations(
replaceLimitations(
softFloatLimitations,
ghidraMipsSoftFloatLimitations(
softFloat,
info.mips_soft_float_storage,
),
);
if (extensionFailure === undefined)
riscvCallingConventionLimitations.splice(
0,
riscvCallingConventionLimitations.length,
...ghidraRiscvCallingConventionLimitations(
replaceLimitations(
riscvCallingConventionLimitations,
ghidraRiscvCallingConventionLimitations(
riscvCallingConvention,
info.riscv_calling_convention,
),
);
if (extensionFailure === undefined)
disabledAnalyzerLimitations.splice(
0,
disabledAnalyzerLimitations.length,
...ghidraDisabledAnalyzerLimitations(info.disabled_analyzers),
replaceLimitations(
disabledAnalyzerLimitations,
ghidraDisabledAnalyzerLimitations(info.disabled_analyzers),
);
}
if (extensionFailure === undefined) return undefined;
+2 -1
View File
@@ -38,6 +38,7 @@ import {
amdString,
readAmdMetadata,
} from "./AmdGpuMetadata.js";
import { appendAll } from "../domain/arrayAppend.js";
const sectionBytes = (
source: GpuSource,
section: NativeSection,
@@ -351,7 +352,7 @@ const cudaKernels = async (
kernel.parameter_bytes = decoded.parameter_bytes;
kernel.parameter_count = decoded.parameter_count;
kernel.parameters = decoded.parameters;
kernel.limitations.push(...decoded.limitations);
appendAll(kernel.limitations, decoded.limitations);
} else {
complete = false;
kernel.limitations.push(
+2 -1
View File
@@ -53,6 +53,7 @@ import {
readPtxTarget,
} from "./PtxKernels.js";
import type { AmdGpuTargetIdentity } from "./AmdGpuTarget.js";
import { appendAll } from "../domain/arrayAppend.js";
/** File declarations only. Every child range stays in its named parent coordinate space. */
export class GpuInventory {
@@ -110,7 +111,7 @@ export class GpuInventory {
}
#objectLimitations(row: GpuObject, messages: string[]): void {
this.#retain(messages);
row.limitations.push(...messages);
appendAll(row.limitations, messages);
}
/** Reserve input/output work atomically, before any payload allocation or read. */
#budgetWork(...sizes: number[]): void {
+10 -5
View File
@@ -59,6 +59,7 @@ import {
type IdaDisassembly,
type IdaFunction,
} from "./IdaProtocolValues.js";
import { appendAll } from "../domain/arrayAppend.js";
/** One operation's raw source observations, retained alongside normalized results. */
export interface IdaRawObservation {
@@ -160,7 +161,10 @@ export class IdaOperationRunner {
)[0];
if (value === undefined)
throw new AnalysisProtocolError("IDA MCP omitted its function page.");
result.push(...value.data.map((fn) => idaFunctionSchema.parse(fn)));
appendAll(
result,
value.data.map((fn) => idaFunctionSchema.parse(fn)),
);
offset = nextPageOffset(value.next_offset, current);
}
return result;
@@ -322,7 +326,7 @@ export class IdaOperationRunner {
(tool, args) => this.call(tool, args),
fn.address,
);
this.limitations.push(...graph.limitations);
appendAll(this.limitations, graph.limitations);
for (const callee of graph.callees)
if (!result.has(callee.address))
result.set(callee.address, identity(callee));
@@ -362,8 +366,9 @@ export class IdaOperationRunner {
throw new AnalysisProtocolError(
`${this.protocol === "native" ? "Native IDA" : "IDA"} string search failed: ${page.error}`,
);
result.push(
...page.matches.map((s) => ({ address: s.addr, value: s.string })),
appendAll(
result,
page.matches.map((s) => ({ address: s.addr, value: s.string })),
);
offset = nextOffset(page.cursor, current);
}
@@ -399,7 +404,7 @@ export class IdaOperationRunner {
);
this.verifyAssembly(fn, page.asm);
if (result === undefined) result = page.asm;
else result.lines.push(...page.asm.lines);
else appendAll(result.lines, page.asm.lines);
offset = nextOffset(page.cursor, offset);
}
if (result === undefined)
+2 -1
View File
@@ -40,6 +40,7 @@ import {
import type { ProviderProcessSupervisor } from "../../process/ProviderProcess.js";
import { prepareWakaruExecution } from "./WakaruExecution.js";
import { RECOVERY_LIMITS } from "./WakaruRelease.js";
import { appendAll } from "../../domain/arrayAppend.js";
const OPERATION = "recover_javascript_sources";
@@ -196,7 +197,7 @@ export class WakaruProvider implements JavaScriptRecoveryPort {
if (cleanupOwner !== undefined)
residuals.push(cleanupOwner.launch.ownership?.runId ?? "wakaru-worker");
const filesPending = this.#resources.hasPending;
if (filesPending) residuals.push(...this.#resources.cleanupResources);
if (filesPending) appendAll(residuals, this.#resources.cleanupResources);
if (root !== undefined && cleanupOwner === undefined && !filesPending) {
const failedRoot = root;
try {
+5 -1
View File
@@ -33,6 +33,7 @@ import {
} from "../artifacts/AnalysisCleanupLease.js";
import { ArtifactReaderFailure } from "../artifacts/ArtifactReader.js";
import type { OwnedFileHandle } from "../filesystem/OwnedFileHandle.js";
import { appendAll } from "../domain/arrayAppend.js";
interface WorkspaceRoot {
readonly root: PrivateRuntimeRoot;
@@ -226,7 +227,10 @@ export class KunaTargetCommands {
// The exact process lease remains available for the next retry.
}
}
failures.push(...this.#retained.map(({ error }) => error));
appendAll(
failures,
this.#retained.map(({ error }) => error),
);
const workspace = await this.#workspace;
if (this.#retained.length === 0) {
if (workspace?.ok === true) {
+2 -1
View File
@@ -1,5 +1,6 @@
import { isIPv4, isIPv6 } from "node:net";
import { err, ok, type Result } from "../domain/result.js";
import { appendAll } from "../domain/arrayAppend.js";
interface DnsWireFailure {
readonly kind: "invalid_dns_wire";
@@ -39,7 +40,7 @@ const fields = (source: unknown): Map<string, unknown[]> => {
continue;
const values = result.get(key) ?? [];
if (key === "dns.id_raw") values.push(field);
else if (Array.isArray(field)) values.push(...field);
else if (Array.isArray(field)) appendAll(values, field);
else values.push(field);
result.set(key, values);
}
+3 -1
View File
@@ -32,6 +32,7 @@ import type {
ProcessRunTokenObservation,
ProcessTableEntry,
} from "./ProcessOwnership.js";
import { appendAll } from "../domain/arrayAppend.js";
const responseSchema = z.strictObject({
results: z.array(
@@ -561,7 +562,8 @@ export const createDarwinProcessRunTokenReader = (
compilerArguments.push("-module-cache-path", moduleCachePath);
compilerEnvironment.CLANG_MODULE_CACHE_PATH = moduleCachePath;
}
compilerArguments.push(...sourceFiles, "-o", output);
appendAll(compilerArguments, sourceFiles);
compilerArguments.push("-o", output);
try {
await execFileOutput(compiler.executable, compilerArguments, {
timeout: compilerTimeoutMs,
+4 -2
View File
@@ -29,6 +29,7 @@ import {
type ProcessLifecycleObservation,
type ProcessExitSettlementBudget,
} from "./ProcessLifecycle.js";
import { appendAll } from "../domain/arrayAppend.js";
const execFileAsync = promisify(execFile);
@@ -1058,8 +1059,9 @@ const createOwnedCleanupPlan = async (
exitSettlement,
);
if (failures.length === 0 && unverifiableSampledGroups.has(processGroupId))
unresolved.push(
...validatedMembers.map(({ pid }) => ({
appendAll(
unresolved,
validatedMembers.map(({ pid }) => ({
pid,
reason: "process-identity-unavailable" as const,
diagnostic:
+2 -1
View File
@@ -1,4 +1,5 @@
import type { ProcessTableEntry } from "./ProcessOwnership.js";
import { appendAll } from "../domain/arrayAppend.js";
/** Exclude process-table entries that the operating system has already reaped. */
export const liveProcesses = (
@@ -24,7 +25,7 @@ export const descendantsOf = (
if (visited.has(process.pid)) continue;
visited.add(process.pid);
descendants.push(process);
pending.push(...(childrenByParent.get(process.pid) ?? []));
appendAll(pending, childrenByParent.get(process.pid) ?? []);
}
return descendants;
};
@@ -87,6 +87,7 @@ import type {
TerminalFrame,
} from "../../domain/process/processCapture.js";
import { partialProcessCaptureObservationSchema } from "../../domain/process/processCapture.js";
import { appendAll } from "../../domain/arrayAppend.js";
interface TerminalExitOptions {
readonly terminal: Pick<IPty, "onExit" | "kill">;
readonly scenario: ProcessScenario;
@@ -1111,7 +1112,7 @@ const releaseCapturedProcessGroup = async (
}> = [];
try {
const cleaned = await host.cleanupProcessGroup(ownership);
unverified.push(...(cleaned.unverified ?? []));
appendAll(unverified, cleaned.unverified ?? []);
if (!cleaned.cleaned)
outcome = { state: "unverified", reason: cleaned.reason };
} catch (cause: unknown) {
@@ -36,6 +36,7 @@ import type {
ReferenceSourceResult,
TraversalState,
} from "./ReferenceSourceReaderTypes.js";
import { appendAll } from "../domain/arrayAppend.js";
const recordDirectoryFailure = (
state: TraversalState,
@@ -230,7 +231,7 @@ const scheduleObservedDirectories = (
directories: PendingDirectory[],
): void => {
directories.reverse();
state.pending.push(...directories);
appendAll(state.pending, directories);
};
const directoryReadFailure = (cause: unknown): FilesystemFailureDetail => {
+2 -1
View File
@@ -11,6 +11,7 @@ import { currentMcpRequestSignal } from "./EvidenceMcpServerCore.js";
import { parseArtifactInventoryEvidence } from "../domain/artifactInventoryEvidence.js";
import { EvidenceIntegrityError } from "../domain/evidenceErrors.js";
import type { Evidence } from "../domain/evidence.js";
import { appendAll } from "../domain/arrayAppend.js";
const documentListSchema = z.array(z.string().min(1));
const procedureInventorySchema = z.array(
@@ -175,7 +176,7 @@ const evidenceCandidates = (
if (session === undefined) return [];
const candidates: string[] = [];
for (const evidence of session.immutableEvidenceBundle().records)
candidates.push(...evidenceValues(kind, evidence));
appendAll(candidates, evidenceValues(kind, evidence));
return candidates;
};
+4 -2
View File
@@ -3,6 +3,7 @@ import { parseWabtInterface } from "./WabtInterface.js";
import { watString, watStringEnd } from "./WabtString.js";
import { watCommentEnd } from "./WabtComment.js";
import { err, ok, type Result } from "../domain/result.js";
import { appendAll } from "../domain/arrayAppend.js";
interface WabtNameMetadataFailure {
readonly _tag: "WabtNameMetadataFailure";
@@ -137,8 +138,9 @@ export const parseWabtWat = (text: string) => {
const names = moduleNames(payload.bytes);
// Framing needs every observed display, even when optional
// metadata coverage is incomplete in this or an earlier section.
debugModuleNames.push(
...(names.ok ? names.value : names.error.partial),
appendAll(
debugModuleNames,
names.ok ? names.value : names.error.partial,
);
if (!names.ok) {
debugNameMetadata = "uninterpretable";
@@ -0,0 +1,111 @@
import { writeFile } from "node:fs/promises";
import { join } from "node:path";
import { expect } from "vitest";
import { MANAGED_NATIVE_VERIFICATION_EXAMPLE } from "../../../src/contracts/managed/managedWorkflowExamples.js";
import {
createEvidence,
parseEvidence,
type Evidence,
} from "../../../src/domain/evidence.js";
import {
managedNativeVerificationResultSchema,
type ManagedNativeVerificationResult,
} from "../../../src/domain/managed/managedNativeVerificationSchemas.js";
import { inspectMachoSchema } from "../../../src/domain/native/nativeInspection.js";
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
import { cliTest } from "../../support/cli/cliFixture.js";
/** More exports than one call can receive as arguments on V8's default stack. */
const EXPORTS = 130_000;
/** Recreate the example's Mach-O Evidence with `count` exports, keeping its matching first row. */
const machoExports = (count: number): Evidence => {
const [source] = MANAGED_NATIVE_VERIFICATION_EXAMPLE.native_observations;
if (source?.subject == null) throw new Error("Missing native example");
const result = inspectMachoSchema.parse(source.normalized_result);
const [match] = result.exports.items;
if (match === undefined) throw new Error("Missing matching export");
const items = [match];
for (let index = 1; index < count; index += 1)
items.push({
name: `_audit_${String(index)}`,
runtime_name: `audit_${String(index)}`,
address: `0x${(0x1000 + index).toString(16)}`,
weak: false,
reexport: false,
source: "nm",
});
return createEvidence(
{
path: source.subject.local_path,
sha256: source.subject.digest.sha256,
format: source.subject.format,
},
source.provider,
{
operation: source.operation,
parameters: source.parameters,
result: {
...result,
exports: { ...result.exports, items, total: count },
},
rawResult: null,
limitations: source.limitations,
},
);
};
/** P/Invoke outcomes, apart from the native record identity they cite. */
const outcomes = (
result: ManagedNativeVerificationResult,
native: Evidence,
): unknown =>
JSON.parse(
JSON.stringify(result.pinvoke_imports, (key, value: unknown) =>
key === "item_id" ? undefined : value,
).replaceAll(native.evidence_id, "<native>"),
);
cliTest(
"verifies P/Invoke boundaries against a native inventory wider than call arguments allow",
async ({ cli }) => {
const directory = await createTestTempDirectory("rea-managed-wide-");
const verify = async (count: number) => {
const native = machoExports(count);
const path = join(directory, `verify-${String(count)}.json`);
await writeFile(
path,
JSON.stringify({
managed_boundaries:
MANAGED_NATIVE_VERIFICATION_EXAMPLE.managed_boundaries,
native_observations: [native],
}),
);
const command = await cli.run({
arguments: ["verify-managed-native-boundaries", path, "--json"],
});
expect(command.exitCode, command.stdout + command.stderr).toBe(0);
const result = managedNativeVerificationResultSchema.parse(
parseEvidence(command.json).normalized_result,
);
return { result, native };
};
const control = await verify(3);
const wide = await verify(EXPORTS);
expect(wide.result.summary).toEqual(control.result.summary);
expect(wide.result.summary).toMatchObject({ verified: 1 });
expect(outcomes(wide.result, wide.native)).toEqual(
outcomes(control.result, control.native),
);
expect(wide.result.pinvoke_imports[0]).toMatchObject({
status: "verified",
matched_native: {
evidence_id: wide.native.evidence_id,
name: "open_native",
},
});
},
120_000,
);
@@ -0,0 +1,101 @@
import { mkdirSync } from "node:fs";
import { join } from "node:path";
import { afterAll, beforeAll, describe, expect, it } from "vitest";
import { ArtifactReaderFailure } from "../../../src/artifacts/ArtifactReader.js";
import { DirectoryArtifactReader } from "../../../src/artifacts/DirectoryArtifactReader.js";
import { readReferenceSource } from "../../support/referenceSourceResourceScope.js";
import {
createTestWorkspace,
removeTestWorkspace,
type TestWorkspace,
} from "../../support/workspace/workspaceFixture.js";
/** More children than one call can receive as arguments on V8's default stack. */
const CHILD_DIRECTORIES = 130_000;
const childNames = (): string[] =>
Array.from({ length: CHILD_DIRECTORIES }, (_, index) => `d${index}`);
const missingChildren = (paths: Iterable<string>): string[] => {
const listed = new Set(paths);
return childNames().filter((name) => !listed.has(name));
};
const traverse = async (
root: string,
maximumEntries: number,
stopAfter?: { readonly entries: number; readonly reason: Error },
): Promise<{ readonly paths: string[]; readonly failure: unknown }> => {
const reader = new DirectoryArtifactReader(root);
const controller = new AbortController();
const paths: string[] = [];
try {
for await (const entry of reader.entries(controller.signal, {
maximumEntries,
})) {
paths.push(entry.path);
if (paths.length === stopAfter?.entries)
controller.abort(stopAfter.reason);
}
return { paths, failure: undefined };
} catch (failure: unknown) {
return { paths, failure };
} finally {
await reader.close();
}
};
// One shallow tree serves both independent traversal owners; every child is
// empty, so only the parent's child list is wide.
describe("directory traversal of a parent wider than call arguments allow", () => {
let workspace: TestWorkspace | undefined;
let root = "";
beforeAll(async () => {
workspace = await createTestWorkspace("rea-wide-directory-");
root = await workspace.mkdir("tree");
for (const name of childNames()) mkdirSync(join(root, name));
}, 60_000);
afterAll(async () => {
if (workspace !== undefined) await removeTestWorkspace(workspace.root);
}, 60_000);
it("schedules the artifact reader's children within the selected entry budget", async () => {
// Cancel once the parent listing is complete. The next cancellation
// checkpoint follows scheduling its children for descent.
const reason = new Error("Stop after the wide parent listing");
const listed = await traverse(root, CHILD_DIRECTORIES, {
entries: CHILD_DIRECTORIES,
reason,
});
expect(listed.failure).toBeInstanceOf(ArtifactReaderFailure);
expect(listed.failure).toMatchObject({
reason: "cancelled",
cause: reason,
});
expect(listed.paths).toHaveLength(CHILD_DIRECTORIES);
expect(missingChildren(listed.paths)).toEqual([]);
// A smaller selected allowance stays a typed refusal at its actual bound.
const refused = await traverse(root, 2_000);
expect(refused.paths).toHaveLength(2_000);
expect(refused.failure).toBeInstanceOf(ArtifactReaderFailure);
expect(refused.failure).toMatchObject({
reason: "limit",
examinedEntries: 2_000,
maximumEntries: 2_000,
});
}, 60_000);
it("reads every child directory into the reference-source inventory", async () => {
const result = await readReferenceSource(root);
if (!result.ok) throw result.error;
const { entries } = result.value;
expect(entries).toHaveLength(CHILD_DIRECTORIES);
expect(
entries.filter(
(entry) => entry.status !== "read" || entry.kind !== "directory",
),
).toEqual([]);
expect(missingChildren(entries.map(({ path }) => path))).toEqual([]);
}, 120_000);
});
@@ -0,0 +1,25 @@
import { expect, it } from "vitest";
import { analyzeCommonJsStaticFixture } from "../../fixtures/javascriptCommonJsAnalysis.js";
// The source analyzer runs on a child process's main-thread stack. This forked
// project shares that stack size; worker threads have a larger default stack.
it("selects a published binding list wider than call arguments allow", () => {
const publish = (count: number) => {
const bindings = Array.from({ length: count }, (_, i) => `v${i}`);
const source = `const{app}=require('electron');export const[${bindings.join(",")}]=[0,app.setAppPath];`;
const analysis = analyzeCommonJsStaticFixture(source);
expect(analysis.parse_error_count).toBe(0);
// The possible change is the exported declaration, which ends the source.
return (analysis.electron.app_path_changes ?? []).map(
({ location, ...change }) => {
expect(location.end).toEqual({ line: 1, column: source.length });
return { ...change, start: location.start };
},
);
};
const control = publish(3);
expect(control).toHaveLength(1);
// More bindings than one call can receive as arguments on V8's default stack.
expect(publish(130_000)).toEqual(control);
});
@@ -0,0 +1,45 @@
import { describe, it } from "vitest";
import { RuleTester } from "oxlint/plugins-dev";
import rule from "../../scripts/lint/rules/no-bulk-append-spread.mjs";
RuleTester.describe = describe;
RuleTester.it = it;
const tester = new RuleTester({
languageOptions: { parserOptions: { lang: "ts" } },
});
tester.run("no-bulk-append-spread", rule, {
valid: [
"appendAll(target, items);",
"target.push(item);",
"target.push(first, second);",
"target.unshift(item);",
"target.splice(0, target.length);",
"const copy = [...items];",
"call(...items);",
"Math.max(...values);",
"target[method](...items);",
],
invalid: (
[
["target.push(...items);", "push"],
["target.push(first, ...items);", "push"],
["state.pending.push(...directories.reverse());", "push"],
["target?.push(...items);", "push"],
['target["push"](...items);', "push"],
["target.unshift(...items);", "unshift"],
["target.splice(0, target.length, ...items);", "splice"],
// Type-only and grouping wrappers leave the evaluated callee unchanged.
["target.push!(...items);", "push"],
["(target.push)(...items);", "push"],
["(target.push as typeof target.push)(...items);", "push"],
["(target.push satisfies Function)(...items);", "push"],
["(<typeof target.push>target.push)(...items);", "push"],
["target!.push(...items);", "push"],
["(target as string[]).unshift(...items);", "unshift"],
["(<string[]>target).splice(0, 0, ...items);", "splice"],
] as const
).map(([code, method]) => ({
code,
errors: [{ messageId: "spread", data: { method } }],
})),
});