• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 use std::{borrow::Cow, ffi::CStr, io, os::raw::c_void, ptr};
2 
3 use thiserror::Error;
4 
5 use crate::{
6     sys::{JavaVMInitArgs, JavaVMOption},
7     JNIVersion,
8 };
9 
10 use cfg_if::cfg_if;
11 
12 mod char_encoding_generic;
13 
14 #[cfg(windows)]
15 mod char_encoding_windows;
16 
17 /// Errors that can occur when invoking a [`JavaVM`](super::vm::JavaVM) with the
18 /// [Invocation API](https://docs.oracle.com/en/java/javase/12/docs/specs/jni/invocation.html).
19 #[derive(Debug, Error)]
20 #[non_exhaustive]
21 pub enum JvmError {
22     /// [`InitArgsBuilder::option`] or [`InitArgsBuilder::try_option`] was used, but the supplied
23     /// string contains a U+0000 code point (except at the end).
24     ///
25     /// This error is not raised if the string has a single U+0000 code point at the end.
26     ///
27     /// [`InitArgsBuilder::option_encoded`] never raises this error.
28     #[error("internal null in option: {0}")]
29     NullOptString(String),
30 
31     /// [`InitArgsBuilder::option`] or [`InitArgsBuilder::try_option`] was used, but the option
32     /// string is too long.
33     ///
34     /// Currently, this error only occurs on Windows, where string length is limited to 1MB to
35     /// avoid overflow in [`WideCharToMultiByte`] (see [discussion]). String length is not
36     /// currently limited (other than by available memory) on other platforms.
37     ///
38     /// [`InitArgsBuilder::option_encoded`] never raises this error, regardless of platform.
39     ///
40     /// [discussion]: https://github.com/jni-rs/jni-rs/pull/414
41     /// [`WideCharToMultiByte`]: https://learn.microsoft.com/en-us/windows/win32/api/stringapiset/nf-stringapiset-widechartomultibyte
42     #[error("option is too long: {opt_string}")]
43     #[non_exhaustive]
44     OptStringTooLong {
45         /// The option string.
46         opt_string: String,
47     },
48 
49     /// [`InitArgsBuilder::option`] or [`InitArgsBuilder::try_option`] was used, but the option
50     /// string is not representable in the platform default character encoding.
51     ///
52     /// [`InitArgsBuilder::option_encoded`] never raises this error.
53     #[error(
54         "option {opt_string:?} is not representable in the platform default character encoding"
55     )]
56     #[non_exhaustive]
57     OptStringNotRepresentable {
58         /// The option string.
59         opt_string: String,
60     },
61 
62     /// [`InitArgsBuilder::option`] or [`InitArgsBuilder::try_option`] was used, but the platform
63     /// reported an error converting it to its default character encoding.
64     ///
65     /// [`InitArgsBuilder::option_encoded`] never raises this error.
66     #[error("couldn't convert option {opt_string:?} to the platform default character encoding: {error}")]
67     #[non_exhaustive]
68     OptStringTranscodeFailure {
69         /// The option string.
70         opt_string: String,
71 
72         /// The error reported by the platform's character encoding conversion routine.
73         #[source]
74         error: io::Error,
75     },
76 }
77 
78 impl JvmError {
79     /// Returns the JVM option that caused the error, if it was caused by one.
opt_string(&self) -> Option<&str>80     pub fn opt_string(&self) -> Option<&str> {
81         match self {
82             Self::NullOptString(opt_string) => Some(opt_string),
83             Self::OptStringTooLong { opt_string, .. } => Some(opt_string),
84             Self::OptStringNotRepresentable { opt_string, .. } => Some(opt_string),
85             Self::OptStringTranscodeFailure { opt_string, .. } => Some(opt_string),
86         }
87         .map(String::as_str)
88     }
89 
90     #[cfg(all(test, windows))]
opt_string_mut(&mut self) -> Option<&mut String>91     fn opt_string_mut(&mut self) -> Option<&mut String> {
92         match self {
93             Self::NullOptString(opt_string) => Some(opt_string),
94             Self::OptStringTooLong { opt_string, .. } => Some(opt_string),
95             Self::OptStringNotRepresentable { opt_string, .. } => Some(opt_string),
96             Self::OptStringTranscodeFailure { opt_string, .. } => Some(opt_string),
97         }
98     }
99 }
100 
101 const SPECIAL_OPTIONS: &[&str] = &["vfprintf", "abort", "exit"];
102 
103 const SPECIAL_OPTIONS_C: &[&CStr] = unsafe {
104     &[
105         CStr::from_bytes_with_nul_unchecked(b"vfprintf\0"),
106         CStr::from_bytes_with_nul_unchecked(b"abort\0"),
107         CStr::from_bytes_with_nul_unchecked(b"exit\0"),
108     ]
109 };
110 
111 /// Builder for JavaVM InitArgs.
112 ///
113 /// *This API requires "invocation" feature to be enabled,
114 /// see ["Launching JVM from Rust"](struct.JavaVM.html#launching-jvm-from-rust).*
115 #[derive(Debug)]
116 pub struct InitArgsBuilder<'a> {
117     opts: Result<Vec<Cow<'a, CStr>>, JvmError>,
118     ignore_unrecognized: bool,
119     version: JNIVersion,
120 }
121 
122 impl<'a> Default for InitArgsBuilder<'a> {
default() -> Self123     fn default() -> Self {
124         InitArgsBuilder {
125             opts: Ok(vec![]),
126             ignore_unrecognized: false,
127             version: JNIVersion::V8,
128         }
129     }
130 }
131 
132 impl<'a> InitArgsBuilder<'a> {
133     /// Create a new default InitArgsBuilder
new() -> Self134     pub fn new() -> Self {
135         Default::default()
136     }
137 
138     /// Adds a JVM option, such as `-Djavax.net.debug=all`.
139     ///
140     /// See [the JNI specification][jni-options] for details on which options are accepted.
141     ///
142     /// The `vfprintf`, `abort`, and `exit` options are unsupported at this time. Setting one of
143     /// these options has no effect.
144     ///
145     /// The option must not contain any U+0000 code points except one at the end. A U+0000 code
146     /// point at the end is not required, but on platforms where UTF-8 is the default character
147     /// encoding, including one U+0000 code point at the end will make this method run slightly
148     /// faster.
149     ///
150     /// # Errors
151     ///
152     /// This method can fail if:
153     ///
154     /// * `opt_string` contains a U+0000 code point before the end.
155     /// * `opt_string` cannot be represented in the platform default character encoding.
156     /// * the platform's character encoding conversion API reports some other error.
157     /// * `opt_string` is too long. (In the current implementation, the maximum allowed length is
158     ///   1048576 bytes on Windows. There is currently no limit on other platforms.)
159     ///
160     /// Errors raised by this method are deferred. If an error occurs, it is returned from
161     /// [`InitArgsBuilder::build`] instead.
162     ///
163     /// [jni-options]: https://docs.oracle.com/en/java/javase/11/docs/specs/jni/invocation.html#jni_createjavavm
option(mut self, opt_string: impl AsRef<str> + Into<Cow<'a, str>>) -> Self164     pub fn option(mut self, opt_string: impl AsRef<str> + Into<Cow<'a, str>>) -> Self {
165         if let Err(error) = self.try_option(opt_string) {
166             self.opts = Err(error);
167         }
168 
169         self
170     }
171 
172     /// Adds a JVM option, such as `-Djavax.net.debug=all`. Returns an error immediately upon
173     /// failure.
174     ///
175     /// This is an alternative to [`InitArgsBuilder::option`] that does not defer errors. See
176     /// below for details.
177     ///
178     /// See [the JNI specification][jni-options] for details on which options are accepted.
179     ///
180     /// The `vfprintf`, `abort`, and `exit` options are unsupported at this time. Setting one of
181     /// these options has no effect.
182     ///
183     /// The option must not contain any U+0000 code points except one at the end. A U+0000 code
184     /// point at the end is not required, but on platforms where UTF-8 is the default character
185     /// encoding, including one U+0000 code point at the end will make this method run slightly
186     /// faster.
187     ///
188     /// # Errors
189     ///
190     /// This method can fail if:
191     ///
192     /// * `opt_string` contains a U+0000 code point before the end.
193     /// * `opt_string` cannot be represented in the platform default character encoding.
194     /// * the platform's character encoding conversion API reports some other error.
195     /// * `opt_string` is too long. (In the current implementation, the maximum allowed length is
196     ///   1048576 bytes on Windows. There is currently no limit on other platforms.)
197     ///
198     /// Unlike the `option` method, this one does not defer errors. If the `opt_string` cannot be
199     /// used, then this method returns `Err` and `self` is not changed. If there is already a
200     /// deferred error, however, then this method does nothing.
201     ///
202     /// [jni-options]: https://docs.oracle.com/en/java/javase/11/docs/specs/jni/invocation.html#jni_createjavavm
try_option(&mut self, opt_string: impl Into<Cow<'a, str>>) -> Result<(), JvmError>203     pub fn try_option(&mut self, opt_string: impl Into<Cow<'a, str>>) -> Result<(), JvmError> {
204         let opt_string = opt_string.into();
205 
206         // If there is already a deferred error, do nothing.
207         let opts = match &mut self.opts {
208             Ok(ok) => ok,
209             Err(_) => return Ok(()),
210         };
211 
212         // If the option is the empty string, then skip everything else and pass a constant empty
213         // C string. This isn't just an optimization; Win32 `WideCharToMultiByte` will **fail** if
214         // passed an empty string, so we have to do this check first.
215         if matches!(opt_string.as_ref(), "" | "\0") {
216             opts.push(Cow::Borrowed(unsafe {
217                 // Safety: This string not only is null-terminated without any interior null bytes,
218                 // it's nothing but a null terminator.
219                 CStr::from_bytes_with_nul_unchecked(b"\0")
220             }));
221             return Ok(());
222         }
223         // If this is one of the special options, do nothing.
224         else if SPECIAL_OPTIONS.contains(&&*opt_string) {
225             return Ok(());
226         }
227 
228         let encoded: Cow<'a, CStr> = {
229             cfg_if! {
230                 if #[cfg(windows)] {
231                     char_encoding_windows::str_to_cstr_win32_default_codepage(opt_string)?
232                 }
233                 else {
234                     // Assume UTF-8 on all other platforms.
235                     char_encoding_generic::utf8_to_cstr(opt_string)?
236                 }
237             }
238         };
239 
240         opts.push(encoded);
241         Ok(())
242     }
243 
244     /// Adds a JVM option, such as `-Djavax.net.debug=all`. The option must be a `CStr` encoded in
245     /// the platform default character encoding.
246     ///
247     /// This is an alternative to [`InitArgsBuilder::option`] that does not do any encoding. This
248     /// method is not `unsafe` as it cannot cause undefined behavior, but the option will be
249     /// garbled (that is, become [mojibake](https://en.wikipedia.org/wiki/Mojibake)) if not
250     /// encoded correctly.
251     ///
252     /// See [the JNI specification][jni-options] for details on which options are accepted.
253     ///
254     /// The `vfprintf`, `abort`, and `exit` options are unsupported at this time. Setting one of
255     /// these options has no effect.
256     ///
257     /// This method does not fail, and will neither return nor defer an error.
258     ///
259     /// [jni-options]: https://docs.oracle.com/en/java/javase/11/docs/specs/jni/invocation.html#jni_createjavavm
option_encoded(mut self, opt_string: impl Into<Cow<'a, CStr>>) -> Self260     pub fn option_encoded(mut self, opt_string: impl Into<Cow<'a, CStr>>) -> Self {
261         let opt_string = opt_string.into();
262 
263         // If there is already a deferred error, do nothing.
264         let opts = match &mut self.opts {
265             Ok(ok) => ok,
266             Err(_) => return self,
267         };
268 
269         // If this is one of the special options, do nothing.
270         if SPECIAL_OPTIONS_C.contains(&&*opt_string) {
271             return self;
272         }
273 
274         // Add the option.
275         opts.push(opt_string);
276 
277         self
278     }
279 
280     /// Set JNI version for the init args
281     ///
282     /// Default: V8
version(self, version: JNIVersion) -> Self283     pub fn version(self, version: JNIVersion) -> Self {
284         let mut s = self;
285         s.version = version;
286         s
287     }
288 
289     /// Set the `ignoreUnrecognized` init arg flag
290     ///
291     /// If ignoreUnrecognized is true, JavaVM::new ignores all unrecognized option strings that
292     /// begin with "-X" or "_". If ignoreUnrecognized is false, JavaVM::new returns Err as soon as
293     /// it encounters any unrecognized option strings.
294     ///
295     /// Default: `false`
ignore_unrecognized(self, ignore: bool) -> Self296     pub fn ignore_unrecognized(self, ignore: bool) -> Self {
297         let mut s = self;
298         s.ignore_unrecognized = ignore;
299         s
300     }
301 
302     /// Build the `InitArgs`
303     ///
304     /// # Errors
305     ///
306     /// If a call to [`InitArgsBuilder::option`] caused a deferred error, it is returned from this
307     /// method.
build(self) -> Result<InitArgs<'a>, JvmError>308     pub fn build(self) -> Result<InitArgs<'a>, JvmError> {
309         let opt_strings = self.opts?;
310 
311         let opts: Vec<JavaVMOption> = opt_strings
312             .iter()
313             .map(|opt_string| JavaVMOption {
314                 optionString: opt_string.as_ptr() as _,
315                 extraInfo: ptr::null_mut(),
316             })
317             .collect();
318 
319         Ok(InitArgs {
320             inner: JavaVMInitArgs {
321                 version: self.version.into(),
322                 ignoreUnrecognized: self.ignore_unrecognized as _,
323                 options: opts.as_ptr() as _,
324                 nOptions: opts.len() as _,
325             },
326             _opts: opts,
327             _opt_strings: opt_strings,
328         })
329     }
330 
331     /// Returns collected options.
332     ///
333     /// If a call to [`InitArgsBuilder::option`] caused a deferred error, then this method returns
334     /// a reference to that error.
options(&self) -> Result<&[Cow<'a, CStr>], &JvmError>335     pub fn options(&self) -> Result<&[Cow<'a, CStr>], &JvmError> {
336         self.opts.as_ref().map(Vec::as_slice)
337     }
338 }
339 
340 /// JavaVM InitArgs.
341 ///
342 /// *This API requires "invocation" feature to be enabled,
343 /// see ["Launching JVM from Rust"](struct.JavaVM.html#launching-jvm-from-rust).*
344 pub struct InitArgs<'a> {
345     inner: JavaVMInitArgs,
346 
347     // `JavaVMOption` structures are stored here. The JVM accesses this `Vec`'s contents through a
348     // raw pointer.
349     _opts: Vec<JavaVMOption>,
350 
351     // Option strings are stored here. This ensures that any that are owned aren't dropped before
352     // the JVM is finished with them.
353     _opt_strings: Vec<Cow<'a, CStr>>,
354 }
355 
356 impl<'a> InitArgs<'a> {
inner_ptr(&self) -> *mut c_void357     pub(crate) fn inner_ptr(&self) -> *mut c_void {
358         &self.inner as *const _ as _
359     }
360 }
361