Bill Sun
Bill Sun

Reputation: 65

could cast objc_msgsend to variable-length arguments function safely

suppose I got an object and one of its selector at runtime, I intended to call it safely, so I defined

#define objc_msgsend_va ((void (*)(id, SEL, ...))objc_msgsend)

#define Call_object(obj, sel, ...) objc_msgsend_va(obj, sel, ## __VA_ARGS__)

Could I Safely replace any method call with Call_object macro, or it could lead to some kind of crash.

This is to defined a macro to run arbitrary runtime method on iOS device, I could not explicit cast to correct function type for every method, so I used variable-length arguments.

The main consideration is the safety to use macro like this.

Upvotes: 1

Views: 284

Answers (1)

Richard J. Ross III
Richard J. Ross III

Reputation: 55573

Bottom Line:

Nope, this is definitely unsafe. It will work in a lot of common situations, but va_args is not the same as passing arguments directly to a function.

Please, please, PLEASE, go use NSInvocation. It solves about 99% of this for you, while being much safer. There may be edge-cases it doesn't solve (SSE/AVX vectors in parameter lists, for example), but it will be leaps and bounds ahead of what you could possibly hack together.

Let's break this down by architecture, and see where this may likely work.

Universal

These issues will appear everywhere, with no real solutions available.

  1. Floating points are always upsized into doubles when passed as va_args. See the C standard, section §6.5.2.2

  2. The same is true for shorts, and chars. They'll all be upsized too, which will cause much pain and suffering when passing it through. Technically (unlike for floats, as those are sometimes passed in special FPU registers), for integer types you can solve this by wrapping them in a single element struct. This will prevent them from being promoted. Please don't do this, though.

  3. Unions, or complicated structs structs. Additional alignment and padding and considerations are required. You're going to have to dive deep into compiler code if you want a stable solution to this.

  4. Things are EVEN more complicated if you start touching the return values, and you need to understand when structs get packed into registers for return values by the architecture (see a lot of previous discussions).

PowerPC

I'm not bothering with this one. If you still need to target these macOS devices, two things:

  1. I feel VERY bad for you.

  2. You likely have way more context than I do on this. Good luck, and godspeed.

x86

32-bit x86 is fairly sane. There's no complicated register-argument passing, off the top of my head, and I feel like this is probably fairly sane. The biggest thing, aside from the previously mentioned issues would be issues caused by the x87 FPU. Avoid floats, and you probably won't burn the world here.

x86-64

64-bit x86 is another beast altogether. You're gonna have to learn a lot about how the compiler assigns registers to arguments (and return values!) and of course, the new SSE registers for floating points.

ARMv6/ARMv7/ARMv7s (32-bit ARM)

I'm going to lump these together, there's no major differences from this perspective.

Like x86-64, you'll have to learn a bit about which registers go where in the stack if you want to be 100% safe, but the floating point passing is simpler here, if memory recalls.

Apple's ABI guide here would be your friend.

ARM64

This is where my knowledge is shakiest, and I apologize if any of this information is incorrect.

Most function calls are preformed entirely through registers, so all of our previous worries are still here. There's now new FPU registers you'll have to deal with for your floating points, but nothing insurmountable if you've gotten to this point.

Upvotes: 3

Related Questions