First working prototype of debugger

This commit is contained in:
konsoletyper 2014-07-30 17:46:41 +04:00
parent f6853eda80
commit c2eecaefca
8 changed files with 113 additions and 121 deletions

View File

@ -107,7 +107,7 @@ public class ChromeRDPDebugger implements JavaScriptDebugger {
} }
@Override @Override
public JavaScriptBreakpoint createBreakpoint(JavaScriptLocation location) { public synchronized JavaScriptBreakpoint createBreakpoint(JavaScriptLocation location) {
RDPBreakpoint breakpoint = new RDPBreakpoint(this, location); RDPBreakpoint breakpoint = new RDPBreakpoint(this, location);
breakpoints.add(breakpoint); breakpoints.add(breakpoint);
if (endpoint != null) { if (endpoint != null) {
@ -116,7 +116,7 @@ public class ChromeRDPDebugger implements JavaScriptDebugger {
return breakpoint; return breakpoint;
} }
void destroyBreakpoint(RDPBreakpoint breakpoint) { synchronized void destroyBreakpoint(RDPBreakpoint breakpoint) {
breakpoints.remove(breakpoint); breakpoints.remove(breakpoint);
if (endpoint != null) { if (endpoint != null) {
endpoint.destroyBreakpoint(breakpoint); endpoint.destroyBreakpoint(breakpoint);
@ -140,4 +140,10 @@ public class ChromeRDPDebugger implements JavaScriptDebugger {
listener.scriptAdded(script); listener.scriptAdded(script);
} }
} }
void fireBreakpointStatusChanged(RDPBreakpoint breakpoint) {
for (JavaScriptDebuggerListener listener : listeners) {
listener.breakpointChanged(breakpoint);
}
}
} }

View File

@ -18,7 +18,10 @@ package org.teavm.chromerpd;
import java.io.IOException; import java.io.IOException;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import javax.websocket.*; import javax.websocket.OnClose;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.ServerEndpoint; import javax.websocket.server.ServerEndpoint;
import org.codehaus.jackson.JsonNode; import org.codehaus.jackson.JsonNode;
import org.codehaus.jackson.map.ObjectMapper; import org.codehaus.jackson.map.ObjectMapper;
@ -27,7 +30,9 @@ import org.teavm.chromerpd.data.LocationDTO;
import org.teavm.chromerpd.data.Message; import org.teavm.chromerpd.data.Message;
import org.teavm.chromerpd.data.Response; import org.teavm.chromerpd.data.Response;
import org.teavm.chromerpd.messages.*; import org.teavm.chromerpd.messages.*;
import org.teavm.debugging.*; import org.teavm.debugging.JavaScriptBreakpoint;
import org.teavm.debugging.JavaScriptCallFrame;
import org.teavm.debugging.JavaScriptLocation;
/** /**
* *
@ -39,9 +44,9 @@ public class ChromeRDPDebuggerEndpoint {
private RDPCallFrame[] callStack = new RDPCallFrame[0]; private RDPCallFrame[] callStack = new RDPCallFrame[0];
private Map<String, String> scripts = new HashMap<>(); private Map<String, String> scripts = new HashMap<>();
private Map<String, String> scriptIds = new HashMap<>(); private Map<String, String> scriptIds = new HashMap<>();
private boolean suspended; private boolean suspended = false;
private ObjectMapper mapper = new ObjectMapper(); private ObjectMapper mapper = new ObjectMapper();
private Map<Integer, Deferred> deferredResponses = new HashMap<>(); private Map<Integer, ResponseHandler> responseHandlers = new HashMap<>();
private int messageIdGenerator; private int messageIdGenerator;
boolean closed; boolean closed;
private ChromeRDPDebugger debugger; private ChromeRDPDebugger debugger;
@ -70,7 +75,7 @@ public class ChromeRDPDebuggerEndpoint {
JsonNode jsonMessage = mapper.readTree(messageText); JsonNode jsonMessage = mapper.readTree(messageText);
if (jsonMessage.has("result")) { if (jsonMessage.has("result")) {
Response response = mapper.reader(Response.class).readValue(jsonMessage); Response response = mapper.reader(Response.class).readValue(jsonMessage);
deferredResponses.remove(response.getId()).set(response.getResult()); responseHandlers.remove(response.getId()).received(response.getResult());
} else { } else {
Message message = mapper.reader(Message.class).readValue(messageText); Message message = mapper.reader(Message.class).readValue(messageText);
switch (message.getMethod()) { switch (message.getMethod()) {
@ -91,13 +96,14 @@ public class ChromeRDPDebuggerEndpoint {
return mapper.reader(type).readValue(node); return mapper.reader(type).readValue(node);
} }
private void firePaused(SuspendedNotification params) { private synchronized void firePaused(SuspendedNotification params) {
suspended = true; suspended = true;
CallFrameDTO[] callFrameDTOs = params.getCallFrames(); CallFrameDTO[] callFrameDTOs = params.getCallFrames();
RDPCallFrame[] callFrames = new RDPCallFrame[callFrameDTOs.length]; RDPCallFrame[] callStack = new RDPCallFrame[callFrameDTOs.length];
for (int i = 0; i < callFrames.length; ++i) { for (int i = 0; i < callStack.length; ++i) {
callFrames[i] = map(callFrameDTOs[i]); callStack[i] = map(callFrameDTOs[i]);
} }
this.callStack = callStack;
debugger.firePaused(); debugger.firePaused();
} }
@ -117,12 +123,13 @@ public class ChromeRDPDebuggerEndpoint {
return dto; return dto;
} }
private void fireResumed() { private synchronized void fireResumed() {
suspended = false; suspended = false;
callStack = null;
debugger.fireResumed(); debugger.fireResumed();
} }
private void scriptParsed(ScriptParsedNotification params) { private synchronized void scriptParsed(ScriptParsedNotification params) {
if (scripts.containsKey(params.getScriptId())) { if (scripts.containsKey(params.getScriptId())) {
return; return;
} }
@ -188,7 +195,7 @@ public class ChromeRDPDebuggerEndpoint {
sendMessage(message); sendMessage(message);
} }
public void continueToLocation(JavaScriptLocation location) { public synchronized void continueToLocation(JavaScriptLocation location) {
Message message = new Message(); Message message = new Message();
message.setMethod("Debugger.continueToLocation"); message.setMethod("Debugger.continueToLocation");
ContinueToLocationCommand params = new ContinueToLocationCommand(); ContinueToLocationCommand params = new ContinueToLocationCommand();
@ -197,35 +204,34 @@ public class ChromeRDPDebuggerEndpoint {
sendMessage(message); sendMessage(message);
} }
public boolean isSuspended() { public synchronized boolean isSuspended() {
return suspended; return suspended;
} }
public JavaScriptCallFrame[] getCallStack() { public synchronized JavaScriptCallFrame[] getCallStack() {
return callStack; return callStack;
} }
public JavaScriptBreakpoint getCurrentBreakpoint() { public synchronized JavaScriptBreakpoint getCurrentBreakpoint() {
return null; return null;
} }
public void updateBreakpoint(RDPBreakpoint breakpoint) { public synchronized void updateBreakpoint(final RDPBreakpoint breakpoint) {
Message message = new Message(); Message message = new Message();
message.setId(messageIdGenerator++); message.setId(++messageIdGenerator);
message.setMethod("Debugger.setBreakpoint"); message.setMethod("Debugger.setBreakpoint");
SetBreakpointCommand params = new SetBreakpointCommand(); SetBreakpointCommand params = new SetBreakpointCommand();
params.setLocation(unmap(breakpoint.getLocation())); params.setLocation(unmap(breakpoint.getLocation()));
message.setParams(mapper.valueToTree(params)); message.setParams(mapper.valueToTree(params));
Deferred deferred = new Deferred(); ResponseHandler handler = new ResponseHandler() {
deferredResponses.put(message.getId(), deferred); @Override public void received(JsonNode node) throws IOException {
sendMessage(message); SetBreakpointResponse response = mapper.reader(SetBreakpointResponse.class).readValue(node);
try {
SetBreakpointResponse response = mapper.reader(SetBreakpointResponse.class)
.readValue((JsonNode)deferred.get());
breakpoint.chromeId = response.getBreakpointId(); breakpoint.chromeId = response.getBreakpointId();
} catch (IOException e) { debugger.fireBreakpointStatusChanged(breakpoint);
throw new RuntimeException(e);
} }
};
responseHandlers.put(message.getId(), handler);
sendMessage(message);
} }
void destroyBreakpoint(RDPBreakpoint breakpoint) { void destroyBreakpoint(RDPBreakpoint breakpoint) {
@ -238,4 +244,8 @@ public class ChromeRDPDebuggerEndpoint {
sendMessage(message); sendMessage(message);
} }
} }
interface ResponseHandler {
void received(JsonNode node) throws IOException;
}
} }

View File

@ -1,48 +0,0 @@
/*
* Copyright 2014 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.chromerpd;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicBoolean;
/**
*
* @author Alexey Andreev
*/
public class Deferred {
private volatile Object calculatedValue;
private AtomicBoolean calculated = new AtomicBoolean();
private CountDownLatch latch = new CountDownLatch(1);
public Object get() {
while (latch.getCount() > 0) {
try {
latch.await();
} catch (InterruptedException e) {
throw new RuntimeException("Interrupted while awaiting for value");
}
}
return calculatedValue;
}
public void set(Object value) {
if (!calculated.compareAndSet(false, true)) {
throw new IllegalStateException("Future already calculated");
}
calculatedValue = value;
latch.countDown();
}
}

View File

@ -23,7 +23,7 @@ import org.teavm.debugging.JavaScriptLocation;
* @author Alexey Andreev * @author Alexey Andreev
*/ */
public class RDPBreakpoint implements JavaScriptBreakpoint { public class RDPBreakpoint implements JavaScriptBreakpoint {
String chromeId; volatile String chromeId;
private ChromeRDPDebugger debugger; private ChromeRDPDebugger debugger;
private JavaScriptLocation location; private JavaScriptLocation location;
@ -41,12 +41,13 @@ public class RDPBreakpoint implements JavaScriptBreakpoint {
public void destroy() { public void destroy() {
if (debugger != null) { if (debugger != null) {
debugger.destroyBreakpoint(this); debugger.destroyBreakpoint(this);
chromeId = null;
debugger = null; debugger = null;
} }
} }
@Override @Override
public boolean isValid() { public boolean isValid() {
return false; return chromeId != null;
} }
} }

View File

@ -3,7 +3,7 @@
"name": "TeaVM debugger agent", "name": "TeaVM debugger agent",
"description": "TeaVM debugger agent, that sends RDP commands over WebSocket", "description": "TeaVM debugger agent, that sends RDP commands over WebSocket",
"version": "1.0", "version": "0.2",
"permissions" : ["debugger", "activeTab", "tabs"], "permissions" : ["debugger", "activeTab", "tabs"],

View File

@ -37,13 +37,8 @@ public class Breakpoint {
return location; return location;
} }
public void destroy() { public synchronized void destroy() {
for (JavaScriptBreakpoint jsBreakpoint : jsBreakpoints) { debugger.destroyBreakpoint(this);
jsBreakpoint.destroy();
debugger.breakpointMap.remove(jsBreakpoint);
}
debugger.breakpoints.remove(this);
jsBreakpoints.clear();
debugger = null; debugger = null;
} }
@ -51,7 +46,7 @@ public class Breakpoint {
return valid; return valid;
} }
public boolean isDestroyed() { public synchronized boolean isDestroyed() {
return debugger == null; return debugger == null;
} }

View File

@ -61,8 +61,9 @@ public class DebugInformation {
int end = description.generatedLocationStart[line + 1]; int end = description.generatedLocationStart[line + 1];
GeneratedLocation[] resultArray = new GeneratedLocation[(end - start) / 2]; GeneratedLocation[] resultArray = new GeneratedLocation[(end - start) / 2];
for (int i = 0; i < resultArray.length; ++i) { for (int i = 0; i < resultArray.length; ++i) {
resultArray[i] = new GeneratedLocation(description.generatedLocationData[i * 2], int genLine = description.generatedLocationData[start++];
description.generatedLocationData[i * 2 + 1]); int genColumn = description.generatedLocationData[start++];
resultArray[i] = new GeneratedLocation(genLine, genColumn);
} }
return Arrays.asList(resultArray); return Arrays.asList(resultArray);
} }

