Skip to content

det: an unsigned binary ADD / SUBTRACT wraps as cobc's native arithmetic does; lifted COMP-5 under TRUNC(STD) (#4684) - #4689

Merged
squid-protocol merged 10 commits into
mainfrom
fix/4684-binary-overflow
Oct 8, 2026
Merged

squid-protocol merged 10 commits into
mainfrom
fix/4684-binary-overflow

Conversation

@squid-protocol

@squid-protocol squid-protocol commented Oct 8, 2026 •

Copy link
Copy Markdown
Owner

Fixes #4684

Reworked per the owner decision on this PR (2026-10-08) and the policy in #4702: IBM is the reference; GnuCOBOL is the instrument. Merged origin/main (#4654 and #4677 are in; main's side taken for their pre-squash hunks).

The decision: an unsigned binary ADD / SUBTRACT stores IBM's absolute value

  • IBM reference.
    • Enterprise COBOL for z/OS 6.4 Language Reference (SC27-8713-03), MOVE statement, "Elementary moves", p. 404: "If the receiving item is unsigned, no operational sign is generated for the receiving item and the absolute value of the sending item is used in the move."
    • The LR does not say in so many words that an arithmetic statement's store follows this rule: confirm (oracle policy: IBM is the reference, GnuCOBOL the instrument — audit register entries + z/OS calibration oracle #4702, z/OS calibration).
    • 6.4 Programming Guide (SC27-8714-03), "TRUNC", pp. 419-420: COMP-5 behaves as TRUNC(BIN) whatever TRUNC says. Under TRUNC(BIN) a value is "truncated only at halfword, fullword, or doubleword boundaries". Under TRUNC(STD) it is truncated to "the number of digits in the PICTURE clause".
  • Det port. Cobol.storeNative / binaryNative now store IBM's value: 1 − 3 into an unsigned PIC 9(4) COMP is 2, not libcob's native wrap of 65534. They are store / binary.
  • Where the marker stays. gen.native_add / fits_int still mark the statements cobc compiles to native integer arithmetic (cb_build_optim_add / _sub), so the generated Java names where the oracle departs.
  • COMP-5 with a lone NOT ON SIZE ERROR. cobc leaves such a statement native and unchecked. The port now keeps IBM's size check (storeChecked), as before det: an unsigned binary ADD / SUBTRACT wraps as cobc's native arithmetic does; lifted COMP-5 under TRUNC(STD) (#4684) #4689.
  • The other det: an unsigned binary ADD / SUBTRACT wraps as cobc's native arithmetic does; lifted COMP-5 under TRUNC(STD) (#4684) #4689 fixes, checked against IBM, are kept.
    • An unsigned doubleword is not lifted to a Java long. Under TRUNC(BIN) it holds up to 2 ** 64 − 1, per IBM's doubleword boundary.
    • A lifted COMP-5 item keeps its bytes under TRUNC(STD). IBM: "TRUNC has no effect on COMP-5".

Oracle side: no cobc option, so the wrap is a declared difference

  • What I tried. I ran these on the pinned gitgalaxy-gnucobol:3 image (cobc 3.1.2 -std=ibm): -O0, -O2, -fbinary-size=1-2-4-8 / 1--8, -fbinary-byteorder=native, -fnotrunc, -fno-constant-folding, -fno-arithmetic-osvs, -std=ibm-strict, -std=mvs, -debug, -fec=EC-SIZE / -TRUNCATION / -OVERFLOW. None turns the native path off. No /etc/gnucobol/*.conf key does either.
  • The only exception. -fbinary-truncate keeps COMP / COMP-4 / BINARY off the native path, but that flag is TRUNC(STD) itself. So equivalence_common.COBC_OPTIONS is unchanged.
  • Register C4 (docs/language_status/oracle_assumptions.md) is rewritten with the policy wording and the IBM references. It records the native wrap as a declared oracle-vs-IBM difference: MATCHED (det runtime = IBM), DIFFERS (oracle vs IBM, declared). No proven case reaches it.
  • The proof is not loosened.
    • test_det_binary_store.py's end-to-end test (TRUNC(BIN) and TRUNC(STD), bytes and typed) asserts the oracle's measured values, cobc's wrap included.
    • It also asserts that the port's output equals the oracle's line for line, except on exactly the named DECLARED_C4 lines, where the port shows IBM's value and the oracle does not.
    • A proven case that reached the wrap would still fail its compare.
  • test_det_programs._cobol keeps main's raw and gains flags. _java_run keeps raw and gains trunc_std.

Verification

Part A / Part B (unchanged from the first version)

🤖 Generated with Claude Code

https://claude.ai/code/session_01MQNoe4wPJgr7dbUvs3DG6D

squid-protocol and others added 5 commits October 7, 2026 21:17
…SVS does (#4287)

The det translator replays cobc -std=ibm's compile-time ARITHMETIC-OSVS decision
(det/osvs.py, from GnuCOBOL 3.1.2 typeck.c / tree.c): dmax from receivers and
cb_walk_cond, the expr_decp stack with its pushes, pending aligns flushed on the
next load, constant folding, conditions built right to left, the state an
EVALUATE leaves to its sentence. It emits Cobol.align (libcob's
cob_decimal_align, its downward shift included) where cobc emits
cob_decimal_align, and a literal on the right of an operation as a Cobol.Dc
(libcob's decimal constant, whose scale its uses change). Cobol.divide keeps
cob_decimal_div's places and Cobol.power cob_decimal_pow's trimming.

Register C2 rewritten: the det runtime matches the oracle; the oracle's
departures from IBM's rule are listed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The oracle (GnuCOBOL 3.1.2 -std=ibm) leaves every receiver unchanged on a zero
divisor, with or without ON SIZE ERROR; the det port threw ArithmeticException.
Cobol.divide now returns libcob's NaN (scale -32768) and raises the statement's
size error; add/subtract/multiply/negate/power carry it where the translator sees
a division or exponent below; store/storeChecked leave the receiver (lifted
receivers guarded with isNan); Cobol.align truncates a NaN to 0 as libcob does.
A division in a function argument is 0 (cob_intr_binop); FUNCTION MOD/REM by
zero are 0. 0 ** 0 raises the size error; a non-finite exponent is NaN.
Statements with ON SIZE ERROR clear and read the size-error state.
DIVIDE REMAINDER now uses the quotient truncated to the receiver's places
(cob_div_quotient), not the stored quotient.

Register entry oracle_assumptions C14 (IBM: undefined / S0CB on z/OS).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…4655)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… arithmetic does (#4684)

cobc 3.1.2 compiles ADD / SUBTRACT ... TO / FROM an unsigned binary item of no
decimal places, with one operand that fits a C int and no ROUNDED or store
option, to native integer arithmetic: a result below zero wraps modulo
2 ** bits (1 - 3 is 65534) where libcob's decimal store keeps the absolute
value. COMP-5 always, COMP / COMP-4 / BINARY under TRUNC(BIN) only.
gen.native_add picks the statements as cb_build_add / cb_fits_int do;
Cobol.storeNative / binaryNative store them. An unsigned doubleword is no
longer lifted to a long (it holds up to 2 ** 64 - 1).

Every other binary store measured (about 4,000: COMP / COMP-4 / BINARY /
COMP-5, signed and unsigned, halfword to doubleword, scaled, COMPUTE / ADD /
SUBTRACT / MULTIPLY / DIVIDE / GIVING / MOVE, both TRUNC settings) already
matched on this base; the issue's own case was fixed by ARITHMETIC-OSVS
(#4287). oracle_assumptions C4 records the measurement, IBM's absolute
value, and the one MOVE cobc stores unlike IBM under TRUNC(STD).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Comment thread gitgalaxy/tools/cobol_to_java/det/gen.py Fixed
Comment thread gitgalaxy/tools/cobol_to_java/det/osvs.py Fixed
Comment thread tests/cobol_mainframe/test_det_osvs.py Fixed
Comment thread tests/cobol_mainframe/test_det_size_error.py Fixed
@github-actions

github-actions Bot commented Oct 8, 2026

Copy link
Copy Markdown
Contributor

🐦‍⬛ Muninn Security Scan

✅ No security issues found.

🐦‍⬛ Powered by Muninn · Skald Lab

squid-protocol and others added 2 commits October 8, 2026 02:33
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…4684)

Cobol.binary built its scratch item as COMP, so a typed (lifted) COMP-5
receiver lost its high-order digits under TRUNC(STD): COMPUTE N = 12345
into S9(4) COMP-5 gave 2345, cobc -fbinary-truncate 12345. The store of a
lifted COMP-5 item now names it (Cobol.binary's comp5 overload).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@squid-protocol squid-protocol changed the title det: an unsigned binary ADD / SUBTRACT wraps as cobc's native arithmetic does (#4684) det: an unsigned binary ADD / SUBTRACT wraps as cobc's native arithmetic does; lifted COMP-5 under TRUNC(STD) (#4684) Oct 8, 2026
@squid-protocol

Copy link
Copy Markdown
Owner Author

Owner decision (2026-10-08): follow IBM, not GnuCOBOL. Unsigned binary ADD/SUBTRACT stores the absolute value as IBM documents (1 - 3 = 2), not libcob's native wrap (65534). Make the ORACLE side z/OS-like too: first look for a cobc option/config that disables the native binary add/subtract path (so cobc yields IBM's result); if none exists, record it in oracle_assumptions C4 as a declared oracle-vs-IBM difference the comparator expects (IBM value on the port side), never by loosening the proof. Policy: the oracle should be as z/OS-like as possible so it's a better comparator.

🤖 Generated with Claude Code

@squid-protocol

Copy link
Copy Markdown
Owner Author

Owner decision, final (2026-10-08; supersedes my previous comment's wording). Policy: IBM is the reference; GnuCOBOL is the instrument. IBM documents the absolute value for an unsigned binary receiver, so the det port stores |result| (1 - 3 = 2) under the estate's TRUNC option. On the oracle side, first look for a cobc option or config that turns off the native binary add/subtract wrap. If none exists, record it in oracle_assumptions C4 as a declared oracle-vs-IBM difference the comparator expects. Never loosen the proof. To be calibrated later against real Enterprise COBOL (#4702).

🤖 Generated with Claude Code

squid-protocol and others added 3 commits October 8, 2026 17:41
Takes main's side of #4677 / #4654's pre-squash hunks and keeps #4689's own changes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MQNoe4wPJgr7dbUvs3DG6D
…value; cobc's native wrap is declared (#4684)

Owner policy (2026-10-08, #4702): IBM is the reference, GnuCOBOL the instrument. IBM stores the
absolute value in an unsigned receiver (6.4 Language Reference, "Elementary moves"; the arithmetic
store: confirm), truncated per TRUNC (6.4 Programming Guide, "TRUNC"). Cobol.storeNative /
binaryNative now store that (1 - 3 = 2); gen.native_add still marks where cobc compiles native
arithmetic, and a COMP-5 statement with a lone NOT ON SIZE ERROR keeps IBM's size check.

No cobc 3.1.2 option or config entry turns cobc's native wrap off, so oracle_assumptions C4 declares
it as an oracle-vs-IBM difference: test_det_binary_store expects cobc's wrap on the oracle and IBM's
value on the port on exactly the named lines (DECLARED_C4), equality on every other line.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MQNoe4wPJgr7dbUvs3DG6D
@squid-protocol
squid-protocol marked this pull request as ready for review October 8, 2026 23:01
@squid-protocol
squid-protocol merged commit b9d955c into main Oct 8, 2026
37 checks passed
@squid-protocol
squid-protocol deleted the fix/4684-binary-overflow branch October 8, 2026 23:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

det runtime: a result overflowing a binary receiver's bytes (no ON SIZE ERROR) is stored unlike GnuCOBOL

2 participants