Skip to content

[Bug] NullReferenceException in MutantPlacer.PlaceStatementControlledMutations when a constructor declares an out variable in its base(...) initializer #3810

Description

@starbuck251

Describe the bug

When a constructor declares an out variable (out var x) inside its base(...) initializer, Stryker crashes with an unhandled AggregateException / NullReferenceException while injecting mutations.

The process aborts (exit code 134 on macOS), so no report is produced and the entire run is lost — even when only a single file in the solution contains the construct.

To Reproduce

A class library plus an xUnit test project. Library code (BCL only, no third-party types):

namespace Repro;

public abstract class BaseQuery
{
    protected BaseQuery(bool a) => A = a;

    public bool A { get; }
}

public class DerivedQuery : BaseQuery
{
    public DerivedQuery(string? value)
        : base(int.TryParse(value, out var parsed) && parsed == 1)
    {
    }
}

Tests (needed only so the initial test run passes, otherwise Stryker aborts before it reaches mutation):

using Repro;
using Xunit;

public class QueryTests
{
    [Fact]
    public void True_for_one() => Assert.True(new DerivedQuery("1").A);

    [Fact]
    public void False_for_other() => Assert.False(new DerivedQuery("2").A);
}

Run from the test project directory:

dotnet-stryker --project Repro.csproj --configuration Release

Actual behaviour

[ERR] An error occurred during the mutation test run
System.AggregateException: One or more errors occurred. (Object reference not set to an instance of an object.)
 ---> System.NullReferenceException: Object reference not set to an instance of an object.
   at Stryker.Core.Helpers.RoslynHelper.InjectMutation[T](T sourceNode, Mutation mutation) in /_/src/Stryker.Core/Stryker.Core/Helpers/RoslynHelper.cs:line 121
   at Stryker.Core.Mutants.MutationStore.<>c__DisplayClass18_0.<Inject>b__0(Mutant m) in /_/src/Stryker.Core/Stryker.Core/Mutants/MutationStore.cs:line 243
   at System.Linq.Enumerable.SelectListIterator`2.MoveNext()
   at System.Linq.Enumerable.Aggregate[TSource,TAccumulate](IEnumerable`1 source, TAccumulate seed, Func`3 func)
   at Stryker.Core.Mutants.MutantPlacer.PlaceStatementControlledMutations(StatementSyntax original, IEnumerable`1 mutants) in /_/src/Stryker.Core/Stryker.Core/Mutants/MutantPlacer.cs:line 110
   at Stryker.Core.Mutants.MutationStore.Inject(BlockSyntax mutatedNode, ExpressionSyntax originalNode, Boolean needReturn) in /_/src/Stryker.Core/Stryker.Core/Mutants/MutationStore.cs:line 242
   at Stryker.Core.Mutants.MutationContext.InjectMutations(BlockSyntax mutatedNode, ExpressionSyntax originalNode, Boolean needReturn) in /_/src/Stryker.Core/Stryker.Core/Mutants/MutationContext.cs:line 196
   at Stryker.Core.Mutants.CsharpNodeOrchestrators.BaseFunctionOrchestrator`1.InjectMutations(T sourceNode, T targetNode, SemanticModel semanticModel, MutationContext context) in /_/src/Stryker.Core/Stryker.Core/Mutants/CsharpNodeOrchestrators/BaseFunctionOrchestrator.cs:line 142

Followed by Unhandled exception and process abort.

Expected behavior

A construct Stryker cannot mutate should trigger the standard rollback for that node and let the run continue, rather than terminating the process and discarding the whole report. This matches the expectation stated in #3572.

Isolating the trigger

The out variable itself is fine — it is the position (inside the constructor initializer) that breaks. Both variants below were run against the same repro project:

Variant Result
: base(int.TryParse(value, out var parsed) && parsed == 1) crash, exit 134
: base(Parse(value) == 1) with private static int Parse(string? v) => int.TryParse(v, out var parsed) ? parsed : 0; exit 0, 100.00 % score

Also checked, and not required to trigger it:

  • Reading the out variable from a later argument of the same initializer. It crashes with a single argument and no cross-argument read.
  • Any specific enum or third-party type — int.TryParse is enough.

Desktop

  • OS: macOS (Darwin 25.5.0, Apple Silicon)
  • .NET SDK 10.0.301, target framework net10.0
  • Stryker: 4.16.0 (dotnet-stryker global tool — current latest, so there is no version to upgrade to)
  • Test framework: xUnit

Not yet reproduced on Linux or Windows — only macOS was available to test on.

Additional context

  • Related but not a duplicate: Delegate with Out Result Crashes Stryker During Mutation #3572 ("Delegate with Out Result Crashes Stryker During Mutation"). That one fails in DefaultInitializationEngine.InjectOutParametersInitializationRoslynHelper.BuildDefaultExpression; this one fails in MutantPlacer.PlaceStatementControlledMutationsRoslynHelper.InjectMutation. Same family — both surface as an NRE through BaseFunctionOrchestrator.InjectMutations, and both take the whole run down instead of rolling back — so a shared fix (defensive rollback at the orchestrator boundary) may address both.
  • Note for anyone hitting this: adding the file to mutate as an exclusion does not avoid the crash. Mutants are created and injected before FilePatternMutantFilter runs, so the crash happens first. The only workaround is to change the source.
  • --verbosity debug is useful for locating the offending file: the last Mutant N created lines before the error belong to the constructor that fails to inject.
  • Diagnosis, minimal repro, and trigger isolation in this report were produced with Claude Code (Opus model).

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions