Преглед на файлове

Fix property attributes

`property_copyAttributeValue()` has a bug, and we had the same bug in our own code.

When the type encoding of a property has a comma in the name—such as a templated C++ object might, like std::string—property_copyAttributeValue does not copy the full type encoding out of the property attributes string. Instead it copies up to the first comma.

As an example, NSString has a private property `std::string stdString` and the type encoding for `std::string` has lots of commas in it. It's type encoding looks like this:

```
{basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >={__compressed_pair<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >::__rep, std::__1::allocator<char> >={__rep}}}
```

When you call property_copyAttributeValue() for this particular property, it thinks the type is just `{basic_string<char` and that's all you get back.

Looking into the source code of `iteratePropertyAttributes` in the objc runtime, I can see all it does is search for the next comma instead of attempting to parse out a type encoding as it should—or at least, look for a closing brace if the type is a struct.
Tanner Bennett преди 6 години
родител
ревизия
d506fee663

+ 3 - 0
Classes/Utility/Categories/NSDictionary+ObjcRuntime.h

@@ -8,6 +8,7 @@
 //
 
 #import <Foundation/Foundation.h>
+#import <objc/runtime.h>
 
 @interface NSDictionary (ObjcRuntime)
 
@@ -15,4 +16,6 @@
 /// Keys representing a boolean value should have a value of \c @YES instead of an empty string.
 - (NSString *)propertyAttributesString;
 
++ (instancetype)attributesDictionaryForProperty:(objc_property_t)property;
+
 @end

+ 18 - 0
Classes/Utility/Categories/NSDictionary+ObjcRuntime.m

@@ -86,4 +86,22 @@
     return attributes.copy;
 }
 
++ (instancetype)attributesDictionaryForProperty:(objc_property_t)property {
+    NSMutableDictionary *attrs = [NSMutableDictionary new];
+
+    for (NSString *key in [FLEXRuntimeUtility allPropertyAttributeKeys]) {
+        char *value = property_copyAttributeValue(property, key.UTF8String);
+        if (value) {
+            attrs[key] = [[NSString alloc]
+                initWithBytesNoCopy:value
+                length:strlen(value)
+                encoding:NSUTF8StringEncoding
+                freeWhenDone:YES
+            ];
+        }
+    }
+
+    return attrs.copy;
+}
+
 @end

+ 3 - 0
Classes/Utility/Categories/NSString+ObjcRuntime.h

@@ -15,6 +15,9 @@
 /// Values are either a string or \c @YES. Boolean attributes which are false will not be
 /// present in the dictionary. See this link on how to construct a proper attributes string:
 /// https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtPropertyIntrospection.html
+///
+/// Note: this method doesn't work properly for certain type encodings, and neither does
+/// the property_copyAttributeValue function in the runtime itself. Radar: FB7499230
 - (NSDictionary *)propertyAttributes;
 
 @end

+ 1 - 0
Classes/Utility/Categories/NSString+ObjcRuntime.m

@@ -30,6 +30,7 @@
         FLEXPropertyAttribute c = (FLEXPropertyAttribute)[attribute characterAtIndex:0];
         switch (c) {
             case FLEXPropertyAttributeTypeEncoding:
+                // Note: the type encoding here is not always correct. Radar: FB7499230
                 attributes[kFLEXPropertyAttributeKeyTypeEncoding] = [attribute stringbyDeletingCharacterAtIndex:0];
                 break;
             case FLEXPropertyAttributeBackingIvarName:

+ 0 - 2
Classes/Utility/Runtime/FLEXPropertyAttributes.h

@@ -29,8 +29,6 @@ NS_ASSUME_NONNULL_BEGIN
 }
 
 + (instancetype)attributesForProperty:(objc_property_t)property;
-/// @warning Raises an exception if \e attributes is invalid or \c nil.
-+ (instancetype)attributesFromString:(NSString *)attributes;
 /// @warning Raises an exception if \e attributes is invalid, \c nil, or contains unsupported keys.
 + (instancetype)attributesFromDictionary:(NSDictionary *)attributes;
 

