From 005c6a340574dd5faf58c9eff8c9570a8efa13c0 Mon Sep 17 00:00:00 2001 From: Buyaa Namnan Date: Mon, 16 Oct 2023 10:34:06 -0700 Subject: [PATCH 1/4] Emit/Call member references in IL --- .../src/Resources/Strings.resx | 3 + .../Reflection/Emit/FieldBuilderImpl.cs | 7 +- .../System/Reflection/Emit/ILGeneratorImpl.cs | 96 +++++++++++- .../Reflection/Emit/MethodBuilderImpl.cs | 8 +- .../Reflection/Emit/ModuleBuilderImpl.cs | 138 ++++++++++++++++-- .../AssemblySaveILGeneratorTests.cs | 110 +++++++++++++- .../AssemblySaveTools.cs | 1 - 7 files changed, 333 insertions(+), 30 deletions(-) diff --git a/src/libraries/System.Reflection.Emit/src/Resources/Strings.resx b/src/libraries/System.Reflection.Emit/src/Resources/Strings.resx index cc67be68a05b9..96b77d2e6af13 100644 --- a/src/libraries/System.Reflection.Emit/src/Resources/Strings.resx +++ b/src/libraries/System.Reflection.Emit/src/Resources/Strings.resx @@ -192,4 +192,7 @@ Only 'OpCode.Switch' can be used. + + The specified opcode cannot be passed to EmitCall. + \ No newline at end of file diff --git a/src/libraries/System.Reflection.Emit/src/System/Reflection/Emit/FieldBuilderImpl.cs b/src/libraries/System.Reflection.Emit/src/System/Reflection/Emit/FieldBuilderImpl.cs index 39e8881c2ab8c..54c24aa7bdb92 100644 --- a/src/libraries/System.Reflection.Emit/src/System/Reflection/Emit/FieldBuilderImpl.cs +++ b/src/libraries/System.Reflection.Emit/src/System/Reflection/Emit/FieldBuilderImpl.cs @@ -7,8 +7,6 @@ using System.Globalization; using System.Reflection.Metadata; using System.Reflection.Metadata.Ecma335; -using System.Runtime.InteropServices; -using System.Runtime.InteropServices.Marshalling; namespace System.Reflection.Emit { @@ -23,6 +21,7 @@ internal sealed class FieldBuilderImpl : FieldBuilder internal int _offset; internal List? _customAttributes; internal object? _defaultValue = DBNull.Value; + internal FieldDefinitionHandle _handle; internal FieldBuilderImpl(TypeBuilderImpl typeBuilder, string fieldName, Type type, FieldAttributes attributes) { @@ -107,7 +106,7 @@ protected override void SetCustomAttributeCore(ConstructorInfo con, ReadOnlySpan return; case "System.Runtime.InteropServices.MarshalAsAttribute": _attributes |= FieldAttributes.HasFieldMarshal; - _marshallingData = MarshallingData.CreateMarshallingData(con, binaryAttribute, isField : true); + _marshallingData = MarshallingData.CreateMarshallingData(con, binaryAttribute, isField: true); return; } @@ -124,7 +123,7 @@ protected override void SetOffsetCore(int iOffset) #region MemberInfo Overrides - public override int MetadataToken => throw new NotImplementedException(); + public override int MetadataToken => _handle == default ? 0 : MetadataTokens.GetToken(_handle); public override Module Module => _typeBuilder.Module; 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 eb9d5f70a81dc..a8835cf29f717 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 @@ -18,6 +18,7 @@ internal sealed class ILGeneratorImpl : ILGenerator private int _maxStackSize; private int _currentStack; private Dictionary _labelTable = new(2); + private List> _memberReferences = new(); internal ILGeneratorImpl(MethodBuilder methodBuilder, int size) { @@ -28,7 +29,7 @@ internal ILGeneratorImpl(MethodBuilder methodBuilder, int size) } internal int GetMaxStackSize() => _maxStackSize; - + internal List> GetMemberReferences() => _memberReferences; internal InstructionEncoder Instructions => _il; internal bool HasDynamicStackAllocation => _hasDynamicStackAllocation; @@ -55,6 +56,13 @@ private void UpdateStackSize(OpCode opCode) _maxStackSize = Math.Max(_maxStackSize, _currentStack); } + private void UpdateStackSize(OpCode opCode, int stackchange) + { + _currentStack += opCode.EvaluationStackDelta; + _currentStack += stackchange; + _maxStackSize = Math.Max(_maxStackSize, _currentStack); + } + public void EmitOpcode(OpCode opcode) { if (opcode == OpCodes.Localloc) @@ -230,10 +238,88 @@ 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, 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(); - public override void Emit(OpCode opcode, Type cls) => throw new NotImplementedException(); - public override void EmitCall(OpCode opcode, MethodInfo methodInfo, Type[]? optionalParameterTypes) => throw new NotImplementedException(); + + public override void Emit(OpCode opcode, FieldInfo field) + { + ArgumentNullException.ThrowIfNull(field); + + EmitMember(opcode, field); + } + + public override void Emit(OpCode opcode, MethodInfo meth) + { + ArgumentNullException.ThrowIfNull(meth); + + if (opcode.Equals(OpCodes.Call) || opcode.Equals(OpCodes.Callvirt) || opcode.Equals(OpCodes.Newobj)) + { + EmitCall(opcode, meth, null); + } + else + { + EmitMember(opcode, meth); + } + } + + private void EmitMember(OpCode opcode, MemberInfo member) + { + EmitOpcode(opcode); + _memberReferences.Add(new KeyValuePair + (member, new BlobWriter(_il.CodeBuilder.ReserveBytes(sizeof(int))))); + } + + public override void Emit(OpCode opcode, Type cls) + { + ArgumentNullException.ThrowIfNull(cls); + + EmitOpcode(opcode); + ModuleBuilder module = (ModuleBuilder)_methodBuilder.Module; + _il.Token(module.GetTypeMetadataToken(cls)); + } + + public override void EmitCall(OpCode opcode, MethodInfo methodInfo, Type[]? optionalParameterTypes) + { + ArgumentNullException.ThrowIfNull(methodInfo); + + if (!(opcode.Equals(OpCodes.Call) || opcode.Equals(OpCodes.Callvirt) || opcode.Equals(OpCodes.Newobj))) + throw new ArgumentException(SR.Argument_NotMethodCallOpcode, nameof(opcode)); + + int stackchange = 0; + + // Push the return value if there is one. + if (methodInfo.ReturnType != typeof(void)) + { + stackchange++; + } + + // Pop the parameters. + if (methodInfo is MethodBuilderImpl builder) + { + stackchange -= builder.ParameterCount; + } + else + { + stackchange -= methodInfo.GetParameters().Length; + } + + // Pop the this parameter if the method is non-static and the + // instruction is not newobj. + if (!methodInfo.IsStatic && !opcode.Equals(OpCodes.Newobj)) + { + stackchange--; + } + + // Pop the optional parameters off the stack. + if (optionalParameterTypes != null) + { + stackchange -= optionalParameterTypes.Length; + } + + _il.OpCode((ILOpCode)opcode.Value); + UpdateStackSize(opcode, stackchange); + _memberReferences.Add(new KeyValuePair + (methodInfo, new BlobWriter(_il.CodeBuilder.ReserveBytes(sizeof(int))))); + } + public override void EmitCalli(OpCode opcode, CallingConventions callingConvention, Type? returnType, Type[]? parameterTypes, Type[]? optionalParameterTypes) => throw new NotImplementedException(); public override void EmitCalli(OpCode opcode, CallingConvention unmanagedCallConv, Type? returnType, Type[]? parameterTypes) => throw new NotImplementedException(); public override void EndExceptionBlock() => throw new NotImplementedException(); diff --git a/src/libraries/System.Reflection.Emit/src/System/Reflection/Emit/MethodBuilderImpl.cs b/src/libraries/System.Reflection.Emit/src/System/Reflection/Emit/MethodBuilderImpl.cs index 0b0fa27f091e7..afb6518fe261c 100644 --- a/src/libraries/System.Reflection.Emit/src/System/Reflection/Emit/MethodBuilderImpl.cs +++ b/src/libraries/System.Reflection.Emit/src/System/Reflection/Emit/MethodBuilderImpl.cs @@ -27,6 +27,7 @@ internal sealed class MethodBuilderImpl : MethodBuilder internal DllImportData? _dllImportData; internal List? _customAttributes; internal ParameterBuilderImpl[]? _parameters; + internal MethodDefinitionHandle _handle; internal MethodBuilderImpl(string name, MethodAttributes attributes, CallingConventions callingConventions, Type? returnType, Type[]? parameterTypes, ModuleBuilderImpl module, TypeBuilderImpl declaringType) @@ -52,6 +53,8 @@ internal sealed class MethodBuilderImpl : MethodBuilder _initLocals = true; } + internal int ParameterCount => _parameterTypes == null? 0 : _parameterTypes.Length; + internal ILGeneratorImpl? ILGeneratorImpl => _ilGenerator; internal BlobBuilder GetMethodSignatureBlob() => MetadataSignatureHelper.MethodSignatureEncoder(_module, @@ -201,7 +204,7 @@ protected override void SetImplementationFlagsCore(MethodImplAttributes attribut public override bool IsSecurityCritical => true; public override bool IsSecuritySafeCritical => false; public override bool IsSecurityTransparent => false; - public override int MetadataToken { get => throw new NotImplementedException(); } + public override int MetadataToken => _handle == default ? 0 : MetadataTokens.GetToken(_handle); public override RuntimeMethodHandle MethodHandle => throw new NotSupportedException(SR.NotSupported_DynamicModule); public override Type? ReflectedType { get => throw new NotImplementedException(); } public override ParameterInfo ReturnParameter { get => throw new NotImplementedException(); } @@ -224,8 +227,7 @@ public override int GetHashCode() public override MethodImplAttributes GetMethodImplementationFlags() => _methodImplFlags; - public override ParameterInfo[] GetParameters() - => throw new NotImplementedException(); + public override ParameterInfo[] GetParameters() => throw new NotImplementedException(); public override object Invoke(object? obj, BindingFlags invokeAttr, Binder? binder, object?[]? parameters, CultureInfo? culture) => throw new NotSupportedException(SR.NotSupported_DynamicModule); 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 c30398046ab60..ff48a0db42a23 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 @@ -17,6 +17,7 @@ internal sealed class ModuleBuilderImpl : ModuleBuilder private readonly MetadataBuilder _metadataBuilder; private readonly Dictionary _assemblyReferences = new(); private readonly Dictionary _typeReferences = new(); + private readonly Dictionary _memberReferences = new(); private readonly List _typeDefinitions = new(); private readonly Dictionary _ctorReferences = new(); private Dictionary? _moduleReferences; @@ -161,8 +162,8 @@ internal void AppendMetadata(MethodBodyStreamEncoder methodBodyEncoder) } WriteCustomAttributes(typeBuilder._customAttributes, typeHandle); - WriteMethods(typeBuilder, genericParams, methodBodyEncoder); WriteFields(typeBuilder); + WriteMethods(typeBuilder._methodDefinitions, genericParams, methodBodyEncoder); } // Now write all generic parameters in order @@ -180,20 +181,32 @@ internal void AppendMetadata(MethodBodyStreamEncoder methodBodyEncoder) } } - private void WriteMethods(TypeBuilderImpl typeBuilder, List genericParams, MethodBodyStreamEncoder methodBodyEncoder) + // All method handles need to pre populated so that they can be referenced from other method's IL + private void PrePopulateMethodDefinitionHandles(List methods) { - foreach (MethodBuilderImpl method in typeBuilder._methodDefinitions) + foreach (MethodBuilderImpl method in methods) + { + method._handle = MetadataTokens.MethodDefinitionHandle(_nextMethodDefRowId++); + } + } + + private void WriteMethods(List methods, List genericParams, MethodBodyStreamEncoder methodBodyEncoder) + { + PrePopulateMethodDefinitionHandles(methods); + + foreach (MethodBuilderImpl method in methods) { int offset = -1; ILGeneratorImpl? il = method.ILGeneratorImpl; if (il != null) { + FillMemberReferences(il); offset = AddMethodBody(method, il, methodBodyEncoder); } - MethodDefinitionHandle methodHandle = AddMethodDefinition(method, method.GetMethodSignatureBlob(), offset, _nextParameterRowId); - WriteCustomAttributes(method._customAttributes, methodHandle); - _nextMethodDefRowId++; + MethodDefinitionHandle handle = AddMethodDefinition(method, method.GetMethodSignatureBlob(), offset, _nextParameterRowId); + Debug.Assert(method._handle == handle); + WriteCustomAttributes(method._customAttributes, handle); if (method.IsGenericMethodDefinition) { @@ -201,7 +214,7 @@ private void WriteMethods(TypeBuilderImpl typeBuilder, List pair in il.GetMemberReferences()) + { + pair.Value.WriteInt32(MetadataTokens.GetToken(GetMemberHandle(pair.Key))); + } + } + private static int AddMethodBody(MethodBuilderImpl method, ILGeneratorImpl il, MethodBodyStreamEncoder methodBodyEncoder) => methodBodyEncoder.AddMethodBody( instructionEncoder: il.Instructions, @@ -249,23 +270,23 @@ private void WriteFields(TypeBuilderImpl typeBuilder) { foreach (FieldBuilderImpl field in typeBuilder._fieldDefinitions) { - FieldDefinitionHandle fieldHandle = AddFieldDefinition(field, MetadataSignatureHelper.FieldSignatureEncoder(field.FieldType, this)); - WriteCustomAttributes(field._customAttributes, fieldHandle); + field._handle = AddFieldDefinition(field, MetadataSignatureHelper.FieldSignatureEncoder(field.FieldType, this)); + WriteCustomAttributes(field._customAttributes, field._handle); _nextFieldDefRowId++; if (field._offset > 0 && (typeBuilder.Attributes & TypeAttributes.ExplicitLayout) != 0) { - AddFieldLayout(fieldHandle, field._offset); + AddFieldLayout(field._handle, field._offset); } if (field._marshallingData != null) { - AddMarshalling(fieldHandle, field._marshallingData.SerializeMarshallingData()); + AddMarshalling(field._handle, field._marshallingData.SerializeMarshallingData()); } if (field._defaultValue != DBNull.Value) { - AddDefaultValue(fieldHandle, field._defaultValue); + AddDefaultValue(field._handle, field._defaultValue); } } } @@ -318,6 +339,50 @@ private TypeReferenceHandle GetTypeReference(Type type) return typeHandle; } + private MemberReferenceHandle GetMemberReference(MemberInfo member) + { + if (!_memberReferences.TryGetValue(member, out var memberHandle)) + { + memberHandle = AddMemberReference(member.Name, GetTypeReference(member.DeclaringType!), GetMemberSignature(member)); + _memberReferences.Add(member, memberHandle); + } + + return memberHandle; + } + + private BlobBuilder GetMemberSignature(MemberInfo member) + { + if (member is MethodInfo method) + { + return MetadataSignatureHelper.MethodSignatureEncoder(this, ParameterTypes(method.GetParameters()), method.ReturnType, + MethodBuilderImpl.GetSignatureConvention(method.CallingConvention), method.GetGenericArguments().Length, !method.IsStatic); + } + + if (member is FieldInfo field) + { + return MetadataSignatureHelper.FieldSignatureEncoder(field.FieldType, this); + } + + throw new NotSupportedException(); + } + + private static Type[] ParameterTypes(ParameterInfo[] parameterInfos) + { + if (parameterInfos.Length == 0) + { + return Type.EmptyTypes; + } + + Type[] parameterTypes = new Type[parameterInfos.Length]; + + for (int i=0; i + _metadataBuilder.AddMemberReference( + parent: parent, + name: _metadataBuilder.GetOrAddString(memberName), + signature: _metadataBuilder.GetOrAddBlob(signature)); + private MemberReferenceHandle AddConstructorReference(TypeReferenceHandle parent, ConstructorInfo method) { var blob = MetadataSignatureHelper.ConstructorSignatureEncoder(method.GetParameters(), this); @@ -435,6 +506,21 @@ internal EntityHandle GetTypeHandle(Type type) return GetTypeReference(type); } + internal EntityHandle GetMemberHandle(MemberInfo member) + { + if (member is MethodBuilderImpl mb && Equals(mb.Module)) + { + return mb._handle; + } + + if (member is FieldBuilderImpl fb && Equals(fb.Module)) + { + return fb._handle; + } + + return GetMemberReference(member); + } + internal TypeBuilder DefineNestedType(string name, TypeAttributes attr, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] Type? parent, Type[]? interfaces, PackingSize packingSize, int typesize, TypeBuilderImpl? enclosingType) { @@ -448,13 +534,33 @@ internal EntityHandle GetTypeHandle(Type type) public override string Name => ""; public override string ScopeName => _name; public override bool IsDefined(Type attributeType, bool inherit) => throw new NotImplementedException(); - public override int GetFieldMetadataToken(FieldInfo field) => throw new NotImplementedException(); + + public override int GetFieldMetadataToken(FieldInfo field) + { + if (field is FieldBuilderImpl fb && fb._handle != default) + { + return MetadataTokens.GetToken(fb._handle); + } + + return 0; + } + public override int GetMethodMetadataToken(ConstructorInfo constructor) => throw new NotImplementedException(); - public override int GetMethodMetadataToken(MethodInfo method) => throw new NotImplementedException(); + + public override int GetMethodMetadataToken(MethodInfo method) + { + if (method is MethodBuilderImpl mb && mb._handle != default) + { + return MetadataTokens.GetToken(mb._handle); + } + + return 0; + } public override int GetStringMetadataToken(string stringConstant) => MetadataTokens.GetToken(_metadataBuilder.GetOrAddUserString(stringConstant)); - public override int GetTypeMetadataToken(Type type) => throw new NotImplementedException(); + public override int GetTypeMetadataToken(Type type) => MetadataTokens.GetToken(GetTypeHandle(type)); + protected override void CreateGlobalFunctionsCore() => throw new NotImplementedException(); protected override EnumBuilder DefineEnumCore(string name, TypeAttributes visibility, Type underlyingType) diff --git a/src/libraries/System.Reflection.Emit/tests/PersistableAssemblyBuilder/AssemblySaveILGeneratorTests.cs b/src/libraries/System.Reflection.Emit/tests/PersistableAssemblyBuilder/AssemblySaveILGeneratorTests.cs index 31af05ed611e3..6c8213c472997 100644 --- a/src/libraries/System.Reflection.Emit/tests/PersistableAssemblyBuilder/AssemblySaveILGeneratorTests.cs +++ b/src/libraries/System.Reflection.Emit/tests/PersistableAssemblyBuilder/AssemblySaveILGeneratorTests.cs @@ -307,7 +307,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 +361,113 @@ public void Label_SwitchCase() Assert.Equal(69, bodyBytes.Length); } } + + [Fact] + public void ReferencesFieldInIL() + { + using (TempFile file = TempFile.Create()) + { + AssemblyBuilder ab = AssemblySaveTools.PopulateAssemblyBuilderTypeBuilderAndSaveMethod(out TypeBuilder tb, out MethodInfo saveMethod); + MethodBuilder methodBuilder = tb.DefineMethod("Method1", MethodAttributes.Public, typeof(int), new[] { typeof(int) }); + FieldBuilder fbNumber = tb.DefineField("_number", typeof(int), FieldAttributes.Private); + Assert.Equal(0, fbNumber.MetadataToken); + + ILGenerator il = methodBuilder.GetILGenerator(); + il.Emit(OpCodes.Ldarg_0); + il.Emit(OpCodes.Ldfld, fbNumber); + il.Emit(OpCodes.Ldarg_1); + il.Emit(OpCodes.Mul); + il.Emit(OpCodes.Ret); + saveMethod.Invoke(ab, new object[] { file.Path }); + + Assembly assemblyFromDisk = AssemblySaveTools.LoadAssemblyFromPath(file.Path); + Type typeFromDisk = assemblyFromDisk.Modules.First().GetType("MyType"); + byte[]? bodyBytes = typeFromDisk.GetMethod("Method1").GetMethodBody().GetILAsByteArray(); + Assert.Equal(9, bodyBytes.Length); + Assert.NotEqual(0, fbNumber.MetadataToken); + Assert.Equal(OpCodes.Ldarg_0.Value, bodyBytes[0]); + Assert.Equal(OpCodes.Ldfld.Value, bodyBytes[1]); + Assert.Equal(fbNumber.MetadataToken, BitConverter.ToInt32(bodyBytes.AsSpan().Slice(2, 4))); + Assert.Equal(OpCodes.Ldarg_1.Value, bodyBytes[6]); + Assert.Equal(OpCodes.Mul.Value, bodyBytes[7]); + Assert.Equal(OpCodes.Ret.Value, bodyBytes[8]); + } + } + + [Fact] + public void ReferencesFieldAndMethodsInIL() + { + using (TempFile file = TempFile.Create()) + { + AssemblyBuilder ab = AssemblySaveTools.PopulateAssemblyBuilderTypeBuilderAndSaveMethod(out TypeBuilder tb, out MethodInfo saveMethod); + MethodBuilder methodMain = tb.DefineMethod("Main", MethodAttributes.Public, typeof(void), new[] { typeof(int) }); + FieldBuilder field = tb.DefineField("_field", typeof(int), FieldAttributes.Private); + MethodInfo writeLineString = typeof(Console).GetMethod("WriteLine", new[] { typeof(string) }); + MethodInfo writeLineObj = typeof(Console).GetMethod("WriteLine", new[] { typeof(string), typeof(object), typeof(object), typeof(object) }); + MethodBuilder methodMultiply = tb.DefineMethod("Multiply", MethodAttributes.Public, typeof(int), new[] { typeof(int) }); + /* + class MyType + { + private int _field; + int Multiply(int value) => _field * value; + void Main(int a) + { + Console.WriteLine("Displaying the expression:"); + Console.WriteLine("{0} * {1} = {2}", _field, a, Multiply(a)); + } + } + */ + ILGenerator il = methodMultiply.GetILGenerator(); + il.Emit(OpCodes.Ldarg_0); + il.Emit(OpCodes.Ldfld, field); + il.Emit(OpCodes.Ldarg_1); + il.Emit(OpCodes.Mul); + il.Emit(OpCodes.Ret); + + ILGenerator ilMain = methodMain.GetILGenerator(); + ilMain.Emit(OpCodes.Ldstr, "Displaying the expression:"); + ilMain.Emit(OpCodes.Call, writeLineString); + ilMain.Emit(OpCodes.Ldstr, "{0} * {1} = {2}"); + ilMain.Emit(OpCodes.Ldarg_0); + ilMain.Emit(OpCodes.Ldfld, field); + ilMain.Emit(OpCodes.Box, typeof(int)); + ilMain.Emit(OpCodes.Ldarg_1); + ilMain.Emit(OpCodes.Box, typeof(int)); + ilMain.Emit(OpCodes.Ldarg_0); + ilMain.Emit(OpCodes.Ldarg_1); + ilMain.Emit(OpCodes.Call, methodMultiply); + ilMain.Emit(OpCodes.Box, typeof(int)); + ilMain.Emit(OpCodes.Call, writeLineObj); + ilMain.Emit(OpCodes.Ret); + + saveMethod.Invoke(ab, new object[] { file.Path }); + Console.WriteLine(file.Path); + + Assembly assemblyFromDisk = AssemblySaveTools.LoadAssemblyFromPath(file.Path); + Type typeFromDisk = assemblyFromDisk.Modules.First().GetType("MyType"); + byte[]? bodyBytes = typeFromDisk.GetMethod("Main").GetMethodBody().GetILAsByteArray(); + Assert.Equal(OpCodes.Ldstr.Value, bodyBytes[0]); + Assert.Equal(OpCodes.Call.Value, bodyBytes[5]); + // Bytes 6, 7, 8, 9 are token for writeLineString, but it is not same as the value before save + Assert.Equal(OpCodes.Ldstr.Value, bodyBytes[10]); + Assert.Equal(OpCodes.Ldarg_0.Value, bodyBytes[15]); + Assert.Equal(OpCodes.Ldfld.Value, bodyBytes[16]); + Assert.Equal(field.MetadataToken, BitConverter.ToInt32(bodyBytes.AsSpan().Slice(17, 4))); + Assert.Equal(OpCodes.Box.Value, bodyBytes[21]); + int intTypeToken = BitConverter.ToInt32(bodyBytes.AsSpan().Slice(22, 4)); + Assert.Equal(OpCodes.Ldarg_1.Value, bodyBytes[26]); + Assert.Equal(OpCodes.Box.Value, bodyBytes[27]); + Assert.Equal(intTypeToken, BitConverter.ToInt32(bodyBytes.AsSpan().Slice(28, 4))); + Assert.Equal(OpCodes.Ldarg_0.Value, bodyBytes[32]); + Assert.Equal(OpCodes.Ldarg_1.Value, bodyBytes[33]); + Assert.Equal(OpCodes.Call.Value, bodyBytes[34]); + Assert.Equal(methodMultiply.MetadataToken, BitConverter.ToInt32(bodyBytes.AsSpan().Slice(35, 4))); + Assert.Equal(OpCodes.Box.Value, bodyBytes[39]); + Assert.Equal(intTypeToken, BitConverter.ToInt32(bodyBytes.AsSpan().Slice(40, 4))); + Assert.Equal(OpCodes.Call.Value, bodyBytes[44]); + // Bytes 24, 46, 47, 48 are token for writeLineObj, but it is not same as the value before save + Assert.Equal(OpCodes.Ret.Value, bodyBytes[49]); + } + } } } diff --git a/src/libraries/System.Reflection.Emit/tests/PersistableAssemblyBuilder/AssemblySaveTools.cs b/src/libraries/System.Reflection.Emit/tests/PersistableAssemblyBuilder/AssemblySaveTools.cs index e756c8c4cf7cd..f63eb084abcf0 100644 --- a/src/libraries/System.Reflection.Emit/tests/PersistableAssemblyBuilder/AssemblySaveTools.cs +++ b/src/libraries/System.Reflection.Emit/tests/PersistableAssemblyBuilder/AssemblySaveTools.cs @@ -4,7 +4,6 @@ using System.Collections.Generic; using System.IO; using System.Linq; -using System.Runtime.InteropServices; using Xunit; namespace System.Reflection.Emit.Tests From d8d12eab152049e68fb9a9d3b96d0580dccd9166 Mon Sep 17 00:00:00 2001 From: Buyaa Namnan Date: Fri, 27 Oct 2023 08:45:57 -0700 Subject: [PATCH 2/4] Add more tests, add double iterations for prepopulating tokens --- .../Reflection/Emit/ModuleBuilderImpl.cs | 41 ++++++--- .../System/Reflection/Emit/TypeBuilderImpl.cs | 2 + .../MethodBuilderGetILGenerator.cs | 2 +- .../AssemblySaveILGeneratorTests.cs | 90 ++++++++++++++++++- 4 files changed, 121 insertions(+), 14 deletions(-) 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 ff48a0db42a23..ce696f4fd8536 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 @@ -120,6 +120,8 @@ internal void AppendMetadata(MethodBodyStreamEncoder methodBodyEncoder) WriteCustomAttributes(_customAttributes, moduleHandle); + PrePopulateTypeMembersTokens(); + // All generic parameters for all types and methods should be written in specific order List genericParams = new(); // Add each type definition to metadata table. @@ -131,7 +133,7 @@ internal void AppendMetadata(MethodBodyStreamEncoder methodBodyEncoder) parent = GetTypeHandle(typeBuilder.BaseType); } - TypeDefinitionHandle typeHandle = AddTypeDefinition(typeBuilder, parent, _nextMethodDefRowId, _nextFieldDefRowId); + TypeDefinitionHandle typeHandle = AddTypeDefinition(typeBuilder, parent, typeBuilder._firsMethodToken, typeBuilder._firstFieldToken); Debug.Assert(typeBuilder._handle.Equals(typeHandle)); if (typeBuilder.IsGenericType) @@ -152,7 +154,7 @@ internal void AppendMetadata(MethodBodyStreamEncoder methodBodyEncoder) foreach (Type iface in typeBuilder._interfaces) { _metadataBuilder.AddInterfaceImplementation(typeHandle, GetTypeHandle(iface)); - // TODO: need to add interface mapping between interface method and implemented method + // TODO: need to add interface mapping between interface field and implemented field } } @@ -181,7 +183,26 @@ internal void AppendMetadata(MethodBodyStreamEncoder methodBodyEncoder) } } - // All method handles need to pre populated so that they can be referenced from other method's IL + // Need to pre populate all type members tokens so that they can be referenced from other type's metadata + private void PrePopulateTypeMembersTokens() + { + foreach (TypeBuilderImpl typeBuilder in _typeDefinitions) + { + typeBuilder._firsMethodToken = _nextMethodDefRowId; + typeBuilder._firstFieldToken = _nextFieldDefRowId; + PrePopulateMethodDefinitionHandles(typeBuilder._methodDefinitions); + PrePopulateFieldDefinitionHandles(typeBuilder._fieldDefinitions); + } + } + + private void PrePopulateFieldDefinitionHandles(List fieldDefinitions) + { + foreach (FieldBuilderImpl field in fieldDefinitions) + { + field._handle = MetadataTokens.FieldDefinitionHandle(_nextFieldDefRowId++); + } + } + private void PrePopulateMethodDefinitionHandles(List methods) { foreach (MethodBuilderImpl method in methods) @@ -192,8 +213,6 @@ private void PrePopulateMethodDefinitionHandles(List methods) private void WriteMethods(List methods, List genericParams, MethodBodyStreamEncoder methodBodyEncoder) { - PrePopulateMethodDefinitionHandles(methods); - foreach (MethodBuilderImpl method in methods) { int offset = -1; @@ -270,23 +289,23 @@ private void WriteFields(TypeBuilderImpl typeBuilder) { foreach (FieldBuilderImpl field in typeBuilder._fieldDefinitions) { - field._handle = AddFieldDefinition(field, MetadataSignatureHelper.FieldSignatureEncoder(field.FieldType, this)); - WriteCustomAttributes(field._customAttributes, field._handle); - _nextFieldDefRowId++; + FieldDefinitionHandle handle = AddFieldDefinition(field, MetadataSignatureHelper.FieldSignatureEncoder(field.FieldType, this)); + Debug.Assert(field._handle == handle); + WriteCustomAttributes(field._customAttributes, handle); if (field._offset > 0 && (typeBuilder.Attributes & TypeAttributes.ExplicitLayout) != 0) { - AddFieldLayout(field._handle, field._offset); + AddFieldLayout(handle, field._offset); } if (field._marshallingData != null) { - AddMarshalling(field._handle, field._marshallingData.SerializeMarshallingData()); + AddMarshalling(handle, field._marshallingData.SerializeMarshallingData()); } if (field._defaultValue != DBNull.Value) { - AddDefaultValue(field._handle, field._defaultValue); + AddDefaultValue(handle, field._defaultValue); } } } diff --git a/src/libraries/System.Reflection.Emit/src/System/Reflection/Emit/TypeBuilderImpl.cs b/src/libraries/System.Reflection.Emit/src/System/Reflection/Emit/TypeBuilderImpl.cs index ba77906508ebb..3a07d13105954 100644 --- a/src/libraries/System.Reflection.Emit/src/System/Reflection/Emit/TypeBuilderImpl.cs +++ b/src/libraries/System.Reflection.Emit/src/System/Reflection/Emit/TypeBuilderImpl.cs @@ -26,6 +26,8 @@ internal sealed class TypeBuilderImpl : TypeBuilder private Type? _enumUnderlyingType; internal readonly TypeDefinitionHandle _handle; + internal int _firstFieldToken; + internal int _firsMethodToken; internal readonly List _methodDefinitions = new(); internal readonly List _fieldDefinitions = new(); internal List? _interfaces; diff --git a/src/libraries/System.Reflection.Emit/tests/MethodBuilder/MethodBuilderGetILGenerator.cs b/src/libraries/System.Reflection.Emit/tests/MethodBuilder/MethodBuilderGetILGenerator.cs index 2ce5cab8152e2..e2ad01e1c4603 100644 --- a/src/libraries/System.Reflection.Emit/tests/MethodBuilder/MethodBuilderGetILGenerator.cs +++ b/src/libraries/System.Reflection.Emit/tests/MethodBuilder/MethodBuilderGetILGenerator.cs @@ -34,7 +34,7 @@ public void GetILGenerator_Int(int size) } [Theory] - [ActiveIssue("https://github.com/dotnet/runtime/issues/2389", TestRuntimes.Mono)] + [ActiveIssue("https://github.com/dotnet/runtime/issues/2389", TestRuntimes.Mono)] [InlineData(TypeAttributes.Public, MethodAttributes.Public | MethodAttributes.PinvokeImpl)] [InlineData(TypeAttributes.Abstract, MethodAttributes.PinvokeImpl)] [InlineData(TypeAttributes.Abstract, MethodAttributes.Abstract | MethodAttributes.PinvokeImpl)] diff --git a/src/libraries/System.Reflection.Emit/tests/PersistableAssemblyBuilder/AssemblySaveILGeneratorTests.cs b/src/libraries/System.Reflection.Emit/tests/PersistableAssemblyBuilder/AssemblySaveILGeneratorTests.cs index 6c8213c472997..7663372ccdb4c 100644 --- a/src/libraries/System.Reflection.Emit/tests/PersistableAssemblyBuilder/AssemblySaveILGeneratorTests.cs +++ b/src/libraries/System.Reflection.Emit/tests/PersistableAssemblyBuilder/AssemblySaveILGeneratorTests.cs @@ -437,11 +437,10 @@ void Main(int a) ilMain.Emit(OpCodes.Ldarg_1); ilMain.Emit(OpCodes.Call, methodMultiply); ilMain.Emit(OpCodes.Box, typeof(int)); - ilMain.Emit(OpCodes.Call, writeLineObj); + ilMain.Emit(OpCodes.Call, writeLineObj); ilMain.Emit(OpCodes.Ret); saveMethod.Invoke(ab, new object[] { file.Path }); - Console.WriteLine(file.Path); Assembly assemblyFromDisk = AssemblySaveTools.LoadAssemblyFromPath(file.Path); Type typeFromDisk = assemblyFromDisk.Modules.First().GetType("MyType"); @@ -469,5 +468,92 @@ void Main(int a) Assert.Equal(OpCodes.Ret.Value, bodyBytes[49]); } } + + [Fact] + public void EmitWriteLineMacroTest() + { + using (TempFile file = TempFile.Create()) + { + AssemblyBuilder ab = AssemblySaveTools.PopulateAssemblyBuilderTypeBuilderAndSaveMethod(out TypeBuilder type1, out MethodInfo saveMethod); + MethodBuilder method = type1.DefineMethod("meth", MethodAttributes.Public, typeof(int), Type.EmptyTypes); + FieldBuilder field = type1.DefineField("field", typeof(int), FieldAttributes.Public | FieldAttributes.Static); + ILGenerator ilGenerator = method.GetILGenerator(); + ilGenerator.Emit(OpCodes.Ldc_I4_1); + ilGenerator.Emit(OpCodes.Stsfld, field); + ilGenerator.EmitWriteLine(field); + ilGenerator.EmitWriteLine("Emit WriteLine"); + // TODO: ilGenerator.EmitWriteLine(local); later + ilGenerator.Emit(OpCodes.Ldsfld, field); + ilGenerator.Emit(OpCodes.Ret); + + saveMethod.Invoke(ab, new object[] { file.Path }); + + Assembly assemblyFromDisk = AssemblySaveTools.LoadAssemblyFromPath(file.Path); + Type typeFromDisk = assemblyFromDisk.Modules.First().GetType("MyType"); + byte[]? bodyBytes = typeFromDisk.GetMethod("meth").GetMethodBody().GetILAsByteArray(); + Assert.Equal(OpCodes.Ldc_I4_1.Value, bodyBytes[0]); + Assert.Equal(OpCodes.Stsfld.Value, bodyBytes[1]); + Assert.Equal(field.MetadataToken, BitConverter.ToInt32(bodyBytes.AsSpan().Slice(2, 4))); + Assert.Equal(OpCodes.Call.Value, bodyBytes[6]); + Assert.Equal(OpCodes.Ldsfld.Value, bodyBytes[11]); + Assert.Equal(field.MetadataToken, BitConverter.ToInt32(bodyBytes.AsSpan().Slice(12, 4))); + Assert.Equal(OpCodes.Callvirt.Value, bodyBytes[16]); + Assert.Equal(OpCodes.Ldstr.Value, bodyBytes[21]); + Assert.Equal(OpCodes.Call.Value, bodyBytes[26]); + Assert.Equal(OpCodes.Ldsfld.Value, bodyBytes[31]); + Assert.Equal(field.MetadataToken, BitConverter.ToInt32(bodyBytes.AsSpan().Slice(32, 4))); + Assert.Equal(OpCodes.Ret.Value, bodyBytes[36]); + } + } + + [Fact] + public void ReferenceStaticFieldAndMethodsInIL() + { + using (TempFile file = TempFile.Create()) + { + AssemblyBuilder ab = AssemblySaveTools.PopulateAssemblyBuilderTypeBuilderAndSaveMethod(out TypeBuilder tb, out MethodInfo saveMethod); + MethodBuilder methodMain = tb.DefineMethod("Main", MethodAttributes.Public, typeof(int), new[] { typeof(int) }); + TypeBuilder anotherType = ab.GetDynamicModule("MyModule").DefineType("AnotherType", TypeAttributes.Public); + FieldBuilder field = anotherType.DefineField("StaticField", typeof(int), FieldAttributes.Public | FieldAttributes.Static); + MethodBuilder staticMethod = anotherType.DefineMethod("StaticMethod", MethodAttributes.Public | MethodAttributes.Static, typeof(void), Type.EmptyTypes); + + /* class MyType + { + int Main(int a) + { + AnotherType.StaticField = a; + AnotherType.StaticMethod(); + return AnotherType.StaticField; + } + } + public class AnotherType + { + public static int StaticField; + void static StaticMethod() { } + }*/ + ILGenerator ilMain = methodMain.GetILGenerator(); + ilMain.Emit(OpCodes.Call, staticMethod); + ilMain.Emit(OpCodes.Ldarg_1); + ilMain.Emit(OpCodes.Stsfld, field); + ilMain.Emit(OpCodes.Ldsfld, field); + ilMain.Emit(OpCodes.Ret); + ILGenerator il = staticMethod.GetILGenerator(); + il.Emit(OpCodes.Ret); + + saveMethod.Invoke(ab, new object[] { file.Path }); + + Assembly assemblyFromDisk = AssemblySaveTools.LoadAssemblyFromPath(file.Path); + Type typeFromDisk = assemblyFromDisk.Modules.First().GetType("MyType"); + byte[]? bodyBytes = typeFromDisk.GetMethod("Main").GetMethodBody().GetILAsByteArray(); + Assert.Equal(OpCodes.Call.Value, bodyBytes[0]); + Assert.Equal(staticMethod.MetadataToken, BitConverter.ToInt32(bodyBytes.AsSpan().Slice(1, 4))); + Assert.Equal(OpCodes.Ldarg_1.Value, bodyBytes[5]); + Assert.Equal(OpCodes.Stsfld.Value, bodyBytes[6]); + Assert.Equal(field.MetadataToken, BitConverter.ToInt32(bodyBytes.AsSpan().Slice(7, 4))); + Assert.Equal(OpCodes.Ldsfld.Value, bodyBytes[11]); + Assert.Equal(field.MetadataToken, BitConverter.ToInt32(bodyBytes.AsSpan().Slice(12, 4))); + Assert.Equal(OpCodes.Ret.Value, bodyBytes[16]); + } + } } } From 67def4aa97e0d7d40fcce1f922fa813e64c4d419 Mon Sep 17 00:00:00 2001 From: Buyaa Namnan Date: Fri, 27 Oct 2023 14:42:38 -0700 Subject: [PATCH 3/4] Emit constructor reference, add more tests --- .../src/Resources/Strings.resx | 3 + .../System/Reflection/Emit/ILGeneratorImpl.cs | 45 ++++++- .../Reflection/Emit/ModuleBuilderImpl.cs | 7 +- .../MethodBuilderGetILGenerator.cs | 2 +- .../AssemblySaveILGeneratorTests.cs | 119 +++++++++++++++--- 5 files changed, 155 insertions(+), 21 deletions(-) diff --git a/src/libraries/System.Reflection.Emit/src/Resources/Strings.resx b/src/libraries/System.Reflection.Emit/src/Resources/Strings.resx index 96643e5d40b0e..fb87824e399c5 100644 --- a/src/libraries/System.Reflection.Emit/src/Resources/Strings.resx +++ b/src/libraries/System.Reflection.Emit/src/Resources/Strings.resx @@ -198,4 +198,7 @@ The specified opcode cannot be passed to EmitCall. + + Calling convention must be VarArgs. + \ No newline at end of file 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 4431e5c91889a..66efdc9aedf4d 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 @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Collections.Generic; +using System.Diagnostics; using System.Reflection.Metadata; using System.Reflection.Metadata.Ecma335; using System.Runtime.InteropServices; @@ -219,7 +220,32 @@ public override void Emit(OpCode opcode, string str) _il.Token(tempVal); } - public override void Emit(OpCode opcode, ConstructorInfo con) => throw new NotImplementedException(); + public override void Emit(OpCode opcode, ConstructorInfo con) + { + ArgumentNullException.ThrowIfNull(con); + + if (!(opcode.Equals(OpCodes.Call) || opcode.Equals(OpCodes.Callvirt) || opcode.Equals(OpCodes.Newobj))) + { + throw new ArgumentException(SR.Argument_NotMethodCallOpcode, nameof(opcode)); + } + + int stackchange = 0; + // Push the return value if there is one. + stackchange++; + // Pop the parameters. + stackchange -= con.GetParameters().Length; + // Pop the this parameter if the constructor is non-static and the + // instruction is not newobj. + if (!con.IsStatic && !opcode.Equals(OpCodes.Newobj)) + { + stackchange--; + } + + UpdateStackSize(opcode, stackchange); + _il.OpCode((ILOpCode)opcode.Value); + _memberReferences.Add(new KeyValuePair + (con, new BlobWriter(_il.CodeBuilder.ReserveBytes(sizeof(int))))); + } public override void Emit(OpCode opcode, Label label) { @@ -236,6 +262,8 @@ public override void Emit(OpCode opcode, Label label) public override void Emit(OpCode opcode, Label[] labels) { + ArgumentNullException.ThrowIfNull(labels); + if (!opcode.Equals(OpCodes.Switch)) { throw new ArgumentException(SR.Argument_MustBeSwitchOpCode, nameof(opcode)); @@ -322,8 +350,18 @@ public override void EmitCall(OpCode opcode, MethodInfo methodInfo, Type[]? opti ArgumentNullException.ThrowIfNull(methodInfo); if (!(opcode.Equals(OpCodes.Call) || opcode.Equals(OpCodes.Callvirt) || opcode.Equals(OpCodes.Newobj))) + { throw new ArgumentException(SR.Argument_NotMethodCallOpcode, nameof(opcode)); + } + + _il.OpCode((ILOpCode)opcode.Value); + UpdateStackSize(opcode, GetStackChange(opcode, methodInfo, optionalParameterTypes)); + _memberReferences.Add(new KeyValuePair + (methodInfo, new BlobWriter(_il.CodeBuilder.ReserveBytes(sizeof(int))))); + } + private static int GetStackChange(OpCode opcode, MethodInfo methodInfo, Type[]? optionalParameterTypes) + { int stackchange = 0; // Push the return value if there is one. @@ -355,10 +393,7 @@ public override void EmitCall(OpCode opcode, MethodInfo methodInfo, Type[]? opti stackchange -= optionalParameterTypes.Length; } - _il.OpCode((ILOpCode)opcode.Value); - UpdateStackSize(opcode, stackchange); - _memberReferences.Add(new KeyValuePair - (methodInfo, new BlobWriter(_il.CodeBuilder.ReserveBytes(sizeof(int))))); + return stackchange; } public override void EmitCalli(OpCode opcode, CallingConventions callingConvention, Type? returnType, Type[]? parameterTypes, Type[]? optionalParameterTypes) => throw new NotImplementedException(); 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 633f4ce434cb4..855fdb5591233 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 @@ -154,7 +154,7 @@ internal void AppendMetadata(MethodBodyStreamEncoder methodBodyEncoder) foreach (Type iface in typeBuilder._interfaces) { _metadataBuilder.AddInterfaceImplementation(typeHandle, GetTypeHandle(iface)); - // TODO: need to add interface mapping between interface field and implemented field + // TODO: need to add interface mapping between interface method and implemented method } } @@ -383,6 +383,11 @@ private BlobBuilder GetMemberSignature(MemberInfo member) return MetadataSignatureHelper.FieldSignatureEncoder(field.FieldType, this); } + if (member is ConstructorInfo ctor) + { + return MetadataSignatureHelper.ConstructorSignatureEncoder(ctor.GetParameters(), this); + } + throw new NotSupportedException(); } diff --git a/src/libraries/System.Reflection.Emit/tests/MethodBuilder/MethodBuilderGetILGenerator.cs b/src/libraries/System.Reflection.Emit/tests/MethodBuilder/MethodBuilderGetILGenerator.cs index e2ad01e1c4603..2ce5cab8152e2 100644 --- a/src/libraries/System.Reflection.Emit/tests/MethodBuilder/MethodBuilderGetILGenerator.cs +++ b/src/libraries/System.Reflection.Emit/tests/MethodBuilder/MethodBuilderGetILGenerator.cs @@ -34,7 +34,7 @@ public void GetILGenerator_Int(int size) } [Theory] - [ActiveIssue("https://github.com/dotnet/runtime/issues/2389", TestRuntimes.Mono)] + [ActiveIssue("https://github.com/dotnet/runtime/issues/2389", TestRuntimes.Mono)] [InlineData(TypeAttributes.Public, MethodAttributes.Public | MethodAttributes.PinvokeImpl)] [InlineData(TypeAttributes.Abstract, MethodAttributes.PinvokeImpl)] [InlineData(TypeAttributes.Abstract, MethodAttributes.Abstract | MethodAttributes.PinvokeImpl)] diff --git a/src/libraries/System.Reflection.Emit/tests/PersistableAssemblyBuilder/AssemblySaveILGeneratorTests.cs b/src/libraries/System.Reflection.Emit/tests/PersistableAssemblyBuilder/AssemblySaveILGeneratorTests.cs index 2b9d34455144a..c24fb69307606 100644 --- a/src/libraries/System.Reflection.Emit/tests/PersistableAssemblyBuilder/AssemblySaveILGeneratorTests.cs +++ b/src/libraries/System.Reflection.Emit/tests/PersistableAssemblyBuilder/AssemblySaveILGeneratorTests.cs @@ -544,7 +544,7 @@ public void LocalBuilderExceptions() } [Fact] - public void ReferencesFieldInIL() + public void ReferenceFieldInIL() { using (TempFile file = TempFile.Create()) { @@ -576,7 +576,7 @@ public void ReferencesFieldInIL() } [Fact] - public void ReferencesFieldAndMethodsInIL() + public void ReferenceFieldAndMethodsInIL() { using (TempFile file = TempFile.Create()) { @@ -659,11 +659,14 @@ public void EmitWriteLineMacroTest() MethodBuilder method = type1.DefineMethod("meth", MethodAttributes.Public, typeof(int), Type.EmptyTypes); FieldBuilder field = type1.DefineField("field", typeof(int), FieldAttributes.Public | FieldAttributes.Static); ILGenerator ilGenerator = method.GetILGenerator(); + LocalBuilder local = ilGenerator.DeclareLocal(typeof(int)); ilGenerator.Emit(OpCodes.Ldc_I4_1); + ilGenerator.Emit(OpCodes.Stloc_0); + ilGenerator.Emit(OpCodes.Ldloc_0); ilGenerator.Emit(OpCodes.Stsfld, field); ilGenerator.EmitWriteLine(field); ilGenerator.EmitWriteLine("Emit WriteLine"); - // TODO: ilGenerator.EmitWriteLine(local); later + ilGenerator.EmitWriteLine(local); ilGenerator.Emit(OpCodes.Ldsfld, field); ilGenerator.Emit(OpCodes.Ret); @@ -673,17 +676,22 @@ public void EmitWriteLineMacroTest() Type typeFromDisk = assemblyFromDisk.Modules.First().GetType("MyType"); byte[]? bodyBytes = typeFromDisk.GetMethod("meth").GetMethodBody().GetILAsByteArray(); Assert.Equal(OpCodes.Ldc_I4_1.Value, bodyBytes[0]); - Assert.Equal(OpCodes.Stsfld.Value, bodyBytes[1]); - Assert.Equal(field.MetadataToken, BitConverter.ToInt32(bodyBytes.AsSpan().Slice(2, 4))); - Assert.Equal(OpCodes.Call.Value, bodyBytes[6]); - Assert.Equal(OpCodes.Ldsfld.Value, bodyBytes[11]); - Assert.Equal(field.MetadataToken, BitConverter.ToInt32(bodyBytes.AsSpan().Slice(12, 4))); - Assert.Equal(OpCodes.Callvirt.Value, bodyBytes[16]); - Assert.Equal(OpCodes.Ldstr.Value, bodyBytes[21]); - Assert.Equal(OpCodes.Call.Value, bodyBytes[26]); - Assert.Equal(OpCodes.Ldsfld.Value, bodyBytes[31]); - Assert.Equal(field.MetadataToken, BitConverter.ToInt32(bodyBytes.AsSpan().Slice(32, 4))); - Assert.Equal(OpCodes.Ret.Value, bodyBytes[36]); + Assert.Equal(OpCodes.Stloc_0.Value, bodyBytes[1]); + Assert.Equal(OpCodes.Ldloc_0.Value, bodyBytes[2]); + Assert.Equal(OpCodes.Stsfld.Value, bodyBytes[3]); + Assert.Equal(field.MetadataToken, BitConverter.ToInt32(bodyBytes.AsSpan().Slice(4, 4))); + Assert.Equal(OpCodes.Call.Value, bodyBytes[8]); + Assert.Equal(OpCodes.Ldsfld.Value, bodyBytes[13]); + Assert.Equal(field.MetadataToken, BitConverter.ToInt32(bodyBytes.AsSpan().Slice(14, 4))); + Assert.Equal(OpCodes.Callvirt.Value, bodyBytes[18]); + Assert.Equal(OpCodes.Ldstr.Value, bodyBytes[23]); + Assert.Equal(OpCodes.Call.Value, bodyBytes[28]); + Assert.Equal(OpCodes.Call.Value, bodyBytes[33]); + Assert.Equal(OpCodes.Ldloc_0.Value, bodyBytes[38]); + Assert.Equal(OpCodes.Callvirt.Value, bodyBytes[39]); + Assert.Equal(OpCodes.Ldsfld.Value, bodyBytes[44]); + Assert.Equal(field.MetadataToken, BitConverter.ToInt32(bodyBytes.AsSpan().Slice(45, 4))); + Assert.Equal(OpCodes.Ret.Value, bodyBytes[49]); } } @@ -735,5 +743,88 @@ public class AnotherType Assert.Equal(OpCodes.Ret.Value, bodyBytes[16]); } } + + [Fact] + public void ReferenceConstructorInIL() + { + using (TempFile file = TempFile.Create()) + { + AssemblyBuilder ab = AssemblySaveTools.PopulateAssemblyBuilderTypeBuilderAndSaveMethod(out TypeBuilder tb, out MethodInfo saveMethod); + MethodBuilder methodBuilder = tb.DefineMethod("Method1", MethodAttributes.Public, typeof(Version), new[] { typeof(int), typeof(int) }); + ConstructorInfo ctor = typeof(Version).GetConstructor(new[] { typeof(int), typeof(int) }); + + ILGenerator il = methodBuilder.GetILGenerator(); + il.Emit(OpCodes.Ldarg_1); + il.Emit(OpCodes.Ldarg_2); + il.Emit(OpCodes.Newobj, ctor); + il.Emit(OpCodes.Ret); + + saveMethod.Invoke(ab, new object[] { file.Path }); + + Assembly assemblyFromDisk = AssemblySaveTools.LoadAssemblyFromPath(file.Path); + Type typeFromDisk = assemblyFromDisk.Modules.First().GetType("MyType"); + byte[]? bodyBytes = typeFromDisk.GetMethod("Method1").GetMethodBody().GetILAsByteArray(); + Assert.Equal(OpCodes.Ldarg_1.Value, bodyBytes[0]); + Assert.Equal(OpCodes.Ldarg_2.Value, bodyBytes[1]); + Assert.Equal(OpCodes.Newobj.Value, bodyBytes[2]); + Assert.Equal(OpCodes.Ret.Value, bodyBytes[7]); + } + } + + [Fact] + public void ReferenceAType() + { + using (TempFile file = TempFile.Create()) + { + AssemblyBuilder ab = AssemblySaveTools.PopulateAssemblyBuilderTypeBuilderAndSaveMethod(out TypeBuilder tb, out MethodInfo saveMethod); + MethodBuilder method = tb.DefineMethod("meth1", MethodAttributes.Public | MethodAttributes.Static, typeof(bool), new Type[0]); + ILGenerator ilGenerator = method.GetILGenerator(); + LocalBuilder lb0 = ilGenerator.DeclareLocal(typeof(ValueTuple)); + ilGenerator.Emit(OpCodes.Ldloca, lb0); + ilGenerator.Emit(OpCodes.Initobj, typeof(ValueTuple)); + ilGenerator.Emit(OpCodes.Ldc_I4, 1); + ilGenerator.Emit(OpCodes.Ret); + + saveMethod.Invoke(ab, new object[] { file.Path }); + + Assembly assemblyFromDisk = AssemblySaveTools.LoadAssemblyFromPath(file.Path); + Type typeFromDisk = assemblyFromDisk.Modules.First().GetType("MyType"); + byte[]? bodyBytes = typeFromDisk.GetMethod("meth1").GetMethodBody().GetILAsByteArray(); + Assert.Equal(OpCodes.Ldloca_S.Value, bodyBytes[0]); // short form of Ldloca + Assert.Equal(0, bodyBytes[1]); + Assert.Equal(0xFE, bodyBytes[2]); // Initobj = 0xfe15 + Assert.Equal(0x15, bodyBytes[3]); + Assert.Equal(OpCodes.Ldc_I4_1.Value, bodyBytes[8]); + Assert.Equal(OpCodes.Ret.Value, bodyBytes[9]); + } + } + + [Fact] + public void MemberReferenceExceptions() + { + AssemblyBuilder ab = AssemblySaveTools.PopulateAssemblyBuilderTypeBuilderAndSaveMethod(out TypeBuilder type, out MethodInfo saveMethod); + MethodBuilder method = type.DefineMethod("Method1", MethodAttributes.Public); + ILGenerator il = method.GetILGenerator(); + MethodInfo nullMethod = null; + ConstructorInfo nullConstructor = null; + FieldInfo nullField = null; + Label[] nullArray = null; + Type nullType = null; + + Assert.Throws(() => il.Emit(OpCodes.Call, nullMethod)); + Assert.Throws(() => il.Emit(OpCodes.Callvirt, nullConstructor)); + Assert.Throws(() => il.Emit(OpCodes.Ldfld, nullField)); + Assert.Throws(() => il.Emit(OpCodes.Switch, nullArray)); + Assert.Throws(() => il.Emit(OpCodes.Switch, nullType)); + Assert.Throws(() => il.EmitCall(OpCodes.Call, nullMethod, null)); + // only OpCodes.Switch expected + Assert.Throws(() => il.Emit(OpCodes.Call, new Label[0])); + // only OpCodes.Call or .OpCodes.Callvirt or OpCodes.Newob expected + Assert.Throws(() => il.Emit(OpCodes.Switch, typeof(object).GetConstructor(Type.EmptyTypes))); + // Undefined label + Assert.Throws(() => il.MarkLabel(new Label())); + // only OpCodes.Call or OpCodes.Callvirt or OpCodes.Newob expected + Assert.Throws(() => il.EmitCall(OpCodes.Ldfld, method, null)); + } } } From a123eef4c6237f3cd73ef35cd2852eb4eaa357bb Mon Sep 17 00:00:00 2001 From: Buyaa Namnan Date: Mon, 30 Oct 2023 08:55:19 -0700 Subject: [PATCH 4/4] Apply feedbacks --- .../src/Resources/Strings.resx | 3 -- .../System/Reflection/Emit/ILGeneratorImpl.cs | 30 +++++++++---------- 2 files changed, 15 insertions(+), 18 deletions(-) diff --git a/src/libraries/System.Reflection.Emit/src/Resources/Strings.resx b/src/libraries/System.Reflection.Emit/src/Resources/Strings.resx index fb87824e399c5..96643e5d40b0e 100644 --- a/src/libraries/System.Reflection.Emit/src/Resources/Strings.resx +++ b/src/libraries/System.Reflection.Emit/src/Resources/Strings.resx @@ -198,7 +198,4 @@ The specified opcode cannot be passed to EmitCall. - - Calling convention must be VarArgs. - \ No newline at end of file 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 66efdc9aedf4d..75e06e19f87c4 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 @@ -71,10 +71,10 @@ private void UpdateStackSize(OpCode opCode) _maxStackSize = Math.Max(_maxStackSize, _currentStack); } - private void UpdateStackSize(OpCode opCode, int stackchange) + private void UpdateStackSize(OpCode opCode, int stackChange) { _currentStack += opCode.EvaluationStackDelta; - _currentStack += stackchange; + _currentStack += stackChange; _maxStackSize = Math.Max(_maxStackSize, _currentStack); } @@ -229,19 +229,19 @@ public override void Emit(OpCode opcode, ConstructorInfo con) throw new ArgumentException(SR.Argument_NotMethodCallOpcode, nameof(opcode)); } - int stackchange = 0; - // Push the return value if there is one. - stackchange++; + int stackChange = 0; + // Push the return value + stackChange++; // Pop the parameters. - stackchange -= con.GetParameters().Length; + stackChange -= con.GetParameters().Length; // Pop the this parameter if the constructor is non-static and the // instruction is not newobj. if (!con.IsStatic && !opcode.Equals(OpCodes.Newobj)) { - stackchange--; + stackChange--; } - UpdateStackSize(opcode, stackchange); + UpdateStackSize(opcode, stackChange); _il.OpCode((ILOpCode)opcode.Value); _memberReferences.Add(new KeyValuePair (con, new BlobWriter(_il.CodeBuilder.ReserveBytes(sizeof(int))))); @@ -362,38 +362,38 @@ public override void EmitCall(OpCode opcode, MethodInfo methodInfo, Type[]? opti private static int GetStackChange(OpCode opcode, MethodInfo methodInfo, Type[]? optionalParameterTypes) { - int stackchange = 0; + int stackChange = 0; // Push the return value if there is one. if (methodInfo.ReturnType != typeof(void)) { - stackchange++; + stackChange++; } // Pop the parameters. if (methodInfo is MethodBuilderImpl builder) { - stackchange -= builder.ParameterCount; + stackChange -= builder.ParameterCount; } else { - stackchange -= methodInfo.GetParameters().Length; + stackChange -= methodInfo.GetParameters().Length; } // Pop the this parameter if the method is non-static and the // instruction is not newobj. if (!methodInfo.IsStatic && !opcode.Equals(OpCodes.Newobj)) { - stackchange--; + stackChange--; } // Pop the optional parameters off the stack. if (optionalParameterTypes != null) { - stackchange -= optionalParameterTypes.Length; + stackChange -= optionalParameterTypes.Length; } - return stackchange; + return stackChange; } public override void EmitCalli(OpCode opcode, CallingConventions callingConvention, Type? returnType, Type[]? parameterTypes, Type[]? optionalParameterTypes) => throw new NotImplementedException();