Coverage Report

Created: 2026-08-14 07:12

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/html5ever/tendril/src/fmt.rs
Line
Count
Source
1
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
2
// https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
3
// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
4
// option. This file may not be copied, modified, or distributed
5
// except according to those terms.
6
7
//! Marker types for formats.
8
//!
9
//! This module defines the types and traits used to mark a `Tendril`
10
//! with the format of data it contains. It includes those formats
11
//! for which `Tendril` supports at least some operations without
12
//! conversion.
13
//!
14
//! To convert a string tendril to/from a byte tendril in an arbitrary
15
//! character encoding, see the `encode` and `decode` methods on
16
//! `Tendril`.
17
//!
18
//! `Tendril` operations may become memory-unsafe if data invalid for
19
//! the format sneaks in. For that reason, these traits require
20
//! `unsafe impl`.
21
22
use std::default::Default;
23
use std::{char, mem, str};
24
25
use crate::futf::{self, Codepoint, Meaning};
26
27
/// Implementation details.
28
///
29
/// You don't need these unless you are implementing
30
/// a new format.
31
pub mod imp {
32
    use std::default::Default;
33
    use std::{iter, slice};
34
35
    /// Describes how to fix up encodings when concatenating.
36
    ///
37
    /// We can drop characters on either side of the splice,
38
    /// and insert up to 4 bytes in the middle.
39
    pub struct Fixup {
40
        pub drop_left: u32,
41
        pub drop_right: u32,
42
        pub insert_len: u32,
43
        pub insert_bytes: [u8; 4],
44
    }
45
46
    impl Default for Fixup {
47
        #[inline(always)]
48
78.7M
        fn default() -> Fixup {
49
78.7M
            Fixup {
50
78.7M
                drop_left: 0,
51
78.7M
                drop_right: 0,
52
78.7M
                insert_len: 0,
53
78.7M
                insert_bytes: [0; 4],
54
78.7M
            }
55
78.7M
        }
56
    }
57
58
    pub struct SingleByteCharIndices<'a> {
59
        inner: iter::Enumerate<slice::Iter<'a, u8>>,
60
    }
61
62
    impl<'a> Iterator for SingleByteCharIndices<'a> {
63
        type Item = (usize, char);
64
65
        #[inline]
66
0
        fn next(&mut self) -> Option<(usize, char)> {
67
0
            self.inner
68
0
                .next()
69
0
                .map(|(i, &b)| unsafe { (i, char::from_u32_unchecked(b as u32)) })
70
0
        }
71
    }
72
73
    impl<'a> SingleByteCharIndices<'a> {
74
        #[inline]
75
0
        pub fn new(buf: &'a [u8]) -> SingleByteCharIndices<'a> {
76
0
            SingleByteCharIndices {
77
0
                inner: buf.iter().enumerate(),
78
0
            }
79
0
        }
80
    }
81
}
82
83
/// Trait for format marker types.
84
///
85
/// The type implementing this trait is usually not instantiated.
86
/// It's used with a phantom type parameter of `Tendril`.
87
pub unsafe trait Format {
88
    /// Check whether the buffer is valid for this format.
89
    fn validate(buf: &[u8]) -> bool;
90
91
    /// Check whether the buffer is valid for this format.
92
    ///
93
    /// You may assume the buffer is a prefix of a valid buffer.
94
    #[inline]
95
12.4k
    fn validate_prefix(buf: &[u8]) -> bool {
96
12.4k
        <Self as Format>::validate(buf)
97
12.4k
    }
<tendril::fmt::Bytes as tendril::fmt::Format>::validate_prefix
Line
Count
Source
95
12.4k
    fn validate_prefix(buf: &[u8]) -> bool {
96
12.4k
        <Self as Format>::validate(buf)
97
12.4k
    }
Unexecuted instantiation: <_ as tendril::fmt::Format>::validate_prefix
98
99
    /// Check whether the buffer is valid for this format.
100
    ///
101
    /// You may assume the buffer is a suffix of a valid buffer.
102
    #[inline]
103
38.2M
    fn validate_suffix(buf: &[u8]) -> bool {
104
38.2M
        <Self as Format>::validate(buf)
105
38.2M
    }
<tendril::fmt::Bytes as tendril::fmt::Format>::validate_suffix
Line
Count
Source
103
38.2M
    fn validate_suffix(buf: &[u8]) -> bool {
104
38.2M
        <Self as Format>::validate(buf)
105
38.2M
    }
Unexecuted instantiation: <_ as tendril::fmt::Format>::validate_suffix
106
107
    /// Check whether the buffer is valid for this format.
108
    ///
109
    /// You may assume the buffer is a contiguous subsequence
110
    /// of a valid buffer, but not necessarily a prefix or
111
    /// a suffix.
112
    #[inline]
113
4.60M
    fn validate_subseq(buf: &[u8]) -> bool {
114
4.60M
        <Self as Format>::validate(buf)
115
4.60M
    }
<tendril::fmt::Bytes as tendril::fmt::Format>::validate_subseq
Line
Count
Source
113
4.60M
    fn validate_subseq(buf: &[u8]) -> bool {
114
4.60M
        <Self as Format>::validate(buf)
115
4.60M
    }
Unexecuted instantiation: <_ as tendril::fmt::Format>::validate_subseq
116
117
    /// Compute any fixup needed when concatenating buffers.
118
    ///
119
    /// The default is to do nothing.
120
    ///
121
    /// The function is `unsafe` because it may assume the input
122
    /// buffers are already valid for the format. Also, no
123
    /// bounds-checking is performed on the return value!
124
    #[inline(always)]
125
78.7M
    unsafe fn fixup(_lhs: &[u8], _rhs: &[u8]) -> imp::Fixup {
126
78.7M
        Default::default()
127
78.7M
    }
<tendril::fmt::UTF8 as tendril::fmt::Format>::fixup
Line
Count
Source
125
78.7M
    unsafe fn fixup(_lhs: &[u8], _rhs: &[u8]) -> imp::Fixup {
126
78.7M
        Default::default()
127
78.7M
    }
Unexecuted instantiation: <_ as tendril::fmt::Format>::fixup
128
}
129
130
/// Indicates that one format is a subset of another.
131
///
132
/// The subset format can be converted to the superset format
133
/// for free.
134
pub unsafe trait SubsetOf<Super>: Format
135
where
136
    Super: Format,
