Skip to content

Fix MA0113 NullReferenceException and named-argument false negatives - #1382

Merged
meziantou merged 1 commit into
mainfrom
feature/datetime-analyzer-null-ref-653867
Sep 6, 2026
Merged

meziantou merged 1 commit into
mainfrom
feature/datetime-analyzer-null-ref-653867

Conversation

@meziantou

Copy link
Copy Markdown
Owner

The crash

_ = new DateTime(1970, 1, 1, 0, 0, 0, null);

binds to DateTime(int, int, int, int, int, int, Calendar). The 7-argument branch of UseDateTimeUnixEpochAnalyzer passed that null Calendar argument to IsDateTimeKindUtc, which did:

return argument.Value.TryGetConstantValue(out var value, cancellationToken) && (DateTimeKind)value! == DateTimeKind.Utc;

A null literal has ConstantValue.HasValue == true with a null value, so unboxing it to the DateTimeKind struct threw a NullReferenceException. The ! suppressed the only compile-time signal that value was nullable.

An unhandled analyzer exception surfaces as AD0001 and disables MA0113 for the rest of the compilation; in the IDE it recurs while the user types. The trigger is ordinary, compiling C#.

IsDateTimeKindUtc now verifies the argument's parameter type is System.DateTimeKind before looking at the constant, and pattern-matches it (value is int intValue) instead of unboxing. Either check alone stops the crash; both are cheap.

The positional-argument assumption

The rule also indexed IObjectCreationOperation.Arguments positionally, assuming parameter order. Arguments is in source order, so this was a real false-negative source rather than a theoretical one — any named-argument reordering silently defeated the rule:

_ = new DateTime(day: 1, month: 1, year: 1970); // MA0113 was not reported

ArgumentsEquals now indexes the expected values by IParameterSymbol.Ordinal and requires every leading parameter to have been matched. The arguments the rule inspects on their own — the DateTimeKind, the TimeSpan offset, and the DateTime.UnixEpoch reference — are looked up by ordinal through a new GetArgument helper instead of Arguments[n].

As a side effect, trailing parameters the rule does not model (the Calendar of DateTimeOffset(..., Calendar, TimeSpan)) are now skipped by ordinal rather than compared against an int, so they can neither crash nor accidentally match.

Tests

8 cases added to UseDateTimeUnixEpochAnalyzerTests: the null and non-null Calendar overloads for both DateTime and DateTimeOffset, and named/reordered arguments in both the match and the non-match direction.

I ran the new tests against the unfixed analyzer first to confirm they are meaningful — 4 failed, including the AD0001 / NullReferenceException on new DateTime(1970, 1, 1, 0, 0, 0, null) and three missed diagnostics on reordered named arguments.

With the fix, all 32 tests in the class pass on Roslyn 4.8, 4.14, 5.0, 5.6 and 5.9, and the full 3884-test default-version suite passes. dotnet run --project src/DocumentationGenerator produced no markdown changes, as expected for a behavior-only fix.

`new DateTime(1970, 1, 1, 0, 0, 0, null)` binds to the
`DateTime(int, int, int, int, int, int, Calendar)` overload. The 7-argument
branch passed that `null` `Calendar` argument to `IsDateTimeKindUtc`, which
unboxed the constant with `(DateTimeKind)value!`. A `null` literal has a
constant value, so the unboxing threw a `NullReferenceException`, surfacing as
`AD0001` and disabling the rule for the rest of the compilation.
`IsDateTimeKindUtc` now checks that the parameter type is `System.DateTimeKind`
and pattern-matches the constant instead of unboxing it.

The rule also assumed `IObjectCreationOperation.Arguments` is in parameter
order, while it is in source order, so any named-argument reordering such as
`new DateTime(day: 1, month: 1, year: 1970)` silently defeated the rule.
`ArgumentsEquals` now indexes the expected values by `IParameterSymbol.Ordinal`
and requires every leading parameter to be matched, and the arguments the rule
inspects on their own (`DateTimeKind`, the `TimeSpan` offset and
`DateTime.UnixEpoch`) are looked up by ordinal. Trailing parameters that the
rule does not model, such as the `Calendar` of
`DateTimeOffset(..., Calendar, TimeSpan)`, are skipped instead of being
compared to an `int`.
@meziantou
meziantou merged commit 6c31e2e into main Sep 6, 2026
13 checks passed
@meziantou
meziantou deleted the feature/datetime-analyzer-null-ref-653867 branch September 6, 2026 02:50
This was referenced Sep 6, 2026
This was referenced Sep 17, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant