Skip to content

Commit 81f24cc

Browse files
Merge pull request #37 from ruby/katei/fix-wrong-gc
Protect JS-wrapped Ruby objects to avoid use after free from JS side
2 parents d063777 + 392f565 commit 81f24cc

File tree

3 files changed

+27
-1
lines changed

3 files changed

+27
-1
lines changed

ext/js/js-core.c

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@ extern VALUE rb_cString;
1616
extern VALUE rb_cTrueClass;
1717
extern VALUE rb_cFalseClass;
1818

19+
// from js/js-core.c
20+
void rb_abi_lend_object(VALUE obj);
21+
1922
static VALUE rb_mJS;
2023
static VALUE rb_cJS_Object;
2124

@@ -312,6 +315,7 @@ static VALUE _rb_js_import_from_js(VALUE obj) {
312315
* Returns +obj+ wrapped by JS class RbValue.
313316
*/
314317
static VALUE _rb_js_obj_wrap(VALUE obj, VALUE wrapping) {
318+
rb_abi_lend_object(wrapping);
315319
return jsvalue_s_new(rb_js_abi_host_rb_object_to_js_rb_value((uint32_t)wrapping));
316320
}
317321

ext/witapi/witapi-core.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ static VALUE rb_abi_lend_object_internal(VALUE obj) {
9090
}
9191
return Qundef;
9292
}
93-
static void rb_abi_lend_object(VALUE obj) {
93+
void rb_abi_lend_object(VALUE obj) {
9494
RB_WASM_DEBUG_LOG("rb_abi_lend_object: obj = %p\n", (void *)obj);
9595
int state;
9696
RB_WASM_LIB_RT(rb_protect(rb_abi_lend_object_internal, obj, &state));

packages/npm-packages/ruby-wasm-wasi/test/js_from_rb.test.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { RbValue } from "../dist/index.umd";
12
import { initRubyVM } from "./init";
23

34
describe("Manipulation of JS from Ruby", () => {
@@ -208,6 +209,27 @@ describe("Manipulation of JS from Ruby", () => {
208209
expect(o1.toString()).toEqual(o1Clone.toString());
209210
});
210211

212+
test("Wrapped Ruby object should live until wrapper will be released", async () => {
213+
const vm = await initRubyVM();
214+
const run = vm.eval(`
215+
require "js"
216+
proc do |imports|
217+
imports.call(:mark_js_object_live, JS::Object.wrap(Object.new))
218+
end
219+
`);
220+
const livingObjects = new Set<RbValue>();
221+
run.call("call", vm.wrap({
222+
mark_js_object_live: (object: RbValue) => {
223+
livingObjects.add(object);
224+
}
225+
}));
226+
vm.eval("GC.start");
227+
for (const object of livingObjects) {
228+
// Ensure that all objects are still alive
229+
object.call("itself")
230+
}
231+
})
232+
211233
test("Guard null", async () => {
212234
const vm = await initRubyVM();
213235
const result = vm.eval(`

0 commit comments

Comments
 (0)