Skip to content

Commit 16c4303

Browse files
committed
runtime: improve UnsupportedClassVersionError message
1 parent 2641fbc commit 16c4303

File tree

6 files changed

+93
-75
lines changed

6 files changed

+93
-75
lines changed

runtime/src/classpath/loader/mod.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -488,7 +488,12 @@ impl ClassLoader {
488488
// 2.2. Otherwise, if the purported representation is not of a supported major or
489489
// minor version (§4.1), derivation throws an UnsupportedClassVersionError.
490490
if !SUPPORTED_MAJOR_VERSION_RANGE.contains(&(classfile.major_version as u1)) {
491-
throw!(@DEFER UnsupportedClassVersionError);
491+
throw!(@DEFER UnsupportedClassVersionError,
492+
"{name_str} has been compiled by a more recent version of the Java Runtime (class file version {}.{}), this version of the Java Runtime only recognizes class file versions up to {}.0",
493+
classfile.major_version,
494+
classfile.minor_version,
495+
SUPPORTED_MAJOR_VERSION_RANGE.end()
496+
);
492497
}
493498

494499
// 2.3. Otherwise, if the purported representation does not actually represent a class or

runtime/src/initialization.rs

Lines changed: 30 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use crate::objects::instance::class::ClassInstance;
77
use crate::objects::reference::Reference;
88
use crate::options::JvmOptions;
99
use crate::symbols::sym;
10-
use crate::thread::exceptions::Throws;
10+
use crate::thread::exceptions::{Exception, Throws};
1111
use crate::thread::{JavaThread, JavaThreadBuilder};
1212
use crate::{classes, java_call};
1313

@@ -17,28 +17,32 @@ use jni::error::JniError;
1717
use jni::java_vm::JavaVm;
1818
use jni::sys::{JNI_OK, JavaVMInitArgs, jint};
1919

20+
/// Errors that can occur during VM initialization.
21+
pub enum InitializationError {
22+
/// An exception was thrown before its class and/or the main thread were initialized.
23+
///
24+
/// This exception can *not* be thrown normally. The VM is not in a valid state to continue
25+
/// executing Java code.
26+
EarlyExceptionThrown(Exception),
27+
/// Some other error occurred.
28+
///
29+
/// In the case of [`JniError::ExceptionThrown`], the thread will be available to print the
30+
/// exception if necessary.
31+
Other(JniError),
32+
}
33+
2034
/// Creates and initializes the Java VM
2135
///
2236
/// # Errors
2337
///
24-
/// Errors come in the form ([`JniError`], Option<[`Reference`]>), where the [`Reference`] is the exception thrown.
25-
///
26-
/// There are two error cases:
27-
///
28-
/// * **Before** the creation of the main thread, in which case the exception will be `None` since the
29-
/// VM will not be able to throw any exception.
30-
/// * **After** the creation of the main thread, where the exception *should* be `Some`. In this case,
31-
/// the caller should print the contents of the exception.
32-
pub fn create_java_vm(
33-
args: Option<&JavaVMInitArgs>,
34-
) -> Result<JavaVm, (JniError, Option<Reference>)> {
38+
/// See [`InitializationError`].
39+
pub fn create_java_vm(args: Option<&JavaVMInitArgs>) -> Result<JavaVm, InitializationError> {
3540
let _span = tracing::debug_span!("initialization").entered();
3641
tracing::debug!("Creating Java VM");
3742

3843
let options = match args {
39-
Some(args) => {
40-
unsafe { JvmOptions::load(args) }.map_err(|_| (JniError::InvalidArguments, None))?
41-
},
44+
Some(args) => unsafe { JvmOptions::load(args) }
45+
.map_err(|_| InitializationError::Other(JniError::InvalidArguments))?,
4246
None => JvmOptions::default(),
4347
};
4448

@@ -52,18 +56,7 @@ pub fn create_java_vm(
5256
JavaThread::set_current_thread(thread);
5357
}
5458

55-
if let Err((e, thread_available)) = initialize_thread(JavaThread::current(), true) {
56-
if !thread_available {
57-
return Err((e, None));
58-
}
59-
60-
let Some(exception) = JavaThread::current().take_pending_exception() else {
61-
tracing::warn!("Exception thrown but not set?");
62-
return Err((e, None));
63-
};
64-
65-
return Err((e, Some(exception)));
66-
}
59+
initialize_thread(JavaThread::current(), true)?;
6760

6861
Ok(unsafe { main_java_vm() })
6962
}
@@ -88,28 +81,26 @@ pub fn create_java_vm(
8881
pub(crate) fn initialize_thread(
8982
thread: &'static JavaThread,
9083
do_java_lang_system_init: bool,
91-
) -> Result<(), (JniError, bool)> {
84+
) -> Result<(), InitializationError> {
9285
crate::modules::with_module_lock(Module::create_java_base);
9386

9487
// Load some important classes
95-
if let Throws::Exception(_) = load_global_classes() {
96-
return Err((JniError::ExceptionThrown, false));
88+
if let Throws::Exception(exception) = load_global_classes() {
89+
return Err(InitializationError::EarlyExceptionThrown(exception));
9790
}
9891

9992
init_field_offsets();
10093

10194
// Init some important classes
102-
if let Throws::Exception(_) = initialize_global_classes(thread) {
103-
// An exception was thrown while initializing classes, no thread exists to handle it.
104-
return Err((JniError::ExceptionThrown, false));
95+
if let Throws::Exception(exception) = initialize_global_classes(thread) {
96+
return Err(InitializationError::EarlyExceptionThrown(exception));
10597
}
10698

10799
if !create_thread_object(thread) {
108-
// An exception was thrown, this thread is NOT safe to use.
109-
return Err((JniError::ExceptionThrown, false));
100+
return Err(InitializationError::Other(JniError::ExceptionThrown));
110101
}
111102

112-
// SAFETY: Preconditions filled in `init_field_offsets` & `initialize_global_classes`
103+
// SAFETY: Preconditions filled in `init_field_offsets` && `initialize_global_classes`
113104
unsafe {
114105
misc::UnsafeConstants::init();
115106
}
@@ -118,9 +109,9 @@ pub(crate) fn initialize_thread(
118109
classes::java::lang::invoke::MethodHandle::init_entry_points();
119110

120111
if do_java_lang_system_init {
121-
init_phase_1(thread).map_err(|e| (e, true))?;
122-
init_phase_2(thread).map_err(|e| (e, true))?;
123-
init_phase_3(thread).map_err(|e| (e, true))?;
112+
init_phase_1(thread).map_err(InitializationError::Other)?;
113+
init_phase_2(thread).map_err(InitializationError::Other)?;
114+
init_phase_3(thread).map_err(InitializationError::Other)?;
124115
}
125116

126117
Ok(())

runtime/src/native/jni/invocation_api/mod.rs

Lines changed: 24 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
//!
33
//! Vendors can deliver Java-enabled applications without having to link with the Java VM source code.
44
5+
use crate::initialization::InitializationError;
56
use crate::thread::{JavaThread, JavaThreadBuilder};
67
use crate::{classes, initialization};
78

@@ -98,21 +99,31 @@ pub unsafe extern "system" fn JNI_CreateJavaVM(
9899
let vm;
99100
match initialization::create_java_vm(Some(args)) {
100101
Ok(java_vm) => vm = java_vm,
101-
Err((e, exception)) => {
102-
if let JniError::ExceptionThrown = e {
103-
eprintln!("Error occurred during initialization of VM");
104-
if let Some(exception) = exception {
105-
classes::java::lang::Throwable::print_stack_trace_without_java_system(
106-
exception,
107-
JavaThread::current(),
108-
);
109-
110-
// If a VM was created and initialized to the point that an exception was thrown,
111-
// the entire process just gets aborted like Hotspot.
112-
std::process::abort();
113-
}
102+
// A manually constructed exception was thrown. We can expect that either the exception
103+
// classes aren't loaded or the thread isn't initialized. Either way, we just print the
104+
// little information we have.
105+
Err(InitializationError::EarlyExceptionThrown(exception)) => {
106+
eprintln!("Error occurred during initialization of VM");
107+
eprintln!("{exception}");
108+
109+
// If a VM was created and initialized to the point that an exception was thrown,
110+
// the entire process just gets aborted like Hotspot.
111+
std::process::abort();
112+
},
113+
// Same as above, except we actually have an initialized thread and some classes loaded
114+
Err(InitializationError::Other(JniError::ExceptionThrown)) => {
115+
eprintln!("Error occurred during initialization of VM");
116+
117+
let thread = JavaThread::current();
118+
if let Some(exception) = thread.take_pending_exception() {
119+
classes::java::lang::Throwable::print_stack_trace_without_java_system(
120+
exception, thread,
121+
);
114122
}
115123

124+
std::process::abort();
125+
},
126+
Err(InitializationError::Other(e)) => {
116127
// Otherwise, the error can just be returned to the caller
117128
return e.as_jint();
118129
},

runtime/src/native/mod.rs

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -54,34 +54,34 @@ pub(crate) fn lib_java() -> &'static Library {
5454

5555
pub(crate) mod java {
5656
pub(crate) mod io {
57-
pub(crate) mod FileDescriptor;
5857
pub(crate) mod FileInputStream;
59-
pub(crate) mod FileOutputStream;
60-
pub(crate) mod FileSystem;
6158
pub(crate) mod UnixFileSystem;
59+
pub(crate) mod FileDescriptor;
60+
pub(crate) mod FileSystem;
61+
pub(crate) mod FileOutputStream;
6262
}
6363
pub(crate) mod lang {
6464
pub(crate) mod invoke {
65-
pub(crate) mod MethodHandle;
6665
pub(crate) mod MethodHandleNatives;
66+
pub(crate) mod MethodHandle;
6767
}
6868
pub(crate) mod r#ref {
69-
pub(crate) mod Finalizer;
7069
pub(crate) mod PhantomReference;
7170
pub(crate) mod Reference;
71+
pub(crate) mod Finalizer;
7272
}
7373
pub(crate) mod reflect {
7474
pub(crate) mod Array;
7575
}
76-
pub(crate) mod Class;
77-
pub(crate) mod ClassLoader;
78-
pub(crate) mod Module;
79-
pub(crate) mod Runtime;
80-
pub(crate) mod String;
8176
pub(crate) mod StringBuilder;
77+
pub(crate) mod Runtime;
8278
pub(crate) mod StringUTF16;
83-
pub(crate) mod Thread;
79+
pub(crate) mod Module;
80+
pub(crate) mod ClassLoader;
8481
pub(crate) mod Throwable;
82+
pub(crate) mod Thread;
83+
pub(crate) mod String;
84+
pub(crate) mod Class;
8585
}
8686
pub(crate) mod security {
8787
pub(crate) mod AccessController;
@@ -91,18 +91,18 @@ pub(crate) mod java {
9191
pub(crate) mod jdk {
9292
pub(crate) mod internal {
9393
pub(crate) mod misc {
94-
pub(crate) mod CDS;
9594
pub(crate) mod ScopedMemoryAccess;
96-
pub(crate) mod Signal;
97-
pub(crate) mod Unsafe;
95+
pub(crate) mod CDS;
9896
pub(crate) mod VM;
97+
pub(crate) mod Unsafe;
98+
pub(crate) mod Signal;
9999
}
100100
pub(crate) mod util {
101101
pub(crate) mod SystemProps;
102102
}
103103
pub(crate) mod loader {
104-
pub(crate) mod BootLoader;
105104
pub(crate) mod NativeLibraries;
105+
pub(crate) mod BootLoader;
106106
pub(crate) mod NativeLibrary;
107107
}
108108
pub(crate) mod reflect {
@@ -112,3 +112,4 @@ pub(crate) mod jdk {
112112
}
113113
}
114114
}
115+

runtime/src/objects/class/ptr.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,7 @@ impl ClassPtr {
166166
}
167167
},
168168
Throws::Exception(e) => {
169-
if e.kind().class() == classes::java_lang_VirtualMachineError() {
169+
if e.kind().class()? == classes::java_lang_VirtualMachineError() {
170170
return Throws::PENDING_EXCEPTION;
171171
}
172172

runtime/src/thread/exceptions.rs

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ use crate::objects::instance::class::ClassInstance;
77
use crate::objects::reference::Reference;
88
use crate::symbols::{Symbol, sym};
99

10+
use std::fmt::Display;
1011
use std::ops::{ControlFlow, FromResidual, Try};
1112

1213
use classfile::accessflags::MethodAccessFlags;
@@ -228,19 +229,17 @@ impl ExceptionKind {
228229
}
229230
}
230231

231-
pub fn class(&self) -> ClassPtr {
232+
pub fn class(&self) -> Throws<ClassPtr> {
232233
if *self == ExceptionKind::PendingException {
233234
let Some(exception) = JavaThread::current().pending_exception() else {
234235
panic!("Thread has no pending exception");
235236
};
236237

237-
return exception.extract_target_class();
238+
return Throws::Ok(exception.extract_target_class());
238239
}
239240

240241
let class_name = self.class_name();
241-
ClassLoader::bootstrap()
242-
.load(class_name)
243-
.expect("exception class should exist")
242+
ClassLoader::bootstrap().load(class_name)
244243
}
245244
}
246245

@@ -279,7 +278,9 @@ impl Exception {
279278
return;
280279
}
281280

282-
let this = Reference::class(ClassInstance::new(self.kind.class()));
281+
let this = Reference::class(ClassInstance::new(
282+
self.kind.class().expect("class should be loaded"),
283+
));
283284

284285
match self.message {
285286
Some(message) => {
@@ -318,6 +319,15 @@ impl Exception {
318319
}
319320
}
320321

322+
impl Display for Exception {
323+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
324+
match &self.message {
325+
Some(message) => write!(f, "{:?}: {message}", self.kind),
326+
None => write!(f, "{:?}", self.kind),
327+
}
328+
}
329+
}
330+
321331
// TODO: Document, maybe also have a second private macro to hide construction patterns
322332
macro_rules! throw {
323333
($thread:expr, $($tt:tt)*) => {{

0 commit comments

Comments
 (0)