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/parser/src/config.rs b/parser/src/config.rs
new file mode 100644
index 0000000..4204acf
--- /dev/null
+++ b/parser/src/config.rs
@@ -0,0 +1,500 @@
+// 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 std::borrow::Cow;
+use std::collections::hash_map::DefaultHasher;
+use std::hash::{Hash, Hasher};
+
+use itertools::Itertools;
+use proc_macro2::Span;
+use quote::ToTokens;
+
+#[cfg(feature = "reproduction_case")]
+use quote::format_ident;
+use syn::{
+    parse::{Parse, ParseStream},
+    Signature, Token, TypePath,
+};
+use syn::{Ident, Result as ParseResult};
+use thiserror::Error;
+
+use crate::{directives::get_directives, RustPath};
+
+use quote::quote;
+
+#[derive(PartialEq, Clone, Debug, Hash)]
+pub enum UnsafePolicy {
+    AllFunctionsSafe,
+    AllFunctionsUnsafe,
+}
+
+impl Default for UnsafePolicy {
+    fn default() -> Self {
+        Self::AllFunctionsUnsafe
+    }
+}
+
+impl Parse for UnsafePolicy {
+    fn parse(input: ParseStream) -> ParseResult<Self> {
+        if input.parse::<Option<Token![unsafe]>>()?.is_some() {
+            return Ok(UnsafePolicy::AllFunctionsSafe);
+        }
+        let r = match input.parse::<Option<syn::Ident>>()? {
+            Some(id) => {
+                if id == "unsafe_ffi" {
+                    Ok(UnsafePolicy::AllFunctionsSafe)
+                } else {
+                    Err(syn::Error::new(id.span(), "expected unsafe_ffi"))
+                }
+            }
+            None => Ok(UnsafePolicy::AllFunctionsUnsafe),
+        };
+        if !input.is_empty() {
+            return Err(syn::Error::new(
+                Span::call_site(),
+                "unexpected tokens within safety directive",
+            ));
+        }
+        r
+    }
+}
+
+impl ToTokens for UnsafePolicy {
+    fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
+        if *self == UnsafePolicy::AllFunctionsSafe {
+            tokens.extend(quote! { unsafe })
+        }
+    }
+}
+
+/// An entry in the allowlist.
+#[derive(Hash, Debug)]
+pub enum AllowlistEntry {
+    Item(String),
+    Namespace(String),
+}
+
+impl AllowlistEntry {
+    fn to_bindgen_item(&self) -> String {
+        match self {
+            AllowlistEntry::Item(i) => i.clone(),
+            AllowlistEntry::Namespace(ns) => format!("{}::.*", ns),
+        }
+    }
+}
+
+/// Allowlist configuration.
+#[derive(Hash, Debug)]
+pub enum Allowlist {
+    Unspecified(Vec<AllowlistEntry>),
+    All,
+    Specific(Vec<AllowlistEntry>),
+}
+
+/// Errors that may be encountered while adding allowlist entries.
+#[derive(Error, Debug)]
+pub enum AllowlistErr {
+    #[error("Conflict between generate/generate_ns! and generate_all! - use one not both")]
+    ConflictingGenerateAndGenerateAll,
+}
+
+impl Allowlist {
+    pub fn push(&mut self, item: AllowlistEntry) -> Result<(), AllowlistErr> {
+        match self {
+            Allowlist::Unspecified(ref mut uncommitted_list) => {
+                let new_list = uncommitted_list
+                    .drain(..)
+                    .chain(std::iter::once(item))
+                    .collect();
+                *self = Allowlist::Specific(new_list);
+            }
+            Allowlist::All => {
+                return Err(AllowlistErr::ConflictingGenerateAndGenerateAll);
+            }
+            Allowlist::Specific(list) => list.push(item),
+        };
+        Ok(())
+    }
+
+    pub(crate) fn set_all(&mut self) -> Result<(), AllowlistErr> {
+        if matches!(self, Allowlist::Specific(..)) {
+            return Err(AllowlistErr::ConflictingGenerateAndGenerateAll);
+        }
+        *self = Allowlist::All;
+        Ok(())
+    }
+}
+
+#[allow(clippy::derivable_impls)] // nightly-only
+impl Default for Allowlist {
+    fn default() -> Self {
+        Allowlist::Unspecified(Vec::new())
+    }
+}
+
+#[derive(Debug, Hash)]
+pub struct Subclass {
+    pub superclass: String,
+    pub subclass: Ident,
+}
+
+#[derive(Clone, Hash)]
+pub struct RustFun {
+    pub path: RustPath,
+    pub sig: Signature,
+    pub receiver: Option<Ident>,
+}
+
+impl std::fmt::Debug for RustFun {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        f.debug_struct("RustFun")
+            .field("path", &self.path)
+            .field("sig", &self.sig.to_token_stream().to_string())
+            .finish()
+    }
+}
+
+#[derive(Debug, Clone, Hash)]
+pub struct ExternCppType {
+    pub rust_path: TypePath,
+    pub opaque: bool,
+}
+
+/// Newtype wrapper so we can implement Hash.
+#[derive(Debug, Default)]
+pub struct ExternCppTypeMap(pub HashMap<String, ExternCppType>);
+
+impl std::hash::Hash for ExternCppTypeMap {
+    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
+        for (k, v) in &self.0 {
+            k.hash(state);
+            v.hash(state);
+        }
+    }
+}
+
+/// Newtype wrapper so we can implement Hash.
+#[derive(Debug, Default)]
+pub struct ConcretesMap(pub HashMap<String, Ident>);
+
+impl std::hash::Hash for ConcretesMap {
+    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
+        for (k, v) in &self.0 {
+            k.hash(state);
+            v.hash(state);
+        }
+    }
+}
+
+#[derive(Debug, Default, Hash)]
+pub struct IncludeCppConfig {
+    pub inclusions: Vec<String>,
+    pub unsafe_policy: UnsafePolicy,
+    pub parse_only: bool,
+    pub exclude_impls: bool,
+    pub(crate) pod_requests: Vec<String>,
+    pub allowlist: Allowlist,
+    pub(crate) blocklist: Vec<String>,
+    pub(crate) constructor_blocklist: Vec<String>,
+    pub instantiable: Vec<String>,
+    pub(crate) exclude_utilities: bool,
+    pub(crate) mod_name: Option<Ident>,
+    pub rust_types: Vec<RustPath>,
+    pub subclasses: Vec<Subclass>,
+    pub extern_rust_funs: Vec<RustFun>,
+    pub concretes: ConcretesMap,
+    pub externs: ExternCppTypeMap,
+}
+
+impl Parse for IncludeCppConfig {
+    fn parse(input: ParseStream) -> ParseResult<Self> {
+        let mut config = IncludeCppConfig::default();
+
+        while !input.is_empty() {
+            let has_hexathorpe = input.parse::<Option<syn::token::Pound>>()?.is_some();
+            let ident: syn::Ident = input.parse()?;
+            let args;
+            let (possible_directives, to_parse, parse_completely) = if has_hexathorpe {
+                (&get_directives().need_hexathorpe, input, false)
+            } else {
+                input.parse::<Option<syn::token::Bang>>()?;
+                syn::parenthesized!(args in input);
+                (&get_directives().need_exclamation, &args, true)
+            };
+            let all_possible = possible_directives.keys().join(", ");
+            let ident_str = ident.to_string();
+            match possible_directives.get(&ident_str) {
+                None => {
+                    return Err(syn::Error::new(
+                        ident.span(),
+                        format!("expected {}", all_possible),
+                    ));
+                }
+                Some(directive) => directive.parse(to_parse, &mut config, &ident.span())?,
+            }
+            if parse_completely && !to_parse.is_empty() {
+                return Err(syn::Error::new(
+                    ident.span(),
+                    format!("found unexpected input within the directive {}", ident_str),
+                ));
+            }
+            if input.is_empty() {
+                break;
+            }
+        }
+        Ok(config)
+    }
+}
+
+impl IncludeCppConfig {
+    pub fn get_pod_requests(&self) -> &[String] {
+        &self.pod_requests
+    }
+
+    pub fn get_mod_name(&self) -> Ident {
+        self.mod_name
+            .as_ref()
+            .cloned()
+            .unwrap_or_else(|| Ident::new("ffi", Span::call_site()))
+    }
+
+    /// Whether to avoid generating the standard helpful utility
+    /// functions which we normally include in every mod.
+    pub fn exclude_utilities(&self) -> bool {
+        self.exclude_utilities
+    }
+
+    /// Items which the user has explicitly asked us to generate;
+    /// we should raise an error if we weren't able to do so.
+    pub fn must_generate_list(&self) -> Box<dyn Iterator<Item = String> + '_> {
+        if let Allowlist::Specific(items) = &self.allowlist {
+            Box::new(
+                items
+                    .iter()
+                    .filter_map(|i| match i {
+                        AllowlistEntry::Item(i) => Some(i),
+                        AllowlistEntry::Namespace(_) => None,
+                    })
+                    .chain(self.pod_requests.iter())
+                    .cloned(),
+            )
+        } else {
+            Box::new(self.pod_requests.iter().cloned())
+        }
+    }
+
+    /// The allowlist of items to be passed into bindgen, if any.
+    pub fn bindgen_allowlist(&self) -> Option<Box<dyn Iterator<Item = String> + '_>> {
+        match &self.allowlist {
+            Allowlist::All => None,
+            Allowlist::Specific(items) => Some(Box::new(
+                items
+                    .iter()
+                    .map(AllowlistEntry::to_bindgen_item)
+                    .chain(self.pod_requests.iter().cloned())
+                    .chain(self.active_utilities())
+                    .chain(self.subclasses.iter().flat_map(|sc| {
+                        [
+                            format!("{}Cpp", sc.subclass),
+                            sc.subclass.to_string(), // TODO may not be necessary
+                            sc.superclass.clone(),
+                        ]
+                    })),
+            )),
+            Allowlist::Unspecified(_) => unreachable!(),
+        }
+    }
+
+    fn active_utilities(&self) -> Vec<String> {
+        if self.exclude_utilities {
+            Vec::new()
+        } else {
+            vec![self.get_makestring_name()]
+        }
+    }
+
+    fn is_subclass_or_superclass(&self, cpp_name: &str) -> bool {
+        self.subclasses
+            .iter()
+            .flat_map(|sc| {
+                [
+                    Cow::Owned(sc.subclass.to_string()),
+                    Cow::Borrowed(&sc.superclass),
+                ]
+            })
+            .any(|item| cpp_name == item.as_str())
+    }
+
+    /// Whether this type is on the allowlist specified by the user.
+    ///
+    /// A note on the allowlist handling in general. It's used in two places:
+    /// 1) As directives to bindgen
+    /// 2) After bindgen has generated code, to filter the APIs which
+    ///    we pass to cxx.
+    /// This second pass may seem redundant. But sometimes bindgen generates
+    /// unnecessary stuff.
+    pub fn is_on_allowlist(&self, cpp_name: &str) -> bool {
+        self.active_utilities().iter().any(|item| *item == cpp_name)
+            || self.is_subclass_or_superclass(cpp_name)
+            || self.is_subclass_holder(cpp_name)
+            || self.is_subclass_cpp(cpp_name)
+            || self.is_rust_fun(cpp_name)
+            || self.is_concrete_type(cpp_name)
+            || match &self.allowlist {
+                Allowlist::Unspecified(_) => panic!("Eek no allowlist yet"),
+                Allowlist::All => true,
+                Allowlist::Specific(items) => items.iter().any(|entry| match entry {
+                    AllowlistEntry::Item(i) => i == cpp_name,
+                    AllowlistEntry::Namespace(ns) => cpp_name.starts_with(ns),
+                }),
+            }
+    }
+
+    pub fn is_on_blocklist(&self, cpp_name: &str) -> bool {
+        self.blocklist.contains(&cpp_name.to_string())
+    }
+
+    pub fn is_on_constructor_blocklist(&self, cpp_name: &str) -> bool {
+        self.constructor_blocklist.contains(&cpp_name.to_string())
+    }
+
+    pub fn get_blocklist(&self) -> impl Iterator<Item = &String> {
+        self.blocklist.iter()
+    }
+
+    fn is_concrete_type(&self, cpp_name: &str) -> bool {
+        self.concretes.0.values().any(|val| *val == cpp_name)
+    }
+
+    /// Get a hash of the contents of this `include_cpp!` block.
+    pub fn get_hash(&self) -> u64 {
+        let mut s = DefaultHasher::new();
+        self.hash(&mut s);
+        s.finish()
+    }
+
+    /// In case there are multiple sets of ffi mods in a single binary,
+    /// endeavor to return a name which can be used to make symbols
+    /// unique.
+    pub fn uniquify_name_per_mod(&self, name: &str) -> String {
+        format!("{}_{:#x}", name, self.get_hash())
+    }
+
+    pub fn get_makestring_name(&self) -> String {
+        self.uniquify_name_per_mod("autocxx_make_string")
+    }
+
+    pub fn is_rust_type(&self, id: &Ident) -> bool {
+        self.rust_types
+            .iter()
+            .any(|rt| rt.get_final_ident() == &id.to_string())
+            || self.is_subclass_holder(&id.to_string())
+    }
+
+    fn is_rust_fun(&self, possible_fun: &str) -> bool {
+        self.extern_rust_funs
+            .iter()
+            .map(|fun| &fun.sig.ident)
+            .any(|id| id == possible_fun)
+    }
+
+    pub fn superclasses(&self) -> impl Iterator<Item = &String> {
+        let mut uniquified = HashSet::new();
+        uniquified.extend(self.subclasses.iter().map(|sc| &sc.superclass));
+        uniquified.into_iter()
+    }
+
+    pub fn is_subclass_holder(&self, id: &str) -> bool {
+        self.subclasses
+            .iter()
+            .any(|sc| format!("{}Holder", sc.subclass) == id)
+    }
+
+    fn is_subclass_cpp(&self, id: &str) -> bool {
+        self.subclasses
+            .iter()
+            .any(|sc| format!("{}Cpp", sc.subclass) == id)
+    }
+
+    /// Return the filename to which generated .rs should be written.
+    pub fn get_rs_filename(&self) -> String {
+        format!(
+            "autocxx-{}-gen.rs",
+            self.mod_name
+                .as_ref()
+                .map(|id| id.to_string())
+                .unwrap_or_else(|| "ffi-default".into())
+        )
+    }
+
+    pub fn confirm_complete(&mut self) {
+        if matches!(self.allowlist, Allowlist::Unspecified(_)) {
+            self.allowlist = Allowlist::Specific(Vec::new());
+        }
+    }
+
+    /// Used in reduction to substitute all included headers with a single
+    /// preprocessed replacement.
+    pub fn replace_included_headers(&mut self, replacement: &str) {
+        self.inclusions.clear();
+        self.inclusions.push(replacement.to_string());
+    }
+}
+
+#[cfg(feature = "reproduction_case")]
+impl ToTokens for IncludeCppConfig {
+    fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
+        let directives = get_directives();
+        let hexathorpe = syn::token::Pound(Span::call_site());
+        for (id, directive) in &directives.need_hexathorpe {
+            let id = format_ident!("{}", id);
+            for output in directive.output(self) {
+                tokens.extend(quote! {
+                    #hexathorpe #id #output
+                })
+            }
+        }
+        for (id, directive) in &directives.need_exclamation {
+            let id = format_ident!("{}", id);
+            for output in directive.output(self) {
+                tokens.extend(quote! {
+                    #id ! (#output)
+                })
+            }
+        }
+    }
+}
+
+#[cfg(test)]
+mod parse_tests {
+    use crate::config::UnsafePolicy;
+    use syn::parse_quote;
+    #[test]
+    fn test_safety_unsafe() {
+        let us: UnsafePolicy = parse_quote! {
+            unsafe
+        };
+        assert_eq!(us, UnsafePolicy::AllFunctionsSafe)
+    }
+
+    #[test]
+    fn test_safety_unsafe_ffi() {
+        let us: UnsafePolicy = parse_quote! {
+            unsafe_ffi
+        };
+        assert_eq!(us, UnsafePolicy::AllFunctionsSafe)
+    }
+
+    #[test]
+    fn test_safety_safe() {
+        let us: UnsafePolicy = parse_quote! {};
+        assert_eq!(us, UnsafePolicy::AllFunctionsUnsafe)
+    }
+}