Question: <del> // Copyright 2012 The Rust Project Developers. See the COPYRIGHT // file at the top-level directory of this distribution and at // http://rust-lang.org/COPYRIGHT. // // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your // option. This file may not be copied, modified, or distributed // except according to those terms. /*! * High-level text containers. * * Ropes are a high-level representation of text that offers * much better performance than strings for common operations, * and generally reduce memory allocations and copies, while only * entailing a small degradation of less common operations. * * More precisely, where a string is represented as a memory buffer, * a rope is a tree structure whose leaves are slices of immutable * strings. Therefore, concatenation, appending, prepending, substrings, * etc. are operations that require only trivial tree manipulation, * generally without having to copy memory. In addition, the tree * structure of ropes makes them suitable as a form of index to speed-up * access to Unicode characters by index in long chunks of text. * * The following operations are algorithmically faster in ropes: * * * extracting a subrope is logarithmic (linear in strings); * * appending/prepending is near-constant time (linear in strings); * * concatenation is near-constant time (linear in strings); * * char length is constant-time (linear in strings); * * access to a character by index is logarithmic (linear in strings); */ #[allow(missing_doc)]; use std::uint; use std::vec; use std::str; /// The type of ropes. pub type Rope = node::Root; /* Section: Creating a rope */ /// Create an empty rope pub fn empty() -> Rope { return node::Empty; } /** * Adopt a string as a rope. * * # Arguments * * * str - A valid string. * * # Return value * * A rope representing the same string as `str`. Depending of the length * of `str`, this rope may be empty, flat or complex. * * # Performance notes * * * this operation does not copy the string; * * the function runs in linear time. */ pub fn of_str(str: @~str) -> Rope { return of_substr(str, 0u, str.len()); } /** * As `of_str` but for a substring. * * # Arguments * * byte_offset - The offset of `str` at which the rope starts. * * byte_len - The number of bytes of `str` to use. * * # Return value * * A rope representing the same string as `str.slice(byte_offset, * byte_offset + byte_len)`.  Depending on `byte_len`, this rope may * be empty, flat or complex. * * # Performance note * * This operation does not copy the substring. * * # Safety notes * * * this function does _not_ check the validity of the substring; * * this function fails if `byte_offset` or `byte_len` do not match `str`. */ pub fn of_substr(str: @~str, byte_offset: uint, byte_len: uint) -> Rope { if byte_len == 0u { return node::Empty; } if byte_offset + byte_len  > str.len() { fail!(); } return node::Content(node::of_substr(str, byte_offset, byte_len)); } /* Section: Adding things to a rope */ /** * Add one char to the end of the rope * * # Performance note * * * this function executes in near-constant time */ pub fn append_char(rope: Rope, char: char) -> Rope { return append_str(rope, @str::from_chars([char])); } /** * Add one string to the end of the rope * * # Performance note * * * this function executes in near-linear time */ pub fn append_str(rope: Rope, str: @~str) -> Rope { return append_rope(rope, of_str(str)) } /** * Add one char to the beginning of the rope * * # Performance note * * this function executes in near-constant time */ pub fn prepend_char(rope: Rope, char: char) -> Rope { return prepend_str(rope, @str::from_chars([char])); } /** * Add one string to the beginning of the rope * * # Performance note * * this function executes in near-linear time */ pub fn prepend_str(rope: Rope, str: @~str) -> Rope { return append_rope(of_str(str), rope) } /// Concatenate two ropes pub fn append_rope(left: Rope, right: Rope) -> Rope { match (left) { node::Empty => return right, node::Content(left_content) => { match (right) { node::Empty => return left, node::Content(right_content) => { return node::Content(node::concat2(left_content, right_content)); } } } } } /** * Concatenate many ropes. * * If the ropes are balanced initially and have the same height, the resulting * rope remains balanced. However, this function does not take any further * measure to ensure that the result is balanced. */ pub fn concat(v: ~[Rope]) -> Rope { //Copy `v` into a mut vector let mut len = v.len(); if len == 0u { return node::Empty; } let mut ropes = vec::from_elem(len, v[0]); for uint::range(1u, len) |i| { ropes[i] = v[i]; } //Merge progresively while len > 1u { for uint::range(0u, len/2u) |i| { ropes[i] = append_rope(ropes[2u*i], ropes[2u*i+1u]); } if len%2u != 0u { ropes[len/2u] = ropes[len - 1u]; len = len/2u + 1u; } else { len = len/2u; } } //Return final rope return ropes[0]; } /* Section: Keeping ropes healthy */ /** * Balance a rope. * * # Return value * * A copy of the rope in which small nodes have been grouped in memory, * and with a reduced height. * * If you perform numerous rope concatenations, it is generally a good idea * to rebalance your rope at some point, before using it for other purposes. */ pub fn bal(rope:Rope) -> Rope { match (rope) { node::Empty => return rope, node::Content(x) => match (node::bal(x)) { None    => rope, Some(y) => node::Content(y) } } } /* Section: Transforming ropes */ /** * Extract a subrope from a rope. * * # Performance note * * * on a balanced rope, this operation takes algorithmic time; * * this operation does not involve any copying * * # Safety note * * * this function fails if char_offset/char_len do not represent *   valid positions in rope */ pub fn sub_chars(rope: Rope, char_offset: uint, char_len: uint) -> Rope { if char_len == 0u { return node::Empty; } match (rope) { node::Empty => fail!(), node::Content(node) => if char_len > node::char_len(node) { fail!() } else { return node::Content(node::sub_chars(node, char_offset, char_len)) } } } /** * Extract a subrope from a rope. * * # Performance note * * * on a balanced rope, this operation takes algorithmic time; * * this operation does not involve any copying * * # Safety note * * * this function fails if byte_offset/byte_len do not represent *   valid positions in rope */ pub fn sub_bytes(rope: Rope, byte_offset: uint, byte_len: uint) -> Rope { if byte_len == 0u { return node::Empty; } match (rope) { node::Empty => fail!(), node::Content(node) =>if byte_len > node::byte_len(node) { fail!() } else { return node::Content(node::sub_bytes(node, byte_offset, byte_len)) } } } /* Section: Comparing ropes */ /** * Compare two ropes by Unicode lexicographical order. * * This function compares only the contents of the rope, not their structure. * * # Return value * * A negative value if `left < right`, 0 if eq(left, right) or a positive * value if `left > right` */ pub fn cmp(left: Rope, right: Rope) -> int { match ((left, right)) { (node::Empty, node::Empty) => return 0, (node::Empty, _)     => return -1, (_, node::Empty)     => return  1, (node::Content(a), node::Content(b)) => { return node::cmp(a, b); } } } /** * Returns `true` if both ropes have the same content (regardless of * their structure), `false` otherwise */ pub fn eq(left: Rope, right: Rope) -> bool { return cmp(left, right) == 0; } /** * # Arguments * * * left - an arbitrary rope * * right - an arbitrary rope * * # Return value * * `true` if `left <= right` in lexicographical order (regardless of their * structure), `false` otherwise */ pub fn le(left: Rope, right: Rope) -> bool { return cmp(left, right) <= 0; } /** * # Arguments * * * left - an arbitrary rope * * right - an arbitrary rope * * # Return value * * `true` if `left < right` in lexicographical order (regardless of their * structure), `false` otherwise */ pub fn lt(left: Rope, right: Rope) -> bool { return cmp(left, right) < 0; } /** * # Arguments * * * left - an arbitrary rope * * right - an arbitrary rope * * # Return value * *  `true` if `left >= right` in lexicographical order (regardless of their * structure), `false` otherwise */ pub fn ge(left: Rope, right: Rope) -> bool { return cmp(left, right) >= 0; } /** * # Arguments * * * left - an arbitrary rope * * right - an arbitrary rope * * # Return value * * `true` if `left > right` in lexicographical order (regardless of their * structure), `false` otherwise */ pub fn gt(left: Rope, right: Rope) -> bool { return cmp(left, right) > 0; } /* Section: Iterating */ /** * Loop through a rope, char by char * * While other mechanisms are available, this is generally the best manner * of looping through the contents of a rope char by char. If you prefer a * loop that iterates through the contents string by string (e.g. to print * the contents of the rope or output it to the system), however, * you should rather use `traverse_components`. * * # Arguments * * * rope - A rope to traverse. It may be empty. * * it - A block to execute with each consecutive character of the rope. *        Return `true` to continue, `false` to stop. * * # Return value * * `true` If execution proceeded correctly, `false` if it was interrupted, * that is if `it` returned `false` at any point. */ pub fn loop_chars(rope: Rope, it: &fn(c: char) -> bool) -> bool { match (rope) { node::Empty => return true, node::Content(x) => return node::loop_chars(x, it) } } /** * Loop through a rope, char by char, until the end. * * # Arguments * * rope - A rope to traverse. It may be empty * * it - A block to execute with each consecutive character of the rope. */ pub fn iter_chars(rope: Rope, it: &fn(char)) { do loop_chars(rope) |x| { it(x); true }; } /** * Loop through a rope, string by string * * While other mechanisms are available, this is generally the best manner of * looping through the contents of a rope string by string, which may be * useful e.g. to print strings as you see them (without having to copy their * contents into a new string), to send them to then network, to write them to * a file, etc.. If you prefer a loop that iterates through the contents * char by char (e.g. to search for a char), however, you should rather * use `traverse`. * * # Arguments * * * rope - A rope to traverse. It may be empty * * it - A block to execute with each consecutive string component of the *        rope. Return `true` to continue, `false` to stop * * # Return value * * `true` If execution proceeded correctly, `false` if it was interrupted, * that is if `it` returned `false` at any point. */ pub fn loop_leaves(rope: Rope, it: &fn(node::Leaf) -> bool) -> bool{ match (rope) { node::Empty => return true, node::Content(x) => return node::loop_leaves(x, it) } } pub mod iterator { pub mod leaf { use rope::{Rope, node}; pub fn start(rope: Rope) -> node::leaf_iterator::T { match (rope) { node::Empty      => return node::leaf_iterator::empty(), node::Content(x) => return node::leaf_iterator::start(x) } } pub fn next(it: &mut node::leaf_iterator::T) -> Option<node::Leaf> { return node::leaf_iterator::next(it); } } pub mod char { use rope::{Rope, node}; pub fn start(rope: Rope) -> node::char_iterator::T { match (rope) { node::Empty      => return node::char_iterator::empty(), node::Content(x) => return node::char_iterator::start(x) } } pub fn next(it: &mut node::char_iterator::T) -> Option<char> { return node::char_iterator::next(it) } } } /* Section: Rope properties */ /** * Returns the height of the rope. * * The height of the rope is a bound on the number of operations which * must be performed during a character access before finding the leaf in * which a character is contained. * * # Performance note * * Constant time. */ pub fn height(rope: Rope) -> uint { match (rope) { node::Empty      => return 0u, node::Content(x) => return node::height(x) } } /** * The number of character in the rope * * # Performance note * * Constant time. */ pub fn char_len(rope: Rope) -> uint { match (rope) { node::Empty            => return 0u, node::Content(x)       => return node::char_len(x) } } /** * The number of bytes in the rope * * # Performance note * * Constant time. */ pub fn byte_len(rope: Rope) -> uint { match (rope) { node::Empty            => return 0u, node::Content(x)       => return node::byte_len(x) } } /** * The character at position `pos` * * # Arguments * * * pos - A position in the rope * * # Safety notes * * The function will fail if `pos` is not a valid position in the rope. * * # Performance note * * This function executes in a time proportional to the height of the * rope + the (bounded) length of the largest leaf. */ pub fn char_at(rope: Rope, pos: uint) -> char { match (rope) { node::Empty => fail!(), node::Content(x) => return node::char_at(x, pos) } } /* Section: Implementation */ pub mod node { use rope::node; use std::cast; use std::uint; use std::vec; /// Implementation of type `rope` pub enum Root { /// An empty rope Empty, /// A non-empty rope Content(@Node), } /** * A text component in a rope. * * This is actually a slice in a rope, so as to ensure maximal sharing. * * # Fields * * * byte_offset = The number of bytes skipped in `content` * * byte_len - The number of bytes of `content` to use * * char_len - The number of chars in the leaf. * * content - Contents of the leaf. * *     Note that we can have `char_len < content.char_len()`, if *     this leaf is only a subset of the string. Also note that the *     string can be shared between several ropes, e.g. for indexing *     purposes. */ pub struct Leaf { byte_offset: uint, byte_len: uint, char_len: uint, content: @~str, } /** * A node obtained from the concatenation of two other nodes * * # Fields * * * left - The node containing the beginning of the text. * * right - The node containing the end of the text. * * char_len - The number of chars contained in all leaves of this node. * * byte_len - The number of bytes in the subrope. * *     Used to pre-allocate the correct amount of storage for *     serialization. * * * height - Height of the subrope. * *     Used for rebalancing and to allocate stacks for traversals. */ pub struct Concat { //FIXME (#2744): Perhaps a `vec` instead of `left`/`right` left: @Node, right: @Node, char_len: uint, byte_len: uint, height: uint, } pub enum Node { /// A leaf consisting in a `str` Leaf(Leaf), /// The concatenation of two ropes Concat(Concat), } /** * The maximal number of chars that _should_ be permitted in a single node * * This is not a strict value */ pub static HINT_MAX_LEAF_CHAR_LEN: uint = 256u; /** * The maximal height that _should_ be permitted in a tree. * * This is not a strict value */ pub static HINT_MAX_NODE_HEIGHT:   uint = 16u; /** * Adopt a string as a node. * * If the string is longer than `max_leaf_char_len`, it is * logically split between as many leaves as necessary. Regardless, * the string itself is not copied. * * Performance note: The complexity of this function is linear in * the length of `str`. */ pub fn of_str(str: @~str) -> @Node { return of_substr(str, 0u, str.len()); } /** * Adopt a slice of a string as a node. * * If the slice is longer than `max_leaf_char_len`, it is logically split * between as many leaves as necessary. Regardless, the string itself * is not copied * * # Arguments * * * byte_start - The byte offset where the slice of `str` starts. * * byte_len   - The number of bytes from `str` to use. * * # Safety note * * Behavior is undefined if `byte_start` or `byte_len` do not represent * valid positions in `str` */ pub fn of_substr(str: @~str, byte_start: uint, byte_len: uint) -> @Node { return of_substr_unsafer(str, byte_start, byte_len, str.slice(byte_start, byte_start + byte_len).char_len()); } /** * Adopt a slice of a string as a node. * * If the slice is longer than `max_leaf_char_len`, it is logically split * between as many leaves as necessary. Regardless, the string itself * is not copied * * # Arguments * * * byte_start - The byte offset where the slice of `str` starts. * * byte_len - The number of bytes from `str` to use. * * char_len - The number of chars in `str` in the interval *              [byte_start, byte_start+byte_len) * * # Safety notes * * * Behavior is undefined if `byte_start` or `byte_len` do not represent *   valid positions in `str` * * Behavior is undefined if `char_len` does not accurately represent the *   number of chars between byte_start and byte_start+byte_len */ pub fn of_substr_unsafer(str: @~str, byte_start: uint, byte_len: uint, char_len: uint) -> @Node { assert!((byte_start + byte_len <= str.len())); let candidate = @Leaf(Leaf { byte_offset: byte_start, byte_len: byte_len, char_len: char_len, content: str, }); if char_len <= HINT_MAX_LEAF_CHAR_LEN { return candidate; } else { //Firstly, split `str` in slices of HINT_MAX_LEAF_CHAR_LEN let mut leaves = uint::div_ceil(char_len, HINT_MAX_LEAF_CHAR_LEN); //Number of leaves let mut nodes  = vec::from_elem(leaves, candidate); let mut i = 0u; let mut offset = byte_start; let first_leaf_char_len = if char_len%HINT_MAX_LEAF_CHAR_LEN == 0u { HINT_MAX_LEAF_CHAR_LEN } else { char_len%HINT_MAX_LEAF_CHAR_LEN }; while i < leaves { let chunk_char_len: uint = if i == 0u  { first_leaf_char_len } else { HINT_MAX_LEAF_CHAR_LEN }; let chunk_byte_len = str.slice_from(offset).slice_chars(0, chunk_char_len).len(); nodes[i] = @Leaf(Leaf { byte_offset: offset, byte_len: chunk_byte_len, char_len: chunk_char_len, content: str, }); offset += chunk_byte_len; i      += 1u; } //Then, build a tree from these slices by collapsing them while leaves > 1u { i = 0u; while i < leaves - 1u {//Concat nodes 0 with 1, 2 with 3 etc. nodes[i/2u] = concat2(nodes[i], nodes[i + 1u]); i += 2u; } if i == leaves - 1u { //And don't forget the last node if it is in even position nodes[i/2u] = nodes[i]; } leaves = uint::div_ceil(leaves, 2u); } return nodes[0u]; } } pub fn byte_len(node: @Node) -> uint { //FIXME (#2744): Could we do this without the pattern-matching? match (*node) { Leaf(y) => y.byte_len, Concat(ref y) => y.byte_len } } pub fn char_len(node: @Node) -> uint { match (*node) { Leaf(y) => y.char_len, Concat(ref y) => y.char_len } } /** * Concatenate a forest of nodes into one tree. * * # Arguments * * * forest - The forest. This vector is progressively rewritten during *            execution and should be discarded as meaningless afterwards. */ pub fn tree_from_forest_destructive(forest: &mut [@Node]) -> @Node { let mut i; let mut len = forest.len(); while len > 1u { i = 0u; while i < len - 1u {//Concat nodes 0 with 1, 2 with 3 etc. let mut left  = forest[i]; let mut right = forest[i+1u]; let left_len = char_len(left); let right_len= char_len(right); let mut left_height= height(left); let mut right_height=height(right); if left_len + right_len > HINT_MAX_LEAF_CHAR_LEN { if left_len <= HINT_MAX_LEAF_CHAR_LEN { left = flatten(left); left_height = height(left); } if right_len <= HINT_MAX_LEAF_CHAR_LEN { right = flatten(right); right_height = height(right); } } if left_height >= HINT_MAX_NODE_HEIGHT { left = of_substr_unsafer(@serialize_node(left), 0u,byte_len(left), left_len); } if right_height >= HINT_MAX_NODE_HEIGHT { right = of_substr_unsafer(@serialize_node(right), 0u,byte_len(right), right_len); } forest[i/2u] = concat2(left, right); i += 2u; } if i == len - 1u { //And don't forget the last node if it is in even position forest[i/2u] = forest[i]; } len = uint::div_ceil(len, 2u); } return forest[0]; } pub fn serialize_node(node: @Node) -> ~str { unsafe { let mut buf = vec::from_elem(byte_len(node), 0); let mut offset = 0u;//Current position in the buffer let mut it = leaf_iterator::start(node); loop { match leaf_iterator::next(&mut it) { None => break, Some(x) => { //FIXME (#2744): Replace with memcpy or something similar let local_buf: ~[u8] = cast::transmute(copy *x.content); let mut i = x.byte_offset; while i < x.byte_len { buf[offset] = local_buf[i]; offset += 1u; i      += 1u; } cast::forget(local_buf); } } } return cast::transmute(buf); } } /** * Replace a subtree by a single leaf with the same contents. * * * Performance note * * This function executes in linear time. */ pub fn flatten(node: @Node) -> @Node { match (*node) { Leaf(_) => node, Concat(ref x) => { @Leaf(Leaf { byte_offset: 0u, byte_len: x.byte_len, char_len: x.char_len, content: @serialize_node(node), }) } } } /** * Balance a node. * * # Algorithm * * * if the node height is smaller than `HINT_MAX_NODE_HEIGHT`, do nothing * * otherwise, gather all leaves as a forest, rebuild a balanced node, *   concatenating small leaves along the way * * # Return value * * * `None` if no transformation happened * * `Some(x)` otherwise, in which case `x` has the same contents *    as `node` bot lower height and/or fragmentation. */ pub fn bal(node: @Node) -> Option<@Node> { if height(node) < HINT_MAX_NODE_HEIGHT { return None; } //1. Gather all leaves as a forest let mut forest = ~[]; let mut it = leaf_iterator::start(node); loop { match leaf_iterator::next(&mut it) { None    => break, Some(x) => forest.push(@Leaf(x)) } } //2. Rebuild tree from forest let root = @*tree_from_forest_destructive(forest); return Some(root); } /** * Compute the subnode of a node. * * # Arguments * * * node        - A node * * byte_offset - A byte offset in `node` * * byte_len    - The number of bytes to return * * # Performance notes * * * this function performs no copying; * * this function executes in a time proportional to the height of `node` * * # Safety notes * * This function fails if `byte_offset` or `byte_len` do not represent * valid positions in `node`. */ pub fn sub_bytes(node: @Node, byte_offset: uint, byte_len: uint) -> @Node { let mut node        = node; let mut byte_offset = byte_offset; loop { if byte_offset == 0u && byte_len == node::byte_len(node) { return node; } match (*node) { node::Leaf(x) => { let char_len = x.content.slice(byte_offset, byte_offset + byte_len).char_len(); return @Leaf(Leaf { byte_offset: byte_offset, byte_len: byte_len, char_len: char_len, content: x.content, }); } node::Concat(ref x) => { let left_len: uint = node::byte_len(x.left); if byte_offset <= left_len { if byte_offset + byte_len <= left_len { //Case 1: Everything fits in x.left, tail-call node = x.left; } else { //Case 2: A (non-empty, possibly full) suffix //of x.left and a (non-empty, possibly full) prefix //of x.right let left_result  = sub_bytes(x.left, byte_offset, left_len); let right_result = sub_bytes(x.right, 0u, left_len - byte_offset); return concat2(left_result, right_result); } } else { //Case 3: Everything fits in x.right byte_offset -= left_len; node = x.right; } } } }; } /** * Compute the subnode of a node. * * # Arguments * * * node        - A node * * char_offset - A char offset in `node` * * char_len    - The number of chars to return * * # Performance notes * * * this function performs no copying; * * this function executes in a time proportional to the height of `node` * * # Safety notes * * This function fails if `char_offset` or `char_len` do not represent * valid positions in `node`. */ pub fn sub_chars(node: @Node, char_offset: uint, char_len: uint) -> @Node { let mut node        = node; let mut char_offset = char_offset; loop { match (*node) { node::Leaf(x) => { if char_offset == 0u && char_len == x.char_len { return node; } let byte_offset = x.content.slice_chars(0, char_offset).len(); let byte_len    = x.content.slice_from(byte_offset).slice_chars(0, char_len).len(); return @Leaf(Leaf { byte_offset: byte_offset, byte_len: byte_len, char_len: char_len, content: x.content, }); } node::Concat(ref x) => { if char_offset == 0u && char_len == x.char_len {return node;} let left_len : uint = node::char_len(x.left); if char_offset <= left_len { if char_offset + char_len <= left_len { //Case 1: Everything fits in x.left, tail call node        = x.left; } else { //Case 2: A (non-empty, possibly full) suffix //of x.left and a (non-empty, possibly full) prefix //of x.right let left_result  = sub_chars(x.left, char_offset, left_len); let right_result = sub_chars(x.right, 0u, left_len - char_offset); return concat2(left_result, right_result); } } else { //Case 3: Everything fits in x.right, tail call node = x.right; char_offset -= left_len; } } } }; } pub fn concat2(left: @Node, right: @Node) -> @Node { @Concat(Concat { left: left, right: right, char_len: char_len(left) + char_len(right), byte_len: byte_len(left) + byte_len(right), height: uint::max(height(left), height(right)) + 1u, }) } pub fn height(node: @Node) -> uint { match (*node) { Leaf(_) => 0u, Concat(ref x) => x.height, } } pub fn cmp(a: @Node, b: @Node) -> int { let mut ita = char_iterator::start(a); let mut itb = char_iterator::start(b); let mut result = 0; while result == 0 { match (char_iterator::next(&mut ita), char_iterator::next(&mut itb)) { (None, None) => break, (Some(chara), Some(charb)) => { result = chara.cmp(&charb) as int; } (Some(_), _) => { result = 1; } (_, Some(_)) => { result = -1; } } } return result; } pub fn loop_chars(node: @Node, it: &fn(c: char) -> bool) -> bool { return loop_leaves(node,|leaf| { leaf.content.slice(leaf.byte_offset, leaf.byte_len).iter().all(|c| it(c)) }); } /** * Loop through a node, leaf by leaf * * # Arguments * * * rope - A node to traverse. * * it - A block to execute with each consecutive leaf of the node. *        Return `true` to continue, `false` to stop * * # Arguments * * `true` If execution proceeded correctly, `false` if it was interrupted, * that is if `it` returned `false` at any point. */ pub fn loop_leaves(node: @Node, it: &fn(Leaf) -> bool) -> bool{ let mut current = node; loop { match (*current) { Leaf(x) => return it(x), Concat(ref x) => if loop_leaves(x.left, |l| it(l)) { //non tail call current = x.right;       //tail call } else { return false; } } }; } /** * # Arguments * * * pos - A position in the rope * * # Return value * * The character at position `pos` * * # Safety notes * * The function will fail if `pos` is not a valid position in the rope. * * Performance note: This function executes in a time * proportional to the height of the rope + the (bounded) * length of the largest leaf. */ pub fn char_at(mut node: @Node, mut pos: uint) -> char { loop { match *node { Leaf(x) => return x.content.char_at(pos), Concat(Concat {left, right, _}) => { let left_len = char_len(left); node = if left_len > pos { left } else { pos -= left_len; right }; } } }; } pub mod leaf_iterator { use rope::node::{Concat, Leaf, Node, height}; use std::vec; pub struct T { stack: ~[@Node], stackpos: int, } pub fn empty() -> T { let stack : ~[@Node] = ~[]; T { stack: stack, stackpos: -1 } } pub fn start(node: @Node) -> T { let stack = vec::from_elem(height(node)+1u, node); T { stack: stack, stackpos:  0, } } pub fn next(it: &mut T) -> Option<Leaf> { if it.stackpos < 0 { return None; } loop { let current = it.stack[it.stackpos]; it.stackpos -= 1; match (*current) { Concat(ref x) => { it.stackpos += 1; it.stack[it.stackpos] = x.right; it.stackpos += 1; it.stack[it.stackpos] = x.left; } Leaf(x) => return Some(x) } }; } } pub mod char_iterator { use rope::node::{Leaf, Node}; use rope::node::leaf_iterator; pub struct T { leaf_iterator: leaf_iterator::T, leaf:  Option<Leaf>, leaf_byte_pos: uint, } pub fn start(node: @Node) -> T { T { leaf_iterator: leaf_iterator::start(node), leaf: None, leaf_byte_pos: 0u, } } pub fn empty() -> T { T { leaf_iterator: leaf_iterator::empty(), leaf: None, leaf_byte_pos: 0u, } } pub fn next(it: &mut T) -> Option<char> { loop { match get_current_or_next_leaf(it) { None => return None, Some(_) => { let next_char = get_next_char_in_leaf(it); match next_char { None => loop, Some(_) => return next_char } } } }; } pub fn get_current_or_next_leaf(it: &mut T) -> Option<Leaf> { match it.leaf { Some(_) => return it.leaf, None => { let next = leaf_iterator::next(&mut it.leaf_iterator); match next { None => return None, Some(_) => { it.leaf          = next; it.leaf_byte_pos = 0u; return next; } } } } } pub fn get_next_char_in_leaf(it: &mut T) -> Option<char> { match copy it.leaf { None => return None, Some(aleaf) => { if it.leaf_byte_pos >= aleaf.byte_len { //We are actually past the end of the leaf it.leaf = None; return None } else { let range = aleaf.content.char_range_at((*it).leaf_byte_pos + aleaf.byte_offset); let ch = range.ch; let next = range.next; (*it).leaf_byte_pos = next - aleaf.byte_offset; return Some(ch) } } } } } } #[cfg(test)] mod tests { use rope::*; use std::uint; use std::vec; //Utility function, used for sanity check fn rope_to_string(r: Rope) -> ~str { match (r) { node::Empty => return ~"", node::Content(x) => { let mut str = ~""; fn aux(str: &mut ~str, node: @node::Node) { match (*node) { node::Leaf(x) => { str.push_str(x.content.slice(x.byte_offset, x.byte_offset + x.byte_len)); } node::Concat(ref x) => { aux(str, x.left); aux(str, x.right); } } } aux(&mut str, x); return str } } } #[test] fn trivial() { assert_eq!(char_len(empty()), 0u); assert_eq!(byte_len(empty()), 0u); } #[test] fn of_string1() { let sample = @~"0123456789ABCDE"; let r      = of_str(sample); assert_eq!(char_len(r), sample.char_len()); assert!(rope_to_string(r) == *sample); } #[test] fn of_string2() { let buf = @ mut ~"1234567890"; let mut i = 0; while i < 10 { let a = copy *buf; let b = copy *buf; *buf = a + b; i+=1; } let sample = @copy *buf; let r      = of_str(sample); assert_eq!(char_len(r), sample.char_len()); assert!(rope_to_string(r) == *sample); let mut string_iter = 0u; let string_len = sample.len(); let mut rope_iter = iterator::char::start(r); let mut equal = true; while equal { match (node::char_iterator::next(&mut rope_iter)) { None => { if string_iter < string_len { equal = false; } break; } Some(c) => { let range = sample.char_range_at(string_iter); string_iter = range.next; if range.ch != c { equal = false; break; } } } } assert!(equal); } #[test] fn iter1() { let buf = @ mut ~"1234567890"; let mut i = 0; while i < 10 { let a = copy *buf; let b = copy *buf; *buf = a + b; i+=1; } let sample = @copy *buf; let r      = of_str(sample); let mut len = 0u; let mut it  = iterator::char::start(r); loop { match (node::char_iterator::next(&mut it)) { None => break, Some(_) => len += 1u } } assert_eq!(len, sample.char_len()); } #[test] fn bal1() { let init = @~"1234567890"; let buf  = @mut copy *init; let mut i = 0; while i < 8 { let a = copy *buf; let b = copy *buf; *buf = a + b; i+=1; } let sample = @copy *buf; let r1     = of_str(sample); let mut r2 = of_str(init); i = 0; while i < 8 { r2 = append_rope(r2, r2); i+= 1;} assert!(eq(r1, r2)); let r3 = bal(r2); assert_eq!(char_len(r1), char_len(r3)); assert!(eq(r1, r3)); } #[test] #[ignore] fn char_at1() { //Generate a large rope let mut r = of_str(@~"123456789"); for uint::range(0u, 10u) |_i| { r = append_rope(r, r); } //Copy it in the slowest possible way let mut r2 = empty(); for uint::range(0u, char_len(r)) |i| { r2 = append_char(r2, char_at(r, i)); } assert!(eq(r, r2)); let mut r3 = empty(); for uint::range(0u, char_len(r)) |i| { r3 = prepend_char(r3, char_at(r, char_len(r) - i - 1u)); } assert!(eq(r, r3)); //Additional sanity checks let balr = bal(r); let bal2 = bal(r2); let bal3 = bal(r3); assert!(eq(r, balr)); assert!(eq(r, bal2)); assert!(eq(r, bal3)); assert!(eq(r2, r3)); assert!(eq(bal2, bal3)); } #[test] fn concat1() { //Generate a reasonable rope let chunk = of_str(@~"123456789"); let mut r = empty(); for uint::range(0u, 10u) |_i| { r = append_rope(r, chunk); } //Same rope, obtained with rope::concat let r2 = concat(vec::from_elem(10u, chunk)); assert!(eq(r, r2)); } } </del>

