Cleaner assertion envvar handling
[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 is_exception detected_reinvoked_destructor
83   quote_sub qsub perlstring serialize deep_clone
84   UNRESOLVABLE_CONDITION
85 );
86
87 use constant UNRESOLVABLE_CONDITION => \ '1 = 0';
88
89 sub sigwarn_silencer ($) {
90   my $pattern = shift;
91
92   croak "Expecting a regexp" if ref $pattern ne 'Regexp';
93
94   my $orig_sig_warn = $SIG{__WARN__} || sub { CORE::warn(@_) };
95
96   return sub { &$orig_sig_warn unless $_[0] =~ $pattern };
97 }
98
99 sub perlstring ($) { q{"}. quotemeta( shift ). q{"} };
100
101 sub hrefaddr ($) { sprintf '0x%x', &refaddr||0 }
102
103 sub refdesc ($) {
104   croak "Expecting a reference" if ! length ref $_[0];
105
106   # be careful not to trigger stringification,
107   # reuse @_ as a scratch-pad
108   sprintf '%s%s(0x%x)',
109     ( defined( $_[1] = blessed $_[0]) ? "$_[1]=" : '' ),
110     reftype $_[0],
111     refaddr($_[0]),
112   ;
113 }
114
115 sub refcount ($) {
116   croak "Expecting a reference" if ! length ref $_[0];
117
118   # No tempvars - must operate on $_[0], otherwise the pad
119   # will count as an extra ref
120   B::svref_2object($_[0])->REFCNT;
121 }
122
123 sub serialize ($) {
124   local $Storable::canonical = 1;
125   nfreeze($_[0]);
126 }
127
128 sub scope_guard (&) {
129   croak 'Calling scope_guard() in void context makes no sense'
130     if ! defined wantarray;
131
132   # no direct blessing of coderefs - DESTROY is buggy on those
133   bless [ $_[0] ], 'DBIx::Class::_Util::ScopeGuard';
134 }
135 {
136   package #
137     DBIx::Class::_Util::ScopeGuard;
138
139   sub DESTROY {
140     &DBIx::Class::_Util::detected_reinvoked_destructor;
141
142     local $@ if DBIx::Class::_ENV_::UNSTABLE_DOLLARAT;
143
144     eval {
145       $_[0]->[0]->();
146       1;
147     } or do {
148       Carp::cluck "Execution of scope guard $_[0] resulted in the non-trappable exception:\n\n$@";
149     };
150   }
151 }
152
153
154 sub is_exception ($) {
155   my $e = $_[0];
156
157   # FIXME
158   # this is not strictly correct - an eval setting $@ to undef
159   # is *not* the same as an eval setting $@ to ''
160   # but for the sake of simplicity assume the following for
161   # the time being
162   return 0 unless defined $e;
163
164   my ($not_blank, $suberror);
165   {
166     local $@;
167     eval {
168       # The ne() here is deliberate - a plain length($e), or worse "$e" ne
169       # will entirely obviate the need for the encolsing eval{}, as the
170       # condition we guard against is a missing fallback overload
171       $not_blank = ( $e ne '' );
172       1;
173     } or $suberror = $@;
174   }
175
176   if (defined $suberror) {
177     if (length (my $class = blessed($e) )) {
178       carp_unique( sprintf(
179         'External exception class %s implements partial (broken) overloading '
180       . 'preventing its instances from being used in simple ($x eq $y) '
181       . 'comparisons. Given Perl\'s "globally cooperative" exception '
182       . 'handling this type of brokenness is extremely dangerous on '
183       . 'exception objects, as it may (and often does) result in silent '
184       . '"exception substitution". DBIx::Class tries to work around this '
185       . 'as much as possible, but other parts of your software stack may '
186       . 'not be even aware of this. Please submit a bugreport against the '
187       . 'distribution containing %s and in the meantime apply a fix similar '
188       . 'to the one shown at %s, in order to ensure your exception handling '
189       . 'is saner application-wide. What follows is the actual error text '
190       . "as generated by Perl itself:\n\n%s\n ",
191         $class,
192         $class,
193         'http://v.gd/DBIC_overload_tempfix/',
194         $suberror,
195       ));
196
197       # workaround, keeps spice flowing
198       $not_blank = !!( length $e );
199     }
200     else {
201       # not blessed yet failed the 'ne'... this makes 0 sense...
202       # just throw further
203       die $suberror
204     }
205   }
206   elsif (
207     # a ref evaluating to '' is definitively a "null object"
208     ( not $not_blank )
209       and
210     length( my $class = ref $e )
211   ) {
212     carp_unique( sprintf(
213       "Objects of external exception class '%s' stringify to '' (the "
214     . 'empty string), implementing the so called null-object-pattern. '
215     . 'Given Perl\'s "globally cooperative" exception handling using this '
216     . 'class of exceptions is extremely dangerous, as it may (and often '
217     . 'does) result in silent discarding of errors. DBIx::Class tries to '
218     . 'work around this as much as possible, but other parts of your '
219     . 'software stack may not be even aware of the problem. Please submit '
220     . 'a bugreport against the distribution containing %s',
221
222       ($class) x 2,
223     ));
224
225     $not_blank = 1;
226   }
227
228   return $not_blank;
229 }
230
231 {
232   my $destruction_registry = {};
233
234   sub CLONE {
235     $destruction_registry = { map
236       { defined $_ ? ( refaddr($_) => $_ ) : () }
237       values %$destruction_registry
238     };
239   }
240
241   # This is almost invariably invoked from within DESTROY
242   # throwing exceptions won't work
243   sub detected_reinvoked_destructor {
244
245     # quick "garbage collection" pass - prevents the registry
246     # from slowly growing with a bunch of undef-valued keys
247     defined $destruction_registry->{$_} or delete $destruction_registry->{$_}
248       for keys %$destruction_registry;
249
250     if (! length ref $_[0]) {
251       printf STDERR '%s() expects a blessed reference %s',
252         (caller(0))[3],
253         Carp::longmess,
254       ;
255       return undef; # don't know wtf to do
256     }
257     elsif (! defined $destruction_registry->{ my $addr = refaddr($_[0]) } ) {
258       weaken( $destruction_registry->{$addr} = $_[0] );
259       return 0;
260     }
261     else {
262       carp_unique ( sprintf (
263         'Preventing *MULTIPLE* DESTROY() invocations on %s - an *EXTREMELY '
264       . 'DANGEROUS* condition which is *ALMOST CERTAINLY GLOBAL* within your '
265       . 'application, affecting *ALL* classes without active protection against '
266       . 'this. Diagnose and fix the root cause ASAP!!!%s',
267       refdesc $_[0],
268         ( ( $INC{'Devel/StackTrace.pm'} and ! do { local $@; eval { Devel::StackTrace->VERSION(2) } } )
269           ? " (likely culprit Devel::StackTrace\@@{[ Devel::StackTrace->VERSION ]} found in %INC, http://is.gd/D_ST_refcap)"
270           : ''
271         )
272       ));
273
274       return 1;
275     }
276   }
277 }
278
279 my $module_name_rx = qr/ \A [A-Z_a-z] [0-9A-Z_a-z]* (?: :: [0-9A-Z_a-z]+ )* \z /x;
280 my $ver_rx =         qr/ \A [0-9]+ (?: \. [0-9]+ )* (?: \_ [0-9]+ )*        \z /x;
281
282 sub modver_gt_or_eq ($$) {
283   my ($mod, $ver) = @_;
284
285   croak "Nonsensical module name supplied"
286     if ! defined $mod or $mod !~ $module_name_rx;
287
288   croak "Nonsensical minimum version supplied"
289     if ! defined $ver or $ver !~ $ver_rx;
290
291   no strict 'refs';
292   my $ver_cache = ${"${mod}::__DBIC_MODULE_VERSION_CHECKS__"} ||= ( $mod->VERSION
293     ? {}
294     : croak "$mod does not seem to provide a version (perhaps it never loaded)"
295   );
296
297   ! defined $ver_cache->{$ver}
298     and
299   $ver_cache->{$ver} = do {
300
301     local $SIG{__WARN__} = sigwarn_silencer( qr/\Qisn't numeric in subroutine entry/ )
302       if SPURIOUS_VERSION_CHECK_WARNINGS;
303
304     local $@;
305     local $SIG{__DIE__};
306     eval { $mod->VERSION($ver) } ? 1 : 0;
307   };
308
309   $ver_cache->{$ver};
310 }
311
312 sub modver_gt_or_eq_and_lt ($$$) {
313   my ($mod, $v_ge, $v_lt) = @_;
314
315   croak "Nonsensical maximum version supplied"
316     if ! defined $v_lt or $v_lt !~ $ver_rx;
317
318   return (
319     modver_gt_or_eq($mod, $v_ge)
320       and
321     ! modver_gt_or_eq($mod, $v_lt)
322   ) ? 1 : 0;
323 }
324
325 {
326   my $list_ctx_ok_stack_marker;
327
328   sub fail_on_internal_wantarray () {
329     return if $list_ctx_ok_stack_marker;
330
331     if (! defined wantarray) {
332       croak('fail_on_internal_wantarray() needs a tempvar to save the stack marker guard');
333     }
334
335     my $cf = 1;
336     while ( ( (CORE::caller($cf+1))[3] || '' ) =~ / :: (?:
337
338       # these are public API parts that alter behavior on wantarray
339       search | search_related | slice | search_literal
340
341         |
342
343       # these are explicitly prefixed, since we only recognize them as valid
344       # escapes when they come from the guts of CDBICompat
345       CDBICompat .*? :: (?: search_where | retrieve_from_sql | retrieve_all )
346
347     ) $/x ) {
348       $cf++;
349     }
350
351     my ($fr, $want, $argdesc);
352     {
353       package DB;
354       $fr = [ CORE::caller($cf) ];
355       $want = ( CORE::caller($cf-1) )[5];
356       $argdesc = ref $DB::args[0]
357         ? DBIx::Class::_Util::refdesc($DB::args[0])
358         : 'non '
359       ;
360     };
361
362     if (
363       $want and $fr->[0] =~ /^(?:DBIx::Class|DBICx::)/
364     ) {
365       DBIx::Class::Exception->throw( sprintf (
366         "Improper use of %s instance in list context at %s line %d\n\n    Stacktrace starts",
367         $argdesc, @{$fr}[1,2]
368       ), 'with_stacktrace');
369     }
370
371     my $mark = [];
372     weaken ( $list_ctx_ok_stack_marker = $mark );
373     $mark;
374   }
375 }
376
377 sub fail_on_internal_call {
378   my ($fr, $argdesc);
379   {
380     package DB;
381     $fr = [ CORE::caller(1) ];
382     $argdesc = ref $DB::args[0]
383       ? DBIx::Class::_Util::refdesc($DB::args[0])
384       : undef
385     ;
386   };
387
388   if (
389     $argdesc
390       and
391     $fr->[0] =~ /^(?:DBIx::Class|DBICx::)/
392       and
393     $fr->[1] !~ /\b(?:CDBICompat|ResultSetProxy)\b/  # no point touching there
394   ) {
395     DBIx::Class::Exception->throw( sprintf (
396       "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",
397       $fr->[3], $argdesc, @{$fr}[1,2], ( $fr->[6] || do {
398         require B::Deparse;
399         no strict 'refs';
400         B::Deparse->new->coderef2text(\&{$fr->[3]})
401       }),
402     ), 'with_stacktrace');
403   }
404 }
405
406 1;