test: demo crash on huge Wayland to X transfer
I found an oversight where trying to transfer too much data (16 MiB in my case) from a Wayland selection to an X window causes the X connection to be unable to poll the event with a `ClosedReqLenExceed` error. To replicate, start `xwls`, use `wl-copy` to copy more data than the `maximum-request-size` to the Wayland selection, then attempt to transfer that selection to an X program. I found this easy to do transferring a large, random `.bmp` file to Krita by creating its new image from clipboard functionality. This test replicates the observed behavior and obtains the same panic, to be used as a starting point for implementing incremental selections from Wayland to X.
This commit is contained in:
parent
bf745144ac
commit
334933b212
1 changed files with 117 additions and 3 deletions
|
|
@ -99,12 +99,22 @@ impl Drop for Fixture {
|
||||||
// Sending anything to the quit receiver to stop the main loop. Then we guarantee a main
|
// Sending anything to the quit receiver to stop the main loop. Then we guarantee a main
|
||||||
// thread does not use file descriptors which outlive the Fixture's BorrowedFd
|
// thread does not use file descriptors which outlive the Fixture's BorrowedFd
|
||||||
let return_ptr = Box::into_raw(Box::new(0_usize)) as usize;
|
let return_ptr = Box::into_raw(Box::new(0_usize)) as usize;
|
||||||
self.quit_tx.write_all(&return_ptr.to_ne_bytes()).unwrap();
|
// If the receiver end of the pipe closed, the main thread dropped it, which means that
|
||||||
|
// thread already terminated
|
||||||
|
if self
|
||||||
|
.quit_tx
|
||||||
|
.write_all(&return_ptr.to_ne_bytes())
|
||||||
|
.is_err_and(|e| e.kind() != std::io::ErrorKind::BrokenPipe)
|
||||||
|
{
|
||||||
|
panic!("could not message the main thread to terminate");
|
||||||
|
}
|
||||||
if thread.join().is_err() {
|
if thread.join().is_err() {
|
||||||
log::error!("main thread panicked");
|
log::error!("main thread panicked");
|
||||||
}
|
}
|
||||||
rustix::process::kill_process(self.pid, Signal::TERM).unwrap();
|
if rustix::process::test_kill_process(self.pid).is_ok() {
|
||||||
rustix::process::waitpid(Some(self.pid), WaitOptions::NOHANG).unwrap();
|
rustix::process::kill_process(self.pid, Signal::TERM).unwrap();
|
||||||
|
rustix::process::waitpid(Some(self.pid), WaitOptions::NOHANG).unwrap();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1151,6 +1161,110 @@ fn copy_from_wayland() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn incr_copy_from_wayland() {
|
||||||
|
// After a little binary searching, the test passes on less than 16,777,184 bytes, fails due to
|
||||||
|
// a failed assert on matching `dest1_atom` starting at 16,777,185 bytes, and starts crashing
|
||||||
|
// instead at 16,777,189 bytes.
|
||||||
|
const BYTES: usize = 16_777_189;
|
||||||
|
let mut f = Fixture::new();
|
||||||
|
let mut connection = Connection::new(&f.display);
|
||||||
|
|
||||||
|
let window = connection.new_window(connection.root, 0, 0, 20, 20, false);
|
||||||
|
connection.get_selection_owner_change_events(true, window);
|
||||||
|
f.map_as_toplevel(&mut connection, window);
|
||||||
|
let offer = vec![testwl::PasteData {
|
||||||
|
mime_type: "text/plain".into(),
|
||||||
|
data: std::iter::successors(Some(0u8), |n| Some(n.wrapping_add(1)))
|
||||||
|
.take(BYTES)
|
||||||
|
.collect(),
|
||||||
|
}];
|
||||||
|
|
||||||
|
f.testwl.create_data_offer(offer.clone());
|
||||||
|
connection.await_selection_owner_change();
|
||||||
|
connection.verify_clipboard_owner(connection.wm_window);
|
||||||
|
connection.get_selection_owner_change_events(false, window);
|
||||||
|
|
||||||
|
let dest1_atom = connection
|
||||||
|
.get_reply(&x::InternAtom {
|
||||||
|
name: b"dest1",
|
||||||
|
only_if_exists: false,
|
||||||
|
})
|
||||||
|
.atom();
|
||||||
|
|
||||||
|
connection
|
||||||
|
.send_and_check_request(&x::ConvertSelection {
|
||||||
|
requestor: window,
|
||||||
|
selection: connection.atoms.clipboard,
|
||||||
|
target: connection.atoms.targets,
|
||||||
|
property: dest1_atom,
|
||||||
|
time: x::CURRENT_TIME,
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let request = connection.await_selection_notify();
|
||||||
|
assert_eq!(request.requestor(), window);
|
||||||
|
assert_eq!(request.selection(), connection.atoms.clipboard);
|
||||||
|
assert_eq!(request.target(), connection.atoms.targets);
|
||||||
|
assert_eq!(request.property(), dest1_atom);
|
||||||
|
|
||||||
|
let reply = connection.get_reply(&x::GetProperty {
|
||||||
|
delete: true,
|
||||||
|
window,
|
||||||
|
property: dest1_atom,
|
||||||
|
r#type: x::ATOM_ATOM,
|
||||||
|
long_offset: 0,
|
||||||
|
long_length: 10,
|
||||||
|
});
|
||||||
|
let targets: &[x::Atom] = reply.value();
|
||||||
|
assert_eq!(targets.len(), 1);
|
||||||
|
|
||||||
|
for testwl::PasteData { mime_type, data } in offer {
|
||||||
|
let atom = connection
|
||||||
|
.get_reply(&x::InternAtom {
|
||||||
|
only_if_exists: true,
|
||||||
|
name: mime_type.as_bytes(),
|
||||||
|
})
|
||||||
|
.atom();
|
||||||
|
assert_ne!(atom, x::ATOM_NONE);
|
||||||
|
assert!(targets.contains(&atom));
|
||||||
|
|
||||||
|
std::thread::sleep(std::time::Duration::from_millis(50));
|
||||||
|
connection
|
||||||
|
.send_and_check_request(&x::ConvertSelection {
|
||||||
|
requestor: window,
|
||||||
|
selection: connection.atoms.clipboard,
|
||||||
|
target: atom,
|
||||||
|
property: dest1_atom,
|
||||||
|
time: x::CURRENT_TIME,
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
f.wait_and_dispatch();
|
||||||
|
let request = connection.await_selection_notify();
|
||||||
|
assert_eq!(request.requestor(), window);
|
||||||
|
assert_eq!(request.selection(), connection.atoms.clipboard);
|
||||||
|
assert_eq!(request.target(), atom);
|
||||||
|
// The particular assert which fails in the narrow byte range as mentioned above.
|
||||||
|
assert_eq!(request.property(), dest1_atom);
|
||||||
|
|
||||||
|
let val: Vec<u8> = connection
|
||||||
|
.get_reply(&x::GetProperty {
|
||||||
|
delete: true,
|
||||||
|
window,
|
||||||
|
property: dest1_atom,
|
||||||
|
r#type: x::ATOM_ANY,
|
||||||
|
long_offset: 0,
|
||||||
|
long_length: (BYTES / 4 + BYTES % 4) as u32,
|
||||||
|
})
|
||||||
|
.value()
|
||||||
|
.to_vec();
|
||||||
|
|
||||||
|
assert_eq!(val.len(), data.len());
|
||||||
|
assert_eq!(val, data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: this test doesn't actually match real behavior for some reason...
|
// TODO: this test doesn't actually match real behavior for some reason...
|
||||||
#[test]
|
#[test]
|
||||||
fn different_output_position() {
|
fn different_output_position() {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue