oxidize_pdf/parser/
stack_safe_tests.rs1#[cfg(test)]
7mod tests {
8 use super::super::objects::{PdfArray, PdfDictionary, PdfObject};
9 use super::super::stack_safe::{RecursionGuard, ReferenceStackGuard, StackSafeContext};
10 use super::super::ParseError;
11 use std::time::Duration;
12
13 #[test]
14 fn test_deep_recursion_limit() {
15 let mut context = StackSafeContext::with_limits(10, 60);
16
17 for i in 0..10 {
19 let result = context.enter();
20 assert!(result.is_ok(), "Failed at depth {i}");
21 }
22
23 let result = context.enter();
25 assert!(result.is_err(), "Should have failed at depth 11");
26
27 for _ in 0..10 {
29 context.exit();
30 }
31 }
32
33 #[test]
34 fn test_timeout_protection() {
35 let context = StackSafeContext::with_limits(1000, 0); std::thread::sleep(Duration::from_millis(10));
39 let result = context.check_timeout();
40 assert!(result.is_err(), "Should have timed out");
41 }
42
43 #[test]
44 fn test_circular_reference_detection() {
45 let mut context = StackSafeContext::new();
46
47 assert!(context.push_ref(1, 0).is_ok());
49
50 assert!(context.push_ref(1, 0).is_err());
52
53 context.pop_ref();
55 assert!(context.push_ref(1, 1).is_ok());
56
57 context.pop_ref();
59 assert!(context.push_ref(1, 0).is_ok());
60 }
61
62 #[test]
63 fn test_recursion_guard_raii() {
64 let mut context = StackSafeContext::new();
65 assert_eq!(context.depth, 0);
66
67 {
68 let _guard = RecursionGuard::new(&mut context).unwrap();
69 } assert_eq!(context.depth, 0);
73 }
74
75 #[test]
76 fn test_reference_stack_guard_raii() {
77 let mut context = StackSafeContext::new();
78
79 {
80 let _guard = ReferenceStackGuard::new(&mut context, 5, 0).unwrap();
81 } assert!(context.push_ref(5, 0).is_ok());
86 }
87
88 #[test]
89 fn test_nested_guards() {
90 let mut context = StackSafeContext::with_limits(5, 60);
91
92 {
94 let _guard1 = RecursionGuard::new(&mut context).unwrap();
95 }
97 assert_eq!(context.depth, 0); {
100 let _guard2 = RecursionGuard::new(&mut context).unwrap();
101 }
103 assert_eq!(context.depth, 0); for _ in 0..3 {
107 let result = RecursionGuard::new(&mut context);
108 assert!(result.is_ok());
109 }
111
112 assert_eq!(context.depth, 0);
113 }
114
115 #[test]
116 fn test_guard_failure_cleanup() {
117 let mut context = StackSafeContext::with_limits(2, 60);
118
119 context.enter().unwrap(); context.enter().unwrap(); let result = context.enter();
125 assert!(result.is_err());
126
127 assert_eq!(context.depth, 2);
129
130 context.exit(); context.exit(); assert_eq!(context.depth, 0);
134 }
135
136 #[test]
137 fn test_child_context() {
138 let mut parent_context = StackSafeContext::with_limits(100, 60);
139 parent_context.depth = 10;
140 parent_context.push_ref(1, 0).unwrap();
141 parent_context.pop_ref(); let child_context = parent_context.child();
144
145 assert_eq!(child_context.depth, 10);
147 assert_eq!(child_context.max_depth, 100);
148 assert!(child_context.completed_refs.contains(&(1, 0)));
149
150 assert_eq!(child_context.start_time, parent_context.start_time);
152 }
153
154 #[test]
157 fn test_deeply_nested_arrays() {
158 let mut nested_array = PdfObject::Integer(42);
160
161 for _ in 0..50 {
163 let array = PdfArray(vec![nested_array]);
164 nested_array = PdfObject::Array(array);
165 }
166
167 match nested_array {
170 PdfObject::Array(_) => {
171 assert!(true);
173 }
174 _ => panic!("Expected array"),
175 }
176 }
177
178 #[test]
179 fn test_deeply_nested_dictionaries() {
180 let mut nested_dict = PdfObject::Integer(42);
182
183 for i in 0..50 {
185 let mut dict = PdfDictionary::new();
186 dict.insert(format!("level_{i}"), nested_dict);
187 nested_dict = PdfObject::Dictionary(dict);
188 }
189
190 match nested_dict {
192 PdfObject::Dictionary(_) => {
193 assert!(true);
195 }
196 _ => panic!("Expected dictionary"),
197 }
198 }
199
200 #[test]
201 fn test_malicious_reference_chain() {
202 let mut context = StackSafeContext::new();
204
205 let refs = [(1, 0), (2, 0), (3, 0), (4, 0), (5, 0)];
207
208 for &(obj, gen) in &refs {
210 assert!(context.push_ref(obj, gen).is_ok());
211 }
212
213 assert!(context.push_ref(1, 0).is_err());
215 }
216
217 #[test]
218 fn test_stack_safe_context_defaults() {
219 let context = StackSafeContext::new();
220
221 assert_eq!(context.depth, 0);
222 assert_eq!(context.max_depth, 1000);
223 assert!(context.active_stack.is_empty());
224 assert!(context.completed_refs.is_empty());
225 assert_eq!(context.timeout, Duration::from_secs(120)); }
227
228 #[test]
229 fn test_stack_safe_context_custom_limits() {
230 let context = StackSafeContext::with_limits(500, 10);
231
232 assert_eq!(context.depth, 0);
233 assert_eq!(context.max_depth, 500);
234 assert!(context.active_stack.is_empty());
235 assert!(context.completed_refs.is_empty());
236 assert_eq!(context.timeout, Duration::from_secs(10));
237 }
238
239 #[test]
240 fn test_multiple_reference_generations() {
241 let mut context = StackSafeContext::new();
242
243 assert!(context.push_ref(1, 0).is_ok());
245 assert!(context.push_ref(1, 1).is_ok());
246 assert!(context.push_ref(1, 2).is_ok());
247
248 assert!(context.push_ref(1, 0).is_err());
250 assert!(context.push_ref(1, 1).is_err());
251 assert!(context.push_ref(1, 2).is_err());
252 }
253
254 #[test]
255 fn test_context_exit_idempotent() {
256 let mut context = StackSafeContext::new();
257
258 context.exit();
260 assert_eq!(context.depth, 0);
261
262 context.enter().unwrap();
264 assert_eq!(context.depth, 1);
265 context.exit();
266 assert_eq!(context.depth, 0);
267
268 context.exit();
270 context.exit();
271 assert_eq!(context.depth, 0);
272 }
273
274 #[test]
275 fn test_performance_with_many_references() {
276 let mut context = StackSafeContext::new();
277
278 for obj_num in 0..1000 {
280 assert!(context.push_ref(obj_num, 0).is_ok());
281 context.pop_ref(); }
283
284 assert!(context.push_ref(500, 0).is_ok());
287 context.pop_ref();
288 assert!(context.push_ref(1001, 0).is_ok()); }
290
291 #[test]
292 fn test_error_messages_informatve() {
293 let mut context = StackSafeContext::with_limits(2, 1); context.enter().unwrap();
297 context.enter().unwrap();
298 let depth_error = context.enter().err().unwrap();
299 if let ParseError::SyntaxError { message, .. } = depth_error {
300 assert!(message.contains("Maximum recursion depth exceeded"));
301 assert!(message.contains("3"));
302 assert!(message.contains("2"));
303 } else {
304 panic!("Expected SyntaxError for depth limit");
305 }
306
307 context.push_ref(10, 5).unwrap();
309 let cycle_error = context.push_ref(10, 5).err().unwrap();
310 if let ParseError::SyntaxError { message, .. } = cycle_error {
311 assert!(message.contains("Circular reference detected"));
312 assert!(message.contains("10 5 R"));
313 } else {
314 panic!("Expected SyntaxError for circular reference");
315 }
316
317 std::thread::sleep(Duration::from_millis(1100));
319 let timeout_error = context.check_timeout().err().unwrap();
320 if let ParseError::SyntaxError { message, .. } = timeout_error {
321 assert!(message.contains("Parsing timeout exceeded"));
322 assert!(message.contains("1s"));
323 } else {
324 panic!("Expected SyntaxError for timeout");
325 }
326 }
327}