fix: make debugging work again

This commit is contained in:
TopchetoEU 2024-03-02 13:56:48 +02:00
parent 49dd725669
commit c8253795b2
Signed by: topchetoeu
GPG Key ID: 6531B8583E5F6ED4
12 changed files with 221 additions and 155 deletions

View File

@ -0,0 +1,23 @@
package me.topchetoeu.jscript.common;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
public class RefTracker {
public static void onDestroy(Object obj, Runnable runnable) {
var queue = new ReferenceQueue<>();
var ref = new WeakReference<>(obj, queue);
obj = null;
var th = new Thread(() -> {
try {
queue.remove();
ref.get();
runnable.run();
}
catch (InterruptedException e) { return; }
});
th.setDaemon(true);
th.start();
}
}

View File

@ -1,13 +1,17 @@
package me.topchetoeu.jscript.common.mapping; package me.topchetoeu.jscript.common.mapping;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.SortedSet; import java.util.NavigableSet;
import java.util.Objects;
import java.util.TreeMap; import java.util.TreeMap;
import java.util.TreeSet; import java.util.TreeSet;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import me.topchetoeu.jscript.common.Filename;
import me.topchetoeu.jscript.common.Location; import me.topchetoeu.jscript.common.Location;
import me.topchetoeu.jscript.common.Instruction.BreakpointType; import me.topchetoeu.jscript.common.Instruction.BreakpointType;
import me.topchetoeu.jscript.core.scope.LocalScopeRecord; import me.topchetoeu.jscript.core.scope.LocalScopeRecord;
@ -62,15 +66,15 @@ public class FunctionMap {
public static final FunctionMap EMPTY = new FunctionMap(); public static final FunctionMap EMPTY = new FunctionMap();
private final HashMap<Location, BreakpointType> bps = new HashMap<>(); private final HashMap<Integer, BreakpointType> bps = new HashMap<>();
private final TreeSet<Location> bpLocs = new TreeSet<>(); private final HashMap<Filename, TreeSet<Location>> bpLocs = new HashMap<>();
private final TreeMap<Integer, Location> pcToLoc = new TreeMap<>(); private final TreeMap<Integer, Location> pcToLoc = new TreeMap<>();
private final HashMap<Location, Integer> locToPc = new HashMap<>();
public final String[] localNames, captureNames; public final String[] localNames, captureNames;
public Location toLocation(int pc, boolean approxiamte) { public Location toLocation(int pc, boolean approxiamte) {
if (pcToLoc.size() == 0 || pc < 0 || pc > pcToLoc.lastKey()) return null;
var res = pcToLoc.get(pc); var res = pcToLoc.get(pc);
if (!approxiamte || res != null) return res; if (!approxiamte || res != null) return res;
var entry = pcToLoc.headMap(pc, true).lastEntry(); var entry = pcToLoc.headMap(pc, true).lastEntry();
@ -81,14 +85,39 @@ public class FunctionMap {
return toLocation(pc, false); return toLocation(pc, false);
} }
public BreakpointType getBreakpoint(Location loc) { public BreakpointType getBreakpoint(int pc) {
return bps.getOrDefault(loc, BreakpointType.NONE); return bps.getOrDefault(pc, BreakpointType.NONE);
} }
public Location correctBreakpoint(Location loc) { public Location correctBreakpoint(Location loc) {
return bpLocs.ceiling(loc); var set = bpLocs.get(loc.filename());
if (set == null) return null;
else return set.ceiling(loc);
} }
public SortedSet<Location> breakpoints() { public List<Location> correctBreakpoint(Pattern filename, int line, int column) {
return Collections.unmodifiableSortedSet(bpLocs); var candidates = new HashMap<Filename, TreeSet<Location>>();
for (var name : bpLocs.keySet()) {
if (filename.matcher(name.toString()).matches()) {
candidates.put(name, bpLocs.get(name));
}
}
var res = new ArrayList<Location>(candidates.size());
for (var candidate : candidates.entrySet()) {
res.add(candidate.getValue().ceiling(new Location(line, column, candidate.getKey())));
}
return res;
}
public List<Location> breakpoints(Location start, Location end) {
if (!Objects.equals(start.filename(), end.filename())) return List.of();
NavigableSet<Location> set = bpLocs.get(start.filename());
if (set == null) return List.of();
if (start != null) set = set.tailSet(start, true);
if (end != null) set = set.headSet(end, true);
return set.stream().collect(Collectors.toList());
} }
public Location start() { public Location start() {
@ -100,49 +129,51 @@ public class FunctionMap {
return pcToLoc.lastEntry().getValue(); return pcToLoc.lastEntry().getValue();
} }
public Integer toProgramCounter(Location loc) {
return locToPc.get(loc);
}
public FunctionMap apply(SourceMap map) { public FunctionMap apply(SourceMap map) {
var res = new FunctionMap(); var res = new FunctionMap(Map.of(), Map.of(), localNames, captureNames);
for (var el : new ArrayList<>(pcToLoc.entrySet())) { for (var el : pcToLoc.entrySet()) {
res.pcToLoc.put(el.getKey(), map.toCompiled(el.getValue())); res.pcToLoc.put(el.getKey(), map.toCompiled(el.getValue()));
} }
for (var el : new ArrayList<>(locToPc.entrySet())) {
var mapped = map.toOriginal(el.getKey()); res.bps.putAll(bps);
res.locToPc.put(mapped, el.getValue());
for (var el : bpLocs.entrySet()) {
for (var loc : el.getValue()) {
loc = map.toCompiled(loc);
if (loc == null) continue;
if (!res.bpLocs.containsKey(loc.filename())) res.bpLocs.put(loc.filename(), new TreeSet<>());
res.bpLocs.get(loc.filename()).add(loc);
}
} }
return res; return res;
} }
public FunctionMap clone() { public FunctionMap clone() {
var res = new FunctionMap(); var res = new FunctionMap(Map.of(), Map.of(), localNames, captureNames);
res.pcToLoc.putAll(this.pcToLoc); res.pcToLoc.putAll(this.pcToLoc);
res.locToPc.putAll(this.locToPc); res.bps.putAll(bps);
res.bpLocs.putAll(bpLocs);
res.pcToLoc.putAll(pcToLoc);
return res; return res;
} }
public FunctionMap(Map<Integer, Location> map, Map<Location, BreakpointType> breakpoints, String[] localNames, String[] captureNames) { public FunctionMap(Map<Integer, Location> map, Map<Location, BreakpointType> breakpoints, String[] localNames, String[] captureNames) {
var locToPc = new HashMap<Location, Integer>();
for (var el : map.entrySet()) { for (var el : map.entrySet()) {
var pc = el.getKey(); pcToLoc.put(el.getKey(), el.getValue());
var loc = el.getValue(); locToPc.putIfAbsent(el.getValue(), el.getKey());
var a = pcToLoc.remove(pc);
var b = locToPc.remove(loc);
if (b != null) pcToLoc.remove(b);
if (a != null) locToPc.remove(a);
pcToLoc.put(pc, loc);
locToPc.put(loc, pc);
} }
for (var el : breakpoints.entrySet()) { for (var el : breakpoints.entrySet()) {
if (el.getValue() == null || el.getValue() == BreakpointType.NONE) continue; if (el.getValue() == null || el.getValue() == BreakpointType.NONE) continue;
bps.put(el.getKey(), el.getValue()); bps.put(locToPc.get(el.getKey()), el.getValue());
bpLocs.add(el.getKey());
if (!bpLocs.containsKey(el.getKey().filename())) bpLocs.put(el.getKey().filename(), new TreeSet<>());
bpLocs.get(el.getKey().filename()).add(el.getKey());
} }
this.localNames = localNames; this.localNames = localNames;

View File

@ -1330,7 +1330,7 @@ public class Parsing {
var valRes = parseValue(filename, tokens, i, 0, true); var valRes = parseValue(filename, tokens, i, 0, true);
if (!valRes.isSuccess()) return valRes.transform(); if (!valRes.isSuccess()) return valRes.transform();
valRes.result.setLoc(loc); // valRes.result.setLoc(loc);
var res = ParseRes.res(valRes.result, valRes.n); var res = ParseRes.res(valRes.result, valRes.n);
if (isStatementEnd(tokens, i + res.n)) { if (isStatementEnd(tokens, i + res.n)) {

View File

@ -3,6 +3,8 @@ package me.topchetoeu.jscript.core;
import me.topchetoeu.jscript.common.Filename; import me.topchetoeu.jscript.common.Filename;
import me.topchetoeu.jscript.common.FunctionBody; import me.topchetoeu.jscript.common.FunctionBody;
import me.topchetoeu.jscript.core.exceptions.EngineException; import me.topchetoeu.jscript.core.exceptions.EngineException;
import me.topchetoeu.jscript.core.scope.ValueVariable;
import me.topchetoeu.jscript.core.values.CodeFunction;
public interface Compiler { public interface Compiler {
public Key<Compiler> KEY = new Key<>(); public Key<Compiler> KEY = new Key<>();
@ -14,4 +16,8 @@ public interface Compiler {
throw EngineException.ofError("No compiler attached to engine."); throw EngineException.ofError("No compiler attached to engine.");
}); });
} }
public static CodeFunction compile(Environment env, Filename filename, String raw) {
return new CodeFunction(env, filename.toString(), Compiler.get(env).compile(filename, raw), new ValueVariable[0]);
}
} }

View File

@ -1,6 +1,7 @@
package me.topchetoeu.jscript.core; package me.topchetoeu.jscript.core;
import java.util.Iterator; import java.util.Iterator;
import java.util.List;
import me.topchetoeu.jscript.common.Filename; import me.topchetoeu.jscript.common.Filename;
import me.topchetoeu.jscript.core.debug.DebugContext; import me.topchetoeu.jscript.core.debug.DebugContext;
@ -41,7 +42,8 @@ public class Context implements Extensions {
return res; return res;
} }
@Override public Iterable<Key<?>> keys() { @Override public Iterable<Key<?>> keys() {
return environment.keys(); if (environment == null) return List.of();
else return environment.keys();
// if (engine == null && environment == null) return List.of(); // if (engine == null && environment == null) return List.of();
// if (engine == null) return environment.keys(); // if (engine == null) return environment.keys();

View File

@ -8,6 +8,10 @@ public interface Extensions {
boolean has(Key<?> key); boolean has(Key<?> key);
boolean remove(Key<?> key); boolean remove(Key<?> key);
default void add(Key<Void> key) {
add(key, null);
}
default boolean hasNotNull(Key<?> key) { default boolean hasNotNull(Key<?> key) {
return has(key) && get(key) != null; return has(key) && get(key) != null;
} }
@ -26,6 +30,7 @@ public interface Extensions {
} }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
default void addAll(Extensions source) { default void addAll(Extensions source) {
if (source == null) return;
for (var key : source.keys()) { for (var key : source.keys()) {
add((Key<Object>)key, (Object)source.get(key)); add((Key<Object>)key, (Object)source.get(key));
} }

View File

@ -81,6 +81,7 @@ public class DebugContext {
} }
public void onFunctionLoad(FunctionBody func, FunctionMap map) { public void onFunctionLoad(FunctionBody func, FunctionMap map) {
if (maps != null) maps.put(func, map); if (maps != null) maps.put(func, map);
if (debugger != null) debugger.onFunctionLoad(func, map);
} }
private DebugContext(boolean enabled) { private DebugContext(boolean enabled) {

View File

@ -13,6 +13,7 @@ public class JSCompiler implements Compiler {
private void registerFunc(FunctionBody body, CompileResult res) { private void registerFunc(FunctionBody body, CompileResult res) {
var map = res.map(); var map = res.map();
DebugContext.get(ext).onFunctionLoad(body, map); DebugContext.get(ext).onFunctionLoad(body, map);
for (var i = 0; i < body.children.length; i++) { for (var i = 0; i < body.children.length; i++) {
@ -23,6 +24,7 @@ public class JSCompiler implements Compiler {
@Override public FunctionBody compile(Filename filename, String source) { @Override public FunctionBody compile(Filename filename, String source) {
var res = Parsing.compile(filename, source); var res = Parsing.compile(filename, source);
var func = res.body(); var func = res.body();
DebugContext.get(ext).onSource(filename, source);
registerFunc(func, res); registerFunc(func, res);
return func; return func;

View File

@ -21,7 +21,7 @@ import me.topchetoeu.jscript.core.exceptions.InterruptException;
import me.topchetoeu.jscript.core.exceptions.SyntaxException; import me.topchetoeu.jscript.core.exceptions.SyntaxException;
import me.topchetoeu.jscript.lib.Internals; import me.topchetoeu.jscript.lib.Internals;
import me.topchetoeu.jscript.utils.debug.DebugServer; import me.topchetoeu.jscript.utils.debug.DebugServer;
// import me.topchetoeu.jscript.utils.debug.SimpleDebugger; import me.topchetoeu.jscript.utils.debug.SimpleDebugger;
import me.topchetoeu.jscript.utils.filesystem.Filesystem; import me.topchetoeu.jscript.utils.filesystem.Filesystem;
import me.topchetoeu.jscript.utils.filesystem.MemoryFilesystem; import me.topchetoeu.jscript.utils.filesystem.MemoryFilesystem;
import me.topchetoeu.jscript.utils.filesystem.Mode; import me.topchetoeu.jscript.utils.filesystem.Mode;
@ -62,11 +62,8 @@ public class JScriptRepl {
var raw = Reading.readline(); var raw = Reading.readline();
if (raw == null) break; if (raw == null) break;
var res = engine.pushMsg( var func = Compiler.compile(environment, new Filename("jscript", "repl/" + i + ".js"), raw);
false, environment, var res = engine.pushMsg(false, environment, func, null).await();
new Filename("jscript", "repl/" + i + ".js"),
raw, null
).await();
Values.printValue(null, res); Values.printValue(null, res);
System.out.println(); System.out.println();
} }
@ -126,7 +123,7 @@ public class JScriptRepl {
var ctx = new DebugContext(); var ctx = new DebugContext();
environment.add(DebugContext.KEY, ctx); environment.add(DebugContext.KEY, ctx);
// debugServer.targets.put("target", (ws, req) -> new SimpleDebugger(ws).attach(ctx)); debugServer.targets.put("target", (ws, req) -> new SimpleDebugger(ws).attach(ctx));
engineTask = engine.start(); engineTask = engine.start();
debugTask = debugServer.start(new InetSocketAddress("127.0.0.1", 9229), true); debugTask = debugServer.start(new InetSocketAddress("127.0.0.1", 9229), true);
} }

View File

@ -140,6 +140,7 @@ public class DebugServer {
try { handle(ws, debugger); } try { handle(ws, debugger); }
catch (RuntimeException | IOException e) { catch (RuntimeException | IOException e) {
try { try {
e.printStackTrace();
ws.send(new V8Error(e.getMessage())); ws.send(new V8Error(e.getMessage()));
} }
catch (IOException e2) { /* Shit outta luck */ } catch (IOException e2) { /* Shit outta luck */ }

View File

@ -6,30 +6,31 @@ import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import java.util.TreeSet; import java.util.WeakHashMap;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import me.topchetoeu.jscript.common.Filename; import me.topchetoeu.jscript.common.Filename;
import me.topchetoeu.jscript.common.FunctionBody;
import me.topchetoeu.jscript.common.Instruction;
import me.topchetoeu.jscript.common.Location; import me.topchetoeu.jscript.common.Location;
import me.topchetoeu.jscript.common.Instruction.BreakpointType;
import me.topchetoeu.jscript.common.Instruction.Type;
import me.topchetoeu.jscript.common.events.Notifier; import me.topchetoeu.jscript.common.events.Notifier;
import me.topchetoeu.jscript.common.json.JSON; import me.topchetoeu.jscript.common.json.JSON;
import me.topchetoeu.jscript.common.json.JSONElement; import me.topchetoeu.jscript.common.json.JSONElement;
import me.topchetoeu.jscript.common.json.JSONList; import me.topchetoeu.jscript.common.json.JSONList;
import me.topchetoeu.jscript.common.json.JSONMap; import me.topchetoeu.jscript.common.json.JSONMap;
import me.topchetoeu.jscript.compilation.Instruction; import me.topchetoeu.jscript.common.mapping.FunctionMap;
import me.topchetoeu.jscript.compilation.Instruction.BreakpointType;
import me.topchetoeu.jscript.compilation.Instruction.Type;
import me.topchetoeu.jscript.compilation.parsing.Parsing; import me.topchetoeu.jscript.compilation.parsing.Parsing;
import me.topchetoeu.jscript.core.Context; import me.topchetoeu.jscript.core.Context;
import me.topchetoeu.jscript.core.Engine; import me.topchetoeu.jscript.core.Engine;
import me.topchetoeu.jscript.core.Environment; import me.topchetoeu.jscript.core.Environment;
import me.topchetoeu.jscript.core.Extensions; import me.topchetoeu.jscript.core.EventLoop;
import me.topchetoeu.jscript.core.Frame; import me.topchetoeu.jscript.core.Frame;
import me.topchetoeu.jscript.core.debug.DebugContext; import me.topchetoeu.jscript.core.debug.DebugContext;
import me.topchetoeu.jscript.core.scope.GlobalScope; import me.topchetoeu.jscript.core.scope.GlobalScope;
import me.topchetoeu.jscript.core.values.ArrayValue; import me.topchetoeu.jscript.core.values.ArrayValue;
import me.topchetoeu.jscript.core.values.CodeFunction;
import me.topchetoeu.jscript.core.values.FunctionValue; import me.topchetoeu.jscript.core.values.FunctionValue;
import me.topchetoeu.jscript.core.values.ObjectValue; import me.topchetoeu.jscript.core.values.ObjectValue;
import me.topchetoeu.jscript.core.values.Symbol; import me.topchetoeu.jscript.core.values.Symbol;
@ -76,36 +77,48 @@ public class SimpleDebugger implements Debugger {
this.source = source; this.source = source;
} }
} }
private static class Breakpoint {
public final int id;
public final Location location;
public final String condition;
public Breakpoint(int id, Location location, String condition) { private class Breakpoint {
this.id = id;
this.location = location;
this.condition = condition;
}
}
private static class BreakpointCandidate {
public final int id; public final int id;
public final String condition; public final String condition;
public final Pattern pattern; public final Pattern pattern;
public final int line, start; public final int line, start;
public final HashSet<Breakpoint> resolvedBreakpoints = new HashSet<>(); public final WeakHashMap<FunctionBody, Set<Location>> resolvedLocations = new WeakHashMap<>();
public BreakpointCandidate(int id, Pattern pattern, int line, int start, String condition) { public Breakpoint(int id, Pattern pattern, int line, int start, String condition) {
this.id = id; this.id = id;
this.condition = condition;
this.pattern = pattern; this.pattern = pattern;
this.line = line; this.line = line;
this.start = start; this.start = start;
if (condition != null && condition.trim().equals("")) condition = null; if (condition != null && condition.trim().equals("")) condition = null;
this.condition = condition; }
// TODO: Figure out how to unload a breakpoint
public void addFunc(FunctionBody body, FunctionMap map) {
try {
for (var loc : map.correctBreakpoint(pattern, line, start)) {
if (!resolvedLocations.containsKey(body)) resolvedLocations.put(body, new HashSet<>());
var set = resolvedLocations.get(body);
set.add(loc);
ws.send(new V8Event("Debugger.breakpointResolved", new JSONMap()
.set("breakpointId", id)
.set("location", serializeLocation(loc))
));
}
updateBreakpoints();
}
catch (IOException e) {
ws.close();
close();
}
} }
} }
private class DebugFrame { private class DebugFrame {
public Frame frame; public Frame frame;
public CodeFunction func;
public int id; public int id;
public ObjectValue local, capture, global, valstack; public ObjectValue local, capture, global, valstack;
public JSONMap serialized; public JSONMap serialized;
@ -118,18 +131,17 @@ public class SimpleDebugger implements Debugger {
public DebugFrame(Frame frame, int id) { public DebugFrame(Frame frame, int id) {
this.frame = frame; this.frame = frame;
this.func = frame.function;
this.id = id; this.id = id;
this.global = frame.function.environment.global.obj; this.global = frame.function.environment.global.obj;
this.local = frame.getLocalScope(true); this.local = frame.getLocalScope();
this.capture = frame.getCaptureScope(true); this.capture = frame.getCaptureScope();
Values.makePrototypeChain(frame.ctx, global, capture, local); Values.makePrototypeChain(frame.ctx, global, capture, local);
this.valstack = frame.getValStackScope(); this.valstack = frame.getValStackScope();
this.serialized = new JSONMap() this.serialized = new JSONMap()
.set("callFrameId", id + "") .set("callFrameId", id + "")
.set("functionName", func.name) .set("functionName", frame.function.name)
.set("scopeChain", new JSONList() .set("scopeChain", new JSONList()
.add(new JSONMap() .add(new JSONMap()
.set("type", "local") .set("type", "local")
@ -190,11 +202,10 @@ public class SimpleDebugger implements Debugger {
private ObjectValue emptyObject = new ObjectValue(); private ObjectValue emptyObject = new ObjectValue();
private HashMap<Integer, BreakpointCandidate> idToBptCand = new HashMap<>(); private WeakHashMap<FunctionBody, FunctionMap> mappings = new WeakHashMap<>();
private WeakHashMap<FunctionBody, HashMap<Location, Breakpoint>> bpLocs = new WeakHashMap<>();
private HashMap<Integer, Breakpoint> idToBreakpoint = new HashMap<>(); private HashMap<Integer, Breakpoint> idToBreakpoint = new HashMap<>();
private HashMap<Location, Breakpoint> locToBreakpoint = new HashMap<>();
private HashSet<Location> tmpBreakpts = new HashSet<>();
private HashMap<Filename, Integer> filenameToId = new HashMap<>(); private HashMap<Filename, Integer> filenameToId = new HashMap<>();
private HashMap<Integer, DebugSource> idToSource = new HashMap<>(); private HashMap<Integer, DebugSource> idToSource = new HashMap<>();
@ -276,17 +287,22 @@ public class SimpleDebugger implements Debugger {
return res; return res;
} }
private Location correctLocation(DebugSource source, Location loc) { private void updateBreakpoints() {
var set = source.breakpoints; bpLocs.clear();
if (set.contains(loc)) return loc; for (var bp : idToBreakpoint.values()) {
for (var el : bp.resolvedLocations.entrySet()) {
if (!bpLocs.containsKey(el.getKey())) bpLocs.put(el.getKey(), new HashMap<>());
var map = bpLocs.get(el.getKey());
var tail = set.tailSet(loc); for (var loc : el.getValue()) {
if (tail.isEmpty()) return null; map.put(loc, bp);
}
return tail.first(); }
}
} }
private Location deserializeLocation(JSONElement el, boolean correct) {
private Location deserializeLocation(JSONElement el) {
if (!el.isMap()) throw new RuntimeException("Expected location to be a map."); if (!el.isMap()) throw new RuntimeException("Expected location to be a map.");
var id = Integer.parseInt(el.map().string("scriptId")); var id = Integer.parseInt(el.map().string("scriptId"));
var line = (int)el.map().number("lineNumber") + 1; var line = (int)el.map().number("lineNumber") + 1;
@ -295,7 +311,6 @@ public class SimpleDebugger implements Debugger {
if (!idToSource.containsKey(id)) throw new RuntimeException(String.format("The specified source %s doesn't exist.", id)); if (!idToSource.containsKey(id)) throw new RuntimeException(String.format("The specified source %s doesn't exist.", id));
var res = new Location(line, column, idToSource.get(id).filename); var res = new Location(line, column, idToSource.get(id).filename);
if (correct) res = correctLocation(idToSource.get(id), res);
return res; return res;
} }
private JSONMap serializeLocation(Location loc) { private JSONMap serializeLocation(Location loc) {
@ -318,8 +333,10 @@ public class SimpleDebugger implements Debugger {
} }
private JSONMap serializeObj(Context ctx, Object val, boolean byValue) { private JSONMap serializeObj(Context ctx, Object val, boolean byValue) {
val = Values.normalize(null, val); val = Values.normalize(null, val);
ctx = new Context(ctx.engine.copy(), ctx.environment); var newCtx = new Context();
ctx.engine.add(DebugContext.IGNORE, true); newCtx.addAll(ctx);
newCtx.add(DebugContext.IGNORE);
ctx = newCtx;
if (val == Values.NULL) { if (val == Values.NULL) {
return new JSONMap() return new JSONMap()
@ -507,30 +524,17 @@ public class SimpleDebugger implements Debugger {
} }
} }
private void addBreakpoint(Breakpoint bpt) {
try {
idToBreakpoint.put(bpt.id, bpt);
locToBreakpoint.put(bpt.location, bpt);
ws.send(new V8Event("Debugger.breakpointResolved", new JSONMap()
.set("breakpointId", bpt.id)
.set("location", serializeLocation(bpt.location))
));
}
catch (IOException e) {
ws.close();
close();
}
}
private RunResult run(DebugFrame codeFrame, String code) { private RunResult run(DebugFrame codeFrame, String code) {
if (codeFrame == null) return new RunResult(null, code, new EngineException("Invalid code frame!")); if (codeFrame == null) return new RunResult(null, code, new EngineException("Invalid code frame!"));
var engine = new Engine(); var engine = new Engine();
var env = codeFrame.func.environment.copy(); var env = codeFrame.frame.function.environment.copy();
env.global = new GlobalScope(codeFrame.local); env.global = new GlobalScope(codeFrame.local);
env.remove(EventLoop.KEY);
env.remove(DebugContext.KEY);
env.add(EventLoop.KEY, engine);
var ctx = new Context(engine, env); var ctx = new Context(env);
var awaiter = engine.pushMsg(false, ctx.environment, new Filename("jscript", "eval"), code, codeFrame.frame.thisArg, codeFrame.frame.args); var awaiter = engine.pushMsg(false, ctx.environment, new Filename("jscript", "eval"), code, codeFrame.frame.thisArg, codeFrame.frame.args);
engine.run(true); engine.run(true);
@ -625,16 +629,16 @@ public class SimpleDebugger implements Debugger {
close(); close();
ws.send(msg.respond()); ws.send(msg.respond());
} }
public synchronized void close() { @Override public synchronized void close() {
enabled = false; enabled = false;
execptionType = CatchType.NONE; execptionType = CatchType.NONE;
state = State.RESUMED; state = State.RESUMED;
idToBptCand.clear(); // idToBptCand.clear();
idToBreakpoint.clear(); idToBreakpoint.clear();
locToBreakpoint.clear(); // locToBreakpoint.clear();
tmpBreakpts.clear(); // tmpBreakpts.clear();
filenameToId.clear(); filenameToId.clear();
idToSource.clear(); idToSource.clear();
@ -660,15 +664,14 @@ public class SimpleDebugger implements Debugger {
ws.send(msg.respond(new JSONMap().set("scriptSource", idToSource.get(id).source))); ws.send(msg.respond(new JSONMap().set("scriptSource", idToSource.get(id).source)));
} }
@Override public synchronized void getPossibleBreakpoints(V8Message msg) throws IOException { @Override public synchronized void getPossibleBreakpoints(V8Message msg) throws IOException {
var src = idToSource.get(Integer.parseInt(msg.params.map("start").string("scriptId"))); var start = deserializeLocation(msg.params.get("start"));
var start = deserializeLocation(msg.params.get("start"), false); var end = msg.params.isMap("end") ? deserializeLocation(msg.params.get("end")) : null;
var end = msg.params.isMap("end") ? deserializeLocation(msg.params.get("end"), false) : null;
var res = new JSONList(); var res = new JSONList();
for (var loc : src.breakpoints.tailSet(start, true)) { for (var el : mappings.values()) {
if (end != null && loc.compareTo(end) > 0) break; for (var bp : el.breakpoints(start, end)) {
res.add(serializeLocation(loc)); res.add(serializeLocation(bp));
}
} }
ws.send(msg.respond(new JSONMap().set("locations", res))); ws.send(msg.respond(new JSONMap().set("locations", res)));
@ -694,54 +697,50 @@ public class SimpleDebugger implements Debugger {
Pattern regex; Pattern regex;
if (msg.params.isString("url")) regex = Pattern.compile(Pattern.quote(msg.params.string("url"))); if (msg.params.isString("url")) regex = Pattern.compile(Pattern.quote(msg.params.string("url")));
else regex = Pattern.compile(msg.params.string("urlRegex")); else if (msg.params.isString("urlRegex")) regex = Pattern.compile(msg.params.string("urlRegex"));
else {
ws.send(msg.respond(new JSONMap()
.set("breakpointId", "john-doe")
.set("locations", new JSONList())
));
return;
}
var bpcd = new BreakpointCandidate(nextId(), regex, line, col, cond); var bpt = new Breakpoint(nextId(), regex, line, col, cond);
idToBptCand.put(bpcd.id, bpcd); idToBreakpoint.put(bpt.id, bpt);
var locs = new JSONList(); var locs = new JSONList();
for (var src : idToSource.values()) { for (var el : mappings.entrySet()) {
if (regex.matcher(src.filename.toString()).matches()) { bpt.addFunc(el.getKey(), el.getValue());
var loc = correctLocation(src, new Location(line, col, src.filename)); }
if (loc == null) continue;
var bp = new Breakpoint(nextId(), loc, bpcd.condition);
bpcd.resolvedBreakpoints.add(bp); for (var el : bpt.resolvedLocations.values()) {
for (var loc : el) {
locs.add(serializeLocation(loc)); locs.add(serializeLocation(loc));
addBreakpoint(bp);
} }
} }
ws.send(msg.respond(new JSONMap() ws.send(msg.respond(new JSONMap()
.set("breakpointId", bpcd.id + "") .set("breakpointId", bpt.id + "")
.set("locations", locs) .set("locations", locs)
)); ));
} }
@Override public synchronized void removeBreakpoint(V8Message msg) throws IOException { @Override public synchronized void removeBreakpoint(V8Message msg) throws IOException {
var id = Integer.parseInt(msg.params.string("breakpointId")); var id = Integer.parseInt(msg.params.string("breakpointId"));
if (idToBptCand.containsKey(id)) { idToBreakpoint.remove(id);
var bpcd = idToBptCand.get(id);
for (var bp : bpcd.resolvedBreakpoints) {
idToBreakpoint.remove(bp.id);
locToBreakpoint.remove(bp.location);
}
idToBptCand.remove(id);
}
else if (idToBreakpoint.containsKey(id)) {
var bp = idToBreakpoint.remove(id);
locToBreakpoint.remove(bp.location);
}
ws.send(msg.respond()); ws.send(msg.respond());
} }
@Override public synchronized void continueToLocation(V8Message msg) throws IOException { @Override public synchronized void continueToLocation(V8Message msg) throws IOException {
var loc = deserializeLocation(msg.params.get("location"), true); // TODO: Figure out if we need this
tmpBreakpts.add(loc); // var loc = correctLocation(deserializeLocation(msg.params.get("location")));
resume(State.RESUMED); // tmpBreakpts.add(loc);
ws.send(msg.respond());
// resume(State.RESUMED);
// ws.send(msg.respond());
} }
@Override public synchronized void setPauseOnExceptions(V8Message msg) throws IOException { @Override public synchronized void setPauseOnExceptions(V8Message msg) throws IOException {
@ -915,25 +914,22 @@ public class SimpleDebugger implements Debugger {
ws.send(msg.respond()); ws.send(msg.respond());
} }
@Override public void onSource(Filename filename, String source) { @Override public void onSourceLoad(Filename filename, String source) {
int id = nextId(); int id = nextId();
var src = new DebugSource(id, filename, source); var src = new DebugSource(id, filename, source);
idToSource.put(id, src); idToSource.put(id, src);
filenameToId.put(filename, id); filenameToId.put(filename, id);
for (var bpcd : idToBptCand.values()) {
if (!bpcd.pattern.matcher(filename.toString()).matches()) continue;
var loc = correctLocation(src, new Location(bpcd.line, bpcd.start, filename));
var bp = new Breakpoint(nextId(), loc, bpcd.condition);
if (loc == null) continue;
bpcd.resolvedBreakpoints.add(bp);
addBreakpoint(bp);
}
if (!enabled) pendingSources.add(src); if (!enabled) pendingSources.add(src);
else sendSource(src); else sendSource(src);
} }
@Override public void onFunctionLoad(FunctionBody body, FunctionMap map) {
for (var bpt : idToBreakpoint.values()) {
bpt.addFunc(body, map);
}
mappings.put(body, map);
}
@Override public boolean onInstruction(Context ctx, Frame cf, Instruction instruction, Object returnVal, EngineException error, boolean caught) { @Override public boolean onInstruction(Context ctx, Frame cf, Instruction instruction, Object returnVal, EngineException error, boolean caught) {
if (!enabled) return false; if (!enabled) return false;
@ -945,11 +941,11 @@ public class SimpleDebugger implements Debugger {
synchronized (this) { synchronized (this) {
frame = getFrame(cf); frame = getFrame(cf);
var map = DebugContext.get(ctx).getMap(frame.func); var map = DebugContext.get(ctx).getMap(frame.frame.function);
frame.updateLoc(map.toLocation(frame.frame.codePtr)); frame.updateLoc(map.toLocation(frame.frame.codePtr));
loc = frame.location; loc = frame.location;
bptType = DebugContext.get(ctx).getMap(frame.func).getBreakpoint(loc); bptType = map.getBreakpoint(frame.frame.codePtr);
isBreakpointable = loc != null && (bptType.shouldStepIn()); isBreakpointable = loc != null && (bptType.shouldStepIn());
if (error != null && (execptionType == CatchType.ALL || execptionType == CatchType.UNCAUGHT && !caught)) { if (error != null && (execptionType == CatchType.ALL || execptionType == CatchType.UNCAUGHT && !caught)) {
@ -962,12 +958,12 @@ public class SimpleDebugger implements Debugger {
) { ) {
pauseDebug(ctx, null); pauseDebug(ctx, null);
} }
else if (isBreakpointable && locToBreakpoint.containsKey(loc)) { else if (isBreakpointable && bpLocs.getOrDefault(cf.function.body, new HashMap<>()).containsKey(loc)) {
var bp = locToBreakpoint.get(loc); var bp = bpLocs.get(cf.function.body).get(loc);
var ok = bp.condition == null ? true : Values.toBoolean(run(currFrame, bp.condition).result); var ok = bp.condition == null ? true : Values.toBoolean(run(currFrame, bp.condition).result);
if (ok) pauseDebug(ctx, locToBreakpoint.get(loc)); if (ok) pauseDebug(ctx, bp);
} }
else if (isBreakpointable && tmpBreakpts.remove(loc)) pauseDebug(ctx, null); // else if (isBreakpointable && tmpBreakpts.remove(loc)) pauseDebug(ctx, null);
else if (isBreakpointable && pendingPause) { else if (isBreakpointable && pendingPause) {
pauseDebug(ctx, null); pauseDebug(ctx, null);
pendingPause = false; pendingPause = false;

View File

@ -180,7 +180,9 @@ public class WebSocket implements AutoCloseable {
if (!fin) continue; if (!fin) continue;
var raw = data.toByteArray(); var raw = data.toByteArray();
if (type == 1) return new WebSocketMessage(new String(raw)); if (type == 1) {
return new WebSocketMessage(new String(raw));
}
else return new WebSocketMessage(raw); else return new WebSocketMessage(raw);
} }