Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

Large diffs are not rendered by default.

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

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

9 changes: 9 additions & 0 deletions bindgen-tests/tests/headers/bitfield-accessor-name-conflict.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/// Bitfield accessor name conflicts:
/// - `set_x` getter collides with `x` setter
/// - `set_x_bindgen_bitfield` tests the collision chain (the suffix
/// used for deduplication itself collides with a real field name)
struct test {
char set_x: 1;
char x: 1;
char set_x_bindgen_bitfield: 1;
};
125 changes: 125 additions & 0 deletions bindgen-tests/tests/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -563,6 +563,131 @@ fn test_mixed_header_and_header_contents() {
}
}

#[test]
fn test_bitfield_accessor_name_conflict_torture() {
// This deliberately mixes first-order accessor collisions, repeated
// suffix chains, raw-name collisions, reversed declaration order,
// and C++ methods whose names only collide after suffixing.
let header = r"
struct BasicPair {
char set_x : 1;
char x : 1;
};

struct GetterSetterChain {
char x : 1;
char set_x : 1;
char set_x_bindgen_bitfield : 1;
};

struct RawGetterSetterChain {
char x : 1;
char set_x_raw : 1;
char set_x_raw_bindgen_bitfield : 1;
};

struct ReverseGetterSetterChain {
char set_x_bindgen_bitfield : 1;
char set_x : 1;
char x : 1;
};

struct ReverseRawGetterSetterChain {
char set_x_raw_bindgen_bitfield : 1;
char set_x_raw : 1;
char x : 1;
};

struct MethodAfterAccessorCollision {
char x : 1;
char set_x : 1;
void set_x_bindgen_bitfield();
void set_set_x_bindgen_bitfield(char c);
};

struct MethodAfterRawCollision {
char x : 1;
char set_x_raw : 1;
void set_x_raw_bindgen_bitfield();
void set_set_x_raw_bindgen_bitfield(char c);
};

struct MethodCollisionChain {
char type_ : 1;
char set_type : 1;
char set_type_bindgen_bitfield : 1;
char type();
void set_type_(char c);
void set_set_type_bindgen_bitfield(char c);
};

struct OverloadedMethodConflict {
char foo1 : 1;
char bar : 1;
void foo();
void foo(int);
};

struct DtorConflict {
char destruct : 1;
~DtorConflict();
};

struct DtorShiftConflict {
char destruct1 : 1;
void destruct();
~DtorShiftConflict();
};

struct CtorConflict {
char new_ : 1;
CtorConflict();
CtorConflict(int);
};

struct CtorShiftConflict {
char new1 : 1;
CtorShiftConflict();
CtorShiftConflict(int);
};
";

let bindings = builder()
.disable_header_comment()
.header_contents("bitfield_accessor_name_conflict_torture.hpp", header)
.clang_arg("-xc++")
.clang_arg("--target=x86_64-unknown-linux")
.generate()
.unwrap()
.to_string();

let tmpdir = tempfile::tempdir().unwrap();
let source = tmpdir.path().join("bindings.rs");
let output = tmpdir.path().join("libbindings.rlib");
fs::write(&source, &bindings).unwrap();

let rustc = env::var_os("RUSTC")
.unwrap_or_else(|| std::ffi::OsString::from("rustc"));
// Duplicate accessors still parse as Rust, so compile the generated
// bindings instead of only formatting/parsing them.
let compile = std::process::Command::new(rustc)
.arg("--crate-type=lib")
.arg("--edition=2021")
.arg(&source)
.arg("-o")
.arg(&output)
.output()
.unwrap();

assert!(
compile.status.success(),
"Generated bindings did not compile.\nstdout:\n{}\nstderr:\n{}\nbindings:\n{}",
String::from_utf8_lossy(&compile.stdout),
String::from_utf8_lossy(&compile.stderr),
bindings,
);
}

#[test]
fn test_macro_fallback_non_system_dir() {
let actual = builder()
Expand Down
27 changes: 26 additions & 1 deletion bindgen/codegen/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2783,7 +2783,26 @@ impl CodeGenerator for CompInfo {
}
}

let mut method_names = Default::default();
// Seed method_names with bitfield accessor names so C++
// methods, constructors, and destructors that collide with
// them get renamed instead of producing duplicates.
let mut method_names: HashSet<String> = self
.fields()
.iter()
.filter_map(|f| match f {
Field::Bitfields(ref bu) => Some(bu.bitfields().iter()),
Field::DataMember(_) => None,
})
.flatten()
.filter(|bf| bf.name().is_some())
.flat_map(|bf| {
let g = bf.getter_name().to_owned();
let s = bf.setter_name().to_owned();
let gr = format!("{g}_raw");
let sr = format!("{s}_raw");
[g, s, gr, sr]
})
.collect();
let discovered_id = DiscoveredItemId::new(item.id().as_usize());
if ctx.options().codegen_config.methods() {
for method in self.methods() {
Expand Down Expand Up @@ -3126,6 +3145,12 @@ impl Method {
return;
}

// Mangle the name before dedup so we compare the actual Rust
// identifier (e.g. C++ `type` → Rust `type_`) against
// method_names, which contains bitfield accessor names that
// are already in their Rust-mangled form.
name = ctx.rust_mangle(&name).to_string();

if method_names.contains(&name) {
let mut count = 1;
let mut new_name;
Expand Down
Loading
Loading