jni/wrapper/java_vm/
init_args.rs

1use std::{borrow::Cow, ffi::CStr, io, os::raw::c_void, ptr};
2
3use thiserror::Error;
4
5use crate::{
6    sys::{JavaVMInitArgs, JavaVMOption},
7    JNIVersion,
8};
9
10use cfg_if::cfg_if;
11
12mod char_encoding_generic;
13
14#[cfg(windows)]
15mod 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]
21pub 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
78impl JvmError {
79    /// Returns the JVM option that caused the error, if it was caused by one.
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))]
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
101const SPECIAL_OPTIONS: &[&str] = &["vfprintf", "abort", "exit"];
102
103const 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)]
116pub struct InitArgsBuilder<'a> {
117    opts: Result<Vec<Cow<'a, CStr>>, JvmError>,
118    ignore_unrecognized: bool,
119    version: JNIVersion,
120}
121
122impl<'a> Default for InitArgsBuilder<'a> {
123    fn default() -> Self {
124        InitArgsBuilder {
125            opts: Ok(vec![]),
126            ignore_unrecognized: false,
127            version: JNIVersion::V8,
128        }
129    }
130}
131
132impl<'a> InitArgsBuilder<'a> {
133    /// Create a new default InitArgsBuilder
134    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
164    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
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
260    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
283    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`
296    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.
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.
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).*
344pub 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
356impl<'a> InitArgs<'a> {
357    pub(crate) fn inner_ptr(&self) -> *mut c_void {
358        &self.inner as *const _ as _
359    }
360}