/* vi: set ft=c : */
#if !HAVE_PERL_VERSION(5, 16, 0)
# define CopLABEL_len_flags(c,len,flags) Perl_fetch_cop_label(aTHX_ (c), len, flags)
#endif
enum {
FORBID_LOOPEX_DEFAULT = (1<<0),
};
static void walk_ops_forbid(pTHX_ OP *o, U32 flags, HV *permittedloops, const char *blockname)
{
bool is_loop = FALSE;
SV *labelsv = NULL;
switch(o->op_type) {
case OP_NEXTSTATE:
PL_curcop = (COP *)o;
return;
case OP_RETURN:
goto forbid;
case OP_GOTO:
/* TODO: This might be safe, depending on the target */
goto forbid;
case OP_NEXT:
case OP_LAST:
case OP_REDO:
{
/* OPf_SPECIAL means this is a default loopex */
if(o->op_flags & OPf_SPECIAL) {
if(flags & FORBID_LOOPEX_DEFAULT)
goto forbid;
break;
}
/* OPf_STACKED means it's a dynamically computed label */
if(o->op_flags & OPf_STACKED)
goto forbid;
SV *target = newSVpv(cPVOPo->op_pv, strlen(cPVOPo->op_pv));
#if HAVE_PERL_VERSION(5, 16, 0)
if(cPVOPo->op_private & OPpPV_IS_UTF8)
SvUTF8_on(target);
#endif
SAVEFREESV(target);
if(hv_fetch_ent(permittedloops, target, FALSE, 0))
break;
goto forbid;
}
case OP_LEAVELOOP:
{
STRLEN label_len;
U32 label_flags;
const char *label_pv = CopLABEL_len_flags(PL_curcop, &label_len, &label_flags);
if(label_pv) {
labelsv = newSVpvn(label_pv, label_len);
if(label_flags & SVf_UTF8)
SvUTF8_on(labelsv);
SAVEFREESV(labelsv);
sv_inc(HeVAL(hv_fetch_ent(permittedloops, labelsv, TRUE, 0)));
}
is_loop = TRUE;
break;
}
forbid:
croak("Can't \"%s\" out of %s", PL_op_name[o->op_type], blockname);
default:
break;
}
if(!(o->op_flags & OPf_KIDS))
return;
OP *kid = cUNOPo->op_first;
while(kid) {
walk_ops_forbid(aTHX_ kid, flags, permittedloops, blockname);
kid = OpSIBLING(kid);
if(is_loop) {
/* Now in the body of the loop; we can permit loopex default */
flags &= ~FORBID_LOOPEX_DEFAULT;
}
}
if(is_loop && labelsv) {
HE *he = hv_fetch_ent(permittedloops, labelsv, FALSE, 0);
if(SvIV(HeVAL(he)) > 1)
sv_dec(HeVAL(he));
else
hv_delete_ent(permittedloops, labelsv, 0, 0);
}
}
#ifndef forbid_outofblock_ops
# define forbid_outofblock_ops(o, blockname) S_forbid_outofblock_ops(aTHX_ o, blockname)
static void S_forbid_outofblock_ops(pTHX_ OP *o, const char *blockname)
{
ENTER;
SAVEVPTR(PL_curcop);
HV *looplabels = newHV();
SAVEFREESV((SV *)looplabels);
walk_ops_forbid(aTHX_ o, FORBID_LOOPEX_DEFAULT, looplabels, blockname);
LEAVE;
}
#endif