Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow c_void pointers as return and argument types in more cases (behind a feature flag) #478

Merged
merged 1 commit into from
Jul 31, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions crates/objc2/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).

### Added
* Allow using `MainThreadMarker` in `extern_methods!`.
* Added the feature flag `"relax-void-encoding"`, which when enabled, allows
using `*mut c_void` in a few places where you would otherwise have to
specify the encoding precisely.

### Changed
* Renamed `runtime` types:
Expand Down
7 changes: 7 additions & 0 deletions crates/objc2/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,13 @@ catch-all = ["exception"]
# Enable all verification steps when debug assertions are enabled.
verify = ["malloc"]

# Allow `*const c_void` and `*mut c_void` to be used as arguments and return
# types where other pointers were expected.
#
# This may be useful for CoreFoundation types, or for migrating code from objc
# to objc2.
relax-void-encoding = []

# Expose features that require linking to `libc::free`.
#
# This is not enabled by default because most users won't need it, and it
Expand Down
62 changes: 57 additions & 5 deletions crates/objc2/src/verify.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,22 @@ impl fmt::Display for VerificationError {

impl Error for VerificationError {}

/// Relaxed version of `Encoding::equivalent_to_box` that allows
/// `*mut c_void` and `*const c_void` to be used in place of other pointers.
///
/// Note: This is a top-level comparison; `*mut *mut c_void` or structures
/// containing `*mut c_void` are not allowed differently than usual.
fn relaxed_equivalent_to_box(encoding: &Encoding, expected: &EncodingBox) -> bool {
if cfg!(feature = "relax-void-encoding")
&& matches!(encoding, Encoding::Pointer(&Encoding::Void))
&& matches!(expected, EncodingBox::Pointer(_))
{
true
} else {
encoding.equivalent_to_box(expected)
}
}

pub(crate) fn verify_method_signature(
method: &Method,
args: &[Encoding],
Expand All @@ -80,7 +96,7 @@ pub(crate) fn verify_method_signature(

// TODO: Verify stack layout
let (expected, _stack_layout) = iter.extract_return()?;
if !ret.equivalent_to_box(&expected) {
if !relaxed_equivalent_to_box(ret, &expected) {
return Err(Inner::MismatchedReturn(expected, ret.clone()).into());
}

Expand All @@ -93,7 +109,7 @@ pub(crate) fn verify_method_signature(
if let Some(res) = iter.next() {
// TODO: Verify stack layout
let (expected, _stack_layout) = res?;
if !actual.equivalent_to_box(&expected) {
if !relaxed_equivalent_to_box(actual, &expected) {
return Err(Inner::MismatchedArgument(i, expected, actual.clone()).into());
}
} else {
Expand All @@ -118,9 +134,10 @@ pub(crate) fn verify_method_signature(
mod tests {
use super::*;
use crate::runtime::Sel;
use crate::sel;
use crate::test_utils;
use crate::{msg_send, sel};
use alloc::string::ToString;
use core::ffi::c_void;
use core::panic::{RefUnwindSafe, UnwindSafe};

#[test]
Expand Down Expand Up @@ -179,20 +196,55 @@ mod tests {
#[should_panic = "invalid message send to -[CustomObject foo]: expected return to have type code 'I', but found 'i'"]
fn test_send_message_verified() {
let obj = test_utils::custom_object();
let _: i32 = unsafe { crate::msg_send![&obj, foo] };
let _: i32 = unsafe { msg_send![&obj, foo] };
}

#[test]
#[cfg(debug_assertions)]
#[should_panic = "invalid message send to +[CustomObject abcDef]: method not found"]
fn test_send_message_verified_to_class() {
let cls = test_utils::custom_class();
let _: i32 = unsafe { crate::msg_send![cls, abcDef] };
let _: i32 = unsafe { msg_send![cls, abcDef] };
}

#[test]
fn test_marker_traits() {
fn assert_marker_traits<T: Send + Sync + UnwindSafe + RefUnwindSafe + Unpin>() {}
assert_marker_traits::<VerificationError>();
}

#[test]
fn test_get_reference() {
let mut obj = test_utils::custom_object();
let _: () = unsafe { msg_send![&mut obj, setFoo: 42u32] };

let res: &u32 = unsafe { msg_send![&obj, fooReference] };
assert_eq!(*res, 42);
let res: *const u32 = unsafe { msg_send![&obj, fooReference] };
assert_eq!(unsafe { *res }, 42);
let res: *mut u32 = unsafe { msg_send![&obj, fooReference] };
assert_eq!(unsafe { *res }, 42);
}

#[test]
#[cfg_attr(
all(debug_assertions, not(feature = "relax-void-encoding")),
should_panic = "invalid message send to -[CustomObject fooReference]: expected return to have type code '^I', but found '^v'"
)]
fn test_get_reference_void() {
let mut obj = test_utils::custom_object();
let _: () = unsafe { msg_send![&mut obj, setFoo: 42u32] };

let res: *mut c_void = unsafe { msg_send![&obj, fooReference] };
let res: *mut u32 = res.cast();
assert_eq!(unsafe { *res }, 42);
}

#[test]
#[cfg(debug_assertions)]
#[should_panic = "invalid message send to -[CustomObject foo]: expected return to have type code 'I', but found '^v'"]
fn test_get_integer_void() {
let obj = test_utils::custom_object();
let _: *mut c_void = unsafe { msg_send![&obj, foo] };
}
}
Loading