about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--compiler/rustc_parse/src/parser/expr.rs24
-rw-r--r--compiler/rustc_smir/src/rustc_internal/internal.rs2
-rw-r--r--library/alloc/src/fmt.rs2
-rw-r--r--library/core/src/fmt/builders.rs144
-rw-r--r--library/core/src/fmt/mod.rs3
-rw-r--r--library/core/src/slice/mod.rs10
-rw-r--r--src/doc/rustc/src/exploit-mitigations.md404
-rw-r--r--src/doc/rustc/src/images/image1.pngbin15293 -> 164896 bytes
-rw-r--r--src/doc/rustc/src/images/image2.pngbin28772 -> 155307 bytes
-rw-r--r--src/doc/rustc/src/images/image3.pngbin19069 -> 19936 bytes
-rw-r--r--tests/rustdoc-json/reexport/pub_use_doc_hidden.rs5
-rw-r--r--tests/ui/parser/semi-in-let-chain.rs27
-rw-r--r--tests/ui/parser/semi-in-let-chain.stderr50
13 files changed, 442 insertions, 229 deletions
diff --git a/compiler/rustc_parse/src/parser/expr.rs b/compiler/rustc_parse/src/parser/expr.rs
index 19690a6964b..235b28b6e26 100644
--- a/compiler/rustc_parse/src/parser/expr.rs
+++ b/compiler/rustc_parse/src/parser/expr.rs
@@ -2441,10 +2441,26 @@ impl<'a> Parser<'a> {
                     self.error_on_extra_if(&cond)?;
                     // Parse block, which will always fail, but we can add a nice note to the error
                     self.parse_block().map_err(|mut err| {
-                        err.span_note(
-                            cond_span,
-                            "the `if` expression is missing a block after this condition",
-                        );
+                        if self.prev_token == token::Semi
+                            && self.token == token::AndAnd
+                            && let maybe_let = self.look_ahead(1, |t| t.clone())
+                            && maybe_let.is_keyword(kw::Let)
+                        {
+                            err.span_suggestion(
+                                self.prev_token.span,
+                                "consider removing this semicolon to parse the `let` as part of the same chain",
+                                "",
+                                Applicability::MachineApplicable,
+                            ).span_note(
+                                self.token.span.to(maybe_let.span),
+                                "you likely meant to continue parsing the let-chain starting here",
+                            );
+                        } else {
+                            err.span_note(
+                                cond_span,
+                                "the `if` expression is missing a block after this condition",
+                            );
+                        }
                         err
                     })?
                 }
diff --git a/compiler/rustc_smir/src/rustc_internal/internal.rs b/compiler/rustc_smir/src/rustc_internal/internal.rs
index 78144524ac5..7cfdbbbf703 100644
--- a/compiler/rustc_smir/src/rustc_internal/internal.rs
+++ b/compiler/rustc_smir/src/rustc_internal/internal.rs
@@ -54,7 +54,7 @@ fn ty_const<'tcx>(constant: &Const, tables: &mut Tables<'tcx>) -> rustc_ty::Cons
     match constant.internal(tables) {
         rustc_middle::mir::Const::Ty(c) => c,
         cnst => {
-            panic!("Trying to covert constant `{constant:?}` to type constant, but found {cnst:?}")
+            panic!("Trying to convert constant `{constant:?}` to type constant, but found {cnst:?}")
         }
     }
 }
diff --git a/library/alloc/src/fmt.rs b/library/alloc/src/fmt.rs
index 1e2c35bf735..5b50ef7bf6c 100644
--- a/library/alloc/src/fmt.rs
+++ b/library/alloc/src/fmt.rs
@@ -555,6 +555,8 @@
 pub use core::fmt::Alignment;
 #[stable(feature = "rust1", since = "1.0.0")]
 pub use core::fmt::Error;
+#[unstable(feature = "debug_closure_helpers", issue = "117729")]
+pub use core::fmt::FormatterFn;
 #[stable(feature = "rust1", since = "1.0.0")]
 pub use core::fmt::{write, Arguments};
 #[stable(feature = "rust1", since = "1.0.0")]
