Audit all local() calls within lib/ and t/lib
[dbsrgits/DBIx-Class.git] / lib / DBIx / Class / Schema.pm
index dca80c8..3bcc37f 100644 (file)
@@ -4,21 +4,25 @@ use strict;
 use warnings;
 
 use base 'DBIx::Class';
+use mro 'c3';
 
 use DBIx::Class::Carp;
 use Try::Tiny;
 use Scalar::Util qw/weaken blessed/;
-use DBIx::Class::_Util qw(refcount quote_sub);
+use DBIx::Class::_Util qw(
+  refcount quote_sub scope_guard
+  is_exception dbic_internal_try
+  fail_on_internal_call
+);
 use Devel::GlobalDestruction;
 use namespace::clean;
 
-__PACKAGE__->mk_classdata('class_mappings' => {});
-__PACKAGE__->mk_classdata('source_registrations' => {});
-__PACKAGE__->mk_classdata('storage_type' => '::DBI');
-__PACKAGE__->mk_classdata('storage');
-__PACKAGE__->mk_classdata('exception_action');
-__PACKAGE__->mk_classdata('stacktrace' => $ENV{DBIC_TRACE} || 0);
-__PACKAGE__->mk_classdata('default_resultset_attributes' => {});
+__PACKAGE__->mk_group_accessors( inherited => qw( storage exception_action ) );
+__PACKAGE__->mk_classaccessor('class_mappings' => {});
+__PACKAGE__->mk_classaccessor('source_registrations' => {});
+__PACKAGE__->mk_classaccessor('storage_type' => '::DBI');
+__PACKAGE__->mk_classaccessor('stacktrace' => $ENV{DBIC_TRACE} || 0);
+__PACKAGE__->mk_classaccessor('default_resultset_attributes' => {});
 
 =head1 NAME
 
