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