xamarin-macios/runtime/runtime.m

2933 строки
90 KiB
Objective-C

/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
/*
* Authors: Rolf Bjarne Kvinge
*
* Copyright (C) 2014 Xamarin Inc. (www.xamarin.com)
*
*/
#include <pthread.h>
#include <objc/runtime.h>
#include <sys/stat.h>
#include <dlfcn.h>
#include "product.h"
#include "shared.h"
#include "delegates.h"
#include "runtime-internal.h"
#include "xamarin/xamarin.h"
#if defined (DEBUG)
//extern BOOL NSZombieEnabled;
#endif
/*
* These are values that can be configured in xamarin_setup.
*
* The defaults values here have been chosen to minimize
* the configuration required for the simulator (in particular
* the simlauncher binaries).
*/
#if DEBUG
bool xamarin_gc_pump = false;
#endif
#if MONOMAC
// FIXME: implement release mode for monomac.
bool xamarin_debug_mode = true;
bool xamarin_mac_hybrid_aot = false;
bool xamarin_mac_modern = false;
#else
bool xamarin_debug_mode = false;
#endif
bool xamarin_disable_lldb_attach = false;
bool xamarin_disable_omit_fp = false;
#if DEBUG
bool xamarin_init_mono_debug = true;
#else
bool xamarin_init_mono_debug = false;
#endif
int xamarin_log_level = 0;
const char *xamarin_executable_name = NULL;
#if MONOMAC
NSString * xamarin_custom_bundle_name = nil;
bool xamarin_is_mkbundle = false;
char *xamarin_entry_assembly_path = NULL;
#endif
#if defined (__i386__)
const char *xamarin_arch_name = "i386";
#elif defined (__x86_64__)
const char *xamarin_arch_name = "x86_64";
#else
const char *xamarin_arch_name = NULL;
#endif
#if TARGET_OS_WATCH
bool xamarin_is_gc_coop = true;
#else
bool xamarin_is_gc_coop = false;
#endif
enum MarshalObjectiveCExceptionMode xamarin_marshal_objectivec_exception_mode = MarshalObjectiveCExceptionModeDefault;
enum MarshalManagedExceptionMode xamarin_marshal_managed_exception_mode = MarshalManagedExceptionModeDefault;
enum XamarinLaunchMode xamarin_launch_mode = XamarinLaunchModeApp;
bool xamarin_supports_dynamic_registration = true;
/* Callbacks */
xamarin_setup_callback xamarin_setup = NULL;
xamarin_register_module_callback xamarin_register_modules = NULL;
xamarin_register_assemblies_callback xamarin_register_assemblies = NULL;
xamarin_extension_main_callback xamarin_extension_main = NULL;
/* Local variable */
static MonoImage *platform_image;
static MonoClass *inativeobject_class;
static MonoClass *nsobject_class;
static MonoClass *nsvalue_class;
static MonoClass *nsnumber_class;
static MonoClass *nsstring_class;
static MonoClass *runtime_class;
static pthread_mutex_t framework_peer_release_lock;
static MonoGHashTable *xamarin_wrapper_hash;
static bool initialize_started = FALSE;
#include "delegates.inc"
/* Keep Trampolines, InitializationFlags and InitializationOptions in sync with Runtime.cs */
struct Trampolines {
void* tramp;
void* stret_tramp;
void* fpret_single_tramp;
void* fpret_double_tramp;
void* release_tramp;
void* retain_tramp;
void* static_tramp;
void* ctor_tramp;
void* x86_double_abi_stret_tramp;
void* static_fpret_single_tramp;
void* static_fpret_double_tramp;
void* static_stret_tramp;
void* x86_double_abi_static_stret_tramp;
void* long_tramp;
void* static_long_tramp;
#if MONOMAC
void* copy_with_zone1;
void* copy_with_zone2;
#endif
void* get_gchandle_tramp;
void* set_gchandle_tramp;
};
enum InitializationFlags : int {
InitializationFlagsIsPartialStaticRegistrar = 0x01,
/* unused = 0x02,*/
InitializationFlagsDynamicRegistrar = 0x04,
/* unused = 0x08,*/
InitializationFlagsIsSimulator = 0x10,
};
struct InitializationOptions {
int size; // the size of this structure. This is used for version checking.
enum InitializationFlags flags;
struct Delegates* Delegates;
struct Trampolines* Trampolines;
struct MTRegistrationMap* RegistrationData;
enum MarshalObjectiveCExceptionMode MarshalObjectiveCExceptionMode;
enum MarshalManagedExceptionMode MarshalManagedExceptionMode;
#if MONOMAC
enum XamarinLaunchMode LaunchMode;
const char *EntryAssemblyPath;
#endif
struct AssemblyLocations* AssemblyLocations;
};
static struct Trampolines trampolines = {
(void *) &xamarin_trampoline,
(void *) &xamarin_stret_trampoline,
(void *) &xamarin_fpret_single_trampoline,
(void *) &xamarin_fpret_double_trampoline,
(void *) &xamarin_release_trampoline,
(void *) &xamarin_retain_trampoline,
(void *) &xamarin_static_trampoline,
(void *) &xamarin_ctor_trampoline,
#if defined (__i386__)
(void *) &xamarin_x86_double_abi_stret_trampoline,
#else
NULL,
#endif
(void *) &xamarin_static_fpret_single_trampoline,
(void *) &xamarin_static_fpret_double_trampoline,
(void *) &xamarin_static_stret_trampoline,
#if defined (__i386__)
(void *) &xamarin_static_x86_double_abi_stret_trampoline,
#else
NULL,
#endif
(void *) &xamarin_longret_trampoline,
(void *) &xamarin_static_longret_trampoline,
#if MONOMAC
(void *) &xamarin_copyWithZone_trampoline1,
(void *) &xamarin_copyWithZone_trampoline2,
#endif
(void *) &xamarin_get_gchandle_trampoline,
(void *) &xamarin_set_gchandle_trampoline,
};
static struct InitializationOptions options = { 0 };
struct Managed_NSObject {
MonoObject obj;
id handle;
void *class_handle;
uint8_t flags;
};
static void
xamarin_add_internal_call (const char *name, const void *method)
{
/* COOP: With cooperative GC, icalls will run, like managed methods,
* in GC Unsafe mode, avoiding a thread state transition. In return
* the icalls must guarantee that they won't block, or run indefinitely
* without a safepoint, by manually performing a transition to GC Safe
* mode. With backward-compatible hybrid GC, icalls run in GC Safe
* mode and the Mono API functions take care of thread state
* transitions, so don't need to perform GC thread state transitions
* themselves.
*
*/
if (xamarin_is_gc_coop)
mono_dangerous_add_raw_internal_call (name, method);
else
mono_add_internal_call (name, method);
}
id
xamarin_get_nsobject_handle (MonoObject *obj)
{
// COOP: Reading managed data, must be in UNSAFE mode
MONO_ASSERT_GC_UNSAFE;
struct Managed_NSObject *mobj = (struct Managed_NSObject *) obj;
return mobj->handle;
}
void
xamarin_set_nsobject_handle (MonoObject *obj, id handle)
{
// COOP: Writing managed data, must be in UNSAFE mode
MONO_ASSERT_GC_UNSAFE;
struct Managed_NSObject *mobj = (struct Managed_NSObject *) obj;
mobj->handle = handle;
}
uint8_t
xamarin_get_nsobject_flags (MonoObject *obj)
{
// COOP: Reading managed data, must be in UNSAFE mode
MONO_ASSERT_GC_UNSAFE;
struct Managed_NSObject *mobj = (struct Managed_NSObject *) obj;
return mobj->flags;
}
void
xamarin_set_nsobject_flags (MonoObject *obj, uint8_t flags)
{
// COOP: Writing managed data, must be in UNSAFE mode
MONO_ASSERT_GC_UNSAFE;
struct Managed_NSObject *mobj = (struct Managed_NSObject *) obj;
mobj->flags = flags;
}
MonoType *
xamarin_get_parameter_type (MonoMethod *managed_method, int index)
{
// COOP: Reading managed data, must be in UNSAFE mode
MONO_ASSERT_GC_UNSAFE;
MonoMethodSignature *msig = mono_method_signature (managed_method);
void *iter = NULL;
MonoType *p = NULL;
if (index == -1) {
p = mono_signature_get_return_type (msig);
} else {
for (int i = 0; i < index + 1; i++)
p = mono_signature_get_params (msig, &iter);
}
return p;
}
MonoObject *
xamarin_get_nsobject_with_type_for_ptr (id self, bool owns, MonoType* type, guint32 *exception_gchandle)
{
// COOP: Reading managed data, must be in UNSAFE mode
MONO_ASSERT_GC_UNSAFE;
int32_t created;
return xamarin_get_nsobject_with_type_for_ptr_created (self, owns, type, &created, exception_gchandle);
}
MonoObject *
xamarin_get_nsobject_with_type_for_ptr_created (id self, bool owns, MonoType *type, int32_t *created, guint32 *exception_gchandle)
{
// COOP: Reading managed data, must be in UNSAFE mode
MONO_ASSERT_GC_UNSAFE;
MonoObject *mobj = NULL;
uint32_t gchandle = 0;
*created = false;
if (self == NULL)
return NULL;
gchandle = xamarin_get_gchandle (self);
if (gchandle != 0) {
mobj = mono_gchandle_get_target (gchandle);
if (mono_object_isinst (mobj, mono_class_from_mono_type (type)) != NULL)
return mobj;
}
return xamarin_get_nsobject_with_type (self, mono_type_get_object (mono_domain_get (), type), created, exception_gchandle);
}
MonoObject *
xamarin_get_managed_object_for_ptr_fast (id self, guint32 *exception_gchandle)
{
// COOP: Reading managed data, must be in UNSAFE mode
MONO_ASSERT_GC_UNSAFE;
MonoObject *mobj = NULL;
uint32_t gchandle = 0;
gchandle = xamarin_get_gchandle (self);
if (gchandle == 0) {
mobj = xamarin_try_get_or_construct_nsobject (self, exception_gchandle);
} else {
mobj = mono_gchandle_get_target (gchandle);
#if DEBUG
if (self != xamarin_get_nsobject_handle (mobj)) {
xamarin_assertion_message ("Internal consistency error, please file a bug (https://github.com/xamarin/xamarin-macios/issues/new). Additional data: found managed object %p=%p (%s) in native object %p (%s).\n",
mobj, xamarin_get_nsobject_handle (mobj), xamarin_class_get_full_name (mono_object_get_class (mobj), exception_gchandle), self, object_getClassName (self));
}
#endif
}
return mobj;
}
void xamarin_framework_peer_lock ()
{
// COOP: CHECK
MONO_ASSERT_GC_UNSAFE;
MONO_ENTER_GC_SAFE;
pthread_mutex_lock (&framework_peer_release_lock);
MONO_EXIT_GC_SAFE;
}
void xamarin_framework_peer_unlock ()
{
pthread_mutex_unlock (&framework_peer_release_lock);
}
MonoClass *
xamarin_get_nsvalue_class ()
{
if (nsvalue_class == NULL)
xamarin_assertion_message ("Internal consistency error, please file a bug (https://github.com/xamarin/xamarin-macios/issues/new). Additional data: can't get the %s class because it's been linked away.\n", "NSValue");
return nsvalue_class;
}
MonoClass *
xamarin_get_nsnumber_class ()
{
if (nsnumber_class == NULL)
xamarin_assertion_message ("Internal consistency error, please file a bug (https://github.com/xamarin/xamarin-macios/issues/new). Additional data: can't get the %s class because it's been linked away.\n", "NSNumber");
return nsnumber_class;
}
bool
xamarin_is_class_nsobject (MonoClass *cls)
{
// COOP: Reading managed data, must be in UNSAFE mode
MONO_ASSERT_GC_UNSAFE;
return mono_class_is_subclass_of (cls, nsobject_class, false);
}
bool
xamarin_is_class_inativeobject (MonoClass *cls)
{
// COOP: Reading managed data, must be in UNSAFE mode
MONO_ASSERT_GC_UNSAFE;
return mono_class_is_subclass_of (cls, inativeobject_class, true);
}
bool
xamarin_is_class_array (MonoClass *cls)
{
// COOP: Reading managed data, must be in UNSAFE mode
MONO_ASSERT_GC_UNSAFE;
return mono_class_is_subclass_of (cls, mono_get_array_class (), false);
}
bool
xamarin_is_class_nsnumber (MonoClass *cls)
{
// COOP: Reading managed data, must be in UNSAFE mode
MONO_ASSERT_GC_UNSAFE;
if (nsnumber_class == NULL)
return false;
return mono_class_is_subclass_of (cls, nsnumber_class, false);
}
bool
xamarin_is_class_nsvalue (MonoClass *cls)
{
// COOP: Reading managed data, must be in UNSAFE mode
MONO_ASSERT_GC_UNSAFE;
if (nsvalue_class == NULL)
return false;
return mono_class_is_subclass_of (cls, nsvalue_class, false);
}
bool
xamarin_is_class_nsstring (MonoClass *cls)
{
// COOP: Reading managed data, must be in UNSAFE mode
MONO_ASSERT_GC_UNSAFE;
if (nsstring_class == NULL)
return false;
return mono_class_is_subclass_of (cls, nsstring_class, false);
}
// Returns if a MonoClass is nullable.
// Will also return the element type (it the type is nullable, and if out pointer is not NULL).
bool
xamarin_is_class_nullable (MonoClass *cls, MonoClass **element_type, guint32 *exception_gchandle)
{
#ifdef DYNAMIC_MONO_RUNTIME
// mono_class_is_nullable/mono_class_get_nullable_param are private
// functions, and as such we can't call find them in libmono.dylib. In
// this case we manually call a managed function to do the work for us (we
// don't use the normal delegate mechanism, because how it's currently
// implemented it would inflict size costs on all platforms, not just
// Xamarin.Mac).
if (!mono_class_is_nullable_exists () || !mono_class_get_nullable_param_exists ()) {
static MonoMethod *get_nullable_type = NULL;
if (get_nullable_type == NULL)
get_nullable_type = mono_class_get_method_from_name (runtime_class, "GetNullableType", 1);
GCHandle type_handle = xamarin_gchandle_new ((MonoObject *) mono_type_get_object (mono_domain_get (), mono_class_get_type (cls)), false);
void *args [1] { type_handle };
MonoObject *exc = NULL;
MonoReflectionType *nullable_type = (MonoReflectionType *) xamarin_gchandle_unwrap (mono_runtime_invoke (get_nullable_type, NULL, args, &exc));
xamarin_gchandle_free (type_handle);
if (exc != NULL) {
*exception_gchandle = mono_gchandle_new (exc, FALSE);
return false;
}
if (element_type != NULL && nullable_type != NULL)
*element_type = mono_class_from_mono_type (mono_reflection_type_get_type (nullable_type));
return nullable_type != NULL;
}
#endif
bool rv = mono_class_is_nullable (cls);
if (rv && element_type)
*element_type = mono_class_get_nullable_param (cls);
return rv;
}
MonoClass *
xamarin_get_nullable_type (MonoClass *cls, guint32 *exception_gchandle)
{
MonoClass *rv = NULL;
xamarin_is_class_nullable (cls, &rv, exception_gchandle);
return rv;
}
#define MANAGED_REF_BIT (1u << 31u)
#define GCHANDLE_WEAK (1u << 30u)
#define GCHANDLE_MASK (MANAGED_REF_BIT | GCHANDLE_WEAK)
// The XamarinExtendedObject protocol is just to avoid a
// compiler warning (no 'xamarinGetGChandle' selector found).
@protocol XamarinExtendedObject
-(uint32_t) xamarinGetGCHandle;
-(void) xamarinSetGCHandle: (uint32_t) gc_handle;
@end
static inline uint32_t
get_raw_gchandle_safe (id self)
{
// COOP: we call a selector, and that must only be done in SAFE mode.
MONO_ASSERT_GC_SAFE_OR_DETACHED;
id<XamarinExtendedObject> xself = self;
return (uint32_t) [xself xamarinGetGCHandle];
}
static inline uint32_t
get_raw_gchandle (id self)
{
// COOP: we call a selector, and that must only be done in SAFE mode.
MONO_ASSERT_GC_UNSAFE;
uint32_t rv;
MONO_ENTER_GC_SAFE;
id<XamarinExtendedObject> xself = self;
rv = (uint32_t) [xself xamarinGetGCHandle];
MONO_EXIT_GC_SAFE;
return rv;
}
static inline void
set_raw_gchandle (id self, uint32_t gc_handle)
{
// COOP: we call a selector, and that must only be done in SAFE mode.
MONO_ASSERT_GC_UNSAFE;
MONO_ENTER_GC_SAFE;
id<XamarinExtendedObject> xself = self;
[xself xamarinSetGCHandle: gc_handle];
MONO_EXIT_GC_SAFE;
}
static inline uint32_t
get_gchandle (id self)
{
// COOP: does not access managed memory: any mode
return get_raw_gchandle (self) & ~GCHANDLE_MASK;
}
uint32_t
xamarin_get_gchandle (id self)
{
// COOP: does not access managed memory: any mode
return get_gchandle (self);
}
uint32_t
xamarin_get_gchandle_with_flags (id self)
{
// COOP: does not access managed memory: any mode
return get_raw_gchandle (self);
}
bool
xamarin_has_managed_ref (id self)
{
// COOP: get_raw_gchandle requires UNSAFE mode, so this function requires it too.
return get_raw_gchandle (self) & MANAGED_REF_BIT;
}
bool
xamarin_has_managed_ref_safe (id self)
{
// COOP: variation of xamarin_has_managed_ref for SAFE mode.
return get_raw_gchandle_safe (self) & MANAGED_REF_BIT;
}
MonoException *
xamarin_create_exception (const char *msg)
{
// COOP: calls mono, needs to be in UNSAFE mode.
MONO_ASSERT_GC_UNSAFE;
return (MonoException *) mono_exception_from_name_msg (mono_get_corlib (), "System", "Exception", msg);
}
typedef struct {
MonoObject object;
MonoMethod *method;
MonoString *name;
MonoReflectionType *reftype;
} PublicMonoReflectionMethod;
MonoMethod *
xamarin_get_reflection_method_method (MonoReflectionMethod *method)
{
// COOP: Reads managed memory, needs to be in UNSAFE mode
MONO_ASSERT_GC_UNSAFE;
PublicMonoReflectionMethod *rm = (PublicMonoReflectionMethod *) method;
return rm->method;
}
id
xamarin_get_handle (MonoObject *obj, guint32 *exception_gchandle)
{
// COOP: Reads managed memory, needs to be in UNSAFE mode
MONO_ASSERT_GC_UNSAFE;
MonoClass *klass;
id rv = nil;
if (obj == NULL)
return nil;
klass = mono_object_get_class (obj);
if (xamarin_is_class_nsobject (klass)) {
rv = xamarin_get_nsobject_handle (obj);
} else if (xamarin_is_class_inativeobject (klass)) {
rv = xamarin_get_handle_for_inativeobject (obj, exception_gchandle);
} else {
char *msg = xamarin_strdup_printf ("Unable to marshal from %s.%s to an Objective-C object. "
"The managed class must either inherit from NSObject or implement INativeObject.",
mono_class_get_namespace (klass), mono_class_get_name (klass));
MonoException *exc = mono_get_exception_execution_engine (msg);
xamarin_free (msg);
*exception_gchandle = mono_gchandle_new ((MonoObject *) exc, FALSE);
}
return rv;
}
#if DEBUG
static void
verify_cast (MonoClass *to, MonoObject *obj, Class from_class, SEL sel, MonoMethod *method, guint32 *exception_gchandle)
{
// COOP: Reads managed memory, needs to be in UNSAFE mode
MONO_ASSERT_GC_UNSAFE;
if (!to)
return;
if (mono_object_isinst (obj, to) == NULL) {
MonoClass *from = mono_object_get_class (obj);
char *method_full_name = mono_method_full_name (method, TRUE);
char *from_name = xamarin_class_get_full_name (from, exception_gchandle);
char *to_name = xamarin_class_get_full_name (to, exception_gchandle);
char *msg = xamarin_strdup_printf ("Unable to cast object of type '%s' (Objective-C type: '%s') to type '%s'.\n"
"Additional information:\n"
"\tSelector: %s\n"
"\tMethod: %s\n", from_name, class_getName(from_class), to_name, sel_getName (sel), method_full_name);
MonoException *mono_ex = mono_exception_from_name_msg (mono_get_corlib (), "System", "InvalidCastException", msg);
mono_free (from_name);
mono_free (to_name);
xamarin_free (msg);
mono_free (method_full_name);
*exception_gchandle = mono_gchandle_new ((MonoObject *) mono_ex, FALSE);
}
}
#endif
void
xamarin_check_for_gced_object (MonoObject *obj, SEL sel, id self, MonoMethod *method, guint32 *exception_gchandle)
{
// COOP: Reads managed memory, needs to be in UNSAFE mode
MONO_ASSERT_GC_UNSAFE;
if (obj != NULL) {
#if DEBUG
verify_cast (mono_method_get_class (method), obj, [self class], sel, method, exception_gchandle);
#endif
return;
}
const char *m = "Failed to marshal the Objective-C object %p (type: %s). "
"Could not find an existing managed instance for this object, "
"nor was it possible to create a new managed instance "
"(because the type '%s' does not have a constructor that takes one IntPtr argument).\n"
"Additional information:\n"
"\tSelector: %s\n"
"\tMethod: %s\n";
char *method_full_name = mono_method_full_name (method, TRUE);
char *type_name = xamarin_lookup_managed_type_name ([self class], exception_gchandle);
if (*exception_gchandle == 0) {
char *msg = xamarin_strdup_printf (m, self, object_getClassName (self), type_name, sel_getName (sel), method_full_name);
guint32 ex_handle = (guint32) xamarin_create_runtime_exception (8027, msg, exception_gchandle);
xamarin_free (msg);
if (*exception_gchandle == 0)
*exception_gchandle = ex_handle;
}
mono_free (type_name);
mono_free (method_full_name);
}
#if DEBUG
//
// We can't do the type-checks below correctly until we support multiple managed peers for each
// native objects. The problem is that Objective-C can fake multiple inheritance by
// overriding isKindOfClass: A test case can be seen in bug #23421: A parameter in a
// callback from Objective-C is typed as 'NSUrlSessionDownloadTask'. Objective-C may give us an instance
// of an internal class __NSCFBackgroundDownloadTask, which doesn't inherit from NSUrlSessionDownloadTask
// (it inherits from NSUrlSessionTask, which is a superclass of NSUrlSessionDownloadTask). In Objective-C
// the __NSCFBackgroundDownloadTask class gets away with this because it overrides isKindOfClass: to return
// YES for NSUrlSessionDownloadTask. We can't rely on isKindOfClass: when creating a managed peer for
// the native object, because we may already have an instance of the "correct" type (if we
// had support for multiple managed peers, we could just create a new instance of the expected type).
//
void
xamarin_verify_parameter (MonoObject *obj, SEL sel, id self, id arg, unsigned long index, MonoClass *expected, MonoMethod *method)
{
// COOP: Reads managed memory, needs to be in UNSAFE mode
MONO_ASSERT_GC_UNSAFE;
// if (arg == NULL)
// return;
//
// if (obj != NULL) {
// verify_cast (expected, obj, [arg class], sel, method);
// return;
// }
//
// const char *m = "Failed to marshal the Objective-C object 0x%x (type: %s). "
// "Could not find an existing managed instance for this object, "
// "nor was it possible to create a new managed instance "
// "(because the type '%s' does not have a constructor that takes one IntPtr argument).\n"
// "Additional information:\n"
// "\tSelector: %s\n"
// "\tMethod: %s\n"
// "\tParameter: %i\n";
//
// char *method_full_name = mono_method_full_name (method, TRUE);
// char *type_name = xamarin_lookup_managed_type_name ([arg class]);
// char *msg = xamarin_strdup_printf (m, arg, object_getClassName (arg), type_name, sel, method_full_name, index);
// MonoException *mex = xamarin_create_exception (msg);
// xamarin_free (msg);
// mono_free (method_full_name);
// mono_free (type_name);
// mono_raise_exception (mex);
}
void
xamarin_check_objc_type (id obj, Class expected_class, SEL sel, id self, int index, MonoMethod *method)
{
// COOP: Reads managed memory, needs to be in UNSAFE mode
MONO_ASSERT_GC_UNSAFE;
// if ([obj isKindOfClass:expected_class])
// return;
//
// const char *m = "Failed to marshal the Objective-C object 0x%x (type: %s), expected an object of type %s.\n"
// "Additional information:\n"
// "\tSelector: %s\n"
// "\tMethod: %s\n"
// "\tParameter: %i\n";
//
// char *method_full_name = mono_method_full_name (method, TRUE);
// char *msg = xamarin_strdup_printf (m, obj, object_getClassName (obj), class_getName (expected_class), sel, method_full_name, index);
// MonoException *mono_ex = mono_exception_from_name_msg (mono_get_corlib (), "System", "InvalidCastException", msg);
// xamarin_free (msg);
// mono_free (method_full_name);
// mono_raise_exception (mono_ex);
}
#endif
char *
xamarin_class_get_full_name (MonoClass *klass, guint32 *exception_gchandle)
{
// COOP: Reads managed memory, needs to be in UNSAFE mode
MONO_ASSERT_GC_UNSAFE;
return xamarin_type_get_full_name (mono_class_get_type (klass), exception_gchandle);
}
char *
xamarin_type_get_full_name (MonoType *type, guint32 *exception_gchandle)
{
// COOP: Reads managed memory, needs to be in UNSAFE mode
MONO_ASSERT_GC_UNSAFE;
return xamarin_reflection_type_get_full_name (mono_type_get_object (mono_domain_get (), type), exception_gchandle);
}
/*
* ToggleRef support
*/
// #define DEBUG_TOGGLEREF 1
static void
gc_register_toggleref (MonoObject *obj, id self, bool isCustomType)
{
// COOP: This is an icall, at entry we're in unsafe mode. Managed memory is accessed, so we stay in unsafe mode.
MONO_ASSERT_GC_UNSAFE;
#ifdef DEBUG_TOGGLEREF
id handle = xamarin_get_nsobject_handle (obj);
PRINT ("**Registering object %p handle %p RC %d flags: %i",
obj,
handle,
(int) (handle ? [handle retainCount] : 0),
xamarin_get_nsobject_flags (obj));
#endif
mono_gc_toggleref_add (obj, TRUE);
// Make sure the GCHandle we have is a weak one for custom types.
if (isCustomType) {
MONO_ENTER_GC_SAFE;
xamarin_switch_gchandle (self, true);
MONO_EXIT_GC_SAFE;
}
}
static MonoToggleRefStatus
gc_toggleref_callback (MonoObject *object)
{
// COOP: this is a callback called by the GC, so I assume the mode here doesn't matter
id handle = NULL;
MonoToggleRefStatus res;
uint8_t flags = xamarin_get_nsobject_flags (object);
bool disposed = (flags & NSObjectFlagsDisposed) == NSObjectFlagsDisposed;
bool has_managed_ref = (flags & NSObjectFlagsHasManagedRef) == NSObjectFlagsHasManagedRef;
if (disposed || !has_managed_ref) {
res = MONO_TOGGLE_REF_DROP; /* Already disposed, we don't need the managed object around */
} else {
handle = xamarin_get_nsobject_handle (object);
if (handle == NULL) { /* This shouldn't really happen */
return MONO_TOGGLE_REF_DROP;
} else {
if ([handle retainCount] == 1)
res = MONO_TOGGLE_REF_WEAK;
else
res = MONO_TOGGLE_REF_STRONG;
}
}
#ifdef DEBUG_TOGGLEREF
const char *rv;
if (res == MONO_TOGGLE_REF_DROP) {
rv = "DROP";
} else if (res == MONO_TOGGLE_REF_STRONG) {
rv = "STRONG";
} else if (res == MONO_TOGGLE_REF_WEAK) {
rv = "WEAK";
} else {
rv = "UNKNOWN";
}
const char *cn = NULL;
if (handle == NULL) {
cn = object_getClassName (xamarin_get_nsobject_handle (object));
} else {
cn = object_getClassName (handle);
}
PRINT ("\tinspecting %p handle:%p %s flags: %i RC %d -> %s\n", object, handle, cn, (int) flags, (int) (handle ? [handle retainCount] : 0), rv);
#endif
return res;
}
static void
gc_event_callback (MonoProfiler *prof, MonoGCEvent event, int generation)
{
// COOP: this is a callback called by the GC, I believe the mode here doesn't matter.
switch (event) {
case MONO_GC_EVENT_PRE_STOP_WORLD:
pthread_mutex_lock (&framework_peer_release_lock);
break;
case MONO_GC_EVENT_POST_START_WORLD:
pthread_mutex_unlock (&framework_peer_release_lock);
break;
default: // silences a compiler warning.
break;
}
}
static void
gc_enable_new_refcount (void)
{
// COOP: this is executed at startup, I believe the mode here doesn't matter.
pthread_mutexattr_t attr;
pthread_mutexattr_init (&attr);
pthread_mutexattr_settype (&attr, PTHREAD_MUTEX_RECURSIVE);
pthread_mutex_init (&framework_peer_release_lock, &attr);
pthread_mutexattr_destroy (&attr);
mono_gc_toggleref_register_callback (gc_toggleref_callback);
xamarin_add_internal_call ("Foundation.NSObject::RegisterToggleRef", (const void *) gc_register_toggleref);
mono_profiler_install_gc (gc_event_callback, NULL);
}
static MonoClass *
get_class_from_name (MonoImage* image, const char *nmspace, const char *name, bool optional = false)
{
// COOP: this is a convenience function executed only at startup, I believe the mode here doesn't matter.
MonoClass *rv = mono_class_from_name (image, nmspace, name);
if (!rv && !optional)
xamarin_assertion_message ("Fatal error: failed to load the class '%s.%s'\n.", nmspace, name);
return rv;
}
struct _MonoProfiler {
int dummy;
};
static void
xamarin_install_mono_profiler ()
{
static _MonoProfiler profiler = { 0 };
// This must be done before any other mono_profiler_install_* functions are called
// (currently gc_enable_new_refcount and xamarin_install_nsautoreleasepool_hooks).
mono_profiler_install (&profiler, NULL);
}
bool
xamarin_file_exists (const char *path)
{
// COOP: no managed access: any mode
struct stat buffer;
return stat (path, &buffer) == 0;
}
MonoAssembly *
xamarin_open_assembly (const char *name)
{
// COOP: this is a function executed only at startup, I believe the mode here doesn't matter.
char path [1024];
MonoAssembly *assembly;
bool exists = false;
#if MONOMAC
if (xamarin_get_is_mkbundle ()) {
assembly = mono_assembly_open (name, NULL);
if (assembly == NULL)
xamarin_assertion_message ("Could not find the required assembly '%s' in the app. This is usually fixed by cleaning and rebuilding your project; if that doesn't work, please file a bug report: https://github.com/xamarin/xamarin-macios/issues/new", name);
return assembly;
}
#endif
exists = xamarin_locate_assembly_resource (name, NULL, name, path, sizeof (path));
#if MONOMAC && DYLIB
if (!exists) {
// Check if we already have the assembly in memory
xamarin_get_assembly_name_without_extension (name, path, sizeof (path));
MonoAssemblyName *aname = mono_assembly_name_new (path);
assembly = mono_assembly_loaded (aname);
mono_assembly_name_free (aname);
if (assembly)
return assembly;
xamarin_assertion_message ("Could not find the assembly '%s' in the app nor as an already loaded assembly. This is usually fixed by cleaning and rebuilding your project; if that doesn't work, please file a bug report: https://github.com/xamarin/xamarin-macios/issues/new", name);
}
#endif
if (!exists)
xamarin_assertion_message ("Could not find the assembly '%s' in the app. This is usually fixed by cleaning and rebuilding your project; if that doesn't work, please file a bug report: https://github.com/xamarin/xamarin-macios/issues/new", name);
assembly = mono_assembly_open (path, NULL);
if (assembly == NULL)
xamarin_assertion_message ("Could not find the required assembly '%s' in the app. This is usually fixed by cleaning and rebuilding your project; if that doesn't work, please file a bug report: https://github.com/xamarin/xamarin-macios/issues/new", name);
return assembly;
}
static bool
register_assembly (MonoAssembly *assembly, guint32 *exception_gchandle)
{
// COOP: this is a function executed only at startup, I believe the mode here doesn't matter.
if (!xamarin_supports_dynamic_registration) {
LOG (PRODUCT ": Skipping assembly registration for %s since it's not needed (dynamic registration is not supported)", mono_assembly_name_get_name (mono_assembly_get_name (assembly)));
return true;
}
xamarin_register_assembly (mono_assembly_get_object (mono_domain_get (), assembly), exception_gchandle);
return *exception_gchandle == 0;
}
MonoAssembly *
xamarin_open_and_register (const char *aname, guint32 *exception_gchandle)
{
// COOP: this is a function executed only at startup, I believe the mode here doesn't matter.
MonoAssembly *assembly;
assembly = xamarin_open_assembly (aname);
register_assembly (assembly, exception_gchandle);
return assembly;
}
static gboolean
is_class_finalization_aware (MonoClass *cls)
{
// COOP: This is a callback called by the GC, I believe the mode here doesn't matter.
gboolean rv = false;
if (nsobject_class)
rv = cls == nsobject_class || mono_class_is_assignable_from (nsobject_class, cls);
//PRINT ("IsClass %s.%s finalization aware: %i\n", mono_class_get_namespace (cls), mono_class_get_name (cls), rv);
return rv;
}
static void
object_queued_for_finalization (MonoObject *object)
{
// COOP: Although this is reading managed memory, it is a callback called by the GC, so I believe the mode here doesn't matter.
/* This is called with the GC lock held, so it can only use signal-safe code */
struct Managed_NSObject *obj = (struct Managed_NSObject *) object;
//PRINT ("In finalization response for %s.%s %p (handle: %p class_handle: %p flags: %i)\n",
obj->flags |= NSObjectFlagsInFinalizerQueue;
}
/*
* Registration map
*/
static int
compare_mtclassmap (const void *a, const void *b)
{
MTClassMap *mapa = (MTClassMap *) a;
MTClassMap *mapb = (MTClassMap *) b;
intptr_t diff = (intptr_t)mapa->handle - (intptr_t)mapb->handle;
const int shift = (sizeof(intptr_t) * 8) - 1;
return (diff >> shift) | !!diff;
}
void
xamarin_add_registration_map (struct MTRegistrationMap *map, bool partial)
{
// COOP: no managed memory access: any mode
options.RegistrationData = map;
if (partial)
options.flags = (InitializationFlags) (options.flags | InitializationFlagsIsPartialStaticRegistrar);
// Sort the type map according to Class
qsort (map->map, (size_t) map->map_count, sizeof (MTClassMap), compare_mtclassmap);
}
/*
* Exception handling
*/
static XamarinUnhandledExceptionFunc unhandled_exception_func;
void
xamarin_install_unhandled_exception_hook (XamarinUnhandledExceptionFunc func)
{
// COOP: no managed memory access: any mode
unhandled_exception_func = func;
}
static MonoObject *
fetch_exception_property (MonoObject *obj, const char *name, bool is_virtual)
{
// COOP: reading managed memory and executing managed code: must be in UNSAFE mode
MONO_ASSERT_GC_UNSAFE;
MonoMethod *get = NULL;
MonoMethod *get_virt = NULL;
MonoObject *exc = NULL;
get = mono_class_get_method_from_name (mono_get_exception_class (), name, 0);
if (get) {
if (is_virtual) {
get_virt = mono_object_get_virtual_method (obj, get);
if (get_virt)
get = get_virt;
}
return (MonoObject *) mono_runtime_invoke (get, obj, NULL, &exc);
} else {
PRINT ("Could not find the property System.Exception.%s", name);
}
return NULL;
}
static char *
fetch_exception_property_string (MonoObject *obj, const char *name, bool is_virtual)
{
// COOP: reading managed memory and executing managed code: must be in UNSAFE mode
MONO_ASSERT_GC_UNSAFE;
MonoString *str = (MonoString *) fetch_exception_property (obj, name, is_virtual);
return str ? mono_string_to_utf8 (str) : NULL;
}
static void
print_exception (MonoObject *exc, bool is_inner, NSMutableString *msg)
{
// COOP: reading managed memory and executing managed code: must be in UNSAFE mode
MONO_ASSERT_GC_UNSAFE;
MonoClass *type = mono_object_get_class (exc);
char *type_name = xamarin_strdup_printf ("%s.%s", mono_class_get_namespace (type), mono_class_get_name (type));
char *trace = fetch_exception_property_string (exc, "get_StackTrace", true);
char *message = fetch_exception_property_string (exc, "get_Message", true);
if (is_inner)
[msg appendString:@" --- inner exception ---\n"];
[msg appendFormat: @"%s (%s)\n", message, type_name];
if (trace)
[msg appendFormat: @"%s\n", trace];
if (unhandled_exception_func && !is_inner)
unhandled_exception_func (exc, type_name, message, trace);
mono_free (trace);
mono_free (message);
xamarin_free (type_name);
}
static NSMutableString *
print_all_exceptions (MonoObject *exc)
{
// COOP: reading managed memory and executing managed code: must be in UNSAFE mode
MONO_ASSERT_GC_UNSAFE;
NSMutableString *str = [[NSMutableString alloc] init];
// fetch the field, since the property might have been linked away.
int counter = 0;
MonoClassField *inner_exception = mono_class_get_field_from_name (mono_object_get_class (exc), "_innerException");
do {
print_exception (exc, counter > 0, str);
if (inner_exception) {
mono_field_get_value (exc, inner_exception, &exc);
} else {
LOG ("Could not find the field _innerException in System.Exception\n");
break;
}
} while (counter++ < 10 && exc);
[str autorelease];
return str;
}
NSString *
xamarin_print_all_exceptions (MonoObject *exc)
{
return print_all_exceptions (exc);
}
void
xamarin_ftnptr_exception_handler (guint32 gchandle)
{
xamarin_process_managed_exception_gchandle (gchandle);
}
void
xamarin_process_managed_exception_gchandle (guint32 gchandle)
{
if (gchandle == 0)
return;
MonoObject *exc = mono_gchandle_get_target (gchandle);
mono_gchandle_free (gchandle);
xamarin_process_managed_exception (exc);
}
void
xamarin_unhandled_exception_handler (MonoObject *exc, gpointer user_data)
{
PRINT ("Unhandled managed exception: %@", print_all_exceptions (exc));
abort ();
}
static void
exception_handler (NSException *exc)
{
// COOP: We won't get here in coop-mode, because we don't set the uncaught objc exception handler in that case.
LOG (PRODUCT ": Received unhandled ObjectiveC exception: %@ %@", [exc name], [exc reason]);
if (xamarin_is_gc_coop) {
PRINT ("Uncaught Objective-C exception: %@", exc);
assert (false); // Re-throwing the Objective-C exception will probably just end up with infinite recursion
}
xamarin_throw_ns_exception (exc);
}
#if defined (DEBUG)
static void *
pump_gc (void *context)
{
// COOP: this runs on a separate thread, so I'm not sure what happens here.
// We can make sure we're in safe mode while sleeping though.
mono_thread_attach (mono_get_root_domain ());
while (xamarin_gc_pump) {
mono_gc_collect (mono_gc_max_generation ());
MONO_ENTER_GC_SAFE;
usleep (1000000);
MONO_EXIT_GC_SAFE;
}
return NULL;
}
#endif /* DEBUG */
static void
log_callback (const char *log_domain, const char *log_level, const char *message, mono_bool fatal, void *user_data)
{
// COOP: Not accessing managed memory: any mode
PRINT ("%s: %s", log_level, message);
if (fatal)
abort ();
}
static void
print_callback (const char *string, mono_bool is_stdout)
{
// COOP: Not accessing managed memory: any mode
PRINT ("%s", string);
}
static int
xamarin_compare_ints (const void *a, const void *b)
{
uint32_t x = *(uint32_t *) a;
uint32_t y = *(uint32_t *) b;
return x < y ? -1 : (x == y ? 0 : 1);
}
uint32_t
xamarin_find_protocol_wrapper_type (uint32_t token_ref)
{
if (options.RegistrationData == NULL || options.RegistrationData->protocol_wrappers == NULL)
return INVALID_TOKEN_REF;
void* ptr = bsearch (&token_ref, options.RegistrationData->protocol_wrappers, (size_t) options.RegistrationData->protocol_wrapper_count, sizeof (MTProtocolWrapperMap), xamarin_compare_ints);
if (ptr == NULL)
return INVALID_TOKEN_REF;
MTProtocolWrapperMap *entry = (MTProtocolWrapperMap *) ptr;
return entry->wrapper_token;
}
void
xamarin_initialize_embedded ()
{
static bool initialized = false;
if (initialized)
return;
initialized = true;
char *argv[] = { NULL };
char *libname = NULL;
Dl_info info;
if (dladdr ((void *) xamarin_initialize_embedded, &info) != 0) {
const char *last_sep = strrchr (info.dli_fname, '/');
if (last_sep == NULL) {
libname = strdup (info.dli_fname);
} else {
libname = strdup (last_sep + 1);
}
argv [0] = libname;
}
if (argv [0] == NULL)
argv [0] = (char *) "embedded";
xamarin_main (1, argv, XamarinLaunchModeEmbedded);
if (libname != NULL)
free (libname);
}
/* Installs g_print/g_error handlers that will redirect output to the system Console */
void
xamarin_install_log_callbacks ()
{
mono_trace_set_log_handler (log_callback, NULL);
mono_trace_set_print_handler (print_callback);
mono_trace_set_printerr_handler (print_callback);
}
void
xamarin_initialize ()
{
// COOP: accessing managed memory: UNSAFE mode
MONO_ASSERT_GC_UNSAFE;
MonoAssembly *assembly = NULL;
MonoMethod *runtime_initialize;
void* params[2];
guint32 exception_gchandle = 0;
MonoObject *exc = NULL;
initialize_started = TRUE;
#ifdef DYNAMIC_MONO_RUNTIME
// We might be called from the managed Runtime.EnsureInitialized method,
// in which case xamarin_initialize_dynamic_runtime has not been called yet.
xamarin_initialize_dynamic_runtime (NULL);
#endif
xamarin_insert_dllmap ();
MONO_ENTER_GC_UNSAFE;
xamarin_install_log_callbacks ();
MonoGCFinalizerCallbacks gc_callbacks;
gc_callbacks.version = MONO_GC_FINALIZER_EXTENSION_VERSION;
gc_callbacks.is_class_finalization_aware = is_class_finalization_aware;
gc_callbacks.object_queued_for_finalization = object_queued_for_finalization;
mono_gc_register_finalizer_callbacks (&gc_callbacks);
if (xamarin_is_gc_coop) {
// There should be no such thing as an unhandled ObjC exception
// when running the GC in cooperative mode, and if we run into it,
// it's a bug somewhere, in which case we must fix it.
} else {
NSSetUncaughtExceptionHandler (exception_handler);
}
assembly = xamarin_open_assembly (PRODUCT_DUAL_ASSEMBLY);
if (!assembly)
xamarin_assertion_message ("Failed to load %s.", PRODUCT_DUAL_ASSEMBLY);
platform_image = mono_assembly_get_image (assembly);
const char *objcruntime = "ObjCRuntime";
const char *foundation = "Foundation";
runtime_class = get_class_from_name (platform_image, objcruntime, "Runtime");
inativeobject_class = get_class_from_name (platform_image, objcruntime, "INativeObject");
nsobject_class = get_class_from_name (platform_image, foundation, "NSObject");
nsnumber_class = get_class_from_name (platform_image, foundation, "NSNumber", true);
nsvalue_class = get_class_from_name (platform_image, foundation, "NSValue", true);
nsstring_class = get_class_from_name (platform_image, foundation, "NSString", true);
xamarin_add_internal_call ("Foundation.NSObject::xamarin_release_managed_ref", (const void *) xamarin_release_managed_ref);
xamarin_add_internal_call ("Foundation.NSObject::xamarin_create_managed_ref", (const void *) xamarin_create_managed_ref);
runtime_initialize = mono_class_get_method_from_name (runtime_class, "Initialize", 1);
if (runtime_initialize == NULL)
xamarin_assertion_message ("Fatal error: failed to load the %s.%s method", "Runtime", "Initialize");
options.size = sizeof (options);
#if MONOTOUCH && (defined(__i386__) || defined (__x86_64__))
options.flags = (enum InitializationFlags) (options.flags | InitializationFlagsIsSimulator);
#endif
options.Delegates = &delegates;
options.Trampolines = &trampolines;
options.MarshalObjectiveCExceptionMode = xamarin_marshal_objectivec_exception_mode;
options.MarshalManagedExceptionMode = xamarin_marshal_managed_exception_mode;
#if MONOMAC
options.LaunchMode = xamarin_launch_mode;
options.EntryAssemblyPath = xamarin_entry_assembly_path;
#endif
params [0] = &options;
mono_runtime_invoke (runtime_initialize, NULL, params, &exc);
if (exc) {
NSLog (@PRODUCT ": An exception occurred when calling Runtime.Initialize:\n%@", xamarin_print_all_exceptions (exc));
xamarin_process_managed_exception (exc);
xamarin_assertion_message ("Can't continue if Runtime.Initialize fails.");
}
if (!register_assembly (assembly, &exception_gchandle))
xamarin_process_managed_exception_gchandle (exception_gchandle);
xamarin_install_mono_profiler (); // must be called before xamarin_install_nsautoreleasepool_hooks or gc_enable_new_refcount
xamarin_install_nsautoreleasepool_hooks ();
#if defined (DEBUG)
if (xamarin_gc_pump) {
pthread_t gc_thread;
pthread_create (&gc_thread, NULL, pump_gc, NULL);
}
#endif
gc_enable_new_refcount ();
MONO_EXIT_GC_UNSAFE;
}
static char *x_bundle_path = NULL;
const char *
xamarin_get_bundle_path ()
{
// COOP: only called at startup, so I believe the mode doesn't matter
if (x_bundle_path != NULL)
return x_bundle_path;
NSBundle *main_bundle = [NSBundle mainBundle];
NSString *bundle_path;
char *result;
#if MONOMAC
if (xamarin_launch_mode == XamarinLaunchModeEmbedded) {
bundle_path = [[[NSBundle bundleForClass: [XamarinAssociatedObject class]] bundlePath] stringByAppendingPathComponent: @"Versions/Current"];
} else {
bundle_path = [[main_bundle bundlePath] stringByAppendingPathComponent:@"Contents"];
}
bundle_path = [bundle_path stringByAppendingPathComponent: xamarin_custom_bundle_name == NULL ? @"MonoBundle" : xamarin_custom_bundle_name];
#else
bundle_path = [main_bundle bundlePath];
#endif
if (main_bundle == NULL)
xamarin_assertion_message ("Could not find the main bundle in the app ([NSBundle mainBundle] returned nil)");
result = mono_path_resolve_symlinks ([bundle_path UTF8String]);
x_bundle_path = strdup (result);
mono_free (result);
return x_bundle_path;
}
void
xamarin_set_bundle_path (const char *path)
{
// COOP: no managed memory access: any mode
free (x_bundle_path);
x_bundle_path = strdup (path);
}
void *
xamarin_calloc (size_t size)
{
// COOP: no managed memory access: any mode
return calloc (size, 1);
}
void
xamarin_free (void *ptr)
{
// COOP: no managed memory access: any mode
// We use this method to free memory returned by mono,
// which means we have to use the free function mono expects.
if (ptr)
free (ptr);
}
char *
xamarin_strdup_printf (const char *msg, ...)
{
// COOP: no managed memory access: any mode
va_list args;
char *formatted = NULL;
va_start (args, msg);
vasprintf (&formatted, msg, args);
va_end (args);
return formatted;
}
void
xamarin_assertion_message (const char *msg, ...)
{
// COOP: no managed memory access: any mode.
va_list args;
char *formatted = NULL;
va_start (args, msg);
vasprintf (&formatted, msg, args);
if (formatted) {
PRINT ( PRODUCT ": %s", formatted);
free (formatted);
}
va_end (args);
abort ();
}
static const char *
objc_skip_type (const char *type)
{
// COOP: no managed memory access: any mode
switch (type [0]) {
case _C_ID:
case _C_CLASS:
case _C_SEL:
case _C_CHR:
case _C_UCHR:
case _C_SHT:
case _C_USHT:
case _C_INT:
case _C_UINT:
case _C_LNG:
case _C_ULNG:
case _C_LNG_LNG:
case _C_ULNG_LNG:
case _C_FLT:
case _C_DBL:
case _C_CHARPTR:
case _C_BOOL:
case _C_VOID:
case _C_UNDEF:
return ++type;
case _C_PTR:
return objc_skip_type (++type);
case _C_BFLD:
type++;
while (*type && *type >= '0' && *type <= '9')
type++;
return type;
case _C_ATOM:
case _C_VECTOR:
case _C_CONST:
case _C_ARY_E:
case _C_UNION_E:
case _C_STRUCT_E:
xamarin_assertion_message ("Unhandled type encoding: %s", type);
break;
case _C_ARY_B: {
do {
type++;
} while (isdigit (*type));
type = objc_skip_type (type);
return ++type;
}
case _C_UNION_B: {
do {
type++;
} while (*type != '=');
type ++;
do {
type = objc_skip_type (type);
} while (*type != _C_UNION_E);
return ++type;
}
case _C_STRUCT_B: {
do {
type++;
} while (*type != '=');
type++;
do {
type = objc_skip_type (type);
} while (*type != _C_STRUCT_E);
return ++type;
}
default:
xamarin_assertion_message ("Unsupported type encoding: %s", type);
break;
}
}
unsigned long
xamarin_objc_type_size (const char *type)
{
const char *original_type = type;
// COOP: no managed memory access: any mode
switch (type [0]) {
case _C_ID: return sizeof (id);
case _C_CLASS: return sizeof (Class);
case _C_SEL: return sizeof (SEL);
case _C_CHR: return sizeof (char);
case _C_UCHR: return sizeof (unsigned char);
case _C_SHT: return sizeof (short);
case _C_USHT: return sizeof (unsigned short);
case _C_INT: return sizeof (int);
case _C_UINT: return sizeof (unsigned int);
case _C_LNG: return sizeof (long);
case _C_ULNG: return sizeof (unsigned long);
case _C_LNG_LNG: return sizeof (long long);
case _C_ULNG_LNG: return sizeof (unsigned long long);
case _C_FLT: return sizeof (float);
case _C_DBL: return sizeof (double);
case _C_BOOL: return sizeof (BOOL);
case _C_VOID: return 0;
case _C_PTR: return sizeof (void *);
case _C_CHARPTR: return sizeof (char *);
case _C_BFLD: {
// Example: [NSDecimalNumberPlaceholder initWithDecimal:] = @28@0:4{?=b8b4b1b1b18[8S]}8
unsigned long bits = 0;
int bc = 1;
while (type [bc] >= '0' && type [bc] <= '9') {
bits = bits * 10ul + (unsigned long) (type [bc] - '0');
bc++;
}
if (bits % sizeof (void *) == 0)
return bits / sizeof (void *);
return 1 + (bits / sizeof (void *));
}
case _C_UNDEF:
case _C_ATOM:
case _C_VECTOR:
xamarin_assertion_message ("Unhandled type encoding: %s", type);
break;
case _C_ARY_B: {
unsigned long size = 0;
unsigned long len = (unsigned long) atol (type+1);
do {
type++;
} while (isdigit (*type));
size = xamarin_objc_type_size (type);
size = (size + (sizeof (void *) - 1)) & ~((sizeof (void *) - 1));
return len * size;
}
case _C_UNION_B: {
unsigned long size = 0;
do {
type++;
if (*type == 0)
xamarin_assertion_message ("Unsupported union type: %s", original_type);
} while (*type != '=');
++type;
do {
if (*type == 0)
xamarin_assertion_message ("Unsupported union type: %s", original_type);
unsigned long tsize = xamarin_objc_type_size (type);
type = objc_skip_type (type);
tsize = (tsize + (sizeof (void *) - 1)) & ~((sizeof (void *) - 1));
size = size > tsize ? size : tsize;
} while (*type != _C_UNION_E);
return size;
}
case _C_STRUCT_B: {
unsigned long size = 0;
do {
type++;
if (*type == 0)
xamarin_assertion_message ("Unsupported struct type: %s", original_type);
} while (*type != '=');
type++;
while (*type != _C_STRUCT_E) {
if (*type == 0)
xamarin_assertion_message ("Unsupported struct type: %s", original_type);
unsigned long item_size = xamarin_objc_type_size (type);
size += (item_size + (sizeof (void *) - 1)) & ~((sizeof (void *) - 1));
type = objc_skip_type (type);
}
return size;
}
// The following are from table 6-2 here: https://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtTypeEncodings.html
case 'r': // _C_CONST
case 'n':
case 'N':
case 'o':
case 'O':
case 'R':
case 'V':
return xamarin_objc_type_size (type + 1);
}
xamarin_assertion_message ("Unsupported type encoding: %s", original_type);
}
/*
* Reference counting
* ==================
*
* There are two types of managed types:
* - Wrapper types for existing ObjectiveC types (such as UIView).
* - Other types derived from wrapper types (henceforth called User types),
* that do not have a corresponding ObjectiveC type.
*
* Wrapper types
* -------------
*
* They are simple. The managed peer's lifetime is not linked to the native object's lifetime.
* The managed peer can be freed by the GC whenever it so determines, and if a managed object
* is needed at a later stage we just recreate it.
*
* User types
* ----------
* These are not that simple. We can't free managed objects at will, since they may contain
* user state. Therefore we must ensure the managed object stays alive as long as needed
* (this is done by using a strong GCHandle) - the problem is determining when it's no longer
* needed.
*
* Historically we've had two cases:
* 1) User calls Dispose, we free the managed ref and break the link between the native and
* managed object, thus allowing the GC to free it (if nothing else is keeping it alive
* of course).
* 2) Refcount reaches 1, in which case we know that the managed ref is the only ref and we
* can safely assume that native code will not use the object again. We break the link,
* release the native object (which will be freed now) and allow the GC to free the
* managed object.
*
* Problem arises in case 1), when users call Dispose and then native code tries to use
* that object for whatever reason. MonoTouch will detect that no managed peer exists,
* and try to (re)create one. This may fail, and an exception is thrown which may kill
* the process (there may be no managed frame / exception handler on the stack at this point).
*
* This solution will do a couple of things when user calls Dispose:
* - Free the managed ref.
* - Not break the link between the native and managed object until the native object's
* refcount reaches 0.
* This will allow us to still lookup the managed object as long as the native object is
* alive.
*
* Implementation details (for user types only)
* ============================================
*
* Managed code releases its ref when either of the following conditions occur:
* - Dispose is called (manually) on the object.
* - The Handle property on the managed object changes.
* - The GC frees the object. This can only happen if refCount == 1 and that ref is a managed ref
* (since otherwise there will be a strong gchandle to the managed object preventing the GC from freeing it).
*
* Objects are removed from the native<->managed dictionary when either of the following conditions occur:
* - The native object is dealloc'ed (refcount reaches 0).
* - The managed object's Handle property changes (the link between the previous Handle
* value and the managed object is then removed from the dictionary).
*
* We need to keep track of two pieces of information in native land:
* - The GCHandle to the managed object.
* - If there is a managed ref or not.
* We already have an ObjectiveC ivar to store the GCHandle, so to not create another ivar
* we use one bit of the GCHandle to store whether there is a managed ref or not (MANAGED_REF_BIT).
*
*/
//#define DEBUG_REF_COUNTING
void
xamarin_create_gchandle (id self, void *managed_object, uint32_t flags, bool force_weak)
{
// COOP: reads managed memory: unsafe mode
MONO_ASSERT_GC_UNSAFE;
// force_weak is to avoid calling retainCount unless needed, since some classes (UIWebView in iOS 5)
// will crash if retainCount is called before init. See bug #9261.
bool weak = force_weak || ([self retainCount] == 1);
uint32_t gchandle;
if (weak) {
gchandle = mono_gchandle_new_weakref ((MonoObject *) managed_object, TRUE);
flags |= GCHANDLE_WEAK;
} else {
gchandle = mono_gchandle_new ((MonoObject *) managed_object, FALSE);
flags &= ~GCHANDLE_WEAK;
}
assert ((gchandle & GCHANDLE_MASK) == 0); // Make sure we don't create too many gchandles...
set_raw_gchandle (self, gchandle | flags);
#if defined(DEBUG_REF_COUNTING)
PRINT ("\tGCHandle created for %p: %d (flags: %p) = %d %s\n", self, gchandle, GINT_TO_POINTER (flags), get_raw_gchandle (self), weak ? "weak" : "strong");
#endif
}
void
xamarin_switch_gchandle (id self, bool to_weak)
{
// COOP: reads managed memory: unsafe mode
MONO_ASSERT_GC_SAFE_OR_DETACHED;
uint32_t new_gchandle;
uint32_t old_gchandle;
uint32_t old_gchandle_raw;
MonoObject *managed_object;
uint32_t flags = MANAGED_REF_BIT;
old_gchandle_raw = get_raw_gchandle_safe (self);
old_gchandle = old_gchandle_raw & ~GCHANDLE_MASK;
if (old_gchandle) {
bool is_weak = (old_gchandle_raw & GCHANDLE_WEAK) == GCHANDLE_WEAK;
if (to_weak == is_weak) {
// we already have the GCHandle we need
#if defined(DEBUG_REF_COUNTING)
PRINT ("Object %p already has a %s GCHandle = %d\n", self, to_weak ? "weak" : "strong", old_gchandle);
#endif
return;
}
} else {
// We don't have a GCHandle. This means there's no managed instance for this
// native object.
// If to_weak is true, then there's obviously nothing to do
// (why create a managed object which can immediately be freed by the GC?).
// If to_weak is false, the question is if we want to create the
// managed object. Bug #30420 says no (previously we didn't, and
// if we do, managed ctors end up being executed at a different moment,
// which breaks implicit assumptions in people's code.)
#if defined(DEBUG_REF_COUNTING)
PRINT ("Object %p has no managed object to create a %s GCHandle for\n", self, to_weak ? "weak" : "strong");
#endif
return;
}
MONO_THREAD_ATTACH; // COOP: will switch to GC_UNSAFE
managed_object = mono_gchandle_get_target (old_gchandle);
if (to_weak) {
new_gchandle = mono_gchandle_new_weakref (managed_object, TRUE);
flags |= GCHANDLE_WEAK;
} else {
new_gchandle = mono_gchandle_new (managed_object, FALSE);
}
mono_gchandle_free (old_gchandle);
if (managed_object) {
// It's possible to not have a managed object if:
// 1. Objective-C holds a weak reference to the native object (and no other strong references)
// - in which case the original (old) gchandle would be a weak one.
// 2. Managed code does not reference the managed object.
// 3. The GC ran and collected the managed object, but the main thread has not gotten
// around to release the native object yet.
// If all these conditions hold, then the original gchandle will point to
// null, because the target would be collected.
xamarin_set_nsobject_flags (managed_object, xamarin_get_nsobject_flags (managed_object) | NSObjectFlagsHasManagedRef);
}
set_raw_gchandle (self, new_gchandle | flags);
MONO_THREAD_DETACH; // COOP: this will switch to GC_SAFE
#if defined(DEBUG_REF_COUNTING)
PRINT ("Switched object %p to %s GCHandle = %d\n", self, to_weak ? "weak" : "strong", new_gchandle);
#endif
}
void
xamarin_free_gchandle (id self, uint32_t gchandle)
{
// COOP: no managed memory access, but calls mono function mono_gc_handle_free. Assuming that function can be called with any mode: this function can be called with any mode as well
if (gchandle) {
#if defined(DEBUG_REF_COUNTING)
PRINT ("\tGCHandle %i destroyed for object %p\n", gchandle, self);
#endif
mono_gchandle_free (gchandle);
set_raw_gchandle (self, 0);
} else {
#if defined(DEBUG_REF_COUNTING)
PRINT ("\tNo GCHandle for the object %p\n", self);
#endif
}
}
void
xamarin_clear_gchandle (id self)
{
// COOP: no managed memory access: any mode
set_raw_gchandle (self, 0);
}
void
xamarin_set_gchandle (id self, uint32_t gchandle)
{
// COOP: no managed memory access: any mode
set_raw_gchandle (self, gchandle);
}
static int
find_user_type_index (MTClassMap *map, int lo, int hi, Class cls)
{
if (hi >= lo) {
int mid = lo + (hi - lo) / 2;
if (map [mid].handle == cls)
return mid;
if ((intptr_t) map [mid].handle > (intptr_t) cls)
return find_user_type_index (map, lo, mid - 1, cls);
return find_user_type_index (map, mid + 1, hi, cls);
}
return -1;
}
static inline bool
is_user_type (id self)
{
Class cls = object_getClass (self);
if (options.RegistrationData != NULL && options.RegistrationData->map_count > 0) {
MTClassMap *map = options.RegistrationData->map;
int idx = find_user_type_index (map, 0, (int) options.RegistrationData->map_count - 1, cls);
if (idx >= 0)
return (map [idx].flags & MTTypeFlagsUserType) == MTTypeFlagsUserType;
// If using the partial static registrar, we need to continue
// If full static registrar, we can return false
if ((options.flags & InitializationFlagsIsPartialStaticRegistrar) != InitializationFlagsIsPartialStaticRegistrar)
return false;
}
// COOP: no managed memory access: any mode
return class_getInstanceMethod (cls, @selector (xamarinSetGCHandle:)) != NULL;
}
#if defined(DEBUG_REF_COUNTING)
int
get_safe_retainCount (id self)
{
// COOP: no managed memory access: any mode
if ([self isKindOfClass: [NSCalendar class]] ||
[self isKindOfClass: [NSInputStream class]] ||
[self isKindOfClass: [NSOutputStream class]]) {
// NSCalendar/NSInputStream may end up with a stack overflow where CFGetRetainCount calls itself
return 666;
} else {
return [self retainCount];
}
}
#endif
void
xamarin_release_managed_ref (id self, MonoObject *managed_obj)
{
// COOP: This is an icall, so at entry we're in unsafe mode.
// COOP: we stay in unsafe mode (since we write to the managed memory) unless calling a selector (which must be done in safe mode)
MONO_ASSERT_GC_UNSAFE;
bool user_type = is_user_type (self);
guint32 exception_gchandle = 0;
#if defined(DEBUG_REF_COUNTING)
PRINT ("monotouch_release_managed_ref (%s Handle=%p) retainCount=%d; HasManagedRef=%i GCHandle=%i IsUserType=%i\n",
class_getName (object_getClass (self)), self, (int32_t) [self retainCount], user_type ? xamarin_has_managed_ref (self) : 666, user_type ? get_gchandle (self) : 666, user_type);
#endif
xamarin_set_nsobject_flags (managed_obj, xamarin_get_nsobject_flags (managed_obj) & ~NSObjectFlagsHasManagedRef);
if (user_type) {
/* clear MANAGED_REF_BIT */
set_raw_gchandle (self, get_raw_gchandle (self) & ~MANAGED_REF_BIT);
MONO_ENTER_GC_SAFE;
[self release];
MONO_EXIT_GC_SAFE;
} else {
/* If we're a wrapper type, we need to unregister here, since we won't enter the release trampoline */
GCHandle managed_obj_handle = xamarin_gchandle_new (managed_obj, false);
xamarin_unregister_nsobject (self, managed_obj_handle, &exception_gchandle);
xamarin_gchandle_free (managed_obj_handle);
//
// This lock is needed so that we can safely call retainCount in the
// toggleref callback.
//
// The race is between the following actions (given a managed object Z):
//
// a1) Thread A nulls out the handle for Z
// a2) Thread A calls release on Z's original handle
// b1) Thread B fetches the handle for Z
// b2) Thread B calls retainCount on Z's handle
//
// Possible execution orders:
//
// 1) a1-*: all such orders are safe, because b1 will read NULL and
// b2 won't do anything
// 2) b1-b2-a1-a2: retainCount before release is safe.
// 3) b1-a1-b2-a2: retainCount before release is safe.
// 4) b1-a1-a2-b2: unsafe; this tries to call retainCount after
// release.
//
// Order 4 would look like this:
//
// * Thread B runs a GC, and starts calling toggleref callbacks.
// * Thread B fetches the handle (H) for object Z in a toggleref
// callback.
// * Thread A calls xamarin_release_managed_ref for object Z, and
// calls release on H, deallocating it.
// * Thread B tries to call retainCount on H, which is now a
// deallocated object.
//
// Solution: lock/unlock the framework peer lock here. This looks
// weird (since nothing happens inside the lock), but it works:
//
// * Thread B runs a GC, locks the framework peer lock, and starts
// calling toggleref callbacks.
// * Thread B fetches the handle (H) for object Z in a toggleref
// callback.
// * Thread A calls xamarin_release_managed_ref for object Z (and
// also nulls out the handle for Z)
// * Thread A tries to lock the framework peer lock, and blocks
// (before calling release on H)
// * Thread B successfully calls retainCount on H
// * Thread B finishes processing all toggleref callbacks, completes
// the GC, and unlocks the framework peer lock.
// * Thread A wakes up, and calls release on H.
//
// An alternative phrasing would be to say that the lock prevents both
// a1 and a2 from happening between b1 and b2 from above, thus making
// order 4 impossible.
//
// Q) Why not just unlock after calling release, to avoid the strange-
// looking empty lock?
// A) Because calling release on an object might end up calling
// managed code (the native object can override dealloc and do all
// sorts of strange things, any of which may end up invoking
// managed code), and we can deadlock:
// 1) Thread T calls release on a native object.
// 2) Thread T executes managed code, which blocks on something
// that's supposed to happen on another thread U.
// 3) Thread U causes a garbage collection.
// 4) Thread U tries to lock the framework peer lock before running
// the GC, and deadlocks because thread T already has the
// framework peer lock.
//
// This is https://github.com/xamarin/xamarin-macios/issues/3943
//
xamarin_framework_peer_lock ();
xamarin_framework_peer_unlock ();
MONO_ENTER_GC_SAFE;
[self release];
MONO_EXIT_GC_SAFE;
}
xamarin_process_managed_exception_gchandle (exception_gchandle);
}
void
xamarin_create_managed_ref (id self, gpointer managed_object, bool retain)
{
// COOP: This is an icall, so at entry we're in unsafe mode.
// COOP: we stay in unsafe mode (since we write to the managed memory) unless calling a selector (which must be done in safe mode)
MONO_ASSERT_GC_UNSAFE;
uint32_t gchandle;
bool user_type = is_user_type (self);
#if defined(DEBUG_REF_COUNTING)
PRINT ("monotouch_create_managed_ref (%s Handle=%p) retainCount=%d; HasManagedRef=%i GCHandle=%i IsUserType=%i\n",
class_getName ([self class]), self, get_safe_retainCount (self), user_type ? xamarin_has_managed_ref (self) : 666, user_type ? get_gchandle (self) : 666, user_type);
#endif
xamarin_set_nsobject_flags ((MonoObject *) managed_object, xamarin_get_nsobject_flags ((MonoObject *) managed_object) | NSObjectFlagsHasManagedRef);
if (user_type) {
gchandle = get_gchandle (self);
if (!gchandle) {
xamarin_create_gchandle (self, managed_object, MANAGED_REF_BIT, !retain);
} else {
#if defined(DEBUG_REF_COUNTING)
xamarin_assertion_message ("GCHandle already exists for %p: %d\n", self, gchandle);
#endif
}
}
if (retain) {
MONO_ENTER_GC_SAFE;
[self retain];
MONO_EXIT_GC_SAFE;
}
mt_dummy_use (managed_object);
}
/*
* Block support
*/
typedef struct {
MonoMethod *method;
int par;
} MethodAndPar;
static gboolean
method_and_par_compare (gconstpointer l, gconstpointer r)
{
MethodAndPar *f = (MethodAndPar *)l;
MethodAndPar *g = (MethodAndPar *) r;
return f->method == g->method && f->par == g->par;
}
static unsigned int
method_and_par_hash (gconstpointer l)
{
MethodAndPar *x = (MethodAndPar *) l;
return (unsigned int) (intptr_t) x->method;
}
static pthread_mutex_t wrapper_hash_lock = PTHREAD_MUTEX_INITIALIZER;
static MonoReferenceQueue *block_wrapper_queue;
/*
* Given a MonoMethod and a parameter, lookup the MethodInfo (MonoReflectionMethod)
* that can be used to create a new delegate, this returns the method that can
* create the method
*/
static GCHandle
get_method_block_wrapper_creator (MonoMethod *method, int par, guint32 *exception_gchandle)
{
// COOP: accesses managed memory: unsafe mode.
MONO_ASSERT_GC_UNSAFE;
MonoObject *res = NULL;
MethodAndPar mp, *nmp;
mp.method = method;
mp.par = par;
// PRINT ("Looking up method and par (%x and %d)", (int) method, par);
MONO_ENTER_GC_SAFE;
pthread_mutex_lock (&wrapper_hash_lock);
MONO_EXIT_GC_SAFE;
if (xamarin_wrapper_hash == NULL) {
xamarin_wrapper_hash = mono_g_hash_table_new_type (method_and_par_hash, method_and_par_compare, MONO_HASH_VALUE_GC);
}
res = (MonoObject *) mono_g_hash_table_lookup (xamarin_wrapper_hash, &mp);
pthread_mutex_unlock (&wrapper_hash_lock);
if (res != NULL){
// PRINT ("Found match: %x", (int) res);
return xamarin_gchandle_new (res, false);
}
res = xamarin_get_block_wrapper_creator (mono_method_get_object (mono_domain_get (), method, NULL), (int) par, exception_gchandle);
if (*exception_gchandle != 0)
return INVALID_GCHANDLE;
// PRINT ("New value: %x", (int) res);
nmp = (MethodAndPar *) malloc (sizeof (MethodAndPar));
*nmp = mp;
MONO_ENTER_GC_SAFE;
pthread_mutex_lock (&wrapper_hash_lock);
MONO_EXIT_GC_SAFE;
mono_g_hash_table_insert (xamarin_wrapper_hash, nmp, res);
pthread_mutex_unlock (&wrapper_hash_lock);
return xamarin_gchandle_new (res, false);
}
void
xamarin_release_block_on_main_thread (void *obj)
{
if ([NSThread isMainThread]) {
_Block_release (obj);
} else {
dispatch_async_f (dispatch_get_main_queue (), obj, (dispatch_function_t) _Block_release);
}
}
/*
* Creates a System.Delegate to wrap an Objective-C proxy when surfacing parameters from Objective-C to C#.
* @method: method where the parameter is found
* @par: index of the parameter that is a delegate
*
* Given a method, and a parameter index that we have previously probed to be a Delegate,
* this method returns a strongly typed System.Delegate that wraps the underlying
* Objective-C block.
*
* This works by enlisting the help of the C# runtime to find a [BlockProxy] attrbute
* on the parameter of the function, or in one of the base definitions. That attribute
* contains a link to a proxy type that can create the delegate, which we in turn invoke
*
* Returns: the instantiated delegate.
*/
MonoObject *
xamarin_get_delegate_for_block_parameter (MonoMethod *method, guint32 token_ref, int par, void *nativeBlock, guint32 *exception_gchandle)
{
// COOP: accesses managed memory: unsafe mode.
MONO_ASSERT_GC_UNSAFE;
MonoObject *delegate = NULL;
GCHandle obj_handle = INVALID_GCHANDLE;
if (nativeBlock == NULL)
return NULL;
if (token_ref != INVALID_TOKEN_REF) {
obj_handle = xamarin_get_method_from_token (token_ref, exception_gchandle);
} else {
obj_handle = get_method_block_wrapper_creator (method, par, exception_gchandle);
}
if (*exception_gchandle != 0)
goto cleanup;
/* retain or copy (if it's a stack block) the block */
nativeBlock = _Block_copy (nativeBlock);
delegate = xamarin_create_block_proxy (obj_handle, nativeBlock, exception_gchandle);
if (*exception_gchandle != 0) {
_Block_release (nativeBlock);
delegate = NULL;
goto cleanup;
}
MONO_ENTER_GC_SAFE;
pthread_mutex_lock (&wrapper_hash_lock);
MONO_EXIT_GC_SAFE;
if (block_wrapper_queue == NULL)
block_wrapper_queue = mono_gc_reference_queue_new (xamarin_release_block_on_main_thread);
mono_gc_reference_queue_add (block_wrapper_queue, delegate, nativeBlock);
pthread_mutex_unlock (&wrapper_hash_lock);
cleanup:
xamarin_gchandle_free (obj_handle);
return delegate;
}
id
xamarin_get_block_for_delegate (MonoMethod *method, MonoObject *delegate, const char *signature, guint32 token_ref, guint32 *exception_gchandle)
{
// COOP: accesses managed memory: unsafe mode.
return xamarin_create_delegate_proxy (mono_method_get_object (mono_domain_get (), method, NULL), delegate, signature, token_ref, exception_gchandle);
}
void
xamarin_set_use_sgen (bool value)
{
}
bool
xamarin_get_use_sgen ()
{
return true;
}
void
xamarin_set_gc_pump_enabled (bool value)
{
#if DEBUG
// COOP: no managed memory access: any mode.
xamarin_gc_pump = value;
#endif
}
const char *
xamarin_skip_encoding_flags (const char *encoding)
{
// COOP: no managed memory access: any mode.
while (true) {
switch (*encoding) {
case 'r': // const
case 'n': // in
case 'N': // inout
case 'o': // out
case 'O': // bycopy
case 'R': // byref
case 'V': // oneway
encoding++;
continue;
default:
return encoding;
}
}
}
void
xamarin_process_nsexception (NSException *ns_exception)
{
xamarin_process_nsexception_using_mode (ns_exception, false);
}
void
xamarin_process_nsexception_using_mode (NSException *ns_exception, bool throwManagedAsDefault)
{
XamarinGCHandle *exc_handle;
guint32 exception_gchandle = 0;
MarshalObjectiveCExceptionMode mode;
mode = xamarin_on_marshal_objectivec_exception (ns_exception, throwManagedAsDefault, &exception_gchandle);
if (exception_gchandle != 0) {
PRINT (PRODUCT ": Got an exception while executing the MarshalObjectiveCException event (this exception will be ignored):");
PRINT ("%@", print_all_exceptions (mono_gchandle_get_target (exception_gchandle)));
mono_gchandle_free (exception_gchandle);
exception_gchandle = 0;
}
if (mode == MarshalObjectiveCExceptionModeDefault)
mode = xamarin_is_gc_coop ? MarshalObjectiveCExceptionModeThrowManagedException : MarshalObjectiveCExceptionModeUnwindManagedCode;
switch (mode) {
case MarshalObjectiveCExceptionModeUnwindManagedCode:
if (xamarin_is_gc_coop)
xamarin_assertion_message ("Cannot unwind managed frames for Objective-C exceptions when the GC is in cooperative mode.");
@throw ns_exception;
break;
case MarshalObjectiveCExceptionModeThrowManagedException:
exc_handle = [[ns_exception userInfo] objectForKey: @"XamarinManagedExceptionHandle"];
if (exc_handle != NULL) {
uint32_t handle = [exc_handle getHandle];
MONO_ENTER_GC_UNSAFE;
MonoObject *exc = mono_gchandle_get_target (handle);
mono_runtime_set_pending_exception ((MonoException *) exc, false);
MONO_EXIT_GC_UNSAFE;
} else {
uint32_t handle = (uint32_t) xamarin_create_ns_exception (ns_exception, &exception_gchandle);
if (exception_gchandle != 0) {
PRINT (PRODUCT ": Got an exception while creating a managed NSException wrapper (will throw this exception instead):");
PRINT ("%@", print_all_exceptions (mono_gchandle_get_target (exception_gchandle)));
handle = exception_gchandle;
exception_gchandle = 0;
}
MONO_ENTER_GC_UNSAFE;
MonoObject *exc = mono_gchandle_get_target (handle);
mono_runtime_set_pending_exception ((MonoException *) exc, false);
mono_gchandle_free (handle);
MONO_EXIT_GC_UNSAFE;
}
break;
case MarshalObjectiveCExceptionModeAbort:
default:
xamarin_assertion_message ("Aborting due to unhandled Objective-C exception:\n%s\n", [[ns_exception description] UTF8String]);
break;
}
}
void
xamarin_process_managed_exception (MonoObject *exception)
{
if (exception == NULL)
return;
MarshalManagedExceptionMode mode;
guint32 exception_gchandle = 0;
uint32_t handle = mono_gchandle_new (exception, false);
mode = xamarin_on_marshal_managed_exception ((int) handle, &exception_gchandle);
mono_gchandle_free (handle);
if (exception_gchandle != 0) {
PRINT (PRODUCT ": Got an exception while executing the MarshalManagedException event (this exception will be ignored):");
PRINT ("%@", print_all_exceptions (mono_gchandle_get_target (exception_gchandle)));
mono_gchandle_free (exception_gchandle);
exception_gchandle = 0;
mode = MarshalManagedExceptionModeDefault;
}
if (mode == MarshalManagedExceptionModeDefault)
mode = xamarin_is_gc_coop ? MarshalManagedExceptionModeThrowObjectiveCException : MarshalManagedExceptionModeUnwindNativeCode;
switch (mode) {
case MarshalManagedExceptionModeDisable:
case MarshalManagedExceptionModeUnwindNativeCode:
if (xamarin_is_gc_coop)
xamarin_assertion_message ("Cannot unwind native frames for managed exceptions when the GC is in cooperative mode.");
//
// We want to maintain the original stack trace of the exception, but unfortunately
// calling mono_raise_exception directly with the original exception will overwrite
// the original stack trace.
//
// The good news is that the managed ExceptionDispatchInfo class is able to capture
// a stack trace for an exception and show it later.
//
// The xamarin_rethrow_managed_exception method will use ExceptionDispatchInfo
// to throw an exception that contains the original stack trace.
//
handle = mono_gchandle_new (exception, false);
xamarin_rethrow_managed_exception (handle, &exception_gchandle);
mono_gchandle_free (handle);
if (exception_gchandle == 0) {
PRINT (PRODUCT ": Did not get a rethrow exception, will throw the original exception. The original stack trace will be lost.");
} else {
exception = mono_gchandle_get_target (exception_gchandle);
mono_gchandle_free (exception_gchandle);
}
mono_raise_exception ((MonoException *) exception);
break;
case MarshalManagedExceptionModeThrowObjectiveCException: {
uint32_t handle = mono_gchandle_new (exception, false);
NSException *ns_exc = xamarin_unwrap_ns_exception (handle, &exception_gchandle);
if (exception_gchandle != 0) {
PRINT (PRODUCT ": Got an exception while unwrapping a managed NSException wrapper (this exception will be ignored):");
PRINT ("%@", print_all_exceptions (mono_gchandle_get_target (exception_gchandle)));
mono_gchandle_free (exception_gchandle);
exception_gchandle = 0;
ns_exc = NULL;
}
if (ns_exc != NULL) {
mono_gchandle_free (handle);
@throw ns_exc;
} else {
// Strangely enough the thread might be detached, if xamarin_process_managed_exception was called from
// xamarin_ftnptr_exception_handler for an exception that occurred in a reverse delegate that
// was called on a detached thread, since in that case the native-to-managed wrapper will have
// returned the thread to a detached state after calling the managed function.
NSString *name;
NSString *reason;
NSDictionary *userInfo;
const char *fullname;
MONO_THREAD_ATTACH; // COOP: will switch to GC_UNSAFE
fullname = xamarin_type_get_full_name (mono_class_get_type (mono_object_get_class (exception)), &exception_gchandle);
if (exception_gchandle != 0) {
PRINT (PRODUCT ": Got an exception when trying to get the typename for an exception (this exception will be ignored):");
PRINT ("%@", print_all_exceptions (mono_gchandle_get_target (exception_gchandle)));
mono_gchandle_free (exception_gchandle);
exception_gchandle = 0;
fullname = "Unknown";
}
name = [NSString stringWithUTF8String: fullname];
char *message = fetch_exception_property_string (exception, "get_Message", true);
reason = [NSString stringWithUTF8String: message];
mono_free (message);
userInfo = [NSDictionary dictionaryWithObject: [XamarinGCHandle createWithHandle: handle] forKey: @"XamarinManagedExceptionHandle"];
MONO_THREAD_DETACH; // COOP: this will switch to GC_SAFE
@throw [[NSException alloc] initWithName: name reason: reason userInfo: userInfo];
}
break;
}
case MarshalManagedExceptionModeAbort:
default:
xamarin_assertion_message ("Aborting due to trying to marshal managed exception:\n%s\n", [print_all_exceptions (exception) UTF8String]);
break;
}
}
void
xamarin_throw_product_exception (int code, const char *message)
{
xamarin_process_managed_exception_gchandle (xamarin_create_product_exception (code, message));
}
guint32
xamarin_create_product_exception (int code, const char *message)
{
return xamarin_create_product_exception_with_inner_exception (code, 0, message);
}
guint32
xamarin_create_product_exception_with_inner_exception (int code, guint32 inner_exception_gchandle, const char *message)
{
guint32 exception_gchandle = 0;
guint32 handle = xamarin_create_product_exception_for_error (code, inner_exception_gchandle, message, &exception_gchandle);
if (exception_gchandle != 0)
return exception_gchandle;
return handle;
}
void
xamarin_insert_dllmap ()
{
#if defined (OBJC_ZEROCOST_EXCEPTIONS) && (defined (__i386__) || defined (__x86_64__))
if (xamarin_marshal_objectivec_exception_mode == MarshalObjectiveCExceptionModeDisable)
return;
#if DYLIB
const char *lib = "libxammac.dylib";
#else
const char *lib = "__Internal";
#endif
mono_dllmap_insert (NULL, "/usr/lib/libobjc.dylib", "objc_msgSend", lib, "xamarin_dyn_objc_msgSend");
mono_dllmap_insert (NULL, "/usr/lib/libobjc.dylib", "objc_msgSendSuper", lib, "xamarin_dyn_objc_msgSendSuper");
mono_dllmap_insert (NULL, "/usr/lib/libobjc.dylib", "objc_msgSend_stret", lib, "xamarin_dyn_objc_msgSend_stret");
mono_dllmap_insert (NULL, "/usr/lib/libobjc.dylib", "objc_msgSendSuper_stret", lib, "xamarin_dyn_objc_msgSendSuper_stret");
LOG (PRODUCT ": Added dllmap for objc_msgSend");
#endif // defined (__i386__) || defined (__x86_64__)
}
void
xamarin_printf (const char *format, ...)
{
va_list list;
va_start (list, format);
xamarin_vprintf (format, list);
va_end (list);
}
void
xamarin_vprintf (const char *format, va_list args)
{
NSString *message = [[NSString alloc] initWithFormat: [NSString stringWithUTF8String: format] arguments: args];
#if TARGET_OS_WATCH && defined (__arm__) // maybe make this configurable somehow?
const char *msg = [message UTF8String];
NSUInteger len = [message lengthOfBytesUsingEncoding:NSUTF8StringEncoding] + 1; // does not include NULL
fwrite (msg, 1, len, stdout);
if (len == 0 || msg [len - 1] != '\n')
fwrite ("\n", 1, 1, stdout);
fflush (stdout);
#else
NSLog (@"%@", message);
#endif
[message release];
}
/*
* File/resource lookup for assemblies
*
* Assemblies can be found in several locations:
* 1. If the assembly is compiled to a framework, in the framework's MonoBundle directory.
* For extensions the framework may be in the containing app's Frameworks directory.
* If an extension is sharing code with the main app, then the assemblies whose build target isn't 'framework' will be located in the container app's root directory.
* A framework may contain multiple assemblies, so it's not possible to deduce the framework name from the assembly name.
* 2. If the assembly is not a framework, in the app's root directory.
*
* The platform assembly (Xamarin.[iOS|TVOS|WatchOS].dll) and any assemblies
* the platform assembly references (mscorlib.dll, System.dll) may be in a
* pointer-size subdirectory (ARCH_SUBDIR).
*
* AOT data files will have an arch-specific infix.
*/
void
xamarin_get_assembly_name_without_extension (const char *aname, char *name, size_t namelen)
{
size_t len = strnlen (aname, namelen);
if (len == namelen)
return;
strlcpy (name, aname, namelen);
if (namelen <= 4 || len <= 4)
return;
const char *ext = name + (len - 4);
if (!strncmp (".exe", ext, 4) || !strncmp (".dll", ext, 4))
name [len - 4] = 0; // strip off any extensions.
}
static bool
xamarin_locate_assembly_resource_for_root (const char *root, const char *culture, const char *resource, char *path, size_t pathlen)
{
if (culture != NULL && *culture != 0) {
// culture-specific directory
if (snprintf (path, pathlen, "%s/%s/%s", root, culture, resource) < 0) {
LOG (PRODUCT ": Failed to construct path for assembly resource (root directory: '%s', culture: '%s', resource: '%s'): %s", root, culture, resource, strerror (errno));
return false;
} else if (xamarin_file_exists (path)) {
return true;
}
}
// arch-specific extension
if (snprintf (path, pathlen, "%s/%s.%s", root, resource, xamarin_arch_name) < 0) {
LOG (PRODUCT ": Failed to construct path for resource: %s (4): %s", resource, strerror (errno));
return false;
} else if (xamarin_file_exists (path)) {
return true;
}
#if !MONOMAC
// pointer-size subdirectory with arch-specific extension
if (snprintf (path, pathlen, "%s/%s/%s.%s", root, ARCH_SUBDIR, resource, xamarin_arch_name) < 0) {
LOG (PRODUCT ": Failed to construct path for resource: %s (5): %s", resource, strerror (errno));
return false;
} else if (xamarin_file_exists (path)) {
return true;
}
// pointer-size subdirectory
if (snprintf (path, pathlen, "%s/%s/%s", root, ARCH_SUBDIR, resource) < 0) {
LOG (PRODUCT ": Failed to construct path for resource: %s (5): %s", resource, strerror (errno));
return false;
} else if (xamarin_file_exists (path)) {
return true;
}
#endif // !MONOMAC
// just the file, no extensions, etc.
if (snprintf (path, pathlen, "%s/%s", root, resource) < 0) {
LOG (PRODUCT ": Failed to construct path for resource: %s (6): %s", resource, strerror (errno));
return false;
} else if (xamarin_file_exists (path)) {
return true;
}
return false;
}
bool
xamarin_locate_assembly_resource_for_name (MonoAssemblyName *assembly_name, const char *resource, char *path, size_t pathlen)
{
const char *culture = mono_assembly_name_get_culture (assembly_name);
const char *aname = mono_assembly_name_get_name (assembly_name);
return xamarin_locate_assembly_resource (aname, culture, resource, path, pathlen);
}
// #define LOG_RESOURCELOOKUP(...) do { NSLog (@ __VA_ARGS__); } while (0);
#define LOG_RESOURCELOOKUP(...)
bool
xamarin_locate_assembly_resource (const char *assembly_name, const char *culture, const char *resource, char *path, size_t pathlen)
{
char root [1024];
char aname [256];
const char *app_path;
LOG_RESOURCELOOKUP (PRODUCT ": Locating the resource '%s' for the assembly '%s' (culture: '%s').", resource, assembly_name, culture);
app_path = xamarin_get_bundle_path ();
xamarin_get_assembly_name_without_extension (assembly_name, aname, sizeof (aname));
// First check if the directory is explicitly set. This directory is relative to the app bundle.
// For extensions this might be a relative path that points to container app (../../Frameworks/...).
const char *explicit_location = xamarin_find_assembly_directory (aname);
if (explicit_location) {
snprintf (root, sizeof (root), "%s/%s", app_path, explicit_location);
if (xamarin_locate_assembly_resource_for_root (root, culture, resource, path, pathlen)) {
LOG_RESOURCELOOKUP (PRODUCT ": Located resource '%s' from explicit path '%s': %s\n", resource, explicit_location, path);
return true;
}
// If we have an explicit location, then that's where the assembly must be.
LOG_RESOURCELOOKUP (PRODUCT ": Could not find the resource '%s' for the assembly '%s' (culture: '%s') in the explicit path '%s'.", resource, assembly_name, culture, explicit_location);
return false;
}
// The root app directory
if (xamarin_locate_assembly_resource_for_root (app_path, culture, resource, path, pathlen)) {
LOG_RESOURCELOOKUP (PRODUCT ": Located resource '%s' from app bundle: %s\n", resource, path);
return true;
}
#if !MONOMAC && (defined(__i386__) || defined (__x86_64__))
// In the simulator we also check in a 'simulator' subdirectory. This is
// so that we can create a framework that works for both simulator and
// device, without affecting device builds in any way (device-specific
// files just go in the MonoBundle directory)
snprintf (root, sizeof (root), "%s/Frameworks/%s.framework/MonoBundle/simulator", app_path, aname);
if (xamarin_locate_assembly_resource_for_root (root, culture, resource, path, pathlen)) {
LOG_RESOURCELOOKUP (PRODUCT ": Located resource '%s' from framework '%s': %s\n", resource, aname, path);
return true;
}
#endif // !MONOMAC
// Then in a framework named as the assembly
snprintf (root, sizeof (root), "%s/Frameworks/%s.framework/MonoBundle", app_path, aname);
if (xamarin_locate_assembly_resource_for_root (root, culture, resource, path, pathlen)) {
LOG_RESOURCELOOKUP (PRODUCT ": Located resource '%s' from framework '%s': %s\n", resource, aname, path);
return true;
}
// Then in the container app's root directory (for extensions)
if (xamarin_launch_mode == XamarinLaunchModeExtension) {
snprintf (root, sizeof (root), "../..");
if (xamarin_locate_assembly_resource_for_root (root, culture, resource, path, pathlen)) {
LOG_RESOURCELOOKUP (PRODUCT ": Located resource '%s' from container app bundle '%s': %s\n", resource, aname, path);
return true;
}
}
return false;
}
void
xamarin_set_assembly_directories (struct AssemblyLocations *directories)
{
if (options.AssemblyLocations)
xamarin_assertion_message ("Assembly directories already set.");
options.AssemblyLocations = directories;
}
static int
compare_assembly_location (const void *key, const void *value)
{
return strcmp ((const char *) key, ((struct AssemblyLocation *) value)->assembly_name);
}
const char *
xamarin_find_assembly_directory (const char *assembly_name)
{
if (options.AssemblyLocations == NULL)
return NULL;
struct AssemblyLocation *entry;
entry = (struct AssemblyLocation *) bsearch (assembly_name, options.AssemblyLocations->locations, options.AssemblyLocations->length, sizeof (struct AssemblyLocation), compare_assembly_location);
return entry ? entry->location : NULL;
}
MonoMethod *
xamarin_get_managed_method_for_token (guint32 token_ref, guint32 *exception_gchandle)
{
MonoReflectionMethod *reflection_method;
reflection_method = (MonoReflectionMethod *) xamarin_gchandle_unwrap (xamarin_get_method_from_token (token_ref, exception_gchandle));
if (*exception_gchandle != 0) return NULL;
return xamarin_get_reflection_method_method (reflection_method);
}
GCHandle
xamarin_gchandle_new (MonoObject *obj, bool track_resurrection)
{
if (obj == NULL)
return INVALID_GCHANDLE;
return GINT_TO_POINTER (mono_gchandle_new (obj, track_resurrection));
}
GCHandle
xamarin_gchandle_new_weakref (MonoObject *obj, bool pinned)
{
if (obj == NULL)
return INVALID_GCHANDLE;
return GINT_TO_POINTER (mono_gchandle_new_weakref (obj, pinned));
}
MonoObject *
xamarin_gchandle_get_target (GCHandle handle)
{
if (handle == INVALID_GCHANDLE)
return NULL;
return mono_gchandle_get_target (GPOINTER_TO_UINT (handle));
}
void
xamarin_gchandle_free (GCHandle handle)
{
if (handle == INVALID_GCHANDLE)
return;
mono_gchandle_free (GPOINTER_TO_UINT (handle));
}
MonoObject *
xamarin_gchandle_unwrap (GCHandle handle)
{
if (handle == INVALID_GCHANDLE)
return NULL;
MonoObject *rv = mono_gchandle_get_target (GPOINTER_TO_UINT (handle));
mono_gchandle_free (GPOINTER_TO_UINT (handle));
return rv;
}
/*
* Object unregistration:
*
* This is a difficult problem, because we need to be able
* to lookup the managed object (given the native object)
* from inside the dealloc implementation in super classes.
*
* Example:
*
* MyManagedObject : MyNativeObject : NSObject
*
* -[MyNativeObject dealloc] may call selectors, and in
* that case we need to be able to look up the managed
* object to invoke the corresponding managed methods.
*
* See 02474ac1dd and bug #24609.
*
* Note: it is not recommmended to invoke selectors from the
* dealloc method, but iOS still does it (and third-party
* libraries as well), so we have to cope with it.
*
* At the same time we must also not unregister after
* the native memory has been freed, because then there's
* a race condition where another thread can create a
* new native object with the same pointer and end up in
* managed code, in which case we might find the managed
* object for the first native object (which has been freed,
* but not unregistered yet), and random InvalidCastExceptions
* occur.
*
* See bug #29801.
*
* Solution:
*
* Fortunately the complete deallocation sequence is documented:
01 // The documented Deallocation Timeline (WWDC 2011, Session 322, 36:22).
02 // 1. -release to zero
03 // * Object is now deallocating and will die.
04 // * New __weak references are not allowed, and will get nil.
05 // * [self dealloc] is called
06 // 2. Subclass -dealloc
07 // * bottom-most subclass -dealloc is called
08 // * Non-ARC code manually releases iVars
09 // * Walk the super-class chain calling -dealloc
10 // 3. NSObject -dealloc
11 // * Simply calls the ObjC runtime object_dispose()
12 // 4. object_dispose()
13 // * Call destructors for C++ iVars
14 // * Call -release for ARC iVars
15 // * Erase associated references
16 // * Erase __weak references
17 // * Call free()
* and our solution to the problem is:
*
* a) For the static registrar, we execute code at line #13 above, by
* adding a C++ iVar (with a destructor). This is the XamarinObject
* type.
*
* b) For the dynamic registrar we have to use another solution, because
* it's not possible to add C++ iVars dynamically. Instead we create
* a new native object (XamarinAssociatedObject), which we associate
* with the native object we're tracking, and which will be deallocated
* when the tracked native object is erasing its associated references
* (line #15) from above.
*
* Associated references is a lot slower and more memory hungry than the C++
* iVar, which is why we're not using associated references in all cases.
*
*/
/*
* XamarinObject
*
* With the static registrar we add a C++ ivar, and
* in the destructor we unregister the managed instance.
*/
XamarinObject::~XamarinObject ()
{
// COOP: no managed memory access: any mode.
@try {
xamarin_notify_dealloc (native_object, gc_handle & ~GCHANDLE_MASK);
} @catch (NSException *ex) {
NSLog (@"%@", ex);
}
native_object = NULL;
gc_handle = 0;
}
/*
* XamarinAssociatedObject
*
* With the dynamic registrar we associate an instance
* of this object to every user type, and in this object's
* dealloc method we unregister the managed instance.
*/
@implementation XamarinAssociatedObject
-(void) dealloc
{
// COOP: no managed memory access: any mode.
xamarin_notify_dealloc (native_object, gc_handle & ~GCHANDLE_MASK);
native_object = NULL;
gc_handle = 0;
[super dealloc];
}
@end
/*
* NonXamarinObject category
*
* Inject a default xamarinGetGCHandle implementation into every object,
* so that we don't have to check if a type is a user type before
* calling xamarinGetGCHandle. TODO: verify if this is really faster than
* checking the type first.
*
* Do not add a xamarinSetGCHandle: method, since we use the presence
* of it to detect whether a particular type is a user type or not
* (see is_user_type).
*/
@implementation NSObject (NonXamarinObject)
-(uint32_t) xamarinGetGCHandle
{
// COOP: no managed memory access: any mode.
return 0;
}
@end
#if MONOMAC
void
xamarin_set_is_mkbundle (bool value)
{
if (initialize_started)
xamarin_assertion_message ("Fatal error: xamarin_set_is_mkbundle called after xamarin_initialize.\n");
xamarin_is_mkbundle = value;
}
bool
xamarin_get_is_mkbundle ()
{
return xamarin_is_mkbundle;
}
#endif
void
xamarin_set_is_debug (bool value)
{
if (initialize_started)
xamarin_assertion_message ("Fatal error: xamarin_set_is_debug called after xamarin_initialize.\n");
xamarin_debug_mode = value;
}
bool
xamarin_get_is_debug ()
{
return xamarin_debug_mode;
}
bool
xamarin_is_managed_exception_marshaling_disabled ()
{
#if DEBUG
if (xamarin_is_gc_coop)
return false;
switch (xamarin_marshal_managed_exception_mode) {
case MarshalManagedExceptionModeDefault:
// If all of the following are true:
// * In debug mode
// * Using the default exception marshaling mode
// * The debugger is attached
// Then disable managed exception marshaling.
return mono_is_debugger_attached ();
case MarshalManagedExceptionModeDisable:
return true;
default:
return false;
}
#else
return false;
#endif
}
/*
* XamarinGCHandle
*/
@implementation XamarinGCHandle
+(XamarinGCHandle *) createWithHandle: (uint32_t) h
{
XamarinGCHandle *rv = [[XamarinGCHandle alloc] init];
rv->handle = h;
[rv autorelease];
return rv;
}
-(void) dealloc
{
mono_gchandle_free (handle);
[super dealloc];
}
-(uint32_t) getHandle
{
return handle;
}
@end