mirror of
https://github.com/morluto/rea.git
synced 2026-11-10 17:30:32 -10:00
Compare commits
10
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
217c84ea7b | ||
|
|
e9d620b400 | ||
|
|
fdee758682 | ||
|
|
7b5ea25e4c | ||
|
|
d2f1212c50 | ||
|
|
e0044a1a51 | ||
|
|
37ece4a8ba | ||
|
|
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",
|
||||
|
||||
@@ -187,6 +187,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;
|
||||
};
|
||||
|
||||
|
||||
@@ -71,6 +71,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;
|
||||
@@ -657,7 +658,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 ||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { PCAP_CAPTURE_UNSUPPORTED_HOST_REASON } from "../domain/pcap/pcapAnalysis.js";
|
||||
import { appendAll } from "../domain/arrayAppend.js";
|
||||
import { DEFAULT_CALL_RELATIONSHIP_DIRECTION } from "../contracts/enhancedInputs.js";
|
||||
import {
|
||||
FIRMWARE_OPERATION_ENGINES,
|
||||
@@ -1180,7 +1181,7 @@ const reachableRequirements = (
|
||||
descriptor.runtime_unavailable === true)
|
||||
);
|
||||
});
|
||||
reached.push(...available);
|
||||
appendAll(reached, available);
|
||||
if (available.length === 0) break;
|
||||
}
|
||||
return reached;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -11,6 +11,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 {
|
||||
@@ -32,13 +33,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?.(clients ?? []);
|
||||
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 = (
|
||||
|
||||
@@ -71,6 +71,7 @@ import {
|
||||
registrationWriteBlocker,
|
||||
type SetupClient,
|
||||
} from "./SupportedClients.js";
|
||||
import { appendAll } from "../domain/arrayAppend.js";
|
||||
|
||||
export class ClientConfigurationCleanupRollbackFailure extends RegularFileCleanupFailure<string> {
|
||||
constructor(
|
||||
@@ -729,8 +730,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",
|
||||
@@ -281,7 +282,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;
|
||||
@@ -487,7 +488,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;
|
||||
@@ -530,7 +531,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,
|
||||
@@ -704,8 +705,9 @@ export const installCanonicalSkill = async (
|
||||
}
|
||||
}
|
||||
const cleanupOwners = [...publications, ...preparedCleanupOwners];
|
||||
rollbackFailures.push(
|
||||
...(await closeSkillPublications(cleanupOwners, resources)),
|
||||
appendAll(
|
||||
rollbackFailures,
|
||||
await closeSkillPublications(cleanupOwners, resources),
|
||||
);
|
||||
if (verified && rollbackFailures.length === 0) return ok("installed");
|
||||
return err(
|
||||
|
||||
@@ -69,6 +69,7 @@ import {
|
||||
redirectedWriteBlocker,
|
||||
type MsixPackageView,
|
||||
} from "./MsixPackageStorage.js";
|
||||
import { appendAll } from "../domain/arrayAppend.js";
|
||||
|
||||
interface ManagedPathStats {
|
||||
readonly uid?: number;
|
||||
@@ -255,7 +256,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;
|
||||
};
|
||||
|
||||
@@ -1223,17 +1224,24 @@ const purgeSelectedData = async (
|
||||
const owned = names.filter(
|
||||
(name) => !kept.some((entry) => entry.name === name),
|
||||
);
|
||||
return async () => [
|
||||
...keptItems,
|
||||
...(await purgeManagedData(root, fileSystem, owned)),
|
||||
];
|
||||
return async () => {
|
||||
const removed = await purgeManagedData(root, fileSystem, owned);
|
||||
const result: UninstallItem[] = [];
|
||||
appendAll(result, keptItems);
|
||||
appendAll(result, removed);
|
||||
return result;
|
||||
};
|
||||
};
|
||||
const reaPlan = await purge(reaRoot, ["cache", "state"]);
|
||||
const plans: (() => Promise<readonly UninstallItem[]>)[] = [];
|
||||
for (const directory of directories)
|
||||
plans.push(await purge(dirname(directory), [basename(directory)]));
|
||||
items.unshift(...(await reaPlan()));
|
||||
for (const plan of plans) items.push(...(await plan()));
|
||||
const merged: UninstallItem[] = [];
|
||||
appendAll(merged, await reaPlan());
|
||||
appendAll(merged, items);
|
||||
for (const plan of plans) appendAll(merged, await plan());
|
||||
items.length = 0;
|
||||
appendAll(items, merged);
|
||||
return items;
|
||||
};
|
||||
|
||||
@@ -1605,8 +1613,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);
|
||||
}
|
||||
|
||||
@@ -7,7 +7,11 @@ import {
|
||||
READER_RUNNER_SLACK_S,
|
||||
readerTimeout,
|
||||
} from "../../domain/desktop/appTaskReaders.js";
|
||||
import { BUILD_REA_TIMEOUT_S } from "../../desktop/BuildComputerController.js";
|
||||
import {
|
||||
BUILD_REA_TIMEOUT_S,
|
||||
fetchHomeFolder,
|
||||
type ControlledComputer,
|
||||
} from "../../desktop/BuildComputerController.js";
|
||||
import { BuildComputerLongRuns } from "../../desktop/BuildComputerLongRuns.js";
|
||||
import { AnalysisError } from "../../domain/analysisErrorBase.js";
|
||||
import { AnalysisCancelledError } from "../../domain/analysisErrorCore.js";
|
||||
@@ -31,10 +35,6 @@ import {
|
||||
import { jsonValueSchema, type JsonValue } from "../../domain/jsonValue.js";
|
||||
import { err, ok, type Result } from "../../domain/result.js";
|
||||
import { AGENTENV_PROVIDER } from "../../desktop/AgentEnvComputerHelper.js";
|
||||
import {
|
||||
fetchHomeFolder,
|
||||
type ControlledComputer,
|
||||
} from "../../desktop/BuildComputerController.js";
|
||||
import {
|
||||
adapterError,
|
||||
ENDS,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
nativeInvestigationTraceSchema,
|
||||
traceNativeInvestigationGraph,
|
||||
joinInterfaceBuilderDispatch,
|
||||
type NativeInvestigationGraph,
|
||||
} from "../../domain/native/nativeInvestigationGraph.js";
|
||||
import { nativeDispatchMetadataResultSchema } from "../../domain/native/objcSwiftMetadata.js";
|
||||
import { inspectNativeDispatch } from "./NativeDispatchMetadataInspection.js";
|
||||
@@ -25,6 +26,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>;
|
||||
|
||||
@@ -92,7 +94,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,
|
||||
@@ -195,7 +197,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,
|
||||
@@ -459,7 +461,7 @@ const expandNativeCalls = async (input: {
|
||||
"provider",
|
||||
);
|
||||
examined += 1;
|
||||
sourceLimitations.push(...execution.limitations);
|
||||
appendAll(sourceLimitations, execution.limitations);
|
||||
if (!execution.outcome.ok && stopsTraversal(execution.outcome.error))
|
||||
return { error: execution.outcome.error };
|
||||
const unknowns = execution.unknowns;
|
||||
|
||||
@@ -23,6 +23,7 @@ import {
|
||||
type SourceLimitation,
|
||||
} from "../SourceLimitations.js";
|
||||
import { createValueTraceBudget } from "./NativeValueTraceBudget.js";
|
||||
import { appendAll } from "../../domain/arrayAppend.js";
|
||||
|
||||
/** The located unknown where unconfirmed provider cleanup stopped a trace. */
|
||||
export const CLEANUP_STOPPED_VALUE_TRACE =
|
||||
@@ -182,8 +183,9 @@ export const traceNativeValues = async (
|
||||
});
|
||||
const flow = dossier.native_value_flow;
|
||||
if (flow !== null)
|
||||
sourceLimitations.push(
|
||||
...flow.limitations.map((limitation) => ({
|
||||
appendAll(
|
||||
sourceLimitations,
|
||||
flow.limitations.map((limitation) => ({
|
||||
provider: execution.value.provider,
|
||||
operation: "analyze_function" as const,
|
||||
limitation,
|
||||
@@ -325,8 +327,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;
|
||||
/**
|
||||
@@ -294,7 +295,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++;
|
||||
@@ -322,7 +323,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(
|
||||
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
import { artifactReaderLimitFailure } from "../ArtifactReaderLimits.js";
|
||||
import { abortIfNeeded } from "../ArtifactHash.js";
|
||||
import { ArtifactReaderFailure } 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");
|
||||
@@ -513,8 +514,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,
|
||||
@@ -587,7 +589,7 @@ const fatClaims = async (
|
||||
for (const slice of parsed.slices) {
|
||||
abortIfNeeded(signal);
|
||||
if (parsed.rangeIssues[slice.index] !== null) continue;
|
||||
claims.push(...(await machOClaims(source, slice.offset, slice.size)));
|
||||
appendAll(claims, await machOClaims(source, slice.offset, slice.size));
|
||||
}
|
||||
return claims;
|
||||
};
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import { createHash } from "node:crypto";
|
||||
|
||||
import { appendAll } from "../../domain/arrayAppend.js";
|
||||
import type { FileHandle } from "node:fs/promises";
|
||||
|
||||
import {
|
||||
@@ -200,7 +202,7 @@ const locateMachO = async (carrier: Carrier): Promise<SectionRange[]> => {
|
||||
declaration.size,
|
||||
declaration,
|
||||
);
|
||||
found.push(...result.found);
|
||||
appendAll(found, result.found);
|
||||
if (result.unread !== null) unread.push(`${label} ${result.unread}`);
|
||||
};
|
||||
for (const [index, slice] of container.header.slices.entries())
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { ArtifactInventorySnapshot } from "../../domain/artifactInventorySnapshot.js";
|
||||
import type { InesInventory } from "../../domain/inesContainer.js";
|
||||
import type { ArtifactEmbeddedFormatProbeFacts } from "../ArtifactEmbeddedFormatProbes.js";
|
||||
import { appendAll } from "../../domain/arrayAppend.js";
|
||||
import { compareUnicodeCodePoints } from "../../domain/unicodeCodePointOrder.js";
|
||||
|
||||
import { AsarArtifactReader } from "../AsarArtifactReader.js";
|
||||
@@ -447,7 +448,7 @@ const buildInventorySnapshot = async (
|
||||
path,
|
||||
formatMetadata: reader?.formatMetadata?.(),
|
||||
});
|
||||
rootOccurrence.limitations.push(...(input.recognitionLimitations ?? []));
|
||||
appendAll(rootOccurrence.limitations, input.recognitionLimitations ?? []);
|
||||
for (const occurrence of input.occurrences)
|
||||
if (
|
||||
occurrence.parent_occurrence_id === null &&
|
||||
|
||||
@@ -47,6 +47,7 @@ import {
|
||||
observeNodeSeaFuseMatches,
|
||||
type NodeSeaFuseObservation,
|
||||
} from "./NodeSeaBlob.js";
|
||||
import { appendAll } from "../../domain/arrayAppend.js";
|
||||
|
||||
const LC_SEGMENT = 0x1;
|
||||
const LC_SEGMENT_64 = 0x19;
|
||||
@@ -250,7 +251,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;
|
||||
};
|
||||
@@ -435,7 +436,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
|
||||
@@ -483,7 +484,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.",
|
||||
|
||||
@@ -18,6 +18,7 @@ import {
|
||||
import { projectAnalysisError } from "../../domain/analysisErrorProjection.js";
|
||||
import { withCaptureReaderCleanup } from "./CaptureFailures.js";
|
||||
import { webNetworkCapturePayloadSchema } from "../../domain/webNetworkCapture.js";
|
||||
import { appendAll } from "../../domain/arrayAppend.js";
|
||||
|
||||
const replySchema = z.discriminatedUnion("ok", [
|
||||
z.strictObject({
|
||||
@@ -158,5 +159,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",
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import type { DesktopLibraryPolicy } from "../domain/desktop/libraryPolicy.js";
|
||||
import { readdir } from "node:fs/promises";
|
||||
|
||||
import { appendAll } from "../domain/arrayAppend.js";
|
||||
|
||||
import {
|
||||
ArtifactReaderFailure,
|
||||
artifactPrimaryCleanupLeases,
|
||||
@@ -143,7 +145,7 @@ const installFacts = async (
|
||||
context.libraryPolicy,
|
||||
),
|
||||
);
|
||||
context.limitations.push(...vendor.limitations);
|
||||
appendAll(context.limitations, vendor.limitations);
|
||||
for (const [sha256, bytes] of vendor.keys)
|
||||
context.signingKeys.set(sha256, bytes);
|
||||
const usable = (repository: AptRepository) =>
|
||||
|
||||
@@ -5,6 +5,8 @@ import {
|
||||
} from "../domain/desktop/libraryPolicy.js";
|
||||
import { realpath } from "node:fs/promises";
|
||||
|
||||
import { appendAll } from "../domain/arrayAppend.js";
|
||||
|
||||
import type { DesktopPackage } from "../domain/desktop/desktopCapture.js";
|
||||
import {
|
||||
readElfDependencies,
|
||||
@@ -195,7 +197,7 @@ export const inspectLibraryPackages = async (
|
||||
const found = library.candidates
|
||||
.map((candidate) => knownOwners.get(candidate))
|
||||
.find((owner) => owner !== undefined && owner.length > 0);
|
||||
if (found !== undefined) names.push(...found);
|
||||
if (found !== undefined) appendAll(names, found);
|
||||
else if (
|
||||
library.candidates.every((candidate) => knownOwners.has(candidate))
|
||||
)
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { randomUUID } from "node:crypto";
|
||||
import { appendAll } from "../domain/arrayAppend.js";
|
||||
import { mkdtemp, rm, writeFile } from "node:fs/promises";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
@@ -238,7 +239,7 @@ export class ScreenJudgeProcess {
|
||||
item.failure.resolveCleanup();
|
||||
} catch (cause: unknown) {
|
||||
failures.push(cause instanceof Error ? cause.message : String(cause));
|
||||
resources.push(...item.owner.cleanupResources);
|
||||
appendAll(resources, item.owner.cleanupResources);
|
||||
}
|
||||
}
|
||||
return failures.length === 0
|
||||
|
||||
@@ -917,9 +917,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 &&
|
||||
|
||||
@@ -72,6 +72,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 {
|
||||
@@ -605,12 +606,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");
|
||||
@@ -558,8 +559,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);
|
||||
};
|
||||
|
||||
|
||||
@@ -21,6 +21,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 {
|
||||
@@ -287,7 +288,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);
|
||||
|
||||
@@ -100,10 +100,10 @@ export interface DesktopAppExportPlan {
|
||||
readonly alias: string;
|
||||
readonly target: string;
|
||||
/**
|
||||
* Whether the selected path is a directory. Captures written before kind
|
||||
* was recorded leave this unknown; the helper must not invent a directory.
|
||||
* Whether the selected path is a directory. Absent when the capture did
|
||||
* not record kind. The rendered helper treats that as unknown.
|
||||
*/
|
||||
readonly kind: "file" | "directory" | "unknown";
|
||||
readonly kind?: "file" | "directory";
|
||||
}[];
|
||||
/** Files under the owned paths that existed before the capture but whose bytes were not retained. */
|
||||
readonly unseededFiles: readonly string[];
|
||||
@@ -313,7 +313,7 @@ const capturedStateAliases = (
|
||||
): {
|
||||
readonly alias: string;
|
||||
readonly target: string;
|
||||
readonly kind: "file" | "directory" | "unknown";
|
||||
readonly kind?: "file" | "directory";
|
||||
}[] => {
|
||||
const { home } = capture.host;
|
||||
const walked = capture.state.state_directories ?? [];
|
||||
@@ -327,21 +327,22 @@ const capturedStateAliases = (
|
||||
const state = index < 0 ? undefined : roots[index]?.path;
|
||||
const alias = relative(path);
|
||||
const target = state === undefined ? null : relative(state);
|
||||
const recorded: "file" | "directory" | "unknown" =
|
||||
kind === "directory"
|
||||
? "directory"
|
||||
: kind === "file"
|
||||
? "file"
|
||||
: "unknown";
|
||||
return alias === null || target === null || alias === target
|
||||
? []
|
||||
: [{ alias, target, kind: recorded }];
|
||||
if (alias === null || target === null || alias === target) return [];
|
||||
const recorded =
|
||||
kind === "directory" || kind === "file" ? kind : undefined;
|
||||
return [
|
||||
{
|
||||
alias,
|
||||
target,
|
||||
...(recorded === undefined ? {} : { kind: recorded }),
|
||||
},
|
||||
];
|
||||
},
|
||||
);
|
||||
const retained: {
|
||||
alias: string;
|
||||
target: string;
|
||||
kind: "file" | "directory" | "unknown";
|
||||
kind?: "file" | "directory";
|
||||
}[] = [];
|
||||
for (const entry of aliases.toSorted(
|
||||
(left, right) => left.alias.length - right.alias.length,
|
||||
@@ -643,7 +644,7 @@ TEXT_SEEDS = frozenset(${pyList(plan.textSeeds)})
|
||||
SEED_MODES = {${Object.entries(plan.seedModes)
|
||||
.map(([home, mode]) => `${py(home)}: 0o${mode.toString(8)}`)
|
||||
.join(", ")}}
|
||||
STATE_ALIASES = [${(plan.stateAliases ?? []).map(({ alias, target, kind }) => `(${py(alias)}, ${py(target)}, ${py(kind)})`).join(", ")}]
|
||||
STATE_ALIASES = [${(plan.stateAliases ?? []).map(({ alias, target, kind }) => `(${py(alias)}, ${py(target)}, ${py(kind ?? "unknown")})`).join(", ")}]
|
||||
WINDOW_CLASS = ${py(plan.windowClass)}
|
||||
WITHHELD = {${(plan.withheld ?? []).map(({ setting, file }) => `${py(setting)}: ${py(file)}`).join(", ")}}
|
||||
${renderPythonHelpers(desktopViewPolicy(plan.viewPolicy))}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { JsonValue } from "../jsonValue.js";
|
||||
import { appendAll } from "../arrayAppend.js";
|
||||
import { isTimestamp, type DesktopTaskCheckInput } from "./appTask.js";
|
||||
import {
|
||||
decisive,
|
||||
@@ -136,7 +137,7 @@ const textSuggestions = (
|
||||
{ added, counted: !truncated },
|
||||
)
|
||||
: { checks: [], notes: [] };
|
||||
suggestions.push(...gained.checks);
|
||||
appendAll(suggestions, gained.checks);
|
||||
const formatNotes = readAsTextNotes(file, linesOf(final));
|
||||
const unreadableAttributes = gained.notes.find(
|
||||
({ code }) => code === "read_as_text",
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { JsonValue } from "../jsonValue.js";
|
||||
import { appendAll } from "../arrayAppend.js";
|
||||
import { isXmlElement, resolvePointer } from "./viewPointer.js";
|
||||
|
||||
/** A file's views at its two ends. */
|
||||
@@ -60,7 +61,7 @@ export const isMemberCount = (
|
||||
for (let node = pending.pop(); node !== undefined; node = pending.pop())
|
||||
if (isXmlElement(node) && node !== own) {
|
||||
tally.set(node.name, (tally.get(node.name) ?? 0) + 1);
|
||||
if (deep) pending.push(...node.children);
|
||||
if (deep) appendAll(pending, node.children);
|
||||
}
|
||||
return tally;
|
||||
};
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { JsonValue } from "../jsonValue.js";
|
||||
import { appendAll } from "../arrayAppend.js";
|
||||
import { isTimestamp, type DesktopTaskCheckInput } from "./appTask.js";
|
||||
import { countTaskRows } from "./appTaskChecks.js";
|
||||
import type { DesktopStatePath } from "./desktopState.js";
|
||||
@@ -151,7 +152,7 @@ export const rowSuggestions = (
|
||||
name,
|
||||
rowWhere(old, row, (column) => old.key.includes(column)),
|
||||
);
|
||||
suggestions.push(...unkeyedRowSuggestions(file, name, old, table));
|
||||
appendAll(suggestions, unkeyedRowSuggestions(file, name, old, table));
|
||||
if (old.rows.length !== table.rows.length)
|
||||
suggestions.push({
|
||||
kind: "row_count",
|
||||
|
||||
@@ -184,7 +184,7 @@ describe("withheld files", () => {
|
||||
"/bin/sh",
|
||||
[
|
||||
"-c",
|
||||
`${command.replace("base64 -w0", "cat")}\nprintf '%s' \"$${item.variable}\"`,
|
||||
`${command.replace("base64 -w0", "cat")}\nprintf '%s' "$${item.variable}"`,
|
||||
],
|
||||
{
|
||||
env: { ...process.env, HOME: home },
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { posix } from "node:path";
|
||||
|
||||
import { appendAll } from "../arrayAppend.js";
|
||||
|
||||
import type { Result } from "../result.js";
|
||||
import { fromUbuntuNobleArchive } from "./appExportInstall.js";
|
||||
import { isWithinAny } from "./desktopStateDiff.js";
|
||||
@@ -80,7 +82,7 @@ const searchDirectories = (object: LoadedObject): readonly string[] => {
|
||||
const rpaths: string[] = [];
|
||||
if (object.dependencies.runpath === undefined)
|
||||
for (let at: LoadedObject | undefined = object; at; at = at.loader)
|
||||
rpaths.push(...expanded(at.path, at.dependencies.rpath));
|
||||
appendAll(rpaths, expanded(at.path, at.dependencies.rpath));
|
||||
return [
|
||||
...new Set([
|
||||
...rpaths,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { JsonValue } from "../jsonValue.js";
|
||||
import { appendAll } from "../arrayAppend.js";
|
||||
import { isTimestamp } from "./appTask.js";
|
||||
import {
|
||||
isGeneratedId,
|
||||
@@ -149,11 +150,9 @@ const candidatesOf = (
|
||||
...ranked.filter(({ tier }) => tier === 3).map(condition),
|
||||
];
|
||||
// Counted on every member, one its identifiers key included, since the evaluator matches any member holding the child.
|
||||
suffixes.push(
|
||||
...children,
|
||||
...placed.map(condition),
|
||||
...generated.map(condition),
|
||||
);
|
||||
appendAll(suffixes, children);
|
||||
appendAll(suffixes, placed.map(condition));
|
||||
appendAll(suffixes, generated.map(condition));
|
||||
const number = names.get(name) ?? names.size;
|
||||
names.set(name, number);
|
||||
// A suffix is empty or opens with `[`, so the number before it cannot run into it.
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import type { z } from "zod";
|
||||
|
||||
import { appendAll } from "./arrayAppend.js";
|
||||
import {
|
||||
AnalysisInputError,
|
||||
type AnalysisInputIssue,
|
||||
@@ -91,7 +93,7 @@ export const projectInputIssuesBounded = (
|
||||
for (; index < issues.length; index += 1) {
|
||||
const issue = issues[index];
|
||||
if (issue === undefined) continue;
|
||||
output.push(...projectIssue(issue, input, budget));
|
||||
appendAll(output, projectIssue(issue, input, budget));
|
||||
}
|
||||
} catch (error) {
|
||||
if (error !== projectionBudgetExceeded) throw error;
|
||||
@@ -397,10 +399,10 @@ const collectKeyExpectedValues = (
|
||||
budget.remaining -= 16 * expected.length;
|
||||
if (budget.remaining < 0) throw projectionBudgetExceeded;
|
||||
}
|
||||
result.push(...expected);
|
||||
appendAll(result, expected);
|
||||
} else if (err.code === "invalid_union" && err.path.length === 0) {
|
||||
for (const nested of err.errors) {
|
||||
result.push(...collectKeyExpectedValues(nested, key, budget));
|
||||
appendAll(result, collectKeyExpectedValues(nested, key, budget));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)) &&
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
mappedRuntimePaths,
|
||||
staticNodeRuntimeScope,
|
||||
} from "./javascriptRuntimeStaticCandidates.js";
|
||||
import { appendAll } from "../arrayAppend.js";
|
||||
|
||||
export interface StaticLoadStateProjection {
|
||||
readonly states: readonly JavaScriptStaticLoadState[];
|
||||
@@ -40,7 +41,7 @@ export const classifyStaticLoadStates = (
|
||||
reconciliationComplete: options.reconciliationComplete,
|
||||
},
|
||||
);
|
||||
states.push(...projection.states);
|
||||
appendAll(states, projection.states);
|
||||
}
|
||||
return { states, omittedStates: 0 };
|
||||
};
|
||||
|
||||
@@ -31,6 +31,7 @@ import {
|
||||
semanticAliasSelectionChoices,
|
||||
type SemanticAliasSelectionPath,
|
||||
} from "./javascriptSemanticAliasSelections.js";
|
||||
import { appendAll } from "../arrayAppend.js";
|
||||
|
||||
export interface SemanticCapturedSelection {
|
||||
readonly at: t.Node;
|
||||
@@ -142,8 +143,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,
|
||||
@@ -417,8 +418,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),
|
||||
]),
|
||||
);
|
||||
@@ -502,8 +504,9 @@ const selectedRequireSourcesAt = (
|
||||
if (facts === undefined) {
|
||||
unknown = true;
|
||||
const retained = semanticBindingAtRead(binding, node, state).binding;
|
||||
pending.push(
|
||||
...inheritLoaderSourceContext(
|
||||
appendAll(
|
||||
pending,
|
||||
inheritLoaderSourceContext(
|
||||
current,
|
||||
projectedLoaderSources(
|
||||
current,
|
||||
@@ -526,8 +529,9 @@ const selectedRequireSourcesAt = (
|
||||
facts.retainsOrigin &&
|
||||
binding.directOrigins.length > 0
|
||||
);
|
||||
pending.push(
|
||||
...inheritLoaderSourceContext(
|
||||
appendAll(
|
||||
pending,
|
||||
inheritLoaderSourceContext(
|
||||
current,
|
||||
projectedLoaderSources(current, facts.sources),
|
||||
),
|
||||
@@ -540,7 +544,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 });
|
||||
@@ -580,8 +584,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,
|
||||
@@ -1245,10 +1250,11 @@ const hasCommonJsModuleSource = (
|
||||
examined.add(identity);
|
||||
const enqueueAt = (
|
||||
depths: readonly number[],
|
||||
...sources: ModuleObjectSource[]
|
||||
sources: readonly ModuleObjectSource[],
|
||||
) => {
|
||||
pending.push(
|
||||
...inheritLoaderSourceContext(next, sources)
|
||||
appendAll(
|
||||
pending,
|
||||
inheritLoaderSourceContext(next, sources)
|
||||
// These leaves cannot contribute a module source and the walker
|
||||
// would discard them without expanding their selected paths.
|
||||
.filter((source) => {
|
||||
@@ -1262,20 +1268,20 @@ const hasCommonJsModuleSource = (
|
||||
.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 (
|
||||
@@ -1285,12 +1291,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
|
||||
@@ -1303,16 +1309,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 (
|
||||
@@ -1329,7 +1335,7 @@ const hasCommonJsModuleSource = (
|
||||
);
|
||||
if (returnedResult.unresolvedInvocation) return "possible";
|
||||
enqueue(
|
||||
...returnedResult.references.map((source) => ({
|
||||
returnedResult.references.map((source) => ({
|
||||
node: source.node,
|
||||
path: [...source.projection, ...path],
|
||||
capturedSelections: semanticShiftCapturedSelections(
|
||||
@@ -1474,7 +1480,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 {
|
||||
|
||||
@@ -47,6 +47,7 @@ import {
|
||||
type SymbolicEnvironment,
|
||||
type SymbolicFunction,
|
||||
} from "./javascriptSymbolicValues.js";
|
||||
import { appendAll } from "../arrayAppend.js";
|
||||
|
||||
interface PropertyWrite {
|
||||
readonly path: readonly string[];
|
||||
@@ -1312,10 +1313,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)) {
|
||||
@@ -1332,8 +1333,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
|
||||
@@ -1348,7 +1350,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) =>
|
||||
|
||||
@@ -4,6 +4,7 @@ import type {
|
||||
} from "./managedMemberComparison.js";
|
||||
import type { ManagedMemberInspection } from "./managedArtifact.js";
|
||||
import { hasSignatureResolution } from "./managedMemberSignatureIdentity.js";
|
||||
import { appendAll } from "../arrayAppend.js";
|
||||
|
||||
interface ComparisonCoverageInput {
|
||||
readonly left: ManagedMemberInspection;
|
||||
@@ -250,7 +251,7 @@ export const comparisonLimitations = (
|
||||
);
|
||||
if (left.coverage.state !== "complete" || right.coverage.state !== "complete")
|
||||
limitations.push("At least one managed member observation is partial.");
|
||||
limitations.push(...capacityLimitations(left, right));
|
||||
appendAll(limitations, capacityLimitations(left, right));
|
||||
if (selectionAgreement === "different")
|
||||
limitations.push(
|
||||
"The observations select different declaring types; a member outside the other side's selection is unknown, not added or removed.",
|
||||
|
||||
@@ -8,6 +8,7 @@ import type {
|
||||
NativeSymbol,
|
||||
PinvokeVerification,
|
||||
} from "./managedNativeVerificationSchemas.js";
|
||||
import { appendAll } from "../arrayAppend.js";
|
||||
|
||||
type PinvokeImport = ManagedNativeBoundaryInspection["pinvoke_imports"][number];
|
||||
type VerifiedPinvoke = {
|
||||
@@ -39,7 +40,7 @@ export const collectNativeSymbols = (
|
||||
unsupported += 1;
|
||||
} else {
|
||||
accepted += 1;
|
||||
symbols.push(...observation.symbols);
|
||||
appendAll(symbols, observation.symbols);
|
||||
if (observation.exportCoverage !== undefined) {
|
||||
if (!observation.exportCoverage.exhaustive) partialExports = true;
|
||||
for (const limitation of observation.exportCoverage.limitations)
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -2371,6 +2371,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,
|
||||
)),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -149,6 +149,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 = (
|
||||
@@ -177,6 +178,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);
|
||||
};
|
||||
|
||||
/** The ReferenceManager observation record of one function-reference result. */
|
||||
const referenceObservationRecord = (
|
||||
normalized: JsonValue,
|
||||
@@ -717,10 +727,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) {
|
||||
@@ -760,34 +769,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;
|
||||
|
||||
@@ -95,6 +95,8 @@ export const observeGhidraEndpoint = async (
|
||||
return { path: endpoint.path };
|
||||
}
|
||||
if (process.platform === "win32") {
|
||||
// A limit failure, including one retained with a failed close, is
|
||||
// classified by observationFailure so cleanup stays on the error.
|
||||
const encoded = readWindowsPrivateRuntimeFile(
|
||||
endpoint.path,
|
||||
MAX_TCP_ENDPOINT_BYTES,
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -54,6 +54,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 {
|
||||
@@ -111,7 +112,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 {
|
||||
|
||||
@@ -63,6 +63,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 {
|
||||
@@ -164,7 +165,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;
|
||||
@@ -326,7 +330,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));
|
||||
@@ -366,8 +370,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);
|
||||
}
|
||||
@@ -403,7 +408,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";
|
||||
import { WakaruReportReadFailure } from "./WakaruReport.js";
|
||||
|
||||
const OPERATION = "recover_javascript_sources";
|
||||
@@ -208,7 +209,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;
|
||||
@@ -242,7 +243,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(
|
||||
@@ -562,7 +563,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,
|
||||
|
||||
@@ -32,6 +32,7 @@ import {
|
||||
type ProcessLifecycleObservation,
|
||||
type ProcessExitSettlementBudget,
|
||||
} from "./ProcessLifecycle.js";
|
||||
import { appendAll } from "../domain/arrayAppend.js";
|
||||
|
||||
const execFileAsync = promisify(execFile);
|
||||
|
||||
@@ -1074,8 +1075,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,
|
||||
@@ -234,7 +235,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, maximumInterfaceBytes?: number) => {
|
||||
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);
|
||||
});
|
||||
@@ -45,11 +45,13 @@ it.each(["normal", "copied", "parameters", "forged", "source"] as const)(
|
||||
() => ({
|
||||
execute: async () =>
|
||||
ok({
|
||||
...createAnalysisExecution(["0x100"], provider, {
|
||||
...(mode === "source" && source !== undefined
|
||||
...createAnalysisExecution(
|
||||
["0x100"],
|
||||
provider,
|
||||
mode === "source" && source !== undefined
|
||||
? { sourceEvidence: [source] }
|
||||
: {}),
|
||||
}),
|
||||
: {},
|
||||
),
|
||||
...(mode === "forged" && forged !== undefined
|
||||
? { evidenceId: forged.evidence_id }
|
||||
: {}),
|
||||
|
||||
@@ -145,7 +145,7 @@ print(json.dumps(_view(Path(sys.argv[2]))))
|
||||
} else {
|
||||
if (withTask instanceof Error) throw withTask;
|
||||
expect(withTask.normalized_result).toMatchObject({
|
||||
task: { failing_on_seed: ["done"] },
|
||||
tasks: [{ failing_on_seed: ["done"] }],
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
Loaded 100 of 101 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user