Skip to content

Commit d024e6d

Browse files
committed
Merge pull request #1569 from jakub-gonet/jgonet/binary-part
Add `binary_part/3` to BIFs These changes are made under both the "Apache 2.0" and the "GNU Lesser General Public License 2.1 or later" license terms (dual license). SPDX-License-Identifier: Apache-2.0 OR LGPL-2.1-or-later
2 parents 672ca63 + 19395fa commit d024e6d

File tree

7 files changed

+114
-61
lines changed

7 files changed

+114
-61
lines changed

src/libAtomVM/bif.c

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,30 @@ term bif_erlang_bit_size_1(Context *ctx, uint32_t fail_label, int live, term arg
108108
return term_from_int32(len);
109109
}
110110

111+
term bif_erlang_binary_part_3(Context *ctx, uint32_t fail_label, int live, term arg1, term arg2, term arg3)
112+
{
113+
VALIDATE_VALUE_BIF(fail_label, arg1, term_is_binary);
114+
VALIDATE_VALUE_BIF(fail_label, arg2, term_is_integer);
115+
VALIDATE_VALUE_BIF(fail_label, arg3, term_is_integer);
116+
117+
avm_int_t pos = term_to_int(arg2);
118+
avm_int_t len = term_to_int(arg3);
119+
BinaryPosLen slice;
120+
if (UNLIKELY(!term_normalize_binary_pos_len(arg1, pos, len, &slice))) {
121+
RAISE_ERROR_BIF(fail_label, BADARG_ATOM);
122+
}
123+
124+
TERM_DEBUG_ASSERT((sizeof(ctx->x) / sizeof(ctx->x[0])) >= MAX_REG + 1);
125+
ctx->x[live] = arg1;
126+
size_t heap_size = term_sub_binary_heap_size(arg1, len);
127+
if (UNLIKELY(memory_ensure_free_with_roots(ctx, heap_size, live + 1, ctx->x, MEMORY_CAN_SHRINK) != MEMORY_GC_OK)) {
128+
RAISE_ERROR_BIF(fail_label, OUT_OF_MEMORY_ATOM);
129+
}
130+
arg1 = ctx->x[live];
131+
132+
return term_maybe_create_sub_binary(arg1, slice.pos, slice.len, &ctx->heap, ctx->global);
133+
}
134+
111135
term bif_erlang_is_atom_1(Context *ctx, uint32_t fail_label, term arg1)
112136
{
113137
UNUSED(ctx);

src/libAtomVM/bif.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ const struct ExportedFunction *bif_registry_get_handler(AtomString module, AtomS
4444
term bif_erlang_self_0(Context *ctx);
4545
term bif_erlang_byte_size_1(Context *ctx, uint32_t fail_label, int live, term arg1);
4646
term bif_erlang_bit_size_1(Context *ctx, uint32_t fail_label, int live, term arg1);
47+
term bif_erlang_binary_part_3(Context *ctx, uint32_t fail_label, int live, term arg1, term arg2, term arg3);
4748
term bif_erlang_length_1(Context *ctx, uint32_t fail_label, int live, term arg1);
4849

4950
term bif_erlang_is_atom_1(Context *ctx, uint32_t fail_label, term arg1);

src/libAtomVM/bifs.gperf

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ erlang:self/0, {.bif.base.type = BIFFunctionType, .bif.bif0_ptr = bif_erlang_sel
3939
erlang:length/1, {.gcbif.base.type = GCBIFFunctionType, .gcbif.gcbif1_ptr = bif_erlang_length_1}
4040
erlang:byte_size/1, {.gcbif.base.type = GCBIFFunctionType, .gcbif.gcbif1_ptr = bif_erlang_byte_size_1}
4141
erlang:bit_size/1, {.gcbif.base.type = GCBIFFunctionType, .gcbif.gcbif1_ptr = bif_erlang_bit_size_1}
42+
erlang:binary_part/3, {.gcbif.base.type = GCBIFFunctionType, .gcbif.gcbif3_ptr = bif_erlang_binary_part_3}
4243
erlang:get/1, {.bif.base.type = BIFFunctionType, .bif.bif1_ptr = bif_erlang_get_1}
4344
erlang:is_atom/1, {.bif.base.type = BIFFunctionType, .bif.bif1_ptr = bif_erlang_is_atom_1}
4445
erlang:is_bitstring/1, {.bif.base.type = BIFFunctionType, .bif.bif1_ptr = bif_erlang_is_binary_1}

src/libAtomVM/nifs.c

Lines changed: 8 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3078,33 +3078,26 @@ static term nif_binary_last_1(Context *ctx, int argc, term argv[])
30783078
static term nif_binary_part_3(Context *ctx, int argc, term argv[])
30793079
{
30803080
UNUSED(argc);
3081-
3082-
term bin_term = argv[0];
3081+
term pattern_term = argv[0];
30833082
term pos_term = argv[1];
30843083
term len_term = argv[2];
3085-
3086-
VALIDATE_VALUE(bin_term, term_is_binary);
3084+
VALIDATE_VALUE(pattern_term, term_is_binary);
30873085
VALIDATE_VALUE(pos_term, term_is_integer);
30883086
VALIDATE_VALUE(len_term, term_is_integer);
30893087

3090-
int bin_size = term_binary_size(bin_term);
30913088
avm_int_t pos = term_to_int(pos_term);
30923089
avm_int_t len = term_to_int(len_term);
3093-
3094-
if (len < 0) {
3095-
pos += len;
3096-
len = -len;
3097-
}
3098-
3099-
if (UNLIKELY((pos < 0) || (pos > bin_size) || (pos + len > bin_size))) {
3090+
BinaryPosLen slice;
3091+
if (UNLIKELY(!term_normalize_binary_pos_len(pattern_term, pos, len, &slice))) {
31003092
RAISE_ERROR(BADARG_ATOM);
31013093
}
31023094

3103-
size_t size = term_sub_binary_heap_size(bin_term, len);
3104-
if (UNLIKELY(memory_ensure_free_with_roots(ctx, size, 1, &bin_term, MEMORY_CAN_SHRINK) != MEMORY_GC_OK)) {
3095+
size_t heap_size = term_sub_binary_heap_size(pattern_term, len);
3096+
if (UNLIKELY(memory_ensure_free_with_roots(ctx, heap_size, 1, &pattern_term, MEMORY_CAN_SHRINK) != MEMORY_GC_OK)) {
31053097
RAISE_ERROR(OUT_OF_MEMORY_ATOM);
31063098
}
3107-
return term_maybe_create_sub_binary(bin_term, pos, len, &ctx->heap, ctx->global);
3099+
3100+
return term_maybe_create_sub_binary(pattern_term, slice.pos, slice.len, &ctx->heap, ctx->global);
31083101
}
31093102

31103103
static term nif_binary_split(Context *ctx, int argc, term argv[])

src/libAtomVM/term.h

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,12 @@ struct PrinterFun
136136
printer_function_t print;
137137
};
138138

139+
typedef struct BinaryPosLen
140+
{
141+
avm_int_t pos;
142+
avm_int_t len;
143+
} BinaryPosLen;
144+
139145
enum RefcBinaryFlags
140146
{
141147
RefcNoFlags = 0,
@@ -1130,6 +1136,33 @@ static inline term term_create_empty_binary(size_t size, Heap *heap, GlobalConte
11301136
return t;
11311137
}
11321138

1139+
static inline bool term_normalize_binary_pos_len(term binary, avm_int_t pos, avm_int_t len, BinaryPosLen *pos_len)
1140+
{
1141+
avm_int_t size = (avm_int_t) term_binary_size(binary);
1142+
if (len < 0) {
1143+
pos += len;
1144+
len = -len;
1145+
}
1146+
1147+
if (UNLIKELY((pos < 0) || (pos > size) || (pos + len > size))) {
1148+
return false;
1149+
}
1150+
1151+
pos_len->pos = pos;
1152+
pos_len->len = len;
1153+
return true;
1154+
}
1155+
1156+
static inline bool term_is_invalid_binary_pos_len(BinaryPosLen pos_len)
1157+
{
1158+
return pos_len.pos == -1 && pos_len.len == -1;
1159+
}
1160+
1161+
static inline BinaryPosLen term_invalid_binary_pos_len(void)
1162+
{
1163+
return (BinaryPosLen) { .pos = -1, .len = -1 };
1164+
}
1165+
11331166
/**
11341167
* @brief Insert an binary into a binary (using bit syntax).
11351168
*

tests/erlang_tests/test_binary_part.erl

Lines changed: 46 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -20,67 +20,68 @@
2020

2121
-module(test_binary_part).
2222

23-
-export([start/0, id/1, some_part/1, first_part/1, empty_part/1, compare_bin/2]).
23+
-export([start/0, id/1, fail_with_badarg/1, compare_bin/2, as_guard/4]).
24+
-define(ID(Arg), ?MODULE:id(Arg)).
2425

2526
start() ->
26-
B1 = some_part(id(<<"012Testxyz">>)),
27-
B2 = first_part(id(<<"First01234">>)),
28-
B3 = some_part(id(<<"XYZLast">>)),
29-
B4 = empty_part(id(<<"">>)),
30-
B5 = first_part(id(<<"01234">>)),
31-
B6 = not_fail2(1, -1),
27+
ok = test_with_binary_part_fun(?ID(fun binary:part/3)),
28+
ok = test_with_binary_part_fun(?ID(fun erlang:binary_part/3)),
29+
ok = test_with_binary_part_fun(
30+
?ID(fun(Bin, Pos, Len) ->
31+
Pattern = binary_part(Bin, Pos, Len),
32+
?MODULE:as_guard(Bin, Pattern, Pos, Len)
33+
end)
34+
),
35+
0.
3236

33-
compare_bin(B1, <<"Test">>) + compare_bin(B2, <<"First">>) + compare_bin(B3, <<"Last">>) +
34-
byte_size(B4) + compare_bin(B5, <<"01234">>) + fail1(<<":(">>) + fail1({0, 1, 2, 3, 4}) +
35-
fail2(-1, 0) + fail2(0, -1) + compare_bin(<<":">>, B6) + fail2(-1, 2) + fail2(1, fail) +
36-
fail2(fail, 1).
37+
test_with_binary_part_fun(BinaryPart) ->
38+
Middle = BinaryPart(?ID(<<"012Testxyz">>), 3, 4),
39+
ok = compare_bin(Middle, <<"Test">>),
40+
First = BinaryPart(?ID(<<"First01234">>), 0, 5),
41+
ok = compare_bin(First, <<"First">>),
42+
Last = BinaryPart(?ID(<<"XYZLast">>), 3, 4),
43+
ok = compare_bin(Last, <<"Last">>),
44+
Empty = BinaryPart(?ID(<<"">>), 0, 0),
45+
0 = byte_size(Empty),
46+
All = BinaryPart(?ID(<<"01234">>), 0, 5),
47+
ok = compare_bin(All, <<"01234">>),
48+
NegativeCount = BinaryPart(?ID(<<"xyz">>), 1, -1),
49+
ok = compare_bin(NegativeCount, <<"x">>),
50+
ok = fail_with_badarg(fun() -> BinaryPart(?ID(<<"PastEnd">>), 0, 8) end),
51+
BadBinary = {0, 1, 2, 3, 4},
52+
ok = fail_with_badarg(fun() -> BinaryPart(?ID(BadBinary), 0, 1) end),
53+
ok = fail_with_badarg(fun() -> BinaryPart(?ID(BadBinary), 0, 0) end),
54+
ok = fail_with_badarg(fun() -> BinaryPart(?ID(<<"PosBeforeStart">>), -1, 0) end),
55+
ok = fail_with_badarg(fun() -> BinaryPart(?ID(<<"LenBeforeStart">>), 0, -1) end),
56+
ok = fail_with_badarg(fun() -> BinaryPart(?ID(<<"PartiallyBeforeStart">>), -1, 2) end),
57+
ok = fail_with_badarg(fun() -> BinaryPart(?ID(<<"BadPos">>), fail, 1) end),
58+
ok = fail_with_badarg(fun() -> BinaryPart(?ID(<<"BadLen">>), 1, fail) end),
59+
ok.
3760

38-
empty_part(Bin1) ->
39-
binary:part(id(Bin1), 0, 0).
40-
41-
first_part(Bin1) ->
42-
binary:part(id(Bin1), 0, 5).
43-
44-
some_part(Bin1) ->
45-
binary:part(id(Bin1), 3, 4).
61+
as_guard(Bin, Pattern, Pos, Len) when binary_part(Bin, Pos, Len) == Pattern ->
62+
Pattern;
63+
as_guard(_Bin, _Pattern, _Pos, _Len) ->
64+
erlang:error(badarg).
4665

4766
id(X) ->
4867
X.
4968

50-
fail1(X) ->
51-
try binary:part(X, 0, 3) of
52-
_Any -> 1000
53-
catch
54-
error:badarg -> 1;
55-
_:_ -> 2000
56-
end.
57-
58-
fail2(X, Y) ->
59-
try binary:part(<<":((">>, X, Y) of
60-
_Any -> 1000
61-
catch
62-
error:badarg -> 1;
63-
_:_ -> 2000
64-
end.
65-
66-
not_fail2(X, Y) ->
67-
try binary:part(<<":((">>, X, Y) of
68-
Any -> Any
69+
fail_with_badarg(Fun) ->
70+
try Fun() of
71+
Ret -> {unexpected, Ret}
6972
catch
70-
error:badarg -> 1;
71-
_:_ -> 2000
73+
error:badarg -> ok;
74+
C:E -> {unexpected, C, E}
7275
end.
7376

7477
compare_bin(Bin1, Bin2) ->
7578
compare_bin(Bin1, Bin2, byte_size(Bin1) - 1).
7679

7780
compare_bin(_Bin1, _Bin2, -1) ->
78-
1;
81+
ok;
7982
compare_bin(Bin1, Bin2, Index) ->
8083
B1 = binary:at(Bin1, Index),
8184
case binary:at(Bin2, Index) of
82-
B1 ->
83-
compare_bin(Bin1, Bin2, Index - 1);
84-
_Any ->
85-
0
85+
B1 -> compare_bin(Bin1, Bin2, Index - 1);
86+
_Any -> error
8687
end.

tests/test.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -320,7 +320,7 @@ struct Test tests[] = {
320320
TEST_CASE_EXPECTED(test_atom_to_binary, 1),
321321
TEST_CASE(test_unicode),
322322

323-
TEST_CASE_EXPECTED(test_binary_part, 12),
323+
TEST_CASE(test_binary_part),
324324
TEST_CASE(test_binary_split),
325325

326326
TEST_CASE_COND(plusone, 134217728, LONG_MAX != 9223372036854775807),

0 commit comments

Comments
 (0)