• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

facet-rs / facet / 19903754435

03 Dec 2025 05:59PM UTC coverage: 59.12% (+0.1%) from 59.013%
19903754435

push

github

fasterthanlime
fix: use strict provenance APIs in facet-value

For inline values (null, booleans, short strings), we pack data directly
into pointer bits - there's no actual memory being pointed to. Previously
we used `bits as *mut u8` which is an integer-to-pointer cast that
violates strict provenance.

Now we use:
- `ptr::without_provenance_mut()` for creating fake pointers from integers
- `.addr()` instead of `as usize` for getting the address

This makes miri happy with `-Zmiri-strict-provenance`.

4 of 7 new or added lines in 1 file covered. (57.14%)

420 existing lines in 9 files now uncovered.

20944 of 35426 relevant lines covered (59.12%)

537.13 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

79.53
/facet-value/src/string.rs
1
//! String value type.
2

3
#[cfg(feature = "alloc")]
4
use alloc::alloc::{Layout, alloc, dealloc};
5
#[cfg(feature = "alloc")]
6
use alloc::string::String;
7
use core::borrow::Borrow;
8
use core::cmp::Ordering;
9
use core::fmt::{self, Debug, Formatter};
10
use core::hash::{Hash, Hasher};
11
use core::mem;
12
use core::ops::Deref;
13
use core::ptr;
14
use static_assertions::const_assert;
15
use static_assertions::const_assert_eq;
16

17
use crate::value::{TypeTag, Value};
18

19
/// Header for heap-allocated strings.
20
#[repr(C, align(8))]
21
struct StringHeader {
22
    /// Length of the string in bytes
23
    len: usize,
24
    // String data follows immediately after
25
}
26

27
/// A string value.
28
///
29
/// `VString` stores UTF-8 string data. Short strings (up to 7 bytes on 64-bit targets) are
30
/// embedded directly in the `Value` pointer bits, while longer strings fall back to heap storage.
31
#[repr(transparent)]
32
#[derive(Clone)]
33
pub struct VString(pub(crate) Value);
34

