1796 lines
66 KiB
C++
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 ---
|
|
|