From 452d4ec5473657e01694629b49e3f45ad94fe239 Mon Sep 17 00:00:00 2001 From: Vedant Tewari Date: Tue, 9 Jul 2024 08:26:25 -0500 Subject: [PATCH 1/7] Initial handling of calls to static Java methods Changes mostly include: * New option `--with{out}-java` for `configure` script * Runtime support for calling `static void (void)` methods * Lookup of JVM library when JAVA_HOME is set, and pre-load when possible * Two preliminary tests Co-authored-by: Nicolas Berthier Co-authored-by: Simon Sobisch --- ChangeLog | 8 +- DEPENDENCIES | 15 +++ DEPENDENCIES.md | 10 ++ NEWS | 2 +- cobc/ChangeLog | 4 + cobc/cobc.c | 6 ++ cobc/codegen.c | 68 ++++++++++++- cobc/parser.y | 5 + cobc/tree.h | 1 + configure.ac | 62 +++++++++++- libcob/ChangeLog | 10 ++ libcob/Makefile.am | 16 +++- libcob/call.c | 161 ++++++++++++++++++++++++++++++- libcob/common.c | 6 ++ libcob/common.h | 17 ++++ libcob/fileio.c | 13 --- libcob/java.c | 128 +++++++++++++++++++++++++ libcob/sysdefines.h | 12 +++ m4/ax_jni_include_dir.m4 | 163 ++++++++++++++++++++++++++++++++ m4/ax_prog_java.m4 | 115 ++++++++++++++++++++++ m4/ax_prog_java_works.m4 | 91 ++++++++++++++++++ m4/ax_prog_javac.m4 | 79 ++++++++++++++++ m4/ax_prog_javac_works.m4 | 72 ++++++++++++++ tests/ChangeLog | 4 + tests/Makefile.am | 1 + tests/atlocal.in | 8 ++ tests/testsuite.at | 1 + tests/testsuite.src/run_java.at | 67 +++++++++++++ 28 files changed, 1123 insertions(+), 22 deletions(-) create mode 100644 libcob/java.c create mode 100644 m4/ax_jni_include_dir.m4 create mode 100644 m4/ax_prog_java.m4 create mode 100644 m4/ax_prog_java_works.m4 create mode 100644 m4/ax_prog_javac.m4 create mode 100644 m4/ax_prog_javac_works.m4 create mode 100644 tests/testsuite.src/run_java.at diff --git a/ChangeLog b/ChangeLog index 8af2a8eae..8a5188323 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,11 @@ + +2024-07-12 Vedant Tewari -2023-02-25 Ron Norman + * ax_prog_java.m4: Added macro for jni check + * ax_jni_include_dir.m4: Added macro for jni check + * configure.ac: added support for Java interoperability through JNI + +2023-02-25 Ron Norman * configure.ac: Add check for sys/time.h diff --git a/DEPENDENCIES b/DEPENDENCIES index 2a1bde415..5a96a03ef 100644 --- a/DEPENDENCIES +++ b/DEPENDENCIES @@ -137,6 +137,21 @@ The following libraries ARE required WHEN : JSON-C is distributed under Expat License. +5) JNI (Java Native Interface) support is used + + BOTH runtime AND development components required. + + Java Development Kit (JDK) 8 or later + + https://openjdk.org/ + + The JDK is distributed under various open-source licenses depending + on the vendor and version. Common licenses include the GNU General + Public License (GPL) and the Oracle Binary Code License Agreement. + + To enable JNI support, ensure that the JDK is installed on your system, + and set the appropriate environment variables (e.g., JAVA_HOME) to point + to the JDK installation directory. See HACKING if you wish to hack the GnuCOBOL source or build directly from version control as this includes the list of additional tools diff --git a/DEPENDENCIES.md b/DEPENDENCIES.md index 266459ff7..a94327471 100644 --- a/DEPENDENCIES.md +++ b/DEPENDENCIES.md @@ -119,3 +119,13 @@ Support for GENERATE JSON is provided by *one* of the following: JSON-C is distributed under Expat License. +JNI Support +------------ + +Support for JNI (Java Native Interface) is provided by: + +* [Java Development Kit (JDK)](https://openjdk.java.net/) 8 or later. + + The JDK is distributed under various open-source licenses depending on the vendor and version. Common licenses include the GNU General Public License (GPL) and the Oracle Binary Code License Agreement. + +To enable JNI support, ensure that the JDK is installed on your system, and set the appropriate environment variables (e.g., JAVA_HOME) to point to the JDK installation directory. \ No newline at end of file diff --git a/NEWS b/NEWS index 72f3e9ed6..3b2393747 100644 --- a/NEWS +++ b/NEWS @@ -20,7 +20,7 @@ NEWS - user visible changes -*- outline -*- * New GnuCOBOL features - +** Initial support for Java interoperability through JNI (new optional dependency JDK) ** file handling: added backends for ODBC (so far PostgrSQL, MySQL, SQLite, MSSQL) and OCI, along with new directory COB_SCHEMA_DIR containing the necessary internal schema files to match the file definition to the diff --git a/cobc/ChangeLog b/cobc/ChangeLog index 9aa4a85e9..651f43bfe 100644 --- a/cobc/ChangeLog +++ b/cobc/ChangeLog @@ -1,4 +1,8 @@ +2024-08-14 Nicolas Berthier + + * cobc.c (cobc_print_info): added note for Java interoperability + 2024-08-04 David Declerck Adjustments to merge 2022-12-21: diff --git a/cobc/cobc.c b/cobc/cobc.c index 88d357929..9c5369589 100644 --- a/cobc/cobc.c +++ b/cobc/cobc.c @@ -2620,6 +2620,12 @@ cobc_print_info (void) cobc_var_print (_("JSON library"), _(WITH_JSON), 0); +#ifdef WITH_JNI + cobc_var_print (_("Java interoperability"), _("enabled"), 0); +#else + cobc_var_print (_("Java interoperability"), _("disabled"), 0); +#endif + #ifdef COB_DEBUG_LOG cobc_var_print ("DEBUG_LOG", _("enabled"), 0); #endif diff --git a/cobc/codegen.c b/cobc/codegen.c index 61bbe9300..2d7dd9071 100644 --- a/cobc/codegen.c +++ b/cobc/codegen.c @@ -1,7 +1,7 @@ /* Copyright (C) 2003-2024 Free Software Foundation, Inc. Written by Keisuke Nishida, Roger While, Ron Norman, Simon Sobisch, - Edward Hart + Edward Hart, Vedant Tewari This file is part of GnuCOBOL. @@ -147,6 +147,7 @@ static struct literal_list *literal_cache = NULL; static struct field_list *field_cache = NULL; static struct field_list *local_field_cache = NULL; static struct call_list *call_cache = NULL; +static struct call_list *call_java_cache = NULL; static struct call_list *func_call_cache = NULL; static struct static_call_list *static_call_cache = NULL; static struct base_list *base_cache = NULL; @@ -395,6 +396,22 @@ lookup_source (const char *p) return source_id++; } +static void +lookup_java_call(const char *p) +{ + struct call_list *clp; + + for (clp = call_java_cache; clp; clp = clp->next) { + if (strcmp (p, clp->call_name) == 0) { + return; + } + } + clp = cobc_parse_malloc (sizeof (struct call_list)); + clp->call_name = p; + clp->next = call_java_cache; + call_java_cache = clp; +} + static void lookup_call (const char *p) { @@ -1978,6 +1995,11 @@ output_call_cache (void) output_local ("static cob_call_union\tcall_%s;\n", call->call_name); } + call_java_cache = call_list_reverse (call_java_cache); + for (call = call_java_cache; call; call = call->next) { + output_local ("static cob_java_handle*\tcall_java_%s;\n", + call->call_name); + } func_call_cache = call_list_reverse (func_call_cache); for (call = func_call_cache; call; call = call->next) { output_local ("static cob_call_union\tfunc_%s;\n", @@ -7068,6 +7090,45 @@ output_field_constant (cb_tree x, int n, const char *flagname) output_newline (); } +static void +output_java_call (struct cb_call *p) +{ + char* full_name = (char *)CB_LITERAL(p->name)->data; /* Assume java.prefix (enforced in `parser.y`, rule `call_body`)*/ + char* class_and_method_name = full_name + 5; + char *last_dot; + char *method_name; + const char *class_name; + char* mangled; + + mangled = strdup(class_and_method_name); + for (size_t i = 0; i < strlen(mangled) + 1; i++) { + mangled[i] = (mangled[i] == '.') ? '_' : mangled[i]; + } + + last_dot = strrchr(class_and_method_name, '.'); + if (last_dot == NULL) { + cobc_err_msg (_("malformed call '%s' to a Java method"), class_and_method_name); + COBC_ABORT (); + } + + *last_dot = '\0'; + method_name = last_dot + 1; + class_name = class_and_method_name; + + lookup_java_call(mangled); + output_line("if (call_java_%s == NULL)", mangled); + output_block_open(); + + output_prefix(); + output_line("call_java_%s = ", mangled); + output("cob_resolve_java(\"%s\", \"%s\", \"()V\");", class_name, method_name); + output_newline (); + output_prefix (); + output_line("cob_call_java(call_java_%s);\n", mangled); + output_newline(); + output_block_close(); +} + static void output_call (struct cb_call *p) { @@ -7097,6 +7158,11 @@ output_call (struct cb_call *p) } system_call = NULL; + if (p->convention & CB_CONV_JAVA) { + output_java_call(p); + return; + } + #ifdef _WIN32 if (p->convention & CB_CONV_STDCALL) { convention = "_std"; diff --git a/cobc/parser.y b/cobc/parser.y index b86ff0288..ce6d0c2ea 100644 --- a/cobc/parser.y +++ b/cobc/parser.y @@ -12253,6 +12253,11 @@ call_body: /* Check parameter conformance, if we can work out what is being called. */ if (CB_LITERAL_P ($3)) { + /* Check for "Java." prefix and set call convention */ + char* s = (char *)CB_LITERAL ($3)->data; + if (strncasecmp("Java.", s, 5) == 0) { + call_conv = CB_CONV_JAVA; + } cb_check_conformance ($3, $7, $8); } else if (CB_REFERENCE_P ($3)) { cb_tree ref = cb_ref ($3); diff --git a/cobc/tree.h b/cobc/tree.h index 55278955d..ea3484d0d 100644 --- a/cobc/tree.h +++ b/cobc/tree.h @@ -199,6 +199,7 @@ enum cb_tag { #define CB_CONV_THUNK_16 (1 << 5) #define CB_CONV_STDCALL (1 << 6) #define CB_CONV_COBOL (1 << 15) +#define CB_CONV_JAVA (1 << 16) #define CB_CONV_C (0) #define CB_CONV_PASCAL (CB_CONV_L_TO_R | CB_CONV_CALLEE_STACK) diff --git a/configure.ac b/configure.ac index 5083447ba..a06017d49 100644 --- a/configure.ac +++ b/configure.ac @@ -113,6 +113,8 @@ AH_TEMPLATE([WITH_JSON], [JSON handler]) AH_TEMPLATE([WITH_CJSON], [Use cJSON library/source as JSON handler]) AH_TEMPLATE([WITH_JSON_C], [Use JSON-C library as JSON handler]) +AH_TEMPLATE([WITH_JNI], [Support for Java calls through JNI]) + AH_TEMPLATE([COB_EXPORT_DYN], [Compile/link option for exporting symbols]) AH_TEMPLATE([COB_PIC_FLAGS], [Compile/link option for PIC code]) AH_TEMPLATE([COB_DEBUG_FLAGS], [Compile/link option for debugging]) @@ -483,7 +485,6 @@ AC_CHECK_HEADERS([sys/types.h signal.h stddef.h], [], # optional: AC_CHECK_HEADERS([sys/time.h locale.h fcntl.h dlfcn.h sys/wait.h sys/sysmacros.h]) - # Checks for typedefs, structures, and compiler characteristics. AC_C_CONST AC_C_BIGENDIAN @@ -933,7 +934,56 @@ AS_IF([test "$with_xml2" = "yes" -o "$with_xml2" = "check"], [ LIBS="$curr_libs"; CPPFLAGS="$curr_cppflags" ]) +# Check for JNI +AC_ARG_WITH([java], + [AS_HELP_STRING([--without-java], + [disable Java Interoperability])]) +cob_has_jni=no +AS_IF([test "x$with_java" != "xno"], [ + dnl Find `java` and $JAVA_HOME (we need the latter to locate libjvm). + dnl AX_CHECK_JAVA_HOME dnl (<- does not appear to work properly) + AX_PROG_JAVA + if test "x$JAVA_HOME" = x; then + JAVA_HOME="$( $JAVA -XshowSettings:properties -version 2>&1 >/dev/null | \ + $SED -e '/^[ ]*java.home/!d' -e 's/.*=[ ]*//' )" + AC_MSG_NOTICE([Found Java home: ${JAVA_HOME}]) + else + AC_MSG_NOTICE([Given Java home: ${JAVA_HOME}]) + fi + dnl Note: AX_PROG_JAVAC might find a `javac` binary that does + dnl not match the version of `$JAVA` found above, so we set + dnl its path manually. + JAVAC="$JAVA_HOME/bin/javac" + AX_PROG_JAVAC_WORKS + + AX_JNI_INCLUDE_DIR + AS_IF([test "$JNI_INCLUDE_DIRS" != ""], [ + for JNI_INCLUDE_DIR in $JNI_INCLUDE_DIRS; do + JNI_CPPFLAGS="$JNI_CPPFLAGS -I$JNI_INCLUDE_DIR" + done + for _dir in "${JAVA_HOME}/jre/lib" "${JAVA_HOME}/lib"; do + if test -d "$_dir"; then + JNI_LIBS="$JNI_LIBS -L$_dir" + fi + if test -d "$_dir/server"; then + JNI_LIBS="$JNI_LIBS -L$_dir/server" + fi + done + curr_LIBS="$LIBS" + LIBS="$LIBS $JNI_LIBS" + AC_CHECK_LIB([jvm], [JNI_CreateJavaVM], [ + AC_DEFINE([WITH_JNI], [1]) + JNI_LDFLAGS="$JNI_LIBS" + JNI_LIBS="-ljvm" + cob_has_jni=yes + ]) + LIBS="$curr_LIBS" + ]) +]) +AS_IF([test "x$with_java" = "xyes" -a "x$cob_has_jni" != "xyes"], [ + AC_MSG_ERROR([Java interoperability requested, but JNI was not found]) +]) # Checks for cjson/json-c. AC_MSG_NOTICE([Checks for JSON handler]) @@ -2570,6 +2620,8 @@ AM_CONDITIONAL([COB_MAKE_LMDB_LIB], [test "$cob_gen_lmdb" = "yes"]) AM_CONDITIONAL([LOCAL_CJSON],[test "$USE_JSON" = "local"]) +AM_CONDITIONAL([COB_HAS_JNI], [test "$cob_has_jni" = "yes"]) + unset COB_USES_GCC unset COB_USES_GCC_NO_ICC unset COB_USES_ICC_ONLY @@ -2660,6 +2712,11 @@ AC_SUBST([OCI_CFLAGS]) AC_SUBST([BDB_CFLAGS]) AC_SUBST([LMDB_CFLAGS]) +AC_SUBST([JAVAC]) +AC_SUBST([JNI_LIBS]) +AC_SUBST([JNI_LDFLAGS]) +AC_SUBST([JNI_CPPFLAGS]) + dnl was used in bin/Makefile.am - seems not to be needed dnl AC_SUBST([COB_EXPORT_DYN]) @@ -2683,6 +2740,7 @@ AC_SUBST([COB_HAS_OCEXTFH]) AC_SUBST([COB_HAS_CURSES]) AC_SUBST([COB_HAS_XML2]) AC_SUBST([COB_HAS_JSON]) +AC_SUBST([COB_HAS_JNI]) AC_SUBST([COB_HAS_64_BIT_POINTER]) AC_SUBST([COB_PATCH_LEVEL], [$with_patch_level]) # needed for bin/cob-config @@ -2804,4 +2862,6 @@ case "$USE_JSON" in ;; esac +AC_MSG_NOTICE([ Build with Java interoperability: ${cob_has_jni}]) + unset DEFINE_DL diff --git a/libcob/ChangeLog b/libcob/ChangeLog index 70a953966..2d206626b 100644 --- a/libcob/ChangeLog +++ b/libcob/ChangeLog @@ -1,4 +1,14 @@ +2024-08-14 Nicolas Berthier + + * common.c (print_info_detailed): added note for Java interoperability + +2024-06-12 Vedant Tewari + + * Makefile.am: Updated to include JNI-related files + * common.h: Added internal state for JNI support + * java.c: Implemented JNI support for GnuCOBOL + 2024-08-04 David Declerck Adjustments to merge 2022-12-21: diff --git a/libcob/Makefile.am b/libcob/Makefile.am index 272481a4c..1fe6d084b 100644 --- a/libcob/Makefile.am +++ b/libcob/Makefile.am @@ -1,8 +1,8 @@ # # Makefile gnucobol/libcob # -# Copyright (C) 2003-2012, 2014, 2017-2023 Free Software Foundation, Inc. -# Written by Keisuke Nishida, Roger While, Simon Sobisch +# Copyright (C) 2003-2012, 2014, 2017-2024 Free Software Foundation, Inc. +# Written by Keisuke Nishida, Roger While, Simon Sobisch, Vedant Tewari # # This file is part of GnuCOBOL. # @@ -125,8 +125,18 @@ else lib_lm = endif +if COB_HAS_JNI +lib_jni = libcobjni.la +libcobjni_la_LIBADD = libcob.la $(JNI_LIBS) +libcobjni_la_SOURCES = java.c +libcobjni_la_LDFLAGS = $(AM_LDFLAGS) $(JNI_LDFLAGS) -version-info 1:0:0 +libcobjni_la_CFLAGS = $(AM_CFLAGS) $(JNI_CPPFLAGS) +else +lib_jni = +endif + lib_LTLIBRARIES = libcob.la $(lib_ci) $(lib_di) $(lib_vb) $(lib_vc) \ - $(lib_od) $(lib_oc) $(lib_db) $(lib_lm) + $(lib_od) $(lib_oc) $(lib_db) $(lib_lm) $(lib_jni) EXTRA_DIST = fisam.c pkgincludedir = $(includedir)/libcob diff --git a/libcob/call.c b/libcob/call.c index 61fdf3d51..b927b63ce 100644 --- a/libcob/call.c +++ b/libcob/call.c @@ -123,6 +123,15 @@ lt_dlerror (void) #endif +#if defined (_WIN32) || defined (USE_LIBDL) +/* Try pre-loading libjvm/jvm.dll if JAVA_HOME is set. */ +# define JVM_PRELOAD 1 +static lt_dlhandle jvm_handle = NULL; +#else +/* Using libltdl, no need to preload. */ +# define JVM_PRELOAD 0 +#endif + #include "sysdefines.h" /* Force symbol exports */ @@ -785,7 +794,7 @@ cob_encode_program_id (const unsigned char *const name, default: break; } - + return pos; } @@ -1160,6 +1169,7 @@ cob_load_lib (const char *library, const char *entry, char *reason) } #endif + DEBUG_LOG ("call", ("lt_dlopenlcl '%s'\n", library)); p = lt_dlopenlcl (library); if (p) { p = lt_dlsym (p, entry); @@ -1275,7 +1285,7 @@ cob_module_clean (cob_module *m) { struct call_hash *p; struct call_hash **q; - + #ifndef COB_ALT_HASH const char *entry; @@ -1845,6 +1855,13 @@ cob_exit_call (void) } base_dynload_ptr = NULL; +#if JVM_PRELOAD + if (jvm_handle) { + lt_dlclose (jvm_handle); + jvm_handle = NULL; + } +#endif + #if !defined(_WIN32) && !defined(USE_LIBDL) lt_dlexit (); #if 0 /* RXWRXW - ltdl leak */ @@ -1974,3 +1991,143 @@ cob_init_call (cob_global *lptr, cob_settings* sptr, const int check_mainhandle) call_lastsize = CALL_BUFF_SIZE; } +/* Java API handling */ + +/* "Standard" path suffixes to the dynamically loadable JVM library, from + "typical" JAVA_HOME. */ +const char* const path_to_jvm[] = { +#if defined(_WIN32) || defined(__CYGWIN__) +# define JVM_FILE "jvm.dll" + "\\jre\\bin\\server", + "\\jre\\bin\\client", +#else +# define JVM_FILE "libjvm." COB_MODULE_EXT + "/lib/server", + "/lib/client", +#endif + NULL, +}; + +static void +init_jvm_search_dirs (void) { + const char *java_home; + const char *path_suffix = NULL; + char jvm_path[COB_FILE_MAX]; + unsigned int i = 0; + + if ((java_home = getenv ("JAVA_HOME")) == NULL) { + DEBUG_LOG ("call", ("JAVA_HOME is not defined\n")); + return; + } + + DEBUG_LOG ("call", ("JAVA_HOME='%s'\n", java_home)); + + while ((path_suffix = path_to_jvm[i++]) != NULL) { +#if JVM_PRELOAD + /* Lookup libjvm.so/jvm.dll */ + if (snprintf (jvm_path, (size_t)COB_FILE_MAX, "%s%s%c%s", + java_home, path_suffix, + SLASH_CHAR, JVM_FILE) == 0) { + continue; + } + if (access (jvm_path, F_OK) != 0) { + DEBUG_LOG ("call", ("'%s': not found\n", jvm_path)); + continue; + } + DEBUG_LOG ("call", ("preloading '%s': ", jvm_path)); + jvm_handle = lt_dlopen (jvm_path); + DEBUG_LOG ("call", ("%s\n", jvm_handle != NULL ? "success" : "failed")); + break; +#else + /* Append to search path. */ +# warning On some systems, JAVA_HOME-based lookup via `libltdl` does not work + if (snprintf (jvm_path, (size_t)COB_FILE_MAX, "%s%s", + java_home, path_suffix) == 0) { + continue; + } + DEBUG_LOG ("call", ("appending '%s' to load path: ", jvm_path)); + int success = lt_dladdsearchdir (jvm_path); + DEBUG_LOG ("call", ("%s\n", success == 0 ? "success" : "failed")); +#endif + } +} + +#define LIBCOBJNI_MODULE_NAME (LIB_PRF "cobjni" LIB_SUF) +#define LIBCOBJNI_ENTRY_NAME "cob_jni_init" + +typedef void (*java_init_func) (cob_java_api*); + +static cob_java_api *java_api; +static char module_errmsg[256]; + +static int +cob_init_java (void) { + java_init_func jinit; + + init_jvm_search_dirs (); + + java_api = cob_malloc (sizeof (cob_java_api)); + if (java_api == NULL) { + return 1; + } + + module_errmsg[0] = 0; + jinit = (java_init_func) cob_load_lib (LIBCOBJNI_MODULE_NAME, + LIBCOBJNI_ENTRY_NAME, + module_errmsg); + if (jinit == NULL) { + /* recheck with libcob */ + jinit = cob_load_lib ("libcob-5", + LIBCOBJNI_ENTRY_NAME, + NULL); + } + if (jinit == NULL) { + /* Error message will be reported in the `cob_call_java` that + should follow. */ + cob_free (java_api); + java_api = NULL; + return 1; + } + jinit (java_api); + return 0; +} + +cob_java_handle* +cob_resolve_java (const char *class_name, + const char *method_name, + const char *method_signature) { +#if WITH_JNI + if (java_api == NULL && cob_init_java ()) { + return NULL; + } + return java_api->cob_resolve (class_name, method_name, method_signature); +#else + return NULL; +#endif +} + +void +cob_call_java (const cob_java_handle *method_handle) { +#if WITH_JNI + if (java_api == NULL) { + cob_runtime_error (_("Java interoperability module cannot be loaded: %s"), + module_errmsg); + return; + } + return java_api->cob_call (method_handle); +#else + static int first_java = 1; + + COB_UNUSED (method_handle); + + if (first_java) { + first_java = 0; + cob_runtime_warning (_("runtime is not configured to support %s"), + "JNI"); + } +#if 0 /* TODO: if there is a register in Java-interop, then set it */ + set_json_exception (JSON_INTERNAL_ERROR); +#endif + cob_add_exception (COB_EC_IMP_FEATURE_DISABLED); +#endif +} diff --git a/libcob/common.c b/libcob/common.c index 8da537498..575ef4f42 100644 --- a/libcob/common.c +++ b/libcob/common.c @@ -9631,6 +9631,12 @@ print_info_detailed (const int verbose) var_print (_("JSON library"), _("disabled"), "", 0); #endif +#ifdef WITH_JNI + var_print (_("Java interoperability"), _("enabled"), "", 0); +#else + var_print (_("Java interoperability"), _("disabled"), "", 0); +#endif + var_print (_("extended screen I/O"), (char*)&screenio_info, "", 0); var_print (_("mouse support"), mouse_support, "", 0); diff --git a/libcob/common.h b/libcob/common.h index 7ae84641d..d477d2922 100644 --- a/libcob/common.h +++ b/libcob/common.h @@ -1287,6 +1287,18 @@ struct cob_call_struct { cob_call_union cob_cstr_cancel; /* Cancel entry */ }; +/* Java interoperability */ + +typedef struct __cob_java_static_method cob_java_handle; + +/* Indirect Java API struct */ +typedef struct __cob_java_api { + cob_java_handle* (*cob_resolve) (const char *class_name, + const char* method_name, + const char *method_signature); + void (*cob_call) (const cob_java_handle *method_handle); +} cob_java_api; + /* Screen structure */ typedef struct __cob_screen { struct __cob_screen *next; /* Pointer to next */ @@ -2277,6 +2289,11 @@ COB_EXPIMP int cob_get_name_line ( char *prog, int *line ); COB_EXPIMP void cob_runtime_warning_external (const char *, const int, const char *, ...) COB_A_FORMAT34; +COB_EXPIMP cob_java_handle* cob_resolve_java (const char *class_name, + const char* method_name, + const char *type_signature); +COB_EXPIMP void cob_call_java (const cob_java_handle *method_handle); + /*******************************/ /* Functions in screenio.c */ diff --git a/libcob/fileio.c b/libcob/fileio.c index 57ccb075d..31fbf04ba 100644 --- a/libcob/fileio.c +++ b/libcob/fileio.c @@ -314,18 +314,6 @@ static struct cob_fileio_funcs *fileio_funcs[COB_IO_MAX] = { ¬_available_funcs }; -#if defined (__CYGWIN__) -#define LIB_PRF "cyg" -#else -#define LIB_PRF "lib" -#endif - -#if defined(_WIN32) || defined(__CYGWIN__) -#define LIB_SUF "-1." COB_MODULE_EXT -#else -#define LIB_SUF "." COB_MODULE_EXT -#endif - static struct { int loaded; /* Module is loaded and ready */ int config; /* Module was configured into compiler */ @@ -9824,4 +9812,3 @@ cob_fork_fileio (cob_global *lptr, cob_settings *sptr) } } } - diff --git a/libcob/java.c b/libcob/java.c new file mode 100644 index 000000000..9f569aa81 --- /dev/null +++ b/libcob/java.c @@ -0,0 +1,128 @@ +/* + Copyright (C) 2024 Free Software Foundation, Inc. + Written by Vedant Tewari, Nicolas Berthier, + + This file is part of GnuCOBOL. + + The GnuCOBOL runtime library is free software: you can redistribute it + and/or modify it under the terms of the GNU Lesser General Public License + as published by the Free Software Foundation, either version 3 of the + License, or (at your option) any later version. + + GnuCOBOL is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with GnuCOBOL. If not, see . +*/ + +#include +#include +#include +#include + +/* Force symbol exports */ +#define COB_LIB_EXPIMP +#include "libcob.h" +#include "coblocal.h" + +/* Declarations */ +static JavaVM *jvm = NULL; +static JNIEnv *env = NULL; + +typedef struct __cob_java_static_method { + jclass cls; + jmethodID mid; +} cob_java_handle; + +static int /* non-zero means there's an error */ +jvm_load (void) { + /* JDK/JRE 6 VM initialization arguments */ + JavaVMInitArgs args; + JavaVMOption* options; + args.version = JNI_VERSION_1_6; + const char *classpath = getenv("CLASSPATH"); + if (classpath == NULL) { + classpath = ""; + } + /* inline */ + args.nOptions = 1; + size_t option_len = strlen("-Djava.class.path=") + strlen(classpath) + 1; + options = (JavaVMOption*)cob_malloc(sizeof(JavaVMOption) * 1); + options[0].optionString = (char*)cob_malloc(option_len); + strcpy(options[0].optionString, "-Djava.class.path="); + strcat(options[0].optionString, classpath); + args.options = options; + args.ignoreUnrecognized = 1; + /* loading and initializing a Java VM, returning as JNI interface */ + return JNI_CreateJavaVM(&jvm, (void**)&env, &args); +} + +static +cob_java_handle* +resolve_java (const char *class_name, + const char *method_name, + const char *method_signature) { + jclass cls; + jmethodID mid; + cob_java_handle *handle; + + char *jni_class_name = strdup(class_name); + for (char *p = jni_class_name; *p; ++p) { + if (*p == '.') { + *p = '_'; + } + } + + cls = (*env)->FindClass(env, jni_class_name); + free(jni_class_name); + if (!cls) { + return NULL; + } + + mid = (*env)->GetStaticMethodID(env, cls, method_name, method_signature); + if (!mid) { + (*env)->DeleteLocalRef(env, cls); + return NULL; + } + + handle = (cob_java_handle*)cob_malloc(sizeof(cob_java_handle)); + if (!handle) { + (*env)->DeleteLocalRef(env, cls); + return NULL; + } + + handle->cls = (*env)->NewGlobalRef(env, cls); + handle->mid = mid; + (*env)->DeleteLocalRef(env, cls); + + return handle; +} + +static void +call_java (const cob_java_handle *method_handle) +{ + if (method_handle == NULL) { + return; + } + (*env)->CallStaticVoidMethod(env, + method_handle->cls, + method_handle->mid, NULL); + if ((*env)->ExceptionCheck(env)) { + (*env)->ExceptionDescribe(env); + (*env)->ExceptionClear(env); + } +} + +/* Entry-point for the module: initializes a Java API structure. */ +int +cob_jni_init (cob_java_api *api) { + if (jvm_load ()) { + return 1; /* Unable to initialize/load the JVM */ + } + api->cob_resolve = resolve_java; + api->cob_call = call_java; + return 0; +} diff --git a/libcob/sysdefines.h b/libcob/sysdefines.h index cefd17c34..a5a0cd9b9 100644 --- a/libcob/sysdefines.h +++ b/libcob/sysdefines.h @@ -243,6 +243,18 @@ #define SLASH_STR "\\" #endif +#if defined (__CYGWIN__) +#define LIB_PRF "cyg" +#else +#define LIB_PRF "lib" +#endif + +#if defined(_WIN32) || defined(__CYGWIN__) +#define LIB_SUF "-1." COB_MODULE_EXT +#else +#define LIB_SUF "." COB_MODULE_EXT +#endif + #ifdef __DJGPP__ #define HAVE_8DOT3_FILENAMES #endif diff --git a/m4/ax_jni_include_dir.m4 b/m4/ax_jni_include_dir.m4 new file mode 100644 index 000000000..071bb2862 --- /dev/null +++ b/m4/ax_jni_include_dir.m4 @@ -0,0 +1,163 @@ +# =========================================================================== +# https://www.gnu.org/software/autoconf-archive/ax_jni_include_dir.html +# =========================================================================== +# +# SYNOPSIS +# +# AX_JNI_INCLUDE_DIR +# +# DESCRIPTION +# +# AX_JNI_INCLUDE_DIR finds include directories needed for compiling +# programs using the JNI interface. +# +# JNI include directories are usually in the Java distribution. This is +# deduced from the value of $JAVA_HOME, $JAVAC, or the path to "javac", in +# that order. When this macro completes, a list of directories is left in +# the variable JNI_INCLUDE_DIRS. +# +# Example usage follows: +# +# AX_JNI_INCLUDE_DIR +# +# for JNI_INCLUDE_DIR in $JNI_INCLUDE_DIRS +# do +# CPPFLAGS="$CPPFLAGS -I$JNI_INCLUDE_DIR" +# done +# +# If you want to force a specific compiler: +# +# - at the configure.in level, set JAVAC=yourcompiler before calling +# AX_JNI_INCLUDE_DIR +# +# - at the configure level, setenv JAVAC +# +# This macro depends on AC_CANONICAL_HOST which requires that config.guess +# and config.sub be distributed along with the source code. See autoconf +# manual for details. +# +# Note: This macro can work with the autoconf M4 macros for Java programs. +# This particular macro is not part of the original set of macros. +# +# LICENSE +# +# Copyright (c) 2008 Don Anderson +# +# Copying and distribution of this file, with or without modification, are +# permitted in any medium without royalty provided the copyright notice +# and this notice are preserved. This file is offered as-is, without any +# warranty. + +#serial 15 + +AU_ALIAS([AC_JNI_INCLUDE_DIR], [AX_JNI_INCLUDE_DIR]) +AC_DEFUN([AX_JNI_INCLUDE_DIR],[ + +AC_REQUIRE([AC_CANONICAL_HOST]) + +JNI_INCLUDE_DIRS="" + +if test "x$JAVA_HOME" != x; then + _JTOPDIR="$JAVA_HOME" +else + if test "x$JAVAC" = x; then + JAVAC=javac + fi + AC_PATH_PROG([_ACJNI_JAVAC], [$JAVAC], [no]) + if test "x$_ACJNI_JAVAC" = xno; then + AC_MSG_ERROR([cannot find JDK; try setting \$JAVAC or \$JAVA_HOME]) + fi + _ACJNI_FOLLOW_SYMLINKS("$_ACJNI_JAVAC") + _JTOPDIR=`echo "$_ACJNI_FOLLOWED" | sed -e 's://*:/:g' -e 's:/[[^/]]*$::'` +fi + +case "$host_os" in + darwin*) # Apple Java headers are inside the Xcode bundle. + major=$(sw_vers -productVersion | \ + sed -n -e 's/^\(@<:@0-9@:>@*\)..*/\1/p') + _AS_ECHO_LOG([MACOS_MAJOR=$major]) + if @<:@ "$major" -gt 10 @:>@; then + _JINC="$_JTOPDIR/include" + else + minor=$(sw_vers -productVersion | \ + sed -n -e 's/^@<:@0-9@:>@*.\(@<:@0-9@:>@*\).@<:@0-9@:>@*/\1/p') + _AS_ECHO_LOG([MACOS_MINOR=$minor]) + # if test "x$minor" = "x"; then # + # _JINC="$_JTOPDIR/include" + # el + if @<:@ "$minor" -gt "7" @:>@; then + _JTOPDIR="$(xcrun --show-sdk-path)/System/Library/Frameworks/JavaVM.framework" + _JINC="$_JTOPDIR/Headers" + else + _JTOPDIR="/System/Library/Frameworks/JavaVM.framework" + _JINC="$_JTOPDIR/Headers" + fi + fi + ;; + *) _JINC="$_JTOPDIR/include";; +esac +_AS_ECHO_LOG([_JTOPDIR=$_JTOPDIR]) +_AS_ECHO_LOG([_JINC=$_JINC]) + +# On Mac OS X 10.6.4, jni.h is a symlink: +# /System/Library/Frameworks/JavaVM.framework/Versions/Current/Headers/jni.h +# -> ../../CurrentJDK/Headers/jni.h. +AC_CACHE_CHECK(jni headers, ac_cv_jni_header_path, +[ + if test -f "$_JINC/jni.h"; then + ac_cv_jni_header_path="$_JINC" + JNI_INCLUDE_DIRS="$JNI_INCLUDE_DIRS $ac_cv_jni_header_path" + else + _JTOPDIR=`echo "$_JTOPDIR" | sed -e 's:/[[^/]]*$::'` + if test -f "$_JTOPDIR/include/jni.h"; then + ac_cv_jni_header_path="$_JTOPDIR/include" + JNI_INCLUDE_DIRS="$JNI_INCLUDE_DIRS $ac_cv_jni_header_path" + else + ac_cv_jni_header_path=none + fi + fi +]) + +# get the likely subdirectories for system specific java includes +case "$host_os" in +bsdi*) _JNI_INC_SUBDIRS="bsdos";; +freebsd*) _JNI_INC_SUBDIRS="freebsd";; +darwin*) _JNI_INC_SUBDIRS="darwin";; +linux*) _JNI_INC_SUBDIRS="linux genunix";; +osf*) _JNI_INC_SUBDIRS="alpha";; +solaris*) _JNI_INC_SUBDIRS="solaris";; +mingw*) _JNI_INC_SUBDIRS="win32";; +cygwin*) _JNI_INC_SUBDIRS="win32";; +*) _JNI_INC_SUBDIRS="genunix";; +esac + +if test "x$ac_cv_jni_header_path" != "xnone"; then + # add any subdirectories that are present + for JINCSUBDIR in $_JNI_INC_SUBDIRS + do + if test -d "$_JTOPDIR/include/$JINCSUBDIR"; then + JNI_INCLUDE_DIRS="$JNI_INCLUDE_DIRS $_JTOPDIR/include/$JINCSUBDIR" + fi + done +fi +]) + +# _ACJNI_FOLLOW_SYMLINKS +# Follows symbolic links on , +# finally setting variable _ACJNI_FOLLOWED +# ---------------------------------------- +AC_DEFUN([_ACJNI_FOLLOW_SYMLINKS],[ +# find the include directory relative to the javac executable +_cur="$1" +while ls -ld "$_cur" 2>/dev/null | grep " -> " >/dev/null; do + AC_MSG_CHECKING([symlink for $_cur]) + _slink=`ls -ld "$_cur" | sed 's/.* -> //'` + case "$_slink" in + /*) _cur="$_slink";; + # 'X' avoids triggering unwanted echo options. + *) _cur=`echo "X$_cur" | sed -e 's/^X//' -e 's:[[^/]]*$::'`"$_slink";; + esac + AC_MSG_RESULT([$_cur]) +done +_ACJNI_FOLLOWED="$_cur" +])# _ACJNI diff --git a/m4/ax_prog_java.m4 b/m4/ax_prog_java.m4 new file mode 100644 index 000000000..c2e6964e2 --- /dev/null +++ b/m4/ax_prog_java.m4 @@ -0,0 +1,115 @@ +# =========================================================================== +# https://www.gnu.org/software/autoconf-archive/ax_prog_java.html +# =========================================================================== +# +# SYNOPSIS +# +# AX_PROG_JAVA +# +# DESCRIPTION +# +# Here is a summary of the main macros: +# +# AX_PROG_JAVAC: finds a Java compiler. +# +# AX_PROG_JAVA: finds a Java virtual machine. +# +# AX_CHECK_CLASS: finds if we have the given class (beware of CLASSPATH!). +# +# AX_CHECK_RQRD_CLASS: finds if we have the given class and stops +# otherwise. +# +# AX_TRY_COMPILE_JAVA: attempt to compile user given source. +# +# AX_TRY_RUN_JAVA: attempt to compile and run user given source. +# +# AX_JAVA_OPTIONS: adds Java configure options. +# +# AX_PROG_JAVA tests an existing Java virtual machine. It uses the +# environment variable JAVA then tests in sequence various common Java +# virtual machines. For political reasons, it starts with the free ones. +# You *must* call [AX_PROG_JAVAC] before. +# +# If you want to force a specific VM: +# +# - at the configure.in level, set JAVA=yourvm before calling AX_PROG_JAVA +# +# (but after AC_INIT) +# +# - at the configure level, setenv JAVA +# +# You can use the JAVA variable in your Makefile.in, with @JAVA@. +# +# *Warning*: its success or failure can depend on a proper setting of the +# CLASSPATH env. variable. +# +# TODO: allow to exclude virtual machines (rationale: most Java programs +# cannot run with some VM like kaffe). +# +# Note: This is part of the set of autoconf M4 macros for Java programs. +# It is VERY IMPORTANT that you download the whole set, some macros depend +# on other. Unfortunately, the autoconf archive does not support the +# concept of set of macros, so I had to break it for submission. +# +# A Web page, with a link to the latest CVS snapshot is at +# . +# +# This is a sample configure.in Process this file with autoconf to produce +# a configure script. +# +# AC_INIT(UnTag.java) +# +# dnl Checks for programs. +# AC_CHECK_CLASSPATH +# AX_PROG_JAVAC +# AX_PROG_JAVA +# +# dnl Checks for classes +# AX_CHECK_RQRD_CLASS(org.xml.sax.Parser) +# AX_CHECK_RQRD_CLASS(com.jclark.xml.sax.Driver) +# +# AC_OUTPUT(Makefile) +# +# LICENSE +# +# Copyright (c) 2008 Stephane Bortzmeyer +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; either version 2 of the License, or (at your +# option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General +# Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program. If not, see . +# +# As a special exception, the respective Autoconf Macro's copyright owner +# gives unlimited permission to copy, distribute and modify the configure +# scripts that are the output of Autoconf when processing the Macro. You +# need not follow the terms of the GNU General Public License when using +# or distributing such scripts, even though portions of the text of the +# Macro appear in them. The GNU General Public License (GPL) does govern +# all other use of the material that constitutes the Autoconf Macro. +# +# This special exception to the GPL applies to versions of the Autoconf +# Macro released by the Autoconf Archive. When you make and distribute a +# modified version of the Autoconf Macro, you may extend this special +# exception to the GPL to apply to your modified version as well. + +#serial 10 + +AU_ALIAS([AC_PROG_JAVA], [AX_PROG_JAVA]) +AC_DEFUN([AX_PROG_JAVA],[ +m4_define([m4_ax_prog_java_list], [kaffe java])dnl +AS_IF([test "x$JAVAPREFIX" = x], + [test x$JAVA = x && AC_CHECK_PROGS([JAVA], [m4_ax_prog_java_list])], + [test x$JAVA = x && AC_CHECK_PROGS([JAVA], [m4_ax_prog_java_list], [], [$JAVAPREFIX/bin])]) +test x$JAVA = x && AC_MSG_ERROR([no acceptable Java virtual machine found in \$PATH]) +m4_undefine([m4_ax_prog_java_list])dnl +AX_PROG_JAVA_WORKS +AC_PROVIDE([$0])dnl +]) diff --git a/m4/ax_prog_java_works.m4 b/m4/ax_prog_java_works.m4 new file mode 100644 index 000000000..bc7052619 --- /dev/null +++ b/m4/ax_prog_java_works.m4 @@ -0,0 +1,91 @@ +# =========================================================================== +# https://www.gnu.org/software/autoconf-archive/ax_prog_java_works.html +# =========================================================================== +# +# SYNOPSIS +# +# AX_PROG_JAVA_WORKS +# +# DESCRIPTION +# +# Internal use ONLY. +# +# Note: This is part of the set of autoconf M4 macros for Java programs. +# It is VERY IMPORTANT that you download the whole set, some macros depend +# on other. Unfortunately, the autoconf archive does not support the +# concept of set of macros, so I had to break it for submission. The +# general documentation, as well as the sample configure.in, is included +# in the AX_PROG_JAVA macro. +# +# LICENSE +# +# Copyright (c) 2008 Stephane Bortzmeyer +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; either version 2 of the License, or (at your +# option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General +# Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program. If not, see . +# +# As a special exception, the respective Autoconf Macro's copyright owner +# gives unlimited permission to copy, distribute and modify the configure +# scripts that are the output of Autoconf when processing the Macro. You +# need not follow the terms of the GNU General Public License when using +# or distributing such scripts, even though portions of the text of the +# Macro appear in them. The GNU General Public License (GPL) does govern +# all other use of the material that constitutes the Autoconf Macro. +# +# This special exception to the GPL applies to versions of the Autoconf +# Macro released by the Autoconf Archive. When you make and distribute a +# modified version of the Autoconf Macro, you may extend this special +# exception to the GPL to apply to your modified version as well. + +#serial 11 + +AU_ALIAS([AC_PROG_JAVA_WORKS], [AX_PROG_JAVA_WORKS]) +AC_DEFUN([AX_PROG_JAVA_WORKS], [ + if test x$ac_cv_prog_javac_works = xno; then + AC_MSG_ERROR([Cannot compile java source. $JAVAC does not work properly]) + fi + if test x$ac_cv_prog_javac_works = x; then + AX_PROG_JAVAC + fi +AC_CACHE_CHECK(if $JAVA works, ac_cv_prog_java_works, [ +JAVA_TEST=Test.java +CLASS_TEST=Test.class +TEST=Test +changequote(, )dnl +cat << \EOF > $JAVA_TEST +/* [#]line __oline__ "configure" */ +public class Test { +public static void main (String args[]) { + System.exit (0); +} } +EOF +changequote([, ])dnl + if AC_TRY_COMMAND($JAVAC $JAVACFLAGS $JAVA_TEST) && test -s $CLASS_TEST; then + : + else + echo "configure: failed program was:" >&AS_MESSAGE_LOG_FD + cat $JAVA_TEST >&AS_MESSAGE_LOG_FD + AC_MSG_ERROR(The Java compiler $JAVAC failed (see config.log, check the CLASSPATH?)) + fi +if AC_TRY_COMMAND($JAVA -classpath . $JAVAFLAGS $TEST) >/dev/null 2>&1; then + ac_cv_prog_java_works=yes +else + echo "configure: failed program was:" >&AS_MESSAGE_LOG_FD + cat $JAVA_TEST >&AS_MESSAGE_LOG_FD + AC_MSG_ERROR(The Java VM $JAVA failed (see config.log, check the CLASSPATH?)) +fi +rm -f $JAVA_TEST $CLASS_TEST +]) +AC_PROVIDE([$0])dnl +] +) diff --git a/m4/ax_prog_javac.m4 b/m4/ax_prog_javac.m4 new file mode 100644 index 000000000..8abb733fc --- /dev/null +++ b/m4/ax_prog_javac.m4 @@ -0,0 +1,79 @@ +# =========================================================================== +# https://www.gnu.org/software/autoconf-archive/ax_prog_javac.html +# =========================================================================== +# +# SYNOPSIS +# +# AX_PROG_JAVAC +# +# DESCRIPTION +# +# AX_PROG_JAVAC tests an existing Java compiler. It uses the environment +# variable JAVAC then tests in sequence various common Java compilers. For +# political reasons, it starts with the free ones. +# +# If you want to force a specific compiler: +# +# - at the configure.in level, set JAVAC=yourcompiler before calling +# AX_PROG_JAVAC +# +# - at the configure level, setenv JAVAC +# +# You can use the JAVAC variable in your Makefile.in, with @JAVAC@. +# +# *Warning*: its success or failure can depend on a proper setting of the +# CLASSPATH env. variable. +# +# TODO: allow to exclude compilers (rationale: most Java programs cannot +# compile with some compilers like guavac). +# +# Note: This is part of the set of autoconf M4 macros for Java programs. +# It is VERY IMPORTANT that you download the whole set, some macros depend +# on other. Unfortunately, the autoconf archive does not support the +# concept of set of macros, so I had to break it for submission. The +# general documentation, as well as the sample configure.in, is included +# in the AX_PROG_JAVA macro. +# +# LICENSE +# +# Copyright (c) 2008 Stephane Bortzmeyer +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; either version 2 of the License, or (at your +# option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General +# Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program. If not, see . +# +# As a special exception, the respective Autoconf Macro's copyright owner +# gives unlimited permission to copy, distribute and modify the configure +# scripts that are the output of Autoconf when processing the Macro. You +# need not follow the terms of the GNU General Public License when using +# or distributing such scripts, even though portions of the text of the +# Macro appear in them. The GNU General Public License (GPL) does govern +# all other use of the material that constitutes the Autoconf Macro. +# +# This special exception to the GPL applies to versions of the Autoconf +# Macro released by the Autoconf Archive. When you make and distribute a +# modified version of the Autoconf Macro, you may extend this special +# exception to the GPL to apply to your modified version as well. + +#serial 8 + +AU_ALIAS([AC_PROG_JAVAC], [AX_PROG_JAVAC]) +AC_DEFUN([AX_PROG_JAVAC],[ +m4_define([m4_ax_prog_javac_list],["gcj -C" guavac jikes javac])dnl +AS_IF([test "x$JAVAPREFIX" = x], + [test "x$JAVAC" = x && AC_CHECK_PROGS([JAVAC], [m4_ax_prog_javac_list])], + [test "x$JAVAC" = x && AC_CHECK_PROGS([JAVAC], [m4_ax_prog_javac_list], [], [$JAVAPREFIX/bin])]) +m4_undefine([m4_ax_prog_javac_list])dnl +test "x$JAVAC" = x && AC_MSG_ERROR([no acceptable Java compiler found in \$PATH]) +AX_PROG_JAVAC_WORKS +AC_PROVIDE([$0])dnl +]) diff --git a/m4/ax_prog_javac_works.m4 b/m4/ax_prog_javac_works.m4 new file mode 100644 index 000000000..9b48149d8 --- /dev/null +++ b/m4/ax_prog_javac_works.m4 @@ -0,0 +1,72 @@ +# =========================================================================== +# https://www.gnu.org/software/autoconf-archive/ax_prog_javac_works.html +# =========================================================================== +# +# SYNOPSIS +# +# AX_PROG_JAVAC_WORKS +# +# DESCRIPTION +# +# Internal use ONLY. +# +# Note: This is part of the set of autoconf M4 macros for Java programs. +# It is VERY IMPORTANT that you download the whole set, some macros depend +# on other. Unfortunately, the autoconf archive does not support the +# concept of set of macros, so I had to break it for submission. The +# general documentation, as well as the sample configure.in, is included +# in the AX_PROG_JAVA macro. +# +# LICENSE +# +# Copyright (c) 2008 Stephane Bortzmeyer +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; either version 2 of the License, or (at your +# option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General +# Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program. If not, see . +# +# As a special exception, the respective Autoconf Macro's copyright owner +# gives unlimited permission to copy, distribute and modify the configure +# scripts that are the output of Autoconf when processing the Macro. You +# need not follow the terms of the GNU General Public License when using +# or distributing such scripts, even though portions of the text of the +# Macro appear in them. The GNU General Public License (GPL) does govern +# all other use of the material that constitutes the Autoconf Macro. +# +# This special exception to the GPL applies to versions of the Autoconf +# Macro released by the Autoconf Archive. When you make and distribute a +# modified version of the Autoconf Macro, you may extend this special +# exception to the GPL to apply to your modified version as well. + +#serial 7 + +AU_ALIAS([AC_PROG_JAVAC_WORKS], [AX_PROG_JAVAC_WORKS]) +AC_DEFUN([AX_PROG_JAVAC_WORKS],[ +AC_CACHE_CHECK([if $JAVAC works], ac_cv_prog_javac_works, [ +JAVA_TEST=Test.java +CLASS_TEST=Test.class +cat << \EOF > $JAVA_TEST +/* [#]line __oline__ "configure" */ +public class Test { +} +EOF +if AC_TRY_COMMAND($JAVAC $JAVACFLAGS $JAVA_TEST) >/dev/null 2>&1; then + ac_cv_prog_javac_works=yes +else + AC_MSG_ERROR([The Java compiler $JAVAC failed (see config.log, check the CLASSPATH?)]) + echo "configure: failed program was:" >&AS_MESSAGE_LOG_FD + cat $JAVA_TEST >&AS_MESSAGE_LOG_FD +fi +rm -f $JAVA_TEST $CLASS_TEST +]) +AC_PROVIDE([$0])dnl +]) diff --git a/tests/ChangeLog b/tests/ChangeLog index a92686469..a650692db 100644 --- a/tests/ChangeLog +++ b/tests/ChangeLog @@ -1,4 +1,8 @@ +2024-08-14 Nicolas Berthier + + * testsuite.src/run_java.at: new testsuite for Java interoperability + 2023-01-21 Simon Sobisch * atlocal.in: prefer config.status replacement over environment var diff --git a/tests/Makefile.am b/tests/Makefile.am index 3b5549231..79e88d15e 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -51,6 +51,7 @@ testsuite_sources = \ testsuite.src/run_functions.at \ testsuite.src/run_fundamental.at \ testsuite.src/run_initialize.at \ + testsuite.src/run_java.at \ testsuite.src/run_misc.at \ testsuite.src/run_ml.at \ testsuite.src/run_refmod.at \ diff --git a/tests/atlocal.in b/tests/atlocal.in index ef679d979..079a4903c 100644 --- a/tests/atlocal.in +++ b/tests/atlocal.in @@ -58,6 +58,8 @@ GREP=@GREP@ SED=@SED@ export AWK GREP SED +JAVAC=@JAVAC@ + # be sure to use the English messages LC_ALL=C export LC_ALL @@ -218,6 +220,7 @@ if test "$GNUCOBOL_TEST_LOCAL" != "1"; then COB_HAS_XML2="@COB_HAS_XML2@" COB_HAS_JSON="@COB_HAS_JSON@" COB_HAS_CURSES="@COB_HAS_CURSES@" + COB_HAS_JNI="@COB_HAS_JNI@" else COB_OBJECT_EXT="$(grep COB_OBJECT_EXT info.out | cut -d: -f2 | cut -b2-)" @@ -256,6 +259,11 @@ else else COB_HAS_JSON="no" fi + if test $(grep -i -c "Java interoperability.*disabled" info.out) = 0; then + COB_HAS_JNI="yes" + else + COB_HAS_JNI="no" + fi # see note below if test $(grep -i -c " screen .*disabled" info.out) = 0; then COB_HAS_CURSES="yes" diff --git a/tests/testsuite.at b/tests/testsuite.at index 12a44f76a..977162d0c 100644 --- a/tests/testsuite.at +++ b/tests/testsuite.at @@ -72,6 +72,7 @@ m4_include([run_returncode.at]) m4_include([run_functions.at]) # 15 Intrinsic Functions / 9.4 User-Defined Functions m4_include([run_extensions.at]) m4_include([run_ml.at]) +m4_include([run_java.at]) ## Data Representation AT_BANNER([Data Representation]) diff --git a/tests/testsuite.src/run_java.at b/tests/testsuite.src/run_java.at new file mode 100644 index 000000000..0061d498a --- /dev/null +++ b/tests/testsuite.src/run_java.at @@ -0,0 +1,67 @@ +## Copyright (C) 2024 Free Software Foundation, Inc. +## Written by Nicolas Berthier, Vedant Tewari +## +## This file is part of GnuCOBOL. +## +## The GnuCOBOL compiler is free software: you can redistribute it +## and/or modify it under the terms of the GNU General Public License +## as published by the Free Software Foundation, either version 3 of the +## License, or (at your option) any later version. +## +## GnuCOBOL is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with GnuCOBOL. If not, see . + +### GnuCOBOL Test Suite + +### Java interoperability tests + +AT_SETUP([CALL Java static void (void)]) +AT_KEYWORDS([extensions jni]) + +AT_SKIP_IF([test "$COB_HAS_JNI" = "no"]) + +AT_DATA([Test.java], [ +public class Test { + public static void printHelloWorld () { + System.out.println ("Hello world!"); + } +} +]) +AT_DATA([prog.cob], [ + IDENTIFICATION DIVISION. + PROGRAM-ID. prog. + PROCEDURE DIVISION. + CALL "Java.Test.printHelloWorld" + STOP RUN. +]) + +AT_CHECK([$JAVAC Test.java], [0], [], []) +AT_CHECK([$COMPILE prog.cob], [0], [], []) +AT_CHECK([$COBCRUN_DIRECT ./prog], [0], +[Hello world! +]) +AT_CLEANUP + + +AT_SETUP([CALL Java static void (void) (missing)]) +AT_KEYWORDS([extensions jni]) + +AT_SKIP_IF([test "$COB_HAS_JNI" = "no"]) + +AT_DATA([prog.cob], [ + IDENTIFICATION DIVISION. + PROGRAM-ID. prog. + PROCEDURE DIVISION. + CALL "Java.Test.missingMethod" + STOP RUN. +]) + +AT_CHECK([$COMPILE prog.cob], [0], [], []) +# TODO: should fail! +AT_CHECK([$COBCRUN_DIRECT ./prog], [0], []) +AT_CLEANUP From 7b31a0bb02f385eab86839463c6fb93413c31319 Mon Sep 17 00:00:00 2001 From: Nicolas Berthier Date: Thu, 22 Aug 2024 10:13:16 +0200 Subject: [PATCH 2/7] Adjust check for linking with JVM library --- configure.ac | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/configure.ac b/configure.ac index a06017d49..8191a5e96 100644 --- a/configure.ac +++ b/configure.ac @@ -969,16 +969,33 @@ AS_IF([test "x$with_java" != "xno"], [ if test -d "$_dir/server"; then JNI_LIBS="$JNI_LIBS -L$_dir/server" fi + if test -d "$_dir/client"; then + JNI_LIBS="$JNI_LIBS -L$_dir/client" + fi done curr_LIBS="$LIBS" - LIBS="$LIBS $JNI_LIBS" - AC_CHECK_LIB([jvm], [JNI_CreateJavaVM], [ + curr_CPPFLAGS="$CPPFLAGS" + LIBS="$LIBS $JNI_LIBS -ljvm" + CPPFLAGS="$CPPFLAGS $JNI_CPPFLAGS" + AC_MSG_CHECKING([if -ljvm brings JNI symbols]) + AC_LINK_IFELSE([ + AC_LANG_SOURCE([[ + #include + void main (void) { + (void) JNI_CreateJavaVM (NULL, NULL, NULL); + } + ]]) + ], [ + AC_MSG_RESULT([yes]) AC_DEFINE([WITH_JNI], [1]) JNI_LDFLAGS="$JNI_LIBS" JNI_LIBS="-ljvm" cob_has_jni=yes + ], [ + AC_MSG_RESULT([no]) ]) LIBS="$curr_LIBS" + CPPFLAGS="$curr_CPPFLAGS" ]) ]) AS_IF([test "x$with_java" = "xyes" -a "x$cob_has_jni" != "xyes"], [ From 332e29c8ca3d1985c601225f0ded8679d506226a Mon Sep 17 00:00:00 2001 From: Vedant Tewari Date: Thu, 22 Aug 2024 19:49:29 -0500 Subject: [PATCH 3/7] Updated output java call --- cobc/codegen.c | 89 +++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 88 insertions(+), 1 deletion(-) diff --git a/cobc/codegen.c b/cobc/codegen.c index 2d7dd9071..fca7e158f 100644 --- a/cobc/codegen.c +++ b/cobc/codegen.c @@ -7098,7 +7098,9 @@ output_java_call (struct cb_call *p) char *last_dot; char *method_name; const char *class_name; + char method_signature[256] = "("; char* mangled; + struct cb_tree *ptr; mangled = strdup(class_and_method_name); for (size_t i = 0; i < strlen(mangled) + 1; i++) { @@ -7115,18 +7117,103 @@ output_java_call (struct cb_call *p) method_name = last_dot + 1; class_name = class_and_method_name; + for(ptr = p->args; ptr != NULL; ptr = ptr->next) { + switch(CB_TREE_TAG(ptr)) { + case CB_TAG_INTEGER: + strcat(method_signature, "I"); + break; + case CB_TAG_LONG: + strcat(method_signature, "J"); + break; + case CB_TAG_FLOAT: + strcat(method_signature, "F"); + break; + case CB_TAG_DOUBLE: + strcat(method_signature, "D"); + break; + case CB_TAG_BOOLEAN: + strcat(method_signature, "Z"); + break; + case CB_TAG_BYTE: + strcat(method_signature, "B"); + break; + case CB_TAG_SHORT: + strcat(method_signature, "S"); + break; + case CB_TAG_CHAR: + strcat(method_signature, "C"); + break; + case CB_TAG_STRING: + strcat(method_signature, "Ljava/lang/String;"); + break; + case CB_TAG_OBJECT: + strcat(method_signature, "Ljava/lang/Object;"); + break; + case CB_TAG_LITERAL: + if(CB_TREE_CATEGORY(ptr) == CB_CATEGORY_NUMERIC) { + strcat(method_signature, "I"); + } else if(CB_TREE_CATEGORY(ptr) == CB_CATEGORY_ALPHANUMERIC) { + strcat(method_signature, "Ljava/lang/String;"); + } + break; + case CB_TAG_ARRAY: + switch(CB_TREE_TAG(CB_TREE(ptr)->next)) { + case CB_TAG_INTEGER: + strcat(method_signature, "[I"); + break; + case CB_TAG_LONG: + strcat(method_signature, "[J"); + break; + case CB_TAG_FLOAT: + strcat(method_signature, "[F"); + break; + case CB_TAG_DOUBLE: + strcat(method_signature, "[D"); + break; + case CB_TAG_BOOLEAN: + strcat(method_signature, "[Z"); + break; + case CB_TAG_BYTE: + strcat(method_signature, "[B"); + break; + case CB_TAG_SHORT: + strcat(method_signature, "[S"); + break; + case CB_TAG_CHAR: + strcat(method_signature, "[C"); + break; + case CB_TAG_STRING: + strcat(method_signature, "[Ljava/lang/String;"); + break; + case CB_TAG_OBJECT: + strcat(method_signature, "[Ljava/lang/Object;"); + break; + default: + cobc_err_msg(_("Unsupported array type in Java method call")); + COBC_ABORT(); + } + break; + default: + cobc_err_msg(_("Unsupported argument type in Java method call")); + COBC_ABORT(); + } + } + + strcat(method_signature, ")V"); + lookup_java_call(mangled); output_line("if (call_java_%s == NULL)", mangled); output_block_open(); output_prefix(); output_line("call_java_%s = ", mangled); - output("cob_resolve_java(\"%s\", \"%s\", \"()V\");", class_name, method_name); + output("cob_resolve_java(\"%s\", \"%s\", \"()V\");", class_name, method_name, method_signature); output_newline (); output_prefix (); output_line("cob_call_java(call_java_%s);\n", mangled); output_newline(); output_block_close(); + cobc_free(mangled); } static void From 1f90ff3ebaf05a8b044430838d109e10321c8cce Mon Sep 17 00:00:00 2001 From: Vedant Tewari Date: Mon, 2 Sep 2024 01:37:38 -0500 Subject: [PATCH 4/7] Tag fixes and modifies to call_cache --- cobc/codegen.c | 114 +++++++++++++++++++++---------------------------- 1 file changed, 48 insertions(+), 66 deletions(-) diff --git a/cobc/codegen.c b/cobc/codegen.c index fca7e158f..d3b758e21 100644 --- a/cobc/codegen.c +++ b/cobc/codegen.c @@ -120,6 +120,7 @@ struct field_list { struct call_list { struct call_list *next; const char *call_name; + const char *method_sig; }; #define COB_RETURN_INT 0 @@ -397,17 +398,18 @@ lookup_source (const char *p) } static void -lookup_java_call(const char *p) +lookup_java_call(const char *p, const char *signature) { struct call_list *clp; for (clp = call_java_cache; clp; clp = clp->next) { - if (strcmp (p, clp->call_name) == 0) { + if (strcmp (p, clp->call_name) == 0 && strcmp(signature, clp->method_sig) == 0) { return; } } clp = cobc_parse_malloc (sizeof (struct call_list)); clp->call_name = p; + clp->method_sig = signature; clp->next = call_java_cache; call_java_cache = clp; } @@ -7098,9 +7100,9 @@ output_java_call (struct cb_call *p) char *last_dot; char *method_name; const char *class_name; - char method_signature[256] = "("; + char method_signature[2048] = "("; char* mangled; - struct cb_tree *ptr; + struct cb_tree_common *ptr; mangled = strdup(class_and_method_name); for (size_t i = 0; i < strlen(mangled) + 1; i++) { @@ -7110,43 +7112,31 @@ output_java_call (struct cb_call *p) last_dot = strrchr(class_and_method_name, '.'); if (last_dot == NULL) { cobc_err_msg (_("malformed call '%s' to a Java method"), class_and_method_name); - COBC_ABORT (); + return; } *last_dot = '\0'; method_name = last_dot + 1; class_name = class_and_method_name; - for(ptr = p->args; ptr != NULL; ptr = ptr->next) { + for (int i = 0; (ptr = ((struct cb_tree_common **)p->args)[i]) != NULL; i++) { switch(CB_TREE_TAG(ptr)) { case CB_TAG_INTEGER: strcat(method_signature, "I"); break; - case CB_TAG_LONG: - strcat(method_signature, "J"); - break; - case CB_TAG_FLOAT: + case CB_USAGE_FLOAT: strcat(method_signature, "F"); break; - case CB_TAG_DOUBLE: + case CB_USAGE_DOUBLE: strcat(method_signature, "D"); break; - case CB_TAG_BOOLEAN: + case CB_CLASS_BOOLEAN: strcat(method_signature, "Z"); break; - case CB_TAG_BYTE: - strcat(method_signature, "B"); - break; - case CB_TAG_SHORT: - strcat(method_signature, "S"); - break; - case CB_TAG_CHAR: - strcat(method_signature, "C"); - break; case CB_TAG_STRING: strcat(method_signature, "Ljava/lang/String;"); break; - case CB_TAG_OBJECT: + case CB_USAGE_OBJECT: strcat(method_signature, "Ljava/lang/Object;"); break; case CB_TAG_LITERAL: @@ -7156,64 +7146,56 @@ output_java_call (struct cb_call *p) strcat(method_signature, "Ljava/lang/String;"); } break; - case CB_TAG_ARRAY: - switch(CB_TREE_TAG(CB_TREE(ptr)->next)) { - case CB_TAG_INTEGER: - strcat(method_signature, "[I"); - break; - case CB_TAG_LONG: - strcat(method_signature, "[J"); - break; - case CB_TAG_FLOAT: - strcat(method_signature, "[F"); - break; - case CB_TAG_DOUBLE: - strcat(method_signature, "[D"); - break; - case CB_TAG_BOOLEAN: - strcat(method_signature, "[Z"); - break; - case CB_TAG_BYTE: - strcat(method_signature, "[B"); - break; - case CB_TAG_SHORT: - strcat(method_signature, "[S"); - break; - case CB_TAG_CHAR: - strcat(method_signature, "[C"); - break; - case CB_TAG_STRING: - strcat(method_signature, "[Ljava/lang/String;"); - break; - case CB_TAG_OBJECT: - strcat(method_signature, "[Ljava/lang/Object;"); - break; - default: - cobc_err_msg(_("Unsupported array type in Java method call")); - COBC_ABORT(); - } - break; - default: - cobc_err_msg(_("Unsupported argument type in Java method call")); - COBC_ABORT(); - } + case CB_TAG_LIST: + { + struct cb_tree_common **list_elements = (struct cb_tree_common **) ptr; + for (int j = 0; list_elements[j] != NULL; j++) { + switch (CB_TREE_TAG(list_elements[j])) { + case CB_TAG_INTEGER: + strcat(method_signature, "[I"); + break; + case CB_USAGE_FLOAT: + strcat(method_signature, "[F"); + break; + case CB_USAGE_DOUBLE: + strcat(method_signature, "[D"); + break; + case CB_CLASS_BOOLEAN: + strcat(method_signature, "[Z"); + break; + case CB_TAG_STRING: + strcat(method_signature, "[Ljava/lang/String;"); + break; + case CB_USAGE_OBJECT: + strcat(method_signature, "[Ljava/lang/Object;"); + break; + default: + cobc_err_msg(_("Unsupported array type in Java method call")); + COBC_ABORT(); + } + } + } + break; + default: + cobc_err_msg(_("Unsupported argument type in Java method call")); + COBC_ABORT(); + } } strcat(method_signature, ")V"); - lookup_java_call(mangled); + lookup_java_call(mangled, method_signature); output_line("if (call_java_%s == NULL)", mangled); output_block_open(); output_prefix(); output_line("call_java_%s = ", mangled); - output("cob_resolve_java(\"%s\", \"%s\", \"()V\");", class_name, method_name, method_signature); + output("cob_resolve_java(\"%s\", \"%s\", \"%s\", \"()V\");", class_name, method_name, method_signature); output_newline (); output_prefix (); output_line("cob_call_java(call_java_%s);\n", mangled); output_newline(); output_block_close(); - cobc_free(mangled); } static void From da1b85df1f4655adcc44636ffa8fabdab24c629a Mon Sep 17 00:00:00 2001 From: Vedant Tewari Date: Mon, 9 Sep 2024 01:34:31 -0500 Subject: [PATCH 5/7] Added checks for return type signature and array dimensions --- cobc/codegen.c | 105 ++++++++++++++++++++++++++++++++++++------------- libcob/java.c | 4 ++ 2 files changed, 81 insertions(+), 28 deletions(-) diff --git a/cobc/codegen.c b/cobc/codegen.c index d3b758e21..36e4940ed 100644 --- a/cobc/codegen.c +++ b/cobc/codegen.c @@ -7100,6 +7100,7 @@ output_java_call (struct cb_call *p) char *last_dot; char *method_name; const char *class_name; + char return_type_signature[32]; char method_signature[2048] = "("; char* mangled; struct cb_tree_common *ptr; @@ -7146,34 +7147,50 @@ output_java_call (struct cb_call *p) strcat(method_signature, "Ljava/lang/String;"); } break; - case CB_TAG_LIST: - { - struct cb_tree_common **list_elements = (struct cb_tree_common **) ptr; - for (int j = 0; list_elements[j] != NULL; j++) { - switch (CB_TREE_TAG(list_elements[j])) { - case CB_TAG_INTEGER: - strcat(method_signature, "[I"); - break; - case CB_USAGE_FLOAT: - strcat(method_signature, "[F"); - break; - case CB_USAGE_DOUBLE: - strcat(method_signature, "[D"); - break; - case CB_CLASS_BOOLEAN: - strcat(method_signature, "[Z"); - break; - case CB_TAG_STRING: - strcat(method_signature, "[Ljava/lang/String;"); - break; - case CB_USAGE_OBJECT: - strcat(method_signature, "[Ljava/lang/Object;"); - break; - default: - cobc_err_msg(_("Unsupported array type in Java method call")); - COBC_ABORT(); - } - } + case CB_TAG_LIST: + { + struct cb_tree_common **list_elements = (struct cb_tree_common **) ptr; + int array_dimension = 1; + + while (list_elements != NULL) { + struct cb_tree_common **inner_list = NULL; + for (int j = 0; list_elements[j] != NULL; j++) { + if (CB_TREE_TAG(list_elements[j]) == CB_TAG_LIST) { + array_dimension++; + inner_list = (struct cb_tree_common **) list_elements[j]; + } else { + switch (CB_TREE_TAG(list_elements[j])) { + case CB_TAG_INTEGER: + strcat(method_signature, "[I"); + break; + case CB_USAGE_FLOAT: + strcat(method_signature, "[F"); + break; + case CB_USAGE_DOUBLE: + strcat(method_signature, "[D"); + break; + case CB_CLASS_BOOLEAN: + strcat(method_signature, "[Z"); + break; + case CB_TAG_STRING: + strcat(method_signature, "[Ljava/lang/String;"); + break; + case CB_USAGE_OBJECT: + strcat(method_signature, "[Ljava/lang/Object;"); + break; + default: + cobc_err_msg(_("Unsupported array element type in Java method call")); + COBC_ABORT(); + } + } + } + list_elements = inner_list; + } + + if (array_dimension > 2) { + cobc_err_msg(_("Unsupported array dimension: %d"), array_dimension); + COBC_ABORT(); + } } break; default: @@ -7182,6 +7199,38 @@ output_java_call (struct cb_call *p) } } + if (p->call_returning == NULL) { + strcat(method_signature, ")V"); + strcpy(return_type_signature, "void"); + } else { + switch(CB_TREE_TAG(p->call_returning)) { + case CB_TAG_INTEGER: + strcat(method_signature, ")I"); + strcpy(return_type_signature, "jint"); + break; + case CB_TAG_STRING: + strcat(method_signature, ")Ljava/lang/String;"); + strcpy(return_type_signature, "jstring"); + break; + case CB_USAGE_FLOAT: + strcat(method_signature, ")F"); + strcpy(return_type_signature, "jfloat"); + break; + case CB_USAGE_DOUBLE: + strcat(method_signature, ")D"); + strcpy(return_type_signature, "jdouble"); + break; + case CB_CLASS_BOOLEAN: + strcat(method_signature, ")Z"); + strcpy(return_type_signature, "jboolean"); + break; + default: + strcat(method_signature, ")V"); + strcpy(return_type_signature, "void"); + break; + } + } + strcat(method_signature, ")V"); lookup_java_call(mangled, method_signature); diff --git a/libcob/java.c b/libcob/java.c index 9f569aa81..266cf2fc5 100644 --- a/libcob/java.c +++ b/libcob/java.c @@ -79,11 +79,15 @@ resolve_java (const char *class_name, cls = (*env)->FindClass(env, jni_class_name); free(jni_class_name); if (!cls) { + (*env)->ExceptionDescribe(env); + (*env)->ExceptionClear(env); return NULL; } mid = (*env)->GetStaticMethodID(env, cls, method_name, method_signature); if (!mid) { + (*env)->ExceptionDescribe(env); + (*env)->ExceptionClear(env); (*env)->DeleteLocalRef(env, cls); return NULL; } From 0013016ed04ea82a36a023506800f6fd1b7c0fb2 Mon Sep 17 00:00:00 2001 From: Vedant Tewari Date: Wed, 25 Sep 2024 14:44:19 -0500 Subject: [PATCH 6/7] Adding type checking and buffer safety for Java method signatures --- cobc/codegen.c | 112 +++++++++++++++++++++++++++++++++---------------- cobc/typeck.c | 45 +++++++++++++++++++- 2 files changed, 121 insertions(+), 36 deletions(-) diff --git a/cobc/codegen.c b/cobc/codegen.c index 36e4940ed..e567da20e 100644 --- a/cobc/codegen.c +++ b/cobc/codegen.c @@ -7100,8 +7100,8 @@ output_java_call (struct cb_call *p) char *last_dot; char *method_name; const char *class_name; - char return_type_signature[32]; - char method_signature[2048] = "("; + char return_type_signature[COB_MINI_BUFF]; + char method_signature[COB_NORMAL_BUFF] = "("; char* mangled; struct cb_tree_common *ptr; @@ -7122,29 +7122,44 @@ output_java_call (struct cb_call *p) for (int i = 0; (ptr = ((struct cb_tree_common **)p->args)[i]) != NULL; i++) { switch(CB_TREE_TAG(ptr)) { - case CB_TAG_INTEGER: - strcat(method_signature, "I"); - break; + case CB_TAG_INTEGER: { + struct cb_picture* pic = CB_FIELD(ptr)->pic; + if (pic) { + int n = pic->digits; + if (CB_FIELD(ptr)->usage == CB_USAGE_COMP_5) { + if (n >= 1 && n <= 4) { + strncat(method_signature, "S", COB_NORMAL_BUFF - strlen(method_signature) - 1); + } else if (n >= 5 && n <= 9) { + strncat(method_signature, "I", COB_NORMAL_BUFF - strlen(method_signature) - 1); + } else if (n >= 10 && n <= 18) { + strncat(method_signature, "J", COB_NORMAL_BUFF - strlen(method_signature) - 1); + } + } else if (CB_FIELD(ptr)->usage == CB_USAGE_PACKED || CB_FIELD(ptr)->usage == CB_USAGE_DISPLAY) { + strncat(method_signature, "Ljava/math/BigDecimal;", COB_NORMAL_BUFF - strlen(method_signature) - 1); + } + } + break; + } case CB_USAGE_FLOAT: - strcat(method_signature, "F"); + strncat(method_signature, "F", COB_NORMAL_BUFF - strlen(method_signature) - 1); break; case CB_USAGE_DOUBLE: - strcat(method_signature, "D"); + strncat(method_signature, "D", COB_NORMAL_BUFF - strlen(method_signature) - 1); break; case CB_CLASS_BOOLEAN: - strcat(method_signature, "Z"); + strncat(method_signature, "Z", COB_NORMAL_BUFF - strlen(method_signature) - 1); break; case CB_TAG_STRING: - strcat(method_signature, "Ljava/lang/String;"); + strncat(method_signature, "Ljava/lang/String;", COB_NORMAL_BUFF - strlen(method_signature) - 1); break; case CB_USAGE_OBJECT: - strcat(method_signature, "Ljava/lang/Object;"); + strncat(method_signature, "Ljava/lang/Object;", COB_NORMAL_BUFF - strlen(method_signature) - 1); break; case CB_TAG_LITERAL: if(CB_TREE_CATEGORY(ptr) == CB_CATEGORY_NUMERIC) { - strcat(method_signature, "I"); + strncat(method_signature, "I", COB_NORMAL_BUFF - strlen(method_signature) - 1); } else if(CB_TREE_CATEGORY(ptr) == CB_CATEGORY_ALPHANUMERIC) { - strcat(method_signature, "Ljava/lang/String;"); + strncat(method_signature, "Ljava/lang/String;", COB_NORMAL_BUFF - strlen(method_signature) - 1); } break; case CB_TAG_LIST: @@ -7160,23 +7175,38 @@ output_java_call (struct cb_call *p) inner_list = (struct cb_tree_common **) list_elements[j]; } else { switch (CB_TREE_TAG(list_elements[j])) { - case CB_TAG_INTEGER: - strcat(method_signature, "[I"); - break; + case CB_TAG_INTEGER: { + struct cb_picture* pic = CB_FIELD(list_elements[j])->pic; + if (pic) { + int n = pic->digits; + if (CB_FIELD(list_elements[j])->usage == CB_USAGE_COMP_5) { + if (n >= 1 && n <= 4) { + strncat(method_signature, "[S", COB_NORMAL_BUFF - strlen(method_signature) - 1); + } else if (n >= 5 && n <= 9) { + strncat(method_signature, "[I", COB_NORMAL_BUFF - strlen(method_signature) - 1); + } else if (n >= 10 && n <= 18) { + strncat(method_signature, "[J", COB_NORMAL_BUFF - strlen(method_signature) - 1); + } + } else if (CB_FIELD(list_elements[j])->usage == CB_USAGE_PACKED || CB_FIELD(list_elements[j])->usage == CB_USAGE_DISPLAY) { + strncat(method_signature, "[Ljava/math/BigDecimal;", COB_NORMAL_BUFF - strlen(method_signature) - 1); + } + } + break; + } case CB_USAGE_FLOAT: - strcat(method_signature, "[F"); + strncat(method_signature, "[F", COB_NORMAL_BUFF - strlen(method_signature) - 1); break; case CB_USAGE_DOUBLE: - strcat(method_signature, "[D"); + strncat(method_signature, "[D", COB_NORMAL_BUFF - strlen(method_signature) - 1); break; case CB_CLASS_BOOLEAN: - strcat(method_signature, "[Z"); + strncat(method_signature, "[Z", COB_NORMAL_BUFF - strlen(method_signature) - 1); break; case CB_TAG_STRING: - strcat(method_signature, "[Ljava/lang/String;"); + strncat(method_signature, "[Ljava/lang/String;", COB_NORMAL_BUFF - strlen(method_signature) - 1); break; case CB_USAGE_OBJECT: - strcat(method_signature, "[Ljava/lang/Object;"); + strncat(method_signature, "[Ljava/lang/Object;", COB_NORMAL_BUFF - strlen(method_signature) - 1); break; default: cobc_err_msg(_("Unsupported array element type in Java method call")); @@ -7186,46 +7216,58 @@ output_java_call (struct cb_call *p) } list_elements = inner_list; } - - if (array_dimension > 2) { - cobc_err_msg(_("Unsupported array dimension: %d"), array_dimension); - COBC_ABORT(); - } } break; default: - cobc_err_msg(_("Unsupported argument type in Java method call")); COBC_ABORT(); } } if (p->call_returning == NULL) { - strcat(method_signature, ")V"); + strncat(method_signature, ")V", COB_NORMAL_BUFF - strlen(method_signature) - 1); strcpy(return_type_signature, "void"); } else { switch(CB_TREE_TAG(p->call_returning)) { - case CB_TAG_INTEGER: - strcat(method_signature, ")I"); - strcpy(return_type_signature, "jint"); + case CB_TAG_INTEGER: { + struct cb_picture* pic = CB_FIELD(p->call_returning)->pic; + if (pic) { + int n = pic->digits; + if (CB_FIELD(p->call_returning)->usage == CB_USAGE_COMP_5) { + if (n >= 1 && n <= 4) { + strncat(method_signature, ")S", COB_NORMAL_BUFF - strlen(method_signature) - 1); + strcpy(return_type_signature, "jshort"); + } else if (n >= 5 && n <= 9) { + strncat(method_signature, ")I", COB_NORMAL_BUFF - strlen(method_signature) - 1); + strcpy(return_type_signature, "jint"); + } else if (n >= 10 && n <= 18) { + strncat(method_signature, ")J", COB_NORMAL_BUFF - strlen(method_signature) - 1); + strcpy(return_type_signature, "jlong"); + } + } else if (CB_FIELD(p->call_returning)->usage == CB_USAGE_PACKED || CB_FIELD(p->call_returning)->usage == CB_USAGE_DISPLAY) { + strncat(method_signature, ")Ljava/math/BigDecimal;", COB_NORMAL_BUFF - strlen(method_signature) - 1); + strcpy(return_type_signature, "jobject"); + } + } break; + } case CB_TAG_STRING: - strcat(method_signature, ")Ljava/lang/String;"); + strncat(method_signature, ")Ljava/lang/String;", COB_NORMAL_BUFF - strlen(method_signature) - 1); strcpy(return_type_signature, "jstring"); break; case CB_USAGE_FLOAT: - strcat(method_signature, ")F"); + strncat(method_signature, ")F", COB_NORMAL_BUFF - strlen(method_signature) - 1); strcpy(return_type_signature, "jfloat"); break; case CB_USAGE_DOUBLE: - strcat(method_signature, ")D"); + strncat(method_signature, ")D", COB_NORMAL_BUFF - strlen(method_signature) - 1); strcpy(return_type_signature, "jdouble"); break; case CB_CLASS_BOOLEAN: - strcat(method_signature, ")Z"); + strncat(method_signature, ")Z", COB_NORMAL_BUFF - strlen(method_signature) - 1); strcpy(return_type_signature, "jboolean"); break; default: - strcat(method_signature, ")V"); + strncat(method_signature, ")V", COB_NORMAL_BUFF - strlen(method_signature) - 1); strcpy(return_type_signature, "void"); break; } diff --git a/cobc/typeck.c b/cobc/typeck.c index d147d211a..c04b12dc0 100644 --- a/cobc/typeck.c +++ b/cobc/typeck.c @@ -3985,6 +3985,50 @@ cb_check_conformance (cb_tree prog_ref, cb_tree using_list, } } + /* Additional conformance checks for Java method calls */ + for (l = using_list; l; l = CB_CHAIN(l)) { + struct cb_tree_common *arg = (struct cb_tree_common *)CB_VALUE(l); + + /* Ensure only field identifiers are used */ + if (CB_TREE_TAG(arg) == CB_TAG_LITERAL || CB_TREE_TAG(arg) == CB_TAG_FUNCALL) { + cobc_err_msg(_("Invalid argument type in Java method call. Only field identifiers are allowed.")); + COBC_ABORT(); + } + + /* Check for unsupported array element types and dimensions */ + if (CB_TREE_TAG(arg) == CB_TAG_LIST) { + int array_dimension = 1; + struct cb_tree_common **list_elements = (struct cb_tree_common **)arg; + while (list_elements != NULL) { + struct cb_tree_common **inner_list = NULL; + for (int j = 0; list_elements[j] != NULL; j++) { + if (CB_TREE_TAG(list_elements[j]) == CB_TAG_LIST) { + array_dimension++; + inner_list = (struct cb_tree_common **)list_elements[j]; + } else { + switch (CB_TREE_TAG(list_elements[j])) { + case CB_TAG_INTEGER: + case CB_USAGE_FLOAT: + case CB_USAGE_DOUBLE: + case CB_CLASS_BOOLEAN: + case CB_TAG_STRING: + case CB_USAGE_OBJECT: + break; + default: + cobc_err_msg(_("Unsupported array element type in Java method call")); + COBC_ABORT(); + } + } + } + list_elements = inner_list; + } + if (array_dimension > 2) { + cobc_err_msg(_("Unsupported array dimension: %d"), array_dimension); + COBC_ABORT(); + } + } + } + /* Check RETURNING item. */ if (returning && program->returning) { @@ -15478,7 +15522,6 @@ cb_emit_xml_parse (cb_tree data, cb_tree proc, #endif ref = cb_ref (data); if (CB_FIELD_P (ref)) { - struct cb_field * field = CB_FIELD (ref); /* type checks here */ cb_emit (cb_build_xml_parse (data, proc, returning_national, encoding, validation)); From 91751c8db8dfd73534b9ef26406379aee244dda0 Mon Sep 17 00:00:00 2001 From: Vedant Tewari Date: Fri, 15 Nov 2024 08:59:05 -0600 Subject: [PATCH 7/7] Only considering int types for now --- cobc/codegen.c | 181 ++++++++++--------------------------------------- 1 file changed, 37 insertions(+), 144 deletions(-) diff --git a/cobc/codegen.c b/cobc/codegen.c index e567da20e..c5c6de381 100644 --- a/cobc/codegen.c +++ b/cobc/codegen.c @@ -7121,160 +7121,53 @@ output_java_call (struct cb_call *p) class_name = class_and_method_name; for (int i = 0; (ptr = ((struct cb_tree_common **)p->args)[i]) != NULL; i++) { - switch(CB_TREE_TAG(ptr)) { - case CB_TAG_INTEGER: { - struct cb_picture* pic = CB_FIELD(ptr)->pic; - if (pic) { - int n = pic->digits; - if (CB_FIELD(ptr)->usage == CB_USAGE_COMP_5) { - if (n >= 1 && n <= 4) { - strncat(method_signature, "S", COB_NORMAL_BUFF - strlen(method_signature) - 1); - } else if (n >= 5 && n <= 9) { - strncat(method_signature, "I", COB_NORMAL_BUFF - strlen(method_signature) - 1); - } else if (n >= 10 && n <= 18) { - strncat(method_signature, "J", COB_NORMAL_BUFF - strlen(method_signature) - 1); - } - } else if (CB_FIELD(ptr)->usage == CB_USAGE_PACKED || CB_FIELD(ptr)->usage == CB_USAGE_DISPLAY) { - strncat(method_signature, "Ljava/math/BigDecimal;", COB_NORMAL_BUFF - strlen(method_signature) - 1); + if (CB_TREE_TAG(ptr) == CB_TAG_INTEGER) { + struct cb_picture* pic = CB_FIELD(ptr)->pic; + if (pic) { + int n = pic->digits; + if (CB_FIELD(ptr)->usage == CB_USAGE_COMP_5) { + if (n >= 1 && n <= 4) { + strncat(method_signature, "S", COB_NORMAL_BUFF - strlen(method_signature) - 1); + } else if (n >= 5 && n <= 9) { + strncat(method_signature, "I", COB_NORMAL_BUFF - strlen(method_signature) - 1); + } else if (n >= 10 && n <= 18) { + strncat(method_signature, "J", COB_NORMAL_BUFF - strlen(method_signature) - 1); } + } else if (CB_FIELD(ptr)->usage == CB_USAGE_PACKED || CB_FIELD(ptr)->usage == CB_USAGE_DISPLAY) { + strncat(method_signature, "Ljava/math/BigDecimal;", COB_NORMAL_BUFF - strlen(method_signature) - 1); } - break; - } - case CB_USAGE_FLOAT: - strncat(method_signature, "F", COB_NORMAL_BUFF - strlen(method_signature) - 1); - break; - case CB_USAGE_DOUBLE: - strncat(method_signature, "D", COB_NORMAL_BUFF - strlen(method_signature) - 1); - break; - case CB_CLASS_BOOLEAN: - strncat(method_signature, "Z", COB_NORMAL_BUFF - strlen(method_signature) - 1); - break; - case CB_TAG_STRING: - strncat(method_signature, "Ljava/lang/String;", COB_NORMAL_BUFF - strlen(method_signature) - 1); - break; - case CB_USAGE_OBJECT: - strncat(method_signature, "Ljava/lang/Object;", COB_NORMAL_BUFF - strlen(method_signature) - 1); - break; - case CB_TAG_LITERAL: - if(CB_TREE_CATEGORY(ptr) == CB_CATEGORY_NUMERIC) { - strncat(method_signature, "I", COB_NORMAL_BUFF - strlen(method_signature) - 1); - } else if(CB_TREE_CATEGORY(ptr) == CB_CATEGORY_ALPHANUMERIC) { - strncat(method_signature, "Ljava/lang/String;", COB_NORMAL_BUFF - strlen(method_signature) - 1); - } - break; - case CB_TAG_LIST: - { - struct cb_tree_common **list_elements = (struct cb_tree_common **) ptr; - int array_dimension = 1; - - while (list_elements != NULL) { - struct cb_tree_common **inner_list = NULL; - for (int j = 0; list_elements[j] != NULL; j++) { - if (CB_TREE_TAG(list_elements[j]) == CB_TAG_LIST) { - array_dimension++; - inner_list = (struct cb_tree_common **) list_elements[j]; - } else { - switch (CB_TREE_TAG(list_elements[j])) { - case CB_TAG_INTEGER: { - struct cb_picture* pic = CB_FIELD(list_elements[j])->pic; - if (pic) { - int n = pic->digits; - if (CB_FIELD(list_elements[j])->usage == CB_USAGE_COMP_5) { - if (n >= 1 && n <= 4) { - strncat(method_signature, "[S", COB_NORMAL_BUFF - strlen(method_signature) - 1); - } else if (n >= 5 && n <= 9) { - strncat(method_signature, "[I", COB_NORMAL_BUFF - strlen(method_signature) - 1); - } else if (n >= 10 && n <= 18) { - strncat(method_signature, "[J", COB_NORMAL_BUFF - strlen(method_signature) - 1); - } - } else if (CB_FIELD(list_elements[j])->usage == CB_USAGE_PACKED || CB_FIELD(list_elements[j])->usage == CB_USAGE_DISPLAY) { - strncat(method_signature, "[Ljava/math/BigDecimal;", COB_NORMAL_BUFF - strlen(method_signature) - 1); - } - } - break; - } - case CB_USAGE_FLOAT: - strncat(method_signature, "[F", COB_NORMAL_BUFF - strlen(method_signature) - 1); - break; - case CB_USAGE_DOUBLE: - strncat(method_signature, "[D", COB_NORMAL_BUFF - strlen(method_signature) - 1); - break; - case CB_CLASS_BOOLEAN: - strncat(method_signature, "[Z", COB_NORMAL_BUFF - strlen(method_signature) - 1); - break; - case CB_TAG_STRING: - strncat(method_signature, "[Ljava/lang/String;", COB_NORMAL_BUFF - strlen(method_signature) - 1); - break; - case CB_USAGE_OBJECT: - strncat(method_signature, "[Ljava/lang/Object;", COB_NORMAL_BUFF - strlen(method_signature) - 1); - break; - default: - cobc_err_msg(_("Unsupported array element type in Java method call")); - COBC_ABORT(); - } - } - } - list_elements = inner_list; - } - } - break; - default: - COBC_ABORT(); + } } } if (p->call_returning == NULL) { strncat(method_signature, ")V", COB_NORMAL_BUFF - strlen(method_signature) - 1); strcpy(return_type_signature, "void"); - } else { - switch(CB_TREE_TAG(p->call_returning)) { - case CB_TAG_INTEGER: { - struct cb_picture* pic = CB_FIELD(p->call_returning)->pic; - if (pic) { - int n = pic->digits; - if (CB_FIELD(p->call_returning)->usage == CB_USAGE_COMP_5) { - if (n >= 1 && n <= 4) { - strncat(method_signature, ")S", COB_NORMAL_BUFF - strlen(method_signature) - 1); - strcpy(return_type_signature, "jshort"); - } else if (n >= 5 && n <= 9) { - strncat(method_signature, ")I", COB_NORMAL_BUFF - strlen(method_signature) - 1); - strcpy(return_type_signature, "jint"); - } else if (n >= 10 && n <= 18) { - strncat(method_signature, ")J", COB_NORMAL_BUFF - strlen(method_signature) - 1); - strcpy(return_type_signature, "jlong"); - } - } else if (CB_FIELD(p->call_returning)->usage == CB_USAGE_PACKED || CB_FIELD(p->call_returning)->usage == CB_USAGE_DISPLAY) { - strncat(method_signature, ")Ljava/math/BigDecimal;", COB_NORMAL_BUFF - strlen(method_signature) - 1); - strcpy(return_type_signature, "jobject"); - } - } - break; + } else if (CB_TREE_TAG(p->call_returning) == CB_TAG_INTEGER) { + struct cb_picture* pic = CB_FIELD(p->call_returning)->pic; + if (pic) { + int n = pic->digits; + if (CB_FIELD(p->call_returning)->usage == CB_USAGE_COMP_5) { + if (n >= 1 && n <= 4) { + strncat(method_signature, ")S", COB_NORMAL_BUFF - strlen(method_signature) - 1); + strcpy(return_type_signature, "jshort"); + } else if (n >= 5 && n <= 9) { + strncat(method_signature, ")I", COB_NORMAL_BUFF - strlen(method_signature) - 1); + strcpy(return_type_signature, "jint"); + } else if (n >= 10 && n <= 18) { + strncat(method_signature, ")J", COB_NORMAL_BUFF - strlen(method_signature) - 1); + strcpy(return_type_signature, "jlong"); + } + } else if (CB_FIELD(p->call_returning)->usage == CB_USAGE_PACKED || CB_FIELD(p->call_returning)->usage == CB_USAGE_DISPLAY) { + strncat(method_signature, ")Ljava/math/BigDecimal;", COB_NORMAL_BUFF - strlen(method_signature) - 1); + strcpy(return_type_signature, "jobject"); } - case CB_TAG_STRING: - strncat(method_signature, ")Ljava/lang/String;", COB_NORMAL_BUFF - strlen(method_signature) - 1); - strcpy(return_type_signature, "jstring"); - break; - case CB_USAGE_FLOAT: - strncat(method_signature, ")F", COB_NORMAL_BUFF - strlen(method_signature) - 1); - strcpy(return_type_signature, "jfloat"); - break; - case CB_USAGE_DOUBLE: - strncat(method_signature, ")D", COB_NORMAL_BUFF - strlen(method_signature) - 1); - strcpy(return_type_signature, "jdouble"); - break; - case CB_CLASS_BOOLEAN: - strncat(method_signature, ")Z", COB_NORMAL_BUFF - strlen(method_signature) - 1); - strcpy(return_type_signature, "jboolean"); - break; - default: - strncat(method_signature, ")V", COB_NORMAL_BUFF - strlen(method_signature) - 1); - strcpy(return_type_signature, "void"); - break; } + } else { + strncat(method_signature, ")V", COB_NORMAL_BUFF - strlen(method_signature) - 1); + strcpy(return_type_signature, "void"); } - strcat(method_signature, ")V"); - lookup_java_call(mangled, method_signature); output_line("if (call_java_%s == NULL)", mangled); output_block_open(); @@ -7282,8 +7175,8 @@ output_java_call (struct cb_call *p) output_prefix(); output_line("call_java_%s = ", mangled); output("cob_resolve_java(\"%s\", \"%s\", \"%s\", \"()V\");", class_name, method_name, method_signature); - output_newline (); - output_prefix (); + output_newline(); + output_prefix(); output_line("cob_call_java(call_java_%s);\n", mangled); output_newline(); output_block_close();