Adds support of object's fields in debugger watches

This commit is contained in:
konsoletyper 2014-08-06 23:03:50 +04:00
parent 8d0432dd5e
commit 461528b51f
21 changed files with 400 additions and 73 deletions

View File

@ -63,11 +63,14 @@ public class ChromeRDPDebugger implements JavaScriptDebugger, ChromeRDPExchangeC
private ChromeRDPExchangeListener exchangeListener = new ChromeRDPExchangeListener() {
@Override public void received(String messageText) throws IOException {
JsonNode jsonMessage = mapper.readTree(messageText);
if (jsonMessage.has("result")) {
if (jsonMessage.has("id")) {
Response response = mapper.reader(Response.class).readValue(jsonMessage);
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()));
@ -290,6 +293,40 @@ public class ChromeRDPDebugger implements JavaScriptDebugger, ChromeRDPExchangeC
}
}
String getClassName(String objectId) {
if (exchange == null) {
return null;
}
Message message = new Message();
message.setId(messageIdGenerator.incrementAndGet());
message.setMethod("Runtime.callFunctionOn");
CallFunctionCommand params = new CallFunctionCommand();
CallArgumentDTO arg = new CallArgumentDTO();
arg.setObjectId(objectId);
params.setObjectId(objectId);
params.setArguments(new CallArgumentDTO[] { arg });
params.setFunctionDeclaration("$dbg_class");
message.setParams(mapper.valueToTree(params));
final BlockingQueue<String> sync = new LinkedTransferQueue<>();
responseHandlers.put(message.getId(), new ResponseHandler() {
@Override public void received(JsonNode node) throws IOException {
if (node == null) {
sync.add("");
} else {
CallFunctionResponse response = mapper.reader(CallFunctionResponse.class).readValue(node);
sync.add(response.getResult().getValue().getTextValue());
}
}
});
sendMessage(message);
try {
String result = sync.take();
return result.isEmpty() ? null : result;
} catch (InterruptedException e) {
return null;
}
}
private List<RDPLocalVariable> parseProperties(PropertyDescriptorDTO[] properties) {
List<RDPLocalVariable> variables = new ArrayList<>();
if (properties != null) {
@ -298,14 +335,16 @@ public class ChromeRDPDebugger implements JavaScriptDebugger, ChromeRDPExchangeC
RDPValue value;
switch (remoteValue.getType()) {
case "undefined":
value = new RDPValue("undefined");
value = new RDPValue(this, "undefined", "undefined", null);
break;
case "object":
case "function":
value = new RDPValue(remoteValue.getObjectId());
value = new RDPValue(this, remoteValue.getDescription(), remoteValue.getType(),
remoteValue.getObjectId());
break;
default:
value = new RDPValue(remoteValue.getValue().asText());
value = new RDPValue(this, remoteValue.getValue().asText(), remoteValue.getType(),
remoteValue.getObjectId());
break;
}

View File

@ -4,13 +4,14 @@ import java.util.AbstractMap;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;
/**
*
* @author Alexey Andreev <konsoletyper@gmail.com>
*/
public class RDPScope extends AbstractMap<String, RDPLocalVariable> {
private volatile Map<String, RDPLocalVariable> backingMap;
private AtomicReference<Map<String, RDPLocalVariable>> backingMap = new AtomicReference<>();
private ChromeRDPDebugger debugger;
private String id;
@ -22,32 +23,31 @@ public class RDPScope extends AbstractMap<String, RDPLocalVariable> {
@Override
public Set<Entry<String, RDPLocalVariable>> entrySet() {
initBackingMap();
return backingMap.entrySet();
return backingMap.get().entrySet();
}
@Override
public int size() {
initBackingMap();
return backingMap.size();
return backingMap.get().size();
}
@Override
public RDPLocalVariable get(Object key) {
initBackingMap();
return backingMap.get(key);
return backingMap.get().get(key);
}
private void initBackingMap() {
if (backingMap != null) {
if (backingMap.get() != null) {
return;
}
if (id == null) {
backingMap = new HashMap<>();
}
Map<String, RDPLocalVariable> newBackingMap = new HashMap<>();
for (RDPLocalVariable variable : debugger.getScope(id)) {
newBackingMap.put(variable.getName(), variable);
if (id != null) {
for (RDPLocalVariable variable : debugger.getScope(id)) {
newBackingMap.put(variable.getName(), variable);
}
}
backingMap = newBackingMap;
backingMap.compareAndSet(null, newBackingMap);
}
}

View File

@ -11,9 +11,18 @@ import org.teavm.debugging.JavaScriptVariable;
*/
public class RDPValue implements JavaScriptValue {
private String representation;
private String typeName;
private ChromeRDPDebugger debugger;
private String objectId;
private Map<String, ? extends JavaScriptVariable> properties;
public RDPValue(String representation) {
public RDPValue(ChromeRDPDebugger debugger, String representation, String typeName, String objectId) {
this.representation = representation;
this.typeName = typeName;
this.debugger = debugger;
this.objectId = objectId;
properties = objectId != null ? new RDPScope(debugger, objectId) :
Collections.<String, RDPLocalVariable>emptyMap();
}
@Override
@ -21,8 +30,19 @@ public class RDPValue implements JavaScriptValue {
return representation;
}
@Override
public String getClassName() {
if (objectId != null) {
String className = debugger.getClassName(objectId);
return className != null ? className : "object";
} else {
return typeName;
}
}
@SuppressWarnings("unchecked")
@Override
public Map<String, JavaScriptVariable> getProperties() {
return Collections.emptyMap();
return (Map<String, JavaScriptVariable>)properties;
}
}

View File

@ -0,0 +1,30 @@
package org.teavm.chromerdp.data;
import org.codehaus.jackson.JsonNode;
import org.codehaus.jackson.annotate.JsonIgnoreProperties;
/**
*
* @author Alexey Andreev <konsoletyper@gmail.com>
*/
@JsonIgnoreProperties(ignoreUnknown = true)
public class CallArgumentDTO {
private String objectId;
private JsonNode value;
public String getObjectId() {
return objectId;
}
public void setObjectId(String objectId) {
this.objectId = objectId;
}
public JsonNode getValue() {
return value;
}
public void setValue(JsonNode value) {
this.value = value;
}
}

View File

@ -0,0 +1,48 @@
package org.teavm.chromerdp.messages;
import org.codehaus.jackson.annotate.JsonIgnoreProperties;
import org.teavm.chromerdp.data.CallArgumentDTO;
/**
*
* @author Alexey Andreev <konsoletyper@gmail.com>
*/
@JsonIgnoreProperties(ignoreUnknown = true)
public class CallFunctionCommand {
private String objectId;
private String functionDeclaration;
private CallArgumentDTO[] arguments;
private boolean returnByValue;
public String getObjectId() {
return objectId;
}
public void setObjectId(String objectId) {
this.objectId = objectId;
}
public String getFunctionDeclaration() {
return functionDeclaration;
}
public void setFunctionDeclaration(String functionDeclaration) {
this.functionDeclaration = functionDeclaration;
}
public CallArgumentDTO[] getArguments() {
return arguments;
}
public void setArguments(CallArgumentDTO[] arguments) {
this.arguments = arguments;
}
public boolean isReturnByValue() {
return returnByValue;
}
public void setReturnByValue(boolean returnByValue) {
this.returnByValue = returnByValue;
}
}

View File

@ -0,0 +1,30 @@
package org.teavm.chromerdp.messages;
import org.codehaus.jackson.annotate.JsonIgnoreProperties;
import org.teavm.chromerdp.data.RemoteObjectDTO;
/**
*
* @author Alexey Andreev <konsoletyper@gmail.com>
*/
@JsonIgnoreProperties(ignoreUnknown = true)
public class CallFunctionResponse {
private RemoteObjectDTO result;
private boolean wasThrown;
public RemoteObjectDTO getResult() {
return result;
}
public void setResult(RemoteObjectDTO result) {
this.result = result;
}
public boolean isWasThrown() {
return wasThrown;
}
public void setWasThrown(boolean wasThrown) {
this.wasThrown = wasThrown;
}
}

View File

@ -38,7 +38,8 @@ DebuggerAgent.prototype.connectToServer = function() {
DebuggerAgent.prototype.receiveMessage = function(message) {
chrome.debugger.sendCommand(this.debuggee, message.method, message.params, function(response) {
if (message.id) {
var responseToServer = { id : message.id, result : response };
var responseToServer = { id : message.id, result : response,
error : response ? undefined : chrome.runtime.lastError };
this.connection.send(JSON.stringify(responseToServer));
}
}.bind(this));

View File

@ -33,6 +33,8 @@ public class DebugInformation {
Map<String, Integer> fileNameMap;
String[] classNames;
Map<String, Integer> classNameMap;
String[] fields;
Map<String, Integer> fieldMap;
String[] methods;
Map<String, Integer> methodMap;
String[] variableNames;
@ -43,6 +45,7 @@ public class DebugInformation {
Mapping methodMapping;
Mapping lineMapping;
Mapping[] variableMappings;
List<Map<Integer, Integer>> classesMetadata;
public String[] getCoveredSourceFiles() {
return fileNames.clone();
@ -118,6 +121,19 @@ public class DebugInformation {
return componentByKey(mapping, variableNames, location);
}
public String getFieldMeaning(String className, String jsName) {
Integer classIndex = classNameMap.get(className);
if (classIndex == null) {
return null;
}
Integer jsIndex = fieldMap.get(jsName);
if (jsIndex == null) {
return null;
}
Integer fieldIndex = classesMetadata.get(classIndex).get(jsIndex);
return fieldIndex != null ? fields[fieldIndex] : null;
}
private <T> T componentByKey(Mapping mapping, T[] values, GeneratedLocation location) {
int keyIndex = indexByKey(mapping, location);
int valueIndex = keyIndex >= 0 ? mapping.values[keyIndex] : -1;
@ -142,6 +158,7 @@ public class DebugInformation {
void rebuildMaps() {
fileNameMap = mapArray(fileNames);
classNameMap = mapArray(classNames);
fieldMap = mapArray(fields);
methodMap = mapArray(methods);
variableNameMap = mapArray(variableNames);
}

View File

@ -29,6 +29,7 @@ public class DebugInformationBuilder implements DebugInformationEmitter {
private DebugInformation debugInformation;
private MappedList files = new MappedList();
private MappedList classes = new MappedList();
private MappedList fields = new MappedList();
private MappedList methods = new MappedList();
private MappedList variableNames = new MappedList();
private Mapping fileMapping = new Mapping();
@ -39,6 +40,8 @@ public class DebugInformationBuilder implements DebugInformationEmitter {
private MethodDescriptor currentMethod;
private String currentClass;
private String currentFileName;
private int currentClassMetadata = -1;
private List<ClassMetadata> classesMetadata = new ArrayList<>();
private int currentLine;
public LocationProvider getLocationProvider() {
@ -96,18 +99,32 @@ public class DebugInformationBuilder implements DebugInformationEmitter {
mapping.add(locationProvider, sourceIndex);
}
@Override
public void addClass(String className) {
int classIndex = classes.index(className);
while (classIndex >= classesMetadata.size()) {
classesMetadata.add(new ClassMetadata());
}
currentClassMetadata = classIndex;
}
@Override
public void addField(String fieldName, String jsName) {
ClassMetadata metadata = classesMetadata.get(currentClassMetadata);
int fieldIndex = fields.index(fieldName);
int jsIndex = fields.index(jsName);
metadata.fieldMap.put(jsIndex, fieldIndex);
}
public DebugInformation getDebugInformation() {
if (debugInformation == null) {
debugInformation = new DebugInformation();
debugInformation.fileNames = files.getItems();
debugInformation.fileNameMap = files.getIndexes();
debugInformation.fileNames = files.getItems();;
debugInformation.classNames = classes.getItems();
debugInformation.classNameMap = classes.getIndexes();
debugInformation.fields = fields.getItems();
debugInformation.methods = methods.getItems();
debugInformation.methodMap = methods.getIndexes();
debugInformation.variableNames = variableNames.getItems();
debugInformation.variableNameMap = variableNames.getIndexes();
debugInformation.fileMapping = fileMapping.build();
debugInformation.lineMapping = lineMapping.build();
@ -119,7 +136,19 @@ public class DebugInformationBuilder implements DebugInformationEmitter {
debugInformation.variableMappings[var] = mapping.build();
}
List<Map<Integer, Integer>> builtMetadata = new ArrayList<>(classes.list.size());
for (int i = 0; i < classes.list.size(); ++i) {
if (i >= classesMetadata.size()) {
builtMetadata.add(new HashMap<Integer, Integer>());
} else {
Map<Integer, Integer> map = new HashMap<>(classesMetadata.get(i).fieldMap);
builtMetadata.add(map);
}
}
debugInformation.classesMetadata = builtMetadata;
debugInformation.rebuildFileDescriptions();
debugInformation.rebuildMaps();
}
return debugInformation;
}
@ -172,4 +201,8 @@ public class DebugInformationBuilder implements DebugInformationEmitter {
return new HashMap<>(map);
}
}
static class ClassMetadata {
Map<Integer, Integer> fieldMap = new HashMap<>();
}
}

View File

@ -32,4 +32,8 @@ public interface DebugInformationEmitter {
void emitClass(String className);
void emitVariable(String sourceName, String generatedName);
void addClass(String className);
void addField(String fieldName, String jsName);
}

View File

@ -18,6 +18,10 @@ package org.teavm.debugging;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
*
@ -35,6 +39,7 @@ class DebugInformationReader {
DebugInformation debugInfo = new DebugInformation();
debugInfo.fileNames = readStrings();
debugInfo.classNames = readStrings();
debugInfo.fields = readStrings();
debugInfo.methods = readStrings();
debugInfo.variableNames = readStrings();
debugInfo.fileMapping = readMapping();
@ -42,6 +47,7 @@ class DebugInformationReader {
debugInfo.classMapping = readMapping();
debugInfo.methodMapping = readMapping();
debugInfo.variableMappings = readVariableMappings(debugInfo.variableNames.length);
debugInfo.classesMetadata = readClassesMetadata(debugInfo.classNames.length);
debugInfo.rebuildFileDescriptions();
debugInfo.rebuildMaps();
return debugInfo;
@ -58,6 +64,22 @@ class DebugInformationReader {
return mappings;
}
private List<Map<Integer, Integer>> readClassesMetadata(int count) throws IOException {
List<Map<Integer, Integer>> classes = new ArrayList<>(count);
for (int i = 0; i < count; ++i) {
Map<Integer, Integer> cls = new HashMap<>();
classes.add(cls);
int entryCount = readUnsignedNumber();
resetRelativeNumber();
for (int j = 0; j < entryCount; ++j) {
int key = readRelativeNumber();
int value = readUnsignedNumber();
cls.put(key, value);
}
}
return classes;
}
private int processSign(int number) {
boolean negative = (number & 1) != 0;
number >>>= 1;

View File

@ -17,6 +17,10 @@ package org.teavm.debugging;
import java.io.DataOutput;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
/**
*
@ -33,6 +37,7 @@ class DebugInformationWriter {
public void write(DebugInformation debugInfo) throws IOException {
writeStringArray(debugInfo.fileNames);
writeStringArray(debugInfo.classNames);
writeStringArray(debugInfo.fields);
writeStringArray(debugInfo.methods);
writeStringArray(debugInfo.variableNames);
@ -41,6 +46,7 @@ class DebugInformationWriter {
writeMapping(debugInfo.classMapping);
writeMapping(debugInfo.methodMapping);
writeVariableMappings(debugInfo);
writeClassMetadata(debugInfo.classesMetadata);
}
private void writeVariableMappings(DebugInformation debugInfo) throws IOException {
@ -57,6 +63,20 @@ class DebugInformationWriter {
}
}
private void writeClassMetadata(List<Map<Integer, Integer>> classes) throws IOException {
for (int i = 0; i < classes.size(); ++i) {
Map<Integer, Integer> cls = classes.get(i);
writeUnsignedNumber(cls.size());
List<Integer> keys = new ArrayList<>(cls.keySet());
Collections.sort(keys);
resetRelativeNumber();
for (int key : keys) {
writeRelativeNumber(key);
writeUnsignedNumber(cls.get(key));
}
}
}
private int nonNullVariableMappings(DebugInformation debugInfo) {
int count = 0;
for (int i = 0; i < debugInfo.variableMappings.length; ++i) {

View File

@ -288,6 +288,16 @@ public class Debugger {
return debugInfo.getVariableMeaningAt(location.getLine(), location.getColumn(), variable);
}
public String mapField(String className, String jsField) {
for (DebugInformation debugInfo : debugInformationMap.values()) {
String meaning = debugInfo.getFieldMeaning(className, jsField);
if (meaning != null) {
return meaning;
}
}
return null;
}
private JavaScriptDebuggerListener javaScriptListener = new JavaScriptDebuggerListener() {
@Override
public void resumed() {

View File

@ -43,4 +43,12 @@ public class DummyDebugInformationEmitter implements DebugInformationEmitter {
@Override
public void setLocationProvider(LocationProvider locationProvider) {
}
@Override
public void addClass(String className) {
}
@Override
public void addField(String fieldName, String jsName) {
}
}

View File

@ -24,5 +24,7 @@ import java.util.Map;
public interface JavaScriptValue {
String getRepresentation();
String getClassName();
Map<String, JavaScriptVariable> getProperties();
}

View File

@ -0,0 +1,59 @@
package org.teavm.debugging;
import java.util.AbstractMap;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;
/**
*
* @author Alexey Andreev <konsoletyper@gmail.com>
*/
class PropertyMap extends AbstractMap<String, Variable>{
private String className;
private AtomicReference<Map<String, Variable>> backingMap = new AtomicReference<>();
private Map<String, JavaScriptVariable> jsVariables;
private Debugger debugger;
public PropertyMap(String className, Map<String, JavaScriptVariable> jsVariables, Debugger debugger) {
this.className = className;
this.jsVariables = jsVariables;
this.debugger = debugger;
}
@Override
public int size() {
updateBackingMap();
return backingMap.get().size();
}
@Override
public Variable get(Object key) {
updateBackingMap();
return backingMap.get().get(key);
}
@Override
public Set<Entry<String, Variable>> entrySet() {
updateBackingMap();
return backingMap.get().entrySet();
}
private void updateBackingMap() {
if (backingMap.get() != null) {
return;
}
Map<String, Variable> vars = new HashMap<>();
for (Map.Entry<String, JavaScriptVariable> entry : jsVariables.entrySet()) {
JavaScriptVariable jsVar = entry.getValue();
String name = debugger.mapField(className, entry.getKey());
if (name == null) {
continue;
}
Value value = new Value(debugger, jsVar.getValue());
vars.put(entry.getKey(), new Variable(name, value));
}
backingMap.compareAndSet(null, vars);
}
}

View File

@ -1,41 +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.debugging;
import java.util.Collections;
import java.util.Map;
/**
*
* @author Alexey Andreev
*/
class SimpleValue extends Value {
private String representation;
public SimpleValue(String representation) {
this.representation = representation;
}
@Override
public String getRepresentation() {
return representation;
}
@Override
public Map<String, Variable> getProperties() {
return Collections.emptyMap();
}
}

View File

@ -16,13 +16,34 @@
package org.teavm.debugging;
import java.util.Map;
import java.util.concurrent.atomic.AtomicReference;
/**
*
* @author Alexey Andreev <konsoletyper@gmail.com>
*/
public abstract class Value {
public abstract String getRepresentation();
public class Value {
private Debugger debugger;
private JavaScriptValue jsValue;
private AtomicReference<PropertyMap> properties = new AtomicReference<>();
public abstract Map<String, Variable> getProperties();
Value(Debugger debugger, JavaScriptValue jsValue) {
this.debugger = debugger;
this.jsValue = jsValue;
}
public String getRepresentation() {
return jsValue.getRepresentation();
}
public String getType() {
return jsValue.getClassName();
}
public Map<String, Variable> getProperties() {
if (properties.get() == null) {
properties.compareAndSet(null, new PropertyMap(jsValue.getClassName(), jsValue.getProperties(), debugger));
}
return properties.get();
}
}

View File

@ -66,7 +66,7 @@ class VariableMap extends AbstractMap<String, Variable> {
if (name == null) {
continue;
}
SimpleValue value = new SimpleValue(jsVar.getValue().getRepresentation());
Value value = new Value(debugger, jsVar.getValue());
vars.put(entry.getKey(), new Variable(name, value));
}
backingMap.compareAndSet(null, vars);

View File

@ -243,6 +243,7 @@ public class Renderer implements ExprVisitor, StatementVisitor, RenderingContext
public void render(ClassNode cls) throws RenderingException {
debugEmitter.emitClass(cls.getName());
debugEmitter.addClass(cls.getName());
try {
writer.append("function ").appendClass(cls.getName()).append("()").ws().append("{")
.indent().softNewLine();
@ -254,8 +255,10 @@ public class Renderer implements ExprVisitor, StatementVisitor, RenderingContext
if (value == null) {
value = getDefaultValue(field.getType());
}
writer.append("this.").appendField(new FieldReference(cls.getName(), field.getName())).ws()
.append("=").ws().append(constantToString(value)).append(";").softNewLine();
FieldReference fieldRef = new FieldReference(cls.getName(), field.getName());
writer.append("this.").appendField(fieldRef).ws().append("=").ws().append(constantToString(value))
.append(";").softNewLine();
debugEmitter.addField(field.getName(), naming.getNameFor(fieldRef));
}
writer.outdent().append("}").newLine();
@ -267,8 +270,8 @@ public class Renderer implements ExprVisitor, StatementVisitor, RenderingContext
if (value == null) {
value = getDefaultValue(field.getType());
}
writer.appendClass(cls.getName()).append('.')
.appendField(new FieldReference(cls.getName(), field.getName())).ws().append("=").ws()
FieldReference fieldRef = new FieldReference(cls.getName(), field.getName());
writer.appendClass(cls.getName()).append('.').appendField(fieldRef).ws().append("=").ws()
.append(constantToString(value)).append(";").softNewLine();
}

View File

@ -398,7 +398,8 @@ function $dbg_class(obj) {
if (obj instanceof Long) {
return "long";
}
return obj.constructor.$meta.name;
var meta = obj.constructor.$meta;
return meta ? meta.name : "";
}
Long = function(lo, hi) {