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?