Repository: dtolnay/cxx (rust)

Issue:
Allow raw pointers in extern fn signature
See https://github.com/dtolnay/cxx/issues/122#issuecomment-615372359 for the rationale.

```rust
mod ffi {
    extern "C" {
        type T;
        unsafe fn f(_: *mut T);
    }
    extern "Rust" {
        unsafe fn g(_: *mut T);
    }
}
```

The presence of `*const` or `*mut` in a function argument should enforce that the function is marked unsafe.

Pull request description:
Support raw pointers in cxx::bridge.
This allows raw pointers to types to be passed into and out of
cxx::bridge extern "C++" APIs. As normal with raw pointers in Rust,
there are no safety or lifetime guarantees.

Passing a raw pointer into such an API requires that the
function be marked "unsafe".

Fixes #164.

Patch:
diff --git a/README.md b/README.md
index b3058f6b..c5d93aee 100644
--- a/README.md
+++ b/README.md
@@ -333,6 +333,7 @@ returns of functions.
 <tr><td><a href="https://docs.rs/cxx/1.0/cxx/struct.CxxVector.html">CxxVector&lt;T&gt;</a></td><td>std::vector&lt;T&gt;</td><td><sup><i>cannot be passed by value, cannot hold opaque Rust type</i></sup></td></tr>
 <tr><td>fn(T, U) -&gt; V</td><td>rust::Fn&lt;V(T, U)&gt;</td><td><sup><i>only passing from Rust to C++ is implemented so far</i></sup></td></tr>
 <tr><td>Result&lt;T&gt;</td><td>throw/catch</td><td><sup><i>allowed as return type only</i></sup></td></tr>
+<tr><td>*mut T, *const T</td><td>T*, const T*</td><td></td></tr>
 </table>
 
 The C++ API of the `rust` namespace is defined by the *include/cxx.h* file in
diff --git a/book/src/binding/raw_ptr.md b/book/src/binding/raw_ptr.md
new file mode 100644
index 00000000..7ad64754
--- /dev/null
+++ b/book/src/binding/raw_ptr.md
@@ -0,0 +1,68 @@
+{{#title *mut T, *const T — Rust ♡ C++}}
+# *mut T, *const T - Raw Pointers
+
+cxx supports tranfer of raw pointers across the FFI boundary.
+
+Generally, it's better to use the bindings for [std::unique_ptr\<T\>](uniqueptr.md)
+or references. You should resort to using raw pointers only where lifetimes
+are too complicated to model with standard cxx facilities.
+
+As is normal with raw pointers in Rust, you'll need to use `unsafe` when
+working with them. In particular, to pass any raw pointer into a cxx
+bridge function you will need to declare the function `unsafe`, even if
+the overall `extern "C++"` section is already marked as `unsafe`. By calling
+such a function, you're committing that you - the human - know enough about
+the lifetimes of those objects that the compiler doesn't need to do checks.
+
+On the other hand, C++ functions can freely return raw pointers to
+Rust without `unsafe`, but actually using those raw pointers in any way is likely
+to require an `unsafe` keyword.
+
+## Example
+
+```rust,noplayground
+// src/main.rs
+
+#[cxx::bridge]
+mod ffi {
+    unsafe extern "C++" {
+
+        include!("tests/ffi/tests.h");
+        //include!("example/include/container.h");
+
+        type ComplexHierarchicContainer;
+
+        fn new_hierarchic_container() -> UniquePtr<ComplexHierarchicContainer>;
+        fn add_value(self: Pin<&mut ComplexHierarchicContainer>, key: &CxxString, value: &CxxString);
+        fn add_child(self: Pin<&mut ComplexHierarchicContainer>, key: &CxxString) -> *mut ComplexHierarchicContainer;
+        // ...
+    }
+}
+
+fn main() {
+    let mut container = ffi17::new_hierarchic_container();
+    cxx::let_cxx_string!(key = "a");
+    let mut subcontainer = container.pin_mut().add_child(&key);
+    let mut subcontainer = unsafe { std::pin::Pin::new_unchecked(subcontainer.as_mut().unwrap()) };
+    cxx::let_cxx_string!(key2 = "b");
+    cxx::let_cxx_string!(value = "c");
+    subcontainer.add_value(&key2, &value);
+    // ...
+}
+```
+
+```cpp
+// include/container.h
+
+#pragma once
+
+class ComplexHierarchicContainer {
+public:
+    ComplexHierarchicContainer();
+    void add_value(const std::string& key, const std::string& value);
+    ComplexHierarchicContainer* add_child(const std::string& key);
+    // ...
+};
+
+std::unique_ptr<ComplexHierarchicContainer> new_hierarchic_container();
+```
diff --git a/book/src/bindings.md b/book/src/bindings.md
index 4a765b36..9ba0c410 100644
--- a/book/src/bindings.md
+++ b/book/src/bindings.md
@@ -22,6 +22,7 @@ returns of extern functions.
 <tr><td style="padding:3px 6px"><b><a href="binding/cxxvector.md">CxxVector&lt;T&gt;</a></b></td><td style="padding:3px 6px">std::vector&lt;T&gt;</td><td style="padding:3px 6px"><sup><i>cannot be passed by value, cannot hold opaque Rust type</i></sup></td></tr>
 <tr><td style="padding:3px 6px">fn(T, U) -&gt; V</td><td style="padding:3px 6px"><b><a href="binding/fn.md">rust::Fn&lt;V(T, U)&gt;</a></b></td><td style="padding:3px 6px"><sup><i>only passing from Rust to C++ is implemented so far</i></sup></td></tr>
 <tr><td style="padding:3px 6px"><b><a href="binding/result.md">Result&lt;T&gt;</a></b></td><td style="padding:3px 6px">throw/catch</td><td style="padding:3px 6px"><sup><i>allowed as return type only</i></sup></td></tr>
+<tr><td style="padding:3px 6px"><b><a href="binding/raw_ptr.md">*const T, *mut T</a></b></td><td style="padding:3px 6px">T*, const T*</td><td style="padding:3px 6px"></td></tr>
 </table>
 
 <br>
diff --git a/gen/src/write.rs b/gen/src/write.rs
index a2ce6305..b3911c1b 100644
--- a/gen/src/write.rs
+++ b/gen/src/write.rs
@@ -222,7 +222,7 @@ fn pick_includes_and_builtins(out: &mut OutFile, apis: &[Api]) {
             Type::Fn(_) => out.builtin.rust_fn = true,
             Type::SliceRef(_) => out.builtin.rust_slice = true,
             Type::Array(_) => out.include.array = true,
-            Type::Ref(_) | Type::Void(_) => {}
+            Type::Ref(_) | Type::Void(_) | Type::Ptr(_) => {}
         }
     }
 }
@@ -1179,6 +1179,13 @@ fn write_type(out: &mut OutFile, ty: &Type) {
             write_type(out, &r.inner);
             write!(out, " &");
         }
+        Type::Ptr(p) => {
+            if !p.mutable {
+                write!(out, "const ");
+            }
+            write_type(out, &p.inner);
+            write!(out, " *");
+        }
         Type::Str(_) => {
             write!(out, "::rust::Str");
         }
@@ -1253,7 +1260,7 @@ fn write_space_after_type(out: &mut OutFile, ty: &Type) {
         | Type::SliceRef(_)
         | Type::Fn(_)
         | Type::Array(_) => write!(out, " "),
-        Type::Ref(_) => {}
+        Type::Ref(_) | Type::Ptr(_) => {}
         Type::Void(_) => unreachable!(),
     }
 }
diff --git a/syntax/check.rs b/syntax/check.rs
index 2aefbdfe..052b248b 100644
--- a/syntax/check.rs
+++ b/syntax/check.rs
@@ -8,6 +8,8 @@ use proc_macro2::{Delimiter, Group, Ident, TokenStream};
 use quote::{quote, ToTokens};
 use std::fmt::Display;
 
+use super::Ptr;
+
 pub(crate) struct Check<'a> {
     apis: &'a [Api],
     types: &'a Types<'a>,
@@ -35,6 +37,7 @@ fn do_typecheck(cx: &mut Check) {
             Type::WeakPtr(ptr) => check_type_weak_ptr(cx, ptr),
             Type::CxxVector(ptr) => check_type_cxx_vector(cx, ptr),
             Type::Ref(ty) => check_type_ref(cx, ty),
+            Type::Ptr(ty) => check_type_ptr(cx, ty),
             Type::Array(array) => check_type_array(cx, array),
             Type::Fn(ty) => check_type_fn(cx, ty),
             Type::SliceRef(ty) => check_type_slice_ref(cx, ty),
@@ -221,7 +224,7 @@ fn check_type_ref(cx: &mut Check, ty: &Ref) {
     }
 
     match ty.inner {
-        Type::Fn(_) | Type::Void(_) => {}
+        Type::Fn(_) | Type::Void(_) | Type::Ptr(_) => {}
         Type::Ref(_) => {
             cx.error(ty, "C++ does not allow references to references");
             return;
@@ -232,6 +235,12 @@ fn check_type_ref(cx: &mut Check, ty: &Ref) {
     cx.error(ty, "unsupported reference type");
 }
 
+fn check_type_ptr(cx: &mut Check, ty: &Ptr) {
+    if let Type::Ident(_) = ty.inner { return }
+
+    cx.error(ty, "unsupported pointer type");
+}
+
 fn check_type_slice_ref(cx: &mut Check, ty: &SliceRef) {
     let supported = !is_unsized(cx, &ty.inner)
         || match &ty.inner {
@@ -558,6 +567,7 @@ fn is_unsized(cx: &mut Check, ty: &Type) -> bool {
         | Type::SharedPtr(_)
         | Type::WeakPtr(_)
         | Type::Ref(_)
+        | Type::Ptr(_)
         | Type::Str(_)
         | Type::SliceRef(_) => false,
     }
@@ -631,6 +641,7 @@ fn describe(cx: &mut Check, ty: &Type) -> String {
         Type::SharedPtr(_) => "shared_ptr".to_owned(),
         Type::WeakPtr(_) => "weak_ptr".to_owned(),
         Type::Ref(_) => "reference".to_owned(),
+        Type::Ptr(_) => "raw pointer".to_owned(),
         Type::Str(_) => "&str".to_owned(),
         Type::CxxVector(_) => "C++ vector".to_owned(),
         Type::SliceRef(_) => "slice".to_owned(),
diff --git a/syntax/impls.rs b/syntax/impls.rs
index 3a0b3e0f..954f39b7 100644
--- a/syntax/impls.rs
+++ b/syntax/impls.rs
@@ -5,6 +5,8 @@ use std::hash::{Hash, Hasher};
 use std::mem;
 use std::ops::{Deref, DerefMut};
 
+use super::Ptr;
+
 impl PartialEq for Include {
     fn eq(&self, other: &Include) -> bool {
         let Include {
@@ -47,6 +49,7 @@ impl Hash for Type {
             Type::SharedPtr(t) => t.hash(state),
             Type::WeakPtr(t) => t.hash(state),
             Type::Ref(t) => t.hash(state),
+            Type::Ptr(t) => t.hash(state),
             Type::Str(t) => t.hash(state),
             Type::RustVec(t) => t.hash(state),
             Type::CxxVector(t) => t.hash(state),
@@ -189,6 +192,42 @@ impl Hash for Ref {
     }
 }
 
+impl Eq for Ptr {}
+
+impl PartialEq for Ptr {
+    fn eq(&self, other: &Ptr) -> bool {
+        let Ptr {
+            star: _,
+            mutable,
+            inner,
+            mutability: _,
+            constness: _,
+        } = self;
+        let Ptr {
+            star: _,
+            mutable: mutable2,
+            inner: inner2,
+            mutability: _,
+            constness: _,
+        } = other;
+        mutable == mutable2 && inner == inner2
+    }
+}
+
+impl Hash for Ptr {
+    fn hash<H: Hasher>(&self, state: &mut H) {
+        let Ptr {
+            star: _,
+            mutable,
+            inner,
+            mutability: _,
+            constness: _,
+        } = self;
+        mutable.hash(state);
+        inner.hash(state);
+    }
+}
+
 impl Eq for SliceRef {}
 
 impl PartialEq for SliceRef {
diff --git a/syntax/improper.rs b/syntax/improper.rs
index d672e7d8..6a9b5ac0 100644
--- a/syntax/improper.rs
+++ b/syntax/improper.rs
@@ -32,6 +32,7 @@ impl<'a> Types<'a> {
                 Definite(false)
             }
             Type::Ref(ty) => self.determine_improper_ctype(&ty.inner),
+            Type::Ptr(ty) => self.determine_improper_ctype(&ty.inner),
             Type::Array(ty) => self.determine_improper_ctype(&ty.inner),
         }
     }
diff --git a/syntax/mod.rs b/syntax/mod.rs
index 30faccc7..87e34540 100644
--- a/syntax/mod.rs
+++ b/syntax/mod.rs
@@ -204,6 +204,7 @@ pub enum Type {
     SharedPtr(Box<Ty1>),
     WeakPtr(Box<Ty1>),
     Ref(Box<Ref>),
+    Ptr(Box<Ptr>),
     Str(Box<Ref>),
     CxxVector(Box<Ty1>),
     Fn(Box<Signature>),
@@ -229,6 +230,14 @@ pub struct Ref {
     pub mutability: Option<Token![mut]>,
 }
 
+pub struct Ptr {
+    pub star: Token![*],
+    pub mutable: bool,
+    pub inner: Type,
+    pub mutability: Option<Token![mut]>,
+    pub constness: Option<Token![const]>,
+}
+
 pub struct SliceRef {
     pub ampersand: Token![&],
     pub lifetime: Option<Lifetime>,
diff --git a/syntax/parse.rs b/syntax/parse.rs
index 3dd09f5e..8f8075ac 100644
--- a/syntax/parse.rs
+++ b/syntax/parse.rs
@@ -11,7 +11,7 @@ use crate::syntax::{
 use proc_macro2::{Delimiter, Group, Span, TokenStream, TokenTree};
 use quote::{format_ident, quote, quote_spanned};
 use std::mem;
-use syn::parse::{ParseStream, Parser};
+use syn::{TypePtr, parse::{ParseStream, Parser}};
 use syn::punctuated::Punctuated;
 use syn::{
     Abi, Attribute, Error, Expr, Fields, FnArg, ForeignItem, ForeignItemFn, ForeignItemType,
@@ -21,6 +21,8 @@ use syn::{
     TypeReference, Variant as RustVariant, Visibility,
 };
 
+use super::Ptr;
+
 pub mod kw {
     syn::custom_keyword!(Pin);
     syn::custom_keyword!(Result);
@@ -547,6 +549,7 @@ fn parse_extern_fn(
         ));
     }
 
+    let unsafety = foreign_fn.sig.unsafety;
     let mut receiver = None;
     let mut args = Punctuated::new();
     for arg in foreign_fn.sig.inputs.pairs() {
@@ -583,6 +586,11 @@ fn parse_extern_fn(
                     let attrs = OtherAttrs::none();
                     let visibility = Token![pub](ident.span());
                     let name = pair(Namespace::default(), &ident, None, None);
+                    if let Type::Ptr(_) = &ty {
+                        if unsafety.is_none() {
+                            return Err(Error::new_spanned(arg, "pointer argument requires that the function be marked unsafe"));
+                        }
+                    }
                     args.push_value(Var {
                         doc,
                         attrs,
@@ -966,6 +974,7 @@ fn parse_impl(imp: ItemImpl) -> Result<Api> {
         },
         Type::Ident(_)
         | Type::Ref(_)
+        | Type::Ptr(_)
         | Type::Str(_)
         | Type::Fn(_)
         | Type::Void(_)
@@ -1034,6 +1043,7 @@ fn parse_include(input: ParseStream) -> Result<Include> {
 fn parse_type(ty: &RustType) -> Result<Type> {
     match ty {
         RustType::Reference(ty) => parse_type_reference(ty),
+        RustType::Ptr(ty) => parse_type_ptr(ty),
         RustType::Path(ty) => parse_type_path(ty),
         RustType::Array(ty) => parse_type_array(ty),
         RustType::BareFn(ty) => parse_type_fn(ty),
@@ -1085,6 +1095,26 @@ fn parse_type_reference(ty: &TypeReference) -> Result<Type> {
     })))
 }
 
+fn parse_type_ptr(ty: &TypePtr) -> Result<Type> {
+    let star = ty.star_token;
+    let mutable = ty.mutability.is_some();
+    let constness = ty.const_token;
+    let mutability = ty.mutability;
+    if !constness.is_some() && !mutable {
+        return Err(Error::new_spanned(ty, "pointer is neither const nor mut"));
+    }
+
+    let inner = parse_type(&ty.elem)?;
+    
+    Ok(Type::Ptr(Box::new(Ptr {
+        star,
+        mutable,
+        inner,
+        mutability,
+        constness,
+    })))
+}
+
 fn parse_type_path(ty: &TypePath) -> Result<Type> {
     let path = &ty.path;
     if ty.qself.is_none() && path.leading_colon.is_none() && path.segments.len() == 1 {
diff --git a/syntax/pod.rs b/syntax/pod.rs
index e1c48313..0bf152ee 100644
--- a/syntax/pod.rs
+++ b/syntax/pod.rs
@@ -29,7 +29,7 @@ impl<'a> Types<'a> {
             | Type::WeakPtr(_)
             | Type::CxxVector(_)
             | Type::Void(_) => false,
-            Type::Ref(_) | Type::Str(_) | Type::Fn(_) | Type::SliceRef(_) => true,
+            Type::Ref(_) | Type::Str(_) | Type::Fn(_) | Type::SliceRef(_) | Type::Ptr(_) => true,
             Type::Array(array) => self.is_guaranteed_pod(&array.inner),
         }
     }
diff --git a/syntax/tokens.rs b/syntax/tokens.rs
index bb04e163..d728ac0f 100644
--- a/syntax/tokens.rs
+++ b/syntax/tokens.rs
@@ -7,6 +7,8 @@ use proc_macro2::{Ident, Span, TokenStream};
 use quote::{quote_spanned, ToTokens};
 use syn::{token, Token};
 
+use super::Ptr;
+
 impl ToTokens for Type {
     fn to_tokens(&self, tokens: &mut TokenStream) {
         match self {
@@ -27,6 +29,7 @@ impl ToTokens for Type {
             | Type::CxxVector(ty)
             | Type::RustVec(ty) => ty.to_tokens(tokens),
             Type::Ref(r) | Type::Str(r) => r.to_tokens(tokens),
+            Type::Ptr(p) => p.to_tokens(tokens),
             Type::Array(a) => a.to_tokens(tokens),
             Type::Fn(f) => f.to_tokens(tokens),
             Type::Void(span) => tokens.extend(quote_spanned!(*span=> ())),
@@ -100,6 +103,22 @@ impl ToTokens for Ref {
     }
 }
 
+impl ToTokens for Ptr {
+    fn to_tokens(&self, tokens: &mut TokenStream) {
+        let Ptr {
+            star,
+            mutable: _,
+            inner,
+            mutability,
+            constness,
+        } = self;
+        star.to_tokens(tokens);
+        mutability.to_tokens(tokens);
+        constness.to_tokens(tokens);
+        inner.to_tokens(tokens);
+    }
+}
+
 impl ToTokens for SliceRef {
     fn to_tokens(&self, tokens: &mut TokenStream) {
         let SliceRef {
diff --git a/syntax/types.rs b/syntax/types.rs
index 7bf2baf0..031dc08b 100644
--- a/syntax/types.rs
+++ b/syntax/types.rs
@@ -51,6 +51,7 @@ impl<'a> Types<'a> {
                 | Type::CxxVector(ty)
                 | Type::RustVec(ty) => visit(all, &ty.inner),
                 Type::Ref(r) => visit(all, &r.inner),
+                Type::Ptr(p) => visit(all, &p.inner),
                 Type::Array(a) => visit(all, &a.inner),
                 Type::SliceRef(s) => visit(all, &s.inner),
                 Type::Fn(f) => {


Test patch:
diff --git a/tests/ffi/lib.rs b/tests/ffi/lib.rs
index 32d739db..5f11a6a6 100644
--- a/tests/ffi/lib.rs
+++ b/tests/ffi/lib.rs
@@ -121,6 +121,8 @@ pub mod ffi {
         fn c_return_nested_ns_ref(shared: &ABShared) -> &usize;
         fn c_return_ns_enum(n: u16) -> AEnum;
         fn c_return_nested_ns_enum(n: u16) -> ABEnum;
+        fn c_return_opaque_raw_ptr(n: usize) -> *const C;
+        fn c_return_opaque_mut_raw_ptr(n: usize) -> *mut C;
 
         fn c_take_primitive(n: usize);
         fn c_take_shared(shared: Shared);
@@ -161,6 +163,12 @@ pub mod ffi {
         fn c_take_nested_ns_shared(shared: ABShared);
         fn c_take_rust_vec_ns_shared(v: Vec<AShared>);
         fn c_take_rust_vec_nested_ns_shared(v: Vec<ABShared>);
+        /// # Unsafety
+        /// To keep clippy happy.
+        unsafe fn c_take_opaque_mut_raw_ptr(c: *mut C) -> usize;
+        /// # Unsafety
+        /// To keep clippy happy.
+        unsafe fn c_take_opaque_raw_ptr(c: *const C) -> usize;
 
         fn c_try_return_void() -> Result<()>;
         fn c_try_return_primitive() -> Result<usize>;
diff --git a/tests/ffi/tests.cc b/tests/ffi/tests.cc
index 68ec5449..669c8186 100644
--- a/tests/ffi/tests.cc
+++ b/tests/ffi/tests.cc
@@ -207,6 +207,24 @@ Enum c_return_enum(uint16_t n) {
   }
 }
 
+const C* c_return_opaque_raw_ptr(size_t c) {
+  return new C(c);
+}
+
+C* c_return_opaque_mut_raw_ptr(size_t c) {
+  return new C(c);
+}
+
+size_t c_take_opaque_raw_ptr(const C* c) {
+  return c->get();
+}
+
+size_t c_take_opaque_mut_raw_ptr(C* c) {
+  size_t result = c->get();
+  delete c;
+  return result;
+}
+
 Borrow::Borrow(const std::string &s) : s(s) {}
 
 void Borrow::const_member() const {}
diff --git a/tests/ffi/tests.h b/tests/ffi/tests.h
index a28c1665..3de24e67 100644
--- a/tests/ffi/tests.h
+++ b/tests/ffi/tests.h
@@ -118,6 +118,8 @@ Enum c_return_enum(uint16_t n);
 ::A::AEnum c_return_ns_enum(uint16_t n);
 ::A::B::ABEnum c_return_nested_ns_enum(uint16_t n);
 std::unique_ptr<Borrow> c_return_borrow(const std::string &s);
+const C* c_return_opaque_raw_ptr(size_t n);
+C* c_return_opaque_mut_raw_ptr(size_t n);
 
 void c_take_primitive(size_t n);
 void c_take_shared(Shared shared);
@@ -161,6 +163,8 @@ void c_take_callback(rust::Fn<size_t(rust::String)> callback);
 void c_take_enum(Enum e);
 void c_take_ns_enum(::A::AEnum e);
 void c_take_nested_ns_enum(::A::B::ABEnum e);
+size_t c_take_opaque_raw_ptr(const C* c);
+size_t c_take_opaque_mut_raw_ptr(C* c);
 
 void c_try_return_void();
 size_t c_try_return_primitive();
diff --git a/tests/test.rs b/tests/test.rs
index e0815a64..2c7f7632 100644
--- a/tests/test.rs
+++ b/tests/test.rs
@@ -335,3 +335,19 @@ fn test_extern_opaque() {
     check!(ffi2::c_take_opaque_ns_ref(f.as_ref().unwrap()));
     check!(ffi2::c_take_opaque_ns_ptr(f));
 }
+
+#[test]
+fn test_raw_ptr() {
+    let c = ffi::c_return_opaque_mut_raw_ptr(2023);
+    let mut c_unique = unsafe { cxx::UniquePtr::from_raw(c) };
+    assert_eq!(2023, c_unique.pin_mut().set_succeed(2023).unwrap());
+    // c will be dropped as it's now in a UniquePtr
+
+    let c2 = ffi::c_return_opaque_mut_raw_ptr(2024);
+    assert_eq!(2024, unsafe { ffi::c_take_opaque_raw_ptr(c2) });
+    assert_eq!(2024, unsafe { ffi::c_take_opaque_mut_raw_ptr(c2) }); // deletes c2
+
+    let c3 = ffi::c_return_opaque_raw_ptr(2025);
+    assert_eq!(2025, unsafe { ffi::c_take_opaque_raw_ptr(c3) });
+    assert_eq!(2025, unsafe { ffi::c_take_opaque_mut_raw_ptr(c3 as *mut ffi::C) }); // deletes c3
+}
\ No newline at end of file
diff --git a/tests/ui/unsupported_ptr.rs b/tests/ui/unsupported_ptr.rs
new file mode 100644
index 00000000..a3b5cc41
--- /dev/null
+++ b/tests/ui/unsupported_ptr.rs
@@ -0,0 +1,40 @@
+#[cxx::bridge]
+mod ffi {
+    unsafe extern "C++" {
+        type C;
+
+        fn not_unsafe_ptr(c: *mut C);
+    }
+}
+
+#[cxx::bridge]
+mod ffi2 {
+    unsafe extern "C++" {
+        type C;
+
+        fn get_neither_const_nor_mut() -> *C;
+    }
+}
+
+#[cxx::bridge]
+mod ffi3 {
+    unsafe extern "C++" {
+        type C;
+
+        fn get_ptr_ptr() -> *mut *mut C;
+        fn get_ptr_reference() -> *mut & C;
+        fn get_reference_ptr() -> & *mut C;
+    }
+}
+
+#[cxx::bridge]
+mod ffi4 {
+    unsafe extern "C++" {
+        type C;
+
+        fn get_ptr_vector() -> UniquePtr<CxxVector<*mut C>>;
+        fn get_ptr_unique() -> UniquePtr<*mut C>;
+    }
+}
+
+fn main() {}
diff --git a/tests/ui/unsupported_ptr.stderr b/tests/ui/unsupported_ptr.stderr
new file mode 100644
index 00000000..03756bfd
--- /dev/null
+++ b/tests/ui/unsupported_ptr.stderr
@@ -0,0 +1,49 @@
+error: expected mut or const in raw pointer type
+  --> $DIR/unsupported_ptr.rs:15:43
+   |
+15 |         fn get_neither_const_nor_mut() -> *C;
+   |                                           ^ expected mut or const in raw pointer type
+   |
+   = help: use `*mut T` or `*const T` as appropriate
+
+error: pointer argument requires that the function be marked unsafe
+ --> $DIR/unsupported_ptr.rs:6:27
+  |
+6 |         fn not_unsafe_ptr(c: *mut C);
+  |                           ^^^^^^^^^
+
+error: expected `const` or `mut`
+  --> $DIR/unsupported_ptr.rs:15:44
+   |
+15 |         fn get_neither_const_nor_mut() -> *C;
+   |                                            ^
+
+error: unsupported pointer type
+  --> $DIR/unsupported_ptr.rs:24:29
+   |
+24 |         fn get_ptr_ptr() -> *mut *mut C;
+   |                             ^^^^^^^^^^^
+
+error: unsupported pointer type
+  --> $DIR/unsupported_ptr.rs:25:35
+   |
+25 |         fn get_ptr_reference() -> *mut & C;
+   |                                   ^^^^^^^^
+
+error: unsupported reference type
+  --> $DIR/unsupported_ptr.rs:26:35
+   |
+26 |         fn get_reference_ptr() -> & *mut C;
+   |                                   ^^^^^^^^
+
+error: unsupported vector target type
+  --> $DIR/unsupported_ptr.rs:35:42
+   |
+35 |         fn get_ptr_vector() -> UniquePtr<CxxVector<*mut C>>;
+   |                                          ^^^^^^^^^^^^^^^^^^
+
+error: unsupported unique_ptr target type
+  --> $DIR/unsupported_ptr.rs:36:32
+   |
+36 |         fn get_ptr_unique() -> UniquePtr<*mut C>;
+   |                                ^^^^^^^^^^^^^^^^^