FLEXTypeEncodingParser.m 29 KB

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