File Coverage

oh_core.c
Criterion Covered Total %
statement 147 199 73.8
branch 85 192 44.2
condition n/a
subroutine n/a
pod n/a
total 232 391 59.3


line stmt bran cond sub pod time code
1             /*
2             * oh_core.c -- core implementation of Tie::OrderedHash.
3             *
4             * Storage: blessed AV-of-4 mirroring Tie::IxHash's shape.
5             *
6             * $self = bless [
7             * $idx_hv, # [0] HV mapping key -> index (IV stored in SV)
8             * $keys_av, # [1] AV of keys in insertion order
9             * $vals_av, # [2] AV of values
10             * $iter, # [3] IV: cursor used by Perl FIRSTKEY/NEXTKEY
11             * ] => 'Tie::OrderedHash';
12             *
13             * Perl-level users see a tied hash; the AV layout is internal but
14             * matches Tie::IxHash so power users who poked $ixhash->[1] for keys
15             * still get the right answer. Document the right way: call ->Keys.
16             *
17             * Filename note: this is "oh_core.c" rather than "orderedhash.c"
18             * because xsubpp generates "OrderedHash.c" from OrderedHash.xs and
19             * macOS's case-insensitive filesystem collapses the two. Same trap
20             * we hit on File-Raw-JSON (json.c vs JSON.c).
21             */
22              
23             #include "EXTERN.h"
24             #include "perl.h"
25             #include "XSUB.h"
26              
27             #include "ppport.h"
28             #include "tie_orderedhash.h"
29             #include "oh_core.h"
30              
31             /* ---- internals ------------------------------------------------- */
32              
33             /* Build a key SV from raw bytes, marking the UTF-8 flag when the
34             * bytes are valid UTF-8. Used both for the SVs we store in the
35             * keys AV and for the mortal lookup-key SVs we feed to
36             * hv_fetch_ent / hv_delete_ent.
37             *
38             * Why this matters: callers that pass UTF-8 bytes (eg
39             * File::Raw::JSON's ordered=>1 path, where yyjson's strings are
40             * native UTF-8) want `$h->{"\x{00e9}"}` lookups to match the
41             * key they stored. Without sv_utf8_decode, the stored key has
42             * raw bytes [0xC3, 0xA9] and no UTF-8 flag; a wide-char literal
43             * comes through as character "\x{e9}" (one char) and the byte
44             * comparison misses. sv_utf8_decode validates the bytes and
45             * sets the flag iff they're proper UTF-8 - safe no-op for ASCII
46             * and for non-UTF-8 binary keys. */
47             PERL_STATIC_INLINE SV *
48 81           oh_make_key_sv(pTHX_ const char *key, STRLEN klen)
49             {
50 81           SV *sv = newSVpvn(key, klen);
51 81           sv_utf8_decode(sv);
52 81           return sv;
53             }
54              
55             void
56 163           oh_resolve(pTHX_ SV *self, HV **out_idx, AV **out_keys, AV **out_vals)
57             {
58             AV *av;
59             SV **slot;
60              
61 163 50         if (!self || !SvROK(self) || !SvOBJECT(SvRV(self)))
    50          
    50          
62 0           croak("Tie::OrderedHash: not an object");
63 163 50         if (SvTYPE(SvRV(self)) != SVt_PVAV)
64 0           croak("Tie::OrderedHash: impl is not an array ref");
65 163           av = (AV *)SvRV(self);
66 163 50         if (av_len(av) < 2)
67 0           croak("Tie::OrderedHash: impl has fewer than 3 slots");
68              
69 163           slot = av_fetch(av, 0, 0);
70 163 50         if (!slot || !*slot || !SvROK(*slot) || SvTYPE(SvRV(*slot)) != SVt_PVHV)
    50          
    50          
    50          
71 0           croak("Tie::OrderedHash: slot 0 is not a hash ref");
72 163           *out_idx = (HV *)SvRV(*slot);
73              
74 163           slot = av_fetch(av, 1, 0);
75 163 50         if (!slot || !*slot || !SvROK(*slot) || SvTYPE(SvRV(*slot)) != SVt_PVAV)
    50          
    50          
    50          
76 0           croak("Tie::OrderedHash: slot 1 is not an array ref");
77 163           *out_keys = (AV *)SvRV(*slot);
78              
79 163           slot = av_fetch(av, 2, 0);
80 163 50         if (!slot || !*slot || !SvROK(*slot) || SvTYPE(SvRV(*slot)) != SVt_PVAV)
    50          
    50          
    50          
81 0           croak("Tie::OrderedHash: slot 2 is not an array ref");
82 163           *out_vals = (AV *)SvRV(*slot);
83 163           }
84              
85             SSize_t
86 14           oh_perl_iter_get(pTHX_ SV *self)
87             {
88 14           AV *av = (AV *)SvRV(self);
89 14           SV **slot = av_fetch(av, 3, 0);
90 14 50         if (!slot || !*slot) return 0;
    50          
91 14           return (SSize_t)SvIV(*slot);
92             }
93              
94             void
95 10           oh_perl_iter_set(pTHX_ SV *self, SSize_t pos)
96             {
97 10           AV *av = (AV *)SvRV(self);
98 10           SV **slot = av_fetch(av, 3, 1);
99 10 50         if (slot && *slot)
    50          
100 10           sv_setiv(*slot, (IV)pos);
101 10           }
102              
103             /* ---- public C ABI ---------------------------------------------- */
104              
105             SV *
106 48           tie_oh_new(pTHX)
107             {
108 48           AV *av = newAV();
109 48           HV *stash = gv_stashpv(TIE_OH_CLASS, GV_ADD);
110             SV *rv;
111              
112 48           av_extend(av, 3);
113 48           av_push(av, newRV_noinc((SV *)newHV())); /* [0] idx hv */
114 48           av_push(av, newRV_noinc((SV *)newAV())); /* [1] keys av */
115 48           av_push(av, newRV_noinc((SV *)newAV())); /* [2] vals av */
116 48           av_push(av, newSViv(0)); /* [3] cursor */
117              
118 48           rv = newRV_noinc((SV *)av);
119 48           sv_bless(rv, stash);
120 48           return rv;
121             }
122              
123             void
124 30           tie_oh_store(pTHX_ SV *self, const char *key, STRLEN klen, SV *val)
125             {
126             HV *idx; AV *keys; AV *vals;
127             HE *he;
128             SV *key_sv;
129              
130 30           oh_resolve(aTHX_ self, &idx, &keys, &vals);
131              
132 30           he = hv_fetch_ent(idx, sv_2mortal(oh_make_key_sv(aTHX_ key, klen)), 0, 0);
133 30 100         if (he) {
134             /* Existing key: replace value, preserve position. */
135 1           SSize_t pos = (SSize_t)SvIV(HeVAL(he));
136 1 50         if (!av_store(vals, pos, val))
137 0           SvREFCNT_dec(val);
138 1           return;
139             }
140              
141             /* New key: append to keys+vals, record index in idx. */
142 29           key_sv = oh_make_key_sv(aTHX_ key, klen);
143 29           av_push(keys, key_sv);
144 29 50         if (!av_store(vals, av_len(keys), val)) {
145 0           SvREFCNT_dec(val);
146             }
147             {
148 29           SV *idx_sv = newSViv((IV)av_len(keys));
149 29 50         if (!hv_store_ent(idx, key_sv, idx_sv, 0))
150 0           SvREFCNT_dec(idx_sv);
151             }
152             }
153              
154             SV *
155 0           tie_oh_fetch(pTHX_ SV *self, const char *key, STRLEN klen)
156             {
157             HV *idx; AV *keys; AV *vals;
158             HE *he;
159             SSize_t pos;
160             SV **vslot;
161              
162 0           oh_resolve(aTHX_ self, &idx, &keys, &vals);
163              
164 0           he = hv_fetch_ent(idx, sv_2mortal(oh_make_key_sv(aTHX_ key, klen)), 0, 0);
165 0 0         if (!he) return NULL;
166 0           pos = (SSize_t)SvIV(HeVAL(he));
167              
168 0           vslot = av_fetch(vals, pos, 0);
169 0 0         if (!vslot || !*vslot) return sv_mortalcopy(&PL_sv_undef);
    0          
170 0           return sv_mortalcopy(*vslot);
171             }
172              
173             int
174 0           tie_oh_exists(pTHX_ SV *self, const char *key, STRLEN klen)
175             {
176             HV *idx; AV *keys; AV *vals;
177 0           oh_resolve(aTHX_ self, &idx, &keys, &vals);
178 0           return hv_exists_ent(idx,
179 0           sv_2mortal(oh_make_key_sv(aTHX_ key, klen)), 0) ? 1 : 0;
180             }
181              
182             /* av_remove_at -- like Perl's `splice @arr, pos, 1`. av_delete
183             * leaves a hole (NULL slot, length unchanged); we want to actually
184             * shift everything after `pos` down by one and shrink. */
185             static void
186 28           av_remove_at(pTHX_ AV *av, SSize_t pos)
187             {
188 28           SSize_t i, top = av_len(av);
189             SV *trailing;
190              
191             /* Shift [pos+1..top] down to [pos..top-1]. av_store at slot i
192             * frees the existing SV there; at i==pos that's the value
193             * being removed (caller has already grabbed any return copy). */
194 68 100         for (i = pos; i < top; i++) {
195 40           SV **src = av_fetch(av, i + 1, 0);
196 40 50         if (src && *src) {
    50          
197 40           SvREFCNT_inc(*src);
198 40 50         if (!av_store(av, i, *src))
199 0           SvREFCNT_dec(*src);
200             }
201             }
202             /* Trim the trailing duplicate slot. */
203 28           trailing = av_pop(av);
204 28 50         if (trailing) SvREFCNT_dec(trailing);
205 28           }
206              
207             SV *
208 20           tie_oh_delete(pTHX_ SV *self, const char *key, STRLEN klen)
209             {
210             HV *idx; AV *keys; AV *vals;
211             SV *deleted_idx;
212 20           SV *deleted_val = NULL;
213             SSize_t pos;
214             SSize_t i, n;
215              
216 20           oh_resolve(aTHX_ self, &idx, &keys, &vals);
217              
218 20           deleted_idx = hv_delete_ent(idx,
219             sv_2mortal(oh_make_key_sv(aTHX_ key, klen)), 0, 0);
220 20 100         if (!deleted_idx) return NULL;
221 14           pos = (SSize_t)SvIV(deleted_idx);
222              
223             {
224 14           SV **vslot = av_fetch(vals, pos, 0);
225 14 50         deleted_val = (vslot && *vslot) ? sv_mortalcopy(*vslot)
226 14 50         : sv_mortalcopy(&PL_sv_undef);
227             }
228              
229 14           av_remove_at(aTHX_ vals, pos);
230 14           av_remove_at(aTHX_ keys, pos);
231              
232             /* Renumber: every key whose index was > pos moved down by one. */
233 14           n = av_len(keys) + 1;
234 34 100         for (i = pos; i < n; i++) {
235 20           SV **kslot = av_fetch(keys, i, 0);
236             HE *he;
237 20 50         if (!kslot || !*kslot) continue;
    50          
238 20           he = hv_fetch_ent(idx, *kslot, 0, 0);
239 20 50         if (he && HeVAL(he)) sv_setiv(HeVAL(he), (IV)i);
    50          
240             }
241              
242             {
243 14           SSize_t cur = oh_perl_iter_get(aTHX_ self);
244 14 100         if (cur > n) oh_perl_iter_set(aTHX_ self, n);
245             }
246              
247 14           return deleted_val;
248             }
249              
250             void
251 8           tie_oh_clear(pTHX_ SV *self)
252             {
253             AV *av;
254             SV **slot;
255              
256 8 50         if (!self || !SvROK(self) || SvTYPE(SvRV(self)) != SVt_PVAV)
    50          
    50          
257 0           croak("Tie::OrderedHash: not an impl object");
258 8           av = (AV *)SvRV(self);
259              
260 8           slot = av_fetch(av, 0, 1);
261 8 50         if (slot) sv_setsv(*slot, sv_2mortal(newRV_noinc((SV *)newHV())));
262 8           slot = av_fetch(av, 1, 1);
263 8 50         if (slot) sv_setsv(*slot, sv_2mortal(newRV_noinc((SV *)newAV())));
264 8           slot = av_fetch(av, 2, 1);
265 8 50         if (slot) sv_setsv(*slot, sv_2mortal(newRV_noinc((SV *)newAV())));
266 8           slot = av_fetch(av, 3, 1);
267 8 50         if (slot) sv_setiv(*slot, 0);
268 8           }
269              
270             SSize_t
271 34           tie_oh_count(pTHX_ SV *self)
272             {
273             HV *idx; AV *keys; AV *vals;
274 34           oh_resolve(aTHX_ self, &idx, &keys, &vals);
275 34           return av_len(keys) + 1;
276             }
277              
278             void
279 0           tie_oh_iter_init(pTHX_ SV *self, tie_oh_iter_t *iter)
280             {
281             HV *idx; AV *keys; AV *vals;
282 0           oh_resolve(aTHX_ self, &idx, &keys, &vals);
283 0           iter->pos = 0;
284 0           iter->end = av_len(keys) + 1;
285 0           }
286              
287             int
288 0           tie_oh_iter_next(pTHX_ SV *self, tie_oh_iter_t *iter,
289             const char **out_key, STRLEN *out_klen,
290             SV **out_val)
291             {
292             HV *idx; AV *keys; AV *vals;
293             SV **kslot, **vslot;
294              
295 0           oh_resolve(aTHX_ self, &idx, &keys, &vals);
296 0 0         if (iter->pos >= iter->end) return 0;
297 0 0         if (iter->pos > av_len(keys)) return 0;
298              
299 0           kslot = av_fetch(keys, iter->pos, 0);
300 0 0         if (!kslot || !*kslot) return 0;
    0          
301 0           *out_key = SvPV(*kslot, *out_klen);
302              
303 0           vslot = av_fetch(vals, iter->pos, 0);
304 0 0         *out_val = (vslot && *vslot) ? *vslot : &PL_sv_undef;
    0          
305              
306 0           iter->pos++;
307 0           return 1;
308             }
309              
310             int
311 0           tie_oh_is_instance(pTHX_ SV *sv)
312             {
313             HV *stash;
314             const char *name;
315              
316 0 0         if (!sv || !SvROK(sv) || !SvOBJECT(SvRV(sv))) return 0;
    0          
    0          
317 0 0         if (SvTYPE(SvRV(sv)) != SVt_PVAV) return 0;
318 0           stash = SvSTASH(SvRV(sv));
319 0 0         if (!stash) return 0;
320 0 0         name = HvNAME_get(stash);
    0          
    0          
    0          
    0          
    0          
321 0 0         if (!name) return 0;
322 0           return strEQ(name, TIE_OH_CLASS) ? 1 : 0;
323             }
324              
325             /* ---- internal helpers used by the OO XSUBs --------------------- */
326              
327             SV *
328 10           oh_pop(pTHX_ SV *self, SV **out_key)
329             {
330             HV *idx; AV *keys; AV *vals;
331             SV *kpop, *vpop;
332              
333 10           oh_resolve(aTHX_ self, &idx, &keys, &vals);
334 10 100         if (av_len(keys) < 0) {
335 6 50         if (out_key) *out_key = NULL;
336 6           return NULL;
337             }
338              
339 4           kpop = av_pop(keys);
340 4           vpop = av_pop(vals);
341 4 50         if (kpop) hv_delete_ent(idx, kpop, G_DISCARD, 0);
342              
343 4 50         if (out_key) *out_key = kpop ? sv_2mortal(kpop) : NULL;
    50          
344 4 50         return vpop ? sv_2mortal(vpop) : NULL;
345             }
346              
347             SV *
348 10           oh_shift(pTHX_ SV *self, SV **out_key)
349             {
350             HV *idx; AV *keys; AV *vals;
351             SV *kshift, *vshift;
352             SSize_t i, n;
353              
354 10           oh_resolve(aTHX_ self, &idx, &keys, &vals);
355 10 100         if (av_len(keys) < 0) {
356 6 50         if (out_key) *out_key = NULL;
357 6           return NULL;
358             }
359              
360 4           kshift = av_shift(keys);
361 4           vshift = av_shift(vals);
362 4 50         if (kshift) hv_delete_ent(idx, kshift, G_DISCARD, 0);
363              
364 4           n = av_len(keys) + 1;
365 6 100         for (i = 0; i < n; i++) {
366 2           SV **kslot = av_fetch(keys, i, 0);
367             HE *he;
368 2 50         if (!kslot || !*kslot) continue;
    50          
369 2           he = hv_fetch_ent(idx, *kslot, 0, 0);
370 2 50         if (he && HeVAL(he)) sv_setiv(HeVAL(he), (IV)i);
    50          
371             }
372              
373 4 50         if (out_key) *out_key = kshift ? sv_2mortal(kshift) : NULL;
    50          
374 4 50         return vshift ? sv_2mortal(vshift) : NULL;
375             }
376              
377             void
378 3           oh_unshift_pair(pTHX_ SV *self, SV *key_sv, SV *val)
379             {
380             HV *idx; AV *keys; AV *vals;
381             HE *he;
382             STRLEN klen;
383             const char *key;
384             SSize_t i, n;
385              
386 3           oh_resolve(aTHX_ self, &idx, &keys, &vals);
387 3           key = SvPV(key_sv, klen);
388              
389 3           he = hv_fetch_ent(idx, key_sv, 0, 0);
390 3 100         if (he) {
391 1           SSize_t pos = (SSize_t)SvIV(HeVAL(he));
392 1 50         if (!av_store(vals, pos, val)) SvREFCNT_dec(val);
393 1           return;
394             }
395              
396 2           n = av_len(keys) + 1;
397 2           av_unshift(keys, 1);
398 2           av_unshift(vals, 1);
399             {
400 2           SV *kdup = oh_make_key_sv(aTHX_ key, klen);
401 2 50         if (!av_store(keys, 0, kdup)) SvREFCNT_dec(kdup);
402 2 50         if (!av_store(vals, 0, val)) SvREFCNT_dec(val);
403             }
404             /* Renumber existing keys up by one. */
405 5 100         for (i = 1; i <= n; i++) {
406 3           SV **kslot = av_fetch(keys, i, 0);
407             HE *he2;
408 3 50         if (!kslot || !*kslot) continue;
    50          
409 3           he2 = hv_fetch_ent(idx, *kslot, 0, 0);
410 3 50         if (he2 && HeVAL(he2)) sv_setiv(HeVAL(he2), (IV)i);
    50          
411             }
412             /* Record idx 0 for the new key. */
413             {
414 2           SV **kslot = av_fetch(keys, 0, 0);
415 2           SV *idx_sv = newSViv(0);
416 2 50         SV *kdup = (kslot && *kslot) ? *kslot : key_sv;
    50          
417 2 50         if (!hv_store_ent(idx, kdup, idx_sv, 0))
418 0           SvREFCNT_dec(idx_sv);
419             }
420             }
421              
422             void
423 0           oh_push_pair(pTHX_ SV *self, SV *key_sv, SV *val)
424             {
425             STRLEN klen;
426 0           const char *key = SvPV(key_sv, klen);
427 0           tie_oh_store(aTHX_ self, key, klen, val);
428 0           }