COBOL

COBOL

COBOL

COBOL is responsible for the efficient, reliable, secure, and unseen day-to-day operations of the world's economy.

 View Only

Pitfalls of Invalid Programs and Data

By Dan Zhang posted Thu April 02, 2020 12:43 PM

  

In COBOL, one may write invalid programs or use invalid data in program which can be compiled and executed but lead to technically incorrect or even unexpected results.  It's the responsibility of the COBOL programmer to make sure that programs and data are valid, and as the behavior of invalid programs (or programs with invalid data) is undefined, IBM makes no guarantees that the result in these cases won't change between compiler releases or even PTFs.

While migrating from Enterprise COBOL V4.2 to Enterprise COBOL V5, some users found that invalid programs or programs using invalid data are failing or producing different results in COBOL V5 than they did in COBOL V4.2. Most commonly, this occurred while using invalid data: data values that are incompatible with the data descriptions of the data items in which the values are stored.  Differences in behavior with invalid programs or data were also observed at different OPT levels in COBOL V5. The reason for this is an assumption that IBM Enterprise COBOL compiler development team must make while developing and testing the compiler: that COBOL programs and data are valid and should be treated as valid unless there's a COBOL compiler option that specifies otherwise. Since both COBOL V4.2 and COBOL V5 are making this assumption, both compilers are free to choose the code sequences they want, depending on the OPT and ARCH options.

COBOL V4.2 and V5 often get different instruction sequences for the same COBOL statement, both between compiler versions and between OPT levels in the same version, and different instruction sequences may result in different behaviour when programs or data are invalid. In some of these cases, IBM may provide options to guarantee certain behaviors between V4.2 and V5 or ways to detect invalid programs or data. Please note that the end results will be the same when compiling with either COBOL V4.2 and V5 if the programs and data are valid.

 

The rest of this blog post will describe the situations that IBM COBOL development team is aware of where invalid data or programs could affect behavior, as well as what can be done to detect the problems or force specific behaviors.

 

Invalid USAGE DISPLAY data

From the Enterprise COBOL V5.2 Programming Guide:

Each digit of a valid zoned decimal number is represented by a single byte from X’F0’ through X’F9’. The 4 high-order bits of each byte are zone bits, and the 4 low-order bits of each byte contain the value of the digit. The 4 high-order bits of the low-order byte for SIGN TRAILING represent the sign of the item. The sign is in the high-order byte with SIGN LEADING, or in a separate byte for SIGN IS SEPARATE.

COBOL V5 assumes that the zone bits are always correct. COBOL V5 generates instructions that are different from COBOL V4, producing different results in cases when the zone bits are incorrect. Whether the program displays 'ZERO' or 'NOT ZERO' depends on the compiler options you use in COBOL v4 and in COBOL V5, because different options generate different code sequences that behave differently when the data is not proper USAGE DISPLAY data.


01 VALUE0 PIC X(4) VALUE ’00 0’. <* x’F0F040F0’; the third byte has a x'4' for zone bits, so this is not a valid USAGE DISPLAY value
01 VALUE1 REDEFINES VALUE0 PIC 9(4).
PROCEDURE DIVISION.
   IF VALUE1 = ZERO
      DISPLAY ’ZERO'
   ELSE
      DISPLAY ’NOT ZERO'
   END-IF


Incorrect zone bits can be detected by adding NUMERIC tests in your code or with the ZONECHECK option being released in the next COBOL V5.1 PTF and already available in the most recent COBOL V5.2 PTF. The default is NOZONECHECK, and there's also ZONECHECK(ABD) and ZONECHECK(MSG). The latter two options will check all USAGE DISPLAY items each time they're used in a COBOL statement, and if an item fails the NUMERIC class test, the program will either abend or will display a message. To properly fix the program, you must correct your data.

To force a certain behaviour in COBOL V5, we've added the ZONEDATA option, which allows you to get the same behaviour as when compiling with COBOL V4.

- If you can guarantee that your USAGE DISPLAY data is all correct, use ZONEDATA(PFD) for maximum performance.
- If your USAGE DISPLAY data may be incorrect and you used NUMPROC(MIG) in COBOL V4, use ZONEDATA(MIG) and whichever NUMPROC option is appropriate in COBOL V5
- If your USAGE DISPLAY data may be incorrect and you used NUMPROC(NOPFD) in COBOL V4, use ZONEDATA(NOPFD) and NUMPROC(NOPFD) in COBOL V5
- If your USAGE DISPLAY data may be incorrect and you used NUMPROC(PFD) in COBOL V4, use ZONEDATA(NOPFD) and NUMPROC(PFD) in COBOL V5

 
Invalid USAGE DISPLAY or PACKED-DECIMAL Signs or Digits

