blob: 862f578c172f5d76358e49abc34833b76b464891 [file] [log] [blame]
Rashed Abdel-Tawab4db47f42019-09-06 10:38:22 -07001use 5.008;
2package base;
3
4use strict 'vars';
5use vars qw($VERSION);
6$VERSION = '2.26';
7$VERSION =~ tr/_//d;
8
9# simplest way to avoid indexing of the package: no package statement
10sub base::__inc::unhook { @INC = grep !(ref eq 'CODE' && $_ == $_[0]), @INC }
11# instance is blessed array of coderefs to be removed from @INC at scope exit
12sub base::__inc::scope_guard::DESTROY { base::__inc::unhook $_ for @{$_[0]} }
13
14# constant.pm is slow
15sub SUCCESS () { 1 }
16
17sub PUBLIC () { 2**0 }
18sub PRIVATE () { 2**1 }
19sub INHERITED () { 2**2 }
20sub PROTECTED () { 2**3 }
21
22my $Fattr = \%fields::attr;
23
24sub has_fields {
25 my($base) = shift;
26 my $fglob = ${"$base\::"}{FIELDS};
27 return( ($fglob && 'GLOB' eq ref($fglob) && *$fglob{HASH}) ? 1 : 0 );
28}
29
30sub has_attr {
31 my($proto) = shift;
32 my($class) = ref $proto || $proto;
33 return exists $Fattr->{$class};
34}
35
36sub get_attr {
37 $Fattr->{$_[0]} = [1] unless $Fattr->{$_[0]};
38 return $Fattr->{$_[0]};
39}
40
41if ($] < 5.009) {
42 *get_fields = sub {
43 # Shut up a possible typo warning.
44 () = \%{$_[0].'::FIELDS'};
45 my $f = \%{$_[0].'::FIELDS'};
46
47 # should be centralized in fields? perhaps
48 # fields::mk_FIELDS_be_OK. Peh. As long as %{ $package . '::FIELDS' }
49 # is used here anyway, it doesn't matter.
50 bless $f, 'pseudohash' if (ref($f) ne 'pseudohash');
51
52 return $f;
53 }
54}
55else {
56 *get_fields = sub {
57 # Shut up a possible typo warning.
58 () = \%{$_[0].'::FIELDS'};
59 return \%{$_[0].'::FIELDS'};
60 }
61}
62
63if ($] < 5.008) {
64 *_module_to_filename = sub {
65 (my $fn = $_[0]) =~ s!::!/!g;
66 $fn .= '.pm';
67 return $fn;
68 }
69}
70else {
71 *_module_to_filename = sub {
72 (my $fn = $_[0]) =~ s!::!/!g;
73 $fn .= '.pm';
74 utf8::encode($fn);
75 return $fn;
76 }
77}
78
79sub import {
80 my $class = shift;
81
82 return SUCCESS unless @_;
83
84 # List of base classes from which we will inherit %FIELDS.
85 my $fields_base;
86
87 my $inheritor = caller(0);
88
89 my @bases;
90 foreach my $base (@_) {
91 if ( $inheritor eq $base ) {
92 warn "Class '$inheritor' tried to inherit from itself\n";
93 }
94
95 next if grep $_->isa($base), ($inheritor, @bases);
96
97 # Following blocks help isolate $SIG{__DIE__} and @INC changes
98 {
99 my $sigdie;
100 {
101 local $SIG{__DIE__};
102 my $fn = _module_to_filename($base);
103 my $dot_hidden;
104 eval {
105 my $guard;
106 if ($INC[-1] eq '.' && %{"$base\::"}) {
107 # So: the package already exists => this an optional load
108 # And: there is a dot at the end of @INC => we want to hide it
109 # However: we only want to hide it during our *own* require()
110 # (i.e. without affecting nested require()s).
111 # So we add a hook to @INC whose job is to hide the dot, but which
112 # first checks checks the callstack depth, because within nested
113 # require()s the callstack is deeper.
114 # Since CORE::GLOBAL::require makes it unknowable in advance what
115 # the exact relevant callstack depth will be, we have to record it
116 # inside a hook. So we put another hook just for that at the front
117 # of @INC, where it's guaranteed to run -- immediately.
118 # The dot-hiding hook does its job by sitting directly in front of
119 # the dot and removing itself from @INC when reached. This causes
120 # the dot to move up one index in @INC, causing the loop inside
121 # pp_require() to skip it.
122 # Loaded coded may disturb this precise arrangement, but that's OK
123 # because the hook is inert by that time. It is only active during
124 # the top-level require(), when @INC is in our control. The only
125 # possible gotcha is if other hooks already in @INC modify @INC in
126 # some way during that initial require().
127 # Note that this jiggery hookery works just fine recursively: if
128 # a module loaded via base.pm uses base.pm itself, there will be
129 # one pair of hooks in @INC per base::import call frame, but the
130 # pairs from different nestings do not interfere with each other.
131 my $lvl;
132 unshift @INC, sub { return if defined $lvl; 1 while defined caller ++$lvl; () };
133 splice @INC, -1, 0, sub { return if defined caller $lvl; ++$dot_hidden, &base::__inc::unhook; () };
134 $guard = bless [ @INC[0,-2] ], 'base::__inc::scope_guard';
135 }
136 require $fn
137 };
138 if ($dot_hidden && (my @fn = grep -e && !( -d _ || -b _ ), $fn.'c', $fn)) {
139 require Carp;
140 Carp::croak(<<ERROR);
141Base class package "$base" is not empty but "$fn[0]" exists in the current directory.
142 To help avoid security issues, base.pm now refuses to load optional modules
143 from the current working directory when it is the last entry in \@INC.
144 If your software worked on previous versions of Perl, the best solution
145 is to use FindBin to detect the path properly and to add that path to
146 \@INC. As a last resort, you can re-enable looking in the current working
147 directory by adding "use lib '.'" to your code.
148ERROR
149 }
150 # Only ignore "Can't locate" errors from our eval require.
151 # Other fatal errors (syntax etc) must be reported.
152 #
153 # changing the check here is fragile - if the check
154 # here isn't catching every error you want, you should
155 # probably be using parent.pm, which doesn't try to
156 # guess whether require is needed or failed,
157 # see [perl #118561]
158 die if $@ && $@ !~ /^Can't locate \Q$fn\E .*? at .* line [0-9]+(?:, <[^>]*> (?:line|chunk) [0-9]+)?\.\n\z/s
159 || $@ =~ /Compilation failed in require at .* line [0-9]+(?:, <[^>]*> (?:line|chunk) [0-9]+)?\.\n\z/;
160 unless (%{"$base\::"}) {
161 require Carp;
162 local $" = " ";
163 Carp::croak(<<ERROR);
164Base class package "$base" is empty.
165 (Perhaps you need to 'use' the module which defines that package first,
166 or make that module available in \@INC (\@INC contains: @INC).
167ERROR
168 }
169 $sigdie = $SIG{__DIE__} || undef;
170 }
171 # Make sure a global $SIG{__DIE__} makes it out of the localization.
172 $SIG{__DIE__} = $sigdie if defined $sigdie;
173 }
174 push @bases, $base;
175
176 if ( has_fields($base) || has_attr($base) ) {
177 # No multiple fields inheritance *suck*
178 if ($fields_base) {
179 require Carp;
180 Carp::croak("Can't multiply inherit fields");
181 } else {
182 $fields_base = $base;
183 }
184 }
185 }
186 # Save this until the end so it's all or nothing if the above loop croaks.
187 push @{"$inheritor\::ISA"}, @bases;
188
189 if( defined $fields_base ) {
190 inherit_fields($inheritor, $fields_base);
191 }
192}
193
194sub inherit_fields {
195 my($derived, $base) = @_;
196
197 return SUCCESS unless $base;
198
199 my $battr = get_attr($base);
200 my $dattr = get_attr($derived);
201 my $dfields = get_fields($derived);
202 my $bfields = get_fields($base);
203
204 $dattr->[0] = @$battr;
205
206 if( keys %$dfields ) {
207 warn <<"END";
208$derived is inheriting from $base but already has its own fields!
209This will cause problems. Be sure you use base BEFORE declaring fields.
210END
211
212 }
213
214 # Iterate through the base's fields adding all the non-private
215 # ones to the derived class. Hang on to the original attribute
216 # (Public, Private, etc...) and add Inherited.
217 # This is all too complicated to do efficiently with add_fields().
218 while (my($k,$v) = each %$bfields) {
219 my $fno;
220 if ($fno = $dfields->{$k} and $fno != $v) {
221 require Carp;
222 Carp::croak ("Inherited fields can't override existing fields");
223 }
224
225 if( $battr->[$v] & PRIVATE ) {
226 $dattr->[$v] = PRIVATE | INHERITED;
227 }
228 else {
229 $dattr->[$v] = INHERITED | $battr->[$v];
230 $dfields->{$k} = $v;
231 }
232 }
233
234 foreach my $idx (1..$#{$battr}) {
235 next if defined $dattr->[$idx];
236 $dattr->[$idx] = $battr->[$idx] & INHERITED;
237 }
238}
239
2401;
241
242__END__
243