Good day fellow monastery dwellers! ;-)

This is my first attempt at implementing an exception handling mechanism similar to how it is done in Java/C++ yet compatible with existing eval/die Perl mechanism.

Read my code comments for more info. I'd be happy to receive any kind of constructive feedback. Note, that this is not my attempt to replace any existing Exception handling packages/classes. I'm simply extending the one that I had used in my for some time. Frankly, based on a number of searches that I'd done on CPAN there were only a handful of somewhat similar packages, however, they didn't meet my needs... The purpose for thise effort, therefore, may be to find another 'alternative' approach at error handling. Anyhow, just read the comments to get a better idea at what's going on here ;-)

Thank you in advance for your support.

Exception.pm
package Exception; # DESCRIPTION: # # An attempt at writing an exception handling package # implementing exception handling mechanism similar # to those present in Java/C++. This package # (and the Exception class it implements) may also # be used to encapsulate any piece of code still relaying # on try/die as a means to track program failures. # For example, catch() method is coded to recognize # and deal with die strings ($@ scalar). There are # other approaches at implementing similar exception # mechanism where $@ would hold an exception object. # This package, however, avoids storing # object refs in $@ (e.g. via die $object) in order # to be reverse compatible with code relaying on # $@ being a scalar (conventional try/die mechanism). # Such may be a case if the code implementing/utilizing # this Exception package is used inside a different # package/program that has no knowledge of the Exception # package. # # Here're a few examples of how Exception package could # be used inside your code: # # sub foo { # for ((my $i=10;$i>-10;$i--) { # print "Result: ". (10/$i) ."\n"; # } # } # # (by the way, i _do_ hate having to see # that 'sub' word right after try... # since there's no other way to # pass a block of code as an argument other than # via the anonymous sub/closure) # # try sub { # foo(); # }; # # # catch _all_ exceptions here # if (my $exception = catch()) { # # will log error and quit. # $exception->fatal(); # } # # Note: relating to the use of 'try sub { ... };' construct you # may replace it in favor of 'eval { }' unless you require special # exception handling (which may only be done witih the try() sub, # at least in the future implementations ;-) # # Therefore an alternative code might look something like this: # eval { # foo(); # }; # # # catch all exceptions # if (my $exception = catch()) { # # will log error and quit. # $exception->fatal(); # } # # # EXAMPLE 1: # # All these examples dealt with exceptions caused # by an 'uncontrolled' (meaning none of the Exeption # methods was invoked to cause them) die. However, # lets also look at how throw() works. # # use Exception; # # sub foo { # for ((my $i=10;$i>-10;$i--) { # if ($i == 0) { # # create and throw a new exception # throw new Exception, "Can't devide by 0!"; # } # print "Result: ". (10/$i) ."\n"; # } # } # # eval { # foo(); # }; # # # catch any exception (even that # # caused by an uncontrolled die) # if (catch()) { # $_->fatal(); # handle exception # } # # print "did ok\n"; # # EXAMPLE 2: # # Here's how throwable() might be used. # (note, sub foo() is unchanged) # # use Myexception; # # eval { # # expecting exception 'MyException' # # (so, in case of an uncontrolled die() # # a 'MyException' object will be created) # # Note that throwable('Exception') is # # 'implied' by default even if throwable() # # is not invoked here explicitly # # since any die should cause an exception. # # # throwable('MyException'); # foo(); # }; # # # catch Myexception exception object # # note: this object is infact created # # inside the catch() method # # based on value set by throwable(). # # This will only happen if there was an # # uncontrolled die! That is, if # # throwable() was followed by a # # throw() somewhere inside the same # # eval{} block, an exception object # # created by throw() will be generated # # and returned by this catch(). # if (catch()) { # $_->fatal(); # handle exception # } # # More examples are coming soon... # (I still keep most of them in my head) # use Devel::StackTrace; require Exporter; @ISA = qw(Exporter); @EXPORT = qw(try throw catch throwable); $::trace_depth = 10; $::error_text = ""; # $@ # Recent exception (works similar to $@ for die()) # Cleared only after it's caught. $::recent = undef; # Name of an exception to expect in case # of an uncontrolled (without explicit throw()) die. $::throwable = __PACKAGE__; @::stack_items = qw(package filename line subroutine hasargs wantarray evaltext is_require hints bitmask); sub new { # new() may be invoked via an existing object # like: $exception_object->new() # or via package name # like: new Exception() # or: Exception::new() # Therefore, ref() will return object's # class name if first case is true; otherwise, # name of this package will be used (from $_[0]) my $class = ref($_[0]) || $_[0]; $DB::single = 1; my $self = { # text of exception (optional) info => $_[1], # i figure this data is useful to have? time => CORE::time(), pid => $$, uid => $<, euid => $>, gid => $(, egid => $), stack => new Devel::StackTrace(ignore_package => __PACKA +GE__), }; bless($self, $class); } # standard accessor to property 'info' sub info { return $_[0]->{info} if wantarray; $_[0]->{info} = $_[1]; } ################################# ## SUBS THAT MAY BE REDEFINED ################################# sub trace_as_string { $DB::single = 1; my $self = shift; my $trace_str; $trace_str = $self->{stack}->as_string(); return $trace_str; } sub as_string { my $self = shift; return "\nEXCEPTION!!" . "\nINFO:\n " . $self->{info} . "\nTRACE:\n " . $self->trace_as_string() . "\n"; } # fatal(error code) # # Cause program to die! Print exception information # to STDERR and exit with given error code # or 1 by default. sub fatal { my ($self, $errcode) = ($_[0], $_[1] || 1); print STDERR $self->as_string(); exit($errcode); } ################################# ## CLASS METHODS ################################# # catch(list of exception names) # sub catch { # $@ should always be set (either via # die() or throw()) return undef unless $@; $::error_text = $@; $@ = ""; unless ($::recent) { # if no 'throw()' was invoked # $::recent will not not be set # and therefore, we have to check the # $@ variable set by an uncontrolled # die(). In such case, # we'll create a default throwable # exception (set with throwable()) # $DB::single = 1; $::throwable ||= __PACKAGE__; $::recent = eval('new '.caller().'::'. $::throwable .'()'); if ($@) { die "Failed to initialize '$::throwable' exception!\n" ."Check that throwable('YourClassName') refers to existing cl +ass.\n"; } $::recent->info($::error_text); } # look at list of exceptions that # have to be caught and return # recent exception if a match is found. # Default throwable exception name is used if no other # list of exceptions to look for was specified (in the # argument list) # my $recent_type = ref($::recent); for (@_ || $::throwable) { return get_recent() if $recent_type =~ m/$_/; } # rethrow uncaught exception throw($::recent); } sub clear_recent { $::recent = undef; } sub get_recent { my $bug = $::recent; $::recent = undef; $::throwable = ""; return $bug; } # try a block of code (anonymous sub) # do nothing more (for now) than a simple # eval. sub try { # return if not dealing with code! return unless (ref $_[0] eq 'CODE'); # make sure that the sub is executed # in its own (native) package! my $package = ref $_[0]; eval "package $package; " . '$_[0]->();'; } # should receive a reference to an Exception # object sub throw { $_[0]->info($_[1]) if @_ == 2; # save exception, which will be examined # later inside catch() $::recent ||= $_[0]; # cause die to interrupt program # (normally this will simply cause # eval {} inside try() to return # having $@ set to die string!) die $_[0]->info(); } sub throwable { # implements: # eval { # throwable 'Exception'; # ... # } # this tells which exception object # should be created on an uncontrolled die. # Any throw() statements inside # the block are still valid and may be # used to throw other than the default # exception. # $::throwable = $_[0]; } 1;


