Skip to content

Commit abe54f6

Browse files
committed
stdlib: Fix indentation_bin crash on binaries containing tabs
indentation_bin/5 used N as an absolute byte offset but passed it to binary:part/3 as a segment length. This caused a badarg crash when a binary containing a tab was passed to indentation/2, which happens via fwrite_bin when ~ts precedes ~p. Additionally, the newline clause reset N to 0 without updating Start, causing wrong indentation after multiple newlines. Fix by making N relative to Start (i.e. the segment length).
1 parent 92a82c1 commit abe54f6

2 files changed

Lines changed: 38 additions & 4 deletions

File tree

lib/stdlib/src/io_lib_format.erl

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -465,12 +465,12 @@ indentation([], I) ->
465465
indentation_bin(Bin, I) ->
466466
indentation_bin(Bin, Bin, 0, 0, I).
467467

468-
indentation_bin(<<$\n, Cs/binary>>, Orig, _Start, N,_I) ->
469-
indentation_bin(Cs, Orig, N+1, 0, 0);
468+
indentation_bin(<<$\n, Cs/binary>>, Orig, Start, N, _I) ->
469+
indentation_bin(Cs, Orig, Start+N+1, 0, 0);
470470
indentation_bin(<<$\t, Cs/binary>>, Orig, Start, N, I0) ->
471471
Part = binary:part(Orig, Start, N),
472472
PSz = string:length(Part),
473-
indentation_bin(Cs, Orig, N+1, N+1, ((I0+PSz + 8) div 8) * 8);
473+
indentation_bin(Cs, Orig, Start+N+1, 0, ((I0+PSz + 8) div 8) * 8);
474474
indentation_bin(<<_, Cs/binary>>, Orig, Start, N, I) ->
475475
indentation_bin(Cs, Orig, Start, N+1, I);
476476
indentation_bin(<<>>, Orig, Start, N, I) ->

lib/stdlib/test/io_SUITE.erl

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,8 @@
3939
unscan_format_without_maps_order/1, build_text_without_maps_order/1,
4040
native_records/1, cover_fread/1,
4141
format_w_empty_map/1, format_w_limited/1,
42-
write_record_maps_order/1, write_record_latin1_encoding/1]).
42+
write_record_maps_order/1, write_record_latin1_encoding/1,
43+
indentation_tab/1]).
4344

4445
-export([pretty/2, trf/3, rfd/2]).
4546

@@ -79,6 +80,7 @@ all() ->
7980
native_records,
8081
format_w_empty_map, format_w_limited,
8182
write_record_maps_order, write_record_latin1_encoding,
83+
indentation_tab,
8284
cover_fread].
8385

8486
%% Error cases for output.
@@ -3510,6 +3512,38 @@ fread_good(Format, String) ->
35103512
fread_float_not_accepted(Format, String, []),
35113513
{Term,Remaining}.
35123514

3515+
%% Test that tabs are handled correctly in indentation calculation
3516+
%% for both the format string and binary/list arguments (GH-XXXX).
3517+
indentation_tab(_Config) ->
3518+
%% Tab in format string before ~p — indentation tracked by
3519+
%% build_limited_bin, ~p sees correct column.
3520+
"\tfoo" = fmt("\t~p", [foo]),
3521+
"a\tfoo" = fmt("a\t~p", [foo]),
3522+
"a\nb\tfoo" = fmt("a\nb\t~p", [foo]),
3523+
3524+
%% Tab in binary argument via ~ts before ~p — exercises
3525+
%% indentation_bin/5 (the buggy path).
3526+
"a\tbfoo" = fmt("~ts~p", [<<"a\tb">>, foo]),
3527+
"\tfoo" = fmt("~ts~p", [<<"\t">>, foo]),
3528+
"a\nb\tcfoo" = fmt("~ts~p", [<<"a\nb\tc">>, foo]),
3529+
3530+
%% Tab in list argument via ~ts before ~p — also goes through
3531+
%% indentation_bin when on the binary output path (bformat).
3532+
"a\tbfoo" = fmt("~ts~p", ["a\tb", foo]),
3533+
"\tfoo" = fmt("~ts~p", ["\t", foo]),
3534+
"a\nb\tcfoo" = fmt("~ts~p", ["a\nb\tc", foo]),
3535+
3536+
%% Verify indentation/2 directly for binary vs list equivalence.
3537+
8 = io_lib_format:indentation(<<"\t">>, 0),
3538+
8 = io_lib_format:indentation("\t", 0),
3539+
9 = io_lib_format:indentation(<<"a\tb">>, 0),
3540+
9 = io_lib_format:indentation("a\tb", 0),
3541+
9 = io_lib_format:indentation(<<"a\nb\tc">>, 0),
3542+
9 = io_lib_format:indentation("a\nb\tc", 0),
3543+
10 = io_lib_format:indentation(<<"ab\ncd\tef">>, 0),
3544+
10 = io_lib_format:indentation("ab\ncd\tef", 0),
3545+
ok.
3546+
35133547
fread_bad(Format, String) ->
35143548
{error,{fread,Hint}} = io_lib:fread(Format, String),
35153549
Hint.

0 commit comments

Comments
 (0)