diff --git a/src/coreclr/System.Private.CoreLib/System.Private.CoreLib.csproj b/src/coreclr/System.Private.CoreLib/System.Private.CoreLib.csproj index 20073739b2bbd0..377157b7091670 100644 --- a/src/coreclr/System.Private.CoreLib/System.Private.CoreLib.csproj +++ b/src/coreclr/System.Private.CoreLib/System.Private.CoreLib.csproj @@ -162,7 +162,6 @@ - @@ -170,6 +169,7 @@ + diff --git a/src/coreclr/System.Private.CoreLib/src/System/Reflection/Emit/DynamicILGenerator.cs b/src/coreclr/System.Private.CoreLib/src/System/Reflection/Emit/DynamicILGenerator.cs index 81372c76ce2ec8..0c920d1ea12bdc 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Reflection/Emit/DynamicILGenerator.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Reflection/Emit/DynamicILGenerator.cs @@ -36,14 +36,14 @@ public override LocalBuilder DeclareLocal(Type localType, bool pinned) { ArgumentNullException.ThrowIfNull(localType); - LocalBuilder localBuilder; + RuntimeLocalBuilder localBuilder; RuntimeType? rtType = localType as RuntimeType; if (rtType == null) throw new ArgumentException(SR.Argument_MustBeRuntimeType); - localBuilder = new LocalBuilder(m_localCount, localType, m_methodBuilder, pinned); + localBuilder = new RuntimeLocalBuilder(m_localCount, localType, m_methodBuilder, pinned); // add the localType to local signature m_localSignature.AddArgument(localType, pinned); m_localCount++; diff --git a/src/coreclr/System.Private.CoreLib/src/System/Reflection/Emit/RuntimeILGenerator.cs b/src/coreclr/System.Private.CoreLib/src/System/Reflection/Emit/RuntimeILGenerator.cs index e176ac2cf0e894..b8992dbe5d29b3 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Reflection/Emit/RuntimeILGenerator.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Reflection/Emit/RuntimeILGenerator.cs @@ -819,8 +819,8 @@ public override void Emit(OpCode opcode, LocalBuilder local) ArgumentNullException.ThrowIfNull(local); // Puts the opcode onto the IL stream followed by the information for local variable local. - int tempVal = local.GetLocalIndex(); - if (local.GetMethodBuilder() != m_methodBuilder) + int tempVal = local.LocalIndex; + if (local is not RuntimeLocalBuilder localBuilder || localBuilder.GetMethodBuilder() != m_methodBuilder) { throw new ArgumentException(SR.Argument_UnmatchedMethodForLocal, nameof(local)); } @@ -1185,7 +1185,7 @@ public override LocalBuilder DeclareLocal(Type localType, bool pinned) // add the localType to local signature m_localSignature.AddArgument(localType, pinned); - return new LocalBuilder(m_localCount++, localType, methodBuilder, pinned); + return new RuntimeLocalBuilder(m_localCount++, localType, methodBuilder, pinned); } public override void UsingNamespace(string usingNamespace) diff --git a/src/coreclr/System.Private.CoreLib/src/System/Reflection/Emit/LocalBuilder.cs b/src/coreclr/System.Private.CoreLib/src/System/Reflection/Emit/RuntimeLocalBuilder.cs similarity index 69% rename from src/coreclr/System.Private.CoreLib/src/System/Reflection/Emit/LocalBuilder.cs rename to src/coreclr/System.Private.CoreLib/src/System/Reflection/Emit/RuntimeLocalBuilder.cs index 9dd4d6d5c2cfd9..e85c08daabefbe 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Reflection/Emit/LocalBuilder.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Reflection/Emit/RuntimeLocalBuilder.cs @@ -3,7 +3,7 @@ namespace System.Reflection.Emit { - public sealed class LocalBuilder : LocalVariableInfo + internal sealed class RuntimeLocalBuilder : LocalBuilder { #region Private Data Members private readonly int m_localIndex; @@ -13,9 +13,9 @@ public sealed class LocalBuilder : LocalVariableInfo #endregion #region Constructor - internal LocalBuilder(int localIndex, Type localType, MethodInfo methodBuilder) + internal RuntimeLocalBuilder(int localIndex, Type localType, MethodInfo methodBuilder) : this(localIndex, localType, methodBuilder, false) { } - internal LocalBuilder(int localIndex, Type localType, MethodInfo methodBuilder, bool isPinned) + internal RuntimeLocalBuilder(int localIndex, Type localType, MethodInfo methodBuilder, bool isPinned) { m_isPinned = isPinned; m_localIndex = localIndex; @@ -25,14 +25,7 @@ internal LocalBuilder(int localIndex, Type localType, MethodInfo methodBuilder, #endregion #region Internal Members - internal int GetLocalIndex() - { - return m_localIndex; - } - internal MethodInfo GetMethodBuilder() - { - return m_methodBuilder; - } + internal MethodInfo GetMethodBuilder() => m_methodBuilder; #endregion #region LocalVariableInfo Override diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System.Private.CoreLib.csproj b/src/coreclr/nativeaot/System.Private.CoreLib/src/System.Private.CoreLib.csproj index a007a3ed7fbba5..ec3a3363a511a7 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System.Private.CoreLib.csproj +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System.Private.CoreLib.csproj @@ -144,7 +144,6 @@ - diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Emit/LocalBuilder.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Emit/LocalBuilder.cs deleted file mode 100644 index 2d1afc04d5b730..00000000000000 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Emit/LocalBuilder.cs +++ /dev/null @@ -1,37 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -namespace System.Reflection.Emit -{ - public sealed class LocalBuilder : LocalVariableInfo - { - internal LocalBuilder() - { - // Prevent generating a default constructor - } - - public override bool IsPinned - { - get - { - return default; - } - } - - public override int LocalIndex - { - get - { - return default; - } - } - - public override Type LocalType - { - get - { - return default; - } - } - } -} diff --git a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems index ed3325afbec528..b27a43992e2ce7 100644 --- a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems +++ b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems @@ -418,7 +418,6 @@ - @@ -674,7 +673,9 @@ + + diff --git a/src/libraries/System.Private.CoreLib/src/System/Reflection/Emit/LocalBuilder.cs b/src/libraries/System.Private.CoreLib/src/System/Reflection/Emit/LocalBuilder.cs new file mode 100644 index 00000000000000..7c38c287bf22b5 --- /dev/null +++ b/src/libraries/System.Private.CoreLib/src/System/Reflection/Emit/LocalBuilder.cs @@ -0,0 +1,16 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace System.Reflection.Emit +{ + public abstract class LocalBuilder : LocalVariableInfo + { + /// + /// Initializes a new instance of the class. + /// + /// + /// This constructor is invoked by derived classes. + /// + protected LocalBuilder() { } + } +} diff --git a/src/libraries/System.Reflection.Emit.ILGeneration/ref/System.Reflection.Emit.ILGeneration.cs b/src/libraries/System.Reflection.Emit.ILGeneration/ref/System.Reflection.Emit.ILGeneration.cs index e549331571dba1..f79ee4288df6ce 100644 --- a/src/libraries/System.Reflection.Emit.ILGeneration/ref/System.Reflection.Emit.ILGeneration.cs +++ b/src/libraries/System.Reflection.Emit.ILGeneration/ref/System.Reflection.Emit.ILGeneration.cs @@ -67,9 +67,9 @@ public virtual void ThrowException([System.Diagnostics.CodeAnalysis.DynamicallyA public static bool operator ==(System.Reflection.Emit.Label a, System.Reflection.Emit.Label b) { throw null; } public static bool operator !=(System.Reflection.Emit.Label a, System.Reflection.Emit.Label b) { throw null; } } - public sealed partial class LocalBuilder : System.Reflection.LocalVariableInfo + public abstract class LocalBuilder : System.Reflection.LocalVariableInfo { - internal LocalBuilder() { } + protected LocalBuilder() { } public override bool IsPinned { get { throw null; } } public override int LocalIndex { get { throw null; } } public override System.Type LocalType { get { throw null; } } diff --git a/src/libraries/System.Reflection.Emit/src/Resources/Strings.resx b/src/libraries/System.Reflection.Emit/src/Resources/Strings.resx index cc67be68a05b95..e425a272ac097f 100644 --- a/src/libraries/System.Reflection.Emit/src/Resources/Strings.resx +++ b/src/libraries/System.Reflection.Emit/src/Resources/Strings.resx @@ -186,6 +186,9 @@ Method body should not exist. + + Local passed in does not belong to this ILGenerator. + Invalid Label. diff --git a/src/libraries/System.Reflection.Emit/src/System.Reflection.Emit.csproj b/src/libraries/System.Reflection.Emit/src/System.Reflection.Emit.csproj index 3c6273439966ac..03b448b163bc71 100644 --- a/src/libraries/System.Reflection.Emit/src/System.Reflection.Emit.csproj +++ b/src/libraries/System.Reflection.Emit/src/System.Reflection.Emit.csproj @@ -12,6 +12,7 @@ + diff --git a/src/libraries/System.Reflection.Emit/src/System/Reflection/Emit/ILGeneratorImpl.cs b/src/libraries/System.Reflection.Emit/src/System/Reflection/Emit/ILGeneratorImpl.cs index eb9d5f70a81dc7..e7331233adb3dc 100644 --- a/src/libraries/System.Reflection.Emit/src/System/Reflection/Emit/ILGeneratorImpl.cs +++ b/src/libraries/System.Reflection.Emit/src/System/Reflection/Emit/ILGeneratorImpl.cs @@ -17,6 +17,7 @@ internal sealed class ILGeneratorImpl : ILGenerator private bool _hasDynamicStackAllocation; private int _maxStackSize; private int _currentStack; + private List _locals = new(); private Dictionary _labelTable = new(2); internal ILGeneratorImpl(MethodBuilder methodBuilder, int size) @@ -28,9 +29,9 @@ internal ILGeneratorImpl(MethodBuilder methodBuilder, int size) } internal int GetMaxStackSize() => _maxStackSize; - internal InstructionEncoder Instructions => _il; internal bool HasDynamicStackAllocation => _hasDynamicStackAllocation; + internal List Locals => _locals; public override int ILOffset => _il.Offset; @@ -40,7 +41,19 @@ internal ILGeneratorImpl(MethodBuilder methodBuilder, int size) public override void BeginFaultBlock() => throw new NotImplementedException(); public override void BeginFinallyBlock() => throw new NotImplementedException(); public override void BeginScope() => throw new NotImplementedException(); - public override LocalBuilder DeclareLocal(Type localType, bool pinned) => throw new NotImplementedException(); + + public override LocalBuilder DeclareLocal(Type localType, bool pinned) + { + if (_methodBuilder is not MethodBuilderImpl methodBuilder) + throw new NotSupportedException(); + + ArgumentNullException.ThrowIfNull(localType); + + LocalBuilder local = new LocalBuilderImpl(_locals.Count, localType, methodBuilder, pinned); + _locals.Add(local); + + return local; + } public override Label DefineLabel() { @@ -228,7 +241,34 @@ public override void Emit(OpCode opcode, Label[] labels) } } - public override void Emit(OpCode opcode, LocalBuilder local) => throw new NotImplementedException(); + public override void Emit(OpCode opcode, LocalBuilder local) + { + ArgumentNullException.ThrowIfNull(local); + + if (local is not LocalBuilderImpl localBuilder || localBuilder.GetMethodBuilder() != _methodBuilder) + { + throw new ArgumentException(SR.Argument_UnmatchedMethodForLocal, nameof(local)); + } + + int tempVal = local.LocalIndex; + string name = opcode.Name!; + + if (name.StartsWith("ldloca")) + { + _il.LoadLocalAddress(tempVal); + } + else if (name.StartsWith("ldloc")) + { + _il.LoadLocal(tempVal); + } + else if (name.StartsWith("stloc")) + { + _il.StoreLocal(tempVal); + } + + UpdateStackSize(opcode); + } + public override void Emit(OpCode opcode, SignatureHelper signature) => throw new NotImplementedException(); public override void Emit(OpCode opcode, FieldInfo field) => throw new NotImplementedException(); public override void Emit(OpCode opcode, MethodInfo meth) => throw new NotImplementedException(); diff --git a/src/libraries/System.Reflection.Emit/src/System/Reflection/Emit/LocalBuilderImpl.cs b/src/libraries/System.Reflection.Emit/src/System/Reflection/Emit/LocalBuilderImpl.cs new file mode 100644 index 00000000000000..fa9bffbec4345c --- /dev/null +++ b/src/libraries/System.Reflection.Emit/src/System/Reflection/Emit/LocalBuilderImpl.cs @@ -0,0 +1,35 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace System.Reflection.Emit +{ + internal sealed class LocalBuilderImpl : LocalBuilder + { + #region Private Data Members + private readonly int _localIndex; + private readonly Type _localType; + private readonly MethodInfo _method; + private readonly bool _isPinned; + #endregion + + #region Constructor + internal LocalBuilderImpl(int index, Type type, MethodInfo method, bool isPinned) + { + _isPinned = isPinned; + _localIndex = index; + _localType = type; + _method = method; + } + #endregion + + #region Internal Members + internal MethodInfo GetMethodBuilder() => _method; + #endregion + + #region LocalVariableInfo Override + public override bool IsPinned => _isPinned; + public override Type LocalType => _localType; + public override int LocalIndex => _localIndex; + #endregion + } +} diff --git a/src/libraries/System.Reflection.Emit/src/System/Reflection/Emit/ModuleBuilderImpl.cs b/src/libraries/System.Reflection.Emit/src/System/Reflection/Emit/ModuleBuilderImpl.cs index c30398046ab60e..0dde640eae2694 100644 --- a/src/libraries/System.Reflection.Emit/src/System/Reflection/Emit/ModuleBuilderImpl.cs +++ b/src/libraries/System.Reflection.Emit/src/System/Reflection/Emit/ModuleBuilderImpl.cs @@ -188,7 +188,9 @@ private void WriteMethods(TypeBuilderImpl typeBuilder, List - methodBodyEncoder.AddMethodBody( + private static int AddMethodBody(MethodBuilderImpl method, ILGeneratorImpl il, StandaloneSignatureHandle signature, MethodBodyStreamEncoder bodyEncoder) => + bodyEncoder.AddMethodBody( instructionEncoder: il.Instructions, maxStack: il.GetMaxStackSize(), - localVariablesSignature: default, // TODO + localVariablesSignature: signature, attributes: method.InitLocals ? MethodBodyAttributes.InitLocals : MethodBodyAttributes.None, hasDynamicStackAllocation: il.HasDynamicStackAllocation); diff --git a/src/libraries/System.Reflection.Emit/src/System/Reflection/Emit/SignatureHelper.cs b/src/libraries/System.Reflection.Emit/src/System/Reflection/Emit/SignatureHelper.cs index 5e00821235d997..8a165f306fbe9c 100644 --- a/src/libraries/System.Reflection.Emit/src/System/Reflection/Emit/SignatureHelper.cs +++ b/src/libraries/System.Reflection.Emit/src/System/Reflection/Emit/SignatureHelper.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Collections.Generic; using System.Collections.Immutable; using System.Reflection.Metadata; using System.Reflection.Metadata.Ecma335; @@ -10,6 +11,20 @@ namespace System.Reflection.Emit // TODO: Only support simple signatures. More complex signatures (generics, array, byref, pointers etc) will be added. internal static class MetadataSignatureHelper { + internal static BlobBuilder LocalSignatureEncoder(List locals, ModuleBuilderImpl module) + { + BlobBuilder localSignature = new(); + LocalVariablesEncoder encoder = new BlobEncoder(localSignature).LocalVariableSignature(locals.Count); + + foreach(LocalBuilder local in locals) + { + WriteSignatureForType(encoder.AddVariable().Type(local.LocalType.IsByRef, local.IsPinned), + local.LocalType.IsByRef ? local.LocalType.GetElementType()! : local.LocalType, module); + } + + return localSignature; + } + internal static BlobBuilder FieldSignatureEncoder(Type fieldType, ModuleBuilderImpl module) { BlobBuilder fieldSignature = new(); diff --git a/src/libraries/System.Reflection.Emit/tests/PersistableAssemblyBuilder/AssemblySaveILGeneratorTests.cs b/src/libraries/System.Reflection.Emit/tests/PersistableAssemblyBuilder/AssemblySaveILGeneratorTests.cs index 31af05ed611e3a..5c5141f544c14c 100644 --- a/src/libraries/System.Reflection.Emit/tests/PersistableAssemblyBuilder/AssemblySaveILGeneratorTests.cs +++ b/src/libraries/System.Reflection.Emit/tests/PersistableAssemblyBuilder/AssemblySaveILGeneratorTests.cs @@ -3,7 +3,6 @@ using System.IO; using System.Linq; -using System.Text; using Xunit; namespace System.Reflection.Emit.Tests @@ -307,7 +306,7 @@ public void Label_SwitchCase() ILGenerator il = methodBuilder.GetILGenerator(); Label defaultCase = il.DefineLabel(); Label endOfMethod = il.DefineLabel(); - Label[] jumpTable = [ il.DefineLabel(), il.DefineLabel(), il.DefineLabel(), il.DefineLabel(), il.DefineLabel() ]; + Label[] jumpTable = [il.DefineLabel(), il.DefineLabel(), il.DefineLabel(), il.DefineLabel(), il.DefineLabel()]; // public string Method1(int P_0) => P_0 switch ... il.Emit(OpCodes.Ldarg_1); @@ -361,5 +360,187 @@ public void Label_SwitchCase() Assert.Equal(69, bodyBytes.Length); } } + + [Fact] + public void LocalBuilderMultipleLocalsUsage() + { + using (TempFile file = TempFile.Create()) + { + AssemblyBuilder ab = AssemblySaveTools.PopulateAssemblyBuilderTypeBuilderAndSaveMethod(out TypeBuilder type, out MethodInfo saveMethod); + MethodBuilder methodBuilder = type.DefineMethod("Method1", MethodAttributes.Public | MethodAttributes.Static, typeof(int), new[] { typeof(int), typeof(string) }); + ILGenerator il = methodBuilder.GetILGenerator(); + LocalBuilder intLocal = il.DeclareLocal(typeof(int)); + LocalBuilder stringLocal = il.DeclareLocal(typeof(string)); + LocalBuilder shortLocal = il.DeclareLocal(typeof(short), pinned: true); ; + LocalBuilder int2Local = il.DeclareLocal(typeof(int), pinned: false); + il.Emit(OpCodes.Ldarg, 1); + il.Emit(OpCodes.Stloc_1); + il.Emit(OpCodes.Ldstr, "string value"); + il.Emit(OpCodes.Stloc, stringLocal); + il.Emit(OpCodes.Ldloc, stringLocal); + il.Emit(OpCodes.Starg, 1); + il.Emit(OpCodes.Ldarg_0); + il.Emit(OpCodes.Stloc_0); + il.Emit(OpCodes.Ldc_I4_S, 120); + il.Emit(OpCodes.Stloc, 2); + il.Emit(OpCodes.Ldloc, shortLocal); + il.Emit(OpCodes.Ldloc, 0); + il.Emit(OpCodes.Add); + il.Emit(OpCodes.Stloc, intLocal); + il.Emit(OpCodes.Ldloca, intLocal); + il.Emit(OpCodes.Ldind_I); + il.Emit(OpCodes.Stloc, int2Local); + il.Emit(OpCodes.Ldloc_3); + il.Emit(OpCodes.Ret); + + MethodInfo getMaxStackSizeMethod = LoadILGenerator_GetMaxStackSizeMethod(); + Assert.Equal(2, getMaxStackSizeMethod.Invoke(il, new object[0])); + saveMethod.Invoke(ab, new object[] { file.Path }); + + Assembly assemblyFromDisk = AssemblySaveTools.LoadAssemblyFromPath(file.Path); + Type typeFromDisk = assemblyFromDisk.Modules.First().GetType("MyType"); + MethodBody body = typeFromDisk.GetMethod("Method1").GetMethodBody(); + Assert.Equal(4, body.LocalVariables.Count); + Assert.Equal(intLocal.LocalIndex, body.LocalVariables[0].LocalIndex); + Assert.Equal(intLocal.LocalType.FullName, body.LocalVariables[0].LocalType.FullName); + Assert.Equal(intLocal.IsPinned, body.LocalVariables[0].IsPinned); + Assert.Equal(stringLocal.LocalIndex, body.LocalVariables[1].LocalIndex); + Assert.Equal(stringLocal.LocalType.FullName, body.LocalVariables[1].LocalType.FullName); + Assert.Equal(stringLocal.IsPinned, body.LocalVariables[1].IsPinned); + Assert.Equal(shortLocal.LocalIndex, body.LocalVariables[2].LocalIndex); + Assert.Equal(shortLocal.LocalType.FullName, body.LocalVariables[2].LocalType.FullName); + Assert.Equal(shortLocal.IsPinned, body.LocalVariables[2].IsPinned); + Assert.Equal(int2Local.LocalIndex, body.LocalVariables[3].LocalIndex); + Assert.Equal(int2Local.LocalType.FullName, body.LocalVariables[3].LocalType.FullName); + Assert.Equal(int2Local.IsPinned, body.LocalVariables[3].IsPinned); + byte[]? bodyBytes = body.GetILAsByteArray(); + Assert.Equal((byte)OpCodes.Ldarg_1.Value, bodyBytes[0]); + Assert.Equal((byte)OpCodes.Stloc_1.Value, bodyBytes[1]); + Assert.Equal((byte)OpCodes.Ldstr.Value, bodyBytes[2]); + Assert.Equal((byte)OpCodes.Stloc_1.Value, bodyBytes[7]); + Assert.Equal((byte)OpCodes.Ldloc_1.Value, bodyBytes[8]); + Assert.Equal((byte)OpCodes.Starg_S.Value, bodyBytes[9]); + Assert.Equal((byte)OpCodes.Ldarg_0.Value, bodyBytes[11]); + Assert.Equal((byte)OpCodes.Stloc_0.Value, bodyBytes[12]); + Assert.Equal((byte)OpCodes.Ldc_I4_S.Value, bodyBytes[13]); + Assert.Equal(120, BitConverter.ToInt32(bodyBytes.AsSpan().Slice(14, 4))); + Assert.Equal(0xFE, bodyBytes[18]); // Stloc instruction occupies 2 bytes 0xfe0e + Assert.Equal(0x0E, bodyBytes[19]); + Assert.Equal(2, BitConverter.ToInt32(bodyBytes.AsSpan().Slice(20, 4))); // index 2 of 'il.Emit(OpCodes.Stloc, 2);' instruction + Assert.Equal((byte)OpCodes.Ldloc_2.Value, bodyBytes[24]); + Assert.Equal(0xFE, bodyBytes[25]); // Ldloc = 0xfe0c + Assert.Equal(0x0C, bodyBytes[26]); + Assert.Equal(0, BitConverter.ToInt32(bodyBytes.AsSpan().Slice(27, 4))); // index 0 of 'il.Emit(OpCodes.Ldloc, 0);' instruction + Assert.Equal((byte)OpCodes.Add.Value, bodyBytes[31]); + Assert.Equal((byte)OpCodes.Stloc_0.Value, bodyBytes[32]); + Assert.Equal((byte)OpCodes.Ldloca_S.Value, bodyBytes[33]); + Assert.Equal(0, bodyBytes[34]); // intLocal index is 0 for 'il.Emit(OpCodes.Ldloca, intLocal);' instruction + Assert.Equal((byte)OpCodes.Ldind_I.Value, bodyBytes[35]); + Assert.Equal((byte)OpCodes.Stloc_3.Value, bodyBytes[36]); + Assert.Equal((byte)OpCodes.Ldloc_3.Value, bodyBytes[37]); + Assert.Equal(OpCodes.Ret.Value, bodyBytes[38]); + } + } + + [Fact] + public void LocalBuilderMultipleTypesWithMultipleMethodsWithLocals() + { + using (TempFile file = TempFile.Create()) + { + AssemblyBuilder ab = AssemblySaveTools.PopulateAssemblyBuilderTypeBuilderAndSaveMethod(out TypeBuilder type, out MethodInfo saveMethod); + MethodBuilder methodBuilder = type.DefineMethod("Method1", MethodAttributes.Public | MethodAttributes.Static, typeof(string), new[] { typeof(int), typeof(string) }); + ILGenerator il = methodBuilder.GetILGenerator(); + LocalBuilder intLocal = il.DeclareLocal(typeof(int)); + LocalBuilder stringLocal = il.DeclareLocal(typeof(string)); + LocalBuilder shortLocal = il.DeclareLocal(typeof(short)); + il.Emit(OpCodes.Ldstr, "string value"); + il.Emit(OpCodes.Stloc, stringLocal); + il.Emit(OpCodes.Ldloc, stringLocal); + il.Emit(OpCodes.Starg, 1); + il.Emit(OpCodes.Ldarg_0); + il.Emit(OpCodes.Stloc_0); + il.Emit(OpCodes.Ldc_I4_S, 120); + il.Emit(OpCodes.Stloc, 2); + il.Emit(OpCodes.Ldloc, shortLocal); + il.Emit(OpCodes.Ldloc, 0); + il.Emit(OpCodes.Add); + il.Emit(OpCodes.Stloc, intLocal); + il.Emit(OpCodes.Ldloc, stringLocal); + il.Emit(OpCodes.Ret); + MethodBuilder multiplyMethod = type.DefineMethod("MultiplyMethod", MethodAttributes.Public, typeof(int), new[] { typeof(int) }); + ILGenerator multiplyMethodIL = multiplyMethod.GetILGenerator(); + LocalBuilder iLocal = multiplyMethodIL.DeclareLocal(typeof(int)); + LocalBuilder shLocal = multiplyMethodIL.DeclareLocal(typeof(short)); + multiplyMethodIL.Emit(OpCodes.Ldarg, 1); + multiplyMethodIL.Emit(OpCodes.Stloc, iLocal); + multiplyMethodIL.Emit(OpCodes.Ldloc, iLocal); + multiplyMethodIL.Emit(OpCodes.Ldc_I4_S, 11); + multiplyMethodIL.Emit(OpCodes.Stloc, shLocal); + multiplyMethodIL.Emit(OpCodes.Ldloc, shLocal); + multiplyMethodIL.Emit(OpCodes.Mul); + multiplyMethodIL.Emit(OpCodes.Stloc, iLocal); + multiplyMethodIL.Emit(OpCodes.Ldloc, iLocal); + multiplyMethodIL.Emit(OpCodes.Ret); + + TypeBuilder anotherType = ab.GetDynamicModule("MyModule").DefineType("AnotherType", TypeAttributes.NotPublic, type); + MethodBuilder stringMethod = anotherType.DefineMethod("StringMethod", MethodAttributes.FamORAssem, typeof(string), Type.EmptyTypes); + ILGenerator stringMethodIL = stringMethod.GetILGenerator(); + LocalBuilder strLocal = stringMethodIL.DeclareLocal(typeof(string)); + stringMethodIL.Emit(OpCodes.Ldstr, "Hello world!"); + stringMethodIL.Emit(OpCodes.Stloc, strLocal); + stringMethodIL.Emit(OpCodes.Ldloc, strLocal); + stringMethodIL.Emit(OpCodes.Ret); + MethodBuilder typeMethod = anotherType.DefineMethod("TypeMethod", MethodAttributes.Family, type, new[] { anotherType, type }); + ILGenerator typeMethodIL = typeMethod.GetILGenerator(); + typeMethodIL.Emit(OpCodes.Ldarg, 1); + LocalBuilder typeLocal = typeMethodIL.DeclareLocal(type); + LocalBuilder anotherTypeLocal = typeMethodIL.DeclareLocal(anotherType); + typeMethodIL.Emit(OpCodes.Stloc, anotherTypeLocal); + typeMethodIL.Emit(OpCodes.Ldloc, anotherTypeLocal); + typeMethodIL.Emit(OpCodes.Stloc, typeLocal); + typeMethodIL.Emit(OpCodes.Ldloc, typeLocal); + typeMethodIL.Emit(OpCodes.Ret); + MethodBuilder longMethod = anotherType.DefineMethod("LongMethod", MethodAttributes.Static, typeof(long), Type.EmptyTypes); + ILGenerator longMethodIL = longMethod.GetILGenerator(); + longMethodIL.Emit(OpCodes.Ldc_I8, 1234567L); + LocalBuilder longLocal = longMethodIL.DeclareLocal(typeof(long)); + LocalBuilder shiftLocal = longMethodIL.DeclareLocal(typeof(int)); + longMethodIL.Emit(OpCodes.Stloc, longLocal); + longMethodIL.Emit(OpCodes.Ldc_I4_5); + longMethodIL.Emit(OpCodes.Stloc, shiftLocal); + longMethodIL.Emit(OpCodes.Ldloc, longLocal); + longMethodIL.Emit(OpCodes.Ldloc, shiftLocal); + longMethodIL.Emit(OpCodes.Shl); + longMethodIL.Emit(OpCodes.Stloc, longLocal); + longMethodIL.Emit(OpCodes.Ldloc, longLocal); + longMethodIL.Emit(OpCodes.Ret); + + saveMethod.Invoke(ab, new object[] { file.Path }); + + Assembly assemblyFromDisk = AssemblySaveTools.LoadAssemblyFromPath(file.Path); + Module moduleFromFile = assemblyFromDisk.Modules.First(); + Type typeFromDisk = moduleFromFile.GetType("MyType"); + Assert.Equal(2, typeFromDisk.GetMethod("MultiplyMethod").GetMethodBody().LocalVariables.Count); + Assert.Equal(3, typeFromDisk.GetMethod("Method1").GetMethodBody().LocalVariables.Count); + Type anotherTypeFromDisk = moduleFromFile.GetType("AnotherType"); + Assert.Equal(1, anotherTypeFromDisk.GetMethod("StringMethod", BindingFlags.NonPublic | BindingFlags.Instance).GetMethodBody().LocalVariables.Count); + Assert.Equal(2, anotherTypeFromDisk.GetMethod("TypeMethod", BindingFlags.NonPublic | BindingFlags.Instance).GetMethodBody().LocalVariables.Count); + Assert.Equal(2, anotherTypeFromDisk.GetMethod("LongMethod", BindingFlags.NonPublic | BindingFlags.Static).GetMethodBody().LocalVariables.Count); + } + } + + [Fact] + public void LocalBuilderExceptions() + { + AssemblyBuilder ab = AssemblySaveTools.PopulateAssemblyBuilderTypeBuilderAndSaveMethod(out TypeBuilder type, out MethodInfo saveMethod); + ILGenerator il = type.DefineMethod("Method1", MethodAttributes.Public).GetILGenerator(); + ILGenerator anotherIL = type.DefineMethod("AnotherMethod", MethodAttributes.Public).GetILGenerator(); + LocalBuilder stringLocal = il.DeclareLocal(typeof(string)); + LocalBuilder nullBuilder = null; + + Assert.Throws(() => il.DeclareLocal(null!)); + Assert.Throws(() => il.Emit(OpCodes.Ldloc, nullBuilder)); + Assert.Throws(() => anotherIL.Emit(OpCodes.Ldloc, stringLocal)); + } } } diff --git a/src/mono/System.Private.CoreLib/System.Private.CoreLib.csproj b/src/mono/System.Private.CoreLib/System.Private.CoreLib.csproj index d1f463b24c58bc..fd9cbe151fbe80 100644 --- a/src/mono/System.Private.CoreLib/System.Private.CoreLib.csproj +++ b/src/mono/System.Private.CoreLib/System.Private.CoreLib.csproj @@ -221,7 +221,6 @@ - @@ -231,6 +230,7 @@ + diff --git a/src/mono/System.Private.CoreLib/src/System/Reflection/Emit/RuntimeILGenerator.Mono.cs b/src/mono/System.Private.CoreLib/src/System/Reflection/Emit/RuntimeILGenerator.Mono.cs index 755c552a67b85a..ae834947bc078b 100644 --- a/src/mono/System.Private.CoreLib/src/System/Reflection/Emit/RuntimeILGenerator.Mono.cs +++ b/src/mono/System.Private.CoreLib/src/System/Reflection/Emit/RuntimeILGenerator.Mono.cs @@ -207,7 +207,7 @@ public LabelData(int addr, int maxStack) private int code_len; private int max_stack; private int cur_stack; - private LocalBuilder[]? locals; + private RuntimeLocalBuilder[]? locals; private ILExceptionInfo[]? ex_handlers; private int num_token_fixups; private object? token_fixups; @@ -441,19 +441,19 @@ public override LocalBuilder DeclareLocal(Type localType, bool pinned) ArgumentNullException.ThrowIfNull(localType); if (localType.IsUserType) throw new NotSupportedException(SR.PlatformNotSupported_UserDefinedSubclassesOfType); - LocalBuilder res = new LocalBuilder(localType, this); + RuntimeLocalBuilder res = new RuntimeLocalBuilder(localType, this); res.is_pinned = pinned; if (locals != null) { - LocalBuilder[] new_l = new LocalBuilder[locals.Length + 1]; + RuntimeLocalBuilder[] new_l = new RuntimeLocalBuilder[locals.Length + 1]; Array.Copy(locals, new_l, locals.Length); new_l[locals.Length] = res; locals = new_l; } else { - locals = new LocalBuilder[1]; + locals = new RuntimeLocalBuilder[1]; locals[0] = res; } res.position = (ushort)(locals.Length - 1); @@ -619,10 +619,10 @@ public override void Emit(OpCode opcode, Label[] labels) public override void Emit(OpCode opcode, LocalBuilder local) { ArgumentNullException.ThrowIfNull(local); - if (local.ilgen != this) + if (local is not RuntimeLocalBuilder localBuilder || localBuilder.ilgen != this) throw new ArgumentException(SR.Argument_UnmatchedMethodForLocal, nameof(local)); - uint pos = local.position; + uint pos = localBuilder.position; if ((opcode == OpCodes.Ldloca_S || opcode == OpCodes.Ldloc_S || opcode == OpCodes.Stloc_S) && pos > 255) throw new InvalidOperationException(SR.InvalidOperation_BadInstructionOrIndexOutOfBound); diff --git a/src/mono/System.Private.CoreLib/src/System/Reflection/Emit/LocalBuilder.Mono.cs b/src/mono/System.Private.CoreLib/src/System/Reflection/Emit/RuntimeLocalBuilder.Mono.cs similarity index 93% rename from src/mono/System.Private.CoreLib/src/System/Reflection/Emit/LocalBuilder.Mono.cs rename to src/mono/System.Private.CoreLib/src/System/Reflection/Emit/RuntimeLocalBuilder.Mono.cs index 017e072f7bb725..e178376470ddc7 100644 --- a/src/mono/System.Private.CoreLib/src/System/Reflection/Emit/LocalBuilder.Mono.cs +++ b/src/mono/System.Private.CoreLib/src/System/Reflection/Emit/RuntimeLocalBuilder.Mono.cs @@ -25,7 +25,7 @@ // // -// System.Reflection.Emit/LocalBuilder.cs +// System.Reflection.Emit/RuntimeLocalBuilder.cs // // Authors: // Paolo Molaro (lupus@ximian.com) @@ -41,7 +41,7 @@ namespace System.Reflection.Emit { [StructLayout(LayoutKind.Sequential)] - public sealed partial class LocalBuilder : LocalVariableInfo + internal sealed partial class RuntimeLocalBuilder : LocalBuilder { #region Sync with MonoReflectionLocalBuilder in object-internals.h internal Type type; @@ -55,7 +55,7 @@ public sealed partial class LocalBuilder : LocalVariableInfo private int endOffset; [DynamicDependency(nameof(name))] // Automatically keeps all previous fields too due to StructLayout - internal LocalBuilder(Type t, ILGenerator ilgen) + internal RuntimeLocalBuilder(Type t, ILGenerator ilgen) { this.type = t; this.ilgen = ilgen;