From f0d70f337cf079958e2cacca509bf7051edde288 Mon Sep 17 00:00:00 2001 From: Michael Nebel Date: Wed, 7 Jan 2026 15:05:27 +0100 Subject: [PATCH 1/4] C#: Add null conditional tests. --- .../nullconditional/NullConditional.cs | 32 +++++++++++++++++++ .../nullconditional/maybenull.expected | 2 ++ .../nullconditional/maybenull.ql | 6 ++++ .../nullconditional/nullconditional.expected | 12 +++++++ .../nullconditional/nullconditional.ql | 5 +++ .../library-tests/nullconditional/options | 2 ++ 6 files changed, 59 insertions(+) create mode 100644 csharp/ql/test/library-tests/nullconditional/NullConditional.cs create mode 100644 csharp/ql/test/library-tests/nullconditional/maybenull.expected create mode 100644 csharp/ql/test/library-tests/nullconditional/maybenull.ql create mode 100644 csharp/ql/test/library-tests/nullconditional/nullconditional.expected create mode 100644 csharp/ql/test/library-tests/nullconditional/nullconditional.ql create mode 100644 csharp/ql/test/library-tests/nullconditional/options diff --git a/csharp/ql/test/library-tests/nullconditional/NullConditional.cs b/csharp/ql/test/library-tests/nullconditional/NullConditional.cs new file mode 100644 index 000000000000..04392fb4e626 --- /dev/null +++ b/csharp/ql/test/library-tests/nullconditional/NullConditional.cs @@ -0,0 +1,32 @@ +using System; + +public class C +{ + public int Field; + + public string Property { get; set; } = ""; + + public void M(C c, int[] numbers, bool b) + { + // Get values. + var fieldValue = c?.Field; + var propertyValue = c?.Property; + var n = numbers?[0]; + + // Set values + c?.Field = 42; + c?.Property = "Hello"; + numbers?[0] = 7; + + // Set values using operators + c?.Field -= 1; + c?.Property += " World"; + + // Using the return of an assignment + var x = c?.Field = 10; + + // Using in a conditional expression + int? maybenull = 0; + maybenull = (b ? c : null)?.Field; + } +} diff --git a/csharp/ql/test/library-tests/nullconditional/maybenull.expected b/csharp/ql/test/library-tests/nullconditional/maybenull.expected new file mode 100644 index 000000000000..d506cbbece69 --- /dev/null +++ b/csharp/ql/test/library-tests/nullconditional/maybenull.expected @@ -0,0 +1,2 @@ +| NullConditional.cs:30:22:30:33 | ... ? ... : ... | +| NullConditional.cs:30:30:30:33 | null | diff --git a/csharp/ql/test/library-tests/nullconditional/maybenull.ql b/csharp/ql/test/library-tests/nullconditional/maybenull.ql new file mode 100644 index 000000000000..a11fd2a9151c --- /dev/null +++ b/csharp/ql/test/library-tests/nullconditional/maybenull.ql @@ -0,0 +1,6 @@ +import csharp +import semmle.code.csharp.dataflow.Nullness + +from MaybeNullExpr mne +where mne.getFile().getBaseName() = "NullConditional.cs" +select mne diff --git a/csharp/ql/test/library-tests/nullconditional/nullconditional.expected b/csharp/ql/test/library-tests/nullconditional/nullconditional.expected new file mode 100644 index 000000000000..80d51ab9361d --- /dev/null +++ b/csharp/ql/test/library-tests/nullconditional/nullconditional.expected @@ -0,0 +1,12 @@ +| NullConditional.cs:12:26:12:33 | access to field Field | +| NullConditional.cs:13:29:13:39 | access to property Property | +| NullConditional.cs:14:17:14:27 | access to array element | +| NullConditional.cs:17:9:17:16 | access to field Field | +| NullConditional.cs:18:9:18:19 | access to property Property | +| NullConditional.cs:19:9:19:19 | access to array element | +| NullConditional.cs:22:9:22:16 | access to field Field | +| NullConditional.cs:22:9:22:16 | access to field Field | +| NullConditional.cs:23:9:23:19 | access to property Property | +| NullConditional.cs:23:9:23:19 | access to property Property | +| NullConditional.cs:26:17:26:24 | access to field Field | +| NullConditional.cs:30:21:30:41 | access to field Field | diff --git a/csharp/ql/test/library-tests/nullconditional/nullconditional.ql b/csharp/ql/test/library-tests/nullconditional/nullconditional.ql new file mode 100644 index 000000000000..77735091f79f --- /dev/null +++ b/csharp/ql/test/library-tests/nullconditional/nullconditional.ql @@ -0,0 +1,5 @@ +import csharp + +from QualifiableExpr e +where e.isConditional() +select e diff --git a/csharp/ql/test/library-tests/nullconditional/options b/csharp/ql/test/library-tests/nullconditional/options new file mode 100644 index 000000000000..77b22963f5c8 --- /dev/null +++ b/csharp/ql/test/library-tests/nullconditional/options @@ -0,0 +1,2 @@ +semmle-extractor-options: /nostdlib /noconfig +semmle-extractor-options: --load-sources-from-project:${testdir}/../../resources/stubs/_frameworks/Microsoft.NETCore.App/Microsoft.NETCore.App.csproj From 3d9c0cbb9b2f3d36f7d782b0b291d447eb31c9e2 Mon Sep 17 00:00:00 2001 From: Michael Nebel Date: Wed, 7 Jan 2026 15:22:11 +0100 Subject: [PATCH 2/4] C#: Take null conditional assignments into account in MaybeNullExpr. --- csharp/ql/lib/semmle/code/csharp/dataflow/Nullness.qll | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/csharp/ql/lib/semmle/code/csharp/dataflow/Nullness.qll b/csharp/ql/lib/semmle/code/csharp/dataflow/Nullness.qll index 6a211e71f456..756fd6a4e3a0 100644 --- a/csharp/ql/lib/semmle/code/csharp/dataflow/Nullness.qll +++ b/csharp/ql/lib/semmle/code/csharp/dataflow/Nullness.qll @@ -43,6 +43,12 @@ private Expr maybeNullExpr(Expr reason) { ) or result.(NullCoalescingExpr).getRightOperand() = maybeNullExpr(reason) + or + result = + any(QualifiableExpr qe | + qe.isConditional() and + qe.getQualifier() = maybeNullExpr(reason) + ) } /** An expression that may be `null`. */ From 8bb7ed4d58f6a69bcb960d781cfacc56796f6aa9 Mon Sep 17 00:00:00 2001 From: Michael Nebel Date: Wed, 7 Jan 2026 15:23:52 +0100 Subject: [PATCH 3/4] C#: Update test expected output. --- csharp/ql/test/library-tests/nullconditional/maybenull.expected | 2 ++ 1 file changed, 2 insertions(+) diff --git a/csharp/ql/test/library-tests/nullconditional/maybenull.expected b/csharp/ql/test/library-tests/nullconditional/maybenull.expected index d506cbbece69..06c5c376b8e6 100644 --- a/csharp/ql/test/library-tests/nullconditional/maybenull.expected +++ b/csharp/ql/test/library-tests/nullconditional/maybenull.expected @@ -1,2 +1,4 @@ +| NullConditional.cs:30:9:30:41 | ... = ... | +| NullConditional.cs:30:21:30:41 | access to field Field | | NullConditional.cs:30:22:30:33 | ... ? ... : ... | | NullConditional.cs:30:30:30:33 | null | From 47231378de328433f7a99cc1dcc7b2a3ea5da2e1 Mon Sep 17 00:00:00 2001 From: Michael Nebel Date: Wed, 7 Jan 2026 15:29:16 +0100 Subject: [PATCH 4/4] C#: Add change note. --- .../change-notes/2026-01-06-null-conditional-assignments.md | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 csharp/ql/lib/change-notes/2026-01-06-null-conditional-assignments.md diff --git a/csharp/ql/lib/change-notes/2026-01-06-null-conditional-assignments.md b/csharp/ql/lib/change-notes/2026-01-06-null-conditional-assignments.md new file mode 100644 index 000000000000..e9c5a94478a6 --- /dev/null +++ b/csharp/ql/lib/change-notes/2026-01-06-null-conditional-assignments.md @@ -0,0 +1,4 @@ +--- +category: minorAnalysis +--- +* The `MaybeNullExpr` class now takes null-conditional access (such as `?.` and `?[]`) into account when modeling potential null values.