diff --git a/src/libraries/System.Reflection.Emit/src/Resources/Strings.resx b/src/libraries/System.Reflection.Emit/src/Resources/Strings.resx index e425a272ac097..96643e5d40b0e 100644 --- a/src/libraries/System.Reflection.Emit/src/Resources/Strings.resx +++ b/src/libraries/System.Reflection.Emit/src/Resources/Strings.resx @@ -195,4 +195,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 e7331233adb3d..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 @@ -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; @@ -19,6 +20,7 @@ internal sealed class ILGeneratorImpl : ILGenerator private int _currentStack; private List _locals = new(); private Dictionary _labelTable = new(2); + private List> _memberReferences = new(); internal ILGeneratorImpl(MethodBuilder methodBuilder, int size) { @@ -29,6 +31,7 @@ internal ILGeneratorImpl(MethodBuilder methodBuilder, int size) } internal int GetMaxStackSize() => _maxStackSize; + internal List> GetMemberReferences() => _memberReferences; internal InstructionEncoder Instructions => _il; internal bool HasDynamicStackAllocation => _hasDynamicStackAllocation; internal List Locals => _locals; @@ -68,6 +71,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) @@ -210,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 + 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) { @@ -227,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)); @@ -270,10 +307,95 @@ public override void Emit(OpCode opcode, LocalBuilder local) } 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)); + } + + _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. + 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; + } + + return stackChange; + } + 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 0dde640eae269..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 @@ -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; @@ -119,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. @@ -130,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) @@ -161,8 +164,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,22 +183,51 @@ internal void AppendMetadata(MethodBodyStreamEncoder methodBodyEncoder) } } - private void WriteMethods(TypeBuilderImpl typeBuilder, List genericParams, MethodBodyStreamEncoder methodBodyEncoder) + // 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 (MethodBuilderImpl method in typeBuilder._methodDefinitions) + foreach (FieldBuilderImpl field in fieldDefinitions) + { + field._handle = MetadataTokens.FieldDefinitionHandle(_nextFieldDefRowId++); + } + } + + private void PrePopulateMethodDefinitionHandles(List methods) + { + foreach (MethodBuilderImpl method in methods) + { + method._handle = MetadataTokens.MethodDefinitionHandle(_nextMethodDefRowId++); + } + } + + private void WriteMethods(List methods, List genericParams, MethodBodyStreamEncoder methodBodyEncoder) + { + foreach (MethodBuilderImpl method in methods) { int offset = -1; ILGeneratorImpl? il = method.ILGeneratorImpl; if (il != null) { + FillMemberReferences(il); StandaloneSignatureHandle signature = il.Locals.Count == 0 ? default : _metadataBuilder.AddStandaloneSignature(_metadataBuilder.GetOrAddBlob(MetadataSignatureHelper.LocalSignatureEncoder(il.Locals, this))); offset = AddMethodBody(method, il, signature, 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) { @@ -203,7 +235,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, StandaloneSignatureHandle signature, MethodBodyStreamEncoder bodyEncoder) => bodyEncoder.AddMethodBody( @@ -251,23 +290,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); - _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(fieldHandle, field._offset); + AddFieldLayout(handle, field._offset); } if (field._marshallingData != null) { - AddMarshalling(fieldHandle, field._marshallingData.SerializeMarshallingData()); + AddMarshalling(handle, field._marshallingData.SerializeMarshallingData()); } if (field._defaultValue != DBNull.Value) { - AddDefaultValue(fieldHandle, field._defaultValue); + AddDefaultValue(handle, field._defaultValue); } } } @@ -320,6 +359,55 @@ 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); + } + + if (member is ConstructorInfo ctor) + { + return MetadataSignatureHelper.ConstructorSignatureEncoder(ctor.GetParameters(), 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); @@ -437,6 +531,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) { @@ -450,13 +559,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/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/PersistableAssemblyBuilder/AssemblySaveILGeneratorTests.cs b/src/libraries/System.Reflection.Emit/tests/PersistableAssemblyBuilder/AssemblySaveILGeneratorTests.cs index 5c5141f544c14..c24fb69307606 100644 --- a/src/libraries/System.Reflection.Emit/tests/PersistableAssemblyBuilder/AssemblySaveILGeneratorTests.cs +++ b/src/libraries/System.Reflection.Emit/tests/PersistableAssemblyBuilder/AssemblySaveILGeneratorTests.cs @@ -542,5 +542,289 @@ public void LocalBuilderExceptions() Assert.Throws(() => il.Emit(OpCodes.Ldloc, nullBuilder)); Assert.Throws(() => anotherIL.Emit(OpCodes.Ldloc, stringLocal)); } + + [Fact] + public void ReferenceFieldInIL() + { + 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 ReferenceFieldAndMethodsInIL() + { + 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 }); + + 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]); + } + } + + [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(); + 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"); + ilGenerator.EmitWriteLine(local); + 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.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]); + } + } + + [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]); + } + } + + [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)); + } } } 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