Files
RetroArch/apple_runtime.h
T
LibretroAdmin e5fc0a50e4 apple: the App Store builds compile without warnings, and their uploads stop failing on one bad core
Built the way Xcode builds them - the App Store floors (macOS 12,
iOS/tvOS 15), the projects' warnings, every translation unit - the
tree still warned:

- Delegate methods the SDK deprecates that an older OS still calls
  are no longer implemented in source.  apple_rt_add_method() gives
  the class each one at run time, from +initialize and only on the
  releases that call it: -locationManager:didChangeAuthorizationStatus:
  before macOS 11 / iOS 14 (-locationManagerDidChangeAuthorization:
  from there), -documentPicker:didPickDocumentAtURL: before iOS 11
  (the URL-array method from there), -shouldAutorotateTo-
  InterfaceOrientation: before iOS 6, and NSView's
  -layer:shouldInheritContentsScale:fromWindow: from 10.7.
  class_addMethod is looked up by name, as the 10.4 binary may not
  import it.  CocoaView no longer declares UIDocumentPickerDelegate,
  whose pre-11 SDKs require the method it now adds at run time, and
  -prefersPointerLocked is not built for tvOS, where it is
  unavailable.
- The open panel's file types, the companion's folder icon and the
  cores folder's open are -setAllowedContentTypes:, -iconForContent-
  Type: and -openURL: from macOS 11, the 10.x methods before it, by
  selector where they are not on every release.
- The alert's end selector is looked up by name, and on SDKs older
  than the selectors ui_cocoa.m names (10.6, 10.7, 10.9) they are
  declared in an unimplemented NSObject category, so @selector()
  stays a constant.
- size_t and 64-bit values narrowed without a cast in configuration.c,
  gl3.c, cdrom.c, media_detect_cd.c and vfs_implementation_cdrom.c;
  rchd_read_extent() rejects an extent past the last 32-bit frame
  instead of wrapping into the wrong track.

App Store uploads: one iOS or tvOS core without an unencrypted
LC_ENCRYPTION_INFO_64 - written by Apple's ld, not by other linkers -
fails the whole upload (ITMS-90125).  make-frameworks.sh leaves such a
core out of an App Store build with a warning naming it.  The
Fastfile's check for a build already in TestFlight review passed
Build.all a filter it does not take, so it always failed and the
upload went on to external distribution and stopped there; it asks
App Store Connect for the newest five builds of the platform with
their review submissions instead.
2026-10-07 09:38:29 +02:00

274 lines
11 KiB
Objective-C

