Skip to content
86 changes: 63 additions & 23 deletions src/clang.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,40 @@ use std::hash::Hasher;
use std::os::raw::{c_char, c_int, c_longlong, c_uint, c_ulong, c_ulonglong};
use std::{mem, ptr, slice};

/// Type representing a clang attribute.
///
/// Values of this type can be used to check for different attributes using the `has_attrs`
/// function.
pub struct Attribute {
name: &'static [u8],
kind: Option<CXCursorKind>,
token_kind: CXTokenKind,
}

impl Attribute {
/// A `warn_unused_result` attribute.
pub const MUST_USE: Self = Self {
name: b"warn_unused_result",
// FIXME(emilio): clang-sys doesn't expose `CXCursor_WarnUnusedResultAttr` (from clang 9).
kind: Some(440),
token_kind: CXToken_Identifier,
};

/// A `_Noreturn` attribute.
pub const NO_RETURN: Self = Self {
name: b"_Noreturn",
kind: None,
token_kind: CXToken_Keyword,
};

/// A `[[noreturn]]` attribute.
pub const NO_RETURN_CPP: Self = Self {
name: b"noreturn",
kind: None,
token_kind: CXToken_Identifier,
};
}

/// A cursor into the Clang AST, pointing to an AST node.
///
/// We call the AST node pointed to by the cursor the cursor's "referent".
Expand Down Expand Up @@ -638,35 +672,41 @@ impl Cursor {
}
}

/// Whether this cursor has the `warn_unused_result` attribute.
pub fn has_warn_unused_result_attr(&self) -> bool {
// FIXME(emilio): clang-sys doesn't expose this (from clang 9).
const CXCursor_WarnUnusedResultAttr: CXCursorKind = 440;
self.has_attr("warn_unused_result", Some(CXCursor_WarnUnusedResultAttr))
}
/// Does this cursor have the given attributes?
pub fn has_attrs<const N: usize>(
&self,
attrs: &[Attribute; N],
) -> [bool; N] {
let mut found_attrs = [false; N];
let mut found_count = 0;

/// Does this cursor have the given attribute?
///
/// `name` is checked against unexposed attributes.
fn has_attr(&self, name: &str, clang_kind: Option<CXCursorKind>) -> bool {
let mut found_attr = false;
self.visit(|cur| {
let kind = cur.kind();
found_attr = clang_kind.map_or(false, |k| k == kind) ||
(kind == CXCursor_UnexposedAttr &&
cur.tokens().iter().any(|t| {
t.kind == CXToken_Identifier &&
t.spelling() == name.as_bytes()
}));

if found_attr {
CXChildVisit_Break
} else {
CXChildVisit_Continue
for (idx, attr) in attrs.iter().enumerate() {
let found_attr = &mut found_attrs[idx];
if !*found_attr {
// `attr.name` and` attr.token_kind` are checked against unexposed attributes only.
if attr.kind.map_or(false, |k| k == kind) ||
(kind == CXCursor_UnexposedAttr &&
cur.tokens().iter().any(|t| {
t.kind == attr.token_kind &&
t.spelling() == attr.name
}))
{
*found_attr = true;
found_count += 1;

if found_count == N {
return CXChildVisit_Break;
}
}
}
}

CXChildVisit_Continue
});

found_attr
found_attrs
}

/// Given that this cursor's referent is a `typedef`, get the `Type` that is
Expand Down
4 changes: 4 additions & 0 deletions src/codegen/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4704,6 +4704,10 @@ pub mod utils {
ctx: &BindgenContext,
sig: &FunctionSig,
) -> proc_macro2::TokenStream {
if sig.is_divergent() {
return quote! { -> ! };
}

let return_item = ctx.resolve_item(sig.return_type());
if let TypeKind::Void = *return_item.kind().expect_type().kind() {
quote! {}
Expand Down
37 changes: 33 additions & 4 deletions src/ir/function.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use super::dot::DotAttributes;
use super::item::Item;
use super::traversal::{EdgeKind, Trace, Tracer};
use super::ty::TypeKind;
use crate::clang;
use crate::clang::{self, Attribute};
use crate::parse::{
ClangItemParser, ClangSubItemParser, ParseError, ParseResult,
};
Expand Down Expand Up @@ -228,6 +228,7 @@ pub struct FunctionSig {

/// Whether this function is variadic.
is_variadic: bool,
is_divergent: bool,

/// Whether this function's return value must be used.
must_use: bool,
Expand Down Expand Up @@ -358,13 +359,15 @@ impl FunctionSig {
return_type: TypeId,
argument_types: Vec<(Option<String>, TypeId)>,
is_variadic: bool,
is_divergent: bool,
must_use: bool,
abi: Abi,
) -> Self {
FunctionSig {
return_type,
argument_types,
is_variadic,
is_divergent,
must_use,
abi,
}
Expand Down Expand Up @@ -447,8 +450,23 @@ impl FunctionSig {
}
};

let must_use = ctx.options().enable_function_attribute_detection &&
cursor.has_warn_unused_result_attr();
let (must_use, mut is_divergent) =
if ctx.options().enable_function_attribute_detection {
let [must_use, no_return, no_return_cpp] = cursor.has_attrs(&[
Attribute::MUST_USE,
Attribute::NO_RETURN,
Attribute::NO_RETURN_CPP,
]);
(must_use, no_return || no_return_cpp)
} else {
Default::default()
};

// This looks easy to break but the clang parser keeps the type spelling clean even if
// other attributes are added.
is_divergent =
is_divergent || ty.spelling().contains("__attribute__((noreturn))");

let is_method = kind == CXCursor_CXXMethod;
let is_constructor = kind == CXCursor_Constructor;
let is_destructor = kind == CXCursor_Destructor;
Expand Down Expand Up @@ -528,7 +546,14 @@ impl FunctionSig {
warn!("Unknown calling convention: {:?}", call_conv);
}

Ok(Self::new(ret, args, ty.is_variadic(), must_use, abi))
Ok(Self::new(
ret,
args,
ty.is_variadic(),
is_divergent,
must_use,
abi,
))
}

/// Get this function signature's return type.
Expand Down Expand Up @@ -575,6 +600,10 @@ impl FunctionSig {

matches!(self.abi, Abi::C | Abi::Unknown(..))
}

pub(crate) fn is_divergent(&self) -> bool {
self.is_divergent
}
}

impl ClangSubItemParser for Function {
Expand Down
19 changes: 19 additions & 0 deletions tests/expectations/tests/noreturn.rs

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

4 changes: 4 additions & 0 deletions tests/headers/noreturn.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
// bindgen-flags: --enable-function-attribute-detection -- -std=c++11
_Noreturn void f(void);
__attribute__((noreturn)) void g(void);
[[noreturn]] void h(void);