/* This file is part of the Sub::Op Perl module. * See http://search.cpan.org/dist/Sub-Op/ */ #define PERL_NO_GET_CONTEXT #include "EXTERN.h" #include "perl.h" #include "XSUB.h" #define __PACKAGE__ "Sub::Op" #define __PACKAGE_LEN__ (sizeof(__PACKAGE__)-1) /* --- Compatibility wrappers ---------------------------------------------- */ #define SO_HAS_PERL(R, V, S) (PERL_REVISION > (R) || (PERL_REVISION == (R) && (PERL_VERSION > (V) || (PERL_VERSION == (V) && (PERL_SUBVERSION >= (S)))))) #ifndef GvCV_set # define GvCV_set(G, C) (GvCV(G) = (C)) #endif /* ... Thread safety and multiplicity ...................................... */ #ifndef SO_MULTIPLICITY # if defined(MULTIPLICITY) || defined(PERL_IMPLICIT_CONTEXT) # define SO_MULTIPLICITY 1 # else # define SO_MULTIPLICITY 0 # endif #endif #if SO_MULTIPLICITY && !defined(tTHX) # define tTHX PerlInterpreter* #endif #if SO_MULTIPLICITY && defined(USE_ITHREADS) && defined(dMY_CXT) && defined(MY_CXT) && defined(START_MY_CXT) && defined(MY_CXT_INIT) && (defined(MY_CXT_CLONE) || defined(dMY_CXT_SV)) # define SO_THREADSAFE 1 # ifndef MY_CXT_CLONE # define MY_CXT_CLONE \ dMY_CXT_SV; \ my_cxt_t *my_cxtp = (my_cxt_t*)SvPVX(newSV(sizeof(my_cxt_t)-1)); \ Copy(INT2PTR(my_cxt_t*, SvUV(my_cxt_sv)), my_cxtp, 1, my_cxt_t); \ sv_setuv(my_cxt_sv, PTR2UV(my_cxtp)) # endif #else # define SO_THREADSAFE 0 # undef dMY_CXT # define dMY_CXT dNOOP # undef MY_CXT # define MY_CXT indirect_globaldata # undef START_MY_CXT # define START_MY_CXT STATIC my_cxt_t MY_CXT; # undef MY_CXT_INIT # define MY_CXT_INIT NOOP # undef MY_CXT_CLONE # define MY_CXT_CLONE NOOP #endif #if defined(OP_CHECK_MUTEX_LOCK) && defined(OP_CHECK_MUTEX_UNLOCK) # define SO_CHECK_MUTEX_LOCK OP_CHECK_MUTEX_LOCK # define SO_CHECK_MUTEX_UNLOCK OP_CHECK_MUTEX_UNLOCK #else # define SO_CHECK_MUTEX_LOCK OP_REFCNT_LOCK # define SO_CHECK_MUTEX_UNLOCK OP_REFCNT_UNLOCK #endif typedef OP *(*so_ck_t)(pTHX_ OP *); #ifdef wrap_op_checker # define so_ck_replace(T, NC, OCP) wrap_op_checker((T), (NC), (OCP)) #else STATIC void so_ck_replace(pTHX_ OPCODE type, so_ck_t new_ck, so_ck_t *old_ck_p){ #define so_ck_replace(T, NC, OCP) so_ck_replace(aTHX_ (T), (NC), (OCP)) SO_CHECK_MUTEX_LOCK; if (!*old_ck_p) { *old_ck_p = PL_check[type]; PL_check[type] = new_ck; } SO_CHECK_MUTEX_UNLOCK; } #endif STATIC void so_ck_restore(pTHX_ OPCODE type, so_ck_t *old_ck_p) { #define so_ck_restore(T, OCP) so_ck_restore(aTHX_ (T), (OCP)) SO_CHECK_MUTEX_LOCK; if (*old_ck_p) { PL_check[type] = *old_ck_p; *old_ck_p = 0; } SO_CHECK_MUTEX_UNLOCK; } /* --- Global data --------------------------------------------------------- */ #define MY_CXT_KEY __PACKAGE__ "::_guts" XS_VERSION typedef struct { HV *map; CV *placeholder; #if SO_THREADSAFE tTHX owner; #endif /* SO_THREADSAFE */ } my_cxt_t; START_MY_CXT #if SO_THREADSAFE STATIC SV *so_clone(pTHX_ SV *sv, tTHX owner) { #define so_clone(S, O) so_clone(aTHX_ (S), (O)) CLONE_PARAMS param; AV *stashes = NULL; SV *dupsv; if (SvTYPE(sv) == SVt_PVHV && HvNAME_get(sv)) stashes = newAV(); param.stashes = stashes; param.flags = 0; param.proto_perl = owner; dupsv = sv_dup(sv, ¶m); if (stashes) { av_undef(stashes); SvREFCNT_dec(stashes); } return SvREFCNT_inc(dupsv); } #endif /* SO_THREADSAFE */ /* --- Public API ---------------------------------------------------------- */ #include "sub_op.h" void sub_op_init(sub_op_config_t *c) { c->name = NULL; c->namelen = 0; c->proto = NULL; c->protolen = 0; c->call = 0; c->ref = 0; c->ud = NULL; return; } void sub_op_register(pTHX_ const sub_op_config_t *c, U32 flags) { dMY_CXT; if (!(flags & SUB_OP_REGISTER_STEAL)) c = sub_op_dup(aTHX_ c); (void) hv_store(MY_CXT.map, c->name, c->namelen, newSViv(PTR2IV(c)), 0); } STATIC const char *so_strndup(pTHX_ const char *s, STRLEN len) { #define so_strndup(S, L) so_strndup(aTHX_ (S), (L)) const char *d; if (!s) return NULL; d = PerlMemShared_malloc(len + 1); Copy(s, d, len, char); ((char *) d)[len] = '\0'; return d; } sub_op_config_t *sub_op_dup(pTHX_ const sub_op_config_t *orig) { sub_op_config_t *dupe = PerlMemShared_malloc(sizeof *dupe); dupe->name = so_strndup(orig->name, orig->namelen); dupe->namelen = orig->namelen; dupe->proto = so_strndup(orig->proto, orig->protolen); dupe->protolen = orig->protolen; dupe->call = orig->call; dupe->ref = orig->ref; dupe->ud = orig->ud; return dupe; } void sub_op_free(pTHX_ sub_op_config_t *c) { PerlMemShared_free((char *) c->name); PerlMemShared_free(c); return; } OP *sub_op_study(const OP *o, OP **last_arg_p, OP **rv2cv_p) { OP *ex_list, *last_arg, *rv2cv, *gvop; ex_list = cUNOPo->op_first; /* pushmark when a method call */ if (!ex_list || ex_list->op_type != OP_NULL) goto skip; rv2cv = cUNOPx(ex_list)->op_first; if (!rv2cv) goto skip; while (1) { OP *next = rv2cv->op_sibling; if (!next) break; last_arg = rv2cv; rv2cv = next; } if (!(rv2cv->op_flags & OPf_KIDS)) goto skip; gvop = cUNOPx(rv2cv)->op_first; if (gvop && gvop->op_type == OP_GV) goto done; skip: last_arg = NULL; rv2cv = NULL; gvop = NULL; done: if (last_arg_p) *last_arg_p = last_arg; if (rv2cv_p) *rv2cv_p = rv2cv; return gvop; } /* --- Private helpers ----------------------------------------------------- */ STATIC IV so_hint(pTHX) { #define so_hint() so_hint(aTHX) SV *hint; #if SO_HAS_PERL(5, 9, 5) hint = Perl_refcounted_he_fetch(aTHX_ PL_curcop->cop_hints_hash, NULL, __PACKAGE__, __PACKAGE_LEN__, 0, 0); #else { SV **val = hv_fetch(GvHV(PL_hintgv), __PACKAGE__, __PACKAGE_LEN__, 0); if (!val) return 0; hint = *val; } #endif return (SvOK(hint) && SvIOK(hint)) ? SvIVX(hint) : 0; } STATIC OP *so_find_gvop(const OP *o) { OP *ex_list, *last_arg, *rv2cv, *gvop; ex_list = cUNOPo->op_first; /* pushmark when a method call */ if (!ex_list || ex_list->op_type != OP_NULL) goto skip; rv2cv = cUNOPx(ex_list)->op_first; if (!rv2cv) goto skip; while (1) { OP *next = rv2cv->op_sibling; if (!next) break; rv2cv = next; } if (!(rv2cv->op_flags & OPf_KIDS)) goto skip; gvop = cUNOPx(rv2cv)->op_first; if (gvop && gvop->op_type == OP_GV) return gvop; skip: return NULL; } STATIC OP *(*so_old_ck_entersub)(pTHX_ OP *) = 0; STATIC OP *so_ck_entersub(pTHX_ OP *o) { o = CALL_FPTR(so_old_ck_entersub)(aTHX_ o); if (so_hint()) { OP *gvop; GV *gv; if (o->op_type != OP_ENTERSUB) goto skip; if (o->op_private & OPpENTERSUB_AMPER) /* hopefully \&foo */ goto skip; gvop = so_find_gvop(o); if (!gvop) goto skip; gv = cGVOPx_gv(gvop); { SV **svp; CV *cv = NULL; const char *name = GvNAME(gv); I32 len = GvNAMELEN(gv); const sub_op_config_t *c; dMY_CXT; svp = hv_fetch(MY_CXT.map, name, len, 0); if (!svp) goto skip; c = INT2PTR(const sub_op_config_t *, SvIVX(*svp)); if (gv && SvTYPE(gv) >= SVt_PVGV && (cv = GvCV(gv)) == MY_CXT.placeholder) { SvREFCNT_dec(cv); GvCV_set(gv, NULL); } if (c->call) o = CALL_FPTR(c->call)(aTHX_ o, c->ud); } } skip: return o; } STATIC OP *(*so_old_ck_refgen)(pTHX_ OP *) = 0; STATIC OP *so_ck_refgen(pTHX_ OP *o) { o = CALL_FPTR(so_old_ck_refgen)(aTHX_ o); if (so_hint()) { OP *kid = o; OP *prev = NULL; OP *parent = NULL; while (kid->op_flags & OPf_KIDS) { parent = kid; kid = cUNOPx(kid)->op_first; } if (!parent) goto skip; for (kid; kid; prev = kid, kid = kid->op_sibling) { OP *gvop; GV *gv; const sub_op_config_t *c; if (kid->op_type != OP_RV2CV) continue; gvop = so_find_gvop(kid); if (!gvop) continue; gv = cGVOPx_gv(gvop); { SV **svp; const char *name = GvNAME(gv); I32 len = GvNAMELEN(gv); dMY_CXT; svp = hv_fetch(MY_CXT.map, name, len, 0); if (!svp) continue; c = INT2PTR(const sub_op_config_t *, SvIVX(*svp)); } if (c->ref) { OP *new_kid = CALL_FPTR(c->ref)(aTHX_ kid, c->ud); if (new_kid != kid) { new_kid->op_sibling = kid->op_sibling; new_kid->op_next = new_kid; if (prev) prev->op_sibling = new_kid; else cUNOPx(parent)->op_first = new_kid; op_null(kid); kid = new_kid; } } } } skip: return o; } STATIC OP *(*so_old_ck_gelem)(pTHX_ OP *) = 0; STATIC OP *so_ck_gelem(pTHX_ OP *o) { o = CALL_FPTR(so_old_ck_entersub)(aTHX_ o); if (so_hint()) { OP *rv2gv, *gvop; GV *gv; rv2gv = cUNOPo->op_first; if (!rv2gv) goto skip; gvop = cUNOPx(rv2gv)->op_first; if (!gvop || gvop->op_type != OP_GV) goto skip; gv = cGVOPx_gv(gvop); if (!gv) goto skip; { CV *cv; dMY_CXT; if (gv && SvTYPE(gv) >= SVt_PVGV && (cv = GvCV(gv)) == MY_CXT.placeholder) { SvREFCNT_dec(cv); GvCV_set(gv, NULL); } } } skip: return o; } /* --- XS ------------------------------------------------------------------ */ MODULE = Sub::Op PACKAGE = Sub::Op PROTOTYPES: ENABLE BOOT: { MY_CXT_INIT; MY_CXT.map = newHV(); MY_CXT.placeholder = NULL; #if SO_THREADSAFE MY_CXT.owner = aTHX; #endif /* SO_THREADSAFE */ so_ck_replace(OP_ENTERSUB, so_ck_entersub, &so_old_ck_entersub); so_ck_replace(OP_REFGEN, so_ck_refgen, &so_old_ck_refgen); so_ck_replace(OP_GELEM, so_ck_gelem, &so_old_ck_gelem); } #if SO_THREADSAFE void CLONE(...) PROTOTYPE: DISABLE PREINIT: HV *map; CV *placeholder; tTHX owner; PPCODE: { dMY_CXT; owner = MY_CXT.owner; map = (HV *) so_clone((SV *) MY_CXT.map, owner); placeholder = (CV *) so_clone((SV *) MY_CXT.placeholder, owner); } { MY_CXT_CLONE; MY_CXT.map = map; MY_CXT.placeholder = placeholder; MY_CXT.owner = aTHX; } XSRETURN(0); #endif /* SO_THREADSAFE */ void _placeholder(SV *sv) PROTOTYPE: $ PPCODE: if (SvROK(sv)) { sv = SvRV(sv); if (SvTYPE(sv) >= SVt_PVCV) { dMY_CXT; SvREFCNT_dec(MY_CXT.placeholder); MY_CXT.placeholder = (CV *) SvREFCNT_inc(sv); } } XSRETURN(0); void _constant_sub(SV *sv) PROTOTYPE: $ PPCODE: if (!SvROK(sv)) XSRETURN_UNDEF; sv = SvRV(sv); if (SvTYPE(sv) < SVt_PVCV) XSRETURN_UNDEF; ST(0) = sv_2mortal(newSVuv(CvCONST(sv))); XSRETURN(1); SV * _get_prototype(SV *name) PROTOTYPE: $ PREINIT: HE *he; const sub_op_config_t *c; PPCODE: dMY_CXT; he = hv_fetch_ent(MY_CXT.map, name, 0, 0); if (!he) XSRETURN_UNDEF; c = INT2PTR(const sub_op_config_t *, SvIVX(HeVAL(he))); if (!c->proto) XSRETURN_UNDEF; ST(0) = sv_2mortal(newSVpvn(c->proto, c->protolen)); XSRETURN(1);