File Coverage

blib/lib/FFI/C/StructDef.pm
Criterion Covered Total %
statement 203 209 97.1
branch 93 114 81.5
condition 9 15 60.0
subroutine 44 44 100.0
pod 2 2 100.0
total 351 384 91.4


line stmt bran cond sub pod time code
1             package FFI::C::StructDef;
2              
3 9     9   739299 use strict;
  9         41  
  9         275  
4 9     9   52 use warnings;
  9         16  
  9         198  
5 9     9   152 use 5.008001;
  9         32  
6 9     9   1262 use FFI::C::Util;
  9         19  
  9         433  
7 9     9   2984 use FFI::C::Struct;
  9         25  
  9         260  
8 9     9   61 use FFI::C::FFI ();
  9         25  
  9         225  
9 9     9   46 use FFI::Platypus 1.24;
  9         136  
  9         278  
10 9     9   48 use Ref::Util qw( is_blessed_ref is_plain_arrayref is_ref );
  9         19  
  9         455  
11 9     9   67 use Carp ();
  9         19  
  9         150  
12 9     9   3300 use Sub::Install ();
  9         12697  
  9         164  
13 9     9   3082 use Sub::Util ();
  9         2129  
  9         249  
14 9     9   59 use Scalar::Util qw( refaddr );
  9         26  
  9         492  
15 9     9   53 use constant _is_union => 0;
  9         36  
  9         587  
16 9     9   86 use base qw( FFI::C::Def );
  9         19  
  9         5683  
