From d20df669820c93d135dfd56ab7b3eb9627412f01 Mon Sep 17 00:00:00 2001 From: TopchetoEU <36534413+TopchetoEU@users.noreply.github.com> Date: Sun, 29 Oct 2023 12:25:33 +0200 Subject: [PATCH] feat: improve vscode debugging compatibility --- .../jscript/engine/debug/DebugHandler.java | 2 +- .../jscript/engine/debug/DebugServer.java | 2 +- .../jscript/engine/debug/SimpleDebugger.java | 95 ++++++++++++++----- .../jscript/engine/debug/WebSocket.java | 6 ++ src/me/topchetoeu/jscript/json/JSON.java | 5 +- 5 files changed, 83 insertions(+), 27 deletions(-) diff --git a/src/me/topchetoeu/jscript/engine/debug/DebugHandler.java b/src/me/topchetoeu/jscript/engine/debug/DebugHandler.java index 7d4e2f9..fbcc838 100644 --- a/src/me/topchetoeu/jscript/engine/debug/DebugHandler.java +++ b/src/me/topchetoeu/jscript/engine/debug/DebugHandler.java @@ -25,11 +25,11 @@ public interface DebugHandler { void getProperties(V8Message msg); void releaseObjectGroup(V8Message msg); + void releaseObject(V8Message msg); /** * This method might not execute the actual code for well-known requests */ void callFunctionOn(V8Message msg); - // void nodeWorkerEnable(V8Message msg); void runtimeEnable(V8Message msg); } diff --git a/src/me/topchetoeu/jscript/engine/debug/DebugServer.java b/src/me/topchetoeu/jscript/engine/debug/DebugServer.java index c816f3e..02e7176 100644 --- a/src/me/topchetoeu/jscript/engine/debug/DebugServer.java +++ b/src/me/topchetoeu/jscript/engine/debug/DebugServer.java @@ -59,7 +59,6 @@ public class DebugServer { try { msg = new V8Message(raw.textData()); - // System.out.println(msg.name + ": " + JSON.stringify(msg.params)); } catch (SyntaxException e) { ws.send(new V8Error(e.getMessage())); @@ -90,6 +89,7 @@ public class DebugServer { 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 "NodeWorker.enable": debugger.nodeWorkerEnable(msg); continue; diff --git a/src/me/topchetoeu/jscript/engine/debug/SimpleDebugger.java b/src/me/topchetoeu/jscript/engine/debug/SimpleDebugger.java index a900973..156e389 100644 --- a/src/me/topchetoeu/jscript/engine/debug/SimpleDebugger.java +++ b/src/me/topchetoeu/jscript/engine/debug/SimpleDebugger.java @@ -40,7 +40,13 @@ import me.topchetoeu.jscript.lib.GeneratorLib.Generator; public class SimpleDebugger implements Debugger { public static final String CHROME_GET_PROP_FUNC = "function s(e){let t=this;const n=JSON.parse(e);for(let e=0,i=n.length;e>\",i=e.call(this,r);if(i!==r)return String(i)}catch(r){return`<>${JSON.stringify([String(r),\"object\"])}`}if(typeof this==\"object\"&&this){let r;for(let i of[Symbol.for(\"debug.description\"),Symbol.for(\"nodejs.util.inspect.custom\")])try{r=this[i]();break}catch{}if(!r&&!String(this.toString).includes(\"[native code]\")&&(r=String(this)),r&&!r.startsWith(\"[object \"))return r.length>=t?r.slice(0,t)+\"\\u2026\":r}\n ;\n\n}"; + public static final String VSCODE_STRINGIFY_PROPS = "function(...runtimeArgs){\n let t = 1024; let e = null;\n let r={},i=\"<>\";if(typeof this!=\"object\"||!this)return r;for(let[n,s]of Object.entries(this)){if(e)try{let o=e.call(s,i);if(o!==i){r[n]=String(o);continue}}catch(o){r[n]=`<>${JSON.stringify([String(o),n])}`;continue}if(typeof s==\"object\"&&s){let o;for(let a of runtimeArgs[0])try{o=s[a]();break}catch{}!o&&!String(s.toString).includes(\"[native code]\")&&(o=String(s)),o&&!o.startsWith(\"[object \")&&(r[n]=o.length>=t?o.slice(0,t)+\"\\u2026\":o)}}return r\n ;\n\n}"; + public static final String VSCODE_SYMBOL_REQUEST = "function(){return[Symbol.for(\"debug.description\"),Symbol.for(\"nodejs.util.inspect.custom\")]\n}"; + public static final String VSCODE_SHALLOW_COPY = "function(){let t={__proto__:this.__proto__\n},e=Object.getOwnPropertyNames(this);for(let r=0;r>>0;if(String(n>>>0)===i&&n>>>0!==4294967295)continue;let s=Object.getOwnPropertyDescriptor(this,i);s&&Object.defineProperty(t,i,s)}return t}"; + public static final String VSCODE_FLATTEN_ARRAY = "function(t,e){let r={\n},i=t===-1?0:t,n=e===-1?this.length:t+e;for(let s=i;s idToBptCand = new HashMap<>(); private HashMap idToBreakpoint = new HashMap<>(); @@ -417,7 +424,7 @@ public class SimpleDebugger implements Debugger { env.global = new GlobalScope(local); var ctx = new Context(engine).pushEnv(env); - var awaiter = engine.pushMsg(false, ctx, new Filename("temp", "exec"), code, codeFrame.frame.thisArg, codeFrame.frame.args); + var awaiter = engine.pushMsg(false, ctx, new Filename("temp", "exec"), "(" + code + ")", codeFrame.frame.thisArg, codeFrame.frame.args); engine.run(true); @@ -587,14 +594,22 @@ public class SimpleDebugger implements Debugger { releaseGroup(group); ws.send(msg.respond()); } + @Override + public void releaseObject(V8Message msg) { + var id = Integer.parseInt(msg.params.string("objectId")); + var obj = idToObject.remove(id); + if (obj != null) objectToId.remove(obj); + ws.send(msg.respond()); + } @Override public void getProperties(V8Message msg) { var obj = idToObject.get(Integer.parseInt(msg.params.string("objectId"))); - var own = msg.params.bool("ownProperties") || true; + var own = msg.params.bool("ownProperties"); + var accessorPropertiesOnly = msg.params.bool("accessorPropertiesOnly", false); var currOwn = true; var res = new JSONList(); - while (obj != null) { + while (obj != emptyObject && obj != null) { var ctx = objectToCtx.get(obj); for (var key : obj.keys(true)) { @@ -611,7 +626,7 @@ public class SimpleDebugger implements Debugger { propDesc.set("isOwn", currOwn); res.add(propDesc); } - else { + else if (!accessorPropertiesOnly) { propDesc.set("name", Values.toString(ctx, key)); propDesc.set("value", serializeObj(ctx, obj.getMember(ctx, key))); propDesc.set("writable", obj.memberWritable(key)); @@ -624,14 +639,16 @@ public class SimpleDebugger implements Debugger { obj = obj.getPrototype(ctx); - var protoDesc = new JSONMap(); - protoDesc.set("name", "__proto__"); - protoDesc.set("value", serializeObj(ctx, obj == null ? Values.NULL : obj)); - protoDesc.set("writable", true); - protoDesc.set("enumerable", false); - protoDesc.set("configurable", false); - protoDesc.set("isOwn", currOwn); - res.add(protoDesc); + if (currOwn) { + var protoDesc = new JSONMap(); + protoDesc.set("name", "__proto__"); + protoDesc.set("value", serializeObj(ctx, obj == null ? Values.NULL : obj)); + protoDesc.set("writable", true); + protoDesc.set("enumerable", false); + protoDesc.set("configurable", false); + protoDesc.set("isOwn", currOwn); + res.add(protoDesc); + } currOwn = false; if (own) break; @@ -641,10 +658,24 @@ public class SimpleDebugger implements Debugger { } @Override public void callFunctionOn(V8Message msg) { var src = msg.params.string("functionDeclaration"); - var args = msg.params.list("arguments", new JSONList()).stream().map(v -> v.map()).map(this::deserializeArgument).collect(Collectors.toList()); + var args = msg.params + .list("arguments", new JSONList()) + .stream() + .map(v -> v.map()) + .map(this::deserializeArgument) + .collect(Collectors.toList()); + var thisArg = idToObject.get(Integer.parseInt(msg.params.string("objectId"))); var ctx = objectToCtx.get(thisArg); + while (true) { + var start = src.lastIndexOf("//# sourceURL="); + if (start < 0) break; + var end = src.indexOf("\n", start); + if (end < 0) src = src.substring(0, start); + else src = src.substring(0, start) + src.substring(end + 1); + } + switch (src) { case CHROME_GET_PROP_FUNC: { var path = JSON.parse(new Filename("tmp", "json"), (String)args.get(0)).list(); @@ -653,6 +684,23 @@ public class SimpleDebugger implements Debugger { ws.send(msg.respond(new JSONMap().set("result", serializeObj(ctx, res)))); return; } + case VSCODE_STRINGIFY_VAL: + case VSCODE_STRINGIFY_PROPS: + case VSCODE_SHALLOW_COPY: + ws.send(msg.respond(new JSONMap().set("result", serializeObj(ctx, emptyObject)))); + break; + case VSCODE_FLATTEN_ARRAY: { + ws.send(msg.respond(new JSONMap().set("result", serializeObj(ctx, thisArg)))); + break; + } + case VSCODE_SYMBOL_REQUEST: + ws.send(msg.respond(new JSONMap().set("result", serializeObj(ctx, new ArrayValue(ctx))))); + break; + case VSCODE_CALL: { + var func = (FunctionValue)(args.size() < 1 ? null : args.get(0)); + ws.send(msg.respond(new JSONMap().set("result", serializeObj(ctx, func.call(ctx, thisArg))))); + break; + } default: ws.send(new V8Error("A non well-known function was used with callFunctionOn.")); break; @@ -728,11 +776,7 @@ public class SimpleDebugger implements Debugger { break; case STEPPING_OVER: if (stepOutFrame.frame == frame.frame) { - if (returnVal != Runners.NO_RETURN) { - state = State.STEPPING_OUT; - return false; - } - else if (isBreakpointable && ( + if (isBreakpointable && ( !loc.filename().equals(prevLocation.filename()) || loc.line() != prevLocation.line() )) pauseDebug(ctx, null); @@ -754,10 +798,13 @@ public class SimpleDebugger implements Debugger { if (StackData.frames(ctx).size() == 0) resume(State.RESUMED); else if (stepOutFrame != null && stepOutFrame.frame == frame && - (state == State.STEPPING_OUT || state == State.STEPPING_IN) + (state == State.STEPPING_OUT || state == State.STEPPING_IN || state == State.STEPPING_OVER) ) { - pauseDebug(ctx, null); - updateNotifier.await(); + // if (state == State.STEPPING_OVER) state = State.STEPPING_IN; + // else { + pauseDebug(ctx, null); + updateNotifier.await(); + // } } } diff --git a/src/me/topchetoeu/jscript/engine/debug/WebSocket.java b/src/me/topchetoeu/jscript/engine/debug/WebSocket.java index 6e6efa9..f9e5c3c 100644 --- a/src/me/topchetoeu/jscript/engine/debug/WebSocket.java +++ b/src/me/topchetoeu/jscript/engine/debug/WebSocket.java @@ -109,6 +109,8 @@ public class WebSocket implements AutoCloseable { else send(msg.textData()); } public void send(Object data) { + // TODO: Remove + System.out.println("SEND: " + data); if (closed) throw new IllegalStateException("Object is closed."); write(1, data.toString().getBytes()); } @@ -190,6 +192,10 @@ public class WebSocket implements AutoCloseable { if (!fin) continue; var raw = data.toByteArray(); + // TODO: Remove + System.out.println("RECEIVED: " + new String(raw)); + + if (type == 1) return new WebSocketMessage(new String(raw)); else return new WebSocketMessage(raw); } diff --git a/src/me/topchetoeu/jscript/json/JSON.java b/src/me/topchetoeu/jscript/json/JSON.java index 7d58bf4..a16538f 100644 --- a/src/me/topchetoeu/jscript/json/JSON.java +++ b/src/me/topchetoeu/jscript/json/JSON.java @@ -83,8 +83,11 @@ public class JSON { else return res.transform(); } public static ParseRes parseNumber(Filename filename, List tokens, int i) { + var minus = Parsing.isOperator(tokens, i, Operator.SUBTRACT); + if (minus) i++; + var res = Parsing.parseNumber(filename, tokens, i); - if (res.isSuccess()) return ParseRes.res((Double)res.result.value, res.n); + if (res.isSuccess()) return ParseRes.res((minus ? -1 : 1) * (Double)res.result.value, res.n + (minus ? 1 : 0)); else return res.transform(); } public static ParseRes parseBool(Filename filename, List tokens, int i) {