/* 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)))))) /* ... 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 /* --- Global data --------------------------------------------------------- */ #define MY_CXT_KEY __PACKAGE__ "::_guts" XS_VERSION typedef struct { HV *map; AV *next_pkg; AV *next_name; 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_register(pTHX_ const sub_op_keyword *k) { SV *key = newSViv(PTR2IV(k->pp)); if (!PL_custom_op_names) PL_custom_op_names = newHV(); (void) hv_store_ent(PL_custom_op_names, key, newSVpv(k->name, k->len), 0); if (!PL_custom_op_descs) PL_custom_op_descs = newHV(); (void) hv_store_ent(PL_custom_op_descs, key, newSVpv(k->name, k->len), 0); if (k->check) { SV *check = newSViv(PTR2IV(k->check)); sv_magicext(key, check, PERL_MAGIC_ext, NULL, k->ud, 0); SvREFCNT_dec(check); } { dMY_CXT; (void) hv_store(MY_CXT.map, k->name, k->len, key, 0); } } /* --- Private helpers ----------------------------------------------------- */ #define SO_LINKLIST(O) ((O)->op_next ? (O)->op_next : sub_op_linklist(O)) STATIC OP *sub_op_linklist(pTHX_ OP *o) { #define sub_op_linklist(O) sub_op_linklist(aTHX_ (O)) OP *first; if (o->op_next) return o->op_next; /* establish postfix order */ first = cUNOPo->op_first; if (first) { register OP *kid; o->op_next = SO_LINKLIST(first); kid = first; for (;;) { if (kid->op_sibling) { kid->op_next = SO_LINKLIST(kid->op_sibling); kid = kid->op_sibling; } else { kid->op_next = o; break; } } } else o->op_next = o; return o->op_next; } STATIC IV sub_op_hint(pTHX) { #define sub_op_hint() sub_op_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 *(*sub_op_old_ck_entersub)(pTHX_ OP *) = 0; STATIC OP *sub_op_ck_entersub(pTHX_ OP *o) { dMY_CXT; o = CALL_FPTR(sub_op_old_ck_entersub)(aTHX_ o); if (sub_op_hint()) { dMY_CXT; U32 hash = 0; SV *pkg, *name, *pp_sv; pkg = av_pop(MY_CXT.next_pkg); if (!SvOK(pkg)) return o; name = av_pop(MY_CXT.next_name); if (!SvOK(name)) { SvREFCNT_dec(pkg); return o; } { HV *stash = gv_stashsv(pkg, 0); if (stash) { HE *he = hv_fetch_ent(stash, name, 0, 0); if (he) { CV *cv; SV *gv = HeVAL(he); hash = HeHASH(he); if (gv && SvTYPE(gv) >= SVt_PVGV && (cv = GvCV(gv)) == MY_CXT.placeholder){ SvREFCNT_dec(cv); GvCV(gv) = NULL; if (!GvSV(gv) && !GvAV(gv) && !GvHV(gv) && !GvIO(gv) && !GvFORM(gv)) (void) hv_delete_ent(stash, name, G_DISCARD, hash); } } } } { HE *he = hv_fetch_ent(MY_CXT.map, name, 0, hash); if (!he) goto skip; pp_sv = HeVAL(he); if (!SvOK(pp_sv)) goto skip; } if (o->op_type != OP_ENTERSUB) goto skip; if (o->op_private & OPpENTERSUB_AMPER) /* hopefully \&foo */ goto skip; { OP *ex_list = cUNOPo->op_first; OP *rv2cv, *gvop; OP *last_arg = NULL; /* 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 skip; { GV *gv = cGVOPx_gv(gvop); STRLEN len; const char *s = SvPV_const(name, len); if (GvNAMELEN(gv) == len && strnEQ(GvNAME(gv), s, len)) { o->op_type = OP_CUSTOM; o->op_ppaddr = INT2PTR(Perl_ppaddr_t, SvIVX(pp_sv)); if (last_arg) last_arg->op_sibling = NULL; op_free(rv2cv); { MAGIC *mg = mg_find(pp_sv, PERL_MAGIC_ext); if (mg) { sub_op_check_t check = INT2PTR(sub_op_check_t, SvIVX(mg->mg_obj)); o = CALL_FPTR(check)(aTHX_ o, mg->mg_ptr); } } sub_op_linklist(o); } } } skip: SvREFCNT_dec(pkg); SvREFCNT_dec(name); } return o; } /* --- XS ------------------------------------------------------------------ */ MODULE = Sub::Op PACKAGE = Sub::Op PROTOTYPES: ENABLE BOOT: { MY_CXT_INIT; MY_CXT.map = newHV(); MY_CXT.next_pkg = newAV(); MY_CXT.next_name = newAV(); MY_CXT.placeholder = NULL; #if SO_THREADSAFE MY_CXT.owner = aTHX; #endif /* SO_THREADSAFE */ sub_op_old_ck_entersub = PL_check[OP_ENTERSUB]; PL_check[OP_ENTERSUB] = sub_op_ck_entersub; } #if SO_THREADSAFE void CLONE(...) PROTOTYPE: DISABLE PREINIT: HV *map; CV *placeholder; tTHX owner; CODE: { 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.next_pkg = newAV(); MY_CXT.next_name = newAV(); MY_CXT.placeholder = placeholder; MY_CXT.owner = aTHX; } #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 _incoming(SV *name, SV *pkg) PROTOTYPE: $$ PPCODE: dMY_CXT; av_push(MY_CXT.next_pkg, SvREFCNT_inc(pkg)); av_push(MY_CXT.next_name, SvREFCNT_inc(name)); XSRETURN(0); void _custom_name(SV *op) PROTOTYPE: $ PREINIT: OP *o; SV *key; HE *he; PPCODE: if (!SvROK(op)) XSRETURN_UNDEF; o = INT2PTR(OP *, SvIV(SvRV(op))); if (!o || o->op_type != OP_CUSTOM) XSRETURN_UNDEF; key = newSViv(PTR2IV(o->op_ppaddr)); he = hv_fetch_ent(PL_custom_op_names, key, 0, 0); SvREFCNT_dec(key); if (!he) XSRETURN_UNDEF; ST(0) = sv_mortalcopy(HeVAL(he)); XSRETURN(1);