Set the line number and filename of string evals

Errors from a string eval can be tricky to track down since perl doesn’t tell you where the eval was. It treats each of the string evals as a separate, virtual file because it doesn’t remember where the string argument came from. Since perl compiles that during the run phase (see Know the phases of a Perl program’s execution), the information the compiler dragged along for filenames and line numbers is so longer around.

In general, a string eval is a dangerous and tricky feature, and most good Perl programmers will tell you not to use it (see Know the two different forms of eval). That, in general, is good advice. If you can reasonably get the job done without the string eval, you’re probably better off. Sometimes, however, you need the string eval, and for the rest of this Item I’ll assume you have a good reason.

Consider this bit of code that issues a warning and has a fatal error that the eval catches:

use v5.14;
use warnings;
say 'Hello';

eval q(
	my $string;
	print $string;
	
	die 'Dying in eval';
	);
print $@;

The output shows the warning and the error, and each has a line number. However, there’s no filename and the line number doesn’t correspond either to the actual line of the statement of the line of the eval:

Hello
Use of uninitialized value $string in say at (eval 1) line 3.
Dying in eval at (eval 1) line 5.

You can solve this with a preprocessor comment. Few people know that Perl looks for a couple of special pre-processor commands, but it’s right there in perlsyn. You can change what perl thinks the line number and filename are with a line that starts with # line. You follow that with the line number and the filename (optionally in double quotes):

use warnings;

my $string; # no value there!

print $string; # gets right line and file in warning

# line 58976 "Buster.pm"
print $string;

# line 65783 "Mimi.pm"
print $string;

# line 137
print $string;

Now, the warnings make it look like there are at least two modules having problems even though there is a single file. Without a filename in the command, perl uses the previous filename, as in the last line of this output:

Use of uninitialized value $string in print at line.pl line 5.
Use of uninitialized value $string in print at Buster.pm line 58976.
Use of uninitialized value $string in print at Mimi.pm line 65783.
Use of uninitialized value $string in print at Mimi.pm line 137.

You should do this only when Perl expects a new statement. If you place one of those pre-processor commands in the middle of a statement, odd things can happen:

use warnings;

my $string; # no value there!

print $string; # gets right line and file in warning

print 
# line 58976 "Buster.pm"
	$string;

# line 65783 "Mimi.pm"
print $string;

# line 137
print $string;

The output shows that perl changed the current filename to Buster.pm but did not change the line number. For that warning, the line number is the actual line number:

Use of uninitialized value $string in print at line.pl line 5.
Use of uninitialized value $string in print at Buster.pm line 7.
Use of uninitialized value $string in print at Mimi.pm line 65783.
Use of uninitialized value $string in print at Mimi.pm line 137.

Once you know that, you can use this in your string eval, which perl compiles just like any other Perl code, including this pre-processor command. You can use the special literals __FILE__ and __LINE__ (documented in perldata) to get the current values instead of hard-coding them. You can only use those as separate tokens, which makes their use a bit messy.

This code is the same as the first example in this Item, put with the addition of a pre-processor line as the first line in the eval string:

use v5.14;
use warnings;
say 'Hello';

eval '# line '. __LINE__ . ' "' . __FILE__ . qq("\n) . q(
	my $string;
	print $string;
	
	die 'Dying in eval';
	);
say $@;

Now the eval warnings and error messages show a filename and line number, and the line number corresponds to its actual location in the file:

Hello
Use of uninitialized value $string in print at eval.pl line 7.
Dying in eval at eval.pl line 9.

Note that changing the line number and filename also change __LINE__ and __FILE__, meaning there’s no way to get back to the actual line number and filename. As such, you probably only want to use these to represent the actual values:

use v5.14;
use warnings;
 
# line 58976 "Buster.pm"
say "line ", __LINE__, " file ", __FILE__
line 58976 file Buster.pm

Things to remember

  • String eval act like their own virtual file for line numbering
  • You can modify the current line number and filename with the # line pre-processor command
  • You can use # line in your string eval
  • The special literals __LINE__ and __FILE__ report the line number and filename.
Leave a comment

1 Comments.

  1. Try this but make sure the expression for my_eval starts in the same line as my_eval:

    sub my_eval {
      my ( $expr ) = @_;
      my ( undef, $file, $line ) = caller;
    
      eval "# line $line \"$file\"\n" . $expr;
    }
    
    my_eval q(
      my $string;
      print $string;
    
      die "Dying in eval";
    );
    

Leave a Reply


[ Ctrl + Enter ]

7ads6x98y