Devel-Events-Objects

 view release on metacpan or  search on metacpan

lib/Devel/Events/Generator/Objects.pm  view on Meta::CPAN

#!/usr/bin/perl

package Devel::Events::Generator::Objects;

my $SINGLETON;

BEGIN {
	# before Moose or anything else is parsed, we overload CORE::GLOBAL::bless
	# this will divert bless to an object of our choosing if that variable is filled with something

	*CORE::GLOBAL::bless = sub {
		if ( defined $SINGLETON ) {
			return $SINGLETON->bless(@_);
		} else {
			_core_bless(@_);
		}
	}
}

sub _core_bless {
	my ( $data, $class ) = @_;
	$class = caller(1) unless defined $class;
	CORE::bless($data, $class);
}

use Moose;

with qw/Devel::Events::Generator/;

use Carp qw/croak/;
use Variable::Magic qw/cast getdata/;
use Scalar::Util qw/reftype blessed weaken/;

use B qw/svref_2object CVf_CLONED/;

{
	no warnings 'redefine';

    # for some reason this breaks at compile time
    # we need this version to preserve errors though
	# hopefully no bad calls to bless() are made during the loading of Moose

	*_core_bless = sub {
		my ( $data, $class ) = @_;
		$class = caller(1) unless defined $class;

		my ( $object, $e );
		
		{
			local $@;
			$object = eval { CORE::bless($data, $class) };
			$e = $@;
		}

		unless ( $e ) {
			return $object;
		} else {
			my $line = __LINE__ - 7;
			my $file = quotemeta(__FILE__);

			$e =~ s/ at $file line $line\.\n$//o;

			croak($e);
		}
	};
}

sub enable {
	my $self = shift;
	$SINGLETON = $self;
	weaken($SINGLETON);
}

sub disable {
	$SINGLETON = undef;
}

sub bless {
	my ( $self, $data, $class ) = @_;
	$class = caller(1) unless defined $class;

	my $old_class = blessed($data);

	my $object = _core_bless( $data, $class );

	require Carp::Heavy;
	my $i = Carp::short_error_loc();
	my ( $pkg, $file, $line ) = caller($i);

	$self->object_bless(
		$object,
		class     => $class,
		old_class => $old_class,
		'package' => $pkg,
		file      => $file,
		line      => $line,
	);

	return $object;
}


sub object_bless {
    my ( $self, $object, @args ) = @_;

	my $tracked = $self->track_object($object);

	$self->send_event( object_bless => object => $object, tracked => $tracked, @args );
}

sub object_destroy {
	my ( $self, $object, @args ) = @_;

	$self->send_event( object_destroy => object => $object, @args );

	$self->untrack_object( $object );
}

use constant tracker_magic => Variable::Magic::wizard(
	free => sub {
		my ( $object, $objs ) = @_;
		local $@;
		foreach my $self ( grep { defined } @{ $objs || [] } ) {
			eval { $self->object_destroy( $object ) } # might disappear in global destruction
		}
	},
	data => sub {
		my ( $object, $self ) = @_;
		return $self;
	},
);

sub track_object {
	my ( $self, $object ) = @_;


	my $objects;

	# blech, any idea how to clean this up?

	my $wiz = $self->tracker_magic($object);

	if ( reftype $object eq 'SCALAR' ) {
		$objects = getdata( $$object, $wiz )
			or cast( $$object, $wiz, ( $objects = [] ) );
	} elsif ( reftype $object eq 'HASH' ) {
		$objects = getdata ( %$object, $wiz )
			or cast( %$object, $wiz, ( $objects = [] ) );
	} elsif ( reftype $object eq 'ARRAY' ) {
		$objects = getdata ( @$object, $wiz )
			or cast( @$object, $wiz, ( $objects = [] ) );
	} elsif ( reftype $object eq 'GLOB' or reftype $object eq 'IO' ) {
		$objects = getdata ( *$object, $wiz )
			or cast( *$object, $wiz, ( $objects = [] ) );
	} elsif ( reftype $object eq 'CODE' ) {
		unless ( svref_2object($object)->CvFLAGS & CVf_CLONED ) {
			# can't track it if it never gets garbage collected
			return;
		} else {
			$objects = getdata ( &$object, $wiz )
				or cast( &$object, $wiz, ( $objects = [] ) );
		}
	} else {
		die "patches welcome";
	}

	unless ( grep { $_ eq $self } @$objects ) {
		push @$objects, $self;
		weaken($objects->[-1]);
	}

	return 1;
}

sub untrack_object {
	my ( $self, $object );

	return;
}


__PACKAGE__;

__END__

=pod

=head1 NAME

Devel::Events::Generator::Objects - Generate events for C<bless>ing and
destruction of objects.

=head1 SYNOPSIS

	use Devel::Events::Generator::Objects; # must be loaded before any code you want to instrument

	my $g = Devel::Events::Generator::Objects->new(
		handler => $h,
	);

	$g->enable(); # only one Objects generator may be enabled at a time

	$code->(); # objects being created and destroyed cause events to be generated

	$g->disable();

=head1 DESCRIPTION

This module overrides C<CORE::GLOBAL::bless> on load. The altered version will
delegate back to the original version until an instance of a generator is enabled.

When a generator is enabled (only one L<Devel::Events::Generator::Objects>
instance may be enabled at a time. Use L<Devel::Events::Handler::Multiplex> to
dup events to multiple listeners), the overridden version of C<bless> will
cause an C<object_bless> event to fire, and will also attach magic to the
object to keep track of it's destruction using L<Variable::Magic>.

When the object is freed by the interpreter an C<object_destroy> event is
fired. Unfortunately by this time C<perl> has already unblessed the object in
question, so in order to keep track of the class you must associate it yourself
with the reference address.

L<Devel::Events::Handler::ObjectTracker> contains a detailed usage example.

=head1 EVENTS

=over 4

=item object_bless



( run in 1.232 second using v1.01-cache-2.11-cpan-d8267643d1d )