AngelscriptDebuggerServer/include/angelscriptDebugger.hpp

1796 lines
66 KiB
C++

// begin --- DebugAdapterProtocol ---
// end --- DebugAdapterProtocol ---
// begin --- AngelscriptDebugger.cpp ---
// begin --- AngelscriptDebugger.hpp ---
#ifndef ANGELSCRIPTDEBUGGER_ANGELSCRIPTDEBUGGER_HPP
#define ANGELSCRIPTDEBUGGER_ANGELSCRIPTDEBUGGER_HPP
#include <angelscript.h>
#include <iostream>
#include <unordered_set>
#include <variant>
// begin --- Breakpoint.hpp ---
#ifndef ANGELSCRIPTDEBUGGER_BREAKPOINT_HPP
#define ANGELSCRIPTDEBUGGER_BREAKPOINT_HPP
#include <string>
#include <limits>
struct Breakpoint {
std::string Section;
int32_t Line;
bool operator==(const Breakpoint& other) const { return (Section == other.Section && Line == other.Line); }
};
namespace std {
template <> struct hash<Breakpoint> {
std::size_t operator()(const Breakpoint& k) const {
using std::hash;
using std::size_t;
using std::string;
return ((hash<string>()(k.Section) ^ (hash<int32_t>()(k.Line) << 1)) >> 1);
}
};
}
#endif // ANGELSCRIPTDEBUGGER_BREAKPOINT_HPP
// end --- Breakpoint.hpp ---
// begin --- BaseProtocol.hpp ---
#ifndef ANGELSCRIPTDEBUGGER_BASEPROTOCOL_HPP
#define ANGELSCRIPTDEBUGGER_BASEPROTOCOL_HPP
#include <memory>
#include <optional>
#include <string>
#include <utility>
#include <vector>
// begin --- Utils.hpp ---
#ifndef ANGELSCRIPTDEBUGGER_UTILS_HPP
#define ANGELSCRIPTDEBUGGER_UTILS_HPP
#define JsonSerializeOptional(obj, field) \
if (field.has_value()) \
obj[#field] = field.value()
#define JsonDeserializeOptional(obj, field) \
if (!obj[#field].empty()) \
field = obj[#field]
#endif // ANGELSCRIPTDEBUGGER_UTILS_HPP
// end --- Utils.hpp ---
namespace DebugAdapterProtocol {
static size_t current_sequence = 0;
struct ProtocolMessage {
explicit ProtocolMessage(std::string t) : seq(current_sequence++), type(std::move(t)) {}
ProtocolMessage(std::string t, size_t s) : seq(s), type(std::move(t)) {}
size_t seq;
std::string type{"event"};
static ProtocolMessage* FromJson(nlohmann::json& j);
[[nodiscard]] virtual nlohmann::json ToJson() const { return {{"seq", seq}, {"type", type}}; }
virtual ~ProtocolMessage() = default;
};
struct RequestArguments {
RequestArguments(){};
explicit RequestArguments(nlohmann::json&) {}
virtual ~RequestArguments() = default;
virtual nlohmann::json ToJson() const { return nlohmann::json ::object(); }
};
struct Request : public ProtocolMessage {
Request() : ProtocolMessage("request") {}
virtual std::string GetCommand() const = 0;
virtual std::optional<RequestArguments*> GetArguments() const = 0;
static ProtocolMessage* FromJson(nlohmann::json& j);
[[nodiscard]] nlohmann::json ToJson() const override {
auto o = ProtocolMessage::ToJson();
o["command"] = GetCommand();
if (GetArguments().has_value()) {
o["body"] = GetArguments().value()->ToJson();
}
return o;
}
};
template <class T>
concept IsRequestArguments = std::is_base_of<RequestArguments, T>::value;
template <IsRequestArguments T, const char* S> struct DefinedRequest : public Request {
std::optional<T*> arguments;
static ProtocolMessage* FromJson(nlohmann::json& j);
~DefinedRequest() override {
if (arguments.has_value()) {
delete arguments.value();
}
}
[[nodiscard]] std::string GetCommand() const override { return S; }
[[nodiscard]] std::optional<RequestArguments*> GetArguments() const override { return arguments; }
};
struct EventBody {
EventBody() = default;
explicit EventBody(nlohmann::json&) {}
virtual ~EventBody() = default;
[[nodiscard]] virtual nlohmann::json ToJson() const { return nlohmann::json::object(); };
};
template <class T>
concept IsEventBody = std::is_base_of<EventBody, T>::value;
template <IsEventBody T, const char* S> struct Event : public ProtocolMessage {
Event() : ProtocolMessage("event") {}
explicit Event(T* b) : ProtocolMessage("event"), body(b) {}
std::optional<T*> body;
~Event() override {
if (body.has_value()) {
delete body.value();
}
}
[[nodiscard]] nlohmann::json ToJson() const override {
auto o = ProtocolMessage::ToJson();
o["event"] = S;
if (body.has_value()) {
o["body"] = body.value()->ToJson();
}
return o;
}
};
struct ResponseBody {
ResponseBody() = default;
explicit ResponseBody(nlohmann::json&) {}
virtual ~ResponseBody() = default;
[[nodiscard]] virtual nlohmann::json ToJson() const { return nlohmann::json::object(); };
};
template <class T>
concept IsResponseBody = std::is_base_of<ResponseBody, T>::value;
struct Response : public ProtocolMessage {
Response(size_t seq) : ProtocolMessage("response", seq), request_seq(seq) {}
size_t request_seq;
bool success = true;
std::optional<std::string> message;
virtual std::string GetCommand() const = 0;
virtual std::optional<ResponseBody*> GetBody() const = 0;
};
template <IsResponseBody T, const char* S> struct DefinedResponse : public Response {
DefinedResponse(size_t seq) : Response(seq) {}
std::optional<T*> body;
[[nodiscard]] std::string GetCommand() const override { return S; }
[[nodiscard]] std::optional<ResponseBody*> GetBody() const override { return body; }
[[nodiscard]] nlohmann::json ToJson() const override {
auto o = ProtocolMessage::ToJson();
o["request_seq"] = request_seq;
o["success"] = success;
o["command"] = GetCommand();
JsonSerializeOptional(o, message);
if (body.has_value()) {
o["body"] = body.value()->ToJson();
}
return o;
}
};
}
#endif // ANGELSCRIPTDEBUGGER_BASEPROTOCOL_HPP
// end --- BaseProtocol.hpp ---
class AngelscriptDebugger {
public:
~AngelscriptDebugger() {
_server->close();
delete _server;
}
void Run(uint16_t port);
void Stop() { _server->close(); }
void RegisterContext(asIScriptContext* ctx);
void Continue() {
std::cout << "Continuing from suspension" << std::endl;
_storedVariableReferences.clear();
while (!_pausedContexts.empty()) {
auto* ctx = _pausedContexts[_pausedContexts.size() - 1];
_pausedContexts.pop_back();
std::thread([](asIScriptContext* ctx) { ctx->Execute(); }, ctx).detach();
}
}
bool HasDebuggerAttached() const {
for (const auto& c : _connections) {
if (c->is_open()) {
return true;
}
}
return false;
}
size_t StoreVariable(void* address, int typeId, const std::optional<std::function<void(void* address)>>& release) {
_storedVariableReferences.emplace_back(std::make_unique<PointerVariable>(address, typeId, release));
return _storedVariableReferences.size();
}
private:
static void on_line_callback(asIScriptContext* ctx, AngelscriptDebugger* dbg);
static void on_exception_callback(asIScriptContext* ctx, AngelscriptDebugger* d);
void EnterBreakpoint(asIScriptContext* ctx, const std::string& section, size_t line);
[[noreturn]] void AcceptLoop();
void ClientLoop(asio::ip::tcp::socket&);
void Send(DebugAdapterProtocol::ProtocolMessage* msg);
void Send(asio::ip::tcp::socket* client, DebugAdapterProtocol::ProtocolMessage* msg);
void OnMessage(asio::ip::tcp::socket* client, DebugAdapterProtocol::ProtocolMessage* msg);
void OnRequest(asio::ip::tcp::socket* client, DebugAdapterProtocol::Request* msg);
std::string GetResolvedScriptPath(const char* scriptSection);
asio::io_context _ioContext;
asio::ip::tcp::acceptor* _server;
std::unordered_set<asio::ip::tcp::socket*> _connections;
std::unordered_map<std::string, std::unordered_set<size_t>> _breakpoints;
std::vector<asIScriptContext*> _pausedContexts;
std::optional<std::string> _scriptPath;
struct StackScope {
size_t StackLevel;
uint8_t Type;
StackScope(size_t stackLevel, uint8_t type) : StackLevel(stackLevel), Type(type) {}
};
struct PointerVariable {
void* Address;
int TypeID;
std::optional<std::function<void(void*)>> Release;
PointerVariable(void* address, int typeId, std::optional<std::function<void(void*)>> release)
: Address(address), TypeID(typeId), Release(release) {}
~PointerVariable() {
if (Release.has_value()) {
Release.value()(Address);
}
}
};
std::vector<std::variant<std::unique_ptr<StackScope>, std::unique_ptr<PointerVariable>>> _storedVariableReferences;
};
#endif // ANGELSCRIPTDEBUGGER_ANGELSCRIPTDEBUGGER_HPP
// end --- AngelscriptDebugger.hpp ---
// begin --- ASVariableFormatter.hpp ---
#ifndef ANGELSCRIPTDEBUGGER_ASVARIABLEFORMATTER_HPP
#define ANGELSCRIPTDEBUGGER_ASVARIABLEFORMATTER_HPP
#include <angelscript.h>
// begin --- Types.hpp ---
#ifndef ANGELSCRIPTDEBUGGER_TYPES_HPP
#define ANGELSCRIPTDEBUGGER_TYPES_HPP
#include <optional>
#include <string>
namespace DebugAdapterProtocol {
struct Source {
std::optional<std::string> name;
std::optional<std::string> path;
std::optional<size_t> sourceReference;
Source() {}
Source(std::string path) : path(path) {}
explicit Source(nlohmann::json& j) {
JsonDeserializeOptional(j, name);
JsonDeserializeOptional(j, path);
JsonDeserializeOptional(j, sourceReference);
}
[[nodiscard]] nlohmann::json ToJson() const {
nlohmann::json o = nlohmann::json::object();
JsonSerializeOptional(o, name);
JsonSerializeOptional(o, path);
JsonSerializeOptional(o, sourceReference);
return o;
}
};
struct Breakpoint {
std::optional<size_t> id;
bool verified;
std::optional<std::string> message;
std::optional<Source> source;
std::optional<size_t> line;
std::optional<size_t> column;
std::optional<size_t> endLine;
std::optional<size_t> endColumn;
std::optional<std::string> instructionReference;
std::optional<size_t> offset;
Breakpoint(size_t id, Source& s, size_t line) : id(id), verified(true), source(s), line(line) {}
[[nodiscard]] nlohmann::json ToJson() const {
nlohmann::json o;
JsonSerializeOptional(o, id);
o["verified"] = verified;
JsonSerializeOptional(o, message);
if (source.has_value()) {
o["source"] = source.value().ToJson();
}
JsonSerializeOptional(o, line);
JsonSerializeOptional(o, column);
JsonSerializeOptional(o, endLine);
JsonSerializeOptional(o, endColumn);
JsonSerializeOptional(o, instructionReference);
JsonSerializeOptional(o, offset);
return o;
}
};
struct BreakpointLocation {
size_t line;
std::optional<size_t> column;
std::optional<size_t> endLine;
std::optional<size_t> endColumn;
};
struct SourceBreakpoint {
size_t line;
std::optional<size_t> column;
std::optional<std::string> condition;
std::optional<std::string> hitCondition;
std::optional<std::string> logMessage;
SourceBreakpoint(nlohmann::json j) {
line = j["line"];
JsonDeserializeOptional(j, column);
JsonDeserializeOptional(j, condition);
JsonDeserializeOptional(j, hitCondition);
JsonDeserializeOptional(j, logMessage);
}
[[nodiscard]] nlohmann::json ToJson() const {
nlohmann::json o;
o["line"] = line;
JsonSerializeOptional(o, column);
JsonSerializeOptional(o, condition);
JsonSerializeOptional(o, hitCondition);
JsonSerializeOptional(o, logMessage);
return o;
}
};
struct Module {
std::string id;
std::string name;
std::optional<std::string> path;
std::optional<bool> isOptimized;
std::optional<bool> isUserCode;
std::optional<std::string> version;
std::optional<std::string> symbolStatus;
std::optional<std::string> symbolFilePath;
std::optional<std::string> dateTimeStamp;
std::optional<std::string> addressRange;
[[nodiscard]] nlohmann::json ToJson() const {
nlohmann::json o;
o["id"] = id;
o["name"] = name;
JsonSerializeOptional(o, path);
JsonSerializeOptional(o, isOptimized);
JsonSerializeOptional(o, isUserCode);
JsonSerializeOptional(o, version);
JsonSerializeOptional(o, symbolStatus);
JsonSerializeOptional(o, symbolFilePath);
JsonSerializeOptional(o, dateTimeStamp);
JsonSerializeOptional(o, addressRange);
return o;
}
};
struct Thread {
size_t id;
std::string name;
Thread(size_t id, std::string name) : id(id), name(name) {}
[[nodiscard]] nlohmann::json ToJson() const {
nlohmann::json o;
o["id"] = id;
o["name"] = name;
return o;
}
};
struct StackFrame {
size_t id;
std::string name;
std::optional<Source> source;
size_t line;
size_t column;
std::optional<size_t> endLine;
std::optional<size_t> endColumn;
std::optional<bool> canRestart = false;
std::optional<std::string> instructionPointerReference;
StackFrame(){};
StackFrame(size_t id, std::string name, Source source, size_t line, size_t column)
: id(id), name(name), source(source), line(line), column(column) {}
[[nodiscard]] nlohmann::json ToJson() const {
nlohmann::json o;
o["id"] = id;
o["name"] = name;
if (source.has_value()) {
o["source"] = source.value().ToJson();
}
o["line"] = line;
o["column"] = column;
JsonSerializeOptional(o, endLine);
JsonSerializeOptional(o, endColumn);
JsonSerializeOptional(o, canRestart);
JsonSerializeOptional(o, instructionPointerReference);
return o;
}
};
struct Scope {
std::string name;
std::optional<std::string> presentationHint;
size_t variablesReference;
size_t namedVariables;
size_t indexedVariables = 0;
bool expensive = false;
std::optional<Source> source;
std::optional<size_t> line;
std::optional<size_t> column;
std::optional<size_t> endLine;
std::optional<size_t> endColumn;
Scope(std::string name, size_t reference, std::string presentationHint, size_t namedVariables)
: name(std::move(name)), presentationHint(std::move(presentationHint)), variablesReference(reference),
namedVariables(namedVariables) {}
[[nodiscard]] nlohmann::json ToJson() const {
nlohmann::json o;
o["name"] = name;
JsonSerializeOptional(o, presentationHint);
o["variablesReference"] = variablesReference;
o["namedVariables"] = namedVariables;
o["indexedVariables"] = indexedVariables;
o["expensive"] = expensive;
if (source.has_value()) {
o["source"] = source.value().ToJson();
}
JsonSerializeOptional(o, line);
JsonSerializeOptional(o, column);
JsonSerializeOptional(o, endLine);
JsonSerializeOptional(o, endColumn);
return o;
}
};
struct VariablePresentationHint {
std::optional<std::string> kind;
std::optional<std::vector<std::string>> attributes;
std::optional<std::string> visibility;
[[nodiscard]] nlohmann::json ToJson() const {
nlohmann::json o;
JsonSerializeOptional(o, kind);
JsonSerializeOptional(o, attributes);
JsonSerializeOptional(o, visibility);
return o;
}
};
struct Variable {
std::string name;
std::string value;
std::optional<std::string> type;
std::optional<VariablePresentationHint> presentationHint;
std::optional<std::string> evaluateName;
std::optional<size_t> variablesReference = 0;
std::optional<size_t> namedVariables = 0;
std::optional<size_t> indexedVariables = 0;
std::optional<std::string> memoryReference;
[[nodiscard]] nlohmann::json ToJson() const {
nlohmann::json o;
o["name"] = name;
o["value"] = value;
JsonSerializeOptional(o, type);
if (presentationHint.has_value()) {
o["presentationHint"] = presentationHint.value().ToJson();
}
JsonSerializeOptional(o, evaluateName);
JsonSerializeOptional(o, variablesReference);
JsonSerializeOptional(o, namedVariables);
JsonSerializeOptional(o, indexedVariables);
JsonSerializeOptional(o, memoryReference);
return o;
}
Variable(std::string name, std::string value, std::string type,
std::optional<VariablePresentationHint> presentationHint = {})
: name(std::move(name)), value(std::move(value)), type(std::move(type)),
presentationHint(presentationHint) {}
static Variable FromString(std::string name, std::string value) {
return Variable(
name, value, "string",
VariablePresentationHint{.kind = "data", .attributes = std::vector<std::string>{"rawString"}});
}
static Variable FromNull(std::string name, std::string type) {
return Variable(name, "null", type,
VariablePresentationHint{.kind = "data", .attributes = std::vector<std::string>{}});
}
static Variable FromPointer(std::string name, std::string type, std::string display, size_t variableReference) {
auto v = Variable(
std::move(name), std::move(display), type,
VariablePresentationHint{.kind = "class", .attributes = std::vector<std::string>{"hasObjectId"}});
v.variablesReference = variableReference;
return v;
}
};
}
#endif // ANGELSCRIPTDEBUGGER_TYPES_HPP
// end --- Types.hpp ---
class ASVariableFormatter {
public:
static nlohmann::json GetAsJsonValue(asIScriptEngine* engine, int, void*, int32_t depth = 0);
static DebugAdapterProtocol::Variable GetAsDAPVariable(asIScriptEngine* engine, asIScriptContext* ctx,
AngelscriptDebugger* debugger, const std::string& name, int,
void*);
static void GetChildDAPVariables(std::vector<DebugAdapterProtocol::Variable>&, asIScriptEngine* engine,
asIScriptContext* ctx, AngelscriptDebugger* debugger, int, void*);
};
#endif // ANGELSCRIPTDEBUGGER_ASVARIABLEFORMATTER_HPP
// end --- ASVariableFormatter.hpp ---
// begin --- Events.hpp ---
#ifndef ANGELSCRIPTDEBUGGER_EVENTS_HPP
#define ANGELSCRIPTDEBUGGER_EVENTS_HPP
namespace DebugAdapterProtocol {
#define EventDefinition(name, jsonName, ...) \
static const char __c##name[] = jsonName; \
struct name##EventBody : public EventBody __VA_ARGS__; \
using name##Event = Event<name##EventBody, __c##name>
EventDefinition(Breakpoint, "breakpoint", {
std::string reason;
Breakpoint breakpoint;
nlohmann::json ToJson() const override {
auto o = EventBody::ToJson();
o["reason"] = reason;
o["breakpoint"] = breakpoint.ToJson();
return o;
}
});
EventDefinition(Continued, "continued", {
size_t threadId;
std::optional<bool> allThreadsContinued;
nlohmann::json ToJson() const override {
auto o = EventBody::ToJson();
o["threadId"] = threadId;
JsonSerializeOptional(o, allThreadsContinued);
return o;
}
});
EventDefinition(Exited, "exited", {
int64_t exitCode;
nlohmann::json ToJson() const override {
auto o = EventBody::ToJson();
o["exitCode"] = exitCode;
return o;
}
});
EventDefinition(Initialized, "initialized", {});
EventDefinition(Invalidated, "invalidated", {
std::optional<std::vector<std::string>> invalidatedAreas;
std::optional<size_t> threadId;
std::optional<size_t> stackFrameId;
nlohmann::json ToJson() const override {
auto o = EventBody::ToJson();
JsonSerializeOptional(o, invalidatedAreas);
JsonSerializeOptional(o, threadId);
JsonSerializeOptional(o, stackFrameId);
return o;
}
});
EventDefinition(LoadedSource, "loadedSource", {
std::string reason;
Source source;
nlohmann::json ToJson() const override {
auto o = EventBody::ToJson();
o["reason"] = reason;
o["source"] = source.ToJson();
return o;
}
});
EventDefinition(Memory, "memory", {
std::string memoryReference;
size_t offset;
size_t count;
nlohmann::json ToJson() const override {
auto o = EventBody::ToJson();
o["memoryReference"] = memoryReference;
o["offset"] = offset;
o["count"] = count;
return o;
}
});
EventDefinition(Module, "module", {
std::string reason;
Module module;
nlohmann::json ToJson() const override {
auto o = EventBody::ToJson();
o["reason"] = reason;
o["module"] = module.ToJson();
return o;
}
});
EventDefinition(Output, "output", {
std::optional<std::string> category;
std::string output;
std::optional<std::string> group;
std::optional<int32_t> variablesReference;
std::optional<Source> source;
std::optional<size_t> line;
std::optional<size_t> column;
nlohmann::json ToJson() const override {
auto o = EventBody::ToJson();
JsonSerializeOptional(o, category);
o["output"] = output;
JsonSerializeOptional(o, group);
JsonSerializeOptional(o, variablesReference);
if (source.has_value()) {
o["source"] = source.value().ToJson();
}
JsonSerializeOptional(o, line);
JsonSerializeOptional(o, column);
return o;
}
});
EventDefinition(Process, "process", {
std::string name;
std::optional<size_t> systemProcessId;
std::optional<bool> isLocalProcess;
std::optional<std::string> startMethod;
std::optional<size_t> pointerSize;
nlohmann::json ToJson() const override {
auto o = EventBody::ToJson();
o["name"] = name;
JsonSerializeOptional(o, systemProcessId);
JsonSerializeOptional(o, isLocalProcess);
JsonSerializeOptional(o, startMethod);
JsonSerializeOptional(o, pointerSize);
return o;
}
});
EventDefinition(ProgressEnd, "progressEnd", {
std::string progressId;
std::optional<std::string> message;
nlohmann::json ToJson() const override {
auto o = EventBody::ToJson();
o["progressId"] = progressId;
JsonSerializeOptional(o, message);
return o;
}
});
EventDefinition(ProgressStart, "progressStart", {
std::string progressId;
std::string title;
std::optional<size_t> requestId;
std::optional<bool> cancellable;
std::optional<std::string> message;
std::optional<float> percentage;
nlohmann::json ToJson() const override {
auto o = EventBody::ToJson();
o["progressId"] = progressId;
o["title"] = title;
JsonSerializeOptional(o, requestId);
JsonSerializeOptional(o, cancellable);
JsonSerializeOptional(o, message);
JsonSerializeOptional(o, percentage);
return o;
}
});
EventDefinition(ProgressUpdate, "progressUpdate", {
std::string progressId;
std::optional<std::string> message;
std::optional<float> percentage;
nlohmann::json ToJson() const override {
auto o = EventBody::ToJson();
o["progressId"] = progressId;
JsonSerializeOptional(o, message);
JsonSerializeOptional(o, percentage);
return o;
}
});
EventDefinition(Stopped, "stopped", {
std::string reason;
std::optional<std::string> description;
std::optional<size_t> number;
std::optional<bool> preserveFocusHint = false;
std::optional<std::string> text;
std::optional<bool> allThreadsStopped = true;
std::optional<std::vector<size_t>> hitBreakpointIds;
explicit StoppedEventBody(size_t breakpoint) {
reason = "breakpoint";
hitBreakpointIds = {breakpoint};
}
nlohmann::json ToJson() const override {
auto o = EventBody::ToJson();
o["reason"] = reason;
JsonSerializeOptional(o, description);
JsonSerializeOptional(o, number);
JsonSerializeOptional(o, preserveFocusHint);
JsonSerializeOptional(o, text);
JsonSerializeOptional(o, allThreadsStopped);
JsonSerializeOptional(o, hitBreakpointIds);
return o;
}
});
EventDefinition(terminated, "terminated", {});
EventDefinition(Thread, "thread", {
std::string reason;
std::optional<size_t> threadId;
nlohmann::json ToJson() const override {
auto o = EventBody::ToJson();
o["reason"] = reason;
JsonSerializeOptional(o, threadId);
return o;
}
});
}
#endif // ANGELSCRIPTDEBUGGER_EVENTS_HPP
// end --- Events.hpp ---
// begin --- Requests.hpp ---
#ifndef ANGELSCRIPTDEBUGGER_REQUESTS_HPP
#define ANGELSCRIPTDEBUGGER_REQUESTS_HPP
#include <memory>
#include <optional>
#include <string>
#include <vector>
namespace DebugAdapterProtocol {
#define RequestDefinition(name, jsonName, ...) \
static const char __c##name[] = jsonName; \
struct name##Arguments : public RequestArguments __VA_ARGS__; \
using name##Request = DefinedRequest<name##Arguments, __c##name>
#define RequestResponseDefinition(name, jsonName, ...) \
static const char __cResponse##name[] = jsonName; \
struct name##ResponseBody : public ResponseBody __VA_ARGS__; \
using name##Response = DefinedResponse<name##ResponseBody, __cResponse##name>
#define EmptyRequest(name, jsonName) \
RequestDefinition(name, jsonName, {explicit name##Arguments(nlohmann::json & j) : RequestArguments(j){}})
RequestDefinition(Attach, "attach", {
size_t port;
std::string scriptPath;
explicit AttachArguments(nlohmann::json & j)
: RequestArguments(j), port(j["port"]), scriptPath(j["scriptPath"]) {}
});
RequestDefinition(BreakpointLocations, "breakpointLocations", {
Source source;
size_t line;
std::optional<size_t> column;
std::optional<size_t> endLine;
std::optional<size_t> endColumn;
BreakpointLocationsArguments() {}
explicit BreakpointLocationsArguments(nlohmann::json & j)
: RequestArguments(j), source(j["source"]), line(j["line"]) {
JsonDeserializeOptional(j, column);
JsonDeserializeOptional(j, endLine);
JsonDeserializeOptional(j, endColumn);
}
[[nodiscard]] nlohmann::json ToJson() const override {
auto o = RequestArguments::ToJson();
o["source"] = source.ToJson();
o["line"] = line;
JsonSerializeOptional(o, column);
JsonSerializeOptional(o, endLine);
JsonSerializeOptional(o, endColumn);
return o;
}
});
EmptyRequest(Completions, "completions");
EmptyRequest(ConfigurationDone, "configurationDone");
EmptyRequest(Continue, "continue");
EmptyRequest(DataBreakpointInfo, "dataBreakpointInfo");
EmptyRequest(Disassemble, "disassemble");
RequestDefinition(Disconnect, "disconnect", {
std::optional<bool> restart;
std::optional<bool> terminateDebuggee;
std::optional<bool> suspendDebuggee;
explicit DisconnectArguments(nlohmann::json & j) : RequestArguments(j) {
JsonDeserializeOptional(j, restart);
JsonDeserializeOptional(j, terminateDebuggee);
JsonDeserializeOptional(j, suspendDebuggee);
}
});
EmptyRequest(Evaluate, "evaluate");
EmptyRequest(ExceptionInfo, "exceptionInfo");
EmptyRequest(Goto, "goto");
EmptyRequest(GotoTargets, "gotoTargets");
EmptyRequest(Initialize, "initialize");
RequestResponseDefinition(Initialize, "initialize", {
std::optional<bool> supportsConfigurationDoneRequest = true;
std::optional<bool> supportsFunctionBreakpoints;
std::optional<bool> supportsConditionalBreakpoints;
std::optional<bool> supportsHitConditionalBreakpoints;
std::optional<bool> supportsEvaluateForHovers;
std::optional<bool> supportsStepBack;
std::optional<bool> supportsSetVariable;
std::optional<bool> supportsRestartFrame;
std::optional<bool> supportsGotoTargetsRequest;
std::optional<bool> supportsStepInTargetsRequest;
std::optional<bool> supportsCompletionsRequest;
std::optional<bool> supportsModulesRequest;
std::optional<bool> supportsRestartRequest;
std::optional<bool> supportsExceptionOptions;
std::optional<bool> supportsValueFormattingOptions;
std::optional<bool> supportsExceptionInfoRequest;
std::optional<bool> supportTerminateDebuggee;
std::optional<bool> supportSuspendDebuggee;
std::optional<bool> supportsDelayedStackTraceLoading;
std::optional<bool> supportsLoadedSourcesRequest;
std::optional<bool> supportsLogPoints;
std::optional<bool> supportsTerminateThreadsRequest;
std::optional<bool> supportsSetExpression;
std::optional<bool> supportsTerminateRequest;
std::optional<bool> supportsDataBreakpoints;
std::optional<bool> supportsReadMemoryRequest;
std::optional<bool> supportsWriteMemoryRequest;
std::optional<bool> supportsDisassembleRequest;
std::optional<bool> supportsCancelRequest;
std::optional<bool> supportsBreakpointLocationsRequest;
std::optional<bool> supportsClipboardContext;
std::optional<bool> supportsSteppingGranularity;
std::optional<bool> supportsInstructionBreakpoints;
std::optional<bool> supportsExceptionFilterOptions;
[[nodiscard]] nlohmann::json ToJson() const override {
auto o = ResponseBody::ToJson();
JsonSerializeOptional(o, supportsConfigurationDoneRequest);
JsonSerializeOptional(o, supportsFunctionBreakpoints);
JsonSerializeOptional(o, supportsConditionalBreakpoints);
JsonSerializeOptional(o, supportsHitConditionalBreakpoints);
JsonSerializeOptional(o, supportsEvaluateForHovers);
JsonSerializeOptional(o, supportsStepBack);
JsonSerializeOptional(o, supportsSetVariable);
JsonSerializeOptional(o, supportsRestartFrame);
JsonSerializeOptional(o, supportsGotoTargetsRequest);
JsonSerializeOptional(o, supportsStepInTargetsRequest);
JsonSerializeOptional(o, supportsCompletionsRequest);
JsonSerializeOptional(o, supportsModulesRequest);
JsonSerializeOptional(o, supportsRestartRequest);
JsonSerializeOptional(o, supportsExceptionOptions);
JsonSerializeOptional(o, supportsValueFormattingOptions);
JsonSerializeOptional(o, supportsExceptionInfoRequest);
JsonSerializeOptional(o, supportTerminateDebuggee);
JsonSerializeOptional(o, supportSuspendDebuggee);
JsonSerializeOptional(o, supportsDelayedStackTraceLoading);
JsonSerializeOptional(o, supportsLoadedSourcesRequest);
JsonSerializeOptional(o, supportsLogPoints);
JsonSerializeOptional(o, supportsTerminateThreadsRequest);
JsonSerializeOptional(o, supportsSetExpression);
JsonSerializeOptional(o, supportsTerminateRequest);
JsonSerializeOptional(o, supportsDataBreakpoints);
JsonSerializeOptional(o, supportsReadMemoryRequest);
JsonSerializeOptional(o, supportsWriteMemoryRequest);
JsonSerializeOptional(o, supportsDisassembleRequest);
JsonSerializeOptional(o, supportsCancelRequest);
JsonSerializeOptional(o, supportsBreakpointLocationsRequest);
JsonSerializeOptional(o, supportsClipboardContext);
JsonSerializeOptional(o, supportsSteppingGranularity);
JsonSerializeOptional(o, supportsInstructionBreakpoints);
JsonSerializeOptional(o, supportsExceptionFilterOptions);
return o;
}
});
EmptyRequest(Launch, "launch");
EmptyRequest(LoadedSources, "loadedSources");
EmptyRequest(Modules, "modules");
EmptyRequest(Next, "next");
EmptyRequest(Pause, "pause");
EmptyRequest(ReadMemory, "readMemory");
EmptyRequest(Restart, "restart");
EmptyRequest(RestartFrame, "restartFrame");
EmptyRequest(ReverseContinue, "reverseContinue");
RequestDefinition(Scopes, "scopes", {
size_t frameId;
explicit ScopesArguments(nlohmann::json & j) : RequestArguments(j), frameId(j["frameId"]) {}
});
RequestResponseDefinition(Scopes, "scopes", {
std::vector<Scope> scopes;
ScopesResponseBody(const std::vector<Scope>& scopes) : scopes(scopes) {}
[[nodiscard]] nlohmann::json ToJson() const override {
auto o = ResponseBody::ToJson();
o["scopes"] = nlohmann::json::array();
for (auto& t : scopes) {
o["scopes"].push_back(t.ToJson());
}
return o;
}
});
RequestDefinition(SetBreakpoints, "setBreakpoints", {
Source source;
std::optional<std::vector<SourceBreakpoint>> breakpoints;
std::optional<bool> sourceModified;
explicit SetBreakpointsArguments(nlohmann::json & j) : RequestArguments(j), source(j["source"]) {
if (!j["breakpoints"].empty()) {
std::vector<SourceBreakpoint> v;
for (auto& i : j["breakpoints"]) {
v.emplace_back(i);
}
breakpoints = v;
}
JsonDeserializeOptional(j, sourceModified);
}
});
RequestResponseDefinition(SetBreakpoints, "setBreakpoints", {
std::vector<Breakpoint> breakpoints;
[[nodiscard]] nlohmann::json ToJson() const override {
auto o = ResponseBody::ToJson();
o["breakpoints"] = nlohmann::json::array();
for (auto& bp : breakpoints) {
o["breakpoints"].push_back(bp.ToJson());
}
return o;
}
});
EmptyRequest(SetDataBreakpoints, "setDataBreakpoints");
EmptyRequest(SetExceptionBreakpoints, "setExceptionBreakpoints");
EmptyRequest(SetExpression, "setExpression");
EmptyRequest(SetFunctionBreakpoints, "setFunctionBreakpoints");
EmptyRequest(SetInstructionBreakpoints, "setInstructionBreakpoints");
EmptyRequest(SetVariable, "setVariable");
EmptyRequest(Source, "source");
EmptyRequest(StackTrace, "stackTrace");
RequestResponseDefinition(StackTrace, "stackTrace", {
std::vector<StackFrame> stackFrames;
std::optional<size_t> totalFrames;
StackTraceResponseBody(std::vector<StackFrame> stackFrames)
: stackFrames(stackFrames), totalFrames(stackFrames.size()) {}
[[nodiscard]] nlohmann::json ToJson() const override {
auto o = ResponseBody::ToJson();
o["stackFrames"] = nlohmann::json::array();
for (auto& t : stackFrames) {
o["stackFrames"].push_back(t.ToJson());
}
JsonSerializeOptional(o, totalFrames);
return o;
}
});
EmptyRequest(StepBack, "stepBack");
EmptyRequest(StepIn, "stepIn");
EmptyRequest(StepInTargets, "stepInTargets");
EmptyRequest(StepOut, "stepOut");
RequestDefinition(Terminate, "terminate", {
std::optional<bool> restart;
explicit TerminateArguments(nlohmann::json & j) : RequestArguments(j), restart(j["restart"]) {}
});
EmptyRequest(TerminateThreads, "terminateThreads");
RequestResponseDefinition(Threads, "threads", {
std::vector<Thread> threads;
[[nodiscard]] nlohmann::json ToJson() const override {
auto o = ResponseBody::ToJson();
o["threads"] = nlohmann::json::array();
for (auto& t : threads) {
o["threads"].push_back(t.ToJson());
}
return o;
}
});
EmptyRequest(Threads, "threads");
RequestDefinition(Variables, "variables", {
size_t variablesReference;
explicit VariablesArguments(nlohmann::json & j)
: RequestArguments(j), variablesReference(j["variablesReference"]) {}
});
RequestResponseDefinition(Variables, "variables", {
std::vector<Variable> variables;
VariablesResponseBody(const std::vector<Variable>& v) : variables(v) {}
[[nodiscard]] nlohmann::json ToJson() const override {
auto o = ResponseBody::ToJson();
o["variables"] = nlohmann::json::array();
for (auto& t : variables) {
o["variables"].push_back(t.ToJson());
}
return o;
}
});
EmptyRequest(WriteMemory, "writeMemory");
#undef EmptyRequest
#undef RequestDefinition
}
#endif // ANGELSCRIPTDEBUGGER_REQUESTS_HPP
// end --- Requests.hpp ---
void AngelscriptDebugger::Run(uint16_t port) {
// Listen on port 9012
_server = new asio::ip::tcp::acceptor(_ioContext, asio::ip::tcp::endpoint(asio::ip::tcp::v4(), port));
std::thread([this]() { AcceptLoop(); }).detach();
}
template <typename... Args> std::string string_format(const std::string& format, Args... args) {
int size_s = std::snprintf(nullptr, 0, format.c_str(), args...) + 1; // Extra space for '\0'
if (size_s <= 0) {
throw std::runtime_error("Error during formatting.");
}
auto size = static_cast<size_t>(size_s);
auto buf = std::make_unique<char[]>(size);
std::snprintf(buf.get(), size, format.c_str(), args...);
return std::string(buf.get(), buf.get() + size - 1); // We don't want the '\0' inside
}
std::string AngelscriptDebugger::GetResolvedScriptPath(const char* scriptSection) {
if (_scriptPath.has_value()) {
return string_format(_scriptPath.value(), scriptSection);
}
return scriptSection;
}
void AngelscriptDebugger::on_line_callback(asIScriptContext* ctx, AngelscriptDebugger* d) {
if (ctx->GetState() == asEXECUTION_SUSPENDED) {
return;
}
const char* scriptSection = nullptr;
int column = 0;
int line = ctx->GetLineNumber(0, &column, &scriptSection);
if (line == 0)
return;
auto resolvedScriptPath = d->GetResolvedScriptPath(scriptSection);
auto sectionBreakpoints = d->_breakpoints.find(resolvedScriptPath);
if (sectionBreakpoints != d->_breakpoints.end()) {
if (sectionBreakpoints->second.contains(line)) {
d->EnterBreakpoint(ctx, resolvedScriptPath, line);
}
}
}
size_t GetBreakpointHash(const std::string& section, size_t line) {
using std::hash;
using std::size_t;
using std::string;
return ((hash<string>()(section) ^ (hash<size_t>()(line) << 1)) >> 1);
}
void AngelscriptDebugger::on_exception_callback(asIScriptContext*, AngelscriptDebugger*) {
return;
// const char* scriptSection = nullptr;
// int column = 0;
// int line = ctx->GetExceptionLineNumber(&column, &scriptSection);
// if (line == 0)
// return;
// auto b = Breakpoint{.Section = scriptSection, .Line = line};
// d->EnterBreakpoint(ctx, b);
}
void AngelscriptDebugger::EnterBreakpoint(asIScriptContext* ctx, const std::string& section, size_t line) {
ctx->Suspend();
_pausedContexts.push_back(ctx);
auto* o = new DebugAdapterProtocol::StoppedEvent(
new DebugAdapterProtocol::StoppedEventBody(GetBreakpointHash(section, line)));
Send(o);
}
void AngelscriptDebugger::RegisterContext(asIScriptContext* ctx) {
ctx->SetLineCallback(asFUNCTION(on_line_callback), this, asCALL_CDECL);
ctx->SetExceptionCallback(asFUNCTION(on_exception_callback), this, asCALL_CDECL);
}
[[noreturn]] void AngelscriptDebugger::AcceptLoop() {
while (true) {
auto* client = new asio::ip::tcp::socket(_ioContext);
_server->accept(*client);
std::thread([this, client]() { ClientLoop(*client); }).detach();
}
}
inline std::string trim(const std::string& s) {
auto wsfront = std::find_if_not(s.begin(), s.end(), [](int c) { return std::isspace(c); });
auto wsback = std::find_if_not(s.rbegin(), s.rend(), [](int c) { return std::isspace(c); }).base();
return (wsback <= wsfront ? std::string() : std::string(wsfront, wsback));
}
void AngelscriptDebugger::ClientLoop(asio::ip::tcp::socket& client) {
std::unordered_map<std::string, std::string> headers;
bool in_header_mode = true;
asio::streambuf buffer;
while (true) {
if (!client.is_open()) {
break;
}
if (client.available() == 0 && buffer.size() == 0) {
continue;
}
if (client.available() > 0 && buffer.size() == 0) {
asio::error_code error;
asio::read_until(client, buffer, "\r\n", error);
}
if (in_header_mode) {
std::istream str(&buffer);
std::string s;
std::getline(str, s);
if (s == "\r") {
if (headers.contains("content-length")) {
in_header_mode = false;
}
continue;
}
auto delimiter = s.find(':');
std::string key = trim(s.substr(0, delimiter));
std::transform(key.begin(), key.end(), key.begin(), [](unsigned char c) { return std::tolower(c); });
std::string value = trim(s.substr(delimiter + 1));
headers[key] = value;
} else {
auto contentLength = headers["content-length"];
auto size = (size_t)std::strtol(contentLength.c_str(), nullptr, 10);
std::cout << "message length: " << size << std::endl;
std::stringstream msg;
int64_t to_fetch = (int64_t)size - (int64_t)buffer.size();
auto to_read = std::min(buffer.size(), size);
asio::streambuf::const_buffers_type constBuffer = buffer.data();
std::copy(asio::buffers_begin(constBuffer), asio::buffers_end(constBuffer),
std::ostream_iterator<char>(msg));
buffer.consume(to_read);
if (to_fetch > 0) {
std::vector<uint8_t> v(to_fetch);
asio::read(client, asio::buffer(v), asio::transfer_exactly(to_fetch));
msg << std::string(v.begin(), v.end());
}
auto m = msg.str();
std::cout << "found message: " << m << std::endl;
nlohmann::json j = nlohmann::json::parse(m.begin(), m.end());
auto protocolMessage = DebugAdapterProtocol::ProtocolMessage::FromJson(j);
OnMessage(&client, protocolMessage);
in_header_mode = true;
}
}
}
void AngelscriptDebugger::Send(DebugAdapterProtocol::ProtocolMessage* msg) {
auto body = msg->ToJson().dump();
std::vector<uint8_t> vec(body.begin(), body.end());
for (const auto& conn : _connections) {
if (conn->is_open()) {
asio::error_code ignored_error;
asio::write(*conn, asio::buffer("Content-Length: " + std::to_string(vec.size()) + "\r\n\r\n"),
ignored_error);
asio::write(*conn, asio::buffer(vec), ignored_error);
}
}
std::cout << "Sent message: " << body << std::endl;
delete msg;
}
void AngelscriptDebugger::Send(asio::ip::tcp::socket* client, DebugAdapterProtocol::ProtocolMessage* msg) {
auto body = msg->ToJson().dump();
std::vector<uint8_t> vec(body.begin(), body.end());
if (client->is_open()) {
asio::error_code ignored_error;
asio::write(*client, asio::buffer("Content-Length: " + std::to_string(vec.size()) + "\r\n\r\n"), ignored_error);
asio::write(*client, asio::buffer(vec), ignored_error);
}
std::cout << "Sent message: " << body << std::endl;
delete msg;
}
void AngelscriptDebugger::OnMessage(asio::ip::tcp::socket* client, DebugAdapterProtocol::ProtocolMessage* msg) {
if (msg->type == "request") {
OnRequest(client, dynamic_cast<DebugAdapterProtocol::Request*>(msg));
}
delete msg;
}
const char empty[] = "";
void AngelscriptDebugger::OnRequest(asio::ip::tcp::socket* client, DebugAdapterProtocol::Request* msg) {
if (msg->GetCommand() == "setBreakpoints") {
auto* t = dynamic_cast<DebugAdapterProtocol::SetBreakpointsRequest*>(msg);
auto& args = t->arguments.value();
auto& path = args->source.path.value();
auto* response = new DebugAdapterProtocol::SetBreakpointsResponse(msg->seq);
auto* body = new DebugAdapterProtocol::SetBreakpointsResponseBody();
response->body = body;
std::unordered_set<size_t> sectionBreakpoints;
if (args->breakpoints.has_value()) {
for (auto& bp : args->breakpoints.value()) {
sectionBreakpoints.insert(bp.line);
body->breakpoints.emplace_back(GetBreakpointHash(path, bp.line), args->source, bp.line);
}
}
_breakpoints[path] = sectionBreakpoints;
Send(client, response);
} else if (msg->GetCommand() == "initialize") {
auto response = new DebugAdapterProtocol::InitializeResponse(msg->seq);
auto* body = new DebugAdapterProtocol::InitializeResponseBody();
response->body = body;
Send(client, response);
auto r = new DebugAdapterProtocol::InitializedEvent();
Send(client, r);
} else if (msg->GetCommand() == "disconnect") {
auto response = new DebugAdapterProtocol::DefinedResponse<DebugAdapterProtocol::ResponseBody, empty>(msg->seq);
Send(client, response);
_connections.erase(client);
} else if (msg->GetCommand() == "configurationDone") {
_connections.insert(client);
Send(client, new DebugAdapterProtocol::DefinedResponse<DebugAdapterProtocol::ResponseBody, empty>(msg->seq));
} else if (msg->GetCommand() == "attach") {
auto t = dynamic_cast<DebugAdapterProtocol::AttachRequest*>(msg);
_scriptPath = t->arguments.value()->scriptPath;
Send(client, new DebugAdapterProtocol::DefinedResponse<DebugAdapterProtocol::ResponseBody, empty>(msg->seq));
} else if (msg->GetCommand() == "threads") {
auto response = new DebugAdapterProtocol::ThreadsResponse(msg->seq);
auto* body = new DebugAdapterProtocol::ThreadsResponseBody();
response->body = body;
body->threads = {DebugAdapterProtocol::Thread(0, "main")};
Send(client, response);
} else if (msg->GetCommand() == "continue") {
Continue();
Send(client, new DebugAdapterProtocol::DefinedResponse<DebugAdapterProtocol::ResponseBody, empty>(msg->seq));
} else if (msg->GetCommand() == "stackTrace") {
auto ctx = _pausedContexts[0];
auto response = new DebugAdapterProtocol::StackTraceResponse(msg->seq);
auto stackTrace = std::vector<DebugAdapterProtocol::StackFrame>();
stackTrace.reserve(ctx->GetCallstackSize());
for (asUINT i = 0; i < ctx->GetCallstackSize(); ++i) {
auto func = ctx->GetFunction(i);
const char* scriptSection = nullptr;
int column = 0;
int line = ctx->GetLineNumber(i, &column, &scriptSection);
stackTrace.emplace_back(i, func->GetName(),
DebugAdapterProtocol::Source(GetResolvedScriptPath(scriptSection)), line, column);
}
auto body = new DebugAdapterProtocol::StackTraceResponseBody(stackTrace);
response->body = body;
Send(client, response);
} else if (msg->GetCommand() == "scopes") {
auto t = dynamic_cast<DebugAdapterProtocol::ScopesRequest*>(msg);
auto ctx = _pausedContexts[0];
auto response = new DebugAdapterProtocol::ScopesResponse(msg->seq);
auto scopes = std::vector<DebugAdapterProtocol::Scope>();
auto frameId = t->arguments.value()->frameId;
auto varCount = 0;
for (int i = 0; i < ctx->GetVarCount(t->arguments.value()->frameId); ++i) {
if (ctx->IsVarInScope(i, frameId)) {
varCount++;
}
}
_storedVariableReferences.emplace_back(std::make_unique<StackScope>(frameId, 0));
scopes.emplace_back("Locals", _storedVariableReferences.size(), "locals", varCount);
auto body = new DebugAdapterProtocol::ScopesResponseBody(scopes);
response->body = body;
Send(client, response);
} else if (msg->GetCommand() == "variables") {
auto t = dynamic_cast<DebugAdapterProtocol::VariablesRequest*>(msg);
auto ctx = _pausedContexts[0];
auto response = new DebugAdapterProtocol::VariablesResponse(msg->seq);
auto variables = std::vector<DebugAdapterProtocol::Variable>();
auto reference = t->arguments.value()->variablesReference - 1;
if (reference >= _storedVariableReferences.size() || reference < 0)
return;
auto& variant = _storedVariableReferences[reference];
auto e = ctx->GetEngine();
// We currently can't use asIScriptContext::PushBack on suspended script contexts due to limitations in
// angelscript. Hence we make a new contenxt
auto c = e->CreateContext();
if (holds_alternative<std::unique_ptr<StackScope>>(variant)) {
auto frameId = std::get<std::unique_ptr<StackScope>>(variant)->StackLevel;
// auto scopeType = get<StackScope>(variant).Type;
auto varCount = ctx->GetVarCount(frameId);
for (int i = 0; i < varCount; ++i) {
if (!ctx->IsVarInScope(i, frameId)) {
continue;
}
auto name = ctx->GetVarName(i, frameId);
auto type = ctx->GetVarTypeId(i, frameId);
auto val = ctx->GetAddressOfVar(i, frameId);
variables.push_back(ASVariableFormatter::GetAsDAPVariable(e, c, this, name, type, val));
}
} else {
auto& v = std::get<std::unique_ptr<PointerVariable>>(variant);
try {
ASVariableFormatter::GetChildDAPVariables(variables, e, c, this, v->TypeID, v->Address);
} catch (const std::exception& e) {
std::cout << e.what() << std::endl;
}
}
c->Release();
auto body = new DebugAdapterProtocol::VariablesResponseBody(variables);
response->body = body;
Send(client, response);
} else {
Send(client, new DebugAdapterProtocol::DefinedResponse<DebugAdapterProtocol::ResponseBody, empty>(msg->seq));
}
}
// end --- AngelscriptDebugger.cpp ---
// begin --- BaseProtocol.cpp ---
namespace DebugAdapterProtocol {
ProtocolMessage* ProtocolMessage::FromJson(nlohmann::json& j) {
auto t = j["type"];
ProtocolMessage* msg;
if (t == "request") {
msg = Request::FromJson(j);
} else if (t == "event") {
throw std::logic_error("Unhandled type");
// msg = Event::FromJson(j);
} else if (t == "response") {
msg = Response::FromJson(j);
} else {
throw std::logic_error("Unknown type");
}
msg->type = t;
msg->seq = j["seq"];
return msg;
}
template <IsRequestArguments T, const char* S> ProtocolMessage* DefinedRequest<T, S>::FromJson(nlohmann::json& j) {
std::string c = j["command"];
auto o = new DefinedRequest<T, S>();
auto a = j["arguments"];
if (!a.empty()) {
o->arguments = dynamic_cast<T*>(new T(a));
}
return o;
}
ProtocolMessage* Request::FromJson(nlohmann::json& j) {
std::string c = j["command"];
// This is the worst, we need to fix this eventually.
if (c == "attach")
return AttachRequest::FromJson(j);
if (c == "breakpointLocations")
return BreakpointLocationsRequest::FromJson(j);
if (c == "completions")
return CompletionsRequest::FromJson(j);
if (c == "configurationDone")
return ConfigurationDoneRequest ::FromJson(j);
if (c == "continue")
return ContinueRequest ::FromJson(j);
if (c == "dataBreakpointInfo")
return DataBreakpointInfoRequest ::FromJson(j);
if (c == "disassemble")
return DisassembleRequest ::FromJson(j);
if (c == "disconnect")
return DisconnectRequest ::FromJson(j);
if (c == "evaluate")
return EvaluateRequest ::FromJson(j);
if (c == "exceptionInfo")
return ExceptionInfoRequest ::FromJson(j);
if (c == "Goto")
return GotoRequest ::FromJson(j);
if (c == "GotoTargets")
return GotoTargetsRequest ::FromJson(j);
if (c == "initialize")
return InitializeRequest ::FromJson(j);
if (c == "launch")
return LaunchRequest ::FromJson(j);
if (c == "loadedSources")
return LoadedSourcesRequest ::FromJson(j);
if (c == "modules")
return ModulesRequest ::FromJson(j);
if (c == "next")
return NextRequest ::FromJson(j);
if (c == "pause")
return PauseRequest ::FromJson(j);
if (c == "readMemory")
return ReadMemoryRequest ::FromJson(j);
if (c == "restart")
return RestartRequest ::FromJson(j);
if (c == "restartFrame")
return RestartFrameRequest ::FromJson(j);
if (c == "reverseContinue")
return RestartFrameRequest ::FromJson(j);
if (c == "scopes")
return ScopesRequest ::FromJson(j);
if (c == "setBreakpoints")
return SetBreakpointsRequest ::FromJson(j);
if (c == "setDataBreakpoints")
return SetDataBreakpointsRequest ::FromJson(j);
if (c == "setExceptionBreakpoints")
return SetExceptionBreakpointsRequest ::FromJson(j);
if (c == "setExpression")
return SetExpressionRequest ::FromJson(j);
if (c == "setFunctionBreakpoints")
return SetFunctionBreakpointsRequest ::FromJson(j);
if (c == "setInstructionBreakpoints")
return SetInstructionBreakpointsRequest ::FromJson(j);
if (c == "setVariable")
return SetVariableRequest ::FromJson(j);
if (c == "source")
return SourceRequest ::FromJson(j);
if (c == "stackTrace")
return StackTraceRequest ::FromJson(j);
if (c == "stepBack")
return StepBackRequest ::FromJson(j);
if (c == "stepIn")
return StepInRequest ::FromJson(j);
if (c == "stepInTargets")
return StepInTargetsRequest ::FromJson(j);
if (c == "stepOut")
return StepOutRequest ::FromJson(j);
if (c == "terminate")
return TerminateRequest ::FromJson(j);
if (c == "terminateThreads")
return TerminateThreadsRequest ::FromJson(j);
if (c == "threads")
return ThreadsRequest ::FromJson(j);
if (c == "variables")
return VariablesRequest ::FromJson(j);
if (c == "writeMemory")
return WriteMemoryRequest ::FromJson(j);
throw std::logic_error("Unknown command");
}
}
// end --- BaseProtocol.cpp ---
// begin --- ASVariableFormatter.cpp ---
#include <iostream>
DebugAdapterProtocol::Variable ASVariableFormatter::GetAsDAPVariable(asIScriptEngine* engine, asIScriptContext* ctx,
AngelscriptDebugger* debugger,
const std::string& name, int type, void* address) {
if ((type & asTYPEID_OBJHANDLE) != 0) {
address = *static_cast<void**>(address);
}
switch (type) {
case asTYPEID_BOOL: {
auto v = *static_cast<bool*>(address);
return {name, v ? "true" : "false", "bool"};
};
case asTYPEID_INT8: {
auto v = *static_cast<int8_t*>(address);
return {name, std::to_string(v), "int8"};
};
case asTYPEID_INT16: {
auto v = *static_cast<int16_t*>(address);
return {name, std::to_string(v), "int16"};
}
case asTYPEID_INT32: {
auto v = *static_cast<int32_t*>(address);
return {name, std::to_string(v), "int32"};
};
case asTYPEID_INT64: {
auto v = *static_cast<int64_t*>(address);
return {name, std::to_string(v), "int64"};
};
case asTYPEID_UINT8: {
auto v = *static_cast<uint8_t*>(address);
return {name, std::to_string(v), "uint8"};
};
case asTYPEID_UINT16: {
auto v = *static_cast<uint16_t*>(address);
return {name, std::to_string(v), "uint16"};
};
case asTYPEID_UINT32: {
auto v = *static_cast<uint32_t*>(address);
return {name, std::to_string(v), "uint32"};
};
case asTYPEID_UINT64: {
auto v = *static_cast<uint64_t*>(address);
return {name, std::to_string(v), "uint64"};
};
case asTYPEID_FLOAT: {
auto v = *static_cast<float*>(address);
return {name, std::to_string(v), "float"};
};
case asTYPEID_DOUBLE: {
auto v = *static_cast<double*>(address);
return {name, std::to_string(v), "double"};
};
default: break;
}
if ((type & asTYPEID_SCRIPTOBJECT) != 0) {
auto* obj = static_cast<asIScriptObject*>(address);
std::string typeName = engine->GetTypeInfoById(type)->GetName();
if (obj == nullptr) {
return DebugAdapterProtocol::Variable::FromNull(name, typeName);
}
auto typeData = obj->GetObjectType();
std::string stringified = typeName + "(*" + std::to_string((size_t)address) + ")";
auto stringifiedFunc = typeData->GetMethodByDecl("string opImplConv()");
if (stringifiedFunc != nullptr) {
ctx->PushState();
ctx->Prepare(stringifiedFunc);
ctx->SetObject(address);
if (ctx->Execute() == asEXECUTION_FINISHED) {
stringified = *(std::string*)ctx->GetAddressOfReturnValue();
}
ctx->PopState();
}
size_t reference = 0;
if (obj->GetPropertyCount() > 0) {
std::cout << "Storing scriptObj " << name << std::endl;
obj->AddRef();
reference = debugger->StoreVariable(address, type, [](void* a) {
if (a != nullptr) {
((asIScriptObject*)a)->Release();
}
});
}
return DebugAdapterProtocol::Variable::FromPointer(name, typeName, stringified, reference);
}
// TODO: Caching of type id
if (type == engine->GetTypeIdByDecl("string")) {
auto* str = static_cast<std::string*>(address);
if (str == nullptr) {
return DebugAdapterProtocol::Variable::FromNull(name, "string");
}
return DebugAdapterProtocol::Variable::FromString(name, "\"" + *str + "\"");
}
auto* typeData = engine->GetTypeInfoById(type);
if ((typeData->GetFlags() & asOBJ_ENUM) != 0) {
const auto* enumValue = typeData->GetEnumValueByIndex(*static_cast<int32_t*>(address), nullptr);
return {name, enumValue, typeData->GetName()};
}
if (address == nullptr) {
return DebugAdapterProtocol::Variable::FromNull(name, typeData->GetName());
}
std::string stringified = std::string(typeData->GetName()) + "(*" + std::to_string((size_t)address) + ")";
auto stringifiedFunc = typeData->GetMethodByDecl("string opImplConv()");
if (stringifiedFunc != nullptr) {
ctx->Prepare(stringifiedFunc);
ctx->SetObject(*(void**)address);
if (ctx->Execute() == asEXECUTION_FINISHED) {
stringified = "\"" + *(std::string*)ctx->GetAddressOfReturnValue() + "\"";
}
}
size_t reference = 0;
size_t varCount = typeData->GetPropertyCount();
for (asUINT i = 0; i < typeData->GetMethodCount(); ++i) {
if (typeData->GetMethodByIndex(i, true)->IsProperty()) {
varCount++;
}
}
if (varCount > 0) {
// If we want to re-use an object, we need to make sure to add a reference to it, so it doesn't get accidentally
// cleaned up. This is currently somewhat clunky, we look up the AddRef behaviour, and execute that in a new
// context.
auto b = static_cast<asEBehaviours>(-1);
asIScriptFunction* releaseFunc = nullptr;
bool hasAddRef = false;
for (asUINT i = 0; i < typeData->GetBehaviourCount(); ++i) {
auto* f = typeData->GetBehaviourByIndex(i, &b);
if (b == asBEHAVE_ADDREF) {
// If we push state or re-use the active context, we might lose the reference to the object we have.
// To resolve this we need a new context.
auto* c = ctx->GetEngine()->CreateContext();
assert(c->Prepare(f) >= 0);
assert(c->SetObject(address) >= 0);
assert(c->Execute() >= 0);
c->Release();
hasAddRef = true;
}
if (b == asBEHAVE_RELEASE) {
releaseFunc = f;
}
}
if (!hasAddRef) {
releaseFunc = nullptr;
}
reference = debugger->StoreVariable(address, type, [engine, releaseFunc](void* a) {
if (releaseFunc != nullptr) {
auto* c = engine->CreateContext();
c->Prepare(releaseFunc);
c->SetObject(a);
c->Execute();
c->Release();
}
});
}
return DebugAdapterProtocol::Variable::FromPointer(name, typeData->GetName(), stringified, reference);
}
void ASVariableFormatter::GetChildDAPVariables(std::vector<DebugAdapterProtocol::Variable>& vars,
asIScriptEngine* engine, asIScriptContext* ctx,
AngelscriptDebugger* debugger, int type, void* address) {
if (address == nullptr) {
return;
}
if ((type & asTYPEID_SCRIPTOBJECT) != 0) {
auto* obj = static_cast<asIScriptObject*>(address);
for (asUINT i = 0; i < obj->GetPropertyCount(); ++i) {
const auto* propertyName = obj->GetPropertyName(i);
auto propertyType = obj->GetPropertyTypeId(i);
auto* propertyAddress = obj->GetAddressOfProperty(i);
vars.push_back(GetAsDAPVariable(engine, ctx, debugger, propertyName, propertyType, propertyAddress));
}
return;
}
auto* typeData = engine->GetTypeInfoById(type);
for (asUINT i = 0; i < typeData->GetPropertyCount(); ++i) {
const char* propertyName = nullptr;
int propertyType = 0;
int offset = 0;
typeData->GetProperty(i, &propertyName, &propertyType, nullptr, nullptr, &offset);
vars.push_back(
GetAsDAPVariable(engine, ctx, debugger, propertyName, propertyType, (void*)((size_t)address + offset)));
}
for (asUINT i = 0; i < typeData->GetMethodCount(); ++i) {
auto func = typeData->GetMethodByIndex(i, true);
if (func->IsProperty()) {
assert(ctx->Prepare(func) >= 0);
ctx->SetObject(address);
auto state = ctx->Execute();
if (state == asEXECUTION_FINISHED) {
auto name = std::string(func->GetName()).substr(4);
vars.push_back(GetAsDAPVariable(engine, ctx, debugger, name, func->GetReturnTypeId(),
ctx->GetAddressOfReturnValue()));
}
}
}
}
// end --- ASVariableFormatter.cpp ---