From 626d1abb87b769d9aef92085ab05e8e67bd765fd Mon Sep 17 00:00:00 2001 From: Jules Bertholet Date: Thu, 2 Apr 2026 00:09:54 -0400 Subject: [PATCH 01/46] Add `char::to_casefold()` --- Cargo.lock | 1 + library/core/src/char/methods.rs | 79 ++++++++++++++++ library/core/src/char/mod.rs | 15 +++ library/core/src/unicode/unicode_data.rs | 82 +++++++++++++++- library/coretests/tests/char.rs | 35 +++++++ library/coretests/tests/lib.rs | 1 + library/coretests/tests/unicode.rs | 14 +++ library/coretests/tests/unicode/test_data.rs | 91 ++++++++++++++++++ src/tools/unicode-table-generator/Cargo.toml | 1 + .../src/cascading_map.rs | 5 +- .../src/case_mapping.rs | 85 +++++++++++++++-- src/tools/unicode-table-generator/src/main.rs | 94 +++++++++++++++++-- .../src/raw_emitter.rs | 17 ++-- .../src/unicode_download.rs | 9 +- 14 files changed, 498 insertions(+), 31 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 059b203fb06c4..ebd11aa56c15f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6069,6 +6069,7 @@ checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" name = "unicode-table-generator" version = "0.1.0" dependencies = [ + "rustc-hash 2.1.1", "ucd-parse", ] diff --git a/library/core/src/char/methods.rs b/library/core/src/char/methods.rs index 13c20908c7d67..fbca15848214b 100644 --- a/library/core/src/char/methods.rs +++ b/library/core/src/char/methods.rs @@ -1540,6 +1540,85 @@ impl char { ToUppercase(CaseMappingIter::new(conversions::to_upper(self))) } + /// Returns an iterator that yields the case folding of this `char` as one or more + /// `char`s. + /// + /// Case folding is meant to be used when performing case-insensitive string comparisons, + /// but case-folded strings should not generally be exposed directly to users. For most, + /// but not all, characters, the casefold mapping is identical to the lowercase one. + /// + /// This iterator yields the `char`(s) in the common or full case folding for this `char`, + /// as given by the [Unicode Character Database][ucd] [`CaseFolding.txt`]. + /// + /// [ucd]: https://www.unicode.org/reports/tr44/ + /// [`CaseFolding.txt`]: https://www.unicode.org/Public/UCD/latest/ucd/CaseFolding.txt + /// + /// This operation performs an unconditional mapping without tailoring. That is, the conversion + /// is independent of context and language. + /// + /// It also does not perform any normalization (e.g. NFC). + /// + /// In the [Unicode Standard], Chapter 4 (Character Properties) discusses case folding in + /// general and Chapter 3 (Conformance) discusses the default algorithm for case folding. + /// + /// [Unicode Standard]: https://www.unicode.org/versions/latest/ + /// + /// # Examples + /// + /// The German sharp S `'ß'` (U+DF) is a single Unicode code point + /// that casefolds to `"ss"`. Its uppercase variant '`ẞ`' (U+1E9E) + /// has the same case-folding. + /// + /// As an iterator: + /// + /// ``` + /// #![feature(casefold)] + /// assert!('ß'.to_casefold().eq(['s', 's'])); + /// assert!('ẞ'.to_casefold().eq(['s', 's'])); + /// ``` + /// + /// Using [`to_string`](../std/string/trait.ToString.html#tymethod.to_string): + /// + /// ``` + /// #![feature(casefold)] + /// assert_eq!('ß'.to_casefold().to_string(), "ss"); + /// assert_eq!('ẞ'.to_casefold().to_string(), "ss"); + /// ``` + /// + /// # Note on locale + /// + /// In Turkish and Azeri, the equivalent of 'i' in Latin has five forms instead of two: + /// + /// * 'Dotless': I / ı, sometimes written ï + /// * 'Dotted': İ / i + /// + /// Note that the uppercase undotted 'I' is the same as the Latin. Therefore: + /// + /// ``` + /// #![feature(casefold)] + /// let casefold_i = 'I'.to_casefold().to_string(); + /// ``` + /// + /// The value of `casefold_i` here relies on the language of the text: if we're + /// in `en-US`, it should be `"i"`, but if we're in `tr-TR` or `az-AZ`, it should + /// be `"ı"`. `to_casefold()` does not take this into account, and so: + /// + /// ``` + /// #![feature(casefold)] + /// let casefold_i = 'I'.to_casefold().to_string(); + /// + /// assert_eq!(casefold_i, "i"); + /// ``` + /// + /// holds across languages. + #[must_use = "this returns the case-folded character as a new iterator, \ + without modifying the original"] + #[unstable(feature = "casefold", issue = "none")] + #[inline] + pub fn to_casefold(self) -> ToCasefold { + ToCasefold(CaseMappingIter::new(conversions::to_casefold(self))) + } + /// Checks if the value is within the ASCII range. /// /// # Examples diff --git a/library/core/src/char/mod.rs b/library/core/src/char/mod.rs index 2416406576204..a981de457369e 100644 --- a/library/core/src/char/mod.rs +++ b/library/core/src/char/mod.rs @@ -519,6 +519,21 @@ casemappingiter_impls! { ToLowercase } +casemappingiter_impls! { + #[unstable(feature = "casefold", issue = "none")] + #[unstable(feature = "casefold", issue = "none")] + #[unstable(feature = "casefold", issue = "none")] + #[unstable(feature = "casefold", issue = "none")] + #[unstable(feature = "casefold", issue = "none")] + /// Returns an iterator that yields the case-folded equivalent of a `char`. + /// + /// This `struct` is created by the [`to_casefold`] method on [`char`]. See + /// its documentation for more. + /// + /// [`to_casefold`]: char::to_casefold + ToCasefold +} + #[derive(Debug, Clone)] struct CaseMappingIter(core::array::IntoIter); diff --git a/library/core/src/unicode/unicode_data.rs b/library/core/src/unicode/unicode_data.rs index 83d3808051840..729234506f60c 100644 --- a/library/core/src/unicode/unicode_data.rs +++ b/library/core/src/unicode/unicode_data.rs @@ -10,7 +10,8 @@ // to_lower : 1112 bytes, 1462 codepoints in 185 ranges (U+0000C0 - U+01E921) using 2-level LUT // to_upper : 1998 bytes, 1554 codepoints in 299 ranges (U+0000B5 - U+01E943) using 2-level LUT // to_title : 340 bytes, 135 codepoints in 49 ranges (U+0000DF - U+00FB17) using 2-level LUT -// Total : 9629 bytes +// to_casefold : 32 bytes, 174 codepoints in 5 ranges (U+000131 - U+00ABBF) using 2-level LUT +// Total : 9661 bytes #[inline(always)] const fn bitset_search< @@ -846,7 +847,7 @@ pub mod conversions { } pub fn to_lower(c: char) -> [char; 3] { - // https://util.unicode.org/UnicodeJsps/list-unicodeset.jsp?a=%5B%253AChanges_When_Lowercased%253A%5D-%5B%253AASCII%253A%5D&abb=on + // https://util.unicode.org/UnicodeJsps/list-unicodeset.jsp?a=[:Changes_When_Lowercased:]-[:ASCII:]&abb=on if c < '\u{C0}' { return [c.to_ascii_lowercase(), '\0', '\0']; } @@ -855,7 +856,7 @@ pub mod conversions { } pub fn to_upper(c: char) -> [char; 3] { - // https://util.unicode.org/UnicodeJsps/list-unicodeset.jsp?a=%5B%253AChanges_When_Uppercased%253A%5D-%5B%253AASCII%253A%5D&abb=on + // https://util.unicode.org/UnicodeJsps/list-unicodeset.jsp?a=[:Changes_When_Uppercased:]-[:ASCII:]&abb=on if c < '\u{B5}' { return [c.to_ascii_uppercase(), '\0', '\0']; } @@ -864,7 +865,7 @@ pub mod conversions { } pub fn to_title(c: char) -> [char; 3] { - // https://util.unicode.org/UnicodeJsps/list-unicodeset.jsp?a=%5B%253AChanges_When_Titlecased%253A%5D-%5B%253AASCII%253A%5D&abb=on + // https://util.unicode.org/UnicodeJsps/list-unicodeset.jsp?a=[:Changes_When_Titlecased:]-[:ASCII:]&abb=on if c < '\u{B5}' { return [c.to_ascii_uppercase(), '\0', '\0']; } @@ -872,6 +873,59 @@ pub mod conversions { lookup(c, &TITLECASE_LUT).or_else(|| lookup(c, &UPPERCASE_LUT)).unwrap_or([c, '\0', '\0']) } + pub fn to_casefold(c: char) -> [char; 3] { + // https://util.unicode.org/UnicodeJsps/list-unicodeset.jsp?a=[:Changes_When_Casefolded:]-[:ASCII:]&abb=on + if c < '\u{B5}' { + return [c.to_ascii_lowercase(), '\0', '\0']; + } + + + lookup(c, &CASEFOLD_LUT).unwrap_or_else(|| { + // fall back to lowercase of uppercase + + let uppercase = lookup(c, &UPPERCASE_LUT).unwrap_or([c, '\0', '\0']); + let mut final_result = to_lower(uppercase[0]); + if uppercase[1] != '\0' { + let lowercase_1 = to_lower(uppercase[1]); + debug_assert_eq!(lowercase_1[2], '\0'); + + // If, after updating the Unicode data + // to a new Unicode version, the below + // assertion starts to fail in tests, + // delete it, and uncomment the + // `if` condition and corresponding + // `else` block below it. + debug_assert_eq!(final_result[1], '\0'); + //if final_result[1] == '\0' { + + final_result[1] = lowercase_1[0]; + + if uppercase[2] != '\0' { + debug_assert_eq!(lowercase_1[1], '\0'); + let lowercase_2 = to_lower(uppercase[2]); + debug_assert_eq!(lowercase_2[1], '\0'); + debug_assert_eq!(lowercase_2[2], '\0'); + final_result[2] = lowercase_2[0]; + } else { + // If, after updating the Unicode data + // to a new Unicode version, the below + // assertion starts to fail in tests, + // delete it and uncomment the line + // below it. + debug_assert_eq!(lowercase_1[1], '\0'); + //final_result[2] = lowercase_1[1]; + } + + /*} else { + final_result[2] = lowercase_1[0]; + debug_assert_eq!(lowercase_1[1], '\0'); + debug_assert_eq!(uppercase[2], '\0') + }*/ + } + final_result + }) + } + static LOWERCASE_LUT: L1Lut = L1Lut { l2_luts: [ L2Lut { @@ -1188,4 +1242,24 @@ pub mod conversions { }, ], }; + + static CASEFOLD_LUT: L1Lut = L1Lut { + l2_luts: [ + L2Lut { + singles: &[ // 4 entries, 24 bytes + (Range::singleton(0x0131), 0), (Range::step_by_1(0x13a0..=0x13f5), 0), + (Range::step_by_1(0x13f8..=0x13fd), -8), (Range::step_by_1(0xab70..=0xabbf), 26672), + ], + multis: &[ // 1 entries, 8 bytes + (0x1e9e, [0x0073, 0x0073, 0x0000]), + ], + }, + L2Lut { + singles: &[ // 0 entries, 0 bytes + ], + multis: &[ // 0 entries, 0 bytes + ], + }, + ], + }; } diff --git a/library/coretests/tests/char.rs b/library/coretests/tests/char.rs index 877017f682c97..3c43e4db7b330 100644 --- a/library/coretests/tests/char.rs +++ b/library/coretests/tests/char.rs @@ -212,6 +212,41 @@ fn test_to_uppercase() { assert_eq!(upper('ᾀ'), "ἈΙ"); } +#[test] +fn test_to_casefold() { + fn fold(c: char) -> String { + let to_casefold = c.to_casefold(); + assert_eq!(to_casefold.len(), to_casefold.count()); + let iter: String = c.to_casefold().collect(); + let disp: String = c.to_casefold().to_string(); + assert_eq!(iter, disp); + let iter_rev: String = c.to_casefold().rev().collect(); + let disp_rev: String = disp.chars().rev().collect(); + assert_eq!(iter_rev, disp_rev); + iter + } + assert_eq!(fold('A'), "a"); + assert_eq!(fold('Ö'), "ö"); + assert_eq!(fold('ß'), "ss"); + assert_eq!(fold('ẞ'), "ss"); + assert_eq!(fold('Ü'), "ü"); + assert_eq!(fold('💩'), "💩"); + assert_eq!(fold('Σ'), "σ"); + assert_eq!(fold('ς'), "σ"); + assert_eq!(fold('Τ'), "τ"); + assert_eq!(fold('Ι'), "ι"); + assert_eq!(fold('Γ'), "γ"); + assert_eq!(fold('Μ'), "μ"); + assert_eq!(fold('Α'), "α"); + assert_eq!(fold('Dž'), "dž"); + assert_eq!(fold('fi'), "fi"); + assert_eq!(fold('İ'), "i\u{307}"); + assert_eq!(fold('ꮿ'), "Ꮿ"); + assert_eq!(fold('Ꮿ'), "Ꮿ"); + assert_eq!(fold('ῲ'), "ὼι"); + assert_eq!(fold('\u{0345}'), "ι"); +} + #[test] fn test_is_control() { assert!('\u{0}'.is_control()); diff --git a/library/coretests/tests/lib.rs b/library/coretests/tests/lib.rs index d1e706beb51b6..66260c21c7329 100644 --- a/library/coretests/tests/lib.rs +++ b/library/coretests/tests/lib.rs @@ -10,6 +10,7 @@ #![feature(async_iterator)] #![feature(borrowed_buf_init)] #![feature(bstr)] +#![feature(casefold)] #![feature(cfg_target_has_reliable_f16_f128)] #![feature(char_internals)] #![feature(clone_to_uninit)] diff --git a/library/coretests/tests/unicode.rs b/library/coretests/tests/unicode.rs index 12eed25a1feae..6ca45661f7d83 100644 --- a/library/coretests/tests/unicode.rs +++ b/library/coretests/tests/unicode.rs @@ -124,3 +124,17 @@ fn to_titlecase() { unicode_data::conversions::to_upper, ); } + +#[test] +#[cfg_attr(miri, ignore)] // Miri is too slow +fn to_casefold() { + test_case_mapping(test_data::TO_CASEFOLD, unicode_data::conversions::to_casefold, |c| { + let upper = unicode_data::conversions::to_upper(c); + let lower = upper.map(unicode_data::conversions::to_lower); + let mut result = ['\0'; 3]; + for (i, c) in lower.into_iter().flatten().filter(|&c| c != '\0').enumerate() { + result[i] = c; + } + result + }); +} diff --git a/library/coretests/tests/unicode/test_data.rs b/library/coretests/tests/unicode/test_data.rs index 962770a0ff830..77b976c489c9b 100644 --- a/library/coretests/tests/unicode/test_data.rs +++ b/library/coretests/tests/unicode/test_data.rs @@ -2931,3 +2931,94 @@ pub(super) static TO_TITLE: &[(char, [char; 3]); 135] = &[ ('\u{fb16}', ['\u{54e}', '\u{576}', '\u{0}']), ('\u{fb17}', ['\u{544}', '\u{56d}', '\u{0}']), ]; + +#[rustfmt::skip] +pub(super) static TO_CASEFOLD: &[(char, [char; 3]); 174] = &[ + ('\u{131}', ['\u{131}', '\u{0}', '\u{0}']), ('\u{13a0}', ['\u{13a0}', '\u{0}', '\u{0}']), + ('\u{13a1}', ['\u{13a1}', '\u{0}', '\u{0}']), ('\u{13a2}', ['\u{13a2}', '\u{0}', '\u{0}']), + ('\u{13a3}', ['\u{13a3}', '\u{0}', '\u{0}']), ('\u{13a4}', ['\u{13a4}', '\u{0}', '\u{0}']), + ('\u{13a5}', ['\u{13a5}', '\u{0}', '\u{0}']), ('\u{13a6}', ['\u{13a6}', '\u{0}', '\u{0}']), + ('\u{13a7}', ['\u{13a7}', '\u{0}', '\u{0}']), ('\u{13a8}', ['\u{13a8}', '\u{0}', '\u{0}']), + ('\u{13a9}', ['\u{13a9}', '\u{0}', '\u{0}']), ('\u{13aa}', ['\u{13aa}', '\u{0}', '\u{0}']), + ('\u{13ab}', ['\u{13ab}', '\u{0}', '\u{0}']), ('\u{13ac}', ['\u{13ac}', '\u{0}', '\u{0}']), + ('\u{13ad}', ['\u{13ad}', '\u{0}', '\u{0}']), ('\u{13ae}', ['\u{13ae}', '\u{0}', '\u{0}']), + ('\u{13af}', ['\u{13af}', '\u{0}', '\u{0}']), ('\u{13b0}', ['\u{13b0}', '\u{0}', '\u{0}']), + ('\u{13b1}', ['\u{13b1}', '\u{0}', '\u{0}']), ('\u{13b2}', ['\u{13b2}', '\u{0}', '\u{0}']), + ('\u{13b3}', ['\u{13b3}', '\u{0}', '\u{0}']), ('\u{13b4}', ['\u{13b4}', '\u{0}', '\u{0}']), + ('\u{13b5}', ['\u{13b5}', '\u{0}', '\u{0}']), ('\u{13b6}', ['\u{13b6}', '\u{0}', '\u{0}']), + ('\u{13b7}', ['\u{13b7}', '\u{0}', '\u{0}']), ('\u{13b8}', ['\u{13b8}', '\u{0}', '\u{0}']), + ('\u{13b9}', ['\u{13b9}', '\u{0}', '\u{0}']), ('\u{13ba}', ['\u{13ba}', '\u{0}', '\u{0}']), + ('\u{13bb}', ['\u{13bb}', '\u{0}', '\u{0}']), ('\u{13bc}', ['\u{13bc}', '\u{0}', '\u{0}']), + ('\u{13bd}', ['\u{13bd}', '\u{0}', '\u{0}']), ('\u{13be}', ['\u{13be}', '\u{0}', '\u{0}']), + ('\u{13bf}', ['\u{13bf}', '\u{0}', '\u{0}']), ('\u{13c0}', ['\u{13c0}', '\u{0}', '\u{0}']), + ('\u{13c1}', ['\u{13c1}', '\u{0}', '\u{0}']), ('\u{13c2}', ['\u{13c2}', '\u{0}', '\u{0}']), + ('\u{13c3}', ['\u{13c3}', '\u{0}', '\u{0}']), ('\u{13c4}', ['\u{13c4}', '\u{0}', '\u{0}']), + ('\u{13c5}', ['\u{13c5}', '\u{0}', '\u{0}']), ('\u{13c6}', ['\u{13c6}', '\u{0}', '\u{0}']), + ('\u{13c7}', ['\u{13c7}', '\u{0}', '\u{0}']), ('\u{13c8}', ['\u{13c8}', '\u{0}', '\u{0}']), + ('\u{13c9}', ['\u{13c9}', '\u{0}', '\u{0}']), ('\u{13ca}', ['\u{13ca}', '\u{0}', '\u{0}']), + ('\u{13cb}', ['\u{13cb}', '\u{0}', '\u{0}']), ('\u{13cc}', ['\u{13cc}', '\u{0}', '\u{0}']), + ('\u{13cd}', ['\u{13cd}', '\u{0}', '\u{0}']), ('\u{13ce}', ['\u{13ce}', '\u{0}', '\u{0}']), + ('\u{13cf}', ['\u{13cf}', '\u{0}', '\u{0}']), ('\u{13d0}', ['\u{13d0}', '\u{0}', '\u{0}']), + ('\u{13d1}', ['\u{13d1}', '\u{0}', '\u{0}']), ('\u{13d2}', ['\u{13d2}', '\u{0}', '\u{0}']), + ('\u{13d3}', ['\u{13d3}', '\u{0}', '\u{0}']), ('\u{13d4}', ['\u{13d4}', '\u{0}', '\u{0}']), + ('\u{13d5}', ['\u{13d5}', '\u{0}', '\u{0}']), ('\u{13d6}', ['\u{13d6}', '\u{0}', '\u{0}']), + ('\u{13d7}', ['\u{13d7}', '\u{0}', '\u{0}']), ('\u{13d8}', ['\u{13d8}', '\u{0}', '\u{0}']), + ('\u{13d9}', ['\u{13d9}', '\u{0}', '\u{0}']), ('\u{13da}', ['\u{13da}', '\u{0}', '\u{0}']), + ('\u{13db}', ['\u{13db}', '\u{0}', '\u{0}']), ('\u{13dc}', ['\u{13dc}', '\u{0}', '\u{0}']), + ('\u{13dd}', ['\u{13dd}', '\u{0}', '\u{0}']), ('\u{13de}', ['\u{13de}', '\u{0}', '\u{0}']), + ('\u{13df}', ['\u{13df}', '\u{0}', '\u{0}']), ('\u{13e0}', ['\u{13e0}', '\u{0}', '\u{0}']), + ('\u{13e1}', ['\u{13e1}', '\u{0}', '\u{0}']), ('\u{13e2}', ['\u{13e2}', '\u{0}', '\u{0}']), + ('\u{13e3}', ['\u{13e3}', '\u{0}', '\u{0}']), ('\u{13e4}', ['\u{13e4}', '\u{0}', '\u{0}']), + ('\u{13e5}', ['\u{13e5}', '\u{0}', '\u{0}']), ('\u{13e6}', ['\u{13e6}', '\u{0}', '\u{0}']), + ('\u{13e7}', ['\u{13e7}', '\u{0}', '\u{0}']), ('\u{13e8}', ['\u{13e8}', '\u{0}', '\u{0}']), + ('\u{13e9}', ['\u{13e9}', '\u{0}', '\u{0}']), ('\u{13ea}', ['\u{13ea}', '\u{0}', '\u{0}']), + ('\u{13eb}', ['\u{13eb}', '\u{0}', '\u{0}']), ('\u{13ec}', ['\u{13ec}', '\u{0}', '\u{0}']), + ('\u{13ed}', ['\u{13ed}', '\u{0}', '\u{0}']), ('\u{13ee}', ['\u{13ee}', '\u{0}', '\u{0}']), + ('\u{13ef}', ['\u{13ef}', '\u{0}', '\u{0}']), ('\u{13f0}', ['\u{13f0}', '\u{0}', '\u{0}']), + ('\u{13f1}', ['\u{13f1}', '\u{0}', '\u{0}']), ('\u{13f2}', ['\u{13f2}', '\u{0}', '\u{0}']), + ('\u{13f3}', ['\u{13f3}', '\u{0}', '\u{0}']), ('\u{13f4}', ['\u{13f4}', '\u{0}', '\u{0}']), + ('\u{13f5}', ['\u{13f5}', '\u{0}', '\u{0}']), ('\u{13f8}', ['\u{13f0}', '\u{0}', '\u{0}']), + ('\u{13f9}', ['\u{13f1}', '\u{0}', '\u{0}']), ('\u{13fa}', ['\u{13f2}', '\u{0}', '\u{0}']), + ('\u{13fb}', ['\u{13f3}', '\u{0}', '\u{0}']), ('\u{13fc}', ['\u{13f4}', '\u{0}', '\u{0}']), + ('\u{13fd}', ['\u{13f5}', '\u{0}', '\u{0}']), ('\u{1e9e}', ['s', 's', '\u{0}']), + ('\u{ab70}', ['\u{13a0}', '\u{0}', '\u{0}']), ('\u{ab71}', ['\u{13a1}', '\u{0}', '\u{0}']), + ('\u{ab72}', ['\u{13a2}', '\u{0}', '\u{0}']), ('\u{ab73}', ['\u{13a3}', '\u{0}', '\u{0}']), + ('\u{ab74}', ['\u{13a4}', '\u{0}', '\u{0}']), ('\u{ab75}', ['\u{13a5}', '\u{0}', '\u{0}']), + ('\u{ab76}', ['\u{13a6}', '\u{0}', '\u{0}']), ('\u{ab77}', ['\u{13a7}', '\u{0}', '\u{0}']), + ('\u{ab78}', ['\u{13a8}', '\u{0}', '\u{0}']), ('\u{ab79}', ['\u{13a9}', '\u{0}', '\u{0}']), + ('\u{ab7a}', ['\u{13aa}', '\u{0}', '\u{0}']), ('\u{ab7b}', ['\u{13ab}', '\u{0}', '\u{0}']), + ('\u{ab7c}', ['\u{13ac}', '\u{0}', '\u{0}']), ('\u{ab7d}', ['\u{13ad}', '\u{0}', '\u{0}']), + ('\u{ab7e}', ['\u{13ae}', '\u{0}', '\u{0}']), ('\u{ab7f}', ['\u{13af}', '\u{0}', '\u{0}']), + ('\u{ab80}', ['\u{13b0}', '\u{0}', '\u{0}']), ('\u{ab81}', ['\u{13b1}', '\u{0}', '\u{0}']), + ('\u{ab82}', ['\u{13b2}', '\u{0}', '\u{0}']), ('\u{ab83}', ['\u{13b3}', '\u{0}', '\u{0}']), + ('\u{ab84}', ['\u{13b4}', '\u{0}', '\u{0}']), ('\u{ab85}', ['\u{13b5}', '\u{0}', '\u{0}']), + ('\u{ab86}', ['\u{13b6}', '\u{0}', '\u{0}']), ('\u{ab87}', ['\u{13b7}', '\u{0}', '\u{0}']), + ('\u{ab88}', ['\u{13b8}', '\u{0}', '\u{0}']), ('\u{ab89}', ['\u{13b9}', '\u{0}', '\u{0}']), + ('\u{ab8a}', ['\u{13ba}', '\u{0}', '\u{0}']), ('\u{ab8b}', ['\u{13bb}', '\u{0}', '\u{0}']), + ('\u{ab8c}', ['\u{13bc}', '\u{0}', '\u{0}']), ('\u{ab8d}', ['\u{13bd}', '\u{0}', '\u{0}']), + ('\u{ab8e}', ['\u{13be}', '\u{0}', '\u{0}']), ('\u{ab8f}', ['\u{13bf}', '\u{0}', '\u{0}']), + ('\u{ab90}', ['\u{13c0}', '\u{0}', '\u{0}']), ('\u{ab91}', ['\u{13c1}', '\u{0}', '\u{0}']), + ('\u{ab92}', ['\u{13c2}', '\u{0}', '\u{0}']), ('\u{ab93}', ['\u{13c3}', '\u{0}', '\u{0}']), + ('\u{ab94}', ['\u{13c4}', '\u{0}', '\u{0}']), ('\u{ab95}', ['\u{13c5}', '\u{0}', '\u{0}']), + ('\u{ab96}', ['\u{13c6}', '\u{0}', '\u{0}']), ('\u{ab97}', ['\u{13c7}', '\u{0}', '\u{0}']), + ('\u{ab98}', ['\u{13c8}', '\u{0}', '\u{0}']), ('\u{ab99}', ['\u{13c9}', '\u{0}', '\u{0}']), + ('\u{ab9a}', ['\u{13ca}', '\u{0}', '\u{0}']), ('\u{ab9b}', ['\u{13cb}', '\u{0}', '\u{0}']), + ('\u{ab9c}', ['\u{13cc}', '\u{0}', '\u{0}']), ('\u{ab9d}', ['\u{13cd}', '\u{0}', '\u{0}']), + ('\u{ab9e}', ['\u{13ce}', '\u{0}', '\u{0}']), ('\u{ab9f}', ['\u{13cf}', '\u{0}', '\u{0}']), + ('\u{aba0}', ['\u{13d0}', '\u{0}', '\u{0}']), ('\u{aba1}', ['\u{13d1}', '\u{0}', '\u{0}']), + ('\u{aba2}', ['\u{13d2}', '\u{0}', '\u{0}']), ('\u{aba3}', ['\u{13d3}', '\u{0}', '\u{0}']), + ('\u{aba4}', ['\u{13d4}', '\u{0}', '\u{0}']), ('\u{aba5}', ['\u{13d5}', '\u{0}', '\u{0}']), + ('\u{aba6}', ['\u{13d6}', '\u{0}', '\u{0}']), ('\u{aba7}', ['\u{13d7}', '\u{0}', '\u{0}']), + ('\u{aba8}', ['\u{13d8}', '\u{0}', '\u{0}']), ('\u{aba9}', ['\u{13d9}', '\u{0}', '\u{0}']), + ('\u{abaa}', ['\u{13da}', '\u{0}', '\u{0}']), ('\u{abab}', ['\u{13db}', '\u{0}', '\u{0}']), + ('\u{abac}', ['\u{13dc}', '\u{0}', '\u{0}']), ('\u{abad}', ['\u{13dd}', '\u{0}', '\u{0}']), + ('\u{abae}', ['\u{13de}', '\u{0}', '\u{0}']), ('\u{abaf}', ['\u{13df}', '\u{0}', '\u{0}']), + ('\u{abb0}', ['\u{13e0}', '\u{0}', '\u{0}']), ('\u{abb1}', ['\u{13e1}', '\u{0}', '\u{0}']), + ('\u{abb2}', ['\u{13e2}', '\u{0}', '\u{0}']), ('\u{abb3}', ['\u{13e3}', '\u{0}', '\u{0}']), + ('\u{abb4}', ['\u{13e4}', '\u{0}', '\u{0}']), ('\u{abb5}', ['\u{13e5}', '\u{0}', '\u{0}']), + ('\u{abb6}', ['\u{13e6}', '\u{0}', '\u{0}']), ('\u{abb7}', ['\u{13e7}', '\u{0}', '\u{0}']), + ('\u{abb8}', ['\u{13e8}', '\u{0}', '\u{0}']), ('\u{abb9}', ['\u{13e9}', '\u{0}', '\u{0}']), + ('\u{abba}', ['\u{13ea}', '\u{0}', '\u{0}']), ('\u{abbb}', ['\u{13eb}', '\u{0}', '\u{0}']), + ('\u{abbc}', ['\u{13ec}', '\u{0}', '\u{0}']), ('\u{abbd}', ['\u{13ed}', '\u{0}', '\u{0}']), + ('\u{abbe}', ['\u{13ee}', '\u{0}', '\u{0}']), ('\u{abbf}', ['\u{13ef}', '\u{0}', '\u{0}']), +]; diff --git a/src/tools/unicode-table-generator/Cargo.toml b/src/tools/unicode-table-generator/Cargo.toml index 3ca6e9e316f1d..3be916dc69bf5 100644 --- a/src/tools/unicode-table-generator/Cargo.toml +++ b/src/tools/unicode-table-generator/Cargo.toml @@ -7,3 +7,4 @@ edition = "2024" [dependencies] ucd-parse = "0.1.3" +rustc-hash = "2.0.0" diff --git a/src/tools/unicode-table-generator/src/cascading_map.rs b/src/tools/unicode-table-generator/src/cascading_map.rs index 56e6401908dcf..da06049beb575 100644 --- a/src/tools/unicode-table-generator/src/cascading_map.rs +++ b/src/tools/unicode-table-generator/src/cascading_map.rs @@ -1,7 +1,8 @@ -use std::collections::HashMap; use std::fmt::Write as _; use std::ops::Range; +use rustc_hash::FxHashMap; + use crate::fmt_list; use crate::raw_emitter::RawEmitter; @@ -27,7 +28,7 @@ impl RawEmitter { println!("there are {} points", points.len()); // how many distinct ranges need to be counted? - let mut codepoints_by_high_bytes = HashMap::>::new(); + let mut codepoints_by_high_bytes = FxHashMap::>::default(); for point in points { // assert that there is no whitespace over the 0x3000 range. assert!(point <= 0x3000, "the highest unicode whitespace value has changed"); diff --git a/src/tools/unicode-table-generator/src/case_mapping.rs b/src/tools/unicode-table-generator/src/case_mapping.rs index b7b385542ef53..ee4dfc2514c20 100644 --- a/src/tools/unicode-table-generator/src/case_mapping.rs +++ b/src/tools/unicode-table-generator/src/case_mapping.rs @@ -48,21 +48,33 @@ use std::ops::RangeInclusive; use crate::fmt_helpers::Hex; use crate::{UnicodeData, fmt_list}; -pub(crate) fn generate_case_mapping(data: &UnicodeData) -> (String, [(String, usize); 3]) { +pub(crate) fn generate_case_mapping(data: &UnicodeData) -> (String, [(String, usize); 4]) { let mut file = String::new(); file.push_str("\n\n"); file.push_str(HEADER.trim_start()); file.push('\n'); - let (lower_tables, lower_desc, lower_size) = generate_tables("LOWER", &data.to_lower); + let (lower_tables, lower_desc, lower_size) = generate_tables("LOWERCASE", &data.to_lower); file.push_str(&lower_tables); file.push_str("\n\n"); - let (upper_tables, upper_desc, upper_size) = generate_tables("UPPER", &data.to_upper); + let (upper_tables, upper_desc, upper_size) = generate_tables("UPPERCASE", &data.to_upper); file.push_str(&upper_tables); file.push_str("\n\n"); - let (title_tables, title_desc, title_size) = generate_tables("TITLE", &data.to_title); + let (title_tables, title_desc, title_size) = generate_tables("TITLECASE", &data.to_title); file.push_str(&title_tables); - (file, [(lower_desc, lower_size), (upper_desc, upper_size), (title_desc, title_size)]) + file.push_str("\n\n"); + let (casefold_tables, casefold_desc, casefold_size) = + generate_tables("CASEFOLD", &data.to_casefold); + file.push_str(&casefold_tables); + ( + file, + [ + (lower_desc, lower_size), + (upper_desc, upper_size), + (title_desc, title_size), + (casefold_desc, casefold_size), + ], + ) } // So far, only planes 0 and 1 (Basic Multilingual Plane and Supplementary @@ -205,7 +217,7 @@ fn generate_tables(case: &str, data: &BTreeMap) -> (String, Strin output_high, input_high, "Case-mapping a character should not change its plane" ); - let delta = output_low as i16 - input_low as i16; + let delta = output_low.wrapping_sub(input_low).cast_signed(); let range = Range::singleton(input_low); l2_lut.singles.push((range, delta)); } @@ -264,7 +276,7 @@ fn generate_tables(case: &str, data: &BTreeMap) -> (String, Strin let size = l1_lut.size(); let num_ranges = l1_lut.l2_luts.iter().map(|l2| l2.singles.len() + l2.multis.len()).sum::(); - let table = format!("static {case}CASE_LUT: L1Lut = {l1_lut:#?};"); + let table = format!("static {case}_LUT: L1Lut = {l1_lut:#?};"); let desc = format!( "{:6} codepoints in {:3} ranges (U+{:06X} - U+{:06X}) using 2-level LUT", data.len(), @@ -381,7 +393,7 @@ fn lookup(input: char, l1_lut: &L1Lut) -> Option<[char; 3]> { } pub fn to_lower(c: char) -> [char; 3] { - // https://util.unicode.org/UnicodeJsps/list-unicodeset.jsp?a=%5B%253AChanges_When_Lowercased%253A%5D-%5B%253AASCII%253A%5D&abb=on + // https://util.unicode.org/UnicodeJsps/list-unicodeset.jsp?a=[:Changes_When_Lowercased:]-[:ASCII:]&abb=on if c < '\u{C0}' { return [c.to_ascii_lowercase(), '\0', '\0']; } @@ -390,7 +402,7 @@ pub fn to_lower(c: char) -> [char; 3] { } pub fn to_upper(c: char) -> [char; 3] { - // https://util.unicode.org/UnicodeJsps/list-unicodeset.jsp?a=%5B%253AChanges_When_Uppercased%253A%5D-%5B%253AASCII%253A%5D&abb=on + // https://util.unicode.org/UnicodeJsps/list-unicodeset.jsp?a=[:Changes_When_Uppercased:]-[:ASCII:]&abb=on if c < '\u{B5}' { return [c.to_ascii_uppercase(), '\0', '\0']; } @@ -399,11 +411,64 @@ pub fn to_upper(c: char) -> [char; 3] { } pub fn to_title(c: char) -> [char; 3] { - // https://util.unicode.org/UnicodeJsps/list-unicodeset.jsp?a=%5B%253AChanges_When_Titlecased%253A%5D-%5B%253AASCII%253A%5D&abb=on + // https://util.unicode.org/UnicodeJsps/list-unicodeset.jsp?a=[:Changes_When_Titlecased:]-[:ASCII:]&abb=on if c < '\u{B5}' { return [c.to_ascii_uppercase(), '\0', '\0']; } lookup(c, &TITLECASE_LUT).or_else(|| lookup(c, &UPPERCASE_LUT)).unwrap_or([c, '\0', '\0']) } + +pub fn to_casefold(c: char) -> [char; 3] { + // https://util.unicode.org/UnicodeJsps/list-unicodeset.jsp?a=[:Changes_When_Casefolded:]-[:ASCII:]&abb=on + if c < '\u{B5}' { + return [c.to_ascii_lowercase(), '\0', '\0']; + } + + + lookup(c, &CASEFOLD_LUT).unwrap_or_else(|| { + // fall back to lowercase of uppercase + + let uppercase = lookup(c, &UPPERCASE_LUT).unwrap_or([c, '\0', '\0']); + let mut final_result = to_lower(uppercase[0]); + if uppercase[1] != '\0' { + let lowercase_1 = to_lower(uppercase[1]); + debug_assert_eq!(lowercase_1[2], '\0'); + + // If, after updating the Unicode data + // to a new Unicode version, the below + // assertion starts to fail in tests, + // delete it, and uncomment the + // `if` condition and corresponding + // `else` block below it. + debug_assert_eq!(final_result[1], '\0'); + //if final_result[1] == '\0' { + + final_result[1] = lowercase_1[0]; + + if uppercase[2] != '\0' { + debug_assert_eq!(lowercase_1[1], '\0'); + let lowercase_2 = to_lower(uppercase[2]); + debug_assert_eq!(lowercase_2[1], '\0'); + debug_assert_eq!(lowercase_2[2], '\0'); + final_result[2] = lowercase_2[0]; + } else { + // If, after updating the Unicode data + // to a new Unicode version, the below + // assertion starts to fail in tests, + // delete it and uncomment the line + // below it. + debug_assert_eq!(lowercase_1[1], '\0'); + //final_result[2] = lowercase_1[1]; + } + + /*} else { + final_result[2] = lowercase_1[0]; + debug_assert_eq!(lowercase_1[1], '\0'); + debug_assert_eq!(uppercase[2], '\0') + }*/ + } + final_result + }) +} "; diff --git a/src/tools/unicode-table-generator/src/main.rs b/src/tools/unicode-table-generator/src/main.rs index 398b4c7b7ec5a..a55cd2f657a6d 100644 --- a/src/tools/unicode-table-generator/src/main.rs +++ b/src/tools/unicode-table-generator/src/main.rs @@ -71,11 +71,12 @@ //! index of that offset is utilized as the answer to whether we're in the set //! or not. -use std::collections::{BTreeMap, HashMap}; +use std::collections::BTreeMap; use std::fmt::Write; use std::ops::Range; -use ucd_parse::Codepoints; +use rustc_hash::{FxHashMap, FxHashSet}; +use ucd_parse::{Codepoint, Codepoints}; mod cascading_map; mod case_mapping; @@ -106,6 +107,9 @@ struct UnicodeData { to_title: BTreeMap, /// Only stores mappings that are not to self to_lower: BTreeMap, + /// Only stores mappings that differ from + /// `to_upper` followed by `to_lower` + to_casefold: BTreeMap, } fn to_mapping( @@ -126,7 +130,7 @@ static UNICODE_DIRECTORY: &str = "unicode-downloads"; fn load_data() -> UnicodeData { unicode_download::fetch_latest(); - let mut properties = HashMap::new(); + let mut properties = FxHashMap::default(); for row in ucd_parse::parse::<_, ucd_parse::CoreProperty>(&UNICODE_DIRECTORY).unwrap() { if let Some(name) = PROPERTIES.iter().find(|prop| **prop == row.property.as_str()) { properties.entry(*name).or_insert_with(Vec::new).push(row.codepoints); @@ -138,7 +142,8 @@ fn load_data() -> UnicodeData { } } - let [mut to_lower, mut to_upper, mut to_title] = [const { BTreeMap::new() }; 3]; + let [mut to_lower, mut to_upper, mut to_title, mut to_casefold] = + [const { BTreeMap::new() }; 4]; for row in ucd_parse::UnicodeDataExpander::new( ucd_parse::parse::<_, ucd_parse::UnicodeData>(&UNICODE_DIRECTORY).unwrap(), ) { @@ -189,6 +194,78 @@ fn load_data() -> UnicodeData { } } + fn get_mapping_from_btreemap<'a>( + cp: Codepoint, + map: &'a BTreeMap, + ) -> Vec { + let mapping = + map.get(&cp.value()).copied().map(|cs| cs.map(|c| Codepoint::from_u32(c).unwrap())); + + mapping + .as_ref() + .map(|cs| { + let nul = Codepoint::from_u32(0).unwrap(); + if cs[1] == nul { + &cs[..1] + } else if cs[2] == nul { + &cs[..2] + } else { + &cs[..] + } + }) + .map_or_else(|| vec![cp], ToOwned::to_owned) + } + + let mut nontrivial_casefold = FxHashSet::default(); + + for row in ucd_parse::parse::<_, ucd_parse::CaseFold>(&UNICODE_DIRECTORY).unwrap() { + use ucd_parse::{CaseStatus, Codepoint}; + if matches!(row.status, CaseStatus::Common | CaseStatus::Full) { + let key = row.codepoint.value(); + nontrivial_casefold.insert(key); + + // We store case-fold data only for characters whose case-folding + // differs from the lowercase of their uppercase. + + let lower_upper_mapping: Vec = + get_mapping_from_btreemap(row.codepoint, &to_upper) + .into_iter() + .flat_map(|cp| get_mapping_from_btreemap(cp, &to_lower)) + .collect(); + + if let Some(casefold) = to_mapping(&lower_upper_mapping, &row.mapping) { + to_casefold.insert(key, casefold); + } + } + } + + // Now, account for characters that remain unchanged by case-folding + // (and are therefore omitted from `CaseFolding.txt`), + // but yet differ from the lowercase of their uppercase. + + for c in '\0'..=char::MAX { + let cnum: u32 = c.into(); + if !nontrivial_casefold.contains(&cnum) { + let cp = Codepoint::from_u32(cnum).unwrap(); + + use std::collections::btree_map::Entry; + match to_casefold.entry(cnum) { + Entry::Vacant(vacant_entry) => { + let lower_upper_mapping: Vec = + get_mapping_from_btreemap(cp, &to_upper) + .into_iter() + .flat_map(|cp| get_mapping_from_btreemap(cp, &to_lower)) + .collect(); + + if let Some(casefold) = to_mapping(&lower_upper_mapping, &[cp]) { + vacant_entry.insert(casefold); + } + } + Entry::Occupied(_) => {} + } + } + } + // Filter out ASCII codepoints. to_lower.retain(|&c, _| c > 0x7f); to_upper.retain(|&c, _| c > 0x7f); @@ -207,7 +284,7 @@ fn load_data() -> UnicodeData { .collect(); properties.sort_by_key(|p| p.0); - UnicodeData { ranges: properties, to_lower, to_title, to_upper } + UnicodeData { ranges: properties, to_lower, to_title, to_upper, to_casefold } } fn main() { @@ -259,7 +336,9 @@ fn main() { total_bytes += emitter.bytes_used; } let (conversions, sizes) = case_mapping::generate_case_mapping(&unicode_data); - for (name, (desc, size)) in ["to_lower", "to_upper", "to_title"].iter().zip(sizes) { + for (name, (desc, size)) in + ["to_lower", "to_upper", "to_title", "to_casefold"].iter().zip(sizes) + { table_file.push_str(&format!("// {:16}: {:5} bytes, {desc}\n", name, size,)); total_bytes += size; } @@ -369,10 +448,11 @@ pub(super) static {prop_upper}: &[RangeInclusive; {is_true_len}] = &[{is_t .unwrap(); } - for (name, lut) in ["TO_LOWER", "TO_UPPER", "TO_TITLE"].iter().zip([ + for (name, lut) in ["TO_LOWER", "TO_UPPER", "TO_TITLE", "TO_CASEFOLD"].iter().zip([ &data.to_lower, &data.to_upper, &data.to_title, + &data.to_casefold, ]) { let lut = lut .iter() diff --git a/src/tools/unicode-table-generator/src/raw_emitter.rs b/src/tools/unicode-table-generator/src/raw_emitter.rs index 297965615c1a5..de3395df3806e 100644 --- a/src/tools/unicode-table-generator/src/raw_emitter.rs +++ b/src/tools/unicode-table-generator/src/raw_emitter.rs @@ -1,7 +1,9 @@ -use std::collections::{BTreeMap, BTreeSet, HashMap}; +use std::collections::{BTreeMap, BTreeSet}; use std::fmt::{self, Write}; use std::ops::Range; +use rustc_hash::FxHashMap; + use crate::fmt_list; #[derive(Clone)] @@ -126,8 +128,11 @@ impl RawEmitter { for chunk in compressed_words.chunks(chunk_length) { chunks.insert(chunk); } - let chunk_map = - chunks.iter().enumerate().map(|(idx, &chunk)| (chunk, idx)).collect::>(); + let chunk_map = chunks + .iter() + .enumerate() + .map(|(idx, &chunk)| (chunk, idx)) + .collect::>(); let mut chunk_indices = Vec::new(); for chunk in compressed_words.chunks(chunk_length) { chunk_indices.push(chunk_map[chunk]); @@ -186,7 +191,7 @@ struct Canonicalized { /// Maps an input unique word to the associated index (u8) which is into /// canonical_words or canonicalized_words (in order). - unique_mapping: HashMap, + unique_mapping: FxHashMap, } impl Canonicalized { @@ -253,7 +258,7 @@ impl Canonicalized { // These are mapped words, which will be represented by an index into // the canonical_words and a Mapping; u16 when encoded. let mut canonicalized_words = Vec::new(); - let mut unique_mapping = HashMap::new(); + let mut unique_mapping = FxHashMap::default(); #[derive(Debug, PartialEq, Eq)] enum UniqueMapping { @@ -361,7 +366,7 @@ impl Canonicalized { }, ) }) - .collect::>(); + .collect::>(); let mut distinct_indices = BTreeSet::new(); for &w in unique_words { diff --git a/src/tools/unicode-table-generator/src/unicode_download.rs b/src/tools/unicode-table-generator/src/unicode_download.rs index c9826170905c2..b2fcf6444033d 100644 --- a/src/tools/unicode-table-generator/src/unicode_download.rs +++ b/src/tools/unicode-table-generator/src/unicode_download.rs @@ -7,8 +7,13 @@ static URL_PREFIX: &str = "https://www.unicode.org/Public/UCD/latest/ucd/"; static README: &str = "ReadMe.txt"; -static RESOURCES: &[&str] = - &["DerivedCoreProperties.txt", "PropList.txt", "UnicodeData.txt", "SpecialCasing.txt"]; +static RESOURCES: &[&str] = &[ + "CaseFolding.txt", + "DerivedCoreProperties.txt", + "PropList.txt", + "SpecialCasing.txt", + "UnicodeData.txt", +]; #[track_caller] fn fetch(url: &str) -> Output { From 21a57cbd7d1f51c816933b48c90dafe2bfd8680f Mon Sep 17 00:00:00 2001 From: Jules Bertholet Date: Thu, 2 Apr 2026 20:26:14 -0400 Subject: [PATCH 02/46] Add `str::to_casefold()` --- library/alloc/src/str.rs | 97 +++++++++++++++++++++++++++++++++ library/alloctests/tests/lib.rs | 1 + library/alloctests/tests/str.rs | 8 ++- 3 files changed, 105 insertions(+), 1 deletion(-) diff --git a/library/alloc/src/str.rs b/library/alloc/src/str.rs index cf04402e3d984..d9558f4d1d0d5 100644 --- a/library/alloc/src/str.rs +++ b/library/alloc/src/str.rs @@ -659,6 +659,103 @@ impl str { s } + /// Returns the case-folded equivalent of this string slice, as a new [`String`]. + /// + /// Case folding is a transformation, mostly matching lowercase, that is meant to be used + /// for case-insensitive string comparisons. Case-folded strings should not usually + /// be exposed directly to users. + /// + /// For the precise specification of case folding, see + /// [Chapter 3 (Conformance)](https://www.unicode.org/versions/latest/core-spec/chapter-3/#G63737) + /// of the Unicode standard. + /// + /// Since some characters can expand into multiple characters when case folding, + /// this function returns a [`String`] instead of modifying the parameter in-place. + /// + /// This function does not perform any normalization (e.g. NFC). + /// + /// Like [`char::to_casefold()`] this method does not handle language-specific + /// casing, like Turkish and Azeri I/ı/İ/i. See that method's documentation + /// for more information. + /// + /// # Examples + /// + /// Basic usage: + /// + /// ``` + /// #![feature(casefold)] + /// let s0 = "HELLO"; + /// let s1 = "Hello"; + /// + /// assert_eq!(s0.to_casefold(), s1.to_casefold()); + /// assert_eq!(s0.to_casefold(), "hello") + /// ``` + /// + /// Scripts without case are not changed: + /// + /// ``` + /// #![feature(casefold)] + /// let new_year = "农历新年"; + /// + /// assert_eq!(new_year, new_year.to_casefold()); + /// ``` + /// + /// One character can become multiple: + /// + /// ``` + /// #![feature(casefold)] + /// let s0 = "TSCHÜẞ"; + /// let s1 = "TSCHÜSS"; + /// let s2 = "tschüß"; + /// + /// assert_eq!(s0.to_casefold(), s1.to_casefold()); + /// assert_eq!(s0.to_casefold(), s2.to_casefold()); + /// assert_eq!(s0.to_casefold(), "tschüss"); + /// ``` + /// + /// No NFC normalization is performed: + /// + /// ```rust + /// #![feature(casefold)] + /// // These two strings are visually and semantically identical... + /// let comp = "Á"; + /// let decomp = "Á"; + /// + /// // ... but not codepoint-for-codepoint equal. + /// + /// assert_eq!(comp, "\u{C1}"); + /// assert_eq!(decomp, "A\u{0301}"); + /// + /// // Their case-foldings are likewise unequal: + /// + /// assert_eq!(comp.to_casefold(), "\u{E1}"); + /// assert_eq!(decomp.to_casefold(), "a\u{0301}"); + /// ``` + #[cfg(not(no_global_oom_handling))] + #[rustc_allow_incoherent_impl] + #[must_use = "this returns the case-folded string as a new String, \ + without modifying the original"] + #[unstable(feature = "casefold", issue = "none")] + pub fn to_casefold(&self) -> String { + let (mut s, rest) = convert_while_ascii(self, u8::to_ascii_lowercase); + + for c in rest.chars() { + match conversions::to_casefold(c) { + [a, '\0', _] => s.push(a), + [a, b, '\0'] => { + s.push(a); + s.push(b); + } + [a, b, c] => { + s.push(a); + s.push(b); + s.push(c); + } + } + } + s + } + /// Converts a [`Box`] into a [`String`] without copying or allocating. /// /// # Examples diff --git a/library/alloctests/tests/lib.rs b/library/alloctests/tests/lib.rs index 699a5010282b0..5067fc45eb29b 100644 --- a/library/alloctests/tests/lib.rs +++ b/library/alloctests/tests/lib.rs @@ -3,6 +3,7 @@ #![feature(const_heap)] #![feature(deque_extend_front)] #![feature(iter_array_chunks)] +#![feature(casefold)] #![feature(cow_is_borrowed)] #![feature(core_intrinsics)] #![feature(downcast_unchecked)] diff --git a/library/alloctests/tests/str.rs b/library/alloctests/tests/str.rs index dca2c49249aff..6529005e3e955 100644 --- a/library/alloctests/tests/str.rs +++ b/library/alloctests/tests/str.rs @@ -1886,7 +1886,13 @@ fn to_lowercase() { #[test] fn to_uppercase() { assert_eq!("".to_uppercase(), ""); - assert_eq!("aéDžßfiᾀ".to_uppercase(), "AÉDŽSSFIἈΙ"); + assert_eq!("aéDžßẞfiᾀ".to_uppercase(), "AÉDŽSSẞFIἈΙ"); +} + +#[test] +fn to_casefold() { + assert_eq!("".to_casefold(), ""); + assert_eq!("ꮿfiῲὼ\u{0345}ßẞΣς".to_casefold(), "Ꮿfiὼιὼιssssσσ"); } #[test] From 6584161c67a17bc795b521e562c0cbebbfc407cc Mon Sep 17 00:00:00 2001 From: Jules Bertholet Date: Thu, 2 Apr 2026 20:38:23 -0400 Subject: [PATCH 03/46] Add `str::eq_ignore_case()` With an unoptimized, non-`const` implementation for now. --- library/alloc/src/str.rs | 4 +++- library/core/src/str/mod.rs | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/library/alloc/src/str.rs b/library/alloc/src/str.rs index d9558f4d1d0d5..a3a6be581fbe1 100644 --- a/library/alloc/src/str.rs +++ b/library/alloc/src/str.rs @@ -737,7 +737,9 @@ impl str { without modifying the original"] #[unstable(feature = "casefold", issue = "none")] pub fn to_casefold(&self) -> String { - let (mut s, rest) = convert_while_ascii(self, u8::to_ascii_lowercase); + // SAFETY: `to_ascii_lowercase` preserves ASCII bytes, so the converted + // prefix remains valid UTF-8. + let (mut s, rest) = unsafe { convert_while_ascii(self, u8::to_ascii_lowercase) }; for c in rest.chars() { match conversions::to_casefold(c) { diff --git a/library/core/src/str/mod.rs b/library/core/src/str/mod.rs index 5af399ab1b34c..a8fc30a632642 100644 --- a/library/core/src/str/mod.rs +++ b/library/core/src/str/mod.rs @@ -2826,6 +2826,9 @@ impl str { /// Same as `to_ascii_lowercase(a) == to_ascii_lowercase(b)`, /// but without allocating and copying temporaries. /// + /// For Unicode-aware case-insensitive matching, consider + /// [`str::eq_ignore_case`]. + /// /// # Examples /// /// ``` @@ -2841,6 +2844,38 @@ impl str { self.as_bytes().eq_ignore_ascii_case(other.as_bytes()) } + /// Checks that two strings are a caseless match, according to + /// [Definition 144] in Chapter 3 of the Unicode Standard. + /// + /// [Definition 144]: https://www.unicode.org/versions/latest/core-spec/chapter-3/#G53513 + /// + /// Same as `a.to_casefold() == b.to_casefold()`, + /// but without allocating. See that method's documentation, + /// and [`char::to_casefold()`], + /// for more information about case folding. + /// + /// No normalization (e.g. NFC) is performed, + /// so visually and semantically identical strings + /// might still compare unequal. In addition, + /// this method is independent of language/locale, + /// so the special behavior of I/ı/İ/i + /// in Turkish and Azeri is not handled. + /// + /// # Examples + /// + /// ``` + /// #![feature(casefold)] + /// assert!("Ferris".eq_ignore_case("FERRIS")); + /// assert!("Ferrös".eq_ignore_case("FERRÖS")); + /// assert!("ẞ".eq_ignore_case("ss")); + /// ``` + #[unstable(feature = "casefold", issue = "none")] + #[must_use] + #[inline] + pub fn eq_ignore_case(&self, other: &str) -> bool { + self.chars().flat_map(char::to_casefold).eq(other.chars().flat_map(char::to_casefold)) + } + /// Converts this string to its ASCII upper case equivalent in-place. /// /// ASCII letters 'a' to 'z' are mapped to 'A' to 'Z', From db01ed4b3cf950562e1a0d79e3a032790a04e6e6 Mon Sep 17 00:00:00 2001 From: Jules Bertholet Date: Sat, 18 Apr 2026 00:29:21 -0400 Subject: [PATCH 04/46] Address review comments --- library/alloc/src/str.rs | 9 ++--- library/core/src/char/methods.rs | 61 ++++++++++++++++++++------------ library/core/src/str/mod.rs | 24 +++++++++++-- 3 files changed, 64 insertions(+), 30 deletions(-) diff --git a/library/alloc/src/str.rs b/library/alloc/src/str.rs index a3a6be581fbe1..551547311f481 100644 --- a/library/alloc/src/str.rs +++ b/library/alloc/src/str.rs @@ -672,7 +672,8 @@ impl str { /// Since some characters can expand into multiple characters when case folding, /// this function returns a [`String`] instead of modifying the parameter in-place. /// - /// This function does not perform any normalization (e.g. NFC). + /// This function does not perform any [normalization] (e.g. NFC), + /// so semantically and visually identical strings may compare unequal. /// /// Like [`char::to_casefold()`] this method does not handle language-specific /// casing, like Turkish and Azeri I/ı/İ/i. See that method's documentation @@ -713,7 +714,7 @@ impl str { /// assert_eq!(s0.to_casefold(), "tschüss"); /// ``` /// - /// No NFC normalization is performed: + /// No NFC [normalization] is performed: /// /// ```rust /// #![feature(casefold)] @@ -722,15 +723,15 @@ impl str { /// let decomp = "Á"; /// /// // ... but not codepoint-for-codepoint equal. - /// /// assert_eq!(comp, "\u{C1}"); /// assert_eq!(decomp, "A\u{0301}"); /// /// // Their case-foldings are likewise unequal: - /// /// assert_eq!(comp.to_casefold(), "\u{E1}"); /// assert_eq!(decomp.to_casefold(), "a\u{0301}"); /// ``` + /// + /// [normalization]: https://www.unicode.org/faq/normalization #[cfg(not(no_global_oom_handling))] #[rustc_allow_incoherent_impl] #[must_use = "this returns the case-folded string as a new String, \ diff --git a/library/core/src/char/methods.rs b/library/core/src/char/methods.rs index fbca15848214b..642ced5f1e42a 100644 --- a/library/core/src/char/methods.rs +++ b/library/core/src/char/methods.rs @@ -1076,16 +1076,17 @@ impl char { } /// Returns `true` if this `char` has the `Case_Ignorable` property. This narrow-use property - /// is used to implement context-dependent casing for the Greek letter sigma (uppercase Σ), + /// is used to implement context-dependent casing for the Greek letter sigma (uppercase 'Σ'), /// which has two lowercase forms. /// /// `Case_Ignorable` is [described][D136] in Chapter 3 (Conformance) of the Unicode Core Specification, - /// and specified in the [Unicode Character Database][ucd] [`DerivedCoreProperties.txt`]; - /// see those resources for more information. + /// and specified in the [Unicode Character Database][ucd] [`DerivedCoreProperties.txt`]. + /// See those resources, as well as [`to_lowercase()`]'s documentation, for more information. /// /// [D136]: https://www.unicode.org/versions/latest/core-spec/chapter-3/#G63116 /// [ucd]: https://www.unicode.org/reports/tr44/ /// [`DerivedCoreProperties.txt`]: https://www.unicode.org/Public/UCD/latest/ucd/DerivedCoreProperties.txt + /// [`to_lowercase()`]: Self::to_lowercase() #[must_use] #[inline] #[unstable(feature = "case_ignorable", issue = "154848")] @@ -1155,8 +1156,6 @@ impl char { /// If this `char` expands to multiple `char`s, the iterator yields the `char`s given by /// [`SpecialCasing.txt`]. The maximum number of `char`s in a case mapping is 3. /// - /// [`SpecialCasing.txt`]: https://www.unicode.org/Public/UCD/latest/ucd/SpecialCasing.txt - /// /// This operation performs an unconditional mapping without tailoring. That is, the conversion /// is independent of context and language. See [below](#notes-on-context-and-locale) /// for more information. @@ -1211,14 +1210,25 @@ impl char { /// /// ## Greek sigma /// - /// In Greek, the letter simga (uppercase Σ) has two lowercase forms: - /// ς which is used only at the end of a word, and σ which is used everywhere else. - /// `to_lowercase()` always uses the second form: + /// In Greek, the letter simga (uppercase 'Σ') has two lowercase forms: + /// 'σ' which is used in most situations, and 'ς' which appears only + /// at the end of a word. [`char::to_lowercase()`] always uses the first form: /// /// ``` /// assert_eq!('Σ'.to_lowercase().to_string(), "σ"); /// ``` /// + /// `str::to_lowercase()` (only available with the `alloc` crate) + /// *does* properly handle this contextual mapping, + /// so prefer using that method if you can. Alternatively, you can use + /// [`is_cased()`] and [`is_case_ignorable()`] to implement it yourself. + /// See `Final_Sigma` in [Table 3.17] of the Unicode Standard, + /// along with [`SpecialCasing.txt`], for more details. + /// + /// [`is_cased()`]: Self::is_cased() + /// [`is_case_ignorable()`]: Self::is_case_ignorable() + /// [Table 3.17]: https://www.unicode.org/versions/latest/core-spec/chapter-3/#G54277 + /// /// ## Turkish and Azeri I/ı/İ/i /// /// In Turkish and Azeri, the equivalent of 'i' in Latin has five forms instead of two: @@ -1226,13 +1236,13 @@ impl char { /// * 'Dotless': I / ı, sometimes written ï /// * 'Dotted': İ / i /// - /// Note that the uppercase undotted 'I' is the same as the Latin. Therefore: + /// Note that the uppercase undotted 'I' is the same codepoint as the Latin. Therefore: /// /// ``` /// let lower_i = 'I'.to_lowercase().to_string(); /// ``` /// - /// The value of `lower_i` here relies on the language of the text: if we're + /// `'I'`'s correct lowercase relies on the language of the text: if we're /// in `en-US`, it should be `"i"`, but if we're in `tr-TR` or `az-AZ`, it should /// be `"ı"`. `to_lowercase()` does not take this into account, and so: /// @@ -1243,6 +1253,8 @@ impl char { /// ``` /// /// holds across languages. + /// + /// [`SpecialCasing.txt`]: https://www.unicode.org/Public/UCD/latest/ucd/SpecialCasing.txt #[must_use = "this returns the lowercased character as a new iterator, \ without modifying the original"] #[stable(feature = "rust1", since = "1.0.0")] @@ -1393,22 +1405,22 @@ impl char { /// As stated above, this method is locale-insensitive. /// If you need locale support, consider using an external crate, /// like [`icu_casemap`](https://crates.io/crates/icu_casemap) - /// which is developed by Unicode. A description of a common - /// locale-dependent casing issue follows: + /// which is developed by Unicode. A description of one common + /// locale-dependent casing issue follows (there are others): /// /// In Turkish and Azeri, the equivalent of 'i' in Latin has five forms instead of two: /// /// * 'Dotless': I / ı, sometimes written ï /// * 'Dotted': İ / i /// - /// Note that the lowercase dotted 'i' is the same as the Latin. Therefore: + /// Note that the lowercase dotted 'i' is the same codepoint as the Latin. Therefore: /// /// ``` /// #![feature(titlecase)] /// let upper_i = 'i'.to_titlecase().to_string(); /// ``` /// - /// The value of `upper_i` here relies on the language of the text: if we're + /// `'i'`'s correct titlecase relies on the language of the text: if we're /// in `en-US`, it should be `"I"`, but if we're in `tr-TR` or `az-AZ`, it should /// be `"İ"`. `to_titlecase()` does not take this into account, and so: /// @@ -1505,21 +1517,21 @@ impl char { /// As stated above, this method is locale-insensitive. /// If you need locale support, consider using an external crate, /// like [`icu_casemap`](https://crates.io/crates/icu_casemap) - /// which is developed by Unicode. A description of a common - /// locale-dependent casing issue follows: + /// which is developed by Unicode. A description of one common + /// locale-dependent casing issue follows (there are others): /// /// In Turkish and Azeri, the equivalent of 'i' in Latin has five forms instead of two: /// /// * 'Dotless': I / ı, sometimes written ï /// * 'Dotted': İ / i /// - /// Note that the lowercase dotted 'i' is the same as the Latin. Therefore: + /// Note that the lowercase dotted 'i' is the same codepoint as the Latin. Therefore: /// /// ``` /// let upper_i = 'i'.to_uppercase().to_string(); /// ``` /// - /// The value of `upper_i` here relies on the language of the text: if we're + /// `'i'`'s correct uppercase relies on the language of the text: if we're /// in `en-US`, it should be `"I"`, but if we're in `tr-TR` or `az-AZ`, it should /// be `"İ"`. `to_uppercase()` does not take this into account, and so: /// @@ -1543,12 +1555,13 @@ impl char { /// Returns an iterator that yields the case folding of this `char` as one or more /// `char`s. /// - /// Case folding is meant to be used when performing case-insensitive string comparisons, - /// but case-folded strings should not generally be exposed directly to users. For most, + /// Case folding is meant to be used when performing case-insensitive string comparisons. + /// Case-folded strings should not usually be exposed directly to users. For most, /// but not all, characters, the casefold mapping is identical to the lowercase one. /// /// This iterator yields the `char`(s) in the common or full case folding for this `char`, /// as given by the [Unicode Character Database][ucd] [`CaseFolding.txt`]. + /// The maximum number of `char`s in a case folding is 3. /// /// [ucd]: https://www.unicode.org/reports/tr44/ /// [`CaseFolding.txt`]: https://www.unicode.org/Public/UCD/latest/ucd/CaseFolding.txt @@ -1556,7 +1569,9 @@ impl char { /// This operation performs an unconditional mapping without tailoring. That is, the conversion /// is independent of context and language. /// - /// It also does not perform any normalization (e.g. NFC). + /// It also does not perform any [normalization] (e.g. NFC). + /// + /// [normalization]: https://www.unicode.org/faq/normalization /// /// In the [Unicode Standard], Chapter 4 (Character Properties) discusses case folding in /// general and Chapter 3 (Conformance) discusses the default algorithm for case folding. @@ -1592,14 +1607,14 @@ impl char { /// * 'Dotless': I / ı, sometimes written ï /// * 'Dotted': İ / i /// - /// Note that the uppercase undotted 'I' is the same as the Latin. Therefore: + /// Note that the uppercase undotted 'I' is the same codepoint as the Latin. Therefore: /// /// ``` /// #![feature(casefold)] /// let casefold_i = 'I'.to_casefold().to_string(); /// ``` /// - /// The value of `casefold_i` here relies on the language of the text: if we're + /// `'I'`'s correct case folding relies on the language of the text: if we're /// in `en-US`, it should be `"i"`, but if we're in `tr-TR` or `az-AZ`, it should /// be `"ı"`. `to_casefold()` does not take this into account, and so: /// diff --git a/library/core/src/str/mod.rs b/library/core/src/str/mod.rs index a8fc30a632642..9ff0fd2bd3f31 100644 --- a/library/core/src/str/mod.rs +++ b/library/core/src/str/mod.rs @@ -2851,14 +2851,14 @@ impl str { /// /// Same as `a.to_casefold() == b.to_casefold()`, /// but without allocating. See that method's documentation, - /// and [`char::to_casefold()`], + /// as well as [`char::to_casefold()`], /// for more information about case folding. /// - /// No normalization (e.g. NFC) is performed, + /// No [normalization] (e.g. NFC) is performed, /// so visually and semantically identical strings /// might still compare unequal. In addition, /// this method is independent of language/locale, - /// so the special behavior of I/ı/İ/i + /// so the special behavior of I/ı/İ/i /// in Turkish and Azeri is not handled. /// /// # Examples @@ -2869,6 +2869,24 @@ impl str { /// assert!("Ferrös".eq_ignore_case("FERRÖS")); /// assert!("ẞ".eq_ignore_case("ss")); /// ``` + /// + /// No NFC [normalization] is performed: + /// + /// ```rust + /// #![feature(casefold)] + /// // These two strings are visually and semantically identical... + /// let comp = "Á"; + /// let decomp = "Á"; + /// + /// // ... but not codepoint-for-codepoint equal. + /// assert_eq!(comp, "\u{C1}"); + /// assert_eq!(decomp, "A\u{0301}"); + /// + /// // Their case-foldings are likewise unequal: + /// assert_eq!(!comp.eq_ignore_case(decomp)); + /// ``` + /// + /// [normalization]: https://www.unicode.org/faq/normalization #[unstable(feature = "casefold", issue = "none")] #[must_use] #[inline] From 274ddfa259bef70b37bee725c8a051e1f453e85f Mon Sep 17 00:00:00 2001 From: Jules Bertholet Date: Sat, 18 Apr 2026 07:07:56 -0400 Subject: [PATCH 05/46] Fix doctest --- library/core/src/str/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/core/src/str/mod.rs b/library/core/src/str/mod.rs index 9ff0fd2bd3f31..8b0bdd230a540 100644 --- a/library/core/src/str/mod.rs +++ b/library/core/src/str/mod.rs @@ -2883,7 +2883,7 @@ impl str { /// assert_eq!(decomp, "A\u{0301}"); /// /// // Their case-foldings are likewise unequal: - /// assert_eq!(!comp.eq_ignore_case(decomp)); + /// assert!(!comp.eq_ignore_case(decomp)); /// ``` /// /// [normalization]: https://www.unicode.org/faq/normalization From ee404447ba7ba90821484a8d9478ba41e6df2dc6 Mon Sep 17 00:00:00 2001 From: Jules Bertholet Date: Tue, 26 May 2026 21:49:47 -0400 Subject: [PATCH 06/46] Postfix method names with `_unnormalized` --- library/alloc/src/str.rs | 34 ++++++++++++----------- library/alloctests/tests/str.rs | 6 ++--- library/core/src/char/methods.rs | 46 +++++++++++++++++++++++--------- library/core/src/char/mod.rs | 4 +-- library/core/src/str/mod.rs | 41 +++++++++++++++------------- library/coretests/tests/char.rs | 10 +++---- 6 files changed, 84 insertions(+), 57 deletions(-) diff --git a/library/alloc/src/str.rs b/library/alloc/src/str.rs index 551547311f481..a2597f7e7b072 100644 --- a/library/alloc/src/str.rs +++ b/library/alloc/src/str.rs @@ -672,10 +672,12 @@ impl str { /// Since some characters can expand into multiple characters when case folding, /// this function returns a [`String`] instead of modifying the parameter in-place. /// - /// This function does not perform any [normalization] (e.g. NFC), - /// so semantically and visually identical strings may compare unequal. + /// No [normalization] (e.g. NFC) is performed, so visually and semantically identical strings + /// might still casefold differently. For example, `"Å"` (U+00C5 LATIN CAPITAL LETTER A WITH RING ABOVE) + /// is considered distinct from `"Å"` (A followed by U+030A COMBINING RING ABOVE), + /// even though Unicode considers them canonically equivalent. /// - /// Like [`char::to_casefold()`] this method does not handle language-specific + /// Like [`char::to_casefold_unnormalized()`] this method does not handle language-specific /// casing, like Turkish and Azeri I/ı/İ/i. See that method's documentation /// for more information. /// @@ -688,8 +690,8 @@ impl str { /// let s0 = "HELLO"; /// let s1 = "Hello"; /// - /// assert_eq!(s0.to_casefold(), s1.to_casefold()); - /// assert_eq!(s0.to_casefold(), "hello") + /// assert_eq!(s0.to_casefold_unnormalized(), s1.to_casefold_unnormalized()); + /// assert_eq!(s0.to_casefold_unnormalized(), "hello") /// ``` /// /// Scripts without case are not changed: @@ -698,7 +700,7 @@ impl str { /// #![feature(casefold)] /// let new_year = "农历新年"; /// - /// assert_eq!(new_year, new_year.to_casefold()); + /// assert_eq!(new_year, new_year.to_casefold_unnormalized()); /// ``` /// /// One character can become multiple: @@ -709,9 +711,9 @@ impl str { /// let s1 = "TSCHÜSS"; /// let s2 = "tschüß"; /// - /// assert_eq!(s0.to_casefold(), s1.to_casefold()); - /// assert_eq!(s0.to_casefold(), s2.to_casefold()); - /// assert_eq!(s0.to_casefold(), "tschüss"); + /// assert_eq!(s0.to_casefold_unnormalized(), s1.to_casefold_unnormalized()); + /// assert_eq!(s0.to_casefold_unnormalized(), s2.to_casefold_unnormalized()); + /// assert_eq!(s0.to_casefold_unnormalized(), "tschüss"); /// ``` /// /// No NFC [normalization] is performed: @@ -719,16 +721,16 @@ impl str { /// ```rust /// #![feature(casefold)] /// // These two strings are visually and semantically identical... - /// let comp = "Á"; - /// let decomp = "Á"; + /// let comp = "Å"; + /// let decomp = "Å"; /// /// // ... but not codepoint-for-codepoint equal. - /// assert_eq!(comp, "\u{C1}"); - /// assert_eq!(decomp, "A\u{0301}"); + /// assert_eq!(comp, "\u{C5}"); + /// assert_eq!(decomp, "A\u{030A}"); /// /// // Their case-foldings are likewise unequal: - /// assert_eq!(comp.to_casefold(), "\u{E1}"); - /// assert_eq!(decomp.to_casefold(), "a\u{0301}"); + /// assert_eq!(comp.to_casefold_unnormalized(), "\u{E5}"); + /// assert_eq!(decomp.to_casefold_unnormalized(), "a\u{030A}"); /// ``` /// /// [normalization]: https://www.unicode.org/faq/normalization @@ -737,7 +739,7 @@ impl str { #[must_use = "this returns the case-folded string as a new String, \ without modifying the original"] #[unstable(feature = "casefold", issue = "none")] - pub fn to_casefold(&self) -> String { + pub fn to_casefold_unnormalized(&self) -> String { // SAFETY: `to_ascii_lowercase` preserves ASCII bytes, so the converted // prefix remains valid UTF-8. let (mut s, rest) = unsafe { convert_while_ascii(self, u8::to_ascii_lowercase) }; diff --git a/library/alloctests/tests/str.rs b/library/alloctests/tests/str.rs index 6529005e3e955..1875701b102a8 100644 --- a/library/alloctests/tests/str.rs +++ b/library/alloctests/tests/str.rs @@ -1890,9 +1890,9 @@ fn to_uppercase() { } #[test] -fn to_casefold() { - assert_eq!("".to_casefold(), ""); - assert_eq!("ꮿfiῲὼ\u{0345}ßẞΣς".to_casefold(), "Ꮿfiὼιὼιssssσσ"); +fn to_casefold_unnormalized() { + assert_eq!("".to_casefold_unnormalized(), ""); + assert_eq!("ꮿfiῲὼ\u{0345}ßẞΣς".to_casefold_unnormalized(), "Ꮿfiὼιὼιssssσσ"); } #[test] diff --git a/library/core/src/char/methods.rs b/library/core/src/char/methods.rs index 642ced5f1e42a..57799dd787c16 100644 --- a/library/core/src/char/methods.rs +++ b/library/core/src/char/methods.rs @@ -1566,12 +1566,14 @@ impl char { /// [ucd]: https://www.unicode.org/reports/tr44/ /// [`CaseFolding.txt`]: https://www.unicode.org/Public/UCD/latest/ucd/CaseFolding.txt /// - /// This operation performs an unconditional mapping without tailoring. That is, the conversion - /// is independent of context and language. /// - /// It also does not perform any [normalization] (e.g. NFC). + /// No [normalization] (e.g. NFC) is performed, so visually and semantically identical characters + /// might still casefold differently. For example, `'ά'` (U+03AC GREEK SMALL LETTER ALPHA WITH TONOS) + /// is considered distinct from `'ά'` (U+1F71 GREEK SMALL LETTER ALPHA WITH OXIA), + /// even though Unicode considers them canonically equivalent. /// - /// [normalization]: https://www.unicode.org/faq/normalization + /// In addition, this method is independent of language/locale, + /// so the special behavior of I/ı/İ/i in Turkish and Azeri is not handled. /// /// In the [Unicode Standard], Chapter 4 (Character Properties) discusses case folding in /// general and Chapter 3 (Conformance) discusses the default algorithm for case folding. @@ -1588,16 +1590,34 @@ impl char { /// /// ``` /// #![feature(casefold)] - /// assert!('ß'.to_casefold().eq(['s', 's'])); - /// assert!('ẞ'.to_casefold().eq(['s', 's'])); + /// assert!('ß'.to_casefold_unnormalized().eq(['s', 's'])); + /// assert!('ẞ'.to_casefold_unnormalized().eq(['s', 's'])); /// ``` /// /// Using [`to_string`](../std/string/trait.ToString.html#tymethod.to_string): /// /// ``` /// #![feature(casefold)] - /// assert_eq!('ß'.to_casefold().to_string(), "ss"); - /// assert_eq!('ẞ'.to_casefold().to_string(), "ss"); + /// assert_eq!('ß'.to_casefold_unnormalized().to_string(), "ss"); + /// assert_eq!('ẞ'.to_casefold_unnormalized().to_string(), "ss"); + /// ``` + /// + /// No [normalization] is performed: + /// + /// ```rust + /// #![feature(casefold)] + /// // These two characters are visually and semantically identical; + /// // Unicode considers them to be canonically equivalent. + /// let alpha_tonos = 'ά'; + /// let alpha_oxia = 'ά'; + /// + /// // However, they are different codepoints: + /// assert_eq!(alpha_tonos, '\u{03AC}'); + /// assert_eq!(alpha_oxia, '\u{1F71}'); + /// + /// // Their case-foldings are likewise unequal: + /// assert!(alpha_tonos.to_casefold_unnormalized().eq(['\u{03AC}'])); + /// assert!(alpha_oxia.to_casefold_unnormalized().eq(['\u{1F71}'])); /// ``` /// /// # Note on locale @@ -1611,26 +1631,28 @@ impl char { /// /// ``` /// #![feature(casefold)] - /// let casefold_i = 'I'.to_casefold().to_string(); + /// let casefold_i = 'I'.to_casefold_unnormalized().to_string(); /// ``` /// /// `'I'`'s correct case folding relies on the language of the text: if we're /// in `en-US`, it should be `"i"`, but if we're in `tr-TR` or `az-AZ`, it should - /// be `"ı"`. `to_casefold()` does not take this into account, and so: + /// be `"ı"`. `to_casefold_unnormalized()` does not take this into account, and so: /// /// ``` /// #![feature(casefold)] - /// let casefold_i = 'I'.to_casefold().to_string(); + /// let casefold_i = 'I'.to_casefold_unnormalized().to_string(); /// /// assert_eq!(casefold_i, "i"); /// ``` /// /// holds across languages. + /// + /// [normalization]: https://www.unicode.org/faq/normalization #[must_use = "this returns the case-folded character as a new iterator, \ without modifying the original"] #[unstable(feature = "casefold", issue = "none")] #[inline] - pub fn to_casefold(self) -> ToCasefold { + pub fn to_casefold_unnormalized(self) -> ToCasefold { ToCasefold(CaseMappingIter::new(conversions::to_casefold(self))) } diff --git a/library/core/src/char/mod.rs b/library/core/src/char/mod.rs index a981de457369e..6357b4ba78019 100644 --- a/library/core/src/char/mod.rs +++ b/library/core/src/char/mod.rs @@ -527,10 +527,10 @@ casemappingiter_impls! { #[unstable(feature = "casefold", issue = "none")] /// Returns an iterator that yields the case-folded equivalent of a `char`. /// - /// This `struct` is created by the [`to_casefold`] method on [`char`]. See + /// This `struct` is created by the [`to_casefold_unnormalized`] method on [`char`]. See /// its documentation for more. /// - /// [`to_casefold`]: char::to_casefold + /// [`to_casefold_unnormalized`]: char::to_casefold_unnormalized ToCasefold } diff --git a/library/core/src/str/mod.rs b/library/core/src/str/mod.rs index 8b0bdd230a540..ef712c03f0883 100644 --- a/library/core/src/str/mod.rs +++ b/library/core/src/str/mod.rs @@ -2827,7 +2827,7 @@ impl str { /// but without allocating and copying temporaries. /// /// For Unicode-aware case-insensitive matching, consider - /// [`str::eq_ignore_case`]. + /// [`str::eq_ignore_case_unnormalized`]. /// /// # Examples /// @@ -2849,25 +2849,26 @@ impl str { /// /// [Definition 144]: https://www.unicode.org/versions/latest/core-spec/chapter-3/#G53513 /// - /// Same as `a.to_casefold() == b.to_casefold()`, + /// Same as `a.to_casefold_unnormalized() == b.to_casefold_unnormalized()`, /// but without allocating. See that method's documentation, - /// as well as [`char::to_casefold()`], + /// as well as [`char::to_casefold_unnormalized()`], /// for more information about case folding. /// - /// No [normalization] (e.g. NFC) is performed, - /// so visually and semantically identical strings - /// might still compare unequal. In addition, - /// this method is independent of language/locale, - /// so the special behavior of I/ı/İ/i - /// in Turkish and Azeri is not handled. + /// No [normalization] (e.g. NFC) is performed, so visually and semantically identical strings + /// might still compare unequal. For example, `"Å"` (U+00C5 LATIN CAPITAL LETTER A WITH RING ABOVE) + /// is considered distinct from `"Å"` (A followed by U+030A COMBINING RING ABOVE), + /// even though Unicode considers them canonically equivalent. + /// + /// In addition, this method is independent of language/locale, + /// so the special behavior of I/ı/İ/i in Turkish and Azeri is not handled. /// /// # Examples /// /// ``` /// #![feature(casefold)] - /// assert!("Ferris".eq_ignore_case("FERRIS")); - /// assert!("Ferrös".eq_ignore_case("FERRÖS")); - /// assert!("ẞ".eq_ignore_case("ss")); + /// assert!("Ferris".eq_ignore_case_unnormalized("FERRIS")); + /// assert!("Ferrös".eq_ignore_case_unnormalized("FERRÖS")); + /// assert!("ẞ".eq_ignore_case_unnormalized("ss")); /// ``` /// /// No NFC [normalization] is performed: @@ -2875,23 +2876,25 @@ impl str { /// ```rust /// #![feature(casefold)] /// // These two strings are visually and semantically identical... - /// let comp = "Á"; - /// let decomp = "Á"; + /// let comp = "Å"; + /// let decomp = "Å"; /// /// // ... but not codepoint-for-codepoint equal. - /// assert_eq!(comp, "\u{C1}"); - /// assert_eq!(decomp, "A\u{0301}"); + /// assert_eq!(comp, "\u{C5}"); + /// assert_eq!(decomp, "A\u{030A}"); /// /// // Their case-foldings are likewise unequal: - /// assert!(!comp.eq_ignore_case(decomp)); + /// assert!(!comp.eq_ignore_case_unnormalized(decomp)); /// ``` /// /// [normalization]: https://www.unicode.org/faq/normalization #[unstable(feature = "casefold", issue = "none")] #[must_use] #[inline] - pub fn eq_ignore_case(&self, other: &str) -> bool { - self.chars().flat_map(char::to_casefold).eq(other.chars().flat_map(char::to_casefold)) + pub fn eq_ignore_case_unnormalized(&self, other: &str) -> bool { + self.chars() + .flat_map(char::to_casefold_unnormalized) + .eq(other.chars().flat_map(char::to_casefold_unnormalized)) } /// Converts this string to its ASCII upper case equivalent in-place. diff --git a/library/coretests/tests/char.rs b/library/coretests/tests/char.rs index 3c43e4db7b330..20546b77ee382 100644 --- a/library/coretests/tests/char.rs +++ b/library/coretests/tests/char.rs @@ -213,14 +213,14 @@ fn test_to_uppercase() { } #[test] -fn test_to_casefold() { +fn test_to_casefold_unnormalized() { fn fold(c: char) -> String { - let to_casefold = c.to_casefold(); + let to_casefold = c.to_casefold_unnormalized(); assert_eq!(to_casefold.len(), to_casefold.count()); - let iter: String = c.to_casefold().collect(); - let disp: String = c.to_casefold().to_string(); + let iter: String = c.to_casefold_unnormalized().collect(); + let disp: String = c.to_casefold_unnormalized().to_string(); assert_eq!(iter, disp); - let iter_rev: String = c.to_casefold().rev().collect(); + let iter_rev: String = c.to_casefold_unnormalized().rev().collect(); let disp_rev: String = disp.chars().rev().collect(); assert_eq!(iter_rev, disp_rev); iter From da14011f194875188ef5623384635c87ec5f34a5 Mon Sep 17 00:00:00 2001 From: Jules Bertholet Date: Tue, 26 May 2026 22:37:22 -0400 Subject: [PATCH 07/46] Add tracking issue --- library/alloc/src/str.rs | 2 +- library/core/src/char/methods.rs | 2 +- library/core/src/char/mod.rs | 10 +++++----- library/core/src/str/mod.rs | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/library/alloc/src/str.rs b/library/alloc/src/str.rs index a2597f7e7b072..230039713de8a 100644 --- a/library/alloc/src/str.rs +++ b/library/alloc/src/str.rs @@ -738,7 +738,7 @@ impl str { #[rustc_allow_incoherent_impl] #[must_use = "this returns the case-folded string as a new String, \ without modifying the original"] - #[unstable(feature = "casefold", issue = "none")] + #[unstable(feature = "casefold", issue = "154742")] pub fn to_casefold_unnormalized(&self) -> String { // SAFETY: `to_ascii_lowercase` preserves ASCII bytes, so the converted // prefix remains valid UTF-8. diff --git a/library/core/src/char/methods.rs b/library/core/src/char/methods.rs index 57799dd787c16..f87fe01c6eda3 100644 --- a/library/core/src/char/methods.rs +++ b/library/core/src/char/methods.rs @@ -1650,7 +1650,7 @@ impl char { /// [normalization]: https://www.unicode.org/faq/normalization #[must_use = "this returns the case-folded character as a new iterator, \ without modifying the original"] - #[unstable(feature = "casefold", issue = "none")] + #[unstable(feature = "casefold", issue = "154742")] #[inline] pub fn to_casefold_unnormalized(self) -> ToCasefold { ToCasefold(CaseMappingIter::new(conversions::to_casefold(self))) diff --git a/library/core/src/char/mod.rs b/library/core/src/char/mod.rs index 6357b4ba78019..eab7076c8427d 100644 --- a/library/core/src/char/mod.rs +++ b/library/core/src/char/mod.rs @@ -520,11 +520,11 @@ casemappingiter_impls! { } casemappingiter_impls! { - #[unstable(feature = "casefold", issue = "none")] - #[unstable(feature = "casefold", issue = "none")] - #[unstable(feature = "casefold", issue = "none")] - #[unstable(feature = "casefold", issue = "none")] - #[unstable(feature = "casefold", issue = "none")] + #[unstable(feature = "casefold", issue = "154742")] + #[unstable(feature = "casefold", issue = "154742")] + #[unstable(feature = "casefold", issue = "154742")] + #[unstable(feature = "casefold", issue = "154742")] + #[unstable(feature = "casefold", issue = "154742")] /// Returns an iterator that yields the case-folded equivalent of a `char`. /// /// This `struct` is created by the [`to_casefold_unnormalized`] method on [`char`]. See diff --git a/library/core/src/str/mod.rs b/library/core/src/str/mod.rs index ef712c03f0883..7a951ec710a68 100644 --- a/library/core/src/str/mod.rs +++ b/library/core/src/str/mod.rs @@ -2888,7 +2888,7 @@ impl str { /// ``` /// /// [normalization]: https://www.unicode.org/faq/normalization - #[unstable(feature = "casefold", issue = "none")] + #[unstable(feature = "casefold", issue = "154742")] #[must_use] #[inline] pub fn eq_ignore_case_unnormalized(&self, other: &str) -> bool { From 973e3034d33e3043d7e89f8298370e1bb4633ce0 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Tue, 26 May 2026 23:27:09 +0200 Subject: [PATCH 08/46] enforce noalias on custom allocator Box by default --- src/tools/miri/README.md | 4 + src/tools/miri/src/bin/miri.rs | 12 ++ src/tools/miri/src/borrow_tracker/mod.rs | 4 + .../src/borrow_tracker/stacked_borrows/mod.rs | 7 +- .../src/borrow_tracker/tree_borrows/mod.rs | 49 +++---- .../miri/tests/fail/async-shared-mutable.rs | 2 +- .../fail/async-shared-mutable.stack.stderr | 8 +- .../fail/async-shared-mutable.tree.stderr | 6 +- .../both_borrows/box-custom-alloc-aliasing.rs | 131 ++++++++++++++++++ .../box-custom-alloc-aliasing.stack.stderr | 37 +++++ .../box-custom-alloc-aliasing.tree.stderr | 52 +++++++ .../tests/pass/box-custom-alloc-aliasing.rs | 13 +- 12 files changed, 281 insertions(+), 44 deletions(-) create mode 100644 src/tools/miri/tests/fail/both_borrows/box-custom-alloc-aliasing.rs create mode 100644 src/tools/miri/tests/fail/both_borrows/box-custom-alloc-aliasing.stack.stderr create mode 100644 src/tools/miri/tests/fail/both_borrows/box-custom-alloc-aliasing.tree.stderr diff --git a/src/tools/miri/README.md b/src/tools/miri/README.md index 3d0716a082d43..550a2e25140ef 100644 --- a/src/tools/miri/README.md +++ b/src/tools/miri/README.md @@ -517,6 +517,10 @@ to Miri failing to detect cases of undefined behavior in a program. track interior mutable data on the level of references instead of on the byte-level as is done by default. Therefore, with this flag, Tree Borrows will be more permissive. +* `-Zmiri-tree-borrows-relax-custom-allocator-uniqueness` disables uniqueness assumptions for + `Box` where `A` is not `Global`. The exact aliasing rules for such custom allocators are + still up in the air, and by default Miri is conservative and rejects some allocator + implementations that incur relevant aliasing between the allocation and the allocator. * `-Zmiri-force-page-size=` overrides the default page size for an architecture, in multiples of 1k. `4` is default for most targets. This value should always be a power of 2 and nonzero. diff --git a/src/tools/miri/src/bin/miri.rs b/src/tools/miri/src/bin/miri.rs index ba662346a028c..38cb8ef5717c4 100644 --- a/src/tools/miri/src/bin/miri.rs +++ b/src/tools/miri/src/bin/miri.rs @@ -526,6 +526,8 @@ fn main() -> ExitCode { Some(BorrowTrackerMethod::TreeBorrows(TreeBorrowsParams { precise_interior_mut: true, implicit_writes: false, + // We default this to "unique" for now to keep the design space open. + box_custom_allocator_unique: true, })); } else if arg == "-Zmiri-tree-borrows-no-precise-interior-mut" { match &mut miri_config.borrow_tracker { @@ -547,6 +549,16 @@ fn main() -> ExitCode { "`-Zmiri-tree-borrows` is required before `-Zmiri-tree-borrows-implicit-writes`" ), }; + } else if arg == "-Zmiri-tree-borrows-relax-custom-allocator-uniqueness" { + match &mut miri_config.borrow_tracker { + Some(BorrowTrackerMethod::TreeBorrows(params)) => { + params.box_custom_allocator_unique = false; + } + _ => + fatal_error!( + "`-Zmiri-tree-borrows` is required before `-Zmiri-tree-borrows-relax-custom-allocator-uniqueness`" + ), + }; } else if arg == "-Zmiri-disable-data-race-detector" { miri_config.data_race_detector = false; miri_config.weak_memory_emulation = false; diff --git a/src/tools/miri/src/borrow_tracker/mod.rs b/src/tools/miri/src/borrow_tracker/mod.rs index f98644419f660..f11799e0736f9 100644 --- a/src/tools/miri/src/borrow_tracker/mod.rs +++ b/src/tools/miri/src/borrow_tracker/mod.rs @@ -226,9 +226,12 @@ pub enum BorrowTrackerMethod { /// Parameters that Tree Borrows can take. #[derive(Debug, Copy, Clone, PartialEq, Eq)] pub struct TreeBorrowsParams { + /// Controls whether we track `UnsafeCell` with byte precision. pub precise_interior_mut: bool, /// Controls whether `&mut` function arguments are immediately activated with an implicit write. pub implicit_writes: bool, + /// Controls whether `Box` with custom allocator is considered unique. + pub box_custom_allocator_unique: bool, } impl BorrowTrackerMethod { @@ -236,6 +239,7 @@ impl BorrowTrackerMethod { RefCell::new(GlobalStateInner::new(self, config.tracked_pointer_tags.clone())) } + #[track_caller] pub fn get_tree_borrows_params(self) -> TreeBorrowsParams { match self { BorrowTrackerMethod::TreeBorrows(params) => params, diff --git a/src/tools/miri/src/borrow_tracker/stacked_borrows/mod.rs b/src/tools/miri/src/borrow_tracker/stacked_borrows/mod.rs index beca3993d7a0c..48311985ca3b4 100644 --- a/src/tools/miri/src/borrow_tracker/stacked_borrows/mod.rs +++ b/src/tools/miri/src/borrow_tracker/stacked_borrows/mod.rs @@ -869,12 +869,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { RetagMode::None => return interp_ok(None), // no retagging }; let new_perm = if ty.is_box() { - if ty.is_box_global(*this.tcx) { - NewPermission::from_box_ty(val.layout.ty, mode, this) - } else { - // Boxes with local allocator are not retagged. - return interp_ok(None); - } + NewPermission::from_box_ty(val.layout.ty, mode, this) } else { NewPermission::from_ref_ty(val.layout.ty, mode, this) }; diff --git a/src/tools/miri/src/borrow_tracker/tree_borrows/mod.rs b/src/tools/miri/src/borrow_tracker/tree_borrows/mod.rs index 416057659d34d..efeab43d51fa4 100644 --- a/src/tools/miri/src/borrow_tracker/tree_borrows/mod.rs +++ b/src/tools/miri/src/borrow_tracker/tree_borrows/mod.rs @@ -127,7 +127,7 @@ impl<'tcx> NewPermission { pointee: Ty<'tcx>, ref_mutability: Option, mode: RetagMode, - cx: &crate::MiriInterpCx<'tcx>, + cx: &MiriInterpCx<'tcx>, ) -> Option { if mode == RetagMode::None { return None; @@ -143,15 +143,7 @@ impl<'tcx> NewPermission { // `#[rustc_no_writable]` attribute. For performance reasons, only performs the lookup if // is_protected is true as implicit writes are only performed for protected references. let implicit_writes_enabled = is_protected && { - let implicit_writes = cx - .machine - .borrow_tracker - .as_ref() - .unwrap() - .borrow() - .borrow_tracker_method - .get_tree_borrows_params() - .implicit_writes; + let implicit_writes = cx.get_tree_borrows_params().implicit_writes; let def_id = cx.frame().instance().def_id(); implicit_writes && !find_attr!(cx.tcx, def_id, RustcNoWritable) }; @@ -234,6 +226,14 @@ impl<'tcx> NewPermission { /// the implementation of NewPermission. impl<'tcx> EvalContextPrivExt<'tcx> for crate::MiriInterpCx<'tcx> {} trait EvalContextPrivExt<'tcx>: crate::MiriInterpCxExt<'tcx> { + #[track_caller] + #[inline] + fn get_tree_borrows_params(&self) -> TreeBorrowsParams { + let this = self.eval_context_ref(); + let borrow_tracker = this.machine.borrow_tracker.as_ref().unwrap().borrow(); + borrow_tracker.borrow_tracker_method.get_tree_borrows_params() + } + /// Returns the provenance that should be used henceforth. fn tb_reborrow( &mut self, @@ -326,15 +326,7 @@ trait EvalContextPrivExt<'tcx>: crate::MiriInterpCxExt<'tcx> { } let protected = new_perm.protector.is_some(); - let precise_interior_mut = this - .machine - .borrow_tracker - .as_mut() - .unwrap() - .get_mut() - .borrow_tracker_method - .get_tree_borrows_params() - .precise_interior_mut; + let precise_interior_mut = this.get_tree_borrows_params().precise_interior_mut; // Compute initial "inside" permissions. let loc_state = |frozen: bool| -> LocationState { @@ -487,22 +479,25 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { ) -> InterpResult<'tcx, Option>> { let this = self.eval_context_mut(); let new_perm = match *ty.kind() { - _ if ty.is_box_global(*this.tcx) => { - // The `None` marks this as a Box. - NewPermission::new(ty.builtin_deref(true).unwrap(), None, mode, this) - } ty::Ref(_, pointee, mutability) => NewPermission::new(pointee, Some(mutability), mode, this), + _ if ty.is_box() => { + let box_custom_allocator_unique = + this.get_tree_borrows_params().box_custom_allocator_unique; + if box_custom_allocator_unique || ty.is_box_global(*this.tcx) { + // The `None` marks this as a Box. + NewPermission::new(ty.builtin_deref(true).unwrap(), None, mode, this) + } else { + // No retagging for boxes with custom allocators. + None + } + } ty::RawPtr(..) => { assert!(mode == RetagMode::Raw); // We don't give new tags to raw pointers. None } - _ if ty.is_box() => { - // No retagging for boxes with local allocators. - None - } _ => panic!("tb_retag_ptr_value: invalid type {ty}"), }; if let Some(new_perm) = new_perm { diff --git a/src/tools/miri/tests/fail/async-shared-mutable.rs b/src/tools/miri/tests/fail/async-shared-mutable.rs index 62780e7a11c96..f1667f567eb07 100644 --- a/src/tools/miri/tests/fail/async-shared-mutable.rs +++ b/src/tools/miri/tests/fail/async-shared-mutable.rs @@ -3,7 +3,7 @@ //! `UnsafePinned` must include the effects of `UnsafeCell`. //@revisions: stack tree //@[tree]compile-flags: -Zmiri-tree-borrows -//@normalize-stderr-test: "\[0x[a-fx\d.]+\]" -> "[OFFSET]" +//@normalize-stderr-test: "\[0x[a-fx\d.]+\]" -> "[RANGE]" use core::future::Future; use core::pin::{Pin, pin}; diff --git a/src/tools/miri/tests/fail/async-shared-mutable.stack.stderr b/src/tools/miri/tests/fail/async-shared-mutable.stack.stderr index 7cedb24a25644..bdd004d5da99f 100644 --- a/src/tools/miri/tests/fail/async-shared-mutable.stack.stderr +++ b/src/tools/miri/tests/fail/async-shared-mutable.stack.stderr @@ -1,12 +1,12 @@ -error: Undefined Behavior: attempting a write access using at ALLOC[OFFSET], but that tag does not exist in the borrow stack for this location +error: Undefined Behavior: attempting a write access using at ALLOC[RANGE], but that tag does not exist in the borrow stack for this location --> tests/fail/async-shared-mutable.rs:LL:CC | LL | *x = 1; - | ^^^^^^ this error occurs as part of an access at ALLOC[OFFSET] + | ^^^^^^ this error occurs as part of an access at ALLOC[RANGE] | = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information -help: was created by a Unique retag at offsets [OFFSET] +help: was created by a Unique retag at offsets [RANGE] --> tests/fail/async-shared-mutable.rs:LL:CC | LL | / core::future::poll_fn(move |_| { @@ -15,7 +15,7 @@ LL | | Poll::<()>::Pending LL | | }) LL | | .await | |______________^ -help: was later invalidated at offsets [OFFSET] by a SharedReadOnly retag +help: was later invalidated at offsets [RANGE] by a SharedReadOnly retag --> tests/fail/async-shared-mutable.rs:LL:CC | LL | let _: Pin<&_> = f.as_ref(); // Or: `f.as_mut().into_ref()`. diff --git a/src/tools/miri/tests/fail/async-shared-mutable.tree.stderr b/src/tools/miri/tests/fail/async-shared-mutable.tree.stderr index fb68161837683..f9e75082758dd 100644 --- a/src/tools/miri/tests/fail/async-shared-mutable.tree.stderr +++ b/src/tools/miri/tests/fail/async-shared-mutable.tree.stderr @@ -1,4 +1,4 @@ -error: Undefined Behavior: write access through at ALLOC[OFFSET] is forbidden +error: Undefined Behavior: write access through at ALLOC[RANGE] is forbidden --> tests/fail/async-shared-mutable.rs:LL:CC | LL | *x = 1; @@ -16,13 +16,13 @@ LL | | Poll::<()>::Pending LL | | }) LL | | .await | |______________^ -help: the accessed tag later transitioned to Unique due to a child write access at offsets [OFFSET] +help: the accessed tag later transitioned to Unique due to a child write access at offsets [RANGE] --> tests/fail/async-shared-mutable.rs:LL:CC | LL | *x = 1; | ^^^^^^ = help: this transition corresponds to the first write to a 2-phase borrowed mutable reference -help: the accessed tag later transitioned to Frozen due to a reborrow (acting as a foreign read access) at offsets [OFFSET] +help: the accessed tag later transitioned to Frozen due to a reborrow (acting as a foreign read access) at offsets [RANGE] --> tests/fail/async-shared-mutable.rs:LL:CC | LL | let _: Pin<&_> = f.as_ref(); // Or: `f.as_mut().into_ref()`. diff --git a/src/tools/miri/tests/fail/both_borrows/box-custom-alloc-aliasing.rs b/src/tools/miri/tests/fail/both_borrows/box-custom-alloc-aliasing.rs new file mode 100644 index 0000000000000..93626a354d037 --- /dev/null +++ b/src/tools/miri/tests/fail/both_borrows/box-custom-alloc-aliasing.rs @@ -0,0 +1,131 @@ +//! Test related to : +//! `Box` with custom allocators are still `noalias`, leading to UB. + +//@revisions: stack tree +//@[tree]compile-flags: -Zmiri-tree-borrows +//@normalize-stderr-test: "\[0x[a-fx\d.]+\]" -> "[RANGE]" +#![feature(allocator_api)] + +use std::alloc::{AllocError, Allocator, Layout}; +use std::cell::{Cell, UnsafeCell}; +use std::mem; +use std::ptr::{self, NonNull, addr_of}; +use std::thread::{self, ThreadId}; + +const BIN_SIZE: usize = 8; + +// A bin represents a collection of blocks of a specific layout. +#[repr(align(128))] +struct MyBin { + top: Cell, + thread_id: ThreadId, + memory: UnsafeCell<[usize; BIN_SIZE]>, +} + +impl MyBin { + fn pop(&self) -> Option> { + let top = self.top.get(); + if top == BIN_SIZE { + return None; + } + // Cast the *entire* thing to a raw pointer to not restrict its provenance. + let bin = self as *const MyBin; + let base_ptr = UnsafeCell::raw_get(unsafe { addr_of!((*bin).memory) }).cast::(); + let ptr = unsafe { NonNull::new_unchecked(base_ptr.add(top)) }; + self.top.set(top + 1); + Some(ptr.cast()) + } + + // Pretends to not be a throwaway allocation method like this. A more realistic + // substitute is using intrusive linked lists, which requires access to the + // metadata of this bin as well. + unsafe fn push(&self, ptr: NonNull) { + // For now just check that this really is in this bin. + let start = self.memory.get().addr(); + let end = start + BIN_SIZE * mem::size_of::(); + let addr = ptr.addr().get(); + assert!((start..end).contains(&addr)); + + // We can't update `top` as this may not be the last bin, but we can pretend to do so + // such that the aliasing model checks things. + // We access this via raw pointers so that the error span is in this file. + let top_ptr = (&raw const self.top) as *mut usize; + let top = top_ptr.read(); + //~[tree]^ERROR: /read access .* is forbidden/ + top_ptr.write(top); + } +} + +// A collection of bins. +struct MyAllocator { + thread_id: ThreadId, + // Pretends to be some complex collection of bins, such as an array of linked lists. + bins: Box<[MyBin; 1]>, +} + +impl MyAllocator { + fn new() -> Self { + let thread_id = thread::current().id(); + MyAllocator { + thread_id, + bins: Box::new( + [MyBin { top: Cell::new(0), thread_id, memory: UnsafeCell::default() }; 1], + ), + } + } + + // Pretends to be expensive finding a suitable bin for the layout. + fn find_bin(&self, layout: Layout) -> Option<&MyBin> { + if layout == Layout::new::() { Some(&self.bins[0]) } else { None } + } +} + +unsafe impl Allocator for MyAllocator { + fn allocate(&self, layout: Layout) -> Result, AllocError> { + // Expensive bin search. + let bin = self.find_bin(layout).ok_or(AllocError)?; + let ptr = bin.pop().ok_or(AllocError)?; + Ok(NonNull::slice_from_raw_parts(ptr, layout.size())) + } + + unsafe fn deallocate(&self, ptr: NonNull, _layout: Layout) { + // Make sure accesses via `self` don't disturb anything. + let _val = self.bins[0].top.get(); + // Since manually finding the corresponding bin of `ptr` is very expensive, + // doing pointer arithmetics is preferred. + // But this means we access `top` via `ptr` rather than `self`! + // That is fundamentally the source of the aliasing trouble in this example. + let their_bin = ptr.as_ptr().map_addr(|addr| addr & !127).cast::(); + let thread_id = ptr::read(ptr::addr_of!((*their_bin).thread_id)); + //~[stack]^ERROR: tag does not exist in the borrow stack + if self.thread_id == thread_id { + unsafe { (*their_bin).push(ptr) }; + } else { + todo!("Deallocating from another thread"); + } + // Make sure we can also still access this via `self` after the rest is done. + let _val = self.bins[0].top.get(); + } +} + +// Make sure to involve `Box` in allocating these, +// as that's where `noalias` may come from. +fn v1(t: T, a: A) -> Vec { + (Box::new_in([t], a) as Box<[T], A>).into_vec() +} +fn v2(t: T, a: A) -> Vec { + let v = v1(t, a); + // There was a bug in `into_boxed_slice` that caused aliasing issues, + // so round-trip through that as well. + v.into_boxed_slice().into_vec() +} + +fn main() { + assert!(mem::size_of::() <= 128); // if it grows bigger, the trick to access the "header" no longer works + let my_alloc = MyAllocator::new(); + let a = v1(1usize, &my_alloc); + let b = v2(2usize, &my_alloc); + assert_eq!(a[0] + 1, b[0]); + assert_eq!(addr_of!(a[0]).wrapping_add(1), addr_of!(b[0])); + drop((a, b)); +} diff --git a/src/tools/miri/tests/fail/both_borrows/box-custom-alloc-aliasing.stack.stderr b/src/tools/miri/tests/fail/both_borrows/box-custom-alloc-aliasing.stack.stderr new file mode 100644 index 0000000000000..12fb11665fe6d --- /dev/null +++ b/src/tools/miri/tests/fail/both_borrows/box-custom-alloc-aliasing.stack.stderr @@ -0,0 +1,37 @@ +error: Undefined Behavior: attempting a read access using at ALLOC[RANGE], but that tag does not exist in the borrow stack for this location + --> tests/fail/both_borrows/box-custom-alloc-aliasing.rs:LL:CC + | +LL | let thread_id = ptr::read(ptr::addr_of!((*their_bin).thread_id)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ this error occurs as part of an access at ALLOC[RANGE] + | + = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental + = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information +help: was created by a Unique retag at offsets [RANGE] + --> tests/fail/both_borrows/box-custom-alloc-aliasing.rs:LL:CC + | +LL | (Box::new_in([t], a) as Box<[T], A>).into_vec() + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + = note: stack backtrace: + 0: ::deallocate + at tests/fail/both_borrows/box-custom-alloc-aliasing.rs:LL:CC + 1: <&MyAllocator as std::alloc::Allocator>::deallocate + at RUSTLIB/core/src/alloc/mod.rs:LL:CC + 2: alloc::raw_vec::RawVecInner::<&MyAllocator>::deallocate + at RUSTLIB/alloc/src/raw_vec/mod.rs:LL:CC + 3: as std::ops::Drop>::drop + at RUSTLIB/alloc/src/raw_vec/mod.rs:LL:CC + 4: std::ptr::drop_glue::> - shim(Some(alloc::raw_vec::RawVec)) + at RUSTLIB/core/src/ptr/mod.rs:LL:CC + 5: std::ptr::drop_glue::> - shim(Some(std::vec::Vec)) + at RUSTLIB/core/src/ptr/mod.rs:LL:CC + 6: std::ptr::drop_glue::<(std::vec::Vec, std::vec::Vec)> - shim(Some((std::vec::Vec, std::vec::Vec))) + at RUSTLIB/core/src/ptr/mod.rs:LL:CC + 7: std::mem::drop::<(std::vec::Vec, std::vec::Vec)> + at RUSTLIB/core/src/mem/mod.rs:LL:CC + 8: main + at tests/fail/both_borrows/box-custom-alloc-aliasing.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to 1 previous error + diff --git a/src/tools/miri/tests/fail/both_borrows/box-custom-alloc-aliasing.tree.stderr b/src/tools/miri/tests/fail/both_borrows/box-custom-alloc-aliasing.tree.stderr new file mode 100644 index 0000000000000..44b7c2c13e72a --- /dev/null +++ b/src/tools/miri/tests/fail/both_borrows/box-custom-alloc-aliasing.tree.stderr @@ -0,0 +1,52 @@ +error: Undefined Behavior: read access through at ALLOC[RANGE] is forbidden + --> tests/fail/both_borrows/box-custom-alloc-aliasing.rs:LL:CC + | +LL | let top = top_ptr.read(); + | ^^^^^^^^^^^^^^ Undefined Behavior occurred here + | + = help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental + = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/tree-borrows.md for further information + = help: the accessed tag is a child of the conflicting tag + = help: the conflicting tag has state Disabled which forbids this child read access +help: the accessed tag was created here + --> tests/fail/both_borrows/box-custom-alloc-aliasing.rs:LL:CC + | +LL | unsafe fn push(&self, ptr: NonNull) { + | ^^^^^ +help: the conflicting tag was created here, in the initial state Reserved + --> tests/fail/both_borrows/box-custom-alloc-aliasing.rs:LL:CC + | +LL | (Box::new_in([t], a) as Box<[T], A>).into_vec() + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +help: the conflicting tag later transitioned to Disabled due to a foreign write access at offsets [RANGE] + --> tests/fail/both_borrows/box-custom-alloc-aliasing.rs:LL:CC + | +LL | self.top.set(top + 1); + | ^^^^^^^^^^^^^^^^^^^^^ + = help: this transition corresponds to a loss of read and write permissions + = note: stack backtrace: + 0: MyBin::push + at tests/fail/both_borrows/box-custom-alloc-aliasing.rs:LL:CC + 1: ::deallocate + at tests/fail/both_borrows/box-custom-alloc-aliasing.rs:LL:CC + 2: <&MyAllocator as std::alloc::Allocator>::deallocate + at RUSTLIB/core/src/alloc/mod.rs:LL:CC + 3: alloc::raw_vec::RawVecInner::<&MyAllocator>::deallocate + at RUSTLIB/alloc/src/raw_vec/mod.rs:LL:CC + 4: as std::ops::Drop>::drop + at RUSTLIB/alloc/src/raw_vec/mod.rs:LL:CC + 5: std::ptr::drop_glue::> - shim(Some(alloc::raw_vec::RawVec)) + at RUSTLIB/core/src/ptr/mod.rs:LL:CC + 6: std::ptr::drop_glue::> - shim(Some(std::vec::Vec)) + at RUSTLIB/core/src/ptr/mod.rs:LL:CC + 7: std::ptr::drop_glue::<(std::vec::Vec, std::vec::Vec)> - shim(Some((std::vec::Vec, std::vec::Vec))) + at RUSTLIB/core/src/ptr/mod.rs:LL:CC + 8: std::mem::drop::<(std::vec::Vec, std::vec::Vec)> + at RUSTLIB/core/src/mem/mod.rs:LL:CC + 9: main + at tests/fail/both_borrows/box-custom-alloc-aliasing.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to 1 previous error + diff --git a/src/tools/miri/tests/pass/box-custom-alloc-aliasing.rs b/src/tools/miri/tests/pass/box-custom-alloc-aliasing.rs index 06ddda278765a..6b3365839acd1 100644 --- a/src/tools/miri/tests/pass/box-custom-alloc-aliasing.rs +++ b/src/tools/miri/tests/pass/box-custom-alloc-aliasing.rs @@ -1,10 +1,10 @@ //! Regression test for : //! If `Box` has a local allocator, then it can't be `noalias` as the allocator //! may want to access allocator state based on the data pointer. +//! Ensure that the `-Zmiri-tree-borrows-relax-custom-allocator-uniqueness` flag makes us +//! accept such code. -//@revisions: stack tree tree_implicit_writes -//@[tree_implicit_writes]compile-flags: -Zmiri-tree-borrows -Zmiri-tree-borrows-implicit-writes -//@[tree]compile-flags: -Zmiri-tree-borrows +//@compile-flags: -Zmiri-tree-borrows -Zmiri-tree-borrows-relax-custom-allocator-uniqueness -Zmiri-tree-borrows-implicit-writes #![feature(allocator_api)] use std::alloc::{AllocError, Allocator, Layout}; @@ -46,6 +46,13 @@ impl MyBin { let end = start + BIN_SIZE * mem::size_of::(); let addr = ptr.addr().get(); assert!((start..end).contains(&addr)); + + // We can't update `top` as this may not be the last bin, but we can pretend to do so + // such that the aliasing model checks things. + // We access this via raw pointers so that the error span is in this file. + let top_ptr = (&raw const self.top) as *mut usize; + let top = top_ptr.read(); + top_ptr.write(top); } } From cd44d5f6ac583eb1b3f4b68caaedfba375ce6767 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Wed, 27 May 2026 20:53:15 +0200 Subject: [PATCH 09/46] windows shims: avoid representing directories as paths --- src/tools/miri/src/shims/windows/fs.rs | 38 ++++++-------------------- 1 file changed, 8 insertions(+), 30 deletions(-) diff --git a/src/tools/miri/src/shims/windows/fs.rs b/src/tools/miri/src/shims/windows/fs.rs index a6efc36d75f6c..4e2f70fa561d3 100644 --- a/src/tools/miri/src/shims/windows/fs.rs +++ b/src/tools/miri/src/shims/windows/fs.rs @@ -1,7 +1,6 @@ use std::fs::{Metadata, OpenOptions}; use std::io; use std::io::SeekFrom; -use std::path::PathBuf; use std::time::SystemTime; use bitflags::bitflags; @@ -12,32 +11,6 @@ use crate::shims::files::{FdId, FileDescription, FileHandle}; use crate::shims::windows::handle::{EvalContextExt as _, Handle}; use crate::*; -#[derive(Debug)] -pub struct DirHandle { - pub(crate) path: PathBuf, -} - -impl FileDescription for DirHandle { - fn name(&self) -> &'static str { - "directory" - } - - fn metadata<'tcx>( - &self, - ) -> InterpResult<'tcx, Either, &'static str>> { - interp_ok(Either::Left(self.path.metadata())) - } - - fn destroy<'tcx>( - self, - _self_id: FdId, - _communicate_allowed: bool, - _ecx: &mut MiriInterpCx<'tcx>, - ) -> InterpResult<'tcx, io::Result<()>> { - interp_ok(Ok(())) - } -} - /// Windows supports handles without any read/write/delete permissions - these handles can get /// metadata, but little else. We represent that by storing the metadata from the time the handle /// was opened. @@ -260,9 +233,14 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { } let handle = if is_dir { - // Open this as a directory. - let fd_num = this.machine.fds.insert_new(DirHandle { path: file_name }); - Ok(Handle::File(fd_num)) + // Open this as a directory. We don't support proper directory handles, but we only need + // this for the metadata, so we only store that. That still makes us immune to races, + // though it also means we fail to reflect changes to the dir's metadata. + // FIXME: use `std::fs::Dir`, once that supports getting metadata. + file_name.metadata().map(|meta| { + let fd_num = this.machine.fds.insert_new(MetadataHandle { meta }); + Handle::File(fd_num) + }) } else if creation_disposition == OpenExisting && !(desired_read || desired_write) { // Windows supports handles with no permissions. These allow things such as reading // metadata, but not file content. From d22cc820518716c4ba3bec86ac82828cd7e96fe9 Mon Sep 17 00:00:00 2001 From: Aelin Reidel Date: Mon, 25 May 2026 01:27:01 +0200 Subject: [PATCH 10/46] Shim the LoongArch CRC intrinsics --- src/tools/miri/src/shims/foreign_items.rs | 8 +++ src/tools/miri/src/shims/loongarch.rs | 70 +++++++++++++++++++ src/tools/miri/src/shims/mod.rs | 1 + .../loongarch/intrinsics-loongarch64-crc.rs | 54 ++++++++++++++ 4 files changed, 133 insertions(+) create mode 100644 src/tools/miri/src/shims/loongarch.rs create mode 100644 src/tools/miri/tests/pass/shims/loongarch/intrinsics-loongarch64-crc.rs diff --git a/src/tools/miri/src/shims/foreign_items.rs b/src/tools/miri/src/shims/foreign_items.rs index bff93a7bb9d7a..e4daab7a2005d 100644 --- a/src/tools/miri/src/shims/foreign_items.rs +++ b/src/tools/miri/src/shims/foreign_items.rs @@ -857,6 +857,14 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> { this, link_name, abi, args, dest, ); } + name if name.starts_with("llvm.loongarch.") + && matches!(this.tcx.sess.target.arch, Arch::LoongArch32 | Arch::LoongArch64) + && this.tcx.sess.target.endian == Endian::Little => + { + return shims::loongarch::EvalContextExt::emulate_loongarch_intrinsic( + this, link_name, abi, args, dest, + ); + } // Fallback to shims in submodules. _ => { diff --git a/src/tools/miri/src/shims/loongarch.rs b/src/tools/miri/src/shims/loongarch.rs new file mode 100644 index 0000000000000..e5a57fbcaebfb --- /dev/null +++ b/src/tools/miri/src/shims/loongarch.rs @@ -0,0 +1,70 @@ +use rustc_abi::CanonAbi; +use rustc_middle::ty::Ty; +use rustc_span::Symbol; +use rustc_target::callconv::FnAbi; + +use crate::shims::math::compute_crc32; +use crate::*; + +impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {} +pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { + fn emulate_loongarch_intrinsic( + &mut self, + link_name: Symbol, + abi: &FnAbi<'tcx, Ty<'tcx>>, + args: &[OpTy<'tcx>], + dest: &MPlaceTy<'tcx>, + ) -> InterpResult<'tcx, EmulateItemResult> { + let this = self.eval_context_mut(); + // Prefix should have already been checked. + let unprefixed_name = link_name.as_str().strip_prefix("llvm.loongarch.").unwrap(); + match unprefixed_name { + // Used to implement the crc.w.{b,h,w,d}.w and crcc.w.{b,h,w,d}.w functions. + // https://loongson.github.io/LoongArch-Documentation/LoongArch-Vol1-EN.html#crc-check-instructions + // These are only available on LA64, not on LA32, and are part of + // the LA64 1.0 baseline and therefore always available and don't + // require a target feature to be enabled. + "crc.w.b.w" | "crc.w.h.w" | "crc.w.w.w" | "crc.w.d.w" | "crcc.w.b.w" | "crcc.w.h.w" + | "crcc.w.w.w" | "crcc.w.d.w" + if this.tcx.pointer_size().bits() == 64 => + { + // The polynomial constants below include the leading 1 bit + // (e.g. 0x104C11DB7 instead of 0x04C11DB7) which the the + // polynomial division algorithm requires. + // Note that Loongson's documentation mentions the numbers + // 0xEDB88320 for IEEE802.3 and 0x82F63B78 for Castagnoli, + // which is because their docs put the least significant bit + // first. + let (bit_size, polynomial): (u32, u128) = match unprefixed_name { + "crc.w.b.w" => (8, 0x104C11DB7), + "crc.w.h.w" => (16, 0x104C11DB7), + "crc.w.w.w" => (32, 0x104C11DB7), + "crc.w.d.w" => (64, 0x104C11DB7), + "crcc.w.b.w" => (8, 0x11EDC6F41), + "crcc.w.h.w" => (16, 0x11EDC6F41), + "crcc.w.w.w" => (32, 0x11EDC6F41), + "crcc.w.d.w" => (64, 0x11EDC6F41), + _ => unreachable!(), + }; + + let [data, crc] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?; + let data = this.read_scalar(data)?; + let crc = this.read_scalar(crc)?; + + // The CRC accumulator is always i32. The data argument is i32 for + // b/h/w variants and i64 for the d variant, per the LLVM intrinsic + // definitions. + // https://github.com/llvm/llvm-project/blob/main/llvm/include/llvm/IR/IntrinsicsLoongArch.td + // If the higher bits are non-zero, `compute_crc32` will panic. We should probably + // raise a proper error instead, but outside stdarch nobody can trigger this anyway. + let crc = crc.to_u32()?; + let data = if bit_size == 64 { data.to_u64()? } else { u64::from(data.to_u32()?) }; + + let result = compute_crc32(crc, data, bit_size, polynomial); + this.write_scalar(Scalar::from_u32(result), dest)?; + } + _ => return interp_ok(EmulateItemResult::NotSupported), + } + interp_ok(EmulateItemResult::NeedsReturn) + } +} diff --git a/src/tools/miri/src/shims/mod.rs b/src/tools/miri/src/shims/mod.rs index 551f7b2eff34e..d84e83d49400f 100644 --- a/src/tools/miri/src/shims/mod.rs +++ b/src/tools/miri/src/shims/mod.rs @@ -4,6 +4,7 @@ mod aarch64; mod alloc; mod backtrace; mod files; +mod loongarch; mod math; #[cfg(all(feature = "native-lib", unix))] pub mod native_lib; diff --git a/src/tools/miri/tests/pass/shims/loongarch/intrinsics-loongarch64-crc.rs b/src/tools/miri/tests/pass/shims/loongarch/intrinsics-loongarch64-crc.rs new file mode 100644 index 0000000000000..cc96653991234 --- /dev/null +++ b/src/tools/miri/tests/pass/shims/loongarch/intrinsics-loongarch64-crc.rs @@ -0,0 +1,54 @@ +// We're testing loongarch64-specific intrinsics +//@only-target: loongarch64 +#![feature(stdarch_loongarch)] + +use std::arch::loongarch64::*; + +fn main() { + test_crc_ieee(); + test_crc_castagnoli(); +} + +fn test_crc_ieee() { + // crc.w.b.w: 8-bit input + assert_eq!(crc_w_b_w(0x01, 0x00000000), 0x77073096); + assert_eq!(crc_w_b_w(0x61, 0xffffffff_u32 as i32), 0x174841bc); + assert_eq!(crc_w_b_w(0x2a, 0x2aa1e72b), 0x772d9171); + + // crc.w.h.w: 16-bit input + assert_eq!(crc_w_h_w(0x0001, 0x00000000), 0x191b3141); + assert_eq!(crc_w_h_w(0x1234, 0xffffffff_u32 as i32), 0xf6b56fbf_u32 as i32); + assert_eq!(crc_w_h_w(0x022b, 0x8ecec3b5_u32 as i32), 0x03a1db7c); + + // crc.w.w.w: 32-bit input + assert_eq!(crc_w_w_w(0x00000001, 0x00000000), 0xb8bc6765_u32 as i32); + assert_eq!(crc_w_w_w(0x12345678, 0xffffffff_u32 as i32), 0x5092782d); + assert_eq!(crc_w_w_w(0x00845fed, 0xae2912c8_u32 as i32), 0xc5690dd4_u32 as i32); + + // crc.w.d.w: 64-bit input + assert_eq!(crc_w_d_w(0x0000000000000001, 0x00000000), 0xccaa009e_u32 as i32); + assert_eq!(crc_w_d_w(0x123456789abcdef0, 0xffffffff_u32 as i32), 0xe6ddf8b5_u32 as i32); + assert_eq!(crc_w_d_w(0xc0febeefdadafefe_u64 as i64, 0x0badeafe), 0x61a45fba); +} + +fn test_crc_castagnoli() { + // crcc.w.b.w: 8-bit input + assert_eq!(crcc_w_b_w(0x01, 0x00000000), 0xf26b8303_u32 as i32); + assert_eq!(crcc_w_b_w(0x61, 0xffffffff_u32 as i32), 0x3e2fbccf); + assert_eq!(crcc_w_b_w(0x2a, 0x2aa1e72b), 0xf24122e4_u32 as i32); + + // crcc.w.h.w: 16-bit input + assert_eq!(crcc_w_h_w(0x0001, 0x00000000), 0x13a29877); + assert_eq!(crcc_w_h_w(0x1234, 0xffffffff_u32 as i32), 0xf13f4cea_u32 as i32); + assert_eq!(crcc_w_h_w(0x022b, 0x8ecec3b5_u32 as i32), 0x013bb2fb); + + // crcc.w.w.w: 32-bit input + assert_eq!(crcc_w_w_w(0x00000001, 0x00000000), 0xdd45aab8_u32 as i32); + assert_eq!(crcc_w_w_w(0x12345678, 0xffffffff_u32 as i32), 0x4dece20c); + assert_eq!(crcc_w_w_w(0x00845fed, 0xae2912c8_u32 as i32), 0xffae2ed1_u32 as i32); + + // crcc.w.d.w: 64-bit input + assert_eq!(crcc_w_d_w(0x0000000000000001, 0x00000000), 0x493c7d27); + assert_eq!(crcc_w_d_w(0x123456789abcdef0, 0xffffffff_u32 as i32), 0xd95b664b_u32 as i32); + assert_eq!(crcc_w_d_w(0xc0febeefdadafefe_u64 as i64, 0x0badeafe), 0x5b44f54f); +} From 62cc075ca2bbaf376fc445c813ff246c8abecec8 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Wed, 27 May 2026 21:03:42 +0200 Subject: [PATCH 11/46] aarch64 crc: better variable names --- src/tools/miri/src/shims/aarch64.rs | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/tools/miri/src/shims/aarch64.rs b/src/tools/miri/src/shims/aarch64.rs index 6a914d5cfa68a..d3d411b2d2b01 100644 --- a/src/tools/miri/src/shims/aarch64.rs +++ b/src/tools/miri/src/shims/aarch64.rs @@ -196,10 +196,9 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { _ => unreachable!(), }; - let [left, right] = - this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?; - let left = this.read_scalar(left)?; - let right = this.read_scalar(right)?; + let [crc, data] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?; + let crc = this.read_scalar(crc)?; + let data = this.read_scalar(data)?; // The CRC accumulator is always u32. The data argument is u32 for // b/h/w variants and u64 for the x variant, per the LLVM intrinsic @@ -207,9 +206,8 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // https://github.com/llvm/llvm-project/blob/main/llvm/include/llvm/IR/IntrinsicsAArch64.td // If the higher bits are non-zero, `compute_crc32` will panic. We should probably // raise a proper error instead, but outside stdarch nobody can trigger this anyway. - let crc = left.to_u32()?; - let data = - if bit_size == 64 { right.to_u64()? } else { u64::from(right.to_u32()?) }; + let crc = crc.to_u32()?; + let data = if bit_size == 64 { data.to_u64()? } else { u64::from(data.to_u32()?) }; let result = compute_crc32(crc, data, bit_size, polynomial); this.write_scalar(Scalar::from_u32(result), dest)?; From d4442c05eae392b2d03a2cb975e40edd708e97ba Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Wed, 27 May 2026 21:09:41 +0200 Subject: [PATCH 12/46] add loongarch64-unknown-linux-gnu target to CI (not officially supported) --- src/tools/miri/ci/ci.sh | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/tools/miri/ci/ci.sh b/src/tools/miri/ci/ci.sh index a87c6faa32686..f7c0b739c1dd4 100755 --- a/src/tools/miri/ci/ci.sh +++ b/src/tools/miri/ci/ci.sh @@ -150,11 +150,10 @@ case $HOST_TARGET in i686-unknown-linux-gnu) # Host MIR_OPT=1 MANY_SEEDS=64 TEST_BENCH=1 CARGO_MIRI_ENV=1 run_tests - # Fully, but not officially, supported tier 2 + # Not officially supported tier 2 MANY_SEEDS=16 TEST_TARGET=aarch64-linux-android run_tests - # Partially supported targets (tier 2) - BASIC="empty_main integer heap_alloc libc-mem vec string btreemap" # ensures we have the basics: pre-main code, system allocator - UNIX="hello panic/panic panic/unwind concurrency/simple atomic libc-mem libc-misc libc-random env num_cpus" # the things that are very similar across all Unixes, and hence easily supported there + MANY_SEEDS=16 TEST_TARGET=loongarch64-unknown-linux-gnu run_tests + # Partially supported targets (no_std, tier 2) TEST_TARGET=wasm32-unknown-unknown run_tests_minimal no_std empty_main wasm # this target doesn't really have std TEST_TARGET=thumbv7em-none-eabihf run_tests_minimal no_std ;; From 9eea936a3da7b26495a66c17fb6467460db47f73 Mon Sep 17 00:00:00 2001 From: The Miri Cronjob Bot Date: Thu, 28 May 2026 06:14:28 +0000 Subject: [PATCH 13/46] Prepare for merging from rust-lang/rust This updates the rust-version file to 8f02e856be945d5d879b2ff9b7d19dd6bfb1c0e5. --- src/tools/miri/rust-version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tools/miri/rust-version b/src/tools/miri/rust-version index 4a823e1b6430d..d0dde42d262f9 100644 --- a/src/tools/miri/rust-version +++ b/src/tools/miri/rust-version @@ -1 +1 @@ -1f8e04d34ab0c1fd9574840aa6db670e41593bfb +8f02e856be945d5d879b2ff9b7d19dd6bfb1c0e5 From 97f91eb6575e27767a309a17120d7ee727d0fd56 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Thu, 28 May 2026 09:28:49 +0200 Subject: [PATCH 14/46] flock tests: cover the case of asking for both locks on the same file --- .../miri/tests/pass-dep/libc/libc-fs-flock.rs | 46 ++++++++++++++++--- src/tools/miri/tests/pass/shims/fs.rs | 13 +++--- 2 files changed, 47 insertions(+), 12 deletions(-) diff --git a/src/tools/miri/tests/pass-dep/libc/libc-fs-flock.rs b/src/tools/miri/tests/pass-dep/libc/libc-fs-flock.rs index 52de8c7104c23..d8af9224ed77a 100644 --- a/src/tools/miri/tests/pass-dep/libc/libc-fs-flock.rs +++ b/src/tools/miri/tests/pass-dep/libc/libc-fs-flock.rs @@ -3,6 +3,10 @@ //@ignore-target: android # Does not (always?) have flock //@compile-flags: -Zmiri-disable-isolation +//@revisions: windows_host unix_host +//@[unix_host] ignore-host: windows +//@[windows_host] only-host: windows + use std::fs::File; use std::os::fd::AsRawFd; @@ -18,12 +22,12 @@ fn main() { let files: Vec = (0..3).map(|_| File::open(&path).unwrap()).collect(); - // Test that we can apply many shared locks + // Test that we can apply many shared locks. for file in files.iter() { errno_check(unsafe { libc::flock(file.as_raw_fd(), libc::LOCK_SH) }); } - // Test that shared lock prevents exclusive lock + // Test that shared lock prevents exclusive lock. { let fd = files[0].as_raw_fd(); let err = @@ -31,18 +35,18 @@ fn main() { assert_eq!(err.raw_os_error().unwrap(), libc::EWOULDBLOCK); } - // Unlock shared lock + // Unlock shared lock. for file in files.iter() { errno_check(unsafe { libc::flock(file.as_raw_fd(), libc::LOCK_UN) }); } - // Take exclusive lock + // Take exclusive lock. { let fd = files[0].as_raw_fd(); errno_check(unsafe { libc::flock(fd, libc::LOCK_EX) }); } - // Test that shared lock prevents exclusive and shared locks + // Test that exclusive lock prevents exclusive and shared locks. { let fd = files[1].as_raw_fd(); let err = @@ -55,9 +59,39 @@ fn main() { assert_eq!(err.raw_os_error().unwrap(), libc::EWOULDBLOCK); } - // Unlock exclusive lock + // Unlock exclusive lock. { let fd = files[0].as_raw_fd(); errno_check(unsafe { libc::flock(fd, libc::LOCK_UN) }); + // Redundant unlock also works. + // FIXME(#miri/5074): except on Windows hosts... + if !cfg!(windows_host) { + errno_check(unsafe { libc::flock(fd, libc::LOCK_UN) }); + } + } + + // Test behavior when we acquire multiple locks on the same FD. + // FIXME(#miri/5074): this does not behave correctly on Windows hosts. + if !cfg!(windows_host) { + let fd1 = files[1].as_raw_fd(); + let fd2 = files[2].as_raw_fd(); + + errno_check(unsafe { libc::flock(fd1, libc::LOCK_EX | libc::LOCK_NB) }); + // This converts the exclusive lock to a shared lock. + errno_check(unsafe { libc::flock(fd1, libc::LOCK_SH | libc::LOCK_NB) }); + // Now the other fd can have the shared lock as well. + errno_check(unsafe { libc::flock(fd2, libc::LOCK_SH | libc::LOCK_NB) }); + + // Reset. + errno_check(unsafe { libc::flock(fd1, libc::LOCK_UN) }); + errno_check(unsafe { libc::flock(fd2, libc::LOCK_UN) }); + + // Getting first a shared lock and then upgrading to exclusive should also work. + errno_check(unsafe { libc::flock(fd1, libc::LOCK_SH | libc::LOCK_NB) }); + errno_check(unsafe { libc::flock(fd1, libc::LOCK_EX | libc::LOCK_NB) }); + // This is truly exclusive: fd2 is locked out. + let err = + errno_result(unsafe { libc::flock(fd2, libc::LOCK_SH | libc::LOCK_NB) }).unwrap_err(); + assert_eq!(err.raw_os_error().unwrap(), libc::EWOULDBLOCK); } } diff --git a/src/tools/miri/tests/pass/shims/fs.rs b/src/tools/miri/tests/pass/shims/fs.rs index b249c065318c2..3dcb4a01bf41b 100644 --- a/src/tools/miri/tests/pass/shims/fs.rs +++ b/src/tools/miri/tests/pass/shims/fs.rs @@ -417,20 +417,21 @@ fn test_flock() { let file1 = OpenOptions::new().read(true).write(true).open(&path).unwrap(); let file2 = OpenOptions::new().read(true).write(true).open(&path).unwrap(); - // Test that we can apply many shared locks + // Test that we can apply many shared locks. file1.lock_shared().unwrap(); file2.lock_shared().unwrap(); - // Test that shared lock prevents exclusive lock + // Test that shared lock prevents exclusive lock. assert!(matches!(file1.try_lock().unwrap_err(), fs::TryLockError::WouldBlock)); - // Unlock shared lock + // Unlock both files. file1.unlock().unwrap(); file2.unlock().unwrap(); - // Take exclusive lock + + // Take exclusive lock. file1.lock().unwrap(); - // Test that shared lock prevents exclusive and shared locks + // Test that shared lock prevents exclusive and shared locks. assert!(matches!(file2.try_lock().unwrap_err(), fs::TryLockError::WouldBlock)); assert!(matches!(file2.try_lock_shared().unwrap_err(), fs::TryLockError::WouldBlock)); - // Unlock exclusive lock + // Unlock exclusive lock. file1.unlock().unwrap(); } From b465d6621d49a856da7ab0a47064fe6461918f38 Mon Sep 17 00:00:00 2001 From: WhySoBad <49595640+WhySoBad@users.noreply.github.com> Date: Tue, 26 May 2026 22:40:24 +0200 Subject: [PATCH 15/46] Add support for level-triggered epoll --- .../miri/src/shims/unix/linux_like/epoll.rs | 96 ++-- .../pass-dep/libc/libc-epoll-blocking.rs | 96 ++-- .../pass-dep/libc/libc-epoll-no-blocking.rs | 414 ++++++++++-------- 3 files changed, 350 insertions(+), 256 deletions(-) diff --git a/src/tools/miri/src/shims/unix/linux_like/epoll.rs b/src/tools/miri/src/shims/unix/linux_like/epoll.rs index 3ecc2c1135de8..cf409dba86c59 100644 --- a/src/tools/miri/src/shims/unix/linux_like/epoll.rs +++ b/src/tools/miri/src/shims/unix/linux_like/epoll.rs @@ -1,7 +1,6 @@ use std::cell::RefCell; -use std::collections::{BTreeMap, BTreeSet, VecDeque}; +use std::collections::{BTreeMap, VecDeque}; use std::io; -use std::ops::Bound; use std::time::Duration; use rustc_abi::FieldIdx; @@ -23,7 +22,9 @@ pub struct Epoll { interest_list: RefCell>, /// The subset of interests that is currently considered "ready". Stored separately so we /// can access it more efficiently. - ready_set: RefCell>, + /// This is implemented as a queue so that for level-triggered epoll, all events eventually + /// get returned from `epoll_wait`. The queue does not contain any duplicates. + ready_events: RefCell>, /// The queue of threads blocked on this epoll instance. queue: RefCell>, } @@ -46,6 +47,9 @@ pub struct EpollEventInterest { relevant_events: u32, /// The currently active events for this file descriptor. active_events: u32, + /// Boolean whether this is an edge-triggered interest. + /// When [`false`] it's a level-triggered interest instead. + is_edge_triggered: bool, /// The vector clock for wakeups. clock: VClock, /// User-defined data associated with this interest. @@ -203,12 +207,8 @@ impl EpollInterestTable { .extract_if(range_for_id(id), |_, _| true) // Consume the iterator. .for_each(drop); - epoll - .ready_set - .borrow_mut() - .extract_if(range_for_id(id), |_| true) - // Consume the iterator. - .for_each(drop); + // Remove the ready events for this file description. + epoll.ready_events.borrow_mut().retain(|(fd_id, _)| fd_id != &id); } } } @@ -303,6 +303,13 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { this.read_scalar(&this.project_field(&event, FieldIdx::ZERO)?)?.to_u32()?; let data = this.read_scalar(&this.project_field(&event, FieldIdx::ONE)?)?.to_u64()?; + let is_edge_triggered = if events & epollet == epollet { + events &= !epollet; + true + } else { + false + }; + // Unset the flag we support to discover if any unsupported flags are used. let mut flags = events; // epoll_wait(2) will always wait for epollhup and epollerr; it is not @@ -311,12 +318,6 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { events |= epollhup; events |= epollerr; - if events & epollet != epollet { - // We only support edge-triggered notification for now. - throw_unsup_format!("epoll_ctl: epollet flag must be included."); - } else { - flags &= !epollet; - } if flags & epollin == epollin { flags &= !epollin; } @@ -350,6 +351,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { } let new_interest = EpollEventInterest { relevant_events: events, + is_edge_triggered, data, active_events: 0, clock: VClock::default(), @@ -364,6 +366,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { return this.set_errno_and_return_neg1_i32(LibcError("ENOENT")); }; interest.relevant_events = events; + interest.is_edge_triggered = is_edge_triggered; interest.data = data; } @@ -391,7 +394,11 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // We did not have interest in this. return this.set_errno_and_return_neg1_i32(LibcError("ENOENT")); }; - epfd.ready_set.borrow_mut().remove(&epoll_key); + // Remove the ready event for this key, should one exist. + let mut ready_events = epfd.ready_events.borrow_mut(); + if let Some(idx) = ready_events.iter().position(|k| k == &epoll_key) { + ready_events.remove(idx); + } // If this was the last interest in this FD, remove us from the global list // of who is interested in this FD. if interest_list.range(range_for_id(id)).next().is_none() { @@ -469,7 +476,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { return this.set_errno_and_return_neg1(LibcError("EBADF"), dest); }; - if timeout == 0 || !epfd.ready_set.borrow().is_empty() { + if timeout == 0 || !epfd.ready_events.borrow().is_empty() { // If the timeout is 0 or there is a ready event, we can return immediately. return_ready_list(&epfd, dest, &event, this)?; } else { @@ -590,18 +597,29 @@ fn update_readiness<'tcx>( &mut dyn FnMut(EpollEventKey, &mut EpollEventInterest) -> InterpResult<'tcx>, ) -> InterpResult<'tcx>, ) -> InterpResult<'tcx> { - let mut ready_set = epoll.ready_set.borrow_mut(); + let mut ready_events = epoll.ready_events.borrow_mut(); for_each_interest(&mut |key, interest| { // Update the ready events tracked in this interest. let new_readiness = interest.relevant_events & active_events; let prev_readiness = std::mem::replace(&mut interest.active_events, new_readiness); if new_readiness == 0 { // Un-trigger this, there's nothing left to report here. - ready_set.remove(&key); + if let Some(idx) = ready_events.iter().position(|k| k == &key) { + ready_events.remove(idx); + } } else if force_edge || new_readiness != prev_readiness & new_readiness { - // Either we force an "edge" to be detected, or there's a bit set in `new` - // that was not set in `prev`. In both cases, this is ready now. - ready_set.insert(key); + // Either we force an "edge" to be detected or there's a bit set in `new_readiness` + // that was not set in `prev_readiness`. In both cases, this is ready now. + + // We need to ensure that this event is not already part of the + // `ready_events` queue before enqueueing: + // + if !ready_events.contains(&key) { + ready_events.push_back(key); + } + + // No matter whether this is newly ready or just re-triggered, + // the `epoll_wait` fetching this event should sync with the current thread. ecx.release_clock(|clock| { interest.clock.join(clock); })?; @@ -609,12 +627,12 @@ fn update_readiness<'tcx>( interp_ok(()) })?; // While there are events ready to be delivered, wake up a thread to receive them. - while !ready_set.is_empty() + while !ready_events.is_empty() && let Some(thread_id) = epoll.queue.borrow_mut().pop_front() { - drop(ready_set); // release the "lock" so the unblocked thread can have it + drop(ready_events); // release the "lock" so the unblocked thread can have it ecx.unblock_thread(thread_id, BlockReason::Epoll { epfd: epoll.clone() })?; - ready_set = epoll.ready_set.borrow_mut(); + ready_events = epoll.ready_events.borrow_mut(); } interp_ok(()) @@ -629,7 +647,7 @@ fn return_ready_list<'tcx>( ecx: &mut MiriInterpCx<'tcx>, ) -> InterpResult<'tcx, i32> { let mut interest_list = epfd.interest_list.borrow_mut(); - let mut ready_set = epfd.ready_set.borrow_mut(); + let mut ready_events = epfd.ready_events.borrow_mut(); let mut num_of_events: i32 = 0; let mut array_iter = ecx.project_array_fields(events)?; @@ -644,27 +662,29 @@ fn return_ready_list<'tcx>( } } - // While there is a slot to store another event, and an event to store, deliver that event. - // We can't use an iterator over `ready_set` as we want to remove elements as we go, - // so we track the most recently delivered event to find the next one. We track it as a lower - // bound that we can pass to `BTreeSet::range`. - let mut event_lower_bound = Bound::Unbounded; - while let Some(slot) = array_iter.next(ecx)? - && let Some(&key) = ready_set.range((event_lower_bound, Bound::Unbounded)).next() + // We will fill at most the first `ready_events_len` slots of the array. + // Bounding the iterator this way ensures that we can re-add events + // to the end of the queue during the loop without having them show up in the array. + let ready_events_len = u64::try_from(ready_events.len()).unwrap(); + while let Some((idx, slot)) = array_iter.next(ecx)? + && idx < ready_events_len + && let Some(key) = ready_events.pop_front() { let interest = interest_list.get_mut(&key).expect("non-existent event in ready set"); // Deliver event to caller. ecx.write_int_fields_named( &[("events", interest.active_events.into()), ("u64", interest.data.into())], - &slot.1, + &slot, )?; num_of_events = num_of_events.strict_add(1); // Synchronize receiving thread with the event of interest. ecx.acquire_clock(&interest.clock)?; - // This was an edge-triggered event, so remove it from the ready set. - ready_set.remove(&key); - // Go find the next event. - event_lower_bound = Bound::Excluded(key); + if !interest.is_edge_triggered { + // This is a level-triggered interest, so we need to re-add the event + // at the end of the ready queue: + // + ready_events.push_back(key); + } } ecx.write_int(num_of_events, dest)?; interp_ok(num_of_events) diff --git a/src/tools/miri/tests/pass-dep/libc/libc-epoll-blocking.rs b/src/tools/miri/tests/pass-dep/libc/libc-epoll-blocking.rs index 8865c605a3b65..95f82cfba9f61 100644 --- a/src/tools/miri/tests/pass-dep/libc/libc-epoll-blocking.rs +++ b/src/tools/miri/tests/pass-dep/libc/libc-epoll-blocking.rs @@ -1,6 +1,7 @@ //@only-target: linux android illumos // test_epoll_block_then_unblock and test_epoll_race depend on a deterministic schedule. //@compile-flags: -Zmiri-deterministic-concurrency +//@revisions: edge_triggered level_triggered use std::convert::TryInto; use std::thread; @@ -10,6 +11,10 @@ mod libc_utils; use libc_utils::epoll::*; use libc_utils::*; +/// When the `edge_triggered` revision is active, this is EPOLLET, otherwise +/// it's zero which means we perform level-triggered epolls. +const EPOLLET_OR_ZERO: libc::c_int = if cfg!(edge_triggered) { EPOLLET } else { 0 }; + // This is a set of testcases for blocking epoll. fn main() { @@ -21,7 +26,9 @@ fn main() { multiple_events_wake_multiple_threads(); } -// This test allows epoll_wait to block, then unblock without notification. +// This test allows edge-triggered epoll_wait to block and then unblock +// without notification because the timeout expired. +// The level-triggered epoll_wait should not block. fn test_epoll_block_without_notification() { // Create an epoll instance. let epfd = errno_result(unsafe { libc::epoll_create1(0) }).unwrap(); @@ -31,16 +38,24 @@ fn test_epoll_block_without_notification() { let fd = errno_result(unsafe { libc::eventfd(0, flags) }).unwrap(); // Register eventfd with epoll. - epoll_ctl_add(epfd, fd, libc::EPOLLIN | libc::EPOLLOUT | libc::EPOLLET).unwrap(); + epoll_ctl_add(epfd, fd, EPOLLIN | EPOLLOUT | EPOLLET_OR_ZERO).unwrap(); // epoll_wait to clear notification. - check_epoll_wait::<1>(epfd, &[Ev { events: libc::EPOLLOUT, data: fd }], 0); - - // This epoll wait blocks, and timeout without notification. - check_epoll_wait::<1>(epfd, &[], 5); + check_epoll_wait::<8>(epfd, &[Ev { events: EPOLLOUT, data: fd }], -1); + + if cfg!(edge_triggered) { + // This epoll wait blocks, and timeout without notification. + check_epoll_wait::<1>(epfd, &[], 5); + } else { + // In level-triggered mode we should receive the same events + // as before without timing out. + check_epoll_wait::<8>(epfd, &[Ev { events: EPOLLOUT, data: fd }], -1); + } } -// This test triggers notification and unblocks the epoll_wait before timeout. +// This test triggers notification and unblocks the edge-triggered epoll_wait +// before the timeout exceeds. +// The level-triggered epoll_wait should not block. fn test_epoll_block_then_unblock() { // Create an epoll instance. let epfd = errno_result(unsafe { libc::epoll_create1(0) }).unwrap(); @@ -50,21 +65,33 @@ fn test_epoll_block_then_unblock() { errno_check(unsafe { libc::socketpair(libc::AF_UNIX, libc::SOCK_STREAM, 0, fds.as_mut_ptr()) }); // Register one side of the socketpair with epoll. - epoll_ctl_add(epfd, fds[0], libc::EPOLLIN | libc::EPOLLOUT | libc::EPOLLET).unwrap(); + epoll_ctl_add(epfd, fds[0], EPOLLIN | EPOLLOUT | EPOLLET_OR_ZERO).unwrap(); // epoll_wait to clear notification. - check_epoll_wait::<1>(epfd, &[Ev { events: libc::EPOLLOUT, data: fds[0] }], 0); + check_epoll_wait::<2>(epfd, &[Ev { events: EPOLLOUT, data: fds[0] }], -1); - // epoll_wait before triggering notification so it will block then get unblocked before timeout. let thread1 = thread::spawn(move || { thread::yield_now(); + // Due to deterministic concurrency, we'll only get here when the other thread blocks. write_all(fds[1], b"abcde").unwrap(); }); - check_epoll_wait::<1>(epfd, &[Ev { events: libc::EPOLLIN | libc::EPOLLOUT, data: fds[0] }], 10); + + if cfg!(edge_triggered) { + // Edge-triggered epoll will block until the write succeeds and the buffer + // becomes readable. This is because we already read the writable edge + // before so at the time of calling `epoll_wait` there is no active readiness. + check_epoll_wait::<2>(epfd, &[Ev { events: EPOLLIN | EPOLLOUT, data: fds[0] }], 10); + } else { + // Level-triggered epoll won't wait for the write to succeed because + // _some_ readiness is already set (in this case the EPOLLOUT). + check_epoll_wait::<2>(epfd, &[Ev { events: EPOLLOUT, data: fds[0] }], -1); + } + thread1.join().unwrap(); } -// This test triggers a notification after epoll_wait times out. +// This test triggers a notification after epoll_wait times out in edge-triggered mode. +// In level-triggered the epoll_wait should not time out. fn test_notification_after_timeout() { // Create an epoll instance. let epfd = errno_result(unsafe { libc::epoll_create1(0) }).unwrap(); @@ -74,19 +101,26 @@ fn test_notification_after_timeout() { errno_check(unsafe { libc::socketpair(libc::AF_UNIX, libc::SOCK_STREAM, 0, fds.as_mut_ptr()) }); // Register one side of the socketpair with epoll. - epoll_ctl_add(epfd, fds[0], libc::EPOLLIN | libc::EPOLLOUT | libc::EPOLLET).unwrap(); + epoll_ctl_add(epfd, fds[0], EPOLLIN | EPOLLOUT | EPOLLET_OR_ZERO).unwrap(); // epoll_wait to clear notification. - check_epoll_wait::<1>(epfd, &[Ev { events: libc::EPOLLOUT, data: fds[0] }], 0); - - // epoll_wait timeouts without notification. - check_epoll_wait::<1>(epfd, &[], 10); + check_epoll_wait::<2>(epfd, &[Ev { events: EPOLLOUT, data: fds[0] }], -1); + + if cfg!(edge_triggered) { + // Edge-triggered epoll wait times out without notification because + // we just processed the edge. + check_epoll_wait::<1>(epfd, &[], 10); + } else { + // Level-triggered epoll just returns the same events as before + // without blocking. + check_epoll_wait::<2>(epfd, &[Ev { events: EPOLLOUT, data: fds[0] }], -1); + } // Trigger epoll notification after timeout. write_all(fds[1], b"abcde").unwrap(); // Check the result of the notification. - check_epoll_wait::<1>(epfd, &[Ev { events: libc::EPOLLIN | libc::EPOLLOUT, data: fds[0] }], 10); + check_epoll_wait::<2>(epfd, &[Ev { events: EPOLLIN | EPOLLOUT, data: fds[0] }], 10); } // This test shows a data race before epoll had vector clocks added. @@ -99,7 +133,7 @@ fn test_epoll_race() { let fd = errno_result(unsafe { libc::eventfd(0, flags) }).unwrap(); // Register eventfd with the epoll instance. - epoll_ctl_add(epfd, fd, libc::EPOLLIN | libc::EPOLLET).unwrap(); + epoll_ctl_add(epfd, fd, EPOLLIN | EPOLLET_OR_ZERO).unwrap(); static mut VAL: u8 = 0; let thread1 = thread::spawn(move || { @@ -110,7 +144,7 @@ fn test_epoll_race() { }); thread::yield_now(); // epoll_wait for EPOLLIN. - check_epoll_wait::<8>(epfd, &[Ev { events: libc::EPOLLIN, data: fd }], -1); + check_epoll_wait::<8>(epfd, &[Ev { events: EPOLLIN, data: fd }], -1); // Read from the static mut variable. assert_eq!(unsafe { VAL }, 1); thread1.join().unwrap(); @@ -131,18 +165,14 @@ fn wakeup_on_new_interest() { // Block a thread on the epoll instance. let t = std::thread::spawn(move || { - check_epoll_wait::<8>( - epfd, - &[Ev { events: libc::EPOLLIN | libc::EPOLLOUT, data: fds[1] }], - -1, - ); + check_epoll_wait::<8>(epfd, &[Ev { events: EPOLLIN | EPOLLOUT, data: fds[1] }], -1); }); // Ensure the thread is blocked. std::thread::yield_now(); - // Register fd[1] with EPOLLIN|EPOLLOUT|EPOLLET|EPOLLRDHUP - epoll_ctl_add(epfd, fds[1], libc::EPOLLIN | libc::EPOLLOUT | libc::EPOLLET | libc::EPOLLRDHUP) - .unwrap(); + // Register fd[1] with EPOLLIN|EPOLLOUT|EPOLLRDHUP (and EPOLLET if we're in the + // `edge_triggered` revision). + epoll_ctl_add(epfd, fds[1], EPOLLIN | EPOLLOUT | EPOLLRDHUP | EPOLLET_OR_ZERO).unwrap(); // This should wake up the thread. t.join().unwrap(); @@ -161,12 +191,11 @@ fn multiple_events_wake_multiple_threads() { let fd2 = errno_result(unsafe { libc::dup(fd1) }).unwrap(); // Register both with epoll. - epoll_ctl_add(epfd, fd1, libc::EPOLLIN | libc::EPOLLOUT | libc::EPOLLET).unwrap(); - epoll_ctl_add(epfd, fd2, libc::EPOLLIN | libc::EPOLLOUT | libc::EPOLLET).unwrap(); + epoll_ctl_add(epfd, fd1, EPOLLIN | EPOLLOUT | EPOLLET_OR_ZERO).unwrap(); + epoll_ctl_add(epfd, fd2, EPOLLIN | EPOLLOUT | EPOLLET_OR_ZERO).unwrap(); // Consume the initial events. - let expected = - [Ev { events: libc::EPOLLOUT, data: fd1 }, Ev { events: libc::EPOLLOUT, data: fd2 }]; + let expected = [Ev { events: EPOLLOUT, data: fd1 }, Ev { events: EPOLLOUT, data: fd2 }]; check_epoll_wait::<8>(epfd, &expected, -1); // Block two threads on the epoll, both wanting to get just one event. @@ -191,6 +220,7 @@ fn multiple_events_wake_multiple_threads() { // Both threads should have been woken up so that both events can be consumed. let e1 = t1.join().unwrap(); let e2 = t2.join().unwrap(); - // Ensure that across the two threads we got both events. + + // In both modes we should get both events across the two threads. assert!(expected == [e1, e2] || expected == [e2, e1]); } diff --git a/src/tools/miri/tests/pass-dep/libc/libc-epoll-no-blocking.rs b/src/tools/miri/tests/pass-dep/libc/libc-epoll-no-blocking.rs index e8f54bf85a2d6..10fc6a5395d3b 100644 --- a/src/tools/miri/tests/pass-dep/libc/libc-epoll-no-blocking.rs +++ b/src/tools/miri/tests/pass-dep/libc/libc-epoll-no-blocking.rs @@ -1,12 +1,15 @@ //@only-target: linux android illumos - -use std::convert::TryInto; +//@revisions: edge_triggered level_triggered #[path = "../../utils/libc.rs"] mod libc_utils; use libc_utils::epoll::*; use libc_utils::*; +/// When the `edge_triggered` revision is active, this is EPOLLET, otherwise +/// it's zero which means we perform level-triggered epolls. +const EPOLLET_OR_ZERO: libc::c_int = if cfg!(edge_triggered) { EPOLLET } else { 0 }; + fn main() { test_epoll_socketpair(); test_epoll_socketpair_both_sides(); @@ -27,27 +30,13 @@ fn main() { test_ready_list_fetching_logic(); test_epoll_ctl_epfd_equal_fd(); test_epoll_ctl_notification(); + test_epoll_mixed_modes(); + test_epoll_registered_mode_switch(); test_issue_3858(); test_issue_4374(); test_issue_4374_reads(); } -#[track_caller] -fn check_epoll_wait(epfd: i32, expected_notifications: &[(u32, u64)]) { - let epoll_event = libc::epoll_event { events: 0, u64: 0 }; - let mut array: [libc::epoll_event; N] = [epoll_event; N]; - let maxsize = N; - let array_ptr = array.as_mut_ptr(); - let res = unsafe { libc::epoll_wait(epfd, array_ptr, maxsize.try_into().unwrap(), 0) }; - if res < 0 { - panic!("epoll_wait failed: {}", std::io::Error::last_os_error()); - } - let got_notifications = - unsafe { std::slice::from_raw_parts(array_ptr, res.try_into().unwrap()) }; - let got_notifications = got_notifications.iter().map(|e| (e.events, e.u64)).collect::>(); - assert_eq!(got_notifications, expected_notifications, "got wrong notifications"); -} - fn test_epoll_socketpair() { // Create an epoll instance. let epfd = errno_result(unsafe { libc::epoll_create1(0) }).unwrap(); @@ -59,14 +48,22 @@ fn test_epoll_socketpair() { // Write to fds[0] write_all(fds[0], b"abcde").unwrap(); - // Register fds[1] with EPOLLIN|EPOLLOUT|EPOLLET|EPOLLRDHUP - epoll_ctl_add(epfd, fds[1], EPOLLIN | EPOLLOUT | EPOLLET | EPOLLRDHUP).unwrap(); + // Register fds[1] with EPOLLIN|EPOLLOUT|EPOLLRDHUP (and EPOLLET if we're + // in the `edge_triggered` revision). + epoll_ctl_add(epfd, fds[1], EPOLLIN | EPOLLOUT | EPOLLRDHUP | EPOLLET_OR_ZERO).unwrap(); // Check result from epoll_wait. - check_epoll_wait_noblock::<8>(epfd, &[Ev { data: fds[1], events: EPOLLIN | EPOLLOUT }]); - - // Check that this is indeed using "ET" (edge-trigger) semantics: a second epoll should return nothing. - check_epoll_wait_noblock::<8>(epfd, &[]); + check_epoll_wait_noblock::<2>(epfd, &[Ev { data: fds[1], events: EPOLLIN | EPOLLOUT }]); + + if cfg!(edge_triggered) { + // Check that this is indeed using "ET" (edge-trigger) semantics: a second wait + // should return nothing. + check_epoll_wait_noblock::<1>(epfd, &[]); + } else { + // Check that this is indeed using "LT" (level-trigger) semantics: a second wait + // should return the same readiness. + check_epoll_wait_noblock::<2>(epfd, &[Ev { data: fds[1], events: EPOLLIN | EPOLLOUT }]); + } // Write some more to fds[0]. write_all(fds[0], b"abcde").unwrap(); @@ -74,14 +71,14 @@ fn test_epoll_socketpair() { // This did not change the readiness of fds[1], so we should get no event. // However, Linux seems to always deliver spurious events to the peer on each write, // so we match that. - check_epoll_wait_noblock::<8>(epfd, &[Ev { data: fds[1], events: EPOLLIN | EPOLLOUT }]); + check_epoll_wait_noblock::<2>(epfd, &[Ev { data: fds[1], events: EPOLLIN | EPOLLOUT }]); // Close the peer socketpair. errno_check(unsafe { libc::close(fds[0]) }); // Check result from epoll_wait. We expect to get a read, write, HUP notification from the close // since closing an FD always unblocks reads and writes on its peer. - check_epoll_wait_noblock::<8>( + check_epoll_wait_noblock::<2>( epfd, &[Ev { data: fds[1], events: EPOLLIN | EPOLLOUT | EPOLLHUP | EPOLLRDHUP }], ); @@ -98,17 +95,19 @@ fn test_epoll_ctl_mod() { let mut fds = [-1, -1]; errno_check(unsafe { libc::socketpair(libc::AF_UNIX, libc::SOCK_STREAM, 0, fds.as_mut_ptr()) }); - // Register fds[1] with EPOLLIN|EPOLLET, and data of "0". - epoll_ctl(epfd, EPOLL_CTL_ADD, fds[1], Ev { events: EPOLLIN | EPOLLET, data: 0 }).unwrap(); + // Register fds[1] with EPOLLIN (and EPOLLET if we're in the `edge_triggered` revision), and data of "0". + epoll_ctl(epfd, EPOLL_CTL_ADD, fds[1], Ev { events: EPOLLIN | EPOLLET_OR_ZERO, data: 0 }) + .unwrap(); // Check result from epoll_wait. No notification would be returned. - check_epoll_wait_noblock::<8>(epfd, &[]); + check_epoll_wait_noblock::<1>(epfd, &[]); // Use EPOLL_CTL_MOD to change to EPOLLOUT flag and data. - epoll_ctl(epfd, EPOLL_CTL_MOD, fds[1], Ev { events: EPOLLOUT | EPOLLET, data: 1 }).unwrap(); + epoll_ctl(epfd, EPOLL_CTL_MOD, fds[1], Ev { events: EPOLLOUT | EPOLLET_OR_ZERO, data: 1 }) + .unwrap(); // Check result from epoll_wait. EPOLLOUT notification and new data is expected. - check_epoll_wait_noblock::<8>(epfd, &[Ev { events: EPOLLOUT, data: 1 }]); + check_epoll_wait_noblock::<2>(epfd, &[Ev { events: EPOLLOUT, data: 1 }]); // Write to fds[1] and read from fds[0] to make the notification ready again // (relying on there always being an event when the buffer gets emptied). @@ -116,16 +115,18 @@ fn test_epoll_ctl_mod() { read_exact_array::<3>(fds[0]).unwrap(); // Now that the event is already ready, change the "data" value. - epoll_ctl(epfd, EPOLL_CTL_MOD, fds[1], Ev { events: EPOLLOUT | EPOLLET, data: 2 }).unwrap(); + epoll_ctl(epfd, EPOLL_CTL_MOD, fds[1], Ev { events: EPOLLOUT | EPOLLET_OR_ZERO, data: 2 }) + .unwrap(); // Receive event, with latest data value. - check_epoll_wait_noblock::<8>(epfd, &[Ev { events: EPOLLOUT, data: 2 }]); + check_epoll_wait_noblock::<2>(epfd, &[Ev { events: EPOLLOUT, data: 2 }]); // Do another update that changes nothing. - epoll_ctl(epfd, EPOLL_CTL_MOD, fds[1], Ev { events: EPOLLOUT | EPOLLET, data: 2 }).unwrap(); + epoll_ctl(epfd, EPOLL_CTL_MOD, fds[1], Ev { events: EPOLLOUT | EPOLLET_OR_ZERO, data: 2 }) + .unwrap(); // This re-triggers the event, even if it's the same flags as before. - check_epoll_wait_noblock::<8>(epfd, &[Ev { events: EPOLLOUT, data: 2 }]); + check_epoll_wait_noblock::<2>(epfd, &[Ev { events: EPOLLOUT, data: 2 }]); } fn test_epoll_ctl_del() { @@ -139,18 +140,18 @@ fn test_epoll_ctl_del() { // Write to fds[0] libc_utils::write_all(fds[0], b"abcde").unwrap(); - // Register fds[1] with EPOLLIN|EPOLLOUT|EPOLLET + // Register fds[1] with EPOLLIN|EPOLLOUT (and EPOLLET if we're in the `edge_triggered` revision). let mut ev = libc::epoll_event { - events: (libc::EPOLLIN | libc::EPOLLOUT | libc::EPOLLET) as u32, + events: (EPOLLIN | EPOLLOUT | EPOLLET_OR_ZERO) as u32, u64: u64::try_from(fds[1]).unwrap(), }; let res = unsafe { libc::epoll_ctl(epfd, libc::EPOLL_CTL_ADD, fds[1], &mut ev) }; assert_eq!(res, 0); // Test EPOLL_CTL_DEL. - let res = unsafe { libc::epoll_ctl(epfd, libc::EPOLL_CTL_DEL, fds[1], &mut ev) }; + let res = unsafe { libc::epoll_ctl(epfd, EPOLL_CTL_DEL, fds[1], &mut ev) }; assert_eq!(res, 0); - check_epoll_wait::<8>(epfd, &[]); + check_epoll_wait_noblock::<1>(epfd, &[]); } // This test is for one fd registered under two different epoll instance. @@ -168,15 +169,14 @@ fn test_two_epoll_instance() { // Write to the socketpair. libc_utils::write_all(fds[0], b"abcde").unwrap(); - // Register one side of the socketpair with EPOLLIN | EPOLLOUT | EPOLLET. - epoll_ctl_add(epfd1, fds[1], libc::EPOLLIN | libc::EPOLLOUT | libc::EPOLLET).unwrap(); - epoll_ctl_add(epfd2, fds[1], libc::EPOLLIN | libc::EPOLLOUT | libc::EPOLLET).unwrap(); + // Register one side of the socketpair with EPOLLIN | EPOLLOUT (and EPOLLET + // if we're in the `edge_triggered` revision). + epoll_ctl_add(epfd1, fds[1], EPOLLIN | EPOLLOUT | EPOLLET_OR_ZERO).unwrap(); + epoll_ctl_add(epfd2, fds[1], EPOLLIN | EPOLLOUT | EPOLLET_OR_ZERO).unwrap(); // Notification should be received from both instance of epoll. - let expected_event = u32::try_from(libc::EPOLLIN | libc::EPOLLOUT).unwrap(); - let expected_value = u64::try_from(fds[1]).unwrap(); - check_epoll_wait::<8>(epfd1, &[(expected_event, expected_value)]); - check_epoll_wait::<8>(epfd2, &[(expected_event, expected_value)]); + check_epoll_wait_noblock::<2>(epfd1, &[Ev { events: EPOLLIN | EPOLLOUT, data: fds[1] }]); + check_epoll_wait_noblock::<2>(epfd2, &[Ev { events: EPOLLIN | EPOLLOUT, data: fds[1] }]); } // This test is for two same file description registered under the same epoll instance through dup. @@ -194,24 +194,19 @@ fn test_two_same_fd_in_same_epoll_instance() { assert_ne!(newfd, -1); // Register both fd to the same epoll instance. - let mut ev = libc::epoll_event { - events: (libc::EPOLLIN | libc::EPOLLOUT | libc::EPOLLET).cast_unsigned(), - u64: 5u64, - }; - let res = unsafe { libc::epoll_ctl(epfd, libc::EPOLL_CTL_ADD, fds[1], &mut ev) }; - assert_eq!(res, 0); - let res = unsafe { libc::epoll_ctl(epfd, libc::EPOLL_CTL_ADD, newfd, &mut ev) }; - assert_eq!(res, 0); + epoll_ctl_add(epfd, fds[1], EPOLLIN | EPOLLOUT | EPOLLET_OR_ZERO).unwrap(); + epoll_ctl_add(epfd, newfd, EPOLLIN | EPOLLOUT | EPOLLET_OR_ZERO).unwrap(); // Write to the socketpair. libc_utils::write_all(fds[0], b"abcde").unwrap(); // Two notification should be received. - let expected_event = u32::try_from(libc::EPOLLIN | libc::EPOLLOUT).unwrap(); - let expected_value = 5u64; - check_epoll_wait::<8>( + check_epoll_wait_noblock::<3>( epfd, - &[(expected_event, expected_value), (expected_event, expected_value)], + &[ + Ev { events: EPOLLIN | EPOLLOUT, data: fds[1] }, + Ev { events: EPOLLIN | EPOLLOUT, data: newfd }, + ], ); } @@ -226,20 +221,19 @@ fn test_epoll_eventfd() { // Create an epoll instance. let epfd = errno_result(unsafe { libc::epoll_create1(0) }).unwrap(); - // Register eventfd with EPOLLIN | EPOLLOUT | EPOLLET - epoll_ctl_add(epfd, fd, libc::EPOLLIN | libc::EPOLLOUT | libc::EPOLLET).unwrap(); + // Register eventfd with EPOLLIN | EPOLLOUT (and EPOLLET if we're in the `edge_triggered` + // revision). + epoll_ctl_add(epfd, fd, EPOLLIN | EPOLLOUT | EPOLLET_OR_ZERO).unwrap(); // Check result from epoll_wait. - let expected_event = u32::try_from(libc::EPOLLIN | libc::EPOLLOUT).unwrap(); - let expected_value = u64::try_from(fd).unwrap(); - check_epoll_wait::<8>(epfd, &[(expected_event, expected_value)]); + check_epoll_wait_noblock::<2>(epfd, &[Ev { events: EPOLLIN | EPOLLOUT, data: fd }]); // Write 0 to the eventfd. libc_utils::write_all(fd, &0_u64.to_ne_bytes()).unwrap(); // This does not change the status, so we should get no event. // However, Linux performs a spurious wakeup. - check_epoll_wait::<8>(epfd, &[(expected_event, expected_value)]); + check_epoll_wait_noblock::<2>(epfd, &[Ev { events: EPOLLIN | EPOLLOUT, data: fd }]); // Read from the eventfd. libc_utils::read_exact_array::<8>(fd).unwrap(); @@ -247,15 +241,13 @@ fn test_epoll_eventfd() { // This consumes the event, so the read status is gone. However, deactivation // does not trigger an event. // Still, we see a spurious wakeup. - let expected_event = u32::try_from(libc::EPOLLOUT).unwrap(); - check_epoll_wait::<8>(epfd, &[(expected_event, expected_value)]); + check_epoll_wait_noblock::<2>(epfd, &[Ev { events: EPOLLOUT, data: fd }]); // Write the maximum possible value. libc_utils::write_all(fd, &(u64::MAX - 1).to_ne_bytes()).unwrap(); // This reactivates reads, therefore triggering an event. Writing is no longer possible. - let expected_event = u32::try_from(libc::EPOLLIN).unwrap(); - check_epoll_wait::<8>(epfd, &[(expected_event, expected_value)]); + check_epoll_wait_noblock::<2>(epfd, &[Ev { events: EPOLLIN, data: fd }]); } // When read/write happened on one side of the socketpair, only the other side will be notified. @@ -268,8 +260,8 @@ fn test_epoll_socketpair_both_sides() { errno_check(unsafe { libc::socketpair(libc::AF_UNIX, libc::SOCK_STREAM, 0, fds.as_mut_ptr()) }); // Register both fd to the same epoll instance. - epoll_ctl_add(epfd, fds[0], libc::EPOLLIN | libc::EPOLLOUT | libc::EPOLLET).unwrap(); - epoll_ctl_add(epfd, fds[1], libc::EPOLLIN | libc::EPOLLOUT | libc::EPOLLET).unwrap(); + epoll_ctl_add(epfd, fds[0], EPOLLIN | EPOLLOUT | EPOLLET_OR_ZERO).unwrap(); + epoll_ctl_add(epfd, fds[1], EPOLLIN | EPOLLOUT | EPOLLET_OR_ZERO).unwrap(); // Write to fds[1]. // (We do the write after the register here, unlike in `test_epoll_socketpair`, to ensure @@ -277,23 +269,28 @@ fn test_epoll_socketpair_both_sides() { libc_utils::write_all(fds[1], b"abcde").unwrap(); // Two notification should be received. - let expected_event0 = u32::try_from(libc::EPOLLIN | libc::EPOLLOUT).unwrap(); - let expected_value0 = fds[0] as u64; - let expected_event1 = u32::try_from(libc::EPOLLOUT).unwrap(); - let expected_value1 = fds[1] as u64; - check_epoll_wait::<8>( + check_epoll_wait_noblock::<3>( epfd, - &[(expected_event0, expected_value0), (expected_event1, expected_value1)], + &[Ev { events: EPOLLIN | EPOLLOUT, data: fds[0] }, Ev { events: EPOLLOUT, data: fds[1] }], ); // Read from fds[0]. let buf = libc_utils::read_exact_array::<5>(fds[0]).unwrap(); assert_eq!(buf, *b"abcde"); - // The state of fds[1] does not change (was writable, is writable). - // However, we force a spurious wakeup as the read buffer just got emptied. - // fds[0] lost its readability, but becoming less active is not considered an "edge". - check_epoll_wait::<8>(epfd, &[(expected_event1, expected_value1)]); + if cfg!(edge_triggered) { + // The state of fds[1] does not change (was writable, is writable). + // However, we force a spurious wakeup as the read buffer just got emptied. + // fds[0] lost its readability, but becoming less active is not considered an "edge". + check_epoll_wait_noblock::<2>(epfd, &[Ev { events: EPOLLOUT, data: fds[1] }]) + } else { + // With level-triggered epoll, only the readable readiness for fds[0] should + // no longer be reported. The rest stays the same. + check_epoll_wait_noblock::<3>( + epfd, + &[Ev { events: EPOLLOUT, data: fds[0] }, Ev { events: EPOLLOUT, data: fds[1] }], + ); + } } // When file description is fully closed, epoll_wait should not provide any notification for @@ -306,8 +303,8 @@ fn test_closed_fd() { let flags = libc::EFD_NONBLOCK | libc::EFD_CLOEXEC; let fd = errno_result(unsafe { libc::eventfd(0, flags) }).unwrap(); - // Register eventfd with EPOLLIN | EPOLLOUT | EPOLLET - epoll_ctl_add(epfd, fd, libc::EPOLLIN | libc::EPOLLOUT | libc::EPOLLET).unwrap(); + // Register eventfd with EPOLLIN | EPOLLOUT (and EPOLLET if we're in the `edge_triggered` revision). + epoll_ctl_add(epfd, fd, EPOLLIN | EPOLLOUT | EPOLLET_OR_ZERO).unwrap(); // Write to the eventfd instance. libc_utils::write_all(fd, &1_u64.to_ne_bytes()).unwrap(); @@ -316,7 +313,7 @@ fn test_closed_fd() { errno_check(unsafe { libc::close(fd) }); // No notification should be provided because the file description is closed. - check_epoll_wait::<8>(epfd, &[]); + check_epoll_wait_noblock::<1>(epfd, &[]); } // When a certain file descriptor registered with epoll is closed, but the underlying file description @@ -336,16 +333,14 @@ fn test_not_fully_closed_fd() { // Dup the fd. let newfd = errno_result(unsafe { libc::dup(fd) }).unwrap(); - // Register eventfd with EPOLLIN | EPOLLOUT | EPOLLET - epoll_ctl_add(epfd, fd, libc::EPOLLIN | libc::EPOLLOUT | libc::EPOLLET).unwrap(); + // Register eventfd with EPOLLIN | EPOLLOUT (and EPOLLET if we're in the `edge_triggered` revision). + epoll_ctl_add(epfd, fd, EPOLLIN | EPOLLOUT | EPOLLET_OR_ZERO).unwrap(); // Close the original fd that being used to register with epoll. errno_check(unsafe { libc::close(fd) }); // Notification should still be provided because the file description is not closed. - let expected_event = u32::try_from(libc::EPOLLOUT).unwrap(); - let expected_value = fd as u64; - check_epoll_wait::<1>(epfd, &[(expected_event, expected_value)]); + check_epoll_wait_noblock::<2>(epfd, &[Ev { events: EPOLLOUT, data: fd }]); // Write to the eventfd instance to produce notification. libc_utils::write_all(newfd, &1_u64.to_ne_bytes()).unwrap(); @@ -354,7 +349,7 @@ fn test_not_fully_closed_fd() { errno_check(unsafe { libc::close(newfd) }); // No notification should be provided. - check_epoll_wait::<1>(epfd, &[]); + check_epoll_wait_noblock::<1>(epfd, &[]); } // Each time a notification is provided, it should reflect the file description's readiness @@ -370,13 +365,8 @@ fn test_event_overwrite() { // Create an epoll instance. let epfd = errno_result(unsafe { libc::epoll_create1(0) }).unwrap(); - // Register eventfd with EPOLLIN | EPOLLOUT | EPOLLET - let mut ev = libc::epoll_event { - events: (libc::EPOLLIN | libc::EPOLLOUT | libc::EPOLLET).cast_unsigned(), - u64: u64::try_from(fd).unwrap(), - }; - let res = unsafe { libc::epoll_ctl(epfd, libc::EPOLL_CTL_ADD, fd, &mut ev) }; - assert_eq!(res, 0); + // Register eventfd with EPOLLIN | EPOLLOUT (and EPOLLET if we're in the `edge_triggered` revision). + epoll_ctl_add(epfd, fd, EPOLLIN | EPOLLOUT | EPOLLET_OR_ZERO).unwrap(); // Read from the eventfd instance. let mut buf: [u8; 8] = [0; 8]; @@ -384,9 +374,7 @@ fn test_event_overwrite() { assert_eq!(res, 8); // Check result from epoll_wait. - let expected_event = u32::try_from(libc::EPOLLOUT).unwrap(); - let expected_value = u64::try_from(fd).unwrap(); - check_epoll_wait::<8>(epfd, &[(expected_event, expected_value)]); + check_epoll_wait_noblock::<2>(epfd, &[Ev { events: EPOLLOUT, data: fd }]); } // An epoll notification will be provided for every succesful read in a socketpair. @@ -400,53 +388,58 @@ fn test_socketpair_read() { errno_check(unsafe { libc::socketpair(libc::AF_UNIX, libc::SOCK_STREAM, 0, fds.as_mut_ptr()) }); // Register both fd to the same epoll instance. - let mut ev = libc::epoll_event { - events: (libc::EPOLLIN | libc::EPOLLOUT | libc::EPOLLET).cast_unsigned(), - u64: fds[0] as u64, - }; - let res = unsafe { libc::epoll_ctl(epfd, libc::EPOLL_CTL_ADD, fds[0], &mut ev) }; - assert_eq!(res, 0); - let mut ev = libc::epoll_event { - events: (libc::EPOLLIN | libc::EPOLLOUT | libc::EPOLLET).cast_unsigned(), - u64: fds[1] as u64, - }; - let res = unsafe { libc::epoll_ctl(epfd, libc::EPOLL_CTL_ADD, fds[1], &mut ev) }; - assert_eq!(res, 0); + epoll_ctl_add(epfd, fds[0], EPOLLIN | EPOLLOUT | EPOLLET_OR_ZERO).unwrap(); + epoll_ctl_add(epfd, fds[1], EPOLLIN | EPOLLOUT | EPOLLET_OR_ZERO).unwrap(); // Write a bunch of data bytes to fds[1]. let data = [42u8; 1024]; libc_utils::write_all(fds[1], &data).unwrap(); // Two notification should be received. - let expected_event0 = u32::try_from(libc::EPOLLIN | libc::EPOLLOUT).unwrap(); - let expected_value0 = fds[0] as u64; - let expected_event1 = u32::try_from(libc::EPOLLOUT).unwrap(); - let expected_value1 = fds[1] as u64; - check_epoll_wait::<8>( + check_epoll_wait_noblock::<3>( epfd, - &[(expected_event0, expected_value0), (expected_event1, expected_value1)], + &[Ev { events: EPOLLIN | EPOLLOUT, data: fds[0] }, Ev { events: EPOLLOUT, data: fds[1] }], ); // Read some of the data from fds[0]. let mut buf = [0; 512]; libc_utils::read_exact(fds[0], &mut buf).unwrap(); - - // fds[1] did not change, it is still writable, so we get no event. - let expected_event = u32::try_from(libc::EPOLLOUT).unwrap(); - let expected_value = fds[1] as u64; - check_epoll_wait::<8>(epfd, &[]); + if cfg!(edge_triggered) { + // fds[1] did not change, it is still writable, so we get no event + // in edge-triggered mode. + check_epoll_wait_noblock::<1>(epfd, &[]); + } else { + // In level-triggered mode we expect the same events as before because + // we didn't read everything in the buffer. + check_epoll_wait_noblock::<3>( + epfd, + &[ + Ev { events: EPOLLIN | EPOLLOUT, data: fds[0] }, + Ev { events: EPOLLOUT, data: fds[1] }, + ], + ); + } // Read until the buffer is empty. let rest = data.len() - buf.len(); libc_utils::read_exact(fds[0], &mut buf[..rest]).unwrap(); - // Now we get a notification that fds[1] can be written. This is spurious since it - // could already be written before, but Linux seems to always emit a notification for - // the writer when a read empties the buffer. - check_epoll_wait::<8>(epfd, &[(expected_event, expected_value)]); + if cfg!(edge_triggered) { + // Now we get a notification that fds[1] can be written. This is spurious since it + // could already be written before, but Linux seems to always emit a notification for + // the writer when a read empties the buffer. + check_epoll_wait_noblock::<2>(epfd, &[Ev { events: EPOLLOUT, data: fds[1] }]); + } else { + // In level-triggered mode we expect the same events as before just without + // the readable readiness of fds[0] because we now read everything. + check_epoll_wait_noblock::<3>( + epfd, + &[Ev { events: EPOLLOUT, data: fds[0] }, Ev { events: EPOLLOUT, data: fds[1] }], + ); + } } -// This is to test whether flag that we don't register won't trigger notification. +// This is to test whether a flag that we don't register won't trigger notification. fn test_no_notification_for_unregister_flag() { // Create an epoll instance. let epfd = errno_result(unsafe { libc::epoll_create1(0) }).unwrap(); @@ -455,22 +448,15 @@ fn test_no_notification_for_unregister_flag() { let mut fds = [-1, -1]; errno_check(unsafe { libc::socketpair(libc::AF_UNIX, libc::SOCK_STREAM, 0, fds.as_mut_ptr()) }); - // Register fds[0] with EPOLLOUT|EPOLLET. - let mut ev = libc::epoll_event { - events: (libc::EPOLLOUT | libc::EPOLLET).cast_unsigned(), - u64: u64::try_from(fds[0]).unwrap(), - }; - let res = unsafe { libc::epoll_ctl(epfd, libc::EPOLL_CTL_ADD, fds[0], &mut ev) }; - assert_eq!(res, 0); + // Register fds[0] with EPOLLOUT (and EPOLLET when we're in the `edge_triggered` revision). + epoll_ctl_add(epfd, fds[0], EPOLLOUT | EPOLLET_OR_ZERO).unwrap(); // Write to fds[1]. libc_utils::write_all(fds[1], b"abcde").unwrap(); // Check result from epoll_wait. Since we didn't register EPOLLIN flag, the notification won't // contain EPOLLIN even though fds[0] is now readable. - let expected_event = u32::try_from(libc::EPOLLOUT).unwrap(); - let expected_value = u64::try_from(fds[0]).unwrap(); - check_epoll_wait::<8>(epfd, &[(expected_event, expected_value)]); + check_epoll_wait_noblock::<2>(epfd, &[Ev { events: EPOLLOUT, data: fds[0] }]); } fn test_epoll_wait_maxevent_zero() { @@ -500,17 +486,15 @@ fn test_socketpair_epollerr() { // EPOLLERR will be triggered if we close peer fd that still has data in its read buffer. errno_check(unsafe { libc::close(fds[1]) }); - // Register fds[1] with EPOLLIN|EPOLLOUT|EPOLLET|EPOLLRDHUP - epoll_ctl_add(epfd, fds[0], libc::EPOLLIN | libc::EPOLLOUT | libc::EPOLLET | libc::EPOLLRDHUP) - .unwrap(); + // Register fds[1] with EPOLLIN|EPOLLOUT|EPOLLRDHUP (and EPOLLET when we're in the + // `edge_triggered` revision). + epoll_ctl_add(epfd, fds[0], EPOLLIN | EPOLLOUT | EPOLLRDHUP | EPOLLET_OR_ZERO).unwrap(); // Check result from epoll_wait. - let expected_event = u32::try_from( - libc::EPOLLIN | libc::EPOLLOUT | libc::EPOLLHUP | libc::EPOLLRDHUP | libc::EPOLLERR, - ) - .unwrap(); - let expected_value = u64::try_from(fds[0]).unwrap(); - check_epoll_wait::<8>(epfd, &[(expected_event, expected_value)]); + check_epoll_wait_noblock::<2>( + epfd, + &[Ev { events: EPOLLIN | EPOLLOUT | EPOLLHUP | EPOLLRDHUP | EPOLLERR, data: fds[0] }], + ); } // This is a test for https://github.com/rust-lang/miri/issues/3812, @@ -524,18 +508,24 @@ fn test_epoll_lost_events() { errno_check(unsafe { libc::socketpair(libc::AF_UNIX, libc::SOCK_STREAM, 0, fds.as_mut_ptr()) }); // Register both fd to the same epoll instance. - epoll_ctl_add(epfd, fds[0], libc::EPOLLIN | libc::EPOLLOUT | libc::EPOLLET).unwrap(); - epoll_ctl_add(epfd, fds[1], libc::EPOLLIN | libc::EPOLLOUT | libc::EPOLLET).unwrap(); + epoll_ctl_add(epfd, fds[0], EPOLLIN | EPOLLOUT | EPOLLET_OR_ZERO).unwrap(); + epoll_ctl_add(epfd, fds[1], EPOLLIN | EPOLLOUT | EPOLLET_OR_ZERO).unwrap(); // Two notification should be received. But we only provide buffer for one event. - let expected_event0 = u32::try_from(libc::EPOLLOUT).unwrap(); - let expected_value0 = fds[0] as u64; - check_epoll_wait::<1>(epfd, &[(expected_event0, expected_value0)]); - - // Previous event should be returned for the second epoll_wait. - let expected_event1 = u32::try_from(libc::EPOLLOUT).unwrap(); - let expected_value1 = fds[1] as u64; - check_epoll_wait::<1>(epfd, &[(expected_event1, expected_value1)]); + check_epoll_wait_noblock::<1>(epfd, &[Ev { events: EPOLLOUT, data: fds[0] }]); + + if cfg!(edge_triggered) { + // Previous event should be returned for the second epoll_wait but because we're + // edge-triggered the first event should no longer be returned. + check_epoll_wait_noblock::<2>(epfd, &[Ev { events: EPOLLOUT, data: fds[1] }]); + } else { + // Both events should be returned in level-triggered mode when + // we provide a big enough buffer. + check_epoll_wait_noblock::<3>( + epfd, + &[Ev { events: EPOLLOUT, data: fds[1] }, Ev { events: EPOLLOUT, data: fds[0] }], + ); + } } // This is testing if closing an fd that is already in ready list will cause an empty entry in @@ -551,16 +541,14 @@ fn test_ready_list_fetching_logic() { let fd1 = errno_result(unsafe { libc::eventfd(0, flags) }).unwrap(); // Register both fd to the same epoll instance. At this point, both of them are on the ready list. - epoll_ctl_add(epfd, fd0, libc::EPOLLIN | libc::EPOLLOUT | libc::EPOLLET).unwrap(); - epoll_ctl_add(epfd, fd1, libc::EPOLLIN | libc::EPOLLOUT | libc::EPOLLET).unwrap(); + epoll_ctl_add(epfd, fd0, EPOLLIN | EPOLLOUT | EPOLLET_OR_ZERO).unwrap(); + epoll_ctl_add(epfd, fd1, EPOLLIN | EPOLLOUT | EPOLLET_OR_ZERO).unwrap(); // Close fd0 so the first entry in the ready list will be empty. errno_check(unsafe { libc::close(fd0) }); // Notification for fd1 should be returned. - let expected_event1 = u32::try_from(libc::EPOLLOUT).unwrap(); - let expected_value1 = fd1 as u64; - check_epoll_wait::<1>(epfd, &[(expected_event1, expected_value1)]); + check_epoll_wait_noblock::<2>(epfd, &[Ev { events: EPOLLOUT, data: fd1 }]); } // In epoll_ctl, if the value of epfd equals to fd, EFAULT should be returned. @@ -570,7 +558,7 @@ fn test_epoll_ctl_epfd_equal_fd() { let epfd = errno_result(unsafe { libc::epoll_create1(0) }).unwrap(); let array_ptr = std::ptr::without_provenance_mut::(0x100); - let res = unsafe { libc::epoll_ctl(epfd, libc::EPOLL_CTL_ADD, epfd, array_ptr) }; + let res = unsafe { libc::epoll_ctl(epfd, EPOLL_CTL_ADD, epfd, array_ptr) }; let e = std::io::Error::last_os_error(); assert_eq!(e.raw_os_error(), Some(libc::EFAULT)); assert_eq!(res, -1); @@ -578,7 +566,7 @@ fn test_epoll_ctl_epfd_equal_fd() { // We previously used check_and_update_readiness the moment a file description is registered in an // epoll instance. But this has an unfortunate side effect of returning notification to another -// epfd that shouldn't receive notification. +// epfd that shouldn't receive a notification in edge-triggered mode. fn test_epoll_ctl_notification() { // Create an epoll instance. let epfd0 = unsafe { libc::epoll_create1(0) }; @@ -589,24 +577,87 @@ fn test_epoll_ctl_notification() { errno_check(unsafe { libc::socketpair(libc::AF_UNIX, libc::SOCK_STREAM, 0, fds.as_mut_ptr()) }); // Register one side of the socketpair with epoll. - epoll_ctl_add(epfd0, fds[0], libc::EPOLLIN | libc::EPOLLOUT | libc::EPOLLET).unwrap(); + epoll_ctl_add(epfd0, fds[0], EPOLLIN | EPOLLOUT | EPOLLET_OR_ZERO).unwrap(); // epoll_wait to clear notification for epfd0. - let expected_event = u32::try_from(libc::EPOLLOUT).unwrap(); - let expected_value = fds[0] as u64; - check_epoll_wait::<1>(epfd0, &[(expected_event, expected_value)]); + check_epoll_wait_noblock::<2>(epfd0, &[Ev { events: EPOLLOUT, data: fds[0] }]); // Create another epoll instance. let epfd1 = unsafe { libc::epoll_create1(0) }; assert_ne!(epfd1, -1); // Register the same file description for epfd1. - epoll_ctl_add(epfd1, fds[0], libc::EPOLLIN | libc::EPOLLOUT | libc::EPOLLET).unwrap(); - check_epoll_wait::<1>(epfd1, &[(expected_event, expected_value)]); + epoll_ctl_add(epfd1, fds[0], EPOLLIN | EPOLLOUT | EPOLLET_OR_ZERO).unwrap(); + check_epoll_wait_noblock::<2>(epfd1, &[Ev { events: EPOLLOUT, data: fds[0] }]); + + if cfg!(edge_triggered) { + // Previously this epoll_wait will receive a notification, but we shouldn't return notification + // for this epfd, because there is no I/O event between the two epoll_wait. + check_epoll_wait_noblock::<1>(epfd0, &[]); + } else { + // We should still get the same events in level-triggered mode. + check_epoll_wait_noblock::<2>(epfd0, &[Ev { events: EPOLLOUT, data: fds[0] }]); + } +} + +/// Test storing a level-triggered and an edge-triggered file descriptor +/// in the same epoll instance and calling `epoll_wait` multiple times. +fn test_epoll_mixed_modes() { + // Create an epoll instance. + let epfd = errno_result(unsafe { libc::epoll_create1(0) }).unwrap(); + + // Create a socketpair instance. + let mut fds = [-1, -1]; + errno_check(unsafe { libc::socketpair(libc::AF_UNIX, libc::SOCK_STREAM, 0, fds.as_mut_ptr()) }); + + // Register both fd to the same epoll instance. + // `fds[0]` is added in edge-triggered mode whilst `fds[1]` is added in level-triggered mode. + epoll_ctl_add(epfd, fds[0], EPOLLIN | EPOLLOUT | EPOLLET).unwrap(); + epoll_ctl_add(epfd, fds[1], EPOLLIN | EPOLLOUT | 0).unwrap(); + + // Write to `fds[1]`. + libc_utils::write_all(fds[1], b"abcde").unwrap(); + + // Two events should be received. + check_epoll_wait_noblock::<3>( + epfd, + &[Ev { events: EPOLLIN | EPOLLOUT, data: fds[0] }, Ev { events: EPOLLOUT, data: fds[1] }], + ); + + // If we call epoll_wait again immediately, only the level-triggered interests should be received again. + check_epoll_wait_noblock::<2>(epfd, &[Ev { events: EPOLLOUT, data: fds[1] }]); +} + +/// Test first registering a file descriptor in edge-triggered mode, +/// then consuming it's readiness and then changing it to level-triggered +/// mode. +fn test_epoll_registered_mode_switch() { + // Create an eventfd instance. + let flags = libc::EFD_NONBLOCK | libc::EFD_CLOEXEC; + let fd = errno_result(unsafe { libc::eventfd(0, flags) }).unwrap(); + + // Write 1 to the eventfd instance. + libc_utils::write_all(fd, &1_u64.to_ne_bytes()).unwrap(); + + // Create an epoll instance. + let epfd = errno_result(unsafe { libc::epoll_create1(0) }).unwrap(); + + // Register eventfd with EPOLLIN | EPOLLOUT | EPOLLET. + epoll_ctl_add(epfd, fd, EPOLLIN | EPOLLOUT | EPOLLET).unwrap(); + + // Check result from `epoll_wait`. + check_epoll_wait_noblock::<2>(epfd, &[Ev { events: EPOLLIN | EPOLLOUT, data: fd }]); + + // Because `fd` is registered in edge-triggered mode, the next `epoll_wait` shouldn't + // return any events. + check_epoll_wait_noblock::<1>(epfd, &[]); - // Previously this epoll_wait will receive a notification, but we shouldn't return notification - // for this epfd, because there is no I/O event between the two epoll_wait. - check_epoll_wait::<1>(epfd0, &[]); + // Update the registration for `fd` to switch to level-triggered mode. + epoll_ctl(epfd, EPOLL_CTL_MOD, fd, Ev { events: EPOLLIN | EPOLLOUT, data: fd }).unwrap(); + + // Because `fd` is now registered in level-triggered mode, we should see + // the same events as from the first `epoll_wait`. + check_epoll_wait_noblock::<2>(epfd, &[Ev { events: EPOLLIN | EPOLLOUT, data: fd }]); } // Test for ICE caused by weak epoll interest upgrade succeed, but the attempt to retrieve @@ -623,13 +674,8 @@ fn test_issue_3858() { // Create an epoll instance. let epfd = errno_result(unsafe { libc::epoll_create1(0) }).unwrap(); - // Register eventfd with EPOLLIN | EPOLLET. - let mut ev = libc::epoll_event { - events: (libc::EPOLLIN | libc::EPOLLET).cast_unsigned(), - u64: u64::try_from(fd).unwrap(), - }; - let res = unsafe { libc::epoll_ctl(epfd, libc::EPOLL_CTL_ADD, fd, &mut ev) }; - assert_eq!(res, 0); + // Register eventfd with EPOLLIN (and EPOLLET if we're in the `edge_triggered` revision). + epoll_ctl_add(epfd, fd, EPOLLIN | EPOLLET_OR_ZERO).unwrap(); // Dup the epoll instance. let newfd = unsafe { libc::dup(epfd) }; @@ -655,7 +701,7 @@ fn test_issue_4374() { assert_eq!(unsafe { libc::fcntl(fds[1], libc::F_SETFL, libc::O_NONBLOCK) }, 0); // Register fds[0] with epoll while it is writable (but not readable). - epoll_ctl_add(epfd0, fds[0], libc::EPOLLIN | libc::EPOLLOUT | libc::EPOLLET).unwrap(); + epoll_ctl_add(epfd0, fds[0], EPOLLIN | EPOLLOUT | EPOLLET_OR_ZERO).unwrap(); // Fill up fds[0] so that it is not writable any more. let zeros = [0u8; 512]; @@ -668,7 +714,7 @@ fn test_issue_4374() { } // This should have canceled the previous readiness, so now we get nothing. - check_epoll_wait::<1>(epfd0, &[]); + check_epoll_wait_noblock::<1>(epfd0, &[]); } /// Same as above, but for becoming un-readable. @@ -687,13 +733,11 @@ fn test_issue_4374_reads() { libc_utils::write_all(fds[1], b"abcde").unwrap(); // Register fds[0] with epoll while it is readable. - epoll_ctl_add(epfd0, fds[0], libc::EPOLLIN | libc::EPOLLOUT | libc::EPOLLET).unwrap(); + epoll_ctl_add(epfd0, fds[0], EPOLLIN | EPOLLOUT | EPOLLET_OR_ZERO).unwrap(); // Read fds[0] so it is no longer readable. libc_utils::read_exact_array::<5>(fds[0]).unwrap(); // We should now still see a notification, but only about it being writable. - let expected_event = u32::try_from(libc::EPOLLOUT).unwrap(); - let expected_value = fds[0] as u64; - check_epoll_wait::<1>(epfd0, &[(expected_event, expected_value)]); + check_epoll_wait_noblock::<2>(epfd0, &[Ev { events: EPOLLOUT, data: fds[0] }]); } From 366be2e5b0987498ec80c563076debaa6e68678e Mon Sep 17 00:00:00 2001 From: The Miri Cronjob Bot Date: Sat, 30 May 2026 06:03:01 +0000 Subject: [PATCH 16/46] Prepare for merging from rust-lang/rust This updates the rust-version file to 6368fd52cb9f230dfb156097625993e7a8891800. --- src/tools/miri/rust-version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tools/miri/rust-version b/src/tools/miri/rust-version index d0dde42d262f9..47c935aa9f48a 100644 --- a/src/tools/miri/rust-version +++ b/src/tools/miri/rust-version @@ -1 +1 @@ -8f02e856be945d5d879b2ff9b7d19dd6bfb1c0e5 +6368fd52cb9f230dfb156097625993e7a8891800 From b956f2135ca1c266f8b5a39e3b5250b9e0ce35a7 Mon Sep 17 00:00:00 2001 From: The Miri Cronjob Bot Date: Sat, 30 May 2026 06:12:20 +0000 Subject: [PATCH 17/46] fmt --- src/tools/miri/src/shims/unix/foreign_items.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/tools/miri/src/shims/unix/foreign_items.rs b/src/tools/miri/src/shims/unix/foreign_items.rs index 1b387d6fea5fd..27014f0015772 100644 --- a/src/tools/miri/src/shims/unix/foreign_items.rs +++ b/src/tools/miri/src/shims/unix/foreign_items.rs @@ -342,7 +342,10 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { } "flock" => { // Currently this function does not exist on all Unixes, e.g. on Solaris. - this.check_target_os(&[Os::Linux, Os::Android, Os::FreeBsd, Os::MacOs, Os::Illumos], link_name)?; + this.check_target_os( + &[Os::Linux, Os::Android, Os::FreeBsd, Os::MacOs, Os::Illumos], + link_name, + )?; let [fd, op] = this.check_shim_sig( shim_sig!(extern "C" fn(i32, i32) -> i32), From 22cc43ee512b297d8d112363125745119f93ea59 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Sun, 31 May 2026 11:03:59 +0200 Subject: [PATCH 18/46] reduce sleep times in various tests 200ms is an eternity and slows down test execution --- .../data_race/dangling_thread_async_race.rs | 2 +- .../fail/data_race/dangling_thread_race.rs | 2 +- .../fail/data_race/dealloc_read_race_stack.rs | 2 +- .../data_race/dealloc_write_race_stack.rs | 2 +- .../fail/data_race/read_write_race_stack.rs | 2 +- .../tests/fail/data_race/release_seq_race.rs | 2 +- .../fail/data_race/write_write_race_stack.rs | 2 +- .../tests/pass-dep/concurrency/apple-futex.rs | 24 +++++------ .../tests/pass-dep/concurrency/linux-futex.rs | 40 +++++++++---------- .../pass/concurrency/thread_park_isolated.rs | 6 +-- 10 files changed, 40 insertions(+), 44 deletions(-) diff --git a/src/tools/miri/tests/fail/data_race/dangling_thread_async_race.rs b/src/tools/miri/tests/fail/data_race/dangling_thread_async_race.rs index fbb2c01e5a9aa..4e0268fbd8303 100644 --- a/src/tools/miri/tests/fail/data_race/dangling_thread_async_race.rs +++ b/src/tools/miri/tests/fail/data_race/dangling_thread_async_race.rs @@ -25,7 +25,7 @@ fn main() { // Detach the thread and sleep until it terminates mem::drop(join); - sleep(Duration::from_millis(200)); + sleep(Duration::from_millis(100)); // Spawn and immediately join a thread // to execute the join code-path diff --git a/src/tools/miri/tests/fail/data_race/dangling_thread_race.rs b/src/tools/miri/tests/fail/data_race/dangling_thread_race.rs index 7431bc589ff95..1c0a1cdc8fcf8 100644 --- a/src/tools/miri/tests/fail/data_race/dangling_thread_race.rs +++ b/src/tools/miri/tests/fail/data_race/dangling_thread_race.rs @@ -25,7 +25,7 @@ fn main() { // Detach the thread and sleep until it terminates mem::drop(join); - sleep(Duration::from_millis(200)); + sleep(Duration::from_millis(100)); // Spawn and immediately join a thread // to execute the join code-path diff --git a/src/tools/miri/tests/fail/data_race/dealloc_read_race_stack.rs b/src/tools/miri/tests/fail/data_race/dealloc_read_race_stack.rs index 76c26da057820..688824c9c9ac1 100644 --- a/src/tools/miri/tests/fail/data_race/dealloc_read_race_stack.rs +++ b/src/tools/miri/tests/fail/data_race/dealloc_read_race_stack.rs @@ -34,7 +34,7 @@ fn main() { pointer.store(&mut stack_var as *mut _, Ordering::Release); - sleep(Duration::from_millis(200)); + sleep(Duration::from_millis(100)); // Now `stack_var` gets deallocated. } //~ ERROR: Data race detected between (1) non-atomic read on thread `unnamed-2` and (2) deallocation on thread `unnamed-1` diff --git a/src/tools/miri/tests/fail/data_race/dealloc_write_race_stack.rs b/src/tools/miri/tests/fail/data_race/dealloc_write_race_stack.rs index bdc25100abaf1..4500e5e744a4d 100644 --- a/src/tools/miri/tests/fail/data_race/dealloc_write_race_stack.rs +++ b/src/tools/miri/tests/fail/data_race/dealloc_write_race_stack.rs @@ -34,7 +34,7 @@ fn main() { pointer.store(&mut stack_var as *mut _, Ordering::Release); - sleep(Duration::from_millis(200)); + sleep(Duration::from_millis(100)); // Now `stack_var` gets deallocated. } //~ ERROR: Data race detected between (1) non-atomic write on thread `unnamed-2` and (2) deallocation on thread `unnamed-1` diff --git a/src/tools/miri/tests/fail/data_race/read_write_race_stack.rs b/src/tools/miri/tests/fail/data_race/read_write_race_stack.rs index 4555a82df6c97..035952bbf600a 100644 --- a/src/tools/miri/tests/fail/data_race/read_write_race_stack.rs +++ b/src/tools/miri/tests/fail/data_race/read_write_race_stack.rs @@ -39,7 +39,7 @@ fn main() { pointer.store(&mut stack_var as *mut _, Ordering::Release); - sleep(Duration::from_millis(200)); + sleep(Duration::from_millis(100)); stack_var //~ ERROR: Data race detected between (1) non-atomic write on thread `unnamed-2` and (2) non-atomic read on thread `unnamed-1` }); diff --git a/src/tools/miri/tests/fail/data_race/release_seq_race.rs b/src/tools/miri/tests/fail/data_race/release_seq_race.rs index 5016617e5d7fa..f5c4ed3cc2b2d 100644 --- a/src/tools/miri/tests/fail/data_race/release_seq_race.rs +++ b/src/tools/miri/tests/fail/data_race/release_seq_race.rs @@ -31,7 +31,7 @@ fn main() { let c = c; // avoid field capturing *c.0 = 1; SYNC.store(1, Ordering::Release); - sleep(Duration::from_millis(200)); + sleep(Duration::from_millis(100)); SYNC.store(3, Ordering::Relaxed); }); diff --git a/src/tools/miri/tests/fail/data_race/write_write_race_stack.rs b/src/tools/miri/tests/fail/data_race/write_write_race_stack.rs index b92d17bf8bcd2..843cf1f6c0697 100644 --- a/src/tools/miri/tests/fail/data_race/write_write_race_stack.rs +++ b/src/tools/miri/tests/fail/data_race/write_write_race_stack.rs @@ -39,7 +39,7 @@ fn main() { pointer.store(&mut stack_var as *mut _, Ordering::Release); - sleep(Duration::from_millis(200)); + sleep(Duration::from_millis(100)); stack_var = 1usize; //~ ERROR: Data race detected between (1) non-atomic write on thread `unnamed-2` and (2) non-atomic write on thread `unnamed-1` diff --git a/src/tools/miri/tests/pass-dep/concurrency/apple-futex.rs b/src/tools/miri/tests/pass-dep/concurrency/apple-futex.rs index a28f08c3bb1a0..0205159c4fd07 100644 --- a/src/tools/miri/tests/pass-dep/concurrency/apple-futex.rs +++ b/src/tools/miri/tests/pass-dep/concurrency/apple-futex.rs @@ -62,7 +62,7 @@ fn wait_timeout() { let futex: i32 = 123; - // Wait for 200ms, with nobody waking us up early. + // Wait for 100ms, with nobody waking us up early. unsafe { assert_eq!( libc::os_sync_wait_on_address_with_timeout( @@ -71,14 +71,14 @@ fn wait_timeout() { size_of::(), libc::OS_SYNC_WAIT_ON_ADDRESS_NONE, libc::OS_CLOCK_MACH_ABSOLUTE_TIME, - 200_000_000, + 100_000_000, ), -1, ); assert_eq!(io::Error::last_os_error().raw_os_error().unwrap(), libc::ETIMEDOUT); } - assert!((200..1000).contains(&start.elapsed().as_millis())); + assert!((100..1000).contains(&start.elapsed().as_millis())); } fn wait_absolute_timeout() { @@ -88,15 +88,15 @@ fn wait_absolute_timeout() { #[allow(deprecated)] let mut deadline = unsafe { libc::mach_absolute_time() }; - // Add 200ms. + // Add 100ms. // What we should be doing here is call `mach_timebase_info` to determine the // unit used for `deadline`, but we know what Miri returns for that function: // the unit is nanoseconds. - deadline += 200_000_000; + deadline += 100_000_000; let futex: i32 = 123; - // Wait for 200ms from now, with nobody waking us up early. + // Wait for 100ms from now, with nobody waking us up early. unsafe { assert_eq!( libc::os_sync_wait_on_address_with_deadline( @@ -112,16 +112,14 @@ fn wait_absolute_timeout() { assert_eq!(io::Error::last_os_error().raw_os_error().unwrap(), libc::ETIMEDOUT); } - assert!((200..1000).contains(&start.elapsed().as_millis())); + assert!((100..1000).contains(&start.elapsed().as_millis())); } fn wait_wake() { - let start = Instant::now(); - static mut FUTEX: i32 = 0; let t = thread::spawn(move || { - thread::sleep(Duration::from_millis(200)); + thread::sleep(Duration::from_millis(100)); unsafe { assert_eq!( libc::os_sync_wake_by_address_any( @@ -134,6 +132,8 @@ fn wait_wake() { } }); + let start = Instant::now(); + unsafe { assert_eq!( libc::os_sync_wait_on_address( @@ -146,9 +146,7 @@ fn wait_wake() { ); } - // When running this in stress-gc mode, things can take quite long. - // So the timeout is 3000 ms. - assert!((200..3000).contains(&start.elapsed().as_millis())); + assert!((100..1000).contains(&start.elapsed().as_millis())); t.join().unwrap(); } diff --git a/src/tools/miri/tests/pass-dep/concurrency/linux-futex.rs b/src/tools/miri/tests/pass-dep/concurrency/linux-futex.rs index 19d86f09595d5..5b7a2b7039d80 100644 --- a/src/tools/miri/tests/pass-dep/concurrency/linux-futex.rs +++ b/src/tools/miri/tests/pass-dep/concurrency/linux-futex.rs @@ -78,7 +78,7 @@ fn wait_timeout() { let futex: i32 = 123; - // Wait for 200ms, with nobody waking us up early. + // Wait for 100ms, with nobody waking us up early. unsafe { assert_eq!( libc::syscall( @@ -86,17 +86,19 @@ fn wait_timeout() { addr_of!(futex), libc::FUTEX_WAIT, 123, - &libc::timespec { tv_sec: 0, tv_nsec: 200_000_000 }, + &libc::timespec { tv_sec: 0, tv_nsec: 100_000_000 }, ), -1, ); assert_eq!(io::Error::last_os_error().raw_os_error().unwrap(), libc::ETIMEDOUT); } - assert!((200..1000).contains(&start.elapsed().as_millis())); + assert!((100..1000).contains(&start.elapsed().as_millis())); } fn wait_absolute_timeout() { + static mut FUTEX: i32 = 123; + let start = Instant::now(); // Get the current monotonic timestamp as timespec. @@ -106,21 +108,19 @@ fn wait_absolute_timeout() { now.assume_init() }; - // Add 200ms. - timeout.tv_nsec += 200_000_000; + // Add 100ms. + timeout.tv_nsec += 100_000_000; if timeout.tv_nsec > 1_000_000_000 { timeout.tv_nsec -= 1_000_000_000; timeout.tv_sec += 1; } - let futex: i32 = 123; - - // Wait for 200ms from now, with nobody waking us up early. + // Wait for 100ms from now, with nobody waking us up early. unsafe { assert_eq!( libc::syscall( libc::SYS_futex, - addr_of!(futex), + addr_of!(FUTEX), libc::FUTEX_WAIT_BITSET, 123, &timeout, @@ -132,16 +132,14 @@ fn wait_absolute_timeout() { assert_eq!(io::Error::last_os_error().raw_os_error().unwrap(), libc::ETIMEDOUT); } - assert!((200..1000).contains(&start.elapsed().as_millis())); + assert!((100..1000).contains(&start.elapsed().as_millis())); } fn wait_wake() { - let start = Instant::now(); - static mut FUTEX: i32 = 0; let t = thread::spawn(move || { - thread::sleep(Duration::from_millis(200)); + thread::sleep(Duration::from_millis(100)); unsafe { assert_eq!( libc::syscall( @@ -155,6 +153,8 @@ fn wait_wake() { } }); + let start = Instant::now(); + unsafe { assert_eq!( libc::syscall( @@ -168,19 +168,15 @@ fn wait_wake() { ); } - // When running this in stress-gc mode, things can take quite long. - // So the timeout is 3000 ms. - assert!((200..3000).contains(&start.elapsed().as_millis())); + assert!((100..1000).contains(&start.elapsed().as_millis())); t.join().unwrap(); } fn wait_wake_bitset() { - let start = Instant::now(); - static mut FUTEX: i32 = 0; let t = thread::spawn(move || { - thread::sleep(Duration::from_millis(200)); + thread::sleep(Duration::from_millis(100)); unsafe { assert_eq!( libc::syscall( @@ -195,7 +191,7 @@ fn wait_wake_bitset() { 0, // Didn't match any thread. ); } - thread::sleep(Duration::from_millis(200)); + thread::sleep(Duration::from_millis(100)); unsafe { assert_eq!( libc::syscall( @@ -212,6 +208,8 @@ fn wait_wake_bitset() { } }); + let start = Instant::now(); + unsafe { assert_eq!( libc::syscall( @@ -227,7 +225,7 @@ fn wait_wake_bitset() { ); } - assert!((400..1000).contains(&start.elapsed().as_millis())); + assert!((200..1000).contains(&start.elapsed().as_millis())); t.join().unwrap(); } diff --git a/src/tools/miri/tests/pass/concurrency/thread_park_isolated.rs b/src/tools/miri/tests/pass/concurrency/thread_park_isolated.rs index 35145fe9bd31a..27efffeb29412 100644 --- a/src/tools/miri/tests/pass/concurrency/thread_park_isolated.rs +++ b/src/tools/miri/tests/pass/concurrency/thread_park_isolated.rs @@ -5,8 +5,8 @@ use std::time::{Duration, Instant}; fn main() { let start = Instant::now(); - thread::park_timeout(Duration::from_millis(200)); + thread::park_timeout(Duration::from_millis(100)); - // Thanks to deterministic execution, this will wait *exactly* 200ms, plus the time for the surrounding code. - assert!((200..210).contains(&start.elapsed().as_millis()), "{}", start.elapsed().as_millis()); + // Thanks to deterministic execution, this will wait *exactly* 100ms, plus the time for the surrounding code. + assert!((100..110).contains(&start.elapsed().as_millis()), "{}", start.elapsed().as_millis()); } From c48dd01944c43bdc6ea207d13cb72c0781a23f14 Mon Sep 17 00:00:00 2001 From: acegikmoo Date: Tue, 12 May 2026 09:05:21 +0600 Subject: [PATCH 19/46] feat: Implement linkat syscall for hardlink support Signed-off-by: acegikmoo --- .../miri/src/shims/unix/foreign_items.rs | 10 ++++ src/tools/miri/src/shims/unix/fs.rs | 56 +++++++++++++++++++ src/tools/miri/tests/pass-dep/libc/libc-fs.rs | 37 +++++++++++- src/tools/miri/tests/pass/shims/fs.rs | 25 +++++++++ 4 files changed, 127 insertions(+), 1 deletion(-) diff --git a/src/tools/miri/src/shims/unix/foreign_items.rs b/src/tools/miri/src/shims/unix/foreign_items.rs index 27014f0015772..c2ae65091ee2c 100644 --- a/src/tools/miri/src/shims/unix/foreign_items.rs +++ b/src/tools/miri/src/shims/unix/foreign_items.rs @@ -396,6 +396,16 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { let result = this.symlink(target, linkpath)?; this.write_scalar(result, dest)?; } + "linkat" => { + let [oldfd, oldpath, newfd, newpath, flags] = this.check_shim_sig( + shim_sig!(extern "C" fn(i32, *const _, i32, *const _, i32) -> i32), + link_name, + abi, + args, + )?; + let result = this.linkat(oldfd, oldpath, newfd, newpath, flags)?; + this.write_scalar(result, dest)?; + } "fstat" => { let [fd, buf] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?; let result = this.fstat(fd, buf)?; diff --git a/src/tools/miri/src/shims/unix/fs.rs b/src/tools/miri/src/shims/unix/fs.rs index d221dc52a5852..a65f0ec722cf7 100644 --- a/src/tools/miri/src/shims/unix/fs.rs +++ b/src/tools/miri/src/shims/unix/fs.rs @@ -584,6 +584,62 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { interp_ok(Scalar::from_i32(this.try_unwrap_io_result(result)?)) } + fn linkat( + &mut self, + oldfd_op: &OpTy<'tcx>, + oldpath_op: &OpTy<'tcx>, + newfd_op: &OpTy<'tcx>, + newpath_op: &OpTy<'tcx>, + flags_op: &OpTy<'tcx>, + ) -> InterpResult<'tcx, Scalar> { + let this = self.eval_context_mut(); + + // Load all arguments + let flags = this.read_scalar(flags_op)?.to_i32()?; + let oldfd = this.read_scalar(oldfd_op)?.to_i32()?; + let newfd = this.read_scalar(newfd_op)?.to_i32()?; + let oldpath_ptr = this.read_pointer(oldpath_op)?; + let newpath_ptr = this.read_pointer(newpath_op)?; + + // Reject if isolation is enabled. + if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op { + this.reject_in_isolation("`linkat`", reject_with)?; + return this.set_errno_and_return_neg1_i32(ErrorKind::PermissionDenied); + } + + // Read flags- only support 0 + if flags != 0 { + throw_unsup_format!("unsupported linkat flags {:#x}", flags); + } + + // Read file descriptors + let at_fdcwd = this.eval_libc_i32("AT_FDCWD"); + + // Resolve oldpath + let oldpath = if oldfd == at_fdcwd { + if oldpath_ptr == Pointer::null() { + return this.set_errno_and_return_neg1_i32(ErrorKind::InvalidInput); + } + this.read_path_from_c_str(oldpath_ptr)?.into_owned() + } else { + // We don't support linkat with a real fd yet + return this.set_errno_and_return_neg1_i32(LibcError("EBADF")); + }; + + // Resolve newpath + let newpath = if newfd == at_fdcwd { + if newpath_ptr == Pointer::null() { + return this.set_errno_and_return_neg1_i32(ErrorKind::InvalidInput); + } + this.read_path_from_c_str(newpath_ptr)?.into_owned() + } else { + return this.set_errno_and_return_neg1_i32(LibcError("EBADF")); + }; + + let result = fs::hard_link(&oldpath, &newpath).map(|()| 0); + interp_ok(Scalar::from_i32(this.try_unwrap_io_result(result)?)) + } + fn stat(&mut self, path_op: &OpTy<'tcx>, buf_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> { let this = self.eval_context_mut(); diff --git a/src/tools/miri/tests/pass-dep/libc/libc-fs.rs b/src/tools/miri/tests/pass-dep/libc/libc-fs.rs index 9cc8685827077..cc32fd10b32ac 100644 --- a/src/tools/miri/tests/pass-dep/libc/libc-fs.rs +++ b/src/tools/miri/tests/pass-dep/libc/libc-fs.rs @@ -3,7 +3,7 @@ use std::ffi::{CStr, CString, OsString}; use std::fs::{File, canonicalize, create_dir, remove_dir, remove_file}; -use std::io::{Error, ErrorKind, Write}; +use std::io::{Error, ErrorKind, Read, Write}; use std::os::unix::ffi::OsStrExt; use std::os::unix::io::AsRawFd; use std::path::PathBuf; @@ -22,6 +22,7 @@ fn main() { test_dup_stdout_stderr(); test_canonicalize_too_long(); test_rename(); + test_linkat(); test_ftruncate::(libc::ftruncate); #[cfg(target_os = "linux")] test_ftruncate::(libc::ftruncate64); @@ -1147,3 +1148,37 @@ fn test_pwrite() { // The write should start at the provided byte offset. assert_eq!(&write_buffer[0..bytes_written], &read_buffer[OFFSET..(bytes_written + OFFSET)]); } + +fn test_linkat() { + let source = utils::prepare_with_content("miri_test_libc_linkat_source.txt", b"hello"); + let link = utils::prepare("miri_test_libc_linkat_link.txt"); + + let c_source = CString::new(source.as_os_str().as_bytes()).expect("CString::new failed"); + let c_link = CString::new(link.as_os_str().as_bytes()).expect("CString::new failed"); + + // Call linkat + unsafe { + libc_utils::errno_check(libc::linkat( + libc::AT_FDCWD, + c_source.as_ptr(), + libc::AT_FDCWD, + c_link.as_ptr(), + 0, + )); + } + + // Verify that the hard link works + // Modifications to one are visible through the other + let mut file = std::fs::File::create(&source).unwrap(); + file.write_all(b"hello world").unwrap(); + drop(file); + + let mut file = std::fs::File::open(&link).unwrap(); + let mut contents = Vec::new(); + file.read_to_end(&mut contents).unwrap(); + assert_eq!(contents, b"hello world"); + + // Cleanup + remove_file(&source).unwrap(); + remove_file(&link).unwrap(); +} diff --git a/src/tools/miri/tests/pass/shims/fs.rs b/src/tools/miri/tests/pass/shims/fs.rs index 48b9971026d2a..cc422493c5ae0 100644 --- a/src/tools/miri/tests/pass/shims/fs.rs +++ b/src/tools/miri/tests/pass/shims/fs.rs @@ -34,6 +34,8 @@ fn main() { test_file_set_len(); test_file_sync(); test_rename(); + #[cfg(all(unix, not(target_os = "android")))] + test_hard_link(); // Windows file handling is very incomplete. if cfg!(not(windows)) { test_directory(); @@ -511,3 +513,26 @@ fn test_preadv_pwritev() { f.read_exact(&mut written_bytes).unwrap(); assert_eq!(written_bytes.as_slice(), &write_buffer[0..bytes_written]); } + +#[cfg(unix)] +// We do not support the link syscall on Android +#[cfg(all(unix, not(target_os = "android")))] +fn test_hard_link() { + use std::os::unix::fs::MetadataExt; + let source = utils::prepare_with_content("miri_test_fs_hard_link_source.txt", b"hello"); + let link = utils::prepare("miri_test_fs_hard_link_link.txt"); + + fs::hard_link(&source, &link).unwrap(); + + // Verify both files have same inode + let source_meta = std::fs::metadata(&source).unwrap(); + let link_meta = std::fs::metadata(&link).unwrap(); + assert_eq!(source_meta.ino(), link_meta.ino()); + + // Test error: link already exists + assert_eq!(ErrorKind::AlreadyExists, fs::hard_link(&source, &link).unwrap_err().kind()); + + // Cleanup after test + remove_file(&source).unwrap(); + remove_file(&link).unwrap(); +} From 90d167f40c3512dc24101add8f15d6b5e9ccc98c Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Sun, 31 May 2026 11:46:09 +0200 Subject: [PATCH 20/46] support prctl on Linux (not just Android) --- .../miri/src/shims/unix/android/foreign_items.rs | 14 +------------- src/tools/miri/src/shims/unix/android/mod.rs | 1 - .../miri/src/shims/unix/linux/foreign_items.rs | 2 ++ src/tools/miri/src/shims/unix/linux_like/mod.rs | 1 + .../shims/unix/{android => linux_like}/thread.rs | 5 ++--- .../miri/tests/pass-dep/libc/prctl-threadname.rs | 2 +- .../miri/tests/pass-dep/libc/pthread-threadname.rs | 2 +- 7 files changed, 8 insertions(+), 19 deletions(-) rename src/tools/miri/src/shims/unix/{android => linux_like}/thread.rs (92%) diff --git a/src/tools/miri/src/shims/unix/android/foreign_items.rs b/src/tools/miri/src/shims/unix/android/foreign_items.rs index 5259ca2294884..999750a9e00a9 100644 --- a/src/tools/miri/src/shims/unix/android/foreign_items.rs +++ b/src/tools/miri/src/shims/unix/android/foreign_items.rs @@ -3,11 +3,11 @@ use rustc_middle::ty::Ty; use rustc_span::Symbol; use rustc_target::callconv::FnAbi; -use crate::shims::unix::android::thread::prctl; use crate::shims::unix::env::EvalContextExt as _; use crate::shims::unix::linux_like::epoll::EvalContextExt as _; use crate::shims::unix::linux_like::eventfd::EvalContextExt as _; use crate::shims::unix::linux_like::syscall::syscall; +use crate::shims::unix::linux_like::thread::prctl; use crate::shims::unix::*; use crate::*; @@ -27,18 +27,6 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { let this = self.eval_context_mut(); match link_name.as_str() { // File related shims - "stat" => { - // FIXME: This does not have a direct test (#3179). - let [path, buf] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?; - let result = this.stat(path, buf)?; - this.write_scalar(result, dest)?; - } - "lstat" => { - // FIXME: This does not have a direct test (#3179). - let [path, buf] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?; - let result = this.lstat(path, buf)?; - this.write_scalar(result, dest)?; - } "pread64" => { // FIXME: This does not have a direct test (#3179). let [fd, buf, count, offset] = this.check_shim_sig( diff --git a/src/tools/miri/src/shims/unix/android/mod.rs b/src/tools/miri/src/shims/unix/android/mod.rs index 1f2a74bac5935..09c6507b24f84 100644 --- a/src/tools/miri/src/shims/unix/android/mod.rs +++ b/src/tools/miri/src/shims/unix/android/mod.rs @@ -1,2 +1 @@ pub mod foreign_items; -pub mod thread; diff --git a/src/tools/miri/src/shims/unix/linux/foreign_items.rs b/src/tools/miri/src/shims/unix/linux/foreign_items.rs index e42bf900aceff..caea737582d78 100644 --- a/src/tools/miri/src/shims/unix/linux/foreign_items.rs +++ b/src/tools/miri/src/shims/unix/linux/foreign_items.rs @@ -8,6 +8,7 @@ use self::shims::unix::linux_like::eventfd::EvalContextExt as _; use self::shims::unix::linux_like::syscall::syscall; use crate::machine::{SIGRTMAX, SIGRTMIN}; use crate::shims::unix::foreign_items::EvalContextExt as _; +use crate::shims::unix::linux_like::thread::prctl; use crate::shims::unix::*; use crate::*; @@ -197,6 +198,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { let result = this.unix_gettid(link_name.as_str())?; this.write_scalar(result, dest)?; } + "prctl" => prctl(this, link_name, abi, args, dest)?, // Dynamically invoked syscalls "syscall" => { diff --git a/src/tools/miri/src/shims/unix/linux_like/mod.rs b/src/tools/miri/src/shims/unix/linux_like/mod.rs index 1a22539a4eea9..5d419b91d5799 100644 --- a/src/tools/miri/src/shims/unix/linux_like/mod.rs +++ b/src/tools/miri/src/shims/unix/linux_like/mod.rs @@ -2,3 +2,4 @@ pub mod epoll; pub mod eventfd; pub mod sync; pub mod syscall; +pub mod thread; diff --git a/src/tools/miri/src/shims/unix/android/thread.rs b/src/tools/miri/src/shims/unix/linux_like/thread.rs similarity index 92% rename from src/tools/miri/src/shims/unix/android/thread.rs rename to src/tools/miri/src/shims/unix/linux_like/thread.rs index 4e7b21d7d9443..da76af5704d88 100644 --- a/src/tools/miri/src/shims/unix/android/thread.rs +++ b/src/tools/miri/src/shims/unix/linux_like/thread.rs @@ -18,9 +18,8 @@ pub fn prctl<'tcx>( ) -> InterpResult<'tcx> { let ([op], varargs) = ecx.check_shim_sig_variadic_lenient(abi, CanonAbi::C, link_name, args)?; - // FIXME: Use constants once https://github.com/rust-lang/libc/pull/3941 backported to the 0.2 branch. - let pr_set_name = 15; - let pr_get_name = 16; + let pr_set_name = ecx.eval_libc_i32("PR_SET_NAME"); + let pr_get_name = ecx.eval_libc_i32("PR_GET_NAME"); let res = match ecx.read_scalar(op)?.to_i32()? { op if op == pr_set_name => { diff --git a/src/tools/miri/tests/pass-dep/libc/prctl-threadname.rs b/src/tools/miri/tests/pass-dep/libc/prctl-threadname.rs index 87ae3753fea70..88315fd91c70a 100644 --- a/src/tools/miri/tests/pass-dep/libc/prctl-threadname.rs +++ b/src/tools/miri/tests/pass-dep/libc/prctl-threadname.rs @@ -1,4 +1,4 @@ -//@only-target: android # Miri supports prctl for Android only +//@only-target: linux android # Linux-specific API use std::ffi::{CStr, CString}; use std::thread; diff --git a/src/tools/miri/tests/pass-dep/libc/pthread-threadname.rs b/src/tools/miri/tests/pass-dep/libc/pthread-threadname.rs index 6ac71b5ad1edc..cf7ea4c6abba1 100644 --- a/src/tools/miri/tests/pass-dep/libc/pthread-threadname.rs +++ b/src/tools/miri/tests/pass-dep/libc/pthread-threadname.rs @@ -1,5 +1,5 @@ //@ignore-target: windows # No pthreads on Windows -//@ignore-target: android # No pthread_{get,set}_name on Android +//@ignore-target: android # No pthread_{get,set}name_np on Android use std::ffi::{CStr, CString}; use std::thread; From 58837e82f31d048c59fc937c2ab57d6b4efd69be Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Wed, 27 May 2026 22:07:10 +0200 Subject: [PATCH 21/46] add shim for `neon.pmull64` the aarch64 variant of pclmulqdq, using carryless_mul for polynomial multiplication in GF(2) --- src/tools/miri/src/shims/aarch64.rs | 24 ++++++++++ .../shims/aarch64/intrinsics-aarch64-aes.rs | 45 +++++++++++++++++++ 2 files changed, 69 insertions(+) create mode 100644 src/tools/miri/tests/pass/shims/aarch64/intrinsics-aarch64-aes.rs diff --git a/src/tools/miri/src/shims/aarch64.rs b/src/tools/miri/src/shims/aarch64.rs index 6a914d5cfa68a..1fd4cd324acdc 100644 --- a/src/tools/miri/src/shims/aarch64.rs +++ b/src/tools/miri/src/shims/aarch64.rs @@ -214,6 +214,30 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { let result = compute_crc32(crc, data, bit_size, polynomial); this.write_scalar(Scalar::from_u32(result), dest)?; } + // Polynomial multiply long (64-bit x 64-bit -> 128-bit). + // + // This is the same as "carryless" multiplication, see + // . + // + // Used to implement the vmull_p64 and vmull_high_p64 functions. + // https://developer.arm.com/architectures/instruction-sets/intrinsics/vmull_p64 + "neon.pmull64" => { + // LLVM and GCC group pmull with the AES intrinsics. + // Also see . + this.expect_target_feature_for_intrinsic(link_name, "aes")?; + + let [left, right] = + this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?; + let left = this.read_scalar(left)?.to_u64()?; + let right = this.read_scalar(right)?.to_u64()?; + + let result = left.widening_carryless_mul(right); + + // dest is int8x16_t, transmute to u128 for the write. + let dest = dest.transmute(this.machine.layouts.u128, this)?; + this.write_scalar(Scalar::from_u128(result), &dest)?; + } + _ => return interp_ok(EmulateItemResult::NotSupported), } interp_ok(EmulateItemResult::NeedsReturn) diff --git a/src/tools/miri/tests/pass/shims/aarch64/intrinsics-aarch64-aes.rs b/src/tools/miri/tests/pass/shims/aarch64/intrinsics-aarch64-aes.rs new file mode 100644 index 0000000000000..1345924beecf0 --- /dev/null +++ b/src/tools/miri/tests/pass/shims/aarch64/intrinsics-aarch64-aes.rs @@ -0,0 +1,45 @@ +// We're testing aarch64 AES target specific features. +//@only-target: aarch64 +//@compile-flags: -C target-feature=+neon,+aes + +use std::arch::aarch64::*; +use std::arch::is_aarch64_feature_detected; + +fn main() { + assert!(is_aarch64_feature_detected!("neon")); + assert!(is_aarch64_feature_detected!("aes")); + + unsafe { + test_vmull_p64(); + test_vmull_high_p64(); + } +} + +#[target_feature(enable = "neon,aes")] +unsafe fn test_vmull_p64() { + assert_eq!(vmull_p64(0, 0), 0); + assert_eq!(vmull_p64(0, 0xffffffffffffffff), 0); + assert_eq!(vmull_p64(1, 1), 1); + assert_eq!(vmull_p64(1, 0x8000000000000000), 0x8000000000000000); + + assert_eq!(vmull_p64(0b11, 0b11), 0b101); + + // Check with the same inputs that are used in the x86_64 pclmulqdq test. + assert_eq!( + vmull_p64(0x7fffffffffffffff, 0xdd358416f52ecd34), + (2704901987789626761u128 << 64) | 13036940098130298092u128, + ); +} + +#[target_feature(enable = "neon,aes")] +unsafe fn test_vmull_high_p64() { + // The lower (first) element is ignored. + let a = vcombine_p64(vcreate_p64(123), vcreate_p64(0b11)); + let b = vcombine_p64(vcreate_p64(456), vcreate_p64(0b11)); + assert_eq!(vmull_high_p64(a, b), 0b101); + + // Check with the same inputs that are used in the x86_64 pclmulqdq test. + let a = vcombine_p64(vcreate_p64(0), vcreate_p64(0x7fffffffffffffff)); + let b = vcombine_p64(vcreate_p64(0), vcreate_p64(0xdd358416f52ecd34)); + assert_eq!(vmull_high_p64(a, b), (2704901987789626761u128 << 64) | 13036940098130298092u128); +} From 8b35be8d94aafba523ccc6e99e0af33acb870864 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Sun, 31 May 2026 11:32:43 +0200 Subject: [PATCH 22/46] minor cleanup --- src/tools/miri/src/shims/unix/fs.rs | 39 +++++++++---------- src/tools/miri/tests/pass-dep/libc/libc-fs.rs | 23 +++++------ src/tools/miri/tests/pass/shims/fs.rs | 33 ++++++++++------ 3 files changed, 48 insertions(+), 47 deletions(-) diff --git a/src/tools/miri/src/shims/unix/fs.rs b/src/tools/miri/src/shims/unix/fs.rs index a65f0ec722cf7..0b0837f4b8bc2 100644 --- a/src/tools/miri/src/shims/unix/fs.rs +++ b/src/tools/miri/src/shims/unix/fs.rs @@ -601,40 +601,37 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { let oldpath_ptr = this.read_pointer(oldpath_op)?; let newpath_ptr = this.read_pointer(newpath_op)?; + // Relevant libc constants + let at_fdcwd = this.eval_libc_i32("AT_FDCWD"); + // Reject if isolation is enabled. if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op { this.reject_in_isolation("`linkat`", reject_with)?; return this.set_errno_and_return_neg1_i32(ErrorKind::PermissionDenied); } - // Read flags- only support 0 + // Read flags - only support 0. if flags != 0 { throw_unsup_format!("unsupported linkat flags {:#x}", flags); } - // Read file descriptors - let at_fdcwd = this.eval_libc_i32("AT_FDCWD"); - // Resolve oldpath - let oldpath = if oldfd == at_fdcwd { - if oldpath_ptr == Pointer::null() { - return this.set_errno_and_return_neg1_i32(ErrorKind::InvalidInput); - } - this.read_path_from_c_str(oldpath_ptr)?.into_owned() - } else { - // We don't support linkat with a real fd yet - return this.set_errno_and_return_neg1_i32(LibcError("EBADF")); - }; + if oldfd != at_fdcwd { + throw_unsup_format!("linkat with `olddirfd` not equal to `AT_FDCWD` is not supported"); + } + if oldpath_ptr == Pointer::null() { + return this.set_errno_and_return_neg1_i32(LibcError("EFAULT")); + } + let oldpath = this.read_path_from_c_str(oldpath_ptr)?.into_owned(); // Resolve newpath - let newpath = if newfd == at_fdcwd { - if newpath_ptr == Pointer::null() { - return this.set_errno_and_return_neg1_i32(ErrorKind::InvalidInput); - } - this.read_path_from_c_str(newpath_ptr)?.into_owned() - } else { - return this.set_errno_and_return_neg1_i32(LibcError("EBADF")); - }; + if newfd != at_fdcwd { + throw_unsup_format!("linkat with `newdirfd` not equal to `AT_FDCWD` is not supported"); + } + if newpath_ptr == Pointer::null() { + return this.set_errno_and_return_neg1_i32(LibcError("EFAULT")); + } + let newpath = this.read_path_from_c_str(newpath_ptr)?.into_owned(); let result = fs::hard_link(&oldpath, &newpath).map(|()| 0); interp_ok(Scalar::from_i32(this.try_unwrap_io_result(result)?)) diff --git a/src/tools/miri/tests/pass-dep/libc/libc-fs.rs b/src/tools/miri/tests/pass-dep/libc/libc-fs.rs index cc32fd10b32ac..84b2fab9e82a7 100644 --- a/src/tools/miri/tests/pass-dep/libc/libc-fs.rs +++ b/src/tools/miri/tests/pass-dep/libc/libc-fs.rs @@ -2,8 +2,8 @@ //@compile-flags: -Zmiri-disable-isolation use std::ffi::{CStr, CString, OsString}; -use std::fs::{File, canonicalize, create_dir, remove_dir, remove_file}; -use std::io::{Error, ErrorKind, Read, Write}; +use std::fs::{self, File, canonicalize, create_dir, remove_dir, remove_file}; +use std::io::{Error, ErrorKind, Write}; use std::os::unix::ffi::OsStrExt; use std::os::unix::io::AsRawFd; use std::path::PathBuf; @@ -22,7 +22,6 @@ fn main() { test_dup_stdout_stderr(); test_canonicalize_too_long(); test_rename(); - test_linkat(); test_ftruncate::(libc::ftruncate); #[cfg(target_os = "linux")] test_ftruncate::(libc::ftruncate64); @@ -69,11 +68,12 @@ fn main() { #[cfg(not(target_os = "solaris"))] test_pwritev(); test_pwrite(); + test_linkat(); } #[cfg(target_os = "linux")] #[track_caller] -fn assert_statx_matches_metadata(stx: &libc::statx, meta: &std::fs::Metadata, expected_size: u64) { +fn assert_statx_matches_metadata(stx: &libc::statx, meta: &fs::Metadata, expected_size: u64) { use std::os::unix::fs::MetadataExt; let mask = stx.stx_mask; @@ -153,7 +153,7 @@ fn test_statx_on_file_path() { assert_eq!(ret, 0, "statx failed: {}", std::io::Error::last_os_error()); let stx = stx.assume_init(); - let meta = std::fs::metadata(&path).unwrap(); + let meta = fs::metadata(&path).unwrap(); assert_statx_matches_metadata(&stx, &meta, bytes.len() as u64); } @@ -1167,15 +1167,10 @@ fn test_linkat() { )); } - // Verify that the hard link works - // Modifications to one are visible through the other - let mut file = std::fs::File::create(&source).unwrap(); - file.write_all(b"hello world").unwrap(); - drop(file); - - let mut file = std::fs::File::open(&link).unwrap(); - let mut contents = Vec::new(); - file.read_to_end(&mut contents).unwrap(); + // Verify that the hard link works: + // Modifications to one are visible through the other. + fs::write(&source, b"hello world").unwrap(); + let contents = fs::read(&link).unwrap(); assert_eq!(contents, b"hello world"); // Cleanup diff --git a/src/tools/miri/tests/pass/shims/fs.rs b/src/tools/miri/tests/pass/shims/fs.rs index cc422493c5ae0..393ce66d876d5 100644 --- a/src/tools/miri/tests/pass/shims/fs.rs +++ b/src/tools/miri/tests/pass/shims/fs.rs @@ -34,17 +34,18 @@ fn main() { test_file_set_len(); test_file_sync(); test_rename(); - #[cfg(all(unix, not(target_os = "android")))] - test_hard_link(); // Windows file handling is very incomplete. if cfg!(not(windows)) { test_directory(); test_canonicalize(); - #[cfg(unix)] - test_pread_pwrite(); #[cfg(not(target_os = "solaris"))] test_flock(); + #[cfg(not(target_os = "android"))] + test_hard_link(); + test_readv_writev(); + #[cfg(unix)] + test_pread_pwrite(); #[cfg(all(unix, not(any(target_os = "solaris", target_os = "android"))))] test_preadv_pwritev(); } @@ -514,20 +515,28 @@ fn test_preadv_pwritev() { assert_eq!(written_bytes.as_slice(), &write_buffer[0..bytes_written]); } -#[cfg(unix)] -// We do not support the link syscall on Android -#[cfg(all(unix, not(target_os = "android")))] +// std uses `libc::link` on Android which we do not support. +#[cfg(not(target_os = "android"))] fn test_hard_link() { - use std::os::unix::fs::MetadataExt; let source = utils::prepare_with_content("miri_test_fs_hard_link_source.txt", b"hello"); let link = utils::prepare("miri_test_fs_hard_link_link.txt"); fs::hard_link(&source, &link).unwrap(); - // Verify both files have same inode - let source_meta = std::fs::metadata(&source).unwrap(); - let link_meta = std::fs::metadata(&link).unwrap(); - assert_eq!(source_meta.ino(), link_meta.ino()); + // Verify that the hard link works: + // Modifications to one are visible through the other. + fs::write(&source, b"hello world").unwrap(); + let contents = fs::read(&link).unwrap(); + assert_eq!(contents, b"hello world"); + + // Only on Unix: verify both files have same inode + #[cfg(unix)] + { + use std::os::unix::fs::MetadataExt; + let source_meta = std::fs::metadata(&source).unwrap(); + let link_meta = std::fs::metadata(&link).unwrap(); + assert_eq!(source_meta.ino(), link_meta.ino()); + } // Test error: link already exists assert_eq!(ErrorKind::AlreadyExists, fs::hard_link(&source, &link).unwrap_err().kind()); From a5591746c5b157d11690dce73d0d24ebcd8cb706 Mon Sep 17 00:00:00 2001 From: Jonathan Brouwer Date: Sun, 31 May 2026 14:32:46 +0200 Subject: [PATCH 23/46] Convert feature gating to the new attr parsing infrastructure --- compiler/rustc_ast_passes/src/feature_gate.rs | 11 +-- .../src/attributes/allow_unstable.rs | 11 ++- .../src/attributes/autodiff.rs | 4 +- .../rustc_attr_parsing/src/attributes/body.rs | 3 + .../src/attributes/cfi_encoding.rs | 3 + .../src/attributes/codegen_attrs.rs | 21 ++++- .../src/attributes/confusables.rs | 3 + .../src/attributes/crate_level.rs | 26 +++++- .../src/attributes/debugger.rs | 2 + .../src/attributes/deprecation.rs | 2 + .../attributes/diagnostic/do_not_recommend.rs | 3 +- .../src/attributes/diagnostic/on_const.rs | 2 + .../src/attributes/diagnostic/on_move.rs | 3 +- .../attributes/diagnostic/on_unimplemented.rs | 6 ++ .../src/attributes/diagnostic/on_unknown.rs | 2 + .../attributes/diagnostic/on_unmatch_args.rs | 2 + .../rustc_attr_parsing/src/attributes/doc.rs | 3 +- .../src/attributes/dummy.rs | 5 +- .../src/attributes/inline.rs | 5 +- .../src/attributes/instruction_set.rs | 2 + .../src/attributes/link_attrs.rs | 15 +++- .../src/attributes/lint_helpers.rs | 7 ++ .../src/attributes/loop_match.rs | 4 + .../src/attributes/macro_attrs.rs | 7 ++ .../rustc_attr_parsing/src/attributes/mod.rs | 21 +++-- .../src/attributes/must_not_suspend.rs | 3 + .../src/attributes/must_use.rs | 3 + .../src/attributes/no_implicit_prelude.rs | 3 + .../src/attributes/no_link.rs | 3 + .../src/attributes/non_exhaustive.rs | 2 + .../rustc_attr_parsing/src/attributes/path.rs | 3 + .../src/attributes/pin_v2.rs | 3 + .../src/attributes/prelude.rs | 2 + .../src/attributes/proc_macro_attrs.rs | 5 ++ .../src/attributes/prototype.rs | 5 +- .../rustc_attr_parsing/src/attributes/repr.rs | 8 +- .../src/attributes/rustc_allocator.rs | 7 ++ .../src/attributes/rustc_dump.rs | 18 ++++ .../src/attributes/rustc_internal.rs | 82 ++++++++++++++++--- .../src/attributes/semantics.rs | 3 + .../src/attributes/stability.rs | 35 +++----- .../src/attributes/test_attrs.rs | 9 ++ .../src/attributes/traits.rs | 12 +++ .../src/attributes/transparency.rs | 2 + compiler/rustc_attr_parsing/src/context.rs | 6 +- compiler/rustc_attr_parsing/src/interface.rs | 1 + compiler/rustc_attr_parsing/src/lib.rs | 1 + .../src/session_diagnostics.rs | 7 -- compiler/rustc_attr_parsing/src/stability.rs | 70 ++++++++++++++++ .../src/error_codes/E0734.md | 6 +- compiler/rustc_feature/src/builtin_attrs.rs | 15 ++++ compiler/rustc_feature/src/lib.rs | 5 +- 52 files changed, 408 insertions(+), 84 deletions(-) create mode 100644 compiler/rustc_attr_parsing/src/stability.rs diff --git a/compiler/rustc_ast_passes/src/feature_gate.rs b/compiler/rustc_ast_passes/src/feature_gate.rs index 1b3ce2427c3c7..1bd7108e77260 100644 --- a/compiler/rustc_ast_passes/src/feature_gate.rs +++ b/compiler/rustc_ast_passes/src/feature_gate.rs @@ -2,7 +2,7 @@ use rustc_ast::visit::{self, AssocCtxt, FnCtxt, FnKind, Visitor}; use rustc_ast::{self as ast, AttrVec, GenericBound, NodeId, PatKind, attr, token}; use rustc_attr_parsing::AttributeParser; use rustc_errors::msg; -use rustc_feature::{AttributeGate, BUILTIN_ATTRIBUTE_MAP, BuiltinAttribute, Features}; +use rustc_feature::Features; use rustc_hir::Attribute; use rustc_hir::attrs::AttributeKind; use rustc_session::Session; @@ -155,15 +155,6 @@ impl<'a> PostExpansionVisitor<'a> { impl<'a> Visitor<'a> for PostExpansionVisitor<'a> { fn visit_attribute(&mut self, attr: &ast::Attribute) { - let attr_info = attr.name().and_then(|name| BUILTIN_ATTRIBUTE_MAP.get(&name)); - // Check feature gates for built-in attributes. - if let Some(BuiltinAttribute { - gate: AttributeGate::Gated { feature, message, check, notes, .. }, - .. - }) = attr_info - { - gate_alt!(self, check(self.features), *feature, attr.span, *message, *notes); - } // Check unstable flavors of the `#[doc]` attribute. if attr.has_name(sym::doc) { for meta_item_inner in attr.meta_item_list().unwrap_or_default() { diff --git a/compiler/rustc_attr_parsing/src/attributes/allow_unstable.rs b/compiler/rustc_attr_parsing/src/attributes/allow_unstable.rs index e72533fb3c890..78e9607d8dedc 100644 --- a/compiler/rustc_attr_parsing/src/attributes/allow_unstable.rs +++ b/compiler/rustc_attr_parsing/src/attributes/allow_unstable.rs @@ -1,5 +1,7 @@ use std::iter; +use rustc_feature::AttributeStability; + use super::prelude::*; use crate::session_diagnostics; @@ -16,6 +18,7 @@ impl CombineAttributeParser for AllowInternalUnstableParser { Warn(Target::Arm), ]); const TEMPLATE: AttributeTemplate = template!(Word, List: &["feat1, feat2, ..."]); + const STABILITY: AttributeStability = unstable!(allow_internal_unstable); fn extend( cx: &mut AcceptContext<'_, '_>, @@ -32,6 +35,7 @@ impl CombineAttributeParser for UnstableFeatureBoundParser { const PATH: &[rustc_span::Symbol] = &[sym::unstable_feature_bound]; type Item = (Symbol, Span); const CONVERT: ConvertFn = |items, _| AttributeKind::UnstableFeatureBound(items); + const STABILITY: AttributeStability = unstable!(staged_api); const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[ Allow(Target::Fn), Allow(Target::Impl { of_trait: true }), @@ -43,9 +47,6 @@ impl CombineAttributeParser for UnstableFeatureBoundParser { cx: &mut AcceptContext<'_, '_>, args: &ArgParser, ) -> impl IntoIterator { - if !cx.features().staged_api() { - cx.emit_err(session_diagnostics::StabilityOutsideStd { span: cx.attr_span }); - } parse_unstable(cx, args, ::PATH[0]) .into_iter() .zip(iter::repeat(cx.attr_span)) @@ -65,6 +66,10 @@ impl CombineAttributeParser for RustcAllowConstFnUnstableParser { Allow(Target::Method(MethodKind::TraitImpl)), ]); const TEMPLATE: AttributeTemplate = template!(Word, List: &["feat1, feat2, ..."]); + const STABILITY: AttributeStability = unstable!( + rustc_attrs, + "rustc_allow_const_fn_unstable side-steps feature gating and stability checks" + ); fn extend( cx: &mut AcceptContext<'_, '_>, diff --git a/compiler/rustc_attr_parsing/src/attributes/autodiff.rs b/compiler/rustc_attr_parsing/src/attributes/autodiff.rs index dc63767462201..1ca433a4d6d7b 100644 --- a/compiler/rustc_attr_parsing/src/attributes/autodiff.rs +++ b/compiler/rustc_attr_parsing/src/attributes/autodiff.rs @@ -2,7 +2,7 @@ use std::str::FromStr; use rustc_ast::LitKind; use rustc_ast::expand::autodiff_attrs::{DiffActivity, DiffMode}; -use rustc_feature::{AttributeTemplate, template}; +use rustc_feature::{AttributeStability, AttributeTemplate, template}; use rustc_hir::attrs::{AttributeKind, RustcAutodiff}; use rustc_hir::{MethodKind, Target}; use rustc_span::{Symbol, sym}; @@ -13,6 +13,7 @@ use crate::attributes::prelude::Allow; use crate::context::AcceptContext; use crate::parser::{ArgParser, MetaItemOrLitParser}; use crate::target_checking::AllowedTargets; +use crate::unstable; pub(crate) struct RustcAutodiffParser; @@ -29,6 +30,7 @@ impl SingleAttributeParser for RustcAutodiffParser { List: &["MODE", "WIDTH", "INPUT_ACTIVITIES", "OUTPUT_ACTIVITY"], "https://doc.rust-lang.org/std/autodiff/index.html" ); + const STABILITY: AttributeStability = unstable!(rustc_attrs); fn convert(cx: &mut AcceptContext<'_, '_>, args: &ArgParser) -> Option { let list = match args { diff --git a/compiler/rustc_attr_parsing/src/attributes/body.rs b/compiler/rustc_attr_parsing/src/attributes/body.rs index ff1c78bc10881..b817d9346dafc 100644 --- a/compiler/rustc_attr_parsing/src/attributes/body.rs +++ b/compiler/rustc_attr_parsing/src/attributes/body.rs @@ -1,5 +1,7 @@ //! Attributes that can be found in function body. +use rustc_feature::AttributeStability; + use super::prelude::*; pub(crate) struct CoroutineParser; @@ -7,5 +9,6 @@ pub(crate) struct CoroutineParser; impl NoArgsAttributeParser for CoroutineParser { const PATH: &[Symbol] = &[sym::coroutine]; const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::Closure)]); + const STABILITY: AttributeStability = unstable!(coroutines); const CREATE: fn(rustc_span::Span) -> AttributeKind = |_| AttributeKind::Coroutine; } diff --git a/compiler/rustc_attr_parsing/src/attributes/cfi_encoding.rs b/compiler/rustc_attr_parsing/src/attributes/cfi_encoding.rs index 0f6b5ee8ad9d8..2e7644fc70551 100644 --- a/compiler/rustc_attr_parsing/src/attributes/cfi_encoding.rs +++ b/compiler/rustc_attr_parsing/src/attributes/cfi_encoding.rs @@ -1,3 +1,5 @@ +use rustc_feature::AttributeStability; + use super::prelude::*; pub(crate) struct CfiEncodingParser; impl SingleAttributeParser for CfiEncodingParser { @@ -9,6 +11,7 @@ impl SingleAttributeParser for CfiEncodingParser { Allow(Target::Union), ]); const TEMPLATE: AttributeTemplate = template!(NameValueStr: "encoding"); + const STABILITY: AttributeStability = unstable!(cfi_encoding); fn convert(cx: &mut AcceptContext<'_, '_>, args: &ArgParser) -> Option { let name_value = cx.expect_name_value(args, cx.attr_span, Some(sym::cfi_encoding))?; diff --git a/compiler/rustc_attr_parsing/src/attributes/codegen_attrs.rs b/compiler/rustc_attr_parsing/src/attributes/codegen_attrs.rs index 41abb4806567f..3a748b40b49d7 100644 --- a/compiler/rustc_attr_parsing/src/attributes/codegen_attrs.rs +++ b/compiler/rustc_attr_parsing/src/attributes/codegen_attrs.rs @@ -1,3 +1,4 @@ +use rustc_feature::AttributeStability; use rustc_hir::attrs::{CoverageAttrKind, OptimizeAttr, RtsanSetting, SanitizerSet, UsedBy}; use rustc_session::errors::feature_err; use rustc_span::edition::Edition::Edition2024; @@ -22,6 +23,7 @@ impl SingleAttributeParser for OptimizeParser { Allow(Target::Method(MethodKind::Inherent)), ]); const TEMPLATE: AttributeTemplate = template!(List: &["size", "speed", "none"]); + const STABILITY: AttributeStability = unstable!(optimize_attribute); fn convert(cx: &mut AcceptContext<'_, '_>, args: &ArgParser) -> Option { let single = cx.expect_single_element_list(args, cx.attr_span)?; @@ -54,6 +56,7 @@ impl NoArgsAttributeParser for ColdParser { Allow(Target::ForeignFn), Allow(Target::Closure), ]); + const STABILITY: AttributeStability = AttributeStability::Stable; const CREATE: fn(Span) -> AttributeKind = |_| AttributeKind::Cold; } @@ -73,6 +76,7 @@ impl SingleAttributeParser for CoverageParser { Allow(Target::Crate), ]); const TEMPLATE: AttributeTemplate = template!(OneOf: &[sym::off, sym::on]); + const STABILITY: AttributeStability = unstable!(coverage_attribute); fn convert(cx: &mut AcceptContext<'_, '_>, args: &ArgParser) -> Option { let arg = cx.expect_single_element_list(args, cx.attr_span)?; @@ -116,6 +120,7 @@ impl SingleAttributeParser for ExportNameParser { Warn(Target::MacroCall), ]); const TEMPLATE: AttributeTemplate = template!(NameValueStr: "name"); + const STABILITY: AttributeStability = AttributeStability::Stable; fn convert(cx: &mut AcceptContext<'_, '_>, args: &ArgParser) -> Option { let nv = cx.expect_name_value(args, cx.attr_span, None)?; @@ -143,6 +148,7 @@ impl SingleAttributeParser for RustcObjcClassParser { const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::ForeignStatic)]); const TEMPLATE: AttributeTemplate = template!(NameValueStr: "ClassName"); + const STABILITY: AttributeStability = unstable!(rustc_attrs); fn convert(cx: &mut AcceptContext<'_, '_>, args: &ArgParser) -> Option { let nv = cx.expect_name_value(args, cx.attr_span, None)?; @@ -170,6 +176,7 @@ impl SingleAttributeParser for RustcObjcSelectorParser { const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::ForeignStatic)]); const TEMPLATE: AttributeTemplate = template!(NameValueStr: "methodName"); + const STABILITY: AttributeStability = unstable!(rustc_attrs); fn convert(cx: &mut AcceptContext<'_, '_>, args: &ArgParser) -> Option { let nv = cx.expect_name_value(args, cx.attr_span, None)?; @@ -197,7 +204,7 @@ pub(crate) struct NakedParser { impl AttributeParser for NakedParser { const ATTRIBUTES: AcceptMapping = - &[(&[sym::naked], template!(Word), |this, cx, args| { + &[(&[sym::naked], template!(Word), AttributeStability::Stable, |this, cx, args| { let Some(()) = cx.expect_no_args(args) else { return; }; @@ -331,6 +338,7 @@ impl NoArgsAttributeParser for TrackCallerParser { Warn(Target::Field), Warn(Target::MacroCall), ]); + const STABILITY: AttributeStability = AttributeStability::Stable; const CREATE: fn(Span) -> AttributeKind = AttributeKind::TrackCaller; } @@ -347,6 +355,7 @@ impl NoArgsAttributeParser for NoMangleParser { AllowSilent(Target::Const), // Handled in the `InvalidNoMangleItems` pass Error(Target::Closure), ]); + const STABILITY: AttributeStability = AttributeStability::Stable; const CREATE: fn(Span) -> AttributeKind = AttributeKind::NoMangle; } @@ -365,6 +374,7 @@ impl AttributeParser for UsedParser { const ATTRIBUTES: AcceptMapping = &[( &[sym::used], template!(Word, List: &["compiler", "linker"]), + AttributeStability::Stable, |group: &mut Self, cx, args| { let used_by = match args { ArgParser::NoArgs => UsedBy::Default, @@ -502,6 +512,7 @@ impl CombineAttributeParser for TargetFeatureParser { was_forced: false, }; const TEMPLATE: AttributeTemplate = template!(List: &["enable = \"feat1, feat2\""]); + const STABILITY: AttributeStability = AttributeStability::Stable; fn extend( cx: &mut AcceptContext<'_, '_>, @@ -541,6 +552,7 @@ impl CombineAttributeParser for ForceTargetFeatureParser { Allow(Target::Method(MethodKind::Trait { body: true })), Allow(Target::Method(MethodKind::TraitImpl)), ]); + const STABILITY: AttributeStability = unstable!(effective_target_features); fn extend( cx: &mut AcceptContext<'_, '_>, @@ -554,10 +566,8 @@ pub(crate) struct SanitizeParser; impl SingleAttributeParser for SanitizeParser { const PATH: &[Symbol] = &[sym::sanitize]; - // FIXME: still checked in check_attrs.rs const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(ALL_TARGETS); - const TEMPLATE: AttributeTemplate = template!(List: &[ r#"address = "on|off""#, r#"kernel_address = "on|off""#, @@ -571,6 +581,7 @@ impl SingleAttributeParser for SanitizeParser { r#"thread = "on|off""#, r#"realtime = "nonblocking|blocking|caller""#, ]); + const STABILITY: AttributeStability = unstable!(sanitize); fn convert(cx: &mut AcceptContext<'_, '_>, args: &ArgParser) -> Option { let list = cx.expect_list(args, cx.attr_span)?; @@ -667,6 +678,7 @@ impl NoArgsAttributeParser for ThreadLocalParser { const PATH: &[Symbol] = &[sym::thread_local]; const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::Static), Allow(Target::ForeignStatic)]); + const STABILITY: AttributeStability = unstable!(thread_local); const CREATE: fn(Span) -> AttributeKind = |_| AttributeKind::ThreadLocal; } @@ -675,6 +687,7 @@ pub(crate) struct RustcPassIndirectlyInNonRusticAbisParser; impl NoArgsAttributeParser for RustcPassIndirectlyInNonRusticAbisParser { const PATH: &[Symbol] = &[sym::rustc_pass_indirectly_in_non_rustic_abis]; const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::Struct)]); + const STABILITY: AttributeStability = unstable!(rustc_attrs); const CREATE: fn(Span) -> AttributeKind = AttributeKind::RustcPassIndirectlyInNonRusticAbis; } @@ -684,6 +697,7 @@ impl NoArgsAttributeParser for RustcEiiForeignItemParser { const PATH: &[Symbol] = &[sym::rustc_eii_foreign_item]; const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::ForeignFn), Allow(Target::ForeignStatic)]); + const STABILITY: AttributeStability = unstable!(eii_internals); const CREATE: fn(Span) -> AttributeKind = |_| AttributeKind::RustcEiiForeignItem; } @@ -693,6 +707,7 @@ impl SingleAttributeParser for PatchableFunctionEntryParser { const PATH: &[Symbol] = &[sym::patchable_function_entry]; const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::Fn)]); const TEMPLATE: AttributeTemplate = template!(List: &["prefix_nops = m, entry_nops = n"]); + const STABILITY: AttributeStability = unstable!(patchable_function_entry); fn convert(cx: &mut AcceptContext<'_, '_>, args: &ArgParser) -> Option { let meta_item_list = cx.expect_list(args, cx.attr_span)?; diff --git a/compiler/rustc_attr_parsing/src/attributes/confusables.rs b/compiler/rustc_attr_parsing/src/attributes/confusables.rs index 311fc0d33c1f8..3b2115eeb01e2 100644 --- a/compiler/rustc_attr_parsing/src/attributes/confusables.rs +++ b/compiler/rustc_attr_parsing/src/attributes/confusables.rs @@ -1,3 +1,5 @@ +use rustc_feature::AttributeStability; + use super::prelude::*; use crate::session_diagnostics::EmptyConfusables; @@ -11,6 +13,7 @@ impl AttributeParser for ConfusablesParser { const ATTRIBUTES: AcceptMapping = &[( &[sym::rustc_confusables], template!(List: &[r#""name1", "name2", ..."#]), + unstable!(rustc_attrs), |this, cx, args| { let Some(list) = cx.expect_list(args, cx.attr_span) else { return }; diff --git a/compiler/rustc_attr_parsing/src/attributes/crate_level.rs b/compiler/rustc_attr_parsing/src/attributes/crate_level.rs index 9d6887a338739..c7bf465bfb7a9 100644 --- a/compiler/rustc_attr_parsing/src/attributes/crate_level.rs +++ b/compiler/rustc_attr_parsing/src/attributes/crate_level.rs @@ -1,3 +1,4 @@ +use rustc_feature::AttributeStability; use rustc_hir::attrs::{CrateType, WindowsSubsystemKind}; use rustc_session::lint::builtin::UNKNOWN_CRATE_TYPES; use rustc_span::Symbol; @@ -13,6 +14,7 @@ impl SingleAttributeParser for CrateNameParser { const ON_DUPLICATE: OnDuplicate = OnDuplicate::WarnButFutureError; const TEMPLATE: AttributeTemplate = template!(NameValueStr: "name"); const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::Crate)]); + const STABILITY: AttributeStability = AttributeStability::Stable; fn convert(cx: &mut AcceptContext<'_, '_>, args: &ArgParser) -> Option { let n = cx.expect_name_value(args, cx.attr_span, None)?; @@ -29,11 +31,10 @@ impl CombineAttributeParser for CrateTypeParser { const PATH: &[Symbol] = &[sym::crate_type]; type Item = CrateType; const CONVERT: ConvertFn = |items, _| AttributeKind::CrateType(items); - const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::Crate)]); - const TEMPLATE: AttributeTemplate = template!(NameValueStr: "crate type", "https://doc.rust-lang.org/reference/linkage.html"); + const STABILITY: AttributeStability = AttributeStability::Stable; fn extend( cx: &mut AcceptContext<'_, '_>, @@ -74,6 +75,7 @@ impl SingleAttributeParser for RecursionLimitParser { const ON_DUPLICATE: OnDuplicate = OnDuplicate::WarnButFutureError; const TEMPLATE: AttributeTemplate = template!(NameValueStr: "N", "https://doc.rust-lang.org/reference/attributes/limits.html#the-recursion_limit-attribute"); const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::Crate)]); + const STABILITY: AttributeStability = AttributeStability::Stable; fn convert(cx: &mut AcceptContext<'_, '_>, args: &ArgParser) -> Option { let nv = cx.expect_name_value(args, cx.attr_span, None)?; @@ -88,6 +90,7 @@ impl SingleAttributeParser for MoveSizeLimitParser { const PATH: &[Symbol] = &[sym::move_size_limit]; const TEMPLATE: AttributeTemplate = template!(NameValueStr: "N"); const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::Crate)]); + const STABILITY: AttributeStability = unstable!(large_assignments); fn convert(cx: &mut AcceptContext<'_, '_>, args: &ArgParser) -> Option { let nv = cx.expect_name_value(args, cx.attr_span, None)?; @@ -103,6 +106,7 @@ impl SingleAttributeParser for TypeLengthLimitParser { const ON_DUPLICATE: OnDuplicate = OnDuplicate::WarnButFutureError; const TEMPLATE: AttributeTemplate = template!(NameValueStr: "N"); const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::Crate)]); + const STABILITY: AttributeStability = AttributeStability::Stable; fn convert(cx: &mut AcceptContext<'_, '_>, args: &ArgParser) -> Option { let nv = cx.expect_name_value(args, cx.attr_span, None)?; @@ -117,6 +121,10 @@ impl SingleAttributeParser for PatternComplexityLimitParser { const PATH: &[Symbol] = &[sym::pattern_complexity_limit]; const TEMPLATE: AttributeTemplate = template!(NameValueStr: "N"); const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::Crate)]); + const STABILITY: AttributeStability = unstable!( + rustc_attrs, + "the `#[pattern_complexity_limit]` attribute is used for rustc unit tests" + ); fn convert(cx: &mut AcceptContext<'_, '_>, args: &ArgParser) -> Option { let nv = cx.expect_name_value(args, cx.attr_span, None)?; @@ -130,6 +138,7 @@ pub(crate) struct NoCoreParser; impl NoArgsAttributeParser for NoCoreParser { const PATH: &[Symbol] = &[sym::no_core]; const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::Crate)]); + const STABILITY: AttributeStability = unstable!(no_core); const CREATE: fn(Span) -> AttributeKind = |_| AttributeKind::NoCore; } @@ -139,6 +148,7 @@ impl NoArgsAttributeParser for NoStdParser { const PATH: &[Symbol] = &[sym::no_std]; const ON_DUPLICATE: OnDuplicate = OnDuplicate::Warn; const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::Crate)]); + const STABILITY: AttributeStability = AttributeStability::Stable; const CREATE: fn(Span) -> AttributeKind = |_| AttributeKind::NoStd; } @@ -148,6 +158,7 @@ impl NoArgsAttributeParser for NoMainParser { const PATH: &[Symbol] = &[sym::no_main]; const ON_DUPLICATE: OnDuplicate = OnDuplicate::Warn; const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::Crate)]); + const STABILITY: AttributeStability = AttributeStability::Stable; const CREATE: fn(Span) -> AttributeKind = |_| AttributeKind::NoMain; } @@ -156,6 +167,7 @@ pub(crate) struct RustcCoherenceIsCoreParser; impl NoArgsAttributeParser for RustcCoherenceIsCoreParser { const PATH: &[Symbol] = &[sym::rustc_coherence_is_core]; const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::Crate)]); + const STABILITY: AttributeStability = unstable!(rustc_attrs); const CREATE: fn(Span) -> AttributeKind = |_| AttributeKind::RustcCoherenceIsCore; } @@ -166,6 +178,7 @@ impl SingleAttributeParser for WindowsSubsystemParser { const ON_DUPLICATE: OnDuplicate = OnDuplicate::WarnButFutureError; const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::Crate)]); const TEMPLATE: AttributeTemplate = template!(NameValueStr: ["windows", "console"], "https://doc.rust-lang.org/reference/runtime.html#the-windows_subsystem-attribute"); + const STABILITY: AttributeStability = AttributeStability::Stable; fn convert(cx: &mut AcceptContext<'_, '_>, args: &ArgParser) -> Option { let nv = cx.expect_name_value(args, cx.inner_span, Some(sym::windows_subsystem))?; @@ -191,6 +204,7 @@ pub(crate) struct PanicRuntimeParser; impl NoArgsAttributeParser for PanicRuntimeParser { const PATH: &[Symbol] = &[sym::panic_runtime]; const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::Crate)]); + const STABILITY: AttributeStability = unstable!(panic_runtime); const CREATE: fn(Span) -> AttributeKind = |_| AttributeKind::PanicRuntime; } @@ -199,6 +213,7 @@ pub(crate) struct NeedsPanicRuntimeParser; impl NoArgsAttributeParser for NeedsPanicRuntimeParser { const PATH: &[Symbol] = &[sym::needs_panic_runtime]; const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::Crate)]); + const STABILITY: AttributeStability = unstable!(needs_panic_runtime); const CREATE: fn(Span) -> AttributeKind = |_| AttributeKind::NeedsPanicRuntime; } @@ -207,6 +222,7 @@ pub(crate) struct ProfilerRuntimeParser; impl NoArgsAttributeParser for ProfilerRuntimeParser { const PATH: &[Symbol] = &[sym::profiler_runtime]; const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::Crate)]); + const STABILITY: AttributeStability = unstable!(profiler_runtime); const CREATE: fn(Span) -> AttributeKind = |_| AttributeKind::ProfilerRuntime; } @@ -216,6 +232,7 @@ impl NoArgsAttributeParser for NoBuiltinsParser { const PATH: &[Symbol] = &[sym::no_builtins]; const ON_DUPLICATE: OnDuplicate = OnDuplicate::Warn; const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::Crate)]); + const STABILITY: AttributeStability = AttributeStability::Stable; const CREATE: fn(Span) -> AttributeKind = |_| AttributeKind::NoBuiltins; } @@ -224,6 +241,7 @@ pub(crate) struct RustcPreserveUbChecksParser; impl NoArgsAttributeParser for RustcPreserveUbChecksParser { const PATH: &[Symbol] = &[sym::rustc_preserve_ub_checks]; const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::Crate)]); + const STABILITY: AttributeStability = unstable!(rustc_attrs); const CREATE: fn(Span) -> AttributeKind = |_| AttributeKind::RustcPreserveUbChecks; } @@ -232,6 +250,7 @@ pub(crate) struct RustcNoImplicitBoundsParser; impl NoArgsAttributeParser for RustcNoImplicitBoundsParser { const PATH: &[Symbol] = &[sym::rustc_no_implicit_bounds]; const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::Crate)]); + const STABILITY: AttributeStability = unstable!(rustc_attrs); const CREATE: fn(Span) -> AttributeKind = |_| AttributeKind::RustcNoImplicitBounds; } @@ -240,6 +259,7 @@ pub(crate) struct DefaultLibAllocatorParser; impl NoArgsAttributeParser for DefaultLibAllocatorParser { const PATH: &[Symbol] = &[sym::default_lib_allocator]; const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::Crate)]); + const STABILITY: AttributeStability = unstable!(allocator_internals); const CREATE: fn(Span) -> AttributeKind = |_| AttributeKind::DefaultLibAllocator; } @@ -251,6 +271,7 @@ impl CombineAttributeParser for FeatureParser { const CONVERT: ConvertFn = AttributeKind::Feature; const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::Crate)]); const TEMPLATE: AttributeTemplate = template!(List: &["feature1, feature2, ..."]); + const STABILITY: AttributeStability = AttributeStability::Stable; fn extend( cx: &mut AcceptContext<'_, '_>, @@ -295,6 +316,7 @@ impl CombineAttributeParser for RegisterToolParser { const CONVERT: ConvertFn = |tools, _span| AttributeKind::RegisterTool(tools); const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(ALL_TARGETS); const TEMPLATE: AttributeTemplate = template!(List: &["tool1, tool2, ..."]); + const STABILITY: AttributeStability = unstable!(register_tool); fn extend( cx: &mut AcceptContext<'_, '_>, diff --git a/compiler/rustc_attr_parsing/src/attributes/debugger.rs b/compiler/rustc_attr_parsing/src/attributes/debugger.rs index 0fc4166a178e4..0afda3aa052aa 100644 --- a/compiler/rustc_attr_parsing/src/attributes/debugger.rs +++ b/compiler/rustc_attr_parsing/src/attributes/debugger.rs @@ -1,3 +1,4 @@ +use rustc_feature::AttributeStability; use rustc_hir::attrs::{DebugVisualizer, DebuggerVisualizerType}; use super::prelude::*; @@ -12,6 +13,7 @@ impl CombineAttributeParser for DebuggerViualizerParser { List: &[r#"natvis_file = "...", gdb_script_file = "...""#], "https://doc.rust-lang.org/reference/attributes/debugger.html#the-debugger_visualizer-attribute" ); + const STABILITY: AttributeStability = AttributeStability::Stable; type Item = DebugVisualizer; const CONVERT: ConvertFn = |v, _| AttributeKind::DebuggerVisualizer(v); diff --git a/compiler/rustc_attr_parsing/src/attributes/deprecation.rs b/compiler/rustc_attr_parsing/src/attributes/deprecation.rs index e0adbb0900deb..ee3734bf19541 100644 --- a/compiler/rustc_attr_parsing/src/attributes/deprecation.rs +++ b/compiler/rustc_attr_parsing/src/attributes/deprecation.rs @@ -1,4 +1,5 @@ use rustc_ast::LitKind; +use rustc_feature::AttributeStability; use rustc_hir::attrs::{DeprecatedSince, Deprecation}; use rustc_hir::{RustcVersion, VERSION_PLACEHOLDER}; @@ -62,6 +63,7 @@ impl SingleAttributeParser for DeprecatedParser { List: &[r#"since = "version""#, r#"note = "reason""#, r#"since = "version", note = "reason""#], NameValueStr: "reason" ); + const STABILITY: AttributeStability = AttributeStability::Stable; fn convert(cx: &mut AcceptContext<'_, '_>, args: &ArgParser) -> Option { let features = cx.features(); diff --git a/compiler/rustc_attr_parsing/src/attributes/diagnostic/do_not_recommend.rs b/compiler/rustc_attr_parsing/src/attributes/diagnostic/do_not_recommend.rs index 1d4bcb63980ce..4cb1f202bc525 100644 --- a/compiler/rustc_attr_parsing/src/attributes/diagnostic/do_not_recommend.rs +++ b/compiler/rustc_attr_parsing/src/attributes/diagnostic/do_not_recommend.rs @@ -1,4 +1,4 @@ -use rustc_feature::{AttributeTemplate, template}; +use rustc_feature::{AttributeStability, AttributeTemplate, template}; use rustc_hir::Target; use rustc_hir::attrs::AttributeKind; use rustc_session::lint::builtin::{ @@ -19,6 +19,7 @@ impl SingleAttributeParser for DoNotRecommendParser { // "Allowed" on any target, noop on all but trait impls const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(ALL_TARGETS); const TEMPLATE: AttributeTemplate = template!(Word /*doesn't matter */); + const STABILITY: AttributeStability = AttributeStability::Stable; fn convert(cx: &mut AcceptContext<'_, '_>, args: &ArgParser) -> Option { let attr_span = cx.attr_span; diff --git a/compiler/rustc_attr_parsing/src/attributes/diagnostic/on_const.rs b/compiler/rustc_attr_parsing/src/attributes/diagnostic/on_const.rs index e783e49ef1e83..9670eb851b94a 100644 --- a/compiler/rustc_attr_parsing/src/attributes/diagnostic/on_const.rs +++ b/compiler/rustc_attr_parsing/src/attributes/diagnostic/on_const.rs @@ -1,3 +1,4 @@ +use rustc_feature::AttributeStability; use rustc_hir::attrs::diagnostic::Directive; use rustc_session::lint::builtin::MISPLACED_DIAGNOSTIC_ATTRIBUTES; @@ -14,6 +15,7 @@ impl AttributeParser for OnConstParser { const ATTRIBUTES: AcceptMapping = &[( &[sym::diagnostic, sym::on_const], template!(List: &[r#"/*opt*/ message = "...", /*opt*/ label = "...", /*opt*/ note = "...""#]), + AttributeStability::Stable, // Unstable, stability checked manually in the parser |this, cx, args| { if !cx.features().diagnostic_on_const() { // `UnknownDiagnosticAttribute` is emitted in rustc_resolve/macros.rs diff --git a/compiler/rustc_attr_parsing/src/attributes/diagnostic/on_move.rs b/compiler/rustc_attr_parsing/src/attributes/diagnostic/on_move.rs index b3c6c93a480ee..bacb1da2b87bd 100644 --- a/compiler/rustc_attr_parsing/src/attributes/diagnostic/on_move.rs +++ b/compiler/rustc_attr_parsing/src/attributes/diagnostic/on_move.rs @@ -1,4 +1,4 @@ -use rustc_feature::template; +use rustc_feature::{AttributeStability, template}; use rustc_hir::attrs::AttributeKind; use rustc_session::lint::builtin::MISPLACED_DIAGNOSTIC_ATTRIBUTES; use rustc_span::sym; @@ -42,6 +42,7 @@ impl AttributeParser for OnMoveParser { const ATTRIBUTES: AcceptMapping = &[( &[sym::diagnostic, sym::on_move], template!(List: &[r#"/*opt*/ message = "...", /*opt*/ label = "...", /*opt*/ note = "...""#]), + AttributeStability::Stable, // Unstable, stability checked manually in the parser |this, cx, args| { this.parse(cx, args, Mode::DiagnosticOnMove); }, diff --git a/compiler/rustc_attr_parsing/src/attributes/diagnostic/on_unimplemented.rs b/compiler/rustc_attr_parsing/src/attributes/diagnostic/on_unimplemented.rs index 82876c41de605..6fe5de88edb8c 100644 --- a/compiler/rustc_attr_parsing/src/attributes/diagnostic/on_unimplemented.rs +++ b/compiler/rustc_attr_parsing/src/attributes/diagnostic/on_unimplemented.rs @@ -1,3 +1,4 @@ +use rustc_feature::AttributeStability; use rustc_hir::attrs::diagnostic::Directive; use rustc_session::lint::builtin::MISPLACED_DIAGNOSTIC_ATTRIBUTES; @@ -38,6 +39,7 @@ impl AttributeParser for OnUnimplementedParser { ( &[sym::diagnostic, sym::on_unimplemented], template!(List: &[r#"/*opt*/ message = "...", /*opt*/ label = "...", /*opt*/ note = "...""#]), + AttributeStability::Stable, |this, cx, args| { this.parse(cx, args, Mode::DiagnosticOnUnimplemented); }, @@ -45,6 +47,10 @@ impl AttributeParser for OnUnimplementedParser { ( &[sym::rustc_on_unimplemented], template!(List: &[r#"/*opt*/ message = "...", /*opt*/ label = "...", /*opt*/ note = "...""#]), + unstable!( + rustc_attrs, + "see `#[diagnostic::on_unimplemented]` for the stable equivalent of this attribute" + ), |this, cx, args| { this.parse(cx, args, Mode::RustcOnUnimplemented); }, diff --git a/compiler/rustc_attr_parsing/src/attributes/diagnostic/on_unknown.rs b/compiler/rustc_attr_parsing/src/attributes/diagnostic/on_unknown.rs index 4c327804dd5b9..8283aa277857a 100644 --- a/compiler/rustc_attr_parsing/src/attributes/diagnostic/on_unknown.rs +++ b/compiler/rustc_attr_parsing/src/attributes/diagnostic/on_unknown.rs @@ -1,3 +1,4 @@ +use rustc_feature::AttributeStability; use rustc_hir::attrs::diagnostic::Directive; use rustc_session::lint::builtin::MISPLACED_DIAGNOSTIC_ATTRIBUTES; @@ -49,6 +50,7 @@ impl AttributeParser for OnUnknownParser { const ATTRIBUTES: AcceptMapping = &[( &[sym::diagnostic, sym::on_unknown], template!(List: &[r#"/*opt*/ message = "...", /*opt*/ label = "...", /*opt*/ note = "...""#]), + AttributeStability::Stable, // Unstable, stability checked manually in the parser |this, cx, args| { this.parse(cx, args, Mode::DiagnosticOnUnknown); }, diff --git a/compiler/rustc_attr_parsing/src/attributes/diagnostic/on_unmatch_args.rs b/compiler/rustc_attr_parsing/src/attributes/diagnostic/on_unmatch_args.rs index a3204e6599cf1..b941ee1107ca8 100644 --- a/compiler/rustc_attr_parsing/src/attributes/diagnostic/on_unmatch_args.rs +++ b/compiler/rustc_attr_parsing/src/attributes/diagnostic/on_unmatch_args.rs @@ -1,3 +1,4 @@ +use rustc_feature::AttributeStability; use rustc_hir::attrs::diagnostic::Directive; use rustc_session::lint::builtin::MISPLACED_DIAGNOSTIC_ATTRIBUTES; @@ -15,6 +16,7 @@ impl AttributeParser for OnUnmatchArgsParser { const ATTRIBUTES: AcceptMapping = &[( &[sym::diagnostic, sym::on_unmatch_args], template!(List: &[r#"/*opt*/ message = "...", /*opt*/ label = "...", /*opt*/ note = "...""#]), + AttributeStability::Stable, // Unstable, stability checked manually in the parser |this, cx, args| { if !cx.features().diagnostic_on_unmatch_args() { return; diff --git a/compiler/rustc_attr_parsing/src/attributes/doc.rs b/compiler/rustc_attr_parsing/src/attributes/doc.rs index 535f67f67a263..b776ca0e68951 100644 --- a/compiler/rustc_attr_parsing/src/attributes/doc.rs +++ b/compiler/rustc_attr_parsing/src/attributes/doc.rs @@ -1,6 +1,6 @@ use rustc_ast::ast::{AttrStyle, LitKind, MetaItemLit}; use rustc_errors::{Applicability, msg}; -use rustc_feature::template; +use rustc_feature::{AttributeStability, template}; use rustc_hir::Target; use rustc_hir::attrs::{ AttributeKind, CfgEntry, CfgHideShow, CfgInfo, DocAttribute, DocInline, HideOrShow, @@ -702,6 +702,7 @@ impl AttributeParser for DocParser { ], NameValueStr: "string" ), + AttributeStability::Stable, // Some parts of the attribute are unstable, manually checked in parser |this, cx, args| { this.accept_single_doc_attr(cx, args); }, diff --git a/compiler/rustc_attr_parsing/src/attributes/dummy.rs b/compiler/rustc_attr_parsing/src/attributes/dummy.rs index 622f7e1ceba49..0956cb01800ba 100644 --- a/compiler/rustc_attr_parsing/src/attributes/dummy.rs +++ b/compiler/rustc_attr_parsing/src/attributes/dummy.rs @@ -1,4 +1,4 @@ -use rustc_feature::{AttributeTemplate, template}; +use rustc_feature::{AttributeStability, AttributeTemplate, template}; use rustc_hir::attrs::AttributeKind; use rustc_span::{Symbol, sym}; @@ -6,6 +6,7 @@ use crate::attributes::{OnDuplicate, SingleAttributeParser}; use crate::context::AcceptContext; use crate::parser::ArgParser; use crate::target_checking::{ALL_TARGETS, AllowedTargets}; +use crate::unstable; pub(crate) struct RustcDummyParser; impl SingleAttributeParser for RustcDummyParser { @@ -13,6 +14,8 @@ impl SingleAttributeParser for RustcDummyParser { const ON_DUPLICATE: OnDuplicate = OnDuplicate::Ignore; const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(ALL_TARGETS); const TEMPLATE: AttributeTemplate = template!(Word); // Anything, really + const STABILITY: AttributeStability = + unstable!(rustc_attrs, "the `#[rustc_dummy]` attribute is used for rustc unit tests"); fn convert(_: &mut AcceptContext<'_, '_>, _: &ArgParser) -> Option { Some(AttributeKind::RustcDummy) diff --git a/compiler/rustc_attr_parsing/src/attributes/inline.rs b/compiler/rustc_attr_parsing/src/attributes/inline.rs index aee0537771fd4..aa23482c0f989 100644 --- a/compiler/rustc_attr_parsing/src/attributes/inline.rs +++ b/compiler/rustc_attr_parsing/src/attributes/inline.rs @@ -2,6 +2,7 @@ // note: need to model better how duplicate attr errors work when not using // SingleAttributeParser which is what we have two of here. +use rustc_feature::AttributeStability; use rustc_hir::attrs::{AttributeKind, InlineAttr}; use rustc_session::lint::builtin::ILL_FORMED_ATTRIBUTE_INPUT; @@ -32,6 +33,7 @@ impl SingleAttributeParser for InlineParser { List: &["always", "never"], "https://doc.rust-lang.org/reference/attributes/codegen.html#the-inline-attribute" ); + const STABILITY: AttributeStability = AttributeStability::Stable; fn convert(cx: &mut AcceptContext<'_, '_>, args: &ArgParser) -> Option { match args { @@ -68,7 +70,8 @@ impl SingleAttributeParser for RustcForceInlineParser { Allow(Target::Fn), Allow(Target::Method(MethodKind::Inherent)), ]); - + const STABILITY: AttributeStability = + unstable!(rustc_attrs, "`#[rustc_force_inline]` forces a free function to be inlined"); const TEMPLATE: AttributeTemplate = template!(Word, List: &["reason"], NameValueStr: "reason"); fn convert(cx: &mut AcceptContext<'_, '_>, args: &ArgParser) -> Option { diff --git a/compiler/rustc_attr_parsing/src/attributes/instruction_set.rs b/compiler/rustc_attr_parsing/src/attributes/instruction_set.rs index 6f239a8f5761d..50b1c3f813071 100644 --- a/compiler/rustc_attr_parsing/src/attributes/instruction_set.rs +++ b/compiler/rustc_attr_parsing/src/attributes/instruction_set.rs @@ -1,3 +1,4 @@ +use rustc_feature::AttributeStability; use rustc_hir::attrs::InstructionSetAttr; use super::prelude::*; @@ -15,6 +16,7 @@ impl SingleAttributeParser for InstructionSetParser { Allow(Target::Method(MethodKind::Trait { body: true })), ]); const TEMPLATE: AttributeTemplate = template!(List: &["set"], "https://doc.rust-lang.org/reference/attributes/codegen.html#the-instruction_set-attribute"); + const STABILITY: AttributeStability = AttributeStability::Stable; fn convert(cx: &mut AcceptContext<'_, '_>, args: &ArgParser) -> Option { const POSSIBLE_SYMBOLS: &[Symbol] = &[sym::arm_a32, sym::arm_t32]; diff --git a/compiler/rustc_attr_parsing/src/attributes/link_attrs.rs b/compiler/rustc_attr_parsing/src/attributes/link_attrs.rs index ec084a9fba09c..24798d94c56e2 100644 --- a/compiler/rustc_attr_parsing/src/attributes/link_attrs.rs +++ b/compiler/rustc_attr_parsing/src/attributes/link_attrs.rs @@ -1,5 +1,5 @@ use rustc_errors::msg; -use rustc_feature::Features; +use rustc_feature::{AttributeStability, Features}; use rustc_hir::attrs::AttributeKind::{LinkName, LinkOrdinal, LinkSection}; use rustc_hir::attrs::*; use rustc_session::Session; @@ -34,6 +34,7 @@ impl SingleAttributeParser for LinkNameParser { NameValueStr: "name", "https://doc.rust-lang.org/reference/items/external-blocks.html#the-link_name-attribute" ); + const STABILITY: AttributeStability = AttributeStability::Stable; fn convert(cx: &mut AcceptContext<'_, '_>, args: &ArgParser) -> Option { let nv = cx.expect_name_value(args, cx.attr_span, None)?; @@ -70,6 +71,7 @@ impl CombineAttributeParser for LinkParser { r#"name = "...", kind = "dylib|static|...", wasm_import_module = "...", import_name_type = "decorated|noprefix|undecorated""#, ], "https://doc.rust-lang.org/reference/items/external-blocks.html#the-link-attribute"); const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(ALL_TARGETS); //FIXME Still checked fully in `check_attr.rs` + const STABILITY: AttributeStability = AttributeStability::Stable; fn extend( cx: &mut AcceptContext<'_, '_>, @@ -487,6 +489,7 @@ impl SingleAttributeParser for LinkSectionParser { const PATH: &[Symbol] = &[sym::link_section]; const ON_DUPLICATE: OnDuplicate = OnDuplicate::WarnButFutureError; const SAFETY: AttributeSafety = AttributeSafety::Unsafe { unsafe_since: Some(Edition2024) }; + const STABILITY: AttributeStability = AttributeStability::Stable; const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowListWarnRest(&[ Allow(Target::Static), Allow(Target::Fn), @@ -529,6 +532,7 @@ pub(crate) struct ExportStableParser; impl NoArgsAttributeParser for ExportStableParser { const PATH: &[Symbol] = &[sym::export_stable]; const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(ALL_TARGETS); //FIXME Still checked fully in `check_attr.rs` + const STABILITY: AttributeStability = unstable!(export_stable); const CREATE: fn(Span) -> AttributeKind = |_| AttributeKind::ExportStable; } @@ -537,6 +541,7 @@ impl NoArgsAttributeParser for FfiConstParser { const PATH: &[Symbol] = &[sym::ffi_const]; const SAFETY: AttributeSafety = AttributeSafety::Unsafe { unsafe_since: None }; const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::ForeignFn)]); + const STABILITY: AttributeStability = unstable!(ffi_const); const CREATE: fn(Span) -> AttributeKind = |_| AttributeKind::FfiConst; } @@ -545,6 +550,7 @@ impl NoArgsAttributeParser for FfiPureParser { const PATH: &[Symbol] = &[sym::ffi_pure]; const SAFETY: AttributeSafety = AttributeSafety::Unsafe { unsafe_since: None }; const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::ForeignFn)]); + const STABILITY: AttributeStability = unstable!(ffi_pure); const CREATE: fn(Span) -> AttributeKind = AttributeKind::FfiPure; } @@ -557,6 +563,7 @@ impl NoArgsAttributeParser for RustcStdInternalSymbolParser { Allow(Target::Static), Allow(Target::ForeignStatic), ]); + const STABILITY: AttributeStability = unstable!(rustc_attrs); const CREATE: fn(Span) -> AttributeKind = |_| AttributeKind::RustcStdInternalSymbol; } @@ -573,6 +580,7 @@ impl SingleAttributeParser for LinkOrdinalParser { List: &["ordinal"], "https://doc.rust-lang.org/reference/items/external-blocks.html#the-link_ordinal-attribute" ); + const STABILITY: AttributeStability = AttributeStability::Stable; fn convert(cx: &mut AcceptContext<'_, '_>, args: &ArgParser) -> Option { let ordinal = parse_single_integer(cx, args)?; @@ -603,7 +611,6 @@ pub(crate) struct LinkageParser; impl SingleAttributeParser for LinkageParser { const PATH: &[Symbol] = &[sym::linkage]; - const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[ Allow(Target::Fn), Allow(Target::Method(MethodKind::Inherent)), @@ -614,7 +621,6 @@ impl SingleAttributeParser for LinkageParser { Allow(Target::ForeignFn), Warn(Target::Method(MethodKind::Trait { body: false })), // Not inherited ]); - const TEMPLATE: AttributeTemplate = template!(NameValueStr: [ "available_externally", "common", @@ -626,6 +632,7 @@ impl SingleAttributeParser for LinkageParser { "weak", "weak_odr", ]); + const STABILITY: AttributeStability = unstable!(linkage); fn convert(cx: &mut AcceptContext<'_, '_>, args: &ArgParser) -> Option { let name_value = cx.expect_name_value(args, cx.attr_span, Some(sym::linkage))?; @@ -681,6 +688,7 @@ pub(crate) struct NeedsAllocatorParser; impl NoArgsAttributeParser for NeedsAllocatorParser { const PATH: &[Symbol] = &[sym::needs_allocator]; const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::Crate)]); + const STABILITY: AttributeStability = unstable!(allocator_internals); const CREATE: fn(Span) -> AttributeKind = |_| AttributeKind::NeedsAllocator; } @@ -689,5 +697,6 @@ pub(crate) struct CompilerBuiltinsParser; impl NoArgsAttributeParser for CompilerBuiltinsParser { const PATH: &[Symbol] = &[sym::compiler_builtins]; const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::Crate)]); + const STABILITY: AttributeStability = unstable!(compiler_builtins); const CREATE: fn(Span) -> AttributeKind = |_| AttributeKind::CompilerBuiltins; } diff --git a/compiler/rustc_attr_parsing/src/attributes/lint_helpers.rs b/compiler/rustc_attr_parsing/src/attributes/lint_helpers.rs index 3e01844f45185..75a07e868129e 100644 --- a/compiler/rustc_attr_parsing/src/attributes/lint_helpers.rs +++ b/compiler/rustc_attr_parsing/src/attributes/lint_helpers.rs @@ -1,3 +1,5 @@ +use rustc_feature::AttributeStability; + use super::prelude::*; pub(crate) struct RustcAsPtrParser; @@ -10,6 +12,7 @@ impl NoArgsAttributeParser for RustcAsPtrParser { Allow(Target::Method(MethodKind::Trait { body: true })), Allow(Target::Method(MethodKind::TraitImpl)), ]); + const STABILITY: AttributeStability = unstable!(rustc_attrs); const CREATE: fn(Span) -> AttributeKind = |_| AttributeKind::RustcAsPtr; } @@ -21,6 +24,7 @@ impl NoArgsAttributeParser for RustcPubTransparentParser { Allow(Target::Enum), Allow(Target::Union), ]); + const STABILITY: AttributeStability = unstable!(rustc_attrs); const CREATE: fn(Span) -> AttributeKind = AttributeKind::RustcPubTransparent; } @@ -32,6 +36,7 @@ impl NoArgsAttributeParser for RustcPassByValueParser { Allow(Target::Enum), Allow(Target::TyAlias), ]); + const STABILITY: AttributeStability = unstable!(rustc_attrs); const CREATE: fn(Span) -> AttributeKind = |_| AttributeKind::RustcPassByValue; } @@ -42,6 +47,7 @@ impl NoArgsAttributeParser for RustcShouldNotBeCalledOnConstItemsParser { Allow(Target::Method(MethodKind::Inherent)), Allow(Target::Method(MethodKind::TraitImpl)), ]); + const STABILITY: AttributeStability = unstable!(rustc_attrs); const CREATE: fn(Span) -> AttributeKind = |_| AttributeKind::RustcShouldNotBeCalledOnConstItems; } @@ -54,5 +60,6 @@ impl NoArgsAttributeParser for AutomaticallyDerivedParser { Error(Target::Crate), Error(Target::WherePredicate), ]); + const STABILITY: AttributeStability = AttributeStability::Stable; const CREATE: fn(Span) -> AttributeKind = |_| AttributeKind::AutomaticallyDerived; } diff --git a/compiler/rustc_attr_parsing/src/attributes/loop_match.rs b/compiler/rustc_attr_parsing/src/attributes/loop_match.rs index cf2b0d009acab..57379877e76be 100644 --- a/compiler/rustc_attr_parsing/src/attributes/loop_match.rs +++ b/compiler/rustc_attr_parsing/src/attributes/loop_match.rs @@ -1,9 +1,12 @@ +use rustc_feature::AttributeStability; + use super::prelude::*; pub(crate) struct LoopMatchParser; impl NoArgsAttributeParser for LoopMatchParser { const PATH: &[Symbol] = &[sym::loop_match]; const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::Expression)]); + const STABILITY: AttributeStability = unstable!(loop_match); const CREATE: fn(Span) -> AttributeKind = AttributeKind::LoopMatch; } @@ -11,5 +14,6 @@ pub(crate) struct ConstContinueParser; impl NoArgsAttributeParser for ConstContinueParser { const PATH: &[Symbol] = &[sym::const_continue]; const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::Expression)]); + const STABILITY: AttributeStability = unstable!(loop_match); const CREATE: fn(Span) -> AttributeKind = AttributeKind::ConstContinue; } diff --git a/compiler/rustc_attr_parsing/src/attributes/macro_attrs.rs b/compiler/rustc_attr_parsing/src/attributes/macro_attrs.rs index 067590d23fc8d..effb490549a02 100644 --- a/compiler/rustc_attr_parsing/src/attributes/macro_attrs.rs +++ b/compiler/rustc_attr_parsing/src/attributes/macro_attrs.rs @@ -1,3 +1,4 @@ +use rustc_feature::AttributeStability; use rustc_hir::attrs::{CollapseMacroDebuginfo, MacroUseArgs}; use rustc_session::lint::builtin::INVALID_MACRO_EXPORT_ARGUMENTS; @@ -8,6 +9,7 @@ impl NoArgsAttributeParser for MacroEscapeParser { const PATH: &[Symbol] = &[sym::macro_escape]; const ON_DUPLICATE: OnDuplicate = OnDuplicate::Warn; const ALLOWED_TARGETS: AllowedTargets = MACRO_USE_ALLOWED_TARGETS; + const STABILITY: AttributeStability = AttributeStability::Stable; const CREATE: fn(Span) -> AttributeKind = |_| AttributeKind::MacroEscape; } @@ -41,6 +43,7 @@ impl AttributeParser for MacroUseParser { const ATTRIBUTES: AcceptMapping = &[( &[sym::macro_use], MACRO_USE_TEMPLATE, + AttributeStability::Stable, |group: &mut Self, cx: &mut AcceptContext<'_, '_>, args| { let span = cx.attr_span; group.first_span.get_or_insert(span); @@ -122,6 +125,7 @@ impl NoArgsAttributeParser for AllowInternalUnsafeParser { Warn(Target::Field), Warn(Target::Arm), ]); + const STABILITY: AttributeStability = unstable!(allow_internal_unsafe); const CREATE: fn(Span) -> AttributeKind = |span| AttributeKind::AllowInternalUnsafe(span); } @@ -136,6 +140,7 @@ impl SingleAttributeParser for MacroExportParser { Error(Target::WherePredicate), Error(Target::Crate), ]); + const STABILITY: AttributeStability = AttributeStability::Stable; fn convert(cx: &mut AcceptContext<'_, '_>, args: &ArgParser) -> Option { let local_inner_macros = match args { @@ -171,6 +176,7 @@ impl SingleAttributeParser for CollapseDebugInfoParser { "https://doc.rust-lang.org/reference/attributes/debugger.html#the-collapse_debuginfo-attribute" ); const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::MacroDef)]); + const STABILITY: AttributeStability = AttributeStability::Stable; fn convert(cx: &mut AcceptContext<'_, '_>, args: &ArgParser) -> Option { let single = cx.expect_single_element_list(args, cx.attr_span)?; @@ -200,5 +206,6 @@ pub(crate) struct RustcProcMacroDeclsParser; impl NoArgsAttributeParser for RustcProcMacroDeclsParser { const PATH: &[Symbol] = &[sym::rustc_proc_macro_decls]; const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::Static)]); + const STABILITY: AttributeStability = unstable!(rustc_attrs); const CREATE: fn(Span) -> AttributeKind = |_| AttributeKind::RustcProcMacroDecls; } diff --git a/compiler/rustc_attr_parsing/src/attributes/mod.rs b/compiler/rustc_attr_parsing/src/attributes/mod.rs index 42c6828ef57b7..1ad5c18f64293 100644 --- a/compiler/rustc_attr_parsing/src/attributes/mod.rs +++ b/compiler/rustc_attr_parsing/src/attributes/mod.rs @@ -16,7 +16,7 @@ use std::marker::PhantomData; -use rustc_feature::{AttributeTemplate, template}; +use rustc_feature::{AttributeStability, AttributeTemplate, template}; use rustc_hir::attrs::AttributeKind; use rustc_span::edition::Edition; use rustc_span::{Span, Symbol}; @@ -71,7 +71,8 @@ pub(crate) mod transparency; pub(crate) mod util; type AcceptFn = for<'sess> fn(&mut T, &mut AcceptContext<'_, 'sess>, &ArgParser); -type AcceptMapping = &'static [(&'static [Symbol], AttributeTemplate, AcceptFn)]; +type AcceptMapping = + &'static [(&'static [Symbol], AttributeTemplate, AttributeStability, AcceptFn)]; /// An [`AttributeParser`] is a type which searches for syntactic attributes. /// @@ -130,6 +131,7 @@ pub(crate) trait SingleAttributeParser: 'static { /// applied more than once on the same syntax node. const ON_DUPLICATE: OnDuplicate = OnDuplicate::Error; const SAFETY: AttributeSafety = AttributeSafety::Normal; + const STABILITY: AttributeStability; const ALLOWED_TARGETS: AllowedTargets; @@ -151,8 +153,11 @@ impl Default for Single { } impl AttributeParser for Single { - const ATTRIBUTES: AcceptMapping = - &[(T::PATH, ::TEMPLATE, |group: &mut Single, cx, args| { + const ATTRIBUTES: AcceptMapping = &[( + T::PATH, + ::TEMPLATE, + T::STABILITY, + |group: &mut Single, cx, args| { if let Some(pa) = T::convert(cx, args) { if let Some((_, used)) = group.1 { T::ON_DUPLICATE.exec::(cx, used, cx.attr_span); @@ -160,7 +165,8 @@ impl AttributeParser for Single { group.1 = Some((pa, cx.attr_span)); } } - })]; + }, + )]; const ALLOWED_TARGETS: AllowedTargets = T::ALLOWED_TARGETS; const SAFETY: AttributeSafety = T::SAFETY; @@ -237,6 +243,7 @@ pub(crate) trait NoArgsAttributeParser: 'static { const ON_DUPLICATE: OnDuplicate = OnDuplicate::Error; const ALLOWED_TARGETS: AllowedTargets; const SAFETY: AttributeSafety = AttributeSafety::Normal; + const STABILITY: AttributeStability; /// Create the [`AttributeKind`] given attribute's [`Span`]. const CREATE: fn(Span) -> AttributeKind; @@ -254,6 +261,7 @@ impl SingleAttributeParser for WithoutArgs { const PATH: &[Symbol] = T::PATH; const ON_DUPLICATE: OnDuplicate = T::ON_DUPLICATE; const SAFETY: AttributeSafety = T::SAFETY; + const STABILITY: AttributeStability = T::STABILITY; const ALLOWED_TARGETS: AllowedTargets = T::ALLOWED_TARGETS; const TEMPLATE: AttributeTemplate = template!(Word); @@ -282,6 +290,7 @@ pub(crate) trait CombineAttributeParser: 'static { /// where `x` is a vec of these individual reprs. const CONVERT: ConvertFn; const SAFETY: AttributeSafety = AttributeSafety::Normal; + const STABILITY: AttributeStability; const ALLOWED_TARGETS: AllowedTargets; @@ -317,7 +326,7 @@ impl Default for Combine { impl AttributeParser for Combine { const ATTRIBUTES: AcceptMapping = - &[(T::PATH, T::TEMPLATE, |group: &mut Combine, cx, args| { + &[(T::PATH, T::TEMPLATE, T::STABILITY, |group: &mut Combine, cx, args| { // Keep track of the span of the first attribute, for diagnostics group.first_span.get_or_insert(cx.attr_span); group.items.extend(T::extend(cx, args)) diff --git a/compiler/rustc_attr_parsing/src/attributes/must_not_suspend.rs b/compiler/rustc_attr_parsing/src/attributes/must_not_suspend.rs index f8e5cb37328a2..25fdd5fb3e086 100644 --- a/compiler/rustc_attr_parsing/src/attributes/must_not_suspend.rs +++ b/compiler/rustc_attr_parsing/src/attributes/must_not_suspend.rs @@ -1,3 +1,5 @@ +use rustc_feature::AttributeStability; + use super::prelude::*; pub(crate) struct MustNotSuspendParser; @@ -11,6 +13,7 @@ impl SingleAttributeParser for MustNotSuspendParser { Allow(Target::Trait), ]); const TEMPLATE: AttributeTemplate = template!(Word, List: &["count"]); + const STABILITY: AttributeStability = unstable!(must_not_suspend); fn convert(cx: &mut AcceptContext<'_, '_>, args: &ArgParser) -> Option { let reason = match args { diff --git a/compiler/rustc_attr_parsing/src/attributes/must_use.rs b/compiler/rustc_attr_parsing/src/attributes/must_use.rs index 646ac69b6452a..ae41efd769ed0 100644 --- a/compiler/rustc_attr_parsing/src/attributes/must_use.rs +++ b/compiler/rustc_attr_parsing/src/attributes/must_use.rs @@ -1,3 +1,5 @@ +use rustc_feature::AttributeStability; + use super::prelude::*; pub(crate) struct MustUseParser; @@ -24,6 +26,7 @@ impl SingleAttributeParser for MustUseParser { Word, NameValueStr: "reason", "https://doc.rust-lang.org/reference/attributes/diagnostics.html#the-must_use-attribute" ); + const STABILITY: AttributeStability = AttributeStability::Stable; fn convert(cx: &mut AcceptContext<'_, '_>, args: &ArgParser) -> Option { Some(AttributeKind::MustUse { diff --git a/compiler/rustc_attr_parsing/src/attributes/no_implicit_prelude.rs b/compiler/rustc_attr_parsing/src/attributes/no_implicit_prelude.rs index eb56e28cbdd47..ce172bc41beee 100644 --- a/compiler/rustc_attr_parsing/src/attributes/no_implicit_prelude.rs +++ b/compiler/rustc_attr_parsing/src/attributes/no_implicit_prelude.rs @@ -1,3 +1,5 @@ +use rustc_feature::AttributeStability; + use super::prelude::*; pub(crate) struct NoImplicitPreludeParser; @@ -7,5 +9,6 @@ impl NoArgsAttributeParser for NoImplicitPreludeParser { const ON_DUPLICATE: OnDuplicate = OnDuplicate::Warn; const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowListWarnRest(&[Allow(Target::Mod), Allow(Target::Crate)]); + const STABILITY: AttributeStability = AttributeStability::Stable; const CREATE: fn(Span) -> AttributeKind = |_| AttributeKind::NoImplicitPrelude; } diff --git a/compiler/rustc_attr_parsing/src/attributes/no_link.rs b/compiler/rustc_attr_parsing/src/attributes/no_link.rs index eedd6148a74a1..d067db5df81e6 100644 --- a/compiler/rustc_attr_parsing/src/attributes/no_link.rs +++ b/compiler/rustc_attr_parsing/src/attributes/no_link.rs @@ -1,3 +1,5 @@ +use rustc_feature::AttributeStability; + use super::prelude::*; pub(crate) struct NoLinkParser; @@ -10,5 +12,6 @@ impl NoArgsAttributeParser for NoLinkParser { Warn(Target::Arm), Warn(Target::MacroDef), ]); + const STABILITY: AttributeStability = AttributeStability::Stable; const CREATE: fn(Span) -> AttributeKind = |_| AttributeKind::NoLink; } diff --git a/compiler/rustc_attr_parsing/src/attributes/non_exhaustive.rs b/compiler/rustc_attr_parsing/src/attributes/non_exhaustive.rs index 7793752c8d850..c4cc47e593a6a 100644 --- a/compiler/rustc_attr_parsing/src/attributes/non_exhaustive.rs +++ b/compiler/rustc_attr_parsing/src/attributes/non_exhaustive.rs @@ -1,3 +1,4 @@ +use rustc_feature::AttributeStability; use rustc_hir::Target; use rustc_hir::attrs::AttributeKind; use rustc_span::{Span, Symbol, sym}; @@ -20,5 +21,6 @@ impl NoArgsAttributeParser for NonExhaustiveParser { Warn(Target::MacroDef), Warn(Target::MacroCall), ]); + const STABILITY: AttributeStability = AttributeStability::Stable; const CREATE: fn(Span) -> AttributeKind = AttributeKind::NonExhaustive; } diff --git a/compiler/rustc_attr_parsing/src/attributes/path.rs b/compiler/rustc_attr_parsing/src/attributes/path.rs index 8b6f20478df7c..57114d850dba8 100644 --- a/compiler/rustc_attr_parsing/src/attributes/path.rs +++ b/compiler/rustc_attr_parsing/src/attributes/path.rs @@ -1,3 +1,5 @@ +use rustc_feature::AttributeStability; + use super::prelude::*; pub(crate) struct PathParser; @@ -11,6 +13,7 @@ impl SingleAttributeParser for PathParser { NameValueStr: "file", "https://doc.rust-lang.org/reference/items/modules.html#the-path-attribute" ); + const STABILITY: AttributeStability = AttributeStability::Stable; fn convert(cx: &mut AcceptContext<'_, '_>, args: &ArgParser) -> Option { let nv = cx.expect_name_value(args, cx.attr_span, None)?; diff --git a/compiler/rustc_attr_parsing/src/attributes/pin_v2.rs b/compiler/rustc_attr_parsing/src/attributes/pin_v2.rs index 06e4bc7374e08..ba84b6f8e98c4 100644 --- a/compiler/rustc_attr_parsing/src/attributes/pin_v2.rs +++ b/compiler/rustc_attr_parsing/src/attributes/pin_v2.rs @@ -1,3 +1,4 @@ +use rustc_feature::AttributeStability; use rustc_hir::Target; use rustc_hir::attrs::AttributeKind; use rustc_span::{Span, Symbol, sym}; @@ -5,6 +6,7 @@ use rustc_span::{Span, Symbol, sym}; use crate::attributes::NoArgsAttributeParser; use crate::target_checking::AllowedTargets; use crate::target_checking::Policy::Allow; +use crate::unstable; pub(crate) struct PinV2Parser; @@ -15,5 +17,6 @@ impl NoArgsAttributeParser for PinV2Parser { Allow(Target::Struct), Allow(Target::Union), ]); + const STABILITY: AttributeStability = unstable!(pin_ergonomics); const CREATE: fn(Span) -> AttributeKind = AttributeKind::PinV2; } diff --git a/compiler/rustc_attr_parsing/src/attributes/prelude.rs b/compiler/rustc_attr_parsing/src/attributes/prelude.rs index ae0c1673ed3b4..15d548501cb65 100644 --- a/compiler/rustc_attr_parsing/src/attributes/prelude.rs +++ b/compiler/rustc_attr_parsing/src/attributes/prelude.rs @@ -25,3 +25,5 @@ pub(super) use crate::parser::*; pub(super) use crate::target_checking::Policy::{Allow, Error, Warn}; #[doc(hidden)] pub(super) use crate::target_checking::{ALL_TARGETS, AllowedTargets}; +#[doc(hidden)] +pub(super) use crate::unstable; diff --git a/compiler/rustc_attr_parsing/src/attributes/proc_macro_attrs.rs b/compiler/rustc_attr_parsing/src/attributes/proc_macro_attrs.rs index 3c9e80cef0083..e6c9d80d8b1a3 100644 --- a/compiler/rustc_attr_parsing/src/attributes/proc_macro_attrs.rs +++ b/compiler/rustc_attr_parsing/src/attributes/proc_macro_attrs.rs @@ -1,3 +1,4 @@ +use rustc_feature::AttributeStability; use rustc_session::lint::builtin::AMBIGUOUS_DERIVE_HELPERS; use super::prelude::*; @@ -9,6 +10,7 @@ pub(crate) struct ProcMacroParser; impl NoArgsAttributeParser for ProcMacroParser { const PATH: &[Symbol] = &[sym::proc_macro]; const ALLOWED_TARGETS: AllowedTargets = PROC_MACRO_ALLOWED_TARGETS; + const STABILITY: AttributeStability = AttributeStability::Stable; const CREATE: fn(Span) -> AttributeKind = |_| AttributeKind::ProcMacro; } @@ -16,6 +18,7 @@ pub(crate) struct ProcMacroAttributeParser; impl NoArgsAttributeParser for ProcMacroAttributeParser { const PATH: &[Symbol] = &[sym::proc_macro_attribute]; const ALLOWED_TARGETS: AllowedTargets = PROC_MACRO_ALLOWED_TARGETS; + const STABILITY: AttributeStability = AttributeStability::Stable; const CREATE: fn(Span) -> AttributeKind = |_| AttributeKind::ProcMacroAttribute; } @@ -27,6 +30,7 @@ impl SingleAttributeParser for ProcMacroDeriveParser { List: &["TraitName", "TraitName, attributes(name1, name2, ...)"], "https://doc.rust-lang.org/reference/procedural-macros.html#derive-macros" ); + const STABILITY: AttributeStability = AttributeStability::Stable; fn convert(cx: &mut AcceptContext<'_, '_>, args: &ArgParser) -> Option { let (trait_name, helper_attrs) = parse_derive_like(cx, args, true)?; @@ -43,6 +47,7 @@ impl SingleAttributeParser for RustcBuiltinMacroParser { const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::MacroDef)]); const TEMPLATE: AttributeTemplate = template!(List: &["TraitName", "TraitName, attributes(name1, name2, ...)"]); + const STABILITY: AttributeStability = unstable!(rustc_attrs); fn convert(cx: &mut AcceptContext<'_, '_>, args: &ArgParser) -> Option { let (builtin_name, helper_attrs) = parse_derive_like(cx, args, false)?; diff --git a/compiler/rustc_attr_parsing/src/attributes/prototype.rs b/compiler/rustc_attr_parsing/src/attributes/prototype.rs index 8b8eccab029dd..40d6a80a398fc 100644 --- a/compiler/rustc_attr_parsing/src/attributes/prototype.rs +++ b/compiler/rustc_attr_parsing/src/attributes/prototype.rs @@ -1,6 +1,6 @@ //! Attributes that are only used on function prototypes. -use rustc_feature::{AttributeTemplate, template}; +use rustc_feature::{AttributeStability, AttributeTemplate, template}; use rustc_hir::Target; use rustc_hir::attrs::{AttributeKind, MirDialect, MirPhase}; use rustc_span::{Span, Symbol, sym}; @@ -8,14 +8,15 @@ use rustc_span::{Span, Symbol, sym}; use crate::attributes::SingleAttributeParser; use crate::context::AcceptContext; use crate::parser::{ArgParser, NameValueParser}; -use crate::session_diagnostics; use crate::target_checking::AllowedTargets; use crate::target_checking::Policy::Allow; +use crate::{session_diagnostics, unstable}; pub(crate) struct CustomMirParser; impl SingleAttributeParser for CustomMirParser { const PATH: &[rustc_span::Symbol] = &[sym::custom_mir]; + const STABILITY: AttributeStability = unstable!(custom_mir); const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::Fn)]); diff --git a/compiler/rustc_attr_parsing/src/attributes/repr.rs b/compiler/rustc_attr_parsing/src/attributes/repr.rs index e3d5ba891a5dd..7b4e838a1a7fb 100644 --- a/compiler/rustc_attr_parsing/src/attributes/repr.rs +++ b/compiler/rustc_attr_parsing/src/attributes/repr.rs @@ -1,5 +1,6 @@ use rustc_abi::{Align, Size}; use rustc_ast::{IntTy, LitIntType, LitKind, UintTy}; +use rustc_feature::AttributeStability; use rustc_hir::attrs::IntType::{SignedInt, UnsignedInt}; use rustc_hir::attrs::ReprAttr; @@ -55,6 +56,7 @@ impl CombineAttributeParser for ReprParser { //FIXME Still checked fully in `check_attr.rs` //This one is slightly more complicated because the allowed targets depend on the arguments const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(ALL_TARGETS); + const STABILITY: AttributeStability = AttributeStability::Stable; } fn parse_repr(cx: &mut AcceptContext<'_, '_>, param: &MetaItemParser) -> Option { @@ -223,7 +225,8 @@ impl RustcAlignParser { } impl AttributeParser for RustcAlignParser { - const ATTRIBUTES: AcceptMapping = &[(Self::PATH, Self::TEMPLATE, Self::parse)]; + const ATTRIBUTES: AcceptMapping = + &[(Self::PATH, Self::TEMPLATE, unstable!(fn_align), Self::parse)]; const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[ Allow(Target::Fn), Allow(Target::Method(MethodKind::Inherent)), @@ -252,7 +255,8 @@ impl RustcAlignStaticParser { } impl AttributeParser for RustcAlignStaticParser { - const ATTRIBUTES: AcceptMapping = &[(Self::PATH, Self::TEMPLATE, Self::parse)]; + const ATTRIBUTES: AcceptMapping = + &[(Self::PATH, Self::TEMPLATE, unstable!(static_align), Self::parse)]; const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::Static), Allow(Target::ForeignStatic)]); diff --git a/compiler/rustc_attr_parsing/src/attributes/rustc_allocator.rs b/compiler/rustc_attr_parsing/src/attributes/rustc_allocator.rs index b21677b4f79ea..cacf98bef6e93 100644 --- a/compiler/rustc_attr_parsing/src/attributes/rustc_allocator.rs +++ b/compiler/rustc_attr_parsing/src/attributes/rustc_allocator.rs @@ -1,3 +1,5 @@ +use rustc_feature::AttributeStability; + use super::prelude::*; pub(crate) struct RustcAllocatorParser; @@ -6,6 +8,7 @@ impl NoArgsAttributeParser for RustcAllocatorParser { const PATH: &[Symbol] = &[sym::rustc_allocator]; const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::Fn), Allow(Target::ForeignFn)]); + const STABILITY: AttributeStability = unstable!(rustc_attrs); const CREATE: fn(Span) -> AttributeKind = |_| AttributeKind::RustcAllocator; } @@ -15,6 +18,7 @@ impl NoArgsAttributeParser for RustcAllocatorZeroedParser { const PATH: &[Symbol] = &[sym::rustc_allocator_zeroed]; const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::Fn), Allow(Target::ForeignFn)]); + const STABILITY: AttributeStability = unstable!(rustc_attrs); const CREATE: fn(Span) -> AttributeKind = |_| AttributeKind::RustcAllocatorZeroed; } @@ -25,6 +29,7 @@ impl SingleAttributeParser for RustcAllocatorZeroedVariantParser { const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::Fn), Allow(Target::ForeignFn)]); const TEMPLATE: AttributeTemplate = template!(NameValueStr: "function"); + const STABILITY: AttributeStability = unstable!(rustc_attrs); fn convert(cx: &mut AcceptContext<'_, '_>, args: &ArgParser) -> Option { let nv = cx.expect_name_value(args, cx.attr_span, None)?; let name = cx.expect_string_literal(nv)?; @@ -39,6 +44,7 @@ impl NoArgsAttributeParser for RustcDeallocatorParser { const PATH: &[Symbol] = &[sym::rustc_deallocator]; const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::Fn), Allow(Target::ForeignFn)]); + const STABILITY: AttributeStability = unstable!(rustc_attrs); const CREATE: fn(Span) -> AttributeKind = |_| AttributeKind::RustcDeallocator; } @@ -48,5 +54,6 @@ impl NoArgsAttributeParser for RustcReallocatorParser { const PATH: &[Symbol] = &[sym::rustc_reallocator]; const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::Fn), Allow(Target::ForeignFn)]); + const STABILITY: AttributeStability = unstable!(rustc_attrs); const CREATE: fn(Span) -> AttributeKind = |_| AttributeKind::RustcReallocator; } diff --git a/compiler/rustc_attr_parsing/src/attributes/rustc_dump.rs b/compiler/rustc_attr_parsing/src/attributes/rustc_dump.rs index 8d507065dbdb6..86ef57a60fbac 100644 --- a/compiler/rustc_attr_parsing/src/attributes/rustc_dump.rs +++ b/compiler/rustc_attr_parsing/src/attributes/rustc_dump.rs @@ -1,3 +1,4 @@ +use rustc_feature::AttributeStability; use rustc_hir::attrs::{AttributeKind, RustcDumpLayoutKind}; use rustc_hir::{MethodKind, Target}; use rustc_span::{Span, Symbol, sym}; @@ -10,6 +11,7 @@ pub(crate) struct RustcDumpUserArgsParser; impl NoArgsAttributeParser for RustcDumpUserArgsParser { const PATH: &[Symbol] = &[sym::rustc_dump_user_args]; const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::Fn)]); + const STABILITY: AttributeStability = unstable!(rustc_attrs); const CREATE: fn(Span) -> AttributeKind = |_| AttributeKind::RustcDumpUserArgs; } @@ -18,6 +20,7 @@ pub(crate) struct RustcDumpDefParentsParser; impl NoArgsAttributeParser for RustcDumpDefParentsParser { const PATH: &[Symbol] = &[sym::rustc_dump_def_parents]; const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::Fn)]); + const STABILITY: AttributeStability = unstable!(rustc_attrs); const CREATE: fn(Span) -> AttributeKind = |_| AttributeKind::RustcDumpDefParents; } @@ -35,6 +38,7 @@ impl SingleAttributeParser for RustcDumpDefPathParser { Allow(Target::Impl { of_trait: false }), ]); const TEMPLATE: AttributeTemplate = template!(Word); + const STABILITY: AttributeStability = unstable!(rustc_attrs); fn convert(cx: &mut AcceptContext<'_, '_>, args: &ArgParser) -> Option { cx.expect_no_args(args)?; Some(AttributeKind::RustcDumpDefPath(cx.attr_span)) @@ -46,6 +50,7 @@ pub(crate) struct RustcDumpHiddenTypeOfOpaquesParser; impl NoArgsAttributeParser for RustcDumpHiddenTypeOfOpaquesParser { const PATH: &[Symbol] = &[sym::rustc_dump_hidden_type_of_opaques]; const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::Crate)]); + const STABILITY: AttributeStability = unstable!(rustc_attrs); const CREATE: fn(Span) -> AttributeKind = |_| AttributeKind::RustcDumpHiddenTypeOfOpaques; } @@ -59,6 +64,7 @@ impl NoArgsAttributeParser for RustcDumpInferredOutlivesParser { Allow(Target::Union), Allow(Target::TyAlias), ]); + const STABILITY: AttributeStability = unstable!(rustc_attrs); const CREATE: fn(Span) -> AttributeKind = |_| AttributeKind::RustcDumpInferredOutlives; } @@ -67,6 +73,7 @@ pub(crate) struct RustcDumpItemBoundsParser; impl NoArgsAttributeParser for RustcDumpItemBoundsParser { const PATH: &[Symbol] = &[sym::rustc_dump_item_bounds]; const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::AssocTy)]); + const STABILITY: AttributeStability = unstable!(rustc_attrs); const CREATE: fn(Span) -> AttributeKind = |_| AttributeKind::RustcDumpItemBounds; } @@ -88,6 +95,8 @@ impl CombineAttributeParser for RustcDumpLayoutParser { const TEMPLATE: AttributeTemplate = template!(List: &["abi", "align", "size", "homogenous_aggregate", "debug"]); + const STABILITY: AttributeStability = unstable!(rustc_attrs); + fn extend( cx: &mut AcceptContext<'_, '_>, args: &ArgParser, @@ -155,6 +164,7 @@ impl NoArgsAttributeParser for RustcDumpObjectLifetimeDefaultsParser { Allow(Target::TyAlias), Allow(Target::Union), ]); + const STABILITY: AttributeStability = unstable!(rustc_attrs); const CREATE: fn(Span) -> AttributeKind = |_| AttributeKind::RustcDumpObjectLifetimeDefaults; } @@ -182,6 +192,7 @@ impl NoArgsAttributeParser for RustcDumpPredicatesParser { Allow(Target::TyAlias), Allow(Target::Union), ]); + const STABILITY: AttributeStability = unstable!(rustc_attrs); const CREATE: fn(Span) -> AttributeKind = |_| AttributeKind::RustcDumpPredicates; } @@ -199,6 +210,7 @@ impl SingleAttributeParser for RustcDumpSymbolNameParser { Allow(Target::Impl { of_trait: false }), ]); const TEMPLATE: AttributeTemplate = template!(Word); + const STABILITY: AttributeStability = unstable!(rustc_attrs); fn convert(cx: &mut AcceptContext<'_, '_>, args: &ArgParser) -> Option { cx.expect_no_args(args)?; Some(AttributeKind::RustcDumpSymbolName(cx.attr_span)) @@ -219,6 +231,10 @@ impl NoArgsAttributeParser for RustcDumpVariancesParser { Allow(Target::Struct), Allow(Target::Union), ]); + const STABILITY: AttributeStability = unstable!( + rustc_attrs, + "the `#[rustc_dump_variances]` attribute is used for rustc unit tests" + ); const CREATE: fn(Span) -> AttributeKind = |_| AttributeKind::RustcDumpVariances; } @@ -227,6 +243,7 @@ pub(crate) struct RustcDumpVariancesOfOpaquesParser; impl NoArgsAttributeParser for RustcDumpVariancesOfOpaquesParser { const PATH: &[Symbol] = &[sym::rustc_dump_variances_of_opaques]; const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::Crate)]); + const STABILITY: AttributeStability = unstable!(rustc_attrs); const CREATE: fn(Span) -> AttributeKind = |_| AttributeKind::RustcDumpVariancesOfOpaques; } @@ -238,5 +255,6 @@ impl NoArgsAttributeParser for RustcDumpVtableParser { Allow(Target::Impl { of_trait: true }), Allow(Target::TyAlias), ]); + const STABILITY: AttributeStability = unstable!(rustc_attrs); const CREATE: fn(Span) -> AttributeKind = AttributeKind::RustcDumpVtable; } diff --git a/compiler/rustc_attr_parsing/src/attributes/rustc_internal.rs b/compiler/rustc_attr_parsing/src/attributes/rustc_internal.rs index 8a648550ae8c6..56e415406b63d 100644 --- a/compiler/rustc_attr_parsing/src/attributes/rustc_internal.rs +++ b/compiler/rustc_attr_parsing/src/attributes/rustc_internal.rs @@ -1,6 +1,7 @@ use std::path::PathBuf; use rustc_ast::{LitIntType, LitKind, MetaItemLit}; +use rustc_feature::AttributeStability; use rustc_hir::LangItem; use rustc_hir::attrs::{ BorrowckGraphvizFormatKind, CguFields, CguKind, DivergingBlockBehavior, @@ -20,6 +21,10 @@ pub(crate) struct RustcMainParser; impl NoArgsAttributeParser for RustcMainParser { const PATH: &[Symbol] = &[sym::rustc_main]; const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::Fn)]); + const STABILITY: AttributeStability = unstable!( + rustc_attrs, + "the `#[rustc_main]` attribute is used internally to specify test entry point function" + ); const CREATE: fn(Span) -> AttributeKind = |_| AttributeKind::RustcMain; } @@ -28,6 +33,10 @@ pub(crate) struct RustcMustImplementOneOfParser; impl SingleAttributeParser for RustcMustImplementOneOfParser { const PATH: &[Symbol] = &[sym::rustc_must_implement_one_of]; const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::Trait)]); + const STABILITY: AttributeStability = unstable!( + rustc_attrs, + "the `#[rustc_must_implement_one_of]` attribute is used to change minimal complete definition of a trait. Its syntax and semantics are highly experimental and will be subject to change before stabilization" + ); const TEMPLATE: AttributeTemplate = template!(List: &["function1, function2, ..."]); fn convert(cx: &mut AcceptContext<'_, '_>, args: &ArgParser) -> Option { let list = cx.expect_list(args, cx.attr_span)?; @@ -75,6 +84,8 @@ impl NoArgsAttributeParser for RustcNeverReturnsNullPtrParser { Allow(Target::Method(MethodKind::Trait { body: true })), Allow(Target::Method(MethodKind::TraitImpl)), ]); + const STABILITY: AttributeStability = unstable!(rustc_attrs); + const CREATE: fn(Span) -> AttributeKind = |_| AttributeKind::RustcNeverReturnsNullPtr; } pub(crate) struct RustcNoImplicitAutorefsParser; @@ -88,6 +99,7 @@ impl NoArgsAttributeParser for RustcNoImplicitAutorefsParser { Allow(Target::Method(MethodKind::Trait { body: true })), Allow(Target::Method(MethodKind::TraitImpl)), ]); + const STABILITY: AttributeStability = unstable!(rustc_attrs); const CREATE: fn(Span) -> AttributeKind = |_| AttributeKind::RustcNoImplicitAutorefs; } @@ -98,6 +110,7 @@ impl SingleAttributeParser for RustcLegacyConstGenericsParser { const PATH: &[Symbol] = &[sym::rustc_legacy_const_generics]; const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::Fn)]); const TEMPLATE: AttributeTemplate = template!(List: &["N"]); + const STABILITY: AttributeStability = unstable!(rustc_attrs); fn convert(cx: &mut AcceptContext<'_, '_>, args: &ArgParser) -> Option { let meta_items = cx.expect_list(args, cx.attr_span)?; @@ -141,6 +154,7 @@ impl NoArgsAttributeParser for RustcInheritOverflowChecksParser { Allow(Target::Method(MethodKind::TraitImpl)), Allow(Target::Closure), ]); + const STABILITY: AttributeStability = unstable!(rustc_attrs); const CREATE: fn(Span) -> AttributeKind = |_| AttributeKind::RustcInheritOverflowChecks; } @@ -150,6 +164,7 @@ impl SingleAttributeParser for RustcLintOptDenyFieldAccessParser { const PATH: &[Symbol] = &[sym::rustc_lint_opt_deny_field_access]; const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::Field)]); const TEMPLATE: AttributeTemplate = template!(Word); + const STABILITY: AttributeStability = unstable!(rustc_attrs); fn convert(cx: &mut AcceptContext<'_, '_>, args: &ArgParser) -> Option { let arg = cx.expect_single_element_list(args, cx.attr_span)?; let lint_message = cx.expect_string_literal(arg)?; @@ -163,6 +178,7 @@ pub(crate) struct RustcLintOptTyParser; impl NoArgsAttributeParser for RustcLintOptTyParser { const PATH: &[Symbol] = &[sym::rustc_lint_opt_ty]; const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::Struct)]); + const STABILITY: AttributeStability = unstable!(rustc_attrs); const CREATE: fn(Span) -> AttributeKind = |_| AttributeKind::RustcLintOptTy; } @@ -258,6 +274,7 @@ impl AttributeParser for RustcCguTestAttributeParser { ( &[sym::rustc_partition_reused], template!(List: &[r#"cfg = "...", module = "...""#]), + unstable!(rustc_attrs), |this, cx, args| { this.items.extend(parse_cgu_fields(cx, args, false).map(|(cfg, module, _)| { (cx.attr_span, CguFields::PartitionReused { cfg, module }) @@ -267,6 +284,7 @@ impl AttributeParser for RustcCguTestAttributeParser { ( &[sym::rustc_partition_codegened], template!(List: &[r#"cfg = "...", module = "...""#]), + unstable!(rustc_attrs), |this, cx, args| { this.items.extend(parse_cgu_fields(cx, args, false).map(|(cfg, module, _)| { (cx.attr_span, CguFields::PartitionCodegened { cfg, module }) @@ -276,6 +294,7 @@ impl AttributeParser for RustcCguTestAttributeParser { ( &[sym::rustc_expected_cgu_reuse], template!(List: &[r#"cfg = "...", module = "...", kind = "...""#]), + unstable!(rustc_attrs), |this, cx, args| { this.items.extend(parse_cgu_fields(cx, args, true).map(|(cfg, module, kind)| { // unwrap ok because if not given, we return None in `parse_cgu_fields`. @@ -305,6 +324,7 @@ impl SingleAttributeParser for RustcDeprecatedSafe2024Parser { Allow(Target::Method(MethodKind::TraitImpl)), ]); const TEMPLATE: AttributeTemplate = template!(List: &[r#"audit_that = "...""#]); + const STABILITY: AttributeStability = unstable!(rustc_attrs); fn convert(cx: &mut AcceptContext<'_, '_>, args: &ArgParser) -> Option { let single = cx.expect_single_element_list(args, cx.attr_span)?; @@ -333,6 +353,7 @@ impl NoArgsAttributeParser for RustcConversionSuggestionParser { Allow(Target::Method(MethodKind::Trait { body: true })), Allow(Target::Method(MethodKind::TraitImpl)), ]); + const STABILITY: AttributeStability = unstable!(rustc_attrs); const CREATE: fn(Span) -> AttributeKind = |_| AttributeKind::RustcConversionSuggestion; } @@ -341,6 +362,7 @@ pub(crate) struct RustcCaptureAnalysisParser; impl NoArgsAttributeParser for RustcCaptureAnalysisParser { const PATH: &[Symbol] = &[sym::rustc_capture_analysis]; const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::Closure)]); + const STABILITY: AttributeStability = unstable!(rustc_attrs); const CREATE: fn(Span) -> AttributeKind = |_| AttributeKind::RustcCaptureAnalysis; } @@ -353,6 +375,10 @@ impl SingleAttributeParser for RustcNeverTypeOptionsParser { r#"fallback = "unit", "never", "no""#, r#"diverging_block_default = "unit", "never""#, ]); + const STABILITY: AttributeStability = unstable!( + rustc_attrs, + "`rustc_never_type_options` is used to experiment with never type fallback and work on never type stabilization" + ); fn convert(cx: &mut AcceptContext<'_, '_>, args: &ArgParser) -> Option { let list = cx.expect_list(args, cx.attr_span)?; @@ -418,6 +444,7 @@ pub(crate) struct RustcTrivialFieldReadsParser; impl NoArgsAttributeParser for RustcTrivialFieldReadsParser { const PATH: &[Symbol] = &[sym::rustc_trivial_field_reads]; const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::Trait)]); + const STABILITY: AttributeStability = unstable!(rustc_attrs); const CREATE: fn(Span) -> AttributeKind = |_| AttributeKind::RustcTrivialFieldReads; } @@ -432,6 +459,7 @@ impl NoArgsAttributeParser for RustcNoMirInlineParser { Allow(Target::Method(MethodKind::Trait { body: true })), Allow(Target::Method(MethodKind::TraitImpl)), ]); + const STABILITY: AttributeStability = unstable!(rustc_attrs); const CREATE: fn(Span) -> AttributeKind = |_| AttributeKind::RustcNoMirInline; } @@ -447,6 +475,7 @@ impl NoArgsAttributeParser for RustcNoWritableParser { Allow(Target::Method(MethodKind::TraitImpl)), Allow(Target::Method(MethodKind::Trait { body: true })), ]); + const STABILITY: AttributeStability = unstable!(rustc_attrs); const CREATE: fn(Span) -> AttributeKind = |_| AttributeKind::RustcNoWritable; } @@ -461,6 +490,7 @@ impl NoArgsAttributeParser for RustcLintQueryInstabilityParser { Allow(Target::Method(MethodKind::Trait { body: true })), Allow(Target::Method(MethodKind::TraitImpl)), ]); + const STABILITY: AttributeStability = unstable!(rustc_attrs); const CREATE: fn(Span) -> AttributeKind = |_| AttributeKind::RustcLintQueryInstability; } @@ -475,7 +505,7 @@ impl NoArgsAttributeParser for RustcRegionsParser { Allow(Target::Method(MethodKind::Trait { body: true })), Allow(Target::Method(MethodKind::TraitImpl)), ]); - + const STABILITY: AttributeStability = unstable!(rustc_attrs); const CREATE: fn(Span) -> AttributeKind = |_| AttributeKind::RustcRegions; } @@ -490,7 +520,7 @@ impl NoArgsAttributeParser for RustcLintUntrackedQueryInformationParser { Allow(Target::Method(MethodKind::Trait { body: true })), Allow(Target::Method(MethodKind::TraitImpl)), ]); - + const STABILITY: AttributeStability = unstable!(rustc_attrs); const CREATE: fn(Span) -> AttributeKind = |_| AttributeKind::RustcLintUntrackedQueryInformation; } @@ -500,6 +530,7 @@ impl SingleAttributeParser for RustcSimdMonomorphizeLaneLimitParser { const PATH: &[Symbol] = &[sym::rustc_simd_monomorphize_lane_limit]; const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::Struct)]); const TEMPLATE: AttributeTemplate = template!(NameValueStr: "N"); + const STABILITY: AttributeStability = unstable!(rustc_attrs); fn convert(cx: &mut AcceptContext<'_, '_>, args: &ArgParser) -> Option { let nv = cx.expect_name_value(args, cx.attr_span, None)?; @@ -513,6 +544,7 @@ impl SingleAttributeParser for RustcScalableVectorParser { const PATH: &[Symbol] = &[sym::rustc_scalable_vector]; const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::Struct)]); const TEMPLATE: AttributeTemplate = template!(Word, List: &["count"]); + const STABILITY: AttributeStability = unstable!(rustc_attrs); fn convert(cx: &mut AcceptContext<'_, '_>, args: &ArgParser) -> Option { if args.as_no_args().is_ok() { @@ -534,6 +566,7 @@ impl SingleAttributeParser for LangParser { const PATH: &[Symbol] = &[sym::lang]; const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(ALL_TARGETS); // Targets are checked per lang item in `rustc_passes` const TEMPLATE: AttributeTemplate = template!(NameValueStr: "name"); + const STABILITY: AttributeStability = unstable!(lang_items); fn convert(cx: &mut AcceptContext<'_, '_>, args: &ArgParser) -> Option { let nv = cx.expect_name_value(args, cx.attr_span, None)?; @@ -557,6 +590,7 @@ impl NoArgsAttributeParser for RustcHasIncoherentInherentImplsParser { Allow(Target::Union), Allow(Target::ForeignTy), ]); + const STABILITY: AttributeStability = unstable!(rustc_attrs); const CREATE: fn(Span) -> AttributeKind = |_| AttributeKind::RustcHasIncoherentInherentImpls; } @@ -565,6 +599,7 @@ pub(crate) struct PanicHandlerParser; impl NoArgsAttributeParser for PanicHandlerParser { const PATH: &[Symbol] = &[sym::panic_handler]; const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(ALL_TARGETS); // Targets are checked per lang item in `rustc_passes` + const STABILITY: AttributeStability = AttributeStability::Stable; const CREATE: fn(Span) -> AttributeKind = |_| AttributeKind::Lang(LangItem::PanicImpl); } @@ -579,6 +614,7 @@ impl NoArgsAttributeParser for RustcNounwindParser { Allow(Target::Method(MethodKind::TraitImpl)), Allow(Target::Method(MethodKind::Trait { body: true })), ]); + const STABILITY: AttributeStability = unstable!(rustc_attrs); const CREATE: fn(Span) -> AttributeKind = |_| AttributeKind::RustcNounwind; } @@ -587,6 +623,7 @@ pub(crate) struct RustcOffloadKernelParser; impl NoArgsAttributeParser for RustcOffloadKernelParser { const PATH: &[Symbol] = &[sym::rustc_offload_kernel]; const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::Fn)]); + const STABILITY: AttributeStability = unstable!(rustc_attrs); const CREATE: fn(Span) -> AttributeKind = |_| AttributeKind::RustcOffloadKernel; } @@ -598,7 +635,6 @@ impl CombineAttributeParser for RustcMirParser { type Item = RustcMirKind; const CONVERT: ConvertFn = |items, _| AttributeKind::RustcMir(items); - const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[ Allow(Target::Fn), Allow(Target::Method(MethodKind::Inherent)), @@ -606,8 +642,8 @@ impl CombineAttributeParser for RustcMirParser { Allow(Target::Method(MethodKind::Trait { body: false })), Allow(Target::Method(MethodKind::Trait { body: true })), ]); - const TEMPLATE: AttributeTemplate = template!(List: &["arg1, arg2, ..."]); + const STABILITY: AttributeStability = unstable!(rustc_attrs); fn extend( cx: &mut AcceptContext<'_, '_>, @@ -679,6 +715,10 @@ impl NoArgsAttributeParser for RustcNonConstTraitMethodParser { Allow(Target::Method(MethodKind::Trait { body: true })), Allow(Target::Method(MethodKind::Trait { body: false })), ]); + const STABILITY: AttributeStability = unstable!( + rustc_attrs, + "`#[rustc_non_const_trait_method]` should only used by the standard library to mark trait methods as non-const to allow large traits an easier transition to const" + ); const CREATE: fn(Span) -> AttributeKind = |_| AttributeKind::RustcNonConstTraitMethod; } @@ -690,7 +730,6 @@ impl CombineAttributeParser for RustcCleanParser { type Item = RustcCleanAttribute; const CONVERT: ConvertFn = |items, _| AttributeKind::RustcClean(items); - const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[ // tidy-alphabetical-start Allow(Target::AssocConst), @@ -715,7 +754,7 @@ impl CombineAttributeParser for RustcCleanParser { Allow(Target::Union), // tidy-alphabetical-end ]); - + const STABILITY: AttributeStability = unstable!(rustc_attrs); const TEMPLATE: AttributeTemplate = template!(List: &[r#"cfg = "...", /*opt*/ label = "...", /*opt*/ except = "...""#]); @@ -784,7 +823,6 @@ pub(crate) struct RustcIfThisChangedParser; impl SingleAttributeParser for RustcIfThisChangedParser { const PATH: &[Symbol] = &[sym::rustc_if_this_changed]; - const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[ // tidy-alphabetical-start Allow(Target::AssocConst), @@ -809,8 +847,8 @@ impl SingleAttributeParser for RustcIfThisChangedParser { Allow(Target::Union), // tidy-alphabetical-end ]); - const TEMPLATE: AttributeTemplate = template!(Word, List: &["DepNode"]); + const STABILITY: AttributeStability = unstable!(rustc_attrs); fn convert(cx: &mut AcceptContext<'_, '_>, args: &ArgParser) -> Option { if !cx.cx.sess.opts.unstable_opts.query_dep_graph { @@ -867,8 +905,8 @@ impl CombineAttributeParser for RustcThenThisWouldNeedParser { Allow(Target::Union), // tidy-alphabetical-end ]); - const TEMPLATE: AttributeTemplate = template!(List: &["DepNode"]); + const STABILITY: AttributeStability = unstable!(rustc_attrs); fn extend( cx: &mut AcceptContext<'_, '_>, @@ -895,6 +933,7 @@ impl NoArgsAttributeParser for RustcInsignificantDtorParser { Allow(Target::Struct), Allow(Target::ForeignTy), ]); + const STABILITY: AttributeStability = unstable!(rustc_attrs); const CREATE: fn(Span) -> AttributeKind = |_| AttributeKind::RustcInsignificantDtor; } @@ -933,6 +972,7 @@ impl NoArgsAttributeParser for RustcEffectiveVisibilityParser { Allow(Target::PatField), Allow(Target::Crate), ]); + const STABILITY: AttributeStability = unstable!(rustc_attrs); const CREATE: fn(Span) -> AttributeKind = |_| AttributeKind::RustcEffectiveVisibility; } @@ -959,6 +999,10 @@ impl SingleAttributeParser for RustcDiagnosticItemParser { Allow(Target::Crate), ]); const TEMPLATE: AttributeTemplate = template!(NameValueStr: "name"); + const STABILITY: AttributeStability = unstable!( + rustc_attrs, + "the `#[rustc_diagnostic_item]` attribute allows the compiler to reference types from the standard library for diagnostic purposes" + ); fn convert(cx: &mut AcceptContext<'_, '_>, args: &ArgParser) -> Option { let nv = cx.expect_name_value(args, cx.attr_span, None)?; @@ -978,6 +1022,10 @@ impl NoArgsAttributeParser for RustcDoNotConstCheckParser { Allow(Target::Method(MethodKind::Trait { body: false })), Allow(Target::Method(MethodKind::Trait { body: true })), ]); + const STABILITY: AttributeStability = unstable!( + rustc_attrs, + "`#[rustc_do_not_const_check]` skips const-check for this function's body" + ); const CREATE: fn(Span) -> AttributeKind = |_| AttributeKind::RustcDoNotConstCheck; } @@ -986,6 +1034,11 @@ pub(crate) struct RustcNonnullOptimizationGuaranteedParser; impl NoArgsAttributeParser for RustcNonnullOptimizationGuaranteedParser { const PATH: &[Symbol] = &[sym::rustc_nonnull_optimization_guaranteed]; const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::Struct)]); + const STABILITY: AttributeStability = unstable!( + rustc_attrs, + "the `#[rustc_nonnull_optimization_guaranteed]` attribute is just used to document guaranteed niche optimizations in the standard library", + "the compiler does not even check whether the type indeed is being non-null-optimized; it is your responsibility to ensure that the attribute is only used on types that are optimized" + ); const CREATE: fn(Span) -> AttributeKind = |_| AttributeKind::RustcNonnullOptimizationGuaranteed; } @@ -1000,6 +1053,7 @@ impl NoArgsAttributeParser for RustcStrictCoherenceParser { Allow(Target::Union), Allow(Target::ForeignTy), ]); + const STABILITY: AttributeStability = unstable!(rustc_attrs); const CREATE: fn(Span) -> AttributeKind = AttributeKind::RustcStrictCoherence; } @@ -1009,8 +1063,8 @@ impl SingleAttributeParser for RustcReservationImplParser { const PATH: &[Symbol] = &[sym::rustc_reservation_impl]; const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::Impl { of_trait: true })]); - const TEMPLATE: AttributeTemplate = template!(NameValueStr: "reservation message"); + const STABILITY: AttributeStability = unstable!(rustc_attrs); fn convert(cx: &mut AcceptContext<'_, '_>, args: &ArgParser) -> Option { let nv = cx.expect_name_value(args, cx.attr_span, None)?; @@ -1025,6 +1079,7 @@ pub(crate) struct PreludeImportParser; impl NoArgsAttributeParser for PreludeImportParser { const PATH: &[Symbol] = &[sym::prelude_import]; const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::Use)]); + const STABILITY: AttributeStability = unstable!(prelude_import); const CREATE: fn(Span) -> AttributeKind = |_| AttributeKind::PreludeImport; } @@ -1034,6 +1089,10 @@ impl SingleAttributeParser for RustcDocPrimitiveParser { const PATH: &[Symbol] = &[sym::rustc_doc_primitive]; const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::Mod)]); const TEMPLATE: AttributeTemplate = template!(NameValueStr: "primitive name"); + const STABILITY: AttributeStability = unstable!( + rustc_attrs, + "the `#[rustc_doc_primitive]` attribute is used by the standard library to provide a way to generate documentation for primitive types" + ); fn convert(cx: &mut AcceptContext<'_, '_>, args: &ArgParser) -> Option { let nv = cx.expect_name_value(args, cx.attr_span, None)?; @@ -1048,6 +1107,7 @@ pub(crate) struct RustcIntrinsicParser; impl NoArgsAttributeParser for RustcIntrinsicParser { const PATH: &[Symbol] = &[sym::rustc_intrinsic]; const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::Fn)]); + const STABILITY: AttributeStability = unstable!(intrinsics); const CREATE: fn(Span) -> AttributeKind = |_| AttributeKind::RustcIntrinsic; } @@ -1056,6 +1116,7 @@ pub(crate) struct RustcIntrinsicConstStableIndirectParser; impl NoArgsAttributeParser for RustcIntrinsicConstStableIndirectParser { const PATH: &'static [Symbol] = &[sym::rustc_intrinsic_const_stable_indirect]; const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::Fn)]); + const STABILITY: AttributeStability = unstable!(rustc_attrs); const CREATE: fn(Span) -> AttributeKind = |_| AttributeKind::RustcIntrinsicConstStableIndirect; } @@ -1064,5 +1125,6 @@ pub(crate) struct RustcExhaustiveParser; impl NoArgsAttributeParser for RustcExhaustiveParser { const PATH: &'static [Symbol] = &[sym::rustc_must_match_exhaustively]; const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::Enum)]); + const STABILITY: AttributeStability = unstable!(rustc_attrs); const CREATE: fn(Span) -> AttributeKind = AttributeKind::RustcMustMatchExhaustively; } diff --git a/compiler/rustc_attr_parsing/src/attributes/semantics.rs b/compiler/rustc_attr_parsing/src/attributes/semantics.rs index b5c836e6119dc..3e341aa034c18 100644 --- a/compiler/rustc_attr_parsing/src/attributes/semantics.rs +++ b/compiler/rustc_attr_parsing/src/attributes/semantics.rs @@ -1,8 +1,11 @@ +use rustc_feature::AttributeStability; + use super::prelude::*; pub(crate) struct MayDangleParser; impl NoArgsAttributeParser for MayDangleParser { const PATH: &[Symbol] = &[sym::may_dangle]; const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(ALL_TARGETS); //FIXME Still checked fully in `check_attr.rs` + const STABILITY: AttributeStability = unstable!(dropck_eyepatch); const CREATE: fn(span: Span) -> AttributeKind = AttributeKind::MayDangle; } diff --git a/compiler/rustc_attr_parsing/src/attributes/stability.rs b/compiler/rustc_attr_parsing/src/attributes/stability.rs index 4d8943aa36684..b38bb6d535770 100644 --- a/compiler/rustc_attr_parsing/src/attributes/stability.rs +++ b/compiler/rustc_attr_parsing/src/attributes/stability.rs @@ -1,7 +1,7 @@ use std::num::NonZero; use rustc_errors::ErrorGuaranteed; -use rustc_feature::ACCEPTED_LANG_FEATURES; +use rustc_feature::{ACCEPTED_LANG_FEATURES, AttributeStability}; use rustc_hir::attrs::UnstableRemovedFeature; use rustc_hir::target::GenericParamKind; use rustc_hir::{ @@ -13,16 +13,6 @@ use super::prelude::*; use super::util::parse_version; use crate::session_diagnostics; -macro_rules! reject_outside_std { - ($cx: ident) => { - // Emit errors for non-staged-api crates. - if !$cx.features().staged_api() { - $cx.emit_err(session_diagnostics::StabilityOutsideStd { span: $cx.attr_span }); - return; - } - }; -} - const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[ Allow(Target::Fn), Allow(Target::Struct), @@ -76,8 +66,8 @@ impl AttributeParser for StabilityParser { ( &[sym::stable], template!(List: &[r#"feature = "name", since = "version""#]), + unstable!(staged_api), |this, cx, args| { - reject_outside_std!(cx); if !this.check_duplicate(cx) && let Some((feature, level)) = parse_stability(cx, args) { @@ -88,8 +78,8 @@ impl AttributeParser for StabilityParser { ( &[sym::unstable], template!(List: &[r#"feature = "name", reason = "...", issue = "N""#]), + unstable!(staged_api), |this, cx, args| { - reject_outside_std!(cx); if !this.check_duplicate(cx) && let Some((feature, level)) = parse_unstability(cx, args) { @@ -100,8 +90,8 @@ impl AttributeParser for StabilityParser { ( &[sym::rustc_allowed_through_unstable_modules], template!(NameValueStr: "deprecation message"), + unstable!(staged_api), |this, cx, args| { - reject_outside_std!(cx); let Some(nv) = cx.expect_name_value(args, cx.attr_span, None) else { return; }; @@ -158,8 +148,8 @@ impl AttributeParser for BodyStabilityParser { const ATTRIBUTES: AcceptMapping = &[( &[sym::rustc_default_body_unstable], template!(List: &[r#"feature = "name", reason = "...", issue = "N""#]), + unstable!(staged_api), |this, cx, args| { - reject_outside_std!(cx); if this.stability.is_some() { cx.dcx() .emit_err(session_diagnostics::MultipleStabilityLevels { span: cx.attr_span }); @@ -185,6 +175,7 @@ impl NoArgsAttributeParser for RustcConstStableIndirectParser { Allow(Target::Fn), Allow(Target::Method(MethodKind::Inherent)), ]); + const STABILITY: AttributeStability = unstable!(rustc_attrs); const CREATE: fn(Span) -> AttributeKind = |_| AttributeKind::RustcConstStableIndirect; } @@ -211,9 +202,8 @@ impl AttributeParser for ConstStabilityParser { ( &[sym::rustc_const_stable], template!(List: &[r#"feature = "name""#]), + unstable!(staged_api), |this, cx, args| { - reject_outside_std!(cx); - if !this.check_duplicate(cx) && let Some((feature, level)) = parse_stability(cx, args) { @@ -227,8 +217,8 @@ impl AttributeParser for ConstStabilityParser { ( &[sym::rustc_const_unstable], template!(List: &[r#"feature = "name""#]), + unstable!(staged_api), |this, cx, args| { - reject_outside_std!(cx); if !this.check_duplicate(cx) && let Some((feature, level)) = parse_unstability(cx, args) { @@ -239,8 +229,7 @@ impl AttributeParser for ConstStabilityParser { } }, ), - (&[sym::rustc_promotable], template!(Word), |this, cx, _| { - reject_outside_std!(cx); + (&[sym::rustc_promotable], template!(Word), unstable!(staged_api), |this, _cx, _| { this.promotable = true; }), ]; @@ -474,6 +463,7 @@ impl CombineAttributeParser for UnstableRemovedParser { const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::Crate)]); const TEMPLATE: AttributeTemplate = template!(List: &[r#"feature = "name", reason = "...", link = "...", since = "version""#]); + const STABILITY: AttributeStability = unstable!(staged_api); const CONVERT: ConvertFn = |items, _| AttributeKind::UnstableRemoved(items); @@ -486,11 +476,6 @@ impl CombineAttributeParser for UnstableRemovedParser { let mut link = None; let mut since = None; - if !cx.features().staged_api() { - cx.emit_err(session_diagnostics::StabilityOutsideStd { span: cx.attr_span }); - return None; - } - let list = cx.expect_list(args, cx.attr_span)?; for param in list.mixed() { diff --git a/compiler/rustc_attr_parsing/src/attributes/test_attrs.rs b/compiler/rustc_attr_parsing/src/attributes/test_attrs.rs index 7ffeb3d2194a3..d7b760da8dedd 100644 --- a/compiler/rustc_attr_parsing/src/attributes/test_attrs.rs +++ b/compiler/rustc_attr_parsing/src/attributes/test_attrs.rs @@ -1,3 +1,4 @@ +use rustc_feature::AttributeStability; use rustc_hir::attrs::RustcAbiAttrKind; use rustc_session::lint::builtin::ILL_FORMED_ATTRIBUTE_INPUT; @@ -14,6 +15,7 @@ impl SingleAttributeParser for IgnoreParser { Word, NameValueStr: "reason", "https://doc.rust-lang.org/reference/attributes/testing.html#the-ignore-attribute" ); + const STABILITY: AttributeStability = AttributeStability::Stable; fn convert(cx: &mut AcceptContext<'_, '_>, args: &ArgParser) -> Option { Some(AttributeKind::Ignore { @@ -55,6 +57,7 @@ impl SingleAttributeParser for ShouldPanicParser { Word, List: &[r#"expected = "reason""#], NameValueStr: "reason", "https://doc.rust-lang.org/reference/attributes/testing.html#the-should_panic-attribute" ); + const STABILITY: AttributeStability = AttributeStability::Stable; fn convert(cx: &mut AcceptContext<'_, '_>, args: &ArgParser) -> Option { Some(AttributeKind::ShouldPanic { @@ -82,6 +85,7 @@ impl SingleAttributeParser for ReexportTestHarnessMainParser { const PATH: &[Symbol] = &[sym::reexport_test_harness_main]; const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::Crate)]); const TEMPLATE: AttributeTemplate = template!(NameValueStr: "name"); + const STABILITY: AttributeStability = unstable!(custom_test_frameworks); fn convert(cx: &mut AcceptContext<'_, '_>, args: &ArgParser) -> Option { let nv = cx.expect_name_value( @@ -110,6 +114,7 @@ impl SingleAttributeParser for RustcAbiParser { Allow(Target::Method(MethodKind::Trait { body: false })), Allow(Target::Method(MethodKind::TraitImpl)), ]); + const STABILITY: AttributeStability = unstable!(rustc_attrs); fn convert(cx: &mut AcceptContext<'_, '_>, args: &ArgParser) -> Option { let Some(args) = args.as_list() else { @@ -146,6 +151,7 @@ pub(crate) struct RustcDelayedBugFromInsideQueryParser; impl NoArgsAttributeParser for RustcDelayedBugFromInsideQueryParser { const PATH: &[Symbol] = &[sym::rustc_delayed_bug_from_inside_query]; const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::Fn)]); + const STABILITY: AttributeStability = unstable!(rustc_attrs); const CREATE: fn(Span) -> AttributeKind = |_| AttributeKind::RustcDelayedBugFromInsideQuery; } @@ -160,6 +166,7 @@ impl NoArgsAttributeParser for RustcEvaluateWhereClausesParser { Allow(Target::Method(MethodKind::TraitImpl)), Allow(Target::Method(MethodKind::Trait { body: false })), ]); + const STABILITY: AttributeStability = unstable!(rustc_attrs); const CREATE: fn(Span) -> AttributeKind = |_| AttributeKind::RustcEvaluateWhereClauses; } @@ -169,6 +176,7 @@ impl SingleAttributeParser for TestRunnerParser { const PATH: &[Symbol] = &[sym::test_runner]; const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::Crate)]); const TEMPLATE: AttributeTemplate = template!(List: &["path"]); + const STABILITY: AttributeStability = unstable!(custom_test_frameworks); fn convert(cx: &mut AcceptContext<'_, '_>, args: &ArgParser) -> Option { let single = cx.expect_single_element_list(args, cx.attr_span)?; @@ -191,6 +199,7 @@ impl SingleAttributeParser for RustcTestMarkerParser { Allow(Target::Fn), Allow(Target::Static), ]); + const STABILITY: AttributeStability = unstable!(rustc_attrs); const TEMPLATE: AttributeTemplate = template!(NameValueStr: "test_path"); fn convert(cx: &mut AcceptContext<'_, '_>, args: &ArgParser) -> Option { diff --git a/compiler/rustc_attr_parsing/src/attributes/traits.rs b/compiler/rustc_attr_parsing/src/attributes/traits.rs index 919e8b6ce3974..505cdea2477bf 100644 --- a/compiler/rustc_attr_parsing/src/attributes/traits.rs +++ b/compiler/rustc_attr_parsing/src/attributes/traits.rs @@ -1,5 +1,7 @@ use std::mem; +use rustc_feature::AttributeStability; + use super::prelude::*; use crate::attributes::{NoArgsAttributeParser, SingleAttributeParser}; use crate::context::AcceptContext; @@ -13,6 +15,7 @@ impl SingleAttributeParser for RustcSkipDuringMethodDispatchParser { const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::Trait)]); const TEMPLATE: AttributeTemplate = template!(List: &["array, boxed_slice"]); + const STABILITY: AttributeStability = unstable!(rustc_attrs); fn convert(cx: &mut AcceptContext<'_, '_>, args: &ArgParser) -> Option { let mut array = false; @@ -50,6 +53,7 @@ pub(crate) struct RustcParenSugarParser; impl NoArgsAttributeParser for RustcParenSugarParser { const PATH: &[Symbol] = &[sym::rustc_paren_sugar]; const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::Trait)]); + const STABILITY: AttributeStability = unstable!(rustc_attrs); const CREATE: fn(Span) -> AttributeKind = |_| AttributeKind::RustcParenSugar; } @@ -64,6 +68,7 @@ impl NoArgsAttributeParser for MarkerParser { Warn(Target::Arm), Warn(Target::MacroDef), ]); + const STABILITY: AttributeStability = unstable!(marker_trait_attr); const CREATE: fn(Span) -> AttributeKind = |_| AttributeKind::Marker; } @@ -71,6 +76,7 @@ pub(crate) struct RustcDenyExplicitImplParser; impl NoArgsAttributeParser for RustcDenyExplicitImplParser { const PATH: &[Symbol] = &[sym::rustc_deny_explicit_impl]; const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::Trait)]); + const STABILITY: AttributeStability = unstable!(rustc_attrs); const CREATE: fn(Span) -> AttributeKind = |_| AttributeKind::RustcDenyExplicitImpl; } @@ -78,6 +84,7 @@ pub(crate) struct RustcDynIncompatibleTraitParser; impl NoArgsAttributeParser for RustcDynIncompatibleTraitParser { const PATH: &[Symbol] = &[sym::rustc_dyn_incompatible_trait]; const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::Trait)]); + const STABILITY: AttributeStability = unstable!(rustc_attrs); const CREATE: fn(Span) -> AttributeKind = AttributeKind::RustcDynIncompatibleTrait; } @@ -87,6 +94,7 @@ pub(crate) struct RustcSpecializationTraitParser; impl NoArgsAttributeParser for RustcSpecializationTraitParser { const PATH: &[Symbol] = &[sym::rustc_specialization_trait]; const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::Trait)]); + const STABILITY: AttributeStability = unstable!(rustc_attrs); const CREATE: fn(Span) -> AttributeKind = |_| AttributeKind::RustcSpecializationTrait; } @@ -94,6 +102,7 @@ pub(crate) struct RustcUnsafeSpecializationMarkerParser; impl NoArgsAttributeParser for RustcUnsafeSpecializationMarkerParser { const PATH: &[Symbol] = &[sym::rustc_unsafe_specialization_marker]; const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::Trait)]); + const STABILITY: AttributeStability = unstable!(rustc_attrs); const CREATE: fn(Span) -> AttributeKind = |_| AttributeKind::RustcUnsafeSpecializationMarker; } @@ -103,6 +112,7 @@ pub(crate) struct RustcCoinductiveParser; impl NoArgsAttributeParser for RustcCoinductiveParser { const PATH: &[Symbol] = &[sym::rustc_coinductive]; const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::Trait)]); + const STABILITY: AttributeStability = unstable!(rustc_attrs); const CREATE: fn(Span) -> AttributeKind = |_| AttributeKind::RustcCoinductive; } @@ -111,6 +121,7 @@ impl NoArgsAttributeParser for RustcAllowIncoherentImplParser { const PATH: &[Symbol] = &[sym::rustc_allow_incoherent_impl]; const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::Method(MethodKind::Inherent))]); + const STABILITY: AttributeStability = unstable!(rustc_attrs); const CREATE: fn(Span) -> AttributeKind = AttributeKind::RustcAllowIncoherentImpl; } @@ -119,5 +130,6 @@ impl NoArgsAttributeParser for FundamentalParser { const PATH: &[Symbol] = &[sym::fundamental]; const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::Struct), Allow(Target::Trait)]); + const STABILITY: AttributeStability = unstable!(fundamental); const CREATE: fn(Span) -> AttributeKind = |_| AttributeKind::Fundamental; } diff --git a/compiler/rustc_attr_parsing/src/attributes/transparency.rs b/compiler/rustc_attr_parsing/src/attributes/transparency.rs index c83377a818f80..ad40f30ef418a 100644 --- a/compiler/rustc_attr_parsing/src/attributes/transparency.rs +++ b/compiler/rustc_attr_parsing/src/attributes/transparency.rs @@ -1,3 +1,4 @@ +use rustc_feature::AttributeStability; use rustc_span::hygiene::Transparency; use super::prelude::*; @@ -9,6 +10,7 @@ impl SingleAttributeParser for RustcMacroTransparencyParser { const ON_DUPLICATE: OnDuplicate = OnDuplicate::Custom(|cx, used, unused| { cx.dcx().span_err(vec![used, unused], "multiple macro transparency attributes"); }); + const STABILITY: AttributeStability = unstable!(rustc_attrs); const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::MacroDef)]); const TEMPLATE: AttributeTemplate = template!(NameValueStr: ["transparent", "semiopaque", "opaque"]); diff --git a/compiler/rustc_attr_parsing/src/context.rs b/compiler/rustc_attr_parsing/src/context.rs index 750fe10a483fc..47e5e783f152b 100644 --- a/compiler/rustc_attr_parsing/src/context.rs +++ b/compiler/rustc_attr_parsing/src/context.rs @@ -8,7 +8,7 @@ use std::sync::LazyLock; use rustc_ast::{AttrStyle, MetaItemLit, Safety}; use rustc_data_structures::sync::{DynSend, DynSync}; use rustc_errors::{Diag, DiagCtxtHandle, Diagnostic, Level, MultiSpan}; -use rustc_feature::{AttrSuggestionStyle, AttributeTemplate}; +use rustc_feature::{AttrSuggestionStyle, AttributeStability, AttributeTemplate}; use rustc_hir::AttrPath; use rustc_hir::attrs::AttributeKind; use rustc_parse::parser::Recovery; @@ -81,6 +81,7 @@ pub(super) struct GroupTypeInnerAccept { pub(super) accept_fn: AcceptFn, pub(super) allowed_targets: AllowedTargets, pub(super) safety: AttributeSafety, + pub(super) stability: AttributeStability, pub(super) finalizer: FinalizeFn, } @@ -100,7 +101,7 @@ macro_rules! attribute_parsers { static STATE_OBJECT: RefCell<$names> = RefCell::new(<$names>::default()); }; - for (path, template, accept_fn) in <$names>::ATTRIBUTES { + for (path, template, stability, accept_fn) in <$names>::ATTRIBUTES { match accepters.entry(*path) { Entry::Vacant(e) => { e.insert(GroupTypeInnerAccept { @@ -111,6 +112,7 @@ macro_rules! attribute_parsers { }) }), safety: <$names as crate::attributes::AttributeParser>::SAFETY, + stability: *stability, allowed_targets: <$names as crate::attributes::AttributeParser>::ALLOWED_TARGETS, finalizer: |cx| { let state = STATE_OBJECT.take(); diff --git a/compiler/rustc_attr_parsing/src/interface.rs b/compiler/rustc_attr_parsing/src/interface.rs index ffe0451f29aa9..b0a243849d1b8 100644 --- a/compiler/rustc_attr_parsing/src/interface.rs +++ b/compiler/rustc_attr_parsing/src/interface.rs @@ -346,6 +346,7 @@ impl<'sess> AttributeParser<'sess> { accept.safety, &mut emit_lint, ); + self.check_attribute_stability(&attr_path, attr_span, accept.stability); let Some(args) = ArgParser::from_attr_args( args, diff --git a/compiler/rustc_attr_parsing/src/lib.rs b/compiler/rustc_attr_parsing/src/lib.rs index 3fa11d05d062f..640817a876eea 100644 --- a/compiler/rustc_attr_parsing/src/lib.rs +++ b/compiler/rustc_attr_parsing/src/lib.rs @@ -111,6 +111,7 @@ mod early_parsed; mod errors; mod safety; mod session_diagnostics; +mod stability; mod target_checking; pub mod validate_attr; diff --git a/compiler/rustc_attr_parsing/src/session_diagnostics.rs b/compiler/rustc_attr_parsing/src/session_diagnostics.rs index 9faf0606495b4..1c77446ee48df 100644 --- a/compiler/rustc_attr_parsing/src/session_diagnostics.rs +++ b/compiler/rustc_attr_parsing/src/session_diagnostics.rs @@ -323,13 +323,6 @@ pub(crate) struct ObjcSelectorExpectedStringLiteral { pub span: Span, } -#[derive(Diagnostic)] -#[diag("stability attributes may not be used outside of the standard library", code = E0734)] -pub(crate) struct StabilityOutsideStd { - #[primary_span] - pub span: Span, -} - #[derive(Diagnostic)] #[diag("expected at least one confusable name")] pub(crate) struct EmptyConfusables { diff --git a/compiler/rustc_attr_parsing/src/stability.rs b/compiler/rustc_attr_parsing/src/stability.rs new file mode 100644 index 0000000000000..f948cd29f8f20 --- /dev/null +++ b/compiler/rustc_attr_parsing/src/stability.rs @@ -0,0 +1,70 @@ +use rustc_feature::AttributeStability; +use rustc_hir::AttrPath; +use rustc_session::errors::feature_err; +use rustc_span::{Span, sym}; + +use crate::{AttributeParser, ShouldEmit}; + +#[macro_export] +macro_rules! unstable { + ($feat: ident $(, $notes:expr)*) => { + AttributeStability::Unstable { + gate_name: rustc_span::sym::$feat, + gate_check: rustc_feature::Features::$feat, + notes: &[$($notes),*], + } + }; +} + +impl<'sess> AttributeParser<'sess> { + pub fn check_attribute_stability( + &mut self, + attr_path: &AttrPath, + attr_span: Span, + expected_stability: AttributeStability, + ) { + if matches!(self.should_emit, ShouldEmit::Nothing) { + return; + } + + let AttributeStability::Unstable { gate_check, gate_name, notes } = expected_stability + else { + return; + }; + + if gate_check(self.features()) || attr_span.allows_unstable(gate_name) { + return; + } + + let (explain, default_notes): (String, &[String]) = match gate_name { + sym::rustc_attrs => ("use of an internal attribute".to_string(), &[ + format!("the `#[{attr_path}]` attribute is an internal implementation detail that will never be stable" + )]), + sym::staged_api => ("stability attributes may not be used outside of the standard library".to_string(), &[]), + sym::custom_mir => ("the `#[custom_mir]` attribute is just used for the Rust test suite".to_string(), &[]), + sym::allow_internal_unsafe => ("allow_internal_unsafe side-steps the unsafe_code lint".to_string(), &[]), + sym::allow_internal_unstable => ("allow_internal_unstable side-steps feature gating and stability checks".to_string(), &[]), + sym::compiler_builtins => ("the `#[compiler_builtins]` attribute is used to identify the `compiler_builtins` crate which contains compiler-rt intrinsics and will never be stable".to_string(), &[]), + sym::custom_test_frameworks => ("custom test frameworks are an unstable feature".to_string(), &[]), + sym::linkage => ("the `linkage` attribute is experimental and not portable across platforms".to_string(), &[]), + sym::dropck_eyepatch => ("`may_dangle` has unstable semantics and may be removed in the future".to_string(), &[]), + sym::intrinsics => ("the `#[rustc_intrinsic]` attribute is used to declare intrinsics as function items".to_string(), &[]), + sym::lang_items => ("lang items are subject to change".to_string(), &[]), + sym::prelude_import => ("`#[prelude_import]` is for use by rustc only".to_string(), &[]), + sym::profiler_runtime => ("the `#[profiler_runtime]` attribute is used to identify the `profiler_builtins` crate which contains the profiler runtime and will never be stable".to_string(), &[]), + sym::thread_local => ("`#[thread_local]` is an experimental feature, and does not currently handle destructors".to_string(), &[]), + _ => (format!("the `#[{attr_path}]` attribute is an experimental feature"), &[]), + }; + + let mut diag = feature_err(self.sess, gate_name, attr_span, explain); + + for note in default_notes { + diag.note(note.clone()); + } + for note in notes { + diag.note(*note); + } + + diag.emit(); + } +} diff --git a/compiler/rustc_error_codes/src/error_codes/E0734.md b/compiler/rustc_error_codes/src/error_codes/E0734.md index b912061ec42cc..b305ce2165bb9 100644 --- a/compiler/rustc_error_codes/src/error_codes/E0734.md +++ b/compiler/rustc_error_codes/src/error_codes/E0734.md @@ -1,8 +1,12 @@ +#### Note: this error code is no longer emitted by the compiler. + +This error code was replaced by the more generic E0658. + A stability attribute has been used outside of the standard library. Erroneous code example: -```compile_fail,E0734 +```compile_fail,E0658 #[stable(feature = "a", since = "b")] // invalid #[unstable(feature = "b", issue = "none")] // invalid fn foo(){} diff --git a/compiler/rustc_feature/src/builtin_attrs.rs b/compiler/rustc_feature/src/builtin_attrs.rs index c61b8d66af426..4cc70eb4460fd 100644 --- a/compiler/rustc_feature/src/builtin_attrs.rs +++ b/compiler/rustc_feature/src/builtin_attrs.rs @@ -79,6 +79,21 @@ pub enum AttributeGate { Ungated, } +#[derive(Clone, Debug, Copy)] +pub enum AttributeStability { + /// An attribute that is unstable behind a specified feature fagte + Unstable { + /// The feature gate, for example `rustc_attrs` for rustc_* attributes. + gate_name: Symbol, + /// Check function to be called during the `PostExpansionVisitor` pass, which will be one of the `Features::*` functions + gate_check: fn(&Features) -> bool, + /// Notes to be displayed when an attempt is made to use the attribute without its feature gate. + notes: &'static [&'static str], + }, + /// A stable attribute, can be used on all release channels + Stable, +} + // FIXME(jdonszelmann): move to rustc_hir::attrs /// A template that the attribute input must match. /// Only top-level shape (`#[attr]` vs `#[attr(...)]` vs `#[attr = ...]`) is considered now. diff --git a/compiler/rustc_feature/src/lib.rs b/compiler/rustc_feature/src/lib.rs index ce3ce6fcccee4..2351bb372bad0 100644 --- a/compiler/rustc_feature/src/lib.rs +++ b/compiler/rustc_feature/src/lib.rs @@ -129,8 +129,9 @@ pub fn find_feature_issue(feature: Symbol, issue: GateIssue) -> Option Date: Sun, 31 May 2026 14:33:01 +0200 Subject: [PATCH 24/46] Update uitests --- tests/ui/attributes/malformed-attrs.stderr | 18 +-- tests/ui/attributes/unstable_removed.stderr | 7 +- tests/ui/coroutine/gen_block.e2024.stderr | 22 +-- tests/ui/coroutine/gen_block.none.stderr | 40 +++--- .../feature-gate-rustc_const_unstable.rs | 4 +- .../feature-gate-rustc_const_unstable.stderr | 16 ++- .../feature-gate-staged_api.stderr | 12 +- ...43106-gating-of-builtin-attrs-error.stderr | 16 +-- .../issue-43106-gating-of-stable.rs | 14 ++ .../issue-43106-gating-of-stable.stderr | 134 ++++++++++++++++-- .../issue-43106-gating-of-unstable.rs | 14 ++ .../issue-43106-gating-of-unstable.stderr | 134 ++++++++++++++++-- tests/ui/lang-items/issue-83471.stderr | 38 ++--- .../stability-attribute/issue-106589.stderr | 12 +- ...ity-attribute-non-staged-force-unstable.rs | 10 +- ...attribute-non-staged-force-unstable.stderr | 41 +++++- .../stability-attribute-non-staged.rs | 10 +- .../stability-attribute-non-staged.stderr | 41 +++++- tests/ui/tool-attributes/diagnostic_item.rs | 1 + .../ui/tool-attributes/diagnostic_item.stderr | 1 + .../unstable_feature_bound_staged_api.stderr | 7 +- 21 files changed, 469 insertions(+), 123 deletions(-) diff --git a/tests/ui/attributes/malformed-attrs.stderr b/tests/ui/attributes/malformed-attrs.stderr index 4c9673bf80562..cfa8992a5590e 100644 --- a/tests/ui/attributes/malformed-attrs.stderr +++ b/tests/ui/attributes/malformed-attrs.stderr @@ -126,15 +126,6 @@ error: the `#[proc_macro_derive]` attribute is only usable with crates of the `p LL | #[proc_macro_derive] | ^^^^^^^^^^^^^^^^^^^^ -error[E0658]: allow_internal_unsafe side-steps the unsafe_code lint - --> $DIR/malformed-attrs.rs:215:1 - | -LL | #[allow_internal_unsafe = 1] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | - = help: add `#![feature(allow_internal_unsafe)]` to the crate attributes to enable - = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date - error[E0539]: malformed `windows_subsystem` attribute input --> $DIR/malformed-attrs.rs:26:1 | @@ -788,6 +779,15 @@ LL - #[macro_export = 18] LL + #[macro_export] | +error[E0658]: allow_internal_unsafe side-steps the unsafe_code lint + --> $DIR/malformed-attrs.rs:215:1 + | +LL | #[allow_internal_unsafe = 1] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: add `#![feature(allow_internal_unsafe)]` to the crate attributes to enable + = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date + error[E0565]: malformed `allow_internal_unsafe` attribute input --> $DIR/malformed-attrs.rs:215:1 | diff --git a/tests/ui/attributes/unstable_removed.stderr b/tests/ui/attributes/unstable_removed.stderr index e9c81b833f409..69a67188f3793 100644 --- a/tests/ui/attributes/unstable_removed.stderr +++ b/tests/ui/attributes/unstable_removed.stderr @@ -1,4 +1,4 @@ -error[E0734]: stability attributes may not be used outside of the standard library +error[E0658]: stability attributes may not be used outside of the standard library --> $DIR/unstable_removed.rs:9:1 | LL | / #![unstable_removed( @@ -9,6 +9,9 @@ LL | | link = "https://github.com/rust-lang/rust/issues/141617", LL | | since="1.92.0" LL | | )] | |__^ + | + = help: add `#![feature(staged_api)]` to the crate attributes to enable + = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date error[E0557]: feature `old_feature` has been removed --> $DIR/unstable_removed.rs:3:12 @@ -30,5 +33,5 @@ LL | #![feature(concat_idents)] error: aborting due to 3 previous errors -Some errors have detailed explanations: E0557, E0734. +Some errors have detailed explanations: E0557, E0658. For more information about an error, try `rustc --explain E0557`. diff --git a/tests/ui/coroutine/gen_block.e2024.stderr b/tests/ui/coroutine/gen_block.e2024.stderr index 347f111e79fdc..9590c143aa0d8 100644 --- a/tests/ui/coroutine/gen_block.e2024.stderr +++ b/tests/ui/coroutine/gen_block.e2024.stderr @@ -1,3 +1,14 @@ +error: `yield` can only be used in `#[coroutine]` closures, or `gen` blocks + --> $DIR/gen_block.rs:16:16 + | +LL | let _ = || yield true; + | ^^^^^^^^^^ + | +help: use `#[coroutine]` to make this closure a coroutine + | +LL | let _ = #[coroutine] || yield true; + | ++++++++++++ + error[E0658]: the `#[coroutine]` attribute is an experimental feature --> $DIR/gen_block.rs:20:13 | @@ -18,17 +29,6 @@ LL | let _ = #[coroutine] || {}; = help: add `#![feature(coroutines)]` to the crate attributes to enable = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date -error: `yield` can only be used in `#[coroutine]` closures, or `gen` blocks - --> $DIR/gen_block.rs:16:16 - | -LL | let _ = || yield true; - | ^^^^^^^^^^ - | -help: use `#[coroutine]` to make this closure a coroutine - | -LL | let _ = #[coroutine] || yield true; - | ++++++++++++ - error[E0282]: type annotations needed --> $DIR/gen_block.rs:7:13 | diff --git a/tests/ui/coroutine/gen_block.none.stderr b/tests/ui/coroutine/gen_block.none.stderr index b793033b5216a..2e8962ec066aa 100644 --- a/tests/ui/coroutine/gen_block.none.stderr +++ b/tests/ui/coroutine/gen_block.none.stderr @@ -44,26 +44,6 @@ LL | let _ = #[coroutine] || yield true; = help: add `#![feature(yield_expr)]` to the crate attributes to enable = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date -error[E0658]: the `#[coroutine]` attribute is an experimental feature - --> $DIR/gen_block.rs:20:13 - | -LL | let _ = #[coroutine] || yield true; - | ^^^^^^^^^^^^ - | - = note: see issue #43122 for more information - = help: add `#![feature(coroutines)]` to the crate attributes to enable - = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date - -error[E0658]: the `#[coroutine]` attribute is an experimental feature - --> $DIR/gen_block.rs:24:13 - | -LL | let _ = #[coroutine] || {}; - | ^^^^^^^^^^^^ - | - = note: see issue #43122 for more information - = help: add `#![feature(coroutines)]` to the crate attributes to enable - = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date - error[E0658]: yield syntax is experimental --> $DIR/gen_block.rs:16:16 | @@ -85,6 +65,16 @@ help: use `#[coroutine]` to make this closure a coroutine LL | let _ = #[coroutine] || yield true; | ++++++++++++ +error[E0658]: the `#[coroutine]` attribute is an experimental feature + --> $DIR/gen_block.rs:20:13 + | +LL | let _ = #[coroutine] || yield true; + | ^^^^^^^^^^^^ + | + = note: see issue #43122 for more information + = help: add `#![feature(coroutines)]` to the crate attributes to enable + = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date + error[E0658]: yield syntax is experimental --> $DIR/gen_block.rs:20:29 | @@ -95,6 +85,16 @@ LL | let _ = #[coroutine] || yield true; = help: add `#![feature(yield_expr)]` to the crate attributes to enable = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date +error[E0658]: the `#[coroutine]` attribute is an experimental feature + --> $DIR/gen_block.rs:24:13 + | +LL | let _ = #[coroutine] || {}; + | ^^^^^^^^^^^^ + | + = note: see issue #43122 for more information + = help: add `#![feature(coroutines)]` to the crate attributes to enable + = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date + error: aborting due to 11 previous errors Some errors have detailed explanations: E0422, E0658. diff --git a/tests/ui/feature-gates/feature-gate-rustc_const_unstable.rs b/tests/ui/feature-gates/feature-gate-rustc_const_unstable.rs index 522db1643fd4e..9618e57683ee9 100644 --- a/tests/ui/feature-gates/feature-gate-rustc_const_unstable.rs +++ b/tests/ui/feature-gates/feature-gate-rustc_const_unstable.rs @@ -1,6 +1,8 @@ // Test internal const fn feature gate. -#[rustc_const_unstable(feature="fzzzzzt")] //~ ERROR stability attributes may not be used outside +#[rustc_const_unstable(feature="fzzzzzt")] +//~^ ERROR stability attributes may not be used outside +//~| ERROR missing 'issue' pub const fn bazinga() {} fn main() { diff --git a/tests/ui/feature-gates/feature-gate-rustc_const_unstable.stderr b/tests/ui/feature-gates/feature-gate-rustc_const_unstable.stderr index 869764fa4569a..2c3763af9db72 100644 --- a/tests/ui/feature-gates/feature-gate-rustc_const_unstable.stderr +++ b/tests/ui/feature-gates/feature-gate-rustc_const_unstable.stderr @@ -1,9 +1,19 @@ -error[E0734]: stability attributes may not be used outside of the standard library +error[E0658]: stability attributes may not be used outside of the standard library + --> $DIR/feature-gate-rustc_const_unstable.rs:3:1 + | +LL | #[rustc_const_unstable(feature="fzzzzzt")] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: add `#![feature(staged_api)]` to the crate attributes to enable + = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date + +error[E0547]: missing 'issue' --> $DIR/feature-gate-rustc_const_unstable.rs:3:1 | LL | #[rustc_const_unstable(feature="fzzzzzt")] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -error: aborting due to 1 previous error +error: aborting due to 2 previous errors -For more information about this error, try `rustc --explain E0734`. +Some errors have detailed explanations: E0547, E0658. +For more information about an error, try `rustc --explain E0547`. diff --git a/tests/ui/feature-gates/feature-gate-staged_api.stderr b/tests/ui/feature-gates/feature-gate-staged_api.stderr index 86ba509ae7d10..6e717db165cdc 100644 --- a/tests/ui/feature-gates/feature-gate-staged_api.stderr +++ b/tests/ui/feature-gates/feature-gate-staged_api.stderr @@ -1,15 +1,21 @@ -error[E0734]: stability attributes may not be used outside of the standard library +error[E0658]: stability attributes may not be used outside of the standard library --> $DIR/feature-gate-staged_api.rs:1:1 | LL | #![stable(feature = "a", since = "3.3.3")] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: add `#![feature(staged_api)]` to the crate attributes to enable + = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date -error[E0734]: stability attributes may not be used outside of the standard library +error[E0658]: stability attributes may not be used outside of the standard library --> $DIR/feature-gate-staged_api.rs:8:1 | LL | #[stable(feature = "a", since = "3.3.3")] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: add `#![feature(staged_api)]` to the crate attributes to enable + = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date error: aborting due to 2 previous errors -For more information about this error, try `rustc --explain E0734`. +For more information about this error, try `rustc --explain E0658`. diff --git a/tests/ui/feature-gates/issue-43106-gating-of-builtin-attrs-error.stderr b/tests/ui/feature-gates/issue-43106-gating-of-builtin-attrs-error.stderr index db56f22acfba6..e0313bd7b80dd 100644 --- a/tests/ui/feature-gates/issue-43106-gating-of-builtin-attrs-error.stderr +++ b/tests/ui/feature-gates/issue-43106-gating-of-builtin-attrs-error.stderr @@ -1,3 +1,11 @@ +error: `#[macro_export]` attribute cannot be used on crates + --> $DIR/issue-43106-gating-of-builtin-attrs-error.rs:9:1 + | +LL | #![macro_export] + | ^^^^^^^^^^^^^^^^ + | + = help: `#[macro_export]` can only be applied to macro defs + error[E0658]: use of an internal attribute --> $DIR/issue-43106-gating-of-builtin-attrs-error.rs:11:1 | @@ -8,14 +16,6 @@ LL | #![rustc_main] = note: the `#[rustc_main]` attribute is an internal implementation detail that will never be stable = note: the `#[rustc_main]` attribute is used internally to specify test entry point function -error: `#[macro_export]` attribute cannot be used on crates - --> $DIR/issue-43106-gating-of-builtin-attrs-error.rs:9:1 - | -LL | #![macro_export] - | ^^^^^^^^^^^^^^^^ - | - = help: `#[macro_export]` can only be applied to macro defs - error: `#[rustc_main]` attribute cannot be used on crates --> $DIR/issue-43106-gating-of-builtin-attrs-error.rs:11:1 | diff --git a/tests/ui/feature-gates/issue-43106-gating-of-stable.rs b/tests/ui/feature-gates/issue-43106-gating-of-stable.rs index 621ec01bbe219..ee8b7f09d2801 100644 --- a/tests/ui/feature-gates/issue-43106-gating-of-stable.rs +++ b/tests/ui/feature-gates/issue-43106-gating-of-stable.rs @@ -6,29 +6,43 @@ #![stable()] //~^ ERROR stability attributes may not be used outside of the standard library +//~| ERROR missing 'since' +//~| ERROR missing 'feature' #[stable()] //~^ ERROR stability attributes may not be used outside of the standard library +//~| ERROR missing 'since' +//~| ERROR missing 'feature' mod stable { mod inner { #![stable()] //~^ ERROR stability attributes may not be used outside of the standard library + //~| ERROR missing 'since' + //~| ERROR missing 'feature' } #[stable()] //~^ ERROR stability attributes may not be used outside of the standard library + //~| ERROR missing 'since' + //~| ERROR missing 'feature' fn f() {} #[stable()] //~^ ERROR stability attributes may not be used outside of the standard library + //~| ERROR missing 'since' + //~| ERROR missing 'feature' struct S; #[stable()] //~^ ERROR stability attributes may not be used outside of the standard library + //~| ERROR missing 'since' + //~| ERROR missing 'feature' type T = S; #[stable()] //~^ ERROR stability attributes may not be used outside of the standard library + //~| ERROR missing 'since' + //~| ERROR missing 'feature' impl S {} } diff --git a/tests/ui/feature-gates/issue-43106-gating-of-stable.stderr b/tests/ui/feature-gates/issue-43106-gating-of-stable.stderr index bac3b018e2e43..6f9b6ab18c9f4 100644 --- a/tests/ui/feature-gates/issue-43106-gating-of-stable.stderr +++ b/tests/ui/feature-gates/issue-43106-gating-of-stable.stderr @@ -1,45 +1,151 @@ -error[E0734]: stability attributes may not be used outside of the standard library +error[E0658]: stability attributes may not be used outside of the standard library --> $DIR/issue-43106-gating-of-stable.rs:7:1 | LL | #![stable()] | ^^^^^^^^^^^^ + | + = help: add `#![feature(staged_api)]` to the crate attributes to enable + = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date + +error[E0546]: missing 'feature' + --> $DIR/issue-43106-gating-of-stable.rs:7:1 + | +LL | #![stable()] + | ^^^^^^^^^^^^ + +error[E0542]: missing 'since' + --> $DIR/issue-43106-gating-of-stable.rs:7:1 + | +LL | #![stable()] + | ^^^^^^^^^^^^ + +error[E0658]: stability attributes may not be used outside of the standard library + --> $DIR/issue-43106-gating-of-stable.rs:12:1 + | +LL | #[stable()] + | ^^^^^^^^^^^ + | + = help: add `#![feature(staged_api)]` to the crate attributes to enable + = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date + +error[E0546]: missing 'feature' + --> $DIR/issue-43106-gating-of-stable.rs:12:1 + | +LL | #[stable()] + | ^^^^^^^^^^^ -error[E0734]: stability attributes may not be used outside of the standard library - --> $DIR/issue-43106-gating-of-stable.rs:10:1 +error[E0542]: missing 'since' + --> $DIR/issue-43106-gating-of-stable.rs:12:1 | LL | #[stable()] | ^^^^^^^^^^^ -error[E0734]: stability attributes may not be used outside of the standard library - --> $DIR/issue-43106-gating-of-stable.rs:14:9 +error[E0658]: stability attributes may not be used outside of the standard library + --> $DIR/issue-43106-gating-of-stable.rs:18:9 | LL | #![stable()] | ^^^^^^^^^^^^ + | + = help: add `#![feature(staged_api)]` to the crate attributes to enable + = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date -error[E0734]: stability attributes may not be used outside of the standard library - --> $DIR/issue-43106-gating-of-stable.rs:18:5 +error[E0546]: missing 'feature' + --> $DIR/issue-43106-gating-of-stable.rs:18:9 + | +LL | #![stable()] + | ^^^^^^^^^^^^ + +error[E0542]: missing 'since' + --> $DIR/issue-43106-gating-of-stable.rs:18:9 + | +LL | #![stable()] + | ^^^^^^^^^^^^ + +error[E0658]: stability attributes may not be used outside of the standard library + --> $DIR/issue-43106-gating-of-stable.rs:24:5 | LL | #[stable()] | ^^^^^^^^^^^ + | + = help: add `#![feature(staged_api)]` to the crate attributes to enable + = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date -error[E0734]: stability attributes may not be used outside of the standard library - --> $DIR/issue-43106-gating-of-stable.rs:22:5 +error[E0546]: missing 'feature' + --> $DIR/issue-43106-gating-of-stable.rs:24:5 | LL | #[stable()] | ^^^^^^^^^^^ -error[E0734]: stability attributes may not be used outside of the standard library - --> $DIR/issue-43106-gating-of-stable.rs:26:5 +error[E0542]: missing 'since' + --> $DIR/issue-43106-gating-of-stable.rs:24:5 | LL | #[stable()] | ^^^^^^^^^^^ -error[E0734]: stability attributes may not be used outside of the standard library +error[E0658]: stability attributes may not be used outside of the standard library --> $DIR/issue-43106-gating-of-stable.rs:30:5 | +LL | #[stable()] + | ^^^^^^^^^^^ + | + = help: add `#![feature(staged_api)]` to the crate attributes to enable + = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date + +error[E0546]: missing 'feature' + --> $DIR/issue-43106-gating-of-stable.rs:30:5 + | +LL | #[stable()] + | ^^^^^^^^^^^ + +error[E0542]: missing 'since' + --> $DIR/issue-43106-gating-of-stable.rs:30:5 + | +LL | #[stable()] + | ^^^^^^^^^^^ + +error[E0658]: stability attributes may not be used outside of the standard library + --> $DIR/issue-43106-gating-of-stable.rs:36:5 + | +LL | #[stable()] + | ^^^^^^^^^^^ + | + = help: add `#![feature(staged_api)]` to the crate attributes to enable + = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date + +error[E0546]: missing 'feature' + --> $DIR/issue-43106-gating-of-stable.rs:36:5 + | +LL | #[stable()] + | ^^^^^^^^^^^ + +error[E0542]: missing 'since' + --> $DIR/issue-43106-gating-of-stable.rs:36:5 + | +LL | #[stable()] + | ^^^^^^^^^^^ + +error[E0658]: stability attributes may not be used outside of the standard library + --> $DIR/issue-43106-gating-of-stable.rs:42:5 + | +LL | #[stable()] + | ^^^^^^^^^^^ + | + = help: add `#![feature(staged_api)]` to the crate attributes to enable + = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date + +error[E0546]: missing 'feature' + --> $DIR/issue-43106-gating-of-stable.rs:42:5 + | +LL | #[stable()] + | ^^^^^^^^^^^ + +error[E0542]: missing 'since' + --> $DIR/issue-43106-gating-of-stable.rs:42:5 + | LL | #[stable()] | ^^^^^^^^^^^ -error: aborting due to 7 previous errors +error: aborting due to 21 previous errors -For more information about this error, try `rustc --explain E0734`. +Some errors have detailed explanations: E0542, E0546, E0658. +For more information about an error, try `rustc --explain E0542`. diff --git a/tests/ui/feature-gates/issue-43106-gating-of-unstable.rs b/tests/ui/feature-gates/issue-43106-gating-of-unstable.rs index d507bcd8f15df..eb19ebb2cc710 100644 --- a/tests/ui/feature-gates/issue-43106-gating-of-unstable.rs +++ b/tests/ui/feature-gates/issue-43106-gating-of-unstable.rs @@ -6,29 +6,43 @@ #![unstable()] //~^ ERROR stability attributes may not be used outside of the standard library +//~| ERROR missing 'issue' +//~| ERROR missing 'feature' #[unstable()] //~^ ERROR stability attributes may not be used outside of the standard library +//~| ERROR missing 'issue' +//~| ERROR missing 'feature' mod unstable { mod inner { #![unstable()] //~^ ERROR stability attributes may not be used outside of the standard library + //~| ERROR missing 'issue' + //~| ERROR missing 'feature' } #[unstable()] //~^ ERROR stability attributes may not be used outside of the standard library + //~| ERROR missing 'issue' + //~| ERROR missing 'feature' fn f() {} #[unstable()] //~^ ERROR stability attributes may not be used outside of the standard library + //~| ERROR missing 'issue' + //~| ERROR missing 'feature' struct S; #[unstable()] //~^ ERROR stability attributes may not be used outside of the standard library + //~| ERROR missing 'issue' + //~| ERROR missing 'feature' type T = S; #[unstable()] //~^ ERROR stability attributes may not be used outside of the standard library + //~| ERROR missing 'issue' + //~| ERROR missing 'feature' impl S {} } diff --git a/tests/ui/feature-gates/issue-43106-gating-of-unstable.stderr b/tests/ui/feature-gates/issue-43106-gating-of-unstable.stderr index 9ea60f838008c..f024f1e300117 100644 --- a/tests/ui/feature-gates/issue-43106-gating-of-unstable.stderr +++ b/tests/ui/feature-gates/issue-43106-gating-of-unstable.stderr @@ -1,45 +1,151 @@ -error[E0734]: stability attributes may not be used outside of the standard library +error[E0658]: stability attributes may not be used outside of the standard library --> $DIR/issue-43106-gating-of-unstable.rs:7:1 | LL | #![unstable()] | ^^^^^^^^^^^^^^ + | + = help: add `#![feature(staged_api)]` to the crate attributes to enable + = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date + +error[E0546]: missing 'feature' + --> $DIR/issue-43106-gating-of-unstable.rs:7:1 + | +LL | #![unstable()] + | ^^^^^^^^^^^^^^ + +error[E0547]: missing 'issue' + --> $DIR/issue-43106-gating-of-unstable.rs:7:1 + | +LL | #![unstable()] + | ^^^^^^^^^^^^^^ + +error[E0658]: stability attributes may not be used outside of the standard library + --> $DIR/issue-43106-gating-of-unstable.rs:12:1 + | +LL | #[unstable()] + | ^^^^^^^^^^^^^ + | + = help: add `#![feature(staged_api)]` to the crate attributes to enable + = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date + +error[E0546]: missing 'feature' + --> $DIR/issue-43106-gating-of-unstable.rs:12:1 + | +LL | #[unstable()] + | ^^^^^^^^^^^^^ -error[E0734]: stability attributes may not be used outside of the standard library - --> $DIR/issue-43106-gating-of-unstable.rs:10:1 +error[E0547]: missing 'issue' + --> $DIR/issue-43106-gating-of-unstable.rs:12:1 | LL | #[unstable()] | ^^^^^^^^^^^^^ -error[E0734]: stability attributes may not be used outside of the standard library - --> $DIR/issue-43106-gating-of-unstable.rs:14:9 +error[E0658]: stability attributes may not be used outside of the standard library + --> $DIR/issue-43106-gating-of-unstable.rs:18:9 | LL | #![unstable()] | ^^^^^^^^^^^^^^ + | + = help: add `#![feature(staged_api)]` to the crate attributes to enable + = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date -error[E0734]: stability attributes may not be used outside of the standard library - --> $DIR/issue-43106-gating-of-unstable.rs:18:5 +error[E0546]: missing 'feature' + --> $DIR/issue-43106-gating-of-unstable.rs:18:9 + | +LL | #![unstable()] + | ^^^^^^^^^^^^^^ + +error[E0547]: missing 'issue' + --> $DIR/issue-43106-gating-of-unstable.rs:18:9 + | +LL | #![unstable()] + | ^^^^^^^^^^^^^^ + +error[E0658]: stability attributes may not be used outside of the standard library + --> $DIR/issue-43106-gating-of-unstable.rs:24:5 | LL | #[unstable()] | ^^^^^^^^^^^^^ + | + = help: add `#![feature(staged_api)]` to the crate attributes to enable + = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date -error[E0734]: stability attributes may not be used outside of the standard library - --> $DIR/issue-43106-gating-of-unstable.rs:22:5 +error[E0546]: missing 'feature' + --> $DIR/issue-43106-gating-of-unstable.rs:24:5 | LL | #[unstable()] | ^^^^^^^^^^^^^ -error[E0734]: stability attributes may not be used outside of the standard library - --> $DIR/issue-43106-gating-of-unstable.rs:26:5 +error[E0547]: missing 'issue' + --> $DIR/issue-43106-gating-of-unstable.rs:24:5 | LL | #[unstable()] | ^^^^^^^^^^^^^ -error[E0734]: stability attributes may not be used outside of the standard library +error[E0658]: stability attributes may not be used outside of the standard library --> $DIR/issue-43106-gating-of-unstable.rs:30:5 | +LL | #[unstable()] + | ^^^^^^^^^^^^^ + | + = help: add `#![feature(staged_api)]` to the crate attributes to enable + = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date + +error[E0546]: missing 'feature' + --> $DIR/issue-43106-gating-of-unstable.rs:30:5 + | +LL | #[unstable()] + | ^^^^^^^^^^^^^ + +error[E0547]: missing 'issue' + --> $DIR/issue-43106-gating-of-unstable.rs:30:5 + | +LL | #[unstable()] + | ^^^^^^^^^^^^^ + +error[E0658]: stability attributes may not be used outside of the standard library + --> $DIR/issue-43106-gating-of-unstable.rs:36:5 + | +LL | #[unstable()] + | ^^^^^^^^^^^^^ + | + = help: add `#![feature(staged_api)]` to the crate attributes to enable + = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date + +error[E0546]: missing 'feature' + --> $DIR/issue-43106-gating-of-unstable.rs:36:5 + | +LL | #[unstable()] + | ^^^^^^^^^^^^^ + +error[E0547]: missing 'issue' + --> $DIR/issue-43106-gating-of-unstable.rs:36:5 + | +LL | #[unstable()] + | ^^^^^^^^^^^^^ + +error[E0658]: stability attributes may not be used outside of the standard library + --> $DIR/issue-43106-gating-of-unstable.rs:42:5 + | +LL | #[unstable()] + | ^^^^^^^^^^^^^ + | + = help: add `#![feature(staged_api)]` to the crate attributes to enable + = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date + +error[E0546]: missing 'feature' + --> $DIR/issue-43106-gating-of-unstable.rs:42:5 + | +LL | #[unstable()] + | ^^^^^^^^^^^^^ + +error[E0547]: missing 'issue' + --> $DIR/issue-43106-gating-of-unstable.rs:42:5 + | LL | #[unstable()] | ^^^^^^^^^^^^^ -error: aborting due to 7 previous errors +error: aborting due to 21 previous errors -For more information about this error, try `rustc --explain E0734`. +Some errors have detailed explanations: E0546, E0547, E0658. +For more information about an error, try `rustc --explain E0546`. diff --git a/tests/ui/lang-items/issue-83471.stderr b/tests/ui/lang-items/issue-83471.stderr index 1d5b1c4cd3ed7..b54080148e366 100644 --- a/tests/ui/lang-items/issue-83471.stderr +++ b/tests/ui/lang-items/issue-83471.stderr @@ -4,6 +4,25 @@ error[E0573]: expected type, found built-in attribute `export_name` LL | fn call(export_name); | ^^^^^^^^^^^ not a type +warning: anonymous parameters are deprecated and will be removed in the next edition + --> $DIR/issue-83471.rs:24:13 + | +LL | fn call(export_name); + | ^^^^^^^^^^^ help: try naming the parameter or explicitly ignoring it: `_: export_name` + | + = warning: this is accepted in the current edition (Rust 2015) but is a hard error in Rust 2018! + = note: for more information, see + = note: `#[warn(anonymous_parameters)]` (part of `#[warn(rust_2018_compatibility)]`) on by default + +error[E0718]: `fn` lang item must be applied to a trait with 1 generic argument + --> $DIR/issue-83471.rs:20:1 + | +LL | #[lang = "fn"] + | ^^^^^^^^^^^^^^ +... +LL | trait Fn { + | - this trait has 0 generic arguments + error[E0658]: lang items are subject to change --> $DIR/issue-83471.rs:8:1 | @@ -40,25 +59,6 @@ LL | #[lang = "fn"] = help: add `#![feature(lang_items)]` to the crate attributes to enable = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date -warning: anonymous parameters are deprecated and will be removed in the next edition - --> $DIR/issue-83471.rs:24:13 - | -LL | fn call(export_name); - | ^^^^^^^^^^^ help: try naming the parameter or explicitly ignoring it: `_: export_name` - | - = warning: this is accepted in the current edition (Rust 2015) but is a hard error in Rust 2018! - = note: for more information, see - = note: `#[warn(anonymous_parameters)]` (part of `#[warn(rust_2018_compatibility)]`) on by default - -error[E0718]: `fn` lang item must be applied to a trait with 1 generic argument - --> $DIR/issue-83471.rs:20:1 - | -LL | #[lang = "fn"] - | ^^^^^^^^^^^^^^ -... -LL | trait Fn { - | - this trait has 0 generic arguments - error[E0425]: cannot find function `a` in this scope --> $DIR/issue-83471.rs:30:5 | diff --git a/tests/ui/stability-attribute/issue-106589.stderr b/tests/ui/stability-attribute/issue-106589.stderr index c14ad2da04b69..40b0dcec651e1 100644 --- a/tests/ui/stability-attribute/issue-106589.stderr +++ b/tests/ui/stability-attribute/issue-106589.stderr @@ -1,15 +1,21 @@ -error[E0734]: stability attributes may not be used outside of the standard library +error[E0658]: stability attributes may not be used outside of the standard library --> $DIR/issue-106589.rs:3:1 | LL | #![stable(feature = "foo", since = "1.0.0")] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: add `#![feature(staged_api)]` to the crate attributes to enable + = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date -error[E0734]: stability attributes may not be used outside of the standard library +error[E0658]: stability attributes may not be used outside of the standard library --> $DIR/issue-106589.rs:6:1 | LL | #[unstable(feature = "foo", issue = "none")] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: add `#![feature(staged_api)]` to the crate attributes to enable + = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date error: aborting due to 2 previous errors -For more information about this error, try `rustc --explain E0734`. +For more information about this error, try `rustc --explain E0658`. diff --git a/tests/ui/stability-attribute/stability-attribute-non-staged-force-unstable.rs b/tests/ui/stability-attribute/stability-attribute-non-staged-force-unstable.rs index e7d3b67506a79..ebd70e0a1247b 100644 --- a/tests/ui/stability-attribute/stability-attribute-non-staged-force-unstable.rs +++ b/tests/ui/stability-attribute/stability-attribute-non-staged-force-unstable.rs @@ -1,5 +1,11 @@ //@ compile-flags:-Zforce-unstable-if-unmarked -#[unstable()] //~ ERROR: stability attributes may not be used -#[stable()] //~ ERROR: stability attributes may not be used +#[unstable()] +//~^ ERROR stability attributes may not be used +//~| ERROR missing 'feature' +//~| ERROR missing 'issue' +#[stable()] +//~^ ERROR stability attributes may not be used +//~| ERROR missing 'feature' +//~| ERROR missing 'since' fn main() {} diff --git a/tests/ui/stability-attribute/stability-attribute-non-staged-force-unstable.stderr b/tests/ui/stability-attribute/stability-attribute-non-staged-force-unstable.stderr index 45d965ea0a069..eac32a6cec2cc 100644 --- a/tests/ui/stability-attribute/stability-attribute-non-staged-force-unstable.stderr +++ b/tests/ui/stability-attribute/stability-attribute-non-staged-force-unstable.stderr @@ -1,15 +1,46 @@ -error[E0734]: stability attributes may not be used outside of the standard library +error[E0658]: stability attributes may not be used outside of the standard library --> $DIR/stability-attribute-non-staged-force-unstable.rs:3:1 | LL | #[unstable()] | ^^^^^^^^^^^^^ + | + = help: add `#![feature(staged_api)]` to the crate attributes to enable + = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date + +error[E0546]: missing 'feature' + --> $DIR/stability-attribute-non-staged-force-unstable.rs:3:1 + | +LL | #[unstable()] + | ^^^^^^^^^^^^^ + +error[E0547]: missing 'issue' + --> $DIR/stability-attribute-non-staged-force-unstable.rs:3:1 + | +LL | #[unstable()] + | ^^^^^^^^^^^^^ + +error[E0658]: stability attributes may not be used outside of the standard library + --> $DIR/stability-attribute-non-staged-force-unstable.rs:7:1 + | +LL | #[stable()] + | ^^^^^^^^^^^ + | + = help: add `#![feature(staged_api)]` to the crate attributes to enable + = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date + +error[E0546]: missing 'feature' + --> $DIR/stability-attribute-non-staged-force-unstable.rs:7:1 + | +LL | #[stable()] + | ^^^^^^^^^^^ -error[E0734]: stability attributes may not be used outside of the standard library - --> $DIR/stability-attribute-non-staged-force-unstable.rs:4:1 +error[E0542]: missing 'since' + --> $DIR/stability-attribute-non-staged-force-unstable.rs:7:1 | LL | #[stable()] | ^^^^^^^^^^^ -error: aborting due to 2 previous errors +error: aborting due to 6 previous errors -For more information about this error, try `rustc --explain E0734`. +Some errors have detailed explanations: E0542, E0546, E0547, E0658. +For more information about an error, try `rustc --explain E0542`. diff --git a/tests/ui/stability-attribute/stability-attribute-non-staged.rs b/tests/ui/stability-attribute/stability-attribute-non-staged.rs index 4015f2f971e69..0e16db32ce9d5 100644 --- a/tests/ui/stability-attribute/stability-attribute-non-staged.rs +++ b/tests/ui/stability-attribute/stability-attribute-non-staged.rs @@ -1,3 +1,9 @@ -#[unstable()] //~ ERROR: stability attributes may not be used -#[stable()] //~ ERROR: stability attributes may not be used +#[unstable()] +//~^ ERROR: stability attributes may not be used +//~| ERROR missing 'feature' +//~| ERROR missing 'issue' +#[stable()] +//~^ ERROR: stability attributes may not be used +//~| ERROR missing 'feature' +//~| ERROR missing 'since' fn main() {} diff --git a/tests/ui/stability-attribute/stability-attribute-non-staged.stderr b/tests/ui/stability-attribute/stability-attribute-non-staged.stderr index 391f3c2744d3c..2443e0300a3c5 100644 --- a/tests/ui/stability-attribute/stability-attribute-non-staged.stderr +++ b/tests/ui/stability-attribute/stability-attribute-non-staged.stderr @@ -1,15 +1,46 @@ -error[E0734]: stability attributes may not be used outside of the standard library +error[E0658]: stability attributes may not be used outside of the standard library --> $DIR/stability-attribute-non-staged.rs:1:1 | LL | #[unstable()] | ^^^^^^^^^^^^^ + | + = help: add `#![feature(staged_api)]` to the crate attributes to enable + = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date + +error[E0546]: missing 'feature' + --> $DIR/stability-attribute-non-staged.rs:1:1 + | +LL | #[unstable()] + | ^^^^^^^^^^^^^ + +error[E0547]: missing 'issue' + --> $DIR/stability-attribute-non-staged.rs:1:1 + | +LL | #[unstable()] + | ^^^^^^^^^^^^^ + +error[E0658]: stability attributes may not be used outside of the standard library + --> $DIR/stability-attribute-non-staged.rs:5:1 + | +LL | #[stable()] + | ^^^^^^^^^^^ + | + = help: add `#![feature(staged_api)]` to the crate attributes to enable + = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date + +error[E0546]: missing 'feature' + --> $DIR/stability-attribute-non-staged.rs:5:1 + | +LL | #[stable()] + | ^^^^^^^^^^^ -error[E0734]: stability attributes may not be used outside of the standard library - --> $DIR/stability-attribute-non-staged.rs:2:1 +error[E0542]: missing 'since' + --> $DIR/stability-attribute-non-staged.rs:5:1 | LL | #[stable()] | ^^^^^^^^^^^ -error: aborting due to 2 previous errors +error: aborting due to 6 previous errors -For more information about this error, try `rustc --explain E0734`. +Some errors have detailed explanations: E0542, E0546, E0547, E0658. +For more information about an error, try `rustc --explain E0542`. diff --git a/tests/ui/tool-attributes/diagnostic_item.rs b/tests/ui/tool-attributes/diagnostic_item.rs index 806b140feba38..60817ae31806f 100644 --- a/tests/ui/tool-attributes/diagnostic_item.rs +++ b/tests/ui/tool-attributes/diagnostic_item.rs @@ -1,5 +1,6 @@ #[rustc_diagnostic_item = "foomp"] //~^ ERROR use of an internal attribute [E0658] +//~| NOTE the `#[rustc_diagnostic_item]` attribute is an internal implementation detail that will never be stable //~| NOTE the `#[rustc_diagnostic_item]` attribute allows the compiler to reference types from the standard library for diagnostic purposes struct Foomp; fn main() {} diff --git a/tests/ui/tool-attributes/diagnostic_item.stderr b/tests/ui/tool-attributes/diagnostic_item.stderr index d37044281a136..35e00fd726c45 100644 --- a/tests/ui/tool-attributes/diagnostic_item.stderr +++ b/tests/ui/tool-attributes/diagnostic_item.stderr @@ -5,6 +5,7 @@ LL | #[rustc_diagnostic_item = "foomp"] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = help: add `#![feature(rustc_attrs)]` to the crate attributes to enable + = note: the `#[rustc_diagnostic_item]` attribute is an internal implementation detail that will never be stable = note: the `#[rustc_diagnostic_item]` attribute allows the compiler to reference types from the standard library for diagnostic purposes error: aborting due to 1 previous error diff --git a/tests/ui/unstable-feature-bound/unstable_feature_bound_staged_api.stderr b/tests/ui/unstable-feature-bound/unstable_feature_bound_staged_api.stderr index 35ab89e6ad99c..4f6ff5117a05e 100644 --- a/tests/ui/unstable-feature-bound/unstable_feature_bound_staged_api.stderr +++ b/tests/ui/unstable-feature-bound/unstable_feature_bound_staged_api.stderr @@ -1,9 +1,12 @@ -error[E0734]: stability attributes may not be used outside of the standard library +error[E0658]: stability attributes may not be used outside of the standard library --> $DIR/unstable_feature_bound_staged_api.rs:8:1 | LL | #[unstable_feature_bound(feat_bar)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: add `#![feature(staged_api)]` to the crate attributes to enable + = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date error: aborting due to 1 previous error -For more information about this error, try `rustc --explain E0734`. +For more information about this error, try `rustc --explain E0658`. From fae72718f60f62137c03353c88cef37c8cbfee3c Mon Sep 17 00:00:00 2001 From: Jonathan Brouwer Date: Sun, 31 May 2026 14:55:57 +0200 Subject: [PATCH 25/46] Remove stability information from `BUILTIN_ATTRIBUTES` --- .../rustc_attr_parsing/src/attributes/doc.rs | 2 +- .../rustc_attr_parsing/src/validate_attr.rs | 4 +- compiler/rustc_feature/src/builtin_attrs.rs | 693 ++++++------------ compiler/rustc_feature/src/lib.rs | 5 +- compiler/rustc_resolve/src/diagnostics.rs | 4 +- compiler/rustc_resolve/src/lib.rs | 4 +- 6 files changed, 213 insertions(+), 499 deletions(-) diff --git a/compiler/rustc_attr_parsing/src/attributes/doc.rs b/compiler/rustc_attr_parsing/src/attributes/doc.rs index b776ca0e68951..6e813080a5188 100644 --- a/compiler/rustc_attr_parsing/src/attributes/doc.rs +++ b/compiler/rustc_attr_parsing/src/attributes/doc.rs @@ -41,7 +41,7 @@ fn check_keyword(cx: &mut AcceptContext<'_, '_>, keyword: Symbol, span: Span) -> fn check_attribute(cx: &mut AcceptContext<'_, '_>, attribute: Symbol, span: Span) -> bool { // FIXME: This should support attributes with namespace like `diagnostic::do_not_recommend`. - if rustc_feature::BUILTIN_ATTRIBUTE_MAP.contains_key(&attribute) { + if rustc_feature::BUILTIN_ATTRIBUTE_MAP.contains(&attribute) { return true; } cx.emit_err(DocAttributeNotAttribute { span, attribute }); diff --git a/compiler/rustc_attr_parsing/src/validate_attr.rs b/compiler/rustc_attr_parsing/src/validate_attr.rs index fad773aed5af6..4df2b3dd32446 100644 --- a/compiler/rustc_attr_parsing/src/validate_attr.rs +++ b/compiler/rustc_attr_parsing/src/validate_attr.rs @@ -9,7 +9,7 @@ use rustc_ast::{ self as ast, AttrArgs, Attribute, DelimArgs, MetaItem, MetaItemInner, MetaItemKind, Safety, }; use rustc_errors::{Applicability, Diagnostic, PResult}; -use rustc_feature::{AttributeTemplate, BUILTIN_ATTRIBUTE_MAP, BuiltinAttribute, template}; +use rustc_feature::{AttributeTemplate, BUILTIN_ATTRIBUTE_MAP, template}; use rustc_hir::AttrPath; use rustc_parse::parse_in; use rustc_session::errors::report_lit_error; @@ -29,7 +29,7 @@ pub fn check_attr(psess: &ParseSess, attr: &Attribute) { // Check input tokens for built-in and key-value attributes. match builtin_attr_info { - Some(BuiltinAttribute { name, .. }) => { + Some(name) => { if AttributeParser::is_parsed_attribute(slice::from_ref(&name)) { return; } diff --git a/compiler/rustc_feature/src/builtin_attrs.rs b/compiler/rustc_feature/src/builtin_attrs.rs index 4cc70eb4460fd..673ac38a1d338 100644 --- a/compiler/rustc_feature/src/builtin_attrs.rs +++ b/compiler/rustc_feature/src/builtin_attrs.rs @@ -2,9 +2,8 @@ use std::sync::LazyLock; -use AttributeGate::*; use rustc_ast::ast::Safety; -use rustc_data_structures::fx::FxHashMap; +use rustc_data_structures::fx::FxHashSet; use rustc_hir::AttrStyle; use rustc_span::{Symbol, sym}; @@ -62,23 +61,6 @@ pub fn find_gated_cfg(pred: impl Fn(Symbol) -> bool) -> Option<&'static GatedCfg GATED_CFGS.iter().find(|(cfg_sym, ..)| pred(*cfg_sym)) } -#[derive(Clone, Debug, Copy)] -pub enum AttributeGate { - /// A gated attribute which requires a feature gate to be enabled. - Gated { - /// The feature gate, for example `#![feature(rustc_attrs)]` for rustc_* attributes. - feature: Symbol, - /// The error message displayed when an attempt is made to use the attribute without its feature gate. - message: &'static str, - /// Check function to be called during the `PostExpansionVisitor` pass. - check: fn(&Features) -> bool, - /// Notes to be displayed when an attempt is made to use the attribute without its feature gate. - notes: &'static [&'static str], - }, - /// Ungated attribute, can be used on all release channels - Ungated, -} - #[derive(Clone, Debug, Copy)] pub enum AttributeStability { /// An attribute that is unstable behind a specified feature fagte @@ -210,608 +192,341 @@ macro_rules! template { } }; } -macro_rules! ungated { - ($attr:ident $(,)?) => { - BuiltinAttribute { name: sym::$attr, gate: Ungated } - }; -} - -macro_rules! gated { - ($attr:ident, $gate:ident, $message:expr $(,)?) => { - BuiltinAttribute { - name: sym::$attr, - gate: Gated { - feature: sym::$gate, - message: $message, - check: Features::$gate, - notes: &[], - }, - } - }; - ($attr:ident, $message:expr $(,)?) => { - BuiltinAttribute { - name: sym::$attr, - gate: Gated { - feature: sym::$attr, - message: $message, - check: Features::$attr, - notes: &[], - }, - } - }; -} - -macro_rules! rustc_attr { - (TEST, $attr:ident $(,)?) => { - rustc_attr!( - $attr, - concat!( - "the `#[", - stringify!($attr), - "]` attribute is used for rustc unit tests" - ), - ) - }; - ($attr:ident $(, $notes:expr)* $(,)?) => { - BuiltinAttribute { - name: sym::$attr, - gate: Gated { - feature: sym::rustc_attrs, - message: "use of an internal attribute", - check: Features::rustc_attrs, - notes: &[ - concat!("the `#[", - stringify!($attr), - "]` attribute is an internal implementation detail that will never be stable"), - $($notes),* - ] - }, - } - }; -} - -macro_rules! experimental { - ($attr:ident) => { - concat!("the `#[", stringify!($attr), "]` attribute is an experimental feature") - }; -} - -pub struct BuiltinAttribute { - pub name: Symbol, - pub gate: AttributeGate, -} - /// Attributes that have a special meaning to rustc or rustdoc. #[rustfmt::skip] -pub static BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[ +pub static BUILTIN_ATTRIBUTES: &[Symbol] = &[ // ========================================================================== // Stable attributes: // ========================================================================== // Conditional compilation: - ungated!(cfg), - ungated!(cfg_attr), + sym::cfg, + sym::cfg_attr, // Testing: - ungated!(ignore), - ungated!(should_panic), + sym::ignore, + sym::should_panic, // Macros: - ungated!(automatically_derived), - ungated!(macro_use), - ungated!(macro_escape), // Deprecated synonym for `macro_use`. - ungated!(macro_export), - ungated!(proc_macro), - ungated!(proc_macro_derive), - ungated!(proc_macro_attribute), + sym::automatically_derived, + sym::macro_use, + sym::macro_escape, // Deprecated synonym for `macro_use`. + sym::macro_export, + sym::proc_macro, + sym::proc_macro_derive, + sym::proc_macro_attribute, // Lints: - ungated!(warn), - ungated!(allow), - ungated!(expect), - ungated!(forbid), - ungated!(deny), - ungated!(must_use), - gated!(must_not_suspend, experimental!(must_not_suspend)), - ungated!(deprecated), + sym::warn, + sym::allow, + sym::expect, + sym::forbid, + sym::deny, + sym::must_use, + sym::must_not_suspend, + sym::deprecated, // Crate properties: - ungated!(crate_name), - ungated!(crate_type), + sym::crate_name, + sym::crate_type, // ABI, linking, symbols, and FFI - ungated!(link), - ungated!(link_name), - ungated!(no_link), - ungated!(repr), + sym::link, + sym::link_name, + sym::no_link, + sym::repr, // FIXME(#82232, #143834): temporarily renamed to mitigate `#[align]` nameres ambiguity - gated!(rustc_align, fn_align, experimental!(rustc_align)), - gated!(rustc_align_static, static_align, experimental!(rustc_align_static)), - ungated!(export_name), - ungated!(link_section), - ungated!(no_mangle), - ungated!(used), - ungated!(link_ordinal), - ungated!(naked), + sym::rustc_align, + sym::rustc_align_static, + sym::export_name, + sym::link_section, + sym::no_mangle, + sym::used, + sym::link_ordinal, + sym::naked, // See `TyAndLayout::pass_indirectly_in_non_rustic_abis` for details. - rustc_attr!(rustc_pass_indirectly_in_non_rustic_abis, "types marked with `#[rustc_pass_indirectly_in_non_rustic_abis]` are always passed indirectly by non-Rustic ABIs"), + sym::rustc_pass_indirectly_in_non_rustic_abis, // Limits: - ungated!(recursion_limit), - ungated!(type_length_limit), - gated!(move_size_limit, large_assignments, experimental!(move_size_limit)), + sym::recursion_limit, + sym::type_length_limit, + sym::move_size_limit, // Entry point: - ungated!(no_main), + sym::no_main, // Modules, prelude, and resolution: - ungated!(path), - ungated!(no_std), - ungated!(no_implicit_prelude), - ungated!(non_exhaustive), + sym::path, + sym::no_std, + sym::no_implicit_prelude, + sym::non_exhaustive, // Runtime - ungated!(windows_subsystem), - ungated!(panic_handler), // RFC 2070 + sym::windows_subsystem, + sym::panic_handler, // RFC 2070 // Code generation: - ungated!(inline), - ungated!(cold), - ungated!(no_builtins), - ungated!(target_feature), - ungated!(track_caller), - ungated!(instruction_set), - gated!(force_target_feature, effective_target_features, experimental!(force_target_feature)), - gated!(sanitize, sanitize, experimental!(sanitize)), - gated!(coverage, coverage_attribute, experimental!(coverage)), - - ungated!(doc), + sym::inline, + sym::cold, + sym::no_builtins, + sym::target_feature, + sym::track_caller, + sym::instruction_set, + sym::force_target_feature, + sym::sanitize, + sym::coverage, + + sym::doc, // Debugging - ungated!(debugger_visualizer), - ungated!(collapse_debuginfo), + sym::debugger_visualizer, + sym::collapse_debuginfo, // ========================================================================== // Unstable attributes: // ========================================================================== // Linking: - gated!(export_stable, experimental!(export_stable)), + sym::export_stable, // Testing: - gated!(test_runner, custom_test_frameworks, "custom test frameworks are an unstable feature"), + sym::test_runner, - gated!(reexport_test_harness_main, custom_test_frameworks, "custom test frameworks are an unstable feature"), + sym::reexport_test_harness_main, // RFC #1268 - gated!(marker, marker_trait_attr, experimental!(marker)), - gated!(thread_local, "`#[thread_local]` is an experimental feature, and does not currently handle destructors"), - gated!(no_core, experimental!(no_core)), + sym::marker, + sym::thread_local, + sym::no_core, // RFC 2412 - gated!(optimize, optimize_attribute, experimental!(optimize)), + sym::optimize, - gated!(ffi_pure, experimental!(ffi_pure)), - gated!(ffi_const, experimental!(ffi_const)), - gated!(register_tool, experimental!(register_tool)), + sym::ffi_pure, + sym::ffi_const, + sym::register_tool, // `#[cfi_encoding = ""]` - gated!(cfi_encoding, experimental!(cfi_encoding)), + sym::cfi_encoding, // `#[coroutine]` attribute to be applied to closures to make them coroutines instead - gated!(coroutine, coroutines, experimental!(coroutine)), + sym::coroutine, // RFC 3543 // `#[patchable_function_entry(prefix_nops = m, entry_nops = n)]` - gated!(patchable_function_entry, experimental!(patchable_function_entry)), + sym::patchable_function_entry, // The `#[loop_match]` and `#[const_continue]` attributes are part of the // lang experiment for RFC 3720 tracked in: // // - https://github.com/rust-lang/rust/issues/132306 - gated!(const_continue, loop_match, experimental!(const_continue)), - gated!(loop_match, loop_match, experimental!(loop_match)), + sym::const_continue, + sym::loop_match, // The `#[pin_v2]` attribute is part of the `pin_ergonomics` experiment // that allows structurally pinning, tracked in: // // - https://github.com/rust-lang/rust/issues/130494 - gated!(pin_v2, pin_ergonomics, experimental!(pin_v2)), + sym::pin_v2, // ========================================================================== // Internal attributes: Stability, deprecation, and unsafe: // ========================================================================== - ungated!(feature), + sym::feature, // DuplicatesOk since it has its own validation - ungated!(stable), - ungated!(unstable), - ungated!(unstable_feature_bound), - ungated!(unstable_removed), - ungated!(rustc_const_unstable), - ungated!(rustc_const_stable), - ungated!(rustc_default_body_unstable), - gated!( - allow_internal_unstable, - "allow_internal_unstable side-steps feature gating and stability checks", - ), - gated!( - allow_internal_unsafe, - "allow_internal_unsafe side-steps the unsafe_code lint", - ), - gated!( - rustc_eii_foreign_item, - eii_internals, - "used internally to mark types with a `transparent` representation when it is guaranteed by the documentation", - ), - rustc_attr!( - rustc_allowed_through_unstable_modules, - "rustc_allowed_through_unstable_modules special cases accidental stabilizations of stable items \ - through unstable paths" - ), - rustc_attr!( - rustc_deprecated_safe_2024, - "`#[rustc_deprecated_safe_2024]` is used to declare functions unsafe across the edition 2024 boundary", - ), - rustc_attr!( - rustc_pub_transparent, - "used internally to mark types with a `transparent` representation when it is guaranteed by the documentation", - ), + sym::stable, + sym::unstable, + sym::unstable_feature_bound, + sym::unstable_removed, + sym::rustc_const_unstable, + sym::rustc_const_stable, + sym::rustc_default_body_unstable, + sym::allow_internal_unstable, + sym::allow_internal_unsafe, + sym::rustc_eii_foreign_item, + sym::rustc_allowed_through_unstable_modules, + sym::rustc_deprecated_safe_2024, + sym::rustc_pub_transparent, // ========================================================================== // Internal attributes: Type system related: // ========================================================================== - gated!(fundamental, experimental!(fundamental)), - gated!( - may_dangle, - dropck_eyepatch, - "`may_dangle` has unstable semantics and may be removed in the future", - ), + sym::fundamental, + sym::may_dangle, - rustc_attr!( - rustc_never_type_options, - "`rustc_never_type_options` is used to experiment with never type fallback and work on \ - never type stabilization" - ), + sym::rustc_never_type_options, // ========================================================================== // Internal attributes: Runtime related: // ========================================================================== - rustc_attr!(rustc_allocator), - rustc_attr!(rustc_nounwind), - rustc_attr!(rustc_reallocator), - rustc_attr!(rustc_deallocator), - rustc_attr!(rustc_allocator_zeroed), - rustc_attr!(rustc_allocator_zeroed_variant), - gated!( - default_lib_allocator, - allocator_internals, experimental!(default_lib_allocator), - ), - gated!( - needs_allocator, - allocator_internals, experimental!(needs_allocator), - ), - gated!( - panic_runtime, - experimental!(panic_runtime) - ), - gated!( - needs_panic_runtime, - experimental!(needs_panic_runtime) - ), - gated!( - compiler_builtins, - "the `#[compiler_builtins]` attribute is used to identify the `compiler_builtins` crate \ - which contains compiler-rt intrinsics and will never be stable", - ), - gated!( - profiler_runtime, - "the `#[profiler_runtime]` attribute is used to identify the `profiler_builtins` crate \ - which contains the profiler runtime and will never be stable", - ), + sym::rustc_allocator, + sym::rustc_nounwind, + sym::rustc_reallocator, + sym::rustc_deallocator, + sym::rustc_allocator_zeroed, + sym::rustc_allocator_zeroed_variant, + sym::default_lib_allocator, + sym::needs_allocator, + sym::panic_runtime, + sym::needs_panic_runtime, + sym::compiler_builtins, + sym::profiler_runtime, // ========================================================================== // Internal attributes, Linkage: // ========================================================================== - gated!( - linkage, - "the `linkage` attribute is experimental and not portable across platforms", - ), - rustc_attr!(rustc_std_internal_symbol), - rustc_attr!(rustc_objc_class), - rustc_attr!(rustc_objc_selector), + sym::linkage, + sym::rustc_std_internal_symbol, + sym::rustc_objc_class, + sym::rustc_objc_selector, // ========================================================================== // Internal attributes, Macro related: // ========================================================================== - rustc_attr!(rustc_builtin_macro), - rustc_attr!(rustc_proc_macro_decls), - rustc_attr!( - rustc_macro_transparency, - "used internally for testing macro hygiene", - ), - rustc_attr!(rustc_autodiff), - rustc_attr!(rustc_offload_kernel), + sym::rustc_builtin_macro, + sym::rustc_proc_macro_decls, + sym::rustc_macro_transparency, + sym::rustc_autodiff, + sym::rustc_offload_kernel, // Traces that are left when `cfg` and `cfg_attr` attributes are expanded. // The attributes are not gated, to avoid stability errors, but they cannot be used in stable // or unstable code directly because `sym::cfg_(attr_)trace` are not valid identifiers, they // can only be generated by the compiler. - ungated!(cfg_trace), - ungated!(cfg_attr_trace), + sym::cfg_trace, + sym::cfg_attr_trace, // ========================================================================== // Internal attributes, Diagnostics related: // ========================================================================== - rustc_attr!( - rustc_on_unimplemented, - "see `#[diagnostic::on_unimplemented]` for the stable equivalent of this attribute" - ), - rustc_attr!(rustc_confusables), + sym::rustc_on_unimplemented, + sym::rustc_confusables, // Enumerates "identity-like" conversion methods to suggest on type mismatch. - rustc_attr!(rustc_conversion_suggestion), + sym::rustc_conversion_suggestion, // Prevents field reads in the marked trait or method to be considered // during dead code analysis. - rustc_attr!(rustc_trivial_field_reads), + sym::rustc_trivial_field_reads, // Used by the `rustc::potential_query_instability` lint to warn methods which // might not be stable during incremental compilation. - rustc_attr!(rustc_lint_query_instability), + sym::rustc_lint_query_instability, // Used by the `rustc::untracked_query_information` lint to warn methods which // might not be stable during incremental compilation. - rustc_attr!(rustc_lint_untracked_query_information), + sym::rustc_lint_untracked_query_information, // Used by the `rustc::bad_opt_access` lint to identify `DebuggingOptions` and `CodegenOptions` // types (as well as any others in future). - rustc_attr!(rustc_lint_opt_ty), + sym::rustc_lint_opt_ty, // Used by the `rustc::bad_opt_access` lint on fields // types (as well as any others in future). - rustc_attr!(rustc_lint_opt_deny_field_access), + sym::rustc_lint_opt_deny_field_access, // ========================================================================== // Internal attributes, Const related: // ========================================================================== - rustc_attr!(rustc_promotable), - rustc_attr!(rustc_legacy_const_generics), + sym::rustc_promotable, + sym::rustc_legacy_const_generics, // Do not const-check this function's body. It will always get replaced during CTFE via `hook_special_const_fn`. - rustc_attr!( - rustc_do_not_const_check, - "`#[rustc_do_not_const_check]` skips const-check for this function's body", - ), - rustc_attr!( - rustc_const_stable_indirect, - "this is an internal implementation detail", - ), - rustc_attr!( - rustc_intrinsic_const_stable_indirect, - "this is an internal implementation detail", - ), - rustc_attr!( - rustc_allow_const_fn_unstable, - "rustc_allow_const_fn_unstable side-steps feature gating and stability checks" - ), + sym::rustc_do_not_const_check, + sym::rustc_const_stable_indirect, + sym::rustc_intrinsic_const_stable_indirect, + sym::rustc_allow_const_fn_unstable, // ========================================================================== // Internal attributes, Layout related: // ========================================================================== - rustc_attr!( - rustc_simd_monomorphize_lane_limit, - "the `#[rustc_simd_monomorphize_lane_limit]` attribute is just used by std::simd \ - for better error messages", - ), - rustc_attr!( - rustc_nonnull_optimization_guaranteed, - "the `#[rustc_nonnull_optimization_guaranteed]` attribute is just used to document \ - guaranteed niche optimizations in the standard library", - "the compiler does not even check whether the type indeed is being non-null-optimized; \ - it is your responsibility to ensure that the attribute is only used on types that are optimized", - ), + sym::rustc_simd_monomorphize_lane_limit, + sym::rustc_nonnull_optimization_guaranteed, // ========================================================================== // Internal attributes, Misc: // ========================================================================== - gated!( - lang, lang_items, - "lang items are subject to change", - ), - rustc_attr!( - rustc_as_ptr, - "`#[rustc_as_ptr]` is used to mark functions returning pointers to their inner allocations" - ), - rustc_attr!( - rustc_should_not_be_called_on_const_items, - "`#[rustc_should_not_be_called_on_const_items]` is used to mark methods that don't make sense to be called on interior mutable consts" - ), - rustc_attr!( - rustc_pass_by_value, - "`#[rustc_pass_by_value]` is used to mark types that must be passed by value instead of reference" - ), - rustc_attr!( - rustc_never_returns_null_ptr, - "`#[rustc_never_returns_null_ptr]` is used to mark functions returning non-null pointers" - ), - rustc_attr!( - rustc_no_implicit_autorefs, - "`#[rustc_no_implicit_autorefs]` is used to mark functions for which an autoref to the dereference of a raw pointer should not be used as an argument" - ), - rustc_attr!( - rustc_coherence_is_core, - "`#![rustc_coherence_is_core]` allows inherent methods on builtin types, only intended to be used in `core`" - ), - rustc_attr!( - rustc_coinductive, - "`#[rustc_coinductive]` changes a trait to be coinductive, allowing cycles in the trait solver" - ), - rustc_attr!( - rustc_allow_incoherent_impl, - "`#[rustc_allow_incoherent_impl]` has to be added to all impl items of an incoherent inherent impl" - ), - rustc_attr!( - rustc_preserve_ub_checks, - "`#![rustc_preserve_ub_checks]` prevents the designated crate from evaluating whether UB checks are enabled when optimizing MIR", - ), - rustc_attr!( - rustc_deny_explicit_impl, - "`#[rustc_deny_explicit_impl]` enforces that a trait can have no user-provided impls" - ), - rustc_attr!( - rustc_dyn_incompatible_trait, - "`#[rustc_dyn_incompatible_trait]` marks a trait as dyn-incompatible, \ - even if it otherwise satisfies the requirements to be dyn-compatible." - ), - rustc_attr!( - rustc_has_incoherent_inherent_impls, - "`#[rustc_has_incoherent_inherent_impls]` allows the addition of incoherent inherent impls for \ - the given type by annotating all impl items with `#[rustc_allow_incoherent_impl]`" - ), - rustc_attr!( - rustc_non_const_trait_method, - "`#[rustc_non_const_trait_method]` should only used by the standard library to mark trait methods \ - as non-const to allow large traits an easier transition to const" - ), - - BuiltinAttribute { - name: sym::rustc_diagnostic_item, - gate: Gated { - feature: sym::rustc_attrs, - message: "use of an internal attribute", - check: Features::rustc_attrs, - notes: &["the `#[rustc_diagnostic_item]` attribute allows the compiler to reference types \ - from the standard library for diagnostic purposes"], - }, - }, - gated!( - // Used in resolve: - prelude_import, - "`#[prelude_import]` is for use by rustc only", - ), - gated!( - rustc_paren_sugar, - unboxed_closures, "unboxed_closures are still evolving", - ), - rustc_attr!( - rustc_inherit_overflow_checks, - "the `#[rustc_inherit_overflow_checks]` attribute is just used to control \ - overflow checking behavior of several functions in the standard library that are inlined \ - across crates", - ), - rustc_attr!( - rustc_reservation_impl, - "the `#[rustc_reservation_impl]` attribute is internally used \ - for reserving `impl From for T` as part of the effort to stabilize `!`" - ), - rustc_attr!( - rustc_test_marker, - "the `#[rustc_test_marker]` attribute is used internally to track tests", - ), - rustc_attr!( - rustc_unsafe_specialization_marker, - "the `#[rustc_unsafe_specialization_marker]` attribute is used to check specializations" - ), - rustc_attr!( - rustc_specialization_trait, - "the `#[rustc_specialization_trait]` attribute is used to check specializations" - ), - rustc_attr!( - rustc_main, - "the `#[rustc_main]` attribute is used internally to specify test entry point function", - ), - rustc_attr!( - rustc_skip_during_method_dispatch, - "the `#[rustc_skip_during_method_dispatch]` attribute is used to exclude a trait \ - from method dispatch when the receiver is of the following type, for compatibility in \ - editions < 2021 (array) or editions < 2024 (boxed_slice)" - ), - rustc_attr!( - rustc_must_implement_one_of, - "the `#[rustc_must_implement_one_of]` attribute is used to change minimal complete \ - definition of a trait. Its syntax and semantics are highly experimental and will be \ - subject to change before stabilization", - ), - rustc_attr!( - rustc_doc_primitive, - "the `#[rustc_doc_primitive]` attribute is used by the standard library \ - to provide a way to generate documentation for primitive types", - ), - gated!( - rustc_intrinsic, intrinsics, - "the `#[rustc_intrinsic]` attribute is used to declare intrinsics as function items", - ), - rustc_attr!( - rustc_no_mir_inline, - "`#[rustc_no_mir_inline]` prevents the MIR inliner from inlining a function while not affecting codegen" - ), - rustc_attr!( - rustc_force_inline, - "`#[rustc_force_inline]` forces a free function to be inlined" - ), - rustc_attr!( - rustc_scalable_vector, - "`#[rustc_scalable_vector]` defines a scalable vector type" - ), - rustc_attr!( - rustc_must_match_exhaustively, - "enums with `#[rustc_must_match_exhaustively]` must be matched on with a match block that mentions all variants explicitly" - ), - rustc_attr!( - rustc_no_writable, - "`#[rustc_no_writable]` stops the compiler from considering mutable reference arguments of this function as implicitly writable" - ), + sym::lang, + sym::rustc_as_ptr, + sym::rustc_should_not_be_called_on_const_items, + sym::rustc_pass_by_value, + sym::rustc_never_returns_null_ptr, + sym::rustc_no_implicit_autorefs, + sym::rustc_coherence_is_core, + sym::rustc_coinductive, + sym::rustc_allow_incoherent_impl, + sym::rustc_preserve_ub_checks, + sym::rustc_deny_explicit_impl, + sym::rustc_dyn_incompatible_trait, + sym::rustc_has_incoherent_inherent_impls, + sym::rustc_non_const_trait_method, + + sym::rustc_diagnostic_item, + sym::prelude_import, + sym::rustc_paren_sugar, + sym::rustc_inherit_overflow_checks, + sym::rustc_reservation_impl, + sym::rustc_test_marker, + sym::rustc_unsafe_specialization_marker, + sym::rustc_specialization_trait, + sym::rustc_main, + sym::rustc_skip_during_method_dispatch, + sym::rustc_must_implement_one_of, + sym::rustc_doc_primitive, + sym::rustc_intrinsic, + sym::rustc_no_mir_inline, + sym::rustc_force_inline, + sym::rustc_scalable_vector, + sym::rustc_must_match_exhaustively, + sym::rustc_no_writable, // ========================================================================== // Internal attributes, Testing: // ========================================================================== - rustc_attr!(TEST, rustc_effective_visibility), - rustc_attr!(TEST, rustc_dump_inferred_outlives), - rustc_attr!(TEST, rustc_capture_analysis,), - rustc_attr!(TEST, rustc_insignificant_dtor), - rustc_attr!(TEST, rustc_no_implicit_bounds), - rustc_attr!(TEST, rustc_strict_coherence), - rustc_attr!(TEST, rustc_dump_variances), - rustc_attr!(TEST, rustc_dump_variances_of_opaques), - rustc_attr!(TEST, rustc_dump_hidden_type_of_opaques), - rustc_attr!(TEST, rustc_dump_layout), - rustc_attr!(TEST, rustc_abi), - rustc_attr!(TEST, rustc_regions), - rustc_attr!(TEST, rustc_delayed_bug_from_inside_query), - rustc_attr!(TEST, rustc_dump_user_args), - rustc_attr!(TEST, rustc_evaluate_where_clauses), - rustc_attr!(TEST, rustc_if_this_changed), - rustc_attr!(TEST, rustc_then_this_would_need), - rustc_attr!(TEST, rustc_clean), - rustc_attr!(TEST, rustc_partition_reused), - rustc_attr!(TEST, rustc_partition_codegened), - rustc_attr!(TEST, rustc_expected_cgu_reuse), - rustc_attr!(TEST, rustc_dump_symbol_name), - rustc_attr!(TEST, rustc_dump_def_path), - rustc_attr!(TEST, rustc_mir), - gated!( - custom_mir, "the `#[custom_mir]` attribute is just used for the Rust test suite", - ), - rustc_attr!(TEST, rustc_dump_item_bounds), - rustc_attr!(TEST, rustc_dump_predicates), - rustc_attr!(TEST, rustc_dump_def_parents), - rustc_attr!(TEST, rustc_dump_object_lifetime_defaults), - rustc_attr!(TEST, rustc_dump_vtable), - rustc_attr!(TEST, rustc_dummy), - rustc_attr!(TEST, pattern_complexity_limit), + sym::rustc_effective_visibility, + sym::rustc_dump_inferred_outlives, + sym::rustc_capture_analysis, + sym::rustc_insignificant_dtor, + sym::rustc_no_implicit_bounds, + sym::rustc_strict_coherence, + sym::rustc_dump_variances, + sym::rustc_dump_variances_of_opaques, + sym::rustc_dump_hidden_type_of_opaques, + sym::rustc_dump_layout, + sym::rustc_abi, + sym::rustc_regions, + sym::rustc_delayed_bug_from_inside_query, + sym::rustc_dump_user_args, + sym::rustc_evaluate_where_clauses, + sym::rustc_if_this_changed, + sym::rustc_then_this_would_need, + sym::rustc_clean, + sym::rustc_partition_reused, + sym::rustc_partition_codegened, + sym::rustc_expected_cgu_reuse, + sym::rustc_dump_symbol_name, + sym::rustc_dump_def_path, + sym::rustc_mir, + sym::custom_mir, + sym::rustc_dump_item_bounds, + sym::rustc_dump_predicates, + sym::rustc_dump_def_parents, + sym::rustc_dump_object_lifetime_defaults, + sym::rustc_dump_vtable, + sym::rustc_dummy, + sym::pattern_complexity_limit, ]; pub fn is_builtin_attr_name(name: Symbol) -> bool { BUILTIN_ATTRIBUTE_MAP.get(&name).is_some() } -pub static BUILTIN_ATTRIBUTE_MAP: LazyLock> = - LazyLock::new(|| { - let mut map = FxHashMap::default(); - for attr in BUILTIN_ATTRIBUTES.iter() { - if map.insert(attr.name, attr).is_some() { - panic!("duplicate builtin attribute `{}`", attr.name); - } +pub static BUILTIN_ATTRIBUTE_MAP: LazyLock> = LazyLock::new(|| { + let mut map = FxHashSet::default(); + for attr in BUILTIN_ATTRIBUTES.iter() { + if !map.insert(*attr) { + panic!("duplicate builtin attribute `{}`", attr); } - map - }); + } + map +}); diff --git a/compiler/rustc_feature/src/lib.rs b/compiler/rustc_feature/src/lib.rs index 2351bb372bad0..eea9474a9507b 100644 --- a/compiler/rustc_feature/src/lib.rs +++ b/compiler/rustc_feature/src/lib.rs @@ -129,9 +129,8 @@ pub fn find_feature_issue(feature: Symbol, issue: GateIssue) -> Option Resolver<'ra, 'tcx> { // These trace attributes are compiler-generated and have // deliberately invalid names. .filter(|attr| { - !matches!(attr.name, sym::cfg_trace | sym::cfg_attr_trace) + !matches!(**attr, sym::cfg_trace | sym::cfg_attr_trace) }) - .map(|attr| TypoSuggestion::typo_from_name(attr.name, res)), + .map(|attr| TypoSuggestion::typo_from_name(*attr, res)), ); } } diff --git a/compiler/rustc_resolve/src/lib.rs b/compiler/rustc_resolve/src/lib.rs index ffb2181bae3a9..7987c590f31fd 100644 --- a/compiler/rustc_resolve/src/lib.rs +++ b/compiler/rustc_resolve/src/lib.rs @@ -1838,9 +1838,9 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { builtin_attr_decls: BUILTIN_ATTRIBUTES .iter() .map(|builtin_attr| { - let res = Res::NonMacroAttr(NonMacroAttrKind::Builtin(builtin_attr.name)); + let res = Res::NonMacroAttr(NonMacroAttrKind::Builtin(*builtin_attr)); let decl = arenas.new_pub_def_decl(res, DUMMY_SP, LocalExpnId::ROOT); - (builtin_attr.name, decl) + (*builtin_attr, decl) }) .collect(), registered_tool_decls: registered_tools From d79693e09c40838c65163ae27fb48a7a6f0e7ae4 Mon Sep 17 00:00:00 2001 From: Jonathan Brouwer Date: Sun, 31 May 2026 15:17:03 +0200 Subject: [PATCH 26/46] Fix typo in `DebuggerVisualizerParser` --- compiler/rustc_attr_parsing/src/attributes/debugger.rs | 4 ++-- compiler/rustc_attr_parsing/src/context.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/compiler/rustc_attr_parsing/src/attributes/debugger.rs b/compiler/rustc_attr_parsing/src/attributes/debugger.rs index 0afda3aa052aa..8d2cff35905ed 100644 --- a/compiler/rustc_attr_parsing/src/attributes/debugger.rs +++ b/compiler/rustc_attr_parsing/src/attributes/debugger.rs @@ -3,9 +3,9 @@ use rustc_hir::attrs::{DebugVisualizer, DebuggerVisualizerType}; use super::prelude::*; -pub(crate) struct DebuggerViualizerParser; +pub(crate) struct DebuggerVisualizerParser; -impl CombineAttributeParser for DebuggerViualizerParser { +impl CombineAttributeParser for DebuggerVisualizerParser { const PATH: &[Symbol] = &[sym::debugger_visualizer]; const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::Mod), Allow(Target::Crate)]); diff --git a/compiler/rustc_attr_parsing/src/context.rs b/compiler/rustc_attr_parsing/src/context.rs index 47e5e783f152b..6bbc841964077 100644 --- a/compiler/rustc_attr_parsing/src/context.rs +++ b/compiler/rustc_attr_parsing/src/context.rs @@ -154,7 +154,7 @@ attribute_parsers!( // tidy-alphabetical-start Combine, Combine, - Combine, + Combine, Combine, Combine, Combine, From c238acdd0b978c2ebdc65a395034a9ec8e48b335 Mon Sep 17 00:00:00 2001 From: WhySoBad <49595640+WhySoBad@users.noreply.github.com> Date: Sun, 31 May 2026 16:34:23 +0200 Subject: [PATCH 27/46] fix: remove custom `check_epoll_wait` in epoll data race test --- .../fail-dep/libc/libc-epoll-data-race.rs | 54 ++++--------------- 1 file changed, 11 insertions(+), 43 deletions(-) diff --git a/src/tools/miri/tests/fail-dep/libc/libc-epoll-data-race.rs b/src/tools/miri/tests/fail-dep/libc/libc-epoll-data-race.rs index 336f39e0512eb..5ee42b1c3d7cc 100644 --- a/src/tools/miri/tests/fail-dep/libc/libc-epoll-data-race.rs +++ b/src/tools/miri/tests/fail-dep/libc/libc-epoll-data-race.rs @@ -6,33 +6,13 @@ // ensure deterministic schedule //@compile-flags: -Zmiri-deterministic-concurrency -use std::convert::TryInto; use std::thread; use std::thread::spawn; #[path = "../../utils/libc.rs"] mod libc_utils; - -#[track_caller] -fn check_epoll_wait(epfd: i32, expected_notifications: &[(u32, u64)]) { - let epoll_event = libc::epoll_event { events: 0, u64: 0 }; - let mut array: [libc::epoll_event; N] = [epoll_event; N]; - let maxsize = N; - let array_ptr = array.as_mut_ptr(); - let res = unsafe { libc::epoll_wait(epfd, array_ptr, maxsize.try_into().unwrap(), 0) }; - if res < 0 { - panic!("epoll_wait failed: {}", std::io::Error::last_os_error()); - } - assert_eq!( - res, - expected_notifications.len().try_into().unwrap(), - "got wrong number of notifications" - ); - let got_notifications = - unsafe { std::slice::from_raw_parts(array_ptr, res.try_into().unwrap()) }; - let got_notifications = got_notifications.iter().map(|e| (e.events, e.u64)).collect::>(); - assert_eq!(got_notifications, expected_notifications, "got wrong notifications"); -} +use libc_utils::epoll::*; +use libc_utils::*; fn main() { // Create an epoll instance. @@ -41,27 +21,17 @@ fn main() { // Create two socketpair instances. let mut fds_a = [-1, -1]; - let res = unsafe { libc::socketpair(libc::AF_UNIX, libc::SOCK_STREAM, 0, fds_a.as_mut_ptr()) }; - assert_eq!(res, 0); - + unsafe { + errno_check(libc::socketpair(libc::AF_UNIX, libc::SOCK_STREAM, 0, fds_a.as_mut_ptr())) + }; let mut fds_b = [-1, -1]; - let res = unsafe { libc::socketpair(libc::AF_UNIX, libc::SOCK_STREAM, 0, fds_b.as_mut_ptr()) }; - assert_eq!(res, 0); - - // Register both pipe read ends. - let mut ev = libc::epoll_event { - events: (libc::EPOLLIN | libc::EPOLLET) as _, - u64: u64::try_from(fds_a[1]).unwrap(), + unsafe { + errno_check(libc::socketpair(libc::AF_UNIX, libc::SOCK_STREAM, 0, fds_b.as_mut_ptr())) }; - let res = unsafe { libc::epoll_ctl(epfd, libc::EPOLL_CTL_ADD, fds_a[1], &mut ev) }; - assert_eq!(res, 0); - let mut ev = libc::epoll_event { - events: (libc::EPOLLIN | libc::EPOLLET) as _, - u64: u64::try_from(fds_b[1]).unwrap(), - }; - let res = unsafe { libc::epoll_ctl(epfd, libc::EPOLL_CTL_ADD, fds_b[1], &mut ev) }; - assert_eq!(res, 0); + // Register both pipe read ends. + epoll_ctl_add(epfd, fds_a[1], EPOLLIN | EPOLLET).unwrap(); + epoll_ctl_add(epfd, fds_b[1], EPOLLIN | EPOLLET).unwrap(); static mut VAL_ONE: u8 = 40; // This one will be read soundly. static mut VAL_TWO: u8 = 50; // This one will be read unsoundly. @@ -78,9 +48,7 @@ fn main() { thread::yield_now(); // With room for one event: check result from epoll_wait. - let expected_event = u32::try_from(libc::EPOLLIN).unwrap(); - let expected_value = u64::try_from(fds_a[1]).unwrap(); - check_epoll_wait::<1>(epfd, &[(expected_event, expected_value)]); + check_epoll_wait::<1>(epfd, &[Ev { events: EPOLLIN, data: fds_a[1] }], -1); // Since we only received one event, we have synchronized with // the write to VAL_ONE but not with the one to VAL_TWO. From cad55b530cab2f6ff2dbbcf44d6301d466dd578f Mon Sep 17 00:00:00 2001 From: WhySoBad <49595640+WhySoBad@users.noreply.github.com> Date: Sun, 31 May 2026 16:44:00 +0200 Subject: [PATCH 28/46] fix: remove custom `check_epoll_wait` in epoll block two thread test --- .../libc/libc_epoll_block_two_thread.rs | 61 ++++++------------- .../libc/libc_epoll_block_two_thread.stderr | 13 +++- 2 files changed, 30 insertions(+), 44 deletions(-) diff --git a/src/tools/miri/tests/fail-dep/libc/libc_epoll_block_two_thread.rs b/src/tools/miri/tests/fail-dep/libc/libc_epoll_block_two_thread.rs index 16ac2d643c4e5..89dde67d543bb 100644 --- a/src/tools/miri/tests/fail-dep/libc/libc_epoll_block_two_thread.rs +++ b/src/tools/miri/tests/fail-dep/libc/libc_epoll_block_two_thread.rs @@ -2,42 +2,14 @@ //@only-target: linux android illumos //@error-in-other-file: deadlock -use std::convert::TryInto; use std::thread; #[path = "../../utils/libc.rs"] mod libc_utils; +use libc_utils::epoll::*; // Using `as` cast since `EPOLLET` wraps around -const EPOLL_IN_OUT_ET: u32 = (libc::EPOLLIN | libc::EPOLLOUT | libc::EPOLLET) as _; - -#[track_caller] -fn check_epoll_wait( - epfd: i32, - expected_notifications: &[(u32, u64)], - timeout: i32, -) { - let epoll_event = libc::epoll_event { events: 0, u64: 0 }; - let mut array: [libc::epoll_event; N] = [epoll_event; N]; - let maxsize = N; - let array_ptr = array.as_mut_ptr(); - let res = unsafe { libc::epoll_wait(epfd, array_ptr, maxsize.try_into().unwrap(), timeout) }; - if res < 0 { - panic!("epoll_wait failed: {}", std::io::Error::last_os_error()); - } - assert_eq!( - res, - expected_notifications.len().try_into().unwrap(), - "got wrong number of notifications" - ); - let slice = unsafe { std::slice::from_raw_parts(array_ptr, res.try_into().unwrap()) }; - for (return_event, expected_event) in slice.iter().zip(expected_notifications.iter()) { - let event = return_event.events; - let data = return_event.u64; - assert_eq!(event, expected_event.0, "got wrong events"); - assert_eq!(data, expected_event.1, "got wrong data"); - } -} +const EPOLL_IN_OUT_ET: u32 = (EPOLLIN | EPOLLOUT | EPOLLET) as _; // Test if only one thread is unblocked if multiple threads blocked on same epfd. // Expected execution: @@ -57,23 +29,30 @@ fn main() { let fd2 = unsafe { libc::dup(fd1) }; // Register both with epoll. - let mut ev = libc::epoll_event { events: EPOLL_IN_OUT_ET, u64: fd1 as u64 }; - let res = unsafe { libc::epoll_ctl(epfd, libc::EPOLL_CTL_ADD, fd1, &mut ev) }; - assert_eq!(res, 0); - let mut ev = libc::epoll_event { events: EPOLL_IN_OUT_ET, u64: fd2 as u64 }; - let res = unsafe { libc::epoll_ctl(epfd, libc::EPOLL_CTL_ADD, fd2, &mut ev) }; - assert_eq!(res, 0); + epoll_ctl_add(epfd, fd1, EPOLL_IN_OUT_ET as i32).unwrap(); + epoll_ctl_add(epfd, fd2, EPOLL_IN_OUT_ET as i32).unwrap(); // Consume the initial events. - let expected = [(libc::EPOLLOUT as u32, fd1 as u64), (libc::EPOLLOUT as u32, fd2 as u64)]; - check_epoll_wait::<8>(epfd, &expected, -1); + check_epoll_wait::<8>( + epfd, + &[Ev { events: EPOLLOUT, data: fd1 }, Ev { events: EPOLLOUT, data: fd2 }], + -1, + ); let thread1 = thread::spawn(move || { - check_epoll_wait::<2>(epfd, &expected, -1); + check_epoll_wait::<2>( + epfd, + &[Ev { events: EPOLLOUT, data: fd1 }, Ev { events: EPOLLOUT, data: fd2 }], + -1, + ); }); let thread2 = thread::spawn(move || { - check_epoll_wait::<2>(epfd, &expected, -1); - //~^ERROR: deadlocked + //~vERROR: deadlocked + check_epoll_wait::<2>( + epfd, + &[Ev { events: EPOLLOUT, data: fd1 }, Ev { events: EPOLLOUT, data: fd2 }], + -1, + ); }); // Yield so the threads are both blocked. thread::yield_now(); diff --git a/src/tools/miri/tests/fail-dep/libc/libc_epoll_block_two_thread.stderr b/src/tools/miri/tests/fail-dep/libc/libc_epoll_block_two_thread.stderr index 4f0f9daa3e0e6..6919c2e6f8b96 100644 --- a/src/tools/miri/tests/fail-dep/libc/libc_epoll_block_two_thread.stderr +++ b/src/tools/miri/tests/fail-dep/libc/libc_epoll_block_two_thread.stderr @@ -18,8 +18,12 @@ LL | let ret = unsafe { libc::pthread_join(id, ptr::null_mut()) }; error: the evaluated program deadlocked --> tests/fail-dep/libc/libc_epoll_block_two_thread.rs:LL:CC | -LL | check_epoll_wait::(epfd, &expected, -1); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ thread got stuck here +LL | / check_epoll_wait::( +LL | | epfd, +LL | | &[Ev { events: EPOLLOUT, data: fd1 }, Ev { events: EPOLLOUT, data: fd2 }], +LL | | -1, +LL | | ); + | |_________^ thread got stuck here | = note: this is on thread `unnamed-ID` note: the current function got called indirectly due to this code @@ -27,8 +31,11 @@ note: the current function got called indirectly due to this code | LL | let thread2 = thread::spawn(move || { | ___________________^ -LL | | check_epoll_wait::(epfd, &expected, -1); LL | | +LL | | check_epoll_wait::( +LL | | epfd, +... | +LL | | ); LL | | }); | |______^ From c1572df7ead234e0d60f0562b9a4b84ded5534c8 Mon Sep 17 00:00:00 2001 From: WhySoBad <49595640+WhySoBad@users.noreply.github.com> Date: Sun, 31 May 2026 17:04:34 +0200 Subject: [PATCH 29/46] feat: add `check_epoll_wait_explicit`, remove generic events length --- .../fail-dep/libc/libc-epoll-data-race.rs | 2 +- .../libc/libc_epoll_block_two_thread.rs | 6 +- .../socket-connect-after-failed-connection.rs | 2 +- .../pass-dep/libc/libc-epoll-blocking.rs | 26 +++--- .../pass-dep/libc/libc-epoll-no-blocking.rs | 92 +++++++++---------- .../libc/libc-socket-no-blocking-epoll.rs | 24 ++--- src/tools/miri/tests/utils/libc.rs | 33 +++++-- 7 files changed, 101 insertions(+), 84 deletions(-) diff --git a/src/tools/miri/tests/fail-dep/libc/libc-epoll-data-race.rs b/src/tools/miri/tests/fail-dep/libc/libc-epoll-data-race.rs index 5ee42b1c3d7cc..2a0a0054ab7e6 100644 --- a/src/tools/miri/tests/fail-dep/libc/libc-epoll-data-race.rs +++ b/src/tools/miri/tests/fail-dep/libc/libc-epoll-data-race.rs @@ -48,7 +48,7 @@ fn main() { thread::yield_now(); // With room for one event: check result from epoll_wait. - check_epoll_wait::<1>(epfd, &[Ev { events: EPOLLIN, data: fds_a[1] }], -1); + check_epoll_wait_explicit(epfd, &[Ev { events: EPOLLIN, data: fds_a[1] }], 1, -1); // Since we only received one event, we have synchronized with // the write to VAL_ONE but not with the one to VAL_TWO. diff --git a/src/tools/miri/tests/fail-dep/libc/libc_epoll_block_two_thread.rs b/src/tools/miri/tests/fail-dep/libc/libc_epoll_block_two_thread.rs index 89dde67d543bb..eb74a5c6b38e6 100644 --- a/src/tools/miri/tests/fail-dep/libc/libc_epoll_block_two_thread.rs +++ b/src/tools/miri/tests/fail-dep/libc/libc_epoll_block_two_thread.rs @@ -33,14 +33,14 @@ fn main() { epoll_ctl_add(epfd, fd2, EPOLL_IN_OUT_ET as i32).unwrap(); // Consume the initial events. - check_epoll_wait::<8>( + check_epoll_wait( epfd, &[Ev { events: EPOLLOUT, data: fd1 }, Ev { events: EPOLLOUT, data: fd2 }], -1, ); let thread1 = thread::spawn(move || { - check_epoll_wait::<2>( + check_epoll_wait( epfd, &[Ev { events: EPOLLOUT, data: fd1 }, Ev { events: EPOLLOUT, data: fd2 }], -1, @@ -48,7 +48,7 @@ fn main() { }); let thread2 = thread::spawn(move || { //~vERROR: deadlocked - check_epoll_wait::<2>( + check_epoll_wait( epfd, &[Ev { events: EPOLLOUT, data: fd1 }, Ev { events: EPOLLOUT, data: fd2 }], -1, diff --git a/src/tools/miri/tests/fail-dep/libc/socket-connect-after-failed-connection.rs b/src/tools/miri/tests/fail-dep/libc/socket-connect-after-failed-connection.rs index babd2d8410207..2ab5d5476dea4 100644 --- a/src/tools/miri/tests/fail-dep/libc/socket-connect-after-failed-connection.rs +++ b/src/tools/miri/tests/fail-dep/libc/socket-connect-after-failed-connection.rs @@ -41,7 +41,7 @@ fn main() { epoll_ctl_add(epfd, client_sockfd, EPOLLOUT | EPOLLET | libc::EPOLLERR).unwrap(); // Wait until the socket has an error. - check_epoll_wait::<8>( + check_epoll_wait( epfd, &[Ev { events: libc::EPOLLERR | EPOLLOUT | EPOLLHUP, data: client_sockfd }], -1, diff --git a/src/tools/miri/tests/pass-dep/libc/libc-epoll-blocking.rs b/src/tools/miri/tests/pass-dep/libc/libc-epoll-blocking.rs index 95f82cfba9f61..f9794d76fcd00 100644 --- a/src/tools/miri/tests/pass-dep/libc/libc-epoll-blocking.rs +++ b/src/tools/miri/tests/pass-dep/libc/libc-epoll-blocking.rs @@ -41,15 +41,15 @@ fn test_epoll_block_without_notification() { epoll_ctl_add(epfd, fd, EPOLLIN | EPOLLOUT | EPOLLET_OR_ZERO).unwrap(); // epoll_wait to clear notification. - check_epoll_wait::<8>(epfd, &[Ev { events: EPOLLOUT, data: fd }], -1); + check_epoll_wait(epfd, &[Ev { events: EPOLLOUT, data: fd }], -1); if cfg!(edge_triggered) { // This epoll wait blocks, and timeout without notification. - check_epoll_wait::<1>(epfd, &[], 5); + check_epoll_wait(epfd, &[], 5); } else { // In level-triggered mode we should receive the same events // as before without timing out. - check_epoll_wait::<8>(epfd, &[Ev { events: EPOLLOUT, data: fd }], -1); + check_epoll_wait(epfd, &[Ev { events: EPOLLOUT, data: fd }], -1); } } @@ -68,7 +68,7 @@ fn test_epoll_block_then_unblock() { epoll_ctl_add(epfd, fds[0], EPOLLIN | EPOLLOUT | EPOLLET_OR_ZERO).unwrap(); // epoll_wait to clear notification. - check_epoll_wait::<2>(epfd, &[Ev { events: EPOLLOUT, data: fds[0] }], -1); + check_epoll_wait(epfd, &[Ev { events: EPOLLOUT, data: fds[0] }], -1); let thread1 = thread::spawn(move || { thread::yield_now(); @@ -80,11 +80,11 @@ fn test_epoll_block_then_unblock() { // Edge-triggered epoll will block until the write succeeds and the buffer // becomes readable. This is because we already read the writable edge // before so at the time of calling `epoll_wait` there is no active readiness. - check_epoll_wait::<2>(epfd, &[Ev { events: EPOLLIN | EPOLLOUT, data: fds[0] }], 10); + check_epoll_wait(epfd, &[Ev { events: EPOLLIN | EPOLLOUT, data: fds[0] }], 10); } else { // Level-triggered epoll won't wait for the write to succeed because // _some_ readiness is already set (in this case the EPOLLOUT). - check_epoll_wait::<2>(epfd, &[Ev { events: EPOLLOUT, data: fds[0] }], -1); + check_epoll_wait(epfd, &[Ev { events: EPOLLOUT, data: fds[0] }], -1); } thread1.join().unwrap(); @@ -104,23 +104,23 @@ fn test_notification_after_timeout() { epoll_ctl_add(epfd, fds[0], EPOLLIN | EPOLLOUT | EPOLLET_OR_ZERO).unwrap(); // epoll_wait to clear notification. - check_epoll_wait::<2>(epfd, &[Ev { events: EPOLLOUT, data: fds[0] }], -1); + check_epoll_wait(epfd, &[Ev { events: EPOLLOUT, data: fds[0] }], -1); if cfg!(edge_triggered) { // Edge-triggered epoll wait times out without notification because // we just processed the edge. - check_epoll_wait::<1>(epfd, &[], 10); + check_epoll_wait(epfd, &[], 10); } else { // Level-triggered epoll just returns the same events as before // without blocking. - check_epoll_wait::<2>(epfd, &[Ev { events: EPOLLOUT, data: fds[0] }], -1); + check_epoll_wait(epfd, &[Ev { events: EPOLLOUT, data: fds[0] }], -1); } // Trigger epoll notification after timeout. write_all(fds[1], b"abcde").unwrap(); // Check the result of the notification. - check_epoll_wait::<2>(epfd, &[Ev { events: EPOLLIN | EPOLLOUT, data: fds[0] }], 10); + check_epoll_wait(epfd, &[Ev { events: EPOLLIN | EPOLLOUT, data: fds[0] }], 10); } // This test shows a data race before epoll had vector clocks added. @@ -144,7 +144,7 @@ fn test_epoll_race() { }); thread::yield_now(); // epoll_wait for EPOLLIN. - check_epoll_wait::<8>(epfd, &[Ev { events: EPOLLIN, data: fd }], -1); + check_epoll_wait(epfd, &[Ev { events: EPOLLIN, data: fd }], -1); // Read from the static mut variable. assert_eq!(unsafe { VAL }, 1); thread1.join().unwrap(); @@ -165,7 +165,7 @@ fn wakeup_on_new_interest() { // Block a thread on the epoll instance. let t = std::thread::spawn(move || { - check_epoll_wait::<8>(epfd, &[Ev { events: EPOLLIN | EPOLLOUT, data: fds[1] }], -1); + check_epoll_wait(epfd, &[Ev { events: EPOLLIN | EPOLLOUT, data: fds[1] }], -1); }); // Ensure the thread is blocked. std::thread::yield_now(); @@ -196,7 +196,7 @@ fn multiple_events_wake_multiple_threads() { // Consume the initial events. let expected = [Ev { events: EPOLLOUT, data: fd1 }, Ev { events: EPOLLOUT, data: fd2 }]; - check_epoll_wait::<8>(epfd, &expected, -1); + check_epoll_wait(epfd, &expected, -1); // Block two threads on the epoll, both wanting to get just one event. let t1 = thread::spawn(move || { diff --git a/src/tools/miri/tests/pass-dep/libc/libc-epoll-no-blocking.rs b/src/tools/miri/tests/pass-dep/libc/libc-epoll-no-blocking.rs index 10fc6a5395d3b..969004bfbb6f2 100644 --- a/src/tools/miri/tests/pass-dep/libc/libc-epoll-no-blocking.rs +++ b/src/tools/miri/tests/pass-dep/libc/libc-epoll-no-blocking.rs @@ -53,16 +53,16 @@ fn test_epoll_socketpair() { epoll_ctl_add(epfd, fds[1], EPOLLIN | EPOLLOUT | EPOLLRDHUP | EPOLLET_OR_ZERO).unwrap(); // Check result from epoll_wait. - check_epoll_wait_noblock::<2>(epfd, &[Ev { data: fds[1], events: EPOLLIN | EPOLLOUT }]); + check_epoll_wait_noblock(epfd, &[Ev { data: fds[1], events: EPOLLIN | EPOLLOUT }]); if cfg!(edge_triggered) { // Check that this is indeed using "ET" (edge-trigger) semantics: a second wait // should return nothing. - check_epoll_wait_noblock::<1>(epfd, &[]); + check_epoll_wait_noblock(epfd, &[]); } else { // Check that this is indeed using "LT" (level-trigger) semantics: a second wait // should return the same readiness. - check_epoll_wait_noblock::<2>(epfd, &[Ev { data: fds[1], events: EPOLLIN | EPOLLOUT }]); + check_epoll_wait_noblock(epfd, &[Ev { data: fds[1], events: EPOLLIN | EPOLLOUT }]); } // Write some more to fds[0]. @@ -71,14 +71,14 @@ fn test_epoll_socketpair() { // This did not change the readiness of fds[1], so we should get no event. // However, Linux seems to always deliver spurious events to the peer on each write, // so we match that. - check_epoll_wait_noblock::<2>(epfd, &[Ev { data: fds[1], events: EPOLLIN | EPOLLOUT }]); + check_epoll_wait_noblock(epfd, &[Ev { data: fds[1], events: EPOLLIN | EPOLLOUT }]); // Close the peer socketpair. errno_check(unsafe { libc::close(fds[0]) }); // Check result from epoll_wait. We expect to get a read, write, HUP notification from the close // since closing an FD always unblocks reads and writes on its peer. - check_epoll_wait_noblock::<2>( + check_epoll_wait_noblock( epfd, &[Ev { data: fds[1], events: EPOLLIN | EPOLLOUT | EPOLLHUP | EPOLLRDHUP }], ); @@ -100,14 +100,14 @@ fn test_epoll_ctl_mod() { .unwrap(); // Check result from epoll_wait. No notification would be returned. - check_epoll_wait_noblock::<1>(epfd, &[]); + check_epoll_wait_noblock(epfd, &[]); // Use EPOLL_CTL_MOD to change to EPOLLOUT flag and data. epoll_ctl(epfd, EPOLL_CTL_MOD, fds[1], Ev { events: EPOLLOUT | EPOLLET_OR_ZERO, data: 1 }) .unwrap(); // Check result from epoll_wait. EPOLLOUT notification and new data is expected. - check_epoll_wait_noblock::<2>(epfd, &[Ev { events: EPOLLOUT, data: 1 }]); + check_epoll_wait_noblock(epfd, &[Ev { events: EPOLLOUT, data: 1 }]); // Write to fds[1] and read from fds[0] to make the notification ready again // (relying on there always being an event when the buffer gets emptied). @@ -119,14 +119,14 @@ fn test_epoll_ctl_mod() { .unwrap(); // Receive event, with latest data value. - check_epoll_wait_noblock::<2>(epfd, &[Ev { events: EPOLLOUT, data: 2 }]); + check_epoll_wait_noblock(epfd, &[Ev { events: EPOLLOUT, data: 2 }]); // Do another update that changes nothing. epoll_ctl(epfd, EPOLL_CTL_MOD, fds[1], Ev { events: EPOLLOUT | EPOLLET_OR_ZERO, data: 2 }) .unwrap(); // This re-triggers the event, even if it's the same flags as before. - check_epoll_wait_noblock::<2>(epfd, &[Ev { events: EPOLLOUT, data: 2 }]); + check_epoll_wait_noblock(epfd, &[Ev { events: EPOLLOUT, data: 2 }]); } fn test_epoll_ctl_del() { @@ -151,7 +151,7 @@ fn test_epoll_ctl_del() { // Test EPOLL_CTL_DEL. let res = unsafe { libc::epoll_ctl(epfd, EPOLL_CTL_DEL, fds[1], &mut ev) }; assert_eq!(res, 0); - check_epoll_wait_noblock::<1>(epfd, &[]); + check_epoll_wait_noblock(epfd, &[]); } // This test is for one fd registered under two different epoll instance. @@ -175,8 +175,8 @@ fn test_two_epoll_instance() { epoll_ctl_add(epfd2, fds[1], EPOLLIN | EPOLLOUT | EPOLLET_OR_ZERO).unwrap(); // Notification should be received from both instance of epoll. - check_epoll_wait_noblock::<2>(epfd1, &[Ev { events: EPOLLIN | EPOLLOUT, data: fds[1] }]); - check_epoll_wait_noblock::<2>(epfd2, &[Ev { events: EPOLLIN | EPOLLOUT, data: fds[1] }]); + check_epoll_wait_noblock(epfd1, &[Ev { events: EPOLLIN | EPOLLOUT, data: fds[1] }]); + check_epoll_wait_noblock(epfd2, &[Ev { events: EPOLLIN | EPOLLOUT, data: fds[1] }]); } // This test is for two same file description registered under the same epoll instance through dup. @@ -201,7 +201,7 @@ fn test_two_same_fd_in_same_epoll_instance() { libc_utils::write_all(fds[0], b"abcde").unwrap(); // Two notification should be received. - check_epoll_wait_noblock::<3>( + check_epoll_wait_noblock( epfd, &[ Ev { events: EPOLLIN | EPOLLOUT, data: fds[1] }, @@ -226,14 +226,14 @@ fn test_epoll_eventfd() { epoll_ctl_add(epfd, fd, EPOLLIN | EPOLLOUT | EPOLLET_OR_ZERO).unwrap(); // Check result from epoll_wait. - check_epoll_wait_noblock::<2>(epfd, &[Ev { events: EPOLLIN | EPOLLOUT, data: fd }]); + check_epoll_wait_noblock(epfd, &[Ev { events: EPOLLIN | EPOLLOUT, data: fd }]); // Write 0 to the eventfd. libc_utils::write_all(fd, &0_u64.to_ne_bytes()).unwrap(); // This does not change the status, so we should get no event. // However, Linux performs a spurious wakeup. - check_epoll_wait_noblock::<2>(epfd, &[Ev { events: EPOLLIN | EPOLLOUT, data: fd }]); + check_epoll_wait_noblock(epfd, &[Ev { events: EPOLLIN | EPOLLOUT, data: fd }]); // Read from the eventfd. libc_utils::read_exact_array::<8>(fd).unwrap(); @@ -241,13 +241,13 @@ fn test_epoll_eventfd() { // This consumes the event, so the read status is gone. However, deactivation // does not trigger an event. // Still, we see a spurious wakeup. - check_epoll_wait_noblock::<2>(epfd, &[Ev { events: EPOLLOUT, data: fd }]); + check_epoll_wait_noblock(epfd, &[Ev { events: EPOLLOUT, data: fd }]); // Write the maximum possible value. libc_utils::write_all(fd, &(u64::MAX - 1).to_ne_bytes()).unwrap(); // This reactivates reads, therefore triggering an event. Writing is no longer possible. - check_epoll_wait_noblock::<2>(epfd, &[Ev { events: EPOLLIN, data: fd }]); + check_epoll_wait_noblock(epfd, &[Ev { events: EPOLLIN, data: fd }]); } // When read/write happened on one side of the socketpair, only the other side will be notified. @@ -269,7 +269,7 @@ fn test_epoll_socketpair_both_sides() { libc_utils::write_all(fds[1], b"abcde").unwrap(); // Two notification should be received. - check_epoll_wait_noblock::<3>( + check_epoll_wait_noblock( epfd, &[Ev { events: EPOLLIN | EPOLLOUT, data: fds[0] }, Ev { events: EPOLLOUT, data: fds[1] }], ); @@ -282,11 +282,11 @@ fn test_epoll_socketpair_both_sides() { // The state of fds[1] does not change (was writable, is writable). // However, we force a spurious wakeup as the read buffer just got emptied. // fds[0] lost its readability, but becoming less active is not considered an "edge". - check_epoll_wait_noblock::<2>(epfd, &[Ev { events: EPOLLOUT, data: fds[1] }]) + check_epoll_wait_noblock(epfd, &[Ev { events: EPOLLOUT, data: fds[1] }]) } else { // With level-triggered epoll, only the readable readiness for fds[0] should // no longer be reported. The rest stays the same. - check_epoll_wait_noblock::<3>( + check_epoll_wait_noblock( epfd, &[Ev { events: EPOLLOUT, data: fds[0] }, Ev { events: EPOLLOUT, data: fds[1] }], ); @@ -313,7 +313,7 @@ fn test_closed_fd() { errno_check(unsafe { libc::close(fd) }); // No notification should be provided because the file description is closed. - check_epoll_wait_noblock::<1>(epfd, &[]); + check_epoll_wait_noblock(epfd, &[]); } // When a certain file descriptor registered with epoll is closed, but the underlying file description @@ -340,7 +340,7 @@ fn test_not_fully_closed_fd() { errno_check(unsafe { libc::close(fd) }); // Notification should still be provided because the file description is not closed. - check_epoll_wait_noblock::<2>(epfd, &[Ev { events: EPOLLOUT, data: fd }]); + check_epoll_wait_noblock(epfd, &[Ev { events: EPOLLOUT, data: fd }]); // Write to the eventfd instance to produce notification. libc_utils::write_all(newfd, &1_u64.to_ne_bytes()).unwrap(); @@ -349,7 +349,7 @@ fn test_not_fully_closed_fd() { errno_check(unsafe { libc::close(newfd) }); // No notification should be provided. - check_epoll_wait_noblock::<1>(epfd, &[]); + check_epoll_wait_noblock(epfd, &[]); } // Each time a notification is provided, it should reflect the file description's readiness @@ -374,7 +374,7 @@ fn test_event_overwrite() { assert_eq!(res, 8); // Check result from epoll_wait. - check_epoll_wait_noblock::<2>(epfd, &[Ev { events: EPOLLOUT, data: fd }]); + check_epoll_wait_noblock(epfd, &[Ev { events: EPOLLOUT, data: fd }]); } // An epoll notification will be provided for every succesful read in a socketpair. @@ -396,7 +396,7 @@ fn test_socketpair_read() { libc_utils::write_all(fds[1], &data).unwrap(); // Two notification should be received. - check_epoll_wait_noblock::<3>( + check_epoll_wait_noblock( epfd, &[Ev { events: EPOLLIN | EPOLLOUT, data: fds[0] }, Ev { events: EPOLLOUT, data: fds[1] }], ); @@ -407,11 +407,11 @@ fn test_socketpair_read() { if cfg!(edge_triggered) { // fds[1] did not change, it is still writable, so we get no event // in edge-triggered mode. - check_epoll_wait_noblock::<1>(epfd, &[]); + check_epoll_wait_noblock(epfd, &[]); } else { // In level-triggered mode we expect the same events as before because // we didn't read everything in the buffer. - check_epoll_wait_noblock::<3>( + check_epoll_wait_noblock( epfd, &[ Ev { events: EPOLLIN | EPOLLOUT, data: fds[0] }, @@ -428,11 +428,11 @@ fn test_socketpair_read() { // Now we get a notification that fds[1] can be written. This is spurious since it // could already be written before, but Linux seems to always emit a notification for // the writer when a read empties the buffer. - check_epoll_wait_noblock::<2>(epfd, &[Ev { events: EPOLLOUT, data: fds[1] }]); + check_epoll_wait_noblock(epfd, &[Ev { events: EPOLLOUT, data: fds[1] }]); } else { // In level-triggered mode we expect the same events as before just without // the readable readiness of fds[0] because we now read everything. - check_epoll_wait_noblock::<3>( + check_epoll_wait_noblock( epfd, &[Ev { events: EPOLLOUT, data: fds[0] }, Ev { events: EPOLLOUT, data: fds[1] }], ); @@ -456,7 +456,7 @@ fn test_no_notification_for_unregister_flag() { // Check result from epoll_wait. Since we didn't register EPOLLIN flag, the notification won't // contain EPOLLIN even though fds[0] is now readable. - check_epoll_wait_noblock::<2>(epfd, &[Ev { events: EPOLLOUT, data: fds[0] }]); + check_epoll_wait_noblock(epfd, &[Ev { events: EPOLLOUT, data: fds[0] }]); } fn test_epoll_wait_maxevent_zero() { @@ -491,7 +491,7 @@ fn test_socketpair_epollerr() { epoll_ctl_add(epfd, fds[0], EPOLLIN | EPOLLOUT | EPOLLRDHUP | EPOLLET_OR_ZERO).unwrap(); // Check result from epoll_wait. - check_epoll_wait_noblock::<2>( + check_epoll_wait_noblock( epfd, &[Ev { events: EPOLLIN | EPOLLOUT | EPOLLHUP | EPOLLRDHUP | EPOLLERR, data: fds[0] }], ); @@ -512,16 +512,16 @@ fn test_epoll_lost_events() { epoll_ctl_add(epfd, fds[1], EPOLLIN | EPOLLOUT | EPOLLET_OR_ZERO).unwrap(); // Two notification should be received. But we only provide buffer for one event. - check_epoll_wait_noblock::<1>(epfd, &[Ev { events: EPOLLOUT, data: fds[0] }]); + check_epoll_wait_explicit(epfd, &[Ev { events: EPOLLOUT, data: fds[0] }], 1, 0); if cfg!(edge_triggered) { // Previous event should be returned for the second epoll_wait but because we're // edge-triggered the first event should no longer be returned. - check_epoll_wait_noblock::<2>(epfd, &[Ev { events: EPOLLOUT, data: fds[1] }]); + check_epoll_wait_noblock(epfd, &[Ev { events: EPOLLOUT, data: fds[1] }]); } else { // Both events should be returned in level-triggered mode when // we provide a big enough buffer. - check_epoll_wait_noblock::<3>( + check_epoll_wait_noblock( epfd, &[Ev { events: EPOLLOUT, data: fds[1] }, Ev { events: EPOLLOUT, data: fds[0] }], ); @@ -548,7 +548,7 @@ fn test_ready_list_fetching_logic() { errno_check(unsafe { libc::close(fd0) }); // Notification for fd1 should be returned. - check_epoll_wait_noblock::<2>(epfd, &[Ev { events: EPOLLOUT, data: fd1 }]); + check_epoll_wait_noblock(epfd, &[Ev { events: EPOLLOUT, data: fd1 }]); } // In epoll_ctl, if the value of epfd equals to fd, EFAULT should be returned. @@ -580,7 +580,7 @@ fn test_epoll_ctl_notification() { epoll_ctl_add(epfd0, fds[0], EPOLLIN | EPOLLOUT | EPOLLET_OR_ZERO).unwrap(); // epoll_wait to clear notification for epfd0. - check_epoll_wait_noblock::<2>(epfd0, &[Ev { events: EPOLLOUT, data: fds[0] }]); + check_epoll_wait_noblock(epfd0, &[Ev { events: EPOLLOUT, data: fds[0] }]); // Create another epoll instance. let epfd1 = unsafe { libc::epoll_create1(0) }; @@ -588,15 +588,15 @@ fn test_epoll_ctl_notification() { // Register the same file description for epfd1. epoll_ctl_add(epfd1, fds[0], EPOLLIN | EPOLLOUT | EPOLLET_OR_ZERO).unwrap(); - check_epoll_wait_noblock::<2>(epfd1, &[Ev { events: EPOLLOUT, data: fds[0] }]); + check_epoll_wait_noblock(epfd1, &[Ev { events: EPOLLOUT, data: fds[0] }]); if cfg!(edge_triggered) { // Previously this epoll_wait will receive a notification, but we shouldn't return notification // for this epfd, because there is no I/O event between the two epoll_wait. - check_epoll_wait_noblock::<1>(epfd0, &[]); + check_epoll_wait_noblock(epfd0, &[]); } else { // We should still get the same events in level-triggered mode. - check_epoll_wait_noblock::<2>(epfd0, &[Ev { events: EPOLLOUT, data: fds[0] }]); + check_epoll_wait_noblock(epfd0, &[Ev { events: EPOLLOUT, data: fds[0] }]); } } @@ -619,13 +619,13 @@ fn test_epoll_mixed_modes() { libc_utils::write_all(fds[1], b"abcde").unwrap(); // Two events should be received. - check_epoll_wait_noblock::<3>( + check_epoll_wait_noblock( epfd, &[Ev { events: EPOLLIN | EPOLLOUT, data: fds[0] }, Ev { events: EPOLLOUT, data: fds[1] }], ); // If we call epoll_wait again immediately, only the level-triggered interests should be received again. - check_epoll_wait_noblock::<2>(epfd, &[Ev { events: EPOLLOUT, data: fds[1] }]); + check_epoll_wait_noblock(epfd, &[Ev { events: EPOLLOUT, data: fds[1] }]); } /// Test first registering a file descriptor in edge-triggered mode, @@ -646,18 +646,18 @@ fn test_epoll_registered_mode_switch() { epoll_ctl_add(epfd, fd, EPOLLIN | EPOLLOUT | EPOLLET).unwrap(); // Check result from `epoll_wait`. - check_epoll_wait_noblock::<2>(epfd, &[Ev { events: EPOLLIN | EPOLLOUT, data: fd }]); + check_epoll_wait_noblock(epfd, &[Ev { events: EPOLLIN | EPOLLOUT, data: fd }]); // Because `fd` is registered in edge-triggered mode, the next `epoll_wait` shouldn't // return any events. - check_epoll_wait_noblock::<1>(epfd, &[]); + check_epoll_wait_noblock(epfd, &[]); // Update the registration for `fd` to switch to level-triggered mode. epoll_ctl(epfd, EPOLL_CTL_MOD, fd, Ev { events: EPOLLIN | EPOLLOUT, data: fd }).unwrap(); // Because `fd` is now registered in level-triggered mode, we should see // the same events as from the first `epoll_wait`. - check_epoll_wait_noblock::<2>(epfd, &[Ev { events: EPOLLIN | EPOLLOUT, data: fd }]); + check_epoll_wait_noblock(epfd, &[Ev { events: EPOLLIN | EPOLLOUT, data: fd }]); } // Test for ICE caused by weak epoll interest upgrade succeed, but the attempt to retrieve @@ -714,7 +714,7 @@ fn test_issue_4374() { } // This should have canceled the previous readiness, so now we get nothing. - check_epoll_wait_noblock::<1>(epfd0, &[]); + check_epoll_wait_noblock(epfd0, &[]); } /// Same as above, but for becoming un-readable. @@ -739,5 +739,5 @@ fn test_issue_4374_reads() { libc_utils::read_exact_array::<5>(fds[0]).unwrap(); // We should now still see a notification, but only about it being writable. - check_epoll_wait_noblock::<2>(epfd0, &[Ev { events: EPOLLOUT, data: fds[0] }]); + check_epoll_wait_noblock(epfd0, &[Ev { events: EPOLLOUT, data: fds[0] }]); } diff --git a/src/tools/miri/tests/pass-dep/libc/libc-socket-no-blocking-epoll.rs b/src/tools/miri/tests/pass-dep/libc/libc-socket-no-blocking-epoll.rs index e615f1db39257..80b92e69707f9 100644 --- a/src/tools/miri/tests/pass-dep/libc/libc-socket-no-blocking-epoll.rs +++ b/src/tools/miri/tests/pass-dep/libc/libc-socket-no-blocking-epoll.rs @@ -61,7 +61,7 @@ fn test_connect_nonblock() { epoll_ctl_add(epfd, client_sockfd, EPOLLOUT | EPOLLET | EPOLLERR).unwrap(); // Wait until we are done connecting. - check_epoll_wait::<8>(epfd, &[Ev { events: EPOLLOUT, data: client_sockfd }], -1); + check_epoll_wait(epfd, &[Ev { events: EPOLLOUT, data: client_sockfd }], -1); // There should be no error during async connection. let errno = @@ -95,7 +95,7 @@ fn test_accept_nonblock() { epoll_ctl_add(epfd, server_sockfd, EPOLLIN | EPOLLET | EPOLLERR).unwrap(); // Wait until we get a readable event on the server socket. - check_epoll_wait::<8>(epfd, &[Ev { events: EPOLLIN, data: server_sockfd }], -1); + check_epoll_wait(epfd, &[Ev { events: EPOLLIN, data: server_sockfd }], -1); // Accepting should now be possible. net::accept_ipv4(server_sockfd).unwrap(); @@ -149,7 +149,7 @@ fn test_connect_nonblock_err() { epoll_ctl_add(epfd, client_sockfd, EPOLLOUT | EPOLLET | libc::EPOLLERR).unwrap(); // Wait until the socket has an error. - check_epoll_wait::<8>( + check_epoll_wait( epfd, &[Ev { events: libc::EPOLLERR | EPOLLOUT | EPOLLHUP, data: client_sockfd }], -1, @@ -229,7 +229,7 @@ fn test_recv_nonblock() { Ok(received) => bytes_received += received as usize, Err(err) if err.kind() == ErrorKind::WouldBlock => { // Use epoll to block until there's data available again. - check_epoll_wait::<8>(epfd, &[Ev { events: EPOLLIN, data: client_sockfd }], -1); + check_epoll_wait(epfd, &[Ev { events: EPOLLIN, data: client_sockfd }], -1); } Err(err) => panic!("unexpected error whilst receiving: {err}"), } @@ -339,7 +339,7 @@ fn test_send_nonblock() { }); // Wait until the socket is again writable. - check_epoll_wait::<8>(epfd, &[Ev { events: EPOLLOUT, data: client_sockfd }], -1); + check_epoll_wait(epfd, &[Ev { events: EPOLLOUT, data: client_sockfd }], -1); let fill_buf = [1u8; 100]; // We should be able to write again without blocking because we just received @@ -371,7 +371,7 @@ fn test_shutdown_read_write() { unsafe { libc::shutdown(client_sockfd, libc::SHUT_RDWR) }; // Ensure that the "read end closed", "write end closed", and "readable" readiness are set. - check_epoll_wait::<8>( + check_epoll_wait( epfd, &[Ev { events: EPOLLRDHUP | EPOLLHUP | EPOLLIN, data: client_sockfd }], -1, @@ -399,7 +399,7 @@ fn test_shutdown_read() { unsafe { libc::shutdown(client_sockfd, libc::SHUT_RD) }; // Ensure that the "read end closed" readiness is set. - check_epoll_wait::<8>(epfd, &[Ev { events: EPOLLRDHUP, data: client_sockfd }], -1); + check_epoll_wait(epfd, &[Ev { events: EPOLLRDHUP, data: client_sockfd }], -1); server_thread.join().unwrap(); } @@ -425,7 +425,7 @@ fn test_shutdown_write() { // Ensure that the "read end closed" readiness is set when // the write end of the peer is closed. - check_epoll_wait::<8>(epfd, &[Ev { events: EPOLLRDHUP, data: client_sockfd }], -1); + check_epoll_wait(epfd, &[Ev { events: EPOLLRDHUP, data: client_sockfd }], -1); server_thread.join().unwrap(); } @@ -460,7 +460,7 @@ fn test_readiness_after_short_read() { epoll_ctl_add(epfd, client_sockfd, EPOLLET | EPOLLIN).unwrap(); // Wait until the socket becomes readable. - check_epoll_wait::<8>(epfd, &[Ev { events: EPOLLIN, data: client_sockfd }], -1); + check_epoll_wait(epfd, &[Ev { events: EPOLLIN, data: client_sockfd }], -1); let mut buffer = [0u8; 1024]; @@ -499,7 +499,7 @@ fn test_readiness_after_short_read() { // Wait until the client socket becomes readable again. // If this blocks indefinitely, Miri lost track of the proper status of this socket. - check_epoll_wait::<8>(epfd, &[Ev { events: EPOLLIN, data: client_sockfd }], -1); + check_epoll_wait(epfd, &[Ev { events: EPOLLIN, data: client_sockfd }], -1); // Now we can read the 2nd chunk of data. unsafe { @@ -583,7 +583,7 @@ fn test_readiness_after_short_write() { epoll_ctl_add(epfd, client_sockfd, EPOLLET | EPOLLOUT).unwrap(); // Wait until the socket becomes writable. - check_epoll_wait::<8>(epfd, &[Ev { events: EPOLLOUT, data: client_sockfd }], -1); + check_epoll_wait(epfd, &[Ev { events: EPOLLOUT, data: client_sockfd }], -1); // We now want to fill the write buffer of the socket by repeatedly writing // `buffer` into it. The last write should then be a short write. @@ -630,7 +630,7 @@ fn test_readiness_after_short_write() { // Wait until the socket becomes writable again. // If this blocks indefinitely, Miri lost track of the proper status of this socket. - check_epoll_wait::<8>(epfd, &[Ev { events: EPOLLOUT, data: client_sockfd }], -1); + check_epoll_wait(epfd, &[Ev { events: EPOLLOUT, data: client_sockfd }], -1); // We should again be able to write into the socket. libc_utils::write_all(client_sockfd, &buffer).unwrap(); diff --git a/src/tools/miri/tests/utils/libc.rs b/src/tools/miri/tests/utils/libc.rs index da331a3c934e8..9020b06b912e1 100644 --- a/src/tools/miri/tests/utils/libc.rs +++ b/src/tools/miri/tests/utils/libc.rs @@ -164,24 +164,41 @@ pub mod epoll { epoll_ctl(epfd, EPOLL_CTL_ADD, fd, Ev { events, data: fd }) } + /// Call `epoll_wait` on `epfd` with the provided `timeout`. + /// It fetches at most `max_events` events from `epfd` and + /// ensures that the returned events match the `expected` events. #[track_caller] - pub fn check_epoll_wait(epfd: i32, expected: &[Ev], timeout: i32) { - let mut array: [libc::epoll_event; N] = [libc::epoll_event { events: 0, u64: 0 }; N]; + pub fn check_epoll_wait_explicit(epfd: i32, expected: &[Ev], max_events: usize, timeout: i32) { + let mut events = vec![libc::epoll_event { events: 0, u64: 0 }; max_events]; let num = errno_result(unsafe { - libc::epoll_wait(epfd, array.as_mut_ptr(), N.try_into().unwrap(), timeout) + libc::epoll_wait(epfd, events.as_mut_ptr(), i32::try_from(max_events).unwrap(), timeout) }) .expect("epoll_wait returned an error"); - let got = &mut array[..num.try_into().unwrap()]; - let got = got + let got = events .iter() + .take(num as usize) .map(|e| Ev { events: e.events.cast_signed(), data: e.u64.try_into().unwrap() }) .collect::>(); - assert_eq!(got, expected, "got wrong notifications"); + assert_eq!(got, expected, "got wrong ready events"); + } + + /// Call `epoll_wait` on `epfd` with the provided `timeout` and ensure + /// that the returned events match the `expected` events. + /// This function checks whether `expected` is equal to **all** ready events + /// of `epfd`. If you only want to ensure that `expected` matches a subset + /// of the ready events [`check_epoll_wait_explicit`] should be used instead. + #[track_caller] + pub fn check_epoll_wait(epfd: i32, expected: &[Ev], timeout: i32) { + // We set `max_events` to `expected.len() + 1` to ensure that + // there are no additional ready events besides those which are + // contained in `expected`. + check_epoll_wait_explicit(epfd, &expected, expected.len() + 1, timeout); } + /// This does the same as [`check_epoll_wait`] just without blocking (zero `timeout`). #[track_caller] - pub fn check_epoll_wait_noblock(epfd: i32, expected: &[Ev]) { - check_epoll_wait::(epfd, expected, 0); + pub fn check_epoll_wait_noblock(epfd: i32, expected: &[Ev]) { + check_epoll_wait(epfd, expected, 0); } /// Query the current epoll readiness of a file descriptor. From 0151f301f7fcbdbc5a7ff6326259dbeda03fd0d3 Mon Sep 17 00:00:00 2001 From: WhySoBad <49595640+WhySoBad@users.noreply.github.com> Date: Sun, 31 May 2026 17:30:51 +0200 Subject: [PATCH 30/46] fix: bless the block two thread stderr --- .../tests/fail-dep/libc/libc_epoll_block_two_thread.stderr | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/tools/miri/tests/fail-dep/libc/libc_epoll_block_two_thread.stderr b/src/tools/miri/tests/fail-dep/libc/libc_epoll_block_two_thread.stderr index 6919c2e6f8b96..191e9d59b5c3f 100644 --- a/src/tools/miri/tests/fail-dep/libc/libc_epoll_block_two_thread.stderr +++ b/src/tools/miri/tests/fail-dep/libc/libc_epoll_block_two_thread.stderr @@ -18,7 +18,7 @@ LL | let ret = unsafe { libc::pthread_join(id, ptr::null_mut()) }; error: the evaluated program deadlocked --> tests/fail-dep/libc/libc_epoll_block_two_thread.rs:LL:CC | -LL | / check_epoll_wait::( +LL | / check_epoll_wait( LL | | epfd, LL | | &[Ev { events: EPOLLOUT, data: fd1 }, Ev { events: EPOLLOUT, data: fd2 }], LL | | -1, @@ -32,7 +32,7 @@ note: the current function got called indirectly due to this code LL | let thread2 = thread::spawn(move || { | ___________________^ LL | | -LL | | check_epoll_wait::( +LL | | check_epoll_wait( LL | | epfd, ... | LL | | ); From cf8b4e966ad7c91fe4ddabdacd586f783f36f6f5 Mon Sep 17 00:00:00 2001 From: Mohamed Ali Date: Wed, 27 May 2026 20:34:46 +0300 Subject: [PATCH 31/46] [Priroda] refactor: move entry_fn from Miri bin to eval module to allow external access for Priroda use --- src/tools/miri/src/bin/miri.rs | 59 ++-------------------------------- src/tools/miri/src/eval.rs | 55 ++++++++++++++++++++++++++++++- src/tools/miri/src/lib.rs | 5 +-- 3 files changed, 60 insertions(+), 59 deletions(-) diff --git a/src/tools/miri/src/bin/miri.rs b/src/tools/miri/src/bin/miri.rs index 38cb8ef5717c4..442f96ffaa216 100644 --- a/src/tools/miri/src/bin/miri.rs +++ b/src/tools/miri/src/bin/miri.rs @@ -12,12 +12,10 @@ extern crate rustc_codegen_ssa; extern crate rustc_data_structures; extern crate rustc_driver; extern crate rustc_hir; -extern crate rustc_hir_analysis; extern crate rustc_interface; extern crate rustc_log; extern crate rustc_middle; extern crate rustc_session; -extern crate rustc_span; /// See docs in https://github.com/rust-lang/rust/blob/HEAD/compiler/rustc/src/main.rs /// and https://github.com/rust-lang/rust/pull/146627 for why we need this. @@ -44,15 +42,13 @@ use std::str::FromStr; use std::sync::atomic::{AtomicU32, Ordering}; use miri::{ - BacktraceStyle, BorrowTrackerMethod, GenmcConfig, GenmcCtx, MiriConfig, MiriEntryFnType, - ProvenanceMode, TreeBorrowsParams, ValidationMode, run_genmc_mode, + BacktraceStyle, BorrowTrackerMethod, GenmcConfig, GenmcCtx, MiriConfig, ProvenanceMode, + TreeBorrowsParams, ValidationMode, entry_fn, run_genmc_mode, }; use rustc_codegen_ssa::traits::CodegenBackend; use rustc_data_structures::sync::{self, DynSync}; use rustc_driver::Compilation; -use rustc_hir::def_id::LOCAL_CRATE; use rustc_hir::{self as hir, Node}; -use rustc_hir_analysis::check::check_function_signature; use rustc_interface::interface::Config; use rustc_interface::util::DummyCodegenBackend; use rustc_log::tracing::debug; @@ -61,11 +57,9 @@ use rustc_middle::middle::exported_symbols::{ ExportedSymbol, SymbolExportInfo, SymbolExportKind, SymbolExportLevel, }; use rustc_middle::query::LocalCrate; -use rustc_middle::traits::{ObligationCause, ObligationCauseCode}; -use rustc_middle::ty::{self, Ty, TyCtxt}; +use rustc_middle::ty::TyCtxt; use rustc_session::config::{CrateType, ErrorOutputType, OptLevel}; use rustc_session::{EarlyDiagCtxt, Session}; -use rustc_span::def_id::DefId; use crate::log::setup::{deinit_loggers, init_early_loggers, init_late_loggers}; @@ -85,53 +79,6 @@ impl MiriCompilerCalls { } } -fn entry_fn(tcx: TyCtxt<'_>) -> (DefId, MiriEntryFnType) { - if let Some((def_id, entry_type)) = tcx.entry_fn(()) { - return (def_id, MiriEntryFnType::Rustc(entry_type)); - } - // Look for a symbol in the local crate named `miri_start`, and treat that as the entry point. - let sym = tcx.exported_non_generic_symbols(LOCAL_CRATE).iter().find_map(|(sym, _)| { - if sym.symbol_name_for_local_instance(tcx).name == "miri_start" { Some(sym) } else { None } - }); - if let Some(ExportedSymbol::NonGeneric(id)) = sym { - let start_def_id = id.expect_local(); - let start_span = tcx.def_span(start_def_id); - - let expected_sig = ty::Binder::dummy(tcx.mk_fn_sig_safe_rust_abi( - [tcx.types.isize, Ty::new_imm_ptr(tcx, Ty::new_imm_ptr(tcx, tcx.types.u8))], - tcx.types.isize, - )); - - let correct_func_sig = check_function_signature( - tcx, - ObligationCause::new(start_span, start_def_id, ObligationCauseCode::Misc), - *id, - expected_sig, - ) - .is_ok(); - - if correct_func_sig { - (*id, MiriEntryFnType::MiriStart) - } else { - tcx.dcx().fatal( - "`miri_start` must have the following signature:\n\ - fn miri_start(argc: isize, argv: *const *const u8) -> isize", - ); - } - } else { - tcx.dcx().fatal( - "Miri can only run programs that have a main function.\n\ - Alternatively, you can export a `miri_start` function:\n\ - \n\ - #[cfg(miri)]\n\ - #[unsafe(no_mangle)]\n\ - fn miri_start(argc: isize, argv: *const *const u8) -> isize {\ - \n // Call the actual start function that your project implements, based on your target's conventions.\n\ - }" - ); - } -} - fn run_many_seeds( many_seeds: ManySeedsConfig, eval_entry_once: impl Fn(u64) -> Result<(), NonZeroI32> + DynSync, diff --git a/src/tools/miri/src/eval.rs b/src/tools/miri/src/eval.rs index c9b2b7634b7b6..19b6ff75f95f8 100644 --- a/src/tools/miri/src/eval.rs +++ b/src/tools/miri/src/eval.rs @@ -12,7 +12,10 @@ use rustc_abi::ExternAbi; use rustc_data_structures::fx::{FxHashMap, FxHashSet}; use rustc_errors::FatalErrorMarker; use rustc_hir::def::Namespace; -use rustc_hir::def_id::DefId; +use rustc_hir::def_id::{DefId, LOCAL_CRATE}; +use rustc_hir_analysis::check::check_function_signature; +use rustc_middle::middle::exported_symbols::ExportedSymbol; +use rustc_middle::traits::{ObligationCause, ObligationCauseCode}; use rustc_middle::ty::layout::{HasTyCtxt, HasTypingEnv, LayoutCx}; use rustc_middle::ty::{self, Ty, TyCtxt}; use rustc_session::config::EntryFnType; @@ -31,6 +34,56 @@ pub enum MiriEntryFnType { Rustc(EntryFnType), } +/// Finds the entry point Miri should execute. +/// +/// Public because this is used by Priroda. +pub fn entry_fn(tcx: TyCtxt<'_>) -> (DefId, MiriEntryFnType) { + if let Some((def_id, entry_type)) = tcx.entry_fn(()) { + return (def_id, MiriEntryFnType::Rustc(entry_type)); + } + // Look for a symbol in the local crate named `miri_start`, and treat that as the entry point. + let sym = tcx.exported_non_generic_symbols(LOCAL_CRATE).iter().find_map(|(sym, _)| { + if sym.symbol_name_for_local_instance(tcx).name == "miri_start" { Some(sym) } else { None } + }); + if let Some(ExportedSymbol::NonGeneric(id)) = sym { + let start_def_id = id.expect_local(); + let start_span = tcx.def_span(start_def_id); + + let expected_sig = ty::Binder::dummy(tcx.mk_fn_sig_safe_rust_abi( + [tcx.types.isize, Ty::new_imm_ptr(tcx, Ty::new_imm_ptr(tcx, tcx.types.u8))], + tcx.types.isize, + )); + + let correct_func_sig = check_function_signature( + tcx, + ObligationCause::new(start_span, start_def_id, ObligationCauseCode::Misc), + *id, + expected_sig, + ) + .is_ok(); + + if correct_func_sig { + (*id, MiriEntryFnType::MiriStart) + } else { + tcx.dcx().fatal( + "`miri_start` must have the following signature:\n\ + fn miri_start(argc: isize, argv: *const *const u8) -> isize", + ); + } + } else { + tcx.dcx().fatal( + "Miri can only run programs that have a main function.\n\ + Alternatively, you can export a `miri_start` function:\n\ + \n\ + #[cfg(miri)]\n\ + #[unsafe(no_mangle)]\n\ + fn miri_start(argc: isize, argv: *const *const u8) -> isize {\ + \n // Call the actual start function that your project implements, based on your target's conventions.\n\ + }" + ); + } +} + /// When the main thread would exit, we will yield to any other thread that is ready to execute. /// But we must only do that a finite number of times, or a background thread running `loop {}` /// will hang the program. diff --git a/src/tools/miri/src/lib.rs b/src/tools/miri/src/lib.rs index 160f31d50c4bf..f9058c7a7ac15 100644 --- a/src/tools/miri/src/lib.rs +++ b/src/tools/miri/src/lib.rs @@ -69,6 +69,7 @@ extern crate rustc_const_eval; extern crate rustc_data_structures; extern crate rustc_errors; extern crate rustc_hir; +extern crate rustc_hir_analysis; extern crate rustc_index; extern crate rustc_log; extern crate rustc_middle; @@ -146,7 +147,7 @@ pub use crate::concurrency::init_once::{EvalContextExt as _, InitOnceRef}; pub use crate::concurrency::sync::{CondvarRef, EvalContextExt as _, MutexRef, RwLockRef}; pub use crate::concurrency::thread::{ BlockReason, DynUnblockCallback, EvalContextExt as _, StackEmptyCallback, ThreadId, - ThreadManager, UnblockKind, + ThreadManager, TlsAllocAction, UnblockKind, }; pub use crate::concurrency::{GenmcConfig, GenmcCtx, run_genmc_mode}; pub use crate::data_structures::dedup_range_map::DedupRangeMap; @@ -154,7 +155,7 @@ pub use crate::data_structures::mono_hash_map::MonoHashMap; pub use crate::diagnostics::{ EvalContextExt as _, NonHaltingDiagnostic, TerminationInfo, report_result, }; -pub use crate::eval::{MiriConfig, MiriEntryFnType, create_ecx, eval_entry}; +pub use crate::eval::{MiriConfig, MiriEntryFnType, create_ecx, entry_fn, eval_entry}; pub use crate::helpers::{EvalContextExt as _, ToU64 as _, ToUsize as _}; pub use crate::intrinsics::EvalContextExt as _; pub use crate::machine::{ From 035784702f2c48fcfc4431e523ed23c426bc3f62 Mon Sep 17 00:00:00 2001 From: Mohamed Ali Date: Wed, 27 May 2026 20:46:45 +0300 Subject: [PATCH 32/46] [Priroda] initial standalone CLI crate --- src/tools/miri/priroda/Cargo.lock | 1260 ++++++++++++++++++++ src/tools/miri/priroda/Cargo.toml | 19 + src/tools/miri/priroda/README.md | 37 + src/tools/miri/priroda/rust-toolchain.toml | 2 + src/tools/miri/priroda/src/main.rs | 163 +++ 5 files changed, 1481 insertions(+) create mode 100644 src/tools/miri/priroda/Cargo.lock create mode 100644 src/tools/miri/priroda/Cargo.toml create mode 100644 src/tools/miri/priroda/README.md create mode 100644 src/tools/miri/priroda/rust-toolchain.toml create mode 100644 src/tools/miri/priroda/src/main.rs diff --git a/src/tools/miri/priroda/Cargo.lock b/src/tools/miri/priroda/Cargo.lock new file mode 100644 index 0000000000000..25c97f8919423 --- /dev/null +++ b/src/tools/miri/priroda/Cargo.lock @@ -0,0 +1,1260 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "aes" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66bd29a732b644c0431c6140f370d097879203d79b80c94a6747ba0872adaef8" +dependencies = [ + "cipher", + "cpubits", + "cpufeatures", +] + +[[package]] +name = "anyhow" +version = "1.0.102" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "bincode" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] + +[[package]] +name = "bitflags" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3" + +[[package]] +name = "bumpalo" +version = "3.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" + +[[package]] +name = "capstone" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f442ae0f2f3f1b923334b4a5386c95c69c1cfa072bafa23d6fae6d9682eb1dd4" +dependencies = [ + "capstone-sys", + "static_assertions", +] + +[[package]] +name = "capstone-sys" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4e8087cab6731295f5a2a2bd82989ba4f41d3a428aab2e7c98d8f4db38aac05" +dependencies = [ + "cc", +] + +[[package]] +name = "cc" +version = "1.2.62" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1dce859f0832a7d088c4f1119888ab94ef4b5d6795d1ce05afb7fe159d79f98" +dependencies = [ + "find-msvc-tools", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + +[[package]] +name = "chacha20" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f8d983286843e49675a4b7a2d174efe136dc93a18d69130dd18198a6c167601" +dependencies = [ + "cfg-if", + "cpufeatures", + "rand_core 0.10.1", +] + +[[package]] +name = "chrono" +version = "0.4.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0" +dependencies = [ + "num-traits", +] + +[[package]] +name = "chrono-tz" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6139a8597ed92cf816dfb33f5dd6cf0bb93a6adc938f11039f371bc5bcd26c3" +dependencies = [ + "chrono", + "phf", +] + +[[package]] +name = "cipher" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e34d8227fe1ba289043aeb13792056ff80fd6de1a9f49137a5f499de8e8c78ea" +dependencies = [ + "crypto-common", + "inout", +] + +[[package]] +name = "cpubits" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15b85f9c39137c3a891689859392b1bd49812121d0d61c9caf00d46ed5ce06ae" + +[[package]] +name = "cpufeatures" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" +dependencies = [ + "libc", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[package]] +name = "crypto-common" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77727bb15fa921304124b128af125e7e3b968275d1b108b379190264f4423710" +dependencies = [ + "hybrid-array", +] + +[[package]] +name = "directories" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16f5094c54661b38d03bd7e50df373292118db60b585c08a411c6d840017fe7d" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" +dependencies = [ + "libc", + "option-ext", + "redox_users", + "windows-sys", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys", +] + +[[package]] +name = "fastrand" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" + +[[package]] +name = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "futures-core" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" + +[[package]] +name = "futures-task" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" + +[[package]] +name = "futures-util" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" +dependencies = [ + "futures-core", + "futures-task", + "pin-project-lite", + "slab", +] + +[[package]] +name = "getrandom" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "libc", + "r-efi 5.3.0", + "wasip2", +] + +[[package]] +name = "getrandom" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" +dependencies = [ + "cfg-if", + "libc", + "r-efi 6.0.0", + "rand_core 0.10.1", + "wasip2", + "wasip3", +] + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "foldhash", +] + +[[package]] +name = "hashbrown" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hybrid-array" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9155a582abd142abc056962c29e3ce5ff2ad5469f4246b537ed42c5deba857da" +dependencies = [ + "typenum", +] + +[[package]] +name = "id-arena" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" + +[[package]] +name = "indexmap" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" +dependencies = [ + "equivalent", + "hashbrown 0.17.1", + "serde", + "serde_core", +] + +[[package]] +name = "inout" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4250ce6452e92010fdf7268ccc5d14faa80bb12fc741938534c58f16804e03c7" +dependencies = [ + "hybrid-array", +] + +[[package]] +name = "ipc-channel" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f93600b5616c2d075f8af8dbd23c1d69278c5d24e4913d220cbc60b14c95c180" +dependencies = [ + "bincode", + "crossbeam-channel", + "fnv", + "libc", + "mio", + "rand 0.9.4", + "serde", + "tempfile", + "uuid", + "windows", +] + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "js-sys" +version = "0.3.98" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67df7112613f8bfd9150013a0314e196f4800d3201ae742489d999db2f979f08" +dependencies = [ + "cfg-if", + "futures-util", + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "leb128fmt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + +[[package]] +name = "libc" +version = "0.2.186" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" + +[[package]] +name = "libffi" +version = "5.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0498fe5655f857803e156523e644dcdcdc3b3c7edda42ea2afdae2e09b2db87b" +dependencies = [ + "libc", + "libffi-sys", +] + +[[package]] +name = "libffi-sys" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71d4f1d4ce15091955144350b75db16a96d4a63728500122706fb4d29a26afbb" +dependencies = [ + "cc", +] + +[[package]] +name = "libloading" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "754ca22de805bb5744484a5b151a9e1a8e837d5dc232c2d7d8c2e3492edc8b60" +dependencies = [ + "cfg-if", + "windows-link 0.2.1", +] + +[[package]] +name = "libredox" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e02f3bb43d335493c96bf3fd3a321600bf6bd07ed34bc64118e9293bdffea46c" +dependencies = [ + "libc", +] + +[[package]] +name = "linux-raw-sys" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" + +[[package]] +name = "measureme" +version = "12.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ebd1ebda747ae161a4a377bf93f87e18d46faad2331cc0c7d25b84b1d445f49" +dependencies = [ + "log", + "memmap2", + "parking_lot", + "perf-event-open-sys", + "rustc-hash", + "smallvec", +] + +[[package]] +name = "memchr" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" + +[[package]] +name = "memmap2" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "723e3ebdcdc5c023db1df315364573789f8857c11b631a2fdfad7c00f5c046b4" +dependencies = [ + "libc", +] + +[[package]] +name = "mio" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50b7e5b27aa02a74bac8c3f23f448f8d87ff11f92d3aac1a6ed369ee08cc56c1" +dependencies = [ + "libc", + "log", + "wasi", + "windows-sys", +] + +[[package]] +name = "miri" +version = "0.1.0" +dependencies = [ + "aes", + "bitflags", + "capstone", + "chrono", + "chrono-tz", + "directories", + "getrandom 0.4.2", + "ipc-channel", + "libc", + "libffi", + "libloading", + "measureme", + "mio", + "nix", + "rand 0.10.1", + "serde", + "smallvec", +] + +[[package]] +name = "nix" +version = "0.30.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" +dependencies = [ + "bitflags", + "cfg-if", + "cfg_aliases", + "libc", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-link 0.2.1", +] + +[[package]] +name = "perf-event-open-sys" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b29be2ba35c12c6939f6bc73187f728bba82c3c062ecdc5fa90ea739282a1f58" +dependencies = [ + "libc", +] + +[[package]] +name = "phf" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "913273894cec178f401a31ec4b656318d95473527be05c0752cc41cdc32be8b7" +dependencies = [ + "phf_shared", +] + +[[package]] +name = "phf_shared" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06005508882fb681fd97892ecff4b7fd0fee13ef1aa569f8695dae7ab9099981" +dependencies = [ + "siphasher", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn", +] + +[[package]] +name = "priroda" +version = "0.1.0" +dependencies = [ + "miri", +] + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + +[[package]] +name = "rand" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea" +dependencies = [ + "rand_chacha", + "rand_core 0.9.5", +] + +[[package]] +name = "rand" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2e8e8bcc7961af1fdac401278c6a831614941f6164ee3bf4ce61b7edb162207" +dependencies = [ + "chacha20", + "getrandom 0.4.2", + "rand_core 0.10.1", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.5", +] + +[[package]] +name = "rand_core" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" +dependencies = [ + "getrandom 0.3.4", +] + +[[package]] +name = "rand_core" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags", +] + +[[package]] +name = "redox_users" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" +dependencies = [ + "getrandom 0.2.17", + "libredox", + "thiserror", +] + +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + +[[package]] +name = "rustix" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "semver" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.149" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "siphasher" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ee5873ec9cce0195efcb7a4e9507a04cd49aec9c83d0389df45b1ef7ba2e649" + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "syn" +version = "2.0.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tempfile" +version = "3.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" +dependencies = [ + "fastrand", + "getrandom 0.4.2", + "once_cell", + "rustix", + "windows-sys", +] + +[[package]] +name = "thiserror" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "typenum" +version = "1.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40ce102ab67701b8526c123c1bab5cbe42d7040ccfd0f64af1a385808d2f43de" + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "uuid" +version = "1.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd74a9687298c6858e9b88ec8935ec45d22e8fd5e6394fa1bd4e99a87789c76" +dependencies = [ + "getrandom 0.4.2", + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasip2" +version = "1.0.3+wasi-0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6" +dependencies = [ + "wit-bindgen 0.57.1", +] + +[[package]] +name = "wasip3" +version = "0.4.0+wasi-0.3.0-rc-2026-01-06" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" +dependencies = [ + "wit-bindgen 0.51.0", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.121" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49ace1d07c165b0864824eee619580c4689389afa9dc9ed3a4c75040d82e6790" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.121" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e68e6f4afd367a562002c05637acb8578ff2dea1943df76afb9e83d177c8578" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.121" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d95a9ec35c64b2a7cb35d3fead40c4238d0940c86d107136999567a4703259f2" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.121" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4e0100b01e9f0d03189a92b96772a1fb998639d981193d7dbab487302513441" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wasm-encoder" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" +dependencies = [ + "leb128fmt", + "wasmparser", +] + +[[package]] +name = "wasm-metadata" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" +dependencies = [ + "anyhow", + "indexmap", + "wasm-encoder", + "wasmparser", +] + +[[package]] +name = "wasmparser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" +dependencies = [ + "bitflags", + "hashbrown 0.15.5", + "indexmap", + "semver", +] + +[[package]] +name = "windows" +version = "0.61.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893" +dependencies = [ + "windows-collections", + "windows-core", + "windows-future", + "windows-link 0.1.3", + "windows-numerics", +] + +[[package]] +name = "windows-collections" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8" +dependencies = [ + "windows-core", +] + +[[package]] +name = "windows-core" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link 0.1.3", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-future" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" +dependencies = [ + "windows-core", + "windows-link 0.1.3", + "windows-threading", +] + +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-link" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-numerics" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" +dependencies = [ + "windows-core", + "windows-link 0.1.3", +] + +[[package]] +name = "windows-result" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" +dependencies = [ + "windows-link 0.1.3", +] + +[[package]] +name = "windows-strings" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" +dependencies = [ + "windows-link 0.1.3", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link 0.2.1", +] + +[[package]] +name = "windows-threading" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6" +dependencies = [ + "windows-link 0.1.3", +] + +[[package]] +name = "wit-bindgen" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" +dependencies = [ + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen" +version = "0.57.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" + +[[package]] +name = "wit-bindgen-core" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" +dependencies = [ + "anyhow", + "heck", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" +dependencies = [ + "anyhow", + "heck", + "indexmap", + "prettyplease", + "syn", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" +dependencies = [ + "anyhow", + "bitflags", + "indexmap", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wit-parser", +] + +[[package]] +name = "wit-parser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" +dependencies = [ + "anyhow", + "id-arena", + "indexmap", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser", +] + +[[package]] +name = "zerocopy" +version = "0.8.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zmij" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" diff --git a/src/tools/miri/priroda/Cargo.toml b/src/tools/miri/priroda/Cargo.toml new file mode 100644 index 0000000000000..084773f10e660 --- /dev/null +++ b/src/tools/miri/priroda/Cargo.toml @@ -0,0 +1,19 @@ +[package] +description = "Debugger for Rust MIR." +license = "MIT OR Apache-2.0" +name = "priroda" +repository = "https://github.com/rust-lang/miri" +version = "0.1.0" +edition = "2024" + + +[[bin]] +name = "priroda" +path = "src/main.rs" +doctest = false # and no doc tests + +[dependencies] +miri = { path = ".." } + +[package.metadata.rust-analyzer] +rustc_private = true diff --git a/src/tools/miri/priroda/README.md b/src/tools/miri/priroda/README.md new file mode 100644 index 0000000000000..246fa93fbc44f --- /dev/null +++ b/src/tools/miri/priroda/README.md @@ -0,0 +1,37 @@ +# Priroda + +Priroda is a step-through debugger for Rust programs running under +Miri. + +Current focus: + +- simple CLI prototype +- single-threaded stepping with Miri's interpreter +- commands: empty Enter, `s`, or `step` + +## Setup + +From `miri/`, install the pinned toolchain and the local `cargo-miri` +command: + +```sh +./miri toolchain +./miri install +``` + +Then build the Miri sysroot and export it for Priroda: + +```sh +cargo +miri miri setup +export MIRI_SYSROOT="$(cargo +miri miri setup --print-sysroot)" +``` + +## Run + +Priroda currently reads `MIRI_SYSROOT` directly. After setup: + +```sh +cargo run -p priroda -- tests/pass/empty_main.rs +``` + +At the prompt, press Enter or type `s` / `step`. diff --git a/src/tools/miri/priroda/rust-toolchain.toml b/src/tools/miri/priroda/rust-toolchain.toml new file mode 100644 index 0000000000000..c5d0b97ed4f01 --- /dev/null +++ b/src/tools/miri/priroda/rust-toolchain.toml @@ -0,0 +1,2 @@ +[toolchain] +channel = "miri" diff --git a/src/tools/miri/priroda/src/main.rs b/src/tools/miri/priroda/src/main.rs new file mode 100644 index 0000000000000..f5eaf5f0ac9c4 --- /dev/null +++ b/src/tools/miri/priroda/src/main.rs @@ -0,0 +1,163 @@ +#![feature(rustc_private)] + +use std::task::Poll; + +extern crate miri; +extern crate rustc_codegen_ssa; +extern crate rustc_data_structures; +extern crate rustc_driver; +extern crate rustc_hir; +extern crate rustc_hir_analysis; +extern crate rustc_interface; +extern crate rustc_log; +extern crate rustc_middle; +extern crate rustc_session; + +use std::io::{self, Write}; + +use miri::*; +use rustc_driver::Compilation; +use rustc_hir::attrs::CrateType; +use rustc_interface::interface; +use rustc_middle::ty::TyCtxt; +use rustc_session::EarlyDiagCtxt; +use rustc_session::config::ErrorOutputType; +fn find_sysroot() -> String { + std::env::var("MIRI_SYSROOT") + .expect("set MIRI_SYSROOT to the path from `cargo miri setup --print-sysroot`") +} + +fn main() { + let early_dcx = EarlyDiagCtxt::new(ErrorOutputType::default()); + rustc_driver::init_rustc_env_logger(&early_dcx); + + let mut args: Vec = std::env::args().collect(); + + args.splice(1..1, miri::MIRI_DEFAULT_ARGS.iter().map(ToString::to_string)); + + let sysroot_flag = String::from("--sysroot"); + if !args.contains(&sysroot_flag) { + args.push(sysroot_flag); + args.push(find_sysroot()); + } + //TODO: handle the same `-Z` flags that Miri accepts. + rustc_driver::run_compiler(&args, &mut PrirodaCompilerCalls::new()); +} + +struct PrirodaCompilerCalls; + +impl PrirodaCompilerCalls { + fn new() -> Self { + Self + } +} + +impl rustc_driver::Callbacks for PrirodaCompilerCalls { + fn after_analysis<'tcx>(&mut self, _: &interface::Compiler, tcx: TyCtxt<'tcx>) -> Compilation { + tcx.dcx().emit_stashed_diagnostics(); + tcx.dcx().abort_if_errors(); + + if !tcx.crate_types().contains(&CrateType::Executable) { + //TODO: support non-bin crates by listing functions and letting users call them with manually entered arguments. + tcx.dcx().fatal("priroda only makes sense on bin crates"); + } + + let ecx = create_ecx(tcx); + + let mut session = PrirodaContext::new(ecx); + let result = run_cli_loop(&mut session); + + match result.report_err() { + Ok(()) => {} + Err(err) => + if let Some((return_code, _leak_check)) = report_result(&session.ecx, err) { + //TODO: print the evaluated program's exit code and return to the debugger prompt instead of exiting Priroda. + if return_code != 0 { + std::process::exit(return_code); + } + }, + } + + Compilation::Stop + } +} + +fn create_ecx<'tcx>(tcx: TyCtxt<'tcx>) -> MiriInterpCx<'tcx> { + let (entry_id, entry_type) = miri::entry_fn(tcx); + let config = MiriConfig::default(); + miri::create_ecx(tcx, entry_id, entry_type, &config, None).unwrap() +} + +pub struct PrirodaContext<'tcx> { + ecx: MiriInterpCx<'tcx>, +} + +impl<'tcx> PrirodaContext<'tcx> { + fn new(ecx: MiriInterpCx<'tcx>) -> Self { + Self { ecx } + } + + // TODO: replace the bool with a StepResult enum once we distinguish + // running, finished, breakpoint stops, and other debugger states. + pub fn step(&mut self) -> InterpResult<'tcx, bool> { + if !self.ecx.step()? { + match self.ecx.run_on_stack_empty()? { + Poll::Pending => return interp_ok(true), + Poll::Ready(()) => { + self.ecx.terminate_active_thread(TlsAllocAction::Deallocate)?; + return interp_ok(false); + } + } + } + + interp_ok(true) + } + pub fn print_location(&self) { + let span = self.ecx.machine.current_user_relevant_span(); + let location = self.ecx.tcx.sess.source_map().span_to_diagnostic_string(span); + // TODO: skip noisy std/runtime spans and avoid printing `no-location` + // once the basic command loop is solid. + println!("{location}"); + io::stdout().flush().unwrap(); + } + fn run_command(&mut self, command: SessionCommand) -> InterpResult<'tcx, bool> { + match command { + SessionCommand::Step => self.step(), + } + } +} + +enum SessionCommand { + Step, +} + +fn parse_command(input: &str) -> Option { + match input.trim() { + "" | "s" | "step" => Some(SessionCommand::Step), + _ => None, + } +} + +fn run_cli_loop<'tcx>(session: &mut PrirodaContext<'tcx>) -> InterpResult<'tcx> { + loop { + print!("(priroda) "); + io::stdout().flush().unwrap(); + + let mut input = String::new(); + // TODO: handle EOF explicitly so scripted input can stop the CLI instead + // of being treated like an empty Enter step. + io::stdin().read_line(&mut input).unwrap(); + + if let Some(command) = parse_command(&input) { + match session.run_command(command)? { + false => { + println!("program finished"); + return interp_ok(()); + } + true => session.print_location(), + } + } else { + println!("no command"); + } + } +} From 1ec2ee97cc033c35de6968e0067c8d548e67e403 Mon Sep 17 00:00:00 2001 From: Jules Bertholet Date: Sun, 31 May 2026 17:00:30 -0400 Subject: [PATCH 33/46] Use `ascii::Char` in `convert_while_ascii` --- library/alloc/src/str.rs | 40 +++++++++++++++------------- library/core/src/ascii/ascii_char.rs | 5 ++++ 2 files changed, 26 insertions(+), 19 deletions(-) diff --git a/library/alloc/src/str.rs b/library/alloc/src/str.rs index 230039713de8a..16ffc4d5f91d9 100644 --- a/library/alloc/src/str.rs +++ b/library/alloc/src/str.rs @@ -7,6 +7,8 @@ // It's cleaner to just turn off the unused_imports warning than to fix them. #![allow(unused_imports)] +#[cfg(not(no_global_oom_handling))] +use core::ascii; use core::borrow::{Borrow, BorrowMut}; use core::iter::FusedIterator; use core::mem::MaybeUninit; @@ -431,9 +433,7 @@ impl str { without modifying the original"] #[stable(feature = "unicode_case_mapping", since = "1.2.0")] pub fn to_lowercase(&self) -> String { - // SAFETY: `to_ascii_lowercase` preserves ASCII bytes, so the converted - // prefix remains valid UTF-8. - let (mut s, rest) = unsafe { convert_while_ascii(self, u8::to_ascii_lowercase) }; + let (mut s, rest) = convert_while_ascii(self, ascii::Char::to_lowercase); let prefix_len = s.len(); @@ -638,9 +638,7 @@ impl str { without modifying the original"] #[stable(feature = "unicode_case_mapping", since = "1.2.0")] pub fn to_uppercase(&self) -> String { - // SAFETY: `to_ascii_uppercase` preserves ASCII bytes, so the converted - // prefix remains valid UTF-8. - let (mut s, rest) = unsafe { convert_while_ascii(self, u8::to_ascii_uppercase) }; + let (mut s, rest) = convert_while_ascii(self, ascii::Char::to_uppercase); for c in rest.chars() { match conversions::to_upper(c) { @@ -677,8 +675,8 @@ impl str { /// is considered distinct from `"Å"` (A followed by U+030A COMBINING RING ABOVE), /// even though Unicode considers them canonically equivalent. /// - /// Like [`char::to_casefold_unnormalized()`] this method does not handle language-specific - /// casing, like Turkish and Azeri I/ı/İ/i. See that method's documentation + /// Like [`char::to_casefold_unnormalized()`], this method does not handle language-specific + /// casings like Turkish and Azeri I/ı/İ/i. See that method's documentation /// for more information. /// /// # Examples @@ -740,9 +738,7 @@ impl str { without modifying the original"] #[unstable(feature = "casefold", issue = "154742")] pub fn to_casefold_unnormalized(&self) -> String { - // SAFETY: `to_ascii_lowercase` preserves ASCII bytes, so the converted - // prefix remains valid UTF-8. - let (mut s, rest) = unsafe { convert_while_ascii(self, u8::to_ascii_lowercase) }; + let (mut s, rest) = convert_while_ascii(self, ascii::Char::to_lowercase); for c in rest.chars() { match conversions::to_casefold(c) { @@ -905,15 +901,11 @@ pub unsafe fn from_boxed_utf8_unchecked(v: Box<[u8]>) -> Box { /// /// This function is only public so that it can be verified in a codegen test, /// see `issue-123712-str-to-lower-autovectorization.rs`. -/// -/// # Safety -/// -/// `convert` must return an ASCII byte for every ASCII input byte. #[unstable(feature = "str_internals", issue = "none")] #[doc(hidden)] #[inline] #[cfg(not(no_global_oom_handling))] -pub unsafe fn convert_while_ascii(s: &str, convert: fn(&u8) -> u8) -> (String, &str) { +pub fn convert_while_ascii(s: &str, convert: fn(ascii::Char) -> ascii::Char) -> (String, &str) { // Process the input in chunks of 16 bytes to enable auto-vectorization. // Previously the chunk size depended on the size of `usize`, // but on 32-bit platforms with sse or neon is also the better choice. @@ -921,7 +913,7 @@ pub unsafe fn convert_while_ascii(s: &str, convert: fn(&u8) -> u8) -> (String, & const N: usize = 16; let mut slice = s.as_bytes(); - let mut out = Vec::with_capacity(slice.len()); + let mut out: Vec = Vec::with_capacity(slice.len()); let mut out_slice = out.spare_capacity_mut(); let mut ascii_prefix_len = 0_usize; @@ -946,7 +938,10 @@ pub unsafe fn convert_while_ascii(s: &str, convert: fn(&u8) -> u8) -> (String, & } for j in 0..N { - out_chunk[j] = MaybeUninit::new(convert(&chunk[j])); + out_chunk[j] = MaybeUninit::new( + // SAFETY: we checked that this byte is valid ASCII above + convert(unsafe { ascii::Char::from_u8_unchecked(chunk[j]) }).to_u8(), + ); } ascii_prefix_len += N; @@ -960,10 +955,17 @@ pub unsafe fn convert_while_ascii(s: &str, convert: fn(&u8) -> u8) -> (String, & if byte > 127 { break; } + + let converted_byte = MaybeUninit::new( + // SAFETY: we checked that this byte is valid ASCII above + convert(unsafe { ascii::Char::from_u8_unchecked(byte) }).to_u8(), + ); + // SAFETY: out_slice has at least same length as input slice unsafe { - *out_slice.get_unchecked_mut(0) = MaybeUninit::new(convert(&byte)); + *out_slice.get_unchecked_mut(0) = converted_byte; } + ascii_prefix_len += 1; slice = unsafe { slice.get_unchecked(1..) }; out_slice = unsafe { out_slice.get_unchecked_mut(1..) }; diff --git a/library/core/src/ascii/ascii_char.rs b/library/core/src/ascii/ascii_char.rs index a957005972a20..c8c05ee559f74 100644 --- a/library/core/src/ascii/ascii_char.rs +++ b/library/core/src/ascii/ascii_char.rs @@ -476,6 +476,11 @@ impl AsciiChar { #[unstable(feature = "ascii_char", issue = "110998")] #[inline] pub const unsafe fn from_u8_unchecked(b: u8) -> Self { + assert_unsafe_precondition!( + check_library_ub, + "`ascii::Char::from_u8_unchecked` input cannot exceed 127.", + (b: u8 = b) => b <= 127, + ); // SAFETY: Our safety precondition is that `b` is in-range. unsafe { transmute(b) } } From ee7f3dd5ec6ed9534c4048ec6ef2fac94cf55384 Mon Sep 17 00:00:00 2001 From: Jules Bertholet Date: Sun, 31 May 2026 17:33:41 -0400 Subject: [PATCH 34/46] Document `to_casefold` debug assertions --- library/core/src/unicode/unicode_data.rs | 35 +++++++++++++++++-- library/coretests/tests/unicode.rs | 5 +++ .../src/case_mapping.rs | 35 +++++++++++++++++-- 3 files changed, 69 insertions(+), 6 deletions(-) diff --git a/library/core/src/unicode/unicode_data.rs b/library/core/src/unicode/unicode_data.rs index 729234506f60c..9df020ced674b 100644 --- a/library/core/src/unicode/unicode_data.rs +++ b/library/core/src/unicode/unicode_data.rs @@ -881,17 +881,36 @@ pub mod conversions { lookup(c, &CASEFOLD_LUT).unwrap_or_else(|| { - // fall back to lowercase of uppercase + // Fall back to lowercase of uppercase let uppercase = lookup(c, &UPPERCASE_LUT).unwrap_or([c, '\0', '\0']); + + // We need to take the lowercase of each character in `uppercase`, + // and then concatenate them together. + + // Lowercase the first uppercased char let mut final_result = to_lower(uppercase[0]); + if uppercase[1] != '\0' { + // There's a 2nd uppercase char, lowercase it as well let lowercase_1 = to_lower(uppercase[1]); + + // The lowercase of the second uppercase character + // can't be 3 chars long; + // that would bring the total case-folding length + // above 3 characters, which would violate + // a Unicode stability guarantee. debug_assert_eq!(lowercase_1[2], '\0'); + // Currently, in every case where there + // are multiple uppercased characters, + // the lowercase of the first uppercase + // has length 1. However, Unicode doesn't + // guarantee this. // If, after updating the Unicode data // to a new Unicode version, the below - // assertion starts to fail in tests, + // assertion starts to fail in + // `coretests/tests/unicode.rs` `to_casefold()`, // delete it, and uncomment the // `if` condition and corresponding // `else` block below it. @@ -901,15 +920,25 @@ pub mod conversions { final_result[1] = lowercase_1[0]; if uppercase[2] != '\0' { + // There's a 3rd uppercased char, lowercase it as well. + // Because of the Unicode stability guarantee that case-folding + // does not expand a string more than 3x in length, + // we know this lowercase must be 1 char long. + debug_assert_eq!(lowercase_1[1], '\0'); let lowercase_2 = to_lower(uppercase[2]); debug_assert_eq!(lowercase_2[1], '\0'); debug_assert_eq!(lowercase_2[2], '\0'); final_result[2] = lowercase_2[0]; } else { + // Currently, the lowercase of + // the second uppercase character + // can't be 2 chars long either, + // but Unicode doesn't guarantee this. // If, after updating the Unicode data // to a new Unicode version, the below - // assertion starts to fail in tests, + // assertion starts to fail in + // `coretests/tests/unicode.rs` `to_casefold()`, // delete it and uncomment the line // below it. debug_assert_eq!(lowercase_1[1], '\0'); diff --git a/library/coretests/tests/unicode.rs b/library/coretests/tests/unicode.rs index 6ca45661f7d83..05cea23fd4781 100644 --- a/library/coretests/tests/unicode.rs +++ b/library/coretests/tests/unicode.rs @@ -125,6 +125,11 @@ fn to_titlecase() { ); } +/// This test verifies some assumptions we currently make about Unicode casings +/// which might be falsified by future versions of the standard. +/// It's important that it gets run with debug assertions enabled, +/// so that the debug assertions in `core/src/unicode/unicode_data.rs` +/// `conversions::to_casefold` get run with every possible Unicode character as input. #[test] #[cfg_attr(miri, ignore)] // Miri is too slow fn to_casefold() { diff --git a/src/tools/unicode-table-generator/src/case_mapping.rs b/src/tools/unicode-table-generator/src/case_mapping.rs index ee4dfc2514c20..9336eaf670e6a 100644 --- a/src/tools/unicode-table-generator/src/case_mapping.rs +++ b/src/tools/unicode-table-generator/src/case_mapping.rs @@ -427,17 +427,36 @@ pub fn to_casefold(c: char) -> [char; 3] { lookup(c, &CASEFOLD_LUT).unwrap_or_else(|| { - // fall back to lowercase of uppercase + // Fall back to lowercase of uppercase let uppercase = lookup(c, &UPPERCASE_LUT).unwrap_or([c, '\0', '\0']); + + // We need to take the lowercase of each character in `uppercase`, + // and then concatenate them together. + + // Lowercase the first uppercased char let mut final_result = to_lower(uppercase[0]); + if uppercase[1] != '\0' { + // There's a 2nd uppercase char, lowercase it as well let lowercase_1 = to_lower(uppercase[1]); + + // The lowercase of the second uppercase character + // can't be 3 chars long; + // that would bring the total case-folding length + // above 3 characters, which would violate + // a Unicode stability guarantee. debug_assert_eq!(lowercase_1[2], '\0'); + // Currently, in every case where there + // are multiple uppercased characters, + // the lowercase of the first uppercase + // has length 1. However, Unicode doesn't + // guarantee this. // If, after updating the Unicode data // to a new Unicode version, the below - // assertion starts to fail in tests, + // assertion starts to fail in + // `coretests/tests/unicode.rs` `to_casefold()`, // delete it, and uncomment the // `if` condition and corresponding // `else` block below it. @@ -447,15 +466,25 @@ pub fn to_casefold(c: char) -> [char; 3] { final_result[1] = lowercase_1[0]; if uppercase[2] != '\0' { + // There's a 3rd uppercased char, lowercase it as well. + // Because of the Unicode stability guarantee that case-folding + // does not expand a string more than 3x in length, + // we know this lowercase must be 1 char long. + debug_assert_eq!(lowercase_1[1], '\0'); let lowercase_2 = to_lower(uppercase[2]); debug_assert_eq!(lowercase_2[1], '\0'); debug_assert_eq!(lowercase_2[2], '\0'); final_result[2] = lowercase_2[0]; } else { + // Currently, the lowercase of + // the second uppercase character + // can't be 2 chars long either, + // but Unicode doesn't guarantee this. // If, after updating the Unicode data // to a new Unicode version, the below - // assertion starts to fail in tests, + // assertion starts to fail in + // `coretests/tests/unicode.rs` `to_casefold()`, // delete it and uncomment the line // below it. debug_assert_eq!(lowercase_1[1], '\0'); From 57517b3e1e1dd33da2722f435ff01a46710f97c3 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Mon, 1 Jun 2026 08:06:34 +0200 Subject: [PATCH 35/46] Prepare for merging from rust-lang/rust This updates the rust-version file to bef8e620f19adbfd1530e916ab8caa296ef9c3ee. --- src/tools/miri/rust-version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tools/miri/rust-version b/src/tools/miri/rust-version index 47c935aa9f48a..aaeb424e108f0 100644 --- a/src/tools/miri/rust-version +++ b/src/tools/miri/rust-version @@ -1 +1 @@ -6368fd52cb9f230dfb156097625993e7a8891800 +bef8e620f19adbfd1530e916ab8caa296ef9c3ee From 8954a11438216828ab6a0dab3592fbdc844de91b Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Mon, 1 Jun 2026 09:19:45 +0200 Subject: [PATCH 36/46] minor tweaks --- .../tests/fail-dep/libc/libc-epoll-data-race.rs | 2 +- .../fail-dep/libc/libc_epoll_block_two_thread.rs | 7 ++----- .../tests/pass-dep/libc/libc-epoll-no-blocking.rs | 2 +- src/tools/miri/tests/utils/libc.rs | 14 +++++--------- 4 files changed, 9 insertions(+), 16 deletions(-) diff --git a/src/tools/miri/tests/fail-dep/libc/libc-epoll-data-race.rs b/src/tools/miri/tests/fail-dep/libc/libc-epoll-data-race.rs index 2a0a0054ab7e6..eecf6abb9379f 100644 --- a/src/tools/miri/tests/fail-dep/libc/libc-epoll-data-race.rs +++ b/src/tools/miri/tests/fail-dep/libc/libc-epoll-data-race.rs @@ -48,7 +48,7 @@ fn main() { thread::yield_now(); // With room for one event: check result from epoll_wait. - check_epoll_wait_explicit(epfd, &[Ev { events: EPOLLIN, data: fds_a[1] }], 1, -1); + check_epoll_wait_partial(epfd, &[Ev { events: EPOLLIN, data: fds_a[1] }], 1, -1); // Since we only received one event, we have synchronized with // the write to VAL_ONE but not with the one to VAL_TWO. diff --git a/src/tools/miri/tests/fail-dep/libc/libc_epoll_block_two_thread.rs b/src/tools/miri/tests/fail-dep/libc/libc_epoll_block_two_thread.rs index eb74a5c6b38e6..3eb79121a2f8d 100644 --- a/src/tools/miri/tests/fail-dep/libc/libc_epoll_block_two_thread.rs +++ b/src/tools/miri/tests/fail-dep/libc/libc_epoll_block_two_thread.rs @@ -8,9 +8,6 @@ use std::thread; mod libc_utils; use libc_utils::epoll::*; -// Using `as` cast since `EPOLLET` wraps around -const EPOLL_IN_OUT_ET: u32 = (EPOLLIN | EPOLLOUT | EPOLLET) as _; - // Test if only one thread is unblocked if multiple threads blocked on same epfd. // Expected execution: // 1. Thread 1 blocks. @@ -29,8 +26,8 @@ fn main() { let fd2 = unsafe { libc::dup(fd1) }; // Register both with epoll. - epoll_ctl_add(epfd, fd1, EPOLL_IN_OUT_ET as i32).unwrap(); - epoll_ctl_add(epfd, fd2, EPOLL_IN_OUT_ET as i32).unwrap(); + epoll_ctl_add(epfd, fd1, EPOLLIN | EPOLLOUT | EPOLLET).unwrap(); + epoll_ctl_add(epfd, fd2, EPOLLIN | EPOLLOUT | EPOLLET).unwrap(); // Consume the initial events. check_epoll_wait( diff --git a/src/tools/miri/tests/pass-dep/libc/libc-epoll-no-blocking.rs b/src/tools/miri/tests/pass-dep/libc/libc-epoll-no-blocking.rs index 969004bfbb6f2..c1d6f913e8a1c 100644 --- a/src/tools/miri/tests/pass-dep/libc/libc-epoll-no-blocking.rs +++ b/src/tools/miri/tests/pass-dep/libc/libc-epoll-no-blocking.rs @@ -512,7 +512,7 @@ fn test_epoll_lost_events() { epoll_ctl_add(epfd, fds[1], EPOLLIN | EPOLLOUT | EPOLLET_OR_ZERO).unwrap(); // Two notification should be received. But we only provide buffer for one event. - check_epoll_wait_explicit(epfd, &[Ev { events: EPOLLOUT, data: fds[0] }], 1, 0); + check_epoll_wait_partial(epfd, &[Ev { events: EPOLLOUT, data: fds[0] }], 1, 0); if cfg!(edge_triggered) { // Previous event should be returned for the second epoll_wait but because we're diff --git a/src/tools/miri/tests/utils/libc.rs b/src/tools/miri/tests/utils/libc.rs index 9020b06b912e1..f46e6aad01a5b 100644 --- a/src/tools/miri/tests/utils/libc.rs +++ b/src/tools/miri/tests/utils/libc.rs @@ -168,7 +168,7 @@ pub mod epoll { /// It fetches at most `max_events` events from `epfd` and /// ensures that the returned events match the `expected` events. #[track_caller] - pub fn check_epoll_wait_explicit(epfd: i32, expected: &[Ev], max_events: usize, timeout: i32) { + pub fn check_epoll_wait_partial(epfd: i32, expected: &[Ev], max_events: usize, timeout: i32) { let mut events = vec![libc::epoll_event { events: 0, u64: 0 }; max_events]; let num = errno_result(unsafe { libc::epoll_wait(epfd, events.as_mut_ptr(), i32::try_from(max_events).unwrap(), timeout) @@ -183,16 +183,12 @@ pub mod epoll { } /// Call `epoll_wait` on `epfd` with the provided `timeout` and ensure - /// that the returned events match the `expected` events. - /// This function checks whether `expected` is equal to **all** ready events - /// of `epfd`. If you only want to ensure that `expected` matches a subset - /// of the ready events [`check_epoll_wait_explicit`] should be used instead. + /// that the set of *all* ready events matches `expected`. #[track_caller] pub fn check_epoll_wait(epfd: i32, expected: &[Ev], timeout: i32) { - // We set `max_events` to `expected.len() + 1` to ensure that - // there are no additional ready events besides those which are - // contained in `expected`. - check_epoll_wait_explicit(epfd, &expected, expected.len() + 1, timeout); + // We set `max_events` to `expected.len() + 1` to ensure that there are no additional ready + // events besides those which are contained in `expected`. + check_epoll_wait_partial(epfd, &expected, expected.len() + 1, timeout); } /// This does the same as [`check_epoll_wait`] just without blocking (zero `timeout`). From bf2661d2d1fcf125f92ffac124968810e570783f Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Mon, 1 Jun 2026 09:07:03 +0200 Subject: [PATCH 37/46] windows shims: avoid returning possibly outdated metadata --- src/tools/miri/src/lib.rs | 1 + src/tools/miri/src/shims/files.rs | 41 +++++++++++- src/tools/miri/src/shims/unix/fs.rs | 83 +++++++++++------------- src/tools/miri/src/shims/windows/fs.rs | 90 +++++++++++--------------- 4 files changed, 117 insertions(+), 98 deletions(-) diff --git a/src/tools/miri/src/lib.rs b/src/tools/miri/src/lib.rs index 160f31d50c4bf..72564fdce0409 100644 --- a/src/tools/miri/src/lib.rs +++ b/src/tools/miri/src/lib.rs @@ -9,6 +9,7 @@ feature(abort_unwind) )] #![feature(rustc_private)] +#![feature(dirfd)] #![feature(f16)] #![feature(float_gamma)] #![feature(float_erf)] diff --git a/src/tools/miri/src/shims/files.rs b/src/tools/miri/src/shims/files.rs index 26f98a5f2b196..25dca7b8bb85b 100644 --- a/src/tools/miri/src/shims/files.rs +++ b/src/tools/miri/src/shims/files.rs @@ -1,9 +1,10 @@ use std::any::Any; use std::collections::BTreeMap; -use std::fs::File; +use std::fs::{Dir, File}; use std::io::{ErrorKind, IsTerminal, Read, Seek, SeekFrom, Write}; use std::marker::CoercePointee; use std::ops::Deref; +use std::path::PathBuf; use std::rc::{Rc, Weak}; use std::{fs, io}; @@ -362,6 +363,7 @@ impl FileDescription for io::Stderr { #[derive(Debug)] pub struct FileHandle { pub(crate) file: File, + pub(crate) readable: bool, pub(crate) writable: bool, } @@ -380,6 +382,10 @@ impl FileDescription for FileHandle { ) -> InterpResult<'tcx> { assert!(communicate_allowed, "isolation should have prevented even opening a file"); + if !self.readable { + return finish.call(ecx, Err(ErrorKind::PermissionDenied.into())); + } + let mut file = &self.file; let result = ecx.read_from_host(|buf| file.read(buf), len, ptr)?; finish.call(ecx, result) @@ -468,6 +474,39 @@ impl FileDescription for FileHandle { } } +#[derive(Debug)] +pub struct DirHandle { + #[cfg_attr(bootstrap, allow(unused))] + pub(crate) dir: Dir, + /// Fallback used under `cfg(bootstrap)`. + #[cfg_attr(not(bootstrap), allow(unused))] + pub(crate) path: PathBuf, +} + +impl FileDescription for DirHandle { + fn name(&self) -> &'static str { + "directory" + } + + fn metadata<'tcx>( + &self, + ) -> InterpResult<'tcx, Either, &'static str>> { + #[cfg(not(bootstrap))] + return interp_ok(Either::Left(self.dir.metadata())); + #[cfg(bootstrap)] + return interp_ok(Either::Left(std::fs::metadata(&self.path))); + } + + fn destroy<'tcx>( + self, + _self_id: FdId, + _communicate_allowed: bool, + _ecx: &mut MiriInterpCx<'tcx>, + ) -> InterpResult<'tcx, io::Result<()>> { + interp_ok(Ok(())) + } +} + /// Like /dev/null #[derive(Debug)] pub struct NullOutput; diff --git a/src/tools/miri/src/shims/unix/fs.rs b/src/tools/miri/src/shims/unix/fs.rs index 0b0837f4b8bc2..659125fa3388c 100644 --- a/src/tools/miri/src/shims/unix/fs.rs +++ b/src/tools/miri/src/shims/unix/fs.rs @@ -64,6 +64,10 @@ impl UnixFileDescription for FileHandle { finish: DynMachineCallback<'tcx, Result>, ) -> InterpResult<'tcx> { assert!(communicate_allowed, "isolation should have prevented even opening a file"); + if !self.readable { + return finish.call(ecx, Err(LibcError("EBADF"))); + } + let mut bytes = vec![0; len]; // Emulates pread using seek + read + seek to restore cursor position. // Correctness of this emulation relies on sequential nature of Miri execution. @@ -101,6 +105,10 @@ impl UnixFileDescription for FileHandle { finish: DynMachineCallback<'tcx, Result>, ) -> InterpResult<'tcx> { assert!(communicate_allowed, "isolation should have prevented even opening a file"); + if !self.writable { + return finish.call(ecx, Err(LibcError("EBADF"))); + } + // Emulates pwrite using seek + write + seek to restore cursor position. // Correctness of this emulation relies on sequential nature of Miri execution. // The closure is used to emulate `try` block, since we "bubble" `io::Error` using `?`. @@ -387,6 +395,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { throw_unsup_format!("access mode flags on this target are unsupported"); } let mut writable = true; + let mut readable = true; // Now we check the access mode let access_mode = flag & 0b11; @@ -396,6 +405,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { writable = false; options.read(true); } else if access_mode == o_wronly { + readable = false; options.write(true); } else if access_mode == o_rdwr { options.read(true).write(true); @@ -495,7 +505,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { let fd = options .open(path) - .map(|file| this.machine.fds.insert_new(FileHandle { file, writable })); + .map(|file| this.machine.fds.insert_new(FileHandle { file, writable, readable })); interp_ok(Scalar::from_i32(this.try_unwrap_io_result(fd)?)) } @@ -945,12 +955,12 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // The docs don't talk about what happens for non-regular files... throw_unsup_format!("`fchmod` is only supported on regular files") }; - - // Reject if isolation is enabled. - if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op { - this.reject_in_isolation("`fchmod`", reject_with)?; - return this.set_errno_and_return_neg1_i32(LibcError("EACCES")); + if !file.writable && !file.readable { + // Apparently, `fchmod` on a read-only file is fine. But let's not allow it on a + // path-only file. + return this.set_errno_and_return_neg1_i32(LibcError("EBADF")); } + assert!(this.machine.communicate(), "isolation should have prevented even opening a file"); let permissions = this.host_permissions_from_mode(mode.try_into().unwrap())?; if let Err(err) = file.file.set_permissions(permissions) { @@ -1306,33 +1316,25 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { fn ftruncate64(&mut self, fd_num: i32, length: i128) -> InterpResult<'tcx, Scalar> { let this = self.eval_context_mut(); - // Reject if isolation is enabled. - if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op { - this.reject_in_isolation("`ftruncate64`", reject_with)?; - // Set error code as "EBADF" (bad fd) - return this.set_errno_and_return_neg1_i32(LibcError("EBADF")); - } - let Some(fd) = this.machine.fds.get(fd_num) else { return this.set_errno_and_return_neg1_i32(LibcError("EBADF")); }; - let Some(file) = fd.downcast::() else { // The docs say that EINVAL is returned when the FD "does not reference a regular file // or a POSIX shared memory object" (and we don't support shmem objects). - return interp_ok(this.eval_libc("EINVAL")); + return this.set_errno_and_return_neg1_i32(LibcError("EINVAL")); }; + if !file.writable { + // man page says "EBADF or EINVAL", Linux seems to use EINVAL. + return this.set_errno_and_return_neg1_i32(LibcError("EINVAL")); + } + assert!(this.machine.communicate(), "isolation should have prevented even opening a file"); - if file.writable { - if let Ok(length) = length.try_into() { - let result = file.file.set_len(length); - let result = this.try_unwrap_io_result(result.map(|_| 0i32))?; - interp_ok(Scalar::from_i32(result)) - } else { - this.set_errno_and_return_neg1_i32(LibcError("EINVAL")) - } + if let Ok(length) = length.try_into() { + let result = file.file.set_len(length); + let result = this.try_unwrap_io_result(result.map(|_| 0i32))?; + interp_ok(Scalar::from_i32(result)) } else { - // The file is not writable this.set_errno_and_return_neg1_i32(LibcError("EINVAL")) } } @@ -1405,13 +1407,6 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { let fd = this.read_scalar(fd_op)?.to_i32()?; - // Reject if isolation is enabled. - if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op { - this.reject_in_isolation("`fsync`", reject_with)?; - // Set error code as "EBADF" (bad fd) - return this.set_errno_and_return_neg1_i32(LibcError("EBADF")); - } - self.ffullsync_fd(fd) } @@ -1424,6 +1419,8 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { let file = fd.downcast::().ok_or_else(|| { err_unsup_format!("`fsync` is only supported on file-backed file descriptors") })?; + assert!(this.machine.communicate(), "isolation should have prevented even opening a file"); + let io_result = maybe_sync_file(&file.file, file.writable, File::sync_all); interp_ok(Scalar::from_i32(this.try_unwrap_io_result(io_result)?)) } @@ -1433,13 +1430,6 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { let fd = this.read_scalar(fd_op)?.to_i32()?; - // Reject if isolation is enabled. - if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op { - this.reject_in_isolation("`fdatasync`", reject_with)?; - // Set error code as "EBADF" (bad fd) - return this.set_errno_and_return_neg1_i32(LibcError("EBADF")); - } - let Some(fd) = this.machine.fds.get(fd) else { return this.set_errno_and_return_neg1_i32(LibcError("EBADF")); }; @@ -1447,6 +1437,8 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { let file = fd.downcast::().ok_or_else(|| { err_unsup_format!("`fdatasync` is only supported on file-backed file descriptors") })?; + assert!(this.machine.communicate(), "isolation should have prevented even opening a file"); + let io_result = maybe_sync_file(&file.file, file.writable, File::sync_data); interp_ok(Scalar::from_i32(this.try_unwrap_io_result(io_result)?)) } @@ -1475,13 +1467,6 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { return this.set_errno_and_return_neg1_i32(LibcError("EINVAL")); } - // Reject if isolation is enabled. - if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op { - this.reject_in_isolation("`sync_file_range`", reject_with)?; - // Set error code as "EBADF" (bad fd) - return this.set_errno_and_return_neg1_i32(LibcError("EBADF")); - } - let Some(fd) = this.machine.fds.get(fd) else { return this.set_errno_and_return_neg1_i32(LibcError("EBADF")); }; @@ -1489,6 +1474,8 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { let file = fd.downcast::().ok_or_else(|| { err_unsup_format!("`sync_data_range` is only supported on file-backed file descriptors") })?; + assert!(this.machine.communicate(), "isolation should have prevented even opening a file"); + let io_result = maybe_sync_file(&file.file, file.writable, File::sync_data); interp_ok(Scalar::from_i32(this.try_unwrap_io_result(io_result)?)) } @@ -1714,7 +1701,11 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { let file = fopts.open(bytes_to_os_str(template_bytes)?); match file { Ok(f) => { - let fd = this.machine.fds.insert_new(FileHandle { file: f, writable: true }); + let fd = this.machine.fds.insert_new(FileHandle { + file: f, + writable: true, + readable: true, + }); return interp_ok(Scalar::from_i32(fd)); } Err(e) => diff --git a/src/tools/miri/src/shims/windows/fs.rs b/src/tools/miri/src/shims/windows/fs.rs index 4e2f70fa561d3..de0ad7ffa5edd 100644 --- a/src/tools/miri/src/shims/windows/fs.rs +++ b/src/tools/miri/src/shims/windows/fs.rs @@ -1,4 +1,4 @@ -use std::fs::{Metadata, OpenOptions}; +use std::fs::{self, Dir}; use std::io; use std::io::SeekFrom; use std::time::SystemTime; @@ -7,39 +7,10 @@ use bitflags::bitflags; use rustc_abi::Size; use rustc_target::spec::Os; -use crate::shims::files::{FdId, FileDescription, FileHandle}; +use crate::shims::files::{DirHandle, FileHandle}; use crate::shims::windows::handle::{EvalContextExt as _, Handle}; use crate::*; -/// Windows supports handles without any read/write/delete permissions - these handles can get -/// metadata, but little else. We represent that by storing the metadata from the time the handle -/// was opened. -#[derive(Debug)] -pub struct MetadataHandle { - pub(crate) meta: Metadata, -} - -impl FileDescription for MetadataHandle { - fn name(&self) -> &'static str { - "metadata-only" - } - - fn metadata<'tcx>( - &self, - ) -> InterpResult<'tcx, Either, &'static str>> { - interp_ok(Either::Left(Ok(self.meta.clone()))) - } - - fn destroy<'tcx>( - self, - _self_id: FdId, - _communicate_allowed: bool, - _ecx: &mut MiriInterpCx<'tcx>, - ) -> InterpResult<'tcx, io::Result<()>> { - interp_ok(Ok(())) - } -} - #[derive(Copy, Clone, Debug, PartialEq)] enum CreationDisposition { CreateAlways, @@ -186,8 +157,10 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { throw_unsup_format!("CreateFileW: Template files are not supported"); } - // We need to know if the file is a directory to correctly open directory handles. - // This is racy, but currently the stdlib doesn't appear to offer a better solution. + // We need to know if the file is a directory to correctly open directory handles. This is + // racy, but currently the stdlib doesn't appear to offer a better solution. We do later + // verify that our guess was correct so worst-case, Miri ICEs here. + // FIXME: retry in a loop if we get an error indicating we got the wrong file type? let is_dir = file_name.is_dir(); // BACKUP_SEMANTICS is how Windows calls the act of opening a directory handle. @@ -199,7 +172,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { let desired_read = desired_access & generic_read != 0; let desired_write = desired_access & generic_write != 0; - let mut options = OpenOptions::new(); + let mut options = fs::OpenOptions::new(); if desired_read { desired_access &= !generic_read; options.read(true); @@ -233,23 +206,20 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { } let handle = if is_dir { - // Open this as a directory. We don't support proper directory handles, but we only need - // this for the metadata, so we only store that. That still makes us immune to races, - // though it also means we fail to reflect changes to the dir's metadata. - // FIXME: use `std::fs::Dir`, once that supports getting metadata. - file_name.metadata().map(|meta| { - let fd_num = this.machine.fds.insert_new(MetadataHandle { meta }); - Handle::File(fd_num) - }) - } else if creation_disposition == OpenExisting && !(desired_read || desired_write) { - // Windows supports handles with no permissions. These allow things such as reading - // metadata, but not file content. - file_name.metadata().map(|meta| { - let fd_num = this.machine.fds.insert_new(MetadataHandle { meta }); + // Open this as a directory. + // FIXME: shouldn't we check `creation_disposition` here? + Dir::open(&file_name).map(|dir| { + #[cfg(not(bootstrap))] + assert!( + dir.metadata().unwrap().is_dir(), + "we tried to open a directory and got a file" + ); + let fd_num = this.machine.fds.insert_new(DirHandle { dir, path: file_name }); Handle::File(fd_num) }) } else { - // Open this as a standard file. + // Open this as a standard file. We already set the `read`/`write` flags above, + // but we still need to represent the `creation_disposition`. match creation_disposition { CreateAlways | OpenAlways => { options.create(true); @@ -266,15 +236,33 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { options.append(true); } } - OpenExisting => {} // Default options + OpenExisting => { + if !desired_read && !desired_write { + // Windows supports handles with no permissions. These allow things such as + // reading metadata, but not file content. This is used by `Path::metadata`. + // `std` does not support this. To ensure we behave correctly as often as + // possible, we open the file for reading and live with the fact that this + // might incorrectly return `PermissionDenied`. + // FIXME: We could probably use `OpenOptionsExt`? On a Unix host, + // `O_PATH` apparently can open files for metadata use only. + options.read(true); + } + } TruncateExisting => { options.truncate(true); } } options.open(file_name).map(|file| { - let fd_num = - this.machine.fds.insert_new(FileHandle { file, writable: desired_write }); + assert!( + !file.metadata().unwrap().is_dir(), + "we tried to open a file and got a directory" + ); + let fd_num = this.machine.fds.insert_new(FileHandle { + file, + writable: desired_write, + readable: desired_read, + }); Handle::File(fd_num) }) }; From 19a1abe9756e4804e6e6d2143a7025993f057cc0 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Mon, 1 Jun 2026 09:07:24 +0200 Subject: [PATCH 38/46] libc-fs tests: cover some more corner cases --- src/tools/miri/tests/pass-dep/libc/libc-fs.rs | 32 +++++++++++++++++-- src/tools/miri/tests/pass/shims/fs.rs | 5 +-- 2 files changed, 32 insertions(+), 5 deletions(-) diff --git a/src/tools/miri/tests/pass-dep/libc/libc-fs.rs b/src/tools/miri/tests/pass-dep/libc/libc-fs.rs index 84b2fab9e82a7..d1dbcd1ef4cd9 100644 --- a/src/tools/miri/tests/pass-dep/libc/libc-fs.rs +++ b/src/tools/miri/tests/pass-dep/libc/libc-fs.rs @@ -28,6 +28,7 @@ fn main() { test_file_open_unix_allow_two_args(); test_file_open_unix_needs_three_args(); test_file_open_unix_extra_third_arg(); + test_file_open_dir(); #[cfg(target_os = "linux")] test_o_tmpfile_flag(); test_posix_mkstemp(); @@ -212,21 +213,46 @@ fn test_file_open_unix_allow_two_args() { let path = utils::prepare_with_content("test_file_open_unix_allow_two_args.txt", &[]); let name = CString::new(path.into_os_string().into_encoded_bytes()).unwrap(); - let _fd = unsafe { libc::open(name.as_ptr(), libc::O_RDONLY) }; + let _fd = errno_result(unsafe { libc::open(name.as_ptr(), libc::O_RDONLY) }).unwrap(); } fn test_file_open_unix_needs_three_args() { let path = utils::prepare_with_content("test_file_open_unix_needs_three_args.txt", &[]); let name = CString::new(path.into_os_string().into_encoded_bytes()).unwrap(); - let _fd = unsafe { libc::open(name.as_ptr(), libc::O_CREAT, 0o666) }; + let _fd = + errno_result(unsafe { libc::open(name.as_ptr(), libc::O_CREAT | libc::O_RDWR, 0o666) }) + .unwrap(); } fn test_file_open_unix_extra_third_arg() { let path = utils::prepare_with_content("test_file_open_unix_extra_third_arg.txt", &[]); let name = CString::new(path.into_os_string().into_encoded_bytes()).unwrap(); - let _fd = unsafe { libc::open(name.as_ptr(), libc::O_RDONLY, 42) }; + let _fd = errno_result(unsafe { libc::open(name.as_ptr(), libc::O_RDONLY, 42) }).unwrap(); +} + +fn test_file_open_dir() { + let dir_path = utils::prepare_dir("miri_test_fs_dir"); + create_dir(&dir_path).unwrap(); + let dir_name = CString::new(dir_path.into_os_string().into_encoded_bytes()).unwrap(); + + // Opening it for read-write fails. The error code differs between Unix and Windows hosts. + let err = errno_result(unsafe { libc::open(dir_name.as_ptr(), libc::O_RDWR) }).unwrap_err(); + assert!( + [libc::EISDIR, libc::EPERM].contains(&err.raw_os_error().unwrap()), + "unexpected errno: {err}" + ); + + // Opening it for reading succeeds, but then reading fails. + // FIXME: currently does not behave as expected on Windows hosts. + // See . + // let fd = errno_result(unsafe { libc::open(dir_name.as_ptr(), libc::O_RDONLY) }).unwrap(); + // let mut buf = [0u8; 4]; + // let err = + // errno_result(unsafe { libc::read(fd, buf.as_mut_ptr().cast(), buf.len()) }).unwrap_err(); + // assert_eq!(err.raw_os_error().unwrap(), libc::EISDIR, "unexpected errno: {err}"); + // libc_utils::errno_check(unsafe { libc::close(fd) }); } fn test_dup_stdout_stderr() { diff --git a/src/tools/miri/tests/pass/shims/fs.rs b/src/tools/miri/tests/pass/shims/fs.rs index 393ce66d876d5..cb1088fb08515 100644 --- a/src/tools/miri/tests/pass/shims/fs.rs +++ b/src/tools/miri/tests/pass/shims/fs.rs @@ -224,9 +224,10 @@ fn test_file_set_len() { let file = OpenOptions::new().read(true).open(&path).unwrap(); // Due to https://github.com/rust-lang/miri/issues/4457, we have to assume the failure could // be either of the Windows or Unix kind, no matter which platform we're on. + let err = file.set_len(14).unwrap_err(); assert!( - [ErrorKind::PermissionDenied, ErrorKind::InvalidInput] - .contains(&file.set_len(14).unwrap_err().kind()) + [ErrorKind::PermissionDenied, ErrorKind::InvalidInput].contains(&err.kind()), + "unexpected error: {err}" ); remove_file(&path).unwrap(); From 9aa390e63a9d03b10a8f02bbd0941d50d0ac84c2 Mon Sep 17 00:00:00 2001 From: Mohamed Ali Date: Fri, 29 May 2026 23:18:55 +0300 Subject: [PATCH 39/46] [Priroda] Extract `miri_step` to share core step logic --- src/tools/miri/priroda/src/main.rs | 32 ++++++------------------ src/tools/miri/src/concurrency/thread.rs | 26 +++++++++++++------ 2 files changed, 26 insertions(+), 32 deletions(-) diff --git a/src/tools/miri/priroda/src/main.rs b/src/tools/miri/priroda/src/main.rs index f5eaf5f0ac9c4..775f2403bd1f0 100644 --- a/src/tools/miri/priroda/src/main.rs +++ b/src/tools/miri/priroda/src/main.rs @@ -1,7 +1,5 @@ #![feature(rustc_private)] -use std::task::Poll; - extern crate miri; extern crate rustc_codegen_ssa; extern crate rustc_data_structures; @@ -97,21 +95,12 @@ impl<'tcx> PrirodaContext<'tcx> { Self { ecx } } - // TODO: replace the bool with a StepResult enum once we distinguish - // running, finished, breakpoint stops, and other debugger states. - pub fn step(&mut self) -> InterpResult<'tcx, bool> { - if !self.ecx.step()? { - match self.ecx.run_on_stack_empty()? { - Poll::Pending => return interp_ok(true), - Poll::Ready(()) => { - self.ecx.terminate_active_thread(TlsAllocAction::Deallocate)?; - return interp_ok(false); - } - } - } - - interp_ok(true) + // TODO: return a StepResult enum once we distinguish breakpoint stops, + // program exit, and other debugger states. + pub fn step(&mut self) -> InterpResult<'tcx> { + self.ecx.miri_step() } + pub fn print_location(&self) { let span = self.ecx.machine.current_user_relevant_span(); let location = self.ecx.tcx.sess.source_map().span_to_diagnostic_string(span); @@ -120,7 +109,7 @@ impl<'tcx> PrirodaContext<'tcx> { println!("{location}"); io::stdout().flush().unwrap(); } - fn run_command(&mut self, command: SessionCommand) -> InterpResult<'tcx, bool> { + fn run_command(&mut self, command: SessionCommand) -> InterpResult<'tcx> { match command { SessionCommand::Step => self.step(), } @@ -149,13 +138,8 @@ fn run_cli_loop<'tcx>(session: &mut PrirodaContext<'tcx>) -> InterpResult<'tcx> io::stdin().read_line(&mut input).unwrap(); if let Some(command) = parse_command(&input) { - match session.run_command(command)? { - false => { - println!("program finished"); - return interp_ok(()); - } - true => session.print_location(), - } + session.run_command(command)?; + session.print_location(); } else { println!("no command"); } diff --git a/src/tools/miri/src/concurrency/thread.rs b/src/tools/miri/src/concurrency/thread.rs index b730b9daa99a9..7d9001e73b30d 100644 --- a/src/tools/miri/src/concurrency/thread.rs +++ b/src/tools/miri/src/concurrency/thread.rs @@ -856,6 +856,23 @@ trait EvalContextPrivExt<'tcx>: MiriInterpCxExt<'tcx> { // Public interface to thread management. impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {} pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { + /// Public because this is used by Priroda. + fn miri_step(&mut self) -> InterpResult<'tcx> { + let this = self.eval_context_mut(); + + if !this.step()? { + // See if this thread can do something else. + match this.run_on_stack_empty()? { + Poll::Pending => {} //keep going + Poll::Ready(()) => { + this.terminate_active_thread(TlsAllocAction::Deallocate)?; + } + } + } + + interp_ok(()) + } + #[inline] fn thread_id_try_from(&self, id: impl TryInto) -> Result { self.eval_context_ref().machine.threads.thread_id_try_from(id) @@ -1289,14 +1306,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { } match this.schedule()? { SchedulingAction::ExecuteStep => { - if !this.step()? { - // See if this thread can do something else. - match this.run_on_stack_empty()? { - Poll::Pending => {} // keep going - Poll::Ready(()) => - this.terminate_active_thread(TlsAllocAction::Deallocate)?, - } - } + this.miri_step()?; } SchedulingAction::SleepAndWaitForIo(duration) => { if this.machine.communicate() { From 82d915976d02b34380499127540e2f44bcf618bd Mon Sep 17 00:00:00 2001 From: Mohamed Ali Date: Mon, 1 Jun 2026 14:53:15 +0300 Subject: [PATCH 40/46] [Priroda] Add build-only CI job --- src/tools/miri/.github/workflows/ci.yml | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/tools/miri/.github/workflows/ci.yml b/src/tools/miri/.github/workflows/ci.yml index 14978c2b967b1..73a6d9026e9c4 100644 --- a/src/tools/miri/.github/workflows/ci.yml +++ b/src/tools/miri/.github/workflows/ci.yml @@ -155,6 +155,18 @@ jobs: cd ../rust # ./x does not seem to like being invoked from elsewhere ./x check miri + # This job is intentionally separate from `test` so that Priroda can be + # developed as a separate crate inside the Miri repository for now. + priroda-build: + name: Priroda + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + - uses: ./.github/workflows/setup + - name: build Priroda + working-directory: priroda + run: cargo build --locked + coverage: name: coverage report runs-on: ubuntu-latest @@ -168,7 +180,7 @@ jobs: # ALL THE PREVIOUS JOBS NEED TO BE ADDED TO THE `needs` SECTION OF THIS JOB! # And they should be added below in `cron-fail-notify` as well. conclusion: - needs: [test, style, bootstrap, coverage] + needs: [test, style, bootstrap, coverage, priroda-build] # We need to ensure this job does *not* get skipped if its dependencies fail, # because a skipped job is considered a success by GitHub. So we have to # overwrite `if:`. We use `!cancelled()` to ensure the job does still not get run @@ -252,7 +264,7 @@ jobs: cron-fail-notify: name: cronjob failure notification runs-on: ubuntu-latest - needs: [test, style, bootstrap, coverage] + needs: [test, style, bootstrap, coverage, priroda-build] if: ${{ github.event_name == 'schedule' && failure() }} steps: # Send a Zulip notification From 3d78a303c517dbf0394d538febe0711dd7ac631e Mon Sep 17 00:00:00 2001 From: Krasimir Georgiev Date: Mon, 1 Jun 2026 14:32:08 +0000 Subject: [PATCH 41/46] tests: adapt for LLVM codegen change https://github.com/llvm/llvm-project/commit/7966cbbdd02b686dbee9134514ea113772bcfa62 --- tests/assembly-llvm/simd-bitmask.rs | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/tests/assembly-llvm/simd-bitmask.rs b/tests/assembly-llvm/simd-bitmask.rs index 115d76e8ba927..98f63c165f352 100644 --- a/tests/assembly-llvm/simd-bitmask.rs +++ b/tests/assembly-llvm/simd-bitmask.rs @@ -1,5 +1,5 @@ //@ add-minicore -//@ revisions: x86 x86-avx2 x86-avx512 aarch64 +//@ revisions: x86 x86-avx2 x86-avx512 aarch64-llvm-pre-23 aarch64 //@ [x86] compile-flags: --target=x86_64-unknown-linux-gnu -C llvm-args=-x86-asm-syntax=intel //@ [x86] needs-llvm-components: x86 //@ [x86-avx2] compile-flags: --target=x86_64-unknown-linux-gnu -C llvm-args=-x86-asm-syntax=intel @@ -8,8 +8,12 @@ //@ [x86-avx512] compile-flags: --target=x86_64-unknown-linux-gnu -C llvm-args=-x86-asm-syntax=intel //@ [x86-avx512] compile-flags: -C target-feature=+avx512f,+avx512vl,+avx512bw,+avx512dq //@ [x86-avx512] needs-llvm-components: x86 +//@ [aarch64] min-llvm-version: 23 //@ [aarch64] compile-flags: --target=aarch64-unknown-linux-gnu //@ [aarch64] needs-llvm-components: aarch64 +//@ [aarch64-llvm-pre-23] ignore-llvm-version: 23 - 99 +//@ [aarch64-llvm-pre-23] compile-flags: --target=aarch64-unknown-linux-gnu +//@ [aarch64-llvm-pre-23] needs-llvm-components: aarch64 //@ assembly-output: emit-asm //@ compile-flags: --crate-type=lib -Copt-level=3 -C panic=abort @@ -54,14 +58,23 @@ pub unsafe extern "C" fn bitmask_m8x16(mask: m8x16) -> u16 { // x86-avx512-NOT: vpsllw xmm0 // x86-avx512: vpmovmskb eax, xmm0 // + // aarch64-pre-llvm-23: adrp + // aarch64-pre-llvm-23-NEXT: cmlt + // aarch64-pre-llvm-23-NEXT: ldr + // aarch64-pre-llvm-23-NEXT: and + // aarch64-pre-llvm-23-NEXT: ext + // aarch64-pre-llvm-23-NEXT: zip1 + // aarch64-pre-llvm-23-NEXT: addv + // aarch64-pre-llvm-23-NEXT: fmov + // // aarch64: adrp // aarch64-NEXT: cmlt // aarch64-NEXT: ldr // aarch64-NEXT: and - // aarch64-NEXT: ext - // aarch64-NEXT: zip1 - // aarch64-NEXT: addv - // aarch64-NEXT: fmov + // aarch64-NEXT: addp + // aarch64-NEXT: addp + // aarch64-NEXT: addp + // aarch64-NEXT: umov simd_bitmask(mask) } From c668b83eed1dc7f39673b2e895562d03b3b39fd9 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Fri, 29 May 2026 15:45:10 -0700 Subject: [PATCH 42/46] Use a `ArrayVec` in `CastTarget` This commit switches a fixed-size list of `[Option; 8]` to instead holding `ArrayVec` in the `CastTarget` type used when calculating ABIs. This is inspired by [discussion on Zulip][link] where I'm hoping to in the near future extend the usage of this to possibly beyond 8 elements for a new WebAssembly ABI taking advantage of multi-value. For now though this mostly just switches to array/slice-like idioms of accessors rather than dealing with `Option` as the unit. [link]: https://rust-lang.zulipchat.com/#narrow/channel/131828-t-compiler/topic/Using.20.60ArgAbi.3A.3Amake_direct_deprecated.60/with/598607139 --- Cargo.lock | 1 + .../src/abi/pass_mode.rs | 5 ++- compiler/rustc_codegen_gcc/src/abi.rs | 4 +-- compiler/rustc_codegen_llvm/src/abi.rs | 5 ++- compiler/rustc_codegen_ssa/src/mir/block.rs | 7 ++--- .../rustc_codegen_ssa/src/mir/naked_asm.rs | 2 +- .../src/mono_checks/abi_check.rs | 5 +-- compiler/rustc_target/Cargo.toml | 1 + compiler/rustc_target/src/callconv/mips64.rs | 22 ++++++------- compiler/rustc_target/src/callconv/mod.rs | 26 ++++++++++------ compiler/rustc_target/src/callconv/nvptx64.rs | 15 +++++---- compiler/rustc_target/src/callconv/sparc64.rs | 31 +++++++------------ tests/ui/abi/pass-indirectly-attr.stderr | 11 +------ 13 files changed, 58 insertions(+), 77 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 059b203fb06c4..02d7e51a6d9ae 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4756,6 +4756,7 @@ dependencies = [ name = "rustc_target" version = "0.0.0" dependencies = [ + "arrayvec", "bitflags", "object 0.37.3", "rustc_abi", diff --git a/compiler/rustc_codegen_cranelift/src/abi/pass_mode.rs b/compiler/rustc_codegen_cranelift/src/abi/pass_mode.rs index 0283263cc6047..edbee60471830 100644 --- a/compiler/rustc_codegen_cranelift/src/abi/pass_mode.rs +++ b/compiler/rustc_codegen_cranelift/src/abi/pass_mode.rs @@ -44,9 +44,9 @@ fn apply_attrs_to_abi_param(param: AbiParam, arg_attrs: ArgAttributes) -> AbiPar fn cast_target_to_abi_params(cast: &CastTarget) -> SmallVec<[(Size, AbiParam); 2]> { if let Some(offset_from_start) = cast.rest_offset { - assert!(cast.prefix[1..].iter().all(|p| p.is_none())); + assert_eq!(cast.prefix.len(), 1); assert_eq!(cast.rest.unit.size, cast.rest.total); - let first = cast.prefix[0].unwrap(); + let first = cast.prefix[0]; let second = cast.rest.unit; return smallvec![ (Size::ZERO, reg_to_abi_param(first)), @@ -71,7 +71,6 @@ fn cast_target_to_abi_params(cast: &CastTarget) -> SmallVec<[(Size, AbiParam); 2 let args = cast .prefix .iter() - .flatten() .map(|®| reg_to_abi_param(reg)) .chain((0..rest_count).map(|_| reg_to_abi_param(cast.rest.unit))); diff --git a/compiler/rustc_codegen_gcc/src/abi.rs b/compiler/rustc_codegen_gcc/src/abi.rs index 7239a5bcb0413..fb243ff842c83 100644 --- a/compiler/rustc_codegen_gcc/src/abi.rs +++ b/compiler/rustc_codegen_gcc/src/abi.rs @@ -46,7 +46,7 @@ impl GccType for CastTarget { ) }; - if self.prefix.iter().all(|x| x.is_none()) { + if self.prefix.is_empty() { // Simplify to a single unit when there is no prefix and size <= unit size if self.rest.total <= self.rest.unit.size { return rest_gcc_unit; @@ -62,7 +62,7 @@ impl GccType for CastTarget { let mut args: Vec<_> = self .prefix .iter() - .flat_map(|option_reg| option_reg.map(|reg| reg.gcc_type(cx))) + .map(|reg| reg.gcc_type(cx)) .chain((0..rest_count).map(|_| rest_gcc_unit)) .collect(); diff --git a/compiler/rustc_codegen_llvm/src/abi.rs b/compiler/rustc_codegen_llvm/src/abi.rs index c3bf566b35880..718322a691f70 100644 --- a/compiler/rustc_codegen_llvm/src/abi.rs +++ b/compiler/rustc_codegen_llvm/src/abi.rs @@ -187,7 +187,7 @@ impl LlvmType for CastTarget { // Simplify to a single unit or an array if there's no prefix. // This produces the same layout, but using a simpler type. - if self.prefix.iter().all(|x| x.is_none()) { + if self.prefix.is_empty() { // We can't do this if is_consecutive is set and the unit would get // split on the target. Currently, this is only relevant for i128 // registers. @@ -199,8 +199,7 @@ impl LlvmType for CastTarget { } // Generate a struct type with the prefix and the "rest" arguments. - let prefix_args = - self.prefix.iter().flat_map(|option_reg| option_reg.map(|reg| reg.llvm_type(cx))); + let prefix_args = self.prefix.iter().map(|reg| reg.llvm_type(cx)); let rest_args = (0..rest_count).map(|_| rest_ll_unit); let args: Vec<_> = prefix_args.chain(rest_args).collect(); cx.type_struct(&args, false) diff --git a/compiler/rustc_codegen_ssa/src/mir/block.rs b/compiler/rustc_codegen_ssa/src/mir/block.rs index b6b95c5f12aae..20089cd0803a3 100644 --- a/compiler/rustc_codegen_ssa/src/mir/block.rs +++ b/compiler/rustc_codegen_ssa/src/mir/block.rs @@ -2233,9 +2233,9 @@ fn load_cast<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>( ) -> Bx::Value { let cast_ty = bx.cast_backend_type(cast); if let Some(offset_from_start) = cast.rest_offset { - assert!(cast.prefix[1..].iter().all(|p| p.is_none())); + assert_eq!(cast.prefix.len(), 1); assert_eq!(cast.rest.unit.size, cast.rest.total); - let first_ty = bx.reg_backend_type(&cast.prefix[0].unwrap()); + let first_ty = bx.reg_backend_type(&cast.prefix[0]); let second_ty = bx.reg_backend_type(&cast.rest.unit); let first = bx.load(first_ty, ptr, align); let second_ptr = bx.inbounds_ptradd(ptr, bx.const_usize(offset_from_start.bytes())); @@ -2256,9 +2256,8 @@ pub fn store_cast<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>( align: Align, ) { if let Some(offset_from_start) = cast.rest_offset { - assert!(cast.prefix[1..].iter().all(|p| p.is_none())); + assert_eq!(cast.prefix.len(), 1); assert_eq!(cast.rest.unit.size, cast.rest.total); - assert!(cast.prefix[0].is_some()); let first = bx.extract_value(value, 0); let second = bx.extract_value(value, 1); bx.store(first, ptr, align); diff --git a/compiler/rustc_codegen_ssa/src/mir/naked_asm.rs b/compiler/rustc_codegen_ssa/src/mir/naked_asm.rs index bdefacefd20b9..32a74d7b70587 100644 --- a/compiler/rustc_codegen_ssa/src/mir/naked_asm.rs +++ b/compiler/rustc_codegen_ssa/src/mir/naked_asm.rs @@ -451,7 +451,7 @@ fn wasm_type<'tcx>(signature: &mut String, arg_abi: &ArgAbi<'_, Ty<'tcx>>, ptr_t PassMode::Cast { pad_i32, ref cast } => { // For wasm, Cast is used for single-field primitive wrappers like `struct Wrapper(i64);` assert!(!pad_i32, "not currently used by wasm calling convention"); - assert!(cast.prefix[0].is_none(), "no prefix"); + assert!(cast.prefix.is_empty(), "no prefix"); assert_eq!(cast.rest.total, arg_abi.layout.size, "single item"); let wrapped_wasm_type = match cast.rest.unit.kind { diff --git a/compiler/rustc_monomorphize/src/mono_checks/abi_check.rs b/compiler/rustc_monomorphize/src/mono_checks/abi_check.rs index 10218523ca232..812979d13b26f 100644 --- a/compiler/rustc_monomorphize/src/mono_checks/abi_check.rs +++ b/compiler/rustc_monomorphize/src/mono_checks/abi_check.rs @@ -25,10 +25,7 @@ fn passes_vectors_by_value(mode: &PassMode, repr: &BackendRepr) -> UsesVectorReg match mode { PassMode::Ignore | PassMode::Indirect { .. } => UsesVectorRegisters::No, PassMode::Cast { pad_i32: _, cast } - if cast - .prefix - .iter() - .any(|r| r.is_some_and(|x| matches!(x.kind, RegKind::Vector { .. }))) + if cast.prefix.iter().any(|x| matches!(x.kind, RegKind::Vector { .. })) || matches!(cast.rest.unit.kind, RegKind::Vector { .. }) => { UsesVectorRegisters::FixedVector diff --git a/compiler/rustc_target/Cargo.toml b/compiler/rustc_target/Cargo.toml index ecdb6ab5a576c..d399bfcda50b5 100644 --- a/compiler/rustc_target/Cargo.toml +++ b/compiler/rustc_target/Cargo.toml @@ -5,6 +5,7 @@ edition = "2024" [dependencies] # tidy-alphabetical-start +arrayvec = { version = "0.7", default-features = false } bitflags = "2.4.1" object = { version = "0.37.0", default-features = false, features = ["elf", "macho"] } rustc_abi = { path = "../rustc_abi" } diff --git a/compiler/rustc_target/src/callconv/mips64.rs b/compiler/rustc_target/src/callconv/mips64.rs index 4fafa7b3a5e2c..f17e59c12e57e 100644 --- a/compiler/rustc_target/src/callconv/mips64.rs +++ b/compiler/rustc_target/src/callconv/mips64.rs @@ -1,3 +1,4 @@ +use arrayvec::ArrayVec; use rustc_abi::{ BackendRepr, FieldsShape, Float, HasDataLayout, Primitive, Reg, Size, TyAbiInterface, }; @@ -81,8 +82,7 @@ where { let dl = cx.data_layout(); let size = arg.layout.size; - let mut prefix = [None; 8]; - let mut prefix_index = 0; + let mut prefix = ArrayVec::new(); // Detect need for padding let align = Ord::clamp(arg.layout.align.abi, dl.i64_align, dl.i128_align); @@ -107,7 +107,7 @@ where // doubles not part of another aggregate are passed as floats. let mut last_offset = Size::ZERO; - for i in 0..arg.layout.fields.count() { + 'outer: for i in 0..arg.layout.fields.count() { let field = arg.layout.field(cx, i); let offset = arg.layout.fields.offset(i); @@ -117,19 +117,15 @@ where if offset.is_aligned(dl.f64_align) { // Insert enough integers to cover [last_offset, offset) assert!(last_offset.is_aligned(dl.f64_align)); - for _ in 0..((offset - last_offset).bits() / 64) - .min((prefix.len() - prefix_index) as u64) - { - prefix[prefix_index] = Some(Reg::i64()); - prefix_index += 1; + for _ in 0..((offset - last_offset).bits() / 64) { + if prefix.try_push(Reg::i64()).is_err() { + break 'outer; + } } - if prefix_index == prefix.len() { + if prefix.try_push(Reg::f64()).is_err() { break; } - - prefix[prefix_index] = Some(Reg::f64()); - prefix_index += 1; last_offset = offset + Reg::f64().size; } } @@ -139,7 +135,7 @@ where }; // Extract first 8 chunks as the prefix - let rest_size = size - Size::from_bytes(8) * prefix_index as u64; + let rest_size = size - Size::from_bytes(8) * prefix.len() as u64; arg.cast_to_and_pad_i32( CastTarget::prefixed(prefix, Uniform::new(Reg::i64(), rest_size)), pad_i32, diff --git a/compiler/rustc_target/src/callconv/mod.rs b/compiler/rustc_target/src/callconv/mod.rs index e6eb68c1fe10d..3013d41eacaae 100644 --- a/compiler/rustc_target/src/callconv/mod.rs +++ b/compiler/rustc_target/src/callconv/mod.rs @@ -1,5 +1,6 @@ use std::{fmt, iter}; +use arrayvec::ArrayVec; use rustc_abi::{ AddressSpace, Align, BackendRepr, CanonAbi, ExternAbi, FieldsShape, HasDataLayout, Primitive, Reg, RegKind, Scalar, Size, TyAbiInterface, TyAndLayout, Variants, @@ -264,7 +265,11 @@ impl Uniform { /// (and all data in the padding between the registers is dropped). #[derive(Clone, PartialEq, Eq, Hash, Debug, StableHash)] pub struct CastTarget { - pub prefix: [Option; 8], + // Note that this is fixed to 8 elements for now as ABIs currently don't + // need anything further beyond that, and when this code was originally + // refactored to use `ArrayVec` it was already using 8, so that stuck + // around. + pub prefix: ArrayVec, /// The offset of `rest` from the start of the value. Currently only implemented for a `Reg` /// pair created by the `offset_pair` method. pub rest_offset: Option, @@ -280,18 +285,20 @@ impl From for CastTarget { impl From for CastTarget { fn from(uniform: Uniform) -> CastTarget { - Self::prefixed([None; 8], uniform) + Self::prefixed(Default::default(), uniform) } } impl CastTarget { - pub fn prefixed(prefix: [Option; 8], rest: Uniform) -> Self { + pub fn prefixed(prefix: ArrayVec, rest: Uniform) -> Self { Self { prefix, rest_offset: None, rest, attrs: ArgAttributes::new() } } pub fn offset_pair(a: Reg, offset_from_start: Size, b: Reg) -> Self { + let mut prefix = ArrayVec::new(); + prefix.push(a); Self { - prefix: [Some(a), None, None, None, None, None, None, None], + prefix, rest_offset: Some(offset_from_start), rest: b.into(), attrs: ArgAttributes::new(), @@ -304,7 +311,9 @@ impl CastTarget { } pub fn pair(a: Reg, b: Reg) -> CastTarget { - Self::prefixed([Some(a), None, None, None, None, None, None, None], Uniform::from(b)) + let mut prefix = ArrayVec::new(); + prefix.push(a); + Self::prefixed(prefix, Uniform::from(b)) } /// When you only access the range containing valid data, you can use this unaligned size; @@ -314,10 +323,7 @@ impl CastTarget { let prefix_size = if let Some(offset_from_start) = self.rest_offset { offset_from_start } else { - self.prefix - .iter() - .filter_map(|x| x.map(|reg| reg.size)) - .fold(Size::ZERO, |acc, size| acc + size) + self.prefix.iter().map(|reg| reg.size).fold(Size::ZERO, |acc, size| acc + size) }; // Remaining arguments are passed in chunks of the unit size let rest_size = @@ -333,7 +339,7 @@ impl CastTarget { pub fn align(&self, cx: &C) -> Align { self.prefix .iter() - .filter_map(|x| x.map(|reg| reg.align(cx))) + .map(|reg| reg.align(cx)) .fold(cx.data_layout().aggregate_align.max(self.rest.align(cx)), |acc, align| { acc.max(align) }) diff --git a/compiler/rustc_target/src/callconv/nvptx64.rs b/compiler/rustc_target/src/callconv/nvptx64.rs index a19b89c0132f8..3919b7a69a78b 100644 --- a/compiler/rustc_target/src/callconv/nvptx64.rs +++ b/compiler/rustc_target/src/callconv/nvptx64.rs @@ -1,3 +1,4 @@ +use arrayvec::ArrayVec; use rustc_abi::{HasDataLayout, Reg, Size, TyAbiInterface}; use super::CastTarget; @@ -41,10 +42,9 @@ fn classify_aggregate(arg: &mut ArgAbi<'_, Ty>) { }; if align_bytes == size.bytes() { - arg.cast_to(CastTarget::prefixed( - [Some(reg), None, None, None, None, None, None, None], - Uniform::new(Reg::i8(), Size::ZERO), - )); + let mut prefix = ArrayVec::new(); + prefix.push(reg); + arg.cast_to(CastTarget::prefixed(prefix, Uniform::new(Reg::i8(), Size::ZERO))); } else { arg.cast_to(Uniform::new(reg, size)); } @@ -79,10 +79,9 @@ where }; if arg.layout.size.bytes() / align_bytes == 1 { // Make sure we pass the struct as array at the LLVM IR level and not as a single integer. - arg.cast_to(CastTarget::prefixed( - [Some(unit), None, None, None, None, None, None, None], - Uniform::new(unit, Size::ZERO), - )); + let mut prefix = ArrayVec::new(); + prefix.push(unit); + arg.cast_to(CastTarget::prefixed(prefix, Uniform::new(unit, Size::ZERO))); } else { arg.cast_to(Uniform::new(unit, arg.layout.size)); } diff --git a/compiler/rustc_target/src/callconv/sparc64.rs b/compiler/rustc_target/src/callconv/sparc64.rs index 55f264d89bb4d..b5e5c3e2a88b3 100644 --- a/compiler/rustc_target/src/callconv/sparc64.rs +++ b/compiler/rustc_target/src/callconv/sparc64.rs @@ -1,3 +1,4 @@ +use arrayvec::ArrayVec; use rustc_abi::{ Align, BackendRepr, FieldsShape, Float, HasDataLayout, Primitive, Reg, Size, TyAbiInterface, TyAndLayout, Variants, @@ -147,12 +148,7 @@ fn classify_arg<'a, Ty, C>( let mut double_words = [DoubleWord::Words([Word::Integer; 2]); ARGUMENT_REGISTERS / 2]; classify(cx, &arg.layout, Size::ZERO, &mut double_words); - let mut regs = [None; ARGUMENT_REGISTERS]; - let mut i = 0; - let mut push = |reg| { - regs[i] = Some(reg); - i += 1; - }; + let mut regs = ArrayVec::new(); let mut attrs = ArgAttribute::empty(); for (index, double_word) in double_words.into_iter().enumerate() { @@ -162,7 +158,7 @@ fn classify_arg<'a, Ty, C>( match double_word { // `f128` must be aligned to be assigned a float register. DoubleWord::F128Start if (start_double_word_count + index).is_multiple_of(2) => { - push(Reg::f128()); + regs.push(Reg::f128()); } DoubleWord::F128Start => { // Clang currently handles this case nonsensically, always returning a packed @@ -170,30 +166,27 @@ fn classify_arg<'a, Ty, C>( // the `long double` isn't aligned on the stack, which also makes all future // arguments get passed in the wrong registers. This passes the `f128` in integer // registers when it is unaligned, same as with `f32` and `f64`. - push(Reg::i64()); - push(Reg::i64()); + regs.push(Reg::i64()); + regs.push(Reg::i64()); } DoubleWord::F128End => {} // Already handled by `F128Start` - DoubleWord::F64 => push(Reg::f64()), - DoubleWord::Words([Word::Integer, Word::Integer]) => push(Reg::i64()), + DoubleWord::F64 => regs.push(Reg::f64()), + DoubleWord::Words([Word::Integer, Word::Integer]) => regs.push(Reg::i64()), DoubleWord::Words(words) => { attrs |= ArgAttribute::InReg; for word in words { match word { - Word::F32 => push(Reg::f32()), - Word::Integer => push(Reg::i32()), + Word::F32 => regs.push(Reg::f32()), + Word::Integer => regs.push(Reg::i32()), } } } } } - let cast_target = match regs { - [Some(reg), None, rest @ ..] => { - // Just a single register is needed for this value. - debug_assert!(rest.iter().all(|x| x.is_none())); - CastTarget::from(reg) - } + let cast_target = match regs.as_slice() { + // Just a single register is needed for this value. + [reg] => CastTarget::from(*reg), _ => CastTarget::prefixed(regs, Uniform::new(Reg::i8(), Size::ZERO)), }; diff --git a/tests/ui/abi/pass-indirectly-attr.stderr b/tests/ui/abi/pass-indirectly-attr.stderr index d8cc39cb2e4e3..226d880d7945d 100644 --- a/tests/ui/abi/pass-indirectly-attr.stderr +++ b/tests/ui/abi/pass-indirectly-attr.stderr @@ -123,16 +123,7 @@ error: fn_abi_of(extern_rust) = FnAbi { mode: Cast { pad_i32: false, cast: CastTarget { - prefix: [ - None, - None, - None, - None, - None, - None, - None, - None, - ], + prefix: [], rest_offset: None, rest: Uniform { unit: Reg { From 00d4895da1fede4ef7f8ce1822ae68c9dffe8d0f Mon Sep 17 00:00:00 2001 From: rustbot <47979223+rustbot@users.noreply.github.com> Date: Mon, 1 Jun 2026 19:01:09 +0200 Subject: [PATCH 43/46] Update books --- src/doc/reference | 2 +- src/doc/rust-by-example | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/doc/reference b/src/doc/reference index ad35aca481751..01b0ee707f457 160000 --- a/src/doc/reference +++ b/src/doc/reference @@ -1 +1 @@ -Subproject commit ad35aca481751a06afeb23820a672b0f3b11a476 +Subproject commit 01b0ee707f4571e803c8b2c471d8335a448f5d60 diff --git a/src/doc/rust-by-example b/src/doc/rust-by-example index 898f0ac147922..d3117f6c873ac 160000 --- a/src/doc/rust-by-example +++ b/src/doc/rust-by-example @@ -1 +1 @@ -Subproject commit 898f0ac1479223d332309e0fce88d44b39927d28 +Subproject commit d3117f6c873acbbf331c1d510371d061dfcc975c From 47acd7a71919e252b6e92f004ca4d2c962fa2e6e Mon Sep 17 00:00:00 2001 From: Jonathan Brouwer Date: Sun, 31 May 2026 22:33:55 +0200 Subject: [PATCH 44/46] Remove help message for stability attributes --- compiler/rustc_attr_parsing/src/stability.rs | 7 +++++++ tests/ui/attributes/unstable_removed.stderr | 3 --- .../feature-gate-rustc_const_unstable.stderr | 3 --- .../feature-gate-staged_api.stderr | 6 ------ .../issue-43106-gating-of-stable.stderr | 21 ------------------- .../issue-43106-gating-of-unstable.stderr | 21 ------------------- .../stability-attribute/issue-106589.stderr | 6 ------ ...attribute-non-staged-force-unstable.stderr | 6 ------ .../stability-attribute-non-staged.stderr | 6 ------ .../unstable_feature_bound_staged_api.stderr | 3 --- 10 files changed, 7 insertions(+), 75 deletions(-) diff --git a/compiler/rustc_attr_parsing/src/stability.rs b/compiler/rustc_attr_parsing/src/stability.rs index f948cd29f8f20..7d5843e1c950f 100644 --- a/compiler/rustc_attr_parsing/src/stability.rs +++ b/compiler/rustc_attr_parsing/src/stability.rs @@ -58,6 +58,13 @@ impl<'sess> AttributeParser<'sess> { let mut diag = feature_err(self.sess, gate_name, attr_span, explain); + // Remove the suggestion for `#![feature(staged_api)]` as these attributes are currently + // not usable outside std. If we do ever expose `#[stable]` etc under a different feature + // name then it would be unfortunate to have nightlies out there suggesting `staged_api`. + if gate_name == sym::staged_api { + diag.children.clear(); + } + for note in default_notes { diag.note(note.clone()); } diff --git a/tests/ui/attributes/unstable_removed.stderr b/tests/ui/attributes/unstable_removed.stderr index 69a67188f3793..588122b9775d8 100644 --- a/tests/ui/attributes/unstable_removed.stderr +++ b/tests/ui/attributes/unstable_removed.stderr @@ -9,9 +9,6 @@ LL | | link = "https://github.com/rust-lang/rust/issues/141617", LL | | since="1.92.0" LL | | )] | |__^ - | - = help: add `#![feature(staged_api)]` to the crate attributes to enable - = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date error[E0557]: feature `old_feature` has been removed --> $DIR/unstable_removed.rs:3:12 diff --git a/tests/ui/feature-gates/feature-gate-rustc_const_unstable.stderr b/tests/ui/feature-gates/feature-gate-rustc_const_unstable.stderr index 2c3763af9db72..690e162abe686 100644 --- a/tests/ui/feature-gates/feature-gate-rustc_const_unstable.stderr +++ b/tests/ui/feature-gates/feature-gate-rustc_const_unstable.stderr @@ -3,9 +3,6 @@ error[E0658]: stability attributes may not be used outside of the standard libra | LL | #[rustc_const_unstable(feature="fzzzzzt")] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | - = help: add `#![feature(staged_api)]` to the crate attributes to enable - = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date error[E0547]: missing 'issue' --> $DIR/feature-gate-rustc_const_unstable.rs:3:1 diff --git a/tests/ui/feature-gates/feature-gate-staged_api.stderr b/tests/ui/feature-gates/feature-gate-staged_api.stderr index 6e717db165cdc..c316e4c3b25de 100644 --- a/tests/ui/feature-gates/feature-gate-staged_api.stderr +++ b/tests/ui/feature-gates/feature-gate-staged_api.stderr @@ -3,18 +3,12 @@ error[E0658]: stability attributes may not be used outside of the standard libra | LL | #![stable(feature = "a", since = "3.3.3")] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | - = help: add `#![feature(staged_api)]` to the crate attributes to enable - = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date error[E0658]: stability attributes may not be used outside of the standard library --> $DIR/feature-gate-staged_api.rs:8:1 | LL | #[stable(feature = "a", since = "3.3.3")] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | - = help: add `#![feature(staged_api)]` to the crate attributes to enable - = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date error: aborting due to 2 previous errors diff --git a/tests/ui/feature-gates/issue-43106-gating-of-stable.stderr b/tests/ui/feature-gates/issue-43106-gating-of-stable.stderr index 6f9b6ab18c9f4..509b9f6c3b0ba 100644 --- a/tests/ui/feature-gates/issue-43106-gating-of-stable.stderr +++ b/tests/ui/feature-gates/issue-43106-gating-of-stable.stderr @@ -3,9 +3,6 @@ error[E0658]: stability attributes may not be used outside of the standard libra | LL | #![stable()] | ^^^^^^^^^^^^ - | - = help: add `#![feature(staged_api)]` to the crate attributes to enable - = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date error[E0546]: missing 'feature' --> $DIR/issue-43106-gating-of-stable.rs:7:1 @@ -24,9 +21,6 @@ error[E0658]: stability attributes may not be used outside of the standard libra | LL | #[stable()] | ^^^^^^^^^^^ - | - = help: add `#![feature(staged_api)]` to the crate attributes to enable - = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date error[E0546]: missing 'feature' --> $DIR/issue-43106-gating-of-stable.rs:12:1 @@ -45,9 +39,6 @@ error[E0658]: stability attributes may not be used outside of the standard libra | LL | #![stable()] | ^^^^^^^^^^^^ - | - = help: add `#![feature(staged_api)]` to the crate attributes to enable - = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date error[E0546]: missing 'feature' --> $DIR/issue-43106-gating-of-stable.rs:18:9 @@ -66,9 +57,6 @@ error[E0658]: stability attributes may not be used outside of the standard libra | LL | #[stable()] | ^^^^^^^^^^^ - | - = help: add `#![feature(staged_api)]` to the crate attributes to enable - = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date error[E0546]: missing 'feature' --> $DIR/issue-43106-gating-of-stable.rs:24:5 @@ -87,9 +75,6 @@ error[E0658]: stability attributes may not be used outside of the standard libra | LL | #[stable()] | ^^^^^^^^^^^ - | - = help: add `#![feature(staged_api)]` to the crate attributes to enable - = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date error[E0546]: missing 'feature' --> $DIR/issue-43106-gating-of-stable.rs:30:5 @@ -108,9 +93,6 @@ error[E0658]: stability attributes may not be used outside of the standard libra | LL | #[stable()] | ^^^^^^^^^^^ - | - = help: add `#![feature(staged_api)]` to the crate attributes to enable - = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date error[E0546]: missing 'feature' --> $DIR/issue-43106-gating-of-stable.rs:36:5 @@ -129,9 +111,6 @@ error[E0658]: stability attributes may not be used outside of the standard libra | LL | #[stable()] | ^^^^^^^^^^^ - | - = help: add `#![feature(staged_api)]` to the crate attributes to enable - = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date error[E0546]: missing 'feature' --> $DIR/issue-43106-gating-of-stable.rs:42:5 diff --git a/tests/ui/feature-gates/issue-43106-gating-of-unstable.stderr b/tests/ui/feature-gates/issue-43106-gating-of-unstable.stderr index f024f1e300117..b1774c933560e 100644 --- a/tests/ui/feature-gates/issue-43106-gating-of-unstable.stderr +++ b/tests/ui/feature-gates/issue-43106-gating-of-unstable.stderr @@ -3,9 +3,6 @@ error[E0658]: stability attributes may not be used outside of the standard libra | LL | #![unstable()] | ^^^^^^^^^^^^^^ - | - = help: add `#![feature(staged_api)]` to the crate attributes to enable - = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date error[E0546]: missing 'feature' --> $DIR/issue-43106-gating-of-unstable.rs:7:1 @@ -24,9 +21,6 @@ error[E0658]: stability attributes may not be used outside of the standard libra | LL | #[unstable()] | ^^^^^^^^^^^^^ - | - = help: add `#![feature(staged_api)]` to the crate attributes to enable - = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date error[E0546]: missing 'feature' --> $DIR/issue-43106-gating-of-unstable.rs:12:1 @@ -45,9 +39,6 @@ error[E0658]: stability attributes may not be used outside of the standard libra | LL | #![unstable()] | ^^^^^^^^^^^^^^ - | - = help: add `#![feature(staged_api)]` to the crate attributes to enable - = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date error[E0546]: missing 'feature' --> $DIR/issue-43106-gating-of-unstable.rs:18:9 @@ -66,9 +57,6 @@ error[E0658]: stability attributes may not be used outside of the standard libra | LL | #[unstable()] | ^^^^^^^^^^^^^ - | - = help: add `#![feature(staged_api)]` to the crate attributes to enable - = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date error[E0546]: missing 'feature' --> $DIR/issue-43106-gating-of-unstable.rs:24:5 @@ -87,9 +75,6 @@ error[E0658]: stability attributes may not be used outside of the standard libra | LL | #[unstable()] | ^^^^^^^^^^^^^ - | - = help: add `#![feature(staged_api)]` to the crate attributes to enable - = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date error[E0546]: missing 'feature' --> $DIR/issue-43106-gating-of-unstable.rs:30:5 @@ -108,9 +93,6 @@ error[E0658]: stability attributes may not be used outside of the standard libra | LL | #[unstable()] | ^^^^^^^^^^^^^ - | - = help: add `#![feature(staged_api)]` to the crate attributes to enable - = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date error[E0546]: missing 'feature' --> $DIR/issue-43106-gating-of-unstable.rs:36:5 @@ -129,9 +111,6 @@ error[E0658]: stability attributes may not be used outside of the standard libra | LL | #[unstable()] | ^^^^^^^^^^^^^ - | - = help: add `#![feature(staged_api)]` to the crate attributes to enable - = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date error[E0546]: missing 'feature' --> $DIR/issue-43106-gating-of-unstable.rs:42:5 diff --git a/tests/ui/stability-attribute/issue-106589.stderr b/tests/ui/stability-attribute/issue-106589.stderr index 40b0dcec651e1..e865365ed1f86 100644 --- a/tests/ui/stability-attribute/issue-106589.stderr +++ b/tests/ui/stability-attribute/issue-106589.stderr @@ -3,18 +3,12 @@ error[E0658]: stability attributes may not be used outside of the standard libra | LL | #![stable(feature = "foo", since = "1.0.0")] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | - = help: add `#![feature(staged_api)]` to the crate attributes to enable - = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date error[E0658]: stability attributes may not be used outside of the standard library --> $DIR/issue-106589.rs:6:1 | LL | #[unstable(feature = "foo", issue = "none")] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | - = help: add `#![feature(staged_api)]` to the crate attributes to enable - = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date error: aborting due to 2 previous errors diff --git a/tests/ui/stability-attribute/stability-attribute-non-staged-force-unstable.stderr b/tests/ui/stability-attribute/stability-attribute-non-staged-force-unstable.stderr index eac32a6cec2cc..a72656d7798c2 100644 --- a/tests/ui/stability-attribute/stability-attribute-non-staged-force-unstable.stderr +++ b/tests/ui/stability-attribute/stability-attribute-non-staged-force-unstable.stderr @@ -3,9 +3,6 @@ error[E0658]: stability attributes may not be used outside of the standard libra | LL | #[unstable()] | ^^^^^^^^^^^^^ - | - = help: add `#![feature(staged_api)]` to the crate attributes to enable - = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date error[E0546]: missing 'feature' --> $DIR/stability-attribute-non-staged-force-unstable.rs:3:1 @@ -24,9 +21,6 @@ error[E0658]: stability attributes may not be used outside of the standard libra | LL | #[stable()] | ^^^^^^^^^^^ - | - = help: add `#![feature(staged_api)]` to the crate attributes to enable - = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date error[E0546]: missing 'feature' --> $DIR/stability-attribute-non-staged-force-unstable.rs:7:1 diff --git a/tests/ui/stability-attribute/stability-attribute-non-staged.stderr b/tests/ui/stability-attribute/stability-attribute-non-staged.stderr index 2443e0300a3c5..f69faebbbf83c 100644 --- a/tests/ui/stability-attribute/stability-attribute-non-staged.stderr +++ b/tests/ui/stability-attribute/stability-attribute-non-staged.stderr @@ -3,9 +3,6 @@ error[E0658]: stability attributes may not be used outside of the standard libra | LL | #[unstable()] | ^^^^^^^^^^^^^ - | - = help: add `#![feature(staged_api)]` to the crate attributes to enable - = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date error[E0546]: missing 'feature' --> $DIR/stability-attribute-non-staged.rs:1:1 @@ -24,9 +21,6 @@ error[E0658]: stability attributes may not be used outside of the standard libra | LL | #[stable()] | ^^^^^^^^^^^ - | - = help: add `#![feature(staged_api)]` to the crate attributes to enable - = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date error[E0546]: missing 'feature' --> $DIR/stability-attribute-non-staged.rs:5:1 diff --git a/tests/ui/unstable-feature-bound/unstable_feature_bound_staged_api.stderr b/tests/ui/unstable-feature-bound/unstable_feature_bound_staged_api.stderr index 4f6ff5117a05e..521be360f243f 100644 --- a/tests/ui/unstable-feature-bound/unstable_feature_bound_staged_api.stderr +++ b/tests/ui/unstable-feature-bound/unstable_feature_bound_staged_api.stderr @@ -3,9 +3,6 @@ error[E0658]: stability attributes may not be used outside of the standard libra | LL | #[unstable_feature_bound(feat_bar)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | - = help: add `#![feature(staged_api)]` to the crate attributes to enable - = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date error: aborting due to 1 previous error From 6e2dc81c665e32fdd22575564c1806f650a4907a Mon Sep 17 00:00:00 2001 From: Adam Harvey Date: Mon, 1 Jun 2026 14:27:20 -0700 Subject: [PATCH 45/46] triagebot.toml: add LawnGnome to libs reviewers --- triagebot.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/triagebot.toml b/triagebot.toml index 8c9702c407ea1..03292058b9ba6 100644 --- a/triagebot.toml +++ b/triagebot.toml @@ -1487,6 +1487,7 @@ libs = [ "@thomcc", "@joboet", "@nia-e", + "@LawnGnome", ] infra-ci = [ "@Mark-Simulacrum", From 832388aae7d459beb682898bd44f99f61ba55fae Mon Sep 17 00:00:00 2001 From: sgasho Date: Mon, 1 Jun 2026 18:27:20 +0900 Subject: [PATCH 46/46] Enable Enzyme for aarch64-apple-darwin --- src/ci/github-actions/jobs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ci/github-actions/jobs.yml b/src/ci/github-actions/jobs.yml index a410972c5cab1..6b6d560904a16 100644 --- a/src/ci/github-actions/jobs.yml +++ b/src/ci/github-actions/jobs.yml @@ -517,7 +517,7 @@ auto: - name: dist-aarch64-apple env: SCRIPT: >- - ./x.py dist bootstrap + ./x.py dist bootstrap enzyme --include-default-paths --host=aarch64-apple-darwin --target=aarch64-apple-darwin