+ 24 - 39
Classes/Utility/Runtime/FLEXPropertyAttributes.m

@@ -38,47 +38,32 @@
 #pragma mark Initializers
 
 + (instancetype)attributesForProperty:(objc_property_t)property {
-    return [self attributesFromString:@(property_getAttributes(property))];
+    return [self attributesFromDictionary:[NSDictionary attributesDictionaryForProperty:property]];
 }
 
 + (instancetype)attributesFromDictionary:(NSDictionary *)attributes {
-    NSString *attrs = attributes.propertyAttributesString;
-    if (!attrs) {
-        [NSException raise:NSInternalInconsistencyException
-                    format:@"Invalid property attributes dictionary: %@", attributes];
-    }
-    return [self attributesFromString:attrs];
-}
-
-+ (instancetype)attributesFromString:(NSString *)attributes {
-    return [[self alloc] initWithstring:attributes];
+    return [[self alloc] initWithAttributesDictionary:attributes];
 }
 
-- (id)initWithstring:(NSString *)string {
-    NSParameterAssert(string);
+- (id)initWithAttributesDictionary:(NSDictionary *)attributes {
+    NSParameterAssert(attributes);
     
     self = [super init];
     if (self) {
-        _string = string;
-        
-        _dictionary = string.propertyAttributes;
-        if (!_dictionary) {
-            [NSException raise:NSInternalInconsistencyException
-                        format:@"Invalid property attributes string: %@", string];
-        }
-        
-        _count                = _dictionary.count;
-        _typeEncoding         = _dictionary[kFLEXPropertyAttributeKeyTypeEncoding];
-        _backingIvar          = _dictionary[kFLEXPropertyAttributeKeyBackingIvarName];
-        _oldTypeEncoding      = _dictionary[kFLEXPropertyAttributeKeyOldStyleTypeEncoding];
-        _customGetter         = NSSelectorFromString(_dictionary[kFLEXPropertyAttributeKeyCustomGetter]);
-        _customSetter         = NSSelectorFromString(_dictionary[kFLEXPropertyAttributeKeyCustomSetter]);
-        _isReadOnly           = [_dictionary[kFLEXPropertyAttributeKeyReadOnly] boolValue];
-        _isCopy               = [_dictionary[kFLEXPropertyAttributeKeyCopy] boolValue];
-        _isRetained           = [_dictionary[kFLEXPropertyAttributeKeyRetain] boolValue];
-        _isNonatomic          = [_dictionary[kFLEXPropertyAttributeKeyNonAtomic] boolValue];
-        _isWeak               = [_dictionary[kFLEXPropertyAttributeKeyWeak] boolValue];
-        _isGarbageCollectable = [_dictionary[kFLEXPropertyAttributeKeyGarbageCollectable] boolValue];
+        _dictionary           = attributes;
+        _string               = attributes.propertyAttributesString;
+        _count                = attributes.count;
+        _typeEncoding         = attributes[kFLEXPropertyAttributeKeyTypeEncoding];
+        _backingIvar          = attributes[kFLEXPropertyAttributeKeyBackingIvarName];
+        _oldTypeEncoding      = attributes[kFLEXPropertyAttributeKeyOldStyleTypeEncoding];
+        _customGetter         = NSSelectorFromString(attributes[kFLEXPropertyAttributeKeyCustomGetter]);
+        _customSetter         = NSSelectorFromString(attributes[kFLEXPropertyAttributeKeyCustomSetter]);
+        _isReadOnly           = attributes[kFLEXPropertyAttributeKeyReadOnly] != nil;
+        _isCopy               = attributes[kFLEXPropertyAttributeKeyCopy] != nil;
+        _isRetained           = attributes[kFLEXPropertyAttributeKeyRetain] != nil;
+        _isNonatomic          = attributes[kFLEXPropertyAttributeKeyNonAtomic] != nil;
+        _isWeak               = attributes[kFLEXPropertyAttributeKeyWeak] != nil;
+        _isGarbageCollectable = attributes[kFLEXPropertyAttributeKeyGarbageCollectable] != nil;
     }
     
     return self;
@@ -88,14 +73,14 @@
 
 - (NSString *)description {
     return [NSString
-        stringWithFormat:@"<%@ ivar=%@, readonly=%d, nonatomic=%d, getter=%@, setter=%@>\n%@",
+        stringWithFormat:@"<%@ \"%@\", ivar=%@, readonly=%d, nonatomic=%d, getter=%@, setter=%@>",
         NSStringFromClass(self.class),
+        self.string,
         self.backingIvar ?: @"none",
         self.isReadOnly,
         self.isNonatomic,
-        NSStringFromSelector(self.customGetter) ?: @" ",
-        NSStringFromSelector(self.customSetter) ?: @" ",
-        self.string
+        NSStringFromSelector(self.customGetter) ?: @"none",
+        NSStringFromSelector(self.customSetter) ?: @"none"
     ];
 }
 
@@ -211,11 +196,11 @@
 #pragma mark Copying
 
 - (id)copyWithZone:(NSZone *)zone {
-    return [[FLEXPropertyAttributes class] attributesFromString:self.string];
+    return [[FLEXPropertyAttributes class] attributesFromDictionary:self.dictionary];
 }
 
 - (id)mutableCopyWithZone:(NSZone *)zone {
-    return [[FLEXMutablePropertyAttributes class] attributesFromString:self.string];
+    return [[FLEXMutablePropertyAttributes class] attributesFromDictionary:self.dictionary];
 }
 
 @end

+ 1 - 0
Classes/Utility/Runtime/FLEXRuntimeUtility.h

@@ -104,6 +104,7 @@ typedef NS_ENUM(char, FLEXTypeEncoding)
 + (void)tryAddPropertyWithName:(const char *)name
                     attributes:(NSDictionary<NSString *, NSString *> *)attributePairs
                        toClass:(__unsafe_unretained Class)theClass;
++ (NSArray<NSString *> *)allPropertyAttributeKeys;
 
 // Method Helpers
 + (NSArray *)prettyArgumentComponentsForMethod:(Method)method;

+ 23 - 0
Classes/Utility/Runtime/FLEXRuntimeUtility.m

@@ -213,10 +213,33 @@ const unsigned int kFLEXNumberOfImplicitArgs = 2;
     }
 }
 
++ (NSArray<NSString *> *)allPropertyAttributeKeys
 {
+    static NSArray<NSString *> *allPropertyAttributeKeys = nil;
+    static dispatch_once_t onceToken;
+    dispatch_once(&onceToken, ^{
+        allPropertyAttributeKeys = @[
+            kFLEXPropertyAttributeKeyTypeEncoding,
+            kFLEXPropertyAttributeKeyBackingIvarName,
+            kFLEXPropertyAttributeKeyReadOnly,
+            kFLEXPropertyAttributeKeyCopy,
+            kFLEXPropertyAttributeKeyRetain,
+            kFLEXPropertyAttributeKeyNonAtomic,
+            kFLEXPropertyAttributeKeyCustomGetter,
+            kFLEXPropertyAttributeKeyCustomSetter,
+            kFLEXPropertyAttributeKeyDynamic,
+            kFLEXPropertyAttributeKeyWeak,
+            kFLEXPropertyAttributeKeyGarbageCollectable,
+            kFLEXPropertyAttributeKeyOldStyleTypeEncoding,
+        ];
+    });
 
+    return allPropertyAttributeKeys;
 }
 
+
+#pragma mark - Method Helpers (Public)
+
 + (NSArray<NSString *> *)prettyArgumentComponentsForMethod:(Method)method
 {
     NSMutableArray<NSString *> *components = [NSMutableArray array];