OP_PRINT OP_PUSHMARK OP_RV2AV OP_GV NULL #### +OP_NULL +OP_OR OP_PRINT OP_PUSHMARK OP_RV2AV OP_GV NULL +OP_DIE +OP_PUSHMARK +OP_NULL +OP_GVSV #### use Data::Dumper 'Dumper'; use Devel::CodeDiff 'code_diff'; my @d = code_diff( sub { print @_ }, sub { print @_ or die $! } ); # Fixup the output so it is less verbose for ( @d ) { for ( @$_ ) { $_->[2] =~ /\[(.+)\]/ and $_->[2] = $1; $_ = join " ", @$_; } } print Dumper( @d ); #### $VAR1 = [ '- 3 OP_PRINT', '+ 3 OP_NULL', '- 4 OP_PUSHMARK', '+ 4 OP_OR', '- 5 OP_RV2AV', '+ 5 OP_PRINT', '- 6 OP_GV', '+ 6 OP_PUSHMARK', '- 7 NULL (0xae6) AV (0xae8) REFCNT 2 FLAGS 0xa ARRAY (FILL 1 MAX 3 OFF 0 AvFLAGS 2 Nullsv ', '+ 7 OP_RV2AV', '+ 8 OP_GV', '+ 9 NULL (0xaea) AV (0xaec) REFCNT 2 FLAGS 0xa ARRAY (FILL 1 MAX 3 OFF 0 AvFLAGS 2 Nullsv ', '+ 10 OP_DIE', '+ 11 OP_PUSHMARK', '+ 12 OP_NULL', '+ 13 OP_GVSV' ]; #### package Devel::CodeDiff; use strict; use warnings; use base 'Exporter'; use Algorithm::Diff qw( traverse_balanced ); use B qw( svref_2object class cstring sv_undef walkoptree ); use B::Utils qw(); use B::Asmdata qw( @specialsv_name ); use vars qw( $VERSION @EXPORT_OK %SIDES %ADDR %DONE_GV %LINKS @NODES ); $VERSION = '0.01'; @EXPORT_OK = 'code_diff'; sub code_diff { local %SIDES = ( a => { ADDR => {}, DONE_GV => {}, NODES => [], LINKS => {} }, b => { ADDR => {}, DONE_GV => {}, NODES => [], LINKS => {} } ); Algorithm::Diff::diff( [ as_string( a => svref_2object( $_[0] )->ROOT ) ], [ as_string( b => svref_2object( $_[1] )->ROOT ) ] ); } sub as_string { my $side = shift; my $op = shift; local *ADDR = $SIDES{ $side }{ 'ADDR' }; local *DONE_GV = $SIDES{ $side }{ 'DONE_GV' }; local *NODES = $SIDES{ $side }{ 'NODES' }; local *LINKS = $SIDES{ $side }{ 'LINKS' }; walkoptree( $op, 'CodeDiff_as_string' ); @NODES; } sub ADDR { return 0 if not $_[0]; 0xADD + ( exists $ADDR{$_[0]} ? $ADDR{$_[0]} : ( $ADDR{$_[0]} = scalar keys %ADDR ) ); } sub B::OP::CodeDiff_as_string { my ($op) = @_; return "<" if not $$op; my $class = class $op; bless $op, 'B::OP' if $class eq 'NULL'; $LINKS{ $op }{ 'sibling' } = ADDR( ${$op->sibling} ); push( @NODES, sprintf( "%s (0x%x)\n" . "op_sibling 0x%x\n" . "op_ppaddr %s\n" . "op_targ %d\n" . "op_type %d\n" . "op_flags %d\n" . "op_private %d\n", $class, ADDR( $$op ), ADDR( ${$op->sibling} ), $op->ppaddr, $op->targ, $op->type, $op->flags, $op->private ) ); } sub B::UNOP::CodeDiff_as_string { my ($op) = @_; $LINKS{$op}{'first'} = ADDR( ${$op->first} ); $op->B::OP::CodeDiff_as_string(), $NODES[-1] .= join( "", sprintf( "op_first 0x%x\n", ADDR( ${$op->first} ) ) ); } sub B::BINOP::CodeDiff_as_string { my ($op) = @_; $op->B::UNOP::CodeDiff_as_string(), $NODES[-1] .= join( "", sprintf( "op_last 0x%x\n", ADDR( ${$op->last} ) ) ); } sub B::LOOP::CodeDiff_as_string { my ($op) = @_; $op->B::BINOP::CodeDiff_as_string(), $NODES[-1] .= join( "", sprintf( "op_redoop 0x%x\n" . "op_nextop 0x%x\n" . "op_lastop 0x%x\n", ADDR( ${$op->redoop} ), ADDR( ${$op->nextop} ), ADDR( ${$op->lastop} ) ) ); } sub B::LOGOP::CodeDiff_as_string { my ($op) = @_; $op->B::UNOP::CodeDiff_as_string(), $NODES[-1] .= join( "", sprintf( "op_other 0x%x\n", ADDR( ${$op->other} ) ) ); } sub B::LISTOP::CodeDiff_as_string { my ($op) = @_; $op->B::BINOP::CodeDiff_as_string(), $NODES[-1] .= join( "", sprintf( "op_children %d\n", $op->children ) ); } sub B::PMOP::CodeDiff_as_string { my ($op) = @_; # push( @NODES, ... ) $op->B::LISTOP::CodeDiff_as_string(), $NODES[-1] .= join( "", sprintf( "op_pmreplroot 0x%x\n" . "op_pmreplstart 0x%x\n" . "op_pmnext 0x%x\n" . "op_pmregexp->precomp %s\n" . "op_pmflags 0x%x\n", ADDR( ${$op->pmreplroot} ), ADDR( ${$op->pmreplstart} ), ADDR( ${$op->pmnext} ), cstring($op->precomp), $op->pmflags ) ); $op->pmreplroot->CodeDiff_as_string; } sub B::COP::CodeDiff_as_string { my ($op) = @_; my $cop_io = class($op->io) eq 'SPECIAL' ? '' : $op->io->CodeDiff_as_string; # push( @NODES, ... ) $op->B::OP::CodeDiff_as_string(); $NODES[-1] .= join( "", sprintf( "cop_label %s\n" . "cop_stash_pv %s\n" . "cop_arybase %d\n" . "cop_warnings 0x%x\n" . "cop_io %s\n", $op->label, $op->stashpv, $op->arybase, ${$op->warnings}, cstring($cop_io) ) ); } sub B::SVOP::CodeDiff_as_string { my ($op) = @_; $op->B::OP::CodeDiff_as_string(), $NODES[-1] .= join( "", sprintf( "op_sv 0x%x\n", ADDR( ${$op->sv} ) ) ); $op->sv->CodeDiff_as_string; } sub B::PVOP::CodeDiff_as_string { my ($op) = @_; $op->B::OP::CodeDiff_as_string(), $NODES[-1] .= join( "", sprintf( "op_pv %s\n", cstring($op->pv) ) ); } sub B::PADOP::CodeDiff_as_string { my ($op) = @_; $op->B::OP::CodeDiff_as_string(), $NODES[-1] .= join( "", sprintf( "op_padix %ld\n", $op->padix ) ); } sub B::NULL::CodeDiff_as_string { my ($sv) = @_; push( @NODES, ( $$sv == ${sv_undef()} ? "&sv_undef\n" : sprintf( "NULL (0x%x)\n", ADDR( $$sv ) ) ) ); } sub B::SV::CodeDiff_as_string { my ($sv) = @_; $NODES[-1] .= ( $$sv ? sprintf( "%s (0x%x)\n" . "REFCNT %d\n" . "FLAGS 0x%x\n", class( $sv ), ADDR( $$sv ), $sv->REFCNT, $sv->FLAGS ) : class( $sv ) . " = NULL\n" ); } sub B::RV::CodeDiff_as_string { my ($rv) = @_; B::SV::CodeDiff_as_string($rv), $NODES[-1] .= join( "", sprintf( "RV 0x%x\n", ADDR( ${$rv->RV} ) ) ); $rv->RV->CodeDiff_as_string; } sub B::PV::CodeDiff_as_string { my ($sv) = @_; my $pv = $sv->PV(); $pv = '' if not defined $pv; $sv->B::SV::CodeDiff_as_string(), $NODES[-1] .= join( "", sprintf( "xpv_pv %s\n" . "xpv_cur %s\n", cstring($pv), length($pv) ) ); } sub B::IV::CodeDiff_as_string { my ($sv) = @_; $sv->B::SV::CodeDiff_as_string(), $NODES[-1] .= join( "", sprintf("xiv_iv %d\n", $sv->IV ) ); } sub B::NV::CodeDiff_as_string { my ($sv) = @_; $sv->B::IV::CodeDiff_as_string(), $NODES[-1] .= join( "", sprintf( "xnv_nv %s\n", $sv->NV ) ); } sub B::PVIV::CodeDiff_as_string { my ($sv) = @_; $sv->B::PV::CodeDiff_as_string(), $NODES[-1] .= join( "", sprintf( "xiv_iv %d\n", $sv->IV ) ); } sub B::PVNV::CodeDiff_as_string { my ($sv) = @_; $sv->B::PVIV::CodeDiff_as_string(), $NODES[-1] .= join( "", sprintf( "xnv_nv %s\n", $sv->NV ) ); } sub B::PVLV::CodeDiff_as_string { my ($sv) = @_; $sv->B::PVNV::CodeDiff_as_string(), $NODES[-1] .= join( "", sprintf( "xlv_targoff %d\n" . "xlv_targlen %u\n" . "xlv_type %s\n", $sv->TARGOFF, $sv->TARGLEN, cstring(chr($sv->TYPE)) ) ); } sub B::BM::CodeDiff_as_string { my ($sv) = @_; $sv->B::PVNV::CodeDiff_as_string(), $NODES[-1] .= join( "", sprintf( "xbm_useful %d\n" . "xbm_previous %u\n" . "xbm_rare %s\n", $sv->USEFUL, $sv->PREVIOUS, cstring(chr($sv->RARE)) ) ); } sub B::CV::CodeDiff_as_string { my ($sv) = @_; my ($stash) = $sv->STASH; my ($start) = $sv->START; my ($root) = $sv->ROOT; my ($padlist) = $sv->PADLIST; my ($gv) = $sv->GV; $sv->B::PVNV::CodeDiff_as_string(); $NODES[-1] .= join( "", sprintf( "STASH 0x%x\n" . "START 0x%x\n" . "ROOT 0x%x\n" . "GV 0x%x\n" . "DEPTH %d\n" . "PADLIST 0x%x\n" . "OUTSIDE 0x%x\n", ADDR( $$stash ), ADDR( $$start ), ADDR( $$root ), ADDR( $$gv ), $sv->DEPTH, ADDR( $padlist ), ADDR( ${$sv->OUTSIDE} ) ) ); $_->CodeDiff_as_string for grep $_, ( $gv, $padlist, $root, $start ); } sub B::AV::CodeDiff_as_string { my ($av) = @_; my(@array) = $av->ARRAY; $av->B::SV::CodeDiff_as_string, $NODES[-1] .= join( "", sprintf( "ARRAY (", join( ", ", map( "0x" . ADDR( $$_ ), @array) ), ")\n" ), sprintf( "FILL %d\n" . "MAX %d\n" . "OFF %d\n" . "AvFLAGS %d\n", scalar(@array), $av->MAX, $av->OFF, $av->AvFLAGS ) ); } sub B::GV::CodeDiff_as_string { my ($gv) = @_; if ($DONE_GV{$$gv}++) { $NODES[-1] .= sprintf "GV %s::%s\n", $gv->STASH->NAME, $gv->SAFENAME; return; } my ($sv) = $gv->SV; my ($av) = $gv->AV; my ($cv) = $gv->CV; $gv->B::SV::CodeDiff_as_string, $NODES[-1] .= join( "", sprintf( "NAME %s\n" . "STASH %s (0x%x)\n" . "SV 0x%x\n" . "GvREFCNT %d\n" . "FORM 0x%x\n" . "AV 0x%x\n" . "HV 0x%x\n" . "EGV 0x%x\n" . "CV 0x%x\n" . "CVGEN %d\n" . "GvFLAGS 0x%x\n", $gv->SAFENAME, $gv->STASH->NAME, ADDR( $gv->STASH ), ADDR( $$sv ), $gv->GvREFCNT, ADDR( $gv->FORM ), ADDR( $$av ), ADDR( ${$gv->HV} ), ADDR( ${$gv->EGV} ), ADDR( $$cv ), $gv->CVGEN, $gv->GvFLAGS ) ); $_->CodeDiff_as_string for grep $_, ( $sv, $av, $cv ); } sub B::SPECIAL::CodeDiff_as_string { my $sv = shift; $NODES[-1] .= join "", $specialsv_name[$$sv], "\n"; } 1; __END__ =head1 NAME Devel::CodeDiff - Produces diffs of perl code =head1 SYNOPSIS use Devel::CodeDiff 'code_diff'; use Data::Dumper 'Dumper'; print Dumper( code_diff( sub { print or die }, sub { print } ) ); =head1 DESCRIPTION =head2 EXPORT =head1 SEE ALSO =head1 AUTHOR =head1 COPYRIGHT AND LICENSE =cut