Skip to content

Commit c67f24e

Browse files
authored
rdar://132940984 (Regression: Swift Decoding of Double.greatestFiniteMagnitude as Int causes a crash) (#827)
1 parent 84ea411 commit c67f24e

File tree

2 files changed

+12
-6
lines changed

2 files changed

+12
-6
lines changed

Sources/FoundationEssentials/JSON/JSONDecoder.swift

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -988,17 +988,18 @@ extension JSONDecoderImpl: Decoder {
988988
static private func _slowpath_unwrapFixedWidthInteger<T: FixedWidthInteger>(as type: T.Type, json5: Bool, numberBuffer: BufferView<UInt8>, fullSource: BufferView<UInt8>, digitBeginning: BufferViewIndex<UInt8>, for codingPathNode: _CodingPathNode, _ additionalKey: (some CodingKey)?) throws -> T {
989989
// This is the slow path... If the fast path has failed. For example for "34.0" as an integer, we try to parse as either a Decimal or a Double and then convert back, losslessly.
990990
if let double = Double(prevalidatedBuffer: numberBuffer) {
991+
// T.init(exactly:) guards against non-integer Double(s), but the parser may
992+
// have already transformed the non-integer "1.0000000000000001" into 1, etc.
993+
// Proper lossless behavior should be implemented by the parser.
994+
guard let value = T(exactly: double) else {
995+
throw JSONError.numberIsNotRepresentableInSwift(parsed: String(decoding: numberBuffer, as: UTF8.self))
996+
}
997+
991998
// The distance between Double(s) is >=2 from ±2^53.
992999
// 2^53 may represent either 2^53 or 2^53+1 rounded toward zero.
9931000
// This code makes it so you don't get integer A from integer B.
9941001
// Proper lossless behavior should be implemented by the parser.
9951002
if double.magnitude < Double(sign: .plus, exponent: Double.significandBitCount + 1, significand: 1) {
996-
// T.init(exactly:) guards against non-integer Double(s), but the parser may
997-
// have already transformed the non-integer "1.0000000000000001" into 1, etc.
998-
// Proper lossless behavior should be implemented by the parser.
999-
guard let value = T(exactly: double) else {
1000-
throw JSONError.numberIsNotRepresentableInSwift(parsed: String(decoding: numberBuffer, as: UTF8.self))
1001-
}
10021003
return value
10031004
}
10041005
}

Tests/FoundationEssentialsTests/JSONEncoderTests.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1459,6 +1459,11 @@ final class JSONEncoderTests : XCTestCase {
14591459
_testRoundTrip(of: testValue)
14601460
}
14611461

1462+
func test_decodeLargeDoubleAsInteger() {
1463+
let data = try! JSONEncoder().encode(Double.greatestFiniteMagnitude)
1464+
XCTAssertThrowsError(try JSONDecoder().decode(UInt64.self, from: data))
1465+
}
1466+
14621467
func test_localeDecimalPolicyIndependence() {
14631468
var currentLocale: UnsafeMutablePointer<CChar>? = nil
14641469
if let localePtr = setlocale(LC_ALL, nil) {

0 commit comments

Comments
 (0)