Yesterday I was doing a little exploratory surgery on some code when something completely unrelated caught my eye and further exploration uncovered a bug in perl. The essence of the bug is that Perl's local() function doesn't properly localize the attached magic that holds the pos() information, or rather, it fails to restore the magic properly (it resets it instead) after the scope of the local() call.
A simple test case is:$_ = 'n';while(/./g){{local $_}} which gives an infinite loop in 5.00503 and 5.6.0 (and according to chipmunk, in a recent development release as well).
The following 2 snippets bring the problem and its cause to light -- in the first snippet we can see the effect of the bug if we remove
use Devel::Peek; $_ = 'blah'; while(/./g){ Dump $_; foo(); Dump $_; last; } sub foo { local $_ = 'foo'; pos($_) = 2; Dump $_; } __END__ output is: SV = PVMG(0x8102ff8) at 0x80e72ac REFCNT = 1 FLAGS = (SMG,POK,pPOK) IV = 0 NV = 0 PV = 0x80f3400 "blah"\0 CUR = 4 LEN = 5 MAGIC = 0x80f3660 MG_VIRTUAL = &PL_vtbl_mglob MG_TYPE = 'g' MG_LEN = 1 SV = PVMG(0x8103018) at 0x80e7300 REFCNT = 2 FLAGS = (SMG,POK,pPOK) IV = 0 NV = 0 PV = 0x80f2008 "foo"\0 CUR = 3 LEN = 4 MAGIC = 0x80f3660 MG_VIRTUAL = &PL_vtbl_mglob MG_TYPE = 'g' MG_LEN = 2 SV = PVMG(0x8102ff8) at 0x80e72ac REFCNT = 1 FLAGS = (SMG,POK,pPOK) IV = 0 NV = 0 PV = 0x80f3400 "blah"\0 CUR = 4 LEN = 5 MAGIC = 0x80f3660 MG_VIRTUAL = &PL_vtbl_mglob MG_TYPE = 'g' MG_LEN = -1
Above we see that the first Dump() shows that the current pos() (which is indicated by MG_LEN) is 1 (which is where we are in the string at this point). The second Dump() shows that we now have a new SV (the address differs), though the 'magic' address remains the same -- we manually set pos() to 2 here and it shows up fine in the dump. The third Dump() shows the problem, upon restoring $_, the 'magic' is reset to -1 rather than restored to what it was prior to the local() call in foo(). The second snippet utilizes perl's implicit localization:
use Devel::Peek; $_ = 'blah'; while(/./g){ Dump $_; for ('foo'){ # implicit localization of $_ pos($_) = 2; Dump $_; } Dump $_; last; } __END__ output: SV = PVMG(0x8102ff8) at 0x80e72ac REFCNT = 1 FLAGS = (SMG,POK,pPOK) IV = 0 NV = 0 PV = 0x80f4d78 "blah"\0 CUR = 4 LEN = 5 MAGIC = 0x80f35d0 MG_VIRTUAL = &PL_vtbl_mglob MG_TYPE = 'g' MG_LEN = 1 SV = PVMG(0x8103018) at 0x80f90f0 REFCNT = 3 FLAGS = (SMG,POK,READONLY,pPOK) IV = 0 NV = 0 PV = 0x80f38e8 "foo"\0 CUR = 3 LEN = 4 MAGIC = 0x80f1cb0 MG_VIRTUAL = &PL_vtbl_mglob MG_TYPE = 'g' MG_LEN = 2 SV = PVMG(0x8102ff8) at 0x80e72ac REFCNT = 1 FLAGS = (SMG,POK,pPOK) IV = 0 NV = 0 PV = 0x80f4d78 "blah"\0 CUR = 4 LEN = 5 MAGIC = 0x80f35d0 MG_VIRTUAL = &PL_vtbl_mglob MG_TYPE = 'g' MG_LEN = 1
Here we note that in the second Dump() we not only get a new SV but new attached magic address as well. And after the inner loop, the final Dump() shows that we revert to the original $_ SV and its current attached magic is intact (and thus the loops finishes even without the 'last', which I only kept here to minimize the output). Note, in 5.00502 this snippet also fails to restore MG_LEN, but it was fixed in 5.00503.
That pretty much sums up the bug issue. I posted it here so that others could double check the findings and someone active on p5p could submit a bug report (leaving a note here so p5p isn't flooded with a dozen similar reports). I also thought an example of using Devel::Peek might have some value for the monastery.
Something else I'll point out, merely for the curious (like myself) is that local() produces a new SV. I guess it makes sense now that I think about it, but I hadn't really given it any thought before because I don't find myself taking references to localized globals. The perlsub page has this to say about how local saves away the current value:
This operator works by saving the current values of those variables in its argument list on a hidden stack and restoring them upon exiting the block,
I guess mentally I just thought it saved away the data, not the SV. But we can see from the above that it saves away the whole SV and points the localized variable to a new SV in its place, meaning that if we take a reference to a localized global, our reference points to something other than the package global when we leave the local scope:
$main::var = 12; { local $main::var = 10; $main::bar = \$main::var; } $$main::bar = 42; # doesn't affect $main::var print $main::var; # prints: 12
Now, $bar does *not* hold a reference to $main::var, merely to the SV that stood in its place under the local() call. I just thought I'd point this out because it 'looks' like $bar is a reference to the global $var. On the other hand, if you alias $var with a typeglob, the alias mirrors the real variable in and out of local() scopes.
In reply to bug: local() fails to restore pos() magic by danger
| For: | Use: | ||
| & | & | ||
| < | < | ||
| > | > | ||
| [ | [ | ||
| ] | ] |