document Perl 5.14.0 fixed $@ clobbering in DESTROY
[p5sagit/Try-Tiny.git] / lib / Try / Tiny.pm
index 54d3417..501b006 100644 (file)
@@ -1,17 +1,36 @@
 package Try::Tiny;
+use 5.006;
+# ABSTRACT: Minimal try/catch with proper preservation of $@
+
+our $VERSION = '0.29';
 
 use strict;
 use warnings;
 
-our $VERSION = "0.12";
-$VERSION = eval $VERSION if $VERSION =~ /_/;
-
-use base 'Exporter';
+use Exporter 5.57 'import';
 our @EXPORT = our @EXPORT_OK = qw(try catch finally);
 
 use Carp;
 $Carp::Internal{+__PACKAGE__}++;
 
+BEGIN {
+  my $su = $INC{'Sub/Util.pm'} && defined &Sub::Util::set_subname;
+  my $sn = $INC{'Sub/Name.pm'} && eval { Sub::Name->VERSION(0.08) };
+  unless ($su || $sn) {
+    $su = eval { require Sub::Util; } && defined &Sub::Util::set_subname;
+    unless ($su) {
+      $sn = eval { require Sub::Name; Sub::Name->VERSION(0.08) };
+    }
+  }
+
+  *_subname = $su ? \&Sub::Util::set_subname
+            : $sn ? \&Sub::Name::subname
+            : sub { $_[1] };
+  *_HAS_SUBNAME = ($su || $sn) ? sub(){1} : sub(){0};
+}
+
+my %_finally_guards;
+
 # Need to prototype as @ not $$ because of the way Perl evaluates the prototype.
 # Keeping it at $$ means you only ever get 1 sub because we need to eval in a list
 # context & not a scalar one
@@ -23,7 +42,9 @@ sub try (&;@) {
   # to $failed
   my $wantarray = wantarray;
 
-  my ( $catch, @finally );
+  # work around perl bug by explicitly initializing these, due to the likelyhood
+  # this will be used in global destruction (perl rt#119311)
+  my ( $catch, @finally ) = ();
 
   # find labeled blocks in the argument list.
   # catch and finally tag the blocks by blessing a scalar reference to them.
@@ -48,6 +69,19 @@ sub try (&;@) {
   # not perfect, but we could provide a list of additional errors for
   # $catch->();
 
+  # name the blocks if we have Sub::Name installed
+  _subname(caller().'::try {...} ' => $try)
+    if _HAS_SUBNAME;
+
+  # set up scope guards to invoke the finally blocks at the end.
+  # this should really be a function scope lexical variable instead of
+  # file scope + local but that causes issues with perls < 5.20 due to
+  # perl rt#119311
+  local $_finally_guards{guards} = [
+    map { Try::Tiny::ScopeGuard->_new($_) }
+    @finally
+  ];
+
   # save the value of $@ so we can set $@ back to it in the beginning of the eval
   # and restore $@ after the eval finishes
   my $prev_error = $@;
@@ -68,20 +102,19 @@ sub try (&;@) {
       $try->();
     };
 
-    return 1; # properly set $fail to false
-  } and $error = $@;
+    return 1; # properly set $failed to false
+  };
 
-  # reset the original value of $@
+  # preserve the current error and reset the original value of $@
+  $error = $@;
   $@ = $prev_error;
 
