Affix

 view release on metacpan or  search on metacpan

fuzz/fuzz_shared_lib.pl  view on Meta::CPAN

use v5.40;
use blib;
use lib 'blib/lib', 'lib';
use Affix               qw[:all];
use Test2::Tools::Affix qw[:all];
use Test2::V0 -no_srand => 1;
use Config;
use Data::Dumper;
$Data::Dumper::Terse = 1;
$|++;
#
my $max_iter = $ENV{FUZZ_MAX_ITER} // 1000;
my $timeout  = $ENV{FUZZ_TIMEOUT}  // 5;
my $verbose  = $ENV{FUZZ_VERBOSE}  // 0;
sub compile_source ($code) { compile_ok( <<~'' . $code ) }
	#include <inttypes.h>
	#include <locale.h>
	#include <math.h>
	#include <stdbool.h>
	#include <stddef.h>  // offsetof
	#include <stdio.h>
	#include <stdlib.h>  // malloc
	#include <string.h>
    #include <stdint.h>
	#include <wchar.h>
	// Some tests might actually include perl.h which has the real version of this
	#if !defined(warn)
	#define warn(FORMAT, ...)                                                          \
	    fprintf(stderr, FORMAT " at %s line %i\n", ##__VA_ARGS__, __FILE__, __LINE__); \
	    fflush(stderr);
	#endif
	#if defined _WIN32 || defined __CYGWIN__
	#include <BaseTsd.h>
	//typedef SSIZE_T ssize_t;
	typedef signed __int64 int64_t;
	#ifdef __GNUC__
	#define DLLEXPORT __attribute__((dllexport))
	#else
	#define DLLEXPORT __declspec(dllexport)
	#endif
	#else
	#ifdef __GNUC__
	#if __GNUC__ >= 4
	#define DLLEXPORT __attribute__((visibility("default")))
	#else
	#define DLLEXPORT __attribute__((dllimport))
	#endif
	#else
	#define DLLEXPORT __declspec(dllexport)
	#endif
	#include <inttypes.h>
	#include <sys/types.h>
	#endif
	//ext: .c


# Type catalog: { c_type, infix_sig, perl_type, gen_value }
# Ranges are derived from sizeof() at runtime so they match the actual platform.
my @PRIMITIVES;
{
    my @signed_ints = (
        [ 'int8_t',    'sint8',    sub { SInt8() } ],
        [ 'int16_t',   'sint16',   sub { SInt16() } ],
        [ 'int32_t',   'sint32',   sub { SInt32() } ],
        [ 'int64_t',   'sint64',   sub { SInt64() } ],
        [ 'char',      'char',     sub { Char() } ],
        [ 'short',     'short',    sub { Short() } ],
        [ 'int',       'int',      sub { Int() } ],
        [ 'long',      'long',     sub { Long() } ],
        [ 'long long', 'longlong', sub { LongLong() } ]
    );
    my @unsigned_ints = (
        [ 'uint8_t',            'uint8',     sub { UInt8() } ],
        [ 'uint16_t',           'uint16',    sub { UInt16() } ],
        [ 'uint32_t',           'uint32',    sub { UInt32() } ],
        [ 'uint64_t',           'uint64',    sub { UInt64() } ],
        [ 'unsigned char',      'uchar',     sub { UChar() } ],
        [ 'unsigned short',     'ushort',    sub { UShort() } ],
        [ 'unsigned int',       'uint',      sub { UInt() } ],
        [ 'unsigned long',      'ulong',     sub { ULong() } ],
        [ 'unsigned long long', 'ulonglong', sub { ULongLong() } ]
    );
    my @floats = ( [ 'float', 'float', sub { Float() } ], [ 'double', 'double', sub { Double() } ] );
    my @bool   = ( [ 'bool',  'bool',  sub { Bool() } ] );
    for my $entry (@signed_ints) {
        my ( $c, $sig, $perl ) = @$entry;
        my $bits = sizeof( $perl->() ) * 8;
        my $half = 2**( $bits - 1 );
        push @PRIMITIVES, { c => $c, sig => $sig, perl => $perl, gen => sub { int( rand( $half * 2 ) ) - $half } };
    }
    for my $entry (@unsigned_ints) {
        my ( $c, $sig, $perl ) = @$entry;
        my $bits = sizeof( $perl->() ) * 8;
        my $max  = 2**$bits;
        push @PRIMITIVES, { c => $c, sig => $sig, perl => $perl, gen => sub { int( rand($max) ) } };
    }
    for my $entry (@floats) {
        my ( $c, $sig, $perl ) = @$entry;
        my $prec = sizeof( $perl->() ) == 4 ? 2 : 4;
        push @PRIMITIVES, { c => $c, sig => $sig, perl => $perl, gen => sub { sprintf( "%.*f", $prec, rand(100) - 50 ) } };
    }
    for my $entry (@bool) {
        my ( $c, $sig, $perl ) = @$entry;
        push @PRIMITIVES, { c => $c, sig => $sig, perl => $perl, gen => sub { int( rand(2) ) } };
    }
}
my @SPECIAL = (
    { c => 'size_t',    sig => 'size_t',  perl => sub { Size_t() },  gen => sub { int( rand(1000) ) } },
    { c => 'ssize_t',   sig => 'ssize_t', perl => sub { SSize_t() }, gen => sub { int( rand(1000) ) } },
    { c => 'ptrdiff_t', sig => 'long',    perl => sub { Long() },    gen => sub { int( rand(200) ) - 100 } }
);
sub pick (@list) { $list[ int( rand(@list) ) ] }

sub pick_n ( $n, @list ) {
    my @shuffled = sort { rand(1) <=> rand(1) } @list;
    return @shuffled[ 0 .. $n - 1 ] if $n <= @list;
    return @shuffled;

fuzz/fuzz_shared_lib.pl  view on Meta::CPAN

            },
        ],
        verify => sub ($result) {
            my $expected = $captured_struct->{m0};
            is( $result, $expected,
                "callback struct-by-value roundtrip (expected: $expected, got: " . ( defined $result ? $result : 'undef' ) . ")" );
        },
    };
}

