From e36a124b2b7247b0b9bcded694ac3e007e461a01 Mon Sep 17 00:00:00 2001 From: lefessan Date: Wed, 31 Jan 2024 18:06:56 +0000 Subject: [PATCH] Add options --copy COPYBOOK and --include HEADER to cobc The --copy option can be used to include copybooks before parsing files, for example to perform replacements or include COBOL prototypes. The --include option can be used to include a HEADER in the generated C file, for example to perform verification of external calls combined with -fstatic-call. --- NEWS | 10 +++++ cobc/ChangeLog | 14 +++++++ cobc/cobc.c | 54 ++++++++++++++++++++++--- cobc/cobc.h | 3 ++ cobc/codegen.c | 11 ++++++ cobc/flag.def | 2 +- cobc/help.c | 4 ++ cobc/pplex.l | 59 +++++++++++++++++++++------- cobc/replace.c | 19 ++++++++- doc/gnucobol.texi | 18 ++++++++- tests/testsuite.src/syn_copy.at | 47 ++++++++++++++++++++++ tests/testsuite.src/used_binaries.at | 58 +++++++++++++++++++++++++++ 12 files changed, 276 insertions(+), 23 deletions(-) diff --git a/NEWS b/NEWS index e7480c254..55977763f 100644 --- a/NEWS +++ b/NEWS @@ -22,6 +22,16 @@ NEWS - user visible changes -*- outline -*- * Changes to the COBOL compiler (cobc) options: +** New option --copy COPYBOOK to load copybooks before parsing files. This + option can typically be used to perform replacements without modifying + the source code, or to add prototypes for external calls. + +** New option --include FILE.h to add a #include in the generated C file. + This option can typically be used to force the C compiler to check static + calls to externals. The files are put into quotes, unless they start by + '<'. Quoted files are expected to have absolute paths, as the C compiler + is called in a temp directory instead of the project directory. + ** output of unlimited errors may be requested by -fmax-errors=0, to stop compiliation at first error use -Wfatal-errors ** default value for -fmax-errors was changed from 128 to 20 diff --git a/cobc/ChangeLog b/cobc/ChangeLog index d249d3f50..4beeb50c3 100644 --- a/cobc/ChangeLog +++ b/cobc/ChangeLog @@ -1,4 +1,18 @@ +2023-10-12 Fabrice Le Fessant + + * cobc.c, codegen.c: new option --include FILE, to #include + additional files in the C generated code. Such files can be + used to statically check the number of arguments in static + calls, for example. The files are put into quotes, unless + they start by '<'. Since C files are compiled in a temp dir, + quoted files should be absolute paths. Implementing FR #176 + +2023-10-11 Fabrice Le Fessant + + * cobc.c, pplex.l: new option --copy COPYBOOK, to include a COPYBOOK + before reading the source file + 2023-11-29 Fabrice Le Fessant * cobc.c (cobc_clean_up): when save-temps specifies a directory, diff --git a/cobc/cobc.c b/cobc/cobc.c index 57f254cfb..5285e3b7f 100644 --- a/cobc/cobc.c +++ b/cobc/cobc.c @@ -105,7 +105,9 @@ enum compile_level { #define CB_FLAG_GETOPT_NO_DUMP 13 #define CB_FLAG_GETOPT_EBCDIC_TABLE 14 #define CB_FLAG_GETOPT_DEFAULT_COLSEQ 15 -#define CB_FLAG_MEMORY_CHECK 16 +#define CB_FLAG_GETOPT_MEMORY_CHECK 16 +#define CB_FLAG_GETOPT_COPY_FILE 17 +#define CB_FLAG_GETOPT_INCLUDE_FILE 18 /* Info display limits */ @@ -171,8 +173,8 @@ enum compile_level { #define GC_C_VERSION _("unknown") #endif -#define CB_TEXT_LIST_ADD(y,z) y = cb_text_list_add (y, z) -#define CB_TEXT_LIST_CHK(y,z) y = cb_text_list_chk (y, z) +#define CB_TEXT_LIST_ADD(list,z) list = cb_text_list_add (list, z) +#define CB_TEXT_LIST_CHK(list,z) list = cb_text_list_chk (list, z) #ifdef _MSC_VER #define CB_COPT_0 " /Od" @@ -232,6 +234,8 @@ const char *cb_cobc_build_stamp = NULL; const char *demangle_name = NULL; const char *cb_storage_file_name = NULL; const char *cb_call_extfh = NULL; +struct cb_text_list *cb_copy_list = NULL; +struct cb_text_list *cb_include_file_list = NULL; struct cb_text_list *cb_include_list = NULL; struct cb_text_list *cb_depend_list = NULL; struct cb_text_list *cb_intrinsic_list = NULL; @@ -595,6 +599,8 @@ static const struct option long_options[] = { {"save-temps", CB_OP_ARG, NULL, '_'}, {"std", CB_RQ_ARG, NULL, '$'}, {"conf", CB_RQ_ARG, NULL, '&'}, + {"copy", CB_RQ_ARG, NULL, CB_FLAG_GETOPT_COPY_FILE}, + {"include", CB_RQ_ARG, NULL, CB_FLAG_GETOPT_INCLUDE_FILE}, {"debug", CB_NO_ARG, NULL, 'd'}, {"ext", CB_RQ_ARG, NULL, 'e'}, /* note: kept *undocumented* until GC4, will be changed to '.' */ {"free", CB_NO_ARG, NULL, 'F'}, /* note: not assigned directly as this is only valid for */ @@ -3282,12 +3288,12 @@ process_command_line (const int argc, char **argv) cobc_wants_debug = 1; break; - case 8: + case CB_FLAG_GETOPT_DUMP: /* 8 */ /* -fdump= : Add sections for dump code generation */ cobc_def_dump_opts (cob_optarg, 1); break; - case 13: + case CB_FLAG_GETOPT_NO_DUMP: /* 13 */ /* -fno-dump= : Suppress sections in dump code generation */ if (cob_optarg) { cobc_def_dump_opts (cob_optarg, 0); @@ -3892,7 +3898,7 @@ process_command_line (const int argc, char **argv) } break; - case CB_FLAG_MEMORY_CHECK: /* 16 */ + case CB_FLAG_GETOPT_MEMORY_CHECK: /* 16 */ /* -fmemory-check= : */ if (!cob_optarg) { cb_flag_memory_check = CB_MEMCHK_ALL; @@ -3901,6 +3907,26 @@ process_command_line (const int argc, char **argv) } break; + case CB_FLAG_GETOPT_COPY_FILE: /* 17 */ + /* --copy= : COPY file at beginning */ + if (strlen (cob_optarg) > (COB_MINI_MAX)) { + cobc_err_exit (COBC_INV_PAR, "--copy"); + } + CB_TEXT_LIST_ADD (cb_copy_list, + cobc_strdup (cob_optarg)); + break; + + case CB_FLAG_GETOPT_INCLUDE_FILE: /* 18 */ + /* -include= : add #include "file.h" to + generated C file */ + if (strlen (cob_optarg) > (COB_MINI_MAX)) { + cobc_err_exit (COBC_INV_PAR, "--include"); + } + CB_TEXT_LIST_ADD (cb_include_file_list, + cobc_strdup (cob_optarg)); + cb_flag_c_decl_for_static_call = 0; + break; + case 'A': /* -A : Add options to C compile phase */ COBC_ADD_STR (cobc_cflags, " ", cob_optarg, NULL); @@ -9266,6 +9292,22 @@ main (int argc, char **argv) finish_setup_compiler_env (); finish_setup_internal_env (); + { + struct cb_text_list *l; + for (l = cb_copy_list; l; l=l->next){ + const char *filename; + int has_ext; + char name[COB_MINI_BUFF]; + int len = strlen (l->text); + memcpy (name, l->text, len+1); + has_ext = (strchr (name, '.') != NULL); + filename = cb_copy_find_file (name, has_ext); + if (!filename){ + cobc_err_exit (_("fatal error: could not find --copy argument %s"), name); + } + } + } + /* Reset source format in case text column has been configured manually. */ cobc_set_source_format (cobc_get_source_format ()); diff --git a/cobc/cobc.h b/cobc/cobc.h index 69ed6e441..7b5f30bbd 100644 --- a/cobc/cobc.h +++ b/cobc/cobc.h @@ -473,6 +473,8 @@ extern FILE *cb_listing_file; extern FILE *cb_src_list_file; extern FILE *cb_depend_file; extern struct cb_text_list *cb_depend_list; +extern struct cb_text_list *cb_copy_list; +extern struct cb_text_list *cb_include_file_list; extern struct cb_text_list *cb_include_list; extern struct cb_text_list *cb_intrinsic_list; extern struct cb_text_list *cb_extension_list; @@ -652,6 +654,7 @@ extern void cb_plex_error (const size_t, const char *, ...) COB_A_FORMAT23; extern unsigned int cb_plex_verify (const size_t, const enum cb_support, const char *); +extern const char *cb_copy_find_file (char *name, int has_ext); extern void configuration_warning (const char *, const int, const char *, ...) COB_A_FORMAT34; extern void configuration_error (const char *, const int, diff --git a/cobc/codegen.c b/cobc/codegen.c index 293e3b4e3..ab323a4f9 100644 --- a/cobc/codegen.c +++ b/cobc/codegen.c @@ -1829,6 +1829,17 @@ output_gnucobol_defines (const char *formatted_date) current_compile_tm.tm_sec; output_line ("#define COB_MODULE_TIME\t\t%d", i); + { + struct cb_text_list *l = cb_include_file_list ; + for (;l;l=l->next){ + if (l->text[0] == '<'){ + output_line ("#include %s", l->text); + } else { + output_line ("#include \"%s\"", l->text); + } + } + } + } /* CALL cache */ diff --git a/cobc/flag.def b/cobc/flag.def index d362ee99b..ff16d6f3d 100644 --- a/cobc/flag.def +++ b/cobc/flag.def @@ -166,7 +166,7 @@ CB_FLAG (cb_flag_stack_check, 1, "stack-check", _(" -fstack-check PERFORM stack checking\n" " * turned on by --debug/-g")) -CB_FLAG_OP (1, "memory-check", CB_FLAG_MEMORY_CHECK, +CB_FLAG_OP (1, "memory-check", CB_FLAG_GETOPT_MEMORY_CHECK, _(" -fmemory-check= checks for invalid writes to internal storage,\n" " may be one of: all, pointer, using, none\n" " * default: none, set to all by --debug")) diff --git a/cobc/help.c b/cobc/help.c index 4f4dda32b..3e95a2e19 100644 --- a/cobc/help.c +++ b/cobc/help.c @@ -116,7 +116,11 @@ cobc_print_usage_common_options (void) puts (_(" -X, --Xref specify cross reference in listing")); #endif puts (_(" -I add to copy/include search path")); + puts (_(" --copy include at beginning of file,\n" + " as would COPY copybook.")); puts (_(" -L add to library search path")); + puts (_(" --include add a #include \"file.h\" at the beginning of the C\n" + " generated file (implies -fno-gen-c-decl-static-call)")); puts (_(" -l link the library ")); puts (_(" -K generate CALL to as static")); puts (_(" -D define for COBOL compilation")); diff --git a/cobc/pplex.l b/cobc/pplex.l index 1ecf3672b..02bb2640c 100644 --- a/cobc/pplex.l +++ b/cobc/pplex.l @@ -59,19 +59,21 @@ static int ppwrap (void) { return 1; } +static void insert_copy_arg (void); + #define PPLEX_BUFF_LEN 512 #define YY_INPUT(buf,result,max_size) result = ppinput (buf, max_size); #define ECHO fputs (yytext, yyout) +/* The first --copy COPYBOOK is inserted using this macro. The next + ones will be inserted in <>, when we come back to the toplevel + source file. */ #define YY_USER_INIT \ - if (!plexbuff1) { \ - plexbuff1 = cobc_malloc ((size_t)COB_SMALL_BUFF); \ - } \ - if (!plexbuff2) { \ - plexbuff2 = cobc_malloc ((size_t)COB_SMALL_BUFF); \ - } \ requires_listing_line = 1; \ - comment_allowed = 1; + comment_allowed = 1; \ + copy_list_pointer = cb_copy_list; \ + insert_copy_arg (); + #include "config.h" @@ -179,6 +181,20 @@ static void output_pending_newlines (FILE *); static struct cb_text_list *pp_text_list_add (struct cb_text_list *, const char *, const size_t); +static struct cb_text_list *copy_list_pointer = NULL; + +static void insert_copy_arg (void) +{ + if (copy_list_pointer != NULL){ + int ret = ppcopy (copy_list_pointer->text, NULL, NULL); + if ( ret < 0 ){ /* This should never happen, as we already test it before */ + cobc_err_msg (_("fatal error: %s"), "could not find --copy argument"); + cobc_abort_terminate (0); + } + copy_list_pointer = copy_list_pointer->next; + } +} + %} WORD [_0-9A-Z\x80-\xFF-]+ @@ -1205,6 +1221,13 @@ ENDIF_DIRECTIVE_STATE>{ copy_stack = current_copy_info->next; cobc_free (current_copy_info->dname); cobc_free (current_copy_info); + + /* Check whether we are back at the toplevel source file. In this case, + check if there is a pending copy argument (--copy COPYBOOK) waiting + to be inserted. */ + if (copy_stack->next == NULL){ + insert_copy_arg(); + } } %% @@ -1480,6 +1503,10 @@ ppcopy_try_open (const char *dir, const char *name, int has_ext) const char *extension = ""; struct stat st; + if (!plexbuff2) { + plexbuff2 = cobc_malloc ((size_t)COB_SMALL_BUFF); + } + for (;;) { if (dir) { snprintf (plexbuff2, (size_t)COB_SMALL_MAX, "%s%c%s%s", @@ -1520,8 +1547,8 @@ ppcopy_try_open (const char *dir, const char *name, int has_ext) each with all known copybook extensions: 1 - as is 2 - all known copybook directories */ -static const char * -ppcopy_find_file (char *name, int has_ext) +const char * +cb_copy_find_file (char *name, int has_ext) { const char *filename; { @@ -1597,6 +1624,10 @@ ppcopy (const char *name, const char *lib, struct cb_replace_list *replace_list) cb_current_file->copy_line = cb_source_line; } + if (!plexbuff1) { + plexbuff1 = cobc_malloc ((size_t)COB_SMALL_BUFF); + } + /* TODO: open with path relative to the current file's path, if any (applies both to with and without "lib") */ @@ -1620,10 +1651,10 @@ ppcopy (const char *name, const char *lib, struct cb_replace_list *replace_list) snprintf (plexbuff1, (size_t)COB_SMALL_MAX, "%s%c%s", lib_env, SLASH_CHAR, name); plexbuff1[COB_SMALL_MAX] = 0; - filename = ppcopy_find_file (plexbuff1, has_ext); + filename = cb_copy_find_file (plexbuff1, has_ext); } else { strcpy (plexbuff1, name); - filename = ppcopy_find_file (plexbuff1, has_ext); + filename = cb_copy_find_file (plexbuff1, has_ext); } } } @@ -1634,13 +1665,13 @@ ppcopy (const char *name, const char *lib, struct cb_replace_list *replace_list) snprintf (plexbuff1, (size_t)COB_SMALL_MAX, "%s%c%s", lib, SLASH_CHAR, name); plexbuff1[COB_SMALL_MAX] = 0; - filename = ppcopy_find_file (plexbuff1, has_ext); + filename = cb_copy_find_file (plexbuff1, has_ext); } /* try without library name, if not resolved by env */ if (!filename && !lib_env) { strcpy (plexbuff1, name); - filename = ppcopy_find_file (plexbuff1, has_ext); + filename = cb_copy_find_file (plexbuff1, has_ext); if (filename) { cb_plex_warning (COBC_WARN_FILLER, 0, _("copybook not found in library '%s', library-name ignored"), @@ -1656,7 +1687,7 @@ ppcopy (const char *name, const char *lib, struct cb_replace_list *replace_list) } } else { strcpy (plexbuff1, name); - filename = ppcopy_find_file (plexbuff1, has_ext); + filename = cb_copy_find_file (plexbuff1, has_ext); } /* expected case: filename found */ diff --git a/cobc/replace.c b/cobc/replace.c index cabaa372d..6ebe254f3 100644 --- a/cobc/replace.c +++ b/cobc/replace.c @@ -193,6 +193,17 @@ STRING_OF_LIST(token) /* string_of_text_list (...) */ STRING_OF_LIST(text) +static void dump_replacement(struct cb_replacement_state* repls) +{ + fprintf(stderr, "dump_replacement('%s'):\n", repls->name); + struct cb_replace_list *list = repls->replace_list ; + for (;list;list = list->next){ + fprintf(stderr, " replace: %s\n", string_of_text_list (list->src->text_list)); + fprintf(stderr, " by: %s\n", string_of_text_list (list->new_text)); + } + fprintf(stderr, "=================================================================\n"); +} + #endif /* DEBUG_REPLACE */ /* global state */ @@ -759,7 +770,6 @@ cb_free_replace (void) reset_replacements (copy_repls); reset_replacements (replace_repls); #endif - cobc_free (copy_repls); copy_repls = NULL; @@ -772,6 +782,10 @@ cb_free_replace (void) struct cb_replace_list * cb_get_copy_replacing_list (void) { +#ifdef DEBUG_REPLACE_TRACE + fprintf (stderr, "cb_get_copy_replacing_list()\n"); +#endif + if (copy_repls == NULL) { #ifdef DEBUG_REPLACE_TRACE int i; @@ -845,4 +859,7 @@ cb_set_replace_list (struct cb_replace_list *list, const int is_pushpop) if (cb_src_list_file) { cb_set_print_replace_list (list); } +#ifdef DEBUG_REPLACE_TRACE + dump_replacement(replace_repls); +#endif } diff --git a/doc/gnucobol.texi b/doc/gnucobol.texi index 0ed1f9f0a..7aa0abd4c 100644 --- a/doc/gnucobol.texi +++ b/doc/gnucobol.texi @@ -365,10 +365,26 @@ The following options specify the target type produced by the compiler: @table @code @item -E Preprocess only: compiler directives are executed, comment lines are -removed and @code{COPY} statements are expanded. +removed, and @code{COPY} and @code{REPLACE} statements are performed. The output is sent to stdout, allowing you to directly use it as input for another process. You can manually set an output file using @option{-o}. +@item --copy @var{copybook} +Include @file{copybook} at the beginning of the source code, as if +@code{COPY copybook} had been parsed. + +@item --include @var{file.h} +Add a @code{#include} @file{file.h} at the beginning of the generated +C source file. The file name is put into quotes, unless it starts by +@code{<}. Quoted files should be absolute paths, since C files are compiled +in temporary directories. +The option also implies @option{-fno-gen-c-decl-static-call}. +This option can be used to check function prototypes when +static calls are used. When this option is used, the source file is +compiled in the project directory (instead of the temp directory), and +no prototypes are generated, so ALL static call functions must appear +in the header file, with GnuCOBOL compatible types. + @item -C Translation only. COBOL source files are translated into C files. The output is saved in file @file{*.c}. diff --git a/tests/testsuite.src/syn_copy.at b/tests/testsuite.src/syn_copy.at index 0ac0784b2..ad1550a18 100644 --- a/tests/testsuite.src/syn_copy.at +++ b/tests/testsuite.src/syn_copy.at @@ -1072,3 +1072,50 @@ AT_DATA([prog.cob], [ AT_CHECK([$COMPILE_ONLY prog.cob], [0], [], []) AT_CLEANUP + + +AT_SETUP([Option --copy COPYBOOK]) +AT_KEYWORDS([argument]) + +AT_DATA([copybook1.CPY], [ + REPLACE ==BEGIN PROGRAM== BY ==IDENTIFICATION DIVISION. + PROGRAM-ID.==. +]) +AT_DATA([copybook2.CPY], [ + REPLACE ALSO ==output== BY =="Hello world"==. +]) +AT_DATA([copybook3.CPY], [ + REPLACE ALSOO ==output== BY =="Hello world"==. +]) +AT_DATA([prog.cob], [ + BEGIN PROGRAM prog. + PROCEDURE DIVISION. + DISPLAY output + STOP RUN. +]) + +AT_CHECK([$COMPILE_ONLY prog.cob], [1], [], +[prog.cob:2: error: PROGRAM-ID header missing +prog.cob:2: error: ENVIRONMENT DIVISION header missing +prog.cob:2: error: CONFIGURATION SECTION header missing +prog.cob:2: error: SPECIAL-NAMES header missing +prog.cob:2: error: invalid system-name 'BEGIN' +prog.cob:2: error: syntax error, unexpected PROGRAM, expecting CRT or Identifier +prog.cob:2: error: invalid system-name 'prog' +prog.cob:2: error: syntax error, unexpected ., expecting CRT or Identifier +prog.cob:3: error: syntax error, unexpected PROCEDURE +prog.cob:4: error: PROCEDURE DIVISION header missing +]) + +AT_CHECK([$COMPILE_ONLY --copy copybook1 --copy copybook2 prog.cob], [0], [], []) + +AT_CHECK([$COMPILE_ONLY --copy copybook1 --copy copybook3 prog.cob], [1], [], +[copybook3.CPY:2: error: syntax error, unexpected ==, expecting BY +copybook3.CPY:2: error: PROGRAM-ID header missing +]) + +AT_CHECK([$COMPILE_ONLY --copy copybook1 --copy copybook4 prog.cob], [1], [], +[cobc: error: fatal error: could not find --copy argument copybook4 +]) + +AT_CLEANUP diff --git a/tests/testsuite.src/used_binaries.at b/tests/testsuite.src/used_binaries.at index b99b7e6f4..817d61c50 100644 --- a/tests/testsuite.src/used_binaries.at +++ b/tests/testsuite.src/used_binaries.at @@ -1081,3 +1081,61 @@ HOME/prog.cob:14: warning: ignoring redundant . AT_CLEANUP + +AT_SETUP([check include header file]) +AT_KEYWORDS([-include]) + +AT_DATA([file.h], [ +extern void f(char *, long ); +]) +AT_DATA([prog.cob], [ + IDENTIFICATION DIVISION. + PROGRAM-ID. prog. + PROCEDURE DIVISION. + CALL "f" USING "Hello". +]) + +# No check, program seems correct + +AT_CHECK([$COBC -m -fstatic-call prog.cob], [0], [], []) + +# We ignore the error output, as it depends on the C compiler in use + +AT_CHECK([$COBC -m --include "$PWD/file.h" -fstatic-call prog.cob], [1], [], [ignore]) +AT_DATA([prog2.cob], [ + IDENTIFICATION DIVISION. + PROGRAM-ID. prog. + DATA DIVISION. + WORKING-STORAGE SECTION. + 01 long USAGE BINARY-C-LONG. + PROCEDURE DIVISION. + CALL "f" USING "Hello" BY VALUE long RETURNING NOTHING. +]) + +AT_CHECK([$COBC -m --include "$PWD/file.h" -fstatic-call prog2.cob], [0], [], []) + +AT_CHECK([$COBC -I . -m --include "file.h" -fstatic-call prog2.cob], [0], [], []) + +# We can use --copy to check a CALL against a prototype. However, this +# feature is not fully supported by GnuCOBOL yet, so we get some +# warnings. For exemple: +# * not putting RETURNING triggers an error +# * putting RETURNING NOTHING is not supported +# * putting RETURNING OMITTED is ok, but triggers a warning (see stderr) + +AT_DATA([f.copy], [ + IDENTIFICATION DIVISION. + PROGRAM-ID. f PROTOTYPE. + DATA DIVISION. + LINKAGE SECTION. + 01 a PIC X(20). + 01 b BINARY-C-LONG. + PROCEDURE DIVISION USING a BY VALUE b RETURNING OMITTED. + END PROGRAM f. +]) + +AT_CHECK([$COMPILE_MODULE -Wno-unfinished --copy "f.copy" -fstatic-call prog2.cob], [0], [], +[prog2.cob:8: warning: unexpected RETURNING item +]) + +AT_CLEANUP