File Coverage

_xs_build/src/FieldHash.xs
Criterion Covered Total %
statement 152 154 98.7
branch 112 152 73.6
condition n/a
subroutine n/a
pod n/a
total 264 306 86.2


line stmt bran cond sub pod time code
1             #define NEED_newSV_type
2             #include "xshelper.h"
3             #include "mgx.h"
4             #define NEED_mro_get_linear_isa
5             #include "mro_compat.h"
6              
7             #ifndef HvNAMELEN_get
8             #define HvNAMELEN_get(stash) strlen(HvNAME_get(stash))
9             #endif
10              
11             #if PERL_BCDVERSION < 0x5010000
12             #define HF_USE_TIE TRUE
13             #endif
14              
15             #define PACKAGE "Hash::FieldHash"
16              
17             #ifdef HF_USE_TIE
18             #include "compat58.h"
19             #endif
20              
21             #define OBJECT_REGISTRY_KEY PACKAGE "::" "::META"
22             #define NAME_REGISTRY_KEY OBJECT_REGISTRY_KEY
23              
24             #define INVALID_OBJECT "Invalid object \"%"SVf"\" as a fieldhash key"
25              
26             #define MY_CXT_KEY PACKAGE "::_guts" XS_VERSION
27             typedef struct {
28             AV* object_registry; /* the global object registry */
29             I32 last_id; /* the last allocated id */
30             SV* free_id; /* the top of the linked list */
31              
32             HV* name_registry;
33             bool name_registry_is_stale;
34             } my_cxt_t;
35             START_MY_CXT
36             #define ObjectRegistry (MY_CXT.object_registry)
37             #define LastId (MY_CXT.last_id)
38             #define FreeId (MY_CXT.free_id)
39             #define NameRegistry (MY_CXT.name_registry)
40              
41             #define NameRegistryIsStale (MY_CXT.name_registry_is_stale)
42              
43             static int fieldhash_key_free(pTHX_ SV* const sv, MAGIC* const mg);
44             static MGVTBL fieldhash_key_vtbl = {
45             NULL, /* get */
46             NULL, /* set */
47             NULL, /* len */
48             NULL, /* clear */
49             fieldhash_key_free,
50             NULL, /* copy */
51             NULL, /* dup */
52             #ifdef MGf_LOCAL
53             NULL, /* local */
54             #endif
55             };
56              
57             #define fieldhash_key_mg(sv) MgFind(sv, &fieldhash_key_vtbl)
58              
59             #ifndef HF_USE_TIE
60             static I32 fieldhash_watch(pTHX_ IV const action, SV* const fieldhash);
61             static struct ufuncs fieldhash_ufuncs = {
62             fieldhash_watch, /* uf_val */
63             NULL, /* uf_set */
64             0, /* uf_index */
65             };
66              
67             #define fieldhash_mg(sv) hf_fieldhash_mg(aTHX_ sv)
68             static MAGIC*
69             hf_fieldhash_mg(pTHX_ SV* const sv){
70             MAGIC* mg;
71              
72             assert(sv != NULL);
73 40441 100         for(mg = SvMAGIC(sv); mg; mg = mg->mg_moremagic){
    50          
74 40396 50         if(((struct ufuncs*)mg->mg_ptr) == &fieldhash_ufuncs){
    50          
75             break;
76             }
77             }
78             return mg;
79             }
80              
81             static SV*
82 68           fieldhash_fetch(pTHX_ HV* const fieldhash, SV* const key){
83 68           HE* const he = hv_fetch_ent(fieldhash, key, FALSE, 0U);
84              
85 68 100         return he ? HeVAL(he) : &PL_sv_undef;
86             }
87              
88             static void
89             fieldhash_store(pTHX_ HV* const fieldhash, SV* const key, SV* const val){
90 35           (void)hv_store_ent(fieldhash, key, val, 0U);
91             }
92              
93             #endif /* !HF_USE_TIE */
94              
95             static SV*
96 20076           hf_new_id(pTHX_ pMY_CXT){
97             SV* obj_id;
98 20076 100         if(!FreeId){
99 10023           obj_id = newSV_type(SVt_PVIV);
100 10023           sv_setiv(obj_id, ++LastId);
101             }
102             else{
103             obj_id = FreeId;
104 10053           FreeId = INT2PTR(SV*, SvIVX(obj_id)); /* next node */
105              
106 10053           (void)sv_2iv(obj_id);
107             }
108 20076           return obj_id;
109             }
110              
111             static void
112             hf_free_id(pTHX_ pMY_CXT_ SV* const obj_id){
113             assert(SvTYPE(obj_id) >= SVt_PVIV);
114              
115 20076           SvIV_set(obj_id, PTR2IV(FreeId));
116 20076           SvIOK_off(obj_id);
117 20076           FreeId = obj_id;
118             }
119              
120             static SV*
121             hf_av_find(pTHX_ AV* const av, SV* const sv){
122             SV** const ary = AvARRAY(av);
123 20152           I32 const len = AvFILLp(av)+1;
124             I32 i;
125              
126 20210 100         for(i = 0; i < len; i++){
127 95 100         if(ary[i] == sv){
128             return sv;
129             }
130             }
131             return NULL;
132             }
133              
134             /*
135             defined actions (in 5.10.0) are:
136             HV_FETCH_ISSTORE = 0x04
137             HV_FETCH_ISEXISTS = 0x08
138             HV_FETCH_LVALUE = 0x10
139             HV_FETCH_JUST_SV = 0x20
140             HV_DELETE = 0x40
141             */
142             #define HF_CREATE_KEY(a) (a & (HV_FETCH_ISSTORE | HV_FETCH_LVALUE))
143              
144             static I32
145 40387           fieldhash_watch(pTHX_ IV const action, SV* const fieldhash){
146             MAGIC* const mg = fieldhash_mg(fieldhash);
147             SV* obj_ref;
148             SV* obj;
149             const MAGIC* key_mg;
150             AV* reg; /* field registry */
151              
152             assert(mg != NULL);
153              
154 40387           obj_ref = mg->mg_obj; /* the given hash key */
155              
156 40387 100         if(!SvROK(obj_ref)){ /* it can be an object ID */
157 20148 100         if(!looks_like_number(obj_ref)){ /* looks like an ID? */
158 5           Perl_croak(aTHX_ INVALID_OBJECT, obj_ref);
159             }
160              
161 20143 100         if(!HF_CREATE_KEY(action)){ /* fetch, exists, delete */
162             return 0;
163             }
164             else{ /* store, lvalue fetch */
165             dMY_CXT;
166 19 50         SV** const svp = av_fetch(ObjectRegistry, (I32)SvIV(obj_ref), FALSE);
167              
168 19 100         if(!svp){
169 1           Perl_croak(aTHX_ INVALID_OBJECT, obj_ref);
170             }
171              
172             /* retrieve object from ID */
173             assert(SvIOK(*svp));
174 18           obj = INT2PTR(SV*, SvIVX(*svp));
175             obj_ref = NULL;
176             }
177             }
178             else{
179 20239           obj = SvRV(obj_ref);
180             }
181              
182             assert(!SvIS_FREED(obj));
183              
184 20257 100         key_mg = fieldhash_key_mg(obj);
185 20257 100         if(!key_mg){ /* first access */
186 20085 100         if(!HF_CREATE_KEY(action)){ /* fetch, exists, delete */
187             /* replace the key with a sv that is not a registered ID */
188 9           mg->mg_obj = &PL_sv_no;
189 9           return 0;
190             }
191             else{ /* store, lvalue fetch */
192             dMY_CXT;
193 20076           SV* const obj_id = hf_new_id(aTHX_ aMY_CXT);
194 20076           SV* const obj_weakref = newSViv(PTR2IV(obj));
195              
196 20076           av_store(ObjectRegistry, (I32)SvIVX(obj_id), obj_weakref);
197              
198 20076           mg->mg_obj = obj_id; /* key replacement */
199              
200 20076           reg = newAV(); /* field registry for obj */
201              
202 20076           key_mg = sv_magicext(
203             obj,
204             (SV*)reg,
205             PERL_MAGIC_ext,
206             &fieldhash_key_vtbl,
207             (char*)obj_id,
208             HEf_SVKEY
209             );
210              
211             SvREFCNT_dec(reg); /* refcnt++ in sv_magicext() */
212             }
213             }
214             else{
215             /* key_mg->mg_ptr is obj_id */
216 172           mg->mg_obj = (SV*)key_mg->mg_ptr; /* key replacement */
217              
218 172 100         if(!HF_CREATE_KEY(action)){
219             return 0;
220             }
221              
222 76           reg = (AV*)key_mg->mg_obj;
223             assert(SvTYPE(reg) == SVt_PVAV);
224             }
225              
226             /* add a new fieldhash to the field registry if needed */
227 20152 100         if(!hf_av_find(aTHX_ reg, (SV*)fieldhash)){
228 20115           av_push(reg, (SV*)SvREFCNT_inc_simple_NN(fieldhash));
229             }
230              
231             return 0;
232             }
233              
234             static int
235 20076           fieldhash_key_free(pTHX_ SV* const sv, MAGIC* const mg){
236             PERL_UNUSED_ARG(sv);
237              
238             //warn("key_free(sv=0x%p, mg=0x%p, id=%"SVf")", sv, mg, (SV*)mg->mg_ptr);
239              
240             /*
241             Does nothing during global destruction, because
242             some data may have been released.
243             */
244 20076 50         if(!PL_dirty){
245             dMY_CXT;
246 20076           AV* const reg = (AV*)mg->mg_obj; /* field registry */
247 20076           SV* const obj_id = (SV*)mg->mg_ptr;
248 20076           I32 const len = AvFILLp(reg)+1;
249             I32 i;
250              
251             assert(SvTYPE(reg) == SVt_PVAV);
252              
253              
254             /* delete $fieldhash{$obj} for each fieldhash */
255 40191 100         for(i = 0; i < len; i++){
256 20115           HV* const fieldhash = (HV*)AvARRAY(reg)[i];
257             assert(SvTYPE(fieldhash) == SVt_PVHV);
258              
259             /* NOTE: Don't use G_DISCARD, because it may cause
260             a double-free problem (t/11_panic_malloc.t).
261             */
262 20115           (void)hv_delete_ent(fieldhash, obj_id, 0, 0U);
263             }
264              
265 20076           av_delete(ObjectRegistry, (I32)SvIVX(obj_id), G_DISCARD);
266             hf_free_id(aTHX_ aMY_CXT_ obj_id);
267             }
268              
269 20076           return 0;
270             }
271              
272             MGVTBL hf_accessor_vtbl;
273              
274             XS(XS_Hash__FieldHash_accessor);
275 22           XS(XS_Hash__FieldHash_accessor){
276 44           dVAR; dXSARGS;
277 22           SV* const obj_ref = ST(0);
278             MAGIC* const mg = mg_find_by_vtbl((SV*)cv, &hf_accessor_vtbl);
279 22           HV* const fieldhash = (HV*)mg->mg_obj;
280              
281 22 50         if(items < 1 || !SvROK(obj_ref)){
    100          
282 1           Perl_croak(aTHX_ "The %s() method must be called as an instance method", GvNAME(CvGV(cv)));
283             }
284 21 50         if(items > 2){
285 0           Perl_croak(aTHX_ "Cannot set a list of values to \"%s\"", GvNAME(CvGV(cv)));
286             }
287              
288 21 100         if(items == 1){ /* get */
289 12           ST(0) = fieldhash_fetch(aTHX_ fieldhash, obj_ref);
290             }
291             else{ /* set */
292 9           fieldhash_store(aTHX_ fieldhash, obj_ref, newSVsv(ST(1)));
293             /* returns self */
294             }
295 21           XSRETURN(1);
296             }
297              
298              
299             static HV*
300 49           hf_get_named_fields(pTHX_ HV* const stash, const char** const pkg_ptr, I32* const pkglen_ptr){
301             dMY_CXT;
302 49 50         const char* const pkg = HvNAME_get(stash);
    50          
    50          
    0          
    50          
    50          
303 49 50         I32 const pkglen = HvNAMELEN_get(stash);
    50          
    50          
    0          
    50          
    50          
304 49           SV** const svp = hv_fetch(NameRegistry, pkg, pkglen, FALSE);
305             HV* fields;
306              
307 49 100         if(!svp){
308 6           fields = newHV();
309              
310 6           (void)hv_store(NameRegistry, pkg, pkglen, newRV_noinc((SV*)fields), 0U);
311 6           NameRegistryIsStale = TRUE;
312             }
313             else{
314             assert(SvROK(*svp));
315 43           fields = (HV*)SvRV(*svp);
316             assert(SvTYPE(fields) == SVt_PVHV);
317             }
318              
319 49 50         if(NameRegistryIsStale){
320 49           AV* const isa = mro_get_linear_isa(stash);
321 49           I32 const len = AvFILLp(isa)+1;
322             I32 i;
323 91 100         for(i = 1 /* skip this class */; i < len; i++){
324 42           HE* const he = hv_fetch_ent(NameRegistry, AvARRAY(isa)[i], FALSE, 0U);
325 42 50         HV* const base_fields = he && SvROK(HeVAL(he)) ? (HV*)SvRV(HeVAL(he)) : NULL;
    50          
326              
327 42 50         if(base_fields){
328             char* key;
329             I32 keylen;
330             SV* val;
331 42           hv_iterinit(base_fields);
332 248 100         while((val = hv_iternextsv(base_fields, &key, &keylen))){
333 206           (void)hv_store(fields, key, keylen, newSVsv(val), 0U);
334             }
335             }
336             }
337             }
338              
339 49 100         if(pkg_ptr) *pkg_ptr = pkg;
340 49 100         if(pkglen_ptr) *pkglen_ptr = pkglen;
341              
342 49           return fields;
343             }
344              
345             static void
346 45           hf_add_field(pTHX_ HV* const fieldhash, SV* const name, SV* const package){
347 45 100         if(name){
348             dMY_CXT;
349 7 50         HV* const stash = package ? gv_stashsv(package, TRUE) : CopSTASH(PL_curcop);
350             I32 pkglen;
351             const char* pkg;
352 7           HV* const fields = hf_get_named_fields(aTHX_ stash, &pkg, &pkglen);
353             STRLEN namelen;
354 7 50         const char* namepv = SvPV_const(name, namelen);
355             CV* xsub;
356              
357 7 50         if(hv_exists_ent(fields, name, 0U) && ckWARN(WARN_REDEFINE)){
    0          
358 0           Perl_warner(aTHX_ packWARN(WARN_REDEFINE), "field \"%"SVf"\" redefined or overridden", name);
359             }
360              
361 7           (void)hv_store_ent(fields, name, newRV_inc((SV*)fieldhash), 0U);
362              
363 7           namepv = Perl_form(aTHX_ "%s::%s", pkg, namepv); /* fully qualified name */
364 7           namelen += sizeof("::")-1 + pkglen;
365 7           (void)hv_store(fields, namepv, namelen, newRV_inc((SV*)fieldhash), 0U);
366              
367 7 50         if(ckWARN(WARN_REDEFINE) && get_cv(namepv, 0x00)){
    100          
368 1           Perl_warner(aTHX_ packWARN(WARN_REDEFINE),
369             "Subroutine %s redefined", namepv);
370             }
371              
372 6           xsub = newXS( (char*)namepv, XS_Hash__FieldHash_accessor, __FILE__);
373 6           sv_magicext(
374             (SV*)xsub,
375             (SV*)fieldhash,
376             PERL_MAGIC_ext,
377             &hf_accessor_vtbl,
378             NULL,
379             0
380             );
381 6           CvMETHOD_on(xsub);
382              
383 6           NameRegistryIsStale = TRUE;
384             }
385 44           }
386              
387             MODULE = Hash::FieldHash PACKAGE = Hash::FieldHash
388              
389             PROTOTYPES: DISABLE
390              
391             BOOT:
392             {
393             MY_CXT_INIT;
394 19           ObjectRegistry = get_av(OBJECT_REGISTRY_KEY, GV_ADDMULTI);
395 19           NameRegistry = get_hv( NAME_REGISTRY_KEY, GV_ADDMULTI);
396 19           LastId = -1;
397             }
398              
399             #ifdef USE_ITHREADS
400              
401             void
402             CLONE(...)
403             CODE:
404             MY_CXT_CLONE;
405              
406             ObjectRegistry = get_av(OBJECT_REGISTRY_KEY, GV_ADDMULTI);
407             NameRegistry = get_hv( NAME_REGISTRY_KEY, GV_ADDMULTI);
408             FreeId = NULL;
409             PERL_UNUSED_VAR(items);
410              
411             #endif /* !USE_ITHREADS */
412              
413             #ifndef HF_USE_TIE
414              
415             void
416             fieldhash(HV* hash, SV* name = NULL, SV* package = NULL)
417             PROTOTYPE: \%;$$
418             CODE:
419             assert(SvTYPE(hash) >= SVt_PVMG);
420 54 100         if(!fieldhash_mg((SV*)hash)){
421 45           hv_clear(hash);
422 45           sv_magic((SV*)hash,
423             NULL, /* mg_obj */
424             PERL_MAGIC_uvar, /* mg_type */
425             (char*)&fieldhash_ufuncs, /* mg_ptr as the ufuncs table */
426             0 /* mg_len (0 indicates static data) */
427             );
428              
429 45           hf_add_field(aTHX_ hash, name, package);
430             }
431              
432             #else /* HF_USE_TIE */
433              
434             INCLUDE: compat58.xsi
435              
436             #endif
437              
438              
439             #ifdef FIELDHASH_DEBUG
440              
441             void
442             _dump_internals()
443             PREINIT:
444             dMY_CXT;
445             SV* obj_id;
446             CODE:
447             for(obj_id = FreeId; obj_id; obj_id = INT2PTR(SV*, SvIVX(obj_id))){
448             sv_dump(obj_id);
449             }
450              
451             HV*
452             _name_registry()
453             PREINIT:
454             dMY_CXT;
455             CODE:
456             RETVAL = NameRegistry;
457             OUTPUT:
458             RETVAL
459              
460             #endif /* !FIELDHASH_DEBUG */
461              
462              
463             void
464             from_hash(SV* object, ...)
465             PREINIT:
466             const char* stashname;
467             HV* stash;
468             HV* fields;
469             INIT:
470 23 100         if(!sv_isobject(object)){
471 1           Perl_croak(aTHX_ "The %s() method must be called as an instance method", GvNAME(CvGV(cv)));
472             }
473             CODE:
474 22           stash = SvSTASH(SvRV(object));
475 22           fields = hf_get_named_fields(aTHX_ stash, &stashname, NULL);
476              
477 22 100         if(items == 2){
478 14           SV* const arg = ST(1);
479             HV* hv;
480             char* key;
481             I32 keylen;
482             SV* val;
483              
484 14 50         if(!(SvROK(arg) && SvTYPE(SvRV(arg)) == SVt_PVHV)){
    100          
485 1           Perl_croak(aTHX_ "Single parameters to %s() must be a HASH reference", GvNAME(CvGV(cv)));
486             }
487              
488             hv = (HV*)SvRV(arg);
489 13           hv_iterinit(hv);
490 29 100         while((val = hv_iternextsv(hv, &key, &keylen))){
491 17           SV** const svp = hv_fetch(fields, key, keylen, FALSE);
492              
493 17 100         if(!(svp && SvROK(*svp))){
    50          
494 1           Perl_croak(aTHX_ "No such field \"%s\" for %s", key, stashname);
495             }
496              
497 16           fieldhash_store(aTHX_ (HV*)SvRV(*svp), object, newSVsv(val));
498             }
499             }
500             else{
501             I32 i;
502              
503 8 100         if( ((items-1) % 2) != 0 ){
504 1           Perl_croak(aTHX_ "Odd number of parameters for %s()", GvNAME(CvGV(cv)));
505             }
506              
507 17 100         for(i = 1; i < items; i += 2){
508 11           HE* const he = hv_fetch_ent(fields, ST(i), FALSE, 0U);
509              
510 11 100         if(!(he && SvROK(HeVAL(he)))){
    50          
511 1 50         Perl_croak(aTHX_ "No such field \"%s\" for %s", SvPV_nolen_const(ST(i)), stashname);
512             }
513              
514 10           fieldhash_store(aTHX_ (HV*)SvRV(HeVAL(he)), object, newSVsv(ST(i+1)));
515             }
516             }
517 18           XSRETURN(1); /* returns the first argument */
518              
519             HV*
520             to_hash(SV* object, ...)
521             PREINIT:
522             HV* stash;
523             HV* fields;
524             char* key;
525             I32 keylen;
526             SV* val;
527             bool fully_qualify = FALSE;
528             INIT:
529 21 100         if(!sv_isobject(object)){
530 1           Perl_croak(aTHX_ "The %s() method must be called as an instance method", GvNAME(CvGV(cv)));
531             }
532 36 100         while(items > 1){
533 16           SV* const option = ST(--items);
534              
535 16 100         if(SvOK(option)){
    50          
    50          
536 8 50         if(strEQ(SvPV_nolen_const(option), "-fully_qualify")){
    50          
537             fully_qualify = TRUE;
538             }
539             else{
540 16           Perl_croak(aTHX_ "Unknown option \"%"SVf"\"", option);
541             }
542             }
543             }
544             CODE:
545 20           stash = SvSTASH(SvRV(object));
546 20           fields = hf_get_named_fields(aTHX_ stash, NULL, NULL);
547 20           RETVAL = newHV();
548              
549 20           hv_iterinit(fields);
550 132 100         while((val = hv_iternextsv(fields, &key, &keylen))){
551 112 100         bool const need_to_store = strchr(key, ':') ? fully_qualify : !fully_qualify;
552 112 100         if( need_to_store && SvROK(val) ){
    50          
553 56           HV* const fieldhash = (HV*)SvRV(val);
554 56           SV* const value = fieldhash_fetch(aTHX_ fieldhash, object);
555 112           (void)hv_store(RETVAL, key, keylen, newSVsv(value), 0U);
556             }
557             }
558             OUTPUT:
559             RETVAL