Bug 60534

Summary: perl sort function broken
Product: [Retired] Red Hat Linux Reporter: Dax Kelson <dkelson>
Component: perlAssignee: Chip Turner <cturner>
Status: CLOSED RAWHIDE QA Contact: David Lawrence <dkl>
Severity: high Docs Contact:
Priority: medium    
Version: 7.2   
Target Milestone: ---   
Target Release: ---   
Hardware: All   
OS: Linux   
URL: http://www.gurulabs.com/files/perl-testcase.txt
Whiteboard:
Fixed In Version: Doc Type: Bug Fix
Doc Text:
Story Points: ---
Clone Of: Environment:
Last Closed: 2002-03-01 08:25:35 UTC Type: ---
Regression: --- Mount Type: ---
Documentation: --- CRM:
Verified Versions: Category: ---
oVirt Team: --- RHEL 7.3 requirements from Atomic Host:
Cloudforms Team: --- Target Upstream Version:
Embargoed:

Description Dax Kelson 2002-03-01 07:57:33 UTC
From Bugzilla Helper:
User-Agent: Mozilla/5.0 (X11; U; Linux i686; en-US; rv:0.9.2.1) Gecko/20010901

Description of problem:
When using a subroutine with sort, the $a and $b are set incorrectly. I wrote a
small test case, it is broken in perl-5.6.0-17 (what ships with RH7.2), it works
fine with 5.6.1 and 5.004_05.

Here are the $a and $b sent to the subroutine:

comparing kim-1st August 1975 22:20 WITH dax-4th April 1975 14:10
comparing kim-1st August 1975 22:20 WITH dax-4th April 1975 14:10
comparing kim-1st August 1975 22:20 WITH dax-4th April 1975 14:10
comparing kim-1st August 1975 22:20 WITH dax-4th April 1975 14:10
comparing kim-1st August 1975 22:20 WITH dax-4th April 1975 14:10
comparing kim-1st August 1975 22:20 WITH dax-4th April 1975 14:10
comparing kim-1st August 1975 22:20 WITH dax-4th April 1975 14:10
comparing kim-1st August 1975 22:20 WITH dax-4th April 1975 14:10
comparing kim-1st August 1975 22:20 WITH dax-4th April 1975 14:10
comparing kim-1st August 1975 22:20 WITH dax-4th April 1975 14:10
comparing kim-1st August 1975 22:20 WITH dax-4th April 1975 14:10
comparing kim-1st August 1975 22:20 WITH dax-4th April 1975 14:10
comparing kim-1st August 1975 22:20 WITH dax-4th April 1975 14:10
comparing kim-1st August 1975 22:20 WITH dax-4th April 1975 14:10
comparing kim-1st August 1975 22:20 WITH dax-4th April 1975 14:10

It should be this (Perl 5.6.1 on a NetBSD box):

comparing kim-1st August 1975 22:20 WITH dax-4th April 1975 14:10
comparing bryan-8th January 1972 17:30 WITH dax-4th April 1975 14:10
comparing rick-28th May 1978 17:30 WITH bryan-8th January 1972 17:30
comparing rick-28th May 1978 17:30 WITH dax-4th April 1975 14:10
comparing rick-28th May 1978 17:30 WITH kim-1st August 1975 22:20
comparing sarah-8th January 1972 12:30 WITH bryan-8th January 1972 17:30
comparing heather-28th May 1978 17:30 WITH sarah-8th January 1972 12:30
comparing heather-28th May 1978 17:30 WITH bryan-8th January 1972 17:30
comparing heather-28th May 1978 17:30 WITH dax-4th April 1975 14:10
comparing heather-28th May 1978 17:30 WITH kim-1st August 1975 22:20
comparing heather-28th May 1978 17:30 WITH rick-28th May 1978 17:30

Version-Release number of selected component (if applicable):


How reproducible:
Always

Steps to Reproduce:
1.Try test case
2.broken with perl that comes with RHL 7.2
3.Works with perl 5.6.1 and 5.004_05	

Actual Results:  broken on RH7.2 works on 5.6.1 on other platforms

Expected Results:  same output as 5.6.1 on other platforms

Additional info:

sort is pretty fundamental. I consider this a major problem. I stopped by #perl
and got agreement from the "perl gods".

I wasted 8 hours of work today thinking there was a problem with my code.

Here is the test case:

#########################
#!/usr/bin/perl

use Date::Manip;

@list = ('dax-4th April 1975 14:10', 'kim-1st August 1975 22:20', 'bryan-8th
January 1972 17:30', 'rick-28th May 1978 17:30', 'sarah-8th January 1972 12:30',
'heather-28th May 1978 17:30');

@sorted = sort by_birthdate @list;

sub by_birthdate {
        my($retval,$datea,$dateb,$str1,$str2,$flag);
        (undef,$str1) = split(/-/,$a);
        (undef,$str2) = split(/-/,$b);
        print "comparing $a WITH $b\n"; # this is the first hint of prob
        if ($str1 eq $str2) {
                return(0);
        }
        $datea=&ParseDate($str1);
        $dateb=&ParseDate($str2);
        $flag=&Date_Cmp($datea,$dateb);
        if ($flag<0) {
                return(1);
        } else {
                return(-1);
        }
}


##########################

Comment 1 Dax Kelson 2002-03-01 08:25:30 UTC
I checked and it is broken on the perl shipped with RHL 7.1 too.

Comment 2 Chip Turner 2002-03-01 12:30:14 UTC
You're seeing the result of a bugfix in perl 5.6.1.  In earlier versions, the
sort() function was not fully reentrant.  This meant if your comparison function
used sort, it could confuse perl, resulting in bad sorting, core dumps, or other
various behavior.  Unfortunately, Date::Manip's ParseDate ends up using sort for
some kinds of input, thus triggering the bug you see.

The two options are upgrade to Perl 5.6.1 (not yet available for Red Hat 7.2,
though available in an unsupported version if you would like to test it) or to
work around the problem.  Below is a modified version of your script that works
in 5.6.0 and 5.6.1.  The key here is to do the ParseDate outside of your sorting
function, thus avoiding the problem.  The technique is called a Schwartzian
Transform, and is a bit convoluted, but the idea is to basically pre-compute
ParseDate for each element of the list to be sorted, then sort THAT list.

#!/usr/bin/perl

use Date::Manip;

@list = ('dax-4th April 1975 14:10', 'kim-1st August 1975 22:20',
         'bryan-8th January 1972 17:30', 'rick-28th May 1978 17:30',
         'sarah-8th January 1972 12:30', 'heather-28th May 1978 17:30');

@sorted = map { $_->[0] }
            sort by_birthdate
              map { [ $_, ParseDate((split(/-/, $_, 2))[1]) ] }
                @list;
print "Sorted:\n";
print "\t$_\n" foreach @sorted;

sub by_birthdate {
        my($retval,$datea,$dateb,$str1,$str2,$flag);
        # this is the first hint of prob
        print "comparing $a->[0] WITH $b->[0]\n";
        $flag=&Date_Cmp($a->[1],$b->[1]);
        if ($flag<0) {
                return(1);
        } elsif ($flag > 0) {
                return(-1);
        }
        else {
                return 0;
        }
}