Skip to content

Commit 5001144

Browse files
committed
Forward port changes from v0.6 release branch
Merge fixes from release-0.6 branch, such as OTP-21 CI fix (#1587), silent CodeQL warning (#1582), `is_function/2` guard fix and missing `is_function/2` BIF (#1592).
2 parents 8eae957 + 49c37fd commit 5001144

File tree

14 files changed

+174
-25
lines changed

14 files changed

+174
-25
lines changed

.github/workflows/publish-docs.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ jobs:
129129
git add "doc/${{ github.ref_name }}"
130130
git add .
131131
git diff --exit-code Production || echo "Going to commit"
132-
git diff --exit-code Production || git commit -m "Update Documentation"
132+
git diff --exit-code Production || git commit -m "Update Documentation for ${{ github.ref_name }}"
133133
git log -1
134134
- name: Push changes
135135
if: github.repository == 'atomvm/AtomVM'

CHANGELOG.md

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

4647
### Fixed
4748

@@ -87,6 +88,8 @@ memory error
8788
- Fixed concurrency and memory leak related to links and monitors
8889
- Fixed issues with parsing of line references for stack traces
8990
- Fixed memory corruption issue with `erlang:make_tuple/2`
91+
- Fix potential use after free with code generated from OTP <= 24
92+
- Fix `is_function/2` guard
9093

9194
### Changed
9295

CMakeModules/FindDoxygen.cmake

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
#
2+
# This file is part of AtomVM.
3+
#
4+
# Copyright 2021 Fred Dushin <[email protected]>
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+
find_program(DOXYGEN_PATH doxygen)
22+
23+
if (DOXYGEN_PATH)
24+
set(DOXYGEN_FOUND TRUE)
25+
set(DOXYGEN_BUILD_EXECUTABLE "${DOXYGEN_PATH}")
26+
elseif(DOXYGEN_FIND_REQUIRED)
27+
message(FATAL_ERROR "Doxygen command (doxygen) not found")
28+
endif()

doc/CMakeLists.txt

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -126,27 +126,30 @@ endif()
126126
##
127127
find_package(Sphinx)
128128
if(SPHINX_FOUND)
129+
find_package(Doxygen)
130+
if(DOXYGEN_FOUND)
131+
message("Doxygen found: ${DOXYGEN_BUILD_EXECUTABLE}")
129132
message("Sphinx found: ${SPHINX_BUILD_EXECUTABLE}")
130133
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/Doxyfile.in ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile @ONLY)
131134
file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/pdf_stylesheet.rts DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/)
132135
file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/pdf_template.rtt DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/)
133136
file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/html/apidocs/libatomvm)
134137
add_custom_target(sphinx-html
135-
${SPHINX_BUILD_EXECUTABLE} -q -b html -c ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_BINARY_DIR}/src/ ${CMAKE_CURRENT_BINARY_DIR}/html/
138+
${SPHINX_BUILD_EXECUTABLE} -q --doctree-dir ${CMAKE_CURRENT_BINARY_DIR}/doctree -b html -c ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_BINARY_DIR}/src/ ${CMAKE_CURRENT_BINARY_DIR}/html/
136139
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
137140
COMMENT "Generating Sphinx HTML documentation" VERBATIM
138141
DEPENDS ${DOTFILE_TARGETS} ${ERLANG_EDOC_TARGETS}
139142
)
140143

141144
add_custom_target(sphinx-pdf
142-
${SPHINX_BUILD_EXECUTABLE} -q -D exclude_patterns=apidocs/libatomvm/** -b rinoh -c ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_BINARY_DIR}/src/ ${CMAKE_CURRENT_BINARY_DIR}/pdf/
145+
${SPHINX_BUILD_EXECUTABLE} -q --doctree-dir ${CMAKE_CURRENT_BINARY_DIR}/doctree -D exclude_patterns=apidocs/libatomvm/** -b rinoh -c ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_BINARY_DIR}/src/ ${CMAKE_CURRENT_BINARY_DIR}/pdf/
143146
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
144147
COMMENT "Generating Sphinx PDF documentation" VERBATIM
145148
DEPENDS ${DOTFILE_TARGETS} ${ERLANG_EDOC_TARGETS}
146149
)
147150

148151
add_custom_target(sphinx-epub
149-
${SPHINX_BUILD_EXECUTABLE} -q -D exclude_patterns=apidocs/libatomvm/**,LICENSES/** -b epub -c ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_BINARY_DIR}/src/ ${CMAKE_CURRENT_BINARY_DIR}/epub/
152+
${SPHINX_BUILD_EXECUTABLE} -q --doctree-dir ${CMAKE_CURRENT_BINARY_DIR}/doctree -D exclude_patterns=apidocs/libatomvm/**,LICENSES/** -b epub -c ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_BINARY_DIR}/src/ ${CMAKE_CURRENT_BINARY_DIR}/epub/
150153
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
151154
COMMENT "Generating Sphinx EPub documentation" VERBATIM
152155
DEPENDS ${DOTFILE_TARGETS} ${ERLANG_EDOC_TARGETS}
@@ -162,6 +165,9 @@ if(SPHINX_FOUND)
162165
)
163166
endif()
164167

168+
else()
169+
message("Unable to find Doxygen -- no Sphinx documentation will be generated")
170+
endif()
165171
else()
166172
message("Unable to find Sphinx -- no Sphinx documentation will be generated")
167173
endif()

doc/Doxyfile.in

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2304,7 +2304,7 @@ DIRECTORY_GRAPH = YES
23042304
# The default value is: png.
23052305
# This tag requires that the tag HAVE_DOT is set to YES.
23062306

2307-
DOT_IMAGE_FORMAT = png
2307+
DOT_IMAGE_FORMAT = svg
23082308

23092309
# If DOT_IMAGE_FORMAT is set to svg, then this option can be set to YES to
23102310
# enable generation of interactive SVG images that allow zooming and panning.

doc/conf.py.in

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,8 @@ extensions = [
6060
'sphinx.ext.graphviz'
6161
]
6262

63+
graphviz_output_format = 'svg'
64+
6365
suppress_warnings = [
6466
'epub.unknown_project_files',
6567
'misc.highlighting_failure',
@@ -167,9 +169,11 @@ tag_list = sorted(repo.tags, key=lambda t: t.commit.committed_datetime)
167169
latest_tag = tag_list[-1]
168170
versions = list()
169171
release_list = list()
172+
omit_tag_list = [ 'v0.6.0-alpha.0', 'v0.6.0-alpha.1', 'v0.6.0-alpha.2', 'v0.6.0-beta.0', 'v0.6.0-beta.1', 'v0.6.0-rc.0' ]
170173
for tag in tag_list:
171-
versions.append(tag.name)
172-
release_list.append(tag.name)
174+
if tag.name not in omit_tag_list:
175+
versions.append(tag.name)
176+
release_list.append(tag.name)
173177

174178
omit_branch_list = [ 'release-0.5' ]
175179
branch_list = sorted(repo.branches, key=lambda t: t.commit.committed_datetime)

src/libAtomVM/bif.c

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

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -588,10 +588,10 @@ term context_get_monitor_pid(Context *ctx, uint64_t ref_ticks, bool *is_monitori
588588
if (monitor->ref_ticks == ref_ticks) {
589589
if ((monitor->monitor_obj & 0x3) == CONTEXT_MONITOR_MONITORED_PID_TAG) {
590590
*is_monitoring = false;
591-
return term_from_local_process_id(monitor->monitor_obj >> 4);
591+
return term_from_local_process_id((uint32_t) (monitor->monitor_obj >> 4));
592592
} else if ((monitor->monitor_obj & 0x3) == CONTEXT_MONITOR_MONITORING_PID_TAG) {
593593
*is_monitoring = true;
594-
return term_from_local_process_id(monitor->monitor_obj >> 4);
594+
return term_from_local_process_id((uint32_t) (monitor->monitor_obj >> 4));
595595
} else {
596596
return term_invalid_term();
597597
}

src/libAtomVM/opcodesswitch.h

Lines changed: 14 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4419,9 +4419,6 @@ HOT_FUNC int scheduler_entry_point(GlobalContext *glb)
44194419
uint32_t unit;
44204420
DECODE_LITERAL(unit, pc);
44214421
term src;
4422-
#ifdef IMPL_EXECUTE_LOOP
4423-
const uint8_t *src_pc = pc;
4424-
#endif
44254422
DECODE_COMPACT_TERM(src, pc)
44264423
term flags;
44274424
UNUSED(flags);
@@ -4449,8 +4446,10 @@ HOT_FUNC int scheduler_entry_point(GlobalContext *glb)
44494446

44504447
size_t src_size = term_binary_size(src);
44514448
TRIM_LIVE_REGS(live);
4449+
// there is always room for a MAX_REG + 1 register, used as working register
4450+
x_regs[live] = src;
44524451
// TODO: further investigate extra_val
4453-
if (UNLIKELY(memory_ensure_free_with_roots(ctx, src_size + term_binary_heap_size(size_val / 8) + extra_val, live, x_regs, MEMORY_CAN_SHRINK) != MEMORY_GC_OK)) {
4452+
if (UNLIKELY(memory_ensure_free_with_roots(ctx, src_size + term_binary_heap_size(size_val / 8) + extra_val, live + 1, x_regs, MEMORY_CAN_SHRINK) != MEMORY_GC_OK)) {
44544453
RAISE_ERROR(OUT_OF_MEMORY_ATOM);
44554454
}
44564455
#endif
@@ -4460,7 +4459,7 @@ HOT_FUNC int scheduler_entry_point(GlobalContext *glb)
44604459

44614460
#ifdef IMPL_EXECUTE_LOOP
44624461
TRACE("bs_append/8, fail=%u size=%li unit=%u src=0x%lx dreg=%c%i\n", (unsigned) fail, size_val, (unsigned) unit, src, T_DEST_REG(dreg));
4463-
DECODE_COMPACT_TERM(src, src_pc)
4462+
src = x_regs[live];
44644463
term t = term_create_empty_binary(src_size + size_val / 8, &ctx->heap, ctx->global);
44654464
memcpy((void *) term_binary_data(t), (void *) term_binary_data(src), src_size);
44664465

@@ -5186,9 +5185,6 @@ HOT_FUNC int scheduler_entry_point(GlobalContext *glb)
51865185
uint32_t fail;
51875186
DECODE_LABEL(fail, pc)
51885187
term src;
5189-
#ifdef IMPL_EXECUTE_LOOP
5190-
const uint8_t *src_pc = pc;
5191-
#endif
51925188
DECODE_COMPACT_TERM(src, pc);
51935189
uint32_t live;
51945190
DECODE_LITERAL(live, pc);
@@ -5240,8 +5236,10 @@ HOT_FUNC int scheduler_entry_point(GlobalContext *glb)
52405236
term_set_match_state_offset(src, bs_offset + size_val * unit);
52415237

52425238
TRIM_LIVE_REGS(live);
5239+
// there is always room for a MAX_REG + 1 register, used as working register
5240+
x_regs[live] = bs_bin;
52435241
size_t heap_size = term_sub_binary_heap_size(bs_bin, size_val);
5244-
if (UNLIKELY(memory_ensure_free_with_roots(ctx, heap_size, live, x_regs, MEMORY_CAN_SHRINK) != MEMORY_GC_OK)) {
5242+
if (UNLIKELY(memory_ensure_free_with_roots(ctx, heap_size, live + 1, x_regs, MEMORY_CAN_SHRINK) != MEMORY_GC_OK)) {
52455243
RAISE_ERROR(OUT_OF_MEMORY_ATOM);
52465244
}
52475245
#endif
@@ -5250,9 +5248,7 @@ HOT_FUNC int scheduler_entry_point(GlobalContext *glb)
52505248
DECODE_DEST_REGISTER(dreg, pc);
52515249

52525250
#ifdef IMPL_EXECUTE_LOOP
5253-
// re-compute src
5254-
DECODE_COMPACT_TERM(src, src_pc);
5255-
bs_bin = term_get_match_state_binary(src);
5251+
bs_bin = x_regs[live];
52565252

52575253
term t = term_maybe_create_sub_binary(bs_bin, bs_offset / unit, size_val, &ctx->heap, ctx->global);
52585254
WRITE_REGISTER(dreg, t);
@@ -5439,18 +5435,20 @@ HOT_FUNC int scheduler_entry_point(GlobalContext *glb)
54395435
DECODE_LABEL(label, pc)
54405436
term arg1;
54415437
DECODE_COMPACT_TERM(arg1, pc)
5442-
unsigned int arity;
5443-
DECODE_INTEGER(arity, pc)
5438+
term arity_term;
5439+
DECODE_COMPACT_TERM(arity_term, pc)
54445440

54455441
#ifdef IMPL_EXECUTE_LOOP
54465442
TRACE("is_function2/3, label=%i, arg1=%lx, arity=%i\n", label, arg1, arity);
54475443

5448-
if (term_is_function(arg1)) {
5444+
if (term_is_function(arg1) && term_is_integer(arity_term)) {
54495445
const term *boxed_value = term_to_const_term_ptr(arg1);
54505446

54515447
Module *fun_module = (Module *) boxed_value[1];
54525448
term index_or_module = boxed_value[2];
54535449

5450+
avm_int_t arity = term_to_int(arity_term);
5451+
54545452
uint32_t fun_arity;
54555453

54565454
if (term_is_atom(index_or_module)) {
@@ -5467,7 +5465,7 @@ HOT_FUNC int scheduler_entry_point(GlobalContext *glb)
54675465
fun_arity = fun_arity_and_freeze - fun_n_freeze;
54685466
}
54695467

5470-
if (arity != fun_arity) {
5468+
if ((arity < 0) || (arity != (avm_int_t) fun_arity)) {
54715469
pc = mod->labels[label];
54725470
}
54735471
} else {

tests/erlang_tests/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,7 @@ compile_erlang(test_funs8)
195195
compile_erlang(test_funs9)
196196
compile_erlang(test_funs10)
197197
compile_erlang(test_funs11)
198+
compile_erlang(test_funs12)
198199

199200
compile_erlang(test_make_fun3)
200201

@@ -676,6 +677,7 @@ add_custom_target(erlang_test_modules DEPENDS
676677
test_funs9.beam
677678
test_funs10.beam
678679
test_funs11.beam
680+
test_funs12.beam
679681

680682
test_make_fun3.beam
681683

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 <[email protected]>
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
@@ -234,6 +234,7 @@ struct Test tests[] = {
234234
TEST_CASE_EXPECTED(test_funs9, 3555),
235235
TEST_CASE_EXPECTED(test_funs10, 6817),
236236
TEST_CASE_EXPECTED(test_funs11, 817),
237+
TEST_CASE(test_funs12),
237238
TEST_CASE(test_make_fun3),
238239
TEST_CASE(fun_call_bif),
239240

0 commit comments

Comments
 (0)