mirror of
https://github.com/Eaglercraft-TeaVM-Fork/eagler-teavm.git
synced 2024-12-22 08:14:09 -08:00
Implement TeaVM debugger in IDEA
This commit is contained in:
parent
6e2eb8386b
commit
bad97d70de
|
@ -19,10 +19,11 @@ import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.BlockingQueue;
|
import java.util.concurrent.CompletableFuture;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import java.util.concurrent.ConcurrentMap;
|
import java.util.concurrent.ConcurrentMap;
|
||||||
import java.util.concurrent.LinkedTransferQueue;
|
import java.util.concurrent.ExecutionException;
|
||||||
|
import java.util.concurrent.Future;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
import java.util.concurrent.locks.Lock;
|
import java.util.concurrent.locks.Lock;
|
||||||
import java.util.concurrent.locks.ReentrantLock;
|
import java.util.concurrent.locks.ReentrantLock;
|
||||||
|
@ -30,14 +31,30 @@ import org.codehaus.jackson.JsonNode;
|
||||||
import org.codehaus.jackson.map.ObjectMapper;
|
import org.codehaus.jackson.map.ObjectMapper;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.teavm.chromerdp.data.*;
|
import org.teavm.chromerdp.data.CallArgumentDTO;
|
||||||
import org.teavm.chromerdp.messages.*;
|
import org.teavm.chromerdp.data.CallFrameDTO;
|
||||||
import org.teavm.debugging.javascript.*;
|
import org.teavm.chromerdp.data.LocationDTO;
|
||||||
|
import org.teavm.chromerdp.data.Message;
|
||||||
|
import org.teavm.chromerdp.data.PropertyDescriptorDTO;
|
||||||
|
import org.teavm.chromerdp.data.RemoteObjectDTO;
|
||||||
|
import org.teavm.chromerdp.data.Response;
|
||||||
|
import org.teavm.chromerdp.data.ScopeDTO;
|
||||||
|
import org.teavm.chromerdp.messages.CallFunctionCommand;
|
||||||
|
import org.teavm.chromerdp.messages.CallFunctionResponse;
|
||||||
|
import org.teavm.chromerdp.messages.ContinueToLocationCommand;
|
||||||
|
import org.teavm.chromerdp.messages.GetPropertiesCommand;
|
||||||
|
import org.teavm.chromerdp.messages.GetPropertiesResponse;
|
||||||
|
import org.teavm.chromerdp.messages.RemoveBreakpointCommand;
|
||||||
|
import org.teavm.chromerdp.messages.ScriptParsedNotification;
|
||||||
|
import org.teavm.chromerdp.messages.SetBreakpointCommand;
|
||||||
|
import org.teavm.chromerdp.messages.SetBreakpointResponse;
|
||||||
|
import org.teavm.chromerdp.messages.SuspendedNotification;
|
||||||
|
import org.teavm.debugging.javascript.JavaScriptBreakpoint;
|
||||||
|
import org.teavm.debugging.javascript.JavaScriptCallFrame;
|
||||||
|
import org.teavm.debugging.javascript.JavaScriptDebugger;
|
||||||
|
import org.teavm.debugging.javascript.JavaScriptDebuggerListener;
|
||||||
|
import org.teavm.debugging.javascript.JavaScriptLocation;
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @author Alexey Andreev
|
|
||||||
*/
|
|
||||||
public class ChromeRDPDebugger implements JavaScriptDebugger, ChromeRDPExchangeConsumer {
|
public class ChromeRDPDebugger implements JavaScriptDebugger, ChromeRDPExchangeConsumer {
|
||||||
private static final Logger logger = LoggerFactory.getLogger(ChromeRDPDebugger.class);
|
private static final Logger logger = LoggerFactory.getLogger(ChromeRDPDebugger.class);
|
||||||
private static final Object dummy = new Object();
|
private static final Object dummy = new Object();
|
||||||
|
@ -50,7 +67,8 @@ public class ChromeRDPDebugger implements JavaScriptDebugger, ChromeRDPExchangeC
|
||||||
private ConcurrentMap<String, String> scriptIds = new ConcurrentHashMap<>();
|
private ConcurrentMap<String, String> scriptIds = new ConcurrentHashMap<>();
|
||||||
private boolean suspended;
|
private boolean suspended;
|
||||||
private ObjectMapper mapper = new ObjectMapper();
|
private ObjectMapper mapper = new ObjectMapper();
|
||||||
private ConcurrentMap<Integer, ResponseHandler> responseHandlers = new ConcurrentHashMap<>();
|
private ConcurrentMap<Integer, ResponseHandler<Object>> responseHandlers = new ConcurrentHashMap<>();
|
||||||
|
private ConcurrentMap<Integer, CompletableFuture<Object>> futures = new ConcurrentHashMap<>();
|
||||||
private AtomicInteger messageIdGenerator = new AtomicInteger();
|
private AtomicInteger messageIdGenerator = new AtomicInteger();
|
||||||
private Lock breakpointLock = new ReentrantLock();
|
private Lock breakpointLock = new ReentrantLock();
|
||||||
|
|
||||||
|
@ -89,43 +107,42 @@ public class ChromeRDPDebugger implements JavaScriptDebugger, ChromeRDPExchangeC
|
||||||
private ChromeRDPExchangeListener exchangeListener = messageText -> receiveMessage(messageText);
|
private ChromeRDPExchangeListener exchangeListener = messageText -> receiveMessage(messageText);
|
||||||
|
|
||||||
private void receiveMessage(final String messageText) {
|
private void receiveMessage(final String messageText) {
|
||||||
new Thread() {
|
new Thread(() -> {
|
||||||
@Override public void run() {
|
try {
|
||||||
try {
|
JsonNode jsonMessage = mapper.readTree(messageText);
|
||||||
JsonNode jsonMessage = mapper.readTree(messageText);
|
if (jsonMessage.has("id")) {
|
||||||
if (jsonMessage.has("id")) {
|
Response response = mapper.reader(Response.class).readValue(jsonMessage);
|
||||||
Response response = mapper.reader(Response.class).readValue(jsonMessage);
|
if (response.getError() != null) {
|
||||||
if (response.getError() != null) {
|
if (logger.isWarnEnabled()) {
|
||||||
if (logger.isWarnEnabled()) {
|
logger.warn("Error message #{} received from browser: {}", jsonMessage.get("id"),
|
||||||
logger.warn("Error message #{} received from browser: {}", jsonMessage.get("id"),
|
response.getError().toString());
|
||||||
response.getError().toString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
responseHandlers.remove(response.getId()).received(response.getResult());
|
|
||||||
} else {
|
|
||||||
Message message = mapper.reader(Message.class).readValue(messageText);
|
|
||||||
if (message.getMethod() == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
switch (message.getMethod()) {
|
|
||||||
case "Debugger.paused":
|
|
||||||
firePaused(parseJson(SuspendedNotification.class, message.getParams()));
|
|
||||||
break;
|
|
||||||
case "Debugger.resumed":
|
|
||||||
fireResumed();
|
|
||||||
break;
|
|
||||||
case "Debugger.scriptParsed":
|
|
||||||
scriptParsed(parseJson(ScriptParsedNotification.class, message.getParams()));
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
CompletableFuture<Object> future = futures.remove(response.getId());
|
||||||
if (logger.isErrorEnabled()) {
|
responseHandlers.remove(response.getId()).received(response.getResult(), future);
|
||||||
logger.error("Error receiving message from Google Chrome", e);
|
} else {
|
||||||
|
Message message = mapper.reader(Message.class).readValue(messageText);
|
||||||
|
if (message.getMethod() == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
switch (message.getMethod()) {
|
||||||
|
case "Debugger.paused":
|
||||||
|
firePaused(parseJson(SuspendedNotification.class, message.getParams()));
|
||||||
|
break;
|
||||||
|
case "Debugger.resumed":
|
||||||
|
fireResumed();
|
||||||
|
break;
|
||||||
|
case "Debugger.scriptParsed":
|
||||||
|
scriptParsed(parseJson(ScriptParsedNotification.class, message.getParams()));
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
if (logger.isErrorEnabled()) {
|
||||||
|
logger.error("Error receiving message from Google Chrome", e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}.start();
|
}).start();
|
||||||
}
|
}
|
||||||
|
|
||||||
private synchronized void firePaused(SuspendedNotification params) {
|
private synchronized void firePaused(SuspendedNotification params) {
|
||||||
|
@ -328,7 +345,7 @@ public class ChromeRDPDebugger implements JavaScriptDebugger, ChromeRDPExchangeC
|
||||||
if (logger.isInfoEnabled()) {
|
if (logger.isInfoEnabled()) {
|
||||||
logger.info("Setting breakpoint at {}, message id is ", breakpoint.getLocation(), message.getId());
|
logger.info("Setting breakpoint at {}, message id is ", breakpoint.getLocation(), message.getId());
|
||||||
}
|
}
|
||||||
ResponseHandler handler = node -> {
|
setResponseHandler(message.getId(), (node, out) -> {
|
||||||
if (node != null) {
|
if (node != null) {
|
||||||
SetBreakpointResponse response = mapper.reader(SetBreakpointResponse.class).readValue(node);
|
SetBreakpointResponse response = mapper.reader(SetBreakpointResponse.class).readValue(node);
|
||||||
breakpoint.chromeId = response.getBreakpointId();
|
breakpoint.chromeId = response.getBreakpointId();
|
||||||
|
@ -342,8 +359,7 @@ public class ChromeRDPDebugger implements JavaScriptDebugger, ChromeRDPExchangeC
|
||||||
for (JavaScriptDebuggerListener listener : getListeners()) {
|
for (JavaScriptDebuggerListener listener : getListeners()) {
|
||||||
listener.breakpointChanged(breakpoint);
|
listener.breakpointChanged(breakpoint);
|
||||||
}
|
}
|
||||||
};
|
});
|
||||||
responseHandlers.put(message.getId(), handler);
|
|
||||||
sendMessage(message);
|
sendMessage(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -358,14 +374,18 @@ public class ChromeRDPDebugger implements JavaScriptDebugger, ChromeRDPExchangeC
|
||||||
params.setObjectId(scopeId);
|
params.setObjectId(scopeId);
|
||||||
params.setOwnProperties(true);
|
params.setOwnProperties(true);
|
||||||
message.setParams(mapper.valueToTree(params));
|
message.setParams(mapper.valueToTree(params));
|
||||||
final BlockingQueue<List<RDPLocalVariable>> sync = new LinkedTransferQueue<>();
|
CompletableFuture<List<RDPLocalVariable>> sync = setResponseHandler(message.getId(), (node, out) -> {
|
||||||
responseHandlers.put(message.getId(), node -> {
|
if (node == null) {
|
||||||
GetPropertiesResponse response = mapper.reader(GetPropertiesResponse.class).readValue(node);
|
out.complete(Collections.emptyList());
|
||||||
sync.add(parseProperties(response.getResult()));
|
} else {
|
||||||
|
GetPropertiesResponse response = mapper.reader(GetPropertiesResponse.class).readValue(node);
|
||||||
|
out.complete(parseProperties(response.getResult()));
|
||||||
|
}
|
||||||
});
|
});
|
||||||
sendMessage(message);
|
sendMessage(message);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return sync.take();
|
return read(sync);
|
||||||
} catch (InterruptedException e) {
|
} catch (InterruptedException e) {
|
||||||
return Collections.emptyList();
|
return Collections.emptyList();
|
||||||
}
|
}
|
||||||
|
@ -385,19 +405,19 @@ public class ChromeRDPDebugger implements JavaScriptDebugger, ChromeRDPExchangeC
|
||||||
params.setArguments(new CallArgumentDTO[] { arg });
|
params.setArguments(new CallArgumentDTO[] { arg });
|
||||||
params.setFunctionDeclaration("$dbg_class");
|
params.setFunctionDeclaration("$dbg_class");
|
||||||
message.setParams(mapper.valueToTree(params));
|
message.setParams(mapper.valueToTree(params));
|
||||||
final BlockingQueue<String> sync = new LinkedTransferQueue<>();
|
|
||||||
responseHandlers.put(message.getId(), node -> {
|
CompletableFuture<String> sync = setResponseHandler(message.getId(), (node, out) -> {
|
||||||
if (node == null) {
|
if (node == null) {
|
||||||
sync.add("");
|
out.complete("");
|
||||||
} else {
|
} else {
|
||||||
CallFunctionResponse response = mapper.reader(CallFunctionResponse.class).readValue(node);
|
CallFunctionResponse response = mapper.reader(CallFunctionResponse.class).readValue(node);
|
||||||
RemoteObjectDTO result = response.getResult();
|
RemoteObjectDTO result = response.getResult();
|
||||||
sync.add(result.getValue() != null ? result.getValue().getTextValue() : "");
|
out.complete(result.getValue() != null ? result.getValue().getTextValue() : "");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
sendMessage(message);
|
sendMessage(message);
|
||||||
try {
|
try {
|
||||||
String result = sync.take();
|
String result = read(sync);
|
||||||
return result.isEmpty() ? null : result;
|
return result.isEmpty() ? null : result;
|
||||||
} catch (InterruptedException e) {
|
} catch (InterruptedException e) {
|
||||||
return null;
|
return null;
|
||||||
|
@ -418,20 +438,19 @@ public class ChromeRDPDebugger implements JavaScriptDebugger, ChromeRDPExchangeC
|
||||||
params.setArguments(new CallArgumentDTO[] { arg });
|
params.setArguments(new CallArgumentDTO[] { arg });
|
||||||
params.setFunctionDeclaration("$dbg_repr");
|
params.setFunctionDeclaration("$dbg_repr");
|
||||||
message.setParams(mapper.valueToTree(params));
|
message.setParams(mapper.valueToTree(params));
|
||||||
final BlockingQueue<RepresentationWrapper> sync = new LinkedTransferQueue<>();
|
CompletableFuture<RepresentationWrapper> sync = setResponseHandler(message.getId(), (node, out) -> {
|
||||||
responseHandlers.put(message.getId(), node -> {
|
|
||||||
if (node == null) {
|
if (node == null) {
|
||||||
sync.add(new RepresentationWrapper(null));
|
out.complete(new RepresentationWrapper(null));
|
||||||
} else {
|
} else {
|
||||||
CallFunctionResponse response = mapper.reader(CallFunctionResponse.class).readValue(node);
|
CallFunctionResponse response = mapper.reader(CallFunctionResponse.class).readValue(node);
|
||||||
RemoteObjectDTO result = response.getResult();
|
RemoteObjectDTO result = response.getResult();
|
||||||
sync.add(new RepresentationWrapper(result.getValue() != null
|
out.complete(new RepresentationWrapper(result.getValue() != null
|
||||||
? result.getValue().getTextValue() : null));
|
? result.getValue().getTextValue() : null));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
sendMessage(message);
|
sendMessage(message);
|
||||||
try {
|
try {
|
||||||
RepresentationWrapper result = sync.take();
|
RepresentationWrapper result = read(sync);
|
||||||
return result.repr;
|
return result.repr;
|
||||||
} catch (InterruptedException e) {
|
} catch (InterruptedException e) {
|
||||||
return null;
|
return null;
|
||||||
|
@ -528,7 +547,30 @@ public class ChromeRDPDebugger implements JavaScriptDebugger, ChromeRDPExchangeC
|
||||||
return dto;
|
return dto;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ResponseHandler {
|
@SuppressWarnings("unchecked")
|
||||||
void received(JsonNode node) throws IOException;
|
private <T> CompletableFuture<T> setResponseHandler(int messageId, ResponseHandler<T> handler) {
|
||||||
|
CompletableFuture<T> future = new CompletableFuture<>();
|
||||||
|
futures.put(messageId, (CompletableFuture<Object>) future);
|
||||||
|
responseHandlers.put(messageId, (ResponseHandler<Object>) handler);
|
||||||
|
return future;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ResponseHandler<T> {
|
||||||
|
void received(JsonNode node, CompletableFuture<T> out) throws IOException;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static <T> T read(Future<T> future) throws InterruptedException {
|
||||||
|
try {
|
||||||
|
return future.get();
|
||||||
|
} catch (ExecutionException e) {
|
||||||
|
Throwable cause = e.getCause();
|
||||||
|
if (cause instanceof RuntimeException) {
|
||||||
|
throw (RuntimeException) cause;
|
||||||
|
} else if (cause instanceof Error) {
|
||||||
|
throw (Error) cause;
|
||||||
|
} else {
|
||||||
|
throw new RuntimeException(cause);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2016 Alexey Andreev.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.teavm.chromerdp;
|
||||||
|
|
||||||
|
public class ChromeRDPException extends RuntimeException {
|
||||||
|
public ChromeRDPException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,304 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2016 Alexey Andreev.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.teavm.chromerdp;
|
||||||
|
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.WeakHashMap;
|
||||||
|
import java.util.concurrent.CountDownLatch;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import org.teavm.debugging.Breakpoint;
|
||||||
|
import org.teavm.debugging.CallFrame;
|
||||||
|
import org.teavm.debugging.Debugger;
|
||||||
|
import org.teavm.debugging.DebuggerListener;
|
||||||
|
import org.teavm.debugging.Variable;
|
||||||
|
import org.teavm.debugging.information.URLDebugInformationProvider;
|
||||||
|
|
||||||
|
public final class ChromeRDPRunner {
|
||||||
|
private ChromeRDPServer server;
|
||||||
|
private Debugger debugger;
|
||||||
|
private Map<Breakpoint, Integer> breakpointIds = new WeakHashMap<>();
|
||||||
|
private int currentFrame;
|
||||||
|
private int breakpointIdGen;
|
||||||
|
private volatile Runnable attachListener;
|
||||||
|
private volatile Runnable suspendListener;
|
||||||
|
private volatile Runnable resumeListener;
|
||||||
|
|
||||||
|
private ChromeRDPRunner() {
|
||||||
|
server = new ChromeRDPServer();
|
||||||
|
server.setPort(2357);
|
||||||
|
ChromeRDPDebugger jsDebugger = new ChromeRDPDebugger();
|
||||||
|
server.setExchangeConsumer(jsDebugger);
|
||||||
|
|
||||||
|
new Thread(server::start).start();
|
||||||
|
debugger = new Debugger(jsDebugger, new URLDebugInformationProvider(""));
|
||||||
|
debugger.addListener(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
private DebuggerListener listener = new DebuggerListener() {
|
||||||
|
@Override
|
||||||
|
public void resumed() {
|
||||||
|
if (resumeListener != null) {
|
||||||
|
resumeListener.run();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void paused() {
|
||||||
|
CallFrame[] stack = debugger.getCallStack();
|
||||||
|
if (stack.length > 0) {
|
||||||
|
System.out.println();
|
||||||
|
System.out.println("Suspended at " + stack[0].getLocation());
|
||||||
|
}
|
||||||
|
currentFrame = 0;
|
||||||
|
if (suspendListener != null) {
|
||||||
|
suspendListener.run();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void breakpointStatusChanged(Breakpoint breakpoint) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void attached() {
|
||||||
|
if (attachListener != null) {
|
||||||
|
attachListener.run();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void detached() {
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
public static void main(String[] args) throws IOException {
|
||||||
|
ChromeRDPRunner runner = new ChromeRDPRunner();
|
||||||
|
try {
|
||||||
|
runner.acceptInput();
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
System.out.println("Interrupted");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void acceptInput() throws IOException, InterruptedException {
|
||||||
|
if (!debugger.isAttached()) {
|
||||||
|
System.out.println("Waiting for remote process to attach...");
|
||||||
|
CountDownLatch latch = new CountDownLatch(1);
|
||||||
|
attachListener = latch::countDown;
|
||||||
|
if (!debugger.isAttached()) {
|
||||||
|
try {
|
||||||
|
latch.await();
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
attachListener = null;
|
||||||
|
System.out.println("Attached");
|
||||||
|
}
|
||||||
|
|
||||||
|
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
|
||||||
|
loop: while (true) {
|
||||||
|
System.out.print("> ");
|
||||||
|
String line = reader.readLine();
|
||||||
|
if (line == null) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
line = line.trim();
|
||||||
|
String[] parts = Arrays.stream(line.split(" +"))
|
||||||
|
.map(String::trim)
|
||||||
|
.filter(s -> !s.isEmpty())
|
||||||
|
.toArray(String[]::new);
|
||||||
|
if (parts.length == 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (parts[0]) {
|
||||||
|
case "suspend":
|
||||||
|
if (debugger.isSuspended()) {
|
||||||
|
System.out.println("Suspend command is only available when program is running");
|
||||||
|
} else {
|
||||||
|
CountDownLatch latch = new CountDownLatch(1);
|
||||||
|
suspendListener = latch::countDown;
|
||||||
|
debugger.suspend();
|
||||||
|
latch.await();
|
||||||
|
suspendListener = null;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "detach":
|
||||||
|
break loop;
|
||||||
|
|
||||||
|
case "continue":
|
||||||
|
case "cont":
|
||||||
|
case "c":
|
||||||
|
suspended(parts, resumeCommand);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "breakpoint":
|
||||||
|
case "break":
|
||||||
|
case "br":
|
||||||
|
case "bp":
|
||||||
|
breakpointCommand.execute(parts);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "backtrace":
|
||||||
|
case "bt":
|
||||||
|
suspended(parts, backtraceCommand);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "frame":
|
||||||
|
case "fr":
|
||||||
|
case "f":
|
||||||
|
suspended(parts, frameCommand);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "step":
|
||||||
|
case "s":
|
||||||
|
suspended(parts, stepCommand);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "next":
|
||||||
|
case "n":
|
||||||
|
suspended(parts, nextCommand);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "out":
|
||||||
|
case "o":
|
||||||
|
suspended(parts, outCommand);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "info":
|
||||||
|
suspended(parts, infoCommand);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
System.out.println("Unknown command");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
debugger.detach();
|
||||||
|
server.stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void suspended(String[] arguments, Command command) throws InterruptedException {
|
||||||
|
if (!debugger.isSuspended()) {
|
||||||
|
System.out.println("This command is only available when remote process is suspended");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
command.execute(arguments);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Command resumeCommand = args -> {
|
||||||
|
CountDownLatch latch = new CountDownLatch(1);
|
||||||
|
resumeListener = latch::countDown;
|
||||||
|
debugger.resume();
|
||||||
|
latch.await();
|
||||||
|
resumeListener = null;
|
||||||
|
};
|
||||||
|
|
||||||
|
private Command breakpointCommand = args -> {
|
||||||
|
if (args.length != 3) {
|
||||||
|
System.out.println("Expected 2 arguments");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Breakpoint bp = debugger.createBreakpoint(args[1], Integer.parseInt(args[2]));
|
||||||
|
int id = breakpointIdGen++;
|
||||||
|
breakpointIds.put(bp, id);
|
||||||
|
System.out.println("Breakpoint #" + id + " was set at " + bp.getLocation());
|
||||||
|
};
|
||||||
|
|
||||||
|
private Command backtraceCommand = args -> {
|
||||||
|
CallFrame[] callStack = debugger.getCallStack();
|
||||||
|
for (int i = 0; i < callStack.length; ++i) {
|
||||||
|
StringBuilder sb = new StringBuilder(i == currentFrame ? " -> " : " ");
|
||||||
|
sb.append("#").append(i).append(": ");
|
||||||
|
CallFrame frame = callStack[i];
|
||||||
|
if (frame.getMethod() != null) {
|
||||||
|
sb.append(frame.getMethod().getClassName()).append('.').append(frame.getMethod().getName());
|
||||||
|
} else {
|
||||||
|
sb.append("[unknown method]");
|
||||||
|
}
|
||||||
|
if (frame.getLocation() != null) {
|
||||||
|
sb.append('(').append(frame.getLocation()).append(')');
|
||||||
|
}
|
||||||
|
System.out.println(sb.toString());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private Command frameCommand = args -> {
|
||||||
|
if (args.length != 2) {
|
||||||
|
System.out.println("Expected 1 argument");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
int index = Integer.parseInt(args[1]);
|
||||||
|
int max = debugger.getCallStack().length - 1;
|
||||||
|
if (index < 0 || index > max) {
|
||||||
|
System.out.println("Given frame index is outside of valid range 0.." + max);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
currentFrame = index;
|
||||||
|
};
|
||||||
|
|
||||||
|
private Command stepCommand = args -> debugger.stepInto();
|
||||||
|
|
||||||
|
private Command nextCommand = args -> debugger.stepOver();
|
||||||
|
|
||||||
|
private Command outCommand = args -> debugger.stepOut();
|
||||||
|
|
||||||
|
private Command infoCommand = args -> {
|
||||||
|
if (args.length != 2) {
|
||||||
|
System.out.println("Expected 1 argument");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (args[1]) {
|
||||||
|
case "breakpoints": {
|
||||||
|
List<Breakpoint> sortedBreakpoints = debugger.getBreakpoints().stream()
|
||||||
|
.sorted(Comparator.comparing(breakpointIds::get))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
for (Breakpoint breakpoint : sortedBreakpoints) {
|
||||||
|
int id = breakpointIds.get(breakpoint);
|
||||||
|
System.out.println(" #" + id + ": " + breakpoint.getLocation());
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case "variables": {
|
||||||
|
CallFrame frame = debugger.getCallStack()[currentFrame];
|
||||||
|
for (Variable var : frame.getVariables().values().stream()
|
||||||
|
.sorted(Comparator.comparing(Variable::getName))
|
||||||
|
.collect(Collectors.toList())) {
|
||||||
|
System.out.println(" " + var.getName() + ": " + var.getValue().getType());
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
System.out.println("Invalid argument");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private interface Command {
|
||||||
|
void execute(String[] args) throws InterruptedException;
|
||||||
|
}
|
||||||
|
}
|
|
@ -44,7 +44,6 @@ public class TeaVMDebugProcess extends XDebugProcess {
|
||||||
innerDebugger.addListener(new DebuggerListener() {
|
innerDebugger.addListener(new DebuggerListener() {
|
||||||
@Override
|
@Override
|
||||||
public void resumed() {
|
public void resumed() {
|
||||||
getSession().resume();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -61,7 +61,7 @@ public class TeaVMLineBreakpointHandler extends XBreakpointHandler<XLineBreakpoi
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Breakpoint innerBreakpoint = innerDebugger.createBreakpoint(path, breakpoint.getLine());
|
Breakpoint innerBreakpoint = innerDebugger.createBreakpoint(path, breakpoint.getLine() + 1);
|
||||||
breakpoint.putUserData(TeaVMDebugProcess.INNER_BREAKPOINT_KEY, innerBreakpoint);
|
breakpoint.putUserData(TeaVMDebugProcess.INNER_BREAKPOINT_KEY, innerBreakpoint);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -42,7 +42,7 @@ class TeaVMRunState implements RunProfileState {
|
||||||
@Override
|
@Override
|
||||||
public ExecutionResult execute(Executor executor, @NotNull ProgramRunner runner) throws ExecutionException {
|
public ExecutionResult execute(Executor executor, @NotNull ProgramRunner runner) throws ExecutionException {
|
||||||
XDebuggerManager debuggerManager = XDebuggerManager.getInstance(environment.getProject());
|
XDebuggerManager debuggerManager = XDebuggerManager.getInstance(environment.getProject());
|
||||||
XDebugSession session = debuggerManager.startSession(environment, new XDebugProcessStarter() {
|
XDebugSession session = debuggerManager.startSessionAndShowTab("TeaVM debug", null, new XDebugProcessStarter() {
|
||||||
@NotNull
|
@NotNull
|
||||||
@Override
|
@Override
|
||||||
public XDebugProcess start(@NotNull XDebugSession session) throws ExecutionException {
|
public XDebugProcess start(@NotNull XDebugSession session) throws ExecutionException {
|
||||||
|
@ -50,6 +50,6 @@ class TeaVMRunState implements RunProfileState {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return new DefaultExecutionResult(session.getConsoleView(), session.getDebugProcess().getProcessHandler());
|
return new DefaultExecutionResult(null, session.getDebugProcess().getProcessHandler());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,10 +18,13 @@ package org.teavm.idea.debug;
|
||||||
import com.intellij.openapi.vfs.VirtualFile;
|
import com.intellij.openapi.vfs.VirtualFile;
|
||||||
import com.intellij.xdebugger.XDebuggerUtil;
|
import com.intellij.xdebugger.XDebuggerUtil;
|
||||||
import com.intellij.xdebugger.XSourcePosition;
|
import com.intellij.xdebugger.XSourcePosition;
|
||||||
|
import com.intellij.xdebugger.frame.XCompositeNode;
|
||||||
import com.intellij.xdebugger.frame.XStackFrame;
|
import com.intellij.xdebugger.frame.XStackFrame;
|
||||||
|
import com.intellij.xdebugger.frame.XValueChildrenList;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
import org.teavm.debugging.CallFrame;
|
import org.teavm.debugging.CallFrame;
|
||||||
|
import org.teavm.debugging.Variable;
|
||||||
import org.teavm.debugging.information.SourceLocation;
|
import org.teavm.debugging.information.SourceLocation;
|
||||||
|
|
||||||
class TeaVMStackFrame extends XStackFrame {
|
class TeaVMStackFrame extends XStackFrame {
|
||||||
|
@ -45,10 +48,19 @@ class TeaVMStackFrame extends XStackFrame {
|
||||||
if (innerLocation.getFileName() != null) {
|
if (innerLocation.getFileName() != null) {
|
||||||
virtualFile = stack.findVirtualFile(innerLocation.getFileName());
|
virtualFile = stack.findVirtualFile(innerLocation.getFileName());
|
||||||
}
|
}
|
||||||
line = innerLocation.getLine();
|
line = innerLocation.getLine() - 1;
|
||||||
}
|
}
|
||||||
position = XDebuggerUtil.getInstance().createPosition(virtualFile, line);
|
position = XDebuggerUtil.getInstance().createPosition(virtualFile, line);
|
||||||
}
|
}
|
||||||
return position;
|
return position;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void computeChildren(@NotNull XCompositeNode node) {
|
||||||
|
XValueChildrenList children = new XValueChildrenList();
|
||||||
|
for (Variable variable : innerFrame.getVariables().values()) {
|
||||||
|
children.add(new TeaVMValue(variable.getName(), true, variable.getValue()));
|
||||||
|
}
|
||||||
|
node.addChildren(children, true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,54 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2016 Alexey Andreev.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.teavm.idea.debug;
|
||||||
|
|
||||||
|
import com.intellij.util.PlatformIcons;
|
||||||
|
import com.intellij.xdebugger.frame.XCompositeNode;
|
||||||
|
import com.intellij.xdebugger.frame.XNamedValue;
|
||||||
|
import com.intellij.xdebugger.frame.XValueChildrenList;
|
||||||
|
import com.intellij.xdebugger.frame.XValueNode;
|
||||||
|
import com.intellij.xdebugger.frame.XValuePlace;
|
||||||
|
import javax.swing.Icon;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.teavm.debugging.Value;
|
||||||
|
import org.teavm.debugging.Variable;
|
||||||
|
|
||||||
|
public class TeaVMValue extends XNamedValue {
|
||||||
|
private boolean root;
|
||||||
|
private Value innerValue;
|
||||||
|
|
||||||
|
public TeaVMValue(@NotNull String name, boolean root, Value innerValue) {
|
||||||
|
super(name);
|
||||||
|
this.root = root;
|
||||||
|
this.innerValue = innerValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void computePresentation(@NotNull XValueNode node, @NotNull XValuePlace place) {
|
||||||
|
Icon icon = root ? PlatformIcons.VARIABLE_ICON : PlatformIcons.FIELD_ICON;
|
||||||
|
node.setPresentation(icon, innerValue.getType(), innerValue.getRepresentation(),
|
||||||
|
!innerValue.getProperties().isEmpty());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void computeChildren(@NotNull XCompositeNode node) {
|
||||||
|
XValueChildrenList children = new XValueChildrenList();
|
||||||
|
for (Variable variable : innerValue.getProperties().values()) {
|
||||||
|
children.add(new TeaVMValue(variable.getName(), true, variable.getValue()));
|
||||||
|
}
|
||||||
|
node.addChildren(children, true);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user