Skip Menu |

This queue is for tickets about the Object-Pad CPAN distribution.

Report information
The Basics
Id: 132337
Status: resolved
Priority: 0/
Queue: Object-Pad

People
Owner: Nobody in particular
Requestors: TEAM [...] cpan.org
Cc:
AdminCc:

Bug Information
Severity: (no value)
Broken in: (no value)
Fixed in: 0.21



Subject: Instance slot confusion via eval
Easiest to demonstrate with an example: $ perl -le'use Object::Pad; class Parent { has $thing = 2; } package Child; Object::Pad->import_into("Child"); eval "package Child; class Child extends Parent;"; has $other = 3; method other { return $other } print Child->new->other' ARGH: instance does not have a slot at index 1 at -e line 1. Use-case here is some code which tries to provide Object::Pad support via a pragma - `package Child; use some_pragma; has $data; method data { $data }`
On Mon Apr 13 18:56:12 2020, TEAM wrote: Show quoted text
> $ perl -le'use Object::Pad; class Parent { has $thing = 2; } package > Child; Object::Pad->import_into("Child"); eval "package Child; class > Child extends Parent;"; has $other = 3; method other { return $other } > print Child->new->other'
A number of issues here in both Object::Pad and your example. First problem is that the ->import_into and eval only happen at runtime, which is too late to influence the `has` and `method`. But due to a bug in the way compclassmeta is implemented the previous one from Parent has leaked out; so that is actually the one still in effect there. With that bug fixed (see attached patch), the mistake in the example becomes clear: $ cat rt132337.pl #!/usr/bin/perl use Object::Pad; class Parent { has $thing = 2; } package Child; Object::Pad->import_into("Child"); eval "package Child; class Child extends Parent;"; has $other = 3; method other { return $other } print Child->new->other $ perl -Mblib rt132337.pl Cannot 'has' outside of 'class' at rt132337.pl line 10. Could now fix that by BEGIN-wrapping these two lines, but currently that makes no difference. I suspect a better fix is to arrange for compclassmeta to be restored at "end of block" time in the open ("class Foo;") case, rather than rely on the save stack which would get unwound at the end of the BEGIN block. -- Paul Evans
Subject: rt132337-part1.patch
=== modified file 'lib/Object/Pad.xs' --- old/lib/Object/Pad.xs 2020-04-12 12:53:01 +0000 +++ new/lib/Object/Pad.xs 2020-04-14 16:32:07 +0000 @@ -154,10 +154,30 @@ /* The metadata on the currently-compiling class */ #ifdef MULTIPLICITY -# define compclassmeta \ - (*((ClassMeta **)hv_fetchs(PL_modglobal, "Object::Pad/compclassmeta", GV_ADD))) -# define have_compclassmeta \ - (!!hv_fetchs(PL_modglobal, "Object::Pad/compclassmeta", 0)) +# define compclassmeta (*S_compclassmetaptr(aTHX)) +static ClassMeta **S_compclassmetaptr(pTHX) +{ + SV *sv = *hv_fetchs(PL_modglobal, "Object::Pad/compclassmeta", GV_ADD); + if(!SvOK(sv)) + sv_setiv(sv, 0); + /* Abuse the IV field as a struct pointer; we know it should be OK because + * IV has to be big enough to store a pointer */ + void *ivptr = &(SvIVX(sv)); + return ivptr; +} + +# define have_compclassmeta S_have_compclassmeta(aTHX) +static bool S_have_compclassmeta(pTHX) +{ + SV **svp = hv_fetchs(PL_modglobal, "Object::Pad/compclassmeta", 0); + if(!svp || !*svp) + return false; + + if(SvOK(*svp) && SvIV(*svp)) + return true; + + return false; +} #else /* without MULTIPLICITY there's only one, so we might as well just store it * in a static @@ -771,9 +791,7 @@ import_pragma("experimental", "signatures"); #endif - if(have_compclassmeta) { - SAVEVPTR(compclassmeta); - } + SAVEVPTR(compclassmeta); ClassMeta *meta; Newx(meta, 1, ClassMeta);
On Tue Apr 14 12:35:20 2020, PEVANS wrote: Show quoted text
> Could now fix that by BEGIN-wrapping these two lines, but currently > that makes no difference. I suspect a better fix is to arrange for > compclassmeta to be restored at "end of block" time in the open > ("class Foo;") case, rather than rely on the save stack which would > get unwound at the end of the BEGIN block.
Turns out that even that doesn't help; the same block-level safety in normal operation also shields the contents of the string eval() from having any effect on its caller. I believe the time may now be set to start considering some dynamic API to do these things, as an alternative to using the keyword syntax. Hypothetically then your example would become package Child; BEGIN { Object::Pad->import_into("Child"); Object::Pad->begin_class("Child", extends => "Parent"); } ... to avoid the string eval. -- Paul Evans
Current code as of bzr -r176 (the diff is rather large) now permits: $ cat rt132337.pl #!/usr/bin/perl { use Object::Pad; class Parent { has $thing = 2; } } { package Child; BEGIN { Object::Pad->import_into("Child"); Object::Pad->begin_class("Child", extends => "Parent"); } has $other = 3; method other { return $other } } print Child->new->other, "\n"; $ perl -Mblib rt132337.pl 3 This interface remains entirely undocumented and doubly-experimental, though it is at least unit-tested. It should hopefully stablise and gain some actual MOP-like methods and documentation, once we've had more of a chance to experiment with it and work out what those might look like. -- Paul Evans
Now released in 0.21 -- Paul Evans