Beefy Boxes and Bandwidth Generously Provided by pair Networks
Think about Loose Coupling

comment on

( #3333=superdoc: print w/replies, xml ) Need Help??
I've just found an extremely obscure bug in perl. This is the tale of that bug - where it came from, and how it was found. I hope you find it entertaining.


Yesterday, blakem posted a question about while loops. A while loop in Perl looks like this:
or like this:

The body of the loop (the STATEMENT or BLOCK) is repeatedly executed as long as the condition is true. In Perl, a scalar is false if it is equal, as a string, to either "" or "0". Otherwise it's true. (The undefined value undef has a string value of "", so undef is false.)

But this being Perl, that's not the end of it. Overloading causes one complication, but that's a story for another day. When you're processing a text file, you will often use a loop like this:

while (my $line = <FILE>) { # Do something with the $line }

The problem might come if the file has a line which is blank, or just "0". Ordinarily that wouldn't matter, because the line would be terminated with "\n". So the value of $line would be "\n" or "0\n", which are both true. But what if it's the last line of the file, and it isn't terminated with a newline? Or what if you used the -l command-line switch to perl, which tells it to remove the line terminator automatically? We have to think about these things. The loop condition will then be false, and the loop will terminate before the file's been completely read.

Or will it? Actually, no. Perl is one step ahead, as usual. If Perl sees a loop like the one above, then it will secretly transform it into:

while (defined(my $line = <FILE>)) { # Do something with the $line }
and all is well. Your program works the way that you intended, and peace reigns in the Monastery. You can see this transformation by using the B::Deparse module:

$ perl -MO=Deparse -e 'while(my $line=<FILE>) { print; }' while (defined(my $line = <FILE>)) { print $_; } -e syntax OK

There is one other kind of loop which gets the same magic treatment, and that's if you use the glob operator. The glob operator returns the names of all the files which match a particular wildcard pattern: for example, you can print the names of all the Perl modules in the current directory like this:

while (my $module = glob("*.pm")) { print "Found a module: $module\n"; }
Now, you might have a file called "0", which would cause the same problem as before. So Perl uses the magic defined test here as well.

The old bug

What blakem noticed yesterday is that this magic wasn't working properly in a while loop of the second kind (STATEMENT while CONDITION;). You can test it easily enough: run this code:
my $ok=0; $ok=1 while my $zero = glob("0"); print $ok ? "ok\n" : "not ok\n"
I looked into the source code for perl, and I found a mistake in the code which was testing for that kind of condition. (The code was wrongly checking for a NULL opcode rather than a GLOB opcode.) I wrote up the correction as a patch, and sent it to the perl development mailing list, perl5-porters.

Hugo, one of the porters, replied to my message saying

Interesting - is there any situation in which that test could have succeeded, and so tested definedness when it should have tested truth?
This is starting to get confusing... Let me try and sum up the story up to this point:
  1. In some situations, perl magically puts a defined(...) test around the condition in a while loop.
  2. But the magic wasn't always working properly.
  3. The reason it wasn't working is that there was a mistake in the perl source code...
  4. ... meaning that a different condition was being checked for. (Instead of checking for a GLOB opcode, it was checking for a NULL opcode)
What Hugo asked was whether that condition could ever arise. The question surprised me: I hadn't even considered the possibility of it. So I thought a bit, and poked around a bit, and I realised that the answer was "yes". It can happen!

I won't go too deep into the details, because I don't want this post to turn into a book on the internals of perl. But I'll tell you what I found.

The new bug

I found that the broken condition is triggered by an assignment statement which has a logical operator on its right-hand side. For example,
$foo = $bar || $baz

If a statement like that is used as the condition of a while loop, then perl will (wrongly) insert a defined(...) test. For example,

my $x = 1; die("Oh dear!") while my $foo = $x && 0;
will be silently turned into
my $x = 1; die("Oh dear!") while defined(my $foo = $x && 0);
and so it will die, even though the condition seems to be false!

I wonder if this is the most obscure bug found in perl to date.

Edit Masem 2001-12-18 - Fixed amp; entity to real & in last code blocks.

In reply to A most obscure bug by robin

Use:  <p> text here (a paragraph) </p>
and:  <code> code here </code>
to format your post; it's "PerlMonks-approved HTML":

  • Are you posting in the right place? Check out Where do I post X? to know for sure.
  • Posts may use any of the Perl Monks Approved HTML tags. Currently these include the following:
    <code> <a> <b> <big> <blockquote> <br /> <dd> <dl> <dt> <em> <font> <h1> <h2> <h3> <h4> <h5> <h6> <hr /> <i> <li> <nbsp> <ol> <p> <small> <strike> <strong> <sub> <sup> <table> <td> <th> <tr> <tt> <u> <ul>
  • Snippets of code should be wrapped in <code> tags not <pre> tags. In fact, <pre> tags should generally be avoided. If they must be used, extreme care should be taken to ensure that their contents do not have long lines (<70 chars), in order to prevent horizontal scrolling (and possible janitor intervention).
  • Want more info? How to link or or How to display code and escape characters are good places to start.
Log In?

What's my password?
Create A New User
Domain Nodelet?
and the web crawler heard nothing...

How do I use this? | Other CB clients
Other Users?
Others studying the Monastery: (1)
As of 2021-07-31 12:46 GMT
Find Nodes?
    Voting Booth?

    No recent polls found