/ NSMethodSignature.m
NSMethodSignature.m
  1  //
  2  //  NSMethodSignature.m
  3  //  CoreFoundation
  4  //
  5  //  Copyright (c) 2014 Apportable. All rights reserved.
  6  //
  7  
  8  #import <Foundation/NSMethodSignature.h>
  9  #import "NSObjCRuntimeInternal.h"
 10  #import "NSObjectInternal.h"
 11  
 12  #define ALIGN_TO(value, alignment) \
 13  (((value) % (alignment)) ? \
 14  ((value) + (alignment) - ((value) % (alignment))) : \
 15  (value) \
 16  )
 17  
 18  extern void __CFStringAppendBytes(CFMutableStringRef, const char *, CFIndex, CFStringEncoding);
 19  
 20  @implementation NSMethodSignature
 21  
 22  - (instancetype)initWithObjCTypes:(const char *)types
 23  {
 24      self = [super init];
 25  
 26      if (self == nil)
 27      {
 28          return nil;
 29      }
 30  
 31      _count = 0;
 32      _frameLength = 0;
 33      _typeString = CFStringCreateMutable(NULL, strlen(types));
 34      // strlen(types) is a safe overapproximation to the actual number of types.
 35      _types = calloc(sizeof(NSMethodType), strlen(types));
 36  
 37      if (UNLIKELY(_types == NULL))
 38      {
 39          [self release];
 40          return nil;
 41      }
 42  
 43      const char *currentType = types;
 44      const char *nextType = types;
 45  
 46  #ifdef __LP64__
 47      // On x86-64, the first few arguments are passed in registers as long as
 48      // they satisfy certain conditions. __CF_forwarding_prep and __invoke__
 49      // pack and unpack all register values into a 0xe0-sized block preceeding
 50      // the actual stack frame contents. This means frameLength always starts
 51      // at 0xe0 and grows from there if there are any on-stack arguments.
 52      unsigned short usedGPRegisters = 0;
 53      unsigned short usedSSERegisters = 0;
 54      _frameLength = 0xe0;
 55  #endif
 56  
 57      while (nextType[0])
 58      {
 59          NSMethodType *ms = &_types[_count];
 60  
 61          if (nextType[0] == '>' && nextType[1] == '\0') {
 62              // handle the case where we initialize the signature using the extended type description of a block parameter
 63              // (this is so that we don't have to copy the string and null terminate it in `_signatureForBlockAtArgumentIndex:`)
 64              break;
 65          }
 66  
 67          currentType = nextType;
 68          nextType = NSGetSizeAndAlignment(currentType, &ms->size, &ms->alignment);
 69  
 70          // append the type info WITHOUT the extended type info
 71          __CFStringAppendBytes(_typeString, currentType, nextType - currentType, kCFStringEncodingUTF8);
 72  
 73          // we need to be able to handle extended type encodings.
 74          // these don't affect the size or alignment of the type, but they are considered part of the type and we need to store them
 75          if (nextType[0] == '"') {
 76              // this describes what kind of object the argument expects.
 77              // this case is simple; no nesting possible
 78              nextType = strchr(nextType + 1, '"');
 79              if (!nextType) {
 80                  // no closing quotation mark? invalid type encoding.
 81                  [NSException raise: NSInvalidArgumentException format: @"Invalid type encoding: expected closing quotation mark"];
 82              }
 83              ++nextType; // skip the closing quotation mark
 84          } else if (nextType[0] == '<') {
 85              // this describes the block signature.
 86              // this case is little more complicated; nesting is possible
 87              size_t nestLevel = 1;
 88              ++nextType;
 89              for (; nestLevel > 0 && nextType[0] != '\0'; ++nextType) {
 90                  if (nextType[0] == '<') {
 91                      ++nestLevel;
 92                  } else if (nextType[0] == '>') {
 93                      --nestLevel;
 94                  }
 95              }
 96              if (nestLevel > 0) {
 97                  // still missing a closing angle bracket? invalid type encoding.
 98                  [NSException raise: NSInvalidArgumentException format: @"Invalid type encoding: expected closing angle bracket"];
 99              }
100          }
101  
102          // there might other extended type encodings i haven't encountered, but those two should be the two most important ones
103  
104          ms->type = calloc(nextType - currentType + 1, 1);
105          if (UNLIKELY(ms->type == NULL))
106          {
107              [self release];
108              return nil;
109          }
110          strncpy(ms->type, currentType, nextType - currentType);
111  
112          // Skip advisory size
113          // (but record the size start offset so we can append into the type string)
114          const char* sizeStart = nextType;
115          strtol(nextType, (char **)&nextType, 10);
116  
117          // append the size info
118          if (nextType - sizeStart > 0) {
119              __CFStringAppendBytes(_typeString, sizeStart, nextType - sizeStart, kCFStringEncodingUTF8);
120          }
121  
122          NSUInteger frameAlignment = MAX(ms->alignment, sizeof(int));
123          NSUInteger frameSize = ALIGN_TO(ms->size, frameAlignment);
124  
125          if (_count == 0)
126          {
127              // Determine whether the method is stret, based on the
128              // type of the return value.
129              switch (*stripQualifiersAndComments(_types[0].type))
130              {
131                  case _C_STRUCT_B:
132                  {
133                      if (frameSize > sizeof(int))
134                      {
135                          // Account for the stret return pointer.
136                          _frameLength += sizeof(void *);
137                          _stret = YES;
138  #if __LP64__
139                          // on x86_64, the first GP register (rdi) is used to pass the stret pointer
140                          ++usedGPRegisters;
141  #endif
142                      }
143                      break;
144                  }
145  
146                  default:
147                      // All other cases are non-stret.
148                      break;
149              }
150          }
151          else
152          {
153  #if __arm__
154              _frameLength = ALIGN_TO(_frameLength, frameAlignment);
155  #elif __LP64__
156              // FIXME: This is far from being a complete implementation of
157              // the x86-64 calling convention.
158              BOOL isFP = currentType[0] == _C_FLT || currentType[0] == _C_DBL;
159              if (isFP) {
160                  unsigned short registersNeeded = ALIGN_TO(frameSize, 16) / 16;
161                  if (usedSSERegisters + registersNeeded > 8) {
162                      _types[_count].offset = _frameLength;
163                      _frameLength += frameSize;
164                  } else {
165                      _types[_count].offset = 0x30 + usedSSERegisters * 16;
166                      usedSSERegisters += registersNeeded;
167                  }
168              } else {
169                  unsigned short registersNeeded = ALIGN_TO(frameSize, 8) / 8;
170                  if (usedGPRegisters + registersNeeded > 6 || frameSize > 16) {
171                      _types[_count].offset = _frameLength;
172                      _frameLength += frameSize;
173                  } else {
174                      _types[_count].offset = usedGPRegisters * 8;
175                      usedGPRegisters += registersNeeded;
176                  }
177              }
178  #else
179              _types[_count].offset = _frameLength;
180              _frameLength += frameSize;
181  #endif
182          }
183  
184          _count++;
185      }
186  
187      // Check whether the method is oneway by reading all the
188      // qualifiers of the return type.
189      static const char *qualifiers = "nNoOrRV";
190      char *cur = _types[0].type;
191      while (strchr(qualifiers, *cur)) {
192          if (*cur == 'V') {
193              _isOneway = YES;
194              break;
195          }
196          cur++;
197      }
198  
199      return self;
200  }
201  
202  + (NSMethodSignature *)signatureWithObjCTypes:(const char *)types
203  {
204      return [[[self alloc] initWithObjCTypes:types] autorelease];
205  }
206  
207  - (void)dealloc
208  {
209      for (NSUInteger idx = 0; idx < _count; idx++)
210      {
211          if (_types[idx].type != NULL)
212          {
213              free(_types[idx].type);
214          }
215      }
216  
217      if (_types != NULL)
218      {
219          free(_types);
220      }
221  
222      CFRelease(_typeString);
223      [super dealloc];
224  }
225  
226  - (NSUInteger)numberOfArguments
227  {
228      return _count - 1;
229  }
230  
231  - (const char *)getArgumentTypeAtIndex:(NSUInteger)idx
232  {
233      return _types[idx + 1].type;
234  }
235  
236  - (NSUInteger)frameLength
237  {
238      return _frameLength;
239  }
240  
241  - (BOOL)isOneway
242  {
243      return _isOneway;
244  }
245  
246  - (const char *)methodReturnType
247  {
248      return _types[0].type;
249  }
250  
251  - (NSUInteger)methodReturnLength
252  {
253      return _types[0].size;
254  }
255  
256  - (NSMethodType *)_argInfo:(NSUInteger)index
257  {
258      return &_types[index];
259  }
260  
261  - (BOOL)_stret
262  {
263      return _stret;
264  }
265  
266  - (CFStringRef) _typeString
267  {
268      return _typeString;
269  }
270  
271  - (NSMethodSignature*)_signatureForBlockAtArgumentIndex: (NSUInteger)index
272  {
273      const char* argType = _types[index].type;
274      argType = strchr(argType, '<');
275      if (!argType) {
276          return nil;
277      }
278      return [NSMethodSignature signatureWithObjCTypes: argType + 1];
279  }
280  
281  - (Class)_classForObjectAtArgumentIndex: (NSUInteger)index
282  {
283      const char* argType = _types[index].type;
284      argType = strchr(argType, '"');
285      if (!argType) {
286          return nil;
287      }
288      return objc_getClass(argType + 1);
289  }
290  
291  @end