Skip to content

Commit ff29836

Browse files
authored
fix: prevent VirtualFunction memory leak in native layer (#1121)
1 parent 832b687 commit ff29836

File tree

3 files changed

+101
-8
lines changed

3 files changed

+101
-8
lines changed

src/core/function.cpp

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,20 @@ ValveFunction::ValveFunction(void* ulAddr, Convention_t callingConvention, DataT
9898
m_iCallingConvention = GetDynCallConvention(m_eCallingConvention);
9999
}
100100

101-
ValveFunction::~ValveFunction() {}
101+
ValveFunction::~ValveFunction()
102+
{
103+
if (m_precallback != nullptr)
104+
{
105+
globals::callbackManager.ReleaseCallback(m_precallback);
106+
m_precallback = nullptr;
107+
}
108+
109+
if (m_postcallback != nullptr)
110+
{
111+
globals::callbackManager.ReleaseCallback(m_postcallback);
112+
m_postcallback = nullptr;
113+
}
114+
}
102115

103116
bool ValveFunction::IsCallable() { return (m_eCallingConvention != CONV_CUSTOM) && (m_iCallingConvention != -1); }
104117

src/scripting/natives/natives_memory.cpp

Lines changed: 82 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
#include <ios>
1818
#include <sstream>
19+
#include <unordered_map>
1920

2021
#include "core/function.h"
2122
#include "core/log.h"
@@ -24,7 +25,51 @@
2425
#include "scripting/script_engine.h"
2526

2627
namespace counterstrikesharp {
27-
std::vector<ValveFunction*> m_managed_ptrs;
28+
29+
template <class T> inline void hash_combine(std::size_t& seed, const T& v)
30+
{
31+
std::hash<T> hasher;
32+
seed ^= hasher(v) + 0x9e3779b9 + (seed << 6) + (seed >> 2);
33+
}
34+
35+
struct VirtualFunctionCacheKey
36+
{
37+
void* functionAddr;
38+
Convention_t callingConvention;
39+
std::vector<DataType_t> args;
40+
DataType_t returnType;
41+
int vtableOffset;
42+
43+
bool operator==(const VirtualFunctionCacheKey& other) const
44+
{
45+
return functionAddr == other.functionAddr && callingConvention == other.callingConvention && args == other.args &&
46+
returnType == other.returnType && vtableOffset == other.vtableOffset;
47+
}
48+
};
49+
50+
struct VirtualFunctionCacheKeyHash
51+
{
52+
std::size_t operator()(const VirtualFunctionCacheKey& key) const
53+
{
54+
std::size_t hash = 0;
55+
56+
hash_combine(hash, std::hash<void*>{}(key.functionAddr));
57+
hash_combine(hash, std::hash<int>{}(static_cast<int>(key.callingConvention)));
58+
hash_combine(hash, std::hash<int>{}(static_cast<int>(key.returnType)));
59+
hash_combine(hash, std::hash<int>{}(key.vtableOffset));
60+
61+
for (const auto& arg : key.args)
62+
{
63+
hash_combine(hash, std::hash<int>{}(static_cast<int>(arg)));
64+
}
65+
66+
return hash;
67+
}
68+
};
69+
70+
std::unordered_map<VirtualFunctionCacheKey, ValveFunction*, VirtualFunctionCacheKeyHash> m_virtualFunctionCache;
71+
72+
size_t GetVirtualFunctionCacheSize() { return m_virtualFunctionCache.size(); }
2873

2974
void* FindSignatureNative(ScriptContext& scriptContext)
3075
{
@@ -64,12 +109,28 @@ ValveFunction* CreateVirtualFunctionBySignature(ScriptContext& script_context)
64109
args.push_back(script_context.GetArgument<DataType_t>(5 + i));
65110
}
66111

112+
VirtualFunctionCacheKey cacheKey;
113+
cacheKey.functionAddr = function_addr;
114+
cacheKey.callingConvention = CONV_CDECL;
115+
cacheKey.args = args;
116+
cacheKey.returnType = return_type;
117+
cacheKey.vtableOffset = -1;
118+
119+
auto it = m_virtualFunctionCache.find(cacheKey);
120+
if (it != m_virtualFunctionCache.end())
121+
{
122+
CSSHARP_CORE_TRACE("Virtual function found in cache, reusing existing instance at {}, signature {}", function_addr,
123+
signature_hex_string);
124+
return it->second;
125+
}
126+
67127
auto function = new ValveFunction(function_addr, CONV_CDECL, args, return_type);
68128
function->SetSignature(signature_hex_string);
69129

70-
CSSHARP_CORE_TRACE("Created virtual function, pointer found at {}, signature {}", function_addr, signature_hex_string);
130+
m_virtualFunctionCache[cacheKey] = function;
131+
132+
CSSHARP_CORE_TRACE("Created new virtual function, pointer found at {}, signature {}", function_addr, signature_hex_string);
71133

72-
m_managed_ptrs.push_back(function);
73134
return function;
74135
}
75136

@@ -95,10 +156,27 @@ ValveFunction* CreateVirtualFunction(ScriptContext& script_context)
95156
args.push_back(script_context.GetArgument<DataType_t>(4 + i));
96157
}
97158

