Menu ▾ ▴

#1276 National arguments: ORD, REVERSE, TRIM, TEST-NUMVAL, FORMATTED-DATE, UNSTRING treat PIC N as bytes

GC 3.x
open
nobody
None
5 - default
2026-10-30
2026-10-30
No

Six intrinsic functions and statements give wrong results, or refuse to compile, when you pass them a national (PIC N) operand. A national item is stored as UTF-16 big-endian (N"AB" is 00 41 00 42), and each routine below reads that storage as if it were single alphanumeric bytes. The six cases:

  1. FUNCTION ORD of a national character returns the ordinal of its high-order byte (3.2.0) or stops the run (3.3-dev).
  2. FUNCTION REVERSE of a national item reverses bytes, not characters.
  3. FUNCTION TRIM of a national item removes 0x20 bytes, not national spaces.
  4. FORMATTED-DATE, FORMATTED-TIME and INTEGER-OF-FORMATTED-DATE refuse a national format literal at compile time.
  5. TEST-NUMVAL, TEST-NUMVAL-C and TEST-NUMVAL-F return 1 for a valid national argument.
  6. UNSTRING of a national item stores byte counts in COUNT IN and POINTER.

Each case has a different wrong result in a different routine, so each has its own section. A separate report covers MOVE, the space fill, INITIALIZE and comparison of national items.

How the programs were run

With default warnings, cobc prints -Wunfinished warnings ("handling of USAGE NATIONAL is unfinished; implementation is likely to be changed" and "handling of national literal is unfinished; implementation is likely to be changed"), with source context. To keep this report short, the commands add -Wno-unfinished. With that option, every program except those in case 4 compiles with exit code 0. On 3.2.0 the compile prints only these two lines, which come from the Ubuntu package's C compiler flags:

<command-line>: warning: ‘_FORTIFY_SOURCE’ redefined
<command-line>: note: this is the location of the previous definition

On 3.3-dev the compile prints nothing. For 3.3-dev the commands are the same, using that build's cobc, with LD_LIBRARY_PATH pointing at its libcob.

1. FUNCTION ORD of a national character returns the ordinal of its high-order byte

ordn.cob (attached):

IDENTIFICATION DIVISION.
PROGRAM-ID. ORDN.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 NAT-A PIC N(1) VALUE N"A".
01 NAT-Z PIC N(1) VALUE N"Z".
PROCEDURE DIVISION.
IF NAT-A < NAT-Z DISPLAY "A < Z" END-IF
DISPLAY FUNCTION ORD(NAT-A)
DISPLAY FUNCTION ORD(NAT-Z)
STOP RUN.

What happens

On 3.2.0, cobc -std=cobol2014 -fformat=free -Wno-unfinished -x ordn.cob && ./ordn. The compile exits 0 and the run exits 0. Both characters get the ordinal 1:

A < Z
000000001
000000001

On 3.3-dev r5751, the same command. The compile exits 0 with no diagnostic. The run exits 1:

A < Z
libcob: error: attempt to use non-implemented function

What should happen

The program prints A < Z, then two different ordinals, the first one smaller. ISO/IEC 1989:2023:

  • §15.70.3 argument rule 1: argument-1 "shall be one character position in length and shall be of category alphabetic, alphanumeric, or national". The program is valid.
  • §15.70.4 returned value rule 2: "If the class of argument-1 is national, the returned value is the ordinal position of argument-1 in the current national program collating sequence."
  • §8.8.4.2.10 general rule 1: "The operand that contains the character that is positioned higher in the national collating sequence is the greater operand." The program's own comparison puts N"A" below N"Z", so the two characters cannot have the same ordinal position, whatever national collating sequence the implementor defines.

Why it happens

The cause is cob_intr_ord in libcob/intrinsic.c. In 3.2.0 it returns the first byte of the field plus one for every class, and for a national field that byte is the high-order 0x00. In r5751 the COB_TYPE_NATIONAL case calls error_not_implemented() at run time, while cobc compiles the program without a diagnostic.

Proposed fix

The COB_TYPE_NATIONAL case returns the UTF-16 code unit, read from both bytes, plus one. libcob has no national program collating sequence at run time (cobc parses COLLATING SEQUENCE FOR NATIONAL but passes it on only for file keys), and national operands are compared by their code units, so this is the ordinal in the order the comparison uses. With the patch, ordn prints A < Z, 000000066, 000000091.

