Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add callback to declare functions/statics safe #3058

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions bindgen-tests/tests/headers/parsecb-declare-safe.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// bindgen-parse-callbacks: declare-safe

const int my_safe_var[3] = {1,2,3};

int my_safe_func();
23 changes: 23 additions & 0 deletions bindgen-tests/tests/parse_callbacks/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,28 @@ impl ParseCallbacks for WrapAsVariadicFn {
}
}

#[derive(Debug)]
struct DeclareSafe;

impl ParseCallbacks for DeclareSafe {
fn declare_safe(&self, item_info: ItemInfo<'_>) -> Option<String> {
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<dyn ParseCallbacks> {
match cb {
"enum-variant-rename" => Box::new(EnumVariantRename),
Expand All @@ -154,6 +176,7 @@ pub fn lookup(cb: &str) -> Box<dyn ParseCallbacks> {
}
"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-")
Expand Down
55 changes: 54 additions & 1 deletion bindgen-tests/tests/tests.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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<String> {
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;
}
"#;
Comment on lines +471 to +477
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are these comments meant to describe only the reason why they are marked as safe, i.e. rather than the full signature being correct? Related: rust-lang/rust-clippy#13560.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The specific comments are just there to test things are working, they could be replaced by dummy reasoning for test.

The comments can be used to express any kind of safety reasoning. Generally I'd expect people to only reason about why a function is safe to call or a const is safe to access and to rely on bindgen to generate correct bindings.


assert_eq!(expected, actual);
}

#[test]
fn test_multiple_header_calls_in_builder() {
let actual = builder()
Expand Down
12 changes: 12 additions & 0 deletions bindgen/callbacks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<String> {
None
}

/// Provide a list of custom attributes.
///
/// If no additional attributes are wanted, this function should return an
Expand Down Expand Up @@ -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
Expand Down
55 changes: 53 additions & 2 deletions bindgen/codegen/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
);

Expand Down Expand Up @@ -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;
}
};

Expand Down Expand Up @@ -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<TokenStream>, Option<TokenStream>) {
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,
Expand Down
Loading