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