Skip to content

Commit eee4d5e

Browse files
committed
Add callback to declare functions/statics safe
1 parent 3e0094e commit eee4d5e

File tree

6 files changed

+154
-3
lines changed

6 files changed

+154
-3
lines changed

bindgen-tests/tests/expectations/tests/parsecb-declare-safe.rs

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
// bindgen-parse-callbacks: declare-safe
2+
3+
const int my_safe_var[3] = {1,2,3};
4+
5+
int my_safe_func();

bindgen-tests/tests/parse_callbacks/mod.rs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,28 @@ impl ParseCallbacks for WrapAsVariadicFn {
146146
}
147147
}
148148

149+
#[derive(Debug)]
150+
struct DeclareSafe;
151+
152+
impl ParseCallbacks for DeclareSafe {
153+
fn declare_safe(&self, item_info: ItemInfo<'_>) -> Option<String> {
154+
match item_info.kind {
155+
ItemKind::Function => {
156+
if item_info.name == "my_safe_func" {
157+
return Some("safe to call".to_owned());
158+
}
159+
}
160+
ItemKind::Var => {
161+
if item_info.name == "my_safe_var" {
162+
return Some("safe to access".to_owned());
163+
}
164+
}
165+
_ => todo!(),
166+
}
167+
None
168+
}
169+
}
170+
149171
pub fn lookup(cb: &str) -> Box<dyn ParseCallbacks> {
150172
match cb {
151173
"enum-variant-rename" => Box::new(EnumVariantRename),
@@ -154,6 +176,7 @@ pub fn lookup(cb: &str) -> Box<dyn ParseCallbacks> {
154176
}
155177
"wrap-as-variadic-fn" => Box::new(WrapAsVariadicFn),
156178
"type-visibility" => Box::new(TypeVisibility),
179+
"declare-safe" => Box::new(DeclareSafe),
157180
call_back => {
158181
if let Some(prefix) =
159182
call_back.strip_prefix("remove-function-prefix-")

bindgen-tests/tests/tests.rs

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
use bindgen::{clang_version, Builder};
1+
use bindgen::callbacks::{ItemInfo, ItemKind, ParseCallbacks};
2+
use bindgen::{clang_version, Builder, Formatter};
23
use owo_colors::{OwoColorize, Style};
34
use similar::{ChangeTag, TextDiff};
45
use std::env;
@@ -427,6 +428,58 @@ fn test_header_contents() {
427428
assert_eq!(expected, actual);
428429
}
429430

431+
#[test]
432+
fn test_declare_safe() {
433+
// prettyplease does not retain non-doc comments: https://github.com/dtolnay/prettyplease/issues/50
434+
435+
#[derive(Debug)]
436+
struct DeclareSafe;
437+
438+
impl ParseCallbacks for DeclareSafe {
439+
fn declare_safe(&self, item_info: ItemInfo<'_>) -> Option<String> {
440+
match item_info.kind {
441+
ItemKind::Function => {
442+
if item_info.name == "my_safe_func" {
443+
return Some("safe to call".to_owned());
444+
}
445+
}
446+
ItemKind::Var => {
447+
if item_info.name == "my_safe_var" {
448+
return Some("safe to access".to_owned());
449+
}
450+
}
451+
_ => todo!(),
452+
}
453+
None
454+
}
455+
}
456+
457+
let actual = builder()
458+
.disable_header_comment()
459+
.header_contents(
460+
"safe.h",
461+
"const int my_safe_var[3] = {1,2,3};\
462+
int my_safe_func();",
463+
)
464+
.formatter(Formatter::Rustfmt)
465+
.parse_callbacks(Box::new(DeclareSafe))
466+
.generate()
467+
.unwrap()
468+
.to_string();
469+
470+
let expected = r#"unsafe extern "C" {
471+
/* Safety : "safe to access" */
472+
pub safe static my_safe_var: [::std::os::raw::c_int; 3usize];
473+
}
474+
unsafe extern "C" {
475+
/* Safety : "safe to call" */
476+
pub safe fn my_safe_func() -> ::std::os::raw::c_int;
477+
}
478+
"#;
479+
480+
assert_eq!(expected, actual);
481+
}
482+
430483
#[test]
431484
fn test_multiple_header_calls_in_builder() {
432485
let actual = builder()

bindgen/callbacks.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,17 @@ pub trait ParseCallbacks: fmt::Debug {
129129
vec![]
130130
}
131131

132+
/// Allows declaring items as `safe`.
133+
///
134+
/// The returned string will be prepended to the item as `Safety: ...` comment.
135+
///
136+
/// When using [`Formatter::Prettyplease`][crate::Formatter::Prettyplease] to format code, non-doc comments are removed ([issue][doc_removal]).
137+
///
138+
/// [doc_removal]: https://github.com/dtolnay/prettyplease/issues/50
139+
fn declare_safe(&self, _item_info: ItemInfo<'_>) -> Option<String> {
140+
None
141+
}
142+
132143
/// Provide a list of custom attributes.
133144
///
134145
/// If no additional attributes are wanted, this function should return an
@@ -263,6 +274,7 @@ pub struct ItemInfo<'a> {
263274
}
264275

265276
/// An enum indicating the kind of item for an `ItemInfo`.
277+
#[derive(Copy, Clone)]
266278
#[non_exhaustive]
267279
pub enum ItemKind {
268280
/// A Function

bindgen/codegen/mod.rs

Lines changed: 53 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -806,10 +806,17 @@ impl CodeGenerator for Var {
806806
.unsafe_extern_blocks
807807
.then(|| quote!(unsafe));
808808

809+
let (safety_comment, var_safety) = utils::declare_safe(
810+
&canonical_ident,
811+
crate::callbacks::ItemKind::Var,
812+
ctx,
813+
);
814+
809815
let tokens = quote!(
810816
#safety extern "C" {
811817
#(#attrs)*
812-
pub static #maybe_mut #canonical_ident: #ty;
818+
#safety_comment
819+
pub #var_safety static #maybe_mut #canonical_ident: #ty;
813820
}
814821
);
815822

@@ -4717,11 +4724,18 @@ impl CodeGenerator for Function {
47174724
.unsafe_extern_blocks
47184725
.then(|| quote!(unsafe));
47194726

4727+
let (safety_comment, fn_safety) = utils::declare_safe(
4728+
&ident,
4729+
crate::callbacks::ItemKind::Function,
4730+
ctx,
4731+
);
4732+
47204733
let tokens = quote! {
47214734
#wasm_link_attribute
47224735
#safety extern #abi {
47234736
#(#attributes)*
4724-
pub fn #ident ( #( #args ),* ) #ret;
4737+
#safety_comment
4738+
pub #fn_safety fn #ident ( #( #args ),* ) #ret;
47254739
}
47264740
};
47274741

@@ -5177,18 +5191,55 @@ pub(crate) mod utils {
51775191
use super::helpers::BITFIELD_UNIT;
51785192
use super::serialize::CSerialize;
51795193
use super::{error, CodegenError, CodegenResult, ToRustTyOrOpaque};
5194+
use crate::callbacks::{ItemInfo, ItemKind};
51805195
use crate::ir::context::BindgenContext;
51815196
use crate::ir::context::TypeId;
51825197
use crate::ir::function::{Abi, ClangAbi, FunctionSig};
51835198
use crate::ir::item::{Item, ItemCanonicalPath};
51845199
use crate::ir::ty::TypeKind;
51855200
use crate::{args_are_cpp, file_is_cpp};
5201+
use proc_macro2::{Ident, TokenStream};
51865202
use std::borrow::Cow;
51875203
use std::io::Write;
51885204
use std::mem;
51895205
use std::path::PathBuf;
51905206
use std::str::FromStr;
51915207

5208+
pub(super) fn declare_safe(
5209+
item_ident: &Ident,
5210+
item_kind: ItemKind,
5211+
context: &BindgenContext,
5212+
) -> (Option<TokenStream>, Option<TokenStream>) {
5213+
let safety_comment = context
5214+
.options()
5215+
.rust_features
5216+
.unsafe_extern_blocks
5217+
.then( || {
5218+
context.options().last_callback(|cb| {
5219+
cb.declare_safe(ItemInfo {
5220+
name: &item_ident.to_string(),
5221+
kind: item_kind,
5222+
})
5223+
})
5224+
})
5225+
.flatten()
5226+
.map(|safety_comment| {
5227+
let comment =
5228+
proc_macro2::Punct::new('/', proc_macro2::Spacing::Joint);
5229+
let comment2 =
5230+
proc_macro2::Punct::new('*', proc_macro2::Spacing::Alone);
5231+
let comment3 =
5232+
proc_macro2::Punct::new('*', proc_macro2::Spacing::Joint);
5233+
let comment4 =
5234+
proc_macro2::Punct::new('/', proc_macro2::Spacing::Alone);
5235+
5236+
quote!(#comment #comment2 Safety: #safety_comment #comment3 #comment4)
5237+
});
5238+
5239+
let item_safety = safety_comment.is_some().then_some(quote!(safe));
5240+
(safety_comment, item_safety)
5241+
}
5242+
51925243
pub(super) fn serialize_items(
51935244
result: &CodegenResult,
51945245
context: &BindgenContext,

0 commit comments

Comments
 (0)