Proper fix for the lazy workaround in 7e1774f7
[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     # Dummy NEXTSTATE ensuring the all temporaries on the stack are garbage
323     # collected before leaving this scope. Depending on the code above, this
324     # may very well be just a preventive measure guarding future modifications
325     undef;
326   }
327
328   # This is almost invariably invoked from within DESTROY
329   # throwing exceptions won't work
330   sub detected_reinvoked_destructor {
331
332     # quick "garbage collection" pass - prevents the registry
333     # from slowly growing with a bunch of undef-valued keys
334     defined $destruction_registry->{$_} or delete $destruction_registry->{$_}
335       for keys %$destruction_registry;
336
337     if (! length ref $_[0]) {
338       printf STDERR '%s() expects a blessed reference %s',
339         (caller(0))[3],
340         Carp::longmess,
341       ;
342       return undef; # don't know wtf to do
343     }
344     elsif (! defined $destruction_registry->{ my $addr = refaddr($_[0]) } ) {
345       weaken( $destruction_registry->{$addr} = $_[0] );
346       return 0;
347     }
348     else {
349       carp_unique ( sprintf (
350         'Preventing *MULTIPLE* DESTROY() invocations on %s - an *EXTREMELY '
351       . 'DANGEROUS* condition which is *ALMOST CERTAINLY GLOBAL* within your '
352       . 'application, affecting *ALL* classes without active protection against '
353       . 'this. Diagnose and fix the root cause ASAP!!!%s',
354       refdesc $_[0],
355         ( ( $INC{'Devel/StackTrace.pm'} and ! do { local $@; eval { Devel::StackTrace->VERSION(2) } } )
356           ? " (likely culprit Devel::StackTrace\@@{[ Devel::StackTrace->VERSION ]} found in %INC, http://is.gd/D_ST_refcap)"
357           : ''
358         )
359       ));
360
361       return 1;
362     }
363   }
364 }
365
366 my $module_name_rx = qr/ \A [A-Z_a-z] [0-9A-Z_a-z]* (?: :: [0-9A-Z_a-z]+ )* \z /x;
367 my $ver_rx =         qr/ \A [0-9]+ (?: \. [0-9]+ )* (?: \_ [0-9]+ )*        \z /x;
368
369 sub modver_gt_or_eq ($$) {
370   my ($mod, $ver) = @_;
371
372   croak "Nonsensical module name supplied"
373     if ! defined $mod or $mod !~ $module_name_rx;
374
375   croak "Nonsensical minimum version supplied"
376     if ! defined $ver or $ver !~ $ver_rx;
377
378   no strict 'refs';
379   my $ver_cache = ${"${mod}::__DBIC_MODULE_VERSION_CHECKS__"} ||= ( $mod->VERSION
380     ? {}
381     : croak "$mod does not seem to provide a version (perhaps it never loaded)"
382   );
383
384   ! defined $ver_cache->{$ver}
385     and
386   $ver_cache->{$ver} = do {
387
388     local $SIG{__WARN__} = sigwarn_silencer( qr/\Qisn't numeric in subroutine entry/ )
389       if SPURIOUS_VERSION_CHECK_WARNINGS;
390
391     local $@;
392     local $SIG{__DIE__};
393     eval { $mod->VERSION($ver) } ? 1 : 0;
394   };
395
396   $ver_cache->{$ver};
397 }
398
399 sub modver_gt_or_eq_and_lt ($$$) {
400   my ($mod, $v_ge, $v_lt) = @_;
401
402   croak "Nonsensical maximum version supplied"
403     if ! defined $v_lt or $v_lt !~ $ver_rx;
404
405   return (
406     modver_gt_or_eq($mod, $v_ge)
407       and
408     ! modver_gt_or_eq($mod, $v_lt)
409   ) ? 1 : 0;
410 }
411
412 {
413   my $list_ctx_ok_stack_marker;
414
415   sub fail_on_internal_wantarray () {
416     return if $list_ctx_ok_stack_marker;
417
418     if (! defined wantarray) {
419       croak('fail_on_internal_wantarray() needs a tempvar to save the stack marker guard');
420     }
421
422     my $cf = 1;
423     while ( ( (CORE::caller($cf+1))[3] || '' ) =~ / :: (?:
424
425       # these are public API parts that alter behavior on wantarray
426       search | search_related | slice | search_literal
427
428         |
429
430       # these are explicitly prefixed, since we only recognize them as valid
431       # escapes when they come from the guts of CDBICompat
432       CDBICompat .*? :: (?: search_where | retrieve_from_sql | retrieve_all )
433
434     ) $/x ) {
435       $cf++;
436     }
437
438     my ($fr, $want, $argdesc);
439     {
440       package DB;
441       $fr = [ CORE::caller($cf) ];
442       $want = ( CORE::caller($cf-1) )[5];
443       $argdesc = ref $DB::args[0]
444         ? DBIx::Class::_Util::refdesc($DB::args[0])
445         : 'non '
446       ;
447     };
448
449     if (
450       $want and $fr->[0] =~ /^(?:DBIx::Class|DBICx::)/
451     ) {
452       DBIx::Class::Exception->throw( sprintf (
453         "Improper use of %s instance in list context at %s line %d\n\n    Stacktrace starts",
454         $argdesc, @{$fr}[1,2]
455       ), 'with_stacktrace');
456     }
457
458     my $mark = [];
459     weaken ( $list_ctx_ok_stack_marker = $mark );
460     $mark;
461   }
462 }
463
464 sub fail_on_internal_call {
465   my ($fr, $argdesc);
466   {
467     package DB;
468     $fr = [ CORE::caller(1) ];
469     $argdesc = ref $DB::args[0]
470       ? DBIx::Class::_Util::refdesc($DB::args[0])
471       : undef
472     ;
473   };
474
475   if (
476     $argdesc
477       and
478     $fr->[0] =~ /^(?:DBIx::Class|DBICx::)/
479       and
480     $fr->[1] !~ /\b(?:CDBICompat|ResultSetProxy)\b/  # no point touching there
481   ) {
482     DBIx::Class::Exception->throw( sprintf (
483       "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",
484       $fr->[3], $argdesc, @{$fr}[1,2], ( $fr->[6] || do {
485         require B::Deparse;
486         no strict 'refs';
487         B::Deparse->new->coderef2text(\&{$fr->[3]})
488       }),
489     ), 'with_stacktrace');
490   }
491 }
492
493 1;