35
impl VString {
36
    const INLINE_WORD_BYTES: usize = mem::size_of::<usize>();
37
    const INLINE_DATA_OFFSET: usize = 1;
38
    const INLINE_CAP_BYTES: usize = Self::INLINE_WORD_BYTES - Self::INLINE_DATA_OFFSET;
39
    pub(crate) const INLINE_LEN_MAX: usize = {
40
        const LEN_MASK: usize = (1 << (8 - 3)) - 1;
41
        let cap = mem::size_of::<usize>() - 1;
42
        if cap < LEN_MASK { cap } else { LEN_MASK }
43
    };
44
    const INLINE_LEN_SHIFT: u8 = 3;
45

46
    fn layout(len: usize) -> Layout {
48✔
47
        Layout::new::<StringHeader>()
48✔
48
            .extend(Layout::array::<u8>(len).unwrap())
48✔
49
            .unwrap()
48✔
50
            .0
48✔
51
            .pad_to_align()
48✔
52
    }
48✔
53

54
    #[cfg(feature = "alloc")]
55
    fn alloc(s: &str) -> *mut StringHeader {
24✔
56
        unsafe {
57
            let layout = Self::layout(s.len());
24✔
58
            let ptr = alloc(layout).cast::<StringHeader>();
24✔
59
            (*ptr).len = s.len();
24✔
60

61
            // Copy string data
62
            let data_ptr = ptr.add(1).cast::<u8>();
24✔
63
            ptr::copy_nonoverlapping(s.as_ptr(), data_ptr, s.len());
24✔
64

65
            ptr
24✔
66
        }
67
    }
24✔
68

69
    #[cfg(feature = "alloc")]
70
    fn dealloc_ptr(ptr: *mut StringHeader) {
24✔
71
        unsafe {
24✔
72
            let len = (*ptr).len;
24✔
73
            let layout = Self::layout(len);
24✔
74
            dealloc(ptr.cast::<u8>(), layout);
24✔
75
        }
24✔
76
    }
24✔
77

78
    fn header(&self) -> &StringHeader {
45✔
79
        debug_assert!(!self.is_inline());
45✔
80
        unsafe { &*(self.0.heap_ptr() as *const StringHeader) }
45✔
81
    }
45✔
82

83
    fn data_ptr(&self) -> *const u8 {
45✔
84
        debug_assert!(!self.is_inline());
45✔
85
        // Go through heap_ptr directly to avoid creating intermediate reference
86
        // that would limit provenance to just the header
87
        unsafe { (self.0.heap_ptr() as *const StringHeader).add(1).cast() }
45✔
88
    }
45✔
89

90
    /// Creates a new string from a `&str`.
91
    #[cfg(feature = "alloc")]
92
    #[must_use]
93
    pub fn new(s: &str) -> Self {
469✔
94
        if Self::can_inline(s.len()) {
469✔
95
            return Self::new_inline(s);
445✔
96
        }
24✔
97
        unsafe {
98
            let ptr = Self::alloc(s);
24✔
99
            VString(Value::new_ptr(ptr.cast(), TypeTag::StringOrNull))
24✔
100
        }
101
    }
469✔
102

103
    /// Creates an empty string.
104
    #[cfg(feature = "alloc")]
105
    #[must_use]
106
    pub fn empty() -> Self {
1✔
107
        Self::new_inline("")
1✔
108
    }
1✔
109

110
    /// Returns the length of the string in bytes.
111
    #[must_use]
112
    pub fn len(&self) -> usize {
57✔
113
        if self.is_inline() {
57✔
114
            self.inline_len()
12✔
115
        } else {
116
            self.header().len
45✔
117
        }
118
    }
57✔
119

120
    /// Returns `true` if the string is empty.
121
    #[must_use]
122
    pub fn is_empty(&self) -> bool {
2✔
123
        self.len() == 0
2✔
124
    }
2✔
125

126
    /// Returns the string as a `&str`.
127
    #[must_use]
128
    pub fn as_str(&self) -> &str {
1,634✔
129
        unsafe { core::str::from_utf8_unchecked(self.as_bytes()) }
1,634✔
130
    }
1,634✔
131

132
    /// Returns the string as a byte slice.
133
    #[must_use]
134
    pub fn as_bytes(&self) -> &[u8] {
1,650✔
135
        if self.is_inline() {
1,650✔
136
            unsafe { core::slice::from_raw_parts(self.inline_data_ptr(), self.inline_len()) }
1,605✔
137
        } else {
138
            unsafe { core::slice::from_raw_parts(self.data_ptr(), self.len()) }
45✔
139
        }
140
    }
1,650✔
141

142
    pub(crate) fn clone_impl(&self) -> Value {
28✔
143
        VString::new(self.as_str()).0
28✔
144
    }
28✔
145

146
    pub(crate) fn drop_impl(&mut self) {
470✔
147
        if self.is_inline() {
470✔
148
            return;
446✔
149
        }
24✔
150
        unsafe {
24✔
151
            Self::dealloc_ptr(self.0.heap_ptr_mut().cast());
24✔
152
        }
24✔
153
    }
470✔
154

155
    #[inline]
156
    fn is_inline(&self) -> bool {
3,895✔
157
        self.0.is_inline_string()
3,895✔
158
    }
3,895✔
159

160
    #[inline]
161
    fn can_inline(len: usize) -> bool {
915✔
162
        len <= Self::INLINE_LEN_MAX && len <= Self::INLINE_CAP_BYTES
915✔
163
    }
915✔
164

165
    #[inline]
166
    fn inline_meta_ptr(&self) -> *const u8 {
3,222✔
167
        self as *const VString as *const u8
3,222✔
168
    }
3,222✔
169

170
    #[inline]
171
    fn inline_data_ptr(&self) -> *const u8 {
1,605✔
172
        unsafe { self.inline_meta_ptr().add(Self::INLINE_DATA_OFFSET) }
1,605✔
173
    }
1,605✔
174

175
    #[inline]
176
    fn inline_len(&self) -> usize {
1,617✔
177
        debug_assert!(self.is_inline());
1,617✔
178
        unsafe { (*self.inline_meta_ptr() >> Self::INLINE_LEN_SHIFT) as usize }
1,617✔
179
    }
1,617✔
180

181
    #[cfg(feature = "alloc")]
182
    fn new_inline(s: &str) -> Self {
446✔
183
        debug_assert!(Self::can_inline(s.len()));
446✔
184
        let mut storage = [0u8; Self::INLINE_WORD_BYTES];
446✔
185
        storage[0] = ((s.len() as u8) << Self::INLINE_LEN_SHIFT) | (TypeTag::InlineString as u8);
446✔
186
        storage[Self::INLINE_DATA_OFFSET..Self::INLINE_DATA_OFFSET + s.len()]
446✔
187
            .copy_from_slice(s.as_bytes());
446✔
188
        let bits = usize::from_ne_bytes(storage);
446✔
189
        VString(unsafe { Value::from_bits(bits) })
446✔
190
    }
446✔
191
}
192

