210 lines
7.3 KiB
Python
210 lines
7.3 KiB
Python
#!/usr/bin/env python3
|
|
"""Guard pymalloc_pool_extend's C89 declaration and operation order."""
|
|
|
|
import re
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
source = Path(sys.argv[1]).read_text()
|
|
match = re.search(
|
|
r"\npymalloc_pool_extend\(poolp pool, uint size\)\s*\{(.*?)\n\}",
|
|
source, re.S,
|
|
)
|
|
assert match, "pymalloc_pool_extend definition not found"
|
|
body = re.sub(r"/\*.*?\*/", "", match.group(1), flags=re.S)
|
|
assert body.split()[:2] == ["poolp", "next;"], (
|
|
"pymalloc_pool_extend must declare next without an initializer at function entry"
|
|
)
|
|
|
|
# The extension path returns before reading nextpool. Keep the full-pool
|
|
# unlink reads and writes in their original order.
|
|
expected = """
|
|
poolp next;
|
|
if (UNLIKELY(pool->nextoffset <= pool->maxnextoffset)) {
|
|
pool->freeblock = (block*)pool + pool->nextoffset;
|
|
pool->nextoffset += INDEX2SIZE(size);
|
|
*(block **)(pool->freeblock) = NULL;
|
|
return;
|
|
}
|
|
next = pool->nextpool;
|
|
pool = pool->prevpool;
|
|
next->prevpool = pool;
|
|
pool->nextpool = next;
|
|
"""
|
|
assert body.split() == expected.split(), (
|
|
"preserve pymalloc_pool_extend extension, early return, and unlink evaluation order"
|
|
)
|
|
print("obmalloc pymalloc_pool_extend C89 declaration and original operation order: PASS")
|
|
|
|
|
|
match = re.search(
|
|
r"\nallocate_from_new_pool\(uint size\)\s*\{(.*?)\n\}",
|
|
source, re.S,
|
|
)
|
|
assert match, "allocate_from_new_pool definition not found"
|
|
body = re.sub(r"/\*.*?\*/", "", match.group(1), flags=re.S)
|
|
assert body.split()[:2] == ["poolp", "pool;"], (
|
|
"allocate_from_new_pool must declare pool without an initializer at function entry"
|
|
)
|
|
|
|
assert body.split()[:6] == ["poolp", "pool;", "block", "*bp;", "poolp", "next;"], (
|
|
"allocate_from_new_pool must declare bp and next without initializers at function entry"
|
|
)
|
|
assert re.findall(r"\bblock\s+\*bp\s*;", body) == ["block *bp;"], (
|
|
"allocate_from_new_pool must declare bp exactly once"
|
|
)
|
|
assert re.findall(r"\bpoolp\s+next\b", body) == ["poolp next"], (
|
|
"allocate_from_new_pool must declare next exactly once"
|
|
)
|
|
|
|
# Keep the freepools read after arena creation and arena-list updates, and
|
|
# preserve all subsequent cached-pool, new-pool, and initialization branches.
|
|
# Keep next's usedpools read immediately before the frontlink writes.
|
|
expected = """
|
|
poolp pool;
|
|
block *bp;
|
|
poolp next;
|
|
if (UNLIKELY(usable_arenas == NULL)) {
|
|
#ifdef WITH_MEMORY_LIMITS
|
|
if (narenas_currently_allocated >= MAX_ARENAS) {
|
|
return NULL;
|
|
}
|
|
#endif
|
|
usable_arenas = new_arena();
|
|
if (usable_arenas == NULL) {
|
|
return NULL;
|
|
}
|
|
usable_arenas->nextarena = usable_arenas->prevarena = NULL;
|
|
assert(nfp2lasta[usable_arenas->nfreepools] == NULL);
|
|
nfp2lasta[usable_arenas->nfreepools] = usable_arenas;
|
|
}
|
|
assert(usable_arenas->address != 0);
|
|
assert(usable_arenas->nfreepools > 0);
|
|
if (nfp2lasta[usable_arenas->nfreepools] == usable_arenas) {
|
|
nfp2lasta[usable_arenas->nfreepools] = NULL;
|
|
}
|
|
if (usable_arenas->nfreepools > 1) {
|
|
assert(nfp2lasta[usable_arenas->nfreepools - 1] == NULL);
|
|
nfp2lasta[usable_arenas->nfreepools - 1] = usable_arenas;
|
|
}
|
|
pool = usable_arenas->freepools;
|
|
if (LIKELY(pool != NULL)) {
|
|
usable_arenas->freepools = pool->nextpool;
|
|
usable_arenas->nfreepools--;
|
|
if (UNLIKELY(usable_arenas->nfreepools == 0)) {
|
|
assert(usable_arenas->freepools == NULL);
|
|
assert(usable_arenas->nextarena == NULL ||
|
|
usable_arenas->nextarena->prevarena ==
|
|
usable_arenas);
|
|
usable_arenas = usable_arenas->nextarena;
|
|
if (usable_arenas != NULL) {
|
|
usable_arenas->prevarena = NULL;
|
|
assert(usable_arenas->address != 0);
|
|
}
|
|
}
|
|
else {
|
|
assert(usable_arenas->freepools != NULL ||
|
|
usable_arenas->pool_address <=
|
|
(block*)usable_arenas->address +
|
|
ARENA_SIZE - POOL_SIZE);
|
|
}
|
|
}
|
|
else {
|
|
assert(usable_arenas->nfreepools > 0);
|
|
assert(usable_arenas->freepools == NULL);
|
|
pool = (poolp)usable_arenas->pool_address;
|
|
assert((block*)pool <= (block*)usable_arenas->address +
|
|
ARENA_SIZE - POOL_SIZE);
|
|
pool->arenaindex = (uint)(usable_arenas - arenas);
|
|
assert(&arenas[pool->arenaindex] == usable_arenas);
|
|
pool->szidx = DUMMY_SIZE_IDX;
|
|
usable_arenas->pool_address += POOL_SIZE;
|
|
--usable_arenas->nfreepools;
|
|
if (usable_arenas->nfreepools == 0) {
|
|
assert(usable_arenas->nextarena == NULL ||
|
|
usable_arenas->nextarena->prevarena ==
|
|
usable_arenas);
|
|
usable_arenas = usable_arenas->nextarena;
|
|
if (usable_arenas != NULL) {
|
|
usable_arenas->prevarena = NULL;
|
|
assert(usable_arenas->address != 0);
|
|
}
|
|
}
|
|
}
|
|
next = usedpools[size + size];
|
|
pool->nextpool = next;
|
|
pool->prevpool = next;
|
|
next->nextpool = pool;
|
|
next->prevpool = pool;
|
|
pool->ref.count = 1;
|
|
if (pool->szidx == size) {
|
|
bp = pool->freeblock;
|
|
assert(bp != NULL);
|
|
pool->freeblock = *(block **)bp;
|
|
return bp;
|
|
}
|
|
pool->szidx = size;
|
|
size = INDEX2SIZE(size);
|
|
bp = (block *)pool + POOL_OVERHEAD;
|
|
pool->nextoffset = POOL_OVERHEAD + (size << 1);
|
|
pool->maxnextoffset = POOL_SIZE - size;
|
|
pool->freeblock = bp + size;
|
|
*(block **)(pool->freeblock) = NULL;
|
|
return bp;
|
|
"""
|
|
assert body.split() == expected.split(), (
|
|
"preserve allocate_from_new_pool arena updates, freepools assignment, and control-flow order"
|
|
)
|
|
print("obmalloc allocate_from_new_pool pool/bp/next declarations and original operation order: PASS")
|
|
|
|
|
|
match = re.search(
|
|
r"\npymalloc_alloc\(void \*ctx, size_t nbytes\)\s*\{(.*?)\n\}",
|
|
source, re.S,
|
|
)
|
|
assert match, "pymalloc_alloc definition not found"
|
|
body = re.sub(r"/\*.*?\*/|//[^\n]*", "", match.group(1), flags=re.S)
|
|
assert body.split()[:6] == ["uint", "size;", "poolp", "pool;", "block", "*bp;"], (
|
|
"pymalloc_alloc must declare size, pool, and bp without initializers at function entry"
|
|
)
|
|
|
|
# Preserve the conditional Valgrind checks and request-size early returns before
|
|
# evaluating size and reading usedpools, plus the exact bp branch operations.
|
|
expected = """
|
|
uint size;
|
|
poolp pool;
|
|
block *bp;
|
|
#ifdef WITH_VALGRIND
|
|
if (UNLIKELY(running_on_valgrind == -1)) {
|
|
running_on_valgrind = RUNNING_ON_VALGRIND;
|
|
}
|
|
if (UNLIKELY(running_on_valgrind)) {
|
|
return NULL;
|
|
}
|
|
#endif
|
|
if (UNLIKELY(nbytes == 0)) {
|
|
return NULL;
|
|
}
|
|
if (UNLIKELY(nbytes > SMALL_REQUEST_THRESHOLD)) {
|
|
return NULL;
|
|
}
|
|
size = (uint)(nbytes - 1) >> ALIGNMENT_SHIFT;
|
|
pool = usedpools[size + size];
|
|
if (LIKELY(pool != pool->nextpool)) {
|
|
++pool->ref.count;
|
|
bp = pool->freeblock;
|
|
assert(bp != NULL);
|
|
if (UNLIKELY((pool->freeblock = *(block **)bp) == NULL)) {
|
|
pymalloc_pool_extend(pool, size);
|
|
}
|
|
}
|
|
else {
|
|
bp = allocate_from_new_pool(size);
|
|
}
|
|
return (void *)bp;
|
|
"""
|
|
assert body.split() == expected.split(), (
|
|
"preserve pymalloc_alloc early returns, size/pool evaluation, and bp control-flow order"
|
|
)
|
|
print("obmalloc pymalloc_alloc size/pool/bp declarations and original operation order: PASS")
|