Repository: rust-lang/rust
Pull request: Remove string content from panics

String content can be useful for debugging panics, particularly when you are working on a small codebase where you can infer more about the path taken through your program based on the content of the string.

In production deployments that upload crashes for centralized debugging, string content poses a risk to user privacy. The string can accidentally contain information that the user is not expecting to share with the development team.

On balance it seems like the risks outweigh the benefits. It is easy to add a `dbg!()` statement to gather more information in development; but comparatively tricky to ensure panics are sanitized by every rust app that monitors their production deployments.

Ref https://internals.rust-lang.org/t/stop-including-string-content-in-index-panics/24067

r? @scottmcm


Changed files:
filename: library/alloctests/tests/str.rs
patch: @@ -612,14 +612,14 @@ mod slice_index {
             data: "abcdef";
             good: data[4..4] == "";
             bad: data[4..3];
-            message: "begin > end (4 > 3)";
+            message: "byte range starts at 4 but ends at 3";
         }
 
         in mod rangeinclusive_neg_width {
             data: "abcdef";
             good: data[4..=3] == "";
             bad: data[4..=2];
-            message: "begin > end (4 > 3)";
+            message: "byte range starts at 4 but ends at 3";
         }
     }
 
@@ -659,49 +659,49 @@ mod slice_index {
                 data: super::DATA;
                 bad: data[super::BAD_START..super::GOOD_END];
                 message:
-                    "start byte index 4 is not a char boundary; it is inside 'α' (bytes 3..5) of";
+                    "start byte index 4 is not a char boundary; it is inside 'α' (bytes 3..5 of string)";
             }
 
             in mod range_2 {
                 data: super::DATA;
                 bad: data[super::GOOD_START..super::BAD_END];
                 message:
-                    "end byte index 6 is not a char boundary; it is inside 'β' (bytes 5..7) of";
+                    "end byte index 6 is not a char boundary; it is inside 'β' (bytes 5..7 of string)";
             }
 
             in mod rangefrom {
                 data: super::DATA;
                 bad: data[super::BAD_START..];
                 message:
-                    "start byte index 4 is not a char boundary; it is inside 'α' (bytes 3..5) of";
+                    "start byte index 4 is not a char boundary; it is inside 'α' (bytes 3..5 of string)";
             }
 
             in mod rangeto {
                 data: super::DATA;
                 bad: data[..super::BAD_END];
                 message:
-                    "end byte index 6 is not a char boundary; it is inside 'β' (bytes 5..7) of";
+                    "end byte index 6 is not a char boundary; it is inside 'β' (bytes 5..7 of string)";
             }
 
             in mod rangeinclusive_1 {
                 data: super::DATA;
                 bad: data[super::BAD_START..=super::GOOD_END_INCL];
                 message:
-                    "start byte index 4 is not a char boundary; it is inside 'α' (bytes 3..5) of";
+                    "start byte index 4 is not a char boundary; it is inside 'α' (bytes 3..5 of string)";
             }
 
             in mod rangeinclusive_2 {
                 data: super::DATA;
                 bad: data[super::GOOD_START..=super::BAD_END_INCL];
                 message:
-                    "end byte index 6 is not a char boundary; it is inside 'β' (bytes 5..7) of";
+                    "end byte index 6 is not a char boundary; it is inside 'β' (bytes 5..7 of string)";
             }
 
             in mod rangetoinclusive {
                 data: super::DATA;
                 bad: data[..=super::BAD_END_INCL];
                 message:
-                    "end byte index 6 is not a char boundary; it is inside 'β' (bytes 5..7) of";
+                    "end byte index 6 is not a char boundary; it is inside 'β' (bytes 5..7 of string)";
             }
         }
     }
@@ -716,18 +716,10 @@ mod slice_index {
 
     // check the panic includes the prefix of the sliced string
     #[test]
-    #[should_panic(
-        expected = "end byte index 1024 is out of bounds of `Lorem ipsum dolor sit amet"
-    )]
+    #[should_panic(expected = "end byte index 1024 is out of bounds for string of length 416")]
     fn test_slice_fail_truncated_1() {
         let _ = &LOREM_PARAGRAPH[..1024];
     }
-    // check the truncation in the panic message
-    #[test]
-    #[should_panic(expected = "luctus, im`[...]")]
-    fn test_slice_fail_truncated_2() {
-        let _ = &LOREM_PARAGRAPH[..1024];
-    }
 }
 
 #[test]

