From 78c4836900734693b36618f6b9d8ac98e87578f6 Mon Sep 17 00:00:00 2001 From: Andy Gocke Date: Fri, 20 Mar 2026 21:55:37 +0000 Subject: [PATCH] Fix circular init in AOT equality comparer causing NRE on Mono The defaultComparer field in AssertEqualityComparer (AOT path) eagerly creates AssertEqualityComparerAdapter, which triggers AssertEqualityComparer's static initializer. That initializer reads defaultComparer back via GetDefaultInnerComparer before the field has been assigned. On Mono/WASM this results in DefaultInnerComparer being permanently null, leading to NullReferenceException when comparing value types via IStructuralEquatable. Fix by creating a new instance on each call, matching the non-AOT reflection path behavior. Port of dotnet/arcade#16615 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- Sdk/AssertEqualityComparer_aot.cs | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/Sdk/AssertEqualityComparer_aot.cs b/Sdk/AssertEqualityComparer_aot.cs index 05e61d5c..6bfb1311 100644 --- a/Sdk/AssertEqualityComparer_aot.cs +++ b/Sdk/AssertEqualityComparer_aot.cs @@ -18,13 +18,18 @@ namespace Xunit.Sdk { partial class AssertEqualityComparer { - static readonly IEqualityComparer defaultComparer = new AssertEqualityComparerAdapter(new AssertEqualityComparer()); - + // Create a new instance each call (matching the non-AOT/reflection path behavior) + // rather than caching in a static field. A cached static field causes a circular + // static initialization dependency: the field initializer triggers + // AssertEqualityComparer's static initializer, which reads the field back + // via GetDefaultInnerComparer before it has been assigned. On Mono/WASM, this + // causes DefaultInnerComparer to be permanently null, leading to + // NullReferenceException when comparing value types via IStructuralEquatable. internal static IEqualityComparer GetDefaultComparer(Type _) => - defaultComparer; + new AssertEqualityComparerAdapter(new AssertEqualityComparer()); internal static IEqualityComparer GetDefaultInnerComparer(Type _) => - defaultComparer; + new AssertEqualityComparerAdapter(new AssertEqualityComparer()); } partial class AssertEqualityComparer