Skip to content

Commit

Permalink
[ObjCRuntime] Allow a null delegate in Runtime.ReleaseBlockWhenDelega…
Browse files Browse the repository at this point in the history
…teIsCollected. Fixes #20920. (#20999)

Allow a null delegate in Runtime.ReleaseBlockWhenDelegateIsCollected if the corresponding native pointer is also null.

Fixes #20920.
  • Loading branch information
rolfbjarne authored Aug 9, 2024
1 parent b298ec5 commit 75be879
Show file tree
Hide file tree
Showing 6 changed files with 84 additions and 15 deletions.
6 changes: 3 additions & 3 deletions src/ObjCRuntime/Runtime.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2587,12 +2587,12 @@ public static void ReleaseBlockOnMainThread (IntPtr block)
[EditorBrowsable (EditorBrowsableState.Never)]
static Delegate ReleaseBlockWhenDelegateIsCollected (IntPtr block, Delegate @delegate)
{
if (@delegate is null)
ObjCRuntime.ThrowHelper.ThrowArgumentNullException (nameof (@delegate));

if (block == IntPtr.Zero)
return @delegate;

if (@delegate is null)
ObjCRuntime.ThrowHelper.ThrowArgumentNullException (nameof (@delegate));

if (block_lifetime_table.TryGetValue (@delegate, out var existingCollector)) {
existingCollector.Add (block);
} else {
Expand Down
2 changes: 2 additions & 0 deletions tests/bindings-test/ApiDefinition.cs
Original file line number Diff line number Diff line change
Expand Up @@ -424,6 +424,8 @@ interface ObjCBlockTester {
[Export ("setProtocolWithBlockProperties:required:instance:")]
void SetProtocolWithBlockProperties (IProtocolWithBlockProperties obj, bool required, bool instance);

[Export ("nullableCallback:")]
bool NullableCallback ([NullAllowed] Action<int> completionHandler);
}

delegate void InnerBlock (int magic_number);
Expand Down
3 changes: 3 additions & 0 deletions tests/bindings-test/Messaging.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,8 @@ public struct objc_super {

[DllImport (LIBOBJC_DYLIB, EntryPoint = "objc_msgSend")]
public extern static void void_objc_msgSend_IntPtr_bool_bool (IntPtr receiver, IntPtr selector, IntPtr a, bool b, bool c);

[DllImport (LIBOBJC_DYLIB, EntryPoint = "objc_msgSend")]
public extern static byte byte_objc_msgSend_IntPtr (IntPtr receiver, IntPtr selector, IntPtr a);
}
}
73 changes: 61 additions & 12 deletions tests/bindings-test/RegistrarBindingTest.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
using System;
using System.Threading;
#if NET
using System.Runtime.InteropServices;
#endif

using Foundation;
using ObjCRuntime;
Expand All @@ -8,6 +11,8 @@

using Bindings.Test;

#nullable enable

namespace Xamarin.BindingTests {
[TestFixture]
[Preserve (AllMembers = true)]
Expand Down Expand Up @@ -102,6 +107,13 @@ public void DerivedClassBlockCallback ()
ObjCBlockTester.CallRequiredStaticCallback ();
ObjCBlockTester.CallOptionalStaticCallback ();
DerivedBlockCallbackClass.Answer = 2;

#if NET
Assert.IsFalse (obj.InvokeNullableCallbackNatively (null), "NullableCallback A rv");
int nullableResult = -1;
Assert.IsTrue (obj.InvokeNullableCallbackNatively ((v) => nullableResult = v), "NullableCallback B rv");
Assert.AreEqual (24, nullableResult, "NullableCallback result");
#endif
}
}

Expand Down Expand Up @@ -234,19 +246,56 @@ public override void ClassCallback (Action<int> completionHandler)
{
completionHandler (42);
}

public override bool NullableCallback (Action<int>? completionHandler)
{
if (completionHandler is not null) {
completionHandler (24);
return true;
}
return false;
}

#if NET
[BindingImpl (BindingImplOptions.Optimizable)]
public bool InvokeNullableCallbackNatively (Action<int>? completionHandler)
{
byte rv;
if (completionHandler is null) {
rv = Messaging.byte_objc_msgSend_IntPtr (Handle, Selector.GetHandle ("nullableCallback:"), IntPtr.Zero);
} else {
unsafe {
delegate* unmanaged<IntPtr, int, void> trampoline = &NullableCallbackHandler;
using var block = new BlockLiteral (trampoline, completionHandler, typeof (BlockCallbackTester), nameof (NullableCallbackHandler));
BlockLiteral* block_ptr = &block;
rv = Messaging.byte_objc_msgSend_IntPtr (Handle, Selector.GetHandle ("nullableCallback:"), (IntPtr) block_ptr);
}
}
return rv != 0;
}

[UnmanagedCallersOnly]
static void NullableCallbackHandler (IntPtr block, int magicNumber)
{
var del = BlockLiteral.GetTarget<Action<int>> (block);
if (del is not null) {
del (magicNumber);
}
}
#endif
}

