ikorin24 / U8XmlParser

Extremely fast UTF-8 xml parser library
MIT License
95 stars 13 forks source link

AccessViolationException: Attempted to read or write protected memory. This is often an indication that other memory is corrupt. #36

Closed simon-curtis closed 1 year ago

simon-curtis commented 1 year ago

Describe the bug:

Intermittent bug when trying to get an attribute from a node while in an IEnumerable<XmlNode> from XmlNodeDescendantList

Our code for reference:

var type = _root.Descendants
    .FirstOrDefault(node => node.Name != "xs:attribute" && GetAttribute<string>(node, "name") == inheritedTypeName) 
        is { IsNull: false } type
            ? Visit(type) with { Name = name ?? inheritedTypeName }
            : null;

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static T? GetAttribute<T>(XmlNode? node, string name)
{
    if (node is null || !node.Value.TryFindAttribute(name, out var attribute)) // fails here
        return default;
    var value = attribute.Value.ToString();
    if (string.IsNullOrWhiteSpace(value) || value is "unbounded")
        return default;
    return (T?)TypeDescriptor.GetConverter(typeof(T)).ConvertFromString(value);
}

Environment:

library version: 1.6.1 .NET version: .NET6 6.0.13 OS: Windows10

Steps to Reproduce:

call node.TryFindAttribute(<string value>, out var attribute)

Expected behavior:

Return the attribute

Actual Behavior:

System.AccessViolationException: Attempted to read or write protected memory. This is often an indication that other memory is corrupt.
  at U8Xml.XmlAttributeEnumerableExtension.FindOrDefault[[U8Xml.XmlAttributeList, U8XmlParser, Version=1.6.1.0, Culture=neutral, PublicKeyToken=null]](U8Xml.XmlAttributeList, System.ReadOnlySpan`1<Byte>)
  at U8Xml.XmlAttributeEnumerableExtension.FindOrDefault[[U8Xml.XmlAttributeList, U8XmlParser, Version=1.6.1.0, Culture=neutral, PublicKeyToken=null]](U8Xml.XmlAttributeList, System.ReadOnlySpan`1<Char>)
  at U8Xml.XmlAttributeEnumerableExtension.FindOrDefault[[U8Xml.XmlAttributeList, U8XmlParser, Version=1.6.1.0, Culture=neutral, PublicKeyToken=null]](U8Xml.XmlAttributeList, System.String)
  at U8Xml.XmlAttributeEnumerableExtension.TryFind[[U8Xml.XmlAttributeList, U8XmlParser, Version=1.6.1.0, Culture=neutral, PublicKeyToken=null]](U8Xml.XmlAttributeList, System.String, U8Xml.XmlAttribute ByRef)
  at U8Xml.XmlNode.TryFindAttribute(System.String, U8Xml.XmlAttribute ByRef)
  at CMA.Common.Xml.Validation.Xsd.XsdParser.GetAttribute[[System.__Canon, System.Private.CoreLib, Version=6.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]](System.Nullable`1<U8Xml.XmlNode>, System.String)
  at CMA.Common.Xml.Validation.Xsd.XsdParser+<>c__DisplayClass12_0.<VisitAttribute>b__0(U8Xml.XmlNode)
  at System.Linq.Enumerable.TryGetFirst[[U8Xml.XmlNode, U8XmlParser, Version=1.6.1.0, Culture=neutral, PublicKeyToken=null]](System.Collections.Generic.IEnumerable`1<U8Xml.XmlNode>, System.Func`2<U8Xml.XmlNode,Boolean>, Boolean ByRef)
  at System.Linq.Enumerable.FirstOrDefault[[U8Xml.XmlNode, U8XmlParser, Version=1.6.1.0, Culture=neutral, PublicKeyToken=null]](System.Collections.Generic.IEnumerable`1<U8Xml.XmlNode>, System.Func`2<U8Xml.XmlNode,Boolean>)
simon-curtis commented 1 year ago

We have got around the above error by unwrapping the FirstOrDefault

BaseElement? type = null;
foreach (var childNode in _root.Descendants.TakeWhile(_ => type is null))
{
    if (childNode.Name == "xs:attribute"
        || GetAttribute<string>(childNode, "name") != inheritedTypeName
        || childNode.IsNull)
        continue;
    type = Visit(childNode) with { Name = name ?? inheritedTypeName };
}

And we have subsequently now hit another exception, from a seperate call, throwing in the XmlNodeList.FirstOrDefault method

public Option<XmlNode> FirstOrDefault(Func<XmlNode, bool> predicate)
{
    if (predicate == null)
        ThrowHelper.ThrowNullArg(nameof (predicate));
    foreach (XmlNode xmlNode in this)
    {
        if (predicate(xmlNode))
            return (Option<XmlNode>) ref xmlNode;
    }
    return Option<XmlNode>.Null;
}
simon-curtis commented 1 year ago

I have created my own FindNode method using boxing

private static Option<XmlNode> FindNode(IEnumerable<XmlNode> list, Func<StrongBox<XmlNode>, bool> predicate)
{
    foreach (var xmlNode in list.Where(xmlNode => predicate(new StrongBox<XmlNode>(xmlNode))))
        return (Option<XmlNode>) xmlNode;
    return Option<XmlNode>.Null;
}

unfortunately this didn't work either

Fatal error. System.AccessViolationException: Attempted to read or write protected memory. This is often an indication that other memory is corrupt.
   at System.SpanHelpers.SequenceEqual(Byte ByRef, Byte ByRef, UIntPtr)
   at U8Xml.XmlAttributeEnumerableExtension.FindOrDefault[[U8Xml.XmlAttributeList, U8XmlParser, Version=1.6.1.0, Culture=neutral, PublicKeyToken=null]](U8Xml.XmlAttributeList, System.ReadOnlySpan`1<Byte>)
   at U8Xml.XmlAttributeEnumerableExtension.FindOrDefault[[U8Xml.XmlAttributeList, U8XmlParser, Version=1.6.1.0, Culture=neutral, PublicKeyToken=null]](U8Xml.XmlAttributeList, System.ReadOnlySpan`1<Char>)
   at CMA.Common.Xml.Validation.Xsd.XsdParser+<>c__DisplayClass14_0.<VisitElement>b__1(System.Runtime.CompilerServices.StrongBox`1<U8Xml.XmlNode>)
   at CMA.Common.Xml.Validation.Xsd.XsdParser+<>c__DisplayClass15_0.<FindNode>b__0(U8Xml.XmlNode)
   at System.Linq.Enumerable+WhereEnumerableIterator`1[[U8Xml.XmlNode, U8XmlParser, Version=1.6.1.0, Culture=neutral, PublicKeyToken=null]].MoveNext()
simon-curtis commented 1 year ago

Going to try and specifically keep the GC from cleaning up xmlDoc

public static XsdTypeBinder FromText(string content)
{
    var xmlDoc = XmlParser.Parse(content);
    var rootNode = xmlDoc.Root;
    var parser = new XsdParser(rootNode);
    var elements = parser.Parse();
    GC.KeepAlive(xmlDoc); // <- new code
    return new XsdTypeBinder(elements);
}
ikorin24 commented 1 year ago

@simon-curtis Could you please share the entire code and the XML needed to reproduce this bug?

simon-curtis commented 1 year ago

Turns out it was a GC issue but much higher up in my code. I was using a stream that was being disposed. Wrapping it in using (x) { //... } fixed the issue