Squashed 'third_party/autocxx/' content from commit 629e8fa53
git-subtree-dir: third_party/autocxx
git-subtree-split: 629e8fa531a633164c0b52e2a3cab536d4cd0849
Signed-off-by: Brian Silverman <bsilver16384@gmail.com>
Change-Id: I62a03b0049f49adf029e0204639cdb5468dde1a1
diff --git a/engine/src/conversion/analysis/pod/byvalue_checker.rs b/engine/src/conversion/analysis/pod/byvalue_checker.rs
new file mode 100644
index 0000000..de72eec
--- /dev/null
+++ b/engine/src/conversion/analysis/pod/byvalue_checker.rs
@@ -0,0 +1,343 @@
+// 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 crate::conversion::apivec::ApiVec;
+use crate::{conversion::ConvertError, known_types::known_types};
+use crate::{
+ conversion::{
+ analysis::tdef::TypedefPhase,
+ api::{Api, TypedefKind},
+ },
+ types::{Namespace, QualifiedName},
+};
+use autocxx_parser::IncludeCppConfig;
+use std::collections::HashMap;
+use syn::{ItemStruct, Type};
+
+#[derive(Clone)]
+enum PodState {
+ UnsafeToBePod(String),
+ SafeToBePod,
+ IsPod,
+ IsAlias(QualifiedName),
+}
+
+#[derive(Clone)]
+struct StructDetails {
+ state: PodState,
+ dependent_structs: Vec<QualifiedName>,
+}
+
+impl StructDetails {
+ fn new(state: PodState) -> Self {
+ StructDetails {
+ state,
+ dependent_structs: Vec::new(),
+ }
+ }
+}
+
+/// Type which is able to check whether it's safe to make a type
+/// fully representable by cxx. For instance if it is a struct containing
+/// a struct containing a std::string, the answer is no, because that
+/// std::string contains a self-referential pointer.
+/// It is possible that this is duplicative of the information stored
+/// elsewhere in the `Api` list and could possibly be removed or simplified.
+pub struct ByValueChecker {
+ // Mapping from type name to whether it is safe to be POD
+ results: HashMap<QualifiedName, StructDetails>,
+}
+
+impl ByValueChecker {
+ pub fn new() -> Self {
+ let mut results = HashMap::new();
+ for (tn, by_value_safe) in known_types().get_pod_safe_types() {
+ let safety = if by_value_safe {
+ PodState::IsPod
+ } else {
+ PodState::UnsafeToBePod(format!("type {} is not safe for POD", tn))
+ };
+ results.insert(tn.clone(), StructDetails::new(safety));
+ }
+ ByValueChecker { results }
+ }
+
+ /// Scan APIs to work out which are by-value safe. Constructs a [ByValueChecker]
+ /// that others can use to query the results.
+ pub(crate) fn new_from_apis(
+ apis: &ApiVec<TypedefPhase>,
+ config: &IncludeCppConfig,
+ ) -> Result<ByValueChecker, ConvertError> {
+ let mut byvalue_checker = ByValueChecker::new();
+ for blocklisted in config.get_blocklist() {
+ let tn = QualifiedName::new_from_cpp_name(blocklisted);
+ let safety = PodState::UnsafeToBePod(format!("type {} is on the blocklist", &tn));
+ byvalue_checker
+ .results
+ .insert(tn, StructDetails::new(safety));
+ }
+ for api in apis.iter() {
+ match api {
+ Api::Typedef { analysis, .. } => {
+ let name = api.name();
+ let typedef_type = match analysis.kind {
+ TypedefKind::Type(ref type_item) => match type_item.ty.as_ref() {
+ Type::Path(typ) => {
+ let target_tn = QualifiedName::from_type_path(typ);
+ known_types().consider_substitution(&target_tn)
+ }
+ _ => None,
+ },
+ TypedefKind::Use(_, ref ty) => match **ty {
+ Type::Path(ref typ) => {
+ let target_tn = QualifiedName::from_type_path(typ);
+ known_types().consider_substitution(&target_tn)
+ }
+ _ => None,
+ },
+ };
+ match &typedef_type {
+ Some(typ) => {
+ byvalue_checker.results.insert(
+ name.clone(),
+ StructDetails::new(PodState::IsAlias(
+ QualifiedName::from_type_path(typ),
+ )),
+ );
+ }
+ None => byvalue_checker.ingest_nonpod_type(name.clone()),
+ }
+ }
+ Api::Struct { details, .. } => {
+ byvalue_checker.ingest_struct(&details.item, api.name().get_namespace())
+ }
+ Api::Enum { .. } => {
+ byvalue_checker
+ .results
+ .insert(api.name().clone(), StructDetails::new(PodState::IsPod));
+ }
+ Api::ExternCppType { pod: true, .. } => {
+ byvalue_checker
+ .results
+ .insert(api.name().clone(), StructDetails::new(PodState::IsPod));
+ }
+ _ => {}
+ }
+ }
+ let pod_requests = config
+ .get_pod_requests()
+ .iter()
+ .map(|ty| QualifiedName::new_from_cpp_name(ty))
+ .collect();
+ byvalue_checker
+ .satisfy_requests(pod_requests)
+ .map_err(ConvertError::UnsafePodType)?;
+ Ok(byvalue_checker)
+ }
+
+ fn ingest_struct(&mut self, def: &ItemStruct, ns: &Namespace) {
+ // For this struct, work out whether it _could_ be safe as a POD.
+ let tyname = QualifiedName::new(ns, def.ident.clone());
+ let mut field_safety_problem = PodState::SafeToBePod;
+ let fieldlist = Self::get_field_types(def);
+ for ty_id in &fieldlist {
+ match self.results.get(ty_id) {
+ None => {
+ field_safety_problem = PodState::UnsafeToBePod(format!(
+ "Type {} could not be POD because its dependent type {} isn't known",
+ tyname, ty_id
+ ));
+ break;
+ }
+ Some(deets) => {
+ if let PodState::UnsafeToBePod(reason) = &deets.state {
+ let new_reason = format!("Type {} could not be POD because its dependent type {} isn't safe to be POD. Because: {}", tyname, ty_id, reason);
+ field_safety_problem = PodState::UnsafeToBePod(new_reason);
+ break;
+ }
+ }
+ }
+ }
+ if Self::has_vtable(def) {
+ let reason = format!(
+ "Type {} could not be POD because it has virtual functions.",
+ tyname
+ );
+ field_safety_problem = PodState::UnsafeToBePod(reason);
+ }
+ let mut my_details = StructDetails::new(field_safety_problem);
+ my_details.dependent_structs = fieldlist;
+ self.results.insert(tyname, my_details);
+ }
+
+ fn ingest_nonpod_type(&mut self, tyname: QualifiedName) {
+ let new_reason = format!("Type {} is a typedef to a complex type", tyname);
+ self.results.insert(
+ tyname,
+ StructDetails::new(PodState::UnsafeToBePod(new_reason)),
+ );
+ }
+
+ fn satisfy_requests(&mut self, mut requests: Vec<QualifiedName>) -> Result<(), String> {
+ while !requests.is_empty() {
+ let ty_id = requests.remove(requests.len() - 1);
+ let deets = self.results.get_mut(&ty_id);
+ let mut alias_to_consider = None;
+ match deets {
+ None => {
+ return Err(format!(
+ "Unable to make {} POD because we never saw a struct definition",
+ ty_id
+ ))
+ }
+ Some(deets) => match &deets.state {
+ PodState::UnsafeToBePod(error_msg) => return Err(error_msg.clone()),
+ PodState::IsPod => {}
+ PodState::SafeToBePod => {
+ deets.state = PodState::IsPod;
+ requests.extend_from_slice(&deets.dependent_structs);
+ }
+ PodState::IsAlias(target_type) => {
+ alias_to_consider = Some(target_type.clone());
+ }
+ },
+ }
+ // Do the following outside the match to avoid borrow checker violation.
+ if let Some(alias) = alias_to_consider {
+ match self.results.get(&alias) {
+ None => requests.extend_from_slice(&[alias, ty_id]), // try again after resolving alias target
+ Some(alias_target_deets) => {
+ self.results.get_mut(&ty_id).unwrap().state =
+ alias_target_deets.state.clone();
+ }
+ }
+ }
+ }
+ Ok(())
+ }
+
+ /// Return whether a given type is POD (i.e. can be represented by value in Rust) or not.
+ /// Unless we've got a definite record that it _is_, we return false.
+ /// Some types won't be in our `results` map. For example: (a) AutocxxConcrete types
+ /// which we've synthesized; (b) types we couldn't parse but returned ignorable
+ /// errors so that we could continue. Assume non-POD for all such cases.
+ pub fn is_pod(&self, ty_id: &QualifiedName) -> bool {
+ matches!(
+ self.results.get(ty_id),
+ Some(StructDetails {
+ state: PodState::IsPod,
+ dependent_structs: _,
+ })
+ )
+ }
+
+ fn get_field_types(def: &ItemStruct) -> Vec<QualifiedName> {
+ let mut results = Vec::new();
+ for f in &def.fields {
+ let fty = &f.ty;
+ if let Type::Path(p) = fty {
+ results.push(QualifiedName::from_type_path(p));
+ }
+ // TODO handle anything else which bindgen might spit out, e.g. arrays?
+ }
+ results
+ }
+
+ fn has_vtable(def: &ItemStruct) -> bool {
+ for f in &def.fields {
+ if f.ident.as_ref().map(|id| id == "vtable_").unwrap_or(false) {
+ return true;
+ }
+ }
+ false
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::ByValueChecker;
+ use crate::types::{Namespace, QualifiedName};
+ use syn::{parse_quote, Ident, ItemStruct};
+
+ fn ty_from_ident(id: &Ident) -> QualifiedName {
+ QualifiedName::new_from_cpp_name(&id.to_string())
+ }
+
+ #[test]
+ fn test_primitive_by_itself() {
+ let bvc = ByValueChecker::new();
+ let t_id = QualifiedName::new_from_cpp_name("u32");
+ assert!(bvc.is_pod(&t_id));
+ }
+
+ #[test]
+ fn test_primitives() {
+ let mut bvc = ByValueChecker::new();
+ let t: ItemStruct = parse_quote! {
+ struct Foo {
+ a: i32,
+ b: i64,
+ }
+ };
+ let t_id = ty_from_ident(&t.ident);
+ bvc.ingest_struct(&t, &Namespace::new());
+ bvc.satisfy_requests(vec![t_id.clone()]).unwrap();
+ assert!(bvc.is_pod(&t_id));
+ }
+
+ #[test]
+ fn test_nested_primitives() {
+ let mut bvc = ByValueChecker::new();
+ let t: ItemStruct = parse_quote! {
+ struct Foo {
+ a: i32,
+ b: i64,
+ }
+ };
+ bvc.ingest_struct(&t, &Namespace::new());
+ let t: ItemStruct = parse_quote! {
+ struct Bar {
+ a: Foo,
+ b: i64,
+ }
+ };
+ let t_id = ty_from_ident(&t.ident);
+ bvc.ingest_struct(&t, &Namespace::new());
+ bvc.satisfy_requests(vec![t_id.clone()]).unwrap();
+ assert!(bvc.is_pod(&t_id));
+ }
+
+ #[test]
+ fn test_with_up() {
+ let mut bvc = ByValueChecker::new();
+ let t: ItemStruct = parse_quote! {
+ struct Bar {
+ a: cxx::UniquePtr<CxxString>,
+ b: i64,
+ }
+ };
+ let t_id = ty_from_ident(&t.ident);
+ bvc.ingest_struct(&t, &Namespace::new());
+ bvc.satisfy_requests(vec![t_id.clone()]).unwrap();
+ assert!(bvc.is_pod(&t_id));
+ }
+
+ #[test]
+ fn test_with_cxxstring() {
+ let mut bvc = ByValueChecker::new();
+ let t: ItemStruct = parse_quote! {
+ struct Bar {
+ a: CxxString,
+ b: i64,
+ }
+ };
+ let t_id = ty_from_ident(&t.ident);
+ bvc.ingest_struct(&t, &Namespace::new());
+ assert!(bvc.satisfy_requests(vec![t_id]).is_err());
+ }
+}
diff --git a/engine/src/conversion/analysis/pod/mod.rs b/engine/src/conversion/analysis/pod/mod.rs
new file mode 100644
index 0000000..6722c23
--- /dev/null
+++ b/engine/src/conversion/analysis/pod/mod.rs
@@ -0,0 +1,257 @@
+// 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()
+}