From be643fe6c8773c56df5a60e2339bfe9b395a3ab2 Mon Sep 17 00:00:00 2001 From: Thomas Clegg Date: Mon, 24 Aug 2026 18:20:11 -0500 Subject: [PATCH 1/2] Guard PhoneNumberMatcher.allNumberGroupsRemainGrouped against indexOf(-1) for the country-code skip When the country calling code isn't a literal substring of the candidate text, StringBuilder.indexOf returns -1 and fromIndex became -1 + countryCode.length() instead of a proper "not found" value, silently desyncing the subsequent group-matching loop. Guard it the same way the check one line below already guards its own indexOf call. --- .../i18n/phonenumbers/PhoneNumberMatcher.java | 5 ++++- .../i18n/phonenumbers/PhoneNumberMatcherTest.java | 15 +++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/java/libphonenumber/src/com/google/i18n/phonenumbers/PhoneNumberMatcher.java b/java/libphonenumber/src/com/google/i18n/phonenumbers/PhoneNumberMatcher.java index b812551db0..0d130edf01 100644 --- a/java/libphonenumber/src/com/google/i18n/phonenumbers/PhoneNumberMatcher.java +++ b/java/libphonenumber/src/com/google/i18n/phonenumbers/PhoneNumberMatcher.java @@ -463,7 +463,10 @@ static boolean allNumberGroupsRemainGrouped(PhoneNumberUtil util, if (number.getCountryCodeSource() != CountryCodeSource.FROM_DEFAULT_COUNTRY) { // First skip the country code if the normalized candidate contained it. String countryCode = Integer.toString(number.getCountryCode()); - fromIndex = normalizedCandidate.indexOf(countryCode) + countryCode.length(); + int countryCodeIndex = normalizedCandidate.indexOf(countryCode); + if (countryCodeIndex >= 0) { + fromIndex = countryCodeIndex + countryCode.length(); + } } // Check each group of consecutive digits are not broken into separate groupings in the // {@code normalizedCandidate} string. diff --git a/java/libphonenumber/test/com/google/i18n/phonenumbers/PhoneNumberMatcherTest.java b/java/libphonenumber/test/com/google/i18n/phonenumbers/PhoneNumberMatcherTest.java index d31b33951b..a00255014d 100644 --- a/java/libphonenumber/test/com/google/i18n/phonenumbers/PhoneNumberMatcherTest.java +++ b/java/libphonenumber/test/com/google/i18n/phonenumbers/PhoneNumberMatcherTest.java @@ -77,6 +77,21 @@ public void testContainsMoreThanOneSlashInNationalNumber() throws Exception { assertTrue(PhoneNumberMatcher.containsMoreThanOneSlashInNationalNumber(number, candidate)); } + public void testAllNumberGroupsRemainGroupedCountryCodeNotInCandidate() throws Exception { + // Constructed case: the country code source claims the candidate began with an explicit + // country calling code, but the literal digits of that code ("44") do not actually occur in + // the candidate text. Previously this desynced fromIndex (indexOf returns -1, and -1 plus the + // country code's length was used as a starting position instead of being treated as "not + // found"), which made a grouping match that should succeed incorrectly return false. + PhoneNumber number = new PhoneNumber(); + number.setCountryCode(44); + number.setCountryCodeSource(CountryCodeSource.FROM_NUMBER_WITH_PLUS_SIGN); + StringBuilder candidate = new StringBuilder("020 7031 3000"); + String[] formattedNumberGroups = {"020", "7031", "3000"}; + assertTrue(PhoneNumberMatcher.allNumberGroupsRemainGrouped( + phoneUtil, number, candidate, formattedNumberGroups)); + } + /** See {@link PhoneNumberUtilTest#testParseNationalNumber()}. */ public void testFindNationalNumber() throws Exception { // same cases as in testParseNationalNumber From 435b132294a74b92055df4be1dd01de71bba42f3 Mon Sep 17 00:00:00 2001 From: Thomas Clegg Date: Mon, 24 Aug 2026 18:27:09 -0500 Subject: [PATCH 2/2] Apply the same indexOf/find(-1) guard to the C++ port Same bug as the Java fix, applied to AllNumberGroupsRemainGrouped in phonenumbermatcher.cc: std::string::find returns string::npos on a miss, and the unguarded "+ country_code.size()" wrapped that around via unsigned overflow into a small, wrong, non-negative from_index instead of leaving it as "not found." AllNumberGroupsRemainGrouped is in an anonymous namespace here, so unlike the Java package-private method it isn't reachable from the test binary for a direct unit test; JS has no PhoneNumberMatcher (find-numbers-in-text) implementation at all, so no fix is needed there. --- cpp/src/phonenumbers/phonenumbermatcher.cc | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/cpp/src/phonenumbers/phonenumbermatcher.cc b/cpp/src/phonenumbers/phonenumbermatcher.cc index 794c3c682e..87e99004cb 100644 --- a/cpp/src/phonenumbers/phonenumbermatcher.cc +++ b/cpp/src/phonenumbers/phonenumbermatcher.cc @@ -123,7 +123,10 @@ bool AllNumberGroupsRemainGrouped( if (number.country_code_source() != PhoneNumber::FROM_DEFAULT_COUNTRY) { // First skip the country code if the normalized candidate contained it. string country_code = SimpleItoa(number.country_code()); - from_index = normalized_candidate.find(country_code) + country_code.size(); + size_t country_code_index = normalized_candidate.find(country_code); + if (country_code_index != string::npos) { + from_index = country_code_index + country_code.size(); + } } // Check each group of consecutive digits are not broken into separate // groupings in the normalized_candidate string.