From ff38615dc0734af314733f568571eab6181e63b3 Mon Sep 17 00:00:00 2001 From: John Austin Date: Sun, 7 Apr 2024 11:08:10 -0700 Subject: [PATCH] Adds the Unbox instructions, and a few tests for boxing. --- GrEmit.Tests/OpCodesTests/Test_Boxing.cs | 65 ++++++++++++++++++++++ GrEmit/GroboIL.cs | 21 +++++++ GrEmit/StackMutator.cs | 6 ++ GrEmit/StackMutatorCollection.cs | 1 + GrEmit/StackMutators/Unbox_StackMutator.cs | 20 +++++++ 5 files changed, 113 insertions(+) create mode 100644 GrEmit.Tests/OpCodesTests/Test_Boxing.cs create mode 100644 GrEmit/StackMutators/Unbox_StackMutator.cs diff --git a/GrEmit.Tests/OpCodesTests/Test_Boxing.cs b/GrEmit.Tests/OpCodesTests/Test_Boxing.cs new file mode 100644 index 0000000..248538b --- /dev/null +++ b/GrEmit.Tests/OpCodesTests/Test_Boxing.cs @@ -0,0 +1,65 @@ +using System; +using System.Reflection.Emit; + +using NUnit.Framework; + +namespace GrEmit.Tests.OpCodesTests +{ + [TestFixture] + public class Test_Boxing + { + [Test] + public void TestUnboxInt_Success() + { + var method = new DynamicMethod(Guid.NewGuid().ToString(), typeof(void), new[] {typeof(int)}); + var il = new GroboIL(method); + + var valueTypeLocal = il.DeclareLocal(typeof(int).MakeByRefType()); + + il.Ldc_I4(15); + il.Box(typeof(int)); + il.Unbox(typeof(int)); + il.Stloc(valueTypeLocal); + + il.Ret(); + Console.WriteLine(il.GetILCode()); + } + + // Fails because a string cannot be unboxed. + [Test] + public void TestUnboxString_Failure() + { + var method = new DynamicMethod(Guid.NewGuid().ToString(), typeof(void), new[] {typeof(int)}); + var il = new GroboIL(method); + + il.Ldstr("Test String"); + Assert.Throws(() => il.Unbox(typeof(string))); + } + + // Fails because the wrong type is on the stack + [Test] + public void TestUnboxWrong_Stack() + { + var method = new DynamicMethod(Guid.NewGuid().ToString(), typeof(void), new[] {typeof(int)}); + var il = new GroboIL(method); + + il.Ldc_I4(0); + Assert.Throws(() => il.Unbox(typeof(int))); + } + + // Fails because a string cannot be boxed. + [Test] + public void TestBoxString_Failure() + { + var method = new DynamicMethod(Guid.NewGuid().ToString(), typeof(void), new[] {typeof(int)}); + var il = new GroboIL(method); + + il.Ldstr("Test String"); + Assert.Throws(() => il.Box(typeof(string))); + il.Pop(); + + il.Ret(); + Console.WriteLine(il.GetILCode()); + } + } +} \ No newline at end of file diff --git a/GrEmit/GroboIL.cs b/GrEmit/GroboIL.cs index 52b73b6..3d9dd72 100644 --- a/GrEmit/GroboIL.cs +++ b/GrEmit/GroboIL.cs @@ -1417,6 +1417,27 @@ public void Isinst(Type type) throw new ArgumentNullException("type"); Emit(OpCodes.Isinst, type); } + + /// + /// The unbox instruction converts the object reference (type O), the boxed representation of a value type, + /// to a value type pointer (a managed pointer, type ref), its unboxed form. The supplied value type (valType) + /// is a metadata token indicating the type of value type contained within the boxed object. + /// + /// Unlike Box, which is required to make a copy of a value type for use in the object, unbox is not + /// required to copy the value type from the object. Typically it simply computes the address of the value + /// type that is already present inside of the boxed object. + /// + /// + /// The Type of boxed object. Must be a value type. + /// + public void Unbox(Type type) + { + if (type == null) + throw new ArgumentNullException("type"); + if (!type.IsValueType && !type.IsGenericParameter) + throw new ArgumentException("A value type expected", "type"); + Emit(OpCodes.Unbox, type); + } /// /// Converts the boxed representation of a type specified in the instruction to its unboxed form. diff --git a/GrEmit/StackMutator.cs b/GrEmit/StackMutator.cs index 7f57897..cf1acda 100644 --- a/GrEmit/StackMutator.cs +++ b/GrEmit/StackMutator.cs @@ -119,6 +119,12 @@ protected static void CheckIsAPointer(GroboIL il, ESType type) if (cliType != CLIType.Pointer && cliType != CLIType.NativeInt) ThrowError(il, $"A pointer type expected but was '{type}'"); } + + protected static void CheckIsNotValueType(GroboIL il, ESType type) + { + if (type.ToType().IsValueType) + ThrowError(il, $"A reference type expected but was '{type}'"); + } protected static void CheckCanBeAssigned(GroboIL il, Type to, ESType from) { diff --git a/GrEmit/StackMutatorCollection.cs b/GrEmit/StackMutatorCollection.cs index a1f8074..69cb539 100644 --- a/GrEmit/StackMutatorCollection.cs +++ b/GrEmit/StackMutatorCollection.cs @@ -176,6 +176,7 @@ public static void Mutate(OpCode opCode, GroboIL il, ILInstructionParameter para {OpCodes.Ldtoken, new LdtokenStackMutator()}, {OpCodes.Castclass, new CastclassStackMutator()}, {OpCodes.Isinst, new IsinstStackMutator()}, + {OpCodes.Unbox, new Unbox_StackMutator()}, {OpCodes.Unbox_Any, new Unbox_AnyStackMutator()}, {OpCodes.Box, new BoxStackMutator()}, {OpCodes.Newobj, new NewobjStackMutator()}, diff --git a/GrEmit/StackMutators/Unbox_StackMutator.cs b/GrEmit/StackMutators/Unbox_StackMutator.cs new file mode 100644 index 0000000..e317dda --- /dev/null +++ b/GrEmit/StackMutators/Unbox_StackMutator.cs @@ -0,0 +1,20 @@ +using GrEmit.InstructionParameters; + +namespace GrEmit.StackMutators +{ + internal class Unbox_StackMutator : StackMutator + { + public override void Mutate(GroboIL il, ILInstructionParameter parameter, ref EvaluationStack stack) + { + var unboxedValueType = ((TypeILInstructionParameter)parameter).Type; + if (!unboxedValueType.IsValueType) + { + ThrowError(il, $"Unbox may only be used with value types. Passed [{unboxedValueType}]"); + } + + CheckNotEmpty(il, stack, () => "An object reference to a value type must be put onto the evaluation stack in order to perform the 'unbox' instruction"); + CheckIsNotValueType(il, stack.Pop()); // The stack should contain a managed reference type, which we will unbox. + stack.Push(unboxedValueType.MakeByRefType()); // Pushes a reference to the value type onto the stack. + } + } +} \ No newline at end of file