Skip to content

Commit f1d464d

Browse files
Fix undeterministic hash key due to pointer address in anonymous context name (#98)
`CodingKeys` is automatically derived and defined as `private`, and types defined as private have anonymous context in its mangled name. Then `String(reflecting:)` returns the qualified name of the type including the anonymous context name, which contains the pointer address like `Item.(unknown context at $55eab1620198).CodingKeys` and the address can be different across runs due to ASLR. So we need to stop encoding CodingKeys to reduce the possibility of undeterministic hash key. Our hashing strategy can be still underteministic if the cache key itself is declared as private, but we can't avoid it while keeping the cache key nominal type sensitive (not structurally sensitive).
1 parent 4c2e16f commit f1d464d

File tree

3 files changed

+27
-3
lines changed

3 files changed

+27
-3
lines changed

Sources/GeneratorEngine/Engine.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ public actor Engine {
8585
public subscript(_ query: some Query) -> FileCacheRecord {
8686
get async throws {
8787
let hashEncoder = HashEncoder<SHA512>()
88-
try query.encode(to: hashEncoder)
88+
try hashEncoder.encode(query)
8989
let key = hashEncoder.finalize()
9090

9191
if let fileRecord = try resultsCache.get(key, as: FileCacheRecord.self) {

Sources/GeneratorEngine/Query.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@ final class HashEncoder<Hash: HashFunction>: Encoder {
2626
var userInfo: [CodingUserInfoKey : Any]
2727

2828
func container<Key>(keyedBy type: Key.Type) -> KeyedEncodingContainer<Key> where Key : CodingKey {
29-
String(reflecting: Key.self).hash(with: &self.hashFunction)
3029
return .init(KeyedContainer(encoder: self))
3130
}
3231

@@ -121,7 +120,7 @@ extension HashEncoder: SingleValueEncodingContainer {
121120
guard value is CacheKey else {
122121
throw Error.noCacheKeyConformance(T.self)
123122
}
124-
123+
try String(describing: T.self).encode(to: self)
125124
try value.encode(to: self)
126125
}
127126
}
@@ -237,6 +236,7 @@ extension HashEncoder {
237236
throw Error.noCacheKeyConformance(T.self)
238237
}
239238

239+
try String(reflecting: T.self).encode(to: self.encoder)
240240
key.stringValue.hash(with: &self.encoder.hashFunction)
241241
try value.encode(to: self.encoder)
242242
}

Tests/GeneratorEngineTests/EngineTests.swift

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,4 +149,28 @@ final class EngineTests: XCTestCase {
149149

150150
try await engine.shutDown()
151151
}
152+
153+
struct MyItem: Sendable, CacheKey {
154+
let remoteURL: URL
155+
var localPath: FilePath
156+
let isPrebuilt: Bool
157+
}
158+
159+
func testQueryEncoding() throws {
160+
let item = MyItem(
161+
remoteURL: URL(string: "https://download.swift.org/swift-5.9.2-release/ubuntu2204-aarch64/swift-5.9.2-RELEASE/swift-5.9.2-RELEASE-ubuntu22.04-aarch64.tar.gz")!,
162+
localPath: "/Users/katei/ghq/github.com/apple/swift-sdk-generator/Artifacts/target_swift_5.9.2-RELEASE_aarch64-unknown-linux-gnu.tar.gz",
163+
isPrebuilt: true
164+
)
165+
func hashValue(of key: some CacheKey) throws -> SHA256Digest {
166+
let hasher = HashEncoder<SHA256>()
167+
try hasher.encode(key)
168+
return hasher.finalize()
169+
}
170+
// Ensure that hash key is stable across runs
171+
XCTAssertEqual(
172+
try hashValue(of: item).description,
173+
"SHA256 digest: 5178ba619e00da962d505954d33d0bceceeff29831bf5ee0c878dd1f2568b118"
174+
)
175+
}
152176
}

0 commit comments

Comments
 (0)