Skip to content

Bindgen Incorrectly Mangles C++ Struct/Class Destructor with MSVC ABI #1725

Open
@cwfitzgerald

Description

@cwfitzgerald

This is done on Windows 10. Linux appears to work as expected.

bindgen: 0.53.1
LLVM: 9.0.0 from installer

Correct mangling shown on godbolt. clang-cl isn't shown, but outputs the same, as is expected.

Input C/C++ Header

struct B{
    B();
    ~B();
};

// Bindgen incorrectly demangles this as ??_DB@@QEAAXXZ 
// real: ??1B@@QEAA@XZ
B::~B() {}

class C{
public:
    C();
    ~C();
};

// Bindgen incorrectly demangles this as ??_DC@@QEAAXXZ (same as struct)
// symbol: ??1C@@AEAA@XZ (note the A in AEAA)
C::~C() {}

Bindgen Invocation

$ bindgen file.hpp

Actual Results

Verbose Output

These look pretty important 😛

cargo:warning=couldn't execute `llvm-config --prefix` (error: The system cannot find the file specified. (os error 2))
cargo:warning=set the LLVM_CONFIG_PATH environment variable to a valid `llvm-config` executable
[2020-02-11T06:08:18Z ERROR bindgen::ir::item] Unhandled cursor kind 25: Cursor(~B kind: CXXDestructor, loc: .\file.hpp:8:4, usr: Some("c:@S@B@F@~B#"))
[2020-02-11T06:08:18Z ERROR bindgen::ir::item] Unhandled cursor kind 25: Cursor(~C kind: CXXDestructor, loc: .\file.hpp:18:4, usr: Some("c:@S@C@F@~C#"))

Bindgen Output

Comments my own.

/* automatically generated by rust-bindgen */

#[repr(C)]
#[derive(Debug)]
pub struct B {
    pub _address: u8,
}
#[test]
fn bindgen_test_layout_B() {
    assert_eq!(
        ::std::mem::size_of::<B>(),
        1usize,
        concat!("Size of: ", stringify!(B))
    );
    assert_eq!(
        ::std::mem::align_of::<B>(),
        1usize,
        concat!("Alignment of ", stringify!(B))
    );
}
extern "C" {
    #[link_name = "\u{1}??0B@@QEAA@XZ"]
    pub fn B_B(this: *mut B);
}
extern "C" {
    // incorrect, should be: ??1B@@QEAA@XZ
    #[link_name = "\u{1}??_DB@@QEAAXXZ"]
    pub fn B_B_destructor(this: *mut B);
}
impl B {
    #[inline]
    pub unsafe fn new() -> Self {
        let mut __bindgen_tmp = ::std::mem::MaybeUninit::uninit();
        B_B(__bindgen_tmp.as_mut_ptr());
        __bindgen_tmp.assume_init()
    }
    #[inline]
    pub unsafe fn destruct(&mut self) {
        B_B_destructor(self)
    }
}
#[repr(C)]
#[derive(Debug)]
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 = "\u{1}??0C@@QEAA@XZ"]
    pub fn C_C(this: *mut C);
}
extern "C" {
    // incorrect, should be ??1C@@AEAA@XZ
    #[link_name = "\u{1}??_DC@@QEAAXXZ"]
    pub fn C_C_destructor(this: *mut C);
}
impl C {
    #[inline]
    pub unsafe fn new() -> Self {
        let mut __bindgen_tmp = ::std::mem::MaybeUninit::uninit();
        C_C(__bindgen_tmp.as_mut_ptr());
        __bindgen_tmp.assume_init()
    }
    #[inline]
    pub unsafe fn destruct(&mut self) {
        C_C_destructor(self)
    }
}

Expected Results

As mentioned above, the destructors are getting mangled incorrectly on the MSVC abi. Specifically:

??1C@@AEAA@XZ gets mangled as ??_DC@@QEAAXXZ and
??1B@@QEAA@XZ gets mangled as ??_DB@@QEAAXXZ.

This gives a linker error that is quite confusing talking about a missing reference to a vbase destructor on a class with no parents nor virtuality.

Workaround

For those affected by the issue, if you post process the bindings with a regex, you can automatically filter these:

\?\?_D(.*?)XXZ -> ??1${1}@XZ

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions