Skip to content

Commit 812b1a9

Browse files
committed
[GR-66212] Use the wrapper at the right place where the Java->native call occurs
* Before this change the wrapper was called on the Ruby->Sulong boundary which is too early and meant we might longjmp() over Sulong (= Java) frames which is not supported by Native Image and the JVM.
1 parent c762d0f commit 812b1a9

File tree

4 files changed

+23
-11
lines changed

4 files changed

+23
-11
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ Bug fixes:
99
* Fix `Time.new` with String argument and handle nanoseconds correctly (#3836, @andrykonchin).
1010
* Fix a possible case of infinite recursion when implementing `frozen?` in a native extension (@nirvdrum).
1111
* Fix parameters forwarding to a method call executed with `Kernel#eval` (@andrykonchin).
12+
* Fix segfaults in native extensions when the reference processing thread is interrupted and would `longjmp()` incorrectly (#3903, @eregon).
1213

1314
Compatibility:
1415

lib/cext/ABI_check.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
1
1+
2

lib/truffle/truffle/cext.rb

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,16 @@ module Truffle::CExt
3838
end
3939

4040
SETUP_WRAPPERS = -> lib do
41-
# signature starts with an extra pointer which is for passing the native function to call
41+
# These wrappers must be used when calling native extension functions,
42+
# so that setjmp() is done before calling the native extension function,
43+
# and if the native extension function (potentially through other functions) calls a Ruby C API function
44+
# and if that throws a Java exception, then on return from that Ruby C API function a longjmp()
45+
# will be done to unwind the native stack.
46+
# It is essential that only native frames and no Java frames are unwinded with longjmp(),
47+
# which means these wrappers must be used exactly when calling into native functions, not before or after.
48+
# Sulong-executed C code counts as not-native in this context.
49+
50+
# The signature starts with an extra pointer which is for passing the native function to call
4251
POINTER_TO_POINTER_WRAPPERS = [nil] + (1..16).map do |n|
4352
sig = -"(pointer,#{(['pointer'] * n).join(',')}):pointer"
4453
Primitive.interop_eval_nfi(sig).bind(lib[:"rb_tr_setjmp_wrapper_pointer#{n}_to_pointer"])
@@ -1740,16 +1749,15 @@ def RTYPEDDATA(object)
17401749
if Truffle::Interop.null?(marker_function)
17411750
nil
17421751
else
1743-
-> { Primitive.interop_execute(POINTER_TO_VOID_WRAPPER, [marker_function, struct]) }
1752+
-> { Primitive.interop_execute(marker_function, [struct]) }
17441753
end
17451754
end
17461755

17471756
private def data_sizer(sizer_function, rtypeddata)
1748-
raise unless sizer_function.respond_to?(:call)
17491757
use_cext_lock = Primitive.use_cext_lock?
17501758
proc {
17511759
Primitive.call_with_cext_lock_and_frame(
1752-
POINTER_TO_SIZE_T_WRAPPER, [sizer_function, rtypeddata],
1760+
sizer_function, [rtypeddata],
17531761
Primitive.caller_special_variables_if_available, nil, use_cext_lock)
17541762
}
17551763
end
@@ -1792,7 +1800,7 @@ def rb_data_typed_object_wrap(ruby_class, data, data_type, mark, free, size)
17921800
end
17931801

17941802
def run_data_finalizer(function, data, use_cext_lock)
1795-
Primitive.call_with_cext_lock_and_frame POINTER_TO_VOID_WRAPPER, [function, data], nil, nil, use_cext_lock
1803+
Primitive.call_with_cext_lock_and_frame function, [data], nil, nil, use_cext_lock
17961804
end
17971805

17981806
def run_marker(obj)

src/main/c/cext/data.c

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,27 +29,30 @@ struct RTypedData* rb_tr_rtypeddata_create(const rb_data_type_t *data_type, void
2929
return result;
3030
}
3131

32+
void rb_tr_setjmp_wrapper_pointer1_to_void(void (*func)(VALUE arg), VALUE arg);
33+
size_t rb_tr_setjmp_wrapper_pointer1_to_size_t(size_t (*func)(const void *arg), const void *arg);
34+
3235
void rb_tr_rdata_run_marker(struct RData* rdata) {
3336
void* data = rdata->data;
3437
RUBY_DATA_FUNC dmark = rdata->dmark;
3538
if (data != NULL && dmark != NULL) {
36-
dmark(data);
39+
rb_tr_setjmp_wrapper_pointer1_to_void(dmark, data);
3740
}
3841
}
3942

4043
void rb_tr_rtypeddata_run_marker(struct RTypedData* rtypeddata) {
4144
void* data = rtypeddata->data;
4245
RUBY_DATA_FUNC dmark = rtypeddata->type->function.dmark;
4346
if (data != NULL && dmark != NULL) {
44-
dmark(data);
47+
rb_tr_setjmp_wrapper_pointer1_to_void(dmark, data);
4548
}
4649
}
4750

4851
size_t rb_tr_rtypeddata_run_memsizer(struct RTypedData* rtypeddata) {
4952
void* data = rtypeddata->data;
5053
size_t (*dsize)(const void *) = rtypeddata->type->function.dsize;
5154
if (data != NULL && dsize != NULL) {
52-
return dsize(data);
55+
return rb_tr_setjmp_wrapper_pointer1_to_size_t(dsize, data);
5356
} else {
5457
return 0;
5558
}
@@ -62,7 +65,7 @@ void rb_tr_rdata_run_finalizer(struct RData* rdata) {
6265
if (dfree == RUBY_DEFAULT_FREE) {
6366
ruby_xfree(data);
6467
} else if (dfree != NULL) {
65-
dfree(data);
68+
rb_tr_setjmp_wrapper_pointer1_to_void(dfree, data);
6669
}
6770
}
6871
// Also free the struct RData
@@ -76,7 +79,7 @@ void rb_tr_rtypeddata_run_finalizer(struct RTypedData* rtypeddata) {
7679
if (dfree == RUBY_DEFAULT_FREE) {
7780
ruby_xfree(data);
7881
} else if (dfree != NULL) {
79-
dfree(data);
82+
rb_tr_setjmp_wrapper_pointer1_to_void(dfree, data);
8083
}
8184
} // Also free the struct RTypedData
8285
free(rtypeddata);

0 commit comments

Comments
 (0)