Skip to content

[6.0] rdar://132940984 (Regression: Swift Decoding of Double.greatestFiniteMagnitude as Int causes a crash) (#827) #829

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 7 additions & 6 deletions Sources/FoundationEssentials/JSON/JSONDecoder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -988,17 +988,18 @@ extension JSONDecoderImpl: Decoder {
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 {
// 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.
if let double = Double(prevalidatedBuffer: numberBuffer) {
// T.init(exactly:) guards against non-integer Double(s), but the parser may
// have already transformed the non-integer "1.0000000000000001" into 1, etc.
// Proper lossless behavior should be implemented by the parser.
guard let value = T(exactly: double) else {
throw JSONError.numberIsNotRepresentableInSwift(parsed: String(decoding: numberBuffer, as: UTF8.self))
}

// The distance between Double(s) is >=2 from ±2^53.
// 2^53 may represent either 2^53 or 2^53+1 rounded toward zero.
// This code makes it so you don't get integer A from integer B.
// Proper lossless behavior should be implemented by the parser.
if double.magnitude < Double(sign: .plus, exponent: Double.significandBitCount + 1, significand: 1) {
// T.init(exactly:) guards against non-integer Double(s), but the parser may
// have already transformed the non-integer "1.0000000000000001" into 1, etc.
// Proper lossless behavior should be implemented by the parser.
guard let value = T(exactly: double) else {
throw JSONError.numberIsNotRepresentableInSwift(parsed: String(decoding: numberBuffer, as: UTF8.self))
}
return value
}
}
Expand Down
5 changes: 5 additions & 0 deletions Tests/FoundationEssentialsTests/JSONEncoderTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1450,6 +1450,11 @@ final class JSONEncoderTests : XCTestCase {
_testRoundTrip(of: testValue)
}

func test_decodeLargeDoubleAsInteger() {
let data = try! JSONEncoder().encode(Double.greatestFiniteMagnitude)
XCTAssertThrowsError(try JSONDecoder().decode(UInt64.self, from: data))
}

func test_localeDecimalPolicyIndependence() {
var currentLocale: UnsafeMutablePointer<CChar>? = nil
if let localePtr = setlocale(LC_ALL, nil) {
Expand Down