2. FUNCTION REVERSE of a national item reverses bytes

nrev.cob (attached):

IDENTIFICATION DIVISION.
PROGRAM-ID. NREV.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 NAT PIC N(2) VALUE N"AB".
PROCEDURE DIVISION.
IF FUNCTION REVERSE(NAT) = N"BA" DISPLAY "EQUAL" ELSE DISPLAY "NOT EQUAL" END-IF
STOP RUN.

What happens

On 3.2.0, cobc -std=cobol2014 -fformat=free -Wno-unfinished -x nrev.cob && ./nrev. The compile exits 0 and the run exits 0:

NOT EQUAL

3.3-dev r5751 gives the same output, exit 0.

What should happen

The program prints EQUAL. ISO/IEC 1989:2023:

  • §15.78.1: "The REVERSE function returns a character string of exactly the same length as argument-1 and whose characters are exactly the same as those of argument-1, except that they are in reverse order." For a national argument the function's type is national.
  • §15.78.4 returned value rule 1: "the character in position j of the returned value is the character from position n - j + 1 of argument-1". The positions are national character positions, so N"AB" gives N"BA".

Why it happens

cob_intr_reverse in libcob/intrinsic.c copies srcfield->data[size - i - 1] over srcfield->size bytes, whatever the field's class. N"AB" becomes 42 00 41 00, which is not a sequence of national characters at all.

Proposed fix

For a national field the loop moves two-byte units. With the patch, nrev prints EQUAL.

3. FUNCTION TRIM of a national item removes 0x20 bytes, not national spaces

ntrim.cob (attached):

IDENTIFICATION DIVISION.
PROGRAM-ID. NTRIM.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 NAT PIC N(4) VALUE N" AB ".
PROCEDURE DIVISION.
DISPLAY FUNCTION LENGTH(FUNCTION TRIM(NAT))
IF FUNCTION TRIM(NAT) = N"AB" DISPLAY "EQUAL" ELSE DISPLAY "NOT EQUAL" END-IF
STOP RUN.

What happens

On 3.2.0, cobc -std=cobol2014 -fformat=free -Wno-unfinished -x ntrim.cob && ./ntrim. The compile exits 0 and the run exits 0:

000000003
NOT EQUAL

3.3-dev r5751 gives the same output, exit 0.

What should happen

The program prints a length of 2, then EQUAL. ISO/IEC 1989:2023:

  • §15.96.3 argument rule 3 b): "if argument-1 is class national, argument-2 is as though a national space had been specified".
  • §15.96.4 returned value rule 3: "If neither LEADING nor TRAILING is specified, the returned value is a character string that consists of the characters in argument-1 beginning from the leftmost character position that does not contain argument-2 through the rightmost character position after which all characters contain argument-2."

The 2014 edition has no argument-2 and states the same result in terms of "a space character", with a national function type for a national argument.

Why it happens

cob_intr_trim in libcob/intrinsic.c compares single bytes with ' ' from each end. N" AB " is 00 20 00 41 00 42 00 20, so the leading scan stops at the first 0x00 and the trailing scan removes one byte, which leaves 7 bytes.

Proposed fix

For a national field both scans step over two-byte units and compare with the national space 00 20, as cobc stores N" ". LEADING and TRAILING keep their meaning. With the patch, ntrim prints 000000002 and EQUAL.

4. A national format literal is refused by FORMATTED-DATE, FORMATTED-TIME and INTEGER-OF-FORMATTED-DATE

fdm.cob (attached):

IDENTIFICATION DIVISION.
PROGRAM-ID. FDM.
PROCEDURE DIVISION.
    IF FUNCTION FORMATTED-DATE (N"YYYYMMDD", 1) = N"16010101"
        DISPLAY "OK"
    ELSE
        DISPLAY "WRONG"
    END-IF
    STOP RUN.

What happens

On 3.2.0, cobc -std=cobol2014 -fformat=free -Wno-unfinished -x fdm.cob, exit 1:

fdm.cob:4: error: FUNCTION 'FORMATTED-DATE' has invalid date/time format
    2 | PROGRAM-ID. FDM.
    3 | PROCEDURE DIVISION.
    4 >     IF FUNCTION FORMATTED-DATE (N"YYYYMMDD", 1) = N"16010101"
    5 |         DISPLAY "OK"
    6 |     ELSE

