2323//! - mutator selection (via bit flags)
2424//! - arbitrary data seed for generation
2525//!
26- //! the generated pickles are validated using Python's `pickletools.dis ()` to ensure
27- //! they are structurally valid and can be parsed by the reference implementation.
28- //! this catches bugs in:
26+ //! the generated pickles are validated using Python's `pickletools.genops ()` (same
27+ //! validation logic as scripts/validate-pickles.py) to ensure they are structurally
28+ //! valid and can be parsed by the reference implementation. this catches bugs in:
2929//! - opcode emission logic
3030//! - stack simulation
3131//! - protocol version handling
5252//! Generated pickles must:
5353//! 1. Be non-empty
5454//! 2. End with STOP opcode (0x2e / '.')
55- //! 3. Successfully parse with `pickletools.dis ()`
55+ //! 3. Successfully parse with `pickletools.genops ()` (via Python subprocess)
5656#![ no_main]
5757
5858use libfuzzer_sys:: fuzz_target;
@@ -61,9 +61,29 @@ use pickle_whip::mutators::{
6161 BitFlipMutator , BoundaryMutator , OffByOneMutator ,
6262 StringLengthMutator , CharacterMutator , MemoIndexMutator ,
6363} ;
64+ use std:: process:: { Command , Stdio } ;
65+ use std:: io:: Write ;
6466
65- mod common;
66- use crate :: common:: validate_with_loads;
67+ /// Validate pickle using Python's pickletools.genops() (same as validate-pickles.py)
68+ fn validate_with_python ( pickle_bytes : & [ u8 ] ) -> bool {
69+ let mut child = match Command :: new ( "python3" )
70+ . arg ( "-c" )
71+ . arg ( "import sys, pickletools; list(pickletools.genops(sys.stdin.buffer.read()))" )
72+ . stdin ( Stdio :: piped ( ) )
73+ . stdout ( Stdio :: null ( ) )
74+ . stderr ( Stdio :: null ( ) )
75+ . spawn ( )
76+ {
77+ Ok ( child) => child,
78+ Err ( _) => return true , // Skip validation if Python unavailable
79+ } ;
80+
81+ if let Some ( mut stdin) = child. stdin . take ( ) {
82+ let _ = stdin. write_all ( pickle_bytes) ;
83+ }
84+
85+ child. wait ( ) . map ( |status| status. success ( ) ) . unwrap_or ( false )
86+ }
6787
6888fuzz_target ! ( |data: & [ u8 ] | {
6989 // need at least 7 bytes for configuration + some arbitrary data
@@ -75,7 +95,7 @@ fuzz_target!(|data: &[u8]| {
7595 let protocol = ( data[ 0 ] % 6 ) as usize ;
7696 let version = Version :: try_from( protocol) . unwrap( ) ;
7797
78- let mut gen = Generator :: new( version) ;
98+ let gen = Generator :: new( version) ;
7999
80100 // bytes 1-4: opcode range (min/max)
81101 let min_opcodes = u16 :: from_le_bytes( [ data[ 1 ] , data[ 2 ] ] ) as usize ;
@@ -126,5 +146,11 @@ fuzz_target!(|data: &[u8]| {
126146 // basic structural validation
127147 assert!( !pickle. is_empty( ) , "generated pickle must not be empty" ) ;
128148 assert_eq!( pickle[ pickle. len( ) - 1 ] , b'.' , "pickle must end with STOP opcode" ) ;
149+
150+ // validate with Python's pickletools
151+ assert!(
152+ validate_with_python( & pickle) ,
153+ "generated pickle failed Python validation"
154+ ) ;
129155 }
130156} ) ;
0 commit comments