Skip to content

Commit 3ac2a24

Browse files
committed
Merge pull request #1592 from bettio/fix-is_function_2
Fix `is_function/2` guard and missing BIF is_function2/3 wasn't handling registers as arity argument (see #1382) and is_function/2 BIF was missing. See also #1509 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 6928350 + 6093a74 commit 3ac2a24

File tree

8 files changed

+118
-4
lines changed

8 files changed

+118
-4
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ with nodejs and emscripten)
1414
- Added memory info in `out_of_memory` crash logs to help developers fix memory issues.
1515
- Added documentation and function specs for uart driver
1616
- Added `uart:read/2` with a timeout parameter.
17+
- Missing `erlang:is_function/2` BIF
1718

1819
### Fixed
1920

@@ -60,6 +61,7 @@ memory error
6061
- Fixed issues with parsing of line references for stack traces
6162
- Fixed memory corruption issue with `erlang:make_tuple/2`
6263
- Fix potential use after free with code generated from OTP <= 24
64+
- Fix `is_function/2` guard
6365

6466
### Changed
6567

src/libAtomVM/bif.c

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,50 @@ term bif_erlang_is_function_1(Context *ctx, uint32_t fail_label, term arg1)
148148
return term_is_function(arg1) ? TRUE_ATOM : FALSE_ATOM;
149149
}
150150

