Skip to content

Commit 1ae944d

Browse files
Copilothazzik
andauthored
Add support for starg opcode (#268)
+semver:feature --------- Co-authored-by: Alex Zaytsev <hazzik@gmail.com>
1 parent b748d82 commit 1ae944d

File tree

6 files changed

+145
-11
lines changed

6 files changed

+145
-11
lines changed
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
using System;
2+
using System.Linq.Expressions;
3+
using NUnit.Framework;
4+
5+
namespace DelegateDecompiler.Tests
6+
{
7+
[TestFixture]
8+
public class StargTests : DecompilerTestsBase
9+
{
10+
public class TestClass
11+
{
12+
public static int ModifyParameter(int value)
13+
{
14+
value = value + 1;
15+
return value;
16+
}
17+
18+
public static int ModifyParameterWithCondition(int value)
19+
{
20+
if (value > 0)
21+
value = value * 2;
22+
else
23+
value = -value;
24+
return value;
25+
}
26+
27+
public static string ModifyStringParameter(string text)
28+
{
29+
if (text != null)
30+
text = text + " modified";
31+
return text;
32+
}
33+
34+
// Non-static method for testing instance method parameter modification
35+
public int ModifyInstanceParameter(int value)
36+
{
37+
value = value + 1;
38+
return value;
39+
}
40+
}
41+
42+
[Test]
43+
public void StargProcessor_ShouldHandleSimpleParameterModification()
44+
{
45+
Expression<Func<int, int>> expected = value => value + 1;
46+
Func<int, int> compiled = TestClass.ModifyParameter;
47+
Test(expected, compiled);
48+
}
49+
50+
[Test]
51+
public void StargProcessor_ShouldHandleConditionalParameterModification()
52+
{
53+
Expression<Func<int, int>> expected = value => value <= 0 ? -value : value * 2;
54+
Func<int, int> compiled = TestClass.ModifyParameterWithCondition;
55+
Test(expected, compiled);
56+
}
57+
58+
[Test]
59+
public void StargProcessor_ShouldHandleStringParameterModification()
60+
{
61+
Expression<Func<string, string>> expected = text => text == null ? text : text + " modified";
62+
Func<string, string> compiled = TestClass.ModifyStringParameter;
63+
Test(expected, compiled);
64+
}
65+
66+
[Test]
67+
public void StargProcessor_ShouldHandleInstanceMethodParameterModification()
68+
{
69+
var method = typeof(TestClass).GetMethod(nameof(TestClass.ModifyInstanceParameter));
70+
var expression = method.Decompile();
71+
72+
Assert.That(expression.ToString(), Is.EqualTo("(this, value) => (value + 1)"));
73+
}
74+
}
75+
}

src/DelegateDecompiler/MethodBodyDecompiler.cs

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,21 +16,23 @@ public static LambdaExpression Decompile(MethodInfo method)
1616

1717
public static LambdaExpression Decompile(MethodInfo method, Type declaringType)
1818
{
19-
var args = method.GetParameters()
20-
.Select(p => (Address)Expression.Parameter(p.ParameterType, p.Name))
19+
var parameters = method.GetParameters()
20+
.Select(p => Expression.Parameter(p.ParameterType, p.Name))
2121
.ToList();
2222

2323
var methodType = declaringType ?? method.DeclaringType;
24-
if (!method.IsStatic)
25-
args.Insert(0, Expression.Parameter(methodType, "this"));
24+
if (!method.IsStatic)
25+
parameters.Insert(0, Expression.Parameter(methodType, "this"));
26+
27+
var args = parameters.ConvertAll(x => (Address)x);
2628

2729
var expression = method.IsVirtual && !method.IsFinal
2830
? DecompileOverridable(methodType, method, args)
2931
: DecompileConcrete(method, args);
3032

3133
var optimizedExpression = expression.Optimize();
3234

33-
return Expression.Lambda(optimizedExpression, args.Select(x => (ParameterExpression)x.Expression));
35+
return Expression.Lambda(optimizedExpression, parameters);
3436
}
3537

3638
static Expression DecompileConcrete(MethodInfo method, IList<Address> args)
@@ -42,7 +44,7 @@ static Expression DecompileConcrete(MethodInfo method, IList<Address> args)
4244
var locals = addresses.ToArray();
4345

4446
var instructions = method.GetInstructions();
45-
var expression = Processor.Process(locals, args, instructions.First(), method.ReturnType);
47+
var expression = Processor.Process(method.IsStatic, locals, args, instructions.First(), method.ReturnType);
4648
var localParameters = locals
4749
.Select(l => l.Address.Expression)
4850
.OfType<ParameterExpression>()

src/DelegateDecompiler/Processor.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,10 @@ internal class Processor
2424
static readonly ConcurrentDictionary<MethodInfo, LambdaExpression> AnonymousDelegatesCache =
2525
new ConcurrentDictionary<MethodInfo, LambdaExpression>();
2626

27-
public static Expression Process(VariableInfo[] locals, IList<Address> args, Instruction instruction, Type returnType)
27+
public static Expression Process(bool isStatic, VariableInfo[] locals, IList<Address> args, Instruction instruction, Type returnType)
2828
{
2929
Processor processor = new Processor();
30-
processor.states.Push(new ProcessorState(new Stack<Address>(), locals, args, instruction));
30+
processor.states.Push(new ProcessorState(isStatic, new Stack<Address>(), locals, args, instruction));
3131

3232
var ex = AdjustType(processor.Process(), returnType);
3333

@@ -47,6 +47,7 @@ public static Expression Process(VariableInfo[] locals, IList<Address> args, Ins
4747
new UnaryExpressionProcessor(),
4848
new CgtUnProcessor(),
4949
new LdargProcessor(),
50+
new StargProcessor(),
5051
new LdelemProcessor(),
5152
new LdlenProcessor(),
5253
new LdlocProcessor(),

src/DelegateDecompiler/ProcessorState.cs

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
namespace DelegateDecompiler
99
{
1010
class ProcessorState(
11+
bool isStatic,
1112
Stack<Address> stack,
1213
VariableInfo[] locals,
1314
IList<Address> args,
@@ -21,14 +22,22 @@ class ProcessorState(
2122
public IList<Address> Args { get; } = args;
2223
public Instruction Last { get; } = last;
2324
public Action RunNext { get; set; }
25+
public bool IsStatic { get; } = isStatic;
2426

2527
public Instruction Instruction { get; set; } = instruction;
2628

2729
public ProcessorState Clone(Instruction instruction, Instruction last = null)
2830
{
2931
var addressMap = new Dictionary<Address, Address>();
3032
var buffer = Stack.Select(address => address.Clone(addressMap)).Reverse();
31-
var state = new ProcessorState(new Stack<Address>(buffer), new VariableInfo[Locals.Length], Args.ToArray(), instruction, last, Delegates);
33+
34+
var clonedArgs = new Address[Args.Count];
35+
for (var i = 0; i < Args.Count; i++)
36+
{
37+
clonedArgs[i] = Args[i].Clone(addressMap);
38+
}
39+
40+
var state = new ProcessorState(IsStatic, new Stack<Address>(buffer), new VariableInfo[Locals.Length], clonedArgs, instruction, last, Delegates);
3241
for (var i = 0; i < Locals.Length; i++)
3342
{
3443
state.Locals[i] = new VariableInfo(Locals[i].Type)
@@ -48,6 +57,14 @@ public void Merge(Expression test, ProcessorState leftState, ProcessorState righ
4857
var rightLocal = rightState.Locals[i];
4958
Locals[i].Address = Address.Merge(test, leftLocal.Address, rightLocal.Address, addressMap);
5059
}
60+
61+
for (var i = 0; i < leftState.Args.Count; i++)
62+
{
63+
var leftArg = leftState.Args[i];
64+
var rightArg = rightState.Args[i];
65+
Args[i] = Address.Merge(test, leftArg, rightArg, addressMap);
66+
}
67+
5168
var buffer = new List<Address>();
5269
while (leftState.Stack.Count > 0 || rightState.Stack.Count > 0)
5370
{

src/DelegateDecompiler/Processors/LdargProcessor.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@ public bool Process(ProcessorState state)
3434
static int GetParameterIndex(ProcessorState state)
3535
{
3636
var operand = (ParameterInfo)state.Instruction.Operand;
37-
var arg = state.Args.Single(x => ((ParameterExpression)x.Expression).Name == operand.Name);
38-
return state.Args.IndexOf(arg);
37+
return state.IsStatic ? operand.Position : operand.Position + 1;
3938
}
4039
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Linq.Expressions;
5+
using System.Reflection;
6+
using System.Reflection.Emit;
7+
using Mono.Reflection;
8+
9+
namespace DelegateDecompiler.Processors;
10+
11+
internal class StargProcessor : IProcessor
12+
{
13+
static readonly Dictionary<OpCode, Func<ProcessorState, int>> Operations = new()
14+
{
15+
{ OpCodes.Starg_S, GetParameterIndex },
16+
{ OpCodes.Starg, GetParameterIndex },
17+
};
18+
19+
public bool Process(ProcessorState state)
20+
{
21+
if (!Operations.TryGetValue(state.Instruction.OpCode, out var value))
22+
return false;
23+
24+
StArg(state, value(state));
25+
return true;
26+
}
27+
28+
static void StArg(ProcessorState state, int index)
29+
{
30+
var arg = state.Args[index];
31+
var expression = Processor.AdjustType(state.Stack.Pop(), arg.Type);
32+
arg.Expression = expression.Type == arg.Type ? expression : Expression.Convert(expression, arg.Type);
33+
}
34+
35+
static int GetParameterIndex(ProcessorState state)
36+
{
37+
var operand = (ParameterInfo)state.Instruction.Operand;
38+
return state.IsStatic ? operand.Position : operand.Position + 1;
39+
}
40+
}

0 commit comments

Comments
 (0)