Почему сравнение двух классов .NET Framework с еще одним результатом приводит к исключению stackoverflow? - PullRequest
0 голосов
/ 28 февраля 2019

Проблема

В настоящее время я работаю над созданием приложения.В этом приложении я работал с сериализацией Func.Это как-то разбило мое приложение без исключения.

Авария без исключения заставила меня задуматься о происходящем wtf, поэтому я немного углубился и после некоторого копания наконец-то обнаружил, что где-то в Newtonsoft.Json a List.Containsпроисходит, что затем выполняет проверку на равенство для 2 свойств.

Очевидно, что в результате проверки на равенство получается бесконечный цикл, который вызывает исключение stackoverflow.

Воспроизведение проблемы только с C #

Expression<Func<string, int>> expr = (t) => t.Length;
Func<string, int> exprCompiled = expr.Compile();

var aa = exprCompiled.Method.Module;
var bb = exprCompiled.Method.Module.Assembly;

//This code results in either an infinite loop or a Stackoverflow Exception
var tempresult = aa.Equals(bb);

Console.WriteLine("This code is never executed");

Воспроизвести проблему с Newtonsoft.Json

Expression<Func<string, int>> expr = (t) => t.Length;
Func<string, int> exprCompiled = expr.Compile();

//This code results in either an infinite loop or a Stackoverflow Exception
var res = JsonConvert.SerializeObject(exprCompiled);

Console.WriteLine("This code is never executed");

Фактическая основная проблема

Выполнениенемного подробнее о том, как работает .NET framework. Думаю, проблема заключается в реализации внутреннего класса InternalAssemblyBuilder и внутренних классов InternalModuleBuilder.У них обоих есть переопределение метода Equals, например:

public override bool Equals(object obj)
{
    if (obj == null)
    {
        return false;
    }
    if (obj is InternalAssemblyBuilder)
    {
        return this == obj;
    }
    return obj.Equals(this);
}

Я думаю, это должно быть так:

public override bool Equals(object obj)
{
    if (obj == null)
    {
        return false;
    }
    if (obj is InternalAssemblyBuilder)
    {
        return this == obj;
    }
    return base.Equals(this); //changed obj to base
}

1 Ответ

0 голосов
/ 28 февраля 2019

Как указано в вашем вопросе Фактическая основная проблема , а также NineBerry в комментариях , реализациях Microsoft InternalAssemblyBuilder.Equals(object) иInternalModuleBuilder.Equals(object) кажется сломанным.В частности, в случае проверки равенства между объектом типа InternalAssemblyBuilder и объектом типа InternalModuleBuilder произойдет бесконечная рекурсия.

Чтобы обойти эту проблему, вы можете установить пользовательский IEqualityComparerна JsonSerializer.SettingsEqualityComparer, который заменяет вероятные реализации Equals() для этих типов.Ниже приведен один из таких примеров, использующий ссылочное равенство:

public class CustomJsonEqualityComparer : IEqualityComparer
{
    public static readonly CustomJsonEqualityComparer Instance = new CustomJsonEqualityComparer();

    // Use ImmutableHashSet in later .net versions
    static readonly HashSet<string> naughtyTypes = new HashSet<string>
    {
        "System.Reflection.Emit.InternalAssemblyBuilder",
        "System.Reflection.Emit.InternalModuleBuilder"
    };

    static readonly IEqualityComparer baseComparer = EqualityComparer<object>.Default;

    static bool HasBrokenEquals(Type type)
    {
        return naughtyTypes.Contains(type.FullName);
    }

    #region IEqualityComparer Members

    public bool Equals(object x, object y)
    {
        // Check reference equality
        if ((object)x == y)
            return true;
        // Check null
        else if ((object)x == null || (object)y == null)
            return false;

        var xType = x.GetType();
        if (xType != y.GetType())
            // Types should be identical.
            // Note this check alone might be sufficient to fix the problem.
            return false;

        if (xType.IsClass && !xType.IsPrimitive) // IsPrimitive check for performance
        {
            if (HasBrokenEquals(xType))
            {
                // These naughty types should ONLY be compared via reference equality -- which we have already done.
                // So return false
                return false;
            }
        }
        return baseComparer.Equals(x, y);
    }

    public int GetHashCode(object obj)
    {
        return baseComparer.GetHashCode(obj);
    }

    #endregion
}

Тогда вы будете использовать его следующим образом:

var settings = new JsonSerializerSettings
{
    EqualityComparer = CustomJsonEqualityComparer.Instance,
};
var json = JsonConvert.SerializeObject(exprCompiled, settings);

Примечания:

  • Вам может потребоваться настроить CustomJsonEqualityComparer.HasBrokenEquals(), если другие типы System.Reflection.Emit имеют аналогично испорченные реализации Equals().

  • Возможно, будет достаточно, чтобы два входящих объекта имели одинаковые *Значение 1039 * для GetType(), так как обнаруженные до сих пор поврежденные методы Equals() переполняют стек только в том случае, если сравниваются два разных типа , и оба имеют одинаковую ошибку.

  • Хотя я смог воспроизвести бесконечную рекурсию и убедиться, что она исправлена ​​с использованием некоторых макетов, я не смог подтвердить, что Json.NET действительно может сериализовать ваш Func<string, int> exprCompiled.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...