Skip to content
This repository has been archived by the owner on Jun 8, 2021. It is now read-only.

Commit

Permalink
Merge pull request #257 from SimonSapin/user-data
Browse files Browse the repository at this point in the history
Add a safe API for user data
  • Loading branch information
GuillaumeGomez authored May 13, 2019
2 parents 786e294 + c252832 commit bc8d33d
Show file tree
Hide file tree
Showing 9 changed files with 193 additions and 47 deletions.
20 changes: 10 additions & 10 deletions cairo-sys-rs/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -431,8 +431,8 @@ extern "C" {
pub fn cairo_pattern_get_matrix(pattern: *mut cairo_pattern_t, matrix: *mut Matrix);
pub fn cairo_pattern_get_type(pattern: *mut cairo_pattern_t) -> cairo_pattern_type_t;
pub fn cairo_pattern_get_reference_count(pattern: *mut cairo_pattern_t) -> c_uint;
//pub fn cairo_pattern_set_user_data(pattern: *mut cairo_pattern_t, key: *mut cairo_user_data_key_t, user_data: *mut void, destroy: cairo_destroy_func_t) -> cairo_status_t;
//pub fn cairo_pattern_get_user_data(pattern: *mut cairo_pattern_t, key: *mut cairo_user_data_key_t) -> *mut void;
pub fn cairo_pattern_set_user_data(pattern: *mut cairo_pattern_t, key: *const cairo_user_data_key_t, user_data: *mut c_void, destroy: cairo_destroy_func_t) -> cairo_status_t;
pub fn cairo_pattern_get_user_data(pattern: *mut cairo_pattern_t, key: *const cairo_user_data_key_t) -> *mut c_void;

// CAIRO REGIONS
pub fn cairo_region_create() -> *mut cairo_region_t;
Expand Down Expand Up @@ -519,8 +519,8 @@ extern "C" {
pub fn cairo_font_face_status(font_face: *mut cairo_font_face_t) -> cairo_status_t;
pub fn cairo_font_face_get_type(font_face: *mut cairo_font_face_t) -> cairo_font_type_t;
pub fn cairo_font_face_get_reference_count(font_face: *mut cairo_font_face_t) -> c_uint;
//pub fn cairo_font_face_set_user_data(font_face: *mut cairo_font_face_t, key: *mut cairo_user_data_key_t, user_data: *mut void, destroy: cairo_destroy_func_t) -> cairo_status_t;
//pub fn cairo_font_face_get_user_data(font_face: *mut cairo_font_face_t, key: *mut cairo_user_data_key_t) -> *mut void;
pub fn cairo_font_face_set_user_data(font_face: *mut cairo_font_face_t, key: *const cairo_user_data_key_t, user_data: *mut c_void, destroy: cairo_destroy_func_t) -> cairo_status_t;
pub fn cairo_font_face_get_user_data(font_face: *mut cairo_font_face_t, key: *const cairo_user_data_key_t) -> *mut c_void;

// CAIRO SCALED FONT
pub fn cairo_scaled_font_create(font_face: *mut cairo_font_face_t, font_matrix: *const Matrix, ctm: *const Matrix, options: *const cairo_font_options_t) -> *mut cairo_scaled_font_t;
Expand All @@ -538,8 +538,8 @@ extern "C" {
pub fn cairo_scaled_font_get_scale_matrix(scaled_font: *mut cairo_scaled_font_t, scale_matrix: *mut Matrix);
pub fn cairo_scaled_font_get_type(scaled_font: *mut cairo_scaled_font_t) -> cairo_font_type_t;
pub fn cairo_scaled_font_get_reference_count(font_face: *mut cairo_scaled_font_t) -> c_uint;
//pub fn cairo_scaled_font_set_user_data(scaled_font: *mut cairo_scaled_font_t, key: *mut cairo_user_data_key_t, user_data: *mut void, destroy: cairo_destroy_func_t) -> cairo_status_t;
//pub fn cairo_scaled_font_get_user_data(scaled_font: *mut cairo_scaled_font_t, key: *mut cairo_user_data_key_t) -> *mut void;
pub fn cairo_scaled_font_set_user_data(scaled_font: *mut cairo_scaled_font_t, key: *const cairo_user_data_key_t, user_data: *mut c_void, destroy: cairo_destroy_func_t) -> cairo_status_t;
pub fn cairo_scaled_font_get_user_data(scaled_font: *mut cairo_scaled_font_t, key: *const cairo_user_data_key_t) -> *mut c_void;

// CAIRO FONT OPTIONS
pub fn cairo_font_options_create() -> *mut cairo_font_options_t;
Expand Down Expand Up @@ -580,8 +580,8 @@ extern "C" {
pub fn cairo_surface_status(surface: *mut cairo_surface_t) -> cairo_status_t;
pub fn cairo_surface_get_type(surface: *mut cairo_surface_t) -> cairo_surface_type_t;
pub fn cairo_surface_reference(surface: *mut cairo_surface_t) -> *mut cairo_surface_t;
pub fn cairo_surface_get_user_data(surface: *mut cairo_surface_t, key: *mut cairo_user_data_key_t) -> *mut c_void;
pub fn cairo_surface_set_user_data(surface: *mut cairo_surface_t, key: *mut cairo_user_data_key_t, user_data: *mut c_void, destroy: cairo_destroy_func_t) -> cairo_status_t;
pub fn cairo_surface_get_user_data(surface: *mut cairo_surface_t, key: *const cairo_user_data_key_t) -> *mut c_void;
pub fn cairo_surface_set_user_data(surface: *mut cairo_surface_t, key: *const cairo_user_data_key_t, user_data: *mut c_void, destroy: cairo_destroy_func_t) -> cairo_status_t;
pub fn cairo_surface_get_reference_count(surface: *mut cairo_surface_t) -> c_uint;
pub fn cairo_surface_mark_dirty(surface: *mut cairo_surface_t);
pub fn cairo_surface_create_similar(surface: *mut cairo_surface_t, content: cairo_content_t, width: c_int, height: c_int) -> *mut cairo_surface_t;
Expand Down Expand Up @@ -862,8 +862,8 @@ extern "C" {
pub fn cairo_device_get_type(device: *mut cairo_device_t) -> cairo_device_type_t;
pub fn cairo_device_reference(device: *mut cairo_device_t) -> *mut cairo_device_t;
pub fn cairo_device_get_reference_count(device: *mut cairo_device_t) -> c_uint;
// pub fn cairo_device_set_user_data() -> cairo_status_t;
// pub fn cairo_device_get_user_data() -> *mut c_void;
pub fn cairo_device_set_user_data(device: *mut cairo_device_t, key: *const cairo_user_data_key_t, user_data: *mut c_void, destroy: cairo_destroy_func_t) -> cairo_status_t;
pub fn cairo_device_get_user_data(device: *mut cairo_device_t, key: *const cairo_user_data_key_t) -> *mut c_void;
pub fn cairo_device_acquire(device: *mut cairo_device_t) -> cairo_status_t;
pub fn cairo_device_release(device: *mut cairo_device_t);
pub fn cairo_device_observer_elapsed(device: *mut cairo_device_t) -> c_double;
Expand Down
6 changes: 6 additions & 0 deletions src/device.rs
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,12 @@ impl Device {
}
}
}

user_data_methods! {
Device::to_raw_none,
ffi::cairo_device_get_user_data,
ffi::cairo_device_set_user_data,
}
}

#[cfg(feature = "use_glib")]
Expand Down
6 changes: 6 additions & 0 deletions src/font/font_face.rs
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,12 @@ impl FontFace {
ffi::cairo_ft_font_face_unset_synthesize(self.to_raw_none(), synth_flags.into())
}
}

user_data_methods! {
FontFace::to_raw_none,
ffi::cairo_font_face_get_user_data,
ffi::cairo_font_face_set_user_data,
}
}

#[cfg(not(feature = "use_glib"))]
Expand Down
6 changes: 6 additions & 0 deletions src/font/scaled_font.rs
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,12 @@ impl ScaledFont {

matrix
}

user_data_methods! {
ScaledFont::to_raw_none,
ffi::cairo_scaled_font_get_user_data,
ffi::cairo_scaled_font_set_user_data,
}
}

#[cfg(not(feature = "use_glib"))]
Expand Down
26 changes: 14 additions & 12 deletions src/image_surface.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// Licensed under the MIT license, see the LICENSE file or <http://opensource.org/licenses/MIT>

use std::ops::{Deref, DerefMut};
use std::rc::Rc;
use std::slice;

#[cfg(feature = "use_glib")]
Expand All @@ -14,7 +15,7 @@ use ::enums::{
};

use BorrowError;
use surface::{Surface, SurfaceExt, SurfacePriv};
use surface::{Surface, SurfaceExt};
use Status;
use std::fmt;

Expand Down Expand Up @@ -48,23 +49,26 @@ impl ImageSurface {

pub fn create_for_data<D: AsMut<[u8]> + 'static>(data: D, format: Format, width: i32, height: i32,
stride: i32) -> Result<ImageSurface, Status> {
let mut data: Box<AsMut<[u8]> + 'static> = Box::new(data);
let mut data: Box<dyn AsMut<[u8]>> = Box::new(data);

let (ptr, len) = {
let mut data = (*data).as_mut();
let mut data: &mut [u8] = (*data).as_mut();

(data.as_mut_ptr(), data.len())
};

assert!(len >= (height * stride) as usize);
unsafe {
let r = ImageSurface::from_raw_full(
ffi::cairo_image_surface_create_for_data(ptr, format.into(), width, height, stride));
match r {
Ok(surface) => surface.set_user_data(&IMAGE_SURFACE_DATA, Box::new(data)).map (|_| surface),
Err(status) => Err(status)
}
let result = unsafe {
ImageSurface::from_raw_full(
ffi::cairo_image_surface_create_for_data(ptr, format.into(), width, height, stride)
)
};
if let Ok(surface) = &result {
static IMAGE_SURFACE_DATA: crate::UserDataKey<Box<dyn AsMut<[u8]>>> =
crate::UserDataKey::new();
surface.set_user_data(&IMAGE_SURFACE_DATA, Rc::new(data))
}
result
}

pub fn get_data(&mut self) -> Result<ImageSurfaceData, BorrowError> {
Expand Down Expand Up @@ -103,8 +107,6 @@ impl ImageSurface {
}
}

static IMAGE_SURFACE_DATA: () = ();

#[cfg(feature = "use_glib")]
impl<'a> ToGlibPtr<'a, *mut ffi::cairo_surface_t> for ImageSurface {
type Storage = &'a Surface;
Expand Down
3 changes: 3 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@ macro_rules! gvalue_impl {
}
}

pub use user_data::UserDataKey;

pub use context::{
Context,
RectangleList,
Expand Down Expand Up @@ -150,6 +152,7 @@ pub use xcb::{

pub mod prelude;

#[macro_use] mod user_data;
mod constants;
pub use constants::*;
mod utils;
Expand Down
16 changes: 16 additions & 0 deletions src/patterns.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,14 @@ pub enum Pattern {
Mesh(Mesh),
}

impl Pattern {
user_data_methods! {
Pattern::as_ptr,
ffi::cairo_pattern_get_user_data,
ffi::cairo_pattern_set_user_data,
}
}

impl PatternTrait for Pattern {
type PatternType = Pattern;

Expand Down Expand Up @@ -170,6 +178,14 @@ macro_rules! pattern_type(
}
}

impl $pattern_type {
user_data_methods! {
$pattern_type::as_ptr,
ffi::cairo_pattern_get_user_data,
ffi::cairo_pattern_set_user_data,
}
}

impl Clone for $pattern_type {
fn clone(&self) -> Self {
$pattern_type {
Expand Down
36 changes: 11 additions & 25 deletions src/surface.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
// See the COPYRIGHT file at the top-level directory of this distribution.
// Licensed under the MIT license, see the LICENSE file or <http://opensource.org/licenses/MIT>

use std::mem;
use std::ptr;
use std::slice;
use libc::{c_ulong, c_void};
Expand Down Expand Up @@ -113,6 +112,11 @@ impl Surface {

let user_data = Box::into_raw(b);

unsafe extern "C" fn unbox<T>(data: *mut c_void) {
let data: Box<T> = Box::from_raw(data as *mut T);
drop(data);
}

let status = unsafe {
let mime_type = CString::new(mime_type).unwrap();
ffi::cairo_surface_set_mime_data(self.to_raw_none(),
Expand Down Expand Up @@ -196,6 +200,12 @@ impl Surface {
})
}
}

user_data_methods! {
Surface::to_raw_none,
ffi::cairo_surface_get_user_data,
ffi::cairo_surface_set_user_data,
}
}

#[cfg(feature = "use_glib")]
Expand Down Expand Up @@ -323,30 +333,6 @@ impl fmt::Display for MappedImageSurface {
}
}

pub(crate) trait SurfacePriv {
unsafe fn set_user_data<K, T>(&self, key: &K, data: Box<T>) -> Result<(), Status>;
}

impl<O: AsRef<Surface>> SurfacePriv for O {
unsafe fn set_user_data<K, T>(&self, key: &K, data: Box<T>) -> Result<(), Status> {
let ptr: *mut T = Box::into_raw(data);

assert_eq!(mem::size_of::<*mut c_void>(), mem::size_of_val(&ptr));

let status = ffi::cairo_surface_set_user_data(self.as_ref().0, key as *const _ as *mut _,
ptr as *mut c_void, Some(unbox::<T>));
match Status::from(status) {
Status::Success => Ok(()),
x => Err(x),
}
}
}

unsafe extern "C" fn unbox<T>(data: *mut c_void) {
let data: Box<T> = Box::from_raw(data as *mut T);
drop(data);
}

#[cfg(test)]
mod tests {
use Format;
Expand Down
121 changes: 121 additions & 0 deletions src/user_data.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
use std::marker::PhantomData;

use ffi::cairo_user_data_key_t;

/// A key for indexing user data in various cairo types.
///
/// Some types like [`Surface`] have `get_user_data`, `set_user_data`, and `remove_user_data`
/// methods that take `&'static UserDataKey`, where the address of that reference is significant.
///
/// To reliably have a stable address, the expected usage is to define a `static` item:
///
/// ```
/// use cairo::UserDataKey;
/// static FOO: UserDataKey<String> = UserDataKey::new();
///
/// # fn foo(surface: &cairo::Surface) {
/// surface.get_user_data(&FOO)
/// # ; }
pub struct UserDataKey<T> {
pub(crate) ffi: cairo_user_data_key_t,
marker: PhantomData<*const T>,
}

unsafe impl<T> Sync for UserDataKey<T> {}

impl<T> UserDataKey<T> {
pub const fn new() -> Self {
UserDataKey {
ffi: cairo_user_data_key_t { unused: 0 },
marker: PhantomData,
}
}
}

// In a safe API for user data we can’t make `get_user_data`
// transfer full ownership of the value to the caller (e.g. by returning `Box<T>`)
// because `self` still has a pointer to that value
// and `get_user_data` could be called again with the same key.
//
// We also can’t return a `&T` reference that borrows from `self`
// because the value could be removed with `remove_user_data` or replaced with `set_user_data`
// while the borrow still needs to be valid.
// (Borrowing with `&mut self` would not help as `Self` can be itself reference-counted.)
//
// Therefore the value must be reference-counted.
//
// We use `Rc` over `Arc` because the types implementing these methods are `!Send` and `!Sync`.
// See <https://github.com/gtk-rs/cairo/issues/256>

macro_rules! user_data_methods {
($as_ptr: expr, $ffi_get_user_data: path, $ffi_set_user_data: path,) => {
/// Attach user data to `self` for the given `key`.
pub fn set_user_data<T: 'static>(&self, key: &'static crate::UserDataKey<T>,
value: std::rc::Rc<T>)
{
unsafe extern "C" fn destructor<T>(ptr: *mut libc::c_void) {
let ptr: *const T = ptr as _;
drop(std::rc::Rc::from_raw(ptr))
}
// Safety:
//
// The destructor’s cast and `from_raw` are symetric
// with the `into_raw` and cast below.
// They both transfer ownership of one strong reference:
// neither of them touches the reference count.
let ptr: *const T = std::rc::Rc::into_raw(value);
let ptr = ptr as *mut T as *mut libc::c_void;
let result = unsafe {
$ffi_set_user_data($as_ptr(self), &key.ffi, ptr, Some(destructor::<T>))
};
Status::from(result).ensure_valid()
}

/// Return the user data previously attached to `self` with the given `key`, if any.
pub fn get_user_data<T: 'static>(&self, key: &'static crate::UserDataKey<T>)
-> Option<std::rc::Rc<T>>
{
// Safety:
//
// `Rc::from_raw` would normally take ownership of a strong reference for this pointer.
// But `self` still has a copy of that pointer and `get_user_data` can be called again
// with the same key.
// We use `ManuallyDrop` to avoid running the destructor of that first `Rc`,
// and return a cloned one (which increments the reference count).
//
// If `ffi_get_user_data` returns a non-null pointer,
// there was a previous call to `ffi_set_user_data` with a key with the same address.
// Either:
//
// * This was a call to a Rust `Self::set_user_data` method.
// Because that method takes a `&'static` reference,
// the key used then must live at that address until the end of the process.
// Because `UserDataKey<T>` has a non-zero size regardless of `T`,
// no other `UserDataKey<U>` value can have the same address.
// Therefore the `T` type was the same then at it is now and `cast` is type-safe.
//
// * Or, it is technically possible that the `set` call was to the C function directly,
// with a `cairo_user_data_key_t` in heap-allocated memory that was then freed,
// then `Box::new(UserDataKey::new()).leak()` was used to create a `&'static`
// that happens to have the same address because the allocator for `Box`
// reused that memory region.
// Since this involves a C (or FFI) call *and* is so far out of “typical” use
// of the user data functionality, we consider this a misuse of an unsafe API.
unsafe {
let ptr = $ffi_get_user_data($as_ptr(self), &key.ffi);
let ptr: *mut T = std::ptr::NonNull::new(ptr)?.cast().as_ptr();
let rc = std::mem::ManuallyDrop::new(std::rc::Rc::from_raw(ptr));
Some(std::rc::Rc::clone(&rc))
}
}

/// Unattach from `self` the user data associated with `key`, if any.
/// If there is no other `Rc` strong reference, the data is destroyed.
pub fn remove_user_data<T: 'static>(&self, key: &'static crate::UserDataKey<T>) {
let result = unsafe {
$ffi_set_user_data($as_ptr(self), &key.ffi, std::ptr::null_mut(), None)
};
Status::from(result).ensure_valid()
}
};
}

0 comments on commit bc8d33d

Please sign in to comment.