Files
Python3-Win9x/scripts/vc6-probe-pythoncore-frontier.test.sh

9953 lines
387 KiB
Bash
Executable File

#!/usr/bin/env bash
#
# Regression tests for scripts/vc6-probe-pythoncore-frontier.sh.
#
# The no-toolchain checks exercise the source-list parser/normalizer and the
# per-file define handling that make the frontier source-list-driven. When
# Wine + MSVC600 are available, a bounded live run verifies that the harness
# reports cl.exe's real first-blocker status.
#
# Usage: scripts/vc6-probe-pythoncore-frontier.test.sh
set -u
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
PROBE="$REPO_ROOT/scripts/vc6-probe-pythoncore-frontier.sh"
fail=0
pass() { echo "ok - $1"; }
bad() { echo "not ok - $1"; fail=1; }
out="$($PROBE --list-only 2>&1)"
status=$?
if [ "$status" -eq 0 ]; then
pass "frontier --list-only exits 0"
else
bad "frontier --list-only exits 0 (got exit $status)"
echo "$out"
fi
count="$(printf '%s\n' "$out" | awk 'NF { n++ } END { print n+0 }')"
if [ "$count" -eq 198 ]; then
pass "frontier parses all 198 pythoncore.vcxproj ClCompile entries"
else
bad "frontier parses all 198 pythoncore.vcxproj ClCompile entries (got $count)"
fi
first_fields="$(printf '%s\n' "$out" | awk -F '\t' 'NR == 1 { print $1 "|" $2 "|" $3 }')"
if [ "$first_fields" = 'compile|cpython/Modules/getpath.c|..\Modules\getpath.c' ]; then
pass "frontier normalizes the first Windows-style source path to cpython/Modules/getpath.c"
else
bad "frontier normalizes the first Windows-style source path to cpython/Modules/getpath.c (got: $first_fields)"
fi
getpath_defs="$(printf '%s\n' "$out" | awk -F '\t' '$2 == "cpython/Modules/getpath.c" { print $4; exit }')"
case "$getpath_defs" in
*'PREFIX=NULL'*'EXEC_PREFIX=NULL'*'VERSION=NULL'*'VPATH="..\\.."'*'PYDEBUGEXT=""'*'PLATLIBDIR="DLLs"'*)
pass "frontier preserves getpath.c per-file defines with project-local substitutions"
;;
*)
bad "frontier preserves getpath.c per-file defines with project-local substitutions (got: $getpath_defs)"
;;
esac
sysmodule_defs="$(printf '%s\n' "$out" | awk -F '\t' '$2 == "cpython/Python/sysmodule.c" { print $4; exit }')"
case "$sysmodule_defs" in
*'VPATH="..\\.."'*)
pass "frontier preserves sysmodule.c VPATH per-file define"
;;
*)
bad "frontier preserves sysmodule.c VPATH per-file define (got: $sysmodule_defs)"
;;
esac
binascii_defs="$(printf '%s\n' "$out" | awk -F '\t' '$2 == "cpython/Modules/binascii.c" { print $4; exit }')"
case "$binascii_defs" in
*USE_ZLIB_CRC32*)
bad "frontier disables unavailable zlib CRC32 path for binascii.c (got: $binascii_defs)"
;;
*)
pass "frontier disables unavailable zlib CRC32 path for binascii.c"
;;
esac
# Source the probe after --list-only so the guarded helper definitions can be
# tested without launching Wine or requiring MSVC600.
# shellcheck source=scripts/vc6-probe-pythoncore-frontier.sh
source "$PROBE"
rendered_cmd="$(render_compile_command_for_test 'VPATH="..\\.."' 'PYDEBUGEXT=""' 'PLATLIBDIR="DLLs"')"
case "$rendered_cmd" in
*' /D VPATH=\"..\\\\..\"'*' /D PYDEBUGEXT=\"\"'*' /D PLATLIBDIR=\"DLLs\"'*)
pass "frontier renders quoted per-file string defines safely for CL response files"
;;
*)
bad "frontier renders quoted per-file string defines safely for CL response files (got: $rendered_cmd)"
;;
esac
if printf '%s\n' "$out" | grep -q $'^skip-generated deepfreeze source\tcpython/Python/deepfreeze/deepfreeze.c\t'; then
pass "frontier reports the generated deepfreeze source instead of compiling it blindly"
else
bad "frontier reports the generated deepfreeze source instead of compiling it blindly"
fi
if printf '%s\n' "$out" | grep -q $'^skip-external IncludeExternals-conditioned source\tcpython/Modules/zlibmodule.c\t'; then
pass "frontier reports IncludeExternals-conditioned in-tree zlibmodule.c as external"
else
bad "frontier reports IncludeExternals-conditioned in-tree zlibmodule.c as external"
fi
if printf '%s\n' "$out" | grep -q $'^skip-external IncludeExternals-conditioned source\t$(zlibDir)/adler32.c\t'; then
pass "frontier reports macro-based zlibDir sources as external without normalizing outside the repo"
else
bad "frontier reports macro-based zlibDir sources as external without normalizing outside the repo"
fi
if printf '%s\n' "$out" | cut -f2 | grep -q '\\'; then
bad "frontier normalized source column contains no backslashes"
else
pass "frontier normalized source column contains no backslashes"
fi
# Keep the current cmath.c VC6 frontier explicit: the old compiler has no C99
# NAN macro, so pymath.h must provide Py_NAN without referring to NAN.
if python3 - "$REPO_ROOT/cpython/Include/pymath.h" <<'PY'
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
if "# if defined(_MSC_VER) && _MSC_VER < 1300" not in source:
raise SystemExit(1)
start = source.index("#if !defined(Py_NAN)")
end = source.index("#endif\n\n#endif /* Py_PYMATH_H */", start)
block = source[start:end]
if "# define Py_NAN (Py_HUGE_VAL * 0.0)" not in block:
raise SystemExit(1)
if "# define Py_NAN ((double)NAN)" not in block:
raise SystemExit(1)
PY
then
pass "pymath.h provides a VC6 Py_NAN fallback without C99 NAN"
else
bad "pymath.h provides a VC6 Py_NAN fallback without C99 NAN"
fi
# Keep the current mathmodule.c VC6 frontier explicit: MSVC6 has no CRT
# atanh(), so the guarded fallback must preserve signed zero, NaN, and domain
# error behavior while native compilers retain the CRT path.
if python3 - "$REPO_ROOT/cpython/Modules/mathmodule.c" <<'PY'
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
start = source.index("#if defined(_MSC_VER) && _MSC_VER < 1300")
end = source.index("#else\n# define PY_MATH_ULONG_LONG", start)
block = source[start:end]
if "m_atanh(double x)" not in block:
raise SystemExit("VC6 m_atanh fallback missing")
if "if (Py_IS_NAN(x))" not in block:
raise SystemExit("m_atanh NaN handling missing")
if "if (a >= 1.0)" not in block or "errno = EDOM;" not in block:
raise SystemExit("m_atanh domain handling missing")
if "return x;" not in block:
raise SystemExit("m_atanh signed-zero/small-input preservation missing")
if "FUNC1(atanh, m_atanh, 0," not in source:
raise SystemExit("atanh is not routed through m_atanh")
if "# define m_atanh atanh" not in source:
raise SystemExit("native m_atanh mapping missing")
PY
then
pass "mathmodule provides a guarded VC6 atanh fallback"
else
bad "mathmodule provides a guarded VC6 atanh fallback"
fi
# Keep the current mathmodule.c VC6 frontier explicit: MSVC6 has no CRT
# cbrt(), so the guarded fallback must preserve zero/NaN and native mapping.
if python3 - "$REPO_ROOT/cpython/Modules/mathmodule.c" <<'PY'
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
start = source.index("#if defined(_MSC_VER) && _MSC_VER < 1300")
end = source.index("#else\n# define PY_MATH_ULONG_LONG", start)
block = source[start:end]
if "m_cbrt(double x)" not in block:
raise SystemExit("VC6 m_cbrt fallback missing")
if "if (Py_IS_NAN(x))" not in block or "if (x == 0.0)" not in block:
raise SystemExit("m_cbrt special-value handling missing")
if "FUNC1(cbrt, m_cbrt, 0," not in source:
raise SystemExit("cbrt is not routed through m_cbrt")
if "# define m_cbrt cbrt" not in source:
raise SystemExit("native m_cbrt mapping missing")
PY
then
pass "mathmodule provides a guarded VC6 cbrt fallback"
else
bad "mathmodule provides a guarded VC6 cbrt fallback"
fi
# Keep the current mathmodule.c VC6 frontier explicit: MSVC6 has no CRT
# exp2(), so the guarded fallback must preserve NaN handling and native mapping.
if python3 - "$REPO_ROOT/cpython/Modules/mathmodule.c" <<'PY'
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
start = source.index("#if defined(_MSC_VER) && _MSC_VER < 1300")
end = source.index("#else\n# define PY_MATH_ULONG_LONG", start)
block = source[start:end]
if "m_exp2(double x)" not in block:
raise SystemExit("VC6 m_exp2 fallback missing")
if "if (Py_IS_NAN(x))" not in block:
raise SystemExit("m_exp2 NaN handling missing")
if "FUNC1(exp2, m_exp2, 1," not in source:
raise SystemExit("exp2 is not routed through m_exp2")
if "# define m_exp2 exp2" not in source:
raise SystemExit("native m_exp2 mapping missing")
PY
then
pass "mathmodule provides a guarded VC6 exp2 fallback"
else
bad "mathmodule provides a guarded VC6 exp2 fallback"
fi
# Keep the current mathmodule.c VC6 frontier explicit: MSVC6 has no CRT
# expm1(), so the guarded fallback must preserve small inputs and native mapping.
if python3 - "$REPO_ROOT/cpython/Modules/mathmodule.c" <<'PY'
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
start = source.index("#if defined(_MSC_VER) && _MSC_VER < 1300")
end = source.index("#else\n# define PY_MATH_ULONG_LONG", start)
block = source[start:end]
if "m_expm1(double x)" not in block:
raise SystemExit("VC6 m_expm1 fallback missing")
if "if (fabs(x) < 1e-5)" not in block:
raise SystemExit("m_expm1 small-input handling missing")
if "FUNC1(expm1, m_expm1, 1," not in source:
raise SystemExit("expm1 is not routed through m_expm1")
if "# define m_expm1 expm1" not in source:
raise SystemExit("native m_expm1 mapping missing")
PY
then
pass "mathmodule provides a guarded VC6 expm1 fallback"
else
bad "mathmodule provides a guarded VC6 expm1 fallback"
fi
# Keep the first _pickle.c VC6/C89 slice explicit: batch_list_exact() must
# declare its shared error local before executable statements.
if python3 - "$REPO_ROOT/cpython/Modules/_pickle.c" <<'PY'
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
start = source.index("batch_list_exact(PicklerObject *self, PyObject *obj)")
end = source.index("\n}\n\nstatic int\nsave_list", start) + 2
function = source[start:end]
if " int err;" not in function:
raise SystemExit(1)
if " int err = save(self, item, 0);" in function:
raise SystemExit(1)
if function.count(" err = save(self, item, 0);") != 2:
raise SystemExit(1)
PY
then
pass "_pickle batch_list_exact uses C89 declaration-order-safe error local"
else
bad "_pickle batch_list_exact uses C89 declaration-order-safe error local"
fi
# Keep the current posixmodule.c VC6/C89 frontier explicit: os_spawnv_impl()
# must hoist saved_errno before the first executable statement.
if python3 - "$REPO_ROOT/cpython/Modules/posixmodule.c" <<'PY'
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
start = source.index("os_spawnv_impl(PyObject *module, int mode, path_t *path, PyObject *argv)")
end = source.index("\n}\n\n/*[clinic input]", start) + 2
function = source[start:end]
if " int saved_errno;" not in function:
raise SystemExit("os_spawnv_impl saved_errno declaration missing from opening group")
if " int saved_errno = errno;" in function:
raise SystemExit("os_spawnv_impl saved_errno remains a mixed declaration")
if " saved_errno = errno;\n free_string_array(argvlist, argc);" not in function:
raise SystemExit("os_spawnv_impl saved_errno assignment/cleanup order changed")
PY
then
pass "posix os_spawnv_impl uses a C89 declaration-order-safe saved_errno"
else
bad "posix os_spawnv_impl uses a C89 declaration-order-safe saved_errno"
fi
# Keep the current posixmodule.c VC6 frontier explicit: MSVC6 rejects the
# spelling "long long", while CPython's PY_LONG_LONG alias is already the
# project-compatible 64-bit type.
if python3 - "$REPO_ROOT/cpython/Modules/posixmodule.c" <<'PY'
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
start = source.index("_pystat_fromstructstat(PyObject *module, STRUCT_STAT *st)")
end = source.index("\n}\n", start) + 2
function = source[start:end]
if "sizeof(PY_LONG_LONG) >= sizeof(st->st_size)" not in function:
raise SystemExit("VC6 stat-size assertion must use PY_LONG_LONG")
if "sizeof(long long) >= sizeof(st->st_size)" in function:
raise SystemExit("VC6 stat-size assertion still spells long long")
PY
then
pass "posix stat-size assertion uses the VC6-compatible PY_LONG_LONG spelling"
else
bad "posix stat-size assertion uses the VC6-compatible PY_LONG_LONG spelling"
fi
# Keep the Windows waitpid status local in the opening C89 declaration group,
# preserving the original conversion point and shifted result argument order.
if python3 - "$REPO_ROOT/cpython/Modules/posixmodule.c" <<'PY_WAITPID_TEST'
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
start = source.index("os_waitpid_impl(PyObject *module, intptr_t pid, int options)")
start = source.index("\n{\n", start)
end = source.index("\n}\n", start) + 3
expected = """
{
int status;
intptr_t res;
int async_err = 0;
PY_LONG_LONG ustatus;
do {
Py_BEGIN_ALLOW_THREADS
_Py_BEGIN_SUPPRESS_IPH
res = _cwait(&status, pid, options);
_Py_END_SUPPRESS_IPH
Py_END_ALLOW_THREADS
} while (res < 0 && errno == EINTR && !(async_err = PyErr_CheckSignals()));
if (res < 0)
return (!async_err) ? posix_error() : NULL;
ustatus = (unsigned int)status;
/* shift the status left a byte so this is more like the POSIX waitpid */
return Py_BuildValue(_Py_PARSE_INTPTR "K", res, ustatus << 8);
}
"""
assert source[start:end] == expected, (
"os_waitpid_impl must hoist PY_LONG_LONG ustatus into the opening C89 "
"declaration group and preserve conversion/result order"
)
PY_WAITPID_TEST
then
pass "posix os_waitpid_impl preserves C89 declarations and conversion/result order"
else
bad "posix os_waitpid_impl preserves C89 declarations and conversion/result order"
fi
# Keep the next _pickle.c VC6/C89 slice explicit: save_set() must declare its
# shared error local before the loop's executable statements.
if python3 - "$REPO_ROOT/cpython/Modules/_pickle.c" <<'PY'
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
start = source.index("save_set(PicklerObject *self, PyObject *obj)")
end = source.index("\n}\n\nstatic int\nsave_frozenset", start) + 2
function = source[start:end]
if " int err;" not in function:
raise SystemExit(1)
if " int err = save(self, item, 0);" in function:
raise SystemExit(1)
if function.count(" err = save(self, item, 0);") != 1:
raise SystemExit(1)
PY
then
pass "_pickle save_set uses C89 declaration-order-safe error local"
else
bad "_pickle save_set uses C89 declaration-order-safe error local"
fi
# Keep the current _pickle.c VC6/C89 frontier explicit: Pickler_traverse()
# must declare memo before the preceding Py_VISIT executable statements.
if python3 - "$REPO_ROOT/cpython/Modules/_pickle.c" <<'PY'
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
start = source.index("Pickler_traverse(PicklerObject *self, visitproc visit, void *arg)")
end = source.index("\n}\n\nstatic int\nPickler_clear", start) + 2
function = source[start:end]
if " PyMemoTable *memo;" not in function:
raise SystemExit(1)
if " PyMemoTable *memo = self->memo;" in function:
raise SystemExit(1)
if function.count(" memo = self->memo;") != 1:
raise SystemExit(1)
PY
then
pass "_pickle Pickler_traverse uses C89 declaration-order-safe memo local"
else
bad "_pickle Pickler_traverse uses C89 declaration-order-safe memo local"
fi
# Keep the current _pickle.c VC6/C89 frontier explicit: Unpickler_traverse()
# must declare memo before the preceding Py_VISIT executable statements.
if python3 - "$REPO_ROOT/cpython/Modules/_pickle.c" <<'PY'
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
start = source.index("Unpickler_traverse(UnpicklerObject *self, visitproc visit, void *arg)")
end = source.index("\n}\n\nstatic int\nUnpickler_clear", start) + 2
function = source[start:end]
if " PyObject **memo;" not in function:
raise SystemExit(1)
if " PyObject **memo = self->memo;" in function:
raise SystemExit(1)
if function.count(" memo = self->memo;") != 1:
raise SystemExit(1)
PY
then
pass "_pickle Unpickler_traverse uses C89 declaration-order-safe memo local"
else
bad "_pickle Unpickler_traverse uses C89 declaration-order-safe memo local"
fi
# The next _pickle.c VC6/C89 slice is PicklerMemoProxy.copy(): its loop counter
# must be declared before executable statements and assigned in the for init.
if python3 - "$REPO_ROOT/cpython/Modules/_pickle.c" <<'PY'
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
start = source.index("_pickle_PicklerMemoProxy_copy_impl(PicklerMemoProxyObject *self)")
end = source.index("\n}\n\n/*[clinic input]", start) + 2
function = source[start:end]
if " size_t i;" not in function:
raise SystemExit(1)
if "for (size_t i = 0;" in function:
raise SystemExit(1)
if function.count("for (i = 0;") != 1:
raise SystemExit(1)
PY
then
pass "_pickle PicklerMemoProxy.copy uses C89 declaration-order-safe loop counter"
else
bad "_pickle PicklerMemoProxy.copy uses C89 declaration-order-safe loop counter"
fi
# Keep the current _pickle.c frontier explicit: load_readonly_buffer() must
# hoist its locals before the initial executable stack-underflow check.
if python3 - "$REPO_ROOT/cpython/Modules/_pickle.c" <<'PY'
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
start = source.index("load_readonly_buffer(UnpicklerObject *self)")
end = source.index("\n}\n\nstatic int\nload_unicode", start) + 2
function = source[start:end]
if " Py_ssize_t len;" not in function:
raise SystemExit(1)
if " PyObject *obj;" not in function:
raise SystemExit(1)
if " PyObject *view;" not in function:
raise SystemExit(1)
if " Py_ssize_t len = Py_SIZE(self->stack);" in function:
raise SystemExit(1)
if " PyObject *obj = self->stack->data[len - 1];" in function:
raise SystemExit(1)
if " PyObject *view = PyMemoryView_FromObject(obj);" in function:
raise SystemExit(1)
if function.count(" len = Py_SIZE(self->stack);") != 1:
raise SystemExit(1)
if function.count(" obj = self->stack->data[len - 1];") != 1:
raise SystemExit(1)
if function.count(" view = PyMemoryView_FromObject(obj);") != 1:
raise SystemExit(1)
PY
then
pass "_pickle load_readonly_buffer uses C89 declaration-order-safe locals"
else
bad "_pickle load_readonly_buffer uses C89 declaration-order-safe locals"
fi
# Keep the current _pickle.c frontier explicit: load_next_buffer() must hoist
# its locals before the initial executable NULL check.
if python3 - "$REPO_ROOT/cpython/Modules/_pickle.c" <<'PY'
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
start = source.index("load_next_buffer(UnpicklerObject *self)")
end = source.index("\n}\n\nstatic int\nload_readonly_buffer", start) + 2
function = source[start:end]
if " PickleState *st;" not in function:
raise SystemExit(1)
if " PyObject *buf;" not in function:
raise SystemExit(1)
if " PickleState *st = _Pickle_GetGlobalState();" in function:
raise SystemExit(1)
if " PyObject *buf = PyIter_Next(self->buffers);" in function:
raise SystemExit(1)
if function.count(" st = _Pickle_GetGlobalState();") != 2:
raise SystemExit(1)
if " buf = PyIter_Next(self->buffers);" not in function:
raise SystemExit(1)
PY
then
pass "_pickle load_next_buffer uses C89 declaration-order-safe locals"
else
bad "_pickle load_next_buffer uses C89 declaration-order-safe locals"
fi
# Keep the current _pickle.c VC6/C89 frontier explicit: Unpickler_set_memo()
# must hoist its loop counter before both executable loop sites.
if python3 - "$REPO_ROOT/cpython/Modules/_pickle.c" <<'PY'
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
start = source.index("Unpickler_set_memo(UnpicklerObject *self")
end = source.index("\n}\n\nstatic PyObject *\nUnpickler_get_persload", start) + 2
function = source[start:end]
if " size_t i;" not in function:
raise SystemExit(1)
if "for (size_t i = new_memo_size - 1;" in function:
raise SystemExit(1)
if "for (size_t i = 0;" in function:
raise SystemExit(1)
if function.count("for (i = 0;") != 1:
raise SystemExit(1)
if function.count("for (i = new_memo_size; i-- > 0;)") != 1:
raise SystemExit(1)
PY
then
pass "_pickle Unpickler_set_memo uses C89 declaration-order-safe loop counter"
else
bad "_pickle Unpickler_set_memo uses C89 declaration-order-safe loop counter"
fi
# The active _pickle.c frontier must not depend on SIZE_MAX, which VC6's
# standard headers do not provide. The cleanup loop must retain the same
# descending index sequence using a C89-safe post-decrement condition.
if python3 - "$REPO_ROOT/cpython/Modules/_pickle.c" <<'PY'
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
start = source.index("Unpickler_set_memo(UnpicklerObject *self")
end = source.index("\n}\n\nstatic PyObject *\nUnpickler_get_persload", start) + 2
function = source[start:end]
if "SIZE_MAX" in function:
raise SystemExit(1)
if "for (i--; i != 0; i--)" in function:
raise SystemExit(1)
if "for (i = new_memo_size; i-- > 0;)" not in function:
raise SystemExit(1)
PY
then
pass "_pickle Unpickler_set_memo cleanup avoids unavailable SIZE_MAX"
else
bad "_pickle Unpickler_set_memo cleanup avoids unavailable SIZE_MAX"
fi
# Keep the current _sha3 VC6/C89 frontier explicit: SHA3_dealloc()
# must hoist tp before the preceding executable cleanup statements.
if python3 - "$REPO_ROOT/cpython/Modules/_sha3/sha3module.c" <<'PY'
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
start = source.index("SHA3_dealloc(SHA3object *self)")
end = source.index("\n}\n", start) + 2
function = source[start:end]
if " PyTypeObject *tp;" not in function:
raise SystemExit(1)
if " PyTypeObject *tp = Py_TYPE(self);" in function:
raise SystemExit(1)
if function.count(" tp = Py_TYPE(self);") != 1:
raise SystemExit(1)
sequence = (
"SHA3_dealloc(SHA3object *self) { PyTypeObject *tp; "
"if (self->lock) { PyThread_free_lock(self->lock); } "
"tp = Py_TYPE(self); PyObject_Free(self); Py_DECREF(tp); }"
)
if sequence not in " ".join(function.split()):
raise SystemExit(1)
PY
then
pass "_sha3 SHA3_dealloc uses C89 declaration-order-safe tp local"
else
bad "_sha3 SHA3_dealloc uses C89 declaration-order-safe tp local"
fi
# Keep the current _randommodule.c VC6/C89 frontier explicit: _random_exec()
# must hoist locals while preserving the allocation/error/refcount sequence.
if python3 - "$REPO_ROOT/cpython/Modules/_randommodule.c" <<'PY'
import re
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
start = source.index("_random_exec(PyObject *module)\n{")
end = source.index("\n}\n", start) + 2
function = source[start:end]
function = re.sub(r"/\\*.*?\\*/", "", function, flags=re.S)
function = " ".join(function.split())
if not function.startswith(
"_random_exec(PyObject *module) { "
"_randomstate *state = get_random_state(module); "
"PyObject *longval; PyObject *longtype; "
"state->Random_Type = PyType_FromModuleAndSpec("
):
raise SystemExit(1)
if "PyObject *longval =" in function or "PyObject *longtype =" in function:
raise SystemExit(1)
if function.count("longval = PyLong_FromLong(0);") != 1:
raise SystemExit(1)
if function.count("longtype = PyObject_Type(longval);") != 1:
raise SystemExit(1)
sequence = (
"if (PyModule_AddType(module, (PyTypeObject *)state->Random_Type) < 0) { "
"return -1; } "
"longval = PyLong_FromLong(0); "
"if (longval == NULL) { return -1; } "
"longtype = PyObject_Type(longval); "
"Py_DECREF(longval); "
"if (longtype == NULL) { return -1; } "
'state->Long___abs__ = PyObject_GetAttrString(longtype, "__abs__"); '
"Py_DECREF(longtype); "
"if (state->Long___abs__ == NULL) { return -1; } "
"return 0; }"
)
if sequence not in function:
raise SystemExit(1)
PY
then
pass "_random_exec uses C89 locals and preserves initialization order"
else
bad "_random_exec uses C89 locals and preserves initialization order"
fi
# Keep the current _sha3 frontier explicit: its type and module definitions must
# use positional initializers because VC6 has no C99 designated initializers.
if python3 - "$REPO_ROOT/cpython/Modules/_sha3/sha3module.c" <<'PY'
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
start = source.index("#define SHA3_TYPE_SPEC")
end = source.index("\n\nPyDoc_STRVAR(sha3_224__doc__", start)
macro = source[start:end]
expected_macro = '''#define SHA3_TYPE_SPEC(type_spec_obj, type_name, type_slots) \\
static PyType_Spec type_spec_obj = { \\
"_sha3." type_name, \\
sizeof(SHA3object), \\
0, \\
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_IMMUTABLETYPE, \\
type_slots \\
}'''
if macro != expected_macro:
raise SystemExit(1)
start = source.index("static struct PyModuleDef _sha3module = {")
end = source.index("\n};", start) + 3
module = source[start:end]
expected_module = '''static struct PyModuleDef _sha3module = {
PyModuleDef_HEAD_INIT,
"_sha3",
NULL,
sizeof(SHA3State),
NULL,
_sha3_slots,
_sha3_traverse,
_sha3_clear,
_sha3_free,
};'''
if module != expected_module:
raise SystemExit(1)
PY
then
pass "_sha3 uses VC6-safe positional type and module initializers"
else
bad "_sha3 uses VC6-safe positional type and module initializers"
fi
# Keep the current _sre frontier explicit: SRE(match) must declare ptr before
# executable statements, including the entrance label.
if python3 - "$REPO_ROOT/cpython/Modules/_sre/sre_lib.h" <<'PY'
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
start = source.index("SRE(match)(SRE_STATE* state, const SRE_CODE* pattern, int toplevel)")
end = source.index("\n}\n\n/* need to reset capturing groups", start) + 2
function = source[start:end]
if " const SRE_CHAR *ptr;" not in function:
raise SystemExit(1)
if " const SRE_CHAR *ptr = (SRE_CHAR *)state->ptr;" in function:
raise SystemExit(1)
if function.count(" ptr = (SRE_CHAR *)state->ptr;") != 1:
raise SystemExit(1)
PY
then
pass "_sre SRE(match) uses C89 declaration-order-safe ptr local"
else
bad "_sre SRE(match) uses C89 declaration-order-safe ptr local"
fi
# The active _sre VC6/C89 frontier must declare repeat_of_tail before the
# executable DO_JUMP statement that assigns it.
if python3 - "$REPO_ROOT/cpython/Modules/_sre/sre_lib.h" <<'PY'
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
start = source.index("SRE(match)(SRE_STATE* state, const SRE_CODE* pattern, int toplevel)")
end = source.index("\n}\n\n/* need to reset capturing groups", start) + 2
function = source[start:end]
if " SRE_REPEAT *repeat_of_tail;" not in function:
raise SystemExit(1)
if " SRE_REPEAT *repeat_of_tail = state->repeat;" in function:
raise SystemExit(1)
if function.count(" repeat_of_tail = state->repeat;") != 1:
raise SystemExit(1)
PY
then
pass "_sre MIN_UNTIL uses C89 declaration-order-safe repeat local"
else
bad "_sre MIN_UNTIL uses C89 declaration-order-safe repeat local"
fi
# The active _sre VC6/C89 frontier must declare rc before the preceding
# VTRACE executable statement in the GROUPREF_EXISTS validation case.
if python3 - "$REPO_ROOT/cpython/Modules/_sre/sre.c" <<'PY'
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
start = source.index("_validate_inner(SRE_CODE *code, SRE_CODE *end, Py_ssize_t groups)")
end = source.index("\n}\n\nstatic int\n_validate_outer", start) + 2
function = source[start:end]
if " int rc;" not in function:
raise SystemExit(1)
if " int rc = _validate_inner(code+1, code+skip-1, groups);" in function:
raise SystemExit(1)
if function.count(" rc = _validate_inner(code+1, code+skip-1, groups);") != 1:
raise SystemExit(1)
PY
then
pass "_sre GROUPREF_EXISTS uses C89 declaration-order-safe rc local"
else
bad "_sre GROUPREF_EXISTS uses C89 declaration-order-safe rc local"
fi
# The active _sre designated-initializer frontier must use C89-compatible
# positional PyType_Spec initializers in declared field order.
if python3 - "$REPO_ROOT/cpython/Modules/_sre/sre.c" <<'PY'
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
expected = {
"pattern_spec": '''static PyType_Spec pattern_spec = {
"re.Pattern",
sizeof(PatternObject),
sizeof(SRE_CODE),
(Py_TPFLAGS_DEFAULT | Py_TPFLAGS_IMMUTABLETYPE |
Py_TPFLAGS_DISALLOW_INSTANTIATION | Py_TPFLAGS_HAVE_GC),
pattern_slots,
};''',
"match_spec": '''static PyType_Spec match_spec = {
"re.Match",
sizeof(MatchObject),
sizeof(Py_ssize_t),
(Py_TPFLAGS_DEFAULT | Py_TPFLAGS_IMMUTABLETYPE |
Py_TPFLAGS_DISALLOW_INSTANTIATION | Py_TPFLAGS_HAVE_GC),
match_slots,
};''',
"scanner_spec": '''static PyType_Spec scanner_spec = {
"_" SRE_MODULE ".SRE_Scanner",
sizeof(ScannerObject),
0,
(Py_TPFLAGS_DEFAULT | Py_TPFLAGS_IMMUTABLETYPE |
Py_TPFLAGS_DISALLOW_INSTANTIATION | Py_TPFLAGS_HAVE_GC),
scanner_slots,
};''',
}
for name, block in expected.items():
if block not in source:
raise SystemExit(name)
if ".name = \"re.Pattern\"" in source or ".name = \"re.Match\"" in source:
raise SystemExit("designated initializer remains")
PY
then
pass "_sre PyType_Spec definitions use C89-compatible positional initializers"
else
bad "_sre PyType_Spec definitions use C89-compatible positional initializers"
fi
# The active _sre module definition must use C89-compatible positional
# initializers in the declared PyModuleDef field order.
if python3 - "$REPO_ROOT/cpython/Modules/_sre/sre.c" <<'PY'
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
expected = '''static struct PyModuleDef sremodule = {
PyModuleDef_HEAD_INIT,
"_" SRE_MODULE,
NULL,
sizeof(_sremodulestate),
_functions,
sre_slots,
sre_traverse,
sre_clear,
sre_free,
};'''
if expected not in source:
raise SystemExit("sremodule positional initializer mismatch")
body = source[source.index("static struct PyModuleDef sremodule = {"):]
body = body[:body.index("\n};")]
if ".m_" in body:
raise SystemExit("sremodule designated initializer remains")
PY
then
pass "_sre sremodule uses C89-compatible positional initializers"
else
bad "_sre sremodule uses C89-compatible positional initializers"
fi
# The active _sre VC6/C89 frontier must declare ADD_ULONG_CONSTANT's result
# before the executable NULL check shared by both macro call sites.
if python3 - "$REPO_ROOT/cpython/Modules/_sre/sre.c" <<'PY'
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
start = source.index("#define ADD_ULONG_CONSTANT(")
end = source.index("\n\n", start)
actual = [
line.rstrip().removesuffix("\\").strip()
for line in source[start:end].splitlines()
]
expected = [
"#define ADD_ULONG_CONSTANT(module, name, value)",
"do {",
"PyObject *o = PyLong_FromUnsignedLong(value);",
"int res;",
"if (!o)",
"goto error;",
"res = PyModule_AddObjectRef(module, name, o);",
"Py_DECREF(o);",
"if (res < 0) {",
"goto error;",
"}",
"} while (0)",
]
if actual != expected:
raise SystemExit("ADD_ULONG_CONSTANT declaration/order mismatch")
PY
then
pass "_sre ADD_ULONG_CONSTANT uses C89 declaration-order-safe res local"
else
bad "_sre ADD_ULONG_CONSTANT uses C89 declaration-order-safe res local"
fi
# Keep the active arraymodule.c VC6/C89 frontier explicit: u_setitem()
# must declare len and w before executable statements.
if python3 - "$REPO_ROOT/cpython/Modules/arraymodule.c" <<'PY'
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
start = source.index("u_setitem(arrayobject *ap, Py_ssize_t i, PyObject *v)")
end = source.index("\n}\n\n\nstatic PyObject *\nh_getitem", start) + 2
function = source[start:end]
if " Py_ssize_t len;" not in function:
raise SystemExit(1)
if " wchar_t w;" not in function:
raise SystemExit(1)
if " Py_ssize_t len = PyUnicode_AsWideChar(u, NULL, 0);" in function:
raise SystemExit(1)
if " wchar_t w;\n len =" in function:
raise SystemExit(1)
if function.count(" len = PyUnicode_AsWideChar(u, NULL, 0);") != 1:
raise SystemExit(1)
if " len = PyUnicode_AsWideChar(u, &w, 1);" not in function:
raise SystemExit(1)
PY
then
pass "arraymodule u_setitem uses C89 declaration-order-safe len and w locals"
else
bad "arraymodule u_setitem uses C89 declaration-order-safe len and w locals"
fi
# Keep the next arraymodule.c VC6/C89 frontier explicit: array_repeat()
# must declare locals before executable statements and preserve assignment order.
if python3 - "$REPO_ROOT/cpython/Modules/arraymodule.c" <<'PY'
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
start = source.index("array_repeat(arrayobject *a, Py_ssize_t n)\n{")
end = source.index("\n}\n\nstatic int\narray_del_slice", start) + 2
function = source[start:end]
for declaration in (" Py_ssize_t array_length;", " Py_ssize_t size;", " arrayobject *np;", " Py_ssize_t oldbytes;", " Py_ssize_t newbytes;"):
if declaration not in function:
raise SystemExit("array_repeat declaration missing: " + declaration)
for declaration in (" const Py_ssize_t array_length =", " Py_ssize_t size =", " arrayobject* np =", " const Py_ssize_t oldbytes =", " const Py_ssize_t newbytes ="):
if declaration in function:
raise SystemExit("array_repeat retains mixed declaration: " + declaration)
expected = (
" array_length = Py_SIZE(a);\n"
" if ((array_length != 0) && (n > PY_SSIZE_T_MAX / array_length))"
)
if expected not in function:
raise SystemExit("array_repeat array_length assignment sequence changed")
if " size = array_length * n;" not in function:
raise SystemExit("array_repeat size assignment missing")
if " np = (arrayobject *) newarrayobject" not in function:
raise SystemExit("array_repeat np assignment missing")
if " oldbytes = array_length * a->ob_descr->itemsize;" not in function:
raise SystemExit("array_repeat oldbytes assignment missing")
if " newbytes = oldbytes * n;" not in function:
raise SystemExit("array_repeat newbytes assignment missing")
PY
then
pass "arraymodule array_repeat uses C89 declaration-order-safe locals"
else
bad "arraymodule array_repeat uses C89 declaration-order-safe locals"
fi
# The current arraymodule.c VC6/C89 frontier must declare array_inplace_repeat
# locals before the executable conditional block.
if python3 - "$REPO_ROOT/cpython/Modules/arraymodule.c" <<'PY'
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
start = source.index("array_inplace_repeat(arrayobject *self, Py_ssize_t n)\n{")
end = source.index("\n}\n\n\nstatic PyObject *\nins(", start) + 2
function = source[start:end]
opening = function[:function.index(" if (array_size > 0")]
if " Py_ssize_t size;\n" not in opening:
raise SystemExit("array_inplace_repeat size declaration missing from opening group")
if " Py_ssize_t size = array_size * self->ob_descr->itemsize;" in function:
raise SystemExit("array_inplace_repeat retains mixed declaration")
if function.count(" size = array_size * self->ob_descr->itemsize;") != 1:
raise SystemExit("array_inplace_repeat size assignment missing")
PY
then
pass "arraymodule array_inplace_repeat uses C89 declaration-order-safe size local"
else
bad "arraymodule array_inplace_repeat uses C89 declaration-order-safe size local"
fi
# The next arraymodule.c VC6/C89 frontier must declare the index loop counter
# before the preceding executable start/stop normalization statements.
if python3 - "$REPO_ROOT/cpython/Modules/arraymodule.c" <<'PY'
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
start = source.index("array_array_index_impl(arrayobject *self, PyObject *v, Py_ssize_t start,")
end = source.index("\n}\n\nstatic int\narray_contains", start) + 2
function = source[start:end]
opening = function[:function.index(" if (start < 0)")]
if " Py_ssize_t i;\n" not in opening:
raise SystemExit("array_array_index_impl loop counter declaration missing from opening group")
if "for (Py_ssize_t i = start;" in function:
raise SystemExit("array_array_index_impl retains C99 loop declaration")
if function.count(" for (i = start; i < stop && i < Py_SIZE(self); i++) {") != 1:
raise SystemExit("array_array_index_impl loop assignment sequence changed")
PY
then
pass "arraymodule array_array_index_impl uses C89 declaration-order-safe loop counter"
else
bad "arraymodule array_array_index_impl uses C89 declaration-order-safe loop counter"
fi
# The active arraymodule.c VC6/C89 frontier must hoist array_state declarations
# while preserving the guarded lookup points in fromfile() and tofile().
if python3 - "$REPO_ROOT/cpython/Modules/arraymodule.c" <<'PY'
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
checks = {
"fromfile": (
"array_array_fromfile_impl(arrayobject *self, PyTypeObject *cls, PyObject *f,",
"\n}\n\n/*[clinic input]\narray.array.tofile",
" int not_enough_bytes;\n",
" nbytes = n * itemsize;\n",
),
"tofile": (
"array_array_tofile_impl(arrayobject *self, PyTypeObject *cls, PyObject *f)",
"\n}\n\n/*[clinic input]\narray.array.fromlist",
" Py_ssize_t i;\n",
" for (i = 0; i < nblocks; i++) {\n",
),
}
for name, (signature, end_marker, declaration_anchor, after_lookup) in checks.items():
start = source.index(signature)
end = source.index(end_marker, start) + 2
function = source[start:end]
opening = function[:function.index(" if ")]
if " array_state *state;\n" not in opening:
raise SystemExit(name + ": array_state declaration missing from opening group")
if " array_state *state = get_array_state_by_class(cls);" in function:
raise SystemExit(name + ": array_state declaration retains initializer")
if function.count(" state = get_array_state_by_class(cls);\n") != 1:
raise SystemExit(name + ": state assignment missing or duplicated")
if " state = get_array_state_by_class(cls);\n assert(state != NULL);\n" not in function:
raise SystemExit(name + ": state lookup/assert sequence changed")
if function.index(declaration_anchor) >= function.index(" if "):
raise SystemExit(name + ": declaration anchor moved after executable code")
lookup = function.index(" state = get_array_state_by_class(cls);")
if function.index(after_lookup, lookup) <= lookup:
raise SystemExit(name + ": post-lookup sequence changed")
PY
then
pass "arraymodule fromfile/tofile use C89 declaration-order-safe state locals"
else
bad "arraymodule fromfile/tofile use C89 declaration-order-safe state locals"
fi
# The active arraymodule.c VC6/C89 frontier must hoist fromunicode() locals
# without moving either initializer past its original guard/decrement point.
if python3 - "$REPO_ROOT/cpython/Modules/arraymodule.c" <<'PY'
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
signature = "array_array_fromunicode_impl(arrayobject *self, PyObject *ustr)"
start = source.index(signature)
end = source.index("\n}\n\n/*[clinic input]\narray.array.tounicode", start) + 2
function = source[start:end]
opening = function[:function.index(" if ")]
if " Py_ssize_t ustr_length;\n" not in opening:
raise SystemExit("fromunicode: ustr_length declaration missing from opening group")
if " Py_ssize_t old_size;\n" not in function[:function.index(" ustr_length--")]:
raise SystemExit("fromunicode: old_size declaration missing before decrement")
if " Py_ssize_t ustr_length = PyUnicode_AsWideChar(ustr, NULL, 0);" in function:
raise SystemExit("fromunicode: ustr_length declaration retains initializer")
if " Py_ssize_t old_size = Py_SIZE(self);" in function:
raise SystemExit("fromunicode: old_size declaration retains initializer")
if " ustr_length = PyUnicode_AsWideChar(ustr, NULL, 0);\n assert(ustr_length > 0);" not in function:
raise SystemExit("fromunicode: Unicode conversion assignment/assert sequence changed")
if " old_size = Py_SIZE(self);\n if (array_resize(self, old_size + ustr_length) == -1)" not in function:
raise SystemExit("fromunicode: old_size assignment/resize sequence changed")
PY
then
pass "arraymodule fromunicode uses C89 declaration-order-safe locals"
else
bad "arraymodule fromunicode uses C89 declaration-order-safe locals"
fi
# Keep the current arraymodule.c VC6/C89 frontier explicit: array_richcompare()
# must declare cmp before the executable fast-path statements.
if python3 - "$REPO_ROOT/cpython/Modules/arraymodule.c" <<'PY'
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
start = source.index("array_richcompare(PyObject *v, PyObject *w, int op)")
end = source.index("\n}\n\nstatic PyMethodDef", start) + 2
function = source[start:end]
opening = function[:function.index(" if (!array_Check")]
if " int cmp;\n" not in opening:
raise SystemExit("array_richcompare cmp declaration missing from opening group")
fast_path = function[function.index(" if (va->ob_descr"):function.index("\n }\n\n\n /* Search", function.index(" if (va->ob_descr"))]
if " int cmp;\n" in fast_path:
raise SystemExit("array_richcompare cmp declaration remains after executable statements")
size_path = function[function.index(" compare_sizes: ;"):function.index("\n }\n\n /* We have an item", function.index(" compare_sizes: ;"))]
if " int cmp;\n" in size_path:
raise SystemExit("array_richcompare compare_sizes retains duplicate cmp declaration")
if size_path.count(" case Py_LT: cmp = vs < ws; break;") != 1:
raise SystemExit("array_richcompare compare_sizes cmp assignment sequence changed")
if fast_path.count(" case Py_LT: cmp = result < 0; break;") != 1:
raise SystemExit("array_richcompare cmp assignment sequence changed")
PY
then
pass "arraymodule array_richcompare uses C89 declaration-order-safe cmp local"
else
bad "arraymodule array_richcompare uses C89 declaration-order-safe cmp local"
fi
# The next arraymodule.c VC6/C89 frontier must reuse the opening res local;
# VC6 rejects the fast-path declaration after executable statements.
if python3 - "$REPO_ROOT/cpython/Modules/arraymodule.c" <<'PY'
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
start = source.index("array_richcompare(PyObject *v, PyObject *w, int op)\n{")
end = source.index("\n}\n\nstatic Py_ssize_t\narray_length(", start) + 2
function = source[start:end]
opening = function[:function.index(" if (!array_Check")]
if opening.count(" PyObject *res;\n") != 1:
raise SystemExit("array_richcompare opening res declaration missing")
start = function.index(" if (va->ob_descr")
fast_path = function[start:function.index("\n }\n\n\n /* Search", start)]
if "PyObject *res" in fast_path:
raise SystemExit("array_richcompare fast path retains res declaration")
if fast_path.count(
" }\n"
" res = cmp ? Py_True : Py_False;\n"
" Py_INCREF(res);\n"
" return res;"
) != 1:
raise SystemExit("array_richcompare fast-path res assignment sequence changed")
PY
then
pass "arraymodule array_richcompare reuses C89-safe res local"
else
bad "arraymodule array_richcompare reuses C89-safe res local"
fi
# The next arraymodule.c VC6/C89 frontier must hoist the compare-size locals
# before the label and executable statements in the shared-size path.
if python3 - "$REPO_ROOT/cpython/Modules/arraymodule.c" <<'PY'
import re
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
start = source.index("array_richcompare(PyObject *v, PyObject *w, int op)\n{")
end = source.index("\n}\n\nstatic Py_ssize_t\narray_length(", start) + 2
function = source[start:end]
opening = function[:function.index(" if (!array_Check")]
if re.search(r" Py_ssize_t [^;]*\bvs\b[^;]*\bws\b[^;]*;\n", opening) is None:
raise SystemExit("array_richcompare size locals missing from opening group")
if " Py_ssize_t vs = Py_SIZE(va);" in function:
raise SystemExit("array_richcompare retains post-label vs declaration")
if " Py_ssize_t ws = Py_SIZE(wa);" in function:
raise SystemExit("array_richcompare retains post-label ws declaration")
if function.count(" vs = Py_SIZE(va);\n ws = Py_SIZE(wa);") != 1:
raise SystemExit("array_richcompare size assignments changed")
PY
then
pass "arraymodule array_richcompare uses C89 declaration-order-safe size locals"
else
bad "arraymodule array_richcompare uses C89 declaration-order-safe size locals"
fi
# Keep the current arraymodule.c VC6/C89 frontier explicit: getarrayitem()
# must declare ap before the debug-only executable assertion.
if python3 - "$REPO_ROOT/cpython/Modules/arraymodule.c" <<'PY'
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
start = source.index("getarrayitem(PyObject *op, Py_ssize_t i)")
end = source.index("\n}\n\nstatic int\nins1", start) + 2
function = source[start:end]
if "{\n arrayobject *ap;\n#ifndef NDEBUG" not in function:
raise SystemExit("getarrayitem ap declaration is not before debug block")
if function.count(" arrayobject *ap;") != 1:
raise SystemExit("getarrayitem ap declaration is duplicated")
if " ap = (arrayobject *)op;" not in function:
raise SystemExit("getarrayitem ap assignment missing")
if " arrayobject *ap = (arrayobject *)op;" in function:
raise SystemExit("getarrayitem retains declaration initializer")
PY
then
pass "arraymodule getarrayitem uses C89 declaration-order-safe ap local"
else
bad "arraymodule getarrayitem uses C89 declaration-order-safe ap local"
fi
# Keep the active _struct.c VC6 frontier explicit: MSVC6 has no C99 _Bool,
# so the module must provide a one-byte compatibility typedef locally.
if python3 - "$REPO_ROOT/cpython/Modules/_struct.c" <<'PY'
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
expected = '''#if defined(_MSC_VER) && _MSC_VER < 1300
/* MSVC6 has no C99 _Bool; preserve the one-byte layout used by struct. */
typedef unsigned char _Bool;
#endif'''
if expected not in source:
raise SystemExit("_struct VC6 _Bool compatibility typedef missing")
PY
then
pass "_struct provides a guarded one-byte VC6 _Bool compatibility typedef"
else
bad "_struct provides a guarded one-byte VC6 _Bool compatibility typedef"
fi
# The active _struct.c VC6 frontier must spell its 64-bit integer types through
# CPython's PY_LONG_LONG abstraction because VC6 rejects the long long keyword.
if python3 - "$REPO_ROOT/cpython/Modules/_struct.c" <<'PY'
import re
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
code = re.sub(r"/\*.*?\*/", "", source, flags=re.S)
doc_start = code.index("PyDoc_STRVAR(module_doc,")
doc_end = code.index('\\n");', doc_start) + 3
code = code[:doc_start] + code[doc_end:]
if re.search(r"\b(?:unsigned\s+)?long\s+long\b", code):
raise SystemExit("_struct long long keyword remains in code")
if code.count("PY_LONG_LONG") < 20:
raise SystemExit("_struct PY_LONG_LONG replacements are incomplete")
if code.count("unsigned PY_LONG_LONG") < 7:
raise SystemExit("_struct unsigned PY_LONG_LONG replacements are incomplete")
PY
then
pass "_struct routes signed and unsigned 64-bit types through PY_LONG_LONG"
else
bad "_struct routes signed and unsigned 64-bit types through PY_LONG_LONG"
fi
# Keep the active _stat.c VC6/C89 frontier explicit: stat_exec() must place
# its constant table and loop counter before the initial executable macro call.
if python3 - "$REPO_ROOT/cpython/Modules/_stat.c" <<'PY'
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
start = source.index("stat_exec(PyObject *module)\n{")
end = source.index("\n}\n\n\nstatic PyModuleDef_Slot stat_slots", start) + 2
function = source[start:end]
first_statement = function.index(" ADD_INT_MACRO(module, S_IFDIR);")
if function.index(" const char* st_constants[] = {") >= first_statement:
raise SystemExit(1)
if function.index(" };", function.index("st_constants[]")) >= first_statement:
raise SystemExit(1)
if function.index(" int i;") >= first_statement:
raise SystemExit(1)
if "for (int i" in function:
raise SystemExit(1)
if "for (i = 0; i < (int)Py_ARRAY_LENGTH(st_constants); i++)" not in function:
raise SystemExit(1)
PY
then
pass "_stat stat_exec uses C89 declaration-order-safe constants and loop counter"
else
bad "_stat stat_exec uses C89 declaration-order-safe constants and loop counter"
fi
# The active _stat.c frontier must provide guarded Win9x/VC6 fallbacks for
# constants absent from the VC6 Windows headers, without overriding headers
# that already define them.
if python3 - "$REPO_ROOT/cpython/Modules/_stat.c" <<'PY'
import re
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
start = source.index("#ifdef MS_WINDOWS")
end = source.index("#endif /* MS_WINDOWS */", start)
block = source[start:end]
expected = {
"FILE_ATTRIBUTE_DEVICE": "0x0040",
"FILE_ATTRIBUTE_VIRTUAL": "0x10000",
"IO_REPARSE_TAG_SYMLINK": "0xA000000CL",
}
for name, value in expected.items():
pattern = rf"#ifndef {name}\s+#\s*define {name}\s+{re.escape(value)}\s+#endif"
if re.search(pattern, block) is None:
raise SystemExit(name)
PY
then
pass "_stat provides guarded VC6/Win9x file-attribute fallbacks"
else
bad "_stat provides guarded VC6/Win9x file-attribute fallbacks"
fi
# The active _stat.c frontier must use C89-compatible positional initializers
# for statmodule because VC6 has no C99 designated-initializer support.
if python3 - "$REPO_ROOT/cpython/Modules/_stat.c" <<'PY'
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
expected = '''static struct PyModuleDef statmodule = {
PyModuleDef_HEAD_INIT,
"_stat",
module_doc,
0,
stat_methods,
stat_slots,
NULL,
NULL,
NULL,
};'''
if expected not in source:
raise SystemExit("statmodule positional initializer mismatch")
body = source[source.index("static struct PyModuleDef statmodule = {"):]
body = body[:body.index("\n};")]
if ".m_" in body:
raise SystemExit("statmodule designated initializer remains")
PY
then
pass "_stat statmodule uses C89-compatible positional initializers"
else
bad "_stat statmodule uses C89-compatible positional initializers"
fi
# The active _struct.c module definition must use C89-compatible positional
# initializers in the declared PyModuleDef field order.
if python3 - "$REPO_ROOT/cpython/Modules/_struct.c" <<'PY'
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
expected = '''static struct PyModuleDef _structmodule = {
PyModuleDef_HEAD_INIT,
"_struct",
module_doc,
sizeof(_structmodulestate),
module_functions,
_structmodule_slots,
_structmodule_traverse,
_structmodule_clear,
_structmodule_free,
};'''
if expected not in source:
raise SystemExit("_structmodule positional initializer mismatch")
body = source[source.index("static struct PyModuleDef _structmodule = {"):]
body = body[:body.index("\n};")]
if ".m_" in body:
raise SystemExit("_structmodule designated initializer remains")
PY
then
pass "_struct _structmodule uses C89-compatible positional initializers"
else
bad "_struct _structmodule uses C89-compatible positional initializers"
fi
# The active _struct.c frontier must hoist declarations before executable
# statements for VC6/C89 in s_new(), s_dealloc(), and s_pack().
if python3 - "$REPO_ROOT/cpython/Modules/_struct.c" <<'PY'
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
checks = {
"s_new": ("s_new(PyTypeObject *type, PyObject *args, PyObject *kwds)", "\n}\n\n/*[clinic input]", [" allocfunc alloc_func;"], [" alloc_func = PyType_GetSlot(type, Py_tp_alloc);"]),
"s_dealloc": ("s_dealloc(PyStructObject *s)", "\n}\n\nstatic PyObject *\ns_unpack_internal", [" PyTypeObject *tp;", " freefunc free_func;"], [" tp = Py_TYPE(s);", " free_func = PyType_GetSlot(Py_TYPE(s), Py_tp_free);"]),
"s_pack": ("s_pack(PyObject *self, PyObject *const *args, Py_ssize_t nargs)", "\n}\n\nPyDoc_STRVAR(s_pack_into__doc__", [" _PyBytesWriter writer;"], [" _PyBytesWriter_Init(&writer);"]),
}
for name, (signature, end_marker, declarations, assignments) in checks.items():
start = source.index(signature)
end = source.index(end_marker, start) + 2
function = source[start:end]
for declaration in declarations:
if declaration not in function or declaration[:-1] + " =" in function:
raise SystemExit(f"{name}: declaration not hoisted")
for assignment in assignments:
if assignment not in function:
raise SystemExit(f"{name}: assignment missing")
PY
then
pass "_struct uses C89 declaration-order-safe locals at the active frontier"
else
bad "_struct uses C89 declaration-order-safe locals at the active frontier"
fi
# Keep the current arraymodule.c VC6 frontier explicit: the q/QQ handlers
# must route 64-bit C types through CPython's VC6-compatible spelling.
if python3 - "$REPO_ROOT/cpython/Modules/arraymodule.c" <<'PY'
import re
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
start = source.index("q_getitem(arrayobject *ap, Py_ssize_t i)")
end = source.index("\n}\n\nstatic PyObject *\nf_getitem", start) + 2
family = source[start:end]
if re.search(r"(?<![A-Za-z_])unsigned long long(?![A-Za-z_])", family):
raise SystemExit(1)
if re.search(r"(?<![A-Za-z_])long long(?![A-Za-z_])", family):
raise SystemExit(1)
for spelling in ("PY_LONG_LONG", "unsigned PY_LONG_LONG"):
if spelling not in family:
raise SystemExit(1)
if family.count("(PY_LONG_LONG *)ap->ob_item") != 2:
raise SystemExit(1)
if family.count("(unsigned PY_LONG_LONG *)ap->ob_item") != 2:
raise SystemExit(1)
PY
then
pass "arraymodule q/QQ handlers use VC6-compatible 64-bit type spellings"
else
bad "arraymodule q/QQ handlers use VC6-compatible 64-bit type spellings"
fi
# The active arraymodule.c frontier must declare the compare-loop counter in
# the function declaration group because VC6 has no C99 for-loop declarations.
if python3 - "$REPO_ROOT/cpython/Modules/arraymodule.c" <<'PY'
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
start = source.index("#define DEFINE_COMPAREITEMS")
end = source.index("\n\nDEFINE_COMPAREITEMS(b", start)
macro = source[start:end]
if " Py_ssize_t i; \\\n" not in macro:
raise SystemExit("compare macro loop counter declaration missing")
if " for (i = 0; i < length; ++i) \\\n" not in macro:
raise SystemExit("compare macro still uses C99 for initializer")
if "for (Py_ssize_t i =" in macro:
raise SystemExit("compare macro retains C99 for initializer")
PY
then
pass "arraymodule compare macro uses C89 declaration-order-safe loop counter"
else
bad "arraymodule compare macro uses C89 declaration-order-safe loop counter"
fi
# The active arraymodule.c frontier must route the q/QQ compare macro type
# arguments through CPython's VC6-compatible 64-bit spelling.
if python3 - "$REPO_ROOT/cpython/Modules/arraymodule.c" <<'PY'
import re
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
start = source.index("#define DEFINE_COMPAREITEMS")
end = source.index("\n\nDEFINE_COMPAREITEMS(b", start)
macro = source[start:end]
if "DEFINE_COMPAREITEMS(q, PY_LONG_LONG)" not in source:
raise SystemExit("signed q compare macro argument is not VC6-compatible")
if "DEFINE_COMPAREITEMS(QQ, unsigned PY_LONG_LONG)" not in source:
raise SystemExit("unsigned QQ compare macro argument is not VC6-compatible")
if re.search(r"DEFINE_COMPAREITEMS\(q,\s*long\s+long\)", source):
raise SystemExit("signed q compare macro retains long long")
if re.search(r"DEFINE_COMPAREITEMS\(QQ,\s*unsigned\s+long\s+long\)", source):
raise SystemExit("unsigned QQ compare macro retains long long")
PY
then
pass "arraymodule compare macro uses VC6-compatible 64-bit type arguments"
else
bad "arraymodule compare macro uses VC6-compatible 64-bit type arguments"
fi
# Keep the active arraymodule.c frontier explicit: get_native_typecode()
# must use CPython's VC6-compatible 64-bit type spelling, not long long.
if python3 - "$REPO_ROOT/cpython/Modules/arraymodule.c" <<'PY'
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
start = source.index("typecode_to_mformat_code(char typecode)")
end = source.index("\n}\n\n/* Forward declaration. */", start) + 2
function = source[start:end]
if "intsize = sizeof(PY_LONG_LONG);" not in function:
raise SystemExit("get_native_typecode q/QQ width is not VC6-compatible")
if function.count("intsize = sizeof(PY_LONG_LONG);") != 2:
raise SystemExit("get_native_typecode q/QQ width count changed")
if "intsize = sizeof(long long);" in function:
raise SystemExit("get_native_typecode retains sizeof(long long)")
PY
then
pass "arraymodule get_native_typecode uses VC6-compatible 64-bit widths"
else
bad "arraymodule get_native_typecode uses VC6-compatible 64-bit widths"
fi
# Keep the active arraymodule.c frontier explicit: the q/QQ descriptor widths
# must use CPython's VC6-compatible 64-bit type spelling, not long long.
if python3 - "$REPO_ROOT/cpython/Modules/arraymodule.c" <<'PY'
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
start = source.index("static const struct arraydescr descriptors[]")
end = source.index("\n};", start) + 3
descriptors = source[start:end]
if "{'q', sizeof(PY_LONG_LONG)," not in descriptors:
raise SystemExit("q descriptor does not use VC6-compatible 64-bit width")
if "{'Q', sizeof(PY_LONG_LONG)," not in descriptors:
raise SystemExit("Q descriptor does not use VC6-compatible 64-bit width")
if "{'q', sizeof(long long)," in descriptors:
raise SystemExit("q descriptor retains sizeof(long long)")
if "{'Q', sizeof(long long)," in descriptors:
raise SystemExit("Q descriptor retains sizeof(long long)")
PY
then
pass "arraymodule q/QQ descriptors use VC6-compatible 64-bit widths"
else
bad "arraymodule q/QQ descriptors use VC6-compatible 64-bit widths"
fi
# VC6 C2026: each arraytype_doc literal must fit, while concatenation must
# preserve every byte of the original documentation (including newlines).
if python3 - "$REPO_ROOT/cpython/Modules/arraymodule.c" <<'PY'
import ast
import hashlib
import re
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
match = re.search(r'PyDoc_STRVAR\(arraytype_doc,\s*(.*?)\);', source, re.S)
assert match, "arraytype_doc must retain PyDoc_STRVAR behavior"
body = match.group(1).replace("\\\n", "")
literals = re.findall(r'"(?:[^"\\]|\\.)*"', body)
assert literals and not re.sub(r'"(?:[^"\\]|\\.)*"', "", body).strip(), \
"arraytype_doc must consist of adjacent string literals"
chunks = [ast.literal_eval(literal).encode("ascii") for literal in literals]
assert all(len(chunk) <= 2048 for chunk in chunks), \
"arraytype_doc exceeds VC6's 2048-byte individual string literal limit"
assert hashlib.sha256(b"".join(chunks)).hexdigest() == \
"36d98a6261ec374e1e5ed2da654bb1c47286a2658b86c28800af1543c40f6822", \
"arraytype_doc documentation bytes changed"
assert "{Py_tp_doc, (void *)arraytype_doc}" in source, \
"array type must retain its documentation slot"
PY
then
pass "arraymodule arraytype_doc uses VC6-sized literals with unchanged text"
else
bad "arraymodule arraytype_doc uses VC6-sized literals with unchanged text"
fi
# The active arraymodule.c VC6/C89 frontier must hoist arrayiter_next()'s
# debug-only state declaration before executable statements.
if python3 - "$REPO_ROOT/cpython/Modules/arraymodule.c" <<'PY'
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
start = source.index("arrayiter_next(arrayiterobject *it)\n{")
end = source.index("\n}\n\nstatic void\narrayiter_dealloc", start) + 2
function = source[start:end]
opening = function[:function.index(" assert(it != NULL);")]
if " array_state *state;\n" not in opening:
raise SystemExit("arrayiter_next state declaration missing from opening group")
if " array_state *state = find_array_state_by_type(Py_TYPE(it));" in function:
raise SystemExit("arrayiter_next retains mixed declaration")
if function.count(" state = find_array_state_by_type(Py_TYPE(it));") != 1:
raise SystemExit("arrayiter_next state assignment missing")
if "#ifndef NDEBUG\n state = find_array_state_by_type(Py_TYPE(it));\n assert(PyObject_TypeCheck(it, state->ArrayIterType));" not in function:
raise SystemExit("arrayiter_next debug type-check sequence changed")
PY
then
pass "arraymodule arrayiter_next uses C89 declaration-order-safe state local"
else
bad "arraymodule arrayiter_next uses C89 declaration-order-safe state local"
fi
# The active arraymodule.c VC6/C89 frontier must hoist array iterator reduce
# locals before the executable state lookup and assertion.
if python3 - "$REPO_ROOT/cpython/Modules/arraymodule.c" <<'PY'
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
start = source.index("array_arrayiterator___reduce___impl(arrayiterobject *self, PyTypeObject *cls)")
end = source.index("\n}\n\n/*[clinic input]\narray.arrayiterator.__setstate__", start) + 2
function = source[start:end]
opening = function[:function.index(" assert(state != NULL);")]
if " array_state *state;\n" not in opening:
raise SystemExit("array iterator reduce state declaration missing from opening group")
if " PyObject *func;\n" not in opening:
raise SystemExit("array iterator reduce func declaration missing from opening group")
if " array_state *state = get_array_state_by_class(cls);" in function:
raise SystemExit("array iterator reduce retains mixed state declaration")
if " PyObject *func = _PyEval_GetBuiltin(state->str_iter);" in function:
raise SystemExit("array iterator reduce retains mixed func declaration")
if " state = get_array_state_by_class(cls);\n assert(state != NULL);\n func = _PyEval_GetBuiltin(state->str_iter);\n" not in function:
raise SystemExit("array iterator reduce lookup sequence changed")
PY
then
pass "arraymodule array iterator reduce uses C89 declaration-order-safe locals"
else
bad "arraymodule array iterator reduce uses C89 declaration-order-safe locals"
fi
# The active arraymodule.c VC6/C89 frontier must hoist array_modexec()'s
# locals before the ADD_INTERNED executable statements.
if python3 - "$REPO_ROOT/cpython/Modules/arraymodule.c" <<'PY'
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
start = source.index("array_modexec(PyObject *m)\n{")
end = source.index("\n}\n\nstatic PyModuleDef_Slot arrayslots", start) + 2
function = source[start:end]
opening = function[:function.index(" /* Add interned strings */")]
for declaration in (" PyObject *abc_mod;\n", " PyObject *mutablesequence;\n", " PyObject *res;\n"):
if declaration not in opening:
raise SystemExit("array_modexec local missing from opening declaration group: " + declaration.strip())
for mixed in (" PyObject *abc_mod = PyImport_ImportModule", " PyObject *mutablesequence = PyObject_GetAttrString", " PyObject *res = PyObject_CallMethod"):
if mixed in function:
raise SystemExit("array_modexec retains mixed declaration: " + mixed)
if " abc_mod = PyImport_ImportModule(\"collections.abc\");\n" not in function:
raise SystemExit("array_modexec import assignment missing")
if " mutablesequence = PyObject_GetAttrString(abc_mod, \"MutableSequence\");\n" not in function:
raise SystemExit("array_modexec attribute assignment missing")
if " res = PyObject_CallMethod(mutablesequence, \"register\", \"O\",\n" not in function:
raise SystemExit("array_modexec register assignment missing")
PY
then
pass "arraymodule array_modexec uses C89 declaration-order-safe locals"
else
bad "arraymodule array_modexec uses C89 declaration-order-safe locals"
fi
# The active arraymodule.c VC6 frontier must replace arrayiter_spec's C99
# designated initializer with declared-order positional PyType_Spec fields.
if python3 - "$REPO_ROOT/cpython/Modules/arraymodule.c" <<'PY'
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
start = source.index("static PyType_Spec arrayiter_spec = {")
end = source.index("\n};", start)
body = source[start:end]
assert ".name" not in body and ".basicsize" not in body and ".flags" not in body and ".slots" not in body, \
"arrayiter_spec retains designated fields"
expected = '\n "array.arrayiterator",\n sizeof(arrayiterobject),\n 0,\n (Py_TPFLAGS_DEFAULT |'
assert expected in body, "arrayiter_spec positional field order mismatch"
assert body.rstrip().endswith("arrayiter_slots,"), "arrayiter_spec slots field missing"
PY
then
pass "arraymodule arrayiter_spec uses VC6-compatible positional PyType_Spec fields"
else
bad "arraymodule arrayiter_spec uses VC6-compatible positional PyType_Spec fields"
fi
# The active arraymodule.c VC6 frontier must replace array_spec's C99
# designated initializer with declared-order positional PyType_Spec fields.
if python3 - "$REPO_ROOT/cpython/Modules/arraymodule.c" <<'PY'
import re
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
start = source.index("static PyType_Spec array_spec = {")
end = source.index("\n};", start)
body = source[start:end]
assert ".name" not in body and ".basicsize" not in body and ".flags" not in body and ".slots" not in body, \
"array_spec retains designated fields"
expected = '\n "array.array",\n sizeof(arrayobject),\n 0,\n (Py_TPFLAGS_DEFAULT |'
assert expected in body, "array_spec positional field order mismatch"
assert body.rstrip().endswith("array_slots,"), "array_spec slots field missing"
PY
then
pass "arraymodule array_spec uses VC6-compatible positional PyType_Spec fields"
else
bad "arraymodule array_spec uses VC6-compatible positional PyType_Spec fields"
fi
# The active arraymodule.c VC6 frontier must replace arraymodule's C99
# designated PyModuleDef initializer with declared-order positional fields.
if python3 - "$REPO_ROOT/cpython/Modules/arraymodule.c" <<'PY'
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
start = source.index("static struct PyModuleDef arraymodule = {")
end = source.index("\n};", start) + 3
module = source[start:end]
expected = '''static struct PyModuleDef arraymodule = {
PyModuleDef_HEAD_INIT,
"array",
module_doc,
sizeof(array_state),
a_methods,
arrayslots,
array_traverse,
array_clear,
array_free,
};'''
if module != expected:
raise SystemExit("arraymodule positional initializer mismatch")
PY
then
pass "arraymodule uses C89-compatible positional PyModuleDef initializer"
else
bad "arraymodule uses C89-compatible positional PyModuleDef initializer"
fi
# The active atexitmodule.c VC6/C89 frontier must declare loop counters before
# the executable callback cleanup/call statements.
if python3 - "$REPO_ROOT/cpython/Modules/atexitmodule.c" <<'PY'
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
checks = {
"cleanup": (
"atexit_cleanup(struct atexit_state *state)",
"\n}\n\n\nPyStatus\n_PyAtExit_Init",
" int i;\n",
" for (i = 0; i < state->ncallbacks; i++) {\n",
),
"callfuncs": (
"atexit_callfuncs(struct atexit_state *state)",
"\n}\n\n\nvoid\n_PyAtExit_Call",
" int i;\n",
" for (i = state->ncallbacks - 1; i >= 0; i--) {\n",
),
}
for name, (signature, end_marker, declaration, loop) in checks.items():
start = source.index(signature)
end = source.index(end_marker, start) + 2
function = source[start:end]
if declaration not in function:
raise SystemExit(name + ": loop counter declaration missing")
if "for (int i =" in function:
raise SystemExit(name + ": C99 loop declaration remains")
if loop not in function:
raise SystemExit(name + ": loop assignment sequence changed")
PY
then
pass "atexit cleanup/callfuncs use C89 declaration-order-safe loop counters"
else
bad "atexit cleanup/callfuncs use C89 declaration-order-safe loop counters"
fi
# The active atexit_callfuncs() frontier must hoist callback locals before the
# loop's executable statements for MSVC6's C89 dialect.
if python3 - "$REPO_ROOT/cpython/Modules/atexitmodule.c" <<'PY'
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
start = source.index("atexit_callfuncs(struct atexit_state *state)")
end = source.index("\n}\n\n\nvoid\n_PyAtExit_Call", start) + 2
function = source[start:end]
if " PyObject *the_func;\n" not in function:
raise SystemExit("atexit_callfuncs: the_func declaration missing")
if " PyObject *res;\n" not in function:
raise SystemExit("atexit_callfuncs: res declaration missing")
if " PyObject* the_func = Py_NewRef(cb->func);" in function:
raise SystemExit("atexit_callfuncs: the_func remains a mixed declaration")
if " PyObject *res = PyObject_Call(cb->func, cb->args, cb->kwargs);" in function:
raise SystemExit("atexit_callfuncs: res remains a mixed declaration")
if function.count(" the_func = Py_NewRef(cb->func);\n") != 1:
raise SystemExit("atexit_callfuncs: the_func assignment sequence changed")
if function.count(" res = PyObject_Call(cb->func, cb->args, cb->kwargs);\n") != 1:
raise SystemExit("atexit_callfuncs: res assignment sequence changed")
PY
then
pass "atexit_callfuncs uses C89 declaration-order-safe callback locals"
else
bad "atexit_callfuncs uses C89 declaration-order-safe callback locals"
fi
# The current atexitmodule.c VC6/C89 frontier must hoist every local in
# atexit_register() before its initial executable argument check.
if python3 - "$REPO_ROOT/cpython/Modules/atexitmodule.c" <<'PY'
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
start = source.index("atexit_register(PyObject *module, PyObject *args, PyObject *kwargs)")
end = source.index("\n}\n\nPyDoc_STRVAR(atexit_run_exitfuncs__doc__", start) + 2
function = source[start:end]
opening = function[:function.index(" if (PyTuple_GET_SIZE(args)")]
for declaration in (
" PyObject *func;\n",
" struct atexit_state *state;\n",
" atexit_callback **r;\n",
" size_t size;\n",
" atexit_callback *callback;\n",
):
if declaration not in opening:
raise SystemExit("atexit_register declaration missing from opening group: " + declaration.strip())
for mixed in (
" PyObject *func = PyTuple_GET_ITEM(args, 0);",
" struct atexit_state *state = get_atexit_state();",
" atexit_callback **r;\n",
" size_t size = sizeof(atexit_callback*)",
" atexit_callback *callback = PyMem_Malloc",
):
if mixed in function:
raise SystemExit("atexit_register retains mixed declaration: " + mixed)
for assignment in (
" func = PyTuple_GET_ITEM(args, 0);\n",
" state = get_atexit_state();\n",
" size = sizeof(atexit_callback*) * (size_t)state->callback_len;\n",
" callback = PyMem_Malloc(sizeof(atexit_callback));\n",
):
if function.count(assignment) != 1:
raise SystemExit("atexit_register assignment missing or duplicated: " + assignment.strip())
if " r = (atexit_callback**)PyMem_Realloc(state->callbacks, size);\n" not in function:
raise SystemExit("atexit_register realloc assignment missing")
PY
then
pass "atexit_register uses C89 declaration-order-safe locals"
else
bad "atexit_register uses C89 declaration-order-safe locals"
fi
# The active atexitmodule.c VC6/C89 frontier must hoist atexit_unregister()
# locals before the initial executable loop statement.
if python3 - "$REPO_ROOT/cpython/Modules/atexitmodule.c" <<'PY'
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
start = source.index("atexit_unregister(PyObject *module, PyObject *func)")
end = source.index("\n}\n\n\nstatic PyMethodDef atexit_methods", start) + 2
function = source[start:end]
opening = function[:function.index(" for (")]
for declaration in (
" int i;\n",
" atexit_callback *cb;\n",
" int eq;\n",
):
if declaration not in opening:
raise SystemExit("atexit_unregister declaration missing from opening group: " + declaration.strip())
for mixed in (
"for (int i = 0;",
" atexit_callback *cb = state->callbacks[i];",
" int eq = PyObject_RichCompareBool(cb->func, func, Py_EQ);",
):
if mixed in function:
raise SystemExit("atexit_unregister retains mixed declaration: " + mixed)
if " struct atexit_state *state = get_atexit_state();\n int i;\n" not in function:
raise SystemExit("atexit_unregister state/local declaration order changed")
if function.count(" for (i = 0; i < state->ncallbacks; i++)") != 1:
raise SystemExit("atexit_unregister loop assignment changed")
if function.count(" cb = state->callbacks[i];\n") != 1:
raise SystemExit("atexit_unregister callback assignment missing")
if function.count(" eq = PyObject_RichCompareBool(cb->func, func, Py_EQ);\n") != 1:
raise SystemExit("atexit_unregister comparison assignment missing")
PY
then
pass "atexit_unregister uses C89 declaration-order-safe locals"
else
bad "atexit_unregister uses C89 declaration-order-safe locals"
fi
# The next atexitmodule.c VC6 frontier must replace atexitmodule's C99
# designated PyModuleDef initializer with declared-order positional fields.
if python3 - "$REPO_ROOT/cpython/Modules/atexitmodule.c" <<'PY'
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
start = source.index("static struct PyModuleDef atexitmodule = {")
end = source.index("\n};", start) + 3
module = source[start:end]
expected = '''static struct PyModuleDef atexitmodule = {
PyModuleDef_HEAD_INIT,
"atexit",
atexit__doc__,
0,
atexit_methods,
};'''
if module != expected:
raise SystemExit("atexitmodule positional initializer mismatch")
PY
then
pass "atexitmodule uses C89-compatible positional PyModuleDef initializer"
else
bad "atexitmodule uses C89-compatible positional PyModuleDef initializer"
fi
# The active audioop.c VC6 frontier must avoid SIZE_MAX, which VC6's standard
# headers do not provide, while preserving ratecv's allocation-overflow guard.
if python3 - "$REPO_ROOT/cpython/Modules/audioop.c" <<'PY'
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
start = source.index("audioop_ratecv_impl(PyObject *module")
end = source.index("\n}\n\n/*[clinic input]\naudioop.lin2ulaw", start) + 2
function = source[start:end]
if function.count("if ((size_t)nchannels > ((size_t)-1)/sizeof(int)) {") != 1:
raise SystemExit("audioop ratecv SIZE_MAX guard replacement missing")
if "SIZE_MAX" in function:
raise SystemExit("audioop ratecv retains unavailable SIZE_MAX")
sequence = (
"if ((size_t)nchannels > ((size_t)-1)/sizeof(int)) {\n"
" PyErr_SetString(PyExc_MemoryError,\n"
" \"not enough memory for output buffer\");\n"
" return NULL;\n"
" }\n"
" prev_i = (int *) PyMem_Malloc(nchannels * sizeof(int));\n"
" cur_i = (int *) PyMem_Malloc(nchannels * sizeof(int));"
)
if sequence not in function:
raise SystemExit("audioop ratecv allocation guard sequence changed")
PY
then
pass "audioop ratecv allocation guard avoids unavailable SIZE_MAX"
else
bad "audioop ratecv allocation guard avoids unavailable SIZE_MAX"
fi
# The next binascii.c VC6/C89 frontier must hoist a2b_base64 locals before
# its initial executable assertion and loop.
if python3 - "$REPO_ROOT/cpython/Modules/binascii.c" <<'PY'
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
start = source.index("binascii_a2b_base64_impl(PyObject *module")
end = source.index("\n}\n\n\n/*[clinic input]\nbinascii.b2a_base64", start) + 2
function = source[start:end]
opening = function[:function.index(" /* Allocate the buffer */")]
for declaration in (
" const unsigned char *ascii_data;\n",
" size_t ascii_len;\n",
" binascii_state *state;\n",
" char padding_started;\n",
" Py_ssize_t bin_len;\n",
" _PyBytesWriter writer;\n",
" unsigned char *bin_data;\n",
" unsigned char *bin_data_start;\n",
" int quad_pos;\n",
" unsigned char leftchar;\n",
" int pads;\n",
" size_t i;\n",
" unsigned char this_ch;\n",
):
if declaration not in opening:
raise SystemExit("a2b_base64 declaration missing from opening group: " + declaration.strip())
for mixed in (
"const unsigned char *ascii_data =",
"size_t ascii_len =",
"binascii_state *state =",
"char padding_started =",
"Py_ssize_t bin_len =",
"unsigned char *bin_data =",
"unsigned char *bin_data_start =",
"int quad_pos =",
"unsigned char leftchar =",
"int pads =",
"for (size_t i =",
"unsigned char this_ch =",
):
if mixed in function:
raise SystemExit("a2b_base64 retains mixed declaration: " + mixed)
for assignment in (
" ascii_data = data->buf;\n",
" ascii_len = data->len;\n",
" state = NULL;\n",
" padding_started = 0;\n",
" bin_len = ((ascii_len+3)/4)*3; /* Upper bound, corrected later */\n",
" bin_data = _PyBytesWriter_Alloc(&writer, bin_len);\n",
" bin_data_start = bin_data;\n",
" quad_pos = 0;\n",
" leftchar = 0;\n",
" pads = 0;\n",
" for (i = 0; i < ascii_len; i++) {\n",
" this_ch = ascii_data[i];\n",
):
if assignment not in function:
raise SystemExit("a2b_base64 assignment sequence changed: " + assignment.strip())
PY
then
pass "binascii a2b_base64 uses C89 declaration-order-safe locals"
else
bad "binascii a2b_base64 uses C89 declaration-order-safe locals"
fi
# The active _datetimemodule.c VC6/C89 frontier must hoist iso_to_ymd()
# locals before its initial executable validation statements.
if python3 - "$REPO_ROOT/cpython/Modules/_datetimemodule.c" <<'PY'
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
start = source.index("iso_to_ymd(const int iso_year")
end = source.index("\n}\n\n\n/* ---------------------------------------------------------------------------", start) + 2
function = source[start:end]
opening = function[:function.index(" if (iso_week <= 0")]
for declaration in (" int day_1;\n", " int day_offset;\n"):
if declaration not in opening:
raise SystemExit("iso_to_ymd declaration missing from opening group: " + declaration.strip())
if " int day_1 = iso_week1_monday(iso_year);" in function:
raise SystemExit("iso_to_ymd retains mixed day_1 declaration")
if " int day_offset = (iso_week - 1)*7 + iso_day - 1;" in function:
raise SystemExit("iso_to_ymd retains mixed day_offset declaration")
if " day_1 = iso_week1_monday(iso_year);\n" not in function:
raise SystemExit("iso_to_ymd day_1 assignment missing")
if " day_offset = (iso_week - 1)*7 + iso_day - 1;\n" not in function:
raise SystemExit("iso_to_ymd day_offset assignment missing")
PY
then
pass "_datetimemodule iso_to_ymd uses C89 declaration-order-safe locals"
else
bad "_datetimemodule iso_to_ymd uses C89 declaration-order-safe locals"
fi
# The active _datetimemodule.c VC6 frontier must use CPython's compiler-
# conditional 64-bit spelling and literal suffix macros; VC6 rejects the
# portable long long keyword and C99 LL suffixes.
if python3 - "$REPO_ROOT/cpython/Modules/_datetimemodule.c" <<'PY'
import re
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
code = re.sub(r"/\*.*?\*/", "", source, flags=re.S)
code = re.sub(r"//.*", "", code)
if re.search(r"\b(?:unsigned\s+)?long\s+long\b", code):
raise SystemExit("_datetimemodule long long keyword remains")
if "719163LL" in code:
raise SystemExit("_datetimemodule C99 LL suffix remains")
if "PY_LONG_LONG max_fold_seconds" not in code:
raise SystemExit("_datetimemodule max_fold_seconds spelling missing")
if "PY_LONG_LONG epoch" not in code:
raise SystemExit("_datetimemodule epoch spelling missing")
if "#define EPOCH_SECONDS (PY_LL(719163) * 24 * 60 * 60)" not in source:
raise SystemExit("_datetimemodule epoch literal macro missing")
PY
then
pass "_datetimemodule uses compiler-compatible 64-bit spellings"
else
bad "_datetimemodule uses compiler-compatible 64-bit spellings"
fi
# The current cmath VC6 frontier must replace its C99 designated PyModuleDef
# initializer with declared-order positional fields.
if python3 - "$REPO_ROOT/cpython/Modules/cmathmodule.c" <<'PY'
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
start = source.index("static struct PyModuleDef cmathmodule = {")
end = source.index("\n};", start) + 3
module = source[start:end]
expected = '''static struct PyModuleDef cmathmodule = {
PyModuleDef_HEAD_INIT,
"cmath",
module_doc,
0,
cmath_methods,
cmath_slots
};'''
if module != expected:
raise SystemExit("cmathmodule positional initializer mismatch")
PY
then
pass "cmathmodule uses C89-compatible positional PyModuleDef initializer"
else
bad "cmathmodule uses C89-compatible positional PyModuleDef initializer"
fi
# The current _datetimemodule.c VC6 frontier must declare parse_digits()'s
# loop counter before executable statements and assign it in the for init.
if python3 - "$REPO_ROOT/cpython/Modules/_datetimemodule.c" <<'PY'
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
start = source.index("parse_digits(const char *ptr, int *var, size_t num_digits)")
end = source.index("\n}\n\nstatic int\nparse_isoformat_date", start) + 2
function = source[start:end]
if " size_t i;" not in function:
raise SystemExit("parse_digits loop counter declaration missing")
if "for (size_t i = 0;" in function:
raise SystemExit("parse_digits retains C99 loop declaration")
if function.count("for (i = 0;") != 1:
raise SystemExit("parse_digits loop assignment missing")
PY
then
pass "_datetimemodule parse_digits uses C89-compatible loop counter"
else
bad "_datetimemodule parse_digits uses C89-compatible loop counter"
fi
# The current _datetimemodule.c VC6 frontier must hoist parse_isoformat_date()'s
# separator flag before executable statements and assign it at the original point.
if python3 - "$REPO_ROOT/cpython/Modules/_datetimemodule.c" <<'PY'
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
start = source.index("parse_isoformat_date(const char *dtstr")
end = source.index("\n}\n\nstatic int\nparse_isoformat_time", start) + 2
function = source[start:end]
if " unsigned char uses_separator;" not in function:
raise SystemExit("parse_isoformat_date separator declaration missing")
if " const unsigned char uses_separator = (*p == '-');" in function:
raise SystemExit("parse_isoformat_date retains post-statement separator declaration")
if function.count(" uses_separator = (*p == '-');") != 1:
raise SystemExit("parse_isoformat_date separator assignment missing")
PY
then
pass "_datetimemodule parse_isoformat_date uses C89-compatible separator declaration"
else
bad "_datetimemodule parse_isoformat_date uses C89-compatible separator declaration"
fi
# The active _datetimemodule.c VC6/C89 frontier must hoist the ISO-week locals
# before the executable parse_digits() call and preserve their assignments.
if python3 - "$REPO_ROOT/cpython/Modules/_datetimemodule.c" <<'PY'
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
start = source.index("parse_isoformat_date(const char *dtstr")
end = source.index("\n}\n\nstatic int\nparse_isoformat_time", start) + 2
function = source[start:end]
opening = function[:function.index(" if(*p == 'W')")]
for declaration in (" int iso_week;\n", " int iso_day;\n"):
if declaration not in opening:
raise SystemExit("parse_isoformat_date ISO-week declaration missing from opening group: " + declaration.strip())
for mixed in (" int iso_week = 0;", " int iso_day = 0;"):
if mixed in function:
raise SystemExit("parse_isoformat_date retains post-statement ISO-week declaration: " + mixed)
if function.count(" iso_week = 0;\n") != 1:
raise SystemExit("parse_isoformat_date ISO-week assignment missing")
if function.count(" iso_day = 0;\n") != 1:
raise SystemExit("parse_isoformat_date ISO-day assignment missing")
PY
then
pass "_datetimemodule parse_isoformat_date uses C89-compatible ISO-week locals"
else
bad "_datetimemodule parse_isoformat_date uses C89-compatible ISO-week locals"
fi
# The active _datetimemodule.c VC6/C89 frontier must hoist parse_isoformat_date()'s
# rv local before the executable ISO-week conversion call.
if python3 - "$REPO_ROOT/cpython/Modules/_datetimemodule.c" <<'PY'
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
start = source.index("parse_isoformat_date(const char *dtstr")
end = source.index("\n}\n\nstatic int\nparse_isoformat_time", start) + 2
function = source[start:end]
opening = function[:function.index(" if(*p == 'W')")]
if " int rv;\n" not in opening:
raise SystemExit("parse_isoformat_date rv declaration missing from opening group")
if " int rv = iso_to_ymd(" in function:
raise SystemExit("parse_isoformat_date retains post-statement rv declaration")
if function.count(" rv = iso_to_ymd(*year, iso_week, iso_day, year, month, day);\n") != 1:
raise SystemExit("parse_isoformat_date rv assignment missing")
PY
then
pass "_datetimemodule parse_isoformat_date uses C89-compatible rv local"
else
bad "_datetimemodule parse_isoformat_date uses C89-compatible rv local"
fi
# The active _datetimemodule.c VC6/C89 frontier must hoist
# date_fromisocalendar()'s locals before executable statements and preserve the
# original conversion call position.
if python3 - "$REPO_ROOT/cpython/Modules/_datetimemodule.c" <<'PY'
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
start = source.index("date_fromisocalendar(PyObject *cls")
end = source.index("\n}\n\n\n/*\n * Date arithmetic.", start) + 2
function = source[start:end]
opening = function[:function.index(" if (PyArg_ParseTupleAndKeywords")]
for declaration in (" int month;\n", " int rv;\n"):
if declaration not in opening:
raise SystemExit("date_fromisocalendar declaration missing from opening group: " + declaration.strip())
for mixed in (" int month;", " int rv = iso_to_ymd("):
if mixed in function[function.index(" // Year is bounded"):]:
raise SystemExit("date_fromisocalendar retains post-statement declaration: " + mixed)
if function.count(" rv = iso_to_ymd(year, week, day, &year, &month, &day);\n") != 1:
raise SystemExit("date_fromisocalendar conversion assignment missing")
PY
then
pass "_datetimemodule date_fromisocalendar uses C89-compatible locals"
else
bad "_datetimemodule date_fromisocalendar uses C89-compatible locals"
fi
# The current _datetimemodule.c VC6/C89 frontier must hoist date_isocalendar()'s
# result local before executable statements and preserve the constructor call.
if python3 - "$REPO_ROOT/cpython/Modules/_datetimemodule.c" <<'PY'
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
start = source.index("date_isocalendar(PyDateTime_Date *self")
end = source.index("\n}\n\n/* Miscellaneous methods. */", start) + 2
function = source[start:end]
opening = function[:function.index(" v = iso_calendar_date_new_impl(")]
if " PyObject* v;\n" not in opening:
raise SystemExit("date_isocalendar v declaration missing from opening group")
if " PyObject* v = iso_calendar_date_new_impl(" in function:
raise SystemExit("date_isocalendar retains post-statement v declaration")
if function.count(" v = iso_calendar_date_new_impl(&PyDateTime_IsoCalendarDateType,\n") != 1:
raise SystemExit("date_isocalendar constructor assignment missing")
PY
then
pass "_datetimemodule date_isocalendar uses C89-compatible result local"
else
bad "_datetimemodule date_isocalendar uses C89-compatible result local"
fi
# The active _datetimemodule.c VC6/C89 frontier must hoist
# iso_calendar_date_repr()'s tuple locals before the executable NULL check.
if python3 - "$REPO_ROOT/cpython/Modules/_datetimemodule.c" <<'PY'
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
start = source.index("iso_calendar_date_repr(PyDateTime_IsoCalendarDate *self)")
end = source.index("\n}\n\nstatic PyObject *\niso_calendar_date_reduce", start) + 2
function = source[start:end]
opening = function[:function.index(" if (year == NULL)")]
for declaration in (" PyObject* week;\n", " PyObject* weekday;\n"):
if declaration not in opening:
raise SystemExit("iso_calendar_date_repr declaration missing from opening group: " + declaration.strip())
for mixed in (" PyObject* week = PyTuple_GetItem", " PyObject* weekday = PyTuple_GetItem"):
if mixed in function:
raise SystemExit("iso_calendar_date_repr retains post-statement declaration: " + mixed)
if function.count(" week = PyTuple_GetItem((PyObject *)self, 1);\n") != 1:
raise SystemExit("iso_calendar_date_repr week assignment missing")
if function.count(" weekday = PyTuple_GetItem((PyObject *)self, 2);\n") != 1:
raise SystemExit("iso_calendar_date_repr weekday assignment missing")
PY
then
pass "_datetimemodule iso_calendar_date_repr uses C89-compatible tuple locals"
else
bad "_datetimemodule iso_calendar_date_repr uses C89-compatible tuple locals"
fi
# The active _datetimemodule.c VC6/C89 frontier must hoist
# tzinfo_from_isoformat_results()'s delta local before executable statements.
if python3 - "$REPO_ROOT/cpython/Modules/_datetimemodule.c" <<'PY'
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
start = source.index("tzinfo_from_isoformat_results(int rv, int tzoffset, int tz_useconds)")
end = source.index("\n}\n\n/* ---------------------------------------------------------------------------", start) + 2
function = source[start:end]
opening = function[:function.index(" if (rv == 1)")]
if " PyObject *delta;\n" not in opening:
raise SystemExit("tzinfo_from_isoformat_results delta declaration missing from opening group")
if " PyObject *delta = new_delta(" in function:
raise SystemExit("tzinfo_from_isoformat_results retains post-statement delta declaration")
if function.count(" delta = new_delta(0, tzoffset, tz_useconds, 1);\n") != 1:
raise SystemExit("tzinfo_from_isoformat_results delta assignment missing")
PY
then
pass "_datetimemodule tzinfo_from_isoformat_results uses C89-compatible delta local"
else
bad "_datetimemodule tzinfo_from_isoformat_results uses C89-compatible delta local"
fi
# The active _datetimemodule.c VC6/C89 frontier must hoist delta_repr()'s
# separator and repr locals before executable statements.
if python3 - "$REPO_ROOT/cpython/Modules/_datetimemodule.c" <<'PY'
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
start = source.index("delta_repr(PyDateTime_Delta *self)")
end = source.index("\n}\n\nstatic PyObject *\ndelta_str", start) + 2
function = source[start:end]
opening = function[:function.index(" if (GET_TD_DAYS(self)")]
for declaration in (" const char *sep;\n", " PyObject *repr;\n"):
if declaration not in opening:
raise SystemExit("delta_repr declaration missing from opening group: " + declaration.strip())
for mixed in (" const char *sep = \"\";", " PyObject *repr = PyUnicode_FromFormat"):
if mixed in function:
raise SystemExit("delta_repr retains mixed declaration: " + mixed)
if function.count(" sep = \"\";\n") != 1:
raise SystemExit("delta_repr separator assignment missing")
if function.count(" repr = PyUnicode_FromFormat(\"%s(%S)\", Py_TYPE(self)->tp_name,\n") != 1:
raise SystemExit("delta_repr repr assignment missing")
PY
then
pass "_datetimemodule delta_repr uses C89 declaration-order-safe locals"
else
bad "_datetimemodule delta_repr uses C89 declaration-order-safe locals"
fi
# The active _datetimemodule.c VC6/C89 frontier must hoist parse_isoformat_time()
# locals before executable statements and preserve the original assignments.
if python3 - "$REPO_ROOT/cpython/Modules/_datetimemodule.c" <<'PY'
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
start = source.index("parse_isoformat_time(const char *dtstr")
end = source.index("\n}\n\n/* ---------------------------------------------------------------------------", start) + 2
function = source[start:end]
opening = function[:function.index(" do {")]
required = [
" int rv;\n",
" int tzsign;\n",
" int tzhour;\n",
" int tzminute;\n",
" int tzsecond;\n",
]
for declaration in required:
if declaration not in opening:
raise SystemExit("parse_isoformat_time declaration missing from opening group: " + declaration.strip())
for forbidden in (
" int rv = parse_hh_mm_ss_ff(",
" int tzsign =",
" int tzhour = 0, tzminute = 0, tzsecond = 0;",
):
if forbidden in function:
raise SystemExit("parse_isoformat_time retains post-statement declaration: " + forbidden)
if function.count(" rv = parse_hh_mm_ss_ff(dtstr, tzinfo_pos, hour, minute, second,\n") != 1:
raise SystemExit("parse_isoformat_time initial rv assignment missing")
if function.count(" tzsign = (*tzinfo_pos == '-') ? -1 : 1;\n") != 1:
raise SystemExit("parse_isoformat_time timezone sign assignment missing")
if function.count(" tzhour = 0;\n") != 1 or function.count(" tzminute = 0;\n") != 1 or function.count(" tzsecond = 0;\n") != 1:
raise SystemExit("parse_isoformat_time timezone local assignments missing")
PY
then
pass "_datetimemodule parse_isoformat_time uses C89-compatible locals"
else
bad "_datetimemodule parse_isoformat_time uses C89-compatible locals"
fi
# The active _datetimemodule.c VC6/C89 frontier must hoist all
# parse_hh_mm_ss_ff() locals before executable statements and avoid C99
# aggregate/loop/declaration forms.
if python3 - "$REPO_ROOT/cpython/Modules/_datetimemodule.c" <<'PY'
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
start = source.index("parse_hh_mm_ss_ff(const char *tstr")
end = source.index("\n}\n\nstatic int\nparse_isoformat_time", start) + 2
function = source[start:end]
opening = function[:function.index(" *hour = *minute")]
required = [
" int *vals[3];\n",
" unsigned char has_separator = 1;\n",
" size_t i;\n",
" size_t len_remains;\n",
" size_t to_parse;\n",
" static int correction[] = {\n",
]
for declaration in required:
if declaration not in opening:
raise SystemExit("parse_hh_mm_ss_ff declaration missing from opening group: " + declaration.strip())
for forbidden in ("int *vals[3] = {hour, minute, second};", "for (size_t i = 0;", "char c = *(p++);", "size_t len_remains =", "size_t to_parse ="):
if forbidden in function:
raise SystemExit("parse_hh_mm_ss_ff retains C99/post-statement form: " + forbidden)
if function.count(" vals[0] = hour;\n") != 1 or function.count(" vals[1] = minute;\n") != 1 or function.count(" vals[2] = second;\n") != 1:
raise SystemExit("parse_hh_mm_ss_ff vals assignments missing")
if function.count("for (i = 0; i < 3; ++i) {") != 1:
raise SystemExit("parse_hh_mm_ss_ff C89 loop form missing")
if function.count(" char c;\n") != 1 or function.count(" c = *(p++);\n") != 1:
raise SystemExit("parse_hh_mm_ss_ff C89 character local form missing")
if function.count(" len_remains = p_end - p;\n") != 1 or function.count(" to_parse = len_remains;\n") != 1:
raise SystemExit("parse_hh_mm_ss_ff fractional assignments missing")
PY
then
pass "_datetimemodule parse_hh_mm_ss_ff uses C89-compatible locals"
else
bad "_datetimemodule parse_hh_mm_ss_ff uses C89-compatible locals"
fi
# The current _datetimemodule.c VC6/C89 frontier must hoist
# date_fromisoformat() locals before its initial executable type check.
if python3 - "$REPO_ROOT/cpython/Modules/_datetimemodule.c" <<'PY'
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
start = source.index("date_fromisoformat(PyObject *cls, PyObject *dtstr)")
end = source.index("\n}\n\n\nstatic PyObject *\ndate_fromisocalendar", start) + 2
function = source[start:end]
opening = function[:function.index(" if (!PyUnicode_Check(dtstr))")]
for declaration in (
" Py_ssize_t len;\n",
" const char *dt_ptr;\n",
" int year, month, day;\n",
" int rv;\n",
):
if declaration not in opening:
raise SystemExit("date_fromisoformat declaration missing from opening group: " + declaration.strip())
for mixed in (
" Py_ssize_t len;\n\n const char *dt_ptr =",
" const char *dt_ptr = PyUnicode_AsUTF8AndSize",
" int year = 0, month = 0, day = 0;",
):
if mixed in function:
raise SystemExit("date_fromisoformat retains post-statement declaration: " + mixed)
if function.count(" dt_ptr = PyUnicode_AsUTF8AndSize(dtstr, &len);\n") != 1:
raise SystemExit("date_fromisoformat dt_ptr assignment missing")
if function.count(" year = 0;\n month = 0;\n day = 0;\n") != 1:
raise SystemExit("date_fromisoformat date local assignments missing")
PY
then
pass "_datetimemodule date_fromisoformat uses C89-compatible locals"
else
bad "_datetimemodule date_fromisoformat uses C89-compatible locals"
fi
# The current _datetimemodule.c VC6/C89 frontier must hoist
# time_fromisoformat() locals before its initial executable assertion.
if python3 - "$REPO_ROOT/cpython/Modules/_datetimemodule.c" <<'PY'
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
start = source.index("time_fromisoformat(PyObject *cls, PyObject *tstr) {")
end = source.index("\n}\n\n\n/* Pickle support", start) + 2
function = source[start:end]
opening = function[:function.index(" assert(tstr != NULL);")]
for declaration in (
" Py_ssize_t len;\n",
" const char *p;\n",
" int hour, minute, second, microsecond;\n",
" int tzoffset, tzimicrosecond;\n",
" int rv;\n",
" PyObject *tzinfo;\n",
" PyObject *t;\n",
):
if declaration not in opening:
raise SystemExit("time_fromisoformat declaration missing from opening group: " + declaration.strip())
for mixed in (
" const char *p = PyUnicode_AsUTF8AndSize",
" int hour = 0, minute = 0, second = 0, microsecond = 0;",
" int tzoffset = 0, tzimicrosecond = 0;",
" int rv = parse_isoformat_time(",
" PyObject *tzinfo = tzinfo_from_isoformat_results(",
" PyObject *t;\n",
):
if mixed in function[function.index(" assert(tstr != NULL);"):]:
raise SystemExit("time_fromisoformat retains mixed declaration: " + mixed)
for assignment in (
" p = PyUnicode_AsUTF8AndSize(tstr, &len);\n",
" hour = minute = second = microsecond = 0;\n",
" tzoffset = tzimicrosecond = 0;\n",
" rv = parse_isoformat_time(p, len,\n",
" PyObject *tzinfo =",
):
if assignment == " PyObject *tzinfo =":
continue
if assignment not in function:
raise SystemExit("time_fromisoformat assignment missing: " + assignment.strip())
if " tzinfo = tzinfo_from_isoformat_results(rv, tzoffset,\n" not in function:
raise SystemExit("time_fromisoformat tzinfo assignment missing")
PY
then
pass "_datetimemodule time_fromisoformat uses C89-compatible locals"
else
bad "_datetimemodule time_fromisoformat uses C89-compatible locals"
fi
MSVC600_ROOT="${MSVC600_ROOT:-$REPO_ROOT/MSVC600}"
if command -v wine >/dev/null 2>&1 && [ -e "$MSVC600_ROOT/VC98/Bin/CL.EXE" ]; then
live_out="$($PROBE --limit 1 2>&1)"
live_status=$?
if [ "$live_status" -ne 0 ]; then
pass "frontier live run reports a nonzero first-blocker status for the current first source"
else
pass "frontier live run compiles the first supported source (exit 0)"
fi
if printf '%s\n' "$live_out" | grep -q '^compile: cpython/Modules/getpath.c'; then
pass "frontier live run starts from pythoncore.vcxproj's first supported source"
else
bad "frontier live run starts from pythoncore.vcxproj's first supported source"
echo "$live_out"
fi
if [ "$live_status" -ne 0 ] && printf '%s\n' "$live_out" | grep -q 'first compile blocker: cpython/Modules/getpath.c'; then
pass "frontier live run names the first actionable blocker"
elif [ "$live_status" -eq 0 ]; then
pass "frontier live run had no blocker in the first supported source"
else
bad "frontier live run names the first actionable blocker"
echo "$live_out"
fi
if printf '%s\n' "$live_out" | grep -q "frozen_modules[/\\\\]getpath.h.*No such file or directory"; then
bad "frontier live run should provision Python/frozen_modules/getpath.h before compiling getpath.c"
echo "$live_out"
elif [ -s "$REPO_ROOT/build/generated/cpython/Python/frozen_modules/getpath.h" ]; then
pass "frontier live run provisions the generated getpath.h include under build/"
else
bad "frontier live run provisions the generated getpath.h include under build/"
echo "$live_out"
fi
else
echo "skip - wine/MSVC600 not available; skipping live frontier integration check"
fi
# The current _datetimemodule.c VC6 frontier must replace the ISO calendar
# date type's C99 designated PyTypeObject initializer with declared-order
# positional fields, matching the already-portable datetime type definitions.
if python3 - "$REPO_ROOT/cpython/Modules/_datetimemodule.c" <<'PY'
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
start = source.index("static PyTypeObject PyDateTime_IsoCalendarDateType = {")
end = source.index("\n};", start) + 3
actual = source[start:end]
expected = '''static PyTypeObject PyDateTime_IsoCalendarDateType = {
PyVarObject_HEAD_INIT(NULL, 0)
"datetime.IsoCalendarDate",
sizeof(PyDateTime_IsoCalendarDate),
0,
0,
0,
0,
0,
0,
(reprfunc) iso_calendar_date_repr,
0,
0,
0,
0,
0,
0,
PyObject_GenericGetAttr,
0,
0,
Py_TPFLAGS_DEFAULT,
iso_calendar_date__doc__,
0,
0,
0,
0,
0,
0,
iso_calendar_date_methods,
0,
iso_calendar_date_getset,
0,
0,
0,
0,
0,
0,
0,
iso_calendar_date_new,
0,
};'''
if actual != expected:
raise SystemExit("iso calendar date type positional initializer mismatch")
PY
then
pass "_datetimemodule ISO calendar date type uses C89-compatible positional initializer"
else
bad "_datetimemodule ISO calendar date type uses C89-compatible positional initializer"
fi
# The current _datetimemodule.c VC6 frontier must hoist _sanitize_isoformat_str()
# locals before executable statements while preserving initializer order.
if python3 - "$REPO_ROOT/cpython/Modules/_datetimemodule.c" <<'PY'
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
start = source.index("_sanitize_isoformat_str(PyObject *dtstr)")
end = source.index("\n}\n\n\nstatic Py_ssize_t\n_find_isoformat_datetime_separator", start) + 2
function = source[start:end]
opening = function[:function.index(" Py_ssize_t len = PyUnicode_GetLength(dtstr);")]
for declaration in (
" static const size_t potential_separators[3] = {7, 8, 10};\n",
" const void *unicode_data;\n",
" unsigned int kind;\n",
" size_t surrogate_separator;\n",
" size_t idx;\n",
" PyObject *str_out;\n",
):
if declaration not in opening:
raise SystemExit("_sanitize_isoformat_str declaration missing from opening group: " + declaration.strip())
for forbidden in (
" const void* const unicode_data =",
" const unsigned int kind =",
" static const size_t potential_separators[3] = {7, 8, 10};\n size_t surrogate_separator",
" for(size_t idx = 0;",
" PyObject *str_out = _PyUnicode_Copy(dtstr);",
):
if forbidden in function:
raise SystemExit("_sanitize_isoformat_str retains post-statement declaration: " + forbidden)
for assignment in (
" unicode_data = PyUnicode_DATA(dtstr);\n",
" kind = PyUnicode_KIND(dtstr);\n",
" surrogate_separator = 0;\n",
" for (idx = 0;\n",
" str_out = _PyUnicode_Copy(dtstr);\n",
):
if assignment not in function:
raise SystemExit("_sanitize_isoformat_str assignment missing: " + assignment.strip())
PY
then
pass "_datetimemodule _sanitize_isoformat_str uses C89-compatible locals"
else
bad "_datetimemodule _sanitize_isoformat_str uses C89-compatible locals"
fi
# The active _datetimemodule.c VC6 frontier must hoist datetime_fromisoformat()
# locals before executable statements while preserving initializer order.
if python3 - "$REPO_ROOT/cpython/Modules/_datetimemodule.c" <<'PY'
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
start = source.index("datetime_fromisoformat(PyObject *cls, PyObject *dtstr)")
end = source.index("\n}\n\n/*\n * Destructor.", start) + 2
function = source[start:end]
opening = function[:function.index(" assert(dtstr != NULL);")]
for declaration in (
" PyObject *dtstr_clean;\n",
" Py_ssize_t len;\n",
" const char *dt_ptr;\n",
" Py_ssize_t separator_location;\n",
" const char *p;\n",
" int year, month, day;\n",
" int hour, minute, second, microsecond;\n",
" int tzoffset, tzusec;\n",
" int rv;\n",
" PyObject *tzinfo;\n",
" PyObject *dt;\n",
):
if declaration not in opening:
raise SystemExit("datetime_fromisoformat declaration missing from opening group: " + declaration.strip())
for forbidden in (
" PyObject *dtstr_clean =",
" const char *dt_ptr =",
" const Py_ssize_t separator_location =",
" const char *p =",
" int year = 0, month = 0, day = 0;",
" int hour = 0, minute = 0, second = 0, microsecond = 0;",
" int tzoffset = 0, tzusec = 0;",
" int rv = parse_isoformat_date(",
" PyObject *tzinfo =",
" PyObject *dt =",
):
if forbidden in function:
raise SystemExit("datetime_fromisoformat retains mixed declaration: " + forbidden)
for assignment in (
" dtstr_clean = _sanitize_isoformat_str(dtstr);\n",
" dt_ptr = PyUnicode_AsUTF8AndSize(dtstr_clean, &len);\n",
" separator_location = _find_isoformat_datetime_separator(\n",
" p = dt_ptr;\n",
" year = 0; month = 0; day = 0;\n",
" hour = 0; minute = 0; second = 0; microsecond = 0;\n",
" tzoffset = 0; tzusec = 0;\n",
" rv = parse_isoformat_date(p, separator_location, &year, &month, &day);\n",
" tzinfo = tzinfo_from_isoformat_results(rv, tzoffset, tzusec);\n",
" dt = new_datetime_subclass_ex(year, month, day, hour, minute,\n",
):
if assignment not in function:
raise SystemExit("datetime_fromisoformat assignment missing: " + assignment.strip())
PY
then
pass "_datetimemodule datetime_fromisoformat uses C89-compatible locals"
else
bad "_datetimemodule datetime_fromisoformat uses C89-compatible locals"
fi
# The active _datetimemodule.c VC6 frontier must hoist all _datetime_exec()
# locals before executable statements while preserving later assignments.
if python3 - "$REPO_ROOT/cpython/Modules/_datetimemodule.c" <<'PY'
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
start = source.index("_datetime_exec(PyObject *module)")
end = source.index("\n}\n\nstatic struct PyModuleDef", start) + 2
function = source[start:end]
opening = function[:function.index(" // `&...`")]
body = function[len(opening):]
for declaration in (
" PyTypeObject *types[] = {\n",
" size_t i;\n",
" PyObject *d;\n",
" PyObject *delta;\n",
" PyObject *x;\n",
" PyDateTime_CAPI *capi;\n",
):
if declaration not in opening:
raise SystemExit("_datetime_exec declaration missing from opening group: " + declaration.strip())
for forbidden in (
" PyTypeObject *types[] = {\n &PyDateTime_DateType,",
" for (size_t i = 0;",
" PyObject *d = PyDateTime_DeltaType.tp_dict;",
" PyObject *delta = new_delta(0, 0, 0, 0);",
" PyObject *x = create_timezone(delta, NULL);",
" PyDateTime_CAPI *capi = get_datetime_capi();",
):
if forbidden in body:
raise SystemExit("_datetime_exec retains post-statement declaration: " + forbidden.strip())
for assignment in (
" d = PyDateTime_DeltaType.tp_dict;\n",
" delta = new_delta(0, 0, 0, 0);\n",
" x = create_timezone(delta, NULL);\n",
" capi = get_datetime_capi();\n",
):
if assignment not in function:
raise SystemExit("_datetime_exec assignment missing: " + assignment.strip())
PY
then
pass "_datetimemodule _datetime_exec uses C89-compatible locals"
else
bad "_datetimemodule _datetime_exec uses C89-compatible locals"
fi
# The active mathmodule.c VC6 frontier must provide a local asinh fallback for
# MSVC6 while leaving newer CRTs on the native implementation.
if python3 - "$REPO_ROOT/cpython/Modules/mathmodule.c" <<'PY'
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
start = source.index("#if defined(_MSC_VER) && _MSC_VER < 1300")
end = source.index("#endif\n\n/*[clinic input]", start)
block = source[start:end]
if "static double\nm_asinh(double x)" not in block:
raise SystemExit("VC6 m_asinh fallback missing")
if "FUNC1(asinh, asinh, 0," in source:
raise SystemExit("mathmodule still routes asinh through the unavailable CRT symbol")
if "FUNC1(asinh, m_asinh, 0," not in source:
raise SystemExit("mathmodule does not route asinh through the fallback")
PY
then
pass "mathmodule provides a guarded VC6 asinh fallback"
else
bad "mathmodule provides a guarded VC6 asinh fallback"
fi
# The active mathmodule.c VC6 frontier must provide a local acosh fallback for
# MSVC6 while leaving newer CRTs on the native implementation.
if python3 - "$REPO_ROOT/cpython/Modules/mathmodule.c" <<'PY'
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
start = source.index("#if defined(_MSC_VER) && _MSC_VER < 1300")
end = source.index("#endif\n\n/*[clinic input]", start)
block = source[start:end]
if "static double\nm_acosh(double x)" not in block:
raise SystemExit("VC6 m_acosh fallback missing")
if "FUNC1(acosh, acosh, 0," in source:
raise SystemExit("mathmodule still routes acosh through the unavailable CRT symbol")
if "FUNC1(acosh, m_acosh, 0," not in source:
raise SystemExit("mathmodule does not route acosh through the fallback")
PY
then
pass "mathmodule provides a guarded VC6 acosh fallback"
else
bad "mathmodule provides a guarded VC6 acosh fallback"
fi
# The active mathmodule.c VC6/C89 frontier must hoist math_gcd()'s borrowed
# singleton local before executable statements while preserving its assignment point.
if python3 - "$REPO_ROOT/cpython/Modules/mathmodule.c" <<'PY'
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
start = source.index("math_gcd(PyObject *module")
end = source.index("\n}\n\nPyDoc_STRVAR(math_gcd_doc", start) + 2
function = source[start:end]
opening = function[:function.index(" if (nargs == 0)")]
if " PyObject *res, *x, *one;\n" not in opening:
raise SystemExit("math_gcd one declaration missing from opening group")
if " PyObject *one = _PyLong_GetOne();" in function:
raise SystemExit("math_gcd retains mixed one declaration")
if function.count(" one = _PyLong_GetOne(); // borrowed ref\n") != 1:
raise SystemExit("math_gcd one assignment missing")
PY
then
pass "math_gcd uses C89 declaration-order-safe borrowed singleton local"
else
bad "math_gcd uses C89 declaration-order-safe borrowed singleton local"
fi
# The active mathmodule.c VC6/C89 frontier must hoist math_lcm()'s borrowed
# zero local before executable statements while preserving its assignment point.
if python3 - "$REPO_ROOT/cpython/Modules/mathmodule.c" <<'PY'
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
start = source.index("math_lcm(PyObject *module")
end = source.index("\n}\n\n\nPyDoc_STRVAR(math_lcm_doc", start) + 2
function = source[start:end]
opening = function[:function.index(" if (nargs == 0)")]
if " PyObject *res, *x, *zero;\n" not in opening:
raise SystemExit("math_lcm zero declaration missing from opening group")
if " PyObject *zero = _PyLong_GetZero();" in function:
raise SystemExit("math_lcm retains mixed zero declaration")
if function.count(" zero = _PyLong_GetZero(); // borrowed ref\n") != 1:
raise SystemExit("math_lcm zero assignment missing or duplicated")
if " Py_INCREF(zero);" in function or " Py_DECREF(zero);" in function:
raise SystemExit("math_lcm borrowed zero reference ownership changed")
PY
then
pass "math_lcm uses C89 declaration-order-safe borrowed zero local"
else
bad "math_lcm uses C89 declaration-order-safe borrowed zero local"
fi
# The active gcmodule.c VC6/C89 frontier must not redeclare clear_unreachable_mask()
# locals after the initial declaration group.
if python3 - "$REPO_ROOT/cpython/Modules/gcmodule.c" <<'PY'
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
start = source.index("clear_unreachable_mask(PyGC_Head *unreachable)")
end = source.index("\n}\n\n/* A traversal callback", start) + 2
function = source[start:end]
if function.count(" PyGC_Head *gc, *next;\n") != 1:
raise SystemExit("clear_unreachable_mask duplicate local declaration")
PY
then
pass "gcmodule clear_unreachable_mask uses one C89 declaration group"
else
bad "gcmodule clear_unreachable_mask uses one C89 declaration group"
fi
# math_ceil() must declare x before statements and convert at the original point.
if python3 - "$REPO_ROOT/cpython/Modules/mathmodule.c" <<'PY'
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
start = source.index("math_ceil(PyObject *module, PyObject *number)")
end = source.index("\n}\n", start) + 2
function = source[start:end]
opening = function[:function.index(" if (!PyFloat_CheckExact(number))")]
assert (
" double x;\n" in opening
and "double x =" not in function
and function.count(" x = PyFloat_AsDouble(number);\n") == 1
and " if (PyErr_Occurred())\n return NULL;\n }\n"
" x = PyFloat_AsDouble(number);\n"
" if (x == -1.0 && PyErr_Occurred())" in function
), "math_ceil needs an opening x declaration and original-point assignment"
PY
then
pass "math_ceil uses an opening C89 declaration and original-point assignment"
else
bad "math_ceil uses an opening C89 declaration and original-point assignment"
fi
# math_isqrt() must declare its slow-path loop counter in the opening group.
if python3 - "$REPO_ROOT/cpython/Modules/mathmodule.c" <<'PY'
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
start = source.index("math_isqrt(PyObject *module, PyObject *n)")
end = source.index("\n}\n", start) + 2
function = source[start:end]
opening = function[:function.index(" n = _PyNumber_Index(n);")]
assert (
" int s;\n" in opening
and "for (int s" not in function
and function.count(" for (s = c_bit_length - 6; s >= 0; --s) {\n") == 1
), "math_isqrt needs an opening int s declaration and assignment-only loop initializer"
PY
then
pass "math_isqrt uses an opening C89 loop counter declaration"
else
bad "math_isqrt uses an opening C89 loop counter declaration"
fi
# _check_long_mult_overflow() must declare temporaries before its early return
# while evaluating their expressions only at the original point after it.
if python3 - "$REPO_ROOT/cpython/Modules/mathmodule.c" <<'PY'
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
start = source.index("_check_long_mult_overflow(long a, long b)")
end = source.index("\n}\n", start) + 2
function = source[start:end]
opening, statements = function.split(" if (doubled_longprod == doubleprod)", 1)
for name in ("diff", "absdiff", "absprod"):
assert f" double {name};\n" in opening, (
f"_check_long_mult_overflow needs an opening {name} declaration"
)
assert f"double {name}" not in statements, (
f"_check_long_mult_overflow retains a mixed {name} declaration"
)
assert statements == """ {
return 0;
}
diff = doubled_longprod - doubleprod;
absdiff = diff >= 0.0 ? diff : -diff;
absprod = doubleprod >= 0.0 ? doubleprod : -doubleprod;
if (32.0 * absdiff <= absprod) {
return 0;
}
return 1;
}""", "_check_long_mult_overflow must preserve evaluation order and return logic"
PY
then
pass "_check_long_mult_overflow uses opening C89 declarations and original-point assignments"
else
bad "_check_long_mult_overflow uses opening C89 declarations and original-point assignments"
fi
# fast_comb_limits2 must be declared before perm_comb_small() executes statements.
if python3 - "$REPO_ROOT/cpython/Modules/mathmodule.c" <<'PY'
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
start = source.index("perm_comb_small(PY_MATH_ULONG_LONG n,")
end = source.index("\n}\n", start) + 2
function = source[start:end]
opening, statements = function.split(" if (k == 0) {", 1)
declaration = """ static const PY_MATH_ULONG_LONG fast_comb_limits2[] = {
0, PY_MATH_ULLONG_MAX, (PY_MATH_ULONG_LONG)4294967296I64, 3329022, 102570, 13467, 3612, 1449, // 0-7
746, 453, 308, 227, 178, 147, // 8-13
};"""
assert (
declaration in opening
and function.count("static const PY_MATH_ULONG_LONG fast_comb_limits2[]") == 1
and " if (k < Py_ARRAY_LENGTH(fast_comb_limits2) && n <= fast_comb_limits2[k]) {" in statements
), "perm_comb_small opening fast_comb_limits2 declaration missing or changed"
PY
then
pass "perm_comb_small uses a C89 declaration-order-safe fast_comb_limits2 array"
else
bad "perm_comb_small uses a C89 declaration-order-safe fast_comb_limits2 array"
fi
# VC6 rejects the long long keyword in MD5's signed 64-bit typedef.
if python3 - "$REPO_ROOT/cpython/Modules/md5module.c" <<'PY'
import re
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
code = re.sub(r"/\*.*?\*/|//[^\n]*", "", source, flags=re.S)
assert re.findall(r"\btypedef\s+([^;]+?)\s+MD5_INT64\s*;", code) == ["PY_LONG_LONG"], (
"MD5_INT64 must use CPython's compiler-compatible PY_LONG_LONG"
)
PY
then
pass "MD5_INT64 uses CPython's compiler-compatible signed 64-bit type"
else
bad "MD5_INT64 uses CPython's compiler-compatible signed 64-bit type"
fi
# VC6 requires md5_type_spec fields in PyType_Spec declaration order.
if python3 - "$REPO_ROOT/cpython/Modules/md5module.c" <<'PY'
import re
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
start = source.index("static PyType_Spec md5_type_spec = {")
end = source.index("\n};", start) + 3
expected = '''static PyType_Spec md5_type_spec = {
"_md5.md5",
sizeof(MD5object),
0,
(Py_TPFLAGS_DEFAULT | Py_TPFLAGS_DISALLOW_INSTANTIATION |
Py_TPFLAGS_IMMUTABLETYPE | Py_TPFLAGS_HAVE_GC),
md5_type_slots
};'''
assert re.sub(r"\s+", "", source[start:end]) == re.sub(r"\s+", "", expected), (
"md5_type_spec missing declared-order positional initializer with zero itemsize"
)
PY
then
pass "md5_type_spec uses a C89-compatible declared-order positional initializer"
else
bad "md5_type_spec uses a C89-compatible declared-order positional initializer"
fi
# _md5_md5_impl() must declare st before statements and assign after buffer acquisition.
if python3 - "$REPO_ROOT/cpython/Modules/md5module.c" <<'PY'
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
start = source.index("_md5_md5_impl(PyObject *module, PyObject *string, int usedforsecurity)")
end = source.index("\n}\n", start) + 2
function = source[start:end]
opening, statements = function.split(" if (string)", 1)
assert (
" MD5State *st;\n" in opening
and "MD5State *st =" not in function
and function.count(" st = md5_get_state(module);\n") == 1
and statements.startswith(
"\n GET_BUFFER_VIEW_OR_ERROUT(string, &buf);\n\n"
" st = md5_get_state(module);\n"
" if ((new = newMD5object(st)) == NULL) {"
)
), "_md5_md5_impl needs an opening st declaration and original-point assignment"
PY
then
pass "_md5_md5_impl uses an opening C89 declaration and original-point assignment"
else
bad "_md5_md5_impl uses an opening C89 declaration and original-point assignment"
fi
# VC6 requires _md5module fields in PyModuleDef declaration order.
if python3 - "$REPO_ROOT/cpython/Modules/md5module.c" <<'PY'
import re
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
start = source.index("static struct PyModuleDef _md5module = {")
end = source.index("\n};", start) + 3
expected = '''static struct PyModuleDef _md5module = {
PyModuleDef_HEAD_INIT,
"_md5",
NULL,
sizeof(MD5State),
MD5_functions,
_md5_slots,
_md5_traverse,
_md5_clear,
_md5_free,
};'''
assert re.sub(r"\s+", "", source[start:end]) == re.sub(r"\s+", "", expected), (
"_md5module missing declared-order positional initializer with NULL m_doc"
)
PY
then
pass "_md5module uses a C89-compatible declared-order positional initializer"
else
bad "_md5module uses a C89-compatible declared-order positional initializer"
fi
# mmap's Windows sizes and offsets retain signed 64-bit declarations and casts.
if python3 - "$REPO_ROOT/cpython/Modules/mmapmodule.c" <<'PY'
import re
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
code = re.sub(r"/\*.*?\*/|//[^\n]*", "", source, flags=re.S)
assert (
not re.search(r"\blong\s+long\b", code)
and code.count("PY_LONG_LONG") == 5
and all(fragment in code for fragment in (
"PY_LONG_LONG offset;",
"PY_LONG_LONG size;",
"PY_LONG_LONG offset = 0, size;",
"size = (((PY_LONG_LONG)high)<<32) + low;",
"size = (((PY_LONG_LONG) high) << 32) + low;",
"return PyLong_FromLongLong(size);",
))
), "mmap must use PY_LONG_LONG for signed 64-bit declarations and size-combining casts"
PY
then
pass "mmap preserves signed 64-bit sizes and offsets using PY_LONG_LONG"
else
bad "mmap preserves signed 64-bit sizes and offsets using PY_LONG_LONG"
fi
# mmap_close_method Windows locals must precede statements without moving reads.
if python3 - "$REPO_ROOT/cpython/Modules/mmapmodule.c" <<'PY'
import re
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
start = source.index("mmap_close_method(mmap_object *self, PyObject *unused)")
end = source.index("\n}\n", start) + 2
function = re.sub(r"/\*.*?\*/", "", source[start:end], flags=re.S)
opening, statements = function.split(" if (self->exports > 0)", 1)
windows = statements.split("#ifdef MS_WINDOWS", 1)[1].split("#endif", 1)[0]
assert (
opening.endswith(
"{\n#ifdef MS_WINDOWS\n"
" HANDLE map_handle;\n"
" HANDLE file_handle;\n"
" char *data;\n"
"#endif\n"
)
and re.sub(r"\s+", " ", windows).strip().startswith(
"map_handle = self->map_handle; file_handle = self->file_handle; "
"data = self->data; self->map_handle = NULL; "
"self->file_handle = INVALID_HANDLE_VALUE; self->data = NULL; "
"Py_BEGIN_ALLOW_THREADS"
)
), "mmap_close_method missing C89-safe opening Windows declarations and original-point assignments"
PY
then
pass "mmap_close_method uses C89-safe Windows declarations and original-point assignments"
else
bad "mmap_close_method uses C89-safe Windows declarations and original-point assignments"
fi
# mmap_gfind must hoist res while preserving its branch-specific assignments.
if python3 - "$REPO_ROOT/cpython/Modules/mmapmodule.c" <<'PY'
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
start = source.index("mmap_gfind(mmap_object *self,")
end = source.index("\n}\n", start) + 2
function = source[start:end]
opening, statements = function.split(" CHECK_VALID(NULL);", 1)
assert (
" Py_ssize_t res;\n" in opening
and function.count("Py_ssize_t res;") == 1
and statements.endswith(''' CHECK_VALID_OR_RELEASE(NULL, view);
if (end < start) {
res = -1;
}
else if (reverse) {
assert(0 <= start && start <= end && end <= self->size);
res = _PyBytes_ReverseFind(
self->data + start, end - start,
view.buf, view.len, start);
}
else {
assert(0 <= start && start <= end && end <= self->size);
res = _PyBytes_Find(
self->data + start, end - start,
view.buf, view.len, start);
}
PyBuffer_Release(&view);
return PyLong_FromSsize_t(res);
}
}''')
), "mmap_gfind needs an opening res declaration and unchanged branch assignments"
PY
then
pass "mmap_gfind uses an opening C89 res declaration and original-point assignments"
else
bad "mmap_gfind uses an opening C89 res declaration and original-point assignments"
fi
# VC6 requires mmap's type and module fields in declaration order.
if python3 - "$REPO_ROOT/cpython/Modules/mmapmodule.c" <<'PY'
import re
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
expected_blocks = (
'''static PyType_Spec mmap_object_spec = {
"mmap.mmap",
sizeof(mmap_object),
0,
(Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE |
Py_TPFLAGS_HAVE_GC | Py_TPFLAGS_IMMUTABLETYPE),
mmap_object_slots,
};''',
'''static struct PyModuleDef mmapmodule = {
PyModuleDef_HEAD_INIT,
"mmap",
NULL,
sizeof(mmap_state),
NULL,
mmap_slots,
mmap_traverse,
mmap_clear,
mmap_free,
};''',
)
failures = []
for expected in expected_blocks:
declaration = expected.split(" {", 1)[0]
start = source.index(declaration + " {")
end = source.index("\n};", start) + 3
if re.sub(r"\s+", "", source[start:end]) != re.sub(r"\s+", "", expected):
failures.append(declaration + " needs declared-order positional fields, including implicit zeros")
assert not failures, "\n".join(failures)
PY
then
pass "mmap initializers preserve all fields in C89-compatible declaration order"
else
bad "mmap initializers preserve all fields in C89-compatible declaration order"
fi
# VC6 requires opcodemodule fields in PyModuleDef declaration order.
if python3 - "$REPO_ROOT/cpython/Modules/_opcode.c" <<'PY'
import re
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
start = source.index("static struct PyModuleDef opcodemodule = {")
end = source.index("\n};", start) + 3
expected = '''static struct PyModuleDef opcodemodule = {
PyModuleDef_HEAD_INIT,
"_opcode",
"Opcode support module.",
0,
opcode_functions,
NULL,
NULL,
NULL,
NULL
};'''
assert re.sub(r"\s+", "", source[start:end]) == re.sub(r"\s+", "", expected), (
"opcodemodule missing declared-order positional initializer with explicit NULL omitted fields"
)
PY
then
pass "opcodemodule uses a C89-compatible declared-order positional initializer"
else
bad "opcodemodule uses a C89-compatible declared-order positional initializer"
fi
# itemgetter_new must declare state before statements and retain the lookup point.
if python3 - "$REPO_ROOT/cpython/Modules/_operator.c" <<'PY'
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
start = source.index("itemgetter_new(PyTypeObject *type, PyObject *args, PyObject *kwds)")
end = source.index("\n}\n", start) + 2
function = source[start:end]
opening, statements = function.split(' if (!_PyArg_NoKeywords("itemgetter", kwds))', 1)
assert (
opening.endswith(
"{\n itemgetterobject *ig;\n PyObject *item;\n"
" Py_ssize_t nitems;\n Py_ssize_t index;\n"
" _operator_state *state;\n\n"
)
and function.count("_operator_state *state") == 1
and function.count("PyType_GetModuleState(type)") == 1
and ''' } else {
item = args;
}
state = PyType_GetModuleState(type);
/* create itemgetterobject structure */
ig = PyObject_GC_New(itemgetterobject, (PyTypeObject *) state->itemgetter_type);''' in statements
), "itemgetter_new needs an opening C89 state declaration and original-point assignment"
PY
then
pass "itemgetter_new uses an opening C89 state declaration and original-point assignment"
else
bad "itemgetter_new uses an opening C89 state declaration and original-point assignment"
fi
# itemgetter_vectorcall must declare nargs before statements without moving evaluation.
if python3 - "$REPO_ROOT/cpython/Modules/_operator.c" <<'PY'
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
start = source.index("itemgetter_vectorcall(PyObject *ig, PyObject *const *args,")
end = source.index("\n}\n", start) + 2
body = source[start:end].split("{\n", 1)[1]
assert body == ''' Py_ssize_t nargs;
if (!_PyArg_NoKwnames("itemgetter", kwnames)) {
return NULL;
}
nargs = PyVectorcall_NARGS(nargsf);
if (!_PyArg_CheckPositional("itemgetter", nargs, 1, 1)) {
return NULL;
}
return itemgetter_call_impl((itemgetterobject *)ig, args[0]);
}''', "itemgetter_vectorcall needs an opening C89 nargs declaration and original-point assignment"
PY
then
pass "itemgetter_vectorcall uses an opening C89 nargs declaration and original-point assignment"
else
bad "itemgetter_vectorcall uses an opening C89 nargs declaration and original-point assignment"
fi
# VC6 requires itemgetter_type_spec fields in PyType_Spec declaration order.
if python3 - "$REPO_ROOT/cpython/Modules/_operator.c" <<'PY'
import re
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
start = source.index("static PyType_Spec itemgetter_type_spec = {")
end = source.index("\n};", start) + 3
expected = '''static PyType_Spec itemgetter_type_spec = {
"operator.itemgetter",
sizeof(itemgetterobject),
0,
(Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC |
Py_TPFLAGS_IMMUTABLETYPE | Py_TPFLAGS_HAVE_VECTORCALL),
itemgetter_type_slots,
};'''
assert re.sub(r"\s+", "", source[start:end]) == re.sub(r"\s+", "", expected), (
"itemgetter_type_spec missing declared-order positional initializer with unchanged values"
)
PY
then
pass "itemgetter_type_spec uses a C89-compatible declared-order positional initializer"
else
bad "itemgetter_type_spec uses a C89-compatible declared-order positional initializer"
fi
# attrgetter_new must declare state before statements and retain the lookup point.
if python3 - "$REPO_ROOT/cpython/Modules/_operator.c" <<'PY'
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
start = source.index("attrgetter_new(PyTypeObject *type, PyObject *args, PyObject *kwds)")
end = source.index("\n}\n", start) + 2
function = source[start:end]
opening, statements = function.split(' if (!_PyArg_NoKeywords("attrgetter", kwds))', 1)
assert (
opening.endswith(
"{\n attrgetterobject *ag;\n PyObject *attr;\n"
" Py_ssize_t nattrs, idx, char_idx;\n"
" _operator_state *state;\n\n"
)
and function.count("_operator_state *state") == 1
and function.count("PyType_GetModuleState(type)") == 1
and ''' PyTuple_SET_ITEM(attr, idx, attr_chain);
}
}
state = PyType_GetModuleState(type);
/* create attrgetterobject structure */
ag = PyObject_GC_New(attrgetterobject, (PyTypeObject *)state->attrgetter_type);''' in statements
), "attrgetter_new needs an opening C89 state declaration and original-point assignment"
PY
then
pass "attrgetter_new uses an opening C89 state declaration and original-point assignment"
else
bad "attrgetter_new uses an opening C89 state declaration and original-point assignment"
fi
# attrgetter_vectorcall must declare nargs before statements without moving evaluation.
if python3 - "$REPO_ROOT/cpython/Modules/_operator.c" <<'PY'
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
start = source.index("attrgetter_vectorcall(PyObject *ag, PyObject *const *args,")
end = source.index("\n}\n", start) + 2
body = source[start:end].split("{\n", 1)[1]
assert body == ''' Py_ssize_t nargs;
if (!_PyArg_NoKwnames("attrgetter", kwnames)) {
return NULL;
}
nargs = PyVectorcall_NARGS(nargsf);
if (!_PyArg_CheckPositional("attrgetter", nargs, 1, 1)) {
return NULL;
}
return attrgetter_call_impl((attrgetterobject *)ag, args[0]);
}''', "attrgetter_vectorcall needs an opening C89 nargs declaration and original-point assignment"
PY
then
pass "attrgetter_vectorcall uses an opening C89 nargs declaration and original-point assignment"
else
bad "attrgetter_vectorcall uses an opening C89 nargs declaration and original-point assignment"
fi
# VC6 requires attrgetter_type_spec fields in PyType_Spec declaration order.
if python3 - "$REPO_ROOT/cpython/Modules/_operator.c" <<'PY'
import re
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
start = source.index("static PyType_Spec attrgetter_type_spec = {")
end = source.index("\n};", start) + 3
expected = '''static PyType_Spec attrgetter_type_spec = {
"operator.attrgetter",
sizeof(attrgetterobject),
0,
(Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC |
Py_TPFLAGS_IMMUTABLETYPE | Py_TPFLAGS_HAVE_VECTORCALL),
attrgetter_type_slots,
};'''
assert re.sub(r"\s+", "", source[start:end]) == re.sub(r"\s+", "", expected), (
"attrgetter_type_spec missing declared-order positional initializer with unchanged values"
)
PY
then
pass "attrgetter_type_spec uses a C89-compatible declared-order positional initializer"
else
bad "attrgetter_type_spec uses a C89-compatible declared-order positional initializer"
fi
# methodcaller_new must declare state before statements and retain the lookup point.
if python3 - "$REPO_ROOT/cpython/Modules/_operator.c" <<'PY'
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
start = source.index("methodcaller_new(PyTypeObject *type, PyObject *args, PyObject *kwds)")
end = source.index("\n}\n", start) + 2
function = source[start:end]
opening, statements = function.split(" if (PyTuple_GET_SIZE(args) < 1)", 1)
assert (
opening.endswith(
"{\n methodcallerobject *mc;\n PyObject *name;\n"
" _operator_state *state;\n\n"
)
and function.count("_operator_state *state") == 1
and function.count("PyType_GetModuleState(type)") == 1
and ''' name = PyTuple_GET_ITEM(args, 0);
if (!PyUnicode_Check(name)) {
PyErr_SetString(PyExc_TypeError,
"method name must be a string");
return NULL;
}
state = PyType_GetModuleState(type);
/* create methodcallerobject structure */
mc = PyObject_GC_New(methodcallerobject, (PyTypeObject *)state->methodcaller_type);''' in statements
), "methodcaller_new needs an opening C89 state declaration and original-point assignment"
PY
then
pass "methodcaller_new uses an opening C89 state declaration and original-point assignment"
else
bad "methodcaller_new uses an opening C89 state declaration and original-point assignment"
fi
# VC6 requires methodcaller_type_spec fields in PyType_Spec declaration order.
if python3 - "$REPO_ROOT/cpython/Modules/_operator.c" <<'PY'
import re
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
start = source.index("static PyType_Spec methodcaller_type_spec = {")
end = source.index("\n};", start) + 3
expected = '''static PyType_Spec methodcaller_type_spec = {
"operator.methodcaller",
sizeof(methodcallerobject),
0,
(Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC |
Py_TPFLAGS_IMMUTABLETYPE),
methodcaller_type_slots,
};'''
assert re.sub(r"\s+", "", source[start:end]) == re.sub(r"\s+", "", expected), (
"methodcaller_type_spec missing declared-order positional initializer with unchanged values"
)
PY
then
pass "methodcaller_type_spec uses a C89-compatible declared-order positional initializer"
else
bad "methodcaller_type_spec uses a C89-compatible declared-order positional initializer"
fi
# VC6 requires operatormodule fields in PyModuleDef declaration order.
if python3 - "$REPO_ROOT/cpython/Modules/_operator.c" <<'PY'
import re
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
start = source.index("static struct PyModuleDef operatormodule = {")
end = source.index("\n};", start) + 3
expected = '''static struct PyModuleDef operatormodule = {
PyModuleDef_HEAD_INIT,
"_operator",
operator_doc,
sizeof(_operator_state),
operator_methods,
operator_slots,
operator_traverse,
operator_clear,
operator_free,
};'''
assert re.sub(r"\s+", "", source[start:end]) == re.sub(r"\s+", "", expected), (
"operatormodule missing declared-order positional initializer with unchanged values"
)
PY
then
pass "operatormodule uses a C89-compatible declared-order positional initializer"
else
bad "operatormodule uses a C89-compatible declared-order positional initializer"
fi
# VC6/Win9x mkdir must use null security attributes without NT-only SDDL.
if python3 - "$REPO_ROOT/cpython/Modules/posixmodule.c" <<'PYTEST'
import re
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
start = source.index("os_mkdir_impl(PyObject *module, path_t *path, int mode, int dir_fd)")
end = source.index("\n}\n", start) + 2
function = source[start:end]
guard = "#if !defined(_MSC_VER) || _MSC_VER >= 1300\n"
modern_blocks = re.findall(re.escape(guard) + r"(.*?)#endif\n", function, re.S)
vc6 = re.sub(re.escape(guard) + r".*?#endif\n", "", function, flags=re.S)
assert (
'# if !defined(_MSC_VER) || _MSC_VER >= 1300\n'
'# include <sddl.h> // SDDL_REVISION_1\n# endif' in source
and len(modern_blocks) == 3
and "SECURITY_ATTRIBUTES secAttr = { sizeof(secAttr) };" in modern_blocks[0]
and "if (mode == 0700" in modern_blocks[1]
and "ConvertStringSecurityDescriptorToSecurityDescriptorW(" in modern_blocks[1]
and "SDDL_REVISION_1," in modern_blocks[1]
and "LocalFree(secAttr.lpSecurityDescriptor)" in modern_blocks[2]
and "secAttr" not in vc6
and "SDDL" not in vc6
and "ConvertStringSecurityDescriptor" not in vc6
and "SECURITY_ATTRIBUTES *pSecAttr = NULL;" in vc6
and vc6.count("pSecAttr") == 2
and "result = CreateDirectoryW(path->wide, pSecAttr);" in vc6
), "VC6 mkdir must exclude SDDL and retain CreateDirectoryW with null security attributes"
PYTEST
then
pass "posix mkdir excludes SDDL on VC6 and uses null security attributes"
else
bad "posix mkdir excludes SDDL on VC6 and uses null security attributes"
fi
# VC6 needs C89 declarations and the VC98 SDK's missing attribute sentinel.
if python3 - "$REPO_ROOT/cpython/Modules/posixmodule.c" <<'PY_ATTRIBUTES_TEST'
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
start = source.index("win32_xstat_impl(")
end = source.index("\n}\n", start) + 2
function = source[start:end]
assert (
""" if (fileType != FILE_TYPE_DISK) {
DWORD fileAttributes;
if (fileType == FILE_TYPE_UNKNOWN && GetLastError() != 0) {
retval = -1;
goto cleanup;
}
fileAttributes = GetFileAttributesW(path);
memset(result, 0, sizeof(*result));
if (fileAttributes != INVALID_FILE_ATTRIBUTES &&
fileAttributes & FILE_ATTRIBUTE_DIRECTORY) {""" in function
and function.count("DWORD fileAttributes") == 1
and """# if defined(_MSC_VER) && _MSC_VER < 1300
# ifndef INVALID_FILE_ATTRIBUTES
# define INVALID_FILE_ATTRIBUTES ((DWORD)-1)
# endif
# endif""" in source.split('#include "structmember.h"', 1)[0]
), "fileAttributes needs a C89 declaration, original-point query, and VC6-only SDK sentinel fallback"
PY_ATTRIBUTES_TEST
then
pass "posix fileAttributes preserves query order with C89 declaration and VC6 sentinel"
else
bad "posix fileAttributes preserves query order with C89 declaration and VC6 sentinel"
fi
# VC6 rejects long long; the Windows file offset type must use CPython's alias.
if python3 - "$REPO_ROOT/cpython/Modules/posixmodule.c" <<'PY_OFF_T_TEST'
import re
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
assert re.search(
r"#ifdef MS_WINDOWS\s+typedef\s+PY_LONG_LONG\s+Py_off_t\s*;\s*#else",
source,
), "Windows Py_off_t must use PY_LONG_LONG"
PY_OFF_T_TEST
then
pass "Windows Py_off_t uses PY_LONG_LONG"
else
bad "Windows Py_off_t uses PY_LONG_LONG"
fi
# VC6/Win9x has no reparse points and VC98 lacks the query operation.
if python3 - "$REPO_ROOT/cpython/Modules/posixmodule.c" <<'PY_REPARSE_TEST'
import re
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
start = source.index("win32_get_reparse_tag(HANDLE reparse_point_handle, ULONG *reparse_tag)")
end = source.index("\n}\n", start)
body = source[start:end].split("{\n", 1)[1]
branches = re.fullmatch(
r"#if defined\(_MSC_VER\) && _MSC_VER < 1300\n(.*?)#else\n(.*?)\n#endif",
body, re.S,
)
expected_modern = " char target_buffer[_Py_MAXIMUM_REPARSE_DATA_BUFFER_SIZE];\n _Py_REPARSE_DATA_BUFFER *rdb = (_Py_REPARSE_DATA_BUFFER *)target_buffer;\n DWORD n_bytes_returned;\n\n if (0 == DeviceIoControl(\n reparse_point_handle,\n FSCTL_GET_REPARSE_POINT,\n NULL, 0, /* in buffer */\n target_buffer, sizeof(target_buffer),\n &n_bytes_returned,\n NULL)) /* we're not using OVERLAPPED_IO */\n return FALSE;\n\n if (reparse_tag)\n *reparse_tag = rdb->ReparseTag;\n\n return TRUE;"
assert (
branches is not None
and re.sub(r"/\*.*?\*/", "", branches[1], flags=re.S).strip() == "return FALSE;"
and "FSCTL_GET_REPARSE_POINT" not in branches[1]
and branches[2] == expected_modern
), "VC6 reparse query must return FALSE without FSCTL_GET_REPARSE_POINT and preserve the modern body"
print("ok - VC6 reparse query returns FALSE and preserves the modern body")
PY_REPARSE_TEST
then
pass "posix reparse query excludes the unavailable operation on VC6"
else
bad "posix reparse query excludes the unavailable operation on VC6"
fi
# VC6 requires the UNC flag declaration before statements, but its value must
# still be computed after the final current-directory lookup and error check.
if python3 - "$REPO_ROOT/cpython/Modules/posixmodule.c" <<'PY_WCHDIR_TEST'
import re
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
start = source.index("win32_wchdir(LPCWSTR path)\n{")
end = source.index("\n}\n", start)
body = source[start:end]
opening = body.split(" if(!SetCurrentDirectoryW(path))", 1)[0]
expected_assignment = r''' result = GetCurrentDirectoryW(result, new_path);
if (!result) {
PyMem_RawFree(new_path);
return FALSE;
}
}
is_unc_like_path = (wcsncmp(new_path, L"\\\\", 2) == 0 ||
wcsncmp(new_path, L"//", 2) == 0);
if (!is_unc_like_path) {'''
assert (
" int is_unc_like_path;\n" in opening
and len(re.findall(r"\bint\s+is_unc_like_path\b", body)) == 1
and len(re.findall(r"\bis_unc_like_path\s*=", body)) == 1
and expected_assignment in body
), "win32_wchdir must declare the UNC flag up front and assign it at its original point"
PY_WCHDIR_TEST
then
pass "win32_wchdir preserves C89 UNC declaration and assignment order"
else
bad "win32_wchdir preserves C89 UNC declaration and assignment order"
fi
# VC98 lacks the tag-info type and query API; preserve the modern query verbatim.
if python3 - "$REPO_ROOT/cpython/Modules/posixmodule.c" <<'PY_TAG_INFO_TEST'
import re
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
start = source.index("win32_xstat_impl(const wchar_t *path,")
end = source.index("\n}\n", start)
function = source[start:end]
legacy_guard = "#if defined(_MSC_VER) && _MSC_VER < 1300\n"
modern_guard = "#if !defined(_MSC_VER) || _MSC_VER >= 1300\n"
declaration = re.search(
re.escape(legacy_guard) + r"(.*?)#else\n(.*?)#endif\n", function, re.S,
)
query = re.search(re.escape(modern_guard) + r"(.*?)#endif\n", function, re.S)
expected_query = ' /* Query the reparse tag, and traverse a non-link. */\n if (!traverse) {\n if (!GetFileInformationByHandleEx(hFile, FileAttributeTagInfo,\n &tagInfo, sizeof(tagInfo))) {\n /* Allow devices that do not support FileAttributeTagInfo. */\n switch (GetLastError()) {\n case ERROR_INVALID_PARAMETER:\n case ERROR_INVALID_FUNCTION:\n case ERROR_NOT_SUPPORTED:\n tagInfo.FileAttributes = FILE_ATTRIBUTE_NORMAL;\n tagInfo.ReparseTag = 0;\n break;\n default:\n retval = -1;\n goto cleanup;\n }\n } else if (tagInfo.FileAttributes &\n FILE_ATTRIBUTE_REPARSE_POINT) {\n if (IsReparseTagNameSurrogate(tagInfo.ReparseTag)) {\n if (isUnhandledTag) {\n /* Traversing previously failed for either this link\n or its target. */\n SetLastError(ERROR_CANT_ACCESS_FILE);\n retval = -1;\n goto cleanup;\n }\n /* Traverse a non-link, but not if traversing already failed\n for an unhandled tag. */\n } else if (!isUnhandledTag) {\n CloseHandle(hFile);\n return win32_xstat_impl(path, result, TRUE);\n }\n }\n }\n\n'
vc6 = re.sub(re.escape(modern_guard) + r".*?#endif\n", "", function, flags=re.S)
vc6 = re.sub(
re.escape(legacy_guard) + r"(.*?)#else\n.*?#endif\n",
lambda match: match[1], vc6, flags=re.S,
)
assert (
declaration is not None
and re.sub(r"\s+", " ", declaration[1]).strip()
== "struct { DWORD FileAttributes; DWORD ReparseTag; } tagInfo = { 0 };"
and declaration[2] == " FILE_ATTRIBUTE_TAG_INFO tagInfo = { 0 };\n"
and query is not None and query[1] == expected_query
and not any(name in vc6 for name in (
"FILE_ATTRIBUTE_TAG_INFO", "FileAttributeTagInfo", "GetFileInformationByHandleEx",
))
and "&tagInfo.ReparseTag" in vc6
and "_Py_attribute_data_to_stat(&fileInfo, tagInfo.ReparseTag, result);" in vc6
), "VC6 xstat needs zeroed local tag-info and no unavailable query, preserving the modern path"
PY_TAG_INFO_TEST
then
pass "win32_xstat_impl excludes the tag-info API family on VC6"
else
bad "win32_xstat_impl excludes the tag-info API family on VC6"
fi
# fill_time's complete statement sequence pins C89 declarations and evaluation/cleanup order.
if python3 - "$REPO_ROOT/cpython/Modules/posixmodule.c" <<'PY_FILL_TIME_TEST'
import re
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
start = source.index("fill_time(PyObject *module,")
start = source.index("{\n", start)
end = source.index("\n}\n", start) + 2
body = source[start:end]
expected = """{
int res = -1;
PyObject *s_in_ns = NULL;
PyObject *ns_total = NULL;
PyObject *float_s = NULL;
PyObject *s;
PyObject *ns_fractional;
assert(!PyErr_Occurred());
s = _PyLong_FromTime_t(sec);
ns_fractional = PyLong_FromUnsignedLong(nsec);
if (!(s && ns_fractional)) {
goto exit;
}
s_in_ns = PyNumber_Multiply(s, get_posix_state(module)->billion);
if (!s_in_ns) {
goto exit;
}
ns_total = PyNumber_Add(s_in_ns, ns_fractional);
if (!ns_total)
goto exit;
float_s = PyFloat_FromDouble(sec + 1e-9*nsec);
if (!float_s) {
goto exit;
}
PyStructSequence_SET_ITEM(v, index, s);
PyStructSequence_SET_ITEM(v, index+3, float_s);
PyStructSequence_SET_ITEM(v, index+6, ns_total);
s = NULL;
float_s = NULL;
ns_total = NULL;
assert(!PyErr_Occurred());
res = 0;
exit:
Py_XDECREF(s);
Py_XDECREF(ns_fractional);
Py_XDECREF(s_in_ns);
Py_XDECREF(ns_total);
Py_XDECREF(float_s);
return res;
}"""
# Exact token sequence also rejects any mixed declarations in the body.
assert re.sub(r"\s+", " ", body).strip() == re.sub(r"\s+", " ", expected).strip(), (
"fill_time must declare res, s_in_ns, ns_total, float_s, s, and ns_fractional "
"before assert, without mixed declarations, preserving evaluation and cleanup order"
)
PY_FILL_TIME_TEST
then
pass "fill_time preserves C89 declarations and original evaluation/cleanup order"
else
bad "fill_time preserves C89 declarations and original evaluation/cleanup order"
fi
# The active posixmodule.c VC6/C89 frontier must hoist _pystat_fromstructstat()
# locals before the initial executable assertion while preserving evaluation order.
if python3 - "$REPO_ROOT/cpython/Modules/posixmodule.c" <<'PY'
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
start = source.index("_pystat_fromstructstat(PyObject *module, STRUCT_STAT *st)")
end = source.index("\n}\n\n#ifdef MS_WINDOWS", start) + 2
function = source[start:end]
opening = function[:function.index(" assert(!PyErr_Occurred());")]
for declaration in (" PyObject *StatResultType;\n", " PyObject *v;\n"):
if declaration not in opening:
raise SystemExit("_pystat_fromstructstat declaration missing from opening group")
if " PyObject *StatResultType =" in function or " PyObject *v =" in function:
raise SystemExit("_pystat_fromstructstat retains mixed declaration")
sequence = (
" StatResultType = get_posix_state(module)->StatResultType;\n"
" v = PyStructSequence_New((PyTypeObject *)StatResultType);\n"
)
if sequence not in function:
raise SystemExit("_pystat_fromstructstat assignment sequence changed")
PY
then
pass "_pystat_fromstructstat uses C89 declaration-order-safe locals"
else
bad "_pystat_fromstructstat uses C89 declaration-order-safe locals"
fi
# Nanosecond locals must precede statements without moving branch assignments.
if python3 - "$REPO_ROOT/cpython/Modules/posixmodule.c" <<'PY_STAT_NSEC_TEST'
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
start = source.index("_pystat_fromstructstat(PyObject *module, STRUCT_STAT *st)")
function = source[start:source.index("\n}\n", start) + 2]
opening = """_pystat_fromstructstat(PyObject *module, STRUCT_STAT *st)
{
PyObject *StatResultType;
PyObject *v;
unsigned long ansec, mnsec, cnsec;
assert(!PyErr_Occurred());
"""
sequence = """ SET_ITEM(6, PyLong_FromLongLong(st->st_size));
// Set st_atime, st_mtime and st_ctime
#if defined(HAVE_STAT_TV_NSEC)
ansec = st->st_atim.tv_nsec;
mnsec = st->st_mtim.tv_nsec;
cnsec = st->st_ctim.tv_nsec;
#elif defined(HAVE_STAT_TV_NSEC2)
ansec = st->st_atimespec.tv_nsec;
mnsec = st->st_mtimespec.tv_nsec;
cnsec = st->st_ctimespec.tv_nsec;
#elif defined(HAVE_STAT_NSEC)
ansec = st->st_atime_nsec;
mnsec = st->st_mtime_nsec;
cnsec = st->st_ctime_nsec;
#else
ansec = mnsec = cnsec = 0;
#endif
if (fill_time(module, v, 7, st->st_atime, ansec) < 0) {
goto error;
}
if (fill_time(module, v, 8, st->st_mtime, mnsec) < 0) {
goto error;
}
if (fill_time(module, v, 9, st->st_ctime, cnsec) < 0) {
goto error;
}
"""
assert (function.startswith(opening)
and function.count("unsigned long ansec, mnsec, cnsec;") == 1
and sequence in function), (
"_pystat_fromstructstat nanosecond opening declaration group or original assignment/branch sequence changed"
)
PY_STAT_NSEC_TEST
then
pass "_pystat_fromstructstat nanoseconds preserve C89 declarations and branch assignments"
else
bad "_pystat_fromstructstat nanoseconds preserve C89 declarations and branch assignments"
fi
# The active posixmodule.c VC6/C89 frontier must hoist posix_getcwd()'s
# result local before the initial executable Windows API and error handling.
if python3 - "$REPO_ROOT/cpython/Modules/posixmodule.c" <<'PY'
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
start = source.index("posix_getcwd(int use_bytes)\n{")
end = source.index("\n#else\n const size_t chunk = 1024;", start)
function = source[start:end]
opening = function[:function.index(" Py_BEGIN_ALLOW_THREADS")]
if " PyObject *resobj;\n" not in opening:
raise SystemExit("posix_getcwd resobj declaration missing from opening group")
if " PyObject *resobj = PyUnicode_FromWideChar" in function:
raise SystemExit("posix_getcwd retains mixed resobj declaration")
if function.count(" resobj = PyUnicode_FromWideChar(wbuf2, len);\n") != 1:
raise SystemExit("posix_getcwd resobj assignment missing or duplicated")
sequence = (
" resobj = PyUnicode_FromWideChar(wbuf2, len);\n"
" if (wbuf2 != wbuf) {\n"
" PyMem_RawFree(wbuf2);\n"
" }"
)
if sequence not in function:
raise SystemExit("posix_getcwd result/free sequence changed")
PY
then
pass "posix_getcwd uses a C89 declaration-order-safe resobj local"
else
bad "posix_getcwd uses a C89 declaration-order-safe resobj local"
fi
# Keep os__getfullpathname_impl's result declaration before executable code,
# preserving the original conversion, free, error, and reference handling.
if python3 - "$REPO_ROOT/cpython/Modules/posixmodule.c" <<'PY_FULLPATH_TEST'
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
start = source.index("os__getfullpathname_impl(PyObject *module, path_t *path)")
start = source.index("\n{\n", start)
end = source.index("\n}\n", start) + 3
expected = '''
{
wchar_t *abspath;
PyObject *str;
if (_PyOS_getfullpathname(path->wide, &abspath) < 0) {
return win32_error_object("GetFullPathNameW", path->object);
}
if (abspath == NULL) {
return PyErr_NoMemory();
}
str = PyUnicode_FromWideChar(abspath, wcslen(abspath));
PyMem_RawFree(abspath);
if (str == NULL) {
return NULL;
}
if (path->narrow) {
Py_SETREF(str, PyUnicode_EncodeFSDefault(str));
}
return str;
}
'''
assert source[start:end] == expected, (
"os__getfullpathname_impl must hoist str and preserve conversion/free/error/refcount order"
)
PY_FULLPATH_TEST
then
pass "os__getfullpathname_impl uses a C89 declaration-order-safe str local"
else
bad "os__getfullpathname_impl uses a C89 declaration-order-safe str local"
fi
# VC98 lacks the DOS-volume flag; modern SDK definitions and the call stay intact.
if python3 - "$REPO_ROOT/cpython/Modules/posixmodule.c" <<'PY_VOLUME_NAME_DOS_TEST'
import re
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
fallback = """# if defined(_MSC_VER) && _MSC_VER < 1300
# ifndef VOLUME_NAME_DOS
# define VOLUME_NAME_DOS 0x0
# endif
# endif"""
headers = source.split('#include "structmember.h"', 1)[0]
assert fallback in headers, "VOLUME_NAME_DOS needs a VC6-only ifndef fallback with value 0x0"
assert len(re.findall(r"^\s*#\s*define\s+VOLUME_NAME_DOS\b", source, re.M)) == 1, (
"modern SDK VOLUME_NAME_DOS definitions must remain untouched"
)
assert """ result_length = GetFinalPathNameByHandleW(hFile, target_path,
buf_size, VOLUME_NAME_DOS);""" in source, (
"the final-path query must retain its original DOS-volume flag"
)
PY_VOLUME_NAME_DOS_TEST
then
pass "posix VOLUME_NAME_DOS fallback is VC6-only and preserves the modern path"
else
bad "posix VOLUME_NAME_DOS fallback is VC6-only and preserves the modern path"
fi
# Keep the final-path realloc temporary in the opening C89 declaration group.
if python3 - "$REPO_ROOT/cpython/Modules/posixmodule.c" <<'PY_FINALPATH_TMP_TEST'
import re
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
start = source.index("os__getfinalpathname_impl(PyObject *module, path_t *path)")
start = source.index("\n{\n", start)
end = source.index("\n}\n", start) + 3
function = source[start:end]
opening, body = function.split(" Py_BEGIN_ALLOW_THREADS", 1)
sequence = """ tmp = PyMem_Realloc(target_path != buf ? target_path : NULL,
result_length * sizeof(*tmp));
if (!tmp) {
result = PyErr_NoMemory();
goto cleanup;
}
buf_size = result_length;
target_path = tmp;"""
assert (
opening.count(" wchar_t *tmp;\n") == 1
and len(re.findall(r"\bwchar_t\s*\*\s*tmp\b", function)) == 1
and not re.search(r"\bwchar_t\s*\*\s*tmp\b", body)
and body.count(sequence) == 1
), "os__getfinalpathname_impl must hoist tmp and preserve realloc/NULL-error/size/target order"
PY_FINALPATH_TMP_TEST
then
pass "os__getfinalpathname_impl uses a C89 declaration-order-safe tmp local"
else
bad "os__getfinalpathname_impl uses a C89 declaration-order-safe tmp local"
fi
# Keep the split-root slash iterator in the opening C89 declaration group.
if python3 - "$REPO_ROOT/cpython/Modules/posixmodule.c" <<'PY_SPLITROOT_P_TEST'
import re
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
start = source.index("os__path_splitroot_impl(PyObject *module, path_t *path)")
start = source.index("\n{\n", start)
end = source.index("\n}\n", start) + 3
function = source[start:end]
opening, body = function.split(" buffer =", 1)
sequence = r""" wcscpy(buffer, path->wide);
for (p = wcschr(buffer, L'/'); p; p = wcschr(p, L'/')) {
*p = L'\\';
}"""
assert (
opening.count(" wchar_t *p;\n") == 1
and len(re.findall(r"\bwchar_t\s*\*\s*p\b", function)) == 1
and body.count(sequence) == 1
), "os__path_splitroot_impl must hoist p and preserve copy/search/replace order"
PY_SPLITROOT_P_TEST
then
pass "os__path_splitroot_impl uses a C89 declaration-order-safe p local"
else
bad "os__path_splitroot_impl uses a C89 declaration-order-safe p local"
fi
# Keep normpath locals in the opening C89 declaration group, with all
# evaluations, error returns, and cleanup at their original points.
if python3 - "$REPO_ROOT/cpython/Modules/posixmodule.c" <<'PY_NORMPATH_TEST'
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
start = source.index("os__path_normpath_impl(PyObject *module, PyObject *path)")
start = source.index("\n{\n", start)
end = source.index("\n}\n", start) + 3
expected = """
{
Py_ssize_t len;
wchar_t *buffer;
Py_ssize_t norm_len;
wchar_t *norm_path;
PyObject *result;
if (!PyUnicode_Check(path)) {
PyErr_Format(PyExc_TypeError, "expected 'str', not '%.200s'",
Py_TYPE(path)->tp_name);
return NULL;
}
buffer = PyUnicode_AsWideCharString(path, &len);
if (!buffer) {
return NULL;
}
norm_path = _Py_normpath_and_size(buffer, len, &norm_len);
result = PyUnicode_FromWideChar(norm_path, norm_len);
PyMem_Free(buffer);
return result;
}
"""
assert source[start:end] == expected, (
"os__path_normpath_impl must use an opening C89 declaration group and "
"preserve type-check/allocation/NULL-error/normalization/result/free/return order"
)
PY_NORMPATH_TEST
then
pass "os__path_normpath_impl preserves C89 declarations and evaluation/cleanup order"
else
bad "os__path_normpath_impl preserves C89 declarations and evaluation/cleanup order"
fi
# Keep the current posixmodule.c VC6 frontier explicit: the VC98 SDK lacks
# the reparse-tag constants used by Py_DeleteFileW().
if python3 - "$REPO_ROOT/cpython/Modules/posixmodule.c" <<'PY'
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
start = source.index("#ifdef MS_WINDOWS")
end = source.index("#endif /* MS_WINDOWS */", start)
header = source[start:end]
if "# if defined(_MSC_VER) && _MSC_VER < 1300" not in header:
raise SystemExit("VC6 reparse-tag fallback guard missing")
if "# define IO_REPARSE_TAG_SYMLINK 0xA000000C" not in header:
raise SystemExit("VC6 symlink reparse-tag fallback missing")
if "# define IO_REPARSE_TAG_MOUNT_POINT 0xA0000003" not in header:
raise SystemExit("VC6 mount-point reparse-tag fallback missing")
if "find_data.dwReserved0 == IO_REPARSE_TAG_SYMLINK" not in source:
raise SystemExit("Py_DeleteFileW symlink tag check missing")
if "find_data.dwReserved0 == IO_REPARSE_TAG_MOUNT_POINT" not in source:
raise SystemExit("Py_DeleteFileW mount-point tag check missing")
PY
then
pass "posixmodule provides VC6 reparse-tag fallbacks and preserves Py_DeleteFileW checks"
else
bad "posixmodule provides VC6 reparse-tag fallbacks and preserves Py_DeleteFileW checks"
fi
# Keep environment-loop locals C89-compatible without moving evaluations or cleanup.
if python3 - "$REPO_ROOT/cpython/Modules/posixmodule.c" <<'PY_ENVLIST_TEST'
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
start = source.index("parse_envlist(PyObject* env, Py_ssize_t *envc_ptr)")
start = source.index("\n{\n", start)
end = source.index("\n}\n", start) + 3
expected = r"""
{
Py_ssize_t i, pos, envc;
PyObject *keys=NULL, *vals=NULL;
PyObject *key2, *val2, *keyval;
PyObject *key, *val;
EXECV_CHAR **envlist;
i = PyMapping_Size(env);
if (i < 0)
return NULL;
envlist = PyMem_NEW(EXECV_CHAR *, i + 1);
if (envlist == NULL) {
PyErr_NoMemory();
return NULL;
}
envc = 0;
keys = PyMapping_Keys(env);
if (!keys)
goto error;
vals = PyMapping_Values(env);
if (!vals)
goto error;
if (!PyList_Check(keys) || !PyList_Check(vals)) {
PyErr_Format(PyExc_TypeError,
"env.keys() or env.values() is not a list");
goto error;
}
for (pos = 0; pos < i; pos++) {
key = PyList_GetItem(keys, pos); // Borrowed ref.
if (key == NULL) {
goto error;
}
val = PyList_GetItem(vals, pos); // Borrowed ref.
if (val == NULL) {
goto error;
}
#if defined(HAVE_WEXECV) || defined(HAVE_WSPAWNV)
if (!PyUnicode_FSDecoder(key, &key2))
goto error;
if (!PyUnicode_FSDecoder(val, &val2)) {
Py_DECREF(key2);
goto error;
}
/* Search from index 1 because on Windows starting '=' is allowed for
defining hidden environment variables. */
if (PyUnicode_GET_LENGTH(key2) == 0 ||
PyUnicode_FindChar(key2, '=', 1, PyUnicode_GET_LENGTH(key2), 1) != -1)
{
PyErr_SetString(PyExc_ValueError, "illegal environment variable name");
Py_DECREF(key2);
Py_DECREF(val2);
goto error;
}
keyval = PyUnicode_FromFormat("%U=%U", key2, val2);
#else
if (!PyUnicode_FSConverter(key, &key2))
goto error;
if (!PyUnicode_FSConverter(val, &val2)) {
Py_DECREF(key2);
goto error;
}
if (PyBytes_GET_SIZE(key2) == 0 ||
strchr(PyBytes_AS_STRING(key2) + 1, '=') != NULL)
{
PyErr_SetString(PyExc_ValueError, "illegal environment variable name");
Py_DECREF(key2);
Py_DECREF(val2);
goto error;
}
keyval = PyBytes_FromFormat("%s=%s", PyBytes_AS_STRING(key2),
PyBytes_AS_STRING(val2));
#endif
Py_DECREF(key2);
Py_DECREF(val2);
if (!keyval)
goto error;
if (!fsconvert_strdup(keyval, &envlist[envc++])) {
Py_DECREF(keyval);
goto error;
}
Py_DECREF(keyval);
}
Py_DECREF(vals);
Py_DECREF(keys);
envlist[envc] = 0;
*envc_ptr = envc;
return envlist;
error:
Py_XDECREF(keys);
Py_XDECREF(vals);
free_string_array(envlist, envc);
return NULL;
}
"""
assert source[start:end] == expected, (
"parse_envlist must hoist key and val into the C89 declaration group and "
"preserve PyList_GetItem/evaluation/error/cleanup order"
)
PY_ENVLIST_TEST
then
pass "parse_envlist preserves C89 key/val declarations and evaluation/error/cleanup order"
else
bad "parse_envlist preserves C89 key/val declarations and evaluation/error/cleanup order"
fi
# Keep os_kill_impl declarations before the audit without moving evaluations.
if python3 - "$REPO_ROOT/cpython/Modules/posixmodule.c" <<'PY_OS_KILL_TEST'
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
start = source.index("os_kill_impl(PyObject *module, pid_t pid, Py_ssize_t signal)")
start = source.index("\n{\n", start)
end = source.index("\n}\n", start) + 3
expected = """
{
#ifdef MS_WINDOWS
PyObject *result;
DWORD sig;
DWORD err;
HANDLE handle;
#endif
if (PySys_Audit("os.kill", "in", pid, signal) < 0) {
return NULL;
}
#ifndef MS_WINDOWS
if (kill(pid, (int)signal) == -1) {
return posix_error();
}
// Check immediately if the signal was sent to the current process.
// Don't micro-optimize pid == getpid(), since PyErr_SetString() check
// is cheap.
if (PyErr_CheckSignals()) {
return NULL;
}
Py_RETURN_NONE;
#else /* !MS_WINDOWS */
sig = (DWORD)signal;
/* Console processes which share a common console can be sent CTRL+C or
CTRL+BREAK events, provided they handle said events. */
if (sig == CTRL_C_EVENT || sig == CTRL_BREAK_EVENT) {
if (GenerateConsoleCtrlEvent(sig, (DWORD)pid) == 0) {
err = GetLastError();
PyErr_SetFromWindowsErr(err);
}
else
Py_RETURN_NONE;
}
/* If the signal is outside of what GenerateConsoleCtrlEvent can use,
attempt to open and terminate the process. */
handle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, (DWORD)pid);
if (handle == NULL) {
err = GetLastError();
return PyErr_SetFromWindowsErr(err);
}
if (TerminateProcess(handle, sig) == 0) {
err = GetLastError();
result = PyErr_SetFromWindowsErr(err);
} else {
Py_INCREF(Py_None);
result = Py_None;
}
CloseHandle(handle);
return result;
#endif /* !MS_WINDOWS */
}
"""
assert source[start:end] == expected, (
"os_kill_impl must hoist Windows declarations before the audit and preserve "
"audit/signal/API/error/cleanup order"
)
PY_OS_KILL_TEST
then
pass "os_kill_impl preserves C89 declarations and evaluation/error/cleanup order"
else
bad "os_kill_impl preserves C89 declarations and evaluation/error/cleanup order"
fi
# Keep the current posixmodule.c VC6 frontier explicit: the VC98 SDK has no
# FSCTL_GET_REPARSE_POINT, so os_readlink_impl must be unavailable on VC6
# rather than compiling the NT reparse-point implementation.
if python3 - "$REPO_ROOT/cpython/Modules/posixmodule.c" <<'PY'
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
start = source.index("os_readlink_impl(PyObject *module, path_t *path, int dir_fd)")
end = source.index("\n#endif /* defined(HAVE_READLINK) || defined(MS_WINDOWS) */", start)
function = source[start:end]
expected = """#if defined(_MSC_VER) && _MSC_VER < 1300
PyErr_SetString(PyExc_NotImplementedError,
\"os.readlink is unavailable on this Windows build\");
return NULL;
#else
"""
if expected not in function:
raise SystemExit("VC6 os_readlink fallback missing")
if "FSCTL_GET_REPARSE_POINT" not in function:
raise SystemExit("modern os_readlink reparse implementation missing")
if function.count("#endif") < 1:
raise SystemExit("os_readlink VC6 guard is incomplete")
PY
then
pass "os_readlink excludes the unavailable VC6 reparse API and preserves the modern implementation"
else
bad "os_readlink excludes the unavailable VC6 reparse API and preserves the modern implementation"
fi
# Windows 9x has neither AddDllDirectory nor RemoveDllDirectory. The VC6
# implementations must report this explicitly while retaining the modern API.
if python3 - "$REPO_ROOT/cpython/Modules/posixmodule.c" <<'PY_DLL_DIRECTORY_TEST'
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
add_start = source.index("os__add_dll_directory_impl(PyObject *module, path_t *path)")
add_start = source.index("\n{\n", add_start)
add_end = source.index("\n}\n", add_start) + 3
add = source[add_start:add_end]
remove_start = source.index("os__remove_dll_directory_impl(PyObject *module, PyObject *cookie)")
remove_start = source.index("\n{\n", remove_start)
remove_end = source.index("\n}\n", remove_start) + 3
remove = source[remove_start:remove_end]
fallback = """#if defined(_MSC_VER) && _MSC_VER < 1300
PyErr_SetString(PyExc_NotImplementedError,
\"DLL directory management is unavailable on this Windows build\");
return NULL;
#else
"""
for name, function in (("add", add), ("remove", remove)):
if fallback not in function:
raise SystemExit(f"VC6 os.{name}_dll_directory fallback missing")
if "#endif" not in function:
raise SystemExit(f"os.{name}_dll_directory modern branch not preserved")
if "typedef DLL_DIRECTORY_COOKIE" not in source:
raise SystemExit("modern DLL_DIRECTORY_COOKIE typedefs missing")
PY_DLL_DIRECTORY_TEST
then
pass "DLL directory operations reject unsupported VC6 APIs and preserve modern implementation"
else
bad "DLL directory operations reject unsupported VC6 APIs and preserve modern implementation"
fi
# The VC98 SDK lacks the directory flag used by Windows symlink creation.
if python3 - "$REPO_ROOT/cpython/Modules/posixmodule.c" <<'PY'
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
start = source.index("#ifdef MS_WINDOWS\n# include <aclapi.h>")
end = source.index('\n#include "structmember.h"', start)
expected = """# if defined(_MSC_VER) && _MSC_VER < 1300
# ifndef SYMBOLIC_LINK_FLAG_DIRECTORY
# define SYMBOLIC_LINK_FLAG_DIRECTORY 0x1
# endif
# endif
"""
assert expected in source[start:end], (
"missing Windows-only VC6 SYMBOLIC_LINK_FLAG_DIRECTORY fallback (0x1), "
"guarded to preserve SDK definitions"
)
PY
then
pass "posixmodule provides a guarded VC6 symlink directory flag fallback"
else
bad "posixmodule provides a guarded VC6 symlink directory flag fallback"
fi
# Keep win32_putenv locals in the opening C89 declaration group while
# preserving every evaluation, cleanup, error, and return in the body.
if python3 - "$REPO_ROOT/cpython/Modules/posixmodule.c" <<'PY_WIN32_PUTENV_TEST'
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
start = source.index("win32_putenv(PyObject *name, PyObject *value)")
start = source.index("\n{\n", start)
end = source.index("\n}\n", start) + 3
expected = """
{
PyObject *unicode;
Py_ssize_t size;
wchar_t *env;
int err;
/* Search from index 1 because on Windows starting '=' is allowed for
defining hidden environment variables. */
if (PyUnicode_GET_LENGTH(name) == 0 ||
PyUnicode_FindChar(name, '=', 1, PyUnicode_GET_LENGTH(name), 1) != -1)
{
PyErr_SetString(PyExc_ValueError, "illegal environment variable name");
return NULL;
}
if (value != NULL) {
unicode = PyUnicode_FromFormat("%U=%U", name, value);
}
else {
unicode = PyUnicode_FromFormat("%U=", name);
}
if (unicode == NULL) {
return NULL;
}
env = PyUnicode_AsWideCharString(unicode, &size);
Py_DECREF(unicode);
if (env == NULL) {
return NULL;
}
if (size > _MAX_ENV) {
PyErr_Format(PyExc_ValueError,
"the environment variable is longer than %u characters",
_MAX_ENV);
PyMem_Free(env);
return NULL;
}
if (wcslen(env) != (size_t)size) {
PyErr_SetString(PyExc_ValueError,
"embedded null character");
PyMem_Free(env);
return NULL;
}
/* _wputenv() and SetEnvironmentVariableW() update the environment in the
Process Environment Block (PEB). _wputenv() also updates CRT 'environ'
and '_wenviron' variables, whereas SetEnvironmentVariableW() does not.
Prefer _wputenv() to be compatible with C libraries using CRT
variables and CRT functions using these variables (ex: getenv()). */
err = _wputenv(env);
if (err) {
posix_error();
PyMem_Free(env);
return NULL;
}
PyMem_Free(env);
Py_RETURN_NONE;
}
"""
assert source[start:end] == expected, (
"win32_putenv must hoist unicode/size/env/err into the opening C89 declaration "
"group and preserve evaluation/cleanup/error/return order"
)
PY_WIN32_PUTENV_TEST
then
pass "win32_putenv preserves C89 declarations and evaluation/cleanup/error/return order"
else
bad "win32_putenv preserves C89 declarations and evaluation/cleanup/error/return order"
fi
# The active posixmodule.c VC6 frontier must provide the VC98 CRT's missing
# _MAX_ENV limit without overriding newer SDK definitions.
if python3 - "$REPO_ROOT/cpython/Modules/posixmodule.c" <<'PY'
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
expected = """# if defined(_MSC_VER) && _MSC_VER < 1300
# ifndef _MAX_ENV
# define _MAX_ENV 32767
# endif
# endif"""
if expected not in source:
raise SystemExit("VC6 _MAX_ENV fallback missing")
PY
then
pass "posixmodule provides a guarded VC6 _MAX_ENV fallback"
else
bad "posixmodule provides a guarded VC6 _MAX_ENV fallback"
fi
# Keep terminal-size declarations C89-compatible and preserve the complete body order.
if python3 - "$REPO_ROOT/cpython/Modules/posixmodule.c" <<'PY_TERMSIZE_TEST'
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
start = source.index("os_get_terminal_size_impl(PyObject *module, int fd)")
start = source.index("\n{\n", start)
end = source.index("\n}\n", start) + 3
expected = r"""
{
int columns, lines;
PyObject *termsize;
PyObject *TerminalSizeType;
int pos;
/* Under some conditions stdout may not be connected and
* fileno(stdout) may point to an invalid file descriptor. For example
* GUI apps don't have valid standard streams by default.
*
* If this happens, and the optional fd argument is not present,
* the ioctl below will fail returning EBADF. This is what we want.
*/
#ifdef TERMSIZE_USE_IOCTL
{
struct winsize w;
if (ioctl(fd, TIOCGWINSZ, &w))
return PyErr_SetFromErrno(PyExc_OSError);
columns = w.ws_col;
lines = w.ws_row;
}
#endif /* TERMSIZE_USE_IOCTL */
#ifdef TERMSIZE_USE_CONIO
{
DWORD nhandle;
HANDLE handle;
CONSOLE_SCREEN_BUFFER_INFO csbi;
switch (fd) {
case 0: nhandle = STD_INPUT_HANDLE;
break;
case 1: nhandle = STD_OUTPUT_HANDLE;
break;
case 2: nhandle = STD_ERROR_HANDLE;
break;
default:
return PyErr_Format(PyExc_ValueError, "bad file descriptor");
}
handle = GetStdHandle(nhandle);
if (handle == NULL)
return PyErr_Format(PyExc_OSError, "handle cannot be retrieved");
if (handle == INVALID_HANDLE_VALUE)
return PyErr_SetFromWindowsErr(0);
if (!GetConsoleScreenBufferInfo(handle, &csbi))
return PyErr_SetFromWindowsErr(0);
columns = csbi.srWindow.Right - csbi.srWindow.Left + 1;
lines = csbi.srWindow.Bottom - csbi.srWindow.Top + 1;
}
#endif /* TERMSIZE_USE_CONIO */
TerminalSizeType = get_posix_state(module)->TerminalSizeType;
termsize = PyStructSequence_New((PyTypeObject *)TerminalSizeType);
if (termsize == NULL)
return NULL;
pos = 0;
#define SET_TERMSIZE(CALL) \
do { \
PyObject *item = (CALL); \
if (item == NULL) { \
Py_DECREF(termsize); \
return NULL; \
} \
PyStructSequence_SET_ITEM(termsize, pos++, item); \
} while(0)
SET_TERMSIZE(PyLong_FromLong(columns));
SET_TERMSIZE(PyLong_FromLong(lines));
#undef SET_TERMSIZE
return termsize;
}
"""
assert source[start:end] == expected, (
"os_get_terminal_size_impl must declare TerminalSizeType/pos in the opening "
"C89 group and preserve assignments/SET_TERMSIZE/error/return order"
)
PY_TERMSIZE_TEST
then
pass "terminal size preserves C89 declarations and evaluation/error/return order"
else
bad "terminal size preserves C89 declarations and evaluation/error/return order"
fi
# VC6 targets pre-processor-group Windows; retain the modern all-group query.
if python3 - "$REPO_ROOT/cpython/Modules/posixmodule.c" <<'PY_CPU_COUNT_TEST'
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
start = source.index("os_cpu_count_impl(PyObject *module)")
start = source.index("\n{\n", start)
end = source.index("\n}\n", start) + 3
function = source[start:end]
expected = """ int ncpu = 0;
#ifdef MS_WINDOWS
# if defined(_MSC_VER) && _MSC_VER < 1300
/* Win9x has no processor groups or GetActiveProcessorCount. */
SYSTEM_INFO sysinfo;
GetSystemInfo(&sysinfo);
ncpu = sysinfo.dwNumberOfProcessors;
# else
ncpu = GetActiveProcessorCount(ALL_PROCESSOR_GROUPS);
# endif
#elif defined(__hpux)"""
assert (
expected in function
and function.endswith(""" if (ncpu >= 1)
return PyLong_FromLong(ncpu);
else
Py_RETURN_NONE;
}
""")
), "os_cpu_count must use VC6-only GetSystemInfo and preserve modern all-group query/count-or-None"
PY_CPU_COUNT_TEST
then
pass "os_cpu_count preserves VC6 system count and modern all-group query"
else
bad "os_cpu_count preserves VC6 system count and modern all-group query"
fi
# DirEntry_dealloc must declare free_func before its first executable DECREF.
if python3 - "$REPO_ROOT/cpython/Modules/posixmodule.c" <<'PY_DIRENTRY_FREE_TEST'
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
start = source.index("DirEntry_dealloc(DirEntry *entry)")
end = source.index("\n}\n", start) + 3
function = source[start:end]
expected = """DirEntry_dealloc(DirEntry *entry)
{
PyTypeObject *tp = Py_TYPE(entry);
freefunc free_func;
Py_XDECREF(entry->name);
Py_XDECREF(entry->path);
Py_XDECREF(entry->stat);
Py_XDECREF(entry->lstat);
free_func = PyType_GetSlot(tp, Py_tp_free);
free_func(entry);
Py_DECREF(tp);
}
"""
assert source[start:end] == expected, (
"DirEntry_dealloc must hoist freefunc free_func before executable statements "
"and preserve cleanup/call order"
)
PY_DIRENTRY_FREE_TEST
then
pass "DirEntry_dealloc preserves C89 declaration and cleanup order"
else
bad "DirEntry_dealloc preserves C89 declaration and cleanup order"
fi
# ScandirIterator_dealloc must keep its slot callback declaration C89-safe.
if python3 - "$REPO_ROOT/cpython/Modules/posixmodule.c" <<'PY_SCANDIR_FREE_TEST'
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
start = source.index("ScandirIterator_dealloc(ScandirIterator *iterator)")
end = source.index("\n}\n", start) + 3
expected = """ScandirIterator_dealloc(ScandirIterator *iterator)
{
PyTypeObject *tp = Py_TYPE(iterator);
freefunc free_func;
if (PyObject_CallFinalizerFromDealloc((PyObject *)iterator) < 0)
return;
free_func = PyType_GetSlot(tp, Py_tp_free);
free_func(iterator);
Py_DECREF(tp);
}
"""
assert source[start:end] == expected, (
"ScandirIterator_dealloc must declare free_func before statements "
"and preserve finalizer/free/decref order"
)
PY_SCANDIR_FREE_TEST
then
pass "ScandirIterator_dealloc preserves C89 declaration and finalizer/free order"
else
bad "ScandirIterator_dealloc preserves C89 declaration and finalizer/free order"
fi
# os_scandir_impl must declare the iterator type before executable statements.
if python3 - "$REPO_ROOT/cpython/Modules/posixmodule.c" <<'PY_SCANDIR_IMPL_TEST'
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
start = source.index("os_scandir_impl(PyObject *module, path_t *path)")
end = source.index("\n}\n", start) + 3
function = source[start:end]
expected = """os_scandir_impl(PyObject *module, path_t *path)
/*[clinic end generated code: output=6eb2668b675ca89e input=6bdd312708fc3bb0]*/
{
ScandirIterator *iterator;
PyObject *ScandirIteratorType;
#ifdef MS_WINDOWS
wchar_t *path_strW;
#else
const char *path_str;
#ifdef HAVE_FDOPENDIR
int fd = -1;
#endif
#endif
if (PySys_Audit("os.scandir", "O",
path->object ? path->object : Py_None) < 0) {
return NULL;
}
ScandirIteratorType = get_posix_state(module)->ScandirIteratorType;
"""
assert function.startswith(expected), (
"os_scandir_impl must hoist ScandirIteratorType into its opening C89 "
"declaration group and preserve audit-before-type-lookup order"
)
PY_SCANDIR_IMPL_TEST
then
pass "os_scandir_impl preserves C89 declaration and audit/type lookup order"
else
bad "os_scandir_impl preserves C89 declaration and audit/type lookup order"
fi
# DirEntry_fetch_stat must preserve path conversion/stat/cleanup order in C89.
if python3 - "$REPO_ROOT/cpython/Modules/posixmodule.c" <<'PY_DIRENTRY_STAT_TEST'
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
start = source.index("DirEntry_fetch_stat(PyObject *module, DirEntry *self, int follow_symlinks)")
end = source.index("\n}\n", start) + 3
expected = """DirEntry_fetch_stat(PyObject *module, DirEntry *self, int follow_symlinks)
{
int result;
int saved_errno;
STRUCT_STAT st;
PyObject *ub;
#ifdef MS_WINDOWS
#if USE_UNICODE_WCHAR_CACHE
const wchar_t *path;
#else /* USE_UNICODE_WCHAR_CACHE */
wchar_t *path;
#endif /* USE_UNICODE_WCHAR_CACHE */
#else /* POSIX */
const char *path;
#endif
#ifdef MS_WINDOWS
if (!PyUnicode_FSDecoder(self->path, &ub))
return NULL;
#if USE_UNICODE_WCHAR_CACHE
_Py_COMP_DIAG_PUSH
_Py_COMP_DIAG_IGNORE_DEPR_DECLS
path = PyUnicode_AsUnicode(ub);
_Py_COMP_DIAG_POP
#else /* USE_UNICODE_WCHAR_CACHE */
path = PyUnicode_AsWideCharString(ub, NULL);
Py_DECREF(ub);
#endif /* USE_UNICODE_WCHAR_CACHE */
#else /* POSIX */
if (!PyUnicode_FSConverter(self->path, &ub))
return NULL;
path = PyBytes_AS_STRING(ub);
if (self->dir_fd != DEFAULT_DIR_FD) {
#ifdef HAVE_FSTATAT
if (HAVE_FSTATAT_RUNTIME) {
Py_BEGIN_ALLOW_THREADS
result = fstatat(self->dir_fd, path, &st,
follow_symlinks ? 0 : AT_SYMLINK_NOFOLLOW);
Py_END_ALLOW_THREADS
} else
#endif /* HAVE_FSTATAT */
{
Py_DECREF(ub);
PyErr_SetString(PyExc_NotImplementedError, "can't fetch stat");
return NULL;
}
}
else
#endif
{
Py_BEGIN_ALLOW_THREADS
if (follow_symlinks) {
result = STAT(path, &st);
}
else {
result = LSTAT(path, &st);
}
Py_END_ALLOW_THREADS
}
saved_errno = errno;
#if defined(MS_WINDOWS) && !USE_UNICODE_WCHAR_CACHE
PyMem_Free(path);
#else /* USE_UNICODE_WCHAR_CACHE */
Py_DECREF(ub);
#endif /* USE_UNICODE_WCHAR_CACHE */
if (result != 0) {
errno = saved_errno;
path_object_error(self->path);
return NULL;
}
return _pystat_fromstructstat(module, &st);
}
"""
assert source[start:end] == expected, (
"DirEntry_fetch_stat must hoist all path variants and saved_errno into the "
"opening C89 declaration group and preserve conversion/stat/errno/cleanup order"
)
PY_DIRENTRY_STAT_TEST
then
pass "DirEntry_fetch_stat preserves C89 declarations and conversion/stat/cleanup order"
else
bad "DirEntry_fetch_stat preserves C89 declarations and conversion/stat/cleanup order"
fi
# DirEntry_test_mode must preserve stat/error/state-lookup order with C89 locals.
if python3 - "$REPO_ROOT/cpython/Modules/posixmodule.c" <<'PY_DIRENTRY_MODE_TEST'
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
start = source.index("DirEntry_test_mode(PyTypeObject *defining_class, DirEntry *self,",
source.index("/* Set exception and return -1 on error, 0 for False, 1 for True */"))
end = source.index("\n}\n", start) + 3
expected = """DirEntry_test_mode(PyTypeObject *defining_class, DirEntry *self,
int follow_symlinks, unsigned short mode_bits)
{
PyObject *stat = NULL;
PyObject *st_mode = NULL;
long mode;
int result;
_posixstate *state;
#if defined(MS_WINDOWS) || defined(HAVE_DIRENT_D_TYPE)
int is_symlink;
int need_stat;
#endif
#ifdef MS_WINDOWS
unsigned long dir_bits;
#endif
#ifdef MS_WINDOWS
is_symlink = (self->win32_lstat.st_mode & S_IFMT) == S_IFLNK;
need_stat = follow_symlinks && is_symlink;
#elif defined(HAVE_DIRENT_D_TYPE)
is_symlink = self->d_type == DT_LNK;
need_stat = self->d_type == DT_UNKNOWN || (follow_symlinks && is_symlink);
#endif
#if defined(MS_WINDOWS) || defined(HAVE_DIRENT_D_TYPE)
if (need_stat) {
#endif
stat = os_DirEntry_stat_impl(self, defining_class, follow_symlinks);
if (!stat) {
if (PyErr_ExceptionMatches(PyExc_FileNotFoundError)) {
/* If file doesn't exist (anymore), then return False
(i.e., say it's not a file/directory) */
PyErr_Clear();
return 0;
}
goto error;
}
state = get_posix_state(PyType_GetModule(defining_class));
st_mode = PyObject_GetAttr(stat, state->st_mode);
if (!st_mode)
goto error;
mode = PyLong_AsLong(st_mode);
if (mode == -1 && PyErr_Occurred())
goto error;
Py_CLEAR(st_mode);
Py_CLEAR(stat);
result = (mode & S_IFMT) == mode_bits;
#if defined(MS_WINDOWS) || defined(HAVE_DIRENT_D_TYPE)
}
else if (is_symlink) {
assert(mode_bits != S_IFLNK);
result = 0;
}
else {
assert(mode_bits == S_IFDIR || mode_bits == S_IFREG);
#ifdef MS_WINDOWS
dir_bits = self->win32_lstat.st_file_attributes & FILE_ATTRIBUTE_DIRECTORY;
if (mode_bits == S_IFDIR)
result = dir_bits != 0;
else
result = dir_bits == 0;
#else /* POSIX */
if (mode_bits == S_IFDIR)
result = self->d_type == DT_DIR;
else
result = self->d_type == DT_REG;
#endif
}
#endif
return result;
error:
Py_XDECREF(st_mode);
Py_XDECREF(stat);
return -1;
}
"""
assert source[start:end] == expected, (
"DirEntry_test_mode must hoist state into the opening C89 declaration group "
"and preserve stat/error/state-lookup order"
)
PY_DIRENTRY_MODE_TEST
then
pass "DirEntry_test_mode preserves C89 declarations and stat/error/state-lookup order"
else
bad "DirEntry_test_mode preserves C89 declarations and stat/error/state-lookup order"
fi
# DirEntry.inode must keep Windows locals C89-safe and preserve both branch orders.
if python3 - "$REPO_ROOT/cpython/Modules/posixmodule.c" <<'PY_DIRENTRY_INODE_TEST'
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
start = source.index("os_DirEntry_inode_impl(DirEntry *self)")
start = source.index("\n{\n", start)
end = source.index("\n}\n", start) + 3
# Both platform branches must use CPython's VC6-compatible unsigned type.
assert all(
f"static_assert(sizeof(unsigned PY_LONG_LONG) >= sizeof(self->{field}),"
in source[start:end]
for field in ("win32_file_index", "d_ino")
), "DirEntry.inode static_asserts must both use unsigned PY_LONG_LONG"
expected = """
{
#ifdef MS_WINDOWS
if (!self->got_file_index) {
PyObject *unicode;
STRUCT_STAT stat;
int result;
int saved_errno;
#if USE_UNICODE_WCHAR_CACHE
const wchar_t *path;
#else /* USE_UNICODE_WCHAR_CACHE */
wchar_t *path;
#endif /* USE_UNICODE_WCHAR_CACHE */
if (!PyUnicode_FSDecoder(self->path, &unicode))
return NULL;
#if USE_UNICODE_WCHAR_CACHE
_Py_COMP_DIAG_PUSH
_Py_COMP_DIAG_IGNORE_DEPR_DECLS
path = PyUnicode_AsUnicode(unicode);
result = LSTAT(path, &stat);
saved_errno = errno;
Py_DECREF(unicode);
_Py_COMP_DIAG_POP
#else /* USE_UNICODE_WCHAR_CACHE */
path = PyUnicode_AsWideCharString(unicode, NULL);
Py_DECREF(unicode);
result = LSTAT(path, &stat);
saved_errno = errno;
PyMem_Free(path);
#endif /* USE_UNICODE_WCHAR_CACHE */
if (result != 0) {
errno = saved_errno;
return path_object_error(self->path);
}
self->win32_file_index = stat.st_ino;
self->got_file_index = 1;
}
static_assert(sizeof(unsigned PY_LONG_LONG) >= sizeof(self->win32_file_index),
"DirEntry.win32_file_index is larger than unsigned long long");
return PyLong_FromUnsignedLongLong(self->win32_file_index);
#else /* POSIX */
static_assert(sizeof(unsigned PY_LONG_LONG) >= sizeof(self->d_ino),
"DirEntry.d_ino is larger than unsigned long long");
return PyLong_FromUnsignedLongLong(self->d_ino);
#endif
}
"""
assert source[start:end] == expected, (
"os_DirEntry_inode_impl must hoist branch-typed path and saved_errno before "
"decoder execution and preserve decoder/path/LSTAT/errno/cleanup order"
)
PY_DIRENTRY_INODE_TEST
then
pass "DirEntry.inode preserves C89 declarations and decoder/stat/errno/cleanup order"
else
bad "DirEntry.inode preserves C89 declarations and decoder/stat/errno/cleanup order"
fi
# The VC6 Windows wait-status branch must use CPython's MSVC6 64-bit type.
if python3 - "$REPO_ROOT/cpython/Modules/posixmodule.c" <<'PY_WAITSTATUS_TEST'
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
start = source.index("os_waitstatus_to_exitcode_impl(PyObject *module, PyObject *status_obj)")
end = source.index("\n}\n#endif", start) + 2
function = source[start:end]
windows = function[function.index("#else\n", function.index("#ifndef MS_WINDOWS")):]
declarations = """ unsigned PY_LONG_LONG status;
unsigned PY_LONG_LONG exitcode;
"""
assert declarations in windows, (
"Windows wait-status locals must be in the opening C89 declaration group"
)
assert windows.index("status = PyLong_AsUnsignedLongLong(status_obj);") < windows.index(
"if (status == (unsigned PY_LONG_LONG)-1 && PyErr_Occurred())"
) < windows.index("exitcode = (status >> 8);")
assert "PyErr_Format(PyExc_ValueError, \"invalid exit code: %llu\", exitcode);" in windows
assert "return PyLong_FromUnsignedLong((unsigned long)exitcode);" in windows
PY_WAITSTATUS_TEST
then
pass "Windows wait-status conversion uses VC6-compatible 64-bit declarations"
else
bad "Windows wait-status conversion uses VC6-compatible 64-bit declarations"
fi
# VC6's Windows headers predate the documented DLL-search flags. Require the
# shared fallback definitions after windows.h in both code paths that consume them.
if python3 - "$REPO_ROOT/cpython/Include/internal/pycore_win9x_loadlibrary.h" \
"$REPO_ROOT/cpython/Modules/posixmodule.c" \
"$REPO_ROOT/cpython/Python/dynload_win.c" <<'PY_WIN9X_LOAD_FLAGS_TEST'
import re
import sys
from pathlib import Path
header_path, *source_paths = map(Path, sys.argv[1:])
if not header_path.is_file():
raise SystemExit("shared Win9x LoadLibrary flag compatibility header missing")
header = header_path.read_text()
if not re.search(r"#\s*if\s+defined\(_MSC_VER\)\s*&&\s*_MSC_VER\s*<\s*1300", header):
raise SystemExit("fallback must be restricted to VC6-era MSVC")
values = {
"LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR": "0x00000100",
"LOAD_LIBRARY_SEARCH_APPLICATION_DIR": "0x00000200",
"LOAD_LIBRARY_SEARCH_USER_DIRS": "0x00000400",
"LOAD_LIBRARY_SEARCH_SYSTEM32": "0x00000800",
"LOAD_LIBRARY_SEARCH_DEFAULT_DIRS": "0x00001000",
}
for name, value in values.items():
pattern = rf"#\s*ifndef\s+{name}\s+#\s*define\s+{name}\s+{value}\b"
if not re.search(pattern, header):
raise SystemExit(f"missing documented guarded value for {name}")
for path in source_paths:
source = path.read_text()
windows = re.search(r"#\s*include\s*<windows\.h>", source)
compat = re.search(r'#\s*include\s*"pycore_win9x_loadlibrary\.h"', source)
if windows is None or compat is None or compat.start() <= windows.start():
raise SystemExit(f"compatibility header must follow windows.h in {path.name}")
print("VC6 LoadLibrary flags use documented values and are shared after windows.h")
PY_WIN9X_LOAD_FLAGS_TEST
then
pass "VC6 LoadLibrary search flags have guarded shared definitions in both consumers"
else
bad "VC6 LoadLibrary search flags have guarded shared definitions in both consumers"
fi
# VC6 rejects the mixed declarations and declaration in for-init in posixmodule_exec.
# Keep initialization and traversal operations at their original points.
if python3 - "$REPO_ROOT/cpython/Modules/posixmodule.c" <<'PY_POSIXMODULE_EXEC_C89_TEST'
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
start = source.index("posixmodule_exec(PyObject *m)")
body_start = source.index("\n{\n", start)
end = source.index("\n}\n", body_start) + 3
function = source[start:end]
expected = """posixmodule_exec(PyObject *m)
{
_posixstate *state;
PyObject *ScandirIteratorType;
PyObject *list;
const struct have_function *trace;
state = get_posix_state(m);
"""
if not function.startswith(expected):
raise SystemExit("posixmodule_exec C89 declaration group/initial state assignment mismatch")
if function.count("PyObject *ScandirIteratorType") != 1:
raise SystemExit("ScandirIteratorType declaration must occur once")
if function.count("PyObject *list") != 1:
raise SystemExit("list declaration must occur once")
if function.count("const struct have_function *trace") != 1:
raise SystemExit("trace declaration must occur once")
order = [
function.index("ScandirIteratorType = PyType_FromModuleAndSpec(m, &ScandirIteratorType_spec, NULL);"),
function.index("state->ScandirIteratorType = ScandirIteratorType;"),
function.index("list = PyList_New(0);"),
function.index("for (trace = have_functions; trace->label; trace++)"),
function.index("return _PyModule_Add(m, \"_have_functions\", list);"),
]
if order != sorted(order):
raise SystemExit("posixmodule_exec initialization/traversal/return order changed")
PY_POSIXMODULE_EXEC_C89_TEST
then
pass "posixmodule_exec hoists C89 locals while preserving initialization and traversal order"
else
bad "posixmodule_exec hoists C89 locals while preserving initialization and traversal order"
fi
# PyModuleDef uses a C99 designated initializer; preserve its declared field order for VC6.
if python3 - "$REPO_ROOT/cpython/Modules/posixmodule.c" <<'PY_POSIXMODULE_DEF_C89_TEST'
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
start = source.index("static struct PyModuleDef posixmodule = {")
end = source.index("\n};", start) + 3
expected = """static struct PyModuleDef posixmodule = {
PyModuleDef_HEAD_INIT,
MODNAME,
posix__doc__,
sizeof(_posixstate),
posix_methods,
posixmodile_slots,
_posix_traverse,
_posix_clear,
_posix_free,
};"""
if source[start:end] != expected:
raise SystemExit("posixmodule PyModuleDef fields must use declared positional order")
PY_POSIXMODULE_DEF_C89_TEST
then
pass "posixmodule PyModuleDef uses C89-compatible declared field order"
else
bad "posixmodule PyModuleDef uses C89-compatible declared field order"
fi
# VC6 needs its platform 64-bit spelling plus C89-compatible declarations and initializers.
if python3 - "$REPO_ROOT/cpython/Modules/sha1module.c" <<'PY_SHA1_VC6_TEST'
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
if "typedef PY_LONG_LONG SHA1_INT64;" not in source:
raise SystemExit("SHA1_INT64 must use the compiler-compatible PY_LONG_LONG alias")
type_start = source.index("static PyType_Spec sha1_type_spec = {")
type_end = source.index("\n};", type_start) + 3
expected_type = """static PyType_Spec sha1_type_spec = {
"_sha1.sha1",
sizeof(SHA1object),
0,
(Py_TPFLAGS_DEFAULT | Py_TPFLAGS_DISALLOW_INSTANTIATION |
Py_TPFLAGS_IMMUTABLETYPE | Py_TPFLAGS_HAVE_GC),
sha1_type_slots
};"""
if source[type_start:type_end] != expected_type:
raise SystemExit("sha1_type_spec must use C89-compatible PyType_Spec field order")
start = source.index("_sha1_sha1_impl(PyObject *module, PyObject *string, int usedforsecurity)")
body_start = source.index("\n{\n", start)
end = source.index("\n}\n", body_start) + 3
body = source[body_start + 1:end]
if not body.startswith("""{
SHA1object *new;
Py_buffer buf;
SHA1State *st;
"""):
raise SystemExit("_sha1_sha1_impl must hoist st into its C89 declaration group")
if body.count("SHA1State *st") != 1:
raise SystemExit("_sha1_sha1_impl must declare st exactly once")
if not (body.index("GET_BUFFER_VIEW_OR_ERROUT(string, &buf);") <
body.index("st = sha1_get_state(module);") <
body.index("newSHA1object(st)")):
raise SystemExit("_sha1_sha1_impl must preserve buffer/state/object initialization order")
module_start = source.index("static struct PyModuleDef _sha1module = {")
module_end = source.index("\n};", module_start) + 3
expected_module = """static struct PyModuleDef _sha1module = {
PyModuleDef_HEAD_INIT,
"_sha1",
NULL,
sizeof(SHA1State),
SHA1_functions,
_sha1_slots,
_sha1_traverse,
_sha1_clear,
_sha1_free
};"""
if source[module_start:module_end] != expected_module:
raise SystemExit("_sha1module must use C89-compatible positional PyModuleDef fields")
PY_SHA1_VC6_TEST
then
pass "sha1module uses VC6-compatible 64-bit, C89 declaration, and initializer forms"
else
bad "sha1module uses VC6-compatible 64-bit, C89 declaration, and initializer forms"
fi
# VC6 needs C89-compatible type specs, locals, and the module definition in sha256module.
if python3 - "$REPO_ROOT/cpython/Modules/sha256module.c" <<'PY_SHA256_VC6_TEST'
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
for name, typename in (("sha224_type_spec", "_sha256.sha224"), ("sha256_type_spec", "_sha256.sha256")):
start = source.index(f"static PyType_Spec {name} = {{")
end = source.index("\n};", start) + 3
expected = f"""static PyType_Spec {name} = {{
\"{typename}\",
sizeof(SHAobject),
0,
(Py_TPFLAGS_DEFAULT | Py_TPFLAGS_DISALLOW_INSTANTIATION |
Py_TPFLAGS_IMMUTABLETYPE | Py_TPFLAGS_HAVE_GC),
sha256_types_slots
}};"""
if source[start:end] != expected:
raise SystemExit(f"{name} must use C89-compatible declared PyType_Spec field order")
for function, constructor in (("_sha256_sha256_impl", "newSHA256object"),
("_sha256_sha224_impl", "newSHA224object")):
start = source.index(function + "(PyObject *module, PyObject *string, int usedforsecurity)")
body_start = source.index("\n{\n", start)
end = source.index("\n}\n", body_start) + 3
body = source[body_start + 1:end]
if not body.startswith("""{
Py_buffer buf;
_sha256_state *state;
SHAobject *new;
"""):
raise SystemExit(f"{function} must hoist state/new into its C89 declaration group")
if body.count("_sha256_state *state") != 1 or body.count("SHAobject *new") != 1:
raise SystemExit(f"{function} must declare state and new exactly once")
if not (body.index("GET_BUFFER_VIEW_OR_ERROUT(string, &buf);") <
body.index("state = PyModule_GetState(module);") <
body.index(f"new = {constructor}(state)")):
raise SystemExit(f"{function} must preserve buffer/state/object initialization order")
start = source.index("static struct PyModuleDef _sha256module = {")
end = source.index("\n};", start) + 3
expected_module = """static struct PyModuleDef _sha256module = {
PyModuleDef_HEAD_INIT,
"_sha256",
NULL,
sizeof(_sha256_state),
SHA_functions,
_sha256_slots,
_sha256_traverse,
_sha256_clear,
_sha256_free
};"""
if source[start:end] != expected_module:
raise SystemExit("_sha256module must use C89-compatible positional PyModuleDef fields")
PY_SHA256_VC6_TEST
then
pass "sha256module uses C89-compatible type specs, declarations, and module fields"
else
bad "sha256module uses C89-compatible type specs, declarations, and module fields"
fi
# VC6 rejects C99 ULL suffixes; keep the 64-bit SHA512 constants and casts exact.
if python3 - "$REPO_ROOT/cpython/Modules/sha512module.c" <<'PY_SHA512_VC6_TEST'
import re
import subprocess
import sys
from pathlib import Path
path = Path(sys.argv[1])
source = path.read_text()
repo = path.parents[1]
original = subprocess.run(
["git", "-C", str(repo), "show", "HEAD:Modules/sha512module.c"],
check=True, capture_output=True, text=True,
).stdout
original_values = re.findall(r"(0x[0-9a-fA-F]+)ULL\b", original)
portable_values = re.findall(r"PY_ULL\((0x[0-9a-fA-F]+)\)", source)
if len(original_values) != 83 or original_values != portable_values:
raise SystemExit("all 83 SHA512 64-bit literals must retain their exact values through PY_ULL")
if re.search(r"0x[0-9a-fA-F]+ULL\b", source):
raise SystemExit("sha512module still contains VC6-incompatible ULL literals")
if "unsigned long long" in source:
raise SystemExit("sha512module still contains VC6-incompatible long-long casts")
if source.count("unsigned PY_LONG_LONG") != 3:
raise SystemExit("all SHA512 macro casts must use the compiler-compatible 64-bit type")
for name, typename, slots in (("sha512_sha384_type_spec", "_sha512.sha384", "sha512_sha384_type_slots"),
("sha512_sha512_type_spec", "_sha512.sha512", "sha512_sha512_type_slots")):
start = source.index(f"static PyType_Spec {name} = {{")
end = source.index("\n};", start) + 3
expected = f"""static PyType_Spec {name} = {{
\"{typename}\",
sizeof(SHAobject),
0,
(Py_TPFLAGS_DEFAULT | Py_TPFLAGS_DISALLOW_INSTANTIATION |
Py_TPFLAGS_IMMUTABLETYPE | Py_TPFLAGS_HAVE_GC),
{slots}
}};"""
if source[start:end] != expected:
raise SystemExit(f"{name} must use C89-compatible declared PyType_Spec field order")
start = source.index("static struct PyModuleDef _sha512module = {")
end = source.index("\n};", start) + 3
expected_module = """static struct PyModuleDef _sha512module = {
PyModuleDef_HEAD_INIT,
"_sha512",
NULL,
sizeof(SHA512State),
SHA_functions,
_sha512_slots,
_sha512_traverse,
_sha512_clear,
_sha512_free
};"""
if source[start:end] != expected_module:
raise SystemExit("_sha512module must use C89-compatible positional PyModuleDef fields")
PY_SHA512_VC6_TEST
then
pass "sha512module preserves all SHA constants with VC6-compatible literals and casts"
else
bad "sha512module preserves all SHA constants with VC6-compatible literals and casts"
fi
# VC6 requires positional initialization of the Windows wakeup fields.
if python3 - "$REPO_ROOT/cpython/Modules/signalmodule.c" <<'PY_SIGNAL_WAKEUP_TEST'
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
start = source.index("#ifdef MS_WINDOWS\n#define INVALID_FD")
end = source.index("#else", start)
expected = """#ifdef MS_WINDOWS
#define INVALID_FD ((SOCKET_T)-1)
static volatile struct {
SOCKET_T fd;
int warn_on_full_buffer;
int use_send;
} wakeup = {INVALID_FD, 1, 0};
"""
if source[start:end] != expected:
raise SystemExit("Windows wakeup must use C89 positional {INVALID_FD, 1, 0} in declared field order")
PY_SIGNAL_WAKEUP_TEST
then
pass "signalmodule Windows wakeup uses C89 positional initialization in field order"
else
bad "signalmodule Windows wakeup uses C89 positional initialization in field order"
fi
# VC6 signal support: exclude absent Bluetooth APIs and keep signal paths C89-safe.
if python3 - "$REPO_ROOT/cpython/Modules/signalmodule.c" \
"$REPO_ROOT/cpython/Modules/socketmodule.h" \
"$REPO_ROOT/cpython/Modules/socketmodule.c" \
"$REPO_ROOT/cpython/Include/internal/pycore_pystate.h" <<'PY_SIGNAL_VC6_TEST'
import re
import sys
from pathlib import Path
signal = Path(sys.argv[1]).read_text()
socket_header = Path(sys.argv[2]).read_text()
socket_source = Path(sys.argv[3]).read_text()
pystate = Path(sys.argv[4]).read_text()
def body(signature):
start = signal.index(signature)
opening = signal.index("\n{\n", start)
end = signal.index("\n}\n", opening) + 3
return signal[opening + 1:end]
if "#elif defined(MS_WINDOWS) && defined(AF_BTH)" not in socket_header:
raise SystemExit("Windows Bluetooth sockaddr storage must be gated by AF_BTH")
if "#if defined(MS_WINDOWS) && defined(AF_BTH)\n#define sockaddr_rc SOCKADDR_BTH_REDEF" not in socket_source:
raise SystemExit("Windows Bluetooth socket support must be gated by AF_BTH")
if not re.search(r"#\s*ifndef EWOULDBLOCK\s+#\s*define EWOULDBLOCK EAGAIN", signal):
raise SystemExit("VC6 signal write path needs an EWOULDBLOCK fallback")
if not re.search(r"#if defined\(_MSC_VER\) && _MSC_VER < 1300 && !defined\(__func__\)\s+#\s*define __func__", pystate):
raise SystemExit("pycore_pystate must supply VC6's missing __func__ token")
if "for (int " in signal:
raise SystemExit("signalmodule contains a VC6-incompatible loop declaration")
trip = body("trip_signal(int sig_num)")
if not trip.startswith("""{
int fd;
PyInterpreterState *interp;
"""):
raise SystemExit("trip_signal locals must be in the opening C89 declaration group")
if not (trip.index("interp = _PyInterpreterState_Main();") < trip.index("fd = Py_SAFE_DOWNCAST")):
raise SystemExit("trip_signal must preserve interpreter lookup before fd conversion")
signal_fn = body("signal_signal_impl(PyObject *module, int signalnum, PyObject *handler)")
if not signal_fn.startswith("""{
_signal_module_state *modstate;
PyObject *old_handler;
void (*func)(int);
PyThreadState *tstate;
modstate = get_signal_state(module);
""") or "tstate = _PyThreadState_GET();" not in signal_fn:
raise SystemExit("signal_signal_impl must hoist tstate and preserve state lookup")
wakeup = body("signal_set_wakeup_fd(PyObject *self, PyObject *args, PyObject *kwds)")
if " PyThreadState *tstate;" not in wakeup[:500] or " tstate = _PyThreadState_GET();" not in wakeup:
raise SystemExit("signal_set_wakeup_fd must hoist tstate and assign it after argument parsing")
set_fd = body("PySignal_SetWakeupFd(int fd)")
if not set_fd.startswith("""{
int old_fd;
if (fd < 0) {
""") or "old_fd = Py_SAFE_DOWNCAST" not in set_fd:
raise SystemExit("PySignal_SetWakeupFd must hoist old_fd without reordering normalization")
valid = body("signal_valid_signals_impl(PyObject *module)")
if not (valid.index("PyObject *tup;") < valid.index("Py_BuildValue") and
valid.index("PyObject *set;") < valid.index("Py_BuildValue") and
"set = PySet_New(tup);" in valid):
raise SystemExit("signal_valid_signals must declare Windows locals before statements")
handlers = body("signal_get_set_handlers(signal_state_t *state, PyObject *mod_dict)")
if "for (signum = 1; signum < Py_NSIG; signum++)" not in handlers:
raise SystemExit("signal_get_set_handlers must use a C89 loop counter")
if not (handlers.index("PyObject *old_func;") < handlers.index("old_func = get_handler(signum);") and
handlers.index("PyObject *sigint_func;") < handlers.index("sigint_func = get_handler(SIGINT);")):
raise SystemExit("signal_get_set_handlers must hoist post-statement locals")
module_exec = body("signal_module_exec(PyObject *m)")
if not module_exec.startswith("""{
signal_state_t *state;
_signal_module_state *modstate;
PyObject *d;
PyThreadState *tstate;
assert(!PyErr_Occurred());
"""):
raise SystemExit("signal_module_exec must use one opening C89 declaration group")
module_start = signal.index("static struct PyModuleDef signal_module = {")
module_end = signal.index("\n};", module_start) + 3
expected_module = """static struct PyModuleDef signal_module = {
PyModuleDef_HEAD_INIT,
"_signal",
module_doc,
sizeof(_signal_module_state),
signal_methods,
signal_slots,
#ifdef PYHAVE_ITIMER_ERROR
_signal_module_traverse,
_signal_module_clear,
_signal_module_free,
#endif
};"""
if signal[module_start:module_end] != expected_module:
raise SystemExit("signal_module must use C89-compatible positional PyModuleDef fields")
fini = body("_PySignal_Fini(void)")
if not fini.startswith("""{
signal_state_t *state;
int signum;
state = &signal_global_state;
""") or "for (signum = 1; signum < Py_NSIG; signum++)" not in fini:
raise SystemExit("_PySignal_Fini must hoist its loop counter")
check = body("_PyErr_CheckSignalsTstate(PyThreadState *tstate)")
if not check.startswith("""{
_PyInterpreterFrame *frame;
signal_state_t *state;
int i;
""") or "for (i = 1; i < Py_NSIG; i++)" not in check:
raise SystemExit("_PyErr_CheckSignalsTstate must hoist frame/state/loop locals")
if not (check.index("PyObject *func;") < check.index("func = get_handler(i);") and
check.index("PyObject *arglist;") < check.index("arglist = NULL;") and
check.index("PyObject *result;") < check.index("if (arglist)")):
raise SystemExit("signal handler loop locals must be declared before statements")
interrupt = body("PyErr_SetInterruptEx(int signum)")
if not interrupt.startswith("""{
signal_state_t *state;
PyObject *func;
""") or "state = &signal_global_state;" not in interrupt:
raise SystemExit("PyErr_SetInterruptEx must hoist state and handler locals")
init = body("_PySignal_Init(int install_signal_handlers)")
if not init.startswith("""{
signal_state_t *state;
int signum;
state = &signal_global_state;
""") or "for (signum = 1; signum < Py_NSIG; signum++)" not in init:
raise SystemExit("_PySignal_Init must hoist its loop counter")
print("VC6 signal paths use gated Bluetooth, fallback errno, and C89 declarations")
PY_SIGNAL_VC6_TEST
then
pass "signalmodule and socket Bluetooth paths are VC6-compatible"
else
bad "signalmodule and socket Bluetooth paths are VC6-compatible"
fi
# VC6 symtable module: preserve generated-wrapper and compiler-flag semantics.
if python3 - "$REPO_ROOT/cpython/Modules/symtablemodule.c" \
"$REPO_ROOT/cpython/Modules/clinic/symtablemodule.c.h" <<'PY_SYMTABLE_VC6_TEST'
from pathlib import Path
import sys
source = Path(sys.argv[1]).read_text()
clinic = Path(sys.argv[2]).read_text()
def body(text, signature):
start = text.index(signature)
opening = text.index("\n{\n", start)
closing = text.index("\n}\n", opening) + 3
return text[opening + 1:closing]
wrapper = body(clinic, "_symtable_symtable(PyObject *module, PyObject *const *args, Py_ssize_t nargs)")
if not wrapper.startswith("""{
PyObject *return_value = NULL;
PyObject *source;
PyObject *filename;
const char *startstr;
Py_ssize_t startstr_length;
""") or wrapper.count("Py_ssize_t startstr_length;") != 1:
raise SystemExit("generated _symtable_symtable must declare startstr_length in its opening C89 group")
if wrapper.index("Py_ssize_t startstr_length;") > wrapper.index("if (!_PyArg_CheckPositional"):
raise SystemExit("generated Clinic local must precede statements")
impl = body(source, "_symtable_symtable_impl(PyObject *module, PyObject *source,")
if "PyCompilerFlags cf = _PyCompilerFlags_INIT;" in impl:
raise SystemExit("symtablemodule must not use the compound-literal compiler-flags initializer")
if not (impl.index("PyCompilerFlags cf;") < impl.index("cf.cf_flags = 0;") <
impl.index("cf.cf_feature_version = PY_MINOR_VERSION;") <
impl.index("cf.cf_flags = PyCF_SOURCE_IS_UTF8;") <
impl.index("str = _Py_SourceAsString")):
raise SystemExit("symtablemodule must explicitly initialize compiler flags and preserve assignment order")
if impl.index("const char *str;") > impl.index("cf.cf_flags = 0;"):
raise SystemExit("symtablemodule str local must be declared before statements")
module_start = source.index("static struct PyModuleDef symtablemodule = {")
module_end = source.index("\n};", module_start) + 3
expected = """static struct PyModuleDef symtablemodule = {
PyModuleDef_HEAD_INIT,
"_symtable",
NULL,
0,
symtable_methods,
symtable_slots,
};"""
if source[module_start:module_end] != expected:
raise SystemExit("symtablemodule must use C89-compatible positional PyModuleDef fields")
print("VC6 symtable wrapper, flags, and module definition are C89-compatible")
PY_SYMTABLE_VC6_TEST
then
pass "symtablemodule generated and handwritten code are VC6-compatible"
else
bad "symtablemodule generated and handwritten code are VC6-compatible"
fi
# VC6 first C89 blockers in _threadmodule.c: hoist locals, retain point of use.
if python3 - "$REPO_ROOT/cpython/Modules/_threadmodule.c" <<'PY_THREAD_VC6_TEST'
from pathlib import Path
import sys
source = Path(sys.argv[1]).read_text()
def body(signature):
start = source.rindex(signature)
opening = source.index("\n{\n", start)
closing = source.index("\n}\n", opening) + 3
return source[opening + 1:closing]
lock_dealloc = body("lock_dealloc(lockobject *self)")
if not lock_dealloc.startswith("""{
PyTypeObject *tp;
PyObject_GC_UnTrack(self);
""") or "tp = Py_TYPE(self);" not in lock_dealloc:
raise SystemExit("lock_dealloc must hoist tp and assign it after lock cleanup")
acquire_timed = body("acquire_timed(PyThread_type_lock lock, _PyTime_t timeout)")
if not acquire_timed.startswith("""{
_PyTime_t endtime = 0;
PyLockStatus r;
"""):
raise SystemExit("acquire_timed must declare r before timeout statements")
acquire = body("lock_PyThread_acquire_lock(lockobject *self, PyObject *args, PyObject *kwds)")
if not acquire.startswith("""{
_PyTime_t timeout;
PyLockStatus r;
""") or "r = acquire_timed(self->lock_lock, timeout);" not in acquire:
raise SystemExit("lock acquire must hoist r and assign it after argument parsing")
def initializer(symbol):
start = source.index("static PyType_Spec " + symbol + " = {")
end = source.index("\n};", start) + 3
return source[start:end]
expected_specs = {
"lock_type_spec": """static PyType_Spec lock_type_spec = {
"_thread.lock",
sizeof(lockobject),
0,
(Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC |
Py_TPFLAGS_DISALLOW_INSTANTIATION | Py_TPFLAGS_IMMUTABLETYPE),
lock_type_slots,
};""",
"rlock_type_spec": """static PyType_Spec rlock_type_spec = {
"_thread.RLock",
sizeof(rlockobject),
0,
(Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE |
Py_TPFLAGS_HAVE_GC | Py_TPFLAGS_IMMUTABLETYPE),
rlock_type_slots,
};""",
"local_dummy_type_spec": """static PyType_Spec local_dummy_type_spec = {
"_thread._localdummy",
sizeof(localdummyobject),
0,
(Py_TPFLAGS_DEFAULT | Py_TPFLAGS_DISALLOW_INSTANTIATION |
Py_TPFLAGS_IMMUTABLETYPE),
local_dummy_type_slots,
};""",
"local_type_spec": """static PyType_Spec local_type_spec = {
"_thread._local",
sizeof(localobject),
0,
(Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC |
Py_TPFLAGS_IMMUTABLETYPE),
local_type_slots,
};""",
}
for symbol, expected in expected_specs.items():
if initializer(symbol) != expected:
raise SystemExit(symbol + " must use declared-order positional PyType_Spec fields")
desc_start = source.index("static PyStructSequence_Desc ExceptHookArgs_desc = {")
desc_end = source.index("\n};", desc_start) + 3
expected_desc = """static PyStructSequence_Desc ExceptHookArgs_desc = {
"_thread._ExceptHookArgs",
ExceptHookArgs__doc__,
ExceptHookArgs_fields,
4
};"""
if source[desc_start:desc_end] != expected_desc:
raise SystemExit("ExceptHookArgs_desc must use declared-order positional fields")
module_start = source.index("static struct PyModuleDef thread_module = {")
module_end = source.index("\n};", module_start) + 3
expected_module = """static struct PyModuleDef thread_module = {
PyModuleDef_HEAD_INIT,
"_thread",
thread_doc,
sizeof(thread_module_state),
thread_methods,
thread_module_slots,
thread_module_traverse,
thread_module_clear,
thread_module_free,
};"""
if source[module_start:module_end] != expected_module:
raise SystemExit("thread_module must use declared-order positional PyModuleDef fields")
for name, expected_prefix, after_marker in (
("rlock_dealloc(rlockobject *self)",
"""{
PyTypeObject *tp;
PyObject_GC_UnTrack(self);
""", "PyThread_free_lock(self->rlock_lock);"),
("localdummy_dealloc(localdummyobject *self)",
"""{
PyTypeObject *tp;
if (self->weakreflist != NULL)""", "PyObject_ClearWeakRefs((PyObject *) self);"),
("local_dealloc(localobject *self)",
"""{
PyTypeObject *tp;
/* Weakrefs must be invalidated right now""", "Py_XDECREF(self->key);"),
):
current = body(name)
if not current.startswith(expected_prefix) or not (
current.index(after_marker) < current.index("tp = Py_TYPE(self);")
):
raise SystemExit(name + " must hoist tp but preserve assignment after cleanup")
create_dummy = body("_local_create_dummy(localobject *self, thread_module_state *state)")
if " int r;" not in create_dummy[:250] or not (
create_dummy.index("wr = PyWeakref_NewRef") <
create_dummy.index("r = PyDict_SetItem(self->dummies, wr, ldict);")
):
raise SystemExit("_local_create_dummy must hoist r and assign it after weakref creation")
local_new = body("local_new(PyTypeObject *type, PyObject *args, PyObject *kw)")
if not local_new.startswith("""{
static PyMethodDef wr_callback_def = {
"_localdummy_destroyed", (PyCFunction) _localdummy_destroyed, METH_O
};
PyObject *module;
thread_module_state *state;
localobject *self;
PyObject *wr;
"""):
raise SystemExit("local_new must hoist module/state/self/wr declarations before statements")
for earlier, later in (
("if (type->tp_init == PyBaseObject_Type.tp_init)", "module = PyType_GetModuleByDef"),
("module = PyType_GetModuleByDef", "state = get_thread_state(module);"),
("state = get_thread_state(module);", "self = (localobject *)type->tp_alloc(type, 0);"),
("self->dummies = PyDict_New();", "wr = PyWeakref_NewRef((PyObject *) self, NULL);"),
):
if local_new.index(earlier) >= local_new.index(later):
raise SystemExit("local_new must preserve control flow and original assignment order")
local_clear = body("local_clear(localobject *self)")
key_block = local_clear[local_clear.index("if (self->key) {"):]
if not (key_block.index("PyThreadState *tstate;") < key_block.index("HEAD_LOCK(runtime);") and
key_block.index("HEAD_LOCK(runtime);") < key_block.index("tstate = PyInterpreterState_ThreadHead(interp);") < key_block.index("HEAD_UNLOCK(runtime);")):
raise SystemExit("local_clear must hoist tstate and keep thread-head lookup under the runtime lock")
ldict = body("_ldict(localobject *self, thread_module_state *state)")
if not ldict.startswith("""{
PyObject *tdict;
PyObject *ldict;
PyObject *dummy;
tdict = PyThreadState_GetDict();
"""):
raise SystemExit("_ldict must hoist locals and retain the thread-dict lookup point")
if not (ldict.index("if (tdict == NULL)") < ldict.index("dummy = PyDict_GetItemWithError")):
raise SystemExit("_ldict must not access the dummy until the thread dictionary is validated")
setattro = body("local_setattro(localobject *self, PyObject *name, PyObject *v)")
if not setattro.startswith("""{
PyObject *module;
thread_module_state *state;
PyObject *ldict;
int r;
module = PyType_GetModuleByDef""") or "r = PyObject_RichCompareBool" not in setattro:
raise SystemExit("local_setattro must hoist r while preserving module/dict/compare order")
getattro = body("local_getattro(localobject *self, PyObject *name)")
if not getattro.startswith("""{
PyObject *module;
thread_module_state *state;
PyObject *ldict;
int r;
PyObject *value;
module = PyType_GetModuleByDef""") or not (
getattro.index("r = PyObject_RichCompareBool") <
getattro.index("value = PyDict_GetItemWithError")
):
raise SystemExit("local_getattro must hoist locals and preserve comparison-before-lookup order")
destroyed = body("_localdummy_destroyed(PyObject *localweakref, PyObject *dummyweakref)")
if not destroyed.startswith("""{
PyObject *obj;
localobject *self;
assert(PyWeakref_CheckRef(localweakref));
obj = PyWeakref_GET_OBJECT(localweakref);
""") or not (
destroyed.index("if (obj == Py_None)") < destroyed.index("self = (localobject *)Py_NewRef(obj);")
):
raise SystemExit("_localdummy_destroyed must hoist obj/self and preserve the None check")
thread_run = body("thread_run(void *boot_raw)")
if not thread_run.startswith("""{
struct bootstate *boot;
PyThreadState *tstate;
PyObject *res;
boot = (struct bootstate *) boot_raw;
tstate = boot->tstate;
""") or not (
thread_run.index("_PyThreadState_SetCurrent(tstate);") <
thread_run.index("res = PyObject_Call(boot->func, boot->args, boot->kwargs);")
):
raise SystemExit("thread_run must hoist locals and keep the call after current-thread setup")
start_thread = body("thread_PyThread_start_new_thread(PyObject *self, PyObject *fargs)")
if not start_thread.startswith("""{
PyObject *func, *args, *kwargs = NULL;
PyInterpreterState *interp;
struct bootstate *boot;
unsigned long ident;
""") or not (
start_thread.index("if (!PyArg_UnpackTuple") <
start_thread.index("interp = _PyInterpreterState_GET();") <
start_thread.index("boot = PyMem_RawMalloc(sizeof(struct bootstate));") <
start_thread.index("ident = PyThread_start_new_thread")
):
raise SystemExit("start_new_thread must preserve validation/allocation/thread-start order")
hook_file = body("thread_excepthook_file(PyObject *file, PyObject *exc_type, PyObject *exc_value,")
if not hook_file.startswith("""{
PyObject *name;
PyObject *res;
""") or not (
hook_file.index("name = NULL;") < hook_file.index("if (thread != Py_None)") and
hook_file.index("res = PyObject_CallMethodNoArgs") < hook_file.index("Py_DECREF(res);")
):
raise SystemExit("thread_excepthook_file must hoist locals without changing lookup/flush order")
hook = body("thread_excepthook(PyObject *module, PyObject *args)")
if not hook.startswith("""{
thread_module_state *state;
PyObject *exc_type;
PyObject *exc_value;
PyObject *exc_tb;
PyObject *thread;
PyThreadState *tstate;
PyObject *file;
int res;
state = get_thread_state(module);
""") or not (
hook.index("exc_type = PyStructSequence_GET_ITEM(args, 0);") <
hook.index("if (exc_type == PyExc_SystemExit)") <
hook.index("exc_value = PyStructSequence_GET_ITEM(args, 1);") <
hook.index("tstate = _PyThreadState_GET();") <
hook.index("res = thread_excepthook_file")
):
raise SystemExit("thread_excepthook must hoist locals and retain filtering/lookup/dispatch order")
module_exec = body("thread_module_exec(PyObject *module)")
if not module_exec.startswith("""{
thread_module_state *state;
PyObject *d;
PyTypeObject *rlock_type;
double timeout_max;
double time_max;
state = get_thread_state(module);
d = PyModule_GetDict(module);
""") or any(declaration in module_exec for declaration in (
"PyTypeObject *rlock_type =", "double timeout_max =", "double time_max =")) or not (
module_exec.index("rlock_type = (PyTypeObject *)PyType_FromSpec") <
module_exec.index("timeout_max = (double)PY_TIMEOUT_MAX * 1e-6;") <
module_exec.index("time_max = _PyTime_AsSecondsDouble(_PyTime_MAX);")
):
raise SystemExit("thread_module_exec must hoist locals and preserve type/module initialization order")
time_header = (Path(sys.argv[1]).parents[1] / "Include/cpython/pytime.h").read_text()
if """#if defined(_MSC_VER) && _MSC_VER < 1300
# define _PyTime_MIN PY_LLONG_MIN
# define _PyTime_MAX PY_LLONG_MAX
#else
# define _PyTime_MIN INT64_MIN
# define _PyTime_MAX INT64_MAX
#endif""" not in time_header:
raise SystemExit("pytime limits must use PY_LLONG bounds under VC6 without changing modern targets")
tracemalloc_source = (Path(sys.argv[1]).parents[1] / "Modules/_tracemalloc.c").read_text()
def tracemalloc_body(signature):
start = tracemalloc_source.rindex(signature)
opening = tracemalloc_source.index("\n{\n", start)
closing = tracemalloc_source.index("\n}\n", opening) + 3
return tracemalloc_source[opening + 1:closing]
compare_traceback = tracemalloc_body("hashtable_compare_traceback(const void *key1, const void *key2)")
if not compare_traceback.startswith("""{
const traceback_t *traceback1 = (const traceback_t *)key1;
const traceback_t *traceback2 = (const traceback_t *)key2;
int i;
""") or "for (int i =" in compare_traceback or "for (i=0;" not in compare_traceback:
raise SystemExit("tracemalloc traceback comparison loop must declare its counter before statements")
get_frame = tracemalloc_body("tracemalloc_get_frame(_PyInterpreterFrame *pyframe, frame_t *frame)")
if not get_frame.startswith("""{
int lineno;
PyObject *filename;
_Py_hashtable_entry_t *entry;
frame->filename = &_Py_STR(anon_unknown);
""") or not (
get_frame.index("lineno = _PyInterpreterFrame_GetLine(pyframe);") <
get_frame.index("filename = pyframe->f_code->co_filename;") <
get_frame.index("entry = _Py_hashtable_get_entry(tracemalloc_filenames, filename);")
) or "PyObject *filename =" in get_frame or "_Py_hashtable_entry_t *entry;" in get_frame[get_frame.index("frame->filename"):]:
raise SystemExit("tracemalloc frame capture must hoist locals but preserve assignment points")
traceback_frames = tracemalloc_body("traceback_get_frames(traceback_t *traceback)")
if not traceback_frames.startswith("""{
PyThreadState *tstate;
_PyInterpreterFrame *pyframe;
tstate = PyGILState_GetThisThreadState();
""") or not (
traceback_frames.index("if (tstate == NULL)") <
traceback_frames.index("pyframe = tstate->cframe->current_frame;")
) or "_PyInterpreterFrame *pyframe =" in traceback_frames:
raise SystemExit("tracemalloc frame walk must hoist pyframe and preserve the null-state guard")
remove_trace = tracemalloc_body("tracemalloc_remove_trace(unsigned int domain, uintptr_t ptr)")
if not remove_trace.startswith("""{
_Py_hashtable_t *traces;
trace_t *trace;
assert(_Py_tracemalloc_config.tracing);
""") or not (
remove_trace.index("traces = tracemalloc_get_traces_table(domain);") <
remove_trace.index("trace = _Py_hashtable_steal(traces, TO_PTR(ptr));")
) or "_Py_hashtable_t *traces =" in remove_trace or "trace_t *trace =" in remove_trace:
raise SystemExit("tracemalloc removal must hoist table/trace locals and preserve lookup order")
add_trace = tracemalloc_body("tracemalloc_add_trace(unsigned int domain, uintptr_t ptr,")
if not add_trace.startswith("""{
traceback_t *traceback;
_Py_hashtable_t *traces;
trace_t *trace;
int res;
assert(_Py_tracemalloc_config.tracing);
""") or not (
add_trace.index("traceback = traceback_new();") <
add_trace.index("traces = tracemalloc_get_traces_table(domain);") <
add_trace.index("trace = _Py_hashtable_get(traces, TO_PTR(ptr));") <
add_trace.index("res = _Py_hashtable_set(traces, TO_PTR(ptr), trace);")
) or "traceback_t *traceback =" in add_trace or "int res =" in add_trace:
raise SystemExit("tracemalloc insertion must hoist locals and preserve traceback/table/insert order")
pyport_header = (Path(sys.argv[1]).parents[1] / "Include/pyport.h").read_text()
if """#if defined(_MSC_VER) && _MSC_VER < 1300 && !defined(__func__)
# define __func__ "<unknown>"
#endif""" not in pyport_header:
raise SystemExit("pyport must provide the VC6 __func__ fallback used by Py_FatalError")
traceback_object = tracemalloc_body("traceback_to_pyobject(traceback_t *traceback, _Py_hashtable_t *intern_table)")
if not traceback_object.startswith("""{
PyObject *frames;
int i;
""") or "for (int i=" in traceback_object or "for (i=0;" not in traceback_object:
raise SystemExit("tracemalloc tuple conversion must hoist the frame-loop counter")
copy_traces = tracemalloc_body("tracemalloc_copy_traces(_Py_hashtable_t *traces)")
if not copy_traces.startswith("""{
_Py_hashtable_t *traces2;
int err;
traces2 = tracemalloc_create_traces_table();
""") or not (
copy_traces.index("if (traces2 == NULL)") <
copy_traces.index("err = _Py_hashtable_foreach(traces,")
) or "int err =" in copy_traces:
raise SystemExit("tracemalloc table copy must hoist err and preserve allocation-before-copy")
copy_domains = tracemalloc_body("tracemalloc_copy_domains(_Py_hashtable_t *domains)")
if not copy_domains.startswith("""{
_Py_hashtable_t *domains2;
int err;
domains2 = tracemalloc_create_domains_table();
""") or not (
copy_domains.index("if (domains2 == NULL)") <
copy_domains.index("err = _Py_hashtable_foreach(domains,")
) or "int err =" in copy_domains:
raise SystemExit("tracemalloc domain copy must hoist err and preserve allocation-before-copy")
fill_traces = tracemalloc_body("tracemalloc_get_traces_fill(_Py_hashtable_t *traces,")
if not fill_traces.startswith("""{
get_traces_t *get_traces;
const trace_t *trace;
PyObject *tuple;
int res;
get_traces = user_data;
""") or not (
fill_traces.index("tuple = trace_to_pyobject") <
fill_traces.index("res = PyList_Append(get_traces->list, tuple);")
) or "int res =" in fill_traces:
raise SystemExit("tracemalloc trace-list fill must hoist locals without moving reference operations")
get_traces_impl = tracemalloc_body("_tracemalloc__get_traces_impl(PyObject *module)")
if not get_traces_impl.startswith("""{
get_traces_t get_traces;
int err;
""") or "int err =" in get_traces_impl:
raise SystemExit("tracemalloc get-traces must declare err before statements for VC6")
if not (
get_traces_impl.index("set_reentrant(1);") <
get_traces_impl.index("err = _Py_hashtable_foreach(get_traces.traces,") <
get_traces_impl.index("if (!err)") <
get_traces_impl.index("err = _Py_hashtable_foreach(get_traces.domains,") <
get_traces_impl.index("set_reentrant(0);") <
get_traces_impl.index("if (err)")
):
raise SystemExit("tracemalloc get-traces must preserve traversal and reentrancy order")
get_traceback = tracemalloc_body("tracemalloc_get_traceback(unsigned int domain, uintptr_t ptr)")
assert get_traceback == """{
trace_t *trace;
_Py_hashtable_t *traces;
if (!_Py_tracemalloc_config.tracing)
return NULL;
TABLES_LOCK();
traces = tracemalloc_get_traces_table(domain);
if (traces) {
trace = _Py_hashtable_get(traces, TO_PTR(ptr));
}
else {
trace = NULL;
}
TABLES_UNLOCK();
if (!trace) {
return NULL;
}
return trace->traceback;
}
""", "tracemalloc_get_traceback must hoist only trace/traces and preserve guarded, locked lookup order"
print("_thread and tracemalloc VC6/C89 regression checks passed")
PY_THREAD_VC6_TEST
then
pass "_thread lock paths pass initial VC6 C89 checks"
else
bad "_thread lock paths pass initial VC6 C89 checks"
fi
# Keep this NewReference slice limited to C89 declaration placement, with
# assignments, control flow, and table locking preserved exactly.
if python3 - "$REPO_ROOT/cpython/Modules/_tracemalloc.c" <<'PY_NEWREFERENCE_TEST'
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
start = source.index("_PyTraceMalloc_NewReference(PyObject *op)\n{\n")
opening = source.index("\n{\n", start) + 1
end = source.index("\n}\n", opening) + 3
function = source[opening:end]
declarations = """{
uintptr_t ptr;
PyTypeObject *type;
int res;
trace_t *trace;
"""
assert function.startswith(declarations), (
"_PyTraceMalloc_NewReference must hoist ptr/type/res/trace into the initial C89 declaration group"
)
assert function[len(declarations):] == """ assert(PyGILState_Check());
if (!_Py_tracemalloc_config.tracing) {
/* tracemalloc is not tracing: do nothing */
return -1;
}
type = Py_TYPE(op);
if (PyType_IS_GC(type)) {
ptr = (uintptr_t)((char *)op - sizeof(PyGC_Head));
}
else {
ptr = (uintptr_t)op;
}
res = -1;
TABLES_LOCK();
trace = _Py_hashtable_get(tracemalloc_traces, TO_PTR(ptr));
if (trace != NULL) {
/* update the traceback of the memory block */
traceback_t *traceback = traceback_new();
if (traceback != NULL) {
trace->traceback = traceback;
res = 0;
}
}
/* else: cannot track the object, its memory block size is unknown */
TABLES_UNLOCK();
return res;
}
""", "_PyTraceMalloc_NewReference must preserve assignment points, control flow, and TABLES_LOCK/UNLOCK ordering"
PY_NEWREFERENCE_TEST
then
pass "_PyTraceMalloc_NewReference preserves C89 declarations, assignments, and table locking"
else
bad "_PyTraceMalloc_NewReference preserves C89 declarations, assignments, and table locking"
fi
# Only the first timemodule clock slice: hoist ticks/ns without moving work.
if python3 - "$REPO_ROOT/cpython/Modules/timemodule.c" <<'PY_TIME_CLOCK_TEST'
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
start = source.index("_PyTime_GetClockWithInfo(_PyTime_t *tp, _Py_clock_info_t *info)\n")
opening = source.index("{\n", start)
end = source.index("\n}\n", opening) + 3
function = source[opening:end]
assert function == """{
static int initialized = 0;
clock_t ticks;
_PyTime_t ns;
if (!initialized) {
initialized = 1;
/* Make sure that _PyTime_MulDiv(ticks, SEC_TO_NS, CLOCKS_PER_SEC)
above cannot overflow */
if ((_PyTime_t)CLOCKS_PER_SEC > _PyTime_MAX / SEC_TO_NS) {
PyErr_SetString(PyExc_OverflowError,
"CLOCKS_PER_SEC is too large");
return -1;
}
}
if (info) {
info->implementation = "clock()";
info->resolution = 1.0 / (double)CLOCKS_PER_SEC;
info->monotonic = 1;
info->adjustable = 0;
}
ticks = clock();
if (ticks == (clock_t)-1) {
PyErr_SetString(PyExc_RuntimeError,
"the processor time used is not available "
"or its value cannot be represented");
return -1;
}
ns = _PyTime_MulDiv(ticks, SEC_TO_NS, (_PyTime_t)CLOCKS_PER_SEC);
*tp = _PyTime_FromNanoseconds(ns);
return 0;
}
""", "_PyTime_GetClockWithInfo must hoist only ticks/ns and preserve initialization, info updates, clock/error checks, and conversion order"
PY_TIME_CLOCK_TEST
then
pass "timemodule clock ticks/ns use C89 declarations with unchanged execution order"
else
bad "timemodule clock ticks/ns use C89 declarations with unchanged execution order"
fi
# Only the first timemodule state-declaration slice; compare complete bodies
# to preserve all assignment points, error paths, and platform branches.
if python3 - "$REPO_ROOT/cpython/Modules/timemodule.c" <<'PY_TIME_STATE_TEST'
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
expected = {
'time_gmtime': r"""{
time_t when;
struct tm buf;
time_module_state *state;
if (!parse_time_t_args(args, "|O:gmtime", &when))
return NULL;
errno = 0;
if (_PyTime_gmtime(when, &buf) != 0)
return NULL;
state = get_time_state(module);
#ifdef HAVE_STRUCT_TM_TM_ZONE
return tmtotuple(state, &buf);
#else
return tmtotuple(state, &buf, "UTC", 0);
#endif
}
""",
'time_localtime': r"""{
time_t when;
struct tm buf;
time_module_state *state;
if (!parse_time_t_args(args, "|O:localtime", &when))
return NULL;
if (_PyTime_localtime(when, &buf) != 0)
return NULL;
state = get_time_state(module);
#ifdef HAVE_STRUCT_TM_TM_ZONE
return tmtotuple(state, &buf);
#else
{
struct tm local = buf;
char zone[100];
time_t gmtoff;
strftime(zone, sizeof(zone), "%Z", &buf);
gmtoff = timegm(&buf) - when;
return tmtotuple(state, &local, zone, gmtoff);
}
#endif
}
""",
'time_strftime': r"""{
PyObject *tup = NULL;
struct tm buf;
const time_char *fmt;
#ifdef HAVE_WCSFTIME
wchar_t *format;
#else
PyObject *format;
#endif
PyObject *format_arg;
size_t fmtlen, buflen;
time_char *outbuf = NULL;
size_t i;
PyObject *ret = NULL;
time_module_state *state;
memset((void *) &buf, '\0', sizeof(buf));
/* Will always expect a unicode string to be passed as format.
Given that there's no str type anymore in py3k this seems safe.
*/
if (!PyArg_ParseTuple(args, "U|O:strftime", &format_arg, &tup))
return NULL;
state = get_time_state(module);
if (tup == NULL) {
time_t tt = time(NULL);
if (_PyTime_localtime(tt, &buf) != 0)
return NULL;
}
else if (!gettmarg(state, tup, &buf,
"iiiiiiiii;strftime(): illegal time tuple argument") ||
!checktm(&buf))
{
return NULL;
}
#if defined(_MSC_VER) || (defined(__sun) && defined(__SVR4)) || defined(_AIX) || defined(__VXWORKS__)
if (buf.tm_year + 1900 < 1 || 9999 < buf.tm_year + 1900) {
PyErr_SetString(PyExc_ValueError,
"strftime() requires year in [1; 9999]");
return NULL;
}
#endif
/* Normalize tm_isdst just in case someone foolishly implements %Z
based on the assumption that tm_isdst falls within the range of
[-1, 1] */
if (buf.tm_isdst < -1)
buf.tm_isdst = -1;
else if (buf.tm_isdst > 1)
buf.tm_isdst = 1;
#ifdef HAVE_WCSFTIME
format = PyUnicode_AsWideCharString(format_arg, NULL);
if (format == NULL)
return NULL;
fmt = format;
#else
/* Convert the unicode string to an ascii one */
format = PyUnicode_EncodeLocale(format_arg, "surrogateescape");
if (format == NULL)
return NULL;
fmt = PyBytes_AS_STRING(format);
#endif
#if defined(MS_WINDOWS) && !defined(HAVE_WCSFTIME)
/* check that the format string contains only valid directives */
for (outbuf = strchr(fmt, '%');
outbuf != NULL;
outbuf = strchr(outbuf+2, '%'))
{
if (outbuf[1] == '#')
++outbuf; /* not documented by python, */
if (outbuf[1] == '\0')
break;
if ((outbuf[1] == 'y') && buf.tm_year < 0) {
PyErr_SetString(PyExc_ValueError,
"format %y requires year >= 1900 on Windows");
Py_DECREF(format);
return NULL;
}
}
#elif (defined(_AIX) || (defined(__sun) && defined(__SVR4))) && defined(HAVE_WCSFTIME)
for (outbuf = wcschr(fmt, '%');
outbuf != NULL;
outbuf = wcschr(outbuf+2, '%'))
{
if (outbuf[1] == L'\0')
break;
/* Issue #19634: On AIX, wcsftime("y", (1899, 1, 1, 0, 0, 0, 0, 0, 0))
returns "0/" instead of "99" */
if (outbuf[1] == L'y' && buf.tm_year < 0) {
PyErr_SetString(PyExc_ValueError,
"format %y requires year >= 1900 on AIX");
PyMem_Free(format);
return NULL;
}
}
#endif
fmtlen = time_strlen(fmt);
/* I hate these functions that presume you know how big the output
* will be ahead of time...
*/
for (i = 1024; ; i += i) {
outbuf = (time_char *)PyMem_Malloc(i*sizeof(time_char));
if (outbuf == NULL) {
PyErr_NoMemory();
break;
}
#if defined _MSC_VER && _MSC_VER >= 1400 && defined(__STDC_SECURE_LIB__)
errno = 0;
#endif
_Py_BEGIN_SUPPRESS_IPH
buflen = format_time(outbuf, i, fmt, &buf);
_Py_END_SUPPRESS_IPH
#if defined _MSC_VER && _MSC_VER >= 1400 && defined(__STDC_SECURE_LIB__)
/* VisualStudio .NET 2005 does this properly */
if (buflen == 0 && errno == EINVAL) {
PyErr_SetString(PyExc_ValueError, "Invalid format string");
PyMem_Free(outbuf);
break;
}
#endif
if (buflen > 0 || i >= 256 * fmtlen) {
/* If the buffer is 256 times as long as the format,
it's probably not failing for lack of room!
More likely, the format yields an empty result,
e.g. an empty format, or %Z when the timezone
is unknown. */
#ifdef HAVE_WCSFTIME
ret = PyUnicode_FromWideChar(outbuf, buflen);
#else
ret = PyUnicode_DecodeLocaleAndSize(outbuf, buflen, "surrogateescape");
#endif
PyMem_Free(outbuf);
break;
}
PyMem_Free(outbuf);
}
#ifdef HAVE_WCSFTIME
PyMem_Free(format);
#else
Py_DECREF(format);
#endif
return ret;
}
""",
'time_asctime': r"""{
PyObject *tup = NULL;
struct tm buf;
time_module_state *state;
if (!PyArg_UnpackTuple(args, "asctime", 0, 1, &tup))
return NULL;
state = get_time_state(module);
if (tup == NULL) {
time_t tt = time(NULL);
if (_PyTime_localtime(tt, &buf) != 0)
return NULL;
}
else if (!gettmarg(state, tup, &buf,
"iiiiiiiii;asctime(): illegal time tuple argument") ||
!checktm(&buf))
{
return NULL;
}
return _asctime(&buf);
}
""",
}
failed = []
for name, body in expected.items():
start = source.index(name + "(PyObject *module, PyObject *args)\n")
opening = source.index("{\n", start)
end = source.index("\n}\n", opening) + 3
if source[opening:end] != body:
failed.append(name)
assert not failed, (
"state must use the opening C89 declaration group with the exact body and "
"original get_time_state assignment point preserved: " + ", ".join(failed)
)
PY_TIME_STATE_TEST
then
pass "timemodule four state declarations preserve exact bodies and evaluation order"
else
bad "timemodule four state declarations preserve exact bodies and evaluation order"
fi
# Windows timezone locals in init_timezone must be in the C89 declaration group.
if python3 - "$REPO_ROOT/cpython/Modules/timemodule.c" <<'PY_TIMEZONE_TEST'
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
start = source.rindex("init_timezone(PyObject *m)")
opening = source.index("{\n", start)
end = source.index("\n}\n", opening) + 3
function = source[opening:end]
expected_prefix = """{
#ifdef HAVE_DECL_TZNAME
PyObject *otz0, *otz1;
#ifdef MS_WINDOWS
TIME_ZONE_INFORMATION tzinfo = {0};
#endif
#endif
assert(!PyErr_Occurred());"""
if not function.startswith(expected_prefix):
raise SystemExit("init_timezone must hoist tzname/timezone locals before its first statement")
if " PyObject *otz0, *otz1;" in function[function.index("assert(!PyErr_Occurred());"):]:
raise SystemExit("init_timezone must not redeclare otz locals after executable statements")
if " TIME_ZONE_INFORMATION tzinfo = {0};" in function[function.index("assert(!PyErr_Occurred());"):]:
raise SystemExit("init_timezone must not declare tzinfo in the late Windows branch")
if not (
function.index("tzset();") <
function.index('PyModule_AddIntConstant(m, "daylight", _Py_daylight);') <
function.index("GetTimeZoneInformation(&tzinfo);") <
function.index("otz0 = PyUnicode_FromWideChar(tzinfo.StandardName, -1);") <
function.index("otz1 = PyUnicode_FromWideChar(tzinfo.DaylightName, -1);")
):
raise SystemExit("init_timezone must preserve timezone query and Unicode conversion order")
PY_TIMEZONE_TEST
then
pass "timemodule init_timezone hoists Windows locals and preserves ordering"
else
bad "timemodule init_timezone hoists Windows locals and preserves ordering"
fi
# The VC6 module definition must avoid C99 designated initializers.
if python3 - "$REPO_ROOT/cpython/Modules/timemodule.c" <<'PY_TIMEMODULE_DEF_TEST'
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
start = source.index("static struct PyModuleDef timemodule = {")
body = source[start:].split("{", 1)[1].split("};", 1)[0]
fields = [field.strip() for field in body.split(",") if field.strip()]
expected = [
"PyModuleDef_HEAD_INIT",
'"time"',
"module_doc",
"sizeof(time_module_state)",
"time_methods",
"time_slots",
"time_module_traverse",
"time_module_clear",
"time_module_free",
]
if fields != expected:
raise SystemExit("time module definition must use the CPython field order with positional initializers")
PY_TIMEMODULE_DEF_TEST
then
pass "timemodule definition uses VC6-compatible positional fields"
else
bad "timemodule definition uses VC6-compatible positional fields"
fi
# Windows pysleep: hoist only declarations, preserving every execution point.
if python3 - "$REPO_ROOT/cpython/Modules/timemodule.c" <<'PY_TIME_SLEEP_TEST'
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
function_start = source.index("pysleep(_PyTime_t timeout)\n{\n")
function_open = source.index("{\n", function_start)
declaration_prefix = """{
#ifdef MS_WINDOWS
_PyTime_t timeout_100ns;
LARGE_INTEGER relative_timeout;
HANDLE timer;
HANDLE sigint_event;
HANDLE events[2];
DWORD rc;
#endif
assert(timeout >= 0);
"""
assert source.startswith(declaration_prefix, function_open), (
"Windows pysleep locals must precede the first statement at function scope"
)
start = source.index("#else // MS_WINDOWS\n", function_start) + len("#else // MS_WINDOWS\n")
end = source.index("#endif\n}", start)
branch = source[start:end]
assert branch == """ timeout_100ns = _PyTime_As100Nanoseconds(timeout,
_PyTime_ROUND_CEILING);
// Maintain Windows Sleep() semantics for time.sleep(0)
if (timeout_100ns == 0) {
Py_BEGIN_ALLOW_THREADS
// A value of zero causes the thread to relinquish the remainder of its
// time slice to any other thread that is ready to run. If there are no
// other threads ready to run, the function returns immediately, and
// the thread continues execution.
Sleep(0);
Py_END_ALLOW_THREADS
return 0;
}
// No need to check for integer overflow, both types are signed
assert(sizeof(relative_timeout) == sizeof(timeout_100ns));
// SetWaitableTimer(): a negative due time indicates relative time
relative_timeout.QuadPart = -timeout_100ns;
timer = CreateWaitableTimerExW(NULL, NULL, timer_flags,
TIMER_ALL_ACCESS);
if (timer == NULL) {
PyErr_SetFromWindowsErr(0);
return -1;
}
if (!SetWaitableTimerEx(timer, &relative_timeout,
0, // no period; the timer is signaled once
NULL, NULL, // no completion routine
NULL, // no wake context; do not resume from suspend
0)) // no tolerable delay for timer coalescing
{
PyErr_SetFromWindowsErr(0);
goto error;
}
// Only the main thread can be interrupted by SIGINT.
// Signal handlers are only executed in the main thread.
if (_PyOS_IsMainThread()) {
sigint_event = _PyOS_SigintEvent();
while (1) {
// Check for pending SIGINT signal before resetting the event
if (PyErr_CheckSignals()) {
goto error;
}
ResetEvent(sigint_event);
events[0] = timer;
events[1] = sigint_event;
Py_BEGIN_ALLOW_THREADS
rc = WaitForMultipleObjects(Py_ARRAY_LENGTH(events), events,
// bWaitAll
FALSE,
// No wait timeout
INFINITE);
Py_END_ALLOW_THREADS
if (rc == WAIT_FAILED) {
PyErr_SetFromWindowsErr(0);
goto error;
}
if (rc == WAIT_OBJECT_0) {
// Timer signaled: we are done
break;
}
assert(rc == (WAIT_OBJECT_0 + 1));
// The sleep was interrupted by SIGINT: restart sleeping
}
}
else {
Py_BEGIN_ALLOW_THREADS
rc = WaitForSingleObject(timer, INFINITE);
Py_END_ALLOW_THREADS
if (rc == WAIT_FAILED) {
PyErr_SetFromWindowsErr(0);
goto error;
}
assert(rc == WAIT_OBJECT_0);
// Timer signaled: we are done
}
CloseHandle(timer);
return 0;
error:
CloseHandle(timer);
return -1;
""", "Windows pysleep must preserve assignment points, wait branches, signal handling, and cleanup with one shared rc declaration"
PY_TIME_SLEEP_TEST
then
pass "timemodule Windows pysleep preserves C89 declarations and exact execution order"
else
bad "timemodule Windows pysleep preserves C89 declarations and exact execution order"
fi
# _copy_raw_string keeps its early error return before copying, but declares locals in C89 order.
if python3 - "$REPO_ROOT/cpython/Modules/_xxsubinterpretersmodule.c" <<'PY_COPY_RAW_STRING_TEST'
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
start = source.rindex("_copy_raw_string(PyObject *strobj)")
opening = source.index("{\n", start)
end = source.index("\n}\n", opening) + 2
function = source[opening:end]
expected = """{
const char *str = PyUnicode_AsUTF8(strobj);
char *copied;
if (str == NULL) {
return NULL;
}
copied = PyMem_Malloc(strlen(str)+1);
if (copied == NULL) {
PyErr_NoMemory();
return NULL;
}
strcpy(copied, str);
return copied;
}"""
if function != expected:
raise SystemExit("_copy_raw_string must hoist copied and preserve its null/error/copy order")
PY_COPY_RAW_STRING_TEST
then
pass "_copy_raw_string preserves error and copy order with C89 declarations"
else
bad "_copy_raw_string preserves error and copy order with C89 declarations"
fi
# _sharednsitem_apply hoists all locals and preserves allocation/error order.
if python3 - "$REPO_ROOT/cpython/Modules/_xxsubinterpretersmodule.c" <<'PY_SHAREDNSITEM_APPLY_TEST'
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
start = source.index("_sharednsitem_apply(struct _sharednsitem *item, PyObject *ns)")
opening = source.index("{\n", start)
end = source.index("\n}\n", opening) + 2
function = source[opening:end]
expected = """{
PyObject *name;
PyObject *value;
int res;
name = PyUnicode_FromString(item->name);
if (name == NULL) {
return -1;
}
value = _PyCrossInterpreterData_NewObject(&item->data);
if (value == NULL) {
Py_DECREF(name);
return -1;
}
res = PyDict_SetItem(ns, name, value);
Py_DECREF(name);
Py_DECREF(value);
return res;
}"""
if function != expected:
raise SystemExit("_sharednsitem_apply must hoist locals and preserve error/cleanup order")
PY_SHAREDNSITEM_APPLY_TEST
then
pass "_sharednsitem_apply preserves ordering with C89 declarations"
else
bad "_sharednsitem_apply preserves ordering with C89 declarations"
fi
# _sharedns_free uses a C89-compatible loop declaration without changing cleanup order.
if python3 - "$REPO_ROOT/cpython/Modules/_xxsubinterpretersmodule.c" <<'PY_SHAREDNS_FREE_TEST'
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
start = source.index("_sharedns_free(_sharedns *shared)")
opening = source.index("{\n", start)
end = source.index("\n}\n", opening) + 2
function = source[opening:end]
expected = """{
Py_ssize_t i;
for (i=0; i < shared->len; i++) {
_sharednsitem_clear(&shared->items[i]);
}
PyMem_Free(shared->items);
PyMem_Free(shared);
}"""
if function != expected:
raise SystemExit("_sharedns_free must hoist loop index and retain cleanup order")
PY_SHAREDNS_FREE_TEST
then
pass "_sharedns_free uses C89 loop declarations and preserves cleanup order"
else
bad "_sharedns_free uses C89 loop declarations and preserves cleanup order"
fi
# _get_shared_ns declares locals before statements and preserves dictionary iteration/error behavior.
if python3 - "$REPO_ROOT/cpython/Modules/_xxsubinterpretersmodule.c" <<'PY_GET_SHARED_NS_TEST'
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
start = source.index("_get_shared_ns(PyObject *shareable)")
opening = source.index("{\n", start)
end = source.index("\n}\n", opening) + 2
function = source[opening:end]
expected = """{
Py_ssize_t len;
_sharedns *shared;
Py_ssize_t pos;
Py_ssize_t i;
if (shareable == NULL || shareable == Py_None) {
return NULL;
}
len = PyDict_Size(shareable);
if (len == 0) {
return NULL;
}
shared = _sharedns_new(len);
if (shared == NULL) {
return NULL;
}
pos = 0;
for (i=0; i < len; i++) {
PyObject *key, *value;
if (PyDict_Next(shareable, &pos, &key, &value) == 0) {
break;
}
if (_sharednsitem_init(&shared->items[i], key, value) != 0) {
break;
}
}
if (PyErr_Occurred()) {
_sharedns_free(shared);
return NULL;
}
return shared;
}"""
if function != expected:
raise SystemExit("_get_shared_ns must preserve null/error/iteration order with C89 declarations")
PY_GET_SHARED_NS_TEST
then
pass "_get_shared_ns preserves behavior with C89 declarations"
else
bad "_get_shared_ns preserves behavior with C89 declarations"
fi
# _sharedns_apply moves its loop index out of the C99 for declaration.
if python3 - "$REPO_ROOT/cpython/Modules/_xxsubinterpretersmodule.c" <<'PY_SHAREDNS_APPLY_TEST'
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
start = source.index("_sharedns_apply(_sharedns *shared, PyObject *ns)")
opening = source.index("{\n", start)
end = source.index("\n}\n", opening) + 2
function = source[opening:end]
expected = """{
Py_ssize_t i;
for (i=0; i < shared->len; i++) {
if (_sharednsitem_apply(&shared->items[i], ns) != 0) {
return -1;
}
}
return 0;
}"""
if function != expected:
raise SystemExit("_sharedns_apply must hoist its loop index and preserve iteration order")
PY_SHAREDNS_APPLY_TEST
then
pass "_sharedns_apply uses C89 loop declarations"
else
bad "_sharedns_apply uses C89 loop declarations"
fi
# _sharedexception_bind hoists only outer locals and keeps all error/cleanup points ordered.
if python3 - "$REPO_ROOT/cpython/Modules/_xxsubinterpretersmodule.c" <<'PY_SHAREDEXCEPTION_BIND_TEST'
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
start = source.index("_sharedexception_bind(PyObject *exctype, PyObject *exc, PyObject *tb)")
opening = source.index("{\n", start)
end = source.index("\n}\n", opening) + 2
function = source[opening:end]
expected = """{
char *failure;
_sharedexception *err;
PyObject *name;
assert(exctype != NULL);
failure = NULL;
err = _sharedexception_new();
if (err == NULL) {
goto finally;
}
name = PyUnicode_FromFormat("%S", exctype);
if (name == NULL) {
failure = "unable to format exception type name";
goto finally;
}
err->name = _copy_raw_string(name);
Py_DECREF(name);
if (err->name == NULL) {
if (PyErr_ExceptionMatches(PyExc_MemoryError)) {
failure = "out of memory copying exception type name";
} else {
failure = "unable to encode and copy exception type name";
}
goto finally;
}
if (exc != NULL) {
PyObject *msg = PyUnicode_FromFormat("%S", exc);
if (msg == NULL) {
failure = "unable to format exception message";
goto finally;
}
err->msg = _copy_raw_string(msg);
Py_DECREF(msg);
if (err->msg == NULL) {
if (PyErr_ExceptionMatches(PyExc_MemoryError)) {
failure = "out of memory copying exception message";
} else {
failure = "unable to encode and copy exception message";
}
goto finally;
}
}
finally:
if (failure != NULL) {
PyErr_Clear();
if (err->name != NULL) {
PyMem_Free(err->name);
err->name = NULL;
}
err->msg = failure;
}
return err;
}"""
if function != expected:
raise SystemExit("_sharedexception_bind must preserve failure/cleanup order with C89 declarations")
PY_SHAREDEXCEPTION_BIND_TEST
then
pass "_sharedexception_bind preserves error flow with C89 declarations"
else
bad "_sharedexception_bind preserves error flow with C89 declarations"
fi
# _channel_close_interpreter keeps lock acquisition before result initialization.
if python3 - "$REPO_ROOT/cpython/Modules/_xxsubinterpretersmodule.c" <<'PY_CHANNEL_CLOSE_INTERPRETER_TEST'
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
start = source.index("_channel_close_interpreter(_PyChannelState *chan, int64_t interp, int end)")
opening = source.index("{", start)
end = source.index(chr(10) + "}" + chr(10), opening) + 2
function = source[opening:end]
expected = """{
int res;
PyThread_acquire_lock(chan->mutex, WAIT_LOCK);
res = -1;
if (!chan->open) {
PyErr_SetString(ChannelClosedError, "channel already closed");
goto done;
}
if (_channelends_close_interpreter(chan->ends, interp, end) != 0) {
goto done;
}
chan->open = _channelends_is_open(chan->ends);
res = 0;
done:
PyThread_release_lock(chan->mutex);
return res;
}"""
if function != expected:
raise SystemExit("_channel_close_interpreter must preserve lock/result/error order with C89 declarations")
PY_CHANNEL_CLOSE_INTERPRETER_TEST
then
pass "_channel_close_interpreter preserves lock and error order"
else
bad "_channel_close_interpreter preserves lock and error order"
fi
# Channel lookup/add helpers hoist locals without changing lock or list operations.
if python3 - "$REPO_ROOT/cpython/Modules/_xxsubinterpretersmodule.c" <<'PY_CHANNELS_HELPERS_TEST'
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
checks = {
"_channels_lookup(_channels *channels, int64_t id, PyThread_type_lock *pmutex)": """{
_PyChannelState *chan;
_channelref *ref;
chan = NULL;
PyThread_acquire_lock(channels->mutex, WAIT_LOCK);
if (pmutex != NULL) {
*pmutex = NULL;
}
ref = _channelref_find(channels->head, id, NULL);
if (ref == NULL) {
PyErr_Format(ChannelNotFoundError, "channel %" PRId64 " not found", id);
goto done;
}
if (ref->chan == NULL || !ref->chan->open) {
PyErr_Format(ChannelClosedError, "channel %" PRId64 " closed", id);
goto done;
}
if (pmutex != NULL) {
// The mutex will be closed by the caller.
*pmutex = channels->mutex;
}
chan = ref->chan;
done:
if (pmutex == NULL || *pmutex == NULL) {
PyThread_release_lock(channels->mutex);
}
return chan;
}""",
"_channels_add(_channels *channels, _PyChannelState *chan)": """{
int64_t cid;
int64_t id;
_channelref *ref;
cid = -1;
PyThread_acquire_lock(channels->mutex, WAIT_LOCK);
// Create a new ref.
id = _channels_next_id(channels);
if (id < 0) {
goto done;
}
ref = _channelref_new(id, chan);
if (ref == NULL) {
goto done;
}
// Add it to the list.
// We assume that the channel is a new one (not already in the list).
ref->next = channels->head;
channels->head = ref;
channels->numopen += 1;
cid = id;
done:
PyThread_release_lock(channels->mutex);
return cid;
}""",
}
for signature, expected in checks.items():
start = source.index(signature)
opening = source.index("{", start)
end = source.index(chr(10) + "}" + chr(10), opening) + 2
if source[opening:end] != expected:
raise SystemExit("channel helper must hoist locals and preserve lock/error/list operation order: " + signature)
PY_CHANNELS_HELPERS_TEST
then
pass "_channels_lookup/_channels_add preserve lock order with C89 declarations"
else
bad "_channels_lookup/_channels_add preserve lock order with C89 declarations"
fi
# _channels_close declares ref before lock/error statements and keeps lookup after pchan initialization.
if python3 - "$REPO_ROOT/cpython/Modules/_xxsubinterpretersmodule.c" <<'PY_CHANNELS_CLOSE_TEST'
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
start = source.index("_channels_close(_channels *channels, int64_t cid, _PyChannelState **pchan,")
opening = source.index("{", start)
end = source.index(chr(10) + "}" + chr(10), opening) + 2
function = source[opening:end]
expected_prefix = """{
int res = -1;
_channelref *ref;
PyThread_acquire_lock(channels->mutex, WAIT_LOCK);
if (pchan != NULL) {
*pchan = NULL;
}
ref = _channelref_find(channels->head, cid, NULL);
"""
if not function.startswith(expected_prefix):
raise SystemExit("_channels_close must hoist ref while preserving lock and lookup order")
PY_CHANNELS_CLOSE_TEST
then
pass "_channels_close hoists ref and preserves lock/lookup order"
else
bad "_channels_close hoists ref and preserves lock/lookup order"
fi
# Channel reference operations hoist locals while retaining locking and cleanup order.
if python3 - "$REPO_ROOT/cpython/Modules/_xxsubinterpretersmodule.c" <<'PY_CHANNELS_REF_OPS_TEST'
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
checks = {
"_channels_remove(_channels *channels, int64_t id, _PyChannelState **pchan)": """{
int res = -1;
_channelref *prev;
_channelref *ref;
PyThread_acquire_lock(channels->mutex, WAIT_LOCK);
if (pchan != NULL) {
*pchan = NULL;
}
prev = NULL;
ref = _channelref_find(channels->head, id, &prev);
if (ref == NULL) {
PyErr_Format(ChannelNotFoundError, "channel %" PRId64 " not found", id);
goto done;
}
_channels_remove_ref(channels, ref, prev, pchan);
res = 0;
done:
PyThread_release_lock(channels->mutex);
return res;
}""",
"_channels_add_id_object(_channels *channels, int64_t id)": """{
int res = -1;
_channelref *ref;
PyThread_acquire_lock(channels->mutex, WAIT_LOCK);
ref = _channelref_find(channels->head, id, NULL);
if (ref == NULL) {
PyErr_Format(ChannelNotFoundError, "channel %" PRId64 " not found", id);
goto done;
}
ref->objcount += 1;
res = 0;
done:
PyThread_release_lock(channels->mutex);
return res;
}""",
"_channels_drop_id_object(_channels *channels, int64_t id)": """{
_channelref *prev;
_channelref *ref;
PyThread_acquire_lock(channels->mutex, WAIT_LOCK);
prev = NULL;
ref = _channelref_find(channels->head, id, &prev);
if (ref == NULL) {
// Already destroyed.
goto done;
}
ref->objcount -= 1;
// Destroy if no longer used.
if (ref->objcount == 0) {
_PyChannelState *chan = NULL;
_channels_remove_ref(channels, ref, prev, &chan);
if (chan != NULL) {
_channel_free(chan);
}
}
done:
PyThread_release_lock(channels->mutex);
}""",
"_channels_list_all(_channels *channels, int64_t *count)": """{
int64_t *cids = NULL;
int64_t *ids;
_channelref *ref;
int64_t i;
PyThread_acquire_lock(channels->mutex, WAIT_LOCK);
ids = PyMem_NEW(int64_t, (Py_ssize_t)(channels->numopen));
if (ids == NULL) {
goto done;
}
ref = channels->head;
for (i=0; ref != NULL; ref = ref->next, i++) {
ids[i] = ref->id;
}
*count = channels->numopen;
cids = ids;
done:
PyThread_release_lock(channels->mutex);
return cids;
}""",
}
for signature, expected in checks.items():
start = source.index(signature)
opening = source.index("{", start)
end = source.index(chr(10) + "}" + chr(10), opening) + 2
if source[opening:end] != expected:
raise SystemExit("channel reference helper must preserve locks, result and cleanup order: " + signature)
PY_CHANNELS_REF_OPS_TEST
then
pass "channel reference helpers use C89 declarations and preserve order"
else
bad "channel reference helpers use C89 declarations and preserve order"
fi
# Channel closing/creation helpers hoist locals and keep assignments after their guards.
if python3 - "$REPO_ROOT/cpython/Modules/_xxsubinterpretersmodule.c" <<'PY_CHANNEL_CLOSING_TEST'
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
checks = {
"_channel_set_closing(struct _channelref *ref, PyThread_type_lock mutex)": """{
struct _channel *chan;
int res;
chan = ref->chan;
if (chan == NULL) {
// already closed
return 0;
}
res = -1;
PyThread_acquire_lock(chan->mutex, WAIT_LOCK);
if (chan->closing != NULL) {
PyErr_SetString(ChannelClosedError, "channel closed");
goto done;
}
chan->closing = PyMem_NEW(struct _channel_closing, 1);
if (chan->closing == NULL) {
goto done;
}
chan->closing->ref = ref;
res = 0;
done:
PyThread_release_lock(chan->mutex);
return res;
}""",
"_channel_finish_closing(struct _channel *chan)": """{
struct _channel_closing *closing;
_channelref *ref;
closing = chan->closing;
if (closing == NULL) {
return;
}
ref = closing->ref;
_channel_clear_closing(chan);
// Do the things that would have been done in _channels_close().
ref->chan = NULL;
_channel_free(chan);
}""",
"_channel_create(_channels *channels)": """{
_PyChannelState *chan;
int64_t id;
chan = _channel_new();
if (chan == NULL) {
return -1;
}
id = _channels_add(channels, chan);
if (id < 0) {
_channel_free(chan);
return -1;
}
return id;
}""",
}
for signature, expected in checks.items():
start = source.index(signature)
opening = source.index("{", start)
end = source.index(chr(10) + "}" + chr(10), opening) + 2
if source[opening:end] != expected:
raise SystemExit("channel closing helper must preserve error/assignment/cleanup order: " + signature)
PY_CHANNEL_CLOSING_TEST
then
pass "channel closing/creation helpers use C89 declaration order"
else
bad "channel closing/creation helpers use C89 declaration order"
fi
# _channel_send preserves acquired-lock and cross-interpreter cleanup order with C89 locals.
if python3 - "$REPO_ROOT/cpython/Modules/_xxsubinterpretersmodule.c" <<'PY_CHANNEL_SEND_TEST'
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
start = source.index("_channel_send(_channels *channels, int64_t id, PyObject *obj)")
opening = source.index("{", start)
end = source.index(chr(10) + "}" + chr(10), opening) + 2
function = source[opening:end]
expected = """{
PyInterpreterState *interp;
PyThread_type_lock mutex;
_PyChannelState *chan;
_PyCrossInterpreterData *data;
int res;
interp = _get_current();
if (interp == NULL) {
return -1;
}
// Look up the channel.
mutex = NULL;
chan = _channels_lookup(channels, id, &mutex);
if (chan == NULL) {
return -1;
}
// Past this point we are responsible for releasing the mutex.
if (chan->closing != NULL) {
PyErr_Format(ChannelClosedError, "channel %" PRId64 " closed", id);
PyThread_release_lock(mutex);
return -1;
}
// Convert the object to cross-interpreter data.
data = PyMem_NEW(_PyCrossInterpreterData, 1);
if (data == NULL) {
PyThread_release_lock(mutex);
return -1;
}
if (_PyObject_GetCrossInterpreterData(obj, data) != 0) {
PyThread_release_lock(mutex);
PyMem_Free(data);
return -1;
}
// Add the data to the channel.
res = _channel_add(chan, PyInterpreterState_GetID(interp), data);
PyThread_release_lock(mutex);
if (res != 0) {
_PyCrossInterpreterData_Release(data);
PyMem_Free(data);
return -1;
}
return 0;
}"""
if function != expected:
raise SystemExit("_channel_send must preserve lock/data/error order with C89 declarations")
PY_CHANNEL_SEND_TEST
then
pass "_channel_send preserves lock/data cleanup order"
else
bad "_channel_send preserves lock/data cleanup order"
fi
# _channel_recv hoists locals while preserving lock, null, cleanup and result order.
if python3 - "$REPO_ROOT/cpython/Modules/_xxsubinterpretersmodule.c" <<'PY_CHANNEL_RECV_TEST'
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
start = source.index("_channel_recv(_channels *channels, int64_t id)")
opening = source.index("{", start)
end = source.index(chr(10) + "}" + chr(10), opening) + 2
function = source[opening:end]
expected = """{
PyInterpreterState *interp;
PyThread_type_lock mutex;
_PyChannelState *chan;
_PyCrossInterpreterData *data;
PyObject *obj;
interp = _get_current();
if (interp == NULL) {
return NULL;
}
// Look up the channel.
mutex = NULL;
chan = _channels_lookup(channels, id, &mutex);
if (chan == NULL) {
return NULL;
}
// Past this point we are responsible for releasing the mutex.
// Pop off the next item from the channel.
data = _channel_next(chan, PyInterpreterState_GetID(interp));
PyThread_release_lock(mutex);
if (data == NULL) {
return NULL;
}
// Convert the data back to an object.
obj = _PyCrossInterpreterData_NewObject(data);
_PyCrossInterpreterData_Release(data);
PyMem_Free(data);
if (obj == NULL) {
return NULL;
}
return obj;
}"""
if function != expected:
raise SystemExit("_channel_recv must preserve lock/null/cleanup/result order with C89 declarations")
PY_CHANNEL_RECV_TEST
then
pass "_channel_recv preserves lock/null/cleanup/result order with C89 declarations"
else
bad "_channel_recv preserves lock/null/cleanup/result order with C89 declarations"
fi
# _channel_recv/drop/association hoist declarations without changing resource cleanup.
if python3 - "$REPO_ROOT/cpython/Modules/_xxsubinterpretersmodule.c" <<'PY_CHANNEL_RECV_DROP_TEST'
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
checks = {
"_channel_recv(_channels *channels, int64_t id)": """{
PyInterpreterState *interp;
PyThread_type_lock mutex;
_PyChannelState *chan;
_PyCrossInterpreterData *data;
PyObject *obj;
interp = _get_current();
if (interp == NULL) {
return NULL;
}
// Look up the channel.
mutex = NULL;
chan = _channels_lookup(channels, id, &mutex);
if (chan == NULL) {
return NULL;
}
// Past this point we are responsible for releasing the mutex.
// Pop off the next item from the channel.
data = _channel_next(chan, PyInterpreterState_GetID(interp));
PyThread_release_lock(mutex);
if (data == NULL) {
return NULL;
}
// Convert the data back to an object.
obj = _PyCrossInterpreterData_NewObject(data);
_PyCrossInterpreterData_Release(data);
PyMem_Free(data);
if (obj == NULL) {
return NULL;
}
return obj;
}""",
"_channel_drop(_channels *channels, int64_t id, int send, int recv)": """{
PyInterpreterState *interp;
PyThread_type_lock mutex;
_PyChannelState *chan;
int res;
interp = _get_current();
if (interp == NULL) {
return -1;
}
// Look up the channel.
mutex = NULL;
chan = _channels_lookup(channels, id, &mutex);
if (chan == NULL) {
return -1;
}
// Past this point we are responsible for releasing the mutex.
// Close one or both of the two ends.
res = _channel_close_interpreter(chan, PyInterpreterState_GetID(interp), send-recv);
PyThread_release_lock(mutex);
return res;
}""",
"_channel_is_associated(_channels *channels, int64_t cid, int64_t interp,": """{
_PyChannelState *chan;
_channelend *end;
chan = _channels_lookup(channels, cid, NULL);
if (chan == NULL) {
return -1;
} else if (send && chan->closing != NULL) {
PyErr_Format(ChannelClosedError, "channel %" PRId64 " closed", cid);
return -1;
}
end = _channelend_find(send ? chan->ends->send : chan->ends->recv,
interp, NULL);
return (end != NULL && end->open);
}""",
}
for signature, expected in checks.items():
start = source.index(signature)
opening = source.index("{", start)
end = source.index(chr(10) + "}" + chr(10), opening) + 2
if source[opening:end] != expected:
raise SystemExit("channel recv/drop/association helper changed declaration or cleanup order: " + signature)
PY_CHANNEL_RECV_DROP_TEST
then
pass "_channel_recv/drop/association preserve cleanup order with C89 declarations"
else
bad "_channel_recv/drop/association preserve cleanup order with C89 declarations"
fi
# channelid_new declares end at function entry and assigns it after argument validation.
if python3 - "$REPO_ROOT/cpython/Modules/_xxsubinterpretersmodule.c" <<'PY_CHANNELID_NEW_TEST'
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
start = source.index("channelid_new(PyTypeObject *cls, PyObject *args, PyObject *kwds)")
opening = source.index("{", start)
end = source.index(chr(10) + "}" + chr(10), opening) + 2
expected = """{
static char *kwlist[] = {"id", "send", "recv", "force", "_resolve", NULL};
int64_t cid;
int send = -1;
int recv = -1;
int force = 0;
int resolve = 0;
int end;
if (!PyArg_ParseTupleAndKeywords(args, kwds,
"O&|$pppp:ChannelID.__new__", kwlist,
channel_id_converter, &cid, &send, &recv, &force, &resolve))
return NULL;
// Handle "send" and "recv".
if (send == 0 && recv == 0) {
PyErr_SetString(PyExc_ValueError,
"'send' and 'recv' cannot both be False");
return NULL;
}
end = 0;
if (send == 1) {
if (recv == 0 || recv == -1) {
end = CHANNEL_SEND;
}
}
else if (recv == 1) {
end = CHANNEL_RECV;
}
return (PyObject *)newchannelid(cls, cid, end, _global_channels(),
force, resolve);
}"""
if source[opening:end] != expected:
raise SystemExit("channelid_new must preserve parse/validation/endpoint selection order")
PY_CHANNELID_NEW_TEST
then
pass "channelid_new preserves parse and endpoint selection order with C89 declarations"
else
bad "channelid_new preserves parse and endpoint selection order with C89 declarations"
fi
# ChannelID hash/comparison use C89 declarations and VC6's 64-bit type spelling.
if python3 - "$REPO_ROOT/cpython/Modules/_xxsubinterpretersmodule.c" <<'PY_CHANNELID_COMPARE_TEST'
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
checks = {
"channelid_hash(PyObject *self)": """{
channelid *cid = (channelid *)self;
PyObject *id = PyLong_FromLongLong(cid->id);
Py_hash_t hash;
if (id == NULL) {
return -1;
}
hash = PyObject_Hash(id);
Py_DECREF(id);
return hash;
}""",
"channelid_richcompare(PyObject *self, PyObject *other, int op)": """{
channelid *cid;
int equal;
if (op != Py_EQ && op != Py_NE) {
Py_RETURN_NOTIMPLEMENTED;
}
if (!PyObject_TypeCheck(self, &ChannelIDtype)) {
Py_RETURN_NOTIMPLEMENTED;
}
cid = (channelid *)self;
if (PyObject_TypeCheck(other, &ChannelIDtype)) {
channelid *othercid = (channelid *)other;
equal = (cid->end == othercid->end) && (cid->id == othercid->id);
}
else if (PyLong_Check(other)) {
int overflow;
PY_LONG_LONG othercid;
/* Fast path */
othercid = PyLong_AsLongLongAndOverflow(other, &overflow);
if (othercid == -1 && PyErr_Occurred()) {
return NULL;
}
equal = !overflow && (othercid >= 0) && (cid->id == othercid);
}
else if (PyNumber_Check(other)) {
PyObject *pyid;
PyObject *res;
pyid = PyLong_FromLongLong(cid->id);
if (pyid == NULL) {
return NULL;
}
res = PyObject_RichCompare(pyid, other, op);
Py_DECREF(pyid);
return res;
}
else {
Py_RETURN_NOTIMPLEMENTED;
}
if ((op == Py_EQ && equal) || (op == Py_NE && !equal)) {
Py_RETURN_TRUE;
}
Py_RETURN_FALSE;
}""",
}
for signature, expected in checks.items():
start = source.index(signature)
opening = source.index("{", start)
end = source.index(chr(10) + "}" + chr(10), opening) + 2
if source[opening:end] != expected:
raise SystemExit("ChannelID comparison helper changed error/evaluation order or VC6 type spelling: " + signature)
PY_CHANNELID_COMPARE_TEST
then
pass "ChannelID hash/comparison preserve behavior with VC6-compatible declarations"
else
bad "ChannelID hash/comparison preserve behavior with VC6-compatible declarations"
fi
# ChannelID reconstruction helpers hoist locals while keeping import/refcount timing.
if python3 - "$REPO_ROOT/cpython/Modules/_xxsubinterpretersmodule.c" <<'PY_CHANNELID_RECONSTRUCT_TEST'
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
checks = {
"_channel_from_cid(PyObject *cid, int end)": """{
PyObject *highlevel = PyImport_ImportModule("interpreters");
const char *clsname;
PyObject *cls;
PyObject *chan;
if (highlevel == NULL) {
PyErr_Clear();
highlevel = PyImport_ImportModule("test.support.interpreters");
if (highlevel == NULL) {
return NULL;
}
}
clsname = (end == CHANNEL_RECV) ? "RecvChannel" :
"SendChannel";
cls = PyObject_GetAttrString(highlevel, clsname);
Py_DECREF(highlevel);
if (cls == NULL) {
return NULL;
}
chan = PyObject_CallFunctionObjArgs(cls, cid, NULL);
Py_DECREF(cls);
if (chan == NULL) {
return NULL;
}
return chan;
}""",
"_channelid_from_xid(_PyCrossInterpreterData *data)": """{
struct _channelid_xid *xid = (struct _channelid_xid *)data->data;
PyObject *cid;
PyObject *chan;
// Note that we do not preserve the "resolve" flag.
cid = (PyObject *)newchannelid(&ChannelIDtype, xid->id, xid->end,
_global_channels(), 0, 0);
if (xid->end == 0) {
return cid;
}
if (!xid->resolve) {
return cid;
}
/* Try returning a high-level channel end but fall back to the ID. */
chan = _channel_from_cid(cid, xid->end);
if (chan == NULL) {
PyErr_Clear();
return cid;
}
Py_DECREF(cid);
return chan;
}""",
}
for signature, expected in checks.items():
start = source.index(signature)
opening = source.index("{", start)
end = source.index(chr(10) + "}" + chr(10), opening) + 2
if source[opening:end] != expected:
raise SystemExit("ChannelID reconstruction must preserve import/refcount/resolve behavior: " + signature)
PY_CHANNELID_RECONSTRUCT_TEST
then
pass "ChannelID reconstruction preserves import/refcount behavior with C89 declarations"
else
bad "ChannelID reconstruction preserves import/refcount behavior with C89 declarations"
fi
# _is_running fetches current_frame only after its assertions while declaring locals in C89 order.
if python3 - "$REPO_ROOT/cpython/Modules/_xxsubinterpretersmodule.c" <<'PY_IS_RUNNING_TEST'
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
start = source.index("_is_running(PyInterpreterState *interp)")
opening = source.index("{", start)
end = source.index(chr(10) + "}" + chr(10), opening) + 2
expected = """{
PyThreadState *tstate = PyInterpreterState_ThreadHead(interp);
_PyInterpreterFrame *frame;
if (PyThreadState_Next(tstate) != NULL) {
PyErr_SetString(PyExc_RuntimeError,
"interpreter has more than one thread");
return -1;
}
assert(!PyErr_Occurred());
frame = tstate->cframe->current_frame;
if (frame == NULL) {
return 0;
}
return 1;
}"""
if source[opening:end] != expected:
raise SystemExit("_is_running must retain thread check, assert, and frame lookup order")
PY_IS_RUNNING_TEST
then
pass "_is_running preserves validation order with C89 declarations"
else
bad "_is_running preserves validation order with C89 declarations"
fi
# _run_script hoists locals across labels while keeping all initialization at original control-flow points.
if python3 - "$REPO_ROOT/cpython/Modules/_xxsubinterpretersmodule.c" <<'PY_RUN_SCRIPT_TEST'
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
start = source.index("_run_script(PyInterpreterState *interp, const char *codestr,")
opening = source.index("{", start)
end = source.index(chr(10) + "}" + chr(10), opening) + 2
expected = """{
PyObject *exctype = NULL;
PyObject *excval = NULL;
PyObject *tb = NULL;
PyObject *ns;
PyObject *result;
_sharedexception *sharedexc;
PyObject *main_mod = _PyInterpreterState_GetMainModule(interp);
if (main_mod == NULL) {
goto error;
}
ns = PyModule_GetDict(main_mod); // borrowed
Py_DECREF(main_mod);
if (ns == NULL) {
goto error;
}
Py_INCREF(ns);
// Apply the cross-interpreter data.
if (shared != NULL) {
if (_sharedns_apply(shared, ns) != 0) {
Py_DECREF(ns);
goto error;
}
}
// Run the string (see PyRun_SimpleStringFlags).
result = PyRun_StringFlags(codestr, Py_file_input, ns, ns, NULL);
Py_DECREF(ns);
if (result == NULL) {
goto error;
}
else {
Py_DECREF(result); // We throw away the result.
}
*exc = NULL;
return 0;
error:
PyErr_Fetch(&exctype, &excval, &tb);
sharedexc = _sharedexception_bind(exctype, excval, tb);
Py_XDECREF(exctype);
Py_XDECREF(excval);
Py_XDECREF(tb);
if (sharedexc == NULL) {
fprintf(stderr, "RunFailedError: script raised an uncaught exception");
PyErr_Clear();
sharedexc = NULL;
}
else {
assert(!PyErr_Occurred());
}
*exc = sharedexc;
return -1;
}"""
if source[opening:end] != expected:
raise SystemExit("_run_script must preserve error-label, namespace, and result handling order")
PY_RUN_SCRIPT_TEST
then
pass "_run_script preserves error handling with C89 declarations"
else
bad "_run_script preserves error handling with C89 declarations"
fi
# _run_script_in_interpreter keeps interpreter switching/exception propagation order with C89 locals.
if python3 - "$REPO_ROOT/cpython/Modules/_xxsubinterpretersmodule.c" <<'PY_RUN_SCRIPT_INTERP_TEST'
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
start = source.index("_run_script_in_interpreter(PyInterpreterState *interp, const char *codestr,")
opening = source.index("{", start)
end = source.index(chr(10) + "}" + chr(10), opening) + 2
expected = """{
_sharedns *shared;
PyThreadState *save_tstate;
PyThreadState *tstate;
_sharedexception *exc;
int result;
if (_ensure_not_running(interp) < 0) {
return -1;
}
shared = _get_shared_ns(shareables);
if (shared == NULL && PyErr_Occurred()) {
return -1;
}
// Switch to interpreter.
save_tstate = NULL;
if (interp != PyInterpreterState_Get()) {
// XXX Using the "head" thread isn't strictly correct.
tstate = PyInterpreterState_ThreadHead(interp);
// XXX Possible GILState issues?
save_tstate = PyThreadState_Swap(tstate);
}
// Run the script.
exc = NULL;
result = _run_script(interp, codestr, shared, &exc);
// Switch back.
if (save_tstate != NULL) {
PyThreadState_Swap(save_tstate);
}
// Propagate any exception out to the caller.
if (exc != NULL) {
_sharedexception_apply(exc, RunFailedError);
_sharedexception_free(exc);
}
else if (result != 0) {
// We were unable to allocate a shared exception.
PyErr_NoMemory();
}
if (shared != NULL) {
_sharedns_free(shared);
}
return result;
}"""
if source[opening:end] != expected:
raise SystemExit("_run_script_in_interpreter must preserve switch/exception/shared-state order")
PY_RUN_SCRIPT_INTERP_TEST
then
pass "_run_script_in_interpreter preserves behavior with C89 declarations"
else
bad "_run_script_in_interpreter preserves behavior with C89 declarations"
fi
# Interpreter lifecycle entry points hoist locals without changing thread-state swap/cleanup order.
if python3 - "$REPO_ROOT/cpython/Modules/_xxsubinterpretersmodule.c" <<'PY_INTERP_LIFECYCLE_TEST'
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
checks = {
"interp_create(PyObject *self, PyObject *args, PyObject *kwds)": """{
static char *kwlist[] = {"isolated", NULL};
int isolated = 1;
PyThreadState *save_tstate;
PyThreadState *tstate;
PyInterpreterState *interp;
PyObject *idobj;
if (!PyArg_ParseTupleAndKeywords(args, kwds, "|$i:create", kwlist,
&isolated)) {
return NULL;
}
// Create and initialize the new interpreter.
save_tstate = _PyThreadState_GET();
// XXX Possible GILState issues?
tstate = _Py_NewInterpreter(isolated);
PyThreadState_Swap(save_tstate);
if (tstate == NULL) {
/* Since no new thread state was created, there is no exception to
propagate; raise a fresh one after swapping in the old thread
state. */
PyErr_SetString(PyExc_RuntimeError, "interpreter creation failed");
return NULL;
}
interp = PyThreadState_GetInterpreter(tstate);
idobj = _PyInterpreterState_GetIDObject(interp);
if (idobj == NULL) {
// XXX Possible GILState issues?
save_tstate = PyThreadState_Swap(tstate);
Py_EndInterpreter(tstate);
PyThreadState_Swap(save_tstate);
return NULL;
}
_PyInterpreterState_RequireIDRef(interp, 1);
return idobj;
}""",
"interp_destroy(PyObject *self, PyObject *args, PyObject *kwds)": """{
static char *kwlist[] = {"id", NULL};
PyObject *id;
PyInterpreterState *interp;
PyInterpreterState *current;
PyThreadState *tstate;
PyThreadState *save_tstate;
// XXX Use "L" for id?
if (!PyArg_ParseTupleAndKeywords(args, kwds,
"O:destroy", kwlist, &id)) {
return NULL;
}
// Look up the interpreter.
interp = _PyInterpreterID_LookUp(id);
if (interp == NULL) {
return NULL;
}
// Ensure we don't try to destroy the current interpreter.
current = _get_current();
if (current == NULL) {
return NULL;
}
if (interp == current) {
PyErr_SetString(PyExc_RuntimeError,
"cannot destroy the current interpreter");
return NULL;
}
// Ensure the interpreter isn't running.
/* XXX We *could* support destroying a running interpreter but
aren't going to worry about it for now. */
if (_ensure_not_running(interp) < 0) {
return NULL;
}
// Destroy the interpreter.
tstate = PyInterpreterState_ThreadHead(interp);
// XXX Possible GILState issues?
save_tstate = PyThreadState_Swap(tstate);
Py_EndInterpreter(tstate);
PyThreadState_Swap(save_tstate);
Py_RETURN_NONE;
}""",
"interp_list_all(PyObject *self, PyObject *Py_UNUSED(ignored))": """{
PyObject *ids, *id;
PyInterpreterState *interp;
int res;
ids = PyList_New(0);
if (ids == NULL) {
return NULL;
}
interp = PyInterpreterState_Head();
while (interp != NULL) {
id = _PyInterpreterState_GetIDObject(interp);
if (id == NULL) {
Py_DECREF(ids);
return NULL;
}
// insert at front of list
res = PyList_Insert(ids, 0, id);
Py_DECREF(id);
if (res < 0) {
Py_DECREF(ids);
return NULL;
}
interp = PyInterpreterState_Next(interp);
}
return ids;
}""",
}
for signature, expected in checks.items():
start = source.index(signature)
opening = source.index("{", start)
end = source.index(chr(10) + "}" + chr(10), opening) + 2
if source[opening:end] != expected:
raise SystemExit("interpreter lifecycle helper changed thread-state/list cleanup order: " + signature)
PY_INTERP_LIFECYCLE_TEST
then
pass "interpreter lifecycle entry points preserve swap/order with C89 declarations"
else
bad "interpreter lifecycle entry points preserve swap/order with C89 declarations"
fi
# interp_run_string keeps parse, lookup, and UTF-8 extraction order with C89 declarations.
if python3 - "$REPO_ROOT/cpython/Modules/_xxsubinterpretersmodule.c" <<'PY_INTERP_RUN_STRING_TEST'
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
start = source.index("interp_run_string(PyObject *self, PyObject *args, PyObject *kwds)")
opening = source.index("{", start)
end = source.index(chr(10) + "}" + chr(10), opening) + 2
expected = """{
static char *kwlist[] = {"id", "script", "shared", NULL};
PyObject *id, *code;
PyObject *shared = NULL;
PyInterpreterState *interp;
Py_ssize_t size;
const char *codestr;
if (!PyArg_ParseTupleAndKeywords(args, kwds,
"OU|O:run_string", kwlist,
&id, &code, &shared)) {
return NULL;
}
// Look up the interpreter.
interp = _PyInterpreterID_LookUp(id);
if (interp == NULL) {
return NULL;
}
// Extract code.
codestr = PyUnicode_AsUTF8AndSize(code, &size);
if (codestr == NULL) {
return NULL;
}
if (strlen(codestr) != (size_t)size) {
PyErr_SetString(PyExc_ValueError,
"source code string cannot contain null bytes");
return NULL;
}
// Run the code in the interpreter.
if (_run_script_in_interpreter(interp, codestr, shared) != 0) {
return NULL;
}
Py_RETURN_NONE;
}"""
if source[opening:end] != expected:
raise SystemExit("interp_run_string must preserve parse/lookup/UTF-8 validation order")
PY_INTERP_RUN_STRING_TEST
then
pass "interp_run_string preserves lookup/validation order with C89 declarations"
else
bad "interp_run_string preserves lookup/validation order with C89 declarations"
fi
# Remaining interpreter/channel list entry points hoist C89 locals without shifting checks or cleanup.
if python3 - "$REPO_ROOT/cpython/Modules/_xxsubinterpretersmodule.c" <<'PY_LIST_ENTRYPOINTS_TEST'
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
checks = {
"interp_is_running(PyObject *self, PyObject *args, PyObject *kwds)": """{
static char *kwlist[] = {"id", NULL};
PyObject *id;
PyInterpreterState *interp;
int is_running;
if (!PyArg_ParseTupleAndKeywords(args, kwds,
"O:is_running", kwlist, &id)) {
return NULL;
}
interp = _PyInterpreterID_LookUp(id);
if (interp == NULL) {
return NULL;
}
is_running = _is_running(interp);
if (is_running < 0) {
return NULL;
}
if (is_running) {
Py_RETURN_TRUE;
}
Py_RETURN_FALSE;
}""",
"channel_create(PyObject *self, PyObject *Py_UNUSED(ignored))": """{
int64_t cid = _channel_create(&_globals.channels);
PyObject *id;
if (cid < 0) {
return NULL;
}
id = (PyObject *)newchannelid(&ChannelIDtype, cid, 0,
&_globals.channels, 0, 0);
if (id == NULL) {
if (_channel_destroy(&_globals.channels, cid) != 0) {
// XXX issue a warning?
}
return NULL;
}
assert(((channelid *)id)->channels != NULL);
return id;
}""",
"channel_list_all(PyObject *self, PyObject *Py_UNUSED(ignored))": """{
int64_t count = 0;
int64_t *cids = _channels_list_all(&_globals.channels, &count);
PyObject *ids;
int64_t *cur;
int64_t i;
if (cids == NULL) {
if (count == 0) {
return PyList_New(0);
}
return NULL;
}
ids = PyList_New((Py_ssize_t)count);
if (ids == NULL) {
goto finally;
}
cur = cids;
for (i=0; i < count; cur++, i++) {
PyObject *id = (PyObject *)newchannelid(&ChannelIDtype, *cur, 0,
&_globals.channels, 0, 0);
if (id == NULL) {
Py_DECREF(ids);
ids = NULL;
break;
}
PyList_SET_ITEM(ids, i, id);
}
finally:
PyMem_Free(cids);
return ids;
}""",
"channel_list_interpreters(PyObject *self, PyObject *args, PyObject *kwds)": """{
static char *kwlist[] = {"cid", "send", NULL};
int64_t cid; /* Channel ID */
int send = 0; /* Send or receive end? */
int64_t id;
PyObject *ids, *id_obj;
PyInterpreterState *interp;
int res;
if (!PyArg_ParseTupleAndKeywords(
args, kwds, "O&$p:channel_list_interpreters",
kwlist, channel_id_converter, &cid, &send)) {
return NULL;
}
ids = PyList_New(0);
if (ids == NULL) {
goto except;
}
interp = PyInterpreterState_Head();
while (interp != NULL) {
id = PyInterpreterState_GetID(interp);
assert(id >= 0);
res = _channel_is_associated(&_globals.channels, cid, id, send);
if (res < 0) {
goto except;
}
if (res) {
id_obj = _PyInterpreterState_GetIDObject(interp);
if (id_obj == NULL) {
goto except;
}
res = PyList_Insert(ids, 0, id_obj);
Py_DECREF(id_obj);
if (res < 0) {
goto except;
}
}
interp = PyInterpreterState_Next(interp);
}
goto finally;
except:
Py_XDECREF(ids);
ids = NULL;
finally:
return ids;
}""",
}
for signature, expected in checks.items():
start = source.index(signature)
opening = source.index("{", start)
end = source.index(chr(10) + "}" + chr(10), opening) + 2
if source[opening:end] != expected:
raise SystemExit("interpreter/channel list helper changed checks, ownership, or cleanup order: " + signature)
PY_LIST_ENTRYPOINTS_TEST
then
pass "interpreter/channel list entry points preserve behavior with C89 declarations"
else
bad "interpreter/channel list entry points preserve behavior with C89 declarations"
fi
# Channel receive/module initialization hoist locals while retaining default/refcount and init order.
if python3 - "$REPO_ROOT/cpython/Modules/_xxsubinterpretersmodule.c" <<'PY_CHANNEL_RECV_INIT_TEST'
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
checks = {
"channel_recv(PyObject *self, PyObject *args, PyObject *kwds)": """{
static char *kwlist[] = {"cid", "default", NULL};
int64_t cid;
PyObject *dflt = NULL;
PyObject *obj;
if (!PyArg_ParseTupleAndKeywords(args, kwds, "O&|O:channel_recv", kwlist,
channel_id_converter, &cid, &dflt)) {
return NULL;
}
Py_XINCREF(dflt);
obj = _channel_recv(&_globals.channels, cid);
if (obj != NULL) {
Py_XDECREF(dflt);
return obj;
} else if (PyErr_Occurred()) {
Py_XDECREF(dflt);
return NULL;
} else if (dflt != NULL) {
return dflt;
} else {
PyErr_Format(ChannelEmptyError, "channel %" PRId64 " is empty", cid);
return NULL;
}
}""",
"PyInit__xxsubinterpreters(void)": """{
PyObject *module;
PyObject *ns;
if (_init_globals() != 0) {
return NULL;
}
/* Initialize types */
if (PyType_Ready(&ChannelIDtype) != 0) {
return NULL;
}
/* Create the module */
module = PyModule_Create(&interpretersmodule);
if (module == NULL) {
return NULL;
}
/* Add exception types */
ns = PyModule_GetDict(module); // borrowed
if (interp_exceptions_init(ns) != 0) {
return NULL;
}
if (channel_exceptions_init(ns) != 0) {
return NULL;
}
/* Add other types */
Py_INCREF(&ChannelIDtype);
if (PyDict_SetItemString(ns, "ChannelID", (PyObject *)&ChannelIDtype) != 0) {
return NULL;
}
Py_INCREF(&_PyInterpreterID_Type);
if (PyDict_SetItemString(ns, "InterpreterID", (PyObject *)&_PyInterpreterID_Type) != 0) {
return NULL;
}
if (_PyCrossInterpreterData_RegisterClass(&ChannelIDtype, _channelid_shared)) {
return NULL;
}
return module;
}""",
}
for signature, expected in checks.items():
start = source.index(signature)
opening = source.index("{", start)
end = source.index(chr(10) + "}" + chr(10), opening) + 2
if source[opening:end] != expected:
raise SystemExit("channel receive/module init changed refcount, exception, or registration order: " + signature)
PY_CHANNEL_RECV_INIT_TEST
then
pass "channel receive/module initialization preserve behavior with C89 declarations"
else
bad "channel receive/module initialization preserve behavior with C89 declarations"
fi
# fileio offset types use VC6's established 64-bit alias; Clinic local is declared before statements.
if python3 - "$REPO_ROOT/cpython/Modules/_io/_iomodule.h" "$REPO_ROOT/cpython/Modules/_io/clinic/fileio.c.h" <<'PY_FILEIO_VC6_TEST'
import re
import sys
from pathlib import Path
header = Path(sys.argv[1]).read_text()
clinic = Path(sys.argv[2]).read_text()
if "typedef PY_LONG_LONG Py_off_t;" not in header or "# define PY_OFF_T_COMPAT PY_LONG_LONG" not in header:
raise SystemExit("fileio offset typedef/compat alias must use PY_LONG_LONG for VC6")
if "# define PY_OFF_T_MAX PY_LLONG_MAX" not in header or "# define PY_OFF_T_MIN PY_LLONG_MIN" not in header:
raise SystemExit("Windows file offset bounds must use the VC6-compatible PY_LLONG aliases")
win_start = header.index("/* Windows uses long long for offsets */")
win_end = header.index("\n#else\n", win_start)
windows = header[win_start:win_end]
if re.search(r"typedef\s+long\s+long\s+Py_off_t|#\s*define\s+PY_OFF_T_COMPAT\s+long\s+long", windows):
raise SystemExit("raw long long must not remain in Windows offset type declarations")
start = clinic.index("_io_FileIO___init__(PyObject *self, PyObject *args, PyObject *kwargs)")
end = clinic.index("exit:", start)
body = clinic[start:end]
if body.count("Py_ssize_t mode_length;") != 1:
raise SystemExit("FileIO mode_length must have exactly one declaration")
if not (body.index("Py_ssize_t mode_length;") < body.index("fastargs =") < body.index("if (fastargs[1])") < body.index("mode = PyUnicode_AsUTF8AndSize")):
raise SystemExit("FileIO mode length must remain assigned only after optional-mode validation")
PY_FILEIO_VC6_TEST
then
pass "fileio offset and Clinic declaration invariants pass"
else
bad "fileio offset and Clinic declaration invariants pass"
fi
# resize_buffer keeps its allocation-size read after assertions with a C89 local declaration.
if python3 - "$REPO_ROOT/cpython/Modules/_io/bytesio.c" <<'PY_BYTESIO_RESIZE_TEST'
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
start = source.index("resize_buffer(bytesio *self, size_t size)")
opening = source.index("{", start)
end = source.index(chr(10) + "}" + chr(10), opening) + 2
expected = """{
size_t alloc;
assert(self->buf != NULL);
assert(self->exports == 0);
/* Here, unsigned types are used to avoid dealing with signed integer
overflow, which is undefined in C. */
alloc = PyBytes_GET_SIZE(self->buf);
/* For simplicity, stay in the range of the signed type. Anyway, Python
doesn't allow strings to be longer than this. */
if (size > PY_SSIZE_T_MAX)
goto overflow;
if (size < alloc / 2) {
/* Major downsize; resize down to exact size. */
alloc = size + 1;
}
else if (size < alloc) {
/* Within allocated size; quick exit */
return 0;
}
else if (size <= alloc * 1.125) {
/* Moderate upsize; overallocate similar to list_resize() */
alloc = size + (size >> 3) + (size < 9 ? 3 : 6);
}
else {
/* Major upsize; resize up to exact size */
alloc = size + 1;
}
if (alloc > ((size_t)-1) / sizeof(char))
goto overflow;
if (SHARED_BUF(self)) {
if (unshare_buffer(self, alloc) < 0)
return -1;
}
else {
if (_PyBytes_Resize(&self->buf, alloc) < 0)
return -1;
}
return 0;
overflow:
PyErr_SetString(PyExc_OverflowError,
"new buffer size too large");
return -1;
}"""
if source[opening:end] != expected:
raise SystemExit("resize_buffer must preserve overflow/resize paths and allocation read timing")
PY_BYTESIO_RESIZE_TEST
then
pass "resize_buffer preserves allocation/overflow behavior with C89 declarations"
else
bad "resize_buffer preserves allocation/overflow behavior with C89 declarations"
fi
# write_bytes keeps the buffer acquire, length read, position assertion, and cleanup order with C89 locals.
if python3 - "$REPO_ROOT/cpython/Modules/_io/bytesio.c" <<'PY_BYTESIO_WRITE_TEST'
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
start = source.index("write_bytes(bytesio *self, PyObject *b)")
opening = source.index("{", start)
end = source.index(chr(10) + "}" + chr(10), opening) + 2
expected = """{
Py_buffer buf;
Py_ssize_t len;
size_t endpos;
if (check_closed(self)) {
return -1;
}
if (check_exports(self)) {
return -1;
}
if (PyObject_GetBuffer(b, &buf, PyBUF_CONTIG_RO) < 0) {
return -1;
}
len = buf.len;
if (len == 0) {
goto done;
}
assert(self->pos >= 0);
endpos = (size_t)self->pos + len;
if (endpos > (size_t)PyBytes_GET_SIZE(self->buf)) {
if (resize_buffer(self, endpos) < 0) {
len = -1;
goto done;
}
}
else if (SHARED_BUF(self)) {
if (unshare_buffer(self, Py_MAX(endpos, (size_t)self->string_size)) < 0) {
len = -1;
goto done;
}
}
if (self->pos > self->string_size) {
/* In case of overseek, pad with null bytes the buffer region between
the end of stream and the current position.
0 lo string_size hi
| |<---used--->|<----------available----------->|
| | <--to pad-->|<---to write---> |
0 buf position
*/
memset(PyBytes_AS_STRING(self->buf) + self->string_size, '\\0',
(self->pos - self->string_size) * sizeof(char));
}
/* Copy the data to the internal buffer, overwriting some of the existing
data if self->pos < self->string_size. */
memcpy(PyBytes_AS_STRING(self->buf) + self->pos, buf.buf, len);
self->pos = endpos;
/* Set the new length of the internal string if it has changed. */
if ((size_t)self->string_size < endpos) {
self->string_size = endpos;
}
done:
PyBuffer_Release(&buf);
return len;
}"""
if source[opening:end] != expected:
raise SystemExit("write_bytes must preserve acquisition, position, resizing, and buffer release order")
PY_BYTESIO_WRITE_TEST
then
pass "write_bytes preserves buffer lifetime and resize order with C89 declarations"
else
bad "write_bytes preserves buffer lifetime and resize order with C89 declarations"
fi
# IncrementalNewlineDecoder's 64-bit flags use CPython's VC6-compatible integer spelling.
if python3 - "$REPO_ROOT/cpython/Modules/_io/textio.c" <<'PY_TEXTIO_FLAGS_TEST'
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
for signature in (
"_io_IncrementalNewlineDecoder_getstate_impl(nldecoder_object *self)",
"_io_IncrementalNewlineDecoder_setstate(nldecoder_object *self,",
):
start = source.index(signature)
opening = source.index("{", start)
end = source.index(chr(10) + "}" + chr(10), opening) + 2
body = source[opening:end]
if "unsigned PY_LONG_LONG flag;" not in body or "unsigned long long flag;" in body:
raise SystemExit("IncrementalNewlineDecoder flag must retain unsigned 64-bit width through PY_LONG_LONG")
PY_TEXTIO_FLAGS_TEST
then
pass "textio decoder flags use VC6-compatible unsigned 64-bit types"
else
bad "textio decoder flags use VC6-compatible unsigned 64-bit types"
fi
# TextIOWrapper encoder/error helpers hoist locals without moving codec calls or early returns.
if python3 - "$REPO_ROOT/cpython/Modules/_io/textio.c" <<'PY_TEXTIO_ERROR_HELPERS_TEST'
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
checks = {
"_textiowrapper_fix_encoder_state(textio *self)": """{
PyObject *cookieObj;
PyObject *res;
int cmp;
if (!self->seekable || !self->encoder) {
return 0;
}
self->encoding_start_of_stream = 1;
cookieObj = PyObject_CallMethodNoArgs(
self->buffer, &_Py_ID(tell));
if (cookieObj == NULL) {
return -1;
}
cmp = PyObject_RichCompareBool(cookieObj, _PyLong_GetZero(), Py_EQ);
Py_DECREF(cookieObj);
if (cmp < 0) {
return -1;
}
if (cmp == 0) {
self->encoding_start_of_stream = 0;
res = PyObject_CallMethodOneArg(
self->encoder, &_Py_ID(setstate), _PyLong_GetZero());
if (res == NULL) {
return -1;
}
Py_DECREF(res);
}
return 0;
}""",
"io_check_errors(PyObject *errors)": """{
PyInterpreterState *interp;
Py_ssize_t name_length;
const char *name;
PyObject *handler;
assert(errors != NULL && errors != Py_None);
interp = _PyInterpreterState_GET();
#ifndef Py_DEBUG
/* In release mode, only check in development mode (-X dev) */
if (!_PyInterpreterState_GetConfig(interp)->dev_mode) {
return 0;
}
#else
/* Always check in debug mode */
#endif
/* Avoid calling PyCodec_LookupError() before the codec registry is ready:
before_PyUnicode_InitEncodings() is called. */
if (!interp->unicode.fs_codec.encoding) {
return 0;
}
name = PyUnicode_AsUTF8AndSize(errors, &name_length);
if (name == NULL) {
return -1;
}
if (strlen(name) != (size_t)name_length) {
PyErr_SetString(PyExc_ValueError, "embedded null character in errors");
return -1;
}
handler = PyCodec_LookupError(name);
if (handler != NULL) {
Py_DECREF(handler);
return 0;
}
return -1;
}""",
}
for signature, expected in checks.items():
start = source.index(signature)
opening = source.index("{", start)
end = source.index(chr(10) + "}" + chr(10), opening) + 2
if source[opening:end] != expected:
raise SystemExit("textio helper changed codec/error lookup or reference-handling order: " + signature)
PY_TEXTIO_ERROR_HELPERS_TEST
then
pass "textio encoder/error helpers preserve lookup and return order with C89 locals"
else
bad "textio encoder/error helpers preserve lookup and return order with C89 locals"
fi
# TextIOWrapper init keeps errors UTF-8 extraction after validation while declaring its locals in C89 order.
if python3 - "$REPO_ROOT/cpython/Modules/_io/textio.c" <<'PY_TEXTIO_INIT_ERRORS_TEST'
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
start = source.index("_io_TextIOWrapper___init___impl(textio *self, PyObject *buffer,")
opening = source.index("{", start)
end = source.index("\n}\n", opening) + 2
body = source[opening:end]
for declaration in ("Py_ssize_t errors_len;", "const char *errors_str;"):
if body.count(declaration) != 1 or body.index(declaration) > body.index("self->ok = 0;"):
raise SystemExit("TextIOWrapper init locals must be declared before executable statements")
if "const char *errors_str = PyUnicode_AsUTF8AndSize" in body:
raise SystemExit("TextIOWrapper errors conversion must remain assigned after validation")
markers = ("if (errors == Py_None)", "else if (io_check_errors(errors))", "errors_str = PyUnicode_AsUTF8AndSize(errors, &errors_len);")
positions = [body.index(marker) for marker in markers]
if positions != sorted(positions):
raise SystemExit("TextIOWrapper errors conversion must remain after errors validation")
PY_TEXTIO_INIT_ERRORS_TEST
then
pass "TextIOWrapper init keeps errors conversion after validation with C89 locals"
else
bad "TextIOWrapper init keeps errors conversion after validation with C89 locals"
fi
# textiowrapper_change_encoding hoists codec locals while preserving reference cleanup on every failure.
if python3 - "$REPO_ROOT/cpython/Modules/_io/textio.c" <<'PY_TEXTIO_CHANGE_ENCODING_TEST'
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
start = source.index("textiowrapper_change_encoding(textio *self, PyObject *encoding,")
opening = source.index("{", start)
end = source.index(chr(10) + "}" + chr(10), opening) + 2
expected = """{
const char *c_encoding;
const char *c_errors;
PyObject *codec_info;
/* Use existing settings where new settings are not specified */
if (encoding == Py_None && errors == Py_None && !newline_changed) {
return 0; // no change
}
if (encoding == Py_None) {
encoding = self->encoding;
if (errors == Py_None) {
errors = self->errors;
}
Py_INCREF(encoding);
}
else {
if (_PyUnicode_EqualToASCIIString(encoding, "locale")) {
encoding = _Py_GetLocaleEncodingObject();
if (encoding == NULL) {
return -1;
}
} else {
Py_INCREF(encoding);
}
if (errors == Py_None) {
errors = &_Py_ID(strict);
}
}
Py_INCREF(errors);
c_encoding = PyUnicode_AsUTF8(encoding);
if (c_encoding == NULL) {
Py_DECREF(encoding);
Py_DECREF(errors);
return -1;
}
c_errors = PyUnicode_AsUTF8(errors);
if (c_errors == NULL) {
Py_DECREF(encoding);
Py_DECREF(errors);
return -1;
}
// Create new encoder & decoder
codec_info = _PyCodec_LookupTextEncoding(
c_encoding, "codecs.open()");
if (codec_info == NULL) {
Py_DECREF(encoding);
Py_DECREF(errors);
return -1;
}
if (_textiowrapper_set_decoder(self, codec_info, c_errors) != 0 ||
_textiowrapper_set_encoder(self, codec_info, c_errors) != 0) {
Py_DECREF(codec_info);
Py_DECREF(encoding);
Py_DECREF(errors);
return -1;
}
Py_DECREF(codec_info);
Py_SETREF(self->encoding, encoding);
Py_SETREF(self->errors, errors);
return _textiowrapper_fix_encoder_state(self);
}"""
if source[opening:end] != expected:
raise SystemExit("textiowrapper_change_encoding must preserve codec calls and refcount cleanup order")
PY_TEXTIO_CHANGE_ENCODING_TEST
then
pass "textiowrapper_change_encoding preserves codec/refcount behavior with C89 locals"
else
bad "textiowrapper_change_encoding preserves codec/refcount behavior with C89 locals"
fi
# TextIOWrapper reconfigure hoists its flush result without changing validation or ownership order.
if python3 - "$REPO_ROOT/cpython/Modules/_io/textio.c" <<'PY_TEXTIO_RECONFIGURE_TEST'
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
start = source.index("_io_TextIOWrapper_reconfigure_impl(textio *self, PyObject *encoding,")
opening = source.index("{", start)
end = source.index(chr(10) + "}" + chr(10), opening) + 2
body = source[opening:end]
if body.count("PyObject *res;") != 1 or body.index("PyObject *res;") > body.index("if (encoding"):
raise SystemExit("reconfigure flush result must be declared before executable validation")
if "PyObject *res = PyObject_CallMethodNoArgs" in body:
raise SystemExit("reconfigure flush must remain assigned after validation")
markers = (
"write_through = convert_optional_bool(",
"if (write_through < 0)",
"res = PyObject_CallMethodNoArgs((PyObject *)self, &_Py_ID(flush));",
"Py_DECREF(res);",
"self->b2cratio = 0;",
)
positions = [body.index(marker) for marker in markers]
if positions != sorted(positions):
raise SystemExit("reconfigure must validate optional flags before flush and release the result before state update")
PY_TEXTIO_RECONFIGURE_TEST
then
pass "TextIOWrapper reconfigure preserves validation/flush order with C89 locals"
else
bad "TextIOWrapper reconfigure preserves validation/flush order with C89 locals"
fi
# _textiowrapper_writeflush keeps pending-buffer ownership and write retry order with C89 locals.
if python3 - "$REPO_ROOT/cpython/Modules/_io/textio.c" <<'PY_TEXTIO_WRITEFLUSH_TEST'
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
start = source.index("_textiowrapper_writeflush(textio *self)")
opening = source.index("{", start)
end = source.index(chr(10) + "}" + chr(10), opening) + 2
expected = """{
PyObject *pending;
PyObject *b;
char *buf;
Py_ssize_t pos;
Py_ssize_t i;
PyObject *ret;
if (self->pending_bytes == NULL)
return 0;
pending = self->pending_bytes;
if (PyBytes_Check(pending)) {
b = pending;
Py_INCREF(b);
}
else if (PyUnicode_Check(pending)) {
assert(PyUnicode_IS_ASCII(pending));
assert(PyUnicode_GET_LENGTH(pending) == self->pending_bytes_count);
b = PyBytes_FromStringAndSize(
PyUnicode_DATA(pending), PyUnicode_GET_LENGTH(pending));
if (b == NULL) {
return -1;
}
}
else {
assert(PyList_Check(pending));
b = PyBytes_FromStringAndSize(NULL, self->pending_bytes_count);
if (b == NULL) {
return -1;
}
buf = PyBytes_AsString(b);
pos = 0;
for (i = 0; i < PyList_GET_SIZE(pending); i++) {
PyObject *obj = PyList_GET_ITEM(pending, i);
char *src;
Py_ssize_t len;
if (PyUnicode_Check(obj)) {
assert(PyUnicode_IS_ASCII(obj));
src = PyUnicode_DATA(obj);
len = PyUnicode_GET_LENGTH(obj);
}
else {
assert(PyBytes_Check(obj));
if (PyBytes_AsStringAndSize(obj, &src, &len) < 0) {
Py_DECREF(b);
return -1;
}
}
memcpy(buf + pos, src, len);
pos += len;
}
assert(pos == self->pending_bytes_count);
}
self->pending_bytes_count = 0;
self->pending_bytes = NULL;
Py_DECREF(pending);
do {
ret = PyObject_CallMethodOneArg(self->buffer, &_Py_ID(write), b);
} while (ret == NULL && _PyIO_trap_eintr());
Py_DECREF(b);
// NOTE: We cleared buffer but we don't know how many bytes are actually written
// when an error occurred.
if (ret == NULL)
return -1;
Py_DECREF(ret);
return 0;
}"""
if source[opening:end] != expected:
raise SystemExit("writeflush must preserve buffer ownership, list flattening, retry, and error order")
PY_TEXTIO_WRITEFLUSH_TEST
then
pass "textio writeflush preserves ownership/retry behavior with C89 locals"
else
bad "textio writeflush preserves ownership/retry behavior with C89 locals"
fi
# TextIOWrapper.write declares byte-size and list-append locals before validation; assignment/refcount order stays fixed.
if python3 - "$REPO_ROOT/cpython/Modules/_io/textio.c" <<'PY_TEXTIO_WRITE_IMPL_TEST'
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
start = source.index("_io_TextIOWrapper_write_impl(textio *self, PyObject *text)")
opening = source.index("{", start)
end = source.index(chr(10) + "}" + chr(10), opening) + 2
body = source[opening:end]
for declaration in ("Py_ssize_t bytes_len;", "int list_ret;"):
if body.count(declaration) != 1 or body.index(declaration) > body.index("if (PyUnicode_READY(text)"):
raise SystemExit("write locals must be declared before executable statements")
if "Py_ssize_t bytes_len =" in body or "int ret = PyList_Append" in body:
raise SystemExit("write locals must not be redeclared after statements")
markers = (
"if (b != text && !PyBytes_Check(b))",
"if (b == text)",
"bytes_len = PyBytes_GET_SIZE(b);",
"list_ret = PyList_Append(self->pending_bytes, b);",
)
positions = [body.index(marker) for marker in markers]
append_pos = positions[-1]
decref_pos = body.index("Py_DECREF(b);", append_pos)
append_error_pos = body.index("if (list_ret < 0)", decref_pos)
positions.extend((decref_pos, append_error_pos))
if positions != sorted(positions):
raise SystemExit("write must preserve encoded-value validation, length, append and decref order")
PY_TEXTIO_WRITE_IMPL_TEST
then
pass "TextIOWrapper write preserves validation/append ownership with C89 locals"
else
bad "TextIOWrapper write preserves validation/append ownership with C89 locals"
fi
# textiowrapper_read_chunk preserves snapshot construction and failure cleanup with a C89 declaration.
if python3 - "$REPO_ROOT/cpython/Modules/_io/textio.c" <<'PY_TEXTIO_READ_CHUNK_TEST'
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
start = source.index("textiowrapper_read_chunk(textio *self, Py_ssize_t size_hint)")
opening = source.index("{", start)
end = source.index(chr(10) + "}" + chr(10), opening) + 2
body = source[opening:end]
if body.count("PyObject *snapshot;") != 1 or body.index("PyObject *snapshot;") > body.index("if (self->decoder == NULL)"):
raise SystemExit("read_chunk snapshot must be declared before executable statements")
if "PyObject *snapshot = Py_BuildValue" in body:
raise SystemExit("read_chunk snapshot must remain constructed at its original point")
markers = (
"PyBytes_Concat(&next_input, input_chunk);",
"if (next_input == NULL)",
"snapshot = Py_BuildValue(\"NN\", dec_flags, next_input);",
"if (snapshot == NULL)",
"Py_XSETREF(self->snapshot, snapshot);",
)
positions = [body.index(marker) for marker in markers[:4]]
dec_flags_pos = body.index("dec_flags = NULL;", positions[-1])
snapshot_set_pos = body.index(markers[4], dec_flags_pos)
positions.extend((dec_flags_pos, snapshot_set_pos))
if positions != sorted(positions):
raise SystemExit("read_chunk must preserve snapshot failure handling and reference ownership")
PY_TEXTIO_READ_CHUNK_TEST
then
pass "textio read_chunk preserves snapshot/error ownership with C89 locals"
else
bad "textio read_chunk preserves snapshot/error ownership with C89 locals"
fi
# TextIOWrapper.seek declares the zero singleton up front but evaluates it after seekability validation.
if python3 - "$REPO_ROOT/cpython/Modules/_io/textio.c" <<'PY_TEXTIO_SEEK_ZERO_TEST'
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
start = source.index("_io_TextIOWrapper_seek_impl(textio *self, PyObject *cookieObj, int whence)")
opening = source.index("{", start)
end = source.index(chr(10) + "}" + chr(10), opening) + 2
body = source[opening:end]
if body.count("PyObject *zero;") != 1 or body.index("PyObject *zero;") > body.index("CHECK_ATTACHED(self);"):
raise SystemExit("seek zero singleton must be declared before executable statements")
if "PyObject *zero = _PyLong_GetZero()" in body:
raise SystemExit("seek zero singleton lookup must remain after seekability check")
markers = (
"if (!self->seekable)",
"zero = _PyLong_GetZero(); // borrowed reference",
"switch (whence)",
"PyObject_RichCompareBool(cookieObj, zero, Py_EQ)",
)
positions = [body.index(marker) for marker in markers]
if positions != sorted(positions):
raise SystemExit("seek must preserve validation and zero lookup timing")
PY_TEXTIO_SEEK_ZERO_TEST
then
pass "TextIOWrapper seek preserves validation/singleton timing with C89 local"
else
bad "TextIOWrapper seek preserves validation/singleton timing with C89 local"
fi
# _PyIO_get_console_type hoists locals while keeping path conversion, handle lookup and cleanup at their original points.
if python3 - "$REPO_ROOT/cpython/Modules/_io/winconsoleio.c" <<'PY_WINCONSOLE_TYPE_TEST'
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
start = source.index("_PyIO_get_console_type(PyObject *path_or_fd)")
opening = source.index("{", start)
end = source.index(chr(10) + "}" + chr(10), opening) + 2
body = source[opening:end]
first_statement = body.index("fd = PyLong_AsLong(path_or_fd);")
for declaration in (
"int fd;", "HANDLE handle;", "PyObject *decoded;", "wchar_t *decoded_wstr;",
"char m;", "DWORD length;", "wchar_t name_buf[MAX_PATH];", "wchar_t *pname_buf;",
):
if body.count(declaration) != 1 or body.index(declaration) > first_statement:
raise SystemExit("console type locals must be declared before executable statements")
for old in ("int fd = PyLong_AsLong", "HANDLE handle =", "char m =", "wchar_t name_buf[MAX_PATH], *pname_buf = name_buf"):
if old in body:
raise SystemExit("console type locals must not be declared after executable statements")
markers = (
"fd = PyLong_AsLong(path_or_fd);",
"PyErr_Clear();",
"handle = _Py_get_osfhandle_noraise(fd);",
"if (!PyUnicode_FSDecoder(path_or_fd, &decoded))",
"decoded_wstr = PyUnicode_AsWideCharString(decoded, NULL);",
"m = '\\0';",
"length = GetFullPathNameW(decoded_wstr, MAX_PATH, pname_buf, NULL);",
"PyMem_Free(decoded_wstr);",
)
positions = [body.index(marker) for marker in markers]
if positions != sorted(positions):
raise SystemExit("console type lookup and cleanup order must remain unchanged")
PY_WINCONSOLE_TYPE_TEST
then
pass "console type detection preserves control/cleanup order with C89 locals"
else
bad "console type detection preserves control/cleanup order with C89 locals"
fi
# Console byte-buffer helpers keep loop bounds/order while replacing C99 loop declarations.
if python3 - "$REPO_ROOT/cpython/Modules/_io/winconsoleio.c" <<'PY_WINCONSOLE_BUFFER_LOOPS_TEST'
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
def body_for(signature):
start = source.index(signature)
opening = source.index("{", start)
end = source.index(chr(10) + "}" + chr(10), opening) + 2
return source[opening:end]
buflen = body_for("_buflen(winconsoleio *self)")
if buflen.count("DWORD i;") != 1 or buflen.index("DWORD i;") > buflen.index("for (i = 0;"):
raise SystemExit("_buflen loop index must be declared before the loop")
if "for (DWORD i =" in buflen:
raise SystemExit("_buflen must use a C89 loop declaration")
copyfrombuf = body_for("_copyfrombuf(winconsoleio *self, char *buf, DWORD len)")
if copyfrombuf.count("int i;") != 1 or copyfrombuf.index("int i;") > copyfrombuf.index("while (self->buf[0]"):
raise SystemExit("_copyfrombuf loop index must be declared before executable statements")
if "for (int i =" in copyfrombuf:
raise SystemExit("_copyfrombuf must use a C89 loop declaration")
markers = ("while (self->buf[0] && len--)", "for (i = 1;", "self->buf[SMALLBUF - 1] = 0;")
positions = [copyfrombuf.index(marker) for marker in markers]
if positions != sorted(positions):
raise SystemExit("_copyfrombuf must preserve byte shift order")
PY_WINCONSOLE_BUFFER_LOOPS_TEST
then
pass "console buffer helpers preserve loop behavior with C89 declarations"
else
bad "console buffer helpers preserve loop behavior with C89 declarations"
fi
# read_console_w keeps the allow-threads boundaries and call/error order while hoisting C89 locals.
if python3 - "$REPO_ROOT/cpython/Modules/_io/winconsoleio.c" <<'PY_WINCONSOLE_READ_W_TEST'
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
start = source.index("read_console_w(HANDLE handle, DWORD maxlen, DWORD *readlen)")
opening = source.index("{", start)
end = source.index(chr(10) + "}" + chr(10), opening) + 2
body = source[opening:end]
for declaration in ("DWORD off;", "HANDLE hInterruptEvent;"):
if body.count(declaration) != 1 or body.index(declaration) > body.index("wchar_t *buf ="):
raise SystemExit("read_console_w function locals must precede executable statements")
loop = body.index("while (off < maxlen) {")
first_loop_statement = body.index("n = (DWORD)-1;", loop)
for declaration in ("DWORD n;", "DWORD len;", "BOOL res;"):
if body.count(declaration) != 1 or body.index(declaration) < loop or body.index(declaration) > first_loop_statement:
raise SystemExit("read_console_w loop locals must be declared at the start of the loop body")
for old in ("DWORD off = 0;", "DWORD n = (DWORD)-1;", "DWORD len = min(", "BOOL res = ReadConsoleW", "HANDLE hInterruptEvent ="):
if body.count(old) > 1:
raise SystemExit("read_console_w must not redeclare C89 locals")
markers = (
"Py_BEGIN_ALLOW_THREADS",
"off = 0;",
"while (off < maxlen)",
"res = ReadConsoleW(handle, &buf[off], len, &n, NULL);",
"hInterruptEvent = _PyOS_SigintEvent();",
"Py_END_ALLOW_THREADS",
)
positions = [body.index(marker) for marker in markers]
if positions != sorted(positions):
raise SystemExit("read_console_w must preserve thread-release and console-read ordering")
PY_WINCONSOLE_READ_W_TEST
then
pass "read_console_w preserves allow-threads/call order with C89 locals"
else
bad "read_console_w preserves allow-threads/call order with C89 locals"
fi
# readinto hoists only declarations, retaining all evaluation/control/cleanup order.
if python3 - "$REPO_ROOT/cpython/Modules/_io/winconsoleio.c" <<'PY_WINCONSOLE_READINTO_TEST'
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
start = source.index("readinto(winconsoleio *self, char *buf, Py_ssize_t len)")
opening = source.index("{", start)
end = source.index("\n}\n", opening) + 2
body = source[opening:end]
expected = r'''{
HANDLE handle;
DWORD wlen;
DWORD read_len;
DWORD n;
wchar_t *wbuf;
int err;
DWORD u8n;
if (self->fd == -1) {
err_closed();
return -1;
}
if (!self->readable) {
err_mode("reading");
return -1;
}
if (len == 0)
return 0;
if (len > BUFMAX) {
PyErr_Format(PyExc_ValueError, "cannot read more than %d bytes", BUFMAX);
return -1;
}
handle = _Py_get_osfhandle(self->fd);
if (handle == INVALID_HANDLE_VALUE)
return -1;
/* Each character may take up to 4 bytes in the final buffer.
This is highly conservative, but necessary to avoid
failure for any given Unicode input (e.g. \U0010ffff).
If the caller requests fewer than 4 bytes, we buffer one
character.
*/
wlen = (DWORD)(len / 4);
if (wlen == 0) {
wlen = 1;
}
read_len = _copyfrombuf(self, buf, (DWORD)len);
if (read_len) {
buf = &buf[read_len];
len -= read_len;
wlen -= 1;
}
if (len == read_len || wlen == 0)
return read_len;
wbuf = read_console_w(handle, wlen, &n);
if (wbuf == NULL)
return -1;
if (n == 0) {
PyMem_Free(wbuf);
return read_len;
}
err = 0;
u8n = 0;
Py_BEGIN_ALLOW_THREADS
if (len < 4) {
if (WideCharToMultiByte(CP_UTF8, 0, wbuf, n,
self->buf, sizeof(self->buf) / sizeof(self->buf[0]),
NULL, NULL))
u8n = _copyfrombuf(self, buf, (DWORD)len);
} else {
u8n = WideCharToMultiByte(CP_UTF8, 0, wbuf, n,
buf, (DWORD)len, NULL, NULL);
}
if (u8n) {
read_len += u8n;
u8n = 0;
} else {
err = GetLastError();
if (err == ERROR_INSUFFICIENT_BUFFER) {
/* Calculate the needed buffer for a more useful error, as this
means our "/ 4" logic above is insufficient for some input.
*/
u8n = WideCharToMultiByte(CP_UTF8, 0, wbuf, n,
NULL, 0, NULL, NULL);
}
}
Py_END_ALLOW_THREADS
PyMem_Free(wbuf);
if (u8n) {
PyErr_Format(PyExc_SystemError,
"Buffer had room for %zd bytes but %u bytes required",
len, u8n);
return -1;
}
if (err) {
PyErr_SetFromWindowsErr(err);
return -1;
}
return read_len;
}'''
if body != expected:
raise SystemExit("readinto must hoist C89 locals while preserving evaluation/control/cleanup order")
PY_WINCONSOLE_READINTO_TEST
then
pass "readinto preserves evaluation/control/cleanup order with C89 locals"
else
bad "readinto preserves evaluation/control/cleanup order with C89 locals"
fi
# read_console_w detects UTF-16 high surrogates by code-unit range, without Vista-only C3_HIGHSURROGATE.
if python3 - "$REPO_ROOT/cpython/Modules/_io/winconsoleio.c" <<'PY_WINCONSOLE_SURROGATE_TEST'
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
start = source.index("read_console_w(HANDLE handle, DWORD maxlen, DWORD *readlen)")
opening = source.index("{", start)
end = source.index(chr(10) + "}" + chr(10), opening) + 2
body = source[opening:end]
condition = "buf[*readlen - 1] >= 0xD800 &&\n buf[*readlen - 1] <= 0xDBFF"
if condition not in body:
raise SystemExit("read_console_w must detect the full UTF-16 high-surrogate range")
if "C3_HIGHSURROGATE" in body or "GetStringTypeW(CT_CTYPE3" in body:
raise SystemExit("read_console_w must not depend on the Vista-only high-surrogate type flag")
condition_pos = body.index(condition)
block_threads_pos = body.index("Py_BLOCK_THREADS", condition_pos)
realloc_pos = body.index("PyMem_Realloc(buf, maxlen * sizeof(wchar_t))", block_threads_pos)
unblock_threads_pos = body.index("Py_UNBLOCK_THREADS", realloc_pos)
if not condition_pos < realloc_pos < unblock_threads_pos:
raise SystemExit("surrogate-triggered buffer growth must retain its thread/resize ordering")
PY_WINCONSOLE_SURROGATE_TEST
then
pass "console read preserves surrogate-boundary growth on legacy Windows"
else
bad "console read preserves surrogate-boundary growth on legacy Windows"
fi
# readall buffer reallocation and generated console-init mode parsing retain their evaluation points.
if python3 - "$REPO_ROOT/cpython/Modules/_io/winconsoleio.c" "$REPO_ROOT/cpython/Modules/_io/clinic/winconsoleio.c.h" <<'PY_WINCONSOLE_READALL_CLINIC_TEST'
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
clinic = Path(sys.argv[2]).read_text()
start = source.index("_io__WindowsConsoleIO_readall_impl(winconsoleio *self)")
opening = source.index("{", start)
end = source.index(chr(10) + "}" + chr(10), opening) + 2
body = source[opening:end]
loop = body[body.index("while (1) {"):]
first_loop_statement = loop.index("if (len >= (Py_ssize_t)bufsize)")
if loop.count("wchar_t *tmp;") != 1 or loop.index("wchar_t *tmp;") > first_loop_statement:
raise SystemExit("readall temporary buffer pointer must be declared before loop statements")
if "wchar_t *tmp = PyMem_Realloc" in loop:
raise SystemExit("readall reallocation must remain at its original point")
markers = ("bufsize = newsize;", "tmp = PyMem_Realloc(buf,", "if (tmp == NULL)", "buf = tmp;")
positions = [loop.index(marker) for marker in markers]
if positions != sorted(positions):
raise SystemExit("readall must preserve buffer resize and failure order")
start = clinic.index("_io__WindowsConsoleIO___init__(PyObject *self")
opening = clinic.index("{", start)
end = clinic.index(chr(10) + "}" + chr(10), opening) + 2
wrapper = clinic[opening:end]
if wrapper.count("Py_ssize_t mode_length;") != 1 or wrapper.index("Py_ssize_t mode_length;") > wrapper.index("fastargs ="):
raise SystemExit("generated mode_length local must be in the wrapper declaration group")
if "Py_ssize_t mode_length;\n mode =" in wrapper:
raise SystemExit("generated mode_length must not remain declared after validation")
if wrapper.index("if (!PyUnicode_Check(fastargs[1]))") > wrapper.index("mode = PyUnicode_AsUTF8AndSize"):
raise SystemExit("generated mode validation must remain before UTF-8 conversion")
PY_WINCONSOLE_READALL_CLINIC_TEST
then
pass "console readall/Clinic locals preserve buffer and validation order"
else
bad "console readall/Clinic locals preserve buffer and validation order"
fi
# _io_text_encoding assigns preconfig only after warning handling while declaring it in C89 order.
if python3 - "$REPO_ROOT/cpython/Modules/_io/_iomodule.c" <<'PY_IOMODULE_PRECONFIG_TEST'
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
start = source.index("_io_text_encoding_impl(PyObject *module, PyObject *encoding, int stacklevel)")
opening = source.index("{", start)
end = source.index(chr(10) + "}" + chr(10), opening) + 2
body = source[opening:end]
first_statement = body.index("if (encoding == NULL || encoding == Py_None)")
if body.count("const PyPreConfig *preconfig;") != 1 or body.index("const PyPreConfig *preconfig;") > first_statement:
raise SystemExit("preconfig must be declared before executable statements")
if "const PyPreConfig *preconfig =" in body:
raise SystemExit("preconfig lookup must not be initialized early")
markers = (
"PyErr_WarnEx(PyExc_EncodingWarning,",
"preconfig = &_PyRuntime.preconfig;",
"if (preconfig->utf8_mode)",
)
positions = [body.index(marker) for marker in markers]
if positions != sorted(positions):
raise SystemExit("preconfig lookup must remain after warning handling")
PY_IOMODULE_PRECONFIG_TEST
then
pass "io encoding selection preserves warning/preconfig order with C89 local"
else
bad "io encoding selection preserves warning/preconfig order with C89 local"
fi
# _io cleanup/init loops and open Clinic mode length use C89 declarations without shifting work.
if python3 - "$REPO_ROOT/cpython/Modules/_io/_iomodule.c" "$REPO_ROOT/cpython/Modules/_io/clinic/_iomodule.c.h" <<'PY_IOMODULE_C89_TEST'
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
clinic = Path(sys.argv[2]).read_text()
start = source.index("_PyIO_Fini(void)")
opening = source.index("{", start)
end = source.index(chr(10) + "}" + chr(10), opening) + 2
fini = source[opening:end]
if "Py_ssize_t i;" not in fini or "for (i = Py_ARRAY_LENGTH(static_types) - 1;" not in fini:
raise SystemExit("_PyIO_Fini loop index must use C89 declaration order")
if "for (Py_ssize_t i=" in fini:
raise SystemExit("_PyIO_Fini must not declare its index in the for initializer")
start = source.index("PyInit__io(void)")
opening = source.index("{", start)
end = source.index(chr(10) + "}" + chr(10), opening) + 2
init = source[opening:end]
if init.index("size_t i;") > init.index("PyObject *m = PyModule_Create"):
raise SystemExit("PyInit__io loop index must be in the declaration group")
if "for (i = 0; i < Py_ARRAY_LENGTH(static_types); i++)" not in init:
raise SystemExit("PyInit__io must keep the static-type iteration range")
if "for (size_t i=" in init:
raise SystemExit("PyInit__io must not declare its index in the for initializer")
start = clinic.index("_io_open(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames)")
opening = clinic.index("{", start)
end = clinic.index(chr(10) + "}" + chr(10), opening) + 2
wrapper = clinic[opening:end]
if wrapper.count("Py_ssize_t mode_length;") != 1 or wrapper.index("Py_ssize_t mode_length;") > wrapper.index("args = _PyArg_UnpackKeywords"):
raise SystemExit("open Clinic mode_length must be declared before wrapper statements")
if wrapper.index("if (!PyUnicode_Check(args[1]))") > wrapper.index("mode = PyUnicode_AsUTF8AndSize(args[1], &mode_length)"):
raise SystemExit("open mode validation must remain before UTF-8 conversion")
PY_IOMODULE_C89_TEST
then
pass "io module loops/open wrapper preserve C89 behavior"
else
bad "io module loops/open wrapper preserve C89 behavior"
fi
# CJK codec helpers preserve reference cleanup and module field order with VC6-compatible declarations.
if python3 - "$REPO_ROOT/cpython/Modules/cjkcodecs/cjkcodecs.h" <<'PY_CJKCODECS_VC6_TEST'
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
def body_for(signature):
start = source.index(signature)
opening = source.index("{", start)
end = source.index(chr(10) + "}" + chr(10), opening) + 2
return source[opening:end]
getcodecmod = body_for("getmultibytecodec(void)")
if getcodecmod.count("PyObject *mod;") != 1 or getcodecmod.count("PyObject *cofunc;") != 1:
raise SystemExit("getmultibytecodec locals must be declared in C89 order")
if getcodecmod.index("PyObject *cofunc;") > getcodecmod.index("if (mod == NULL)"):
raise SystemExit("getmultibytecodec result local must be declared before executable statements")
if "PyObject *cofunc = PyObject_GetAttrString" in getcodecmod:
raise SystemExit("getmultibytecodec attribute lookup must remain after import/error check")
markers = ("mod = PyImport_ImportModuleNoBlock", "if (mod == NULL)", "cofunc = PyObject_GetAttrString", "Py_DECREF(mod);", "return cofunc;")
positions = [getcodecmod.index(marker) for marker in markers]
if positions != sorted(positions):
raise SystemExit("getmultibytecodec must preserve import/refcount order")
register = body_for("register_maps(PyObject *module)")
if register.count("PyObject *capsule;") != 1 or register.index("PyObject *capsule;") > register.index("for (h = mapping_list;"):
raise SystemExit("register_maps capsule local must be declared before iteration")
if "PyObject *capsule = PyCapsule_New" in register:
raise SystemExit("register_maps capsule creation must remain in the mapping loop")
if not (register.index("strcpy(mhname + sizeof(\"__map_\") - 1, h->charset);") < register.index("capsule = PyCapsule_New((void *)h,") < register.index("if (capsule == NULL)") < register.index("PyModule_AddObject(module, mhname, capsule)")):
raise SystemExit("register_maps must preserve capsule creation/error/add ownership order")
module_start = source.index("static struct PyModuleDef _cjk_module =")
module_end = source.index("};", module_start) + 2
module_def = source[module_start:module_end]
if any(field in module_def for field in (".m_name =", ".m_size =", ".m_methods =", ".m_slots =")):
raise SystemExit("CJK module definition must not use designated initializers")
normalized_module = " ".join(module_def.replace("\\\n", " ").split())
expected = "static struct PyModuleDef _cjk_module = { PyModuleDef_HEAD_INIT, \"_codecs_\"#loc, NULL, 0, _cjk_methods, _cjk_slots, NULL, NULL, NULL, };"
if normalized_module != expected:
raise SystemExit("CJK module definition must follow PyModuleDef field order")
PY_CJKCODECS_VC6_TEST
then
pass "CJK codecs preserve reference and module initialization behavior"
else
bad "CJK codecs preserve reference and module initialization behavior"
fi
# multibytecodec_dealloc keeps GC/type deallocation order with a C89 local.
if python3 - "$REPO_ROOT/cpython/Modules/cjkcodecs/multibytecodec.c" <<'PY_MULTIBYTECODEC_DEALLOC_TEST'
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
start = source.index("multibytecodec_dealloc(MultibyteCodecObject *self)")
opening = source.index("{", start)
end = source.index(chr(10) + "}" + chr(10), opening) + 2
body = source[opening:end]
if body.count("PyTypeObject *tp;") != 1 or body.index("PyTypeObject *tp;") > body.index("PyObject_GC_UnTrack(self);"):
raise SystemExit("dealloc type pointer must be declared before executable statements")
if "PyTypeObject *tp = Py_TYPE(self);" in body:
raise SystemExit("dealloc type lookup must remain after GC untracking")
markers = ("PyObject_GC_UnTrack(self);", "tp = Py_TYPE(self);", "tp->tp_free(self);", "Py_DECREF(tp);")
positions = [body.index(marker) for marker in markers]
if positions != sorted(positions):
raise SystemExit("dealloc must preserve GC untrack, free, and decref ordering")
PY_MULTIBYTECODEC_DEALLOC_TEST
then
pass "multibyte codec deallocation preserves order with C89 local"
else
bad "multibyte codec deallocation preserves order with C89 local"
fi
# CJK dynamic type specifications use the verified PyType_Spec field order (name, size, itemsize, flags, slots).
if python3 - "$REPO_ROOT/cpython/Modules/cjkcodecs/multibytecodec.c" <<'PY_MULTIBYTECODEC_SPECS_TEST'
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
specs = (
("multibytecodec_spec", "MultibyteCodec", "MultibyteCodecObject", "multibytecodec_slots", False),
("encoder_spec", "MultibyteIncrementalEncoder", "MultibyteIncrementalEncoderObject", "encoder_slots", True),
("decoder_spec", "MultibyteIncrementalDecoder", "MultibyteIncrementalDecoderObject", "decoder_slots", True),
("reader_spec", "MultibyteStreamReader", "MultibyteStreamReaderObject", "reader_slots", True),
("writer_spec", "MultibyteStreamWriter", "MultibyteStreamWriterObject", "writer_slots", True),
)
for name, typename, object_type, slots, base_type in specs:
start = source.index("static PyType_Spec " + name + " = {")
end = source.index("};", start) + 2
spec = source[start:end]
if any(field in spec for field in (".name =", ".basicsize =", ".itemsize =", ".flags =", ".slots =")):
raise SystemExit("PyType_Spec designated fields must be positional: " + name)
expected = ('MODULE_NAME ".' + typename + '"', "sizeof(" + object_type + ")", "0", "Py_TPFLAGS_BASETYPE" if base_type else "Py_TPFLAGS_DISALLOW_INSTANTIATION", slots)
positions = [spec.index(field) for field in expected]
if positions != sorted(positions):
raise SystemExit("PyType_Spec field order/itemsize changed: " + name)
PY_MULTIBYTECODEC_SPECS_TEST
then
pass "multibytecodec PyType_Spec field ordering is verified"
else
bad "multibytecodec PyType_Spec field ordering is verified"
fi
# Multibyte codec module-state locals and module definition preserve initialization order/field layout.
if python3 - "$REPO_ROOT/cpython/Modules/cjkcodecs/multibytecodec.c" <<'PY_MULTIBYTECODEC_STATE_TEST'
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
def body_for(signature):
start = source.index(signature)
opening = source.index("{", start)
end = source.index(chr(10) + "}" + chr(10), opening) + 2
return source[opening:end]
cases = (
("mbiencoder_new(PyTypeObject *type", "if (!PyArg_ParseTupleAndKeywords", "codec = PyObject_GetAttrString", "state = _multibyte_codec_find_state_by_type(type);", "if (!MultibyteCodec_Check(state, codec))"),
("mbidecoder_new(PyTypeObject *type", "if (!PyArg_ParseTupleAndKeywords", "codec = PyObject_GetAttrString", "state = _multibyte_codec_find_state_by_type(type);", "if (!MultibyteCodec_Check(state, codec))"),
("mbstreamreader_new(PyTypeObject *type", "if (!PyArg_ParseTupleAndKeywords", "codec = PyObject_GetAttrString", "state = _multibyte_codec_find_state_by_type(type);", "if (!MultibyteCodec_Check(state, codec))"),
("mbstreamwriter_new(PyTypeObject *type", "if (!PyArg_ParseTupleAndKeywords", "codec = PyObject_GetAttrString", "state = _multibyte_codec_find_state_by_type(type);", "if (!MultibyteCodec_Check(state, codec))"),
("_multibytecodec_MultibyteStreamWriter_write_impl(", "state = PyType_GetModuleState(cls);", "assert(state != NULL);", "if (mbstreamwriter_iwrite(self, strobj, state->str_write))"),
("_multibytecodec_MultibyteStreamWriter_writelines_impl(", "if (!PySequence_Check(lines))", "state = PyType_GetModuleState(cls);", "assert(state != NULL);", "for (i = 0;"),
("_multibytecodec_MultibyteStreamWriter_reset_impl(", "if (!self->pending)", "pwrt = multibytecodec_encode", "assert(PyBytes_Check(pwrt));", "state = PyType_GetModuleState(cls);"),
("_multibytecodec___create_codec(PyObject *module, PyObject *arg)", "if (!PyCapsule_IsValid(arg,", "codec = PyCapsule_GetPointer", "codec->codecinit", "state = _multibytecodec_get_state(module);"),
)
for signature, *ordered in cases:
body = body_for(signature)
if body.count("_multibytecodec_state *state;") != 1:
raise SystemExit("module state local must be declared once at function scope: " + signature)
first_statement = body.index(ordered[0])
if body.index("_multibytecodec_state *state;") > first_statement:
raise SystemExit("module state local must precede executable statements: " + signature)
if "_multibytecodec_state *state =" in body:
raise SystemExit("module state lookup must remain at its original execution point: " + signature)
positions = [body.index(marker) for marker in ordered]
if positions != sorted(positions):
raise SystemExit("module state lookup/order changed: " + signature)
module_start = source.index("static struct PyModuleDef _multibytecodecmodule =")
module_end = source.index("};", module_start) + 2
module = source[module_start:module_end]
if any(field in module for field in (".m_base =", ".m_name =", ".m_doc =", ".m_size =", ".m_methods =", ".m_slots =", ".m_traverse =", ".m_clear =", ".m_free =")):
raise SystemExit("module definition must not use designated initializers")
normalized = " ".join(module.replace("\\\n", " ").split())
expected = "static struct PyModuleDef _multibytecodecmodule = { PyModuleDef_HEAD_INIT, \"_multibytecodec\", NULL, sizeof(_multibytecodec_state), _multibytecodec_methods, _multibytecodec_slots, _multibytecodec_traverse, _multibytecodec_clear, _multibytecodec_free, };"
if normalized != expected:
raise SystemExit("module definition fields must match PyModuleDef order")
PY_MULTIBYTECODEC_STATE_TEST
then
pass "multibyte codec state/module initialization ordering passes"
else
bad "multibyte codec state/module initialization ordering passes"
fi
# overlapped_dealloc keeps cancellation, buffer cleanup and type release order with a C89 local.
if python3 - "$REPO_ROOT/cpython/Modules/_winapi.c" <<'PY_WINAPI_OVERLAPPED_DEALLOC_TEST'
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
start = source.index("overlapped_dealloc(OverlappedObject *self)")
opening = source.index("{", start)
end = source.index(chr(10) + "}" + chr(10), opening) + 2
body = source[opening:end]
if body.count("PyTypeObject *tp;") != 1 or body.index("PyTypeObject *tp;") > body.index("PyObject_GC_UnTrack(self);"):
raise SystemExit("overlapped type local must be declared before executable statements")
if "PyTypeObject *tp = Py_TYPE(self);" in body:
raise SystemExit("overlapped type lookup must remain after buffer cleanup")
markers = ("PyBuffer_Release(&self->write_buffer);", "Py_CLEAR(self->read_buffer);", "tp = Py_TYPE(self);", "tp->tp_free(self);", "Py_DECREF(tp);")
positions = [body.index(marker) for marker in markers]
if positions != sorted(positions):
raise SystemExit("overlapped deallocation must preserve buffer/type release order")
PY_WINAPI_OVERLAPPED_DEALLOC_TEST
then
pass "overlapped deallocation preserves cleanup order with C89 local"
else
bad "overlapped deallocation preserves cleanup order with C89 local"
fi
# _winapi OVERLAPPED PyType_Spec uses C89-compatible positional fields.
if python3 - "$REPO_ROOT/cpython/Modules/_winapi.c" <<'PY_WINAPI_OVERLAPPED_SPEC_TEST'
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
start = source.index("static PyType_Spec winapi_overlapped_type_spec = {")
end = source.index("};", start) + 2
spec = source[start:end]
fields = (".name =", ".basicsize =", ".flags =", ".slots =")
if any(field in spec for field in fields):
raise SystemExit("OVERLAPPED PyType_Spec must not use designated initializers")
ordered = ("\"_winapi.Overlapped\"", "sizeof(OverlappedObject)", "0", "Py_TPFLAGS_DEFAULT", "winapi_overlapped_type_slots")
positions = [spec.index(field) for field in ordered]
if positions != sorted(positions):
raise SystemExit("OVERLAPPED PyType_Spec must follow name,size,itemsize,flags,slots order")
PY_WINAPI_OVERLAPPED_SPEC_TEST
then
pass "OVERLAPPED type specification uses positional C89 fields"
else
bad "OVERLAPPED type specification uses positional C89 fields"
fi
# _winapi supplies exact legacy-SDK spellings without asserting Win9x API support.
if python3 - "$REPO_ROOT/cpython/Modules/_winapi.c" <<'PY_WINAPI_LEGACY_SDK_CONSTANTS_TEST'
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
expected = """#if defined(_MSC_VER) && _MSC_VER < 1300
# ifndef INVALID_FILE_ATTRIBUTES
# define INVALID_FILE_ATTRIBUTES ((DWORD)-1)
# endif
# ifndef FSCTL_SET_REPARSE_POINT
# define FSCTL_SET_REPARSE_POINT 0x000900A4
# endif
# ifndef LOCALE_NAME_INVARIANT
# define LOCALE_NAME_INVARIANT L""
# endif
# ifndef LOCALE_NAME_MAX_LENGTH
# define LOCALE_NAME_MAX_LENGTH 85
# endif
# ifndef LOCALE_NAME_SYSTEM_DEFAULT
# define LOCALE_NAME_SYSTEM_DEFAULT L"!sys-default-locale"
# endif
# ifndef LOCALE_NAME_USER_DEFAULT
# define LOCALE_NAME_USER_DEFAULT NULL
# endif
# ifndef LCMAP_SORTHANDLE
# define LCMAP_SORTHANDLE 0x20000000
# endif
# ifndef LCMAP_HASH
# define LCMAP_HASH 0x00040000
# endif
# ifndef LCMAP_TITLECASE
# define LCMAP_TITLECASE 0x00000300
# endif
# ifndef FILE_FLAG_FIRST_PIPE_INSTANCE
# define FILE_FLAG_FIRST_PIPE_INSTANCE 0x00080000
# endif
# ifndef FILE_MAP_EXECUTE
# define FILE_MAP_EXECUTE 0x0020
# endif
# ifndef SEC_LARGE_PAGES
# define SEC_LARGE_PAGES 0x80000000
# endif
# ifndef SEC_WRITECOMBINE
# define SEC_WRITECOMBINE 0x40000000
# endif
# ifndef ABOVE_NORMAL_PRIORITY_CLASS
# define ABOVE_NORMAL_PRIORITY_CLASS 0x00008000
# endif
# ifndef BELOW_NORMAL_PRIORITY_CLASS
# define BELOW_NORMAL_PRIORITY_CLASS 0x00004000
# endif
# ifndef CREATE_BREAKAWAY_FROM_JOB
# define CREATE_BREAKAWAY_FROM_JOB 0x01000000
# endif
#endif"""
if expected not in source:
raise SystemExit("_winapi legacy SDK constants must retain exact documented values")
PY_WINAPI_LEGACY_SDK_CONSTANTS_TEST
then
pass "_winapi legacy SDK constants match documented values"
else
bad "_winapi legacy SDK constants match documented values"
fi
# sort_environment_keys hoists C89 locals but keeps callable and argument initialization at the same points.
if python3 - "$REPO_ROOT/cpython/Modules/_winapi.c" <<'PY_WINAPI_SORT_ENVIRONMENT_KEYS_TEST'
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
start = source.index("sort_environment_keys(PyObject *keys)")
opening = source.index("{", start)
end = source.index(chr(10) + "}" + chr(10), opening) + 2
body = source[opening:end]
for declaration in ("PyObject *keyfunc;", "PyObject *kwnames;", "PyObject *args[2];", "PyObject *ret;"):
if body.count(declaration) != 1 or body.index(declaration) > body.index("keyfunc = PyCFunction_New"):
raise SystemExit("sort_environment_keys locals must be declared before executable statements")
markers = ("keyfunc = PyCFunction_New", "if (keyfunc == NULL)", "kwnames = Py_BuildValue", "if (kwnames == NULL)", "args[0] = keys;", "args[1] = keyfunc;", "ret = PyObject_VectorcallMethod", "if (ret == NULL)", "Py_DECREF(ret);")
positions = [body.index(marker) for marker in markers]
cleanup_positions = (body.rindex("Py_DECREF(keyfunc);"), body.rindex("Py_DECREF(kwnames);"))
if positions != sorted(positions) or not (positions[6] < cleanup_positions[0] < cleanup_positions[1] < positions[7]):
raise SystemExit("sort_environment_keys must preserve construction and cleanup order")
PY_WINAPI_SORT_ENVIRONMENT_KEYS_TEST
then
pass "sort_environment_keys preserves call and cleanup order with C89 locals"
else
bad "sort_environment_keys preserves call and cleanup order with C89 locals"
fi
# _winapi environment helpers use C89 declarations without moving cleanup points.
if python3 - "$REPO_ROOT/cpython/Modules/_winapi.c" <<'PY_WINAPI_ENV_HELPERS_TEST'
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
def body_for(signature):
start = source.index(signature)
opening = source.index("{", start)
end = source.index(chr(10) + "}" + chr(10), opening) + 2
return source[opening:end]
compare = body_for("compare_string_ordinal(PyObject *str1")
for declaration in ("wchar_t *s1;", "wchar_t *s2;"):
if compare.count(declaration) != 1 or compare.index(declaration) > compare.index("s1 = PyUnicode_AsWideCharString"):
raise SystemExit("ordinal-comparison buffers must be declared before lookup")
order = (compare.index("s1 = PyUnicode_AsWideCharString"), compare.index("if (s1 == NULL)"), compare.index("s2 = PyUnicode_AsWideCharString"), compare.index("if (s2 == NULL)"), compare.index("CompareStringOrdinal"), compare.rindex("PyMem_Free(s1);"), compare.index("PyMem_Free(s2);"))
if order != tuple(sorted(order)):
raise SystemExit("ordinal-comparison lookup and cleanup order changed")
dedup = body_for("dedup_environment_keys(PyObject *keys)")
for declaration in ("PyObject *result;", "Py_ssize_t i;"):
if dedup.count(declaration) != 1 or dedup.index(declaration) > dedup.index("result = PyList_New(0)"):
raise SystemExit("dedup locals must be declared before list creation")
if "for (Py_ssize_t i =" in dedup:
raise SystemExit("dedup loop index must be declared in C89 order")
loop = dedup[dedup.index("for (i =") :]
for declaration in ("PyObject *key;", "PyObject *next_key;", "int compare_result;"):
if loop.count(declaration) != 1 or loop.index(declaration) > loop.index("key = PyList_GET_ITEM"):
raise SystemExit("dedup loop locals must be declared before loop statements")
if "PyObject *next_key =" in loop:
raise SystemExit("dedup next-key lookup must remain an assignment at its original point")
normalize = body_for("normalize_environment(PyObject *environment)")
for declaration in ("PyObject *keys;", "PyObject *normalized_keys;", "PyObject *result;", "PyObject *key;", "PyObject *value;", "int i;", "int ret;"):
if normalize.count(declaration) != 1 or normalize.index(declaration) > normalize.index("keys = PyMapping_Keys(environment)"):
raise SystemExit("normalization locals must be declared before mapping access")
if "for (int i =" in normalize or "PyObject *normalized_keys =" in normalize or "PyObject *result =" in normalize:
raise SystemExit("normalize_environment retains a late C99 declaration")
PY_WINAPI_ENV_HELPERS_TEST
then
pass "_winapi environment helpers satisfy C89 declaration placement"
else
bad "_winapi environment helpers satisfy C89 declaration placement"
fi
# VC6 routes CreateProcess through STARTUPINFOW and rejects Vista-only attribute lists safely.
if python3 - "$REPO_ROOT/cpython/Modules/_winapi.c" <<'PY_WINAPI_LEGACY_CREATEPROCESS_TEST'
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
marker = "/* Vista-only process/thread attribute APIs are unavailable in VC6. */"
comment = source.index(marker)
start = source.rfind("#if defined(_MSC_VER) && _MSC_VER < 1300", 0, comment)
modern = source.index("#else", comment)
legacy = source[start:modern]
if any(name in legacy for name in ("LPPROC_THREAD_ATTRIBUTE_LIST", "InitializeProcThreadAttributeList", "UpdateProcThreadAttribute", "DeleteProcThreadAttributeList", "STARTUPINFOEXW")):
raise SystemExit("VC6 branch must not reference Vista-only process attribute APIs")
if "PyErr_SetFromWindowsErr(ERROR_CALL_NOT_IMPLEMENTED)" not in legacy:
raise SystemExit("non-None startup attribute lists must fail explicitly on the legacy branch")
body_start = source.index("_winapi_CreateProcess_impl(")
opening = source.index("{", body_start)
end = source.index(chr(10) + "}" + chr(10), opening) + 2
body = source[opening:end]
startup_pair = chr(10).join(("#if defined(_MSC_VER) && _MSC_VER < 1300", " STARTUPINFOW si;", "#else", " STARTUPINFOEXW si;", "#endif"))
if startup_pair not in body or "STARTUPINFOW *si_base;" not in body:
raise SystemExit("CreateProcess must select the correct startup structure and base pointer")
call_start = body.index("Py_BEGIN_ALLOW_THREADS")
call_end = body.index("Py_END_ALLOW_THREADS", call_start)
call = body[call_start:call_end]
if "#if defined(_MSC_VER) && _MSC_VER < 1300" not in call or "EXTENDED_STARTUPINFO_PRESENT" not in call:
raise SystemExit("CreateProcess must use extended flags only in the modern branch")
PY_WINAPI_LEGACY_CREATEPROCESS_TEST
then
pass "VC6 CreateProcess avoids Vista-only startup attribute APIs"
else
bad "VC6 CreateProcess avoids Vista-only startup attribute APIs"
fi
# _winapi_LCMapStringEx_impl keeps validation, allocation and error cleanup order with C89 locals.
if python3 - "$REPO_ROOT/cpython/Modules/_winapi.c" <<'PY_WINAPI_LCMAP_C89_TEST'
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
start = source.index("_winapi_LCMapStringEx_impl(PyObject *module")
opening = source.index("{", start)
end = source.index(chr(10) + "}" + chr(10), opening) + 2
body = source[opening:end]
first_statement = body.index("if (flags &")
for declaration in ("wchar_t *locale_;", "Py_ssize_t src_size;", "wchar_t *src_;", "int dest_size;", "wchar_t *dest;", "int nmapped;", "PyObject *ret;"):
if body.count(declaration) != 1 or body.index(declaration) > first_statement:
raise SystemExit("LCMapStringEx locals must be declared before validation")
markers = ("if (flags &", "locale_ = PyUnicode_AsWideCharString", "if (!locale_)", "src_ = PyUnicode_AsWideCharString", "if (!src_)", "if (src_size > INT_MAX)", "dest_size = LCMapStringEx", "dest = PyMem_NEW", "nmapped = LCMapStringEx", "ret = PyUnicode_FromWideChar")
positions = [body.index(marker) for marker in markers]
if positions != sorted(positions) or positions[-1] > body.rindex("PyMem_DEL(dest)"):
raise SystemExit("LCMapStringEx validation/allocation order changed")
PY_WINAPI_LCMAP_C89_TEST
then
pass "LCMapStringEx declaration order preserves validation and cleanup"
else
bad "LCMapStringEx declaration order preserves validation and cleanup"
fi
# _winapi registry traversal and module setup use C89 locals and the PyModuleDef field order.
if python3 - "$REPO_ROOT/cpython/Modules/_winapi.c" <<'PY_WINAPI_REGISTRY_MODULE_TEST'
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
def body_for(signature):
start = source.index(signature)
opening = source.index("{", start)
end = source.index(chr(10) + "}" + chr(10), opening) + 2
return source[opening:end]
registry = body_for("_winapi__mimetypes_read_windows_registry_impl(")
for declaration in ("DWORD i;", "int j;"):
if registry.count(declaration) != 1 or registry.index(declaration) > registry.index("err = RegOpenKeyExW"):
raise SystemExit("registry traversal indices must be declared before registry calls")
if "for (DWORD i =" in registry or "for (int j =" in registry or registry.count("for (j = 0; j < entry; ++j)") != 2:
raise SystemExit("registry traversal loops must use the hoisted C89 indices")
if not (registry.index("Py_BLOCK_THREADS") < registry.index("for (j =") < registry.index("Py_UNBLOCK_THREADS") < registry.rindex("for (j =")):
raise SystemExit("registry callback thread-state boundaries changed")
exec_body = body_for("winapi_exec(PyObject *m)")
for declaration in ("WinApiState *st;", "PyObject *d;"):
if exec_body.count(declaration) != 1 or exec_body.index(declaration) > exec_body.index("st = winapi_get_state(m)"):
raise SystemExit("module exec locals must be declared before initialization")
if not (exec_body.index("PyModule_AddType(m, st->overlapped_type)") < exec_body.index("d = PyModule_GetDict(m)")):
raise SystemExit("module dictionary lookup must remain after type registration")
start = source.index("static struct PyModuleDef winapi_module = {")
end = source.index("};", start) + 2
normalized = " ".join(source[start:end].replace(chr(92) + chr(10), " ").split())
expected = "static struct PyModuleDef winapi_module = { PyModuleDef_HEAD_INIT, \"_winapi\", NULL, sizeof(WinApiState), winapi_functions, winapi_slots, winapi_traverse, winapi_clear, winapi_free, };"
if normalized != expected:
raise SystemExit("_winapi PyModuleDef must use the verified positional field order")
PY_WINAPI_REGISTRY_MODULE_TEST
then
pass "_winapi registry and module setup follow C89/layout rules"
else
bad "_winapi registry and module setup follow C89/layout rules"
fi
# Core object access helpers hoist C89 method-table/result locals without moving slot calls.
if python3 - "$REPO_ROOT/cpython/Objects/abstract.c" <<'PY_ABSTRACT_ACCESSOR_C89_TEST'
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
def body_for(signature):
start = source.index(signature)
opening = source.index("{", start)
end = source.index(chr(10) + "}" + chr(10), opening) + 2
return source[opening:end]
checks = (
("PyObject_Size(PyObject *o)", ("PySequenceMethods *m;",), "if (o == NULL)", "m = Py_TYPE(o)->tp_as_sequence;", "if (m && m->sq_length)"),
("PyObject_GetItem(PyObject *o, PyObject *key)", ("PyMappingMethods *m;", "PySequenceMethods *ms;"), "if (o == NULL || key == NULL)", "m = Py_TYPE(o)->tp_as_mapping;", "if (m && m->mp_subscript)", "ms = Py_TYPE(o)->tp_as_sequence;", "if (ms && ms->sq_item)"),
("PyObject_SetItem(PyObject *o, PyObject *key, PyObject *value)", ("PyMappingMethods *m;",), "if (o == NULL || key == NULL || value == NULL)", "m = Py_TYPE(o)->tp_as_mapping;", "if (m && m->mp_ass_subscript)"),
("PyObject_DelItem(PyObject *o, PyObject *key)", ("PyMappingMethods *m;",), "if (o == NULL || key == NULL)", "m = Py_TYPE(o)->tp_as_mapping;", "if (m && m->mp_ass_subscript)"),
("PyObject_GetBuffer(PyObject *obj", ("int res;",), "if (pb == NULL", "res = (*pb->bf_getbuffer)", "assert(_Py_CheckSlotResult"),
)
for item in checks:
signature, declarations, *markers = item
body = body_for(signature)
first = markers[0]
for declaration in declarations:
if body.count(declaration) != 1 or body.index(declaration) > body.index(first):
raise SystemExit(signature + " local declarations must precede statements")
positions = [body.index(marker) for marker in markers]
if positions != sorted(positions):
raise SystemExit(signature + " null guards/slot calls changed order")
if "PyMappingMethods *m =" in body_for("PyObject_SetItem(PyObject *o, PyObject *key, PyObject *value)"):
raise SystemExit("PyObject_SetItem retains a late initialized method-table local")
PY_ABSTRACT_ACCESSOR_C89_TEST
then
pass "core object access helpers preserve slot order with C89 locals"
else
bad "core object access helpers preserve slot order with C89 locals"
fi
# Numeric dispatch helpers hoist late method-table/slot locals without reordering dispatch.
if python3 - "$REPO_ROOT/cpython/Objects/abstract.c" <<'PY_ABSTRACT_NUMERIC_C89_TEST'
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
def body_for(signature):
start = source.index(signature)
opening = source.index("{", start)
end = source.index(chr(10) + "}" + chr(10), opening) + 2
return source[opening:end]
def require_before(body, declaration, marker):
if body.count(declaration) != 1 or body.index(declaration) > body.index(marker):
raise SystemExit(declaration + " must be a C89 local before " + marker)
b = body_for("PyNumber_Check(PyObject *o)")
require_before(b, "PyNumberMethods *nb;", "if (o == NULL)")
if not (b.index("if (o == NULL)") < b.index("nb = Py_TYPE(o)->tp_as_number;") < b.index("return nb &&")):
raise SystemExit("PyNumber_Check changed null/slot evaluation order")
b = body_for("binary_op1(PyObject *v, PyObject *w, const int op_slot")
for d in ("binaryfunc slotv;", "binaryfunc slotw;"):
require_before(b, d, "if (Py_TYPE(v)->tp_as_number")
if "binaryfunc slotw;" in b[b.index("if (Py_TYPE(v)->tp_as_number"):]:
raise SystemExit("binary_op1 retains a late slotw declaration")
b = body_for("ternary_op(PyObject *v,")
for d in ("PyNumberMethods *mv;", "PyNumberMethods *mw;", "PyNumberMethods *mz;", "ternaryfunc slotv;", "ternaryfunc slotw;"):
require_before(b, d, "mv = Py_TYPE(v)->tp_as_number;")
if not (b.index("mv = Py_TYPE(v)->tp_as_number;") < b.index("mw = Py_TYPE(w)->tp_as_number;") < b.index("if (mv != NULL)") < b.index("slotv = NB_TERNOP") < b.index("slotw = NB_TERNOP") < b.index("mz = Py_TYPE(z)->tp_as_number;")):
raise SystemExit("ternary_op changed slot lookup order")
b = body_for("PyNumber_Add(PyObject *v, PyObject *w)")
require_before(b, "PySequenceMethods *m;", "PyObject *result")
if not (b.index("PyObject *result = BINARY_OP1") < b.index("Py_DECREF(result);") < b.index("m = Py_TYPE(v)->tp_as_sequence;") < b.index("if (m && m->sq_concat)")):
raise SystemExit("PyNumber_Add changed binary-then-sequence fallback order")
b = body_for("sequence_repeat(ssizeargfunc repeatfunc")
require_before(b, "PyObject *res;", "if (_PyIndex_Check(n))")
if not (b.index("count = PyNumber_AsSsize_t") < b.index("res = (*repeatfunc)(seq, count);") < b.index("return res;")):
raise SystemExit("sequence_repeat changed conversion/call order")
for signature, slot in (("PyNumber_Negative(PyObject *o)", "nb_negative"), ("PyNumber_Positive(PyObject *o)", "nb_positive"), ("PyNumber_Invert(PyObject *o)", "nb_invert"), ("PyNumber_Absolute(PyObject *o)", "nb_absolute")):
b = body_for(signature)
require_before(b, "PyNumberMethods *m;", "if (o == NULL)")
if not (b.index("if (o == NULL)") < b.index("m = Py_TYPE(o)->tp_as_number;") < b.index("if (m && m->" + slot + ")")):
raise SystemExit(signature + " changed null/slot dispatch order")
b = body_for("_PyNumber_Index(PyObject *item)")
require_before(b, "PyObject *result;", "if (item == NULL)")
if not (b.index("if (!_PyIndex_Check(item))") < b.index("result = Py_TYPE(item)->tp_as_number->nb_index(item);") < b.index("assert(_Py_CheckSlotResult(item, \"__index__\"")):
raise SystemExit("_PyNumber_Index changed validation/call order")
b = body_for("PyNumber_AsSsize_t(PyObject *item, PyObject *err)")
require_before(b, "PyThreadState *tstate;", "Py_ssize_t result;")
if not (b.index("if (result != -1)") < b.index("tstate = _PyThreadState_GET();") < b.index("runerr = _PyErr_Occurred(tstate);")):
raise SystemExit("PyNumber_AsSsize_t changed overflow handling order")
b = body_for("PyNumber_Float(PyObject *o)")
for declaration in ("PyNumberMethods *m;", "double val;"):
require_before(b, declaration, "if (o == NULL)")
if not (b.index("if (o == NULL)") < b.index("if (PyFloat_CheckExact(o))") < b.index("m = Py_TYPE(o)->tp_as_number;") < b.index("if (m && m->nb_float)") < b.index("val = PyFloat_AS_DOUBLE(res);") < b.index("Py_DECREF(res);") < b.index("if (m && m->nb_index)") < b.index("val = PyLong_AsDouble(res);")):
raise SystemExit("PyNumber_Float changed slot/fallback conversion order")
b = body_for("PyNumber_ToBase(PyObject *n, int base)")
for declaration in ("PyObject *index;", "PyObject *res;"):
require_before(b, declaration, "if (!(base ==")
if not (b.index("if (!(base ==") < b.index("index = _PyNumber_Index(n);") < b.index("res = _PyLong_Format(index, base);") < b.index("Py_DECREF(index);") < b.index("return res;")):
raise SystemExit("PyNumber_ToBase changed validation/ownership order")
PY_ABSTRACT_NUMERIC_C89_TEST
then
pass "numeric dispatch helpers preserve slot order with C89 locals"
else
bad "numeric dispatch helpers preserve slot order with C89 locals"
fi
# Sequence operation fallbacks retain null-check and slot-priority order after local hoisting.
if python3 - "$REPO_ROOT/cpython/Objects/abstract.c" <<'PY_ABSTRACT_SEQUENCE_C89_TEST'
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
def body_for(signature):
start = source.index(signature)
opening = source.index("{", start)
end = source.index(chr(10) + "}" + chr(10), opening) + 2
return source[opening:end]
checks = (
("PySequence_Size(PyObject *s)", "if (s == NULL)", "m = Py_TYPE(s)->tp_as_sequence;", "if (m && m->sq_length)"),
("PySequence_Concat(PyObject *s, PyObject *o)", "if (s == NULL || o == NULL)", "m = Py_TYPE(s)->tp_as_sequence;", "if (m && m->sq_concat)"),
("PySequence_Repeat(PyObject *o, Py_ssize_t count)", "if (o == NULL)", "m = Py_TYPE(o)->tp_as_sequence;", "if (m && m->sq_repeat)"),
("PySequence_InPlaceConcat(PyObject *s, PyObject *o)", "if (s == NULL || o == NULL)", "m = Py_TYPE(s)->tp_as_sequence;", "if (m && m->sq_inplace_concat)"),
("PySequence_InPlaceRepeat(PyObject *o, Py_ssize_t count)", "if (o == NULL)", "m = Py_TYPE(o)->tp_as_sequence;", "if (m && m->sq_inplace_repeat)"),
)
for signature, null_check, assignment, first_slot in checks:
body = body_for(signature)
if body.count("PySequenceMethods *m;") != 1 or body.index("PySequenceMethods *m;") > body.index(null_check):
raise SystemExit(signature + " must declare m at function entry")
if not (body.index(null_check) < body.index(assignment) < body.index(first_slot)):
raise SystemExit(signature + " changed null-check or slot-dispatch order")
if "PySequenceMethods *m =" in body:
raise SystemExit(signature + " retains a late initialized local")
more_checks = (
("PySequence_GetItem(PyObject *s, Py_ssize_t i)", ("PySequenceMethods *m;", "PyObject *res;"), "if (s == NULL)", "m = Py_TYPE(s)->tp_as_sequence;", "if (m && m->sq_item)", "res = m->sq_item(s, i);", "assert(_Py_CheckSlotResult(s, \"__getitem__\""),
("PySequence_GetSlice(PyObject *s, Py_ssize_t i1, Py_ssize_t i2)", ("PyMappingMethods *mp;", "PyObject *slice;", "PyObject *res;"), "if (!s)", "mp = Py_TYPE(s)->tp_as_mapping;", "if (mp && mp->mp_subscript)", "slice = _PySlice_FromIndices(i1, i2);", "res = mp->mp_subscript(s, slice);", "Py_DECREF(slice);"),
("PySequence_SetItem(PyObject *s, Py_ssize_t i, PyObject *o)", ("PySequenceMethods *m;", "int res;"), "if (s == NULL)", "m = Py_TYPE(s)->tp_as_sequence;", "if (m && m->sq_ass_item)", "res = m->sq_ass_item(s, i, o);", "assert(_Py_CheckSlotResult(s, \"__setitem__\""),
("PySequence_DelItem(PyObject *s, Py_ssize_t i)", ("PySequenceMethods *m;", "int res;"), "if (s == NULL)", "m = Py_TYPE(s)->tp_as_sequence;", "if (m && m->sq_ass_item)", "res = m->sq_ass_item(s, i, (PyObject *)NULL);", "assert(_Py_CheckSlotResult(s, \"__delitem__\""),
("PySequence_SetSlice(PyObject *s, Py_ssize_t i1, Py_ssize_t i2, PyObject *o)", ("PyMappingMethods *mp;", "PyObject *slice;", "int res;"), "if (s == NULL)", "mp = Py_TYPE(s)->tp_as_mapping;", "if (mp && mp->mp_ass_subscript)", "slice = _PySlice_FromIndices(i1, i2);", "res = mp->mp_ass_subscript(s, slice, o);", "Py_DECREF(slice);"),
("PySequence_DelSlice(PyObject *s, Py_ssize_t i1, Py_ssize_t i2)", ("PyMappingMethods *mp;", "PyObject *slice;", "int res;"), "if (s == NULL)", "mp = Py_TYPE(s)->tp_as_mapping;", "if (mp && mp->mp_ass_subscript)", "slice = _PySlice_FromIndices(i1, i2);", "res = mp->mp_ass_subscript(s, slice, NULL);", "Py_DECREF(slice);"),
)
for item in more_checks:
signature, declarations, *markers = item
body = body_for(signature)
for declaration in declarations:
if body.count(declaration) != 1 or body.index(declaration) > body.index(markers[0]):
raise SystemExit(signature + " declarations must precede the null check")
positions = [body.index(marker) for marker in markers]
if positions != sorted(positions):
raise SystemExit(signature + " changed bounds/slot/ownership order")
PY_ABSTRACT_SEQUENCE_C89_TEST
then
pass "sequence fallbacks preserve null and slot order with C89 locals"
else
bad "sequence fallbacks preserve null and slot order with C89 locals"
fi
# Sequence/mapping containment preserves the slot-first fast path and fallback order.
if python3 - "$REPO_ROOT/cpython/Objects/abstract.c" <<'PY_ABSTRACT_CONTAINS_MAPPING_TEST'
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
def body_for(signature):
start = source.index(signature)
opening = source.index("{", start)
end = source.index(chr(10) + "}" + chr(10), opening) + 2
return source[opening:end]
b = body_for("PySequence_Contains(PyObject *seq, PyObject *ob)")
if b.count("Py_ssize_t result;") != 1 or b.index("Py_ssize_t result;") > b.index("PySequenceMethods *sqm ="):
raise SystemExit("PySequence_Contains result must be declared at entry")
if not (b.index("if (sqm != NULL && sqm->sq_contains != NULL)") < b.index("int res = (*sqm->sq_contains)(seq, ob)") < b.index("result = _PySequence_IterSearch(seq, ob, PY_ITERSEARCH_CONTAINS);") < b.index("return Py_SAFE_DOWNCAST(result")):
raise SystemExit("PySequence_Contains changed slot/fallback order")
b = body_for("PyMapping_Size(PyObject *o)")
if b.count("PyMappingMethods *m;") != 1 or b.index("PyMappingMethods *m;") > b.index("if (o == NULL)"):
raise SystemExit("PyMapping_Size method table must be declared at entry")
if not (b.index("if (o == NULL)") < b.index("m = Py_TYPE(o)->tp_as_mapping;") < b.index("if (m && m->mp_length)") < b.index("if (Py_TYPE(o)->tp_as_sequence")):
raise SystemExit("PyMapping_Size changed null/mapping/sequence fallback order")
PY_ABSTRACT_CONTAINS_MAPPING_TEST
then
pass "sequence containment and mapping size preserve fallback order"
else
bad "sequence containment and mapping size preserve fallback order"
fi
# Recursive isinstance/issubclass and async iteration hoist locals without changing recursion or ownership order.
if python3 - "$REPO_ROOT/cpython/Objects/abstract.c" <<'PY_ABSTRACT_RECURSIVE_C89_TEST'
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
def body_for(signature):
start = source.index(signature)
opening = source.index("{", start)
end = source.index(chr(10) + "}" + chr(10), opening) + 2
return source[opening:end]
for signature, entry_marker in (("object_recursive_isinstance(PyThreadState *tstate", "if (Py_IS_TYPE(inst, (PyTypeObject *)cls))"), ("object_issubclass(PyThreadState *tstate", "if (PyType_CheckExact(cls))")):
body = body_for(signature)
for declaration in ("Py_ssize_t n;", "Py_ssize_t i;", "int r;", "PyObject *item;", "PyObject *res;", "int ok;"):
if body.count(declaration) != 1 or body.index(declaration) > body.index(entry_marker):
raise SystemExit(signature + " locals must be declared at function entry")
if "for (Py_ssize_t i =" in body:
raise SystemExit(signature + " retains a C99 loop declaration")
if not (body.index("if (PyTuple_Check(cls))") < body.index("_Py_EnterRecursiveCallTstate") < body.index("n = PyTuple_GET_SIZE(cls);") < body.index("r = 0;") < body.index("for (i = 0;") < body.index("item = PyTuple_GET_ITEM(cls, i);") < body.index("_Py_LeaveRecursiveCallTstate(tstate);")):
raise SystemExit(signature + " changed recursion guard/tuple traversal order")
body = body_for("PyObject_GetAIter(PyObject *o)")
for declaration in ("PyTypeObject *t = Py_TYPE(o);", "unaryfunc f;", "PyObject *it;"):
if body.count(declaration) != 1 or body.index(declaration) > body.index("if (t->tp_as_async == NULL"):
raise SystemExit("PyObject_GetAIter declarations must precede the async-slot guard")
if not (body.index("f = t->tp_as_async->am_aiter;") < body.index("it = (*f)(o);") < body.index("if (it != NULL && !PyAIter_Check(it))") < body.index("return it;")):
raise SystemExit("PyObject_GetAIter changed call/validation order")
PY_ABSTRACT_RECURSIVE_C89_TEST
then
pass "recursive type checks and async iteration preserve C89 control flow"
else
bad "recursive type checks and async iteration preserve C89 control flow"
fi
# bool_vectorcall and PyBool_Type remain C89/VC6 compatible without changing vectorcall slot placement.
if python3 - "$REPO_ROOT/cpython/Objects/boolobject.c" <<'PY_BOOLOBJECT_C89_TEST'
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
def body_for(signature):
start = source.index(signature)
opening = source.index("{", start)
end = source.index(chr(10) + "}" + chr(10), opening) + 2
return source[opening:end]
body = body_for("bool_vectorcall(PyObject *type")
if body.count("Py_ssize_t nargs;") != 1 or body.index("Py_ssize_t nargs;") > body.index("if (!_PyArg_NoKwnames"):
raise SystemExit("bool_vectorcall nargs must be declared before statements")
if not (body.index("if (!_PyArg_NoKwnames") < body.index("nargs = PyVectorcall_NARGS(nargsf);") < body.index("if (!_PyArg_CheckPositional")):
raise SystemExit("bool_vectorcall changed keyword/positional validation order")
type_start = source.index("PyTypeObject PyBool_Type = {")
type_end = source.index("\n};", type_start) + 3
type_body = " ".join(source[type_start:type_end].split())
expected = "bool_new, /* tp_new */ 0, /* tp_free */ 0, /* tp_is_gc */ 0, /* tp_bases */ 0, /* tp_mro */ 0, /* tp_cache */ 0, /* tp_subclasses */ 0, /* tp_weaklist */ 0, /* tp_del */ 0, /* tp_version_tag */ 0, /* tp_finalize */ bool_vectorcall, /* tp_vectorcall */ }"
if ".tp_vectorcall =" in type_body or expected not in type_body:
raise SystemExit("PyBool_Type must use positional fields through tp_vectorcall")
PY_BOOLOBJECT_C89_TEST
then
pass "bool type preserves vectorcall field placement with C89 initializers"
else
bad "bool type preserves vectorcall field placement with C89 initializers"
fi
# bytearray Clinic wrappers declare UTF-8 lengths before validation and convert at the same points.
if python3 - "$REPO_ROOT/cpython/Objects/clinic/bytearrayobject.c.h" <<'PY_BYTEARRAY_CLINIC_C89_TEST'
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
def body_for(signature):
start = source.index(signature)
opening = source.index("{", start)
end = source.index(chr(10) + "}" + chr(10), opening) + 2
return source[opening:end]
checks = (("bytearray___init__(PyObject *self, PyObject *args", "fastargs = _PyArg_UnpackKeywords", "fastargs[1]", "encoding_length", "encoding = PyUnicode_AsUTF8AndSize(fastargs[1], &encoding_length);", "fastargs[2]", "errors_length", "errors = PyUnicode_AsUTF8AndSize(fastargs[2], &errors_length);"), ("bytearray_decode(PyByteArrayObject *self", "args = _PyArg_UnpackKeywords", "args[0]", "encoding_length", "encoding = PyUnicode_AsUTF8AndSize(args[0], &encoding_length);", "args[1]", "errors_length", "errors = PyUnicode_AsUTF8AndSize(args[1], &errors_length);"))
for signature, first_call, encoding_check, encoding_len, encoding_call, errors_check, errors_len, errors_call in checks:
body = body_for(signature)
for declaration in ("Py_ssize_t " + encoding_len + ";", "Py_ssize_t " + errors_len + ";"):
if body.count(declaration) != 1 or body.index(declaration) > body.index(first_call):
raise SystemExit(signature + " length locals must precede statements")
positions = [body.index(x) for x in (first_call, encoding_check, encoding_call, errors_check, errors_call)]
if positions != sorted(positions):
raise SystemExit(signature + " changed argument validation/conversion order")
PY_BYTEARRAY_CLINIC_C89_TEST
then
pass "bytearray Clinic wrappers preserve UTF-8 validation order with C89 locals"
else
bad "bytearray Clinic wrappers preserve UTF-8 validation order with C89 locals"
fi
# bytearray repeat and slice-assignment locals move to C89 declaration groups without moving operations.
if python3 - "$REPO_ROOT/cpython/Objects/bytearrayobject.c" <<'PY_BYTEARRAY_CORE_C89_TEST'
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
def body_for(signature):
start = source.index(signature)
opening = source.index("{", start)
end = source.index(chr(10) + "}" + chr(10), opening) + 2
return source[opening:end]
checks = (("bytearray_repeat(PyByteArrayObject *self, Py_ssize_t count)", ("Py_ssize_t mysize;", "Py_ssize_t size;", "PyByteArrayObject *result;", "const char *buf;"), ("if (count < 0)", "mysize = Py_SIZE(self);", "if (count > 0 && mysize > PY_SSIZE_T_MAX / count)", "size = mysize * count;", "result = (PyByteArrayObject *)PyByteArray_FromStringAndSize(NULL, size);", "buf = PyByteArray_AS_STRING(self);")), ("bytearray_irepeat(PyByteArrayObject *self, Py_ssize_t count)", ("Py_ssize_t mysize;", "Py_ssize_t size;", "char *buf;"), ("if (count < 0)", "mysize = Py_SIZE(self);", "if (count > 0 && mysize > PY_SSIZE_T_MAX / count)", "size = mysize * count;", "if (PyByteArray_Resize((PyObject *)self, size) < 0)", "buf = PyByteArray_AS_STRING(self);")))
for signature, declarations, markers in checks:
body = body_for(signature)
for declaration in declarations:
if body.count(declaration) != 1 or body.index(declaration) > body.index(markers[0]):
raise SystemExit(signature + " locals must precede statements")
positions = [body.index(marker) for marker in markers]
if positions != sorted(positions):
raise SystemExit(signature + " changed overflow/resize/copy order")
body = body_for("bytearray_ass_subscript(PyByteArrayObject *self")
if body.count("int ival;") != 1 or body.index("int ival;") > body.index("buf = PyByteArray_AS_STRING(self);"):
raise SystemExit("bytearray_ass_subscript value local must be declared at entry")
if not (body.index("Py_ssize_t i = PyNumber_AsSsize_t") < body.index("ival = -1;") < body.index("if (values && !_getbytevalue(values, &ival))")):
raise SystemExit("bytearray_ass_subscript changed index/value conversion order")
body = body_for("bytearray___init___impl(PyByteArrayObject *self")
start = body.index("if (PyList_CheckExact(arg) || PyTuple_CheckExact(arg)) {")
end = body.index("\n }\nslowpath:", start) + 6
block = body[start:end]
for declaration in ("Py_ssize_t size;", "PyObject **items;", "char *s;", "Py_ssize_t i;"):
if block.count(declaration) != 1 or block.index(declaration) > block.index("size = PySequence_Fast_GET_SIZE(arg);"):
raise SystemExit("bytearray fast list locals must precede the resize")
if "for (Py_ssize_t i =" in block:
raise SystemExit("bytearray fast list retains a C99 loop declaration")
if not (block.index("size = PySequence_Fast_GET_SIZE(arg);") < block.index("PyByteArray_Resize((PyObject *)self, size)") < block.index("items = PySequence_Fast_ITEMS(arg);") < block.index("s = PyByteArray_AS_STRING(self);") < block.index("for (i = 0;") < block.index("int value;") < block.index("int rc;") < block.index("rc = _getbytevalue(items[i], &value);") < block.index("s[i] = value;")):
raise SystemExit("bytearray fast list changed resize/validation/copy order")
PY_BYTEARRAY_CORE_C89_TEST
then
pass "bytearray repeat/slice helpers preserve operation order with C89 locals"
else
bad "bytearray repeat/slice helpers preserve operation order with C89 locals"
fi
# fastsearch debug logging uses fixed-arity macros compatible with the VC6 preprocessor.
if python3 - "$REPO_ROOT/cpython/Objects/stringlib/fastsearch.h" <<'PY_FASTSEARCH_VC6_MACRO_TEST'
import re
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
if "# define LOG(args) printf args" not in source or "# define LOG(args) ((void)0)" not in source:
raise SystemExit("fastsearch LOG must use fixed-arity debug/no-op definitions")
if re.search(r"^\s*#\s*define\s+LOG\s*\(\s*\.\.\.", source, re.MULTILINE) or "__VA_ARGS__" in source:
raise SystemExit("fastsearch must not use variadic macros")
for line in source.splitlines():
if re.search(r"\bLOG\(", line) and not re.search(r"#\s*define\s+LOG\(", line) and "LOG((" not in line:
raise SystemExit("fastsearch LOG invocation must parenthesize its argument tuple")
body_start = source.index("STRINGLIB(_preprocess)(const STRINGLIB_CHAR *needle")
body_open = source.index("{", body_start)
body_end = source.index(chr(10) + "}" + chr(10), body_open) + 2
body = source[body_open:body_end]
for declaration in ("Py_ssize_t i;", "STRINGLIB_CHAR last;", "Py_ssize_t not_found_shift;"):
if body.count(declaration) != 1 or body.index(declaration) > body.index("p->needle = needle;" ):
raise SystemExit("fastsearch preprocessing locals must be declared at function entry")
if "for (Py_ssize_t i =" in body:
raise SystemExit("fastsearch preprocessing retains C99 loop declarations")
if not (body.index("p->gap = len_needle;") < body.index("last = needle[len_needle - 1] & TABLE_MASK;") < body.index("for (i = len_needle - 2;") < body.index("not_found_shift = Py_MIN(len_needle, MAX_SHIFT);") < body.index("for (i = 0;") < body.index("for (i = len_needle - not_found_shift;")):
raise SystemExit("fastsearch preprocessing changed table construction order")
body_start = source.index("STRINGLIB(_two_way)(const STRINGLIB_CHAR *haystack")
body_open = source.index("{", body_start)
body_end = source.index(chr(10) + "}" + chr(10), body_open) + 2
body = source[body_open:body_end]
for declaration in ("Py_ssize_t memory;", "Py_ssize_t i;", "Py_ssize_t shift;", "Py_ssize_t gap_jump_end;", "Py_ssize_t mem_jump;"):
if body.count(declaration) != 1 or body.index(declaration) > body.index("LOG((" ):
raise SystemExit("fastsearch two-way locals must precede logging and algorithm statements")
if "for (Py_ssize_t i =" in body:
raise SystemExit("fastsearch two-way search retains C99 loop declarations")
if not (body.index("if (p->is_periodic)") < body.index("memory = 0;") < body.index("for (;;)") < body.index("shift = table[") < body.index("window_last += shift;") < body.index("i = Py_MAX(cut, memory);") < body.index("if (needle[i] != window[i])")):
raise SystemExit("fastsearch periodic search changed memory/shift/comparison order")
if not (body.index("gap_jump_end = Py_MIN(len_needle, cut + gap);") < body.index("for (i = cut;") < body.index("for (i = gap_jump_end;") < body.index("for (i = 0; i < cut;")):
raise SystemExit("fastsearch nonperiodic mismatch order changed")
body_start = source.index("STRINGLIB(_two_way_find)(const STRINGLIB_CHAR *haystack")
body_open = source.index("{", body_start)
body_end = source.index(chr(10) + "}" + chr(10), body_open) + 2
body = source[body_open:body_end]
if body.count("STRINGLIB(prework) p;") != 1 or body.index("STRINGLIB(prework) p;") > body.index("LOG((" ):
raise SystemExit("fastsearch find prework must be declared before logging")
body_start = source.index("STRINGLIB(_two_way_count)(const STRINGLIB_CHAR *haystack")
body_open = source.index("{", body_start)
body_end = source.index(chr(10) + "}" + chr(10), body_open) + 2
body = source[body_open:body_end]
for declaration in ("STRINGLIB(prework) p;", "Py_ssize_t index;", "Py_ssize_t count;"):
if body.count(declaration) != 1 or body.index(declaration) > body.index("LOG((" ):
raise SystemExit("fastsearch count locals must be declared before logging")
if not (body.index("LOG((") < body.index("STRINGLIB(_preprocess)(needle, len_needle, &p);") < body.index("index = 0;") < body.index("count = 0;") < body.index("while (1)")):
raise SystemExit("fastsearch count initialization/preprocessing order changed")
PY_FASTSEARCH_VC6_MACRO_TEST
then
pass "fastsearch logging macros parse with the VC6 preprocessor"
else
bad "fastsearch logging macros parse with the VC6 preprocessor"
fi
# Strip bounds use C89 entry declarations while preserving the executable body.
if python3 - "$REPO_ROOT/cpython/Objects/bytearrayobject.c" <<'PY_BYTEARRAY_STRIP_C89_TEST'
import re
import sys
from pathlib import Path
source = Path(sys.argv[1]).read_text()
start = source.index("\n{", source.index("bytearray_strip_impl_helper(")) + 2
end = source.index("\n}", start)
body = source[start:end]
opening, execution = body.split(" if (bytes == Py_None)", 1)
for name in ("left", "right"):
declaration = "Py_ssize_t " + name + ";"
if opening.count(declaration) != 1 or body.count(declaration) != 1:
raise SystemExit(name + " must be declared once in the opening group")
if re.search(r"\bPy_ssize_t\b", execution):
raise SystemExit("strip bounds retain late declarations")
expected = r""" if (bytes == Py_None) {
bytesptr = "\t\n\r\f\v ";
byteslen = 6;
}
else {
if (PyObject_GetBuffer(bytes, &vbytes, PyBUF_SIMPLE) != 0)
return NULL;
bytesptr = (const char*)vbytes.buf;
byteslen = vbytes.len;
}
myptr = PyByteArray_AS_STRING(self);
mysize = Py_SIZE(self);
left = 0;
if (striptype != RIGHTSTRIP) {
while (left < mysize && memchr(bytesptr, (unsigned char)myptr[left], byteslen))
left++;
}
right = mysize;
if (striptype != LEFTSTRIP) {
do {
right--;
} while (right >= left && memchr(bytesptr, (unsigned char)myptr[right], byteslen));
right++;
}
if (bytes != Py_None)
PyBuffer_Release(&vbytes);
return PyByteArray_FromStringAndSize(myptr + left, right - left);"""
if " ".join((" if (bytes == Py_None)" + execution).split()) != " ".join(expected.split()):
raise SystemExit("strip initialization/control flow changed execution order")
PY_BYTEARRAY_STRIP_C89_TEST
then
pass "bytearray strip preserves execution order with C89 bound declarations"
else
bad "bytearray strip preserves execution order with C89 bound declarations"
fi
if python3 "$REPO_ROOT/scripts/vc6-classobject-c89.test.py" "$REPO_ROOT/cpython/Objects/classobject.c"; then
pass "class method vectorcall preserves C89 declarations and cleanup ordering"
else
bad "class method vectorcall preserves C89 declarations and cleanup ordering"
fi
if python3 "$REPO_ROOT/scripts/vc6-codeobject-c89.test.py" "$REPO_ROOT/cpython/Objects/codeobject.c"; then
pass "codeobject early C89 declarations preserve order"
else
bad "codeobject early C89 declarations preserve order"
fi
if python3 "$REPO_ROOT/scripts/vc6-descrobject-classmethod-c89.test.py" "$REPO_ROOT/cpython/Objects/descrobject.c" "$REPO_ROOT/cpython/Objects/object.c"; then
pass "descrobject C89 dispatch and VC6 type initialization order"
else
bad "descrobject C89 dispatch and VC6 type initialization order"
fi
if python3 "$REPO_ROOT/scripts/vc6-dict-consistency-c89.test.py" "$REPO_ROOT/cpython/Objects/stringlib/eq.h" "$REPO_ROOT/cpython/Objects/dictobject.c"; then
pass "dict consistency C89 declarations and logic order"
else
bad "dict consistency C89 declarations and logic order"
fi
if python3 "$REPO_ROOT/scripts/vc6-call-first-c89.test.py" "$REPO_ROOT/cpython/Objects/call.c"; then
pass "call.c vectorcall C89 locals preserve dispatch order"
else
bad "call.c vectorcall C89 locals preserve dispatch order"
fi
if python3 "$REPO_ROOT/scripts/vc6-bytes-core-c89.test.py" "$REPO_ROOT/cpython/Objects/bytesobject.c"; then
pass "bytes find/repeat C89 locals preserve execution order"
else
bad "bytes find/repeat C89 locals preserve execution order"
fi
if python3 "$REPO_ROOT/scripts/vc6-stringlib-partition-c89.test.py" "$REPO_ROOT/cpython/Objects/stringlib/partition.h"; then
pass "stringlib partition uses C89 declarations and preserves ownership order"
else
bad "stringlib partition uses C89 declarations and preserves ownership order"
fi
if python3 "$REPO_ROOT/scripts/vc6-bytes-format-c89.test.py" "$REPO_ROOT/cpython/Objects/bytesobject.c"; then
pass "bytes format debug pointer uses C89 declaration order"
else
bad "bytes format debug pointer uses C89 declaration order"
fi
if python3 "$REPO_ROOT/scripts/vc6-bytes-clinic-c89.test.py" "$REPO_ROOT/cpython/Objects/clinic/bytesobject.c.h"; then
pass "bytes Clinic C89 declarations remain at wrapper entry"
else
bad "bytes Clinic C89 declarations remain at wrapper entry"
fi
if python3 "$REPO_ROOT/scripts/vc6-bytearray-join-c89.test.py" "$REPO_ROOT/cpython/Objects/bytearrayobject.c"; then
pass "bytearray_join C89 declaration preserves export/join/unexport order"
else
bad "bytearray_join C89 declaration preserves export/join/unexport order"
fi
if python3 "$REPO_ROOT/scripts/vc6-fileobject-opencode-c89.test.py" "$REPO_ROOT/cpython/Objects/fileobject.c"; then
pass "fileobject open-code hook C89 declaration and validation order"
else
bad "fileobject open-code hook C89 declaration and validation order"
fi
if python3 "$REPO_ROOT/scripts/vc6-floatobject-pack4-c89.test.py" "$REPO_ROOT/cpython/Objects/floatobject.c" "$REPO_ROOT/cpython/Objects/clinic/floatobject.c.h"; then
pass "floatobject float32 fallback C89 declarations and order"
else
bad "floatobject float32 fallback C89 declarations and order"
fi
if python3 "$REPO_ROOT/scripts/vc6-frameobject-c89.test.py" "$REPO_ROOT/cpython/Objects/frameobject.c"; then
pass "frameobject frame_getlocals C89 declaration and ownership order"
else
bad "frameobject frame_getlocals C89 declaration and ownership order"
fi
if [ "$fail" -ne 0 ]; then
echo "FAILED"
exit 1
fi
echo "ALL TESTS PASSED"