17              
18             our @CARP_NOT = qw( FFI::C::Util FFI::C );
19              
20             # ABSTRACT: Structured data definition for FFI
21             our $VERSION = '0.14'; # VERSION
22              
23              
24             sub _is_kind
25             {
26 82     82   186 my($self, $name, $want) = @_;
27 82         153 my $kind = eval { $self->ffi->kindof($name) };
  82         178  
28 82 100       5716 return undef unless defined $kind;
29 70         218 return $kind eq $want;
30             }
31              
32             sub new
33             {
34 46     46 1 86959 my $self = shift->SUPER::new(@_);
35              
36 46         80 my %args = %{ delete $self->{args} };
  46         211  
37              
38 46 100       185 $self->{trim_string} = delete $args{trim_string} ? 1 : 0;
39 46         79 my $offset = 0;
40 46         63 my $alignment = 0;
41 46         74 my $anon = 0;
42              
43 46 100       77 if(my @members = @{ delete $args{members} || [] })
  46 100       256  
44             {
45 39 50       127 Carp::croak("Odd number of arguments in member spec") if scalar(@members) % 2;
46 39         100 while(@members)
47             {
48 80         149 my $name = shift @members;
49 80         134 my $spec = shift @members;
50 80         112 my %member;
51              
52 80 50 33     404 if($name ne ':' && $self->{members}->{$name})
53             {
54 0         0 Carp::croak("More than one member with the name $name");
55             }
56              
57 80 50       587 if($name eq ':')
    50          
    50          
58             {
59 0         0 $name .= (++$anon);
60             }
61             elsif($name !~ /^[A-Za-z_][A-Za-z_0-9]*$/)
62             {
63 0         0 Carp::croak("Illegal member name");
64             }
65             elsif($name eq 'new')
66             {
67 0         0 Carp::croak("new now allowed as a member name");
68             }
69              
70 80 100       294 if(my $def = $self->ffi->def('FFI::C::Def', $spec))
    100          
71             {
72 4         46 $spec = $def;
73             }
74             elsif($def = $self->ffi->def('FFI::C::EnumDef', $spec))
75             {
76 4         57 $spec = $def;
77             }
78              
79 80 100       842 if(is_blessed_ref $spec)
    100          
    100          
    50          
80             {
81 14 100       79 if($spec->isa('FFI::C::Def'))
    50          
82             {
83 10 100       234 Carp::croak("Canot nest a struct or union def inside of itself")
84             if refaddr($spec) == refaddr($self);
85 9         21 $member{nest} = $spec;
86 9         25 $member{size} = $spec->size;
87 9         33 $member{align} = $spec->align;
88             }
89             elsif($spec->isa('FFI::C::EnumDef'))
90             {
91 4         14 $member{spec} = $spec->type;
92 4         9 $member{size} = $self->ffi->sizeof($spec->type);
93 4         97 $member{align} = $self->ffi->alignof($spec->type);
94 4         2658 $member{enum} = $spec;
95             }
96             }
97             elsif($self->_is_kind($spec, 'scalar'))
98             {
99 56         133 $member{spec} = $spec;
100 56         128 $member{size} = $self->ffi->sizeof($spec);
101 56         2037 $member{align} = $self->ffi->alignof($spec);
102             }
103             elsif($self->_is_kind($spec, 'array'))
104             {
105 4         24 $member{spec} = $self->ffi->unitof($spec);
106 4         83 $member{count} = $self->ffi->countof($spec);
107 4         76 $member{size} = $self->ffi->sizeof($spec);
108 4         70 $member{unitsize} = $self->ffi->sizeof($member{spec});
109 4         155 $member{align} = $self->ffi->alignof($spec);
110             Carp::croak("array members must have at least one element")
111 4 50       2345 unless $member{count} > 0;
112             }
113             elsif($self->_is_kind("$spec*", 'record'))
114             {
115 6         11 local $@;
116 6         11 $member{align} = eval { $self->ffi->alignof("$spec*") };
  6         13  
117 6 100       3184 $member{trim_string} = 1 if $self->{trim_string};
118 6         13 $member{spec} = $spec;
119 6         12 $member{rec} = 1;
120 6         22 $member{size} = $self->ffi->sizeof("$spec*");
121 6 50       136 Carp::croak("undefined, or unsupported type: $spec") if $@;
122             }
123             else
124             {
125 0         0 Carp::croak("undefined or unsupported type: $spec");
126             }
127              
128 79 100       30265 $self->{align} = $member{align} if $member{align} > $self->{align};
129              
130 79 100       344 if($self->_is_union)
131             {
132 18 100       60 $self->{size} = $member{size} if $member{size} > $self->{size};
133 18         40 $member{offset} = 0;
134             }
135             else
136             {
137 61         193 $offset++ while $offset % $member{align};
138 61         103 $member{offset} = $offset;
139 61         107 $offset += $member{size};
140             }
141              
142 79         366 $self->{members}->{$name} = \%member;
143             }
144             }
145              
146 45 100       180 $self->{size} = $offset unless $self->_is_union;
147              
148 45         166 Carp::carp("Unknown argument: $_") for sort keys %args;
149              
150 45 100       160 if($self->class)
151             {
152             # not handled by the superclass:
153             # 3. Any nested cdefs must have Perl classes.
154              
155 17         41 foreach my $name (keys %{ $self->{members} })
  17         98  
156             {
157 42 50       106 next if $name =~ /^:/;
158 42         70 my $member = $self->{members}->{$name};
159 42         95 my $accessor = $self->class . '::' . $name;
160             Carp::croak("Missing Perl class for $accessor")
161 42 50 66     136 if $member->{nest} && !$member->{nest}->{class};
162             }
163              
164 17         41 $self->_generate_class(keys %{ $self->{members} });
  17         133  
165              
166             {
167 17         745 my $ffi = $self->ffi;
  17         53  
168              
169 17         31 foreach my $name (keys %{ $self->{members} })
  17         61  
170             {
171 42         1199 my $offset = $self->{members}->{$name}->{offset};
172 42         61 my $code;
173 42 100       98 if($self->{members}->{$name}->{nest})
174             {
175 5         19 my $class = $self->{members}->{$name}->{nest}->{class};
176             $code = sub {
177 63     63   545 my $self = shift;
        57      
        56      
178 63         101 my $ptr = $self->{ptr} + $offset;
179 63         144 my $m = $class->new($ptr,$self);
180 63 100       149 FFI::C::Util::perl_to_c($m, $_[0]) if @_;
181 63         134 $m;
182 5         25 };
183             }
184             else
185             {
186 37         82 my $type = $self->{members}->{$name}->{spec} . '*';
187 37         59 my $size = $self->{members}->{$name}->{size};
188              
189 37         150 my $set = $ffi->function( FFI::C::FFI::memcpy_addr() => ['opaque',$type,'size_t'] => $type)->sub_ref;
190 37         5790 my $get = $ffi->function( 0 => ['opaque'] => $type)->sub_ref;
191              
192 37 100       3515 if($self->{members}->{$name}->{rec})
    100          
    100          
193             {
194 4 100       14 if($self->{trim_string})
195             {
196 1 50       8 unless(__PACKAGE__->can('_cast_string'))
197             {
198 1         5 $ffi->attach_cast('_cast_string', 'opaque', 'string');
199             }
200 1         197 $set = $ffi->function( FFI::C::FFI::memcpy_addr() => ['opaque',$type,'size_t'] => 'string')->sub_ref;
201 1         153 $get = \&_cast_string;
202             }
203             $code = sub {
204 30     30   2915 my $self = shift;
        30      
        24      
205 30         57 my $ptr = $self->{ptr} + $offset;
206 30 100       62 if(@_)
207             {
208 9     9   96 my $length = do { use bytes; length $_[0] };
  9         21  
  9         45  
  14         16  
  14         26  
209 14 50       60 my $src = \($size > $length ? $_[0] . ("\0" x ($size-$length)) : $_[0]);
210 14         81 return $set->($ptr, $src, $size);
211             }
212 16         88 $get->($ptr)
213 4         28 };
214             }
215             elsif(my $count = $self->{members}->{$name}->{count})
216             {
217 2         42 my $unitsize = $self->{members}->{$name}->{unitsize};
218 2         11 my $atype = $self->{members}->{$name}->{spec} . "[$count]";
219 2         12 my $all = $ffi->function( FFI::C::FFI::memcpy_addr() => ['opaque',$atype,'size_t'] => 'void' );
220             $code = sub {
221 56     56   1702 my $self = shift;
222 56 100       125 if(defined $_[0])
223             {
224 42 100       112 if(is_plain_arrayref $_[0])
    50          
225             {
226 2         12 my $array = shift;
227 2 50       10 Carp::croak("$name OOB index on array member") if @$array > $count;
228 2         8 my $ptr = $self->{ptr} + $offset;
229 2         6 my $size = (@$array ) * $unitsize;
230 2         103 $all->($ptr, $array, (@$array * $unitsize));
231             # we don't want to have to get the array and tie it if
232             # it isn't going to be used anyway.
233 2 100       45 return unless defined wantarray; ## no critic (Community::Wantarray)
234             }
235             elsif(! is_ref $_[0])
236             {
237 40         93 my $index = shift;
238 40 100       399 Carp::croak("$name Negative index on array member") if $index < 0;
239 38 100       260 Carp::croak("$name OOB index on array member") if $index >= $count;
240 36         71 my $ptr = $self->{ptr} + $offset + $index * $unitsize;
241             return @_
242 6         61 ? ${ $set->($ptr,\$_[0],$unitsize) }
243 36 100       73 : ${ $get->($ptr) };
  30         185  
244             }
245             else
246             {
247 0         0 Carp::croak("$name tried to set element to non-scalar");
248             }
249             }
250 15         28 my @a;
251 15         72 tie @a, 'FFI::C::Struct::MemberArrayTie', $self, $name, $count;
252 15         75 return \@a;
253 2         257 };
254             }
255             elsif(my $enum = $self->{members}->{$name}->{enum})
256             {
257 2         7 my $str_lookup = $enum->str_lookup;
258 2         4 my $int_lookup = $enum->int_lookup;
259 2 100       4 if($enum->rev eq 'str')
260             {
261             $code = sub {
262 5     61   15 my $self = shift;
        17      
263 5         11 my $ptr = $self->{ptr} + $offset;
264 5 50 66     21 Carp::croak("$name tried to set member to non-scalar") if @_ && is_ref $_[0];
265             my $ret = @_
266             ? do {
267             my $arg = exists $str_lookup->{$_[0]}
268             ? $str_lookup->{$_[0]}
269 2 50       10 : exists $int_lookup->{$_[0]}
    100          
270             ? $_[0]
271             : Carp::croak("No such value for $name: $_[0]");
272 2         2 ${ $set->($ptr,\$arg,$size) }
  2         16  
273             }
274 5 100       14 : ${ $get->($ptr) };
  3         17  
275             $int_lookup->{$ret}
276 5 50       33 ? $int_lookup->{$ret}
277             : $ret;
278 1         5 };
279             }
280             else
281             {
282             $code = sub {
283 5     5   16 my $self = shift;
        6      
284 5         11 my $ptr = $self->{ptr} + $offset;
285 5 50 66     24 Carp::croak("$name tried to set member to non-scalar") if @_ && is_ref $_[0];
286             @_
287             ? do {
288             my $arg = exists $str_lookup->{$_[0]}
289             ? $str_lookup->{$_[0]}
290 2 50       8 : exists $int_lookup->{$_[0]}
    100          
291             ? $_[0]
292             : Carp::croak("No such value for $name: $_[0]");
293 2         4 ${ $set->($ptr,\$arg,$size) }
  2         18  
294             }
295 5 100       11 : ${ $get->($ptr) };
  3         22  
296 1         7 };
297             }
298             }
299             else
300             {
301             $code = sub {
302 160     165   15555 my $self = shift;
        160      
        137      
        160      
        118      
        141      
        118      
        106      
        87      
        87      
        87      
        87      
        87      
        106      
        106      
303 160         277 my $ptr = $self->{ptr} + $offset;
304 160 50 66     395 Carp::croak("$name tried to set member to non-scalar") if @_ && is_ref $_[0];
305             @_
306 52         265 ? ${ $set->($ptr,\$_[0],$size) }
307 160 100       270 : ${ $get->($ptr) };
  108         630  
308 29         154 };
309             }
310             }
311              
312 42         116 Sub::Util::set_subname(join('::', $self->class, $name), $code);
313 42         106 Sub::Install::install_sub({
314             code => $code,
315             into => $self->class,
316             as => $name,
317             });
318             }
319             }
320             }
321              
322 45         1074 $self;
323             }
324              
325              
326 8     8 1 744 sub trim_string { shift->{trim_string} }
327              
328             1;
329              
330             __END__