assert(!(HvAUX(stash)->xhv_mro_meta));
Newxz(newmeta, 1, struct mro_meta);
HvAUX(stash)->xhv_mro_meta = newmeta;
- newmeta->sub_generation = 1;
-
- /* Manually flag UNIVERSAL as being universal.
- This happens early in perl booting (when universal.c
- does the newXS calls for UNIVERSAL::*), and infects
- other packages as they are added to UNIVERSAL's MRO
- */
- if(HvNAMELEN_get(stash) == 9
- && strEQ(HEK_KEY(HvAUX(stash)->xhv_name), "UNIVERSAL")) {
- HvMROMETA(stash)->is_universal = 1;
- }
+ newmeta->cache_gen = 1;
+ newmeta->pkg_gen = 1;
return newmeta;
}
if (newmeta->mro_linear_c3)
newmeta->mro_linear_c3
= (AV*) SvREFCNT_inc(sv_dup((SV*)newmeta->mro_linear_c3, param));
- if (newmeta->mro_isarev)
- newmeta->mro_isarev
- = (HV*) SvREFCNT_inc(sv_dup((SV*)newmeta->mro_isarev, param));
if (newmeta->mro_nextmethod)
newmeta->mro_nextmethod
= (HV*) SvREFCNT_inc(sv_dup((SV*)newmeta->mro_nextmethod, param));
stashname = HvNAME_get(stash);
if (!stashname)
- Perl_croak(aTHX_
- "Can't linearize anonymous symbol table");
+ Perl_croak(aTHX_ "Can't linearize anonymous symbol table");
if (level > 100)
Perl_croak(aTHX_ "Recursive inheritance detected in package '%s'",
exists check and avoid adding duplicate classnames to
the MRO as we go. */
- HV* stored = (HV*)sv_2mortal((SV*)newHV());
+ HV* const stored = (HV*)sv_2mortal((SV*)newHV());
SV **svp = AvARRAY(av);
I32 items = AvFILLp(av) + 1;
stashname = HvNAME_get(stash);
stashname_len = HvNAMELEN_get(stash);
if (!stashname)
- Perl_croak(aTHX_
- "Can't linearize anonymous symbol table");
+ Perl_croak(aTHX_ "Can't linearize anonymous symbol table");
if (level > 100)
Perl_croak(aTHX_ "Recursive inheritance detected in package '%s'",
/* not in cache, make a new one */
- retval = newAV();
- av_push(retval, newSVpvn(stashname, stashname_len)); /* us first */
-
gvp = (GV**)hv_fetchs(stash, "ISA", FALSE);
isa = (gvp && (gv = *gvp) && isGV_with_GP(gv)) ? GvAV(gv) : NULL;
if(isa && AvFILLp(isa) >= 0) {
SV** seqs_ptr;
I32 seqs_items;
- HV* tails = (HV*)sv_2mortal((SV*)newHV());
- AV* seqs = (AV*)sv_2mortal((SV*)newAV());
+ HV* const tails = (HV*)sv_2mortal((SV*)newHV());
+ AV* const seqs = (AV*)sv_2mortal((SV*)newAV());
I32* heads;
/* This builds @seqs, which is an array of arrays.
I32 items = AvFILLp(isa) + 1;
SV** isa_ptr = AvARRAY(isa);
while(items--) {
- AV* isa_lin;
- SV* isa_item = *isa_ptr++;
- HV* isa_item_stash = gv_stashsv(isa_item, 0);
+ SV* const isa_item = *isa_ptr++;
+ HV* const isa_item_stash = gv_stashsv(isa_item, 0);
if(!isa_item_stash) {
/* if no stash, make a temporary fake MRO
containing just itself */
- isa_lin = (AV*)sv_2mortal((SV*)newAV());
+ AV* const isa_lin = newAV();
av_push(isa_lin, newSVsv(isa_item));
+ av_push(seqs, (SV*)isa_lin);
}
else {
- isa_lin = mro_get_linear_isa_c3(isa_item_stash, level + 1); /* recursion */
+ /* recursion */
+ AV* const isa_lin = mro_get_linear_isa_c3(isa_item_stash, level + 1);
+ av_push(seqs, SvREFCNT_inc_simple_NN((SV*)isa_lin));
}
- av_push(seqs, (SV*)isa_lin);
}
- av_push(seqs, (SV*)isa);
+ av_push(seqs, SvREFCNT_inc_simple_NN((SV*)isa));
/* This builds "heads", which as an array of integer array
indices, one per seq, which point at the virtual "head"
seqs_ptr = AvARRAY(seqs);
seqs_items = AvFILLp(seqs) + 1;
while(seqs_items--) {
- AV* seq = (AV*)*seqs_ptr++;
+ AV* const seq = (AV*)*seqs_ptr++;
I32 seq_items = AvFILLp(seq);
if(seq_items > 0) {
SV** seq_ptr = AvARRAY(seq) + 1;
while(seq_items--) {
- SV* seqitem = *seq_ptr++;
- HE* he = hv_fetch_ent(tails, seqitem, 0, 0);
+ SV* const seqitem = *seq_ptr++;
+ HE* const he = hv_fetch_ent(tails, seqitem, 0, 0);
if(!he) {
hv_store_ent(tails, seqitem, newSViv(1), 0);
}
else {
- SV* val = HeVAL(he);
+ SV* const val = HeVAL(he);
sv_inc(val);
}
}
}
}
+ /* Initialize retval to build the return value in */
+ retval = newAV();
+ av_push(retval, newSVpvn(stashname, stashname_len)); /* us first */
+
/* This loop won't terminate until we either finish building
the MRO, or get an exception. */
while(1) {
- SV* seqhead = NULL;
SV* cand = NULL;
SV* winner = NULL;
- SV* val;
- HE* tail_entry;
- AV* seq;
int s;
/* "foreach $seq (@seqs)" */
- SV** avptr = AvARRAY(seqs);
+ SV** const avptr = AvARRAY(seqs);
for(s = 0; s <= AvFILLp(seqs); s++) {
SV** svp;
- seq = (AV*)(avptr[s]);
+ AV * const seq = (AV*)(avptr[s]);
+ SV* seqhead;
if(!seq) continue; /* skip empty seqs */
svp = av_fetch(seq, heads[s], 0);
seqhead = *svp; /* seqhead = head of this seq */
if(!winner) {
+ HE* tail_entry;
+ SV* val;
/* if we haven't found a winner for this round yet,
and this seqhead is not in tails (or the count
for it in tails has dropped to zero), then this
NULL out any seq which is now empty,
and adjust tails for consistency */
- int new_head = ++heads[s];
+ const int new_head = ++heads[s];
if(new_head > AvFILLp(seq)) {
+ SvREFCNT_dec(avptr[s]);
avptr[s] = NULL;
}
else {
+ HE* tail_entry;
+ SV* val;
/* Because we know this new seqhead used to be
a tail, we can assume it is in tails and has
a positive value, which we need to dec */
hierarchy is not C3-incompatible */
if(!winner) {
/* we have to do some cleanup before we croak */
- SV** svp = AvARRAY(seqs);
- items = AvFILLp(seqs) + 1;
- while (items--)
- *svp++ = NULL;
SvREFCNT_dec(retval);
Safefree(heads);
}
}
}
+ else { /* @ISA was undefined or empty */
+ /* build a retval containing only ourselves */
+ retval = newAV();
+ av_push(retval, newSVpvn(stashname, stashname_len));
+ }
/* we don't want anyone modifying the cache entry but us,
and we do so by replacing it completely */
Perl_mro_get_linear_isa(pTHX_ HV *stash)
{
struct mro_meta* meta;
+
assert(stash);
- assert(HvAUX(stash));
+ if(!SvOOK(stash))
+ Perl_croak(aTHX_ "Can't linearize anonymous symbol table");
meta = HvMROMETA(stash);
if(meta->mro_which == MRO_DFS) {
} else {
Perl_croak(aTHX_ "panic: invalid MRO!");
}
+ return NULL; /* NOT REACHED */
}
/*
HE* iter;
SV** svp;
I32 items;
- struct mro_meta* meta;
- char* stashname;
+ bool is_universal;
+ struct mro_meta * meta;
- stashname = HvNAME_get(stash);
+ const char * const stashname = HvNAME_get(stash);
+ const STRLEN stashname_len = HvNAMELEN_get(stash);
+
+ if(!stashname)
+ Perl_croak(aTHX_ "Can't call mro_isa_changed_in() on anonymous symbol table");
/* wipe out the cached linearizations for this stash */
meta = HvMROMETA(stash);
meta->mro_linear_dfs = NULL;
meta->mro_linear_c3 = NULL;
+ /* Inc the package generation, since our @ISA changed */
+ meta->pkg_gen++;
+
/* Wipe the global method cache if this package
is UNIVERSAL or one of its parents */
- if(meta->is_universal)
- PL_sub_generation++;
- /* Wipe the local method cache otherwise */
- else
- meta->sub_generation++;
+ svp = hv_fetch(PL_isarev, stashname, stashname_len, 0);
+ isarev = svp ? (HV*)*svp : NULL;
+
+ if((stashname_len == 9 && strEQ(stashname, "UNIVERSAL"))
+ || (isarev && hv_exists(isarev, "UNIVERSAL", 9))) {
+ PL_sub_generation++;
+ is_universal = TRUE;
+ }
+ else { /* Wipe the local method cache otherwise */
+ meta->cache_gen++;
+ is_universal = FALSE;
+ }
/* wipe next::method cache too */
if(meta->mro_nextmethod) hv_clear(meta->mro_nextmethod);
-
+
/* Iterate the isarev (classes that are our children),
wiping out their linearization and method caches */
- if((isarev = meta->mro_isarev)) {
+ if(isarev) {
hv_iterinit(isarev);
while((iter = hv_iternext(isarev))) {
- SV* revkey = hv_iterkeysv(iter);
+ SV* const revkey = hv_iterkeysv(iter);
HV* revstash = gv_stashsv(revkey, 0);
- struct mro_meta* revmeta = HvMROMETA(revstash);
+ struct mro_meta* revmeta;
+
+ if(!revstash) continue;
+ revmeta = HvMROMETA(revstash);
SvREFCNT_dec((SV*)revmeta->mro_linear_dfs);
SvREFCNT_dec((SV*)revmeta->mro_linear_c3);
revmeta->mro_linear_dfs = NULL;
revmeta->mro_linear_c3 = NULL;
- if(!meta->is_universal)
- revmeta->sub_generation++;
+ if(!is_universal)
+ revmeta->cache_gen++;
if(revmeta->mro_nextmethod)
hv_clear(revmeta->mro_nextmethod);
}
while (items--) {
SV* const sv = *svp++;
- struct mro_meta* mrometa;
HV* mroisarev;
- HV* mrostash = gv_stashsv(sv, 0);
- if(!mrostash) {
- mrostash = gv_stashsv(sv, GV_ADD);
- /*
- We created the package on the fly, so
- that we could store isarev information.
- This flag lets gv_fetchmeth know about it,
- so that it can still generate the very useful
- "Can't locate package Foo for @Bar::ISA" warning.
- */
- HvMROMETA(mrostash)->fake = 1;
+ HE *he = hv_fetch_ent(PL_isarev, sv, 0, 0);
+ if(!he) {
+ he = hv_store_ent(PL_isarev, sv, (SV*)newHV(), 0);
}
-
- mrometa = HvMROMETA(mrostash);
- mroisarev = mrometa->mro_isarev;
-
- /* is_universal is viral */
- if(meta->is_universal)
- mrometa->is_universal = 1;
-
- if(!mroisarev)
- mroisarev = mrometa->mro_isarev = newHV();
+ mroisarev = (HV*)HeVAL(he);
/* This hash only ever contains PL_sv_yes. Storing it over itself is
almost as cheap as calling hv_exists, so on aggregate we expect to
save time by not making two calls to the common HV code for the
case where it doesn't exist. */
- hv_store(mroisarev, stashname, strlen(stashname), &PL_sv_yes, 0);
+ hv_store(mroisarev, stashname, stashname_len, &PL_sv_yes, 0);
if(isarev) {
hv_iterinit(isarev);
while((iter = hv_iternext(isarev))) {
- SV* revkey = hv_iterkeysv(iter);
- hv_store_ent(mroisarev, revkey, &PL_sv_yes, 0);
+ I32 revkeylen;
+ char* const revkey = hv_iterkey(iter, &revkeylen);
+ hv_store(mroisarev, revkey, revkeylen, &PL_sv_yes, 0);
}
}
}
/*
=for apidoc mro_method_changed_in
-Like C<mro_isa_changed_in>, but invalidates method
-caching on any child classes of the given stash, so
-that they might notice the changes in this one.
+Invalidates method caching on any child classes
+of the given stash, so that they might notice
+the changes in this one.
Ideally, all instances of C<PL_sub_generation++> in
-the perl source should be replaced by calls to this.
-Some already are, but some are more difficult to
-replace.
+perl source outside of C<mro.c> should be
+replaced by calls to this.
+
+Perl automatically handles most of the common
+ways a method might be redefined. However, there
+are a few ways you could change a method in a stash
+without the cache code noticing, in which case you
+need to call this method afterwards:
-Perl has always had problems with method caches
-getting out of sync when one directly manipulates
-stashes via things like C<%{Foo::} = %{Bar::}> or
-C<${Foo::}{bar} = ...> or the equivalent. If
-you do this in core or XS code, call this afterwards
-on the destination stash to get things back in sync.
+1) Directly manipulating the stash HV entries from
+XS code.
-If you're doing such a thing from pure perl, use
-C<mro::method_changed_in(classname)>, which
-just calls this.
+2) Assigning a reference to a readonly scalar
+constant into a stash entry in order to create
+a constant subroutine (like constant.pm
+does).
+
+This same method is available from pure perl
+via, C<mro::method_changed_in(classname)>.
=cut
*/
void
Perl_mro_method_changed_in(pTHX_ HV *stash)
{
- struct mro_meta* meta = HvMROMETA(stash);
- HV* isarev;
- HE* iter;
+ const char * const stashname = HvNAME_get(stash);
+ const STRLEN stashname_len = HvNAMELEN_get(stash);
+
+ SV ** const svp = hv_fetch(PL_isarev, stashname, stashname_len, 0);
+ HV * const isarev = svp ? (HV*)*svp : NULL;
+
+ if(!stashname)
+ Perl_croak(aTHX_ "Can't call mro_method_changed_in() on anonymous symbol table");
+
+ /* Inc the package generation, since a local method changed */
+ HvMROMETA(stash)->pkg_gen++;
/* If stash is UNIVERSAL, or one of UNIVERSAL's parents,
invalidate all method caches globally */
- if(meta->is_universal) {
+ if((stashname_len == 9 && strEQ(stashname, "UNIVERSAL"))
+ || (isarev && hv_exists(isarev, "UNIVERSAL", 9))) {
PL_sub_generation++;
return;
}
/* else, invalidate the method caches of all child classes,
but not itself */
- if((isarev = meta->mro_isarev)) {
+ if(isarev) {
+ HE* iter;
+
hv_iterinit(isarev);
while((iter = hv_iternext(isarev))) {
- SV* revkey = hv_iterkeysv(iter);
- HV* revstash = gv_stashsv(revkey, 0);
- struct mro_meta* mrometa = HvMROMETA(revstash);
- mrometa->sub_generation++;
+ SV* const revkey = hv_iterkeysv(iter);
+ HV* const revstash = gv_stashsv(revkey, 0);
+ struct mro_meta* mrometa;
+
+ if(!revstash) continue;
+ mrometa = HvMROMETA(revstash);
+ mrometa->cache_gen++;
if(mrometa->mro_nextmethod)
hv_clear(mrometa->mro_nextmethod);
}
return i;
}
-STATIC SV*
-__nextcan(pTHX_ SV* self, I32 throw_nomethod)
-{
- register I32 cxix;
- register const PERL_CONTEXT *ccstack = cxstack;
- const PERL_SI *top_si = PL_curstackinfo;
- HV* selfstash;
- GV* cvgv;
- SV *stashname;
- const char *fq_subname;
- const char *subname;
- STRLEN fq_subname_len;
- STRLEN stashname_len;
- STRLEN subname_len;
- SV* sv;
- GV** gvp;
- AV* linear_av;
- SV** linear_svp;
- SV* linear_sv;
- HV* curstash;
- GV* candidate = NULL;
- CV* cand_cv = NULL;
- const char *hvname;
- I32 items;
- struct mro_meta* selfmeta;
- HV* nmcache;
- HE* cache_entry;
-
- if(sv_isobject(self))
- selfstash = SvSTASH(SvRV(self));
- else
- selfstash = gv_stashsv(self, 0);
-
- assert(selfstash);
-
- hvname = HvNAME_get(selfstash);
- if (!hvname)
- Perl_croak(aTHX_ "Can't use anonymous symbol table for method lookup");
-
- cxix = __dopoptosub_at(cxstack, cxstack_ix);
-
- /* This block finds the contextually-enclosing fully-qualified subname,
- much like looking at (caller($i))[3] until you find a real sub that
- isn't ANON, etc */
- for (;;) {
- /* we may be in a higher stacklevel, so dig down deeper */
- while (cxix < 0) {
- if(top_si->si_type == PERLSI_MAIN)
- Perl_croak(aTHX_ "next::method/next::can/maybe::next::method must be used in method context");
- top_si = top_si->si_prev;
- ccstack = top_si->si_cxstack;
- cxix = __dopoptosub_at(ccstack, top_si->si_cxix);
- }
-
- if(CxTYPE((PERL_CONTEXT*)(&ccstack[cxix])) != CXt_SUB
- || (PL_DBsub && GvCV(PL_DBsub) && ccstack[cxix].blk_sub.cv == GvCV(PL_DBsub))) {
- cxix = __dopoptosub_at(ccstack, cxix - 1);
- continue;
- }
-
- {
- const I32 dbcxix = __dopoptosub_at(ccstack, cxix - 1);
- if (PL_DBsub && GvCV(PL_DBsub) && dbcxix >= 0 && ccstack[dbcxix].blk_sub.cv == GvCV(PL_DBsub)) {
- if(CxTYPE((PERL_CONTEXT*)(&ccstack[dbcxix])) != CXt_SUB) {
- cxix = dbcxix;
- continue;
- }
- }
- }
-
- cvgv = CvGV(ccstack[cxix].blk_sub.cv);
-
- if(!isGV(cvgv)) {
- cxix = __dopoptosub_at(ccstack, cxix - 1);
- continue;
- }
-
- /* we found a real sub here */
- sv = sv_2mortal(newSV(0));
-
- gv_efullname3(sv, cvgv, NULL);
-
- fq_subname = SvPVX(sv);
- fq_subname_len = SvCUR(sv);
-
- subname = strrchr(fq_subname, ':');
- if(!subname)
- Perl_croak(aTHX_ "next::method/next::can/maybe::next::method cannot find enclosing method");
-
- subname++;
- subname_len = fq_subname_len - (subname - fq_subname);
- if(subname_len == 8 && strEQ(subname, "__ANON__")) {
- cxix = __dopoptosub_at(ccstack, cxix - 1);
- continue;
- }
- break;
- }
-
- /* If we made it to here, we found our context */
-
- /* Initialize the next::method cache for this stash
- if necessary */
- selfmeta = HvMROMETA(selfstash);
- if(!(nmcache = selfmeta->mro_nextmethod)) {
- nmcache = selfmeta->mro_nextmethod = newHV();
- }
-
- /* Use the cached coderef if it exists */
- else if((cache_entry = hv_fetch_ent(nmcache, sv, 0, 0))) {
- SV* val = HeVAL(cache_entry);
- if(val == &PL_sv_undef) {
- if(throw_nomethod)
- Perl_croak(aTHX_ "No next::method '%s' found for %s", subname, hvname);
- }
- return val;
- }
-
- /* beyond here is just for cache misses, so perf isn't as critical */
-
- stashname_len = subname - fq_subname - 2;
- stashname = sv_2mortal(newSVpvn(fq_subname, stashname_len));
-
- linear_av = mro_get_linear_isa_c3(selfstash, 0); /* has ourselves at the top of the list */
-
- linear_svp = AvARRAY(linear_av);
- items = AvFILLp(linear_av) + 1;
-
- /* Walk down our MRO, skipping everything up
- to the contextually enclosing class */
- while (items--) {
- linear_sv = *linear_svp++;
- assert(linear_sv);
- if(sv_eq(linear_sv, stashname))
- break;
- }
-
- /* Now search the remainder of the MRO for the
- same method name as the contextually enclosing
- method */
- if(items > 0) {
- while (items--) {
- linear_sv = *linear_svp++;
- assert(linear_sv);
- curstash = gv_stashsv(linear_sv, FALSE);
-
- if (!curstash || (HvMROMETA(curstash)->fake && !HvFILL(curstash))) {
- if (ckWARN(WARN_SYNTAX))
- Perl_warner(aTHX_ packWARN(WARN_SYNTAX), "Can't locate package %"SVf" for @%s::ISA",
- (void*)linear_sv, hvname);
- continue;
- }
-
- assert(curstash);
-
- gvp = (GV**)hv_fetch(curstash, subname, subname_len, 0);
- if (!gvp) continue;
-
- candidate = *gvp;
- assert(candidate);
-
- if (SvTYPE(candidate) != SVt_PVGV)
- gv_init(candidate, curstash, subname, subname_len, TRUE);
-
- /* Notably, we only look for real entries, not method cache
- entries, because in C3 the method cache of a parent is not
- valid for the child */
- if (SvTYPE(candidate) == SVt_PVGV && (cand_cv = GvCV(candidate)) && !GvCVGEN(candidate)) {
- SvREFCNT_inc_simple_void_NN((SV*)cand_cv);
- hv_store_ent(nmcache, newSVsv(sv), (SV*)cand_cv, 0);
- return (SV*)cand_cv;
- }
- }
- }
-
- hv_store_ent(nmcache, newSVsv(sv), &PL_sv_undef, 0);
- if(throw_nomethod)
- Perl_croak(aTHX_ "No next::method '%s' found for %s", subname, hvname);
- return &PL_sv_undef;
-}
-
#include "XSUB.h"
XS(XS_mro_get_linear_isa);
XS(XS_mro_get_mro);
XS(XS_mro_get_isarev);
XS(XS_mro_is_universal);
-XS(XS_mro_get_global_sub_gen);
XS(XS_mro_invalidate_method_caches);
-XS(XS_mro_get_sub_generation);
XS(XS_mro_method_changed_in);
-XS(XS_next_can);
-XS(XS_next_method);
-XS(XS_maybe_next_method);
+XS(XS_mro_get_pkg_gen);
+XS(XS_mro_nextcan);
void
Perl_boot_core_mro(pTHX)
newXSproto("mro::get_mro", XS_mro_get_mro, file, "$");
newXSproto("mro::get_isarev", XS_mro_get_isarev, file, "$");
newXSproto("mro::is_universal", XS_mro_is_universal, file, "$");
- newXSproto("mro::get_global_sub_generation", XS_mro_get_global_sub_gen, file, "");
newXSproto("mro::invalidate_all_method_caches", XS_mro_invalidate_method_caches, file, "");
- newXSproto("mro::get_sub_generation", XS_mro_get_sub_generation, file, "$");
newXSproto("mro::method_changed_in", XS_mro_method_changed_in, file, "$");
- newXS("next::can", XS_next_can, file);
- newXS("next::method", XS_next_method, file);
- newXS("maybe::next::method", XS_maybe_next_method, file);
+ newXSproto("mro::get_pkg_gen", XS_mro_get_pkg_gen, file, "$");
+ newXS("mro::_nextcan", XS_mro_nextcan, file);
}
XS(XS_mro_get_linear_isa) {
classname = ST(0);
class_stash = gv_stashsv(classname, 0);
- if(!class_stash) Perl_croak(aTHX_ "No such class: '%"SVf"'!", SVfARG(classname));
- if(items > 1) {
- char* which = SvPV_nolen(ST(1));
+ if(!class_stash) {
+ /* No stash exists yet, give them just the classname */
+ AV* isalin = newAV();
+ av_push(isalin, newSVsv(classname));
+ ST(0) = sv_2mortal(newRV_noinc((SV*)isalin));
+ XSRETURN(1);
+ }
+ else if(items > 1) {
+ const char* const which = SvPV_nolen(ST(1));
if(strEQ(which, "dfs"))
RETVAL = mro_get_linear_isa_dfs(class_stash, 0);
else if(strEQ(which, "c3"))
meta->mro_which = which;
/* Only affects local method cache, not
even child classes */
- meta->sub_generation++;
+ meta->cache_gen++;
if(meta->mro_nextmethod)
hv_clear(meta->mro_nextmethod);
}
dXSARGS;
SV* classname;
HV* class_stash;
- struct mro_meta* meta;
PERL_UNUSED_ARG(cv);
classname = ST(0);
class_stash = gv_stashsv(classname, 0);
- if(!class_stash) Perl_croak(aTHX_ "No such class: '%"SVf"'!", SVfARG(classname));
- meta = HvMROMETA(class_stash);
- if(meta->mro_which == MRO_DFS)
+ if(!class_stash || HvMROMETA(class_stash)->mro_which == MRO_DFS)
ST(0) = sv_2mortal(newSVpvn("dfs", 3));
else
ST(0) = sv_2mortal(newSVpvn("c3", 2));
dVAR;
dXSARGS;
SV* classname;
- HV* class_stash;
+ SV** svp;
HV* isarev;
+ char* classname_pv;
+ STRLEN classname_len;
+ AV* ret_array;
PERL_UNUSED_ARG(cv);
classname = ST(0);
- class_stash = gv_stashsv(classname, 0);
- if(!class_stash) Perl_croak(aTHX_ "No such class: '%"SVf"'!", SVfARG(classname));
-
SP -= items;
-
- if((isarev = HvMROMETA(class_stash)->mro_isarev)) {
+
+
+ classname_pv = SvPV_nolen(classname);
+ classname_len = strlen(classname_pv);
+ svp = hv_fetch(PL_isarev, classname_pv, classname_len, 0);
+ isarev = svp ? (HV*)*svp : NULL;
+
+ ret_array = newAV();
+ if(isarev) {
HE* iter;
hv_iterinit(isarev);
while((iter = hv_iternext(isarev)))
- XPUSHs(hv_iterkeysv(iter));
+ av_push(ret_array, newSVsv(hv_iterkeysv(iter)));
}
+ XPUSHs(sv_2mortal(newRV_noinc((SV*)ret_array)));
PUTBACK;
return;
dVAR;
dXSARGS;
SV* classname;
- HV* class_stash;
+ HV* isarev;
+ char* classname_pv;
+ STRLEN classname_len;
+ SV** svp;
PERL_UNUSED_ARG(cv);
if (items != 1)
- Perl_croak(aTHX_ "Usage: mro::get_mro(classname)");
+ Perl_croak(aTHX_ "Usage: mro::is_universal(classname)");
classname = ST(0);
- class_stash = gv_stashsv(classname, 0);
- if(!class_stash) Perl_croak(aTHX_ "No such class: '%"SVf"'!", SVfARG(classname));
- if (HvMROMETA(class_stash)->is_universal)
+ classname_pv = SvPV_nolen(classname);
+ classname_len = strlen(classname_pv);
+
+ svp = hv_fetch(PL_isarev, classname_pv, classname_len, 0);
+ isarev = svp ? (HV*)*svp : NULL;
+
+ if((classname_len == 9 && strEQ(classname_pv, "UNIVERSAL"))
+ || (isarev && hv_exists(isarev, "UNIVERSAL", 9)))
XSRETURN_YES;
else
XSRETURN_NO;
}
-XS(XS_mro_get_global_sub_gen)
-{
- dVAR;
- dXSARGS;
-
- PERL_UNUSED_ARG(cv);
-
- if (items != 0)
- Perl_croak(aTHX_ "Usage: mro::get_global_sub_generation()");
-
- ST(0) = sv_2mortal(newSViv(PL_sub_generation));
- XSRETURN(1);
-}
-
XS(XS_mro_invalidate_method_caches)
{
dVAR;
XSRETURN_EMPTY;
}
-XS(XS_mro_get_sub_generation)
+XS(XS_mro_method_changed_in)
{
dVAR;
dXSARGS;
PERL_UNUSED_ARG(cv);
if(items != 1)
- Perl_croak(aTHX_ "Usage: mro::get_sub_generation(classname)");
-
+ Perl_croak(aTHX_ "Usage: mro::method_changed_in(classname)");
+
classname = ST(0);
+
class_stash = gv_stashsv(classname, 0);
if(!class_stash) Perl_croak(aTHX_ "No such class: '%"SVf"'!", SVfARG(classname));
- ST(0) = sv_2mortal(newSViv(HvMROMETA(class_stash)->sub_generation));
- XSRETURN(1);
+ mro_method_changed_in(class_stash);
+
+ XSRETURN_EMPTY;
}
-XS(XS_mro_method_changed_in)
+XS(XS_mro_get_pkg_gen)
{
dVAR;
dXSARGS;
PERL_UNUSED_ARG(cv);
if(items != 1)
- Perl_croak(aTHX_ "Usage: mro::method_changed_in(classname)");
+ Perl_croak(aTHX_ "Usage: mro::get_pkg_gen(classname)");
classname = ST(0);
class_stash = gv_stashsv(classname, 0);
- if(!class_stash) Perl_croak(aTHX_ "No such class: '%"SVf"'!", SVfARG(classname));
- mro_method_changed_in(class_stash);
+ SP -= items;
- XSRETURN_EMPTY;
+ XPUSHs(sv_2mortal(newSViv(
+ class_stash ? HvMROMETA(class_stash)->pkg_gen : 0
+ )));
+
+ PUTBACK;
+ return;
}
-XS(XS_next_can)
+XS(XS_mro_nextcan)
{
dVAR;
dXSARGS;
SV* self = ST(0);
- SV* methcv = __nextcan(aTHX_ self, 0);
+ const I32 throw_nomethod = SvIVX(ST(1));
+ register I32 cxix = cxstack_ix;
+ register const PERL_CONTEXT *ccstack = cxstack;
+ const PERL_SI *top_si = PL_curstackinfo;
+ HV* selfstash;
+ SV *stashname;
+ const char *fq_subname;
+ const char *subname;
+ STRLEN stashname_len;
+ STRLEN subname_len;
+ SV* sv;
+ GV** gvp;
+ AV* linear_av;
+ SV** linear_svp;
+ const char *hvname;
+ I32 entries;
+ struct mro_meta* selfmeta;
+ HV* nmcache;
+ I32 i;
PERL_UNUSED_ARG(cv);
- PERL_UNUSED_VAR(items);
- if(methcv == &PL_sv_undef) {
- ST(0) = &PL_sv_undef;
+ SP -= items;
+
+ if(sv_isobject(self))
+ selfstash = SvSTASH(SvRV(self));
+ else
+ selfstash = gv_stashsv(self, 0);
+
+ assert(selfstash);
+
+ hvname = HvNAME_get(selfstash);
+ if (!hvname)
+ Perl_croak(aTHX_ "Can't use anonymous symbol table for method lookup");
+
+ /* This block finds the contextually-enclosing fully-qualified subname,
+ much like looking at (caller($i))[3] until you find a real sub that
+ isn't ANON, etc (also skips over pureperl next::method, etc) */
+ for(i = 0; i < 2; i++) {
+ cxix = __dopoptosub_at(ccstack, cxix);
+ for (;;) {
+ GV* cvgv;
+ STRLEN fq_subname_len;
+
+ /* we may be in a higher stacklevel, so dig down deeper */
+ while (cxix < 0) {
+ if(top_si->si_type == PERLSI_MAIN)
+ Perl_croak(aTHX_ "next::method/next::can/maybe::next::method must be used in method context");
+ top_si = top_si->si_prev;
+ ccstack = top_si->si_cxstack;
+ cxix = __dopoptosub_at(ccstack, top_si->si_cxix);
+ }
+
+ if(CxTYPE((PERL_CONTEXT*)(&ccstack[cxix])) != CXt_SUB
+ || (PL_DBsub && GvCV(PL_DBsub) && ccstack[cxix].blk_sub.cv == GvCV(PL_DBsub))) {
+ cxix = __dopoptosub_at(ccstack, cxix - 1);
+ continue;
+ }
+
+ {
+ const I32 dbcxix = __dopoptosub_at(ccstack, cxix - 1);
+ if (PL_DBsub && GvCV(PL_DBsub) && dbcxix >= 0 && ccstack[dbcxix].blk_sub.cv == GvCV(PL_DBsub)) {
+ if(CxTYPE((PERL_CONTEXT*)(&ccstack[dbcxix])) != CXt_SUB) {
+ cxix = dbcxix;
+ continue;
+ }
+ }
+ }
+
+ cvgv = CvGV(ccstack[cxix].blk_sub.cv);
+
+ if(!isGV(cvgv)) {
+ cxix = __dopoptosub_at(ccstack, cxix - 1);
+ continue;
+ }
+
+ /* we found a real sub here */
+ sv = sv_2mortal(newSV(0));
+
+ gv_efullname3(sv, cvgv, NULL);
+
+ fq_subname = SvPVX(sv);
+ fq_subname_len = SvCUR(sv);
+
+ subname = strrchr(fq_subname, ':');
+ if(!subname)
+ Perl_croak(aTHX_ "next::method/next::can/maybe::next::method cannot find enclosing method");
+
+ subname++;
+ subname_len = fq_subname_len - (subname - fq_subname);
+ if(subname_len == 8 && strEQ(subname, "__ANON__")) {
+ cxix = __dopoptosub_at(ccstack, cxix - 1);
+ continue;
+ }
+ break;
+ }
+ cxix--;
}
- else {
- ST(0) = sv_2mortal(newRV_inc(methcv));
+
+ /* If we made it to here, we found our context */
+
+ /* Initialize the next::method cache for this stash
+ if necessary */
+ selfmeta = HvMROMETA(selfstash);
+ if(!(nmcache = selfmeta->mro_nextmethod)) {
+ nmcache = selfmeta->mro_nextmethod = newHV();
+ }
+ else { /* Use the cached coderef if it exists */
+ HE* cache_entry = hv_fetch_ent(nmcache, sv, 0, 0);
+ if (cache_entry) {
+ SV* const val = HeVAL(cache_entry);
+ if(val == &PL_sv_undef) {
+ if(throw_nomethod)
+ Perl_croak(aTHX_ "No next::method '%s' found for %s", subname, hvname);
+ XSRETURN_EMPTY;
+ }
+ XPUSHs(sv_2mortal(newRV_inc(val)));
+ XSRETURN(1);
+ }
}
- XSRETURN(1);
-}
+ /* beyond here is just for cache misses, so perf isn't as critical */
-XS(XS_next_method)
-{
- dMARK;
- dAX;
- SV* self = ST(0);
- SV* methcv = __nextcan(aTHX_ self, 1);
+ stashname_len = subname - fq_subname - 2;
+ stashname = sv_2mortal(newSVpvn(fq_subname, stashname_len));
- PERL_UNUSED_ARG(cv);
+ linear_av = mro_get_linear_isa_c3(selfstash, 0); /* has ourselves at the top of the list */
- PL_markstack_ptr++;
- call_sv(methcv, GIMME_V);
-}
+ linear_svp = AvARRAY(linear_av);
+ entries = AvFILLp(linear_av) + 1;
-XS(XS_maybe_next_method)
-{
- dMARK;
- dAX;
- SV* self = ST(0);
- SV* methcv = __nextcan(aTHX_ self, 0);
+ /* Walk down our MRO, skipping everything up
+ to the contextually enclosing class */
+ while (entries--) {
+ SV * const linear_sv = *linear_svp++;
+ assert(linear_sv);
+ if(sv_eq(linear_sv, stashname))
+ break;
+ }
- PERL_UNUSED_ARG(cv);
+ /* Now search the remainder of the MRO for the
+ same method name as the contextually enclosing
+ method */
+ if(entries > 0) {
+ while (entries--) {
+ SV * const linear_sv = *linear_svp++;
+ HV* curstash;
+ GV* candidate;
+ CV* cand_cv;
- if(methcv == &PL_sv_undef) {
- ST(0) = &PL_sv_undef;
- XSRETURN(1);
+ assert(linear_sv);
+ curstash = gv_stashsv(linear_sv, FALSE);
+
+ if (!curstash) {
+ if (ckWARN(WARN_SYNTAX))
+ Perl_warner(aTHX_ packWARN(WARN_SYNTAX), "Can't locate package %"SVf" for @%s::ISA",
+ (void*)linear_sv, hvname);
+ continue;
+ }
+
+ assert(curstash);
+
+ gvp = (GV**)hv_fetch(curstash, subname, subname_len, 0);
+ if (!gvp) continue;
+
+ candidate = *gvp;
+ assert(candidate);
+
+ if (SvTYPE(candidate) != SVt_PVGV)
+ gv_init(candidate, curstash, subname, subname_len, TRUE);
+
+ /* Notably, we only look for real entries, not method cache
+ entries, because in C3 the method cache of a parent is not
+ valid for the child */
+ if (SvTYPE(candidate) == SVt_PVGV && (cand_cv = GvCV(candidate)) && !GvCVGEN(candidate)) {
+ SvREFCNT_inc_simple_void_NN((SV*)cand_cv);
+ hv_store_ent(nmcache, newSVsv(sv), (SV*)cand_cv, 0);
+ XPUSHs(sv_2mortal(newRV_inc((SV*)cand_cv)));
+ XSRETURN(1);
+ }
+ }
}
- PL_markstack_ptr++;
- call_sv(methcv, GIMME_V);
+ hv_store_ent(nmcache, newSVsv(sv), &PL_sv_undef, 0);
+ if(throw_nomethod)
+ Perl_croak(aTHX_ "No next::method '%s' found for %s", subname, hvname);
+ XSRETURN_EMPTY;
}
/*