File Coverage

xs/accessors.h
Criterion Covered Total %
statement 154 159 96.8
branch 179 610 29.3
condition n/a
subroutine n/a
pod n/a
total 333 769 43.3


line stmt bran cond sub pod time code
1             #ifndef __INHERITED_XS_IMPL_H_
2             #define __INHERITED_XS_IMPL_H_
3              
4             #include "fimpl.h"
5             #include "util.h"
6             #include "op.h"
7              
8             /*
9             These macroses impose the following rules:
10             - SP is at the start of the args list
11             - SP may become invalid afterwards, so don't touch it
12             - PL_stack_sp is updated when needed
13              
14             The latter may be not that obvious, but it's a result of a callback doing
15             dirty stack work for us. Note that only essential cleanup is done
16             after call_sv().
17             */
18              
19             #define TYPE_INHERITED (type == InheritedCb || type == InheritedCbNamed)
20              
21             #define PUSH_PAYLOAD_KEY \
22             /* METHOD_NAMED path won't give us a free SP slot */ \
23             EXTEND(SP, items + 1); \
24             *(SP += items + 1) = payload->hash_key; \
25             PUTBACK; \
26             /*
27             re-enable when messing with stack for tests
28             assert(PL_curstackinfo->si_stack_hwm >= PL_stack_sp - PL_stack_base);
29             */ \
30              
31              
32             #define CALL_READ_CB(result) \
33             if (TYPE_INHERITED && payload->read_cb) { \
34             ENTER; \
35             PUSHMARK(SP); \
36             *(SP+1) = result; \
37             if (type == InheritedCbNamed) { \
38             PUSH_PAYLOAD_KEY; \
39             } \
40             call_sv(payload->read_cb, G_SCALAR); \
41             LEAVE; \
42             } else { \
43             *(SP+1) = result; \
44             } \
45              
46             #define CALL_WRITE_CB(slot, need_alloc) \
47             if (TYPE_INHERITED && payload->write_cb) { \
48             ENTER; \
49             PUSHMARK(SP); \
50             if (type == InheritedCbNamed) { \
51             PUSH_PAYLOAD_KEY; \
52             } \
53             call_sv(payload->write_cb, G_SCALAR); \
54             SPAGAIN; \
55             LEAVE; \
56             if (need_alloc) slot = newSV(0); \
57             sv_setsv(slot, *SP); \
58             *SP = slot; \
59             } else { \
60             if (need_alloc) slot = newSV(0); \
61             sv_setsv(slot, *(SP+2)); \
62             PUSHs(slot); \
63             PUTBACK; \
64             } \
65              
66             #define CALL_WRITE_WEAKEN(slot) \
67             if (opts & IsWeak) sv_rvweaken(slot)
68              
69             #define READONLY_TYPE_ASSERT \
70             assert(type == Inherited || type == PrivateClass || type == ObjectOnly || type == LazyClass)
71              
72             #define READONLY_CROAK_CHECK \
73             if (!TYPE_INHERITED && (opts & IsReadonly)) { \
74             READONLY_TYPE_ASSERT; \
75             croak("Can't set value in readonly accessor"); \
76             return; \
77             } \
78              
79             #define SET_GVGP_FLAGS(glob, sv)\
80             if (SvOK(sv)) { \
81             GvGPFLAGS_on(glob); \
82             \
83             } else { \
84             GvGPFLAGS_off(glob); \
85             GvLINE(glob) = 0; \
86             } \
87              
88             template static
89             SV*
90 129           CAIXS_icache_get(pTHX_ HV* stash, GV* glob) {
91 129 100         const struct mro_meta* stash_meta = HvMROMETA(stash);
    100          
92 129           const long long curgen = (long long)PL_sub_generation + stash_meta->pkg_gen;
93              
94 129 100         if (GvLINE(glob) == curgen || GvGPFLAGS(glob)) return GvSV(glob);
    100          
    100          
    100          
95 20 50         if (overflow && UNLIKELY(curgen > ((U32)1 << 31) - 1)) {
96 0           warn("MRO cache generation 31 bit wraparound");
97 0           PL_sub_generation = 0;
98             }
99              
100 45           return NULL;
101             }
102              
103             static SV*
104 20           CAIXS_icache_update(pTHX_ HV* stash, GV* glob, SV* pkg_key) {
105 20           AV* supers = mro_get_linear_isa(stash);
106             /*
107             First entry in the 'mro_get_linear_isa' list is the 'stash' itself.
108             It's already been tested, so ajust both counter and iterator to skip over it.
109             */
110 20           SSize_t fill = AvFILLp(supers);
111 20           SV** supers_list = AvARRAY(supers);
112              
113             SV* elem;
114 20           SV* result = NULL;
115              
116 20           GV* stack[fill + 1];
117             #ifdef DEBUGGING
118             memzero(stack, (fill + 1) * sizeof(GV*));
119             #endif
120 20           stack[fill] = glob;
121              
122 53 100         while (result == NULL && --fill >= 0) {
    100          
    100          
123 33           elem = *(++supers_list);
124             assert(elem); /* mro_get_linear_isa returns dense array */
125              
126 33           HV* next_stash = gv_stashsv(elem, 0);
127             /*
128             Skip entries for empty stashes to save some memory.
129             This may result in gaps in the 'stack' array, but we don't care.
130             */
131 33 100         if (LIKELY(next_stash != NULL)) {
132 30           GV* next_gv = CAIXS_fetch_glob(aTHX_ next_stash, pkg_key);
133 30           stack[fill] = next_gv;
134              
135 30           result = CAIXS_icache_get(aTHX_ next_stash, next_gv);
136             }
137             }
138              
139 20 100         if (UNLIKELY(result == NULL)) {
140             assert(fill == -1);
141              
142             /* Since we don't force stash creation in the above loop, do it here */
143 15           HV* root_stash = gv_stashsv(*supers_list, GV_ADD);
144 15           stack[0] = CAIXS_fetch_glob(aTHX_ root_stash, pkg_key);
145              
146             assert(stack[0]);
147 15 100         result = GvSVn(stack[0]); /* undef from root */
148 15           GvGPFLAGS_on(stack[0]); /* yeah, valid 'undef', to speed up lookups later */
149             }
150              
151 20           GV* cur_gv = stack[AvFILLp(supers)];
152             assert(cur_gv && cur_gv == glob);
153              
154 20 50         const struct mro_meta* stash_meta = HvMROMETA(GvSTASH(cur_gv));
155 20           const U32 curgen = PL_sub_generation + stash_meta->pkg_gen;
156 20           GvLINE(cur_gv) = curgen & (((U32)1 << 31) - 1); /* perl may lack 'gp_flags' field, so we must care about the highest bit */
157              
158             /* copy-by-reference */
159 20           SV** sv_slot = &GvSV(cur_gv);
160              
161 20           SvREFCNT_inc_simple_void_NN(result);
162 20           SvREFCNT_dec(*sv_slot);
163 20           *sv_slot = result;
164              
165 20           return result;
166             }
167              
168             static void
169 27           CAIXS_icache_clear(pTHX_ HV* stash, SV* pkg_key, SV* base_sv) {
170 27 50         SV** svp = hv_fetchhek(PL_isarev, HvENAME_HEK(stash));
    50          
    50          
    50          
    50          
    50          
    50          
    50          
    50          
    50          
    50          
    50          
    50          
    50          
    50          
    50          
    50          
    50          
    50          
    50          
    50          
    50          
    50          
    50          
    50          
171 27 100         if (svp) {
172 3           HV* isarev = (HV*)*svp;
173              
174 3 50         if (HvUSEDKEYS(isarev)) {
    50          
175 3           STRLEN hvmax = HvMAX(isarev);
176 3           HE** hvarr = HvARRAY(isarev);
177              
178 3           SV* pl_yes = &PL_sv_yes; /* not that I care much about ithreads, but still */
179 27 100         for (STRLEN bucket_num = 0; bucket_num <= hvmax; ++bucket_num) {
180 28 100         for (const HE* he = hvarr[bucket_num]; he; he = HeNEXT(he)) {
181             assert(HeVAL(he) == &PL_sv_placeholder || HeVAL(he) == &PL_sv_yes);
182              
183 4 50         if (HeVAL(he) == pl_yes) { /* mro_core.c stores only them */
184             /* access PL_stashcache through HEK interface directly here? */
185 4           HEK* hkey = HeKEY_hek(he);
186 4           HV* revstash = gv_stashpvn(HEK_KEY(hkey), HEK_LEN(hkey), HEK_UTF8(hkey) | GV_ADD);
187 4           GV* revglob = CAIXS_fetch_glob(aTHX_ revstash, pkg_key);
188              
189 4 50         if (base_sv == NULL) {
190             /* invalidates all non-root nodes */
191 4 50         if (!GvGPFLAGS(revglob)) GvLINE(revglob) = 0;
192              
193             } else {
194             /* since all the cache elements point to the same sv, invalidate only it's copies */
195 0 0         if (GvSV(revglob) == base_sv) {
196             assert(!GvGPFLAGS(revglob));
197 4           GvLINE(revglob) = 0;
198             }
199             }
200             }
201             }
202             }
203             }
204             }
205 27           }
206              
207             template
208             struct FImpl {
209 13           static void CAIXS_accessor(pTHX_ SV** SP, CV* cv, HV* stash) {
210 13           dAXMARK; dITEMS;
211              
212 13           CAIXS_install_entersub(aTHX);
213 13 100         if (UNLIKELY(!items)) croak("Usage: $obj->constructor or __PACKAGE__->constructor");
214              
215 12           PL_stack_sp = ++MARK; /* PUTBACK */
216              
217 12 50         if (!stash) stash = CAIXS_find_stash(aTHX_ *MARK, cv);
218 12           SV** ret = MARK++;
219              
220             SV* self;
221 12 100         if (items == 2 && SvROK(*MARK) && SvTYPE(SvRV(*MARK)) == SVt_PVHV) {
    100          
    100          
222 3           self = *MARK;
223              
224 9 100         } else if (items == 2 && !SvOK(*MARK)) {
    100          
    50          
    50          
225 1           self = sv_2mortal(newRV_noinc((SV*)newHV()));
226              
227 8 100         } else if ((items & 1) == 0) {
228 3           croak("Odd number of elements in hash constructor");
229              
230             } else {
231 5           HV* hash = newHV();
232              
233 10 100         while (MARK < SP) {
234 5           SV* key = *MARK++;
235             /* Don't bother with retval here, as in pp_anonhash */
236 5           hv_store_ent(hash, key, newSVsv(*MARK++), 0);
237             }
238              
239 5           self = sv_2mortal(newRV_noinc((SV*)hash));
240             }
241              
242 9           sv_bless(self, stash);
243 9           *ret = self;
244 9           return;
245             }};
246              
247             template
248             struct FImpl {
249 10           static void CAIXS_accessor(pTHX_ SV** SP, CV* cv, HV* stash) {
250 10           dAXMARK; dITEMS;
251              
252 10 50         if (UNLIKELY(!items)) croak("Usage: $obj->accessor or __PACKAGE__->accessor");
    0          
    50          
    50          
253 10           shared_keys* payload = CAIXS_find_payload(cv);
254              
255 10           if (items > 1) {
256 2           const int type = LazyClass; /* for READONLY_CROAK_CHECK */
257 1           READONLY_CROAK_CHECK;
258              
259 2 0         PUSHMARK(SP - items); /* our dAXMARK has popped one */
    50          
260 2           FImpl::CAIXS_accessor(aTHX_ SP, cv, stash);
261              
262             } else {
263 7           ENTER;
264 1           if (opts & IsWeak) SAVETMPS;
265 7 50         PUSHMARK(--SP); /* SP -= items */
    0          
    50          
    50          
266 7           call_sv(payload->lazy_cb, G_SCALAR);
267 7           SPAGAIN;
268              
269 7           sv_setsv(payload->storage, *SP);
270 6           *SP = payload->storage;
271              
272             if (opts & IsWeak) {
273 1           CALL_WRITE_WEAKEN(payload->storage);
274 1 50         FREETMPS; /* that's done to immediately free stored value if it's only hard reference had been held on the stack */
    0          
275             }
276              
277 7           LEAVE;
278             }
279              
280             /* Lazy getter may call setter, and so we'd end up here twice, but SvREFCNT_dec is required only once */
281 9           if (payload->lazy_cb) {
282 8           CvXSUB(cv) = (XSUBADDR_t)&CAIXS_entersub_wrapper;
283 8           SvREFCNT_dec_NN(payload->lazy_cb);
284 8           payload->lazy_cb = NULL;
285             }
286              
287 9           return;
288             }};
289              
290             template
291             struct FImpl {
292 87           static void CAIXS_accessor(pTHX_ SV** SP, CV* cv, HV* stash) {
293 87           dAXMARK; dITEMS;
294 87           SP -= items;
295              
296 87 50         if (UNLIKELY(!items)) croak("Usage: $obj->accessor or __PACKAGE__->accessor");
    0          
    100          
    50          
297              
298 86           CAIXS_install_entersub(aTHX);
299 86           shared_keys* payload = CAIXS_find_payload(cv);
300              
301 86           const int type = PrivateClass; /* for CALL_*_CB */
302              
303 86 100         if (items == 1) {
    0          
    100          
    100          
304 59           CALL_READ_CB(payload->storage);
305 78           return;
306             }
307              
308 8           READONLY_CROAK_CHECK;
309 19           CALL_WRITE_CB(payload->storage, 0);
310 2           CALL_WRITE_WEAKEN(payload->storage);
311 19           return;
312             }};
313              
314             /* covers type = {Inherited, InheritedCb, InheritedCbNamed, ObjectOnly} */
315             template
316             struct FImpl {
317 322           static void CAIXS_accessor(pTHX_ SV** SP, CV* cv, HV* stash) {
318 322           dAXMARK; dITEMS;
319 322           SP -= items;
320              
321 322 0         if (UNLIKELY(!items)) croak("Usage: $obj->accessor or __PACKAGE__->accessor");
    0          
    50          
    50          
    0          
    0          
    0          
    0          
    0          
    0          
    50          
    50          
    50          
    50          
    100          
    50          
322              
323 321           CAIXS_install_entersub(aTHX);
324 321           shared_keys* payload = CAIXS_find_payload(cv);
325              
326 321           SV* self = *(SP+1);
327 321           if (SvROK(self)) {
328 194           HV* obj = (HV*)SvRV(self);
329 194 0         if (UNLIKELY(SvTYPE((SV*)obj) != SVt_PVHV)) {
    0          
    50          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    50          
    50          
    50          
    50          
    100          
    50          
330 4           croak("Inherited accessors work only with hash-based objects");
331             }
332              
333 190 0         if (items > 1) {
    0          
    100          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    100          
    100          
    100          
    100          
    100          
    100          
334 4           READONLY_CROAK_CHECK;
335              
336             SV* new_value;
337 47 0         CALL_WRITE_CB(new_value, 1);
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    50          
    50          
    50          
    50          
    100          
    50          
338 46 0         if (UNLIKELY(!hv_store_ent(obj, payload->hash_key, new_value, 0))) {
    0          
    50          
    0          
    0          
    0          
    0          
    0          
    50          
    50          
    50          
    50          
339 0           SvREFCNT_dec_NN(new_value);
340 43           croak("Can't store new hash value");
341             }
342 3           CALL_WRITE_WEAKEN(new_value);
343 45           return;
344            
345             } else {
346 139           HE* hent = hv_fetch_ent(obj, payload->hash_key, 0, 0);
347 139 0         if (hent) {
    0          
    50          
    50          
348 27 0         CALL_READ_CB(HeVAL(hent));
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    50          
    50          
    50          
    50          
    100          
    50          
349 264           return;
350              
351             } else if (type == ObjectOnly) {
352 1           CALL_READ_CB(&PL_sv_undef);
353 1           return;
354             }
355             }
356             }
357              
358             if (type == ObjectOnly) {
359 1           croak("Can't use object accessor on non-object");
360             return; /* gcc detects unreachability even with bare croak(), but it won't hurt */
361             }
362              
363             /* Couldn't find value in the object, so initiate a package lookup. */
364              
365 142 0         if (!stash) stash = CAIXS_find_stash(aTHX_ self, cv);
    0          
    50          
    0          
    0          
    0          
    0          
    0          
    0          
    100          
    100          
    0          
366              
367 142 0         if (items > 1) {
    0          
    100          
    0          
    0          
    0          
    0          
    0          
    0          
    100          
    100          
    0          
368 0           READONLY_CROAK_CHECK;
369              
370 42           GV* glob = CAIXS_fetch_glob(aTHX_ stash, payload->pkg_key);
371 42           SV* new_value = GvSV(glob);
372              
373 42           if (!GvGPFLAGS(glob)) {
374             /*
375             When this is an already calculated cache point (new_value != NULL),
376             wipe will be performed only to the 'new_value' copies. Otherwise,
377             erase the whole cache.
378             */
379 27           CAIXS_icache_clear(aTHX_ stash, payload->pkg_key, new_value);
380 27           SvREFCNT_dec(new_value);
381              
382 27           GvSV(glob) = newSV(0);
383 36           new_value = GvSV(glob);
384              
385             } else {
386             assert(new_value);
387             }
388              
389 42 0         CALL_WRITE_CB(new_value, 0);
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    100          
    50          
390 38 0         SET_GVGP_FLAGS(glob, new_value);
    0          
    0          
    0          
    0          
    0          
    50          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    50          
    0          
    0          
    100          
    50          
    50          
391 1           CALL_WRITE_WEAKEN(new_value);
392              
393 38           return;
394             }
395              
396 100           GV* glob = CAIXS_fetch_glob(aTHX_ stash, payload->pkg_key);
397 99           SV* result = CAIXS_icache_get(aTHX_ stash, glob);
398              
399             /* lazy cache builder */
400 99 0         if (!result) result = CAIXS_icache_update(aTHX_ stash, glob, payload->pkg_key);
    0          
    50          
    0          
    0          
    0          
    0          
    0          
    0          
    100          
    100          
    0          
401              
402 21 0         CALL_READ_CB(result);
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    100          
    50          
403 95           return;
404             }};
405              
406             #endif /* __INHERITED_XS_IMPL_H_ */