# Callback with struct return (reverse trampoline returning struct by value)
sub generate_callback_struct_ret_fn {
    my ($fn_name) = @_;
    my @all = grep {
        $_->{c} ne 'float'             &&
            $_->{c} ne 'double'        &&
            $_->{c} ne 'char'          &&
            $_->{c} ne 'unsigned char' &&
            $_->{c} ne 'bool'          &&
            sizeof( $_->{perl}->() )
            <= 4
    } @PRIMITIVES;
    my @fields      = pick_n( 2, @all );           # 2-field struct
    my $struct_name = 'CR' . int( rand(99999) );
    my ( @struct_members, @struct_perl_fields, @struct_sig_fields );
    for my $i ( 0 .. $#fields ) {
        my $f = $fields[$i];
        push @struct_members,     "$f->{c} m$i;";
        push @struct_perl_fields, "m$i", $f->{perl}->();
        push @struct_sig_fields,  "m$i:" . $f->{sig};
    }
    my $struct_body = join( ' ', @struct_members );
    my $struct_sig  = '{' . join( ',', @struct_sig_fields ) . '}';
    my $cb_name     = 'cb_' . int( rand(99999) );

    # C: callback takes int arg, returns struct; wrapper calls it and returns m0
    my $ret_c  = $fields[0]->{c};
    my $c_code = <<"END_C";
typedef struct { $struct_body } $struct_name;

typedef $struct_name (*$cb_name)(int);

$ret_c $fn_name($cb_name op, int x) {
    $struct_name s = op(x);
    return s.m0;
}
END_C
    my $perl_struct = Struct [@struct_perl_fields];
    my $val         = $fields[0]->{gen}->();
    return {
        c_code     => $c_code,
        c_name     => $fn_name,
        sig_args   => [ "(*($struct_sig)->int)", 'int' ],
        sig_ret    => $fields[0]->{sig},
        perl_args  => [ Callback [ [ Int() ] => $perl_struct ], Int() ],
        perl_ret   => $fields[0]->{perl}->(),
        gen_values => [
            sub {
                sub ($x) { { m0 => $val } }    # callback: return struct with m0 = val
            },
            sub {$val},                        # argument x (ignored by callback logic)
        ],
        verify => sub ($result) {
            my $ok = $result == $val;
            unless ($ok) {
                diag "callback struct ret: got=$result expected=$val";
            }
            ok( $ok, "callback struct-return roundtrip" );
        },
    };
}

# Bitfield struct (random widths 1-31, tests bitfield layout/marshalling)
sub generate_bitfield_fn {
    my ($fn_name)   = @_;
    my $struct_name = 'BF' . int( rand(99999) );
    my $nfields     = 2 + int( rand(2) );          # 2..3 bitfields
    my ( @struct_members, @struct_perl_fields, @struct_sig_fields, @c_params, @perl_args, @sig_args, @gen_values );
    my @widths;
    my @values;
    for my $i ( 0 .. $nfields - 1 ) {
        my $width = 1 + int( rand(31) );           # 1..31 bits
        push @widths, $width;
        my $max_val = ( 1 << $width ) - 1;
        my $val     = int( rand( $max_val + 1 ) );
        push @values, $val;
        my $fname = 'm' . $i;
        push @struct_members,     "uint32_t $fname : $width;";
        push @struct_perl_fields, $fname, UInt32(), $width;
        push @struct_sig_fields,  "$fname:uint32:$width";
        my $pname = "p$i";
        push @c_params,  "uint32_t $pname";
        push @perl_args, UInt32();
        push @sig_args,  'uint';
    }
    my $struct_body = join( ' ', @struct_members );
    my $struct_sig  = '{' . join( ',', @struct_sig_fields ) . '}';

    # C: take struct by value, return sum of all bitfield members
    my $sum_expr = join( ' + ', map {"s.m$_"} 0 .. $nfields - 1 );
    my $c_code   = <<"END_C";
typedef struct { $struct_body } $struct_name;

uint32_t $fn_name($struct_name s) {
    return $sum_expr;
}
END_C
    my $struct_type = Struct [@struct_perl_fields];
    my $expected    = 0;
    $expected += $_ for @values;
    return {
        c_code     => $c_code,
        c_name     => $fn_name,
        sig_args   => [$struct_sig],
        sig_ret    => 'uint',
        perl_args  => [$struct_type],
        perl_ret   => UInt32(),
        gen_values => [
            sub {
                my %h;
                for my $i ( 0 .. $nfields - 1 ) {



( run in 1.293 second using v1.01-cache-2.11-cpan-3fabe0161c3 )