Fix bugs in debugger

This commit is contained in:
Alexey Andreev 2019-02-12 12:10:33 +03:00
parent 5c90d786a0
commit 89189c7454
6 changed files with 418 additions and 17 deletions

View File

@ -18,15 +18,25 @@ package org.teavm.ast;
import java.util.List;
public class RecursiveVisitor implements ExprVisitor, StatementVisitor {
protected void beforeVisit(Expr expr) {
}
protected void afterVisit(Expr expr) {
}
@Override
public void visit(BinaryExpr expr) {
beforeVisit(expr);
expr.getFirstOperand().acceptVisitor(this);
expr.getSecondOperand().acceptVisitor(this);
afterVisit(expr);
}
@Override
public void visit(UnaryExpr expr) {
beforeVisit(expr);
expr.getOperand().acceptVisitor(this);
afterVisit(expr);
}
@Override
@ -39,9 +49,11 @@ public class RecursiveVisitor implements ExprVisitor, StatementVisitor {
@Override
public void visit(ConditionalExpr expr) {
beforeVisit(expr);
expr.getCondition().acceptVisitor(this);
expr.getConsequent().acceptVisitor(this);
expr.getAlternative().acceptVisitor(this);
afterVisit(expr);
}
public void visit(List<Statement> statements) {
@ -57,6 +69,8 @@ public class RecursiveVisitor implements ExprVisitor, StatementVisitor {
@Override
public void visit(ConstantExpr expr) {
beforeVisit(expr);
afterVisit(expr);
}
@Override
@ -68,12 +82,16 @@ public class RecursiveVisitor implements ExprVisitor, StatementVisitor {
@Override
public void visit(VariableExpr expr) {
beforeVisit(expr);
afterVisit(expr);
}
@Override
public void visit(SubscriptExpr expr) {
beforeVisit(expr);
expr.getArray().acceptVisitor(this);
expr.getIndex().acceptVisitor(this);
afterVisit(expr);
}
@Override
@ -87,7 +105,9 @@ public class RecursiveVisitor implements ExprVisitor, StatementVisitor {
@Override
public void visit(UnwrapArrayExpr expr) {
beforeVisit(expr);
expr.getArray().acceptVisitor(this);
afterVisit(expr);
}
@Override
@ -100,9 +120,11 @@ public class RecursiveVisitor implements ExprVisitor, StatementVisitor {
@Override
public void visit(InvocationExpr expr) {
beforeVisit(expr);
for (Expr argument : expr.getArguments()) {
argument.acceptVisitor(this);
}
afterVisit(expr);
}
@Override
@ -112,9 +134,11 @@ public class RecursiveVisitor implements ExprVisitor, StatementVisitor {
@Override
public void visit(QualificationExpr expr) {
beforeVisit(expr);
if (expr.getQualified() != null) {
expr.getQualified().acceptVisitor(this);
}
afterVisit(expr);
}
@Override
@ -123,6 +147,8 @@ public class RecursiveVisitor implements ExprVisitor, StatementVisitor {
@Override
public void visit(NewExpr expr) {
beforeVisit(expr);
afterVisit(expr);
}
@Override
@ -131,14 +157,18 @@ public class RecursiveVisitor implements ExprVisitor, StatementVisitor {
@Override
public void visit(NewArrayExpr expr) {
beforeVisit(expr);
expr.getLength().acceptVisitor(this);
afterVisit(expr);
}
@Override
public void visit(NewMultiArrayExpr expr) {
beforeVisit(expr);
for (Expr dimension : expr.getDimensions()) {
dimension.acceptVisitor(this);
}
afterVisit(expr);
}
@Override
@ -150,7 +180,9 @@ public class RecursiveVisitor implements ExprVisitor, StatementVisitor {
@Override
public void visit(InstanceOfExpr expr) {
beforeVisit(expr);
expr.getExpr().acceptVisitor(this);
afterVisit(expr);
}
@Override
@ -160,7 +192,9 @@ public class RecursiveVisitor implements ExprVisitor, StatementVisitor {
@Override
public void visit(CastExpr expr) {
beforeVisit(expr);
expr.getValue().acceptVisitor(this);
afterVisit(expr);
}
@Override
@ -169,7 +203,9 @@ public class RecursiveVisitor implements ExprVisitor, StatementVisitor {
@Override
public void visit(PrimitiveCastExpr expr) {
beforeVisit(expr);
expr.getValue().acceptVisitor(this);
afterVisit(expr);
}
@Override

View File

@ -0,0 +1,349 @@
/*
* Copyright 2019 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.ast.analysis;
import com.carrotsearch.hppc.IntArrayDeque;
import com.carrotsearch.hppc.IntArrayList;
import com.carrotsearch.hppc.IntDeque;
import com.carrotsearch.hppc.IntHashSet;
import com.carrotsearch.hppc.ObjectIntHashMap;
import com.carrotsearch.hppc.ObjectIntMap;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.teavm.ast.AssignmentStatement;
import org.teavm.ast.BlockStatement;
import org.teavm.ast.BreakStatement;
import org.teavm.ast.ConditionalExpr;
import org.teavm.ast.ConditionalStatement;
import org.teavm.ast.ContinueStatement;
import org.teavm.ast.Expr;
import org.teavm.ast.IdentifiedStatement;
import org.teavm.ast.InitClassStatement;
import org.teavm.ast.MonitorEnterStatement;
import org.teavm.ast.MonitorExitStatement;
import org.teavm.ast.RecursiveVisitor;
import org.teavm.ast.ReturnStatement;
import org.teavm.ast.Statement;
import org.teavm.ast.SwitchClause;
import org.teavm.ast.SwitchStatement;
import org.teavm.ast.ThrowStatement;
import org.teavm.ast.TryCatchStatement;
import org.teavm.ast.WhileStatement;
import org.teavm.common.Graph;
import org.teavm.common.GraphBuilder;
import org.teavm.model.TextLocation;
public final class LocationGraphBuilder {
private LocationGraphBuilder() {
}
public static Map<TextLocation, TextLocation[]> build(Statement node) {
Visitor visitor = new Visitor();
node.acceptVisitor(visitor);
Graph graph = visitor.builder.build();
TextLocation[][] locations = propagate(visitor.locations.toArray(new TextLocation[0]), graph);
Map<TextLocation, Set<TextLocation>> builder = new LinkedHashMap<>();
for (int i = 0; i < graph.size(); ++i) {
for (int j : graph.outgoingEdges(i)) {
for (TextLocation from : locations[i]) {
for (TextLocation to : locations[j]) {
builder.computeIfAbsent(from, k -> new LinkedHashSet<>()).add(to);
}
}
}
}
Map<TextLocation, TextLocation[]> result = new LinkedHashMap<>();
for (Map.Entry<TextLocation, Set<TextLocation>> entry : builder.entrySet()) {
result.put(entry.getKey(), entry.getValue().toArray(new TextLocation[0]));
}
return result;
}
private static TextLocation[][] propagate(TextLocation[] locations, Graph graph) {
List<Set<TextLocation>> result = new ArrayList<>();
boolean[] stop = new boolean[graph.size()];
IntDeque queue = new IntArrayDeque();
for (int i = 0; i < stop.length; ++i) {
Set<TextLocation> set = new LinkedHashSet<>();
result.add(set);
if (locations[i] != null) {
stop[i] = true;
queue.addLast(i);
set.add(locations[i]);
}
}
while (!queue.isEmpty()) {
int node = queue.removeFirst();
for (int successor : graph.outgoingEdges(node)) {
if (stop[successor]) {
continue;
}
if (result.get(successor).addAll(result.get(node))) {
queue.addLast(successor);
}
}
}
return result.stream().map(s -> s.toArray(new TextLocation[0])).toArray(TextLocation[][]::new);
}
static class Visitor extends RecursiveVisitor {
static final int[] EMPTY = new int[0];
int[] nodes = EMPTY;
ObjectIntMap<IdentifiedStatement> breakNodes = new ObjectIntHashMap<>();
ObjectIntMap<IdentifiedStatement> continueNodes = new ObjectIntHashMap<>();
IdentifiedStatement defaultBreakTarget;
IdentifiedStatement defaultContinueTarget;
GraphBuilder builder = new GraphBuilder();
List<TextLocation> locations = new ArrayList<>();
@Override
protected void afterVisit(Expr expr) {
setLocation(expr.getLocation());
}
@Override
public void visit(BlockStatement statement) {
int exit = createNode(null);
breakNodes.put(statement, exit);
super.visit(statement);
breakNodes.remove(statement);
setNode(exit);
}
@Override
public void visit(WhileStatement statement) {
IdentifiedStatement oldDefaultBreakTarget = defaultBreakTarget;
IdentifiedStatement oldDefaultContinueTarget = defaultContinueTarget;
int head = createNode(null);
int exit = createNode(null);
setNode(head);
breakNodes.put(statement, exit);
continueNodes.put(statement, head);
defaultBreakTarget = statement;
defaultContinueTarget = statement;
if (statement.getCondition() != null) {
statement.getCondition().acceptVisitor(this);
}
for (int node : nodes) {
builder.addEdge(node, exit);
}
visit(statement.getBody());
for (int node : nodes) {
builder.addEdge(node, head);
}
nodes = new int[] { exit };
defaultBreakTarget = oldDefaultBreakTarget;
defaultContinueTarget = oldDefaultContinueTarget;
breakNodes.remove(statement);
continueNodes.remove(statement);
}
@Override
public void visit(SwitchStatement statement) {
IdentifiedStatement oldDefaultBreakTarget = defaultBreakTarget;
int exit = createNode(null);
breakNodes.put(statement, exit);
defaultBreakTarget = statement;
statement.getValue().acceptVisitor(this);
int[] headNodes = nodes;
for (SwitchClause clause : statement.getClauses()) {
nodes = headNodes;
visit(clause.getBody());
for (int node : nodes) {
builder.addEdge(node, exit);
}
}
nodes = headNodes;
visit(statement.getDefaultClause());
for (int node : nodes) {
builder.addEdge(node, exit);
}
nodes = new int[] { exit };
defaultBreakTarget = oldDefaultBreakTarget;
breakNodes.remove(statement);
}
@Override
public void visit(ConditionalStatement statement) {
statement.getCondition().acceptVisitor(this);
IntArrayList exit = new IntArrayList();
int[] head = nodes;
visit(statement.getConsequent());
exit.add(nodes);
nodes = head;
visit(statement.getAlternative());
exit.add(nodes);
nodes = distinct(exit);
}
private int[] distinct(IntArrayList list) {
IntHashSet set = new IntHashSet();
int j = 0;
int[] result = new int[list.size()];
for (int i = 0; i < list.size(); ++i) {
int e = list.get(i);
if (set.add(e)) {
result[j++] = e;
}
}
if (j < result.length) {
result = Arrays.copyOf(result, j);
}
return result;
}
@Override
public void visit(BreakStatement statement) {
IdentifiedStatement target = statement.getTarget();
if (target == null) {
target = defaultBreakTarget;
}
int targetNode = breakNodes.get(target);
for (int node : nodes) {
builder.addEdge(node, targetNode);
}
nodes = EMPTY;
}
@Override
public void visit(ContinueStatement statement) {
IdentifiedStatement target = statement.getTarget();
if (target == null) {
target = defaultContinueTarget;
}
int targetNode = continueNodes.get(target);
for (int node : nodes) {
builder.addEdge(node, targetNode);
}
nodes = EMPTY;
}
@Override
public void visit(ThrowStatement statement) {
super.visit(statement);
setLocation(statement.getLocation());
nodes = EMPTY;
}
@Override
public void visit(ReturnStatement statement) {
super.visit(statement);
setLocation(statement.getLocation());
nodes = EMPTY;
}
@Override
public void visit(TryCatchStatement statement) {
int catchNode = createNode(null);
for (Statement s : statement.getProtectedBody()) {
s.acceptVisitor(this);
for (int node : nodes) {
builder.addEdge(node, catchNode);
}
}
nodes = new int[] { catchNode };
visit(statement.getHandler());
}
@Override
public void visit(AssignmentStatement statement) {
super.visit(statement);
setLocation(statement.getLocation());
}
@Override
public void visit(InitClassStatement statement) {
super.visit(statement);
setLocation(statement.getLocation());
}
@Override
public void visit(MonitorEnterStatement statement) {
super.visit(statement);
setLocation(statement.getLocation());
}
@Override
public void visit(MonitorExitStatement statement) {
super.visit(statement);
setLocation(statement.getLocation());
}
@Override
public void visit(ConditionalExpr expr) {
expr.getCondition().acceptVisitor(this);
IntArrayList exit = new IntArrayList();
int[] head = nodes;
expr.getConsequent().acceptVisitor(this);
exit.add(nodes);
nodes = head;
expr.getAlternative().acceptVisitor(this);
exit.add(nodes);
nodes = distinct(exit);
}
private void setNode(int node) {
for (int prevNode : nodes) {
builder.addEdge(prevNode, node);
}
nodes = new int[] { node };
}
private void setLocation(TextLocation location) {
if (location == null) {
return;
}
int node = createNode(location);
for (int prevNode : nodes) {
builder.addEdge(prevNode, node);
}
nodes = new int[] { node };
}
private int createNode(TextLocation location) {
int index = locations.size();
locations.add(location);
return index;
}
}
}

View File

@ -270,12 +270,13 @@ class StatementGenerator implements InstructionVisitor {
insn.getConsequent(), insn.getAlternative());
break;
case NOT_NULL:
branch(Expr.binary(BinaryOperation.NOT_EQUALS, null, Expr.var(insn.getOperand().getIndex()),
Expr.constant(null)), insn.getConsequent(), insn.getAlternative());
branch(withLocation(Expr.binary(BinaryOperation.NOT_EQUALS, null,
Expr.var(insn.getOperand().getIndex()), Expr.constant(null), currentLocation)),
insn.getConsequent(), insn.getAlternative());
break;
case NULL:
branch(Expr.binary(BinaryOperation.EQUALS, null, Expr.var(insn.getOperand().getIndex()),
Expr.constant(null)), insn.getConsequent(), insn.getAlternative());
branch(withLocation(Expr.binary(BinaryOperation.EQUALS, null, Expr.var(insn.getOperand().getIndex()),
Expr.constant(null))), insn.getConsequent(), insn.getAlternative());
break;
}
}

View File

@ -35,6 +35,10 @@ import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import org.teavm.ast.ClassNode;
import org.teavm.ast.MethodNode;
import org.teavm.ast.RegularMethodNode;
import org.teavm.ast.Statement;
import org.teavm.ast.analysis.LocationGraphBuilder;
import org.teavm.ast.decompilation.Decompiler;
import org.teavm.backend.javascript.codegen.AliasProvider;
import org.teavm.backend.javascript.codegen.DefaultAliasProvider;
@ -331,11 +335,16 @@ public class JavaScriptTarget implements TeaVMTarget, TeaVMJavaScriptHost {
renderer.setMinifying(minifying);
renderer.setProgressConsumer(controller::reportProgress);
if (debugEmitter != null) {
for (String className : classes.getClassNames()) {
ClassHolder cls = classes.get(className);
for (MethodHolder method : cls.getMethods()) {
if (method.getProgram() != null) {
emitCFG(debugEmitter, method.getProgram());
for (ClassNode classNode : clsNodes) {
ClassHolder cls = classes.get(classNode.getName());
for (MethodNode methodNode : classNode.getMethods()) {
if (methodNode instanceof RegularMethodNode) {
emitCFG(debugEmitter, ((RegularMethodNode) methodNode).getBody());
} else {
MethodHolder method = cls.getMethod(methodNode.getReference().getDescriptor());
if (method != null && method.getProgram() != null) {
emitCFG(debugEmitter, method.getProgram());
}
}
}
if (controller.wasCancelled()) {
@ -575,7 +584,14 @@ public class JavaScriptTarget implements TeaVMTarget, TeaVMJavaScriptHost {
}
private void emitCFG(DebugInformationEmitter emitter, Program program) {
Map<TextLocation, TextLocation[]> cfg = ProgramUtils.getLocationCFG(program);
emitCFG(emitter, ProgramUtils.getLocationCFG(program));
}
private void emitCFG(DebugInformationEmitter emitter, Statement program) {
emitCFG(emitter, LocationGraphBuilder.build(program));
}
private void emitCFG(DebugInformationEmitter emitter, Map<TextLocation, TextLocation[]> cfg) {
for (Map.Entry<TextLocation, TextLocation[]> entry : cfg.entrySet()) {
SourceLocation location = map(entry.getKey());
SourceLocation[] successors = new SourceLocation[entry.getValue().length];

View File

@ -276,21 +276,16 @@ public class StatementRenderer implements ExprVisitor, StatementVisitor {
public void visit(WhileStatement statement) {
try {
debugEmitter.emitStatementStart();
if (statement.getCondition() != null && statement.getCondition().getLocation() != null) {
pushLocation(statement.getCondition().getLocation());
}
if (statement.getId() != null) {
writer.append(mapBlockId(statement.getId())).append(":").ws();
}
writer.append("while").ws().append("(");
writer.append("while");
writer.ws().append("(");
if (statement.getCondition() != null) {
prevCallSite = debugEmitter.emitCallSite();
precedence = Precedence.min();
statement.getCondition().acceptVisitor(this);
debugEmitter.emitCallSite();
if (statement.getCondition().getLocation() != null) {
popLocation();
}
} else {
writer.append("true");
}

View File

@ -209,6 +209,10 @@ class DebugInformationWriter {
}
private void writeCFG(RecordArray mapping) throws IOException {
if (mapping == null) {
writeUnsignedNumber(0);
return;
}
writeUnsignedNumber(mapping.size());
writeRle(mapping.cut(0));
IntegerArray sizes = new IntegerArray(1);