Skip to content

Commit 559f3fc

Browse files
committed
externalterm: parse big integers
Add support in SMALL_BIG_EXT parsing to big integers (that means integers that are >= 8 bytes and <= 32 bytes). Signed-off-by: Davide Bettio <davide@uninstall.it>
1 parent 759a5f9 commit 559f3fc

File tree

3 files changed

+125
-37
lines changed

3 files changed

+125
-37
lines changed

src/libAtomVM/externalterm.c

+50-18
Original file line numberDiff line numberDiff line change
@@ -430,16 +430,39 @@ static term parse_external_terms(const uint8_t *external_term_buf, size_t *eterm
430430
}
431431

432432
case SMALL_BIG_EXT: {
433-
uint8_t num_bytes = external_term_buf[1];
434-
uint8_t sign = external_term_buf[2];
435-
avm_uint64_t unsigned_value = read_bytes(external_term_buf + 3, num_bytes);
436-
// NB due to call to calculate_heap_usage, there is no loss of precision:
437-
// 1. 0 <= unsigned_value <= INT64_MAX if sign is 0
438-
// 2. 0 <= unsigned_value <= INT64_MAX + 1 if sign is not 0
439-
avm_int64_t value = int64_cond_neg_unsigned(sign != 0x00, unsigned_value);
440-
*eterm_size = SMALL_BIG_EXT_BASE_SIZE + num_bytes;
433+
uint8_t int_len = external_term_buf[1];
434+
uint8_t sign_byte = external_term_buf[2];
435+
const uint8_t *int_bytes = external_term_buf + 3;
436+
bool is_negative = sign_byte != 0x00;
437+
438+
if (int_len <= 8) {
439+
avm_uint64_t unsigned_value = read_bytes(int_bytes, int_len);
440+
if (!uint64_does_overflow_int64(unsigned_value, is_negative)) {
441+
avm_int64_t value = int64_cond_neg_unsigned(is_negative, unsigned_value);
442+
*eterm_size = SMALL_BIG_EXT_BASE_SIZE + int_len;
443+
return term_make_maybe_boxed_int64(value, heap);
444+
}
445+
}
441446

442-
return term_make_maybe_boxed_int64(value, heap);
447+
// int_len > 8 || uint64_does_overflow_int64
448+
intn_digit_t bigint[INTN_MAX_RES_LEN];
449+
int count = intn_from_integer_bytes(int_bytes, int_len, IntnLittleEndian, bigint, NULL);
450+
if (UNLIKELY(count < 0)) {
451+
// this means a bug, `calculate_heap_usage` already checks this
452+
AVM_ABORT();
453+
}
454+
455+
size_t intn_data_size;
456+
size_t rounded_res_len;
457+
term_intn_to_term_size(count, &intn_data_size, &rounded_res_len);
458+
459+
intn_integer_sign_t sign = is_negative ? IntNNegativeInteger : IntNPositiveInteger;
460+
term bigint_term
461+
= term_create_uninitialized_intn(intn_data_size, (term_integer_sign_t) sign, heap);
462+
intn_digit_t *dest_buf = (void *) term_intn_data(bigint_term);
463+
intn_copy(bigint, count, dest_buf, rounded_res_len);
464+
465+
return bigint_term;
443466
}
444467

