mirror of
https://github.com/morluto/rea.git
synced 2026-11-07 05:56:19 -10:00
Compare commits
3
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3b9b461924 | ||
|
|
7d5a459987 | ||
|
|
fb566c800c |
No files matched your search
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 },
|
||||
});
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
@@ -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>();
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 = (
|
||||
|
||||
@@ -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] }),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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" ||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 };
|
||||
}
|
||||
|
||||
@@ -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 = (
|
||||
|
||||
@@ -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,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)
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
)),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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}`,
|
||||
),
|
||||
);
|
||||
|
||||
@@ -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"]);
|
||||
});
|
||||
@@ -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);
|
||||
};
|
||||
@@ -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.`,
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 };
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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(":")}`,
|
||||
),
|
||||
);
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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],
|
||||
})),
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
)),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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 => {
|
||||
|
||||
@@ -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
@@ -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 } }],
|
||||
})),
|
||||
});
|
||||
Reference in new issue
Block a user