3.3-dev r5751 gives the same output, exit 1.

fdl.cob (attached) is the same program with alphanumeric literals ("YYYYMMDD", "16010101"). It compiles with exit 0, and the run prints OK with exit 0 on both versions.

sib.cob (attached) shows the same refusal for the other two functions:

IDENTIFICATION DIVISION.
PROGRAM-ID. SIB.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 D PIC N(8) VALUE N"16010101".
PROCEDURE DIVISION.
    DISPLAY FUNCTION INTEGER-OF-FORMATTED-DATE (N"YYYYMMDD", D)
    DISPLAY FUNCTION LENGTH (FUNCTION FORMATTED-TIME (N"hhmmss", 0))
    STOP RUN.

On 3.2.0, cobc -std=cobol2014 -fformat=free -Wno-unfinished -x sib.cob, exit 1:

sib.cob:7: error: FUNCTION 'INTEGER-OF-FORMATTED-DATE' has invalid date/time format
    5 | 01 D PIC N(8) VALUE N"16010101".
    6 | PROCEDURE DIVISION.
    7 >     DISPLAY FUNCTION INTEGER-OF-FORMATTED-DATE (N"YYYYMMDD", D)
    8 |     DISPLAY FUNCTION LENGTH (FUNCTION FORMATTED-TIME (N"hhmmss", 0))
    9 |     STOP RUN.
sib.cob:8: error: FUNCTION 'FORMATTED-TIME' has invalid date/time format
    6 | PROCEDURE DIVISION.
    7 |     DISPLAY FUNCTION INTEGER-OF-FORMATTED-DATE (N"YYYYMMDD", D)
    8 >     DISPLAY FUNCTION LENGTH (FUNCTION FORMATTED-TIME (N"hhmmss", 0))
    9 |     STOP RUN.
   10 | <EOF>

On 3.3-dev r5751, the same command, exit 1:

sib.cob:7: error: FUNCTION 'INTEGER-OF-FORMATTED-DATE' has invalid date/time format
    5 | 01 D PIC N(8) VALUE N"16010101".
    6 | PROCEDURE DIVISION.
    7 >     DISPLAY FUNCTION INTEGER-OF-FORMATTED-DATE (N"YYYYMMDD", D)
    8 |     DISPLAY FUNCTION LENGTH (FUNCTION FORMATTED-TIME (N"hhmmss", 0))
    9 |     STOP RUN.
sib.cob:8: error: FUNCTION 'FORMATTED-TIME' has invalid date/time format
    8 >     DISPLAY FUNCTION LENGTH (FUNCTION FORMATTED-TIME (N"hhmmss", 0))
    9 |     STOP RUN.
   10 | <EOF>

What should happen

fdm.cob compiles and prints OK. sib.cob compiles and prints the values 1 and 6. ISO/IEC 1989:2023:

  • §15.39.3 argument rule 1 (FORMATTED-DATE): "Argument-1 shall be a national or alphanumeric literal." Rule 2: "The content of argument-1 shall be a date format."
  • §15.39.1: "The type of this function depends on the type of argument-1". A national argument-1 gives a national function.
  • §15.41.3 argument rule 1 (FORMATTED-TIME) and §15.48.3 argument rule 1 (INTEGER-OF-FORMATTED-DATE): "Argument-1 shall be a national or alphanumeric literal." §15.48.3 rule 3: argument-2 "shall be a data item of the same type as argument-1", which D satisfies.
  • §15.5.2: the integer date form is "based on a starting date of Monday, January 1, 1601", so integer date 1 is 16010101.

Where the fix belongs

We think the fix starts in the scanner (cobc/scanner.l, the "poor-man's conversion iso-8859 -> utf-16"). It stores N"YYYYMMDD" as UTF-16, so its first byte is NUL. valid_const_date_time_args in cobc/tree.c takes that data from try_get_constant_data and passes it to valid_format, which calls cob_valid_date_format, cob_valid_time_format or cob_valid_datetime_format. Those read a single-byte C string, so they see an empty string. The check has to convert a national literal to its characters first.

After that, the run-time functions that take a format argument also need to read a national format. In libcob/intrinsic.c these are cob_intr_formatted_date, cob_intr_formatted_time, cob_intr_formatted_datetime, cob_intr_integer_of_formatted_date, cob_intr_seconds_from_formatted_time and cob_intr_test_formatted_datetime. The first three build an alphanumeric result and must return a national one, and cobc must give the function the national category, or the comparison with N"16010101" in fdm.cob compares an alphanumeric result with a national literal.

