1 use std::{fmt, str::FromStr};
2 
3 use combine::{
4     between, many, many1, parser, satisfy, token, ParseError, Parser, StdParseResult, Stream,
5 };
6 
7 use crate::errors::*;
8 
9 /// A primitive java type. These are the things that can be represented without
10 /// an object.
11 #[allow(missing_docs)]
12 #[derive(Eq, PartialEq, Debug, Clone, Copy)]
13 pub enum Primitive {
14     Boolean, // Z
15     Byte,    // B
16     Char,    // C
17     Double,  // D
18     Float,   // F
19     Int,     // I
20     Long,    // J
21     Short,   // S
22     Void,    // V
23 }
24 
25 impl fmt::Display for Primitive {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result26     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
27         match *self {
28             Primitive::Boolean => write!(f, "Z"),
29             Primitive::Byte => write!(f, "B"),
30             Primitive::Char => write!(f, "C"),
31             Primitive::Double => write!(f, "D"),
32             Primitive::Float => write!(f, "F"),
33             Primitive::Int => write!(f, "I"),
34             Primitive::Long => write!(f, "J"),
35             Primitive::Short => write!(f, "S"),
36             Primitive::Void => write!(f, "V"),
37         }
38     }
39 }
40 
41 /// Enum representing any java type in addition to method signatures.
42 #[allow(missing_docs)]
43 #[derive(Eq, PartialEq, Debug, Clone)]
44 pub enum JavaType {
45     Primitive(Primitive),
46     Object(String),
47     Array(Box<JavaType>),
48     Method(Box<TypeSignature>),
49 }
50 
51 impl FromStr for JavaType {
52     type Err = Error;
53 
from_str(s: &str) -> std::result::Result<Self, Self::Err>54     fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
55         parser(parse_type)
56             .parse(s)
57             .map(|res| res.0)
58             .map_err(|e| Error::ParseFailed(e, s.to_owned()))
59     }
60 }
61 
62 impl fmt::Display for JavaType {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result63     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
64         match *self {
65             JavaType::Primitive(ref ty) => ty.fmt(f),
66             JavaType::Object(ref name) => write!(f, "L{name};"),
67             JavaType::Array(ref ty) => write!(f, "[{ty}"),
68             JavaType::Method(ref m) => m.fmt(f),
69         }
70     }
71 }
72 
73 /// Enum representing any java type that may be used as a return value
74 ///
75 /// This type intentionally avoids capturing any heap allocated types (to avoid
76 /// allocations while making JNI method calls) and so it doesn't fully qualify
77 /// the object or array types with a String like `JavaType::Object` does.
78 #[allow(missing_docs)]
79 #[derive(Eq, PartialEq, Debug, Clone)]
80 pub enum ReturnType {
81     Primitive(Primitive),
82     Object,
83     Array,
84 }
85 
86 impl FromStr for ReturnType {
87     type Err = Error;
88 
from_str(s: &str) -> std::result::Result<Self, Self::Err>89     fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
90         parser(parse_return)
91             .parse(s)
92             .map(|res| res.0)
93             .map_err(|e| Error::ParseFailed(e, s.to_owned()))
94     }
95 }
96 
97 impl fmt::Display for ReturnType {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result98     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
99         match *self {
100             ReturnType::Primitive(ref ty) => ty.fmt(f),
101             ReturnType::Object => write!(f, "L;"),
102             ReturnType::Array => write!(f, "["),
103         }
104     }
105 }
106 
107 /// A method type signature. This is the structure representation of something
108 /// like `(Ljava/lang/String;)Z`. Used by the `call_(object|static)_method`
109 /// functions on jnienv to ensure safety.
110 #[allow(missing_docs)]
111 #[derive(Eq, PartialEq, Debug, Clone)]
112 pub struct TypeSignature {
113     pub args: Vec<JavaType>,
114     pub ret: ReturnType,
115 }
116 
117 impl TypeSignature {
118     /// Parse a signature string into a TypeSignature enum.
119     // Clippy suggests implementing `FromStr` or renaming it which is not possible in our case.
120     #[allow(clippy::should_implement_trait)]
from_str<S: AsRef<str>>(s: S) -> Result<TypeSignature>121     pub fn from_str<S: AsRef<str>>(s: S) -> Result<TypeSignature> {
122         Ok(match parser(parse_sig).parse(s.as_ref()).map(|res| res.0) {
123             Ok(JavaType::Method(sig)) => *sig,
124             Err(e) => return Err(Error::ParseFailed(e, s.as_ref().to_owned())),
125             _ => unreachable!(),
126         })
127     }
128 }
129 
130 impl fmt::Display for TypeSignature {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result131     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
132         write!(f, "(")?;
133         for a in &self.args {
134             write!(f, "{a}")?;
135         }
136         write!(f, ")")?;
137         write!(f, "{}", self.ret)?;
138         Ok(())
139     }
140 }
141 
parse_primitive<S: Stream<Token = char>>(input: &mut S) -> StdParseResult<Primitive, S> where S::Error: ParseError<char, S::Range, S::Position>,142 fn parse_primitive<S: Stream<Token = char>>(input: &mut S) -> StdParseResult<Primitive, S>
143 where
144     S::Error: ParseError<char, S::Range, S::Position>,
145 {
146     let boolean = token('Z').map(|_| Primitive::Boolean);
147     let byte = token('B').map(|_| Primitive::Byte);
148     let char_type = token('C').map(|_| Primitive::Char);
149     let double = token('D').map(|_| Primitive::Double);
150     let float = token('F').map(|_| Primitive::Float);
151     let int = token('I').map(|_| Primitive::Int);
152     let long = token('J').map(|_| Primitive::Long);
153     let short = token('S').map(|_| Primitive::Short);
154     let void = token('V').map(|_| Primitive::Void);
155 
156     (boolean
157         .or(byte)
158         .or(char_type)
159         .or(double)
160         .or(float)
161         .or(int)
162         .or(long)
163         .or(short)
164         .or(void))
165     .parse_stream(input)
166     .into()
167 }
168 
parse_array<S: Stream<Token = char>>(input: &mut S) -> StdParseResult<JavaType, S> where S::Error: ParseError<char, S::Range, S::Position>,169 fn parse_array<S: Stream<Token = char>>(input: &mut S) -> StdParseResult<JavaType, S>
170 where
171     S::Error: ParseError<char, S::Range, S::Position>,
172 {
173     let marker = token('[');
174     (marker, parser(parse_type))
175         .map(|(_, ty)| JavaType::Array(Box::new(ty)))
176         .parse_stream(input)
177         .into()
178 }
179 
parse_object<S: Stream<Token = char>>(input: &mut S) -> StdParseResult<JavaType, S> where S::Error: ParseError<char, S::Range, S::Position>,180 fn parse_object<S: Stream<Token = char>>(input: &mut S) -> StdParseResult<JavaType, S>
181 where
182     S::Error: ParseError<char, S::Range, S::Position>,
183 {
184     let marker = token('L');
185     let end = token(';');
186     let obj = between(marker, end, many1(satisfy(|c| c != ';')));
187 
188     obj.map(JavaType::Object).parse_stream(input).into()
189 }
190 
parse_type<S: Stream<Token = char>>(input: &mut S) -> StdParseResult<JavaType, S> where S::Error: ParseError<char, S::Range, S::Position>,191 fn parse_type<S: Stream<Token = char>>(input: &mut S) -> StdParseResult<JavaType, S>
192 where
193     S::Error: ParseError<char, S::Range, S::Position>,
194 {
195     parser(parse_primitive)
196         .map(JavaType::Primitive)
197         .or(parser(parse_array))
198         .or(parser(parse_object))
199         .or(parser(parse_sig))
200         .parse_stream(input)
201         .into()
202 }
203 
parse_return<S: Stream<Token = char>>(input: &mut S) -> StdParseResult<ReturnType, S> where S::Error: ParseError<char, S::Range, S::Position>,204 fn parse_return<S: Stream<Token = char>>(input: &mut S) -> StdParseResult<ReturnType, S>
205 where
206     S::Error: ParseError<char, S::Range, S::Position>,
207 {
208     parser(parse_primitive)
209         .map(ReturnType::Primitive)
210         .or(parser(parse_array).map(|_| ReturnType::Array))
211         .or(parser(parse_object).map(|_| ReturnType::Object))
212         .parse_stream(input)
213         .into()
214 }
215 
parse_args<S: Stream<Token = char>>(input: &mut S) -> StdParseResult<Vec<JavaType>, S> where S::Error: ParseError<char, S::Range, S::Position>,216 fn parse_args<S: Stream<Token = char>>(input: &mut S) -> StdParseResult<Vec<JavaType>, S>
217 where
218     S::Error: ParseError<char, S::Range, S::Position>,
219 {
220     between(token('('), token(')'), many(parser(parse_type)))
221         .parse_stream(input)
222         .into()
223 }
224 
parse_sig<S: Stream<Token = char>>(input: &mut S) -> StdParseResult<JavaType, S> where S::Error: ParseError<char, S::Range, S::Position>,225 fn parse_sig<S: Stream<Token = char>>(input: &mut S) -> StdParseResult<JavaType, S>
226 where
227     S::Error: ParseError<char, S::Range, S::Position>,
228 {
229     (parser(parse_args), parser(parse_return))
230         .map(|(a, r)| TypeSignature { args: a, ret: r })
231         .map(|sig| JavaType::Method(Box::new(sig)))
232         .parse_stream(input)
233         .into()
234 }
235 
236 #[cfg(test)]
237 mod test {
238     use super::*;
239 
240     #[test]
test_parser()241     fn test_parser() {
242         let inputs = [
243             "(Ljava/lang/String;I)V",
244             "[Lherp;",
245             // fails because the return type does not contain the class name: "(IBVZ)L;"
246             // "(IBVZ)Ljava/lang/String;",
247         ];
248 
249         for each in inputs.iter() {
250             let res = JavaType::from_str(each).unwrap();
251             println!("{res:#?}");
252             let s = format!("{res}");
253             assert_eq!(s, *each);
254             let res2 = JavaType::from_str(each).unwrap();
255             println!("{res2:#?}");
256             assert_eq!(res2, res);
257         }
258     }
259 
260     #[test]
test_parser_invalid_signature()261     fn test_parser_invalid_signature() {
262         let signature = "()Ljava/lang/List"; // no semicolon
263         let res = JavaType::from_str(signature);
264 
265         match res {
266             Ok(any) => {
267                 panic!("Unexpected result: {}", any);
268             }
269             Err(err) => {
270                 assert!(err.to_string().contains("input: ()Ljava/lang/List"));
271             }
272         }
273     }
274 }
275