Skip to content

Commit 92b1b02

Browse files
authored
Fix crash when using keypaths on final classes in Predicates (#1475)
1 parent 473b0d1 commit 92b1b02

File tree

2 files changed

+18
-9
lines changed

2 files changed

+18
-9
lines changed

Sources/FoundationEssentials/Predicate/KeyPath+Inspection.swift

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ extension UInt32 {
1919
private static var COMPUTED_COMPONENT_PAYLOAD_ARGUMENTS_MASK: UInt32 { 0x0008_0000 }
2020
private static var COMPUTED_COMPONENT_PAYLOAD_SETTABLE_MASK: UInt32 { 0x0040_0000 }
2121

22+
private static var STORED_COMPONENT_PAYLOAD_MAXIMUM_INLINE_OFFSET: UInt32 { 0x007F_FFFC }
23+
2224
fileprivate var _keyPathHeader_bufferSize: Int {
2325
Int(self & Self.KEYPATH_HEADER_BUFFER_SIZE_MASK)
2426
}
@@ -31,6 +33,11 @@ extension UInt32 {
3133
self & Self.COMPONENT_HEADER_PAYLOAD_MASK
3234
}
3335

36+
fileprivate var _keyPathComponentHeader_storedIsInline: Bool {
37+
// If the payload value is greater than the maximum inline offset then it is one of the out-of-line sentinel values
38+
_keyPathComponentHeader_payload <= Self.STORED_COMPONENT_PAYLOAD_MAXIMUM_INLINE_OFFSET
39+
}
40+
3441
fileprivate var _keyPathComponentHeader_computedHasArguments: Bool {
3542
(_keyPathComponentHeader_payload & Self.COMPUTED_COMPONENT_PAYLOAD_ARGUMENTS_MASK) != 0
3643
}
@@ -40,10 +47,6 @@ extension UInt32 {
4047
}
4148
}
4249

43-
private func _keyPathOffset<T>(_ root: T.Type, _ keyPath: AnyKeyPath) -> Int? {
44-
MemoryLayout<T>.offset(of: keyPath as! PartialKeyPath<T>)
45-
}
46-
4750
extension AnyKeyPath {
4851
private static var WORD_SIZE: Int { MemoryLayout<Int>.size }
4952

@@ -57,11 +60,10 @@ extension AnyKeyPath {
5760
case 1: // struct/tuple/self stored property
5861
fallthrough
5962
case 3: // class stored property
60-
// Key paths to stored properties are only single-component if MemoryLayout.offset(of:) returns an offset
61-
func project<T>(_: T.Type) -> Bool {
62-
_keyPathOffset(T.self, self) == nil
63-
}
64-
if _openExistential(Self.rootType, do: project) {
63+
// Stored property components are either just the payload, or the payload plus 32 bits if the offset is not stored in-line
64+
// Note: we cannot use MemoryLayout.offset(of:) here because not all single-component keypaths have direct offsets (for example, stored properties in final classes)
65+
let size = (firstComponentHeader._keyPathComponentHeader_storedIsInline) ? MemoryLayout<UInt32>.size : MemoryLayout<UInt64>.size
66+
if header._keyPathHeader_bufferSize > size {
6567
fatalError("Predicate does not support keypaths with multiple components")
6668
}
6769
case 2: // computed

Tests/FoundationEssentialsTests/PredicateTests.swift

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -310,6 +310,13 @@ private struct PredicateTests {
310310
$0.a == $0.c && $0.b == now
311311
}
312312
}
313+
314+
@Test func finalKeyPaths() {
315+
final class Foo {
316+
var id: Int = 1
317+
}
318+
_ = #Predicate<Foo> { $0.id == 2 }
319+
}
313320

314321
@Test
315322
func regex() throws {

0 commit comments

Comments
 (0)