1
5
Fork
You've already forked bsdutils
1

Fix the handling of REG_STARTEND in sed #47

Merged
dcantrell merged 1 commit from dashea/bsdutils:sed-REG_STARTEND into main 2025年11月12日 19:46:08 +01:00
Contributor
Copy link

sed contains regexec calls that use REG_STARTEND with starts and ends
that are potentially not the start or end of the string being processed.
Adjust the string passed to regexec and the returned match offsets to
account for the difference in behavior when not using REG_STARTEND.

The do-while loop in substitute is the only use of REG_STARTEND with a non-zero start. Without the adjustments, doing multiple substitutions (e.g., echo 'a:b:c' | sed 's/:/ /g') would loop forever.

It's not perfect; it won't handle NUL bytes, and it won't handle word boundaries (also a non-standard extension) correctly. For example, echo one two three | sed 's/\<./1/g' with this change and the glibc regex functions will return "111 111 11111" instead of "1ne 1wo 1hree". I don't think it's possible to handle those cases without including the regex functions from FreeBSD's C library.

The difference in REG_STARTEND's newline behavior should be fine, at least. The do-while loop calls regexec with REG_NOTBOL, and the expression is never compiled with REG_NEWLINE, so regexec will correctly not match '^' before the substring.

sed contains regexec calls that use REG_STARTEND with starts and ends that are potentially not the start or end of the string being processed. Adjust the string passed to regexec and the returned match offsets to account for the difference in behavior when not using REG_STARTEND. The do-while loop in substitute is the only use of REG_STARTEND with a non-zero start. Without the adjustments, doing multiple substitutions (e.g., `echo 'a:b:c' | sed 's/:/ /g'`) would loop forever. It's not perfect; it won't handle NUL bytes, and it won't handle word boundaries (also a non-standard extension) correctly. For example, `echo one two three | sed 's/\<./1/g'` with this change and the glibc regex functions will return "111 111 11111" instead of "1ne 1wo 1hree". I don't think it's possible to handle those cases without including the regex functions from FreeBSD's C library. The difference in REG_STARTEND's newline behavior should be fine, at least. The do-while loop calls regexec with REG_NOTBOL, and the expression is never compiled with REG_NEWLINE, so regexec will correctly not match '^' before the substring.

What about what NetBSD has in their code?

https://github.com/NetBSD/src/tree/trunk/usr.bin/sed

When faced with a portability issue and the FreeBSD code isn't as portable, I go and look at the BSD for toaster ovens since they tend to be more portable. I know that Free and Net share stuff back and forth so it's not deviating too far (likely) from what FreeBSD has.

I haven't looked at the NetBSD sed, so that may be a non-starter.

What about what NetBSD has in their code? https://github.com/NetBSD/src/tree/trunk/usr.bin/sed When faced with a portability issue and the FreeBSD code isn't as portable, I go and look at the BSD for toaster ovens since they tend to be more portable. I know that Free and Net share stuff back and forth so it's not deviating too far (likely) from what FreeBSD has. I haven't looked at the NetBSD sed, so that may be a non-starter.
Author
Contributor
Copy link

hm, looks like I reinvented half of what they do. Their substitute loop is set up so that regexec_e is always called with a start of 0, and then there's some #ifndef REG_STARTEND sections in regexec_e that create a copy of the substring.

hm, looks like I reinvented half of what they do. Their substitute loop is set up so that regexec_e is always called with a start of 0, and then there's some `#ifndef REG_STARTEND` sections in regexec_e that create a copy of the substring.
dashea force-pushed sed-REG_STARTEND from be2459a866 to 93f3b0ed6d 2025年11月11日 20:21:22 +01:00 Compare
Author
Contributor
Copy link

I did a little more digging, and the reason FreeBSD uses non-zero starts in substitute() is to fix a bug described in https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=209352. I changed my change to just undo this patch.

fwiw NetBSD still has this bug.

It sure would be nice the BSDs or GNU had a test suite for sed that made any damn sense to run, but they don't so I didn't. It works with hanoi.sed and math.sed and the case that led me down this rabbit hole to start with, so maybe I didn't break things too horribly.

Just swapping to NetBSD's sed outright might also be an option.

I did a little more digging, and the reason FreeBSD uses non-zero starts in substitute() is to fix a bug described in <https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=209352>. I changed my change to just undo this patch. fwiw NetBSD still has this bug. It sure would be nice the BSDs or GNU had a test suite for sed that made any damn sense to run, but they don't so I didn't. It works with hanoi.sed and math.sed and the case that led me down this rabbit hole to start with, so maybe I didn't break things too horribly. Just swapping to NetBSD's sed outright might also be an option.

For the tests, I just meant the tests/ subdirectory that is found in usr.bin/sed/ in the FreeBSD source. I think that's just a matter of running the shell scripts in there but making sure it invokes the built sed rather than the one on the system. I just haven't looked in to how to hook that up.

I was going to ask about importing sed from NetBSD instead. I'll merge this for now but maybe this might be a combined project of some things imported from FreeBSD, but some imported from NetBSD when the FreeBSD is just not workable. It would probably be easiest to just import from NetBSD directly anyway, but those commands are far more basic than the FreeBSD ones. I see the FreeBSD developers actually extend these and add in options for GNU compatibility or GNU options that are useful. I'm not sure NetBSD really cares about that.

For the tests, I just meant the tests/ subdirectory that is found in usr.bin/sed/ in the FreeBSD source. I think that's just a matter of running the shell scripts in there but making sure it invokes the built sed rather than the one on the system. I just haven't looked in to how to hook that up. I was going to ask about importing sed from NetBSD instead. I'll merge this for now but maybe this might be a combined project of some things imported from FreeBSD, but some imported from NetBSD when the FreeBSD is just not workable. It would probably be easiest to just import from NetBSD directly anyway, but those commands are far more basic than the FreeBSD ones. I see the FreeBSD developers actually extend these and add in options for GNU compatibility or GNU options that are useful. I'm not sure NetBSD really cares about that.
Sign in to join this conversation.
No reviewers
Milestone
Clear milestone
No items
No milestone
Projects
Clear projects
No items
No project
Assignees
Clear assignees
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
dcantrell/bsdutils!47
Reference in a new issue
dcantrell/bsdutils
No description provided.
Delete branch "dashea/bsdutils:sed-REG_STARTEND"

Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?