File: | jdk/src/hotspot/share/cds/cppVtables.cpp |
Warning: | line 83, column 3 Undefined or garbage value returned to caller |
Press '?' to see keyboard shortcuts
Keyboard shortcuts:
1 | /* | |||
2 | * Copyright (c) 2020, 2021, Oracle and/or its affiliates. All rights reserved. | |||
3 | * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. | |||
4 | * | |||
5 | * This code is free software; you can redistribute it and/or modify it | |||
6 | * under the terms of the GNU General Public License version 2 only, as | |||
7 | * published by the Free Software Foundation. | |||
8 | * | |||
9 | * This code is distributed in the hope that it will be useful, but WITHOUT | |||
10 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | |||
11 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License | |||
12 | * version 2 for more details (a copy is included in the LICENSE file that | |||
13 | * accompanied this code). | |||
14 | * | |||
15 | * You should have received a copy of the GNU General Public License version | |||
16 | * 2 along with this work; if not, write to the Free Software Foundation, | |||
17 | * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. | |||
18 | * | |||
19 | * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA | |||
20 | * or visit www.oracle.com if you need additional information or have any | |||
21 | * questions. | |||
22 | * | |||
23 | */ | |||
24 | ||||
25 | #include "precompiled.hpp" | |||
26 | #include "cds/archiveUtils.hpp" | |||
27 | #include "cds/archiveBuilder.hpp" | |||
28 | #include "cds/cppVtables.hpp" | |||
29 | #include "cds/metaspaceShared.hpp" | |||
30 | #include "logging/log.hpp" | |||
31 | #include "oops/instanceClassLoaderKlass.hpp" | |||
32 | #include "oops/instanceMirrorKlass.hpp" | |||
33 | #include "oops/instanceRefKlass.hpp" | |||
34 | #include "oops/methodData.hpp" | |||
35 | #include "oops/objArrayKlass.hpp" | |||
36 | #include "oops/typeArrayKlass.hpp" | |||
37 | #include "runtime/arguments.hpp" | |||
38 | #include "utilities/globalDefinitions.hpp" | |||
39 | ||||
40 | // Objects of the Metadata types (such as Klass and ConstantPool) have C++ vtables. | |||
41 | // (In GCC this is the field <Type>::_vptr, i.e., first word in the object.) | |||
42 | // | |||
43 | // Addresses of the vtables and the methods may be different across JVM runs, | |||
44 | // if libjvm.so is dynamically loaded at a different base address. | |||
45 | // | |||
46 | // To ensure that the Metadata objects in the CDS archive always have the correct vtable: | |||
47 | // | |||
48 | // + at dump time: we redirect the _vptr to point to our own vtables inside | |||
49 | // the CDS image | |||
50 | // + at run time: we clone the actual contents of the vtables from libjvm.so | |||
51 | // into our own tables. | |||
52 | ||||
53 | // Currently, the archive contains ONLY the following types of objects that have C++ vtables. | |||
54 | #define CPP_VTABLE_TYPES_DO(f)f(ConstantPool) f(InstanceKlass) f(InstanceClassLoaderKlass) f (InstanceMirrorKlass) f(InstanceRefKlass) f(Method) f(ObjArrayKlass ) f(TypeArrayKlass) \ | |||
55 | f(ConstantPool) \ | |||
56 | f(InstanceKlass) \ | |||
57 | f(InstanceClassLoaderKlass) \ | |||
58 | f(InstanceMirrorKlass) \ | |||
59 | f(InstanceRefKlass) \ | |||
60 | f(Method) \ | |||
61 | f(ObjArrayKlass) \ | |||
62 | f(TypeArrayKlass) | |||
63 | ||||
64 | class CppVtableInfo { | |||
65 | intptr_t _vtable_size; | |||
66 | intptr_t _cloned_vtable[1]; | |||
67 | public: | |||
68 | static int num_slots(int vtable_size) { | |||
69 | return 1 + vtable_size; // Need to add the space occupied by _vtable_size; | |||
70 | } | |||
71 | int vtable_size() { return int(uintx(_vtable_size)); } | |||
72 | void set_vtable_size(int n) { _vtable_size = intptr_t(n); } | |||
73 | intptr_t* cloned_vtable() { return &_cloned_vtable[0]; } | |||
74 | void zero() { memset(_cloned_vtable, 0, sizeof(intptr_t) * vtable_size()); } | |||
75 | // Returns the address of the next CppVtableInfo that can be placed immediately after this CppVtableInfo | |||
76 | static size_t byte_size(int vtable_size) { | |||
77 | CppVtableInfo i; | |||
78 | return pointer_delta(&i._cloned_vtable[vtable_size], &i, sizeof(u1)); | |||
79 | } | |||
80 | }; | |||
81 | ||||
82 | static inline intptr_t* vtable_of(const Metadata* m) { | |||
83 | return *((intptr_t**)m); | |||
| ||||
84 | } | |||
85 | ||||
86 | template <class T> class CppVtableCloner { | |||
87 | static int get_vtable_length(const char* name); | |||
88 | ||||
89 | public: | |||
90 | // Allocate a clone of the vtable of T from the shared metaspace; | |||
91 | // Initialize the contents of this clone. | |||
92 | static CppVtableInfo* allocate_and_initialize(const char* name); | |||
93 | ||||
94 | // Copy the contents of the vtable of T into info->_cloned_vtable; | |||
95 | static void initialize(const char* name, CppVtableInfo* info); | |||
96 | ||||
97 | static void init_orig_cpp_vtptr(int kind); | |||
98 | }; | |||
99 | ||||
100 | template <class T> | |||
101 | CppVtableInfo* CppVtableCloner<T>::allocate_and_initialize(const char* name) { | |||
102 | int n = get_vtable_length(name); | |||
| ||||
103 | CppVtableInfo* info = | |||
104 | (CppVtableInfo*)ArchiveBuilder::current()->rw_region()->allocate(CppVtableInfo::byte_size(n)); | |||
105 | info->set_vtable_size(n); | |||
106 | initialize(name, info); | |||
107 | return info; | |||
108 | } | |||
109 | ||||
110 | template <class T> | |||
111 | void CppVtableCloner<T>::initialize(const char* name, CppVtableInfo* info) { | |||
112 | T tmp; // Allocate temporary dummy metadata object to get to the original vtable. | |||
113 | int n = info->vtable_size(); | |||
114 | intptr_t* srcvtable = vtable_of(&tmp); | |||
115 | intptr_t* dstvtable = info->cloned_vtable(); | |||
116 | ||||
117 | // We already checked (and, if necessary, adjusted n) when the vtables were allocated, so we are | |||
118 | // safe to do memcpy. | |||
119 | log_debug(cds, vtables)(!(LogImpl<(LogTag::_cds), (LogTag::_vtables), (LogTag::__NO_TAG ), (LogTag::__NO_TAG), (LogTag::__NO_TAG), (LogTag::__NO_TAG) >::is_level(LogLevel::Debug))) ? (void)0 : LogImpl<(LogTag ::_cds), (LogTag::_vtables), (LogTag::__NO_TAG), (LogTag::__NO_TAG ), (LogTag::__NO_TAG), (LogTag::__NO_TAG)>::write<LogLevel ::Debug>("Copying %3d vtable entries for %s", n, name); | |||
120 | memcpy(dstvtable, srcvtable, sizeof(intptr_t) * n); | |||
121 | } | |||
122 | ||||
123 | // To determine the size of the vtable for each type, we use the following | |||
124 | // trick by declaring 2 subclasses: | |||
125 | // | |||
126 | // class CppVtableTesterA: public InstanceKlass {virtual int last_virtual_method() {return 1;} }; | |||
127 | // class CppVtableTesterB: public InstanceKlass {virtual void* last_virtual_method() {return NULL}; }; | |||
128 | // | |||
129 | // CppVtableTesterA and CppVtableTesterB's vtables have the following properties: | |||
130 | // - Their size (N+1) is exactly one more than the size of InstanceKlass's vtable (N) | |||
131 | // - The first N entries have are exactly the same as in InstanceKlass's vtable. | |||
132 | // - Their last entry is different. | |||
133 | // | |||
134 | // So to determine the value of N, we just walk CppVtableTesterA and CppVtableTesterB's tables | |||
135 | // and find the first entry that's different. | |||
136 | // | |||
137 | // This works on all C++ compilers supported by Oracle, but you may need to tweak it for more | |||
138 | // esoteric compilers. | |||
139 | ||||
140 | template <class T> class CppVtableTesterB: public T { | |||
141 | public: | |||
142 | virtual int last_virtual_method() {return 1;} | |||
143 | }; | |||
144 | ||||
145 | template <class T> class CppVtableTesterA : public T { | |||
146 | public: | |||
147 | virtual void* last_virtual_method() { | |||
148 | // Make this different than CppVtableTesterB::last_virtual_method so the C++ | |||
149 | // compiler/linker won't alias the two functions. | |||
150 | return NULL__null; | |||
151 | } | |||
152 | }; | |||
153 | ||||
154 | template <class T> | |||
155 | int CppVtableCloner<T>::get_vtable_length(const char* name) { | |||
156 | CppVtableTesterA<T> a; | |||
157 | CppVtableTesterB<T> b; | |||
158 | ||||
159 | intptr_t* avtable = vtable_of(&a); | |||
160 | intptr_t* bvtable = vtable_of(&b); | |||
161 | ||||
162 | // Start at slot 1, because slot 0 may be RTTI (on Solaris/Sparc) | |||
163 | int vtable_len = 1; | |||
164 | for (; ; vtable_len++) { | |||
165 | if (avtable[vtable_len] != bvtable[vtable_len]) { | |||
166 | break; | |||
167 | } | |||
168 | } | |||
169 | log_debug(cds, vtables)(!(LogImpl<(LogTag::_cds), (LogTag::_vtables), (LogTag::__NO_TAG ), (LogTag::__NO_TAG), (LogTag::__NO_TAG), (LogTag::__NO_TAG) >::is_level(LogLevel::Debug))) ? (void)0 : LogImpl<(LogTag ::_cds), (LogTag::_vtables), (LogTag::__NO_TAG), (LogTag::__NO_TAG ), (LogTag::__NO_TAG), (LogTag::__NO_TAG)>::write<LogLevel ::Debug>("Found %3d vtable entries for %s", vtable_len, name); | |||
170 | ||||
171 | return vtable_len; | |||
172 | } | |||
173 | ||||
174 | #define ALLOCATE_AND_INITIALIZE_VTABLE(c)_index[c_Kind] = CppVtableCloner<c>::allocate_and_initialize ("c"); ArchivePtrMarker::mark_pointer(&_index[c_Kind]); \ | |||
175 | _index[c##_Kind] = CppVtableCloner<c>::allocate_and_initialize(#c); \ | |||
176 | ArchivePtrMarker::mark_pointer(&_index[c##_Kind]); | |||
177 | ||||
178 | #define INITIALIZE_VTABLE(c)CppVtableCloner<c>::initialize("c", _index[c_Kind]); \ | |||
179 | CppVtableCloner<c>::initialize(#c, _index[c##_Kind]); | |||
180 | ||||
181 | #define INIT_ORIG_CPP_VTPTRS(c)CppVtableCloner<c>::init_orig_cpp_vtptr(c_Kind); \ | |||
182 | CppVtableCloner<c>::init_orig_cpp_vtptr(c##_Kind); | |||
183 | ||||
184 | #define DECLARE_CLONED_VTABLE_KIND(c)c_Kind, c ## _Kind, | |||
185 | ||||
186 | enum ClonedVtableKind { | |||
187 | // E.g., ConstantPool_Kind == 0, InstanceKlass_Kind == 1, etc. | |||
188 | CPP_VTABLE_TYPES_DO(DECLARE_CLONED_VTABLE_KIND)ConstantPool_Kind, InstanceKlass_Kind, InstanceClassLoaderKlass_Kind , InstanceMirrorKlass_Kind, InstanceRefKlass_Kind, Method_Kind , ObjArrayKlass_Kind, TypeArrayKlass_Kind, | |||
189 | _num_cloned_vtable_kinds | |||
190 | }; | |||
191 | ||||
192 | // This is a map of all the original vtptrs. E.g., for | |||
193 | // ConstantPool *cp = new (...) ConstantPool(...) ; // a dynamically allocated constant pool | |||
194 | // the following holds true: | |||
195 | // _orig_cpp_vtptrs[ConstantPool_Kind] == ((intptr_t**)cp)[0] | |||
196 | static intptr_t* _orig_cpp_vtptrs[_num_cloned_vtable_kinds]; | |||
197 | static bool _orig_cpp_vtptrs_inited = false; | |||
198 | ||||
199 | template <class T> | |||
200 | void CppVtableCloner<T>::init_orig_cpp_vtptr(int kind) { | |||
201 | assert(kind < _num_cloned_vtable_kinds, "sanity")do { if (!(kind < _num_cloned_vtable_kinds)) { (*g_assert_poison ) = 'X';; report_vm_error("/home/daniel/Projects/java/jdk/src/hotspot/share/cds/cppVtables.cpp" , 201, "assert(" "kind < _num_cloned_vtable_kinds" ") failed" , "sanity"); ::breakpoint(); } } while (0); | |||
202 | T tmp; // Allocate temporary dummy metadata object to get to the original vtable. | |||
203 | intptr_t* srcvtable = vtable_of(&tmp); | |||
204 | _orig_cpp_vtptrs[kind] = srcvtable; | |||
205 | } | |||
206 | ||||
207 | // This is the index of all the cloned vtables. E.g., for | |||
208 | // ConstantPool* cp = ....; // an archived constant pool | |||
209 | // InstanceKlass* ik = ....;// an archived class | |||
210 | // the following holds true: | |||
211 | // _index[ConstantPool_Kind]->cloned_vtable() == ((intptr_t**)cp)[0] | |||
212 | // _index[InstanceKlass_Kind]->cloned_vtable() == ((intptr_t**)ik)[0] | |||
213 | CppVtableInfo** CppVtables::_index = NULL__null; | |||
214 | ||||
215 | char* CppVtables::dumptime_init(ArchiveBuilder* builder) { | |||
216 | assert(DumpSharedSpaces, "must")do { if (!(DumpSharedSpaces)) { (*g_assert_poison) = 'X';; report_vm_error ("/home/daniel/Projects/java/jdk/src/hotspot/share/cds/cppVtables.cpp" , 216, "assert(" "DumpSharedSpaces" ") failed", "must"); ::breakpoint (); } } while (0); | |||
217 | size_t vtptrs_bytes = _num_cloned_vtable_kinds * sizeof(CppVtableInfo*); | |||
218 | _index = (CppVtableInfo**)builder->rw_region()->allocate(vtptrs_bytes); | |||
219 | ||||
220 | CPP_VTABLE_TYPES_DO(ALLOCATE_AND_INITIALIZE_VTABLE)_index[ConstantPool_Kind] = CppVtableCloner<ConstantPool> ::allocate_and_initialize("ConstantPool"); ArchivePtrMarker:: mark_pointer(&_index[ConstantPool_Kind]); _index[InstanceKlass_Kind ] = CppVtableCloner<InstanceKlass>::allocate_and_initialize ("InstanceKlass"); ArchivePtrMarker::mark_pointer(&_index [InstanceKlass_Kind]); _index[InstanceClassLoaderKlass_Kind] = CppVtableCloner<InstanceClassLoaderKlass>::allocate_and_initialize ("InstanceClassLoaderKlass"); ArchivePtrMarker::mark_pointer( &_index[InstanceClassLoaderKlass_Kind]); _index[InstanceMirrorKlass_Kind ] = CppVtableCloner<InstanceMirrorKlass>::allocate_and_initialize ("InstanceMirrorKlass"); ArchivePtrMarker::mark_pointer(& _index[InstanceMirrorKlass_Kind]); _index[InstanceRefKlass_Kind ] = CppVtableCloner<InstanceRefKlass>::allocate_and_initialize ("InstanceRefKlass"); ArchivePtrMarker::mark_pointer(&_index [InstanceRefKlass_Kind]); _index[Method_Kind] = CppVtableCloner <Method>::allocate_and_initialize("Method"); ArchivePtrMarker ::mark_pointer(&_index[Method_Kind]); _index[ObjArrayKlass_Kind ] = CppVtableCloner<ObjArrayKlass>::allocate_and_initialize ("ObjArrayKlass"); ArchivePtrMarker::mark_pointer(&_index [ObjArrayKlass_Kind]); _index[TypeArrayKlass_Kind] = CppVtableCloner <TypeArrayKlass>::allocate_and_initialize("TypeArrayKlass" ); ArchivePtrMarker::mark_pointer(&_index[TypeArrayKlass_Kind ]);; | |||
221 | ||||
222 | size_t cpp_tables_size = builder->rw_region()->top() - builder->rw_region()->base(); | |||
223 | builder->alloc_stats()->record_cpp_vtables((int)cpp_tables_size); | |||
224 | ||||
225 | return (char*)_index; | |||
226 | } | |||
227 | ||||
228 | void CppVtables::serialize(SerializeClosure* soc) { | |||
229 | soc->do_ptr((void**)&_index); | |||
230 | if (soc->reading()) { | |||
231 | CPP_VTABLE_TYPES_DO(INITIALIZE_VTABLE)CppVtableCloner<ConstantPool>::initialize("ConstantPool" , _index[ConstantPool_Kind]); CppVtableCloner<InstanceKlass >::initialize("InstanceKlass", _index[InstanceKlass_Kind]) ; CppVtableCloner<InstanceClassLoaderKlass>::initialize ("InstanceClassLoaderKlass", _index[InstanceClassLoaderKlass_Kind ]); CppVtableCloner<InstanceMirrorKlass>::initialize("InstanceMirrorKlass" , _index[InstanceMirrorKlass_Kind]); CppVtableCloner<InstanceRefKlass >::initialize("InstanceRefKlass", _index[InstanceRefKlass_Kind ]); CppVtableCloner<Method>::initialize("Method", _index [Method_Kind]); CppVtableCloner<ObjArrayKlass>::initialize ("ObjArrayKlass", _index[ObjArrayKlass_Kind]); CppVtableCloner <TypeArrayKlass>::initialize("TypeArrayKlass", _index[TypeArrayKlass_Kind ]);; | |||
232 | } | |||
233 | } | |||
234 | ||||
235 | intptr_t* CppVtables::get_archived_vtable(MetaspaceObj::Type msotype, address obj) { | |||
236 | if (!_orig_cpp_vtptrs_inited) { | |||
237 | CPP_VTABLE_TYPES_DO(INIT_ORIG_CPP_VTPTRS)CppVtableCloner<ConstantPool>::init_orig_cpp_vtptr(ConstantPool_Kind ); CppVtableCloner<InstanceKlass>::init_orig_cpp_vtptr( InstanceKlass_Kind); CppVtableCloner<InstanceClassLoaderKlass >::init_orig_cpp_vtptr(InstanceClassLoaderKlass_Kind); CppVtableCloner <InstanceMirrorKlass>::init_orig_cpp_vtptr(InstanceMirrorKlass_Kind ); CppVtableCloner<InstanceRefKlass>::init_orig_cpp_vtptr (InstanceRefKlass_Kind); CppVtableCloner<Method>::init_orig_cpp_vtptr (Method_Kind); CppVtableCloner<ObjArrayKlass>::init_orig_cpp_vtptr (ObjArrayKlass_Kind); CppVtableCloner<TypeArrayKlass>:: init_orig_cpp_vtptr(TypeArrayKlass_Kind);; | |||
238 | _orig_cpp_vtptrs_inited = true; | |||
239 | } | |||
240 | ||||
241 | Arguments::assert_is_dumping_archive(); | |||
242 | int kind = -1; | |||
243 | switch (msotype) { | |||
244 | case MetaspaceObj::SymbolType: | |||
245 | case MetaspaceObj::TypeArrayU1Type: | |||
246 | case MetaspaceObj::TypeArrayU2Type: | |||
247 | case MetaspaceObj::TypeArrayU4Type: | |||
248 | case MetaspaceObj::TypeArrayU8Type: | |||
249 | case MetaspaceObj::TypeArrayOtherType: | |||
250 | case MetaspaceObj::ConstMethodType: | |||
251 | case MetaspaceObj::ConstantPoolCacheType: | |||
252 | case MetaspaceObj::AnnotationsType: | |||
253 | case MetaspaceObj::MethodCountersType: | |||
254 | case MetaspaceObj::RecordComponentType: | |||
255 | // These have no vtables. | |||
256 | break; | |||
257 | case MetaspaceObj::MethodDataType: | |||
258 | // We don't archive MethodData <-- should have been removed in removed_unsharable_info | |||
259 | ShouldNotReachHere()do { (*g_assert_poison) = 'X';; report_should_not_reach_here( "/home/daniel/Projects/java/jdk/src/hotspot/share/cds/cppVtables.cpp" , 259); ::breakpoint(); } while (0); | |||
260 | break; | |||
261 | default: | |||
262 | for (kind = 0; kind < _num_cloned_vtable_kinds; kind ++) { | |||
263 | if (vtable_of((Metadata*)obj) == _orig_cpp_vtptrs[kind]) { | |||
264 | break; | |||
265 | } | |||
266 | } | |||
267 | if (kind >= _num_cloned_vtable_kinds) { | |||
268 | fatal("Cannot find C++ vtable for " INTPTR_FORMAT " -- you probably added"do { (*g_assert_poison) = 'X';; report_fatal(INTERNAL_ERROR, "/home/daniel/Projects/java/jdk/src/hotspot/share/cds/cppVtables.cpp" , 270, "Cannot find C++ vtable for " "0x%016" "l" "x" " -- you probably added" " a new subtype of Klass or MetaData without updating CPP_VTABLE_TYPES_DO" , p2i(obj)); ::breakpoint(); } while (0) | |||
269 | " a new subtype of Klass or MetaData without updating CPP_VTABLE_TYPES_DO",do { (*g_assert_poison) = 'X';; report_fatal(INTERNAL_ERROR, "/home/daniel/Projects/java/jdk/src/hotspot/share/cds/cppVtables.cpp" , 270, "Cannot find C++ vtable for " "0x%016" "l" "x" " -- you probably added" " a new subtype of Klass or MetaData without updating CPP_VTABLE_TYPES_DO" , p2i(obj)); ::breakpoint(); } while (0) | |||
270 | p2i(obj))do { (*g_assert_poison) = 'X';; report_fatal(INTERNAL_ERROR, "/home/daniel/Projects/java/jdk/src/hotspot/share/cds/cppVtables.cpp" , 270, "Cannot find C++ vtable for " "0x%016" "l" "x" " -- you probably added" " a new subtype of Klass or MetaData without updating CPP_VTABLE_TYPES_DO" , p2i(obj)); ::breakpoint(); } while (0); | |||
271 | } | |||
272 | } | |||
273 | ||||
274 | if (kind >= 0) { | |||
275 | assert(kind < _num_cloned_vtable_kinds, "must be")do { if (!(kind < _num_cloned_vtable_kinds)) { (*g_assert_poison ) = 'X';; report_vm_error("/home/daniel/Projects/java/jdk/src/hotspot/share/cds/cppVtables.cpp" , 275, "assert(" "kind < _num_cloned_vtable_kinds" ") failed" , "must be"); ::breakpoint(); } } while (0); | |||
276 | return _index[kind]->cloned_vtable(); | |||
277 | } else { | |||
278 | return NULL__null; | |||
279 | } | |||
280 | } | |||
281 | ||||
282 | void CppVtables::zero_archived_vtables() { | |||
283 | assert(DumpSharedSpaces, "dump-time only")do { if (!(DumpSharedSpaces)) { (*g_assert_poison) = 'X';; report_vm_error ("/home/daniel/Projects/java/jdk/src/hotspot/share/cds/cppVtables.cpp" , 283, "assert(" "DumpSharedSpaces" ") failed", "dump-time only" ); ::breakpoint(); } } while (0); | |||
284 | for (int kind = 0; kind < _num_cloned_vtable_kinds; kind ++) { | |||
285 | _index[kind]->zero(); | |||
286 | } | |||
287 | } | |||
288 | ||||
289 | bool CppVtables::is_valid_shared_method(const Method* m) { | |||
290 | assert(MetaspaceShared::is_in_shared_metaspace(m), "must be")do { if (!(MetaspaceShared::is_in_shared_metaspace(m))) { (*g_assert_poison ) = 'X';; report_vm_error("/home/daniel/Projects/java/jdk/src/hotspot/share/cds/cppVtables.cpp" , 290, "assert(" "MetaspaceShared::is_in_shared_metaspace(m)" ") failed", "must be"); ::breakpoint(); } } while (0); | |||
291 | return vtable_of(m) == _index[Method_Kind]->cloned_vtable(); | |||
292 | } |