rust-lang / rust-bindgen

Automatically generates Rust FFI bindings to C (and some C++) libraries.
https://rust-lang.github.io/rust-bindgen/
BSD 3-Clause "New" or "Revised" License
4.43k stars 695 forks source link

C++ template with nested declaration and out-of-line definition #798

Open cbourjau opened 7 years ago

cbourjau commented 7 years ago

Using the magic of creduce I found an issue on nested templates which apparently lurk somewhere in a large library that I try to wrap. I think this is a bug, or am I doing something wrong?

Input C/C++ Header

template <typename, typename = int> class A {
  class B;
};

template <typename _CharT, typename _Traits> class A<_CharT, _Traits>::B {
  _Traits traits_type;
};

class C {
protected:
  void m_fn1(A<char>);
};

class Test {
  C fChain;
};

Bindgen Invocation

    let bindings = bindgen::Builder::default()
        .clang_arg("-x")
        .clang_arg("c++")
        .clang_arg("-std=c++11")
        .whitelisted_type("Test")
        .generate_comments(false)
        .header("src/test.hpp")
        .generate()
        .expect("Unable to generate bindings");

Actual Results

The generated rust code uses undefined types:

/* automatically generated by rust-bindgen */

#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct A {
    pub _address: u8,
}
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct A_B {
    pub traits_type: _Traits,
}
#[repr(C)]
#[derive(Debug, Copy)]
pub struct C {
    pub _address: u8,
}
#[test]
fn bindgen_test_layout_C() {
    assert_eq!(::std::mem::size_of::<C>() , 1usize , concat ! (
               "Size of: " , stringify ! ( C ) ));
    assert_eq! (::std::mem::align_of::<C>() , 1usize , concat ! (
                "Alignment of " , stringify ! ( C ) ));
}
extern "C" {
    #[link_name = "_ZN1C5m_fn1E1AIciE"]
    pub fn C_m_fn1(this: *mut C, arg1: A);
}
impl Clone for C {
    fn clone(&self) -> Self { *self }
}
impl C {
    #[inline]
    pub unsafe fn m_fn1(&mut self, arg1: A) { C_m_fn1(self, arg1) }
}
#[repr(C)]
#[derive(Debug, Copy)]
pub struct Test {
    pub fChain: C,
}
#[test]
fn bindgen_test_layout_Test() {
    assert_eq!(::std::mem::size_of::<Test>() , 1usize , concat ! (
               "Size of: " , stringify ! ( Test ) ));
    assert_eq! (::std::mem::align_of::<Test>() , 1usize , concat ! (
                "Alignment of " , stringify ! ( Test ) ));
    assert_eq! (unsafe {
                & ( * ( 0 as * const Test ) ) . fChain as * const _ as usize }
                , 0usize , concat ! (
                "Alignment of field: " , stringify ! ( Test ) , "::" ,
                stringify ! ( fChain ) ));
}
impl Clone for Test {
    fn clone(&self) -> Self { *self }
}

which yields the following error when compiled

