File Coverage

blib/lib/Devel/GlobalPhase.pm
Criterion Covered Total %
statement 26 26 100.0
branch 8 12 66.6
condition 4 9 44.4
subroutine 8 8 100.0
pod 1 2 50.0
total 47 57 82.4


line stmt bran cond sub pod time code
1             package Devel::GlobalPhase;
2 5     5   7200 use strict;
  5         6  
  5         126  
3 5     5   18 use warnings;
  5         6  
  5         229  
4              
5             our $VERSION = '0.003000';
6             $VERSION = eval $VERSION;
7              
8 5     5   17 use base 'Exporter';
  5         7  
  5         869  
9              
10             our @EXPORT = qw(global_phase);
11              
12             BEGIN {
13 5 50 33 5   57 *_CALLER_CAN_SEGFAULT = ("$]" >= 5.008009 && "$]" < 5.018000) ? sub(){1} : sub(){0};
14 5 50       880 *_NATIVE_GLOBAL_PHASE = "$]" >= 5.014000 ? sub(){1} : sub(){0};
15             }
16              
17             sub global_phase ();
18             sub tie_global_phase;
19             sub _refresh_END ();
20              
21             sub import {
22 4     4   18 my $class = shift;
23 4         4 my $var;
24             my @imports = map {
25 4 50 33     5 ($_ && $_ eq '-var') ? do {
  1         8  
26 1         1 $var = 1;
27 1         2 ();
28             } : $_;
29             } @_;
30 4 100 66     25 if (@imports || !$var) {
31 3         193 Exporter::export_to_level($class, 1, @imports);
32             }
33 4         9 _refresh_END;
34 4 100       49 if ($var) {
35 1         18 tie_global_phase;
36             }
37             }
38              
39             BEGIN {
40 5     5   9 if (_NATIVE_GLOBAL_PHASE) {
41 5 50   8 1 435 eval <<'END_CODE' or die $@;
  8     1 0 1583  
  1         24  
42              
43             sub global_phase () {
44             return ${^GLOBAL_PHASE};
45             }
46              
47             sub tie_global_phase { 1 }
48              
49             sub _refresh_END () { 1 }
50              
51             1;
52              
53             END_CODE
54             }
55             else {
56             eval <<'END_CODE' or die $@;
57              
58             use B ();
59              
60             my $global_phase = 'START';
61             if (B::main_start()->isa('B::NULL')) {
62             # loaded during initial compile
63             eval <<'END_EVAL' or die $@;
64              
65             CHECK { $global_phase = 'CHECK' }
66             # try to install an END block as late as possible so it will run first.
67             INIT { my $capture = $global_phase; eval q( END { $global_phase = 'END' } ) }
68             # INIT is FIFO so we can force our sub to be first
69             unshift @{ B::init_av()->object_2svref }, sub { $global_phase = 'INIT' };
70              
71             1;
72              
73             END_EVAL
74             }
75             else {
76             # loaded during runtime
77             $global_phase = 'RUN';
78             }
79             END { $global_phase = 'END' }
80              
81             sub _refresh_END () {
82             my $capture = $global_phase;
83             eval q[ END { $global_phase = 'END' } ];
84             }
85              
86             sub global_phase () {
87             if ($global_phase eq 'DESTRUCT') {
88             # no need for extra checks at this point
89             }
90             elsif ($global_phase eq 'START') {
91             # we use a CHECK block to set this as well, but we can't force
92             # ours to run before other CHECKS
93             if (!B::main_root()->isa('B::NULL') && B::main_cv()->DEPTH == 0) {
94             $global_phase = 'CHECK';
95             }
96             }
97             elsif (${B::main_cv()} == 0) {
98             $global_phase = 'DESTRUCT';
99             }
100             elsif ($global_phase eq 'INIT' && B::main_cv()->DEPTH > 0) {
101             _refresh_END;
102             $global_phase = 'RUN';
103             }
104              
105             # this is slow and can segfault, so skip it
106             if (!_CALLER_CAN_SEGFAULT && $global_phase eq 'RUN' && $^S) {
107             # END blocks are FILO so we can't install one to run first.
108             # only way to detect END reliably seems to be by using caller.
109             # I hate this but it seems to be the best available option.
110             # The top two frames will be an eval and the END block.
111             my $i = 0;
112             $i++ while defined CORE::caller($i + 1);
113             if ($i < 1) {
114             # there should always be the sub call and an eval frame ($^S is true).
115             # this will only happen if we're in END, but the outer frames are broken.
116             $global_phase = 'END';
117             }
118             elsif ($i > 1) {
119             my $top = CORE::caller($i);
120             my $next = CORE::caller($i - 1);
121             if (!$top || !$next) {
122             $global_phase = 'END';
123             }
124             elsif ($top eq 'main' && $next eq 'main') {
125             # If we're ENDing due to an exit or die in a sub generated in an eval,
126             # these caller calls can cause a segfault. I can't find a way to detect
127             # this.
128             my @top = CORE::caller($i);
129             my @next = CORE::caller($i - 1);
130             if (
131             $top[3] eq '(eval)'
132             && $next[3] =~ /::END$/
133             && $top[2] == $next[2]
134             && $top[1] eq $next[1]
135             ) {
136             $global_phase = 'END';
137             }
138             }
139             }
140             }
141              
142             return $global_phase;
143             }
144              
145             {
146             package # hide
147             Devel::GlobalPhase::_Tie;
148              
149             sub TIESCALAR { bless \(my $s), $_[0]; }
150             sub STORE {
151             die sprintf "Modification of a read-only value attempted at %s line %s.\n", (caller(0))[1,2];
152             }
153             sub FETCH {
154             return undef
155             if caller eq 'Devel::GlobalDestruction';
156             Devel::GlobalPhase::global_phase;
157             }
158             sub DESTROY {
159             my $tied = tied ${^GLOBAL_PHASE};
160             if ($tied && $tied == $_[0]) {
161             untie ${^GLOBAL_PHASE};
162             my $phase = Devel::GlobalPhase::global_phase;
163             Internals::SvREADONLY($phase, 1) if defined &Internals::SvREADONLY;
164             *{^GLOBAL_PHASE} = \$phase;
165             }
166             }
167             }
168              
169             sub tie_global_phase {
170             unless ('Devel::GlobalPhase::_Tie' eq ref tied ${^GLOBAL_PHASE}) {
171             tie ${^GLOBAL_PHASE}, 'Devel::GlobalPhase::_Tie';
172             }
173             1;
174             }
175              
176             1;
177             END_CODE
178             }
179             }
180              
181             1;
182              
183             __END__