445468
case ATOM_EXT: {
@@ -681,20 +704,29 @@ static int calculate_heap_usage(const uint8_t *external_term_buf, size_t remaini
681704

682705
case SMALL_BIG_EXT: {
683706
size_t num_bytes = external_term_buf[1];
684-
if (UNLIKELY(num_bytes > 8 || remaining < (SMALL_BIG_EXT_BASE_SIZE + num_bytes))) {
707+
if (UNLIKELY(remaining < (SMALL_BIG_EXT_BASE_SIZE + num_bytes)
708+
|| num_bytes > INTN_MAX_UNSIGNED_BYTES_SIZE)) {
685709
return INVALID_TERM_SIZE;
686710
}
687711
uint8_t sign = external_term_buf[2];
712+
bool is_negative = sign != 0x00;
688713
*eterm_size = SMALL_BIG_EXT_BASE_SIZE + num_bytes;
689-
avm_uint64_t unsigned_value = read_bytes(external_term_buf + 3, num_bytes);
690-
// NB. We currently support max 64-bit signed integers (assuming two's complement signed values in 63 bits)
691-
if (UNLIKELY((sign == 0 && unsigned_value > INT64_MAX) || (sign != 0 && unsigned_value > (((avm_uint64_t) INT64_MAX) + 1)))) {
692-
return INVALID_TERM_SIZE;
714+
715+
if (LIKELY(num_bytes <= 8)) {
716+
avm_uint64_t unsigned_value = read_bytes(external_term_buf + 3, num_bytes);
717+
if (!uint64_does_overflow_int64(unsigned_value, is_negative)) {
718+
// Compute the size with the sign as -2^27 or -2^59 can be encoded
719+
// on 1 term while 2^27 and 2^59 respectively (32/64 bits) cannot.
720+
avm_int64_t value = int64_cond_neg_unsigned(is_negative, unsigned_value);
721+
return term_boxed_integer_size(value);
722+
}
693723
}
694-
// Compute the size with the sign as -2^27 or -2^59 can be encoded
695-
// on 1 term while 2^27 and 2^59 respectively (32/64 bits) cannot.
696-
avm_int64_t value = int64_cond_neg_unsigned(sign != 0x00, unsigned_value);
697-
return term_boxed_integer_size(value);
724+
725+
// num_bytes > 8 bytes || uint64_does_overflow_int64
726+
size_t data_size;
727+
size_t unused_rounded_len;
728+
term_intn_to_term_size(num_bytes, &data_size, &unused_rounded_len);
729+
return data_size + 1;
698730
}
699731

700732
case ATOM_EXT: {

tests/erlang_tests/bigint.erl

+34-1
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,8 @@ start() ->
4848
test_mul() +
4949
parse_bigint() +
5050
test_cmp() +
51-
conv_to_from_float().
51+
conv_to_from_float() +
52+
external_term_decode().
5253

5354
test_mul() ->
5455
Expected_INT64_MIN = ?MODULE:pow(-2, 63),
@@ -579,6 +580,38 @@ conv_to_from_float() ->
579580

580581
0.
581582

583+
external_term_decode() ->
584+
T1B = ?MODULE:id(<<"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9E024D5C1207BCB8FCDD50C17BBBB">>),
585+
T1 = ?MODULE:id(erlang:binary_to_integer(T1B, 16)),
586+
T1 = ?MODULE:id(
587+
erlang:binary_to_term(
588+
?MODULE:id(
589+
<<131, 110, 32, 0, 187, 187, 23, 12, 213, 205, 143, 203, 123, 32, 193, 213, 36, 224,
590+
249, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
591+
255, 255>>
592+
)
593+
)
594+
),
595+
T2B = ?MODULE:id(<<"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9E024D5C1207BCB8FCDD50C17BDA">>),
596+
T2 = ?MODULE:id(erlang:binary_to_integer(T2B, 16)),
597+
T2 = ?MODULE:id(
598+
erlang:binary_to_term(
599+
?MODULE:id(
600+
<<131, 110, 32, 0, 218, 123, 193, 80, 221, 252, 184, 188, 7, 18, 92, 77, 2, 158,
601+
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
602+
255, 15>>
603+
)
604+
)
605+
),
606+
T3B = ?MODULE:id(<<"-FFFFFFFFFFFFFFFF">>),
607+
T3 = ?MODULE:id(erlang:binary_to_integer(T3B, 16)),
608+
T3 = ?MODULE:id(
609+
erlang:binary_to_term(
610+
?MODULE:id(<<131, 110, 8, 1, 255, 255, 255, 255, 255, 255, 255, 255>>)
611+
)
612+
),
613+
0.
614+
582615
divtrunc(X, Y) ->
583616
erlang:trunc(X / Y).
584617

tests/erlang_tests/small_big_ext.erl

+41-18
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020

2121
-module(small_big_ext).
2222

23-
-export([start/0]).
23+
-export([start/0, id/1]).
2424

2525
-define(INT64_MAX, 9223372036854775807).
2626
-define(INT64_MIN, -9223372036854775808).
@@ -55,44 +55,64 @@ start() ->
5555
true = test_reverse(pow(59) - 1, <<131, 110, 8, 0, 255, 255, 255, 255, 255, 255, 255, 7>>),
5656
true = test_reverse(-pow(59), <<131, 110, 8, 1, 0, 0, 0, 0, 0, 0, 0, 8>>),
5757

58+
% TODO: enable as soon as serialization for big integers is ready
59+
%true = test_reverse(
60+
% erlang:binary_to_integer(?MODULE:id(<<"8000000000000001">>), 16),
61+
% <<131, 110, 8, 0, 1, 0, 0, 0, 0, 0, 0, 128>>
62+
%),
63+
%true = test_reverse(
64+
% erlang:binary_to_integer(?MODULE:id(<<"-8000000000000002">>), 16),
65+
% <<131, 110, 8, 1, 2, 0, 0, 0, 0, 0, 0, 128>>
66+
%),
67+
%true = test_reverse(
68+
% erlang:binary_to_integer(?MODULE:id(<<"100000000000000000000000000000000">>), 16),
69+
% <<131, 110, 17, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1>>
70+
%),
71+
5872
%% missing sign
5973
ok = assert_badarg(
6074
fun() ->
6175
erlang:binary_to_term(<<131, 110, 0>>)
6276
end
6377
),
6478

65-
%% we currently only support up to 64 bit (signed) integers
79+
%% we currently only support up to 256 bit (unsigned) integers
6680
case erlang:system_info(machine) of
6781
"BEAM" ->
68-
test_reverse(
69-
pow(63) + 1, <<131, 110, 8, 0, 1, 0, 0, 0, 0, 0, 0, 128>>
70-
),
71-
test_reverse(
72-
-(pow(63) + 2), <<131, 110, 8, 1, 2, 0, 0, 0, 0, 0, 0, 128>>
82+
true = test_reverse(
83+
erlang:binary_to_integer(
84+
?MODULE:id(
85+
<<"20000000000000000000000000000000000000000000000000000000000000000">>
86+
),
87+
16
88+
),
89+
<<131, 110, 33, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
90+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2>>
7391
),
74-
test_reverse(
75-
pow(128), <<131, 110, 17, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1>>
92+
true = test_reverse(
93+
erlang:binary_to_integer(
94+
?MODULE:id(
95+
<<"-20000000000000000000000000000000000000000000000000000000000000000">>
96+
),
97+
16
98+
),
99+
<<131, 110, 33, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
100+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2>>
76101
);
77102
_ ->
78103
ok = assert_badarg(
79104
fun() ->
80105
erlang:binary_to_term(
81-
<<131, 110, 8, 0, 1, 0, 0, 0, 0, 0, 0, 128>>
106+
<<131, 110, 33, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
107+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2>>
82108
)
83109
end
84110
),
85111
ok = assert_badarg(
86112
fun() ->
87113
erlang:binary_to_term(
88-
<<131, 110, 17, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1>>
89-
)
90-
end
91-
),
92-
ok = assert_badarg(
93-
fun() ->
94-
erlang:binary_to_term(
95-
<<131, 110, 8, 1, 2, 0, 0, 0, 0, 0, 0, 128>>
114+
<<131, 110, 33, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
115+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2>>
96116
)
97117
end
98118
)
@@ -119,3 +139,6 @@ pow(0) ->
119139
pow(X) ->
120140
Y = pow(X - 1),
121141
Y bsl 1.
142+
143+
id(N) ->
144+
N.

0 commit comments

Comments
 (0)