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