diff --git a/src/builtin.c b/src/builtin.c index a3b7a61a..c618cd52 100644 --- a/src/builtin.c +++ b/src/builtin.c @@ -1086,8 +1086,14 @@ static jv f_match(jq_state *jq, jv input, jv regex, jv modifiers, jv testmode) { onig_foreach_name(reg, f_match_name_iter, &captures); match = jv_object_set(match, jv_string("captures"), captures); result = jv_array_append(result, match); - // ensure '"qux" | match("(?=u)"; "g")' matches just once - start = (const UChar*)(input_string+region->end[0]+1); + // ensure '"qux" | match("(?=u)"; "g")' matches just once; advance the + // search start past the whole codepoint, not a single byte, so we don't + // land in the middle of a multibyte character (which would yield a + // spurious duplicate match at the same offset). + start = (const UChar*)(input_string + region->end[0] + + ((size_t)region->end[0] < length + ? jvp_utf8_decode_length(input_string[region->end[0]]) + : 1)); continue; } diff --git a/tests/onig.test b/tests/onig.test index 3b189d40..0aaeeca2 100644 --- a/tests/onig.test +++ b/tests/onig.test @@ -11,6 +11,19 @@ "ab" [{"offset":0,"length":0,"string":"","captures":[]},{"offset":1,"length":0,"string":"","captures":[]},{"offset":2,"length":0,"string":"","captures":[]}] +# global zero-width matches must not land inside a multibyte character +[match(""; "g") | .offset] +"aé€🚀b" +[0,1,2,3,4,5] + +gsub(""; "-") +"aé" +"-a-é-" + +[splits("")] +"aé" +["","a","é",""] + [match("a"; "gi")] "āáàä" []