From 5fdb1d19de6e7149e4bb551448282b5cfcab45e6 Mon Sep 17 00:00:00 2001 From: Vitor Hugo Date: Thu, 11 Jun 2026 11:28:08 -0300 Subject: [PATCH] fix(Platform): make arm64 nil-variant and v8 hash identically Platform.== treats arm64 with nil variant as equal to arm64/v8, but hash(into:) used description which serializes them differently ("linux/arm64" vs "linux/arm64/v8"). This violated the Hashable contract and caused Set/Dictionary lookups to miss entries when one platform was decoded from JSON (no variant field) and another was created via Platform(from:) or Platform.current (which set variant=v8). The practical consequence was inconsistent platform-string normalization across stages of a single container build, which could cause COPY --from= to fail to resolve the source stage. Fix hash(into:) to normalize arm64 nil variant to "v8" before hashing, matching the existing == behavior. --- Sources/ContainerizationOCI/Platform.swift | 9 ++++++++- .../OCIPlatformTests.swift | 15 +++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/Sources/ContainerizationOCI/Platform.swift b/Sources/ContainerizationOCI/Platform.swift index a9c17482..b52d385f 100644 --- a/Sources/ContainerizationOCI/Platform.swift +++ b/Sources/ContainerizationOCI/Platform.swift @@ -277,7 +277,14 @@ extension Platform: Hashable { } public func hash(into hasher: inout Swift.Hasher) { - hasher.combine(description) + hasher.combine(os) + hasher.combine(architecture) + // arm64 with no variant is equivalent to arm64/v8 per the == implementation + if architecture == "arm64" { + hasher.combine(variant ?? "v8") + } else { + hasher.combine(variant) + } } } diff --git a/Tests/ContainerizationOCITests/OCIPlatformTests.swift b/Tests/ContainerizationOCITests/OCIPlatformTests.swift index 8977a526..7ffebedc 100644 --- a/Tests/ContainerizationOCITests/OCIPlatformTests.swift +++ b/Tests/ContainerizationOCITests/OCIPlatformTests.swift @@ -66,4 +66,19 @@ struct OCIPlatformTests { let rhs = Platform(arch: "arm64", os: "linux", variant: nil) #expect(lhs == rhs, "Both nil variants => variantEqual is true => overall equal") } + + @Test func arm64_nilAndV8_sameHashValue() { + let withoutVariant = Platform(arch: "arm64", os: "linux", variant: nil) + let withV8 = Platform(arch: "arm64", os: "linux", variant: "v8") + // Equal platforms must produce the same hash — violating this breaks Set/Dictionary lookups + #expect(withoutVariant.hashValue == withV8.hashValue, "arm64 nil variant and v8 must hash identically") + } + + @Test func arm64_nilAndV8_setLookup() { + let withoutVariant = Platform(arch: "arm64", os: "linux", variant: nil) + let withV8 = Platform(arch: "arm64", os: "linux", variant: "v8") + var set = Set() + set.insert(withoutVariant) + #expect(set.contains(withV8), "arm64/v8 must be found in a Set that contains arm64 with nil variant") + } }