193
const _: () = {
194
    const_assert_eq!(VString::INLINE_DATA_OFFSET, 1);
195
    const_assert!(
196
        VString::INLINE_CAP_BYTES <= VString::INLINE_WORD_BYTES - VString::INLINE_DATA_OFFSET
197
    );
198
    const_assert!(VString::INLINE_LEN_MAX <= VString::INLINE_CAP_BYTES);
199
};
200

201
impl Deref for VString {
202
    type Target = str;
203

204
    fn deref(&self) -> &str {
2✔
205
        self.as_str()
2✔
206
    }
2✔
207
}
208

209
impl Borrow<str> for VString {
UNCOV
210
    fn borrow(&self) -> &str {
×
UNCOV
211
        self.as_str()
×
UNCOV
212
    }
×
213
}
214

215
impl AsRef<str> for VString {
UNCOV
216
    fn as_ref(&self) -> &str {
×
UNCOV
217
        self.as_str()
×
UNCOV
218
    }
×
219
}
220

221
impl AsRef<[u8]> for VString {
UNCOV
222
    fn as_ref(&self) -> &[u8] {
×
UNCOV
223
        self.as_bytes()
×
UNCOV
224
    }
×
225
}
226

227
impl PartialEq for VString {
228
    fn eq(&self, other: &Self) -> bool {
81✔
229
        self.as_str() == other.as_str()
81✔
230
    }
81✔
231
}
232

233
impl Eq for VString {}
234

235
impl PartialOrd for VString {
236
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
1✔
237
        Some(self.cmp(other))
1✔
238
    }
1✔
239
}
240

241
impl Ord for VString {
242
    fn cmp(&self, other: &Self) -> Ordering {
1✔
243
        self.as_str().cmp(other.as_str())
1✔
244
    }
1✔
245
}
246

247
impl Hash for VString {
UNCOV
248
    fn hash<H: Hasher>(&self, state: &mut H) {
×
UNCOV
249
        self.as_str().hash(state);
×
UNCOV
250
    }
×
251
}
252

253
impl Debug for VString {
UNCOV
254
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
×
255
        Debug::fmt(self.as_str(), f)
×
256
    }
×
257
}
258

259
impl fmt::Display for VString {
260
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
2✔
261
        fmt::Display::fmt(self.as_str(), f)
2✔
262
    }
2✔
263
}
264

265
impl Default for VString {
UNCOV
266
    fn default() -> Self {
×
UNCOV
267
        Self::empty()
×
UNCOV
268
    }
×
269
}
270

271
// === PartialEq with str ===
272

273
impl PartialEq<str> for VString {
UNCOV
274
    fn eq(&self, other: &str) -> bool {
×
UNCOV
275
        self.as_str() == other
×
UNCOV
276
    }
×
277
}
278

279
impl PartialEq<VString> for str {
UNCOV
280
    fn eq(&self, other: &VString) -> bool {
×
UNCOV
281
        self == other.as_str()
×
UNCOV
282
    }
×
283
}
284

285
impl PartialEq<&str> for VString {
286
    fn eq(&self, other: &&str) -> bool {
1✔
287
        self.as_str() == *other
1✔
288
    }
1✔
289
}
290

291
#[cfg(feature = "alloc")]
292
impl PartialEq<String> for VString {
UNCOV
293
    fn eq(&self, other: &String) -> bool {
×
UNCOV
294
        self.as_str() == other.as_str()
×
UNCOV
295
    }
×
296
}
297

298
#[cfg(feature = "alloc")]
299
impl PartialEq<VString> for String {
UNCOV
300
    fn eq(&self, other: &VString) -> bool {
×
UNCOV
301
        self.as_str() == other.as_str()
×
UNCOV
302
    }
×
303
}
304

