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