Affix

 view release on metacpan or  search on metacpan

t/092_struct_signature_layout.t  view on Meta::CPAN

use Test2::V0 -no_srand => 1;
use Affix qw[:all];
#
# Regression test for struct member layout when a struct type is declared
# inline in a function signature (as opposed to a typedef-registered type).
#
# Structs inside function signatures used to keep the offsets computed at
# parse time, while their members were still unresolved named types (size 0).
# The layout-recalc pass (Layout stage) never descended into function
# signatures, so every member ended up at the same offset. Bindings such as
# libpng's `png_image_write_to_file` then read/wrote members at the wrong
# bytes (version read back as 0, libpng complained about the struct layout).
#
# The struct below mirrors png_image's shape: a leading member that pushes
# the following scalar fields off offset 0, and fields typed through a named
# type so they are unresolved until the Resolve stage.
my $c_source = <<'END_C';
#include "std.h"
//ext: .c

#include <stdbool.h>
#include <stddef.h>

typedef unsigned int png_uint_32;

typedef struct {
    void *      opaque;
    png_uint_32 version;
    png_uint_32 width;
    png_uint_32 height;
    png_uint_32 format;
    png_uint_32 flags;
} png_image;

DLLEXPORT bool set_image(png_image * out, png_uint_32 version, png_uint_32 width,
                         png_uint_32 height, png_uint_32 format, png_uint_32 flags) {
    if (!out)
        return false;
    out->version = version;
    out->width   = width;
    out->height  = height;
    out->format  = format;
    out->flags   = flags;
    return true;
}

DLLEXPORT bool check_image(const png_image * in) {
    if (!in)
        return false;
    return in->version == 1 && in->width == 2 && in->height == 3 && in->format == 4 && in->flags == 5;
}

DLLEXPORT size_t image_sizeof(void) { return sizeof(png_image); }
END_C
#
my $lib = compile_ok($c_source);
ok( $lib, "Library compiled at $lib" );
#
typedef png_uint_32 => Int;
#
# The struct is anonymous (declared inline in the signature), so it is laid
# out by the signature's Layout stage, not by the type registry.
my $sig = Struct [
    opaque  => Pointer [Void],
    version => png_uint_32(),
    width   => png_uint_32(),
    height  => png_uint_32(),
    format  => png_uint_32(),
    flags   => png_uint_32(),
];
#
subtest 'struct declared inline in signature' => sub {
    isa_ok my $set = wrap( $lib, 'set_image', [ Pointer [$sig], png_uint_32(), png_uint_32(), png_uint_32(), png_uint_32(), png_uint_32() ] => Bool ),
        ['Affix'];
    isa_ok my $check    = wrap( $lib, 'check_image',  [ Pointer [$sig] ] => Bool ),   ['Affix'];
    isa_ok my $c_sizeof = wrap( $lib, 'image_sizeof', []                 => Size_t ), ['Affix'];

    # Layout must match C's sizeof(png_image): opaque(8) + 5 * uint32(4) = 28, padded to 32.
    is sizeof($sig), $c_sizeof->(), 'inline signature struct has the correct sizeof';
    is sizeof($sig), 32,            'inline signature struct sizeof is 32';
    my $h = { version => 1, width => 2, height => 3, format => 4, flags => 5 };
    ok $set->( $h, 1, 2, 3, 4, 5 ), 'set_image writes all fields at the correct offsets';
    is $h->{version}, 1, 'version written back';
    is $h->{width},   2, 'width written back';
    is $h->{height},  3, 'height written back';
    is $h->{format},  4, 'format written back';
    is $h->{flags},   5, 'flags written back';
    ok $check->( { version => 1, width => 2, height => 3, format => 4, flags => 5 } ), 'check_image reads hashref from the correct offsets';



( run in 2.020 seconds using v1.01-cache-2.11-cpan-788537b7465 )