Skip to content

fix: NumberParseException instead of ArgumentOutOfRangeException on malformed RFC3966 phone-context ordering - #406

Merged
twcclegg merged 1 commit into
mainfrom
fix/rfc3966-phone-context-ordering
Aug 25, 2026
Merged

fix: NumberParseException instead of ArgumentOutOfRangeException on malformed RFC3966 phone-context ordering#406
twcclegg merged 1 commit into
mainfrom
fix/rfc3966-phone-context-ordering

Conversation

@twcclegg

Copy link
Copy Markdown
Owner

Summary

BuildNationalNumberForParsing assumed the "tel:" prefix, if present anywhere in the input, always occurs before the ";phone-context=" marker. When it occurs after — e.g. ";phone-context=example.com;tel:1234"indexOfPhoneContext - indexOfNationalNumber goes negative, and StringBuilder.Append(string, int, int) throws an unchecked ArgumentOutOfRangeException straight out of the public PhoneNumberUtil.Parse API, instead of the documented NumberParseException.

Found via code review of #401 (narrowing PhoneNumbers.Extensions.PhoneNumber.TryParse's catch to catch (NumberParseException)) — that narrowing is correct, but it stopped silently swallowing this pre-existing bug. It's reachable directly through PhoneNumberUtil.Parse with no dependency on #401, and is worse combined with #405's new [PhoneNumber] ValidationAttribute: a ValidationAttribute.IsValid is conventionally expected to never throw on arbitrary/untrusted input, so this could crash model validation on a crafted string.

Fix: guard the ordering assumption and throw NumberParseException(NOT_A_NUMBER, ...) instead, matching the error this method already throws just above for other malformed phone-context input.

Recommend merging this before/alongside #401 and #405.

Test plan

  • Reproduced standalone: PhoneNumberUtil.GetInstance().Parse(";phone-context=example.com;tel:1234", null) threw ArgumentOutOfRangeException before this fix.
  • Added a regression case to the existing TestFailedParseOnInvalidNumbers (VerifyFailure(";phone-context=example.com;tel:1234", RegionCode.ZZ, ErrorType.NOT_A_NUMBER)), alongside the other phone-context failure cases.
  • dotnet build csharp --no-restore — 0 warnings/errors
  • dotnet test csharp/PhoneNumbers.slnx — 864 tests passed (net8.0 + net10.0)

…n on malformed RFC3966 phone-context ordering

BuildNationalNumberForParsing assumed the "tel:" prefix, if present,
always occurs before the ";phone-context=" marker. When it occurs
after (e.g. ";phone-context=example.com;tel:1234"), indexOfPhoneContext
- indexOfNationalNumber went negative and leaked an unchecked
ArgumentOutOfRangeException from StringBuilder.Append(string, int, int)
out of the public Parse API, instead of the documented
NumberParseException.

Found via code review of a separate change that narrowed a
catch-all in PhoneNumbers.Extensions.PhoneNumber.TryParse to
catch (NumberParseException) specifically — that narrowing was correct,
but it stopped silently swallowing this pre-existing bug.
@codecov

codecov Bot commented Aug 24, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 77.27%. Comparing base (a40ff86) to head (ba0a42d).

Additional details and impacted files
@@            Coverage Diff             @@
##             main     #406      +/-   ##
==========================================
+ Coverage   77.26%   77.27%   +0.01%     
==========================================
  Files          39       39              
  Lines        4548     4551       +3     
  Branches     1129     1130       +1     
==========================================
+ Hits         3514     3517       +3     
  Misses        783      783              
  Partials      251      251              

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@github-actions

Copy link
Copy Markdown

📊 Benchmark Results

Commit: ba0a42d · Full run · Linux ubuntu-24.04-arm

Both sides were measured on the same runner in the same job, so the numbers are
comparable. Treat sub-percent differences as noise.

PR branch

BenchmarkDotNet v0.15.8, Linux Ubuntu 24.04.4 LTS (Noble Numbat)
Neoverse-N2, 4 physical cores
.NET SDK 10.0.400
  [Host]    : .NET 10.0.11 (10.0.11, 10.0.1126.37416), Arm64 RyuJIT armv8.0-a
  .NET 10.0 : .NET 10.0.11 (10.0.11, 10.0.1126.37416), Arm64 RyuJIT armv8.0-a

Job=.NET 10.0  Runtime=.NET 10.0  

Method PhoneNumberCount Mean Error StdDev Gen0 Allocated
InputDigitPerKeystroke 1000 4.387 ms 0.0119 ms 0.0111 ms 54.6875 3.87 MB

BenchmarkDotNet v0.15.8, Linux Ubuntu 24.04.4 LTS (Noble Numbat)
Neoverse-N2, 4 physical cores
.NET SDK 10.0.400
  [Host]     : .NET 10.0.11 (10.0.11, 10.0.1126.37416), Arm64 RyuJIT armv8.0-a
  Job-AMQORM : .NET 10.0.11 (10.0.11, 10.0.1126.37416), Arm64 RyuJIT armv8.0-a

