From b92830cb17075c84e9ae17a8aa9efee5e0dfdb40 Mon Sep 17 00:00:00 2001 From: Mahmoud Al-Qudsi Date: Wed, 20 Nov 2024 14:48:20 -0600 Subject: [PATCH] Change `readch()` into `try_readch()` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- src/input_common.rs | 36 ++++++++++++++++++++++++++---------- 1 file changed, 26 insertions(+), 10 deletions(-) diff --git a/src/input_common.rs b/src/input_common.rs index fa57f6bac..e223c2323 100644 --- a/src/input_common.rs +++ b/src/input_common.rs @@ -593,28 +593,44 @@ pub trait InputEventQueuer { 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 - /// convert them to a wchar_t. Conversion is done using mbrtowc. If a character has previously - /// been read and then 'unread' using \c input_common_unreadch, that character is returned. + /// An "infallible" version of [`try_readch`](Self::try_readch) to be used when the input pipe + /// fd is expected to outlive the input reader. Will panic upon EOF. + #[inline(always)] 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 { loop { // Do we have something enqueued already? // Note this may be initially true, or it may become true through calls to // iothread_service_main() or env_universal_barrier() below. 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. self.prepare_to_select(); 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 { ReadbResult::Eof => { - return CharEvent::Eof; + return Some(CharEvent::Eof); } ReadbResult::Interrupted => { @@ -682,16 +698,16 @@ pub trait InputEventQueuer { continue; } return if let Some(key) = key { - CharEvent::from_key_seq(key, seq) + Some(CharEvent::from_key_seq(key, seq)) } else { self.insert_front(seq.chars().skip(1).map(CharEvent::from_char)); let Some(c) = seq.chars().next() else { 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, } } }