port: expand VC6 C89 core frontier
This commit is contained in:
+213
-1
@@ -1,4 +1,196 @@
|
||||
## Latest revision (frameobject C89 work; frontier advances past floatobject)
|
||||
## Latest revision (generated compatibility patch refreshed; VC6 frontier in object.c)
|
||||
|
||||
CPython baseline: `v3.11.16`, commit `41388c9cb160d0886d5ca00d2e6c878260a8a4549`.
|
||||
|
||||
The incremental C89/VC6 port now compiles `memoryobject.c`, `methodobject.c`, and `moduleobject.c` directly with VC6. Focused checks cover their declaration, evaluation, allocation, reference, and type-initialization ordering. Additional C89 conversions and focused checks have been added for `object.c`; its direct VC6 compile still fails with 72 diagnostics, many likely cascading from late declarations and remaining C99 constructs. This is compile-only progress; no interpreter link or Windows 98 SE runtime test is verified.
|
||||
|
||||
The tracked compatibility patch `compat/msvc600/cpython-3.11.16-vc6-headers.patch` has been regenerated from the full nested CPython working-tree diff and the untracked `Include/internal/pycore_win9x_loadlibrary.h`. A byte-for-byte check confirmed the patch contains all 121 modified/new file diffs. The nested CPython checkout remains detached; its source changes are represented in the root repository by this patch, not by committing the nested checkout.
|
||||
|
||||
Next: finish `object.c`, rerun the forced source-list compile frontier and focused regressions, then tackle linking and real Windows 98 SE execution separately. No link/runtime success is claimed.
|
||||
|
||||
---
|
||||
|
||||
## Latest revision (`PyLong_FromUnsignedLongLong` uses VC6-compatible unsigned type spelling)
|
||||
|
||||
Date: 2026-10-03 14:15 UTC
|
||||
|
||||
Read `PORT_STATUS.md`, `README.md`, and the invoked legacy-toolchain guidance before work. The on-disk `python-3-win9x-msvc6.md` reference was absent. Preserved all pre-existing outer and detached CPython work; no reset, clean, stash, discard, commit, push, or publication. CPython remains at tag `v3.11.16`, commit `41388c9cb160d0886d5ca00d2e6c878260a8a4549` (the same hash as recorded previously). The prior recorded `_PyLong_FromLarge()` frontier was stale. Forced-fresh MSVC600 `cl.exe` via Wine re-probe confirmed the actual first blocker was C2632 at `Objects/longobject.c(374)` and `(376)`: `unsigned long long` is rejected by VC6 in the `PyLong_FromUnsignedLongLong` definition and `PYLONG_FROM_UINT` instantiation. The compiler probe output did not print a separate compiler version banner.
|
||||
|
||||
Used one focused Codex CLI lane (`codex-cli 0.153.4`, workspace-write) for one narrow TDD slice. Extended `scripts/vc6-longobject-c89.test.py`, observed RED against the original spelling, then changed only the function parameter and macro type argument to `unsigned PY_LONG_LONG`. This matches the public declaration in `Include/longobject.h` and the existing VC6 mapping to `__int64` in `PC/pyconfig.h`, preserving unsigned 64-bit intent. No other source declaration or behavior changed. The existing later C89 errors were left untouched.
|
||||
|
||||
Exact verification:
|
||||
|
||||
- Initial forced-fresh probe: `XDG_RUNTIME_DIR="$PWD/build/current-recheck/runtime" WINEPREFIX=/home/ubuntu/.wine-win9xport WINEDEBUG=-all VC6_FRONTIER_FORCE=1 bash scripts/vc6-probe-pythoncore-frontier.sh` — exit 2, `total=95 compiled=94 cached=0 skipped=0`; first errors C2632 at `longobject.c(374)` and `(376)`, followed by line-407 C89 errors. Log: `/home/ubuntu/.hermes/profiles/maxwell/cache/terminal-output/out-1791036400-3732714-7090.log`.
|
||||
- RED: `python3 scripts/vc6-longobject-c89.test.py cpython/Objects/longobject.c` — exit 1, expected assertion that `PyLong_FromUnsignedLongLong` must use the public/VC6-compatible unsigned `PY_LONG_LONG` spelling.
|
||||
- GREEN: same focused test — exit 0; all six longobject assertions passed.
|
||||
- Applicable regression suite: `bash scripts/vc6-probe-pythoncore-frontier.test.sh` — exit 1. The longobject checks and other applicable checks passed; two known unrelated failures remain: console type lookup/cleanup order and numeric dispatch (`ValueError: substring not found`). Log: `/tmp/vc6-longlong-slice/suite.log`.
|
||||
- Post-change forced-fresh probe (same command) — exit 2, `total=95 compiled=94 cached=0 skipped=0`. The C2632 errors at lines 374/376 are gone; first stable errors now occur at line 407 in `PyLong_FromDouble()` (C2275/C2065 on late `PyLongObject *v`, followed by C89 declaration errors). Later independent long-long/CRT diagnostics remain. Log: `/home/ubuntu/.hermes/profiles/maxwell/cache/terminal-output/out-1791036840-3732714-b110.log`.
|
||||
- `git -C cpython diff --check -- Objects/longobject.c` and `git diff --check -- scripts/vc6-longobject-c89.test.py` — exit 0. Preservation comparison confirmed only this source and focused test changed beyond the pre-run snapshot; `PORT_STATUS.md`, compatibility patch, and all unrelated local changes were preserved.
|
||||
|
||||
Files changed by this slice: `cpython/Objects/longobject.c`, `scripts/vc6-longobject-c89.test.py`, and this status entry. This is compiler-probe progress only: no linked `pythoncore.dll`/`python.exe`, Wine interpreter execution, or actual Windows 98 SE execution. Next frontier: test-first address only the first late-local declaration family in `PyLong_FromDouble()` at line 407, preserving all fast-path and validation/evaluation order; keep subsequent long-long/CRT issues separate.
|
||||
|
||||
---
|
||||
|
||||
## Previous revision (`_PyLong_FromLarge` C89 locals hoisted; frontier advances within longobject)
|
||||
|
||||
Date: 2026-10-03 09:47 UTC
|
||||
|
||||
Read `PORT_STATUS.md`, `README.md`, and the legacy-toolchain guidance first. The referenced on-disk `python-3-win9x-msvc6.md` file was not found in the active profile or repository; the invoked skill guidance was available. Preserved all pre-existing outer and detached CPython work; no reset, clean, stash, discard, commit, push, or publication. CPython remains at tag `v3.11.16`, commit `41388c9cb160d0886d5ca00d2e6c878260a8a4549`.
|
||||
|
||||
The recorded `_PyLong_FromLarge()` frontier remained current on forced-fresh probe. Used one focused Codex CLI lane (`codex-cli 0.153.4`, workspace-write) for one narrow TDD slice. Extended `scripts/vc6-longobject-c89.test.py`, observed RED before the source edit, then hoisted only `twodigits abs_ival`, `twodigits t`, `Py_ssize_t ndigits`, `PyLongObject *v`, `digit *p`, and `int sign` to `_PyLong_FromLarge()`'s entry declarations in `cpython/Objects/longobject.c`. Initializers became assignments at their original execution points. Assertions, sign/absolute-value branches, digit-count loop, allocation, initialization, digit-write loop, and return ordering remain unchanged. Independently inspected the nested diff and confirmed only this function changed in the nested CPython slice.
|
||||
|
||||
Exact verification:
|
||||
|
||||
- Initial forced-fresh compiler probe: `XDG_RUNTIME_DIR="$PWD/build/current-recheck/runtime" WINEPREFIX=/home/ubuntu/.wine-win9xport WINEDEBUG=-all VC6_FRONTIER_FORCE=1 bash scripts/vc6-probe-pythoncore-frontier.sh` — exit 2, `total=95 compiled=94 cached=0 skipped=0`. `_blake2/blake2s_impl.c` compiled. First errors were C2275/C2146/C2065 on late `twodigits t` at `longobject.c(239)`, followed by late `ndigits` at 240 and `v` at 245. Full log: `/home/ubuntu/.hermes/profiles/maxwell/cache/terminal-output/out-1791020452-3685379-2910.log`.
|
||||
- RED: `python3 scripts/vc6-longobject-c89.test.py cpython/Objects/longobject.c` — exit 1 at the new `_PyLong_FromLarge` assertion; the prior get_small_int, `_PyLong_New`, and `_PyLong_FromMedium` checks passed.
|
||||
- GREEN: same focused command — exit 0; all four longobject regression checks passed.
|
||||
- Applicable broad regression: `bash scripts/vc6-probe-pythoncore-frontier.test.sh` — exit 1. Longobject checks and the rest of applicable checks run and pass. Two known unrelated failures remain: console type lookup/cleanup order and numeric dispatch (`ValueError: substring not found`).
|
||||
- Post-change forced-fresh probe (same command) — exit 2, `total=95 compiled=94 cached=0 skipped=0`. `longobject.c` now compiles past `_PyLong_FromLarge`; first errors advanced to line 364 in `PyLong_FromLong()` (C2275/C2146/C2065 on a late `Py_ssize_t ndigits`, then cascading late `t`, `v`, and `p` declarations). Subsequent diagnostics include independent long-long/CRT blockers. Full log: `/home/ubuntu/.hermes/profiles/maxwell/cache/terminal-output/out-1791020810-3685379-5b50.log`.
|
||||
- `git -C cpython diff --check -- Objects/longobject.c` and `git diff --check -- PORT_STATUS.md scripts/vc6-longobject-c89.test.py` — exit 0.
|
||||
|
||||
Files changed by this slice: `cpython/Objects/longobject.c`, `scripts/vc6-longobject-c89.test.py`, and this status entry. Compiler-probe progress only: no linked `pythoncore.dll`/`python.exe`, Wine interpreter execution, or actual Windows 98 SE execution. Next frontier: test-first address only the first `PyLong_FromLong()` declaration family at line 364, retaining its small/medium fast paths and digit-count/allocation order; handle later `long long` and CRT issues separately.
|
||||
|
||||
---
|
||||
|
||||
## Previous revision (`_PyLong_FromMedium` C89 locals hoisted; frontier advances within longobject)
|
||||
|
||||
Date: 2026-10-03 07:39 UTC
|
||||
|
||||
Read `PORT_STATUS.md`, `README.md`, and the legacy-toolchain reference before acting. Preserved the existing outer worktree and detached CPython changes; no reset, clean, stash, discard, commit, push, or publication. CPython remains at tag `v3.11.16`, commit `41388c9cb160d0886d5ca00d2e6c878260a8a4549`. The prior `_PyLong_New()` frontier was freshly rechecked: `_blake2/blake2s_impl.c` compiled, `_PyLong_New()`'s line-156 diagnostic was gone, and the first blocker was `_PyLong_FromMedium()` at line 205.
|
||||
|
||||
Used one focused Codex CLI coding lane (`codex-cli 0.153.4`, workspace-write). Strict TDD: extended `scripts/vc6-longobject-c89.test.py` for `_PyLong_FromMedium()` declarations and statement order, observed RED before the production edit, then hoisted only `PyLongObject *v`, `Py_ssize_t sign`, and `digit abs_x` to the opening declaration group in `cpython/Objects/longobject.c`. Assignments remain at their original points; assertions, allocation, NULL/error/return, initialization, digit write, and return order are preserved. The agent made no other changes; independently inspected the narrow diff.
|
||||
|
||||
Exact verification:
|
||||
|
||||
- Initial forced-fresh probe: `XDG_RUNTIME_DIR="$PWD/build/current-recheck/runtime" WINEPREFIX=/home/ubuntu/.wine-win9xport WINEDEBUG=-all VC6_FRONTIER_FORCE=1 bash scripts/vc6-probe-pythoncore-frontier.sh` — exit 2, `total=95 compiled=94 cached=0 skipped=0`. `_blake2/blake2s_impl.c` compiled. First blocker was C2143 at `longobject.c(205)` on the late `PyLongObject *v`; dependent errors included C2065 `v` and late `Py_ssize_t sign` / `digit abs_x`. Full log: `/home/ubuntu/.hermes/profiles/maxwell/cache/terminal-output/out-1791012585-3658734-75d0.log`.
|
||||
- RED: `python3 scripts/vc6-longobject-c89.test.py cpython/Objects/longobject.c` — exit 1, expected assertion that `_PyLong_FromMedium` declarations and order did not match; prior `get_small_int` and `_PyLong_New` checks passed.
|
||||
- GREEN: same command — exit 0; all three longobject checks passed, including the new `_PyLong_FromMedium` check.
|
||||
- Applicable regression suite: `bash scripts/vc6-probe-pythoncore-frontier.test.sh` — exit 1. The broad suite ran; this slice's source behavior assertion passed when run directly. Two known unrelated failures remain: console type lookup/cleanup order and numeric dispatch (`ValueError: substring not found`).
|
||||
- Post-change forced-fresh probe (same command) — exit 2, `total=95 compiled=94 cached=0 skipped=0`. `_PyLong_FromMedium()` passed its former diagnostics. First stable diagnostics now occur in `_PyLong_FromLarge()` at `longobject.c(239)` on late `twodigits t` (C2275/C2146, with C2065 `t`), followed by late `Py_ssize_t ndigits` at line 240 and late `PyLongObject *v` at line 245. Full log: `build/current-recheck/longobject-frommedium-post.log`.
|
||||
- `git diff --check -- PORT_STATUS.md scripts/vc6-longobject-c89.test.py && git -C cpython diff --check -- Objects/longobject.c` — exit 0.
|
||||
|
||||
Files changed by this slice: `cpython/Objects/longobject.c`, `scripts/vc6-longobject-c89.test.py`, and this status entry. This is compiler-probe progress only: no linked `pythoncore.dll`/`python.exe`, Wine interpreter execution, or actual Windows 98 SE execution. Next frontier: test-first hoist only the first `_PyLong_FromLarge()` declaration family in function-entry order, preserving assertions, sign/absolute-value decisions, digit-count loop, allocation, initialization, and digit-write order; keep later `long long`/CRT blockers separate.
|
||||
|
||||
---
|
||||
|
||||
## Previous revision (_PyLong_New ndigits declaration hoisted; frontier remains longobject)
|
||||
|
||||
Date: 2026-10-03 05:16 UTC
|
||||
|
||||
Read `PORT_STATUS.md`, `README.md`, and the CPython/Win9x/MSVC6 reference before acting. Preserved existing outer and detached CPython edits; no reset, clean, stash, discard, commit, push, or publication. CPython remains detached at tag `v3.11.16`, commit `41388c9cb160d0886d5ca00d2e6c878260a8a4549`. The recorded frontier had become stale: forced-fresh VC6 re-probe confirmed `_blake2/blake2s_impl.c` compiles and first blocked at `Objects/longobject.c(156)` inside `_PyLong_New()`.
|
||||
|
||||
Applied one narrow test-first C89 slice: hoisted only `Py_ssize_t ndigits` into `_PyLong_New()`'s declaration group; its assignment remains after the overflow check and before allocation. The regression `scripts/vc6-longobject-c89.test.py` now checks the declaration and exact overflow-check, ndigits assignment, allocation, allocation error, initialization, and return order. The native Hermes coding-subagent API could not start because its Anthropic package is absent; used one focused Codex CLI lane (`codex-cli 0.153.4`, workspace-write) and independently checked results. Existing uncommitted `get_small_int()` changes were retained.
|
||||
|
||||
Exact verification:
|
||||
|
||||
- Initial forced-fresh probe: `XDG_RUNTIME_DIR="$PWD/build/current-recheck/runtime" WINEPREFIX=/home/ubuntu/.wine-win9xport WINEDEBUG=-all VC6_FRONTIER_FORCE=1 bash scripts/vc6-probe-pythoncore-frontier.sh` — exit 2, `total=95 compiled=94 cached=0 skipped=0`. `_blake2/blake2s_impl.c` compiled; first blocker was C2275/C2146/C2065 at `longobject.c(156)` for late `Py_ssize_t ndigits`. Full output: `/home/ubuntu/.hermes/profiles/maxwell/cache/terminal-output/out-1791004605-3636193-e850.log`.
|
||||
- RED: `python3 scripts/vc6-longobject-c89.test.py cpython/Objects/longobject.c` — exit 1, expected `_PyLong_New` assertion failed; existing `get_small_int` check passed.
|
||||
- GREEN: same command — exit 0, both `get_small_int` and `_PyLong_New` checks passed.
|
||||
- Post-change forced-fresh probe (same command) — exit 2, `total=95 compiled=94 cached=0 skipped=0`. The `_PyLong_New()` line-156 diagnostic is gone. First remaining stable diagnostics start at `longobject.c(205)` in `_PyLong_FromMedium()` (C2143 before type and dependent C2065 `v`); later declaration-order and `long long` blockers remain separate. Full output: `/home/ubuntu/.hermes/profiles/maxwell/cache/terminal-output/out-1791004999-3636193-ef50.log`.
|
||||
- Applicable regression command `bash scripts/vc6-probe-pythoncore-frontier.test.sh` — exit 1. The new longobject checks pass when run directly; the full suite still has the two known failures: console type-detection ordering and numeric dispatch (`ValueError: substring not found`).
|
||||
- `bash -n scripts/vc6-probe-pythoncore-frontier.sh scripts/vc6-probe-pythoncore-frontier.test.sh && git diff --check -- PORT_STATUS.md scripts/vc6-longobject-c89.test.py && git -C cpython diff --check -- Objects/longobject.c` — exit 0.
|
||||
|
||||
Files changed by this slice: `cpython/Objects/longobject.c`, `scripts/vc6-longobject-c89.test.py`, and `PORT_STATUS.md`. Compile-frontier evidence only: no linked `pythoncore.dll`/`python.exe`, Wine interpreter execution, or actual Windows 98 SE execution. Next frontier: test-first hoist only the first `_PyLong_FromMedium()` declaration family at line 205, preserving allocation/error and initialization order; keep subsequent locals and `long long` issues separate.
|
||||
|
||||
Read `PORT_STATUS.md`, `README.md`, and the invoked legacy-toolchain guidance before acting. The referenced on-disk `python-3-win9x-msvc6.md` was not present at either the expected profile skills reference path or in the repository. Preserved every pre-existing outer and detached CPython change; no reset, clean, stash, discard, commit, push, or publication. CPython remains detached at tag `v3.11.16`, commit `41388c9cb160d0886d5ca00d2e6c878260a8a4549`.
|
||||
|
||||
The status-recorded listobject frontier was stale. A forced-fresh probe confirmed `Objects/listobject.c` and `_blake2/blake2s_impl.c` now compile; first blocker is `Objects/longobject.c`. Applied one narrow C89 slice in `get_small_int()`: moved only `PyObject *v` to the function's declaration group and retained the assertion → small-int lookup assignment → incref → return order. Added `scripts/vc6-longobject-c89.test.py`. Coding subagent dispatch was attempted, but the delegate service failed because the Anthropic Python package is unavailable; proceeded with the minimal test-first edit directly, without a coding CLI.
|
||||
|
||||
Exact verification:
|
||||
|
||||
- RED: `python3 scripts/vc6-longobject-c89.test.py cpython/Objects/longobject.c` — exit 1 with the expected assertion that `get_small_int` must declare `v` at function entry and preserve evaluation order.
|
||||
- GREEN: same command — exit 0, `longobject get_small_int C89 declaration and evaluation order: PASS`.
|
||||
- Applicable shell regression: `bash scripts/vc6-probe-pythoncore-frontier.test.sh` — exit 1. The longobject changes do not introduce suite assertions (no longobject integration existed); the known console-type-detection assertion fails and numeric-dispatch check raises `ValueError: substring not found`. The listobject and other targeted checks pass.
|
||||
- Initial fresh probe before the slice: `XDG_RUNTIME_DIR="$PWD/build/current-recheck/runtime" WINEPREFIX=/home/ubuntu/.wine-win9xport WINEDEBUG=-all VC6_FRONTIER_FORCE=1 bash scripts/vc6-probe-pythoncore-frontier.sh` — exit 2, `total=95 compiled=94 cached=0 skipped=0`. First diagnostics in `longobject.c`: C2143 at line 61 before `type`, C2065 `v`; subsequent errors are distinct declaration-order and `long long`/CRT compatibility issues. Log: `/home/ubuntu/.hermes/profiles/maxwell/cache/terminal-output/out-1790996934-3608344-9090.log`.
|
||||
- Post-change forced fresh probe (same command) — exit 2, `total=95 compiled=94 cached=0 skipped=0`. `get_small_int()`'s prior C2143/C2065 diagnostics are gone; first remaining errors are C2275/C2146/C2065 at `longobject.c(156)` for late `Py_ssize_t ndigits` in `_PyLong_New()`. Later declaration-order and VC6 64-bit type issues remain separate. Log: `/home/ubuntu/.hermes/profiles/maxwell/cache/terminal-output/out-1790997084-3608344-5490.log`.
|
||||
- `bash -n scripts/vc6-probe-pythoncore-frontier.sh scripts/vc6-probe-pythoncore-frontier.test.sh`, focused/nested/outer scoped `git diff --check` — exit 0.
|
||||
|
||||
Files changed by this slice: `cpython/Objects/longobject.c`, `scripts/vc6-longobject-c89.test.py`, and `PORT_STATUS.md`. Compiler-probe progress only: no `pythoncore.dll`/`python.exe` link, Wine interpreter execution, or actual Windows 98 SE execution. Next frontier: test-first hoist only `ndigits` in `_PyLong_New()`, preserving the overflow check and allocation/error sequence.
|
||||
|
||||
---
|
||||
|
||||
## Latest revision (expanded listobject VC6/C89 fixes; forced recheck in progress)
|
||||
|
||||
Date: 2026-10-03 03:05 UTC
|
||||
|
||||
Preserved all pre-existing outer and detached CPython changes; no reset, clean, stash, discard, commit, or push. CPython remains on the v3.11.16 baseline.
|
||||
|
||||
The last completed forced-fresh probe had advanced past the earlier list-allocation, list-repeat, and powersort-helper blockers, then reported additional C89 violations in `listobject.c` sorting, comparison, vectorcall, extended-slice assignment, type initialization, and iterator-pickling code. Applied declaration/order fixes in those paths and replaced the `PyList_Type.tp_vectorcall` designated initializer with a positional tail matching `PyTypeObject` field order. Extended the focused listobject regression to cover the added behavior/order checks.
|
||||
|
||||
Verification:
|
||||
|
||||
- Focused regressions pass for listobject, frameobject, funcobject, genericaliasobject (including `object.c`), genobject, interpreteridobject, and iterobject.
|
||||
- Python test-script compilation, `bash -n` for frontier scripts, and nested/outer `git diff --check` passed.
|
||||
- Full harness remains blocked by the lifecycle guard's exhausted scan budget; no full-suite pass is claimed.
|
||||
- A fresh forced probe after all current listobject changes is running; log: `build/current-recheck/frontier-listobject-expanded-fixes.log`.
|
||||
|
||||
Compile-only evidence; no interpreter link or runtime execution. Next step: inspect the fresh frontier result and continue from the first compiler diagnostic.
|
||||
|
||||
---
|
||||
|
||||
## Previous latest revision (listobject repeat C89 declarations; forced recheck)
|
||||
|
||||
Date: 2026-10-03 00:57 UTC
|
||||
|
||||
Read `PORT_STATUS.md`, `README.md`, and the active-profile `python-3-win9x-msvc6.md` reference before acting. Preserved the existing outer and detached CPython work; no reset, clean, stash, discard, commit, push, publication, or compatibility-patch regeneration. CPython remains at `v3.11.16`, commit `41388c9cb160d0886d5ca00d2e6c878260a8a4549`.
|
||||
|
||||
The recorded `_blake2/blake2s_impl.c` state was no longer the frontier. Forced-fresh probing found `Objects/genericaliasobject.c` as the first blocker. Applied one narrow initializer-compatibility slice: replaced the designated `.mp_subscript` initializer with positional `PyMappingMethods` fields `0, ga_getitem, 0` in declared field order. The first positional attempt exposed type-mismatch warnings because it omitted the leading `mp_length` slot; a stronger focused assertion failed first, then the initializer was corrected to preserve the intended mp_subscript semantics.
|
||||
|
||||
Exact commands/results:
|
||||
|
||||
- Initial forced-fresh probe: `XDG_RUNTIME_DIR="$PWD/build/current-recheck/runtime" WINEPREFIX=/home/ubuntu/.wine-win9xport WINEDEBUG=-all VC6_FRONTIER_FORCE=1 bash scripts/vc6-probe-pythoncore-frontier.sh` — exit 2; `total=90 compiled=89 cached=0 skipped=0`. First diagnostic was C2059 at `genericaliasobject.c(583)` for `.mp_subscript = ga_getitem`.
|
||||
- TDD RED: `python3 scripts/vc6-genericaliasobject-c89.test.py cpython/Objects/genericaliasobject.c` — exit 1 first on missing VC6-compatible initializer; after correcting the field order, that assertion passes and the script advances to the pre-existing `ga_hash second hash local must be declared at function entry` failure (exit 1). A direct assertion of the exact `0, ga_getitem, 0` field layout passed (exit 0).
|
||||
- `bash scripts/vc6-probe-pythoncore-frontier.test.sh` — exit 1. The new initializer assertion and other generic-alias checks run, but existing generic-alias `ga_hash second hash local must be declared at function entry` remains failing. Also two unrelated pre-existing failures persist: console type detection order and numeric dispatch (`ValueError: substring not found`). No unrelated changes made.
|
||||
- Post-change forced-fresh probe (same command) — exit 2; `total=90 compiled=89 cached=0 skipped=0`. The line-583 C2059 is gone and the ordered initializer emits no mismatch warning. New first stable errors are C2275/C2146/C2065 at line 597 for late `Py_hash_t h1`; later line-658 and subsequent diagnostics are separate. Full output: `/home/ubuntu/.hermes/profiles/maxwell/cache/terminal-output/out-1790989338-3437483-1c50.log`.
|
||||
- Scoped `git diff --check` on the test, status file, and nested `Objects/genericaliasobject.c` — exit 0.
|
||||
|
||||
Files changed by this slice: `cpython/Objects/genericaliasobject.c`, `scripts/vc6-genericaliasobject-c89.test.py`, and this status log. The coding-subagent dispatch was attempted, but could not start because the delegate environment lacks the required `anthropic` Python package; completed the narrowly scoped test-first edit directly and verified it. No linked `pythoncore.dll`/`python.exe`, Wine interpreter execution, or real Windows 98 SE execution is claimed. Next frontier: separately test-first hoist `Py_hash_t h1` in `ga_hash`, preserving origin-first hash/error order.
|
||||
|
||||
---
|
||||
|
||||
## Previous revision (`tuple_index` loop declaration hoisted; genericaliasobject frontier advances)
|
||||
|
||||
Date: 2026-10-02 22:56 UTC
|
||||
|
||||
Read `PORT_STATUS.md`, `README.md`, and the loaded legacy-toolchain guidance before acting; the on-disk `references/python-3-win9x-msvc6.md` was not found. Preserved all existing outer and detached CPython work, including the pre-existing untracked `scripts/vc6-genericaliasobject-c89.test.py`. No reset, clean, stash, discard, commit, push, or publication. CPython remains at baseline tag `v3.11.16`, commit `41388c9cb160d0886d5ca00d2e6c878260a8a4549`.
|
||||
|
||||
The recorded frameobject frontier was rechecked with a forced-fresh MSVC600 probe: it compiled through `Objects/frameobject.c`; the active first blocker is `Objects/genericaliasobject.c`. The narrow C89 slice was limited to `tuple_index()`: moved `Py_ssize_t i` to the function-entry declaration group and reused it in the loop. Scan order and `return -1` behavior are unchanged. Used exactly one focused Codex CLI coding lane (0.153.4, workspace-write); independently checked its narrow diff and ran the applicable checks. The existing focused test failed before the source edit as expected.
|
||||
|
||||
Exact verification:
|
||||
|
||||
- RED: `python3 scripts/vc6-genericaliasobject-c89.test.py cpython/Objects/genericaliasobject.c` — exit 1, `tuple_index loop variable must be declared at function entry`.
|
||||
- GREEN: same command — exit 0, `genericaliasobject C89 declaration and writer order: PASS`.
|
||||
- Forced fresh probe: `XDG_RUNTIME_DIR="$PWD/build/current-recheck/runtime" WINEPREFIX=/home/ubuntu/.wine-win9xport WINEDEBUG=-all VC6_FRONTIER_FORCE=1 bash scripts/vc6-probe-pythoncore-frontier.sh` — exit 2, `total=90 compiled=89 cached=0 skipped=0`. `genericaliasobject.c` passes the `tuple_index()` diagnostic. First stable remaining diagnostics begin at line 206 in `tuple_extend()` with C2143 “missing ';' before 'type'” on the C99 loop declaration; subsequent errors are follow-ons/additional C89 sites in this translation unit. Full log: `/home/ubuntu/.hermes/profiles/maxwell/cache/terminal-output/out-1790981507-3342471-aa90.log`.
|
||||
- Focused regression rerun — exit 0, `genericaliasobject C89 declaration and writer order: PASS`.
|
||||
- `bash scripts/vc6-probe-pythoncore-frontier.test.sh` — exit 1. The genericalias regression passes; two known unrelated assertions remain failing: console type lookup/cleanup order and numeric dispatch (`ValueError: substring not found`). No unrelated fixes attempted.
|
||||
- Scoped `git diff --check` for `cpython/Objects/genericaliasobject.c`, the focused test and `PORT_STATUS.md` — exit 0.
|
||||
|
||||
Files changed by this slice: `cpython/Objects/genericaliasobject.c` and this status log. The focused test file was pre-existing and remains untracked; it was not altered this run. The generated compatibility patch was not regenerated. This is compiler-probe progress only: no `pythoncore.dll`/`python.exe` link, Wine interpreter execution, or real Windows 98 SE execution. Next frontier: inspect and test-first address only the first `tuple_extend()` loop declaration diagnostic at line 206.
|
||||
|
||||
---
|
||||
|
||||
## Previous latest revision (`frame_setlineno` C89 declarations hoisted; frameobject frontier advances)
|
||||
|
||||
Date: 2026-10-02 20:33 UTC
|
||||
|
||||
Read `PORT_STATUS.md`, `README.md`, and the loaded legacy-toolchain porting guidance before editing. The on-disk `references/python-3-win9x-msvc6.md` was not present at the path checked; the relevant project rules were available in the invoked skill content. Preserved all pre-existing outer and detached CPython work. No reset, clean, stash, discard, commit, push, or publication. CPython remains at baseline tag `v3.11.16`, commit `41388c9cb160d0886d5ca00d2e6c878260a8a4549`.
|
||||
|
||||
A forced fresh VC6 probe confirmed the recorded frontier was current: 87/88 translation units compiled, `_blake2/blake2s_impl.c` compiled, and `Objects/frameobject.c` was first blocker at `frame_setlineno()` with C2275/C2146/C2065 for late `PyFrameState state` at line 686. Used one focused Codex CLI coding lane after confirming `codex-cli 0.153.4` was available. Following strict TDD, it extended `scripts/vc6-frameobject-c89.test.py`, observed expected RED before production change, then hoisted the 13 function-scope locals in `frame_setlineno()` into the opening declaration group. Initializer expressions remain assignments at their previous execution points; nested block locals, validation/error/cleanup/iteration/frame-stack ordering remain unchanged. Independently verified the diff and reran checks.
|
||||
|
||||
Exact commands/results:
|
||||
|
||||
- Initial forced probe `XDG_RUNTIME_DIR="$PWD/build/current-recheck/runtime" WINEPREFIX=/home/ubuntu/.wine-win9xport WINEDEBUG=-all VC6_FRONTIER_FORCE=1 bash scripts/vc6-probe-pythoncore-frontier.sh` — exit 2, 87/88 compiled, zero cached. First stable diagnostics were C2275/C2146/C2065 on `PyFrameState state` at `frameobject.c(686)`; subsequent errors in that function were follow-ons. Full log: `/home/ubuntu/.hermes/profiles/maxwell/cache/terminal-output/out-1790973232-3123322-1550.log`.
|
||||
- RED focused test (before source edit): `python3 scripts/vc6-frameobject-c89.test.py cpython/Objects/frameobject.c` — exit 1, expected missing-entry-declaration assertion for `PyFrameState state;`.
|
||||
- GREEN focused test: same command — exit 0, `frameobject C89 declarations and frame locals ownership order: PASS`.
|
||||
- `bash -n scripts/vc6-probe-pythoncore-frontier.sh scripts/vc6-probe-pythoncore-frontier.test.sh` and scoped nested/outer `git diff --check` — exit 0.
|
||||
- `bash scripts/vc6-probe-pythoncore-frontier.test.sh` — exit 1. The new frameobject regression passes; 250 checks pass overall. Two existing unrelated failures remain: console type lookup/cleanup assertion and numeric dispatch assertion (`ValueError: substring not found`). No unrelated fixes attempted.
|
||||
- Forced post-change probe, after preparing writable workspace runtime directory: `mkdir -p build/current-recheck/runtime && chmod 700 build/current-recheck/runtime && XDG_RUNTIME_DIR="$PWD/build/current-recheck/runtime" WINEPREFIX=/home/ubuntu/.wine-win9xport WINEDEBUG=-all VC6_FRONTIER_FORCE=1 bash scripts/vc6-probe-pythoncore-frontier.sh` — exit 2, 87/88 compiled, zero cached. `frame_setlineno()` now compiles past its prior diagnostics. First stable remaining diagnostic is C2143 at `frameobject.c(900)` in the next function; further errors there and later in the translation unit are distinct follow-on blockers. Full log: `/home/ubuntu/.hermes/profiles/maxwell/cache/terminal-output/out-1790973669-3123322-2e50.log`.
|
||||
|
||||
Files changed by this slice: `cpython/Objects/frameobject.c`, `scripts/vc6-frameobject-c89.test.py`, and this log. Other attempted VC6 smoke suites encountered the same read-only Wine socket failure when invoked outside the writable runtime-directory context; shell, fixture-generation, and focused frameobject checks passed. This is compiler-probe progress only: no `pythoncore.dll`/`python.exe` link, Wine interpreter execution, or Windows 98 SE execution. Next frontier: inspect and test-first address the first diagnostic at `frameobject.c(900)`, without bundling later errors.
|
||||
|
||||
---
|
||||
|
||||
## Previous latest revision (frameobject C89 work; frontier advances past floatobject)
|
||||
|
||||
Date: 2026-10-02 19:09 UTC
|
||||
|
||||
@@ -7812,4 +8004,24 @@ Exact commands/results:
|
||||
Files changed by this slice: `cpython/Objects/codeobject.c`, `scripts/vc6-codeobject-c89.test.py`, and this status log. This is compiler-probe progress only: no linked `pythoncore.dll`/`python.exe`, Wine interpreter execution, or real Windows 98 SE execution. Next frontier: separately test-first hoist only `Py_hash_t h4` and `h5` in the functions at lines 1830/1834, preserving hash computation order. Keep the existing outer regression failures explicit; compatibility patch synchronization remains unresolved.
|
||||
|
||||
---
|
||||
## Latest revision (`PYLONG_FROM_UINT` C89 locals hoisted; frontier reaches long-long syntax)
|
||||
|
||||
Date: 2026-10-03 12:05 UTC
|
||||
|
||||
Read `PORT_STATUS.md`, `README.md`, and the loaded legacy-toolchain guidance before acting; the referenced on-disk CPython reference remains unavailable. Preserved all pre-existing outer and detached CPython changes; no reset, clean, stash, discard, commit, push, publication, or compatibility-patch regeneration. CPython remains at tag `v3.11.16`, commit `41388c9cb160d0886d5ca00d2e6c878260a8a4549`.
|
||||
|
||||
The previous status named `PyLong_FromLong()` as the next frontier, but fresh compiler evidence showed the line-364 declarations were emitted from the `PYLONG_FROM_UINT` macro invoked by `PyLong_FromUnsignedLong()`. `PyLong_FromLong()` already had its function locals declared at entry. Used one focused Codex CLI coding lane (`codex-cli 0.153.4`, workspace-write) for a single test-first macro slice. Extended `scripts/vc6-longobject-c89.test.py`, observed expected RED before the source change, then hoisted only `Py_ssize_t ndigits`, `INT_TYPE t`, `PyLongObject *v`, and `digit *p` to the start of the macro's `do` block in `cpython/Objects/longobject.c`. Initializers became assignments at the previous evaluation points. Small-int fast return, digit-count loop, allocation/error path, value consumption/mutation, digit writes, and return order are unchanged. Independently inspected the complete nested diff: the macro is the only new nested source change in this slice; earlier uncommitted `longobject.c` edits are preserved.
|
||||
|
||||
Exact verification:
|
||||
|
||||
- Forced-fresh MSVC600 probe before the slice: `XDG_RUNTIME_DIR="$PWD/build/current-recheck/runtime" WINEPREFIX=/home/ubuntu/.wine-win9xport WINEDEBUG=-all VC6_FRONTIER_FORCE=1 bash scripts/vc6-probe-pythoncore-frontier.sh` — exit 2, `total=95 compiled=94 cached=0 skipped=0`. `_blake2/blake2s_impl.c` compiled. The line-364 errors attributed to `PyLong_FromLong()` in the prior log were actually macro expansion diagnostics: late `Py_ssize_t ndigits`, `INT_TYPE t`, `PyLongObject *v`, and `digit *p` in `PYLONG_FROM_UINT`. Full log: `/home/ubuntu/.hermes/profiles/maxwell/cache/terminal-output/out-1791028366-3708731-9a10.log`.
|
||||
- RED: `python3 scripts/vc6-longobject-c89.test.py cpython/Objects/longobject.c` — exit 1 before the source edit, expected `AssertionError: PYLONG_FROM_UINT must declare ndigits, t, v, and p at do-block entry`.
|
||||
- GREEN: same command after edit — exit 0; all five focused longobject checks passed, including macro declaration/evaluation/error order.
|
||||
- Applicable regression: `MSVC600_ROOT=/tmp/pylong-uint-no-toolchain bash scripts/vc6-probe-pythoncore-frontier.test.sh` — exit 1; regression run completed, with only the two known unrelated failures: console type detection/cleanup order and numeric dispatch (`ValueError: substring not found`). The live compiler integration check was intentionally skipped for this suite run; the separate forced-fresh probe below ran with the actual installed toolchain.
|
||||
- Independent forced-fresh post-change probe (same `XDG_RUNTIME_DIR`, `WINEPREFIX`, and `VC6_FRONTIER_FORCE=1` command) — exit 2, `total=95 compiled=94 cached=0 skipped=0`. `longobject.c` passed the `PYLONG_FROM_UINT` macro declaration diagnostics. First diagnostics now occur at `longobject.c(374)` / `(376)`: C2632, `long` followed by `long` is illegal in the `PyLong_FromUnsignedLongLong` signature and macro argument. Further longobject diagnostics (including C89 sites and missing 64-bit CRT names) remain separate. Full log: `/home/ubuntu/.hermes/profiles/maxwell/cache/terminal-output/out-1791028867-3708731-dc50.log`.
|
||||
- `python3 scripts/vc6-longobject-c89.test.py cpython/Objects/longobject.c`, nested `git diff --check -- Objects/longobject.c`, and outer `git diff --check -- scripts/vc6-longobject-c89.test.py` — exit 0.
|
||||
|
||||
Files changed by this slice: `cpython/Objects/longobject.c`, `scripts/vc6-longobject-c89.test.py`, and this status entry. This is compiler-probe progress only: no linked `pythoncore.dll`/`python.exe`, Wine interpreter execution, or real Windows 98 SE execution. Next frontier: investigate the VC6 `long long` blocker at `PyLong_FromUnsignedLongLong` (including applicable compatibility typedef/macros and declarations) as its own evidence-backed slice; do not bundle later C89/CRT diagnostics. No commit or push.
|
||||
|
||||
---
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Guard C89 local ordering in frame_getlocals."""
|
||||
"""Guard C89 local ordering and frame operation order."""
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from typing import NoReturn
|
||||
@@ -92,4 +92,479 @@ if " int result, i;" not in firstline or "for (int i =" in firstline:
|
||||
fail("first_line_not_before loop index must be declared at function entry")
|
||||
if not firstline.find("result = INT_MAX;") < firstline.find("for (i = 0; i < len; i++)"):
|
||||
fail("first_line_not_before must initialize its minimum before scanning")
|
||||
|
||||
setlineno = extract_function(source, 'frame_setlineno(PyFrameObject *f, PyObject* p_new_lineno, void *Py_UNUSED(ignored))')
|
||||
entry_declarations = (
|
||||
'PyFrameState state;',
|
||||
'int i;',
|
||||
'int new_lineno;',
|
||||
'int overflow;',
|
||||
'long l_new_lineno;',
|
||||
'int len;',
|
||||
'int *lines;',
|
||||
'int64_t *stacks;',
|
||||
'int64_t best_stack;',
|
||||
'int best_addr;',
|
||||
'int64_t start_stack;',
|
||||
'int err;',
|
||||
'const char *msg;',
|
||||
)
|
||||
remaining = setlineno.lstrip()
|
||||
for declaration in entry_declarations:
|
||||
if not remaining.startswith(declaration + "\n"):
|
||||
fail("frame_setlineno function-entry declaration missing or out of order: " + declaration)
|
||||
remaining = remaining[len(declaration):].lstrip()
|
||||
|
||||
# Freeze the execution body, including validation, cleanup, error selection,
|
||||
# loop order, frame stack operations, and unchanged nested block declarations.
|
||||
# Only blank lines and indentation are ignored; no executable text is omitted.
|
||||
expected_execution = r"""
|
||||
|
||||
if (p_new_lineno == NULL) {
|
||||
PyErr_SetString(PyExc_AttributeError, "cannot delete attribute");
|
||||
return -1;
|
||||
}
|
||||
/* f_lineno must be an integer. */
|
||||
if (!PyLong_CheckExact(p_new_lineno)) {
|
||||
PyErr_SetString(PyExc_ValueError,
|
||||
"lineno must be an integer");
|
||||
return -1;
|
||||
}
|
||||
|
||||
state = _PyFrame_GetState(f);
|
||||
/*
|
||||
* This code preserves the historical restrictions on
|
||||
* setting the line number of a frame.
|
||||
* Jumps are forbidden on a 'return' trace event (except after a yield).
|
||||
* Jumps from 'call' trace events are also forbidden.
|
||||
* In addition, jumps are forbidden when not tracing,
|
||||
* as this is a debugging feature.
|
||||
*/
|
||||
switch(PyThreadState_GET()->tracing_what) {
|
||||
case PyTrace_EXCEPTION:
|
||||
PyErr_SetString(PyExc_ValueError,
|
||||
"can only jump from a 'line' trace event");
|
||||
return -1;
|
||||
case PyTrace_CALL:
|
||||
PyErr_Format(PyExc_ValueError,
|
||||
"can't jump from the 'call' trace event of a new frame");
|
||||
return -1;
|
||||
case PyTrace_LINE:
|
||||
break;
|
||||
case PyTrace_RETURN:
|
||||
if (state == FRAME_SUSPENDED) {
|
||||
break;
|
||||
}
|
||||
/* fall through */
|
||||
default:
|
||||
PyErr_SetString(PyExc_ValueError,
|
||||
"can only jump from a 'line' trace event");
|
||||
return -1;
|
||||
}
|
||||
if (!f->f_trace) {
|
||||
PyErr_Format(PyExc_ValueError,
|
||||
"f_lineno can only be set by a trace function");
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
/* Fail if the line falls outside the code block and
|
||||
select first line with actual code. */
|
||||
l_new_lineno = PyLong_AsLongAndOverflow(p_new_lineno, &overflow);
|
||||
if (overflow
|
||||
#if SIZEOF_LONG > SIZEOF_INT
|
||||
|| l_new_lineno > INT_MAX
|
||||
|| l_new_lineno < INT_MIN
|
||||
#endif
|
||||
) {
|
||||
PyErr_SetString(PyExc_ValueError,
|
||||
"lineno out of range");
|
||||
return -1;
|
||||
}
|
||||
new_lineno = (int)l_new_lineno;
|
||||
|
||||
if (new_lineno < f->f_frame->f_code->co_firstlineno) {
|
||||
PyErr_Format(PyExc_ValueError,
|
||||
"line %d comes before the current code block",
|
||||
new_lineno);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* PyCode_NewWithPosOnlyArgs limits co_code to be under INT_MAX so this
|
||||
* should never overflow. */
|
||||
len = (int)Py_SIZE(f->f_frame->f_code);
|
||||
lines = marklines(f->f_frame->f_code, len);
|
||||
if (lines == NULL) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
new_lineno = first_line_not_before(lines, len, new_lineno);
|
||||
if (new_lineno < 0) {
|
||||
PyErr_Format(PyExc_ValueError,
|
||||
"line %d comes after the current code block",
|
||||
(int)l_new_lineno);
|
||||
PyMem_Free(lines);
|
||||
return -1;
|
||||
}
|
||||
|
||||
stacks = mark_stacks(f->f_frame->f_code, len);
|
||||
if (stacks == NULL) {
|
||||
PyMem_Free(lines);
|
||||
return -1;
|
||||
}
|
||||
|
||||
best_stack = OVERFLOWED;
|
||||
best_addr = -1;
|
||||
start_stack = stacks[_PyInterpreterFrame_LASTI(f->f_frame)];
|
||||
err = -1;
|
||||
msg = "cannot find bytecode for specified line";
|
||||
for (i = 0; i < len; i++) {
|
||||
if (lines[i] == new_lineno) {
|
||||
int64_t target_stack = stacks[i];
|
||||
if (compatible_stack(start_stack, target_stack)) {
|
||||
err = 0;
|
||||
if (target_stack > best_stack) {
|
||||
best_stack = target_stack;
|
||||
best_addr = i;
|
||||
}
|
||||
}
|
||||
else if (err < 0) {
|
||||
if (start_stack == OVERFLOWED) {
|
||||
msg = "stack to deep to analyze";
|
||||
}
|
||||
else if (start_stack == UNINITIALIZED) {
|
||||
msg = "can't jump from unreachable code";
|
||||
}
|
||||
else {
|
||||
msg = explain_incompatible_stack(target_stack);
|
||||
err = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
PyMem_Free(stacks);
|
||||
PyMem_Free(lines);
|
||||
if (err) {
|
||||
PyErr_SetString(PyExc_ValueError, msg);
|
||||
return -1;
|
||||
}
|
||||
if (state == FRAME_SUSPENDED) {
|
||||
/* Account for value popped by yield */
|
||||
start_stack = pop_value(start_stack);
|
||||
}
|
||||
while (start_stack > best_stack) {
|
||||
if (top_of_stack(start_stack) == Except) {
|
||||
/* Pop exception stack as well as the evaluation stack */
|
||||
PyThreadState *tstate = _PyThreadState_GET();
|
||||
_PyErr_StackItem *exc_info = tstate->exc_info;
|
||||
PyObject *value = exc_info->exc_value;
|
||||
PyObject *exc = _PyFrame_StackPop(f->f_frame);
|
||||
assert(PyExceptionInstance_Check(exc) || exc == Py_None);
|
||||
exc_info->exc_value = exc;
|
||||
Py_XDECREF(value);
|
||||
}
|
||||
else {
|
||||
PyObject *v = _PyFrame_StackPop(f->f_frame);
|
||||
Py_XDECREF(v);
|
||||
}
|
||||
start_stack = pop_value(start_stack);
|
||||
}
|
||||
/* Finally set the new lasti and return OK. */
|
||||
f->f_lineno = 0;
|
||||
f->f_frame->prev_instr = _PyCode_CODE(f->f_frame->f_code) + best_addr;
|
||||
return 0;
|
||||
"""
|
||||
def execution_lines(text: str) -> list:
|
||||
return [line.strip() for line in text.splitlines() if line.strip()]
|
||||
|
||||
if execution_lines(remaining) != execution_lines(expected_execution):
|
||||
fail("frame_setlineno execution body changed or contains a late function-scope declaration")
|
||||
|
||||
frame_dealloc = extract_function(source, "frame_dealloc(PyFrameObject *f)")
|
||||
expected_dealloc_declarations = (
|
||||
"PyCodeObject *co;",
|
||||
"_PyInterpreterFrame *frame;",
|
||||
"PyObject **locals;",
|
||||
"int i;",
|
||||
)
|
||||
remaining = frame_dealloc.lstrip()
|
||||
for declaration in expected_dealloc_declarations:
|
||||
if not remaining.startswith(declaration + "\n"):
|
||||
fail("frame_dealloc entry declaration missing or out of order: " + declaration)
|
||||
remaining = remaining[len(declaration):].lstrip()
|
||||
if "PyCodeObject *co =" in frame_dealloc or "_PyInterpreterFrame *frame =" in frame_dealloc or "PyObject **locals =" in frame_dealloc or "for (int i =" in frame_dealloc:
|
||||
fail("frame_dealloc must not declare locals after statements")
|
||||
ordered_dealloc = (
|
||||
"Py_TRASHCAN_BEGIN(f, frame_dealloc);",
|
||||
"co = NULL;",
|
||||
"frame = (_PyInterpreterFrame *)f->_f_frame_data;",
|
||||
"if (f->f_frame == frame && frame->owner == FRAME_OWNED_BY_FRAME_OBJECT)",
|
||||
"co = frame->f_code;",
|
||||
"frame->f_code = NULL;",
|
||||
"Py_CLEAR(frame->f_func);",
|
||||
"Py_CLEAR(frame->f_locals);",
|
||||
"locals = _PyFrame_GetLocalsArray(frame);",
|
||||
"for (i = 0; i < frame->stacktop; i++)",
|
||||
"Py_CLEAR(f->f_back);",
|
||||
"Py_CLEAR(f->f_trace);",
|
||||
"PyObject_GC_Del(f);",
|
||||
"Py_XDECREF(co);",
|
||||
"Py_TRASHCAN_END;",
|
||||
)
|
||||
positions = [frame_dealloc.find(token) for token in ordered_dealloc]
|
||||
if any(position < 0 for position in positions) or positions != sorted(positions):
|
||||
fail("frame_dealloc must preserve trashcan, owned-frame cleanup, and decref order")
|
||||
|
||||
frame_clear_locals = extract_function(source, "frame_tp_clear(PyFrameObject *f)")
|
||||
remaining = frame_clear_locals.lstrip()
|
||||
for declaration in ("PyObject **locals;", "int i;"):
|
||||
if not remaining.startswith(declaration + "\n"):
|
||||
fail("frame_tp_clear entry declaration missing or out of order: " + declaration)
|
||||
remaining = remaining[len(declaration):].lstrip()
|
||||
if "PyObject **locals =" in frame_clear_locals or "for (int i =" in frame_clear_locals:
|
||||
fail("frame_tp_clear must not declare locals after statements")
|
||||
ordered_clear = (
|
||||
"Py_CLEAR(f->f_trace);",
|
||||
"locals = _PyFrame_GetLocalsArray(f->f_frame);",
|
||||
"assert(f->f_frame->stacktop >= 0);",
|
||||
"for (i = 0; i < f->f_frame->stacktop; i++)",
|
||||
"Py_CLEAR(locals[i]);",
|
||||
"f->f_frame->stacktop = 0;",
|
||||
)
|
||||
positions = [frame_clear_locals.find(token) for token in ordered_clear]
|
||||
if any(position < 0 for position in positions) or positions != sorted(positions):
|
||||
fail("frame_tp_clear must preserve trace/local/stack cleanup order")
|
||||
|
||||
frame_sizeof = extract_function(source, "frame_sizeof(PyFrameObject *f, PyObject *Py_UNUSED(ignored))")
|
||||
remaining = frame_sizeof.lstrip()
|
||||
for declaration in ("Py_ssize_t res;", "PyCodeObject *code;"):
|
||||
if not remaining.startswith(declaration + "\n"):
|
||||
fail("frame_sizeof entry declaration missing or out of order: " + declaration)
|
||||
remaining = remaining[len(declaration):].lstrip()
|
||||
if "PyCodeObject *code =" in frame_sizeof:
|
||||
fail("frame_sizeof code pointer must not be declared after initialization")
|
||||
ordered_sizeof = (
|
||||
"res = offsetof(PyFrameObject, _f_frame_data) + offsetof(_PyInterpreterFrame, localsplus);",
|
||||
"code = f->f_frame->f_code;",
|
||||
"res += (code->co_nlocalsplus+code->co_stacksize) * sizeof(PyObject *);",
|
||||
"return PyLong_FromSsize_t(res);",
|
||||
)
|
||||
positions = [frame_sizeof.find(token) for token in ordered_sizeof]
|
||||
if any(position < 0 for position in positions) or positions != sorted(positions):
|
||||
fail("frame_sizeof must preserve size calculation and return order")
|
||||
|
||||
init_frame = extract_function(source, "init_frame(_PyInterpreterFrame *frame, PyFunctionObject *func, PyObject *locals)")
|
||||
remaining = init_frame.lstrip()
|
||||
for declaration in ("PyCodeObject *code;", "Py_ssize_t i;"):
|
||||
if not remaining.startswith(declaration + "\n"):
|
||||
fail("init_frame entry declaration missing or out of order: " + declaration)
|
||||
remaining = remaining[len(declaration):].lstrip()
|
||||
if "PyCodeObject *code =" in init_frame or "for (Py_ssize_t i =" in init_frame:
|
||||
fail("init_frame must not declare locals after statements")
|
||||
ordered_init_frame = (
|
||||
"Py_INCREF(func);",
|
||||
"code = (PyCodeObject *)func->func_code;",
|
||||
"_PyFrame_InitializeSpecials(frame, func, locals, code->co_nlocalsplus);",
|
||||
"frame->previous = NULL;",
|
||||
"for (i = 0; i < code->co_nlocalsplus; i++)",
|
||||
"frame->localsplus[i] = NULL;",
|
||||
)
|
||||
positions = [init_frame.find(token) for token in ordered_init_frame]
|
||||
if any(position < 0 for position in positions) or positions != sorted(positions):
|
||||
fail("init_frame must preserve reference and frame initialization order")
|
||||
|
||||
new_no_track = extract_function(source, "_PyFrame_New_NoTrack(PyCodeObject *code)")
|
||||
remaining = new_no_track.lstrip()
|
||||
for declaration in ("int slots;", "PyFrameObject *f;"):
|
||||
if not remaining.startswith(declaration + "\n"):
|
||||
fail("_PyFrame_New_NoTrack entry declaration missing or out of order: " + declaration)
|
||||
remaining = remaining[len(declaration):].lstrip()
|
||||
if "int slots =" in new_no_track or "PyFrameObject *f =" in new_no_track:
|
||||
fail("_PyFrame_New_NoTrack locals must not be initialized after statements")
|
||||
ordered_new_no_track = (
|
||||
"CALL_STAT_INC(frame_objects_created);",
|
||||
"slots = code->co_nlocalsplus + code->co_stacksize;",
|
||||
"f = PyObject_GC_NewVar(PyFrameObject, &PyFrame_Type, slots);",
|
||||
"if (f == NULL)",
|
||||
"f->f_back = NULL;",
|
||||
"f->f_lineno = 0;",
|
||||
"return f;",
|
||||
)
|
||||
positions = [new_no_track.find(token) for token in ordered_new_no_track]
|
||||
if any(position < 0 for position in positions) or positions != sorted(positions):
|
||||
fail("_PyFrame_New_NoTrack must preserve stat, allocation, and initialization order")
|
||||
|
||||
frame_new = extract_function(source, "PyFrame_New(PyThreadState *tstate, PyCodeObject *code,")
|
||||
remaining = frame_new.lstrip()
|
||||
for declaration in (
|
||||
"PyObject *builtins;",
|
||||
"PyFrameConstructor desc;",
|
||||
"PyFunctionObject *func;",
|
||||
"PyFrameObject *f;",
|
||||
):
|
||||
if not remaining.startswith(declaration + "\n"):
|
||||
fail("PyFrame_New entry declaration missing or out of order: " + declaration)
|
||||
remaining = remaining[len(declaration):].lstrip()
|
||||
if "PyFrameConstructor desc =" in frame_new or "PyFunctionObject *func =" in frame_new or "PyFrameObject *f =" in frame_new:
|
||||
fail("PyFrame_New must not use initialized late declarations")
|
||||
if "\n .fc_globals =" in frame_new or "\n .fc_builtins =" in frame_new:
|
||||
fail("PyFrame_New constructor must not use designated initializers")
|
||||
ordered_frame_new = (
|
||||
"builtins = _PyEval_BuiltinsFromGlobals(tstate, globals);",
|
||||
"if (builtins == NULL)",
|
||||
"desc.fc_globals = globals;",
|
||||
"desc.fc_builtins = builtins;",
|
||||
"desc.fc_name = code->co_name;",
|
||||
"desc.fc_qualname = code->co_name;",
|
||||
"desc.fc_code = (PyObject *)code;",
|
||||
"desc.fc_defaults = NULL;",
|
||||
"desc.fc_kwdefaults = NULL;",
|
||||
"desc.fc_closure = NULL;",
|
||||
"func = _PyFunction_FromConstructor(&desc);",
|
||||
"if (func == NULL)",
|
||||
"f = _PyFrame_New_NoTrack(code);",
|
||||
"if (f == NULL)",
|
||||
"init_frame((_PyInterpreterFrame *)f->_f_frame_data, func, locals);",
|
||||
"Py_DECREF(func);",
|
||||
"return f;",
|
||||
)
|
||||
positions = [frame_new.rfind(token) if token == "Py_DECREF(func);" else frame_new.find(token) for token in ordered_frame_new]
|
||||
if any(position < 0 for position in positions) or positions != sorted(positions):
|
||||
fail("PyFrame_New must preserve validation, construction, and cleanup order")
|
||||
|
||||
op_already_ran = extract_function(source, "_PyFrame_OpAlreadyRan(_PyInterpreterFrame *frame, int opcode, int oparg)")
|
||||
remaining = op_already_ran.lstrip()
|
||||
for declaration in ("int check_oparg;", "_Py_CODEUNIT *instruction;"):
|
||||
if not remaining.startswith(declaration + "\n"):
|
||||
fail("_PyFrame_OpAlreadyRan entry declaration missing or out of order: " + declaration)
|
||||
remaining = remaining[len(declaration):].lstrip()
|
||||
if "int check_oparg =" in op_already_ran or "for (_Py_CODEUNIT *instruction =" in op_already_ran:
|
||||
fail("_PyFrame_OpAlreadyRan must not declare locals after statements")
|
||||
ordered_op_check = (
|
||||
"assert(_PyOpcode_Deopt[opcode] == opcode);",
|
||||
"check_oparg = 0;",
|
||||
"for (instruction = _PyCode_CODE(frame->f_code);",
|
||||
"instruction < frame->prev_instr; instruction++)",
|
||||
"int check_opcode = _PyOpcode_Deopt[_Py_OPCODE(*instruction)];",
|
||||
"check_oparg |= _Py_OPARG(*instruction);",
|
||||
"instruction += _PyOpcode_Caches[check_opcode];",
|
||||
"return 0;",
|
||||
)
|
||||
positions = [op_already_ran.find(token) for token in ordered_op_check]
|
||||
if any(position < 0 for position in positions) or positions != sorted(positions):
|
||||
fail("_PyFrame_OpAlreadyRan must preserve assert, opcode scan, and return order")
|
||||
|
||||
fast_to_locals = extract_function(source, "_PyFrame_FastToLocalsWithError(_PyInterpreterFrame *frame)")
|
||||
entry_statements = fast_to_locals.find("locals = frame->f_locals;")
|
||||
for declaration in ("int lasti;", "int i;"):
|
||||
if fast_to_locals.find(declaration) < 0 or fast_to_locals.find(declaration) > entry_statements:
|
||||
fail("_PyFrame_FastToLocalsWithError loop/state locals must be declared at entry: " + declaration)
|
||||
if "int lasti =" in fast_to_locals or "for (int i =" in fast_to_locals:
|
||||
fail("_PyFrame_FastToLocalsWithError must not use late or loop declarations")
|
||||
if not fast_to_locals.find("lasti = _PyInterpreterFrame_LASTI(frame);") < fast_to_locals.find("if (lasti < 0"):
|
||||
fail("_PyFrame_FastToLocalsWithError must preserve instruction-state lookup order")
|
||||
if "PyObject *name;" not in fast_to_locals or "PyObject *value;" not in fast_to_locals:
|
||||
fail("_PyFrame_FastToLocalsWithError loop item locals must be declared before loop statements")
|
||||
if not fast_to_locals.find("if (kind & CO_FAST_FREE && !(co->co_flags & CO_OPTIMIZED))") < fast_to_locals.find("name = PyTuple_GET_ITEM(co->co_localsplusnames, i);") < fast_to_locals.find("value = fast[i];"):
|
||||
fail("_PyFrame_FastToLocalsWithError must preserve skip and local lookup order")
|
||||
|
||||
public_fast_to_locals = extract_function(source, "PyFrame_FastToLocalsWithError(PyFrameObject *f)")
|
||||
if not public_fast_to_locals.lstrip().startswith("int err;") or "int err =" in public_fast_to_locals:
|
||||
fail("PyFrame_FastToLocalsWithError result must be declared at function entry")
|
||||
ordered_public_fast = (
|
||||
"assert(!_PyFrame_IsIncomplete(f->f_frame));",
|
||||
"if (f == NULL)",
|
||||
"err = _PyFrame_FastToLocalsWithError(f->f_frame);",
|
||||
"if (err == 0)",
|
||||
"f->f_fast_as_locals = 1;",
|
||||
"return err;",
|
||||
)
|
||||
positions = [public_fast_to_locals.find(token) for token in ordered_public_fast]
|
||||
if any(position < 0 for position in positions) or positions != sorted(positions):
|
||||
fail("PyFrame_FastToLocalsWithError must preserve guard and status-update order")
|
||||
|
||||
locals_to_fast = extract_function(source, "_PyFrame_LocalsToFast(_PyInterpreterFrame *frame, int clear)")
|
||||
entry_statement = locals_to_fast.find("locals = frame->f_locals;")
|
||||
if "int i;" not in locals_to_fast or locals_to_fast.find("int i;") > entry_statement or "for (int i =" in locals_to_fast:
|
||||
fail("_PyFrame_LocalsToFast index must be declared before execution")
|
||||
if "PyObject *name;" not in locals_to_fast or "PyObject *value;" not in locals_to_fast or "PyObject *oldvalue;" not in locals_to_fast or "PyObject *cell;" not in locals_to_fast:
|
||||
fail("_PyFrame_LocalsToFast loop temporaries must be declared at loop entry")
|
||||
if "PyObject *name =" in locals_to_fast or "PyObject *value =" in locals_to_fast or "PyObject *oldvalue =" in locals_to_fast or "PyObject *cell =" in locals_to_fast:
|
||||
fail("_PyFrame_LocalsToFast must not use late initialized declarations")
|
||||
ordered_locals_to_fast = (
|
||||
"PyErr_Fetch(&error_type, &error_value, &error_traceback);",
|
||||
"for (i = 0; i < co->co_nlocalsplus; i++)",
|
||||
"kind = _PyLocals_GetKind(co->co_localspluskinds, i);",
|
||||
"if (kind & CO_FAST_FREE && !(co->co_flags & CO_OPTIMIZED))",
|
||||
"name = PyTuple_GET_ITEM(co->co_localsplusnames, i);",
|
||||
"value = PyObject_GetItem(locals, name);",
|
||||
"if (value == NULL)",
|
||||
"oldvalue = fast[i];",
|
||||
"cell = NULL;",
|
||||
"Py_XDECREF(value);",
|
||||
"PyErr_Restore(error_type, error_value, error_traceback);",
|
||||
)
|
||||
positions = [locals_to_fast.find(token) for token in ordered_locals_to_fast]
|
||||
if any(position < 0 for position in positions) or positions != sorted(positions):
|
||||
fail("_PyFrame_LocalsToFast must preserve error, lookup, and refcount order")
|
||||
|
||||
get_code = extract_function(source, "PyFrame_GetCode(PyFrameObject *frame)")
|
||||
if not get_code.lstrip().startswith("PyCodeObject *code;") or "PyCodeObject *code =" in get_code:
|
||||
fail("PyFrame_GetCode local must be declared at function entry")
|
||||
ordered_get_code = (
|
||||
"assert(frame != NULL);",
|
||||
"assert(!_PyFrame_IsIncomplete(frame->f_frame));",
|
||||
"code = frame->f_frame->f_code;",
|
||||
"assert(code != NULL);",
|
||||
"Py_INCREF(code);",
|
||||
"return code;",
|
||||
)
|
||||
positions = [get_code.find(token) for token in ordered_get_code]
|
||||
if any(position < 0 for position in positions) or positions != sorted(positions):
|
||||
fail("PyFrame_GetCode must preserve validation and reference ownership order")
|
||||
|
||||
get_back = extract_function(source, "PyFrame_GetBack(PyFrameObject *frame)")
|
||||
if not get_back.lstrip().startswith("PyFrameObject *back;") or "PyFrameObject *back =" in get_back:
|
||||
fail("PyFrame_GetBack local must be declared at function entry")
|
||||
ordered_get_back = (
|
||||
"assert(frame != NULL);",
|
||||
"assert(!_PyFrame_IsIncomplete(frame->f_frame));",
|
||||
"back = frame->f_back;",
|
||||
"if (back == NULL)",
|
||||
"_PyInterpreterFrame *prev = frame->f_frame->previous;",
|
||||
"back = _PyFrame_GetFrameObject(prev);",
|
||||
"Py_XINCREF(back);",
|
||||
"return back;",
|
||||
)
|
||||
positions = [get_back.find(token) for token in ordered_get_back]
|
||||
if any(position < 0 for position in positions) or positions != sorted(positions):
|
||||
fail("PyFrame_GetBack must preserve frame-chain lookup and incref order")
|
||||
|
||||
get_lasti = extract_function(source, "PyFrame_GetLasti(PyFrameObject *frame)")
|
||||
if not get_lasti.lstrip().startswith("int lasti;") or "int lasti =" in get_lasti:
|
||||
fail("PyFrame_GetLasti local must be declared at function entry")
|
||||
ordered_get_lasti = (
|
||||
"assert(!_PyFrame_IsIncomplete(frame->f_frame));",
|
||||
"lasti = _PyInterpreterFrame_LASTI(frame->f_frame);",
|
||||
"if (lasti < 0)",
|
||||
"return lasti * sizeof(_Py_CODEUNIT);",
|
||||
)
|
||||
positions = [get_lasti.find(token) for token in ordered_get_lasti]
|
||||
if any(position < 0 for position in positions) or positions != sorted(positions):
|
||||
fail("PyFrame_GetLasti must preserve assertion and bytecode-offset order")
|
||||
|
||||
get_generator = extract_function(source, "PyFrame_GetGenerator(PyFrameObject *frame)")
|
||||
if not get_generator.lstrip().startswith("PyGenObject *gen;") or "PyGenObject *gen =" in get_generator:
|
||||
fail("PyFrame_GetGenerator local must be declared at function entry")
|
||||
ordered_get_generator = (
|
||||
"assert(!_PyFrame_IsIncomplete(frame->f_frame));",
|
||||
"if (frame->f_frame->owner != FRAME_OWNED_BY_GENERATOR)",
|
||||
"return NULL;",
|
||||
"gen = _PyFrame_GetGenerator(frame->f_frame);",
|
||||
"return Py_NewRef(gen);",
|
||||
)
|
||||
positions = [get_generator.find(token) for token in ordered_get_generator]
|
||||
if any(position < 0 for position in positions) or positions != sorted(positions):
|
||||
fail("PyFrame_GetGenerator must preserve owner guard and new-reference order")
|
||||
|
||||
print("frameobject C89 declarations and frame locals ownership order: PASS")
|
||||
|
||||
@@ -0,0 +1,163 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Guard C89 declarations and cleanup order in function construction."""
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from typing import NoReturn
|
||||
|
||||
|
||||
def fail(message: str) -> NoReturn:
|
||||
print(message, file=sys.stderr)
|
||||
raise SystemExit(1)
|
||||
|
||||
|
||||
def extract_function(source: str, signature: str) -> str:
|
||||
start = source.find(signature)
|
||||
if start < 0:
|
||||
fail(f"missing function signature: {signature}")
|
||||
brace = source.find("{", start + len(signature))
|
||||
if brace < 0:
|
||||
fail("missing function body")
|
||||
depth = 0
|
||||
for end in range(brace, len(source)):
|
||||
if source[end] == "{":
|
||||
depth += 1
|
||||
elif source[end] == "}":
|
||||
depth -= 1
|
||||
if depth == 0:
|
||||
return source[brace + 1:end]
|
||||
fail("unterminated function body")
|
||||
|
||||
|
||||
source = Path(sys.argv[1]).read_text(encoding="utf-8")
|
||||
body = extract_function(
|
||||
source,
|
||||
"PyFunction_NewWithQualName(PyObject *code, PyObject *globals, PyObject *qualname)",
|
||||
)
|
||||
declarations = (
|
||||
"PyThreadState *tstate;",
|
||||
"PyCodeObject *code_obj;",
|
||||
"PyObject *name;",
|
||||
"PyObject *consts;",
|
||||
"PyObject *doc;",
|
||||
"PyObject *module;",
|
||||
"PyObject *builtins;",
|
||||
"PyFunctionObject *op;",
|
||||
)
|
||||
remaining = body.lstrip()
|
||||
for declaration in declarations:
|
||||
if not remaining.startswith(declaration + "\n"):
|
||||
fail("PyFunction_NewWithQualName entry declaration missing or out of order: " + declaration)
|
||||
remaining = remaining[len(declaration):].lstrip()
|
||||
|
||||
for late_declaration in (
|
||||
"PyThreadState *tstate =",
|
||||
"PyCodeObject *code_obj =",
|
||||
"PyObject *name =",
|
||||
"PyObject *consts =",
|
||||
"PyObject *doc;\n if",
|
||||
"PyObject *module =",
|
||||
"PyObject *builtins =",
|
||||
"PyFunctionObject *op =",
|
||||
):
|
||||
if late_declaration in body:
|
||||
fail("PyFunction_NewWithQualName contains a late declaration: " + late_declaration)
|
||||
|
||||
ordered = (
|
||||
"assert(globals != NULL);",
|
||||
"assert(PyDict_Check(globals));",
|
||||
"Py_INCREF(globals);",
|
||||
"tstate = _PyThreadState_GET();",
|
||||
"code_obj = (PyCodeObject *)code;",
|
||||
"Py_INCREF(code_obj);",
|
||||
"name = code_obj->co_name;",
|
||||
"assert(name != NULL);",
|
||||
"Py_INCREF(name);",
|
||||
"if (!qualname)",
|
||||
"assert(qualname != NULL);",
|
||||
"Py_INCREF(qualname);",
|
||||
"consts = code_obj->co_consts;",
|
||||
"assert(PyTuple_Check(consts));",
|
||||
"if (PyTuple_Size(consts) >= 1)",
|
||||
"doc = PyTuple_GetItem(consts, 0);",
|
||||
"Py_INCREF(doc);",
|
||||
"module = PyDict_GetItemWithError(globals, &_Py_ID(__name__));",
|
||||
"builtins = NULL;",
|
||||
"if (module == NULL && _PyErr_Occurred(tstate))",
|
||||
"Py_XINCREF(module);",
|
||||
"builtins = _PyEval_BuiltinsFromGlobals(tstate, globals);",
|
||||
"Py_INCREF(builtins);",
|
||||
"op = PyObject_GC_New(PyFunctionObject, &PyFunction_Type);",
|
||||
"if (op == NULL)",
|
||||
"op->func_globals = globals;",
|
||||
"op->func_qualname = qualname;",
|
||||
"op->func_doc = doc;",
|
||||
"_PyObject_GC_TRACK(op);",
|
||||
"return (PyObject *)op;",
|
||||
"error:",
|
||||
"Py_DECREF(globals);",
|
||||
"Py_DECREF(code_obj);",
|
||||
"Py_DECREF(doc);",
|
||||
"Py_XDECREF(module);",
|
||||
"Py_XDECREF(builtins);",
|
||||
"return NULL;",
|
||||
)
|
||||
positions = [body.find(token) for token in ordered]
|
||||
if any(position < 0 for position in positions) or positions != sorted(positions):
|
||||
fail("PyFunction_NewWithQualName must preserve acquisition, failure cleanup, and ownership order")
|
||||
|
||||
version = extract_function(source, "_PyFunction_GetVersionForCurrentState(PyFunctionObject *func)")
|
||||
if not version.lstrip().startswith("uint32_t v;") or "uint32_t v =" in version:
|
||||
fail("_PyFunction_GetVersionForCurrentState local must be declared at function entry")
|
||||
ordered_version = (
|
||||
"if (func->func_version != 0)",
|
||||
"if (next_func_version == 0)",
|
||||
"v = next_func_version++;",
|
||||
"func->func_version = v;",
|
||||
"return v;",
|
||||
)
|
||||
positions = [version.find(token) for token in ordered_version]
|
||||
if any(position < 0 for position in positions) or positions != sorted(positions):
|
||||
fail("function-version allocation must preserve guard and update order")
|
||||
|
||||
annotation_dict = extract_function(source, "func_get_annotation_dict(PyFunctionObject *op)")
|
||||
if "if (PyTuple_CheckExact(op->func_annotations)) {\n Py_ssize_t i;" not in annotation_dict:
|
||||
fail("func_get_annotation_dict loop index must be declared at the owning block entry")
|
||||
if "for (Py_ssize_t i =" in annotation_dict:
|
||||
fail("func_get_annotation_dict must not declare the loop index in its for initializer")
|
||||
if not annotation_dict.find("assert(PyTuple_GET_SIZE(ann_tuple) % 2 == 0);") < annotation_dict.find("for (i = 0;") < annotation_dict.find("Py_SETREF(op->func_annotations, ann_dict);"):
|
||||
fail("func_get_annotation_dict must preserve tuple validation, iteration, and replacement order")
|
||||
|
||||
get_annotations = extract_function(source, "func_get_annotations(PyFunctionObject *op, void *Py_UNUSED(ignored))")
|
||||
if not get_annotations.lstrip().startswith("PyObject *d;") or "PyObject *d =" in get_annotations:
|
||||
fail("func_get_annotations result local must be declared at function entry")
|
||||
ordered_get_annotations = (
|
||||
"if (op->func_annotations == NULL)",
|
||||
"op->func_annotations = PyDict_New();",
|
||||
"d = func_get_annotation_dict(op);",
|
||||
"if (d)",
|
||||
"Py_INCREF(d);",
|
||||
"return d;",
|
||||
)
|
||||
positions = [get_annotations.find(token) for token in ordered_get_annotations]
|
||||
if any(position < 0 for position in positions) or positions != sorted(positions):
|
||||
fail("func_get_annotations must preserve lazy allocation and incref order")
|
||||
|
||||
copy_attr = extract_function(source, "functools_copy_attr(PyObject *wrapper, PyObject *wrapped, PyObject *name)")
|
||||
remaining = copy_attr.lstrip()
|
||||
for declaration in ("PyObject *value;", "int res;"):
|
||||
if not remaining.startswith(declaration + "\n"):
|
||||
fail("functools_copy_attr entry declaration missing or out of order: " + declaration)
|
||||
remaining = remaining[len(declaration):].lstrip()
|
||||
if "PyObject *value =" in copy_attr or "int res =" in copy_attr:
|
||||
fail("functools_copy_attr must not declare initialized locals after statements")
|
||||
ordered_copy_attr = (
|
||||
"value = PyObject_GetAttr(wrapped, name);",
|
||||
"if (value == NULL)",
|
||||
"res = PyObject_SetAttr(wrapper, name, value);",
|
||||
"Py_DECREF(value);",
|
||||
"return res;",
|
||||
)
|
||||
positions = [copy_attr.find(token) for token in ordered_copy_attr]
|
||||
if any(position < 0 for position in positions) or positions != sorted(positions):
|
||||
fail("functools_copy_attr must preserve attribute lookup, error handling, and decref order")
|
||||
print("funcobject C89 declarations and function-construction ownership order: PASS")
|
||||
@@ -0,0 +1,344 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Guard C89 declarations and writer error order in GenericAlias repr."""
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from typing import NoReturn
|
||||
|
||||
|
||||
def fail(message: str) -> NoReturn:
|
||||
print(message, file=sys.stderr)
|
||||
raise SystemExit(1)
|
||||
|
||||
|
||||
def extract_function(source: str, signature: str) -> str:
|
||||
start = source.find(signature)
|
||||
if start < 0:
|
||||
fail(f"missing function signature: {signature}")
|
||||
brace = source.find("{", start + len(signature))
|
||||
if brace < 0:
|
||||
fail("missing function body")
|
||||
depth = 0
|
||||
for end in range(brace, len(source)):
|
||||
if source[end] == "{":
|
||||
depth += 1
|
||||
elif source[end] == "}":
|
||||
depth -= 1
|
||||
if depth == 0:
|
||||
return source[brace + 1:end]
|
||||
fail("unterminated function body")
|
||||
|
||||
|
||||
source = Path(sys.argv[1]).read_text(encoding="utf-8")
|
||||
if "static PyMappingMethods ga_as_mapping = {\n 0,\n ga_getitem,\n 0,\n};" not in source:
|
||||
fail("ga_as_mapping must use VC6-compatible positional fields in PyMappingMethods order")
|
||||
body = extract_function(source, "ga_repr(PyObject *self)")
|
||||
if "Py_ssize_t i;" not in body or "for (Py_ssize_t i =" in body:
|
||||
fail("ga_repr loop index must be declared before executable statements")
|
||||
if "PyObject *p;" not in body or "PyObject *p =" in body:
|
||||
fail("ga_repr tuple item must be declared at the loop-block entry")
|
||||
ordered = (
|
||||
"_PyUnicodeWriter_Init(&writer);",
|
||||
"if (alias->starred)",
|
||||
"if (ga_repr_item(&writer, alias->origin) < 0)",
|
||||
"if (_PyUnicodeWriter_WriteASCIIString(&writer, \"[\", 1) < 0)",
|
||||
"for (i = 0; i < len; i++)",
|
||||
"if (i > 0)",
|
||||
"p = PyTuple_GET_ITEM(alias->args, i);",
|
||||
"if (ga_repr_item(&writer, p) < 0)",
|
||||
"if (len == 0)",
|
||||
"if (_PyUnicodeWriter_WriteASCIIString(&writer, \"]\", 1) < 0)",
|
||||
"return _PyUnicodeWriter_Finish(&writer);",
|
||||
"_PyUnicodeWriter_Dealloc(&writer);",
|
||||
)
|
||||
positions = [body.find(token) for token in ordered]
|
||||
if any(position < 0 for position in positions) or positions != sorted(positions):
|
||||
fail("ga_repr must preserve writer and tuple-iteration error order")
|
||||
|
||||
tuple_index = extract_function(source, "tuple_index(PyObject *self, Py_ssize_t len, PyObject *item)")
|
||||
if not tuple_index.lstrip().startswith("Py_ssize_t i;") or "for (Py_ssize_t i =" in tuple_index:
|
||||
fail("tuple_index loop variable must be declared at function entry")
|
||||
if not tuple_index.find("for (i = 0; i < len; i++)") < tuple_index.find("if (PyTuple_GET_ITEM(self, i) == item)") < tuple_index.find("return -1;"):
|
||||
fail("tuple_index must preserve scan and not-found behavior")
|
||||
|
||||
tuple_extend = extract_function(source, "tuple_extend(PyObject **dst, Py_ssize_t dstindex,\n PyObject **src, Py_ssize_t count)")
|
||||
if not tuple_extend.lstrip().startswith("Py_ssize_t i;") or "for (Py_ssize_t i =" in tuple_extend:
|
||||
fail("tuple_extend loop index must be declared at function entry")
|
||||
if "PyObject *item;" not in tuple_extend or "PyObject *item =" in tuple_extend:
|
||||
fail("tuple_extend item local must be declared at loop-block entry")
|
||||
ordered_extend = (
|
||||
"assert(count >= 0);",
|
||||
"if (_PyTuple_Resize(dst, PyTuple_GET_SIZE(*dst) + count - 1) != 0)",
|
||||
"assert(dstindex + count <= PyTuple_GET_SIZE(*dst));",
|
||||
"for (i = 0; i < count; ++i)",
|
||||
"item = src[i];",
|
||||
"Py_INCREF(item);",
|
||||
"PyTuple_SET_ITEM(*dst, dstindex + i, item);",
|
||||
"return dstindex + count;",
|
||||
)
|
||||
positions = [tuple_extend.find(token) for token in ordered_extend]
|
||||
if any(position < 0 for position in positions) or positions != sorted(positions):
|
||||
fail("tuple_extend must preserve resize, retain, and write order")
|
||||
|
||||
make_parameters = extract_function(source, "_Py_make_parameters(PyObject *args)")
|
||||
entry_declarations = (
|
||||
"Py_ssize_t nargs;",
|
||||
"Py_ssize_t len;",
|
||||
"PyObject *parameters;",
|
||||
"Py_ssize_t iparam;",
|
||||
"Py_ssize_t iarg;",
|
||||
)
|
||||
remaining = make_parameters.lstrip()
|
||||
for declaration in entry_declarations:
|
||||
if not remaining.startswith(declaration + "\n"):
|
||||
fail("_Py_make_parameters entry declaration missing or out of order: " + declaration)
|
||||
remaining = remaining[len(declaration):].lstrip()
|
||||
if "for (Py_ssize_t iarg =" in make_parameters or "Py_ssize_t iparam =" in make_parameters:
|
||||
fail("_Py_make_parameters must not declare function-scope locals after statements")
|
||||
if "Py_ssize_t j;" not in make_parameters or "for (Py_ssize_t j =" in make_parameters:
|
||||
fail("_Py_make_parameters nested loop index must be declared at its block entry")
|
||||
|
||||
subs_tvars = extract_function(source, "subs_tvars(PyObject *obj, PyObject *params,\n PyObject **argitems, Py_ssize_t nargs)")
|
||||
if "Py_ssize_t j;" not in subs_tvars or "Py_ssize_t i;" not in subs_tvars:
|
||||
fail("subs_tvars loop indices must be declared at the tuple-processing block entry")
|
||||
if "Py_ssize_t j =" in subs_tvars or "for (Py_ssize_t i =" in subs_tvars:
|
||||
fail("subs_tvars must not initialize indices after statements or in for headers")
|
||||
ordered_subs = (
|
||||
"Py_ssize_t nparams = PyTuple_GET_SIZE(params);",
|
||||
"Py_ssize_t nsubargs = PyTuple_GET_SIZE(subparams);",
|
||||
"PyObject *subargs = PyTuple_New(nsubargs);",
|
||||
"if (subargs == NULL)",
|
||||
"j = 0;",
|
||||
"for (i = 0; i < nsubargs; ++i)",
|
||||
"PyObject *arg = PyTuple_GET_ITEM(subparams, i);",
|
||||
"Py_ssize_t iparam = tuple_index(params, nparams, arg);",
|
||||
"if (iparam >= 0)",
|
||||
"Py_INCREF(arg);",
|
||||
"PyTuple_SET_ITEM(subargs, j, arg);",
|
||||
"Py_DECREF(subargs);",
|
||||
"Py_XDECREF(subparams);",
|
||||
"return obj;",
|
||||
)
|
||||
positions = [subs_tvars.find(token) for token in ordered_subs]
|
||||
if any(position < 0 for position in positions) or positions != sorted(positions):
|
||||
fail("subs_tvars must preserve tuple allocation, substitution, and cleanup order")
|
||||
|
||||
is_unpacked = extract_function(source, "_is_unpacked_typevartuple(PyObject *arg)")
|
||||
if not is_unpacked.lstrip().startswith("PyObject *tmp;\n int res;") or "int res =" in is_unpacked:
|
||||
fail("_is_unpacked_typevartuple locals must be declared at function entry")
|
||||
ordered_is_unpacked = (
|
||||
"if (PyType_Check(arg))",
|
||||
"res = _PyObject_LookupAttr(arg, &_Py_ID(__typing_is_unpacked_typevartuple__), &tmp);",
|
||||
"if (res > 0)",
|
||||
"res = PyObject_IsTrue(tmp);",
|
||||
"Py_DECREF(tmp);",
|
||||
"return res;",
|
||||
)
|
||||
positions = [is_unpacked.find(token) for token in ordered_is_unpacked]
|
||||
if any(position < 0 for position in positions) or positions != sorted(positions):
|
||||
fail("_is_unpacked_typevartuple must preserve type-check, lookup, and cleanup order")
|
||||
|
||||
unpack_args = extract_function(source, "_unpack_args(PyObject *item)")
|
||||
entry_unpacked = ("PyObject *newargs;", "int is_tuple;", "Py_ssize_t nitems;", "PyObject **argitems;", "Py_ssize_t i;")
|
||||
remaining = unpack_args.lstrip()
|
||||
for declaration in entry_unpacked:
|
||||
if not remaining.startswith(declaration + "\n"):
|
||||
fail("_unpack_args entry declaration missing or out of order: " + declaration)
|
||||
remaining = remaining[len(declaration):].lstrip()
|
||||
if "for (Py_ssize_t i =" in unpack_args:
|
||||
fail("_unpack_args must not declare its index in the for initializer")
|
||||
ordered_unpack = (
|
||||
"newargs = PyList_New(0);",
|
||||
"if (newargs == NULL)",
|
||||
"is_tuple = PyTuple_Check(item);",
|
||||
"nitems = is_tuple ? PyTuple_GET_SIZE(item) : 1;",
|
||||
"argitems = is_tuple ? &PyTuple_GET_ITEM(item, 0) : &item;",
|
||||
"for (i = 0; i < nitems; i++)",
|
||||
"item = argitems[i];",
|
||||
"if (!PyType_Check(item))",
|
||||
"PyList_Append(newargs, item)",
|
||||
"Py_SETREF(newargs, PySequence_Tuple(newargs));",
|
||||
"return newargs;",
|
||||
)
|
||||
positions = [unpack_args.find(token) for token in ordered_unpack]
|
||||
if any(position < 0 for position in positions) or positions != sorted(positions):
|
||||
fail("_unpack_args must preserve list allocation, element processing, and tuple conversion order")
|
||||
|
||||
subs_parameters = extract_function(source, "_Py_subs_parameters(PyObject *self, PyObject *args, PyObject *parameters, PyObject *item)")
|
||||
entry_subs = ("Py_ssize_t nparams;", "Py_ssize_t i;", "int is_tuple;", "Py_ssize_t nitems;", "PyObject **argitems;", "Py_ssize_t nargs;", "PyObject *newargs;", "Py_ssize_t iarg;", "Py_ssize_t jarg;")
|
||||
remaining = subs_parameters.lstrip()
|
||||
for declaration in entry_subs:
|
||||
if not remaining.startswith(declaration + "\n"):
|
||||
fail("_Py_subs_parameters entry declaration missing or out of order: " + declaration)
|
||||
remaining = remaining[len(declaration):].lstrip()
|
||||
if "for (Py_ssize_t i =" in subs_parameters or "for (Py_ssize_t iarg =" in subs_parameters:
|
||||
fail("_Py_subs_parameters must not declare loop indices in for initializers")
|
||||
ordered_subs_parameters = (
|
||||
"nparams = PyTuple_GET_SIZE(parameters);",
|
||||
"if (nparams == 0)",
|
||||
"item = _unpack_args(item);",
|
||||
"for (i = 0; i < nparams; i++)",
|
||||
"is_tuple = PyTuple_Check(item);",
|
||||
"nitems = is_tuple ? PyTuple_GET_SIZE(item) : 1;",
|
||||
"argitems = is_tuple ? &PyTuple_GET_ITEM(item, 0) : &item;",
|
||||
"if (nitems != nparams)",
|
||||
"nargs = PyTuple_GET_SIZE(args);",
|
||||
"newargs = PyTuple_New(nargs);",
|
||||
"for (iarg = 0, jarg = 0; iarg < nargs; iarg++)",
|
||||
"Py_DECREF(item);",
|
||||
"return newargs;",
|
||||
)
|
||||
positions = [subs_parameters.rfind(token) if token == "Py_DECREF(item);" else subs_parameters.find(token) for token in ordered_subs_parameters]
|
||||
if any(position < 0 for position in positions) or positions != sorted(positions):
|
||||
fail("_Py_subs_parameters must preserve validation, unpacking, substitution, and cleanup order")
|
||||
|
||||
ga_getitem = extract_function(source, "ga_getitem(PyObject *self, PyObject *item)")
|
||||
if not ga_getitem.lstrip().startswith("gaobject *alias = (gaobject *)self;\n PyObject *newargs;\n PyObject *res;"):
|
||||
fail("ga_getitem result locals must be declared before executable statements")
|
||||
if "PyObject *newargs =" in ga_getitem or "PyObject *res =" in ga_getitem:
|
||||
fail("ga_getitem must not declare initialized result locals late")
|
||||
ordered_getitem = (
|
||||
"if (alias->parameters == NULL)",
|
||||
"alias->parameters = _Py_make_parameters(alias->args);",
|
||||
"newargs = _Py_subs_parameters(self, alias->args, alias->parameters, item);",
|
||||
"if (newargs == NULL)",
|
||||
"res = Py_GenericAlias(alias->origin, newargs);",
|
||||
"((gaobject *)res)->starred = alias->starred;",
|
||||
"Py_DECREF(newargs);",
|
||||
"return res;",
|
||||
)
|
||||
positions = [ga_getitem.find(token) for token in ordered_getitem]
|
||||
if any(position < 0 for position in positions) or positions != sorted(positions):
|
||||
fail("ga_getitem must preserve lazy parameters, allocation, starred flag, and decref order")
|
||||
|
||||
ga_hash = extract_function(source, "ga_hash(PyObject *self)")
|
||||
if "Py_hash_t h1;" not in ga_hash or "Py_hash_t h1 =" in ga_hash:
|
||||
fail("ga_hash second hash local must be declared at function entry")
|
||||
ordered_hash = (
|
||||
"Py_hash_t h0 = PyObject_Hash(alias->origin);",
|
||||
"if (h0 == -1)",
|
||||
"h1 = PyObject_Hash(alias->args);",
|
||||
"if (h1 == -1)",
|
||||
"return h0 ^ h1;",
|
||||
)
|
||||
positions = [ga_hash.find(token) for token in ordered_hash]
|
||||
if any(position < 0 for position in positions) or positions != sorted(positions):
|
||||
fail("ga_hash must preserve origin-first hashing and error checks")
|
||||
|
||||
ga_getattro = extract_function(source, "ga_getattro(PyObject *self, PyObject *name)")
|
||||
if "const char * const *p;" not in ga_getattro or "for (const char * const *p =" in ga_getattro:
|
||||
fail("ga_getattro attribute-table iterator must be declared before the loop")
|
||||
ordered_getattro = (
|
||||
"if (PyUnicode_Check(name))",
|
||||
"for (p = attr_exceptions; ; p++)",
|
||||
"if (*p == NULL)",
|
||||
"return PyObject_GetAttr(alias->origin, name);",
|
||||
"if (_PyUnicode_EqualToASCIIString(name, *p))",
|
||||
"return PyObject_GenericGetAttr(self, name);",
|
||||
)
|
||||
positions = [ga_getattro.find(token) for token in ordered_getattro]
|
||||
if any(position < 0 for position in positions) or positions != sorted(positions):
|
||||
fail("ga_getattro must preserve exception-name scan and origin fallback order")
|
||||
|
||||
ga_richcompare = extract_function(source, "ga_richcompare(PyObject *a, PyObject *b, int op)")
|
||||
if "gaobject *aa;" not in ga_richcompare or "gaobject *bb;" not in ga_richcompare or "int eq;" not in ga_richcompare:
|
||||
fail("ga_richcompare locals must be declared before execution")
|
||||
if "gaobject *aa =" in ga_richcompare or "gaobject *bb =" in ga_richcompare or "int eq =" in ga_richcompare:
|
||||
fail("ga_richcompare must not declare locals after control flow")
|
||||
ordered_richcompare = (
|
||||
"if (!_PyGenericAlias_Check(b)",
|
||||
"if (op == Py_NE)",
|
||||
"aa = (gaobject *)a;",
|
||||
"bb = (gaobject *)b;",
|
||||
"if (aa->starred != bb->starred)",
|
||||
"eq = PyObject_RichCompareBool(aa->origin, bb->origin, Py_EQ);",
|
||||
"if (eq < 0)",
|
||||
"return PyObject_RichCompare(aa->args, bb->args, Py_EQ);",
|
||||
)
|
||||
positions = [ga_richcompare.find(token) for token in ordered_richcompare]
|
||||
if any(position < 0 for position in positions) or positions != sorted(positions):
|
||||
fail("ga_richcompare must preserve comparison dispatch and error order")
|
||||
ga_dir = extract_function(source, "ga_dir(PyObject *self, PyObject *Py_UNUSED(ignored))")
|
||||
if "PyObject *dir_entry;" not in ga_dir or "const char * const *p;" not in ga_dir or "int contains;" not in ga_dir:
|
||||
fail("ga_dir working locals must be declared before execution")
|
||||
if "PyObject *dir_entry =" in ga_dir or "for (const char * const *p =" in ga_dir or "int contains =" in ga_dir:
|
||||
fail("ga_dir must not declare locals after the origin lookup")
|
||||
ordered_dir = (
|
||||
"dir = PyObject_Dir(alias->origin);",
|
||||
"if (dir == NULL)",
|
||||
"dir_entry = NULL;",
|
||||
"for (p = attr_exceptions; ; p++)",
|
||||
"dir_entry = PyUnicode_FromString(*p);",
|
||||
"contains = PySequence_Contains(dir, dir_entry);",
|
||||
"Py_CLEAR(dir_entry);",
|
||||
"return dir;",
|
||||
"Py_DECREF(dir);",
|
||||
"Py_XDECREF(dir_entry);",
|
||||
)
|
||||
positions = [ga_dir.find(token) for token in ordered_dir]
|
||||
if any(position < 0 for position in positions) or positions != sorted(positions):
|
||||
fail("ga_dir must preserve directory lookup, append, and cleanup order")
|
||||
|
||||
ga_new = extract_function(source, "ga_new(PyTypeObject *type, PyObject *args, PyObject *kwds)")
|
||||
if "PyObject *origin;" not in ga_new or "PyObject *arguments;" not in ga_new or "gaobject *self;" not in ga_new:
|
||||
fail("ga_new allocation locals must be declared before validation")
|
||||
if "PyObject *origin =" in ga_new or "PyObject *arguments =" in ga_new or "gaobject *self =" in ga_new:
|
||||
fail("ga_new must not declare initialized locals after argument checks")
|
||||
ordered_new = (
|
||||
"if (!_PyArg_NoKeywords(\"GenericAlias\", kwds))",
|
||||
"if (!_PyArg_CheckPositional(\"GenericAlias\", PyTuple_GET_SIZE(args), 2, 2))",
|
||||
"origin = PyTuple_GET_ITEM(args, 0);",
|
||||
"arguments = PyTuple_GET_ITEM(args, 1);",
|
||||
"self = (gaobject *)type->tp_alloc(type, 0);",
|
||||
"if (self == NULL)",
|
||||
"if (!setup_ga(self, origin, arguments))",
|
||||
"Py_DECREF(self);",
|
||||
"return (PyObject *)self;",
|
||||
)
|
||||
positions = [ga_new.find(token) for token in ordered_new]
|
||||
if any(position < 0 for position in positions) or positions != sorted(positions):
|
||||
fail("ga_new must preserve argument validation, allocation, and failure cleanup order")
|
||||
|
||||
ga_iternext = extract_function(source, "ga_iternext(gaiterobject *gi)")
|
||||
if "gaobject *alias;" not in ga_iternext or "PyObject *starred_alias;" not in ga_iternext:
|
||||
fail("ga_iternext result locals must be declared before the exhausted-iterator guard")
|
||||
if "gaobject *alias =" in ga_iternext or "PyObject *starred_alias =" in ga_iternext:
|
||||
fail("ga_iternext must not declare initialized locals after the guard")
|
||||
ordered_next = (
|
||||
"if (gi->obj == NULL)",
|
||||
"alias = (gaobject *)gi->obj;",
|
||||
"starred_alias = Py_GenericAlias(alias->origin, alias->args);",
|
||||
"if (starred_alias == NULL)",
|
||||
"((gaobject *)starred_alias)->starred = true;",
|
||||
"Py_SETREF(gi->obj, NULL);",
|
||||
"return starred_alias;",
|
||||
)
|
||||
positions = [ga_iternext.find(token) for token in ordered_next]
|
||||
if any(position < 0 for position in positions) or positions != sorted(positions):
|
||||
fail("ga_iternext must preserve exhausted-state, allocation, and ownership order")
|
||||
|
||||
if "#if defined(MS_WINDOWS) && defined(_MSC_VER) && _MSC_VER < 1300\nstatic PyNumberMethods ga_as_number = {0};" not in source:
|
||||
fail("VC6 ga_as_number initializer must use C89-compatible zero initialization")
|
||||
if "PyTypeObject _Py_GenericAliasIterType = {\n PyVarObject_HEAD_INIT(&PyType_Type, 0)\n};" not in source:
|
||||
fail("VC6 GenericAlias iterator type must defer designated slots to runtime setup")
|
||||
if "PyTypeObject Py_GenericAliasType = {\n PyVarObject_HEAD_INIT(&PyType_Type, 0)\n};" not in source:
|
||||
fail("VC6 GenericAlias type must defer designated slots to runtime setup")
|
||||
if "_Py_GenericAlias_InitTypes(void)" not in source:
|
||||
fail("VC6 GenericAlias type-slot initializer missing")
|
||||
init_types = extract_function(source, "_Py_GenericAlias_InitTypes(void)")
|
||||
for field in ("tp_name", "tp_doc", "tp_basicsize", "tp_dealloc", "tp_repr", "tp_as_number", "tp_as_mapping", "tp_hash", "tp_call", "tp_getattro", "tp_flags", "tp_traverse", "tp_richcompare", "tp_weaklistoffset", "tp_methods", "tp_members", "tp_alloc", "tp_new", "tp_free", "tp_getset", "tp_iter", "tp_vectorcall_offset"):
|
||||
if "Py_GenericAliasType." + field + " =" not in init_types:
|
||||
fail("VC6 GenericAlias runtime setup missing slot: " + field)
|
||||
for field in ("tp_name", "tp_basicsize", "tp_iter", "tp_iternext", "tp_traverse", "tp_methods", "tp_dealloc", "tp_clear", "tp_flags"):
|
||||
if "_Py_GenericAliasIterType." + field + " =" not in init_types:
|
||||
fail("VC6 GenericAlias iterator runtime setup missing slot: " + field)
|
||||
if "ga_as_number.nb_or = _Py_union_type_or;" not in init_types:
|
||||
fail("VC6 GenericAlias number methods runtime setup missing nb_or")
|
||||
if len(sys.argv) > 2:
|
||||
object_source = Path(sys.argv[2]).read_text(encoding="utf-8")
|
||||
if "_Py_GenericAlias_InitTypes();" not in object_source:
|
||||
fail("object.c must initialize GenericAlias type slots before PyType_Ready")
|
||||
type_loop = object_source.find("// All other static types (unless initialized elsewhere)")
|
||||
setup_call = object_source.find("_Py_GenericAlias_InitTypes();")
|
||||
if setup_call < 0 or type_loop < 0 or setup_call > type_loop:
|
||||
fail("GenericAlias type-slot initialization must precede static type readiness")
|
||||
print("genericaliasobject C89 declaration and writer order: PASS")
|
||||
@@ -0,0 +1,277 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Guard C89 declarations and frame traversal order in genobject.c."""
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from typing import NoReturn
|
||||
|
||||
|
||||
def fail(message: str) -> NoReturn:
|
||||
print(message, file=sys.stderr)
|
||||
raise SystemExit(1)
|
||||
|
||||
|
||||
def extract_function(source: str, signature: str) -> str:
|
||||
start = source.find(signature)
|
||||
if start < 0:
|
||||
fail(f"missing function signature: {signature}")
|
||||
brace = source.find("{", start + len(signature))
|
||||
if brace < 0:
|
||||
fail("missing function body")
|
||||
depth = 0
|
||||
for end in range(brace, len(source)):
|
||||
if source[end] == "{":
|
||||
depth += 1
|
||||
elif source[end] == "}":
|
||||
depth -= 1
|
||||
if depth == 0:
|
||||
return source[brace + 1:end]
|
||||
fail("unterminated function body")
|
||||
|
||||
|
||||
source = Path(sys.argv[1]).read_text(encoding="utf-8")
|
||||
body = extract_function(source, "gen_traverse(PyGenObject *gen, visitproc visit, void *arg)")
|
||||
if "int err;" not in body or "int err =" in body:
|
||||
fail("gen_traverse frame result local must be declared before traversal assertions")
|
||||
ordered = (
|
||||
"if (gen->gi_frame_state < FRAME_CLEARED)",
|
||||
"_PyInterpreterFrame *frame = (_PyInterpreterFrame *)(gen->gi_iframe);",
|
||||
"assert(frame->frame_obj == NULL ||",
|
||||
"err = _PyFrame_Traverse(frame, visit, arg);",
|
||||
"if (err)",
|
||||
"return exc_state_traverse(&gen->gi_exc_state, visit, arg);",
|
||||
)
|
||||
positions = [body.find(token) for token in ordered]
|
||||
if any(position < 0 for position in positions) or positions != sorted(positions):
|
||||
fail("gen_traverse must preserve frame validation, visit, and error return order")
|
||||
|
||||
gen_yf = extract_function(source, "_PyGen_yf(PyGenObject *gen)")
|
||||
if "_Py_CODEUNIT next;" not in gen_yf or "_Py_CODEUNIT next =" in gen_yf:
|
||||
fail("_PyGen_yf instruction local must be declared at function entry")
|
||||
ordered_yf = (
|
||||
"PyObject *yf = NULL;",
|
||||
"if (gen->gi_frame_state < FRAME_CLEARED)",
|
||||
"if (gen->gi_frame_state == FRAME_CREATED)",
|
||||
"return NULL;",
|
||||
"next = frame->prev_instr[1];",
|
||||
"if (_PyOpcode_Deopt[_Py_OPCODE(next)] != RESUME",
|
||||
"yf = _PyFrame_StackPeek(frame);",
|
||||
"Py_INCREF(yf);",
|
||||
"return yf;",
|
||||
)
|
||||
positions = [gen_yf.find(token) for token in ordered_yf]
|
||||
if any(position < 0 for position in positions) or positions != sorted(positions):
|
||||
fail("_PyGen_yf must preserve created-frame guard and yield-from inspection order")
|
||||
|
||||
gen_throw = extract_function(source, "_gen_throw(PyGenObject *gen, int close_on_genexit,\n PyObject *typ, PyObject *val, PyObject *tb)")
|
||||
if "PyFrameState state;" not in gen_throw:
|
||||
fail("_gen_throw nested generator-state locals must be declared at branch entry")
|
||||
recursive_branch = gen_throw.split("if (PyGen_CheckExact(yf) || PyCoro_CheckExact(yf)) {", 1)[1].split("} else {", 1)[0]
|
||||
if "PyFrameState state;" not in recursive_branch or "PyFrameState state =" in recursive_branch:
|
||||
fail("_gen_throw recursive branch state local must be declared before current-frame updates")
|
||||
ordered_recursive = (
|
||||
"PyThreadState *tstate = _PyThreadState_GET();",
|
||||
"_PyInterpreterFrame *prev = tstate->cframe->current_frame;",
|
||||
"frame->previous = prev;",
|
||||
"tstate->cframe->current_frame = frame;",
|
||||
"state = gen->gi_frame_state;",
|
||||
"gen->gi_frame_state = FRAME_EXECUTING;",
|
||||
"ret = _gen_throw((PyGenObject *)yf, close_on_genexit,",
|
||||
"gen->gi_frame_state = state;",
|
||||
"tstate->cframe->current_frame = prev;",
|
||||
"frame->previous = NULL;",
|
||||
)
|
||||
positions = [recursive_branch.find(token) for token in ordered_recursive]
|
||||
if any(position < 0 for position in positions) or positions != sorted(positions):
|
||||
fail("_gen_throw must preserve recursive frame/state restoration order")
|
||||
iterator_branch = gen_throw.split("} else {", 1)[1]
|
||||
if "PyFrameState state;" not in iterator_branch or "PyFrameState state =" in iterator_branch:
|
||||
fail("_gen_throw iterator branch state local must be declared at branch entry")
|
||||
ordered_iterator = (
|
||||
"PyObject *meth;",
|
||||
"if (_PyObject_LookupAttr(yf, &_Py_ID(throw), &meth) < 0)",
|
||||
"if (meth == NULL)",
|
||||
"state = gen->gi_frame_state;",
|
||||
"gen->gi_frame_state = FRAME_EXECUTING;",
|
||||
"ret = PyObject_CallFunctionObjArgs(meth, typ, val, tb, NULL);",
|
||||
"gen->gi_frame_state = state;",
|
||||
)
|
||||
positions = [iterator_branch.find(token) for token in ordered_iterator]
|
||||
if any(position < 0 for position in positions) or positions != sorted(positions):
|
||||
fail("_gen_throw must preserve iterator lookup and state restoration order")
|
||||
|
||||
throw_failure = gen_throw.split("if (!ret) {", 1)[1]
|
||||
if "int jump;" not in throw_failure or "int jump =" in throw_failure:
|
||||
fail("_gen_throw SEND jump local must be declared at its failure-branch entry")
|
||||
ordered_jump = (
|
||||
"PyObject *val;",
|
||||
"assert(gen->gi_frame_state < FRAME_CLEARED);",
|
||||
"ret = _PyFrame_StackPop((_PyInterpreterFrame *)gen->gi_iframe);",
|
||||
"assert(_PyInterpreterFrame_LASTI(frame) >= 0);",
|
||||
"assert(_Py_OPCODE(frame->prev_instr[-1]) == SEND);",
|
||||
"jump = _Py_OPARG(frame->prev_instr[-1]);",
|
||||
"frame->prev_instr += jump - 1;",
|
||||
"if (_PyGen_FetchStopIterationValue(&val) == 0)",
|
||||
)
|
||||
positions = [throw_failure.find(token) for token in ordered_jump]
|
||||
if any(position < 0 for position in positions) or positions != sorted(positions):
|
||||
fail("_gen_throw must preserve SEND validation and jump/stop-value handling order")
|
||||
|
||||
gen_sizeof = extract_function(source, "gen_sizeof(PyGenObject *gen, PyObject *Py_UNUSED(ignored))")
|
||||
if "PyCodeObject *code;" not in gen_sizeof or "PyCodeObject *code =" in gen_sizeof:
|
||||
fail("gen_sizeof code local must be declared before size calculation")
|
||||
ordered_sizeof = (
|
||||
"res = offsetof(PyGenObject, gi_iframe) + offsetof(_PyInterpreterFrame, localsplus);",
|
||||
"code = gen->gi_code;",
|
||||
"res += (code->co_nlocalsplus+code->co_stacksize) * sizeof(PyObject *);",
|
||||
"return PyLong_FromSsize_t(res);",
|
||||
)
|
||||
positions = [gen_sizeof.find(token) for token in ordered_sizeof]
|
||||
if any(position < 0 for position in positions) or positions != sorted(positions):
|
||||
fail("gen_sizeof must preserve base-size, code-size, and result order")
|
||||
|
||||
make_coro = extract_function(source, "_Py_MakeCoro(PyFunctionObject *func)")
|
||||
entry_coro = ("int coro_flags;", "PyObject *coro;", "PyThreadState *tstate;", "int origin_depth;")
|
||||
remaining = make_coro.lstrip()
|
||||
for declaration in entry_coro:
|
||||
if not remaining.startswith(declaration + "\n"):
|
||||
fail("_Py_MakeCoro entry declaration missing or out of order: " + declaration)
|
||||
remaining = remaining[len(declaration):].lstrip()
|
||||
if "int coro_flags =" in make_coro or "PyObject *coro =" in make_coro or "PyThreadState *tstate =" in make_coro or "int origin_depth =" in make_coro:
|
||||
fail("_Py_MakeCoro must not declare initialized locals after control flow")
|
||||
if "PyObject *cr_origin;" not in make_coro or "PyObject *cr_origin =" in make_coro:
|
||||
fail("_Py_MakeCoro origin local must be declared at the fallback branch entry")
|
||||
ordered_coro = (
|
||||
"coro_flags = ((PyCodeObject *)func->func_code)->co_flags &",
|
||||
"if (coro_flags == CO_GENERATOR)",
|
||||
"if (coro_flags == CO_ASYNC_GENERATOR)",
|
||||
"assert (coro_flags == CO_COROUTINE);",
|
||||
"coro = make_gen(&PyCoro_Type, func);",
|
||||
"tstate = _PyThreadState_GET();",
|
||||
"origin_depth = tstate->coroutine_origin_tracking_depth;",
|
||||
"if (origin_depth == 0)",
|
||||
"cr_origin = compute_cr_origin(origin_depth, _PyEval_GetFrame()->previous);",
|
||||
"return coro;",
|
||||
)
|
||||
positions = [make_coro.find(token) for token in ordered_coro]
|
||||
if any(position < 0 for position in positions) or positions != sorted(positions):
|
||||
fail("_Py_MakeCoro must preserve coroutine selection, creation, and origin tracking order")
|
||||
|
||||
compute_origin_start = source.rfind("static PyObject *\ncompute_cr_origin(int origin_depth, _PyInterpreterFrame *current_frame)")
|
||||
if compute_origin_start < 0:
|
||||
fail("missing compute_cr_origin definition")
|
||||
compute_origin = extract_function(source[compute_origin_start:], "compute_cr_origin(int origin_depth, _PyInterpreterFrame *current_frame)")
|
||||
for declaration in ("_PyInterpreterFrame *frame;", "int frame_count;", "PyObject *cr_origin;", "int i;"):
|
||||
if declaration not in compute_origin:
|
||||
fail("compute_cr_origin local must be declared before frame traversal: " + declaration)
|
||||
if "int frame_count =" in compute_origin or "PyObject *cr_origin =" in compute_origin or "for (int i =" in compute_origin:
|
||||
fail("compute_cr_origin must not declare initialized locals after traversal starts")
|
||||
ordered_origin = (
|
||||
"frame = current_frame;",
|
||||
"frame_count = 0;",
|
||||
"for (; frame && frame_count < origin_depth; ++frame_count)",
|
||||
"cr_origin = PyTuple_New(frame_count);",
|
||||
"if (cr_origin == NULL)",
|
||||
"frame = current_frame;\n for (i = 0; i < frame_count; ++i)",
|
||||
"PyTuple_SET_ITEM(cr_origin, i, frameinfo);",
|
||||
"return cr_origin;",
|
||||
)
|
||||
positions = [compute_origin.find(token) for token in ordered_origin]
|
||||
if any(position < 0 for position in positions) or positions != sorted(positions):
|
||||
fail("compute_cr_origin must preserve frame counting, tuple creation, and collection order")
|
||||
|
||||
pycoro_new = extract_function(source, "PyCoro_New(PyFrameObject *f, PyObject *name, PyObject *qualname)")
|
||||
for declaration in ("PyObject *coro;", "PyThreadState *tstate;", "int origin_depth;"):
|
||||
if declaration not in pycoro_new:
|
||||
fail("PyCoro_New local must be declared at function entry: " + declaration)
|
||||
if "PyObject *coro =" in pycoro_new or "PyThreadState *tstate =" in pycoro_new or "int origin_depth =" in pycoro_new:
|
||||
fail("PyCoro_New must not declare initialized locals after allocation")
|
||||
if "PyObject *cr_origin;" not in pycoro_new or "PyObject *cr_origin =" in pycoro_new:
|
||||
fail("PyCoro_New origin local must be declared at the fallback branch entry")
|
||||
ordered_pycoro = (
|
||||
"coro = gen_new_with_qualname(&PyCoro_Type, f, name, qualname);",
|
||||
"if (!coro)",
|
||||
"tstate = _PyThreadState_GET();",
|
||||
"origin_depth = tstate->coroutine_origin_tracking_depth;",
|
||||
"if (origin_depth == 0)",
|
||||
"cr_origin = compute_cr_origin(origin_depth, _PyEval_GetFrame());",
|
||||
"return coro;",
|
||||
)
|
||||
positions = [pycoro_new.find(token) for token in ordered_pycoro]
|
||||
if any(position < 0 for position in positions) or positions != sorted(positions):
|
||||
fail("PyCoro_New must preserve allocation and origin-tracking order")
|
||||
|
||||
gen_new_qualname = extract_function(source, "gen_new_with_qualname(PyTypeObject *type, PyFrameObject *f,\n PyObject *name, PyObject *qualname)")
|
||||
if "_PyInterpreterFrame *frame;" not in gen_new_qualname or "_PyInterpreterFrame *frame =" in gen_new_qualname:
|
||||
fail("gen_new_with_qualname frame local must be declared before allocation checks")
|
||||
ordered_gen_new = (
|
||||
"PyCodeObject *code = f->f_frame->f_code;",
|
||||
"int size = code->co_nlocalsplus + code->co_stacksize;",
|
||||
"PyGenObject *gen = PyObject_GC_NewVar(PyGenObject, type, size);",
|
||||
"if (gen == NULL)",
|
||||
"assert(f->f_frame->frame_obj == NULL);",
|
||||
"assert(f->f_frame->owner == FRAME_OWNED_BY_FRAME_OBJECT);",
|
||||
"frame = (_PyInterpreterFrame *)gen->gi_iframe;",
|
||||
"_PyFrame_Copy((_PyInterpreterFrame *)f->_f_frame_data, frame);",
|
||||
"gen->gi_frame_state = FRAME_CREATED;",
|
||||
"return (PyObject *)gen;",
|
||||
)
|
||||
positions = [gen_new_qualname.find(token) for token in ordered_gen_new]
|
||||
if any(position < 0 for position in positions) or positions != sorted(positions):
|
||||
fail("gen_new_with_qualname must preserve frame validation, copy, and ownership order")
|
||||
|
||||
asend_dealloc = extract_function(source, "async_gen_asend_dealloc(PyAsyncGenASend *o)")
|
||||
if not asend_dealloc.lstrip().startswith("#if _PyAsyncGen_MAXFREELIST > 0\n struct _Py_async_gen_state *state;\n#endif"):
|
||||
fail("async_gen_asend_dealloc state local must be declared at function entry under its feature guard")
|
||||
if "struct _Py_async_gen_state *state =" in asend_dealloc:
|
||||
fail("async_gen_asend_dealloc must not declare state after object cleanup")
|
||||
ordered_asend_dealloc = (
|
||||
"_PyObject_GC_UNTRACK((PyObject *)o);",
|
||||
"Py_CLEAR(o->ags_gen);",
|
||||
"Py_CLEAR(o->ags_sendval);",
|
||||
"state = get_async_gen_state();",
|
||||
"assert(state->asend_numfree != -1);",
|
||||
"if (state->asend_numfree < _PyAsyncGen_MAXFREELIST)",
|
||||
"state->asend_freelist[state->asend_numfree++] = o;",
|
||||
"PyObject_GC_Del(o);",
|
||||
)
|
||||
positions = [asend_dealloc.find(token) for token in ordered_asend_dealloc]
|
||||
if any(position < 0 for position in positions) or positions != sorted(positions):
|
||||
fail("async_gen_asend_dealloc must preserve untracking, clearing, freelist, and free order")
|
||||
|
||||
wrapped_dealloc = extract_function(source, "async_gen_wrapped_val_dealloc(_PyAsyncGenWrappedValue *o)")
|
||||
if not wrapped_dealloc.lstrip().startswith("#if _PyAsyncGen_MAXFREELIST > 0\n struct _Py_async_gen_state *state;\n#endif"):
|
||||
fail("wrapped-value freelist state must be declared at function entry under its feature guard")
|
||||
ordered_wrapped = (
|
||||
"_PyObject_GC_UNTRACK((PyObject *)o);",
|
||||
"Py_CLEAR(o->agw_val);",
|
||||
"state = get_async_gen_state();",
|
||||
"assert(state->value_numfree != -1);",
|
||||
"if (state->value_numfree < _PyAsyncGen_MAXFREELIST)",
|
||||
"state->value_freelist[state->value_numfree++] = o;",
|
||||
"PyObject_GC_Del(o);",
|
||||
)
|
||||
positions = [wrapped_dealloc.find(token) for token in ordered_wrapped]
|
||||
if any(position < 0 for position in positions) or positions != sorted(positions):
|
||||
fail("wrapped-value deallocator must preserve untrack, clear, freelist, and free order")
|
||||
|
||||
wrapped_new = extract_function(source, "_PyAsyncGenValueWrapperNew(PyObject *val)")
|
||||
state_decl = "#if _PyAsyncGen_MAXFREELIST > 0\n struct _Py_async_gen_state *state;\n#endif"
|
||||
if state_decl not in wrapped_new or wrapped_new.find(state_decl) > wrapped_new.find("assert(val);"):
|
||||
fail("_PyAsyncGenValueWrapperNew freelist state must be declared before executable statements")
|
||||
if "struct _Py_async_gen_state *state =" in wrapped_new:
|
||||
fail("_PyAsyncGenValueWrapperNew must not initialize state in a late declaration")
|
||||
ordered_wrapped_new = (
|
||||
"assert(val);",
|
||||
"state = get_async_gen_state();",
|
||||
"assert(state->value_numfree != -1);",
|
||||
"if (state->value_numfree)",
|
||||
"o = PyObject_GC_New(_PyAsyncGenWrappedValue,",
|
||||
"o->agw_val = val;",
|
||||
"Py_INCREF(val);",
|
||||
"_PyObject_GC_TRACK((PyObject*)o);",
|
||||
"return (PyObject*)o;",
|
||||
)
|
||||
positions = [wrapped_new.find(token) for token in ordered_wrapped_new]
|
||||
if any(position < 0 for position in positions) or positions != sorted(positions):
|
||||
fail("_PyAsyncGenValueWrapperNew must preserve freelist, allocation, and value ownership order")
|
||||
print("genobject C89 declaration and generator ownership order: PASS")
|
||||
@@ -0,0 +1,116 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Guard C89 declarations and ownership/order invariants in interpreteridobject.c."""
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from typing import NoReturn
|
||||
|
||||
|
||||
def fail(message: str) -> NoReturn:
|
||||
print(message, file=sys.stderr)
|
||||
raise SystemExit(1)
|
||||
|
||||
|
||||
def extract_function(source: str, signature: str) -> str:
|
||||
start = source.find(signature)
|
||||
if start < 0:
|
||||
fail(f"missing function signature: {signature}")
|
||||
brace = source.find("{", start + len(signature))
|
||||
if brace < 0:
|
||||
fail("missing function body")
|
||||
depth = 0
|
||||
for end in range(brace, len(source)):
|
||||
if source[end] == "{":
|
||||
depth += 1
|
||||
elif source[end] == "}":
|
||||
depth -= 1
|
||||
if depth == 0:
|
||||
return source[brace + 1:end]
|
||||
fail("unterminated function body")
|
||||
|
||||
|
||||
def ordered(body: str, tokens: tuple[str, ...], message: str) -> None:
|
||||
positions = [body.find(token) for token in tokens]
|
||||
if any(position < 0 for position in positions) or positions != sorted(positions):
|
||||
fail(message)
|
||||
|
||||
|
||||
source = Path(sys.argv[1]).read_text(encoding="utf-8")
|
||||
|
||||
newinterpid = extract_function(source, "newinterpid(PyTypeObject *cls, int64_t id, int force)")
|
||||
for declaration in ("PyInterpreterState *interp;", "interpid *self;"):
|
||||
if declaration not in newinterpid:
|
||||
fail("newinterpid local must be declared at function entry: " + declaration)
|
||||
if "PyInterpreterState *interp =" in newinterpid or "interpid *self =" in newinterpid:
|
||||
fail("newinterpid must not initialize locals in late declarations")
|
||||
ordered(newinterpid, (
|
||||
"interp = _PyInterpreterState_LookUpID(id);",
|
||||
"if (interp == NULL)",
|
||||
"if (interp != NULL)",
|
||||
"_PyInterpreterState_IDIncref(interp)",
|
||||
"self = PyObject_New(interpid, cls);",
|
||||
"if (self == NULL)",
|
||||
"_PyInterpreterState_IDDecref(interp);",
|
||||
"self->id = id;",
|
||||
"return self;",
|
||||
), "newinterpid must preserve lookup, incref, allocation, rollback, and assignment order")
|
||||
|
||||
dealloc = extract_function(source, "interpid_dealloc(PyObject *v)")
|
||||
for declaration in ("int64_t id;", "PyInterpreterState *interp;"):
|
||||
if declaration not in dealloc:
|
||||
fail("interpid_dealloc local must be declared at function entry: " + declaration)
|
||||
ordered(dealloc, (
|
||||
"id = ((interpid *)v)->id;",
|
||||
"interp = _PyInterpreterState_LookUpID(id);",
|
||||
"_PyInterpreterState_IDDecref(interp);",
|
||||
"PyErr_Clear();",
|
||||
"Py_TYPE(v)->tp_free(v);",
|
||||
), "interpid_dealloc must preserve ID lookup, reference release, and free order")
|
||||
|
||||
hash_body = extract_function(source, "interpid_hash(PyObject *self)")
|
||||
if "Py_hash_t hash;" not in hash_body or "Py_hash_t hash =" in hash_body:
|
||||
fail("interpid_hash hash result must be declared before object hashing")
|
||||
ordered(hash_body, (
|
||||
"PyObject *obj = PyLong_FromLongLong(id->id);",
|
||||
"if (obj == NULL)",
|
||||
"hash = PyObject_Hash(obj);",
|
||||
"Py_DECREF(obj);",
|
||||
"return hash;",
|
||||
), "interpid_hash must preserve allocation, hashing, decref, and return order")
|
||||
|
||||
richcompare = extract_function(source, "interpid_richcompare(PyObject *self, PyObject *other, int op)")
|
||||
for declaration in ("interpid *id;", "int equal;"):
|
||||
if declaration not in richcompare:
|
||||
fail("interpid_richcompare local must be declared at function entry: " + declaration)
|
||||
ordered(richcompare, (
|
||||
"if (op != Py_EQ && op != Py_NE)",
|
||||
"if (!PyObject_TypeCheck(self, &_PyInterpreterID_Type))",
|
||||
"id = (interpid *)self;",
|
||||
"if (PyObject_TypeCheck(other, &_PyInterpreterID_Type))",
|
||||
"interpid *otherid = (interpid *)other;",
|
||||
"equal = (id->id == otherid->id);",
|
||||
"else if (PyLong_CheckExact(other))",
|
||||
"int overflow;",
|
||||
"PY_LONG_LONG otherid;",
|
||||
"otherid = PyLong_AsLongLongAndOverflow(other, &overflow);",
|
||||
"equal = !overflow && (otherid >= 0) && (id->id == otherid);",
|
||||
"else if (PyNumber_Check(other))",
|
||||
"pyid = PyLong_FromLongLong(id->id);",
|
||||
"res = PyObject_RichCompare(pyid, other, op);",
|
||||
"Py_DECREF(pyid);",
|
||||
"return res;",
|
||||
"if ((op == Py_EQ && equal) || (op == Py_NE && !equal))",
|
||||
"Py_RETURN_TRUE;",
|
||||
"Py_RETURN_FALSE;",
|
||||
), "interpid_richcompare must preserve dispatch, overflow, and reference ordering")
|
||||
|
||||
get_id = extract_function(source, "_PyInterpreterState_GetIDObject(PyInterpreterState *interp)")
|
||||
if "int64_t id;" not in get_id or "int64_t id =" in get_id:
|
||||
fail("_PyInterpreterState_GetIDObject ID local must be declared at function entry")
|
||||
ordered(get_id, (
|
||||
"if (_PyInterpreterState_IDInitref(interp) != 0)",
|
||||
"id = PyInterpreterState_GetID(interp);",
|
||||
"if (id < 0)",
|
||||
"return (PyObject *)newinterpid(&_PyInterpreterID_Type, id, 0);",
|
||||
), "_PyInterpreterState_GetIDObject must preserve initref and ID validation order")
|
||||
|
||||
print("interpreteridobject C89 declarations and ownership order: PASS")
|
||||
@@ -0,0 +1,98 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Guard C89 declaration placement and reference order in iterobject.c."""
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from typing import NoReturn
|
||||
|
||||
|
||||
def fail(message: str) -> NoReturn:
|
||||
print(message, file=sys.stderr)
|
||||
raise SystemExit(1)
|
||||
|
||||
|
||||
def extract_function(source: str, signature: str) -> str:
|
||||
start = source.find(signature)
|
||||
if start < 0:
|
||||
fail(f"missing function signature: {signature}")
|
||||
brace = source.find("{", start + len(signature))
|
||||
if brace < 0:
|
||||
fail("missing function body")
|
||||
depth = 0
|
||||
for end in range(brace, len(source)):
|
||||
if source[end] == "{":
|
||||
depth += 1
|
||||
elif source[end] == "}":
|
||||
depth -= 1
|
||||
if depth == 0:
|
||||
return source[brace + 1:end]
|
||||
fail("unterminated function body")
|
||||
|
||||
|
||||
def ordered(body: str, tokens: tuple[str, ...], message: str) -> None:
|
||||
positions = [body.find(token) for token in tokens]
|
||||
if any(position < 0 for position in positions) or positions != sorted(positions):
|
||||
fail(message)
|
||||
|
||||
|
||||
source = Path(sys.argv[1]).read_text(encoding="utf-8")
|
||||
|
||||
getiter = extract_function(source, "anextawaitable_getiter(anextawaitableobject *obj)")
|
||||
for declaration in ("PyObject *awaitable;", "unaryfunc getter;", "PyObject *new_awaitable;"):
|
||||
if declaration not in getiter:
|
||||
fail("anextawaitable_getiter local must be declared before use: " + declaration)
|
||||
for late in ("PyObject *awaitable =", "unaryfunc getter =", "PyObject *new_awaitable ="):
|
||||
if late in getiter:
|
||||
fail("anextawaitable_getiter must not initialize a local in a late declaration: " + late)
|
||||
branch = getiter.split("if (Py_TYPE(awaitable)->tp_iternext == NULL) {", 1)[1]
|
||||
if branch.find("unaryfunc getter;") > branch.find("getter = Py_TYPE(awaitable)->tp_as_async->am_await;"):
|
||||
fail("anextawaitable_getiter branch locals must be declared before statements")
|
||||
if branch.find("PyObject *new_awaitable;") > branch.find("getter = Py_TYPE(awaitable)->tp_as_async->am_await;"):
|
||||
fail("anextawaitable_getiter branch locals must be declared before statements")
|
||||
ordered(getiter, (
|
||||
"assert(obj->wrapped != NULL);",
|
||||
"awaitable = _PyCoro_GetAwaitableIter(obj->wrapped);",
|
||||
"if (awaitable == NULL)",
|
||||
"if (Py_TYPE(awaitable)->tp_iternext == NULL)",
|
||||
"getter = Py_TYPE(awaitable)->tp_as_async->am_await;",
|
||||
"new_awaitable = getter(awaitable);",
|
||||
"if (new_awaitable == NULL)",
|
||||
"Py_SETREF(awaitable, new_awaitable);",
|
||||
"if (!PyIter_Check(awaitable))",
|
||||
"return awaitable;",
|
||||
), "anextawaitable_getiter must preserve awaitable conversion and cleanup order")
|
||||
|
||||
iternext = extract_function(source, "anextawaitable_iternext(anextawaitableobject *obj)")
|
||||
for declaration in ("PyObject *awaitable;", "PyObject *result;"):
|
||||
if declaration not in iternext:
|
||||
fail("anextawaitable_iternext local must be declared at function entry: " + declaration)
|
||||
if "PyObject *result =" in iternext:
|
||||
fail("anextawaitable_iternext result must not be declared after the NULL guard")
|
||||
ordered(iternext, (
|
||||
"awaitable = anextawaitable_getiter(obj);",
|
||||
"if (awaitable == NULL)",
|
||||
"result = (*Py_TYPE(awaitable)->tp_iternext)(awaitable);",
|
||||
"Py_DECREF(awaitable);",
|
||||
"if (result != NULL)",
|
||||
"if (PyErr_ExceptionMatches(PyExc_StopAsyncIteration))",
|
||||
"_PyGen_SetStopIterationValue(obj->default_value);",
|
||||
"\n return NULL;",
|
||||
), "anextawaitable_iternext must preserve call, decref, and StopAsyncIteration handling order")
|
||||
|
||||
proxy = extract_function(source, "anextawaitable_proxy(anextawaitableobject *obj, char *meth, PyObject *arg)")
|
||||
for declaration in ("PyObject *awaitable;", "PyObject *ret;"):
|
||||
if declaration not in proxy:
|
||||
fail("anextawaitable_proxy local must be declared at function entry: " + declaration)
|
||||
if "PyObject *ret =" in proxy:
|
||||
fail("anextawaitable_proxy result must not be declared after the NULL guard")
|
||||
ordered(proxy, (
|
||||
"awaitable = anextawaitable_getiter(obj);",
|
||||
"if (awaitable == NULL)",
|
||||
"ret = PyObject_CallMethod(awaitable, meth, \"O\", arg);",
|
||||
"Py_DECREF(awaitable);",
|
||||
"if (ret != NULL)",
|
||||
"if (PyErr_ExceptionMatches(PyExc_StopAsyncIteration))",
|
||||
"_PyGen_SetStopIterationValue(obj->default_value);",
|
||||
"\n return NULL;",
|
||||
), "anextawaitable_proxy must preserve call, decref, and exception handling order")
|
||||
|
||||
print("iterobject anextawaitable C89 declarations and reference order: PASS")
|
||||
@@ -0,0 +1,306 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Guard C89 list-allocation declarations and ownership order in listobject.c."""
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from typing import NoReturn
|
||||
|
||||
|
||||
def fail(message: str) -> NoReturn:
|
||||
print(message, file=sys.stderr)
|
||||
raise SystemExit(1)
|
||||
|
||||
|
||||
def extract_function(source: str, signature: str) -> str:
|
||||
start = source.find(signature)
|
||||
if start < 0:
|
||||
fail(f"missing function signature: {signature}")
|
||||
brace = source.find("{", start + len(signature))
|
||||
if brace < 0:
|
||||
fail("missing function body")
|
||||
depth = 0
|
||||
for end in range(brace, len(source)):
|
||||
if source[end] == "{":
|
||||
depth += 1
|
||||
elif source[end] == "}":
|
||||
depth -= 1
|
||||
if depth == 0:
|
||||
return source[brace + 1:end]
|
||||
fail("unterminated function body")
|
||||
|
||||
|
||||
def ordered(body: str, tokens: tuple[str, ...], message: str) -> None:
|
||||
positions = [body.find(token) for token in tokens]
|
||||
if any(position < 0 for position in positions) or positions != sorted(positions):
|
||||
fail(message)
|
||||
|
||||
|
||||
source = Path(sys.argv[1]).read_text(encoding="utf-8")
|
||||
|
||||
preallocate = extract_function(source, "list_preallocate_exact(PyListObject *self, Py_ssize_t size)")
|
||||
if "PyObject **items;" not in preallocate or "PyObject **items =" in preallocate:
|
||||
fail("list_preallocate_exact items local must be declared before size adjustment")
|
||||
ordered(preallocate, (
|
||||
"assert(self->ob_item == NULL);",
|
||||
"assert(size > 0);",
|
||||
"size = (size + 1) & ~(size_t)1;",
|
||||
"items = PyMem_New(PyObject*, size);",
|
||||
"if (items == NULL)",
|
||||
"PyErr_NoMemory();",
|
||||
"self->ob_item = items;",
|
||||
"self->allocated = size;",
|
||||
"return 0;",
|
||||
), "list_preallocate_exact must preserve validation, rounding, allocation, and assignment order")
|
||||
|
||||
list_new = extract_function(source, "PyList_New(Py_ssize_t size)")
|
||||
entry = "PyListObject *op;\n#if PyList_MAXFREELIST > 0\n struct _Py_list_state *state;\n#endif"
|
||||
if entry not in list_new:
|
||||
fail("PyList_New freelist state must be declared at function entry under its feature guard")
|
||||
if "struct _Py_list_state *state =" in list_new:
|
||||
fail("PyList_New must not initialize state in a late declaration")
|
||||
ordered(list_new, (
|
||||
"if (size < 0)",
|
||||
"PyErr_BadInternalCall();",
|
||||
"return NULL;",
|
||||
"state = get_list_state();",
|
||||
"assert(state->numfree != -1);",
|
||||
"if (PyList_MAXFREELIST && state->numfree)",
|
||||
"state->numfree--;",
|
||||
"op = state->free_list[state->numfree];",
|
||||
"_Py_NewReference((PyObject *)op);",
|
||||
"op = PyObject_GC_New(PyListObject, &PyList_Type);",
|
||||
"if (op == NULL)",
|
||||
"if (size <= 0)",
|
||||
"op->ob_item = (PyObject **) PyMem_Calloc(size, sizeof(PyObject *));",
|
||||
"Py_DECREF(op);",
|
||||
"Py_SET_SIZE(op, size);",
|
||||
"op->allocated = size;",
|
||||
"_PyObject_GC_TRACK(op);",
|
||||
"return (PyObject *) op;",
|
||||
), "PyList_New must preserve validation, freelist, allocation, cleanup, and tracking order")
|
||||
|
||||
new_prealloc = extract_function(source, "list_new_prealloc(Py_ssize_t size)")
|
||||
if "PyListObject *op;" not in new_prealloc or "PyListObject *op =" in new_prealloc:
|
||||
fail("list_new_prealloc object local must be declared before validation")
|
||||
ordered(new_prealloc, (
|
||||
"assert(size > 0);",
|
||||
"op = (PyListObject *) PyList_New(0);",
|
||||
"if (op == NULL)",
|
||||
"assert(op->ob_item == NULL);",
|
||||
"op->ob_item = PyMem_New(PyObject *, size);",
|
||||
"if (op->ob_item == NULL)",
|
||||
"Py_DECREF(op);",
|
||||
"op->allocated = size;",
|
||||
"return (PyObject *) op;",
|
||||
), "list_new_prealloc must preserve validation, allocation, cleanup, and assignment order")
|
||||
|
||||
list_dealloc = extract_function(source, "list_dealloc(PyListObject *op)")
|
||||
entry = "Py_ssize_t i;\n#if PyList_MAXFREELIST > 0\n struct _Py_list_state *state;\n#endif"
|
||||
if entry not in list_dealloc:
|
||||
fail("list_dealloc freelist state must be declared before trashcan/deallocation operations")
|
||||
if "struct _Py_list_state *state =" in list_dealloc:
|
||||
fail("list_dealloc must not initialize state after item cleanup")
|
||||
ordered(list_dealloc, (
|
||||
"Py_ssize_t i;",
|
||||
"PyObject_GC_UnTrack(op);",
|
||||
"Py_TRASHCAN_BEGIN(op, list_dealloc)",
|
||||
"i = Py_SIZE(op);",
|
||||
"Py_XDECREF(op->ob_item[i]);",
|
||||
"PyMem_Free(op->ob_item);",
|
||||
"state = get_list_state();",
|
||||
"assert(state->numfree != -1);",
|
||||
"if (state->numfree < PyList_MAXFREELIST && PyList_CheckExact(op))",
|
||||
"state->free_list[state->numfree++] = op;",
|
||||
"Py_TYPE(op)->tp_free((PyObject *)op);",
|
||||
"Py_TRASHCAN_END",
|
||||
), "list_dealloc must preserve untrack, item cleanup, freelist, and free order")
|
||||
|
||||
list_repeat = extract_function(source, "list_repeat(PyListObject *a, Py_ssize_t n)")
|
||||
for declaration in ("PyObject **dest;", "PyObject **dest_end;", "PyObject *elem;", "PyObject **src;", "PyObject **src_end;"):
|
||||
if declaration not in list_repeat:
|
||||
fail("list_repeat local must be declared at function entry: " + declaration)
|
||||
for late in ("PyObject **dest =", "PyObject **dest_end =", "PyObject *elem =", "PyObject **src =", "PyObject **src_end ="):
|
||||
if late in list_repeat:
|
||||
fail("list_repeat must not initialize a local in a late declaration: " + late)
|
||||
ordered(list_repeat, (
|
||||
"if (n < 0)",
|
||||
"if (n > 0 && Py_SIZE(a) > PY_SSIZE_T_MAX / n)",
|
||||
"size = Py_SIZE(a) * n;",
|
||||
"if (size == 0)",
|
||||
"np = (PyListObject *) list_new_prealloc(size);",
|
||||
"if (np == NULL)",
|
||||
"dest = np->ob_item;",
|
||||
"dest_end = dest + size;",
|
||||
"if (Py_SIZE(a) == 1)",
|
||||
"elem = a->ob_item[0];",
|
||||
"Py_SET_REFCNT(elem, Py_REFCNT(elem) + n);",
|
||||
"src = a->ob_item;",
|
||||
"src_end = src + Py_SIZE(a);",
|
||||
"Py_SET_REFCNT(*src, Py_REFCNT(*src) + n);",
|
||||
"src = np->ob_item;",
|
||||
"Py_SET_SIZE(np, size);",
|
||||
"return (PyObject *) np;",
|
||||
), "list_repeat must preserve size, pointer, and reference-count update order")
|
||||
|
||||
powerloop = extract_function(source, "powerloop(Py_ssize_t s1, Py_ssize_t n1, Py_ssize_t n2, Py_ssize_t n)")
|
||||
for declaration in ("Py_ssize_t a;", "Py_ssize_t b;"):
|
||||
if declaration not in powerloop:
|
||||
fail("powerloop midpoint local must be declared at function entry: " + declaration)
|
||||
if "Py_ssize_t a =" in powerloop or "Py_ssize_t b =" in powerloop:
|
||||
fail("powerloop midpoint locals must not be declared after assertions")
|
||||
ordered(powerloop, (
|
||||
"int result = 0;",
|
||||
"assert(s1 >= 0);",
|
||||
"assert(n1 > 0 && n2 > 0);",
|
||||
"assert(s1 + n1 + n2 <= n);",
|
||||
"a = 2 * s1 + n1;",
|
||||
"b = a + n1 + n2;",
|
||||
"for (;;) {",
|
||||
"++result;",
|
||||
"return result;",
|
||||
), "powerloop must preserve midpoint calculations and iteration order")
|
||||
|
||||
new_run = extract_function(source, "found_new_run(MergeState *ms, Py_ssize_t n2)")
|
||||
branch = new_run.split("if (ms->n) {", 1)[1].split("\n }", 1)[0]
|
||||
for declaration in ("struct s_slice *p;", "Py_ssize_t s1;", "Py_ssize_t n1;", "int power;"):
|
||||
if declaration not in branch:
|
||||
fail("found_new_run locals must be declared at the run branch entry: " + declaration)
|
||||
for late in ("struct s_slice *p =", "Py_ssize_t s1 =", "Py_ssize_t n1 =", "int power ="):
|
||||
if late in branch:
|
||||
fail("found_new_run must not initialize a local after the branch assertion: " + late)
|
||||
ordered(branch, (
|
||||
"assert(ms->n > 0);",
|
||||
"p = ms->pending;",
|
||||
"s1 = p[ms->n - 1].base.keys - ms->basekeys;",
|
||||
"n1 = p[ms->n - 1].len;",
|
||||
"power = powerloop(s1, n1, n2, ms->listlen);",
|
||||
"while (ms->n > 1 && p[ms->n - 2].power > power)",
|
||||
"p[ms->n - 1].power = power;",
|
||||
), "found_new_run must preserve assertion, run lookup, power computation, and merge order")
|
||||
|
||||
sort_impl = extract_function(source, "list_sort_impl(PyListObject *self, PyObject *keyfunc, int reverse)")
|
||||
if "PyObject *key;" not in sort_impl or "PyObject *key =" in sort_impl:
|
||||
fail("list_sort_impl key local must be declared before sort-time checks")
|
||||
ordered(sort_impl, (
|
||||
"if (keyfunc == Py_None)",
|
||||
"saved_ob_size = Py_SIZE(self);",
|
||||
"Py_SET_SIZE(self, 0);",
|
||||
"if (keyfunc == NULL)",
|
||||
"if (saved_ob_size > 1)",
|
||||
"for (i=0; i < saved_ob_size; i++)",
|
||||
"key = (keys_are_in_tuples ?",
|
||||
"if (!Py_IS_TYPE(key, key_type))",
|
||||
), "list_sort_impl must preserve key preparation and list mutation order")
|
||||
|
||||
for name, signature in (
|
||||
("list_index_impl", "list_index_impl(PyListObject *self, PyObject *value, Py_ssize_t start,\n Py_ssize_t stop)"),
|
||||
("list_count", "list_count(PyListObject *self, PyObject *value)"),
|
||||
("list_remove", "list_remove(PyListObject *self, PyObject *value)"),
|
||||
):
|
||||
body = extract_function(source, signature)
|
||||
loop = body.split("for (i =", 1)[1].split("\n }", 1)[0]
|
||||
if "int cmp;" not in loop or "int cmp =" in loop:
|
||||
fail(name + " comparison result must be declared before incref/comparison")
|
||||
if "cmp = PyObject_RichCompareBool(obj, value, Py_EQ);" not in loop:
|
||||
fail(name + " must assign comparison result after acquiring obj reference")
|
||||
ordered(loop, (
|
||||
"PyObject *obj = self->ob_item[i];",
|
||||
"int cmp;",
|
||||
"Py_INCREF(obj);",
|
||||
"cmp = PyObject_RichCompareBool(obj, value, Py_EQ);",
|
||||
"Py_DECREF(obj);",
|
||||
), name + " must preserve item reference and comparison ordering")
|
||||
|
||||
richcompare = extract_function(source, "list_richcompare(PyObject *v, PyObject *w, int op)")
|
||||
loop = richcompare.split("for (i =", 1)[1].split("\n }", 1)[0]
|
||||
for declaration in ("PyObject *vitem;", "PyObject *witem;", "int k;"):
|
||||
if declaration not in loop:
|
||||
fail("list_richcompare loop local must be declared before item comparisons: " + declaration)
|
||||
ordered(loop, (
|
||||
"vitem = vl->ob_item[i];",
|
||||
"witem = wl->ob_item[i];",
|
||||
"if (vitem == witem)",
|
||||
"Py_INCREF(vitem);",
|
||||
"Py_INCREF(witem);",
|
||||
"k = PyObject_RichCompareBool(vitem, witem, Py_EQ);",
|
||||
"Py_DECREF(vitem);",
|
||||
"Py_DECREF(witem);",
|
||||
), "list_richcompare must preserve item comparison/reference ordering")
|
||||
if "PyObject *vitem;" not in richcompare or "PyObject *result;" not in richcompare:
|
||||
fail("list_richcompare final comparison locals must be declared in its final block")
|
||||
|
||||
vectorcall = extract_function(source, "list_vectorcall(PyObject *type, PyObject * const*args,\n size_t nargsf, PyObject *kwnames)")
|
||||
for declaration in ("Py_ssize_t nargs;", "PyObject *list;"):
|
||||
if declaration not in vectorcall:
|
||||
fail("list_vectorcall local must be declared at function entry: " + declaration)
|
||||
ordered(vectorcall, (
|
||||
"if (!_PyArg_NoKwnames(\"list\", kwnames))",
|
||||
"nargs = PyVectorcall_NARGS(nargsf);",
|
||||
"if (!_PyArg_CheckPositional(\"list\", nargs, 0, 1))",
|
||||
"list = PyType_GenericAlloc(_PyType_CAST(type), 0);",
|
||||
"if (list == NULL)",
|
||||
"if (nargs)",
|
||||
"return list;",
|
||||
), "list_vectorcall must preserve argument checks and allocation order")
|
||||
|
||||
ass_subscript = extract_function(source, "list_ass_subscript(PyListObject* self, PyObject* item, PyObject* value)")
|
||||
assign_branch = ass_subscript.split("else {\n /* assign slice */", 1)[1]
|
||||
if "Py_ssize_t slicelength;" not in assign_branch or "Py_ssize_t slicelength =" in assign_branch:
|
||||
fail("extended-slice assignment length must be declared before sequence lookup")
|
||||
ordered(assign_branch, (
|
||||
"Py_ssize_t slicelength;",
|
||||
"if (self == (PyListObject*)value)",
|
||||
"seq = PySequence_Fast(value,",
|
||||
"if (!seq)",
|
||||
"slicelength = adjust_slice_indexes(self, &start, &stop,",
|
||||
"if (step == 1)",
|
||||
"if (PySequence_Fast_GET_SIZE(seq) != slicelength)",
|
||||
), "extended-slice assignment must preserve input validation and sequence handling order")
|
||||
|
||||
type_start = source.find("PyTypeObject PyList_Type = {")
|
||||
if type_start < 0:
|
||||
fail("missing PyList_Type initializer")
|
||||
type_end = source.find("\n};", type_start)
|
||||
if type_end < 0:
|
||||
fail("unterminated PyList_Type initializer")
|
||||
type_init = source[type_start:type_end]
|
||||
if ".tp_vectorcall" in type_init:
|
||||
fail("PyList_Type vectorcall must use a VC6-compatible positional initializer")
|
||||
ordered(type_init, (
|
||||
"PyObject_GC_Del,",
|
||||
"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 */",
|
||||
"list_vectorcall, /* tp_vectorcall */",
|
||||
), "PyList_Type positional tail must match PyTypeObject field order")
|
||||
|
||||
reduce_signature = "listiter_reduce_general(void *_it, int forward)"
|
||||
reduce_start = source.rfind(reduce_signature)
|
||||
if reduce_start < 0:
|
||||
fail("missing listiter_reduce_general definition")
|
||||
reduce_general = extract_function(source[reduce_start:], reduce_signature)
|
||||
for declaration in ("listiterobject *it;", "listreviterobject *rev_it;"):
|
||||
if declaration not in reduce_general:
|
||||
fail("listiter_reduce_general iterator local must be declared at function entry: " + declaration)
|
||||
ordered(reduce_general, (
|
||||
"if (forward)",
|
||||
"iter = _PyEval_GetBuiltin(&_Py_ID(iter));",
|
||||
"if (!iter)",
|
||||
"it = (listiterobject *)_it;",
|
||||
"} else {",
|
||||
"iter = _PyEval_GetBuiltin(&_Py_ID(reversed));",
|
||||
"rev_it = (listreviterobject *)_it;",
|
||||
), "listiter_reduce_general must preserve builtin lookup before iterator access")
|
||||
forward_guard = reduce_general.find("if (!iter)")
|
||||
reverse_guard = reduce_general.rfind("if (!iter)")
|
||||
if not (forward_guard < reduce_general.find("it = (listiterobject *)_it;") <
|
||||
reduce_general.find("} else {") <
|
||||
reduce_general.find("iter = _PyEval_GetBuiltin(&_Py_ID(reversed));") <
|
||||
reverse_guard < reduce_general.find("rev_it = (listreviterobject *)_it;")):
|
||||
fail("listiter_reduce_general must check both builtin lookups before touching iterators")
|
||||
print("listobject C89 declarations and allocation order: PASS")
|
||||
@@ -0,0 +1,339 @@
|
||||
#!/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<body>.*?)\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<body>.*?)\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<body>.*?)\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<body>.*?)\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<body>.*?)^ \} 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<parameter>[^)]*)\)\s*\{(?P<body>.*?)\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<body>.*?)\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<body>.*?)\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<body>.*?)\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<body>.*?)\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<body>.*?)\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<body>.*?)\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<body>.*?)\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<body>.*?)\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<body>.*?)\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<body>.*?)\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<body>.*?)\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<body>.*?)\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<body>.*?)\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<body>.*?)\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<body>.*?)\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")
|
||||
@@ -0,0 +1,100 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Focused Visual C++ 6.0 C89 regression checks for memoryobject.c."""
|
||||
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
source = Path(sys.argv[1]).read_text()
|
||||
|
||||
match = re.search(
|
||||
r"static int\s+copy_single\(PyMemoryViewObject \*self, const Py_buffer \*dest, const Py_buffer \*src\)\s*\{(?P<body>.*?)\n\}",
|
||||
source,
|
||||
re.S,
|
||||
)
|
||||
assert match, "copy_single definition not found"
|
||||
body = match.group("body")
|
||||
expected = """
|
||||
char *mem;
|
||||
CHECK_RELEASED_INT_AGAIN(self);
|
||||
mem = NULL;
|
||||
"""
|
||||
assert body.split()[:len(expected.split())] == expected.split(), (
|
||||
"copy_single must declare mem before the release-check macro while keeping its initialization after the check"
|
||||
)
|
||||
assert "char *mem =" not in body, "copy_single must not initialize mem before the release check"
|
||||
print("memoryobject copy_single C89 local and release-check order: PASS")
|
||||
|
||||
assert not re.search(r"\blong\s+long\b", source), "memoryobject must use PY_LONG_LONG spellings supported by VC6"
|
||||
assert re.search(r"\bPY_LONG_LONG\b", source), "memoryobject must retain native 64-bit format support through PY_LONG_LONG"
|
||||
assert not re.search(r"\b_Bool\b", source), "memoryobject must use the VC6-compatible one-byte bool representation"
|
||||
assert "case '?': UNPACK_SINGLE(ld, ptr, unsigned char); goto convert_bool;" in source, "memoryobject bool unpacking must preserve nonzero-to-bool conversion"
|
||||
assert "PACK_SINGLE(ptr, ld, unsigned char);" in source, "memoryobject bool packing must use a one-byte type after PyObject_IsTrue normalization"
|
||||
print("memoryobject VC6 64-bit and bool format spellings: PASS")
|
||||
|
||||
match = re.search(r"static PyObject \*\s*memoryiter_next\(memoryiterobject \*it\)\s*\{(?P<body>.*?)\n\}", source, re.S)
|
||||
assert match, "memoryiter_next definition not found"
|
||||
body = match.group("body")
|
||||
assert "Py_buffer *view =" not in body and "char *ptr =" not in body, "memoryiter_next must not declare locals after CHECK_RELEASED"
|
||||
assert "if (it->it_index < it->it_length) {\n Py_buffer *view;\n char *ptr;\n\n CHECK_RELEASED(seq);\n view = &(seq->view);\n ptr = (char *)seq->view.buf;" in body, "memoryiter_next must declare conditional locals before release checks and preserve pointer setup order"
|
||||
print("memoryobject memoryiter_next C89 locals and release-check order: PASS")
|
||||
|
||||
match = re.search(r"static PyObject \*\s*memory_iter\(PyObject \*seq\)\s*\{(?P<body>.*?)\n\}", source, re.S)
|
||||
assert match, "memory_iter definition not found"
|
||||
body = match.group("body")
|
||||
expected = """
|
||||
PyMemoryViewObject *obj;
|
||||
int ndims;
|
||||
const char *fmt;
|
||||
memoryiterobject *it;
|
||||
if (!PyMemoryView_Check(seq))
|
||||
"""
|
||||
assert body.split()[:len(expected.split())] == expected.split(), "memory_iter must declare locals before validation"
|
||||
ordered = ("if (!PyMemoryView_Check(seq))", "obj = (PyMemoryViewObject *)seq;", "ndims = obj->view.ndim;", "fmt = adjust_fmt(&obj->view);", "it = PyObject_GC_New(memoryiterobject, &_PyMemoryIter_Type);")
|
||||
positions = [body.find(token) for token in ordered]
|
||||
assert all(position >= 0 for position in positions) and positions == sorted(positions), "memory_iter must preserve validation, type/shape setup, format adjustment, and allocation order"
|
||||
assert not re.search(r"\b(?:PyMemoryViewObject|int|const char|memoryiterobject)\s+\*?\w+\s*=", body), "memory_iter must not retain late initialized declarations"
|
||||
print("memoryobject memory_iter C89 locals and iterator allocation order: PASS")
|
||||
|
||||
match = re.search(r"PyTypeObject _PyMemoryIter_Type = \{(?P<body>.*?)\n\};", source, re.S)
|
||||
assert match, "_PyMemoryIter_Type initializer not found"
|
||||
body = match.group("body")
|
||||
assert ".tp_" not in body, "_PyMemoryIter_Type must use positional fields instead of C99 designated initializers"
|
||||
expected = (
|
||||
"PyVarObject_HEAD_INIT(&PyType_Type, 0)",
|
||||
'"memory_iterator"',
|
||||
"sizeof(memoryiterobject)",
|
||||
"0,",
|
||||
"(destructor)memoryiter_dealloc",
|
||||
"0,",
|
||||
"0,",
|
||||
"0,",
|
||||
"0,",
|
||||
"0,",
|
||||
"0,",
|
||||
"0,",
|
||||
"0,",
|
||||
"0,",
|
||||
"0,",
|
||||
"0,",
|
||||
"PyObject_GenericGetAttr",
|
||||
"0,",
|
||||
"0,",
|
||||
"Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC",
|
||||
"0,",
|
||||
"(traverseproc)memoryiter_traverse",
|
||||
"0,",
|
||||
"0,",
|
||||
"0,",
|
||||
"PyObject_SelfIter",
|
||||
"(iternextfunc)memoryiter_next",
|
||||
)
|
||||
positions = []
|
||||
cursor = 0
|
||||
for token in expected:
|
||||
position = body.find(token, cursor)
|
||||
positions.append(position)
|
||||
if position >= 0:
|
||||
cursor = position + len(token)
|
||||
assert all(position >= 0 for position in positions), "_PyMemoryIter_Type positional initializer must preserve all configured field slots"
|
||||
print("memoryobject _PyMemoryIter_Type C89 positional initializer: PASS")
|
||||
@@ -0,0 +1,132 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Focused Visual C++ 6.0 C89 regression checks for methodobject.c."""
|
||||
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
source = Path(sys.argv[1]).read_text()
|
||||
|
||||
match = re.search(
|
||||
r"PyObject \*\s*PyCMethod_New\(PyMethodDef \*ml, PyObject \*self, PyObject \*module, PyTypeObject \*cls\)\s*\{(?P<body>.*?)\n\}",
|
||||
source,
|
||||
re.S,
|
||||
)
|
||||
assert match, "PyCMethod_New definition not found"
|
||||
body = match.group("body")
|
||||
declarations = ("vectorcallfunc vectorcall;", "PyCFunctionObject *op;", "PyCMethodObject *om;", "switch (ml->ml_flags")
|
||||
declaration_positions = [body.find(token) for token in declarations]
|
||||
assert all(position >= 0 for position in declaration_positions) and declaration_positions == sorted(declaration_positions), (
|
||||
"PyCMethod_New must declare op and om at function entry before its dispatch switch"
|
||||
)
|
||||
ordered = (
|
||||
"switch (ml->ml_flags",
|
||||
"op = NULL;",
|
||||
"if (ml->ml_flags & METH_METHOD)",
|
||||
"om = PyObject_GC_New(PyCMethodObject, &PyCMethod_Type);",
|
||||
"op = PyObject_GC_New(PyCFunctionObject, &PyCFunction_Type);",
|
||||
"op->m_weakreflist = NULL;",
|
||||
)
|
||||
positions = [body.find(token) for token in ordered]
|
||||
assert all(position >= 0 for position in positions) and positions == sorted(positions), (
|
||||
"PyCMethod_New must preserve flag dispatch, null initialization, allocation, and field setup order"
|
||||
)
|
||||
assert "PyCFunctionObject *op =" not in body and "PyCMethodObject *om =" not in body, (
|
||||
"PyCMethod_New must not introduce late C89 declarations"
|
||||
)
|
||||
print("methodobject PyCMethod_New C89 declarations and allocation order: PASS")
|
||||
|
||||
match = re.search(r"PyTypeObject PyCMethod_Type = \{(?P<body>.*?)\n\};", source, re.S)
|
||||
assert match, "PyCMethod_Type initializer not found"
|
||||
body = match.group("body")
|
||||
assert ".tp_" not in body, "PyCMethod_Type must use positional fields instead of C99 designated initializers"
|
||||
expected = (
|
||||
"PyVarObject_HEAD_INIT(&PyType_Type, 0)",
|
||||
'"builtin_method"',
|
||||
"sizeof(PyCMethodObject)",
|
||||
) + ("0,",) * 27 + ("&PyCFunction_Type",)
|
||||
positions = []
|
||||
cursor = 0
|
||||
for token in expected:
|
||||
position = body.find(token, cursor)
|
||||
positions.append(position)
|
||||
if position >= 0:
|
||||
cursor = position + len(token)
|
||||
assert all(position >= 0 for position in positions), "PyCMethod_Type positional initializer must preserve the base-type slot"
|
||||
print("methodobject PyCMethod_Type C89 positional initializer: PASS")
|
||||
|
||||
|
||||
def function_body(name):
|
||||
found = re.search(
|
||||
r"static PyObject \*\s*" + re.escape(name) + r"\s*\([^;]*\)\s*\{(?P<body>.*?)\n\}",
|
||||
source,
|
||||
re.S,
|
||||
)
|
||||
assert found, name + " definition not found"
|
||||
return found.group("body")
|
||||
|
||||
|
||||
checks = {
|
||||
"cfunction_vectorcall_FASTCALL": (
|
||||
"PyThreadState *tstate;",
|
||||
"Py_ssize_t nargs;",
|
||||
"_PyCFunctionFast meth;",
|
||||
"PyObject *result;",
|
||||
"tstate = _PyThreadState_GET();",
|
||||
"nargs = PyVectorcall_NARGS(nargsf);",
|
||||
"meth = (_PyCFunctionFast)",
|
||||
"result = meth(",
|
||||
),
|
||||
"cfunction_vectorcall_FASTCALL_KEYWORDS": (
|
||||
"PyThreadState *tstate;",
|
||||
"Py_ssize_t nargs;",
|
||||
"_PyCFunctionFastWithKeywords meth;",
|
||||
"PyObject *result;",
|
||||
"tstate = _PyThreadState_GET();",
|
||||
"nargs = PyVectorcall_NARGS(nargsf);",
|
||||
"meth = (_PyCFunctionFastWithKeywords)",
|
||||
"result = meth(",
|
||||
),
|
||||
"cfunction_vectorcall_NOARGS": (
|
||||
"Py_ssize_t nargs;",
|
||||
"PyCFunction meth;",
|
||||
"PyObject *result;",
|
||||
"PyThreadState *tstate = _PyThreadState_GET();",
|
||||
"if (cfunction_check_kwargs(",
|
||||
"nargs = PyVectorcall_NARGS(nargsf);",
|
||||
"meth = (PyCFunction)cfunction_enter_call(",
|
||||
"result = _PyCFunction_TrampolineCall(",
|
||||
),
|
||||
"cfunction_vectorcall_O": (
|
||||
"Py_ssize_t nargs;",
|
||||
"PyCFunction meth;",
|
||||
"PyObject *result;",
|
||||
"PyThreadState *tstate = _PyThreadState_GET();",
|
||||
"if (cfunction_check_kwargs(",
|
||||
"nargs = PyVectorcall_NARGS(nargsf);",
|
||||
"meth = (PyCFunction)cfunction_enter_call(",
|
||||
"result = _PyCFunction_TrampolineCall(",
|
||||
),
|
||||
"cfunction_call": (
|
||||
"PyThreadState *tstate;",
|
||||
"int flags;",
|
||||
"PyCFunction meth;",
|
||||
"PyObject *self;",
|
||||
"PyObject *result;",
|
||||
"assert(kwargs == NULL || PyDict_Check(kwargs));",
|
||||
"tstate = _PyThreadState_GET();",
|
||||
"flags = PyCFunction_GET_FLAGS(func);",
|
||||
"meth = PyCFunction_GET_FUNCTION(func);",
|
||||
"self = PyCFunction_GET_SELF(func);",
|
||||
),
|
||||
}
|
||||
for name, tokens in checks.items():
|
||||
body = function_body(name)
|
||||
positions = [body.find(token) for token in tokens]
|
||||
assert all(position >= 0 for position in positions) and positions == sorted(positions), (
|
||||
name + " must keep declarations before statements and preserve evaluation order"
|
||||
)
|
||||
for declaration in tokens[:5]:
|
||||
if " = " not in declaration and declaration.endswith(";"):
|
||||
assert declaration in body, name + " is missing an opening local declaration"
|
||||
print("methodobject vectorcall and call paths C89 declarations and evaluation order: PASS")
|
||||
@@ -0,0 +1,69 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Focused Visual C++ 6.0 C89 regression checks for moduleobject.c."""
|
||||
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
source = Path(sys.argv[1]).read_text()
|
||||
|
||||
|
||||
def function_body(name):
|
||||
match = re.search(
|
||||
re.escape(name) + r"\s*\([^;]*\)\s*\{(?P<body>.*?)\n\}",
|
||||
source,
|
||||
re.S,
|
||||
)
|
||||
assert match, name + " definition not found"
|
||||
return match.group("body")
|
||||
|
||||
|
||||
def ordered(body, tokens, message):
|
||||
positions = [body.find(token) for token in tokens]
|
||||
assert all(position >= 0 for position in positions) and positions == sorted(positions), message
|
||||
|
||||
|
||||
body = function_body("_PyModule_IsExtension")
|
||||
ordered(body, (
|
||||
"PyModuleObject *module;",
|
||||
"PyModuleDef *def;",
|
||||
"if (!PyModule_Check(obj))",
|
||||
"module = (PyModuleObject*)obj;",
|
||||
"def = module->md_def;",
|
||||
), "_PyModule_IsExtension must declare its locals before validation and preserve lookup order")
|
||||
assert "PyModuleObject *module =" not in body and "PyModuleDef *def =" not in body
|
||||
print("moduleobject _PyModule_IsExtension C89 locals and lookup order: PASS")
|
||||
|
||||
body = function_body("_PyModuleSpec_IsUninitializedSubmodule")
|
||||
ordered(body, (
|
||||
"PyObject *value;",
|
||||
"int is_uninitialized;",
|
||||
"if (spec == NULL)",
|
||||
"value = PyObject_GetAttr(spec, &_Py_ID(_uninitialized_submodules));",
|
||||
"is_uninitialized = PySequence_Contains(value, name);",
|
||||
"Py_DECREF(value);",
|
||||
), "_PyModuleSpec_IsUninitializedSubmodule must hoist declarations while preserving reference order")
|
||||
assert "PyObject *value =" not in body and "int is_uninitialized =" not in body
|
||||
print("moduleobject uninitialized-submodule C89 locals and reference order: PASS")
|
||||
|
||||
body = function_body("module_getattro")
|
||||
ordered(body, (
|
||||
"PyObject *attr, *mod_name, *getattr, *spec;",
|
||||
"attr = PyObject_GenericGetAttr((PyObject *)m, name);",
|
||||
"Py_INCREF(mod_name);",
|
||||
"spec = PyDict_GetItemWithError(m->md_dict, &_Py_ID(__spec__));",
|
||||
), "module_getattro must hoist spec without changing lookup timing")
|
||||
assert "PyObject *spec =" not in body
|
||||
print("moduleobject module_getattro C89 local and lookup order: PASS")
|
||||
|
||||
body = function_body("module_get_annotations")
|
||||
ordered(body, (
|
||||
"PyObject *dict;",
|
||||
"PyObject *annotations;",
|
||||
"int result;",
|
||||
"dict = PyObject_GetAttr((PyObject *)m, &_Py_ID(__dict__));",
|
||||
"annotations = PyDict_GetItemWithError(dict, &_Py_ID(__annotations__));",
|
||||
"result = PyDict_SetItem(",
|
||||
), "module_get_annotations must hoist locals and preserve dictionary/error order")
|
||||
assert "PyObject *annotations =" not in body and "int result =" not in body
|
||||
print("moduleobject module_get_annotations C89 locals and dictionary order: PASS")
|
||||
@@ -0,0 +1,53 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Focused Visual C++ 6.0 C89 regression checks for object.c."""
|
||||
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
source = Path(sys.argv[1]).read_text()
|
||||
|
||||
|
||||
def body(name):
|
||||
found = re.search(re.escape(name) + r"\s*\([^;]*\)\s*\{(?P<body>.*?)\n\}", source, re.S)
|
||||
assert found, name + " definition not found"
|
||||
return found.group("body")
|
||||
|
||||
|
||||
def check(name, tokens, message):
|
||||
text = body(name)
|
||||
positions = [text.find(token) for token in tokens]
|
||||
assert all(position >= 0 for position in positions) and positions == sorted(positions), message
|
||||
return text
|
||||
|
||||
|
||||
text = check("PyObject_CallFinalizerFromDealloc", (
|
||||
"Py_ssize_t refcnt;",
|
||||
"if (Py_REFCNT(self) != 0)",
|
||||
"refcnt = Py_REFCNT(self);",
|
||||
"_Py_NewReference(self);",
|
||||
"Py_SET_REFCNT(self, refcnt);",
|
||||
), "PyObject_CallFinalizerFromDealloc must hoist refcnt while preserving resurrection order")
|
||||
assert "Py_ssize_t refcnt =" not in text
|
||||
print("object PyObject_CallFinalizerFromDealloc C89 local and refcount order: PASS")
|
||||
|
||||
text = check("_PyObject_Dump", (
|
||||
"PyTypeObject *type;",
|
||||
"PyGILState_STATE gil;",
|
||||
"PyObject *error_type, *error_value, *error_traceback;",
|
||||
"if (_PyObject_IsFreed(op))",
|
||||
"type = Py_TYPE(op);",
|
||||
"gil = PyGILState_Ensure();",
|
||||
"PyErr_Fetch(&error_type, &error_value, &error_traceback);",
|
||||
), "_PyObject_Dump must hoist locals without changing diagnostics or exception preservation")
|
||||
assert "PyTypeObject *type =" not in text and "PyGILState_STATE gil =" not in text
|
||||
print("object _PyObject_Dump C89 locals and exception preservation: PASS")
|
||||
|
||||
text = check("PyObject_Repr", (
|
||||
"PyObject *res;",
|
||||
"PyThreadState *tstate;",
|
||||
"if (PyErr_CheckSignals())",
|
||||
"tstate = _PyThreadState_GET();",
|
||||
), "PyObject_Repr must hoist tstate while preserving signal-check order")
|
||||
assert "PyThreadState *tstate =" not in text
|
||||
print("object PyObject_Repr C89 local and signal-check order: PASS")
|
||||
@@ -9945,6 +9945,66 @@ else
|
||||
bad "frameobject frame_getlocals C89 declaration and ownership order"
|
||||
fi
|
||||
|
||||
if python3 "$REPO_ROOT/scripts/vc6-funcobject-c89.test.py" "$REPO_ROOT/cpython/Objects/funcobject.c"; then
|
||||
pass "funcobject C89 declarations preserve function-construction ownership order"
|
||||
else
|
||||
bad "funcobject C89 declarations preserve function-construction ownership order"
|
||||
fi
|
||||
|
||||
if python3 "$REPO_ROOT/scripts/vc6-genericaliasobject-c89.test.py" "$REPO_ROOT/cpython/Objects/genericaliasobject.c" "$REPO_ROOT/cpython/Objects/object.c"; then
|
||||
pass "genericaliasobject C89 declarations preserve writer and ownership order"
|
||||
else
|
||||
bad "genericaliasobject C89 declarations preserve writer and ownership order"
|
||||
fi
|
||||
|
||||
if python3 "$REPO_ROOT/scripts/vc6-genobject-c89.test.py" "$REPO_ROOT/cpython/Objects/genobject.c"; then
|
||||
pass "genobject generator traversal C89 declaration and error order"
|
||||
else
|
||||
bad "genobject generator traversal C89 declaration and error order"
|
||||
fi
|
||||
|
||||
if python3 "$REPO_ROOT/scripts/vc6-interpreteridobject-c89.test.py" "$REPO_ROOT/cpython/Objects/interpreteridobject.c"; then
|
||||
pass "interpreteridobject C89 declarations and ownership order"
|
||||
else
|
||||
bad "interpreteridobject C89 declarations and ownership order"
|
||||
fi
|
||||
|
||||
if python3 "$REPO_ROOT/scripts/vc6-iterobject-c89.test.py" "$REPO_ROOT/cpython/Objects/iterobject.c"; then
|
||||
pass "iterobject anextawaitable C89 declarations and reference order"
|
||||
else
|
||||
bad "iterobject anextawaitable C89 declarations and reference order"
|
||||
fi
|
||||
|
||||
if python3 "$REPO_ROOT/scripts/vc6-listobject-c89.test.py" "$REPO_ROOT/cpython/Objects/listobject.c"; then
|
||||
pass "listobject C89 declarations preserve allocation and ownership order"
|
||||
else
|
||||
bad "listobject C89 declarations preserve allocation and ownership order"
|
||||
fi
|
||||
|
||||
if python3 "$REPO_ROOT/scripts/vc6-longobject-c89.test.py" "$REPO_ROOT/cpython/Objects/longobject.c"; then
|
||||
pass "longobject C89 declarations preserve allocation and evaluation order"
|
||||
else
|
||||
bad "longobject C89 declarations preserve allocation and evaluation order"
|
||||
fi
|
||||
|
||||
if python3 "$REPO_ROOT/scripts/vc6-memoryobject-c89.test.py" "$REPO_ROOT/cpython/Objects/memoryobject.c"; then
|
||||
pass "memoryobject VC6 C89 declarations and native-format type spellings"
|
||||
else
|
||||
bad "memoryobject VC6 C89 declarations and native-format type spellings"
|
||||
fi
|
||||
|
||||
if python3 "$REPO_ROOT/scripts/vc6-methodobject-c89.test.py" "$REPO_ROOT/cpython/Objects/methodobject.c"; then
|
||||
pass "methodobject VC6 C89 declarations and type initialization"
|
||||
else
|
||||
bad "methodobject VC6 C89 declarations and type initialization"
|
||||
fi
|
||||
|
||||
if python3 "$REPO_ROOT/scripts/vc6-moduleobject-c89.test.py" "$REPO_ROOT/cpython/Objects/moduleobject.c"; then
|
||||
pass "moduleobject VC6 C89 declarations and evaluation order"
|
||||
else
|
||||
bad "moduleobject VC6 C89 declarations and evaluation order"
|
||||
fi
|
||||
|
||||
if [ "$fail" -ne 0 ]; then
|
||||
echo "FAILED"
|
||||
exit 1
|
||||
|
||||
Reference in New Issue
Block a user