COBOL Module 3 – Numbers in storage, money arithmetic and dates
Invalid numeric data, 0C7 abends and validation
What COBOL does with spaces or bad sign half-bytes in numeric fields: the NUMERIC test, GnuCOBOL run-time checks, z/OS 0C7 data exceptions and reject files.
What you will learn
- Validate input with the NUMERIC class test before using it in arithmetic
- Explain how invalid zoned or packed data behaves in GnuCOBOL with and without run-time checks
- Match a z/OS 0C7 data exception and the NUMPROC and NUMCHECK options to GnuCOBOL's behaviour
- Route bad records to an error file instead of ending the run
Before you start
What you’ll learn · Before you start
What you will learn
- Validate input with the NUMERIC class test before using it in arithmetic
- Explain how invalid zoned or packed data behaves in GnuCOBOL with and without run-time checks
- Match a z/OS 0C7 data exception and the NUMPROC and NUMCHECK options to GnuCOBOL's behaviour
- Route bad records to an error file instead of ending the run
Before you start
On this page
A record whose amount field holds five spaces goes through ADD in a GnuCOBOL build without checks as if the amount were 0, and the run’s total is wrong by that record without a message. On z/OS, invalid digits or signs in decimal data are the classic cause of an S0C7 abend, which stops the job instead.
Below you will test fields with IS NUMERIC before any arithmetic, see what GnuCOBOL does with bad zoned and packed bytes with and without run-time checks, match that to the 0C7 data exception and to IBM’s NUMPROC and NUMCHECK options, and route bad records to a reject file with a reason while the run goes on.
IS NUMERIC tests the bytes against the PICTURE and the USAGE
A PICTURE describes what an item should hold, and nothing checks it when a record is read or a group is moved. The class condition: A test of which characters an item holds: NUMERIC for digits only, ALPHABETIC for letters and spaces, and ALPHABETIC-UPPER or ALPHABETIC-LOWER for one case of letters and spaces. More in “Conditions, IF and EVALUATE decision tables” IS NUMERIC is the check: IBM’s guide says it tests the contents of an item “against a set of values that are valid for the PICTURE and USAGE”. For zoned decimal that means a digit in every byte, with a valid sign in the last zone of a signed item; for packed decimal, digits 0 to 9 in every digit half-byte and a valid sign in the last. This program sets three zoned values and six packed ones through a REDEFINES, as bad data arrives in a file, and tests each:
IDENTIFICATION DIVISION.
PROGRAM-ID. CLASSTEST.
* The NUMERIC class test on three zoned fields and six packed
* ones. Each field's bytes are set through a REDEFINES, as bad
* data arrives from a file, and printed in hexadecimal.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 Z-AMT PIC 9(5).
01 Z-TEXT REDEFINES Z-AMT PIC X(5).
01 P-RAW PIC X(3).
01 P-AMT REDEFINES P-RAW PIC S9(5) COMP-3.
PROCEDURE DIVISION.
DISPLAY "PIC 9(5)"
MOVE "01234" TO Z-TEXT
PERFORM TEST-ZONED
MOVE SPACES TO Z-TEXT
PERFORM TEST-ZONED
MOVE "1234X" TO Z-TEXT
PERFORM TEST-ZONED
DISPLAY "PIC S9(5) COMP-3"
MOVE X'12345C' TO P-RAW
PERFORM TEST-PACKED
MOVE X'12345D' TO P-RAW
PERFORM TEST-PACKED
MOVE X'12345F' TO P-RAW
PERFORM TEST-PACKED
MOVE X'12345A' TO P-RAW
PERFORM TEST-PACKED
MOVE X'1234AC' TO P-RAW
PERFORM TEST-PACKED
MOVE X'404040' TO P-RAW
PERFORM TEST-PACKED
STOP RUN.
TEST-ZONED.
IF Z-AMT IS NUMERIC
DISPLAY " " FUNCTION HEX-OF(Z-TEXT) " numeric"
ELSE
DISPLAY " " FUNCTION HEX-OF(Z-TEXT) " not numeric"
END-IF.
TEST-PACKED.
IF P-AMT IS NUMERIC
DISPLAY " " FUNCTION HEX-OF(P-RAW) " numeric"
ELSE
DISPLAY " " FUNCTION HEX-OF(P-RAW) " not numeric"
END-IF. from session import files, run
# The NUMERIC class test, built with GnuCOBOL's default dialect and with -std=ibm. -A passes an option on to the C
# compiler: this one keeps it quiet about the byte X'AC' inside a literal.
files("classtest.cob")
run("""
cobc -x -A -Wno-invalid-source-encoding classtest.cob
./classtest
cobc -x -std=ibm -A -Wno-invalid-source-encoding -o classtest-ibm classtest.cob
./classtest-ibm
""") Output
$ cobc -x -A -Wno-invalid-source-encoding classtest.cob $ ./classtest PIC 9(5) 3031323334 numeric 2020202020 not numeric 3132333458 not numeric PIC S9(5) COMP-3 12345C numeric 12345D numeric 12345F not numeric 12345A not numeric 1234AC not numeric 404040 not numeric $ cobc -x -std=ibm -A -Wno-invalid-source-encoding -o classtest-ibm classtest.cob $ ./classtest-ibm PIC 9(5) 3031323334 numeric 2020202020 not numeric 3132333458 not numeric PIC S9(5) COMP-3 12345C numeric 12345D numeric 12345F numeric 12345A not numeric 1234AC not numeric 404040 not numeric
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 classtest_session.py
The zoned field passed only with five digits: spaces, X’2020202020’, and 1234X, whose last byte is X’58’, both failed. The packed field passed with X’12345C’ and X’12345D’. It failed with X’1234AC’, whose fifth digit half-byte is A, with X’12345A’, and with X’404040’, the bytes of three EBCDIC spaces, whose sign half-byte is 0. Step through the packed test:
The drawing shows the whole scene; its 5 steps are listed below.
Every step as text
- The digits. PIC S9(5) COMP-3 is three bytes, five digit half-bytes and a sign half-byte. The class test reads the digit half-bytes first, and each must hold 0 to 9.
- A bad digit. X'1234AC' has the half-byte A where the fifth digit should be. A is 10, no decimal digit, so the field is not numeric in both builds, whatever its sign says.
- The sign. The last half-byte must be a sign the test accepts. X'12345C' and X'12345D', positive and negative, passed in both builds.
- An F sign. X'12345F' ends in F, the sign of an unsigned item. For this signed item GnuCOBOL's default dialect said not numeric, and X'12345A' failed too.
- The IBM dialect. Built with -std=ibm, whose configuration sets hostsign to yes, the same X'12345F' was numeric. IBM's guide gives C, D and F as the valid signs of a signed item under NUMPROC(NOPFD), the default, with NUMCLS(PRIM).
X’12345F’ is the case that differs by dialect. F is the sign of an unsigned item, and GnuCOBOL’s default dialect rejected it in a signed field, while -std=ibm, whose configuration sets hostsign to yes, accepted it. IBM’s guide gives the same answer for z/OS: with NUMCLS(PRIM) in effect the valid signs of a signed item under NUMPROC(NOPFD) are C, D and F.
Testing the amount after the arithmetic
Wrong: ADD REC-AMT TO TOTAL, then IF REC-AMT IS NUMERIC to report the record. The bad bytes are already in the total. Right: test where the record comes in, before any statement uses the field as a number, and keep the record out of every total when it fails.
Placed there, the test turns a bad field into a decision the program makes, instead of a number it computes.
Key idea
IS NUMERIC checks the bytes, not the value: digits in every position and a sign the dialect accepts, so a field should pass it before any arithmetic reads it.
Checkpoint
Check: The NUMERIC class test
Without checks GnuCOBOL computes with bad bytes; a debug build stops
What happens when a program skips the test depends on how it was built. This program adds an amount of spaces, the amount 1234X and the packed bytes X’1234AC’ to totals. The session builds it three times: with GnuCOBOL’s defaults, with -debug, which turns on every run-time check, and with -fec=EC-DATA-INCOMPATIBLE, which turns on only the check for numeric data that is not numeric:
IDENTIFICATION DIVISION.
PROGRAM-ID. BAD-DATA.
* Arithmetic on invalid numeric data, as a record read from a
* file might hold it: an amount of spaces, an amount ending in a
* letter, and a packed field with a hexadecimal A among its
* digits.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 IN-REC.
05 REC-ID PIC X(4).
05 REC-AMT PIC 9(5).
01 P-RAW PIC X(3).
01 P-AMT REDEFINES P-RAW PIC S9(5) COMP-3.
01 TOTAL PIC 9(7) VALUE ZERO.
01 P-TOTAL PIC S9(7) COMP-3 VALUE ZERO.
01 E-OUT PIC -(7)9.
PROCEDURE DIVISION.
MOVE "A001 " TO IN-REC
ADD REC-AMT TO TOTAL
DISPLAY "after an amount of spaces TOTAL " TOTAL
MOVE "A0021234X" TO IN-REC
ADD REC-AMT TO TOTAL
DISPLAY "after the amount 1234X TOTAL " TOTAL
MOVE X'1234AC' TO P-RAW
ADD P-AMT TO P-TOTAL
MOVE P-TOTAL TO E-OUT
DISPLAY "after packed X'1234AC' TOTAL " E-OUT
STOP RUN. from session import files, run
# The same program built three ways: with no run-time checks, with -debug, and with only the check for incompatible
# data. The line "Started by" names the program the shell ran.
files("bad-data.cob")
run("""
cobc -x -A -Wno-invalid-source-encoding bad-data.cob
./bad-data
cobc -x -debug -A -Wno-invalid-source-encoding -o bad-data-debug bad-data.cob
!./bad-data-debug
cobc -x -fec=EC-DATA-INCOMPATIBLE -A -Wno-invalid-source-encoding -o bad-data-ec bad-data.cob
!./bad-data-ec
""") Output
$ cobc -x -A -Wno-invalid-source-encoding bad-data.cob $ ./bad-data after an amount of spaces TOTAL 0000000 after the amount 1234X TOTAL 0012348 after packed X'1234AC' TOTAL 12350 $ cobc -x -debug -A -Wno-invalid-source-encoding -o bad-data-debug bad-data.cob $ ./bad-data-debug libcob: bad-data.cob:19: error: 'REC-AMT' (Type: NUMERIC DISPLAY) not numeric: ' ' Last statement of "BAD-DATA" was ADD at line 19 of bad-data.cob ENTRY BAD-DATA at bad-data.cob:18 Started by ./bad-data-debug [exit status 1] $ cobc -x -fec=EC-DATA-INCOMPATIBLE -A -Wno-invalid-source-encoding -o bad-data-ec bad-data.cob $ ./bad-data-ec libcob: bad-data.cob:19: error: 'REC-AMT' (Type: NUMERIC DISPLAY) not numeric: ' ' Last statement of "BAD-DATA" was ADD at line 19 of bad-data.cob Started by ./bad-data-ec [exit status 1]
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 bad_data_session.py
The default build added all three without a word. The total stayed 0000000 after the spaces, became 0012348 after 1234X, and the packed total became 12350. GnuCOBOL 3.2’s NEWS explains the first two: for invalid numeric DISPLAY data it now uses “only the second half-byte” of each character, so a space, X’20’, is 0 and the X, X’58’, is 8, and it warns that results differ from earlier versions. The packed half-byte A was read as 10. Both checked builds stopped at the first ADD instead, with the field, its type and its bytes, ‘REC-AMT’ (Type: NUMERIC DISPLAY) not numeric, the statement, ADD at line 19, and exit status 1. Step through the two outcomes:
The drawing shows the whole scene; its 6 steps are listed below.
Every step as text
- The record. The amount field of the record holds five spaces, X'2020202020', as a short or empty line of a file leaves it. PIC 9(5) promises digits, and nothing checked the promise when the record was read.
- The ADD. Line 19 adds REC-AMT to the total. What happens next depends on how the program was built, not on the source.
- No checks. The default build adds without testing. GnuCOBOL 3.2 reads only the second half-byte of each invalid character, so every space, X'20', counts as the digit 0.
- A silent zero. TOTAL stays 0000000 and the run carries on. The amount 1234X, next in the run, was taken as 12348 the same way, its X'58' read as an 8.
- With -debug. Built with -debug, or with only -fec=EC-DATA-INCOMPATIBLE, the program tests the field before it uses it, and finds it not numeric.
- A stop with its reason. libcob names the field, its type and its bytes, 'REC-AMT' (Type: NUMERIC DISPLAY) not numeric, and the last statement, ADD at line 19, then ends the run with exit status 1.
Run tests and new feeds with the data check on
Build test runs, and the first runs of a new input feed, with -fec=EC-DATA-INCOMPATIBLE. A stop that names the field and the statement costs minutes; a total built from bytes read as zeros can cost a reconciliation.
The two outcomes come from the same source and differ only in how it was built, so the build options belong in a program’s documentation, next to its record layouts.
Key idea
Without checks GnuCOBOL turns bad bytes into plausible numbers, and a build with -debug or -fec=EC-DATA-INCOMPATIBLE stops at the first one, with the field and the statement named.
Checkpoint
Check: Invalid data with and without run-time checks
On z/OS the same bytes end the step with a data exception
The mainframe’s decimal arithmetic does not carry on with bad bytes. IBM’s MVS system codes list 0C7 as a data exception: The z/OS program check of system completion code 0C7, raised when decimal arithmetic meets a zoned or packed operand with invalid digits or an invalid sign. It ends the step unless the program prevents it., and IBM’s page on the INVDATA option describes the data behind it, DISPLAY and packed items “that could contain invalid digits, an invalid sign code, or invalid zone bits”, and the S0C7 abend an invalid sign can cause. A job that meets a packed field of EBCDIC spaces, X’404040’, stops there, where the GnuCOBOL build without checks carried on. Three compiler options decide what Enterprise COBOL accepts and checks:
| Option and default | What it does | Nearest in GnuCOBOL |
|---|---|---|
| NUMPROC, default NOPFD | accepts any valid sign; PFD is faster but needs the preferred signs C, D and F | -fhostsign, set by -std=ibm, for the sign F |
| NUMCHECK, default NONUMCHECK | adds numeric class tests to zoned and packed sending items; MSG warns, ABD ends the run | -fec=EC-DATA-INCOMPATIBLE, which ends the run |
| INVDATA, default NOINVDATA | tells the compiler that DISPLAY and packed data may be invalid, to stay close to older compilers | none |
IBM warns that NUMCHECK is “much slower” than NONUMCHECK, so it suits test runs and the first runs of a new feed better than everyday production. The local relative of a mainframe dump is -fdump=ALL, which prints the program’s data after the stop:
from session import files, run
# The -debug build again, with -fdump=ALL: after the error, libcob prints the program's data. The dump's header names
# the time the program was compiled, which changes with every build, so the session shows it as "(build time)".
files("bad-data.cob")
run("""
cobc -x -debug -fdump=ALL -A -Wno-invalid-source-encoding -o bad-data-dump bad-data.cob
!./bad-data-dump
""", mask=[(r"compiled [A-Z][a-z]{2} +\d{1,2} \d{4} \d{2}:\d{2}:\d{2}", "compiled (build time)")]) Output
$ cobc -x -debug -fdump=ALL -A -Wno-invalid-source-encoding -o bad-data-dump bad-data.cob $ ./bad-data-dump libcob: bad-data.cob:19: error: 'REC-AMT' (Type: NUMERIC DISPLAY) not numeric: ' ' Last statement of "BAD-DATA" was ADD at line 19 of bad-data.cob ENTRY BAD-DATA at bad-data.cob:18 Started by ./bad-data-dump Dump Program-Id BAD-DATA from bad-data.cob compiled (build time) WORKING-STORAGE ********************** 77 RETURN-CODE +000000000 01 IN-REC. 05 REC-ID 'A001' 05 REC-AMT 01 P-RAW ALL SPACES 01 TOTAL 0000000 01 P-TOTAL +0000000 01 E-OUT 0 END OF DUMP - BAD-DATA ********************** [exit status 1]
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 dump_session.py
The dump shows the record as it arrived, REC-ID ‘A001’ and nothing but blanks after REC-AMT, next to the totals, which is the first thing to read after an abend: the field that holds the bad bytes, and the record it came from.
Bad input is also an attack surface
Treat every input record and screen field as untrusted, as OWASP’s Input Validation Cheat Sheet advises for data that crosses a trust boundary: check its type, format and range where it enters, with an allowlist of the values the program accepts.
Checkpoint
Check: Data exceptions, NUMPROC and NUMCHECK on z/OS
Validate every record where it enters, and route the bad ones
Stopping at the first bad record is right in a test and expensive in a nightly batch, where one bad amount would hold back thousands of good ones. The pattern that keeps both is to validate each record before any arithmetic, write the good ones on and the bad ones to a reject file: A file to which a program writes the input records it refuses, each with a reason code and its bytes as they arrived, so the run can go on and the records can be corrected and sent again. with a reason code, and report the count to the caller. This program does it for eight synthetic transactions; the file statements are the subject of a later lesson, and only the checks matter here:
IDENTIFICATION DIVISION.
PROGRAM-ID. VALIDATE.
* Reads transactions, checks each one before any arithmetic, and
* writes it either to the accepted file or, with a reason code,
* to the reject file. Ends with RETURN-CODE 4 when it rejected
* any record.
ENVIRONMENT DIVISION.
INPUT-OUTPUT SECTION.
FILE-CONTROL.
SELECT TX-FILE ASSIGN TO "transactions.txt"
ORGANIZATION IS LINE SEQUENTIAL
FILE STATUS IS TX-STATUS.
SELECT OK-FILE ASSIGN TO "accepted.txt"
ORGANIZATION IS LINE SEQUENTIAL.
SELECT BAD-FILE ASSIGN TO "rejects.txt"
ORGANIZATION IS LINE SEQUENTIAL.
DATA DIVISION.
FILE SECTION.
FD TX-FILE.
01 TX-REC.
05 TX-ACCOUNT PIC X(6).
05 TX-CODE PIC X.
88 TX-CODE-OK VALUE "D" "W".
05 TX-AMOUNT PIC 9(5)V99.
FD OK-FILE.
01 OK-REC PIC X(14).
FD BAD-FILE.
01 BAD-REC.
05 BAD-REASON PIC XX.
05 FILLER PIC X.
05 BAD-DATA PIC X(14).
05 FILLER PIC X.
05 BAD-TEXT PIC X(24).
WORKING-STORAGE SECTION.
01 TX-STATUS PIC XX.
01 END-OF-FILE PIC X VALUE "N".
88 NO-MORE VALUE "Y".
01 REASON PIC XX.
88 RECORD-OK VALUE SPACES.
01 REASON-TEXT PIC X(24).
01 MAX-AMOUNT PIC 9(5)V99 VALUE 50000.00.
01 READ-COUNT PIC 9(5) VALUE ZERO.
01 OK-COUNT PIC 9(5) VALUE ZERO.
01 BAD-COUNT PIC 9(5) VALUE ZERO.
01 OK-TOTAL PIC 9(7)V99 VALUE ZERO.
01 E-TOTAL PIC Z,ZZZ,ZZ9.99.
PROCEDURE DIVISION.
OPEN INPUT TX-FILE
IF TX-STATUS NOT = "00"
DISPLAY "cannot open transactions.txt: " TX-STATUS
MOVE 16 TO RETURN-CODE
STOP RUN
END-IF
OPEN OUTPUT OK-FILE BAD-FILE
PERFORM UNTIL NO-MORE
READ TX-FILE
AT END SET NO-MORE TO TRUE
NOT AT END PERFORM CHECK-AND-ROUTE
END-READ
END-PERFORM
CLOSE TX-FILE OK-FILE BAD-FILE
MOVE OK-TOTAL TO E-TOTAL
DISPLAY "read " READ-COUNT ", accepted " OK-COUNT
", rejected " BAD-COUNT ", accepted total " E-TOTAL
IF BAD-COUNT > ZERO
MOVE 4 TO RETURN-CODE
END-IF
STOP RUN.
CHECK-AND-ROUTE.
ADD 1 TO READ-COUNT
MOVE SPACES TO REASON REASON-TEXT
EVALUATE TRUE
WHEN TX-ACCOUNT = SPACES
MOVE "04" TO REASON
MOVE "no account number" TO REASON-TEXT
WHEN NOT TX-CODE-OK
MOVE "03" TO REASON
MOVE "unknown transaction code" TO REASON-TEXT
WHEN TX-AMOUNT IS NOT NUMERIC
MOVE "01" TO REASON
MOVE "amount is not numeric" TO REASON-TEXT
WHEN TX-AMOUNT = ZERO OR TX-AMOUNT > MAX-AMOUNT
MOVE "02" TO REASON
MOVE "amount out of range" TO REASON-TEXT
END-EVALUATE
IF RECORD-OK
ADD 1 TO OK-COUNT
ADD TX-AMOUNT TO OK-TOTAL
WRITE OK-REC FROM TX-REC
ELSE
ADD 1 TO BAD-COUNT
MOVE SPACES TO BAD-REC
MOVE REASON TO BAD-REASON
MOVE TX-REC TO BAD-DATA
MOVE REASON-TEXT TO BAD-TEXT
WRITE BAD-REC
END-IF. from session import files, run
# Eight synthetic transactions through the validation program, then the two files it wrote. The program ends with
# RETURN-CODE 4 because it rejected records, so the shell reports exit status 4.
files("validate.cob", "transactions.txt")
run("""
cat transactions.txt
cobc -x validate.cob
!./validate
cat accepted.txt
cat rejects.txt
""") Output
$ cat transactions.txt
A10001D0250000
A10002W00500
A10003D0000000
A10004X0010000
A10005W9999999
D0001000
A10007W0012550
A10008D00-1500
$ cobc -x validate.cob
$ ./validate
read 00008, accepted 00002, rejected 00006, accepted total 2,625.50
[exit status 4]
$ cat accepted.txt
A10001D0250000
A10007W0012550
$ cat rejects.txt
01 A10002W00500 amount is not numeric
02 A10003D0000000 amount out of range
03 A10004X0010000 unknown transaction code
02 A10005W9999999 amount out of range
04 D0001000 no account number
01 A10008D00-1500 amount is not numeric
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 validate_session.py
Two records were accepted, for a total of 2,625.50, and six went to rejects.txt, each with its reason and its bytes as they arrived. The run went to its end, and RETURN-CODE 4 told the shell that something was refused. The checks run in order, from the bytes to the meaning: an account is present, the code is one the program knows, the amount is numeric, and only then is the amount compared with the business range, because a comparison on bad bytes is exactly what the class test prevents. Step through one record:
The drawing shows the whole scene; its 7 steps are listed below.
Every step as text
- Read. READ brings one transaction of 14 bytes into TX-REC, an account, a code and an amount. Nothing about the bytes is checked yet.
- The account. The first WHEN of the EVALUATE tests for an account of spaces. The sixth record failed here and went to the reject file with reason 04.
- The code. The condition name TX-CODE-OK lists the codes the program knows, D and W, an allowlist. The fourth record, code X, failed with reason 03.
- The class test. Only now does the program look at the amount, with IS NUMERIC before any arithmetic. The amounts 00500 with two spaces and 00-1500 failed with reason 01.
- The range. A numeric amount can still be wrong for the business. Zero and 99999.99, above the limit of 50000.00, failed with reason 02.
- Accepted. A record that passed all four checks is added to the total and written to accepted.txt. Two did, for a total of 2,625.50.
- Rejected. Each failed record went to rejects.txt with its reason code and its bytes as they arrived. Six did, the run went on to the end, and RETURN-CODE 4 told the caller.
The code check is an allowlist: the condition name TX-CODE-OK lists D and W, so X, and any code nobody thought of, is refused. OWASP’s cheat sheet gives the same advice: state the values a program accepts, and refuse everything outside them.
Key idea
Validate every record where it enters, from the bytes to the business rules, and route a failure to a reject file with a reason, so the good records still run and the bad ones can be corrected.
Checkpoint
Check: Validating and routing bad records
Practise: find the bad bytes before the program does
Each question shows bytes or a record from this lesson’s runs. Decide what the test, the build or the check does with them before you choose:
Practice that adapts to you
Practise this lesson
12 questions about 4 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.
Add a fifth reason code
Add a check to validate.cob that refuses withdrawals above 20,000.00 with reason 05, after the range check. Add a
record A10009W2500000 to transactions.txt, predict both output files, then run
cobc -x validate.cob && ./validate and compare.
Interview questions
Warm-up (fresher to 2–5 years): what is an S0C7 abend, and what usually causes it?
S0C7 is the z/OS system completion code for a data exception: a decimal instruction met an operand that is not valid decimal data, so the program ends abnormally. In COBOL it usually means a zoned or packed field holds bytes that are not digits or not a valid sign: spaces or LOW-VALUES in an amount, a record read with the wrong layout, a group MOVE that put text into a number, or a field that was never initialised. IBM’s INVDATA page describes such data: invalid digits, an invalid sign code or invalid zone bits. The fix is to find the record and the field, usually from the dump, then validate input with IS NUMERIC where it enters and route bad records to a reject file. NUMCHECK can add such tests automatically while testing. A GnuCOBOL build without checks does not abend; it computes with the bad bytes.
Two harder questions apply these ideas: a nightly job that abends after a new feed, and a port to GnuCOBOL whose abends disappeared. Work out your answer first, then open each first step:
Model answers open with any pass: ₹79 for 1 week ($2.99), one payment that never renews.
-
Mid to senior
A nightly batch that has run for years abends with S0C7 on the first night after a new partner starts sending transactions. The job processes 400,000 records. How do you find the bad record and field quickly, keep the night's run going, and stop it from happening again?
-
Mid to senior
A COBOL job is moved from z/OS to GnuCOBOL. On z/OS it occasionally abended with S0C7; on GnuCOBOL it has never failed, and the team calls that an improvement. Is it? What would you check, and how would you make the new platform behave safely?
Organisations named on this page
MySmartCoPilot is independent of IBM, the GnuCOBOL project and the OWASP Foundation. The class test, the options and the system codes are cited from their documentation, listed under References. The outputs on this page are GnuCOBOL 3.2 runs on synthetic data.
Flashcards · 8 cards
Recall it, then check
Say the answer to yourself before you look: recalling it is what makes it stick.
-
What does the class condition IS NUMERIC check?
The bytes, against the item's PICTURE and USAGE: a digit in every zoned position or packed digit half-byte, and a sign the dialect accepts. It says nothing about the value's range.
-
Do spaces in an amount field pass IS NUMERIC?
No: X'2020202020' in a zoned field fails, and so do EBCDIC spaces, X'404040', in a packed field, whose sign half-byte is 0.
-
Does X'12345F' pass IS NUMERIC in a signed packed field?
It depends on the dialect. GnuCOBOL's default rejects the unsigned sign F there; -std=ibm accepts it, as Enterprise COBOL does under NUMPROC(NOPFD) with NUMCLS(PRIM).
-
What does a GnuCOBOL 3.2 build without checks do when ADD meets an amount of spaces?
It reads only the second half-byte of each character, so a space counts as 0 and the X of 1234X as 8. The total is wrong and no message appears.
-
Which GnuCOBOL builds stop at invalid numeric data?
A -debug build, with every run-time check, or one with just -fec=EC-DATA-INCOMPATIBLE. The stop names the field, its bytes, the statement and its line.
-
What is an S0C7 abend?
The z/OS data exception: a decimal instruction met bytes that are not valid digits or signs, such as spaces in a packed amount, and the step ends abnormally.
-
What does IBM's NUMCHECK option do?
It adds numeric class tests to zoned and packed sending items: with MSG it warns, with ABD it ends the run. IBM warns that it is much slower than NONUMCHECK, which makes it a tool for test runs and a new feed's first runs.
-
How should a batch program treat a record with a bad amount?
Check every record where it enters, from its bytes to its business rules, and send each failure, with its reason code, to a reject file. The good records still run, and a return code reports the rejects.
Key takeaways
- IS NUMERIC tests the bytes against the PICTURE and USAGE: spaces, 1234X, X’1234AC’ and X’404040’ failed it, and X’12345F’ passed only under the IBM rules.
- Without checks GnuCOBOL 3.2 computed with bad bytes, spaces as 0 and 1234X as 12348, while -debug or -fec=EC-DATA-INCOMPATIBLE stopped at the statement and named the field.
- On z/OS invalid digits or signs in decimal data cause the 0C7 data exception; NUMPROC decides the signs accepted and NUMCHECK adds class tests at a cost.
- Validate each record where it enters, in order from the bytes to the business rules, and route failures to a reject file with a reason and a return code, so the good records still run.
Terms in this lesson
- data exception
- The z/OS program check of system completion code 0C7, raised when decimal arithmetic meets a zoned or packed operand with invalid digits or an invalid sign. It ends the step unless the program prevents it.
- reject file
- A file to which a program writes the input records it refuses, each with a reason code and its bytes as they arrived, so the run can go on and the records can be corrected and sent again.
References
- Enterprise COBOL for z/OS 6.5: Checking for incompatible data (numeric class test) (IBM)
- Enterprise COBOL for z/OS 6.5: Class condition (IBM)
- Enterprise COBOL for z/OS 6.5: NUMPROC compiler option (IBM)
- Enterprise COBOL for z/OS 6.5: NUMCHECK compiler option (IBM)
- Enterprise COBOL for z/OS 6.5: INVDATA compiler option (IBM)
- Enterprise COBOL for z/OS 6.5: Sign representation of zoned and packed-decimal data (IBM)
- z/OS MVS System Codes: 0Cx (IBM)
- GnuCOBOL 3.2 NEWS (GnuCOBOL project (GNU))
- GnuCOBOL manual: compiler options (GnuCOBOL project (GNU))
- GnuCOBOL 3.2 dialect configuration: ibm-strict.conf (GnuCOBOL project (GNU))
- OWASP Cheat Sheet Series: Input Validation Cheat Sheet (OWASP Foundation)
Related tools
Report a problem with this lesson
Kept only in this browser. Your Learn progress