fix: make national-prefix stripping's null handling provable - #459
Merged
Conversation
Codecov Report❌ Patch coverage is
❌ Your patch check has failed because the patch coverage (85.71%) is below the target coverage (90.00%). You can increase the patch coverage or adjust the target coverage. Additional details and impacted files@@ Coverage Diff @@
## main #459 +/- ##
==========================================
+ Coverage 87.68% 87.69% +0.01%
==========================================
Files 43 43
Lines 3889 3893 +4
Branches 992 993 +1
==========================================
+ Hits 3410 3414 +4
- Misses 276 277 +1
+ Partials 203 202 -1 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
Two CodeQL correctness alerts, both in code whose null handling was correct but not locally provable. cs/dereferenced-value-may-be-null, PhoneNumberUtil.cs:2614. The internal MaybeStripNationalPrefixAndCarrierCode overload accepts the number as a StringBuilder, as a string, or as both, and folded the two into a single `numberString?.Length ?? number?.Length ?? 0` before dereferencing `number` in `numberString ??= number.ToString()`. The dereference is in fact unreachable with a null `number` -- reaching it requires `numberString` to be null, which makes the length come from `number`, so a null `number` returns early -- but that argument spans two statements and a `??` chain, and every other use of `number` in the method is null-conditional, so the code reads as if the ToString() were the one unguarded access. Branch on which form was supplied instead, so the ToString() sits inside the arm where `number` is known to be non-null. Behaviour and the allocation-free early return are unchanged: the string is still only materialised after the length and national-prefix checks pass, and a null or empty number still returns false without touching `metadata`. cs/null-argument-to-equals, TestMetadataFilter.cs:892. The test asserted MetadataFilter.Equals(null) is false, which is a real contract worth holding, but the literal null argument reads as a comparison the compiler can decide. Pass the null through an `object?` local so the call is a run-time reference comparison, and extend the test to the rest of the contract the override implements: false for a non-MetadataFilter object, true for a separately built filter with an equal blacklist, false for one with a different blacklist. Also add TestMaybeStripNationalPrefixLeavesEmptyNumberAlone, covering the zero-length early return that the restructure touched, with and without a carrier code requested.
The widened equality test passed a string literal straight to Equals, which trips cs/equals-on-unrelated-types (error) - CodeQL sees a comparison between statically incomparable types, which is exactly the smell that rule exists to catch. Asserting that Equals rejects a foreign type is the one place the comparison is deliberate, so hold it in an object local, the same way the null case already does.
Both arms of the numberString/number branch repeated !metadata.HasNationalPrefixForParsing. Check it once up front - it does not depend on which form of the number the caller supplied - so each arm is left with only its own zero-length test. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01K1NSXom6AbfgVknJDtfR9C
twcclegg
force-pushed
the
fix/codeql-null-safety
branch
from
September 6, 2026 01:27
3274311 to
fc0b8db
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Both null-related CodeQL alerts, which are the only two open alerts that looked like latent bugs rather than style.
1.
cs/dereferenced-value-may-be-null—PhoneNumberUtil.cs:2614MaybeStripNationalPrefixAndCarrierCodeaccepts the number in either form and folded both into one length:It cannot actually throw today. Reaching the
ToString()requiresnumberString == null, and in that case the length comes fromnumber?.Length ?? 0, so a nullnumberyields 0 and returns early. Every caller was checked —PhoneNumberUtil.cs:2500, the public overload at:2597,PhoneNumberUtil.cs:2942, andPhoneNumberMatcher.cs:748— and none passes a nullnumberwith a non-nullnumberString.It still warranted a change: the safety argument spans two statements and a
??chain, while every other use ofnumberin the method is null-conditional (number?.Remove,number is not null). The file's own convention says "numbermay be null here" and this one line dereferences it bare.The method now branches on which form was supplied, so the dereference sits in the arm where
numberis provably non-null. Behavior is byte-identical, including the ordering that keepsmetadatauntouched for a null/empty number and keeps the string un-materialised until after the length andHasNationalPrefixForParsingchecks — the hot-path allocation concern AGENTS.md calls out.Since the change is behavior-preserving there is no test that fails before and passes after, and none is claimed.
TestMaybeStripNationalPrefixLeavesEmptyNumberAlonewas added to pin the zero-length early return (with and without a carrier code), which is the branch the restructure rewrote.2.
cs/null-argument-to-equals—TestMetadataFilter.cs:892TestEquals_WhenNull_ReturnsFalsedidnew MetadataFilter(...).Equals(null). The contract is real and worth asserting —MetadataFilter.Equalsisobj is not MetadataFilter other → false, and the test came fromaba077bb"add missing coverage over equals method" — but a literalnullargument reads as a compile-time constant comparison, which is what the query flags.The null now goes through an
object?local so it is a run-time reference comparison, and the test was widened to the rest of the override's contract: false for a non-MetadataFilter, true for a separately built filter with an equal blacklist, false forForLiteBuild(). Renamed accordingly. The assertion was kept, not deleted.Verification
dotnet build csharp/PhoneNumbers.slnx -p:TargetFrameworks=net10.0— 0 warnings, 0 errors (TreatWarningsAsErrorsis on).dotnet test csharp/PhoneNumbers.slnx -p:TargetFrameworks=net10.0— PhoneNumbers.Test 441 passed (440 before, +1 new), PhoneNumbers.Extensions.Test 51 passed. 492 total, zero failures, nothing skipped.No public API or parsing behavior changed.