blob: 6722c237f7582967d91a3cf60619b161ba329d3f [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.
mod byvalue_checker;
use indexmap::map::IndexMap as HashMap;
use indexmap::set::IndexSet as HashSet;
use autocxx_parser::IncludeCppConfig;
use byvalue_checker::ByValueChecker;
use syn::{ItemEnum, ItemStruct, Type, Visibility};
use crate::{
conversion::{
analysis::type_converter::{self, add_analysis, TypeConversionContext, TypeConverter},
api::{AnalysisPhase, Api, ApiName, CppVisibility, NullPhase, StructDetails, TypeKind},
apivec::ApiVec,
convert_error::{ConvertErrorWithContext, ErrorContext},
error_reporter::convert_apis,
parse::BindgenSemanticAttributes,
ConvertError,
},
types::{Namespace, QualifiedName},
};
use super::tdef::{TypedefAnalysis, TypedefPhase};
pub(crate) struct FieldInfo {
pub(crate) ty: Type,
pub(crate) type_kind: type_converter::TypeKind,
}
pub(crate) struct PodAnalysis {
pub(crate) kind: TypeKind,
pub(crate) bases: HashSet<QualifiedName>,
/// Base classes for which we should create casts.
/// That's just those which are on the allowlist,
/// because otherwise we don't know whether they're
/// abstract or not.
pub(crate) castable_bases: HashSet<QualifiedName>,
pub(crate) field_deps: HashSet<QualifiedName>,
pub(crate) field_info: Vec<FieldInfo>,
pub(crate) is_generic: bool,
pub(crate) in_anonymous_namespace: bool,
}
pub(crate) struct PodPhase;
impl AnalysisPhase for PodPhase {
type TypedefAnalysis = TypedefAnalysis;
type StructAnalysis = PodAnalysis;
type FunAnalysis = ();
}
/// In our set of APIs, work out which ones are safe to represent
/// by value in Rust (e.g. they don't have a destructor) and record
/// as such. Return a set of APIs annotated with extra metadata,
/// and an object which can be used to query the POD status of any
/// type whether or not it's one of the [Api]s.
pub(crate) fn analyze_pod_apis(
apis: ApiVec<TypedefPhase>,
config: &IncludeCppConfig,
) -> Result<ApiVec<PodPhase>, ConvertError> {
// This next line will return an error if any of the 'generate_pod'
// directives from the user can't be met because, for instance,
// a type contains a std::string or some other type which can't be
// held safely by value in Rust.
let byvalue_checker = ByValueChecker::new_from_apis(&apis, config)?;
let mut extra_apis = ApiVec::new();
let mut type_converter = TypeConverter::new(config, &apis);
let mut results = ApiVec::new();
convert_apis(
apis,
&mut results,
Api::fun_unchanged,
|name, details, _| {
analyze_struct(
&byvalue_checker,
&mut type_converter,
&mut extra_apis,
name,
details,
config,
)
},
analyze_enum,
Api::typedef_unchanged,
);
// Conceivably, the process of POD-analysing the first set of APIs could result
// in us creating new APIs to concretize generic types.
let extra_apis: ApiVec<PodPhase> = extra_apis.into_iter().map(add_analysis).collect();
let mut more_extra_apis = ApiVec::new();
convert_apis(
extra_apis,
&mut results,
Api::fun_unchanged,
|name, details, _| {
analyze_struct(
&byvalue_checker,
&mut type_converter,
&mut more_extra_apis,
name,
details,
config,
)
},
analyze_enum,
Api::typedef_unchanged,
);
assert!(more_extra_apis.is_empty());
Ok(results)
}
fn analyze_enum(
name: ApiName,
mut item: ItemEnum,
) -> Result<Box<dyn Iterator<Item = Api<PodPhase>>>, ConvertErrorWithContext> {
let metadata = BindgenSemanticAttributes::new_retaining_others(&mut item.attrs);
metadata.check_for_fatal_attrs(&name.name.get_final_ident())?;
Ok(Box::new(std::iter::once(Api::Enum { name, item })))
}
fn analyze_struct(
byvalue_checker: &ByValueChecker,
type_converter: &mut TypeConverter,
extra_apis: &mut ApiVec<NullPhase>,
name: ApiName,
mut details: Box<StructDetails>,
config: &IncludeCppConfig,
) -> Result<Box<dyn Iterator<Item = Api<PodPhase>>>, ConvertErrorWithContext> {
let id = name.name.get_final_ident();
if details.vis != CppVisibility::Public {
return Err(ConvertErrorWithContext(
ConvertError::NonPublicNestedType,
Some(ErrorContext::new_for_item(id)),
));
}
let metadata = BindgenSemanticAttributes::new_retaining_others(&mut details.item.attrs);
metadata.check_for_fatal_attrs(&id)?;
let bases = get_bases(&details.item);
let mut field_deps = HashSet::new();
let mut field_info = Vec::new();
let field_conversion_errors = get_struct_field_types(
type_converter,
name.name.get_namespace(),
&details.item,
&mut field_deps,
&mut field_info,
extra_apis,
);
let type_kind = if byvalue_checker.is_pod(&name.name) {
// It's POD so any errors encountered parsing its fields are important.
// Let's not allow anything to be POD if it's got rvalue reference fields.
if details.has_rvalue_reference_fields {
return Err(ConvertErrorWithContext(
ConvertError::RValueReferenceField,
Some(ErrorContext::new_for_item(id)),
));
}
if let Some(err) = field_conversion_errors.into_iter().next() {
return Err(ConvertErrorWithContext(
err,
Some(ErrorContext::new_for_item(id)),
));
}
TypeKind::Pod
} else {
TypeKind::NonPod
};
let castable_bases = bases
.iter()
.filter(|(_, is_public)| **is_public)
.map(|(base, _)| base)
.filter(|base| config.is_on_allowlist(&base.to_cpp_name()))
.cloned()
.collect();
let is_generic = !details.item.generics.params.is_empty();
let in_anonymous_namespace = name
.name
.ns_segment_iter()
.any(|ns| ns.starts_with("_bindgen_mod"));
Ok(Box::new(std::iter::once(Api::Struct {
name,
details,
analysis: PodAnalysis {
kind: type_kind,
bases: bases.into_keys().collect(),
castable_bases,
field_deps,
field_info,
is_generic,
in_anonymous_namespace,
},
})))
}
fn get_struct_field_types(
type_converter: &mut TypeConverter,
ns: &Namespace,
s: &ItemStruct,
field_deps: &mut HashSet<QualifiedName>,
field_info: &mut Vec<FieldInfo>,
extra_apis: &mut ApiVec<NullPhase>,
) -> Vec<ConvertError> {
let mut convert_errors = Vec::new();
for f in &s.fields {
let annotated =
type_converter.convert_type(f.ty.clone(), ns, &TypeConversionContext::WithinReference);
match annotated {
Ok(mut r) => {
extra_apis.append(&mut r.extra_apis);
// Skip base classes represented as fields. Anything which wants to include bases can chain
// those to the list we're building.
if !f
.ident
.as_ref()
.map(|id| {
id.to_string().starts_with("_base")
|| id.to_string().starts_with("__bindgen_padding")
})
.unwrap_or(false)
{
field_deps.extend(r.types_encountered);
field_info.push(FieldInfo {
ty: r.ty,
type_kind: r.kind,
});
}
}
Err(e) => convert_errors.push(e),
};
}
convert_errors
}
/// Map to whether the bases are public.
fn get_bases(item: &ItemStruct) -> HashMap<QualifiedName, bool> {
item.fields
.iter()
.filter_map(|f| {
let is_public = matches!(f.vis, Visibility::Public(_));
match &f.ty {
Type::Path(typ) => f
.ident
.as_ref()
.filter(|id| id.to_string().starts_with("_base"))
.map(|_| (QualifiedName::from_type_path(typ), is_public)),
_ => None,
}
})
.collect()
}