#include "ASVariableFormatter.hpp" #include 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(address); } switch (type) { case asTYPEID_BOOL: { auto v = *static_cast(address); return {name, v ? "true" : "false", "bool"}; }; case asTYPEID_INT8: { auto v = *static_cast(address); return {name, std::to_string(v), "int8"}; }; case asTYPEID_INT16: { auto v = *static_cast(address); return {name, std::to_string(v), "int16"}; } case asTYPEID_INT32: { auto v = *static_cast(address); return {name, std::to_string(v), "int32"}; }; case asTYPEID_INT64: { auto v = *static_cast(address); return {name, std::to_string(v), "int64"}; }; case asTYPEID_UINT8: { auto v = *static_cast(address); return {name, std::to_string(v), "uint8"}; }; case asTYPEID_UINT16: { auto v = *static_cast(address); return {name, std::to_string(v), "uint16"}; }; case asTYPEID_UINT32: { auto v = *static_cast(address); return {name, std::to_string(v), "uint32"}; }; case asTYPEID_UINT64: { auto v = *static_cast(address); return {name, std::to_string(v), "uint64"}; }; case asTYPEID_FLOAT: { auto v = *static_cast(address); return {name, std::to_string(v), "float"}; }; case asTYPEID_DOUBLE: { auto v = *static_cast(address); return {name, std::to_string(v), "double"}; }; default: break; } if ((type & asTYPEID_SCRIPTOBJECT) != 0) { auto* obj = static_cast(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(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(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(-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 void ASVariableFormatter::FormatArrayLike(std::vector& 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& vars, asIScriptEngine* engine, asIScriptContext* ctx, AngelscriptDebugger* debugger, int type, void* address) { if (address == nullptr) { return; } if ((type & asTYPEID_SCRIPTOBJECT) != 0) { auto* obj = static_cast(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( vars, engine, ctx, address, debugger, indexFunc); } else if (t == asTYPEID_INT32){ FormatArrayLike( vars, engine, ctx, address, debugger, indexFunc); } } // vars.push_back(DebugAdapterProtocol::Variable("has_indexer", "true", "string", {})); } }