Skip to content

Commit 5fdcc64

Browse files
committed
Add supprt for new c23 deallocators
This commit introduces 2 new functions to add support for `free_sized` and `free_aligned_sized` deallocators introduced in c23. We ensure that `free_sized` and `free_aligned_sized` are skipped if unavailable otherwise we get a `FREE` record recorded for them. Signed-off-by: Nana Adjei Manu <[email protected]>
1 parent 1425302 commit 5fdcc64

File tree

5 files changed

+192
-0
lines changed

5 files changed

+192
-0
lines changed

src/memray/_memray/hooks.cpp

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,20 @@ free(void* ptr) noexcept
196196
}
197197
}
198198

199+
#if defined(__GLIBC__)
200+
void
201+
free_sized(void* ptr, size_t size) noexcept
202+
{
203+
memray::intercept::free(ptr);
204+
}
205+
206+
void
207+
free_aligned_sized(void* ptr, size_t alignment, size_t size) noexcept
208+
{
209+
memray::intercept::free(ptr);
210+
}
211+
#endif
212+
199213
void*
200214
realloc(void* ptr, size_t size) noexcept
201215
{

src/memray/_memray/hooks.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@
3131
FOR_EACH_HOOKED_FUNCTION(prctl) \
3232
FOR_EACH_HOOKED_FUNCTION(pvalloc) \
3333
FOR_EACH_HOOKED_FUNCTION(mmap64)
34+
FOR_EACH_HOOKED_FUNCTION(free_sized) \
35+
FOR_EACH_HOOKED_FUNCTION(free_aligned_sized)
3436
#else
3537
# define MEMRAY_PLATFORM_HOOKED_FUNCTIONS \
3638
FOR_EACH_HOOKED_FUNCTION(memalign) \
@@ -199,6 +201,12 @@ mmap(void* addr, size_t length, int prot, int flags, int fd, off_t offset) noexc
199201
#if defined(__GLIBC__)
200202
void*
201203
mmap64(void* addr, size_t length, int prot, int flags, int fd, off64_t offset) noexcept;
204+
205+
void
206+
free_sized(void* ptr, size_t size) noexcept;
207+
208+
void
209+
free_aligned_sized(void* ptr, size_t alignment, size_t size) noexcept;
202210
#endif
203211

204212
int
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
#define PY_SSIZE_T_CLEAN
2+
#include <Python.h>
3+
4+
#include <cassert>
5+
#include <cstdlib>
6+
7+
#ifdef __linux__
8+
#include <malloc.h>
9+
#endif
10+
11+
namespace { // unnamed
12+
13+
extern "C" void
14+
test_free_sized()
15+
{
16+
void* ptr = malloc(1024);
17+
assert(ptr != nullptr);
18+
19+
#ifdef __GLIBC__
20+
extern void free_sized(void* ptr, size_t size);
21+
free_sized(ptr, 1024);
22+
#else
23+
free(ptr);
24+
#endif
25+
}
26+
27+
extern "C" void
28+
test_free_aligned_sized()
29+
{
30+
void* ptr = aligned_alloc(64, 1024);
31+
assert(ptr != nullptr);
32+
33+
#ifdef __GLIBC__
34+
extern void free_aligned_sized(void* ptr, size_t alignment, size_t size);
35+
free_aligned_sized(ptr, 64, 1024);
36+
#else
37+
free(ptr);
38+
#endif
39+
}
40+
41+
extern "C" void
42+
test_both_free_functions()
43+
{
44+
void* ptr1 = malloc(512);
45+
assert(ptr1 != nullptr);
46+
#ifdef __GLIBC__
47+
extern void free_sized(void* ptr, size_t size);
48+
free_sized(ptr1, 512);
49+
#else
50+
free(ptr1);
51+
#endif
52+
53+
void* ptr2 = aligned_alloc(32, 256);
54+
assert(ptr2 != nullptr);
55+
#ifdef __GLIBC__
56+
extern void free_aligned_sized(void* ptr, size_t alignment, size_t size);
57+
free_aligned_sized(ptr2, 32, 256);
58+
#else
59+
free(ptr2);
60+
#endif
61+
}
62+
63+
PyObject*
64+
run_free_sized_test(PyObject*, PyObject*)
65+
{
66+
test_free_sized();
67+
Py_RETURN_NONE;
68+
}
69+
70+
PyObject*
71+
run_free_aligned_sized_test(PyObject*, PyObject*)
72+
{
73+
test_free_aligned_sized();
74+
Py_RETURN_NONE;
75+
}
76+
77+
PyObject*
78+
run_both_tests(PyObject*, PyObject*)
79+
{
80+
test_both_free_functions();
81+
Py_RETURN_NONE;
82+
}
83+
84+
} // unnamed namespace
85+
86+
static PyMethodDef
87+
free_sized_methods[] = {
88+
{"run_free_sized_test", run_free_sized_test, METH_NOARGS, "Test free_sized function"},
89+
{"run_free_aligned_sized_test", run_free_aligned_sized_test, METH_NOARGS, "Test free_aligned_sized function"},
90+
{"run_both_tests", run_both_tests, METH_NOARGS, "Test both free functions"},
91+
{nullptr, nullptr, 0, nullptr}
92+
};
93+
94+
static PyModuleDef
95+
free_sized_module = {
96+
PyModuleDef_HEAD_INIT,
97+
"free_sized_test",
98+
"Test module for free_sized and free_aligned_sized functions",
99+
-1,
100+
free_sized_methods,
101+
nullptr,
102+
nullptr,
103+
nullptr,
104+
nullptr
105+
};
106+
107+
PyMODINIT_FUNC
108+
PyInit_free_sized_test()
109+
{
110+
return PyModule_Create(&free_sized_module);
111+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
from setuptools import Extension, setup
2+
3+
setup(
4+
name="free_sized_extension",
5+
ext_modules=[
6+
Extension(
7+
"free_sized_test",
8+
sources=["free_sized_test.cpp"],
9+
language="c++",
10+
)
11+
],
12+
)

tests/integration/test_extensions.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
TEST_MULTITHREADED_EXTENSION = HERE / "multithreaded_extension"
1616
TEST_MISBEHAVING_EXTENSION = HERE / "misbehaving_extension"
1717
TEST_RPATH_EXTENSION = HERE / "rpath_extension"
18+
TEST_FREE_SIZED_EXTENSION = HERE / "free_sized_extension"
1819

1920

2021
@pytest.mark.valgrind
@@ -386,3 +387,49 @@ def test_dlopen_with_rpath(tmpdir, monkeypatch):
386387
# THEN
387388
with Tracker(output):
388389
hello_world()
390+
391+
@pytest.mark.skipif(
392+
not hasattr(ctypes.CDLL(None), "free_sized"),
393+
reason="free_sized not available on this system"
394+
)
395+
def test_free_sized_extension(tmpdir, monkeypatch):
396+
"""Test tracking allocations in a native extension which uses free_sized and free_aligned_sized."""
397+
# GIVEN
398+
output = Path(tmpdir) / "test.bin"
399+
extension_name = "free_sized_extension"
400+
extension_path = tmpdir / extension_name
401+
shutil.copytree(TEST_FREE_SIZED_EXTENSION, extension_path)
402+
subprocess.run(
403+
[sys.executable, str(extension_path / "setup.py"), "build_ext", "--inplace"],
404+
check=True,
405+
cwd=extension_path,
406+
capture_output=True,
407+
)
408+
409+
# WHEN
410+
with monkeypatch.context() as ctx:
411+
ctx.setattr(sys, "path", [*sys.path, str(extension_path)])
412+
from free_sized_test import run_both_tests # type: ignore
413+
414+
with Tracker(output):
415+
run_both_tests()
416+
417+
# THEN
418+
records = list(FileReader(output).get_allocation_records())
419+
assert records
420+
421+
# Check that at least 2 allocations from malloc and aligned_alloc
422+
mallocs = [record for record in records if record.allocator == AllocatorType.MALLOC]
423+
assert len(mallocs) >= 2
424+
425+
# Check that corresponding FREE records - this verifies hooks are working!
426+
mallocs_addr = {record.address for record in mallocs}
427+
frees = [
428+
record
429+
for record in records
430+
if record.address in mallocs_addr and record.allocator == AllocatorType.FREE
431+
]
432+
assert len(frees) >= 2
433+
434+
assert all(len(malloc.stack_trace()) == 0 for malloc in mallocs)
435+
assert all(len(free.stack_trace()) == 0 for free in frees)

0 commit comments

Comments
 (0)