Repository: rust-lang/rust-bindgen (rust)

Issue:
__attribute__((noreturn)) in function pointer argument applies to function's type
<!-- Thanks for filing a bindgen issue! We appreciate it :-) -->

### Input C/C++ Header

```c
void foo(__attribute__((noreturn)) void (*arg)(void));
```

### Bindgen Invocation

`$ bindgen /tmp/foo.h`

### Actual Results

```rust
/* automatically generated by rust-bindgen 0.66.1 */

extern "C" {
    pub fn foo(arg: ::std::option::Option<unsafe extern "C" fn() -> !>) -> !;
}
```

### Expected Results

```rust
/* automatically generated by rust-bindgen 0.66.1 */

extern "C" {
    pub fn foo(arg: ::std::option::Option<unsafe extern "C" fn() -> !>);
}
```

Note the lack of the second `-> !`.

### Notes

I'm fairly sure the problem is here:

https://github.com/rust-lang/rust-bindgen/blob/d0c2b1e3de54860cebb649af6fee2abca8410506/bindgen/ir/function.rs#L508-L511

Since the type of argument of the function contains `__attribute__((noreturn))`, this assumes that the function does as well...

Pull request description:
Correctly handle  __attribute__((noreturn)) in function arguments
Fixes #2715.

I have not been able to find a way to get at this information directly through the C API of libclang 9, so instead this patch just checks how deeply nested in parentheses the `__attribute__((noreturn))` qualifier is.

Patch:
diff --git a/bindgen/Cargo.toml b/bindgen/Cargo.toml
index 2a01c6ca..7dd4d01d 100644
--- a/bindgen/Cargo.toml
+++ b/bindgen/Cargo.toml
@@ -36,7 +36,7 @@ peeking_take_while = "0.1.2"
 prettyplease = { version = "0.2.7", optional = true, features = ["verbatim"] }
 proc-macro2 = { version = "1", default-features = false }
 quote = { version = "1", default-features = false }
-regex = { version = "1.5", default-features = false, features = ["std", "unicode-perl"] }
+regex = { version = "1.5.1", default-features = false, features = ["std", "unicode-perl"] }
 rustc-hash = "1.0.1"
 shlex = "1"
 syn = { version = "2.0", features = ["full", "extra-traits", "visit-mut"] }
diff --git a/bindgen/ir/function.rs b/bindgen/ir/function.rs
index 6679a3d5..448bcd22 100644
--- a/bindgen/ir/function.rs
+++ b/bindgen/ir/function.rs
@@ -505,10 +505,24 @@ impl FunctionSig {
                 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))");
+        // Check if the type contains __attribute__((noreturn)) outside of parentheses. This is
+        // somewhat fragile, but it seems to be the only way to get at this information as of
+        // libclang 9.
+        let ty_spelling = ty.spelling();
+        let has_attribute_noreturn = ty_spelling
+            .match_indices("__attribute__((noreturn))")
+            .any(|(i, _)| {
+                let depth = ty_spelling[..i]
+                    .bytes()
+                    .filter_map(|ch| match ch {
+                        b'(' => Some(1),
+                        b')' => Some(-1),
+                        _ => None,
+                    })
+                    .sum::<isize>();
+                depth == 0
+            });
+        is_divergent = is_divergent || has_attribute_noreturn;
 
         let is_method = kind == CXCursor_CXXMethod;
         let is_constructor = kind == CXCursor_Constructor;


Test patch:
diff --git a/bindgen-tests/tests/expectations/tests/noreturn.rs b/bindgen-tests/tests/expectations/tests/noreturn.rs
index 2b9b316b..2081d3d4 100644
--- a/bindgen-tests/tests/expectations/tests/noreturn.rs
+++ b/bindgen-tests/tests/expectations/tests/noreturn.rs
@@ -11,3 +11,11 @@ extern "C" {
     #[link_name = "\u{1}_Z1hv"]
     pub fn h() -> !;
 }
+extern "C" {
+    #[link_name = "\u{1}_Z1iPFvvE"]
+    pub fn i(arg: ::std::option::Option<unsafe extern "C" fn() -> !>);
+}
+extern "C" {
+    #[link_name = "\u{1}_Z1jPFvvE"]
+    pub fn j(arg: ::std::option::Option<unsafe extern "C" fn() -> !>) -> !;
+}
diff --git a/bindgen-tests/tests/headers/noreturn.hpp b/bindgen-tests/tests/headers/noreturn.hpp
index 4ce1e11e..bf9d58c7 100644
--- a/bindgen-tests/tests/headers/noreturn.hpp
+++ b/bindgen-tests/tests/headers/noreturn.hpp
@@ -2,3 +2,5 @@
 _Noreturn void f(void);
 __attribute__((noreturn)) void g(void);
 [[noreturn]] void h(void);
+void i(__attribute__((noreturn)) void (*arg)(void));
+__attribute__((noreturn)) void j(__attribute__((noreturn)) void (*arg)(void));