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