How to (if possible) set a breakpoint to a specific closure?

Viewed 111

When using multiple instances of a specific closure, the Perl debugger lists the same line number range for each instance, while the closures are actually different. As the parameter to the debugger's b command is a line number, I wonder whether I can set a breakpoint to a specific closure.

Consider this interactive example:

  DB<13> sub _s($) { my $s = $_[0]; sub () { $s }; }
  DB<17> $s1 = _s(1)
  DB<18> $s2 = _s(2)
  DB<20> x $s1->()
0  1
  DB<21> x $s2->()
0  2
  DB<22> x $s1
0  CODE(0x55c7e8754958)
   -> &main::__ANON__ in 0
  DB<23> x $s2
0  CODE(0x55c7e8764990)
   -> &main::__ANON__ in 0

So the code references clearly indicate that $s1 and $s2 are different. Now could I set a breakpoint when $s1 is called?

Note

My first version tried sub s($) { ... }, resulting in syntax error ... (Might be a runaway multi-line ;; string starting on line 2) when trying to call s(1). So it seems you cannot really override s/old/new/ ;-)

Another Note

If you set a breakpoint on a line like

my $checker = sub ($) { ... };

inside a subroutine (using closures inside closures), then execution stops each time when $checker is being assigned to, but not when the closure is being called. My work-around for that was to insert line breaks like this (and set the breakpoint to the ... line):

my $checker = sub ($) {
    ...
};
3 Answers

Depending on your problem, a solution could be to create a "decorated" function that sets a fixed breakpoint (and assign it back to $s1).

DB<1> sub _s($) { my $s = $_[0]; sub () { $s }; }
DB<2> x $s1 = _s(1)
0  CODE(0x55c7dc96b3a8)
   -> &CODE(0x55c7dc96b3a8) in ???
DB<3> x $s1_nob = $s1
0  CODE(0x55c7dc96b3a8)
   -> &main::__ANON__ in 0
DB<4> x $s1 = sub {$DB::single=1; $s1_nob->()}
0  CODE(0x55c7dc9f1260)
   -> &main::__ANON__[(eval 30)[/usr/share/perl/5.26/perl5db.pl:738]:2] in (eval 30)[/usr/share/perl/5.26/perl5db.pl:738]:2-2
DB<6> x $s1->()
main::CODE(0x55c7dc9f1260)((eval 30)[/usr/share/perl/5.26/perl5db.pl:738]:2):
2:        $s1 = sub {$DB::single=1; $s1_nob->()};
DB<<7>> s
0  1

Based on the answer of @Georg Mavridis I tried this:

  DB<1> sub _s($) { my $s = $_[0]; sub () { $s }; }

  DB<24> sub DEBUG($;$) { my ($f, $d) = @_; sub { $DB::single = $d; $f->() } }

  DB<27> x DEBUG(_s(1), 1)->()
main::CODE(0x5647d76c16d0)((eval 30)[/usr/lib/perl5/5.26.1/perl5db.pl:738]:2):
2:  sub DEBUG($;$) { my ($f, $d) = @_; sub { $DB::single = $d; $f->() } };
  DB<<28>> n
0  1
  DB<28> x DEBUG(_s(1), 0)->()
0  1

The ugly part is that DEBUG calls the closure inside, so it must know the parameters of the closure. So I redefined it, shuffling the parameters):

  DB<7> sub DEBUG($$;@) { my ($d, $f, @args) = @_; $DB::single = $d; $f->(@args) }

  DB<8> x DEBUG(1, _s(42))
main::DEBUG((eval 12)[/usr/lib/perl5/5.26.1/perl5db.pl:738]:2):
2:  sub DEBUG($$;@) { my ($d, $f, @args) = @_; $DB::single = $d; $f->(@args) };
  DB<<9>> n
0  42
  DB<9> x DEBUG(0, _s(42))
0  42

Now you use the first parameter to define single-stepping, the second parameter is the closure, and arguments following are passed to the closure.

But still this requires a code modification (replacing $f->($a, $b, $c) with DEBUG($d, $f, $a, $b, $c)) when the closure is called. Still not pretty.

It seems you (the debugger) cannot set a breakpoint to a closure, even it has a name (continuing with the code from the question):

  DB<48> *{main::xxx} = _s(33)

  DB<49> x xxx
0  33
  DB<50> *{main::yyy} = _s(44)

  DB<51> x yyy
0  44
  DB<52> b xxx
Subroutine main::xxx not found.
  DB<55> x ref \&xxx
0  'CODE'
  DB<56> sub zzz() { 22 }  ### let's compare with a "regular" subroutine

  DB<57> x zzz
0  22
  DB<58> x ref \&zzz
0  'CODE'

I guess Perl needs an enhancement request.

Related