Skip to content
This repository has been archived by the owner on Oct 6, 2023. It is now read-only.

Bug: [IL2CPP] ExecutionEngineException on deserializing array of struct #77

Open
2 tasks done
SergeOxe opened this issue Jan 25, 2021 · 4 comments
Open
2 tasks done
Labels
bug Something isn't working

Comments

@SergeOxe
Copy link

SergeOxe commented Jan 25, 2021

Expected behavior

Successfully deserializing json with array of nested struct.
{"classProp":"this is classProp Value", "myArray":[{"someProp":"someProp Value"}]}

Actual behavior

Getting ExecutionEngineException.

ExecutionEngineException: Attempting to call method 'System.Collections.Generic.List`1[[MyClass+NestedStruct, Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null]]::.cctor' for which no ahead of time (AOT) code was generated.

Steps to reproduce

  • New project
  • Import jillejr.newtonsoft.json-for-unity via UPM
  • Add following script to scene:
public class MyClass
{
    public struct NestedStruct
    {
        public string someProp;
    }
        
    public string classProp;
    public NestedStruct[] myArray;
}
public class Main : MonoBehaviour
{
    private string JsonToParse = "{\"classProp\":\"this is classProp Value\"," +
                                 "\"myArray\":[{\"someProp\":\"someProp Value\"}]}";
    void Start()
    {
        var d = JsonConvert.DeserializeObject<MyClass>(JsonToParse);
        Debug.Log(d.classProp);
        Debug.Log(d.myArray.Length);
    }
}
  • Run the game

Details

Please Note that if I changing the struct to class, it will work as expected.

Host machine OS running Unity Editor 👉 MacOS Catalina

Unity build target 👉 Android

Newtonsoft.Json-for-Unity package version 👉 12.0.301

I was using Unity version 👉 2018.4.26

Checklist

  • Shutdown Unity, deleted the /Library folder, opened project again in Unity, and problem still remains.
  • Checked to be using latest version of the package.
@SergeOxe SergeOxe added the bug Something isn't working label Jan 25, 2021
@applejag
Copy link
Owner

Hi @SergeOxe! Thanks for making such a clear issue! This is actually a known issue with the package, and I apologize for not being clear about it back.

The issue here is about some AOT generation. Funny, System.Collections.Generic.List<T> is one of the more difficult types to get an AOT error about :)

I have written some wiki pages, a bit hidden, over 'ere: https://github.com/jilleJr/Newtonsoft.Json-for-Unity/wiki/Fix-AOT-compilation-errors, and in this case, I would suggest looking into using AotHelper. Especially the AotHelper.EnsureList<T>

To fix this, you need to add AotHelper.EnsureList<NestedStruct>() to a file that won't be stripped (such as a class that inherits from MonoBehaviour, a type with the [Preserve] attribute, or a type that's preserved via a link.xml file). The code does not need to run, nor does it need to be added to a GameObject in a scene; it only need to be included in the build. Example:

using UnityEngine;
using Newtonsoft.Json.Utility;

public class AotEnforcements : MonoBehaviour
{
    public void Awake()
    {
        AotHelper.EnsureList<NestedStruct>();
    }
}

Hope that resolves it for you! :)

@applejag
Copy link
Owner

applejag commented Jan 25, 2021

For reference, the reason it works when it's a class and not when it's a struct is because the IL2CPP compiler does some generics reuse in its code generation.

The Unity engine already has some references to some List<T> types here and there, and the IL2CPP compiler can take the code it generates for example for a List<GameObject> and reuse the exact same implementation for List<NestedClass>. This is because both GameObject and NestedClass in this example are reference types. I.e. they are just pointers. It doesn't matter if one's a pointer to a GameObject and one's to a NestedClass, they both are just the same size of a .NET pointer.

In the background, a List<T> stores its items in an array of T[]. To restate: GameObject[] and NestedClass[] arrays both look the exact same in memory, and IL2CPP can abuse this. Example memory of such array:

0: int32, array length (4 in this case)
1: int64, pointer to GameObject at index 0
2: int64, pointer to GameObject at index 1
3: int64, pointer to GameObject at index 2
4: int64, pointer to GameObject at index 3
0: int32, array length (4 in this case)
1: int64, pointer to NestedClass at index 0
2: int64, pointer to NestedClass at index 1
3: int64, pointer to NestedClass at index 2
4: int64, pointer to NestedClass at index 3

Structs have this particular property of being embedded wherever they're used. For example, if we had the following struct:

struct Vector2 {
    public int x;
    public int y;
}

Then the memory of an Vector2[] would look like so:

0: int32, array length (4 in this case)
1: int32, Vector2.x of Vector2 at index 0
2: int32, Vector2.y of Vector2 at index 0
3: int32, Vector2.x of Vector2 at index 1
4: int32, Vector2.y of Vector2 at index 1
5: int32, Vector2.x of Vector2 at index 2
6: int32, Vector2.y of Vector2 at index 2
7: int32, Vector2.x of Vector2 at index 3
8: int32, Vector2.y of Vector2 at index 3

Because structs behaves like this, IL2CPP does not even try to reuse generic types where the generic type argument is a struct.

More about this here: https://blogs.unity3d.com/2015/06/16/il2cpp-internals-generic-sharing-implementation/

@SergeOxe
Copy link
Author

@jilleJr Thanks for your quick response and detailed explanation! You are doing a great job 💪. I missed the wiki page, thanks!

@applejag
Copy link
Owner

@SergeOxe Heh thanks :) I got into writing mood so may have written too much detail /shrug. Tell me if AotHelper fixed the problem! I do find joy in resolving these issues

mratarusama added a commit to mratarusama/KissinVKR that referenced this issue May 26, 2022
rYuuk added a commit to readyplayerme/rpm-unity-sdk-avatar-creator that referenced this issue Aug 8, 2023
## [SDK-301](https://ready-player-me.atlassian.net/browse/SDK-301)

## Description

- Added handling of expired refresh token - logs out user if refresh
token expires
- Fixed the blocking error on android #33 - fix found here
applejag/Newtonsoft.Json-for-Unity#77 (comment)
- Updated readme with supported platform
- Adjusted loading popup ui according portrait mode
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

2 participants