Search Unity

Bug BufferOverflow(?) when changing private static readonly fields of static class with reflection.

Discussion in 'Scripting' started by subamanis, Mar 24, 2021.

  1. subamanis

    subamanis

    Joined:
    May 19, 2018
    Posts:
    11
    Changing the value of a private static readonly field of a static class using reflection, doesn't reliably work. I have the following methods that use reflection to set values to variables or retrieve a variable (static variables)
    Code (CSharp):
    1.    
    2.     //Provide the names of the variables and values for them, and this method sets them.
    3.     public static void InitMembers (Type type, string[] names, object[] values)
    4.     {
    5.         for (int i = 0; i < names.Length; i++)
    6.         {
    7.             type.GetField(names[i], BindingFlags.NonPublic | BindingFlags.Public |
    8.                                     BindingFlags.Static).SetValue(null, values[i]);
    9.         }
    10.     }
    11.  
    12.  
    13.     //Can be used for static classes
    14.     public static object GetMember (Type type, string attrName)
    15.     {
    16.         return type.GetField(attrName, BindingFlags.NonPublic | BindingFlags.Public  |
    17.                                            BindingFlags.Static).GetValue(null);
    18.     }
    I have a test static class:
    Code (CSharp):
    1.  
    2. static class TestStatic
    3. {
    4.     private static readonly string[] strs1 = new string[] {"11","22","33","44"};
    5.  
    6.     public static string[] getStrs()
    7.     {
    8.         return strs1;
    9.     }
    10.  
    11.     public static void printArray(string prefix, string[] arr)
    12.     {
    13.         string s = prefix+"length: "+arr.Length+"{"+arr[0]+"}";
    14.         Debug.Log(s);
    15.     }
    16. }
    17.  
    And I have this test that changes the value of strs to a new string[] with one element only and then asserts that the strs value that we are getting from the public method of the class is the same as the one we are getting with reflection:

    Code (CSharp):
    1. [Test]
    2. public void some_test(){
    3.     TestingUtils.InitMembers(typeof(TestStatic), new string[]{"strs1"},
    4.                               new object[]{new string[]{"eh"}});
    5.     Assert.AreEqual(TestStatic.getStrs(),
    6.                         TestingUtils.GetMember(typeof(TestStatic), "strs1"));
    7. }
    8.        
    I run the tests:
    1 time : passes
    2 time : passes
    3+ time : oh boy...
    Basically after the second time, each time it fails in a different spectacular way, some examples:

    Expected: System.Array+InternalEnumerator`1[[UnityEditor.Scripting.ScriptCompilation.CustomScriptAssemblyPlatform, UnityEditor.CoreModule, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null]]
    But was: < "eh" >

    Expected: System.Collections.Generic.HashSet`1+Enumerator[[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]
    But was: < "eh" >

    Expected: <UnityEngine.GUILayoutOption>
    But was: < "eh" >

    Yeah you get the point. Looks like a bufferoverflow of some sort. Funny thing is that if i remove the readonly keyword from strs then it works!

    Also another thing, If you change the value of a static field of a class in a (EditMode) test, then it will keep this value if you rerun the test. But if you modify the code in any way, even blank line, to make it recompile, the value gets reset.
    Not cool. How can this be happening?
     
  2. Madgvox

    Madgvox

    Joined:
    Apr 13, 2014
    Posts:
    1,317
    You're not changing the actual code, so it's not saving your change anywhere. When the code is recompiled, the original value is restored. This is to be expected.

    As to the original issue, looks like this has something to do with the Unity runtime. Another user reported similar symptoms years ago: https://stackoverflow.com/a/23615644

    I think the short answer is don't reflect readonly fields.
     
    Last edited: Mar 24, 2021
  3. Antistone

    Antistone

    Joined:
    Feb 22, 2014
    Posts:
    2,836
    What's the purpose of declaring a field readonly if you're going to change it anyway?
     
  4. subamanis

    subamanis

    Joined:
    May 19, 2018
    Posts:
    11
    Purpose of testing. I am not changing the value in normal code.
     
  5. subamanis

    subamanis

    Joined:
    May 19, 2018
    Posts:
    11
    Sorry I am not sure I am getting it. Yes I am not changing the actual code that's the point, I only modify the value at runtime, how can it still have the same changed value when i rerun the test?
     
  6. Madgvox

    Madgvox

    Joined:
    Apr 13, 2014
    Posts:
    1,317
    Ah, I misunderstood your frustration -- the changed value will stay between tests because the editor environment & all the objects loaded in it, including static classes, are persistent until a domain reload occurs. This happens on recompile, due to code changes, or on entering play mode if you have domain reloading enabled. Executing a test doesn't reload the domain, so static state will persist.

    In your case, you would need to make use of setup/teardown capabilities to make sure that what you're testing is in the correct state when you run your tests. In this instance, you might reset the value of your static variable.
     
    eisenpony and Kurt-Dekker like this.
  7. subamanis

    subamanis

    Joined:
    May 19, 2018
    Posts:
    11
    Thanks for the info! SetUp / TearDown was the solution I resorted to too.