/* You may distribute under the terms of either the GNU General Public License
* or the Artistic License (the same terms as Perl itself)
*
* (C) Paul Evans, 2023-2024 -- leonerd@leonerd.org.uk
*/
#define PERL_NO_GET_CONTEXT
#include "EXTERN.h"
#include "perl.h"
#include "XSUB.h"
#include "object_pad.h"
#define HAVE_PERL_VERSION(R, V, S) \
(PERL_REVISION > (R) || (PERL_REVISION == (R) && (PERL_VERSION > (V) || (PERL_VERSION == (V) && (PERL_SUBVERSION >= (S))))))
#include "compilerun_sv.c.inc"
#include "optree-additions.c.inc"
#include "DataChecks.h"
static int checkmagic_get(pTHX_ SV *sv, MAGIC *mg)
{
SV *fieldsv = mg->mg_obj;
sv_setsv_nomg(sv, fieldsv);
return 1;
}
static int checkmagic_set(pTHX_ SV *sv, MAGIC *mg)
{
struct DataChecks_Checker *checker = (struct DataChecks_Checker *)mg->mg_ptr;
assert_value(checker, sv);
SV *fieldsv = mg->mg_obj;
sv_setsv_nomg(fieldsv, sv);
return 1;
}
static const MGVTBL vtbl_checkmagic = {
.svt_get = &checkmagic_get,
.svt_set = &checkmagic_set,
};
static OP *pp_wrap_checkmagic(pTHX)
{
dSP;
SV *sv = TOPs;
SV *ret = sv_newmortal();
struct DataChecks_Checker *checker = (struct DataChecks_Checker *)cUNOP_AUX->op_aux;
sv_magicext(ret, sv, PERL_MAGIC_ext, &vtbl_checkmagic, (char *)checker, 0);
SETs(ret);
RETURN;
}
static SV *checked_parse(pTHX_ FieldMeta *fieldmeta, SV *valuesrc, void *_funcdata)
{
if(mop_field_get_sigil(fieldmeta) != '$')
croak("Can only apply the :Checked attribute to scalar fields");
dSP;
ENTER;
SAVETMPS;
/* eval_sv() et.al. will forgets what package we're actually running in
* because during compiletime, CopSTASH(PL_curcop == &PL_compiling) isn't
* accurate. We need to help it along
*/
SAVECOPSTASH_FREE(PL_curcop);
CopSTASH_set(PL_curcop, PL_curstash);
compilerun_sv(valuesrc, G_SCALAR);
SPAGAIN;
SV *ret = SvREFCNT_inc(POPs);
FREETMPS;
LEAVE;
return ret;
}
static bool checked_apply(pTHX_ FieldMeta *fieldmeta, SV *value, SV **attrdata_ptr, void *_funcdata)
{
if(mop_field_get_sigil(fieldmeta) != '$')
croak("Can only apply the :Checked attribute to scalar fields");
struct DataChecks_Checker *checker = make_checkdata(value);
SvREFCNT_dec(value);
gen_assertmess(checker,
sv_2mortal(newSVpvf("Field %" SVf, SVfARG(mop_field_get_name(fieldmeta)))),
NULL);
*attrdata_ptr = (SV *)checker;
return TRUE;
}
static void checked_gen_accessor_ops(pTHX_ FieldMeta *fieldmeta, SV *attrdata, void *_funcdata,
enum AccessorType type, struct AccessorGenerationCtx *ctx)
{
struct DataChecks_Checker *checker = (struct DataChecks_Checker *)attrdata;
switch(type) {
case ACCESSOR_READER:
return;
case ACCESSOR_WRITER:
ctx->bodyop = op_append_elem(OP_LINESEQ,
make_assertop(checker, newSLUGOP(0)),
ctx->bodyop);
return;
case ACCESSOR_LVALUE_MUTATOR:
{
OP *o = ctx->retop;
if(o->op_type != OP_RETURN)
croak("Expected ctx->retop to be OP_RETURN");
OP *kid = o->op_flags & OPf_KIDS ? cLISTOPo->op_first : NULL, *prevkid = NULL;
if(kid && kid->op_type == OP_PUSHMARK)
prevkid = kid, kid = OpSIBLING(kid);
// TODO: maybe kid is always OP_PADSV, or maybe not.. Should we assert on it?
OP *newkid = newUNOP_AUX(OP_CUSTOM, 0, kid, (UNOP_AUX_item *)attrdata);
newkid->op_ppaddr = &pp_wrap_checkmagic;
if(prevkid)
OpMORESIB_set(prevkid, newkid);
else
croak("TODO: Need to set newkid as kid of listop?!");
if(OpSIBLING(kid))
OpMORESIB_set(newkid, OpSIBLING(kid));
else
OpLASTSIB_set(newkid, o);
if(cLISTOPo->op_last == kid)
cLISTOPo->op_last = newkid;
OpLASTSIB_set(kid, newkid);
return;
}
case ACCESSOR_COMBINED:
ctx->bodyop = op_append_elem(OP_LINESEQ,
newLOGOP(OP_AND, 0,
/* scalar @_ */
op_contextualize(newUNOP(OP_RV2AV, 0, newGVOP(OP_GV, 0, PL_defgv)), G_SCALAR),
make_assertop(checker, newSLUGOP(0))),
ctx->bodyop);
return;
default:
croak("TODO: Unsure what to do with accessor type %d and :Checked", type);
}
}
static OP *checked_gen_valueassert_op(pTHX_ FieldMeta *fieldmeta, SV *attrdata, void *_funcdata,
OP *valueop)
{
struct DataChecks_Checker *checker = (struct DataChecks_Checker *)attrdata;
return make_assertop(checker, valueop);
}
static const struct FieldHookFuncs checked_hooks = {
.ver = OBJECTPAD_ABIVERSION,
.flags = OBJECTPAD_FLAG_ATTR_MUST_VALUE,
.permit_hintkey = "Object::Pad::FieldAttr::Checked/Checked",
.parse = &checked_parse,
.apply = &checked_apply,
.gen_accessor_ops = &checked_gen_accessor_ops,
.gen_valueassert_op = &checked_gen_valueassert_op,
};
MODULE = Object::Pad::FieldAttr::Checked PACKAGE = Object::Pad::FieldAttr::Checked
BOOT:
boot_data_checks(0.09);
register_field_attribute("Checked", &checked_hooks, NULL);