diff options
Diffstat (limited to 'src')
-rwxr-xr-x | src/bin/bindgen.rs | 20 | ||||
-rw-r--r-- | src/ir/context.rs | 6 | ||||
-rw-r--r-- | src/ir/item.rs | 54 | ||||
-rwxr-xr-x | src/lib.rs | 65 | ||||
-rw-r--r-- | src/uses.rs | 102 |
5 files changed, 231 insertions, 16 deletions
diff --git a/src/bin/bindgen.rs b/src/bin/bindgen.rs index 2a7995e7..17385d85 100755 --- a/src/bin/bindgen.rs +++ b/src/bin/bindgen.rs @@ -84,6 +84,10 @@ Options: matching <regex>. Same behavior on emptyness than the type whitelisting. + --dummy-uses=<path> For testing purposes, generate a C/C++ file + containing dummy uses of all types defined in + the input header. + <clang-args> Options other than stated above are passed directly through to clang. "; @@ -182,6 +186,11 @@ fn parse_args_or_exit(args: Vec<String>) -> (BindgenOptions, Box<io::Write>) { "--use-msvc-mangling" => { options.msvc_mangling = true; } + "--dummy-uses" => { + let dummy_path = iter.next() + .expect("--dummy-uses expects a file path"); + options.dummy_uses = Some(dummy_path); + } other if source_file.is_none() => { source_file = Some(other.into()); } @@ -193,6 +202,12 @@ fn parse_args_or_exit(args: Vec<String>) -> (BindgenOptions, Box<io::Write>) { if let Some(source_file) = source_file.take() { options.clang_args.push(source_file); + options.input_header = options.clang_args.last().cloned(); + } else { + options.input_header = options.clang_args + .iter() + .find(|arg| arg.ends_with(".h") || arg.ends_with(".hpp")) + .cloned(); } let out = if let Some(ref path_name) = dest_file { @@ -244,9 +259,12 @@ pub fn main() { let (options, out) = parse_args_or_exit(bind_args); - let bindings = Bindings::generate(options, None) + let mut bindings = Bindings::generate(options, None) .expect("Unable to generate bindings"); + bindings.write_dummy_uses() + .expect("Unable to write dummy uses to file."); + bindings.write(out) .expect("Unable to write bindings to file."); } diff --git a/src/ir/context.rs b/src/ir/context.rs index 6c56eefe..beccc514 100644 --- a/src/ir/context.rs +++ b/src/ir/context.rs @@ -384,8 +384,10 @@ impl<'ctx> BindgenContext<'ctx> { // because we remove it before the end of this function. self.gen_ctx = Some(unsafe { mem::transmute(&ctx) }); - self.resolve_typerefs(); - self.process_replacements(); + if !self.collected_typerefs() { + self.resolve_typerefs(); + self.process_replacements(); + } let ret = cb(self); self.gen_ctx = None; diff --git a/src/ir/item.rs b/src/ir/item.rs index 4e893e35..690f4222 100644 --- a/src/ir/item.rs +++ b/src/ir/item.rs @@ -38,13 +38,44 @@ pub trait ItemCanonicalName { /// } /// ``` /// -/// For bar, the canonical path is `foo::BAR`, while the canonical name is just -/// `BAR`. +/// For bar, the canonical path is `vec!["foo", "BAR"]`, while the canonical +/// name is just `"BAR"`. pub trait ItemCanonicalPath { /// Get the canonical path for this item. fn canonical_path(&self, ctx: &BindgenContext) -> Vec<String>; } +/// A trait for iterating over an item and its parents and up its ancestor chain +/// up to (but not including) the implicit root module. +pub trait ItemAncestors { + /// Get an iterable over this item's ancestors. + fn ancestors<'a, 'b>(&self, ctx: &'a BindgenContext<'b>) -> ItemAncestorsIter<'a, 'b>; +} + +/// An iterator over an item and its ancestors. +pub struct ItemAncestorsIter<'a, 'b> + where 'b: 'a, +{ + item: ItemId, + ctx: &'a BindgenContext<'b>, +} + +impl<'a, 'b> Iterator for ItemAncestorsIter<'a, 'b> + where 'b: 'a, +{ + type Item = ItemId; + + fn next(&mut self) -> Option<Self::Item> { + let item = self.ctx.resolve_item(self.item); + if item.parent_id() == self.item { + None + } else { + self.item = item.parent_id(); + Some(item.id()) + } + } +} + /// A single identifier for an item. /// /// TODO: Build stronger abstractions on top of this, like TypeId(ItemId)? @@ -76,6 +107,25 @@ impl ItemCanonicalPath for ItemId { } } +impl ItemAncestors for ItemId { + fn ancestors<'a, 'b>(&self, + ctx: &'a BindgenContext<'b>) + -> ItemAncestorsIter<'a, 'b> { + ItemAncestorsIter { + item: *self, + ctx: ctx, + } + } +} + +impl ItemAncestors for Item { + fn ancestors<'a, 'b>(&self, + ctx: &'a BindgenContext<'b>) + -> ItemAncestorsIter<'a, 'b> { + self.id().ancestors(ctx) + } +} + impl TypeCollector for ItemId { type Extra = (); @@ -60,6 +60,7 @@ mod clang; mod ir; mod parse; mod regex_set; +mod uses; #[cfg(rustfmt)] mod codegen; @@ -68,16 +69,17 @@ doc_mod!(clang, clang_docs); doc_mod!(ir, ir_docs); doc_mod!(parse, parse_docs); doc_mod!(regex_set, regex_set_docs); +doc_mod!(uses, uses_docs); mod codegen { include!(concat!(env!("OUT_DIR"), "/codegen.rs")); } - use ir::context::BindgenContext; use ir::item::{Item, ItemId}; use parse::{ClangItemParser, ParseError}; use regex_set::RegexSet; + use std::borrow::Borrow; use std::collections::HashSet; use std::fs::OpenOptions; @@ -118,10 +120,19 @@ pub fn builder() -> Builder { impl Builder { /// Set the input C/C++ header. - pub fn header<T: Into<String>>(self, header: T) -> Builder { + pub fn header<T: Into<String>>(mut self, header: T) -> Builder { + let header = header.into(); + self.options.input_header = Some(header.clone()); self.clang_arg(header) } + /// Generate a C/C++ file that includes the header and has dummy uses of + /// every type defined in the header. + pub fn dummy_uses<T: Into<String>>(mut self, dummy_uses: T) -> Builder { + self.options.dummy_uses = Some(dummy_uses.into()); + self + } + /// Hide the given type from the generated bindings. pub fn hide_type<T: Into<String>>(mut self, arg: T) -> Builder { self.options.hidden_types.insert(arg.into()); @@ -200,7 +211,7 @@ impl Builder { } /// Generate the Rust bindings using the options built up thus far. - pub fn generate(self) -> Result<Bindings, ()> { + pub fn generate<'ctx>(self) -> Result<Bindings<'ctx>, ()> { Bindings::generate(self.options, None) } } @@ -274,6 +285,13 @@ pub struct BindgenOptions { /// The set of arguments to pass straight through to Clang. pub clang_args: Vec<String>, + + /// The input header file. + pub input_header: Option<String>, + + /// Generate a dummy C/C++ file that includes the header and has dummy uses + /// of all types defined therein. See the `uses` module for more. + pub dummy_uses: Option<String>, } impl Default for BindgenOptions { @@ -296,6 +314,8 @@ impl Default for BindgenOptions { msvc_mangling: false, raw_lines: vec![], clang_args: vec![], + input_header: None, + dummy_uses: None, } } } @@ -314,20 +334,20 @@ pub enum LinkType { } /// Generated Rust bindings. -#[derive(Debug, Clone)] -pub struct Bindings { +#[derive(Debug)] +pub struct Bindings<'ctx> { + context: BindgenContext<'ctx>, module: ast::Mod, - raw_lines: Vec<String>, } -impl Bindings { +impl<'ctx> Bindings<'ctx> { /// Generate bindings for the given options. /// /// Deprecated - use a `Builder` instead #[deprecated] pub fn generate(options: BindgenOptions, span: Option<Span>) - -> Result<Bindings, ()> { + -> Result<Bindings<'ctx>, ()> { let span = span.unwrap_or(DUMMY_SP); let mut context = BindgenContext::new(options); @@ -339,8 +359,8 @@ impl Bindings { }; Ok(Bindings { + context: context, module: module, - raw_lines: context.options().raw_lines.clone(), }) } @@ -376,11 +396,11 @@ impl Bindings { try!(writer.write("/* automatically generated by rust-bindgen */\n\n" .as_bytes())); - for line in self.raw_lines.iter() { + for line in self.context.options().raw_lines.iter() { try!(writer.write(line.as_bytes())); try!(writer.write("\n".as_bytes())); } - if !self.raw_lines.is_empty() { + if !self.context.options().raw_lines.is_empty() { try!(writer.write("\n".as_bytes())); } @@ -390,6 +410,29 @@ impl Bindings { try!(eof(&mut ps.s)); ps.s.out.flush() } + + /// Generate and write dummy uses of all the types we parsed, if we've been + /// requested to do so in the options. + /// + /// See the `uses` module for more information. + pub fn write_dummy_uses(&mut self) -> io::Result<()> { + let file = + if let Some(ref dummy_path) = self.context.options().dummy_uses { + Some(try!(OpenOptions::new() + .write(true) + .truncate(true) + .create(true) + .open(dummy_path))) + } else { + None + }; + + if let Some(file) = file { + try!(uses::generate_dummy_uses(&mut self.context, file)); + } + + Ok(()) + } } /// Determines whether the given cursor is in any of the files matched by the diff --git a/src/uses.rs b/src/uses.rs new file mode 100644 index 00000000..47f72da6 --- /dev/null +++ b/src/uses.rs @@ -0,0 +1,102 @@ +//! Take in our IR and output a C/C++ file with dummy uses of each IR type. +//! +//! Say that we had this C++ header, `header.hpp`: +//! +//! ```c++ +//! class Point { +//! int x; +//! int y; +//! } +//! +//! enum Bar { +//! THIS, +//! THAT, +//! OTHER +//! } +//! ``` +//! +//! If we generated dummy uses for this header, we would get a `.cpp` file like +//! this: +//! +//! ```c++ +//! #include "header.hpp" +//! +//! void dummy(Point*) {} +//! void dummy(Bar*) {} +//! ``` +//! +//! This is useful because we can compile this `.cpp` file into an object file, +//! and then compare its debugging information to the debugging information +//! generated for our Rust bindings. These two sets of debugging information had +//! better agree on the C/C++ types' physical layout, or else our bindings are +//! incorrect! +//! +//! "But you still haven't explained why we have to generate the dummy uses" you +//! complain. Well if the types are never used, then they are elided when the +//! C/C++ compiler generates debugging information. + +use ir::context::BindgenContext; +use ir::item::{Item, ItemAncestors, ItemCanonicalName}; +use std::io; + +// Like `canonical_path`, except we always take namespaces into account, ignore +// the generated names of anonymous items, and return a `String`. +// +// TODO: Would it be easier to try and demangle the USR? +fn namespaced_name(ctx: &BindgenContext, item: &Item) -> String { + let mut names: Vec<_> = item.ancestors(ctx) + .map(|id| ctx.resolve_item(id).canonical_name(ctx)) + .filter(|name| !name.starts_with("_bindgen_")) + .collect(); + names.reverse(); + names.join("::") +} + +/// Generate the dummy uses for all the items in the given context, and write +/// the dummy uses to `dest`. +pub fn generate_dummy_uses<W>(ctx: &mut BindgenContext, + mut dest: W) + -> io::Result<()> + where W: io::Write, +{ + ctx.gen(|ctx| { + let input_header = ctx.options() + .input_header + .as_ref() + .expect("Should not generate dummy uses without an input header"); + + try!(writeln!(dest, "/* automatically generated by rust-bindgen */")); + try!(writeln!(dest, "")); + try!(writeln!(dest, "#include \"{}\"", input_header)); + try!(writeln!(dest, "")); + + let type_items = ctx.whitelisted_items() + .map(|id| ctx.resolve_item(id)) + .filter(|item| { + // We only want type items. + if let Some(ty) = item.kind().as_type() { + // However, we don't want anonymous types, as we can't + // generate dummy uses for them. + ty.name().is_some() && + // Nor do we want builtin types or named template type + // arguments. Again, we can't generate dummy uses for + // these. + !ty.is_builtin_or_named() && + // And finally, we won't be creating any dummy + // specializations, so ignore template declarations and + // partial specializations. + item.applicable_template_args(ctx).is_empty() + } else { + false + } + }) + .map(|item| namespaced_name(ctx, item)) + .enumerate(); + + for (idx, name) in type_items { + try!(writeln!(dest, "void dummy{}({}*) {{ }}", idx, name)); + } + + Ok(()) + }) +} |