137
{
138
    /// Validate the *other* direction of conversion; check if
139
    /// this buffer from the superset format conforms to the
140
    /// subset format.
141
    ///
142
    /// The default calls `Self::validate`, but some conversions
143
    /// may implement a check which is cheaper than validating
144
    /// from scratch.
145
0
    fn revalidate_subset(x: &[u8]) -> bool {
146
0
        Self::validate(x)
147
0
    }
148
}
149
150
/// Indicates a format which corresponds to a Rust slice type,
151
/// representing exactly the same invariants.
152
pub unsafe trait SliceFormat: Format + Sized {
153
    type Slice: ?Sized + Slice;
154
}
155
156
/// Indicates a format which contains characters from Unicode
157
/// (all of it, or some proper subset).
158
pub unsafe trait CharFormat<'a>: Format {
159
    /// Iterator for characters and their byte indices.
160
    type Iter: Iterator<Item = (usize, char)>;
161
162
    /// Iterate over the characters of the string and their byte
163
    /// indices.
164
    ///
165
    /// You may assume the buffer is *already validated* for `Format`.
166
    unsafe fn char_indices(buf: &'a [u8]) -> Self::Iter;
167
168
    /// Encode the character as bytes and pass them to a continuation.
169
    ///
170
    /// Returns `Err(())` iff the character cannot be represented.
171
    fn encode_char<F>(ch: char, cont: F) -> Result<(), ()>
172
    where
173
        F: FnOnce(&[u8]);
174
}
175
176
/// Indicates a Rust slice type that is represented in memory as bytes.
177
pub unsafe trait Slice {
178
    /// Access the raw bytes of the slice.
179
    fn as_bytes(&self) -> &[u8];
180
181
    /// Convert a byte slice to this kind of slice.
182
    ///
183
    /// You may assume the buffer is *already validated*
184
    /// for `Format`.
185
    unsafe fn from_bytes(x: &[u8]) -> &Self;
186
187
    /// Convert a byte slice to this kind of slice.
188
    ///
189
    /// You may assume the buffer is *already validated*
190
    /// for `Format`.
191
    unsafe fn from_mut_bytes(x: &mut [u8]) -> &mut Self;
192
}
193
194
/// Marker type for uninterpreted bytes.
195
///
196
/// Validation will never fail for this format.
197
#[derive(Copy, Clone, Default, Debug)]
198
pub struct Bytes;
199
200
unsafe impl Format for Bytes {
201
    #[inline(always)]
202
42.8M
    fn validate(_: &[u8]) -> bool {
203
42.8M
        true
204
42.8M
    }
205
}
206
207
unsafe impl SliceFormat for Bytes {
208
    type Slice = [u8];
209
}
210
211
unsafe impl Slice for [u8] {
212
    #[inline(always)]
213
0
    fn as_bytes(&self) -> &[u8] {
214
0
        self
215
0
    }
216
217
    #[inline(always)]
218
94.8M
    unsafe fn from_bytes(x: &[u8]) -> &[u8] {
219
94.8M
        x
220
94.8M
    }
221
222
    #[inline(always)]
223
109k
    unsafe fn from_mut_bytes(x: &mut [u8]) -> &mut [u8] {
224
109k
        x
225
109k
    }
226
}
227
228
/// Marker type for ASCII text.
229
#[derive(Copy, Clone, Default, Debug)]
230
pub struct ASCII;
231
232
unsafe impl Format for ASCII {
233
    #[inline]
234
0
    fn validate(buf: &[u8]) -> bool {
235
0
        buf.iter().all(|&n| n <= 127)
236
0
    }
237
238
    #[inline(always)]
239
0
    fn validate_prefix(_: &[u8]) -> bool {
240
0
        true
241
0
    }
242
243
    #[inline(always)]
244
0
    fn validate_suffix(_: &[u8]) -> bool {
245
0
        true
246
0
    }
247
248
    #[inline(always)]
249
0
    fn validate_subseq(_: &[u8]) -> bool {
250
0
        true
251
0
    }
252
}
253
254
unsafe impl SubsetOf<UTF8> for ASCII {}
255
unsafe impl SubsetOf<Latin1> for ASCII {}
256
257
unsafe impl<'a> CharFormat<'a> for ASCII {
258
    type Iter = imp::SingleByteCharIndices<'a>;
259
260
    #[inline]
261
0
    unsafe fn char_indices(buf: &'a [u8]) -> imp::SingleByteCharIndices<'a> {
262
0
        imp::SingleByteCharIndices::new(buf)
263
0
    }
264
265
    #[inline]
266
0
    fn encode_char<F>(ch: char, cont: F) -> Result<(), ()>
267
0
    where
268
0
        F: FnOnce(&[u8]),
269
    {
270
0
        let n = ch as u32;
271
0
        if n > 0x7F {
272
0
            return Err(());
273
0
        }
274
0
        cont(&[n as u8]);
275
0
        Ok(())
276
0
    }
277
}
278
279
/// Marker type for UTF-8 text.
280
#[derive(Copy, Clone, Default, Debug)]
281
pub struct UTF8;
282
283
unsafe impl Format for UTF8 {
284
    #[inline]
285
0
    fn validate(buf: &[u8]) -> bool {
286
0
        str::from_utf8(buf).is_ok()
287
0
    }
288
289
    #[inline]
290
3.98M
    fn validate_prefix(buf: &[u8]) -> bool {
291
3.98M
        if buf.is_empty() {
292
0
            return true;
293
3.98M
        }
294
0
        matches!(
295
3.98M
            futf::classify(buf, buf.len() - 1),
296
            Some(Codepoint {
297
                meaning: Meaning::Whole(_),
298
                ..
299
            })
300
        )
301
3.98M
    }
<tendril::fmt::UTF8 as tendril::fmt::Format>::validate_prefix
Line
Count
Source
290
3.98M
    fn validate_prefix(buf: &[u8]) -> bool {
291
3.98M
        if buf.is_empty() {
292
0
            return true;
293
3.98M
        }
294
0
        matches!(
295
3.98M
            futf::classify(buf, buf.len() - 1),
296
            Some(Codepoint {
297
                meaning: Meaning::Whole(_),
298
                ..
299
            })
300
        )
301
3.98M
    }
Unexecuted instantiation: <tendril::fmt::UTF8 as tendril::fmt::Format>::validate_prefix
302
303
    #[inline]
304
4.41M
    fn validate_suffix(buf: &[u8]) -> bool {
305
4.41M
        if buf.is_empty() {
306
0
            return true;
307
4.41M
        }
308
0
        matches!(
309
4.41M
            futf::classify(buf, 0),
310
            Some(Codepoint {
311
                meaning: Meaning::Whole(_),
312
                ..
313
            })
314
        )
315
4.41M
    }
<tendril::fmt::UTF8 as tendril::fmt::Format>::validate_suffix
Line
Count
Source
304
420k
    fn validate_suffix(buf: &[u8]) -> bool {
305
420k
        if buf.is_empty() {
306
0
            return true;
307
420k
        }
308
0
        matches!(
309
420k
            futf::classify(buf, 0),
310
            Some(Codepoint {
311
                meaning: Meaning::Whole(_),
312
                ..
313
            })
314
        )
315
420k
    }
<tendril::fmt::UTF8 as tendril::fmt::Format>::validate_suffix
Line
Count
Source
304
3.98M
    fn validate_suffix(buf: &[u8]) -> bool {
305
3.98M
        if buf.is_empty() {
306
0
            return true;
307
3.98M
        }
308
0
        matches!(
309
3.98M
            futf::classify(buf, 0),
310
            Some(Codepoint {
311
                meaning: Meaning::Whole(_),
312
                ..
313
            })
314
        )
315
3.98M
    }
Unexecuted instantiation: <tendril::fmt::UTF8 as tendril::fmt::Format>::validate_suffix
316
317
    #[inline]
318
3.98M
    fn validate_subseq(buf: &[u8]) -> bool {
319
3.98M
        <Self as Format>::validate_prefix(buf) && <Self as Format>::validate_suffix(buf)
320
3.98M
    }
<tendril::fmt::UTF8 as tendril::fmt::Format>::validate_subseq
Line
Count
Source
318
3.98M
    fn validate_subseq(buf: &[u8]) -> bool {
319
3.98M
        <Self as Format>::validate_prefix(buf) && <Self as Format>::validate_suffix(buf)
320
3.98M
    }
Unexecuted instantiation: <tendril::fmt::UTF8 as tendril::fmt::Format>::validate_subseq
321
}
322
323
unsafe impl SubsetOf<WTF8> for UTF8 {}
324
325
unsafe impl SliceFormat for UTF8 {
326
    type Slice = str;
327
}
328
329
unsafe impl Slice for str {
330
    #[inline(always)]
331
41.2M
    fn as_bytes(&self) -> &[u8] {
332
41.2M
        str::as_bytes(self)
333
41.2M
    }
334
335
    #[inline(always)]
336
244M
    unsafe fn from_bytes(x: &[u8]) -> &str {
337
244M
        str::from_utf8_unchecked(x)
338
244M
    }
339
340
    #[inline(always)]
341
0
    unsafe fn from_mut_bytes(x: &mut [u8]) -> &mut str {
342
0
        mem::transmute(x)
343
0
    }
344
}
345
346
unsafe impl<'a> CharFormat<'a> for UTF8 {
347
    type Iter = str::CharIndices<'a>;
348
349
    #[inline]
350
90.5M
    unsafe fn char_indices(buf: &'a [u8]) -> str::CharIndices<'a> {
351
90.5M
        str::from_utf8_unchecked(buf).char_indices()
352
90.5M
    }
<tendril::fmt::UTF8 as tendril::fmt::CharFormat>::char_indices
Line
Count
Source
350
90.5M
    unsafe fn char_indices(buf: &'a [u8]) -> str::CharIndices<'a> {
351
90.5M
        str::from_utf8_unchecked(buf).char_indices()
352
90.5M
    }
Unexecuted instantiation: <tendril::fmt::UTF8 as tendril::fmt::CharFormat>::char_indices
353
354
    #[inline]
355
0
    fn encode_char<F>(ch: char, cont: F) -> Result<(), ()>
356
0
    where
357
0
        F: FnOnce(&[u8]),
358
    {
359
0
        cont(ch.encode_utf8(&mut [0_u8; 4]).as_bytes());
360
0
        Ok(())
361
0
    }
362
}
363
364
/// Marker type for WTF-8 text.
365
///
366
/// See the [WTF-8 spec](https://simonsapin.github.io/wtf-8/).
367
#[derive(Copy, Clone, Default, Debug)]
368
pub struct WTF8;
369
370
#[inline]
371
0
fn wtf8_meaningful(m: Meaning) -> bool {
372
0
    matches!(
373
0
        m,
374
        Meaning::Whole(_) | Meaning::LeadSurrogate(_) | Meaning::TrailSurrogate(_)
375
    )
376
0
}
377
378
unsafe impl Format for WTF8 {
379
    #[inline]
380
0
    fn validate(buf: &[u8]) -> bool {
381
0
        let mut i = 0;
382
0
        let mut prev_lead = false;
383
0
        while i < buf.len() {
384
0
            let Some(codept) = futf::classify(buf, i) else {
385
0
                return false;
386
            };
387
0
            if !wtf8_meaningful(codept.meaning) {
388
0
                return false;
389
0
            }
390
0
            i += codept.bytes.len();
391
0
            prev_lead = match codept.meaning {
392
0
                Meaning::TrailSurrogate(_) if prev_lead => return false,
393
0
                Meaning::LeadSurrogate(_) => true,
394
0
                _ => false,
395
            };
396
        }
397
398
0
        true
399
0
    }
400
401
    #[inline]
402
0
    fn validate_prefix(buf: &[u8]) -> bool {
403
0
        if buf.is_empty() {
404
0
            return true;
405
0
        }
406
0
        match futf::classify(buf, buf.len() - 1) {
407
0
            Some(c) => wtf8_meaningful(c.meaning),
408
0
            _ => false,
409
        }
410
0
    }
411
412
    #[inline]
413
0
    fn validate_suffix(buf: &[u8]) -> bool {
414
0
        if buf.is_empty() {
415
0
            return true;
416
0
        }
417
0
        match futf::classify(buf, 0) {
418
0
            Some(c) => wtf8_meaningful(c.meaning),
419
0
            _ => false,
420
        }
421
0
    }
422
423
    #[inline]
424
0
    fn validate_subseq(buf: &[u8]) -> bool {
425
0
        <Self as Format>::validate_prefix(buf) && <Self as Format>::validate_suffix(buf)
426
0
    }
427
428
    #[inline]
429
0
    unsafe fn fixup(lhs: &[u8], rhs: &[u8]) -> imp::Fixup {
430
        const ERR: &str = "WTF8: internal error";
431
432
0
        if lhs.len() >= 3 && rhs.len() >= 3 {
433
            if let (
434
                Some(Codepoint {
435
0
                    meaning: Meaning::LeadSurrogate(hi),
436
                    ..
437
                }),
438
                Some(Codepoint {
439
0
                    meaning: Meaning::TrailSurrogate(lo),
440
                    ..
441
                }),
442
0
            ) = (futf::classify(lhs, lhs.len() - 1), futf::classify(rhs, 0))
443
            {
444
0
                let mut fixup = imp::Fixup {
445
0
                    drop_left: 3,
446
0
                    drop_right: 3,
447
0
                    insert_len: 0,
448
0
                    insert_bytes: [0_u8; 4],
449
0
                };
450
451
0
                let n = 0x10000 + ((hi as u32) << 10) + (lo as u32);
452
453
0
                let ch = char::from_u32(n).expect(ERR);
454
0
                fixup.insert_len = ch.encode_utf8(&mut fixup.insert_bytes).len() as u32;
455
456
0
                return fixup;
457
0
            }
458
0
        }
459
460
0
        Default::default()
461
0
    }
462
}
463
464
/// Marker type for the single-byte encoding of the first 256 Unicode codepoints.
465
///
466
/// This is IANA's "ISO-8859-1". It's ISO's "ISO 8859-1" with the addition of the
467
/// C0 and C1 control characters from ECMA-48 / ISO 6429.
468
///
469
/// Not to be confused with WHATWG's "latin1" or "iso8859-1" labels (or the
470
/// many other aliases), which actually stand for Windows-1252.
471
#[derive(Copy, Clone, Default, Debug)]
472
pub struct Latin1;
473
474
unsafe impl Format for Latin1 {
475
    #[inline(always)]
476
0
    fn validate(_: &[u8]) -> bool {
477
0
        true
478
0
    }
479
480
    #[inline(always)]
481
0
    fn validate_prefix(_: &[u8]) -> bool {
482
0
        true
483
0
    }
484
485
    #[inline(always)]
486
0
    fn validate_suffix(_: &[u8]) -> bool {
487
0
        true
488
0
    }
489
490
    #[inline(always)]
491
0
    fn validate_subseq(_: &[u8]) -> bool {
492
0
        true
493
0
    }
494
}
495
496
unsafe impl<'a> CharFormat<'a> for Latin1 {
497
    type Iter = imp::SingleByteCharIndices<'a>;
498
499
    #[inline]
500
0
    unsafe fn char_indices(buf: &'a [u8]) -> imp::SingleByteCharIndices<'a> {
501
0
        imp::SingleByteCharIndices::new(buf)
502
0
    }
503
504
    #[inline]
505
0
    fn encode_char<F>(ch: char, cont: F) -> Result<(), ()>
506
0
    where
507
0
        F: FnOnce(&[u8]),
508
    {
509
0
        let n = ch as u32;
510
0
        if n > 0xFF {
511
0
            return Err(());
512
0
        }
513
0
        cont(&[n as u8]);
514
0
        Ok(())
515
0
    }
516
}