Skip to content

Improve Plist and JSON Encoder/Decoder resilience to errors thrown during coding #12969

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

Closed
wants to merge 2 commits into from
Closed
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
78 changes: 43 additions & 35 deletions stdlib/public/SDK/Foundation/JSONEncoder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -664,31 +664,38 @@ extension _JSONEncoder {

// This method is called "box_" instead of "box" to disambiguate it from the overloads. Because the return type here is different from all of the "box" overloads (and is more general), any "box" calls in here would call back into "box" recursively instead of calling the appropriate overload, which is not what we want.
fileprivate func box_<T : Encodable>(_ value: T) throws -> NSObject? {
if T.self == Date.self || T.self == NSDate.self {
// Respect Date encoding strategy
return try self.box((value as! Date))
} else if T.self == Data.self || T.self == NSData.self {
// Respect Data encoding strategy
return try self.box((value as! Data))
} else if T.self == URL.self || T.self == NSURL.self {
// Encode URLs as single strings.
return self.box((value as! URL).absoluteString)
} else if T.self == Decimal.self || T.self == NSDecimalNumber.self {
// JSONSerialization can natively handle NSDecimalNumber.
return (value as! NSDecimalNumber)
}

// The value should request a container from the _JSONEncoder.
let depth = self.storage.count
try value.encode(to: self)
do {
if T.self == Date.self || T.self == NSDate.self {
// Respect Date encoding strategy
return try self.box((value as! Date))
} else if T.self == Data.self || T.self == NSData.self {
// Respect Data encoding strategy
return try self.box((value as! Data))
} else if T.self == URL.self || T.self == NSURL.self {
// Encode URLs as single strings.
return self.box((value as! URL).absoluteString)
} else if T.self == Decimal.self || T.self == NSDecimalNumber.self {
// JSONSerialization can natively handle NSDecimalNumber.
return (value as! NSDecimalNumber)
}

// The top container should be a new container.
guard self.storage.count > depth else {
return nil
}
// The value should request a container from the _JSONEncoder.
try value.encode(to: self)

return self.storage.popContainer()
}
// The top container should be a new container.
guard self.storage.count > depth else {
return nil
}

return self.storage.popContainer()
} catch let error {
if self.storage.count > depth {
assert(self.storage.count == depth + 1, "Internal inconsistency: more than one container on encoder stack during error unwinding")
self.storage.popContainer()
}
throw error
}
}

// MARK: - _JSONReferencingEncoder
Expand Down Expand Up @@ -2011,9 +2018,9 @@ extension _JSONDecoder {
switch self.options.dateDecodingStrategy {
case .deferredToDate:
self.storage.push(container: value)
let date = try Date(from: self)
self.storage.popContainer()
return date
defer { self.storage.popContainer() }

return try Date(from: self)

case .secondsSince1970:
let double = try self.unbox(value, as: Double.self)!
Expand Down Expand Up @@ -2045,9 +2052,9 @@ extension _JSONDecoder {

case .custom(let closure):
self.storage.push(container: value)
let date = try closure(self)
self.storage.popContainer()
return date
defer { self.storage.popContainer() }

return try closure(self)
}
}

Expand All @@ -2057,9 +2064,9 @@ extension _JSONDecoder {
switch self.options.dataDecodingStrategy {
case .deferredToData:
self.storage.push(container: value)
let data = try Data(from: self)
self.storage.popContainer()
return data
defer { self.storage.popContainer() }

return try Data(from: self)

case .base64:
guard let string = value as? String else {
Expand All @@ -2074,9 +2081,9 @@ extension _JSONDecoder {

case .custom(let closure):
self.storage.push(container: value)
let data = try closure(self)
self.storage.popContainer()
return data
defer { self.storage.popContainer() }

return try closure(self)
}
}

Expand Down Expand Up @@ -2116,8 +2123,9 @@ extension _JSONDecoder {
decoded = decimal as! T
} else {
self.storage.push(container: value)
defer { self.storage.popContainer() }

decoded = try type.init(from: self)
self.storage.popContainer()
}

return decoded
Expand Down
13 changes: 11 additions & 2 deletions stdlib/public/SDK/Foundation/PlistEncoder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -490,7 +490,15 @@ extension _PlistEncoder {

// The value should request a container from the _PlistEncoder.
let depth = self.storage.count
try value.encode(to: self)
do {
try value.encode(to: self)
} catch let error {
if self.storage.count > depth {
assert(self.storage.count == depth + 1, "Internal inconsistency: more than one container on encoder stack during error unwinding")
self.storage.popContainer()
}
throw error
}

// The top container should be a new container.
guard self.storage.count > depth else {
Expand Down Expand Up @@ -1764,8 +1772,9 @@ extension _PlistDecoder {
decoded = data as! T
} else {
self.storage.push(container: value)
defer { self.storage.popContainer() }

decoded = try type.init(from: self)
self.storage.popContainer()
}

return decoded
Expand Down