Skip to content

Use CFNumberCreate() as factory initializer on NSNumber #1093

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

Conversation

norio-nomura
Copy link
Contributor

@norio-nomura norio-nomura commented Jul 2, 2017

This replaces #1025 by further utilizing the Factory Initializer Method (SR-5255).


Use CFNumberCreate() as factory initializer on NSNumber.init(value:)

  • Change NSNumber.init(value:) to returning instance created by CFNumberCreate()
  • Remove NSNumber._objCType

Change the properties of NSNumber to call CFNumberGetValue () in the same way as Foundation.

Fixes SR-4907

  • Add TestNSNumber.test_objCType() reproducing crash
    Expected results are sampled from macOS
  • Fix crash on accessing objCType property of NSNumber

Change NSNumber.objCType to returning same values with darwin platform version

  • Change NSNumber.objCType to returning same values with darwin
  • Change NSNumber.init(value: Bool) to returning __NSCFBoolean by using _Factory protocol

Since NSNumber.objCType returns c with both of Bool and Int8 on darwin platform, NSNumber.init(value: Bool) is changed to returns __NSCFBoolean by using _Factory protocol, and it changed to use _cfTypeID to distinguish between Bool and Int8.

  • Change JSONSerialization to use _cfTypeID for checking whether NSNumber is Boolean or not
  • Change _JSONDecoder to use _cfTypeID for checking whether NSNumber is Boolean or not
  • Change NSNumber.compare(_:) to use objCType
  • Add missing entry to TestNumber.allTests
  • Fix NSKeyedUnarchiver.decodeBool(forKey:) and add test to TestNSPredicate.test_NSCoding()

@@ -198,14 +198,20 @@ extension NSNumber : ExpressibleByFloatLiteral, ExpressibleByIntegerLiteral, Exp

}

@_silgen_name("_CFNumberGetType2")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We really should not use silgen_name. Instead can you just put this in ForSwiftFoundationOnly.h?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the difference between ForSwiftFoundationOnly.h and ForFoundationOnly.h?
It seems _CFNumberInit*() functions are placed in ForFoundationOnly.h.
Should _CFNumberGetType2 also be placed in ForFoundationOnly.h?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ForFoundationOnly.h is used in both the CoreFoundation shipped on Darwin as well as swift-corlibs-foundation, whereas ForSwiftFoundationOnly.h is used for swift-corelibs-foundation exclusively.

Putting it in ForSwitFoundationOnly.h means that I can merge the closed source version and the open source version easier ;)

var high: Int64
var low: UInt64
}

