• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 use crate::clang::{Clang, Node};
2 use crate::syntax::attrs::OtherAttrs;
3 use crate::syntax::cfg::CfgExpr;
4 use crate::syntax::namespace::Namespace;
5 use crate::syntax::report::Errors;
6 use crate::syntax::{Api, Discriminant, Doc, Enum, EnumRepr, ForeignName, Pair, Variant};
7 use flate2::write::GzDecoder;
8 use memmap::Mmap;
9 use proc_macro2::{Delimiter, Group, Ident, TokenStream};
10 use quote::{format_ident, quote, quote_spanned};
11 use std::env;
12 use std::fmt::{self, Display};
13 use std::fs::File;
14 use std::io::Write;
15 use std::path::PathBuf;
16 use std::str::FromStr;
17 use syn::{parse_quote, Path};
18 
19 const CXX_CLANG_AST: &str = "CXX_CLANG_AST";
20 
load(cx: &mut Errors, apis: &mut [Api])21 pub fn load(cx: &mut Errors, apis: &mut [Api]) {
22     let ref mut variants_from_header = Vec::new();
23     for api in apis {
24         if let Api::Enum(enm) = api {
25             if enm.variants_from_header {
26                 if enm.variants.is_empty() {
27                     variants_from_header.push(enm);
28                 } else {
29                     let span = span_for_enum_error(enm);
30                     cx.error(
31                         span,
32                         "enum with #![variants_from_header] must be written with no explicit variants",
33                     );
34                 }
35             }
36         }
37     }
38 
39     let span = match variants_from_header.get(0) {
40         None => return,
41         Some(enm) => enm.variants_from_header_attr.clone().unwrap(),
42     };
43 
44     let ast_dump_path = match env::var_os(CXX_CLANG_AST) {
45         Some(ast_dump_path) => PathBuf::from(ast_dump_path),
46         None => {
47             let msg = format!(
48                 "environment variable ${} has not been provided",
49                 CXX_CLANG_AST,
50             );
51             return cx.error(span, msg);
52         }
53     };
54 
55     let memmap = File::open(&ast_dump_path).and_then(|file| unsafe { Mmap::map(&file) });
56     let mut gunzipped;
57     let ast_dump_bytes = match match memmap {
58         Ok(ref memmap) => {
59             let is_gzipped = memmap.get(..2) == Some(b"\x1f\x8b");
60             if is_gzipped {
61                 gunzipped = Vec::new();
62                 let decode_result = GzDecoder::new(&mut gunzipped).write_all(memmap);
63                 decode_result.map(|_| gunzipped.as_slice())
64             } else {
65                 Ok(memmap as &[u8])
66             }
67         }
68         Err(error) => Err(error),
69     } {
70         Ok(bytes) => bytes,
71         Err(error) => {
72             let msg = format!("failed to read {}: {}", ast_dump_path.display(), error);
73             return cx.error(span, msg);
74         }
75     };
76 
77     let ref root: Node = match serde_json::from_slice(ast_dump_bytes) {
78         Ok(root) => root,
79         Err(error) => {
80             let msg = format!("failed to read {}: {}", ast_dump_path.display(), error);
81             return cx.error(span, msg);
82         }
83     };
84 
85     let ref mut namespace = Vec::new();
86     traverse(cx, root, namespace, variants_from_header, None);
87 
88     for enm in variants_from_header {
89         if enm.variants.is_empty() {
90             let span = &enm.variants_from_header_attr;
91             let name = CxxName(&enm.name);
92             let msg = format!("failed to find any C++ definition of enum {}", name);
93             cx.error(span, msg);
94         }
95     }
96 }
97 
traverse<'a>( cx: &mut Errors, node: &'a Node, namespace: &mut Vec<&'a str>, variants_from_header: &mut [&mut Enum], mut idx: Option<usize>, )98 fn traverse<'a>(
99     cx: &mut Errors,
100     node: &'a Node,
101     namespace: &mut Vec<&'a str>,
102     variants_from_header: &mut [&mut Enum],
103     mut idx: Option<usize>,
104 ) {
105     match &node.kind {
106         Clang::NamespaceDecl(decl) => {
107             let name = match &decl.name {
108                 Some(name) => name,
109                 // Can ignore enums inside an anonymous namespace.
110                 None => return,
111             };
112             namespace.push(name);
113             idx = None;
114         }
115         Clang::EnumDecl(decl) => {
116             let name = match &decl.name {
117                 Some(name) => name,
118                 None => return,
119             };
120             idx = None;
121             for (i, enm) in variants_from_header.iter_mut().enumerate() {
122                 if enm.name.cxx == **name && enm.name.namespace.iter().eq(&*namespace) {
123                     if !enm.variants.is_empty() {
124                         let span = &enm.variants_from_header_attr;
125                         let qual_name = CxxName(&enm.name);
126                         let msg = format!("found multiple C++ definitions of enum {}", qual_name);
127                         cx.error(span, msg);
128                         return;
129                     }
130                     let fixed_underlying_type = match &decl.fixed_underlying_type {
131                         Some(fixed_underlying_type) => fixed_underlying_type,
132                         None => {
133                             let span = &enm.variants_from_header_attr;
134                             let name = &enm.name.cxx;
135                             let qual_name = CxxName(&enm.name);
136                             let msg = format!(
137                                 "implicit implementation-defined repr for enum {} is not supported yet; consider changing its C++ definition to `enum {}: int {{...}}",
138                                 qual_name, name,
139                             );
140                             cx.error(span, msg);
141                             return;
142                         }
143                     };
144                     let repr = translate_qual_type(
145                         cx,
146                         enm,
147                         fixed_underlying_type
148                             .desugared_qual_type
149                             .as_ref()
150                             .unwrap_or(&fixed_underlying_type.qual_type),
151                     );
152                     enm.repr = EnumRepr::Foreign { rust_type: repr };
153                     idx = Some(i);
154                     break;
155                 }
156             }
157             if idx.is_none() {
158                 return;
159             }
160         }
161         Clang::EnumConstantDecl(decl) => {
162             if let Some(idx) = idx {
163                 let enm = &mut *variants_from_header[idx];
164                 let span = enm
165                     .variants_from_header_attr
166                     .as_ref()
167                     .unwrap()
168                     .path
169                     .get_ident()
170                     .unwrap()
171                     .span();
172                 let cxx_name = match ForeignName::parse(&decl.name, span) {
173                     Ok(foreign_name) => foreign_name,
174                     Err(_) => {
175                         let span = &enm.variants_from_header_attr;
176                         let msg = format!("unsupported C++ variant name: {}", decl.name);
177                         return cx.error(span, msg);
178                     }
179                 };
180                 let rust_name: Ident = match syn::parse_str(&decl.name) {
181                     Ok(ident) => ident,
182                     Err(_) => format_ident!("__Variant{}", enm.variants.len()),
183                 };
184                 let discriminant = match discriminant_value(&node.inner) {
185                     ParsedDiscriminant::Constant(discriminant) => discriminant,
186                     ParsedDiscriminant::Successor => match enm.variants.last() {
187                         None => Discriminant::zero(),
188                         Some(last) => match last.discriminant.checked_succ() {
189                             Some(discriminant) => discriminant,
190                             None => {
191                                 let span = &enm.variants_from_header_attr;
192                                 let msg = format!(
193                                     "overflow processing discriminant value for variant: {}",
194                                     decl.name,
195                                 );
196                                 return cx.error(span, msg);
197                             }
198                         },
199                     },
200                     ParsedDiscriminant::Fail => {
201                         let span = &enm.variants_from_header_attr;
202                         let msg = format!(
203                             "failed to obtain discriminant value for variant: {}",
204                             decl.name,
205                         );
206                         cx.error(span, msg);
207                         Discriminant::zero()
208                     }
209                 };
210                 enm.variants.push(Variant {
211                     cfg: CfgExpr::Unconditional,
212                     doc: Doc::new(),
213                     attrs: OtherAttrs::none(),
214                     name: Pair {
215                         namespace: Namespace::ROOT,
216                         cxx: cxx_name,
217                         rust: rust_name,
218                     },
219                     discriminant,
220                     expr: None,
221                 });
222             }
223         }
224         _ => {}
225     }
226     for inner in &node.inner {
227         traverse(cx, inner, namespace, variants_from_header, idx);
228     }
229     if let Clang::NamespaceDecl(_) = &node.kind {
230         let _ = namespace.pop().unwrap();
231     }
232 }
233 
translate_qual_type(cx: &mut Errors, enm: &Enum, qual_type: &str) -> Path234 fn translate_qual_type(cx: &mut Errors, enm: &Enum, qual_type: &str) -> Path {
235     let rust_std_name = match qual_type {
236         "char" => "c_char",
237         "int" => "c_int",
238         "long" => "c_long",
239         "long long" => "c_longlong",
240         "signed char" => "c_schar",
241         "short" => "c_short",
242         "unsigned char" => "c_uchar",
243         "unsigned int" => "c_uint",
244         "unsigned long" => "c_ulong",
245         "unsigned long long" => "c_ulonglong",
246         "unsigned short" => "c_ushort",
247         unsupported => {
248             let span = &enm.variants_from_header_attr;
249             let qual_name = CxxName(&enm.name);
250             let msg = format!(
251                 "unsupported underlying type for {}: {}",
252                 qual_name, unsupported,
253             );
254             cx.error(span, msg);
255             "c_int"
256         }
257     };
258     let span = enm
259         .variants_from_header_attr
260         .as_ref()
261         .unwrap()
262         .path
263         .get_ident()
264         .unwrap()
265         .span();
266     let ident = Ident::new(rust_std_name, span);
267     let path = quote_spanned!(span=> ::cxx::core::ffi::#ident);
268     parse_quote!(#path)
269 }
270 
271 enum ParsedDiscriminant {
272     Constant(Discriminant),
273     Successor,
274     Fail,
275 }
276 
discriminant_value(mut clang: &[Node]) -> ParsedDiscriminant277 fn discriminant_value(mut clang: &[Node]) -> ParsedDiscriminant {
278     if clang.is_empty() {
279         // No discriminant expression provided; use successor of previous
280         // descriminant.
281         return ParsedDiscriminant::Successor;
282     }
283 
284     loop {
285         if clang.len() != 1 {
286             return ParsedDiscriminant::Fail;
287         }
288 
289         let node = &clang[0];
290         match &node.kind {
291             Clang::ImplicitCastExpr => clang = &node.inner,
292             Clang::ConstantExpr(expr) => match Discriminant::from_str(&expr.value) {
293                 Ok(discriminant) => return ParsedDiscriminant::Constant(discriminant),
294                 Err(_) => return ParsedDiscriminant::Fail,
295             },
296             _ => return ParsedDiscriminant::Fail,
297         }
298     }
299 }
300 
span_for_enum_error(enm: &Enum) -> TokenStream301 fn span_for_enum_error(enm: &Enum) -> TokenStream {
302     let enum_token = enm.enum_token;
303     let mut brace_token = Group::new(Delimiter::Brace, TokenStream::new());
304     brace_token.set_span(enm.brace_token.span);
305     quote!(#enum_token #brace_token)
306 }
307 
308 struct CxxName<'a>(&'a Pair);
309 
310 impl<'a> Display for CxxName<'a> {
fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result311     fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
312         for namespace in &self.0.namespace {
313             write!(formatter, "{}::", namespace)?;
314         }
315         write!(formatter, "{}", self.0.cxx)
316     }
317 }
318