-
Notifications
You must be signed in to change notification settings - Fork 10.5k
Add a safe API for NSValue and migrate NSValue value fetching to the size variants for validation #22265
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
Conversation
@swift-ci please smoke test |
50e8501
to
cc88199
Compare
@swift-ci please smoke test |
extension NSValue { | ||
@_transparent // this must be transparent to pass the type encoding over to the _getObjCTypeEncoding function | ||
public func value<StoredType>(of type: StoredType.Type) -> StoredType? { | ||
guard strcmp(objCType, _getObjCTypeEncoding(StoredType.self)) == 0 else { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@transparent
doesn't guarantee this, but I'm also concerned about adding this check to NSValue when it wasn't there before. You can see #19691 for an example where this would cause a problem.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well there are some other monsters under the water here. Passing in String.self or Any.self crashes the compiler.
cc88199
to
bbcc7bd
Compare
@swift-ci please smoke test |
return nil | ||
} | ||
let allocated = UnsafeMutablePointer<StoredType>.allocate(capacity: 1) | ||
defer { allocated.deallocate() } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This feels potentially dangerous to me. Are we absolutely guaranteed that the read from this address to copy the value out will happen prior to the deallocation?
I don't have a better answer, this is just triggering alarms in my head. Who can we ask about this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Returning by value needs to make that copy so that it be returned; I'm not sure what sequence of events would cause the return out not to be filled before this defer is invoked.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yup, the deallocation happens at the end of the scope, after the assignment of the return value to send back from my understanding
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Confirming that this is guaranteed to do the right thing; Philippe is correct.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay, I figured as much. I just wanted to be absolutely certain.
@@ -23,3 +23,30 @@ ${ ObjectiveCBridgeableImplementationForNSValue("CGVector") } | |||
${ ObjectiveCBridgeableImplementationForNSValue("CGSize") } | |||
${ ObjectiveCBridgeableImplementationForNSValue("CGAffineTransform") } | |||
|
|||
extension NSValue { | |||
public func value<StoredType>(of type: StoredType.Type) -> StoredType? { | |||
if StoredType.self == AnyObject.self { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This case and the NSObject
case can just be coalesced into
if type is AnyObject.Type {
guard String(cString: objcType) == "@" else {
return nil
}
return self.nonretainedObjectValue as? StoredType
}
The metatype check should catch all reference types
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
but this would allow swift classes not rooted on NSObject right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, but couldn't you end up doing the same thing by storing something as an AnyObject
in the first place?
At least I guess we can coalesce the cases with if StoredType.self == AnyObject.self || StoredType.self is NSObject.Type
— it just looks like both implementations are exactly equal.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is being rooted in NSObject a requirement of something? The code already allows Swift classes not rooted in NSObject, and secretly all Swift objects on Darwin are rooted in NSObject anyway, IIRC.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(Feel free to correct me if not so.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess it is probably isomorphic anyhow on Darwin platforms, I can collapse it
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Swift objects on Darwin are not rooted in NSObject; they just conform to NSObjectProtocol.
I can't think of any problem passing a Swift class instance through an NSValue (as long as you keep it alive while doing so), but I might not have all the information.
getValue(allocated, size: storedSize) | ||
return allocated.pointee | ||
} | ||
return nil |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm a little concerned about returning nil
here — wouldn't it surprise someone to box up a
struct Foo {
let name: String
let value: Any
}
but not be able to unbox it safely this way?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is, but it is also fundamentally unsafe to do it, since String is not a POD.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you cant really safely box that in the first place tbh, that is something that should be addressed later
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, the crux of that example was actually meant to be the Any
and not the String
, but the same applies — it's not safe to box up a Foo
, but for someone who has already boxed up the Foo
, they can only get it out using the existing, less safe methods.
It would be nicer to be less restrictive on fetching values out and more restrictive on stuffing values in, but I guess we can do that in the future.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think it's practically possible for someone to have ended up with an already-NSValue-boxed Foo. The Swift runtime won't ever box it in a Foo
, and client code would have to go pretty far out of their way to manually copy the struct into an NSValue themself. (Nevermind that once they did, they'd have to deal with memory leaks and/or use-after-frees because NSValue can't copy or destroy a non-POD value when the object is copied or deallocated.)
bbcc7bd
to
25ccee6
Compare
@swift-ci please smoke test |
1 similar comment
@swift-ci please smoke test |
25ccee6
to
504c1b8
Compare
@swift-ci please smoke test |
504c1b8
to
23feeff
Compare
@swift-ci please smoke test |
23feeff
to
660203c
Compare
@swift-ci please smoke test |
660203c
to
f0d2f1f
Compare
@swift-ci please smoke test |
f0d2f1f
to
5586886
Compare
@swift-ci please smoke test |
5586886
to
48b71a6
Compare
@swift-ci please smoke test |
48b71a6
to
f10ce87
Compare
@swift-ci please smoke test |
…size variants for validation
f10ce87
to
6b438b5
Compare
@swift-ci please smoke test |
No description provided.