X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=blobdiff_plain;f=lib%2FDBIx%2FClass%2F_Util.pm;h=a8785c08c567ea216429c15897ac2bb37dd0948d;hb=b090048f6d8bd2cba0bae8ea7ec26459dd20dca8;hp=bb06ec2cb3a984f0c77bec711dbac6dea7def42e;hpb=3b0202245e84a09a41ac31a13b80547a300a227e;p=dbsrgits%2FDBIx-Class.git diff --git a/lib/DBIx/Class/_Util.pm b/lib/DBIx/Class/_Util.pm index bb06ec2..a8785c0 100644 --- a/lib/DBIx/Class/_Util.pm +++ b/lib/DBIx/Class/_Util.pm @@ -6,9 +6,10 @@ 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; +# For the love of everything that is crab-like: DO NOT reach into this +# The entire thing is really fragile and should not be screwed with +# unless absolutely and unavoidably necessary +our $__describe_class_query_cache; BEGIN { package # hide from pause @@ -17,15 +18,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 => ( "$]" < 5.008003 ) ? 1 : 0, + BROKEN_GOTO => ( PERL_VERSION < 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 # @@ -44,11 +53,9 @@ 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 ); @@ -64,51 +71,72 @@ BEGIN { require Digest::MD5; require Math::BigInt; + my $cur_class; + no strict 'refs'; + # the non-assign-unless-there-is-a-hash is deliberate - ( $mro_recursor_stack->{cache} || {} )->{$_[0]}{gen} ||= ( + ( $__describe_class_query_cache->{'!internal!'} || {} )->{$_[0]}{gen} ||= ( Math::BigInt->new( '0x' . ( Digest::MD5::md5_hex( join "\0", map { - ( $mro_recursor_stack->{cache} || {} )->{$_}{methlist} ||= do { + ( $__describe_class_query_cache->{'!internal!'} || {} )->{$_}{methlist} ||= ( + + $cur_class = $_ - my $class = $_; + and - no strict 'refs'; - my %methlist = + # RV to be hashed up and turned into a number + join "\0", ( + $cur_class, map - # this is essentially a uniq_by step - # it is crucial on OLD_MRO - {( Scalar::Util::refaddr($_) => $_ )} + {( + # stringification should be sufficient, ignore names/refaddr entirely + $_, + do { + my @attrs; + local $@; + local $SIG{__DIE__} if $SIG{__DIE__}; + # attributes::get may throw on blessed-false crefs :/ + eval { @attrs = attributes::get( $_ ); 1 } + or warn "Unable to determine attributes of coderef $_ due to the following error: $@"; + @attrs; + }, + )} map - { + {( + # skip dummy C::C3 helper crefs + ! ( ( $Class::C3::MRO{$cur_class} || {} )->{methods}{$_} ) + and ( - ref(\ "${class}::"->{$_} ) ne 'GLOB' + ref(\ "${cur_class}::"->{$_} ) ne 'GLOB' or - defined( *{ "${class}::"->{$_} }{CODE} ) + defined( *{ "${cur_class}::"->{$_} }{CODE} ) ) - ? ( \&{"${class}::$_"} ) + ) + ? ( \&{"${cur_class}::$_"} ) : () } - keys %{ "${class}::" } - ; - - # RV to be hashed up and turned into a number - join "\0", ( - $class, - map {( - $_, # refaddr is sufficient, ignore names entirely - @{ - ( $mro_recursor_stack->{cache} || {} )->{attrs}{$_} - ||= - [ attributes::get( $methlist{$_} ) ] - }, - )} sort keys %methlist - ), - } - } ( 'UNIVERSAL', @{ - ( $mro_recursor_stack->{cache} || {} )->{$_[0]}{linear_isa} - ||= - mro::get_linear_isa($_[0]) - } ) ) ) ) + keys %{ "${cur_class}::" } + ) + ) + } ( + + @{ + ( $__describe_class_query_cache->{'!internal!'} || {} )->{$_[0]}{linear_isa} + ||= + mro::get_linear_isa($_[0]) + }, + + (( + ( $__describe_class_query_cache->{'!internal!'} || {} )->{$_[0]}{is_universal} + ||= + mro::is_universal($_[0]) + ) ? () : @{ + ( $__describe_class_query_cache->{'!internal!'} || {} )->{UNIVERSAL}{linear_isa} + ||= + mro::get_linear_isa("UNIVERSAL") + } ), + + ) ) ) ) ); }; } @@ -123,13 +151,11 @@ BEGIN { # to be outfits with *COPY PASTED* pieces of lib/DBIx/Class/Storage/* # in their production codebases. There is no point in breaking these # if whatever they used actually continues to work - my $warned; my $sigh = sub { - - require Carp; - my $cluck = "The @{[ (caller(1))[3] ]} constant is no more - adjust your code" . Carp::longmess(); - - warn $cluck unless $warned->{$cluck}++; + DBIx::Class::_Util::emit_loud_diag( + skip_frames => 1, + msg => "The @{[ (caller(1))[3] ]} constant is no more - adjust your code" + ); 0; }; @@ -137,6 +163,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'; @@ -156,8 +184,9 @@ 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 describe_class_methods - scope_guard detected_reinvoked_destructor + refdesc refcount hrefaddr set_subname get_subname describe_class_methods + scope_guard detected_reinvoked_destructor emit_loud_diag + true false is_exception dbic_internal_try visit_namespaces quote_sub qsub perlstring serialize deep_clone dump_value uniq parent_dir mkdir_p @@ -166,15 +195,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); -} # 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] @@ -182,16 +206,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 = { @@ -205,28 +240,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 ($) { @@ -293,7 +323,14 @@ sub visit_namespaces { $visited_count; } -# FIXME In another life switch this to a polyfill like the one in namespace::clean +# FIXME In another life switch these to a polyfill like the ones in namespace::clean +sub get_subname ($) { + my $gv = B::svref_2object( $_[0] )->GV; + wantarray + ? ( $gv->STASH->NAME, $gv->NAME ) + : ( join '::', $gv->STASH->NAME, $gv->NAME ) + ; +} sub set_subname ($$) { # fully qualify name @@ -353,6 +390,75 @@ sub dump_value ($) { $dump_str; } +my $seen_loud_screams; +sub emit_loud_diag { + my $args = { ref $_[0] eq 'HASH' ? %{$_[0]} : @_ }; + + unless ( defined $args->{msg} and length $args->{msg} ) { + emit_loud_diag( + msg => "No 'msg' value supplied to emit_loud_diag()" + ); + exit 70; + } + + my $msg = "\n$0: $args->{msg}"; + + # when we die - we usually want to keep doing it + $args->{emit_dups} = !!$args->{confess} + unless exists $args->{emit_dups}; + + local $Carp::CarpLevel = + ( $args->{skip_frames} || 0 ) + + + $Carp::CarpLevel + + + # hide our own frame + 1 + ; + + my $longmess = Carp::longmess(); + + # different object references will thwart deduplication without this + ( my $key = "${msg}\n${longmess}" ) =~ s/\b0x[0-9a-f]+\b/0x.../gi; + + return $seen_loud_screams->{$key} if + $seen_loud_screams->{$key}++ + and + ! $args->{emit_dups} + ; + + $msg .= $longmess + unless $msg =~ /\n\z/; + + print STDERR "$msg\n" + or + print STDOUT "\n!!!STDERR ISN'T WRITABLE!!!:$msg\n"; + + return $seen_loud_screams->{$key} + unless $args->{confess}; + + # increment *again*, because... Carp. + $Carp::CarpLevel++; + + # not $msg - Carp will reapply the longmess on its own + Carp::confess($args->{msg}); +} + + +### +### This is *NOT* boolean.pm - deliberately not using a singleton +### +{ + package # hide from pause + DBIx::Class::_Util::_Bool; + use overload + bool => sub { ${$_[0]} }, + fallback => 1, + ; +} +sub true () { my $x = 1; bless \$x, "DBIx::Class::_Util::_Bool" } +sub false () { my $x = 0; bless \$x, "DBIx::Class::_Util::_Bool" } + sub scope_guard (&) { croak 'Calling scope_guard() in void context makes no sense' if ! defined wantarray; @@ -374,8 +480,9 @@ sub scope_guard (&) { 1; } or - Carp::cluck( - "Execution of scope guard $_[0] resulted in the non-trappable exception:\n\n$@" + DBIx::Class::_Util::emit_loud_diag( + emit_dups => 1, + msg => "Execution of scope guard $_[0] resulted in the non-trappable exception:\n\n$@\n " ); } } @@ -440,18 +547,16 @@ sub is_exception ($) { and length( my $class = ref $e ) ) { - carp_unique( sprintf( - "Objects of external exception class '%s' stringify to '' (the " + carp_unique( + "Objects of external exception class '$class' stringify to '' (the " . 'empty string), implementing the so called null-object-pattern. ' . 'Given Perl\'s "globally cooperative" exception handling using this ' . 'class of exceptions is extremely dangerous, as it may (and often ' . 'does) result in silent discarding of errors. DBIx::Class tries to ' . 'work around this as much as possible, but other parts of your ' . 'software stack may not be even aware of the problem. Please submit ' - . 'a bugreport against the distribution containing %s', - - ($class) x 2, - )); + . "a bugreport against the distribution containing '$class'", + ); $not_blank = 1; } @@ -564,10 +669,10 @@ sub is_exception ($) { for keys %$destruction_registry; if (! length ref $_[0]) { - printf STDERR '%s() expects a blessed reference %s', - (caller(0))[3], - Carp::longmess, - ; + emit_loud_diag( + emit_dups => 1, + msg => (caller(0))[3] . '() expects a blessed reference' + ); return undef; # don't know wtf to do } elsif (! defined $destruction_registry->{ my $addr = refaddr($_[0]) } ) { @@ -575,7 +680,7 @@ sub is_exception ($) { return 0; } else { - carp_unique ( sprintf ( + emit_loud_diag( msg => sprintf ( 'Preventing *MULTIPLE* DESTROY() invocations on %s - an *EXTREMELY ' . 'DANGEROUS* condition which is *ALMOST CERTAINLY GLOBAL* within your ' . 'application, affecting *ALL* classes without active protection against ' @@ -639,14 +744,29 @@ sub modver_gt_or_eq_and_lt ($$$) { } { - # 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 $args = ( + ref $_[0] eq 'HASH' ? $_[0] + : ( @_ == 1 and ! length ref $_[0] ) ? { class => $_[0] } + : { @_ } + ); + + my ($class, $requested_mro) = @{$args}{qw( class use_mro )}; - croak "Expecting a class name" - if not defined $_[0] or $_[0] !~ $module_name_rx; + croak "Expecting a class name either as the sole argument or a 'class' option" + 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 $internal_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) @@ -656,87 +776,72 @@ sub modver_gt_or_eq_and_lt ($$$) { # 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}; + local $__describe_class_query_cache->{'!internal!'} = {} + unless $__describe_class_query_cache->{'!internal!'}; my $my_gen = 0; - $my_gen += get_real_pkg_gen($_) for ( - 'UNIVERSAL', - my ($class, @my_ISA) = @{ - $mro_recursor_stack->{cache}{$_[0]}{linear_isa} + $my_gen += get_real_pkg_gen($_) for ( my @full_ISA = ( + + @{ + $__describe_class_query_cache->{'!internal!'}{$internal_cache_key}{linear_isa} ||= - mro::get_linear_isa($_[0]) - } - ); + mro::get_linear_isa($class, $requested_mro) + }, - my $slot = $describe_class_query_cache->{$class} ||= {}; + (( + $__describe_class_query_cache->{'!internal!'}{$class}{is_universal} + ||= + mro::is_universal($class) + ) ? () : @{ + $__describe_class_query_cache->{'!internal!'}{UNIVERSAL}{linear_isa} + ||= + mro::get_linear_isa("UNIVERSAL") + }), + + )); + + my $slot = $__describe_class_query_cache->{$query_cache_key} ||= {}; unless ( ($slot->{cumulative_gen}||0) == $my_gen ) { # reset %$slot = ( class => $class, - isa => [ @my_ISA ], # copy before we shove UNIVERSAL into it + isa => { map { $_ => 1 } @full_ISA }, + linear_isa => [ + @{ $__describe_class_query_cache->{'!internal!'}{$internal_cache_key}{linear_isa} } + [ 1 .. $#{$__describe_class_query_cache->{'!internal!'}{$internal_cache_key}{linear_isa}} ] + ], mro => { - type => mro::get_mro($class), + type => $requested_mro, + is_c3 => ( ($requested_mro eq 'c3') ? 1 : 0 ), }, cumulative_gen => $my_gen, ); - $slot->{mro}{is_c3} = ($slot->{mro}{type} eq 'c3') ? 1 : 0; - push @my_ISA, 'UNIVERSAL'; + # remove ourselves from ISA + shift @full_ISA; # ensure the cache is populated for the parents, code below can then # efficiently operate over the query_cache directly - for (reverse @my_ISA) { - my ($parent_gen, @parent_ISA); - - # and even more skips before calling out recursively - describe_class_methods($_) unless ( - $describe_class_query_cache->{$_}{cumulative_gen} - and - $parent_gen = get_real_pkg_gen($_) - and - ( - ( - (undef, @parent_ISA) = @{ - $mro_recursor_stack->{cache}{$_}{linear_isa} - ||= - mro::get_linear_isa($_) - } - ) == 1 - or - do { - $parent_gen += get_real_pkg_gen($_) for @parent_ISA; - 1; - } - ) - and - $describe_class_query_cache->{$_}{cumulative_gen} == $parent_gen - ); - } + describe_class_methods($_) for reverse @full_ISA; - my ($methods_seen_via_ISA_on_old_mro, $current_node_refaddr); no strict 'refs'; # combine full ISA-order inherited and local method list into a # "shadowing stack" ( - $current_node_refaddr = refaddr($_) - - and - - # on complex MI herarchies the method can be anywhere in the - # shadow stack - look through the entire slot, not just [0] - ( ! grep { - refaddr($_) == $current_node_refaddr - } @{ $slot->{methods}{ $_->{name} } || [] } ) + unshift @{ $slot->{methods}{$_->{name}} }, $_ and - unshift @{ $slot->{methods}{$_->{name}} }, $_ + ( + $_->{via_class} ne $class + or + $slot->{methods_defined_in_class}{$_->{name}} = $_ + ) and @@ -748,49 +853,38 @@ sub modver_gt_or_eq_and_lt ($$$) { ) for ( - # what describe_class_methods for @my_ISA produced above - ( map { $_->[0] } map { - values %{ $describe_class_query_cache->{$_}{methods} } - } reverse @my_ISA ), + # 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 { ( - # these 2 OR-ed checks are sufficient for 5.10+ + # need to account for dummy helper crefs under OLD_MRO ( - ref(\ "${class}::"->{$_} ) ne 'GLOB' + ! DBIx::Class::_ENV_::OLD_MRO or - defined( *{ "${class}::"->{$_} }{CODE} ) + ! ( ( $Class::C3::MRO{$class} || {} )->{methods}{$_} ) ) and - # need to account for dummy helper crefs under OLD_MRO + # these 2 OR-ed checks are sufficient for 5.10+ ( - ! DBIx::Class::_ENV_::OLD_MRO + ref(\ "${class}::"->{$_} ) ne 'GLOB' or - ( - $methods_seen_via_ISA_on_old_mro ||= do { - my $rv = {}; - $rv->{$_->{name}}->{ refaddr( \&{ "$_->{via_class}::$_->{name}"} ) } = 1 for - map { @$_ } map - { values %{ $describe_class_query_cache->{$_}{methods} } } - @my_ISA; - $rv; - } - and - ( - ! $methods_seen_via_ISA_on_old_mro->{$_} - or - ! $methods_seen_via_ISA_on_old_mro->{$_}{ refaddr( \&{"${class}::${_}"} ) } - ) - ) + defined( *{ "${class}::"->{$_} }{CODE} ) ) ) ? { via_class => $class, name => $_, - attributes => { map { $_ => 1 } @{ - $mro_recursor_stack->{cache}{attrs}{ refaddr \&{"${class}::${_}"} } - ||= - [ attributes::get( \&{"${class}::${_}"} ) ] + attributes => { map { $_ => 1 } do { + my @attrs; + local $@; + local $SIG{__DIE__} if $SIG{__DIE__}; + # attributes::get may throw on blessed-false crefs :/ + eval { @attrs = attributes::get( \&{"${class}::${_}"} ); 1 } + or warn "Unable to determine attributes of the \\&${class}::$_ method due to following error: $@"; + @attrs; } }, } : () @@ -810,7 +904,7 @@ sub modver_gt_or_eq_and_lt ($$$) { $slot->{cumulative_gen} = 0; $slot->{cumulative_gen} += get_real_pkg_gen($_) - for $class, @my_ISA; + for $class, @full_ISA; } }