1#!perl 2 3# Test the various op trees that turn sub () { ... } into a constant, and 4# some variants that don’t. 5 6BEGIN { 7 chdir 't'; 8 require './test.pl'; 9 set_up_inc('../lib'); 10} 11plan 148; 12 13# @tests is an array of hash refs, each of which can have various keys: 14# 15# nickname - name of the sub to use in test names 16# generator - a sub returning a code ref to test 17# finally - sub to run after the tests 18# 19# Each of the following gives expected test results. If the key is 20# omitted, the test is skipped: 21# 22# retval - the returned code ref’s return value 23# same_retval - whether the same scalar is returned each time 24# inlinable - whether the sub is inlinable 25# deprecated - whether the sub returning a code ref will emit a depreca- 26# tion warning when called 27# method - whether the sub has the :method attribute 28# exception - sub now throws an exception (previously threw 29# deprecation warning) 30 31my $exception_134138 = 'Constants from lexical variables potentially modified ' 32 . 'elsewhere are no longer permitted'; 33 34# [perl #63540] Don’t treat sub { if(){.....}; "constant" } as a constant 35sub blonk { ++$blonk_was_called } 36push @tests, { 37 nickname => 'sub with null+kids (if-block), then constant', 38 generator => sub { 39 # This used to turn into a constant with the value of $x 40 my $x = 7; 41 sub() { if($x){ () = "tralala"; blonk() }; 0 } 42 }, 43 retval => 0, 44 same_retval => 0, 45 inlinable => 0, 46 deprecated => 0, 47 method => 0, 48 finally => sub { ok($blonk_was_called, 'RT #63540'); }, 49}; 50 51# [perl #79908] 52push @tests, { 53 nickname => 'sub with simple lexical modified elsewhere', 54 generator => sub { my $x = 5; my $ret = sub(){$x}; $x = 7; $ret }, 55 exception => $exception_134138, 56}; 57 58push @tests, { 59 nickname => 'sub with simple lexical unmodified elsewhere', 60 generator => sub { my $x = 5; sub(){$x} }, 61 retval => 5, 62 same_retval => 0, 63 inlinable => 1, 64 deprecated => 0, 65 method => 0, 66}; 67 68push @tests, { 69 nickname => 'return $variable modified elsewhere', 70 generator => sub { my $x=5; my $ret = sub(){return $x}; $x = 7; $ret }, 71 retval => 7, 72 same_retval => 0, 73 inlinable => 0, 74 deprecated => 0, 75 method => 0, 76}; 77 78push @tests, { 79 nickname => 'return $variable unmodified elsewhere', 80 generator => sub { my $x = 5; sub(){return $x} }, 81 retval => 5, 82 same_retval => 0, 83 inlinable => 0, 84 deprecated => 0, 85 method => 0, 86}; 87 88push @tests, { 89 nickname => 'sub () { 0; $x } with $x modified elsewhere', 90 generator => sub { my $x = 5; my $ret = sub(){0;$x}; $x = 8; $ret }, 91 retval => 8, 92 same_retval => 0, 93 inlinable => 0, 94 deprecated => 0, 95 method => 0, 96}; 97 98push @tests, { 99 nickname => 'sub () { 0; $x } with $x unmodified elsewhere', 100 generator => sub { my $x = 5; my $y = $x; sub(){0;$x} }, 101 retval => 5, 102 same_retval => 0, 103 inlinable => 1, 104 deprecated => 0, 105 method => 0, 106}; 107 108# Explicit return after optimised statement, not at end of sub 109push @tests, { 110 nickname => 'sub () { 0; return $x; ... }', 111 generator => sub { my $x = 5; sub () { 0; return $x; ... } }, 112 retval => 5, 113 same_retval => 0, 114 inlinable => 0, 115 deprecated => 0, 116 method => 0, 117}; 118 119# Explicit return after optimised statement, at end of sub [perl #123092] 120push @tests, { 121 nickname => 'sub () { 0; return $x }', 122 generator => sub { my $x = 5; sub () { 0; return $x } }, 123 retval => 5, 124 same_retval => 0, 125 inlinable => 0, 126 deprecated => 0, 127 method => 0, 128}; 129 130# Multiple closure tests 131push @tests, { 132 nickname => 'simple lexical after another closure and no lvalue', 133 generator => sub { 134 my $x = 5; 135 # This closure prevents inlining, though theoretically it shouldn’t 136 # have to. If you change the behaviour, just change the test. This 137 # fails the refcount check in op.c:op_const_sv, which is necessary for 138 # the sake of \(my $x = 1) (tested below). 139 my $sub1 = sub () { () = $x }; 140 sub () { $x }; 141 }, 142 retval => 5, 143 same_retval => 0, 144 inlinable => 0, 145 deprecated => 0, 146 method => 0, 147}; 148push @tests, { 149 nickname => 'simple lexical before another closure and no lvalue', 150 generator => sub { 151 my $x = 5; 152 my $ret = sub () { $x }; 153 # This does not prevent inlining and never has. 154 my $sub1 = sub () { () = $x }; 155 $ret; 156 }, 157 retval => 5, 158 same_retval => 0, 159 inlinable => 1, 160 deprecated => 0, 161 method => 0, 162}; 163push @tests, { 164 nickname => 'simple lexical after an lvalue closure', 165 generator => sub { 166 my $x = 5; 167 # This has always prevented inlining 168 my $sub1 = sub () { $x++ }; 169 sub () { $x }; 170 }, 171 retval => 5, 172 same_retval => 0, 173 inlinable => 0, 174 deprecated => 0, 175 method => 0, 176}; 177push @tests, { 178 nickname => 'simple lexical before an lvalue closure', 179 generator => sub { 180 my $x = 5; 181 my $ret = sub () { $x }; # <-- simple lexical op tree 182 # Traditionally this has not prevented inlining, though it should. But 183 # since $ret has a simple lexical op tree, we preserve backward-compat- 184 # ibility, but deprecate it. 185 my $sub1 = sub () { $x++ }; 186 $ret; 187 }, 188 exception => $exception_134138, 189}; 190push @tests, { 191 nickname => 'complex lexical op tree before an lvalue closure', 192 generator => sub { 193 my $x = 5; 194 my $ret = sub () { 0; $x }; # <-- more than just a lexical 195 # This used not to prevent inlining, though it should, and now does. 196 my $sub1 = sub () { $x++ }; 197 $ret; 198 }, 199 retval => 5, 200 same_retval => 0, 201 inlinable => 0, 202 deprecated => 0, 203 method => 0, 204}; 205push @tests, { 206 nickname => 'complex lexical op tree before a nested lvalue closure', 207 generator => sub { 208 my $x = 5; 209 my $ret = sub () { 0; $x }; # <-- more than just a lexical 210 # This used not to prevent inlining, though it should, and now does. 211 my $sub1 = sub () { sub () { $x++ } }; # nested 212 $ret; 213 }, 214 retval => 5, 215 same_retval => 0, 216 inlinable => 0, 217 deprecated => 0, 218 method => 0, 219}; 220 221use feature 'state', 'lexical_subs'; 222no warnings 'experimental::lexical_subs'; 223 224# Constant constants 225push @tests, { 226 nickname => 'sub with constant', 227 generator => sub { sub () { 8 } }, 228 retval => 8, 229 same_retval => 0, 230 inlinable => 1, 231 deprecated => 0, 232 method => 0, 233}; 234push @tests, { 235 nickname => 'sub with constant and return', 236 generator => sub { sub () { return 8 } }, 237 retval => 8, 238 same_retval => 0, 239 inlinable => 0, 240 deprecated => 0, 241 method => 0, 242}; 243push @tests, { 244 nickname => 'sub with optimised statement and constant', 245 generator => sub { sub () { 0; 8 } }, 246 retval => 8, 247 same_retval => 0, 248 inlinable => 1, 249 deprecated => 0, 250 method => 0, 251}; 252push @tests, { 253 nickname => 'sub with optimised statement, constant and return', 254 generator => sub { sub () { 0; return 8 } }, 255 retval => 8, 256 same_retval => 0, 257 inlinable => 0, 258 deprecated => 0, 259 method => 0, 260}; 261push @tests, { 262 nickname => 'my sub with constant', 263 generator => sub { my sub x () { 8 } \&x }, 264 retval => 8, 265 same_retval => 0, 266 inlinable => 1, 267 deprecated => 0, 268 method => 0, 269}; 270push @tests, { 271 nickname => 'my sub with constant and return', 272 generator => sub { my sub x () { return 8 } \&x }, 273 retval => 8, 274 same_retval => 0, 275 inlinable => 0, 276 deprecated => 0, 277 method => 0, 278}; 279push @tests, { 280 nickname => 'my sub with optimised statement and constant', 281 generator => sub { my sub x () { 0; 8 } \&x }, 282 retval => 8, 283 same_retval => 0, 284 inlinable => 1, 285 deprecated => 0, 286 method => 0, 287}; 288push @tests, { 289 nickname => 'my sub with optimised statement, constant and return', 290 generator => sub { my sub x () { 0; return 8 } \&x }, 291 retval => 8, 292 same_retval => 0, 293 inlinable => 0, 294 deprecated => 0, 295 method => 0, 296}; 297 298# String eval 299push @tests, { 300 nickname => 'sub () { $x } with eval in scope', 301 generator => sub { 302 my $outer = 43; 303 my $ret = sub () { $outer }; 304 eval '$outer++'; 305 $ret; 306 }, 307 exception => $exception_134138, 308}; 309push @tests, { 310 nickname => 'sub () { $x } with s///ee in scope', 311 generator => sub { 312 my $outer = 43; 313 my $dummy = '$outer++'; 314 my $ret = sub () { $outer }; 315 $dummy =~ s//$dummy/ee; 316 $ret; 317 }, 318 exception => $exception_134138, 319}; 320push @tests, { 321 nickname => 'sub () { $x } with eval not in scope', 322 generator => sub { 323 my $ret; 324 { 325 my $outer = 43; 326 $ret = sub () { $outer }; 327 } 328 eval ''; 329 $ret; 330 }, 331 retval => 43, 332 same_retval => 0, 333 inlinable => 1, 334 deprecated => 0, 335 method => 0, 336}; 337 338push @tests, { 339 nickname => 'sub () { my $x; state sub z { $x } $outer }', 340 generator => sub { 341 my $outer = 43; 342 sub () { my $x; state sub z { $x } $outer } 343 }, 344 retval => 43, 345 same_retval => 0, 346 inlinable => 0, 347 deprecated => 0, 348 method => 0, 349}; 350 351push @tests, { 352 nickname => 'closure after \(my $x=1)', 353 generator => sub { 354 $y = \(my $x = 1); 355 my $ret = sub () { $x }; 356 $$y += 7; 357 $ret; 358 }, 359 retval => 8, 360 same_retval => 0, 361 inlinable => 0, 362 deprecated => 0, 363 method => 0, 364}; 365 366push @tests, { 367 nickname => 'sub:method with simple lexical', 368 generator => sub { my $y; sub():method{$y} }, 369 retval => undef, 370 same_retval => 0, 371 inlinable => 1, 372 deprecated => 0, 373 method => 1, 374}; 375push @tests, { 376 nickname => 'sub:method with constant', 377 generator => sub { sub():method{3} }, 378 retval => 3, 379 same_retval => 0, 380 inlinable => 1, 381 deprecated => 0, 382 method => 1, 383}; 384push @tests, { 385 nickname => 'my sub:method with constant', 386 generator => sub { my sub x ():method{3} \&x }, 387 retval => 3, 388 same_retval => 0, 389 inlinable => 1, 390 deprecated => 0, 391 method => 1, 392}; 393 394push @tests, { 395 nickname => 'sub closing over state var', 396 generator => sub { state $x = 3; sub () {$x} }, 397 retval => 3, 398 same_retval => 0, 399 inlinable => 1, 400 deprecated => 0, 401 method => 0, 402}; 403push @tests, { 404 nickname => 'sub closing over state var++', 405 generator => sub { state $x++; sub () { $x } }, 406 exception => $exception_134138, 407}; 408 409 410use feature 'refaliasing'; 411no warnings 'experimental::refaliasing'; 412for \%_ (@tests) { 413 my $nickname = $_{nickname}; 414 if (exists $_{exception} and $_{exception}) { 415 local $@; 416 eval { my $sub = &{$_{generator}}; }; 417 like($@, qr/$_{exception}/, "$nickname: now throws exception (RT 134138)"); 418 next; 419 } 420 my $w; 421 local $SIG{__WARN__} = sub { $w = shift }; 422 my $sub = &{$_{generator}}; 423 if (exists $_{deprecated}) { 424 if ($_{deprecated}) { 425 like $w, qr/^Constants from lexical variables potentially (?x: 426 )modified elsewhere are deprecated\. This will (?x: 427 )not be allowed in Perl 5\.32 at /, 428 "$nickname is deprecated"; 429 } 430 else { 431 is $w, undef, "$nickname is not deprecated"; 432 } 433 } 434 if (exists $_{retval}) { 435 is &$sub, $_{retval}, "retval of $nickname"; 436 } 437 if (exists $_{same_retval}) { 438 my $same = $_{same_retval} ? "same" : "different"; 439 &{$_{same_retval} ? \&is : \&isnt}( 440 \scalar &$sub(), \scalar &$sub(), 441 "$nickname gives $same retval each call" 442 ); 443 } 444 if (exists $_{inlinable}) { 445 local *temp_inlinability_test = $sub; 446 $w = undef; 447 use warnings 'redefine'; 448 *temp_inlinability_test = sub (){}; 449 my $S = $_{inlinable} ? "Constant s" : "S"; 450 my $not = " not" x! $_{inlinable}; 451 like $w, qr/^${S}ubroutine .* redefined at /, 452 "$nickname is$not inlinable"; 453 } 454 if (exists $_{method}) { 455 local *time = $sub; 456 $w = undef; 457 use warnings 'ambiguous'; 458 eval "()=time"; 459 if ($_{method}) { 460 is $w, undef, "$nickname has :method attribute"; 461 } 462 else { 463 like $w, qr/^Ambiguous call resolved as CORE::time\(\), (?x: 464 )qualify as such or use & at /, 465 "$nickname has no :method attribute"; 466 } 467 } 468 469 &{$_{finally} or next} 470} 471 472# This used to fail an assertion in leave_scope. For some reason, it did 473# not fail within the framework above. 474sub { my $x = "x"; my $sub = sub () { $x }; undef $sub; } ->(); 475pass("No assertion failure when turning on PADSTALE on lexical shared by" 476 ." erstwhile constant"); 477 478{ 479 my $sub = sub { 480 my $x = "x"x2000; sub () {$x}; 481 }->(); 482 $y = &$sub; 483 $z = &$sub; 484 is $z, $y, 'inlinable sub ret vals are not swipable'; 485} 486 487