"There is no system but GNU, and Linux is one of its kernels." -- Confession of Faith

Replies are listed 'Best First'.
Re: Exception handling (alternatives)
by danger (Priest) on Dec 21, 2001 at 13:20 UTC
    From your code:
    # (by the way, i _do_ hate having to see # that 'sub' word right after try... # since there's no other way to # pass a block of code as an argument other than # via the anonymous sub/closure) # # try sub { # foo(); # };

    You can use prototypes to enable passing sub-refs without the 'sub' keyword. See, for example, the perlsub manpage, and/or some of the other exception handling modules:Exception::Class, Error, and the older Exception for variations on try{}catch{} examples (actually, Exception::Class doesn't implement try/catch directly but you can use it in conjunction with Error.pm (according to the docs) to get that syntax with it). The (&) prototype is one of the few useful uses of prototypes (in certain circumstances).

      Great suggestion there! ;-).

      I'll take a look. I sort of suspected that there should have been something to help me out in that case (to eliminate the need of using 'sub' word).



      "There is no system but GNU, and Linux is one of its kernels." -- Confession of Faith
        Here's what I've been able to dig up by running perldoc perlsub. This piece of code is exactly to the point in my case. Again, thanks for pointing me in the right direction! ;-) cheers.
        sub try (&@) { my($try,$catch) = @_; eval { &$try }; if ($@) { local $_ = $@; &$catch; } } sub catch (&) { $_[0] } try { die "phooey"; } catch { /phooey/ and print "unphooey\n"; };


        "There is no system but GNU, and Linux is one of its kernels." -- Confession of Faith
