File Coverage

blib/lib/mysubs.pm
Criterion Covered Total %
statement 163 186 87.6
branch 34 58 58.6
condition 2 8 25.0
subroutine 30 35 85.7
pod 2 14 14.2
total 231 301 76.7


line stmt bran cond sub pod time code
1             package mysubs;
2              
3 15     15   7155222 use 5.008001;
  15         68  
  15         725  
4              
5 15     15   93 use strict;
  15         28  
  15         792  
6 15     15   107 use warnings;
  15         53  
  15         2041  
7              
8             use constant {
9 15         1812 UNDO => 0,
10             REDO => 1,
11 15     15   100 };
  15         30  
12              
13 15     15   23765 use B::Hooks::EndOfScope;
  15         576736  
  15         119  
14 15     15   20637 use B::Hooks::OP::Annotation;
  15         24011  
  15         817  
15 15     15   20126 use B::Hooks::OP::Check;
  15         258977  
  15         993  
16 15     15   147 use Carp qw(croak carp);
  15         31  
  15         1273  
17 15     15   16630 use Devel::Pragma qw(ccstash fqname my_hints new_scope on_require);
  15         507946  
  15         136  
18 15     15   4410 use Scalar::Util;
  15         30  
  15         771  
19 15     15   114 use XSLoader;
  15         26  
  15         14221  
