mirror of
https://github.com/Eaglercraft-TeaVM-Fork/eagler-teavm.git
synced 2024-12-22 08:14:09 -08:00
Wasm: working on Chrome RDP debugger
This commit is contained in:
parent
9d3927e196
commit
a2715f2c79
|
@ -22,14 +22,20 @@ import java.util.List;
|
|||
import org.teavm.common.CollectionUtil;
|
||||
|
||||
public class LineInfo {
|
||||
private int offset;
|
||||
private LineInfoSequence[] sequences;
|
||||
private List<? extends LineInfoSequence> sequenceList;
|
||||
|
||||
public LineInfo(LineInfoSequence[] sequences) {
|
||||
public LineInfo(int offset, LineInfoSequence[] sequences) {
|
||||
this.offset = offset;
|
||||
this.sequences = sequences.clone();
|
||||
sequenceList = Collections.unmodifiableList(Arrays.asList(this.sequences));
|
||||
}
|
||||
|
||||
public int offset() {
|
||||
return offset;
|
||||
}
|
||||
|
||||
public List<? extends LineInfoSequence> sequences() {
|
||||
return sequenceList;
|
||||
}
|
||||
|
|
|
@ -93,6 +93,9 @@ public class DebugInfoParser {
|
|||
if (sectionCode == 0) {
|
||||
return parseSection(pos + sectionSize);
|
||||
} else {
|
||||
if (sectionCode == 3) {
|
||||
lines.setOffset(pos);
|
||||
}
|
||||
return skip(sectionSize, "Error skipping section " + sectionCode + " of size " + sectionSize);
|
||||
}
|
||||
});
|
||||
|
|
|
@ -42,6 +42,7 @@ public class DebugLinesParser extends DebugSectionParser {
|
|||
private int address;
|
||||
private MethodInfo currentMethod;
|
||||
private int sequenceStartAddress;
|
||||
private int offset;
|
||||
|
||||
public DebugLinesParser(
|
||||
DebugFileParser files,
|
||||
|
@ -56,8 +57,13 @@ public class DebugLinesParser extends DebugSectionParser {
|
|||
return lineInfo;
|
||||
}
|
||||
|
||||
public void setOffset(int offset) {
|
||||
this.offset = offset;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doParse() {
|
||||
address = offset;
|
||||
while (ptr < data.length) {
|
||||
var cmd = data[ptr++] & 0xFF;
|
||||
switch (cmd) {
|
||||
|
@ -85,7 +91,7 @@ public class DebugLinesParser extends DebugSectionParser {
|
|||
break;
|
||||
}
|
||||
}
|
||||
lineInfo = new LineInfo(sequences.toArray(new LineInfoSequence[0]));
|
||||
lineInfo = new LineInfo(offset, sequences.toArray(new LineInfoSequence[0]));
|
||||
sequences = null;
|
||||
commands = null;
|
||||
stateStack = null;
|
||||
|
|
|
@ -35,7 +35,7 @@ public final class CollectionUtil {
|
|||
while (true) {
|
||||
var i = (l + u) / 2;
|
||||
var t = keyExtractor.apply(list.get(i));
|
||||
var cmp = comparator.compare(t, key);
|
||||
var cmp = comparator.compare(key, t);
|
||||
if (cmp == 0) {
|
||||
return i;
|
||||
} else if (cmp > 0) {
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
*/
|
||||
package org.teavm.debugging;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import org.teavm.common.Promise;
|
||||
import org.teavm.debugging.information.DebugInformation;
|
||||
|
@ -62,7 +63,11 @@ public class CallFrame {
|
|||
|
||||
public Promise<Map<String, Variable>> getVariables() {
|
||||
if (variables == null) {
|
||||
variables = debugger.createVariables(originalCallFrame, debugInformation);
|
||||
if (debugInformation != null) {
|
||||
variables = debugger.createVariables(originalCallFrame, debugInformation);
|
||||
} else {
|
||||
variables = Promise.of(Collections.emptyMap());
|
||||
}
|
||||
}
|
||||
return variables;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,76 @@
|
|||
/*
|
||||
* 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.debugging;
|
||||
|
||||
import org.teavm.backend.wasm.debug.parser.DebugInfoParser;
|
||||
import org.teavm.backend.wasm.debug.parser.DebugInfoReader;
|
||||
import org.teavm.common.CompletablePromise;
|
||||
import org.teavm.common.Promise;
|
||||
|
||||
class DebugInfoReaderImpl implements DebugInfoReader {
|
||||
private byte[] data;
|
||||
private int ptr;
|
||||
private CompletablePromise<Integer> promise;
|
||||
private byte[] target;
|
||||
private int offset;
|
||||
private int count;
|
||||
|
||||
DebugInfoReaderImpl(byte[] data) {
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
DebugInfoParser read() {
|
||||
var debugInfoParser = new DebugInfoParser(this);
|
||||
Promise.runNow(() -> {
|
||||
debugInfoParser.parse().catchVoid(Throwable::printStackTrace);
|
||||
complete();
|
||||
});
|
||||
return debugInfoParser;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Promise<Integer> skip(int amount) {
|
||||
promise = new CompletablePromise<>();
|
||||
count = amount;
|
||||
return promise;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Promise<Integer> read(byte[] buffer, int offset, int count) {
|
||||
promise = new CompletablePromise<>();
|
||||
this.target = buffer;
|
||||
this.offset = offset;
|
||||
this.count = count;
|
||||
return promise;
|
||||
}
|
||||
|
||||
private void complete() {
|
||||
while (promise != null) {
|
||||
var p = promise;
|
||||
count = Math.min(count, data.length - ptr);
|
||||
promise = null;
|
||||
if (target != null) {
|
||||
System.arraycopy(data, ptr, target, offset, count);
|
||||
target = null;
|
||||
}
|
||||
ptr += count;
|
||||
if (count == 0) {
|
||||
count = -1;
|
||||
}
|
||||
p.complete(count);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -16,6 +16,7 @@
|
|||
package org.teavm.debugging;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Base64;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
|
@ -24,10 +25,12 @@ import java.util.LinkedHashSet;
|
|||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import org.teavm.backend.wasm.debug.info.LineInfo;
|
||||
import org.teavm.backend.wasm.debug.info.LineInfoFileCommand;
|
||||
import org.teavm.backend.wasm.debug.info.MethodInfo;
|
||||
import org.teavm.common.Promise;
|
||||
import org.teavm.debugging.information.DebugInformation;
|
||||
import org.teavm.debugging.information.DebugInformationProvider;
|
||||
import org.teavm.debugging.information.DebuggerCallSite;
|
||||
import org.teavm.debugging.information.DebuggerCallSiteVisitor;
|
||||
import org.teavm.debugging.information.DebuggerStaticCallSite;
|
||||
import org.teavm.debugging.information.DebuggerVirtualCallSite;
|
||||
|
@ -38,22 +41,28 @@ import org.teavm.debugging.javascript.JavaScriptCallFrame;
|
|||
import org.teavm.debugging.javascript.JavaScriptDebugger;
|
||||
import org.teavm.debugging.javascript.JavaScriptDebuggerListener;
|
||||
import org.teavm.debugging.javascript.JavaScriptLocation;
|
||||
import org.teavm.debugging.javascript.JavaScriptScript;
|
||||
import org.teavm.debugging.javascript.JavaScriptVariable;
|
||||
import org.teavm.model.MethodReference;
|
||||
import org.teavm.model.ValueType;
|
||||
|
||||
public class Debugger {
|
||||
private Set<DebuggerListener> listeners = new LinkedHashSet<>();
|
||||
private JavaScriptDebugger javaScriptDebugger;
|
||||
private DebugInformationProvider debugInformationProvider;
|
||||
private List<JavaScriptBreakpoint> temporaryBreakpoints = new ArrayList<>();
|
||||
private Map<String, DebugInformation> debugInformationMap = new HashMap<>();
|
||||
private Map<JavaScriptScript, DebugInformation> debugInformationMap = new HashMap<>();
|
||||
private Map<String, Set<DebugInformation>> debugInformationFileMap = new HashMap<>();
|
||||
private Map<DebugInformation, String> scriptMap = new HashMap<>();
|
||||
private Map<JavaScriptScript, LineInfo> wasmLineInfoMap = new HashMap<>();
|
||||
private Map<LineInfo, JavaScriptScript> wasmScriptMap = new HashMap<>();
|
||||
private Map<String, Set<LineInfo>> wasmInfoFileMap = new HashMap<>();
|
||||
private Map<DebugInformation, JavaScriptScript> scriptMap = new HashMap<>();
|
||||
private Map<JavaScriptBreakpoint, Breakpoint> breakpointMap = new HashMap<>();
|
||||
private Set<Breakpoint> breakpoints = new LinkedHashSet<>();
|
||||
private Set<? extends Breakpoint> readonlyBreakpoints = Collections.unmodifiableSet(breakpoints);
|
||||
private CallFrame[] callStack;
|
||||
private Set<String> scriptNames = new LinkedHashSet<>();
|
||||
private Set<String> allSourceFiles = new LinkedHashSet<>();
|
||||
|
||||
public Debugger(JavaScriptDebugger javaScriptDebugger, DebugInformationProvider debugInformationProvider) {
|
||||
this.javaScriptDebugger = javaScriptDebugger;
|
||||
|
@ -98,61 +107,81 @@ public class Debugger {
|
|||
}
|
||||
|
||||
private Promise<Void> step(boolean enterMethod) {
|
||||
CallFrame[] callStack = getCallStack();
|
||||
var callStack = getCallStack();
|
||||
if (callStack == null || callStack.length == 0) {
|
||||
return jsStep(enterMethod);
|
||||
}
|
||||
CallFrame recentFrame = callStack[0];
|
||||
var recentFrame = callStack[0];
|
||||
if (recentFrame.getLocation() == null || recentFrame.getLocation().getFileName() == null
|
||||
|| recentFrame.getLocation().getLine() < 0) {
|
||||
return jsStep(enterMethod);
|
||||
}
|
||||
Set<JavaScriptLocation> successors = new HashSet<>();
|
||||
var successors = new HashSet<JavaScriptLocation>();
|
||||
boolean first = true;
|
||||
for (CallFrame frame : callStack) {
|
||||
boolean exits;
|
||||
String script = frame.getOriginalLocation().getScript();
|
||||
DebugInformation debugInfo = debugInformationMap.get(script);
|
||||
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) {
|
||||
CallSiteSuccessorFinder successorFinder = new CallSiteSuccessorFinder(debugInfo, script,
|
||||
successors);
|
||||
DebuggerCallSite[] callSites = debugInfo.getCallSites(frame.getLocation());
|
||||
for (DebuggerCallSite callSite : callSites) {
|
||||
callSite.acceptVisitor(successorFinder);
|
||||
loop:
|
||||
for (var frame : callStack) {
|
||||
var script = frame.getOriginalLocation().getScript();
|
||||
switch (script.getLanguage()) {
|
||||
case JS:
|
||||
if (!addJsBreakpoints(frame, script, enterMethod, first, successors)) {
|
||||
break loop;
|
||||
}
|
||||
break;
|
||||
case WASM: {
|
||||
var info = wasmLineInfoMap.get(script);
|
||||
if (info != null) {
|
||||
return enterMethod ? javaScriptDebugger.stepInto() : javaScriptDebugger.stepOver();
|
||||
}
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
exits = true;
|
||||
}
|
||||
if (!exits) {
|
||||
break;
|
||||
}
|
||||
enterMethod = false;
|
||||
if (!first && frame.getLocation() != null) {
|
||||
for (GeneratedLocation location : debugInfo.getGeneratedLocations(frame.getLocation())) {
|
||||
successors.add(new JavaScriptLocation(script, location.getLine(), location.getColumn()));
|
||||
}
|
||||
}
|
||||
first = false;
|
||||
}
|
||||
|
||||
List<Promise<Void>> jsBreakpointPromises = new ArrayList<>();
|
||||
for (JavaScriptLocation successor : successors) {
|
||||
var jsBreakpointPromises = new ArrayList<Promise<Void>>();
|
||||
for (var successor : successors) {
|
||||
jsBreakpointPromises.add(javaScriptDebugger.createBreakpoint(successor)
|
||||
.thenVoid(temporaryBreakpoints::add));
|
||||
}
|
||||
return Promise.allVoid(jsBreakpointPromises).thenAsync(v -> javaScriptDebugger.resume());
|
||||
}
|
||||
|
||||
private boolean addJsBreakpoints(CallFrame frame, JavaScriptScript script, boolean enterMethod, boolean first,
|
||||
Set<JavaScriptLocation> successors) {
|
||||
var debugInfo = debugInformationMap.get(script);
|
||||
boolean exits;
|
||||
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;
|
||||
}
|
||||
if (!first && frame.getLocation() != null) {
|
||||
for (var location : debugInfo.getGeneratedLocations(frame.getLocation())) {
|
||||
successors.add(new JavaScriptLocation(script, location.getLine(), location.getColumn()));
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static class CallSiteSuccessorFinder implements DebuggerCallSiteVisitor {
|
||||
private DebugInformation debugInfo;
|
||||
private String script;
|
||||
private JavaScriptScript script;
|
||||
Set<JavaScriptLocation> locations;
|
||||
|
||||
CallSiteSuccessorFinder(DebugInformation debugInfo, String script, Set<JavaScriptLocation> locations) {
|
||||
CallSiteSuccessorFinder(DebugInformation debugInfo, JavaScriptScript script,
|
||||
Set<JavaScriptLocation> locations) {
|
||||
this.debugInfo = debugInfo;
|
||||
this.script = script;
|
||||
this.locations = locations;
|
||||
|
@ -177,7 +206,7 @@ public class Debugger {
|
|||
}
|
||||
}
|
||||
|
||||
private boolean addFollowing(DebugInformation debugInfo, SourceLocation location, String script,
|
||||
private boolean addFollowing(DebugInformation debugInfo, SourceLocation location, JavaScriptScript script,
|
||||
Set<SourceLocation> visited, Set<JavaScriptLocation> successors) {
|
||||
if (!visited.add(location)) {
|
||||
return false;
|
||||
|
@ -205,7 +234,12 @@ public class Debugger {
|
|||
}
|
||||
|
||||
private List<DebugInformation> debugInformationBySource(String sourceFile) {
|
||||
Set<DebugInformation> list = debugInformationFileMap.get(sourceFile);
|
||||
var list = debugInformationFileMap.get(sourceFile);
|
||||
return list != null ? new ArrayList<>(list) : Collections.emptyList();
|
||||
}
|
||||
|
||||
private List<LineInfo> wasmLineInfoBySource(String sourceFile) {
|
||||
var list = wasmInfoFileMap.get(sourceFile);
|
||||
return list != null ? new ArrayList<>(list) : Collections.emptyList();
|
||||
}
|
||||
|
||||
|
@ -222,7 +256,7 @@ public class Debugger {
|
|||
for (DebugInformation debugInformation : debugInformationBySource(fileName)) {
|
||||
Collection<GeneratedLocation> locations = debugInformation.getGeneratedLocations(fileName, line);
|
||||
for (GeneratedLocation location : locations) {
|
||||
JavaScriptLocation jsLocation = new JavaScriptLocation(scriptMap.get(debugInformation),
|
||||
var jsLocation = new JavaScriptLocation(scriptMap.get(debugInformation),
|
||||
location.getLine(), location.getColumn());
|
||||
promises.add(javaScriptDebugger.createBreakpoint(jsLocation).thenVoid(temporaryBreakpoints::add));
|
||||
}
|
||||
|
@ -239,11 +273,11 @@ public class Debugger {
|
|||
}
|
||||
|
||||
public Collection<? extends String> getSourceFiles() {
|
||||
return debugInformationFileMap.keySet();
|
||||
return allSourceFiles;
|
||||
}
|
||||
|
||||
public Promise<Breakpoint> createBreakpoint(SourceLocation location) {
|
||||
Breakpoint breakpoint = new Breakpoint(this, location);
|
||||
var breakpoint = new Breakpoint(this, location);
|
||||
breakpoints.add(breakpoint);
|
||||
return updateInternalBreakpoints(breakpoint).then(v -> {
|
||||
updateBreakpointStatus(breakpoint, false);
|
||||
|
@ -260,18 +294,18 @@ public class Debugger {
|
|||
return Promise.VOID;
|
||||
}
|
||||
|
||||
List<Promise<Void>> promises = new ArrayList<>();
|
||||
for (JavaScriptBreakpoint jsBreakpoint : breakpoint.jsBreakpoints) {
|
||||
var promises = new ArrayList<Promise<Void>>();
|
||||
for (var jsBreakpoint : breakpoint.jsBreakpoints) {
|
||||
breakpointMap.remove(jsBreakpoint);
|
||||
promises.add(jsBreakpoint.destroy());
|
||||
}
|
||||
|
||||
List<JavaScriptBreakpoint> jsBreakpoints = new ArrayList<>();
|
||||
SourceLocation location = breakpoint.getLocation();
|
||||
for (DebugInformation debugInformation : debugInformationBySource(location.getFileName())) {
|
||||
Collection<GeneratedLocation> locations = debugInformation.getGeneratedLocations(location);
|
||||
for (GeneratedLocation genLocation : locations) {
|
||||
JavaScriptLocation jsLocation = new JavaScriptLocation(scriptMap.get(debugInformation),
|
||||
var jsBreakpoints = new ArrayList<JavaScriptBreakpoint>();
|
||||
var location = breakpoint.getLocation();
|
||||
for (var debugInformation : debugInformationBySource(location.getFileName())) {
|
||||
var locations = debugInformation.getGeneratedLocations(location);
|
||||
for (var genLocation : locations) {
|
||||
var jsLocation = new JavaScriptLocation(scriptMap.get(debugInformation),
|
||||
genLocation.getLine(), genLocation.getColumn());
|
||||
promises.add(javaScriptDebugger.createBreakpoint(jsLocation).thenVoid(jsBreakpoint -> {
|
||||
jsBreakpoints.add(jsBreakpoint);
|
||||
|
@ -279,11 +313,27 @@ public class Debugger {
|
|||
}));
|
||||
}
|
||||
}
|
||||
for (var wasmLineInfo : wasmLineInfoBySource(location.getFileName())) {
|
||||
for (var sequence : wasmLineInfo.sequences()) {
|
||||
for (var loc : sequence.unpack().locations()) {
|
||||
if (loc.location().line() == location.getLine()
|
||||
&& loc.location().file().fullName().equals(location.getFileName())) {
|
||||
var jsLocation = new JavaScriptLocation(wasmScriptMap.get(wasmLineInfo),
|
||||
0, loc.address());
|
||||
promises.add(javaScriptDebugger.createBreakpoint(jsLocation).thenVoid(jsBreakpoint -> {
|
||||
jsBreakpoints.add(jsBreakpoint);
|
||||
breakpointMap.put(jsBreakpoint, breakpoint);
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
breakpoint.jsBreakpoints = jsBreakpoints;
|
||||
|
||||
return Promise.allVoid(promises);
|
||||
}
|
||||
|
||||
|
||||
private DebuggerListener[] getListeners() {
|
||||
return listeners.toArray(new DebuggerListener[0]);
|
||||
}
|
||||
|
@ -312,32 +362,98 @@ public class Debugger {
|
|||
if (callStack == null) {
|
||||
// TODO: with inlining enabled we can have several JVM methods compiled into one JavaScript function
|
||||
// so we must consider this case.
|
||||
List<CallFrame> frames = new ArrayList<>();
|
||||
var frames = new ArrayList<CallFrame>();
|
||||
boolean wasEmpty = false;
|
||||
for (JavaScriptCallFrame jsFrame : javaScriptDebugger.getCallStack()) {
|
||||
DebugInformation debugInformation = debugInformationMap.get(jsFrame.getLocation().getScript());
|
||||
SourceLocation loc;
|
||||
if (debugInformation != null) {
|
||||
loc = debugInformation.getSourceLocation(jsFrame.getLocation().getLine(),
|
||||
jsFrame.getLocation().getColumn());
|
||||
} else {
|
||||
loc = null;
|
||||
for (var jsFrame : javaScriptDebugger.getCallStack()) {
|
||||
List<SourceLocationWithMethod> locations;
|
||||
DebugInformation debugInformation = null;
|
||||
switch (jsFrame.getLocation().getScript().getLanguage()) {
|
||||
case JS:
|
||||
debugInformation = debugInformationMap.get(jsFrame.getLocation().getScript());
|
||||
locations = mapJsFrames(jsFrame, debugInformation);
|
||||
break;
|
||||
case WASM:
|
||||
locations = mapWasmFrames(jsFrame);
|
||||
break;
|
||||
default:
|
||||
locations = Collections.emptyList();
|
||||
break;
|
||||
}
|
||||
boolean empty = loc == null || (loc.getFileName() == null && loc.getLine() < 0);
|
||||
MethodReference method = !empty && debugInformation != null
|
||||
? debugInformation.getMethodAt(jsFrame.getLocation().getLine(),
|
||||
jsFrame.getLocation().getColumn())
|
||||
: null;
|
||||
if (!empty || !wasEmpty) {
|
||||
frames.add(new CallFrame(this, jsFrame, loc, method, debugInformation));
|
||||
for (var locWithMethod : locations) {
|
||||
var loc = locWithMethod.loc;
|
||||
var method = locWithMethod.method;
|
||||
if (!locWithMethod.empty || !wasEmpty) {
|
||||
frames.add(new CallFrame(this, jsFrame, loc, method, debugInformation));
|
||||
}
|
||||
wasEmpty = locWithMethod.empty;
|
||||
}
|
||||
wasEmpty = empty;
|
||||
}
|
||||
callStack = frames.toArray(new CallFrame[0]);
|
||||
}
|
||||
return callStack.clone();
|
||||
}
|
||||
|
||||
private static class SourceLocationWithMethod {
|
||||
private final boolean empty;
|
||||
private final SourceLocation loc;
|
||||
private final MethodReference method;
|
||||
|
||||
SourceLocationWithMethod(boolean empty, SourceLocation loc, MethodReference method) {
|
||||
this.empty = empty;
|
||||
this.loc = loc;
|
||||
this.method = method;
|
||||
}
|
||||
}
|
||||
|
||||
private List<SourceLocationWithMethod> mapJsFrames(JavaScriptCallFrame frame,
|
||||
DebugInformation debugInformation) {
|
||||
SourceLocation loc;
|
||||
if (debugInformation != null) {
|
||||
loc = debugInformation.getSourceLocation(frame.getLocation().getLine(),
|
||||
frame.getLocation().getColumn());
|
||||
} else {
|
||||
loc = null;
|
||||
}
|
||||
boolean empty = loc == null || (loc.getFileName() == null && loc.getLine() < 0);
|
||||
var method = !empty && debugInformation != null
|
||||
? debugInformation.getMethodAt(frame.getLocation().getLine(), frame.getLocation().getColumn())
|
||||
: null;
|
||||
return Collections.singletonList(new SourceLocationWithMethod(empty, loc, method));
|
||||
}
|
||||
|
||||
private List<SourceLocationWithMethod> mapWasmFrames(JavaScriptCallFrame frame) {
|
||||
var lineInfo = wasmLineInfoMap.get(frame.getLocation().getScript());
|
||||
if (lineInfo == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
var sequence = lineInfo.find(frame.getLocation().getColumn());
|
||||
if (sequence == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
var instructionLocation = sequence.unpack().find(frame.getLocation().getColumn());
|
||||
if (instructionLocation == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
var location = instructionLocation.location();
|
||||
var result = new ArrayList<SourceLocationWithMethod>();
|
||||
while (true) {
|
||||
var loc = new SourceLocation(location.file().fullName(), location.line());
|
||||
var inlining = location.inlining();
|
||||
var method = inlining != null ? inlining.method() : sequence.method();
|
||||
result.add(new SourceLocationWithMethod(false, loc, getMethodReference(method)));
|
||||
if (inlining == null) {
|
||||
break;
|
||||
}
|
||||
location = inlining.location();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private MethodReference getMethodReference(MethodInfo methodInfo) {
|
||||
return new MethodReference(methodInfo.cls().fullName(), methodInfo.name(), ValueType.VOID);
|
||||
}
|
||||
|
||||
Promise<Map<String, Variable>> createVariables(JavaScriptCallFrame jsFrame, DebugInformation debugInformation) {
|
||||
return jsFrame.getVariables().then(jsVariables -> {
|
||||
Map<String, Variable> vars = new HashMap<>();
|
||||
|
@ -356,29 +472,72 @@ public class Debugger {
|
|||
});
|
||||
}
|
||||
|
||||
private void addScript(String name) {
|
||||
if (!name.isEmpty()) {
|
||||
scriptNames.add(name);
|
||||
private void addScript(JavaScriptScript script) {
|
||||
Promise<Void> promise;
|
||||
switch (script.getLanguage()) {
|
||||
case JS:
|
||||
promise = addJavaScriptScript(script);
|
||||
break;
|
||||
case WASM:
|
||||
promise = addWasmScript(script);
|
||||
break;
|
||||
default:
|
||||
promise = Promise.VOID;
|
||||
break;
|
||||
}
|
||||
if (debugInformationMap.containsKey(name)) {
|
||||
updateBreakpoints();
|
||||
return;
|
||||
}
|
||||
DebugInformation debugInfo = debugInformationProvider.getDebugInformation(name);
|
||||
promise.thenVoid(v -> updateBreakpoints());
|
||||
}
|
||||
|
||||
private Promise<Void> addJavaScriptScript(JavaScriptScript script) {
|
||||
var debugInfo = debugInformationProvider.getDebugInformation(script.getUrl());
|
||||
if (debugInfo == null) {
|
||||
return;
|
||||
return Promise.VOID;
|
||||
}
|
||||
debugInformationMap.put(name, debugInfo);
|
||||
for (String sourceFile : debugInfo.getFilesNames()) {
|
||||
Set<DebugInformation> list = debugInformationFileMap.get(sourceFile);
|
||||
debugInformationMap.put(script, debugInfo);
|
||||
for (var sourceFile : debugInfo.getFilesNames()) {
|
||||
var list = debugInformationFileMap.get(sourceFile);
|
||||
if (list == null) {
|
||||
list = new HashSet<>();
|
||||
debugInformationFileMap.put(sourceFile, list);
|
||||
allSourceFiles.add(sourceFile);
|
||||
}
|
||||
list.add(debugInfo);
|
||||
}
|
||||
scriptMap.put(debugInfo, name);
|
||||
updateBreakpoints();
|
||||
scriptMap.put(debugInfo, script);
|
||||
return Promise.VOID;
|
||||
}
|
||||
|
||||
private Promise<Void> addWasmScript(JavaScriptScript script) {
|
||||
return script.getSource().thenVoid(source -> {
|
||||
if (source == null) {
|
||||
return;
|
||||
}
|
||||
var decoder = Base64.getDecoder();
|
||||
var reader = new DebugInfoReaderImpl(decoder.decode(source));
|
||||
var parser = reader.read();
|
||||
if (parser.getLineInfo() != null) {
|
||||
wasmLineInfoMap.put(script, parser.getLineInfo());
|
||||
wasmScriptMap.put(parser.getLineInfo(), script);
|
||||
for (var sequence : parser.getLineInfo().sequences()) {
|
||||
for (var command : sequence.commands()) {
|
||||
if (command instanceof LineInfoFileCommand) {
|
||||
addWasmInfoFile(((LineInfoFileCommand) command).file().fullName(),
|
||||
parser.getLineInfo());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void addWasmInfoFile(String sourceFile, LineInfo wasmLineInfo) {
|
||||
var list = wasmInfoFileMap.get(sourceFile);
|
||||
if (list == null) {
|
||||
list = new HashSet<>();
|
||||
wasmInfoFileMap.put(sourceFile, list);
|
||||
}
|
||||
list.add(wasmLineInfo);
|
||||
allSourceFiles.add(sourceFile);
|
||||
}
|
||||
|
||||
public Set<? extends String> getScriptNames() {
|
||||
|
@ -488,8 +647,8 @@ public class Debugger {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void scriptAdded(String name) {
|
||||
addScript(name);
|
||||
public void scriptAdded(JavaScriptScript script) {
|
||||
addScript(script);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
*/
|
||||
package org.teavm.debugging.javascript;
|
||||
|
||||
import java.util.Map;
|
||||
import org.teavm.common.Promise;
|
||||
|
||||
public interface JavaScriptDebugger {
|
||||
|
@ -32,8 +33,6 @@ public interface JavaScriptDebugger {
|
|||
|
||||
Promise<Void> stepOver();
|
||||
|
||||
Promise<Void> continueToLocation(JavaScriptLocation location);
|
||||
|
||||
boolean isSuspended();
|
||||
|
||||
boolean isAttached();
|
||||
|
@ -43,4 +42,6 @@ public interface JavaScriptDebugger {
|
|||
JavaScriptCallFrame[] getCallStack();
|
||||
|
||||
Promise<JavaScriptBreakpoint> createBreakpoint(JavaScriptLocation location);
|
||||
|
||||
Map<? extends String, ? extends JavaScriptScript> getScripts();
|
||||
}
|
||||
|
|
|
@ -26,5 +26,5 @@ public interface JavaScriptDebuggerListener {
|
|||
|
||||
void breakpointChanged(JavaScriptBreakpoint breakpoint);
|
||||
|
||||
void scriptAdded(String name);
|
||||
void scriptAdded(JavaScriptScript script);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
/*
|
||||
* 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.debugging.javascript;
|
||||
|
||||
public enum JavaScriptLanguage {
|
||||
JS,
|
||||
WASM,
|
||||
UNKNOWN
|
||||
}
|
|
@ -18,17 +18,17 @@ package org.teavm.debugging.javascript;
|
|||
import java.util.Objects;
|
||||
|
||||
public class JavaScriptLocation {
|
||||
private String script;
|
||||
private JavaScriptScript script;
|
||||
private int line;
|
||||
private int column;
|
||||
|
||||
public JavaScriptLocation(String script, int line, int column) {
|
||||
public JavaScriptLocation(JavaScriptScript script, int line, int column) {
|
||||
this.script = script;
|
||||
this.line = line;
|
||||
this.column = column;
|
||||
}
|
||||
|
||||
public String getScript() {
|
||||
public JavaScriptScript getScript() {
|
||||
return script;
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
* 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.debugging.javascript;
|
||||
|
||||
import org.teavm.common.Promise;
|
||||
|
||||
public interface JavaScriptScript {
|
||||
String getId();
|
||||
|
||||
JavaScriptLanguage getLanguage();
|
||||
|
||||
String getUrl();
|
||||
|
||||
Promise<String> getSource();
|
||||
}
|
|
@ -169,6 +169,8 @@ public abstract class BaseChromeRDPDebugger implements ChromeRDPExchangeConsumer
|
|||
message.setMethod(method);
|
||||
if (params != null) {
|
||||
message.setParams(mapper.valueToTree(params));
|
||||
} else {
|
||||
message.setParams(mapper.createObjectNode());
|
||||
}
|
||||
|
||||
sendMessage(message);
|
||||
|
|
|
@ -21,6 +21,7 @@ import java.util.ArrayList;
|
|||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
@ -37,12 +38,13 @@ import org.teavm.chromerdp.messages.CallFunctionCommand;
|
|||
import org.teavm.chromerdp.messages.CallFunctionResponse;
|
||||
import org.teavm.chromerdp.messages.CompileScriptCommand;
|
||||
import org.teavm.chromerdp.messages.CompileScriptResponse;
|
||||
import org.teavm.chromerdp.messages.ContinueToLocationCommand;
|
||||
import org.teavm.chromerdp.messages.GetPropertiesCommand;
|
||||
import org.teavm.chromerdp.messages.GetPropertiesResponse;
|
||||
import org.teavm.chromerdp.messages.GetScriptSourceCommand;
|
||||
import org.teavm.chromerdp.messages.RemoveBreakpointCommand;
|
||||
import org.teavm.chromerdp.messages.RunScriptCommand;
|
||||
import org.teavm.chromerdp.messages.ScriptParsedNotification;
|
||||
import org.teavm.chromerdp.messages.ScriptSource;
|
||||
import org.teavm.chromerdp.messages.SetBreakpointCommand;
|
||||
import org.teavm.chromerdp.messages.SetBreakpointResponse;
|
||||
import org.teavm.chromerdp.messages.SuspendedNotification;
|
||||
|
@ -52,7 +54,9 @@ import org.teavm.debugging.javascript.JavaScriptBreakpoint;
|
|||
import org.teavm.debugging.javascript.JavaScriptCallFrame;
|
||||
import org.teavm.debugging.javascript.JavaScriptDebugger;
|
||||
import org.teavm.debugging.javascript.JavaScriptDebuggerListener;
|
||||
import org.teavm.debugging.javascript.JavaScriptLanguage;
|
||||
import org.teavm.debugging.javascript.JavaScriptLocation;
|
||||
import org.teavm.debugging.javascript.JavaScriptScript;
|
||||
import org.teavm.debugging.javascript.JavaScriptVariable;
|
||||
|
||||
public class ChromeRDPDebugger extends BaseChromeRDPDebugger implements JavaScriptDebugger {
|
||||
|
@ -62,8 +66,8 @@ public class ChromeRDPDebugger extends BaseChromeRDPDebugger implements JavaScri
|
|||
private Set<RDPBreakpoint> breakpoints = new LinkedHashSet<>();
|
||||
private Map<String, RDPNativeBreakpoint> breakpointsByChromeId = new HashMap<>();
|
||||
private volatile RDPCallFrame[] callStack = new RDPCallFrame[0];
|
||||
private Map<String, String> scripts = new HashMap<>();
|
||||
private Map<String, String> scriptIds = new HashMap<>();
|
||||
private Map<String, ChromeRDPScript> scripts = new LinkedHashMap<>();
|
||||
private Map<String, JavaScriptScript> readonlyScripts = Collections.unmodifiableMap(scripts);
|
||||
private volatile boolean suspended;
|
||||
private Promise<Void> runtimeEnabledPromise;
|
||||
|
||||
|
@ -162,17 +166,37 @@ public class ChromeRDPDebugger extends BaseChromeRDPDebugger implements JavaScri
|
|||
if (params.getUrl() == null) {
|
||||
return Promise.VOID;
|
||||
}
|
||||
if (scripts.putIfAbsent(params.getScriptId(), params.getUrl()) != null) {
|
||||
return Promise.VOID;
|
||||
var language = JavaScriptLanguage.JS;
|
||||
if (params.getScriptLanguage() != null) {
|
||||
switch (params.getScriptLanguage()) {
|
||||
case "WebAssembly":
|
||||
language = JavaScriptLanguage.WASM;
|
||||
break;
|
||||
case "JavaScript":
|
||||
language = JavaScriptLanguage.JS;
|
||||
break;
|
||||
default:
|
||||
language = JavaScriptLanguage.UNKNOWN;
|
||||
break;
|
||||
}
|
||||
}
|
||||
var script = new ChromeRDPScript(this, params.getScriptId(), language, params.getUrl());
|
||||
scripts.put(script.getId(), script);
|
||||
if (params.getUrl().equals("file://fake")) {
|
||||
return Promise.VOID;
|
||||
}
|
||||
scriptIds.put(params.getUrl(), params.getScriptId());
|
||||
for (JavaScriptDebuggerListener listener : getListeners()) {
|
||||
listener.scriptAdded(params.getUrl());
|
||||
for (var listener : getListeners()) {
|
||||
listener.scriptAdded(script);
|
||||
}
|
||||
return injectFunctions(params.getExecutionContextId());
|
||||
if (language == JavaScriptLanguage.JS) {
|
||||
return injectFunctions(params.getExecutionContextId());
|
||||
}
|
||||
return Promise.VOID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<? extends String, ? extends JavaScriptScript> getScripts() {
|
||||
return readonlyScripts;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -210,13 +234,6 @@ public class ChromeRDPDebugger extends BaseChromeRDPDebugger implements JavaScri
|
|||
return callMethodAsync("Debugger.stepOver", void.class, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Promise<Void> continueToLocation(JavaScriptLocation location) {
|
||||
ContinueToLocationCommand params = new ContinueToLocationCommand();
|
||||
params.setLocation(unmap(location));
|
||||
return callMethodAsync("Debugger.continueToLocation", void.class, params);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSuspended() {
|
||||
return isAttached() && suspended;
|
||||
|
@ -492,8 +509,8 @@ public class ChromeRDPDebugger extends BaseChromeRDPDebugger implements JavaScri
|
|||
}
|
||||
|
||||
private LocationDTO unmap(JavaScriptLocation location) {
|
||||
LocationDTO dto = new LocationDTO();
|
||||
dto.setScriptId(scriptIds.get(location.getScript()));
|
||||
var dto = new LocationDTO();
|
||||
dto.setScriptId(location.getScript().getId());
|
||||
dto.setLineNumber(location.getLine());
|
||||
dto.setColumnNumber(location.getColumn());
|
||||
return dto;
|
||||
|
@ -511,4 +528,11 @@ public class ChromeRDPDebugger extends BaseChromeRDPDebugger implements JavaScri
|
|||
return Collections.unmodifiableMap(newBackingMap);
|
||||
});
|
||||
}
|
||||
|
||||
Promise<String> getScriptSource(String id) {
|
||||
var callArgs = new GetScriptSourceCommand();
|
||||
callArgs.scriptId = id;
|
||||
return callMethodAsync("Debugger.getScriptSource", ScriptSource.class, callArgs)
|
||||
.then(source -> source.bytecode);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -35,6 +35,7 @@ import org.teavm.debugging.DebuggerListener;
|
|||
import org.teavm.debugging.Variable;
|
||||
import org.teavm.debugging.information.URLDebugInformationProvider;
|
||||
import org.teavm.debugging.javascript.JavaScriptLocation;
|
||||
import org.teavm.debugging.javascript.JavaScriptScript;
|
||||
import org.teavm.debugging.javascript.JavaScriptVariable;
|
||||
|
||||
public final class ChromeRDPRunner {
|
||||
|
@ -261,7 +262,7 @@ public final class ChromeRDPRunner {
|
|||
};
|
||||
|
||||
private Promise<Void> tryResolveJsBreakpoint(String fileName, int lineNumber, int columnNumber) {
|
||||
String[] fileNames = resolveJsFileName(fileName);
|
||||
var fileNames = resolveJsFileName(fileName);
|
||||
if (fileNames.length == 0) {
|
||||
System.out.println("Unknown file: " + fileName);
|
||||
return Promise.VOID;
|
||||
|
@ -277,28 +278,8 @@ public final class ChromeRDPRunner {
|
|||
});
|
||||
}
|
||||
|
||||
private String[] resolveJsFileName(String fileName) {
|
||||
if (debugger.getScriptNames().contains(fileName)) {
|
||||
return new String[] { fileName };
|
||||
}
|
||||
|
||||
String[] result = debugger.getScriptNames().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 JavaScriptScript[] resolveJsFileName(String fileName) {
|
||||
return new JavaScriptScript[0];
|
||||
}
|
||||
|
||||
private String[] resolveFileName(String fileName) {
|
||||
|
|
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
* 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.chromerdp;
|
||||
|
||||
import org.teavm.common.Promise;
|
||||
import org.teavm.debugging.javascript.JavaScriptLanguage;
|
||||
import org.teavm.debugging.javascript.JavaScriptScript;
|
||||
|
||||
class ChromeRDPScript implements JavaScriptScript {
|
||||
private ChromeRDPDebugger debugger;
|
||||
private String id;
|
||||
private JavaScriptLanguage language;
|
||||
private String url;
|
||||
|
||||
ChromeRDPScript(ChromeRDPDebugger debugger, String id, JavaScriptLanguage language, String url) {
|
||||
this.debugger = debugger;
|
||||
this.id = id;
|
||||
this.language = language;
|
||||
this.url = url;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JavaScriptLanguage getLanguage() {
|
||||
return language;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUrl() {
|
||||
return url;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Promise<String> getSource() {
|
||||
return debugger.getScriptSource(id);
|
||||
}
|
||||
}
|
|
@ -1,127 +0,0 @@
|
|||
/*
|
||||
* 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.chromerdp;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Base64;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.Executor;
|
||||
import org.teavm.backend.wasm.debug.parser.DebugInfoParser;
|
||||
import org.teavm.backend.wasm.debug.parser.DebugInfoReader;
|
||||
import org.teavm.chromerdp.data.Message;
|
||||
import org.teavm.chromerdp.messages.GetScriptSourceCommand;
|
||||
import org.teavm.chromerdp.messages.ScriptParsedNotification;
|
||||
import org.teavm.chromerdp.messages.ScriptSource;
|
||||
import org.teavm.common.CompletablePromise;
|
||||
import org.teavm.common.Promise;
|
||||
|
||||
public class WasmChromeRDPDebugger extends BaseChromeRDPDebugger {
|
||||
public WasmChromeRDPDebugger(Executor executor) {
|
||||
super(executor);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onAttach() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDetach() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Promise<Void> handleMessage(Message message) throws IOException {
|
||||
switch (message.getMethod()) {
|
||||
case "Debugger.scriptParsed":
|
||||
return scriptParsed(parseJson(ScriptParsedNotification.class, message.getParams()));
|
||||
}
|
||||
return Promise.VOID;
|
||||
}
|
||||
|
||||
private Promise<Void> scriptParsed(ScriptParsedNotification params) {
|
||||
if (Objects.equals(params.getScriptLanguage(), "WebAssembly")) {
|
||||
var callArgs = new GetScriptSourceCommand();
|
||||
callArgs.scriptId = params.getScriptId();
|
||||
return callMethodAsync("Debugger.getScriptSource", ScriptSource.class, callArgs)
|
||||
.thenVoid(source -> parseWasm(source, params.getUrl()));
|
||||
}
|
||||
return Promise.VOID;
|
||||
}
|
||||
|
||||
private void parseWasm(ScriptSource source, String url) {
|
||||
if (source.bytecode == null) {
|
||||
return;
|
||||
}
|
||||
var bytes = Base64.getDecoder().decode(source.bytecode);
|
||||
var reader = new DebugInfoReaderImpl(bytes);
|
||||
var debugInfoParser = new DebugInfoParser(reader);
|
||||
Promise.runNow(() -> {
|
||||
debugInfoParser.parse().catchVoid(Throwable::printStackTrace);
|
||||
reader.complete();
|
||||
});
|
||||
var lineInfo = debugInfoParser.getLineInfo();
|
||||
if (lineInfo != null) {
|
||||
System.out.println("Debug information found in script: " + url);
|
||||
}
|
||||
}
|
||||
|
||||
private static class DebugInfoReaderImpl implements DebugInfoReader {
|
||||
private byte[] data;
|
||||
private int ptr;
|
||||
private CompletablePromise<Integer> promise;
|
||||
private byte[] target;
|
||||
private int offset;
|
||||
private int count;
|
||||
|
||||
DebugInfoReaderImpl(byte[] data) {
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Promise<Integer> skip(int amount) {
|
||||
promise = new CompletablePromise<>();
|
||||
count = amount;
|
||||
return promise;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Promise<Integer> read(byte[] buffer, int offset, int count) {
|
||||
promise = new CompletablePromise<>();
|
||||
this.target = buffer;
|
||||
this.offset = offset;
|
||||
this.count = count;
|
||||
return promise;
|
||||
}
|
||||
|
||||
private void complete() {
|
||||
while (promise != null) {
|
||||
var p = promise;
|
||||
count = Math.min(count, data.length - ptr);
|
||||
promise = null;
|
||||
if (target != null) {
|
||||
System.arraycopy(data, ptr, target, offset, count);
|
||||
target = null;
|
||||
}
|
||||
ptr += count;
|
||||
if (count == 0) {
|
||||
count = -1;
|
||||
}
|
||||
p.complete(count);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,55 +0,0 @@
|
|||
/*
|
||||
* 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.chromerdp;
|
||||
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
|
||||
public class WasmChromeRDPRunner {
|
||||
private ChromeRDPServer server;
|
||||
BlockingQueue<Runnable> queue = new LinkedBlockingQueue<>();
|
||||
|
||||
private WasmChromeRDPRunner() {
|
||||
server = new ChromeRDPServer();
|
||||
server.setPort(2357);
|
||||
var wasmDebugger = new WasmChromeRDPDebugger(queue::offer);
|
||||
server.setExchangeConsumer(wasmDebugger);
|
||||
|
||||
new Thread(server::start).start();
|
||||
|
||||
Thread.setDefaultUncaughtExceptionHandler((t, e) -> {
|
||||
System.err.println("Uncaught exception in thread " + t);
|
||||
e.printStackTrace();
|
||||
});
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
var runner = new WasmChromeRDPRunner();
|
||||
try {
|
||||
while (true) {
|
||||
runner.run();
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
System.out.println("Interrupted");
|
||||
}
|
||||
}
|
||||
|
||||
public void run() throws InterruptedException {
|
||||
while (true) {
|
||||
queue.take().run();
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user