X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=blobdiff_plain;f=lib%2FDBIx%2FClass%2F_Util.pm;h=76f9b358941c64344ed3eb4d0cf2673ab3171f72;hb=3605497bcb83ef83a4859a84e52c03f77f3cd626;hp=a713ee7db8ff0840594e2ff8ed2f777f2ab537ad;hpb=10be570e51ef741ead5f0e8d5ceca78499a8965c;p=dbsrgits%2FDBIx-Class.git diff --git a/lib/DBIx/Class/_Util.pm b/lib/DBIx/Class/_Util.pm index a713ee7..76f9b35 100644 --- a/lib/DBIx/Class/_Util.pm +++ b/lib/DBIx/Class/_Util.pm @@ -6,7 +6,7 @@ use DBIx::Class::StartupCheck; # load es early as we can, usually a noop use warnings; use strict; -use constant SPURIOUS_VERSION_CHECK_WARNINGS => ( "$]" < 5.010 ? 1 : 0); +my $mro_recursor_stack; BEGIN { package # hide from pause @@ -15,15 +15,23 @@ BEGIN { use Config; use constant { + PERL_VERSION => "$]", + OS_NAME => "$^O", + }; + + use constant { # but of course - BROKEN_FORK => ($^O eq 'MSWin32') ? 1 : 0, + BROKEN_FORK => (OS_NAME eq 'MSWin32') ? 1 : 0, + + BROKEN_GOTO => ( PERL_VERSION < 5.008003 ) ? 1 : 0, - BROKEN_GOTO => ( "$]" < 5.008003 ) ? 1 : 0, + # perl -MScalar::Util=weaken -e 'weaken( $hash{key} = \"value" )' + BROKEN_WEAK_SCALARREF_VALUES => ( PERL_VERSION < 5.008003 ) ? 1 : 0, HAS_ITHREADS => $Config{useithreads} ? 1 : 0, - UNSTABLE_DOLLARAT => ( "$]" < 5.013002 ) ? 1 : 0, + UNSTABLE_DOLLARAT => ( PERL_VERSION < 5.013002 ) ? 1 : 0, ( map # @@ -42,17 +50,85 @@ BEGIN { ), IV_SIZE => $Config{ivsize}, - - OS_NAME => $^O, }; - if ( "$]" < 5.009_005) { + if ( PERL_VERSION < 5.009_005) { require MRO::Compat; constant->import( OLD_MRO => 1 ); + + # + # Yes, I know this is a rather PHP-ish name, but please first read + # https://metacpan.org/source/BOBTFISH/MRO-Compat-0.12/lib/MRO/Compat.pm#L363-368 + # + # Even if we are using Class::C3::XS it still won't work, as doing + # defined( *{ "SubClass::"->{$_} }{CODE} ) + # will set pkg_gen to the same value for SubClass and *ALL PARENTS* + # + *DBIx::Class::_Util::get_real_pkg_gen = sub ($) { + require Digest::MD5; + require Math::BigInt; + + # the non-assign-unless-there-is-a-hash is deliberate + ( $mro_recursor_stack->{cache} || {} )->{$_[0]}{gen} ||= ( + Math::BigInt->new( '0x' . ( Digest::MD5::md5_hex( join "\0", map { + + ( $mro_recursor_stack->{cache} || {} )->{$_}{methlist} ||= do { + + my $class = $_; + no strict 'refs'; + + # RV to be hashed up and turned into a number + join "\0", ( + $class, + map + {( + # stringification should be sufficient, ignore names/refaddr entirely + $_, + attributes::get( $_ ), + )} + map + {( + # skip dummy C::C3 helper crefs + ! ( ( $Class::C3::MRO{$class} || {} )->{methods}{$_} ) + and + ( + ref(\ "${class}::"->{$_} ) ne 'GLOB' + or + defined( *{ "${class}::"->{$_} }{CODE} ) + ) + ) + ? ( \&{"${class}::$_"} ) + : () + } + keys %{ "${class}::" } + ); + } + } ( + + @{ + ( $mro_recursor_stack->{cache} || {} )->{$_[0]}{linear_isa} + ||= + mro::get_linear_isa($_[0]) + }, + + (( + ( $mro_recursor_stack->{cache} || {} )->{$_[0]}{is_universal} + ||= + mro::is_universal($_[0]) + ) ? () : @{ + ( $mro_recursor_stack->{cache} || {} )->{UNIVERSAL}{linear_isa} + ||= + mro::get_linear_isa("UNIVERSAL") + } ), + + ) ) ) ) + ); + }; } else { require mro; constant->import( OLD_MRO => 0 ); + *DBIx::Class::_Util::get_real_pkg_gen = \&mro::get_pkg_gen; } # Both of these are no longer used for anything. However bring @@ -74,6 +150,8 @@ BEGIN { sub PEEPEENESS () { &$sigh } } +use constant SPURIOUS_VERSION_CHECK_WARNINGS => ( DBIx::Class::_ENV_::PERL_VERSION < 5.010 ? 1 : 0); + # FIXME - this is not supposed to be here # Carp::Skip to the rescue soon use DBIx::Class::Carp '^DBIx::Class|^DBICTest'; @@ -84,6 +162,7 @@ use Storable 'nfreeze'; use Scalar::Util qw(weaken blessed reftype refaddr); use Sub::Quote qw(qsub); use Sub::Name (); +use attributes (); # Already correctly prototyped: perlbrew exec perl -MStorable -e 'warn prototype \&Storable::dclone' BEGIN { *deep_clone = \&Storable::dclone } @@ -92,7 +171,7 @@ use base 'Exporter'; our @EXPORT_OK = qw( sigwarn_silencer modver_gt_or_eq modver_gt_or_eq_and_lt fail_on_internal_wantarray fail_on_internal_call - refdesc refcount hrefaddr set_subname + refdesc refcount hrefaddr set_subname describe_class_methods scope_guard detected_reinvoked_destructor is_exception dbic_internal_try visit_namespaces quote_sub qsub perlstring serialize deep_clone dump_value uniq @@ -102,16 +181,10 @@ our @EXPORT_OK = qw( use constant UNRESOLVABLE_CONDITION => \ '1 = 0'; -BEGIN { - # add preliminary attribute support - # FIXME FIXME FIXME - # To be revisited when Moo with proper attr support ships - Sub::Quote->VERSION(2.002); - require attributes; -} # Override forcing no_defer, and adding naming consistency checks +our %refs_closed_over_by_quote_sub_installed_crefs; sub quote_sub { - Carp::confess( "Anonymous quoting not supported by the DBIC sub_quote override - supply a sub name" ) if + Carp::confess( "Anonymous quoting not supported by the DBIC quote_sub override - supply a sub name" ) if @_ < 2 or ! defined $_[1] @@ -119,16 +192,27 @@ sub quote_sub { length ref $_[1] ; - Carp::confess( "The DBIC sub_quote override expects sub name '$_[0]' to be fully qualified" ) - unless $_[0] =~ /::/; + Carp::confess( "The DBIC quote_sub override expects sub name '$_[0]' to be fully qualified" ) + unless (my $stash) = $_[0] =~ /^(.+)::/; + + Carp::confess( + "The DBIC sub_quote override does not support 'no_install'" + ) if ( + $_[3] + and + $_[3]->{no_install} + ); - Carp::confess( "The DBIC sub_quote override expects the sub name '$_[0]' to match the supplied 'package' argument" ) if + Carp::confess( + 'The DBIC quote_sub override expects the namespace-part of sub name ' + . "'$_[0]' to match the supplied package argument '$_[3]->{package}'" + ) if ( $_[3] and defined $_[3]->{package} and - index( $_[0], $_[3]->{package} ) != 0 - ; + $stash ne $_[3]->{package} + ); my @caller = caller(0); my $sq_opts = { @@ -142,28 +226,23 @@ sub quote_sub { no_defer => 1, }; - my $cref = Sub::Quote::quote_sub( $_[0], $_[1], $_[2]||{}, $sq_opts ); - - # FIXME FIXME FIXME - # To be revisited when Moo with proper attr support ships - if( - # external application does not work on things like :prototype(...), :lvalue, etc - my @attrs = grep { - $_ !~ /^[a-z]/ + weaken ( + # just use a growing counter, no need to perform neither compaction + # nor any special ithread-level handling + $refs_closed_over_by_quote_sub_installed_crefs + { scalar keys %refs_closed_over_by_quote_sub_installed_crefs } + = $_ + ) for grep { + length ref $_ + and + ( + ! DBIx::Class::_ENV_::BROKEN_WEAK_SCALARREF_VALUES or - Carp::confess( "The DBIC sub_quote override does not support applying of reserved attribute '$_'" ) - } @{ $sq_opts->{attributes} || []} - ) { - Carp::confess( "The DBIC sub_quote override does not allow mixing 'attributes' with 'no_install'" ) - if $sq_opts->{no_install}; - - # might be different from $sq_opts->{package}; - my ($install_into) = $_[0] =~ /(.+)::[^:]+$/; - - attributes->import( $install_into, $cref, @attrs ); - } + ref $_ ne 'SCALAR' + ) + } values %{ $_[2] || {} }; - $cref; + Sub::Quote::quote_sub( $_[0], $_[1], $_[2]||{}, $sq_opts ); } sub sigwarn_silencer ($) { @@ -575,6 +654,166 @@ sub modver_gt_or_eq_and_lt ($$$) { ) ? 1 : 0; } +{ + # FIXME - should be a private my(), but I'm too uncertain whether + # all bases are covered + our $describe_class_query_cache; + + sub describe_class_methods { + my ($class, $requested_mro) = @_; + + croak "Expecting a class name" + if not defined $class or $class !~ $module_name_rx; + + $requested_mro ||= mro::get_mro($class); + + # mro::set_mro() does not bump pkg_gen - WHAT THE FUCK?! + my $query_cache_key = "$class|$requested_mro"; + + my $stack_cache_key = + ( mro::get_mro($class) eq $requested_mro ) + ? $class + : $query_cache_key + ; + + # use a cache on old MRO, since while we are recursing in this function + # nothing can possibly change (the speedup is immense) + # (yes, people could be tie()ing the stash and adding methods on access + # but there is a limit to how much crazy can be supported here) + # + # we use the cache for linear_isa lookups on new MRO as well - it adds + # a *tiny* speedup, and simplifies the code a lot + # + local $mro_recursor_stack->{cache} = {} + unless $mro_recursor_stack->{cache}; + + my $my_gen = 0; + + $my_gen += get_real_pkg_gen($_) for ( my @full_ISA = ( + + @{ + $mro_recursor_stack->{cache}{$stack_cache_key}{linear_isa} + ||= + mro::get_linear_isa($class, $requested_mro) + }, + + (( + $mro_recursor_stack->{cache}{$class}{is_universal} + ||= + mro::is_universal($class) + ) ? () : @{ + $mro_recursor_stack->{cache}{UNIVERSAL}{linear_isa} + ||= + mro::get_linear_isa("UNIVERSAL") + }), + + )); + + my $slot = $describe_class_query_cache->{$query_cache_key} ||= {}; + + unless ( ($slot->{cumulative_gen}||0) == $my_gen ) { + + # remove ourselves from ISA + shift @full_ISA; + + # reset + %$slot = ( + class => $class, + isa => [ + @{ $mro_recursor_stack->{cache}{$stack_cache_key}{linear_isa} } + [ 1 .. $#{$mro_recursor_stack->{cache}{$stack_cache_key}{linear_isa}} ] + ], + mro => { + type => $requested_mro, + is_c3 => ( ($requested_mro eq 'c3') ? 1 : 0 ), + }, + cumulative_gen => $my_gen, + ); + + # ensure the cache is populated for the parents, code below can then + # efficiently operate over the query_cache directly + describe_class_methods($_) for reverse @full_ISA; + + no strict 'refs'; + + # combine full ISA-order inherited and local method list into a + # "shadowing stack" + + ( + unshift @{ $slot->{methods}{$_->{name}} }, $_ + + and + + ( + $_->{via_class} ne $class + or + $slot->{methods_defined_in_class}{$_->{name}} = $_ + ) + + and + + @{ $slot->{methods}{$_->{name}} } > 1 + + and + + $slot->{methods_with_supers}{$_->{name}} = $slot->{methods}{$_->{name}} + + ) for ( + + # what describe_class_methods for @full_ISA produced above + ( map { values %{ + $describe_class_query_cache->{$_}{methods_defined_in_class} || {} + } } map { "$_|" . mro::get_mro($_) } reverse @full_ISA ), + + # our own non-cleaned subs + their attributes + ( map { + ( + # need to account for dummy helper crefs under OLD_MRO + ( + ! DBIx::Class::_ENV_::OLD_MRO + or + ! ( ( $Class::C3::MRO{$class} || {} )->{methods}{$_} ) + ) + and + # these 2 OR-ed checks are sufficient for 5.10+ + ( + ref(\ "${class}::"->{$_} ) ne 'GLOB' + or + defined( *{ "${class}::"->{$_} }{CODE} ) + ) + ) ? { + via_class => $class, + name => $_, + attributes => { + map { $_ => 1 } attributes::get( \&{"${class}::${_}"} ) + }, + } + : () + } keys %{"${class}::"} ) + ); + + + # recalculate the pkg_gen on newer perls under Taint mode, + # because of shit like: + # perl -T -Mmro -e 'package Foo; sub bar {}; defined( *{ "Foo::"->{bar}}{CODE} ) and warn mro::get_pkg_gen("Foo") for (1,2,3)' + # + if ( + ! DBIx::Class::_ENV_::OLD_MRO + and + ${^TAINT} + ) { + + $slot->{cumulative_gen} = 0; + $slot->{cumulative_gen} += get_real_pkg_gen($_) + for $class, @full_ISA; + } + } + + # RV + +{ %$slot }; + } +} + # # Why not just use some higher-level module or at least File::Spec here? @@ -686,12 +925,29 @@ sub fail_on_internal_call { ; }; + my @fr2; + # need to make allowance for a proxy-yet-direct call + my $check_fr = ( + $fr->[0] eq 'DBIx::Class::ResultSourceProxy' + and + @fr2 = (CORE::caller(2)) + and + ( + ( $fr->[3] =~ /([^:])+$/ )[0] + eq + ( $fr2[3] =~ /([^:])+$/ )[0] + ) + ) + ? \@fr2 + : $fr + ; + if ( $argdesc and - $fr->[0] =~ /^(?:DBIx::Class|DBICx::)/ + $check_fr->[0] =~ /^(?:DBIx::Class|DBICx::)/ and - $fr->[1] !~ /\b(?:CDBICompat|ResultSetProxy)\b/ # no point touching there + $check_fr->[1] !~ /\b(?:CDBICompat|ResultSetProxy)\b/ # no point touching there ) { DBIx::Class::Exception->throw( sprintf ( "Illegal internal call of indirect proxy-method %s() with argument '%s': examine the last lines of the proxy method deparse below to determine what to call directly instead at %s on line %d\n\n%s\n\n Stacktrace starts",