01 CHARS PIC X(3) VALUE x'123450'.
01 X REDEFINES CHARS PIC S9(5) PACKED-DECIMAL.
PROCEDURE DIVISION.
    COMPUTE X = X * 10.
    DISPLAY X.

This program uses a REDEFINE to put an invalid sign code in X (x'0' instead of x'A' through x'F'). Most uses of PACKED-DECIMAL or USAGE DISPLAY data, particularly arithmetic, with invalid sign codes or digits will cause a data exception, making them easy to detect. To properly fix the problem, you must correct your data.


Creating values outside the picture clause

Some COBOL data types may require larger space than is needed to represent the items as defined by their PICTURE clause. For example, the leftmost nibble of an even-precision PACKED-DECIMAL value isn't used and should be x'0', and a PIC 9(4) COMP field is stored in two bytes and has a maximum value of 65535, which is a five-digit number, so there are some bit patterns that don't match the PICTURE clause.

01 X PIC X(2).
01 Y REDEFINES X PIC 9(2) PACKED-DECIMAL.
01 Z REDEFINES X PIC 9(4) COMP.
PROCEDURE DIVISION.
    MOVE x'123F' TO X.
    MOVE x'FFFF' TO X.


The first move sets X to x'123F', which is 4671 in binary (which is within the allowed range for Z) but is +123 in PACKED-DECIMAL, which is larger than two digits. Code assuming that PACKED-DECIMAL values don't have more digits than their picture clauses specify could behave incorrectly. The second move sets X to x'FFFF', which isn't a valid PACKED-DECIMAL value at all (though nearly any operation on Y at this point will result in a data exception abend, so at least the problem can be caught), and is also 65535 in unsigned binary. For code compiled with TRUNC(STD), where we generally expect the compiler to always truncate, we'll end up with a COMP value larger than its picture clause, so any assumptions the compiler makes that all values are properly truncated are invalid. For code compiled with TRUNC(OPT), this code violates the promise the programmers make with the compiler that data always conforms to the PICTURE clause, and as it says in the Enterprise COBOL Programming Guide, unpredictable results could occur.

We're unaware of any specific problems that could occur in either scenario (since we don't test invalid data or programs, we haven't found any ourselves), but violating the PICTURE clause with redefines or other methods is invalid and should be avoided.

There's no easy way to detect the COMP case, but using COMP-5 items (generally recommended for performance) or TRUNC(BIN) means that we expect to use the whole range of bits available, so there are no bit patterns outside the expected range of values in that case. For writing a non-zero value to the leftmost nibble of an even-precision PACKED-DECIMAL value, the recommended solution is to use an odd-precision value instead; this avoids any issues and provides better performance (the compiler never has to generate code to set the leftmost nibble to x'0'). COBOL V5.2 users can compile with RULES(NOEVENPACK) to get warnings when even-precision PACKED-DECIMAL values are used.


Accessing data outside the bounds of a table

01 MY-TABLE.
  02 TBL PIC X(4) OCCURS 10.
01 MY-SCRATCH PIC X(60).
01 MY-INDEX PIC 9(4) COMP.
PROCEDURE DIVISION.
    MOVE 20 TO MY-INDEX.
    MOVE ALL 'X' TO MY-SCRATCH.
    MOVE SPACES TO TBL(MY-INDEX)
    DISPLAY MY-SCRATCH.


Executing the code above will end up writing spaces to the middle of MY-SCRATCH because the MOVE SPACES statement uses an index outside the bounds of TBL. If you compile with SSRANGE, the compiler will abend whenever data outside of table bounds is accessed. Similarly, writing too much data to a table with an index (e.g. TBL PIC X(4) OCCURS 10 INDEXED BY MY-IDX) may overwrite the index in COBOL V5, since indices in V5 are stored immediately after the table. This too can be detected with SSRANGE.

Accessing a table with an incorrect value for the data item specified in an OCCURS DEPENDING ON clause

01 X PIC 9(5) BINARY.
01 MY-TABLE.
  02 T OCCURS 0 TO 1 TIMES DEPENDING ON X.
    05 MY-FIELD PIC X(1).
