mirror of
https://github.com/Eaglercraft-TeaVM-Fork/eagler-teavm.git
synced 2024-12-22 08:14:09 -08:00
Wasm: working on step-in and step-over in debugger
This commit is contained in:
parent
159db29757
commit
a6add26aaa
|
@ -51,7 +51,7 @@ public class ControlFlowInfo {
|
||||||
for (int i = 0; i < functions.size(); ++i) {
|
for (int i = 0; i < functions.size(); ++i) {
|
||||||
var range = functions.get(i);
|
var range = functions.get(i);
|
||||||
out.println("Range #" + i + ": [" + range.start() + ".." + range.end() + ")");
|
out.println("Range #" + i + ": [" + range.start() + ".." + range.end() + ")");
|
||||||
for (var iter = range.iterator(); iter.hasNext(); iter.next()) {
|
for (var iter = range.iterator(0); iter.hasNext(); iter.next()) {
|
||||||
out.print(" " + Integer.toHexString(iter.address()));
|
out.print(" " + Integer.toHexString(iter.address()));
|
||||||
if (iter.isCall()) {
|
if (iter.isCall()) {
|
||||||
out.print(" (call)");
|
out.print(" (call)");
|
||||||
|
|
|
@ -21,7 +21,9 @@ public class FunctionControlFlow {
|
||||||
int[] offsets;
|
int[] offsets;
|
||||||
int[] data;
|
int[] data;
|
||||||
|
|
||||||
FunctionControlFlow(int[] offsets, int[] data) {
|
FunctionControlFlow(int start, int end, int[] offsets, int[] data) {
|
||||||
|
this.start = start;
|
||||||
|
this.end = end;
|
||||||
this.offsets = offsets;
|
this.offsets = offsets;
|
||||||
this.data = data;
|
this.data = data;
|
||||||
}
|
}
|
||||||
|
@ -34,7 +36,33 @@ public class FunctionControlFlow {
|
||||||
return end;
|
return end;
|
||||||
}
|
}
|
||||||
|
|
||||||
public FunctionControlFlowIterator iterator() {
|
public FunctionControlFlowIterator iterator(int index) {
|
||||||
return new FunctionControlFlowIterator(this);
|
return new FunctionControlFlowIterator(this, index);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int count() {
|
||||||
|
return offsets.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int findIndex(int address) {
|
||||||
|
var l = 0;
|
||||||
|
var u = offsets.length;
|
||||||
|
while (true) {
|
||||||
|
var i = (l + u) / 2;
|
||||||
|
var t = data[offsets[i]];
|
||||||
|
if (address == t) {
|
||||||
|
return i;
|
||||||
|
} else if (address > t) {
|
||||||
|
l = i + 1;
|
||||||
|
if (l > u) {
|
||||||
|
return i + 1;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
u = i - 1;
|
||||||
|
if (u < l) {
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,9 +18,16 @@ package org.teavm.backend.wasm.debug.info;
|
||||||
import com.carrotsearch.hppc.IntArrayList;
|
import com.carrotsearch.hppc.IntArrayList;
|
||||||
|
|
||||||
public class FunctionControlFlowBuilder {
|
public class FunctionControlFlowBuilder {
|
||||||
|
private int start;
|
||||||
|
private int end;
|
||||||
private IntArrayList offsets = new IntArrayList();
|
private IntArrayList offsets = new IntArrayList();
|
||||||
private IntArrayList data = new IntArrayList();
|
private IntArrayList data = new IntArrayList();
|
||||||
|
|
||||||
|
public FunctionControlFlowBuilder(int start, int end) {
|
||||||
|
this.start = start;
|
||||||
|
this.end = end;
|
||||||
|
}
|
||||||
|
|
||||||
public void addBranch(int position, int[] targets) {
|
public void addBranch(int position, int[] targets) {
|
||||||
offsets.add(data.size() << 1);
|
offsets.add(data.size() << 1);
|
||||||
data.add(position);
|
data.add(position);
|
||||||
|
@ -38,6 +45,6 @@ public class FunctionControlFlowBuilder {
|
||||||
}
|
}
|
||||||
|
|
||||||
public FunctionControlFlow build() {
|
public FunctionControlFlow build() {
|
||||||
return new FunctionControlFlow(offsets.toArray(), data.toArray());
|
return new FunctionControlFlow(start, end, offsets.toArray(), data.toArray());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,8 +24,9 @@ public class FunctionControlFlowIterator {
|
||||||
private int offset;
|
private int offset;
|
||||||
private boolean isCall;
|
private boolean isCall;
|
||||||
|
|
||||||
FunctionControlFlowIterator(FunctionControlFlow controlFlow) {
|
FunctionControlFlowIterator(FunctionControlFlow controlFlow, int index) {
|
||||||
this.controlFlow = controlFlow;
|
this.controlFlow = controlFlow;
|
||||||
|
this.index = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean hasNext() {
|
public boolean hasNext() {
|
||||||
|
@ -37,6 +38,10 @@ public class FunctionControlFlowIterator {
|
||||||
valid = false;
|
valid = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void rewind(int index) {
|
||||||
|
this.index = index;
|
||||||
|
}
|
||||||
|
|
||||||
private void fill() {
|
private void fill() {
|
||||||
if (!valid) {
|
if (!valid) {
|
||||||
valid = true;
|
valid = true;
|
||||||
|
|
|
@ -49,13 +49,18 @@ public class LineInfoUnpackedSequence {
|
||||||
}
|
}
|
||||||
|
|
||||||
public InstructionLocation find(int address) {
|
public InstructionLocation find(int address) {
|
||||||
|
var index = findIndex(address);
|
||||||
|
return index >= 0 ? locations.get(index) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int findIndex(int address) {
|
||||||
if (address < startAddress || address >= endAddress) {
|
if (address < startAddress || address >= endAddress) {
|
||||||
return null;
|
return -1;
|
||||||
}
|
}
|
||||||
var index = CollectionUtil.binarySearch(locations, address, InstructionLocation::address);
|
var index = CollectionUtil.binarySearch(locations, address, InstructionLocation::address);
|
||||||
if (index < 0) {
|
if (index < 0) {
|
||||||
index = -index;
|
index = -index;
|
||||||
}
|
}
|
||||||
return locations.get(Math.min(locations.size() - 1, index));
|
return Math.min(locations.size() - 1, index);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,223 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2022 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.backend.wasm.debug.info;
|
||||||
|
|
||||||
|
import com.carrotsearch.hppc.IntArrayDeque;
|
||||||
|
import com.carrotsearch.hppc.IntHashSet;
|
||||||
|
import com.carrotsearch.hppc.IntSet;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
import org.teavm.backend.wasm.debug.parser.DebugInfoParser;
|
||||||
|
import org.teavm.common.ByteArrayAsyncInputStream;
|
||||||
|
import org.teavm.common.CollectionUtil;
|
||||||
|
|
||||||
|
public class StepLocationsFinder {
|
||||||
|
public final DebugInfo debugInfo;
|
||||||
|
private LineInfoSequence lines;
|
||||||
|
private FunctionControlFlow graph;
|
||||||
|
private List<Point> points;
|
||||||
|
private IntArrayDeque queue = new IntArrayDeque();
|
||||||
|
private String currentFileName;
|
||||||
|
private int currentLine;
|
||||||
|
private boolean enterMethod;
|
||||||
|
private IntSet breakpointAddresses = new IntHashSet();
|
||||||
|
private IntSet callAddresses = new IntHashSet();
|
||||||
|
private IntSet visited = new IntHashSet();
|
||||||
|
|
||||||
|
public StepLocationsFinder(DebugInfo debugInfo) {
|
||||||
|
this.debugInfo = debugInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean step(String fileName, int line, int address, boolean enterMethod) {
|
||||||
|
address -= debugInfo.offset();
|
||||||
|
updatePoints(address);
|
||||||
|
if (points == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var index = CollectionUtil.binarySearch(points, address, p -> p.address);
|
||||||
|
if (index < 0) {
|
||||||
|
index = -index - 2;
|
||||||
|
}
|
||||||
|
if (index < 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.enterMethod = enterMethod;
|
||||||
|
currentFileName = fileName;
|
||||||
|
currentLine = line;
|
||||||
|
queue.addLast(index);
|
||||||
|
callAddresses.clear();
|
||||||
|
breakpointAddresses.clear();
|
||||||
|
while (!queue.isEmpty()) {
|
||||||
|
processTask();
|
||||||
|
}
|
||||||
|
visited.clear();
|
||||||
|
currentFileName = null;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int[] getBreakpointAddresses() {
|
||||||
|
return breakpointAddresses.toArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int[] getCallAddresses() {
|
||||||
|
return callAddresses.toArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updatePoints(int address) {
|
||||||
|
var lines = debugInfo.lines().find(address);
|
||||||
|
var graph = debugInfo.controlFlow().find(address);
|
||||||
|
if (lines == null || graph == null) {
|
||||||
|
this.lines = null;
|
||||||
|
this.graph = null;
|
||||||
|
points = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lines != this.lines || graph != this.graph) {
|
||||||
|
this.lines = lines;
|
||||||
|
this.graph = graph;
|
||||||
|
points = null;
|
||||||
|
}
|
||||||
|
if (points == null) {
|
||||||
|
points = createPoints();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<Point> createPoints() {
|
||||||
|
var list = new ArrayList<Point>();
|
||||||
|
var indexInLines = 0;
|
||||||
|
var graphIter = graph.iterator(0);
|
||||||
|
var commandExecutor = new LineInfoCommandExecutor();
|
||||||
|
|
||||||
|
while (indexInLines < lines.commands().size() && graphIter.hasNext()) {
|
||||||
|
boolean nextInGraph;
|
||||||
|
if (graphIter == null) {
|
||||||
|
nextInGraph = false;
|
||||||
|
} else if (indexInLines >= lines.commands().size()) {
|
||||||
|
nextInGraph = true;
|
||||||
|
} else {
|
||||||
|
nextInGraph = lines.commands().get(indexInLines).address() >= graphIter.address();
|
||||||
|
}
|
||||||
|
if (nextInGraph) {
|
||||||
|
var point = new Point(graphIter.address());
|
||||||
|
list.add(point);
|
||||||
|
point.isCall = graphIter.isCall();
|
||||||
|
point.next = graphIter.targets();
|
||||||
|
graphIter.next();
|
||||||
|
} else {
|
||||||
|
var cmd = lines.commands().get(indexInLines++);
|
||||||
|
cmd.acceptVisitor(commandExecutor);
|
||||||
|
var location = commandExecutor.createLocation();
|
||||||
|
if (location != null && location.location() != null) {
|
||||||
|
Point point;
|
||||||
|
if (!list.isEmpty() && list.get(list.size() - 1).address == cmd.address()) {
|
||||||
|
point = list.get(list.size() - 1);
|
||||||
|
} else {
|
||||||
|
point = new Point(cmd.address());
|
||||||
|
list.add(point);
|
||||||
|
}
|
||||||
|
point.location = location.location();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var point : list) {
|
||||||
|
var next = point.next;
|
||||||
|
if (next == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
int j = 0;
|
||||||
|
for (int i = 0; i < next.length; ++i) {
|
||||||
|
var foundIndex = CollectionUtil.binarySearch(list, next[i], p -> p.address);
|
||||||
|
if (foundIndex < 0) {
|
||||||
|
foundIndex = -foundIndex - 1;
|
||||||
|
}
|
||||||
|
if (foundIndex >= list.size()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
next[j++] = foundIndex;
|
||||||
|
}
|
||||||
|
if (j != next.length) {
|
||||||
|
if (j == 0) {
|
||||||
|
point.next = null;
|
||||||
|
} else {
|
||||||
|
point.next = Arrays.copyOf(next, j);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
list.trimToSize();
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void processTask() {
|
||||||
|
var index = queue.removeFirst();
|
||||||
|
if (!visited.add(index)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var point = points.get(index);
|
||||||
|
if (point.location != null && !isCurrent(point.location)) {
|
||||||
|
breakpointAddresses.add(point.address + debugInfo.offset());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (enterMethod) {
|
||||||
|
breakpointAddresses.add(point.address + debugInfo.offset());
|
||||||
|
callAddresses.add(point.address + debugInfo.offset());
|
||||||
|
}
|
||||||
|
if (point.next != null) {
|
||||||
|
for (var nextIndex : point.next) {
|
||||||
|
queue.addLast(nextIndex);
|
||||||
|
}
|
||||||
|
} else if (index < points.size() - 1) {
|
||||||
|
queue.addLast(index + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isCurrent(Location loc) {
|
||||||
|
return loc.line() == currentLine && loc.file().fullName().equals(currentFileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class Point {
|
||||||
|
int address;
|
||||||
|
int[] next;
|
||||||
|
boolean isCall;
|
||||||
|
Location location;
|
||||||
|
|
||||||
|
Point(int address) {
|
||||||
|
this.address = address;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(String[] args) throws IOException {
|
||||||
|
var file = new File("/home/konsoletyper/prog/apache-tomcat-10.0.4/webapps/wasm/classes.wasm");
|
||||||
|
var input = new ByteArrayAsyncInputStream(Files.readAllBytes(file.toPath()));
|
||||||
|
var parser = new DebugInfoParser(input);
|
||||||
|
input.readFully(parser::parse);
|
||||||
|
var debugInfo = parser.getDebugInfo();
|
||||||
|
|
||||||
|
var finder = new StepLocationsFinder(debugInfo);
|
||||||
|
finder.step("org/teavm/samples/wasi/WasiTest2.java", 9, 0x943e + debugInfo.offset(), false);
|
||||||
|
System.out.println(Arrays.toString(finder.getBreakpointAddresses()));
|
||||||
|
}
|
||||||
|
}
|
|
@ -31,7 +31,7 @@ import org.teavm.backend.wasm.parser.Opcode;
|
||||||
public class ControlFlowParser implements CodeSectionListener, CodeListener, AddressListener {
|
public class ControlFlowParser implements CodeSectionListener, CodeListener, AddressListener {
|
||||||
private int previousAddress;
|
private int previousAddress;
|
||||||
private int address;
|
private int address;
|
||||||
private FunctionControlFlowBuilder cfb;
|
private int startAddress;
|
||||||
private List<Branch> branches = new ArrayList<>();
|
private List<Branch> branches = new ArrayList<>();
|
||||||
private List<FunctionControlFlow> ranges = new ArrayList<>();
|
private List<FunctionControlFlow> ranges = new ArrayList<>();
|
||||||
private List<Branch> pendingBranches = new ArrayList<>();
|
private List<Branch> pendingBranches = new ArrayList<>();
|
||||||
|
@ -50,7 +50,7 @@ public class ControlFlowParser implements CodeSectionListener, CodeListener, Add
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean functionStart(int index, int size) {
|
public boolean functionStart(int index, int size) {
|
||||||
cfb = new FunctionControlFlowBuilder();
|
startAddress = address;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -71,7 +71,7 @@ public class ControlFlowParser implements CodeSectionListener, CodeListener, Add
|
||||||
|
|
||||||
private int startBlock(boolean loop) {
|
private int startBlock(boolean loop) {
|
||||||
var token = blocks.size();
|
var token = blocks.size();
|
||||||
var branch = !loop ? newBranch(false) : null;
|
var branch = !loop ? newPendingBranch(false) : null;
|
||||||
var block = new Block(branch, address);
|
var block = new Block(branch, address);
|
||||||
blocks.add(block);
|
blocks.add(block);
|
||||||
if (branch != null) {
|
if (branch != null) {
|
||||||
|
@ -169,6 +169,7 @@ public class ControlFlowParser implements CodeSectionListener, CodeListener, Add
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void functionEnd() {
|
public void functionEnd() {
|
||||||
|
var cfb = new FunctionControlFlowBuilder(startAddress, address);
|
||||||
for (var branch : branches) {
|
for (var branch : branches) {
|
||||||
if (branch.isCall) {
|
if (branch.isCall) {
|
||||||
cfb.addCall(branch.address, branch.targets.toArray());
|
cfb.addCall(branch.address, branch.targets.toArray());
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
*/
|
*/
|
||||||
package org.teavm.debugging;
|
package org.teavm.debugging;
|
||||||
|
|
||||||
|
import com.carrotsearch.hppc.IntHashSet;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Base64;
|
import java.util.Base64;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
@ -25,9 +26,11 @@ import java.util.LinkedHashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.function.Predicate;
|
||||||
import org.teavm.backend.wasm.debug.info.DebugInfo;
|
import org.teavm.backend.wasm.debug.info.DebugInfo;
|
||||||
import org.teavm.backend.wasm.debug.info.LineInfoFileCommand;
|
import org.teavm.backend.wasm.debug.info.LineInfoFileCommand;
|
||||||
import org.teavm.backend.wasm.debug.info.MethodInfo;
|
import org.teavm.backend.wasm.debug.info.MethodInfo;
|
||||||
|
import org.teavm.backend.wasm.debug.info.StepLocationsFinder;
|
||||||
import org.teavm.backend.wasm.debug.parser.DebugInfoParser;
|
import org.teavm.backend.wasm.debug.parser.DebugInfoParser;
|
||||||
import org.teavm.common.ByteArrayAsyncInputStream;
|
import org.teavm.common.ByteArrayAsyncInputStream;
|
||||||
import org.teavm.common.Promise;
|
import org.teavm.common.Promise;
|
||||||
|
@ -53,6 +56,7 @@ public class Debugger {
|
||||||
private JavaScriptDebugger javaScriptDebugger;
|
private JavaScriptDebugger javaScriptDebugger;
|
||||||
private DebugInformationProvider debugInformationProvider;
|
private DebugInformationProvider debugInformationProvider;
|
||||||
private List<JavaScriptBreakpoint> temporaryBreakpoints = new ArrayList<>();
|
private List<JavaScriptBreakpoint> temporaryBreakpoints = new ArrayList<>();
|
||||||
|
private Predicate<JavaScriptBreakpoint> temporaryBreakpointHandler;
|
||||||
private Map<JavaScriptScript, DebugInformation> debugInformationMap = new HashMap<>();
|
private Map<JavaScriptScript, DebugInformation> debugInformationMap = new HashMap<>();
|
||||||
private Map<String, Set<DebugInformation>> debugInformationFileMap = new HashMap<>();
|
private Map<String, Set<DebugInformation>> debugInformationFileMap = new HashMap<>();
|
||||||
private Map<JavaScriptScript, DebugInfo> wasmDebugInfoMap = new HashMap<>();
|
private Map<JavaScriptScript, DebugInfo> wasmDebugInfoMap = new HashMap<>();
|
||||||
|
@ -65,6 +69,7 @@ public class Debugger {
|
||||||
private CallFrame[] callStack;
|
private CallFrame[] callStack;
|
||||||
private Set<String> scriptNames = new LinkedHashSet<>();
|
private Set<String> scriptNames = new LinkedHashSet<>();
|
||||||
private Set<String> allSourceFiles = new LinkedHashSet<>();
|
private Set<String> allSourceFiles = new LinkedHashSet<>();
|
||||||
|
private StepLocationsFinder wasmStepLocationsFinder;
|
||||||
|
|
||||||
public Debugger(JavaScriptDebugger javaScriptDebugger, DebugInformationProvider debugInformationProvider) {
|
public Debugger(JavaScriptDebugger javaScriptDebugger, DebugInformationProvider debugInformationProvider) {
|
||||||
this.javaScriptDebugger = javaScriptDebugger;
|
this.javaScriptDebugger = javaScriptDebugger;
|
||||||
|
@ -113,70 +118,91 @@ public class Debugger {
|
||||||
if (callStack == null || callStack.length == 0) {
|
if (callStack == null || callStack.length == 0) {
|
||||||
return jsStep(enterMethod);
|
return jsStep(enterMethod);
|
||||||
}
|
}
|
||||||
var recentFrame = callStack[0];
|
var frame = callStack[0];
|
||||||
if (recentFrame.getLocation() == null || recentFrame.getLocation().getFileName() == null
|
if (frame.getLocation() == null || frame.getLocation().getFileName() == null
|
||||||
|| recentFrame.getLocation().getLine() < 0) {
|
|| frame.getLocation().getLine() < 0) {
|
||||||
return jsStep(enterMethod);
|
return jsStep(enterMethod);
|
||||||
}
|
}
|
||||||
var successors = new HashSet<JavaScriptLocation>();
|
var successors = new HashSet<JavaScriptLocation>();
|
||||||
boolean first = true;
|
var script = frame.getOriginalLocation().getScript();
|
||||||
loop:
|
var hasSuccessors = false;
|
||||||
for (var frame : callStack) {
|
if (frame.getLocation() != null && frame.getLocation().getFileName() != null
|
||||||
var script = frame.getOriginalLocation().getScript();
|
&& frame.getLocation().getLine() >= 0) {
|
||||||
switch (script.getLanguage()) {
|
switch (script.getLanguage()) {
|
||||||
case JS:
|
case JS:
|
||||||
if (!addJsBreakpoints(frame, script, enterMethod, first, successors)) {
|
hasSuccessors = addJsBreakpoints(frame, script, enterMethod, successors);
|
||||||
break loop;
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
case WASM: {
|
case WASM: {
|
||||||
var info = wasmDebugInfoMap.get(script);
|
var promise = stepWasm(frame, enterMethod);
|
||||||
if (info != null) {
|
if (promise != null) {
|
||||||
return enterMethod ? javaScriptDebugger.stepInto() : javaScriptDebugger.stepOver();
|
return promise;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case UNKNOWN:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
enterMethod = false;
|
|
||||||
first = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var jsBreakpointPromises = new ArrayList<Promise<Void>>();
|
if (hasSuccessors) {
|
||||||
for (var successor : successors) {
|
return jsStep(enterMethod);
|
||||||
jsBreakpointPromises.add(javaScriptDebugger.createBreakpoint(successor)
|
} else {
|
||||||
.thenVoid(temporaryBreakpoints::add));
|
return createTemporaryBreakpoints(successors, null).thenAsync(v -> javaScriptDebugger.stepOut());
|
||||||
}
|
}
|
||||||
return Promise.allVoid(jsBreakpointPromises).thenAsync(v -> javaScriptDebugger.resume());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean addJsBreakpoints(CallFrame frame, JavaScriptScript script, boolean enterMethod, boolean first,
|
private Promise<Void> createTemporaryBreakpoints(Collection<JavaScriptLocation> locations,
|
||||||
|
Predicate<JavaScriptBreakpoint> handler) {
|
||||||
|
var jsBreakpointPromises = new ArrayList<Promise<Void>>();
|
||||||
|
for (var location : locations) {
|
||||||
|
jsBreakpointPromises.add(javaScriptDebugger.createBreakpoint(location)
|
||||||
|
.thenVoid(temporaryBreakpoints::add));
|
||||||
|
}
|
||||||
|
temporaryBreakpointHandler = handler;
|
||||||
|
return Promise.allVoid(jsBreakpointPromises);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean addJsBreakpoints(CallFrame frame, JavaScriptScript script, boolean enterMethod,
|
||||||
Set<JavaScriptLocation> successors) {
|
Set<JavaScriptLocation> successors) {
|
||||||
var debugInfo = debugInformationMap.get(script);
|
var debugInfo = debugInformationMap.get(script);
|
||||||
boolean exits;
|
if (debugInfo == null) {
|
||||||
if (frame.getLocation() != null && frame.getLocation().getFileName() != null
|
|
||||||
&& frame.getLocation().getLine() >= 0 && debugInfo != null) {
|
|
||||||
exits = addFollowing(debugInfo, frame.getLocation(), script, new HashSet<>(), successors);
|
|
||||||
if (enterMethod) {
|
|
||||||
var successorFinder = new CallSiteSuccessorFinder(debugInfo, script, successors);
|
|
||||||
var callSites = debugInfo.getCallSites(frame.getLocation());
|
|
||||||
for (var callSite : callSites) {
|
|
||||||
callSite.acceptVisitor(successorFinder);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
exits = true;
|
|
||||||
}
|
|
||||||
if (!exits) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (!first && frame.getLocation() != null) {
|
addFollowing(debugInfo, frame.getLocation(), script, new HashSet<>(), successors);
|
||||||
for (var location : debugInfo.getGeneratedLocations(frame.getLocation())) {
|
if (enterMethod) {
|
||||||
successors.add(new JavaScriptLocation(script, location.getLine(), location.getColumn()));
|
var successorFinder = new CallSiteSuccessorFinder(debugInfo, script, successors);
|
||||||
|
var callSites = debugInfo.getCallSites(frame.getLocation());
|
||||||
|
for (var callSite : callSites) {
|
||||||
|
callSite.acceptVisitor(successorFinder);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Promise<Void> stepWasm(CallFrame frame, boolean enterMethod) {
|
||||||
|
var debugInfo = wasmDebugInfoMap.get(frame.getOriginalLocation().getScript());
|
||||||
|
if (debugInfo == null || debugInfo.controlFlow() == null || debugInfo.lines() == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (wasmStepLocationsFinder == null || wasmStepLocationsFinder.debugInfo != debugInfo) {
|
||||||
|
wasmStepLocationsFinder = new StepLocationsFinder(debugInfo);
|
||||||
|
}
|
||||||
|
wasmStepLocationsFinder.step(frame.getLocation().getFileName(), frame.getLocation().getLine(),
|
||||||
|
frame.getOriginalLocation().getColumn(), enterMethod);
|
||||||
|
|
||||||
|
var locations = new ArrayList<JavaScriptLocation>();
|
||||||
|
for (var breakpointAddress : wasmStepLocationsFinder.getBreakpointAddresses()) {
|
||||||
|
locations.add(new JavaScriptLocation(frame.getOriginalLocation().getScript(), 0, breakpointAddress));
|
||||||
|
}
|
||||||
|
var callAddresses = IntHashSet.from(wasmStepLocationsFinder.getCallAddresses());
|
||||||
|
var result = createTemporaryBreakpoints(locations, br -> {
|
||||||
|
if (br != null && br.isValid() && callAddresses.contains(br.getLocation().getColumn())) {
|
||||||
|
javaScriptDebugger.stepInto();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
return result.thenVoid(x -> javaScriptDebugger.stepOut());
|
||||||
}
|
}
|
||||||
|
|
||||||
static class CallSiteSuccessorFinder implements DebuggerCallSiteVisitor {
|
static class CallSiteSuccessorFinder implements DebuggerCallSiteVisitor {
|
||||||
|
@ -599,20 +625,29 @@ public class Debugger {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void firePaused(JavaScriptBreakpoint breakpoint) {
|
private void firePaused(JavaScriptBreakpoint breakpoint) {
|
||||||
List<JavaScriptBreakpoint> temporaryBreakpoints = new ArrayList<>(this.temporaryBreakpoints);
|
var temporaryBreakpoints = new ArrayList<>(this.temporaryBreakpoints);
|
||||||
|
var handler = temporaryBreakpointHandler;
|
||||||
this.temporaryBreakpoints.clear();
|
this.temporaryBreakpoints.clear();
|
||||||
List<Promise<Void>> promises = new ArrayList<>();
|
temporaryBreakpointHandler = null;
|
||||||
for (JavaScriptBreakpoint jsBreakpoint : temporaryBreakpoints) {
|
var promises = new ArrayList<Promise<Void>>();
|
||||||
|
for (var jsBreakpoint : temporaryBreakpoints) {
|
||||||
promises.add(jsBreakpoint.destroy());
|
promises.add(jsBreakpoint.destroy());
|
||||||
}
|
}
|
||||||
callStack = null;
|
callStack = null;
|
||||||
Promise.allVoid(promises).thenVoid(v -> {
|
Promise.allVoid(promises).thenVoid(v -> {
|
||||||
Breakpoint javaBreakpoint = null;
|
Breakpoint javaBreakpoint = null;
|
||||||
if (breakpoint != null && !temporaryBreakpoints.contains(breakpoint)) {
|
JavaScriptBreakpoint tmpBreakpoint = null;
|
||||||
javaBreakpoint = breakpointMap.get(breakpoint);
|
if (breakpoint != null) {
|
||||||
|
if (temporaryBreakpoints.contains(breakpoint)) {
|
||||||
|
tmpBreakpoint = breakpoint;
|
||||||
|
} else {
|
||||||
|
javaBreakpoint = breakpointMap.get(breakpoint);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
for (DebuggerListener listener : getListeners()) {
|
if (handler == null || !handler.test(tmpBreakpoint)) {
|
||||||
listener.paused(javaBreakpoint);
|
for (var listener : getListeners()) {
|
||||||
|
listener.paused(javaBreakpoint);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user