diff --git a/rs_bindings_from_cc/importers/cxx_record.cc b/rs_bindings_from_cc/importers/cxx_record.cc index 87a349c8e..4cab0aa0c 100644 --- a/rs_bindings_from_cc/importers/cxx_record.cc +++ b/rs_bindings_from_cc/importers/cxx_record.cc @@ -1177,6 +1177,11 @@ std::optional CXXRecordDeclImporter::Import( FormattedError::FromStatus(std::move(trait_derives).status())); } + if (*is_thread_safe) { + trait_derives->send = true; + trait_derives->sync = true; + } + absl::StatusOr safety_annotation = GetSafetyAnnotation(*record_decl); if (!safety_annotation.ok()) { diff --git a/rs_bindings_from_cc/test/annotations/BUILD b/rs_bindings_from_cc/test/annotations/BUILD index f8a938e1c..18961727e 100644 --- a/rs_bindings_from_cc/test/annotations/BUILD +++ b/rs_bindings_from_cc/test/annotations/BUILD @@ -159,3 +159,32 @@ crubit_rust_test( "@crate_index//:googletest", ], ) + +crubit_test_cc_library( + name = "thread_safe", + hdrs = ["thread_safe.h"], + deps = [ + "//support:annotations", + ], +) + +crubit_rust_test( + name = "thread_safe_test", + srcs = ["thread_safe_test.rs"], + cc_deps = [ + ":thread_safe", + ], + deps = [ + "//support:ctor", + "@crate_index//:googletest", + "@crate_index//:static_assertions", # v1 + ], +) + +golden_test( + name = "thread_safe_golden_test", + basename = "thread_safe", + cc_library = "thread_safe", + golden_cc = "thread_safe_api_impl.cc", + golden_rs = "thread_safe_rs_api.rs", +) diff --git a/rs_bindings_from_cc/test/annotations/thread_safe.h b/rs_bindings_from_cc/test/annotations/thread_safe.h new file mode 100644 index 000000000..3d5e25c6c --- /dev/null +++ b/rs_bindings_from_cc/test/annotations/thread_safe.h @@ -0,0 +1,37 @@ +// Part of the Crubit project, under the Apache License v2.0 with LLVM +// Exceptions. See /LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#ifndef THIRD_PARTY_CRUBIT_RS_BINDINGS_FROM_CC_TEST_ANNOTATIONS_THREAD_SAFE_H_ +#define THIRD_PARTY_CRUBIT_RS_BINDINGS_FROM_CC_TEST_ANNOTATIONS_THREAD_SAFE_H_ + +#include "support/annotations.h" + +namespace crubit::test { + +// A simple thread-safe struct. +class CRUBIT_THREAD_SAFE ThreadSafeStruct final { + public: + int ConstGet() const { return x_; } + // A non-const method for testing the generation behavior. + // The implementation doesn't actually do anything non-const, but it doesn't + // matter for what we are testing, here. + int NonConstGet() { return x_; } + + private: + int x_ = 0; +}; + +// A regular (non-thread-safe) struct for comparison. +class RegularStruct final { + public: + int ConstGet() const { return x_; } + int NonConstGet() { return x_; } + + private: + int x_ = 0; +}; + +} // namespace crubit::test + +#endif // THIRD_PARTY_CRUBIT_RS_BINDINGS_FROM_CC_TEST_ANNOTATIONS_THREAD_SAFE_H_ diff --git a/rs_bindings_from_cc/test/annotations/thread_safe_api_impl.cc b/rs_bindings_from_cc/test/annotations/thread_safe_api_impl.cc new file mode 100644 index 000000000..da71cf772 --- /dev/null +++ b/rs_bindings_from_cc/test/annotations/thread_safe_api_impl.cc @@ -0,0 +1,70 @@ +// Part of the Crubit project, under the Apache License v2.0 with LLVM +// Exceptions. See /LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +// Automatically @generated Rust bindings for the following C++ target: +// //rs_bindings_from_cc/test/annotations:thread_safe +// Features: fmt, supported, types + +#include "support/internal/cxx20_backports.h" +#include "support/internal/offsetof.h" +#include "support/internal/sizeof.h" + +#include +#include + +// Public headers of the C++ library being wrapped. +#include "rs_bindings_from_cc/test/annotations/thread_safe.h" + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wthread-safety-analysis" + +static_assert(CRUBIT_SIZEOF(class crubit::test::ThreadSafeStruct) == 4); +static_assert(alignof(class crubit::test::ThreadSafeStruct) == 4); + +extern "C" void __rust_thunk___ZN6crubit4test16ThreadSafeStructC1Ev( + class crubit::test::ThreadSafeStruct* __this) { + crubit::construct_at(__this); +} + +extern "C" int __rust_thunk___ZNK6crubit4test16ThreadSafeStruct8ConstGetEv( + class crubit::test::ThreadSafeStruct const* __this) { + return __this->ConstGet(); +} + +static_assert((int (::crubit::test::ThreadSafeStruct::*)() const) & + ::crubit::test::ThreadSafeStruct::ConstGet); + +extern "C" int __rust_thunk___ZN6crubit4test16ThreadSafeStruct11NonConstGetEv( + class crubit::test::ThreadSafeStruct* __this) { + return __this->NonConstGet(); +} + +static_assert((int (::crubit::test::ThreadSafeStruct::*)()) & + ::crubit::test::ThreadSafeStruct::NonConstGet); + +static_assert(CRUBIT_SIZEOF(class crubit::test::RegularStruct) == 4); +static_assert(alignof(class crubit::test::RegularStruct) == 4); + +extern "C" void __rust_thunk___ZN6crubit4test13RegularStructC1Ev( + class crubit::test::RegularStruct* __this) { + crubit::construct_at(__this); +} + +extern "C" int __rust_thunk___ZNK6crubit4test13RegularStruct8ConstGetEv( + class crubit::test::RegularStruct const* __this) { + return __this->ConstGet(); +} + +static_assert((int (::crubit::test::RegularStruct::*)() const) & + ::crubit::test::RegularStruct::ConstGet); + +extern "C" int __rust_thunk___ZN6crubit4test13RegularStruct11NonConstGetEv( + class crubit::test::RegularStruct* __this) { + return __this->NonConstGet(); +} + +static_assert((int (::crubit::test::RegularStruct::*)()) & + ::crubit::test::RegularStruct::NonConstGet); + +#pragma clang diagnostic pop diff --git a/rs_bindings_from_cc/test/annotations/thread_safe_rs_api.rs b/rs_bindings_from_cc/test/annotations/thread_safe_rs_api.rs new file mode 100644 index 000000000..bdccf3c7b --- /dev/null +++ b/rs_bindings_from_cc/test/annotations/thread_safe_rs_api.rs @@ -0,0 +1,246 @@ +// Part of the Crubit project, under the Apache License v2.0 with LLVM +// Exceptions. See /LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +// Automatically @generated Rust bindings for the following C++ target: +// //rs_bindings_from_cc/test/annotations:thread_safe +// Features: fmt, supported, types + +#![rustfmt::skip] +#![feature(custom_inner_attributes, negative_impls)] +#![allow(stable_features)] +#![allow(improper_ctypes)] +#![allow(nonstandard_style)] +#![allow(unused)] +#![allow(deprecated)] +#![deny(warnings)] + +pub mod crubit { + pub mod test { + /// A simple thread-safe struct. + /// + /// Generated from: rs_bindings_from_cc/test/annotations/thread_safe.h;l=13 + #[derive(Clone, Copy, ::ctor::MoveAndAssignViaCopy)] + #[repr(C, align(4))] + ///CRUBIT_ANNOTATE: cpp_type=crubit :: test :: ThreadSafeStruct + pub struct ThreadSafeStruct { + __non_field_data: [::core::mem::MaybeUninit; 0], + /// Reason for representing this field as a blob of bytes: + /// Types of non-public C++ fields can be elided away + pub(crate) x_: [::core::mem::MaybeUninit; 4], + } + unsafe impl Send for ThreadSafeStruct {} + unsafe impl Sync for ThreadSafeStruct {} + unsafe impl ::cxx::ExternType for ThreadSafeStruct { + type Id = ::cxx::type_id!("crubit :: test :: ThreadSafeStruct"); + type Kind = ::cxx::kind::Trivial; + } + impl ThreadSafeStruct { + /// # Safety + /// + /// The caller must ensure that the following unsafe arguments are not misused by the function: + /// * `__this`: raw pointer + /// + /// Generated from: rs_bindings_from_cc/test/annotations/thread_safe.h;l=15 + #[inline(always)] + pub unsafe fn ConstGet(__this: *const Self) -> ::ffi_11::c_int { + unsafe { self::thread_safe_struct::ConstGet(__this) } + } + /// A non-const method for testing the generation behavior. + /// The implementation doesn't actually do anything non-const, but it doesn't + /// matter for what we are testing, here. + /// + /// # Safety + /// + /// The caller must ensure that the following unsafe arguments are not misused by the function: + /// * `__this`: raw pointer + /// + /// Generated from: rs_bindings_from_cc/test/annotations/thread_safe.h;l=19 + #[inline(always)] + pub unsafe fn NonConstGet(__this: *mut Self) -> ::ffi_11::c_int { + unsafe { self::thread_safe_struct::NonConstGet(__this) } + } + } + + /// Generated from: rs_bindings_from_cc/test/annotations/thread_safe.h;l=13 + impl Default for ThreadSafeStruct { + #[inline(always)] + fn default() -> Self { + let mut tmp = ::core::mem::MaybeUninit::::zeroed(); + unsafe { + crate::detail::__rust_thunk___ZN6crubit4test16ThreadSafeStructC1Ev( + &raw mut tmp as *mut _, + ); + tmp.assume_init() + } + } + } + + pub mod thread_safe_struct { + /// # Safety + /// + /// The caller must ensure that the following unsafe arguments are not misused by the function: + /// * `__this`: raw pointer + /// + /// Generated from: rs_bindings_from_cc/test/annotations/thread_safe.h;l=15 + #[inline(always)] + pub(crate) unsafe fn ConstGet( + __this: *const crate::crubit::test::ThreadSafeStruct, + ) -> ::ffi_11::c_int { + unsafe { + crate::detail::__rust_thunk___ZNK6crubit4test16ThreadSafeStruct8ConstGetEv( + __this, + ) + } + } + /// A non-const method for testing the generation behavior. + /// The implementation doesn't actually do anything non-const, but it doesn't + /// matter for what we are testing, here. + /// + /// # Safety + /// + /// The caller must ensure that the following unsafe arguments are not misused by the function: + /// * `__this`: raw pointer + /// + /// Generated from: rs_bindings_from_cc/test/annotations/thread_safe.h;l=19 + #[inline(always)] + pub(crate) unsafe fn NonConstGet( + __this: *mut crate::crubit::test::ThreadSafeStruct, + ) -> ::ffi_11::c_int { + unsafe { + crate::detail::__rust_thunk___ZN6crubit4test16ThreadSafeStruct11NonConstGetEv( + __this, + ) + } + } + } + + /// A regular (non-thread-safe) struct for comparison. + /// + /// Generated from: rs_bindings_from_cc/test/annotations/thread_safe.h;l=26 + #[derive(Clone, Copy, ::ctor::MoveAndAssignViaCopy)] + #[repr(C, align(4))] + ///CRUBIT_ANNOTATE: cpp_type=crubit :: test :: RegularStruct + pub struct RegularStruct { + __non_field_data: [::core::mem::MaybeUninit; 0], + /// Reason for representing this field as a blob of bytes: + /// Types of non-public C++ fields can be elided away + pub(crate) x_: [::core::mem::MaybeUninit; 4], + } + impl !Send for RegularStruct {} + impl !Sync for RegularStruct {} + unsafe impl ::cxx::ExternType for RegularStruct { + type Id = ::cxx::type_id!("crubit :: test :: RegularStruct"); + type Kind = ::cxx::kind::Trivial; + } + impl RegularStruct { + /// # Safety + /// + /// The caller must ensure that the following unsafe arguments are not misused by the function: + /// * `__this`: raw pointer + /// + /// Generated from: rs_bindings_from_cc/test/annotations/thread_safe.h;l=28 + #[inline(always)] + pub unsafe fn ConstGet(__this: *const Self) -> ::ffi_11::c_int { + unsafe { self::regular_struct::ConstGet(__this) } + } + /// # Safety + /// + /// The caller must ensure that the following unsafe arguments are not misused by the function: + /// * `__this`: raw pointer + /// + /// Generated from: rs_bindings_from_cc/test/annotations/thread_safe.h;l=29 + #[inline(always)] + pub unsafe fn NonConstGet(__this: *mut Self) -> ::ffi_11::c_int { + unsafe { self::regular_struct::NonConstGet(__this) } + } + } + + /// Generated from: rs_bindings_from_cc/test/annotations/thread_safe.h;l=26 + impl Default for RegularStruct { + #[inline(always)] + fn default() -> Self { + let mut tmp = ::core::mem::MaybeUninit::::zeroed(); + unsafe { + crate::detail::__rust_thunk___ZN6crubit4test13RegularStructC1Ev( + &raw mut tmp as *mut _, + ); + tmp.assume_init() + } + } + } + + pub mod regular_struct { + /// # Safety + /// + /// The caller must ensure that the following unsafe arguments are not misused by the function: + /// * `__this`: raw pointer + /// + /// Generated from: rs_bindings_from_cc/test/annotations/thread_safe.h;l=28 + #[inline(always)] + pub(crate) unsafe fn ConstGet( + __this: *const crate::crubit::test::RegularStruct, + ) -> ::ffi_11::c_int { + unsafe { + crate::detail::__rust_thunk___ZNK6crubit4test13RegularStruct8ConstGetEv(__this) + } + } + /// # Safety + /// + /// The caller must ensure that the following unsafe arguments are not misused by the function: + /// * `__this`: raw pointer + /// + /// Generated from: rs_bindings_from_cc/test/annotations/thread_safe.h;l=29 + #[inline(always)] + pub(crate) unsafe fn NonConstGet( + __this: *mut crate::crubit::test::RegularStruct, + ) -> ::ffi_11::c_int { + unsafe { + crate::detail::__rust_thunk___ZN6crubit4test13RegularStruct11NonConstGetEv( + __this, + ) + } + } + } + } +} + +// namespace crubit::test + +mod detail { + #[allow(unused_imports)] + use super::*; + unsafe extern "C" { + pub(crate) unsafe fn __rust_thunk___ZN6crubit4test16ThreadSafeStructC1Ev( + __this: *mut ::core::ffi::c_void, + ); + pub(crate) unsafe fn __rust_thunk___ZNK6crubit4test16ThreadSafeStruct8ConstGetEv( + __this: *const crate::crubit::test::ThreadSafeStruct, + ) -> ::ffi_11::c_int; + pub(crate) unsafe fn __rust_thunk___ZN6crubit4test16ThreadSafeStruct11NonConstGetEv( + __this: *mut crate::crubit::test::ThreadSafeStruct, + ) -> ::ffi_11::c_int; + pub(crate) unsafe fn __rust_thunk___ZN6crubit4test13RegularStructC1Ev( + __this: *mut ::core::ffi::c_void, + ); + pub(crate) unsafe fn __rust_thunk___ZNK6crubit4test13RegularStruct8ConstGetEv( + __this: *const crate::crubit::test::RegularStruct, + ) -> ::ffi_11::c_int; + pub(crate) unsafe fn __rust_thunk___ZN6crubit4test13RegularStruct11NonConstGetEv( + __this: *mut crate::crubit::test::RegularStruct, + ) -> ::ffi_11::c_int; + } +} + +const _: () = { + assert!(::core::mem::size_of::() == 4); + assert!(::core::mem::align_of::() == 4); + static_assertions::assert_impl_all!(crate::crubit::test::ThreadSafeStruct: Copy,Clone); + static_assertions::assert_not_impl_any!(crate::crubit::test::ThreadSafeStruct: Drop); + assert!(::core::mem::offset_of!(crate::crubit::test::ThreadSafeStruct, x_) == 0); + assert!(::core::mem::size_of::() == 4); + assert!(::core::mem::align_of::() == 4); + static_assertions::assert_impl_all!(crate::crubit::test::RegularStruct: Copy,Clone); + static_assertions::assert_not_impl_any!(crate::crubit::test::RegularStruct: Drop); + assert!(::core::mem::offset_of!(crate::crubit::test::RegularStruct, x_) == 0); +}; diff --git a/rs_bindings_from_cc/test/annotations/thread_safe_test.rs b/rs_bindings_from_cc/test/annotations/thread_safe_test.rs new file mode 100644 index 000000000..63860a551 --- /dev/null +++ b/rs_bindings_from_cc/test/annotations/thread_safe_test.rs @@ -0,0 +1,49 @@ +// Part of the Crubit project, under the Apache License v2.0 with LLVM +// Exceptions. See /LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +use ctor::emplace; +use ctor::CtorNew; +use googletest::gtest; +use static_assertions::assert_not_impl_any; +use thread_safe::crubit::test::ThreadSafeStruct; + +#[gtest] +fn test_thread_safe_is_send() { + fn assert_send() {} + assert_send::(); +} + +#[gtest] +fn test_thread_safe_is_sync() { + fn assert_sync() {} + assert_sync::(); +} + +#[gtest] +fn test_thread_safe_struct_methods_via_shared_self_ref() { + let s = emplace!(ThreadSafeStruct::ctor_new(())); + // TODO(b/475929893) Update to use &self method calls once implemented. + let ptr: *mut ThreadSafeStruct = s.as_ref().get_ref() as *const _ as *mut _; + unsafe { + assert_eq!(ThreadSafeStruct::ConstGet(ptr as *const _), 0); + assert_eq!(ThreadSafeStruct::NonConstGet(ptr), 0); + } +} + +#[gtest] +fn test_regular_struct_is_not_send_or_sync() { + assert_not_impl_any!(thread_safe::crubit::test::RegularStruct: Send, Sync); +} + +#[gtest] +fn test_regular_struct_round_trip_via_raw_ptr() { + use thread_safe::crubit::test::RegularStruct; + let s = emplace!(RegularStruct::ctor_new(())); + // The non-thread-safe struct must use raw pointers for method calls. + let ptr: *mut RegularStruct = s.as_ref().get_ref() as *const _ as *mut _; + unsafe { + assert_eq!(RegularStruct::ConstGet(ptr as *const _), 0); + assert_eq!(RegularStruct::NonConstGet(ptr), 0); + } +} diff --git a/support/annotations.h b/support/annotations.h index fa58cb0e8..b4092ed46 100644 --- a/support/annotations.h +++ b/support/annotations.h @@ -333,7 +333,7 @@ // Marks a type as thread-safe for Rust interop. // // Types annotated with `CRUBIT_THREAD_SAFE` will: -// (TODO: b/475929893) * Implement `Send + Sync` in Rust +// * Implement `Send + Sync` in Rust // (TODO: b/475929893) * Have their internal representation wrapped in // `UnsafeCell`, allowing non-const C++ methods to be called via shared // references (`&self`)