diff --git a/library/core/src/fmt/builders.rs b/library/core/src/fmt/builders.rs
index 47db53ac6f3..4ccb585862c 100644
--- a/library/core/src/fmt/builders.rs
+++ b/library/core/src/fmt/builders.rs
@@ -130,6 +130,18 @@ impl<'a, 'b: 'a> DebugStruct<'a, 'b> {
     /// ```
     #[stable(feature = "debug_builders", since = "1.2.0")]
     pub fn field(&mut self, name: &str, value: &dyn fmt::Debug) -> &mut Self {
+        self.field_with(name, |f| value.fmt(f))
+    }
+
+    /// Adds a new field to the generated struct output.
+    ///
+    /// This method is equivalent to [`DebugStruct::field`], but formats the
+    /// value using a provided closure rather than by calling [`Debug::fmt`].
+    #[unstable(feature = "debug_closure_helpers", issue = "117729")]
+    pub fn field_with<F>(&mut self, name: &str, value_fmt: F) -> &mut Self
+    where
+        F: FnOnce(&mut fmt::Formatter<'_>) -> fmt::Result,
+    {
         self.result = self.result.and_then(|_| {
             if self.is_pretty() {
                 if !self.has_fields {
@@ -140,14 +152,14 @@ impl<'a, 'b: 'a> DebugStruct<'a, 'b> {
                 let mut writer = PadAdapter::wrap(self.fmt, &mut slot, &mut state);
                 writer.write_str(name)?;
                 writer.write_str(": ")?;
-                value.fmt(&mut writer)?;
+                value_fmt(&mut writer)?;
                 writer.write_str(",\n")
             } else {
                 let prefix = if self.has_fields { ", " } else { " { " };
                 self.fmt.write_str(prefix)?;
                 self.fmt.write_str(name)?;
                 self.fmt.write_str(": ")?;
-                value.fmt(self.fmt)
+                value_fmt(self.fmt)
             }
         });
 
@@ -315,6 +327,18 @@ impl<'a, 'b: 'a> DebugTuple<'a, 'b> {
     /// ```
     #[stable(feature = "debug_builders", since = "1.2.0")]
     pub fn field(&mut self, value: &dyn fmt::Debug) -> &mut Self {
+        self.field_with(|f| value.fmt(f))
+    }
+
+    /// Adds a new field to the generated tuple struct output.
+    ///
+    /// This method is equivalent to [`DebugTuple::field`], but formats the
+    /// value using a provided closure rather than by calling [`Debug::fmt`].
+    #[unstable(feature = "debug_closure_helpers", issue = "117729")]
+    pub fn field_with<F>(&mut self, value_fmt: F) -> &mut Self
+    where
+        F: FnOnce(&mut fmt::Formatter<'_>) -> fmt::Result,
+    {
         self.result = self.result.and_then(|_| {
             if self.is_pretty() {
                 if self.fields == 0 {
@@ -323,12 +347,12 @@ impl<'a, 'b: 'a> DebugTuple<'a, 'b> {
                 let mut slot = None;
                 let mut state = Default::default();
                 let mut writer = PadAdapter::wrap(self.fmt, &mut slot, &mut state);
-                value.fmt(&mut writer)?;
+                value_fmt(&mut writer)?;
                 writer.write_str(",\n")
             } else {
                 let prefix = if self.fields == 0 { "(" } else { ", " };
                 self.fmt.write_str(prefix)?;
-                value.fmt(self.fmt)
+                value_fmt(self.fmt)
             }
         });
 
@@ -385,7 +409,10 @@ struct DebugInner<'a, 'b: 'a> {
 }
 
 impl<'a, 'b: 'a> DebugInner<'a, 'b> {
-    fn entry(&mut self, entry: &dyn fmt::Debug) {
+    fn entry_with<F>(&mut self, entry_fmt: F)
+    where
+        F: FnOnce(&mut fmt::Formatter<'_>) -> fmt::Result,
+    {
         self.result = self.result.and_then(|_| {
             if self.is_pretty() {
                 if !self.has_fields {
@@ -394,13 +421,13 @@ impl<'a, 'b: 'a> DebugInner<'a, 'b> {
                 let mut slot = None;
                 let mut state = Default::default();
                 let mut writer = PadAdapter::wrap(self.fmt, &mut slot, &mut state);
-                entry.fmt(&mut writer)?;
+                entry_fmt(&mut writer)?;
                 writer.write_str(",\n")
             } else {
                 if self.has_fields {
                     self.fmt.write_str(", ")?
                 }
-                entry.fmt(self.fmt)
+                entry_fmt(self.fmt)
             }
         });
 
@@ -475,7 +502,20 @@ impl<'a, 'b: 'a> DebugSet<'a, 'b> {
     /// ```
     #[stable(feature = "debug_builders", since = "1.2.0")]
     pub fn entry(&mut self, entry: &dyn fmt::Debug) -> &mut Self {
-        self.inner.entry(entry);
+        self.inner.entry_with(|f| entry.fmt(f));
+        self
+    }
+
+    /// Adds a new entry to the set output.
+    ///
+    /// This method is equivalent to [`DebugSet::entry`], but formats the
+    /// entry using a provided closure rather than by calling [`Debug::fmt`].
+    #[unstable(feature = "debug_closure_helpers", issue = "117729")]
+    pub fn entry_with<F>(&mut self, entry_fmt: F) -> &mut Self
+    where
+        F: FnOnce(&mut fmt::Formatter<'_>) -> fmt::Result,
+    {
+        self.inner.entry_with(entry_fmt);
         self
     }
 
@@ -605,7 +645,20 @@ impl<'a, 'b: 'a> DebugList<'a, 'b> {
     /// ```
     #[stable(feature = "debug_builders", since = "1.2.0")]
     pub fn entry(&mut self, entry: &dyn fmt::Debug) -> &mut Self {
-        self.inner.entry(entry);
+        self.inner.entry_with(|f| entry.fmt(f));
+        self
+    }
+
+    /// Adds a new entry to the list output.
+    ///
+    /// This method is equivalent to [`DebugList::entry`], but formats the
+    /// entry using a provided closure rather than by calling [`Debug::fmt`].
+    #[unstable(feature = "debug_closure_helpers", issue = "117729")]
+    pub fn entry_with<F>(&mut self, entry_fmt: F) -> &mut Self
+    where
+        F: FnOnce(&mut fmt::Formatter<'_>) -> fmt::Result,
+    {
+        self.inner.entry_with(entry_fmt);
         self
     }
 
@@ -775,6 +828,18 @@ impl<'a, 'b: 'a> DebugMap<'a, 'b> {
     /// ```
     #[stable(feature = "debug_map_key_value", since = "1.42.0")]
     pub fn key(&mut self, key: &dyn fmt::Debug) -> &mut Self {
+        self.key_with(|f| key.fmt(f))
+    }
+
+    /// Adds the key part of a new entry to the map output.
+    ///
+    /// This method is equivalent to [`DebugMap::key`], but formats the
+    /// key using a provided closure rather than by calling [`Debug::fmt`].
+    #[unstable(feature = "debug_closure_helpers", issue = "117729")]
+    pub fn key_with<F>(&mut self, key_fmt: F) -> &mut Self
+    where
+        F: FnOnce(&mut fmt::Formatter<'_>) -> fmt::Result,
+    {
         self.result = self.result.and_then(|_| {
             assert!(
                 !self.has_key,
@@ -789,13 +854,13 @@ impl<'a, 'b: 'a> DebugMap<'a, 'b> {
                 let mut slot = None;
                 self.state = Default::default();
                 let mut writer = PadAdapter::wrap(self.fmt, &mut slot, &mut self.state);
-                key.fmt(&mut writer)?;
+                key_fmt(&mut writer)?;
                 writer.write_str(": ")?;
             } else {
                 if self.has_fields {
                     self.fmt.write_str(", ")?
                 }
-                key.fmt(self.fmt)?;
+                key_fmt(self.fmt)?;
                 self.fmt.write_str(": ")?;
             }
 
@@ -839,16 +904,28 @@ impl<'a, 'b: 'a> DebugMap<'a, 'b> {
     /// ```
     #[stable(feature = "debug_map_key_value", since = "1.42.0")]
     pub fn value(&mut self, value: &dyn fmt::Debug) -> &mut Self {
+        self.value_with(|f| value.fmt(f))
+    }
+
+    /// Adds the value part of a new entry to the map output.
+    ///
+    /// This method is equivalent to [`DebugMap::value`], but formats the
+    /// value using a provided closure rather than by calling [`Debug::fmt`].
+    #[unstable(feature = "debug_closure_helpers", issue = "117729")]
+    pub fn value_with<F>(&mut self, value_fmt: F) -> &mut Self
+    where
+        F: FnOnce(&mut fmt::Formatter<'_>) -> fmt::Result,
+    {
         self.result = self.result.and_then(|_| {
             assert!(self.has_key, "attempted to format a map value before its key");
 
             if self.is_pretty() {
                 let mut slot = None;
                 let mut writer = PadAdapter::wrap(self.fmt, &mut slot, &mut self.state);
-                value.fmt(&mut writer)?;
+                value_fmt(&mut writer)?;
                 writer.write_str(",\n")?;
             } else {
-                value.fmt(self.fmt)?;
+                value_fmt(self.fmt)?;
             }
 
             self.has_key = false;
@@ -936,3 +1013,44 @@ impl<'a, 'b: 'a> DebugMap<'a, 'b> {
         self.fmt.alternate()
     }
 }
+
+/// Implements [`fmt::Debug`] and [`fmt::Display`] using a function.
+///
+/// # Examples
+///
+/// ```
+/// #![feature(debug_closure_helpers)]
+/// use std::fmt;
+///
+/// let value = 'a';
+/// assert_eq!(format!("{}", value), "a");
+/// assert_eq!(format!("{:?}", value), "'a'");
+///
+/// let wrapped = fmt::FormatterFn(|f| write!(f, "{:?}", &value));
+/// assert_eq!(format!("{}", wrapped), "'a'");
+/// assert_eq!(format!("{:?}", wrapped), "'a'");
+/// ```
+#[unstable(feature = "debug_closure_helpers", issue = "117729")]
+pub struct FormatterFn<F>(pub F)
+where
+    F: Fn(&mut fmt::Formatter<'_>) -> fmt::Result;
+
+#[unstable(feature = "debug_closure_helpers", issue = "117729")]
+impl<F> fmt::Debug for FormatterFn<F>
+where
+    F: Fn(&mut fmt::Formatter<'_>) -> fmt::Result,
+{
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        (self.0)(f)
+    }
+}
+
+#[unstable(feature = "debug_closure_helpers", issue = "117729")]
+impl<F> fmt::Display for FormatterFn<F>
+where
+    F: Fn(&mut fmt::Formatter<'_>) -> fmt::Result,
+{
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        (self.0)(f)
+    }
+}
diff --git a/library/core/src/fmt/mod.rs b/library/core/src/fmt/mod.rs
index c45ab094a20..e1b7b46a1ed 100644
--- a/library/core/src/fmt/mod.rs
+++ b/library/core/src/fmt/mod.rs
@@ -39,6 +39,9 @@ pub enum Alignment {
 #[stable(feature = "debug_builders", since = "1.2.0")]
 pub use self::builders::{DebugList, DebugMap, DebugSet, DebugStruct, DebugTuple};
 
+#[unstable(feature = "debug_closure_helpers", issue = "117729")]
+pub use self::builders::FormatterFn;
+
 /// The type returned by formatter methods.
 ///
 /// # Examples
diff --git a/library/core/src/slice/mod.rs b/library/core/src/slice/mod.rs
index 45080eda2ce..6cf5d48a167 100644
--- a/library/core/src/slice/mod.rs
+++ b/library/core/src/slice/mod.rs
@@ -640,6 +640,11 @@ impl<T> [T] {
     /// Calling this method with an out-of-bounds index is *[undefined behavior]*
     /// even if the resulting reference is not used.
     ///
+    /// You can think of this like `.get(index).unwrap_unchecked()`.  It's UB
+    /// to call `.get_unchecked(len)`, even if you immediately convert to a
+    /// pointer.  And it's UB to call `.get_unchecked(..len + 1)`,
+    /// `.get_unchecked(..=len)`, or similar.
+    ///
     /// [`get`]: slice::get
     /// [undefined behavior]: https://doc.rust-lang.org/reference/behavior-considered-undefined.html
     ///
@@ -675,6 +680,11 @@ impl<T> [T] {
     /// Calling this method with an out-of-bounds index is *[undefined behavior]*
     /// even if the resulting reference is not used.
     ///
+    /// You can think of this like `.get_mut(index).unwrap_unchecked()`.  It's
+    /// UB to call `.get_unchecked_mut(len)`, even if you immediately convert
+    /// to a pointer.  And it's UB to call `.get_unchecked_mut(..len + 1)`,
+    /// `.get_unchecked_mut(..=len)`, or similar.
+    ///
     /// [`get_mut`]: slice::get_mut
     /// [undefined behavior]: https://doc.rust-lang.org/reference/behavior-considered-undefined.html
     ///
diff --git a/src/doc/rustc/src/exploit-mitigations.md b/src/doc/rustc/src/exploit-mitigations.md
index 172048704f4..d4e2fc52e97 100644
--- a/src/doc/rustc/src/exploit-mitigations.md
+++ b/src/doc/rustc/src/exploit-mitigations.md
@@ -1,12 +1,12 @@
 # Exploit Mitigations
 
-This chapter documents the exploit mitigations supported by the Rust
-compiler, and is by no means an extensive survey of the Rust programming
-language’s security features.
+This chapter documents the exploit mitigations supported by the Rust compiler,
+and is by no means an extensive survey of the Rust programming language’s
+security features.
 
 This chapter is for software engineers working with the Rust programming
-language, and assumes prior knowledge of the Rust programming language and
-its toolchain.
+language, and assumes prior knowledge of the Rust programming language and its
+toolchain.
 
 
 ## Introduction
@@ -14,8 +14,8 @@ its toolchain.
 The Rust programming language provides memory[1] and thread[2] safety
 guarantees via its ownership[3], references and borrowing[4], and slice
 types[5] features. However, Unsafe Rust[6] introduces unsafe blocks, unsafe
-functions and methods, unsafe traits, and new types that are not subject to
-the borrowing rules.
+functions and methods, unsafe traits, and new types that are not subject to the
+borrowing rules.
 
 Parts of the Rust standard library are implemented as safe abstractions over
 unsafe code (and historically have been vulnerable to memory corruption[7]).
@@ -23,33 +23,32 @@ Furthermore, the Rust code and documentation encourage creating safe
 abstractions over unsafe code. This can cause a false sense of security if
 unsafe code is not properly reviewed and tested.
 
-Unsafe Rust introduces features that do not provide the same memory and
-thread safety guarantees. This causes programs or libraries to be
-susceptible to memory corruption (CWE-119)[8] and concurrency issues
-(CWE-557)[9]. Modern C and C++ compilers provide exploit mitigations to
-increase the difficulty to exploit vulnerabilities resulting from these
-issues. Therefore, the Rust compiler must also support these exploit
-mitigations in order to mitigate vulnerabilities resulting from the use of
-Unsafe Rust. This chapter documents these exploit mitigations and how they
-apply to Rust.
+Unsafe Rust introduces features that do not provide the same memory and thread
+safety guarantees. This causes programs or libraries to be susceptible to
+memory corruption (CWE-119)[8] and concurrency issues (CWE-557)[9]. Modern C
+and C++ compilers provide exploit mitigations to increase the difficulty to
+exploit vulnerabilities resulting from these issues. Therefore, the Rust
+compiler must also support these exploit mitigations in order to mitigate
+vulnerabilities resulting from the use of Unsafe Rust. This chapter documents
+these exploit mitigations and how they apply to Rust.
 
-This chapter does not discuss the effectiveness of these exploit mitigations
-as they vary greatly depending on several factors besides their design and
-implementation, but rather describe what they do, so their effectiveness can
-be understood within a given context.
+This chapter does not discuss the effectiveness of these exploit mitigations as
+they vary greatly depending on several factors besides their design and
+implementation, but rather describe what they do, so their effectiveness can be
+understood within a given context.
 
 
 ## Exploit mitigations
 
-This section documents the exploit mitigations applicable to the Rust
-compiler when building programs for the Linux operating system on the AMD64
-architecture and equivalent.<sup id="fnref:1" role="doc-noteref"><a
-href="#fn:1" class="footnote">1</a></sup>
+This section documents the exploit mitigations applicable to the Rust compiler
+when building programs for the Linux operating system on the AMD64 architecture
+and equivalent.<sup id="fnref:1" role="doc-noteref"><a href="#fn:1"
+class="footnote">1</a></sup> All examples in this section were built using
+nightly builds of the Rust compiler on Debian testing.
 
-The Rust Programming Language currently has no specification. The Rust
-compiler (i.e., rustc) is the language reference implementation. All
-references to “the Rust compiler” in this chapter refer to the language
-reference implementation.
+The Rust Programming Language currently has no specification. The Rust compiler
+(i.e., rustc) is the language reference implementation. All references to “the
+Rust compiler” in this chapter refer to the language reference implementation.
 
 Table I \
 Summary of exploit mitigations supported by the Rust compiler when building
@@ -83,8 +82,8 @@ instructing the dynamic linker to load it similarly to a shared object at a
 random load address, thus also benefiting from address-space layout
 randomization (ASLR). This is also referred to as “full ASLR”.
 
-The Rust compiler supports position-independent executable, and enables it
-by default since version 0.12.0 (2014-10-09)[10]–[13].
+The Rust compiler supports position-independent executable, and enables it by
+default since version 0.12.0 (2014-10-09)[10]–[13].
 
 ```text
 $ readelf -h target/release/hello-rust | grep Type:
@@ -93,8 +92,7 @@ $ readelf -h target/release/hello-rust | grep Type:
 Fig. 1. Checking if an executable is a position-independent executable.
 
 An executable with an object type of `ET_DYN` (i.e., shared object) and not
-`ET_EXEC` (i.e., executable) is a position-independent executable (see Fig.
-1).
+`ET_EXEC` (i.e., executable) is a position-independent executable (see Fig. 1).
 
 
 ### Integer overflow checks
@@ -104,8 +102,11 @@ behavior (which may cause vulnerabilities) by checking for results of signed
 and unsigned integer computations that cannot be represented in their type,
 resulting in an overflow or wraparound.
 
-The Rust compiler supports integer overflow checks, and enables it when
-debug assertions are enabled since version 1.1.0 (2015-06-25)[14]–[20].
+The Rust compiler supports integer overflow checks, and enables it when debug
+assertions are enabled since version 1.0.0 (2015-05-15)[14]–[17], but support
+for it was not completed until version 1.1.0 (2015-06-25)[16]. An option to
+control integer overflow checks was later stabilized in version 1.17.0
+(2017-04-27)[18]–[20].
 
 ```compile_fail
 fn main() {
@@ -136,21 +137,21 @@ u: 0
 Fig. 4. Build and execution of hello-rust-integer with debug assertions
 disabled.
 
-Integer overflow checks are enabled when debug assertions are enabled (see
-Fig. 3), and disabled when debug assertions are disabled (see Fig. 4). To
-enable integer overflow checks independently, use the option to control
-integer overflow checks, scoped attributes, or explicit checking methods
-such as `checked_add`<sup id="fnref:2" role="doc-noteref"><a href="#fn:2"
+Integer overflow checks are enabled when debug assertions are enabled (see Fig.
+3), and disabled when debug assertions are disabled (see Fig. 4). To enable
+integer overflow checks independently, use the option to control integer
+overflow checks, scoped attributes, or explicit checking methods such as
+`checked_add`<sup id="fnref:2" role="doc-noteref"><a href="#fn:2"
 class="footnote">2</a></sup>.
 
-It is recommended that explicit wrapping methods such as `wrapping_add` be
-used when wrapping semantics are intended, and that explicit checking and
-wrapping methods always be used when using Unsafe Rust.
+It is recommended that explicit wrapping methods such as `wrapping_add` be used
+when wrapping semantics are intended, and that explicit checking and wrapping
+methods always be used when using Unsafe Rust.
 
-<small id="fn:2">2\. See [the `u32` docs](../std/primitive.u32.html)
-for more information on the checked, overflowing, saturating, and wrapping
-methods (using u32 as an example). <a href="#fnref:2"
-class="reversefootnote" role="doc-backlink">↩</a></small>
+<small id="fn:2">2\. See [the `u32` docs](../std/primitive.u32.html) for more
+information on the checked, overflowing, saturating, and wrapping methods
+(using u32 as an example). <a href="#fnref:2" class="reversefootnote"
+role="doc-backlink">↩</a></small>
 
 
 ### Non-executable memory regions
@@ -158,17 +159,16 @@ class="reversefootnote" role="doc-backlink">↩</a></small>
 Non-executable memory regions increase the difficulty of exploitation by
 limiting the memory regions that can be used to execute arbitrary code. Most
 modern processors provide support for the operating system to mark memory
-regions as non executable, but it was previously emulated by software, such
-as in grsecurity/PaX's
-[PAGEEXEC](https://pax.grsecurity.net/docs/pageexec.txt) and
-[SEGMEXEC](https://pax.grsecurity.net/docs/segmexec.txt), on processors that
-did not provide support for it. This is also known as “No Execute (NX) Bit”,
-“Execute Disable (XD) Bit”, “Execute Never (XN) Bit”, and others.
+regions as non executable, but it was previously emulated by software, such as
+in grsecurity/PaX’s [PAGEEXEC](https://pax.grsecurity.net/docs/pageexec.txt)
+and [SEGMEXEC](https://pax.grsecurity.net/docs/segmexec.txt), on processors
+that did not provide support for it. This is also known as “No Execute (NX)
+Bit”, “Execute Disable (XD) Bit”, “Execute Never (XN) Bit”, and others.
 
 The Rust compiler supports non-executable memory regions, and enables it by
-default since its initial release, version 0.1 (2012-01-20)[21], [22], but
-has regressed since then[23]–[25], and enforced by default since version
-1.8.0 (2016-04-14)[25].
+default since its initial release, version 0.1 (2012-01-20)[21], [22], but has
+regressed since then[23]–[25], and enforced by default since version 1.8.0
+(2016-04-14)[25].
 
 ```text
 $ readelf -l target/release/hello-rust | grep -A 1 GNU_STACK
@@ -178,9 +178,9 @@ $ readelf -l target/release/hello-rust | grep -A 1 GNU_STACK
 Fig. 5. Checking if non-executable memory regions are enabled for a given
 binary.
 
-The presence of an element of type `PT_GNU_STACK` in the program header
-table with the `PF_X` (i.e., executable) flag unset indicates non-executable
-memory regions<sup id="fnref:3" role="doc-noteref"><a href="#fn:3"
+The presence of an element of type `PT_GNU_STACK` in the program header table
+with the `PF_X` (i.e., executable) flag unset indicates non-executable memory
+regions<sup id="fnref:3" role="doc-noteref"><a href="#fn:3"
 class="footnote">3</a></sup> are enabled for a given binary (see Fig. 5).
 Conversely, the presence of an element of type `PT_GNU_STACK` in the program
 header table with the `PF_X` flag set or the absence of an element of type
@@ -196,38 +196,40 @@ class="reversefootnote" role="doc-backlink">↩</a></small>
 
 Stack clashing protection protects the stack from overlapping with another
 memory region—allowing arbitrary data in both to be overwritten using each
-other—by reading from the stack pages as the stack grows to cause a page
-fault when attempting to read from the guard page/region. This is also
-referred to as “stack probes” or “stack probing”.
+other—by reading from the stack pages as the stack grows to cause a page fault
+when attempting to read from the guard page/region. This is also referred to as
+“stack probes” or “stack probing”.
 
 The Rust compiler supports stack clashing protection via stack probing, and
 enables it by default since version 1.20.0 (2017-08-31)[26]–[29].
 
-![Screenshot of IDA Pro listing cross references to __rust_probestack in hello-rust.](images/image1.png "Cross references to __rust_probestack in hello-rust.")
-Fig. 6. IDA Pro listing cross references to `__rust_probestack` in
-hello-rust.
-
 ```rust
-fn hello() {
-    println!("Hello, world!");
+fn main() {
+    let v: [u8; 16384] = [1; 16384];
+    let first = &v[0];
+    println!("The first element is: {first}");
 }
+```
+Fig. 6. hello-rust-stack-probe-1 program.
 
+![Screenshot of IDA Pro listing the "unrolled loop" stack probe variant in modified hello-rust.](images/image1.png "The \"unrolled loop\" stack probe variant in modified hello-rust.")
+Fig. 7. The "unrolled loop" stack probe variant in modified hello-rust.
+
+```rust
 fn main() {
-    let _: [u64; 1024] = [0; 1024];
-    hello();
+    let v: [u8; 65536] = [1; 65536];
+    let first = &v[0];
+    println!("The first element is: {first}");
 }
 ```
-Fig 7. Modified hello-rust.
+Fig. 8. hello-rust-stack-probe-2 program.
 
-![Screenshot of IDA Pro listing cross references to __rust_probestack in modified hello-rust.](images/image2.png "Cross references to __rust_probestack in modified hello-rust.")
-Fig. 8. IDA Pro listing cross references to `__rust_probestack` in modified
-hello-rust.
+![Screenshot of IDA Pro listing the "standard loop" stack probe variant in modified hello-rust.](images/image2.png "The \"standard loop\" stack probe variant in modified hello-rust.")
+Fig. 9. The "standard loop" stack probe variant in modified hello-rust.
 
-To check if stack clashing protection is enabled for a given binary, search
-for cross references to `__rust_probestack`. The `__rust_probestack` is
-called in the prologue of functions whose stack size is larger than a page
-size (see Fig. 6), and can be forced for illustration purposes by modifying
-the hello-rust example as seen in Fig. 7 and Fig. 8.
+To check if stack clashing protection is enabled for a given binary, look for
+any of the two stack probe variants in the prologue of functions whose stack
+size is larger than a page size (see Figs. 6–9).
 
 
 ### Read-only relocations and immediate binding
@@ -246,21 +248,20 @@ $ readelf -l target/release/hello-rust | grep GNU_RELRO
 ```
 Fig. 9. Checking if read-only relocations is enabled for a given binary.
 
-The presence of an element of type `PT_GNU_RELRO` in the program header
-table indicates read-only relocations are enabled for a given binary (see
-Fig. 9). Conversely, the absence of an element of type `PT_GNU_RELRO` in the
-program header table indicates read-only relocations are not enabled for a
-given binary.
+The presence of an element of type `PT_GNU_RELRO` in the program header table
+indicates read-only relocations are enabled for a given binary (see Fig. 9).
+Conversely, the absence of an element of type `PT_GNU_RELRO` in the program
+header table indicates read-only relocations are not enabled for a given
+binary.
 
 **Immediate binding** protects additional segments containing relocations
-(i.e., `.got.plt`) from being overwritten by instructing the dynamic linker
-to perform all relocations before transferring control to the program during
-startup, so all segments containing relocations can be marked read only
-(when combined with read-only relocations). This is also referred to as
-“full RELRO”.
+(i.e., `.got.plt`) from being overwritten by instructing the dynamic linker to
+perform all relocations before transferring control to the program during
+startup, so all segments containing relocations can be marked read only (when
+combined with read-only relocations). This is also referred to as “full RELRO”.
 
-The Rust compiler supports immediate binding, and enables it by default
-since version 1.21.0 (2017-10-12)[30], [31].
+The Rust compiler supports immediate binding, and enables it by default since
+version 1.21.0 (2017-10-12)[30], [31].
 
 ```text
 $ readelf -d target/release/hello-rust | grep BIND_NOW
@@ -270,16 +271,15 @@ Fig. 10. Checking if immediate binding is enabled for a given binary.
 
 The presence of an element with the `DT_BIND_NOW` tag and the `DF_BIND_NOW`
 flag<sup id="fnref:4" role="doc-noteref"><a href="#fn:4"
-class="footnote">4</a></sup> in the dynamic section indicates immediate
-binding is enabled for a given binary (see Fig. 10). Conversely, the absence
-of an element with the `DT_BIND_NOW` tag and the `DF_BIND_NOW` flag in the
-dynamic section indicates immediate binding is not enabled for a given
-binary.
+class="footnote">4</a></sup> in the dynamic section indicates immediate binding
+is enabled for a given binary (see Fig. 10). Conversely, the absence of an
+element with the `DT_BIND_NOW` tag and the `DF_BIND_NOW` flag in the dynamic
+section indicates immediate binding is not enabled for a given binary.
 
 The presence of both an element of type `PT_GNU_RELRO` in the program header
-table and of an element with the `DT_BIND_NOW` tag and the `DF_BIND_NOW`
-flag in the dynamic section indicates full RELRO is enabled for a given
-binary (see Fig. 9 and Fig. 10).
+table and of an element with the `DT_BIND_NOW` tag and the `DF_BIND_NOW` flag
+in the dynamic section indicates full RELRO is enabled for a given binary (see
+Figs. 9–10).
 
 <small id="fn:4">4\. And the `DF_1_NOW` flag for some link editors. <a
 href="#fnref:4" class="reversefootnote" role="doc-backlink">↩</a></small>
@@ -287,26 +287,24 @@ href="#fnref:4" class="reversefootnote" role="doc-backlink">↩</a></small>
 
 ### Heap corruption protection
 
-Heap corruption protection protects memory allocated dynamically by
-performing several checks, such as checks for corrupted links between list
-elements, invalid pointers, invalid sizes, double/multiple “frees” of the
-same memory allocated, and many corner cases of these. These checks are
-implementation specific, and vary per allocator.
+Heap corruption protection protects memory allocated dynamically by performing
+several checks, such as checks for corrupted links between list elements,
+invalid pointers, invalid sizes, double/multiple “frees” of the same memory
+allocated, and many corner cases of these. These checks are implementation
+specific, and vary per allocator.
 
 [ARM Memory Tagging Extension
 (MTE)](https://community.arm.com/developer/ip-products/processors/b/processors-ip-blog/posts/enhancing-memory-safety),
-when available, will provide hardware assistance for a probabilistic
-mitigation to detect memory safety violations by tagging memory allocations,
-and automatically checking that the correct tag is used on every memory
-access.
+when available, will provide hardware assistance for a probabilistic mitigation
+to detect memory safety violations by tagging memory allocations, and
+automatically checking that the correct tag is used on every memory access.
 
 Rust’s default allocator has historically been
-[jemalloc](http://jemalloc.net/), and it has long been the cause of issues
-and the subject of much discussion[32]–[38]. Consequently, it has been
-removed as the default allocator in favor of the operating system’s standard
-C library default allocator<sup id="fnref:5" role="doc-noteref"><a
-href="#fn:5" class="footnote">5</a></sup> since version 1.32.0
-(2019-01-17)[39].
+[jemalloc](http://jemalloc.net/), and it has long been the cause of issues and
+the subject of much discussion[32]–[38]. Consequently, it has been removed as
+the default allocator in favor of the operating system’s standard C library
+default allocator<sup id="fnref:5" role="doc-noteref"><a href="#fn:5"
+class="footnote">5</a></sup> since version 1.32.0 (2019-01-17)[39].
 
 ```rust,no_run
 fn main() {
@@ -330,8 +328,7 @@ $ cargo run
 free(): invalid next size (normal)
 Aborted
 ```
-Fig. 12. Build and execution of hello-rust-heap with debug assertions
-enabled.
+Fig. 12. Build and execution of hello-rust-heap with debug assertions enabled.
 
 ```text
 $ cargo run --release
@@ -341,47 +338,41 @@ $ cargo run --release
 free(): invalid next size (normal)
 Aborted
 ```
-Fig. 13. Build and execution of hello-rust-heap with debug assertions
-disabled.
+Fig. 13. Build and execution of hello-rust-heap with debug assertions disabled.
 
-Heap corruption checks are being performed when using the default allocator
-(i.e., the GNU Allocator) as seen in Fig. 12 and Fig. 13.
+Heap corruption checks are performed when using the default allocator (i.e.,
+the GNU Allocator) (see Figs. 12–13).
 
 <small id="fn:5">5\. Linux's standard C library default allocator is the GNU
-Allocator, which is derived from ptmalloc (pthreads malloc) by Wolfram
-Gloger, which in turn is derived from dlmalloc (Doug Lea malloc) by Doug
-Lea. <a href="#fnref:5" class="reversefootnote"
-role="doc-backlink">↩</a></small>
+Allocator, which is derived from ptmalloc (pthreads malloc) by Wolfram Gloger,
+which in turn is derived from dlmalloc (Doug Lea malloc) by Doug Lea. <a
+href="#fnref:5" class="reversefootnote" role="doc-backlink">↩</a></small>
 
 
 ### Stack smashing protection
 
-Stack smashing protection protects programs from stack-based buffer
-overflows by inserting a random guard value between local variables and the
-saved return instruction pointer, and checking if this value has changed
-when returning from a function. This is also known as “Stack Protector” or
-“Stack Smashing Protector (SSP)”.
+Stack smashing protection protects programs from stack-based buffer overflows
+by inserting a random guard value between local variables and the saved return
+instruction pointer, and checking if this value has changed when returning from
+a function. This is also known as “Stack Protector” or “Stack Smashing
+Protector (SSP)”.
 
-The Rust compiler supports stack smashing protection on nightly builds[42].
+The Rust compiler supports stack smashing protection on nightly builds[40].
 
 ![Screenshot of IDA Pro listing cross references to __stack_chk_fail in hello-rust.](images/image3.png "Cross references to __stack_chk_fail in hello-rust.")
-Fig. 14. IDA Pro listing cross references to `__stack_chk_fail` in
-hello-rust.
+Fig. 14. IDA Pro listing cross references to `__stack_chk_fail` in hello-rust.
 
-To check if stack smashing protection is enabled for a given binary, search
-for cross references to `__stack_chk_fail`. The presence of these
-cross-references in Rust-compiled code (e.g., `hello_rust::main`) indicates
-that the stack smashing protection is enabled (see Fig. 14).
+To check if stack smashing protection is enabled for a given binary, search for
+cross references to `__stack_chk_fail` (see Fig. 14).
 
 
 ### Forward-edge control flow protection
 
-Forward-edge control flow protection protects programs from having its
-control flow changed/hijacked by performing checks to ensure that
-destinations of indirect branches are one of their valid destinations in the
-control flow graph. The comprehensiveness of these checks vary per
-implementation. This is also known as “forward-edge control flow integrity
-(CFI)”.
+Forward-edge control flow protection protects programs from having its control
+flow changed/hijacked by performing checks to ensure that destinations of
+indirect branches are one of their valid destinations in the control flow
+graph. The comprehensiveness of these checks vary per implementation. This is
+also known as “forward-edge control flow integrity (CFI)”.
 
 Newer processors provide hardware assistance for forward-edge control flow
 protection, such as ARM Branch Target Identification (BTI), ARM Pointer
@@ -394,22 +385,19 @@ commercially available [grsecurity/PaX Reuse Attack Protector
 (RAP)](https://grsecurity.net/rap_faq).
 
 The Rust compiler supports forward-edge control flow protection on nightly
-builds[40]-[41] <sup id="fnref:6" role="doc-noteref"><a href="#fn:6"
+builds[41]-[42] <sup id="fnref:6" role="doc-noteref"><a href="#fn:6"
 class="footnote">6</a></sup>.
 
 ```text
-$ readelf -s -W target/debug/rust-cfi | grep "\.cfi"
-    12: 0000000000005170    46 FUNC    LOCAL  DEFAULT   14 _RNvCsjaOHoaNjor6_8rust_cfi7add_one.cfi
-    15: 00000000000051a0    16 FUNC    LOCAL  DEFAULT   14 _RNvCsjaOHoaNjor6_8rust_cfi7add_two.cfi
-    17: 0000000000005270   396 FUNC    LOCAL  DEFAULT   14 _RNvCsjaOHoaNjor6_8rust_cfi4main.cfi
-...
+$ readelf -s -W target/release/hello-rust | grep "\.cfi"
+     5: 0000000000006480   657 FUNC    LOCAL  DEFAULT   15 _ZN10hello_rust4main17h4e359f1dcd627c83E.cfi
 ```
-Fig. 15. Checking if LLVM CFI is enabled for a given binary[41].
+Fig. 15. Checking if LLVM CFI is enabled for a given binary.
 
 The presence of symbols suffixed with ".cfi" or the `__cfi_init` symbol (and
-references to `__cfi_check`) indicates that LLVM CFI (i.e., forward-edge control
-flow protection) is enabled for a given binary. Conversely, the absence of
-symbols suffixed with ".cfi" or the `__cfi_init` symbol (and references to
+references to `__cfi_check`) indicates that LLVM CFI (i.e., forward-edge
+control flow protection) is enabled for a given binary. Conversely, the absence
+of symbols suffixed with ".cfi" or the `__cfi_init` symbol (and references to
 `__cfi_check`) indicates that LLVM CFI is not enabled for a given binary (see
 Fig. 15).
 
@@ -421,48 +409,47 @@ class="reversefootnote" role="doc-backlink">↩</a></small>
 ### Backward-edge control flow protection
 
 **Shadow stack** protects saved return instruction pointers from being
-overwritten by storing a copy of them on a separate (shadow) stack, and
-using these copies as authoritative values when returning from functions.
-This is also known as “ShadowCallStack” and “Return Flow Guard”, and is
-considered an implementation of backward-edge control flow protection (or
-“backward-edge CFI”).
+overwritten by storing a copy of them on a separate (shadow) stack, and using
+these copies as authoritative values when returning from functions. This is
+also known as “ShadowCallStack” and “Return Flow Guard”, and is considered an
+implementation of backward-edge control flow protection (or “backward-edge
+CFI”).
 
 **Safe stack** protects not only the saved return instruction pointers, but
-also register spills and some local variables from being overwritten by
-storing unsafe variables, such as large arrays, on a separate (unsafe)
-stack, and using these unsafe variables on the separate stack instead. This
-is also known as “SafeStack”, and is also considered an implementation of
-backward-edge control flow protection.
+also register spills and some local variables from being overwritten by storing
+unsafe variables, such as large arrays, on a separate (unsafe) stack, and using
+these unsafe variables on the separate stack instead. This is also known as
+“SafeStack”, and is also considered an implementation of backward-edge control
+flow protection.
 
-Both shadow and safe stack are intended to be a more comprehensive
-alternatives to stack smashing protection as they protect the saved return
-instruction pointers (and other data in the case of safe stack) from
-arbitrary writes and non-linear out-of-bounds writes.
+Both shadow and safe stack are intended to be a more comprehensive alternatives
+to stack smashing protection as they protect the saved return instruction
+pointers (and other data in the case of safe stack) from arbitrary writes and
+non-linear out-of-bounds writes.
 
 Newer processors provide hardware assistance for backward-edge control flow
-protection, such as ARM Pointer Authentication, and Intel Shadow Stack as
-part of Intel CET.
+protection, such as ARM Pointer Authentication, and Intel Shadow Stack as part
+of Intel CET.
 
-The Rust compiler supports shadow stack for aarch64 only
-<sup id="fnref:7" role="doc-noteref"><a href="#fn:7" class="footnote">7</a></sup>
-on nightly Rust compilers [43]-[44]. Safe stack is available on nightly
-Rust compilers [45]-[46].
+The Rust compiler supports shadow stack for the AArch64 architecture<sup
+id="fnref:7" role="doc-noteref"><a href="#fn:7" class="footnote">7</a></sup>on
+nightly builds[43]-[44], and also supports safe stack on nightly
+builds[45]-[46].
 
 ```text
 $ readelf -s target/release/hello-rust | grep __safestack_init
-  1177: 00000000000057b0   444 FUNC    GLOBAL DEFAULT    9 __safestack_init
+   678: 0000000000008c80   426 FUNC    GLOBAL DEFAULT   15 __safestack_init
 ```
 Fig. 16. Checking if LLVM SafeStack is enabled for a given binary.
 
-The presence of the `__safestack_init` symbol indicates that LLVM SafeStack
-is enabled for a given binary (see Fig. 16). Conversely, the absence of the
-`__safestack_init` symbol indicates that LLVM SafeStack is not enabled for a
-given binary.
+The presence of the `__safestack_init` symbol indicates that LLVM SafeStack is
+enabled for a given binary. Conversely, the absence of the `__safestack_init`
+symbol indicates that LLVM SafeStack is not enabled for a given binary (see
+Fig. 16).
 
-<small id="fn:7">7\. The shadow stack implementation for the AMD64
-architecture and equivalent in LLVM was removed due to performance and
-security issues. <a href="#fnref:7" class="reversefootnote"
-role="doc-backlink">↩</a></small>
+<small id="fn:7">7\. The shadow stack implementation for the AMD64 architecture
+and equivalent in LLVM was removed due to performance and security issues. <a
+href="#fnref:7" class="reversefootnote" role="doc-backlink">↩</a></small>
 
 
 ## Appendix
@@ -470,29 +457,28 @@ role="doc-backlink">↩</a></small>
 As of the latest version of the [Linux Standard Base (LSB) Core
 Specification](https://refspecs.linuxfoundation.org/LSB_5.0.0/LSB-Core-generic/LSB-Core-generic/progheader.html),
 the `PT_GNU_STACK` program header indicates whether the stack should be
-executable, and the absence of this header indicates that the stack should
-be executable. However, the Linux kernel currently sets the
-`READ_IMPLIES_EXEC` personality upon loading any executable with the
-`PT_GNU_STACK` program header and the `PF_X `flag set or with the absence of
-this header, resulting in not only the stack, but also all readable virtual
-memory mappings being executable.
+executable, and the absence of this header indicates that the stack should be
+executable. However, the Linux kernel currently sets the `READ_IMPLIES_EXEC`
+personality upon loading any executable with the `PT_GNU_STACK` program header
+and the `PF_X` flag set or with the absence of this header, resulting in not
+only the stack, but also all readable virtual memory mappings being executable.
 
 An attempt to fix this [was made in
 2012](https://lore.kernel.org/lkml/f298f914-2239-44e4-8aa1-a51282e7fac0@zmail15.collab.prod.int.phx2.redhat.com/),
 and another [was made in
 2020](https://lore.kernel.org/kernel-hardening/20200327064820.12602-1-keescook@chromium.org/).
 The former never landed, and the latter partially fixed it, but introduced
-other issues—the absence of the `PT_GNU_STACK` program header still causes
-not only the stack, but also all readable virtual memory mappings to be
-executable in some architectures, such as IA-32 and equivalent (or causes
-the stack to be non-executable in some architectures, such as AMD64 and
-equivalent, contradicting the LSB).
+other issues—the absence of the `PT_GNU_STACK` program header still causes not
+only the stack, but also all readable virtual memory mappings to be executable
+in some architectures, such as IA-32 and equivalent (or causes the stack to be
+non-executable in some architectures, such as AMD64 and equivalent,
+contradicting the LSB).
 
-The `READ_IMPLIES_EXEC` personality needs to be completely separated from
-the `PT_GNU_STACK` program header by having a separate option for it (or
-setarch -X could just be used whenever `READ_IMPLIES_EXEC` is needed), and
-the absence of the `PT_GNU_STACK` program header needs to have more secure
-defaults (unrelated to `READ_IMPLIES_EXEC`).
+The `READ_IMPLIES_EXEC` personality needs to be completely separated from the
+`PT_GNU_STACK` program header by having a separate option for it (or setarch -X
+could just be used whenever `READ_IMPLIES_EXEC` is needed), and the absence of
+the `PT_GNU_STACK` program header needs to have more secure defaults (unrelated
+to `READ_IMPLIES_EXEC`).
 
 
 ## References
@@ -576,19 +562,19 @@ defaults (unrelated to `READ_IMPLIES_EXEC`).
 25. A. Clark. “Explicitly disable stack execution on linux and bsd #30859.”
     GitHub. <https://github.com/rust-lang/rust/pull/30859>.
 
-26. “Replace stack overflow checking with stack probes #16012.” GitHub.
+26. Zoxc. “Replace stack overflow checking with stack probes #16012.” GitHub.
     <https://github.com/rust-lang/rust/issues/16012>.
 
-27. B. Striegel. “Extend stack probe support to non-tier-1 platforms, and
-    clarify policy for mitigating LLVM-dependent unsafety #43241.” GitHub.
-    <https://github.com/rust-lang/rust/issues/43241>.
-
-28. A. Crichton. “rustc: Implement stack probes for x86 #42816.” GitHub.
+27. A. Crichton. “rustc: Implement stack probes for x86 #42816.” GitHub.
     <https://github.com/rust-lang/rust/pull/42816>.
 
-29. A. Crichton. “Add \_\_rust\_probestack intrinsic #175.” GitHub.
+28. A. Crichton. “Add \_\_rust\_probestack intrinsic #175.” GitHub.
     <https://github.com/rust-lang/compiler-builtins/pull/175>.
 
+29. S. Guelton, S. Ledru, J. Stone. “Bringing Stack Clash Protection to Clang /
+    X86 — the Open Source Way.” The LLVM Project Blog.
+    <https://blog.llvm.org/posts/2021-01-05-stack-clash-protection/>.
+
 30. B. Anderson. “Consider applying -Wl,-z,relro or -Wl,-z,relro,-z,now by
     default #29877.” GitHub. <https://github.com/rust-lang/rust/issues/29877>.
 
@@ -621,16 +607,16 @@ defaults (unrelated to `READ_IMPLIES_EXEC`).
 39. A. Crichton. “Remove the alloc\_jemalloc crate #55238.” GitHub.
     <https://github.com/rust-lang/rust/pull/55238>.
 
-40. R. de C Valle. “Tracking Issue for LLVM Control Flow Integrity (CFI) Support
+40. bbjornse. “Add codegen option for using LLVM stack smash protection #84197.”
+    GitHub. <https://github.com/rust-lang/rust/pull/84197>
+
+41. R. de C. Valle. “Tracking Issue for LLVM Control Flow Integrity (CFI) Support
     for Rust #89653.” GitHub. <https://github.com/rust-lang/rust/issues/89653>.
 
-41. “ControlFlowIntegrity.” The Rust Unstable Book.
+42. “ControlFlowIntegrity.” The Rust Unstable Book.
     [https://doc.rust-lang.org/unstable-book/compiler-flags/sanitizer.html#controlflowintegrity](../unstable-book/compiler-flags/sanitizer.html#controlflowintegrity).
 
-42. bbjornse. “add codegen option for using LLVM stack smash protection #84197.”
-    GitHub. <https://github.com/rust-lang/rust/pull/84197>
-
-43. ivanloz. “Add support for LLVM ShadowCallStack. #98208.” GitHub.
+43. I. Lozano. “Add support for LLVM ShadowCallStack #98208.” GitHub.
     <https://github.com/rust-lang/rust/pull/98208>.
 
 44. “ShadowCallStack.” The Rust Unstable Book.
diff --git a/src/doc/rustc/src/images/image1.png b/src/doc/rustc/src/images/image1.png
index ee2d3fd4f43..0da45e56620 100644
--- a/src/doc/rustc/src/images/image1.png
+++ b/src/doc/rustc/src/images/image1.png
Binary files differdiff --git a/src/doc/rustc/src/images/image2.png b/src/doc/rustc/src/images/image2.png
index 03061e1f0b1..a9cf23f8737 100644
--- a/src/doc/rustc/src/images/image2.png
+++ b/src/doc/rustc/src/images/image2.png
Binary files differdiff --git a/src/doc/rustc/src/images/image3.png b/src/doc/rustc/src/images/image3.png
index ef02c605ead..844a2fe6747 100644
--- a/src/doc/rustc/src/images/image3.png
+++ b/src/doc/rustc/src/images/image3.png
Binary files differdiff --git a/tests/rustdoc-json/reexport/pub_use_doc_hidden.rs b/tests/rustdoc-json/reexport/pub_use_doc_hidden.rs
index 46c3da4c15b..15d194ef5d9 100644
--- a/tests/rustdoc-json/reexport/pub_use_doc_hidden.rs
+++ b/tests/rustdoc-json/reexport/pub_use_doc_hidden.rs
@@ -2,11 +2,12 @@
 
 mod repeat_n {
     #[doc(hidden)]
+    /// not here
     pub struct RepeatN {}
 }
 
+/// not here
 pub use repeat_n::RepeatN;
 
 // @count "$.index[*][?(@.name=='pub_use_doc_hidden')].inner.items[*]" 0
-// @!has "$.index[*][?(@.kind=='struct')]"
-// @!has "$.index[*][?(@.kind=='import')]"
+// @!has "$.index[*][?(@.docs == 'not here')]"
diff --git a/tests/ui/parser/semi-in-let-chain.rs b/tests/ui/parser/semi-in-let-chain.rs
new file mode 100644
index 00000000000..9c21af0372d
--- /dev/null
+++ b/tests/ui/parser/semi-in-let-chain.rs
@@ -0,0 +1,27 @@
+// Issue #117720
+
+#![feature(let_chains)]
+
+fn main() {
+    if let () = ()
+        && let () = (); //~ERROR
+        && let () = ()
+    {
+    }
+}
+
+fn foo() {
+    if let () = ()
+        && () == (); //~ERROR
+        && 1 < 0
+    {
+    }
+}
+
+fn bar() {
+    if let () = ()
+        && () == (); //~ERROR
+        && let () = ()
+    {
+    }
+}
diff --git a/tests/ui/parser/semi-in-let-chain.stderr b/tests/ui/parser/semi-in-let-chain.stderr
new file mode 100644
index 00000000000..c1a8f92965e
--- /dev/null
+++ b/tests/ui/parser/semi-in-let-chain.stderr
@@ -0,0 +1,50 @@
+error: expected `{`, found `;`
+  --> $DIR/semi-in-let-chain.rs:7:23
+   |
+LL |         && let () = ();
+   |                       ^ expected `{`
+   |
+note: you likely meant to continue parsing the let-chain starting here
+  --> $DIR/semi-in-let-chain.rs:8:9
+   |
+LL |         && let () = ()
+   |         ^^^^^^
+help: consider removing this semicolon to parse the `let` as part of the same chain
+   |
+LL -         && let () = ();
+LL +         && let () = ()
+   |
+
+error: expected `{`, found `;`
+  --> $DIR/semi-in-let-chain.rs:15:20
+   |
+LL |         && () == ();
+   |                    ^ expected `{`
+   |
+note: the `if` expression is missing a block after this condition
+  --> $DIR/semi-in-let-chain.rs:14:8
+   |
+LL |       if let () = ()
+   |  ________^
+LL | |         && () == ();
+   | |___________________^
+
+error: expected `{`, found `;`
+  --> $DIR/semi-in-let-chain.rs:23:20
+   |
+LL |         && () == ();
+   |                    ^ expected `{`
+   |
+note: you likely meant to continue parsing the let-chain starting here
+  --> $DIR/semi-in-let-chain.rs:24:9
+   |
+LL |         && let () = ()
+   |         ^^^^^^
+help: consider removing this semicolon to parse the `let` as part of the same chain
+   |
+LL -         && () == ();
+LL +         && () == ()
+   |
+
+error: aborting due to 3 previous errors
+