FLEXTypeEncodingParser.m 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880
  1. //
  2. // FLEXTypeEncodingParser.m
  3. // FLEX
  4. //
  5. // Created by Tanner Bennett on 8/22/19.
  6. // Copyright © 2019 Flipboard. All rights reserved.
  7. //
  8. #import "FLEXTypeEncodingParser.h"
  9. #import "FLEXRuntimeUtility.h"
  10. #define S(__ch) ({ \
  11. unichar __c = __ch; \
  12. [[NSString alloc] initWithCharacters:&__c length:1]; \
  13. })
  14. typedef struct FLEXTypeInfo {
  15. /// The size is unaligned. -1 if not supported at all.
  16. ssize_t size;
  17. ssize_t align;
  18. /// NO if the type cannot be supported at all
  19. /// YES if the type is either fully or partially supported.
  20. BOOL supported;
  21. /// YES if the type was only partially supported, such as in
  22. /// the case of unions in pointer types, or named structure
  23. /// types without member info. These can be corrected manually
  24. /// since they can be fixed or replaced with less info.
  25. BOOL fixesApplied;
  26. /// Whether this type is a union or one of its members
  27. /// recursively contains a union, exlcuding pointers.
  28. ///
  29. /// Unions are tricky because they're supported by
  30. /// \c NSGetSizeAndAlignment but not by \c NSMethodSignature
  31. /// so we need to track whenever a type contains a union
  32. /// so that we can clean it out of pointer types.
  33. BOOL containsUnion;
  34. } FLEXTypeInfo;
  35. /// Type info for a completely unsupported type.
  36. static FLEXTypeInfo FLEXTypeInfoUnsupported = (FLEXTypeInfo){ -1, 0, NO, NO, NO };
  37. /// Type info for the void return type.
  38. static FLEXTypeInfo FLEXTypeInfoVoid = (FLEXTypeInfo){ 0, 0, YES, NO, NO };
  39. /// Builds type info for a fully or partially supported type.
  40. static inline FLEXTypeInfo FLEXTypeInfoMake(ssize_t size, ssize_t align, BOOL fixed) {
  41. return (FLEXTypeInfo){ size, align, YES, fixed, NO };
  42. }
  43. /// Builds type info for a fully or partially supported type.
  44. static inline FLEXTypeInfo FLEXTypeInfoMakeU(ssize_t size, ssize_t align, BOOL fixed, BOOL hasUnion) {
  45. return (FLEXTypeInfo){ size, align, YES, fixed, hasUnion };
  46. }
  47. BOOL FLEXGetSizeAndAlignment(const char *type, NSUInteger *sizep, NSUInteger *alignp) {
  48. NSInteger size = 0, align = 0;
  49. size = [FLEXTypeEncodingParser sizeForTypeEncoding:@(type) alignment:&align];
  50. if (size == -1) {
  51. return NO;
  52. }
  53. if (sizep) {
  54. *sizep = (NSUInteger)size;
  55. }
  56. if (alignp) {
  57. *alignp = (NSUInteger)size;
  58. }
  59. return YES;
  60. }
  61. @interface FLEXTypeEncodingParser ()
  62. @property (nonatomic, readonly) NSScanner *scan;
  63. @property (nonatomic, readonly) NSString *scanned;
  64. @property (nonatomic, readonly) NSString *unscanned;
  65. @property (nonatomic, readonly) char nextChar;
  66. /// Replacements are made to this string as we scan as needed
  67. @property (nonatomic) NSMutableString *cleaned;
  68. /// Offset for \e further replacements to be made within \c cleaned
  69. @property (nonatomic, readonly) NSUInteger cleanedReplacingOffset;
  70. @end
  71. @implementation FLEXTypeEncodingParser
  72. - (NSString *)scanned {
  73. return [self.scan.string substringToIndex:self.scan.scanLocation];
  74. }
  75. - (NSString *)unscanned {
  76. return [self.scan.string substringFromIndex:self.scan.scanLocation];
  77. }
  78. #pragma mark Initialization
  79. - (id)initWithObjCTypes:(NSString *)typeEncoding {
  80. self = [super init];
  81. if (self) {
  82. _scan = [NSScanner scannerWithString:typeEncoding];
  83. _scan.caseSensitive = YES;
  84. _cleaned = typeEncoding.mutableCopy;
  85. }
  86. return self;
  87. }
  88. #pragma mark Public
  89. + (BOOL)methodTypeEncodingSupported:(NSString *)typeEncoding cleaned:(NSString * __autoreleasing *)cleanedEncoding {
  90. FLEXTypeEncodingParser *parser = [[self alloc] initWithObjCTypes:typeEncoding];
  91. while (!parser.scan.isAtEnd) {
  92. FLEXTypeInfo info = [parser parseNextType];
  93. if (!info.supported || info.containsUnion) {
  94. return NO;
  95. }
  96. }
  97. if (cleanedEncoding) {
  98. *cleanedEncoding = parser.cleaned.copy;
  99. }
  100. return YES;
  101. }
  102. + (NSString *)type:(NSString *)typeEncoding forMethodArgumentAtIndex:(NSUInteger)idx {
  103. FLEXTypeEncodingParser *parser = [[self alloc] initWithObjCTypes:typeEncoding];
  104. // Scan up to the argument we want
  105. for (NSUInteger i = 0; i < idx; i++) {
  106. if (![parser scanPastArg]) {
  107. [NSException raise:NSRangeException
  108. format:@"Index %lu out of bounds for type encoding '%@'", idx, typeEncoding];
  109. }
  110. }
  111. return [parser scanArg];
  112. }
  113. + (ssize_t)size:(NSString *)typeEncoding forMethodArgumentAtIndex:(NSUInteger)idx {
  114. return [self sizeForTypeEncoding:[self type:typeEncoding forMethodArgumentAtIndex:idx] alignment:nil];
  115. }
  116. + (ssize_t)sizeForTypeEncoding:(NSString *)type alignment:(ssize_t *)alignOut {
  117. return [self sizeForTypeEncoding:type alignment:alignOut unaligned:NO];
  118. }
  119. + (ssize_t)sizeForTypeEncoding:(NSString *)type alignment:(ssize_t *)alignOut unaligned:(BOOL)unaligned {
  120. FLEXTypeInfo info = [self parseType:type];
  121. ssize_t size = info.size;
  122. ssize_t align = info.align;
  123. if (info.supported) {
  124. if (alignOut) {
  125. *alignOut = align;
  126. }
  127. if (!unaligned) {
  128. size += size % align;
  129. }
  130. }
  131. // size is -1 if not supported
  132. return size;
  133. }
  134. + (FLEXTypeInfo)parseType:(NSString *)type cleaned:(NSString * __autoreleasing *)cleanedEncoding {
  135. FLEXTypeEncodingParser *parser = [[self alloc] initWithObjCTypes:type];
  136. FLEXTypeInfo info = [parser parseNextType];
  137. if (cleanedEncoding) {
  138. *cleanedEncoding = parser.cleaned;
  139. }
  140. return info;
  141. }
  142. + (FLEXTypeInfo)parseType:(NSString *)type {
  143. return [self parseType:type cleaned:nil];
  144. }
  145. #pragma mark Private
  146. - (NSCharacterSet *)identifierFirstCharCharacterSet {
  147. static NSCharacterSet *identifierFirstSet = nil;
  148. static dispatch_once_t onceToken;
  149. dispatch_once(&onceToken, ^{
  150. NSString *allowed = @"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_$";
  151. identifierFirstSet = [NSCharacterSet characterSetWithCharactersInString:allowed];
  152. });
  153. return identifierFirstSet;
  154. }
  155. - (NSCharacterSet *)identifierCharacterSet {
  156. static NSCharacterSet *identifierSet = nil;
  157. static dispatch_once_t onceToken;
  158. dispatch_once(&onceToken, ^{
  159. NSString *allowed = @"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_$1234567890";
  160. identifierSet = [NSCharacterSet characterSetWithCharactersInString:allowed];
  161. });
  162. return identifierSet;
  163. }
  164. - (char)nextChar {
  165. NSScanner *scan = self.scan;
  166. return [scan.string characterAtIndex:scan.scanLocation];
  167. }
  168. /// For scanning struct/class names
  169. - (NSString *)scanIdentifier {
  170. NSString *prefix = nil, *suffix = nil;
  171. // Identifiers cannot start with a number
  172. if (![self.scan scanCharactersFromSet:self.identifierFirstCharCharacterSet intoString:&prefix]) {
  173. return nil;
  174. }
  175. // Optional because identifier may just be one character
  176. [self.scan scanCharactersFromSet:self.identifierCharacterSet intoString:&suffix];
  177. if (suffix) {
  178. return [prefix stringByAppendingString:suffix];
  179. }
  180. return prefix;
  181. }
  182. /// @return the size in bytes
  183. - (ssize_t)sizeForType:(FLEXTypeEncoding)type {
  184. switch (type) {
  185. case FLEXTypeEncodingChar: return sizeof(char);
  186. case FLEXTypeEncodingInt: return sizeof(int);
  187. case FLEXTypeEncodingShort: return sizeof(short);
  188. case FLEXTypeEncodingLong: return sizeof(long);
  189. case FLEXTypeEncodingLongLong: return sizeof(long long);
  190. case FLEXTypeEncodingUnsignedChar: return sizeof(unsigned char);
  191. case FLEXTypeEncodingUnsignedInt: return sizeof(unsigned int);
  192. case FLEXTypeEncodingUnsignedShort: return sizeof(unsigned short);
  193. case FLEXTypeEncodingUnsignedLong: return sizeof(unsigned long);
  194. case FLEXTypeEncodingUnsignedLongLong: return sizeof(unsigned long long);
  195. case FLEXTypeEncodingFloat: return sizeof(float);
  196. case FLEXTypeEncodingDouble: return sizeof(double);
  197. case FLEXTypeEncodingLongDouble: return sizeof(long double);
  198. case FLEXTypeEncodingCBool: return sizeof(_Bool);
  199. case FLEXTypeEncodingVoid: return 0;
  200. case FLEXTypeEncodingCString: return sizeof(char *);
  201. case FLEXTypeEncodingObjcObject: return sizeof(id);
  202. case FLEXTypeEncodingObjcClass: return sizeof(Class);
  203. case FLEXTypeEncodingSelector: return sizeof(SEL);
  204. // Unknown / '?' is typically a pointer. In the rare case
  205. // it isn't, such as in '{?=...}', it is never passed here.
  206. case FLEXTypeEncodingUnknown:
  207. case FLEXTypeEncodingPointer: return sizeof(uintptr_t);
  208. default: return -1;
  209. }
  210. }
  211. - (FLEXTypeInfo)parseNextType {
  212. NSUInteger start = self.scan.scanLocation;
  213. // Check for void first
  214. if ([self scanChar:FLEXTypeEncodingVoid]) {
  215. // Skip argument frame for method signatures
  216. [self scanSize];
  217. return FLEXTypeInfoVoid;
  218. }
  219. // Scan optional const
  220. [self scanChar:FLEXTypeEncodingConst];
  221. // Check for pointer, then scan next
  222. if ([self scanChar:FLEXTypeEncodingPointer]) {
  223. // Recurse to scan something else
  224. NSUInteger pointerTypeStart = self.scan.scanLocation;
  225. if ([self scanPastArg]) {
  226. // Make sure the pointer type is supported, and clean it if not
  227. NSUInteger pointerTypeLength = self.scan.scanLocation - pointerTypeStart;
  228. NSString *pointerType = [self.scan.string
  229. substringWithRange:NSMakeRange(pointerTypeStart, pointerTypeLength)
  230. ];
  231. // Deeeep nested cleaning info gets lost here
  232. NSString *cleaned = nil;
  233. FLEXTypeInfo info = [self.class parseType:pointerType cleaned:&cleaned];
  234. BOOL needsCleaning = !info.supported || info.containsUnion || info.fixesApplied;
  235. // Clean the type if it is unsupported, malformed, or contains a union.
  236. // (Unions are supported by NSGetSizeAndAlignment but not
  237. // supported by NSMethodSignature for some reason)
  238. if (needsCleaning) {
  239. // if unsupported, no cleaning occurred in parseType:cleaned: above.
  240. // Otherwise, the type is partially supported and we did clean it,
  241. // and we will replace this type with the cleaned type from above.
  242. if (!info.supported || info.containsUnion) {
  243. cleaned = [self cleanPointeeTypeAtLocation:pointerTypeStart];
  244. }
  245. [self.cleaned replaceCharactersInRange:NSMakeRange(
  246. pointerTypeStart - self.cleanedReplacingOffset, pointerTypeLength
  247. ) withString:cleaned];
  248. }
  249. // Skip optional frame offset
  250. [self scanSize];
  251. ssize_t size = [self sizeForType:FLEXTypeEncodingPointer];
  252. return FLEXTypeInfoMake(size, size, !info.supported || info.fixesApplied);
  253. } else {
  254. // Scan failed, abort
  255. self.scan.scanLocation = start;
  256. return FLEXTypeInfoUnsupported;
  257. }
  258. }
  259. // Check for struct/union/array
  260. char next = self.nextChar;
  261. BOOL didScanSUA = YES, structOrUnion = NO, isUnion = NO;
  262. FLEXTypeEncoding opening = FLEXTypeEncodingNull, closing = FLEXTypeEncodingNull;
  263. switch (next) {
  264. case FLEXTypeEncodingStructBegin:
  265. structOrUnion = YES;
  266. opening = FLEXTypeEncodingStructBegin;
  267. closing = FLEXTypeEncodingStructEnd;
  268. break;
  269. case FLEXTypeEncodingUnionBegin:
  270. structOrUnion = isUnion = YES;
  271. opening = FLEXTypeEncodingUnionBegin;
  272. closing = FLEXTypeEncodingUnionEnd;
  273. break;
  274. case FLEXTypeEncodingArrayBegin:
  275. opening = FLEXTypeEncodingArrayBegin;
  276. closing = FLEXTypeEncodingArrayEnd;
  277. break;
  278. default:
  279. didScanSUA = NO;
  280. break;
  281. }
  282. if (didScanSUA) {
  283. BOOL containsUnion = isUnion;
  284. BOOL fixesApplied = NO;
  285. NSUInteger backup = self.scan.scanLocation;
  286. // Ensure we have a closing tag
  287. if (![self scanPair:opening close:closing]) {
  288. // Scan failed, abort
  289. self.scan.scanLocation = start;
  290. return FLEXTypeInfoUnsupported;
  291. }
  292. // Move cursor just after opening tag (struct/union/array)
  293. NSInteger arrayCount = -1;
  294. self.scan.scanLocation = backup + 1;
  295. if (!structOrUnion) {
  296. arrayCount = [self scanSize];
  297. if (!arrayCount || self.nextChar == FLEXTypeEncodingArrayEnd) {
  298. // Malformed array type:
  299. // 1. Arrays must have a count after the opening brace
  300. // 2. Arrays must have an element type after the count
  301. self.scan.scanLocation = start;
  302. return FLEXTypeInfoUnsupported;
  303. }
  304. } else {
  305. // If we encounter the ?= portion of something like {?=b8b4b1b1b18[8S]}
  306. // then we skip over it, since it means nothing to us in this context.
  307. // It is completely optional, and if it fails, we go right back where we were.
  308. [self scanTypeName];
  309. }
  310. // Sum sizes of members together:
  311. // Scan for bitfields before checking for other members
  312. //
  313. // Arrays will only have one "member," but
  314. // this logic still works for them
  315. ssize_t sizeSoFar = 0;
  316. ssize_t maxAlign = 0;
  317. NSMutableString *cleanedBackup = self.cleaned.mutableCopy;
  318. while (![self scanChar:closing]) {
  319. next = self.nextChar;
  320. // Check for bitfields, which we cannot support because
  321. // type encodings for bitfields do not include alignment info
  322. if (next == FLEXTypeEncodingBitField) {
  323. self.scan.scanLocation = start;
  324. return FLEXTypeInfoUnsupported;
  325. }
  326. // Structure fields could be named
  327. if (next == FLEXTypeEncodingQuote) {
  328. [self scanPair:FLEXTypeEncodingQuote close:FLEXTypeEncodingQuote];
  329. }
  330. FLEXTypeInfo info = [self parseNextType];
  331. if (!info.supported || info.containsUnion) {
  332. // The above call is the only time in this method where
  333. // `cleaned` might be mutated recursively, so this is the
  334. // only place where we need to keep and restore a backup
  335. //
  336. // For instance, if we've been iterating over the members
  337. // of a struct and we've encountered a few pointers so far
  338. // that we needed to clean, and suddenly we come across an
  339. // unsupported member, we need to be able to "rewind" and
  340. // undo any changes to `self.cleaned` so that the parent
  341. // call in the call stack can wipe the current structure
  342. // clean entirely if needed. Example below:
  343. //
  344. // Initial: ^{foo=^{pair<d,d>}{^pair<i,i>}{invalid_type<d>}}
  345. // v-- here
  346. // 1st clean: ^{foo=^{?=}{^pair<i,i>}{invalid_type<d>}
  347. // v-- here
  348. // 2nd clean: ^{foo=^{?=}{?=}{invalid_type<d>}
  349. // v-- here
  350. // Can't clean: ^{foo=^{?=}{?=}{invalid_type<d>}
  351. // v-- to here
  352. // Rewind: ^{foo=^{pair<d,d>}{^pair<i,i>}{invalid_type<d>}}
  353. // Final clean: ^{foo=}
  354. self.cleaned = cleanedBackup;
  355. self.scan.scanLocation = start;
  356. return FLEXTypeInfoUnsupported;
  357. }
  358. // Unions are the size of their largest member,
  359. // arrays are element.size x length, and
  360. // structs are the sum of their members
  361. if (structOrUnion) {
  362. if (isUnion) { // Union
  363. sizeSoFar = MAX(sizeSoFar, info.size);
  364. } else { // Struct
  365. sizeSoFar += info.size;
  366. }
  367. } else { // Array
  368. sizeSoFar = info.size * arrayCount;
  369. }
  370. // Propogate the max alignment and other metadata
  371. maxAlign = MAX(maxAlign, info.align);
  372. containsUnion = containsUnion || info.containsUnion;
  373. fixesApplied = fixesApplied || info.fixesApplied;
  374. }
  375. // Skip optional frame offset
  376. [self scanSize];
  377. return FLEXTypeInfoMakeU(sizeSoFar, maxAlign, fixesApplied, containsUnion);
  378. }
  379. // Scan single thing and possible size and return
  380. ssize_t size = -1;
  381. char t = self.nextChar;
  382. switch (t) {
  383. case FLEXTypeEncodingUnknown:
  384. case FLEXTypeEncodingChar:
  385. case FLEXTypeEncodingInt:
  386. case FLEXTypeEncodingShort:
  387. case FLEXTypeEncodingLong:
  388. case FLEXTypeEncodingLongLong:
  389. case FLEXTypeEncodingUnsignedChar:
  390. case FLEXTypeEncodingUnsignedInt:
  391. case FLEXTypeEncodingUnsignedShort:
  392. case FLEXTypeEncodingUnsignedLong:
  393. case FLEXTypeEncodingUnsignedLongLong:
  394. case FLEXTypeEncodingFloat:
  395. case FLEXTypeEncodingDouble:
  396. case FLEXTypeEncodingLongDouble:
  397. case FLEXTypeEncodingCBool:
  398. case FLEXTypeEncodingCString:
  399. case FLEXTypeEncodingSelector:
  400. case FLEXTypeEncodingBitField: {
  401. self.scan.scanLocation++;
  402. // Skip optional frame offset
  403. [self scanSize];
  404. if (t == FLEXTypeEncodingBitField) {
  405. self.scan.scanLocation = start;
  406. return FLEXTypeInfoUnsupported;
  407. } else {
  408. // Compute size
  409. size = [self sizeForType:t];
  410. }
  411. }
  412. break;
  413. case FLEXTypeEncodingObjcObject:
  414. case FLEXTypeEncodingObjcClass: {
  415. self.scan.scanLocation++;
  416. // These might have numbers OR quotes after them
  417. // Skip optional frame offset
  418. [self scanSize];
  419. [self scanPair:FLEXTypeEncodingQuote close:FLEXTypeEncodingQuote];
  420. size = sizeof(id);
  421. }
  422. break;
  423. default: break;
  424. }
  425. if (size > 0) {
  426. // Alignment of scalar types is its size
  427. return FLEXTypeInfoMake(size, size, NO);
  428. }
  429. self.scan.scanLocation = start;
  430. return FLEXTypeInfoUnsupported;
  431. }
  432. - (BOOL)scanString:(NSString *)str {
  433. return [self.scan scanString:str intoString:nil];
  434. }
  435. - (BOOL)canScanString:(NSString *)str {
  436. NSScanner *scan = self.scan;
  437. NSUInteger len = str.length;
  438. unichar buff1[len], buff2[len];
  439. [str getCharacters:buff1];
  440. [scan.string getCharacters:buff2 range:NSMakeRange(scan.scanLocation, len)];
  441. if (memcmp(buff1, buff2, len) == 0) {
  442. return YES;
  443. }
  444. return NO;
  445. }
  446. - (BOOL)canScanChar:(char)c {
  447. NSScanner *scan = self.scan;
  448. if (scan.scanLocation >= scan.string.length) return NO;
  449. return [scan.string characterAtIndex:scan.scanLocation] == c;
  450. }
  451. - (BOOL)scanChar:(char)c {
  452. if ([self canScanChar:c]) {
  453. self.scan.scanLocation++;
  454. return YES;
  455. }
  456. return NO;
  457. }
  458. - (BOOL)scanChar:(char)c into:(char *)ref {
  459. if ([self scanChar:c]) {
  460. *ref = c;
  461. return YES;
  462. }
  463. return NO;
  464. }
  465. - (ssize_t)scanSize {
  466. NSInteger size = 0;
  467. if ([self.scan scanInteger:&size]) {
  468. return size;
  469. }
  470. return 0;
  471. }
  472. - (NSString *)scanPair:(char)c1 close:(char)c2 {
  473. // Starting position and string variables
  474. NSUInteger start = self.scan.scanLocation;
  475. NSString *s1 = S(c1);
  476. // Scan opening tag
  477. if (![self scanChar:c1]) {
  478. self.scan.scanLocation = start;
  479. return nil;
  480. }
  481. // Character set for scanning up to either symbol
  482. NSCharacterSet *bothChars = ({
  483. unichar buff[2] = { c1, c2 };
  484. NSString *bothCharsStr = [[NSString alloc] initWithCharacters:buff length:2];
  485. [NSCharacterSet characterSetWithCharactersInString:bothCharsStr];
  486. });
  487. // Stack for finding pairs, starting with the opening symbol
  488. NSMutableArray *stack = [NSMutableArray arrayWithObject:s1];
  489. // Algorithm for scanning to the closing end of a pair of opening/closing symbols
  490. // scanUpToCharactersFromSet:intoString: returns NO if you're already at one of the chars,
  491. // so we need to check if we can actually scan one if it returns NO
  492. while ([self.scan scanUpToCharactersFromSet:bothChars intoString:nil] ||
  493. [self canScanChar:c1] || [self canScanChar:c2]) {
  494. // Closing symbol found
  495. if ([self scanChar:c2]) {
  496. if (!stack.count) {
  497. // Abort, no matching opening symbol
  498. self.scan.scanLocation = start;
  499. return nil;
  500. }
  501. // Pair found, pop opening symbol
  502. [stack removeLastObject];
  503. // Exit loop if we reached the closing brace we needed
  504. if (!stack.count) {
  505. break;
  506. }
  507. }
  508. // Opening symbol found
  509. if ([self scanChar:c1]) {
  510. // Begin pair
  511. [stack addObject:s1];
  512. }
  513. }
  514. if (stack.count) {
  515. // Abort, no matching closing symbol
  516. self.scan.scanLocation = start;
  517. return nil;
  518. }
  519. // Slice out the string we just scanned
  520. return [self.scan.string
  521. substringWithRange:NSMakeRange(start, self.scan.scanLocation - start)
  522. ];
  523. }
  524. - (BOOL)scanPastArg {
  525. NSUInteger start = self.scan.scanLocation;
  526. // Check for void first
  527. if ([self scanChar:FLEXTypeEncodingVoid]) {
  528. return YES;
  529. }
  530. // Scan optional const
  531. [self scanChar:FLEXTypeEncodingConst];
  532. // Check for pointer, then scan next
  533. if ([self scanChar:FLEXTypeEncodingPointer]) {
  534. // Recurse to scan something else
  535. if ([self scanPastArg]) {
  536. return YES;
  537. } else {
  538. // Scan failed, abort
  539. self.scan.scanLocation = start;
  540. return NO;
  541. }
  542. }
  543. char next = self.nextChar;
  544. // Check for struct/union/array, scan past it
  545. FLEXTypeEncoding opening = FLEXTypeEncodingNull, closing = FLEXTypeEncodingNull;
  546. BOOL checkPair = YES;
  547. switch (next) {
  548. case FLEXTypeEncodingStructBegin:
  549. opening = FLEXTypeEncodingStructBegin;
  550. closing = FLEXTypeEncodingStructEnd;
  551. break;
  552. case FLEXTypeEncodingUnionBegin:
  553. opening = FLEXTypeEncodingUnionBegin;
  554. closing = FLEXTypeEncodingUnionEnd;
  555. break;
  556. case FLEXTypeEncodingArrayBegin:
  557. opening = FLEXTypeEncodingArrayBegin;
  558. closing = FLEXTypeEncodingArrayEnd;
  559. break;
  560. default:
  561. checkPair = NO;
  562. break;
  563. }
  564. if (checkPair && [self scanPair:opening close:closing]) {
  565. return YES;
  566. }
  567. // Scan single thing and possible size and return
  568. switch (next) {
  569. case FLEXTypeEncodingUnknown:
  570. case FLEXTypeEncodingChar:
  571. case FLEXTypeEncodingInt:
  572. case FLEXTypeEncodingShort:
  573. case FLEXTypeEncodingLong:
  574. case FLEXTypeEncodingLongLong:
  575. case FLEXTypeEncodingUnsignedChar:
  576. case FLEXTypeEncodingUnsignedInt:
  577. case FLEXTypeEncodingUnsignedShort:
  578. case FLEXTypeEncodingUnsignedLong:
  579. case FLEXTypeEncodingUnsignedLongLong:
  580. case FLEXTypeEncodingFloat:
  581. case FLEXTypeEncodingDouble:
  582. case FLEXTypeEncodingLongDouble:
  583. case FLEXTypeEncodingCBool:
  584. case FLEXTypeEncodingCString:
  585. case FLEXTypeEncodingSelector:
  586. case FLEXTypeEncodingBitField: {
  587. self.scan.scanLocation++;
  588. // Size is optional
  589. [self scanSize];
  590. return YES;
  591. }
  592. case FLEXTypeEncodingObjcObject:
  593. case FLEXTypeEncodingObjcClass: {
  594. self.scan.scanLocation++;
  595. // These might have numbers OR quotes after them
  596. [self scanSize] || [self scanPair:FLEXTypeEncodingQuote close:FLEXTypeEncodingQuote];
  597. return YES;
  598. }
  599. default: break;
  600. }
  601. self.scan.scanLocation = start;
  602. return NO;
  603. }
  604. - (NSString *)scanArg {
  605. NSUInteger start = self.scan.scanLocation;
  606. if (![self scanPastArg]) {
  607. return nil;
  608. }
  609. return [self.scan.string
  610. substringWithRange:NSMakeRange(start, self.scan.scanLocation - start)
  611. ];
  612. }
  613. - (BOOL)scanTypeName {
  614. NSUInteger start = self.scan.scanLocation;
  615. // The ?= portion of something like {?=b8b4b1b1b18[8S]}
  616. if ([self scanChar:FLEXTypeEncodingUnknown]) {
  617. if (![self scanString:@"="]) {
  618. // No size information available for strings like {?=}
  619. self.scan.scanLocation = start;
  620. return NO;
  621. }
  622. } else {
  623. if (![self scanIdentifier] || ![self scanString:@"="]) {
  624. // 1. Not a valid identifier
  625. // 2. No size information available for strings like {CGPoint}
  626. self.scan.scanLocation = start;
  627. return NO;
  628. }
  629. }
  630. return YES;
  631. }
  632. - (NSString *)extractTypeNameFromScanLocation:(BOOL)allowMissingTypeInfo closing:(FLEXTypeEncoding)closeTag {
  633. NSUInteger start = self.scan.scanLocation;
  634. // The ?= portion of something like {?=b8b4b1b1b18[8S]}
  635. if ([self scanChar:FLEXTypeEncodingUnknown]) {
  636. return @"?";
  637. } else {
  638. NSString *typeName = [self scanIdentifier];
  639. char next = self.nextChar;
  640. if (!typeName) {
  641. // Did not scan an identifier
  642. self.scan.scanLocation = start;
  643. return nil;
  644. }
  645. switch (next) {
  646. case '=':
  647. return typeName;
  648. default: {
  649. // = is non-optional unless we allowMissingTypeInfo, in whcih
  650. // case the next character needs to be a closing brace
  651. if (allowMissingTypeInfo && next == closeTag) {
  652. return typeName;
  653. } else {
  654. // Not a valid identifier; possibly a generic C++ type
  655. // i.e. {pair<T, U>} where `name` was found as `pair`
  656. self.scan.scanLocation = start;
  657. return nil;
  658. }
  659. }
  660. }
  661. }
  662. }
  663. - (NSString *)cleanPointeeTypeAtLocation:(NSUInteger)scanLocation {
  664. NSUInteger start = self.scan.scanLocation;
  665. self.scan.scanLocation = scanLocation;
  666. // The return / cleanup code for when the scanned type is already clean
  667. NSString * (^typeIsClean)() = ^NSString * {
  668. NSString *clean = [self.scan.string
  669. substringWithRange:NSMakeRange(scanLocation, self.scan.scanLocation - scanLocation)
  670. ];
  671. // Reset scan location even on success, because this method is not supposed to change it
  672. self.scan.scanLocation = start;
  673. return clean;
  674. };
  675. // No void, this is not a return type
  676. // Scan optional const
  677. [self scanChar:FLEXTypeEncodingConst];
  678. char next = self.nextChar;
  679. switch (next) {
  680. case FLEXTypeEncodingPointer:
  681. // Recurse to scan something else
  682. [self scanChar:next];
  683. return [self cleanPointeeTypeAtLocation:self.scan.scanLocation];
  684. case FLEXTypeEncodingArrayBegin:
  685. // All arrays are supported, scan past them
  686. if ([self scanPair:FLEXTypeEncodingArrayBegin close:FLEXTypeEncodingArrayEnd]) {
  687. return typeIsClean();
  688. }
  689. break;
  690. case FLEXTypeEncodingUnionBegin:
  691. // Unions are not supported at all in NSMethodSignature
  692. // We could check for the closing token to be safe, but eh
  693. self.scan.scanLocation = start;
  694. return @"?";
  695. case FLEXTypeEncodingStructBegin: {
  696. FLEXTypeInfo info = [self parseNextType];
  697. if (info.supported && !info.fixesApplied) {
  698. return typeIsClean();
  699. }
  700. // The structure we just tried to scan is unsupported, so just return its name
  701. // if it has one. If not, just return a question mark.
  702. self.scan.scanLocation++; // Skip past {
  703. NSString *name = [self extractTypeNameFromScanLocation:YES closing:FLEXTypeEncodingStructEnd];
  704. if (name) {
  705. // Got the name, scan past the closing token
  706. [self.scan scanUpToString:@"}" intoString:nil];
  707. if (![self scanChar:FLEXTypeEncodingStructEnd]) {
  708. // Missing struct close token
  709. self.scan.scanLocation = start;
  710. return nil;
  711. }
  712. } else {
  713. // Did not scan valid identifier, possibly a C++ type
  714. self.scan.scanLocation = start;
  715. return @"{?=}";
  716. }
  717. // Reset scan location even on success, because this method is not supposed to change it
  718. self.scan.scanLocation = start;
  719. return ({ // "{name=}"
  720. NSMutableString *format = @"{".mutableCopy;
  721. [format appendString:name];
  722. [format appendString:@"=}"];
  723. format;
  724. });
  725. }
  726. default:
  727. break;
  728. }
  729. // Check for other types, which in theory are all valid but whatever
  730. FLEXTypeInfo info = [self parseNextType];
  731. if (info.supported && !info.fixesApplied) {
  732. return typeIsClean();
  733. }
  734. self.scan.scanLocation = start;
  735. return @"?";
  736. }
  737. - (NSUInteger)cleanedReplacingOffset {
  738. return self.scan.string.length - self.cleaned.length;
  739. }
  740. @end