-  # set up a scope guard to invoke the finally block at the end
-  my @guards =
-    map { Try::Tiny::ScopeGuard->_new($_, $failed ? $error : ()) }
-    @finally;
-
   # at this point $failed contains a true value if the eval died, even if some
   # destructor overwrote $@ as the eval was unwinding.
   if ( $failed ) {
+    # pass $error to the finally blocks
+    push @$_, $error for @{$_finally_guards{guards}};
+
     # if we got an error, invoke the catch block.
     if ( $catch ) {
       # This works like given($error), but is backwards compatible and
@@ -104,8 +137,10 @@ sub try (&;@) {
 sub catch (&;@) {
   my ( $block, @rest ) = @_;
 
-  croak 'Useless bare catch()' unless defined wantarray;
+  croak 'Useless bare catch()' unless wantarray;
 
+  _subname(caller().'::catch {...} ' => $block)
+    if _HAS_SUBNAME;
   return (
     bless(\$block, 'Try::Tiny::Catch'),
     @rest,
@@ -115,8 +150,10 @@ sub catch (&;@) {
 sub finally (&;@) {
   my ( $block, @rest ) = @_;
 
-  croak 'Useless bare finally()' unless defined wantarray;
+  croak 'Useless bare finally()' unless wantarray;
 
+  _subname(caller().'::finally {...} ' => $block)
+    if _HAS_SUBNAME;
   return (
     bless(\$block, 'Try::Tiny::Finally'),
     @rest,
@@ -127,15 +164,30 @@ sub finally (&;@) {
   package # hide from PAUSE
     Try::Tiny::ScopeGuard;
 
+  use constant UNSTABLE_DOLLARAT => ("$]" < '5.013002') ? 1 : 0;
+
   sub _new {
     shift;
     bless [ @_ ];
   }
 
   sub DESTROY {
-    my @guts = @{ shift() };
-    my $code = shift @guts;
-    $code->(@guts);
+    my ($code, @args) = @{ $_[0] };
+
+    local $@ if UNSTABLE_DOLLARAT;
+    eval {
+      $code->(@args);
+      1;
+    } or do {
+      warn
+        "Execution of finally() block $code resulted in an exception, which "
+      . '*CAN NOT BE PROPAGATED* due to fundamental limitations of Perl. '
+      . 'Your program will continue as if this event never took place. '
+      . "Original exception text follows:\n\n"
+      . (defined $@ ? $@ : '$@ left undefined...')
+      . "\n"
+      ;
+    }
   }
 }
 
@@ -145,10 +197,6 @@ __END__
 
 =pod
 
-=head1 NAME
-
-Try::Tiny - minimal try/catch with proper preservation of $@
-
 =head1 SYNOPSIS
 
 You can use Try::Tiny's C<try> and C<catch> to expect and handle exceptional
@@ -196,8 +244,8 @@ context or the empty list in list context. The following examples all
 assign C<"bar"> to C<$x>:
 
   my $x = try { die "foo" } catch { "bar" };
-  my $x = try { die "foo" } || { "bar" };
-  my $x = (try { die "foo" }) // { "bar" };
+  my $x = try { die "foo" } || "bar";
+  my $x = (try { die "foo" }) // "bar";
 
   my $x = eval { die "foo" } || "bar";
 
@@ -211,6 +259,13 @@ C<finally> blocks are always executed making them suitable for cleanup code
 which cannot be handled using local.  You can add as many C<finally> blocks to a
 given C<try> block as you like.
 
+Note that adding a C<finally> block without a preceding C<catch> block
+suppresses any errors. This behaviour is consistent with using a standalone
+C<eval>, but it is not consistent with C<try>/C<finally> patterns found in
+other programming languages, such as Java, Python, Javascript or C#. If you
+learnt the C<try>/C<finally> pattern from one of these languages, watch out for
+this.
+
 =head1 EXPORTS
 
 All functions are exported by default using L<Exporter>.
@@ -306,6 +361,11 @@ B<You must always do your own error handling in the C<finally> block>. C<Try::Ti
 not do anything about handling possible errors coming from code located in these
 blocks.
 
+Furthermore B<exceptions in C<finally> blocks are not trappable and are unable
+to influence the execution of your program>. This is due to limitation of
+C<DESTROY>-based scope guards, which C<finally> is implemented on top of. This
+may change in a future version of Try::Tiny.
+
 In the same way C<catch()> blesses the code reference this subroutine does the same
 except it bless them as C<Try::Tiny::Finally>.
 
@@ -326,8 +386,10 @@ not yet handled.
 C<$@> must be properly localized before invoking C<eval> in order to avoid this
 issue.
 
-More specifically, C<$@> is clobbered at the beginning of the C<eval>, which
-also makes it impossible to capture the previous error before you die (for
+More specifically,
+L<before Perl version 5.14.0|perl5140delta/"Exception Handling">
+C<$@> was clobbered at the beginning of the C<eval>, which
+also made it impossible to capture the previous error before you die (for
 instance when making exception objects with error stacks).
 
 For this reason C<try> will actually set C<$@> to its previous value (the one
@@ -370,7 +432,7 @@ because due to the previous caveats it may have been unset.
 C<$@> could also be an overloaded error object that evaluates to false, but
 that's asking for trouble anyway.
 
-The classic failure mode is:
+The classic failure mode (fixed in L<Perl 5.14.0|perl5140delta/"Exception Handling">) is:
 
   sub Object::DESTROY {
     eval { ... }
@@ -406,9 +468,13 @@ be sure the C<eval> was aborted due to an error:
 This is because an C<eval> that caught a C<die> will always return a false
 value.
 
-=head1 SHINY SYNTAX
+=head1 ALTERNATE SYNTAX
+
+Using Perl 5.10 you can use L<perlsyn/"Switch statements"> (but please don't,
+because that syntax has since been deprecated because there was too much
+unexpected magical behaviour).
 
-Using Perl 5.10 you can use L<perlsyn/"Switch statements">.
+=for stopwords topicalizer
 
 The C<catch> block is invoked in a topicalizer context (like a C<given> block),
 but note that you can't return a useful value from C<catch> using the C<when>
@@ -431,7 +497,7 @@ concisely match errors:
 =item *
 
 C<@_> is not available within the C<try> block, so you need to copy your
-arglist. In case you want to work with argument values directly via C<@_>
+argument list. In case you want to work with argument values directly via C<@_>
 aliasing (i.e. allow C<$_[1] = "foo">), you need to pass C<@_> by reference:
 
   sub foo {
@@ -469,7 +535,21 @@ Instead, you should capture the return value:
     my $success = try {
       die;
       1;
+    };
+    return unless $success;
+
+    say "This text WILL NEVER appear!";
+  }
+  # OR
+  sub parent_sub_with_catch {
+    my $success = try {
+      die;
+      1;
     }
+    catch {
+      # do something with $_
+      return undef; #see note
+    };
     return unless $success;
 
     say "This text WILL NEVER appear!";
@@ -485,11 +565,13 @@ C<try> introduces another caller stack frame. L<Sub::Uplevel> is not used. L<Car
 will not report this when using full stack traces, though, because
 C<%Carp::Internal> is used. This lack of magic is considered a feature.
 
+=for stopwords unhygienically
+
 =item *
 
 The value of C<$_> in the C<catch> block is not guaranteed to be the value of
 the exception thrown (C<$@>) in the C<try> block.  There is no safe way to
-ensure this, since C<eval> may be used unhygenically in destructors.  The only
+ensure this, since C<eval> may be used unhygienically in destructors.  The only
 guarantee is that the C<catch> will be called if an exception is thrown.
 
 =item *
@@ -535,9 +617,9 @@ confusing behavior:
     }
   }
 
-Note that this behavior was changed once again in L<Perl5 version 18
-|https://metacpan.org/module/perldelta#given-now-aliases-the-global-_>.
-However, since the entirety of lexical C<$_> is now L<considired experimental
+Note that this behavior was changed once again in
+L<Perl5 version 18|https://metacpan.org/module/perldelta#given-now-aliases-the-global-_>.
+However, since the entirety of lexical C<$_> is now L<considered experimental
 |https://metacpan.org/module/perldelta#Lexical-_-is-now-experimental>, it
 is unclear whether the new version 18 behavior is final.
 
@@ -581,25 +663,10 @@ issues with C<$@>, but you still need to localize to prevent clobbering.
 I gave a lightning talk about this module, you can see the slides (Firefox
 only):
 
-L<web.archive.org/web/20100628040134/http://nothingmuch.woobling.org/talks/takahashi.xul>
+L<http://web.archive.org/web/20100628040134/http://nothingmuch.woobling.org/talks/takahashi.xul>
 
 Or read the source:
 
 L<http://web.archive.org/web/20100305133605/http://nothingmuch.woobling.org/talks/yapc_asia_2009/try_tiny.yml>
 
-=head1 VERSION CONTROL
-
-L<http://github.com/nothingmuch/try-tiny/>
-
-=head1 AUTHOR
-
-Yuval Kogman E<lt>nothingmuch@woobling.orgE<gt>
-
-=head1 COPYRIGHT
-
-  Copyright (c) 2009 Yuval Kogman. All rights reserved.
-  This program is free software; you can redistribute
-  it and/or modify it under the terms of the MIT license.
-
 =cut
-