blob: 0818aa52f020a409ccdaa92932f9723d6c9779c3 [file] [log] [blame]
// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
use indexmap::map::IndexMap as HashMap;
use indexmap::set::IndexSet as HashSet;
use crate::{
conversion::{
api::{Api, ApiName, NullPhase, StructDetails, SubclassName, TypedefKind, UnanalyzedApi},
apivec::ApiVec,
ConvertError,
},
types::Namespace,
types::QualifiedName,
};
use crate::{
conversion::{
convert_error::{ConvertErrorWithContext, ErrorContext},
error_reporter::report_any_error,
},
types::validate_ident_ok_for_cxx,
};
use autocxx_parser::{IncludeCppConfig, RustPath};
use syn::{parse_quote, Fields, Ident, Item, Type, TypePath, UseTree};
use super::{
super::utilities::generate_utilities, bindgen_semantic_attributes::BindgenSemanticAttributes,
};
use super::parse_foreign_mod::ParseForeignMod;
/// Parses a bindgen mod in order to understand the APIs within it.
pub(crate) struct ParseBindgen<'a> {
config: &'a IncludeCppConfig,
apis: ApiVec<NullPhase>,
}
fn api_name(ns: &Namespace, id: Ident, attrs: &BindgenSemanticAttributes) -> ApiName {
ApiName::new_with_cpp_name(ns, id, attrs.get_original_name())
}
pub(crate) fn api_name_qualified(
ns: &Namespace,
id: Ident,
attrs: &BindgenSemanticAttributes,
) -> Result<ApiName, ConvertErrorWithContext> {
match validate_ident_ok_for_cxx(&id.to_string()) {
Err(e) => {
let ctx = ErrorContext::new_for_item(id);
Err(ConvertErrorWithContext(e, Some(ctx)))
}
Ok(..) => Ok(api_name(ns, id, attrs)),
}
}
impl<'a> ParseBindgen<'a> {
pub(crate) fn new(config: &'a IncludeCppConfig) -> Self {
ParseBindgen {
config,
apis: ApiVec::new(),
}
}
/// Parses items found in the `bindgen` output and returns a set of
/// `Api`s together with some other data.
pub(crate) fn parse_items(
mut self,
items: Vec<Item>,
) -> Result<ApiVec<NullPhase>, ConvertError> {
let items = Self::find_items_in_root(items)?;
if !self.config.exclude_utilities() {
generate_utilities(&mut self.apis, self.config);
}
self.add_apis_from_config();
let root_ns = Namespace::new();
self.parse_mod_items(items, root_ns);
self.confirm_all_generate_directives_obeyed()?;
self.replace_extern_cpp_types();
Ok(self.apis)
}
/// Some API items are not populated from bindgen output, but instead
/// directly from items in the config.
fn add_apis_from_config(&mut self) {
self.apis
.extend(self.config.subclasses.iter().map(|sc| Api::Subclass {
name: SubclassName::new(sc.subclass.clone()),
superclass: QualifiedName::new_from_cpp_name(&sc.superclass),
}));
self.apis
.extend(self.config.extern_rust_funs.iter().map(|fun| {
let id = fun.sig.ident.clone();
Api::RustFn {
name: ApiName::new_in_root_namespace(id),
details: fun.clone(),
receiver: fun.receiver.as_ref().map(|receiver_id| {
QualifiedName::new(&Namespace::new(), receiver_id.clone())
}),
}
}));
let unique_rust_types: HashSet<&RustPath> = self.config.rust_types.iter().collect();
self.apis.extend(unique_rust_types.into_iter().map(|path| {
let id = path.get_final_ident();
Api::RustType {
name: ApiName::new_in_root_namespace(id.clone()),
path: path.clone(),
}
}));
self.apis.extend(
self.config
.concretes
.0
.iter()
.map(|(cpp_definition, rust_id)| {
let name = ApiName::new_in_root_namespace(rust_id.clone());
Api::ConcreteType {
name,
cpp_definition: cpp_definition.clone(),
rs_definition: None,
}
}),
);
}
/// We do this last, _after_ we've parsed all the APIs, because we might want to actually
/// replace some of the existing APIs (structs/enums/etc.) with replacements.
fn replace_extern_cpp_types(&mut self) {
let pod_requests: HashSet<_> = self.config.get_pod_requests().iter().collect();
let replacements: HashMap<_, _> = self
.config
.externs
.0
.iter()
.map(|(cpp_definition, details)| {
let qn = QualifiedName::new_from_cpp_name(cpp_definition);
let pod = pod_requests.contains(&qn.to_cpp_name());
(
qn.clone(),
Api::ExternCppType {
name: ApiName::new_from_qualified_name(qn),
details: details.clone(),
pod,
},
)
})
.collect();
self.apis
.retain(|api| !replacements.contains_key(api.name()));
self.apis.extend(replacements.into_iter().map(|(_, v)| v));
}
fn find_items_in_root(items: Vec<Item>) -> Result<Vec<Item>, ConvertError> {
for item in items {
match item {
Item::Mod(root_mod) => {
// With namespaces enabled, bindgen always puts everything
// in a mod called 'root'. We don't want to pass that
// onto cxx, so jump right into it.
assert!(root_mod.ident == "root");
if let Some((_, items)) = root_mod.content {
return Ok(items);
}
}
_ => return Err(ConvertError::UnexpectedOuterItem),
}
}
Ok(Vec::new())
}
/// Interpret the bindgen-generated .rs for a particular
/// mod, which corresponds to a C++ namespace.
fn parse_mod_items(&mut self, items: Vec<Item>, ns: Namespace) {
// This object maintains some state specific to this namespace, i.e.
// this particular mod.
let mut mod_converter = ParseForeignMod::new(ns.clone());
let mut more_apis = ApiVec::new();
for item in items {
report_any_error(&ns, &mut more_apis, || {
self.parse_item(item, &mut mod_converter, &ns)
});
}
self.apis.append(&mut more_apis);
mod_converter.finished(&mut self.apis);
}
fn parse_item(
&mut self,
item: Item,
mod_converter: &mut ParseForeignMod,
ns: &Namespace,
) -> Result<(), ConvertErrorWithContext> {
match item {
Item::ForeignMod(fm) => {
mod_converter.convert_foreign_mod_items(fm.items);
Ok(())
}
Item::Struct(s) => {
if s.ident.to_string().ends_with("__bindgen_vtable") {
return Ok(());
}
let annotations = BindgenSemanticAttributes::new(&s.attrs);
// cxx::bridge can't cope with type aliases to generic
// types at the moment.
let name = api_name_qualified(ns, s.ident.clone(), &annotations)?;
let err = annotations.check_for_fatal_attrs(&s.ident).err();
let api = if ns.is_empty() && self.config.is_rust_type(&s.ident) {
None
} else if Self::spot_forward_declaration(&s.fields)
|| (Self::spot_zero_length_struct(&s.fields) && err.is_some())
{
// Forward declarations are recorded especially because we can't
// store them in UniquePtr or similar.
// Templated forward declarations don't appear with an _unused field (which is what
// we spot in the previous clause) but instead with an _address field.
// So, solely in the case where we're storing up an error about such
// a templated type, we'll also treat such cases as forward declarations.
Some(UnanalyzedApi::ForwardDeclaration { name, err })
} else {
let has_rvalue_reference_fields = s.fields.iter().any(|f| {
BindgenSemanticAttributes::new(&f.attrs).has_attr("rvalue_reference")
});
Some(UnanalyzedApi::Struct {
name,
details: Box::new(StructDetails {
vis: annotations.get_cpp_visibility(),
layout: annotations.get_layout(),
item: s,
has_rvalue_reference_fields,
}),
analysis: (),
})
};
if let Some(api) = api {
if !self.config.is_on_blocklist(&api.name().to_cpp_name()) {
self.apis.push(api);
}
}
Ok(())
}
Item::Enum(e) => {
let annotations = BindgenSemanticAttributes::new(&e.attrs);
let api = UnanalyzedApi::Enum {
name: api_name_qualified(ns, e.ident.clone(), &annotations)?,
item: e,
};
if !self.config.is_on_blocklist(&api.name().to_cpp_name()) {
self.apis.push(api);
}
Ok(())
}
Item::Impl(imp) => {
// We *mostly* ignore all impl blocks generated by bindgen.
// Methods also appear in 'extern "C"' blocks which
// we will convert instead. At that time we'll also construct
// synthetic impl blocks.
// We do however record which methods were spotted, since
// we have no other way of working out which functions are
// static methods vs plain functions.
mod_converter.convert_impl_items(imp);
Ok(())
}
Item::Mod(itm) => {
if let Some((_, items)) = itm.content {
let new_ns = ns.push(itm.ident.to_string());
self.parse_mod_items(items, new_ns);
}
Ok(())
}
Item::Use(use_item) => {
let mut segs = Vec::new();
let mut tree = &use_item.tree;
loop {
match tree {
UseTree::Path(up) => {
segs.push(up.ident.clone());
tree = &up.tree;
}
UseTree::Name(un) if un.ident == "root" => break, // we do not add this to any API since we generate equivalent
// use statements in our codegen phase.
UseTree::Rename(urn) => {
let old_id = &urn.ident;
let new_id = &urn.rename;
let new_tyname = QualifiedName::new(ns, new_id.clone());
assert!(segs.remove(0) == "self", "Path didn't start with self");
assert!(
segs.remove(0) == "super",
"Path didn't start with self::super"
);
// This is similar to the path encountered within 'tree'
// but without the self::super prefix which is unhelpful
// in our output mod, because we prefer relative paths
// (we're nested in another mod)
let old_path: TypePath = parse_quote! {
#(#segs)::* :: #old_id
};
let old_tyname = QualifiedName::from_type_path(&old_path);
if new_tyname == old_tyname {
return Err(ConvertErrorWithContext(
ConvertError::InfinitelyRecursiveTypedef(new_tyname),
Some(ErrorContext::new_for_item(new_id.clone())),
));
}
let annotations = BindgenSemanticAttributes::new(&use_item.attrs);
self.apis.push(UnanalyzedApi::Typedef {
name: api_name(ns, new_id.clone(), &annotations),
item: TypedefKind::Use(
parse_quote! {
pub use #old_path as #new_id;
},
Box::new(Type::Path(old_path)),
),
old_tyname: Some(old_tyname),
analysis: (),
});
break;
}
_ => {
return Err(ConvertErrorWithContext(
ConvertError::UnexpectedUseStatement(
segs.into_iter().last().map(|i| i.to_string()),
),
None,
))
}
}
}
Ok(())
}
Item::Const(const_item) => {
let annotations = BindgenSemanticAttributes::new(&const_item.attrs);
self.apis.push(UnanalyzedApi::Const {
name: api_name(ns, const_item.ident.clone(), &annotations),
const_item,
});
Ok(())
}
Item::Type(ity) => {
let annotations = BindgenSemanticAttributes::new(&ity.attrs);
// It's known that sometimes bindgen will give us duplicate typedefs with the
// same name - see test_issue_264.
self.apis.push(UnanalyzedApi::Typedef {
name: api_name(ns, ity.ident.clone(), &annotations),
item: TypedefKind::Type(ity),
old_tyname: None,
analysis: (),
});
Ok(())
}
_ => Err(ConvertErrorWithContext(
ConvertError::UnexpectedItemInMod,
None,
)),
}
}
fn spot_forward_declaration(s: &Fields) -> bool {
Self::spot_field(s, "_unused")
}
fn spot_zero_length_struct(s: &Fields) -> bool {
Self::spot_field(s, "_address")
}
fn spot_field(s: &Fields, desired_id: &str) -> bool {
s.iter()
.filter_map(|f| f.ident.as_ref())
.any(|id| id == desired_id)
}
fn confirm_all_generate_directives_obeyed(&self) -> Result<(), ConvertError> {
let api_names: HashSet<_> = self
.apis
.iter()
.map(|api| api.name().to_cpp_name())
.collect();
for generate_directive in self.config.must_generate_list() {
if !api_names.contains(&generate_directive) {
return Err(ConvertError::DidNotGenerateAnything(generate_directive));
}
}
Ok(())
}
}