goodgulf has asked for the wisdom of the Perl Monks concerning the following question:

It is my job to maintain perl code. I tracked down this interesting "bug". Can someone explain what's wrong with the outdented line?
Code: print "Before fubar\n" . Dumper(@g::TaskList); $Body .= "$_<br>" while(<LOG>); print "After fubar\n" . Dumper(@g::TaskList); Output: Before fubar $VAR1 = 'Localize'; $VAR2 = 'ErrorCheck'; After fubar $VAR1 = undef; $VAR2 = 'ErrorCheck';
Note that the following replacement code (explicit to the max) works without incident. Why are the two not identical? Programming Perl/3e, pg. 81 claims that the first line should work. So, what gives?:
Working (identical?) version: while (my $line = <LOG>) { $Body .= "${line}<br>"; }
Also, a check of the memory locations of $Body and @g::TaskList showed them to be "miles" apart, and not altered by the code, in case you were curious. Thanks muchly for any insight.

Replies are listed 'Best First'.
Re: Side Effects
by Tanktalus (Canon) on Apr 07, 2005 at 00:00 UTC

    Most likely, somehow, $g::TaskList[0] is an alias to $_ (although I'm not entirely sure how that happened). And while(<LOG>) does not localise $_, meaning that you've just clobbered $_ through the while. Try putting your body into:

    { local $_; $Body .= "$_<br>" while (<LOG>); }
    I'm betting the problem goes away here.

      The preferred way to do this is local *_; (which of course also affects @_, &_, etc.). local $_; can have problems if what $_ is aliased to is magic. In 5.10, the preferred way will be my $_;.
Re: Side Effects
by ikegami (Patriarch) on Apr 07, 2005 at 04:18 UTC

    Unlike foreach loops, while does not localize $_. In your code, $_ must be an alias to $g::TaskList[0], like $array[0] is in the following code:

    @array = ('a'); foreach (@array) { # $_ is an alias for $array[0]. while (<DATA>) { } # $_ and $array[0] are clobbered print("[$_]$/"); # Prints "[]" } print("[$array[0]]$/"); # Prints "[]"

    The fix is to localize $_:

    @array = ('a'); foreach (@array) { # $_ is an alias for $array[0]. local $_ = $_; # This $_ is not an alias for $array[0]. while (<DATA>) { } # $_ is clobbered, but not $array[0] print("[$_]$/"); # Prints "[]" } print("[$array[0]]$/"); # Prints "[a]"

    If you want to preserve $_ within the loop, use:

    @array = ('a'); foreach (@array) { # $_ is an alias for $array[0]. { local $_; # This $_ is not an alias for $array[0]. while (<DATA>) { } } # Neither $_ nor $array[0] are clobbered print("[$_]$/"); # Prints "[a]" } print("[$array[0]]$/"); # Prints "[a]"

    or:

    @array = ('a'); foreach (@array) { # $_ is an alias for $array[0]; while (my $line = <DATA>) { } # Neither $_ nor $array[0] are clobbered print("[$_]$/"); # Prints "[a]" } print("[$array[0]]$/"); # Prints "[a]"
      Actually, $g::TaskList is not used, named, or referred to in the entire module that the above code appears in (though it is global). (It is used by an upstream sub that calls down into this subroutine though, but wouldn't $_ at least be local to the subroutine?) Even if $_ somehow became an alias to a global variable that was not used in the module in question, how would using $_ as an r-value undef said global?
      $Body .= $_;
      How does that undef $_, or anything it might be aliasing to? Sorry if I'm not getting it (I understand the idea that making it local will fix it, but I'd like to understand what makes this not work in the first place, because I just cannot figure out how $_ became an alias to TaskList). Thanks for the continued attention. :)
        It doesn't, it's the while(<LOG>) that clears it. while(<LOG>) is short for while ($_ = <LOG>). When the end of the file is reached, <LOG> returns undef, undefining $_. My snippets demonstrate this behaviour.
        OK, here's what I'm thinking.
        $Body .= $_ while <LOG>;
        is equivalent to
        while ($_ = <LOG>) { $Body .= $_; }
        Therefore, $_ is both an L-value and an R-value. If we assume for a moment that $_ is somehow an alias for $g::TaskList, then it should be correct that for each iteration of the while loop, $_ (and thus $g::TaskList) will get assigned the next line from <LOG>. At the end of the while loop, what would happen to $_? Would it get undef'd? Would this undef any aliases? So, let's assume all this is true... Here is my code path:
        In Localize.pm: (SNIP) foreach my $lang (@g::LanguageList) { $g::Language = $lang; unless (isExcludedBuild($g::Product, $g::Architecture, $g::Flavor) + || $g::Architecture =~ /ia64/i || $g::Language =~ /usa/i) { (SNIP) &BuildBlah(); } } (SNIP) In Utilities.pm: sub BuildBlah { my $FlavorVariation = shift; (snip) if(FileExists("Blah")) { SendMail($g::MailPurpose{"BuildFailed"}, "Blah", $FlavorVariat +ion); } (snip) } In Reporting.pm: sub SendMail { my $Purpose = shift; my @OptionalParameters = @_; my $Body = ""; (snip) $Body = "<html> <body background=nothing bgproperties=\"fixed\">"; if($Purpose eq $g::MailPurpose{BuildNominated}) { (snip) } elsif($Purpose eq $g::MailPurpose{BuildFailed}) { (snip) open(LOG, "$LogDir\\$g::BuildErrFile"); while (my $line = <LOG>) { $Body .= "${line}<br>"; } close LOG; }
        Although I've snipped a lot of code, this follows the execution path, and you can see that $g::TaskList is never mentioned going way way back up through several loops, modules and subroutine calls. Do I need to backtrack further? I would think the foreach above would wipe out any alias of $_ to TaskList that might have been set higher up in the stack trace.
Re: Side Effects
by dragonchild (Archbishop) on Apr 07, 2005 at 00:18 UTC
    Another random, and very unlikely, possibility is that some sick twisted soul has $Body as a tied variable to something whose STORE does nasty things to $g::TaskList[0]. Unlikely, but possible.
Re: Side Effects
by tlm (Prior) on Apr 06, 2005 at 23:53 UTC

    I don't see the problem immediately, but I can tell you that the working version is far from identical to the original. In this the working version the loop variable holding the last read input line is a lexical scalar, while in the buggy version it is a global.

    Update: I still can't figure it out; I need to see more code, preferably something I can run and reproduce the error with. Also, I fixed the wording of my original reply.

    the lowliest monk