From 8691d685b2674ef3a7bc001f684d30c45f328096 Mon Sep 17 00:00:00 2001 From: Ambr0se Date: Sun, 16 Nov 2025 13:51:23 +0800 Subject: [PATCH 1/2] fix: Prevent VirtualFunction memory leak in native layer --- src/core/function.cpp | 15 ++++- src/scripting/natives/natives_memory.cpp | 75 +++++++++++++++++++++++- 2 files changed, 87 insertions(+), 3 deletions(-) diff --git a/src/core/function.cpp b/src/core/function.cpp index 313d47550..da58833a5 100644 --- a/src/core/function.cpp +++ b/src/core/function.cpp @@ -98,7 +98,20 @@ ValveFunction::ValveFunction(void* ulAddr, Convention_t callingConvention, DataT m_iCallingConvention = GetDynCallConvention(m_eCallingConvention); } -ValveFunction::~ValveFunction() {} +ValveFunction::~ValveFunction() +{ + if (m_precallback != nullptr) + { + globals::callbackManager.ReleaseCallback(m_precallback); + m_precallback = nullptr; + } + + if (m_postcallback != nullptr) + { + globals::callbackManager.ReleaseCallback(m_postcallback); + m_postcallback = nullptr; + } +} bool ValveFunction::IsCallable() { return (m_eCallingConvention != CONV_CUSTOM) && (m_iCallingConvention != -1); } diff --git a/src/scripting/natives/natives_memory.cpp b/src/scripting/natives/natives_memory.cpp index 939dc6182..822be30e3 100644 --- a/src/scripting/natives/natives_memory.cpp +++ b/src/scripting/natives/natives_memory.cpp @@ -16,6 +16,7 @@ #include #include +#include #include "core/function.h" #include "core/log.h" @@ -26,6 +27,41 @@ namespace counterstrikesharp { std::vector m_managed_ptrs; +struct VirtualFunctionCacheKey +{ + void* functionAddr; + Convention_t callingConvention; + std::vector args; + DataType_t returnType; + int vtableOffset; + + bool operator==(const VirtualFunctionCacheKey& other) const + { + return functionAddr == other.functionAddr && callingConvention == other.callingConvention && args == other.args && + returnType == other.returnType && vtableOffset == other.vtableOffset; + } +}; + +struct VirtualFunctionCacheKeyHash +{ + std::size_t operator()(const VirtualFunctionCacheKey& key) const + { + std::size_t hash = std::hash{}(key.functionAddr); + hash ^= std::hash{}(static_cast(key.callingConvention)) << 1; + hash ^= std::hash{}(static_cast(key.returnType)) << 2; + hash ^= std::hash{}(key.vtableOffset) << 3; + + for (size_t i = 0; i < key.args.size(); ++i) + { + hash ^= std::hash{}(static_cast(key.args[i])) << (4 + i); + } + + return hash; + } +}; + +std::unordered_map m_virtualFunctionCache; + void* FindSignatureNative(ScriptContext& scriptContext) { auto moduleName = scriptContext.GetArgument(0); @@ -64,12 +100,29 @@ ValveFunction* CreateVirtualFunctionBySignature(ScriptContext& script_context) args.push_back(script_context.GetArgument(5 + i)); } + VirtualFunctionCacheKey cacheKey; + cacheKey.functionAddr = function_addr; + cacheKey.callingConvention = CONV_CDECL; + cacheKey.args = args; + cacheKey.returnType = return_type; + cacheKey.vtableOffset = -1; + + auto it = m_virtualFunctionCache.find(cacheKey); + if (it != m_virtualFunctionCache.end()) + { + CSSHARP_CORE_TRACE("Virtual function found in cache, reusing existing instance at {}, signature {}", function_addr, + signature_hex_string); + return it->second; + } + auto function = new ValveFunction(function_addr, CONV_CDECL, args, return_type); function->SetSignature(signature_hex_string); - CSSHARP_CORE_TRACE("Created virtual function, pointer found at {}, signature {}", function_addr, signature_hex_string); - m_managed_ptrs.push_back(function); + m_virtualFunctionCache[cacheKey] = function; + + CSSHARP_CORE_TRACE("Created new virtual function, pointer found at {}, signature {}", function_addr, signature_hex_string); + return function; } @@ -95,10 +148,28 @@ ValveFunction* CreateVirtualFunction(ScriptContext& script_context) args.push_back(script_context.GetArgument(4 + i)); } + VirtualFunctionCacheKey cacheKey; + cacheKey.functionAddr = function_addr; + cacheKey.callingConvention = CONV_THISCALL; + cacheKey.args = args; + cacheKey.returnType = return_type; + cacheKey.vtableOffset = vtable_offset; + + auto it = m_virtualFunctionCache.find(cacheKey); + if (it != m_virtualFunctionCache.end()) + { + CSSHARP_CORE_TRACE("Virtual function found in cache, reusing existing instance at {}, offset {}", function_addr, vtable_offset); + return it->second; + } + auto function = new ValveFunction(function_addr, CONV_THISCALL, args, return_type); function->SetOffset(vtable_offset); m_managed_ptrs.push_back(function); + m_virtualFunctionCache[cacheKey] = function; + + CSSHARP_CORE_TRACE("Created new virtual function at {}, offset {}", function_addr, vtable_offset); + return function; } From a46ddcd4df255dff53132f07aaaabc2ae185d2cc Mon Sep 17 00:00:00 2001 From: Ambr0se Date: Mon, 17 Nov 2025 09:56:05 +0800 Subject: [PATCH 2/2] refactor: Improve virtual function cache implementation --- src/scripting/natives/natives_memory.cpp | 25 +++++++++++++++--------- src/scripting/natives/natives_vector.cpp | 8 +++++--- 2 files changed, 21 insertions(+), 12 deletions(-) diff --git a/src/scripting/natives/natives_memory.cpp b/src/scripting/natives/natives_memory.cpp index 822be30e3..69af5151d 100644 --- a/src/scripting/natives/natives_memory.cpp +++ b/src/scripting/natives/natives_memory.cpp @@ -25,7 +25,12 @@ #include "scripting/script_engine.h" namespace counterstrikesharp { -std::vector m_managed_ptrs; + +template inline void hash_combine(std::size_t& seed, const T& v) +{ + std::hash hasher; + seed ^= hasher(v) + 0x9e3779b9 + (seed << 6) + (seed >> 2); +} struct VirtualFunctionCacheKey { @@ -46,14 +51,16 @@ struct VirtualFunctionCacheKeyHash { std::size_t operator()(const VirtualFunctionCacheKey& key) const { - std::size_t hash = std::hash{}(key.functionAddr); - hash ^= std::hash{}(static_cast(key.callingConvention)) << 1; - hash ^= std::hash{}(static_cast(key.returnType)) << 2; - hash ^= std::hash{}(key.vtableOffset) << 3; + std::size_t hash = 0; - for (size_t i = 0; i < key.args.size(); ++i) + hash_combine(hash, std::hash{}(key.functionAddr)); + hash_combine(hash, std::hash{}(static_cast(key.callingConvention))); + hash_combine(hash, std::hash{}(static_cast(key.returnType))); + hash_combine(hash, std::hash{}(key.vtableOffset)); + + for (const auto& arg : key.args) { - hash ^= std::hash{}(static_cast(key.args[i])) << (4 + i); + hash_combine(hash, std::hash{}(static_cast(arg))); } return hash; @@ -62,6 +69,8 @@ struct VirtualFunctionCacheKeyHash std::unordered_map m_virtualFunctionCache; +size_t GetVirtualFunctionCacheSize() { return m_virtualFunctionCache.size(); } + void* FindSignatureNative(ScriptContext& scriptContext) { auto moduleName = scriptContext.GetArgument(0); @@ -118,7 +127,6 @@ ValveFunction* CreateVirtualFunctionBySignature(ScriptContext& script_context) auto function = new ValveFunction(function_addr, CONV_CDECL, args, return_type); function->SetSignature(signature_hex_string); - m_managed_ptrs.push_back(function); m_virtualFunctionCache[cacheKey] = function; CSSHARP_CORE_TRACE("Created new virtual function, pointer found at {}, signature {}", function_addr, signature_hex_string); @@ -165,7 +173,6 @@ ValveFunction* CreateVirtualFunction(ScriptContext& script_context) auto function = new ValveFunction(function_addr, CONV_THISCALL, args, return_type); function->SetOffset(vtable_offset); - m_managed_ptrs.push_back(function); m_virtualFunctionCache[cacheKey] = function; CSSHARP_CORE_TRACE("Created new virtual function at {}, offset {}", function_addr, vtable_offset); diff --git a/src/scripting/natives/natives_vector.cpp b/src/scripting/natives/natives_vector.cpp index e08a6ae48..973006a4b 100644 --- a/src/scripting/natives/natives_vector.cpp +++ b/src/scripting/natives/natives_vector.cpp @@ -41,17 +41,19 @@ CREATE_SETTER_FUNCTION(Vector, float, Z, Vector*, obj->z = value); std::vector managed_vectors; std::vector managed_angles; extern std::vector managed_game_events; -extern std::vector m_managed_ptrs; + +extern size_t GetVirtualFunctionCacheSize(); CON_COMMAND(css_dump_leaks, "dump css leaks") { + auto virtualFunctionCount = GetVirtualFunctionCacheSize(); Msg("===== Dumping leaks =====\n"); Msg("\tVector: %i (%zu B)\n", managed_vectors.size(), managed_vectors.size() * sizeof(Vector)); Msg("\tAngles: %i (%zu B)\n", managed_angles.size(), managed_angles.size() * sizeof(QAngle)); Msg("\tGameEvents: %i (~B)\n", managed_game_events.size()); - Msg("\tVirtual Functions: %i (%zu B)\n", m_managed_ptrs.size(), m_managed_ptrs.size() * sizeof(ValveFunction)); + Msg("\tVirtual Functions: %i (%zu B)\n", virtualFunctionCount, virtualFunctionCount * sizeof(ValveFunction)); Msg("\tTotal size: %zu B\n", (managed_vectors.size() * sizeof(Vector)) + (managed_angles.size() * sizeof(QAngle)) + - (m_managed_ptrs.size() * sizeof(ValveFunction))); + (virtualFunctionCount * sizeof(ValveFunction))); Msg("===== Dumping leaks =====\n"); }