Change readch() into try_readch()

This lets us call into the entirety of the prior `readch()` with an exhaustible
input stream without panicking on the `unreachable!()` call. The previous
functionality is kept under the old name by calling `try_readch()` with the
`blocking` parameter set to `true` (100% same behavior as before).

While the `try_readch(false)` entrypoint isn't used directly by the current fish
codebase, it is required in order to automate input reader tests without the
overhead and complexity of running the test harness in a tty emulator emulator
like pexpect or tmux, which moreover necessitates out-of-process testing – which
is incompatible with most perf-guided testing harnesses.

I hope to be able to upstream harness integrations using this entry point in the
near future.
This commit is contained in:
Mahmoud Al-Qudsi 2024-11-20 14:48:20 -06:00
parent b3108c0cee
commit b92830cb17

View File

@ -593,28 +593,44 @@ pub trait InputEventQueuer {
self.get_input_data_mut().queue.pop_front() self.get_input_data_mut().queue.pop_front()
} }
/// Function used by input_readch to read bytes from stdin until enough bytes have been read to /// An "infallible" version of [`try_readch`](Self::try_readch) to be used when the input pipe
/// convert them to a wchar_t. Conversion is done using mbrtowc. If a character has previously /// fd is expected to outlive the input reader. Will panic upon EOF.
/// been read and then 'unread' using \c input_common_unreadch, that character is returned. #[inline(always)]
fn readch(&mut self) -> CharEvent { fn readch(&mut self) -> CharEvent {
match self.try_readch(/*blocking*/ true) {
Some(c) => c,
None => unreachable!(),
}
}
/// Function used by [`input_readch`] to read bytes from stdin until enough bytes have been read to
/// convert them to a wchar_t. Conversion is done using `mbrtowc`. If a character has previously
/// been read and then 'unread', that character is returned.
///
/// This is guaranteed to keep returning `Some(CharEvent)` so long as the input stream remains
/// open; `None` is only returned upon EOF as the main loop within blocks until input becomes
/// available.
///
/// This method is used directly by the fuzzing harness to avoid a panic on bounded inputs.
fn try_readch(&mut self, blocking: bool) -> Option<CharEvent> {
loop { loop {
// Do we have something enqueued already? // Do we have something enqueued already?
// Note this may be initially true, or it may become true through calls to // Note this may be initially true, or it may become true through calls to
// iothread_service_main() or env_universal_barrier() below. // iothread_service_main() or env_universal_barrier() below.
if let Some(mevt) = self.try_pop() { if let Some(mevt) = self.try_pop() {
return mevt; return Some(mevt);
} }
// We are going to block; but first allow any override to inject events. // We are going to block; but first allow any override to inject events.
self.prepare_to_select(); self.prepare_to_select();
if let Some(mevt) = self.try_pop() { if let Some(mevt) = self.try_pop() {
return mevt; return Some(mevt);
} }
let rr = readb(self.get_in_fd(), /*blocking=*/ true); let rr = readb(self.get_in_fd(), blocking);
match rr { match rr {
ReadbResult::Eof => { ReadbResult::Eof => {
return CharEvent::Eof; return Some(CharEvent::Eof);
} }
ReadbResult::Interrupted => { ReadbResult::Interrupted => {
@ -682,16 +698,16 @@ pub trait InputEventQueuer {
continue; continue;
} }
return if let Some(key) = key { return if let Some(key) = key {
CharEvent::from_key_seq(key, seq) Some(CharEvent::from_key_seq(key, seq))
} else { } else {
self.insert_front(seq.chars().skip(1).map(CharEvent::from_char)); self.insert_front(seq.chars().skip(1).map(CharEvent::from_char));
let Some(c) = seq.chars().next() else { let Some(c) = seq.chars().next() else {
continue; continue;
}; };
CharEvent::from_key_seq(Key::from_raw(c), seq) Some(CharEvent::from_key_seq(Key::from_raw(c), seq))
}; };
} }
ReadbResult::NothingToRead => unreachable!(), ReadbResult::NothingToRead => return None,
} }
} }
} }