@@ -501,6 +501,63 @@ static int fuzz_elementtree_parsewhole(const char* data, size_t size) {
501501 return 0 ;
502502}
503503
504+ #define MAX_PYCOMPILE_TEST_SIZE 16384
505+ static char pycompile_scratch [MAX_PYCOMPILE_TEST_SIZE ];
506+
507+ static const int start_vals [] = {Py_eval_input , Py_single_input , Py_file_input };
508+ const size_t NUM_START_VALS = sizeof (start_vals ) / sizeof (start_vals [0 ]);
509+
510+ static const int optimize_vals [] = {-1 , 0 , 1 , 2 };
511+ const size_t NUM_OPTIMIZE_VALS = sizeof (optimize_vals ) / sizeof (optimize_vals [0 ]);
512+
513+ /* Fuzz `PyCompileStringExFlags` using a variety of input parameters.
514+ * That function is essentially behind the `compile` builtin */
515+ static int fuzz_pycompile (const char * data , size_t size ) {
516+ // Ignore overly-large inputs, and account for a NUL terminator
517+ if (size > MAX_PYCOMPILE_TEST_SIZE - 1 ) {
518+ return 0 ;
519+ }
520+
521+ // Need 2 bytes for parameter selection
522+ if (size < 2 ) {
523+ return 0 ;
524+ }
525+
526+ // Use first byte to determine element of `start_vals` to use
527+ unsigned char start_idx = (unsigned char ) data [0 ];
528+ int start = start_vals [start_idx % NUM_START_VALS ];
529+
530+ // Use second byte to determine element of `optimize_vals` to use
531+ unsigned char optimize_idx = (unsigned char ) data [1 ];
532+ int optimize = optimize_vals [optimize_idx % NUM_OPTIMIZE_VALS ];
533+
534+ // Create a NUL-terminated C string from the remaining input
535+ memcpy (pycompile_scratch , data + 2 , size - 2 );
536+ // Put a NUL terminator just after the copied data. (Space was reserved already.)
537+ pycompile_scratch [size - 2 ] = '\0' ;
538+
539+ // XXX: instead of always using NULL for the `flags` value to
540+ // `Py_CompileStringExFlags`, there are many flags that conditionally
541+ // change parser behavior:
542+ //
543+ // #define PyCF_TYPE_COMMENTS 0x1000
544+ // #define PyCF_ALLOW_TOP_LEVEL_AWAIT 0x2000
545+ // #define PyCF_ONLY_AST 0x0400
546+ //
547+ // It would be good to test various combinations of these, too.
548+ PyCompilerFlags * flags = NULL ;
549+
550+ PyObject * result = Py_CompileStringExFlags (pycompile_scratch , "<fuzz input>" , start , flags , optimize );
551+ if (result == NULL ) {
552+ /* compilation failed, most likely from a syntax error */
553+ PyErr_Clear ();
554+ } else {
555+ Py_DECREF (result );
556+ }
557+
558+ return 0 ;
559+ }
560+
504561/* Run fuzzer and abort on failure. */
505562static int _run_fuzz (const uint8_t * data , size_t size , int (* fuzzer )(const char * , size_t )) {
506563 int rv = fuzzer ((const char * ) data , size );
@@ -642,6 +699,9 @@ int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
642699 }
643700
644701 rv |= _run_fuzz (data , size , fuzz_elementtree_parsewhole );
702+ #endif
703+ #if !defined(_Py_FUZZ_ONE ) || defined(_Py_FUZZ_fuzz_pycompile )
704+ rv |= _run_fuzz (data , size , fuzz_pycompile );
645705#endif
646706 return rv ;
647707}
0 commit comments