+/* 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);
+
+ {
+ 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);
+
+ 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);