The patch attached to this report does not cover this case: the change spans cobc and six run-time functions.

5. TEST-NUMVAL, TEST-NUMVAL-C and TEST-NUMVAL-F return 1 for a valid national argument

tnnat.cob (attached):

IDENTIFICATION DIVISION.
PROGRAM-ID. TNNAT.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 WS-N PIC N(3) VALUE N"123".
01 WS-R PIC S9(4) VALUE 0.
PROCEDURE DIVISION.
    COMPUTE WS-R = FUNCTION NUMVAL(WS-N)
    DISPLAY "NUMVAL " WS-R
    COMPUTE WS-R = FUNCTION TEST-NUMVAL(WS-N)
    DISPLAY "TEST-NUMVAL " WS-R
    COMPUTE WS-R = FUNCTION TEST-NUMVAL-F(WS-N)
    DISPLAY "TEST-NUMVAL-F " WS-R
    COMPUTE WS-R = FUNCTION TEST-NUMVAL-C(WS-N)
    DISPLAY "TEST-NUMVAL-C " WS-R
    STOP RUN.

What happens

On 3.2.0, cobc -std=cobol2014 -fformat=free -Wno-unfinished -x tnnat.cob && ./tnnat. The compile exits 0 and the run exits 0:

NUMVAL +0123
TEST-NUMVAL +0001
TEST-NUMVAL-F +0001
TEST-NUMVAL-C +0001

3.3-dev r5751 gives the same output, exit 0.

NUMVAL accepts the item, but all three test functions report position 1 as invalid.

What should happen

The program prints NUMVAL +0123, then +0000 from each of the three test functions. ISO/IEC 1989:2023:

  • §15.93.3 argument rule 1 (TEST-NUMVAL), and the same rule in §15.95.3 (TEST-NUMVAL-F): "Argument-1 shall be an alphanumeric or national literal or a data item of class alphanumeric or national." §15.94.3 rule 1 gives TEST-NUMVAL-C the argument rules of NUMVAL-C, and §15.68.3 rule 1 says "Argument-1 shall be of category alphanumeric or national."
  • §15.67.3 rule 1: the NUMVAL format applies to "an alphanumeric or national literal or an alphanumeric or national data item". 123 conforms to it.
  • §15.93.4 returned value rule 1 a): "If the content of argument-1 conforms to the argument rules for the NUMVAL function", the returned value is zero. §15.94.4 1) a) and §15.95.4 1) a) say the same for NUMVAL-C and NUMVAL-F.

Why it happens

cob_check_numval (used by cob_intr_test_numval and cob_intr_test_numval_c) and cob_check_numval_f (used by cob_intr_test_numval_f) in libcob/intrinsic.c read srcfield->data one byte at a time as alphanumeric characters. The first byte of a national digit is 0x00. Both functions already carry the comment "FIXME later: srcfield may be of category national...".

Proposed fix

In place of each FIXME, a national argument is checked in a copy with one byte per national character (its value for U+0000 to U+00FF, 0x00 otherwise). A character above U+00FF is never valid in these formats, so the returned position is the same as for the national argument. A national currency string for TEST-NUMVAL-C is copied the same way. One that holds a character above U+00FF is still not matched, and the patch marks that with a FIXME. With the patch, tnnat prints:

NUMVAL +0123
TEST-NUMVAL +0000
TEST-NUMVAL-F +0000
TEST-NUMVAL-C +0000

6. UNSTRING of a national item stores byte counts in COUNT IN and POINTER

nunstr.cob (attached):

IDENTIFICATION DIVISION.
PROGRAM-ID. NUNSTR.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 SRC PIC N(5) VALUE N"AB,CD".
01 R1 PIC N(5).
01 CNT PIC 9(3) VALUE 0.
01 PTR PIC 9(3) VALUE 1.
PROCEDURE DIVISION.
    UNSTRING SRC DELIMITED BY N","
        INTO R1 COUNT IN CNT
        WITH POINTER PTR
    END-UNSTRING
    DISPLAY "CNT " CNT " PTR " PTR
    STOP RUN.

What happens

