FLEXTypeEncodingParser.m 30 KB

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