From 04fa12be2eae71d8a0a3989f852369f185b556d0 Mon Sep 17 00:00:00 2001 From: Dan Gudmundsson Date: Tue, 11 Aug 2026 13:13:05 +0200 Subject: [PATCH] stdlib: Fix unicode_util:gc/1 breaking binary continuations MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The gc/1 fast-path for $\r was calling cp(R0) and returning [CP|T] when \r was not followed by \n. This "exploded" the binary tail into mixed chardata (integers + binary fragments), which downstream functions in string.erl (bin_search_loop, bin_search_inv_n) could not handle — they expect [BinR|Cont] when is_binary(BinR). This caused string:trim/3 to return wrong results or crash with {case_clause,[]} when trimming strings containing binaries followed by another list element, and the binary contained $\r not followed by $\n. Fix: return the original input (Str) instead of [CP|cp(R0)], consistent with what gc_1/1 already does. Closes #11380 --- lib/stdlib/test/string_SUITE.erl | 1 + lib/stdlib/test/unicode_util_SUITE.erl | 6 ++++++ lib/stdlib/uc_spec/gen_unicode_mod.escript | 4 ++-- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/lib/stdlib/test/string_SUITE.erl b/lib/stdlib/test/string_SUITE.erl index dc7afaaa441d..cd553c5cc567 100644 --- a/lib/stdlib/test/string_SUITE.erl +++ b/lib/stdlib/test/string_SUITE.erl @@ -327,6 +327,7 @@ trim(_) -> ?TEST(["..h", ".e", <<"j..">>], [both, ". "], "h.ej"), ?TEST(["..h", <<".ejsa"/utf8>>, "n.."], [both, ". "], "h.ejsan"), %% Test that it behaves with graphemes (i.e. nfd tests are the hard part) + ?TEST([<<"\r \r">>, []], [trailing, [$\r, [$\r, $\n]]], "\r "), ?TEST([1013,101,778,101,101], [trailing, [101]], [1013,101,778]), ?TEST("aaåaa", [both, "a"], "å"), ?TEST(["aaa",778,"äöoo"], [both, "ao"], "åäö"), diff --git a/lib/stdlib/test/unicode_util_SUITE.erl b/lib/stdlib/test/unicode_util_SUITE.erl index 42a119fba997..2fca23963196 100644 --- a/lib/stdlib/test/unicode_util_SUITE.erl +++ b/lib/stdlib/test/unicode_util_SUITE.erl @@ -123,6 +123,12 @@ gc(Config) -> "hejsan" = fetch(["hej"|<<"san">>], Get), {error, <<128>>} = Get(<<128>>), {error, [<<128>>, 0]} = Get([<<128>>, 0]), + [$\r, <<>>, []] = Get([<<$\r>>, []]), + [$\r|<<$a>>] = Get(<<$\r,$a>>), + [$\r|<<$a>>] = Get([<<$\r,$a>>]), + [$\r, <<$a>>, []] = Get([<<$\r,$a>>, []]), + [$\r, <<$a>>, <<>>] = Get([<<$\r,$a>>, <<>>]), + {'EXIT', _} = catch Get([-1]), {'EXIT', _} = catch Get([-1, $a]), diff --git a/lib/stdlib/uc_spec/gen_unicode_mod.escript b/lib/stdlib/uc_spec/gen_unicode_mod.escript index 0278197e985f..ee23e335ea6d 100644 --- a/lib/stdlib/uc_spec/gen_unicode_mod.escript +++ b/lib/stdlib/uc_spec/gen_unicode_mod.escript @@ -666,10 +666,10 @@ gen_gc(Fd, GBP) -> io:put_chars(Fd, "gc([]=R) -> R;\n" "gc([CP]=R) when ?IS_CP(CP) -> R;\n" - "gc([$\\r=CP|R0]) ->\n" + "gc([$\\r=CP|R0] = Str) ->\n" " case cp(R0) of % Don't break CRLF\n" " [$\\n|R1] -> [[$\\r,$\\n]|R1];\n" - " T -> [CP|T]\n" + " _ -> Str % Keep the tail binary\n" " end;\n" "gc([CP1|T1]=T) when ?IS_CP(CP1), CP1 < 256 ->\n" " case T1 of\n"