autocxx_engine/conversion/parse/
parse_bindgen.rs

1// Copyright 2020 Google LLC
2//
3// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
4// https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
5// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
6// option. This file may not be copied, modified, or distributed
7// except according to those terms.
8
9use indexmap::map::IndexMap as HashMap;
10use indexmap::set::IndexSet as HashSet;
11
12use crate::{
13    conversion::{
14        api::{Api, ApiName, NullPhase, StructDetails, SubclassName, TypedefKind, UnanalyzedApi},
15        apivec::ApiVec,
16        check_for_fatal_attrs,
17        convert_error::LocatedConvertErrorFromRust,
18        type_helpers::type_is_reference,
19        utilities::generate_utilities,
20        ConvertError, ConvertErrorFromCpp,
21    },
22    known_types, minisyn,
23    types::{Namespace, QualifiedName},
24    ParseCallbackResults,
25};
26use crate::{
27    conversion::{
28        convert_error::{ConvertErrorWithContext, ErrorContext},
29        error_reporter::report_any_error,
30    },
31    types::validate_ident_ok_for_cxx,
32};
33use autocxx_parser::{IncludeCppConfig, RustPath};
34use syn::{parse_quote, Fields, Ident, Item, Type, TypePath, UseTree};
35
36use super::parse_foreign_mod::ParseForeignMod;
37
38/// Parses a bindgen mod in order to understand the APIs within it.
39pub(crate) struct ParseBindgen<'a> {
40    config: &'a IncludeCppConfig,
41    apis: ApiVec<NullPhase>,
42    parse_callback_results: &'a ParseCallbackResults,
43}
44
45fn api_name(ns: &Namespace, id: Ident, callback_results: &ParseCallbackResults) -> ApiName {
46    let qn = QualifiedName::new(ns, minisyn::Ident(id.clone()));
47    // TODO FIXME squash reduncancy
48    ApiName::new_with_cpp_name(ns, id.into(), callback_results.get_original_name(&qn))
49}
50
51pub(crate) fn api_name_qualified(
52    ns: &Namespace,
53    id: Ident,
54    callback_results: &ParseCallbackResults,
55) -> Result<ApiName, ConvertErrorWithContext> {
56    match validate_ident_ok_for_cxx(&id.to_string()) {
57        Err(e) => {
58            let ctx = ErrorContext::new_for_item(id.into());
59            Err(ConvertErrorWithContext(
60                ConvertErrorFromCpp::InvalidIdent(e),
61                Some(ctx),
62            ))
63        }
64        Ok(..) => Ok(api_name(ns, id, callback_results)),
65    }
66}
67
68impl<'a> ParseBindgen<'a> {
69    pub(crate) fn new(
70        config: &'a IncludeCppConfig,
71        parse_callback_results: &'a ParseCallbackResults,
72    ) -> Self {
73        ParseBindgen {
74            config,
75            apis: ApiVec::new(),
76            parse_callback_results,
77        }
78    }
79
80    /// Parses items found in the `bindgen` output and returns a set of
81    /// `Api`s together with some other data.
82    pub(crate) fn parse_items(
83        mut self,
84        items: &[Item],
85        source_file_contents: &str,
86    ) -> Result<ApiVec<NullPhase>, ConvertError> {
87        let items = Self::find_items_in_root(items).map_err(ConvertError::Cpp)?;
88        if !self.config.exclude_utilities() {
89            generate_utilities(&mut self.apis, self.config);
90        }
91        self.add_apis_from_config(source_file_contents)
92            .map_err(ConvertError::Rust)?;
93        let root_ns = Namespace::new();
94        self.parse_mod_items(items, root_ns);
95        self.confirm_all_generate_directives_obeyed()
96            .map_err(ConvertError::Cpp)?;
97        self.replace_extern_cpp_types();
98        Ok(self.apis)
99    }
100
101    /// Some API items are not populated from bindgen output, but instead
102    /// directly from items in the config.
103    fn add_apis_from_config(
104        &mut self,
105        source_file_contents: &str,
106    ) -> Result<(), LocatedConvertErrorFromRust> {
107        self.apis
108            .extend(self.config.subclasses.iter().map(|sc| Api::Subclass {
109                name: SubclassName::new(sc.subclass.clone().into()),
110                superclass: QualifiedName::new_from_cpp_name(&sc.superclass),
111            }));
112        for fun in &self.config.extern_rust_funs {
113            let id = fun.sig.ident.clone();
114            self.apis.push(Api::RustFn {
115                name: ApiName::new_in_root_namespace(id.into()),
116                details: fun.clone(),
117                deps: super::extern_fun_signatures::assemble_extern_fun_deps(
118                    &fun.sig,
119                    source_file_contents,
120                )?,
121            })
122        }
123        let unique_rust_types: HashSet<&RustPath> = self.config.rust_types.iter().collect();
124        self.apis.extend(unique_rust_types.into_iter().map(|path| {
125            let id = path.get_final_ident();
126            Api::RustType {
127                name: ApiName::new_in_root_namespace(id.clone().into()),
128                path: path.clone(),
129            }
130        }));
131        self.apis.extend(
132            self.config
133                .concretes
134                .0
135                .iter()
136                .map(|(cpp_definition, rust_id)| {
137                    let name = ApiName::new_in_root_namespace(rust_id.clone().into());
138                    Api::ConcreteType {
139                        name,
140                        cpp_definition: cpp_definition.clone(),
141                        rs_definition: None,
142                    }
143                }),
144        );
145        Ok(())
146    }
147
148    /// We do this last, _after_ we've parsed all the APIs, because we might want to actually
149    /// replace some of the existing APIs (structs/enums/etc.) with replacements.
150    fn replace_extern_cpp_types(&mut self) {
151        let pod_requests: HashSet<_> = self.config.get_pod_requests().iter().collect();
152        let replacements: HashMap<_, _> = self
153            .config
154            .externs
155            .0
156            .iter()
157            .map(|(cpp_definition, details)| {
158                let qn = QualifiedName::new_from_cpp_name(cpp_definition);
159                let pod = pod_requests.contains(&qn.to_cpp_name());
160                (
161                    qn.clone(),
162                    Api::ExternCppType {
163                        name: ApiName::new_from_qualified_name(qn),
164                        details: details.clone(),
165                        pod,
166                    },
167                )
168            })
169            .collect();
170        self.apis
171            .retain(|api| !replacements.contains_key(api.name()));
172        self.apis.extend(replacements.into_iter().map(|(_, v)| v));
173    }
174
175    fn find_items_in_root(items: &[Item]) -> Result<Option<&Vec<Item>>, ConvertErrorFromCpp> {
176        for item in items {
177            if let Item::Mod(root_mod) = item {
178                // With namespaces enabled, bindgen always puts everything
179                // in a mod called 'root'. We don't want to pass that
180                // onto cxx, so jump right into it.
181                assert!(root_mod.ident == "root");
182                if let Some((_, items)) = &root_mod.content {
183                    return Ok(Some(items));
184                }
185            }
186        }
187        Ok(None)
188    }
189
190    /// Interpret the bindgen-generated .rs for a particular
191    /// mod, which corresponds to a C++ namespace.
192    fn parse_mod_items(&mut self, items: Option<&Vec<Item>>, ns: Namespace) {
193        // This object maintains some state specific to this namespace, i.e.
194        // this particular mod.
195        let mut mod_converter = ParseForeignMod::new(ns.clone(), self.parse_callback_results);
196        let mut more_apis = ApiVec::new();
197        let empty_vec = vec![];
198        for item in items.unwrap_or(&empty_vec) {
199            report_any_error(&ns, &mut more_apis, || {
200                self.parse_item(item, &mut mod_converter, &ns)
201            });
202        }
203        self.apis.append(&mut more_apis);
204        mod_converter.finished(&mut self.apis);
205    }
206
207    fn parse_item(
208        &mut self,
209        item: &Item,
210        mod_converter: &mut ParseForeignMod,
211        ns: &Namespace,
212    ) -> Result<(), ConvertErrorWithContext> {
213        match item {
214            Item::ForeignMod(fm) => {
215                mod_converter.convert_foreign_mod_items(&fm.items);
216                Ok(())
217            }
218            Item::Struct(s) => {
219                if s.ident.to_string().ends_with("__bindgen_vtable") {
220                    return Ok(());
221                }
222                // cxx::bridge can't cope with type aliases to generic
223                // types at the moment.
224                let name = api_name_qualified(ns, s.ident.clone(), self.parse_callback_results)?;
225                if known_types().is_known_subtitute_type(&name.name) {
226                    // This is one of the replacement types, e.g.
227                    // root::Str replacing rust::Str or
228                    // root::string replacing root::std::string
229                    return Ok(());
230                }
231                let mut err = check_for_fatal_attrs(self.parse_callback_results, &name.name).err();
232                let api = if (ns.is_empty() && self.config.is_rust_type(&s.ident))
233                    || known_types().is_known_type(&name.name)
234                {
235                    None
236                } else if Self::spot_forward_declaration(&s.fields)
237                    || (Self::spot_zero_length_struct(&s.fields) && err.is_some())
238                {
239                    // Forward declarations are recorded especially because we can't
240                    // store them in UniquePtr or similar.
241                    // Templated forward declarations don't appear with an _unused field (which is what
242                    // we spot in the previous clause) but instead with an _address field.
243                    // So, solely in the case where we're storing up an error about such
244                    // a templated type, we'll also treat such cases as forward declarations.
245                    //
246                    // We'll also at this point check for one specific problem with
247                    // forward declarations.
248                    if err.is_none() && name.cpp_name().is_nested() {
249                        err = Some(ConvertErrorWithContext(
250                            ConvertErrorFromCpp::ForwardDeclaredNestedType,
251                            Some(ErrorContext::new_for_item(s.ident.clone().into())),
252                        ));
253                    }
254                    Some(UnanalyzedApi::ForwardDeclaration { name, err })
255                } else {
256                    let has_rvalue_reference_fields = Self::spot_rvalue_reference_fields(&s.fields);
257                    Some(UnanalyzedApi::Struct {
258                        name,
259                        details: Box::new(StructDetails {
260                            item: s.clone().into(),
261                            has_rvalue_reference_fields,
262                        }),
263                        analysis: (),
264                    })
265                };
266                if let Some(api) = api {
267                    if !self.config.is_on_blocklist(&api.name().to_cpp_name()) {
268                        self.apis.push(api);
269                    }
270                }
271                Ok(())
272            }
273            Item::Enum(e) => {
274                let api = UnanalyzedApi::Enum {
275                    name: api_name_qualified(ns, e.ident.clone(), self.parse_callback_results)?,
276                    item: e.clone().into(),
277                };
278                if !self.config.is_on_blocklist(&api.name().to_cpp_name()) {
279                    self.apis.push(api);
280                }
281                Ok(())
282            }
283            Item::Impl(imp) => {
284                // We *mostly* ignore all impl blocks generated by bindgen.
285                // Methods also appear in 'extern "C"' blocks which
286                // we will convert instead. At that time we'll also construct
287                // synthetic impl blocks.
288                // We do however record which methods were spotted, since
289                // we have no other way of working out which functions are
290                // static methods vs plain functions.
291                mod_converter.convert_impl_items(imp.clone());
292                Ok(())
293            }
294            Item::Mod(itm) => {
295                if let Some((_, items)) = &itm.content {
296                    let new_ns = ns.push(itm.ident.to_string());
297                    self.parse_mod_items(Some(items), new_ns);
298                }
299                Ok(())
300            }
301            Item::Use(use_item) => {
302                let mut segs = Vec::new();
303                let mut tree = &use_item.tree;
304                loop {
305                    match tree {
306                        UseTree::Path(up) => {
307                            segs.push(up.ident.clone());
308                            tree = &up.tree;
309                        }
310                        UseTree::Name(un) if un.ident == "root" => break, // we do not add this to any API since we generate equivalent
311                        // use statements in our codegen phase.
312                        UseTree::Rename(urn) => {
313                            let old_id = &urn.ident;
314                            let new_id = &urn.rename;
315                            if new_id == "bindgen_cchar16_t" {
316                                return Ok(());
317                            }
318                            let new_tyname = QualifiedName::new(ns, new_id.clone().into());
319                            assert!(segs.remove(0) == "self", "Path didn't start with self");
320                            assert!(
321                                segs.remove(0) == "super",
322                                "Path didn't start with self::super"
323                            );
324                            // This is similar to the path encountered within 'tree'
325                            // but without the self::super prefix which is unhelpful
326                            // in our output mod, because we prefer relative paths
327                            // (we're nested in another mod)
328                            let old_path: TypePath = parse_quote! {
329                                #(#segs)::* :: #old_id
330                            };
331                            let old_tyname = QualifiedName::from_type_path(&old_path);
332                            if new_tyname == old_tyname {
333                                return Err(ConvertErrorWithContext(
334                                    ConvertErrorFromCpp::InfinitelyRecursiveTypedef(new_tyname),
335                                    Some(ErrorContext::new_for_item(new_id.clone().into())),
336                                ));
337                            }
338                            self.apis.push(UnanalyzedApi::Typedef {
339                                name: api_name(ns, new_id.clone(), self.parse_callback_results),
340                                item: TypedefKind::Use(Box::new(Type::Path(old_path).into())),
341                                old_tyname: Some(old_tyname),
342                                analysis: (),
343                            });
344                            break;
345                        }
346                        _ => return Ok(()),
347                    }
348                }
349                Ok(())
350            }
351            Item::Const(const_item) => {
352                // Bindgen generates const expressions for nested unnamed enums,
353                // but autcxx will refuse to expand those enums, making these consts
354                // invalid.
355                let mut enum_type_name_valid = true;
356                if let Type::Path(p) = &*const_item.ty {
357                    if let Some(p) = &p.path.segments.last() {
358                        if validate_ident_ok_for_cxx(&p.ident.to_string()).is_err() {
359                            enum_type_name_valid = false;
360                        }
361                    }
362                }
363                if enum_type_name_valid {
364                    self.apis.push(UnanalyzedApi::Const {
365                        name: api_name(ns, const_item.ident.clone(), self.parse_callback_results),
366                        const_item: const_item.clone().into(),
367                    });
368                }
369                Ok(())
370            }
371            Item::Type(ity) => {
372                // It's known that sometimes bindgen will give us duplicate typedefs with the
373                // same name - see test_issue_264.
374                self.apis.push(UnanalyzedApi::Typedef {
375                    name: api_name(ns, ity.ident.clone(), self.parse_callback_results),
376                    item: TypedefKind::Type(ity.clone().into()),
377                    old_tyname: None,
378                    analysis: (),
379                });
380                Ok(())
381            }
382            _ => Err(ConvertErrorWithContext(
383                ConvertErrorFromCpp::UnexpectedItemInMod,
384                None,
385            )),
386        }
387    }
388
389    fn spot_forward_declaration(s: &Fields) -> bool {
390        Self::spot_field(s, "_unused")
391    }
392
393    fn spot_zero_length_struct(s: &Fields) -> bool {
394        Self::spot_field(s, "_address")
395    }
396
397    fn spot_field(s: &Fields, desired_id: &str) -> bool {
398        s.iter()
399            .filter_map(|f| f.ident.as_ref())
400            .any(|id| id == desired_id)
401    }
402
403    fn spot_rvalue_reference_fields(s: &Fields) -> bool {
404        s.iter().any(|f| type_is_reference(&f.ty, true))
405    }
406
407    fn confirm_all_generate_directives_obeyed(&self) -> Result<(), ConvertErrorFromCpp> {
408        let api_names: HashSet<_> = self
409            .apis
410            .iter()
411            .map(|api| api.name().to_cpp_name())
412            .collect();
413        for generate_directive in self.config.must_generate_list() {
414            if !api_names.contains(&generate_directive) {
415                return Err(ConvertErrorFromCpp::DidNotGenerateAnything(
416                    generate_directive,
417                ));
418            }
419        }
420        Ok(())
421    }
422}