Repository: dtolnay/cxx
Pull request #474 (2020-11-16 01:18:43)

Enforce mutable references to opaque extern C++ types are pinned
Without this, it's possible for Rust code to obtain two different `&mut T` to an opaque extern C++ type `T` and do something like e.g. [swap](https://doc.rust-lang.org/std/mem/fn.swap.html) them, which shouldn't work because Rust does not have the size of the underlying C++ `T`.


Patch:
diff --git a/gen/build/src/lib.rs b/gen/build/src/lib.rs
index 5c6297a7..d2a04587 100644
--- a/gen/build/src/lib.rs
+++ b/gen/build/src/lib.rs
@@ -50,6 +50,7 @@
     clippy::inherent_to_string,
     clippy::needless_doctest_main,
     clippy::new_without_default,
+    clippy::nonminimal_bool,
     clippy::or_fun_call,
     clippy::toplevel_ref_arg
 )]
diff --git a/gen/cmd/src/main.rs b/gen/cmd/src/main.rs
index c7230393..e6070308 100644
--- a/gen/cmd/src/main.rs
+++ b/gen/cmd/src/main.rs
@@ -3,6 +3,7 @@
     clippy::inherent_to_string,
     clippy::large_enum_variant,
     clippy::new_without_default,
+    clippy::nonminimal_bool,
     clippy::or_fun_call,
     clippy::toplevel_ref_arg
 )]
diff --git a/gen/lib/src/lib.rs b/gen/lib/src/lib.rs
index 963e8707..506b0072 100644
--- a/gen/lib/src/lib.rs
+++ b/gen/lib/src/lib.rs
@@ -11,6 +11,7 @@
 #![allow(
     clippy::inherent_to_string,
     clippy::new_without_default,
+    clippy::nonminimal_bool,
     clippy::or_fun_call,
     clippy::toplevel_ref_arg
 )]
diff --git a/macro/src/expand.rs b/macro/src/expand.rs
index 903585ec..bb935890 100644
--- a/macro/src/expand.rs
+++ b/macro/src/expand.rs
@@ -9,6 +9,7 @@ use crate::syntax::{
 };
 use proc_macro2::{Ident, Span, TokenStream};
 use quote::{format_ident, quote, quote_spanned, ToTokens};
+use std::collections::BTreeSet as Set;
 use std::mem;
 use syn::{parse_quote, Result, Token};
 
@@ -59,6 +60,7 @@ fn expand(ffi: Module, apis: &[Api], types: &Types) -> TokenStream {
         }
     }
 