305
// === From implementations ===
306

307
#[cfg(feature = "alloc")]
308
impl From<&str> for VString {
309
    fn from(s: &str) -> Self {
259✔
310
        Self::new(s)
259✔
311
    }
259✔
312
}
313

314
#[cfg(feature = "alloc")]
315
impl From<String> for VString {
UNCOV
316
    fn from(s: String) -> Self {
×
UNCOV
317
        Self::new(&s)
×
318
    }
×
319
}
320

321
#[cfg(feature = "alloc")]
322
impl From<&String> for VString {
UNCOV
323
    fn from(s: &String) -> Self {
×
UNCOV
324
        Self::new(s)
×
UNCOV
325
    }
×
326
}
327

328
#[cfg(feature = "alloc")]
329
impl From<VString> for String {
UNCOV
330
    fn from(s: VString) -> Self {
×
UNCOV
331
        s.as_str().into()
×
UNCOV
332
    }
×
333
}
334

335
// === Value conversions ===
336

337
impl AsRef<Value> for VString {
UNCOV
338
    fn as_ref(&self) -> &Value {
×
UNCOV
339
        &self.0
×
UNCOV
340
    }
×
341
}
342

343
impl AsMut<Value> for VString {
UNCOV
344
    fn as_mut(&mut self) -> &mut Value {
×
UNCOV
345
        &mut self.0
×
UNCOV
346
    }
×
347
}
348

349
impl From<VString> for Value {
350
    fn from(s: VString) -> Self {
7✔
351
        s.0
7✔
352
    }
7✔
353
}
354

355
impl VString {
356
    /// Converts this VString into a Value, consuming self.
357
    #[inline]
358
    pub fn into_value(self) -> Value {
11✔
359
        self.0
11✔
360
    }
11✔
361
}
362

363
#[cfg(feature = "alloc")]
364
impl From<&str> for Value {
365
    fn from(s: &str) -> Self {
142✔
366
        VString::new(s).0
142✔
367
    }
142✔
368
}
369

370
#[cfg(feature = "alloc")]
371
impl From<String> for Value {
UNCOV
372
    fn from(s: String) -> Self {
×
UNCOV
373
        VString::new(&s).0
×
UNCOV
374
    }
×
375
}
376

377
#[cfg(feature = "alloc")]
378
impl From<&String> for Value {
UNCOV
379
    fn from(s: &String) -> Self {
×
UNCOV
380
        VString::new(s).0
×
UNCOV
381
    }
×
382
}
383

