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 7aca0fd1a1..c7359cc13a 100644 --- a/bindgen-tests/tests/parse_callbacks/mod.rs +++ b/bindgen-tests/tests/parse_callbacks/mod.rs @@ -146,6 +146,28 @@ impl ParseCallbacks for WrapAsVariadicFn { } } +#[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), @@ -154,6 +176,7 @@ pub fn lookup(cb: &str) -> Box { } "wrap-as-variadic-fn" => Box::new(WrapAsVariadicFn), "type-visibility" => Box::new(TypeVisibility), + "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 fc0b41d187..082a821e65 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 8a21e98dea..ab062b43b4 100644 --- a/bindgen/callbacks.rs +++ b/bindgen/callbacks.rs @@ -129,6 +129,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 @@ -263,6 +274,7 @@ pub struct ItemInfo<'a> { } /// An enum indicating the kind of item for an `ItemInfo`. +#[derive(Copy, Clone)] #[non_exhaustive] pub enum ItemKind { /// A Function diff --git a/bindgen/codegen/mod.rs b/bindgen/codegen/mod.rs index 403ac42839..f423581235 100644 --- a/bindgen/codegen/mod.rs +++ b/bindgen/codegen/mod.rs @@ -806,10 +806,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; } ); @@ -4717,11 +4724,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! { #wasm_link_attribute #safety extern #abi { #(#attributes)* - pub fn #ident ( #( #args ),* ) #ret; + #safety_comment + pub #fn_safety fn #ident ( #( #args ),* ) #ret; } }; @@ -5177,18 +5191,55 @@ pub(crate) mod utils { use super::helpers::BITFIELD_UNIT; use super::serialize::CSerialize; use super::{error, CodegenError, CodegenResult, ToRustTyOrOpaque}; + use crate::callbacks::{ItemInfo, ItemKind}; use crate::ir::context::BindgenContext; use crate::ir::context::TypeId; use crate::ir::function::{Abi, ClangAbi, FunctionSig}; use crate::ir::item::{Item, ItemCanonicalPath}; use crate::ir::ty::TypeKind; use crate::{args_are_cpp, file_is_cpp}; + use proc_macro2::{Ident, TokenStream}; use std::borrow::Cow; use std::io::Write; use std::mem; use std::path::PathBuf; use std::str::FromStr; + pub(super) fn declare_safe( + item_ident: &Ident, + item_kind: ItemKind, + context: &BindgenContext, + ) -> (Option, Option) { + let safety_comment = context + .options() + .rust_features + .unsafe_extern_blocks + .then( || { + context.options().last_callback(|cb| { + cb.declare_safe(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,