shedd
shedd

Reputation: 4218

'Goto undefined subroutine &main::1' writing a simple Perl debugger

I'm trying to write a simple Perl debugger and I'm running into the following problem.

I'm running the following code as a debugger:

{
  package DB;

  sub DB { }

  sub sub
  {
    &$sub;

    # this is what produces the problem
    $i = 1*1;
  }
}

1;

I'm loading this in by setting the PERL5DB environment variable - e.g.:

export PERL5DB="BEGIN { require './debugger/tracer.pl'; }

Given this simple little Perl script:

#!/usr/bin/env perl

use Getopt::Long;

print "hello world";

I'm running the script as:

perl -d test.pl

When run, it generates the following error:

$ perl -d test.pl
Goto undefined subroutine &main::1 at /home/vagrant/perl5/perlbrew/perls/perl-5.16.0/lib/site_perl/5.16.0/Exporter.pm line 25.
BEGIN failed--compilation aborted at test.pl line 6.

I've isolated the problem to anything that is run after the &$sub; call in sub in the debugger. This problem is happening with certain packages being included in the base Perl script - in this case, Getopt::Long, though I've also found the same result with IO::File.

My Perl is pretty rusty, particularly with respect to advanced topics like the debugger.

Can anyone help me understand how I can get code executing after the &$sub; call in sub in the debugger to place nicely with the packages that I'm importing?

Thanks!

Upvotes: 2

Views: 1810

Answers (2)

vasanthela
vasanthela

Reputation: 21

Adding on to the answer from Ilmari Karonen.

DB::sub can also be called in a no value (void) context, therefore the return handling needs to take this into account. Refer to the documentation in wantarray for more details.

The following code handles all three cases.

package DB {
    sub DB { }
    sub sub {

        # call &sub, save the return value in @rv  
        my @rv;          
        if(defined(wantarray)) {
          @rv = (wantarray ? &$sub : scalar &$sub);
        }
        else {
          # wantarray is undef
          &$sub;
        }

        # after invoking &$sub

        # return @rv
        if(defined(wantarray)) {
          return (wantarray ? @rv : $rv[0]);
        }
        else {
          return undef
        }
    }
}

1;

Upvotes: 2

Ilmari Karonen
Ilmari Karonen

Reputation: 50328

When you leave a Perl subroutine without using an explicit return statement, Perl will return the value of the last statement in the subroutine.

In particular, this means that if you have a subroutine that calls another subroutine as its last statement, like this:

package DB {
    sub sub {
        warn "Hello from DB::sub, about to call $sub\n";
        &$sub;
    }
}

then the return value of the other subroutine called via &$sub will be passed to the original caller, just as if you'd done an explicit return &$sub.

However, if the &$sub call is not the last thing in your DB::sub subroutine, then Perl will just throw away its return value and instead return the value of you actual last statement — in this case $i = 1*1, which evaluates to the number 1.

Now, when you define a custom debugger like that, Perl will wrap every ordinary subroutine call with a call to your DB::sub subroutine. Thus, your code causes every subroutine call to return the number 1! It's hardly a surprise that this will break a lot of things very badly.

Specifically, based on your error message, it looks like something in the Exporter module (which is used by many other modules to export symbols to the caller's namespace) is calling a subroutine that should return a reference to another subroutine. But since, because of your debugger, it's actually returning 1, the following attempt to call the returned subroutine ends up trying to call a subroutine named 1 (which gets mapped to the main:: package because numeric symbol names are superglobal), which then fails.


But what if you really need to do something in your DB::sub after calling &$sub? Well, the workaround is to save the return value, like this:

package DB {
    sub DB { }
    sub sub {
        warn "Hello from DB::sub, about to call $sub...\n";

        # call &sub, save the return value in @rv
        my @rv = (wantarray ? &$sub : scalar &$sub);

        warn "Hello again from DB::sub, just called $sub and got @rv!\n";

        # ...and return the saved return value
        return (wantarray ? @rv : $rv[0]);
    }
}

1;

(The code is slightly complicated by the fact that our DB::sub might be called in either list or scalar context, and we need to pass the appropriate context on to &$sub. The wantarray should take care of that, though.)

Upvotes: 6

Related Questions