Skip to content

Commit 85ed6a0

Browse files
committed
fix(native-agent): promote jfloat to c_double before the variadic JNI call
1 parent e2f0f5d commit 85ed6a0

2 files changed

Lines changed: 37 additions & 5 deletions

File tree

‎native-agent/src/exceptions.rs‎

Lines changed: 33 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -246,14 +246,34 @@ fn make_local_variable(
246246
entry: &jvmtiLocalVariableEntry,
247247
index: jint,
248248
) -> Result<()> {
249+
// JVMTI defines a local's valid range as half-open: [start_location, start_location +
250+
// length). At the end offset the slot may already have been reused for another variable,
251+
// so reading it yields either an unrelated value or a JVMTI error.
249252
let in_scope = location >= entry.start_location
250-
&& location <= entry.start_location + i64::from(entry.length);
253+
&& location < entry.start_location + i64::from(entry.length);
251254

252255
// Building the name string only matters for slots we actually emit.
253256
let local = if in_scope {
254-
let name = jni_env.new_string_utf(entry.name)?;
255-
let value = get_local_value(jvmti_env, jni_env, capture, thread, depth, entry)?;
256-
jni_env.new_object_StringL(capture.local_variable, capture.local_variable_ctor, name, value)?
257+
// A slot that cannot be read is dropped on its own. Propagating would abort the whole
258+
// capture, so a single unreadable slot would cost every frame on the stack its locals.
259+
match get_local_value(jvmti_env, jni_env, capture, thread, depth, entry) {
260+
Ok(value) => {
261+
let name = jni_env.new_string_utf(entry.name)?;
262+
jni_env.new_object_StringL(
263+
capture.local_variable,
264+
capture.local_variable_ctor,
265+
name,
266+
value,
267+
)?
268+
}
269+
Err(e) => {
270+
debug!("skipping local variable in slot {}: {}", entry.slot, e);
271+
// The failing paths clear the pending exception themselves; this is belt and
272+
// braces, because leaving one pending would break the next JNI call.
273+
jni_env.clear_pending_exception();
274+
ptr::null_mut()
275+
}
276+
}
257277
} else {
258278
ptr::null_mut()
259279
};
@@ -314,7 +334,15 @@ fn get_local_value(
314334
b'F' => {
315335
let mut val: jfloat = 0.0;
316336
jvmti_env.get_local_float(thread, depth, entry.slot, &mut val)?;
317-
box_value(jni_env, &capture.float_box, val)
337+
// C varargs promote float to double, and HotSpot's argument pusher reads a
338+
// jdouble for an F parameter. rustc's E0617 cannot catch this because the value
339+
// reaches the variadic call through a generic; passing the jfloat unpromoted
340+
// makes the JVM read 8 bytes where 4 were written.
341+
box_value(
342+
jni_env,
343+
&capture.float_box,
344+
val as ::std::os::raw::c_double,
345+
)
318346
}
319347
b'D' => {
320348
let mut val: jdouble = 0.0;

‎native-agent/src/jni.rs‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,10 @@ impl JniEnv {
3232
}
3333
}
3434

35+
/// `arg` is forwarded to a C-variadic function, so it must already be promoted to the
36+
/// type the callee reads: `c_double` for a `jfloat`, `c_int` for anything narrower than
37+
/// an int. rustc's E0617 only fires on a concrete argument type, so a generic `T` slips
38+
/// past it and the mismatch shows up as garbage values at runtime, never as an error.
3539
pub fn call_static_object_method<T>(
3640
&mut self,
3741
class: ::jvmti::jclass,

0 commit comments

Comments
 (0)