File Coverage

blib/lib/Package/Watchdog.pm
Criterion Covered Total %
statement 42 48 87.5
branch 3 4 75.0
condition 12 16 75.0
subroutine 10 12 83.3
pod 6 6 100.0
total 73 86 84.8


line stmt bran cond sub pod time code
1             package Package::Watchdog;
2 1     1   1145 use strict;
  1         2  
  1         40  
3 1     1   5 use warnings;
  1         2  
  1         108  
4 1     1   6 use Carp;
  1         2  
  1         103  
5 1     1   1033 use Package::Watchdog::Tracker::Watch;
  1         3  
  1         26  
6 1     1   6 use Package::Watchdog::Util;
  1         2  
  1         547  
7              
8             #{{{ POD
9              
10             =pod
11              
12             =head1 NAME
13              
14             Package::Watchdog - Forbid subs in one package from accessing subs in another package, directly or otherwise.
15              
16             =head1 DESCRIPTION
17              
18             A watchdog object will 'watch' several packages and subs in their namespaces.
19             The watchdog has a list of packages and their subs that should be considered
20             off-limits to the packages being watched. If their is a violation, ie a watched
21             package sub accesses a forbidden package sub, the watchdog will react. A
22             watchdog can react by dying, issuing a warning, or running a custom subroutine.
23              
24             =head1 SYNOPSYS
25              
26             First we set some example packages:
27              
28             {
29             # This packages subs will be forbidden
30             package My::Package;
31             use strict;
32             use warnings;
33             sub a { 'a' };
34             sub b { 'b' };
35             sub c { 'c' };
36              
37             # The next packages will be watches for violations
38             # Note, every one calls a sub in the forbidden package
39              
40             package My::WatchA;
41             use strict;
42             use warnings;
43             sub a { My::Package::a() };
44             sub b { My::Package::a() };
45             sub c { My::Package::a() };
46             # ignore for now.
47             sub d { My::Package::d() };
48              
49             package My::WatchB;
50             use strict;
51             use warnings;
52             sub a { My::Package::a() };
53             sub b { My::Package::a() };
54             sub c { My::Package::a() };
55             }
56              
57             Now we set up the watchdog:
58              
59             $wd = Package::Watchdog->new()
60             # All subs in My::WatchA are included in the watch since none are specified
61             ->watch( package => 'My::WatchA', name => 'watch a' )
62             # Only sub a() in My::WatchB will be included in the watch
63             ->watch( package => 'My::WatchB', subs => [ 'a' ], name => 'watch b')
64             # A second watcher will be placed on My::WatchA sub a()
65             ->watch( package => 'My::WatchA', subs => [ 'a' ], name => 'watch c')
66             # All subs will be forbidden if none are listed.
67             ->forbid( 'My::Package' );
68              
69             The subs in My::Package are only forbidden to My::WatchA and My::WatchB, when
70             called outside those packages My::Package susb still work normally.
71              
72             The following will all die after a warning:
73              
74             My::WatchA::a();
75             My::WatchA::b();
76             My::WatchA::c();
77              
78             The following still work:
79              
80             My::Package::a();
81             My::Package::b();
82             My::Package::c();
83              
84             You can make the watchdog bark, but not bite. If you create the watchdog with
85             'warn' as a parameter then violations will generate warnings, but will not die.
86              
87             my $wd = Package::Watchdog->new( 'warn' );
88              
89             You can also create a custom reaction to violations. Please see the custom
90             reaction section for more information. The original sub will be run after the
91             custom reaction unless the custom reaction dies.
92              
93             # Custom reaction
94             my $wd = Package::Watchdog->new( sub { ... } );
95              
96             You can also provide different reactions for each watch:
97              
98             $wd = Package::Watchdog->new()
99             ->watch( package => 'My::WatchA', react => 'warn' )
100             ->watch( package => 'My::WatchB', react => 'die' )
101             ->watch( package => 'My::WatchC', react => sub { ... } );
102              
103             The watchdog can be killed by calling the kill() method. Alternately it can fall out of scope and be destroyed. The following are all ways to kill the watchdog:
104              
105             $wd->kill();
106             $wd = undef; #When no other references to the watchdog exist.
107              
108              
109             {
110             my $wd2 = Package::Watchdog->new();
111             # $wd2 is in effect
112             }
113             # $wd2 is dead.
114              
115             =head1 CUSTOM REACTIONS
116              
117             Custom reactions are anonymous subs.
118              
119             my $react = sub {
120             my %params = @_;
121             ... do stuff ...
122             };
123              
124             The custom react sub will be passed the following:
125              
126             %params = (
127             watch => Package::Watchdog::Tracker::Watch, # The watch that was triggered
128             watched => Package::Watchdog::Sub::Watched, # The class that manages the watched sub that was called.
129             watched_params => [ Params with which the watched sub was called ],
130             forbidden => Package::Watchdog::Sub::Forbidden, # The class that manages the forbidden sub that was called.
131             forbidden_params => [ Params with which the forbidden sub was called ],
132             );
133              
134             It is safe to die within your custom reaction. The forbidden sub will run normally unless the custom reaction dies.
135              
136             =head1 NOTES AND CAVEATS
137              
138             =over 4
139              
140             =item Inherited subs
141              
142             When Package::Watchdog obtains a list of all subs in a package, inherited subs
143             are not included.
144              
145             =back
146              
147             =head1 ACCESSORS
148              
149             The following accessors methods are automatically generated using
150             Package::Watchdog::Util::build_accessors(). These are listed purely for
151             documentation purposes. They are not for use by the user.
152              
153             =over 4
154              
155             =item react()
156              
157             =item watches()
158              
159             =item forbids()
160              
161             =item stack()
162              
163             =back
164              
165             =head1 METHODS
166              
167             Unless otherwise specified methods all return the watchdog object and are chainable.
168              
169             =over 4
170              
171             =cut
172              
173             #}}}
174              
175             our $VERSION = 0.09;
176              
177             my @ACCESSORS = qw/react watches forbids stack/;
178             build_accessors( @ACCESSORS );
179              
180             =item new( $reaction )
181              
182             Create a new watchdog object.
183              
184             $reaction must be one of 'die', 'warn', or a coderef (sub { ... })
185              
186             =cut
187              
188             sub new {
189 3     3 1 846 my $class = shift;
190 3         6 my ( $react ) = @_;
191              
192 3 50 66     36 die( "React must be one of 'die', 'warn', or a coderef." )
      100        
      33        
      66        
193             if $react && $react ne 'die'
194             && $react ne 'warn'
195             && ( ref $react && ref $react ne 'CODE' );
196              
197 3   100     36 my $self = bless(
198             {
199             react => $react || 'die',
200             watches => [],
201             forbids => [],
202             stack => [],
203             },
204             $class
205             );
206              
207 3         11 return $self;
208             }
209              
210             =item watch( package => $package, subs => [ ... ], react => $react, name => $name )
211              
212             Start watching the specified subs in the specified package. If subs is omited
213             or contains '*' then all package subs will be watched.
214              
215             =cut
216              
217             sub watch {
218 5     5 1 13 my $self = shift;
219 5         18 my $watch = Package::Watchdog::Tracker::Watch->new(
220             react => $self->react,
221             @_,
222             stack => $self->stack,
223             );
224 5         11 push @{ $self->watches } => $watch;
  5         35  
225 5         25 return $self;
226             }
227              
228             =item forbid( $package, $subs )
229              
230             Forbid the specified subs in the specified package. The second argument should
231             be an arrayref.
232              
233             =cut
234              
235             sub forbid {
236 4     4 1 6 my $self = shift;
237 4         6 my ( $package, $subs ) = @_;
238 4   100     11 my $forbid = Package::Watchdog::Tracker::Forbid->new(
239             package => $package,
240             stack => $self->stack,
241             subs => $subs || undef,
242             );
243 4         4 push @{ $self->forbids } => $forbid;
  4         11  
244 4         14 return $self;
245             }
246              
247             =item unwatch()
248              
249             *Unimplemented.*
250              
251             =cut
252              
253             sub unwatch {
254 0     0 1 0 my $self = shift;
255 0         0 die( "Not yet implemented." );
256 0         0 return $self; #I know pointless atm.
257             }
258              
259             =item unforbid()
260              
261             *Unimplemented.*
262              
263             =cut
264              
265             sub unforbid {
266 0     0 1 0 my $self = shift;
267 0         0 die( "Not yet implemented." );
268 0         0 return $self; #I know pointless atm.
269             }
270              
271             =item kill()
272              
273             Will make the watchdog inefective, removes all watches and forbids.
274              
275             =cut
276              
277             sub kill {
278 4     4 1 6 my $self = shift;
279              
280 4         19 my %instances = %Package::Watchdog::Sub::INSTANCES;
281 4         12 for my $class ( keys %instances ) {
282 6         8 for my $package ( keys %{ $instances{$class}}) {
  6         20  
283 12         15 for my $sub( keys %{ $instances{$class}{$package}}) {
  12         45  
284 27         45 my $item = $instances{$class}{$package}{$sub};
285 27 100       100 $item->restore() if $item;
286             }
287             }
288             }
289              
290 4         165 return $self;
291             }
292              
293             sub DESTROY {
294 3     3   776 shift->kill();
295             }
296              
297             1;
298              
299             __END__