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