c22a5c64b9671c265392413f296c758f2c10fe6a
[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     } or do {
149       Carp::cluck "Execution of scope guard $_[0] resulted in the non-trappable exception:\n\n$@";
150     };
151   }
152 }
153
154
155 sub is_exception ($) {
156   my $e = $_[0];
157
158   # FIXME
159   # this is not strictly correct - an eval setting $@ to undef
160   # is *not* the same as an eval setting $@ to ''
161   # but for the sake of simplicity assume the following for
162   # the time being
163   return 0 unless defined $e;
164
165   my ($not_blank, $suberror);
166   {
167     local $@;
168     eval {
169       # The ne() here is deliberate - a plain length($e), or worse "$e" ne
170       # will entirely obviate the need for the encolsing eval{}, as the
171       # condition we guard against is a missing fallback overload
172       $not_blank = ( $e ne '' );
173       1;
174     } or $suberror = $@;
175   }
176
177   if (defined $suberror) {
178     if (length (my $class = blessed($e) )) {
179       carp_unique( sprintf(
180         'External exception class %s implements partial (broken) overloading '
181       . 'preventing its instances from being used in simple ($x eq $y) '
182       . 'comparisons. Given Perl\'s "globally cooperative" exception '
183       . 'handling this type of brokenness is extremely dangerous on '
184       . 'exception objects, as it may (and often does) result in silent '
185       . '"exception substitution". DBIx::Class tries to work around this '
186       . 'as much as possible, but other parts of your software stack may '
187       . 'not be even aware of this. Please submit a bugreport against the '
188       . 'distribution containing %s and in the meantime apply a fix similar '
189       . 'to the one shown at %s, in order to ensure your exception handling '
190       . 'is saner application-wide. What follows is the actual error text '
191       . "as generated by Perl itself:\n\n%s\n ",
192         $class,
193         $class,
194         'http://v.gd/DBIC_overload_tempfix/',
195         $suberror,
196       ));
197
198       # workaround, keeps spice flowing
199       $not_blank = !!( length $e );
200     }
201     else {
202       # not blessed yet failed the 'ne'... this makes 0 sense...
203       # just throw further
204       die $suberror
205     }
206   }
207   elsif (
208     # a ref evaluating to '' is definitively a "null object"
209     ( not $not_blank )
210       and
211     length( my $class = ref $e )
212   ) {
213     carp_unique( sprintf(
214       "Objects of external exception class '%s' stringify to '' (the "
215     . 'empty string), implementing the so called null-object-pattern. '
216     . 'Given Perl\'s "globally cooperative" exception handling using this '
217     . 'class of exceptions is extremely dangerous, as it may (and often '
218     . 'does) result in silent discarding of errors. DBIx::Class tries to '
219     . 'work around this as much as possible, but other parts of your '
220     . 'software stack may not be even aware of the problem. Please submit '
221     . 'a bugreport against the distribution containing %s',
222
223       ($class) x 2,
224     ));
225
226     $not_blank = 1;
227   }
228
229   return $not_blank;
230 }
231
232 {
233   my $callstack_state;
234
235   # Recreate the logic of try(), while reusing the catch()/finally() as-is
236   #
237   # FIXME: We need to move away from Try::Tiny entirely (way too heavy and
238   # yes, shows up ON TOP of profiles) but this is a batle for another maint
239   sub dbic_internal_try (&;@) {
240
241     my $try_cref = shift;
242     my $catch_cref = undef;  # apparently this is a thing... https://rt.perl.org/Public/Bug/Display.html?id=119311
243
244     for my $arg (@_) {
245
246       if( ref($arg) eq 'Try::Tiny::Catch' ) {
247
248         croak 'dbic_internal_try() may not be followed by multiple catch() blocks'
249           if $catch_cref;
250
251         $catch_cref = $$arg;
252       }
253       elsif ( ref($arg) eq 'Try::Tiny::Finally' ) {
254         croak 'dbic_internal_try() does not support finally{}';
255       }
256       else {
257         croak(
258           'dbic_internal_try() encountered an unexpected argument '
259         . "'@{[ defined $arg ? $arg : 'UNDEF' ]}' - perhaps "
260         . 'a missing semi-colon before or ' # trailing space important
261         );
262       }
263     }
264
265     my $wantarray = wantarray;
266     my $preexisting_exception = $@;
267
268     my @ret;
269     my $all_good = eval {
270       $@ = $preexisting_exception;
271
272       local $callstack_state->{in_internal_try} = 1
273         unless $callstack_state->{in_internal_try};
274
275       # always unset - someone may have snuck it in
276       local $SIG{__DIE__}
277         if $SIG{__DIE__};
278
279
280       if( $wantarray ) {
281         @ret = $try_cref->();
282       }
283       elsif( defined $wantarray ) {
284         $ret[0] = $try_cref->();
285       }
286       else {
287         $try_cref->();
288       }
289
290       1;
291     };
292
293     my $exception = $@;
294     $@ = $preexisting_exception;
295
296     if ( $all_good ) {
297       return $wantarray ? @ret : $ret[0]
298     }
299     elsif ( $catch_cref ) {
300       for ( $exception ) {
301         return $catch_cref->($exception);
302       }
303     }
304
305     return;
306   }
307
308   sub in_internal_try { !! $callstack_state->{in_internal_try} }
309 }
310
311 {
312   my $destruction_registry = {};
313
314   sub CLONE {
315     $destruction_registry = { map
316       { defined $_ ? ( refaddr($_) => $_ ) : () }
317       values %$destruction_registry
318     };
319   }
320
321   # This is almost invariably invoked from within DESTROY
322   # throwing exceptions won't work
323   sub detected_reinvoked_destructor {
324
325     # quick "garbage collection" pass - prevents the registry
326     # from slowly growing with a bunch of undef-valued keys
327     defined $destruction_registry->{$_} or delete $destruction_registry->{$_}
328       for keys %$destruction_registry;
329
330     if (! length ref $_[0]) {
331       printf STDERR '%s() expects a blessed reference %s',
332         (caller(0))[3],
333         Carp::longmess,
334       ;
335       return undef; # don't know wtf to do
336     }
337     elsif (! defined $destruction_registry->{ my $addr = refaddr($_[0]) } ) {
338       weaken( $destruction_registry->{$addr} = $_[0] );
339       return 0;
340     }
341     else {
342       carp_unique ( sprintf (
343         'Preventing *MULTIPLE* DESTROY() invocations on %s - an *EXTREMELY '
344       . 'DANGEROUS* condition which is *ALMOST CERTAINLY GLOBAL* within your '
345       . 'application, affecting *ALL* classes without active protection against '
346       . 'this. Diagnose and fix the root cause ASAP!!!%s',
347       refdesc $_[0],
348         ( ( $INC{'Devel/StackTrace.pm'} and ! do { local $@; eval { Devel::StackTrace->VERSION(2) } } )
349           ? " (likely culprit Devel::StackTrace\@@{[ Devel::StackTrace->VERSION ]} found in %INC, http://is.gd/D_ST_refcap)"
350           : ''
351         )
352       ));
353
354       return 1;
355     }
356   }
357 }
358
359 my $module_name_rx = qr/ \A [A-Z_a-z] [0-9A-Z_a-z]* (?: :: [0-9A-Z_a-z]+ )* \z /x;
360 my $ver_rx =         qr/ \A [0-9]+ (?: \. [0-9]+ )* (?: \_ [0-9]+ )*        \z /x;
361
362 sub modver_gt_or_eq ($$) {
363   my ($mod, $ver) = @_;
364
365   croak "Nonsensical module name supplied"
366     if ! defined $mod or $mod !~ $module_name_rx;
367
368   croak "Nonsensical minimum version supplied"
369     if ! defined $ver or $ver !~ $ver_rx;
370
371   no strict 'refs';
372   my $ver_cache = ${"${mod}::__DBIC_MODULE_VERSION_CHECKS__"} ||= ( $mod->VERSION
373     ? {}
374     : croak "$mod does not seem to provide a version (perhaps it never loaded)"
375   );
376
377   ! defined $ver_cache->{$ver}
378     and
379   $ver_cache->{$ver} = do {
380
381     local $SIG{__WARN__} = sigwarn_silencer( qr/\Qisn't numeric in subroutine entry/ )
382       if SPURIOUS_VERSION_CHECK_WARNINGS;
383
384     local $@;
385     local $SIG{__DIE__};
386     eval { $mod->VERSION($ver) } ? 1 : 0;
387   };
388
389   $ver_cache->{$ver};
390 }
391
392 sub modver_gt_or_eq_and_lt ($$$) {
393   my ($mod, $v_ge, $v_lt) = @_;
394
395   croak "Nonsensical maximum version supplied"
396     if ! defined $v_lt or $v_lt !~ $ver_rx;
397
398   return (
399     modver_gt_or_eq($mod, $v_ge)
400       and
401     ! modver_gt_or_eq($mod, $v_lt)
402   ) ? 1 : 0;
403 }
404
405 {
406   my $list_ctx_ok_stack_marker;
407
408   sub fail_on_internal_wantarray () {
409     return if $list_ctx_ok_stack_marker;
410
411     if (! defined wantarray) {
412       croak('fail_on_internal_wantarray() needs a tempvar to save the stack marker guard');
413     }
414
415     my $cf = 1;
416     while ( ( (CORE::caller($cf+1))[3] || '' ) =~ / :: (?:
417
418       # these are public API parts that alter behavior on wantarray
419       search | search_related | slice | search_literal
420
421         |
422
423       # these are explicitly prefixed, since we only recognize them as valid
424       # escapes when they come from the guts of CDBICompat
425       CDBICompat .*? :: (?: search_where | retrieve_from_sql | retrieve_all )
426
427     ) $/x ) {
428       $cf++;
429     }
430
431     my ($fr, $want, $argdesc);
432     {
433       package DB;
434       $fr = [ CORE::caller($cf) ];
435       $want = ( CORE::caller($cf-1) )[5];
436       $argdesc = ref $DB::args[0]
437         ? DBIx::Class::_Util::refdesc($DB::args[0])
438         : 'non '
439       ;
440     };
441
442     if (
443       $want and $fr->[0] =~ /^(?:DBIx::Class|DBICx::)/
444     ) {
445       DBIx::Class::Exception->throw( sprintf (
446         "Improper use of %s instance in list context at %s line %d\n\n    Stacktrace starts",
447         $argdesc, @{$fr}[1,2]
448       ), 'with_stacktrace');
449     }
450
451     my $mark = [];
452     weaken ( $list_ctx_ok_stack_marker = $mark );
453     $mark;
454   }
455 }
456
457 sub fail_on_internal_call {
458   my ($fr, $argdesc);
459   {
460     package DB;
461     $fr = [ CORE::caller(1) ];
462     $argdesc = ref $DB::args[0]
463       ? DBIx::Class::_Util::refdesc($DB::args[0])
464       : undef
465     ;
466   };
467
468   if (
469     $argdesc
470       and
471     $fr->[0] =~ /^(?:DBIx::Class|DBICx::)/
472       and
473     $fr->[1] !~ /\b(?:CDBICompat|ResultSetProxy)\b/  # no point touching there
474   ) {
475     DBIx::Class::Exception->throw( sprintf (
476       "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",
477       $fr->[3], $argdesc, @{$fr}[1,2], ( $fr->[6] || do {
478         require B::Deparse;
479         no strict 'refs';
480         B::Deparse->new->coderef2text(\&{$fr->[3]})
481       }),
482     ), 'with_stacktrace');
483   }
484 }
485
486 1;