clang: Only match the attribute name when detecting function attributes. When an attribute comes from a macro, libclang reports an extent that starts at the attribute in the macro definition but ends at the expansion site. Tokenizing that range lexes everything in between, so `has_attrs` saw the `_Noreturn` from an unrelated `#define` and marked a deprecated function as `-> !`. If the macro lives in another file, the range spans two files and tokenizes to nothing, so a real `_Noreturn` behind such a macro was missed. Only look at the first token at the start of the attribute's extent, which is the attribute name and is lexed from the right place either way. Fixes #3496
diff --git a/bindgen-tests/tests/expectations/tests/issue-3496-noreturn-unrelated-attribute.rs b/bindgen-tests/tests/expectations/tests/issue-3496-noreturn-unrelated-attribute.rs new file mode 100644 index 0000000..d94b609 --- /dev/null +++ b/bindgen-tests/tests/expectations/tests/issue-3496-noreturn-unrelated-attribute.rs
@@ -0,0 +1,10 @@ +#![allow(dead_code, non_snake_case, non_camel_case_types, non_upper_case_globals)] +unsafe extern "C" { + pub fn foo() -> ::std::os::raw::c_int; +} +unsafe extern "C" { + pub fn bar() -> !; +} +unsafe extern "C" { + pub fn baz() -> !; +}
diff --git a/bindgen-tests/tests/headers/issue-3496-noreturn-unrelated-attribute.h b/bindgen-tests/tests/headers/issue-3496-noreturn-unrelated-attribute.h new file mode 100644 index 0000000..49b540f --- /dev/null +++ b/bindgen-tests/tests/headers/issue-3496-noreturn-unrelated-attribute.h
@@ -0,0 +1,9 @@ +// bindgen-flags: --enable-function-attribute-detection +#include "issue-3496/noreturn.h" + +#define DEPRECATED __attribute__((deprecated("foo"))) +#define NORETURN _Noreturn + +DEPRECATED int foo(void); +NORETURN void bar(void); +OTHER_FILE_NORETURN void baz(void);
diff --git a/bindgen-tests/tests/headers/issue-3496/noreturn.h b/bindgen-tests/tests/headers/issue-3496/noreturn.h new file mode 100644 index 0000000..dfc14f1 --- /dev/null +++ b/bindgen-tests/tests/headers/issue-3496/noreturn.h
@@ -0,0 +1 @@ +#define OTHER_FILE_NORETURN _Noreturn
diff --git a/bindgen/clang.rs b/bindgen/clang.rs index 44e254f..eff4751 100644 --- a/bindgen/clang.rs +++ b/bindgen/clang.rs
@@ -790,7 +790,7 @@ // `attr.name` and` attr.token_kind` are checked against unexposed attributes only. if attr.kind == Some(kind) || (kind == CXCursor_UnexposedAttr && - cur.tokens().iter().any(|t| { + cur.first_token().is_some_and(|t| { t.kind == attr.token_kind && t.spelling() == attr.name })) @@ -960,7 +960,19 @@ /// Gets the tokens that correspond to that cursor. pub(crate) fn tokens(&self) -> RawTokens<'_> { - RawTokens::new(self) + RawTokens::new(self, self.extent()) + } + + /// Gets the first token of this cursor. + /// + /// Unlike `tokens()`, this also works for cursors that come from a macro + /// expansion. The extent of those starts inside the macro definition but + /// ends at the expansion site, so tokenizing it yields everything in + /// between, or nothing at all if the macro is defined in another file. + pub(crate) fn first_token(&self) -> Option<ClangToken> { + let start = unsafe { clang_getRangeStart(self.extent()) }; + let range = unsafe { clang_getRange(start, start) }; + RawTokens::new(self, range).iter().next() } /// Gets the tokens that correspond to that cursor as `cexpr` tokens. @@ -998,10 +1010,9 @@ } impl<'a> RawTokens<'a> { - fn new(cursor: &'a Cursor) -> Self { + fn new(cursor: &'a Cursor, range: CXSourceRange) -> Self { let mut tokens = ptr::null_mut(); let mut token_count = 0; - let range = cursor.extent(); let tu = unsafe { clang_Cursor_getTranslationUnit(cursor.x) }; unsafe { clang_tokenize(tu, range, &mut tokens, &mut token_count) }; Self {