diff --git a/teavm-chrome-rdp/src/main/java/org/teavm/chromerdp/ChromeRDPDebugger.java b/teavm-chrome-rdp/src/main/java/org/teavm/chromerdp/ChromeRDPDebugger.java index c421957e6..d0b1cbf37 100644 --- a/teavm-chrome-rdp/src/main/java/org/teavm/chromerdp/ChromeRDPDebugger.java +++ b/teavm-chrome-rdp/src/main/java/org/teavm/chromerdp/ChromeRDPDebugger.java @@ -6,6 +6,8 @@ import java.util.Collections; import java.util.List; import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; import org.codehaus.jackson.JsonNode; import org.codehaus.jackson.map.ObjectMapper; import org.teavm.chromerdp.data.*; @@ -20,6 +22,7 @@ public class ChromeRDPDebugger implements JavaScriptDebugger, ChromeRDPExchangeC private static final Object dummy = new Object(); private ChromeRDPExchange exchange; private ConcurrentMap listeners = new ConcurrentHashMap<>(); + private ConcurrentMap breakpointLocationMap = new ConcurrentHashMap<>(); private ConcurrentMap breakpoints = new ConcurrentHashMap<>(); private volatile RDPCallFrame[] callStack = new RDPCallFrame[0]; private ConcurrentMap scripts = new ConcurrentHashMap<>(); @@ -28,6 +31,7 @@ public class ChromeRDPDebugger implements JavaScriptDebugger, ChromeRDPExchangeC private ObjectMapper mapper = new ObjectMapper(); private ConcurrentMap responseHandlers = new ConcurrentHashMap<>(); private AtomicInteger messageIdGenerator = new AtomicInteger(); + private Lock breakpointLock = new ReentrantLock(); private List getListeners() { return new ArrayList<>(listeners.keySet()); @@ -74,7 +78,7 @@ public class ChromeRDPDebugger implements JavaScriptDebugger, ChromeRDPExchangeC if (jsonMessage.has("id")) { Response response = mapper.reader(Response.class).readValue(jsonMessage); if (response.getError() != null) { - System.err.println(response.getError().toString()); + System.err.println("#" + jsonMessage.get("id") + ": " + response.getError().toString()); } responseHandlers.remove(response.getId()).received(response.getResult()); } else { @@ -235,21 +239,49 @@ public class ChromeRDPDebugger implements JavaScriptDebugger, ChromeRDPExchangeC @Override public JavaScriptBreakpoint createBreakpoint(JavaScriptLocation location) { - RDPBreakpoint breakpoint = new RDPBreakpoint(this, location); - breakpoints.put(breakpoint, dummy); - updateBreakpoint(breakpoint); + RDPBreakpoint breakpoint; + + breakpointLock.lock(); + try { + breakpoint = breakpointLocationMap.get(location); + if (breakpoint == null) { + breakpoint = new RDPBreakpoint(this, location); + breakpointLocationMap.put(location, breakpoint); + updateBreakpoint(breakpoint); + } + breakpoint.referenceCount.incrementAndGet(); + breakpoints.put(breakpoint, dummy); + } finally { + breakpointLock.unlock(); + } + return breakpoint; } void destroyBreakpoint(RDPBreakpoint breakpoint) { - breakpoints.remove(breakpoint); - if (breakpoint.chromeId != null) { - Message message = new Message(); - message.setMethod("Debugger.removeBreakpoint"); - RemoveBreakpointCommand params = new RemoveBreakpointCommand(); - params.setBreakpointId(breakpoint.chromeId); - message.setParams(mapper.valueToTree(params)); - sendMessage(message); + if (breakpoint.referenceCount.decrementAndGet() > 0) { + return; + } + breakpointLock.lock(); + try { + if (breakpoint.referenceCount.get() > 0) { + return; + } + breakpointLocationMap.remove(breakpoint.getLocation()); + breakpoints.remove(breakpoint); + if (breakpoint.chromeId != null) { + System.out.println("Removing breakpoint at " + breakpoint.getLocation()); + Message message = new Message(); + message.setMethod("Debugger.removeBreakpoint"); + RemoveBreakpointCommand params = new RemoveBreakpointCommand(); + params.setBreakpointId(breakpoint.chromeId); + message.setParams(mapper.valueToTree(params)); + sendMessage(message); + } + breakpoint.debugger = null; + breakpoint.chromeId = null; + } finally { + breakpointLock.unlock(); } } @@ -260,21 +292,24 @@ public class ChromeRDPDebugger implements JavaScriptDebugger, ChromeRDPExchangeC } void updateBreakpoint(final RDPBreakpoint breakpoint) { - if (exchange == null) { + if (exchange == null || breakpoint.chromeId != null) { return; } - Message message = new Message(); + final Message message = new Message(); message.setId(messageIdGenerator.incrementAndGet()); message.setMethod("Debugger.setBreakpoint"); SetBreakpointCommand params = new SetBreakpointCommand(); params.setLocation(unmap(breakpoint.getLocation())); message.setParams(mapper.valueToTree(params)); + System.out.println("Setting breakpoint at: " + breakpoint.getLocation()); ResponseHandler handler = new ResponseHandler() { @Override public void received(JsonNode node) throws IOException { if (node != null) { SetBreakpointResponse response = mapper.reader(SetBreakpointResponse.class).readValue(node); breakpoint.chromeId = response.getBreakpointId(); } else { + System.err.println("Error setting breakpoint at " + breakpoint.getLocation() + + ", message id is " + message.getId()); breakpoint.chromeId = null; } for (JavaScriptDebuggerListener listener : getListeners()) { diff --git a/teavm-chrome-rdp/src/main/java/org/teavm/chromerdp/RDPBreakpoint.java b/teavm-chrome-rdp/src/main/java/org/teavm/chromerdp/RDPBreakpoint.java index 08411fd65..278400419 100644 --- a/teavm-chrome-rdp/src/main/java/org/teavm/chromerdp/RDPBreakpoint.java +++ b/teavm-chrome-rdp/src/main/java/org/teavm/chromerdp/RDPBreakpoint.java @@ -15,6 +15,7 @@ */ package org.teavm.chromerdp; +import java.util.concurrent.atomic.AtomicInteger; import org.teavm.debugging.JavaScriptBreakpoint; import org.teavm.debugging.JavaScriptLocation; @@ -24,8 +25,9 @@ import org.teavm.debugging.JavaScriptLocation; */ public class RDPBreakpoint implements JavaScriptBreakpoint { volatile String chromeId; - private ChromeRDPDebugger debugger; + ChromeRDPDebugger debugger; private JavaScriptLocation location; + AtomicInteger referenceCount = new AtomicInteger(); RDPBreakpoint(ChromeRDPDebugger debugger, JavaScriptLocation location) { this.debugger = debugger; @@ -41,8 +43,6 @@ public class RDPBreakpoint implements JavaScriptBreakpoint { public void destroy() { if (debugger != null) { debugger.destroyBreakpoint(this); - chromeId = null; - debugger = null; } } diff --git a/teavm-core/src/main/java/org/teavm/debugging/Breakpoint.java b/teavm-core/src/main/java/org/teavm/debugging/Breakpoint.java index baf995aa8..4bcc22135 100644 --- a/teavm-core/src/main/java/org/teavm/debugging/Breakpoint.java +++ b/teavm-core/src/main/java/org/teavm/debugging/Breakpoint.java @@ -46,7 +46,7 @@ public class Breakpoint { return valid; } - public synchronized boolean isDestroyed() { + public boolean isDestroyed() { return debugger == null; } diff --git a/teavm-core/src/main/java/org/teavm/debugging/DebugInformation.java b/teavm-core/src/main/java/org/teavm/debugging/DebugInformation.java index b75fe157a..8d9e914c9 100644 --- a/teavm-core/src/main/java/org/teavm/debugging/DebugInformation.java +++ b/teavm-core/src/main/java/org/teavm/debugging/DebugInformation.java @@ -61,7 +61,7 @@ public class DebugInformation { if (description == null) { return Collections.emptyList(); } - if (line > description.generatedLocationStart.length) { + if (line >= description.generatedLocationStart.length - 1) { return Collections.emptyList(); } int start = description.generatedLocationStart[line]; diff --git a/teavm-core/src/main/java/org/teavm/debugging/DebugInformationBuilder.java b/teavm-core/src/main/java/org/teavm/debugging/DebugInformationBuilder.java index ee79b31b4..743ec5514 100644 --- a/teavm-core/src/main/java/org/teavm/debugging/DebugInformationBuilder.java +++ b/teavm-core/src/main/java/org/teavm/debugging/DebugInformationBuilder.java @@ -136,9 +136,9 @@ public class DebugInformationBuilder implements DebugInformationEmitter { } for (SourceLocation succ : successors) { if (succ == null) { - cfg.add(location.getLine(), fileIndex, -1); + cfg.add(location.getLine(), -1, fileIndex); } else { - cfg.add(location.getLine(), files.index(succ.getFileName()), succ.getLine()); + cfg.add(location.getLine(), succ.getLine(), files.index(succ.getFileName())); } } } @@ -183,6 +183,7 @@ public class DebugInformationBuilder implements DebugInformationEmitter { cfgs[i] = this.cfgs.get(i).build(); } } + debugInformation.controlFlowGraphs = cfgs; debugInformation.rebuildFileDescriptions(); debugInformation.rebuildMaps(); @@ -354,7 +355,7 @@ public class DebugInformationBuilder implements DebugInformationEmitter { } long[] pairs = new long[linesChunk.size()]; for (int j = 0; j < pairs.length; ++j) { - pairs[j] = (filesChunk.get(j) << 32) | linesChunk.get(j); + pairs[j] = (((long)filesChunk.get(j)) << 32) | linesChunk.get(j); } Arrays.sort(pairs); int distinctSize = 0; @@ -362,16 +363,16 @@ public class DebugInformationBuilder implements DebugInformationEmitter { long pair = pairs[j]; if (distinctSize == 0 || pair != pairs[distinctSize]) { pairs[distinctSize++] = pair; - linesData.add((int)(pair >>> 32)); - filesData.add((int)pair); + filesData.add((int)(pair >>> 32)); + linesData.add((int)pair); } } offsets[i + 1] = linesData.size(); } DebugInformation.CFG cfg = new DebugInformation.CFG(); cfg.offsets = offsets; - cfg.lines = lines.getAll(); - cfg.files = files.getAll(); + cfg.lines = linesData.getAll(); + cfg.files = filesData.getAll(); return cfg; } } diff --git a/teavm-core/src/main/java/org/teavm/debugging/Debugger.java b/teavm-core/src/main/java/org/teavm/debugging/Debugger.java index 21d72ab30..dcb5ef39d 100644 --- a/teavm-core/src/main/java/org/teavm/debugging/Debugger.java +++ b/teavm-core/src/main/java/org/teavm/debugging/Debugger.java @@ -54,24 +54,62 @@ public class Debugger { listeners.remove(listener); } - public synchronized void suspend() { + public void suspend() { javaScriptDebugger.suspend(); } - public synchronized void resume() { + public void resume() { javaScriptDebugger.resume(); } - public synchronized void stepInto() { + public void stepInto() { javaScriptDebugger.stepInto(); } - public synchronized void stepOut() { + public void stepOut() { javaScriptDebugger.stepOut(); } - public synchronized void stepOver() { - javaScriptDebugger.stepOver(); + public void stepOver() { + CallFrame[] callStack = getCallStack(); + if (callStack == null || callStack.length == 0) { + javaScriptDebugger.stepOver(); + return; + } + CallFrame recentFrame = callStack[0]; + if (recentFrame.getLocation() == null || recentFrame.getLocation().getFileName() == null || + recentFrame.getLocation().getLine() < 0) { + javaScriptDebugger.stepOver(); + return; + } + Set successors = new HashSet<>(); + for (int i = 0; i < callStack.length; ++i) { + CallFrame frame = callStack[i]; + boolean exits = false; + for (Map.Entry entry : debugInformationMap.entrySet()) { + DebugInformation debugInfo = entry.getValue(); + SourceLocation[] following = debugInfo.getFollowingLines(frame.getLocation()); + if (following == null) { + continue; + } + for (SourceLocation successor : debugInfo.getFollowingLines(frame.getLocation())) { + if (successor == null) { + exits = true; + } else { + for (GeneratedLocation loc : debugInfo.getGeneratedLocations(successor)) { + successors.add(new JavaScriptLocation(entry.getKey(), loc.getLine(), loc.getColumn())); + } + } + } + } + if (!exits) { + break; + } + } + for (JavaScriptLocation successor : successors) { + temporaryBreakpoints.add(javaScriptDebugger.createBreakpoint(successor)); + } + javaScriptDebugger.resume(); } private List debugInformationBySource(String sourceFile) { @@ -249,8 +287,8 @@ public class Debugger { } private void fireResumed() { - List termporaryBreakpoints = new ArrayList<>(); - this.temporaryBreakpoints.drainTo(termporaryBreakpoints); + List temporaryBreakpoints = new ArrayList<>(); + this.temporaryBreakpoints.drainTo(temporaryBreakpoints); for (JavaScriptBreakpoint jsBreakpoint : temporaryBreakpoints) { jsBreakpoint.destroy(); } diff --git a/teavm-core/src/main/java/org/teavm/debugging/JavaScriptLocation.java b/teavm-core/src/main/java/org/teavm/debugging/JavaScriptLocation.java index 64d6372fc..0bb830ac3 100644 --- a/teavm-core/src/main/java/org/teavm/debugging/JavaScriptLocation.java +++ b/teavm-core/src/main/java/org/teavm/debugging/JavaScriptLocation.java @@ -15,6 +15,8 @@ */ package org.teavm.debugging; +import java.util.Objects; + /** * * @author Alexey Andreev @@ -42,6 +44,23 @@ public class JavaScriptLocation { return column; } + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof JavaScriptLocation)) { + return false; + } + JavaScriptLocation other = (JavaScriptLocation)obj; + return Objects.equals(other.script, script) && other.line == line; + } + + @Override + public int hashCode() { + return (31 + line) * 31 + Objects.hashCode(script); + } + @Override public String toString() { return script + ":(" + line + ";" + column + ")"; diff --git a/teavm-core/src/main/java/org/teavm/model/InstructionLocation.java b/teavm-core/src/main/java/org/teavm/model/InstructionLocation.java index 40bee89cb..eb6a92898 100644 --- a/teavm-core/src/main/java/org/teavm/model/InstructionLocation.java +++ b/teavm-core/src/main/java/org/teavm/model/InstructionLocation.java @@ -58,4 +58,9 @@ public class InstructionLocation { InstructionLocation other = (InstructionLocation)obj; return Objects.equals(fileName, other.fileName) && line == other.line; } + + @Override + public String toString() { + return fileName + ":" + line; + } } diff --git a/teavm-core/src/main/java/org/teavm/model/util/LocationGraphBuilder.java b/teavm-core/src/main/java/org/teavm/model/util/LocationGraphBuilder.java new file mode 100644 index 000000000..3179fafdf --- /dev/null +++ b/teavm-core/src/main/java/org/teavm/model/util/LocationGraphBuilder.java @@ -0,0 +1,139 @@ +/* + * 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.model.util; + +import java.util.*; +import org.teavm.common.Graph; +import org.teavm.model.BasicBlock; +import org.teavm.model.Instruction; +import org.teavm.model.InstructionLocation; +import org.teavm.model.Program; + +/** + * + * @author Alexey Andreev + */ +class LocationGraphBuilder { + private Map> graphBuilder; + private List> startLocations; + private List additionalConnections; + + public Map build(Program program) { + graphBuilder = new HashMap<>(); + Graph graph = ProgramUtils.buildControlFlowGraph(program); + dfs(graph, program); + return assemble(); + } + + private void dfs(Graph graph, Program program) { + startLocations = new ArrayList<>(Collections.>nCopies(graph.size(), null)); + additionalConnections = new ArrayList<>(); + Deque stack = new ArrayDeque<>(); + for (int i = 0; i < graph.size(); ++i) { + if (graph.incomingEdgesCount(i) == 0) { + stack.push(new Step(null, new HashSet(), i)); + } + } + boolean[] visited = new boolean[graph.size()]; + InstructionLocation[] blockLocations = new InstructionLocation[graph.size()]; + + while (!stack.isEmpty()) { + Step step = stack.pop(); + if (visited[step.block]) { + if (step.location != null) { + additionalConnections.add(new AdditionalConnection(step.location, startLocations.get(step.block))); + } + continue; + } + visited[step.block] = true; + startLocations.set(step.block, step.startLocations); + BasicBlock block = program.basicBlockAt(step.block); + InstructionLocation location = step.location; + boolean started = false; + for (Instruction insn : block.getInstructions()) { + if (insn.getLocation() != null) { + if (!started) { + step.startLocations.add(location); + } + started = true; + if (blockLocations[step.block] == null) { + blockLocations[step.block] = insn.getLocation(); + } + if (location != null && !Objects.equals(location, insn.getLocation())) { + addEdge(location, insn.getLocation()); + } + location = insn.getLocation(); + } + } + if (graph.outgoingEdgesCount(step.block) == 0) { + addEdge(location, new InstructionLocation(null, -1)); + } else { + for (int next : graph.outgoingEdges(step.block)) { + stack.push(new Step(location, started ? new HashSet() : step.startLocations, + next)); + } + } + } + } + + private Map assemble() { + for (AdditionalConnection additionalConn : additionalConnections) { + for (InstructionLocation succ : additionalConn.successors) { + addEdge(additionalConn.location, succ); + } + } + Map locationGraph = new HashMap<>(); + for (Map.Entry> entry : graphBuilder.entrySet()) { + InstructionLocation[] successors = entry.getValue().toArray(new InstructionLocation[0]); + for (int i = 0; i < successors.length; ++i) { + if (successors[i].getLine() < 0) { + successors[i] = null; + } + } + locationGraph.put(entry.getKey(), successors); + } + return locationGraph; + } + + private void addEdge(InstructionLocation source, InstructionLocation dest) { + Set successors = graphBuilder.get(source); + if (successors == null) { + successors = new HashSet<>(); + graphBuilder.put(source, successors); + } + successors.add(dest); + } + + static class Step { + InstructionLocation location; + Set startLocations; + int block; + public Step(InstructionLocation location, Set startLocations, int block) { + this.location = location; + this.startLocations = startLocations; + this.block = block; + } + } + + static class AdditionalConnection { + InstructionLocation location; + Set successors; + public AdditionalConnection(InstructionLocation location, Set successors) { + this.location = location; + this.successors = successors; + } + } +} diff --git a/teavm-core/src/main/java/org/teavm/model/util/ProgramUtils.java b/teavm-core/src/main/java/org/teavm/model/util/ProgramUtils.java index 3d31047ff..6acc9c20c 100644 --- a/teavm-core/src/main/java/org/teavm/model/util/ProgramUtils.java +++ b/teavm-core/src/main/java/org/teavm/model/util/ProgramUtils.java @@ -69,64 +69,7 @@ public final class ProgramUtils { } public static Map getLocationCFG(Program program) { - Graph graph = buildControlFlowGraph(program); - class Step { - InstructionLocation location; - int block; - } - Deque stack = new ArrayDeque<>(); - for (int i = 0; i < graph.size(); ++i) { - if (graph.incomingEdgesCount(i) == 0) { - Step step = new Step(); - step.block = i; - step.location = null; - stack.push(step); - } - } - boolean[] visited = new boolean[graph.size()]; - Map> locationGraphBuilder = new HashMap<>(); - while (!stack.isEmpty()) { - Step step = stack.pop(); - if (visited[step.block]) { - continue; - } - visited[step.block] = true; - BasicBlock block = program.basicBlockAt(step.block); - InstructionLocation location = step.location; - for (Instruction insn : block.getInstructions()) { - if (insn.getLocation() != null) { - if (location != null) { - Set successors = locationGraphBuilder.get(location); - if (successors == null) { - successors = new HashSet<>(); - locationGraphBuilder.put(location, successors); - } - successors.add(insn.getLocation()); - } - location = insn.getLocation(); - } - } - if (graph.outgoingEdgesCount(step.block) == 0) { - Set successors = locationGraphBuilder.get(location); - if (successors == null) { - successors = new HashSet<>(); - locationGraphBuilder.put(location, successors); - } - successors.add(new InstructionLocation(null, -1)); - } else { - for (int next : graph.outgoingEdges(step.block)) { - step = new Step(); - step.location = location; - step.block = next; - stack.push(step); - } - } - } - Map locationGraph = new HashMap<>(); - for (Map.Entry> entry : locationGraphBuilder.entrySet()) { - locationGraph.put(entry.getKey(), entry.getValue().toArray(new InstructionLocation[0])); - } - return locationGraph; + return new LocationGraphBuilder().build(program); } public static Program copy(ProgramReader program) { diff --git a/teavm-core/src/main/java/org/teavm/vm/TeaVM.java b/teavm-core/src/main/java/org/teavm/vm/TeaVM.java index e0d597275..70adbaa68 100644 --- a/teavm-core/src/main/java/org/teavm/vm/TeaVM.java +++ b/teavm-core/src/main/java/org/teavm/vm/TeaVM.java @@ -21,6 +21,7 @@ import org.teavm.codegen.*; import org.teavm.common.FiniteExecutor; import org.teavm.common.ServiceRepository; import org.teavm.debugging.DebugInformationEmitter; +import org.teavm.debugging.SourceLocation; import org.teavm.dependency.*; import org.teavm.javascript.Decompiler; import org.teavm.javascript.Renderer; @@ -349,6 +350,14 @@ public class TeaVM implements TeaVMHost, ServiceRepository { SourceWriter sourceWriter = builder.build(writer); Renderer renderer = new Renderer(sourceWriter, classSet, classLoader, this); if (debugEmitter != null) { + for (String className : classSet.getClassNames()) { + ClassHolder cls = classSet.get(className); + for (MethodHolder method : cls.getMethods()) { + if (method.getProgram() != null) { + emitCFG(debugEmitter, method.getProgram()); + } + } + } renderer.setDebugEmitter(debugEmitter); } renderer.getDebugEmitter().setLocationProvider(sourceWriter); @@ -387,6 +396,25 @@ public class TeaVM implements TeaVMHost, ServiceRepository { } } + private void emitCFG(DebugInformationEmitter emitter, Program program) { + Map cfg = ProgramUtils.getLocationCFG(program); + for (Map.Entry entry : cfg.entrySet()) { + SourceLocation location = map(entry.getKey()); + SourceLocation[] successors = new SourceLocation[entry.getValue().length]; + for (int i = 0; i < entry.getValue().length; ++i) { + successors[i] = map(entry.getValue()[i]); + } + emitter.addSuccessors(location, successors); + } + } + + private static SourceLocation map(InstructionLocation location) { + if (location == null) { + return null; + } + return new SourceLocation(location.getFileName(), location.getLine()); + } + private void devirtualize(ListableClassHolderSource classes, DependencyInfo dependency) { final Devirtualization devirtualization = new Devirtualization(dependency, classes); for (String className : classes.getClassNames()) {