Runtime=.NET 10.0  InvocationCount=1  IterationCount=20  
LaunchCount=1  RunStrategy=ColdStart  UnrollFactor=1  
WarmupCount=1  

Method Mean Error StdDev Allocated
CreateInstance 391.3 μs 106.2 μs 122.3 μs 119.48 KB
CreateInstanceAndLoadAllRegions 7,048.4 μs 367.3 μs 423.0 μs 1620.34 KB
FirstRegionLookup 452.0 μs 139.0 μs 160.1 μs 124.54 KB

BenchmarkDotNet v0.15.8, Linux Ubuntu 24.04.4 LTS (Noble Numbat)
Neoverse-N2, 4 physical cores
.NET SDK 10.0.400
  [Host]    : .NET 10.0.11 (10.0.11, 10.0.1126.37416), Arm64 RyuJIT armv8.0-a
  .NET 10.0 : .NET 10.0.11 (10.0.11, 10.0.1126.37416), Arm64 RyuJIT armv8.0-a

Job=.NET 10.0  Runtime=.NET 10.0  

Method PhoneNumberCount Mean Error StdDev Gen0 Allocated
ExtractPossibleNumber_CleanInput 1000 20.91 μs 0.014 μs 0.013 μs - -
ExtractPossibleNumber_WithLeadingJunk 1000 38.06 μs 0.064 μs 0.060 μs 0.6714 48360 B

BenchmarkDotNet v0.15.8, Linux Ubuntu 24.04.4 LTS (Noble Numbat)
Neoverse-N2, 4 physical cores
.NET SDK 10.0.400
  [Host]    : .NET 10.0.11 (10.0.11, 10.0.1126.37416), Arm64 RyuJIT armv8.0-a
  .NET 10.0 : .NET 10.0.11 (10.0.11, 10.0.1126.37416), Arm64 RyuJIT armv8.0-a

Job=.NET 10.0  Runtime=.NET 10.0  

Method PhoneNumberCount Mean Error StdDev Gen0 Allocated
FindNumbers_Valid 100 144.3 μs 0.26 μs 0.23 μs 0.9766 70.71 KB
FindNumbers_StrictGrouping 100 311.5 μs 0.79 μs 0.74 μs 1.4648 123.99 KB

BenchmarkDotNet v0.15.8, Linux Ubuntu 24.04.4 LTS (Noble Numbat)
Neoverse-N2, 4 physical cores
.NET SDK 10.0.400
  [Host]    : .NET 10.0.11 (10.0.11, 10.0.1126.37416), Arm64 RyuJIT armv8.0-a
  .NET 10.0 : .NET 10.0.11 (10.0.11, 10.0.1126.37416), Arm64 RyuJIT armv8.0-a

Job=.NET 10.0  Runtime=.NET 10.0  

Method PhoneNumberCount Mean Error StdDev Gen0 Allocated
GetDescriptionForNumber 1000 1,466.17 μs 5.438 μs 4.821 μs 1.9531 196.59 KB
GetDisplayCountry 1000 18.49 μs 0.029 μs 0.024 μs 0.0916 7.56 KB

BenchmarkDotNet v0.15.8, Linux Ubuntu 24.04.4 LTS (Noble Numbat)
Neoverse-N2, 4 physical cores
.NET SDK 10.0.400
  [Host]    : .NET 10.0.11 (10.0.11, 10.0.1126.37416), Arm64 RyuJIT armv8.0-a
  .NET 10.0 : .NET 10.0.11 (10.0.11, 10.0.1126.37416), Arm64 RyuJIT armv8.0-a

Job=.NET 10.0  Runtime=.NET 10.0  

Method PhoneNumberCount Mean Error StdDev Gen0 Allocated
ParseValidateAndFormatPhoneNumbers 1000 2,548.0 μs 5.59 μs 5.23 μs 7.8125 577.71 KB
ParseOnly 1000 438.1 μs 0.83 μs 0.77 μs 4.8828 348.13 KB
ParseNationalFormat 1000 775.5 μs 1.30 μs 1.15 μs 5.8594 431.36 KB
ParseWithExtension 1000 1,128.9 μs 1.97 μs 1.64 μs 13.6719 957.38 KB
ValidateOnly 1000 743.5 μs 1.14 μs 1.01 μs - 41.11 KB
FormatOnly 1000 1,089.7 μs 1.82 μs 1.52 μs 1.9531 187.92 KB
PR base

BenchmarkDotNet v0.15.8, Linux Ubuntu 24.04.4 LTS (Noble Numbat)
Neoverse-N2, 4 physical cores
.NET SDK 10.0.400
  [Host]    : .NET 10.0.11 (10.0.11, 10.0.1126.37416), Arm64 RyuJIT armv8.0-a
  .NET 10.0 : .NET 10.0.11 (10.0.11, 10.0.1126.37416), Arm64 RyuJIT armv8.0-a

Job=.NET 10.0  Runtime=.NET 10.0  

Method PhoneNumberCount Mean Error StdDev Gen0 Allocated
InputDigitPerKeystroke 1000 4.381 ms 0.0103 ms 0.0091 ms 54.6875 3.87 MB

BenchmarkDotNet v0.15.8, Linux Ubuntu 24.04.4 LTS (Noble Numbat)
Neoverse-N2, 4 physical cores
.NET SDK 10.0.400
  [Host]     : .NET 10.0.11 (10.0.11, 10.0.1126.37416), Arm64 RyuJIT armv8.0-a
  Job-AMQORM : .NET 10.0.11 (10.0.11, 10.0.1126.37416), Arm64 RyuJIT armv8.0-a

Runtime=.NET 10.0  InvocationCount=1  IterationCount=20  
LaunchCount=1  RunStrategy=ColdStart  UnrollFactor=1  
WarmupCount=1  

Method Mean Error StdDev Allocated
CreateInstance 385.5 μs 111.7 μs 128.6 μs 119.48 KB
CreateInstanceAndLoadAllRegions 7,026.2 μs 393.0 μs 452.6 μs 1620.34 KB
FirstRegionLookup 454.5 μs 149.0 μs 171.6 μs 124.54 KB

BenchmarkDotNet v0.15.8, Linux Ubuntu 24.04.4 LTS (Noble Numbat)
Neoverse-N2, 4 physical cores
.NET SDK 10.0.400
  [Host]    : .NET 10.0.11 (10.0.11, 10.0.1126.37416), Arm64 RyuJIT armv8.0-a
  .NET 10.0 : .NET 10.0.11 (10.0.11, 10.0.1126.37416), Arm64 RyuJIT armv8.0-a

Job=.NET 10.0  Runtime=.NET 10.0  

Method PhoneNumberCount Mean Error StdDev Gen0 Allocated
ExtractPossibleNumber_CleanInput 1000 20.92 μs 0.016 μs 0.015 μs - -
ExtractPossibleNumber_WithLeadingJunk 1000 38.57 μs 0.054 μs 0.051 μs 0.6714 48360 B

BenchmarkDotNet v0.15.8, Linux Ubuntu 24.04.4 LTS (Noble Numbat)
Neoverse-N2, 4 physical cores
.NET SDK 10.0.400
  [Host]    : .NET 10.0.11 (10.0.11, 10.0.1126.37416), Arm64 RyuJIT armv8.0-a
  .NET 10.0 : .NET 10.0.11 (10.0.11, 10.0.1126.37416), Arm64 RyuJIT armv8.0-a

Job=.NET 10.0  Runtime=.NET 10.0  

Method PhoneNumberCount Mean Error StdDev Gen0 Allocated
FindNumbers_Valid 100 141.3 μs 0.33 μs 0.31 μs 0.9766 70.71 KB
FindNumbers_StrictGrouping 100 314.8 μs 0.64 μs 0.60 μs 1.4648 123.99 KB

BenchmarkDotNet v0.15.8, Linux Ubuntu 24.04.4 LTS (Noble Numbat)
Neoverse-N2, 4 physical cores
.NET SDK 10.0.400
  [Host]    : .NET 10.0.11 (10.0.11, 10.0.1126.37416), Arm64 RyuJIT armv8.0-a
  .NET 10.0 : .NET 10.0.11 (10.0.11, 10.0.1126.37416), Arm64 RyuJIT armv8.0-a

Job=.NET 10.0  Runtime=.NET 10.0  

Method PhoneNumberCount Mean Error StdDev Gen0 Allocated
GetDescriptionForNumber 1000 1,432.58 μs 4.543 μs 4.250 μs 1.9531 196.59 KB
GetDisplayCountry 1000 18.59 μs 0.024 μs 0.022 μs 0.0916 7.56 KB

BenchmarkDotNet v0.15.8, Linux Ubuntu 24.04.4 LTS (Noble Numbat)
Neoverse-N2, 4 physical cores
.NET SDK 10.0.400
  [Host]    : .NET 10.0.11 (10.0.11, 10.0.1126.37416), Arm64 RyuJIT armv8.0-a
  .NET 10.0 : .NET 10.0.11 (10.0.11, 10.0.1126.37416), Arm64 RyuJIT armv8.0-a

Job=.NET 10.0  Runtime=.NET 10.0  

Method PhoneNumberCount Mean Error StdDev Gen0 Allocated
ParseValidateAndFormatPhoneNumbers 1000 2,619.0 μs 12.96 μs 11.49 μs 7.8125 577.71 KB
ParseOnly 1000 438.7 μs 0.88 μs 0.74 μs 4.8828 348.13 KB
ParseNationalFormat 1000 776.0 μs 1.53 μs 1.44 μs 5.8594 431.36 KB
ParseWithExtension 1000 1,115.3 μs 1.80 μs 1.59 μs 13.6719 957.38 KB
ValidateOnly 1000 776.7 μs 8.11 μs 7.59 μs - 41.11 KB
FormatOnly 1000 1,073.6 μs 3.05 μs 2.85 μs 1.9531 187.92 KB

@twcclegg
twcclegg merged commit 1f66a10 into main Aug 25, 2026
8 checks passed
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