Skip to content

Commit

Permalink
Fix multi-domain support
Browse files Browse the repository at this point in the history
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
aarononeal committed Sep 14, 2013
1 parent fb4c6bd commit 53bea81
Show file tree
Hide file tree
Showing 11 changed files with 275 additions and 46 deletions.
12 changes: 10 additions & 2 deletions libraries/Monobjc/Class.cs
Expand Up @@ -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>
Expand Down
44 changes: 41 additions & 3 deletions libraries/Monobjc/ObjectiveCRuntime.Utils.cs
Expand Up @@ -20,20 +20,28 @@
// 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;

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>
Expand All @@ -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);
}
}
}
20 changes: 17 additions & 3 deletions libraries/Monobjc/ObjectiveCRuntime.cs
Expand Up @@ -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>
Expand All @@ -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);
Expand Down Expand Up @@ -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>
Expand Down
21 changes: 18 additions & 3 deletions libraries/Monobjc/Runtime/Bridge.Utils.cs
Expand Up @@ -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>
Expand All @@ -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>
Expand All @@ -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>
Expand Down
22 changes: 13 additions & 9 deletions libraries/Monobjc/Runtime/Bridge.cs
Expand Up @@ -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
Expand Down
10 changes: 9 additions & 1 deletion native/Monobjc/sources/domain.h
Expand Up @@ -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;
Expand Down Expand Up @@ -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.
Expand All @@ -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__
92 changes: 79 additions & 13 deletions native/Monobjc/sources/domain.mm
Expand Up @@ -29,6 +29,7 @@
*/
#include "cache.h"
#include "constants.h"
#include "definitions.h"
#include "domain.h"
#include "glib.h"
#include "logging.h"
Expand All @@ -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);
Expand Down Expand Up @@ -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;
}
4 changes: 3 additions & 1 deletion native/Monobjc/sources/icalls.def
Expand Up @@ -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 -----
Expand Down

0 comments on commit 53bea81

Please sign in to comment.