mirror of
https://github.com/corda/corda.git
synced 2025-01-03 19:54:13 +00:00
implement Runtime.addShutdownHook and Thread.setDaemon; avoid segfaults due to an application calling e.g. CallStaticBooleanMethod when it really meant CallStaticVoidMethod
This commit is contained in:
parent
df3baeb83b
commit
c4b5ecec90
@ -59,6 +59,8 @@ public class Runtime {
|
||||
return new MyProcess(process[0], (int) process[1], (int) process[2], (int) process[3]);
|
||||
}
|
||||
|
||||
public native void addShutdownHook(Thread t);
|
||||
|
||||
private static native void exec(String[] command, long[] process);
|
||||
|
||||
private static native int exitValue(long pid);
|
||||
|
@ -252,10 +252,14 @@ public class Thread implements Runnable {
|
||||
return daemon;
|
||||
}
|
||||
|
||||
public void setDaemon(boolean v) {
|
||||
daemon = v;
|
||||
public synchronized void setDaemon(boolean v) {
|
||||
if (v != daemon) {
|
||||
setDaemon(this, v);
|
||||
}
|
||||
}
|
||||
|
||||
private static native void setDaemon(Thread t, boolean increment);
|
||||
|
||||
public static native void yield();
|
||||
|
||||
public synchronized void join() throws InterruptedException {
|
||||
|
@ -726,6 +726,8 @@ extern "C" JNIEXPORT void JNICALL
|
||||
Avian_java_lang_Runtime_exit
|
||||
(Thread* t, object, uintptr_t* arguments)
|
||||
{
|
||||
shutDown(t);
|
||||
|
||||
t->m->system->exit(*arguments);
|
||||
}
|
||||
|
||||
@ -745,6 +747,18 @@ Avian_java_lang_Runtime_totalMemory
|
||||
return 0;
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT void JNICALL
|
||||
Avian_java_lang_Runtime_addShutdownHook
|
||||
(Thread* t, object, uintptr_t* arguments)
|
||||
{
|
||||
object hook = reinterpret_cast<object>(arguments[1]);
|
||||
PROTECT(t, hook);
|
||||
|
||||
ACQUIRE(t, t->m->shutdownLock);
|
||||
|
||||
t->m->shutdownHooks = makePair(t, hook, t->m->shutdownHooks);
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT int64_t JNICALL
|
||||
Avian_java_lang_Throwable_trace
|
||||
(Thread* t, object, uintptr_t* arguments)
|
||||
@ -840,16 +854,8 @@ extern "C" JNIEXPORT int64_t JNICALL
|
||||
Avian_java_lang_Thread_doStart
|
||||
(Thread* t, object, uintptr_t* arguments)
|
||||
{
|
||||
object this_ = reinterpret_cast<object>(*arguments);
|
||||
|
||||
Thread* p = t->m->processor->makeThread(t->m, this_, t);
|
||||
|
||||
if (t->m->system->success(t->m->system->start(&(p->runnable)))) {
|
||||
return reinterpret_cast<int64_t>(p);
|
||||
} else {
|
||||
p->exit();
|
||||
return 0;
|
||||
}
|
||||
return reinterpret_cast<int64_t>
|
||||
(startThread(t, reinterpret_cast<object>(*arguments)));
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT void JNICALL
|
||||
@ -896,6 +902,27 @@ Avian_java_lang_Thread_enumerate
|
||||
return count;
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT void JNICALL
|
||||
Avian_java_lang_Thread_setDaemon
|
||||
(Thread* t, object, uintptr_t* arguments)
|
||||
{
|
||||
object thread = reinterpret_cast<object>(arguments[0]);
|
||||
bool daemon = arguments[1] != 0;
|
||||
|
||||
ACQUIRE_RAW(t, t->m->stateLock);
|
||||
|
||||
threadDaemon(t, thread) = daemon;
|
||||
|
||||
if (daemon) {
|
||||
++ t->m->daemonCount;
|
||||
} else {
|
||||
expect(t, t->m->daemonCount);
|
||||
-- t->m->daemonCount;
|
||||
}
|
||||
|
||||
t->m->stateLock->notifyAll(t->systemThread);
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT int64_t JNICALL
|
||||
Avian_avian_resource_Handler_00024ResourceInputStream_getContentLength
|
||||
(Thread* t, object, uintptr_t* arguments)
|
||||
|
@ -25,37 +25,6 @@ const uintptr_t InterfaceMethodID
|
||||
const uintptr_t NonVirtualMethodID
|
||||
= (static_cast<uintptr_t>(1) << (BitsPerWord - 2));
|
||||
|
||||
jint JNICALL
|
||||
DestroyJavaVM(Machine* m)
|
||||
{
|
||||
System* s = m->system;
|
||||
Heap* h = m->heap;
|
||||
Processor* p = m->processor;
|
||||
Finder* f = m->finder;
|
||||
Thread* t = m->rootThread;
|
||||
|
||||
// wait for other threads to exit
|
||||
{ ACQUIRE(t, m->stateLock);
|
||||
|
||||
while (m->liveCount > 1) {
|
||||
t->m->stateLock->wait(t->systemThread, 0);
|
||||
}
|
||||
}
|
||||
|
||||
int exitCode = (t->exception ? -1 : 0);
|
||||
enter(t, Thread::ActiveState);
|
||||
t->exit();
|
||||
|
||||
m->dispose();
|
||||
h->disposeFixies();
|
||||
p->dispose();
|
||||
h->dispose();
|
||||
f->dispose();
|
||||
s->dispose();
|
||||
|
||||
return exitCode;
|
||||
}
|
||||
|
||||
jint JNICALL
|
||||
AttachCurrentThread(Machine* m, Thread** t, void*)
|
||||
{
|
||||
@ -94,6 +63,30 @@ DetachCurrentThread(Machine* m)
|
||||
}
|
||||
}
|
||||
|
||||
jint JNICALL
|
||||
DestroyJavaVM(Machine* m)
|
||||
{
|
||||
Thread* t; AttachCurrentThread(m, &t, 0);
|
||||
|
||||
// wait for other non-daemon threads to exit
|
||||
{ ACQUIRE(t, t->m->stateLock);
|
||||
while (t->m->liveCount - t->m->daemonCount > 1) {
|
||||
t->m->stateLock->wait(t->systemThread, 0);
|
||||
}
|
||||
}
|
||||
|
||||
{ ENTER(t, Thread::ActiveState);
|
||||
|
||||
shutDown(t);
|
||||
}
|
||||
|
||||
int exitCode = (t->exception ? -1 : 0);
|
||||
|
||||
t->exit();
|
||||
|
||||
return exitCode;
|
||||
}
|
||||
|
||||
jint JNICALL
|
||||
GetEnv(Machine* m, Thread** t, jint version)
|
||||
{
|
||||
@ -431,7 +424,7 @@ CallBooleanMethodV(Thread* t, jobject o, jmethodID m, va_list a)
|
||||
|
||||
object method = getMethod(t, m);
|
||||
object r = t->m->processor->invokeList(t, method, *o, true, a);
|
||||
return (t->exception ? false : (intValue(t, r) != 0));
|
||||
return (r ? (intValue(t, r) != 0) : false);
|
||||
}
|
||||
|
||||
jboolean JNICALL
|
||||
@ -454,7 +447,7 @@ CallByteMethodV(Thread* t, jobject o, jmethodID m, va_list a)
|
||||
|
||||
object method = getMethod(t, m);
|
||||
object r = t->m->processor->invokeList(t, method, *o, true, a);
|
||||
return (t->exception ? 0 : intValue(t, r));
|
||||
return (r ? intValue(t, r) : 0);
|
||||
}
|
||||
|
||||
jbyte JNICALL
|
||||
@ -477,7 +470,7 @@ CallCharMethodV(Thread* t, jobject o, jmethodID m, va_list a)
|
||||
|
||||
object method = getMethod(t, m);
|
||||
object r = t->m->processor->invokeList(t, method, *o, true, a);
|
||||
return (t->exception ? 0 : intValue(t, r));
|
||||
return (r ? intValue(t, r) : 0);
|
||||
}
|
||||
|
||||
jchar JNICALL
|
||||
@ -500,7 +493,7 @@ CallShortMethodV(Thread* t, jobject o, jmethodID m, va_list a)
|
||||
|
||||
object method = getMethod(t, m);
|
||||
object r = t->m->processor->invokeList(t, method, *o, true, a);
|
||||
return (t->exception ? 0 : intValue(t, r));
|
||||
return (r ? intValue(t, r) : 0);
|
||||
}
|
||||
|
||||
jshort JNICALL
|
||||
@ -523,7 +516,7 @@ CallIntMethodV(Thread* t, jobject o, jmethodID m, va_list a)
|
||||
|
||||
object method = getMethod(t, m);
|
||||
object r = t->m->processor->invokeList(t, method, *o, true, a);
|
||||
return (t->exception ? 0 : intValue(t, r));
|
||||
return (r ? intValue(t, r) : 0);
|
||||
}
|
||||
|
||||
jint JNICALL
|
||||
@ -546,7 +539,7 @@ CallLongMethodV(Thread* t, jobject o, jmethodID m, va_list a)
|
||||
|
||||
object method = getMethod(t, m);
|
||||
object r = t->m->processor->invokeList(t, method, *o, true, a);
|
||||
return (t->exception ? 0 : longValue(t, r));
|
||||
return (r ? longValue(t, r) : 0);
|
||||
}
|
||||
|
||||
jlong JNICALL
|
||||
@ -569,7 +562,7 @@ CallFloatMethodV(Thread* t, jobject o, jmethodID m, va_list a)
|
||||
|
||||
object method = getMethod(t, m);
|
||||
object r = t->m->processor->invokeList(t, method, *o, true, a);
|
||||
return (t->exception ? 0 : bitsToFloat(intValue(t, r)));
|
||||
return (r ? bitsToFloat(intValue(t, r)) : 0);
|
||||
}
|
||||
|
||||
jfloat JNICALL
|
||||
@ -592,7 +585,7 @@ CallDoubleMethodV(Thread* t, jobject o, jmethodID m, va_list a)
|
||||
|
||||
object method = getMethod(t, m);
|
||||
object r = t->m->processor->invokeList(t, method, *o, true, a);
|
||||
return (t->exception ? 0 : bitsToDouble(longValue(t, r)));
|
||||
return (r ? bitsToDouble(longValue(t, r)) : 0);
|
||||
}
|
||||
|
||||
jdouble JNICALL
|
||||
@ -666,7 +659,7 @@ CallStaticBooleanMethodV(Thread* t, jclass, jmethodID m, va_list a)
|
||||
ENTER(t, Thread::ActiveState);
|
||||
|
||||
object r = t->m->processor->invokeList(t, getStaticMethod(t, m), 0, true, a);
|
||||
return (t->exception ? 0 : (intValue(t, r) != 0));
|
||||
return (r ? (intValue(t, r) != 0) : false);
|
||||
}
|
||||
|
||||
jboolean JNICALL
|
||||
@ -688,7 +681,7 @@ CallStaticByteMethodV(Thread* t, jclass, jmethodID m, va_list a)
|
||||
ENTER(t, Thread::ActiveState);
|
||||
|
||||
object r = t->m->processor->invokeList(t, getStaticMethod(t, m), 0, true, a);
|
||||
return (t->exception ? 0 : intValue(t, r));
|
||||
return (r ? intValue(t, r) : 0);
|
||||
}
|
||||
|
||||
jbyte JNICALL
|
||||
@ -710,7 +703,7 @@ CallStaticCharMethodV(Thread* t, jclass, jmethodID m, va_list a)
|
||||
ENTER(t, Thread::ActiveState);
|
||||
|
||||
object r = t->m->processor->invokeList(t, getStaticMethod(t, m), 0, true, a);
|
||||
return (t->exception ? 0 : intValue(t, r));
|
||||
return (r ? intValue(t, r) : 0);
|
||||
}
|
||||
|
||||
jchar JNICALL
|
||||
@ -732,7 +725,7 @@ CallStaticShortMethodV(Thread* t, jclass, jmethodID m, va_list a)
|
||||
ENTER(t, Thread::ActiveState);
|
||||
|
||||
object r = t->m->processor->invokeList(t, getStaticMethod(t, m), 0, true, a);
|
||||
return (t->exception ? 0 : intValue(t, r));
|
||||
return (r ? intValue(t, r) : 0);
|
||||
}
|
||||
|
||||
jshort JNICALL
|
||||
@ -754,7 +747,7 @@ CallStaticIntMethodV(Thread* t, jclass, jmethodID m, va_list a)
|
||||
ENTER(t, Thread::ActiveState);
|
||||
|
||||
object r = t->m->processor->invokeList(t, getStaticMethod(t, m), 0, true, a);
|
||||
return (t->exception ? 0 : intValue(t, r));
|
||||
return (r ? intValue(t, r) : 0);
|
||||
}
|
||||
|
||||
jint JNICALL
|
||||
@ -776,7 +769,7 @@ CallStaticLongMethodV(Thread* t, jclass, jmethodID m, va_list a)
|
||||
ENTER(t, Thread::ActiveState);
|
||||
|
||||
object r = t->m->processor->invokeList(t, getStaticMethod(t, m), 0, true, a);
|
||||
return (t->exception ? 0 : longValue(t, r));
|
||||
return (r ? longValue(t, r) : 0);
|
||||
}
|
||||
|
||||
jlong JNICALL
|
||||
@ -798,7 +791,7 @@ CallStaticFloatMethodV(Thread* t, jclass, jmethodID m, va_list a)
|
||||
ENTER(t, Thread::ActiveState);
|
||||
|
||||
object r = t->m->processor->invokeList(t, getStaticMethod(t, m), 0, true, a);
|
||||
return (t->exception ? 0 : bitsToFloat(intValue(t, r)));
|
||||
return (r ? bitsToFloat(intValue(t, r)) : 0);
|
||||
}
|
||||
|
||||
jfloat JNICALL
|
||||
@ -820,7 +813,7 @@ CallStaticDoubleMethodV(Thread* t, jclass, jmethodID m, va_list a)
|
||||
ENTER(t, Thread::ActiveState);
|
||||
|
||||
object r = t->m->processor->invokeList(t, getStaticMethod(t, m), 0, true, a);
|
||||
return (t->exception ? 0 : bitsToDouble(longValue(t, r)));
|
||||
return (r ? bitsToDouble(longValue(t, r)) : 0);
|
||||
}
|
||||
|
||||
jdouble JNICALL
|
||||
|
104
src/machine.cpp
104
src/machine.cpp
@ -146,6 +146,50 @@ disposeAll(Thread* m, Thread* o)
|
||||
dispose(m, o, false);
|
||||
}
|
||||
|
||||
void
|
||||
turnOffTheLights(Thread* t)
|
||||
{
|
||||
expect(t, t->m->liveCount == 1);
|
||||
|
||||
joinAll(t, t->m->rootThread);
|
||||
|
||||
enter(t, Thread::ExitState);
|
||||
|
||||
for (object* p = &(t->m->finalizers); *p;) {
|
||||
object f = *p;
|
||||
*p = finalizerNext(t, *p);
|
||||
|
||||
void (*function)(Thread*, object);
|
||||
memcpy(&function, &finalizerFinalize(t, f), BytesPerWord);
|
||||
function(t, finalizerTarget(t, f));
|
||||
}
|
||||
|
||||
for (object* p = &(t->m->tenuredFinalizers); *p;) {
|
||||
object f = *p;
|
||||
*p = finalizerNext(t, *p);
|
||||
|
||||
void (*function)(Thread*, object);
|
||||
memcpy(&function, &finalizerFinalize(t, f), BytesPerWord);
|
||||
function(t, finalizerTarget(t, f));
|
||||
}
|
||||
|
||||
Machine* m = t->m;
|
||||
|
||||
disposeAll(t, t->m->rootThread);
|
||||
|
||||
System* s = m->system;
|
||||
Heap* h = m->heap;
|
||||
Processor* p = m->processor;
|
||||
Finder* f = m->finder;
|
||||
|
||||
m->dispose();
|
||||
h->disposeFixies();
|
||||
p->dispose();
|
||||
h->dispose();
|
||||
f->dispose();
|
||||
s->dispose();
|
||||
}
|
||||
|
||||
void
|
||||
killZombies(Thread* t, Thread* o)
|
||||
{
|
||||
@ -1976,12 +2020,14 @@ Machine::Machine(System* system, Heap* heap, Finder* finder,
|
||||
propertyCount(propertyCount),
|
||||
activeCount(0),
|
||||
liveCount(0),
|
||||
daemonCount(0),
|
||||
fixedFootprint(0),
|
||||
localThread(0),
|
||||
stateLock(0),
|
||||
heapLock(0),
|
||||
classLock(0),
|
||||
referenceLock(0),
|
||||
shutdownLock(0),
|
||||
libraries(0),
|
||||
loader(0),
|
||||
loadClassMethod(0),
|
||||
@ -1996,6 +2042,7 @@ Machine::Machine(System* system, Heap* heap, Finder* finder,
|
||||
finalizeQueue(0),
|
||||
weakReferences(0),
|
||||
tenuredWeakReferences(0),
|
||||
shutdownHooks(0),
|
||||
unsafe(false),
|
||||
triedBuiltinOnLoad(false),
|
||||
heapPoolIndex(0)
|
||||
@ -2009,6 +2056,7 @@ Machine::Machine(System* system, Heap* heap, Finder* finder,
|
||||
not system->success(system->make(&heapLock)) or
|
||||
not system->success(system->make(&classLock)) or
|
||||
not system->success(system->make(&referenceLock)) or
|
||||
not system->success(system->make(&shutdownLock)) or
|
||||
not system->success
|
||||
(system->load(&libraries, findProperty(this, "avian.bootstrap"), false)))
|
||||
{
|
||||
@ -2024,6 +2072,7 @@ Machine::dispose()
|
||||
heapLock->dispose();
|
||||
classLock->dispose();
|
||||
referenceLock->dispose();
|
||||
shutdownLock->dispose();
|
||||
|
||||
if (libraries) {
|
||||
libraries->disposeAll();
|
||||
@ -2150,7 +2199,7 @@ Thread::exit()
|
||||
enter(this, Thread::ExclusiveState);
|
||||
|
||||
if (m->liveCount == 1) {
|
||||
vm::exit(this);
|
||||
turnOffTheLights(this);
|
||||
} else {
|
||||
enter(this, Thread::ZombieState);
|
||||
}
|
||||
@ -2160,6 +2209,8 @@ Thread::exit()
|
||||
void
|
||||
Thread::dispose()
|
||||
{
|
||||
threadPeer(this, javaThread) = 0;
|
||||
|
||||
if (systemThread) {
|
||||
systemThread->dispose();
|
||||
}
|
||||
@ -2170,31 +2221,41 @@ Thread::dispose()
|
||||
}
|
||||
|
||||
void
|
||||
exit(Thread* t)
|
||||
shutDown(Thread* t)
|
||||
{
|
||||
enter(t, Thread::ExitState);
|
||||
ACQUIRE(t, t->m->shutdownLock);
|
||||
|
||||
joinAll(t, t->m->rootThread);
|
||||
object hooks = t->m->shutdownHooks;
|
||||
PROTECT(t, hooks);
|
||||
|
||||
for (object* p = &(t->m->finalizers); *p;) {
|
||||
object f = *p;
|
||||
*p = finalizerNext(t, *p);
|
||||
t->m->shutdownHooks = 0;
|
||||
|
||||
void (*function)(Thread*, object);
|
||||
memcpy(&function, &finalizerFinalize(t, f), BytesPerWord);
|
||||
function(t, finalizerTarget(t, f));
|
||||
object h = hooks;
|
||||
PROTECT(t, h);
|
||||
for (; h; h = pairSecond(t, h)) {
|
||||
startThread(t, pairFirst(t, h));
|
||||
}
|
||||
|
||||
for (object* p = &(t->m->tenuredFinalizers); *p;) {
|
||||
object f = *p;
|
||||
*p = finalizerNext(t, *p);
|
||||
// wait for hooks to exit
|
||||
h = hooks;
|
||||
for (; h; h = pairSecond(t, h)) {
|
||||
while (true) {
|
||||
Thread* ht = reinterpret_cast<Thread*>(threadPeer(t, pairFirst(t, h)));
|
||||
|
||||
void (*function)(Thread*, object);
|
||||
memcpy(&function, &finalizerFinalize(t, f), BytesPerWord);
|
||||
function(t, finalizerTarget(t, f));
|
||||
{ ACQUIRE(t, t->m->stateLock);
|
||||
|
||||
if (ht == 0
|
||||
or ht->state == Thread::ZombieState
|
||||
or ht->state == Thread::JoinedState)
|
||||
{
|
||||
break;
|
||||
} else {
|
||||
ENTER(t, Thread::IdleState);
|
||||
t->m->stateLock->wait(t->systemThread, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
disposeAll(t, t->m->rootThread);
|
||||
}
|
||||
|
||||
void
|
||||
@ -2255,6 +2316,10 @@ enter(Thread* t, Thread::State s)
|
||||
if (s == Thread::ZombieState) {
|
||||
assert(t, t->m->liveCount > 0);
|
||||
-- t->m->liveCount;
|
||||
|
||||
if (threadDaemon(t, t->javaThread)) {
|
||||
-- t->m->daemonCount;
|
||||
}
|
||||
}
|
||||
t->state = s;
|
||||
|
||||
@ -2308,7 +2373,7 @@ enter(Thread* t, Thread::State s)
|
||||
|
||||
t->state = s;
|
||||
|
||||
while (t->m->liveCount > 1) {
|
||||
while (t->m->liveCount - t->m->daemonCount > 1) {
|
||||
t->m->stateLock->wait(t->systemThread, 0);
|
||||
}
|
||||
} break;
|
||||
@ -3436,6 +3501,7 @@ visitRoots(Machine* m, Heap::Visitor* v)
|
||||
v->visit(&(m->byteArrayMap));
|
||||
v->visit(&(m->types));
|
||||
v->visit(&(m->jniMethodTable));
|
||||
v->visit(&(m->shutdownHooks));
|
||||
|
||||
for (Thread* t = m->rootThread; t; t = t->peer) {
|
||||
::visitRoots(t, v);
|
||||
|
@ -1178,12 +1178,14 @@ class Machine {
|
||||
unsigned propertyCount;
|
||||
unsigned activeCount;
|
||||
unsigned liveCount;
|
||||
unsigned daemonCount;
|
||||
unsigned fixedFootprint;
|
||||
System::Local* localThread;
|
||||
System::Monitor* stateLock;
|
||||
System::Monitor* heapLock;
|
||||
System::Monitor* classLock;
|
||||
System::Monitor* referenceLock;
|
||||
System::Monitor* shutdownLock;
|
||||
System::Library* libraries;
|
||||
object loader;
|
||||
object loadClassMethod;
|
||||
@ -1198,6 +1200,7 @@ class Machine {
|
||||
object finalizeQueue;
|
||||
object weakReferences;
|
||||
object tenuredWeakReferences;
|
||||
object shutdownHooks;
|
||||
bool unsafe;
|
||||
bool triedBuiltinOnLoad;
|
||||
JavaVMVTable javaVMVTable;
|
||||
@ -1394,6 +1397,9 @@ dispose(Thread* t, Reference* r)
|
||||
void
|
||||
collect(Thread* t, Heap::CollectionType type);
|
||||
|
||||
void
|
||||
shutDown(Thread* t);
|
||||
|
||||
#ifdef VM_STRESS
|
||||
|
||||
inline void
|
||||
@ -1599,6 +1605,19 @@ setObjectClass(Thread*, object o, object value)
|
||||
| (reinterpret_cast<uintptr_t>(cast<object>(o, 0)) & (~PointerMask)));
|
||||
}
|
||||
|
||||
inline Thread*
|
||||
startThread(Thread* t, object javaThread)
|
||||
{
|
||||
Thread* p = t->m->processor->makeThread(t->m, javaThread, t);
|
||||
|
||||
if (t->m->system->success(t->m->system->start(&(p->runnable)))) {
|
||||
return p;
|
||||
} else {
|
||||
p->exit();
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
inline const char*
|
||||
findProperty(Machine* m, const char* name)
|
||||
{
|
||||
@ -2341,9 +2360,6 @@ interrupt(Thread*, Thread* target)
|
||||
object
|
||||
intern(Thread* t, object s);
|
||||
|
||||
void
|
||||
exit(Thread* t);
|
||||
|
||||
void
|
||||
walk(Thread* t, Heap::Walker* w, object o, unsigned start);
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user