blazar has asked for the wisdom of the Perl Monks concerning the following question:

Not really a question, but not a meditation, either. Just reporting an interesting clpmisc thread (link @ GG). Someone may have to add something after all.

croak/confess from within File::Find (Paul Lalli)

I've been staring at this and playing with variations for over an hour now. Can someone help me out? My goal is to be able to use croak() from within a subroutine that is called by the &wanted subroutine which is passed to File::Find::find(). I want the error message printed as a result of this croak() to list the line number of the call to the final subroutine. Here is a short-but-complete script to demonstrate the problem I'm having:

#!/usr/bin/perl use strict; use warnings; use Carp; use File::Find; die "Usage: $0 [croak|confess] [level]\n" unless @ARGV == 2; my ($which, $carplevel) = @ARGV; sub err { $Carp::CarpLevel = $carplevel; if ($which eq 'croak') { croak "You did something bad!"; #line 12 } else { confess "You did something bad!"; #line 14 } } sub wanted { err(); #line 19 } find(\&wanted, '.'); #line 22 __END__

So, find() is calling wanted() which is calling err() which is calling croak(). What I want is an error message saying: "You did something bad! at ff_carp.pl line 19". However:

$ ./ff_carp.pl croak 0 You did something bad! at /opt2/Perl5_8_4/lib/perl5/5.8.4/File/Find.pm line 810

Calling croak with a CarpLevel of 0 shows me where in File::Find the wanted() subroutine is being called.

$ ./ff_carp.pl croak 1 You did something bad! at ./ff_carp.pl line 22

But if I call croak with a CarpLevel of 1, I instead get where in my main file find() is being called.

So instead I tried looking at the whole stack trace, to see what's going wrong. I tried calling confess with a CarpLevel of 0:

$ ./ff_carp.pl confess 0 You did something bad! at ./ff_carp.pl line 14 main::err() called at ./ff_carp.pl line 19 main::wanted() called at /opt2/Perl5_8_4/lib/perl5/5.8.4/File/ Find.pm line 810 File::Find::_find_dir('HASH(0x13e82c)', ., 2) called at /opt2/ Perl5_8_4/lib/perl5/5.8.4/File/Find.pm line 690 File::Find::_find_opt('HASH(0x13e82c)', .) called at /opt2/ Perl5_8_4/lib/perl5/5.8.4/File/Find.pm line 1193 File::Find::find('CODE(0x1b66d0)', .) called at ./ff_carp.pl line 22

Now this is exactly what I'd expect. It's telling me the actual confess was called on line 14, inside the err() function that was called on line 19, inside the wanted() function that was called within File::Find.pm. Exactly. And furthermore, if I skip a level by setting CarpLevel to 1:

$ ./ff_carp.pl confess 1 You did something bad! at ./ff_carp.pl line 19 main::wanted() called at /opt2/Perl5_8_4/lib/perl5/5.8.4/File/ Find.pm line 810 File::Find::_find_dir('HASH(0x13e82c)', ., 2) called at /opt2/ Perl5_8_4/lib/perl5/5.8.4/File/Find.pm line 690 File::Find::_find_opt('HASH(0x13e82c)', .) called at /opt2/ Perl5_8_4/lib/perl5/5.8.4/File/Find.pm line 1193 File::Find::find('CODE(0x1b66d0)', .) called at ./ff_carp.pl line 22

Again, Exactly what I expect. I skipped a level, so the first level being printed is the source of the err() call, line 19.

Now - how do I get croak() to behave the same way? I don't want to print the entire stack trace, I just want it to tell me that the subroutine was called from line 19, like confess() does when I skip a level.

Thank you for any insight you can provide,
Paul Lalli

Re: croak/confess from within File::Find (Anno)

I've been staring at this and playing with variations for over an hour now. Can someone help me out? My goal is to be able to use croak() from within a subroutine that is called by the &wanted subroutine which is passed to File::Find::find(). I want the error message printed as a result of this croak() to list the line number of the call to the final subroutine. Here is a short-but-complete script to demonstrate the problem I'm having:

#!/usr/bin/perl use strict; use warnings; use Carp; use File::Find; die "Usage: $0 [croak|confess] [level]\n" unless @ARGV == 2; my ($which, $carplevel) = @ARGV; sub err { $Carp::CarpLevel = $carplevel; if ($which eq 'croak') { croak "You did something bad!"; #line 12 } else { confess "You did something bad!"; #line 14 } } sub wanted { err(); #line 19 }

Add this:

push our @CARP_NOT, 'File::Find';
find(\&wanted, '.'); #line 22 __END__

Carp has a hard time assigning an error location in callback situations. It climbs the stack, essentially watching for a change in the calling package. When called from a callback it meets that change earlier than intended (from your main to File::Find). The variable @CARP_NOT is checked and a package change is ignored if one of the packages is on the other's @CARP_NOT.

Anno

Replies are listed 'Best First'.
Re: croak/confess from within File::Find
by shmem (Chancellor) on Sep 12, 2007 at 12:46 UTC
    ++ for bringing that here, so I read it :-)

    I didn't know about @CARP_NOT, so I learned something new today.

    Someone may have to add something after all.

    Well, rather than staring for an hour I would have been pragmatic and used pragmas, and caller:

    sub err { $Carp::CarpLevel = $carplevel; if ($which eq 'croak') { my @c = $carplevel ? caller($carplevel-1) : (undef,__FILE__,__ +LINE__); die "You did something bad! at $c[1] line $c[2]\n"; } else { confess "You did something bad!"; #line 14 } }

    and croak/confess carplevels are consistent.

    It seems that setting @CARP_NOT makes croak do a confess.

    --shmem

    _($_=" "x(1<<5)."?\n".q·/)Oo.  G°\        /
                                  /\_¯/(q    /
    ----------------------------  \__(m.====·.(_("always off the crowd"))."·
    ");sub _{s./.($e="'Itrs `mnsgdq Gdbj O`qkdq")=~y/"-y/#-z/;$e.e && print}
Re: croak/confess from within File::Find
by blazar (Canon) on Sep 13, 2007 at 08:32 UTC

    D'Oh! There were other two interesting posts in the original thread, which settle down the question completely. I'm reporting them here as a reply, else I think the root node may become cluttered as it's late now for such thorough update. To Anno's reply Paul Lalli posted the followup hereafter:

    Re: croak/confess from within File::Find (Paul Lalli)

    Anno,

    Thank you for the information. Annoyingly, it seems that if all the packages are "trusted" (as per Carp's docs), then croak behaves exactly like confess:

    I suppose that's better than nothing. It just bugs me that there doesn't seem to be a way to just have it print where the subroutine was called from. I suppose I could fiddle with caller() to get exactly what I want, but doesn't it seem like carp/croak should be able to do this on its own?

    Paul Lalli

    Re: croak/confess from within File::Find (Anno)

    You can have that if you compile the wanted() function in a package of its own:

    { package CarpCatcher; use Carp; sub wanted { err(); #line 12 } sub err { croak ("You did something bad!"); } push our @CARP_NOT, 'File::Find'; } use File::Find; find(\&CarpCatcher::wanted, '.'); __END__

    Anno