about summary refs log tree commit diff
diff options
context:
space:
mode:
authorMatthias Krüger <matthias.krueger@famsik.de>2024-09-28 09:35:08 +0200
committerGitHub <noreply@github.com>2024-09-28 09:35:08 +0200
commit6e9db86787e09e9a15a0d3b9a317fb2d0fc33de9 (patch)
tree5b2801840c8b050de23d37eb423f5b4ba3e4c7f0
parentb6576e3f63916034810d24172c35a5ac1c0ef704 (diff)
parentd77664bed945fbbd5ab102f59a38b1ef0e728b10 (diff)
downloadrust-6e9db86787e09e9a15a0d3b9a317fb2d0fc33de9.tar.gz
rust-6e9db86787e09e9a15a0d3b9a317fb2d0fc33de9.zip
Rollup merge of #125404 - a1phyr:fix-read_buf-uses, r=workingjubilee
Fix `read_buf` uses in `std`

Following lib-team decision here: https://github.com/rust-lang/rust/issues/78485#issuecomment-2122992314

Guard against the pathological behavior of both returning an error and performing a read.
-rw-r--r--library/std/src/io/buffered/bufreader.rs2
-rw-r--r--library/std/src/io/buffered/bufreader/buffer.rs4
-rw-r--r--library/std/src/io/mod.rs35
-rw-r--r--library/std/src/io/tests.rs63
4 files changed, 88 insertions, 16 deletions
diff --git a/library/std/src/io/buffered/bufreader.rs b/library/std/src/io/buffered/bufreader.rs
index fcb3e36027b..8b46738ab8a 100644
--- a/library/std/src/io/buffered/bufreader.rs
+++ b/library/std/src/io/buffered/bufreader.rs
@@ -357,7 +357,7 @@ impl<R: ?Sized + Read> Read for BufReader<R> {
         let prev = cursor.written();
 
         let mut rem = self.fill_buf()?;
-        rem.read_buf(cursor.reborrow())?;
+        rem.read_buf(cursor.reborrow())?; // actually never fails
 
         self.consume(cursor.written() - prev); //slice impl of read_buf known to never unfill buf
 
diff --git a/library/std/src/io/buffered/bufreader/buffer.rs b/library/std/src/io/buffered/bufreader/buffer.rs
index 3df7e3971da..52fe49985c6 100644
--- a/library/std/src/io/buffered/bufreader/buffer.rs
+++ b/library/std/src/io/buffered/bufreader/buffer.rs
@@ -143,11 +143,13 @@ impl Buffer {
                 buf.set_init(self.initialized);
             }
 
-            reader.read_buf(buf.unfilled())?;
+            let result = reader.read_buf(buf.unfilled());
 
             self.pos = 0;
             self.filled = buf.len();
             self.initialized = buf.init_len();
+
+            result?;
         }
         Ok(self.buffer())
     }
diff --git a/library/std/src/io/mod.rs b/library/std/src/io/mod.rs
index 0b57d01f273..dd6458c38c6 100644
--- a/library/std/src/io/mod.rs
+++ b/library/std/src/io/mod.rs
@@ -474,18 +474,28 @@ pub(crate) fn default_read_to_end<R: Read + ?Sized>(
         }
 
         let mut cursor = read_buf.unfilled();
-        loop {
+        let result = loop {
             match r.read_buf(cursor.reborrow()) {
-                Ok(()) => break,
                 Err(e) if e.is_interrupted() => continue,
-                Err(e) => return Err(e),
+                // Do not stop now in case of error: we might have received both data
+                // and an error
+                res => break res,
             }
-        }
+        };
 
         let unfilled_but_initialized = cursor.init_ref().len();
         let bytes_read = cursor.written();
         let was_fully_initialized = read_buf.init_len() == buf_len;
 
+        // SAFETY: BorrowedBuf's invariants mean this much memory is initialized.
+        unsafe {
+            let new_len = bytes_read + buf.len();
+            buf.set_len(new_len);
+        }
+
+        // Now that all data is pushed to the vector, we can fail without data loss
+        result?;
+
         if bytes_read == 0 {
             return Ok(buf.len() - start_len);
         }
