Skip to content

Commit 1457e4d

Browse files
authored
Merge pull request #11885 from cpwhidden/decoding-bool-coercion
[stdlib] Prevent coercion from Bool to numerical types when decoding JSON and plist
2 parents 110c843 + 997fe01 commit 1457e4d

File tree

4 files changed

+98
-24
lines changed

4 files changed

+98
-24
lines changed

stdlib/public/SDK/Foundation/JSONEncoder.swift

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1767,7 +1767,7 @@ extension _JSONDecoder {
17671767
fileprivate func unbox(_ value: Any, as type: Int.Type) throws -> Int? {
17681768
guard !(value is NSNull) else { return nil }
17691769

1770-
guard let number = value as? NSNumber else {
1770+
guard let number = value as? NSNumber, number !== kCFBooleanTrue, number !== kCFBooleanFalse else {
17711771
throw DecodingError._typeMismatch(at: self.codingPath, expectation: type, reality: value)
17721772
}
17731773

@@ -1782,7 +1782,7 @@ extension _JSONDecoder {
17821782
fileprivate func unbox(_ value: Any, as type: Int8.Type) throws -> Int8? {
17831783
guard !(value is NSNull) else { return nil }
17841784

1785-
guard let number = value as? NSNumber else {
1785+
guard let number = value as? NSNumber, number !== kCFBooleanTrue, number !== kCFBooleanFalse else {
17861786
throw DecodingError._typeMismatch(at: self.codingPath, expectation: type, reality: value)
17871787
}
17881788

@@ -1797,7 +1797,7 @@ extension _JSONDecoder {
17971797
fileprivate func unbox(_ value: Any, as type: Int16.Type) throws -> Int16? {
17981798
guard !(value is NSNull) else { return nil }
17991799

1800-
guard let number = value as? NSNumber else {
1800+
guard let number = value as? NSNumber, number !== kCFBooleanTrue, number !== kCFBooleanFalse else {
18011801
throw DecodingError._typeMismatch(at: self.codingPath, expectation: type, reality: value)
18021802
}
18031803

@@ -1812,7 +1812,7 @@ extension _JSONDecoder {
18121812
fileprivate func unbox(_ value: Any, as type: Int32.Type) throws -> Int32? {
18131813
guard !(value is NSNull) else { return nil }
18141814

1815-
guard let number = value as? NSNumber else {
1815+
guard let number = value as? NSNumber, number !== kCFBooleanTrue, number !== kCFBooleanFalse else {
18161816
throw DecodingError._typeMismatch(at: self.codingPath, expectation: type, reality: value)
18171817
}
18181818

@@ -1827,7 +1827,7 @@ extension _JSONDecoder {
18271827
fileprivate func unbox(_ value: Any, as type: Int64.Type) throws -> Int64? {
18281828
guard !(value is NSNull) else { return nil }
18291829

1830-
guard let number = value as? NSNumber else {
1830+
guard let number = value as? NSNumber, number !== kCFBooleanTrue, number !== kCFBooleanFalse else {
18311831
throw DecodingError._typeMismatch(at: self.codingPath, expectation: type, reality: value)
18321832
}
18331833

@@ -1842,7 +1842,7 @@ extension _JSONDecoder {
18421842
fileprivate func unbox(_ value: Any, as type: UInt.Type) throws -> UInt? {
18431843
guard !(value is NSNull) else { return nil }
18441844

1845-
guard let number = value as? NSNumber else {
1845+
guard let number = value as? NSNumber, number !== kCFBooleanTrue, number !== kCFBooleanFalse else {
18461846
throw DecodingError._typeMismatch(at: self.codingPath, expectation: type, reality: value)
18471847
}
18481848

@@ -1857,7 +1857,7 @@ extension _JSONDecoder {
18571857
fileprivate func unbox(_ value: Any, as type: UInt8.Type) throws -> UInt8? {
18581858
guard !(value is NSNull) else { return nil }
18591859

1860-
guard let number = value as? NSNumber else {
1860+
guard let number = value as? NSNumber, number !== kCFBooleanTrue, number !== kCFBooleanFalse else {
18611861
throw DecodingError._typeMismatch(at: self.codingPath, expectation: type, reality: value)
18621862
}
18631863

@@ -1872,7 +1872,7 @@ extension _JSONDecoder {
18721872
fileprivate func unbox(_ value: Any, as type: UInt16.Type) throws -> UInt16? {
18731873
guard !(value is NSNull) else { return nil }
18741874

1875-
guard let number = value as? NSNumber else {
1875+
guard let number = value as? NSNumber, number !== kCFBooleanTrue, number !== kCFBooleanFalse else {
18761876
throw DecodingError._typeMismatch(at: self.codingPath, expectation: type, reality: value)
18771877
}
18781878

@@ -1887,7 +1887,7 @@ extension _JSONDecoder {
18871887
fileprivate func unbox(_ value: Any, as type: UInt32.Type) throws -> UInt32? {
18881888
guard !(value is NSNull) else { return nil }
18891889

1890-
guard let number = value as? NSNumber else {
1890+
guard let number = value as? NSNumber, number !== kCFBooleanTrue, number !== kCFBooleanFalse else {
18911891
throw DecodingError._typeMismatch(at: self.codingPath, expectation: type, reality: value)
18921892
}
18931893

@@ -1902,7 +1902,7 @@ extension _JSONDecoder {
19021902
fileprivate func unbox(_ value: Any, as type: UInt64.Type) throws -> UInt64? {
19031903
guard !(value is NSNull) else { return nil }
19041904

1905-
guard let number = value as? NSNumber else {
1905+
guard let number = value as? NSNumber, number !== kCFBooleanTrue, number !== kCFBooleanFalse else {
19061906
throw DecodingError._typeMismatch(at: self.codingPath, expectation: type, reality: value)
19071907
}
19081908

@@ -1917,7 +1917,7 @@ extension _JSONDecoder {
19171917
fileprivate func unbox(_ value: Any, as type: Float.Type) throws -> Float? {
19181918
guard !(value is NSNull) else { return nil }
19191919

1920-
if let number = value as? NSNumber {
1920+
if let number = value as? NSNumber, number !== kCFBooleanTrue, number !== kCFBooleanFalse {
19211921
// We are willing to return a Float by losing precision:
19221922
// * If the original value was integral,
19231923
// * and the integral value was > Float.greatestFiniteMagnitude, we will fail
@@ -1963,7 +1963,7 @@ extension _JSONDecoder {
19631963
fileprivate func unbox(_ value: Any, as type: Double.Type) throws -> Double? {
19641964
guard !(value is NSNull) else { return nil }
19651965

1966-
if let number = value as? NSNumber {
1966+
if let number = value as? NSNumber, number !== kCFBooleanTrue, number !== kCFBooleanFalse {
19671967
// We are always willing to return the number as a Double:
19681968
// * If the original value was integral, it is guaranteed to fit in a Double; we are willing to lose precision past 2^53 if you encoded a UInt64 but requested a Double
19691969
// * If it was a Float or Double, you will get back the precise value

stdlib/public/SDK/Foundation/PlistEncoder.swift

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1549,7 +1549,7 @@ extension _PlistDecoder {
15491549
fileprivate func unbox(_ value: Any, as type: Int.Type) throws -> Int? {
15501550
if let string = value as? String, string == _plistNull { return nil }
15511551

1552-
guard let number = value as? NSNumber else {
1552+
guard let number = value as? NSNumber, number !== kCFBooleanTrue, number !== kCFBooleanFalse else {
15531553
throw DecodingError._typeMismatch(at: self.codingPath, expectation: type, reality: value)
15541554
}
15551555

@@ -1564,7 +1564,7 @@ extension _PlistDecoder {
15641564
fileprivate func unbox(_ value: Any, as type: Int8.Type) throws -> Int8? {
15651565
if let string = value as? String, string == _plistNull { return nil }
15661566

1567-
guard let number = value as? NSNumber else {
1567+
guard let number = value as? NSNumber, number !== kCFBooleanTrue, number !== kCFBooleanFalse else {
15681568
throw DecodingError._typeMismatch(at: self.codingPath, expectation: type, reality: value)
15691569
}
15701570

@@ -1579,7 +1579,7 @@ extension _PlistDecoder {
15791579
fileprivate func unbox(_ value: Any, as type: Int16.Type) throws -> Int16? {
15801580
if let string = value as? String, string == _plistNull { return nil }
15811581

1582-
guard let number = value as? NSNumber else {
1582+
guard let number = value as? NSNumber, number !== kCFBooleanTrue, number !== kCFBooleanFalse else {
15831583
throw DecodingError._typeMismatch(at: self.codingPath, expectation: type, reality: value)
15841584
}
15851585

@@ -1594,7 +1594,7 @@ extension _PlistDecoder {
15941594
fileprivate func unbox(_ value: Any, as type: Int32.Type) throws -> Int32? {
15951595
if let string = value as? String, string == _plistNull { return nil }
15961596

1597-
guard let number = value as? NSNumber else {
1597+
guard let number = value as? NSNumber, number !== kCFBooleanTrue, number !== kCFBooleanFalse else {
15981598
throw DecodingError._typeMismatch(at: self.codingPath, expectation: type, reality: value)
15991599
}
16001600

@@ -1609,7 +1609,7 @@ extension _PlistDecoder {
16091609
fileprivate func unbox(_ value: Any, as type: Int64.Type) throws -> Int64? {
16101610
if let string = value as? String, string == _plistNull { return nil }
16111611

1612-
guard let number = value as? NSNumber else {
1612+
guard let number = value as? NSNumber, number !== kCFBooleanTrue, number !== kCFBooleanFalse else {
16131613
throw DecodingError._typeMismatch(at: self.codingPath, expectation: type, reality: value)
16141614
}
16151615

@@ -1624,7 +1624,7 @@ extension _PlistDecoder {
16241624
fileprivate func unbox(_ value: Any, as type: UInt.Type) throws -> UInt? {
16251625
if let string = value as? String, string == _plistNull { return nil }
16261626

1627-
guard let number = value as? NSNumber else {
1627+
guard let number = value as? NSNumber, number !== kCFBooleanTrue, number !== kCFBooleanFalse else {
16281628
throw DecodingError._typeMismatch(at: self.codingPath, expectation: type, reality: value)
16291629
}
16301630

@@ -1639,7 +1639,7 @@ extension _PlistDecoder {
16391639
fileprivate func unbox(_ value: Any, as type: UInt8.Type) throws -> UInt8? {
16401640
if let string = value as? String, string == _plistNull { return nil }
16411641

1642-
guard let number = value as? NSNumber else {
1642+
guard let number = value as? NSNumber, number !== kCFBooleanTrue, number !== kCFBooleanFalse else {
16431643
throw DecodingError._typeMismatch(at: self.codingPath, expectation: type, reality: value)
16441644
}
16451645

@@ -1654,7 +1654,7 @@ extension _PlistDecoder {
16541654
fileprivate func unbox(_ value: Any, as type: UInt16.Type) throws -> UInt16? {
16551655
if let string = value as? String, string == _plistNull { return nil }
16561656

1657-
guard let number = value as? NSNumber else {
1657+
guard let number = value as? NSNumber, number !== kCFBooleanTrue, number !== kCFBooleanFalse else {
16581658
throw DecodingError._typeMismatch(at: self.codingPath, expectation: type, reality: value)
16591659
}
16601660

@@ -1669,7 +1669,7 @@ extension _PlistDecoder {
16691669
fileprivate func unbox(_ value: Any, as type: UInt32.Type) throws -> UInt32? {
16701670
if let string = value as? String, string == _plistNull { return nil }
16711671

1672-
guard let number = value as? NSNumber else {
1672+
guard let number = value as? NSNumber, number !== kCFBooleanTrue, number !== kCFBooleanFalse else {
16731673
throw DecodingError._typeMismatch(at: self.codingPath, expectation: type, reality: value)
16741674
}
16751675

@@ -1684,7 +1684,7 @@ extension _PlistDecoder {
16841684
fileprivate func unbox(_ value: Any, as type: UInt64.Type) throws -> UInt64? {
16851685
if let string = value as? String, string == _plistNull { return nil }
16861686

1687-
guard let number = value as? NSNumber else {
1687+
guard let number = value as? NSNumber, number !== kCFBooleanTrue, number !== kCFBooleanFalse else {
16881688
throw DecodingError._typeMismatch(at: self.codingPath, expectation: type, reality: value)
16891689
}
16901690

@@ -1699,7 +1699,7 @@ extension _PlistDecoder {
16991699
fileprivate func unbox(_ value: Any, as type: Float.Type) throws -> Float? {
17001700
if let string = value as? String, string == _plistNull { return nil }
17011701

1702-
guard let number = value as? NSNumber else {
1702+
guard let number = value as? NSNumber, number !== kCFBooleanTrue, number !== kCFBooleanFalse else {
17031703
throw DecodingError._typeMismatch(at: self.codingPath, expectation: type, reality: value)
17041704
}
17051705

@@ -1714,7 +1714,7 @@ extension _PlistDecoder {
17141714
fileprivate func unbox(_ value: Any, as type: Double.Type) throws -> Double? {
17151715
if let string = value as? String, string == _plistNull { return nil }
17161716

1717-
guard let number = value as? NSNumber else {
1717+
guard let number = value as? NSNumber, number !== kCFBooleanTrue, number !== kCFBooleanFalse else {
17181718
throw DecodingError._typeMismatch(at: self.codingPath, expectation: type, reality: value)
17191719
}
17201720

test/stdlib/TestJSONEncoder.swift

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -449,6 +449,34 @@ class TestJSONEncoder : TestJSONEncoderSuper {
449449
// Optional URLs should encode the same way.
450450
_testRoundTrip(of: OptionalTopLevelWrapper(url), expectedJSON: expectedJSON)
451451
}
452+
453+
// MARK: - Type coercion
454+
func testTypeCoercion() {
455+
_testRoundTripTypeCoercionFailure(of: [false, true], as: [Int].self)
456+
_testRoundTripTypeCoercionFailure(of: [false, true], as: [Int8].self)
457+
_testRoundTripTypeCoercionFailure(of: [false, true], as: [Int16].self)
458+
_testRoundTripTypeCoercionFailure(of: [false, true], as: [Int32].self)
459+
_testRoundTripTypeCoercionFailure(of: [false, true], as: [Int64].self)
460+
_testRoundTripTypeCoercionFailure(of: [false, true], as: [UInt].self)
461+
_testRoundTripTypeCoercionFailure(of: [false, true], as: [UInt8].self)
462+
_testRoundTripTypeCoercionFailure(of: [false, true], as: [UInt16].self)
463+
_testRoundTripTypeCoercionFailure(of: [false, true], as: [UInt32].self)
464+
_testRoundTripTypeCoercionFailure(of: [false, true], as: [UInt64].self)
465+
_testRoundTripTypeCoercionFailure(of: [false, true], as: [Float].self)
466+
_testRoundTripTypeCoercionFailure(of: [false, true], as: [Double].self)
467+
_testRoundTripTypeCoercionFailure(of: [0, 1] as [Int], as: [Bool].self)
468+
_testRoundTripTypeCoercionFailure(of: [0, 1] as [Int8], as: [Bool].self)
469+
_testRoundTripTypeCoercionFailure(of: [0, 1] as [Int16], as: [Bool].self)
470+
_testRoundTripTypeCoercionFailure(of: [0, 1] as [Int32], as: [Bool].self)
471+
_testRoundTripTypeCoercionFailure(of: [0, 1] as [Int64], as: [Bool].self)
472+
_testRoundTripTypeCoercionFailure(of: [0, 1] as [UInt], as: [Bool].self)
473+
_testRoundTripTypeCoercionFailure(of: [0, 1] as [UInt8], as: [Bool].self)
474+
_testRoundTripTypeCoercionFailure(of: [0, 1] as [UInt16], as: [Bool].self)
475+
_testRoundTripTypeCoercionFailure(of: [0, 1] as [UInt32], as: [Bool].self)
476+
_testRoundTripTypeCoercionFailure(of: [0, 1] as [UInt64], as: [Bool].self)
477+
_testRoundTripTypeCoercionFailure(of: [0.0, 1.0] as [Float], as: [Bool].self)
478+
_testRoundTripTypeCoercionFailure(of: [0.0, 1.0] as [Double], as: [Bool].self)
479+
}
452480

453481
// MARK: - Helper Functions
454482
private var _jsonEmptyDictionary: Data {
@@ -498,6 +526,14 @@ class TestJSONEncoder : TestJSONEncoderSuper {
498526
expectUnreachable("Failed to decode \(T.self) from JSON: \(error)")
499527
}
500528
}
529+
530+
private func _testRoundTripTypeCoercionFailure<T,U>(of value: T, as type: U.Type) where T : Codable, U : Codable {
531+
do {
532+
let data = try JSONEncoder().encode(value)
533+
let _ = try JSONDecoder().decode(U.self, from: data)
534+
expectUnreachable("Coercion from \(T.self) to \(U.self) was expected to fail.")
535+
} catch {}
536+
}
501537
}
502538

503539
// MARK: - Helper Global Functions
@@ -1067,5 +1103,6 @@ JSONEncoderTests.test("testNestedContainerCodingPaths") { TestJSONEncoder().test
10671103
JSONEncoderTests.test("testSuperEncoderCodingPaths") { TestJSONEncoder().testSuperEncoderCodingPaths() }
10681104
JSONEncoderTests.test("testInterceptDecimal") { TestJSONEncoder().testInterceptDecimal() }
10691105
JSONEncoderTests.test("testInterceptURL") { TestJSONEncoder().testInterceptURL() }
1106+
JSONEncoderTests.test("testTypeCoercion") { TestJSONEncoder().testTypeCoercion() }
10701107
runAllTests()
10711108
#endif

test/stdlib/TestPlistEncoder.swift

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,34 @@ class TestPropertyListEncoder : TestPropertyListEncoderSuper {
170170
_testRoundTrip(of: topLevel, in: .xml, expectedPlist: try! PropertyListSerialization.data(fromPropertyList: plist, format: .xml, options: 0))
171171
}
172172

173+
// MARK: - Type coercion
174+
func testTypeCoercion() {
175+
_testRoundTripTypeCoercionFailure(of: [false, true], as: [Int].self)
176+
_testRoundTripTypeCoercionFailure(of: [false, true], as: [Int8].self)
177+
_testRoundTripTypeCoercionFailure(of: [false, true], as: [Int16].self)
178+
_testRoundTripTypeCoercionFailure(of: [false, true], as: [Int32].self)
179+
_testRoundTripTypeCoercionFailure(of: [false, true], as: [Int64].self)
180+
_testRoundTripTypeCoercionFailure(of: [false, true], as: [UInt].self)
181+
_testRoundTripTypeCoercionFailure(of: [false, true], as: [UInt8].self)
182+
_testRoundTripTypeCoercionFailure(of: [false, true], as: [UInt16].self)
183+
_testRoundTripTypeCoercionFailure(of: [false, true], as: [UInt32].self)
184+
_testRoundTripTypeCoercionFailure(of: [false, true], as: [UInt64].self)
185+
_testRoundTripTypeCoercionFailure(of: [false, true], as: [Float].self)
186+
_testRoundTripTypeCoercionFailure(of: [false, true], as: [Double].self)
187+
_testRoundTripTypeCoercionFailure(of: [0, 1] as [Int], as: [Bool].self)
188+
_testRoundTripTypeCoercionFailure(of: [0, 1] as [Int8], as: [Bool].self)
189+
_testRoundTripTypeCoercionFailure(of: [0, 1] as [Int16], as: [Bool].self)
190+
_testRoundTripTypeCoercionFailure(of: [0, 1] as [Int32], as: [Bool].self)
191+
_testRoundTripTypeCoercionFailure(of: [0, 1] as [Int64], as: [Bool].self)
192+
_testRoundTripTypeCoercionFailure(of: [0, 1] as [UInt], as: [Bool].self)
193+
_testRoundTripTypeCoercionFailure(of: [0, 1] as [UInt8], as: [Bool].self)
194+
_testRoundTripTypeCoercionFailure(of: [0, 1] as [UInt16], as: [Bool].self)
195+
_testRoundTripTypeCoercionFailure(of: [0, 1] as [UInt32], as: [Bool].self)
196+
_testRoundTripTypeCoercionFailure(of: [0, 1] as [UInt64], as: [Bool].self)
197+
_testRoundTripTypeCoercionFailure(of: [0.0, 1.0] as [Float], as: [Bool].self)
198+
_testRoundTripTypeCoercionFailure(of: [0.0, 1.0] as [Double], as: [Bool].self)
199+
}
200+
173201
// MARK: - Helper Functions
174202
private var _plistEmptyDictionaryBinary: Data {
175203
return Data(base64Encoded: "YnBsaXN0MDDQCAAAAAAAAAEBAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAJ")!
@@ -211,6 +239,14 @@ class TestPropertyListEncoder : TestPropertyListEncoderSuper {
211239
expectUnreachable("Failed to decode \(T.self) from plist: \(error)")
212240
}
213241
}
242+
243+
private func _testRoundTripTypeCoercionFailure<T,U>(of value: T, as type: U.Type) where T : Codable, U : Codable {
244+
do {
245+
let data = try PropertyListEncoder().encode(value)
246+
let _ = try PropertyListDecoder().decode(U.self, from: data)
247+
expectUnreachable("Coercion from \(T.self) to \(U.self) was expected to fail.")
248+
} catch {}
249+
}
214250
}
215251

216252
// MARK: - Helper Global Functions
@@ -694,5 +730,6 @@ PropertyListEncoderTests.test("testSuperEncoderCodingPaths") { TestPropertyListE
694730
PropertyListEncoderTests.test("testEncodingTopLevelData") { TestPropertyListEncoder().testEncodingTopLevelData() }
695731
PropertyListEncoderTests.test("testInterceptData") { TestPropertyListEncoder().testInterceptData() }
696732
PropertyListEncoderTests.test("testInterceptDate") { TestPropertyListEncoder().testInterceptDate() }
733+
PropertyListEncoderTests.test("testTypeCoercion") { TestPropertyListEncoder().testTypeCoercion() }
697734
runAllTests()
698735
#endif

0 commit comments

Comments
 (0)