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

kaidokert / picojson-rs / 16083236704

05 Jul 2025 01:35AM UTC coverage: 94.358% (-0.2%) from 94.535%
16083236704

push

github

web-flow
Panic free for SliceParser (#27)

* Panic free for SliceParser

198 of 211 new or added lines in 11 files covered. (93.84%)

1 existing line in 1 file now uncovered.

2191 of 2322 relevant lines covered (94.36%)

132.6 hits per line

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

93.29
/picojson/src/ujson/bitstack/mod.rs
1
// SPDX-License-Identifier: Apache-2.0
2

3
use core::cmp::PartialEq;
4
use core::ops::{BitAnd, BitOr, Shl, Shr};
5

6
/// Trait for bit buckets - provides bit storage for JSON parser state.
7
/// This trait is implemented for both integer and [T; N] types.
8
///
9
/// NOTE: BitBucket implementations do NOT implement depth tracking.
10
/// This is the responsibility of the caller.
11
pub trait BitBucket: Default {
12
    /// Pushes a bit (true for 1, false for 0) onto the stack.
13
    fn push(&mut self, bit: bool);
14
    /// Pops the top bit off the stack, returning it if the stack isn’t empty.
15
    fn pop(&mut self) -> bool;
16
    /// Returns the top bit without removing it.
17
    fn top(&self) -> bool;
18
}
19

20
/// Automatic implementation for builtin-types ( u8, u32 etc ).
21
/// Any type that implements the required traits is automatically implemented for BitBucket.
22
impl<T> BitBucket for T
23
where
24
    T: Shl<u8, Output = T>
25
        + Shr<u8, Output = T>
26
        + BitAnd<T, Output = T>
27
        + BitOr<Output = T>
28
        + PartialEq
29
        + Clone
30
        + Default,
31
    T: From<u8>, // To create 0 and 1 constants
32
{
33
    fn push(&mut self, bit: bool) {
426✔
34
        *self = (self.clone() << 1u8) | T::from(bit as u8);
426✔
35
    }
426✔
36

37
    fn pop(&mut self) -> bool {
121✔
38
        let bit = (self.clone() & T::from(1)) != T::from(0);
121✔
39
        *self = self.clone() >> 1u8;
121✔
40
        bit
121✔
41
    }
121✔
42

43
    fn top(&self) -> bool {
240✔
44
        (self.clone() & T::from(1)) != T::from(0)
240✔
45
    }
240✔
46
}
47

48
// TODO: Can this be implemented for slices as well ?
49

50
/// Trait for depth counters - tracks nesting depth.
51
///
52
/// This trait provides overflow-safe operations for tracking JSON nesting depth.
53
/// Implemented for all unsigned integer types.
54
pub trait DepthCounter: core::fmt::Debug + Copy {
55
    /// Create a zero depth value
56
    fn zero() -> Self;
57

58
    /// Increment depth, returning (new_value, overflow_occurred)
59
    fn increment(self) -> (Self, bool);
60

61
    /// Decrement depth, returning (new_value, underflow_occurred)
62
    fn decrement(self) -> (Self, bool);
63

64
    /// Check if depth is zero
65
    fn is_zero(self) -> bool;
66
}
67

68
macro_rules! impl_depth_counter {
69
    ($($t:ty),*) => {
70
        $(
71
            impl DepthCounter for $t {
72
                #[inline]
73
                fn zero() -> Self { 0 }
176✔
74

75
                #[inline]
76
                fn increment(self) -> (Self, bool) { self.overflowing_add(1) }
431✔
77

78
                #[inline]
79
                fn decrement(self) -> (Self, bool) { self.overflowing_sub(1) }
125✔
80

81
                #[inline]
82
                fn is_zero(self) -> bool { self == 0 }
781✔
83
            }
84
        )*
85
    };
86
}
87

88
// Implement for all unsigned integer types
89
impl_depth_counter!(u8, u16, u32, u64, u128, usize);
90

91
/// Configuration trait for BitStack systems - defines bucket and counter types.
92
pub trait BitStackConfig {
93
    type Bucket: BitBucket + Default;
94
    type Counter: DepthCounter + Default;
95
}
96

97
/// Default depth configuration using [u32] for tracking bits and [u8] for counting depth.
98
pub struct DefaultConfig;
99

100
impl BitStackConfig for DefaultConfig {
101
    type Bucket = u32;
102
    type Counter = u8;
103
}
104
/// BitStack configuration for custom bit depth parsing.
105
///
106
/// Allows specifying custom types for bit storage and depth counting.
107
///
108
/// # Type Parameters
109
///
110
/// * `B` - The bit bucket type used for storing the bit stack. Must implement [`BitBucket`].
111
/// * `C` - The counter type used for tracking nesting depth. Must implement [`DepthCounter`].
112
///
113
/// Example: `BitStack<u64, u16>` for 64-bit nesting depth with 16 counter.
114
pub struct BitStackStruct<B, C> {
115
    _phantom: core::marker::PhantomData<(B, C)>,
116
}
117

118
impl<B, C> BitStackConfig for BitStackStruct<B, C>
119
where
120
    B: BitBucket + Default,
121
    C: DepthCounter + Default,
122
{
123
    type Bucket = B;
124
    type Counter = C;
125
}
126

127
/// Array-based BitStack implementation for large storage capacity.
128
///
129
/// Example use:
130
/// ```rust
131
/// # use picojson::{SliceParser, ArrayBitStack};
132
/// let parser = SliceParser::<ArrayBitStack<10, u32, u16>>::with_config("{}");
133
/// ```
134
/// This defines a 10-element array of [u32] for depth tracking bits, with a [u16] counter, allowing 320 levels of depth.
135
pub type ArrayBitStack<const N: usize, T, D> = BitStackStruct<ArrayBitBucket<N, T>, D>;
136

137
/// Array-based BitBucket implementation for large storage capacity.
138
///
139
/// Provides large BitBucket storage using multiple elements.
140
/// This can be used to parse very deeply nested JSON.
141
///
142
/// Use the [ArrayBitStack] convenience wrapper to create this.
143
#[derive(Debug)]
144
pub struct ArrayBitBucket<const N: usize, T>(pub [T; N]);
145

146
impl<const N: usize, T: Default + Copy> Default for ArrayBitBucket<N, T> {
147
    fn default() -> Self {
11✔
148
        ArrayBitBucket([T::default(); N])
11✔
149
    }
11✔
150
}
151

152
impl<const N: usize, T> BitBucket for ArrayBitBucket<N, T>
153
where
154
    T: Shl<u8, Output = T>
155
        + Shr<u8, Output = T>
156
        + BitAnd<T, Output = T>
157
        + core::ops::BitOr<Output = T>
158
        + PartialEq
159
        + Clone
160
        + From<u8>
161
        + Copy
162
        + Default,
163
{
164
    fn push(&mut self, bit: bool) {
78✔
165
        // Strategy: Use array as big-endian storage, with leftmost element as most significant
166
        // Shift all elements left, carrying overflow from right to left
167
        let bit_val = T::from(bit as u8);
78✔
168
        let mut carry = bit_val;
78✔
169
        let element_bits = (core::mem::size_of::<T>() * 8) as u8;
78✔
170
        let msb_shift = element_bits.saturating_sub(1);
78✔
171

172
        // Start from the rightmost (least significant) element and work left
173
        for i in (0..N).rev() {
276✔
174
            let old_msb = if let Some(element) = self.0.get(i) {
276✔
175
                (*element >> msb_shift) & T::from(1) // Extract MSB that will be lost
276✔
176
            } else {
NEW
177
                continue;
×
178
            };
179
            if let Some(element_mut) = self.0.get_mut(i) {
276✔
180
                *element_mut = (*element_mut << 1u8) | carry;
276✔
181
            }
276✔
182
            carry = old_msb;
276✔
183
        }
184
        // Note: carry from leftmost element is discarded (overflow)
185
    }
78✔
186

187
    fn pop(&mut self) -> bool {
86✔
188
        // Safely get the last element, returning false if N is 0.
189
        let bit = if let Some(last_element) = self.0.get(N.saturating_sub(1)) {
86✔
190
            (*last_element & T::from(1)) != T::from(0)
86✔
191
        } else {
NEW
192
            return false;
×
193
        };
194

195
        // Shift all elements right, carrying underflow from left to right
196
        let mut carry = T::from(0);
86✔
197
        let element_bits = (core::mem::size_of::<T>() * 8) as u8;
86✔
198
        let msb_shift = element_bits.saturating_sub(1);
86✔
199

200
        // Start from the leftmost (most significant) element and work right
201
        for i in 0..N {
373✔
202
            let old_lsb = if let Some(element) = self.0.get(i) {
287✔
203
                *element & T::from(1) // Extract LSB that will be lost
287✔
204
            } else {
NEW
205
                continue;
×
206
            };
207
            if let Some(element_mut) = self.0.get_mut(i) {
287✔
208
                *element_mut = (*element_mut >> 1u8) | (carry << msb_shift);
287✔
209
            }
287✔
210
            carry = old_lsb;
287✔
211
        }
212

213
        bit
86✔
214
    }
86✔
215

216
    fn top(&self) -> bool {
19✔
217
        // Safely get the last element, returning false if N is 0.
218
        if let Some(last_element) = self.0.get(N.saturating_sub(1)) {
19✔
219
            (*last_element & T::from(1)) != T::from(0)
19✔
220
        } else {
NEW
221
            false
×
222
        }
223
    }
19✔
224
}
225

226
#[cfg(test)]
227
mod tests {
228
    use super::*;
229

230
    #[test]
231
    fn test_bitstack() {
1✔
232
        let mut bitstack = 0;
1✔
233
        bitstack.push(true);
1✔
234
        bitstack.push(false);
1✔
235
        assert_eq!(bitstack.pop(), false);
1✔
236
        assert_eq!(bitstack.pop(), true);
1✔
237
    }
1✔
238

239
    #[test]
240
    fn test_array_bitstack() {
1✔
241
        // Test ArrayBitStack with 2 u8 elements (16-bit total capacity)
242
        let mut bitstack: ArrayBitBucket<2, u8> = ArrayBitBucket::default();
1✔
243

244
        // Test basic push/pop operations
245
        bitstack.push(true);
1✔
246
        bitstack.push(false);
1✔
247
        bitstack.push(true);
1✔
248

249
        // Verify top() doesn't modify stack
250
        assert_eq!(bitstack.top(), true);
1✔
251
        assert_eq!(bitstack.top(), true);
1✔
252

253
        // Verify LIFO order
254
        assert_eq!(bitstack.pop(), true);
1✔
255
        assert_eq!(bitstack.pop(), false);
1✔
256
        assert_eq!(bitstack.pop(), true);
1✔
257
    }
1✔
258

259
    #[test]
260
    fn test_array_bitstack_large_capacity() {
1✔
261
        // Test larger ArrayBitStack (320-bit capacity with 10 u32 elements)
262
        let mut bitstack: ArrayBitBucket<10, u32> = ArrayBitBucket::default();
1✔
263

264
        // Push many bits to test multi-element handling
265
        let pattern = [true, false, true, true, false, false, true, false];
1✔
266
        for &bit in &pattern {
9✔
267
            bitstack.push(bit);
8✔
268
        }
8✔
269

270
        // Pop and verify reverse order (LIFO)
271
        for &expected in pattern.iter().rev() {
8✔
272
            assert_eq!(bitstack.pop(), expected);
8✔
273
        }
274
    }
1✔
275

276
    #[test]
277
    fn test_element_size_handling() {
1✔
278
        // Test that bitstack correctly handles different element sizes
279

280
        // Test u8 elements (8-bit each)
281
        let mut bitstack_u8: ArrayBitBucket<1, u8> = ArrayBitBucket::default();
1✔
282

283
        // Fill all 8 bits of a u8 element
284
        for i in 0..8 {
9✔
285
            bitstack_u8.push(i % 2 == 0); // alternating pattern: true, false, true, false...
8✔
286
        }
8✔
287

288
        // Verify we can retrieve all 8 bits in LIFO order
289
        for i in (0..8).rev() {
8✔
290
            assert_eq!(bitstack_u8.pop(), i % 2 == 0);
8✔
291
        }
292

293
        // Test u32 elements (32-bit each)
294
        let mut bitstack_u32: ArrayBitBucket<1, u32> = ArrayBitBucket::default();
1✔
295

296
        // Fill all 32 bits of a u32 element
297
        for i in 0..32 {
33✔
298
            bitstack_u32.push(i % 3 == 0); // pattern: true, false, false, true, false, false...
32✔
299
        }
32✔
300

301
        // Verify we can retrieve all 32 bits in LIFO order
302
        for i in (0..32).rev() {
32✔
303
            assert_eq!(bitstack_u32.pop(), i % 3 == 0);
32✔
304
        }
305
    }
1✔
306

307
    #[test]
308
    fn test_array_bitstack_basic_moved() {
1✔
309
        // Test ArrayBitStack with 2 u8 elements (16-bit total capacity)
310
        let mut bitstack: ArrayBitBucket<2, u8> = ArrayBitBucket::default();
1✔
311

312
        // Test basic push/pop operations
313
        bitstack.push(true);
1✔
314
        bitstack.push(false);
1✔
315
        bitstack.push(true);
1✔
316

317
        // Verify top() doesn't modify stack
318
        assert_eq!(bitstack.top(), true);
1✔
319
        assert_eq!(bitstack.top(), true);
1✔
320

321
        // Verify LIFO order
322
        assert_eq!(bitstack.pop(), true);
1✔
323
        assert_eq!(bitstack.pop(), false);
1✔
324
        assert_eq!(bitstack.pop(), true);
1✔
325
    }
1✔
326

327
    #[test]
328
    fn test_array_bitstack_large_capacity_moved() {
1✔
329
        // Test larger ArrayBitStack (320-bit capacity with 10 u32 elements)
330
        let mut bitstack: ArrayBitBucket<10, u32> = ArrayBitBucket::default();
1✔
331

332
        // Push many bits to test multi-element handling
333
        let pattern = [true, false, true, true, false, false, true, false];
1✔
334
        for &bit in &pattern {
9✔
335
            bitstack.push(bit);
8✔
336
        }
8✔
337

338
        // Pop and verify reverse order (LIFO)
339
        for &expected in pattern.iter().rev() {
8✔
340
            assert_eq!(bitstack.pop(), expected);
8✔
341
        }
342
    }
1✔
343

344
    #[test]
345
    fn test_array_bitstack_element_overflow_moved() {
1✔
346
        // Test ArrayBitStack with 2 u8 elements to verify cross-element operations
347
        let mut bitstack: ArrayBitBucket<2, u8> = ArrayBitBucket::default();
1✔
348

349
        // Push more than 8 bits to force usage of multiple elements
350
        let bits = [
1✔
351
            true, false, true, false, true, false, true, false, true, true,
1✔
352
        ];
1✔
353
        for &bit in &bits {
11✔
354
            bitstack.push(bit);
10✔
355
        }
10✔
356

357
        // Pop all bits and verify order
358
        for &expected in bits.iter().rev() {
10✔
359
            assert_eq!(bitstack.pop(), expected);
10✔
360
        }
361
    }
1✔
362

363
    #[test]
364
    fn test_array_bitstack_empty_behavior_moved() {
1✔
365
        // Test behavior when popping from an empty ArrayBitStack
366
        // With the new API, empty stacks return false (no depth tracking needed)
367
        let mut bitstack: ArrayBitBucket<2, u8> = ArrayBitBucket::default();
1✔
368

369
        // CURRENT BEHAVIOR: Empty stack returns false (was Some(false) before API change)
370
        // This behavior is now the intended design - no depth tracking needed
371
        assert!(!bitstack.pop(), "Empty stack returns false");
1✔
372
        assert!(!bitstack.top(), "Empty stack top() returns false");
1✔
373

374
        // Test that underflow doesn't panic (at least it's safe)
375
        assert_eq!(
1✔
376
            bitstack.pop(),
1✔
377
            false,
378
            "Multiple underflow calls don't panic"
×
379
        );
380
        assert_eq!(
1✔
381
            bitstack.pop(),
1✔
382
            false,
383
            "Multiple underflow calls don't panic"
×
384
        );
385
    }
1✔
386

387
    #[test]
388
    fn test_array_bitstack_underflow_does_not_panic_moved() {
1✔
389
        // Test that multiple underflow attempts are safe (don't panic)
390
        // This is important for robustness even with the current incorrect API
391
        let mut bitstack: ArrayBitBucket<1, u8> = ArrayBitBucket::default();
1✔
392

393
        // Multiple calls to pop() on empty stack should not panic
394
        for i in 0..5 {
6✔
395
            let result = bitstack.pop();
5✔
396
            // With new API, just ensure it doesn't panic and returns a bool
397
            assert_eq!(
5✔
398
                result,
399
                false,
400
                "Empty ArrayBitStack pop() attempt {} should return false",
×
401
                i + 1
×
402
            );
403

404
            let top_result = bitstack.top();
5✔
405
            assert_eq!(
5✔
406
                top_result,
407
                false,
408
                "Empty ArrayBitStack top() attempt {} should return false",
×
409
                i + 1
×
410
            );
411
        }
412
    }
1✔
413
}
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