+    let mut expanded_unique_ptr = Set::new();
     for ty in types {
         let explicit_impl = types.explicit_impls.get(ty);
         if let Type::RustBox(ty) = ty {
@@ -77,6 +79,7 @@ fn expand(ffi: Module, apis: &[Api], types: &Types) -> TokenStream {
             if let Type::Ident(ident) = &ptr.inner {
                 if Atom::from(&ident.rust).is_none()
                     && (explicit_impl.is_some() || !types.aliases.contains_key(&ident.rust))
+                    && expanded_unique_ptr.insert(&ident.rust)
                 {
                     expanded.extend(expand_unique_ptr(ident, types, explicit_impl));
                 }
@@ -244,10 +247,15 @@ fn expand_cxx_function_shim(efn: &ExternFn, types: &Types) -> TokenStream {
     let doc = &efn.doc;
     let decl = expand_cxx_function_decl(efn, types);
     let receiver = efn.receiver.iter().map(|receiver| {
-        let ampersand = receiver.ampersand;
-        let mutability = receiver.mutability;
         let var = receiver.var;
-        quote!(#ampersand #mutability #var)
+        if receiver.pinned {
+            let ty = receiver.ty();
+            quote!(#var: #ty)
+        } else {
+            let ampersand = receiver.ampersand;
+            let mutability = receiver.mutability;
+            quote!(#ampersand #mutability #var)
+        }
     });
     let args = efn.args.iter().map(|arg| quote!(#arg));
     let all_args = receiver.chain(args);
@@ -272,7 +280,10 @@ fn expand_cxx_function_shim(efn: &ExternFn, types: &Types) -> TokenStream {
                 quote!(#var.as_mut_ptr() as *const ::cxx::private::RustString)
             }
             Type::RustBox(_) => quote!(::std::boxed::Box::into_raw(#var)),
-            Type::UniquePtr(_) => quote!(::cxx::UniquePtr::into_raw(#var)),
+            Type::UniquePtr(ptr) => match ptr.pinned {
+                false => quote!(::cxx::UniquePtr::into_raw(#var)),
+                true => quote!(::cxx::UniquePtr::__pin_into_raw(#var)),
+            },
             Type::RustVec(_) => quote!(#var.as_mut_ptr() as *const ::cxx::private::RustVec<_>),
             Type::Ref(ty) => match &ty.inner {
                 Type::Ident(ident) if ident.rust == RustString => match ty.mutability {
@@ -366,7 +377,10 @@ fn expand_cxx_function_shim(efn: &ExternFn, types: &Types) -> TokenStream {
                         quote!(#call.into_vec())
                     }
                 }
-                Type::UniquePtr(_) => quote!(::cxx::UniquePtr::from_raw(#call)),
+                Type::UniquePtr(ptr) => match ptr.pinned {
+                    false => quote!(::cxx::UniquePtr::from_raw(#call)),
+                    true => quote!(::cxx::UniquePtr::__pin_from_raw(#call)),
+                },
                 Type::Ref(ty) => match &ty.inner {
                     Type::Ident(ident) if ident.rust == RustString => match ty.mutability {
                         None => quote!(#call.as_string()),
@@ -539,7 +553,10 @@ fn expand_rust_function_shim_impl(
                     quote!(::std::mem::take((*#ident).as_mut_vec()))
                 }
             }
-            Type::UniquePtr(_) => quote!(::cxx::UniquePtr::from_raw(#ident)),
+            Type::UniquePtr(ptr) => match ptr.pinned {
+                false => quote!(::cxx::UniquePtr::from_raw(#ident)),
+                true => quote!(::cxx::UniquePtr::__pin_from_raw(#ident)),
+            },
             Type::Ref(ty) => match &ty.inner {
                 Type::Ident(i) if i.rust == RustString => match ty.mutability {
                     None => quote!(#ident.as_string()),
@@ -583,7 +600,10 @@ fn expand_rust_function_shim_impl(
                 Some(quote!(::cxx::private::RustVec::from))
             }
         }
-        Type::UniquePtr(_) => Some(quote!(::cxx::UniquePtr::into_raw)),
+        Type::UniquePtr(ptr) => match ptr.pinned {
+            false => Some(quote!(::cxx::UniquePtr::into_raw)),
+            true => Some(quote!(::cxx::UniquePtr::__pin_into_raw)),
+        },
         Type::Ref(ty) => match &ty.inner {
             Type::Ident(ident) if ident.rust == RustString => match ty.mutability {
                 None => Some(quote!(::cxx::private::RustString::from_ref)),
diff --git a/macro/src/lib.rs b/macro/src/lib.rs
index 852aacc9..1de7e6cf 100644
--- a/macro/src/lib.rs
+++ b/macro/src/lib.rs
@@ -2,6 +2,7 @@
     clippy::inherent_to_string,
     clippy::large_enum_variant,
     clippy::new_without_default,
+    clippy::nonminimal_bool,
     clippy::or_fun_call,
     clippy::toplevel_ref_arg,
     clippy::useless_let_if_seq
diff --git a/src/unique_ptr.rs b/src/unique_ptr.rs
index 3a5fc669..283c2f15 100644
--- a/src/unique_ptr.rs
+++ b/src/unique_ptr.rs
@@ -7,6 +7,7 @@ use core::fmt::{self, Debug, Display};
 use core::marker::PhantomData;
 use core::mem;
 use core::ops::{Deref, DerefMut};
+use core::pin::Pin;
 use core::ptr;
 
 /// Binding to C++ `std::unique_ptr<T, std::default_delete<T>>`.
@@ -87,6 +88,16 @@ where
             ty: PhantomData,
         }
     }
+
+    #[doc(hidden)]
+    pub unsafe fn __pin_into_raw(ptr: Pin<UniquePtr<T>>) -> *mut T {
+        Self::into_raw(Pin::into_inner_unchecked(ptr))
+    }
+
+    #[doc(hidden)]
+    pub unsafe fn __pin_from_raw(raw: *mut T) -> Pin<Self> {
+        Pin::new_unchecked(Self::from_raw(raw))
+    }
 }
 
 unsafe impl<T> Send for UniquePtr<T> where T: Send + UniquePtrTarget {}
diff --git a/syntax/check.rs b/syntax/check.rs
index 73ce8856..7e368f45 100644
--- a/syntax/check.rs
+++ b/syntax/check.rs
@@ -151,6 +151,24 @@ fn check_type_ref(cx: &mut Check, ty: &Ref) {
         cx.error(ty, "references with explicit lifetimes are not supported");
     }
 
+    if ty.mutability.is_some() && !ty.pinned {
+        if let Some(requires_pin) = match &ty.inner {
+            Type::Ident(ident) if ident.rust == CxxString || is_opaque_cxx(cx, &ident.rust) => {
+                Some(ident.rust.to_string())
+            }
+            Type::CxxVector(_) => Some("CxxVector<...>".to_owned()),
+            _ => None,
+        } {
+            cx.error(
+                ty,
+                format!(
+                    "mutable reference to C++ type requires a pin -- use Pin<&mut {}>",
+                    requires_pin,
+                ),
+            );
+        }
+    }
+
     match ty.inner {
         Type::Fn(_) | Type::Void(_) => {}
         Type::Ref(_) => {
@@ -251,6 +269,17 @@ fn check_api_fn(cx: &mut Check, efn: &ExternFn) {
             && !cx.types.rust.contains(&receiver.ty.rust)
         {
             cx.error(span, "unrecognized receiver type");
+        } else if receiver.mutability.is_some()
+            && !receiver.pinned
+            && is_opaque_cxx(cx, &receiver.ty.rust)
+        {
+            cx.error(
+                span,
+                format!(
+                    "mutable reference to C++ type requires a pin -- use `self: Pin<&mut {}>`",
+                    receiver.ty.rust,
+                ),
+            );
         }
 
         if receiver.lifetime.is_some() {
@@ -370,13 +399,14 @@ fn is_unsized(cx: &mut Check, ty: &Type) -> bool {
         Type::CxxVector(_) | Type::Slice(_) | Type::Void(_) => return true,
         _ => return false,
     };
-    ident == CxxString
-        || cx.types.cxx.contains(ident)
-            && !cx.types.structs.contains_key(ident)
-            && !cx.types.enums.contains_key(ident)
-            && !(cx.types.aliases.contains_key(ident)
-                && cx.types.required_trivial.contains_key(ident))
-        || cx.types.rust.contains(ident)
+    ident == CxxString || is_opaque_cxx(cx, ident) || cx.types.rust.contains(ident)
+}
+
+fn is_opaque_cxx(cx: &mut Check, ty: &Ident) -> bool {
+    cx.types.cxx.contains(ty)
+        && !cx.types.structs.contains_key(ty)
+        && !cx.types.enums.contains_key(ty)
+        && !(cx.types.aliases.contains_key(ty) && cx.types.required_trivial.contains_key(ty))
 }
 
 fn span_for_struct_error(strct: &Struct) -> TokenStream {
diff --git a/syntax/impls.rs b/syntax/impls.rs
index a4b393ae..58d8fd33 100644
--- a/syntax/impls.rs
+++ b/syntax/impls.rs
@@ -81,29 +81,36 @@ impl Eq for Ty1 {}
 impl PartialEq for Ty1 {
     fn eq(&self, other: &Ty1) -> bool {
         let Ty1 {
+            pinned,
             name,
             langle: _,
             inner,
             rangle: _,
+            pin_tokens: _,
         } = self;
         let Ty1 {
+            pinned: pinned2,
             name: name2,
             langle: _,
             inner: inner2,
             rangle: _,
+            pin_tokens: _,
         } = other;
-        name == name2 && inner == inner2
+        pinned == pinned2 && name == name2 && inner == inner2
     }
 }
 
 impl Hash for Ty1 {
     fn hash<H: Hasher>(&self, state: &mut H) {
         let Ty1 {
+            pinned,
             name,
             langle: _,
             inner,
             rangle: _,
+            pin_tokens: _,
         } = self;
+        pinned.hash(state);
         name.hash(state);
         inner.hash(state);
     }
@@ -114,29 +121,39 @@ impl Eq for Ref {}
 impl PartialEq for Ref {
     fn eq(&self, other: &Ref) -> bool {
         let Ref {
+            pinned,
             ampersand: _,
             lifetime,
             mutability,
             inner,
+            pin_tokens: _,
         } = self;
         let Ref {
+            pinned: pinned2,
             ampersand: _,
             lifetime: lifetime2,
             mutability: mutability2,
             inner: inner2,
+            pin_tokens: _,
         } = other;
-        lifetime == lifetime2 && mutability.is_some() == mutability2.is_some() && inner == inner2
+        pinned == pinned2
+            && lifetime == lifetime2
+            && mutability.is_some() == mutability2.is_some()
+            && inner == inner2
     }
 }
 
 impl Hash for Ref {
     fn hash<H: Hasher>(&self, state: &mut H) {
         let Ref {
+            pinned,
             ampersand: _,
             lifetime,
             mutability,
             inner,
+            pin_tokens: _,
         } = self;
+        pinned.hash(state);
         lifetime.hash(state);
         mutability.is_some().hash(state);
         inner.hash(state);
@@ -223,35 +240,45 @@ impl Eq for Receiver {}
 impl PartialEq for Receiver {
     fn eq(&self, other: &Receiver) -> bool {
         let Receiver {
+            pinned,
             ampersand: _,
             lifetime,
             mutability,
             var: _,
             ty,
             shorthand: _,
+            pin_tokens: _,
         } = self;
         let Receiver {
+            pinned: pinned2,
             ampersand: _,
             lifetime: lifetime2,
             mutability: mutability2,
             var: _,
             ty: ty2,
             shorthand: _,
+            pin_tokens: _,
         } = other;
-        lifetime == lifetime2 && mutability.is_some() == mutability2.is_some() && ty == ty2
+        pinned == pinned2
+            && lifetime == lifetime2
+            && mutability.is_some() == mutability2.is_some()
+            && ty == ty2
     }
 }
 
 impl Hash for Receiver {
     fn hash<H: Hasher>(&self, state: &mut H) {
         let Receiver {
+            pinned,
             ampersand: _,
             lifetime,
             mutability,
             var: _,
             ty,
             shorthand: _,
+            pin_tokens: _,
         } = self;
+        pinned.hash(state);
         lifetime.hash(state);
         mutability.is_some().hash(state);
         ty.hash(state);
diff --git a/syntax/mod.rs b/syntax/mod.rs
index 94c2cc85..853db90b 100644
--- a/syntax/mod.rs
+++ b/syntax/mod.rs
@@ -134,12 +134,14 @@ pub struct Var {
 }
 
 pub struct Receiver {
+    pub pinned: bool,
     pub ampersand: Token![&],
     pub lifetime: Option<Lifetime>,
     pub mutability: Option<Token![mut]>,
     pub var: Token![self],
     pub ty: ResolvableName,
     pub shorthand: bool,
+    pub pin_tokens: Option<(kw::Pin, Token![<], Token![>])>,
 }
 
 pub struct Variant {
@@ -163,17 +165,21 @@ pub enum Type {
 }
 
 pub struct Ty1 {
+    pub pinned: bool,
     pub name: Ident,
     pub langle: Token![<],
     pub inner: Type,
     pub rangle: Token![>],
+    pub pin_tokens: Option<(kw::Pin, Token![<], Token![>])>,
 }
 
 pub struct Ref {
+    pub pinned: bool,
     pub ampersand: Token![&],
     pub lifetime: Option<Lifetime>,
     pub mutability: Option<Token![mut]>,
     pub inner: Type,
+    pub pin_tokens: Option<(kw::Pin, Token![<], Token![>])>,
 }
 
 pub struct Slice {
diff --git a/syntax/parse.rs b/syntax/parse.rs
index a8a76d01..60104924 100644
--- a/syntax/parse.rs
+++ b/syntax/parse.rs
@@ -18,6 +18,7 @@ use syn::{
 };
 
 pub mod kw {
+    syn::custom_keyword!(Pin);
     syn::custom_keyword!(Result);
 }
 
@@ -387,12 +388,14 @@ fn parse_extern_fn(
             FnArg::Receiver(arg) => {
                 if let Some((ampersand, lifetime)) = &arg.reference {
                     receiver = Some(Receiver {
+                        pinned: false,
                         ampersand: *ampersand,
                         lifetime: lifetime.clone(),
                         mutability: arg.mutability,
                         var: arg.self_token,
                         ty: ResolvableName::make_self(arg.self_token.span),
                         shorthand: true,
+                        pin_tokens: None,
                     });
                     continue;
                 }
@@ -417,12 +420,14 @@ fn parse_extern_fn(
                 if let Type::Ref(reference) = ty {
                     if let Type::Ident(ident) = reference.inner {
                         receiver = Some(Receiver {
+                            pinned: reference.pinned,
                             ampersand: reference.ampersand,
                             lifetime: reference.lifetime,
                             mutability: reference.mutability,
                             var: Token![self](ident.rust.span()),
                             ty: ident,
                             shorthand: false,
+                            pin_tokens: reference.pin_tokens,
                         });
                         continue;
                     }
@@ -625,10 +630,12 @@ fn parse_type_reference(ty: &TypeReference, namespace: &Namespace) -> Result<Typ
         _ => Type::Ref,
     };
     Ok(which(Box::new(Ref {
+        pinned: false,
         ampersand: ty.and_token,
         lifetime: ty.lifetime.clone(),
         mutability: ty.mutability,
         inner,
+        pin_tokens: None,
     })))
 }
 
@@ -644,42 +651,66 @@ fn parse_type_path(ty: &TypePath, namespace: &Namespace) -> Result<Type> {
                     if let GenericArgument::Type(arg) = &generic.args[0] {
                         let inner = parse_type(arg, namespace)?;
                         return Ok(Type::UniquePtr(Box::new(Ty1 {
+                            pinned: false,
                             name: ident,
                             langle: generic.lt_token,
                             inner,
                             rangle: generic.gt_token,
+                            pin_tokens: None,
                         })));
                     }
                 } else if ident == "CxxVector" && generic.args.len() == 1 {
                     if let GenericArgument::Type(arg) = &generic.args[0] {
                         let inner = parse_type(arg, namespace)?;
                         return Ok(Type::CxxVector(Box::new(Ty1 {
+                            pinned: false,
                             name: ident,
                             langle: generic.lt_token,
                             inner,
                             rangle: generic.gt_token,
+                            pin_tokens: None,
                         })));
                     }
                 } else if ident == "Box" && generic.args.len() == 1 {
                     if let GenericArgument::Type(arg) = &generic.args[0] {
                         let inner = parse_type(arg, namespace)?;
                         return Ok(Type::RustBox(Box::new(Ty1 {
+                            pinned: false,
                             name: ident,
                             langle: generic.lt_token,
                             inner,
                             rangle: generic.gt_token,
+                            pin_tokens: None,
                         })));
                     }
                 } else if ident == "Vec" && generic.args.len() == 1 {
                     if let GenericArgument::Type(arg) = &generic.args[0] {
                         let inner = parse_type(arg, namespace)?;
                         return Ok(Type::RustVec(Box::new(Ty1 {
+                            pinned: false,
                             name: ident,
                             langle: generic.lt_token,
                             inner,
                             rangle: generic.gt_token,
+                            pin_tokens: None,
                         })));
                     }
+                } else if ident == "Pin" && generic.args.len() == 1 {
+                    if let GenericArgument::Type(arg) = &generic.args[0] {
+                        let inner = parse_type(arg, namespace)?;
+                        let pin_token = kw::Pin(ident.span());
+                        if let Type::Ref(mut inner) = inner {
+                            inner.pinned = true;
+                            inner.pin_tokens =
+                                Some((pin_token, generic.lt_token, generic.gt_token));
+                            return Ok(Type::Ref(inner));
+                        } else if let Type::UniquePtr(mut inner) = inner {
+                            inner.pinned = true;
+                            inner.pin_tokens =
+                                Some((pin_token, generic.lt_token, generic.gt_token));
+                            return Ok(Type::UniquePtr(inner));
+                        }
+                    }
                 }
             }
             PathArguments::Parenthesized(_) => {}
diff --git a/syntax/tokens.rs b/syntax/tokens.rs
index 0d6c0660..af8088da 100644
--- a/syntax/tokens.rs
+++ b/syntax/tokens.rs
@@ -40,6 +40,10 @@ impl ToTokens for Ty1 {
     fn to_tokens(&self, tokens: &mut TokenStream) {
         let span = self.name.span();
         let name = self.name.to_string();
+        if let Some((pin, langle, _rangle)) = self.pin_tokens {
+            tokens.extend(quote_spanned!(pin.span=> ::std::pin::Pin));
+            langle.to_tokens(tokens);
+        }
         if let "UniquePtr" | "CxxVector" = name.as_str() {
             tokens.extend(quote_spanned!(span=> ::cxx::));
         } else if name == "Vec" {
@@ -49,15 +53,25 @@ impl ToTokens for Ty1 {
         self.langle.to_tokens(tokens);
         self.inner.to_tokens(tokens);
         self.rangle.to_tokens(tokens);
+        if let Some((_pin, _langle, rangle)) = self.pin_tokens {
+            rangle.to_tokens(tokens);
+        }
     }
 }
 
 impl ToTokens for Ref {
     fn to_tokens(&self, tokens: &mut TokenStream) {
+        if let Some((pin, langle, _rangle)) = self.pin_tokens {
+            tokens.extend(quote_spanned!(pin.span=> ::std::pin::Pin));
+            langle.to_tokens(tokens);
+        }
         self.ampersand.to_tokens(tokens);
         self.lifetime.to_tokens(tokens);
         self.mutability.to_tokens(tokens);
         self.inner.to_tokens(tokens);
+        if let Some((_pin, _langle, rangle)) = self.pin_tokens {
+            rangle.to_tokens(tokens);
+        }
     }
 }
 
@@ -172,9 +186,16 @@ impl Receiver {
 
 impl ToTokens for ReceiverType<'_> {
     fn to_tokens(&self, tokens: &mut TokenStream) {
+        if let Some((pin, langle, _rangle)) = self.0.pin_tokens {
+            tokens.extend(quote_spanned!(pin.span=> ::std::pin::Pin));
+            langle.to_tokens(tokens);
+        }
         self.0.ampersand.to_tokens(tokens);
         self.0.lifetime.to_tokens(tokens);
         self.0.mutability.to_tokens(tokens);
         self.0.ty.to_tokens(tokens);
+        if let Some((_pin, _langle, rangle)) = self.0.pin_tokens {
+            rangle.to_tokens(tokens);
+        }
     }
 }


Test patch:
diff --git a/tests/ffi/lib.rs b/tests/ffi/lib.rs
index ed6740db..825b13b6 100644
--- a/tests/ffi/lib.rs
+++ b/tests/ffi/lib.rs
@@ -124,7 +124,7 @@ pub mod ffi {
         fn c_return_primitive() -> usize;
         fn c_return_shared() -> Shared;
         fn c_return_box() -> Box<R>;
-        fn c_return_unique_ptr() -> UniquePtr<C>;
+        fn c_return_unique_ptr() -> Pin<UniquePtr<C>>;
         fn c_return_ref(shared: &Shared) -> &usize;
         fn c_return_mut(shared: &mut Shared) -> &mut usize;
         fn c_return_str(shared: &Shared) -> &str;
@@ -137,10 +137,10 @@ pub mod ffi {
         fn c_return_unique_ptr_vector_shared() -> UniquePtr<CxxVector<Shared>>;
         fn c_return_unique_ptr_vector_opaque() -> UniquePtr<CxxVector<C>>;
         fn c_return_ref_vector(c: &C) -> &CxxVector<u8>;
-        fn c_return_mut_vector(c: &mut C) -> &mut CxxVector<u8>;
+        fn c_return_mut_vector(c: Pin<&mut C>) -> Pin<&mut CxxVector<u8>>;
         fn c_return_rust_vec() -> Vec<u8>;
         fn c_return_ref_rust_vec(c: &C) -> &Vec<u8>;
-        fn c_return_mut_rust_vec(c: &mut C) -> &mut Vec<u8>;
+        fn c_return_mut_rust_vec(c: Pin<&mut C>) -> &mut Vec<u8>;
         fn c_return_rust_vec_string() -> Vec<String>;
         fn c_return_identity(_: usize) -> usize;
         fn c_return_sum(_: usize, _: usize) -> usize;
@@ -199,11 +199,10 @@ pub mod ffi {
         fn c_try_return_ref_rust_vec(c: &C) -> Result<&Vec<u8>>;
 
         fn get(self: &C) -> usize;
-        fn set(self: &mut C, n: usize) -> usize;
+        fn set(self: Pin<&mut C>, n: usize) -> usize;
         fn get2(&self) -> usize;
-        fn set2(&mut self, n: usize) -> usize;
-        fn set_succeed(&mut self, n: usize) -> Result<usize>;
-        fn get_fail(&mut self) -> Result<usize>;
+        fn set_succeed(self: Pin<&mut C>, n: usize) -> Result<usize>;
+        fn get_fail(self: Pin<&mut C>) -> Result<usize>;
         fn c_method_on_shared(self: &Shared) -> usize;
 
         #[rust_name = "i32_overloaded_method"]
diff --git a/tests/ffi/module.rs b/tests/ffi/module.rs
index aff1b1c5..c8363b61 100644
--- a/tests/ffi/module.rs
+++ b/tests/ffi/module.rs
@@ -8,6 +8,6 @@ pub mod ffi {
 
         type C = crate::ffi::C;
 
-        fn c_take_unique_ptr(c: UniquePtr<C>);
+        fn c_take_unique_ptr(c: Pin<UniquePtr<C>>);
     }
 }
diff --git a/tests/ffi/tests.cc b/tests/ffi/tests.cc
index fdf7a149..61ef701c 100644
--- a/tests/ffi/tests.cc
+++ b/tests/ffi/tests.cc
@@ -26,12 +26,7 @@ size_t C::set(size_t n) {
   return this->n;
 }
 
-size_t C::set2(size_t n) {
-  this->n = n;
-  return this->n;
-}
-
-size_t C::set_succeed(size_t n) { return this->set2(n); }
+size_t C::set_succeed(size_t n) { return this->set(n); }
 
 size_t C::get_fail() { throw std::runtime_error("unimplemented"); }
 
diff --git a/tests/ffi/tests.h b/tests/ffi/tests.h
index d231939e..5acf469d 100644
--- a/tests/ffi/tests.h
+++ b/tests/ffi/tests.h
@@ -45,7 +45,6 @@ public:
   size_t get() const;
   size_t set(size_t n);
   size_t get2() const;
-  size_t set2(size_t n);
   size_t set_succeed(size_t n);
   size_t get_fail();
   const std::vector<uint8_t> &get_v() const;
diff --git a/tests/test.rs b/tests/test.rs
index 2bae4d85..ff6abd9e 100644
--- a/tests/test.rs
+++ b/tests/test.rs
@@ -188,12 +188,11 @@ fn test_c_method_calls() {
 
     let old_value = unique_ptr.get();
     assert_eq!(2020, old_value);
-    assert_eq!(2021, unique_ptr.set(2021));
+    assert_eq!(2021, unique_ptr.as_mut().set(2021));
     assert_eq!(2021, unique_ptr.get());
-    assert_eq!(old_value, unique_ptr.set2(old_value));
-    assert_eq!(old_value, unique_ptr.get2());
-    assert_eq!(2022, unique_ptr.set_succeed(2022).unwrap());
-    assert!(unique_ptr.get_fail().is_err());
+    assert_eq!(2021, unique_ptr.get2());
+    assert_eq!(2022, unique_ptr.as_mut().set_succeed(2022).unwrap());
+    assert!(unique_ptr.as_mut().get_fail().is_err());
     assert_eq!(2021, ffi::Shared { z: 0 }.c_method_on_shared());
 }
 
diff --git a/tests/ui/pin_mut_opaque.rs b/tests/ui/pin_mut_opaque.rs
new file mode 100644
index 00000000..ac1ca43a
--- /dev/null
+++ b/tests/ui/pin_mut_opaque.rs
@@ -0,0 +1,14 @@
+#[cxx::bridge]
+mod ffi {
+    unsafe extern "C++" {
+        type Opaque;
+        fn f(arg: &mut Opaque);
+        fn g(&mut self);
+        fn h(self: &mut Opaque);
+        fn s(s: &mut CxxString);
+        fn v(v: &mut CxxVector<u8>);
+    }
+
+}
+
+fn main() {}
diff --git a/tests/ui/pin_mut_opaque.stderr b/tests/ui/pin_mut_opaque.stderr
new file mode 100644
index 00000000..32d5cfcb
--- /dev/null
+++ b/tests/ui/pin_mut_opaque.stderr
@@ -0,0 +1,29 @@
+error: mutable reference to C++ type requires a pin -- use Pin<&mut Opaque>
+ --> $DIR/pin_mut_opaque.rs:5:19
+  |
+5 |         fn f(arg: &mut Opaque);
+  |                   ^^^^^^^^^^^
+
+error: mutable reference to C++ type requires a pin -- use Pin<&mut CxxString>
+ --> $DIR/pin_mut_opaque.rs:8:17
+  |
+8 |         fn s(s: &mut CxxString);
+  |                 ^^^^^^^^^^^^^^
+
+error: mutable reference to C++ type requires a pin -- use Pin<&mut CxxVector<...>>
+ --> $DIR/pin_mut_opaque.rs:9:17
+  |
+9 |         fn v(v: &mut CxxVector<u8>);
+  |                 ^^^^^^^^^^^^^^^^^^
+
+error: mutable reference to C++ type requires a pin -- use `self: Pin<&mut Opaque>`
+ --> $DIR/pin_mut_opaque.rs:6:14
+  |
+6 |         fn g(&mut self);
+  |              ^^^^^^^^^
+
+error: mutable reference to C++ type requires a pin -- use `self: Pin<&mut Opaque>`
+ --> $DIR/pin_mut_opaque.rs:7:20
+  |
+7 |         fn h(self: &mut Opaque);
+  |                    ^^^^^^^^^^^