This is a live mirror of the Perl 5 development currently hosted at https://github.com/perl/perl5
In Perl_pad_new(), allocate a 2 element array for padlist.
[perl5.git] / mro.c
diff --git a/mro.c b/mro.c
index 7506994..1aac225 100644 (file)
--- a/mro.c
+++ b/mro.c
@@ -441,22 +441,24 @@ Takes the necessary steps (cache invalidations, mostly)
 when the @ISA of the given package has changed.  Invoked
 by the C<setisa> magic, should not need to invoke directly.
 
-=for apidoc mro_isa_changed_in3
-
-Takes the necessary steps (cache invalidations, mostly)
-when the @ISA of the given package has changed.  Invoked
-by the C<setisa> magic, should not need to invoke directly.
-
-The stash can be passed as the first argument, or its name and length as
-the second and third (or both). If just the name is passed and the stash
-does not exist, then only the subclasses' method and isa caches will be
-invalidated.
-
 =cut
 */
+
+/* Macro to avoid repeating the code five times. */
+#define CLEAR_LINEAR(mEta)                                     \
+    if (mEta->mro_linear_all) {                                 \
+       SvREFCNT_dec(MUTABLE_SV(mEta->mro_linear_all));          \
+       mEta->mro_linear_all = NULL;                              \
+       /* This is just acting as a shortcut pointer.  */          \
+       mEta->mro_linear_current = NULL;                            \
+    } else if (mEta->mro_linear_current) {                           \
+       /* Only the current MRO is stored, so this owns the data.  */ \
+       SvREFCNT_dec(mEta->mro_linear_current);                        \
+       mEta->mro_linear_current = NULL;                                \
+    }
+
 void
-Perl_mro_isa_changed_in3(pTHX_ HV* stash, const char *stashname,
-                         STRLEN stashname_len)
+Perl_mro_isa_changed_in(pTHX_ HV* stash)
 {
     dVAR;
     HV* isarev;
@@ -465,42 +467,30 @@ Perl_mro_isa_changed_in3(pTHX_ HV* stash, const char *stashname,
     SV** svp;
     I32 items;
     bool is_universal;
-    struct mro_meta * meta = NULL;
+    struct mro_meta * meta;
     HV *isa = NULL;
 
-    if(!stashname && stash) {
-        stashname = HvENAME_get(stash);
-        stashname_len = HvENAMELEN_get(stash);
-    }
-    else if(!stash)
-        stash = gv_stashpvn(stashname, stashname_len, 0 /* don't add */);
+    const char * const stashname = HvENAME_get(stash);
+    const STRLEN stashname_len = HvENAMELEN_get(stash);
+
+    PERL_ARGS_ASSERT_MRO_ISA_CHANGED_IN;
 
     if(!stashname)
         Perl_croak(aTHX_ "Can't call mro_isa_changed_in() on anonymous symbol table");
 
-    if(stash) {
-      /* wipe out the cached linearizations for this stash */
-      meta = HvMROMETA(stash);
-      if (meta->mro_linear_all) {
-       SvREFCNT_dec(MUTABLE_SV(meta->mro_linear_all));
-       meta->mro_linear_all = NULL;
-       /* This is just acting as a shortcut pointer.  */
-       meta->mro_linear_current = NULL;
-      } else if (meta->mro_linear_current) {
-       /* Only the current MRO is stored, so this owns the data.  */
-       SvREFCNT_dec(meta->mro_linear_current);
-       meta->mro_linear_current = NULL;
-      }
-      if (meta->isa) {
+
+    /* wipe out the cached linearizations for this stash */
+    meta = HvMROMETA(stash);
+    CLEAR_LINEAR(meta);
+    if (meta->isa) {
        /* Steal it for our own purposes. */
        isa = (HV *)sv_2mortal((SV *)meta->isa);
        meta->isa = NULL;
-      }
-
-      /* Inc the package generation, since our @ISA changed */
-      meta->pkg_gen++;
     }
 
+    /* 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 */
 
@@ -513,12 +503,12 @@ Perl_mro_isa_changed_in3(pTHX_ HV* stash, const char *stashname,
         is_universal = TRUE;
     }
     else { /* Wipe the local method cache otherwise */
-        if(meta) meta->cache_gen++;
+        meta->cache_gen++;
        is_universal = FALSE;
     }
 
     /* wipe next::method cache too */
-    if(meta && meta->mro_nextmethod) hv_clear(meta->mro_nextmethod);
+    if(meta->mro_nextmethod) hv_clear(meta->mro_nextmethod);
 
     /* Iterate the isarev (classes that are our children),
        wiping out their linearization, method and isa caches
@@ -548,16 +538,7 @@ Perl_mro_isa_changed_in3(pTHX_ HV* stash, const char *stashname,
 
             if(!revstash) continue;
             revmeta = HvMROMETA(revstash);
-           if (revmeta->mro_linear_all) {
-               SvREFCNT_dec(MUTABLE_SV(revmeta->mro_linear_all));
-               revmeta->mro_linear_all = NULL;
-               /* This is just acting as a shortcut pointer.  */
-               revmeta->mro_linear_current = NULL;
-           } else if (revmeta->mro_linear_current) {
-               /* Only the current MRO is stored, so this owns the data.  */
-               SvREFCNT_dec(revmeta->mro_linear_current);
-               revmeta->mro_linear_current = NULL;
-           }
+           CLEAR_LINEAR(revmeta);
             if(!is_universal)
                 revmeta->cache_gen++;
             if(revmeta->mro_nextmethod)
@@ -585,11 +566,6 @@ Perl_mro_isa_changed_in3(pTHX_ HV* stash, const char *stashname,
                 HV * const isa = (HV *)HeVAL(iter);
                 const HEK *namehek;
 
-                /* Re-calculate the linearisation, unless a previous iter-
-                   ation was for a subclass of this class. */
-                if(!HvMROMETA(revstash)->isa)
-                    (void)mro_get_linear_isa(revstash);
-
                 /* We're starting at the 2nd element, skipping revstash */
                 linear_mro = mro_get_linear_isa(revstash);
                 svp = AvARRAY(linear_mro) + 1;
@@ -634,14 +610,10 @@ Perl_mro_isa_changed_in3(pTHX_ HV* stash, const char *stashname,
         }
     }
 
-    /* Now iterate our MRO (parents), and:
-         1) Add ourselves and everything from our isarev to their isarev
-         2) Delete the parent’s entry from the (now temporary) isa hash
+    /* Now iterate our MRO (parents), adding ourselves and everything from
+       our isarev to their isarev.
     */
 
-    /* This only applies if the stash exists. */
-    if(!stash) goto clean_up_isarev;
-
     /* We're starting at the 2nd element, skipping ourselves here */
     linear_mro = mro_get_linear_isa(stash);
     svp = AvARRAY(linear_mro) + 1;
@@ -669,7 +641,6 @@ Perl_mro_isa_changed_in3(pTHX_ HV* stash, const char *stashname,
        (void)hv_store(mroisarev, stashname, stashname_len, &PL_sv_yes, 0);
     }
 
-   clean_up_isarev:
     /* Delete our name from our former parents’ isarevs. */
     if(isa && HvARRAY(isa))
         mro_clean_isarev(isa, stashname, stashname_len, meta->isa);
@@ -773,47 +744,44 @@ Perl_mro_package_moved(pTHX_ HV * const stash, HV * const oldstash,
        as neither B nor B::B can be updated before the other, since they
        will reset caches on foo, which will see either B or B::B with the
        wrong name. The names must be set on *all* affected stashes before
-       we do anything else.
+       we do anything else. (And linearisations must be cleared, too.)
      */
     stashes = (HV *) sv_2mortal((SV *)newHV());
-    mro_gather_and_rename(stashes, stash, oldstash, newname, newname_len);
-
-    /* Iterate through the stashes, wiping isa linearisations, but leaving
-       the isa hash (which mro_isa_changed_in needs for adjusting the
-       isarev hashes belonging to parent classes). */
-    hv_iterinit(stashes);
-    while((iter = hv_iternext(stashes))) {
-       if(HeVAL(iter) != &PL_sv_yes && HvENAME(HeVAL(iter))) {
-           struct mro_meta* meta;
-           meta = HvMROMETA((HV *)HeVAL(iter));
-           if (meta->mro_linear_all) {
-               SvREFCNT_dec(MUTABLE_SV(meta->mro_linear_all));
-               meta->mro_linear_all = NULL;
-               /* This is just acting as a shortcut pointer.  */
-               meta->mro_linear_current = NULL;
-           } else if (meta->mro_linear_current) {
-               /* Only the current MRO is stored, so this owns the data.  */
-               SvREFCNT_dec(meta->mro_linear_current);
-               meta->mro_linear_current = NULL;
-           }
-        }
-    }
+    mro_gather_and_rename(
+     stashes, (HV *) sv_2mortal((SV *)newHV()),
+     stash, oldstash, newname, newname_len
+    );
 
     /* Once the caches have been wiped on all the classes, call
        mro_isa_changed_in on each. */
     hv_iterinit(stashes);
     while((iter = hv_iternext(stashes))) {
-       if(HeVAL(iter) != &PL_sv_yes && HvENAME(HeVAL(iter)))
-           mro_isa_changed_in((HV *)HeVAL(iter));
-       /* We are not holding a refcount, so eliminate the pointer before
-        * stashes is freed. */
-       HeVAL(iter) = NULL;
+       HV * const stash = *(HV **)HEK_KEY(HeKEY_hek(iter));
+       if(HvENAME(stash)) {
+           /* We have to restore the original meta->isa (that
+              mro_gather_and_rename set aside for us) this way, in case
+              one class in this list is a superclass of a another class
+              that we have already encountered. In such a case, meta->isa
+              will have been overwritten without old entries being deleted 
+              from PL_isarev. */
+           struct mro_meta * const meta = HvMROMETA(stash);
+           if(meta->isa != (HV *)HeVAL(iter)){
+               SvREFCNT_dec(meta->isa);
+               meta->isa
+                = HeVAL(iter) == &PL_sv_yes
+                   ? NULL
+                   : (HV *)HeVAL(iter);
+               HeVAL(iter) = NULL; /* We donated our reference count. */
+           }
+           mro_isa_changed_in(stash);
+       }
     }
 }
 
 void
-S_mro_gather_and_rename(pTHX_ HV * const stashes, HV *stash, HV *oldstash,
-                              const char *name, I32 namlen)
+S_mro_gather_and_rename(pTHX_ HV * const stashes, HV * const seen_stashes,
+                              HV *stash, HV *oldstash, const char *name,
+                              I32 namlen)
 {
     register XPVHV* xhv;
     register HE *entry;
@@ -825,19 +793,48 @@ S_mro_gather_and_rename(pTHX_ HV * const stashes, HV *stash, HV *oldstash,
 
     PERL_ARGS_ASSERT_MRO_GATHER_AND_RENAME;
 
+    /* We use the seen_stashes hash to keep track of which packages have
+       been encountered so far. This must be separate from the main list of
+       stashes, as we need to distinguish between stashes being assigned
+       and stashes being replaced/deleted. (A nested stash can be on both
+       sides of an assignment. We cannot simply skip iterating through a
+       stash on the right if we have seen it on the left, as it will not
+       get its ename assigned to it.)
+
+       To avoid allocating extra SVs, instead of a bitfield we can make
+       bizarre use of immortals:
+
+        &PL_sv_undef:  seen on the left  (oldstash)
+        &PL_sv_no   :  seen on the right (stash)
+        &PL_sv_yes  :  seen on both sides
+
+     */
+
     if(oldstash) {
        /* Add to the big list. */
+       struct mro_meta * meta;
        HE * const entry
         = (HE *)
             hv_common(
-             stashes, NULL, (const char *)&oldstash, sizeof(HV *), 0,
+             seen_stashes, NULL, (const char *)&oldstash, sizeof(HV *), 0,
              HV_FETCH_LVALUE|HV_FETCH_EMPTY_HE, NULL, 0
             );
-       if(HeVAL(entry) == (SV *)oldstash) {
+       if(HeVAL(entry) == &PL_sv_undef || HeVAL(entry) == &PL_sv_yes) {
            oldstash = NULL;
            goto check_stash;
        }
-       HeVAL(entry) = (SV *)oldstash;
+       HeVAL(entry)
+        = HeVAL(entry) == &PL_sv_no ? &PL_sv_yes : &PL_sv_undef;
+       meta = HvMROMETA(oldstash);
+       (void)
+         hv_store(
+          stashes, (const char *)&oldstash, sizeof(HV *),
+          meta->isa
+           ? SvREFCNT_inc_simple_NN((SV *)meta->isa)
+           : &PL_sv_yes,
+          0
+         );
+       CLEAR_LINEAR(meta);
 
        /* Update the effective name. */
        if(HvENAME_get(oldstash)) {
@@ -855,7 +852,6 @@ S_mro_gather_and_rename(pTHX_ HV * const stashes, HV *stash, HV *oldstash,
          * from PL_isarev, since we still need it. hv_delete mortifies it
          * for us, so sv_2mortal is not necessary. */
          if(HvENAME_HEK(oldstash) != enamehek) {
-           const struct mro_meta * meta = HvMROMETA(oldstash);
            if(meta->isa && HvARRAY(meta->isa))
                mro_clean_isarev(meta->isa, name, namlen, NULL);
            isarev = (HV *)hv_delete(PL_isarev, name, namlen, 0);
@@ -866,11 +862,8 @@ S_mro_gather_and_rename(pTHX_ HV * const stashes, HV *stash, HV *oldstash,
     if(stash) {
        hv_ename_add(stash, name, namlen);
 
-       /* Add it to the big list. We use the stash itself as the value if
-       * it needs mro_isa_changed_in called on it. Otherwise we just use
-       * &PL_sv_yes to indicate that we have seen it. */
-
-       /* The stash needs mro_isa_changed_in called on it if it was
+       /* Add it to the big list if it needs
+       * mro_isa_changed_in called on it. That happens if it was
        * detached from the symbol table (so it had no HvENAME) before
        * being assigned to the spot named by the ‘name’ variable, because
        * its cached isa linerisation is now stale (the effective name
@@ -886,12 +879,28 @@ S_mro_gather_and_rename(pTHX_ HV * const stashes, HV *stash, HV *oldstash,
        entry
         = (HE *)
             hv_common(
-             stashes, NULL, (const char *)&stash, sizeof(HV *), 0,
+             seen_stashes, NULL, (const char *)&stash, sizeof(HV *), 0,
              HV_FETCH_LVALUE|HV_FETCH_EMPTY_HE, NULL, 0
             );
-       if(HeVAL(entry) == &PL_sv_yes || HeVAL(entry) == (SV *)stash)
+       if(HeVAL(entry) == &PL_sv_yes || HeVAL(entry) == &PL_sv_no)
            stash = NULL;
-       else HeVAL(entry) = stash_had_name ? &PL_sv_yes : (SV *)stash;
+       else {
+           HeVAL(entry)
+            = HeVAL(entry) == &PL_sv_undef ? &PL_sv_yes : &PL_sv_no;
+           if(!stash_had_name)
+           {
+               struct mro_meta * const meta = HvMROMETA(stash);
+               (void)
+                 hv_store(
+                  stashes, (const char *)&stash, sizeof(HV *),
+                  meta->isa
+                   ? SvREFCNT_inc_simple_NN((SV *)meta->isa)
+                   : &PL_sv_yes,
+                  0
+                 );
+               CLEAR_LINEAR(meta);
+           }
+       }
     }
 
     if(!stash && !oldstash)
@@ -912,16 +921,19 @@ S_mro_gather_and_rename(pTHX_ HV * const stashes, HV *stash, HV *oldstash,
            I32 len;
            const char* const revkey = hv_iterkey(iter, &len);
            HV* revstash = gv_stashpvn(revkey, len, 0);
+           struct mro_meta * meta;
 
            if(!revstash) continue;
-           entry
-            = (HE *)
-                hv_common(
-                 stashes, NULL, (const char *)&revstash, sizeof(HV *), 0,
-                 HV_FETCH_LVALUE|HV_FETCH_EMPTY_HE, NULL, 0
-                );
-           HeVAL(entry) = (SV *)revstash;
-           
+           meta = HvMROMETA(revstash);
+           (void)
+             hv_store(
+              stashes, (const char *)&revstash, sizeof(HV *),
+              meta->isa
+               ? SvREFCNT_inc_simple_NN((SV *)meta->isa)
+               : &PL_sv_yes,
+              0
+             );
+           CLEAR_LINEAR(meta);
         }
     }
 
@@ -982,7 +994,8 @@ S_mro_gather_and_rename(pTHX_ HV * const stashes, HV *stash, HV *oldstash,
                            sv_catpvn(namesv, key, len-2);
                            name = SvPV_const(namesv, namlen);
                            mro_gather_and_rename(
-                            stashes, substash, oldsubstash, name, namlen
+                            stashes, seen_stashes,
+                            substash, oldsubstash, name, namlen
                            );
                        }
                    }
@@ -996,9 +1009,10 @@ S_mro_gather_and_rename(pTHX_ HV * const stashes, HV *stash, HV *oldstash,
     /* Skip the entire loop if the hash is empty.   */
     if (stash && HvUSEDKEYS(stash)) {
        xhv = (XPVHV*)SvANY(stash);
+       riter = -1;
 
        /* Iterate through the new stash, skipping $seen{$key} items,
-          calling mro_gather_and_rename(stashes, entry, NULL, ...). */
+          calling mro_gather_and_rename(stashes,seen,entry,NULL, ...). */
        while (++riter <= (I32)xhv->xhv_max) {
            entry = (HvARRAY(stash))[riter];
 
@@ -1039,7 +1053,8 @@ S_mro_gather_and_rename(pTHX_ HV * const stashes, HV *stash, HV *oldstash,
                        sv_catpvn(namesv, key, len-2);
                        subname = SvPV_const(namesv, subnamlen);
                        mro_gather_and_rename(
-                         stashes, substash, NULL, subname, subnamlen
+                         stashes, seen_stashes,
+                         substash, NULL, subname, subnamlen
                        );
                    }
                }