+ if (uvars == 1) {
+ vmg_uvar_ud *ud;
+
+ for (prevmagic = NULL, mg = SvMAGIC(sv); mg; prevmagic = mg, mg = moremagic){
+ moremagic = mg->mg_moremagic;
+ if (mg->mg_type == PERL_MAGIC_uvar)
+ break;
+ }
+
+ ud = (vmg_uvar_ud *) mg->mg_ptr;
+ if (ud->old_uf.uf_val || ud->old_uf.uf_set) {
+ /* Revert the original uvar magic. */
+ struct ufuncs *uf;
+ Newx(uf, 1, struct ufuncs);
+ *uf = ud->old_uf;
+ Safefree(ud);
+ mg->mg_ptr = (char *) uf;
+ mg->mg_len = sizeof(*uf);
+ } else {
+ /* Remove the uvar magic. */
+ vmg_mg_del(sv, prevmagic, mg, moremagic);
+ }
+ }
+ }
+#endif /* VMG_UVAR */
+
+ vmg_mg_magical(sv);
+
+ return 1;
+}
+
+/* ... OP info ............................................................. */
+
+#define VMG_OP_INFO_NAME 1
+#define VMG_OP_INFO_OBJECT 2
+
+#if VMG_THREADSAFE
+STATIC perl_mutex vmg_op_name_init_mutex;
+#endif
+
+STATIC U32 vmg_op_name_init = 0;
+STATIC unsigned char vmg_op_name_len[MAXO] = { 0 };
+
+STATIC void vmg_op_info_init(pTHX_ unsigned int opinfo) {
+#define vmg_op_info_init(W) vmg_op_info_init(aTHX_ (W))
+ switch (opinfo) {
+ case VMG_OP_INFO_NAME:
+ VMG_LOCK(&vmg_op_name_init_mutex);
+ if (!vmg_op_name_init) {
+ OPCODE t;
+ for (t = 0; t < OP_max; ++t)
+ vmg_op_name_len[t] = strlen(PL_op_name[t]);
+ vmg_op_name_init = 1;
+ }
+ VMG_UNLOCK(&vmg_op_name_init_mutex);
+ break;
+ case VMG_OP_INFO_OBJECT: {
+ dMY_CXT;
+ if (!MY_CXT.b__op_stashes[0]) {
+ int c;
+ require_pv("B.pm");
+ for (c = OPc_NULL; c < OPc_MAX; ++c)
+ MY_CXT.b__op_stashes[c] = gv_stashpv(vmg_opclassnames[c], 1);
+ }
+ break;
+ }
+ default:
+ break;
+ }
+}
+
+STATIC SV *vmg_op_info(pTHX_ unsigned int opinfo) {
+#define vmg_op_info(W) vmg_op_info(aTHX_ (W))
+ if (!PL_op)
+ return &PL_sv_undef;
+
+ switch (opinfo) {
+ case VMG_OP_INFO_NAME: {
+ OPCODE t = PL_op->op_type;
+ return sv_2mortal(newSVpvn(PL_op_name[t], vmg_op_name_len[t]));
+ }
+ case VMG_OP_INFO_OBJECT: {
+ dMY_CXT;
+ return sv_bless(sv_2mortal(newRV_noinc(newSViv(PTR2IV(PL_op)))),
+ MY_CXT.b__op_stashes[vmg_opclass(PL_op)]);
+ }
+ default:
+ break;
+ }
+
+ return &PL_sv_undef;
+}
+
+/* --- svt callbacks ------------------------------------------------------- */
+
+#define VMG_CB_CALL_ARGS_MASK 15
+#define VMG_CB_CALL_ARGS_SHIFT 4
+#define VMG_CB_CALL_OPINFO (VMG_OP_INFO_NAME|VMG_OP_INFO_OBJECT) /* 1|2 */
+#define VMG_CB_CALL_GUARD 4
+
+STATIC int vmg_dispell_guard_oncroak(pTHX_ void *ud) {
+ dMY_CXT;
+
+ MY_CXT.depth--;
+
+ /* If we're at the upmost magic call and we're about to die, we can just free
+ * the tokens right now, since we will jump past the problematic part of our
+ * caller. */
+ if (MY_CXT.depth == 0 && MY_CXT.freed_tokens) {
+ vmg_magic_chain_free(MY_CXT.freed_tokens, NULL);
+ MY_CXT.freed_tokens = NULL;
+ }
+
+ return 1;
+}
+
+STATIC int vmg_dispell_guard_free(pTHX_ SV *sv, MAGIC *mg) {
+ vmg_magic_chain_free((MAGIC *) mg->mg_ptr, NULL);
+
+ return 0;
+}
+
+#if VMG_THREADSAFE
+
+STATIC int vmg_dispell_guard_dup(pTHX_ MAGIC *mg, CLONE_PARAMS *params) {
+ /* The freed magic tokens aren't cloned by perl because it cannot reach them
+ * (they have been detached from their parent SV when they were enqueued).
+ * Hence there's nothing to purge in the new thread. */
+ mg->mg_ptr = NULL;
+
+ return 0;
+}
+
+#endif /* VMG_THREADSAFE */
+
+STATIC MGVTBL vmg_dispell_guard_vtbl = {
+ NULL, /* get */
+ NULL, /* set */
+ NULL, /* len */
+ NULL, /* clear */
+ vmg_dispell_guard_free, /* free */
+ NULL, /* copy */
+#if VMG_THREADSAFE
+ vmg_dispell_guard_dup, /* dup */
+#else
+ NULL, /* dup */
+#endif
+#if MGf_LOCAL
+ NULL, /* local */
+#endif /* MGf_LOCAL */
+};
+
+STATIC SV *vmg_dispell_guard_new(pTHX_ MAGIC *root) {
+#define vmg_dispell_guard_new(R) vmg_dispell_guard_new(aTHX_ (R))
+ SV *guard;
+
+ guard = sv_newmortal();
+ sv_magicext(guard, NULL, PERL_MAGIC_ext, &vmg_dispell_guard_vtbl,
+ (char *) root, 0);
+
+ return guard;
+}
+
+STATIC int vmg_cb_call(pTHX_ SV *cb, unsigned int flags, SV *sv, ...) {
+ va_list ap;
+ int ret = 0;
+ unsigned int i, args, opinfo;
+ MAGIC **chain = NULL;
+ SV *svr;
+
+ dSP;
+
+ args = flags & VMG_CB_CALL_ARGS_MASK;
+ flags >>= VMG_CB_CALL_ARGS_SHIFT;
+ opinfo = flags & VMG_CB_CALL_OPINFO;
+
+ ENTER;
+ SAVETMPS;
+
+ PUSHMARK(SP);
+ EXTEND(SP, args + 1);
+ PUSHs(sv_2mortal(newRV_inc(sv)));
+ va_start(ap, sv);
+ for (i = 0; i < args; ++i) {
+ SV *sva = va_arg(ap, SV *);
+ PUSHs(sva ? sva : &PL_sv_undef);
+ }
+ va_end(ap);
+ if (opinfo)
+ XPUSHs(vmg_op_info(opinfo));
+ PUTBACK;
+
+ if (flags & VMG_CB_CALL_GUARD) {
+ dMY_CXT;
+ MY_CXT.depth++;
+ vmg_call_sv(cb, G_SCALAR, vmg_dispell_guard_oncroak, NULL);
+ MY_CXT.depth--;
+ if (MY_CXT.depth == 0 && MY_CXT.freed_tokens)
+ chain = &MY_CXT.freed_tokens;
+ } else {
+ vmg_call_sv(cb, G_SCALAR, 0, NULL);
+ }
+
+ SPAGAIN;
+ svr = POPs;
+ if (SvOK(svr))
+ ret = (int) SvIV(svr);
+ PUTBACK;
+
+ FREETMPS;
+ LEAVE;
+
+ if (chain) {
+ vmg_dispell_guard_new(*chain);
+ *chain = NULL;