@@ -191,7 +195,7 @@ sub _ns_get_rsrc_instance {
   my $me = shift;
   my $rs_class = ref ($_[0]) || $_[0];
 
-  return try {
+  return dbic_internal_try {
     $rs_class->result_source_instance
   } catch {
     $me->throw_exception (
@@ -521,7 +525,10 @@ version, overload L</connection> instead.
 
 =cut
 
-sub connect { shift->clone->connection(@_) }
+sub connect {
+  DBIx::Class::_ENV_::ASSERT_NO_INTERNAL_INDIRECT_CALLS and fail_on_internal_call;
+  shift->clone->connection(@_);
+}
 
 =head2 resultset
 
@@ -765,6 +772,8 @@ those values.
 =cut
 
 sub populate {
+  DBIx::Class::_ENV_::ASSERT_NO_INTERNAL_INDIRECT_CALLS and fail_on_internal_call;
+
   my ($self, $name, $data) = @_;
   my $rs = $self->resultset($name)
     or $self->throw_exception("'$name' is not a resultset");
@@ -803,7 +812,7 @@ sub connection {
 
   $storage_class =~ s/^::/DBIx::Class::Storage::/;
 
-  try {
+  dbic_internal_try {
     $self->ensure_class_loaded ($storage_class);
   }
   catch {
@@ -862,25 +871,6 @@ will produce the output
 
 =cut
 
-# this might be oversimplified
-# sub compose_namespace {
-#   my ($self, $target, $base) = @_;
-
-#   my $schema = $self->clone;
-#   foreach my $source_name ($schema->sources) {
-#     my $source = $schema->source($source_name);
-#     my $target_class = "${target}::${source_name}";
-#     $self->inject_base(
-#       $target_class => $source->result_class, ($base ? $base : ())
-#     );
-#     $source->result_class($target_class);
-#     $target_class->result_source_instance($source)
-#       if $target_class->can('result_source_instance');
-#     $schema->register_source($source_name, $source);
-#   }
-#   return $schema;
-# }
-
 sub compose_namespace {
   my ($self, $target, $base) = @_;
 
@@ -918,6 +908,7 @@ sub compose_namespace {
       }
     }
 
+    # Legacy stuff, not inserting INDIRECT assertions
     quote_sub "${target}::${_}" => "shift->schema->$_(\@_)"
       for qw(class source resultset);
   }
@@ -1055,26 +1046,70 @@ default behavior will provide a detailed stack trace.
 =cut
 
 sub throw_exception {
-  my $self = shift;
+  my ($self, @args) = @_;
+
+  if (
+    ! DBIx::Class::_Util::in_internal_try()
+      and
+    my $act = $self->exception_action
+  ) {
+
+    my $guard_disarmed;
+
+    my $guard = scope_guard {
+      return if $guard_disarmed;
+      local $SIG{__WARN__} if $SIG{__WARN__};
+      Carp::cluck("
+                    !!! DBIx::Class INTERNAL PANIC !!!
 
-  if (my $act = $self->exception_action) {
-    if ($act->(@_)) {
-      DBIx::Class::Exception->throw(
+The exception_action() handler installed on '$self'
+aborted the stacktrace below via a longjmp (either via Return::Multilevel or
+plain goto, or Scope::Upper or something equally nefarious). There currently
+is nothing safe DBIx::Class can do, aside from displaying this error. A future
+version ( 0.082900, when available ) will reduce the cases in which the
+handler is invoked, but this is neither a complete solution, nor can it do
+anything for other software that might be affected by a similar problem.
+
+                      !!! FIX YOUR ERROR HANDLING !!!
+
+This guard was activated beginning"
+      );
+    };
+
+    dbic_internal_try {
+      # if it throws - good, we'll assign to @args in the end
+      # if it doesn't - do different things depending on RV truthiness
+      if( $act->(@args) ) {
+        $args[0] = (
           "Invocation of the exception_action handler installed on $self did *not*"
         .' result in an exception. DBIx::Class is unable to function without a reliable'
-        .' exception mechanism, ensure that exception_action does not hide exceptions'
-        ." (original error: $_[0])"
-      );
+        .' exception mechanism, ensure your exception_action does not hide exceptions'
+        ." (original error: $args[0])"
+        );
+      }
+      else {
+        carp_unique (
+          "The exception_action handler installed on $self returned false instead"
+        .' of throwing an exception. This behavior has been deprecated, adjust your'
+        .' handler to always rethrow the supplied error'
+        );
+      }
+
+      1;
     }
+    catch {
+      # We call this to get the necessary warnings emitted and disregard the RV
+      # as it's definitely an exception if we got as far as this catch{} block
+      is_exception(
+        $args[0] = $_
+      );
+    };
 
-    carp_unique (
-      "The exception_action handler installed on $self returned false instead"
-    .' of throwing an exception. This behavior has been deprecated, adjust your'
-    .' handler to always rethrow the supplied error.'
-    );
+    # Done guarding against https://github.com/PerlDancer/Dancer2/issues/1125
+    $guard_disarmed = 1;
   }
 
-  DBIx::Class::Exception->throw($_[0], $self->stacktrace);
+  DBIx::Class::Exception->throw( $args[0], $self->stacktrace );
 }
 
 =head2 deploy
@@ -1192,14 +1227,12 @@ format.
 sub ddl_filename {
   my ($self, $type, $version, $dir, $preversion) = @_;
 
-  require File::Spec;
-
   $version = "$preversion-$version" if $preversion;
 
   my $class = blessed($self) || $self;
   $class =~ s/::/-/g;
 
-  return File::Spec->catfile($dir, "$class-$version-$type.sql");
+  return "$dir/$class-$version-$type.sql";
 }
 
 =head2 thaw
@@ -1213,7 +1246,6 @@ reference to any schema, so are rather useless.
 sub thaw {
   my ($self, $obj) = @_;
   local $DBIx::Class::ResultSourceHandle::thaw_schema = $self;
-  require Storable;
   return Storable::thaw($obj);
 }
 
@@ -1225,7 +1257,6 @@ it is just provided here for symmetry.
 =cut
 
 sub freeze {
-  require Storable;
   return Storable::nfreeze($_[1]);
 }
 
@@ -1248,7 +1279,6 @@ objects so their references to the schema object
 sub dclone {
   my ($self, $obj) = @_;
   local $DBIx::Class::ResultSourceHandle::thaw_schema = $self;
-  require Storable;
   return Storable::dclone($obj);
 }
 
@@ -1363,7 +1393,7 @@ sub _register_source {
   return $source if $params->{extra};
 
   my $rs_class = $source->result_class;
-  if ($rs_class and my $rsrc = try { $rs_class->result_source_instance } ) {
+  if ($rs_class and my $rsrc = dbic_internal_try { $rs_class->result_source_instance } ) {
     my %map = %{$self->class_mappings};
     if (
       exists $map{$rs_class}
@@ -1388,6 +1418,9 @@ sub _register_source {
 
 my $global_phase_destroy;
 sub DESTROY {
+  ### NO detected_reinvoked_destructor check
+  ### This code very much relies on being called multuple times
+
   return if $global_phase_destroy ||= in_global_destruction;
 
   my $self = shift;
@@ -1402,7 +1435,8 @@ sub DESTROY {
     # however beware - on older perls the exception seems randomly untrappable
     # due to some weird race condition during thread joining :(((
     if (length ref $srcs->{$source_name} and refcount($srcs->{$source_name}) > 1) {
-      local $@;
+      local $SIG{__DIE__} if $SIG{__DIE__};
+      local $@ if DBIx::Class::_ENV_::UNSTABLE_DOLLARAT;
       eval {
         $srcs->{$source_name}->schema($self);
         weaken $srcs->{$source_name};
@@ -1414,6 +1448,11 @@ sub DESTROY {
       last;
     }
   }
+
+  # Dummy NEXTSTATE ensuring the all temporaries on the stack are garbage
+  # collected before leaving this scope. Depending on the code above, this
+  # may very well be just a preventive measure guarding future modifications
+  undef;
 }
 
 sub _unregister_source {
@@ -1470,13 +1509,12 @@ sub compose_connection {
   carp_once "compose_connection deprecated as of 0.08000"
     unless $INC{"DBIx/Class/CDBICompat.pm"};
 
-  my $base = 'DBIx::Class::ResultSetProxy';
-  try {
-    eval "require ${base};"
+  dbic_internal_try {
+    require DBIx::Class::ResultSetProxy;
   }
   catch {
     $self->throw_exception
-      ("No arguments to load_classes and couldn't load ${base} ($_)")
+      ("No arguments to load_classes and couldn't load DBIx::Class::ResultSetProxy ($_)")
   };
 
   if ($self eq $target) {
@@ -1484,15 +1522,15 @@ sub compose_connection {
     foreach my $source_name ($self->sources) {
       my $source = $self->source($source_name);
       my $class = $source->result_class;
-      $self->inject_base($class, $base);
-      $class->mk_classdata(resultset_instance => $source->resultset);
-      $class->mk_classdata(class_resolver => $self);
+      $self->inject_base($class, 'DBIx::Class::ResultSetProxy');
+      $class->mk_classaccessor(resultset_instance => $source->resultset);
+      $class->mk_classaccessor(class_resolver => $self);
     }
     $self->connection(@info);
     return $self;
   }
 
-  my $schema = $self->compose_namespace($target, $base);
+  my $schema = $self->compose_namespace($target, 'DBIx::Class::ResultSetProxy');
   quote_sub "${target}::schema", '$s', { '$s' => \$schema };
 
   $schema->connection(@info);
@@ -1500,21 +1538,24 @@ sub compose_connection {
     my $source = $schema->source($source_name);
     my $class = $source->result_class;
     #warn "$source_name $class $source ".$source->storage;
-    $class->mk_classdata(result_source_instance => $source);
-    $class->mk_classdata(resultset_instance => $source->resultset);
-    $class->mk_classdata(class_resolver => $schema);
+    $class->mk_classaccessor(result_source_instance => $source);
+    $class->mk_classaccessor(resultset_instance => $source->resultset);
+    $class->mk_classaccessor(class_resolver => $schema);
   }
   return $schema;
 }
 
-1;
-
-=head1 AUTHOR AND CONTRIBUTORS
+=head1 FURTHER QUESTIONS?
 
-See L<AUTHOR|DBIx::Class/AUTHOR> and L<CONTRIBUTORS|DBIx::Class/CONTRIBUTORS> in DBIx::Class
+Check the list of L<additional DBIC resources|DBIx::Class/GETTING HELP/SUPPORT>.
 
-=head1 LICENSE
+=head1 COPYRIGHT AND LICENSE
 
-You may distribute this code under the same terms as Perl itself.
+This module is free software L<copyright|DBIx::Class/COPYRIGHT AND LICENSE>
+by the L<DBIx::Class (DBIC) authors|DBIx::Class/AUTHORS>. You can
+redistribute it and/or modify it under the same terms as the
+L<DBIx::Class library|DBIx::Class/COPYRIGHT AND LICENSE>.
 
 =cut
+
+1;