cargo build
   Compiling alice-sys v0.1.0 (file:///home/christian/repos/rust/bindgen_mvnp)
error[E0412]: cannot find type `_Traits` in this scope
  --> /home/christian/repos/rust/bindgen_mvnp/target/debug/build/alice-sys-350880093f241c3d/out/bindings.rs:11:22
   |
11 |     pub traits_type: _Traits,
   |                      ^^^^^^^ not found in this scope

error[E0412]: cannot find type `_Traits` in this scope
  --> /home/christian/repos/rust/bindgen_mvnp/target/debug/build/alice-sys-350880093f241c3d/out/bindings.rs:11:22
   |
11 |     pub traits_type: _Traits,
   |                      ^^^^^^^ not found in this scope

error[E0204]: the trait `Copy` may not be implemented for this type
  --> /home/christian/repos/rust/bindgen_mvnp/target/debug/build/alice-sys-350880093f241c3d/out/bindings.rs:9:17
   |
9  | #[derive(Debug, Copy, Clone)]
   |                 ^^^^
10 | pub struct A_B {
11 |     pub traits_type: _Traits,
   |     ------------------------ this field does not implement `Copy`

error: aborting due to 3 previous errors

Expected Results

I would expect the _Traits type to be defined in the rust code.

RUST_LOG=bindgen Output

RUST_LOG=bindgen did not yield additional output

fitzgen commented 7 years ago

Thanks for the bug report!

RUST_LOG=bindgen did not yield additional output

FYI, to get the logging, this should be set when running bindgen, not when compiling Rust code emitted by bindgen. There's been some confusion over this point in the past before and we should probably make the instructions more clear.

fitzgen commented 7 years ago

Also, thanks for the reduced test case, this helps a lot!

fitzgen commented 7 years ago

Here is a further reduced version of the test case:

// bindgen-flags: -- -std=c++11

template <typename T>
class Outer {
    // Declaration, but no inline definition.
    class Inner;
};

// Out of line definition.
template <typename U>
class Outer<U>::Inner {
    U inner_member;
};

and the IR graph we construct from it:

ir

I guess the problem is that we don't understand that effectively T == U, but understanding that seems kind of hard, at first blush.

fitzgen commented 7 years ago

And here is the clang cursor AST:

``` ( kind = ClassTemplate spelling = "Outer" location = ./tests/headers/issue-798-nested-template.hpp:4:7 is-definition? true is-declaration? true is-inlined-function? false template-kind = ClassDecl usr = "c:@ST>1#T@Outer" semantic-parent.kind = TranslationUnit semantic-parent.spelling = "./tests/headers/issue-798-nested-template.hpp" semantic-parent.location = builtin definitions semantic-parent.is-definition? false semantic-parent.is-declaration? false semantic-parent.is-inlined-function? false type.kind = Invalid ( kind = TemplateTypeParameter spelling = "T" location = ./tests/headers/issue-798-nested-template.hpp:3:20 is-definition? true is-declaration? true is-inlined-function? false usr = "c:issue-798-nested-template.hpp@43" semantic-parent.kind = ClassTemplate semantic-parent.spelling = "Outer" semantic-parent.location = ./tests/headers/issue-798-nested-template.hpp:4:7 semantic-parent.is-definition? true semantic-parent.is-declaration? true semantic-parent.is-inlined-function? false semantic-parent.template-kind = ClassDecl semantic-parent.usr = "c:@ST>1#T@Outer" semantic-parent.semantic-parent.kind = TranslationUnit semantic-parent.semantic-parent.spelling = "./tests/headers/issue-798-nested-template.hpp" semantic-parent.semantic-parent.location = builtin definitions semantic-parent.semantic-parent.is-definition? false semantic-parent.semantic-parent.is-declaration? false semantic-parent.semantic-parent.is-inlined-function? false type.kind = Unexposed type.cconv = 100 type.spelling = "T" type.is-variadic? false type.canonical.kind = Unexposed type.canonical.cconv = 100 type.canonical.spelling = "type-parameter-0-0" type.canonical.is-variadic? false ) ( kind = ClassDecl spelling = "Inner" location = ./tests/headers/issue-798-nested-template.hpp:6:11 is-definition? false is-declaration? true is-inlined-function? false usr = "c:@ST>1#T@Outer@S@Inner" semantic-parent.kind = ClassTemplate semantic-parent.spelling = "Outer" semantic-parent.location = ./tests/headers/issue-798-nested-template.hpp:4:7 semantic-parent.is-definition? true semantic-parent.is-declaration? true semantic-parent.is-inlined-function? false semantic-parent.template-kind = ClassDecl semantic-parent.usr = "c:@ST>1#T@Outer" semantic-parent.semantic-parent.kind = TranslationUnit semantic-parent.semantic-parent.spelling = "./tests/headers/issue-798-nested-template.hpp" semantic-parent.semantic-parent.location = builtin definitions semantic-parent.semantic-parent.is-definition? false semantic-parent.semantic-parent.is-declaration? false semantic-parent.semantic-parent.is-inlined-function? false type.kind = Record type.cconv = 100 type.spelling = "Inner" type.is-variadic? false type.declaration.kind = ClassDecl type.declaration.spelling = "Inner" type.declaration.location = ./tests/headers/issue-798-nested-template.hpp:11:17 type.declaration.is-definition? true type.declaration.is-declaration? true type.declaration.is-inlined-function? false type.declaration.usr = "c:@ST>1#T@Outer@S@Inner" type.declaration.canonical.kind = ClassDecl type.declaration.canonical.spelling = "Inner" type.declaration.canonical.location = ./tests/headers/issue-798-nested-template.hpp:6:11 type.declaration.canonical.is-definition? false type.declaration.canonical.is-declaration? true type.declaration.canonical.is-inlined-function? false type.declaration.canonical.usr = "c:@ST>1#T@Outer@S@Inner" type.declaration.canonical.semantic-parent.kind = ClassTemplate type.declaration.canonical.semantic-parent.spelling = "Outer" type.declaration.canonical.semantic-parent.location = ./tests/headers/issue-798-nested-template.hpp:4:7 type.declaration.canonical.semantic-parent.is-definition? true type.declaration.canonical.semantic-parent.is-declaration? true type.declaration.canonical.semantic-parent.is-inlined-function? false type.declaration.canonical.semantic-parent.template-kind = ClassDecl type.declaration.canonical.semantic-parent.usr = "c:@ST>1#T@Outer" type.declaration.canonical.semantic-parent.semantic-parent.kind = TranslationUnit type.declaration.canonical.semantic-parent.semantic-parent.spelling = "./tests/headers/issue-798-nested-template.hpp" type.declaration.canonical.semantic-parent.semantic-parent.location = builtin definitions type.declaration.canonical.semantic-parent.semantic-parent.is-definition? false type.declaration.canonical.semantic-parent.semantic-parent.is-declaration? false type.declaration.canonical.semantic-parent.semantic-parent.is-inlined-function? false type.declaration.semantic-parent.kind = ClassTemplate type.declaration.semantic-parent.spelling = "Outer" type.declaration.semantic-parent.location = ./tests/headers/issue-798-nested-template.hpp:4:7 type.declaration.semantic-parent.is-definition? true type.declaration.semantic-parent.is-declaration? true type.declaration.semantic-parent.is-inlined-function? false type.declaration.semantic-parent.template-kind = ClassDecl type.declaration.semantic-parent.usr = "c:@ST>1#T@Outer" type.declaration.semantic-parent.semantic-parent.kind = TranslationUnit type.declaration.semantic-parent.semantic-parent.spelling = "./tests/headers/issue-798-nested-template.hpp" type.declaration.semantic-parent.semantic-parent.location = builtin definitions type.declaration.semantic-parent.semantic-parent.is-definition? false type.declaration.semantic-parent.semantic-parent.is-declaration? false type.declaration.semantic-parent.semantic-parent.is-inlined-function? false ) ) ( kind = ClassDecl spelling = "Inner" location = ./tests/headers/issue-798-nested-template.hpp:11:17 is-definition? true is-declaration? true is-inlined-function? false usr = "c:@ST>1#T@Outer@S@Inner" canonical.kind = ClassDecl canonical.spelling = "Inner" canonical.location = ./tests/headers/issue-798-nested-template.hpp:6:11 canonical.is-definition? false canonical.is-declaration? true canonical.is-inlined-function? false canonical.usr = "c:@ST>1#T@Outer@S@Inner" canonical.semantic-parent.kind = ClassTemplate canonical.semantic-parent.spelling = "Outer" canonical.semantic-parent.location = ./tests/headers/issue-798-nested-template.hpp:4:7 canonical.semantic-parent.is-definition? true canonical.semantic-parent.is-declaration? true canonical.semantic-parent.is-inlined-function? false canonical.semantic-parent.template-kind = ClassDecl canonical.semantic-parent.usr = "c:@ST>1#T@Outer" canonical.semantic-parent.semantic-parent.kind = TranslationUnit canonical.semantic-parent.semantic-parent.spelling = "./tests/headers/issue-798-nested-template.hpp" canonical.semantic-parent.semantic-parent.location = builtin definitions canonical.semantic-parent.semantic-parent.is-definition? false canonical.semantic-parent.semantic-parent.is-declaration? false canonical.semantic-parent.semantic-parent.is-inlined-function? false semantic-parent.kind = ClassTemplate semantic-parent.spelling = "Outer" semantic-parent.location = ./tests/headers/issue-798-nested-template.hpp:4:7 semantic-parent.is-definition? true semantic-parent.is-declaration? true semantic-parent.is-inlined-function? false semantic-parent.template-kind = ClassDecl semantic-parent.usr = "c:@ST>1#T@Outer" semantic-parent.semantic-parent.kind = TranslationUnit semantic-parent.semantic-parent.spelling = "./tests/headers/issue-798-nested-template.hpp" semantic-parent.semantic-parent.location = builtin definitions semantic-parent.semantic-parent.is-definition? false semantic-parent.semantic-parent.is-declaration? false semantic-parent.semantic-parent.is-inlined-function? false type.kind = Record type.cconv = 100 type.spelling = "Inner" type.is-variadic? false ( kind = TemplateRef spelling = "Outer" location = ./tests/headers/issue-798-nested-template.hpp:11:7 is-definition? false is-declaration? false is-inlined-function? false referenced.kind = ClassTemplate referenced.spelling = "Outer" referenced.location = ./tests/headers/issue-798-nested-template.hpp:4:7 referenced.is-definition? true referenced.is-declaration? true referenced.is-inlined-function? false referenced.template-kind = ClassDecl referenced.usr = "c:@ST>1#T@Outer" referenced.semantic-parent.kind = TranslationUnit referenced.semantic-parent.spelling = "./tests/headers/issue-798-nested-template.hpp" referenced.semantic-parent.location = builtin definitions referenced.semantic-parent.is-definition? false referenced.semantic-parent.is-declaration? false referenced.semantic-parent.is-inlined-function? false type.kind = Invalid ) ( kind = TypeRef spelling = "U" location = ./tests/headers/issue-798-nested-template.hpp:11:13 is-definition? false is-declaration? false is-inlined-function? false referenced.kind = TemplateTypeParameter referenced.spelling = "U" referenced.location = ./tests/headers/issue-798-nested-template.hpp:10:20 referenced.is-definition? true referenced.is-declaration? true referenced.is-inlined-function? false referenced.usr = "c:issue-798-nested-template.hpp@173" referenced.semantic-parent.kind = TranslationUnit referenced.semantic-parent.spelling = "./tests/headers/issue-798-nested-template.hpp" referenced.semantic-parent.location = builtin definitions referenced.semantic-parent.is-definition? false referenced.semantic-parent.is-declaration? false referenced.semantic-parent.is-inlined-function? false type.kind = Unexposed type.cconv = 100 type.spelling = "U" type.is-variadic? false type.canonical.kind = Unexposed type.canonical.cconv = 100 type.canonical.spelling = "type-parameter-0-0" type.canonical.is-variadic? false ) ( kind = FieldDecl spelling = "inner_member" location = ./tests/headers/issue-798-nested-template.hpp:12:7 is-definition? true is-declaration? true is-inlined-function? false usr = "c:@ST>1#T@Outer@S@Inner@FI@inner_member" semantic-parent.kind = ClassDecl semantic-parent.spelling = "Inner" semantic-parent.location = ./tests/headers/issue-798-nested-template.hpp:11:17 semantic-parent.is-definition? true semantic-parent.is-declaration? true semantic-parent.is-inlined-function? false semantic-parent.usr = "c:@ST>1#T@Outer@S@Inner" semantic-parent.canonical.kind = ClassDecl semantic-parent.canonical.spelling = "Inner" semantic-parent.canonical.location = ./tests/headers/issue-798-nested-template.hpp:6:11 semantic-parent.canonical.is-definition? false semantic-parent.canonical.is-declaration? true semantic-parent.canonical.is-inlined-function? false semantic-parent.canonical.usr = "c:@ST>1#T@Outer@S@Inner" semantic-parent.canonical.semantic-parent.kind = ClassTemplate semantic-parent.canonical.semantic-parent.spelling = "Outer" semantic-parent.canonical.semantic-parent.location = ./tests/headers/issue-798-nested-template.hpp:4:7 semantic-parent.canonical.semantic-parent.is-definition? true semantic-parent.canonical.semantic-parent.is-declaration? true semantic-parent.canonical.semantic-parent.is-inlined-function? false semantic-parent.canonical.semantic-parent.template-kind = ClassDecl semantic-parent.canonical.semantic-parent.usr = "c:@ST>1#T@Outer" semantic-parent.canonical.semantic-parent.semantic-parent.kind = TranslationUnit semantic-parent.canonical.semantic-parent.semantic-parent.spelling = "./tests/headers/issue-798-nested-template.hpp" semantic-parent.canonical.semantic-parent.semantic-parent.location = builtin definitions semantic-parent.canonical.semantic-parent.semantic-parent.is-definition? false semantic-parent.canonical.semantic-parent.semantic-parent.is-declaration? false semantic-parent.canonical.semantic-parent.semantic-parent.is-inlined-function? false semantic-parent.semantic-parent.kind = ClassTemplate semantic-parent.semantic-parent.spelling = "Outer" semantic-parent.semantic-parent.location = ./tests/headers/issue-798-nested-template.hpp:4:7 semantic-parent.semantic-parent.is-definition? true semantic-parent.semantic-parent.is-declaration? true semantic-parent.semantic-parent.is-inlined-function? false semantic-parent.semantic-parent.template-kind = ClassDecl semantic-parent.semantic-parent.usr = "c:@ST>1#T@Outer" semantic-parent.semantic-parent.semantic-parent.kind = TranslationUnit semantic-parent.semantic-parent.semantic-parent.spelling = "./tests/headers/issue-798-nested-template.hpp" semantic-parent.semantic-parent.semantic-parent.location = builtin definitions semantic-parent.semantic-parent.semantic-parent.is-definition? false semantic-parent.semantic-parent.semantic-parent.is-declaration? false semantic-parent.semantic-parent.semantic-parent.is-inlined-function? false type.kind = Unexposed type.cconv = 100 type.spelling = "U" type.is-variadic? false type.canonical.kind = Unexposed type.canonical.cconv = 100 type.canonical.spelling = "type-parameter-0-0" type.canonical.is-variadic? false ( kind = TypeRef spelling = "U" location = ./tests/headers/issue-798-nested-template.hpp:12:5 is-definition? false is-declaration? false is-inlined-function? false referenced.kind = TemplateTypeParameter referenced.spelling = "U" referenced.location = ./tests/headers/issue-798-nested-template.hpp:10:20 referenced.is-definition? true referenced.is-declaration? true referenced.is-inlined-function? false referenced.usr = "c:issue-798-nested-template.hpp@173" referenced.semantic-parent.kind = TranslationUnit referenced.semantic-parent.spelling = "./tests/headers/issue-798-nested-template.hpp" referenced.semantic-parent.location = builtin definitions referenced.semantic-parent.is-definition? false referenced.semantic-parent.is-declaration? false referenced.semantic-parent.is-inlined-function? false type.kind = Unexposed type.cconv = 100 type.spelling = "U" type.is-variadic? false type.canonical.kind = Unexposed type.canonical.cconv = 100 type.canonical.spelling = "type-parameter-0-0" type.canonical.is-variadic? false ) ) ) ```
cbourjau commented 7 years ago

You guys do magic with bindgen, thats for sure! I'm afraid that I am out of my depth a bit...

fitzgen commented 7 years ago

I think the only way to determine that T == U would be if we used de Bruijn indexing for template arguments, which we should probably do sometime down the road (clang does this internally and you can see this in the canonical spelling of type parameters) but it is definitely not on my docket for the foreseeable future.

cbourjau commented 7 years ago

Ok, thanks a lot for taking the time to look into this, though! I think I can get by making a bunch of types opaque in my use case.

fitzgen commented 7 years ago

Ok, thanks a lot for taking the time to look into this, though!

No problem!

I think I can get by making a bunch of types opaque in my use case.

Yep, this is the reality of working with C++ in bindgen, unfortunately.

helloqirun commented 7 years ago

I have encountered a similar case.

template < typename T, T v > struct S
{
  static constexpr T value = v;
};
template < typename T, T v > constexpr T S < T, v >::value;
fitzgen commented 7 years ago

I have encountered a similar case.

Thanks for the additional test case; it's not 100% clear to me that this isn't a different bug. It involves non-type template parameters, which are not supported by bindgen at the moment. Ideally, we should avoid generating any code for S::value so that the rest of the bindings are still usable.