1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
"""Issue #1282: a payload that lands before the first base, from Python.
A 5'-shuffled cis allele can denote "the reference with bases added at the very
front". Before this fix the derived member reached ``hgvs_pos_to_index`` with
``start == 0`` and the subtraction underflowed.
**The 5' arm is no longer reachable from Python.** ``direction=`` was removed
from every Python entry point with the rest of ferro's public 5' surface
(``README.md`` rule 6), so the reproducers below can no longer be driven into
the underflowing path from here. The behavioural coverage is unchanged and
lives in ``tests/it/issue_1282_position_zero.rs``, whose five tests all pin the
5' arm against the internal direction type — nothing was dropped, the guard
simply has one owner now instead of two.
What is still worth pinning *here*, and what this file was always really about,
is the PyO3 boundary. A Rust panic crosses PyO3 as
``pyo3_runtime.PanicException``, which subclasses ``BaseException`` rather than
``Exception`` — so it slips straight through the ``except Exception`` a caller
would reasonably wrap ``normalize`` in. And the wheel is built ``--release``
(``ci.yml``'s Python Wheel Test job, and every published artifact), where
``[profile.release]`` sets no ``overflow-checks`` — so there the subtraction
wrapped silently to an index near ``usize::MAX`` rather than panicking at all.
Both doors are only observable from here, so the reproducers are still driven
through the boundary, now on the shipped 3' path.
``tests/python/test_coordinates.py`` covers the other half — the standalone
``hgvs_pos_to_index(0)`` conversion — but that is the helper, not the path that
reached it.
"""
=
# Leading ``T`` run, so a 5' shuffle drives the payload to the contig start.
# Identical to the Rust fixture's ``SEQ``, so the two suites pin one behaviour.
=
# The reported reproducer and two siblings, all of which underflowed. Each
# denotes the reference with an ``A`` added before base 1: the substitution
# rewrites base 1 to ``A`` and the insertion lengthens the ``T`` run. The
# insertion's own start position is irrelevant to the outcome, which is why all
# three collapse to one answer.
=
# "Insert ``A`` immediately 5' of base 1" spelled as the boundary delins that
# every other bound in ``src/normalize/mod.rs`` already uses, because
# ``g.0_1ins`` is not a position any HGVS axis has. Still the 5' answer, and
# still asserted — in ``tests/it/issue_1282_position_zero.rs``.
=
"""Build a normalizer over the synthetic reference.
There is no direction to choose: ferro shuffles 3'.
"""
=
= /
return
"""The keyword that drove these reproducers into the underflow is gone.
Asserted rather than assumed, because the dangerous removal would be a
*silently ignored* keyword: a caller writing ``direction="5prime"`` would
then get a 3' answer to a 5' question with nothing to tell them.
"""
=
= /
"""The reported crash: a description comes back instead of a panic.
Reaching the assertion at all is the point — a ``PanicException`` would
abort the call rather than return, and in a release wheel the underflow
wrapped silently instead. Driven on the shipped 3' path now that 5' is not
reachable from Python; the 5' arm's exact outputs are pinned in
``tests/it/issue_1282_position_zero.rs``.
"""
=
assert
"""Re-normalizing the output must not move it, or the clamp is a one-pass patch."""
=
=
assert ==
assert ==
"""The clamp fires only at interbase 0.
Without this, a clamp that fired too eagerly would rewrite valid insertions
into ``delins`` and still pass the assertions above.
"""
=
assert ==
# An interior insertion into the leading T-run. On the shipped 3' path it
# 3'-shifts to the end of that run and types as a duplication; the value is
# measured, not assumed. What matters for this guard is the negative: it is
# NOT rewritten into the interbase-0 boundary delins.
=
assert ==
assert !=