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()
+}