Browse Source

Update parser: NSMethodSignature doesn't support unions

Tanner Bennett 6 years ago
parent
commit
7ddb46ff9f

+ 87 - 51
Classes/Utility/Runtime/FLEXTypeEncodingParser.m

@@ -73,7 +73,8 @@ BOOL FLEXGetSizeAndAlignment(const char *type, NSUInteger *sizep, NSUInteger *al
 + (BOOL)methodTypeEncodingSupported:(NSString *)typeEncoding cleaned:(NSString * __autoreleasing *)cleanedEncoding {
     FLEXTypeEncodingParser *parser = [[self alloc] initWithObjCTypes:typeEncoding];
     while (!parser.scan.isAtEnd) {
-        if ([parser scanAndGetSizeAndAlignForNextType:nil] == -1) {
+        BOOL containsUnion = NO;
+        if ([parser parseNextType:nil hasUnion:&containsUnion] == -1 || containsUnion) {
             return NO;
         }
     }
@@ -108,8 +109,16 @@ BOOL FLEXGetSizeAndAlignment(const char *type, NSUInteger *sizep, NSUInteger *al
 }
 
 + (ssize_t)sizeForTypeEncoding:(NSString *)type alignment:(ssize_t *)alignOut unaligned:(BOOL)unaligned {
+    return [self parseType:type alignment:alignOut unaligned:unaligned hasUnion:nil];
+}
+
+/// @return the size of the type
++ (ssize_t)parseType:(NSString *)type
+           alignment:(ssize_t *)alignOut
+           unaligned:(BOOL)unaligned
+            hasUnion:(BOOL *)containsUnion {
     ssize_t align = 0;
-    ssize_t size = [[[self alloc] initWithObjCTypes:type] scanAndGetSizeAndAlignForNextType:&align];
+    ssize_t size = [[[self alloc] initWithObjCTypes:type] parseNextType:&align hasUnion:containsUnion];
 
     if (size == -1 || size == 0) {
         return size;
@@ -175,7 +184,7 @@ BOOL FLEXGetSizeAndAlignment(const char *type, NSUInteger *sizep, NSUInteger *al
     return prefix;
 }
 
-/// Size in BYTES
+/// @return the size in bytes
 - (ssize_t)sizeForType:(FLEXTypeEncoding)type {
     switch (type) {
         case FLEXTypeEncodingChar: return sizeof(char);
@@ -206,8 +215,10 @@ BOOL FLEXGetSizeAndAlignment(const char *type, NSUInteger *sizep, NSUInteger *al
     }
 }
 
-/// Size in bytes
-- (ssize_t)scanAndGetSizeAndAlignForNextType:(ssize_t *)alignment {
+/// @param containsUnion an out param indicating whether the given type contains
+/// a union, exlcuding pointers to/with unions. Must be initialized to \c NO
+/// @return the (unaligned) size of the type in bytes
+- (ssize_t)parseNextType:(ssize_t *)alignment hasUnion:(BOOL *)containsUnion {
     NSUInteger start = self.scan.scanLocation;
 
     // Check for void first
@@ -231,9 +242,18 @@ BOOL FLEXGetSizeAndAlignment(const char *type, NSUInteger *sizep, NSUInteger *al
                 substringWithRange:NSMakeRange(pointerTypeStart, pointerTypeLength)
             ];
             
-            // Is it supported?
-            if ([self.class sizeForTypeEncoding:pointerType alignment:nil] == -1) {
-                // No, so clean it
+            BOOL pointeeHasUnion = NO;
+            BOOL unsupported = -1 == [self.class
+                parseType:pointerType
+                alignment:nil
+                unaligned:NO
+                hasUnion:&pointeeHasUnion
+            ];
+            // Clean the type if it is unsupported or contains a union
+            // (Unions are supported by NSGetSizeAndAlignment but not
+            // supported by NSMethodSignature for some reason)
+            if (unsupported || pointeeHasUnion) {
+                // We don't set *containsUnion here since we can clean it out
                 NSString *cleaned = [self cleanPointeeTypeAtLocation:pointerTypeStart];
                 [self.cleaned replaceCharactersInRange:NSMakeRange(
                     pointerTypeStart - self.cleanedReplacingOffset, pointerTypeLength
@@ -281,6 +301,11 @@ BOOL FLEXGetSizeAndAlignment(const char *type, NSUInteger *sizep, NSUInteger *al
     }
     
     if (didScanSUA) {
+        // We found a union, set this flag
+        if (isUnion && containsUnion) {
+            *containsUnion = YES;
+        }
+        
         NSUInteger backup = self.scan.scanLocation;
 
         // Ensure we have a closing tag
@@ -332,7 +357,7 @@ BOOL FLEXGetSizeAndAlignment(const char *type, NSUInteger *sizep, NSUInteger *al
             }
 
             ssize_t align = 0;
-            ssize_t size = [self scanAndGetSizeAndAlignForNextType:&align];
+            ssize_t size = [self parseNextType:&align hasUnion:containsUnion];
             if (size == -1) {
                 // The above call is the only time in this method where
                 // cleaned might be mutated recursively, so this is the
@@ -725,61 +750,72 @@ BOOL FLEXGetSizeAndAlignment(const char *type, NSUInteger *sizep, NSUInteger *al
 
     // Scan optional const
     [self scanChar:FLEXTypeEncodingConst];
-
-    // Check for pointer, then scan next
-    if ([self scanChar:FLEXTypeEncodingPointer]) {
-        // Recurse to scan something else
-        return [self cleanPointeeTypeAtLocation:self.scan.scanLocation];
-    }
     
-    // All arrays are supported, scan past them
-    if ([self scanPair:FLEXTypeEncodingArrayBegin close:FLEXTypeEncodingArrayEnd]) {
-        return typeIsClean();
-    }
-
-    // Check for struct/union
-    if ([self canScanChar:FLEXTypeEncodingStructBegin] || [self canScanChar:FLEXTypeEncodingUnionBegin]) {
-        if ([self scanAndGetSizeAndAlignForNextType:nil] != -1) {
-            return typeIsClean();
-        }
-        
-        // The structure we just tried to scan is unsupported, so just return its name
-        // if it has one. If not, just return a question mark.
-        BOOL isStruct = [self scanChar:FLEXTypeEncodingStructBegin];
-        BOOL isUnion = [self scanChar:FLEXTypeEncodingUnionBegin];
-        assert(isStruct || isUnion);
-        
-        NSString *open = isStruct ? @"{" : @"(";
-        NSString *close = isStruct ? @"}" : @")";
-        char closec = isStruct ? FLEXTypeEncodingStructEnd : FLEXTypeEncodingUnionEnd;
-        
-        NSString *name = [self extractTypeNameFromScanLocation];
-        if (name) {
-            // Got the name, scan past the closing token
-            [self.scan scanUpToString:close intoString:nil];
-            if (![self scanChar:closec]) {
-                // Missing struct/union close token
+    char next = self.nextChar;
+    switch (next) {
+        case FLEXTypeEncodingPointer:
+            // Recurse to scan something else
+            [self scanChar:next];
+            return [self cleanPointeeTypeAtLocation:self.scan.scanLocation];
+            
+        case FLEXTypeEncodingArrayBegin:
+            // All arrays are supported, scan past them
+            if ([self scanPair:FLEXTypeEncodingArrayBegin close:FLEXTypeEncodingArrayEnd]) {
+                return typeIsClean();
+            }
+            break;
+            
+        case FLEXTypeEncodingUnionBegin:
+            // Unions are not supported at all in NSMethodSignature
+            // We could check for the closing token to be safe, but eh
+            self.scan.scanLocation = start;
+            return @"?";
+            
+        case FLEXTypeEncodingStructBegin: {
+            BOOL containsUnion = NO;
+            if ([self parseNextType:nil hasUnion:&containsUnion] != -1 && !containsUnion) {
+                return typeIsClean();
+            }
+            
+            // The structure we just tried to scan is unsupported, so just return its name
+            // if it has one. If not, just return a question mark.
+            self.scan.scanLocation++; // Skip past {
+            NSString *name = [self extractTypeNameFromScanLocation];
+            if (name) {
+                // Got the name, scan past the closing token
+                [self.scan scanUpToString:@"}" intoString:nil];
+                if (![self scanChar:FLEXTypeEncodingStructEnd]) {
+                    // Missing struct close token
+                    self.scan.scanLocation = start;
+                    return nil;
+                }
+            } else {
+                // Did not scan valid identifier, possibly a C++ type
                 self.scan.scanLocation = start;
-                return nil;
+                return @"{?=}";
             }
-        } else {
-            // Did not scan valid identifier, possibly a C++ type
+            
+            // Reset scan location even on success, because this method is not supposed to change it
             self.scan.scanLocation = start;
-            return @"{?=}";
+            return ({ // "{name=}"
+                NSMutableString *format = @"{".mutableCopy;
+                [format appendString:name];
+                [format appendString:@"=}"];
+                format;
+            });
         }
         
-        // Reset scan location even on success, because this method is not supposed to change it
-        self.scan.scanLocation = start;
-        return [NSString stringWithFormat:@"%@%@=%@", open, name, close];
+        default:
+            break;
     }
     
     // Check for other types, which in theory are all valid but whatever
-    if ([self scanAndGetSizeAndAlignForNextType:nil] != -1) {
+    if ([self parseNextType:nil hasUnion:nil] != -1) {
         return typeIsClean();
     }
     
     self.scan.scanLocation = start;
-    return @"{?=}";
+    return @"?";
 }
 
 - (NSUInteger)cleanedReplacingOffset {

+ 28 - 0
FLEXTests/FLEXTypeEncodingParserTests.m

@@ -34,6 +34,23 @@ typedef struct Anon {
     int baz;
 } Anon;
 
+typedef struct HasBitfield {
+    int a: 1;
+} HasBitfield;
+
+typedef struct HasArray {
+    int a[2];
+} HasArray;
+
+typedef struct HasUnion {
+    int a, b;
+    union {
+        float y, z;
+        char a;
+    } u;
+    char c;
+} HasUnion;
+
 @implementation FLEXTypeEncodingParserTests
 
 - (void)setUp {
@@ -157,6 +174,8 @@ typedef struct Anon {
     // Arrays do not affect alignment like nested structs do
     XCTAssertEqual(sizeof(ArrayInMiddle), 4);
     XCTAssertEqual(sizeof(ArrayAtEnd), 4);
+    
+    XCTAssertEqual(sizeof(HasUnion), 16);
 
     // Test my method of converting calculated sizes to actual sizes
     // for FLEXTypeEncodingParser
@@ -194,6 +213,9 @@ typedef struct Anon {
         @"^?32@0:8r^{_CAPropertyInfo=I[2:]b16b16*^{__CFString}}16"
         "r^{_CAPropertyInfo=I[2:]b16b16*^{__CFString}}24":
             @"^?32@0:8r^{_CAPropertyInfo=}16r^{_CAPropertyInfo=}24",
+        
+        // NSMethodSignature doesn't support unions for some reason
+        @"^{?=(pj_timestamp={?=II}Q)Iii}20@0:8i16": @"^{?=}20@0:8i16",
     };
     
     [uncleanToClean enumerateKeysAndObjectsUsingBlock:^(NSString *needsCleaning, NSString *cleaned, BOOL *stop) {
@@ -203,4 +225,10 @@ typedef struct Anon {
     }];
 }
 
+- (void)testSupportedTypeEncodings {
+    XCTAssertThrows(NSGetSizeAndAlignment(@encode(HasBitfield), nil, nil));
+    XCTAssertNoThrow(NSGetSizeAndAlignment(@encode(HasArray), nil, nil));
+    XCTAssertNoThrow(NSGetSizeAndAlignment(@encode(HasUnion), nil, nil));
+}
+
 @end