diff --git a/bindgen-tests/tests/expectations/tests/parsecb-declare-safe.rs b/bindgen-tests/tests/expectations/tests/parsecb-declare-safe.rs new file mode 100644 index 0000000000..4ecd938dd8 --- /dev/null +++ b/bindgen-tests/tests/expectations/tests/parsecb-declare-safe.rs @@ -0,0 +1,7 @@ +#![allow(dead_code, non_snake_case, non_camel_case_types, non_upper_case_globals)] +unsafe extern "C" { + pub safe static my_safe_var: [::std::os::raw::c_int; 3usize]; +} +unsafe extern "C" { + pub safe fn my_safe_func() -> ::std::os::raw::c_int; +} diff --git a/bindgen-tests/tests/headers/parsecb-declare-safe.h b/bindgen-tests/tests/headers/parsecb-declare-safe.h new file mode 100644 index 0000000000..77d6f73b7d --- /dev/null +++ b/bindgen-tests/tests/headers/parsecb-declare-safe.h @@ -0,0 +1,5 @@ +// bindgen-parse-callbacks: declare-safe + +const int my_safe_var[3] = {1,2,3}; + +int my_safe_func(); diff --git a/bindgen-tests/tests/parse_callbacks/mod.rs b/bindgen-tests/tests/parse_callbacks/mod.rs index 02d7fe8316..dc29f2052e 100644 --- a/bindgen-tests/tests/parse_callbacks/mod.rs +++ b/bindgen-tests/tests/parse_callbacks/mod.rs @@ -160,6 +160,28 @@ impl ParseCallbacks for OperatorRename { } } +#[derive(Debug)] +struct DeclareSafe; + +impl ParseCallbacks for DeclareSafe { + fn declare_safe(&self, item_info: ItemInfo<'_>) -> Option { + match item_info.kind { + ItemKind::Function => { + if item_info.name == "my_safe_func" { + return Some("safe to call".to_owned()); + } + } + ItemKind::Var => { + if item_info.name == "my_safe_var" { + return Some("safe to access".to_owned()); + } + } + _ => todo!(), + } + None + } +} + pub fn lookup(cb: &str) -> Box { match cb { "enum-variant-rename" => Box::new(EnumVariantRename), @@ -169,6 +191,7 @@ pub fn lookup(cb: &str) -> Box { "wrap-as-variadic-fn" => Box::new(WrapAsVariadicFn), "type-visibility" => Box::new(TypeVisibility), "operator-rename" => Box::new(OperatorRename), + "declare-safe" => Box::new(DeclareSafe), call_back => { if let Some(prefix) = call_back.strip_prefix("remove-function-prefix-") diff --git a/bindgen-tests/tests/tests.rs b/bindgen-tests/tests/tests.rs index 6e3c358d3e..030c179bf1 100644 --- a/bindgen-tests/tests/tests.rs +++ b/bindgen-tests/tests/tests.rs @@ -1,4 +1,5 @@ -use bindgen::{clang_version, Builder}; +use bindgen::callbacks::{ItemInfo, ItemKind, ParseCallbacks}; +use bindgen::{clang_version, Builder, Formatter}; use owo_colors::{OwoColorize, Style}; use similar::{ChangeTag, TextDiff}; use std::env; @@ -427,6 +428,58 @@ fn test_header_contents() { assert_eq!(expected, actual); } +#[test] +fn test_declare_safe() { + // prettyplease does not retain non-doc comments: https://github.com/dtolnay/prettyplease/issues/50 + + #[derive(Debug)] + struct DeclareSafe; + + impl ParseCallbacks for DeclareSafe { + fn declare_safe(&self, item_info: ItemInfo<'_>) -> Option { + match item_info.kind { + ItemKind::Function => { + if item_info.name == "my_safe_func" { + return Some("safe to call".to_owned()); + } + } + ItemKind::Var => { + if item_info.name == "my_safe_var" { + return Some("safe to access".to_owned()); + } + } + _ => todo!(), + } + None + } + } + + let actual = builder() + .disable_header_comment() + .header_contents( + "safe.h", + "const int my_safe_var[3] = {1,2,3};\ + int my_safe_func();", + ) + .formatter(Formatter::Rustfmt) + .parse_callbacks(Box::new(DeclareSafe)) + .generate() + .unwrap() + .to_string(); + + let expected = r#"unsafe extern "C" { + /* Safety : "safe to access" */ + pub safe static my_safe_var: [::std::os::raw::c_int; 3usize]; +} +unsafe extern "C" { + /* Safety : "safe to call" */ + pub safe fn my_safe_func() -> ::std::os::raw::c_int; +} +"#; + + assert_eq!(expected, actual); +} + #[test] fn test_multiple_header_calls_in_builder() { let actual = builder() diff --git a/bindgen/callbacks.rs b/bindgen/callbacks.rs index 630a306aec..3bb69cb781 100644 --- a/bindgen/callbacks.rs +++ b/bindgen/callbacks.rs @@ -134,6 +134,17 @@ pub trait ParseCallbacks: fmt::Debug { vec![] } + /// Allows declaring items as `safe`. + /// + /// The returned string will be prepended to the item as `Safety: ...` comment. + /// + /// When using [`Formatter::Prettyplease`][crate::Formatter::Prettyplease] to format code, non-doc comments are removed ([issue][doc_removal]). + /// + /// [doc_removal]: https://github.com/dtolnay/prettyplease/issues/50 + fn declare_safe(&self, _item_info: ItemInfo<'_>) -> Option { + None + } + /// Provide a list of custom attributes. /// /// If no additional attributes are wanted, this function should return an diff --git a/bindgen/codegen/mod.rs b/bindgen/codegen/mod.rs index a5aa73b5d8..d084f29f5c 100644 --- a/bindgen/codegen/mod.rs +++ b/bindgen/codegen/mod.rs @@ -856,10 +856,17 @@ impl CodeGenerator for Var { .unsafe_extern_blocks .then(|| quote!(unsafe)); + let (safety_comment, var_safety) = utils::declare_safe( + &canonical_ident, + crate::callbacks::ItemKind::Var, + ctx, + ); + let tokens = quote!( #safety extern "C" { #(#attrs)* - pub static #maybe_mut #canonical_ident: #ty; + #safety_comment + pub #var_safety static #maybe_mut #canonical_ident: #ty; } ); @@ -4870,11 +4877,18 @@ impl CodeGenerator for Function { .unsafe_extern_blocks .then(|| quote!(unsafe)); + let (safety_comment, fn_safety) = utils::declare_safe( + &ident, + crate::callbacks::ItemKind::Function, + ctx, + ); + let tokens = quote! { #block_attributes #safety extern #abi { #(#attributes)* - pub fn #ident ( #( #args ),* ) #ret; + #safety_comment + pub #fn_safety fn #ident ( #( #args ),* ) #ret; } }; @@ -5346,6 +5360,44 @@ pub(crate) mod utils { use std::path::PathBuf; use std::str::FromStr; + pub(super) fn declare_safe( + item_ident: &proc_macro2::Ident, + item_kind: crate::callbacks::ItemKind, + context: &BindgenContext, + ) -> ( + Option, + Option, + ) { + let safety_comment = context + .options() + .rust_features + .unsafe_extern_blocks + .then( || { + context.options().last_callback(|cb| { + cb.declare_safe(crate::callbacks::ItemInfo { + name: &item_ident.to_string(), + kind: item_kind, + }) + }) + }) + .flatten() + .map(|safety_comment| { + let comment = + proc_macro2::Punct::new('/', proc_macro2::Spacing::Joint); + let comment2 = + proc_macro2::Punct::new('*', proc_macro2::Spacing::Alone); + let comment3 = + proc_macro2::Punct::new('*', proc_macro2::Spacing::Joint); + let comment4 = + proc_macro2::Punct::new('/', proc_macro2::Spacing::Alone); + + quote!(#comment #comment2 Safety: #safety_comment #comment3 #comment4) + }); + + let item_safety = safety_comment.is_some().then_some(quote!(safe)); + (safety_comment, item_safety) + } + pub(super) fn serialize_items( result: &CodegenResult, context: &BindgenContext,