line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
#include "next.h" |
2
|
|
|
|
|
|
|
#include |
3
|
|
|
|
|
|
|
#include |
4
|
|
|
|
|
|
|
|
5
|
|
|
|
|
|
|
namespace xs { |
6
|
|
|
|
|
|
|
|
7
|
|
|
|
|
|
|
static MGVTBL c3_marker; |
8
|
|
|
|
|
|
|
|
9
|
|
|
|
|
|
|
#ifndef FORCEINLINE |
10
|
|
|
|
|
|
|
# if defined(_MSC_VER) |
11
|
|
|
|
|
|
|
# define FORCEINLINE __forceinline |
12
|
|
|
|
|
|
|
# elif defined(__GNUC_) && _GNUC__ > 3 |
13
|
|
|
|
|
|
|
# define FORCEINLINE inline _attribute_ ((_always_inline_)) |
14
|
|
|
|
|
|
|
# else |
15
|
|
|
|
|
|
|
# define FORCEINLINE inline |
16
|
|
|
|
|
|
|
# endif |
17
|
|
|
|
|
|
|
#endif |
18
|
|
|
|
|
|
|
|
19
|
|
|
|
|
|
|
#if PERL_VERSION >= 20 |
20
|
|
|
|
|
|
|
#define HvSUPERCACHE(hv) (HvMROMETA(stash)->super) |
21
|
|
|
|
|
|
|
#else |
22
|
|
|
|
|
|
|
#define HvSUPERCACHE(hv) (HvAUX(stash)->xhv_super) |
23
|
|
|
|
|
|
|
#endif |
24
|
|
|
|
|
|
|
|
25
|
63
|
|
|
|
|
|
static FORCEINLINE I32 __dopoptosub_at (const PERL_CONTEXT* cxstk, I32 startingblock) { |
26
|
|
|
|
|
|
|
I32 i; |
27
|
69
|
100
|
|
|
|
|
for (i = startingblock; i >= 0; --i) if (CxTYPE(cxstk+i) == CXt_SUB) return i; |
|
|
100
|
|
|
|
|
|
28
|
1
|
|
|
|
|
|
return i; |
29
|
|
|
|
|
|
|
} |
30
|
|
|
|
|
|
|
|
31
|
|
|
|
|
|
|
// finds the contextually-enclosing fully-qualified subname, much like looking at (caller($i))[3] until you find a real sub that isn't ANON, etc |
32
|
56
|
|
|
|
|
|
static FORCEINLINE GV* _find_sub (pTHX_ SV** fqnp) { |
33
|
56
|
|
|
|
|
|
const PERL_SI* top_si = PL_curstackinfo; |
34
|
56
|
|
|
|
|
|
const PERL_CONTEXT* ccstack = cxstack; |
35
|
56
|
|
|
|
|
|
I32 cxix = __dopoptosub_at(ccstack, cxstack_ix); |
36
|
|
|
|
|
|
|
|
37
|
62
|
|
|
|
|
|
for (;;) { |
38
|
|
|
|
|
|
|
/* we may be in a higher stacklevel, so dig down deeper */ |
39
|
63
|
100
|
|
|
|
|
while (cxix < 0) { |
40
|
1
|
50
|
|
|
|
|
if (top_si->si_type == PERLSI_MAIN) throw std::logic_error("next::method/next::can/maybe::next::method must be used in method context"); |
|
|
50
|
|
|
|
|
|
41
|
0
|
|
|
|
|
|
top_si = top_si->si_prev; |
42
|
0
|
|
|
|
|
|
ccstack = top_si->si_cxstack; |
43
|
0
|
|
|
|
|
|
cxix = __dopoptosub_at(ccstack, top_si->si_cxix); |
44
|
|
|
|
|
|
|
} |
45
|
|
|
|
|
|
|
|
46
|
62
|
50
|
|
|
|
|
if (PL_DBsub && GvCV(PL_DBsub)) { |
|
|
50
|
|
|
|
|
|
47
|
0
|
0
|
|
|
|
|
if (ccstack[cxix].blk_sub.cv == GvCV(PL_DBsub)) { |
48
|
0
|
|
|
|
|
|
cxix = __dopoptosub_at(ccstack, cxix - 1); |
49
|
0
|
|
|
|
|
|
continue; |
50
|
|
|
|
|
|
|
} |
51
|
0
|
|
|
|
|
|
const I32 dbcxix = __dopoptosub_at(ccstack, cxix - 1); |
52
|
0
|
0
|
|
|
|
|
if (dbcxix >= 0 && ccstack[dbcxix].blk_sub.cv == GvCV(PL_DBsub)) { |
|
|
0
|
|
|
|
|
|
53
|
0
|
0
|
|
|
|
|
if (CxTYPE((PERL_CONTEXT*)(&ccstack[dbcxix])) != CXt_SUB) { |
54
|
0
|
|
|
|
|
|
cxix = dbcxix; |
55
|
0
|
|
|
|
|
|
continue; |
56
|
|
|
|
|
|
|
} |
57
|
|
|
|
|
|
|
} |
58
|
|
|
|
|
|
|
} |
59
|
|
|
|
|
|
|
|
60
|
|
|
|
|
|
|
/* we found a real sub here */ |
61
|
62
|
|
|
|
|
|
CV* cv = ccstack[cxix].blk_sub.cv; |
62
|
62
|
|
|
|
|
|
GV* gv = CvGV(cv); |
63
|
62
|
|
|
|
|
|
HV* stash = GvSTASH(gv); |
64
|
|
|
|
|
|
|
|
65
|
62
|
|
|
|
|
|
MAGIC* mg = mg_findext((SV*)gv, PERL_MAGIC_ext, &c3_marker); |
66
|
62
|
100
|
|
|
|
|
if (mg && (HV*)mg->mg_ptr == stash) { |
|
|
50
|
|
|
|
|
|
67
|
10
|
|
|
|
|
|
*fqnp = mg->mg_obj; |
68
|
10
|
|
|
|
|
|
return gv; |
69
|
|
|
|
|
|
|
} |
70
|
|
|
|
|
|
|
|
71
|
52
|
50
|
|
|
|
|
if (!stash || !HvNAME(stash) || (GvNAMELEN(gv) == 8 && !memcmp(GvNAME(gv), "__ANON__", 8))) { // ANON sub |
|
|
50
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
72
|
7
|
|
|
|
|
|
cxix = __dopoptosub_at(ccstack, cxix - 1); |
73
|
7
|
|
|
|
|
|
continue; |
74
|
|
|
|
|
|
|
} |
75
|
|
|
|
|
|
|
|
76
|
45
|
|
|
|
|
|
return gv; |
77
|
|
|
|
|
|
|
} |
78
|
|
|
|
|
|
|
} |
79
|
|
|
|
|
|
|
|
80
|
55
|
|
|
|
|
|
static FORCEINLINE SV* _make_shared_fqn (pTHX_ GV* gv) { |
81
|
55
|
|
|
|
|
|
HV* stash = GvSTASH(gv); |
82
|
55
|
50
|
|
|
|
|
STRLEN pkglen = HvNAMELEN(stash); |
|
|
50
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
83
|
55
|
|
|
|
|
|
STRLEN fqnlen = pkglen + GvNAMELEN(gv) + 2; |
84
|
55
|
|
|
|
|
|
char fqn[fqnlen+1]; |
85
|
55
|
50
|
|
|
|
|
memcpy(fqn, HvNAME(stash), pkglen); |
|
|
50
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
86
|
55
|
|
|
|
|
|
fqn[pkglen] = ':'; |
87
|
55
|
|
|
|
|
|
fqn[pkglen+1] = ':'; |
88
|
55
|
|
|
|
|
|
memcpy(fqn + pkglen + 2, GvNAME(gv), GvNAMELEN(gv)); |
89
|
55
|
|
|
|
|
|
fqn[fqnlen] = 0; |
90
|
55
|
50
|
|
|
|
|
return newSVpvn_share(fqn, (HvNAMEUTF8(stash) || GvNAMEUTF8(gv)) ? -(I32)fqnlen : (I32)fqnlen, 0); |
|
|
50
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
91
|
|
|
|
|
|
|
} |
92
|
|
|
|
|
|
|
|
93
|
2
|
|
|
|
|
|
static FORCEINLINE void _throw_no_next_method (HV* selfstash, GV* context) { |
94
|
4
|
50
|
|
|
|
|
std::string subname(GvNAME(context), GvNAMELEN(context)); |
95
|
4
|
50
|
|
|
|
|
std::string stashname(HvNAME(selfstash), HvNAMELEN(selfstash)); |
|
|
50
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
96
|
2
|
50
|
|
|
|
|
throw std::logic_error(std::string("No next::method '") + subname + "' found for " + stashname); |
|
|
50
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
97
|
|
|
|
|
|
|
} |
98
|
|
|
|
|
|
|
|
99
|
139
|
|
|
|
|
|
static FORCEINLINE CV* _method (pTHX_ HV* selfstash, GV* context, SV* fqnsv) { |
100
|
139
|
100
|
|
|
|
|
if (!fqnsv) { // cache FQN SV with shared COW hash of current sub in magic to perform hash lookup with precomputed hash |
101
|
129
|
|
|
|
|
|
HV* stash = GvSTASH(context); |
102
|
129
|
|
|
|
|
|
MAGIC* mg = mg_findext((SV*)context, PERL_MAGIC_ext, &c3_marker); |
103
|
129
|
100
|
|
|
|
|
if (!mg || (HV*)mg->mg_ptr != stash) { |
|
|
50
|
|
|
|
|
|
104
|
55
|
50
|
|
|
|
|
if (mg) sv_unmagicext((SV*)context, PERL_MAGIC_ext, &c3_marker); |
105
|
55
|
|
|
|
|
|
bool had_magic = SvRMAGICAL(context); |
106
|
55
|
|
|
|
|
|
fqnsv = _make_shared_fqn(aTHX_ context); |
107
|
55
|
|
|
|
|
|
mg = sv_magicext((SV*)context, fqnsv, PERL_MAGIC_ext, &c3_marker, (const char*)stash, 0); |
108
|
55
|
|
|
|
|
|
mg->mg_flags |= MGf_REFCOUNTED; |
109
|
55
|
50
|
|
|
|
|
if (!had_magic) SvRMAGICAL_off(context); |
110
|
|
|
|
|
|
|
} |
111
|
129
|
|
|
|
|
|
fqnsv = mg->mg_obj; |
112
|
|
|
|
|
|
|
} |
113
|
|
|
|
|
|
|
|
114
|
139
|
100
|
|
|
|
|
struct mro_meta* selfmeta = HvMROMETA(selfstash); |
115
|
139
|
|
|
|
|
|
HV* nmcache = selfmeta->mro_nextmethod; |
116
|
139
|
100
|
|
|
|
|
if (nmcache) { // Use the cached coderef if it exists |
117
|
119
|
|
|
|
|
|
HE* he = hv_fetch_ent(nmcache, fqnsv, 0, 0); |
118
|
119
|
100
|
|
|
|
|
if (he) { |
119
|
80
|
|
|
|
|
|
SV* const val = HeVAL(he); |
120
|
119
|
100
|
|
|
|
|
return val == &PL_sv_undef ? NULL : (CV*)val; |
121
|
|
|
|
|
|
|
} |
122
|
|
|
|
|
|
|
} |
123
|
20
|
|
|
|
|
|
else nmcache = selfmeta->mro_nextmethod = newHV(); //Initialize the next::method cache for this stash if necessary |
124
|
|
|
|
|
|
|
|
125
|
|
|
|
|
|
|
/* beyond here is just for cache misses, so perf isn't as critical */ |
126
|
59
|
|
|
|
|
|
HV* stash = GvSTASH(context); |
127
|
59
|
|
|
|
|
|
char* subname = GvNAME(context); |
128
|
59
|
|
|
|
|
|
STRLEN subname_len = GvNAMELEN(context); |
129
|
59
|
|
|
|
|
|
bool subname_utf8 = GvNAMEUTF8(context); |
130
|
59
|
50
|
|
|
|
|
char* stashname = HvNAME(stash); |
|
|
50
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
131
|
59
|
50
|
|
|
|
|
STRLEN stashname_len = HvNAMELEN(stash); |
|
|
50
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
132
|
|
|
|
|
|
|
|
133
|
|
|
|
|
|
|
/* has ourselves at the top of the list */ |
134
|
59
|
|
|
|
|
|
const mro_alg*const algo = Perl_mro_get_from_name(aTHX_ sv_2mortal(newSVpvs("c3"))); |
135
|
59
|
|
|
|
|
|
AV* linear_av = algo->resolve(aTHX_ selfstash, 0); |
136
|
59
|
|
|
|
|
|
SV** linear_svp = AvARRAY(linear_av); |
137
|
59
|
|
|
|
|
|
I32 entries = AvFILLp(linear_av) + 1; |
138
|
|
|
|
|
|
|
|
139
|
|
|
|
|
|
|
/* Walk down our MRO, skipping everything up to the contextually enclosing class */ |
140
|
90
|
100
|
|
|
|
|
while (entries--) { |
141
|
89
|
|
|
|
|
|
SV*const linear_sv = *linear_svp++; |
142
|
|
|
|
|
|
|
assert(linear_sv); |
143
|
89
|
100
|
|
|
|
|
if (SvCUR(linear_sv) == stashname_len && !memcmp(SvPVX(linear_sv), stashname, stashname_len)) break; |
|
|
100
|
|
|
|
|
|
144
|
|
|
|
|
|
|
} |
145
|
|
|
|
|
|
|
|
146
|
|
|
|
|
|
|
/* Now search the remainder of the MRO for the same method name as the contextually enclosing method */ |
147
|
59
|
100
|
|
|
|
|
if (entries > 0) { |
148
|
94
|
100
|
|
|
|
|
while (entries--) { |
149
|
86
|
|
|
|
|
|
SV*const linear_sv = *linear_svp++; |
150
|
|
|
|
|
|
|
assert(linear_sv); |
151
|
86
|
|
|
|
|
|
HV* curstash = gv_stashsv(linear_sv, 0); |
152
|
|
|
|
|
|
|
|
153
|
86
|
50
|
|
|
|
|
if (!curstash) { |
154
|
0
|
0
|
|
|
|
|
if (ckWARN(WARN_SYNTAX)) Perl_warner(aTHX_ |
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
155
|
|
|
|
|
|
|
packWARN(WARN_SYNTAX), "Can't locate package %" SVf " for @%" HEKf "::ISA", |
156
|
0
|
0
|
|
|
|
|
(void*)linear_sv, HEKfARG( HvNAME_HEK(selfstash) ) |
157
|
0
|
|
|
|
|
|
); |
158
|
0
|
|
|
|
|
|
continue; |
159
|
|
|
|
|
|
|
} |
160
|
|
|
|
|
|
|
|
161
|
86
|
100
|
|
|
|
|
GV** gvp = (GV**)hv_fetch(curstash, subname, subname_utf8 ? -(I32)subname_len : (I32)subname_len, 0); |
162
|
86
|
100
|
|
|
|
|
if (!gvp) continue; |
163
|
|
|
|
|
|
|
|
164
|
49
|
|
|
|
|
|
GV* candidate = *gvp; |
165
|
|
|
|
|
|
|
assert(candidate); |
166
|
|
|
|
|
|
|
|
167
|
49
|
50
|
|
|
|
|
if (SvTYPE(candidate) != SVt_PVGV) |
168
|
0
|
0
|
|
|
|
|
gv_init_pvn(candidate, curstash, subname, subname_len, GV_ADDMULTI|(subname_utf8 ? SVf_UTF8 : 0)); |
169
|
|
|
|
|
|
|
|
170
|
|
|
|
|
|
|
/* Notably, we only look for real entries, not method cache |
171
|
|
|
|
|
|
|
entries, because in C3 the method cache of a parent is not |
172
|
|
|
|
|
|
|
valid for the child */ |
173
|
|
|
|
|
|
|
CV* cand_cv; |
174
|
49
|
50
|
|
|
|
|
if (SvTYPE(candidate) == SVt_PVGV && (cand_cv = GvCV(candidate)) && !GvCVGEN(candidate)) { |
|
|
50
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
175
|
49
|
|
|
|
|
|
SvREFCNT_inc_simple_void_NN(MUTABLE_SV(cand_cv)); |
176
|
49
|
|
|
|
|
|
hv_store_ent(nmcache, fqnsv, MUTABLE_SV(cand_cv), 0); |
177
|
49
|
|
|
|
|
|
return cand_cv; |
178
|
|
|
|
|
|
|
} |
179
|
|
|
|
|
|
|
} |
180
|
|
|
|
|
|
|
} |
181
|
|
|
|
|
|
|
|
182
|
10
|
|
|
|
|
|
hv_store_ent(nmcache, fqnsv, &PL_sv_undef, 0); |
183
|
10
|
|
|
|
|
|
return NULL; |
184
|
|
|
|
|
|
|
} |
185
|
|
|
|
|
|
|
|
186
|
15
|
|
|
|
|
|
CV* next::method (pTHX_ HV* selfstash) { |
187
|
15
|
|
|
|
|
|
SV* fqn = NULL; |
188
|
15
|
50
|
|
|
|
|
GV* context = _find_sub(aTHX_ &fqn); |
189
|
15
|
50
|
|
|
|
|
return _method(aTHX_ selfstash, context, fqn); |
190
|
|
|
|
|
|
|
} |
191
|
|
|
|
|
|
|
|
192
|
41
|
|
|
|
|
|
CV* next::method_strict (pTHX_ HV* selfstash) { |
193
|
41
|
|
|
|
|
|
SV* fqn = NULL; |
194
|
41
|
100
|
|
|
|
|
GV* context = _find_sub(aTHX_ &fqn); |
195
|
40
|
50
|
|
|
|
|
CV* ret = _method(aTHX_ selfstash, context, fqn); |
196
|
40
|
100
|
|
|
|
|
if (!ret) _throw_no_next_method(selfstash, context); |
|
|
50
|
|
|
|
|
|
197
|
38
|
|
|
|
|
|
return ret; |
198
|
|
|
|
|
|
|
} |
199
|
|
|
|
|
|
|
|
200
|
0
|
|
|
|
|
|
CV* next::method (pTHX_ HV* selfstash, GV* context) { return _method(aTHX_ selfstash, context, NULL); } |
201
|
|
|
|
|
|
|
|
202
|
0
|
|
|
|
|
|
CV* next::method_strict (pTHX_ HV* selfstash, GV* context) { |
203
|
0
|
|
|
|
|
|
CV* ret = _method(aTHX_ selfstash, context, NULL); |
204
|
0
|
0
|
|
|
|
|
if (!ret) _throw_no_next_method(selfstash, context); |
205
|
0
|
|
|
|
|
|
return ret; |
206
|
|
|
|
|
|
|
} |
207
|
|
|
|
|
|
|
|
208
|
312
|
|
|
|
|
|
static FORCEINLINE CV* _super_method (pTHX_ HV* selfstash, GV* context) { |
209
|
|
|
|
|
|
|
//omit comparing strings for speed |
210
|
312
|
50
|
|
|
|
|
if (HvMROMETA(selfstash)->mro_which->length != 3) return _method(aTHX_ selfstash, context, NULL); // C3 |
|
|
100
|
|
|
|
|
|
211
|
|
|
|
|
|
|
// DFS |
212
|
228
|
|
|
|
|
|
HV* stash = GvSTASH(context); |
213
|
228
|
|
|
|
|
|
HEK* hek = GvNAME_HEK(context); |
214
|
228
|
50
|
|
|
|
|
HV* cache = HvSUPERCACHE(stash); |
215
|
228
|
100
|
|
|
|
|
if (cache) { |
216
|
224
|
|
|
|
|
|
const HE* const he = (HE*)hv_common(cache, NULL, HEK_KEY(hek), HEK_LEN(hek), HEK_UTF8(hek), 0, NULL, HEK_HASH(hek)); |
217
|
224
|
100
|
|
|
|
|
if (he) { |
218
|
218
|
|
|
|
|
|
GV* gv = MUTABLE_GV(HeVAL(he)); |
219
|
224
|
50
|
|
|
|
|
if (isGV(gv) && (!GvCVGEN(gv) || GvCVGEN(gv) == (PL_sub_generation + HvMROMETA(stash)->cache_gen))) return GvCV(gv); |
|
|
50
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
220
|
|
|
|
|
|
|
} |
221
|
|
|
|
|
|
|
} |
222
|
10
|
|
|
|
|
|
GV* ret = gv_fetchmethod_pvn_flags(stash, HEK_KEY(hek), HEK_LEN(hek), GV_AUTOLOAD|GV_SUPER); |
223
|
10
|
100
|
|
|
|
|
return ret ? (isGV(ret) ? GvCV(ret) : (CV*)ret) : NULL; |
|
|
50
|
|
|
|
|
|
224
|
|
|
|
|
|
|
} |
225
|
|
|
|
|
|
|
|
226
|
360
|
|
|
|
|
|
CV* super::method (pTHX_ HV* selfstash, GV* context) { return _super_method(aTHX_ selfstash, context); } |
227
|
|
|
|
|
|
|
|
228
|
132
|
|
|
|
|
|
CV* super::method_strict (pTHX_ HV* selfstash, GV* context) { |
229
|
132
|
|
|
|
|
|
CV* ret = _super_method(aTHX_ selfstash, context); |
230
|
132
|
50
|
|
|
|
|
if (!ret) { |
231
|
0
|
0
|
|
|
|
|
std::string subname(GvNAME(context), GvNAMELEN(context)); |
232
|
0
|
0
|
|
|
|
|
std::string stashname(HvNAME(selfstash), HvNAMELEN(selfstash)); |
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
233
|
0
|
0
|
|
|
|
|
throw std::logic_error(std::string("No super::") + subname + " found for " + stashname); |
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
234
|
|
|
|
|
|
|
} |
235
|
132
|
|
|
|
|
|
return ret; |
236
|
|
|
|
|
|
|
} |
237
|
|
|
|
|
|
|
|
238
|
|
|
|
|
|
|
} |