summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorbors-servo <lbergstrom+bors@mozilla.com>2016-11-02 17:09:20 -0500
committerGitHub <noreply@github.com>2016-11-02 17:09:20 -0500
commit33534e576003aec13d0d7f59d75c2980aae35273 (patch)
treeda65404086e37b8f340347acda93523a71ad6865
parent6b303852f4a923e809c688dc5ab86c466f2901ae (diff)
parentcd2754c4c75501a6b2f1f0e2a96c70dde39d3433 (diff)
Auto merge of #192 - fitzgen:use-debug-info-in-tests, r=emilio
Groundwork/infrastructure for asserting debug info in tests This adds the ability to generate dummy C/C++ uses of the whitelisted types in an input header, and extends the testing infrastructure to generate these uses. This is the first part of #151. The second part (which I am holding off on for the moment because I want to focus on regenerating SpiderMonkey bindings) would be to compile these dummy uses into object files, and then comparing their DWARF debug info to the DWARF debug info in our compiled Rust bindings and asserting that size and alignment matches up to each other. r? @emilio
-rwxr-xr-xsrc/bin/bindgen.rs20
-rw-r--r--src/ir/context.rs6
-rw-r--r--src/ir/item.rs54
-rwxr-xr-xsrc/lib.rs65
-rw-r--r--src/uses.rs102
-rw-r--r--tests/tests.rs80
-rwxr-xr-xtests/tools/run-bindgen.py14
-rw-r--r--tests/uses/.gitignore2
8 files changed, 296 insertions, 47 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 = ();
diff --git a/src/lib.rs b/src/lib.rs
index 1592c275..1e840246 100755
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -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(())
+ })
+}
diff --git a/tests/tests.rs b/tests/tests.rs
index addaa5ad..4954ac6f 100644
--- a/tests/tests.rs
+++ b/tests/tests.rs
@@ -12,10 +12,13 @@ use std::process;
const TEST_BATCH_DEFAULT_SIZE: usize = 16;
-fn spawn_run_bindgen<P, Q, R>(run_bindgen: P, bindgen: Q, header: R) -> process::Child
+fn spawn_run_bindgen<P, Q, R>(run_bindgen: P,
+ bindgen: Q,
+ header: R)
+ -> process::Child
where P: AsRef<Path>,
Q: AsRef<Path>,
- R: AsRef<Path>
+ R: AsRef<Path>,
{
let run_bindgen = run_bindgen.as_ref();
let bindgen = bindgen.as_ref();
@@ -36,19 +39,34 @@ fn spawn_run_bindgen<P, Q, R>(run_bindgen: P, bindgen: Q, header: R) -> process:
expected.push(file_name);
expected.set_extension("rs");
- let mut cmd = process::Command::new(run_bindgen);
- cmd.stdout(process::Stdio::piped())
+ // And the same style conversion as above, but for the dummy uses. We assume
+ // that .hpp means we should generate a .cpp uses file, and .h means we
+ // should generate a .c file.
+
+ let mut dummy_uses = PathBuf::from(header);
+ let file_name = dummy_uses.file_name()
+ .expect("Should still have filename")
+ .to_os_string();
+ dummy_uses.pop();
+ dummy_uses.pop();
+ dummy_uses.push("uses");
+ dummy_uses.push(file_name);
+ dummy_uses.set_extension(if header.extension().and_then(|s| s.to_str()) ==
+ Some("hpp") {
+ "cpp"
+ } else {
+ "c"
+ });
+
+ process::Command::new(run_bindgen)
+ .stdout(process::Stdio::piped())
.stderr(process::Stdio::piped())
.arg(bindgen)
.arg(header)
- .arg(expected);
-
- if cfg!(feature = "llvm_stable") {
- cmd.arg("--feature")
- .arg("llvm_stable");
- }
-
- cmd.spawn()
+ .arg(expected)
+ .arg("--dummy-uses")
+ .arg(dummy_uses)
+ .spawn()
.expect("Should be able to spawn run-bindgen.py child process")
}
@@ -84,12 +102,12 @@ fn run_bindgen_tests() {
.map(|result| result.expect("Should read directory entry"));
let tests = entries.filter(|entry| {
- match entry.path().extension().map(|s| s.to_str()) {
- Some(Some("h")) |
- Some(Some("hpp")) => true,
- _ => false,
- }
- }).collect::<Vec<_>>();
+ match entry.path().extension().and_then(|s| s.to_str()) {
+ Some("h") | Some("hpp") => true,
+ _ => false,
+ }
+ })
+ .collect::<Vec<_>>();
let batch_size = env::var("BINDGEN_TEST_BATCH_SIZE")
.ok()
@@ -101,22 +119,26 @@ fn run_bindgen_tests() {
// consumed when testing, so that we don't overload the system.
let children = tests.chunks(batch_size).map(|x| {
- x.iter().map(|entry| {
- let child = spawn_run_bindgen(run_bindgen.clone(), bindgen.clone(), entry.path());
- (entry.path(), child)
- }).collect::<Vec<_>>()
+ x.iter()
+ .map(|entry| {
+ let child = spawn_run_bindgen(run_bindgen.clone(),
+ bindgen.clone(),
+ entry.path());
+ (entry.path(), child)
+ })
+ .collect::<Vec<_>>()
});
let failures: Vec<_> = children.flat_map(|x| {
- x.into_iter().filter_map(|(path, mut child)| {
- let passed = child.wait()
- .expect("Should wait on child process")
- .success();
+ x.into_iter().filter_map(|(path, mut child)| {
+ let passed = child.wait()
+ .expect("Should wait on child process")
+ .success();
- if passed { None } else { Some((path, child)) }
+ if passed { None } else { Some((path, child)) }
+ })
})
- })
- .collect();
+ .collect();
let num_failures = failures.len();
diff --git a/tests/tools/run-bindgen.py b/tests/tools/run-bindgen.py
index bc8b567b..1f5f504e 100755
--- a/tests/tools/run-bindgen.py
+++ b/tests/tools/run-bindgen.py
@@ -41,6 +41,10 @@ def make_parser():
nargs=1,
help="Run tests that depend on bindgen being built with \
the given feature.")
+ parser.add_argument("--dummy-uses",
+ dest="dummy_uses",
+ help="The path to generate dummy C/C++ uses of the \
+ whitelisted types from the input header at.")
return parser
def usage_and_exit(*args):
@@ -117,9 +121,11 @@ def run_cmd(command, **kwargs):
print("run-bindgen.py: running", command)
subprocess.check_call(command, **kwargs)
-def generate_bindings(bindgen, flags, header, output):
+def generate_bindings(bindgen, dummy_uses, flags, header, output):
"""Generate the rust bindings."""
command = [bindgen, "-o", output]
+ if dummy_uses:
+ command.extend(["--dummy-uses", dummy_uses])
command.extend(flags)
command.append(header)
run_cmd(command, cwd=os.getcwd(), env=make_bindgen_env())
@@ -166,7 +172,11 @@ def main():
test_flags = get_bindgen_flags(args.header)
expected_bindings = get_expected_bindings(args.rust_bindings)
- generate_bindings(args.bindgen, test_flags, args.header, args.rust_bindings)
+ generate_bindings(args.bindgen,
+ args.dummy_uses,
+ test_flags,
+ args.header,
+ args.rust_bindings)
test_generated_bindings(args.rust_bindings)
check_actual_vs_expected(expected_bindings, args.rust_bindings)
sys.exit(0)
diff --git a/tests/uses/.gitignore b/tests/uses/.gitignore
new file mode 100644
index 00000000..40d7cb4c
--- /dev/null
+++ b/tests/uses/.gitignore
@@ -0,0 +1,2 @@
+*.c
+*.cpp