public class PropertyBlock : NSObject, IProtocolWithBlockProperties {
[Export ("myOptionalProperty")]
public SimpleCallback MyOptionalProperty { get; set; }
public SimpleCallback MyOptionalProperty { get; set; } = () => { Assert.Fail ("No block set?"); };

public SimpleCallback MyRequiredProperty { get; set; }
public SimpleCallback MyRequiredProperty { get; set; } = () => { Assert.Fail ("No block set?"); };

[Export ("myOptionalStaticProperty")]
public static SimpleCallback MyOptionalStaticProperty { get; set; }
public static SimpleCallback? MyOptionalStaticProperty { get; set; }

[Export ("myRequiredStaticProperty")]
public static SimpleCallback MyRequiredStaticProperty { get; set; }
public static SimpleCallback? MyRequiredStaticProperty { get; set; }
}

[Test]
Expand Down Expand Up @@ -291,15 +340,15 @@ public void ProtocolWithNativeBlockProperties (bool required, bool instance)
ObjCBlockTester.SetProtocolWithBlockProperties (pb, required, instance);
if (required) {
if (instance) {
pb.MyRequiredProperty ();
pb.MyRequiredProperty! ();
} else {
PropertyBlock.MyRequiredStaticProperty ();
PropertyBlock.MyRequiredStaticProperty! ();
}
} else {
if (instance) {
pb.MyOptionalProperty ();
pb.MyOptionalProperty! ();
} else {
PropertyBlock.MyOptionalStaticProperty ();
PropertyBlock.MyOptionalStaticProperty! ();
}
}
Assert.AreEqual (calledCounter + 1, ObjCBlockTester.CalledBlockCount, "Blocks called");
Expand Down Expand Up @@ -353,16 +402,16 @@ public void LinkedAway (bool required, bool instance)
// have been linked away (which happen when _forcing_ the dynamic registrar to be linked away).
public class FakePropertyBlock : NSObject {
[Export ("myOptionalProperty")]
public SimpleCallback MyOptionalProperty { get; set; }
public SimpleCallback? MyOptionalProperty { get; set; }

[Export ("myRequiredProperty")]
public SimpleCallback MyRequiredProperty { get; set; }
public SimpleCallback? MyRequiredProperty { get; set; }

[Export ("myOptionalStaticProperty")]
public static SimpleCallback MyOptionalStaticProperty { get; set; }
public static SimpleCallback? MyOptionalStaticProperty { get; set; }

[Export ("myRequiredStaticProperty")]
public static SimpleCallback MyRequiredStaticProperty { get; set; }
public static SimpleCallback? MyRequiredStaticProperty { get; set; }
}
}
}
9 changes: 9 additions & 0 deletions tests/test-libraries/libtest.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ extern "C" {
int theUltimateAnswer ();
void useZLib ();

// NS_ASSUME_NONNULL_BEGIN doesn't work
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wnullability-completeness"

typedef void (^x_block_callback)();
void x_call_block (x_block_callback block);
void* x_call_func_3 (void* (*fptr)(void*, void*, void*), void* p1, void* p2, void* p3);
Expand Down Expand Up @@ -237,6 +241,8 @@ typedef void (^outerBlock) (innerBlock callback);

+(void) setProtocolWithBlockProperties: (id<ProtocolWithBlockProperties>) obj required: (bool) required instance: (bool) instance;
+(int) calledBlockCount;

-(bool) nullableCallback: (void (^ __nullable)(int32_t magic_number))completionHandler;
@end

@interface FreedNotifier : NSObject {
Expand Down Expand Up @@ -294,6 +300,9 @@ typedef void (^outerBlock) (innerBlock callback);

@end

#pragma clang diagnostic pop
// NS_ASSUME_NONNULL_END

#ifdef __cplusplus
} /* extern "C" */
#endif
Expand Down
6 changes: 6 additions & 0 deletions tests/test-libraries/libtest.m
Original file line number Diff line number Diff line change
Expand Up @@ -812,6 +812,12 @@ +(int) calledBlockCount
return called_blocks;
}

-(bool) nullableCallback: (void (^ __nullable)(int32_t magic_number))completionHandler
{
assert (false); // THIS FUNCTION SHOULD BE OVERRIDDEN
return false;
}

static void block_called ()
{
#pragma clang diagnostic push
Expand Down

9 comments on commit 75be879

@vs-mobiletools-engineering-service2

This comment was marked as outdated.

@vs-mobiletools-engineering-service2

This comment was marked as outdated.

@vs-mobiletools-engineering-service2

This comment was marked as outdated.

@vs-mobiletools-engineering-service2

This comment was marked as outdated.

@vs-mobiletools-engineering-service2

This comment was marked as outdated.

@vs-mobiletools-engineering-service2

This comment was marked as outdated.

@vs-mobiletools-engineering-service2

This comment was marked as outdated.

@vs-mobiletools-engineering-service2

This comment was marked as outdated.

@vs-mobiletools-engineering-service2

This comment was marked as outdated.

Please sign in to comment.