2"""Allocator/deallocator pairing: dt_alloc_align* must be freed by dt_free_align, and
5WHY THIS EXISTS, and why a build cannot replace it. dt_alloc_align() is _aligned_malloc()
6on Windows and posix_memalign() everywhere else; dt_free_align() is _aligned_free() on
7Windows and g_free() everywhere else. So on Linux and macOS BOTH families end at free(),
8every mismatch works perfectly, and no test, sanitizer or review on those platforms will
9ever see one. On Windows the same code corrupts the heap, and it crashes somewhere else
10entirely, later, in whatever unlucky code touches the heap next.
12That is a bug class you cannot find by running the program on the machine you develop on,
13which is what makes it worth a static check. Both directions are reported:
15 * something from malloc/calloc/g_malloc/g_new/strdup freed with dt_free_align()
16 * something from dt_alloc_align()/dt_calloc_align() freed with free()/g_free()/dt_free()
18Scope: matches allocations and frees of the SAME expression within the SAME file. That is
19deliberately narrow -- matching by name across files reports `data`, `buffer` and `img` in
20unrelated translation units and buries the real findings. It cannot see a struct field
21allocated in one file and freed in another; the generic-destructor case that produced the
22original bug report is prevented in the API instead (see dt_cache_seed in caches/cache.h),
23which is the better fix where it is available.
25Usage: python3 tools/check_alloc_pairing.py [--quiet]
26Exits non-zero if any mismatch is found.
28import re, os, collections
30ALIGNED_ALLOC = re.compile(
r'\b(dt_alloc_align\w*|dt_calloc_align\w*|dt_alloc_perthread\w*|dt_realloc_align\w*)\s*\(')
31PLAIN_ALLOC = re.compile(
r'(?<![_\w])(malloc|calloc|realloc|strdup|strndup|g_malloc\d*|g_try_malloc\d*|g_realloc|g_new\d*|g_strdup\w*|g_slice_new\w*)\s*\(')
32ALIGNED_FREE = re.compile(
r'\bdt_free_align(?:_ptr)?\s*\(\s*([^,;)]*)')
33PLAIN_FREE = re.compile(
r'(?<![_\w])(free|g_free|dt_free|dt_free_gpointer)\s*\(\s*([^,;)]*)')
34ASSIGN = re.compile(
r'([A-Za-z_][\w\.\->\[\]\s]*?)\s*=\s*(?:\([^)]*\)\s*)*$')
38 e = re.sub(
r'^\(+\s*|\s*\)+$',
'', e)
39 e = re.sub(
r'\(\s*[\w\s]*\*+\s*\)',
'', e)
40 e = e.strip().lstrip(
'&').strip()
41 e = re.sub(
r'\[[^\]]*\]',
'[]', e)
42 e = re.sub(
r'\s+',
'', e)
46for root, dirs, fs
in os.walk(
'src'):
47 if 'external' in root.split(os.sep):
continue
49 if f.endswith((
'.c',
'.cc',
'.h')): files.append(os.path.join(root,f))
52for path
in sorted(files):
53 if path.endswith(
'system/mem_alloc.h'):
continue
54 try: lines = open(path, encoding=
'utf-8', errors=
'replace').read().split(
'\n')
55 except Exception:
continue
56 A = collections.defaultdict(list); P = collections.defaultdict(list)
57 AF = collections.defaultdict(list); PF = collections.defaultdict(list)
58 for i, ln
in enumerate(lines, 1):
59 code = re.sub(
r'//.*$',
'', ln)
61 if st.startswith(
'*')
or st.startswith(
'/*')
or st.startswith(
'#'):
continue
62 for rx, b
in ((ALIGNED_ALLOC, A), (PLAIN_ALLOC, P)):
65 a = ASSIGN.search(code[:m.start()])
68 if k
and not k.endswith(
'='): b[k].append((i, ln.strip()[:120]))
69 m = ALIGNED_FREE.search(code)
72 if k: AF[k].append((i, ln.strip()[:120]))
73 for m
in PLAIN_FREE.finditer(code):
75 if k: PF[k].append((i, ln.strip()[:120]))
76 for k
in set(P) & set(AF):
77 findings.append((
"PLAIN alloc -> dt_free_align", path, k, P[k], AF[k]))
78 for k
in set(A) & set(PF):
79 findings.append((
"ALIGNED alloc -> plain free", path, k, A[k], PF[k]))
82quiet =
"--quiet" in sys.argv
86 print(
"OK: every dt_alloc_align* is freed by dt_free_align, and nothing else is.")
89print(f
"Allocator/deallocator mismatches: {len(findings)}")
91for kind, path, k, al, fr
in findings:
92 print(f
"[{kind}] {path} `{k}`")
93 for i,t
in al[:2]: print(f
" alloc :{i} {t}")
94 for i,t
in fr[:2]: print(f
" free :{i} {t}")
96print(
"dt_alloc_align is _aligned_malloc on Windows and dt_free_align is _aligned_free;")
97print(
"everywhere else both end at free(). Each of these works on Linux and corrupts the")
98print(
"heap on Windows, crashing later in unrelated code.")