20              
21             our $VERSION = '1.14';
22             our @CARP_NOT = qw(B::Hooks::EndOfScope);
23              
24             XSLoader::load(__PACKAGE__, $VERSION);
25              
26             my $DEBUG = xs_get_debug(); # flag indicating whether debug messages should be printed
27              
28             # The key under which the $installed hash is installed in %^H i.e. 'mysubs'
29             # Defined as a preprocessor macro in mysubs.xs to ensure the Perl and XS are kept in sync
30             my $MYSUBS = xs_sig();
31              
32             # accessors for the debug flags - note there is one for Perl ($DEBUG) and one defined
33             # in the XS (MYSUBS_DEBUG). The accessors ensure that the two are kept in sync
34 0     0 0 0 sub get_debug() { $DEBUG }
35 1   50 1 0 116 sub set_debug($) { xs_set_debug($DEBUG = shift || 0) }
36 1     1 0 10 sub start_trace() { set_debug(1) }
37 0     0 0 0 sub stop_trace() { set_debug(0) }
38              
39             # This logs glob transitions i.e. installations and uninstallations of globs - identified
40             # by their IDs (see below)
41             sub debug ($$$$$) {
42 1     1 0 3 my ($class, $action, $fqname, $old, $new) = @_;
43 1         4 my $glold = glob_id($old);
44 1         3 my $glnew = glob_id($new);
45 1         291 carp "$class: $action $fqname ($glold => $glnew)";
46             }
47              
48             # The unique identifier for a typeglob - formatted as a hex value
49             #
50             # There's a bit of indirection in the GV struct that means we have to reach inside
51             # it to get the moral equivalent of its Scalar::Util::refaddr(). That's done in XS,
52             # and this sub pretty-prints it as a hex value
53             sub glob_id($) {
54 2     2 0 12 sprintf '0x%x', xs_glob_id($_[0]);
55             }
56              
57             # return a deep copy of the $installed hash - a hash containing the installed
58             # subs after any invocation of mysubs::import or mysubs::unimport
59             #
60             # the hash is cloned to ensure that inner/nested scopes don't clobber/contaminate
61             # outer/previous scopes with their new bindings. Likewise, unimport installs
62             # a new hash to ensure that previous bindings aren't clobbered e.g.
63             #
64             # {
65             # package Foo;
66             #
67             # use mysubs bar => sub { ... };
68             #
69             # bar();
70             #
71             # no mysubs; # don't clobber the bindings associated with the previous subroutine call
72             # }
73             #
74             # The hash and array refs are copied, but the globs are preserved.
75              
76             # XXX: for some reason, Clone's clone doesn't seem to work here
77             sub clone($) {
78 10     10 0 20 my $orig = shift;
79 10         47 return { map { $_ => [ @{$orig->{$_}} ] } keys %$orig };
  15         23  
  15         1874684  
80             }
81              
82             # return true if $ref ISA $class - works with non-references, unblessed references and objects
83             sub _isa($$) {
84 32     32   76 my ($ref, $class) = @_;
85 32 50       285 return Scalar::Util::blessed(ref) ? $ref->isa($class) : ref($ref) eq $class;
86             }
87              
88             # croak with the name of this package prefixed
89             sub pcroak(@) {
90 0     0 0 0 croak __PACKAGE__, ': ', @_;
91             }
92              
93             # load a perl module
94             sub load($) {
95 0     0 0 0 my $symbol = shift;
96 0         0 my $module = (fqname $symbol)[0];
97 0         0 eval "require $module";
98 0 0       0 pcroak "can't load $module: $@" if ($@);
99             }
100              
101             # install a clone of the current typeglob for the supplied symbol and add a new CODE entry
102             # mst++ and phaylon++ for this idea
103             sub install_sub($$) {
104 32     32 0 71 my ($symbol, $sub) = @_;
105 32         115 my ($stash, $name) = fqname($symbol);
106              
107 15     15   106 no strict 'refs';
  15         43  
  15         26673  
108              
109 32         3388 my $old_glob = delete ${"$stash\::"}{$name};
  32         334  
110              
111             # create the new glob
112 32         47 *{"$stash\::$name"} = $sub;
  32         715  
113              
114             # copy slots over from the old glob
115 32 100       610 if ($old_glob) {
116 20         51 for my $slot (qw(SCALAR ARRAY HASH IO FORMAT)) {
117 100 100       123 *{"$stash\::$name"} = *{$old_glob}{$slot} if (defined *{$old_glob}{$slot});
  22         288  
  22         168  
  100         2137  
118             }
119             }
120              
121 32 50       113 return wantarray ? ($old_glob, *{"$stash\::$name"}) : *{"$stash\::$name"};
  32         541  
  0         0  
122             }
123              
124             # restore the typeglob that existed before the lexical sub was defined - or delete it if it didn't exist
125             sub glob_install($$) {
126 33     33 0 87 my ($symbol, $glob) = @_;
127 33         161 my ($stash, $name) = fqname($symbol);
128              
129 15     15   481 no strict 'refs';
  15         7223  
  15         24162  
130              
131 33         1111 my $old_glob = delete ${"$stash\::"}{$name};
  33         170  
132 33 100       119 ${"$stash\::"}{$name} = $glob if ($glob);
  27         86  
133              
134 33         118 return $old_glob;
135             }
136              
137             # this function is used to enter or leave a lexical context, where "context" means a set of
138             # lexical bindings in the form of globs with or without subroutines in the CODE slot
139             #
140             # for each lexical sub, import() creates or augments a hash that stores globs in the UNDO and REDO slots.
141             # these globs represent the before and after state of the glob corresponding to the supplied
142             # (fully-qualified) sub name. The UNDO glob is the glob prior to any declaration of a lexical
143             # sub with that name, and the REDO glob is the currently-active glob, with the most-recently
144             # defined lexical sub in its CODE slot.
145             #
146             # This data is used to clean up around compile-time requires: install is called to uninstall the
147             # current globs (UNDO); require() is called; then install is called again to reinstall the active
148             # globs (REDO). this ensures lexical subs don't leak across file boundaries if the current package
149             # is re-opened in a required file
150              
151             sub install($$) {
152 22     22 0 39 my ($installed, $action_id) = @_;
153              
154 22         65 for my $fqname (keys %$installed) {
155 22         151 my $action = [ 'uninstalling', 'installing' ]->[$action_id];
156 22         82 my $old_glob = glob_install($fqname, $installed->{$fqname}->[$action_id]);
157              
158 22 50       237 debug('mysubs', $action, $fqname, $old_glob, $installed->{$fqname}->[$action_id]) if ($DEBUG);
159             }
160             }
161              
162             # install one or more lexical subs in the current scope
163             #
164             # import() has to keep track of three things:
165             #
166             # 1) $installed keeps track of *all* currently active lexical subs so that they can be
167             # uninstalled before (compile-time) require() and reinstalled afterwards
168             # 2) $restore keeps track of *all* active lexical subs in the outer scope
169             # so that they can be restored at the end of the current scope
170             # 3) $unimport keeps track of which subs have been installed by *this* class (which may be a subclass of
171             # mysubs) in this scope, so that they can be unimported with "no MyPragma (...)"
172             #
173             # In theory, restoration is done in two passes, the first over $installed and the second over $restore:
174             #
175             # 1) new/overridden: reinstate all the subs in $installed to their previous state in $restore (if any)
176             # 2) deleted: reinstate all the subs in $restore that are not defined in $installed (because
177             # they were explicitly unimported)
178             #
179             # In practice, as an optimization, an auxilliary hash ($remainder) is used to keep track of the
180             # elements of $restore that were removed (via unimport) from $installed. This reduces the overhead
181             # of the second pass so that it doesn't redundantly traverse elements covered by the first pass.
182              
183             sub import_for {
184 31     31 1 498 my ($class, $namespace, %bindings) = @_;
185              
186             # return unless (%bindings);
187              
188 31         266 my $autoload = delete $bindings{-autoload};
189 31         106 my $debug = delete $bindings{-debug};
190 31         127 my $hints = my_hints;
191 31         251 my $caller = ccstash();
192 31         56 my $installed;
193              
194 31 50       133 if (defined $debug) {
195 0         0 my $old_debug = get_debug();
196 0 0       0 if ($debug != $old_debug) {
197 0         0 set_debug($debug);
198 0     0   0 on_scope_end { set_debug($old_debug) };
  0         0  
199             }
200             }
201              
202 31 100       132 if (new_scope($MYSUBS)) {
203 27         850 my $top_level = 0;
204 27         67 my $restore = $hints->{$MYSUBS};
205              
206 27 100       75 if ($restore) {
207 4         15 $installed = $hints->{$MYSUBS} = clone($restore); # clone
208             } else {
209 23         39 $top_level = 1;
210 23         50 $restore = {};
211 23         118 $installed = $hints->{$MYSUBS} = {}; # create
212              
213             # when a compile-time require (or do FILE) is performed, uninstall all
214             # lexical subs (UNDO) and the check hook (xs_leave) beforehand,
215             # and reinstate the lexical subs and check hook afterwards
216              
217             on_require(
218 11     11   376 sub { my $hash = shift; install($hash->{$MYSUBS}, UNDO); xs_leave() },
  11         46  
  11         61  
219 11     11   510596 sub { my $hash = shift; install($hash->{$MYSUBS}, REDO); xs_enter() }
  11         137  
  11         71  
220 23         300 );
221              
222 23         1393 xs_enter();
223             }
224              
225             # keep it around for runtime i.e. prototype()
226 27         118 xs_cache($installed);
227              
228             on_scope_end {
229 27     27   17459 my $hints = my_hints; # refresh the %^H reference - doesn't work without this
230 27         185 my $installed = $hints->{$MYSUBS};
231              
232             # this hash records (or will record) the lexical subs unimported from
233             # the current scope
234 27         81 my $remainder = { %$restore };
235              
236 27         114 for my $fqname (keys %$installed) {
237 0 0       0 if (exists $restore->{$fqname}) {
238 0 0       0 unless (xs_glob_eq($installed->{$fqname}->[REDO], $restore->{$fqname}->[REDO])) {
239 0 0       0 $class->debug(
240             'restoring (overridden)',
241             $fqname,
242             $installed->{$fqname}->[REDO],
243             $restore->{$fqname}->[REDO]
244             ) if ($DEBUG);
245 0         0 glob_install($fqname, $restore->{$fqname}->[REDO]);
246             }
247             } else {
248 0 0       0 $class->debug(
249             'deleting',
250             $fqname,
251             $installed->{$fqname}->[REDO],
252             $installed->{$fqname}->[UNDO]
253             ) if ($DEBUG);
254 0         0 glob_install($fqname, $installed->{$fqname}->[UNDO]);
255             }
256              
257 0         0 delete $remainder->{$fqname};
258             }
259              
260 27         417 for my $fqname (keys %$remainder) {
261 4 50       19 $class->debug(
262             'restoring (unimported)',
263             $fqname,
264             $restore->{$fqname}->[UNDO],
265             $restore->{$fqname}->[REDO]
266             ) if ($DEBUG);
267 4         37 glob_install($fqname, $restore->{$fqname}->[REDO]);
268             }
269 27         299 };
270              
271             # disable mysubs altogether when we leave the top-level scope in which it was enabled
272             # XXX this must be done here i.e. *after* the scope restoration handler
273 27 100       686 on_scope_end \&xs_leave if ($top_level);
274             } else {
275 4         92 $installed = $hints->{$MYSUBS}; # augment
276             }
277              
278             # Note: the namespace-specific unimport data is stored under a mysubs-flavoured name (e.g. "mysubs(MyPragma)")
279             # rather than the unadorned class name (e.g. "MyPragma"). The subclass might well have its own
280             # uses for $^H{$namespace}, so we keep our mitts off it
281             #
282             # Also, the unadorned class name can't be used as the unimport key if the class being used is "mysubs"
283             # itself (i.e. "use mysubs qw(...)" rather than "use MyPragma qw(...)") because
284             # "mysubs" is already spoken for as the installed hash key ($MYSUBS)
285              
286 31         263 my $subclass = "$MYSUBS($namespace)";
287 31         47 my $unimport;
288              
289             # never use the $namespace as the identifier for new_scope() - see above
290 31 100       113 if (new_scope($subclass)) {
291 28         792 my $temp = $hints->{$subclass};
292 28 100       311 $unimport = $hints->{$subclass} = $temp ? { %$temp } : {}; # clone/create
293             } else {
294 3         60 $unimport = $hints->{$subclass}; # augment
295             }
296              
297 31         123 for my $name (keys %bindings) {
298 32         58 my $sub = $bindings{$name};
299              
300             # normalize bindings
301 32 50       94 unless (_isa($sub, 'CODE')) {
302 0   0     0 $sub = do {
303             load($sub) if (($sub =~ s/^\+//) || $autoload);
304 15     15   1288 no strict 'refs';
  15         111  
  15         19437  
305             *{$sub}{CODE}
306             } || pcroak "can't find subroutine: '$sub'";
307             }
308              
309 32         357 my $fqname = fqname($name, $caller);
310 32         1062 my ($old, $new) = install_sub($fqname, $sub);
311              
312 32 100       118 if (exists $installed->{$fqname}) {
313 3 50       182 $class->debug('redefining', $fqname, $old, $new) if ($DEBUG);
314 3         23 $installed->{$fqname}->[REDO] = $new;
315             } else {
316 29 100       203 $class->debug('creating', $fqname, $old, $new) if ($DEBUG);
317 29         133 $installed->{$fqname} = [];
318 29         78 $installed->{$fqname}->[UNDO] = $old;
319 29         118 $installed->{$fqname}->[REDO] = $new;
320             }
321              
322 32         4733 $unimport->{$fqname} = $new;
323             }
324             }
325              
326             sub import {
327 30     30   12822 my $class = shift; # ignore invocant
328 30         128 $class->import_for($class, @_);
329             }
330            
331             # uninstall one or more lexical subs from the current scope
332             sub unimport_for {
333 6     6 1 36 my $class = shift;
334 6         8 my $namespace = shift;
335 6         21 my $hints = my_hints;
336 6         43 my $subclass = "$MYSUBS($namespace)";
337 6         11 my $unimport;
338              
339 6 50 33     50 return unless (($^H & 0x20000) && ($unimport = $hints->{$subclass}));
340              
341 6         22 my $caller = ccstash();
342 6 100       34 my @subs = @_ ? (map { scalar(fqname($_, $caller)) } @_) : keys(%$unimport);
  2         11  
343 6         43 my $installed = $hints->{$MYSUBS};
344 6         17 my $new_installed = clone($installed);
345 6         13 my $deleted = 0;
346              
347 6         14 for my $fqname (@subs) {
348 7         19 my $glob = $unimport->{$fqname};
349              
350 7 50       18 if ($glob) { # the glob this module/subclass installed
351             # if the current glob ($installed->{$fqname}->[REDO]) is the glob this module installed ($unimport->{$fqname})
352 7 50       32 if (xs_glob_eq($glob, $installed->{$fqname}->[REDO])) {
353 7         18 my $old = $installed->{$fqname}->[REDO];
354 7         15 my $new = $installed->{$fqname}->[UNDO];
355              
356 7 50       21 $class->debug('unimporting', $fqname, $old, $new) if ($DEBUG);
357 7         23 glob_install($fqname, $installed->{$fqname}->[UNDO]); # restore the glob to its pre-lexical sub state
358              
359             # what import adds, unimport taketh away
360 7         18 delete $new_installed->{$fqname};
361 7         26 delete $unimport->{$fqname};
362              
363 7         35 ++$deleted;
364             } else {
365 0         0 carp "$namespace: attempt to unimport a shadowed lexical sub: $fqname";
366             }
367             } else {
368 0         0 carp "$namespace: attempt to unimport an undefined lexical sub: $fqname";
369             }
370             }
371              
372 6 50       26 if ($deleted) {
373 6         400 xs_cache($hints->{$MYSUBS} = $new_installed);
374             }
375             }
376              
377             sub unimport {
378 4     4   5008 my $class = shift;
379 4         21 $class->unimport_for($class, @_);
380             }
381              
382             1;
383              
384             __END__