@@ -433,6 +433,64 @@ public void test_prepare_v3_span_tolerates_uninitialised_pzTail()
433433 GC . KeepAlive ( rolling ) ;
434434 }
435435
436+ // Audit #1: the string overloads of prepare_v2/v3 used to throw
437+ // ArgumentOutOfRangeException on the same MISUSE path because they
438+ // unconditionally sliced the (now empty) tail span. These tests
439+ // assert they return rc cleanly with an empty tail string instead.
440+ [ Fact ]
441+ public void test_prepare_v2_string_tolerates_uninitialised_pzTail ( )
442+ {
443+ var rolling = new byte [ 256 ] [ ] ;
444+ var rnd = new Random ( 42 ) ;
445+ for ( var i = 0 ; i < 512 ; i ++ )
446+ {
447+ rolling [ i % rolling . Length ] = new byte [ rnd . Next ( 1024 , 128 * 1024 ) ] ;
448+
449+ var db = ugly . open ( ":memory:" ) ;
450+ db . manual_close_v2 ( ) ;
451+ try
452+ {
453+ int rc = raw . sqlite3_prepare_v2 ( db , "SELECT 1;" , out var stmt , out string tail ) ;
454+
455+ Assert . Equal ( raw . SQLITE_MISUSE , rc ) ;
456+ Assert . Equal ( "" , tail ) ;
457+ stmt ? . Dispose ( ) ;
458+ }
459+ finally
460+ {
461+ db . Dispose ( ) ;
462+ }
463+ }
464+ GC . KeepAlive ( rolling ) ;
465+ }
466+
467+ [ Fact ]
468+ public void test_prepare_v3_string_tolerates_uninitialised_pzTail ( )
469+ {
470+ var rolling = new byte [ 256 ] [ ] ;
471+ var rnd = new Random ( 42 ) ;
472+ for ( var i = 0 ; i < 512 ; i ++ )
473+ {
474+ rolling [ i % rolling . Length ] = new byte [ rnd . Next ( 1024 , 128 * 1024 ) ] ;
475+
476+ var db = ugly . open ( ":memory:" ) ;
477+ db . manual_close_v2 ( ) ;
478+ try
479+ {
480+ int rc = raw . sqlite3_prepare_v3 ( db , "SELECT 1;" , 0 , out var stmt , out string tail ) ;
481+
482+ Assert . Equal ( raw . SQLITE_MISUSE , rc ) ;
483+ Assert . Equal ( "" , tail ) ;
484+ stmt ? . Dispose ( ) ;
485+ }
486+ finally
487+ {
488+ db . Dispose ( ) ;
489+ }
490+ }
491+ GC . KeepAlive ( rolling ) ;
492+ }
493+
436494 [ Fact ]
437495 public void test_bind_parameter_index ( )
438496 {
0 commit comments