ptx: fix infinite loop with a nullable --word-regexp

'ptx -W "a*"' would never terminate.  SKIP_SOMETHING advanced the cursor
by the length of the match, which is zero when the word regexp matches
the empty string, and no other value in the surrounding loop conditions
changes.  find_occurs_in_text already skips zero length matches, and a
zero length --context-regexp match is rejected outright; only this macro
lacked the forward progress guarantee.

* src/ptx.c (SKIP_SOMETHING): Advance by at least one byte on a zero
length match, as the other branches of the macro already do.
* tests/ptx/word-regex-loop.sh: New test.
* tests/local.mk (all_tests): Reference it.
* NEWS: Mention the fix.

Link: https://github.com/coreutils/coreutils/pull/328
This commit is contained in:
Sylvestre Ledru authored and Pádraig Brady committed 2026-08-10 22:24:21 +01:00
1 parent 3e9eaa6d61
commit f6539191f1
4 files changed
+49 -4

No files matched your search

+7 -3
View File
@@ -34,9 +34,13 @@ GNU coreutils NEWS -*- outline -*-
like when processing large tab stops.
[This bug was present in "the beginning".]
'ptx -G' no longer hangs when the output width is smaller than twice the
gap size, as with 'ptx -G -w4', or when a long reference leaves that
little room, as with 'ptx -G -r'.
'ptx -G' no longer loops forever when the output width is smaller than
twice the gap size, as with 'ptx -G -w4', or when a long reference
leaves that little room, as with 'ptx -G -r'.
[This bug was present in "the beginning".]
'ptx -W' no longer loops forever with a word regular expression that can
match the empty string, like: echo ab | ptx -W 'a*'.
[This bug was present in "the beginning".]
'shred' no longer blocks when opening a FIFO that has no readers.
+4 -1
View File
@@ -176,6 +176,9 @@ static BLOCK *text_buffers; /* files to study */
while (cursor > start && isspace (to_uchar (cursor[-1]))) \
cursor--
/* Always advance by at least one byte, lest a nullable word regexp
such as 'a*' match the empty string and loop forever. */
#define SKIP_SOMETHING(cursor, limit) \
if (word_regex.string) \
{ \
@@ -184,7 +187,7 @@ static BLOCK *text_buffers; /* files to study */
0, NULL); \
if (count == -2) \
matcher_error (); \
cursor += count == -1 ? 1 : count; \
cursor += count <= 0 ? 1 : count; \
} \
else if (word_fastmap[to_uchar (*cursor)]) \
while (cursor < limit && word_fastmap[to_uchar (*cursor)]) \
+1
View File
@@ -280,6 +280,7 @@ all_tests = \
tests/date/date.pl \
tests/date/date-next-dow.pl \
tests/ptx/ptx-overrun.sh \
tests/ptx/word-regex-loop.sh \
tests/misc/xstrtol.pl \
tests/tail/overlay-headers.sh \
tests/tail/pid.sh \
+37
View File
@@ -0,0 +1,37 @@
#!/bin/sh
# Ensure a nullable --word-regexp does not make ptx loop forever
# Copyright (C) 2026 Free Software Foundation, Inc.
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
. "${srcdir=.}/tests/init.sh"; path_prepend_ ./src
print_ver_ ptx timeout
# A nullable word regexp matching the empty string left the cursor
# unchanged in SKIP_SOMETHING, so define_all_fields() spun forever.
echo 'aa bb cc' > in || framework_failure_
for re in 'a*' '[a-z]*' '[ab]*' '\(a\)*'; do
timeout 10 ptx -W "$re" in >/dev/null \
|| { warn_ "ptx -W '$re' failed or hung"; fail=1; }
done
# Verify match semantics
timeout 10 ptx -W '[ab]*' in > out &&
timeout 10 ptx -W '[ab]+' in > exp &&
compare exp out || fail=1
Exit $fail