Skip to content

Commit c00b151

Browse files
committed
Separate 'goto' tests according to flavor tested
Create t/op/goto-sub.t to hold tests for 'goto &NAME', as they will presumably be unaffected by deprecation/fatalization of certain other usages of 'goto'. In the course of working on this a few other small cleanups were made, e.g., disambiguation of various uses of $ok; small whitespace cleanup; more use of {} blocks to distinguish among clusters of tests.
1 parent da43c74 commit c00b151

File tree

3 files changed

+372
-303
lines changed

3 files changed

+372
-303
lines changed

MANIFEST

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6348,6 +6348,7 @@ t/op/getppid.t See if getppid works
63486348
t/op/glob.t See if <*> works
63496349
t/op/gmagic.t See if GMAGIC works
63506350
t/op/goto.t See if goto works
6351+
t/op/goto-sub.t See if goto &NAME works
63516352
t/op/goto_xs.t See if "goto &sub" works on XSUBs
63526353
t/op/grent.t See if getgr*() functions work
63536354
t/op/grep.t See if grep() and map() work

t/op/goto-sub.t

Lines changed: 364 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,364 @@
1+
#!./perl
2+
3+
BEGIN {
4+
chdir 't' if -d 't';
5+
require "./test.pl";
6+
set_up_inc( qw(. ../lib) );
7+
require './charset_tools.pl';
8+
}
9+
10+
use warnings;
11+
use strict;
12+
use Config;
13+
plan tests => 40;
14+
15+
# Excerpts from 'perldoc -f goto' as of perl-5.40.1 (Aug 2025)
16+
#
17+
# The "goto &NAME" form is quite different from the other forms of
18+
# "goto". In fact, it isn't a goto in the normal sense at all, and
19+
# doesn't have the stigma associated with other gotos. Instead, it
20+
# exits the current subroutine (losing any changes set by "local")
21+
# and immediately calls in its place the named subroutine using
22+
# the current value of @_. This is used by "AUTOLOAD" subroutines
23+
# that wish to load another subroutine and then pretend that the
24+
# other subroutine had been called in the first place (except that
25+
# any modifications to @_ in the current subroutine are propagated
26+
# to the other subroutine.) After the "goto", not even "caller"
27+
# will be able to tell that this routine was called first.
28+
#
29+
# NAME needn't be the name of a subroutine; it can be a scalar
30+
# variable containing a code reference or a block that evaluates
31+
# to a code reference.
32+
33+
# but earlier, we see:
34+
#
35+
# The "goto EXPR" form expects to evaluate "EXPR" to a code
36+
# reference or a label name. If it evaluates to a code reference,
37+
# it will be handled like "goto &NAME", below. This is especially
38+
# useful for implementing tail recursion via "goto __SUB__".
39+
#
40+
# The purpose this test file is to consolidate all tests formerly found in
41+
# t/op/goto.t that exercise the "goto &NAME" functionality. These should be
42+
# outside the scope of the current (5.42) deprecation of aspects of "goto
43+
# LABEL" (GH #23618) now scheduled for 5.44. If we have done that
44+
# successfully, then during the 5.43 dev cycle we shouldn't see any instances
45+
# of this warning (or of its fatalization replacement).
46+
47+
my $deprecated = 0;
48+
49+
local $SIG{__WARN__} = sub {
50+
if ($_[0] =~ m/jump into a construct.*?, and will become fatal in Perl 5\.42/) {
51+
$deprecated++;
52+
}
53+
else { warn $_[0] }
54+
};
55+
56+
our $foo;
57+
58+
###################
59+
60+
# bug #9990 - don't prematurely free the CV we're &going to.
61+
62+
sub f1 {
63+
my $x;
64+
goto sub { $x=0; ok(1,"don't prematurely free CV\n") }
65+
}
66+
f1();
67+
68+
# bug #99850, which is similar - freeing the subroutine we are about to
69+
# go(in)to during a FREETMPS call should not crash perl.
70+
71+
package _99850 {
72+
sub reftype{}
73+
DESTROY { undef &reftype }
74+
eval { sub { my $guard = bless []; goto &reftype }->() };
75+
}
76+
like $@, qr/^Goto undefined subroutine &_99850::reftype at /,
77+
'goto &foo undefining &foo on sub cleanup';
78+
79+
# When croaking after discovering that the new CV you're about to goto is
80+
# undef, make sure that the old CV isn't doubly freed.
81+
82+
package Do_undef {
83+
my $count;
84+
85+
# creating a new closure here encourages any prematurely freed
86+
# CV to be reallocated
87+
sub DESTROY { undef &undef_sub; my $x = sub { $count } }
88+
89+
sub f {
90+
$count++;
91+
my $guard = bless []; # trigger DESTROY during goto
92+
*undef_sub = sub {};
93+
goto &undef_sub
94+
}
95+
96+
for (1..10) {
97+
eval { f() };
98+
}
99+
::is($count, 10, "goto undef_sub safe");
100+
}
101+
102+
# make sure that nothing nasty happens if the old CV is freed while
103+
# goto'ing
104+
105+
package Free_cv {
106+
my $results;
107+
sub f {
108+
no warnings 'redefine';
109+
*f = sub {};
110+
goto &g;
111+
}
112+
sub g { $results = "(@_)" }
113+
114+
f(1,2,3);
115+
::is($results, "(1 2 3)", "Free_cv");
116+
}
117+
118+
# [perl #29708] - goto &foo could leave foo() at depth two with
119+
# @_ == PL_sv_undef, causing a coredump
120+
121+
my $r = runperl(
122+
prog =>
123+
'sub f { return if $d; $d=1; my $a=sub {goto &f}; &$a; f() } f(); print qq(ok\n)',
124+
stderr => 1
125+
);
126+
is($r, "ok\n", 'avoid pad without an @_');
127+
128+
# see if a modified @_ propagates
129+
{
130+
my $i;
131+
package Foo;
132+
sub DESTROY { my $s = shift; ::is($s->[0], $i, "destroy $i"); }
133+
sub show { ::is(+@_, 5, "show $i",); }
134+
sub start { push @_, 1, "foo", {}; goto &show; }
135+
for (1..3) { $i = $_; start(bless([$_]), 'bar'); }
136+
}
137+
138+
sub auto {
139+
goto &loadit;
140+
}
141+
my $ok;
142+
143+
sub AUTOLOAD { $ok = 1 if "@_" eq "foo" }
144+
145+
$ok = 0;
146+
auto("foo");
147+
ok($ok, 'autoload');
148+
149+
# Test autoloading mechanism.
150+
151+
sub two {
152+
my ($pack, $file, $line) = caller; # Should indicate original call stats.
153+
is("@_ $pack $file $line", "1 2 3 main $::FILE $::LINE",
154+
'autoloading mechanism.');
155+
}
156+
157+
sub one {
158+
eval <<'END';
159+
no warnings 'redefine';
160+
sub one { pass('sub one'); goto &two; fail('sub one tail'); }
161+
END
162+
goto &one;
163+
}
164+
165+
$::FILE = __FILE__;
166+
$::LINE = __LINE__ + 1;
167+
&one(1,2,3);
168+
169+
# deep recursion with gotos eventually caused a stack reallocation
170+
# which messed up buggy internals that didn't expect the stack to move
171+
172+
sub recurse1 {
173+
unshift @_, "x";
174+
no warnings 'recursion';
175+
goto &recurse2;
176+
}
177+
sub recurse2 {
178+
my $x = shift;
179+
$_[0] ? +1 + recurse1($_[0] - 1) : 0
180+
}
181+
182+
{
183+
my $w = 0;
184+
local $SIG{__WARN__} = sub { ++$w };
185+
is(recurse1(500), 500, 'recursive goto &foo');
186+
is $w, 0, 'no recursion warnings for "no warnings; goto &sub"';
187+
delete $SIG{__WARN__};
188+
}
189+
190+
# [perl #32039] Chained goto &sub drops data too early.
191+
192+
sub a32039 { @_=("foo"); goto &b32039; }
193+
sub b32039 { goto &c32039; }
194+
sub c32039 { is($_[0], 'foo', 'chained &goto') }
195+
a32039();
196+
197+
###################
198+
199+
# goto &foo not allowed in evals
200+
201+
sub null { 1 };
202+
eval 'goto &null';
203+
like($@, qr/Can't goto subroutine from an eval-string/, 'eval string');
204+
eval { goto &null };
205+
like($@, qr/Can't goto subroutine from an eval-block/, 'eval block');
206+
207+
# goto &foo leaves @_ alone when called from a sub
208+
sub returnarg { $_[0] };
209+
is sub {
210+
local *_ = ["ick and queasy"];
211+
goto &returnarg;
212+
}->("quick and easy"), "ick and queasy",
213+
'goto &foo with *_{ARRAY} replaced';
214+
my @__ = byte_utf8a_to_utf8n("\xc4\x80");
215+
sub { local *_ = \@__; goto &utf8::decode }->("no thinking aloud");
216+
is "@__", chr 256, 'goto &xsub with replaced *_{ARRAY}';
217+
218+
# And goto &foo should leave reified @_ alone
219+
sub { *__ = \@_; goto &null } -> ("rough and tubbery");
220+
is ${*__}[0], 'rough and tubbery', 'goto &foo leaves reified @_ alone';
221+
222+
# goto &xsub when @_ has nonexistent elements
223+
{
224+
no warnings "uninitialized";
225+
local @_ = ();
226+
$#_++;
227+
& {sub { goto &utf8::encode }};
228+
is @_, 1, 'num of elems in @_ after goto &xsub with nonexistent $_[0]';
229+
is $_[0], "", 'content of nonexistent $_[0] is modified by goto &xsub';
230+
}
231+
232+
# goto &xsub when @_ itself does not exist
233+
undef *_;
234+
eval { & { sub { goto &utf8::encode } } };
235+
# The main thing we are testing is that it did not crash. But make sure
236+
# *_{ARRAY} was untouched, too.
237+
is *_{ARRAY}, undef, 'goto &xsub when @_ does not exist';
238+
239+
# goto &perlsub when @_ itself does not exist [perl #119949]
240+
# This was only crashing when the replaced sub call had an argument list.
241+
# (I.e., &{ sub { goto ... } } did not crash.)
242+
sub {
243+
undef *_;
244+
goto sub {
245+
is *_{ARRAY}, undef, 'goto &perlsub when @_ does not exist';
246+
}
247+
}->();
248+
sub {
249+
local *_;
250+
goto sub {
251+
is *_{ARRAY}, undef, 'goto &sub when @_ does not exist (local *_)';
252+
}
253+
}->();
254+
255+
# [perl #36521] goto &foo in warn handler could defeat recursion avoider
256+
257+
{
258+
my $r = runperl(
259+
stderr => 1,
260+
prog => 'my $d; my $w = sub { return if $d++; warn q(bar)}; local $SIG{__WARN__} = sub { goto &$w; }; warn q(foo);'
261+
);
262+
like($r, qr/bar/, "goto &foo in warn");
263+
}
264+
265+
{
266+
sub TIESCALAR { bless [pop] }
267+
sub FETCH { $_[0][0] }
268+
tie my $t, "", sub { "cluck up porridge" };
269+
is eval { sub { goto $t }->() }//$@, 'cluck up porridge',
270+
'tied arg returning sub ref';
271+
}
272+
273+
# v5.31.3-198-gd2cd363728 broke this. goto &XS_sub wasn't restoring
274+
# cx->blk_sub.old_cxsubix. Would panic in pp_return
275+
276+
{
277+
# isa is an XS sub
278+
sub g198 { goto &UNIVERSAL::isa }
279+
280+
sub f198 {
281+
g198([], 1 );
282+
{
283+
return 1;
284+
}
285+
}
286+
eval { f198(); };
287+
is $@, "", "v5.31.3-198-gd2cd363728";
288+
}
289+
290+
# GH #19188
291+
#
292+
# 'goto &xs_sub' should provide the correct caller context to an XS sub
293+
294+
SKIP:
295+
{
296+
skip "No XS::APItest in miniperl", 6 if is_miniperl();
297+
skip "No XS::APItest in static perl", 6 if not $Config{usedl};
298+
299+
require XS::APItest;
300+
301+
sub f_19188 { goto &XS::APItest::gimme }
302+
sub g_19188{ f_19188(); }
303+
my ($s, @a);
304+
305+
f_19188();
306+
is ($XS::APItest::GIMME_V, 1, 'xs_goto void (#19188)');
307+
308+
$s = f_19188();
309+
is ($XS::APItest::GIMME_V, 2, 'xs_goto scalar (#19188)');
310+
311+
@a = f_19188();
312+
is ($XS::APItest::GIMME_V, 3, 'xs_goto list (#19188)');
313+
314+
g_19188();
315+
is ($XS::APItest::GIMME_V, 1, 'xs_goto indirect void (#19188)');
316+
317+
$s = g_19188();
318+
is ($XS::APItest::GIMME_V, 2, 'xs_goto indirect scalar (#19188)');
319+
320+
@a = g_19188();
321+
is ($XS::APItest::GIMME_V, 3, 'xs_goto indirect list (#19188)');
322+
}
323+
324+
# GH #19936 segfault on goto &xs_sub when calling sub is replaced
325+
SKIP:
326+
{
327+
skip "No XS::APItest in miniperl", 2 if is_miniperl();
328+
skip "No XS::APItest in static perl", 2 if not $Config{usedl};
329+
330+
# utf8::is_utf8() is just an example of an XS sub
331+
sub foo_19936 { *foo_19936 = {}; goto &utf8::is_utf8 }
332+
ok(foo_19936("\x{100}"), "GH #19936 utf8 XS call");
333+
334+
# the gimme XS function accesses PL_op, which was null before the fix
335+
sub bar_19936 { *bar_19936 = {}; goto &XS::APItest::gimme }
336+
my @a = bar_19936();
337+
is($XS::APItest::GIMME_V, 3, "GH #19936 gimme XS call");
338+
}
339+
340+
# goto &sub could leave AvARRAY() slots of @_ uninitialised.
341+
342+
{
343+
my $i = 0;
344+
my $f = sub {
345+
goto &{ sub {} } unless $i++;
346+
$_[1] = 1; # create a hole
347+
# accessing $_[0] is more for valgrind/ASAN to chew on rather than
348+
# we're too concerned about its value. Or it might give "bizarre
349+
# copy" errors.
350+
is($_[0], undef, "goto and AvARRAY");
351+
};
352+
353+
# first call does goto, which gives &$f a fresh AV in pad[0],
354+
# which formerly allocated an AvARRAY for it, but didn't zero it
355+
$f->();
356+
# second call creates hole in @_ which used to to be a wild SV pointer
357+
$f->();
358+
}
359+
360+
361+
# Final test: ensure that we saw no deprecation warnings
362+
# ... but rework this to count fatalizations once work is more developed
363+
364+
is($deprecated, 0, "No 'jump into a construct' warnings seen");

0 commit comments

Comments
 (0)