cobc accepts OCCURS DYNAMIC with only a warning, but gives the table no storage at all. Every subscripted reference then reads or writes outside the record, and the program runs on without any error. A program that uses a dynamic table can silently overwrite its own data.
occdyn2.cob (attached) moves values into two elements of a dynamic table:
IDENTIFICATION DIVISION.
PROGRAM-ID. OCCDYN2.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 I PIC 9 VALUE 1.
01 J PIC 9 VALUE 2.
01 T.
05 E PIC X(3) OCCURS DYNAMIC.
01 W PIC X(3) VALUE "WWW".
PROCEDURE DIVISION.
MOVE "AAA" TO E(I)
MOVE "BBB" TO E(J)
DISPLAY E(I) E(J) W
STOP RUN.
cobc -std=cobol2014 -fformat=free -x occdyn2.cob && ./occdyn2 | od -c
Compiling gives a warning, then a zero-byte record. 3.2.0, exit 0:
occdyn2.cob:8: warning: OCCURS DYNAMIC is not implemented [-Wpending]
6 | 01 J PIC 9 VALUE 2.
7 | 01 T.
8 > 05 E PIC X(3) OCCURS DYNAMIC.
9 | 01 W PIC X(3) VALUE "WWW".
10 | PROCEDURE DIVISION.
/tmp/cob7165_0.c: In function ‘OCCDYN2_’:
/tmp/cob7165_0.c:103:3: warning: ‘memset’ writing 3 bytes into a region of size 0 overflows the destination [-Wstringop-overflow=]
103 | memset (b_3 + 3LL * ((cob_s64_t)(cob_get_numdisp (b_1, 1)) - 1), 65, 3);
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
In file included from /tmp/cob7165_0.c:58:
/tmp/cob7165_0.c.l.h:36:17: note: destination object ‘b_3’ of size 0
36 | static cob_u8_t b_3[0] __attribute__((aligned)); /* T */
| ^~~
/tmp/cob7165_0.c:106:3: warning: ‘memset’ writing 3 bytes into a region of size 0 overflows the destination [-Wstringop-overflow=]
106 | memset (b_3 + 3LL * ((cob_s64_t)(cob_get_numdisp (b_2, 1)) - 1), 66, 3);
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/tmp/cob7165_0.c.l.h:36:17: note: destination object ‘b_3’ of size 0
36 | static cob_u8_t b_3[0] __attribute__((aligned)); /* T */
| ^~~
The default dialect gives the same. 3.3-dev r5751, the same command, exit 0: the same output apart from the temporary file names.
The program then stores outside the record and exits 0. 3.2.0, ./occdyn2 | od -c:
0000000 B B B \0 \0 \0 W W W \n
0000012
3.3-dev r5751 gives byte-identical output, run exit 0.
With -debug the first MOVE is trapped. 3.2.0, cobc -std=cobol2014 -fformat=free -debug -x occdyn2.cob && ./occdyn2, compile output as above (line numbers shifted), run exit 1:
libcob: occdyn2.cob:11: error: subscript of 'E' out of bounds: 1
note: maximum subscript for 'E': 0
Last statement of "OCCDYN2" was MOVE at line 11 of occdyn2.cob
ENTRY OCCDYN2 at occdyn2.cob:11
Started by ./occdyn2
3.3-dev r5751 gives the same output, run exit 1.
cobc should stop with a compile-time error for OCCURS DYNAMIC while it is not implemented, so that no executable is produced that stores outside its record. (-Werror=pending gives this on both versions, but only when the user asks for it.)
This is not a conformance report. Dynamic-capacity tables are an optional language element in ISO/IEC 1989:2023, and the 2014 edition lists them as optional too. GnuCOBOL does not claim them.
For reference, an implementation that does claim them would print AAABBBWWW. §8.5.1.9.3 says that when an element is referenced as a receiving item beyond the current capacity, "a new element is automatically created and the capacity of the table is increased to the value given by the subscript".
In the OCCURS DYNAMIC arm of occurs_clause in cobc/parser.y, the unimplemented feature is only a -Wpending warning, CB_PENDING ("OCCURS DYNAMIC"). Without a TO phrase this arm sets occurs_max to 0, so compute_size gives the table no storage and every subscripted reference lands outside the record. With a TO phrase the table is a fixed table of TO elements, which is not the dynamic-capacity behavior either.
The attached occurs-dynamic-silent.patch is against 3.3-dev r5751 and applies with patch -p1 at the top of the tree. It replaces CB_PENDING ("OCCURS DYNAMIC") with cb_error (_("%s is not implemented"), "OCCURS DYNAMIC"). The message text stays the same and reuses the existing translated string, and an unimplemented intrinsic function is already an error in the same words (FUNCTION '%s' is not implemented, cobc/tree.c). The patch refuses both forms, with and without a TO phrase.
With the patch, built-in-tree cobc, no executable is produced:
$ cobc -std=cobol2014 -fformat=free -x occdyn2.cob
occdyn2.cob:8: error: OCCURS DYNAMIC is not implemented
6 | 01 J PIC 9 VALUE 2.
7 | 01 T.
8 > 05 E PIC X(3) OCCURS DYNAMIC.
9 | 01 W PIC X(3) VALUE "WWW".
10 | PROCEDURE DIVISION.
(exit 1)
The default dialect gives the same output, exit 1.
make check on 3.3-dev r5751 with the patch: 1,373 behaved as expected, 9 skipped, 0 failed, as unpatched (1,372 + the new test); the new test 163 "OCCURS DYNAMIC" (tests/testsuite.src/syn_occurs.at) passes. It checks the error for OCCURS DYNAMIC with and without CAPACITY IN ... FROM ... TO, with the default dialect and -std=cobol2014. No existing test used OCCURS DYNAMIC.
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, both re-run 2026-10-30. On 3.2.0 the Ubuntu C compiler step also prints <command-line>: warning: ‘_FORTIFY_SOURCE’ redefined and its note; that is the distribution's build setting.