fix: Correctly unbox generic parameters wrapped in Il2CppObjectBase when underlying type is ValueType (e.g. Nullable<T>(T value) constructor)#246
Open
dogdie233 wants to merge 2 commits intoBepInEx:masterfrom
Conversation
…ethods with generated value types. Close issue: BepInEx#240
Author
|
The generated Nullable constructor before this fix: public unsafe Nullable(T value)
: this(IL2CPP.il2cpp_object_new(Il2CppClassPointerStore<Nullable<T>>.NativeClassPtr))
{
//IL_0055->IL0058: Incompatible stack types: I vs Ref
//IL_0048->IL0058: Incompatible stack types: I vs Ref
System.IntPtr* ptr = stackalloc System.IntPtr[1];
ref T reference;
if (!typeof(T).IsValueType)
{
object obj = value;
reference = ref *(?*)((!(obj is string)) ? IL2CPP.Il2CppObjectBaseToPtr((Il2CppObjectBase)((obj is Il2CppObjectBase) ? obj : null)) : IL2CPP.ManagedStringToIl2Cpp(obj as string));
}
else
{
reference = ref value;
}
*ptr = (nint)Unsafe.AsPointer(ref reference);
Unsafe.SkipInit(out System.IntPtr intPtr2);
System.IntPtr intPtr = IL2CPP.il2cpp_runtime_invoke(NativeMethodInfoPtr__ctor_Public_Void_T_0, IL2CPP.il2cpp_object_unbox(IL2CPP.Il2CppObjectBaseToPtrNotNull((Il2CppObjectBase)(object)this)), (void**)ptr, ref intPtr2);
Il2CppException.RaiseExceptionIfNecessary(intPtr2);
}after this fix: public unsafe Nullable(T value)
: this(IL2CPP.il2cpp_object_new(Il2CppClassPointerStore<Il2CppSystem.Nullable<T>>.NativeClassPtr))
{
//IL_0085->IL0088: Incompatible stack types: I vs Ref
//IL_0049->IL0088: Incompatible stack types: I vs Ref
//IL_0056->IL0088: Incompatible stack types: I vs Ref
//IL_0071->IL0088: Incompatible stack types: I vs Ref
//IL_0078->IL0088: Incompatible stack types: I vs Ref
System.IntPtr* ptr = stackalloc System.IntPtr[1];
ref T reference;
if (!typeof(T).IsValueType)
{
object obj = value;
if (obj is string)
{
reference = ref *(?*)IL2CPP.ManagedStringToIl2Cpp(obj as string);
}
else
{
System.IntPtr intPtr = IL2CPP.Il2CppObjectBaseToPtr((Il2CppObjectBase)((obj is Il2CppObjectBase) ? obj : null));
reference = ref *(?*)intPtr;
if (intPtr != (System.IntPtr)0)
{
reference = ref *(?*)intPtr;
if (IL2CPP.il2cpp_class_is_valuetype(IL2CPP.il2cpp_object_get_class(intPtr)))
{
reference = ref *(?*)intPtr;
if (typeof(Il2CppSystem.ValueType).IsAssignableFrom(typeof(T)))
{
reference = ref *(?*)IL2CPP.il2cpp_object_unbox(intPtr);
}
}
}
}
}
else
{
reference = ref value;
}
*ptr = (nint)Unsafe.AsPointer(ref reference);
Unsafe.SkipInit(out System.IntPtr intPtr3);
System.IntPtr intPtr2 = IL2CPP.il2cpp_runtime_invoke(NativeMethodInfoPtr__ctor_Public_Void_T_0, IL2CPP.il2cpp_object_unbox(IL2CPP.Il2CppObjectBaseToPtrNotNull((Il2CppObjectBase)(object)this)), (void**)ptr, ref intPtr3);
Il2CppException.RaiseExceptionIfNecessary(intPtr3);
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
This PR addresses a code generation issue regarding how generic parameters (
T) are marshaled when passed to native methods.The Problem
When a method takes a generic parameter
T(e.g.,Method(T value)), andTis instantiated as anIl2CppObjectBasewrapper representing a native ValueType (Struct), the previous codegen logic treated it purely as a reference type.It passed the pointer to the boxed object (header + data) to the native function. However, if the native method signature expects the raw value type (common in generic methods like
Nullable<T>.ctoror generic collection manipulation), this resulted in the native code reading the object header as data, causing data corruption or crashes.The Fix
I updated the code generation template to include a runtime check for generic parameters.
The new logic detects this specific scenario:
Il2CppObjectBase?il2cpp_class_is_valuetype).Tis compatible withIl2CppSystem.ValueType.il2cpp_object_unbox) to get the raw data pointer before passing it to the native method.Related Issue
Fixes #240
(Although the issue specifically reports
Nullable<T>, this fix applies generally to any method acceptingTwhereTis a struct wrapper).Test Plan
I verified the fix using
Il2CppSystem.Nullable<T>as a reproduction case, as it relies heavily on correctly receiving the unboxed value ofT.Test Environment:
AwesomeStruct) defined in the game assembly.Nullable<AwesomeStruct>and verifying data integrity.Code for Testing
Results
Before Fix (Broken):
The native constructor read the object header as the struct data, resulting in garbage values.
After Fix (Working):
The native constructor receives the unboxed data correctly.
(Note: I also verified the Dictionary/Int64 case mentioned in PR #69 to ensure no regression, and it works correctly.)
Implementation Note
Regarding the unbox check condition:
I am currently using
typeof(Il2CppSystem.ValueType).IsAssignableFrom(typeof(T))combined withil2cpp_class_is_valuetypeon the runtime instance.There is an alternative approach using
il2cpp_class_is_valuetype(Il2CppClassPointerStore<T>.NativeClassPtr). I opted forIsAssignableFromas it seemed safer for the generated code flow, but I am open to feedback if checking theNativeClassPtrdirectly is preferred for this generic constraint check.