This commit is contained in:
Tim Bell 2008-07-14 22:13:54 -07:00
commit 46aa35c92c
8 changed files with 417 additions and 136 deletions

View File

@ -40,11 +40,12 @@ public class MonitorInfoImpl extends MirrorImpl
int stack_depth;
MonitorInfoImpl(VirtualMachine vm, ObjectReference mon,
ThreadReference thread, int dpth) {
ThreadReferenceImpl thread, int dpth) {
super(vm);
this.monitor = mon;
this.thread = thread;
this.stack_depth = dpth;
thread.addListener(this);
}

View File

@ -35,12 +35,34 @@ public class ThreadReferenceImpl extends ObjectReferenceImpl
static final int SUSPEND_STATUS_SUSPENDED = 0x1;
static final int SUSPEND_STATUS_BREAK = 0x2;
private ThreadGroupReference threadGroup;
private int suspendedZombieCount = 0;
// This is cached only while the VM is suspended
private static class Cache extends ObjectReferenceImpl.Cache {
String name = null;
/*
* Some objects can only be created while a thread is suspended and are valid
* only while the thread remains suspended. Examples are StackFrameImpl
* and MonitorInfoImpl. When the thread resumes, these objects have to be
* marked as invalid so that their methods can throw
* InvalidStackFrameException if they are called. To do this, such objects
* register themselves as listeners of the associated thread. When the
* thread is resumed, its listeners are notified and mark themselves
* invalid.
* Also, note that ThreadReferenceImpl itself caches some info that
* is valid only as long as the thread is suspended. When the thread
* is resumed, that cache must be purged.
* Lastly, note that ThreadReferenceImpl and its super, ObjectReferenceImpl
* cache some info that is only valid as long as the entire VM is suspended.
* If _any_ thread is resumed, this cache must be purged. To handle this,
* both ThreadReferenceImpl and ObjectReferenceImpl register themselves as
* VMListeners so that they get notified when all threads are suspended and
* when any thread is resumed.
*/
// This is cached for the life of the thread
private ThreadGroupReference threadGroup;
// This is cached only while this one thread is suspended. Each time
// the thread is resumed, we clear this and start with a fresh one.
private static class LocalCache {
JDWP.ThreadReference.Status status = null;
List<StackFrame> frames = null;
int framesStart = -1;
@ -52,6 +74,17 @@ public class ThreadReferenceImpl extends ObjectReferenceImpl
boolean triedCurrentContended = false;
}
private LocalCache localCache;
private void resetLocalCache() {
localCache = new LocalCache();
}
// This is cached only while all threads in the VM are suspended
// Yes, someone could change the name of a thread while it is suspended.
private static class Cache extends ObjectReferenceImpl.Cache {
String name = null;
}
protected ObjectReferenceImpl.Cache newCache() {
return new Cache();
}
@ -59,8 +92,10 @@ public class ThreadReferenceImpl extends ObjectReferenceImpl
// Listeners - synchronized on vm.state()
private List<WeakReference<ThreadListener>> listeners = new ArrayList<WeakReference<ThreadListener>>();
ThreadReferenceImpl(VirtualMachine aVm, long aRef) {
super(aVm,aRef);
resetLocalCache();
vm.state().addListener(this);
}
@ -72,10 +107,24 @@ public class ThreadReferenceImpl extends ObjectReferenceImpl
* VMListener implementation
*/
public boolean vmNotSuspended(VMAction action) {
synchronized (vm.state()) {
processThreadAction(new ThreadAction(this,
ThreadAction.THREAD_RESUMABLE));
if (action.resumingThread() == null) {
// all threads are being resumed
synchronized (vm.state()) {
processThreadAction(new ThreadAction(this,
ThreadAction.THREAD_RESUMABLE));
}
}
/*
* Othewise, only one thread is being resumed:
* if it is us,
* we have already done our processThreadAction to notify our
* listeners when we processed the resume.
* if it is not us,
* we don't want to notify our listeners
* because we are not being resumed.
*/
return super.vmNotSuspended(action);
}
@ -191,23 +240,19 @@ public class ThreadReferenceImpl extends ObjectReferenceImpl
}
private JDWP.ThreadReference.Status jdwpStatus() {
JDWP.ThreadReference.Status status = null;
JDWP.ThreadReference.Status myStatus = localCache.status;
try {
Cache local = (Cache)getCache();
if (local != null) {
status = local.status;
}
if (status == null) {
status = JDWP.ThreadReference.Status.process(vm, this);
if (local != null) {
local.status = status;
if (myStatus == null) {
myStatus = JDWP.ThreadReference.Status.process(vm, this);
if ((myStatus.suspendStatus & SUSPEND_STATUS_SUSPENDED) != 0) {
// thread is suspended, we can cache the status.
localCache.status = myStatus;
}
}
} catch (JDWPException exc) {
} catch (JDWPException exc) {
throw exc.toJDIException();
}
return status;
return myStatus;
}
public int status() {
@ -245,8 +290,7 @@ public class ThreadReferenceImpl extends ObjectReferenceImpl
public ThreadGroupReference threadGroup() {
/*
* Thread group can't change, so it's cached more conventionally
* than other things in this class.
* Thread group can't change, so it's cached once and for all.
*/
if (threadGroup == null) {
try {
@ -260,19 +304,10 @@ public class ThreadReferenceImpl extends ObjectReferenceImpl
}
public int frameCount() throws IncompatibleThreadStateException {
int frameCount = -1;
try {
Cache local = (Cache)getCache();
if (local != null) {
frameCount = local.frameCount;
}
if (frameCount == -1) {
frameCount = JDWP.ThreadReference.FrameCount
if (localCache.frameCount == -1) {
localCache.frameCount = JDWP.ThreadReference.FrameCount
.process(vm, this).frameCount;
if (local != null) {
local.frameCount = frameCount;
}
}
} catch (JDWPException exc) {
switch (exc.errorCode()) {
@ -283,7 +318,7 @@ public class ThreadReferenceImpl extends ObjectReferenceImpl
throw exc.toJDIException();
}
}
return frameCount;
return localCache.frameCount;
}
public List<StackFrame> frames() throws IncompatibleThreadStateException {
@ -297,23 +332,25 @@ public class ThreadReferenceImpl extends ObjectReferenceImpl
/**
* Is the requested subrange within what has been retrieved?
* local is known to be non-null
* local is known to be non-null. Should only be called from
* a sync method.
*/
private boolean isSubrange(Cache local,
int start, int length, List frames) {
if (start < local.framesStart) {
private boolean isSubrange(LocalCache localCache,
int start, int length) {
if (start < localCache.framesStart) {
return false;
}
if (length == -1) {
return (local.framesLength == -1);
return (localCache.framesLength == -1);
}
if (local.framesLength == -1) {
if ((start + length) > (local.framesStart + frames.size())) {
if (localCache.framesLength == -1) {
if ((start + length) > (localCache.framesStart +
localCache.frames.size())) {
throw new IndexOutOfBoundsException();
}
return true;
}
return ((start + length) <= (local.framesStart + local.framesLength));
return ((start + length) <= (localCache.framesStart + localCache.framesLength));
}
public List<StackFrame> frames(int start, int length)
@ -329,51 +366,42 @@ public class ThreadReferenceImpl extends ObjectReferenceImpl
* Private version of frames() allows "-1" to specify all
* remaining frames.
*/
private List<StackFrame> privateFrames(int start, int length)
synchronized private List<StackFrame> privateFrames(int start, int length)
throws IncompatibleThreadStateException {
List<StackFrame> frames = null;
try {
Cache local = (Cache)getCache();
if (local != null) {
frames = local.frames;
}
if (frames == null || !isSubrange(local, start, length, frames)) {
// Lock must be held while creating stack frames so if that two threads
// do this at the same time, one won't clobber the subset created by the other.
try {
if (localCache.frames == null || !isSubrange(localCache, start, length)) {
JDWP.ThreadReference.Frames.Frame[] jdwpFrames
= JDWP.ThreadReference.Frames.
process(vm, this, start, length).frames;
process(vm, this, start, length).frames;
int count = jdwpFrames.length;
frames = new ArrayList<StackFrame>(count);
localCache.frames = new ArrayList<StackFrame>(count);
// Lock must be held while creating stack frames.
// so that a resume will not resume a partially
// created stack.
synchronized (vm.state()) {
for (int i = 0; i<count; i++) {
if (jdwpFrames[i].location == null) {
throw new InternalException("Invalid frame location");
}
StackFrame frame = new StackFrameImpl(vm, this,
jdwpFrames[i].frameID,
jdwpFrames[i].location);
// Add to the frame list
frames.add(frame);
for (int i = 0; i<count; i++) {
if (jdwpFrames[i].location == null) {
throw new InternalException("Invalid frame location");
}
StackFrame frame = new StackFrameImpl(vm, this,
jdwpFrames[i].frameID,
jdwpFrames[i].location);
// Add to the frame list
localCache.frames.add(frame);
}
if (local != null) {
local.frames = frames;
local.framesStart = start;
local.framesLength = length;
}
localCache.framesStart = start;
localCache.framesLength = length;
return Collections.unmodifiableList(localCache.frames);
} else {
int fromIndex = start - local.framesStart;
int fromIndex = start - localCache.framesStart;
int toIndex;
if (length == -1) {
toIndex = frames.size() - fromIndex;
toIndex = localCache.frames.size() - fromIndex;
} else {
toIndex = fromIndex + length;
}
frames = frames.subList(fromIndex, toIndex);
return Collections.unmodifiableList(localCache.frames.subList(fromIndex, toIndex));
}
} catch (JDWPException exc) {
switch (exc.errorCode()) {
@ -384,28 +412,18 @@ public class ThreadReferenceImpl extends ObjectReferenceImpl
throw exc.toJDIException();
}
}
return Collections.unmodifiableList(frames);
}
public List<ObjectReference> ownedMonitors() throws IncompatibleThreadStateException {
List<ObjectReference> monitors = null;
try {
Cache local = (Cache)getCache();
if (local != null) {
monitors = local.ownedMonitors;
}
if (monitors == null) {
monitors = Arrays.asList(
if (localCache.ownedMonitors == null) {
localCache.ownedMonitors = Arrays.asList(
(ObjectReference[])JDWP.ThreadReference.OwnedMonitors.
process(vm, this).owned);
if (local != null) {
local.ownedMonitors = monitors;
if ((vm.traceFlags & vm.TRACE_OBJREFS) != 0) {
vm.printTrace(description() +
" temporarily caching owned monitors"+
" (count = " + monitors.size() + ")");
}
if ((vm.traceFlags & vm.TRACE_OBJREFS) != 0) {
vm.printTrace(description() +
" temporarily caching owned monitors"+
" (count = " + localCache.ownedMonitors.size() + ")");
}
}
} catch (JDWPException exc) {
@ -417,29 +435,22 @@ public class ThreadReferenceImpl extends ObjectReferenceImpl
throw exc.toJDIException();
}
}
return monitors;
return localCache.ownedMonitors;
}
public ObjectReference currentContendedMonitor()
throws IncompatibleThreadStateException {
ObjectReference monitor = null;
try {
Cache local = (Cache)getCache();
if (local != null && local.triedCurrentContended) {
monitor = local.contendedMonitor;
} else {
monitor = JDWP.ThreadReference.CurrentContendedMonitor.
if (localCache.contendedMonitor == null &&
!localCache.triedCurrentContended) {
localCache.contendedMonitor = JDWP.ThreadReference.CurrentContendedMonitor.
process(vm, this).monitor;
if (local != null) {
local.triedCurrentContended = true;
local.contendedMonitor = monitor;
if ((monitor != null) &&
((vm.traceFlags & vm.TRACE_OBJREFS) != 0)) {
vm.printTrace(description() +
" temporarily caching contended monitor"+
" (id = " + monitor.uniqueID() + ")");
}
localCache.triedCurrentContended = true;
if ((localCache.contendedMonitor != null) &&
((vm.traceFlags & vm.TRACE_OBJREFS) != 0)) {
vm.printTrace(description() +
" temporarily caching contended monitor"+
" (id = " + localCache.contendedMonitor.uniqueID() + ")");
}
}
} catch (JDWPException exc) {
@ -450,40 +461,31 @@ public class ThreadReferenceImpl extends ObjectReferenceImpl
throw exc.toJDIException();
}
}
return monitor;
return localCache.contendedMonitor;
}
public List<MonitorInfo> ownedMonitorsAndFrames() throws IncompatibleThreadStateException {
List<MonitorInfo> monitors = null;
try {
Cache local = (Cache)getCache();
if (local != null) {
monitors = local.ownedMonitorsInfo;
}
if (monitors == null) {
if (localCache.ownedMonitorsInfo == null) {
JDWP.ThreadReference.OwnedMonitorsStackDepthInfo.monitor[] minfo;
minfo = JDWP.ThreadReference.OwnedMonitorsStackDepthInfo.process(vm, this).owned;
monitors = new ArrayList<MonitorInfo>(minfo.length);
localCache.ownedMonitorsInfo = new ArrayList<MonitorInfo>(minfo.length);
for (int i=0; i < minfo.length; i++) {
JDWP.ThreadReference.OwnedMonitorsStackDepthInfo.monitor mi =
minfo[i];
MonitorInfo mon = new MonitorInfoImpl(vm, minfo[i].monitor, this, minfo[i].stack_depth);
monitors.add(mon);
localCache.ownedMonitorsInfo.add(mon);
}
if (local != null) {
local.ownedMonitorsInfo = monitors;
if ((vm.traceFlags & vm.TRACE_OBJREFS) != 0) {
vm.printTrace(description() +
" temporarily caching owned monitors"+
" (count = " + monitors.size() + ")");
if ((vm.traceFlags & vm.TRACE_OBJREFS) != 0) {
vm.printTrace(description() +
" temporarily caching owned monitors"+
" (count = " + localCache.ownedMonitorsInfo.size() + ")");
}
}
}
} catch (JDWPException exc) {
switch (exc.errorCode()) {
case JDWP.Error.THREAD_NOT_SUSPENDED:
@ -493,7 +495,7 @@ public class ThreadReferenceImpl extends ObjectReferenceImpl
throw exc.toJDIException();
}
}
return monitors;
return localCache.ownedMonitorsInfo;
}
public void popFrames(StackFrame frame) throws IncompatibleThreadStateException {
@ -511,7 +513,7 @@ public class ThreadReferenceImpl extends ObjectReferenceImpl
}
public void forceEarlyReturn(Value returnValue) throws InvalidTypeException,
ClassNotLoadedException,
ClassNotLoadedException,
IncompatibleThreadStateException {
if (!vm.canForceEarlyReturn()) {
throw new UnsupportedOperationException(
@ -604,6 +606,9 @@ public class ThreadReferenceImpl extends ObjectReferenceImpl
iter.remove();
}
}
// Discard our local cache
resetLocalCache();
}
}
}

View File

@ -38,10 +38,18 @@ class VMAction extends EventObject {
static final int VM_NOT_SUSPENDED = 2;
int id;
ThreadReference resumingThread;
VMAction(VirtualMachine vm, int id) {
this(vm, null, id);
}
// For id = VM_NOT_SUSPENDED, if resumingThread != null, then it is
// the only thread that is being resumed.
VMAction(VirtualMachine vm, ThreadReference resumingThread, int id) {
super(vm);
this.id = id;
this.resumingThread = resumingThread;
}
VirtualMachine vm() {
return (VirtualMachine)getSource();
@ -49,4 +57,8 @@ class VMAction extends EventObject {
int id() {
return id;
}
ThreadReference resumingThread() {
return resumingThread;
}
}

View File

@ -116,16 +116,25 @@ class VMState {
}
/**
* Tell listeners to invalidate suspend-sensitive caches.
* All threads are resuming
*/
synchronized void thaw() {
void thaw() {
thaw(null);
}
/**
* Tell listeners to invalidate suspend-sensitive caches.
* If resumingThread != null, then only that thread is being
* resumed.
*/
synchronized void thaw(ThreadReference resumingThread) {
if (cache != null) {
if ((vm.traceFlags & vm.TRACE_OBJREFS) != 0) {
vm.printTrace("Clearing VM suspended cache");
}
disableCache();
}
processVMAction(new VMAction(vm, VMAction.VM_NOT_SUSPENDED));
processVMAction(new VMAction(vm, resumingThread, VMAction.VM_NOT_SUSPENDED));
}
private synchronized void processVMAction(VMAction action) {

View File

@ -146,8 +146,9 @@ class VirtualMachineImpl extends MirrorImpl
public boolean threadResumable(ThreadAction action) {
/*
* If any thread is resumed, the VM is considered not suspended.
* Just one thread is being resumed so pass it to thaw.
*/
state.thaw();
state.thaw(action.thread());
return true;
}

View File

@ -25,7 +25,8 @@
* @test
* @bug 6230699
* @summary Test ThreadReference.ownedMonitorsAndFrames()
*
* @bug 6701700
* @summary MonitorInfo objects aren't invalidated when the owning thread is resumed
* @author Swamy Venkataramanappa
*
* @run build TestScaffold VMConnection TargetListener TargetAdapter
@ -100,15 +101,15 @@ public class MonitorFrameInfo extends TestScaffold {
if (!mainThread.frame(0).location().method().name()
.equals("foo3")) {
failure("frame failed");
failure("FAILED: frame failed");
}
if (mainThread.frames().size() != (initialSize + 3)) {
failure("frames size failed");
failure("FAILED: frames size failed");
}
if (mainThread.frames().size() != mainThread.frameCount()) {
failure("frames size not equal to frameCount");
failure("FAILED: frames size not equal to frameCount");
}
/* Test monitor frame info.
@ -119,13 +120,32 @@ public class MonitorFrameInfo extends TestScaffold {
if (monitors.size() != expectedCount) {
failure("monitors count is not equal to expected count");
}
MonitorInfo mon = null;
for (int j=0; j < monitors.size(); j++) {
MonitorInfo mon = (MonitorInfo)monitors.get(j);
mon = (MonitorInfo)monitors.get(j);
System.out.println("Monitor obj " + mon.monitor() + "depth =" +mon.stackDepth());
if (mon.stackDepth() != expectedDepth[j]) {
failure("monitor stack depth is not equal to expected depth");
failure("FAILED: monitor stack depth is not equal to expected depth");
}
}
// The last gotten monInfo is in mon. When we resume the thread,
// it should become invalid. We will step out of the top frame
// so that the frame depth in this mon object will no longer be correct.
// That is why the monInfo's have to become invalid when the thread is
// resumed.
stepOut(mainThread);
boolean ok = false;
try {
System.out.println("*** Saved Monitor obj " + mon.monitor() + "depth =" +mon.stackDepth());
} catch(InvalidStackFrameException ee) {
// ok
ok = true;
System.out.println("Got expected InvalidStackFrameException after a resume");
}
if (!ok) {
failure("FAILED: MonitorInfo object was not invalidated by a resume");
}
} else {
System.out.println("can not get monitors frame info");
}

View File

@ -0,0 +1,233 @@
/*
* Copyright 2008 Sun Microsystems, Inc. All Rights Reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
* CA 95054 USA or visit www.sun.com if you need additional information or
* have any questions.
*/
/**
* @test
* @bug 6700889
* @summary Thread resume invalidates all stack frames, even from other threads
*
* @author jjh
*
* @run build TestScaffold VMConnection TargetListener TargetAdapter
* @run compile -g ResumeOneThreadTest.java
* @run main ResumeOneThreadTest
*/
import com.sun.jdi.*;
import com.sun.jdi.event.*;
import com.sun.jdi.request.*;
import java.util.*;
class ResumeOneThreadTarg extends Thread {
static String name1 = "Thread 1";
static String name2 = "Thread 2";
public ResumeOneThreadTarg(String name) {
super(name);
}
public static void main(String[] args) {
System.out.println(" Debuggee: Howdy!");
ResumeOneThreadTarg t1 = new ResumeOneThreadTarg(name1);
ResumeOneThreadTarg t2 = new ResumeOneThreadTarg(name2);
t1.start();
t2.start();
}
// This just starts two threads. Each runs to a bkpt.
public void run() {
if (getName().equals(name1)) {
run1();
} else {
run2();
}
}
public void bkpt1(String p1) {
System.out.println(" Debuggee: bkpt 1");
}
public void run1() {
bkpt1("Hello Alviso!");
}
public void bkpt2() {
System.out.println(" Debuggee: bkpt 2");
}
public void run2() {
bkpt2();
}
}
/********** test program **********/
public class ResumeOneThreadTest extends TestScaffold {
ReferenceType targetClass;
ThreadReference mainThread;
BreakpointRequest request1;
BreakpointRequest request2;
ThreadReference thread1 = null;
ThreadReference thread2 = null;;
boolean theVMisDead = false;
ResumeOneThreadTest (String args[]) {
super(args);
}
public static void main(String[] args) throws Exception {
new ResumeOneThreadTest(args).startTests();
}
synchronized public void breakpointReached(BreakpointEvent event) {
println("-- Got bkpt at: " + event.location());
ThreadReference eventThread = event.thread();
if (eventThread.name().equals(ResumeOneThreadTarg.name1)) {
thread1 = eventThread;
}
if (eventThread.name().equals(ResumeOneThreadTarg.name2)) {
thread2 = eventThread;
}
}
public void vmDied(VMDeathEvent event) {
theVMisDead = true;
}
synchronized public void eventSetComplete(EventSet set) {
if (theVMisDead) {
return;
}
if (thread1 == null || thread2 == null) {
// Don't do a set.resume(), just let the other thread
// keep running until it hits its bkpt.
return;
}
// Both threads are stopped at their bkpts. Get a StackFrame from
// Thread 1 then resume Thread 2 and verify that the saved StackFrame is
// still valid.
// suspend everything.
println("-- All threads suspended");
vm().suspend();
StackFrame t1sf0 = null;
try {
t1sf0 = thread1.frame(0);
} catch (IncompatibleThreadStateException ee) {
failure("FAILED: Exception: " + ee);
}
println("-- t1sf0 args: " + t1sf0.getArgumentValues());
// Ok, we have a StackFrame for thread 1. Resume just thread 2
// Note that thread 2 has been suspended twice - by the SUSPEND_ALL
// bkpt, and by the above vm().suspend(), so we have to resume
// it twice.
request2.disable();
thread2.resume();
thread2.resume();
println("-- Did Resume on thread 2");
// Can we get frames for thread1?
try {
StackFrame t1sf0_1 = thread1.frame(0);
if (!t1sf0.equals(t1sf0_1)) {
failure("FAILED: Got a different frame 0 for thread 1 after resuming thread 2");
}
} catch (IncompatibleThreadStateException ee) {
failure("FAILED: Could not get frames for thread 1: Exception: " + ee);
} catch (Exception ee) {
failure("FAILED: Could not get frames for thread 1: Exception: " + ee);
}
try {
println("-- t1sf0 args: " + t1sf0.getArgumentValues());
} catch (InvalidStackFrameException ee) {
// This is the failure.
failure("FAILED Got InvalidStackFrameException");
vm().dispose();
throw(ee);
}
// Let the debuggee finish
request1.disable();
thread1.resume();
vm().resume();
println("--------------");
}
/********** test core **********/
protected void runTests() throws Exception {
/*
* Get to the top of main()
* to determine targetClass and mainThread
*/
BreakpointEvent bpe = startToMain("ResumeOneThreadTarg");
targetClass = bpe.location().declaringType();
mainThread = bpe.thread();
EventRequestManager erm = vm().eventRequestManager();
final Thread mainThread = Thread.currentThread();
/*
* Set event requests
*/
Location loc1 = findMethod(targetClass, "bkpt1", "(Ljava/lang/String;)V").location();
request1 = erm.createBreakpointRequest(loc1);
request1.setSuspendPolicy(EventRequest.SUSPEND_EVENT_THREAD);
request1.enable();
Location loc2 = findMethod(targetClass, "bkpt2", "()V").location();
request2 = erm.createBreakpointRequest(loc2);
request2.setSuspendPolicy(EventRequest.SUSPEND_EVENT_THREAD);
request2.enable();
/*
* resume the target, listening for events
*/
listenUntilVMDisconnect();
/*
* deal with results of test
* if anything has called failure("foo") testFailed will be true
*/
if (!testFailed) {
println("ResumeOneThreadTest: passed");
} else {
throw new Exception("ResumeOneThreadTest: failed");
}
}
}

View File

@ -115,7 +115,7 @@ class VMConnection {
return cmdLine;
}
// Insert the options at position 1. Blanks in args are not allowed!
String[] v1 = opts.split(" ");
String[] v1 = opts.split(" +");
String[] retVal = new String[cmdLine.length + v1.length];
retVal[0] = cmdLine[0];
System.arraycopy(v1, 0, retVal, 1, v1.length);