Data exceptions and S0C7
On z/OS, the arithmetic behind ADD, COMPUTE and IF A > B on
packed and zoned fields runs as decimal hardware instructions
(AP, SP, MP, ZAP, CP...). Before they calculate, they check
every nibble: digits must be 0–9 and the sign must be a valid sign.
If not, the CPU raises a data exception, program interruption code 7,
and the step ends with the most famous abend on the mainframe:
CEE3207S The system detected a data exception (System Completion Code=0C7).
IEF450I PAYJOB STEP020 - ABEND=S0C7 U0000 REASON=00000007
It is the first abend every mainframe developer learns to fix.
Where bad data comes from
- Spaces in numeric fields.
MOVE SPACES TO CUST-RECfills packed fields withX'40'. An empty input column is spaces too. - Uninitialised storage. A
WORKING-STORAGEfield without aVALUEclause holds whatever was there, oftenX'00'(low-values), which has no sign nibble. - Wrong layout. A copybook out of step with the file, so the program reads text bytes as a packed field.
- Using the record after end of file, or after a failed
READ. - Upstream garbage. Another system sent letters in a number column.
GnuCOBOL doesn't abend
GnuCOBOL does not check nibbles by default. It computes something and carries on. Run this:
IDENTIFICATION DIVISION.
PROGRAM-ID. SOC7DEMO.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 WS-REC.
05 WS-NAME PIC X(10).
05 WS-BALANCE PIC S9(5)V99 COMP-3.
01 WS-TOTAL PIC S9(7)V99 COMP-3 VALUE 0.
PROCEDURE DIVISION.
MOVE SPACES TO WS-REC
DISPLAY "BALANCE BYTES X'" FUNCTION HEX-OF(WS-BALANCE) "'"
IF WS-BALANCE IS NUMERIC
DISPLAY "BALANCE IS NUMERIC"
ELSE
DISPLAY "BALANCE IS NOT NUMERIC"
END-IF
ADD WS-BALANCE TO WS-TOTAL
DISPLAY "TOTAL AFTER ADD: " WS-TOTAL
INITIALIZE WS-REC
DISPLAY "AFTER INITIALIZE X'" FUNCTION HEX-OF(WS-BALANCE) "'"
STOP RUN.
BALANCE BYTES X'20202020'
BALANCE IS NOT NUMERIC
TOTAL AFTER ADD: +0202020.20
AFTER INITIALIZE X'0000000C'
On z/OS the ADD would be S0C7. Here you get a nonsense total of
202,020.20, which is arguably worse: a wrong number in a report nobody
questions. (Spaces are X'20' in ASCII, X'40' in EBCDIC.) GnuCOBOL
can check at run time if you compile with cobc -debug; then the ADD
stops with 'WS-BALANCE' (Type: PACKED-DECIMAL) not numeric.
Defending against it
- Validate input before arithmetic. The class test
IF field IS NUMERICworks on zoned and packed fields and checks every nibble. Don't touch a number from a file until it passes. - Initialise. Give every numeric working-storage field a
VALUE, and clear records withINITIALIZE, notMOVE SPACES. - Reject, don't guess. Write bad records to an error file with a reason, count them, and set a return code so the job and the operators know.
On the job
To fix a S0C7: find the failing statement from the dump (see the abends lesson), identify which field in it holds bad data, then look at that field's bytes in the input dataset in HEX. The fix is almost never "make the abend go away". It is either correcting the data, correcting the layout, or adding the validation that should have been there.
Your task
The branch sales feed sometimes contains damaged records. On z/OS the
first bad one would abend the job with S0C7. Here, GnuCOBOL just
computes garbage: run the starter and it reports a total of
21,407.02. Make the program defend itself.
Input, DD name TXNIN, ORGANIZATION IS SEQUENTIAL,
16-byte records (open it under Datasets and press HEX to see
the damage):
| Field | Picture | Bytes |
|---|---|---|
TXN-ID |
X(6) |
1–6 |
TXN-QTY |
9(3) |
7–9 |
TXN-PRICE |
S9(5)V99 COMP-3 |
10–13 |
TXN-BRANCH |
X(3) |
14–16 |
For each record:
- If
TXN-QTYis not numeric, write a reject line with reasonQTY NOT NUMERIC. - Otherwise, if
TXN-PRICEis not numeric, reject it withPRICE NOT NUMERIC. - Otherwise add
TXN-QTY * TXN-PRICEto the total.
Rejects, DD name REJECTS, line sequential: the transaction
id, a space and the reason (REJ-REC and WRITE-REJECT are in the
starter; move the reason to WS-REASON first):
T00003 QTY NOT NUMERIC
Keep the summary the starter displays, and end with return code 4 if any record was rejected, 0 otherwise.