open class NSNumber : NSValue {
typealias CFType = CFNumber
// This layout MUST be the same as CFNumber so that they are bridgeable
private var _base = _CFInfo(typeID: CFNumberGetTypeID())
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we are using factories for all numbers then we can delete this!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These properties are necessary to maintain memory layout compatibility of NSDecimalNumber.

@@ -78,7 +78,10 @@ internal class __NSCFBoolean : NSNumber {
override var objCType: UnsafePointer<Int8> {
// This must never be fixed to be "B", although that would
// cause correct old-style archiving when this is unarchived.
return UnsafePointer<Int8>(bitPattern: UInt(_NSSimpleObjCType.Char.rawValue.value))!
func _objCType(_ staticString: StaticString) -> UnsafePointer<Int8> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like this trick better than the way I was thinking, however I wonder if we have a guarantee this is safe?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wouldn't the static string need to be a static property to guarantee its lifetime?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not fully certain which way this falls, constant strings should emit static references in the binary so it is probably safe-ish?

We have a few alternatives; we could thunk out to C and use the real @encode since we always have clang available. We could thunk out to C and reference C strings as opaque memory blobs. This approach. Or the other thought I had was something like this https://gist.github.com/phausler/2630092f1b391d6a5db0a562288514bf

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was thinking along the same lines, but simpler:

fileprivate let boolEncoding: StaticString = "B"
fileprivate let boolEncodingPtr: UnsafePointer<Int8> = {
  precondition(boolEncoding.isASCII)
  return UnsafeRawPointer(boolEncoding.utf8Start).assumingMemoryBound(to: Int8.self)
}()

// etc.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yea, I think there is also value in abstracting it out to an isolated file to do that work in and provide a shared internal (minimal) version of @encode

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I discarded following codes that rewrote _NSSimpleObjCType in NSObjCRuntime.swift, since memory layout is not compatible with current implementation.
https://gist.github.com/norio-nomura/a1383f258c38bb1e081597a538f46e8f

Should we create another static values for @encode?
I feel we should do re-writing _NSSimpleObjCType instead of adding them, and doing that with this PR will make it too large.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As I tested, utf8Start points directly cstring in __TEXT,__cstring,cstring_literals whether StaticString is instantiated as static variable or function parameter.

return UnsafeRawPointer(staticString.utf8Start).assumingMemoryBound(to: Int8.self)
}
let numberType = _CFNumberGetType2(_cfObject)
switch numberType {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Iirc gettype2 will only emit SInt* and Float* types

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't know. It looks __CFNumberTypeTable.canonicalType contains only those types. 👍

@phausler
Copy link
Contributor

phausler commented Jul 2, 2017

The sil_gen part is the only absolute thing that is required

@norio-nomura
Copy link
Contributor Author

norio-nomura commented Jul 2, 2017

Can we remove _CFNumberInit*() which is no longer used?

@phausler
Copy link
Contributor

phausler commented Jul 3, 2017

lets hold off on removing _CFNumberInit just yet, I need to verify we are not using that in the objc version (which I was considering)

@norio-nomura
Copy link
Contributor Author

Ah, sorry, it seems I have to fix NSDecimalNumber as well.
It will take some time yet.

@norio-nomura norio-nomura changed the title Use CFNumberCreate() as factory initializer on NSNumber [WIP] Use CFNumberCreate() as factory initializer on NSNumber Jul 3, 2017
@norio-nomura
Copy link
Contributor Author

Ah, sorry, it seems I have to fix NSDecimalNumber as well.

Removing stored instance properties caused issues on NSDecimalNumber. I reverted that.

@norio-nomura norio-nomura changed the title [WIP] Use CFNumberCreate() as factory initializer on NSNumber Use CFNumberCreate() as factory initializer on NSNumber Jul 3, 2017
@phausler
Copy link
Contributor

phausler commented Jul 3, 2017

I can follow up with the removals of _CFNumberInit* and the ivars.

@phausler
Copy link
Contributor

phausler commented Jul 3, 2017

@swift-ci please test and merge

@swift-ci swift-ci merged commit 6d107c1 into swiftlang:master Jul 3, 2017
@norio-nomura norio-nomura deleted the use-factory-initializer-on-nsnumber branch July 3, 2017 22:57
@norio-nomura
Copy link
Contributor Author

Thanks! 🙏

@norio-nomura
Copy link
Contributor Author

I forgot that I have to ask a question.

The results of test are different between arch(arm64) and arch(arm).
On arch(arm64) (iPhone SE w/ iOS 10.3.3):

sInt8Type: c
sInt16Type: s
sInt32Type: i
sInt64Type: q
float32Type: f
float64Type: d
charType: c
shortType: s
intType: i
longType: q
longLongType: q
floatType: f
doubleType: d
cfIndexType: q
nsIntegerType: q
cgFloatType: d
sInt128Type: Q

On arch(arm) (iPad 3rd w/ iOS 9.3.5):

sInt8Type: i
sInt16Type: i
sInt32Type: i
sInt64Type: q
float32Type: f
float64Type: d
charType: i
shortType: i
intType: i
longType: i
longLongType: q
floatType: f
doubleType: d
cfIndexType: i
nsIntegerType: i
cgFloatType: f
sInt128Type: Q

Is not this a problem?

@phausler
Copy link
Contributor

phausler commented Jul 4, 2017

For now that is probably fine, my changes will hopefully bring our strange conditionals per 64 vs 32 bit in more pairity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants