Skip to content

Commit 2b30192

Browse files
committed
pp_sort.c: fix fencepost error in call to av_extend()
In [rt.cpan.org #39196] issue #17496 there is a report that Tie::File produced spurious blank lines in the file after @tied= sort @tied; it turns out that this is because Tie::File treats EXTEND similarly to STORESIZE (which is arguably not entirely correct, but also not that weird) coupled with an off by one error in the calls to av_extend() in pp_sort. This patch fixes the fencepost error, adds some comments to av_extend() to make it clear what it is doing, and adds a test that EXTEND is called by this code with correct argument.
1 parent 3eb35b0 commit 2b30192

File tree

3 files changed

+40
-6
lines changed

3 files changed

+40
-6
lines changed

av.c

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,8 +55,13 @@ Perl_av_reify(pTHX_ AV *av)
5555
/*
5656
=for apidoc av_extend
5757
58-
Pre-extend an array. The C<key> is the index to which the array should be
59-
extended.
58+
Pre-extend an array so that it is capable of storing values at indexes
59+
C<0..key>. Thus C<av_extend(av,99)> guarantees that the array can store 100
60+
elements, i.e. that C<av_store(av, 0, sv)> through C<av_store(av, 99, sv)>
61+
on a plain array will work without any further memory allocation.
62+
63+
If the av argument is a tied array then will call the C<EXTEND> tied
64+
array method with an argument of C<(key+1)>.
6065
6166
=cut
6267
*/
@@ -72,6 +77,15 @@ Perl_av_extend(pTHX_ AV *av, SSize_t key)
7277
mg = SvTIED_mg((const SV *)av, PERL_MAGIC_tied);
7378
if (mg) {
7479
SV *arg1 = sv_newmortal();
80+
/* NOTE: the API for av_extend() is NOT the same as the tie method EXTEND.
81+
*
82+
* The C function takes an *index* (assumes 0 indexed arrays) and ensures
83+
* that the array is at least as large as the index provided.
84+
*
85+
* The tied array method EXTEND takes a *count* and ensures that the array
86+
* is at least that many elements large. Thus we have to +1 the key when
87+
* we call the tied method.
88+
*/
7589
sv_setiv(arg1, (IV)(key + 1));
7690
Perl_magic_methcall(aTHX_ MUTABLE_SV(av), mg, SV_CONST(EXTEND), G_DISCARD, 1,
7791
arg1);

pp_sort.c

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1067,7 +1067,8 @@ PP(pp_sort)
10671067
for (i = 0; i < max; i++)
10681068
base[i] = newSVsv(base[i]);
10691069
av_clear(av);
1070-
av_extend(av, max);
1070+
if (max)
1071+
av_extend(av, max-1);
10711072
for (i=0; i < max; i++) {
10721073
SV * const sv = base[i];
10731074
SV ** const didstore = av_store(av, i, sv);
@@ -1094,7 +1095,7 @@ PP(pp_sort)
10941095
}
10951096
av_clear(av);
10961097
if (max > 0) {
1097-
av_extend(av, max);
1098+
av_extend(av, max-1);
10981099
Copy(base, AvARRAY(av), max, SV*);
10991100
}
11001101
AvFILLp(av) = max - 1;

t/op/sort.t

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ BEGIN {
77
set_up_inc('../lib');
88
}
99
use warnings;
10-
plan(tests => 199);
10+
plan(tests => 203);
11+
use Tie::Array; # we need to test sorting tied arrays
1112

1213
# these shouldn't hang
1314
{
@@ -433,7 +434,6 @@ cmp_ok($x,'eq','123',q(optimized-away comparison block doesn't take any other ar
433434
@a = qw(b c a); $r1 = \$a[1]; @a = sort mysort @a; $r2 = \$a[0];
434435
is "$$r1-$$r2-@a", "c-c-c b a", "inplace sort with function of lexical";
435436

436-
use Tie::Array;
437437
my @t;
438438
tie @t, 'Tie::StdArray';
439439

@@ -494,6 +494,25 @@ cmp_ok($x,'eq','123',q(optimized-away comparison block doesn't take any other ar
494494
is ("@a", "3 4 5", "RT #128340");
495495

496496
}
497+
{
498+
@Tied_Array_EXTEND_Test::ISA= 'Tie::StdArray';
499+
my $extend_count;
500+
sub Tied_Array_EXTEND_Test::EXTEND {
501+
$extend_count= $_[1];
502+
return;
503+
}
504+
my @t;
505+
tie @t, "Tied_Array_EXTEND_Test";
506+
is($extend_count, undef, "test that EXTEND has not been called prior to initialization");
507+
$t[0]=3;
508+
$t[1]=1;
509+
$t[2]=2;
510+
is($extend_count, undef, "test that EXTEND has not been called during initialization");
511+
@t= sort @t;
512+
is($extend_count, 3, "test that EXTEND was called with an argument of 3 by pp_sort()");
513+
is("@t","1 2 3","test that sorting the tied array worked even though EXTEND is a no-op");
514+
}
515+
497516

498517
# Test optimisations of reversed sorts. As we now guarantee stability by
499518
# default, # optimisations which do not provide this are bogus.

0 commit comments

Comments
 (0)