Fix various issues in debugger

This commit is contained in:
Alexey Andreev 2017-07-02 16:25:11 +03:00
parent 46ebc2d694
commit db97b7f732
16 changed files with 406 additions and 199 deletions

View File

@ -29,7 +29,6 @@
</indentOptions> </indentOptions>
</codeStyleSettings> </codeStyleSettings>
<codeStyleSettings language="JAVA"> <codeStyleSettings language="JAVA">
<option name="KEEP_LINE_BREAKS" value="false" />
<option name="KEEP_CONTROL_STATEMENT_IN_ONE_LINE" value="false" /> <option name="KEEP_CONTROL_STATEMENT_IN_ONE_LINE" value="false" />
<option name="KEEP_BLANK_LINES_IN_DECLARATIONS" value="1" /> <option name="KEEP_BLANK_LINES_IN_DECLARATIONS" value="1" />
<option name="KEEP_BLANK_LINES_IN_CODE" value="1" /> <option name="KEEP_BLANK_LINES_IN_CODE" value="1" />

View File

@ -40,7 +40,7 @@ script:
- pushd tests/src/test/js - pushd tests/src/test/js
- export DISPLAY=:99.0 - export DISPLAY=:99.0
- sh -e /etc/init.d/xvfb start - sh -e /etc/init.d/xvfb start
- sleep 3 - sleep 10
- firefox index.html & - firefox index.html &
- FIREFOX_PID=$! - FIREFOX_PID=$!
- node start.js $BASE_PATH/tests/target/js-tests - node start.js $BASE_PATH/tests/target/js-tests

View File