384
#[cfg(test)]
385
mod tests {
386
    use super::*;
387
    use crate::value::{TypeTag, Value};
388

389
    #[test]
390
    fn test_new() {
1✔
391
        let s = VString::new("hello");
1✔
392
        assert_eq!(s.as_str(), "hello");
1✔
393
        assert_eq!(s.len(), 5);
1✔
394
        assert!(!s.is_empty());
1✔
395
    }
1✔
396

397
    #[test]
398
    fn test_empty() {
1✔
399
        let s = VString::empty();
1✔
400
        assert_eq!(s.as_str(), "");
1✔
401
        assert_eq!(s.len(), 0);
1✔
402
        assert!(s.is_empty());
1✔
403
    }
1✔
404

405
    #[test]
406
    fn test_equality() {
1✔
407
        let a = VString::new("hello");
1✔
408
        let b = VString::new("hello");
1✔
409
        let c = VString::new("world");
1✔
410

411
        assert_eq!(a, b);
1✔
412
        assert_ne!(a, c);
1✔
413
        assert_eq!(a, "hello");
1✔
414
        assert_eq!(a.as_str(), "hello");
1✔
415
    }
1✔
416

417
    #[test]
418
    fn test_clone() {
1✔
419
        let a = VString::new("test");
1✔
420
        let b = a.clone();
1✔
421
        assert_eq!(a, b);
1✔
422
    }
1✔
423

424
    #[test]
425
    fn test_unicode() {
1✔
426
        let s = VString::new("hello δΈ–η•Œ 🌍");
1✔
427
        assert_eq!(s.as_str(), "hello δΈ–η•Œ 🌍");
1✔
428
    }
1✔
429

430
    #[test]
431
    fn test_deref() {
1✔
432
        let s = VString::new("hello");
1✔
433
        assert!(s.starts_with("hel"));
1✔
434
        assert!(s.ends_with("llo"));
1✔
435
    }
1✔
436

437
    #[test]
438
    fn test_ordering() {
1✔
439
        let a = VString::new("apple");
1✔
440
        let b = VString::new("banana");
1✔
441
        assert!(a < b);
1✔
442
    }
1✔
443

444
    #[test]
445
    fn test_inline_representation() {
1✔
446
        let s = VString::new("inline");
1✔
447
        assert!(s.is_inline(), "expected inline storage");
1✔
448
        assert_eq!(s.as_str(), "inline");
1✔
449
    }
1✔
450

451
    #[test]
452
    fn test_heap_representation() {
1✔
453
        let long_input = "a".repeat(VString::INLINE_LEN_MAX + 1);
1✔
454
        let s = VString::new(&long_input);
1✔
455
        assert!(!s.is_inline(), "expected heap storage");
1✔
456
        assert_eq!(s.as_str(), long_input);
1✔
457
    }
1✔
458

459
    #[test]
460
    fn inline_capacity_boundaries() {
1✔
461
        for len in 0..=VString::INLINE_LEN_MAX {
8✔
462
            let input = "x".repeat(len);
8✔
463
            let s = VString::new(&input);
8✔
464
            assert!(
8✔
465
                s.is_inline(),
8✔
466
                "expected inline storage for length {} (capacity {})",
467
                len,
468
                VString::INLINE_LEN_MAX
469
            );
470
            assert_eq!(s.len(), len);
8✔
471
            assert_eq!(s.as_str(), input);
8✔
472
            assert_eq!(s.as_bytes(), input.as_bytes());
8✔
473
        }
474

475
        let overflow = "y".repeat(VString::INLINE_LEN_MAX + 1);
1✔
476
        let heap = VString::new(&overflow);
1✔
477
        assert!(
1✔
478
            !heap.is_inline(),
1✔
479
            "length {} should force heap allocation",
UNCOV
480
            overflow.len()
×
481
        );
482
    }
1✔
483

484
    #[test]
485
    fn inline_value_tag_matches() {
1✔
486
        for len in 0..=VString::INLINE_LEN_MAX {
8✔
487
            let input = "z".repeat(len);
8✔
488
            let value = Value::from(input.as_str());
8✔
489
            assert!(value.is_inline_string(), "Value should mark inline string");
8✔
490
            assert_eq!(
8✔
491
                value.ptr_usize() & 0b111,
8✔
492
                TypeTag::InlineString as usize,
8✔
493
                "low bits must store inline string tag"
494
            );
495
            let roundtrip = value.as_string().expect("string value");
8✔
496
            assert_eq!(roundtrip.as_str(), input);
8✔
497
            assert_eq!(roundtrip.as_bytes(), input.as_bytes());
8✔
498
        }
499
    }
1✔
500

501
    #[cfg(target_pointer_width = "64")]
502
    #[test]
503
    fn inline_len_max_is_seven_on_64_bit() {
1✔
504
        assert_eq!(VString::INLINE_LEN_MAX, 7);
1✔
505
    }
1✔
506

507
    #[cfg(target_pointer_width = "32")]
508
    #[test]
509
    fn inline_len_max_is_three_on_32_bit() {
510
        assert_eq!(VString::INLINE_LEN_MAX, 3);
511
    }
512
}
513

