From 7dbe6887f80d98a17ba493044051c589011ec02e Mon Sep 17 00:00:00 2001 From: Vedant Tewari Date: Tue, 9 Jul 2024 08:26:25 -0500 Subject: [PATCH 01/20] 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 8ac6f699cc95cb3cd7157a812289af763de8e705 Mon Sep 17 00:00:00 2001 From: Nicolas Berthier Date: Thu, 22 Aug 2024 10:13:16 +0200 Subject: [PATCH 02/20] 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 70f22e1a14dee4871bde8ac7bdd3c9307f50566a Mon Sep 17 00:00:00 2001 From: Vedant Tewari Date: Thu, 22 Aug 2024 20:03:09 -0500 Subject: [PATCH 03/20] added runtime checks in java.c --- libcob/java.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/libcob/java.c b/libcob/java.c index 9f569aa81..e6fd2a908 100644 --- a/libcob/java.c +++ b/libcob/java.c @@ -25,6 +25,7 @@ /* Force symbol exports */ #define COB_LIB_EXPIMP +#include "common.h" #include "libcob.h" #include "coblocal.h" @@ -77,19 +78,23 @@ resolve_java (const char *class_name, } cls = (*env)->FindClass(env, jni_class_name); - free(jni_class_name); + cob_free(jni_class_name); if (!cls) { + cob_runtime_error(_("Java class '%s' not found"), class_name); return NULL; } mid = (*env)->GetStaticMethodID(env, cls, method_name, method_signature); if (!mid) { + cob_runtime_error(_("Java method '%s' with signature '%s' not found in class '%s'"), + method_name, method_signature, class_name); (*env)->DeleteLocalRef(env, cls); return NULL; } handle = (cob_java_handle*)cob_malloc(sizeof(cob_java_handle)); if (!handle) { + cob_runtime_error(_("Memory allocation failed for Java method handle")); (*env)->DeleteLocalRef(env, cls); return NULL; } From b7b8b42b06a9e668d2a41b101ddda65e22b1ef4b Mon Sep 17 00:00:00 2001 From: Vedant Tewari Date: Thu, 22 Aug 2024 20:42:23 -0500 Subject: [PATCH 04/20] Implemented logic in parser.y to detect malformed errors and unsupported calls --- cobc/codegen.c | 8 ++++---- cobc/parser.y | 15 ++++++++++++--- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/cobc/codegen.c b/cobc/codegen.c index 2d7dd9071..5df61262a 100644 --- a/cobc/codegen.c +++ b/cobc/codegen.c @@ -7093,6 +7093,10 @@ output_field_constant (cb_tree x, int n, const char *flagname) static void output_java_call (struct cb_call *p) { + if (p->args != NULL || p->call_returning != NULL) { + CB_PENDING ("Java method call with parameters or return values"); + COBC_ABORT (); + } 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; @@ -7106,10 +7110,6 @@ 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 (); - } *last_dot = '\0'; method_name = last_dot + 1; diff --git a/cobc/parser.y b/cobc/parser.y index ce6d0c2ea..85575841d 100644 --- a/cobc/parser.y +++ b/cobc/parser.y @@ -12255,9 +12255,18 @@ call_body: 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; - } + if (strncasecmp("Java.", s, 5) == 0) { + call_conv = CB_CONV_JAVA; + } + /* Check for malformed Java method names */ + char* class_and_method_name = s + 5; + if (strchr(class_and_method_name, '.') == NULL) { + cb_error_x ($3, _("malformed Java method name '%s', expected format 'Java.ClassName.methodName'"), s); + } + /* Check for unsupported Java method calls with parameters or return values */ + if($7 != NULL || $8 != NULL) { + CB_PENDING("Java method calls with parameters or return values"); + } cb_check_conformance ($3, $7, $8); } else if (CB_REFERENCE_P ($3)) { cb_tree ref = cb_ref ($3); From 1493f2f512775c2832e5008643414193c2d84730 Mon Sep 17 00:00:00 2001 From: Vedant Tewari Date: Thu, 22 Aug 2024 21:15:02 -0500 Subject: [PATCH 05/20] Added updates to ChangeLog and a description to .texi file --- doc/gnucobol.texi | 40 ++++++++++++++++++++++++++++++++++++++++ libcob/ChangeLog | 11 ++++++++--- 2 files changed, 48 insertions(+), 3 deletions(-) diff --git a/doc/gnucobol.texi b/doc/gnucobol.texi index e724e5d73..fc85d4b27 100644 --- a/doc/gnucobol.texi +++ b/doc/gnucobol.texi @@ -2211,6 +2211,7 @@ For a complete list of supported system routines, * CBL_GC_NANOSLEEP:: Sleep for nanoseconds * CBL_GC_FORK:: Fork the current COBOL process to a new one * CBL_GC_WAITPID:: Wait for a system process to end +* Java Integration:: Interfacing with Java through JNI @end menu @node CBL_GC_GETOPT @@ -2575,6 +2576,45 @@ is not available on the current system. END-DISPLAY @end example +@node Java Integration +@section Java Integration +@cindex Java, JNI, method calls + +GnuCOBOL now supports integration with Java through the Java Native Interface (JNI). This allows COBOL programs to call Java methods directly. + +@subsection Supported Method Calls +Currently, only void Java methods with no parameters are supported. Method calls that do not conform to this restriction will result in an error at compile-time. + +@subsection Setting Up Java Integration +To use Java integration, you need to set the `JAVA_HOME` environment variable to point to your Java installation. Additionally, ensure that your `PATH` includes the Java binaries. + +@subsection Error Handling +Any attempt to call Java methods with parameters or expecting return values will result in a compile-time error. The GnuCOBOL compiler will issue a warning if the method name does not follow the `Java.ClassName.methodName` format. + +@subsection Known Limitations +Currently, GnuCOBOL supports only void methods without parameters. Future updates will address these limitations to provide broader Java integration capabilities. + +@subsection Example Usage +Here is an example of a COBOL program calling a Java method: + +@example + IDENTIFICATION DIVISION. + PROGRAM-ID. SampleJavaCall. + PROCEDURE DIVISION. + CALL "Java.com.example.HelloWorld.printMessage". + STOP RUN. +@end example + +Ensure the class `com.example.HelloWorld` is in your classpath and that `printMessage` is a static void method with no parameters. + +@subsection Environment Variables +Set the following environment variables to enable Java integration: + +@example +export JAVA_HOME=/path/to/java +export PATH=$JAVA_HOME/bin:$PATH +@end example + @node Appendices @menu diff --git a/libcob/ChangeLog b/libcob/ChangeLog index 2d206626b..a9d220e3c 100644 --- a/libcob/ChangeLog +++ b/libcob/ChangeLog @@ -1,13 +1,18 @@ +2024-08-22 Vedant Tewari + * cobc/codegen.c Added error handling to restrict Java method calls to void methods without parameters or return values + * cobc/parser.y Implemented detection of malformed Java method names during parsing. Added checks for unsupported method calls with parameters or return values + * java.c Enhanced error handling with `cob_runtime_error` for missing classes + 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 + * 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 From 0c1eb53281d64c978a19369321f501b486f897da Mon Sep 17 00:00:00 2001 From: Vedant Tewari Date: Mon, 2 Sep 2024 14:59:06 -0500 Subject: [PATCH 06/20] Refactored malformed checks in parser --- cobc/parser.y | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/cobc/parser.y b/cobc/parser.y index 85575841d..9aec12614 100644 --- a/cobc/parser.y +++ b/cobc/parser.y @@ -12257,15 +12257,14 @@ call_body: char* s = (char *)CB_LITERAL ($3)->data; if (strncasecmp("Java.", s, 5) == 0) { call_conv = CB_CONV_JAVA; - } - /* Check for malformed Java method names */ - char* class_and_method_name = s + 5; - if (strchr(class_and_method_name, '.') == NULL) { - cb_error_x ($3, _("malformed Java method name '%s', expected format 'Java.ClassName.methodName'"), s); - } - /* Check for unsupported Java method calls with parameters or return values */ - if($7 != NULL || $8 != NULL) { - CB_PENDING("Java method calls with parameters or return values"); + /* Check for malformed Java method names */ + if (strchr(s + 5, '.') == NULL) { + cb_error_x ($3, _("malformed Java method name '%s', expected format 'Java.ClassName.methodName'"), s); + } + /* Check for unsupported Java method calls with parameters or return values */ + if($7 != NULL || $8 != NULL) { + CB_PENDING("Java method calls with parameters or return values"); + } } cb_check_conformance ($3, $7, $8); } else if (CB_REFERENCE_P ($3)) { From ea840e3aff0ca47ff59b40bc0d126dbe7453203f Mon Sep 17 00:00:00 2001 From: Vedant Tewari Date: Mon, 9 Sep 2024 02:11:48 -0500 Subject: [PATCH 07/20] Added test for invalid name --- tests/testsuite.src/run_java.at | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/tests/testsuite.src/run_java.at b/tests/testsuite.src/run_java.at index 0061d498a..ee5d7af04 100644 --- a/tests/testsuite.src/run_java.at +++ b/tests/testsuite.src/run_java.at @@ -65,3 +65,19 @@ AT_CHECK([$COMPILE prog.cob], [0], [], []) # TODO: should fail! AT_CHECK([$COBCRUN_DIRECT ./prog], [0], []) AT_CLEANUP + +AT_SETUP([CALL Java with malformed method name]) +AT_KEYWORDS([extensions jni malformed]) + +AT_SKIP_IF([test "$COB_HAS_JNI" = "no"]) + +AT_DATA([prog.cob], [ + IDENTIFICATION DIVISION. + PROGRAM-ID. prog. + PROCEDURE DIVISION. + CALL "Java.InvalidName" + STOP RUN. +]) + +AT_CHECK([$COMPILE prog.cob], [1], [], [stderr]) +AT_CLEANUP \ No newline at end of file From 3cfe83da32c5a2481588307377897d26713efaee Mon Sep 17 00:00:00 2001 From: Nicolas Berthier Date: Thu, 12 Sep 2024 16:53:39 +0200 Subject: [PATCH 08/20] Fix simple tests and adjust runtime error handling --- libcob/java.c | 6 ++--- tests/testsuite.src/run_java.at | 47 ++++++++++++++++++++++++++------- 2 files changed, 41 insertions(+), 12 deletions(-) diff --git a/libcob/java.c b/libcob/java.c index e6fd2a908..3e9840300 100644 --- a/libcob/java.c +++ b/libcob/java.c @@ -81,7 +81,7 @@ resolve_java (const char *class_name, cob_free(jni_class_name); if (!cls) { cob_runtime_error(_("Java class '%s' not found"), class_name); - return NULL; + cob_hard_failure (); } mid = (*env)->GetStaticMethodID(env, cls, method_name, method_signature); @@ -89,14 +89,14 @@ resolve_java (const char *class_name, cob_runtime_error(_("Java method '%s' with signature '%s' not found in class '%s'"), method_name, method_signature, class_name); (*env)->DeleteLocalRef(env, cls); - return NULL; + cob_hard_failure (); } handle = (cob_java_handle*)cob_malloc(sizeof(cob_java_handle)); if (!handle) { cob_runtime_error(_("Memory allocation failed for Java method handle")); (*env)->DeleteLocalRef(env, cls); - return NULL; + cob_hard_failure (); } handle->cls = (*env)->NewGlobalRef(env, cls); diff --git a/tests/testsuite.src/run_java.at b/tests/testsuite.src/run_java.at index ee5d7af04..5a76a239c 100644 --- a/tests/testsuite.src/run_java.at +++ b/tests/testsuite.src/run_java.at @@ -48,7 +48,26 @@ AT_CHECK([$COBCRUN_DIRECT ./prog], [0], AT_CLEANUP -AT_SETUP([CALL Java static void (void) (missing)]) +AT_SETUP([CALL Java with malformed method name]) +AT_KEYWORDS([extensions jni malformed]) + +AT_SKIP_IF([test "$COB_HAS_JNI" = "no"]) + +AT_DATA([prog.cob], [ + IDENTIFICATION DIVISION. + PROGRAM-ID. prog. + PROCEDURE DIVISION. + CALL "Java.InvalidName" + STOP RUN. +]) + +AT_CHECK([$COMPILE prog.cob], [1], [], +[prog.cob:5: error: malformed Java method name 'Java.InvalidName', expected format 'Java.ClassName.methodName' +]) +AT_CLEANUP + + +AT_SETUP([CALL Java static void (void) (missing class)]) AT_KEYWORDS([extensions jni]) AT_SKIP_IF([test "$COB_HAS_JNI" = "no"]) @@ -57,27 +76,37 @@ AT_DATA([prog.cob], [ IDENTIFICATION DIVISION. PROGRAM-ID. prog. PROCEDURE DIVISION. - CALL "Java.Test.missingMethod" + CALL "Java.Test.isAMissingClass" STOP RUN. ]) AT_CHECK([$COMPILE prog.cob], [0], [], []) -# TODO: should fail! -AT_CHECK([$COBCRUN_DIRECT ./prog], [0], []) +AT_CHECK([$COBCRUN_DIRECT ./prog], [1], [], +[libcob: prog.cob:5: error: Java class 'Test' not found +]) AT_CLEANUP -AT_SETUP([CALL Java with malformed method name]) -AT_KEYWORDS([extensions jni malformed]) + +AT_SETUP([CALL Java static void (void) (missing method)]) +AT_KEYWORDS([extensions jni]) AT_SKIP_IF([test "$COB_HAS_JNI" = "no"]) +AT_DATA([Test.java], [ +public class Test {} +]) + AT_DATA([prog.cob], [ IDENTIFICATION DIVISION. PROGRAM-ID. prog. PROCEDURE DIVISION. - CALL "Java.InvalidName" + CALL "Java.Test.missingMethod" STOP RUN. ]) -AT_CHECK([$COMPILE prog.cob], [1], [], [stderr]) -AT_CLEANUP \ No newline at end of file +AT_CHECK([$JAVAC Test.java], [0], [], []) +AT_CHECK([$COMPILE prog.cob], [0], [], []) +AT_CHECK([$COBCRUN_DIRECT ./prog], [1], [], +[libcob: prog.cob:5: error: Java method 'missingMethod' with signature '()V' not found in class 'Test' +]) +AT_CLEANUP From bb76d661bc60073aa94b56c970653e37e2c3ffe2 Mon Sep 17 00:00:00 2001 From: Vedant Tewari Date: Sat, 14 Sep 2024 19:47:55 -0500 Subject: [PATCH 09/20] Minor Adjustments --- ChangeLog | 6 +++--- cobc/codegen.c | 26 +++++++++++++++++++++++++- libcob/call.c | 9 +++++++-- tests/testsuite.src/run_java.at | 22 ++++++++++++++++++++++ 4 files changed, 57 insertions(+), 6 deletions(-) diff --git a/ChangeLog b/ChangeLog index 8a5188323..fe73d596b 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,9 +1,9 @@ 2024-07-12 Vedant Tewari - * 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 + * m4/ax_prog_java.m4, m4/ax_jni_include_dir.m4: Added macros for JNI checks + * configure.ac: Added support for Java interoperability through JNI, extending GnuCOBOL ability to interact with external Java libraries + * NEWS, DEPENDENCIES: Updated to reflect support for JNI 2023-02-25 Ron Norman diff --git a/cobc/codegen.c b/cobc/codegen.c index 5df61262a..908847611 100644 --- a/cobc/codegen.c +++ b/cobc/codegen.c @@ -147,7 +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 *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; @@ -7090,6 +7090,28 @@ output_field_constant (cb_tree x, int n, const char *flagname) output_newline (); } +static void +output_exception_handling(struct cb_call *p) +{ + if (p->stmt1) { + output_line("cob_glob_ptr->cob_stmt_exception = 1;"); + output_line("COB_RESET_EXCEPTION(0);"); + } else { + output_line("cob_glob_ptr->cob_stmt_exception = 0;"); + } + + output_line("if ((cob_glob_ptr->cob_exception_code & 0xff00) != 0) {"); + output_block_open(); + + if (p->stmt1) { + output_stmt(p->stmt1); + } else if (p->stmt2) { + output_stmt(p->stmt2); + } + output_block_close(); + output_line("COB_RESET_EXCEPTION(0);"); +} + static void output_java_call (struct cb_call *p) { @@ -7127,6 +7149,7 @@ output_java_call (struct cb_call *p) output_line("cob_call_java(call_java_%s);\n", mangled); output_newline(); output_block_close(); + output_exception_handling(p); } static void @@ -7160,6 +7183,7 @@ output_call (struct cb_call *p) if (p->convention & CB_CONV_JAVA) { output_java_call(p); + output_exception_handling(p); return; } diff --git a/libcob/call.c b/libcob/call.c index b927b63ce..9b3dc391e 100644 --- a/libcob/call.c +++ b/libcob/call.c @@ -2108,6 +2108,11 @@ cob_resolve_java (const char *class_name, void cob_call_java (const cob_java_handle *method_handle) { + if (method_handle == NULL) { + cob_runtime_error(_("Invalid Java method handle: NULL")); + cob_add_exception(COB_EC_ARGUMENT); + return; + } #if WITH_JNI if (java_api == NULL) { cob_runtime_error (_("Java interoperability module cannot be loaded: %s"), @@ -2116,10 +2121,9 @@ cob_call_java (const cob_java_handle *method_handle) { } 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"), @@ -2129,5 +2133,6 @@ cob_call_java (const cob_java_handle *method_handle) { set_json_exception (JSON_INTERNAL_ERROR); #endif cob_add_exception (COB_EC_IMP_FEATURE_DISABLED); +} #endif } diff --git a/tests/testsuite.src/run_java.at b/tests/testsuite.src/run_java.at index 5a76a239c..3b331d4a3 100644 --- a/tests/testsuite.src/run_java.at +++ b/tests/testsuite.src/run_java.at @@ -37,6 +37,9 @@ AT_DATA([prog.cob], [ PROGRAM-ID. prog. PROCEDURE DIVISION. CALL "Java.Test.printHelloWorld" + NOT ON EXCEPTION + DISPLAY "Java call worked" + END-CALL STOP RUN. ]) @@ -110,3 +113,22 @@ AT_CHECK([$COBCRUN_DIRECT ./prog], [1], [], [libcob: prog.cob:5: error: Java method 'missingMethod' with signature '()V' not found in class 'Test' ]) AT_CLEANUP + +AT_SETUP([CALL Java static void (void) (missing class with ON EXCEPTION)]) +AT_KEYWORDS([extensions jni exception]) + +AT_DATA([prog.cob], [ + IDENTIFICATION DIVISION. + PROGRAM-ID. prog. + PROCEDURE DIVISION. + CALL "Java.Test.isAMissingClass" + ON EXCEPTION + DISPLAY "java call not successful" + END-CALL + STOP RUN. +]) + +AT_CHECK([$COMPILE prog.cob], [0], [], []) +AT_CHECK([$COBCRUN_DIRECT ./prog], [0], [], [java call not successful +]) +AT_CLEANUP \ No newline at end of file From c1944d134ebffcf518596301c2b3e3d3de75aae3 Mon Sep 17 00:00:00 2001 From: Vedant Tewari Date: Sun, 15 Sep 2024 16:46:10 -0500 Subject: [PATCH 10/20] Updated documentation, fixed exception handling --- DEPENDENCIES.md | 2 +- NEWS | 4 +++- libcob/java.c | 36 +++++++++++++++++++++++++++++++++--- 3 files changed, 37 insertions(+), 5 deletions(-) diff --git a/DEPENDENCIES.md b/DEPENDENCIES.md index a94327471..db892996f 100644 --- a/DEPENDENCIES.md +++ b/DEPENDENCIES.md @@ -128,4 +128,4 @@ Support for JNI (Java Native Interface) is provided by: 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 + 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 3b2393747..1ef4ce7b7 100644 --- a/NEWS +++ b/NEWS @@ -20,7 +20,9 @@ NEWS - user visible changes -*- outline -*- * New GnuCOBOL features -** Initial support for Java interoperability through JNI (new optional dependency JDK) +** Initial support for Java interoperability through JNI (optional dependency JDK): Java Interoperability: Added initial support for calling + Java methods from COBOL programs via the Java Native Interface (JNI). This feature currently supports static Java methods with no parameters, while handling parameters and return values will be introduced in future updates. The system detects missing Java classes and malformed method names and provides proper error handling. COBOL developers can now use ON EXCEPTION and NOT ON EXCEPTION clauses with Java calls. JNI support is an optional feature that requires the Java Development Kit (JDK) to be installed on the system. Use the --with-java configure option to enable it, and ensure that the JAVA_HOME environment variable is set at runtime to locate the JDK. Without JNI, the runtime will function as usual but Java-related COBOL calls (e.g., CALL "Java..") will not be available + ** 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/libcob/java.c b/libcob/java.c index 3e9840300..af1e69b81 100644 --- a/libcob/java.c +++ b/libcob/java.c @@ -115,9 +115,39 @@ call_java (const cob_java_handle *method_handle) (*env)->CallStaticVoidMethod(env, method_handle->cls, method_handle->mid, NULL); - if ((*env)->ExceptionCheck(env)) { - (*env)->ExceptionDescribe(env); - (*env)->ExceptionClear(env); + jthrowable exception = (*env)->ExceptionOccurred(env); + if(exception) { + jclass throwable = (*env)->FindClass(env, "java/lang/Throwable"); + jmethodID getMessage = (*env)->GetMethodID(env, + throwable, "getMessage", "()Ljava/lang/String;"); + if(getMessage != NULL) { + jstring message = (jstring)(*env)->CallObjectMethod(env, exception, getMessage); + const char *messageChars = (*env)->GetStringUTFChars(env, message, NULL); + cob_runtime_error(_("Java exception: %s"), messageChars); + (*env)->ReleaseStringUTFChars(env, message, messageChars); + (*env)->DeleteLocalRef(env, message); + } + jclass stringWriter = (*env)->FindClass(env, "java/io/StringWriter"); + jclass printWriter = (*env)->FindClass(env, "java/io/PrintWriter"); + jobject stringWriterObj = (*env)->NewObject(env, + stringWriter, + (*env)->GetMethodID(env, stringWriter, "", "()V")); + jobject printWriterObj = (*env)->NewObject(env, + printWriter, + (*env)->GetMethodID(env, printWriter, "", "(Ljava/io/Writer;)V"), + stringWriterObj); + jmethodID printStackTrace = (*env)->GetMethodID(env, throwable, "printStackTrace", "(Ljava/io/PrintWriter;)V"); + (*env)->CallVoidMethod(env, exception, printStackTrace, printWriter); + jmethodID toString = (*env)->GetMethodID(env, stringWriter, "toString", "()Ljava/lang/String;"); + jstring stackTrace = (jstring)(*env)->CallObjectMethod(env, stringWriterObj, toString); + const char *stackTraceChars = (*env)->GetStringUTFChars(env, stackTrace, NULL); + cob_runtime_error(_("Java stack trace: %s"), stackTraceChars); + + (*env)->ReleaseStringUTFChars(env, stackTrace, stackTraceChars); + (*env)->DeleteLocalRef(env, stackTrace); + (*env)->DeleteLocalRef(env, stringWriterObj); + (*env)->DeleteLocalRef(env, printWriterObj); + (*env)->DeleteLocalRef(env, exception); } } From 85be0ef95dd4cf173ddd67558ea5b5c9d2ce2ced Mon Sep 17 00:00:00 2001 From: Vedant Tewari Date: Fri, 18 Oct 2024 09:07:40 -0500 Subject: [PATCH 11/20] Updated codegen, parser, and java handling. Removed m4/ax_prog_javac.m4. --- cobc/codegen.c | 15 +++++---- cobc/parser.y | 11 +------ libcob/java.c | 39 +++++++++++++--------- m4/ax_prog_javac.m4 | 79 --------------------------------------------- 4 files changed, 33 insertions(+), 111 deletions(-) delete mode 100644 m4/ax_prog_javac.m4 diff --git a/cobc/codegen.c b/cobc/codegen.c index 908847611..63d80f206 100644 --- a/cobc/codegen.c +++ b/cobc/codegen.c @@ -7125,31 +7125,32 @@ output_java_call (struct cb_call *p) char *method_name; const char *class_name; char* mangled; + size_t i; mangled = strdup(class_and_method_name); - for (size_t i = 0; i < strlen(mangled) + 1; i++) { + for (i = 0; i < strlen(mangled) + 1; i++) { mangled[i] = (mangled[i] == '.') ? '_' : mangled[i]; } + lookup_java_call(mangled); + last_dot = strrchr(class_and_method_name, '.'); *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("call_java_%s = cob_resolve_java(\"%s\", \"%s\", \"()V\");", mangled, class_name, method_name); output_line("cob_call_java(call_java_%s);\n", mangled); output_newline(); output_block_close(); output_exception_handling(p); + + cobc_free((char*) mangled); + } static void diff --git a/cobc/parser.y b/cobc/parser.y index 9aec12614..c62d02f95 100644 --- a/cobc/parser.y +++ b/cobc/parser.y @@ -12254,17 +12254,8 @@ 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) { + if (strncasecmp("Java.", (char *)CB_LITERAL ($3)->data, 5) == 0) { call_conv = CB_CONV_JAVA; - /* Check for malformed Java method names */ - if (strchr(s + 5, '.') == NULL) { - cb_error_x ($3, _("malformed Java method name '%s', expected format 'Java.ClassName.methodName'"), s); - } - /* Check for unsupported Java method calls with parameters or return values */ - if($7 != NULL || $8 != NULL) { - CB_PENDING("Java method calls with parameters or return values"); - } } cb_check_conformance ($3, $7, $8); } else if (CB_REFERENCE_P ($3)) { diff --git a/libcob/java.c b/libcob/java.c index af1e69b81..893c97711 100644 --- a/libcob/java.c +++ b/libcob/java.c @@ -43,18 +43,24 @@ jvm_load (void) { /* JDK/JRE 6 VM initialization arguments */ JavaVMInitArgs args; JavaVMOption* options; - args.version = JNI_VERSION_1_6; - const char *classpath = getenv("CLASSPATH"); + const char *classpath; + size_t option_len; + char option_buffer[1024]; + + args.version = JNI_VERSION_1_6; + 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); + option_len = strlen("-Djava.class.path=") + strlen(classpath) + 1; + if (option_len > sizeof(option_buffer)) { + return -1; + } + strcpy(option_buffer, "-Djava.class.path="); + strcat(option_buffer, classpath); + options[0].optionString = option_buffer; args.options = options; args.ignoreUnrecognized = 1; /* loading and initializing a Java VM, returning as JNI interface */ @@ -71,17 +77,18 @@ resolve_java (const char *class_name, cob_java_handle *handle; char *jni_class_name = strdup(class_name); - for (char *p = jni_class_name; *p; ++p) { - if (*p == '.') { - *p = '_'; - } - } + for (char *p = jni_class_name; *p; ++p) { + if (*p == '.') { + *p = '/'; + } + } cls = (*env)->FindClass(env, jni_class_name); cob_free(jni_class_name); if (!cls) { cob_runtime_error(_("Java class '%s' not found"), class_name); - cob_hard_failure (); + cob_add_exception(COB_EC_FUNCTION_NOT_FOUND); + return NULL; } mid = (*env)->GetStaticMethodID(env, cls, method_name, method_signature); @@ -89,14 +96,16 @@ resolve_java (const char *class_name, cob_runtime_error(_("Java method '%s' with signature '%s' not found in class '%s'"), method_name, method_signature, class_name); (*env)->DeleteLocalRef(env, cls); - cob_hard_failure (); + cob_add_exception(COB_EC_OO_METHOD); + return NULL; } handle = (cob_java_handle*)cob_malloc(sizeof(cob_java_handle)); if (!handle) { cob_runtime_error(_("Memory allocation failed for Java method handle")); (*env)->DeleteLocalRef(env, cls); - cob_hard_failure (); + cob_add_exception(COB_EC_STORAGE_NOT_AVAIL); + return NULL; } handle->cls = (*env)->NewGlobalRef(env, cls); diff --git a/m4/ax_prog_javac.m4 b/m4/ax_prog_javac.m4 deleted file mode 100644 index 8abb733fc..000000000 --- a/m4/ax_prog_javac.m4 +++ /dev/null @@ -1,79 +0,0 @@ -# =========================================================================== -# 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 -]) From 694669bdb18531bef9c2858d6c16ff79a2efe1a0 Mon Sep 17 00:00:00 2001 From: Vedant Tewari Date: Tue, 29 Oct 2024 08:46:04 -0500 Subject: [PATCH 12/20] Removed '_' checks and added test for package --- cobc/codegen.c | 65 ++++++++++++++++++--------------- libcob/java.c | 8 +--- tests/testsuite.src/run_java.at | 35 ++++++++++++++++++ 3 files changed, 72 insertions(+), 36 deletions(-) diff --git a/cobc/codegen.c b/cobc/codegen.c index 63d80f206..d3e6f36aa 100644 --- a/cobc/codegen.c +++ b/cobc/codegen.c @@ -7113,44 +7113,51 @@ output_exception_handling(struct cb_call *p) } static void -output_java_call (struct cb_call *p) +output_java_call(struct cb_call *p) { - if (p->args != NULL || p->call_returning != NULL) { - CB_PENDING ("Java method call with parameters or return values"); - COBC_ABORT (); - } - 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; - size_t i; + if (p->args != NULL || p->call_returning != NULL) { + CB_PENDING("Java method call with parameters or return values"); + COBC_ABORT(); + } - mangled = strdup(class_and_method_name); - for (i = 0; i < strlen(mangled) + 1; i++) { - mangled[i] = (mangled[i] == '.') ? '_' : mangled[i]; - } + 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; + + // Directly duplicate the class_and_method_name + mangled = strdup(class_and_method_name); + if (!mangled) { + cobc_err_msg(_("Memory allocation failed for mangled name")); + COBC_ABORT(); + } - lookup_java_call(mangled); + lookup_java_call(mangled); - last_dot = strrchr(class_and_method_name, '.'); + last_dot = strrchr(class_and_method_name, '.'); - *last_dot = '\0'; - method_name = last_dot + 1; - class_name = class_and_method_name; + if (last_dot == NULL) { + cobc_err_msg(_("malformed call '%s' to a Java method"), class_and_method_name); + cobc_free(mangled); + return; + } - output_line("if (call_java_%s == NULL)", mangled); - output_block_open(); + *last_dot = '\0'; + method_name = last_dot + 1; + class_name = class_and_method_name; - output_line("call_java_%s = cob_resolve_java(\"%s\", \"%s\", \"()V\");", mangled, class_name, method_name); - output_line("cob_call_java(call_java_%s);\n", mangled); - output_newline(); - output_block_close(); - output_exception_handling(p); + output_line("if (call_java_%s == NULL)", mangled); + output_block_open(); - cobc_free((char*) mangled); + output_line("call_java_%s = cob_resolve_java(\"%s\", \"%s\", \"()V\");", mangled, class_name, method_name); + output_line("cob_call_java(call_java_%s);\n", mangled); + output_newline(); + output_block_close(); + output_exception_handling(p); + cobc_free(mangled); } static void diff --git a/libcob/java.c b/libcob/java.c index 893c97711..e414ebef4 100644 --- a/libcob/java.c +++ b/libcob/java.c @@ -45,7 +45,7 @@ jvm_load (void) { JavaVMOption* options; const char *classpath; size_t option_len; - char option_buffer[1024]; + char option_buffer[COB_NORMAL_BUFF]; args.version = JNI_VERSION_1_6; char *classpath = getenv("CLASSPATH"); @@ -77,12 +77,6 @@ resolve_java (const char *class_name, 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); cob_free(jni_class_name); if (!cls) { diff --git a/tests/testsuite.src/run_java.at b/tests/testsuite.src/run_java.at index 3b331d4a3..8c9a76031 100644 --- a/tests/testsuite.src/run_java.at +++ b/tests/testsuite.src/run_java.at @@ -131,4 +131,39 @@ AT_DATA([prog.cob], [ AT_CHECK([$COMPILE prog.cob], [0], [], []) AT_CHECK([$COBCRUN_DIRECT ./prog], [0], [], [java call not successful ]) +AT_CLEANUP + +AT_SETUP([CALL Java static void (void) in a package]) +AT_KEYWORDS([extensions jni package]) + +AT_SKIP_IF([test "$COB_HAS_JNI" = "no"]) + +AT_DATA([testpackage/Test.java], [ +package testpackage; +public class Test { + public static void printHelloPackage () { + System.out.println("Hello from package!"); + } +} +]) + +AT_DATA([prog.cob], [ + IDENTIFICATION DIVISION. + PROGRAM-ID. prog. + PROCEDURE DIVISION. + CALL "Java.testpackage.Test.printHelloPackage" + NOT ON EXCEPTION + DISPLAY "Java call to package class worked" + END-CALL + STOP RUN. +]) + +AT_CHECK([mkdir -p testpackage]) +AT_CHECK([$JAVAC testpackage/Test.java], [0], [], []) +AT_CHECK([$COMPILE prog.cob], [0], [], []) +AT_CHECK([$COBCRUN_DIRECT ./prog], [0], +[Hello from package! +Java call to package class worked +]) + AT_CLEANUP \ No newline at end of file From 86b62c36d4596ffb9f9d6fe945f992c72b57e3fb Mon Sep 17 00:00:00 2001 From: Vedant Tewari Date: Tue, 12 Nov 2024 08:20:29 -0600 Subject: [PATCH 13/20] Updated output_java_call and cb_check_conformance --- cobc/codegen.c | 12 +----------- cobc/typeck.c | 10 ++++++++++ 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/cobc/codegen.c b/cobc/codegen.c index d3e6f36aa..a82a73128 100644 --- a/cobc/codegen.c +++ b/cobc/codegen.c @@ -7122,7 +7122,6 @@ 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; @@ -7136,14 +7135,7 @@ output_java_call(struct cb_call *p) lookup_java_call(mangled); - 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_free(mangled); - return; - } - + char* last_dot = strrchr(class_and_method_name, '.'); *last_dot = '\0'; method_name = last_dot + 1; class_name = class_and_method_name; @@ -7156,8 +7148,6 @@ output_java_call(struct cb_call *p) output_newline(); output_block_close(); output_exception_handling(p); - - cobc_free(mangled); } static void diff --git a/cobc/typeck.c b/cobc/typeck.c index d147d211a..44d6ce134 100644 --- a/cobc/typeck.c +++ b/cobc/typeck.c @@ -3943,6 +3943,16 @@ cb_check_conformance (cb_tree prog_ref, cb_tree using_list, return; } + if (CB_LITERAL_P(prog_ref)) { + char *full_name = (char *)CB_LITERAL(prog_ref)->data; + char *class_and_method_name = full_name + 5; + char *last_dot = strrchr(class_and_method_name, '.'); + if (last_dot == NULL) { + cobc_err_msg(_("Malformed Java method name '%s'"), class_and_method_name); + return; + } + } + /* Check each parameter is conformant: has right type, has right REFERENCE/VALUE phrase, has right length, etc. From 708c0e49d8d1ca4e84c6513e02e26b35f7fd3830 Mon Sep 17 00:00:00 2001 From: Nicolas Berthier Date: Tue, 5 Nov 2024 15:42:37 +0100 Subject: [PATCH 14/20] Reintroduce `m4/ax_prog.javac.m4` --- m4/ax_prog.javac.m4 | 79 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 m4/ax_prog.javac.m4 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 +]) From 9e9c908ee769549024eda37595d5ed0f5324481b Mon Sep 17 00:00:00 2001 From: Nicolas Berthier Date: Tue, 5 Nov 2024 16:02:31 +0100 Subject: [PATCH 15/20] Fix and reformat in `libcob/java.c` --- libcob/coblocal.h | 2 + libcob/java.c | 105 +++++++++++++++++++++++----------------------- 2 files changed, 54 insertions(+), 53 deletions(-) diff --git a/libcob/coblocal.h b/libcob/coblocal.h index 71be6fcd8..d3f5351d8 100644 --- a/libcob/coblocal.h +++ b/libcob/coblocal.h @@ -480,6 +480,8 @@ COB_HIDDEN void cob_runtime_warning_ss (const char *, const char *); COB_EXPIMP int cob_ncase_cmp (char *, const char *, unsigned ); COB_EXPIMP char * cob_str_case_str (char *, const char *); +COB_EXPIMP int cob_jni_init (cob_java_api *api); + /* static inline of smaller helpers */ static COB_INLINE int diff --git a/libcob/java.c b/libcob/java.c index e414ebef4..34e70101a 100644 --- a/libcob/java.c +++ b/libcob/java.c @@ -1,21 +1,21 @@ /* - Copyright (C) 2024 Free Software Foundation, Inc. - Written by Vedant Tewari, Nicolas Berthier, + Copyright (C) 2024 Free Software Foundation, Inc. + Written by Vedant Tewari, Nicolas Berthier, - This file is part of GnuCOBOL. + 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. + 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. + 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 . + You should have received a copy of the GNU Lesser General Public License + along with GnuCOBOL. If not, see . */ #include @@ -40,31 +40,30 @@ typedef struct __cob_java_static_method { static int /* non-zero means there's an error */ jvm_load (void) { - /* JDK/JRE 6 VM initialization arguments */ - JavaVMInitArgs args; - JavaVMOption* options; + /* JDK/JRE 6 VM initialization arguments */ + JavaVMInitArgs args; + JavaVMOption* options = { 0, }; const char *classpath; size_t option_len; char option_buffer[COB_NORMAL_BUFF]; args.version = JNI_VERSION_1_6; - char *classpath = getenv("CLASSPATH"); - if (classpath == NULL) { - classpath = ""; - } - /* inline */ - args.nOptions = 1; - option_len = strlen("-Djava.class.path=") + strlen(classpath) + 1; - if (option_len > sizeof(option_buffer)) { - return -1; - } + classpath = getenv ("CLASSPATH"); + if (classpath == NULL) { + classpath = ""; + } + args.nOptions = 1; + option_len = strlen("-Djava.class.path=") + strlen(classpath) + 1; + if (option_len > sizeof(option_buffer)) { + return -1; + } strcpy(option_buffer, "-Djava.class.path="); - strcat(option_buffer, classpath); + strcat(option_buffer, classpath); options[0].optionString = option_buffer; - args.options = options; - args.ignoreUnrecognized = 1; - /* loading and initializing a Java VM, returning as JNI interface */ - return JNI_CreateJavaVM(&jvm, (void**)&env, &args); + args.options = options; + args.ignoreUnrecognized = 1; + /* loading and initializing a Java VM, returning as JNI interface */ + return JNI_CreateJavaVM(&jvm, (void**)&env, &args); } static @@ -80,26 +79,26 @@ resolve_java (const char *class_name, cls = (*env)->FindClass(env, jni_class_name); cob_free(jni_class_name); if (!cls) { - cob_runtime_error(_("Java class '%s' not found"), class_name); - cob_add_exception(COB_EC_FUNCTION_NOT_FOUND); - return NULL; + cob_runtime_error (_("Java class '%s' not found"), class_name); + cob_set_exception (COB_EC_FUNCTION_NOT_FOUND); + return NULL; } mid = (*env)->GetStaticMethodID(env, cls, method_name, method_signature); if (!mid) { - cob_runtime_error(_("Java method '%s' with signature '%s' not found in class '%s'"), - method_name, method_signature, class_name); + cob_runtime_error (_("Java method '%s' with signature '%s' not found in class '%s'"), + method_name, method_signature, class_name); (*env)->DeleteLocalRef(env, cls); - cob_add_exception(COB_EC_OO_METHOD); - return NULL; + cob_set_exception (COB_EC_OO_METHOD); + return NULL; } handle = (cob_java_handle*)cob_malloc(sizeof(cob_java_handle)); if (!handle) { - cob_runtime_error(_("Memory allocation failed for Java method handle")); - (*env)->DeleteLocalRef(env, cls); - cob_add_exception(COB_EC_STORAGE_NOT_AVAIL); - return NULL; + cob_runtime_error (_("Memory allocation failed for Java method handle")); + (*env)->DeleteLocalRef (env, cls); + cob_set_exception (COB_EC_STORAGE_NOT_AVAIL); + return NULL; } handle->cls = (*env)->NewGlobalRef(env, cls); @@ -121,10 +120,10 @@ call_java (const cob_java_handle *method_handle) jthrowable exception = (*env)->ExceptionOccurred(env); if(exception) { jclass throwable = (*env)->FindClass(env, "java/lang/Throwable"); - jmethodID getMessage = (*env)->GetMethodID(env, - throwable, "getMessage", "()Ljava/lang/String;"); + jmethodID getMessage = (*env)->GetMethodID(env, + throwable, "getMessage", "()Ljava/lang/String;"); if(getMessage != NULL) { - jstring message = (jstring)(*env)->CallObjectMethod(env, exception, getMessage); + jstring message = (jstring)(*env)->CallObjectMethod(env, exception, getMessage); const char *messageChars = (*env)->GetStringUTFChars(env, message, NULL); cob_runtime_error(_("Java exception: %s"), messageChars); (*env)->ReleaseStringUTFChars(env, message, messageChars); @@ -132,20 +131,20 @@ call_java (const cob_java_handle *method_handle) } jclass stringWriter = (*env)->FindClass(env, "java/io/StringWriter"); jclass printWriter = (*env)->FindClass(env, "java/io/PrintWriter"); - jobject stringWriterObj = (*env)->NewObject(env, - stringWriter, - (*env)->GetMethodID(env, stringWriter, "", "()V")); - jobject printWriterObj = (*env)->NewObject(env, - printWriter, - (*env)->GetMethodID(env, printWriter, "", "(Ljava/io/Writer;)V"), - stringWriterObj); + jobject stringWriterObj = (*env)->NewObject(env, + stringWriter, + (*env)->GetMethodID(env, stringWriter, "", "()V")); + jobject printWriterObj = (*env)->NewObject(env, + printWriter, + (*env)->GetMethodID(env, printWriter, "", "(Ljava/io/Writer;)V"), + stringWriterObj); jmethodID printStackTrace = (*env)->GetMethodID(env, throwable, "printStackTrace", "(Ljava/io/PrintWriter;)V"); (*env)->CallVoidMethod(env, exception, printStackTrace, printWriter); jmethodID toString = (*env)->GetMethodID(env, stringWriter, "toString", "()Ljava/lang/String;"); jstring stackTrace = (jstring)(*env)->CallObjectMethod(env, stringWriterObj, toString); const char *stackTraceChars = (*env)->GetStringUTFChars(env, stackTrace, NULL); cob_runtime_error(_("Java stack trace: %s"), stackTraceChars); - + (*env)->ReleaseStringUTFChars(env, stackTrace, stackTraceChars); (*env)->DeleteLocalRef(env, stackTrace); (*env)->DeleteLocalRef(env, stringWriterObj); From 8b142f5a11524b19780f20a5505d97ddd290006f Mon Sep 17 00:00:00 2001 From: Nicolas Berthier Date: Tue, 5 Nov 2024 17:23:02 +0100 Subject: [PATCH 16/20] Quick fix in code generation --- cobc/codegen.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/cobc/codegen.c b/cobc/codegen.c index a82a73128..172de0422 100644 --- a/cobc/codegen.c +++ b/cobc/codegen.c @@ -7100,7 +7100,7 @@ output_exception_handling(struct cb_call *p) output_line("cob_glob_ptr->cob_stmt_exception = 0;"); } - output_line("if ((cob_glob_ptr->cob_exception_code & 0xff00) != 0) {"); + output_line("if ((cob_glob_ptr->cob_exception_code & 0xff00) != 0) "); output_block_open(); if (p->stmt1) { @@ -7133,6 +7133,8 @@ output_java_call(struct cb_call *p) COBC_ABORT(); } + last_dot = strrchr (mangled, '.'); + *last_dot = '_'; lookup_java_call(mangled); char* last_dot = strrchr(class_and_method_name, '.'); @@ -7143,9 +7145,9 @@ output_java_call(struct cb_call *p) output_line("if (call_java_%s == NULL)", mangled); output_block_open(); - output_line("call_java_%s = cob_resolve_java(\"%s\", \"%s\", \"()V\");", mangled, class_name, method_name); - output_line("cob_call_java(call_java_%s);\n", mangled); - output_newline(); + output_line("call_java_%s = cob_resolve_java(\"%s\", \"%s\", \"()V\");", + mangled, class_name, method_name); + output_line("cob_call_java(call_java_%s);", mangled); output_block_close(); output_exception_handling(p); } From 56aac26a574830c90e9a88544321c9ec83df3e52 Mon Sep 17 00:00:00 2001 From: Nicolas Berthier Date: Wed, 6 Nov 2024 11:44:36 +0100 Subject: [PATCH 17/20] Try and fix loading of `libjvm` for older JREs, and JNI code --- cobc/codegen.c | 87 +++++++++++++++------------------ cobc/typeck.c | 10 ++-- configure.ac | 24 ++++++--- libcob/call.c | 14 ++++-- libcob/coblocal.h | 2 - libcob/java.c | 39 +++++++-------- tests/testsuite.src/run_java.at | 27 +++------- tests/testsuite.src/syn_misc.at | 16 ++++++ 8 files changed, 114 insertions(+), 105 deletions(-) diff --git a/cobc/codegen.c b/cobc/codegen.c index 172de0422..0ab75ecee 100644 --- a/cobc/codegen.c +++ b/cobc/codegen.c @@ -397,7 +397,7 @@ lookup_source (const char *p) } static void -lookup_java_call(const char *p) +lookup_java_call (const char *p) { struct call_list *clp; @@ -407,7 +407,7 @@ lookup_java_call(const char *p) } } clp = cobc_parse_malloc (sizeof (struct call_list)); - clp->call_name = p; + clp->call_name = cobc_parse_strdup (p); clp->next = call_java_cache; call_java_cache = clp; } @@ -7091,65 +7091,56 @@ output_field_constant (cb_tree x, int n, const char *flagname) } static void -output_exception_handling(struct cb_call *p) +output_exception_handling (struct cb_call *p) { - if (p->stmt1) { - output_line("cob_glob_ptr->cob_stmt_exception = 1;"); - output_line("COB_RESET_EXCEPTION(0);"); - } else { - output_line("cob_glob_ptr->cob_stmt_exception = 0;"); - } + if (p->stmt1) { + output_line ("cob_glob_ptr->cob_stmt_exception = 1;"); + output_line ("COB_RESET_EXCEPTION(0);"); + } else { + output_line ("cob_glob_ptr->cob_stmt_exception = 0;"); + } - output_line("if ((cob_glob_ptr->cob_exception_code & 0xff00) != 0) "); - output_block_open(); + output_line ("if ((cob_glob_ptr->cob_exception_code & 0xff00) != 0) "); + output_block_open (); - if (p->stmt1) { - output_stmt(p->stmt1); - } else if (p->stmt2) { - output_stmt(p->stmt2); + if (p->stmt1) { + output_stmt (p->stmt1); + } else if (p->stmt2) { + output_stmt (p->stmt2); } - output_block_close(); - output_line("COB_RESET_EXCEPTION(0);"); + output_block_close (); + output_line ("COB_RESET_EXCEPTION(0);"); } static void -output_java_call(struct cb_call *p) +output_java_call (struct cb_call *p) { - if (p->args != NULL || p->call_returning != NULL) { - CB_PENDING("Java method call with parameters or return values"); - COBC_ABORT(); - } - - 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 *method_name; - const char *class_name; - char* mangled; + char *class_and_method_name, *last_dot, *c; + const char *class_name, *method_name; + char mangled[COB_NORMAL_BUFF]; - // Directly duplicate the class_and_method_name - mangled = strdup(class_and_method_name); - if (!mangled) { - cobc_err_msg(_("Memory allocation failed for mangled name")); - COBC_ABORT(); - } + /* Assume "Java." prefix (enforced in `parser.y`, rule `call_body`) */ + class_and_method_name = (char*)CB_LITERAL(p->name)->data + 5; - last_dot = strrchr (mangled, '.'); - *last_dot = '_'; - lookup_java_call(mangled); + strncpy (mangled, class_and_method_name, COB_NORMAL_MAX); + for (c = mangled; *c; c++) { + if (*c == '.') *c = '_'; + } + lookup_java_call (mangled); - char* last_dot = strrchr(class_and_method_name, '.'); - *last_dot = '\0'; - method_name = last_dot + 1; - class_name = class_and_method_name; + last_dot = strrchr (class_and_method_name, '.'); + *last_dot = '\0'; + method_name = last_dot + 1; + class_name = class_and_method_name; - output_line("if (call_java_%s == NULL)", mangled); - output_block_open(); + output_line ("if (call_java_%s == NULL)", mangled); + output_block_open (); + output_line ("call_java_%s = cob_resolve_java (\"%s\", \"%s\", \"()V\");", + mangled, class_name, method_name); + output_line ("cob_call_java (call_java_%s);", mangled); + output_block_close (); - output_line("call_java_%s = cob_resolve_java(\"%s\", \"%s\", \"()V\");", - mangled, class_name, method_name); - output_line("cob_call_java(call_java_%s);", mangled); - output_block_close(); - output_exception_handling(p); + output_exception_handling (p); } static void diff --git a/cobc/typeck.c b/cobc/typeck.c index 44d6ce134..abd44b795 100644 --- a/cobc/typeck.c +++ b/cobc/typeck.c @@ -3921,7 +3921,7 @@ check_argument_conformance (struct cb_program *program, cb_tree argument_tripple void cb_check_conformance (cb_tree prog_ref, cb_tree using_list, - cb_tree returning) + cb_tree returning) { struct cb_program *program = NULL; cb_tree l; @@ -3950,8 +3950,12 @@ cb_check_conformance (cb_tree prog_ref, cb_tree using_list, if (last_dot == NULL) { cobc_err_msg(_("Malformed Java method name '%s'"), class_and_method_name); return; - } - } + } + if (using_list != NULL || returning != NULL) { + CB_PENDING ("Java method call with parameters or return values"); + COBC_ABORT (); + } + } /* Check each parameter is conformant: has right type, has right diff --git a/configure.ac b/configure.ac index 8191a5e96..190d5e8d2 100644 --- a/configure.ac +++ b/configure.ac @@ -152,6 +152,8 @@ dnl done via AC_CHECK_FUNCS: AH_TEMPLATE([HAVE_RAISE], [Has raise function]) AH_TEMPLATE([HAVE_FINITE_IEEEFP_H], [Declaration of finite function in ieeefp.h instead of math.h]) +AH_TEMPLATE([COB_JAVA_ARCH], [Java platform architecture]) + dnl preparation for cross-compilation AC_ARG_PROGRAM @@ -951,6 +953,11 @@ AS_IF([test "x$with_java" != "xno"], [ AC_MSG_NOTICE([Given Java home: ${JAVA_HOME}]) fi + dnl Note: One more hack needed to properly locate libjvm with + dnl early versions of openjdk (1.8 at least). + JAVA_ARCH="$( $JAVA -XshowSettings:properties -version 2>&1 >/dev/null | \ + $SED -e '/^[ ]*os.arch/!d' -e 's/.*=[ ]*//' )" + 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. @@ -962,15 +969,17 @@ AS_IF([test "x$with_java" != "xno"], [ 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 + for _dir in "${JAVA_HOME}/jre/lib" \ + "${JAVA_HOME}/jre/lib/${JAVA_ARCH}" \ + "${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 - if test -d "$_dir/client"; then - JNI_LIBS="$JNI_LIBS -L$_dir/client" + 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 fi done curr_LIBS="$LIBS" @@ -988,6 +997,7 @@ AS_IF([test "x$with_java" != "xno"], [ ], [ AC_MSG_RESULT([yes]) AC_DEFINE([WITH_JNI], [1]) + AC_DEFINE_UNQUOTED([COB_JAVA_ARCH], ["$JAVA_ARCH"]) JNI_LDFLAGS="$JNI_LIBS" JNI_LIBS="-ljvm" cob_has_jni=yes diff --git a/libcob/call.c b/libcob/call.c index 9b3dc391e..f38e24339 100644 --- a/libcob/call.c +++ b/libcob/call.c @@ -129,7 +129,6 @@ lt_dlerror (void) static lt_dlhandle jvm_handle = NULL; #else /* Using libltdl, no need to preload. */ -# define JVM_PRELOAD 0 #endif #include "sysdefines.h" @@ -1855,7 +1854,7 @@ cob_exit_call (void) } base_dynload_ptr = NULL; -#if JVM_PRELOAD +#ifdef JVM_PRELOAD if (jvm_handle) { lt_dlclose (jvm_handle); jvm_handle = NULL; @@ -1993,6 +1992,8 @@ cob_init_call (cob_global *lptr, cob_settings* sptr, const int check_mainhandle) /* Java API handling */ +#ifdef WITH_JNI + /* "Standard" path suffixes to the dynamically loadable JVM library, from "typical" JAVA_HOME. */ const char* const path_to_jvm[] = { @@ -2003,7 +2004,11 @@ const char* const path_to_jvm[] = { #else # define JVM_FILE "libjvm." COB_MODULE_EXT "/lib/server", + "/jre/lib/server", + "/jre/lib/" COB_JAVA_ARCH "/server", "/lib/client", + "/jre/lib/client", + "/jre/lib/" COB_JAVA_ARCH "/client", #endif NULL, }; @@ -2040,13 +2045,14 @@ init_jvm_search_dirs (void) { break; #else /* Append to search path. */ + int success; # 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); + success = lt_dladdsearchdir (jvm_path); DEBUG_LOG ("call", ("%s\n", success == 0 ? "success" : "failed")); #endif } @@ -2092,6 +2098,8 @@ cob_init_java (void) { return 0; } +#endif /* WITH_JNI */ + cob_java_handle* cob_resolve_java (const char *class_name, const char *method_name, diff --git a/libcob/coblocal.h b/libcob/coblocal.h index d3f5351d8..71be6fcd8 100644 --- a/libcob/coblocal.h +++ b/libcob/coblocal.h @@ -480,8 +480,6 @@ COB_HIDDEN void cob_runtime_warning_ss (const char *, const char *); COB_EXPIMP int cob_ncase_cmp (char *, const char *, unsigned ); COB_EXPIMP char * cob_str_case_str (char *, const char *); -COB_EXPIMP int cob_jni_init (cob_java_api *api); - /* static inline of smaller helpers */ static COB_INLINE int diff --git a/libcob/java.c b/libcob/java.c index 34e70101a..026d440cd 100644 --- a/libcob/java.c +++ b/libcob/java.c @@ -25,8 +25,8 @@ /* Force symbol exports */ #define COB_LIB_EXPIMP +#include "config.h" #include "common.h" -#include "libcob.h" #include "coblocal.h" /* Declarations */ @@ -38,32 +38,29 @@ typedef struct __cob_java_static_method { jmethodID mid; } cob_java_handle; +/* Only exported symbol: */ +int cob_jni_init (cob_java_api *api); + static int /* non-zero means there's an error */ jvm_load (void) { /* JDK/JRE 6 VM initialization arguments */ - JavaVMInitArgs args; - JavaVMOption* options = { 0, }; - const char *classpath; - size_t option_len; - char option_buffer[COB_NORMAL_BUFF]; + JavaVMInitArgs args; + JavaVMOption options[1]; + const char *classpath; + char cp_buffer[COB_MEDIUM_BUFF]; args.version = JNI_VERSION_1_6; - classpath = getenv ("CLASSPATH"); - if (classpath == NULL) { - classpath = ""; - } - args.nOptions = 1; - option_len = strlen("-Djava.class.path=") + strlen(classpath) + 1; - if (option_len > sizeof(option_buffer)) { - return -1; - } - strcpy(option_buffer, "-Djava.class.path="); - strcat(option_buffer, classpath); - options[0].optionString = option_buffer; args.options = options; - args.ignoreUnrecognized = 1; - /* loading and initializing a Java VM, returning as JNI interface */ - return JNI_CreateJavaVM(&jvm, (void**)&env, &args); + args.nOptions = 0; + args.ignoreUnrecognized = JNI_FALSE; + + if ((classpath = getenv ("CLASSPATH")) != NULL) { + snprintf (cp_buffer, COB_MEDIUM_MAX, + "-Djava.class.path=%s", classpath); + options[args.nOptions++].optionString = cp_buffer; + } + + return JNI_CreateJavaVM (&jvm, (void**)&env, &args); } static diff --git a/tests/testsuite.src/run_java.at b/tests/testsuite.src/run_java.at index 8c9a76031..194382550 100644 --- a/tests/testsuite.src/run_java.at +++ b/tests/testsuite.src/run_java.at @@ -32,6 +32,7 @@ public class Test { } } ]) + AT_DATA([prog.cob], [ IDENTIFICATION DIVISION. PROGRAM-ID. prog. @@ -47,25 +48,7 @@ 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 with malformed method name]) -AT_KEYWORDS([extensions jni malformed]) - -AT_SKIP_IF([test "$COB_HAS_JNI" = "no"]) - -AT_DATA([prog.cob], [ - IDENTIFICATION DIVISION. - PROGRAM-ID. prog. - PROCEDURE DIVISION. - CALL "Java.InvalidName" - STOP RUN. -]) - -AT_CHECK([$COMPILE prog.cob], [1], [], -[prog.cob:5: error: malformed Java method name 'Java.InvalidName', expected format 'Java.ClassName.methodName' +Java call worked ]) AT_CLEANUP @@ -114,6 +97,7 @@ AT_CHECK([$COBCRUN_DIRECT ./prog], [1], [], ]) AT_CLEANUP + AT_SETUP([CALL Java static void (void) (missing class with ON EXCEPTION)]) AT_KEYWORDS([extensions jni exception]) @@ -133,11 +117,13 @@ AT_CHECK([$COBCRUN_DIRECT ./prog], [0], [], [java call not successful ]) AT_CLEANUP + AT_SETUP([CALL Java static void (void) in a package]) AT_KEYWORDS([extensions jni package]) AT_SKIP_IF([test "$COB_HAS_JNI" = "no"]) +AT_CHECK([mkdir -p testpackage]) AT_DATA([testpackage/Test.java], [ package testpackage; public class Test { @@ -158,7 +144,6 @@ AT_DATA([prog.cob], [ STOP RUN. ]) -AT_CHECK([mkdir -p testpackage]) AT_CHECK([$JAVAC testpackage/Test.java], [0], [], []) AT_CHECK([$COMPILE prog.cob], [0], [], []) AT_CHECK([$COBCRUN_DIRECT ./prog], [0], @@ -166,4 +151,4 @@ AT_CHECK([$COBCRUN_DIRECT ./prog], [0], Java call to package class worked ]) -AT_CLEANUP \ No newline at end of file +AT_CLEANUP diff --git a/tests/testsuite.src/syn_misc.at b/tests/testsuite.src/syn_misc.at index 7ab38951d..60c9db68b 100644 --- a/tests/testsuite.src/syn_misc.at +++ b/tests/testsuite.src/syn_misc.at @@ -9531,3 +9531,19 @@ pgm1.cob:18: error: start of statement in Area A AT_CLEANUP + +AT_SETUP([CALL Java with malformed method name]) +AT_KEYWORDS([extensions jni malformed]) + +AT_DATA([prog.cob], [ + IDENTIFICATION DIVISION. + PROGRAM-ID. prog. + PROCEDURE DIVISION. + CALL "Java.InvalidName" + STOP RUN. +]) + +AT_CHECK([$COMPILE prog.cob], [1], [], +[prog.cob:5: error: malformed Java method name 'Java.InvalidName', expected format 'Java.ClassName.methodName' +]) +AT_CLEANUP From 35e8c3b608b91ac63f438f709d4ea1c22973aca4 Mon Sep 17 00:00:00 2001 From: Nicolas Berthier Date: Wed, 6 Nov 2024 12:49:30 +0100 Subject: [PATCH 18/20] Fix reporting of failures to load Java interoperability module --- libcob/call.c | 46 ++++++++++++++++++++++++---------------------- 1 file changed, 24 insertions(+), 22 deletions(-) diff --git a/libcob/call.c b/libcob/call.c index f38e24339..040a7beb9 100644 --- a/libcob/call.c +++ b/libcob/call.c @@ -2074,7 +2074,7 @@ cob_init_java (void) { java_api = cob_malloc (sizeof (cob_java_api)); if (java_api == NULL) { - return 1; + goto error; } module_errmsg[0] = 0; @@ -2092,10 +2092,15 @@ cob_init_java (void) { should follow. */ cob_free (java_api); java_api = NULL; - return 1; + goto error; } jinit (java_api); return 0; + + error: + cob_runtime_error (_("Java interoperability module cannot be loaded: %s"), + module_errmsg); + return 1; } #endif /* WITH_JNI */ @@ -2116,31 +2121,28 @@ cob_resolve_java (const char *class_name, void cob_call_java (const cob_java_handle *method_handle) { - if (method_handle == NULL) { - cob_runtime_error(_("Invalid Java method handle: NULL")); - cob_add_exception(COB_EC_ARGUMENT); - return; - } + if (method_handle == NULL) { + cob_runtime_error (_("Invalid Java method handle: NULL")); + cob_add_exception (COB_EC_ARGUMENT); + return; + } #if WITH_JNI - if (java_api == NULL) { - cob_runtime_error (_("Java interoperability module cannot be loaded: %s"), - module_errmsg); + if (java_api == NULL && cob_init_java ()) { return; } - return java_api->cob_call (method_handle); + java_api->cob_call (method_handle); #else -{ - static int first_java = 1; - - if (first_java) { - first_java = 0; - cob_runtime_warning (_("runtime is not configured to support %s"), - "JNI"); - } + { + static int first_java = 1; + 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); + set_json_exception (JSON_INTERNAL_ERROR); #endif - cob_add_exception (COB_EC_IMP_FEATURE_DISABLED); -} + cob_add_exception (COB_EC_IMP_FEATURE_DISABLED); + } #endif } From caf9f31f81c6a53021f9a798200ff55507e7d71e Mon Sep 17 00:00:00 2001 From: Nicolas Berthier Date: Tue, 12 Nov 2024 16:53:02 +0100 Subject: [PATCH 19/20] Fix check in `cb_check_conformance` --- cobc/ChangeLog | 2 ++ cobc/parser.y | 4 ++-- cobc/tree.h | 2 +- cobc/typeck.c | 34 +++++++++++++++++++--------------- 4 files changed, 24 insertions(+), 18 deletions(-) diff --git a/cobc/ChangeLog b/cobc/ChangeLog index 651f43bfe..cfd8c7967 100644 --- a/cobc/ChangeLog +++ b/cobc/ChangeLog @@ -2,6 +2,8 @@ 2024-08-14 Nicolas Berthier * cobc.c (cobc_print_info): added note for Java interoperability + * typeck.c, tree.h (cb_check_conformance): pass call convention to deal + with calls of Java methods 2024-08-04 David Declerck diff --git a/cobc/parser.y b/cobc/parser.y index c62d02f95..4153e47bd 100644 --- a/cobc/parser.y +++ b/cobc/parser.y @@ -12257,13 +12257,13 @@ call_body: if (strncasecmp("Java.", (char *)CB_LITERAL ($3)->data, 5) == 0) { call_conv = CB_CONV_JAVA; } - cb_check_conformance ($3, $7, $8); + cb_check_conformance ($3, $7, $8, call_conv); } else if (CB_REFERENCE_P ($3)) { cb_tree ref = cb_ref ($3); if ((CB_FIELD_P (ref) && CB_FIELD (ref)->flag_item_78) || CB_PROGRAM_P (ref) || CB_PROTOTYPE_P (ref)) { - cb_check_conformance ($3, $7, $8); + cb_check_conformance ($3, $7, $8, call_conv); } } diff --git a/cobc/tree.h b/cobc/tree.h index ea3484d0d..43e399137 100644 --- a/cobc/tree.h +++ b/cobc/tree.h @@ -2592,7 +2592,7 @@ extern cb_tree cb_build_write_advancing_lines (cb_tree, cb_tree); extern cb_tree cb_build_write_advancing_mnemonic (cb_tree, cb_tree); extern cb_tree cb_build_write_advancing_page (cb_tree); extern cb_tree cb_check_sum_field (cb_tree x); -extern void cb_check_conformance (cb_tree, cb_tree, cb_tree); +extern void cb_check_conformance (cb_tree, cb_tree, cb_tree, int); extern void cb_emit_initiate (cb_tree rep); extern void cb_emit_terminate (cb_tree rep); extern void cb_emit_generate (cb_tree rep); diff --git a/cobc/typeck.c b/cobc/typeck.c index abd44b795..9ea4d5d08 100644 --- a/cobc/typeck.c +++ b/cobc/typeck.c @@ -3921,7 +3921,7 @@ check_argument_conformance (struct cb_program *program, cb_tree argument_tripple void cb_check_conformance (cb_tree prog_ref, cb_tree using_list, - cb_tree returning) + cb_tree returning, int call_conv) { struct cb_program *program = NULL; cb_tree l; @@ -3932,6 +3932,24 @@ cb_check_conformance (cb_tree prog_ref, cb_tree using_list, const struct cb_field *prog_returning_field; const struct cb_field *call_returning_field; + if (call_conv == CB_CONV_JAVA && CB_LITERAL_P (prog_ref)) { + char *full_name, *class_and_method_name, *dot; + full_name = (char *)CB_LITERAL(prog_ref)->data; + class_and_method_name = full_name + 5; + dot = strchr (class_and_method_name, '.'); + if (dot == NULL) { + cb_error_x (prog_ref, _("malformed Java method name '%s', " + "expected format 'Java.ClassName.methodName'"), + full_name); + return; + } + if (using_list != NULL || returning != NULL) { + CB_PENDING ("Java method call with parameters or return values"); + COBC_ABORT (); + } + return; + } + /* Try to get the program referred to by prog_ref. */ program = try_get_program (prog_ref); if (!program) { @@ -3943,20 +3961,6 @@ cb_check_conformance (cb_tree prog_ref, cb_tree using_list, return; } - if (CB_LITERAL_P(prog_ref)) { - char *full_name = (char *)CB_LITERAL(prog_ref)->data; - char *class_and_method_name = full_name + 5; - char *last_dot = strrchr(class_and_method_name, '.'); - if (last_dot == NULL) { - cobc_err_msg(_("Malformed Java method name '%s'"), class_and_method_name); - return; - } - if (using_list != NULL || returning != NULL) { - CB_PENDING ("Java method call with parameters or return values"); - COBC_ABORT (); - } - } - /* Check each parameter is conformant: has right type, has right REFERENCE/VALUE phrase, has right length, etc. From 78262a16e1e53e0df0439e96a454c5485d0ab45b Mon Sep 17 00:00:00 2001 From: Nicolas Berthier Date: Tue, 12 Nov 2024 17:09:39 +0100 Subject: [PATCH 20/20] Use fully qualified Java class names --- cobc/codegen.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/cobc/codegen.c b/cobc/codegen.c index 0ab75ecee..d8cefe356 100644 --- a/cobc/codegen.c +++ b/cobc/codegen.c @@ -7115,9 +7115,8 @@ output_exception_handling (struct cb_call *p) static void output_java_call (struct cb_call *p) { - char *class_and_method_name, *last_dot, *c; - const char *class_name, *method_name; - char mangled[COB_NORMAL_BUFF]; + char *class_and_method_name, *class_name, *method_name, *last_dot, *c; + char mangled[COB_NORMAL_BUFF]; /* Assume "Java." prefix (enforced in `parser.y`, rule `call_body`) */ class_and_method_name = (char*)CB_LITERAL(p->name)->data + 5; @@ -7132,6 +7131,9 @@ output_java_call (struct cb_call *p) *last_dot = '\0'; method_name = last_dot + 1; class_name = class_and_method_name; + for (c = class_name; *c; c++) { + if (*c == '.') *c = '/'; + } output_line ("if (call_java_%s == NULL)", mangled); output_block_open ();