Il2CppInterop icon indicating copy to clipboard operation
Il2CppInterop copied to clipboard

Il2CppSystem.ValueType parameters with default value throw an exception when not provided.

Open extraes opened this issue 7 months ago • 2 comments

As an example and for ease of demonstration, unhollowed code from BONELAB is provided:

[CallerCount(14)]
[CachedScanResults(RefRangeStart = 929796, RefRangeEnd = 929810, XrefRangeStart = 929785, XrefRangeEnd = 929796, MetadataInitTokenRva = 0L, MetadataInitFlagRva = 0L)]
public unsafe static UniTask Delay([DefaultParameterValue(null)] int millisecondsDelay, bool ignoreTimeScale = false, PlayerLoopTiming delayTiming = PlayerLoopTiming.Update, CancellationToken cancellationToken = null)
{
    System.IntPtr* ptr = stackalloc System.IntPtr[4];
    *ptr = (nint)(&millisecondsDelay);
    *(bool**)((byte*)ptr + checked((nuint)1u * unchecked((nuint)sizeof(System.IntPtr)))) = &ignoreTimeScale;
    *(PlayerLoopTiming**)((byte*)ptr + checked((nuint)2u * unchecked((nuint)sizeof(System.IntPtr)))) = &delayTiming;
    *(System.IntPtr*)((byte*)ptr + checked((nuint)3u * unchecked((nuint)sizeof(System.IntPtr)))) = IL2CPP.il2cpp_object_unbox(IL2CPP.Il2CppObjectBaseToPtrNotNull(cancellationToken));
    System.Runtime.CompilerServices.Unsafe.SkipInit(out System.IntPtr exc);
    System.IntPtr pointer = IL2CPP.il2cpp_runtime_invoke(NativeMethodInfoPtr_Delay_Public_Static_UniTask_Int32_Boolean_PlayerLoopTiming_CancellationToken_0, (System.IntPtr)0, (void**)ptr, ref exc);
    Il2CppException.RaiseExceptionIfNecessary(exc);
    return new UniTask(pointer);
}

The relevant portions are the following:

  • The parameter CancellationToken cancellationToken = null
  • The conversion/packing of this parameter IL2CPP.il2cpp_object_unbox(IL2CPP.Il2CppObjectBaseToPtrNotNull(cancellationToken))

The parameter is of class type CancellationToken, extending Il2CppSystem.ValueType (unhollowed version of the struct CancellationToken) In the source code for this method in the UniTask repository, this parameter is normally defined as CancellationToken cancellationToken = default(CancellationToken) The unhollowed parameter being defined as null causes a problem when this method is called without filling out all parameters, because obviously Il2CppObjectBaseToPtrNotNull throws an exception when it is null.

This can be remedied on a per-method basis by prefixing the offending methods and replacing parameters, but this is manual and still represents a bug in the generated IL. A solution could be making the IL mirror IL2CPP.Il2CppObjectBaseToPtrNotNull(cancellationToken ?? new CancellationToken()), or keep a cached copy of a default CancellationToken and retrieving that field, to avoid recreating objects, but there's more complexity to keeping that field and possibly making sure it's not collected.

extraes avatar Jun 23 '24 20:06 extraes