AngelscriptDebuggerServer/src/ASVariableFormatter.cpp

247 lines
10 KiB
C++

#include "ASVariableFormatter.hpp"
#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);
}
template <typename T, int (asIScriptContext::*SetArg)(asUINT, T), T (asIScriptContext::*GetLength)()>
void ASVariableFormatter::FormatArrayLike(std::vector<DebugAdapterProtocol::Variable>& vars, asIScriptEngine* engine,
asIScriptContext* ctx, void* address, AngelscriptDebugger* debugger,
asIScriptFunction* indexFunc) {
auto length = (ctx->*GetLength)();
for (T i = 0; i < length; ++i) {
assert(ctx->Prepare(indexFunc) >= 0);
ctx->SetObject(address);
(ctx->*SetArg)(0, i);
ctx->Execute();
auto name = std::to_string(i);
vars.push_back(GetAsDAPVariable(engine, ctx, debugger, name, indexFunc->GetReturnTypeId(),
ctx->GetAddressOfReturnValue()));
}
}
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()));
}
}
}
auto indexFunc = typeData->GetMethodByName("get_opIndex", true);
if (indexFunc != nullptr && indexFunc->GetParamCount() == 1) {
auto lengthFunc = typeData->GetMethodByName("get_Length", true);
int t;
indexFunc->GetParam(0, &t);
if (lengthFunc != nullptr && lengthFunc->GetParamCount() == 0 && lengthFunc->GetReturnTypeId() == t) {
assert(ctx->Prepare(lengthFunc) >= 0);
ctx->SetObject(address);
ctx->Execute();
if (t == asTYPEID_UINT64) {
FormatArrayLike<asQWORD, &asIScriptContext::SetArgQWord, &asIScriptContext::GetReturnQWord>(
vars, engine, ctx, address, debugger, indexFunc);
}
else if (t == asTYPEID_INT32){
FormatArrayLike<asDWORD, &asIScriptContext::SetArgDWord, &asIScriptContext::GetReturnDWord>(
vars, engine, ctx, address, debugger, indexFunc);
}
}
// vars.push_back(DebugAdapterProtocol::Variable("has_indexer", "true", "string", {}));
}
}