Skip to content

Commit 0eeb99b

Browse files
authored
(112983484) Predicate coding shouldn't crash on types with type parameter packs
1 parent 8d8955d commit 0eeb99b

File tree

2 files changed

+102
-0
lines changed

2 files changed

+102
-0
lines changed

Sources/FoundationEssentials/Predicate/Archiving/ExpressionArchiving.swift

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,14 @@ enum PredicateCodableError : Error, CustomStringConvertible {
2020
case disallowedType(typeName: String, path: String)
2121
case disallowedIdentifier(String, path: String)
2222
case reconstructionFailure(PartialType, [Type])
23+
case variadicType(typeName: String, path: String)
2324

2425
var description: String {
2526
switch self {
2627
case .disallowedType(let typeName, let path): return "The '\(typeName)' type is not in the provided allowlist (required by \(path))"
2728
case .disallowedIdentifier(let id, let path): return "The '\(id)' identifier is not in the provided allowlist (required by \(path))"
2829
case .reconstructionFailure(let partial, let args): return "Reconstruction of '\(partial.name)' with the arguments \(args.map(\.swiftType)) failed"
30+
case .variadicType(let typeName, let path): return "The '\(typeName)' type is not allowed because it contains type pack parameters (required by \(path))"
2931
}
3032
}
3133
}
@@ -63,6 +65,11 @@ private struct ExpressionStructure : Codable {
6365
}
6466

6567
init(_ type: Type, with configuration: PredicateCodableConfiguration, path: [String] = []) throws {
68+
#if canImport(ReflectionInternal, _version: "18")
69+
if type.partial?.hasParameterPacks ?? false {
70+
throw PredicateCodableError.variadicType(typeName: _typeName(type.swiftType), path: "/\(path.joined(separator: "/"))")
71+
}
72+
#endif
6673
guard let result = configuration._identifier(for: type) else {
6774
throw PredicateCodableError.disallowedType(typeName: _typeName(type.swiftType), path: "/\(path.joined(separator: "/"))")
6875
}
@@ -91,6 +98,12 @@ private struct ExpressionStructure : Codable {
9198
partial = partialType
9299
}
93100

101+
#if canImport(ReflectionInternal, _version: "18")
102+
if partial.hasParameterPacks {
103+
throw PredicateCodableError.variadicType(typeName: partial.name, path: "/\(path.joined(separator: "/"))")
104+
}
105+
#endif
106+
94107
let argTypes = try args.map {
95108
try $0.reconstruct(with: configuration, path: path + [identifier])
96109
}

Tests/FoundationEssentialsTests/PredicateCodableTests.swift

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -542,6 +542,95 @@ final class PredicateCodableTests: XCTestCase {
542542
XCTAssertThrowsError(try _encodeDecode(predicate, for: EmptyConfig.self))
543543
XCTAssertThrowsError(try _encodeDecode(predicate))
544544
}
545+
546+
func testCapturedVariadicTypes() throws {
547+
#if !canImport(ReflectionInternal, _version: "18")
548+
throw XCTSkip("Insufficient ReflectionInternal version for this test")
549+
#else
550+
struct A<each T> : Equatable, Codable {
551+
init(_: repeat (each T).Type) {}
552+
553+
func encode(to encoder: Encoder) throws {
554+
var container = encoder.singleValueContainer()
555+
try container.encodeNil()
556+
}
557+
558+
init(from decoder: Decoder) throws {
559+
var container = try decoder.singleValueContainer()
560+
guard container.decodeNil() else {
561+
throw DecodingError.dataCorrupted(.init(codingPath: container.codingPath, debugDescription: "Did not find encoded nil"))
562+
}
563+
}
564+
}
565+
566+
let a = A(String.self, Int.self)
567+
568+
let predicate = Predicate<Int> { _ in
569+
// a == a
570+
PredicateExpressions.build_Equal(
571+
lhs: PredicateExpressions.build_Arg(a),
572+
rhs: PredicateExpressions.build_Arg(a)
573+
)
574+
}
575+
576+
let encoder = JSONEncoder()
577+
var config = PredicateCodableConfiguration.standardConfiguration
578+
config.allowPartialType(A< >.self, identifier: "PredicateCodableTests.A")
579+
XCTAssertThrowsError(try encoder.encode(predicate, configuration: config)) {
580+
XCTAssertTrue(String(describing: $0).contains("type is not allowed because it contains type pack parameters"))
581+
}
582+
583+
let json = """
584+
[
585+
{
586+
"expression" : [
587+
null,
588+
null
589+
],
590+
"structure" : {
591+
"identifier" : "PredicateExpressions.Equal",
592+
"args" : [
593+
{
594+
"identifier" : "PredicateExpressions.Value",
595+
"args" : [
596+
{
597+
"identifier": "PredicateCodableTests.A",
598+
"args": [
599+
"Swift.String",
600+
"Swift.Int"
601+
]
602+
}
603+
]
604+
},
605+
{
606+
"args" : [
607+
{
608+
"identifier": "PredicateCodableTests.A",
609+
"args": [
610+
"Swift.String",
611+
"Swift.Int"
612+
]
613+
}
614+
],
615+
"identifier" : "PredicateExpressions.Value"
616+
}
617+
]
618+
},
619+
"variable" : [
620+
{
621+
"key" : 0
622+
}
623+
]
624+
}
625+
]
626+
"""
627+
628+
let decoder = JSONDecoder()
629+
XCTAssertThrowsError(try decoder.decode(Predicate<Int>.self, from: json.data(using: .utf8)!, configuration: config)) {
630+
XCTAssertTrue(String(describing: $0).contains("type is not allowed because it contains type pack parameters"))
631+
}
632+
#endif
633+
}
545634
}
546635

547636
#endif // FOUNDATION_FRAMEWORK

0 commit comments

Comments
 (0)