01 OFLOW PIC X(500).
PROCEDURE DIVISION.
    MOVE 300 TO X.
    MOVE ALL 'X' TO MY-TABLE.
    DISPLAY MY-TABLE
    DISPLAY OFLOW


This is similar to the previous issue. In V4, 300 'X's get moved to MY-TABLE, putting 99 X's at the start of OVERFLOW. In V5, because variable-length moves are handled differently, we write exactly one X followed by 299 bytes of garbage data. The problem here is that X has been set to something outside the allowed range of T and then T is accessed (indirectly, through the MOVE to MY-TABLE). This too is caught with SSRANGE.

Specifying a different size for a data item in a called program's LINKAGE SECTION than in the program that calls it

From the Enterprise COBOL V5.1 Programming Guide, pg. 484:

You must know what data is being passed from the calling program and describe it in the LINKAGE SECTION of each program that is called directly or indirectly by the calling program.

Code the USING phrase after the PROCEDURE DIVISION header to name the parameters that receive the data that is passed from the calling program.

When arguments are passed to the subprogram BY REFERENCE, it is invalid for the subprogram to specify any relationship between its parameters and any fields other than those that are passed and defined in the main program. The subprogram must not:
- Define a parameter to be larger in total number of bytes than the corresponding argument.
- Use subscript references to refer to elements beyond the limits of tables that are passed as arguments by the calling program.
- Use reference modification to access data beyond the length of defined parameters.
- Manipulate the address of a parameter in order to access other data items that are defined in the calling program.

If any of the rules above are violated, unexpected results might occur.


IDENTIFICATION DIVISION.
PROGRAM-ID 'CALLER'.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 SRC PIC X(1000).
01 DEST PIC X(1000).
01 MOVE-LEN PIC 9(4) VALUE 500.
PROCEDURE DIVISION.
    MOVE ALL 'X' TO SRC.
    CALL 'CALLEE' USING SRC, DEST, MOVE-LEN.
    DISPLAY DEST.
    GOBACK.

END PROGRAM CALLER.

IDENTIFICATION DIVISION.
PROGRAM-ID 'CALLEE'.
DATA DIVISION.
WORKING-STORAGE SECTION.
LINKAGE SECTION.
01 SRC PIC X(1).
01 DEST PIC X(1).
01 MOVE-LEN PIC 9(4).
PROCEDURE DIVISION USING SRC, DEST, MOVE-LEN.
    MOVE SRC(1:MOVE-LEN) TO DEST(1:MOVE-LEN).
    GOBACK.
END PROGRAM CALLEE.


In this program, we'd expect to see 500 'X's in DEST. In COBOL V5 at OPT(1), DEST actually contains 245 'X's. CALLEE is violating the rules above by defining SRC and DEST to be different sizes, perhaps because CALLEE is a common module called from several other modules, each passing in a different amount of data, so using PIC X(1) was used as a compromise. What actually happens in COBOL V5 is that for MOVEs at OPT(1|2), we sometimes generate a loop of instructions that move 256 bytes at a time, and then one instruction to move the remaining <= 256 bytes. In cases such as this where we believe the destination to be less than 256 bytes (here, we believe it's only 1 byte), we only generate the final move, not the loop. Since we expect the amount of data we're being asked to move to be <= 256, the instruction we use looks at the rightmost 8 bits of the move length. In this case, the move length is x'1F4'; the rightmost 8 bits is x'F4', or 244, so we only move 245 bytes (the instruction doing the move always adds one to the length).

In this particular case, since we're moving data past the end of a table, SSRANGE will detect the error, but there may be cases where parameters are specified incorrectly in the program being called that SSRANGE cannot detect. For situations in this case, the best way to declare the parameters in the CALLEE is to use the size of the LARGEST amount of data they will be required to hold.

A variation on this case is if an actual parameter passed between programs is a pointer and the pointer is used to initialize a field in the CALLEE declared as a smaller size than in the CALLER.

IBM doesn't test every possible type of incorrectly written program such as ones with invalid data, so the results from compiling the above code examples are undefined and may vary between compilers and when varying the options used. When this blog post was being written, not every set of options likely to have an impact on the generated code was tested.

You can find more information on this topic by reading technote "Applications built with Enterprise COBOL for z/OS V5.x may get different results from previous versions of Enterprise COBOL for z/OS".

0 comments
4 views

Permalink