@ -91,14 +91,16 @@ public class Decompiler {
private List<TryCatchBookmark> tryCatchBookmarks = new ArrayList<>(); private List<TryCatchBookmark> tryCatchBookmarks = new ArrayList<>();
private Deque<Block> stack; private Deque<Block> stack;
private Program program; private Program program;
private boolean friendlyToDebugger;
public Decompiler(ClassHolderSource classSource, ClassLoader classLoader, Set<MethodReference> asyncMethods, public Decompiler(ClassHolderSource classSource, ClassLoader classLoader, Set<MethodReference> asyncMethods,
Set<MethodReference> asyncFamilyMethods) { Set<MethodReference> asyncFamilyMethods, boolean friendlyToDebugger) {
this.classSource = classSource; this.classSource = classSource;
this.classLoader = classLoader; this.classLoader = classLoader;
this.asyncMethods = asyncMethods; this.asyncMethods = asyncMethods;
splitMethods.addAll(asyncMethods); splitMethods.addAll(asyncMethods);
splitMethods.addAll(asyncFamilyMethods); splitMethods.addAll(asyncFamilyMethods);
this.friendlyToDebugger = friendlyToDebugger;
} }
public MethodNodeCache getRegularMethodCache() { public MethodNodeCache getRegularMethodCache() {
@ -275,7 +277,7 @@ public class Decompiler {
} }
Optimizer optimizer = new Optimizer(); Optimizer optimizer = new Optimizer();
optimizer.optimize(methodNode, method.getProgram()); optimizer.optimize(methodNode, method.getProgram(), friendlyToDebugger);
methodNode.getModifiers().addAll(method.getModifiers()); methodNode.getModifiers().addAll(method.getModifiers());
return methodNode; return methodNode;
@ -339,7 +341,7 @@ public class Decompiler {
} }
Optimizer optimizer = new Optimizer(); Optimizer optimizer = new Optimizer();
optimizer.optimize(node, splitter); optimizer.optimize(node, splitter, friendlyToDebugger);
node.getModifiers().addAll(method.getModifiers()); node.getModifiers().addAll(method.getModifiers());
return node; return node;

View File

@ -18,7 +18,9 @@ package org.teavm.ast.optimization;
import java.util.BitSet; import java.util.BitSet;
import org.teavm.ast.AsyncMethodNode; import org.teavm.ast.AsyncMethodNode;
import org.teavm.ast.AsyncMethodPart; import org.teavm.ast.AsyncMethodPart;
import org.teavm.ast.MethodNode;
import org.teavm.ast.RegularMethodNode; import org.teavm.ast.RegularMethodNode;
import org.teavm.ast.VariableNode;
import org.teavm.common.Graph; import org.teavm.common.Graph;
import org.teavm.model.BasicBlock; import org.teavm.model.BasicBlock;
import org.teavm.model.Instruction; import org.teavm.model.Instruction;
@ -31,13 +33,14 @@ import org.teavm.model.util.ProgramUtils;
import org.teavm.model.util.UsageExtractor; import org.teavm.model.util.UsageExtractor;
public class Optimizer { public class Optimizer {
public void optimize(RegularMethodNode method, Program program) { public void optimize(RegularMethodNode method, Program program, boolean friendlyToDebugger) {
ReadWriteStatsBuilder stats = new ReadWriteStatsBuilder(method.getVariables().size()); ReadWriteStatsBuilder stats = new ReadWriteStatsBuilder(method.getVariables().size());
stats.analyze(program); stats.analyze(program);
boolean[] preservedVars = new boolean[stats.writes.length]; boolean[] preservedVars = new boolean[stats.writes.length];
BreakEliminator breakEliminator = new BreakEliminator(); BreakEliminator breakEliminator = new BreakEliminator();
breakEliminator.eliminate(method.getBody()); breakEliminator.eliminate(method.getBody());
OptimizingVisitor optimizer = new OptimizingVisitor(preservedVars, stats.writes, stats.reads); OptimizingVisitor optimizer = new OptimizingVisitor(preservedVars, stats.writes, stats.reads,
friendlyToDebugger);
method.getBody().acceptVisitor(optimizer); method.getBody().acceptVisitor(optimizer);
method.setBody(optimizer.resultStmt); method.setBody(optimizer.resultStmt);
int paramCount = method.getReference().parameterCount(); int paramCount = method.getReference().parameterCount();
@ -55,7 +58,7 @@ public class Optimizer {
} }
} }
public void optimize(AsyncMethodNode method, AsyncProgramSplitter splitter) { public void optimize(AsyncMethodNode method, AsyncProgramSplitter splitter, boolean friendlyToDebugger) {
LivenessAnalyzer liveness = new LivenessAnalyzer(); LivenessAnalyzer liveness = new LivenessAnalyzer();
liveness.analyze(splitter.getOriginalProgram()); liveness.analyze(splitter.getOriginalProgram());
@ -70,7 +73,8 @@ public class Optimizer {
BreakEliminator breakEliminator = new BreakEliminator(); BreakEliminator breakEliminator = new BreakEliminator();
breakEliminator.eliminate(part.getStatement()); breakEliminator.eliminate(part.getStatement());
findEscapingLiveVars(liveness, cfg, splitter, i, preservedVars); findEscapingLiveVars(liveness, cfg, splitter, i, preservedVars);
OptimizingVisitor optimizer = new OptimizingVisitor(preservedVars, stats.writes, stats.reads); OptimizingVisitor optimizer = new OptimizingVisitor(preservedVars, stats.writes, stats.reads,
friendlyToDebugger);
part.getStatement().acceptVisitor(optimizer); part.getStatement().acceptVisitor(optimizer);
part.setStatement(optimizer.resultStmt); part.setStatement(optimizer.resultStmt);
} }
@ -93,6 +97,14 @@ public class Optimizer {
} }
} }
private void preserveDebuggableVars(boolean[] variablesToPreserve, MethodNode methodNode) {
for (VariableNode varNode : methodNode.getVariables()) {
if (varNode.getName() != null) {
variablesToPreserve[varNode.getIndex()] = true;
}
}
}
private void findEscapingLiveVars(LivenessAnalyzer liveness, Graph cfg, AsyncProgramSplitter splitter, private void findEscapingLiveVars(LivenessAnalyzer liveness, Graph cfg, AsyncProgramSplitter splitter,
int partIndex, boolean[] output) { int partIndex, boolean[] output) {
Program originalProgram = splitter.getOriginalProgram(); Program originalProgram = splitter.getOriginalProgram();

View File

@ -15,8 +15,11 @@
*/ */
package org.teavm.ast.optimization; package org.teavm.ast.optimization;
import java.util.ArrayDeque;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Deque;
import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -58,6 +61,7 @@ import org.teavm.ast.UnaryOperation;
import org.teavm.ast.UnwrapArrayExpr; import org.teavm.ast.UnwrapArrayExpr;
import org.teavm.ast.VariableExpr; import org.teavm.ast.VariableExpr;
import org.teavm.ast.WhileStatement; import org.teavm.ast.WhileStatement;
import org.teavm.model.TextLocation;
class OptimizingVisitor implements StatementVisitor, ExprVisitor { class OptimizingVisitor implements StatementVisitor, ExprVisitor {
private Expr resultExpr; private Expr resultExpr;
@ -66,11 +70,17 @@ class OptimizingVisitor implements StatementVisitor, ExprVisitor {
private final int[] writeFrequencies; private final int[] writeFrequencies;
private final int[] readFrequencies; private final int[] readFrequencies;
private List<Statement> resultSequence; private List<Statement> resultSequence;
private boolean friendlyToDebugger;
private TextLocation currentLocation;
private Deque<TextLocation> locationStack = new LinkedList<>();
private Deque<TextLocation> notNullLocationStack = new ArrayDeque<>();
OptimizingVisitor(boolean[] preservedVars, int[] writeFrequencies, int[] readFrequencies) { OptimizingVisitor(boolean[] preservedVars, int[] writeFrequencies, int[] readFrequencies,
boolean friendlyToDebugger) {
this.preservedVars = preservedVars; this.preservedVars = preservedVars;
this.writeFrequencies = writeFrequencies; this.writeFrequencies = writeFrequencies;
this.readFrequencies = readFrequencies; this.readFrequencies = readFrequencies;
this.friendlyToDebugger = friendlyToDebugger;
} }
private static boolean isZero(Expr expr) { private static boolean isZero(Expr expr) {
@ -81,8 +91,26 @@ class OptimizingVisitor implements StatementVisitor, ExprVisitor {
return expr instanceof BinaryExpr && ((BinaryExpr) expr).getOperation() == BinaryOperation.COMPARE; return expr instanceof BinaryExpr && ((BinaryExpr) expr).getOperation() == BinaryOperation.COMPARE;
} }
private void pushLocation(TextLocation location) {
locationStack.push(location);
if (location != null) {
if (currentLocation != null) {
notNullLocationStack.push(currentLocation);
}
currentLocation = location;
}
}
private void popLocation() {
if (locationStack.pop() != null) {
currentLocation = notNullLocationStack.pollFirst();
}
}
@Override @Override
public void visit(BinaryExpr expr) { public void visit(BinaryExpr expr) {
pushLocation(expr.getLocation());
try {
switch (expr.getOperation()) { switch (expr.getOperation()) {
case AND: case AND:
case OR: case OR:
@ -134,10 +162,15 @@ class OptimizingVisitor implements StatementVisitor, ExprVisitor {
expr.setFirstOperand(a); expr.setFirstOperand(a);
expr.setSecondOperand(b); expr.setSecondOperand(b);
resultExpr = expr; resultExpr = expr;
} finally {
popLocation();
}
} }
@Override @Override
public void visit(UnaryExpr expr) { public void visit(UnaryExpr expr) {
pushLocation(expr.getLocation());
try {
expr.getOperand().acceptVisitor(this); expr.getOperand().acceptVisitor(this);
Expr operand = resultExpr; Expr operand = resultExpr;
if (expr.getOperation() == UnaryOperation.NEGATE && operand instanceof ConstantExpr) { if (expr.getOperation() == UnaryOperation.NEGATE && operand instanceof ConstantExpr) {
@ -149,6 +182,9 @@ class OptimizingVisitor implements StatementVisitor, ExprVisitor {
} }
expr.setOperand(operand); expr.setOperand(operand);
resultExpr = expr; resultExpr = expr;
} finally {
popLocation();
}
} }
private boolean tryMakePositive(ConstantExpr constantExpr) { private boolean tryMakePositive(ConstantExpr constantExpr) {
@ -177,6 +213,8 @@ class OptimizingVisitor implements StatementVisitor, ExprVisitor {
@Override @Override
public void visit(ConditionalExpr expr) { public void visit(ConditionalExpr expr) {
pushLocation(expr.getLocation());
try {
expr.getCondition().acceptVisitor(this); expr.getCondition().acceptVisitor(this);
Expr cond = resultExpr; Expr cond = resultExpr;
expr.getConsequent().acceptVisitor(this); expr.getConsequent().acceptVisitor(this);
@ -187,6 +225,9 @@ class OptimizingVisitor implements StatementVisitor, ExprVisitor {
expr.setConsequent(consequent); expr.setConsequent(consequent);
expr.setAlternative(alternative); expr.setAlternative(alternative);
resultExpr = expr; resultExpr = expr;
} finally {
popLocation();
}
} }
@Override @Override
@ -196,6 +237,8 @@ class OptimizingVisitor implements StatementVisitor, ExprVisitor {
@Override @Override
public void visit(VariableExpr expr) { public void visit(VariableExpr expr) {
pushLocation(expr.getLocation());
try {
int index = expr.getIndex(); int index = expr.getIndex();
resultExpr = expr; resultExpr = expr;
if (writeFrequencies[index] != 1) { if (writeFrequencies[index] != 1) {
@ -219,19 +262,26 @@ class OptimizingVisitor implements StatementVisitor, ExprVisitor {
return; return;
} }
VariableExpr var = (VariableExpr) assignment.getLeftValue(); VariableExpr var = (VariableExpr) assignment.getLeftValue();
if (var.getLocation() != null && assignment.getLocation() != null if (friendlyToDebugger) {
&& !assignment.getLocation().equals(var.getLocation())) { if (currentLocation != null && assignment.getLocation() != null
&& !assignment.getLocation().equals(currentLocation)) {
return; return;
} }
}
if (var.getIndex() == index) { if (var.getIndex() == index) {
resultSequence.remove(resultSequence.size() - 1); resultSequence.remove(resultSequence.size() - 1);
assignment.getRightValue().setLocation(assignment.getLocation()); assignment.getRightValue().setLocation(assignment.getLocation());
assignment.getRightValue().acceptVisitor(this); assignment.getRightValue().acceptVisitor(this);
} }
} finally {
popLocation();
}
} }
@Override @Override
public void visit(SubscriptExpr expr) { public void visit(SubscriptExpr expr) {
pushLocation(expr.getLocation());
try {
expr.getIndex().acceptVisitor(this); expr.getIndex().acceptVisitor(this);
Expr index = resultExpr; Expr index = resultExpr;
expr.getArray().acceptVisitor(this); expr.getArray().acceptVisitor(this);
@ -239,18 +289,28 @@ class OptimizingVisitor implements StatementVisitor, ExprVisitor {
expr.setArray(array); expr.setArray(array);
expr.setIndex(index); expr.setIndex(index);
resultExpr = expr; resultExpr = expr;
} finally {
popLocation();
}
} }
@Override @Override
public void visit(UnwrapArrayExpr expr) { public void visit(UnwrapArrayExpr expr) {
pushLocation(expr.getLocation());
try {
expr.getArray().acceptVisitor(this); expr.getArray().acceptVisitor(this);
Expr arrayExpr = resultExpr; Expr arrayExpr = resultExpr;
expr.setArray(arrayExpr); expr.setArray(arrayExpr);
resultExpr = expr; resultExpr = expr;
} finally {
popLocation();
}
} }
@Override @Override
public void visit(InvocationExpr expr) { public void visit(InvocationExpr expr) {
pushLocation(expr.getLocation());
try {
Expr[] args = new Expr[expr.getArguments().size()]; Expr[] args = new Expr[expr.getArguments().size()];
for (int i = expr.getArguments().size() - 1; i >= 0; --i) { for (int i = expr.getArguments().size() - 1; i >= 0; --i) {
expr.getArguments().get(i).acceptVisitor(this); expr.getArguments().get(i).acceptVisitor(this);
@ -260,6 +320,9 @@ class OptimizingVisitor implements StatementVisitor, ExprVisitor {
expr.getArguments().set(i, args[i]); expr.getArguments().set(i, args[i]);
} }
resultExpr = expr; resultExpr = expr;
} finally {
popLocation();
}
} }
private boolean tryApplyConstructor(InvocationExpr expr) { private boolean tryApplyConstructor(InvocationExpr expr) {
@ -303,12 +366,17 @@ class OptimizingVisitor implements StatementVisitor, ExprVisitor {
@Override @Override
public void visit(QualificationExpr expr) { public void visit(QualificationExpr expr) {
pushLocation(expr.getLocation());
try {
if (expr.getQualified() != null) { if (expr.getQualified() != null) {
expr.getQualified().acceptVisitor(this); expr.getQualified().acceptVisitor(this);
Expr qualified = resultExpr; Expr qualified = resultExpr;
expr.setQualified(qualified); expr.setQualified(qualified);
} }
resultExpr = expr; resultExpr = expr;
} finally {
popLocation();
}
} }
@Override @Override
@ -318,45 +386,72 @@ class OptimizingVisitor implements StatementVisitor, ExprVisitor {
@Override @Override
public void visit(NewArrayExpr expr) { public void visit(NewArrayExpr expr) {
pushLocation(expr.getLocation());
try {
expr.getLength().acceptVisitor(this); expr.getLength().acceptVisitor(this);
Expr length = resultExpr; Expr length = resultExpr;
expr.setLength(length); expr.setLength(length);
resultExpr = expr; resultExpr = expr;
} finally {
popLocation();
}
} }
@Override @Override
public void visit(NewMultiArrayExpr expr) { public void visit(NewMultiArrayExpr expr) {
pushLocation(expr.getLocation());
try {
for (int i = 0; i < expr.getDimensions().size(); ++i) { for (int i = 0; i < expr.getDimensions().size(); ++i) {
Expr dimension = expr.getDimensions().get(i); Expr dimension = expr.getDimensions().get(i);
dimension.acceptVisitor(this); dimension.acceptVisitor(this);
expr.getDimensions().set(i, resultExpr); expr.getDimensions().set(i, resultExpr);
} }
resultExpr = expr; resultExpr = expr;
} finally {
popLocation();
}
} }
@Override @Override
public void visit(InstanceOfExpr expr) { public void visit(InstanceOfExpr expr) {
pushLocation(expr.getLocation());
try {
expr.getExpr().acceptVisitor(this); expr.getExpr().acceptVisitor(this);
expr.setExpr(resultExpr); expr.setExpr(resultExpr);
resultExpr = expr; resultExpr = expr;
} finally {
popLocation();
}
} }
@Override @Override
public void visit(CastExpr expr) { public void visit(CastExpr expr) {
pushLocation(expr.getLocation());
try {
expr.getValue().acceptVisitor(this); expr.getValue().acceptVisitor(this);
expr.setValue(resultExpr); expr.setValue(resultExpr);
resultExpr = expr; resultExpr = expr;
} finally {
popLocation();
}
} }
@Override @Override
public void visit(PrimitiveCastExpr expr) { public void visit(PrimitiveCastExpr expr) {
pushLocation(expr.getLocation());
try {
expr.getValue().acceptVisitor(this); expr.getValue().acceptVisitor(this);
expr.setValue(resultExpr); expr.setValue(resultExpr);
resultExpr = expr; resultExpr = expr;
} finally {
popLocation();
}
} }
@Override @Override
public void visit(AssignmentStatement statement) { public void visit(AssignmentStatement statement) {
pushLocation(statement.getLocation());
try {
if (statement.getLeftValue() == null) { if (statement.getLeftValue() == null) {
statement.getRightValue().acceptVisitor(this); statement.getRightValue().acceptVisitor(this);
if (resultExpr instanceof InvocationExpr && tryApplyConstructor((InvocationExpr) resultExpr)) { if (resultExpr instanceof InvocationExpr && tryApplyConstructor((InvocationExpr) resultExpr)) {
@ -377,6 +472,9 @@ class OptimizingVisitor implements StatementVisitor, ExprVisitor {
statement.setRightValue(right); statement.setRightValue(right);
resultStmt = statement; resultStmt = statement;
} }
} finally {
popLocation();
}
} }
private List<Statement> processSequence(List<Statement> statements) { private List<Statement> processSequence(List<Statement> statements) {
@ -724,23 +822,38 @@ class OptimizingVisitor implements StatementVisitor, ExprVisitor {
@Override @Override
public void visit(ReturnStatement statement) { public void visit(ReturnStatement statement) {
pushLocation(statement.getLocation());
try {
if (statement.getResult() != null) { if (statement.getResult() != null) {
statement.getResult().acceptVisitor(this); statement.getResult().acceptVisitor(this);
statement.setResult(resultExpr); statement.setResult(resultExpr);
} }
resultStmt = statement; resultStmt = statement;
} finally {
popLocation();
}
} }
@Override @Override
public void visit(ThrowStatement statement) { public void visit(ThrowStatement statement) {
pushLocation(statement.getLocation());
try {
statement.getException().acceptVisitor(this); statement.getException().acceptVisitor(this);
statement.setException(resultExpr); statement.setException(resultExpr);
resultStmt = statement; resultStmt = statement;
} finally {
popLocation();
}
} }
@Override @Override
public void visit(InitClassStatement statement) { public void visit(InitClassStatement statement) {
pushLocation(statement.getLocation());
try {
resultStmt = statement; resultStmt = statement;
} finally {
popLocation();
}
} }
@Override @Override
@ -761,15 +874,25 @@ class OptimizingVisitor implements StatementVisitor, ExprVisitor {
@Override @Override
public void visit(MonitorEnterStatement statement) { public void visit(MonitorEnterStatement statement) {
pushLocation(statement.getLocation());
try {
statement.getObjectRef().acceptVisitor(this); statement.getObjectRef().acceptVisitor(this);
statement.setObjectRef(resultExpr); statement.setObjectRef(resultExpr);
resultStmt = statement; resultStmt = statement;
} finally {
popLocation();
}
} }
@Override @Override
public void visit(MonitorExitStatement statement) { public void visit(MonitorExitStatement statement) {
pushLocation(statement.getLocation());
try {
statement.getObjectRef().acceptVisitor(this); statement.getObjectRef().acceptVisitor(this);
statement.setObjectRef(resultExpr); statement.setObjectRef(resultExpr);
resultStmt = statement; resultStmt = statement;
} finally {
popLocation();
}
} }
} }

View File

@ -294,7 +294,8 @@ public class JavaScriptTarget implements TeaVMTarget, TeaVMJavaScriptHost {
asyncMethods.addAll(asyncFinder.getAsyncMethods()); asyncMethods.addAll(asyncFinder.getAsyncMethods());
asyncFamilyMethods.addAll(asyncFinder.getAsyncFamilyMethods()); asyncFamilyMethods.addAll(asyncFinder.getAsyncFamilyMethods());
Decompiler decompiler = new Decompiler(classes, controller.getClassLoader(), asyncMethods, asyncFamilyMethods); Decompiler decompiler = new Decompiler(classes, controller.getClassLoader(), asyncMethods, asyncFamilyMethods,
controller.isFriendlyToDebugger());
decompiler.setRegularMethodCache(controller.isIncremental() ? astCache : null); decompiler.setRegularMethodCache(controller.isIncremental() ? astCache : null);
for (Map.Entry<MethodReference, Generator> entry : methodGenerators.entrySet()) { for (Map.Entry<MethodReference, Generator> entry : methodGenerators.entrySet()) {

View File

@ -926,7 +926,7 @@ public class StatementRenderer implements ExprVisitor, StatementVisitor {
break; break;
} }
if (expr.getLocation() != null) { if (expr.getLocation() != null) {
pushLocation(expr.getLocation()); popLocation();
} }
} catch (IOException e) { } catch (IOException e) {
throw new RenderingException("IO error occured", e); throw new RenderingException("IO error occured", e);

View File

@ -291,7 +291,7 @@ public class WasmTarget implements TeaVMTarget, TeaVMWasmHost {
classes, vtableProvider, tagRegistry, binaryWriter); classes, vtableProvider, tagRegistry, binaryWriter);
Decompiler decompiler = new Decompiler(classes, controller.getClassLoader(), new HashSet<>(), Decompiler decompiler = new Decompiler(classes, controller.getClassLoader(), new HashSet<>(),
new HashSet<>()); new HashSet<>(), false);
WasmStringPool stringPool = classGenerator.getStringPool(); WasmStringPool stringPool = classGenerator.getStringPool();
WasmGenerationContext context = new WasmGenerationContext(classes, module, controller.getDiagnostics(), WasmGenerationContext context = new WasmGenerationContext(classes, module, controller.getDiagnostics(),
vtableProvider, tagRegistry, stringPool); vtableProvider, tagRegistry, stringPool);

View File

@ -34,8 +34,8 @@ public class Debugger {
private ConcurrentMap<String, ConcurrentMap<DebugInformation, Object>> debugInformationFileMap = private ConcurrentMap<String, ConcurrentMap<DebugInformation, Object>> debugInformationFileMap =
new ConcurrentHashMap<>(); new ConcurrentHashMap<>();
private ConcurrentMap<DebugInformation, String> scriptMap = new ConcurrentHashMap<>(); private ConcurrentMap<DebugInformation, String> scriptMap = new ConcurrentHashMap<>();
final ConcurrentMap<JavaScriptBreakpoint, Breakpoint> breakpointMap = new ConcurrentHashMap<>(); private final ConcurrentMap<JavaScriptBreakpoint, Breakpoint> breakpointMap = new ConcurrentHashMap<>();
ConcurrentMap<Breakpoint, Object> breakpoints = new ConcurrentHashMap<>(); private final ConcurrentMap<Breakpoint, Object> breakpoints = new ConcurrentHashMap<>();
private volatile CallFrame[] callStack; private volatile CallFrame[] callStack;
public Debugger(JavaScriptDebugger javaScriptDebugger, DebugInformationProvider debugInformationProvider) { public Debugger(JavaScriptDebugger javaScriptDebugger, DebugInformationProvider debugInformationProvider) {
@ -126,12 +126,12 @@ public class Debugger {
javaScriptDebugger.resume(); javaScriptDebugger.resume();
} }
private static class CallSiteSuccessorFinder implements DebuggerCallSiteVisitor { static class CallSiteSuccessorFinder implements DebuggerCallSiteVisitor {
private DebugInformation debugInfo; private DebugInformation debugInfo;
private String script; private String script;
Set<JavaScriptLocation> locations; Set<JavaScriptLocation> locations;
public CallSiteSuccessorFinder(DebugInformation debugInfo, String script, Set<JavaScriptLocation> locations) { CallSiteSuccessorFinder(DebugInformation debugInfo, String script, Set<JavaScriptLocation> locations) {
this.debugInfo = debugInfo; this.debugInfo = debugInfo;
this.script = script; this.script = script;
this.locations = locations; this.locations = locations;
@ -185,7 +185,7 @@ public class Debugger {
private List<DebugInformation> debugInformationBySource(String sourceFile) { private List<DebugInformation> debugInformationBySource(String sourceFile) {
Map<DebugInformation, Object> list = debugInformationFileMap.get(sourceFile); Map<DebugInformation, Object> list = debugInformationFileMap.get(sourceFile);
return list != null ? new ArrayList<>(list.keySet()) : Collections.<DebugInformation>emptyList(); return list != null ? new ArrayList<>(list.keySet()) : Collections.emptyList();
} }
public void continueToLocation(SourceLocation location) { public void continueToLocation(SourceLocation location) {
@ -218,6 +218,10 @@ public class Debugger {
return createBreakpoint(new SourceLocation(file, line)); return createBreakpoint(new SourceLocation(file, line));
} }
public Collection<? extends String> getSourceFiles() {
return debugInformationFileMap.keySet();
}
public Breakpoint createBreakpoint(SourceLocation location) { public Breakpoint createBreakpoint(SourceLocation location) {
synchronized (breakpointMap) { synchronized (breakpointMap) {
Breakpoint breakpoint = new Breakpoint(this, location); Breakpoint breakpoint = new Breakpoint(this, location);
@ -232,7 +236,7 @@ public class Debugger {
return new HashSet<>(breakpoints.keySet()); return new HashSet<>(breakpoints.keySet());
} }
void updateInternalBreakpoints(Breakpoint breakpoint) { private void updateInternalBreakpoints(Breakpoint breakpoint) {
if (breakpoint.isDestroyed()) { if (breakpoint.isDestroyed()) {
return; return;
} }
@ -259,7 +263,7 @@ public class Debugger {
return listeners.keySet().toArray(new DebuggerListener[0]); return listeners.keySet().toArray(new DebuggerListener[0]);
} }
void updateBreakpointStatus(Breakpoint breakpoint, boolean fireEvent) { private void updateBreakpointStatus(Breakpoint breakpoint, boolean fireEvent) {
boolean valid = false; boolean valid = false;
for (JavaScriptBreakpoint jsBreakpoint : breakpoint.jsBreakpoints) { for (JavaScriptBreakpoint jsBreakpoint : breakpoint.jsBreakpoints) {
if (jsBreakpoint.isValid()) { if (jsBreakpoint.isValid()) {

View File

@ -63,6 +63,9 @@ class VariableMap extends AbstractMap<String, Variable> {
String[] names = debugger.mapVariable(entry.getKey(), location); String[] names = debugger.mapVariable(entry.getKey(), location);
Value value = new Value(debugger, jsVar.getValue()); Value value = new Value(debugger, jsVar.getValue());
for (String name : names) { for (String name : names) {
if (name == null) {
name = "js:" + jsVar.getName();
}
vars.put(name, new Variable(name, value)); vars.put(name, new Variable(name, value));
} }
} }

View File

@ -216,7 +216,7 @@ public class DebugInformation {
int[] valueIndexes = mapping.get(keyIndex).getArray(0); int[] valueIndexes = mapping.get(keyIndex).getArray(0);
String[] result = new String[valueIndexes.length]; String[] result = new String[valueIndexes.length];
for (int i = 0; i < result.length; ++i) { for (int i = 0; i < result.length; ++i) {
result[i] = variableNames[valueIndexes[i]]; result[i] = valueIndexes[i] >= 0 ? variableNames[valueIndexes[i]] : null;
} }
return result; return result;
} }

View File

@ -132,11 +132,8 @@ public class DebugInformationBuilder implements DebugInformationEmitter {
} }
Arrays.sort(sourceIndexes); Arrays.sort(sourceIndexes);
int generatedIndex = variableNames.index(generatedName); int generatedIndex = variableNames.index(generatedName);
RecordArrayBuilder mapping = variableMappings.get(generatedIndex); RecordArrayBuilder mapping = variableMappings.computeIfAbsent(generatedIndex,
if (mapping == null) { k -> new RecordArrayBuilder(2, 1));
mapping = new RecordArrayBuilder(2, 1);
variableMappings.put(generatedIndex, mapping);
}
RecordArrayBuilder.Record record = add(mapping); RecordArrayBuilder.Record record = add(mapping);
RecordArrayBuilder.SubArray array = record.getArray(0); RecordArrayBuilder.SubArray array = record.getArray(0);

View File

@ -661,5 +661,10 @@ public class TeaVM implements TeaVMHost, ServiceRepository {
public Map<String, String> getExportedClasses() { public Map<String, String> getExportedClasses() {
return readonlyExportedClasses; return readonlyExportedClasses;
} }
@Override
public boolean isFriendlyToDebugger() {
return optimizationLevel == TeaVMOptimizationLevel.SIMPLE;
}
}; };
} }

View File

@ -39,6 +39,8 @@ public interface TeaVMTargetController {
boolean isIncremental(); boolean isIncremental();
boolean isFriendlyToDebugger();
Map<String, TeaVMEntryPoint> getEntryPoints(); Map<String, TeaVMEntryPoint> getEntryPoints();
Map<String, String> getExportedClasses(); Map<String, String> getExportedClasses();

View File

@ -24,6 +24,8 @@ import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future; import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
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;
@ -119,7 +121,12 @@ public class ChromeRDPDebugger implements JavaScriptDebugger, ChromeRDPExchangeC
} }
} }
CompletableFuture<Object> future = futures.remove(response.getId()); CompletableFuture<Object> future = futures.remove(response.getId());
try {
responseHandlers.remove(response.getId()).received(response.getResult(), future); responseHandlers.remove(response.getId()).received(response.getResult(), future);
} catch (RuntimeException e) {
logger.warn("Error processing message ${}", response.getId(), e);
future.completeExceptionally(e);
}
} else { } else {
Message message = mapper.reader(Message.class).readValue(messageText); Message message = mapper.reader(Message.class).readValue(messageText);
if (message.getMethod() == null) { if (message.getMethod() == null) {
@ -386,7 +393,7 @@ public class ChromeRDPDebugger implements JavaScriptDebugger, ChromeRDPExchangeC
try { try {
return read(sync); return read(sync);
} catch (InterruptedException e) { } catch (InterruptedException | TimeoutException e) {
return Collections.emptyList(); return Collections.emptyList();
} }
} }
@ -421,6 +428,8 @@ public class ChromeRDPDebugger implements JavaScriptDebugger, ChromeRDPExchangeC
return result.isEmpty() ? null : result; return result.isEmpty() ? null : result;
} catch (InterruptedException e) { } catch (InterruptedException e) {
return null; return null;
} catch (TimeoutException e) {
return "<timed out>";
} }
} }
@ -454,6 +463,8 @@ public class ChromeRDPDebugger implements JavaScriptDebugger, ChromeRDPExchangeC
return result.repr; return result.repr;
} catch (InterruptedException e) { } catch (InterruptedException e) {
return null; return null;
} catch (TimeoutException e) {
return "<timed out>";
} }
} }
@ -559,9 +570,9 @@ public class ChromeRDPDebugger implements JavaScriptDebugger, ChromeRDPExchangeC
void received(JsonNode node, CompletableFuture<T> out) throws IOException; void received(JsonNode node, CompletableFuture<T> out) throws IOException;
} }
private static <T> T read(Future<T> future) throws InterruptedException { private static <T> T read(Future<T> future) throws InterruptedException, TimeoutException {
try { try {
return future.get(); return future.get(1500, TimeUnit.MILLISECONDS);
} catch (ExecutionException e) { } catch (ExecutionException e) {
Throwable cause = e.getCause(); Throwable cause = e.getCause();
if (cause instanceof RuntimeException) { if (cause instanceof RuntimeException) {

View File

@ -51,6 +51,11 @@ public final class ChromeRDPRunner {
new Thread(server::start).start(); new Thread(server::start).start();
debugger = new Debugger(jsDebugger, new URLDebugInformationProvider("")); debugger = new Debugger(jsDebugger, new URLDebugInformationProvider(""));
debugger.addListener(listener); debugger.addListener(listener);
Thread.setDefaultUncaughtExceptionHandler((t, e) -> {
System.err.println("Uncaught exception in thread " + t);
e.printStackTrace();
});
} }
private DebuggerListener listener = new DebuggerListener() { private DebuggerListener listener = new DebuggerListener() {
@ -221,12 +226,55 @@ public final class ChromeRDPRunner {
System.out.println("Expected 2 arguments"); System.out.println("Expected 2 arguments");
return; return;
} }
Breakpoint bp = debugger.createBreakpoint(args[1], Integer.parseInt(args[2]));
String[] fileNames = resolveFileName(args[1]);
if (fileNames.length == 0) {
System.out.println("Unknown file: " + args[1]);
return;
} else if (fileNames.length > 1) {
System.out.println("Ambiguous file name: " + args[1] + ". Possible names are: "
+ Arrays.toString(fileNames));
return;
}
Breakpoint bp = debugger.createBreakpoint(fileNames[0], Integer.parseInt(args[2]));
int id = breakpointIdGen++; int id = breakpointIdGen++;
breakpointIds.put(bp, id); breakpointIds.put(bp, id);
System.out.println("Breakpoint #" + id + " was set at " + bp.getLocation()); System.out.println("Breakpoint #" + id + " was set at " + bp.getLocation());
}; };
private String[] resolveFileName(String fileName) {
if (debugger.getSourceFiles().contains(fileName)) {
return new String[] { fileName };
}
String[] result = debugger.getSourceFiles().stream()
.filter(f -> f.endsWith(fileName) && isPrecededByPathSeparator(f, fileName))
.toArray(String[]::new);
if (result.length == 1) {
return result;
}
return debugger.getSourceFiles().stream()
.filter(f -> {
int index = f.lastIndexOf('.');
if (index <= 0) {
return false;
}
String nameWithoutExt = f.substring(0, index);
return nameWithoutExt.endsWith(fileName) && isPrecededByPathSeparator(nameWithoutExt, fileName);
})
.toArray(String[]::new);
}
private static boolean isPrecededByPathSeparator(String actualName, String specifiedName) {
if (actualName.length() < specifiedName.length() + 1) {
return false;
}
char c = actualName.charAt(actualName.length() - specifiedName.length() - 1);
return c == '/' || c == '\\';
}
private Command backtraceCommand = args -> { private Command backtraceCommand = args -> {
CallFrame[] callStack = debugger.getCallStack(); CallFrame[] callStack = debugger.getCallStack();
for (int i = 0; i < callStack.length; ++i) { for (int i = 0; i < callStack.length; ++i) {