2

I have been experiencing a very hard to track down bug with some code that describes and parses patterns, much like RegExp. This issue is that one pattern operator throws null reference exceptions, and only in a very specific context, despite no obvious (to me) difference. Furthermore, I have <Nullable>enable</Nullable> set in the project, and the entire analyzer isn't complaining, so the analyzer doesn't see the issue either.

I have, in simplified form, the following type:

public class Pattern {
    private readonly Node Head;

    internal Pattern(Node Head) {
        if (Head is null) throw new ArgumentNullException("Head");
        this.Head = Head;
    }

    public static Pattern operator |(Pattern Left, Pattern Right) {
        if (Left is null) throw new ArgumentNullException("Left");
        if (Right is null) throw new ArgumentNullException("Right");
        return new Pattern(new Alternator(Left.Head, Right.Head));
    }

    public Result Consume(ref Source Source) => Head.Consume(ref Source);
}

Node is as follows:

public abstract class Node {
    public abstract Result Consume(ref Source Source);
}

And different types of Node are implemented with their own Consume() as well as any required private variables. Only one of these concrete types is giving me problems:

public sealed class Alternator : Node {
    private readonly Node Left;
    private readonly Node Right;

    internal Alternator(Node Left, Node Right) {
        if (Left is null) throw new ArgumentNullException("Left");
        this.Left = Left;
        if (Right is null) throw new ArgumentNullException("Right");
        this.Right = Right;
    }

    public override Result Consume(ref Source Source) {
         // This implementation is fine
    }
}

Then we have some readonly Pattern defined like the following:

public static class Control {
    public static readonly Pattern C0ControlCharacter = (Pattern)((Char) => (0x00 <= Char && <= 0x1F) || Char == 0x7F);

    public static readonly Pattern ControlCharacter = C0ControlCharacter | C1ControlCharacter;
}

Later calls like the following work fine

Control.ControlCharacter.Consume(Source);

So we can safely say the | operator for constructing Alternator of two Pattern into a Pattern works fine.

However, what seems to be similar code fails:

public static partial class Latin {
    public static readonly Pattern BasicLetter = BasicLowercase | BasicUppercase;

    public static readonly Pattern BasicLowercase = (Pattern)((Char) => 0x61 <= Char && Char <= 0x7A);
}

and then calling it like:

Latin.BasicLowercase.Consume(Source);

Notice how BasicLowercase is clearly not an Alternator and doesn't use |. The exact error I get is:

Test method Tests.PatternTests.BasicLowercaseTests threw exception: 
        System.TypeInitializationException: The type initializer for 'System.Text.Patterns.Latin' threw an exception. ---> System.ArgumentNullException: Value cannot be null.
        Parameter name: Left

with the stack trace:

at Pattern.op_BitwiseOr(Pattern Left, Pattern Right)…
at Latin_cctor() in LatinBasic.cs...

These problems occur throughout the entire Latin class, so I thought maybe it had to do with it being partial, so I removed that and gave each component unique names, but that didn't change anything.

I have no idea how to debug this because what it says is happening doesn't make any sense to me. How can the stack trace show operations that aren't even being called at that point?

EylM
  • 5,967
  • 2
  • 16
  • 28
Patrick Kelly
  • 633
  • 5
  • 22
  • 6
    Pretty sure the fields are initialized in order, so move your aggregate fields after the components that make them up. – Dan Aug 01 '19 at 19:02
  • 1
    Please [edit] your question to include the full source code you have as a [mcve], which can be compiled and tested by others. It should throw the TypeInitializationException you get. – Progman Aug 01 '19 at 19:05

1 Answers1

1

Dan was correct. I was thinking field order didn't matter because the order methods are present doesn't matter. However because these are static fields, the order does.

Patrick Kelly
  • 633
  • 5
  • 22