in reply to Re^4: Net::SSH2 test connection to remote host
in thread Net::SSH2 test connection to remote host

relying on $@ to determine failure can trigger both false positives and false negatives

Can you explain how?

The usual way to handle errors from functions that die to signal them is:

eval { do_whatever() }; if ($@) { # handle error ... }
or if you want to use the value returned by do_whatever():
my $res = eval { do_whatever() }; if ($@) { # handle error ... } # use $res here;
I believe that the 1 usage comes from...
eval { do_whatever(); 1 } or print "failed!\n";

Replies are listed 'Best First'.
Re^6: Net::SSH2 test connection to remote host
by JavaFan (Canon) on Sep 30, 2008 at 21:20 UTC
    The problem is that eval { } makes a scope for the statements to be executed. On scope exit, code may be executed (for instance, DESTROY methods). That code can clear $@, or set it - it may even call eval itself.

    That's the reason you cannot trust $@ to be set if, and only if, the eval failed.

    Here's an example.

    #!/usr/bin/perl use 5.010; use strict; use warnings; use Test::More tests => 3; eval {my $x = bless [], 'one'}; is $@, ""; eval {my $x = bless [], 'two'}; is $@, ""; eval {my $x = bless [], 'three'; die "foo\n";}; is $@, "foo\n"; __END__ 1..3 ok 1 ok 2 ok 3
    Now we change the code by adding some DESTROY methods - and note that this is the only change in the code.
    #!/usr/bin/perl use 5.010; use strict; use warnings; use Test::More tests => 3; eval {my $x = bless [], 'one'}; is $@, ""; eval {my $x = bless [], 'two'}; is $@, ""; eval {my $x = bless [], 'three'; die "foo\n";}; is $@, "foo\n"; sub one::DESTROY {1;} sub two::DESTROY {$@ = "foo";} sub three::DESTROY {$@ = "";} __END__ 1..3 ok 1 not ok 2 # Failed test at /tmp/test line 11. # got: 'foo' # expected: '' not ok 3 # Failed test at /tmp/test line 12. # got: '' # expected: 'foo # ' # Looks like you failed 2 tests of 3.
    As you can see, after the second eval $@ is set where you don't expect it to be (the eval succeeded), and while the third eval died, $@ is false.

    And note that while I did direct assignment to $@ in the DESTROY methods, I could also have used evals in the DESTROY methods to create false positive or negatives.

      In my opinion, DESTROY methods should not have side effects like changing $@ (or $? or $!), and if they do, it is a bug!

      And I don't see that programming defensively against this kind of bugs is the right thing to do. It just complicates the code unnecessarily. The only reason I see to do that is when the DESTROY method is outside your control (i.e. in a package from CPAN).

      But if it's your own code, just...

      sub DESTROY { local $@; ... }
        In my opinion, DESTROY methods should not have side effects like changing $@ (or $? or $!), and if they do, it is a bug!

        That's an opinion shared by others, but other people disagree. See for instance a thread about this on p5p this summer. Note that if DESTROY wouldn't propagate changes to $@, $! or $? DESTROY would be different from other functions, because $@, $! and $? behave differently. It also means you can have code that throws an exception, but afterwards $@ isn't set (your local $@ suffers from the same problem):

        sub one::DESTROY { die "foo\n"; } eval {my $x = bless [], 'one'}; say "\$\@ = $@"; __END__ $@ = (in cleanup) foo
        but
        sub one::DESTROY { local $@; die "foo\n"; } eval {my $x = bless [], 'one'}; say "\$\@ = $@"; __END__ $@ =
        This is far from ideal as well, and I doubt it's much better than the current situation.

        People have suggested a new variable, @@, where new errors get pushed onto, instead of having them override $@. But noone has written a patch to actually do this.

Re^6: Net::SSH2 test connection to remote host
by kyle (Abbot) on Sep 30, 2008 at 15:53 UTC