+static int vmg_dispell_guard_free(pTHX_ SV *sv, MAGIC *mg) {
+ vmg_magic_chain_free((MAGIC *) mg->mg_ptr, NULL);
+
+ return 0;
+}
+
+#if XSH_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 /* XSH_THREADSAFE */
+
+static MGVTBL vmg_dispell_guard_vtbl = {
+ NULL, /* get */
+ NULL, /* set */
+ NULL, /* len */
+ NULL, /* clear */
+ vmg_dispell_guard_free, /* free */
+ NULL, /* copy */
+#if XSH_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();
+ vmg_sv_magicext(guard, NULL, &vmg_dispell_guard_vtbl, 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;
+
+ PUSHSTACKi(PERLSI_MAGIC);
+
+ 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) {
+ dXSH_CXT;
+ XSH_CXT.depth++;
+ vmg_call_sv(cb, G_SCALAR, vmg_dispell_guard_oncroak, NULL);
+ XSH_CXT.depth--;
+ if (XSH_CXT.depth == 0 && XSH_CXT.freed_tokens)
+ chain = &XSH_CXT.freed_tokens;
+ } else {
+ vmg_call_sv(cb, G_SCALAR, 0, NULL);
+ }
+
+ SPAGAIN;
+ svr = POPs;
+ if (SvOK(svr))
+ ret = (int) SvIV(svr);
+ if (SvROK(svr))
+ SvREFCNT_inc(svr);
+ else
+ svr = NULL;
+ PUTBACK;
+
+ POPSTACK;
+
+ FREETMPS;
+ LEAVE;
+
+ if (svr && !SvTEMP(svr))
+ sv_2mortal(svr);
+
+ if (chain) {
+ vmg_dispell_guard_new(*chain);
+ *chain = NULL;
+ }
+
+ return ret;
+}
+
+#define VMG_CB_FLAGS(OI, A) \
+ ((((unsigned int) (OI)) << VMG_CB_CALL_ARGS_SHIFT) | (A))
+
+#define vmg_cb_call1(I, OI, S, A1) \
+ vmg_cb_call(aTHX_ (I), VMG_CB_FLAGS((OI), 1), (S), (A1))
+#define vmg_cb_call2(I, OI, S, A1, A2) \
+ vmg_cb_call(aTHX_ (I), VMG_CB_FLAGS((OI), 2), (S), (A1), (A2))
+#define vmg_cb_call3(I, OI, S, A1, A2, A3) \
+ vmg_cb_call(aTHX_ (I), VMG_CB_FLAGS((OI), 3), (S), (A1), (A2), (A3))
+
+/* ... Default no-op magic callback ........................................ */
+
+static int vmg_svt_default_noop(pTHX_ SV *sv, MAGIC *mg) {
+ return 0;
+}
+
+/* ... get magic ........................................................... */
+
+static int vmg_svt_get(pTHX_ SV *sv, MAGIC *mg) {
+ const vmg_wizard *w = vmg_wizard_from_mg_nocheck(mg);
+
+ return vmg_cb_call1(w->cb_get, w->opinfo, sv, mg->mg_obj);
+}
+
+#define vmg_svt_get_noop vmg_svt_default_noop
+
+/* ... set magic ........................................................... */
+
+static int vmg_svt_set(pTHX_ SV *sv, MAGIC *mg) {
+ const vmg_wizard *w = vmg_wizard_from_mg_nocheck(mg);
+
+ return vmg_cb_call1(w->cb_set, w->opinfo, sv, mg->mg_obj);
+}
+
+#define vmg_svt_set_noop vmg_svt_default_noop