Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

String::push_str invalidates interior references even when it does not reallocate #70301

Closed
matklad opened this issue Mar 23, 2020 · 5 comments · Fixed by #70558
Closed

String::push_str invalidates interior references even when it does not reallocate #70301

matklad opened this issue Mar 23, 2020 · 5 comments · Fixed by #70558
Labels
A-collections Area: `std::collection` C-bug Category: This is a bug. T-libs-api Relevant to the library API team, which will review and decide on the PR/issue.

Comments

@matklad
Copy link
Member

matklad commented Mar 23, 2020

To my knowledge, the following code is intended to be legal:

fn main() {
    let mut buf = String::with_capacity(11);
    buf.push_str("hello");
    let hello: &str = unsafe { &*(buf.as_str() as *const _) }; // laundering the lifetime -- we take care that `buf` does not reallocate, so that's okay.
    buf.push_str(" world");
    println!("{}", hello);
}

However, Miri currently flags this as UB.

I believe this is #60847, but for String. Discovered while writing this post.

cc @RalfJung

@matklad matklad added A-collections Area: `std::collection` C-bug Category: This is a bug. labels Mar 23, 2020
@RalfJung
Copy link
Member

I believe this is #60847, but for String

Yeah this looks pretty similar. String uses Vec under the hood though, so I wonder why the issue still exists here.

Looks like push_str is implemented via extend_from_slice -- and indeed, this errors, too:

fn main() {
    let mut v = Vec::with_capacity(10);
    v.push(0);
    let v0 = unsafe { &*(&v[0] as *const _) }; // laundering the lifetime -- we take care that `v` does not reallocate, so that's okay.
    v.extend_from_slice(&[1]);
    let _val = *v0;
}

@jonas-schievink jonas-schievink added the T-libs-api Relevant to the library API team, which will review and decide on the PR/issue. label Mar 23, 2020
@RalfJung
Copy link
Member

The problem is in this line:

self.get_unchecked_mut(len..).copy_from_slice(slice);

The get_unchecked_mut here is a slice method, so this creates a slice covering the entire Vec, which overlaps with the mutable reference created before and thus invalidates it.

@RalfJung
Copy link
Member

It shouldn't be too hard to locally fix this, but that duplicates some code and there are some other uses of get_unchecked_mut in vec.rs. Ideally, this would all use raw slices to avoid making any uniqueness promises. But without #60639, raw slices are not very useful.

@RalfJung
Copy link
Member

Here's a possible fix:

diff --git a/src/liballoc/vec.rs b/src/liballoc/vec.rs
index 4769091183a..fd2b336eceb 100644
--- a/src/liballoc/vec.rs
+++ b/src/liballoc/vec.rs
@@ -2121,8 +2121,9 @@ where
         self.reserve(slice.len());
         unsafe {
             let len = self.len();
+            let dst_slice = slice::from_raw_parts_mut(self.as_mut_ptr().add(len), slice.len());
+            dst_slice.copy_from_slice(slice);
             self.set_len(len + slice.len());
-            self.get_unchecked_mut(len..).copy_from_slice(slice);
         }
     }
 }

@RalfJung
Copy link
Member

Fix PR is up: #70558

@bors bors closed this as completed in 7e4ed72 Apr 5, 2020
bors added a commit to rust-lang/miri that referenced this issue Apr 5, 2020
test Vec::extend

Currently fails, until rust-lang/rust#70301 gets fixed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-collections Area: `std::collection` C-bug Category: This is a bug. T-libs-api Relevant to the library API team, which will review and decide on the PR/issue.
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants