Two symptoms, one off-by-one, so they are filed together; split them if the
maintainers prefer.
Symptom one, a wrong answer. POS(needle, haystack, start, range)'s range does not bound where
a match may fit, nor where it may begin:
say pos('an','axan',1,3) /* 3 -- the match ends at position 4, outside the range */
say pos('an','zxan',1,3) /* 0 */
Same start, same range; the haystacks differ only in a decoy a at position 1 that is not itself a
match. StringUtil::pos (interpreter/classes/support/StringUtil.cpp) sets endpointer to one past
the last position at which the whole needle fits, memchrs for the first byte over
endpointer - haypointer bytes, and on a candidate whose first byte matched but whose whole did not,
rescans from haypointer + 1 with that same length, measured from the rejected candidate rather
than from where the scan resumes. Every rescan therefore ends one position past endpointer.
The oracle's own twin is the proof this is a defect and not an extension. caselessPos walks
_range - needle_length + 1 probes one at a time, so 'axan'~caselessPos('an',1,3) is 0 where
'axan'~pos('an',1,3) is 3. LASTPOS uses a different primitive and is clean, checked by a
16-by-10 start-by-range sweep.
The overrun is one position and does not accumulate (axaxan is 0 at range 4 and 5 at range 5),
it holds for longer needles (axxabc gives 4 and zxxabc gives 0, both at range 5), and a one-byte
needle takes an early return before the loop and cannot overrun.
Symptom two, a segfault. When the search runs to the end of the haystack, the position one past
the window is the byte past the string itself, where the C++ reads the RexxString's NUL terminator.
A needle whose last byte is '00'x therefore matches off the end:
say pos('a'||'00'x, 'aa') /* 2, over a two-byte haystack */
say changestr('a'||'00'x, 'aa', 'ZZZ') /* SIGSEGV, rc 139 */
POS and COUNTSTR merely report that position and survive. CHANGESTR copies the haystack up to
and including the matched needle, and copying through a match that runs past the buffer walks off the
end of the allocation.
Not a duplicate of #2010 (ChangeStr wrong message for a negative fourth argument) or #2012
(Insert with -1), both of which are argument-validation messages rather than this scan bound.
Anonymous