9953 lines
387 KiB
Bash
Executable File
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"
|