159+
VirtualFunctionCacheKey cacheKey;
160+
cacheKey.functionAddr = function_addr;
161+
cacheKey.callingConvention = CONV_THISCALL;
162+
cacheKey.args = args;
163+
cacheKey.returnType = return_type;
164+
cacheKey.vtableOffset = vtable_offset;
165+
166+
auto it = m_virtualFunctionCache.find(cacheKey);
167+
if (it != m_virtualFunctionCache.end())
168+
{
169+
CSSHARP_CORE_TRACE("Virtual function found in cache, reusing existing instance at {}, offset {}", function_addr, vtable_offset);
170+
return it->second;
171+
}
172+
98173
auto function = new ValveFunction(function_addr, CONV_THISCALL, args, return_type);
99174
function->SetOffset(vtable_offset);
100175

101-
m_managed_ptrs.push_back(function);
176+
m_virtualFunctionCache[cacheKey] = function;
177+
178+
CSSHARP_CORE_TRACE("Created new virtual function at {}, offset {}", function_addr, vtable_offset);
179+
102180
return function;
103181
}
104182

src/scripting/natives/natives_vector.cpp

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,17 +41,19 @@ CREATE_SETTER_FUNCTION(Vector, float, Z, Vector*, obj->z = value);
4141
std::vector<Vector*> managed_vectors;
4242
std::vector<QAngle*> managed_angles;
4343
extern std::vector<IGameEvent*> managed_game_events;
44-
extern std::vector<ValveFunction*> m_managed_ptrs;
44+
45+
extern size_t GetVirtualFunctionCacheSize();
4546

4647
CON_COMMAND(css_dump_leaks, "dump css leaks")
4748
{
49+
auto virtualFunctionCount = GetVirtualFunctionCacheSize();
4850
Msg("===== Dumping leaks =====\n");
4951
Msg("\tVector: %i (%zu B)\n", managed_vectors.size(), managed_vectors.size() * sizeof(Vector));
5052
Msg("\tAngles: %i (%zu B)\n", managed_angles.size(), managed_angles.size() * sizeof(QAngle));
5153
Msg("\tGameEvents: %i (~B)\n", managed_game_events.size());
52-
Msg("\tVirtual Functions: %i (%zu B)\n", m_managed_ptrs.size(), m_managed_ptrs.size() * sizeof(ValveFunction));
54+
Msg("\tVirtual Functions: %i (%zu B)\n", virtualFunctionCount, virtualFunctionCount * sizeof(ValveFunction));
5355
Msg("\tTotal size: %zu B\n", (managed_vectors.size() * sizeof(Vector)) + (managed_angles.size() * sizeof(QAngle)) +
54-
(m_managed_ptrs.size() * sizeof(ValveFunction)));
56+
(virtualFunctionCount * sizeof(ValveFunction)));
5557
Msg("===== Dumping leaks =====\n");
5658
}
5759

0 commit comments

Comments
 (0)