I Bless You in the Name of the Stringified Object

A co-worker came to me today with a curious error message:

use DateTime;
my $date = DateTime->new( year => 2013, month => 4, day => 15 );
$date->set_time_zone("Australia/Sydney");
print $date->today;'

This code gives the error Can't locate object method "_normalize_nanoseconds" via package "2013-04-15T00:00:00" at /usr2/local/perlbrew/perls/perl-5.16.3/lib/site_perl/5.16.3/x86_64-linux-thread-multi/DateTime.pm line 252.

The package "2013-04-15T00:00:00" is the curious part: It looks like a stringified DateTime, but who could possibly be stringifying a DateTime object and then using that as a package name?

It turns out the problem is at $date->today. today() is a constructor, constructors are class methods, constructors inevitably call bless on the class they are invoked with. But, we called this constructor with an object instance (a blessed reference), not a class.

In an object without overloads, this fails (as expected) with an error message: Attempt to bless into a reference. But DateTime overloads stringification. So instead of trying to use an object as a package name, the object gets stringified and that string gets used as a package name.

This problem could be mitigated by checking for refs in the constructor, dying as a result. I'm not sure if it would be a good idea to disallow stringification during a call to bless, for historical reasons (it's not a bug if it's working as expected, it is a bug if a change breaks code).

Here's some sample code to play around with the problem. Enable/disable the overload (comment out the use overload (...)) and see what changes:

use strict;
use warnings;

package Foo;

use overload (
    q{""} => sub { return "Bar" },
);

sub new {
    my ( $class ) = @_;
    return bless {}, $class;
}

sub greet {
    print "Hello, World!\n";
}

package main;

my $obj = Foo->new;
$obj->greet;

my $clone = $obj->new;
$clone->greet;

4 Comments

I will add this to the list of reasons I hate Perl 5's incredibly broken overloading (especially for strings and numbers).

Patches for DateTime would be very welcome. I think it'd make sense to have the constructor explicitly check to make sure that it receives a class name, not an object.

One other thing (on the subject of overloading in DateTime) that I've found useful at $work is to be able to overload addition and subtraction to work with seconds as well as ::Duration objects. No idea whether that's actually been implemented in the official code -- at $work we use a subclass of DateTime with a variety of (occasionally pretty hackish and idiosyncratic) DWIMmery. I'll get working on a clean-room implementation of the non-insane bits, and submit it to the RT.

In fact, it's not even worth a trip to the RT. It looks like it's this simple...


49a50
> use Scalar::Util qw( blessed );
199a201
> $class = blessed($class) || $class;
1594a1597,1601
>
> if ( "$dur" =~ /([+-]?(?:\d+\.)?\d+(?:e[+-]?(?:\d+\.)?\d+)?)/i) {
> return $dt->clone->add_seconds($1);
> }
>
1599c1606
< . " Only a DateTime::Duration object can "
---
> . " Only a DateTime::Duration object or a number of seconds can "
1621a1629,1632
> if ( "$dur" =~ /([+-]?(?:\d+\.)?\d+(?:e[+-]?(?:\d+\.)?\d+)?)/i) {
> return $dt->clone->subtract_seconds($1);
> }
>
1627c1638
< . " Only a DateTime::Duration or DateTime object can "
---
> . " Only a DateTime::Duration or DateTime object or a number of seconds can "

I'm wondering if a "::" overload makes sense, specially if a future version of perl would allow for blessing into an anonymous stash.

Leave a comment

About preaction

user-pic