move debugging to lib

This commit is contained in:
2025-01-24 22:42:33 +02:00
parent 3c4d05abd4
commit 4352550ae9
24 changed files with 478 additions and 504 deletions

View File

@@ -1,19 +0,0 @@
package me.topchetoeu.j2s.repl;
import me.topchetoeu.j2s.compilation.json.JSON;
import me.topchetoeu.j2s.compilation.json.JSONMap;
public class V8Error {
public final String message;
public V8Error(String message) {
this.message = message;
}
@Override
public String toString() {
return JSON.stringify(new JSONMap().set("error", new JSONMap()
.set("message", message)
));
}
}

View File

@@ -1,246 +0,0 @@
package me.topchetoeu.j2s.repl.debug;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.security.MessageDigest;
import java.util.Base64;
import java.util.HashMap;
import me.topchetoeu.j2s.common.Metadata;
import me.topchetoeu.j2s.common.Reading;
import me.topchetoeu.j2s.common.SyntaxException;
import me.topchetoeu.j2s.compilation.json.JSON;
import me.topchetoeu.j2s.compilation.json.JSONList;
import me.topchetoeu.j2s.compilation.json.JSONMap;
import me.topchetoeu.j2s.repl.debug.WebSocketMessage.Type;
public class DebugServer {
public static String browserDisplayName = Metadata.name() + "/" + Metadata.version();
public final HashMap<String, DebuggerProvider> targets = new HashMap<>();
private final byte[] favicon, index, protocol;
private final Object connNotifier = new Object();
private static void send(HttpRequest req, String val) throws IOException {
req.writeResponse(200, "OK", "application/json", val.getBytes());
}
// SILENCE JAVA
private MessageDigest getDigestInstance() {
try {
return MessageDigest.getInstance("sha1");
}
catch (Throwable e) { throw new RuntimeException(e); }
}
private static Thread runAsync(Runnable func, String name) {
var res = new Thread(func);
res.setName(name);
res.start();
return res;
}
private void handle(WebSocket ws, Debugger debugger) throws IOException {
WebSocketMessage raw;
while ((raw = ws.receive()) != null) {
if (raw.type != Type.Text) {
ws.send(new V8Error("Expected a text message."));
continue;
}
V8Message msg;
try {
msg = new V8Message(raw.textData());
}
catch (SyntaxException e) {
ws.send(new V8Error(e.getMessage()));
return;
}
switch (msg.name) {
case "Debugger.enable":
synchronized (connNotifier) {
connNotifier.notify();
}
debugger.enable(msg);
continue;
case "Debugger.disable": debugger.close(); continue;
case "Debugger.setBreakpointByUrl": debugger.setBreakpointByUrl(msg); continue;
case "Debugger.removeBreakpoint": debugger.removeBreakpoint(msg); continue;
case "Debugger.continueToLocation": debugger.continueToLocation(msg); continue;
case "Debugger.getScriptSource": debugger.getScriptSource(msg); continue;
case "Debugger.getPossibleBreakpoints": debugger.getPossibleBreakpoints(msg); continue;
case "Debugger.resume": debugger.resume(msg); continue;
case "Debugger.pause": debugger.pause(msg); continue;
case "Debugger.stepInto": debugger.stepInto(msg); continue;
case "Debugger.stepOut": debugger.stepOut(msg); continue;
case "Debugger.stepOver": debugger.stepOver(msg); continue;
case "Debugger.setPauseOnExceptions": debugger.setPauseOnExceptions(msg); continue;
case "Debugger.evaluateOnCallFrame": debugger.evaluateOnCallFrame(msg); continue;
case "Runtime.releaseObjectGroup": debugger.releaseObjectGroup(msg); continue;
case "Runtime.releaseObject": debugger.releaseObject(msg); continue;
case "Runtime.getProperties": debugger.getProperties(msg); continue;
case "Runtime.callFunctionOn": debugger.callFunctionOn(msg); continue;
case "Runtime.enable": debugger.runtimeEnable(msg); continue;
}
if (
msg.name.startsWith("DOM.") ||
msg.name.startsWith("DOMDebugger.") ||
msg.name.startsWith("Emulation.") ||
msg.name.startsWith("Input.") ||
msg.name.startsWith("Network.") ||
msg.name.startsWith("Page.")
) ws.send(new V8Error("This isn't a browser..."));
else ws.send(new V8Error("This API is not supported yet."));
}
debugger.close();
}
private void onWsConnect(HttpRequest req, Socket socket, DebuggerProvider debuggerProvider) {
var key = req.headers.get("sec-websocket-key");
if (key == null) {
req.writeResponse(
426, "Upgrade Required", "text/txt",
"Expected a WS upgrade".getBytes()
);
return;
}
var resKey = Base64.getEncoder().encodeToString(getDigestInstance().digest(
(key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11").getBytes()
));
req.writeCode(101, "Switching Protocols");
req.writeHeader("Connection", "Upgrade");
req.writeHeader("Sec-WebSocket-Accept", resKey);
req.writeLastHeader("Upgrade", "WebSocket");
var ws = new WebSocket(socket);
var debugger = debuggerProvider.getDebugger(ws, req);
if (debugger == null) {
ws.close();
return;
}
runAsync(() -> {
var handle = new Thread(debugger::close);
Runtime.getRuntime().addShutdownHook(handle);
try { handle(ws, debugger); }
catch (RuntimeException | IOException e) {
try {
e.printStackTrace();
ws.send(new V8Error(e.getMessage()));
}
catch (IOException e2) { /* Shit outta luck */ }
}
finally {
Runtime.getRuntime().removeShutdownHook(handle);
ws.close();
debugger.close();
}
}, "Debug Handler");
}
public void awaitConnection() throws InterruptedException {
synchronized (connNotifier) {
connNotifier.wait();
}
}
public void run(InetSocketAddress address) {
try {
ServerSocket server = new ServerSocket();
server.bind(address);
try {
while (true) {
var socket = server.accept();
var req = HttpRequest.read(socket);
if (req == null) continue;
switch (req.path) {
case "/json/version":
send(req, "{\"Browser\":\"" + browserDisplayName + "\",\"Protocol-Version\":\"1.1\"}");
break;
case "/json/list":
case "/json": {
var res = new JSONList();
for (var el : targets.entrySet()) {
res.add(new JSONMap()
.set("description", "J2S debugger")
.set("favicon", "/favicon.ico")
.set("id", el.getKey())
.set("type", "node")
.set("webSocketDebuggerUrl", "ws://" + address.getHostString() + ":" + address.getPort() + "/" + el.getKey())
);
}
send(req, JSON.stringify(res));
break;
}
case "/json/protocol":
req.writeResponse(200, "OK", "application/json", protocol);
break;
case "/json/new":
case "/json/activate":
case "/json/close":
case "/devtools/inspector.html":
req.writeResponse(
501, "Not Implemented", "text/txt",
"This feature isn't (and probably won't be) implemented.".getBytes()
);
break;
case "/":
case "/index.html":
req.writeResponse(200, "OK", "text/html", index);
break;
case "/favicon.ico":
req.writeResponse(200, "OK", "image/png", favicon);
break;
default:
if (req.path.length() > 1 && targets.containsKey(req.path.substring(1))) {
onWsConnect(req, socket, targets.get(req.path.substring(1)));
}
break;
}
}
}
finally { server.close(); }
}
catch (IOException e) { throw new UncheckedIOException(e); }
}
public Thread start(InetSocketAddress address, boolean daemon) {
var res = new Thread(() -> run(address), "Debug Server");
res.setDaemon(daemon);
res.start();
return res;
}
public DebugServer() {
this.favicon = Reading.resourceToBytes("debugger/favicon.png");
this.protocol = Reading.resourceToBytes("debugger/protocol.json");
this.index = Reading.resourceToString("debugger/index.html")
.replace("${NAME}", Metadata.name())
.replace("${VERSION}", Metadata.version())
.replace("${AUTHOR}", Metadata.author())
.getBytes();
}
}

View File

@@ -1,37 +0,0 @@
package me.topchetoeu.j2s.repl.debug;
import java.io.IOException;
import me.topchetoeu.j2s.runtime.debug.DebugHandler;
public interface Debugger extends DebugHandler {
void close();
void enable(V8Message msg) throws IOException;
void disable(V8Message msg) throws IOException;
void setBreakpointByUrl(V8Message msg) throws IOException;
void removeBreakpoint(V8Message msg) throws IOException;
void continueToLocation(V8Message msg) throws IOException;
void getScriptSource(V8Message msg) throws IOException;
void getPossibleBreakpoints(V8Message msg) throws IOException;
void resume(V8Message msg) throws IOException;
void pause(V8Message msg) throws IOException;
void stepInto(V8Message msg) throws IOException;
void stepOut(V8Message msg) throws IOException;
void stepOver(V8Message msg) throws IOException;
void setPauseOnExceptions(V8Message msg) throws IOException;
void evaluateOnCallFrame(V8Message msg) throws IOException;
void getProperties(V8Message msg) throws IOException;
void releaseObjectGroup(V8Message msg) throws IOException;
void releaseObject(V8Message msg) throws IOException;
void callFunctionOn(V8Message msg) throws IOException;
void runtimeEnable(V8Message msg) throws IOException;
}

View File

@@ -1,5 +0,0 @@
package me.topchetoeu.j2s.repl.debug;
public interface DebuggerProvider {
Debugger getDebugger(WebSocket socket, HttpRequest req);
}

View File

@@ -1,101 +0,0 @@
package me.topchetoeu.j2s.repl.debug;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.Socket;
import java.util.HashMap;
import java.util.IllegalFormatException;
import java.util.Map;
import me.topchetoeu.j2s.common.Reading;
public class HttpRequest {
public final String method;
public final String path;
public final Map<String, String> headers;
public final OutputStream out;
public void writeCode(int code, String name) {
try { out.write(("HTTP/1.1 " + code + " " + name + "\r\n").getBytes()); }
catch (IOException e) { }
}
public void writeHeader(String name, String value) {
try { out.write((name + ": " + value + "\r\n").getBytes()); }
catch (IOException e) { }
}
public void writeLastHeader(String name, String value) {
try { out.write((name + ": " + value + "\r\n\r\n").getBytes()); }
catch (IOException e) { }
}
public void writeHeadersEnd() {
try { out.write("\n".getBytes()); }
catch (IOException e) { }
}
public void writeResponse(int code, String name, String type, byte[] data) {
writeCode(code, name);
writeHeader("Content-Type", type);
writeLastHeader("Content-Length", data.length + "");
try {
out.write(data);
out.close();
}
catch (IOException e) { }
}
public void writeResponse(int code, String name, String type, InputStream data) {
writeResponse(code, name, type, Reading.streamToBytes(data));
}
public HttpRequest(String method, String path, Map<String, String> headers, OutputStream out) {
this.method = method;
this.path = path;
this.headers = headers;
this.out = out;
}
// We dont need no http library
public static HttpRequest read(Socket socket) {
try {
var str = socket.getInputStream();
var lines = new BufferedReader(new InputStreamReader(str));
var line = lines.readLine();
var i1 = line.indexOf(" ");
var i2 = line.indexOf(" ", i1 + 1);
if (i1 < 0 || i2 < 0) {
socket.close();
return null;
}
var method = line.substring(0, i1).trim().toUpperCase();
var path = line.substring(i1 + 1, i2).trim();
var headers = new HashMap<String, String>();
while (!(line = lines.readLine()).isEmpty()) {
var i = line.indexOf(":");
if (i < 0) continue;
var name = line.substring(0, i).trim().toLowerCase();
var value = line.substring(i + 1).trim();
if (name.length() == 0) continue;
headers.put(name, value);
}
if (headers.containsKey("content-length")) {
try {
var i = Integer.parseInt(headers.get("content-length"));
str.skip(i);
}
catch (IllegalFormatException e) { /* ¯\_(ツ)_/¯ */ }
}
return new HttpRequest(method, path, headers, socket.getOutputStream());
}
catch (IOException | NullPointerException e) { return null; }
}
}

View File

@@ -1,216 +0,0 @@
package me.topchetoeu.j2s.repl.debug;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.function.IntUnaryOperator;
import me.topchetoeu.j2s.common.Environment;
import me.topchetoeu.j2s.runtime.Frame;
import me.topchetoeu.j2s.runtime.exceptions.EngineException;
import me.topchetoeu.j2s.runtime.values.KeyCache;
import me.topchetoeu.j2s.runtime.values.Member;
import me.topchetoeu.j2s.runtime.values.Value;
import me.topchetoeu.j2s.runtime.values.Member.FieldMember;
import me.topchetoeu.j2s.runtime.values.functions.FunctionValue;
import me.topchetoeu.j2s.runtime.values.objects.ObjectValue;
import me.topchetoeu.j2s.runtime.values.primitives.StringValue;
import me.topchetoeu.j2s.runtime.values.primitives.SymbolValue;
import me.topchetoeu.j2s.runtime.values.primitives.numbers.NumberValue;
public class ScopeObject extends Value {
public static final class ScopeMember extends FieldMember {
public final Frame frame;
public final int i;
@Override public Value get(Environment env, Value self) {
return frame.getVar(i);
}
@Override public boolean set(Environment env, Value val, Value self) {
frame.setVar(i, val);
return true;
}
public ScopeMember(Value self, Frame frame, int i) {
super(self, false, true, true);
this.frame = frame;
this.i = i;
}
}
private final Map<String, FieldMember> fields = new HashMap<>();
public final ObjectValue proto;
@Override public StringValue type() {
return StringValue.of("object");
}
@Override public boolean isPrimitive() {
return false;
}
@Override public Value toPrimitive(Environment env) {
throw EngineException.ofType("Value couldn't be converted to a primitive.");
}
@Override public NumberValue toNumber(Environment env) {
return NumberValue.NAN;
}
@Override public String toString(Environment env) {
return "[Scope]";
}
@Override public boolean toBoolean() {
return true;
}
@Override public Member getOwnMember(Environment env, KeyCache key) {
if (key.isSymbol()) return null;
var strKey = key.toString(env);
return fields.get(strKey);
}
@Override public Set<String> getOwnMembers(Environment env, boolean onlyEnumerable) {
return fields.keySet();
}
@Override public Set<SymbolValue> getOwnSymbolMembers(Environment env, boolean onlyEnumerable) {
return new HashSet<>();
}
@Override public boolean defineOwnField(Environment env, KeyCache key, Value val, Boolean writable, Boolean enumerable, Boolean configurable) {
if (key.isSymbol()) return false;
var strKey = key.toString(env);
var field = fields.get(strKey);
if (field == null) return false;
return field.reconfigure(env, this, val, writable, enumerable, configurable);
}
@Override public boolean defineOwnProperty(Environment env, KeyCache key, Optional<FunctionValue> get, Optional<FunctionValue> set, Boolean enumerable, Boolean configurable) {
return false;
}
@Override public boolean deleteOwnMember(Environment env, KeyCache key) {
return key.isSymbol() || !fields.containsKey(key.toString(env));
}
@Override public boolean setPrototype(Environment env, ObjectValue val) {
return false;
}
@Override public State getState() {
return State.SEALED;
}
@Override public void preventExtensions() { }
@Override public void seal() { }
@Override public void freeze() { }
@Override public ObjectValue getPrototype(Environment env) {
return proto;
}
public void add(String name, Value val) {
fields.put(name, FieldMember.of(this, val, false));
}
public void remove(String name) {
fields.remove(name);
}
private ScopeObject(ObjectValue proto) {
this.proto = proto;
}
public ScopeObject(Frame frame, String[] names, IntUnaryOperator transformer, ObjectValue proto) {
this.proto = proto;
for (var i = 0; i < names.length; i++) {
fields.put(names[i], new ScopeMember(this, frame, transformer.applyAsInt(i)));
}
}
private static String[] fixCaptures(Frame frame, String[] names) {
if (names == null) {
names = new String[frame.captures.length];
for (var i = 0; i < names.length; i++) {
names[i] = "var_" + i;
}
}
else if (names.length > frame.captures.length) {
var newNames = new String[frame.captures.length];
System.arraycopy(names, 0, newNames, 0, frame.captures.length);
names = newNames;
}
else if (names.length < frame.captures.length) {
var newNames = new String[frame.captures.length];
System.arraycopy(names, 0, newNames, 0, names.length);
for (var i = names.length; i < frame.captures.length; i++) {
names[i] = "cap_" + i;
}
names = newNames;
}
return names;
}
private static String[] fixLocals(Frame frame, String[] names) {
if (names == null) {
names = new String[frame.locals.length];
for (var i = 0; i < names.length; i++) {
names[i] = "var_" + i;
}
}
else if (names.length > frame.locals.length) {
var newNames = new String[frame.locals.length];
System.arraycopy(names, 0, newNames, 0, frame.locals.length);
names = newNames;
}
else if (names.length < frame.locals.length) {
var newNames = new String[frame.locals.length];
System.arraycopy(names, 0, newNames, 0, names.length);
for (var i = names.length; i < frame.locals.length; i++) {
names[i] = "var_" + i;
}
names = newNames;
}
return names;
}
private static String[] fixCapturables(Frame frame, String[] names) {
if (names == null) {
names = new String[frame.capturables.length];
for (var i = 0; i < names.length; i++) {
names[i] = "var_" + (frame.locals.length + i);
}
}
else if (names.length > frame.capturables.length) {
var newNames = new String[frame.capturables.length];
System.arraycopy(names, 0, newNames, 0, frame.capturables.length);
names = newNames;
}
else if (names.length < frame.capturables.length) {
var newNames = new String[frame.capturables.length];
System.arraycopy(names, 0, newNames, 0, names.length);
for (var i = names.length; i < frame.capturables.length; i++) {
names[i] = "var_" + (frame.locals.length + i);
}
names = newNames;
}
return names;
}
public static ScopeObject locals(Frame frame, String[] names) {
return new ScopeObject(frame, fixLocals(frame, names), v -> v, null);
}
public static ScopeObject capturables(Frame frame, String[] names) {
return new ScopeObject(frame, fixCapturables(frame, names), v -> v + frame.locals.length, null);
}
public static ScopeObject captures(Frame frame, String[] names) {
return new ScopeObject(frame, fixCaptures(frame, names), v -> ~v, null);
}
public static ScopeObject combine(ObjectValue proto, ScopeObject ...objs) {
var res = new ScopeObject(proto);
for (var el : objs) {
res.fields.putAll(el.fields);
}
return res;
}
public static ScopeObject all(Frame frame, String[] local, String[] capturables, String[] captures) {
return combine((ObjectValue)Value.global(frame.env), locals(frame, local), capturables(frame, capturables), captures(frame, captures));
}
}

View File

@@ -1,82 +0,0 @@
package me.topchetoeu.j2s.repl.debug;
import java.util.HashMap;
import java.util.WeakHashMap;
import me.topchetoeu.j2s.common.Environment;
import me.topchetoeu.j2s.common.Filename;
import me.topchetoeu.j2s.common.Instruction;
import me.topchetoeu.j2s.common.FunctionBody;
import me.topchetoeu.j2s.common.FunctionMap;
import me.topchetoeu.j2s.runtime.Frame;
import me.topchetoeu.j2s.runtime.debug.DebugHandler;
import me.topchetoeu.j2s.runtime.exceptions.EngineException;
import me.topchetoeu.j2s.runtime.values.Value;
public class SimpleDebugHandler implements DebugHandler {
private HashMap<Filename, String> sources;
private WeakHashMap<FunctionBody, FunctionMap> maps;
private DebugHandler debugger;
public synchronized boolean attachDebugger(DebugHandler debugger) {
if (this.debugger != null) return false;
if (sources != null) {
for (var source : sources.entrySet()) debugger.onSourceLoad(source.getKey(), source.getValue());
}
if (maps != null) {
for (var map : maps.entrySet()) debugger.onFunctionLoad(map.getKey(), map.getValue());
}
this.debugger = debugger;
return true;
}
public boolean detachDebugger(DebugHandler debugger) {
if (this.debugger != debugger) return false;
return detachDebugger();
}
public boolean detachDebugger() {
this.debugger = null;
return true;
}
public DebugHandler debugger() {
return debugger;
}
public FunctionMap getMap(Environment env, FunctionBody func) {
if (maps == null) return null;
return maps.get(func);
}
public void onFramePop(Environment env, Frame frame) {
if (debugger != null) debugger.onFramePop(env, frame);
}
public void onFramePush(Environment env, Frame frame) {
if (debugger != null) debugger.onFramePush(env, frame);
}
@Override public boolean onInstruction(Environment env, Frame frame, Instruction instruction, Value returnVal, EngineException error, boolean caught) {
if (debugger != null) return debugger.onInstruction(env, frame, instruction, returnVal, error, caught);
else return false;
}
@Override public void onSourceLoad(Filename filename, String source) {
if (debugger != null) debugger.onSourceLoad(filename, source);
if (sources != null) sources.put(filename, source);
}
@Override public void onFunctionLoad(FunctionBody func, FunctionMap map) {
if (maps != null) maps.put(func, map);
if (debugger != null) debugger.onFunctionLoad(func, map);
}
private SimpleDebugHandler(boolean enabled) {
if (enabled) {
sources = new HashMap<>();
maps = new WeakHashMap<>();
}
}
public SimpleDebugHandler() {
this(true);
}
}

View File

@@ -1,39 +0,0 @@
package me.topchetoeu.j2s.repl.debug;
import me.topchetoeu.j2s.common.Environment;
import me.topchetoeu.j2s.runtime.Frame;
import me.topchetoeu.j2s.runtime.values.Value;
import me.topchetoeu.j2s.runtime.values.objects.ArrayLikeValue;
public class StackObject extends ArrayLikeValue {
public final Frame frame;
@Override public Value get(int i) {
if (!has(i)) return null;
return frame.stack[i];
}
@Override public boolean set(Environment env, int i, Value val) {
if (!has(i)) return false;
frame.stack[i] = val;
return true;
}
@Override public boolean has(int i) {
return i >= 0 && i < frame.stackPtr;
}
@Override public boolean remove(int i) {
return false;
}
@Override public boolean setSize(int val) {
return false;
}
@Override public int size() {
return frame.stackPtr;
}
// @Override public void set(int i, Value val) {
// }
public StackObject(Frame frame) {
super();
this.frame = frame;
}
}

View File

@@ -1,19 +0,0 @@
package me.topchetoeu.j2s.repl.debug;
import me.topchetoeu.j2s.compilation.json.JSON;
import me.topchetoeu.j2s.compilation.json.JSONMap;
public class V8Error {
public final String message;
public V8Error(String message) {
this.message = message;
}
@Override
public String toString() {
return JSON.stringify(new JSONMap().set("error", new JSONMap()
.set("message", message)
));
}
}

View File

@@ -1,22 +0,0 @@
package me.topchetoeu.j2s.repl.debug;
import me.topchetoeu.j2s.compilation.json.JSON;
import me.topchetoeu.j2s.compilation.json.JSONMap;
public class V8Event {
public final String name;
public final JSONMap params;
public V8Event(String name, JSONMap params) {
this.name = name;
this.params = params;
}
@Override
public String toString() {
return JSON.stringify(new JSONMap()
.set("method", name)
.set("params", params)
);
}
}

View File

@@ -1,50 +0,0 @@
package me.topchetoeu.j2s.repl.debug;
import java.util.Map;
import me.topchetoeu.j2s.compilation.json.JSON;
import me.topchetoeu.j2s.compilation.json.JSONElement;
import me.topchetoeu.j2s.compilation.json.JSONMap;
public class V8Message {
public final String name;
public final int id;
public final JSONMap params;
public V8Message(String name, int id, Map<String, JSONElement> params) {
this.name = name;
this.params = new JSONMap(params);
this.id = id;
}
public V8Result respond(JSONMap result) {
return new V8Result(id, result);
}
public V8Result respond() {
return new V8Result(id, new JSONMap());
}
public V8Message(JSONMap raw) {
if (!raw.isNumber("id")) throw new IllegalArgumentException("Expected number property 'id'.");
if (!raw.isString("method")) throw new IllegalArgumentException("Expected string property 'method'.");
this.name = raw.string("method");
this.id = (int)raw.number("id");
this.params = raw.contains("params") ? raw.map("params") : new JSONMap();
}
public V8Message(String raw) {
this(JSON.parse(null, raw).map());
}
public JSONMap toMap() {
var res = new JSONMap();
return res;
}
@Override
public String toString() {
return JSON.stringify(new JSONMap()
.set("method", name)
.set("params", params)
.set("id", id)
);
}
}

View File

@@ -1,22 +0,0 @@
package me.topchetoeu.j2s.repl.debug;
import me.topchetoeu.j2s.compilation.json.JSON;
import me.topchetoeu.j2s.compilation.json.JSONMap;
public class V8Result {
public final int id;
public final JSONMap result;
public V8Result(int id, JSONMap result) {
this.id = id;
this.result = result;
}
@Override
public String toString() {
return JSON.stringify(new JSONMap()
.set("id", id)
.set("result", result)
);
}
}

View File

@@ -1,186 +0,0 @@
package me.topchetoeu.j2s.repl.debug;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import me.topchetoeu.j2s.repl.debug.WebSocketMessage.Type;
public class WebSocket implements AutoCloseable {
public long maxLength = 1 << 20;
private Socket socket;
private boolean closed = false;
private OutputStream out() throws IOException {
return socket.getOutputStream();
}
private InputStream in() throws IOException {
return socket.getInputStream();
}
private long readLen(int byteLen) throws IOException {
long res = 0;
if (byteLen == 126) {
res |= in().read() << 8;
res |= in().read();
return res;
}
else if (byteLen == 127) {
res |= in().read() << 56;
res |= in().read() << 48;
res |= in().read() << 40;
res |= in().read() << 32;
res |= in().read() << 24;
res |= in().read() << 16;
res |= in().read() << 8;
res |= in().read();
return res;
}
else return byteLen;
}
private byte[] readMask(boolean has) throws IOException {
if (has) {
return new byte[] {
(byte)in().read(),
(byte)in().read(),
(byte)in().read(),
(byte)in().read()
};
}
else return new byte[4];
}
private void writeLength(int len) throws IOException {
if (len < 126) {
out().write((int)len);
}
else if (len <= 0xFFFF) {
out().write(126);
out().write((int)(len >> 8) & 0xFF);
out().write((int)len & 0xFF);
}
else {
out().write(127);
out().write(0);
out().write(0);
out().write(0);
out().write(0);
out().write((len >> 24) & 0xFF);
out().write((len >> 16) & 0xFF);
out().write((len >> 8) & 0xFF);
out().write(len & 0xFF);
}
}
private synchronized void write(int type, byte[] data) throws IOException {
out().write(type | 0x80);
writeLength(data.length);
out().write(data);
}
public void send(String data) throws IOException {
if (closed) throw new IllegalStateException("Websocket is closed.");
write(1, data.getBytes());
}
public void send(byte[] data) throws IOException {
if (closed) throw new IllegalStateException("Websocket is closed.");
write(2, data);
}
public void send(WebSocketMessage msg) throws IOException {
if (msg.type == Type.Binary) send(msg.binaryData());
else send(msg.textData());
}
public void send(Object data) throws IOException {
if (closed) throw new IllegalStateException("Websocket is closed.");
write(1, data.toString().getBytes());
}
public void close(String reason) {
if (socket != null) {
try {
write(8, reason.getBytes());
socket.close();
}
catch (Throwable e) { }
}
socket = null;
closed = true;
}
public void close() {
close("");
}
private WebSocketMessage fail(String reason) {
System.out.println("WebSocket Error: " + reason);
close(reason);
return null;
}
private byte[] readData() throws IOException {
var maskLen = in().read();
var hasMask = (maskLen & 0x80) != 0;
var len = (int)readLen(maskLen & 0x7F);
var mask = readMask(hasMask);
if (len > maxLength) fail("WebSocket Error: client exceeded configured max message size");
else {
var buff = new byte[len];
if (in().read(buff) < len) fail("WebSocket Error: payload too short");
else {
for (int i = 0; i < len; i++) {
buff[i] ^= mask[(int)(i % 4)];
}
return buff;
}
}
return null;
}
public WebSocketMessage receive() throws IOException {
var data = new ByteArrayOutputStream();
var type = 0;
while (socket != null && !closed) {
var finId = in().read();
if (finId < 0) break;
var fin = (finId & 0x80) != 0;
int id = finId & 0x0F;
if (id == 0x8) { close(); return null; }
if (id >= 0x8) {
if (!fin) return fail("WebSocket Error: client-sent control frame was fragmented");
if (id == 0x9) write(0xA, data.toByteArray());
continue;
}
if (type == 0) type = id;
if (type == 0) return fail("WebSocket Error: client used opcode 0x00 for first fragment");
var buff = readData();
if (buff == null) break;
if (data.size() + buff.length > maxLength) return fail("WebSocket Error: client exceeded configured max message size");
data.write(buff);
if (!fin) continue;
var raw = data.toByteArray();
if (type == 1) {
return new WebSocketMessage(new String(raw));
}
else return new WebSocketMessage(raw);
}
return null;
}
public WebSocket(Socket socket) {
this.socket = socket;
}
}

View File

@@ -1,29 +0,0 @@
package me.topchetoeu.j2s.repl.debug;
public class WebSocketMessage {
public static enum Type {
Text,
Binary,
}
public final Type type;
private final Object data;
public final String textData() {
if (type != Type.Text) throw new IllegalStateException("Message is not text.");
return (String)data;
}
public final byte[] binaryData() {
if (type != Type.Binary) throw new IllegalStateException("Message is not binary.");
return (byte[])data;
}
public WebSocketMessage(String data) {
this.type = Type.Text;
this.data = data;
}
public WebSocketMessage(byte[] data) {
this.type = Type.Binary;
this.data = data;
}
}