File Coverage

src/xs/clone.cc
Criterion Covered Total %
statement 94 94 100.0
branch 92 138 66.6
condition n/a
subroutine n/a
pod n/a
total 186 232 80.1


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 = 5000;
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 15152           static void _clone (pTHX_ SV* dest, SV* source, CrossData*& xdata, I32 depth) {
50 15152 100         if (depth > CLONE_MAX_DEPTH) throw std::invalid_argument("clone: max depth (5000) reached, it looks like you passed a cycled structure");
    50          
51              
52 15150 100         if (SvROK(source)) { // reference
53 5051           SV* source_val = SvRV(source);
54 5051           svtype val_type = SvTYPE(source_val);
55              
56 5051 100         if (val_type == SVt_PVCV || val_type == SVt_PVIO) { // CV and IO cannot be copied - just set reference to the same SV
    100          
57 4 50         SvSetSV_nosteal(dest, source);
    50          
58 4 100         if (SvWEAKREF(source)) sv_rvweaken(dest);
    50          
59 4           return;
60             }
61              
62 5047           uint64_t id = PTR2UV(source_val);
63 5047 100         if (xdata) {
64 25 50         auto it = xdata->map.find(id);
65 25 100         if (it != xdata->map.end()) {
66 5 50         SvSetSV_nosteal(dest, it->second);
    50          
67 5 100         if (SvWEAKREF(source)) sv_rvweaken(dest);
    50          
68 7           return;
69             }
70 20 100         if (SvWEAKREF(source)) {
71             // 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
72 10002 50         xdata->weakrefs.push_back({dest, id});
73 20           return;
74             }
75             }
76              
77             GV* cloneGV;
78 5040           bool is_object = SvOBJECT(source_val);
79              
80             // cloning an object with custom clone behavior
81 5040 100         if (is_object) {
82 5010 50         auto mg = mg_findext(source_val, PERL_MAGIC_ext, &clone_marker);
83 5010 100         if (mg) {
84 2           xdata = reinterpret_cast(mg->mg_ptr); // restore top-map after recursive clone() call
85             }
86 5008 50         else if ((cloneGV = gv_fetchmeth(SvSTASH(source_val), HOOK_METHOD, HOOK_METHLEN, 0))) {
    100          
87             // set cloning flag into object's magic to prevent infinite loop if user calls 'clone' again from hook
88 3 50         sv_magicext(source_val, NULL, PERL_MAGIC_ext, &clone_marker, (const char*)xdata, 0);
89 3 50         dSP; ENTER; SAVETMPS;
    50          
90 3 50         PUSHMARK(SP);
    0          
91 3 50         XPUSHs(source);
    0          
92 3           PUTBACK;
93 3 50         int count = call_sv((SV*)GvCV(cloneGV), G_SCALAR);
94 3           SPAGAIN;
95 3           SV* retval = NULL;
96 6 100         while (count--) retval = POPs;
97 3 50         if (retval) SvSetSV(dest, retval);
    50          
    50          
98 3           PUTBACK;
99 3 50         FREETMPS; LEAVE;
    50          
    50          
100             // remove cloning flag from object's magic
101 3 50         sv_unmagicext(source_val, PERL_MAGIC_ext, &clone_marker);
102 3 100         if (xdata) xdata->map[id] = dest;
    50          
103 5010           return;
104             }
105             }
106              
107 5037 50         SV* refval = newSV(0);
108 5037 50         sv_upgrade(dest, SVt_RV);
109 5037           SvRV_set(dest, refval);
110 5037           SvROK_on(dest);
111              
112 5037 100         if (is_object) sv_bless(dest, SvSTASH(source_val)); // cloning an object without any specific clone behavior
    50          
113 5037 100         if (xdata) xdata->map[id] = dest;
    50          
114 5037 100         _clone(aTHX_ refval, source_val, xdata, depth+1);
115              
116 49           return;
117             }
118              
119 10099           switch (SvTYPE(source)) {
120             case SVt_IV: // integer
121             case SVt_NV: // long double
122             case SVt_PV: // string
123             case SVt_PVIV: // string + integer
124             case SVt_PVNV: // string + long double
125             case SVt_PVMG: // blessed scalar (doesn't really true, it's just vars or magic vars)
126             case SVt_PVGV: // typeglob
127             #if PERL_VERSION > 16
128             case SVt_REGEXP: // regexp
129             #endif
130 5062 50         SvSetSV_nosteal(dest, source);
131 5062           return;
132             #if PERL_VERSION <= 16 // fix bug in SvSetSV_nosteal while copying regexp SV prior to perl 5.16.0
133             case SVt_REGEXP: // regexp
134             SvSetSV_nosteal(dest, source);
135             if (SvSTASH(dest) == NULL) SvSTASH_set(dest, gv_stashpv("Regexp",0));
136             return;
137             #endif
138             case SVt_PVAV: { // array
139 19           sv_upgrade(dest, SVt_PVAV);
140 19           SV** srclist = AvARRAY((AV*)source);
141 19           SSize_t srcfill = AvFILLp((AV*)source);
142 19           av_extend((AV*)dest, srcfill); // dest is an empty array. we can set directly it's SV** array for speed
143 19           AvFILLp((AV*)dest) = srcfill; // set array len
144 19           SV** dstlist = AvARRAY((AV*)dest);
145 62 100         for (SSize_t i = 0; i <= srcfill; ++i) {
146 45           SV* srcval = *srclist++;
147 45 50         if (srcval != NULL) { // if not empty slot
148 45           SV* elem = newSV(0);
149 45           dstlist[i] = elem;
150 45           _clone(aTHX_ elem, srcval, xdata, depth+1);
151             }
152             }
153 17           return;
154             }
155             case SVt_PVHV: { // hash
156 5018           sv_upgrade(dest, SVt_PVHV);
157 5018           STRLEN hvmax = HvMAX((HV*)source);
158 5018           HE** hvarr = HvARRAY((HV*)source);
159 5018 50         if (!hvarr) return;
160              
161 40164 100         for (STRLEN i = 0; i <= hvmax; ++i) {
162             const HE* entry;
163 45178 100         for (entry = hvarr[i]; entry; entry = HeNEXT(entry)) {
164 10032           HEK* hek = HeKEY_hek(entry);
165 10032 50         SV* elem = newSV(0);
166 10032 50         hv_storehek((HV*)dest, hek, elem);
167 10032 100         _clone(aTHX_ elem, HeVAL(entry), xdata, depth+1);
168             }
169             }
170              
171 20           return;
172             }
173             case SVt_NULL: // undef
174             default: // BIND, LVALUE, FORMAT - are not copied
175 5148           return;
176             }
177             }
178              
179 28 50         }
    50