#!/usr/bin/env python3 """Focused VC6 C89 regression checks for Objects/longobject.c.""" import pathlib import re import sys source = pathlib.Path(sys.argv[1]).read_text(encoding="utf-8") match = re.search(r"static PyObject \*\s*get_small_int\(sdigit ival\)\s*\{(?P.*?)\n\}", source, re.S) assert match, "get_small_int definition not found" body = match.group("body") assert re.search(r"\A\s*PyObject \*v;\s*assert\(IS_SMALL_INT\(ival\)\);\s*v = \(PyObject \*\)&_PyLong_SMALL_INTS\[_PY_NSMALLNEGINTS \+ ival\];\s*Py_INCREF\(v\);\s*return v;", body), "get_small_int must declare v at function entry and retain assertion, lookup, incref, and return order" print("longobject get_small_int C89 declaration and evaluation order: PASS") match = re.search(r"PyLongObject \*\s*_PyLong_New\(Py_ssize_t size\)\s*\{(?P.*?)\n\}", source, re.S) assert match, "_PyLong_New definition not found" body = re.sub(r"/\*.*?\*/", "", match.group("body"), flags=re.S) expected = """ PyLongObject *result; Py_ssize_t ndigits; if (size > (Py_ssize_t)MAX_LONG_DIGITS) { PyErr_SetString(PyExc_OverflowError, "too many digits in integer"); return NULL; } ndigits = size ? size : 1; result = PyObject_Malloc(offsetof(PyLongObject, ob_digit) + ndigits*sizeof(digit)); if (!result) { PyErr_NoMemory(); return NULL; } _PyObject_InitVar((PyVarObject*)result, &PyLong_Type, size); return result; """ assert body.split() == expected.split(), "_PyLong_New must declare ndigits at function entry and retain overflow check, digit count, allocation, error, initialization, and return order" print("longobject _PyLong_New C89 declaration and allocation/error order: PASS") match = re.search(r"static PyObject \*\s*_PyLong_FromMedium\(sdigit x\)\s*\{(?P.*?)\n\}", source, re.S) assert match, "_PyLong_FromMedium definition not found" body = re.sub(r"/\*.*?\*/", "", match.group("body"), flags=re.S) expected = """ PyLongObject *v; Py_ssize_t sign; digit abs_x; assert(!IS_SMALL_INT(x)); assert(is_medium_int(x)); v = PyObject_Malloc(sizeof(PyLongObject)); if (v == NULL) { PyErr_NoMemory(); return NULL; } sign = x < 0 ? -1: 1; abs_x = x < 0 ? -x : x; _PyObject_InitVar((PyVarObject*)v, &PyLong_Type, sign); v->ob_digit[0] = abs_x; return (PyObject*)v; """ assert body.split() == expected.split(), "_PyLong_FromMedium must declare v, sign, and abs_x at function entry and retain assertions, allocation, NULL/error/return, sign, abs_x, initialization, digit write, and return order" print("longobject _PyLong_FromMedium C89 declarations and evaluation/error order: PASS") match = re.search(r"static PyObject \*\s*_PyLong_FromLarge\(stwodigits ival\)\s*\{(?P.*?)\n\}", source, re.S) assert match, "_PyLong_FromLarge definition not found" body = re.sub(r"/\*.*?\*/", "", match.group("body"), flags=re.S) expected = """ twodigits abs_ival; twodigits t; Py_ssize_t ndigits; PyLongObject *v; digit *p; int sign; assert(!is_medium_int(ival)); if (ival < 0) { abs_ival = 0U-(twodigits)ival; sign = -1; } else { abs_ival = (twodigits)ival; sign = 1; } assert(abs_ival >> PyLong_SHIFT != 0); t = abs_ival >> (PyLong_SHIFT * 2); ndigits = 2; while (t) { ++ndigits; t >>= PyLong_SHIFT; } v = _PyLong_New(ndigits); if (v != NULL) { p = v->ob_digit; Py_SET_SIZE(v, ndigits * sign); t = abs_ival; while (t) { *p++ = Py_SAFE_DOWNCAST( t & PyLong_MASK, twodigits, digit); t >>= PyLong_SHIFT; } } return (PyObject *)v; """ assert body.split() == expected.split(), "_PyLong_FromLarge must declare abs_ival, t, ndigits, v, p, and sign at function entry and retain assertions, sign/absolute-value branches, digit count, allocation, initialization, digit writes, and return order" print("longobject _PyLong_FromLarge C89 declarations and evaluation order: PASS") match = re.search(r"^#define PYLONG_FROM_UINT\(INT_TYPE, ival\) (?P.*?)^ \} while\(0\)$", source, re.S | re.M) assert match, "PYLONG_FROM_UINT definition not found" body = match.group("body") + " } while(0)" body = re.sub(r"\\\n", "\n", body) body = re.sub(r"/\*.*?\*/", "", body, flags=re.S) assert re.search(r"\A\s*do\s*\{\s*Py_ssize_t ndigits;\s*INT_TYPE t;\s*PyLongObject \*v;\s*digit \*p;", body), "PYLONG_FROM_UINT must declare ndigits, t, v, and p at do-block entry" expected = """ do { Py_ssize_t ndigits; INT_TYPE t; PyLongObject *v; digit *p; if (IS_SMALL_UINT(ival)) { return get_small_int((sdigit)(ival)); } ndigits = 0; t = (ival); while (t) { ++ndigits; t >>= PyLong_SHIFT; } v = _PyLong_New(ndigits); if (v == NULL) { return NULL; } p = v->ob_digit; while ((ival)) { *p++ = (digit)((ival) & PyLong_MASK); (ival) >>= PyLong_SHIFT; } return (PyObject *)v; } while(0) """ assert body.split() == expected.split(), "PYLONG_FROM_UINT must retain small-int early return, ndigits/t evaluation, digit count, allocation, NULL return, p initialization, ival evaluation/mutation, digit writes, and return order" print("longobject PYLONG_FROM_UINT C89 declarations and evaluation/error order: PASS") match = re.search(r"PyObject \*\s*PyLong_FromUnsignedLongLong\((?P[^)]*)\)\s*\{(?P.*?)\n\}", source, re.S) assert match, "PyLong_FromUnsignedLongLong definition not found" assert match.group("parameter").split() == "unsigned PY_LONG_LONG ival".split(), "PyLong_FromUnsignedLongLong must use unsigned PY_LONG_LONG to match the public declaration and VC6 __int64 spelling" assert match.group("body").split() == "PYLONG_FROM_UINT(unsigned PY_LONG_LONG, ival);".split(), "PyLong_FromUnsignedLongLong must pass unsigned PY_LONG_LONG to PYLONG_FROM_UINT, preserving the unsigned 64-bit temporary" print("longobject PyLong_FromUnsignedLongLong VC6 parameter and macro type spelling: PASS") match = re.search(r"PyObject \*\s*PyLong_FromDouble\(double dval\)\s*\{(?P.*?)\n\}", source, re.S) assert match, "PyLong_FromDouble definition not found" body = re.sub(r"/\*.*?\*/", "", match.group("body"), flags=re.S) assert re.search(r"\A\s*PyLongObject \*v;\s*double frac;\s*int i, ndig, expo, neg;\s*const double int_max = \(unsigned long\)LONG_MAX \+ 1;", body), "PyLong_FromDouble locals must precede the fast-path statement" ordered = ( "const double int_max = (unsigned long)LONG_MAX + 1;", "if (-int_max < dval && dval < int_max)", "return PyLong_FromLong((long)dval);", "neg = 0;", "if (Py_IS_INFINITY(dval))", "if (Py_IS_NAN(dval))", "if (dval < 0.0)", "frac = frexp(dval, &expo);", "ndig = (expo-1) / PyLong_SHIFT + 1;", "v = _PyLong_New(ndig);", "frac = ldexp(frac, (expo-1) % PyLong_SHIFT + 1);", "for (i = ndig; --i >= 0; )", "if (neg)", "return (PyObject *)v;", ) positions = [body.find(token) for token in ordered] assert all(position >= 0 for position in positions) and positions == sorted(positions), "PyLong_FromDouble must preserve fast-path, validation, sign, conversion, allocation, digit-loop, and return order" print("longobject PyLong_FromDouble C89 declarations and evaluation/error order: PASS") code = re.sub(r"/\*.*?\*/|//[^\n]*|\"(?:\\.|[^\"\\])*\"", "", source, flags=re.S) assert not re.search(r"\blong\s+long\b", code), "longobject must spell 64-bit integers with PY_LONG_LONG for VC6" assert not re.search(r"\bLLONG_(?:MAX|MIN)\b", code), "longobject must use PY_LLONG_MAX/PY_LLONG_MIN instead of unavailable LLONG limits" for signature in ( r"PyLong_FromLongLong\(PY_LONG_LONG ival\)", r"PyLong_AsLongLong\(PyObject \*vv\)", r"PyLong_AsUnsignedLongLong\(PyObject \*vv\)", r"PyLong_AsUnsignedLongLongMask\(PyObject \*vv\)", r"PyLong_AsLongLongAndOverflow\(PyObject \*vv, int \*overflow\)", ): assert re.search(signature, source), "missing VC6-compatible long API declaration/definition: " + signature print("longobject 64-bit API uses VC6 PY_LONG_LONG spellings: PASS") match = re.search(r"static digit\s+inplace_divrem1\(digit \*pout, digit \*pin, Py_ssize_t size, digit n\)\s*\{(?P.*?)\n\}", source, re.S) assert match, "inplace_divrem1 definition not found" body = re.sub(r"/\*.*?\*/", "", match.group("body"), flags=re.S) loop = re.search(r"while \(--size >= 0\)\s*\{(?P.*?)\n \}", body, re.S) assert loop, "inplace_divrem1 digit loop not found" loop_body = loop.group("body") assert re.search(r"\A\s*twodigits dividend;\s*digit quotient;\s*dividend = \(\(twodigits\)remainder << PyLong_SHIFT\) \| pin\[size\];\s*quotient = \(digit\)\(dividend / n\);\s*remainder = dividend % n;\s*pout\[size\] = quotient;", loop_body), "inplace_divrem1 loop temporaries must be declared before the executable dividend calculation, preserving divide/remainder/store order" print("longobject inplace_divrem1 loop C89 declaration and division order: PASS") late_ucs4 = "else {\n assert (kind == PyUnicode_4BYTE_KIND);\n Py_UCS4 *p;" entry_ucs4 = "else {\n Py_UCS4 *p;\n assert (kind == PyUnicode_4BYTE_KIND);" assert source.count(late_ucs4) == 0, "Py_UCS4 locals must be hoisted before their branch assertions" assert source.count(entry_ucs4) == 2, "both long-formatting branches must declare Py_UCS4 before checking the kind" print("longobject Unicode formatting Py_UCS4 C89 branch declarations: PASS") match = re.search(r"PyObject \*\s*PyLong_FromString\(const char \*str, char \*\*pend, int base\)\s*\{(?P.*?)\n\}", source, re.S) assert match, "PyLong_FromString definition not found" body = re.sub(r"/\*.*?\*/", "", match.group("body"), flags=re.S) assert re.search(r"\A\s*int sign = 1, error_if_nonzero = 0;\s*const char \*start, \*orig_str = str;\s*PyLongObject \*z = NULL;\s*PyObject \*strobj;\s*Py_ssize_t slen;\s*double fsize_z;", body), "PyLong_FromString must declare fsize_z in the function's opening declaration group" ordered = ( "if (digits > _PY_LONG_MAX_STR_DIGITS_THRESHOLD)", "fsize_z = (double)digits * log_base_BASE[base] + 1.0;", "if (fsize_z > (double)MAX_LONG_DIGITS)", "size_z = (Py_ssize_t)fsize_z;", "z = _PyLong_New(size_z);", ) positions = [body.find(token) for token in ordered] assert all(position >= 0 for position in positions) and positions == sorted(positions), "PyLong_FromString must preserve digit-limit, size calculation, overflow, allocation, and conversion order" print("longobject PyLong_FromString C89 size declaration and parsing order: PASS") match = re.search(r"static int\s+long_divrem\(PyLongObject \*a, PyLongObject \*b,\s*PyLongObject \*\*pdiv, PyLongObject \*\*prem\)\s*\{(?P.*?)\n\}", source, re.S) assert match, "long_divrem definition not found" body = match.group("body") short_case = re.search(r"if \(size_a < size_b.*?\) \{(?P.*?)\n \}", body, re.S) assert short_case, "long_divrem smaller-dividend branch not found" branch = short_case.group("body") tokens = ( "PyObject *zero;", "*prem = (PyLongObject *)long_long((PyObject *)a);", "if (*prem == NULL)", "zero = _PyLong_GetZero();", "Py_INCREF(zero);", "*pdiv = (PyLongObject*)zero;", ) positions = [branch.find(token) for token in tokens] assert all(position >= 0 for position in positions) and positions == sorted(positions), "long_divrem must declare zero at branch entry and preserve remainder, error, singleton, incref, and quotient ordering" print("longobject long_divrem C89 local and reference order: PASS") match = re.search(r"PyObject \*\s*_PyLong_Add\(PyLongObject \*a, PyLongObject \*b\)\s*\{(?P.*?)\n\}", source, re.S) assert match, "_PyLong_Add definition not found" body = match.group("body") assert re.match(r"\s*PyLongObject \*z;", body), "_PyLong_Add must declare z at function entry before the small-value fast path" assert " PyLongObject *z;" not in body[body.find("if (IS_MEDIUM_VALUE(a)"):], "_PyLong_Add must not declare z after an executable fast-path statement" ordered = ( "PyLongObject *z;", "if (IS_MEDIUM_VALUE(a) && IS_MEDIUM_VALUE(b))", "return _PyLong_FromSTwoDigits(medium_value(a) + medium_value(b));", "if (Py_SIZE(a) < 0)", "z = x_add(a, b);", "return (PyObject *)z;", ) positions = [body.find(token) for token in ordered] assert all(position >= 0 for position in positions) and positions == sorted(positions), "_PyLong_Add must preserve small-integer fast path and sign/operation order" print("longobject _PyLong_Add C89 declaration and arithmetic order: PASS") match = re.search(r"static PyObject \*\s*long_pow\(PyObject \*v, PyObject \*w, PyObject \*x\)\s*\{(?P.*?)\n\}", source, re.S) assert match, "long_pow definition not found" body = match.group("body") assert 0 <= body.find("digit bi, bit;") < body.find("CHECK_BINOP(v, w);"), "long_pow must declare exponent digit temporaries with its opening locals" assert "digit bi = i ?" not in body and "digit bit;" not in body[body.find("i = Py_SIZE(b);"):], "long_pow must not declare digit temporaries after executable statements" ordered = ("i = Py_SIZE(b);", "bi = i ? b->ob_digit[i-1] : 0;", "if (i <= 1 && bi <= 3)") positions = [body.find(token) for token in ordered] assert all(position >= 0 for position in positions) and positions == sorted(positions), "long_pow must preserve exponent digit setup and branch ordering" print("longobject long_pow C89 declarations and exponent setup order: PASS") branch_start = body.find("else {\n int pending;\n int blen;") branch_end = body.find("\n }\n\n if (negativeOutput", branch_start) assert branch_start >= 0 and branch_end > branch_start, "long_pow k-ary branch boundaries not found" branch = body[branch_start:branch_end] assert branch.find("int pending;") < branch.find("Py_INCREF(a);") and branch.find("int blen;") < branch.find("Py_INCREF(a);"), "long_pow pending and bit-length locals must be declared at branch entry" assert "int pending = 0, blen = 0;" not in branch, "long_pow pending locals must be declared separately from their original initialization point" ordered = ("Py_CLEAR(a2);", "pending = 0;", "blen = 0;", "#define ABSORB_PENDING") positions = [branch.find(token) for token in ordered] assert all(position >= 0 for position in positions) and positions == sorted(positions), "long_pow must preserve pending initialization after table setup and before the sliding-window macro" print("longobject long_pow C89 pending declarations and initialization order: PASS") match = re.search(r"static int\s+divmod_shift\(PyObject \*shiftby, Py_ssize_t \*wordshift, digit \*remshift\)\s*\{(?P.*?)\n\}", source, re.S) assert match, "divmod_shift definition not found" body = match.group("body") assert re.match(r"\s*Py_ssize_t lshiftby;\s*PyLongObject \*wordshift_obj;", body), "divmod_shift must declare both conversion temporaries at function entry" assert "Py_ssize_t lshiftby =" not in body and "PyLongObject *wordshift_obj =" not in body, "divmod_shift must preserve original conversion points as assignments" ordered = ("assert(PyLong_Check(shiftby));", "assert(Py_SIZE(shiftby) >= 0);", "lshiftby = PyLong_AsSsize_t((PyObject *)shiftby);", "wordshift_obj = divrem1((PyLongObject *)shiftby, PyLong_SHIFT, remshift);", "*wordshift = PyLong_AsSsize_t((PyObject *)wordshift_obj);") positions = [body.find(token) for token in ordered] assert all(position >= 0 for position in positions) and positions == sorted(positions), "divmod_shift must preserve validation, conversion, fallback division, and result extraction order" print("longobject divmod_shift C89 locals and overflow-fallback order: PASS") match = re.search(r"static PyObject \*\s*long_rshift1\(PyLongObject \*a, Py_ssize_t wordshift, digit remshift\)\s*\{(?P.*?)\n\}", source, re.S) assert match, "long_rshift1 definition not found" body = match.group("body") assert "digit sticky = 0;" not in body and "for (Py_ssize_t j =" not in body and "for (Py_ssize_t i =" not in body, "long_rshift1 must not declare locals in executable branches or C99 for initializers" negative_matches = list(re.finditer(r"if \(a_negative\)\s*\{(?P.*?)\n \}", body, re.S)) assert negative_matches, "long_rshift1 negative-input branch not found" negative_body = negative_matches[-1].group("body") assert negative_body.find("digit sticky;") < negative_body.find("Py_SET_SIZE(z, -newsize);"), "long_rshift1 must declare sticky before branch statements" assert negative_body.find("sticky = 0;") > negative_body.find("Py_SET_SIZE(z, -newsize);"), "long_rshift1 must initialize sticky after setting the result sign" assert "{\n Py_ssize_t j;\n for (j = 0; j < wordshift; j++)" in negative_body, "long_rshift1 must keep the sticky-scan counter block-scoped with C89 declarations" assert "{\n Py_ssize_t i, j;\n i = 0;\n j = wordshift + 1;\n for (; j < size_a; i++, j++)" in body, "long_rshift1 must keep copy-loop counters block-scoped and preserve initial values" print("longobject long_rshift1 C89 locals and shifted-digit order: PASS") for name, op in (("long_and", "&"), ("long_xor", "^"), ("long_or", "|")): match = re.search(r"static PyObject \*\s*" + name + r"\(PyObject \*a, PyObject \*b\)\s*\{(?P.*?)\n\}", source, re.S) assert match, name + " definition not found" body = match.group("body") expected = """ PyLongObject *x; PyLongObject *y; CHECK_BINOP(a, b); x = (PyLongObject*)a; y = (PyLongObject*)b; """ assert body.split()[:len(expected.split())] == expected.split(), name + " must declare x/y before type checking, then cast and preserve the small-value fast path" assert "PyLongObject *x =" not in body and "PyLongObject *y =" not in body, name + " must not declare cast locals after CHECK_BINOP" assert "return long_bitwise(x, '" + op + "', y);" in body, name + " must retain the expected general bitwise operation" print("longobject bitwise entry points C89 locals and dispatch order: PASS") match = re.search(r"PyObject \*\s*_PyLong_DivmodNear\(PyObject \*a, PyObject \*b\)\s*\{(?P.*?)\n\}", source, re.S) assert match, "_PyLong_DivmodNear definition not found" body = match.group("body") assert re.match(r"\s*PyLongObject \*quo = NULL, \*rem = NULL;\s*PyObject \*twice_rem, \*result, \*temp;\s*PyObject \*one;\s*int quo_is_odd, quo_is_neg;\s*Py_ssize_t cmp;", body), "_PyLong_DivmodNear must declare one with the function's opening locals" assert "PyObject *one =" not in body, "_PyLong_DivmodNear must preserve _PyLong_GetOne lookup at its original execution point" ordered = ("if (!PyLong_Check(a) || !PyLong_Check(b))", "quo_is_neg =", "long_divrem(", "one = _PyLong_GetOne();", "twice_rem = long_lshift((PyObject *)rem, one);") positions = [body.find(token) for token in ordered] assert all(position >= 0 for position in positions) and positions == sorted(positions), "_PyLong_DivmodNear must preserve validation, divrem, singleton lookup, and shift order" print("longobject _PyLong_DivmodNear C89 local and singleton lookup order: PASS") match = re.search(r"static PyObject \*\s*int_bit_count_impl\(PyObject \*self\)\s*/\*.*?\*/\s*\{(?P.*?)\n\}", source, re.S) assert match, "int_bit_count_impl definition not found" body = match.group("body") expected = """ PyLongObject *z; Py_ssize_t ndigits, bit_count, ndigits_fast, i; PyObject *result; assert(self != NULL); assert(PyLong_Check(self)); z = (PyLongObject *)self; ndigits = Py_ABS(Py_SIZE(z)); bit_count = 0; """ assert body.split()[:len(expected.split())] == expected.split(), "int_bit_count_impl must place declarations before assertions and preserve initialization order" assert "Py_ssize_t ndigits =" not in body and "Py_ssize_t bit_count =" not in body and "for (Py_ssize_t i =" not in body, "int_bit_count_impl must not use late declarations or C99 loop variables" assert "PyObject *result =" not in body and "PyObject *x =" not in body and "PyObject *y =" not in body, "int_bit_count_impl must initialize object locals at their original execution points" assert "Py_ssize_t ndigits_fast =" not in body, "int_bit_count_impl must calculate the fast digit bound at the original point" ordered = ("ndigits_fast = Py_MIN(ndigits, PY_SSIZE_T_MAX/PyLong_SHIFT);", "for (i = 0; i < ndigits_fast; i++)", "result = PyLong_FromSsize_t(bit_count);", "for (i = ndigits_fast; i < ndigits; i++)") positions = [body.find(token) for token in ordered] assert all(position >= 0 for position in positions) and positions == sorted(positions), "int_bit_count_impl must preserve fast and overflow-safe accumulation order" loop = re.search(r"for \(i = ndigits_fast; i < ndigits; i\+\+\)\s*\{(?P.*?)\n \}", body, re.S) assert loop and re.match(r"\s*PyObject \*x;\s*PyObject \*y;\s*x = PyLong_FromLong\(popcount_digit\(z->ob_digit\[i\]\)\);", loop.group("body")), "int_bit_count_impl must declare per-digit temporaries before the loop body statements" assert 0 <= loop.group("body").find("y = long_add(") < loop.group("body").find("Py_DECREF(x);"), "int_bit_count_impl must preserve accumulation before releasing the temporary digit count" print("longobject int_bit_count_impl C89 declarations and accumulation order: PASS")