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