On 3.2.0, cobc -std=cobol2014 -fformat=free -Wno-unfinished -x nunstr.cob && ./nunstr. The compile exits 0 and the run exits 0:

CNT 004 PTR 007

3.3-dev r5751 gives the same output, exit 0.

What should happen

The program prints CNT 002 PTR 004. ISO/IEC 1989:2023:

  • §14.9.48.4 general rule 11 e): "a value equal to the number of characters examined, excluding any delimiter characters, shall be moved into the area referenced by identifier-6". A and B are 2 characters.
  • §14.9.48.4 general rule 13: the POINTER item "will be incremented by one for each character examined in the data item referenced by identifier-1". A, B and the comma give 1 + 3 = 4.
  • §3.124, national character position: "location within a national data item of a national character". The characters of a national item are national characters.

Why it happens

In libcob/strings.c, cob_unstring_init_intern sets st->offset from the POINTER item (cob_get_int (st->ptr) - 1), cob_unstring_into_intern advances it by byte counts and stores the byte match_size as COUNT IN, and cob_unstring_finish_intern stores st->offset + 1 into the POINTER item.

Proposed fix

For a national sending item, the three functions convert between bytes and national character positions where the POINTER and COUNT IN items are read and stored. The delimiter scan steps by national characters, so a delimiter can no longer match at an odd byte offset. With the patch, nunstr prints CNT 002 PTR 004.

Proposed patch

national-intrinsics-and-statements.patch (attached) is against 3.3-dev r5751 and applies with patch -p1 at the top of the source tree. It covers cases 1, 2, 3, 5 and 6. Case 4 is not covered.

  • In libcob/intrinsic.c it changes cob_intr_ord, cob_intr_reverse, cob_intr_trim, cob_check_numval and cob_check_numval_f, with a new static helper, narrow_national.
  • In libcob/strings.c it changes the three UNSTRING functions named in case 6.
  • Each change applies only to national operands. Alphanumeric operands take the code paths they take today.
  • It adds two testsuite cases, "FUNCTION ORD, REVERSE, TRIM, TEST-NUMVAL national" in run_functions.at and "UNSTRING national with COUNT IN and POINTER" in run_misc.at, and ChangeLog entries in libcob and tests.

With the patch, the same commands on the patched build give, for each attached program:

=== ordn
A < Z
000000066
000000091
=== nrev
EQUAL
=== ntrim
000000002
EQUAL
=== tnnat
NUMVAL +0123
TEST-NUMVAL +0000
TEST-NUMVAL-F +0000
TEST-NUMVAL-C +0000
=== nunstr
CNT 002 PTR 004

Each compile and run exits 0. fdm.cob and sib.cob still fail to compile as shown in case 4, and fdl.cob still prints OK.

make check on 3.3-dev r5751 with the patch: 1,374 tests behaved as expected, 9 were skipped, 0 failed. Unpatched, 1,372 behaved as expected, 9 were skipped and 0 failed; the difference is the two new tests, which pass. On the unpatched build the programs of the two new tests fail: the first stops with "libcob: error: attempt to use non-implemented function" (ORD), and the second reports COUNT IN 004 and POINTER 011.

  • #142: also a wrong FUNCTION ORD result from cob_intr_ord, but for an alphanumeric argument under a PROGRAM COLLATING SEQUENCE, not for a national argument.
  • No existing ticket reports any of the six defects here.
  • The two NUMVAL validators in case 5 already carry a FIXME comment about national fields.

Details

Versions: GnuCOBOL 3.2.0 (Ubuntu gnucobol3 3.2-4build1) and 3.3-dev r5751 (CI snapshot 3.3-dev.138 of 2026-10-29), x86_64 Linux. All runs were repeated on 2026-10-30.

Attached: ordn.cob, nrev.cob, ntrim.cob, fdm.cob, fdl.cob, sib.cob, tnnat.cob, nunstr.cob, and national-intrinsics-and-statements.patch.

Editions: the rules quoted above are from ISO/IEC 1989:2023. The same rules are in the committee draft of the 2014 edition (CD 1.2, 2009), with the TRIM wording noted in case 3, so all eight programs are valid under -std=cobol2014.

How the fix sites were found: we read the r5751 source. For cases 1, 2, 3, 5 and 6 the patch confirms the sites named. For case 4 we did not trace them in a debugger, so they are inferred.

9 Attachments

Discussion


Log in to post a comment.