Re: Exception handling (alternatives)
by perrin (Chancellor) on Dec 21, 2001 at 20:12 UTC
    Frankly, based on a number of searches that I'd done on CPAN there were only a handful of somewhat similar packages, however, they didn't meet my needs... The purpose for thise effort, therefore, may be to find another 'alternative' approach at error handling.

    Can you explain a little about how your approach differs from Error? At first glance, I don't see much difference.

      It definitely is a very decent package. However, it doesn't seem to handle plain die() correctly (or at least the way I expect it to)
      try { die "foobar"; } catch { print "failed\n"; exit; } print "ok";
      After running this code (having included Error.pm etc.), I do see the "failed" string, however, I can't seem to be able to see the die string ("foobar"). Here's what the package dump (via perldb "V Error" command) looks inside catch {} clause:
      $("" = 'stringify' $(0+ = 'value' $Debug = 1 $VERSION = 0.15 $Depth = 0 $() = 1 %OVERLOAD = ( 'dummy' => 1 )
      Clearly, the Error package doesn't seem to have a record of the original die string. For my intents and purposes I need just that since I have to wrap my exception handling code around older code that relies on eval/die as it's exception handling mechanism.

      Despite of this, there does seem to be a (lengthy) workaround this problem:
      try { eval { die "foobar"; }; if ($@) { throw Error::Bad($@); } } catch Error::Bad with { my $err = shift; print "Failed: $err\n"; exit; } print "ok";
      However, even this code doesn't work for some reason =/. Here's what I get:
      okCan't use string ("1") as a HASH ref while "strict refs" in use at E +rror.pm line 183.
      I would be greatful if someone could explain me how this should be made to work ;-).

      Thank you for pointing to Error.pm, however. Looking at it as is, I think I could simply modify it a bit here and there to fit my needs (and hopefully make it useful to others who are in a like position).

      "There is no system but GNU, and Linux is one of its kernels." -- Confession of Faith
        You can catch normal die stuff with Error. I used it to catch DBI errors with the RaiseError flag on.

        try { die "foobar"; } catch Error with { my $error = shift; print "failed: $error\n"; exit; }; print "ok";

        Make sure you don't leave off the last semi-colon after the catch block, or it won't work.

Re: Exception handling (alternatives)
by thraxil (Prior) on Dec 21, 2001 at 23:59 UTC

    i'm happy to see anyone working on this kind of stuff. i've played around with the exception handling modules on CPAN and recently ended up writing my own which is very similar to what you have but is more focused on being a layered error handling system similar to what is found in large dbms's. (not clean enough to show off yet though)

    i'm not really sure that this or any of the ones in CPAN really offer anything more than syntactic sugar over the regular eval/die technique. what i'd really like to see is a pragma or something that gives us the real powerful part of java's exception handling: compile-time checking. for those who aren't familiar with how it works in java, if any code in the try block can possibly throw an exception, the compiler forces you to put an appropriate catch() in place. it gets to be a pain sometimes when you use a class method that potentially throws lots of exceptions because you have to explicitly catch them all, but it does a good job of keeping runtime errors out of your program.

    anders pearson

Re: Exception handling (alternatives)
by impossiblerobot (Deacon) on Dec 21, 2001 at 22:28 UTC
    I hope I'm not (merely) betraying my ignorance here, but what (other than making Perl look like some other languages) does this have over the normal Perl error-handling methods?

    (I couldn't really tell from your examples.)

    There are probably some things you can do with this that would be difficult the normal Perl way. But not knowing Java, etc., I'm afraid I can't see them (without some help).

    Impossible Robot
      (Strange... I think I submitted this node once. But since I can't see it here, I'll try to repost. Pardon me if i'm wrong :))

      Let me try to help your ignorance ;-).

      First, OO-ish style of error handling (where objects such as Error are involved etc.) is a concept easily understood by anyone who's ever sinned with any other language than Perl. I confess to having had a lot of dealing with Java (and continue to do so until even now) and C/C++. Therefore, dealing with similar (in terms of look and logic) error handling mechanism in Perl is something natural to me. It is not to say, however, that I lack appreciation for the good old eval/die construct. These are perfectly good in their own right.

      Further, OO-ish approach allows for a number of other 'conveniences' (if I could put it that way...). To name a few, I could for example encapsulate error handling code inside an easy to understand/work with Error class. For example, i could write an Error class that would write to a special Log file and optionally even email certain party of interest (such as sysadmin...) on any error. Having tucked this into would also clear my main program code and make the main logic easy to understand. There also may be certain errors that might have to be handled in a particular way. For certain purposes creation of separate Error type classes is well warranted. Say, you could write an Error::DBIFail exception class to handle rall back procedures in case of a DBI fail (I know DBI may provide for a basic rallback mechanism... yet that may not always suffice the need).

      I hope my ran was helpful? ;)

      "There is no system but GNU, and Linux is one of its kernels." -- Confession of Faith
        Thanks, vladb. I understand that making the exception-handling look like other languages can increase the comfort level. But I still don't see the advantages over 'eval'ing and calling an error-handling subroutine (which could be separated from the main program and do the things you mentioned).

        I'm not trying to be cantankerous, but as I attempt to increase my Perl skills (as well as possibly needing to learn Java soon), this seems to be an important issue.

        Maybe it's just the fact that although I think I understand OO programming, I don't get it. :-)

        Impossible Robot