filename: library/core/src/str/mod.rs
patch: @@ -81,25 +81,21 @@ const fn slice_error_fail_ct(_: &str, _: usize, _: usize) -> ! {
 
 #[track_caller]
 fn slice_error_fail_rt(s: &str, begin: usize, end: usize) -> ! {
-    const MAX_DISPLAY_LENGTH: usize = 256;
-    let trunc_len = s.floor_char_boundary(MAX_DISPLAY_LENGTH);
-    let s_trunc = &s[..trunc_len];
-    let ellipsis = if trunc_len < s.len() { "[...]" } else { "" };
     let len = s.len();
 
     // 1. begin is OOB.
     if begin > len {
-        panic!("start byte index {begin} is out of bounds of `{s_trunc}`{ellipsis}");
+        panic!("start byte index {begin} is out of bounds for string of length {len}");
     }
 
     // 2. end is OOB.
     if end > len {
-        panic!("end byte index {end} is out of bounds of `{s_trunc}`{ellipsis}");
+        panic!("end byte index {end} is out of bounds for string of length {len}");
     }
 
     // 3. range is backwards.
     if begin > end {
-        panic!("begin > end ({begin} > {end}) when slicing `{s_trunc}`{ellipsis}")
+        panic!("byte range starts at {begin} but ends at {end}");
     }
 
     // 4. begin is inside a character.
@@ -109,7 +105,7 @@ fn slice_error_fail_rt(s: &str, begin: usize, end: usize) -> ! {
         let range = floor..ceil;
         let ch = s[floor..ceil].chars().next().unwrap();
         panic!(
-            "start byte index {begin} is not a char boundary; it is inside {ch:?} (bytes {range:?}) of `{s_trunc}`{ellipsis}"
+            "start byte index {begin} is not a char boundary; it is inside {ch:?} (bytes {range:?} of string)"
         )
     }
 
@@ -120,15 +116,15 @@ fn slice_error_fail_rt(s: &str, begin: usize, end: usize) -> ! {
         let range = floor..ceil;
         let ch = s[floor..ceil].chars().next().unwrap();
         panic!(
-            "end byte index {end} is not a char boundary; it is inside {ch:?} (bytes {range:?}) of `{s_trunc}`{ellipsis}"
+            "end byte index {end} is not a char boundary; it is inside {ch:?} (bytes {range:?} of string)"
         )
     }
 
     // 6. end is OOB and range is inclusive (end == len).
     // This test cannot be combined with 2. above because for cases like
     // `"abcαβγ"[4..9]` the error is that 4 is inside 'α', not that 9 is OOB.
     debug_assert_eq!(end, len);
-    panic!("end byte index {end} is out of bounds of `{s_trunc}`{ellipsis}");
+    panic!("end byte index {end} is out of bounds for string of length {len}");
 }
 
 impl str {

filename: library/core/src/wtf8.rs
patch: @@ -484,11 +484,22 @@ unsafe fn slice_unchecked(s: &Wtf8, begin: usize, end: usize) -> &Wtf8 {
     }
 }
 
-/// Copied from core::str::raw::slice_error_fail
 #[inline(never)]
 fn slice_error_fail(s: &Wtf8, begin: usize, end: usize) -> ! {
-    assert!(begin <= end);
-    panic!("index {begin} and/or {end} in `{s:?}` do not lie on character boundary");
+    let len = s.len();
+    if begin > len {
+        panic!("start byte index {begin} is out of bounds for string of length {len}");
+    }
+    if end > len {
+        panic!("end byte index {end} is out of bounds for string of length {len}");
+    }
+    if begin > end {
+        panic!("byte range starts at {begin} but ends at {end}");
+    }
+    if !s.is_code_point_boundary(begin) {
+        panic!("byte index {begin} is not a code point boundary");
+    }
+    panic!("byte index {end} is not a code point boundary");
 }
 
 /// Iterator for the code points of a WTF-8 string.

filename: tests/ui/intrinsics/const-eval-select-backtrace-std.run.stderr
patch: @@ -1,4 +1,4 @@
 
 thread 'main' ($TID) panicked at $DIR/const-eval-select-backtrace-std.rs:6:8:
-start byte index 1 is out of bounds of ``
+start byte index 1 is out of bounds for string of length 0
 note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace