Skip to content

Commit 3aa6069

Browse files
authored
[release/10.0.1xx] [src] Fix calling ConformsToProtocol on native instances that don't have a managed peer yet. Fixes #24111. (#24190)
Say we have: 1. A native type with an init method. 2. A managed binding for that native type, but the init method isn't bound. 3. A managed subclass of the managed binding (also without an implementation for the init method). In this case, native code can create a native instance of the subclass, but no managed code is executed, which means there won't be a corresponding managed peer either. This became a problem in our ConformsToProtocol override/implementation, because recently it changed to just return 'false' if we couldn't find a managed peer for a native instance. The fix is to create the managed instance in that case (by doing what we did before: call "Runtime.GetNSObject" on the native handle). Fixes #24111. --------- Backport of #24189.
1 parent dc72e6c commit 3aa6069

File tree

5 files changed

+41
-2
lines changed

5 files changed

+41
-2
lines changed

src/ObjCRuntime/Runtime.cs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2704,8 +2704,13 @@ public static string? OriginalWorkingDirectory {
27042704
static nint InvokeConformsToProtocol (IntPtr gchandle, IntPtr handle, IntPtr protocol)
27052705
{
27062706
var obj = GetGCHandleTarget (gchandle) as NSObject;
2707-
if (obj is null)
2708-
return 0;
2707+
if (obj is null) {
2708+
// conformsToProtocol is special, we might get here before a managed instance has been created
2709+
// for a native instance, in which case we need to create the managed instance (which GetNSObject will do)
2710+
obj = GetNSObject (handle);
2711+
if (obj is null)
2712+
return 0;
2713+
}
27092714
var rv = obj.ConformsToProtocol (protocol);
27102715
return rv ? 1 : 0;
27112716
}

tests/bindings-test/ApiDefinition.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -840,4 +840,8 @@ interface WrappedNSDictionary {
840840
UIEdgeInsets UIEdgeInsetsField { get; set; }
841841
#endif // !__MACOS____MACOS__
842842
}
843+
844+
[BaseType (typeof (NSObject))]
845+
[DisableDefaultCtor]
846+
interface ClassWithNoDefaultCtor { }
843847
}

tests/monotouch-test/ObjCRuntime/RuntimeTest.cs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
using UIKit;
1010
#endif
1111

12+
using Bindings.Test;
1213
using MonoTests.System.Net.Http;
1314
using Xamarin.Utils;
1415

@@ -912,6 +913,27 @@ class IntPtrBoolConstructor : DisposableObject {
912913
{
913914
}
914915
}
916+
917+
[Test]
918+
public void ConformsToProtocolCreatesManagedInstance ()
919+
{
920+
// The point of this test is to verify what happens if an instance of a subclassed type is created
921+
// through an Objective-C initializer that is not bound in managed code. In this case, no managed
922+
// code will be executed when the native instance is created, which means there won't be a managed
923+
// instance either.
924+
// At some point this caused trouble for our conformsToProtocol: override/implementation: we'd just
925+
// return 'false' if there was no managed instance for a native object.
926+
var cls = new Class (typeof (ConformsToProtocolCreatesManagedInstanceTestClass));
927+
var handle = Messaging.IntPtr_objc_msgSend (cls.Handle, Selector.GetHandle ("alloc"));
928+
handle = Messaging.IntPtr_objc_msgSend (handle, Selector.GetHandle ("init"));
929+
var conforms = Messaging.bool_objc_msgSend_IntPtr (handle, Selector.GetHandle ("conformsToProtocol:"), Runtime.GetProtocol ("NSObject"));
930+
Messaging.void_objc_msgSend (handle, Selector.GetHandle ("release"));
931+
Assert.That (conforms, Is.EqualTo (true), "Conforms");
932+
}
933+
934+
class ConformsToProtocolCreatesManagedInstanceTestClass : ClassWithNoDefaultCtor {
935+
protected ConformsToProtocolCreatesManagedInstanceTestClass (NativeHandle handle) : base (handle) { }
936+
}
915937
}
916938

917939
[TestFixture]

tests/test-libraries/libtest.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -361,6 +361,10 @@ typedef void (^outerBlock) (innerBlock callback);
361361
@property (retain) NSPointerArray* array;
362362
@end
363363

364+
@interface ClassWithNoDefaultCtor : NSObject {
365+
}
366+
@end
367+
364368
#pragma clang diagnostic pop
365369
// NS_ASSUME_NONNULL_END
366370

tests/test-libraries/libtest.m

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1535,4 +1535,8 @@ -(void) callDoSomething: (int*) nilObjectCount nonNilObjectCount: (int *) nonNil
15351535

15361536
@end
15371537

1538+
@implementation ClassWithNoDefaultCtor : NSObject {
1539+
}
1540+
@end
1541+
15381542
#include "libtest.decompile.m"

0 commit comments

Comments
 (0)