Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

support for general purpose param object[] #25

Closed
friuns2 opened this issue Jul 14, 2017 · 12 comments
Closed

support for general purpose param object[] #25

friuns2 opened this issue Jul 14, 2017 · 12 comments

Comments

@friuns2
Copy link

friuns2 commented Jul 14, 2017

i'm integrating harmony with jurrassic javascript runtime intepreter and i stuck at problem

for example original method is test(int a, string b)

but i would like to patch it with testPrefix(object[] o) so o[0]=a and o[1]=b

but its not possible currently in harmony

@pardeike
Copy link
Owner

Harmony is not designed to do that out of the box. However, it is possible to create a transpiler that does exactly this by manipulating the IL code to shuffle the arguments that are on the CIL stack into an array. Doing this in plain Harmony would actually slow down things and I would consider this special interest.

@friuns2
Copy link
Author

friuns2 commented Jul 14, 2017

if you plan add it in future i made working implementation
in EmitCallParameter.
   if (patchParam.Name == "_params")
                {
                    Emitter.Emit(il, OpCodes.Ldc_I4, originalParameters.Length);
                    Emitter.Emit(il, OpCodes.Newarr, typeof(object));
                    for (int i = 0; i < originalParameters.Length; i++)
                    {
                        Emitter.Emit(il, OpCodes.Dup);
                        Emitter.Emit(il, OpCodes.Ldc_I4, i);
                        Emitter.Emit(il, OpCodes.Ldarg, i + 1);
                        Emitter.Emit(il, OpCodes.Box, originalParameters[i].ParameterType);
                        Emitter.Emit(il, OpCodes.Stelem_Ref);
                    }
                    continue;
                }

@pardeike
Copy link
Owner

I have to double check all corner cases and all types of parameters but if I find a good general solution I might add it to Harmony too. The main problem for me is that it should support all types and even work with “ref” or “out” parameters.

@FrodoOf9Fingers
Copy link

FrodoOf9Fingers commented Feb 24, 2018

Why do you have this line friuns2?
Emitter.Emit(il, OpCodes.Ldarg, i + 1);
Should it just be 'i' instead of 'i+1'? Aren't arguments 0 based? (I'm not very good with CIL)

@pardeike
Copy link
Owner

Actually: both. A static method has its arguments 0-based but an instance method has the instance as its first (hidden) argument.

Also, putting arguments into an array will fail with ‘ref’ arguments so I have not implemented it yet.

@pardeike
Copy link
Owner

Too many corner cases that will fail. I'll close this for now. If you want to use array parameters, make your own transpiler submethod that you can pipe your instructions through and that calls one of your method with the array.

@DoctorVanGogh
Copy link
Contributor

Architecture idea:

Don't promise object[] mapping but a PatchParameter[] mapping with:

abstract class PatchParameter {
  abstract Object UntypedValue { get; set; }
  abstract Type Type { get; }
}

class PatchParameter<T> : PatchParameter {
 private bool _touched = false;
 private bool _canSet = false;
 
 // out parameter
 PatchParameter() {
  _canSet = true;
 }
 
 // ref or byvalue parameter
 PatchParameter(T value, bool isRef = false) {
  _touched = true;
  _value = value;
  _canSet = isRef;
 }
 
 T Value { 
    get {       
      return !_touched 
        ? throw new InvalidOperationException("need to set value first")
        :  _value;
    }
    set {
      if (!_canSet)
        throw new InvalidOperationException("cannot set value"); 
      _value = value;
      _touched = true;
    }
  }
  

 override Object UntypedValue => Value;
 override Type Type => typeof(T);

 bool CanGet => _touched;
 bool CanSet => _canSet;
}

@pardeike
Copy link
Owner

Can you give me an example on how you would use that feature?

@dustinlacewell
Copy link

Can you give me an example on how you would use that feature?

I am building a kind of debugger for Unity. It'd be great to have a general patch that could accept the various data such as the original instance for instance methods, the class, arguments and the actual parameter values and then report this reflected information in my system.

The only thing missing is a way to generically accept all parameter values.

@pardeike pardeike reopened this Feb 26, 2020
@pardeike
Copy link
Owner

Maybe someone else steps in and makes this happen

@LunarWhisper
Copy link

Yes, this is a great feature, which is VERY needed.
Here is my dirty solution:

	internal static class MethodPatcher
	{
		public static string PARAMS_VAR = "__args";

// ...

				if (patchParam.Name.Equals(PARAMS_VAR, StringComparison.Ordinal))
				{
					var tupleType = typeof(ValueTuple<String, Object>);
					var tupleCtor = tupleType.GetConstructor(new[] {typeof(String), typeof(Object)});

					if (patchParam.ParameterType.IsByRef)
						throw new NotSupportedException("patchParam.ParameterType.IsByRef");
					if (patchParam.ParameterType != tupleType.MakeArrayType())
						throw new NotSupportedException("patchParam.ParameterType !=  typeof(ValueTuple<String, Object>)");

					Int32 count = originalParameters.Length;
					//if (!original.IsStatic)
					//	count--;

					Emitter.Emit(il, OpCodes.Ldc_I4, count);
					Emitter.Emit(il, OpCodes.Newarr, tupleType);
					for (int i = 0, k = original.IsStatic ? 0 : 1; i < count; i++, k++)
					{
						ParameterInfo arg = originalParameters[i];
						Emitter.Emit(il, OpCodes.Dup);
						Emitter.Emit(il, OpCodes.Ldc_I4, i); // array[i]
						Emitter.Emit(il, OpCodes.Ldstr, arg.Name); // nameof(args[k])
						Emitter.Emit(il, OpCodes.Ldarg, k); // args[k]
						Type parameterType = arg.ParameterType;
						if (parameterType.IsByRef)
						{
							Type elementType = parameterType.GetElementType();
							if (elementType.IsValueType)
							{
								parameterType = elementType;
								Emitter.Emit(il, OpCodes.Ldobj, parameterType);
							}
							else
							{
								Emitter.Emit(il, OpCodes.Ldind_Ref);
							}
						}

						if (parameterType.IsValueType)
							Emitter.Emit(il, OpCodes.Box, parameterType);

						// Tuple
						Emitter.Emit(il, OpCodes.Newobj, tupleCtor);
						Emitter.Emit(il, OpCodes.Stelem, tupleType);

						// Object
						//Emitter.Emit(il, OpCodes.Stelem_Ref);
					}
					continue;
				}

And usage:

        public static void Patch(/*MethodInfo __originalMethod,*/ params (String, Object)[] __args)
        {
            StringBuilder sb = new StringBuilder();
            sb.Append("Trace: ");
                //sb.Append(__originalMethod.ToString());
            sb.Append('(');
            
            foreach ((String argName, Object argValue) in  __args)
            {
                sb.Append(argValue?.GetType().Name);
                sb.Append(' ');
                sb.Append(argName);
                sb.Append(" = ");
                sb.Append(argValue ?? "");
                sb.Append(", ");
            }

            if (__args.Length > 0)
                sb.Length -= 2;
            sb.AppendLine(")");

            if (counter == 0)
            {
                counter++;
                Console.WriteLine(sb);
                counter--;
            }
        }

Right now, my padawan is doing a graduation project that uses Harmony to trace methods without restarting the application. :)

If this functionality will support out of the box, it will be awesome!

@pardeike
Copy link
Owner

I am working on an implementation that considers all edge cases. Maybe it finds its way into Harmony 2.2

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

6 participants