Bugzilla – Bug 317995
[GMCS] Converting array to IEnumerable<T>
Last modified: 2007-09-15 21:24:46 UTC
---- Reported by emrysk@gmail.com 2005-05-15 00:47:48 MST ---- I'm using Mono 1.1.7 built from source on Ubuntu (Hoary). The following is a working example. It throws a NullReferenceException from AddRange. using System.Collections.Generic; class Program { static void Main() { List<byte> list = new List<byte>(); byte[] bytes = new byte[] { 100, 100 }; list.AddRange(bytes); } } This is the odd bit, though. If I call: foreach (byte b in bytes) list.Add(b); instead of AddRange, it works just fine. Looking at Mono's source, AddRange is implemented in this chunk of code, which should be identical to what I'm doing with foreach: [MonoTODO ("PERFORMANCE: fix if it is an IList <T>")] public void AddRange(IEnumerable<T> collection) { foreach (T t in collection) Add (t); } Perhaps this is a bug with IEnumerable<byte>? Here's the exception dump: Unhandled Exception: System.NullReferenceException: A null value was found where an object instance was required. in <0x00022> System.Collections.Generic.List`1<System.Byte>:AddRange (IEnumerable`1 ) in <0x0006d> Program:Main () ---- Additional Comments From bmaurer@users.sf.net 2005-05-15 01:45:14 MST ---- More direct test case: using System; using System.Collections.Generic; class X { static void Main () { X <byte>.Y (new byte []{ 100, 100 }); } } class X <T> { public static void Y (IEnumerable <T> x) { foreach (T t in x) { Console.WriteLine (t); } } } ---- Additional Comments From martin@ximian.com 2005-05-17 10:07:20 MST ---- This is a runtime issue. csc generates ==== .method private hidebysig static void Main() cil managed { .entrypoint // Code size 12 (0xc) .maxstack 8 IL_0000: ldc.i4.0 IL_0001: newarr [mscorlib]System.Byte IL_0006: call void class X`1<uint8>::Y(class [mscorlib]System.Collections.Generic.IEnumerable`1<!0>) IL_000b: ret } // end of method X::Main ==== And this is working fine on the MS runtime, but crashes with mono. ---- Additional Comments From miguel@ximian.com 2005-05-18 19:31:50 MST ---- Simpler test case: using System; using System.Collections.Generic; class X { static byte [] x = new byte [] {100, 100}; static void Main () { X <byte>.Y (x); } } class X <T> { public static void Y (IEnumerable <T> x) { IEnumerator<T> tor = x.GetEnumerator (); } } The method X<byte>.Y is: .method public hidebysig static void Y(class [mscorlib]System.Collections.Generic.IEnumerable`1<!T> x) cil managed { // Code size 8 (0x8) .maxstack 2 .locals init (class [mscorlib]System.Collections.Generic.IEnumerator`1<!T> V_0) IL_0000: ldarg.0 IL_0001: callvirt instance class [mscorlib]System.Collections.Generic.IEnumerator`1<!0> class [mscorlib]System.Collections.Generic.IEnumerable`1<!T>::GetEnumerator() IL_0006: stloc.0 IL_0007: ret } // end of method X`1::Y Which gets JITed into: 00000000 <X_1_Y>: 0: 55 push %ebp 1: 8b ec mov %esp,%ebp 3: 83 ec 04 sub $0x4,%esp 6: c7 45 fc 00 00 00 00 movl $0x0,0xfffffffc(%ebp) d: 8b 45 08 mov 0x8(%ebp),%eax 10: 50 push %eax 11: 8b 00 mov (%eax),%eax 13: 8b 40 0c mov 0xc(%eax),%eax 16: 8b 80 98 00 00 00 mov 0x98(%eax),%eax 1c: ff 10 call *(%eax) 1e: 59 pop %ecx 1f: 89 45 fc mov %eax,0xfffffffc(%ebp) 22: c9 leave 23: c3 ret Now, here is what happens, the value loaded in offset 16 is zero, so the call "%eax" produces the segfault: The code at instructions 0x13 and 0x16 is produced by inssel.c's mini_emit_load_intf_reg_vtable, by the following code: 12779 MONO_EMIT_NEW_LOAD_MEMBASE (s, ioffset_reg, vtable_reg, G_STRUCT_OFFSET (MonoVTable, interface_offsets)); That is the 0xc($eax), $eax load 12770 MONO_EMIT_NEW_LOAD_MEMBASE (s, intf_reg, ioffset_reg, klass->interface_id * SIZEOF_VOID_P); That is the 0x98($eax), $eax load And this is what results in a null value. ---- Additional Comments From martin@ximian.com 2005-05-19 08:43:56 MST ---- Whoever looks at this bug, don't get confused by Miguel's last comment - it's not the assembly code which is wrong. ---- Additional Comments From martin@ximian.com 2005-05-19 08:44:21 MST ---- I hate bugzilla for typing in a long comment and then eating it :-( ---- Additional Comments From martin@ximian.com 2005-05-19 08:45:59 MST ---- Just look at the IL: IL_0001: newarr [mscorlib]System.Byte IL_0006: call void class X`1<uint8>::Y(class [mscorlib]System.Collections.Generic.IEnumerable`1<!0>) After the `newarr', there's an instance of System.Array on the stack. The `call' expects an instance of S.C.G.IEnumerable<T>. Later on, the called method will make an indirect call through the vtable to call `GetEnumerator()' (or whatever) and this'll crash since it's using the wrong `MonoClass *'. ---- Additional Comments From miguel@ximian.com 2005-05-19 12:49:38 MST ---- Paolo, can you comment on this? ---- Additional Comments From lupus@ximian.com 2005-05-20 02:48:54 MST ---- There is no info in the metadata that tells the runtime that an array implements IEnumerable<T>, so the runtime structures that would allow calling IEnumerable<T> methods on arrays are not there. We likely need to fake this and special case it in the runtime, since I doubt there is a C# syntax to express that a non-generics class implements a generics interface. This likely applies to IList<T> and ICollection<T>, too. ---- Additional Comments From lupus@ximian.com 2005-05-20 14:05:58 MST ---- Adding the generic interfaces is not enough, of course, since we also need the implementation of the methods in the interfaces. I'm installing beta2 and checking if Array is really a generic type there, or if we can put a generic class between Array and the actual array class, otherwise it's going to be a mess, since we'd have to generate the generic methods at runtime, too. ---- Additional Comments From nazgul@omega.pl 2005-05-20 14:18:16 MST ---- Lupus: primitive numeric types (like Int32) are implementing IComparable<T> so it is possible in both runtime and C# also: class A : IComparable <A> { int Compare (A) {... } } If I am missing something or I'm simply wrong, then ignore me - I just saw this strange statement of yours... ---- Additional Comments From mass@akuma.org 2005-06-19 03:56:28 MST ---- I'll toss my two cents in here as well, since I hit this bug writing tests for the generics code (and may need it fixed for the future) Array in 2005b2 does not implement any generic interfaces, this makes sense - to support varying generic interfaces, it would need to be changed into a generic type. I imagine arrays are special-cased in their runtime so that the types remains the same (child types of Array, inheriting array implementations of interfaces), but also have their interface tables stuffed with an implementation of IList<T>, which includes ICollection<T> and IEnumerable<T>. I could certainly author a quick C# ArrayList<T> if it helps resolve this bug, but cannot imagine this could be used without some runtime changes. ---- Additional Comments From mass@akuma.org 2005-06-19 04:47:00 MST ---- just checked on 2005b2, Type.GetInterfaces() only reports non-generic types. ---- Additional Comments From mass@akuma.org 2005-06-20 02:34:24 MST ---- Well, let me clarify that comment - other types (generic and not) report generic interfaces just fine. However, int[] only reports Array as a parent and IEnumerable, IList and ICollection as interfaces it supports. This makes me think it is special-cased in the MS runtime in some way. If someone desires disassembly of a cast and call, drop me a line and I'll cut&paste from the vs.net 2k5b2 debug window. ---- Additional Comments From mass@akuma.org 2005-06-26 14:19:59 MST ---- Created an attachment (id=167943) Test app against vs.net 2k5b2, with output in comment ---- Additional Comments From martin@ximian.com 2005-06-27 03:17:17 MST ---- Paolo, there's one thing which worries me: shouldn't the runtime detect that the types are incompatible (because it doesn't know yet that that's allowed) and reject this as invalid IL - ie. in the same way as if you'd try to pass an array to a function taking a string ? My first idea of fixing this was having the verifier somehow change the type of the array. ---- Additional Comments From martin@ximian.com 2005-06-27 03:19:26 MST ---- Paolo, what do you think about doing this in NEWARR ? Everytime you create an array where all elements have the same type T, create an instance of some special generic class System.InternalArray<T> instead of just System.Array. This shouldn't require too many changes in the JIT, just in NEWARR. ---- Additional Comments From martin@ximian.com 2005-06-27 13:23:42 MST ---- I'm almost done with a fix. ---- Additional Comments From martin@ximian.com 2005-06-28 07:26:24 MST ---- Fixed in SVN. Note that arrays also implement IList<T> - I added a slightly improved test case as gtest-177.cs. ---- Additional Comments From martin@ximian.com 2005-06-28 07:27:40 MST ---- *** https://bugzilla.novell.com/show_bug.cgi?id=MONO74991 has been marked as a duplicate of this bug. *** This bug blocked bug(s) 74945 74991. Imported an attachment (id=167943) Unknown bug field "cf_op_sys_details" encountered while moving bug <cf_op_sys_details>Ubuntu Linux 5.05 (Hoary)</cf_op_sys_details> Unknown operating system unknown. Setting to default OS "Other".