Skip to content
52 changes: 52 additions & 0 deletions src/DelegateDecompiler.Tests/UnsupportedOpcodeProcessorTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using DelegateDecompiler.Processors;
using NUnit.Framework;
using Mono.Reflection;

namespace DelegateDecompiler.Tests
{
[TestFixture]
public class UnsupportedOpcodeProcessorTests : DecompilerTestsBase
{
static readonly ConstructorInfo InstructionCtor = typeof(Instruction)
.GetConstructor(BindingFlags.NonPublic | BindingFlags.Instance, null, new[] { typeof(int), typeof(OpCode) }, null);

[Test]
public void UnsupportedOpcode_ShouldThrowDescriptiveException()
{
Func<int, int> test = x => x;

var instruction = test.Method.GetInstructions().First();

var state = new ProcessorState(true, new Stack<Address>(), new VariableInfo[0], new List<Address>(), instruction);

var processor = new UnsupportedOpcodeProcessor();

var exception = Assert.Throws<NotSupportedException>(() => processor.Process(state));
Assert.That(exception.Message, Does.Contain(instruction.OpCode.ToString()));
}

[Test]
public void UnsupportedOpcodeProcessor_AlwaysThrows()
{
var instruction = CreateInstruction(0, OpCodes.Cpobj);
var state = new ProcessorState(true, new Stack<Address>(), new VariableInfo[0], new List<Address>(), instruction);

var processor = new UnsupportedOpcodeProcessor();

var exception = Assert.Throws<NotSupportedException>(() => processor.Process(state));
Assert.That(exception.Message, Does.Contain("cpobj"));
}

static Instruction CreateInstruction(int offset, OpCode opcode)
{
var instruction = (Instruction)InstructionCtor.Invoke(new object[] { offset, opcode });
Assume.That(instruction, Is.Not.Null);
return instruction;
}
}
}
8 changes: 6 additions & 2 deletions src/DelegateDecompiler/Processor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,9 @@ public static Expression Process(bool isStatic, VariableInfo[] locals, IList<Add
new LdfldProcessor(),
new StfldProcessor(),
new StsfldProcessor(),
new StelemProcessor()
new StelemProcessor(),
// This should be last one
new UnsupportedOpcodeProcessor()
};

Processor()
Expand Down Expand Up @@ -220,7 +222,9 @@ Expression Process()
}
else if (!processors.Any(processor => processor.Process(state)))
{
Debug.WriteLine("Unhandled!!!");
// This should never happen since UnsupportedOpcodeProcessor is the last processor
// and it always processes (by throwing an exception)
throw new InvalidOperationException("No processor handled the instruction, including the fallback processor.");
}

state.Instruction = state.Instruction.Next;
Expand Down
13 changes: 13 additions & 0 deletions src/DelegateDecompiler/Processors/UnsupportedOpcodeProcessor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using System;
using System.Reflection.Emit;

namespace DelegateDecompiler.Processors;

internal class UnsupportedOpcodeProcessor : IProcessor
{
public bool Process(ProcessorState state) =>
throw new NotSupportedException(
$"The IL opcode '{state.Instruction.OpCode}' is not supported by DelegateDecompiler. " +
$"This opcode cannot be decompiled into an expression tree. " +
$"Consider simplifying the method or using a different approach.");
}