Bug 57222

Summary: System.Reflection.AmbiguousMatchException for two fields with same name but different types
Product: [Mono] Runtime Reporter: Jonathan Chambers <joncham>
Component: ReflectionAssignee: Aleksey Kliger <aleksey>
Status: RESOLVED FIXED    
Severity: normal CC: masafa, mono-bugs+mono, mono-bugs+runtime, vargaz
Priority: ---    
Version: master   
Target Milestone: 15.3   
Hardware: PC   
OS: Mac OS   
Tags: Is this bug a regression?: Yes
Last known good build: 4.8.0

Description Jonathan Chambers 2017-06-07 02:38:05 UTC
Run the following example code and get this exception. No exception when running on .NET

Unhandled Exception:
System.Reflection.AmbiguousMatchException: Ambiguous match found.
  at System.RuntimeType.GetField (System.String name, System.Reflection.BindingFlags bindingAttr) [0x0006b] in <400071ddcfe64ed8a3531490bb763536>:0 
  at System.RuntimeType.GetField (System.Reflection.FieldInfo fromNoninstanciated) [0x00025] in <400071ddcfe64ed8a3531490bb763536>:0 
  at System.Reflection.Emit.FieldBuilder.RuntimeResolve () [0x0000b] in <400071ddcfe64ed8a3531490bb763536>:0 
  at System.Reflection.Emit.ModuleBuilder.FixupTokens (System.Collections.Generic.Dictionary`2[TKey,TValue] token_map, System.Collections.Generic.Dictionary`2[TKey,TValue] member_map, System.Collections.Generic.Dictionary`2[TKey,TValue] inst_tokens, System.Boolean open) [0x0009c] in <400071ddcfe64ed8a3531490bb763536>:0 
  at System.Reflection.Emit.ModuleBuilder.FixupTokens () [0x0001b] in <400071ddcfe64ed8a3531490bb763536>:0 
  at System.Reflection.Emit.ModuleBuilder.Save () [0x0005b] in <400071ddcfe64ed8a3531490bb763536>:0 
  at System.Reflection.Emit.AssemblyBuilder.Save (System.String assemblyFileName, System.Reflection.PortableExecutableKinds portableExecutableKind, System.Reflection.ImageFileMachine imageFileMachine) [0x0022b] in <400071ddcfe64ed8a3531490bb763536>:0 
  at ConsoleApplication2.Program.EmitAndSaveAssembly (System.String fileName) [0x000f6] in <228a70eddd894134b148a8dd9a7a9fc9>:0 
  at ConsoleApplication2.Program.Main (System.String[] args) [0x00001] in <228a70eddd894134b148a8dd9a7a9fc9>:0 

Sample code:

using System;
using System.Reflection;
using System.Reflection.Emit;

namespace ConsoleApplication2
{
    internal class Program
    {
        public static void Main (string[] args)
        {
            string fileName = args[0];
            var testTypeName = "Jon";

            var assemblyName = new AssemblyName { Name = testTypeName };
            var dynamicAssembly = AssemblyBuilder.DefineDynamicAssembly (assemblyName, AssemblyBuilderAccess.Save);
            var dynamicModule = dynamicAssembly.DefineDynamicModule (assemblyName.Name, fileName);
            var typeBuilder = dynamicModule.DefineType (testTypeName, TypeAttributes.Public | TypeAttributes.Class);

            var mainMethod = typeBuilder.DefineMethod ("Main", MethodAttributes.Public | MethodAttributes.Static, typeof (int), new Type[0]);
            var mainMethodIl = mainMethod.GetILGenerator ();

            var f1 = typeBuilder.DefineField ("x", typeof (byte), FieldAttributes.Private | FieldAttributes.Static);
            var f2 = typeBuilder.DefineField ("x", typeof (sbyte), FieldAttributes.Private | FieldAttributes.Static);

            mainMethodIl.Emit (OpCodes.Ldsflda, f1);
            mainMethodIl.Emit (OpCodes.Ldsflda, f2);
            mainMethodIl.Emit (OpCodes.Pop);
            mainMethodIl.Emit (OpCodes.Pop);
            mainMethodIl.Emit (OpCodes.Ldc_I4_0);
            mainMethodIl.Emit (OpCodes.Ret);

            typeBuilder.CreateType ();
            dynamicAssembly.SetEntryPoint (mainMethod);

            dynamicAssembly.Save (fileName, PortableExecutableKinds.Required32Bit, ImageFileMachine.I386);
        }
    }
}
Comment 1 Jonathan Chambers 2017-06-07 02:38:46 UTC
Reproduced in Mono 5.0 and 5.2. Regression from 4.8
Comment 2 Jonathan Chambers 2017-06-07 02:56:45 UTC
Also note, two fields of the same name and same type are allowed as well.

using System;
using System.Reflection;
using System.Reflection.Emit;

namespace ConsoleApplication2
{
    internal class Program
    {
        public static void Main (string[] args)
        {
            string fileName = args[0];
            var testTypeName = "Jon";

            var assemblyName = new AssemblyName { Name = testTypeName };
            var dynamicAssembly = AssemblyBuilder.DefineDynamicAssembly (assemblyName, AssemblyBuilderAccess.Save);
            var dynamicModule = dynamicAssembly.DefineDynamicModule (assemblyName.Name, fileName);
            var typeBuilder = dynamicModule.DefineType (testTypeName, TypeAttributes.Public | TypeAttributes.Class);

            var mainMethod = typeBuilder.DefineMethod ("Main", MethodAttributes.Public | MethodAttributes.Static, typeof (int), new Type[0]);
            var mainMethodIl = mainMethod.GetILGenerator ();

            var f1 = typeBuilder.DefineField ("x", typeof (byte), FieldAttributes.Private | FieldAttributes.Static);
            var f2 = typeBuilder.DefineField ("x", typeof (byte), FieldAttributes.Private | FieldAttributes.Static);

            mainMethodIl.Emit (OpCodes.Ldsflda, f1);
            mainMethodIl.Emit (OpCodes.Ldsflda, f2);
            mainMethodIl.Emit (OpCodes.Pop);
            mainMethodIl.Emit (OpCodes.Pop);
            mainMethodIl.Emit (OpCodes.Ldc_I4_0);
            mainMethodIl.Emit (OpCodes.Ret);

            typeBuilder.CreateType ();
            dynamicAssembly.SetEntryPoint (mainMethod);

            dynamicAssembly.Save (fileName, PortableExecutableKinds.Required32Bit, ImageFileMachine.I386);
        }
    }
}
Comment 3 Aleksey Kliger 2017-06-07 15:59:27 UTC
Looks like the FixupTokens() rewrite in a0ad8154f7cdca706802aa196d95686e0f8d3b7b didn't account that our RuntimeType.GetField (FieldInfo) method is using names for lookup, which doesn't work out.

I bet this also goes wrong with properties and events.   Also worth checking that if a base and a derived class both define a field with the same name that a method in the derived class that references both fields will get the right ones (and not two references to the derived class' fields or something like that).
Comment 4 Zoltan Varga 2017-06-08 18:33:13 UTC
https://github.com/mono/mono/pull/4996
Comment 5 Aleksey Kliger 2017-06-08 21:49:26 UTC
We thought up another way to fix this https://github.com/mono/mono/pull/4997

Also properties and events boil down to methods.

Although methods aren't affected by this bug, I added a regression test for
this case anyway in case the implementation changes.

Currently MethodBuilder.RuntimeResolve() goes through a different codepath - it
calls RuntimeType.GetMethod (MethodInfo) which calls into an icall which looks
for a method with a matching token rather than a matching name.