DB-Hooks

 view release on metacpan or  search on metacpan

lib/DB/Hooks.pm  view on Meta::CPAN

		$msg[1] .=  " D>$rand";
		DB::say @msg;
	}

	my @res;
	{
		# IT: 2+2;e;
		# This will require Data::Dump which triggers postpone method to be called
		# and reenter the debugger. So we should check recurse when DB::dbcall is made:
		# TODO? Should we move this into DB::new?
		DB::start_dd   if DB::state( 'dbcall' );


		# NOTICE: Old value of $DB::single/trace/signal is stored by constructor
		# and restored when we leave this block
		my $dd_frame =  DB::new( $ctx );


		# Here we control debugger debugging (also see DB::process)
		$^D |= (1<<30)   if DB::state( 'dd' );

		DB::state( dbcall => 1 +$level );
		defined wantarray? @res= &$sub : &$sub;
		DB::state( dbcall => undef );

		# $^D &= ~(1<<30)   if DB::state( 'dd' ); # TODO??? Should we turn off flag at the end
	}

	($DB::after_dbcall->(), undef $DB::after_dbcall)   if $DB::after_dbcall;

	DB::say '<-- dbcall'. " " .DB::_c( DB::flags, '1;90' ) ." D<$rand"   if $ddd &32;
	return defined wantarray? @res : ();
}


# NOTICE:
# In theory any additional pakcage usage may break user's code
# because this usage cause packages to be loaded in different order under debugger
# in compare to the order they are loaded without it
use Sub::Metadata qw/ mutate_sub_is_debuggable /;


## The debugger instance
mutate_sub_is_debuggable( \&state, 0 );
sub state {
	my( $name, $value ) =  @_;
	# During a global destruction if something was loaded before the debugger it will be
	# destroyed after the debugger. Calls to `DESTROY` will still fire DB::sub, but the
	# debugger does not exists already, thus @DB::state is empty
	# TODO: We can undefine DB::sub if last instance was destroyed instead of frequently
	# checks here
	return @_>1 ? $DB::state[-1]{ ddd } =  $value : $DB::state[-1] && $DB::state[-1]{ ddd } // 0
	   if $name eq 'ddd';

	my $hash =  $DB::state[$DB::instance];

	my $ddd =  $hash->{ ddd } // 0;
	# Track changes to DB::state  OR  reads from DB::state
	if( $ddd&4 && @_>1  ||  $ddd&8 && @_<=1 ) {
		my( $sub ) =  (caller(1))[3];
		my( $file, $line ) =  (caller)[1,2];
		my $old_value =  vis_undef( $hash->{ $name } );
		my $new_value =  @_ > 1 ? ' -> ' .vis_undef( $value ) : '';

		DB::say "Access from $sub ($file:$line) to "
			.DB::_c( $name, 36 ) ." state($DB::instance): "
			.DB::_c( "$old_value$new_value", '3;37' )
	}

	return $hash->{ $name }   unless @_ > 1;
	delete $hash->{ $name }, return   unless defined $value;

	$DB::trace = $value   if $name eq 'trace';
	return $hash->{ $name } =  $value;

	$name =  '*'   unless exists $DB::variables{ $name };
	return $DB::variables{ $name }( @_ );
}



sub int_vrbl {
	my( $name, $value ) =  @_;

	if( @_ > 1 ) {
		${ "DB::$name" } =  $value;
	}

	return dbg_vrbl( @_ );
}



sub dbg_vrbl {
	my( $name, $value ) =  @_;

	# TODO: Assert the change to saved 'context'
	# We should not rewrite saved context otherwise we restore to wrong context
	# Access to context(0) state: ARRAY -> ARRAY

	my $hash =  $DB::state[-1];
	if( @_ > 1 ) {
		defined $value
			? $hash->{ $name } =  $value
			: delete $hash->{ $name };
	}

	return $hash->{ $name };
}



