runtime: LINQ bug: ConcatNIterator.LazyToArray() produces incorrect results

Here’s a repro: https://github.com/gulbanana/repro-corefx-concat-toarray

banana@forsyth MINGW64 /c/code/repro-corefx-concat-toarray (master)
$ dotnet run -f netcoreapp1.0                                      
A B C                                                              
                                                               
banana@forsyth MINGW64 /c/code/repro-corefx-concat-toarray (master)
$ dotnet run -f netcoreapp2.0                                      
A B A                                                              

The problem occurs when using Concat multiple times and then calling ToArray().

using System;
using System.Collections.Generic;
using System.Linq;

class Program
{
    class A { public override string ToString() => "A"; }
    class B { public override string ToString() => "B"; }
    class C { public override string ToString() => "C"; }

    static void Main(string[] args)
    {
        var results = new List<A> { new A() }
            .Concat(new List<object> { new B() })
            .Concat(new List<C> { new C() })
            .ToArray();

        Console.WriteLine(string.Join(' ', results));
    }
}

This program should print “A B C”, but it instead prints “A B A”. I believe the problem is in the specialised implementation of concatenation for 3±enumerables-of-collections. I ran into it in an actual project (an IEnumerable<BaseType> containing multiple subclass instances) but I’ve worked around the bug by using ToList() instead.

[EDIT] Add C# syntax highlight by @karelz

About this issue

  • Original URL
  • State: closed
  • Created 7 years ago
  • Comments: 34 (33 by maintainers)

Commits related to this issue

Most upvoted comments

I think we should include the fix in 2.0.2 servicing release.

My scenario is part of an ORM - assembling a list of “operations” all subclassing a common base.

I’ve just realised this is what was causing a bug with a website I was porting from netfx to corefx, and finding it hard to reproduce because the order of the sources to the concatenation was non-deterministic and only sometimes in the order to trigger this, but I didn’t think that would matter to the bug. Easily worked around now I know what it is, so thanks for that, @gulbanana

@jamesqo

Here is a repro (from @vuminhle). It seems very specific - reducing the list sizes or changing the construction in various ways loses the repro.

using System;
using System.Collections.Generic;
using System.Linq;

namespace repro {
    internal class Program {
        private static void Main(string[] args) {
            A[] list = List1().Concat(List2()).Concat(List3()).ToArray();
            foreach (A a in list) {
                Console.WriteLine(a.Value);
            }
        }

        internal static IEnumerable<A> List1() {
            for (var i = 0; i < 4; i++) {
                yield return new A(i);
            }
        }

        internal static IEnumerable<A> List2() {
            return Enumerable.Range(0, 2).Select(v => new A(v));
        }

        internal static IEnumerable<A> List3() {
            for (var i = 0; i < 5; i++) {
                yield return new A(i);
            }
        }

        internal class A {
            public A(int v) {
                Value = v;
            }

            public int Value { get; }
        }
    }
}

I should be able to submit a fix tmrw for this. Thanks for all of the preliminary investigation @JonHanna and @OmarTawfik.

edit: After thinking about it in my mind for a while, I believe what’s happening is the column = 0 part is causing the returned CopyPosition (which is supposed to represent the position copied up to) to be inaccurate. In the example in @JonHanna’s 2nd comment, we should start copying after the null from (row 0, column 1) but instead do it from (row 0, column 0).

My scenario is part of an ORM - assembling a list of “operations” all subclassing a common base. Since triggering the bug requires all of concat(), toarray() and multiple-subtypes, it probably isn’t common. It was very disconcerting though, seeing the knock-on effects when 1+1+1 was no longer equal to 3 😃

This looks pretty bad - I wonder what is the impact of this bug. Which kind of scenarios is it going to show up? @VSadov @OmarTawfik can you please dig into it as a priority? If it is impactful, we may need to fix it in 2.0 servicing.