151+
term bif_erlang_is_function_2(Context *ctx, uint32_t fail_label, term arg1, term arg2)
152+
{
153+
VALIDATE_VALUE_BIF(fail_label, arg2, term_is_any_integer);
154+
155+
if (!term_is_integer(arg2)) {
156+
// function takes any positive integer, including big integers
157+
// but internally we use only small integers
158+
return FALSE_ATOM;
159+
}
160+
avm_int_t arity = term_to_int(arg2);
161+
if (arity < 0) {
162+
RAISE_ERROR_BIF(fail_label, BADARG_ATOM);
163+
}
164+
165+
if (!term_is_function(arg1)) {
166+
return FALSE_ATOM;
167+
}
168+
169+
// following part has been taken from opcodesswitch.h
170+
// TODO: factor this out
171+
const term *boxed_value = term_to_const_term_ptr(arg1);
172+
173+
Module *fun_module = (Module *) boxed_value[1];
174+
term index_or_module = boxed_value[2];
175+
176+
uint32_t fun_arity;
177+
178+
if (term_is_atom(index_or_module)) {
179+
fun_arity = term_to_int(boxed_value[3]);
180+
181+
} else {
182+
uint32_t fun_index = term_to_int32(index_or_module);
183+
184+
uint32_t fun_label;
185+
uint32_t fun_arity_and_freeze;
186+
uint32_t fun_n_freeze;
187+
188+
module_get_fun(fun_module, fun_index, &fun_label, &fun_arity_and_freeze, &fun_n_freeze);
189+
fun_arity = fun_arity_and_freeze - fun_n_freeze;
190+
}
191+
192+
return (arity == ((avm_int_t) fun_arity)) ? TRUE_ATOM : FALSE_ATOM;
193+
}
194+
151195
term bif_erlang_is_integer_1(Context *ctx, uint32_t fail_label, term arg1)
152196
{
153197
UNUSED(ctx);

src/libAtomVM/bif.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ term bif_erlang_is_binary_1(Context *ctx, uint32_t fail_label, term arg1);
5151
term bif_erlang_is_boolean_1(Context *ctx, uint32_t fail_label, term arg1);
5252
term bif_erlang_is_float_1(Context *ctx, uint32_t fail_label, term arg1);
5353
term bif_erlang_is_function_1(Context *ctx, uint32_t fail_label, term arg1);
54+
term bif_erlang_is_function_2(Context *ctx, uint32_t fail_label, term arg1, term arg2);
5455
term bif_erlang_is_integer_1(Context *ctx, uint32_t fail_label, term arg1);
5556
term bif_erlang_is_list_1(Context *ctx, uint32_t fail_label, term arg1);
5657
term bif_erlang_is_number_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
@@ -46,6 +46,7 @@ erlang:is_binary/1, {.bif.base.type = BIFFunctionType, .bif.bif1_ptr = bif_erlan
4646
erlang:is_boolean/1, {.bif.base.type = BIFFunctionType, .bif.bif1_ptr = bif_erlang_is_boolean_1}
4747
erlang:is_float/1, {.bif.base.type = BIFFunctionType, .bif.bif1_ptr = bif_erlang_is_float_1}
4848
erlang:is_function/1, {.bif.base.type = BIFFunctionType, .bif.bif1_ptr = bif_erlang_is_function_1}
49+
erlang:is_function/2, {.bif.base.type = BIFFunctionType, .bif.bif2_ptr = bif_erlang_is_function_2}
4950
erlang:is_integer/1, {.bif.base.type = BIFFunctionType, .bif.bif1_ptr = bif_erlang_is_integer_1}
5051
erlang:is_list/1, {.bif.base.type = BIFFunctionType, .bif.bif1_ptr = bif_erlang_is_list_1}
5152
erlang:is_number/1, {.bif.base.type = BIFFunctionType, .bif.bif1_ptr = bif_erlang_is_number_1}

src/libAtomVM/opcodesswitch.h

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5353,18 +5353,20 @@ HOT_FUNC int scheduler_entry_point(GlobalContext *glb)
53535353
DECODE_LABEL(label, pc)
53545354
term arg1;
53555355
DECODE_COMPACT_TERM(arg1, pc)
5356-
unsigned int arity;
5357-
DECODE_INTEGER(arity, pc)
5356+
term arity_term;
5357+
DECODE_COMPACT_TERM(arity_term, pc)
53585358

53595359
#ifdef IMPL_EXECUTE_LOOP
53605360
TRACE("is_function2/3, label=%i, arg1=%lx, arity=%i\n", label, arg1, arity);
53615361

5362-
if (term_is_function(arg1)) {
5362+
if (term_is_function(arg1) && term_is_integer(arity_term)) {
53635363
const term *boxed_value = term_to_const_term_ptr(arg1);
53645364

53655365
Module *fun_module = (Module *) boxed_value[1];
53665366
term index_or_module = boxed_value[2];
53675367

5368+
avm_int_t arity = term_to_int(arity_term);
5369+
53685370
uint32_t fun_arity;
53695371

53705372
if (term_is_atom(index_or_module)) {
@@ -5381,7 +5383,7 @@ HOT_FUNC int scheduler_entry_point(GlobalContext *glb)
53815383
fun_arity = fun_arity_and_freeze - fun_n_freeze;
53825384
}
53835385

5384-
if (arity != fun_arity) {
5386+
if ((arity < 0) || (arity != (avm_int_t) fun_arity)) {
53855387
pc = mod->labels[label];
53865388
}
53875389
} else {

tests/erlang_tests/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,7 @@ compile_erlang(test_funs8)
194194
compile_erlang(test_funs9)
195195
compile_erlang(test_funs10)
196196
compile_erlang(test_funs11)
197+
compile_erlang(test_funs12)
197198

198199
compile_erlang(test_make_fun3)
199200

@@ -668,6 +669,7 @@ add_custom_target(erlang_test_modules DEPENDS
668669
test_funs9.beam
669670
test_funs10.beam
670671
test_funs11.beam
672+
test_funs12.beam
671673

672674
test_make_fun3.beam
673675

tests/erlang_tests/test_funs12.erl

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
%
2+
% This file is part of AtomVM.
3+
%
4+
% Copyright 2025 Davide Bettio <davide@uninstall.it>
5+
%
6+
% Licensed under the Apache License, Version 2.0 (the "License");
7+
% you may not use this file except in compliance with the License.
8+
% You may obtain a copy of the License at
9+
%
10+
% http://www.apache.org/licenses/LICENSE-2.0
11+
%
12+
% Unless required by applicable law or agreed to in writing, software
13+
% distributed under the License is distributed on an "AS IS" BASIS,
14+
% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
% See the License for the specific language governing permissions and
16+
% limitations under the License.
17+
%
18+
% SPDX-License-Identifier: Apache-2.0 OR LGPL-2.1-or-later
19+
%
20+
21+
-module(test_funs12).
22+
-export([start/0, check_guard/3, check_bool/3, discard/1, get_fun/0]).
23+
24+
start() ->
25+
CheckGuardFun = fun ?MODULE:check_guard/3,
26+
SelfFun = fun erlang:self/0,
27+
{A, F} = ?MODULE:get_fun(),
28+
true = ?MODULE:check_guard(3, CheckGuardFun, 3),
29+
false = ?MODULE:check_guard([], CheckGuardFun, []),
30+
true = ?MODULE:check_bool(SelfFun, F, 1),
31+
false = ?MODULE:check_bool([], {}, 1),
32+
ok = expect_error(fun() -> ?MODULE:check_bool({}, [], not_integer) end, error, badarg),
33+
ok = expect_error(fun() -> ?MODULE:check_bool(SelfFun, F, not_integer) end, error, badarg),
34+
ok = expect_error(fun() -> ?MODULE:check_bool(SelfFun, F, -20) end, error, badarg),
35+
1 = ?MODULE:check_guard(A, F, A),
36+
?MODULE:check_guard(0, SelfFun, 0).
37+
38+
check_guard(A, B, A) when A > 1 andalso is_function(B, A) ->
39+
discard(B);
40+
check_guard(A, B, A) when is_function(B, A) ->
41+
A;
42+
check_guard(A, _B, A) when A < 0 ->
43+
error;
44+
check_guard(_A, _B, _A) ->
45+
false.
46+
47+
discard(_X) ->
48+
true.
49+
50+
check_bool(A, B, C) ->
51+
is_function(A, C) or is_function(B, C).
52+
53+
get_fun() ->
54+
{1, fun(X) -> X + 1 end}.
55+
56+
expect_error(Fun, A, B) ->
57+
try Fun() of
58+
Result -> {error, Result}
59+
catch
60+
A:B -> ok
61+
end.

tests/test.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,7 @@ struct Test tests[] = {
233233
TEST_CASE_EXPECTED(test_funs9, 3555),
234234
TEST_CASE_EXPECTED(test_funs10, 6817),
235235
TEST_CASE_EXPECTED(test_funs11, 817),
236+
TEST_CASE(test_funs12),
236237
TEST_CASE(test_make_fun3),
237238
TEST_CASE(fun_call_bif),
238239

0 commit comments

Comments
 (0)