514
#[cfg(all(test, feature = "bolero-inline-tests"))]
515
mod bolero_props {
516
    use super::*;
517
    use crate::ValueType;
518
    use crate::array::VArray;
519
    use alloc::string::String;
520
    use alloc::vec::Vec;
521
    use bolero::check;
522

523
    #[test]
524
    fn bolero_inline_string_round_trip() {
525
        check!().with_type::<Vec<u8>>().for_each(|bytes: &Vec<u8>| {
526
            if bytes.len() > VString::INLINE_LEN_MAX + 8 {
527
                // Keep the generator focused on short payloads to hit inline cases hard.
528
                return;
529
            }
530

531
            if let Ok(s) = String::from_utf8(bytes.clone()) {
532
                let value = Value::from(s.as_str());
533
                let roundtrip = value.as_string().expect("expected string value");
534
                assert_eq!(roundtrip.as_str(), s);
535

536
                if VString::can_inline(s.len()) {
537
                    assert!(value.is_inline_string(), "expected inline tag for {s:?}");
538
                } else {
539
                    assert!(!value.is_inline_string(), "unexpected inline tag for {s:?}");
540
                }
541
            }
542
        });
543
    }
544

545
    #[test]
546
    fn bolero_string_mutation_sequences() {
547
        check!().with_type::<Vec<u8>>().for_each(|bytes: &Vec<u8>| {
548
            let mut value = Value::from("");
549
            let mut expected = String::new();
550

551
            for chunk in bytes.chunks(3).take(24) {
552
                let selector = chunk.first().copied().unwrap_or(0) % 3;
553
                match selector {
554
                    0 => {
555
                        let ch = (b'a' + chunk.get(1).copied().unwrap_or(0) % 26) as char;
556
                        expected.push(ch);
557
                    }
558
                    1 => {
559
                        if !expected.is_empty() {
560
                            let len = chunk
561
                                .get(1)
562
                                .copied()
563
                                .map(|n| (n as usize) % expected.len())
564
                                .unwrap_or(0);
565
                            expected.truncate(len);
566
                        }
567
                    }
568
                    _ => expected.clear(),
569
                }
570

571
                overwrite_value_string(&mut value, &expected);
572
                assert_eq!(value.as_string().unwrap().as_str(), expected);
573
                assert_eq!(
574
                    value.is_inline_string(),
575
                    expected.len() <= VString::INLINE_LEN_MAX,
576
                    "mutation sequence should keep inline status accurate"
577
                );
578
            }
579
        });
580
    }
581

582
    #[test]
583
    fn bolero_array_model_matches() {
584
        check!().with_type::<Vec<u8>>().for_each(|bytes: &Vec<u8>| {
585
            let mut arr = VArray::new();
586
            let mut model: Vec<String> = Vec::new();
587

588
            for chunk in bytes.chunks(4).take(20) {
589
                match chunk.first().copied().unwrap_or(0) % 4 {
590
                    0 => {
591
                        let content = inline_string_from_chunk(chunk, 1);
592
                        arr.push(Value::from(content.as_str()));
593
                        model.push(content);
594
                    }
595
                    1 => {
596
                        let idx = chunk.get(1).copied().unwrap_or(0) as usize;
597
                        if !model.is_empty() {
598
                            let idx = idx % model.len();
599
                            model.remove(idx);
600
                            let _ = arr.remove(idx);
601
                        }
602
                    }
603
                    2 => {
604
                        let content = inline_string_from_chunk(chunk, 2);
605
                        if model.is_empty() {
606
                            arr.insert(0, Value::from(content.as_str()));
607
                            model.insert(0, content);
608
                        } else {
609
                            let len = model.len();
610
                            let idx = (chunk.get(2).copied().unwrap_or(0) as usize) % (len + 1);
611
                            arr.insert(idx, Value::from(content.as_str()));
612
                            model.insert(idx, content);
613
                        }
614
                    }
615
                    _ => {
616
                        arr.clear();
617
                        model.clear();
618
                    }
619
                }
620

621
                assert_eq!(arr.len(), model.len());
622
                for (value, expected) in arr.iter().zip(model.iter()) {
623
                    assert_eq!(value.value_type(), ValueType::String);
624
                    assert_eq!(value.as_string().unwrap().as_str(), expected);
625
                    assert_eq!(
626
                        value.is_inline_string(),
627
                        expected.len() <= VString::INLINE_LEN_MAX
628
                    );
629
                }
630
            }
631
        });
632
    }
633

634
    fn overwrite_value_string(value: &mut Value, new_value: &str) {
635
        let slot = value.as_string_mut().expect("expected string value");
636
        *slot = VString::new(new_value);
637
    }
638

639
    fn inline_string_from_chunk(chunk: &[u8], seed_idx: usize) -> String {
640
        let len_hint = chunk.get(seed_idx).copied().unwrap_or(0) as usize;
641
        let len = len_hint % (VString::INLINE_LEN_MAX.saturating_sub(1).max(1));
642
        (0..len)
643
            .map(|i| {
644
                let byte = chunk.get(i % chunk.len()).copied().unwrap_or(b'a');
645
                (b'a' + (byte % 26)) as char
646
            })
647
            .collect()
648
    }
649
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc