blob: 3634295b92de3af6c97604938ced514f6a55d090 [file] [log] [blame]
Brian Silverman4e662aa2022-05-11 23:10:19 -07001// 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;
11use std::borrow::Cow;
12use std::collections::hash_map::DefaultHasher;
13use std::hash::{Hash, Hasher};
14
15use itertools::Itertools;
16use proc_macro2::Span;
17use quote::ToTokens;
18
19#[cfg(feature = "reproduction_case")]
20use quote::format_ident;
21use syn::{
22 parse::{Parse, ParseStream},
23 Signature, Token, TypePath,
24};
25use syn::{Ident, Result as ParseResult};
26use thiserror::Error;
27
28use crate::{directives::get_directives, RustPath};
29
30use quote::quote;
31
Austin Schuh6ea9bfa2023-08-06 19:05:10 -070032#[derive(PartialEq, Eq, Clone, Debug, Hash)]
Brian Silverman4e662aa2022-05-11 23:10:19 -070033pub enum UnsafePolicy {
34 AllFunctionsSafe,
35 AllFunctionsUnsafe,
Brian Silvermanf3ec38b2022-07-06 20:43:36 -070036 ReferencesWrappedAllFunctionsSafe,
Brian Silverman4e662aa2022-05-11 23:10:19 -070037}
38
39impl Default for UnsafePolicy {
40 fn default() -> Self {
41 Self::AllFunctionsUnsafe
42 }
43}
44
45impl Parse for UnsafePolicy {
46 fn parse(input: ParseStream) -> ParseResult<Self> {
47 if input.parse::<Option<Token![unsafe]>>()?.is_some() {
48 return Ok(UnsafePolicy::AllFunctionsSafe);
49 }
50 let r = match input.parse::<Option<syn::Ident>>()? {
51 Some(id) => {
52 if id == "unsafe_ffi" {
53 Ok(UnsafePolicy::AllFunctionsSafe)
Brian Silvermanf3ec38b2022-07-06 20:43:36 -070054 } else if id == "unsafe_references_wrapped" {
55 Ok(UnsafePolicy::ReferencesWrappedAllFunctionsSafe)
Brian Silverman4e662aa2022-05-11 23:10:19 -070056 } else {
Brian Silvermanf3ec38b2022-07-06 20:43:36 -070057 Err(syn::Error::new(
58 id.span(),
59 "expected unsafe_ffi or unsafe_references_wrapped",
60 ))
Brian Silverman4e662aa2022-05-11 23:10:19 -070061 }
62 }
63 None => Ok(UnsafePolicy::AllFunctionsUnsafe),
64 };
65 if !input.is_empty() {
66 return Err(syn::Error::new(
67 Span::call_site(),
68 "unexpected tokens within safety directive",
69 ));
70 }
71 r
72 }
73}
74
75impl ToTokens for UnsafePolicy {
76 fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
77 if *self == UnsafePolicy::AllFunctionsSafe {
78 tokens.extend(quote! { unsafe })
Brian Silvermanf3ec38b2022-07-06 20:43:36 -070079 } else if *self == UnsafePolicy::ReferencesWrappedAllFunctionsSafe {
80 tokens.extend(quote! { unsafe_references_wrapped })
Brian Silverman4e662aa2022-05-11 23:10:19 -070081 }
82 }
83}
84
Brian Silvermanf3ec38b2022-07-06 20:43:36 -070085impl UnsafePolicy {
86 /// Whether we are treating C++ references as a different thing from Rust
87 /// references and therefore have to generate lots of code for a CppRef type
88 pub fn requires_cpprefs(&self) -> bool {
89 matches!(self, Self::ReferencesWrappedAllFunctionsSafe)
90 }
91}
92
Brian Silverman4e662aa2022-05-11 23:10:19 -070093/// An entry in the allowlist.
94#[derive(Hash, Debug)]
95pub enum AllowlistEntry {
96 Item(String),
97 Namespace(String),
98}
99
100impl AllowlistEntry {
101 fn to_bindgen_item(&self) -> String {
102 match self {
103 AllowlistEntry::Item(i) => i.clone(),
Austin Schuh6ea9bfa2023-08-06 19:05:10 -0700104 AllowlistEntry::Namespace(ns) => format!("{ns}::.*"),
Brian Silverman4e662aa2022-05-11 23:10:19 -0700105 }
106 }
107}
108
109/// Allowlist configuration.
110#[derive(Hash, Debug)]
111pub enum Allowlist {
112 Unspecified(Vec<AllowlistEntry>),
113 All,
114 Specific(Vec<AllowlistEntry>),
115}
116
117/// Errors that may be encountered while adding allowlist entries.
118#[derive(Error, Debug)]
119pub enum AllowlistErr {
120 #[error("Conflict between generate/generate_ns! and generate_all! - use one not both")]
121 ConflictingGenerateAndGenerateAll,
122}
123
124impl Allowlist {
125 pub fn push(&mut self, item: AllowlistEntry) -> Result<(), AllowlistErr> {
126 match self {
127 Allowlist::Unspecified(ref mut uncommitted_list) => {
128 let new_list = uncommitted_list
129 .drain(..)
130 .chain(std::iter::once(item))
131 .collect();
132 *self = Allowlist::Specific(new_list);
133 }
134 Allowlist::All => {
135 return Err(AllowlistErr::ConflictingGenerateAndGenerateAll);
136 }
137 Allowlist::Specific(list) => list.push(item),
138 };
139 Ok(())
140 }
141
142 pub(crate) fn set_all(&mut self) -> Result<(), AllowlistErr> {
143 if matches!(self, Allowlist::Specific(..)) {
144 return Err(AllowlistErr::ConflictingGenerateAndGenerateAll);
145 }
146 *self = Allowlist::All;
147 Ok(())
148 }
149}
150
151#[allow(clippy::derivable_impls)] // nightly-only
152impl Default for Allowlist {
153 fn default() -> Self {
154 Allowlist::Unspecified(Vec::new())
155 }
156}
157
158#[derive(Debug, Hash)]
159pub struct Subclass {
160 pub superclass: String,
161 pub subclass: Ident,
162}
163
164#[derive(Clone, Hash)]
165pub struct RustFun {
166 pub path: RustPath,
167 pub sig: Signature,
Austin Schuh6ea9bfa2023-08-06 19:05:10 -0700168 pub has_receiver: bool,
Brian Silverman4e662aa2022-05-11 23:10:19 -0700169}
170
171impl std::fmt::Debug for RustFun {
172 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
173 f.debug_struct("RustFun")
174 .field("path", &self.path)
175 .field("sig", &self.sig.to_token_stream().to_string())
176 .finish()
177 }
178}
179
180#[derive(Debug, Clone, Hash)]
181pub struct ExternCppType {
182 pub rust_path: TypePath,
183 pub opaque: bool,
184}
185
186/// Newtype wrapper so we can implement Hash.
187#[derive(Debug, Default)]
188pub struct ExternCppTypeMap(pub HashMap<String, ExternCppType>);
189
190impl std::hash::Hash for ExternCppTypeMap {
191 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
192 for (k, v) in &self.0 {
193 k.hash(state);
194 v.hash(state);
195 }
196 }
197}
198
199/// Newtype wrapper so we can implement Hash.
200#[derive(Debug, Default)]
201pub struct ConcretesMap(pub HashMap<String, Ident>);
202
203impl std::hash::Hash for ConcretesMap {
204 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
205 for (k, v) in &self.0 {
206 k.hash(state);
207 v.hash(state);
208 }
209 }
210}
211
212#[derive(Debug, Default, Hash)]
213pub struct IncludeCppConfig {
214 pub inclusions: Vec<String>,
215 pub unsafe_policy: UnsafePolicy,
216 pub parse_only: bool,
217 pub exclude_impls: bool,
218 pub(crate) pod_requests: Vec<String>,
219 pub allowlist: Allowlist,
220 pub(crate) blocklist: Vec<String>,
221 pub(crate) constructor_blocklist: Vec<String>,
222 pub instantiable: Vec<String>,
223 pub(crate) exclude_utilities: bool,
224 pub(crate) mod_name: Option<Ident>,
225 pub rust_types: Vec<RustPath>,
226 pub subclasses: Vec<Subclass>,
227 pub extern_rust_funs: Vec<RustFun>,
228 pub concretes: ConcretesMap,
229 pub externs: ExternCppTypeMap,
230}
231
232impl Parse for IncludeCppConfig {
233 fn parse(input: ParseStream) -> ParseResult<Self> {
234 let mut config = IncludeCppConfig::default();
235
236 while !input.is_empty() {
237 let has_hexathorpe = input.parse::<Option<syn::token::Pound>>()?.is_some();
238 let ident: syn::Ident = input.parse()?;
239 let args;
240 let (possible_directives, to_parse, parse_completely) = if has_hexathorpe {
241 (&get_directives().need_hexathorpe, input, false)
242 } else {
Austin Schuh6ea9bfa2023-08-06 19:05:10 -0700243 input.parse::<Option<syn::token::Not>>()?;
Brian Silverman4e662aa2022-05-11 23:10:19 -0700244 syn::parenthesized!(args in input);
245 (&get_directives().need_exclamation, &args, true)
246 };
247 let all_possible = possible_directives.keys().join(", ");
248 let ident_str = ident.to_string();
249 match possible_directives.get(&ident_str) {
250 None => {
251 return Err(syn::Error::new(
252 ident.span(),
Austin Schuh6ea9bfa2023-08-06 19:05:10 -0700253 format!("expected {all_possible}"),
Brian Silverman4e662aa2022-05-11 23:10:19 -0700254 ));
255 }
256 Some(directive) => directive.parse(to_parse, &mut config, &ident.span())?,
257 }
258 if parse_completely && !to_parse.is_empty() {
259 return Err(syn::Error::new(
260 ident.span(),
Austin Schuh6ea9bfa2023-08-06 19:05:10 -0700261 format!("found unexpected input within the directive {ident_str}"),
Brian Silverman4e662aa2022-05-11 23:10:19 -0700262 ));
263 }
264 if input.is_empty() {
265 break;
266 }
267 }
268 Ok(config)
269 }
270}
271
272impl IncludeCppConfig {
273 pub fn get_pod_requests(&self) -> &[String] {
274 &self.pod_requests
275 }
276
277 pub fn get_mod_name(&self) -> Ident {
278 self.mod_name
279 .as_ref()
280 .cloned()
281 .unwrap_or_else(|| Ident::new("ffi", Span::call_site()))
282 }
283
284 /// Whether to avoid generating the standard helpful utility
285 /// functions which we normally include in every mod.
286 pub fn exclude_utilities(&self) -> bool {
287 self.exclude_utilities
288 }
289
290 /// Items which the user has explicitly asked us to generate;
291 /// we should raise an error if we weren't able to do so.
292 pub fn must_generate_list(&self) -> Box<dyn Iterator<Item = String> + '_> {
293 if let Allowlist::Specific(items) = &self.allowlist {
294 Box::new(
295 items
296 .iter()
297 .filter_map(|i| match i {
298 AllowlistEntry::Item(i) => Some(i),
299 AllowlistEntry::Namespace(_) => None,
300 })
301 .chain(self.pod_requests.iter())
302 .cloned(),
303 )
304 } else {
305 Box::new(self.pod_requests.iter().cloned())
306 }
307 }
308
309 /// The allowlist of items to be passed into bindgen, if any.
310 pub fn bindgen_allowlist(&self) -> Option<Box<dyn Iterator<Item = String> + '_>> {
311 match &self.allowlist {
312 Allowlist::All => None,
313 Allowlist::Specific(items) => Some(Box::new(
314 items
315 .iter()
316 .map(AllowlistEntry::to_bindgen_item)
317 .chain(self.pod_requests.iter().cloned())
318 .chain(self.active_utilities())
319 .chain(self.subclasses.iter().flat_map(|sc| {
320 [
321 format!("{}Cpp", sc.subclass),
322 sc.subclass.to_string(), // TODO may not be necessary
323 sc.superclass.clone(),
324 ]
325 })),
326 )),
327 Allowlist::Unspecified(_) => unreachable!(),
328 }
329 }
330
331 fn active_utilities(&self) -> Vec<String> {
332 if self.exclude_utilities {
333 Vec::new()
334 } else {
335 vec![self.get_makestring_name()]
336 }
337 }
338
339 fn is_subclass_or_superclass(&self, cpp_name: &str) -> bool {
340 self.subclasses
341 .iter()
342 .flat_map(|sc| {
343 [
344 Cow::Owned(sc.subclass.to_string()),
345 Cow::Borrowed(&sc.superclass),
346 ]
347 })
348 .any(|item| cpp_name == item.as_str())
349 }
350
351 /// Whether this type is on the allowlist specified by the user.
352 ///
353 /// A note on the allowlist handling in general. It's used in two places:
354 /// 1) As directives to bindgen
355 /// 2) After bindgen has generated code, to filter the APIs which
356 /// we pass to cxx.
357 /// This second pass may seem redundant. But sometimes bindgen generates
358 /// unnecessary stuff.
359 pub fn is_on_allowlist(&self, cpp_name: &str) -> bool {
360 self.active_utilities().iter().any(|item| *item == cpp_name)
361 || self.is_subclass_or_superclass(cpp_name)
362 || self.is_subclass_holder(cpp_name)
363 || self.is_subclass_cpp(cpp_name)
364 || self.is_rust_fun(cpp_name)
Austin Schuh6ea9bfa2023-08-06 19:05:10 -0700365 || self.is_rust_type_name(cpp_name)
Brian Silverman4e662aa2022-05-11 23:10:19 -0700366 || self.is_concrete_type(cpp_name)
367 || match &self.allowlist {
368 Allowlist::Unspecified(_) => panic!("Eek no allowlist yet"),
369 Allowlist::All => true,
370 Allowlist::Specific(items) => items.iter().any(|entry| match entry {
371 AllowlistEntry::Item(i) => i == cpp_name,
372 AllowlistEntry::Namespace(ns) => cpp_name.starts_with(ns),
373 }),
374 }
375 }
376
377 pub fn is_on_blocklist(&self, cpp_name: &str) -> bool {
378 self.blocklist.contains(&cpp_name.to_string())
379 }
380
381 pub fn is_on_constructor_blocklist(&self, cpp_name: &str) -> bool {
382 self.constructor_blocklist.contains(&cpp_name.to_string())
383 }
384
385 pub fn get_blocklist(&self) -> impl Iterator<Item = &String> {
386 self.blocklist.iter()
387 }
388
389 fn is_concrete_type(&self, cpp_name: &str) -> bool {
390 self.concretes.0.values().any(|val| *val == cpp_name)
391 }
392
393 /// Get a hash of the contents of this `include_cpp!` block.
394 pub fn get_hash(&self) -> u64 {
395 let mut s = DefaultHasher::new();
396 self.hash(&mut s);
397 s.finish()
398 }
399
400 /// In case there are multiple sets of ffi mods in a single binary,
401 /// endeavor to return a name which can be used to make symbols
402 /// unique.
403 pub fn uniquify_name_per_mod(&self, name: &str) -> String {
404 format!("{}_{:#x}", name, self.get_hash())
405 }
406
407 pub fn get_makestring_name(&self) -> String {
408 self.uniquify_name_per_mod("autocxx_make_string")
409 }
410
411 pub fn is_rust_type(&self, id: &Ident) -> bool {
Austin Schuh6ea9bfa2023-08-06 19:05:10 -0700412 let id_string = id.to_string();
413 self.is_rust_type_name(&id_string) || self.is_subclass_holder(&id_string)
414 }
415
416 fn is_rust_type_name(&self, possible_ty: &str) -> bool {
Brian Silverman4e662aa2022-05-11 23:10:19 -0700417 self.rust_types
418 .iter()
Austin Schuh6ea9bfa2023-08-06 19:05:10 -0700419 .any(|rt| rt.get_final_ident() == possible_ty)
Brian Silverman4e662aa2022-05-11 23:10:19 -0700420 }
421
422 fn is_rust_fun(&self, possible_fun: &str) -> bool {
423 self.extern_rust_funs
424 .iter()
425 .map(|fun| &fun.sig.ident)
426 .any(|id| id == possible_fun)
427 }
428
429 pub fn superclasses(&self) -> impl Iterator<Item = &String> {
430 let mut uniquified = HashSet::new();
431 uniquified.extend(self.subclasses.iter().map(|sc| &sc.superclass));
432 uniquified.into_iter()
433 }
434
435 pub fn is_subclass_holder(&self, id: &str) -> bool {
436 self.subclasses
437 .iter()
438 .any(|sc| format!("{}Holder", sc.subclass) == id)
439 }
440
441 fn is_subclass_cpp(&self, id: &str) -> bool {
442 self.subclasses
443 .iter()
444 .any(|sc| format!("{}Cpp", sc.subclass) == id)
445 }
446
447 /// Return the filename to which generated .rs should be written.
448 pub fn get_rs_filename(&self) -> String {
449 format!(
450 "autocxx-{}-gen.rs",
451 self.mod_name
452 .as_ref()
453 .map(|id| id.to_string())
454 .unwrap_or_else(|| "ffi-default".into())
455 )
456 }
457
458 pub fn confirm_complete(&mut self) {
459 if matches!(self.allowlist, Allowlist::Unspecified(_)) {
460 self.allowlist = Allowlist::Specific(Vec::new());
461 }
462 }
463
464 /// Used in reduction to substitute all included headers with a single
465 /// preprocessed replacement.
466 pub fn replace_included_headers(&mut self, replacement: &str) {
467 self.inclusions.clear();
468 self.inclusions.push(replacement.to_string());
469 }
470}
471
472#[cfg(feature = "reproduction_case")]
473impl ToTokens for IncludeCppConfig {
474 fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
475 let directives = get_directives();
476 let hexathorpe = syn::token::Pound(Span::call_site());
477 for (id, directive) in &directives.need_hexathorpe {
478 let id = format_ident!("{}", id);
479 for output in directive.output(self) {
480 tokens.extend(quote! {
481 #hexathorpe #id #output
482 })
483 }
484 }
485 for (id, directive) in &directives.need_exclamation {
486 let id = format_ident!("{}", id);
487 for output in directive.output(self) {
488 tokens.extend(quote! {
489 #id ! (#output)
490 })
491 }
492 }
493 }
494}
495
496#[cfg(test)]
497mod parse_tests {
498 use crate::config::UnsafePolicy;
499 use syn::parse_quote;
500 #[test]
501 fn test_safety_unsafe() {
502 let us: UnsafePolicy = parse_quote! {
503 unsafe
504 };
505 assert_eq!(us, UnsafePolicy::AllFunctionsSafe)
506 }
507
508 #[test]
509 fn test_safety_unsafe_ffi() {
510 let us: UnsafePolicy = parse_quote! {
511 unsafe_ffi
512 };
513 assert_eq!(us, UnsafePolicy::AllFunctionsSafe)
514 }
515
516 #[test]
517 fn test_safety_safe() {
518 let us: UnsafePolicy = parse_quote! {};
519 assert_eq!(us, UnsafePolicy::AllFunctionsUnsafe)
520 }
521}