/* RetroArch - A frontend for libretro.
* Copyright (C) 2011-2026 - Daniel De Matteis
*
* RetroArch is free software: you can redistribute it and/or modify it under the terms
* of the GNU General Public License as published by the Free Software Found-
* ation, either version 3 of the License, or (at your option) any later version.
*
* RetroArch is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with RetroArch.
* If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef __APPLE_RUNTIME_H
#define __APPLE_RUNTIME_H
/* Runtime replacement for @available().
*
* @available needs a clang new enough to know the syntax (Xcode 9+)
* and calls __isPlatformVersionAtLeast() every time it is evaluated.
* The helpers here compile on any toolchain down to the ancient ones
* building the legacy iOS/macOS targets, work on any OS version at
* runtime, and resolve the OS version exactly once per process -
* afterwards every check is a cached integer compare.
*/
#include <TargetConditionals.h>
#import <Foundation/Foundation.h>
#if TARGET_OS_IPHONE
#import <UIKit/UIKit.h>
#else
#include <sys/types.h>
#include <sys/sysctl.h>
#endif
/* API_AVAILABLE / API_UNAVAILABLE came with the iOS 11 / macOS 10.13
* SDKs. Against an older SDK, clang gets the same availability
* attribute they expand to - so code annotated as newer than the floor
* still compiles clean there - and other compilers get nothing. Up to
* four platforms per annotation, as the SDK's own macro takes. */
#ifndef API_AVAILABLE
#if defined(__clang__)
#define APPLE_RT_AV_macos(v) __attribute__((availability(macos,introduced=v)))
#define APPLE_RT_AV_macosx(v) __attribute__((availability(macos,introduced=v)))
#define APPLE_RT_AV_ios(v) __attribute__((availability(ios,introduced=v)))
#define APPLE_RT_AV_tvos(v) __attribute__((availability(tvos,introduced=v)))
#define APPLE_RT_AV_watchos(v) __attribute__((availability(watchos,introduced=v)))
#define APPLE_RT_AV_macCatalyst(v) __attribute__((availability(macCatalyst,introduced=v)))
#define APPLE_RT_AV1(a) APPLE_RT_AV_##a
#define APPLE_RT_AV2(a, b) APPLE_RT_AV1(a) APPLE_RT_AV1(b)
#define APPLE_RT_AV3(a, b, c) APPLE_RT_AV1(a) APPLE_RT_AV2(b, c)
#define APPLE_RT_AV4(a, b, c, d) APPLE_RT_AV1(a) APPLE_RT_AV3(b, c, d)
#define APPLE_RT_AV_PICK(_1, _2, _3, _4, name, ...) name
#define API_AVAILABLE(...) \
APPLE_RT_AV_PICK(__VA_ARGS__, APPLE_RT_AV4, APPLE_RT_AV3, \
APPLE_RT_AV2, APPLE_RT_AV1, _unused)(__VA_ARGS__)
#else
#define API_AVAILABLE(...)
#endif
#endif
#ifndef API_UNAVAILABLE
#define API_UNAVAILABLE(...)
#endif
/* Encodes a version as major * 10000 + minor * 100 + patch */
#define APPLE_RUNTIME_VER(maj, min, pat) ((maj) * 10000 + (min) * 100 + (pat))
/* Returns the running OS version, APPLE_RUNTIME_VER-encoded.
* Resolved once; the benign race on the static is idempotent. */
/* unused: a unit may include this header for the macros alone. */
static inline __attribute__((unused)) int apple_runtime_os_version(void)
{
static int ver = -1;
if (ver == -1)
{
int part[3] = {0, 0, 0};
int i = 0;
const char *s = NULL;
#if TARGET_OS_IPHONE
/* Never shimmed, works on any iOS/tvOS version */
s = [[[UIDevice currentDevice] systemVersion] UTF8String];
#else
/* kern.osproductversion (10.13.4+) reports the real OS version;
* the NSProcessInfo APIs are subject to the 10.16 compatibility
* shim for binaries linked against pre-11.0 SDKs. */
char buf[64];
size_t len = sizeof(buf);
if (sysctlbyname("kern.osproductversion", buf, &len, NULL, 0) == 0)
s = buf;
else
{
/* Pre-10.13.4 fallback: "Version 10.9.5 (Build 13F34)" -
* skip ahead to the first digit */
s = [[[NSProcessInfo processInfo] operatingSystemVersionString]
UTF8String];
while (s && *s && (*s < '0' || *s > '9'))
s++;
}
#endif
if (s)
{
for (; *s && i < 3; s++)
{
if (*s >= '0' && *s <= '9')
part[i] = part[i] * 10 + (*s - '0');
else if (*s == '.')
i++;
else
break;
}
}
ver = APPLE_RUNTIME_VER(part[0], part[1], part[2]);
#if !TARGET_OS_IPHONE
/* Belt and braces: if the shim still got to us, 10.16 == 11.0 */
if (ver == APPLE_RUNTIME_VER(10, 16, 0))
ver = APPLE_RUNTIME_VER(11, 0, 0);
#endif
}
return ver;
}
/* Drop-in replacement for if (@available(macOS a, iOS b, tvOS c, *)).
* Pass 0 for platforms the original check did not list: @available
* returns true on platforms not named (that is the '*'), and any
* real OS version is >= 0. */
#if defined(TARGET_OS_TV) && TARGET_OS_TV
#define apple_runtime_available(macos_v, ios_v, tvos_v) \
(apple_runtime_os_version() >= (tvos_v))
#elif TARGET_OS_IPHONE
#define apple_runtime_available(macos_v, ios_v, tvos_v) \
(apple_runtime_os_version() >= (ios_v))
#else
#define apple_runtime_available(macos_v, ios_v, tvos_v) \
(apple_runtime_os_version() >= (macos_v))
#endif
/* Message sends the compiler does not see.
*
* One binary runs from Mac OS X 10.4 / iOS 4.0 up to the current
* release, so an API newer than the deployment floor is called only
* behind an apple_runtime_available() check. Written as a plain
* message expression that call still trips -Wunguarded-availability,
* and on an SDK too old to declare the method it does not compile at
* all. These macros send the message through objc_msgSend with a
* selector looked up by name, which is the very call a message
* expression compiles to: nothing is checked, nothing is slower, and
* being macros there is no inlining for a compiler to decline.
*
* Cache the SEL in a static where a send sits on a hot path;
* sel_registerName is a table lookup, @selector() is free but needs
* the SDK to declare the method. */
#include <dlfcn.h>
/* #import: the 10.5 and older SDKs' objc/message.h has no include guard
* and is written to be imported, as every file here that also pulls it
* in does. */
#import <objc/runtime.h>
#import <objc/message.h>
#ifndef __has_feature
#define __has_feature(x) 0
#endif
#define apple_rt_send_void(obj, sel) \
((void (*)(id, SEL))objc_msgSend)((id)(obj), (sel))
#define apple_rt_send_bool(obj, sel, v) \
((void (*)(id, SEL, BOOL))objc_msgSend)((id)(obj), (sel), (BOOL)(v))
#define apple_rt_send_id(obj, sel, v) \
((void (*)(id, SEL, id))objc_msgSend)((id)(obj), (sel), (id)(v))
#define apple_rt_get_id(obj, sel) \
((id (*)(id, SEL))objc_msgSend)((id)(obj), (sel))
#define apple_rt_get_long(obj, sel) \
((long (*)(id, SEL))objc_msgSend)((id)(obj), (sel))
#define apple_rt_send_long(obj, sel, v) \
((void (*)(id, SEL, long))objc_msgSend)((id)(obj), (sel), (long)(v))
/* BOOL is a char on most ABIs: read through a BOOL-returning cast, never
* a wider one, or the bits above it are whatever the register held. */
#define apple_rt_get_bool(obj, sel) \
((BOOL (*)(id, SEL))objc_msgSend)((id)(obj), (sel))
/* An int-typed result (an NS_ENUM(int, ...) such as CLAuthorizationStatus):
* read as int, for the same reason. */
#define apple_rt_get_int(obj, sel) \
((int (*)(id, SEL))objc_msgSend)((id)(obj), (sel))
#define apple_rt_get_id_arg(obj, sel, a) \
((id (*)(id, SEL, id))objc_msgSend)((id)(obj), (sel), (id)(a))
/* A struct the ABI returns in memory: every target but arm64 does that
* for anything over 16 bytes (armv7, i386 and PPC for far less), and
* goes through objc_msgSend_stret; arm64 has no _stret entry at all.
* Only for structs larger than 16 bytes - a smaller one comes back in
* registers on x86_64, where _stret would be wrong. */
#if defined(__arm64__) || defined(__aarch64__)
#define apple_rt_get_large_struct(T, obj, sel) \
(((T (*)(id, SEL))objc_msgSend)((id)(obj), (sel)))
#else
#define apple_rt_get_large_struct(T, obj, sel) \
(((T (*)(id, SEL))objc_msgSend_stret)((id)(obj), (sel)))
#endif
/* A struct of 9 to 16 bytes (a CAFrameRateRange, an NSPoint of
* doubles): x86_64 and arm64 return it in registers through
* objc_msgSend, armv7, i386 and PPC in memory through _stret. */
#if defined(__arm64__) || defined(__aarch64__) || defined(__x86_64__)
#define apple_rt_get_mid_struct(T, obj, sel) \
(((T (*)(id, SEL))objc_msgSend)((id)(obj), (sel)))
#else
#define apple_rt_get_mid_struct(T, obj, sel) \
(((T (*)(id, SEL))objc_msgSend_stret)((id)(obj), (sel)))
#endif
/* An -init... sent by selector to the result of +alloc. The cast
* carries the init family's ownership - the receiver is consumed and
* the result comes back +1 - so ARC emits exactly what it does for
* [[cls alloc] init...]; under MRC that is what the call does anyway. */
#if __has_feature(objc_arc)
#define APPLE_RT_CONSUMED __attribute__((ns_consumed))
#define APPLE_RT_RETURNS_RETAINED __attribute__((ns_returns_retained))
#else
#define APPLE_RT_CONSUMED
#define APPLE_RT_RETURNS_RETAINED
#endif
typedef id (*apple_rt_init_id_fn)(APPLE_RT_CONSUMED id, SEL, id)
APPLE_RT_RETURNS_RETAINED;
typedef id (*apple_rt_init_id_long_fn)(APPLE_RT_CONSUMED id, SEL, id, long)
APPLE_RT_RETURNS_RETAINED;
#define apple_rt_init_id(obj, sel, a) \
((apple_rt_init_id_fn)objc_msgSend)((obj), (sel), (id)(a))
#define apple_rt_init_id_long(obj, sel, a, l) \
((apple_rt_init_id_long_fn)objc_msgSend)((obj), (sel), (id)(a), (long)(l))
/* i386 returns floating point on the x87 stack and needs the _fpret
* entry; every other ABI returns it in a register through objc_msgSend. */
#if defined(__i386__)
#define apple_rt_get_double(obj, sel) \
((double (*)(id, SEL))objc_msgSend_fpret)((id)(obj), (sel))
#else
#define apple_rt_get_double(obj, sel) \
((double (*)(id, SEL))objc_msgSend)((id)(obj), (sel))
#endif
/* A class by name, nil when this OS (or this binary) does not have it. */
#define apple_rt_class(name) ((id)objc_getClass(name))
/* Gives a class a method at run time. For a delegate method the SDK
* marks deprecated or unavailable that an older OS still calls: the
* class answers it only on the releases that need it, done once from
* +initialize, and implementing it does not warn. @fn is a C function
* taking (id self, SEL _cmd, ...) and @types its Objective-C type
* encoding, which must outlive the call. class_addMethod is 10.5 and
* the 10.4 binary may not import it at all, so it is found by name; on
* 10.4 nothing is added. */
#define apple_rt_add_method(cls, name, fn, types) \
do { \
BOOL (*add_)(Class, SEL, IMP, const char *) = \
(BOOL (*)(Class, SEL, IMP, const char *)) \
dlsym(RTLD_DEFAULT, "class_addMethod"); \
if (add_) \
add_((Class)(cls), sel_registerName(name), (IMP)(fn), (types)); \
} while (0)
/* The address of an exported object constant (an NSString * such as
* AVCaptureDeviceTypeExternal) by name, NULL when this OS does not
* export it; apple_rt_obj_at reads the object, which stays the
* framework's own. */
#define apple_rt_constant_addr(name) ((void **)dlsym(RTLD_DEFAULT, (name)))
#if __has_feature(objc_arc)
#define apple_rt_obj_at(p) ((__bridge id)*(p))
#else
#define apple_rt_obj_at(p) ((id)*(p))
#endif
#endif /* __APPLE_RUNTIME_H */