diff options
-rwxr-xr-x | src/bin/bindgen.rs | 20 | ||||
-rwxr-xr-x | src/clang.rs | 239 | ||||
-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 | ||||
-rw-r--r-- | tests/expectations/base-to-derived.rs | 19 | ||||
-rw-r--r-- | tests/headers/base-to-derived.hpp | 19 | ||||
-rw-r--r-- | tests/tests.rs | 80 | ||||
-rwxr-xr-x | tests/tools/run-bindgen.py | 14 | ||||
-rw-r--r-- | tests/uses/.gitignore | 2 |
11 files changed, 342 insertions, 278 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/clang.rs b/src/clang.rs index af194242..57c42eaf 100755 --- a/src/clang.rs +++ b/src/clang.rs @@ -123,6 +123,9 @@ impl Cursor { /// Return the number of template arguments used by this cursor's referent, /// if the referent is either a template specialization or /// declaration. Returns -1 otherwise. + /// + /// NOTE: This may not return `Some` for some non-fully specialized + /// templates, see #193 and #194. pub fn num_template_args(&self) -> Option<u32> { let n: c_int = unsafe { clang_Cursor_getNumTemplateArguments(self.x) }; @@ -185,7 +188,7 @@ impl Cursor { /// Is the referent a fully specialized template specialization without any /// remaining free template arguments? pub fn is_fully_specialized_template(&self) -> bool { - self.is_template() && self.num_template_args().unwrap() > 0 + self.is_template() && self.num_template_args().unwrap_or(0) > 0 } /// Is the referent a template specialization that still has remaining free @@ -1142,239 +1145,13 @@ impl UnsavedFile { } /// Convert a cursor kind into a static string. -pub fn kind_to_str(x: Enum_CXCursorKind) -> &'static str { - match x { - CXCursor_UnexposedDecl => "UnexposedDecl", - CXCursor_StructDecl => "StructDecl", - CXCursor_UnionDecl => "UnionDecl", - CXCursor_ClassDecl => "ClassDecl", - CXCursor_EnumDecl => "EnumDecl", - CXCursor_FieldDecl => "FieldDecl", - CXCursor_EnumConstantDecl => "EnumConstantDecl", - CXCursor_FunctionDecl => "FunctionDecl", - CXCursor_VarDecl => "VarDecl", - CXCursor_ParmDecl => "ParmDecl", - CXCursor_ObjCInterfaceDecl => "ObjCInterfaceDecl", - CXCursor_ObjCCategoryDecl => "ObjCCategoryDecl", - CXCursor_ObjCProtocolDecl => "ObjCProtocolDecl", - CXCursor_ObjCPropertyDecl => "ObjCPropertyDecl", - CXCursor_ObjCIvarDecl => "ObjCIvarDecl", - CXCursor_ObjCInstanceMethodDecl => "ObjCInstanceMethodDecl", - CXCursor_ObjCClassMethodDecl => "ObjCClassMethodDecl", - CXCursor_ObjCImplementationDecl => "ObjCImplementationDecl", - CXCursor_ObjCCategoryImplDecl => "ObjCCategoryImplDecl", - CXCursor_TypedefDecl => "TypedefDecl", - CXCursor_CXXMethod => "CXXMethod", - CXCursor_Namespace => "Namespace", - CXCursor_LinkageSpec => "LinkageSpec", - CXCursor_Constructor => "Constructor", - CXCursor_Destructor => "Destructor", - CXCursor_ConversionFunction => "ConversionFunction", - CXCursor_TemplateTypeParameter => "TemplateTypeParameter", - CXCursor_NonTypeTemplateParameter => "NonTypeTemplateParameter", - CXCursor_TemplateTemplateParameter => "TemplateTemplateParameter", - CXCursor_FunctionTemplate => "FunctionTemplate", - CXCursor_ClassTemplate => "ClassTemplate", - CXCursor_ClassTemplatePartialSpecialization => { - // FIXME: Ugly hack for rustfmt, should go away! - // - // I plan to convert this into an enum right away anyway, though. - return "ClassTemplatePartialSpecialization"; - } - CXCursor_NamespaceAlias => "NamespaceAlias", - CXCursor_UsingDirective => "UsingDirective", - CXCursor_UsingDeclaration => "UsingDeclaration", - CXCursor_TypeAliasDecl => "TypeAliasDecl", - CXCursor_ObjCSynthesizeDecl => "ObjCSynthesizeDecl", - CXCursor_ObjCDynamicDecl => "ObjCDynamicDecl", - CXCursor_CXXAccessSpecifier => "CXXAccessSpecifier", - // CXCursor_FirstDecl => "FirstDecl", - // CXCursor_LastDecl => "LastDecl", - CXCursor_FirstRef => "FirstRef", - // CXCursor_ObjCSuperClassRef => "ObjCSuperClassRef", - CXCursor_ObjCProtocolRef => "ObjCProtocolRef", - CXCursor_ObjCClassRef => "ObjCClassRef", - CXCursor_TypeRef => "TypeRef", - CXCursor_CXXBaseSpecifier => "CXXBaseSpecifier", - CXCursor_TemplateRef => "TemplateRef", - CXCursor_NamespaceRef => "NamespaceRef", - CXCursor_MemberRef => "MemberRef", - // CXCursor_LabelRef => "LabelRef", - CXCursor_OverloadedDeclRef => "OverloadedDeclRef", - CXCursor_VariableRef => "VariableRef", - // CXCursor_LastRef => "LastRef", - CXCursor_FirstInvalid => "FirstInvalid", - // CXCursor_InvalidFile => "InvalidFile", - CXCursor_NoDeclFound => "NoDeclFound", - CXCursor_NotImplemented => "NotImplemented", - CXCursor_InvalidCode => "InvalidCode", - // CXCursor_LastInvalid => "LastInvalid", - CXCursor_FirstExpr => "FirstExpr", - // CXCursor_UnexposedExpr => "UnexposedExpr", - CXCursor_DeclRefExpr => "DeclRefExpr", - CXCursor_MemberRefExpr => "MemberRefExpr", - CXCursor_CallExpr => "CallExpr", - CXCursor_ObjCMessageExpr => "ObjCMessageExpr", - CXCursor_BlockExpr => "BlockExpr", - CXCursor_IntegerLiteral => "IntegerLiteral", - CXCursor_FloatingLiteral => "FloatingLiteral", - CXCursor_ImaginaryLiteral => "ImaginaryLiteral", - CXCursor_StringLiteral => "StringLiteral", - CXCursor_CharacterLiteral => "CharacterLiteral", - CXCursor_ParenExpr => "ParenExpr", - CXCursor_UnaryOperator => "UnaryOperator", - CXCursor_ArraySubscriptExpr => "ArraySubscriptExpr", - CXCursor_BinaryOperator => "BinaryOperator", - CXCursor_CompoundAssignOperator => "CompoundAssignOperator", - CXCursor_ConditionalOperator => "ConditionalOperator", - CXCursor_CStyleCastExpr => "CStyleCastExpr", - CXCursor_CompoundLiteralExpr => "CompoundLiteralExpr", - CXCursor_InitListExpr => "InitListExpr", - CXCursor_AddrLabelExpr => "AddrLabelExpr", - CXCursor_StmtExpr => "StmtExpr", - CXCursor_GenericSelectionExpr => "GenericSelectionExpr", - CXCursor_GNUNullExpr => "GNUNullExpr", - CXCursor_CXXStaticCastExpr => "CXXStaticCastExpr", - CXCursor_CXXDynamicCastExpr => "CXXDynamicCastExpr", - CXCursor_CXXReinterpretCastExpr => "CXXReinterpretCastExpr", - CXCursor_CXXConstCastExpr => "CXXConstCastExpr", - CXCursor_CXXFunctionalCastExpr => "CXXFunctionalCastExpr", - CXCursor_CXXTypeidExpr => "CXXTypeidExpr", - CXCursor_CXXBoolLiteralExpr => "CXXBoolLiteralExpr", - CXCursor_CXXNullPtrLiteralExpr => "CXXNullPtrLiteralExpr", - CXCursor_CXXThisExpr => "CXXThisExpr", - CXCursor_CXXThrowExpr => "CXXThrowExpr", - CXCursor_CXXNewExpr => "CXXNewExpr", - CXCursor_CXXDeleteExpr => "CXXDeleteExpr", - CXCursor_UnaryExpr => "UnaryExpr", - CXCursor_ObjCStringLiteral => "ObjCStringLiteral", - CXCursor_ObjCEncodeExpr => "ObjCEncodeExpr", - CXCursor_ObjCSelectorExpr => "ObjCSelectorExpr", - CXCursor_ObjCProtocolExpr => "ObjCProtocolExpr", - CXCursor_ObjCBridgedCastExpr => "ObjCBridgedCastExpr", - CXCursor_PackExpansionExpr => "PackExpansionExpr", - CXCursor_SizeOfPackExpr => "SizeOfPackExpr", - CXCursor_LambdaExpr => "LambdaExpr", - CXCursor_ObjCBoolLiteralExpr => "ObjCBoolLiteralExpr", - // CXCursor_LastExpr => "LastExpr", - CXCursor_FirstStmt => "FirstStmt", - // CXCursor_UnexposedStmt => "UnexposedStmt", - CXCursor_LabelStmt => "LabelStmt", - CXCursor_CompoundStmt => "CompoundStmt", - CXCursor_CaseStmt => "CaseStmt", - CXCursor_DefaultStmt => "DefaultStmt", - CXCursor_IfStmt => "IfStmt", - CXCursor_SwitchStmt => "SwitchStmt", - CXCursor_WhileStmt => "WhileStmt", - CXCursor_DoStmt => "DoStmt", - CXCursor_ForStmt => "ForStmt", - CXCursor_GotoStmt => "GotoStmt", - CXCursor_IndirectGotoStmt => "IndirectGotoStmt", - CXCursor_ContinueStmt => "ContinueStmt", - CXCursor_BreakStmt => "BreakStmt", - CXCursor_ReturnStmt => "ReturnStmt", - CXCursor_AsmStmt => "AsmStmt", - CXCursor_ObjCAtTryStmt => "ObjCAtTryStmt", - CXCursor_ObjCAtCatchStmt => "ObjCAtCatchStmt", - CXCursor_ObjCAtFinallyStmt => "ObjCAtFinallyStmt", - CXCursor_ObjCAtThrowStmt => "ObjCAtThrowStmt", - CXCursor_ObjCAtSynchronizedStmt => "ObjCAtSynchronizedStmt", - CXCursor_ObjCAutoreleasePoolStmt => "ObjCAutoreleasePoolStmt", - CXCursor_ObjCForCollectionStmt => "ObjCForCollectionStmt", - CXCursor_CXXCatchStmt => "CXXCatchStmt", - CXCursor_CXXTryStmt => "CXXTryStmt", - CXCursor_CXXForRangeStmt => "CXXForRangeStmt", - CXCursor_SEHTryStmt => "SEHTryStmt", - CXCursor_SEHExceptStmt => "SEHExceptStmt", - CXCursor_SEHFinallyStmt => "SEHFinallyStmt", - CXCursor_NullStmt => "NullStmt", - CXCursor_DeclStmt => "DeclStmt", - // CXCursor_LastStmt => "LastStmt", - CXCursor_TranslationUnit => "TranslationUnit", - CXCursor_FirstAttr => "FirstAttr", - // CXCursor_UnexposedAttr => "UnexposedAttr", - CXCursor_IBActionAttr => "IBActionAttr", - CXCursor_IBOutletAttr => "IBOutletAttr", - CXCursor_IBOutletCollectionAttr => "IBOutletCollectionAttr", - CXCursor_CXXFinalAttr => "CXXFinalAttr", - CXCursor_CXXOverrideAttr => "CXXOverrideAttr", - CXCursor_AnnotateAttr => "AnnotateAttr", - CXCursor_AsmLabelAttr => "AsmLabelAttr", - // CXCursor_LastAttr => "LastAttr", - CXCursor_PreprocessingDirective => "PreprocessingDirective", - CXCursor_MacroDefinition => "MacroDefinition", - CXCursor_MacroExpansion => "MacroExpansion", - // CXCursor_MacroInstantiation => "MacroInstantiation", - CXCursor_InclusionDirective => "InclusionDirective", - // CXCursor_FirstPreprocessing => "FirstPreprocessing", - // CXCursor_LastPreprocessing => "LastPreprocessing", - CXCursor_PackedAttr => "PackedAttr", - CXCursor_ModuleImportDecl => "ModuleImportDecl", - CXCursor_TypeAliasTemplateDecl => "TypeAliasTemplateDecl", - CXCursor_StaticAssert => "StaticAssert", - _ => "?", - } +pub fn kind_to_str(x: Enum_CXCursorKind) -> String { + unsafe { clang_getCursorKindSpelling(x) }.into() } /// Convert a type kind to a static string. -pub fn type_to_str(x: Enum_CXTypeKind) -> &'static str { - match x { - CXType_Invalid => "Invalid", - CXType_Unexposed => "Unexposed", - CXType_Void => "Void", - CXType_Bool => "Bool", - CXType_Char_U => "Char_U", - CXType_UChar => "UChar", - CXType_Char16 => "Char16", - CXType_Char32 => "Char32", - CXType_UShort => "UShort", - CXType_UInt => "UInt", - CXType_ULong => "ULong", - CXType_ULongLong => "ULongLong", - CXType_UInt128 => "UInt128", - CXType_Char_S => "Char_S", - CXType_SChar => "SChar", - CXType_WChar => "WChar", - CXType_Short => "Short", - CXType_Int => "Int", - CXType_Long => "Long", - CXType_LongLong => "LongLong", - CXType_Int128 => "Int128", - CXType_Float => "Float", - CXType_Double => "Double", - CXType_LongDouble => "LongDouble", - CXType_NullPtr => "NullPtr", - CXType_Overload => "Overload", - CXType_Dependent => "Dependent", - CXType_ObjCId => "ObjCId", - CXType_ObjCClass => "ObjCClass", - CXType_ObjCSel => "ObjCSel", - // CXType_FirstBuiltin => "FirstBuiltin", - // CXType_LastBuiltin => "LastBuiltin", - CXType_Complex => "Complex", - CXType_Pointer => "Pointer", - CXType_BlockPointer => "BlockPointer", - CXType_LValueReference => "LValueReference", - CXType_RValueReference => "RValueReference", - CXType_Record => "Record", - CXType_Enum => "Enum", - CXType_Typedef => "Typedef", - CXType_ObjCInterface => "ObjCInterface", - CXType_ObjCObjectPointer => "ObjCObjectPointer", - CXType_FunctionNoProto => "FunctionNoProto", - CXType_FunctionProto => "FunctionProto", - CXType_ConstantArray => "ConstantArray", - CXType_Vector => "Vector", - CXType_IncompleteArray => "IncompleteArray", - CXType_VariableArray => "VariableArray", - CXType_DependentSizedArray => "DependentSizedArray", - CXType_MemberPointer => "MemberPointer", - #[cfg(not(feature="llvm_stable"))] - CXType_Auto => "Auto", - #[cfg(not(feature="llvm_stable"))] - CXType_Elaborated => "Elaborated", - _ => "?", - } +pub fn type_to_str(x: Enum_CXTypeKind) -> String { + unsafe { clang_getTypeKindSpelling(x).into() } } /// Dump the Clang AST to stdout for debugging purposes. diff --git a/src/ir/context.rs b/src/ir/context.rs index b7e96964..c2214340 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 c58fa81f..0c12b9a9 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(()) + }) +} diff --git a/tests/expectations/base-to-derived.rs b/tests/expectations/base-to-derived.rs new file mode 100644 index 00000000..c2af2c43 --- /dev/null +++ b/tests/expectations/base-to-derived.rs @@ -0,0 +1,19 @@ +/* automatically generated by rust-bindgen */ + + +#![allow(non_snake_case)] + + +#[repr(C)] +#[derive(Debug, Copy)] +pub struct false_type { + pub _address: u8, +} +#[test] +fn bindgen_test_layout_false_type() { + assert_eq!(::std::mem::size_of::<false_type>() , 1usize); + assert_eq!(::std::mem::align_of::<false_type>() , 1usize); +} +impl Clone for false_type { + fn clone(&self) -> Self { *self } +} diff --git a/tests/headers/base-to-derived.hpp b/tests/headers/base-to-derived.hpp new file mode 100644 index 00000000..ea31e0f2 --- /dev/null +++ b/tests/headers/base-to-derived.hpp @@ -0,0 +1,19 @@ +// bindgen-flags: -- -std=c++11 + +struct false_type {}; + +template<typename _From, typename _To, bool> +struct __is_base_to_derived_ref; + +template<typename _From, typename _To> +struct __is_base_to_derived_ref<_From, _To, true> +{ + typedef _To type; + + static constexpr bool value = type::value; +}; + +template<typename _From, typename _To> +struct __is_base_to_derived_ref<_From, _To, false> +: public false_type +{ }; 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 |