Caleb Cushing's Blog

Using ref to fix 5 year old bug

So I haven’t been hacking perl for 5 (or more) years but I forked Template::ShowStartStop from Template::Timer which is that old. since I forked it this test has bugged me since I didn’t really understand the test, the section of code it referred to or the actual problem.

This is an approximation of the error you’d get.

Couldn’t render template “undef error - Can’t call method “name” on unblessed reference at /usr/lib/perl5/site_perl/5.8.0/Template/Timer.pm line 66.

This error is actually a copy from bug #15457 but I happen to know it’s basically the same error you’d get with an eval.

So what’s the problem? can you spot it? ( original timer code )

my $template =
    ref($what) eq 'ARRAY'
        ? join( ' + ', @{$what} )
        : ref($what)
            ? $what->name
            : $what;

Ugh! That’s ugly, let’s take a cue from Perl Best Practices and format it as a tabular ternary instead.

my $template
    # conditional           # set $template to
    = ref($what) eq 'ARRAY' ? join( ' + ', @{$what} )
    : ref($what)            ? $what->name
    :                         $what
    ;

Now we can read it…

Bug #13225 suggests a problem with eval and the following fix (which I translated into something more similar to the current code).

my $template
    # conditional           # set $template to
    = ref($what) eq 'ARRAY'  ? join( ' + ', @{$what} )
    : ref($what) eq 'SCALAR' ? '(evaluated block)'
    : ref($what)             ? $what->name
    :                          $what
    ;

well that should fix our eval problem… since an eval will be a reference of type SCALAR. but what happens if it’s a reference not of type ARRAY or SCALAR? then we’ll still get that error.

According to the ref function documentation

If the referenced object has been blessed into a package, then that package name is returned instead. You can think of ref as a typeof operator.

what this means is I could check what type of object it was that I was normally getting. In order to do this I put $what into the output I get. I found out that most of the time $what is a Template::Document. So now I optimized my code for that situation.

my $template
    # conditional                        # set $template to
    = ref($what) eq 'Template::Document' ? $what->name
    : ref($what) eq 'ARRAY'              ? join( ' + ', @{$what} )
    : ref($what) eq 'SCALAR'             ? '(evaluated block)'
    :                                      $what
    ;

See the thing is that we really don’t want $what->name method to be called unless it’s a Template::Document which actually has that method. I’m not sure that I’m not missing any tests at this point, but I’m pretty confident that, at least, this kind of bug won’t crash my module anymore.


Share

comments powered by Disqus