@@ -499,12 +509,6 @@ pub(crate) fn default_read_to_end<R: Read + ?Sized>(
         // store how much was initialized but not filled
         initialized = unfilled_but_initialized;
 
-        // SAFETY: BorrowedBuf's invariants mean this much memory is initialized.
-        unsafe {
-            let new_len = bytes_read + buf.len();
-            buf.set_len(new_len);
-        }
-
         // Use heuristics to determine the max read size if no initial size hint was provided
         if size_hint.is_none() {
             // The reader is returning short reads but it doesn't call ensure_init().
@@ -974,6 +978,8 @@ pub trait Read {
     /// with uninitialized buffers. The new data will be appended to any existing contents of `buf`.
     ///
     /// The default implementation delegates to `read`.
+    ///
+    /// This method makes it possible to return both data and an error but it is advised against.
     #[unstable(feature = "read_buf", issue = "78485")]
     fn read_buf(&mut self, buf: BorrowedCursor<'_>) -> Result<()> {
         default_read_buf(|b| self.read(b), buf)
@@ -2941,7 +2947,7 @@ impl<T: Read> Read for Take<T> {
             }
 
             let mut cursor = sliced_buf.unfilled();
-            self.inner.read_buf(cursor.reborrow())?;
+            let result = self.inner.read_buf(cursor.reborrow());
 
             let new_init = cursor.init_ref().len();
             let filled = sliced_buf.len();
@@ -2956,13 +2962,14 @@ impl<T: Read> Read for Take<T> {
             }
 
             self.limit -= filled as u64;
+
+            result
         } else {
             let written = buf.written();
-            self.inner.read_buf(buf.reborrow())?;
+            let result = self.inner.read_buf(buf.reborrow());
             self.limit -= (buf.written() - written) as u64;
+            result
         }
-
-        Ok(())
     }
 }
 
diff --git a/library/std/src/io/tests.rs b/library/std/src/io/tests.rs
index f551dcd401e..56b71c47dc7 100644
--- a/library/std/src/io/tests.rs
+++ b/library/std/src/io/tests.rs
@@ -735,6 +735,69 @@ fn read_buf_full_read() {
     assert_eq!(BufReader::new(FullRead).fill_buf().unwrap().len(), DEFAULT_BUF_SIZE);
 }
 
+struct DataAndErrorReader(&'static [u8]);
+
+impl Read for DataAndErrorReader {
+    fn read(&mut self, _buf: &mut [u8]) -> io::Result<usize> {
+        panic!("We want tests to use `read_buf`")
+    }
+
+    fn read_buf(&mut self, buf: io::BorrowedCursor<'_>) -> io::Result<()> {
+        self.0.read_buf(buf).unwrap();
+        Err(io::Error::other("error"))
+    }
+}
+
+#[test]
+fn read_buf_data_and_error_take() {
+    let mut buf = [0; 64];
+    let mut buf = io::BorrowedBuf::from(buf.as_mut_slice());
+
+    let mut r = DataAndErrorReader(&[4, 5, 6]).take(1);
+    assert!(r.read_buf(buf.unfilled()).is_err());
+    assert_eq!(buf.filled(), &[4]);
+
+    assert!(r.read_buf(buf.unfilled()).is_ok());
+    assert_eq!(buf.filled(), &[4]);
+    assert_eq!(r.get_ref().0, &[5, 6]);
+}
+
+#[test]
+fn read_buf_data_and_error_buf() {
+    let mut r = BufReader::new(DataAndErrorReader(&[4, 5, 6]));
+
+    assert!(r.fill_buf().is_err());
+    assert_eq!(r.fill_buf().unwrap(), &[4, 5, 6]);
+}
+
+#[test]
+fn read_buf_data_and_error_read_to_end() {
+    let mut r = DataAndErrorReader(&[4, 5, 6]);
+
+    let mut v = Vec::with_capacity(200);
+    assert!(r.read_to_end(&mut v).is_err());
+
+    assert_eq!(v, &[4, 5, 6]);
+}
+
+#[test]
+fn read_to_end_error() {
+    struct ErrorReader;
+
+    impl Read for ErrorReader {
+        fn read(&mut self, _buf: &mut [u8]) -> io::Result<usize> {
+            Err(io::Error::other("error"))
+        }
+    }
+
+    let mut r = [4, 5, 6].chain(ErrorReader);
+
+    let mut v = Vec::with_capacity(200);
+    assert!(r.read_to_end(&mut v).is_err());
+
+    assert_eq!(v, &[4, 5, 6]);
+}
+
 #[test]
 // Miri does not support signalling OOM
 #[cfg_attr(miri, ignore)]