How to deduce from NSMethodSignature that a struct argument is passed by pointer?

How to deduce from NSMethodSignature that a struct argument is passed by pointer?

Specifically on ARM.

For example if I have:

@protocol TestProtocol <NSObject>
- (void)time:(CMTime)time;
- (void)rect:(CGRect)point;
@end

And then I do:

  struct objc_method_description methodDescription1 =
    protocol_getMethodDescription(@protocol(TestProtocol), @selector(time:), YES, YES);
  struct objc_method_description methodDescription2 =
    protocol_getMethodDescription(@protocol(TestProtocol), @selector(rect:), YES, YES);

  NSMethodSignature *sig1 = [NSMethodSignature signatureWithObjCTypes:methodDescription1.types];
  NSMethodSignature *sig2 = [NSMethodSignature signatureWithObjCTypes:methodDescription2.types];

  const char *arg1 = [sig1 getArgumentTypeAtIndex:2];
  const char *arg2 = [sig2 getArgumentTypeAtIndex:2];

  NSLog(@"%s %s", methodDescription1.types, arg1);
  NSLog(@"%s %s", methodDescription2.types, arg2);

The output is:

v40@0:8{?=qiIq}16 {?=qiIq}
v48@0:8{CGRect={CGPoint=dd}{CGSize=dd}}16 {CGRect={CGPoint=dd}{CGSize=dd}}

Both look similar, no indication that CMTime will be actually passed as a pointer.

But when I print the debug description:

  NSLog(@"%@", [sig1 debugDescription]);
  NSLog(@"%@", [sig2 debugDescription]);

The first prints:

  ...
  argument 2: -------- -------- -------- --------
        type encoding (^) '^{?=qiIq}'
        flags {isPointer}
  ...

While the second prints:

  ...
  argument 2: -------- -------- -------- --------
        type encoding ({) '{CGRect={CGPoint=dd}{CGSize=dd}}'
        flags {isStruct}
  ...

So this information is indeed stored in the method signature, but how do I retrieve it without parsing the debug description?

Are there rules I can use to deduce this myself? I tried to experiment with different structs but it is hard to spot a pattern.

Replies

Are there rules I can use to deduce this myself?

I think this is fallout from the standard Arm calling conventions. Writing ARM64 Code for Apple Platforms has a pointer to the Arm doc for this (my references are from ARM IHI 0055B, which I just downloaded). Section 4.3.5.1 of that doc defines the term Homogeneous Floating-point Aggregate (HFA). If I’m reading this correctly, a CGRect qualifies as an HFA, and thus it’s passed in registers per 5.4.2.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

Add a Comment

I still curious if I can get this info from the runtime without the need to implement these rules myself.

I couldn’t find a way to get that info via the NSMethodSignature public API. The debugDescription property is dumping internal state and not all of that internal state is accessible via the public API [1].

Why do you need this info?

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

[1] Curiously, while rummaging around in the code I stumbled across various Arm-specific internal routines with HFA in the name.

  • Thank you for the effort. I am using OCMock mocking library in tests and recently started running our test suit on arm64. On this arch the library cause stack corruption with some of our tests. How? When a stub is created, it stores the NSInvocation. Instead of retaining it's arguments with the [NSInvocation retianArguments] they decided to implement their own retaining method The retaining method does not handle a situation when a struct needs to be copied from the stack, probably because struct arguments becoming pointers was not a thing in x86_64. I want to fix this by storing the argument somewhere and then using the stored argument when matching the invocation against an actual call. For this I need to identify arguments which needs to be copied (or copy everything, which I will probably do if I don't find a solution).

Add a Comment

I want to fix this by …

I don’t think you’re going to be able to do that without architecture-specific code. The point of NSInvocation is that it’s supposed to isolate you from this architecture-specific stuff. However, NSInvocation is very much a black box. It does what it does well, but if that doesn’t meet your needs, and so you need to look inside the box, it doesn’t provide any infrastructure for helping with the architecture-specific stuff.

Instead of retaining it's arguments with the -retainArguments they decided to implement their own retaining method

Do you know why?

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

  • Their documentation:

    Don't do a regular -retainArguments on the invocation that we use for matching. NSInvocation effectively does an strcpy on char* arguments which messes up matching them literally and blows up with anyPointer (in strlen since it's not actually a C string). Also on the off-chance that anInvocation contains self as an argument, -retainArguments would create a retain cycle.

    Note that anyPointer is an OCMock specific term.

Add a Comment