#![allow(
clippy::pedantic,
clippy::nursery,
clippy::unwrap_used,
clippy::expect_used,
clippy::indexing_slicing,
clippy::print_stdout,
clippy::print_stderr,
clippy::panic,
clippy::panic_in_result_fn
)]
use jdwp_client::events::EventKind;
use jdwp_client::{EventFilters, JdwpConnection, MonitorKind, SuspendPolicy};
use std::time::{Duration, Instant};
const WINDOW: Duration = Duration::from_secs(3);
async fn collect(conn: &mut JdwpConnection, label: &str) {
while conn.try_recv_event().await.is_some() {}
let deadline = Instant::now() + WINDOW;
let mut n = 0usize;
let mut by_lock: std::collections::BTreeMap<String, usize> = std::collections::BTreeMap::new();
while Instant::now() < deadline {
let Some(set) = conn.try_recv_event().await else {
tokio::time::sleep(Duration::from_millis(20)).await;
continue;
};
for e in &set.events {
let monitor = match &e.details {
EventKind::MonitorContendedEnter { monitor }
| EventKind::MonitorContendedEntered { monitor }
| EventKind::MonitorWait { monitor, .. }
| EventKind::MonitorWaited { monitor, .. } => monitor,
other => {
println!(" (non-monitor event: {other:?})");
continue;
}
};
n += 1;
let name = match conn.get_object_reference_type(monitor.monitor).await {
Ok(t) => conn.get_signature(t).await.unwrap_or_else(|_| "<unreadable>".to_string()),
Err(_) => "<unreadable>".to_string(),
};
*by_lock.entry(name).or_default() += 1;
if let EventKind::MonitorWait { timeout, .. } = &e.details {
*by_lock.entry(format!(" timeout={timeout}")).or_default() += 1;
}
if let EventKind::MonitorWaited { timed_out, .. } = &e.details {
*by_lock.entry(format!(" timed_out={timed_out}")).or_default() += 1;
}
}
}
println!("{label}: {n} event(s) in {WINDOW:?}");
for (k, v) in by_lock {
println!(" {k} × {v}");
}
}
async fn class_id(conn: &mut JdwpConnection, dotted: &str) -> u64 {
let sig = format!("L{};", dotted.replace('.', "/"));
let classes = conn.classes_by_signature(&sig).await.expect("lookup");
let id = classes.first().map_or(0, |c| c.type_id);
println!(" {dotted} -> type id 0x{id:x} ({} copy/copies)", classes.len());
id
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let port: u16 = std::env::args().nth(1).unwrap_or_else(|| "8826".into()).parse()?;
let mut conn = JdwpConnection::connect("localhost", port).await?;
println!("== connected on port {port}");
let v = conn.get_version().await?;
println!("== {} (JDWP {}.{})", v.vm_version, v.jdwp_major, v.jdwp_minor);
let caps = conn.capabilities_new().await?;
println!(
"== canRequestMonitorEvents (17) = {}, canGetMonitorFrameInfo (18) = {}",
caps.can_request_monitor_events, caps.can_get_monitor_frame_info
);
println!("== canGetInstanceInfo (16) = {} (sanity: DISC-10 measured true)", caps.can_get_instance_info);
let mut armed = Vec::new();
for kind in MonitorKind::ALL {
let req = conn
.set_monitor_request(kind, SuspendPolicy::None, None, EventFilters::default())
.await
.expect("arm");
println!("== armed {} (kind {}) as request {req}", kind.label(), kind.event_kind());
armed.push((kind, req));
}
collect(&mut conn, "all four, unfiltered").await;
for (kind, req) in armed {
conn.clear_monitor_request(req, kind).await?;
}
println!("\n== resolving the types");
let probe_class = class_id(&mut conn, "MonitorProbe").await;
let fast_lock = class_id(&mut conn, "MonitorProbe$FastLock").await;
let timeout_lock = class_id(&mut conn, "MonitorProbe$TimeoutLock").await;
for (kind, name, type_id) in [
(MonitorKind::Blocked, "location class (MonitorProbe)", probe_class),
(MonitorKind::Blocked, "monitor class (FastLock)", fast_lock),
(MonitorKind::Wait, "location class (MonitorProbe)", probe_class),
(MonitorKind::Wait, "monitor class (TimeoutLock)", timeout_lock),
] {
match conn
.set_monitor_request(kind, SuspendPolicy::None, Some(type_id), EventFilters::default())
.await
{
Ok(req) => {
collect(&mut conn, &format!("{} + ClassOnly {name}", kind.label())).await;
conn.clear_monitor_request(req, kind).await?;
}
Err(e) => println!("{} + ClassOnly {name}: REFUSED — {e}", kind.label()),
}
}
let fields = conn.get_fields(probe_class).await?;
let fast_field = fields.iter().find(|f| f.name == "FAST").expect("MonitorProbe.FAST");
let fast_obj = match conn.get_reference_values(probe_class, vec![fast_field.field_id]).await?.first() {
Some(v) => match v.data {
jdwp_client::types::ValueData::Object(id) => id,
ref other => panic!("MonitorProbe.FAST is not a reference: {other:?}"),
},
None => panic!("no value for MonitorProbe.FAST"),
};
println!("\n== MonitorProbe.FAST (the lock OBJECT) = 0x{fast_obj:x}");
match conn
.set_monitor_request(
MonitorKind::Blocked,
SuspendPolicy::None,
None,
EventFilters { count: None, thread: None, instance: Some(fast_obj) },
)
.await
{
Ok(req) => {
println!("== InstanceOnly ACCEPTED on a monitor request (request {req}) — is it APPLIED?");
println!(
" (every frame here is static, so `this` is null: any FastLock event at all means inert,"
);
println!(" and a SlowLock/TimeoutLock event means it did not even scope to the named object)");
collect(&mut conn, "blocked + InstanceOnly on the FastLock object").await;
conn.clear_monitor_request(req, MonitorKind::Blocked).await?;
}
Err(e) => println!("== InstanceOnly REFUSED on a monitor request: {e}"),
}
Ok(())
}