/* This file is part of the Variable::Magic Perl module. * See http://search.cpan.org/dist/Variable-Magic/ */ #include /* sprintf() */ #define PERL_NO_GET_CONTEXT #include "EXTERN.h" #include "perl.h" #include "XSUB.h" #define __PACKAGE__ "Variable::Magic" #define R(S) fprintf(stderr, "R(" #S ") = %d\n", SvREFCNT(S)) #define PERL_VERSION_LE(R, V, S) (PERL_REVISION < (R) || (PERL_REVISION == (R) && (PERL_VERSION < (V) || (PERL_VERSION == (V) && (PERL_SUBVERSION <= (S)))))) #define PERL_API_VERSION_GE(R, V, S) (PERL_API_REVISION > (R) || (PERL_API_REVISION == (R) && (PERL_API_VERSION > (V) || (PERL_API_VERSION == (V) && (PERL_API_SUBVERSION >= (S)))))) /* --- Compatibility ------------------------------------------------------- */ #ifndef Newx # define Newx(v, n, c) New(0, v, n, c) #endif #ifndef SvMAGIC_set # define SvMAGIC_set(sv, val) (SvMAGIC(sv) = (val)) #endif #ifndef dMY_CXT # define MY_CXT vmg_globaldata # define dMY_CXT # define START_MY_CXT STATIC my_cxt_t MY_CXT; # define MY_CXT_INIT #endif #ifndef PERL_MAGIC_ext # define PERL_MAGIC_ext '~' #endif #ifndef MGf_COPY # define MGf_COPY 0 #endif /* !MGf_COPY */ #ifndef MGf_DUP # define MGf_DUP 0 #endif /* !MGf_DUP */ #ifndef MGf_LOCAL # define MGf_LOCAL 0 #endif /* !MGf_LOCAL */ /* --- Context-safe global data -------------------------------------------- */ #define MY_CXT_KEY __PACKAGE__ "::_guts" XS_VERSION typedef struct { HV *wizz; U16 count; } my_cxt_t; START_MY_CXT /* --- Signatures ---------------------------------------------------------- */ #define SIG_MIN ((U16) (1u << 8)) #define SIG_MAX ((U16) (1u << 16 - 1)) #define SIG_NBR (SIG_MAX - SIG_MIN + 1) #define SIG_WIZ ((U16) (1u << 8 - 1)) /* ... Generate signatures ................................................. */ STATIC U16 vmg_gensig(pTHX) { #define vmg_gensig() vmg_gensig(aTHX) U16 sig; char buf[8]; dMY_CXT; do { sig = SIG_NBR * Drand01() + SIG_MIN; } while (hv_exists(MY_CXT.wizz, buf, sprintf(buf, "%u", sig))); return sig; } /* --- MGWIZ structure ----------------------------------------------------- */ typedef struct { MGVTBL *vtbl; U16 sig; int uvar; SV *cb_data; SV *cb_get, *cb_set, *cb_len, *cb_clear, *cb_free; #if MGf_COPY SV *cb_copy; #endif /* MGf_COPY */ #if MGf_DUP SV *cb_dup; #endif /* MGf_DUP */ #if MGf_LOCAL SV *cb_local; #endif /* MGf_LOCAL */ SV *cb_fetch, *cb_store, *cb_exists, *cb_delete; } MGWIZ; #define MGWIZ2SV(W) (newSVuv(PTR2UV(W))) #define SV2MGWIZ(S) (INT2PTR(MGWIZ*, SvUVX((SV *) (S)))) /* ... Construct private data .............................................. */ STATIC SV *vmg_data_new(pTHX_ SV *ctor, SV *sv, AV *args) { #define vmg_data_new(C, S, A) vmg_data_new(aTHX_ (C), (S), (A)) SV *nsv; dSP; int count; ENTER; SAVETMPS; PUSHMARK(SP); XPUSHs(sv_2mortal(newRV_inc(sv))); if (args != NULL) { I32 i, alen = av_len(args); for (i = 0; i < alen; ++i) { XPUSHs(*av_fetch(args, i, 0)); } } PUTBACK; count = call_sv(ctor, G_SCALAR); SPAGAIN; if (count != 1) { croak("Callback needs to return 1 scalar\n"); } nsv = POPs; #if PERL_VERSION_LE(5, 8, 2) nsv = sv_newref(nsv); /* Workaround some bug in SvREFCNT_inc() */ #else SvREFCNT_inc(nsv); /* Or it will be destroyed in FREETMPS */ #endif PUTBACK; FREETMPS; LEAVE; return nsv; } STATIC SV *vmg_data_get(SV *sv, U16 sig) { MAGIC *mg, *moremagic; MGWIZ *w; if (SvTYPE(sv) >= SVt_PVMG) { for (mg = SvMAGIC(sv); mg; mg = moremagic) { moremagic = mg->mg_moremagic; if ((mg->mg_type == PERL_MAGIC_ext) && (mg->mg_private == sig)) { break; } } if (mg) { return mg->mg_obj; } } return NULL; } /* ... Magic cast/dispell .................................................. */ STATIC I32 vmg_uf_val(pTHX_ IV idx, SV *sv); STATIC UV vmg_cast(pTHX_ SV *sv, SV *wiz, AV *args) { #define vmg_cast(S, W, A) vmg_cast(aTHX_ (S), (W), (A)) int has_uvar = 0; MAGIC *mg = NULL, *moremagic = NULL; MGWIZ *w; SV *data; w = SV2MGWIZ(wiz); if ((SvTYPE(sv) >= SVt_PVMG) && (mg = SvMAGIC(sv))) { if (mg->mg_type == PERL_MAGIC_uvar) { has_uvar = 1; } do { if ((mg->mg_type == PERL_MAGIC_ext) && (mg->mg_private == w->sig)) { break;} mg = mg->mg_moremagic; } while (mg); if (mg) { return 1; } } if (w->uvar && (SvTYPE(sv) >= SVt_PVHV) && !has_uvar) { struct ufuncs uf; uf.uf_val = vmg_uf_val; uf.uf_set = NULL; uf.uf_index = 0; sv_magicext(sv, NULL, PERL_MAGIC_uvar, &PL_vtbl_uvar, (char *) &uf, sizeof(uf)); } data = (w->cb_data) ? vmg_data_new(w->cb_data, sv, args) : NULL; mg = sv_magicext(sv, data, PERL_MAGIC_ext, w->vtbl, (char *) wiz, HEf_SVKEY); mg->mg_private = w->sig; mg->mg_flags = mg->mg_flags #if MGf_COPY | MGf_COPY #endif /* MGf_COPY */ #if MGf_DUP | MGf_DUP #endif /* MGf_DUP */ #if MGf_LOCAL | MGf_LOCAL #endif /* MGf_LOCAL */ ; return 1; } STATIC UV vmg_dispell(pTHX_ SV *sv, U16 sig) { #define vmg_dispell(S, Z) vmg_dispell(aTHX_ (S), (Z)) MAGIC *mg, *prevmagic, *moremagic = NULL; MGWIZ *w; if (SvTYPE(sv) < SVt_PVMG) { return 0; } for (prevmagic = NULL, mg = SvMAGIC(sv); mg; prevmagic = mg, mg = moremagic) { moremagic = mg->mg_moremagic; if ((mg->mg_type == PERL_MAGIC_ext) && (mg->mg_private == sig)) { break; } } if (!mg) { return 0; } if (prevmagic) { prevmagic->mg_moremagic = moremagic; } else { SvMAGIC_set(sv, moremagic); } mg->mg_moremagic = NULL; if (mg->mg_obj != sv) { SvREFCNT_dec(mg->mg_obj); } /* Destroy private data */ SvREFCNT_dec((SV *) mg->mg_ptr); /* Unreference the wizard */ Safefree(mg); return 1; } /* ... svt callbacks ....................................................... */ STATIC int vmg_cb_call(pTHX_ SV *cb, SV *sv, SV *data) { #define vmg_cb_call(I, S, D) vmg_cb_call(aTHX_ (I), (S), (D)) int ret; dSP; int count; ENTER; SAVETMPS; PUSHMARK(SP); XPUSHs(sv_2mortal(newRV_inc(sv))); if (data) { XPUSHs(data); } PUTBACK; count = call_sv(cb, G_SCALAR); SPAGAIN; if (count != 1) { croak("Callback needs to return 1 scalar\n"); } ret = POPi; PUTBACK; FREETMPS; LEAVE; return ret; } STATIC int vmg_cb_call2(pTHX_ SV *cb, SV *sv, SV *data, SV *extra) { #define vmg_cb_call2(I, S, D, E) vmg_cb_call2(aTHX_ (I), (S), (D), (E)) int ret; dSP; int count; ENTER; SAVETMPS; PUSHMARK(SP); XPUSHs(sv_2mortal(newRV_inc(sv))); XPUSHs(data ? data : &PL_sv_undef); if (extra) { XPUSHs(extra); } PUTBACK; count = call_sv(cb, G_SCALAR); SPAGAIN; if (count != 1) { croak("Callback needs to return 1 scalar\n"); } ret = POPi; PUTBACK; FREETMPS; LEAVE; return ret; } STATIC int vmg_svt_get(pTHX_ SV *sv, MAGIC *mg) { return vmg_cb_call(SV2MGWIZ(mg->mg_ptr)->cb_get, sv, mg->mg_obj); } STATIC int vmg_svt_set(pTHX_ SV *sv, MAGIC *mg) { return vmg_cb_call(SV2MGWIZ(mg->mg_ptr)->cb_set, sv, mg->mg_obj); } STATIC U32 vmg_svt_len(pTHX_ SV *sv, MAGIC *mg) { U32 ret; dSP; int count; ENTER; SAVETMPS; PUSHMARK(SP); XPUSHs(sv_2mortal(newRV_inc(sv))); XPUSHs((mg->mg_obj) ? (mg->mg_obj) : &PL_sv_undef); if (SvTYPE(sv) == SVt_PVAV) { XPUSHs(sv_2mortal(newSViv(av_len((AV *) sv) + 1))); } PUTBACK; count = call_sv(SV2MGWIZ(mg->mg_ptr)->cb_len, G_SCALAR); SPAGAIN; if (count != 1) { croak("Callback needs to return 1 scalar\n"); } ret = POPi; PUTBACK; FREETMPS; LEAVE; return ret - 1; } STATIC int vmg_svt_clear(pTHX_ SV *sv, MAGIC *mg) { return vmg_cb_call(SV2MGWIZ(mg->mg_ptr)->cb_clear, sv, mg->mg_obj); } STATIC int vmg_svt_free(pTHX_ SV *sv, MAGIC *mg) { /* So that it can survive tmp cleanup in vmg_cb_call */ SvREFCNT_inc(sv); /* Perl_mg_free will get rid of the magic and decrement mg->mg_obj and * mg->mg_ptr reference count */ return vmg_cb_call(SV2MGWIZ(mg->mg_ptr)->cb_free, sv, mg->mg_obj); } #if MGf_COPY STATIC int vmg_svt_copy(pTHX_ SV *sv, MAGIC *mg, SV *nsv, const char *name, int namelen) { return vmg_cb_call2(SV2MGWIZ(mg->mg_ptr)->cb_copy, sv, mg->mg_obj, nsv); } #endif /* MGf_COPY */ #if MGf_DUP STATIC int vmg_svt_dup(pTHX_ MAGIC *mg, CLONE_PARAMS *p) { return 0; } #endif /* MGf_DUP */ #if MGf_LOCAL STATIC int vmg_svt_local(pTHX_ SV *nsv, MAGIC *mg) { return vmg_cb_call(SV2MGWIZ(mg->mg_ptr)->cb_local, nsv, mg->mg_obj); } #endif /* MGf_LOCAL */ STATIC I32 vmg_uf_val(pTHX_ IV idx, SV *sv) { MAGIC *mg; SV *key; for (mg = SvMAGIC(sv); mg; mg = mg->mg_moremagic) { if (mg->mg_type == PERL_MAGIC_uvar) { key = mg->mg_obj; break; } } if (!key) { return 0; } for (mg = SvMAGIC(sv); mg; mg = mg->mg_moremagic) { MGWIZ *w; if ((mg->mg_type != PERL_MAGIC_ext) || (mg->mg_private < SIG_MIN) || (mg->mg_private > SIG_MAX)) { continue; } w = SV2MGWIZ(mg->mg_ptr); if (!w->uvar) { continue; } switch (idx & (HV_FETCH_ISSTORE|HV_FETCH_ISEXISTS|HV_FETCH_LVALUE|HV_DELETE)){ case 0: if (w->cb_fetch) { return vmg_cb_call2(w->cb_fetch, sv, mg->mg_obj, key); } break; case HV_FETCH_ISSTORE: case HV_FETCH_LVALUE: case (HV_FETCH_ISSTORE|HV_FETCH_LVALUE): if (w->cb_store) { return vmg_cb_call2(w->cb_store, sv, mg->mg_obj, key); } break; case HV_FETCH_ISEXISTS: if (w->cb_exists) { return vmg_cb_call2(w->cb_exists, sv, mg->mg_obj, key);} break; case HV_DELETE: if (w->cb_delete) { return vmg_cb_call2(w->cb_delete, sv, mg->mg_obj, key);} break; } } return 0; } /* ... Wizard destructor ................................................... */ STATIC int vmg_wizard_free(pTHX_ SV *wiz, MAGIC *mg) { char buf[8]; MGWIZ *w; dMY_CXT; w = SV2MGWIZ(wiz); SvREFCNT_inc(wiz); /* Fake survival - it's gonna be deleted anyway */ #if PERL_API_VERSION_GE(5, 9, 5) SvREFCNT_inc(wiz); /* One more push */ #endif if (hv_delete(MY_CXT.wizz, buf, sprintf(buf, "%u", w->sig), 0)) { --MY_CXT.count; } if (w->cb_data != NULL) { SvREFCNT_dec(SvRV(w->cb_data)); } if (w->cb_get != NULL) { SvREFCNT_dec(SvRV(w->cb_get)); } if (w->cb_set != NULL) { SvREFCNT_dec(SvRV(w->cb_set)); } if (w->cb_len != NULL) { SvREFCNT_dec(SvRV(w->cb_len)); } if (w->cb_clear != NULL) { SvREFCNT_dec(SvRV(w->cb_clear)); } if (w->cb_free != NULL) { SvREFCNT_dec(SvRV(w->cb_free)); } #if MGf_COPY if (w->cb_copy != NULL) { SvREFCNT_dec(SvRV(w->cb_copy)); } #endif /* MGf_COPY */ #if MGf_DUP if (w->cb_dup != NULL) { SvREFCNT_dec(SvRV(w->cb_dup)); } #endif /* MGf_DUP */ #if MGf_LOCAL if (w->cb_local != NULL) { SvREFCNT_dec(SvRV(w->cb_local)); } #endif /* MGf_LOCAL */ if (w->cb_fetch != NULL) { SvREFCNT_dec(SvRV(w->cb_fetch)); } if (w->cb_store != NULL) { SvREFCNT_dec(SvRV(w->cb_store)); } if (w->cb_exists != NULL) { SvREFCNT_dec(SvRV(w->cb_exists)); } if (w->cb_delete != NULL) { SvREFCNT_dec(SvRV(w->cb_delete)); } Safefree(w->vtbl); Safefree(w); return 0; } STATIC MGVTBL vmg_wizard_vtbl = { NULL, /* get */ NULL, /* set */ NULL, /* len */ NULL, /* clear */ vmg_wizard_free, /* free */ #if MGf_COPY NULL, /* copy */ #endif /* MGf_COPY */ #if MGf_DUP NULL, /* dup */ #endif /* MGf_DUP */ #if MGf_LOCAL NULL, /* local */ #endif /* MGf_LOCAL */ }; /* --- Error messages and misc helpers ------------------------------------- */ STATIC const char vmg__wizard_args[] = "_wizard() called with a wrong number of arguments - use wizard() instead"; STATIC const char vmg_invalid_wiz[] = "Invalid wizard object"; STATIC const char vmg_invalid_sv[] = "Invalid variable"; STATIC const char vmg_invalid_sig[] = "Invalid numeric signature"; STATIC const char vmg_toomanysigs[] = "Too many magic signatures used"; STATIC const char vmg_argstorefailed[] = "Error while storing arguments"; STATIC U16 vmg_sv2sig(pTHX_ SV *sv) { #define vmg_sv2sig(S) vmg_sv2sig(aTHX_ (S)) U16 sig; if (SvIOK(sv)) { sig = SvUVX(sv); } else if (SvNOK(sv)) { sig = SvNVX(sv); } else if ((SvPOK(sv) && grok_number(SvPVX(sv), SvCUR(sv), NULL))) { sig = SvUV(sv); } else { croak(vmg_invalid_sig); } if (sig < SIG_MIN) { sig += SIG_MIN; } if (sig > SIG_MAX) { sig %= SIG_MAX + 1; } return sig; } #define VMG_SET_CB(T, M, CB) \ cb = (CB); \ (M)->cb_##T = (SvROK(cb)) ? newRV_inc(SvRV(cb)) : NULL; #define VMG_SET_CB_SVT(T, V, M, CB) \ cb = (CB); \ if (SvROK(cb)) { \ (V)->svt_##T = vmg_svt_##T; (M)->cb_##T = newRV_inc(SvRV(cb)); \ } else { \ (V)->svt_##T = NULL; (M)->cb_##T = NULL; \ } /* --- XS ------------------------------------------------------------------ */ MODULE = Variable::Magic PACKAGE = Variable::Magic PROTOTYPES: ENABLE BOOT: { HV *stash; MY_CXT_INIT; MY_CXT.wizz = newHV(); MY_CXT.count = 0; stash = gv_stashpv(__PACKAGE__, 1); newCONSTSUB(stash, "SIG_MIN", newSVuv(SIG_MIN)); newCONSTSUB(stash, "SIG_MAX", newSVuv(SIG_MAX)); newCONSTSUB(stash, "SIG_NBR", newSVuv(SIG_NBR)); newCONSTSUB(stash, "MGf_COPY", newSVuv(MGf_COPY)); newCONSTSUB(stash, "MGf_DUP", newSVuv(MGf_DUP)); newCONSTSUB(stash, "MGf_LOCAL", newSVuv(MGf_LOCAL)); } SV *_wizard(SV *svsig, ...) PROTOTYPE: $@ PREINIT: U16 sig; I32 i; char buf[8]; MGWIZ *w; MGVTBL *t; MAGIC *mg; SV *cb, *sv; CODE: dMY_CXT; if (items != 14) { croak(vmg__wizard_args); } if (SvOK(svsig)) { SV **old; sig = vmg_sv2sig(svsig); if (old = hv_fetch(MY_CXT.wizz, buf, sprintf(buf, "%u", sig), 0)) { ST(0) = sv_2mortal(newRV_inc(*old)); XSRETURN(1); } } else { if (MY_CXT.count >= SIG_NBR) { croak(vmg_toomanysigs); } sig = vmg_gensig(); } Newx(t, 1, MGVTBL); Newx(w, 1, MGWIZ); w->vtbl = t; w->sig = sig; VMG_SET_CB(data, w, ST(1)); VMG_SET_CB_SVT(get, t, w, ST(2)); VMG_SET_CB_SVT(set, t, w, ST(3)); VMG_SET_CB_SVT(len, t, w, ST(4)); VMG_SET_CB_SVT(clear, t, w, ST(5)); VMG_SET_CB_SVT(free, t, w, ST(6)); #if MGf_COPY VMG_SET_CB_SVT(copy, t, w, ST(7)); #endif /* MGf_COPY */ #if MGf_DUP VMG_SET_CB_SVT(dup, t, w, ST(8)); #endif /* MGf_DUP */ #if MGf_LOCAL VMG_SET_CB_SVT(local, t, w, ST(9)); #endif /* MGf_LOCAL */ VMG_SET_CB(fetch, w, ST(10)); VMG_SET_CB(store, w, ST(11)); VMG_SET_CB(exists, w, ST(12)); VMG_SET_CB(delete, w, ST(13)); w->uvar = (w->cb_fetch || w->cb_store || w->cb_exists || w->cb_delete); sv = MGWIZ2SV(w); mg = sv_magicext(sv, NULL, PERL_MAGIC_ext, &vmg_wizard_vtbl, NULL, -1); mg->mg_private = SIG_WIZ; #if MGf_COPY if (t->svt_copy) { mg->mg_flags |= MGf_COPY; } #endif /* MGf_COPY */ #if MGf_DUP if (t->svt_dup) { mg->mg_flags |= MGf_DUP; } #endif /* MGf_DUP */ #if MGf_LOCAL if (t->svt_local) { mg->mg_flags |= MGf_LOCAL; } #endif /* MGf_LOCAL */ hv_store(MY_CXT.wizz, buf, sprintf(buf, "%u", sig), sv, 0); ++MY_CXT.count; RETVAL = newRV_noinc(sv); OUTPUT: RETVAL SV *gensig() PROTOTYPE: CODE: dMY_CXT; if (MY_CXT.count >= SIG_NBR) { croak(vmg_toomanysigs); } RETVAL = newSVuv(vmg_gensig()); OUTPUT: RETVAL SV *getsig(SV *wiz) PROTOTYPE: $ CODE: if (!SvROK(wiz)) { croak(vmg_invalid_wiz); } RETVAL = newSVuv(SV2MGWIZ(SvRV(wiz))->sig); OUTPUT: RETVAL SV *cast(SV *sv, SV *wiz, ...) PROTOTYPE: \[$@%&*]$@ PREINIT: AV *args = NULL; SV *ret; CODE: dMY_CXT; if (SvROK(wiz)) { wiz = SvRV(wiz); } else if (SvOK(wiz)) { char buf[8]; SV **old; U16 sig = vmg_sv2sig(wiz); if (old = hv_fetch(MY_CXT.wizz, buf, sprintf(buf, "%u", sig), 0)) { wiz = *old; } else { XSRETURN_UNDEF; } } else { croak(vmg_invalid_sig); } if (items > 2) { I32 i; args = newAV(); av_fill(args, items - 2); for (i = 2; i < items; ++i) { SV *arg = ST(i); SvREFCNT_inc(arg); if (av_store(args, i - 2, arg) == NULL) { croak(vmg_argstorefailed); } } } ret = newSVuv(vmg_cast(SvRV(sv), wiz, args)); SvREFCNT_dec(args); RETVAL = ret; OUTPUT: RETVAL SV *getdata(SV *sv, SV *wiz) PROTOTYPE: \[$@%&*]$ PREINIT: SV *data; U16 sig; CODE: dMY_CXT; if (SvROK(wiz)) { sig = SV2MGWIZ(SvRV(wiz))->sig; } else if (SvOK(wiz)) { char buf[8]; sig = vmg_sv2sig(wiz); if (!hv_fetch(MY_CXT.wizz, buf, sprintf(buf, "%u", sig), 0)) { XSRETURN_UNDEF; } } else { croak(vmg_invalid_wiz); } data = vmg_data_get(SvRV(sv), sig); if (!data) { XSRETURN_UNDEF; } ST(0) = newSVsv(data); XSRETURN(1); SV *dispell(SV *sv, SV *wiz) PROTOTYPE: \[$@%&*]$ PREINIT: U16 sig; CODE: dMY_CXT; if (SvROK(wiz)) { sig = SV2MGWIZ(SvRV(wiz))->sig; } else if (SvOK(wiz)) { char buf[8]; sig = vmg_sv2sig(wiz); if (!hv_fetch(MY_CXT.wizz, buf, sprintf(buf, "%u", sig), 0)) { XSRETURN_UNDEF; } } else { croak(vmg_invalid_wiz); } RETVAL = newSVuv(vmg_dispell(SvRV(sv), sig)); OUTPUT: RETVAL