#define NEED_newSV_type #include "xshelper.h" #include "mgx.h" #define NEED_mro_get_linear_isa #include "mro_compat.h" #if PERL_BCDVERSION < 0x5010000 #define HF_USE_TIE TRUE #endif #define PACKAGE "Hash::FieldHash" #ifdef HF_USE_TIE #include "compat58.h" #endif #define OBJECT_REGISTRY_KEY PACKAGE "::" "::META" #define NAME_REGISTRY_KEY OBJECT_REGISTRY_KEY #define INVALID_OBJECT "Invalid object \"%"SVf"\" as a fieldhash key" #define MY_CXT_KEY PACKAGE "::_guts" XS_VERSION typedef struct { AV* object_registry; /* the global object registry */ I32 last_id; /* the last allocated id */ SV* free_id; /* the top of the linked list */ HV* name_registry; bool name_registry_is_stale; } my_cxt_t; START_MY_CXT #define ObjectRegistry (MY_CXT.object_registry) #define LastId (MY_CXT.last_id) #define FreeId (MY_CXT.free_id) #define NameRegistry (MY_CXT.name_registry) #define NameRegistryIsStale (MY_CXT.name_registry_is_stale) static int fieldhash_key_free(pTHX_ SV* const sv, MAGIC* const mg); static MGVTBL fieldhash_key_vtbl = { NULL, /* get */ NULL, /* set */ NULL, /* len */ NULL, /* clear */ fieldhash_key_free, NULL, /* copy */ NULL, /* dup */ #ifdef MGf_LOCAL NULL, /* local */ #endif }; #define fieldhash_key_mg(sv) MgFind(sv, &fieldhash_key_vtbl) #ifndef HF_USE_TIE static I32 fieldhash_watch(pTHX_ IV const action, SV* const fieldhash); static struct ufuncs fieldhash_ufuncs = { fieldhash_watch, /* uf_val */ NULL, /* uf_set */ 0, /* uf_index */ }; #define fieldhash_mg(sv) hf_fieldhash_mg(aTHX_ sv) static MAGIC* hf_fieldhash_mg(pTHX_ SV* const sv){ MAGIC* mg; assert(sv != NULL); for(mg = SvMAGIC(sv); mg; mg = mg->mg_moremagic){ if(((struct ufuncs*)mg->mg_ptr) == &fieldhash_ufuncs){ break; } } return mg; } static SV* fieldhash_fetch(pTHX_ HV* const fieldhash, SV* const key){ HE* const he = hv_fetch_ent(fieldhash, key, FALSE, 0U); return he ? HeVAL(he) : &PL_sv_undef; } static void fieldhash_store(pTHX_ HV* const fieldhash, SV* const key, SV* const val){ (void)hv_store_ent(fieldhash, key, val, 0U); } #endif /* !HF_USE_TIE */ static SV* hf_new_id(pTHX_ pMY_CXT){ SV* obj_id; if(!FreeId){ obj_id = newSV_type(SVt_PVIV); sv_setiv(obj_id, ++LastId); } else{ obj_id = FreeId; FreeId = INT2PTR(SV*, SvIVX(obj_id)); /* next node */ (void)sv_2iv(obj_id); } return obj_id; } static void hf_free_id(pTHX_ pMY_CXT_ SV* const obj_id){ assert(SvTYPE(obj_id) >= SVt_PVIV); SvIV_set(obj_id, PTR2IV(FreeId)); SvIOK_off(obj_id); FreeId = obj_id; } static SV* hf_av_find(pTHX_ AV* const av, SV* const sv){ SV** const ary = AvARRAY(av); I32 const len = AvFILLp(av)+1; I32 i; for(i = 0; i < len; i++){ if(ary[i] == sv){ return sv; } } return NULL; } /* defined actions (in 5.10.0) are: HV_FETCH_ISSTORE = 0x04 HV_FETCH_ISEXISTS = 0x08 HV_FETCH_LVALUE = 0x10 HV_FETCH_JUST_SV = 0x20 HV_DELETE = 0x40 */ #define HF_CREATE_KEY(a) (a & (HV_FETCH_ISSTORE | HV_FETCH_LVALUE)) static I32 fieldhash_watch(pTHX_ IV const action, SV* const fieldhash){ MAGIC* const mg = fieldhash_mg(fieldhash); SV* obj_ref; SV* obj; const MAGIC* key_mg; AV* reg; /* field registry */ assert(mg != NULL); obj_ref = mg->mg_obj; /* the given hash key */ if(!SvROK(obj_ref)){ /* it can be an object ID */ if(!looks_like_number(obj_ref)){ /* looks like an ID? */ Perl_croak(aTHX_ INVALID_OBJECT, obj_ref); } if(!HF_CREATE_KEY(action)){ /* fetch, exists, delete */ return 0; } else{ /* store, lvalue fetch */ dMY_CXT; SV** const svp = av_fetch(ObjectRegistry, (I32)SvIV(obj_ref), FALSE); if(!svp){ Perl_croak(aTHX_ INVALID_OBJECT, obj_ref); } /* retrieve object from ID */ assert(SvIOK(*svp)); obj = INT2PTR(SV*, SvIVX(*svp)); obj_ref = NULL; } } else{ obj = SvRV(obj_ref); } assert(!SvIS_FREED(obj)); key_mg = fieldhash_key_mg(obj); if(!key_mg){ /* first access */ if(!HF_CREATE_KEY(action)){ /* fetch, exists, delete */ /* replace the key with a sv that is not a registered ID */ mg->mg_obj = &PL_sv_no; return 0; } else{ /* store, lvalue fetch */ dMY_CXT; SV* const obj_id = hf_new_id(aTHX_ aMY_CXT); SV* const obj_weakref = newSViv(PTR2IV(obj)); av_store(ObjectRegistry, (I32)SvIVX(obj_id), obj_weakref); mg->mg_obj = obj_id; /* key replacement */ reg = newAV(); /* field registry for obj */ key_mg = sv_magicext( obj, (SV*)reg, PERL_MAGIC_ext, &fieldhash_key_vtbl, (char*)obj_id, HEf_SVKEY ); SvREFCNT_dec(reg); /* refcnt++ in sv_magicext() */ } } else{ /* key_mg->mg_ptr is obj_id */ mg->mg_obj = (SV*)key_mg->mg_ptr; /* key replacement */ if(!HF_CREATE_KEY(action)){ return 0; } reg = (AV*)key_mg->mg_obj; assert(SvTYPE(reg) == SVt_PVAV); } /* add a new fieldhash to the field registry if needed */ if(!hf_av_find(aTHX_ reg, (SV*)fieldhash)){ av_push(reg, (SV*)SvREFCNT_inc_simple_NN(fieldhash)); } return 0; } static int fieldhash_key_free(pTHX_ SV* const sv, MAGIC* const mg){ PERL_UNUSED_ARG(sv); //warn("key_free(sv=0x%p, mg=0x%p, id=%"SVf")", sv, mg, (SV*)mg->mg_ptr); /* Does nothing during global destruction, because some data may have been released. */ if(!PL_dirty){ dMY_CXT; AV* const reg = (AV*)mg->mg_obj; /* field registry */ SV* const obj_id = (SV*)mg->mg_ptr; I32 const len = AvFILLp(reg)+1; I32 i; assert(SvTYPE(reg) == SVt_PVAV); /* delete $fieldhash{$obj} for each fieldhash */ for(i = 0; i < len; i++){ HV* const fieldhash = (HV*)AvARRAY(reg)[i]; assert(SvTYPE(fieldhash) == SVt_PVHV); /* NOTE: Don't use G_DISCARD, because it may cause a double-free problem (t/11_panic_malloc.t). */ (void)hv_delete_ent(fieldhash, obj_id, 0, 0U); } av_delete(ObjectRegistry, (I32)SvIVX(obj_id), G_DISCARD); hf_free_id(aTHX_ aMY_CXT_ obj_id); } return 0; } MGVTBL hf_accessor_vtbl; XS(XS_Hash__FieldHash_accessor); XS(XS_Hash__FieldHash_accessor){ dVAR; dXSARGS; SV* const obj_ref = ST(0); MAGIC* const mg = mg_find_by_vtbl((SV*)cv, &hf_accessor_vtbl); HV* const fieldhash = (HV*)mg->mg_obj; if(items < 1 || !SvROK(obj_ref)){ Perl_croak(aTHX_ "The %s() method must be called as an instance method", GvNAME(CvGV(cv))); } if(items > 2){ Perl_croak(aTHX_ "Cannot set a list of values to \"%s\"", GvNAME(CvGV(cv))); } if(items == 1){ /* get */ ST(0) = fieldhash_fetch(aTHX_ fieldhash, obj_ref); } else{ /* set */ fieldhash_store(aTHX_ fieldhash, obj_ref, newSVsv(ST(1))); /* returns self */ } XSRETURN(1); } static HV* hf_get_named_fields(pTHX_ HV* const stash, const char** const pkg_ptr, I32* const pkglen_ptr){ dMY_CXT; const char* const pkg = HvNAME_get(stash); I32 const pkglen = HvNAMELEN_get(stash); SV** const svp = hv_fetch(NameRegistry, pkg, pkglen, FALSE); HV* fields; if(!svp){ fields = newHV(); (void)hv_store(NameRegistry, pkg, pkglen, newRV_noinc((SV*)fields), 0U); NameRegistryIsStale = TRUE; } else{ assert(SvROK(*svp)); fields = (HV*)SvRV(*svp); assert(SvTYPE(fields) == SVt_PVHV); } if(NameRegistryIsStale){ AV* const isa = mro_get_linear_isa(stash); I32 const len = AvFILLp(isa)+1; I32 i; for(i = 1 /* skip this class */; i < len; i++){ HE* const he = hv_fetch_ent(NameRegistry, AvARRAY(isa)[i], FALSE, 0U); HV* const base_fields = he && SvROK(HeVAL(he)) ? (HV*)SvRV(HeVAL(he)) : NULL; if(base_fields){ char* key; I32 keylen; SV* val; hv_iterinit(base_fields); while((val = hv_iternextsv(base_fields, &key, &keylen))){ (void)hv_store(fields, key, keylen, newSVsv(val), 0U); } } } } if(pkg_ptr) *pkg_ptr = pkg; if(pkglen_ptr) *pkglen_ptr = pkglen; return fields; } static void hf_add_field(pTHX_ HV* const fieldhash, SV* const name, SV* const package){ if(name){ dMY_CXT; HV* const stash = package ? gv_stashsv(package, TRUE) : CopSTASH(PL_curcop); I32 pkglen; const char* pkg; HV* const fields = hf_get_named_fields(aTHX_ stash, &pkg, &pkglen); STRLEN namelen; const char* namepv = SvPV_const(name, namelen); CV* xsub; if(hv_exists_ent(fields, name, 0U) && ckWARN(WARN_REDEFINE)){ Perl_warner(aTHX_ packWARN(WARN_REDEFINE), "field \"%"SVf"\" redefined or overridden", name); } (void)hv_store_ent(fields, name, newRV_inc((SV*)fieldhash), 0U); namepv = Perl_form(aTHX_ "%s::%s", pkg, namepv); /* fully qualified name */ namelen += sizeof("::")-1 + pkglen; (void)hv_store(fields, namepv, namelen, newRV_inc((SV*)fieldhash), 0U); if(ckWARN(WARN_REDEFINE) && get_cv(namepv, 0x00)){ Perl_warner(aTHX_ packWARN(WARN_REDEFINE), "Subroutine %s redefined", namepv); } xsub = newXS( (char*)namepv, XS_Hash__FieldHash_accessor, __FILE__); sv_magicext( (SV*)xsub, (SV*)fieldhash, PERL_MAGIC_ext, &hf_accessor_vtbl, NULL, 0 ); CvMETHOD_on(xsub); NameRegistryIsStale = TRUE; } } MODULE = Hash::FieldHash PACKAGE = Hash::FieldHash PROTOTYPES: DISABLE BOOT: { MY_CXT_INIT; ObjectRegistry = get_av(OBJECT_REGISTRY_KEY, GV_ADDMULTI); NameRegistry = get_hv( NAME_REGISTRY_KEY, GV_ADDMULTI); LastId = -1; } #ifdef USE_ITHREADS void CLONE(...) CODE: MY_CXT_CLONE; ObjectRegistry = get_av(OBJECT_REGISTRY_KEY, GV_ADDMULTI); NameRegistry = get_hv( NAME_REGISTRY_KEY, GV_ADDMULTI); FreeId = NULL; PERL_UNUSED_VAR(items); #endif /* !USE_ITHREADS */ #ifndef HF_USE_TIE void fieldhash(HV* hash, SV* name = NULL, SV* package = NULL) PROTOTYPE: \%;$$ CODE: assert(SvTYPE(hash) >= SVt_PVMG); if(!fieldhash_mg((SV*)hash)){ hv_clear(hash); sv_magic((SV*)hash, NULL, /* mg_obj */ PERL_MAGIC_uvar, /* mg_type */ (char*)&fieldhash_ufuncs, /* mg_ptr as the ufuncs table */ 0 /* mg_len (0 indicates static data) */ ); hf_add_field(aTHX_ hash, name, package); } #else /* HF_USE_TIE */ INCLUDE: compat58.xsi #endif #ifdef FIELDHASH_DEBUG void _dump_internals() PREINIT: dMY_CXT; SV* obj_id; CODE: for(obj_id = FreeId; obj_id; obj_id = INT2PTR(SV*, SvIVX(obj_id))){ sv_dump(obj_id); } HV* _name_registry() PREINIT: dMY_CXT; CODE: RETVAL = NameRegistry; OUTPUT: RETVAL #endif /* !FIELDHASH_DEBUG */ void from_hash(SV* object, ...) PREINIT: const char* stashname; HV* stash; HV* fields; INIT: if(!sv_isobject(object)){ Perl_croak(aTHX_ "The %s() method must be called as an instance method", GvNAME(CvGV(cv))); } CODE: stash = SvSTASH(SvRV(object)); fields = hf_get_named_fields(aTHX_ stash, &stashname, NULL); if(items == 2){ SV* const arg = ST(1); HV* hv; char* key; I32 keylen; SV* val; if(!(SvROK(arg) && SvTYPE(SvRV(arg)) == SVt_PVHV)){ Perl_croak(aTHX_ "Single parameters to %s() must be a HASH reference", GvNAME(CvGV(cv))); } hv = (HV*)SvRV(arg); hv_iterinit(hv); while((val = hv_iternextsv(hv, &key, &keylen))){ SV** const svp = hv_fetch(fields, key, keylen, FALSE); if(!(svp && SvROK(*svp))){ Perl_croak(aTHX_ "No such field \"%s\" for %s", key, stashname); } fieldhash_store(aTHX_ (HV*)SvRV(*svp), object, newSVsv(val)); } } else{ I32 i; if( ((items-1) % 2) != 0 ){ Perl_croak(aTHX_ "Odd number of parameters for %s()", GvNAME(CvGV(cv))); } for(i = 1; i < items; i += 2){ HE* const he = hv_fetch_ent(fields, ST(i), FALSE, 0U); if(!(he && SvROK(HeVAL(he)))){ Perl_croak(aTHX_ "No such field \"%s\" for %s", SvPV_nolen_const(ST(i)), stashname); } fieldhash_store(aTHX_ (HV*)SvRV(HeVAL(he)), object, newSVsv(ST(i+1))); } } XSRETURN(1); /* returns the first argument */ HV* to_hash(SV* object, ...) PREINIT: HV* stash; HV* fields; char* key; I32 keylen; SV* val; bool fully_qualify = FALSE; INIT: if(!sv_isobject(object)){ Perl_croak(aTHX_ "The %s() method must be called as an instance method", GvNAME(CvGV(cv))); } while(items > 1){ SV* const option = ST(--items); if(SvOK(option)){ if(strEQ(SvPV_nolen_const(option), "-fully_qualify")){ fully_qualify = TRUE; } else{ Perl_croak(aTHX_ "Unknown option \"%"SVf"\"", option); } } } CODE: stash = SvSTASH(SvRV(object)); fields = hf_get_named_fields(aTHX_ stash, NULL, NULL); RETVAL = newHV(); hv_iterinit(fields); while((val = hv_iternextsv(fields, &key, &keylen))){ bool const need_to_store = strchr(key, ':') ? fully_qualify : !fully_qualify; if( need_to_store && SvROK(val) ){ HV* const fieldhash = (HV*)SvRV(val); SV* const value = fieldhash_fetch(aTHX_ fieldhash, object); (void)hv_store(RETVAL, key, keylen, newSVsv(value), 0U); } } OUTPUT: RETVAL