//===----------------------------------------------------------------------===// // // This source file is part of the Swift Collections open source project // // Copyright (c) 2021 Apple Inc. and the Swift project authors // Licensed under Apache License v2.0 with Runtime Library Exception // // See https://swift.org/LICENSE.txt for license information // //===----------------------------------------------------------------------===// /// A Plist-style encoder that attempts to enforce a strict interpretation of /// the `Encodable` protocol requirements. The encoded value is of an enum type /// that exactly reflects the values encoded, allowing easy matching of /// actual vs expected outputs. /// /// This encoder verifies that `encoder(to:)` implementations do not keep /// containers alive beyond their expected lifetime. /// /// FIXME: Fix error reporting. We ought to throw errors instead of trapping /// on validation problems, and we should include context information to help /// isolating the problem. public class MinimalEncoder { public enum Value: Hashable { case null case bool(Bool) case int(Int) case int8(Int8) case int16(Int16) case int32(Int32) case int64(Int64) case uint(UInt) case uint8(UInt8) case uint16(UInt16) case uint32(UInt32) case uint64(UInt64) case float(Float) case double(Double) case string(String) case array([Value]) case dictionary([String: Value]) } var userInfo: [CodingUserInfoKey: Any] public init(userInfo: [CodingUserInfoKey: Any] = [:]) { self.userInfo = userInfo } public func encode(_ value: T) throws -> Value { let encoder = Encoder(base: self, parent: nil, key: nil) return try encoder._encode(value, key: nil) } public static func encode(_ value: T) throws -> Value { let base = MinimalEncoder() return try base.encode(value) } } extension MinimalEncoder { struct _Key: CodingKey { var stringValue: String var intValue: Int? static let `super` = Self("super") init(_ string: String) { self.stringValue = string self.intValue = nil } init(_ index: Int) { self.stringValue = "Index \(index)" self.intValue = index } init?(stringValue: String) { self.init(stringValue) } init?(intValue: Int) { self.init(intValue) } } } extension MinimalEncoder.Value: CustomStringConvertible { func _description(prefix: String, indent: String) -> String { switch self { case .null: return ".null" case .bool(let v): return ".bool(\(v))" case .int(let v): return ".int(\(v))" case .int8(let v): return ".int8(\(v))" case .int16(let v): return ".int16(\(v))" case .int32(let v): return ".int32(\(v))" case .int64(let v): return ".int64(\(v))" case .uint(let v): return ".uint(\(v))" case .uint8(let v): return ".uint8(\(v))" case .uint16(let v): return ".uint16(\(v))" case .uint32(let v): return ".uint32(\(v))" case .uint64(let v): return ".uint64(\(v))" case .float(let v): return ".float(\(v))" case .double(let v): return ".double(\(v))" case .string(let v): return ".string(\(String(reflecting: v)))" case .array(let value): if value.count == 0 { return ".array([])" } var result = ".array([\n" for v in value { result += prefix result += indent result += v._description(prefix: prefix + indent, indent: indent) result += ",\n" } result += prefix + "])" return result case .dictionary(let value): if value.count == 0 { return ".dictionary([:])" } var result = ".dictionary([\n" for (k, v) in value.sorted(by: { $0.key < $1.key }) { result += prefix result += indent result += String(reflecting: k) result += ": " result += v._description(prefix: prefix + indent, indent: indent) result += ",\n" } result += prefix + "])" return result } } public var description: String { self._description(prefix: "", indent: " ") } } extension MinimalDecoder.Value { public var typeDescription: String { switch self { case .null: return ".null" case .bool(_): return ".bool" case .int(_): return ".int" case .int8(_): return ".int8" case .int16(_): return ".int16" case .int32(_): return ".int32" case .int64(_): return ".int64" case .uint(_): return ".uint" case .uint8(_): return ".uint8" case .uint16(_): return ".uint16" case .uint32(_): return ".uint32" case .uint64(_): return ".uint64" case .float(_): return ".float" case .double(_): return ".double" case .string(_): return ".string" case .array(_): return ".array" case .dictionary(_): return ".dictionary" } } } protocol _MinimalEncoderContainer: AnyObject { var base: MinimalEncoder { get } var parent: _MinimalEncoderContainer? { get } var key: CodingKey? { get } var isValid: Bool { get } func finalize() -> MinimalEncoder.Value? } extension _MinimalEncoderContainer { var codingPath: [CodingKey] { expectTrue(isValid, "Container isn't valid", trapping: true) var path: [CodingKey] = [] var encoder: _MinimalEncoderContainer? = self while let e = encoder { if let key = e.key { path.append(key) } encoder = e.parent } path.reverse() return path } func _encode(_ value: T, key: CodingKey?) throws -> MinimalEncoder.Value { expectTrue(isValid, "Container isn't valid", trapping: true) var encoder = MinimalEncoder.Encoder(base: base, parent: self, key: key) try value.encode(to: encoder) expectTrue(isKnownUniquelyReferenced(&encoder), "\(T.self).encode(to:) retained its encoder", trapping: true) guard let result = encoder.finalize() else { preconditionFailure("\(T.self).encode(to:) failed to encode anything") } return result } } extension MinimalEncoder { final class Encoder { unowned let base: MinimalEncoder unowned let parent: _MinimalEncoderContainer? let key: CodingKey? var container: _MinimalEncoderContainer? var isValid = true init(base: MinimalEncoder, parent: _MinimalEncoderContainer?, key: CodingKey?) { self.base = base self.parent = parent self.key = key } } } extension MinimalEncoder.Encoder: _MinimalEncoderContainer { func finalize() -> MinimalEncoder.Value? { expectTrue(isValid, "Container isn't valid", trapping: true) let value = container?.finalize() isValid = false return value } } extension MinimalEncoder.Encoder: Encoder { var userInfo: [CodingUserInfoKey: Any] { base.userInfo } func container( keyedBy type: Key.Type ) -> KeyedEncodingContainer { expectTrue(isValid, "Encoder isn't valid", trapping: true) expectNil(container, "Cannot extract multiple containers", trapping: true) let keyed = MinimalEncoder.KeyedContainer(base: base, parent: self, key: nil) self.container = keyed return .init(keyed) } func unkeyedContainer() -> UnkeyedEncodingContainer { expectTrue(isValid, "Encoder isn't valid", trapping: true) expectNil(container, "Cannot extract multiple containers", trapping: true) let unkeyed = MinimalEncoder.UnkeyedContainer(base: base, parent: self, key: nil) self.container = unkeyed return unkeyed } func singleValueContainer() -> SingleValueEncodingContainer { expectTrue(isValid, "Encoder isn't valid", trapping: true) expectNil(container, "Cannot extract multiple containers", trapping: true) let single = MinimalEncoder.SingleValueContainer(base: base, parent: self, key: nil) self.container = single return single } } extension MinimalEncoder { final class KeyedContainer { unowned let base: MinimalEncoder unowned let parent: _MinimalEncoderContainer? var key: CodingKey? var values: [String: Value] = [:] var isValid = true var pendingContainer: AnyObject? init(base: MinimalEncoder, parent: _MinimalEncoderContainer?, key: CodingKey?) { self.base = base self.parent = parent self.key = key } func commitPendingContainer() { expectTrue(self.isValid, "Container isn't valid", trapping: true) guard var c = pendingContainer else { return } self.pendingContainer = nil expectTrue(isKnownUniquelyReferenced(&c), "Container was retained", trapping: true) let container = c as! _MinimalEncoderContainer let key = container.key! expectNil(values[key.stringValue], "Key \(key.stringValue) has already been encoded", trapping: true) guard let v = container.finalize() else { preconditionFailure("Container remained empty")} values[key.stringValue] = v } } } extension MinimalEncoder.KeyedContainer: _MinimalEncoderContainer { func finalize() -> MinimalEncoder.Value? { expectTrue(isValid, "Container isn't valid", trapping: true) commitPendingContainer() isValid = false return .dictionary(values) } } extension MinimalEncoder.KeyedContainer: KeyedEncodingContainerProtocol { func encodeNil(forKey key: Key) throws { expectTrue(isValid, "Container isn't valid", trapping: true) commitPendingContainer() expectNil(values[key.stringValue], "Key \(key.stringValue) has already been encoded", trapping: true) values[key.stringValue] = .null } func encode(_ value: Bool, forKey key: Key) throws { expectTrue(isValid, "Container isn't valid", trapping: true) commitPendingContainer() expectNil(values[key.stringValue], "Key \(key.stringValue) has already been encoded", trapping: true) values[key.stringValue] = .bool(value) } func encode(_ value: Int, forKey key: Key) throws { expectTrue(isValid, "Container isn't valid", trapping: true) commitPendingContainer() expectNil(values[key.stringValue], "Key \(key.stringValue) has already been encoded", trapping: true) values[key.stringValue] = .int(value) } func encode(_ value: Int8, forKey key: Key) throws { expectTrue(isValid, "Container isn't valid", trapping: true) commitPendingContainer() expectNil(values[key.stringValue], "Key \(key.stringValue) has already been encoded", trapping: true) values[key.stringValue] = .int8(value) } func encode(_ value: Int16, forKey key: Key) throws { expectTrue(isValid, "Container isn't valid", trapping: true) commitPendingContainer() expectNil(values[key.stringValue], "Key \(key.stringValue) has already been encoded", trapping: true) values[key.stringValue] = .int16(value) } func encode(_ value: Int32, forKey key: Key) throws { expectTrue(isValid, "Container isn't valid", trapping: true) commitPendingContainer() expectNil(values[key.stringValue], "Key \(key.stringValue) has already been encoded", trapping: true) values[key.stringValue] = .int32(value) } func encode(_ value: Int64, forKey key: Key) throws { expectTrue(isValid, "Container isn't valid", trapping: true) commitPendingContainer() expectNil(values[key.stringValue], "Key \(key.stringValue) has already been encoded", trapping: true) values[key.stringValue] = .int64(value) } func encode(_ value: UInt, forKey key: Key) throws { expectTrue(isValid, "Container isn't valid", trapping: true) commitPendingContainer() expectNil(values[key.stringValue], "Key \(key.stringValue) has already been encoded", trapping: true) values[key.stringValue] = .uint(value) } func encode(_ value: UInt8, forKey key: Key) throws { expectTrue(isValid, "Container isn't valid", trapping: true) commitPendingContainer() expectNil(values[key.stringValue], "Key \(key.stringValue) has already been encoded", trapping: true) values[key.stringValue] = .uint8(value) } func encode(_ value: UInt16, forKey key: Key) throws { expectTrue(isValid, "Container isn't valid", trapping: true) commitPendingContainer() expectNil(values[key.stringValue], "Key \(key.stringValue) has already been encoded", trapping: true) values[key.stringValue] = .uint16(value) } func encode(_ value: UInt32, forKey key: Key) throws { expectTrue(isValid, "Container isn't valid", trapping: true) commitPendingContainer() expectNil(values[key.stringValue], "Key \(key.stringValue) has already been encoded", trapping: true) values[key.stringValue] = .uint32(value) } func encode(_ value: UInt64, forKey key: Key) throws { expectTrue(isValid, "Container isn't valid", trapping: true) commitPendingContainer() expectNil(values[key.stringValue], "Key \(key.stringValue) has already been encoded", trapping: true) values[key.stringValue] = .uint64(value) } func encode(_ value: Float, forKey key: Key) throws { expectTrue(isValid, "Container isn't valid", trapping: true) commitPendingContainer() expectNil(values[key.stringValue], "Key \(key.stringValue) has already been encoded", trapping: true) values[key.stringValue] = .float(value) } func encode(_ value: Double, forKey key: Key) throws { expectTrue(isValid, "Container isn't valid", trapping: true) commitPendingContainer() expectNil(values[key.stringValue], "Key \(key.stringValue) has already been encoded", trapping: true) values[key.stringValue] = .double(value) } func encode(_ value: String, forKey key: Key) throws { expectTrue(isValid, "Container isn't valid", trapping: true) commitPendingContainer() expectNil(values[key.stringValue], "Key \(key.stringValue) has already been encoded", trapping: true) values[key.stringValue] = .string(value) } func encode(_ value: T, forKey key: Key) throws { expectTrue(isValid, "Container isn't valid", trapping: true) commitPendingContainer() expectNil(values[key.stringValue], "Key \(key.stringValue) has already been encoded", trapping: true) values[key.stringValue] = try _encode(value, key: key) } func nestedContainer( keyedBy keyType: NestedKey.Type, forKey key: Key ) -> KeyedEncodingContainer { expectTrue(isValid, "Container isn't valid", trapping: true) commitPendingContainer() expectNil(values[key.stringValue], "Key \(key.stringValue) has already been encoded", trapping: true) let keyed = MinimalEncoder.KeyedContainer(base: base, parent: self, key: key) pendingContainer = keyed return .init(keyed) } func nestedUnkeyedContainer(forKey key: Key) -> UnkeyedEncodingContainer { expectTrue(isValid, "Container isn't valid", trapping: true) commitPendingContainer() expectNil(values[key.stringValue], "Key \(key.stringValue) has already been encoded", trapping: true) let unkeyed = MinimalEncoder.UnkeyedContainer(base: base, parent: self, key: key) pendingContainer = unkeyed return unkeyed } func superEncoder() -> Encoder { let key = MinimalEncoder._Key.super expectTrue(isValid, "Container isn't valid", trapping: true) commitPendingContainer() expectNil(values[key.stringValue], "Key \(key) has already been encoded", trapping: true) let sup = MinimalEncoder.Encoder(base: base, parent: self, key: key) pendingContainer = sup return sup } func superEncoder(forKey key: Key) -> Encoder { expectTrue(isValid, "Container isn't valid", trapping: true) commitPendingContainer() expectNil(values[key.stringValue], "Key \(key) has already been encoded", trapping: true) let sup = MinimalEncoder.Encoder(base: base, parent: self, key: key) pendingContainer = sup return sup } } extension MinimalEncoder { final class UnkeyedContainer { unowned let base: MinimalEncoder unowned let parent: _MinimalEncoderContainer? let key: CodingKey? var values: [Value] = [] var isValid = true var pendingContainer: AnyObject? init(base: MinimalEncoder, parent: _MinimalEncoderContainer?, key: CodingKey?) { self.base = base self.parent = parent self.key = key } func commitPendingContainer() { expectTrue(self.isValid, "Container isn't valid", trapping: true) guard var c = pendingContainer else { return } self.pendingContainer = nil expectTrue(isKnownUniquelyReferenced(&c), "Container was retained", trapping: true) let container = c as! _MinimalEncoderContainer guard let v = container.finalize() else { preconditionFailure("Container remained empty")} values.append(v) } } } extension MinimalEncoder.UnkeyedContainer: _MinimalEncoderContainer { func finalize() -> MinimalEncoder.Value? { expectTrue(isValid, "Container isn't valid", trapping: true) commitPendingContainer() isValid = false return .array(values) } } extension MinimalEncoder.UnkeyedContainer: UnkeyedEncodingContainer { var count: Int { values.count } func encodeNil() throws { expectTrue(isValid, "Container isn't valid", trapping: true) commitPendingContainer() values.append(.null) } func encode(_ value: Bool) throws { expectTrue(isValid, "Container isn't valid", trapping: true) commitPendingContainer() values.append(.bool(value)) } func encode(_ value: Int) throws { expectTrue(isValid, "Container isn't valid", trapping: true) commitPendingContainer() values.append(.int(value)) } func encode(_ value: Int8) throws { expectTrue(isValid, "Container isn't valid", trapping: true) commitPendingContainer() values.append(.int8(value)) } func encode(_ value: Int16) throws { expectTrue(isValid, "Container isn't valid", trapping: true) commitPendingContainer() values.append(.int16(value)) } func encode(_ value: Int32) throws { expectTrue(isValid, "Container isn't valid", trapping: true) commitPendingContainer() values.append(.int32(value)) } func encode(_ value: Int64) throws { expectTrue(isValid, "Container isn't valid", trapping: true) commitPendingContainer() values.append(.int64(value)) } func encode(_ value: UInt) throws { expectTrue(isValid, "Container isn't valid", trapping: true) commitPendingContainer() values.append(.uint(value)) } func encode(_ value: UInt8) throws { expectTrue(isValid, "Container isn't valid", trapping: true) commitPendingContainer() values.append(.uint8(value)) } func encode(_ value: UInt16) throws { expectTrue(isValid, "Container isn't valid", trapping: true) commitPendingContainer() values.append(.uint16(value)) } func encode(_ value: UInt32) throws { expectTrue(isValid, "Container isn't valid", trapping: true) commitPendingContainer() values.append(.uint32(value)) } func encode(_ value: UInt64) throws { expectTrue(isValid, "Container isn't valid", trapping: true) commitPendingContainer() values.append(.uint64(value)) } func encode(_ value: Float) throws { expectTrue(isValid, "Container isn't valid", trapping: true) commitPendingContainer() values.append(.float(value)) } func encode(_ value: Double) throws { expectTrue(isValid, "Container isn't valid", trapping: true) commitPendingContainer() values.append(.double(value)) } func encode(_ value: String) throws { expectTrue(isValid, "Container isn't valid", trapping: true) commitPendingContainer() values.append(.string(value)) } func encode(_ value: T) throws { expectTrue(isValid, "Container isn't valid", trapping: true) commitPendingContainer() values.append(try _encode(value, key: key)) } func nestedContainer( keyedBy keyType: NestedKey.Type ) -> KeyedEncodingContainer { expectTrue(isValid, "Container isn't valid", trapping: true) commitPendingContainer() let keyed = MinimalEncoder.KeyedContainer( base: base, parent: self, key: MinimalEncoder._Key(count)) pendingContainer = keyed return .init(keyed) } func nestedUnkeyedContainer() -> UnkeyedEncodingContainer { expectTrue(isValid, "Container isn't valid", trapping: true) commitPendingContainer() let unkeyed = MinimalEncoder.UnkeyedContainer(base: base, parent: self, key: MinimalEncoder._Key(count)) pendingContainer = unkeyed return unkeyed } func superEncoder() -> Encoder { expectTrue(isValid, "Container isn't valid", trapping: true) commitPendingContainer() let sup = MinimalEncoder.Encoder(base: base, parent: self, key: MinimalEncoder._Key(count)) pendingContainer = sup return sup } } extension MinimalEncoder { final class SingleValueContainer { unowned let base: MinimalEncoder unowned let parent: _MinimalEncoderContainer? let key: CodingKey? var value: Value? var isValid = true init(base: MinimalEncoder, parent: _MinimalEncoderContainer?, key: CodingKey?) { self.base = base self.parent = parent self.key = key } } } extension MinimalEncoder.SingleValueContainer: _MinimalEncoderContainer { func finalize() -> MinimalEncoder.Value? { expectTrue(isValid, "Container isn't valid", trapping: true) isValid = false return value } } extension MinimalEncoder.SingleValueContainer: SingleValueEncodingContainer { func encodeNil() throws { expectTrue(isValid, "Container isn't valid", trapping: true) expectNil(self.value, "Cannot encode multiple values into a single-value container", trapping: true) self.value = .null } func encode(_ value: Bool) throws { expectTrue(isValid, "Container isn't valid", trapping: true) expectNil(self.value, "Cannot encode multiple values into a single-value container", trapping: true) self.value = .bool(value) } func encode(_ value: Int) throws { expectTrue(isValid, "Container isn't valid", trapping: true) expectNil(self.value, "Cannot encode multiple values into a single-value container", trapping: true) self.value = .int(value) } func encode(_ value: Int8) throws { expectTrue(isValid, "Container isn't valid", trapping: true) expectNil(self.value, "Cannot encode multiple values into a single-value container", trapping: true) self.value = .int8(value) } func encode(_ value: Int16) throws { expectTrue(isValid, "Container isn't valid", trapping: true) expectNil(self.value, "Cannot encode multiple values into a single-value container", trapping: true) self.value = .int16(value) } func encode(_ value: Int32) throws { expectTrue(isValid, "Container isn't valid", trapping: true) expectNil(self.value, "Cannot encode multiple values into a single-value container", trapping: true) self.value = .int32(value) } func encode(_ value: Int64) throws { expectTrue(isValid, "Container isn't valid", trapping: true) expectNil(self.value, "Cannot encode multiple values into a single-value container", trapping: true) self.value = .int64(value) } func encode(_ value: UInt) throws { expectTrue(isValid, "Container isn't valid", trapping: true) expectNil(self.value, "Cannot encode multiple values into a single-value container", trapping: true) self.value = .uint(value) } func encode(_ value: UInt8) throws { expectTrue(isValid, "Container isn't valid", trapping: true) expectNil(self.value, "Cannot encode multiple values into a single-value container", trapping: true) self.value = .uint8(value) } func encode(_ value: UInt16) throws { expectTrue(isValid, "Container isn't valid", trapping: true) expectNil(self.value, "Cannot encode multiple values into a single-value container", trapping: true) self.value = .uint16(value) } func encode(_ value: UInt32) throws { expectTrue(isValid, "Container isn't valid", trapping: true) expectNil(self.value, "Cannot encode multiple values into a single-value container", trapping: true) self.value = .uint32(value) } func encode(_ value: UInt64) throws { expectTrue(isValid, "Container isn't valid", trapping: true) expectNil(self.value, "Cannot encode multiple values into a single-value container", trapping: true) self.value = .uint64(value) } func encode(_ value: Float) throws { expectTrue(isValid, "Container isn't valid", trapping: true) expectNil(self.value, "Cannot encode multiple values into a single-value container", trapping: true) self.value = .float(value) } func encode(_ value: Double) throws { expectTrue(isValid, "Container isn't valid", trapping: true) expectNil(self.value, "Cannot encode multiple values into a single-value container", trapping: true) self.value = .double(value) } func encode(_ value: String) throws { expectTrue(isValid, "Container isn't valid", trapping: true) expectNil(self.value, "Cannot encode multiple values into a single-value container", trapping: true) self.value = .string(value) } func encode(_ value: T) throws { expectTrue(isValid, "Container isn't valid", trapping: true) expectNil(self.value, "Cannot encode multiple values into a single-value container", trapping: true) self.value = try _encode(value, key: nil) } }