mirror of
https://github.com/morluto/rea.git
synced 2026-11-05 06:15:59 +00:00
fix(apple): order projection paths by Unicode code point (#3294)
The Apple projection sorted application roots, bundle paths and symlinks with relational string operators, which compare UTF-16 code units, so a name with U+10000 sorted before one with U+FFFD. Use the shared compareUnicodeCodePoints instead and remove compareProjectionStrings, whose documented code point contract it did not meet. Fixes #3177
This commit is contained in:
6 files changed
+35
-20
No files matched your search
@@ -10,7 +10,6 @@ import { nativeRuntimeConvention } from "../nativeConvention.js";
|
||||
import {
|
||||
applicationInventoryProjectionInputSchema,
|
||||
bridgeCandidateCoverageSchema,
|
||||
compareProjectionStrings,
|
||||
projectCartesianCandidates,
|
||||
projectedBridgeCandidateSchema,
|
||||
projectedComponentSchema,
|
||||
@@ -21,6 +20,7 @@ import {
|
||||
unavailableComponentSchema,
|
||||
unavailableComponents,
|
||||
} from "../mobileApplicationGraphProjection.js";
|
||||
import { compareUnicodeCodePoints } from "../unicodeCodePointOrder.js";
|
||||
|
||||
const evidenceIdSchema = projectionEvidenceIdSchema;
|
||||
const componentSchema = projectedComponentSchema;
|
||||
@@ -133,7 +133,7 @@ export const projectAndroidApplication = (
|
||||
root_format: "apk" as const,
|
||||
source_evidence_ids: evidence
|
||||
.map(({ evidence_id: id }) => id)
|
||||
.sort(compareProjectionStrings),
|
||||
.sort(compareUnicodeCodePoints),
|
||||
components,
|
||||
unavailable_components: unavailable,
|
||||
runtime_families: runtimeFamilies(all),
|
||||
@@ -184,7 +184,7 @@ const runtimeFamilies = (all: readonly Component[]) => {
|
||||
const convention = nativeRuntimeConvention(path);
|
||||
if (convention !== null) families.add(convention);
|
||||
}
|
||||
return [...families].sort(compareProjectionStrings);
|
||||
return [...families].sort(compareUnicodeCodePoints);
|
||||
};
|
||||
|
||||
const bridgeCandidates = (
|
||||
|
||||
@@ -677,4 +677,30 @@ describe("Apple path conventions", () => {
|
||||
);
|
||||
expect(result.runtime_families).not.toContain("swift-objective-c");
|
||||
});
|
||||
|
||||
it.each([
|
||||
// U+10000 sorts before U+FFFD by UTF-16 code unit, after it by code point.
|
||||
["an astral and a BMP name", "\uFFFD", "\u{10000}"],
|
||||
["two BMP names", "\uE000", "\uFFFD"],
|
||||
])(
|
||||
"orders roots, bundles and symlinks by code point for %s",
|
||||
(_label, low, high) => {
|
||||
const result = project(
|
||||
inventoryEvidence(
|
||||
"zip",
|
||||
"Fixture.zip",
|
||||
[high, low].flatMap((name) => [
|
||||
{ path: `A${name}.app/Contents/Info.plist` },
|
||||
{ path: `A${name}.app/Contents/Resources/link`, kind: "symlink" },
|
||||
]),
|
||||
),
|
||||
);
|
||||
const roots = [`A${low}.app`, `A${high}.app`];
|
||||
expect(result.application_roots).toEqual(roots);
|
||||
expect(result.bundles.map(({ path }) => path)).toEqual(roots);
|
||||
expect(result.symlinks).toEqual(
|
||||
roots.map((root) => `${root}/Contents/Resources/link`),
|
||||
);
|
||||
},
|
||||
);
|
||||
});
|
||||
@@ -28,10 +28,8 @@ import {
|
||||
bridgeCandidateCoverageSchema,
|
||||
projectCartesianCandidates,
|
||||
} from "../bridgeCandidateProjection.js";
|
||||
import {
|
||||
compareProjectionStrings,
|
||||
projectionDigest,
|
||||
} from "../mobileApplicationGraphProjection.js";
|
||||
import { projectionDigest } from "../mobileApplicationGraphProjection.js";
|
||||
import { compareUnicodeCodePoints as compare } from "../unicodeCodePointOrder.js";
|
||||
|
||||
const evidenceIdSchema = prefixedDigestSchema("ev");
|
||||
const pathSchema = z.string().min(1);
|
||||
@@ -450,6 +448,5 @@ const projectionLimitations = (facts: {
|
||||
"A bridge basis is inferred from the native path and is repeated for every managed component.",
|
||||
];
|
||||
|
||||
const compare = compareProjectionStrings;
|
||||
const digest = (value: unknown): string =>
|
||||
projectionDigest(value, "Apple application projection");
|
||||
@@ -1,6 +1,7 @@
|
||||
import { z } from "zod";
|
||||
|
||||
import { digestSchema, prefixedDigestSchema } from "../digests.js";
|
||||
import { compareUnicodeCodePoints as compare } from "../unicodeCodePointOrder.js";
|
||||
|
||||
const pathSchema = z.string().min(1);
|
||||
|
||||
@@ -376,6 +377,3 @@ export const platformsOf = (bundles: readonly Bundle[]): ("ios" | "macos")[] =>
|
||||
}),
|
||||
),
|
||||
].sort(compare);
|
||||
|
||||
const compare = (left: string, right: string): number =>
|
||||
left < right ? -1 : left > right ? 1 : 0;
|
||||
@@ -10,7 +10,6 @@ import { nativeRuntimeConvention } from "../nativeConvention.js";
|
||||
import {
|
||||
applicationInventoryProjectionInputSchema,
|
||||
bridgeCandidateCoverageSchema,
|
||||
compareProjectionStrings,
|
||||
projectCartesianCandidates,
|
||||
projectedBridgeCandidateSchema,
|
||||
projectedComponentSchema,
|
||||
@@ -21,6 +20,7 @@ import {
|
||||
unavailableComponentSchema,
|
||||
unavailableComponents,
|
||||
} from "../mobileApplicationGraphProjection.js";
|
||||
import { compareUnicodeCodePoints } from "../unicodeCodePointOrder.js";
|
||||
|
||||
const evidenceIdSchema = projectionEvidenceIdSchema;
|
||||
const componentSchema = projectedComponentSchema;
|
||||
@@ -124,7 +124,7 @@ export const projectHarmonyApplication = (
|
||||
root_format: rootFormat,
|
||||
source_evidence_ids: evidence
|
||||
.map(({ evidence_id: id }) => id)
|
||||
.sort(compareProjectionStrings),
|
||||
.sort(compareUnicodeCodePoints),
|
||||
packaging_model: packagingModel(classified.manifests),
|
||||
components: classified,
|
||||
unavailable_components: unavailable,
|
||||
@@ -206,7 +206,7 @@ const runtimeFamilies = (all: readonly Component[]) => {
|
||||
const convention = nativeRuntimeConvention(path);
|
||||
if (convention !== null) families.add(convention);
|
||||
}
|
||||
return [...families].sort(compareProjectionStrings);
|
||||
return [...families].sort(compareUnicodeCodePoints);
|
||||
};
|
||||
|
||||
const bridgeCandidates = (
|
||||
|
||||
@@ -119,12 +119,6 @@ export const unavailableComponents = (
|
||||
|
||||
export { bridgeCandidateCoverageSchema, projectCartesianCandidates };
|
||||
|
||||
/** Unicode code point ordering so projection arrays sort deterministically. */
|
||||
export const compareProjectionStrings = (
|
||||
left: string,
|
||||
right: string,
|
||||
): number => (left < right ? -1 : left > right ? 1 : 0);
|
||||
|
||||
/** Digest a canonical JSON projection payload for its stable identifier. */
|
||||
export const projectionDigest = (value: unknown, label: string): string => {
|
||||
const encoded = canonicalize(value);
|
||||
|
||||
Reference in new issue
Block a user