Skip to content

Commit cdd53ac

Browse files
committed
fix: add proper lifetime bounds for callbacks
1 parent d5dd45b commit cdd53ac

File tree

16 files changed

+420
-275
lines changed

16 files changed

+420
-275
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
2323
* `LayoutGrid::insert_at` no longer takes `left` and `height` arguments
2424
* Many APIs which took `u64` or `i64` arguments now take `i32` for wider compatibility
2525
* The semi-unstable `iui::draw` subsystem is again exported to downstream consumers of the `iui` crate.
26+
* `UI::queue_main` and `UI::on_should_quit` now require passed closures to be `'static`, for soundness
27+
* All callback registration functions require that their callbacks live at least as long as the `UI` token, for soundness
2628

2729
### Deprecated
2830

iui/src/callback_helpers.rs

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
use std::mem;
2+
use std::os::raw::c_void;
3+
4+
/// Transmutes a raw mutable pointer into a mutable reference.
5+
pub unsafe fn from_void_ptr<'ptr, F>(ptr: *mut c_void) -> &'ptr mut F {
6+
mem::transmute(ptr)
7+
}
8+
9+
/// Places any value on the heap, producing a heap pointer to it.
10+
/// Can leak memory if the pointer is never freed.
11+
pub fn to_heap_ptr<F>(item: F) -> *mut c_void {
12+
Box::into_raw(Box::new(item)) as *mut c_void
13+
}
14+
15+
#[cfg(test)]
16+
mod tests {
17+
use super::*;
18+
#[test]
19+
fn ptr_roundtripping() {
20+
let value: i32 = 1024;
21+
let expected: i32 = value.clone();
22+
23+
let ptr: *mut c_void = to_heap_ptr(value);
24+
println!("Reconstituting value from the heap at {:?}.", ptr);
25+
let actual: &mut i32 = unsafe { from_void_ptr::<i32>(ptr) };
26+
// This is so the memory will get freed.
27+
let boxed: Box<i32> = unsafe { Box::from_raw(ptr as *mut i32) };
28+
29+
assert_eq!(*actual, expected);
30+
assert_eq!(*boxed, expected);
31+
mem::forget(actual);
32+
}
33+
}

iui/src/compile_tests.rs

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
//! Examples of unsound code that IUI statically prevents from compiling.
2+
//!
3+
//! Here, we attempt to place use-after-free some callbacks.
4+
//!
5+
//! ```compile_fail
6+
//! let ev = iui::UI::init().unwrap();
7+
//!
8+
//! {
9+
//! let v = vec![1, 2, 3, 4];
10+
//! ev.queue_main(|| {
11+
//! for i in &v {
12+
//! println!("{}", i);
13+
//! }
14+
//! });
15+
//! }
16+
//!
17+
//! ev.quit();
18+
//! ev.main();
19+
//! ```
20+
//!
21+
//! ```compile_fail
22+
//! let ev = iui::UI::init().unwrap();
23+
//!
24+
//! {
25+
//! let v = vec![1, 2, 3, 4];
26+
//! ev.on_should_quit(|| {
27+
//! for i in &v {
28+
//! println!("{}", i);
29+
//! }
30+
//! });
31+
//! }
32+
//!
33+
//! ev.quit();
34+
//! ev.main();
35+
//! ```
36+
//!
37+
//! ```
38+
//! let ev = iui::UI::init().unwrap();
39+
//!
40+
//! let v = vec![1, 2, 3, 4];
41+
//! ev.on_should_quit(move || {
42+
//! for i in &v {
43+
//! println!("{}", i);
44+
//! }
45+
//! });
46+
//!
47+
//! ev.quit();
48+
//! ev.main();
49+
//! ```

iui/src/controls/basic.rs

Lines changed: 0 additions & 90 deletions
This file was deleted.

iui/src/controls/button.rs

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
use super::Control;
2+
use callback_helpers::{from_void_ptr, to_heap_ptr};
3+
use std::ffi::{CStr, CString};
4+
use std::mem;
5+
use std::os::raw::c_void;
6+
use ui::UI;
7+
use ui_sys::{self, uiButton, uiControl};
8+
9+
define_control!{
10+
/// A textual button which users can click on, causing a callback to run.
11+
rust_type: Button,
12+
sys_type: uiButton
13+
}
14+
15+
impl Button {
16+
/// Create a new button with the given text as its label.
17+
pub fn new(_ctx: &UI, text: &str) -> Button {
18+
unsafe {
19+
let c_string = CString::new(text.as_bytes().to_vec()).unwrap();
20+
Button::from_raw(ui_sys::uiNewButton(c_string.as_ptr()))
21+
}
22+
}
23+
24+
/// Get a copy of the existing text on the button.
25+
pub fn text(&self, _ctx: &UI) -> String {
26+
unsafe {
27+
CStr::from_ptr(ui_sys::uiButtonText(self.uiButton))
28+
.to_string_lossy()
29+
.into_owned()
30+
}
31+
}
32+
33+
/// Get a reference to the existing text on the button.
34+
pub fn text_ref(&self, _ctx: &UI) -> &CStr {
35+
unsafe { CStr::from_ptr(ui_sys::uiButtonText(self.uiButton)) }
36+
}
37+
38+
/// Set the text on the button.
39+
pub fn set_text(&mut self, _ctx: &UI, text: &str) {
40+
unsafe {
41+
let c_string = CString::new(text.as_bytes().to_vec()).unwrap();
42+
ui_sys::uiButtonSetText(self.uiButton, c_string.as_ptr())
43+
}
44+
}
45+
46+
/// Run the given callback when the button is clicked.
47+
pub fn on_clicked<'ctx, F>(&mut self, _ctx: &'ctx UI, callback: F)
48+
where
49+
F: FnMut(&mut Button) + 'ctx,
50+
{
51+
extern "C" fn c_callback<G>(button: *mut uiButton, data: *mut c_void)
52+
where
53+
G: FnMut(&mut Button),
54+
{
55+
let mut button = Button { uiButton: button };
56+
unsafe {
57+
from_void_ptr::<G>(data)(&mut button);
58+
}
59+
}
60+
unsafe {
61+
ui_sys::uiButtonOnClicked(self.uiButton, Some(c_callback::<F>), to_heap_ptr(callback));
62+
}
63+
}
64+
}

iui/src/controls/create_macro.rs

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
1-
// Defines a new control, creating a Rust wrapper, a `Deref` implementation, and a destructor.
2-
// An example of use:
3-
//
4-
// define_control!{
5-
// /// Some documentation
6-
// #[attribute(whatever="something")]
7-
// rust_type: Slider,
8-
// sys_type: uiSlider,
9-
// }
1+
/// Defines a new control, creating a Rust wrapper, a `Deref` implementation, and a destructor.
2+
/// An example of use:
3+
/// ```ignore
4+
/// define_control!{
5+
/// /// Some documentation
6+
/// #[attribute(whatever="something")]
7+
/// rust_type: Slider,
8+
/// sys_type: uiSlider,
9+
/// }
10+
/// ```
1011
macro_rules! define_control {
1112
// Match first any attributes (incl. doc comments) and then the actual invocation
1213
{$(#[$attr:meta])* rust_type: $rust_type:ident, sys_type: $sys_type:ident$(,)* } => {

0 commit comments

Comments
 (0)