Streamline couple code paths/texts, no func changes (goes with 7cb35852)
[dbsrgits/DBIx-Class.git] / lib / DBIx / Class / _Util.pm
1 package # hide from PAUSE
2   DBIx::Class::_Util;
3
4 use warnings;
5 use strict;
6
7 use constant SPURIOUS_VERSION_CHECK_WARNINGS => ( "$]" < 5.010 ? 1 : 0);
8
9 BEGIN {
10   package # hide from pause
11     DBIx::Class::_ENV_;
12
13   use Config;
14
15   use constant {
16
17     # but of course
18     BROKEN_FORK => ($^O eq 'MSWin32') ? 1 : 0,
19
20     BROKEN_GOTO => ( "$]" < 5.008003 ) ? 1 : 0,
21
22     HAS_ITHREADS => $Config{useithreads} ? 1 : 0,
23
24     UNSTABLE_DOLLARAT => ( "$]" < 5.013002 ) ? 1 : 0,
25
26     DBICTEST => $INC{"DBICTest/Util.pm"} ? 1 : 0,
27
28     # During 5.13 dev cycle HELEMs started to leak on copy
29     # add an escape for these perls ON SMOKERS - a user will still get death
30     PEEPEENESS => ( eval { DBICTest::RunMode->is_smoker } && ( "$]" >= 5.013005 and "$]" <= 5.013006) ),
31
32     ( map
33       #
34       # the "DBIC_" prefix below is crucial - this is what makes CI pick up
35       # all envvars without further adjusting its scripts
36       # DO NOT CHANGE to the more logical { $_ => !!( $ENV{"DBIC_$_"} ) }
37       #
38       { substr($_, 5) => !!( $ENV{$_} ) }
39       qw(
40         DBIC_SHUFFLE_UNORDERED_RESULTSETS
41         DBIC_ASSERT_NO_INTERNAL_WANTARRAY
42         DBIC_ASSERT_NO_INTERNAL_INDIRECT_CALLS
43         DBIC_STRESSTEST_UTF8_UPGRADE_GENERATED_COLLAPSER_SOURCE
44         DBIC_STRESSTEST_COLUMN_INFO_UNAWARE_STORAGE
45       )
46     ),
47
48     IV_SIZE => $Config{ivsize},
49
50     OS_NAME => $^O,
51   };
52
53   if ( "$]" < 5.009_005) {
54     require MRO::Compat;
55     constant->import( OLD_MRO => 1 );
56   }
57   else {
58     require mro;
59     constant->import( OLD_MRO => 0 );
60   }
61 }
62
63 # FIXME - this is not supposed to be here
64 # Carp::Skip to the rescue soon
65 use DBIx::Class::Carp '^DBIx::Class|^DBICTest';
66
67 use B ();
68 use Carp 'croak';
69 use Storable 'nfreeze';
70 use Scalar::Util qw(weaken blessed reftype refaddr);
71 use List::Util qw(first);
72 use Sub::Quote qw(qsub quote_sub);
73
74 # Already correctly prototyped: perlbrew exec perl -MStorable -e 'warn prototype \&Storable::dclone'
75 BEGIN { *deep_clone = \&Storable::dclone }
76
77 use base 'Exporter';
78 our @EXPORT_OK = qw(
79   sigwarn_silencer modver_gt_or_eq modver_gt_or_eq_and_lt
80   fail_on_internal_wantarray fail_on_internal_call
81   refdesc refcount hrefaddr
82   scope_guard detected_reinvoked_destructor
83   is_exception dbic_internal_try
84   quote_sub qsub perlstring serialize deep_clone
85   UNRESOLVABLE_CONDITION
86 );
87
88 use constant UNRESOLVABLE_CONDITION => \ '1 = 0';
89
90 sub sigwarn_silencer ($) {
91   my $pattern = shift;
92
93   croak "Expecting a regexp" if ref $pattern ne 'Regexp';
94
95   my $orig_sig_warn = $SIG{__WARN__} || sub { CORE::warn(@_) };
96
97   return sub { &$orig_sig_warn unless $_[0] =~ $pattern };
98 }
99
100 sub perlstring ($) { q{"}. quotemeta( shift ). q{"} };
101
102 sub hrefaddr ($) { sprintf '0x%x', &refaddr||0 }
103
104 sub refdesc ($) {
105   croak "Expecting a reference" if ! length ref $_[0];
106
107   # be careful not to trigger stringification,
108   # reuse @_ as a scratch-pad
109   sprintf '%s%s(0x%x)',
110     ( defined( $_[1] = blessed $_[0]) ? "$_[1]=" : '' ),
111     reftype $_[0],
112     refaddr($_[0]),
113   ;
114 }
115
116 sub refcount ($) {
117   croak "Expecting a reference" if ! length ref $_[0];
118
119   # No tempvars - must operate on $_[0], otherwise the pad
120   # will count as an extra ref
121   B::svref_2object($_[0])->REFCNT;
122 }
123
124 sub serialize ($) {
125   local $Storable::canonical = 1;
126   nfreeze($_[0]);
127 }
128
129 sub scope_guard (&) {
130   croak 'Calling scope_guard() in void context makes no sense'
131     if ! defined wantarray;
132
133   # no direct blessing of coderefs - DESTROY is buggy on those
134   bless [ $_[0] ], 'DBIx::Class::_Util::ScopeGuard';
135 }
136 {
137   package #
138     DBIx::Class::_Util::ScopeGuard;
139
140   sub DESTROY {
141     &DBIx::Class::_Util::detected_reinvoked_destructor;
142
143     local $@ if DBIx::Class::_ENV_::UNSTABLE_DOLLARAT;
144
145     eval {
146       $_[0]->[0]->();
147       1;
148     }
149       or
150     Carp::cluck(
151       "Execution of scope guard $_[0] resulted in the non-trappable exception:\n\n$@"
152     );
153   }
154 }
155
156
157 sub is_exception ($) {
158   my $e = $_[0];
159
160   # FIXME
161   # this is not strictly correct - an eval setting $@ to undef
162   # is *not* the same as an eval setting $@ to ''
163   # but for the sake of simplicity assume the following for
164   # the time being
165   return 0 unless defined $e;
166
167   my ($not_blank, $suberror);
168   {
169     local $@;
170     eval {
171       # The ne() here is deliberate - a plain length($e), or worse "$e" ne
172       # will entirely obviate the need for the encolsing eval{}, as the
173       # condition we guard against is a missing fallback overload
174       $not_blank = ( $e ne '' );
175       1;
176     } or $suberror = $@;
177   }
178
179   if (defined $suberror) {
180     if (length (my $class = blessed($e) )) {
181       carp_unique( sprintf(
182         'External exception class %s implements partial (broken) overloading '
183       . 'preventing its instances from being used in simple ($x eq $y) '
184       . 'comparisons. Given Perl\'s "globally cooperative" exception '
185       . 'handling this type of brokenness is extremely dangerous on '
186       . 'exception objects, as it may (and often does) result in silent '
187       . '"exception substitution". DBIx::Class tries to work around this '
188       . 'as much as possible, but other parts of your software stack may '
189       . 'not be even aware of this. Please submit a bugreport against the '
190       . 'distribution containing %s and in the meantime apply a fix similar '
191       . 'to the one shown at %s, in order to ensure your exception handling '
192       . 'is saner application-wide. What follows is the actual error text '
193       . "as generated by Perl itself:\n\n%s\n ",
194         $class,
195         $class,
196         'http://v.gd/DBIC_overload_tempfix/',
197         $suberror,
198       ));
199
200       # workaround, keeps spice flowing
201       $not_blank = !!( length $e );
202     }
203     else {
204       # not blessed yet failed the 'ne'... this makes 0 sense...
205       # just throw further
206       die $suberror
207     }
208   }
209   elsif (
210     # a ref evaluating to '' is definitively a "null object"
211     ( not $not_blank )
212       and
213     length( my $class = ref $e )
214   ) {
215     carp_unique( sprintf(
216       "Objects of external exception class '%s' stringify to '' (the "
217     . 'empty string), implementing the so called null-object-pattern. '
218     . 'Given Perl\'s "globally cooperative" exception handling using this '
219     . 'class of exceptions is extremely dangerous, as it may (and often '
220     . 'does) result in silent discarding of errors. DBIx::Class tries to '
221     . 'work around this as much as possible, but other parts of your '
222     . 'software stack may not be even aware of the problem. Please submit '
223     . 'a bugreport against the distribution containing %s',
224
225       ($class) x 2,
226     ));
227
228     $not_blank = 1;
229   }
230
231   return $not_blank;
232 }
233
234 {
235   my $callstack_state;
236
237   # Recreate the logic of try(), while reusing the catch()/finally() as-is
238   #
239   # FIXME: We need to move away from Try::Tiny entirely (way too heavy and
240   # yes, shows up ON TOP of profiles) but this is a batle for another maint
241   sub dbic_internal_try (&;@) {
242
243     my $try_cref = shift;
244     my $catch_cref = undef;  # apparently this is a thing... https://rt.perl.org/Public/Bug/Display.html?id=119311
245
246     for my $arg (@_) {
247
248       if( ref($arg) eq 'Try::Tiny::Catch' ) {
249
250         croak 'dbic_internal_try() may not be followed by multiple catch() blocks'
251           if $catch_cref;
252
253         $catch_cref = $$arg;
254       }
255       elsif ( ref($arg) eq 'Try::Tiny::Finally' ) {
256         croak 'dbic_internal_try() does not support finally{}';
257       }
258       else {
259         croak(
260           'dbic_internal_try() encountered an unexpected argument '
261         . "'@{[ defined $arg ? $arg : 'UNDEF' ]}' - perhaps "
262         . 'a missing semi-colon before or ' # trailing space important
263         );
264       }
265     }
266
267     my $wantarray = wantarray;
268     my $preexisting_exception = $@;
269
270     my @ret;
271     my $all_good = eval {
272       $@ = $preexisting_exception;
273
274       local $callstack_state->{in_internal_try} = 1
275         unless $callstack_state->{in_internal_try};
276
277       # always unset - someone may have snuck it in
278       local $SIG{__DIE__}
279         if $SIG{__DIE__};
280
281
282       if( $wantarray ) {
283         @ret = $try_cref->();
284       }
285       elsif( defined $wantarray ) {
286         $ret[0] = $try_cref->();
287       }
288       else {
289         $try_cref->();
290       }
291
292       1;
293     };
294
295     my $exception = $@;
296     $@ = $preexisting_exception;
297
298     if ( $all_good ) {
299       return $wantarray ? @ret : $ret[0]
300     }
301     elsif ( $catch_cref ) {
302       for ( $exception ) {
303         return $catch_cref->($exception);
304       }
305     }
306
307     return;
308   }
309
310   sub in_internal_try { !! $callstack_state->{in_internal_try} }
311 }
312
313 {
314   my $destruction_registry = {};
315
316   sub CLONE {
317     $destruction_registry = { map
318       { defined $_ ? ( refaddr($_) => $_ ) : () }
319       values %$destruction_registry
320     };
321   }
322
323   # This is almost invariably invoked from within DESTROY
324   # throwing exceptions won't work
325   sub detected_reinvoked_destructor {
326
327     # quick "garbage collection" pass - prevents the registry
328     # from slowly growing with a bunch of undef-valued keys
329     defined $destruction_registry->{$_} or delete $destruction_registry->{$_}
330       for keys %$destruction_registry;
331
332     if (! length ref $_[0]) {
333       printf STDERR '%s() expects a blessed reference %s',
334         (caller(0))[3],
335         Carp::longmess,
336       ;
337       return undef; # don't know wtf to do
338     }
339     elsif (! defined $destruction_registry->{ my $addr = refaddr($_[0]) } ) {
340       weaken( $destruction_registry->{$addr} = $_[0] );
341       return 0;
342     }
343     else {
344       carp_unique ( sprintf (
345         'Preventing *MULTIPLE* DESTROY() invocations on %s - an *EXTREMELY '
346       . 'DANGEROUS* condition which is *ALMOST CERTAINLY GLOBAL* within your '
347       . 'application, affecting *ALL* classes without active protection against '
348       . 'this. Diagnose and fix the root cause ASAP!!!%s',
349       refdesc $_[0],
350         ( ( $INC{'Devel/StackTrace.pm'} and ! do { local $@; eval { Devel::StackTrace->VERSION(2) } } )
351           ? " (likely culprit Devel::StackTrace\@@{[ Devel::StackTrace->VERSION ]} found in %INC, http://is.gd/D_ST_refcap)"
352           : ''
353         )
354       ));
355
356       return 1;
357     }
358   }
359 }
360
361 my $module_name_rx = qr/ \A [A-Z_a-z] [0-9A-Z_a-z]* (?: :: [0-9A-Z_a-z]+ )* \z /x;
362 my $ver_rx =         qr/ \A [0-9]+ (?: \. [0-9]+ )* (?: \_ [0-9]+ )*        \z /x;
363
364 sub modver_gt_or_eq ($$) {
365   my ($mod, $ver) = @_;
366
367   croak "Nonsensical module name supplied"
368     if ! defined $mod or $mod !~ $module_name_rx;
369
370   croak "Nonsensical minimum version supplied"
371     if ! defined $ver or $ver !~ $ver_rx;
372
373   no strict 'refs';
374   my $ver_cache = ${"${mod}::__DBIC_MODULE_VERSION_CHECKS__"} ||= ( $mod->VERSION
375     ? {}
376     : croak "$mod does not seem to provide a version (perhaps it never loaded)"
377   );
378
379   ! defined $ver_cache->{$ver}
380     and
381   $ver_cache->{$ver} = do {
382
383     local $SIG{__WARN__} = sigwarn_silencer( qr/\Qisn't numeric in subroutine entry/ )
384       if SPURIOUS_VERSION_CHECK_WARNINGS;
385
386     local $@;
387     local $SIG{__DIE__};
388     eval { $mod->VERSION($ver) } ? 1 : 0;
389   };
390
391   $ver_cache->{$ver};
392 }
393
394 sub modver_gt_or_eq_and_lt ($$$) {
395   my ($mod, $v_ge, $v_lt) = @_;
396
397   croak "Nonsensical maximum version supplied"
398     if ! defined $v_lt or $v_lt !~ $ver_rx;
399
400   return (
401     modver_gt_or_eq($mod, $v_ge)
402       and
403     ! modver_gt_or_eq($mod, $v_lt)
404   ) ? 1 : 0;
405 }
406
407 {
408   my $list_ctx_ok_stack_marker;
409
410   sub fail_on_internal_wantarray () {
411     return if $list_ctx_ok_stack_marker;
412
413     if (! defined wantarray) {
414       croak('fail_on_internal_wantarray() needs a tempvar to save the stack marker guard');
415     }
416
417     my $cf = 1;
418     while ( ( (CORE::caller($cf+1))[3] || '' ) =~ / :: (?:
419
420       # these are public API parts that alter behavior on wantarray
421       search | search_related | slice | search_literal
422
423         |
424
425       # these are explicitly prefixed, since we only recognize them as valid
426       # escapes when they come from the guts of CDBICompat
427       CDBICompat .*? :: (?: search_where | retrieve_from_sql | retrieve_all )
428
429     ) $/x ) {
430       $cf++;
431     }
432
433     my ($fr, $want, $argdesc);
434     {
435       package DB;
436       $fr = [ CORE::caller($cf) ];
437       $want = ( CORE::caller($cf-1) )[5];
438       $argdesc = ref $DB::args[0]
439         ? DBIx::Class::_Util::refdesc($DB::args[0])
440         : 'non '
441       ;
442     };
443
444     if (
445       $want and $fr->[0] =~ /^(?:DBIx::Class|DBICx::)/
446     ) {
447       DBIx::Class::Exception->throw( sprintf (
448         "Improper use of %s instance in list context at %s line %d\n\n    Stacktrace starts",
449         $argdesc, @{$fr}[1,2]
450       ), 'with_stacktrace');
451     }
452
453     my $mark = [];
454     weaken ( $list_ctx_ok_stack_marker = $mark );
455     $mark;
456   }
457 }
458
459 sub fail_on_internal_call {
460   my ($fr, $argdesc);
461   {
462     package DB;
463     $fr = [ CORE::caller(1) ];
464     $argdesc = ref $DB::args[0]
465       ? DBIx::Class::_Util::refdesc($DB::args[0])
466       : undef
467     ;
468   };
469
470   if (
471     $argdesc
472       and
473     $fr->[0] =~ /^(?:DBIx::Class|DBICx::)/
474       and
475     $fr->[1] !~ /\b(?:CDBICompat|ResultSetProxy)\b/  # no point touching there
476   ) {
477     DBIx::Class::Exception->throw( sprintf (
478       "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",
479       $fr->[3], $argdesc, @{$fr}[1,2], ( $fr->[6] || do {
480         require B::Deparse;
481         no strict 'refs';
482         B::Deparse->new->coderef2text(\&{$fr->[3]})
483       }),
484     ), 'with_stacktrace');
485   }
486 }
487
488 1;