View File

@ -79,7 +79,7 @@ public class Debugger {
continueToLocation(location.getFileName(), location.getLine()); continueToLocation(location.getFileName(), location.getLine());
} }
public void continueToLocation(String fileName, int line) { public synchronized void continueToLocation(String fileName, int line) {
if (!javaScriptDebugger.isSuspended()) { if (!javaScriptDebugger.isSuspended()) {
return; return;
} }
@ -105,7 +105,7 @@ public class Debugger {
return createBreakpoint(new SourceLocation(file, line)); return createBreakpoint(new SourceLocation(file, line));
} }
public Breakpoint createBreakpoint(SourceLocation location) { public synchronized Breakpoint createBreakpoint(SourceLocation location) {
Breakpoint breakpoint = new Breakpoint(this, location); Breakpoint breakpoint = new Breakpoint(this, location);
breakpoints.add(breakpoint); breakpoints.add(breakpoint);
updateInternalBreakpoints(breakpoint); updateInternalBreakpoints(breakpoint);
@ -113,8 +113,8 @@ public class Debugger {
return breakpoint; return breakpoint;
} }
public Set<Breakpoint> getBreakpoints() { public synchronized Set<Breakpoint> getBreakpoints() {
return Collections.unmodifiableSet(breakpoints); return new HashSet<>(breakpoints);
} }
void updateInternalBreakpoints(Breakpoint breakpoint) { void updateInternalBreakpoints(Breakpoint breakpoint) {
@ -122,6 +122,7 @@ public class Debugger {
breakpointMap.remove(jsBreakpoint); breakpointMap.remove(jsBreakpoint);
jsBreakpoint.destroy(); jsBreakpoint.destroy();
} }
breakpoint.jsBreakpoints.clear();
SourceLocation location = breakpoint.getLocation(); SourceLocation location = breakpoint.getLocation();
for (DebugInformation debugInformation : debugInformationBySource(location.getFileName())) { for (DebugInformation debugInformation : debugInformationBySource(location.getFileName())) {
Collection<GeneratedLocation> locations = debugInformation.getGeneratedLocations(location); Collection<GeneratedLocation> locations = debugInformation.getGeneratedLocations(location);
@ -130,6 +131,7 @@ public class Debugger {
genLocation.getLine(), genLocation.getColumn()); genLocation.getLine(), genLocation.getColumn());
JavaScriptBreakpoint jsBreakpoint = javaScriptDebugger.createBreakpoint(jsLocation); JavaScriptBreakpoint jsBreakpoint = javaScriptDebugger.createBreakpoint(jsLocation);
breakpoint.jsBreakpoints.add(jsBreakpoint); breakpoint.jsBreakpoints.add(jsBreakpoint);
breakpointMap.put(jsBreakpoint, breakpoint);
} }
} }
} }
@ -151,8 +153,8 @@ public class Debugger {
} }
} }
public CallFrame[] getCallStack() { public synchronized CallFrame[] getCallStack() {
if (isSuspended()) { if (!isSuspended()) {
return null; return null;
} }
if (callStack == null) { if (callStack == null) {
@ -182,7 +184,7 @@ public class Debugger {
return callStack.clone(); return callStack.clone();
} }
private void addScript(String name) { private synchronized void addScript(String name) {
if (debugInformationMap.containsKey(name)) { if (debugInformationMap.containsKey(name)) {
return; return;
} }
@ -210,9 +212,16 @@ public class Debugger {
return javaScriptDebugger.isAttached(); return javaScriptDebugger.isAttached();
} }
private JavaScriptDebuggerListener javaScriptListener = new JavaScriptDebuggerListener() { synchronized void destroyBreakpoint(Breakpoint breakpoint) {
@Override for (JavaScriptBreakpoint jsBreakpoint : breakpoint.jsBreakpoints) {
public void resumed() { jsBreakpoint.destroy();
breakpointMap.remove(jsBreakpoint);
}
breakpoint.jsBreakpoints.clear();
breakpoints.remove(this);
}
private synchronized void fireResumed() {
for (JavaScriptBreakpoint jsBreakpoint : temporaryJsBreakpoints) { for (JavaScriptBreakpoint jsBreakpoint : temporaryJsBreakpoints) {
jsBreakpoint.destroy(); jsBreakpoint.destroy();
} }
@ -222,8 +231,41 @@ public class Debugger {
} }
} }
private synchronized void fireAttached() {
for (Breakpoint breakpoint : breakpoints) {
updateInternalBreakpoints(breakpoint);
updateBreakpointStatus(breakpoint, false);
}
for (DebuggerListener listener : listeners) {
listener.attached();
}
}
private synchronized void fireDetached() {
for (Breakpoint breakpoint : breakpoints) {
updateBreakpointStatus(breakpoint, false);
}
for (DebuggerListener listener : listeners) {
listener.detached();
}
}
private synchronized void fireBreakpointChanged(JavaScriptBreakpoint jsBreakpoint) {
Breakpoint breakpoint = breakpointMap.get(jsBreakpoint);
if (breakpoint != null) {
updateBreakpointStatus(breakpoint, true);
}
}
private JavaScriptDebuggerListener javaScriptListener = new JavaScriptDebuggerListener() {
@Override
public void resumed() {
fireResumed();
}
@Override @Override
public void paused() { public void paused() {
callStack = null;
for (DebuggerListener listener : listeners) { for (DebuggerListener listener : listeners) {
listener.paused(); listener.paused();
} }
@ -236,32 +278,17 @@ public class Debugger {
@Override @Override
public void attached() { public void attached() {
for (Breakpoint breakpoint : breakpoints) { fireAttached();
updateInternalBreakpoints(breakpoint);
updateBreakpointStatus(breakpoint, false);
}
for (DebuggerListener listener : listeners) {
listener.attached();
}
} }
@Override @Override
public void detached() { public void detached() {
for (Breakpoint breakpoint : breakpoints) { fireDetached();
updateBreakpointStatus(breakpoint, true);
}
for (DebuggerListener listener : listeners) {
listener.detached();
}
} }
@Override @Override
public void breakpointChanged(JavaScriptBreakpoint jsBreakpoint) { public void breakpointChanged(JavaScriptBreakpoint jsBreakpoint) {
Breakpoint breakpoint = breakpointMap.get(jsBreakpoint); fireBreakpointChanged(jsBreakpoint);
if (breakpoint != null) {
updateInternalBreakpoints(breakpoint);
updateBreakpointStatus(breakpoint, false);
}
} }
}; };
} }