sub new {
	my $ctx =  shift;

	my( $ddd, $t, $dd ) =  0;
	if( @DB::state ) { # There is no debugger states if debugger loading is in progress
		# Save internal flags when we enter debugger
		DB::state( context =>  $ctx        );
		DB::state( single  =>  $DB::single );
		DB::state( trace   =>  $DB::trace  );

lib/DB/Hooks.pm  view on Meta::CPAN

	# Find testcase for this condition
	if( @DB::state ) { # There is no debugger states if main script was terminated
		$DB::single =  DB::state( 'single' ) // 0;
		$DB::trace  =  DB::state( 'trace'  ) // 0;
		DB::say 'Debug flag is restored: $single =  ' .$DB::single   if $ddd &32;

		my $ctx =  DB::state( 'context' );
		# WARNING: Do not keep any references to user's data eg. @_
		# Otherwise we postpone object desctruction process.
		DB::state( context => undef ); # $ctx->[0] =  undef # TODO: IT
		DB::finish_dd       if DB::state( 'xx' );
		# TODO: Flag should be restored from main frame if we do not continue debugging
		# TODO: IT; Compare with 090da7c: NOTICE: Create debugger instance only ...
		restore_context( $ctx );
	}
}



# Parse x=y pairs. Options in different format are skipped. Eg. ::Name will be handled
# later by ::import.
sub parse_options {
	my @opts =  split ' ', $ENV{ PERLDB_OPTS }   if defined $ENV{ PERLDB_OPTS };

	for( @opts, @_ ) {
		if( /^([:\w]+)=(.*)/ ) {
			$DB::options{ $1 } =  $2;
		}
		else {
			$DB::options{ $_ } =  1;
		}
	}
}



# This sub applies options from $DB::options to DB::state
# NOTICE: This sub is called twice: at compile time and before run time of 'main' package
sub apply_options {
	$DB::options{ DumpObjects } //=  1;
	if( $DB::options{ DumpObjects } ) {
		require Scalar::Util;
		Scalar::Util->import(qw/ reftype blessed /);
		mutate_sub_is_debuggable( \&DB::reftype, 0 );
		mutate_sub_is_debuggable( \&DB::blessed, 0 );
	}


	my $ct =  shift // '';
	# { ddd } flag should be first to see debugging messages for next
	for( qw/ ddd dd trace TraceLoad TraceGoto TraceCall TraceStack TraceReturn / ) {
		DB::state( $_ =>  $DB::options{ "${ct}$_" } );
	}

	# When we are going to work at white_box we must activate black_box
	# because white_box can be activated only from black_box (see DB::sub)
	if( $DB::options{ "${ct}white_box" } ) {
		DB::state( black_box_active => 1 );
	}

	# We may check if (caller)[3] eq 'import' to distinguish CT and RT
	$^P &= ~0x20               if $DB::options{ NonStop };
	DB::state( 'single', 1 )   if $DB::options{ Stop }; # IT: commit:1191c38
}



sub _leave {
	my $class =  shift;

	# Now debugger and all required modules are loaded (except Descendant).
	# We should inspect %DB::options and setup corresponding perl debugger
	# *internal* values
	# When <DEBUGGER>::import returns the next OP will be first OP from main::

	# We are leaving debugger
	my $handler; $handler =  DB::on( return => sub{
		#TODO? Should be call this from 'postpone'?
		return   unless $_[0] eq $class .'::import';

		DB::unsubscribe( return => $handler );
		$DB::after_dbcall = sub{
			my $dbg =  DB::new;              # Create RT debugger
			$DB::instance++;
			# NOTICE: Circular reference is here.
			# The debugger instance exists until main script is terminated
			DB::state( debugger => $dbg );   # Make debugger instance accessable via state
			#TODO? Probably we want apply options as soon as possible, but keep an eye to
			# DB::instance, because we may apply them to a wrong instance.
			apply_options();

			# TODO: Because RT and CT options are differ on command line
			# We may leave CT debugger alive for history reasons
			# Delete CT debugger
			push @DB::state, shift @DB::state; # Swap RT and CT debuggers
			DB::state( debugger => undef );
			$DB::instance--;


			no strict 'refs'; no warnings 'redefine';
			*{ "DB::on" }          =  \&{ "DB::RT_on" };
			*{ "DB::unsubscribe" } =  \&{ "DB::RT_unsubscribe" };

			DB::say DB::c_( "RT debugger initialized:" , '1;32' ), DB::dumper \@DB::state
			   if DB::state( 'ddd' ) &16;
		};

		return;
	});
}



## Evaluation in usercontext
# We put code here to execute it only once
my $usercontext; BEGIN {
($usercontext =  <<'CODE') =~ s#^\t##gm;
	BEGIN {
		( $^H, ${^WARNING_BITS}, my $hr ) =  @{ DB::state( 'context' )->[0] }[8..10];
		%^H =  %$hr   if $hr;
	}



( run in 0.344 second using v1.01-cache-2.11-cpan-ad19def0cd9 )