?

Log in

No account? Create an account
Fun bug - brad's life [entries|archive|friends|userinfo]
Brad Fitzpatrick

[ website | bradfitz.com ]
[ userinfo | livejournal userinfo ]
[ archive | journal archive ]

Fun bug [Oct. 30th, 2005|12:35 am]
Brad Fitzpatrick
[Tags|, ]

Here's a fun bug. See if you can find what's happening:
my @data = function();
my @sorted = sort { $a->method <=> $b->method } @data;

foreach my $item (@sorted) {
    $item->other_method;
}

The error message I was getting:

Can't call method "other_method" on undefined value at line ...

The question to you is: how is that possible?

I think you'll smile if/when you realize the answer.
LinkReply

Comments:
[User Picture]From: taral
2005-10-30 07:40 am (UTC)
I got nothing. I'm inclined to say this gives perl +1 evil.
(Reply) (Thread)
[User Picture]From: brad
2005-10-30 07:43 am (UTC)
Naah, nothing evil here. Perl's not doing any more or less than it "should" ... it's taking the only path possible.
(Reply) (Parent) (Thread)
[User Picture]From: taral
2005-10-30 05:38 pm (UTC)
The only path possible is function() == undef?
(Reply) (Parent) (Thread)
[User Picture]From: jope
2005-10-30 07:47 am (UTC)
Huh. Unless the list is small, or the function overhead is negligible, seems to me that the result of method() for each @data object really should be cached, just as an easy optimization.

That is, of course, unless the result of method() for a given object changes from one invocation to the next, in which case, you've got bigger problems.
(Reply) (Thread)
[User Picture]From: brad
2005-10-30 07:53 am (UTC)
Unless the list is small, or the function overhead is negligible, seems to me that the result of method() for each @data object really should be cached, just as an easy optimization.

Yup, that's called the
Schwarzian Transform, a well-known Perl trick.

But that's not this bug.

And method doesn't mutate the object.

Search harder. :-)

Why am I getting that error message and not "can't call method 'method'"?
(Reply) (Parent) (Thread)
[User Picture]From: brad
2005-10-30 08:00 am (UTC)
(Probably a bad link for that. I just did what Google returned first.)
(Reply) (Parent) (Thread)
From: plix
2005-10-30 08:11 am (UTC)
My best guess is that the problem results from <=> returning undef when one of the operands is NaN.
(Reply) (Parent) (Thread)
[User Picture]From: brad
2005-10-30 08:17 am (UTC)
Perl doesn't have NaN, just undef. (Maybe it does in Math::* somewhere, but not here.)

But in any case, that's not it.
(Reply) (Parent) (Thread)
From: plix
2005-10-30 08:24 am (UTC)
Math::BigInt, but checking the docs to make sure of that I noticed that perl would have just fatal-ed on sort due to an undefined result.
(Reply) (Parent) (Thread)
From: plix
2005-10-30 08:32 am (UTC)
My only other guess is that it has to do with the scoping foreach performs because of my.
(Reply) (Parent) (Thread)
[User Picture]From: jope
2005-10-30 08:39 am (UTC)
I didn't mean that lack of caching was the bug, though yes, it is a candidate for Scwartzian transform.

And I didn't mean that the object itself needed to be mutated by the method, only that the method itself may return a different result on subsequent calls. I have no idea how sort would behave in such case. Just nonuseful ordering, one would hope.

Looking at the answer in subsequent comment though: D'oh! My mistake in assuming function() gave a sane list to begin with, simply because that was the first line quoted.
(Reply) (Parent) (Thread)
From: edge_walker
2005-10-30 01:05 pm (UTC)

perldoc -f sort says

The comparison function is required to behave. If it returns inconsistent results (sometimes saying $x[1] is less than $x[2] and sometimes saying the opposite, for example) the results are not well-defined.

which translates to “here be segfaults” with recent perls.

(Reply) (Parent) (Thread)
[User Picture]From: markpasc
2005-10-30 08:01 am (UTC)
I opened the comments hoping to find the answer, and was disappointed... then I realized what it might be, and tested it out.

Hehe!
(Reply) (Thread)
[User Picture]From: brad
2005-10-30 08:05 am (UTC)
Hop on AIM.
(Reply) (Parent) (Thread)
[User Picture]From: taral
2005-10-30 06:52 pm (UTC)
You have AIM?
(Reply) (Parent) (Thread)
From: billemon
2005-10-30 08:31 am (UTC)
The list only has one item, and it's (undef).
method never gets called, because there's only one item in the list.
*boom*?
(Reply) (Thread)
[User Picture]From: brad
2005-10-30 08:32 am (UTC)
Yup. :-)
(Reply) (Parent) (Thread)
From: billemon
2005-10-30 08:37 am (UTC)
Was hoping you'd leave it screened, I was gonna hang and see how many comments you got saying "huh?" :)
(Reply) (Parent) (Thread)
[User Picture]From: brad
2005-10-30 08:41 am (UTC)
Rescreened!
(Reply) (Parent) (Thread)
[User Picture]From: moonwick
2005-10-30 09:15 am (UTC)
Ooooh, took me a few minutes.

Is function() returning a single value, undef, leading to the comparison code never being called by sort?
(Reply) (Thread)
[User Picture]From: brad
2005-10-30 09:25 am (UTC)
Yup. :-)
(Reply) (Parent) (Thread)
[User Picture]From: gaal
2005-10-30 10:07 am (UTC)
Got me stumped for a bit :-)

function() returned a list of one element, undef.

A similar problem could happen if it returned a listref (or hashref or whatever): that means a one-element list in @data, which likewise doesn't get ->method invoked on it; then later in the foreach you die with 'Can't call method "other_method" on unblessed reference'.
(Reply) (Thread)
[User Picture]From: brentdax
2005-10-30 03:28 pm (UTC)
...I never knew Perl did that...
(Reply) (Thread)
[User Picture]From: xb95
2005-10-30 08:16 pm (UTC)
Haven't read the other answers yet, but after thinking on it for a few minutes...

1. function() returns undef on error (rather than empty list)
2. @data = (undef)
3. the sort is optimized out (list of length 1)
4. @sorted = (undef)
5. undef->other_method = boom

Now I go to read other comments?
(Reply) (Thread)
[User Picture]From: idonotlikepeas
2005-10-31 03:20 pm (UTC)
Hmm. Without looking at the other comments...

It's possible you're getting a single undef value out of function(), which would obviate the need for the other sort to begin with, and it would be optimized away. You can get similar results if you run sort without putting the results anywhere.
(Reply) (Thread)