swiftlang / swift-corelibs-foundation

The Foundation Project, providing core utilities, internationalization, and OS independence
swift.org
Apache License 2.0
5.28k stars 1.13k forks source link

[SR-6731] PropertyListDecoder unable to decode __NSCFType #3748

Open krzyzanowskim opened 6 years ago

krzyzanowskim commented 6 years ago
Previous ID SR-6731
Radar None
Original Reporter @krzyzanowskim
Type Bug
Additional Detail from JIRA | | | |------------------|-----------------| |Votes | 0 | |Component/s | Foundation | |Labels | Bug | |Assignee | None | |Priority | Medium | md5: 243c24973c4c93e917da850b1907c06d

Issue Description:

The plist file can store NSObject aka AnyObject aka Any aka CFTypeRef aka NSCFType and SwiftFoundation NSKeyedArchiver can read those.

The PropertyListSerialization is able to handle that too. YET, there is no way to decode this value using Decodable and PropertyListDecoder.

error is: eg. "Expected to decode Data but found __NSCFType instead."

I wonder if is it possible to (any)

  1. bitcast those to Any

  2. treat as raw bytes (return Data)

anything is better than throw error.

belkadan commented 6 years ago

cc @phausler

phausler commented 6 years ago

PropertyListSerialization cannot handle Any. __NSCFTypes should always be non bridged CF object refs.

Only the following types are permissible to PropertyListSerialization (note this is different than PropertyListDecoder btw).

https://developer.apple.com/library/content/documentation/General/Conceptual/DevPedia-CocoaCore/PropertyList.html

Array, Dictionary, String, Data, Date, Int8, Int16, Int32, Int64, UInt8, UInt16, UInt32, UInt64, Int, UInt, Float, Double, Bool, NSArray, NSDictionary, NSString, NSData, NSDate, NSNumber, CFArray, CFDictionary, CFString, CFData, CFDate, CFNumber, and CFBoolean should be the only types that can exist in a property list. (it is worth noting that any of the collection types, ala Array or NSArray MUST ONLY contain property list types and Dictionaries and NSDictionaries MUST ONLY contain String/NSString/CFString keys and MUST ONLY contain property list values)

Now that all being said: it is quite possible via the Codable protocol to marshal any structured type into a decomposition that is represented by those types.

phausler commented 6 years ago

Perhaps it might be useful for a code-example of how you are getting this error.

pbk20191 commented 9 months ago

This happens by the CFKeyedArchiverUID which represent the reference inside object graph.

minimum error throwing code is like below

    let somethingCloseToUid = ["CF$UID": 1]
    let encoder = PropertyListEncoder()
    /// Decoding error is thrown when outputformat is xml
    encoder.outputFormat = .xml
    let uidData = try encoder.encode(somethingCloseToUid)
    ///Expected to decode Dictionary<String, Any> but found __NSCFType instead. (Darwin)
    ///Expected to decode Dictionary<String, Any> but found _NSKeyedArchiverUID instead. (linux)
    let dictionary = try PropertyListDecoder().decode([String:Int].self, from: uidData)

CFKeyedArchiverUID is created by NSCoder and can be find by below

class DummyBase: NSObject, NSSecureCoding {

    class var supportsSecureCoding: Bool { true }

    func encode(with coder: NSCoder) {

    }
    override init() {
        super.init()
    }

    required init?(coder: NSCoder) {
        super.init()
    }
}

func createdByNSCoding() throws {
    let nscodingData = try NSKeyedArchiver.archivedData(withRootObject: DummyBase(), requiringSecureCoding: true)
    let plist = try PropertyListSerialization.propertyList(from: nscodingData, format: nil)
    print(plist)
    if let dictionary = plist as? [String:Any], let top = dictionary["$top"] as? NSDictionary, let root = top["root"] as? AnyObject {

        #if canImport(Darwin)
        let typeID = CFGetTypeID(root)
        /// CFKeyedArchiverUID  __NSCFType
        print(CFCopyTypeIDDescription(typeID) as String, type(of: root))
        #else
        print(root)
        #endif
        let data = try PropertyListSerialization.data(fromPropertyList: root, format: .xml, options: 0)
        /*
         <?xml version="1.0" encoding="UTF-8"?>
         <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
         <plist version="1.0">
         <dict>
             <key>CF$UID</key>
             <integer>1</integer>
         </dict>
         </plist>
         */
        let xmlString = String(decoding: data, as: UTF8.self)

    }
}