diff --git a/lib/src/matching.dart b/lib/src/matching.dart index fb37e19a..f2922695 100644 --- a/lib/src/matching.dart +++ b/lib/src/matching.dart @@ -658,15 +658,26 @@ class matching { if (rx_match == null) { break; } + // rx_match.start/index are relative to `pattern` (the substring), + // not to `password`. Treating them as absolute positions, and + // re-including the match's last character in the next substring + // (`... - 1`), can make `last_index` oscillate between two values + // forever for overlapping matches (e.g. two overlapping year-like + // digit runs against the recent_year regex), growing `matches` + // without bound until the process runs out of memory. Track the + // absolute offset explicitly and advance strictly past the + // matched token so the loop always makes forward progress. String? token = rx_match.group(0); - last_index = rx_match.start + rx_match.group(0)!.length - 1; + final int abs_start = last_index + rx_match.start; + final int abs_end = abs_start + token!.length - 1; matches.add(PasswordMatch() ..pattern = 'regex' ..token = token - ..i = rx_match.start - ..j = rx_match.start + rx_match.group(0)!.length - 1 + ..i = abs_start + ..j = abs_end ..regex_name = name ..regex_match = rx_match); + last_index = abs_end + 1; } }); return sorted(matches); diff --git a/test/src/matching_test.dart b/test/src/matching_test.dart index 694bca63..970bfe03 100644 --- a/test/src/matching_test.dart +++ b/test/src/matching_test.dart @@ -775,6 +775,17 @@ void main() { } }); + test('regex matching terminates on overlapping matches', () { + // Regression test: two overlapping recent_year matches (here "2011" + // and "2001" sharing the digit at index 3) used to make `regex_match` + // compute a non-monotonic cursor and loop forever, growing its match + // list without bound until the process ran out of memory. + final matches = matching.regex_match('20112001Vi-'); + expect(matches.length, 2); + expect(matches[0].token, '2011'); + expect(matches[1].token, '2001'); + }); + test('date matching', () { for (final sep in ['', ' ', '-', '/', '\\', '_', '.']) { final password = "13${sep}2${sep}1921";