b4c552455705d54c43df1ade9d1c03a8c64147b9
[p5sagit/B-Hooks-EndOfScope-WithFallback.git] / lib / B / Hooks / EndOfScope / WithFallback.pm
1 package B::Hooks::EndOfScope::WithFallback;
2
3 use strict;
4 use warnings;
5
6 our $VERSION = '0.01';
7
8 # note - the tie() fallback will probably work on 5.6 as well,
9 # if you need to go that low - patches passing tests will be accepted
10 use 5.008001;
11
12 my ($v_m_req, $bheos_ver);
13 BEGIN{
14   # Adjust the Makefile.PL if changing this minimum version
15   $v_m_req = '0.34';
16
17   # FIXME - remove if merged with B::H::EOS
18   $bheos_ver = '0.09';
19 }
20
21 BEGIN {
22   *__HAS_VM = eval {
23     require Variable::Magic;
24     Variable::Magic->VERSION($v_m_req);
25   } ? sub () { 1 } : sub () { 0 };
26 }
27
28 # FIXME - remove if merged with B::H::EOS
29 BEGIN {
30   *__HAS_BHEOS = __HAS_VM && eval {
31     require B::Hooks::EndOfScope;
32     B::Hooks::EndOfScope->VERSION ($bheos_ver) 
33   } ? sub () { 1 } : sub () { 0 };
34 }
35
36 use Sub::Exporter -setup => {
37   exports => ['on_scope_end'],
38   groups  => { default => ['on_scope_end'] },
39 };
40
41 =head1 NAME
42
43 B::Hooks::EndOfScope::WithFallback - B::Hooks::EndOfScope without an XS dependency
44
45 =head1 SYNOPSIS
46
47   on_scope_end { ... };
48
49 =head1 DESCRIPTION
50
51 Just like its twin L<B::Hooks::EndOfScope> this module allows you to execute
52 code when perl finished compiling the surrounding scope. The only difference
53 is that this module will function even without the presence of the XS
54 dependency L<Variable::Magic>. The behavior and API is identical to that of
55 L<B::Hooks::EndOfScope> with the exception of one caveat as listed below.
56
57 =head1 WHY ANOTHER MODULE
58
59 While the design of the non-XS implementation is sound and passes every test
60 of the original L<B::Hooks::EndOfScope> distribution, the authors of
61 L<B::Hooks::EndOfScope> are currently not interested in integrating it into
62 their distribution on philosophical grounds.
63
64 =head1 CAVEATS
65
66 Handling exceptions in scope-end callbacks is tricky business. While
67 L<Variable::Magic> has access to some very dark sorcery to make it possible to
68 throw an exception from within a callback, the pure-perl impleentation does
69 not have access to these hacks. Therefore, what would have been a compile-time
70 exception is instead emulated with output on C<STDERR> and an immediate exit
71 via L<POSIX/_exit>. This can potentially have an impact on your code, since
72 no C<END> blocks, nor C<DESTROY> callbacks will execute.
73
74 =head1 FUNCTIONS
75
76 =head2 on_scope_end
77
78     on_scope_end { ... };
79
80     on_scope_end $code;
81
82 Registers C<$code> to be executed after the surrounding scope has been
83 compiled.
84
85 This is exported by default. See L<Sub::Exporter> on how to customize it.
86
87 =cut
88
89 # FIXME - remove if merged with B::H::EOS
90 # already loaded - might as well
91 if (__HAS_BHEOS) {
92   *on_scope_end = \&B::Hooks::EndOfScope::on_scope_end;
93 }
94
95 # we have V::M - just replicate everything here, do not
96 # even try to load B::H::EOS (may or may not be installed)
97 # the amount of code is minimal anyway, and we save on
98 # the syscalls to find/load B/H/EOS.pm
99 elsif (__HAS_VM) {
100
101   my $wiz = Variable::Magic::wizard (
102     data => sub { [$_[1]] },
103     free => sub { $_->() for @{ $_[1] }; () }
104   );
105
106   # str-eval so it is subnamed correctly
107   eval <<'EOS' or die $@;
108
109   sub on_scope_end (&) {
110     my $cb = shift;
111
112     $^H |= 0x020000;
113
114     if (my $stack = Variable::Magic::getdata %^H, $wiz) {
115       push @{ $stack }, $cb;
116     }
117     else {
118       Variable::Magic::cast %^H, $wiz, $cb;
119     }
120   }
121
122   1;  # for the above `or die()`
123
124 EOS
125 }
126 else {
127   # str eval for the above reason and so we do not burn cycles defining
128   # packages we will not use
129   eval <<'EOS' or die $@;
130
131   require Tie::Hash;
132
133   {
134     package B::Hooks::EndOfScope::WithFallback::_TieHintHash;
135
136     use warnings;
137     use strict;
138
139     our @ISA = 'Tie::ExtraHash';
140   }
141
142   # Instead of relying on specific destruction order like the V::M
143   # implementation does, use an explicit array with its own destructor
144   # ordering. This is a potential FIXME for the V::M-using code (it may
145   # very well start unwinding the *other* way), but leaving it as-is
146   # for now to match what B::H::EOS does.
147   {
148     package B::Hooks::EndOfScope::WithFallback::_ScopeGuardArray;
149
150     use warnings;
151     use strict;
152
153     sub new { bless [], ref $_[0] || $_[0] }
154
155     sub DESTROY {
156       local $@ = '';
157       # keep unwinding the stack until something decides to throw
158       while (@{$_[0]} and $@ eq '') {
159         eval { $_[0]->[0]{code}->(); shift @{$_[0]} };
160       }
161
162       if ( (my $err = $@) ne '') {
163         # argh argh argh - why did you have to throw in a scope-end?!
164         # we can not properly throw during a  BEGIN from within pure-perl
165         # (V::M does some very weird XS magic to be able to). However - we
166         # are still compiling - so we can very well just exit() with a long
167         # explanation. Exitting with a normal exit() however won't work, as
168         # it causes perl to segfault (even 5.14), so doing the POSIX thing
169         # instead
170         my $exit_code = $ENV{B_HOOKS_EOS_PP_ON_DIE_EXITCODE};
171         $exit_code = 1 if (! defined $exit_code or ! length $exit_code);
172
173         print STDERR <<EOE; require POSIX; POSIX::_exit($exit_code);
174
175 ========================================================================
176                !!!   F A T A L   E R R O R   !!!
177
178              Exception thrown by scope-end callback
179 ========================================================================
180
181 B::Hooks::EndOfScope::WithFallback is currently operating in pure-perl
182 fallback mode, because your system is lacking the necessary dependency
183 Variable::Magic $v_m_req
184 In this mode B::Hooks::EndOfScope::WithFallback is unable to accomodate
185 callbacks throwing exception, due to the design of perl itself. Your
186 entire application will terminate immediately using POSIX::_exit (this
187 means nothing else beyond this point will execute, including any END
188 blocks you may have defined. The callback originally defined around
189 $_[0]->[0]{caller} terminated with the following error:
190
191 $err
192 EOE
193       }
194     }
195   }
196
197   sub on_scope_end (&) {
198     $^H |= 0x020000;
199
200     my $stack;
201     if(my $t = tied( %^H ) ) {
202       if ( (my $c = ref $t) ne 'B::Hooks::EndOfScope::WithFallback::_TieHintHash') {
203         die <<EOE;
204
205 ========================================================================
206                !!!   F A T A L   E R R O R   !!!
207
208                  foreign tie() of %^H detected
209 ========================================================================
210
211 B::Hooks::EndOfScope::WithFallback is currently operating in pure-perl
212 fallback mode, because your system is lacking the necessary dependency
213 Variable::Magic $v_m_req
214 In this mode B::Hooks::EndOfScope::WithFallback expects to be able to tie()
215 the hinthash %^H, however it is apparently already tied by means unknown to
216 the tie-class $c
217
218 Since this is a no-win situation execution will abort here and now. Please
219 try to find out which other module is relying on hinthash tie() ability,
220 and file a bug for both the perpetrator and B::Hooks::EndOfScope::WithFallback
221 so that the authors can figure out an acceptable way of moving forward.
222
223 EOE
224       }
225       $stack = $t->[1];
226     }
227     else {
228       tie(
229         %^H,
230         'B::Hooks::EndOfScope::WithFallback::_TieHintHash',
231         ($stack = B::Hooks::EndOfScope::WithFallback::_ScopeGuardArray->new),
232       );
233     }
234
235     my ($f, @callsite);
236     do { @callsite = caller(++$f) }
237       while (@callsite and $callsite[1] =~ /\(eval.+\)/);
238
239     push @$stack, {
240       code => shift(),
241       caller => sprintf '%s line %s', @callsite[1,2]
242     };
243   }
244
245   1;  # for the above `or die()`
246
247 EOS
248 }
249
250 =head1 SEE ALSO
251
252 L<B::Hooks::EndOfScope>
253
254 =head1 AUTHOR
255
256 ribasushi: Peter Rabbitson <ribasushi@cpan.org>
257
258 =head1 CONTRIBUTORS
259
260 None as of yet
261
262 =head1 COPYRIGHT
263
264 Copyright (c) 2011 the B::Hooks::EndOfScope::WithFalback L</AUTHOR> and L</CONTRIBUTORS>
265 as listed above.
266
267 =head1 LICENSE
268
269 This library is free software and may be distributed under the same terms
270 as perl itself.
271
272 =cut
273
274 1;