CsvHelper icon indicating copy to clipboard operation
CsvHelper copied to clipboard

Stackoverflow Exception on AutoMapMembers

Open bradmarder opened this issue 6 years ago • 28 comments

I didn't actually hit a stackoverflow exception after running this code for like an hour. I assume eventually it will occur. Something is causing a circular dependency. I spent some trying to debug with no luck. If you provide me some guidance, I can create a full repro. In the meantime, here is just a snapshot of the stacktrace while it just keeps growing.

   at CsvHelper.Configuration.ClassMap.AutoMapMembers(ClassMap map, Configuration configuration, LinkedList`1 mapParents, Int32 indexStart)
   at CsvHelper.Configuration.ClassMap.AutoMapMembers(ClassMap map, Configuration configuration, LinkedList`1 mapParents, Int32 indexStart)
   at CsvHelper.Configuration.ClassMap.AutoMapMembers(ClassMap map, Configuration configuration, LinkedList`1 mapParents, Int32 indexStart)
   at CsvHelper.Configuration.ClassMap.AutoMapMembers(ClassMap map, Configuration configuration, LinkedList`1 mapParents, Int32 indexStart)
   at CsvHelper.Configuration.ClassMap.AutoMapMembers(ClassMap map, Configuration configuration, LinkedList`1 mapParents, Int32 indexStart)
   at CsvHelper.Configuration.ClassMap.AutoMapConstructorParameters(ClassMap map, Configuration configuration, LinkedList`1 mapParents, Int32 indexStart)
   at CsvHelper.Configuration.ClassMap.AutoMap(Configuration configuration)
   at CsvHelper.Configuration.Configuration.AutoMap(Type type)
   at CsvHelper.CsvWriter.WriteHeader(Type type)
   at CsvHelper.CsvWriter.WriteRecords[T](IEnumerable`1 records)

bradmarder avatar Jan 08 '19 03:01 bradmarder

Can you provide the class structure that it's trying to map?

JoshClose avatar Jan 08 '19 06:01 JoshClose

@JoshClose I'm having a really difficult time creating a repro... this is a true heisenbug!

The model that I'm trying to write has a single ctor that accepts a single object.

This "object" has property that links to another object....which links to another object, etc. Just your standard entity framework models, nothing really special, no crazy reflection etc.

Every time I attempt to narrow down the chain, and eliminate every property that is causing the infinite loop, I just inevitably end up at the start of the chain again. I realize this is probably an awful explanation, and I'm sorry for that.

Anyway, if you have any suggestions for how to narrow this down or if I should attempt to log certain lines in csvhelper, let me know.

bradmarder avatar Jan 11 '19 04:01 bradmarder

Do you want to email me an example so it isn't public?

JoshClose avatar Jan 11 '19 04:01 JoshClose

Meaning, the full class structure that causes the failure.

JoshClose avatar Jan 11 '19 04:01 JoshClose

@JoshClose have you found anything on this issue yet?

We've recently begun hitting SIGSEGV faults that appear to be related to the inclusion of a ClassMap when deserializing. When we comment out the registration of the ClassMap, we don't get the issue, but when we include it, we typically do.

It appears to be something that's timing related, as there isn't a solid repro case, and the frequency of hitting it increases as we spin up more threads doing simultaneous parsing. For example, running 12 threads right now repeatedly, parsing the same files over and over, I'll hit the SIGSEGV approximately 75% of the runs.

I'm trying to better info, but unfortunately .NET Core doesn't throw a StackOverflowException - just hands back the dump, which has precious little info!

philipflesher avatar Jan 23 '19 15:01 philipflesher

Can you reproduce it while debugging? If so, the PDB files are on NuGet. You can set your symbols server and debug into the source code.

https://docs.microsoft.com/en-us/visualstudio/debugger/specify-symbol-dot-pdb-and-source-files-in-the-visual-studio-debugger?view=vs-2017

JoshClose avatar Jan 23 '19 22:01 JoshClose

@JoshClose yes, I just pulled in the entire repo and dumped a ton of logs in there. I'm not knowledgeable enough about the code to know what I should be looking for though. All I know is that method kept cycling in a loop, albeit over a sequence of different reflected types.

bradmarder avatar Jan 24 '19 03:01 bradmarder

Do you have some code you could zip up and email me so I can reproduce it?

JoshClose avatar Jan 24 '19 17:01 JoshClose

For my part, I have not yet been able to reproduce it consistently. I don't believe there are any shared objects that I'm using across the threads, yet the number of threads running at once (each operating on separate CSV's) seems to have a significant impact on the likelihood of hitting the SIGSEGV.

I'm feeling like the issue I'm chasing here isn't related to the one originally reported by @bradmarder. His is happening on writes, mine on reads. It also doesn't seem related to using a ClassMap any longer - I've been able to hit it without that.

@JoshClose just for my sanity - is the library thread-safe at a module level? Meaning, no shared state across instances (statics/globals), etc.?

philipflesher avatar Jan 24 '19 20:01 philipflesher

@philipflesher I would have to go through and do an audit. There should be locks in places where there are statics, but it's possible I've added something at some point and forgot it.

JoshClose avatar Jan 24 '19 21:01 JoshClose

Could you point me to any specific places in code where you might suspect a recursive dive? Or maybe some of the static/shared variables I could put specific watches on?

philipflesher avatar Jan 24 '19 22:01 philipflesher

This is a place when auto mapping that it could happen. https://github.com/JoshClose/CsvHelper/blob/6b339704fe22eb8c90b2d3da6e8470561d7c708a/src/CsvHelper/Configuration/ClassMap.cs#L145

There is a CheckForCircularReference method that is called to ensure that doesn't happen. There could be a bug there though.

JoshClose avatar Jan 26 '19 16:01 JoshClose

The ObjectResolver has a static. https://github.com/JoshClose/CsvHelper/blob/6b339704fe22eb8c90b2d3da6e8470561d7c708a/src/CsvHelper/ObjectResolver.cs#L20

ReflectionHelper https://github.com/JoshClose/CsvHelper/blob/6b339704fe22eb8c90b2d3da6e8470561d7c708a/src/CsvHelper/ReflectionHelper.cs#L51

JoshClose avatar Jan 26 '19 16:01 JoshClose

We've just come across this same one whilst writing, and we've resolved it by adding in a parameterless public constructor to our class for the fields and it solved the issue.

If you remove the Public Sub New() then you it still fail with the exception

We ran it through with the CsvHelpersource code and it's in the automap constructor that then gets caught in a loop doing the AutoMapMembers

Private Class ExportItem
        Public Property StartDate As String
        Public Property Duration As Double
        Public Property MediaId As Integer
        Public Property Title As String
        Public Property ArtistName As String
        Public Property StationId As Integer
        Public Property StationName As String
        Public Property Location As String
        Public Property LocationNetworkName As String
        Public Property Login As String
        Public Property OnAir As String
        Public Property PlayMode As String
        Public Property Preview As Boolean
        Public Property Reference As String
        Public Property Player As Integer
        Public Property Hardware As String
        Public Sub New()
        End Sub
        Public Sub New(playlogVm As PlayLogItemViewModel)
            With playlogVm
                Me.StartDate = .PlayLog.StartDateTimeUtc.ToString("o")
                Me.Duration = .PlayLog.PlayedDuration.TotalSeconds
                Me.MediaId = .PlayLog.MediaId
                Me.Title = .ItemTitle
                Me.ArtistName = .FirstArtistName
                Me.StationId = .PlayLog.StationId
                Me.StationName = .StationName
                Me.Location = .LocationName
                Me.LocationNetworkName = .LocationNetworkName
                Me.Login = .PlayLog.MyriadLoginName
                Me.OnAir = .OnAirCaption
                Me.PlayMode = .PlayLog.PlayMode.ToString()
                Me.Preview = .PlayLog.PreviewMode
                Me.Reference = .PlayLog.ExtSchedulerReference
                Me.Player = .PlayLog.PlayerNumber
            End With
        End Sub
    End Class
    
    Public Sub ExportToCSV
            Using writer As TextWriter = File.CreateText(filename)
                    Using csvWriter As New CsvHelper.CsvWriter(writer)
                        With csvWriter.Configuration
                            .HasHeaderRecord = True
                        End With
                        csvWriter.WriteHeader(GetType(ExportItem))
                        Await csvWriter.NextRecordAsync()

                        For Each item In .Results
                            Try
                                csvWriter.WriteRecord(New ExportItem(item))
                                Await csvWriter.NextRecordAsync()
                            Catch ex As Exception

                            End Try
                        Next
                    End Using
                    writer.Close()
                End Using
     End Sub

PeterJarrettUK avatar Jan 31 '19 15:01 PeterJarrettUK

What is the definition for PlayLogItemViewModel?

Are you able to create a unit test that fails?

JoshClose avatar Jan 31 '19 19:01 JoshClose

Hello

Same issue: I have a class with 20 properties (only int, string and DateTime) A csv.WriteRecords(..) call and it crashes with stackoverflow.

I tried a mapper class (and registered it in the configuration) with only having a AutoMap() in the constructor: => it crashes with stackoverflow during the AutoMap

Then I mapped every single property ("Map(m => m.Name")) in the mapper class instead of the AutoMap. => it works

Next try (like Mostlypyjamas said): Instead of the mapper class, I only added a parameterless constructor to the data class => it works

Greetings Robert

Robert2334 avatar Feb 13 '19 17:02 Robert2334

I'm running into this with this class: https://gist.github.com/eltiare/54b0bf78ec16adb9027c7f85a1ae1368

Everything works fine until I go to write an instance of OrderExport.

eltiare avatar Mar 15 '19 17:03 eltiare

Found a way of creating a stack overflow with the following code (and using AutoMap):

public class BiOperation
{
	private readonly Operation _operation;

	public BiOperation(Operation operation)
	{
		_operation = operation;
	}

	public string Resource => _operation.Resource;
}

Orcomp avatar Apr 01 '19 11:04 Orcomp

Stackoverflow exception on self referencing class with property of other class:

private class C
{
	public string Id { get; set; }
}

private class SelfCircularZ
{
    public C Other { get; set; }

    public SelfCircularZ Circular { get; set; }
}

[TestMethod]
public void SelfCircularZ()
{
    var config = new CsvHelper.Configuration.Configuration();
    config.AutoMap<SelfCircularZ>();
}

Field Other of custom class C causes stackoverflow, if property is simple type (like string) - no problems.

This line has 0 mapParents and CheckForCircularReference is not skipping processing: https://github.com/JoshClose/CsvHelper/blob/2914f6856febbf7488c53ed65948a00a39f95a22/src/CsvHelper/Configuration/ClassMap.cs#L297

zii-dmg avatar May 28 '19 17:05 zii-dmg

I've also run into this problem recently. When trying to AutoMap() a class with complex dependencies (ex: Entity Framework model class), it would go deep down until StackOverflowException is thrown at:

CsvHelper/src/CsvHelper/Configuration/ClassMap.cs

with indexStart=1786260400

My test code is something like this:

public class MyCsvMap : ClassMap<MyViewModel>
{
    public MyCsvMap()
    {
        AutoMap();
        Map(e => e.object1).Ignore();
        // Ignore other fields ...
    }
}

//Test block
var csvConfig = new CsvHelper.Configuration.Configuration();
csvConfig.RegisterClassMap<MyCsvMap>();

chakrit-uc avatar Jun 26 '19 10:06 chakrit-uc

Also having this issue. Whilst tracing it, I found a typo (paramter)...

if (CheckForCircularReference(parameter.ParameterType, mapParents))
					{
						throw new InvalidOperationException($"A circular reference was detected in constructor paramter '{parameter.Name}'." +
															  "Since all parameters must be supplied for a constructor, this parameter can't be skipped.");
					}

VaderConsulting avatar Aug 12 '19 10:08 VaderConsulting

I think the issue is simple to reproduce by having a property that is typed as a Uri. I've worked around it by temporarily using a string instead.

APIWT avatar Dec 06 '19 17:12 APIWT

I'm getting this on AutoMap() and I'm thinking due to the comments above that it is due to the presence of an Exception member, albeit with an [Ignore] attribute, on the class in question. There could potentially be infinite recursion on the InnerException field, but I'm not sure why it ignores the [Ignore].

gandhis1 avatar May 08 '20 23:05 gandhis1

I'm getting this StackOverflow on WriteRecords as well.

Simplest class:

public class ReconcileRow
{
    public int RegistrationId => Registration.Id;

    [Ignore]
    public Registration Registration { get; set; }
}

The Registration object has properties that have references to the Registration. Regardless of whether I use the [Ignore] attribute, or a ClassMap with Map(x => x.Registration).Ignore(), it causes a StackOverflow.

The confusing part is this version of the class ALSO causes a StackOverflow:

public class ReconcileRow
{
    public ReconcileRow(Registration reg)
    {
        RegistrationId = reg.Id;
    }

    public int RegistrationId { get; set; }
}

I don't see why it would need to reflect into the Registration class in order to write these records! Very bizarre to me..

DanielStout5 avatar May 26 '20 20:05 DanielStout5

Changing the writing to be like this:

        var csv = new CsvWriter(writer);
        csv.Configuration.IgnoreReferences = true;
        csv.WriteRecords(result);

Does work - but of course with this I can't use any reference types.

DanielStout5 avatar May 27 '20 12:05 DanielStout5

Parameterless constructor seemed to resolve this issue for me.

e4stwood avatar Sep 05 '23 11:09 e4stwood

Auto mapping without reference

        var csv = new CsvWriter(writer);
        csv.Configuration.IgnoreReferences = true;
        csv.WriteRecords(result);

Plus Parameterless constructor helped me solve the issue.

Thanks to e4stwood and DanielStout5.

UxxHans avatar Jul 01 '24 08:07 UxxHans

Similar to @zii-dmg the following xUnit test gives a stack overflow when it's trying to auto map properties.

Parameterless constructor doesn't help.

If you comment out one of the reference properties in Person the auto mapping will work.

The only workaround is to setup a ClassMap and manually map each property - ie don't call AutoMap.

using CsvHelper;
using System.Globalization;
using Xunit;

namespace CsvHelperTest
{
   public class DeviceInfo
   {
      public DeviceInfo() { }

      public int Id2 { get; set; }
   }

   public class Person
   {
      public Person() { }
      public int Id { get; set; }

      // If you comment out either of these 2 properties the Automapping works.
      public DeviceInfo DeviceInformation { get; set; }
      public Person AssociatePerson { get; set; }
   }

   public class UnitTest1
   {
      [Fact]
      public void ShouldMap_ButStackOverflows()
      {
         var l = new List<Person>
         {
            new Person { Id = 1 },
            new Person { Id = 2 }
         };

         using (var writer = new StreamWriter("d:\\temp\\csvhelper_file.csv"))
         using (var csv = new CsvWriter(writer, CultureInfo.InvariantCulture))
         {
            csv.WriteRecords(l);
         }
      }
   }
}

mpickers avatar Jul 30 '25 02:07 mpickers