File Coverage

src/xs/clone.cc
Criterion Covered Total %
statement 96 96 100.0
branch 96 146 65.7
condition n/a
subroutine n/a
pod n/a
total 192 242 79.3


line stmt bran cond sub pod time code
1             #include "clone.h"
2             #include
3              
4             #ifndef gv_fetchmeth
5             #define gv_fetchmeth(stash,name,len,level,flags) gv_fetchmethod_autoload(stash,name,0)
6             #endif
7              
8             namespace xs {
9              
10             static const char* HOOK_METHOD = "HOOK_CLONE";
11 7           static const int HOOK_METHLEN = strlen(HOOK_METHOD);
12             static const int CLONE_MAX_DEPTH = 1000;
13              
14             static MGVTBL clone_marker;
15              
16 40           struct CrossData {
17             struct WeakRef {
18             SV* dest;
19             uint64_t key;
20             };
21             std::map map;
22             std::vector weakrefs;
23             };
24              
25             static void _clone (pTHX_ SV*, SV*, CrossData*&, I32);
26              
27 38           Sv clone (const Sv& source, int flags) {
28             dTHX;
29 38 50         Sv ret = Sv::create();
30              
31 38           CrossData* crossdata = NULL;
32 38 100         if (flags & CloneFlags::TRACK_REFS) {
33 22           CrossData data;
34 10           crossdata = &data;
35 10 50         _clone(aTHX_ ret, source, crossdata, 0);
36 10           auto end = data.map.end();
37 12 100         for (const auto& row : data.weakrefs) { // post process weak refs that appeared before their strong refs
38 2 50         auto it = data.map.find(row.key);
39 2 100         if (it == end) continue;
40 1 50         SvSetSV_nosteal(row.dest, it->second);
    50          
41 1 50         sv_rvweaken(row.dest);
42             }
43             }
44 28 100         else _clone(aTHX_ ret, source, crossdata, 0);
45              
46 36           return ret;
47             }
48              
49 2154           static void _clone (pTHX_ SV* dest, SV* source, CrossData*& xdata, I32 depth) {
50 2154 100         if (depth > CLONE_MAX_DEPTH) throw std::invalid_argument(
51 4 50         std::string("clone: max depth (") + std::to_string(CLONE_MAX_DEPTH) + ") reached, it looks like you passed a cycled structure"
    50          
    50          
    50          
52 6 50         );
53              
54 2152 100         if (SvROK(source)) { // reference
55 1051           SV* source_val = SvRV(source);
56 1051           svtype val_type = SvTYPE(source_val);
57              
58 1051 100         if (val_type == SVt_PVCV || val_type == SVt_PVIO) { // CV and IO cannot be copied - just set reference to the same SV
    100          
59 4 50         SvSetSV_nosteal(dest, source);
    50          
60 4 100         if (SvWEAKREF(source)) sv_rvweaken(dest);
    50          
61 4           return;
62             }
63              
64 1047           uint64_t id = PTR2UV(source_val);
65 1047 100         if (xdata) {
66 25 50         auto it = xdata->map.find(id);
67 25 100         if (it != xdata->map.end()) {
68 5 50         SvSetSV_nosteal(dest, it->second);
    50          
69 5 100         if (SvWEAKREF(source)) sv_rvweaken(dest);
    50          
70 7           return;
71             }
72 20 100         if (SvWEAKREF(source)) {
73             // we can't clone object weakref points to right now, because no strong refs for the object cloned so far, we must wait until the end
74 2002 50         xdata->weakrefs.push_back({dest, id});
75 20           return;
76             }
77             }
78              
79             GV* cloneGV;
80 1040           bool is_object = SvOBJECT(source_val);
81              
82             // cloning an object with custom clone behavior
83 1040 100         if (is_object) {
84 1010 50         auto mg = mg_findext(source_val, PERL_MAGIC_ext, &clone_marker);
85 1010 100         if (mg) {
86 2           xdata = reinterpret_cast(mg->mg_ptr); // restore top-map after recursive clone() call
87             }
88 1008 50         else if ((cloneGV = gv_fetchmeth(SvSTASH(source_val), HOOK_METHOD, HOOK_METHLEN, 0))) {
    100          
89             // set cloning flag into object's magic to prevent infinite loop if user calls 'clone' again from hook
90 3 50         sv_magicext(source_val, NULL, PERL_MAGIC_ext, &clone_marker, (const char*)xdata, 0);
91 3 50         dSP; ENTER; SAVETMPS;
    50          
92 3 50         PUSHMARK(SP);
    0          
93 3 50         XPUSHs(source);
    0          
94 3           PUTBACK;
95 3 50         int count = call_sv((SV*)GvCV(cloneGV), G_SCALAR);
96 3           SPAGAIN;
97 3           SV* retval = NULL;
98 6 100         while (count--) retval = POPs;
99 3 50         if (retval) SvSetSV(dest, retval);
    50          
    50          
100 3           PUTBACK;
101 3 50         FREETMPS; LEAVE;
    50          
    50          
102             // remove cloning flag from object's magic
103 3 50         sv_unmagicext(source_val, PERL_MAGIC_ext, &clone_marker);
104 3 100         if (xdata) xdata->map[id] = dest;
    50          
105 1010           return;
106             }
107             }
108              
109 1037 50         SV* refval = newSV(0);
110 1037 50         sv_upgrade(dest, SVt_RV);
111 1037           SvRV_set(dest, refval);
112 1037           SvROK_on(dest);
113              
114 1037 100         if (is_object) sv_bless(dest, SvSTASH(source_val)); // cloning an object without any specific clone behavior
    50          
115 1037 100         if (xdata) xdata->map[id] = dest;
    50          
116 1037 100         _clone(aTHX_ refval, source_val, xdata, depth+1);
117              
118 49           return;
119             }
120              
121 1101           switch (SvTYPE(source)) {
122             case SVt_IV: // integer
123             case SVt_NV: // long double
124             case SVt_PV: // string
125             case SVt_PVIV: // string + integer
126             case SVt_PVNV: // string + long double
127             case SVt_PVMG: // blessed scalar (doesn't really true, it's just vars or magic vars)
128             case SVt_PVGV: // typeglob
129             #if PERL_VERSION > 16
130             case SVt_REGEXP: // regexp
131             #endif
132 64 50         SvSetSV_nosteal(dest, source);
133 64           return;
134             #if PERL_VERSION <= 16 // fix bug in SvSetSV_nosteal while copying regexp SV prior to perl 5.16.0
135             case SVt_REGEXP: // regexp
136             SvSetSV_nosteal(dest, source);
137             if (SvSTASH(dest) == NULL) SvSTASH_set(dest, gv_stashpv("Regexp",0));
138             return;
139             #endif
140             case SVt_PVAV: { // array
141 19           sv_upgrade(dest, SVt_PVAV);
142 19           SV** srclist = AvARRAY((AV*)source);
143 19           SSize_t srcfill = AvFILLp((AV*)source);
144 19           av_extend((AV*)dest, srcfill); // dest is an empty array. we can set directly it's SV** array for speed
145 19           AvFILLp((AV*)dest) = srcfill; // set array len
146 19           SV** dstlist = AvARRAY((AV*)dest);
147 62 100         for (SSize_t i = 0; i <= srcfill; ++i) {
148 45           SV* srcval = *srclist++;
149 45 50         if (srcval != NULL) { // if not empty slot
150 45           SV* elem = newSV(0);
151 45           dstlist[i] = elem;
152 45           _clone(aTHX_ elem, srcval, xdata, depth+1);
153             }
154             }
155 17           return;
156             }
157             case SVt_PVHV: { // hash
158 1018           sv_upgrade(dest, SVt_PVHV);
159 1018           STRLEN hvmax = HvMAX((HV*)source);
160 1018           HE** hvarr = HvARRAY((HV*)source);
161 1018 50         if (!hvarr) return;
162              
163 3174 100         for (STRLEN i = 0; i <= hvmax; ++i) {
164             const HE* entry;
165 3190 100         for (entry = hvarr[i]; entry; entry = HeNEXT(entry)) {
166 1034           HEK* hek = HeKEY_hek(entry);
167 1034 50         SV* elem = newSV(0);
168 1034 50         hv_storehek((HV*)dest, hek, elem);
169 1034 100         _clone(aTHX_ elem, HeVAL(entry), xdata, depth+1);
170             }
171             }
172              
173 20           return;
174             }
175             case SVt_NULL: // undef
176             default: // BIND, LVALUE, FORMAT - are not copied
177 150           return;
178             }
179             }
180              
181 28 50         }
    50