In commit I had to disable one of the rope tests. The purpose of that commit was to fix up a bug in the way operator [] works on strings. In particular it used to let you index up to and including the trailing null; now it considers access to the null byte an index overrun and fails. I tried to figure out exactly what was going on in the rope code for quite a while, and tried adjusting a number of obvious-looking things, but to no avail. I'm not really good with boundary-case arithmetic (are any of us?) and the rope module is a maze of such arithmetic. Sharper minds requested to find the bug. Meanwhile I marked the rope test in question () as ignored. The others seem to work.
Paging
I'll take a look.
There is some discussion on IRC that maybe we should just pitch the rope code as it exists now.
agreed. The code is pretty outdated and the work required to modernize it would probably be similar to the work needed to just re-write it. When I was looking through it, it seemed to be doing some very strange things that I don't think are even close to being safe, so there's that too.


Question: } else { match self.try_coercion_cast(fcx) { Ok(()) => { <del> self.trivial_cast_lint(fcx); debug!(" -> CoercionCast"); fcx.typeck_results.borrow_mut().set_coercion_cast(self.expr.hir_id.local_id); </del> <ins> if self.expr_ty.is_unsafe_ptr() && self.cast_ty.is_unsafe_ptr() { // When casting a raw pointer to another raw pointer, we cannot convert the cast into // a coercion because the pointee types might only differ in regions, which HIR typeck // cannot distinguish. This would cause us to erroneously discard a cast which will // lead to a borrowck error like #113257. // We still did a coercion above to unify inference variables for `ptr as _` casts. // This does cause us to miss some trivial casts in the trival cast lint. debug!(" -> PointerCast"); } else { self.trivial_cast_lint(fcx); debug!(" -> CoercionCast"); fcx.typeck_results .borrow_mut() .set_coercion_cast(self.expr.hir_id.local_id); } </ins> } Err(_) => { match self.do_check(fcx) {

<!-- Thank you for filing a bug report! Please provide a short summary of the bug, along with any information you feel relevant to replicating the bug. --I tried this code: . I expected to see this happen: it compiles, since raw pointer casts should be allowed even for completely different types. Instead, this happened: it fails with: It looks like for some reason the compiler creates a lifetime obligation here. Strangely, it works with : .
I've looked into this. The issue is that typeck considers this cast to be a "coercion cast", because typeck thinks that you could replace the function body with just since the types are the same (it also triggers the lint (allow-by-default)). HIR typeck isn't aware of lifetimes. So then THIR building and MIR building build MIR that doesn't contain a cast but instead just assigns one to the other. The borrow checker is rightfully not pleased. The bug here is that the THIR does not contain an explicit cast anymore. I'm not sure how to best fix this, as the current logic for "is this a no-op cast (called coercion cast in the code)" is just "try to coerce the types in the same way that happens on coercion sites and if it works it has to be useless". Which is wrong in the presence of lifetimes. It seems like the current strategy works this way because it reuses some code in THIR building. Thinking about it a little more, this is very special to pointer casts, because only pointer casts are allowed to change the type like this. Maybe THIR building should just special case pointer casts to always be emitted?
Unfortunately due to this bug there is that should be turned into a raw pointer cast, but we cannot.
I think that's a different case, these are boxes and not raw pointers, and box-box casts are not allowed to do arbitrary changes (and are in fact coercion casts, so the doesn't actually do anything). The error there is correct and this needs a transmute.
I'd like to replace the transmutes by raw pointer casts, which IMO is much preferable, similar to But this bug is in the way of that.


Question: //! } //! //! // This function wants to log its parameter out prior to doing work with it. <del> //! fn do_work<T: Show>(value: &T) { </del> <ins> //! fn do_work<T: Show+'static>(value: &T) { </ins> //!     log(value); //!     // ...do some other work //! }

Example: This should be fixed by code in .
Nominating for 1.0, P-backcompat-lang.
Assigning 1.0, P-backcompat-lang


Question: None => return DummyResult::any(sp), }; // The file will be added to the code map by the parser <del> let file = cx.resolve_path(file, sp); </del> <ins> let file = match cx.resolve_path(file, sp) { Ok(f) => f, Err(mut err) => { err.emit(); return DummyResult::any(sp); }, }; </ins> let directory_ownership = DirectoryOwnership::Owned { relative: None }; let p = parse::new_sub_parser_from_file(cx.parse_sess(), &file, directory_ownership, None, sp);

to reproduce the fault this can be used: will result in this ICE: thread 'rustc' panicked at 'cannot resolve relative path in non-file source ', note: Run with RUSTBACKTRACE=1 environment variable to display a backtrace. error: internal compiler error: unexpected panic note: the compiler unexpectedly panicked. this is a bug. note: we would appreciate a bug report: note: rustc 1.36.0 ( 2019-07-03) running on x86_64-unknown-linux-gnu the question is what is the path that shall be used when the code is from stdin maybe some temp file name in the currrent working dir.
This ICE also occurs when using and , which makes sense as they use the same mechanism under the hood (, , meaning it also .
While poking at this I also noticed that items are silently ignored, and filed .


Question: ARCH=$1 LIB_ARCH=$2 APT_ARCH=$3 <ins> MANUFACTURER=$4 </ins> BINUTILS=2.28.1 GCC=6.5.0 <del> # Choose correct target based on the $ARCH case "$ARCH" in x86_64) TARGET=x86_64-pc-solaris2.10 ;; sparcv9) TARGET=sparcv9-sun-solaris2.10 ;; *) printf 'ERROR: unknown architecture: %sn' "$ARCH" exit 1 esac </del> <ins> TARGET=${ARCH}-${MANUFACTURER}-solaris2.10 </ins> # First up, build binutils mkdir binutils

removed the alias of to , but as far as I can tell did not remove the target itself. After inquiring on Zulip, it was pointed out that the target was demoted to tier three, meaning builds are no longer provided. It appears this was done with no discussion, warning, or even a mention in the release notes. Given that the target hasn't been around for that long (I think? feel free to tell me I'm wrong), I think this change should be reverted. My CI unexpectedly failed after being unable to download the target, and the only solution that wouldn't involve a notable increase in complexity of the CI script is to outright remove the checks for Solaris/Illumos — a let loss for everyone. cc
I think my feeling right now is that we should likely re-enable the sun-solaris target alongside the pc-solaris, I don't think that should be a major problem for CI times or similar. I will see if we can do this in time for 1.52.1, but if it ends up being harder than expected, I think it's likely that either a) we'll push it out to a 1.52.2 release later in the cycle, or simply leave it to 1.53. For the time being, tagging as 1.53.
I will note that the change itself is fine, of course, it's the suddenness and unexpectedness of it that caught me by surprise.
I think it is likely true that we'll eventually drop the sun target, but it does seem like we should communicate that change better at least. Certainly the newly merged target tier policy suggests just that:
It is not clear to me what the impact of was. In particular, the produced target was not changed in , but I guess the target definition is sort of for pc... I think I am not sufficiently confident in moving in a particular direction here - and a full revert does not seem quite right either - so I will likely say this should be left for at least 1.52.2 or 1.53.0. cc as well, who reviewed
Removal of the older target was certainly not my intent; if that happened due to , I am sorry. Both and are there and both targets are usable when compiling with , but I guess that change to one of files might have resulted in artifacts necessary for no longer being available.


Question: ARCH=$1 LIB_ARCH=$2 APT_ARCH=$3 <ins> MANUFACTURER=$4 </ins> BINUTILS=2.28.1 GCC=6.5.0 <del> # Choose correct target based on the $ARCH case "$ARCH" in x86_64) TARGET=x86_64-pc-solaris2.10 ;; sparcv9) TARGET=sparcv9-sun-solaris2.10 ;; *) printf 'ERROR: unknown architecture: %sn' "$ARCH" exit 1 esac </del> <ins> TARGET=${ARCH}-${MANUFACTURER}-solaris2.10 </ins> # First up, build binutils mkdir binutils

I created , which I believe should bring back the ability to the old target.
Reopening to track the illumos target work. I'm not sure it's a release blocker, but would like to not lose sight.
The remaining bug in this issue -- the change in the compiler used to build illumos to be based on a -pc- triple, not -sun- here is not a release blocker.
Realistically I don't think there's anything actionable here, so I'm going to go ahead and close this.


Question: let diagnostic = &sess.span_diagnostic; 'outer: for attr in attrs_iter { <del> if attr.path != "deprecated" { continue </del> <ins> if !attr.check_name("deprecated") { continue; </ins> } <del> mark_used(attr); </del> if depr.is_some() { span_err!(diagnostic, item_sp, E0550, "multiple deprecated attributes"); break } <del> depr = if let Some(metas) = attr.meta_item_list() { let get = |meta: &MetaItem, item: &mut Option<Symbol>| { if item.is_some() { handle_errors(sess, meta.span, AttrError::MultipleItem(meta.name())); return false } if let Some(v) = meta.value_str() { *item = Some(v); true } else { if let Some(lit) = meta.name_value_literal() { handle_errors( sess, lit.span, AttrError::UnsupportedLiteral( "literal in `deprecated`  value must be a string", lit.node.is_bytestr() ), ); } else { span_err!(diagnostic, meta.span, E0551, "incorrect meta item"); </del> <ins> let meta = attr.meta().unwrap(); depr = match &meta.node { MetaItemKind::Word => Some(Deprecation { since: None, note: None }), MetaItemKind::NameValue(..) => { meta.value_str().map(|note| { Deprecation { since: None, note: Some(note) } }) } MetaItemKind::List(list) => { let get = |meta: &MetaItem, item: &mut Option<Symbol>| { if item.is_some() { handle_errors(sess, meta.span, AttrError::MultipleItem(meta.name())); return false </ins> } <ins> if let Some(v) = meta.value_str() { *item = Some(v); true } else { if let Some(lit) = meta.name_value_literal() { handle_errors( sess, lit.span, AttrError::UnsupportedLiteral( "literal in `deprecated`  value must be a string", lit.node.is_bytestr() ), ); } else { span_err!(diagnostic, meta.span, E0551, "incorrect meta item"); } </ins> <del> false } }; </del> <ins> false } }; </ins> <del> let mut since = None; let mut note = None; for meta in metas { match &meta.node { NestedMetaItemKind::MetaItem(mi) => { match &*mi.name().as_str() { "since" => if !get(mi, &mut since) { continue 'outer }, "note" => if !get(mi, &mut note) { continue 'outer }, _ => { handle_errors( sess, meta.span, AttrError::UnknownMetaItem(mi.name(), &["since", "note"]), ); continue 'outer </del> <ins> let mut since = None; let mut note = None; for meta in list { match &meta.node { NestedMetaItemKind::MetaItem(mi) => { match &*mi.name().as_str() { "since" => if !get(mi, &mut since) { continue 'outer }, "note" => if !get(mi, &mut note) { continue 'outer }, _ => { handle_errors( sess, meta.span, AttrError::UnknownMetaItem(mi.name(), &["since", "note"]), ); continue 'outer } </ins> } } <del> } NestedMetaItemKind::Literal(lit) => { handle_errors( sess, lit.span, AttrError::UnsupportedLiteral( "item in `deprecated` must be a key/value pair", false, ), ); continue 'outer </del> <ins> NestedMetaItemKind::Literal(lit) => { handle_errors( sess, lit.span, AttrError::UnsupportedLiteral( "item in `deprecated` must be a key/value pair", false, ), ); continue 'outer } </ins> } } <del> } Some(Deprecation {since: since, note: note}) } else { Some(Deprecation{since: None, note: None}) } </del> <ins> Some(Deprecation { since, note }) } }; </ins> } depr

Look for example to It is deprecated, and in source code there is hint what should be used instead: but in generated html documentation there is no hint about
isn't the correct syntax for the attribute. It should be . You can see it work: . This isn't a rustdoc issue. It's an issue with rustc that it doesn't reject .


Question: <ins> #![crate_type = "lib"] #![feature(unnamed_fields)] #![allow(unused, incomplete_features)] enum K { M { _ : struct { field: u8 }, //~^ error: unnamed fields are not allowed outside of structs or unions //~| error: anonymous structs are not allowed outside of unnamed struct or union fields } } </ins>

<!-- ICE: Rustc '' 'error: internal compiler error: expected ADT to be an item', 'error: internal compiler error: expected ADT to be an item' File: --snippet: Version information Command: <!-- Include a backtrace in the code block by setting in your environment. E.g. . --<details<summary<strongProgram output</strong</summary<p</p</details<!-- query stack: [adtdef] computing ADT definition for [adtdef] computing ADT definition for [typeof] computing type of [inferredoutlivescrate] computing the inferred outlives predicates for items in this crate [inferredoutlivesof] computing inferred outlives predicates of [predicatesdefinedon] computing predicates of [predicatesof] computing predicates of [collectmoditemtypes] collecting item types in top-level module [analysis] running analysis passes on this crate --<!-- TRIAGEBOTSTART --<!-- TRIAGEBOTASSIGNSTART --<!-- TRIAGEBOTASSIGNDATASTARTTRIAGEBOTASSIGNDATAEND --<!-- TRIAGEBOTASSIGNEND --<!-- TRIAGEBOT_END --
cc
Oh, seems that more checks are needed to prevent unnamed fields from occurring in enums.
claim


Question: <ins> error[E0080]: it is undefined behavior to use this value --> $DIR/issue-79690.rs:29:1 | LL | const G: Fat = unsafe { Transmute { t: FOO }.u }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ type validation failed: encountered read of part of a pointer at .1.<deref>.size.foo | = note: The rules on what exactly is undefined behavior aren't clear, so this check might be overzealous. Please open an issue on the rustc repository if you believe it should not be considered undefined behavior. error: aborting due to previous error For more information about this error, try `rustc --explain E0080`. </ins>

<!-- Thank you for finding an Internal Compiler Error! If possible, try to provide a minimal verifiable example. You can read "Rust Bug Minimization Patterns" for how to create smaller examples. --<!-- If you're using the stable version of the compiler, you should also check if the bug also exists in the beta or nightly versions. --: <!-- Include a backtrace in the code block by setting in your environment. E.g. . --<details<summary<strongBacktrace</strong</summary<p</p</details
glacier ""
Error: Parsing glacier command in failed: ...'cier https' ''... Please let know if you're having trouble with this bot.
Compiler error on but ICE on label +regression-from-stable-to-stable
cc
A previous (now-fixed) ICE with the same error message is
Thanks for the report! Would be good to get a better backtrace; this one seems to be missing many frames and there are no line numbers.
, I updated the backtrace in my first post. I used rustup to get the latest trunk version, and the one I have been using does not have line numbers. If there is a flag or configuration for rustup to get rustc binaries with line numbers, I would be happy to change the settings on my testing machine.
I think getting line numbers requires a debug build of the compiler, so it's not quite as easy as installing something via rustup.
Labeling .
Here's a backtrace with debug info: <details</details
Unfortunately, this bottoms out ; more digging will be needed to determine the true underlying cause of the error.
Here is the actual backtrace of the underlying CTFE/Miri error: <details</detailsIt points to this line: can handle pointer values just fine, so I think what is happening is that we are reading parts of a pointer value here, which indeed is not supported in CTFE.
So... I guess the fix is to make this kind of error also be permitted during validation and turn it into a proper validation error?
Permitting it at the top-level is no good, since then we don't know "where in the value" it arose. We need a version of that catches this error and turns it into a validation error with the current "path".
Oh... right, we should fix this in general, and not just wrap in .


Question: <ins> error[E0080]: it is undefined behavior to use this value --> $DIR/issue-79690.rs:29:1 | LL | const G: Fat = unsafe { Transmute { t: FOO }.u }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ type validation failed: encountered read of part of a pointer at .1.<deref>.size.foo | = note: The rules on what exactly is undefined behavior aren't clear, so this check might be overzealous. Please open an issue on the rustc repository if you believe it should not be considered undefined behavior. error: aborting due to previous error For more information about this error, try `rustc --explain E0080`. </ins>

Maybe we should not have any automatic way to bubble up errors and require all sites to use ?
I think we should wrap it but put the wrapped thing into a helper function sow e can also use it for the other places. So basically, you want to use a different error type for validation, one that excludes all the other errors? I am open to refactorings here.
Let's start out with wrapping, and then I can experiment with the refactoring so we can have a look at the usability effect of it Instructions: wrap the call at in a macro invocation and capture the error. Emit a validation message about the fact that reading parts of a pointer is not possible during CTFE. Don't forget to add a test (the failing example from this issue works great as a test) and run the test with
There are quite a few calls in that will all have this problem, so ideally they should all be treated this way. To avoid code duplication, it will probably make sense to move this into a helper function.
Reopening this to track that is still called unguarded a few times in , so e.g. the issue can still arise with .


Question: &format!("comparison of `{}`", cx.ty_to_string(rhs_t))[], StrEqFnLangItem); <del> callee::trans_lang_call(cx, did, &[lhs, rhs], None, debug_loc) </del> <ins> let t = ty::mk_str_slice(cx.tcx(), cx.tcx().mk_region(ty::ReStatic), ast::MutImmutable); // The comparison function gets the slices by value, so we have to make copies here. Even // if the function doesn't write through the pointer, things like lifetime intrinsics // require that we do this properly let lhs_arg = alloc_ty(cx, t, "lhs"); let rhs_arg = alloc_ty(cx, t, "rhs"); memcpy_ty(cx, lhs_arg, lhs, t); memcpy_ty(cx, rhs_arg, rhs, t); let res = callee::trans_lang_call(cx, did, &[lhs_arg, rhs_arg], None, debug_loc); call_lifetime_end(res.bcx, lhs_arg); call_lifetime_end(res.bcx, rhs_arg); res </ins> } let _icx = push_ctxt("compare_values");

Hi, The rust optimiser or some other component is not behaving correctly resulting in wrong results. I have produced a small example here : This was reproduced on Mac yosemite: cargo 0.0.1-pre-nightly ( 2015-01-30 02:47:30 +0000) rustc 1.0.0-nightly ( 2015-02-03 14:56:32 +0000) and centos7 x64, rustc 1.0.0-nightly ( 2015-02-05 23:14:28 +0000) Please let me know if you need any more details.
Reduced test case: This segfaults with = 2 (on Linux x86-64, )
This goes back to at least 0.12 - though it prints garbage values instead of segfaulting. 0.11 seems unaffected.
I've done some hacky . I'm not seeing the original data in the IR. cc Is this another memcpy metadata bug? Or a lifetime intrinsic issue. EDIT: turns out , which is used here, calls on its arguments, as if it was certain they were temporaries that didn't outlive the call (which is wrong, in this case). Not sure if needs to create a temporary for the value it's trying to compare with a constant, or the lifetime intrinsic calls shouldn't be emitted for .
This has two parts. When I extended rustc to emit lifetime intrinsics, I chose to put a call to at the end of functions to mark arguments as dead that are passed by value on the stack. This allows LLVM to remove dead stores at the end of functions that it normally can't remove. This works because normally, the caller makes a copy of the value it passes in. Now, the call that match generates to compare the strings is to , which accepts two slices by-value, and slices are large enough to be passed on the stack. Thus, calls to are emitted. But, the call the match generates does not actually make copies of the slices, but passes in pointers to the original slices. That means that the call marks the original alloca as dead, and then things fall apart. Question: Do we want to drop the call for by-value on-stack arguments, or fix the call to eqslice to make copies? cc
just-a-bug; P-high, not 1.0 milestone.


Question: <ins> // Regression test for ICE #130012 // Checks that we do not ICE while reporting // lifetime mistmatch error trait Fun { type Assoc; } trait MyTrait: for<'a> Fun<Assoc = &'a ()> {} //~^ ERROR binding for associated type `Assoc` references lifetime `'a`, which does not appear in the trait input types //~| ERROR binding for associated type `Assoc` references lifetime `'a`, which does not appear in the trait input types //~| ERROR binding for associated type `Assoc` references lifetime `'a`, which does not appear in the trait input types impl<F: for<'b> Fun<Assoc = &'b ()>> MyTrait for F {} //~^ ERROR binding for associated type `Assoc` references lifetime `'b`, which does not appear in the trait input types //~| ERROR mismatched types fn main() {} </ins>

: current nightly <details<summary<strongBacktrace</strong</summary<p</p</details<!-- TRIAGEBOTSTART --<!-- TRIAGEBOTASSIGNSTART --<!-- TRIAGEBOTASSIGNDATASTARTTRIAGEBOTASSIGNDATAEND --<!-- TRIAGEBOTASSIGNEND --<!-- TRIAGEBOTEND --
claim
This did not used to ICE prior to The PR is in nightly-2024-05-12 and Rust 1.80. The diagnostic from nightly-2024-05-11 before that PR is:


Question: <ins> #![allow(unused)] fn main() { let _foo = b'hello0'; //~^ ERROR character literal may only contain one codepoint //~| HELP if you meant to write a byte string literal, use double quotes let _bar = 'hello'; //~^ ERROR character literal may only contain one codepoint //~| HELP if you meant to write a `str` literal, use double quotes } </ins>

When creating a byte literal with single quotes, like , The error message says: But following the directions would not give us a literal, it would give a literal.
This should be easy to fix, by replacing the message with: when the literal is prefixed by . You may need to see work out which call is originating from (in ) to pass information about the nature of the literal.
Can I work on this?
go ahead! :) Let me know if you have any questions.
Hi, I have been doing some work in this issue, cause this is the first time I'm sending PR to rust, can you give me some feedback. Thanks!


Question: pub fn prepend(&mut self, line: usize, string: &str, style: Style) { self.ensure_lines(line); <del> let string_len = string.len(); </del> <ins> let string_len = string.chars().count(); </ins> // Push the old content over to make room for new content for _ in 0..string_len {

Consider the following rust source (in ): It is obvious that it has syntax error. It also seems obvious that the generated error shouldn't depend on name of file except noting the name of file. However, it doesn't happen in practice: Note extra spaces after . Rust version (although it is not strictly relevant, see below): The core issue lies in the . The idea of the code is to make enough space in the beginning of line (filled with spaces) and then overwrite it character by character. However, in order to estimate the required space, which is wrong since in Rust can contain non-ASCII which require more than one byte for storage and keeps lines in s. It means that this isuue reveals itself only when a non-ASCII string is passed as an argument — and that's exactly what's happening in  under very specific circumstances: when processing a primary file, when is set (i. e. ) and when_ name of the primary file happens to have a non-ASCII name. The errorneous code for was introduced in . This bug landed in stable . The obvious fix is to change the line in into cc
I want to raise a question: is it really an issue worth fixing? It fires under very specific circumstances and the fix makes slower for every case, not only these specific ones.
I believe so.


Question: return None; } } <del> Some(i) </del> <ins> if lines.is_empty() { None } else { Some(lines[0][..i].into()) } </ins> } let data_s = data.as_str();

Hi, I've just published a new version of my crate and the generated documentation is including a lot of bullet points that have never been included before. When I generate the docs locally on the 1.8.1 source code using the latest stable version of rust I see the same formatting for the 1.8.0 that generated, so I think this is a bug that's slipped into the nightly version of Stable local docs: ! ! My guess is that it's because I'm using block comments, the unwanted bullet points seem to match up with empty lines in the documentation comment which consist of just a . Thanks
Seems potentially related to , but the latest builds were from a nightly that should have that fixed in; cc you probably have some idea on where the issue is.
This is highly surprising indeed. Can you run with the last nightly locally and tell us if it reproduces the bug please? If so, I'll try to check what's wrong in rustdoc.
I have ran cargo doc on the latest nightly locally (nightly-x86_64-unknown-linux-gnu rustc 1.60.0-nightly ( 2022-01-20)) and it has reproduced the bug - I see the same generated docs as from the build.
It'll be fixed by
Thanks, is there any way I can get to rebuild the docs for the affected versions or will I have to submit a new version of my library to get a fixed build?
Sure. But that for that, please open an issue on repository directly.


Question: <ins> #![feature(prelude_import)] #![no_std] //@ check-pass //@ aux-build: another-proc-macro.rs //@ compile-flags: -Zunpretty=expanded #![feature(derive_smart_pointer)] #[prelude_import] use ::std::prelude::rust_2015::*; #[macro_use] extern crate std; #[macro_use] extern crate another_proc_macro; use another_proc_macro::{pointee, AnotherMacro}; #[repr(transparent)] pub struct Ptr<'a, #[pointee] T: ?Sized> { data: &'a mut T, } #[automatically_derived] impl<'a, T: ?Sized + ::core::marker::Unsize<__S>, __S: ?Sized> ::core::ops::DispatchFromDyn<Ptr<'a, __S>> for Ptr<'a, T> { } #[automatically_derived] impl<'a, T: ?Sized + ::core::marker::Unsize<__S>, __S: ?Sized> ::core::ops::CoerceUnsized<Ptr<'a, __S>> for Ptr<'a, T> { } const _: () = { const POINTEE_MACRO_ATTR_DERIVED: () = (); }; #[pointee] struct MyStruct; const _: () = { const ANOTHER_MACRO_DERIVED: () = (); }; fn main() {} </ins>

https://crater- https://crater- cc <!-- TRIAGEBOTSTART --<!-- TRIAGEBOTASSIGNSTART --<!-- TRIAGEBOTASSIGNDATASTARTTRIAGEBOTASSIGNDATAEND --<!-- TRIAGEBOTASSIGNEND --<!-- TRIAGEBOT_END --
cc
label F-derivesmartpointer
Thread on zulip: https://rust-
claim
WG-prioritization assigning priority (). label -I-prioritize +P-high


Question: <ins> error[E0277]: expected a `std::ops::Fn<()>` closure, found `fn() {foo}` --> $DIR/fn-traits.rs:24:10 | LL | fn call(f: impl Fn()) { |                 ---- required by this bound in `call` ... LL |     call(foo); |          ^^^ expected an `Fn<()>` closure, found `fn() {foo}` | = help: the trait `std::ops::Fn<()>` is not implemented for `fn() {foo}` = note: wrap the `fn() {foo}` in a closure with no arguments: `|| { /* code */ } = note: `#[target_feature]` functions do not implement the `Fn` traits error[E0277]: expected a `std::ops::FnMut<()>` closure, found `fn() {foo}` --> $DIR/fn-traits.rs:25:14 | LL | fn call_mut(f: impl FnMut()) { |                     ------- required by this bound in `call_mut` ... LL |     call_mut(foo); |              ^^^ expected an `FnMut<()>` closure, found `fn() {foo}` | = help: the trait `std::ops::FnMut<()>` is not implemented for `fn() {foo}` = note: wrap the `fn() {foo}` in a closure with no arguments: `|| { /* code */ } = note: `#[target_feature]` functions do not implement the `Fn` traits error[E0277]: expected a `std::ops::FnOnce<()>` closure, found `fn() {foo}` --> $DIR/fn-traits.rs:26:15 | LL | fn call_once(f: impl FnOnce()) { |                      -------- required by this bound in `call_once` ... LL |     call_once(foo); |               ^^^ expected an `FnOnce<()>` closure, found `fn() {foo}` | = help: the trait `std::ops::FnOnce<()>` is not implemented for `fn() {foo}` = note: wrap the `fn() {foo}` in a closure with no arguments: `|| { /* code */ } = note: `#[target_feature]` functions do not implement the `Fn` traits error[E0277]: expected a `std::ops::Fn<()>` closure, found `unsafe fn() {foo_unsafe}` --> $DIR/fn-traits.rs:28:10 | LL | fn call(f: impl Fn()) { |                 ---- required by this bound in `call` ... LL |     call(foo_unsafe); |          ^^^^^^^^^^ expected an `Fn<()>` closure, found `unsafe fn() {foo_unsafe}` | = help: the trait `std::ops::Fn<()>` is not implemented for `unsafe fn() {foo_unsafe}` = note: wrap the `unsafe fn() {foo_unsafe}` in a closure with no arguments: `|| { /* code */ } = note: `#[target_feature]` functions do not implement the `Fn` traits error[E0277]: expected a `std::ops::FnMut<()>` closure, found `unsafe fn() {foo_unsafe}` --> $DIR/fn-traits.rs:30:14 | LL | fn call_mut(f: impl FnMut()) { |                     ------- required by this bound in `call_mut` ... LL |     call_mut(foo_unsafe); |              ^^^^^^^^^^ expected an `FnMut<()>` closure, found `unsafe fn() {foo_unsafe}` | = help: the trait `std::ops::FnMut<()>` is not implemented for `unsafe fn() {foo_unsafe}` = note: wrap the `unsafe fn() {foo_unsafe}` in a closure with no arguments: `|| { /* code */ } = note: `#[target_feature]` functions do not implement the `Fn` traits error[E0277]: expected a `std::ops::FnOnce<()>` closure, found `unsafe fn() {foo_unsafe}` --> $DIR/fn-traits.rs:32:15 | LL | fn call_once(f: impl FnOnce()) { |                      -------- required by this bound in `call_once` ... LL |     call_once(foo_unsafe); |               ^^^^^^^^^^ expected an `FnOnce<()>` closure, found `unsafe fn() {foo_unsafe}` | = help: the trait `std::ops::FnOnce<()>` is not implemented for `unsafe fn() {foo_unsafe}` = note: wrap the `unsafe fn() {foo_unsafe}` in a closure with no arguments: `|| { /* code */ } = note: `#[target_feature]` functions do not implement the `Fn` traits error: aborting due to 6 previous errors For more information about this error, try `rustc --explain E0277`. </ins>

(Moved from with an PoC.) The following program () demonstrates how current implementation of allows using a targetfeature from safe code without ensuring it's actually available: This is unsound because it allows executing (e.g.) AVX instructions on CPUs that do not implement them, which is UB. It only works because "safe fns with targetfeatures" are erroneously considered to implement the FnOnce/FnMut/Fn traits.
Proposed resolution: It would probably be better to add the to functions implicitly during lowering to HIR. Implicit is already to functions in blocks in the same way. That would make the unsafety checker the only place (besides AST lowering) where they would be treated specially. Like, "yes, the function is unsafe, but we know that it's safe to call in this specific context, so the unsafe block can be omitted". The special coercion checks would no longer be necessary in that case.
Or perhaps the RFC should be revised to still require writing and only unsafety checker need to be enhanced to allow calling unsafe functions without unsafe blocks in the situations known to be safe. That would be even less magic, which is good, IMO. The ergonomic regression would be minor because would still be not required at call sites.
Good catch. Another option is that referencing, not calling, a target-feature function is unsafe, but I guess that is a bit inconsistent given that this already compiles (): In other words, accessing a safe target feature 1.1 function would be unsafe (unless the target feature is in scope), not just calling it. I think that this is what I would expect somehow, but I would expect it to apply uniformly to both safe and unsafe target feature functions (and that's not an option). The downside of this approach is that we don't know why the function was declared unsafe, not really -- was it only because of the target feature? Or were there additional requirements that would have made it unsafe?
For example, a whole bunch of functions take raw pointers and use them to access memory, so they need to be unsafe even if the respective target feature is enabled in the calling context.
The downside of this approach is that we don't know why the function was declared unsafe, not really -- was it only because of the target feature? Or were there additional requirements that would have made it unsafe? Ah, right, that's a good argument.


Question: <ins> error[E0277]: expected a `std::ops::Fn<()>` closure, found `fn() {foo}` --> $DIR/fn-traits.rs:24:10 | LL | fn call(f: impl Fn()) { |                 ---- required by this bound in `call` ... LL |     call(foo); |          ^^^ expected an `Fn<()>` closure, found `fn() {foo}` | = help: the trait `std::ops::Fn<()>` is not implemented for `fn() {foo}` = note: wrap the `fn() {foo}` in a closure with no arguments: `|| { /* code */ } = note: `#[target_feature]` functions do not implement the `Fn` traits error[E0277]: expected a `std::ops::FnMut<()>` closure, found `fn() {foo}` --> $DIR/fn-traits.rs:25:14 | LL | fn call_mut(f: impl FnMut()) { |                     ------- required by this bound in `call_mut` ... LL |     call_mut(foo); |              ^^^ expected an `FnMut<()>` closure, found `fn() {foo}` | = help: the trait `std::ops::FnMut<()>` is not implemented for `fn() {foo}` = note: wrap the `fn() {foo}` in a closure with no arguments: `|| { /* code */ } = note: `#[target_feature]` functions do not implement the `Fn` traits error[E0277]: expected a `std::ops::FnOnce<()>` closure, found `fn() {foo}` --> $DIR/fn-traits.rs:26:15 | LL | fn call_once(f: impl FnOnce()) { |                      -------- required by this bound in `call_once` ... LL |     call_once(foo); |               ^^^ expected an `FnOnce<()>` closure, found `fn() {foo}` | = help: the trait `std::ops::FnOnce<()>` is not implemented for `fn() {foo}` = note: wrap the `fn() {foo}` in a closure with no arguments: `|| { /* code */ } = note: `#[target_feature]` functions do not implement the `Fn` traits error[E0277]: expected a `std::ops::Fn<()>` closure, found `unsafe fn() {foo_unsafe}` --> $DIR/fn-traits.rs:28:10 | LL | fn call(f: impl Fn()) { |                 ---- required by this bound in `call` ... LL |     call(foo_unsafe); |          ^^^^^^^^^^ expected an `Fn<()>` closure, found `unsafe fn() {foo_unsafe}` | = help: the trait `std::ops::Fn<()>` is not implemented for `unsafe fn() {foo_unsafe}` = note: wrap the `unsafe fn() {foo_unsafe}` in a closure with no arguments: `|| { /* code */ } = note: `#[target_feature]` functions do not implement the `Fn` traits error[E0277]: expected a `std::ops::FnMut<()>` closure, found `unsafe fn() {foo_unsafe}` --> $DIR/fn-traits.rs:30:14 | LL | fn call_mut(f: impl FnMut()) { |                     ------- required by this bound in `call_mut` ... LL |     call_mut(foo_unsafe); |              ^^^^^^^^^^ expected an `FnMut<()>` closure, found `unsafe fn() {foo_unsafe}` | = help: the trait `std::ops::FnMut<()>` is not implemented for `unsafe fn() {foo_unsafe}` = note: wrap the `unsafe fn() {foo_unsafe}` in a closure with no arguments: `|| { /* code */ } = note: `#[target_feature]` functions do not implement the `Fn` traits error[E0277]: expected a `std::ops::FnOnce<()>` closure, found `unsafe fn() {foo_unsafe}` --> $DIR/fn-traits.rs:32:15 | LL | fn call_once(f: impl FnOnce()) { |                      -------- required by this bound in `call_once` ... LL |     call_once(foo_unsafe); |               ^^^^^^^^^^ expected an `FnOnce<()>` closure, found `unsafe fn() {foo_unsafe}` | = help: the trait `std::ops::FnOnce<()>` is not implemented for `unsafe fn() {foo_unsafe}` = note: wrap the `unsafe fn() {foo_unsafe}` in a closure with no arguments: `|| { /* code */ } = note: `#[target_feature]` functions do not implement the `Fn` traits error: aborting due to 6 previous errors For more information about this error, try `rustc --explain E0277`. </ins>

The "unsafety checker enhancement" should only affect unsafe functions that are made unsafe through , but not other means (explicit or block).
I'm new to the compiler, but it seemed non-trivial to change functions to unsafe when lowering to HIR. It looks to me like you'd end up with just as many exceptions (ignoring the unsafe in some scenarios), in addition tracking the origin of the . On the other hand it was pretty trivial to not implement the Fn traits instead. I'll open a PR with my fix, but please close it if the other method is preferred.
So there are sort of two questions here. One of them is what user-visible behavior is permitted, and the other is how precisely that is implemented in the compiler. I think the latter only concerns the team in-so-far as it represents the impact on the language spec and on the "mental model" that users must keep in mind. Nominating for the lang-team meeting to discuss solution, which is simply to make "target-feature" functions not implement the traits (just as functions do not implement the traits, along with functions with non-Rust ABIs etc).
I think I was tagged by mistake :wink:
Apologies! Fixed. Github autocompleted that for me. Here I was feeling so grateful to it.
We discussed this in . The conclusion was that the fix that proposed is good for right now. It's already the case that many items do not implement the function trait (e.g., those with custom ABIs or which are unsafe), so this would be "yet another" case (admittedly a somewhat different one in that this case doesn't show up in the type of function pointers, for better or worse). We uncovered a few other interesting observations: pointed out that their ideal preference would be to have something like " only implements " within an unsafe block, but of course the trait system has no precedent for that. It would be sort of like a attached to the impl block (i.e., the impl is usable in some context where unsafe code is allowed). Another possibility might be " only implements within a fn that has a suitable target feature in scope". This is an interesting idea because, although the pointer could escape in the form of a , the idea is that once you enter , you've already demonstrated that you possess the target feature, so it's ok if the value escapes.


Question: <ins> error[E0277]: expected a `std::ops::Fn<()>` closure, found `fn() {foo}` --> $DIR/fn-traits.rs:24:10 | LL | fn call(f: impl Fn()) { |                 ---- required by this bound in `call` ... LL |     call(foo); |          ^^^ expected an `Fn<()>` closure, found `fn() {foo}` | = help: the trait `std::ops::Fn<()>` is not implemented for `fn() {foo}` = note: wrap the `fn() {foo}` in a closure with no arguments: `|| { /* code */ } = note: `#[target_feature]` functions do not implement the `Fn` traits error[E0277]: expected a `std::ops::FnMut<()>` closure, found `fn() {foo}` --> $DIR/fn-traits.rs:25:14 | LL | fn call_mut(f: impl FnMut()) { |                     ------- required by this bound in `call_mut` ... LL |     call_mut(foo); |              ^^^ expected an `FnMut<()>` closure, found `fn() {foo}` | = help: the trait `std::ops::FnMut<()>` is not implemented for `fn() {foo}` = note: wrap the `fn() {foo}` in a closure with no arguments: `|| { /* code */ } = note: `#[target_feature]` functions do not implement the `Fn` traits error[E0277]: expected a `std::ops::FnOnce<()>` closure, found `fn() {foo}` --> $DIR/fn-traits.rs:26:15 | LL | fn call_once(f: impl FnOnce()) { |                      -------- required by this bound in `call_once` ... LL |     call_once(foo); |               ^^^ expected an `FnOnce<()>` closure, found `fn() {foo}` | = help: the trait `std::ops::FnOnce<()>` is not implemented for `fn() {foo}` = note: wrap the `fn() {foo}` in a closure with no arguments: `|| { /* code */ } = note: `#[target_feature]` functions do not implement the `Fn` traits error[E0277]: expected a `std::ops::Fn<()>` closure, found `unsafe fn() {foo_unsafe}` --> $DIR/fn-traits.rs:28:10 | LL | fn call(f: impl Fn()) { |                 ---- required by this bound in `call` ... LL |     call(foo_unsafe); |          ^^^^^^^^^^ expected an `Fn<()>` closure, found `unsafe fn() {foo_unsafe}` | = help: the trait `std::ops::Fn<()>` is not implemented for `unsafe fn() {foo_unsafe}` = note: wrap the `unsafe fn() {foo_unsafe}` in a closure with no arguments: `|| { /* code */ } = note: `#[target_feature]` functions do not implement the `Fn` traits error[E0277]: expected a `std::ops::FnMut<()>` closure, found `unsafe fn() {foo_unsafe}` --> $DIR/fn-traits.rs:30:14 | LL | fn call_mut(f: impl FnMut()) { |                     ------- required by this bound in `call_mut` ... LL |     call_mut(foo_unsafe); |              ^^^^^^^^^^ expected an `FnMut<()>` closure, found `unsafe fn() {foo_unsafe}` | = help: the trait `std::ops::FnMut<()>` is not implemented for `unsafe fn() {foo_unsafe}` = note: wrap the `unsafe fn() {foo_unsafe}` in a closure with no arguments: `|| { /* code */ } = note: `#[target_feature]` functions do not implement the `Fn` traits error[E0277]: expected a `std::ops::FnOnce<()>` closure, found `unsafe fn() {foo_unsafe}` --> $DIR/fn-traits.rs:32:15 | LL | fn call_once(f: impl FnOnce()) { |                      -------- required by this bound in `call_once` ... LL |     call_once(foo_unsafe); |               ^^^^^^^^^^ expected an `FnOnce<()>` closure, found `unsafe fn() {foo_unsafe}` | = help: the trait `std::ops::FnOnce<()>` is not implemented for `unsafe fn() {foo_unsafe}` = note: wrap the `unsafe fn() {foo_unsafe}` in a closure with no arguments: `|| { /* code */ } = note: `#[target_feature]` functions do not implement the `Fn` traits error: aborting due to 6 previous errors For more information about this error, try `rustc --explain E0277`. </ins>

(This would be like a clause.) To that end, I was thinking that you could model this last point using closures, but I found that it doesn't work. In particular, gives an error, even if it appears inside of a function annotated with . This seems like something we should discuss, but it's a separate issue. We did discuss briefly whether target features should be part of the type of function pointers but I think the conclusion was 'well maybe but there is already stable code out there that lets you coerce, and it seems rather niche'. We also discussed whether we should permit one to annotate closures with target-feature annotations. In any case, those are things we can address separately from this issue, but the point is that adopting more accepting solutions can be done in the future.
I'm going to move the question of whether closures should "inherit" target features to the main tracking issue -- I'm not sure if that is something that was discussed as part of the original RFC discussion?
Opened to discuss closures.


Question: } alloc0 (static: FOO, size: 8, align: 8) { <del> ╾───────alloc3────────╼                         │ ╾──────╼ </del> <ins> ╾───────alloc9────────╼                         │ ╾──────╼ </ins> } <del> alloc3 (size: 180, align: 1) { </del> <ins> alloc9 (size: 180, align: 1) { </ins> 0x00 │ ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab │ ................ 0x10 │ ab ab ab ab ab ab ab ab ab ab ab ab ╾──alloc4── │ ............╾─── 0x20 │ ──────────╼ 01 ef cd ab 00 00 00 00 00 00 00 00 │ ───╼............

This little matrix processing code shows miscompilation if compiled with: But it compiles correctly with: With:
Hmm, on first glance the replacement it performs looks correct: It replaces local () with the return place . Previously the return place was only used in the block by a copy statement, so this transformation should be correct. Maybe this part of the diff causes a problem? We're using our return place as the return destination for the inner call. It's possible that there's an undocumented (and maybe unintended) assumption that the return place is not reused like this, either in other MIR optimizations or in codegen. Haven't looked too deeply yet though.
Ah, there's this bad call in LLVM IR: It passes a reference to as both the return pointer, and the second (immutable) argument.
we have this corresponding MIR, where we already incorrectly pass a reference to as an argument, while also assigning the call result to it: The destination propagation pass already has code that prevents merging a call destination with one of the arguments, but here we pass a reference instead. Curiously, the was produced by merging with , which is not really something the pass should be doing since a reference is taken to , and we avoid touching any local that has its address taken.
The propagation is disabled only if both locals have their address taken. Changing that to be more conservative does fix this issue. Minimized:
Oof, this should have been an , not an
Thanks
Opened with a fix


Question: <del> use crate::spec::{LinkerFlavor, PanicStrategy, Target, TargetResult}; </del> <ins> use crate::spec::{LinkerFlavor, Target, TargetResult}; </ins> pub fn target() -> TargetResult { let mut base = super::windows_uwp_msvc_base::opts(); base.max_atomic_width = Some(64); base.has_elf_tls = true; <del> // FIXME: this shouldn't be panic=abort, it should be panic=unwind base.panic_strategy = PanicStrategy::Abort; </del> Ok(Target { llvm_target: "aarch64-pc-windows-msvc".to_string(), target_endian: "little".to_string(),

The and targets force panic=abort. said : This looks related to , which points at these now-merged LLVM changes: It's possible that we do not need to force panic=abort anymore for arm64 Windows as a result. What's the best way to determine if this is still required?
Theoretically the way to fix this issue is: First Next, run the build Finally, run the tests If that all passes, then the change can land! The tests may be able to be skipped, and some tests may fail, so it may be possible to switch the defaults and we can fixup bugs later. I don't personally have hardware to test these changes on myself, though.
I occasionally have access to a Windows arm64 laptop, so I'll try and get a rustc build going on it at some point.


Question: COPY host-x86_64/dist-x86_64-linux/build-binutils.sh /tmp/ RUN ./build-binutils.sh <del> # libssh2 (a dependency of Cargo) requires cmake 2.8.11 or higher but CentOS # only has 2.6.4, so build our own COPY host-x86_64/dist-x86_64-linux/build-cmake.sh /tmp/ RUN ./build-cmake.sh # Need a newer version of gcc than centos has to compile LLVM nowadays </del> <ins> # Need at least GCC 5.1 to compile LLVM nowadays </ins> COPY host-x86_64/dist-x86_64-linux/build-gcc.sh /tmp/ <del> RUN ./build-gcc.sh </del> <ins> RUN ./build-gcc.sh && apt-get remove -y gcc g++ </ins> <del> # CentOS 5.5 has Python 2.4 by default, but LLVM needs 2.7+ </del> <ins> # Debian 6 has Python 2.6 by default, but LLVM needs 2.7+ </ins> COPY host-x86_64/dist-x86_64-linux/build-python.sh /tmp/ RUN ./build-python.sh <del> # Now build LLVM+Clang 7, afterwards configuring further compilations to use the </del> <ins> # LLVM needs cmake 3.4.3 or higher, and is planning to raise to 3.13.4. COPY host-x86_64/dist-x86_64-linux/build-cmake.sh /tmp/ RUN ./build-cmake.sh # Now build LLVM+Clang, afterwards configuring further compilations to use the </ins> # clang/clang++ compilers. <del> COPY host-x86_64/dist-x86_64-linux/build-clang.sh host-x86_64/dist-x86_64-linux/llvm-project-centos.patch /tmp/ </del> <ins> COPY host-x86_64/dist-x86_64-linux/build-clang.sh /tmp/ </ins> RUN ./build-clang.sh ENV CC=clang CXX=clang++ <del> # Apparently CentOS 5.5 desn't have `git` in yum, but we're gonna need it for # cloning, so download and build it here. COPY host-x86_64/dist-x86_64-linux/build-git.sh /tmp/ RUN ./build-git.sh # for sanitizers, we need kernel headers files newer than the ones CentOS ships # with so we install newer ones here COPY host-x86_64/dist-x86_64-linux/build-headers.sh /tmp/ RUN ./build-headers.sh # OpenSSL requires a more recent version of perl # with so we install newer ones here COPY host-x86_64/dist-x86_64-linux/build-perl.sh /tmp/ RUN ./build-perl.sh </del> COPY scripts/sccache.sh /scripts/ RUN sh /scripts/sccache.sh

The docs currently claim that the minimum Linux kernel version for is 2.6.18 (released September 20th, 2006). This is because RHEL 5 used that kernel version. However, RHEL 5 entered ELS on March 31, 2017. Should we continue to support RHEL 5 for , or should we increase the minimum Linux Kernel version to 2.6.27 (2nd LTS) or 2.6.32 (RHEL 6, 3rd LTS)? . Even bumping the min-version to 2.6.27 would allow us to remove most of the Linux-specific hacks in . Example: .
Given that RHEL is the only reason we keep comically old kernel versions around, I would propose that Rust only support RHEL until Maintenance Support ends. This is (essentially) what we already did for RHEL 4. Rust never supported RHEL 4, and back when RHEL 5 still had maintenance support. It would be nice to get someone from Red Hat or a RHEL customer to comment. This policy would allow us to increment the minimum kernel from 2.6.18 to 2.6.32 (and remove a lot of Linux-specific hacks). Note that RHEL has a weird (and very long) support system for RHEL 4, 5, and 6 (sources: and ). It has 5 major steps: Full Support Normal support of the OS Maintenance Support 1 Bug/Security fixes Limited hardware refresh Maintenance Support 2 Bug/Security fixes The end of this phase is considered "Product retirement" Extended Life Cycle Support (ELS) Additional paid product from Red Hat Gives updates for a longer period of time No additional releases/images Extended Life Phase (ELP) No more updates Limited Technical Support End date not given by Red Hat Current status of RHEL versions: RHEL 4 Not supported by Rust Currently in ELP (no end date specified) ELS ended March 31, 2017 RHEL 5 Supported by Rust Currently in ELS Maintenance Support ended March 31, 2017 ELS ends November 30, 2020 RHEL 6 Supported by Rust Currently in Maintenance Support 2 Maintenance Support ends November 30, 2020
cc which I think they could be related.


Question: COPY host-x86_64/dist-x86_64-linux/build-binutils.sh /tmp/ RUN ./build-binutils.sh <del> # libssh2 (a dependency of Cargo) requires cmake 2.8.11 or higher but CentOS # only has 2.6.4, so build our own COPY host-x86_64/dist-x86_64-linux/build-cmake.sh /tmp/ RUN ./build-cmake.sh # Need a newer version of gcc than centos has to compile LLVM nowadays </del> <ins> # Need at least GCC 5.1 to compile LLVM nowadays </ins> COPY host-x86_64/dist-x86_64-linux/build-gcc.sh /tmp/ <del> RUN ./build-gcc.sh </del> <ins> RUN ./build-gcc.sh && apt-get remove -y gcc g++ </ins> <del> # CentOS 5.5 has Python 2.4 by default, but LLVM needs 2.7+ </del> <ins> # Debian 6 has Python 2.6 by default, but LLVM needs 2.7+ </ins> COPY host-x86_64/dist-x86_64-linux/build-python.sh /tmp/ RUN ./build-python.sh <del> # Now build LLVM+Clang 7, afterwards configuring further compilations to use the </del> <ins> # LLVM needs cmake 3.4.3 or higher, and is planning to raise to 3.13.4. COPY host-x86_64/dist-x86_64-linux/build-cmake.sh /tmp/ RUN ./build-cmake.sh # Now build LLVM+Clang, afterwards configuring further compilations to use the </ins> # clang/clang++ compilers. <del> COPY host-x86_64/dist-x86_64-linux/build-clang.sh host-x86_64/dist-x86_64-linux/llvm-project-centos.patch /tmp/ </del> <ins> COPY host-x86_64/dist-x86_64-linux/build-clang.sh /tmp/ </ins> RUN ./build-clang.sh ENV CC=clang CXX=clang++ <del> # Apparently CentOS 5.5 desn't have `git` in yum, but we're gonna need it for # cloning, so download and build it here. COPY host-x86_64/dist-x86_64-linux/build-git.sh /tmp/ RUN ./build-git.sh # for sanitizers, we need kernel headers files newer than the ones CentOS ships # with so we install newer ones here COPY host-x86_64/dist-x86_64-linux/build-headers.sh /tmp/ RUN ./build-headers.sh # OpenSSL requires a more recent version of perl # with so we install newer ones here COPY host-x86_64/dist-x86_64-linux/build-perl.sh /tmp/ RUN ./build-perl.sh </del> COPY scripts/sccache.sh /scripts/ RUN sh /scripts/sccache.sh

It also may be worth to drop support of Windows XP and Vista (especially considering that panics are broken on XP since June 2016, see: ). Previously it was discussed . cc
Besides , which other things would we be able to clean up?
Good question! There are 6 workarounds in for older Linux versions (that I could find). Increasing the minimum version to 2.6.32 (aka 3rd Kernel LTS, aka RHEL 6) would fix 5 of them. Code links are inline: (mentioned above, in 2.6.23) ( in 2.6.24, there is also the mention of a bug occuring on "some linux kernel at some point") to atomically set the flag on the pipe fds ( in 2.6.27) ( in 2.6.27) to permit use of ( in 2.6.28) ( in 4.5, not fixed by this proposal) As you can see, the workarounds fixed by this proposal all have a similar flavor.
I am Red Hat's maintainer for Rust on RHEL -- thanks for the cc. I try to keep an eye on new issues, but this one slipped past me. Red Hat only ships the Rust toolchain to customers for RHEL 7 and RHEL 8. If our customers would like to use Rust on older RHEL, they can do so via , and we'll support them in the same way we would for any other third-party software. Internally we do also build and use on RHEL 6, mostly because it's needed to ship Firefox updates. This is where it gets a little hairy, because each RHEL N is bootstrapped on RHEL N-1 and often kept that way -- meaning a lot of our RHEL 6 builders are still running on a RHEL 5 kernel. I would have to apply local workarounds if upstream Rust loses the ability to run on 2.6.18 kernels. We prefer to keep fixes upstream as much as possible, both for open sharing and to avoid bitrot. Is there much of a benefit to removing the current workarounds? I agree all of that code is annoying and cumbersome, but it's already written, and as far as I know it hasn't been a maintenance burden. Do you see otherwise?


Question: COPY host-x86_64/dist-x86_64-linux/build-binutils.sh /tmp/ RUN ./build-binutils.sh <del> # libssh2 (a dependency of Cargo) requires cmake 2.8.11 or higher but CentOS # only has 2.6.4, so build our own COPY host-x86_64/dist-x86_64-linux/build-cmake.sh /tmp/ RUN ./build-cmake.sh # Need a newer version of gcc than centos has to compile LLVM nowadays </del> <ins> # Need at least GCC 5.1 to compile LLVM nowadays </ins> COPY host-x86_64/dist-x86_64-linux/build-gcc.sh /tmp/ <del> RUN ./build-gcc.sh </del> <ins> RUN ./build-gcc.sh && apt-get remove -y gcc g++ </ins> <del> # CentOS 5.5 has Python 2.4 by default, but LLVM needs 2.7+ </del> <ins> # Debian 6 has Python 2.6 by default, but LLVM needs 2.7+ </ins> COPY host-x86_64/dist-x86_64-linux/build-python.sh /tmp/ RUN ./build-python.sh <del> # Now build LLVM+Clang 7, afterwards configuring further compilations to use the </del> <ins> # LLVM needs cmake 3.4.3 or higher, and is planning to raise to 3.13.4. COPY host-x86_64/dist-x86_64-linux/build-cmake.sh /tmp/ RUN ./build-cmake.sh # Now build LLVM+Clang, afterwards configuring further compilations to use the </ins> # clang/clang++ compilers. <del> COPY host-x86_64/dist-x86_64-linux/build-clang.sh host-x86_64/dist-x86_64-linux/llvm-project-centos.patch /tmp/ </del> <ins> COPY host-x86_64/dist-x86_64-linux/build-clang.sh /tmp/ </ins> RUN ./build-clang.sh ENV CC=clang CXX=clang++ <del> # Apparently CentOS 5.5 desn't have `git` in yum, but we're gonna need it for # cloning, so download and build it here. COPY host-x86_64/dist-x86_64-linux/build-git.sh /tmp/ RUN ./build-git.sh # for sanitizers, we need kernel headers files newer than the ones CentOS ships # with so we install newer ones here COPY host-x86_64/dist-x86_64-linux/build-headers.sh /tmp/ RUN ./build-headers.sh # OpenSSL requires a more recent version of perl # with so we install newer ones here COPY host-x86_64/dist-x86_64-linux/build-perl.sh /tmp/ RUN ./build-perl.sh </del> COPY scripts/sccache.sh /scripts/ RUN sh /scripts/sccache.sh

If there are any known issues with such Linux compatibility code, I am definitely willing to take assignment for fixing them.
(Not a Rust maintainer, but I'm a Rust user and maintain a lot of other OSS software so I have well-developed feelings around supporting Very Old Software :-)) Do you have any sense of on what timeline Rust would be able to drop support for 2.6.18 kernels without causing you pain? In general I don't think people mind supporting configurations that have users and are painful to work around, but needing to support them for forever is a bitter pill to swallow! Particularly as they get harder and harder to test over time (already I have no idea how to test on really old kernels besides building it myself). So if there was an estimate "we'd like to be able to support this until Q2 2020", even if it's not set in stone, I think that would be very helpful!
The other benefit would be that Rust wouldn't have to use CentOS 5 for CI, which means we don't have to patch LLVM (and Emscripten LLVM) to compile on those systems. Of course that's also fairly limited in scope.
As soon as we stop shipping Firefox and Thunderbird updates on RHEL 6, I won't need Rust there anymore. AFAIK this does correspond to the end of Maintenance Support, November 30, 2020. Then I'll be on to RHEL 7 builders as my minimum, probably still with some RHEL 6 2.6.32 kernels involved. It should be fine for me if we update CI to CentOS 6. This is mostly concerned with how the release binaries are linked to GLIBC symbol versions, which is all in userspace. It's a broader community question whether any other Rust users still care about running on RHEL or CentOS 5. (Small caveat - glibc support for a symbol can still return , .)
I noticed that Red Hat also has Extended Life Cycle Support (ELS) for RHEL 6 until June 30, 2024. Will you need RHEL 5 to work during this time? I don't know how ELS works with Firefox updates. Also, is there any reason RHEL 4 support wasn't an issue prior to March 31, 2017 (while RHEL 5 was still normally supported)?


Question: COPY host-x86_64/dist-x86_64-linux/build-binutils.sh /tmp/ RUN ./build-binutils.sh <del> # libssh2 (a dependency of Cargo) requires cmake 2.8.11 or higher but CentOS # only has 2.6.4, so build our own COPY host-x86_64/dist-x86_64-linux/build-cmake.sh /tmp/ RUN ./build-cmake.sh # Need a newer version of gcc than centos has to compile LLVM nowadays </del> <ins> # Need at least GCC 5.1 to compile LLVM nowadays </ins> COPY host-x86_64/dist-x86_64-linux/build-gcc.sh /tmp/ <del> RUN ./build-gcc.sh </del> <ins> RUN ./build-gcc.sh && apt-get remove -y gcc g++ </ins> <del> # CentOS 5.5 has Python 2.4 by default, but LLVM needs 2.7+ </del> <ins> # Debian 6 has Python 2.6 by default, but LLVM needs 2.7+ </ins> COPY host-x86_64/dist-x86_64-linux/build-python.sh /tmp/ RUN ./build-python.sh <del> # Now build LLVM+Clang 7, afterwards configuring further compilations to use the </del> <ins> # LLVM needs cmake 3.4.3 or higher, and is planning to raise to 3.13.4. COPY host-x86_64/dist-x86_64-linux/build-cmake.sh /tmp/ RUN ./build-cmake.sh # Now build LLVM+Clang, afterwards configuring further compilations to use the </ins> # clang/clang++ compilers. <del> COPY host-x86_64/dist-x86_64-linux/build-clang.sh host-x86_64/dist-x86_64-linux/llvm-project-centos.patch /tmp/ </del> <ins> COPY host-x86_64/dist-x86_64-linux/build-clang.sh /tmp/ </ins> RUN ./build-clang.sh ENV CC=clang CXX=clang++ <del> # Apparently CentOS 5.5 desn't have `git` in yum, but we're gonna need it for # cloning, so download and build it here. COPY host-x86_64/dist-x86_64-linux/build-git.sh /tmp/ RUN ./build-git.sh # for sanitizers, we need kernel headers files newer than the ones CentOS ships # with so we install newer ones here COPY host-x86_64/dist-x86_64-linux/build-headers.sh /tmp/ RUN ./build-headers.sh # OpenSSL requires a more recent version of perl # with so we install newer ones here COPY host-x86_64/dist-x86_64-linux/build-perl.sh /tmp/ RUN ./build-perl.sh </del> COPY scripts/sccache.sh /scripts/ RUN sh /scripts/sccache.sh

This issue came up for me when dealing with opening files in , see for more info. No single RHEL 5 issue is that bad, it's mainly just the sum of a bunch of tiny issues.
I'm not on that team, but AFAIK we don't ship Firefox updates for ELS. The last build I see for RHEL 5 was . Maybe there could be an exception for a severe security issue, but I really doubt we'd rebase to newer Firefox for that, which means Rust requirements won't change. New Firefox ESR versions do require a newer Rust toolchain too, which is generally why we have to keep up. Otherwise we could just freeze some older compat rustc while upstream moves on. Rust wasn't required until Firefox 52.
that makes perfect sense to me, thanks for clarifying. So the proposed policy would be: Support RHEL until RHEL is retired (i.e. ends normal support). This would mean: Supporting RHEL 5 (v2.6.18) until November 30, 2020 Supporting RHEL 6 (v2.6.32) until June 30, 2024 Supporting RHEL 7 (v3.10) until May, 2029 This is a much longer support tail than any other Linux distros (that I know of), so it would also be the effective minimum kernel version of , , and their dependencies. How does that sound to people? EDIT: The alternative to this would be Support RHEL until it is retired, taking the table above and incrementing the RHEL versions by 1. It would also mean being able to drop RHEL 5 support now.
Sure, that's ideal for me. :smile: As mentioned, is just needed for kernel support, as far as I'm concerned. Userspace concerns like GLIBC symbol versions can stick to currently supported for my needs.
Red Hat should backport the features we need (CLOEXEC and getrandom, in particular) to whatever kernels that it wants Rust to support. I don't know any good reason why they don't do so, other than it's cheaper to convince the whole world to keep supporting older kernel versions than it is to do the backports. We should change that dynamic.


Question: COPY host-x86_64/dist-x86_64-linux/build-binutils.sh /tmp/ RUN ./build-binutils.sh <del> # libssh2 (a dependency of Cargo) requires cmake 2.8.11 or higher but CentOS # only has 2.6.4, so build our own COPY host-x86_64/dist-x86_64-linux/build-cmake.sh /tmp/ RUN ./build-cmake.sh # Need a newer version of gcc than centos has to compile LLVM nowadays </del> <ins> # Need at least GCC 5.1 to compile LLVM nowadays </ins> COPY host-x86_64/dist-x86_64-linux/build-gcc.sh /tmp/ <del> RUN ./build-gcc.sh </del> <ins> RUN ./build-gcc.sh && apt-get remove -y gcc g++ </ins> <del> # CentOS 5.5 has Python 2.4 by default, but LLVM needs 2.7+ </del> <ins> # Debian 6 has Python 2.6 by default, but LLVM needs 2.7+ </ins> COPY host-x86_64/dist-x86_64-linux/build-python.sh /tmp/ RUN ./build-python.sh <del> # Now build LLVM+Clang 7, afterwards configuring further compilations to use the </del> <ins> # LLVM needs cmake 3.4.3 or higher, and is planning to raise to 3.13.4. COPY host-x86_64/dist-x86_64-linux/build-cmake.sh /tmp/ RUN ./build-cmake.sh # Now build LLVM+Clang, afterwards configuring further compilations to use the </ins> # clang/clang++ compilers. <del> COPY host-x86_64/dist-x86_64-linux/build-clang.sh host-x86_64/dist-x86_64-linux/llvm-project-centos.patch /tmp/ </del> <ins> COPY host-x86_64/dist-x86_64-linux/build-clang.sh /tmp/ </ins> RUN ./build-clang.sh ENV CC=clang CXX=clang++ <del> # Apparently CentOS 5.5 desn't have `git` in yum, but we're gonna need it for # cloning, so download and build it here. COPY host-x86_64/dist-x86_64-linux/build-git.sh /tmp/ RUN ./build-git.sh # for sanitizers, we need kernel headers files newer than the ones CentOS ships # with so we install newer ones here COPY host-x86_64/dist-x86_64-linux/build-headers.sh /tmp/ RUN ./build-headers.sh # OpenSSL requires a more recent version of perl # with so we install newer ones here COPY host-x86_64/dist-x86_64-linux/build-perl.sh /tmp/ RUN ./build-perl.sh </del> COPY scripts/sccache.sh /scripts/ RUN sh /scripts/sccache.sh

The alternative to this would be Support RHEL N until it is retired I think we should not officially support retired OS versions (well, maybe with some grace period), so I prefer this option. Supporting 14 year old kernels seems a bit too much to me. each RHEL N is bootstrapped on RHEL N-1 and often kept that way -- meaning a lot of our RHEL 6 builders are still running on a RHEL 5 kernel Is it possible to apply kernel patches to those builders to add support for functionality like CLOEXEC? Or build Firefox separately on RHEL 6?
I think it won't be fruitful for us to debate the merits of stable enterprise kernels, but no, this is not a possibility. An alternative take is "Red Hat should be responsible for the work in Rust to maintain the support they want" -- here I am, ready and willing. I definitely can't change those kernels. The ones that are stuck on N-1 are precisely to avoid rocking the boat, and backporting features is a big change. Some of our arches do eventually update the builders to their matching N kernel, but some don't, and I don't know all the reasons. If the workarounds are removed, then I will have to reapply them in our own builds. This assumes I keep using only our own binaries -- if I ever have to re-bootstrap from upstream binaries for stage0, I would also have to use some interception (like ) to hack in the workarounds. This is all possible, but much worse than the status quo of maintaining support here.
I think this sounds reasonable. We aren't going to be able to update these kernels, it's just a question of when we would drop support for these OSes. Especially given that we wont need RHEL 5 CI support, I think that leaving things as they are is fine. I opened this issue to verify two things: 1) That we weren't intending on supporting RHEL 5 "forever", and had a clear date when to drop support. 2) That someone from Red Hat actively cared about supporting these older kernels. It looks like both these things are true. We should remove RHEL 5 workarounds after November 30, 2020. This issue can be postponed until then.