Sebastian Barth
Sebastian Barth

Reputation: 4551

Insert method call into setter causes Invalid IL code

I mark some properties with a custom attribute. If it is present I want to manipulate their setter to include a method call after setting the value. The called method is implemented in the parent class of the class that hosts the property. The method has one int parameter which is actually an array index identified in the manipulation phase.

For completeness the setter (could contain anything) looks like this:

IL_0000: newobj System.Void PlayerCspController/<>c__DisplayClass9_0::.ctor()
IL_0005: stloc.0
IL_0006: ldloc.0
IL_0007: ldarg.1
IL_0008: stfld Networking.Data.MoveData PlayerCspController/<>c__DisplayClass9_0::value
IL_000d: nop
IL_000e: ldarg.0
IL_000f: call System.Void Networking.NetworkUtilities::AssertIsOwnerOrServer(Networking.NetworkBehaviour)
IL_0014: nop
IL_0015: ldarg.0
IL_0016: ldfld UnityEngine.Rigidbody2D PlayerCspController::_body
IL_001b: call System.Boolean UnityEngine.Object::op_Implicit(UnityEngine.Object)
IL_0020: brfalse.s IL_0032
IL_0022: ldarg.0
IL_0023: ldfld Starship.Starship PlayerCspController::_starship
IL_0028: call System.Boolean UnityEngine.Object::op_Implicit(UnityEngine.Object)
IL_002d: ldc.i4.0
IL_002e: ceq
IL_0030: br.s IL_0033
IL_0032: ldc.i4.1
IL_0033: stloc.s V_4
IL_0035: ldloc.s V_4
IL_0037: brfalse.s IL_003f
IL_0039: nop
IL_003a: br IL_01a9
IL_003f: ldarg.0
IL_0040: ldfld UnityEngine.Rigidbody2D PlayerCspController::_body
IL_0045: callvirt System.Single UnityEngine.Rigidbody2D::get_mass()
IL_004a: stloc.1
IL_004b: ldarg.0
IL_004c: ldfld System.Single PlayerCspController::linearAcceleration
IL_0051: ldloc.1
IL_0052: mul
IL_0053: ldloc.0
IL_0054: ldfld Networking.Data.MoveData PlayerCspController/<>c__DisplayClass9_0::value
IL_0059: ldfld System.Single Networking.Data.MoveData::Vertical
IL_005e: mul
IL_005f: stloc.2
IL_0060: ldarg.0
IL_0061: ldfld System.Single PlayerCspController::angularAcceleration
IL_0066: ldloc.1
IL_0067: mul
IL_0068: ldloc.0
IL_0069: ldfld Networking.Data.MoveData PlayerCspController/<>c__DisplayClass9_0::value
IL_006e: ldfld System.Single Networking.Data.MoveData::Horizontal
IL_0073: neg
IL_0074: mul
IL_0075: stloc.3
IL_0076: ldloc.2
IL_0077: ldc.r4 0
IL_007c: ceq
IL_007e: ldc.i4.0
IL_007f: ceq
IL_0081: stloc.s V_5
IL_0083: ldloc.s V_5
IL_0085: brfalse.s IL_00ac
IL_0087: nop
IL_0088: ldarg.0
IL_0089: ldfld UnityEngine.Rigidbody2D PlayerCspController::_body
IL_008e: ldarg.0
IL_008f: call UnityEngine.Transform UnityEngine.Component::get_transform()
IL_0094: callvirt UnityEngine.Vector3 UnityEngine.Transform::get_up()
IL_0099: ldloc.2
IL_009a: call UnityEngine.Vector3 UnityEngine.Vector3::op_Multiply(UnityEngine.Vector3,System.Single)
IL_009f: call UnityEngine.Vector2 UnityEngine.Vector2::op_Implicit(UnityEngine.Vector3)
IL_00a4: ldc.i4.0
IL_00a5: callvirt System.Void UnityEngine.Rigidbody2D::AddForce(UnityEngine.Vector2,UnityEngine.ForceMode2D)
IL_00aa: nop
IL_00ab: nop
IL_00ac: ldloc.3
IL_00ad: ldc.r4 0
IL_00b2: ceq
IL_00b4: ldc.i4.0
IL_00b5: ceq
IL_00b7: stloc.s V_6
IL_00b9: ldloc.s V_6
IL_00bb: brfalse.s IL_00cd
IL_00bd: nop
IL_00be: ldarg.0
IL_00bf: ldfld UnityEngine.Rigidbody2D PlayerCspController::_body
IL_00c4: ldloc.3
IL_00c5: ldc.i4.0
IL_00c6: callvirt System.Void UnityEngine.Rigidbody2D::AddTorque(System.Single,UnityEngine.ForceMode2D)
IL_00cb: nop
IL_00cc: nop
IL_00cd: ldarg.0
IL_00ce: ldfld UnityEngine.Rigidbody2D PlayerCspController::_body
IL_00d3: ldarg.0
IL_00d4: ldfld UnityEngine.Rigidbody2D PlayerCspController::_body
IL_00d9: callvirt UnityEngine.Vector2 UnityEngine.Rigidbody2D::get_velocity()
IL_00de: call UnityEngine.Vector3 UnityEngine.Vector2::op_Implicit(UnityEngine.Vector2)
IL_00e3: ldarg.0
IL_00e4: ldfld System.Single PlayerCspController::maxLinearVelocity
IL_00e9: call UnityEngine.Vector3 UnityEngine.Vector3::ClampMagnitude(UnityEngine.Vector3,System.Single)
IL_00ee: call UnityEngine.Vector2 UnityEngine.Vector2::op_Implicit(UnityEngine.Vector3)
IL_00f3: callvirt System.Void UnityEngine.Rigidbody2D::set_velocity(UnityEngine.Vector2)
IL_00f8: nop
IL_00f9: ldarg.0
IL_00fa: ldfld UnityEngine.Rigidbody2D PlayerCspController::_body
IL_00ff: ldarg.0
IL_0100: ldfld UnityEngine.Rigidbody2D PlayerCspController::_body
IL_0105: callvirt System.Single UnityEngine.Rigidbody2D::get_angularVelocity()
IL_010a: ldarg.0
IL_010b: ldfld System.Single PlayerCspController::maxAngularVelocity
IL_0110: neg
IL_0111: ldarg.0
IL_0112: ldfld System.Single PlayerCspController::maxAngularVelocity
IL_0117: call System.Single UnityEngine.Mathf::Clamp(System.Single,System.Single,System.Single)
IL_011c: callvirt System.Void UnityEngine.Rigidbody2D::set_angularVelocity(System.Single)
IL_0121: nop
IL_0122: ldarg.0
IL_0123: ldfld Starship.Starship PlayerCspController::_starship
IL_0128: call System.Boolean UnityEngine.Object::op_Implicit(UnityEngine.Object)
IL_012d: stloc.s V_7
IL_012f: ldloc.s V_7
IL_0131: brfalse.s IL_01a9
IL_0133: nop
IL_0134: ldarg.0
IL_0135: ldfld Starship.Starship PlayerCspController::_starship
IL_013a: callvirt System.Collections.Generic.List`1<Starship.Thruster> Starship.Starship::get_LeftThrusters()
IL_013f: ldloc.0
IL_0140: ldftn System.Void PlayerCspController/<>c__DisplayClass9_0::<set_MoveData>b__0(Starship.Thruster)
IL_0146: newobj System.Void System.Action`1<Starship.Thruster>::.ctor(System.Object,System.IntPtr)
IL_014b: callvirt System.Void System.Collections.Generic.List`1<Starship.Thruster>::ForEach(System.Action`1<!0>)
IL_0150: nop
IL_0151: ldarg.0
IL_0152: ldfld Starship.Starship PlayerCspController::_starship
IL_0157: callvirt System.Collections.Generic.List`1<Starship.Thruster> Starship.Starship::get_RightThrusters()
IL_015c: ldloc.0
IL_015d: ldftn System.Void PlayerCspController/<>c__DisplayClass9_0::<set_MoveData>b__1(Starship.Thruster)
IL_0163: newobj System.Void System.Action`1<Starship.Thruster>::.ctor(System.Object,System.IntPtr)
IL_0168: callvirt System.Void System.Collections.Generic.List`1<Starship.Thruster>::ForEach(System.Action`1<!0>)
IL_016d: nop
IL_016e: ldarg.0
IL_016f: ldfld Starship.Starship PlayerCspController::_starship
IL_0174: callvirt System.Collections.Generic.List`1<Starship.Thruster> Starship.Starship::get_FrontThrusters()
IL_0179: ldloc.0
IL_017a: ldftn System.Void PlayerCspController/<>c__DisplayClass9_0::<set_MoveData>b__2(Starship.Thruster)
IL_0180: newobj System.Void System.Action`1<Starship.Thruster>::.ctor(System.Object,System.IntPtr)
IL_0185: callvirt System.Void System.Collections.Generic.List`1<Starship.Thruster>::ForEach(System.Action`1<!0>)
IL_018a: nop
IL_018b: ldarg.0
IL_018c: ldfld Starship.Starship PlayerCspController::_starship
IL_0191: callvirt System.Collections.Generic.List`1<Starship.Thruster> Starship.Starship::get_BackThrusters()
IL_0196: ldloc.0
IL_0197: ldftn System.Void PlayerCspController/<>c__DisplayClass9_0::<set_MoveData>b__3(Starship.Thruster)
IL_019d: newobj System.Void System.Action`1<Starship.Thruster>::.ctor(System.Object,System.IntPtr)
IL_01a2: callvirt System.Void System.Collections.Generic.List`1<Starship.Thruster>::ForEach(System.Action`1<!0>)
IL_01a7: nop
IL_01a8: nop
IL_01a9: ret

