Repository: rust-analyzer/smol_str (Rust)

Issue:
v0.3.1 Null Bytes inserted if multi-byte unicode character forces builder to move to heap 
Small Example:

```rust
use smol_str::SmolStrBuilder;

fn main() {
    let mut builder = SmolStrBuilder::new();
    let prefix = "ohnonononononononono!";
    dbg!(&builder, prefix, prefix.len());
    builder.push_str(prefix);
    dbg!(&builder);
    builder.push('🤯');
    dbg!(&builder);
    dbg!(builder.finish());
}
```

The final two debug lines here will print:
```
[src/main.rs:12:5] &builder = SmolStrBuilder(
    Heap(
        "ohnonononononononono!\0\0🤯",
    ),
)
[src/main.rs:13:5] builder.finish() = "ohnonononononononono!\0\0🤯"
```

Expected outcome would be 
```
[src/main.rs:12:5] &builder = SmolStrBuilder(
    Heap(
        "ohnonononononononono!🤯",
    ),
)
[src/main.rs:13:5] builder.finish() = "ohnonononononononono!🤯"
```


Discussion:
Oh ... https://github.com/rust-analyzer/smol_str/blob/f680abcbe9acf7d9b6f0f4a7d71e12a661fcaa99/src/lib.rs#L766 this needs to slice buf to the actual len of the inline store ... Good thing that's zero-initialized so its at least not creating invalid utf8 strings ...



Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index c1346a2..41f1377 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,10 @@
 # Changelog
 
+## 0.3.2 - 2024-10-23
+
+- Fix `SmolStrBuilder::push` incorrectly padding null bytes when spilling onto the heap on a
+  multibyte character push
+
 ## 0.3.1 - 2024-09-04
 
 - Fix `SmolStrBuilder` leaking implementation details
diff --git a/Cargo.toml b/Cargo.toml
index 1850605..e89e0e8 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "smol_str"
-version = "0.3.1"
+version = "0.3.2"
 description = "small-string optimized string type with O(1) clone"
 license = "MIT OR Apache-2.0"
 repository = "https://github.com/rust-analyzer/smol_str"
diff --git a/src/lib.rs b/src/lib.rs
index d00ec98..bf88f57 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -763,7 +763,7 @@ impl SmolStrBuilder {
                     let mut heap = String::with_capacity(new_len);
                     // copy existing inline bytes over to the heap
                     // SAFETY: inline data is guaranteed to be valid utf8 for `old_len` bytes
-                    unsafe { heap.as_mut_vec().extend_from_slice(buf) };
+                    unsafe { heap.as_mut_vec().extend_from_slice(&buf[..*len]) };
                     heap.push(c);
                     self.0 = SmolStrBuilderRepr::Heap(heap);
                 }


Test patch:
diff --git a/tests/test.rs b/tests/test.rs
index 81bccf1..96b8b8f 100644
--- a/tests/test.rs
+++ b/tests/test.rs
@@ -255,6 +255,7 @@ fn test_to_smolstr() {
         assert_eq!(a, smol_str::format_smolstr!("{}", a));
     }
 }
+
 #[test]
 fn test_builder_push_str() {
     //empty
@@ -290,6 +291,14 @@ fn test_builder_push_str() {
     let s = builder.finish();
     assert!(s.is_heap_allocated());
     assert_eq!("a".repeat(46), s);
+
+    // heap push on multibyte char
+    let mut builder = SmolStrBuilder::new();
+    builder.push_str("ohnonononononononono!");
+    builder.push('🤯');
+    let s = builder.finish();
+    assert!(s.is_heap_allocated());
+    assert_eq!("ohnonononononononono!🤯", s);
 }
 
 #[test]