Skip to content

Commit 04d8f6f

Browse files
bors[bot]Hywan
andauthored
Merge #1809
1809: fix(c-api) Remove memory leaks and address `TODO`s r=Hywan a=Hywan ~Build on top of #1795. The real diff can be seen here until the base branch is merged: Hywan/wasmer@test-c-api-inline-c-rs...Hywan:fix-c-api-memory-leaks-externs.~ This PR does several things while reviewing the entire C API. It: * Removes several memory leaks (6 if I count correctly), * Addresses many `TODO`s, * Write a `DEVELOPMENT.md` document to uniformize the Rust patterns to handle C behaviors, * Apply what is written in `DEVELOPMENT.md`, * Allow more functions to handle null pointers, * Remove all `rustc` warnings. Co-authored-by: Ivan Enderlin <[email protected]>
2 parents f548f26 + b2ea42d commit 04d8f6f

22 files changed

+476
-270
lines changed

Cargo.lock

+4-4
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
+132
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
# Development Notes
2+
3+
## Ownerships
4+
5+
The [`wasm.h`] header thankfully defines the [`own`] “annotation”. It
6+
specifies _who_ owns _what_. For example, in the following code:
7+
8+
```c
9+
WASM_API_EXTERN own wasm_importtype_t* wasm_importtype_new(
10+
own wasm_name_t* module, own wasm_name_t* name, own wasm_externtype_t*);
11+
```
12+
13+
We must read that `wasm_importtype_new` takes the ownership of all its
14+
three arguments. This function is then responsible to free those
15+
data. We must also read that the returned value is owned by the caller
16+
of this function.
17+
18+
### Rust Pattern
19+
20+
This ownership property translates well in Rust. We have decided to
21+
use the `Box<T>` type to represent an owned pointer. `Box<T>` drops
22+
its content when it's dropped.
23+
24+
Consequently, apart from other patterns, the code above can be written
25+
as follows in Rust:
26+
27+
```rust
28+
#[no_mangle]
29+
pub extern "C" fn wasm_importtype_new(
30+
module: Box<wasm_name_t>,
31+
name: Box<wasm_name_t>,
32+
extern_type: Box<wasm_externtype_t>,
33+
) -> Box<wasm_importtype_t> {
34+
35+
}
36+
```
37+
38+
By reading the code, it is clear that `wasm_importtype_new` takes the
39+
ownership for `module`, `name`, and `extern_type`, and that the result
40+
is owned by the caller.
41+
42+
## `const *T`
43+
44+
A constant pointer can be interpreted in C as an immutable
45+
pointer. Without the [`own`] annotation, it means the ownership is not
46+
transfered anywhere (see [the Ownerships Section][#ownerships]).
47+
48+
### Rust Pattern
49+
50+
`const *T` translates to Rust as `&T`, it's a reference.
51+
52+
## Null Pointer
53+
54+
The [`wasm.h`] header does not say anything about null pointer. The
55+
behavior we agreed on in that passing a null pointer where it is not
56+
expected (i.e. everywhere) will make the function to return null too
57+
with no error.
58+
59+
### Rust Pattern
60+
61+
A nice type property in Rust is that it is possible to write
62+
`Option<NonNull<T>>` to nicely handle null pointer of kind `T`. For an
63+
argument, it translates as follows:
64+
65+
* When the given pointer is null, the argument holds `None`,
66+
* When the given pointer is not null, the arguments holds
67+
`Some(NonNull<T>)`.
68+
69+
Considering [the Ownerships Section][#ownerships], if the pointer is
70+
owned, we can also write `Option<Box<T>>`. This pattern is largely
71+
used in this codebase to represent a “nullable” owned
72+
pointer. Consequently, a code like:
73+
74+
```c
75+
WASM_API_EXTERN own wasm_importtype_t* wasm_importtype_new(
76+
own wasm_name_t* module, own wasm_name_t* name, own wasm_externtype_t*);
77+
```
78+
79+
translates into Rust as:
80+
81+
```rust
82+
#[no_mangle]
83+
pub extern "C" fn wasm_importtype_new(
84+
module: Option<Box<wasm_name_t>>,
85+
name: Option<Box<wasm_name_t>>,
86+
extern_type: Option<Box<wasm_externtype_t>>,
87+
) -> Option<Box<wasm_importtype_t>> {
88+
Some(Box::new(wasm_importtype_t {
89+
name: name?,
90+
module: module?,
91+
extern_type: extern_type?,
92+
}))
93+
}
94+
```
95+
96+
What `name?` (and others) means? It is basically [the `Try` trait
97+
implemented for
98+
`Option`](https://doc.rust-lang.org/std/ops/trait.Try.html#impl-Try):
99+
It returns `None` if the value is `None`, otherwise it unwraps the
100+
`Option`.
101+
102+
Because the function returns `Option<Box<T>>`, `None` represents a
103+
null pointer.
104+
105+
Considering [the `const *T` Section][#const-t], if the pointer is not
106+
owned, we can either write `Option<NonNull<T>>` or `Option<&T>`. It
107+
has been decided to use the second pattern in all the codebase.
108+
109+
## Destructors
110+
111+
The [`wasm.h`] defines `wasm_*_delete` functions. It represents destructors.
112+
113+
## Rust Pattern
114+
115+
The destructors in Rust translate as follow:
116+
117+
```rust
118+
#[no_mangle]
119+
pub unsafe extern "C" fn wasm_*_delete(_: Option<Box<wasm_*_t>>) {}
120+
```
121+
122+
`Box<T>` will take the ownership of the value. It means that Rust will
123+
drop it automatically as soon as it goes out of the
124+
scope. Consequently, the “C destructors” really are the “Rust
125+
destructors”.
126+
127+
The `Option` is here to handle the situation where a null pointer is
128+
passed to the destructor.
129+
130+
131+
[`own`](https://github.com/wasmerio/wasmer/blob/f548f268f2335693b97ad7ca08af72c320daf59a/lib/c-api/tests/wasm_c_api/wasm-c-api/include/wasm.h#L46-L65)
132+
[`wasm.h`](https://github.com/wasmerio/wasmer/blob/f548f268f2335693b97ad7ca08af72c320daf59a/lib/c-api/tests/wasm_c_api/wasm-c-api/include/wasm.h)

lib/c-api/src/wasm_c_api/engine.rs

+22-11
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use crate::error::{update_last_error, CApiError};
12
use cfg_if::cfg_if;
23
use std::sync::Arc;
34
use wasmer::Engine;
@@ -182,14 +183,24 @@ cfg_if! {
182183

183184
/// cbindgen:ignore
184185
#[no_mangle]
185-
pub unsafe extern "C" fn wasm_engine_delete(_wasm_engine_address: Option<Box<wasm_engine_t>>) {}
186+
pub unsafe extern "C" fn wasm_engine_delete(_engine: Option<Box<wasm_engine_t>>) {}
186187

187188
/// cbindgen:ignore
188189
#[no_mangle]
189190
pub extern "C" fn wasm_engine_new_with_config(
190191
config: Box<wasm_config_t>,
191192
) -> Option<Box<wasm_engine_t>> {
192-
// TODO: return useful error messages in failure branches
193+
fn return_with_error<M>(msg: M) -> Option<Box<wasm_engine_t>>
194+
where
195+
M: ToString,
196+
{
197+
update_last_error(CApiError {
198+
msg: msg.to_string(),
199+
});
200+
201+
return None;
202+
};
203+
193204
cfg_if! {
194205
if #[cfg(feature = "compiler")] {
195206
#[allow(unused_mut)]
@@ -199,7 +210,7 @@ pub extern "C" fn wasm_engine_new_with_config(
199210
if #[cfg(feature = "cranelift")] {
200211
Box::new(wasmer_compiler_cranelift::Cranelift::default())
201212
} else {
202-
return None;
213+
return return_with_error("Wasmer has not been compiled with the `cranelift` feature.");
203214
}
204215
}
205216
},
@@ -208,7 +219,7 @@ pub extern "C" fn wasm_engine_new_with_config(
208219
if #[cfg(feature = "llvm")] {
209220
Box::new(wasmer_compiler_llvm::LLVM::default())
210221
} else {
211-
return None;
222+
return return_with_error("Wasmer has not been compiled with the `llvm` feature.");
212223
}
213224
}
214225
},
@@ -217,7 +228,7 @@ pub extern "C" fn wasm_engine_new_with_config(
217228
if #[cfg(feature = "singlepass")] {
218229
Box::new(wasmer_compiler_singlepass::Singlepass::default())
219230
} else {
220-
return None;
231+
return return_with_error("Wasmer has not been compiled with the `singlepass` feature.");
221232
}
222233
}
223234
},
@@ -229,7 +240,7 @@ pub extern "C" fn wasm_engine_new_with_config(
229240
if #[cfg(feature = "jit")] {
230241
Arc::new(JIT::new(&*compiler_config).engine())
231242
} else {
232-
return None;
243+
return return_with_error("Wasmer has not been compiled with the `jit` feature.");
233244
}
234245
}
235246
},
@@ -238,7 +249,7 @@ pub extern "C" fn wasm_engine_new_with_config(
238249
if #[cfg(feature = "native")] {
239250
Arc::new(Native::new(&mut *compiler_config).engine())
240251
} else {
241-
return None;
252+
return return_with_error("Wasmer has not been compiled with the `native` feature.");
242253
}
243254
}
244255
},
@@ -249,7 +260,7 @@ pub extern "C" fn wasm_engine_new_with_config(
249260
if #[cfg(feature = "object-file")] {
250261
Arc::new(ObjectFile::headless().engine())
251262
} else {
252-
return None;
263+
return return_with_error("Wasmer has not been compiled with the `object-file` feature.");
253264
}
254265
}
255266
},
@@ -262,7 +273,7 @@ pub extern "C" fn wasm_engine_new_with_config(
262273
if #[cfg(feature = "jit")] {
263274
Arc::new(JIT::headless().engine())
264275
} else {
265-
return None;
276+
return return_with_error("Wasmer has not been compiled with the `jit` feature.");
266277
}
267278
}
268279
},
@@ -271,7 +282,7 @@ pub extern "C" fn wasm_engine_new_with_config(
271282
if #[cfg(feature = "native")] {
272283
Arc::new(Native::headless().engine())
273284
} else {
274-
return None;
285+
return return_with_error("Wasmer has not been compiled with the `native` feature.");
275286
}
276287
}
277288
},
@@ -280,7 +291,7 @@ pub extern "C" fn wasm_engine_new_with_config(
280291
if #[cfg(feature = "object-file")] {
281292
Arc::new(ObjectFile::headless().engine())
282293
} else {
283-
return None;
294+
return return_with_error("Wasmer has not been compiled with the `object-file` feature.");
284295
}
285296
}
286297
},

lib/c-api/src/wasm_c_api/externals/function.rs

+24-12
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ use std::ffi::c_void;
77
use std::sync::Arc;
88
use wasmer::{Function, Instance, RuntimeError, Val};
99

10+
#[derive(Debug)]
1011
#[allow(non_camel_case_types)]
1112
pub struct wasm_func_t {
1213
pub(crate) inner: Function,
@@ -32,11 +33,14 @@ pub type wasm_env_finalizer_t = unsafe extern "C" fn(c_void);
3233

3334
#[no_mangle]
3435
pub unsafe extern "C" fn wasm_func_new(
35-
store: &wasm_store_t,
36-
function_type: &wasm_functype_t,
37-
callback: wasm_func_callback_t,
36+
store: Option<&wasm_store_t>,
37+
function_type: Option<&wasm_functype_t>,
38+
callback: Option<wasm_func_callback_t>,
3839
) -> Option<Box<wasm_func_t>> {
39-
// TODO: handle null pointers?
40+
let store = store?;
41+
let function_type = function_type?;
42+
let callback = callback?;
43+
4044
let func_sig = &function_type.inner().function_type;
4145
let num_rets = func_sig.results().len();
4246
let inner_callback = move |args: &[Val]| -> Result<Vec<Val>, RuntimeError> {
@@ -84,13 +88,16 @@ pub unsafe extern "C" fn wasm_func_new(
8488

8589
#[no_mangle]
8690
pub unsafe extern "C" fn wasm_func_new_with_env(
87-
store: &wasm_store_t,
88-
function_type: &wasm_functype_t,
89-
callback: wasm_func_callback_with_env_t,
91+
store: Option<&wasm_store_t>,
92+
function_type: Option<&wasm_functype_t>,
93+
callback: Option<wasm_func_callback_with_env_t>,
9094
env: *mut c_void,
9195
finalizer: wasm_env_finalizer_t,
9296
) -> Option<Box<wasm_func_t>> {
93-
// TODO: handle null pointers?
97+
let store = store?;
98+
let function_type = function_type?;
99+
let callback = callback?;
100+
94101
let func_sig = &function_type.inner().function_type;
95102
let num_rets = func_sig.results().len();
96103
let inner_callback =
@@ -143,10 +150,13 @@ pub unsafe extern "C" fn wasm_func_delete(_func: Option<Box<wasm_func_t>>) {}
143150

144151
#[no_mangle]
145152
pub unsafe extern "C" fn wasm_func_call(
146-
func: &wasm_func_t,
147-
args: &wasm_val_vec_t,
153+
func: Option<&wasm_func_t>,
154+
args: Option<&wasm_val_vec_t>,
148155
results: &mut wasm_val_vec_t,
149156
) -> Option<Box<wasm_trap_t>> {
157+
let func = func?;
158+
let args = args?;
159+
150160
let params = args
151161
.into_slice()
152162
.map(|slice| {
@@ -200,6 +210,8 @@ pub unsafe extern "C" fn wasm_func_result_arity(func: &wasm_func_t) -> usize {
200210
}
201211

202212
#[no_mangle]
203-
pub extern "C" fn wasm_func_type(func: &wasm_func_t) -> Box<wasm_functype_t> {
204-
Box::new(wasm_functype_t::new(func.inner.ty().clone()))
213+
pub extern "C" fn wasm_func_type(func: Option<&wasm_func_t>) -> Option<Box<wasm_functype_t>> {
214+
let func = func?;
215+
216+
Some(Box::new(wasm_functype_t::new(func.inner.ty().clone())))
205217
}

0 commit comments

Comments
 (0)