My code adds the message call so that the last lines look like this:

...
IL_01a2: callvirt System.Void System.Collections.Generic.List`1<Starship.Thruster>::ForEach(System.Action`1<!0>)
IL_01a7: nop
IL_01a8: nop
IL_0000: ldc.i4 0
IL_0000: call System.Void Networking.NetworkBehaviour::LocalNetworkVariableChange(System.Int32)
IL_01a9: ret

When I now use the setter in the code I get:

InvalidProgramException: Invalid IL code in PlayerCspController:set_MoveData (Networking.Data.MoveData): IL_01ae: call      0x06000147


(wrapper write-barrier) System.Object.wbarrier_conc(intptr)
System.Reflection.RuntimeMethodInfo.Invoke (System.Object obj, System.Reflection.BindingFlags invokeAttr, System.Reflection.Binder binder, System.Object[] parameters, System.Globalization.CultureInfo culture) (at <b847be55b9fe46ea9a5798e57286b755>:0)
Rethrow as TargetInvocationException: Exception has been thrown by the target of an invocation.
System.Reflection.RuntimeMethodInfo.Invoke (System.Object obj, System.Reflection.BindingFlags invokeAttr, System.Reflection.Binder binder, System.Object[] parameters, System.Globalization.CultureInfo culture) (at <b847be55b9fe46ea9a5798e57286b755>:0)
System.Reflection.RuntimePropertyInfo.SetValue (System.Object obj, System.Object value, System.Reflection.BindingFlags invokeAttr, System.Reflection.Binder binder, System.Object[] index, System.Globalization.CultureInfo culture) (at <b847be55b9fe46ea9a5798e57286b755>:0)
System.Reflection.PropertyInfo.SetValue (System.Object obj, System.Object value, System.Object[] index) (at <b847be55b9fe46ea9a5798e57286b755>:0)
System.Reflection.PropertyInfo.SetValue (System.Object obj, System.Object value) (at <b847be55b9fe46ea9a5798e57286b755>:0)
Networking.NetworkBehaviour+NetworkVariableDescription+<>c__DisplayClass6_1.<.ctor>b__3 (Networking.NetworkBehaviour nb, System.Object value) (at Assets/Scripts/Networking/NetworkBehaviour.NetworkVariableDescription.cs:36)
Networking.NetworkBehaviour+NetworkVariableHandle.SetValue (Networking.NetworkBehaviour networkBehaviour, System.Object value) (at Assets/Scripts/Networking/NetworkBehaviour.NetworkVariableHandle.cs:23)
Networking.NetworkBehaviour.SetFieldValue (System.UInt16 variableIndex, System.Object value) (at Assets/Scripts/Networking/NetworkBehaviour.cs:119)
Networking.PeerController`1[T]._OnNetworkVariableData (Networking.Data.UpdateNetworkVariablesData data) (at Assets/Scripts/Networking/PeerController.cs:95)
Networking.PeerController`1+<>c__DisplayClass1_0[T].<Awake>b__1 () (at Assets/Scripts/Networking/PeerController.cs:24)
Networking.NetworkActions.HandleEnqueuedActions () (at Assets/Scripts/Networking/NetworkActions.cs:29)
Networking.NetworkController.FixedUpdate () (at Assets/Scripts/Networking/NetworkController.cs:133)

The code I use is this:

private static bool ProcessAssembly(AssemblyDefinition assembly, IAssemblyResolver resolver, out bool modified) {
  modified = false;
  try {
    foreach (TypeDefinition klass in assembly.MainModule.Types) {
      if (klass.IsAbstract) {
        continue;
      }

      if (klass.BaseType != null && klass.BaseType.isOfType(typeof(NetworkBehaviour))) {
        Debug.Log($"Found network behavior '{klass.Name}'");

        for (var index = 0; index < klass.Properties.Count; index++) {
          var property = klass.Properties[index];
          var networkVariable = property.CustomAttributes
            .FirstOrDefault(attribute => attribute.AttributeType.isOfType(typeof(NetworkVariable)));

          if (networkVariable != null) {
            var propertySetMethod = property.SetMethod;
            ILProcessor processor = propertySetMethod.Body.GetILProcessor();
            var lastInstruction = propertySetMethod.Body.Instructions.Last();

            foreach (var bodyInstruction in propertySetMethod.Body.Instructions) {
              Debug.Log(bodyInstruction);
            }

            var onLocalChangeMethod = assembly.MainModule
              .Types.Single(td => td.FullName == typeof(NetworkBehaviour).FullName)
              .Methods.Single(md => md.Name == "LocalNetworkVariableChange")
              .Resolve();

            var newInstructions = new List<Instruction> {
              // Load to the stack the index of network variable
              Instruction.Create(OpCodes.Ldc_I4, index),

              // Call the method (I also tried OpCodes.Callvirt instead with no difference)
              Instruction.Create(OpCodes.Call, onLocalChangeMethod)
            };

            foreach (var newInstruction in newInstructions) {
              processor.InsertBefore(lastInstruction, newInstruction);
            }

            modified = true;
          }
        }
      }
    }
    
    return true;
  }
  catch (Exception e) {
    Debug.LogError($"Exception :{e}");
    return false;
  }
}

The method in the super class (Network.NetworkBehaviour) looks like this:

private void LocalNetworkVariableChange(int index) {
  Debug.Log($"Local network variable '{_networkVariables[index].Name}' changed");
}

Any idea, what I am doing wrong?

When this works I want to extend the parameters and also pass the new value as parameter. I could not find out how to do so. Any idea how to do so?

Upvotes: 0

Views: 85

Answers (0)

Related Questions