Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Fix multi-domain support

Prevent cross-domain managed type sharing by appending '_Token' to all .NET classes registered by the bridge in multi-domain scenarios.

By default the first domain to register with the bridge does not mangle type names. Subsequent bridge bootstraps require a token. The token can be specified in the call to ObjectiveCRuntime.Initialize() or generated automatically for each domain by calling ObjectiveCRuntime.EnableAutoDomainTokens() prior to the first bootstrap.
  • Loading branch information...
commit 53bea8111d7be9f7dc6b166ba1f5cf71850f164a 1 parent fb4c6bd
@aarononeal aarononeal authored
View
12 libraries/Monobjc/Class.cs
@@ -122,13 +122,21 @@ public static bool IsMapped (Type type)
/// </summary>
/// <param name = "type">The type that has a <see cref = "ObjectiveCClassAttribute" />.</param>
/// <returns>The class name.</returns>
- internal static String GetAttributeName (Type type)
+ internal static String GetAttributeName (Type type)
{
ObjectiveCClassAttribute attribute = Attribute.GetCustomAttribute (type, typeof(ObjectiveCClassAttribute)) as ObjectiveCClassAttribute;
if (attribute == null) {
throw new ObjectiveCException (String.Format (CultureInfo.CurrentCulture, Resources.NoClassAttributeFoundForType, type));
}
- return String.IsNullOrEmpty (attribute.Name) ? type.Name : attribute.Name;
+ String name = String.IsNullOrEmpty (attribute.Name) ? type.Name : attribute.Name;
+
+ // Native types are never managled
+ if (attribute.IsNative) {
+ return name;
+ }
+
+ // Managed types other than the primary require the domain token
+ return ObjectiveCRuntime.GetDomainManagledName(name);
}
/// <summary>
View
44 libraries/Monobjc/ObjectiveCRuntime.Utils.cs
@@ -20,6 +20,7 @@
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
+using System;
using System.Runtime.CompilerServices;
using Monobjc.Runtime;
@@ -27,13 +28,20 @@ namespace Monobjc
{
public partial class ObjectiveCRuntime
{
+ private static String domainToken;
+
/// <summary>
/// Bootstraps the bridge.
/// </summary>
[MethodImpl(MethodImplOptions.NoInlining)]
- private static void Bootstrap ()
+ private static void Bootstrap (String domainToken)
{
- BootstrapInternal ();
+ BootstrapInternal (domainToken);
+
+ // The domain token passed in may have been NULL but
+ // the native side may have auto generated a token.
+ // We cache it on the managed side.
+ ObjectiveCRuntime.domainToken = GetDomainToken();
}
/// <summary>
@@ -55,16 +63,46 @@ private static bool Is64BitsInternal ()
return Platform.Is64Bits ();
}
+ /// <summary>
+ /// Enable auto generation of domain tokens.
+ /// </summary>
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ public static void EnableAutoDomainTokens ()
+ {
+ EnableAutoDomainTokensInternal ();
+ }
+
/// <summary>
/// Internal call to bootstrap the bridge.
/// </summary>
[MethodImpl(MethodImplOptions.InternalCall)]
- private static extern void BootstrapInternal ();
+ private static extern void BootstrapInternal (String domainToken);
/// <summary>
/// Internal call to cleanup the bridge.
/// </summary>
[MethodImplAttribute(MethodImplOptions.InternalCall)]
private static extern void CleanUpInternal ();
+
+ /// <summary>
+ /// Internal call to get the domain token.
+ /// </summary>
+ [MethodImplAttribute(MethodImplOptions.InternalCall)]
+ private static extern String GetDomainToken ();
+
+ /// <summary>
+ /// Internal call to enable auto domain tokens.
+ /// </summary>
+ [MethodImplAttribute(MethodImplOptions.InternalCall)]
+ private static extern void EnableAutoDomainTokensInternal ();
+
+ internal static string GetDomainManagledName (String name)
+ {
+ if (string.IsNullOrEmpty(domainToken)) {
+ return name;
+ }
+
+ return String.Concat(name, "_", domainToken);
+ }
}
}
View
20 libraries/Monobjc/ObjectiveCRuntime.cs
@@ -93,6 +93,7 @@ static ObjectiveCRuntime ()
/// <para>This method must be called AFTER the loading of the native framework (see <see cref = "LoadFramework" />) and
/// BEFORE any use of the Objective-C classes.</para>
///</summary>
+ ///<param name="domainToken">The token to use on this domain as a suffix for managed classes registered with Objective-C.</param>
///<example>
/// <para>The following code shows how to use the <see cref = "Initialize()" /> method:</para>
/// <code>
@@ -116,18 +117,27 @@ static ObjectiveCRuntime ()
/// }
/// </code>
///</example>
- public static void Initialize ()
+ ///<remarks>
+ /// <para>The default domain token is null which means that managed classes are registered with Objective-C using the name requested.</para>
+ ///
+ /// <para>If the Objective-C runtime is initialized in multiple domains, each domain must have a unique domain token to avoid
+ /// mixing types across domains. The domain token is appended to class names as they are registered. The domain token can be
+ /// passed during the call to Initialize(), or it can be automatically generated by turning on the option with EnableAutoDomainTokens().</para>
+ ///
+ /// <para>Note that when a token is used, any NSCoder or NSNib resources that specify type names for managed code backed classes
+ /// will not resolve unless they were originally specified using a token matching the domain.</para>
+ ///</remarks>
+ public static void Initialize (String domainToken = null)
{
// Allow multiple calls without issue
if (initialized) {
return;
}
- initialized = true;
Logger.Info ("ObjectiveCRuntime", "Bootstrapping the bridge");
// Create the domain-data
- Bootstrap ();
+ Bootstrap (domainToken);
Logger.Info ("ObjectiveCRuntime", "Platform detected:");
Logger.Info ("ObjectiveCRuntime", " System : " + MacOSVersion);
@@ -161,6 +171,10 @@ public static void Initialize ()
foreach (Assembly assembly in AppDomain.CurrentDomain.GetAssemblies()) {
ScanAssembly (assembly);
}
+
+ // Complete initialization only after making it past any methods that may throw.
+ // This is important when running multi-domain because Bootstrap throws.
+ initialized = true;
}
/// <summary>
View
21 libraries/Monobjc/Runtime/Bridge.Utils.cs
@@ -92,7 +92,13 @@ private static String ExtractClassName (Type type)
if (attribute == null) {
throw new ObjectiveCException (String.Format (CultureInfo.CurrentCulture, Resources.NoClassAttributeFoundForType, type.FullName));
}
- return String.IsNullOrEmpty (attribute.Name) ? type.Name : attribute.Name;
+
+ String name = String.IsNullOrEmpty (attribute.Name) ? type.Name : attribute.Name;
+
+ if (attribute.IsNative) {
+ return name;
+ }
+ return ObjectiveCRuntime.GetDomainManagledName(name);
}
/// <summary>
@@ -109,7 +115,11 @@ private static String ExtractCategoryClassName (Type type)
if (String.IsNullOrEmpty (attribute.Name)) {
throw new ObjectiveCException (String.Format (CultureInfo.CurrentCulture, Resources.NoNameInCategoryAttributeForType, type.FullName));
}
- return attribute.Name;
+
+ if (attribute.IsNative) {
+ return attribute.Name;
+ }
+ return ObjectiveCRuntime.GetDomainManagledName(attribute.Name);
}
/// <summary>
@@ -135,7 +145,12 @@ private static String ExtractSuperClassName (Type type)
throw new ObjectiveCException (String.Format (CultureInfo.CurrentCulture, Resources.NoClassAttributeFoundForBaseType, type.FullName));
}
- return String.IsNullOrEmpty (superObjectiveCClassAttribute.Name) ? type.BaseType.Name : superObjectiveCClassAttribute.Name;
+ String name = String.IsNullOrEmpty (superObjectiveCClassAttribute.Name) ? type.BaseType.Name : superObjectiveCClassAttribute.Name;
+
+ if (superObjectiveCClassAttribute.IsNative) {
+ return name;
+ }
+ return ObjectiveCRuntime.GetDomainManagledName(name);
}
/// <summary>
View
22 libraries/Monobjc/Runtime/Bridge.cs
@@ -71,17 +71,21 @@ public static void DefineClass (ClassGenerator classGenerator, Type type)
return;
}
- // Extract class name from attributes
- String superClassName = ExtractSuperClassName (type);
-
- // Get the superclass
- Class superCls = Class.Get (superClassName);
- if (superCls == null) {
- throw new ObjectiveCException (String.Format (CultureInfo.CurrentCulture, Resources.CannotDefineClassBecauseSuperclassDoesNotExists, type, superClassName));
- }
+ // Objects with an Id base type do not have a superclass
+ String superClassName = null;
+ if (typeof(Id) != type.BaseType) {
+ // Extract class name from attributes
+ superClassName = ExtractSuperClassName (type);
+
+ // Get the superclass
+ Class superCls = Class.Get (superClassName);
+ if (superCls == null) {
+ throw new ObjectiveCException (String.Format (CultureInfo.CurrentCulture, Resources.CannotDefineClassBecauseSuperclassDoesNotExists, type, superClassName));
+ }
+ }
if (Logger.DebugEnabled) {
- Logger.Debug ("Bridge", "Defining class " + type + " <-> " + className + " : " + superClassName);
+ Logger.Debug ("Bridge", "Defining class " + type + " <-> " + className + " : " + superClassName ?? "Id");
}
// Collects the informations needed for class generation
View
10 native/Monobjc/sources/domain.h
@@ -40,6 +40,9 @@ typedef struct MonobjcDomainData {
/** @brief The domain identifier. */
int32_t identifier;
+ /** @brief The domain token to suffix managed types. */
+ char *token;
+
#undef DEFINITION
/** @brief Print the fields for the definitions */
#define DEFINITION(TYPE, NAME) TYPE NAME;
@@ -130,7 +133,7 @@ extern pthread_mutex_t __DOMAINS_MUTEX;
/**
* @brief Create the domain data for the current domain.
*/
-void monobjc_create_domain_data();
+void monobjc_create_domain_data(const char *domain_token);
/**
* @brief Destroy the domain data for the current domain.
@@ -150,4 +153,9 @@ MonobjcDomainData *monobjc_get_domain_data(MonoDomain *domain);
*/
void monobjc_remove_instance_in_domains(void *ptr);
+/**
+ * @brief Enable automatic generation of domain tokens.
+ */
+void monobjc_enable_auto_domain_tokens();
+
#endif // __DOMAIN_H__
View
92 native/Monobjc/sources/domain.mm
@@ -29,6 +29,7 @@
*/
#include "cache.h"
#include "constants.h"
+#include "definitions.h"
#include "domain.h"
#include "glib.h"
#include "logging.h"
@@ -38,37 +39,89 @@
/** @brief List of the domain data. */
static GSList *__DOMAINS_DATA = NULL;
+/** @brief True if domain tokens can be auto-assigned. */
+static boolean_t __enable_auto_domain_tokens = FALSE;
+
+/** @brief True if the caller can still change the auto domain token option. */
+static boolean_t __enable_auto_domain_tokens_mutable = TRUE;
+
+/**
+ * @brief True if at least one domain had an empty domain token and
+ * was not mangling names. There can be only one. Never turn
+ * off the flag once activated.
+ */
+static boolean_t __pass_through_domain_actived;
+
#pragma mark ----- Implementation -----
-void monobjc_create_domain_data() {
- MonoDomain *domain = mono_domain_get();
- MonobjcDomainData *data = monobjc_get_domain_data(domain);
- if (data) {
- // This is fatal and cannot be handled. Raise an exception.
- [NSException raise:[NSString stringWithUTF8String:OBJECTIVE_C_EXCEPTION] format:@"%@ Domain #%d - Thread #%u", @"Cannot call monobjc_create_domain_data twice.", data->identifier, MACH_THREAD_ID];
+void monobjc_create_domain_data(const char *domain_token) {
+ @autoreleasepool {
+ MonoDomain *domain = mono_domain_get();
+ MonobjcDomainData *data = monobjc_get_domain_data(domain);
+ if (data) {
+ // This is fatal and cannot be handled. Raise an exception.
+ [NSException raise:[NSString stringWithUTF8String:OBJECTIVE_C_EXCEPTION] format:@"%@ Domain #%d - Thread #%u", @"Cannot call monobjc_create_domain_data twice.", data->identifier, MACH_THREAD_ID];
+ }
+
+ // Verify that the domain is eligible
+ if (!domain_token) {
+ if (__pass_through_domain_actived) {
+ if (__enable_auto_domain_tokens) {
+ // Generate a new UUID
+ CFUUIDRef uuid_ref = CFUUIDCreate(NULL);
+ NSString *uuid = [(NSString *) CFUUIDCreateString(NULL, uuid_ref) autorelease];
+ CFRelease(uuid_ref);
+
+ // Replace "-" with "_"
+ uuid = [uuid stringByReplacingOccurrencesOfString:@"-" withString:@"_"];
+
+ // Get the final token
+ domain_token = [uuid UTF8String];
+ } else {
+ [NSException raise:[NSString stringWithUTF8String:OBJECTIVE_C_EXCEPTION] format:@"%@ Domain #%d - Thread #%u", @"Cannot bootstrap a second domain without a token for name mangling because a pass through domain already exists and auto domain tokens are disabled.", mono_domain_get_id(domain), MACH_THREAD_ID];
+ }
+ } else {
+ __pass_through_domain_actived = TRUE;
+ }
+ }
+
+ // A domain has been created, don't allow the setting to change
+ __enable_auto_domain_tokens_mutable = FALSE;
+
+ // Create the domain data
+ data = g_new(MonobjcDomainData, 1);
+ data->identifier = mono_domain_get_id(domain);
+ data->token = g_strdup(domain_token);
+
+ LOG_INFO(MONOBJC_DOMAIN_GENERAL, "Creating Monobjc domain data for domain #%d with token '%s' from thread #%u", data->identifier, data->token == NULL ? "none" : data->token, MACH_THREAD_ID);
+ __DOMAINS_DATA = g_slist_append(__DOMAINS_DATA, data);
}
- data = g_new(MonobjcDomainData, 1);
- data->identifier = mono_domain_get_id(domain);
- LOG_INFO(MONOBJC_DOMAIN_GENERAL, "Creating Monobjc domain data for domain #%d from thread #%u", data->identifier, MACH_THREAD_ID);
- __DOMAINS_DATA = g_slist_append(__DOMAINS_DATA, data);
}
void monobjc_destroy_domain_data() {
MonoDomain *domain = mono_domain_get();
MonobjcDomainData *data = monobjc_get_domain_data(domain);
if (!data) {
- // This is fatal and cannot be handled. Raise an exception.
- [NSException raise:[NSString stringWithUTF8String:OBJECTIVE_C_EXCEPTION] format:@"%@ Domain #%d - Thread #%u", @"Cannot call monobjc_destroy_domain_data on NULL data.", mono_domain_get_id(domain), MACH_THREAD_ID];
+ @autoreleasepool {
+ // This is fatal and cannot be handled. Raise an exception.
+ [NSException raise:[NSString stringWithUTF8String:OBJECTIVE_C_EXCEPTION] format:@"%@ Domain #%d - Thread #%u", @"Cannot call monobjc_destroy_domain_data on NULL data.", mono_domain_get_id(domain), MACH_THREAD_ID];
+ }
} else {
LOG_INFO(MONOBJC_DOMAIN_GENERAL, "Destroying Monobjc domain data for domain #%d from thread #%u", data->identifier, MACH_THREAD_ID);
__DOMAINS_DATA = g_slist_remove(__DOMAINS_DATA, data);
+ if (!__DOMAINS_DATA) {
+ __enable_auto_domain_tokens_mutable = TRUE;
+ }
+ g_free(data->token);
g_free(data);
}
}
MonobjcDomainData *monobjc_get_domain_data(MonoDomain *domain) {
if (domain == NULL) {
- [NSException raise:NSInvalidArgumentException format:@"The domain is NULL. Verify that the thread has been attached."];
+ @autoreleasepool {
+ [NSException raise:NSInvalidArgumentException format:@"The domain is NULL. Verify that the thread has been attached."];
+ }
}
int32_t identifier = mono_domain_get_id(domain);
@@ -98,3 +151,16 @@ void monobjc_remove_instance_in_domains(void *ptr) {
}
UNLOCK_DOMAINS();
}
+
+void monobjc_enable_auto_domain_tokens() {
+ if (__enable_auto_domain_tokens) {
+ return;
+ }
+
+ if (!__enable_auto_domain_tokens_mutable) {
+ @autoreleasepool {
+ [NSException raise:[NSString stringWithUTF8String:OBJECTIVE_C_EXCEPTION] format:@"%@", @"Cannot enable auto domain tokens because a domain has already been initialized."];
+ }
+ }
+ __enable_auto_domain_tokens = TRUE;
+}
View
4 native/Monobjc/sources/icalls.def
@@ -79,9 +79,11 @@ ICALL("GetTypeAlignmentInternal", int32_t, icall_Monobjc_ObjectiveC
#pragma mark ----- Internal Calls for Monobjc.ObjectiveCRuntime -----
#define ICALLTYPE "Monobjc.ObjectiveCRuntime"
-ICALL("BootstrapInternal", void, icall_Monobjc_ObjectiveCRuntime_Bootstrap, (void))
+ICALL("BootstrapInternal", void, icall_Monobjc_ObjectiveCRuntime_Bootstrap, (MonoString *domain_token))
ICALL("CleanUpInternal", void, icall_Monobjc_ObjectiveCRuntime_CleanUp, (void))
ICALL("GetInstanceInternal", MonoObject *, icall_Monobjc_ObjectiveCRuntime_GetInstance, (MonoType *type, void *ptr, RetrievalMode mode))
+ICALL("EnableAutoDomainTokensInternal", void, icall_Monobjc_ObjectiveCRuntime_EnableAutoDomainTokens, (void))
+ICALL("GetDomainToken", MonoString *, icall_Monobjc_ObjectiveCRuntime_GetDomainToken, (void))
#undef ICALLTYPE
#pragma mark ----- Internal Calls for Monobjc.Runtime.Bridge -----
View
78 native/Monobjc/sources/icalls/icalls-Monobjc.ObjectiveCRuntime.mm
@@ -44,16 +44,42 @@
/**
* @brief Internal call to bootstrap the bridge.
*/
-void icall_Monobjc_ObjectiveCRuntime_Bootstrap(void) {
+void icall_Monobjc_ObjectiveCRuntime_Bootstrap(MonoString *domain_token) {
LOG_INFO(MONOBJC_DOMAIN_GENERAL, "Bootstrapping the bridge...");
- LOCK_DOMAINS();
- monobjc_create_domain_data();
- monobjc_create_definitions();
- monobjc_create_default_descriptors();
- monobjc_create_cache_for_calls();
- monobjc_create_caches();
- UNLOCK_DOMAINS();
+ // Put a pool in place since we're going to use autoreleased Objective-C
+ // objects in some of these methods.
+ @autoreleasepool {
+ MonoException *exc = NULL;
+
+ char *domain_token_utf8 = domain_token == NULL ? NULL : mono_string_to_utf8(domain_token);
+
+ LOCK_DOMAINS();
+ @try {
+ monobjc_create_domain_data(domain_token_utf8);
+ monobjc_create_definitions();
+ monobjc_create_default_descriptors();
+ monobjc_create_cache_for_calls();
+ monobjc_create_caches();
+ }
+ @catch (NSException *ex) {
+ LOG_DEBUG(MONOBJC_DOMAIN_GENERAL, "Native exception caught and rethrown as managed: %s", [[ex description] UTF8String]);
+
+ // Encapsulate the native exception (before the domain data is initialized)
+ MonoAssembly *assembly = monobjc_define_assembly(MONOBJC);
+ MonoImage *image = monobjc_define_image(assembly);
+ exc = mono_exception_from_name_msg(image, MONOBJC, OBJECTIVE_C_EXCEPTION, [[ex description] UTF8String]);
+ }
+ @finally {
+ UNLOCK_DOMAINS();
+ g_free(domain_token_utf8);
+ }
+
+ // If there is an exception, raise it now
+ if (exc) {
+ mono_raise_exception(exc);
+ }
+ }
}
/**
@@ -130,7 +156,7 @@ void icall_Monobjc_ObjectiveCRuntime_CleanUp(void) {
// The wrapper is either not found or needs re-wrapping
if (!wrapper) {
- LOG_DEBUG(MONOBJC_DOMAIN_INSTANCES, "icall_Monobjc_ObjectiveCRuntime_GetInstanceInternal - Creating new instance for %p", ptr);
+ LOG_DEBUG(MONOBJC_DOMAIN_INSTANCES, "icall_Monobjc_ObjectiveCRuntime_GetInstanceInternal - Creating new instance for %p of requested type %s", ptr, mono_class_get_name(mono_type_get_class(type)));
MonoType *wrapper_type = type;
@@ -170,3 +196,37 @@ void icall_Monobjc_ObjectiveCRuntime_CleanUp(void) {
return wrapper;
}
+
+/**
+ * @brief Internal call to enable automatic generation of domain tokens.
+ */
+void icall_Monobjc_ObjectiveCRuntime_EnableAutoDomainTokens() {
+ // Put a pool in place since we're going to use autoreleased Objective-C
+ // objects in some of these methods.
+ @autoreleasepool {
+ @try {
+ monobjc_enable_auto_domain_tokens();
+ }
+ @catch (NSException *ex) {
+ LOG_DEBUG(MONOBJC_DOMAIN_GENERAL, "Native exception caught and rethrown as managed: %s", [[ex description] UTF8String]);
+
+ // Encapsulate the native exception (before the domain data is initialized)
+ MonoAssembly *assembly = monobjc_define_assembly(MONOBJC);
+ MonoImage *image = monobjc_define_image(assembly);
+ MonoException *exc = mono_exception_from_name_msg(image, MONOBJC, OBJECTIVE_C_EXCEPTION, [[ex description] UTF8String]);
+ mono_raise_exception(exc);
+ }
+ }
+}
+
+MonoString *icall_Monobjc_ObjectiveCRuntime_GetDomainToken() {
+ MonoDomain *domain = mono_domain_get();
+ MonobjcDomainData *data = monobjc_get_domain_data(domain);
+ if (!data || !data->token) {
+ return NULL;
+ }
+ // Right now this method is only called once per domain
+ // and then cached on the managed side, otherwise we
+ // would want to cache a MonoString in data instead of UTF8.
+ return mono_string_new(domain, data->token);
+}
View
2  native/Monobjc/sources/messaging.mm
@@ -289,7 +289,7 @@ void monobjc_destroy_cache_for_calls() {
}
MonoObject *monobjc_call_invoke(MonobjcNativeCall *call, void *target, SEL selector, MonoArray *parameters, boolean_t is_super) {
- LOG_DEBUG(MONOBJC_DOMAIN_MESSAGING, "monobjc_call_invoke");
+ LOG_DEBUG(MONOBJC_DOMAIN_MESSAGING, "monobjc_call_invoke: selector = '%s'", sel_getName(selector));
MonoObject *result = NULL;
uint32_t nargs = call->cif->nargs;
View
16 tests/Monobjc.Foundation.Tests/Common/AbstractObjectiveCTests.cs
@@ -47,10 +47,24 @@ public void TestFixtureSetUp ()
foreach (String framework in this.Env.Frameworks) {
ObjectiveCRuntime.LoadFramework (framework);
}
- ObjectiveCRuntime.Initialize ();
} catch (Exception ex) {
Assert.Ignore ("Cannot initialize runtime:\n{0}", ex);
}
+
+ try {
+ // Enable auto domain tokens to support running tests in multiple domains.
+ //
+ // Note: This will append domain tokens to types in secondary domains. If
+ // any tests are added which rely on specific class names being present
+ // (e.g. NSCoder serializtion) then those tests will need to execute in a single
+ // domain on the command-line using the domain=single option or have their
+ // fixtures setup using a well-known token which can be integrated into the
+ // test or resource names and also passed to Initialize().
+ ObjectiveCRuntime.EnableAutoDomainTokens();
+ ObjectiveCRuntime.Initialize ();
+ } catch (Exception ex) {
+ Assert.Fail ("Cannot initialize runtime:\n{0}", ex);
+ }
}
}
}
Please sign in to comment.
Something went wrong with that request. Please try again.