Bug 57222 - System.Reflection.AmbiguousMatchException for two fields with same name but different types
Summary: System.Reflection.AmbiguousMatchException for two fields with same name but d...
Status: RESOLVED FIXED
Alias: None
Product: Runtime
Classification: Mono
Component: Reflection (show other bugs)
Version: master
Hardware: PC Mac OS
: --- normal
Target Milestone: 15.3
Assignee: Aleksey Kliger
URL:
Depends on:
Blocks:
 
Reported: 2017-06-07 02:38 UTC by Jonathan Chambers
Modified: 2017-06-12 14:57 UTC (History)
4 users (show)

Tags:
Is this bug a regression?: Yes
Last known good build: 4.8.0

Notice (2018-05-24): bugzilla.xamarin.com is now in read-only mode.

Please join us on Visual Studio Developer Community and in the Xamarin and Mono organizations on GitHub to continue tracking issues. Bugzilla will remain available for reference in read-only mode. We will continue to work on open Bugzilla bugs, copy them to the new locations as needed for follow-up, and add the new items under Related Links.

Our sincere thanks to everyone who has contributed on this bug tracker over the years. Thanks also for your understanding as we make these adjustments and improvements for the future.


Please create a new report on GitHub or Developer Community with your current version information, steps to reproduce, and relevant error messages or log files if you are hitting an issue that looks similar to this resolved bug and you do not yet see a matching new report.

Related Links:
Status:
RESOLVED FIXED

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.