Skip to content
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

[SR-13784] Exception during Measurement serialization with a Unit that uses custom UnitConverter #3966

Open
swift-ci opened this issue Oct 27, 2020 · 2 comments

Comments

@swift-ci
Copy link
Contributor

Previous ID SR-13784
Radar rdar://problem/70730985
Original Reporter Rostyslav_D (JIRA User)
Type Bug
Environment

Swift 5.3 (swiftlang-1200.0.29.2 clang-1200.0.30.1)
Xcode 12.0 GM

Additional Detail from JIRA
Votes 0
Component/s Foundation, Standard Library
Labels Bug
Assignee None
Priority Medium

md5: 39638118a3a39937e75933935b5cef3d

Issue Description:

In my project I extended a UnitAngle with a custom unit that relies on UnitConverter subclass:

extension UnitAngle {
   static let cosine = UnitAngle(symbol: "cosφ", converter: CosineUnitConverter())
}

class CosineUnitConverter: UnitConverter, NSSecureCoding, Codable {
    static var supportsSecureCoding: Bool { true }
    required convenience init?(coder: NSCoder) {
         self.init()
    }
    func encode(with coder: NSCoder) { }
    override func baseUnitValue(fromValue value: Double) -> Double {
         let rad = acos(value)
         return rad / .pi * 180
    }
    override func value(fromBaseUnitValue baseUnitValue: Double) -> Double {
         let rad = baseUnitValue * .pi / 180
         return cos(rad)
    }
}

In that converter I added conformance to NSSecureCoding similarly as UnitConverterLinear does.
During attempt to serialize such a measurement with NSKeyedArchiver I get a runtime exception EXC_BAD_INSTRUCTION:

let archiver = NSKeyedArchiver(requiringSecureCoding: false)
let measurement = Measurement<UnitAngle>(value: 1, unit: .cosine)
try archiver.encodeEncodable(measurement, forKey: NSKeyedArchiveRootObjectKey)
@typesanitizer
Copy link

@swift-ci create

@swift-ci
Copy link
Contributor Author

Comment by Rostyslav Dovhaliuk (JIRA)

Upon further investigation I noticed that Swift implementation of Measurement struct explicitly checks for converter class and if it's not UnitConverterLinear, it triggers a precondition failure. Considering that almost all types of Measurements and Units framework conform to NSSecureCoding protocol maybe it is reasonable to add such a conformance to UnitConverter base class and then encode and decode it with NSKeyed(Un)archiver like this:

@available(macOS 10.12, iOS 10.0, watchOS 3.0, tvOS 10.0, *)
extension Measurement : Codable {
    private enum CodingKeys : Int, CodingKey {
        case value
        case unit
    }

    private enum UnitCodingKeys : Int, CodingKey {
        case symbol
        case converter
    }

    public init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        let value = try container.decode(Double.self, forKey: .value)

        let unitContainer = try container.nestedContainer(keyedBy: UnitCodingKeys.self, forKey: .unit)
        let symbol = try unitContainer.decode(String.self, forKey: .symbol)

        let unit: UnitType
        if UnitType.self is Dimension.Type {
            let converterData = try unitContainer.decode(Data.self, forKey: .converter)
            let converter = try NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(converterData) as? UnitConverter
            let unitMetaType = (UnitType.self as! Dimension.Type)
            if let converter = converter {
                unit = (unitMetaType.init(symbol: symbol, converter: converter) as! UnitType)
            } else {
                unit = UnitType(symbol: symbol)
            }
        } else {
            unit = UnitType(symbol: symbol)
        }

        self.init(value: value, unit: unit)
    }

    public func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(self.value, forKey: .value)

        var unitContainer = container.nestedContainer(keyedBy: UnitCodingKeys.self, forKey: .unit)
        try unitContainer.encode(self.unit.symbol, forKey: .symbol)

        if UnitType.self is Dimension.Type {
            let converter = (self.unit as! Dimension).converter
            let converterData = try NSKeyedArchiver.archivedData(withRootObject: converter, requiringSecureCoding: false)
            try unitContainer.encode(converterData, forKey: .converter)
        }
    }
}

Any thoughts about such a solution?

@swift-ci swift-ci transferred this issue from apple/swift-issues Apr 25, 2022
@shahmishal shahmishal transferred this issue from apple/swift May 5, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants