# see if the count increases.
BEGIN {
- chdir 't';
+ chdir 't' if -d 't';
@INC = '../lib';
require './test.pl';
use Config;
-plan tests => 50;
+plan tests => 129;
# run some code N times. If the number of SVs at the end of loop N is
# greater than (N-1)*delta at the end of loop 1, we've got a leak
cmp_ok($sv1-$sv0, '<=', ($n-1)*$delta, @rest);
}
-# Like leak, but run a string eval instead; takes into account existing
-# string eval leaks under -Dmad. The code is used instead of the test name
+# Like leak, but run a string eval instead.
+# The code is used instead of the test name
# if the name is absent.
sub eleak {
my ($n,$delta,$code,@rest) = @_;
- leak $n, $delta + !!$Config{mad}, sub { eval $code },
+ no warnings 'deprecated'; # Silence the literal control character warning
+ leak $n, $delta, sub { eval $code },
@rest ? @rest : $code
}
leak(5, 0, sub {push @a,1;pop @a}, "basic check 2 of leak test infrastructure");
leak(5, 1, sub {push @a,1;}, "basic check 3 of leak test infrastructure");
+# delete
+{
+ my $key = "foo";
+ $key++ while exists $ENV{$key};
+ leak(2, 0, sub { delete local $ENV{$key} },
+ 'delete local on nonexistent env var');
+}
+
+# Fatal warnings
+my $f = "use warnings FATAL =>";
+my $all = "$f 'all';";
+eleak(2, 0, "$f 'deprecated'; qq|\\c\{|", 'qq|\c{| with fatal warnings');
+eleak(2, 0, "$f 'syntax'; qq|\\c`|", 'qq|\c`| with fatal warnings');
+eleak(2, 0, "$all /\$\\ /", '/$\ / with fatal warnings');
+eleak(2, 0, "$all s//\\1/", 's//\1/ with fatal warnings');
+eleak(2, 0, "$all qq|\\i|", 'qq|\i| with fatal warnings');
+eleak(2, 0, "$f 'digit'; qq|\\o{9}|", 'qq|\o{9}| with fatal warnings');
+eleak(3, 1, "$f 'misc'; sub foo{} sub foo:lvalue",
+ 'ignored :lvalue with fatal warnings');
+eleak(2, 0, "no warnings; use feature ':all'; $f 'misc';
+ my sub foo{} sub foo:lvalue",
+ 'ignored mysub :lvalue with fatal warnings');
+eleak(2, 0, "no warnings; use feature ':all'; $all
+ my sub foo{} sub foo:lvalue{}",
+ 'fatal mysub redef warning');
+eleak(2, 0, "$all sub foo{} sub foo{}", 'fatal sub redef warning');
+eleak(2, 0, "$all *x=sub {}",
+ 'fatal sub redef warning with sub-to-glob assignment');
+eleak(2, 0, "$all *x=sub() {1}",
+ 'fatal const sub redef warning with sub-to-glob assignment');
+eleak(2, 0, "$all XS::APItest::newCONSTSUB(\\%main::=>name=>0=>1)",
+ 'newCONSTSUB sub redefinition with fatal warnings');
+eleak(2, 0, "$f 'misc'; my\$a,my\$a", 'double my with fatal warnings');
+eleak(2, 0, "$f 'misc'; our\$a,our\$a", 'double our with fatal warnings');
+eleak(2, 0, "$f 'closure';
+ sub foo { my \$x; format=\n\@\n\$x\n.\n} write; ",
+ 'format closing over unavailable var with fatal warnings');
+eleak(2, 0, "$all /(?{})?/ ", '(?{})? with fatal warnings');
+eleak(2, 0, "$all /(?{})+/ ", '(?{})+ with fatal warnings');
+eleak(2, 0, "$all /[\\i]/ ", 'invalid charclass escape with fatal warns');
+eleak(2, 0, "$all /[:foo:]/ ", '/[:foo:]/ with fatal warnings');
+eleak(2, 0, "$all /[a-\\d]/ ", '[a-\d] char class with fatal warnings');
+eleak(2, 0, "$all v111111111111111111111111111111111111111111111111",
+ 'vstring num overflow with fatal warnings');
+
+eleak(2, 0, 'sub{<*>}');
+# Use a random number of ops, so that the glob op does not reuse the same
+# address each time, giving us false passes.
+leak(2, 0, sub { eval '$x+'x(1 + rand() * 100) . '<*>'; },
+ 'freeing partly iterated glob');
+
+eleak(2, 0, 'goto sub {}', 'goto &sub in eval');
+eleak(2, 0, '() = sort { goto sub {} } 1,2', 'goto &sub in sort');
+eleak(2, 0, '/(?{ goto sub {} })/', 'goto &sub in regexp');
+
sub TIEARRAY { bless [], $_[0] }
sub FETCH { $_[0]->[$_[1]] }
sub STORE { $_[0]->[$_[1]] = $_[2] }
leak(5, 0, sub {local $a[0]}, "local \$tied[0]");
}
+# Overloading
+require overload;
+eleak(2, 0, "BEGIN{overload::constant integer=>sub{}} 1,1,1,1,1,1,1,1,1,1",
+ '"too many errors" from constant overloading returning undef');
+# getting this one to leak was complicated; we have to unset LOCALIZE_HH:
+eleak(2, 0, 'BEGIN{overload::constant integer=>sub{}; $^H &= ~ 0x00020000}
+ 1,1,1,1,1,1,1,1,1,1',
+ '"too many errors" from constant overloading with $^H sabotaged');
+eleak(2, 0, "BEGIN{overload::constant integer=>sub{}; undef %^H}
+ 1,1,1,1,1,1,1,1,1,1",
+ '"too many errors" from constant overloading with %^H undefined');
+
+
# [perl #74484] repeated tries leaked SVs on the tmps stack
leak_expr(5, 0, q{"YYYYYa" =~ /.+?(a(.+?)|b)/ }, "trie leak");
my $s;
my @a;
my @count = (0) x 4; # pre-allocate
-
- grep qr/1/ && ($count[$_] = sv_count()) && 99, 0..3;
+ # Using 0..3 with constant endpoints will cause an erroneous test fail-
+ # ure, as the SV in the op tree needs to be copied (to protect it),
+ # but copying happens *during iteration*, causing the number of SVs to
+ # go up. Using a variable (0..$_3) will cause evaluation of the range
+ # operator at run time, not compile time, so the values will already be
+ # on the stack before grep starts.
+ my $_3 = 3;
+
+ grep qr/1/ && ($count[$_] = sv_count()) && 99, 0..$_3;
is(@count[3] - @count[0], 0, "void grep expr: no new tmps per iter");
- grep { qr/1/ && ($count[$_] = sv_count()) && 99 } 0..3;
+ grep { qr/1/ && ($count[$_] = sv_count()) && 99 } 0..$_3;
is(@count[3] - @count[0], 0, "void grep block: no new tmps per iter");
- $s = grep qr/1/ && ($count[$_] = sv_count()) && 99, 0..3;
+ $s = grep qr/1/ && ($count[$_] = sv_count()) && 99, 0..$_3;
is(@count[3] - @count[0], 0, "scalar grep expr: no new tmps per iter");
- $s = grep { qr/1/ && ($count[$_] = sv_count()) && 99 } 0..3;
+ $s = grep { qr/1/ && ($count[$_] = sv_count()) && 99 } 0..$_3;
is(@count[3] - @count[0], 0, "scalar grep block: no new tmps per iter");
- @a = grep qr/1/ && ($count[$_] = sv_count()) && 99, 0..3;
+ @a = grep qr/1/ && ($count[$_] = sv_count()) && 99, 0..$_3;
is(@count[3] - @count[0], 0, "list grep expr: no new tmps per iter");
- @a = grep { qr/1/ && ($count[$_] = sv_count()) && 99 } 0..3;
+ @a = grep { qr/1/ && ($count[$_] = sv_count()) && 99 } 0..$_3;
is(@count[3] - @count[0], 0, "list grep block: no new tmps per iter");
- map qr/1/ && ($count[$_] = sv_count()) && 99, 0..3;
+ map qr/1/ && ($count[$_] = sv_count()) && 99, 0..$_3;
is(@count[3] - @count[0], 0, "void map expr: no new tmps per iter");
- map { qr/1/ && ($count[$_] = sv_count()) && 99 } 0..3;
+ map { qr/1/ && ($count[$_] = sv_count()) && 99 } 0..$_3;
is(@count[3] - @count[0], 0, "void map block: no new tmps per iter");
- $s = map qr/1/ && ($count[$_] = sv_count()) && 99, 0..3;
+ $s = map qr/1/ && ($count[$_] = sv_count()) && 99, 0..$_3;
is(@count[3] - @count[0], 0, "scalar map expr: no new tmps per iter");
- $s = map { qr/1/ && ($count[$_] = sv_count()) && 99 } 0..3;
+ $s = map { qr/1/ && ($count[$_] = sv_count()) && 99 } 0..$_3;
is(@count[3] - @count[0], 0, "scalar map block: no new tmps per iter");
- @a = map qr/1/ && ($count[$_] = sv_count()) && 99, 0..3;
+ @a = map qr/1/ && ($count[$_] = sv_count()) && 99, 0..$_3;
is(@count[3] - @count[0], 3, "list map expr: one new tmp per iter");
- @a = map { qr/1/ && ($count[$_] = sv_count()) && 99 } 0..3;
+ @a = map { qr/1/ && ($count[$_] = sv_count()) && 99 } 0..$_3;
is(@count[3] - @count[0], 3, "list map block: one new tmp per iter");
}
ok(!$weak, "hash referenced weakened SV released");
}
+# prototype() errors
+leak(2,0, sub { eval { prototype "CORE::fu" } }, 'prototype errors');
+
# RT #72246: rcatline memory leak on bad $/
leak(2, 0,
eleak(2,0,'/[:]/');
eleak(2,0,'/[\xdf]/i');
+eleak(2,0,'s![^/]!!');
+eleak(2,0,'/[pp]/');
+eleak(2,0,'/[[:ascii:]]/');
+eleak(2,0,'/[[.zog.]]/');
+eleak(2,0,'/[.zog.]/');
+eleak(2,0,'/|\W/', '/|\W/ [perl #123198]');
+eleak(2,0,'no warnings; /(?[])/');
+eleak(2,0,'no warnings; /(?[[a]+[b]])/');
+eleak(2,0,'no warnings; /(?[[a]-[b]])/');
+eleak(2,0,'no warnings; /(?[[a]&[b]])/');
+eleak(2,0,'no warnings; /(?[[a]|[b]])/');
+eleak(2,0,'no warnings; /(?[[a]^[b]])/');
+eleak(2,0,'no warnings; /(?[![a]])/');
+eleak(2,0,'no warnings; /(?[\p{Word}])/');
+eleak(2,0,'no warnings; /(?[[a]+)])/');
+eleak(2,0,'no warnings; /(?[\d\d)])/');
+
+# These can generate one ref count, but just once.
+eleak(4,1,'chr(0x100) =~ /[[:punct:]]/');
+eleak(4,1,'chr(0x100) =~ /[[:^punct:]]/');
+eleak(4,1,'chr(0x100) =~ /[[:word:]]/');
+eleak(4,1,'chr(0x100) =~ /[[:^word:]]/');
+
+eleak(2,0,'chr(0x100) =~ /\P{Assigned}/');
+leak(2,0,sub { /(??{})/ }, '/(??{})/');
leak(2,0,sub { !$^V }, '[perl #109762] version object in boolean context');
# [perl #114356] run-time rexexp with unchanging pattern got
# inflated refcounts
-
-SKIP: {
- skip "disabled under -Dmad (eval leaks)" if $Config{mad};
- leak(2, 0, sub { eval q{ my $x = "x"; "abc" =~ /$x/ for 1..5 } }, '#114356');
+eleak(2, 0, q{ my $x = "x"; "abc" =~ /$x/ for 1..5 }, '#114356');
+
+eleak(2, 0, 'sub', '"sub" with nothing following');
+eleak(2, 0, '+sub:a{}', 'anon subs with invalid attributes');
+eleak(2, 0, 'no warnings; sub a{1 1}', 'sub with syntax error');
+eleak(2, 0, 'no warnings; sub {1 1}', 'anon sub with syntax error');
+eleak(2, 0, 'no warnings; use feature ":all"; my sub a{1 1}',
+ 'my sub with syntax error');
+
+# Reification (or lack thereof)
+leak(2, 0, sub { sub { local $_[0]; shift }->(1) },
+ 'local $_[0] on surreal @_, followed by shift');
+leak(2, 0, sub { sub { local $_[0]; \@_ }->(1) },
+ 'local $_[0] on surreal @_, followed by reification');
+
+sub recredef {}
+sub Recursive::Redefinition::DESTROY {
+ *recredef = sub { CORE::state $x } # state makes it cloneable
}
+leak(2, 0, sub {
+ bless \&recredef, "Recursive::Redefinition"; eval "sub recredef{}"
+}, 'recursive sub redefinition');
# Syntax errors
eleak(2, 0, '"${<<END}"
', 'unterminated here-doc in quotes in multiline eval');
eleak(2, 0, '"${<<END
}"', 'unterminated here-doc in multiline quotes in eval');
-leak(2, !!$Config{mad}, sub { eval { do './op/svleak.pl' } },
+leak(2, 0, sub { eval { do './op/svleak.pl' } },
'unterminated here-doc in file');
eleak(2, 0, 'tr/9-0//');
eleak(2, 0, 'tr/a-z-0//');
eleak(2, 0, 'no warnings; nonexistent_function 33838',
'bareword followed by number');
+eleak(2, 0, '//dd;'x20, '"too many errors" when parsing m// flags');
+eleak(2, 0, 's///dd;'x20, '"too many errors" when parsing s/// flags');
+eleak(2, 0, 'no warnings; 2 2;BEGIN{}',
+ 'BEGIN block after syntax error');
+{
+ local %INC; # in case Errno is already loaded
+ eleak(2, 0, 'no warnings; 2@!{',
+ 'implicit "use Errno" after syntax error');
+}
+eleak(2, 0, "\"\$\0\356\"", 'qq containing $ <null> something');
+eleak(2, 0, 'END OF TERMS AND CONDITIONS', 'END followed by words');
+eleak(2, 0, "+ + +;qq|\\N{a}|"x10,'qq"\N{a}" after errors');
+eleak(2, 0, "qq|\\N{%}|", 'qq"\N{%}" (invalid charname)');
+eleak(2, 0, "qq|\\N{au}|;", 'qq"\N{invalid}"');
+eleak(2, 0, "qq|\\c|;"x10, '"too many errors" from qq"\c"');
+eleak(2, 0, "qq|\\o|;"x10, '"too many errors" from qq"\o"');
+eleak(2, 0, "qq|\\x{|;"x10, '"too many errors" from qq"\x{"');
+eleak(2, 0, "qq|\\N|;"x10, '"too many errors" from qq"\N"');
+eleak(2, 0, "qq|\\N{|;"x10, '"too many errors" from qq"\N{"');
+eleak(2, 0, "qq|\\N{U+GETG}|;"x10,'"too many errors" from qq"\N{U+JUNK}"');
+
# [perl #114764] Attributes leak scalars
leak(2, 0, sub { eval 'my $x : shared' }, 'my $x :shared used to leak');
eval {%a = ($die_on_fetch, 0)}; # key
eval {%a = (0, $die_on_fetch)}; # value
eval {%a = ($die_on_fetch, $die_on_fetch)}; # both
+ eval {%a = ($die_on_fetch)}; # key, odd elements
}, 'hash assignment does not leak');
leak(2, 0, sub {
eval {@a = ($die_on_fetch)};
sub FIRSTKEY { keys %{$_[0][0]}; each %{$_[0][0]} }
sub NEXTKEY { each %{$_[0][0]} }
}
-leak(2,!!$Config{mad}, sub {
+leak(2, 0, sub {
eval q`
BEGIN {
$hhtie::explosive = 0;
# Run-time regexp code blocks
{
use re 'eval';
- my $madness = !!$Config{mad};
my @tests = ('[(?{})]','(?{})');
for my $t (@tests) {
- leak(2, $madness, sub {
+ leak(2, 0, sub {
/ $t/;
}, "/ \$x/ where \$x is $t does not leak");
- leak(2, $madness, sub {
+ leak(2, 0, sub {
/(?{})$t/;
}, "/(?{})\$x/ where \$x is $t does not leak");
}
leak(2,0,sub{eval{require untohunothu}}, 'requiring nonexistent module');
+
+# [perl #120939]
+use constant const_av_xsub_leaked => 1 .. 3;
+leak(5, 0, sub { scalar &const_av_xsub_leaked }, "const_av_sub in scalar context");
+
+# check that OP_MULTIDEREF doesn't leak when compiled and then freed
+
+eleak(2, 0, <<'EOF', 'OP_MULTIDEREF');
+no strict;
+no warnings;
+my ($x, @a, %h, $r, $k, $i);
+$x = $a[0]{foo}{$k}{$i};
+$x = $h[0]{foo}{$k}{$i};
+$x = $r->[0]{foo}{$k}{$i};
+$x = $mdr::a[0]{foo}{$mdr::k}{$mdr::i};
+$x = $mdr::h[0]{foo}{$mdr::k}{$mdr::i};
+$x = $mdr::r->[0]{foo}{$mdr::k}{$mdr::i};
+EOF