Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Initial JNI configuration #157

Open
wants to merge 20 commits into
base: java-interop
Choose a base branch
from
Open

Conversation

xevor11
Copy link

@xevor11 xevor11 commented Jul 9, 2024

Cleaned up java.c, added the requested changes from PR#150, ready for feedback and comments

@xevor11 xevor11 marked this pull request as draft July 9, 2024 13:31
@xevor11 xevor11 marked this pull request as ready for review July 9, 2024 13:32
configure.ac Outdated Show resolved Hide resolved
configure.ac Outdated Show resolved Hide resolved
libcob/common.h Outdated Show resolved Hide resolved
cobc/codegen.c Outdated Show resolved Hide resolved
cobc/codegen.c Outdated
}
output ("cob_resolve_java ();");
output_newline ();
output_block_close ();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right here you need to generate a call to cob_call_java(…) with the appropriate handle name call_java_%s. Arguments will come in a subsequent PR.

cobc/codegen.c Outdated Show resolved Hide resolved
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This file should not be committed.

libcob/java.c Outdated Show resolved Hide resolved
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Everything under po should not be committed either.

configure.ac Outdated Show resolved Hide resolved
@nberth nberth changed the title Rebased and cleaned up all the changes Initial JNI configuration Jul 10, 2024
@xevor11 xevor11 force-pushed the jni-config branch 2 times, most recently from 8df9f29 to 9164ac8 Compare July 10, 2024 19:30
Copy link
Collaborator

@GitMensch GitMensch left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the real next change that should be done is an actual test for calling a static java method that has no parameters, to verify that java.c indeed works

cobc/codegen.c Outdated
@@ -146,6 +152,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 java_call_list *call_java_cache = NULL;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

minor formatting: use a tab before the *

cobc/codegen.c Outdated
Comment on lines 398 to 406
lookup_java_call(const char *p) {
struct java_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 java_call_list));
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

minor formatting: a space before the (, see lookup_call below (and sorry for not providing an automated formatting rule via gnu indent yet)

cobc/codegen.c Outdated
static void
lookup_call (const char *p)
{
struct call_list *clp;

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

minor formatting: please keep this nl here, reduces the diff :-)

cobc/codegen.c Outdated
Comment on lines 7556 to 7562
if(
#ifdef HAVE_JNI
strncmp("Java.", s, 6) == 0
#else
0
#endif
) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is possible that the compiler isn't build with JNI, but the runtime is - and we don't call into JNI here either, so please use the code unconditional; in any case this whole block should be moved, either to the start of lookup_func_call, or (if there aren't too much places) in the caller with having this code be placed completely in a different function.
Mind the formatting = space before ( in that.

cobc/codegen.c Outdated
#endif
) {
lookup_java_call(s + 6);
output_line ("if (call_java_%s == NULL || cob_glob_ptr->cob_physical_cancel)", name_str);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a way to CANCEL a java class (and if yes to really unload it without jni teardown)?
I guess not - and in this case the check for physical cancel should be dropped.

cobc/codegen.c Outdated
output_block_open();
output_prefix();
output ("call_java_%s = ", name_str);
char *first_dot = strchr(s + 6, '.');
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To be able to compile that with C89 (still a requirement for 4.x, may be dropped later), move this line to the start of the block.

configure.ac Show resolved Hide resolved
configure.ac Outdated
Comment on lines 489 to 492
AC_ARG_ENABLE([java],
[AC_HELP_STRING([--disable-java],
[disable Java Interoperability])])
AS_IF([test X$enable_java != Xno], [
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ARC_ARG_ENABLE is more about optional features (like the debug log) and less about external packages where AC_ARG_WITH is preferable (and would also match what configure has for example for libxml2).

See https://www.gnu.org/savannah-checkouts/gnu/autoconf/manual/autoconf-2.72/html_node/External-Software.html and adjust (you may postpone that part until you have actual running tests, though).

In any case please move the JNI block directly before the libxml2 part.

libcob/common.h Outdated
Comment on lines 1297 to 1298
// #ifdef HAVE_JNI
// #include "jni.h"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

these commented line and the one above should be dropped

@GitMensch
Copy link
Collaborator

I hope my commit to java.c did not break anything... (I just felt that the explanations would take much longer and those are more related to libcob in general than the changes of this project itself).
... if not: thank you for cleaning it up.

And yes, once there is a running simple test, we should update the branch from gcos4 which will then also include the CI. for further commits.

@xevor11
Copy link
Author

xevor11 commented Jul 12, 2024

I hope my commit to java.c did not break anything... (I just felt that the explanations would take much longer and those are more related to libcob in general than the changes of this project itself). ... if not: thank you for cleaning it up.

And yes, once there is a running simple test, we should update the branch from gcos4 which will then also include the CI. for further commits.

image

Update: Fixed warnings

xevor11 added a commit to xevor11/gnucobol that referenced this pull request Jul 12, 2024
cobc/codegen.c Outdated Show resolved Hide resolved
cobc/codegen.c Outdated Show resolved Hide resolved
cobc/codegen.c Outdated
char *method_name;
const char *class_name;

lookup_java_call(p + 5);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here you need to do some mangling: at the moment maybe substitute .s into some other character like _.

cobc/codegen.c Outdated Show resolved Hide resolved
cobc/codegen.c Outdated Show resolved Hide resolved
cobc/codegen.c Outdated Show resolved Hide resolved
cobc/codegen.c Outdated Show resolved Hide resolved
cobc/codegen.c Outdated
Comment on lines 400 to 410
struct java_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 java_call_list));
clp->call_name = p;
clp->next = call_java_cache;
call_java_cache = clp;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please indent on the left with tab

cobc/codegen.c Outdated
Comment on lines 411 to 442
for (clp = func_call_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 = func_call_cache;
func_call_cache = clp;
for (clp = func_call_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 = func_call_cache;
func_call_cache = clp;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

indentation got wrong, please clean up, which will also lead to a smaller diff

Copy link
Collaborator

@GitMensch GitMensch left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Requesting mostly formatting changes (and the variable name pointed out by Nicolas) for now - we really would need a testcase that showcase a call to a static, preferably parameterless, jre internal function works fine.

The one "real" change is to move the check for the java call into the parser - see review comments for the details.

And even if it doesn't work it should still be added to a new testcase at the end of run_extensions.at

cobc/codegen.c Outdated
if(strncasecmp("Java.", s, 5) == 0) {
output_java_call(s + 5);
return;
} else {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

drop that else and the closing brace as you have a return already and don't need the additional scope (and create smaller diffs that way)

ChangeLog Outdated
Comment on lines 2 to 4
* 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
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

space indent here, leave an empty line before and after the name, please

cobc/codegen.c Outdated
@@ -7531,6 +7582,10 @@ output_call (struct cb_call *p)
/* Dynamic link */
if (name_is_literal_or_prototype) {
s = get_program_id_str (p->name);
if(strncasecmp("Java.", s, 5) == 0) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this comparision should go into parser.y

gnucobol/cobc/parser.y

Lines 12529 to 12530 in 00f6832

if (CB_LITERAL_P ($3)) {
cb_check_conformance ($3, $7, $8);

I suggest that after we found out if this is a java call or not, we set the call_convention to a new value matching a define JAVA_CALL. This has the benefit that we can then also check the parameter conformance later (codegen is too later for that) and output a warning if the current environment does not support Java interop.
In this function here we can then directly at the start or even in the caller get to output_call_java().

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@xevor11 To help with that, I've just rebased java-interop on top of master (which is trunk on the upstream). Now you can rebase your jni-config branch on top of that so you'll have access to the piece of code that's linked above.

After that you can define CB_CONV_JAVA in tree.h, and then do the check for the "Java." prefix in parser.y (I think right before the call to cb_check_conformance, but I'm not sure what that latter function does exactly).

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes right before that function, which checks that something is according to a known prototype - in the case of Java that should check that only "java interop compatible" arguments are passed.

@xevor11
Copy link
Author

xevor11 commented Jul 30, 2024

Addressing this specific system call:

System.getProperty("java.version")

I wrote a simple method call:

if ((cob_global_exception & 0x0b00) == 0x0b00) cob_global_exception = 0;
    if (call_java_class_method_call.funcvoid == NULL || cob_glob_ptr->cob_physical_cancel)
  {
    call_java_class_method_call.funcvoid = cob_resolve_cobol ("java_class_method_call", 0, 0);
  }

however internally it must be create the VM and retrieve the methods from the JNI Function Table?

cobc/parser.y Outdated Show resolved Hide resolved
@xevor11
Copy link
Author

xevor11 commented Aug 23, 2024

There is another PR #174 which includes the non-void method handling as well

cobc/parser.y Outdated Show resolved Hide resolved
Copy link
Collaborator

@GitMensch GitMensch left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The overall state is quite good!

Biggest issues:

  • from glancing at the code I think CALL...[NOT] ON EXCEPTION is broken (adjust the tests as requested and we'll see)
  • the build system need some changes
  • @nberth has a bit to do on the delay-load part (incl. ChangeLog entries)
  • if possible I'd like to see the parameter check already, but we can leave this for later as well

DEPENDENCIES.md Outdated
Comment on lines 129 to 131
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.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please word-wrap as for SCREEN SECTION

NEWS Outdated
@@ -20,7 +20,7 @@ NEWS - user visible changes -*- outline -*-


* New GnuCOBOL features

** Initial support for Java interoperability through JNI (new optional dependency JDK)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please adjust as for "JSON GENERATE" (just check in the same file searching for "json"):

  • what is available now
  • what is needed for support (I guess this will only be a dependency for runtime support, e. g. all checks in cobc work identical without that)
  • add the related configure option / flags
  • word-wrap your new content to col80

ChangeLog Outdated
Comment on lines 4 to 5
* ax_prog_java.m4: Added macro for jni check
* ax_jni_include_dir.m4: Added macro for jni check
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • combine both m4 entries to one entry (comma separate filename, include their "m4/" prefix)
  • make the configure entry more specific, like the 2020-07-19 one
  • add something like NEWS, DEPENDENCIES: updated for JNI

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This file and most others in cobc miss a ChangeLog entry

cobc/codegen.c Outdated Show resolved Hide resolved
libcob/java.c Outdated
Comment on lines 118 to 119
if ((*env)->ExceptionCheck(env)) {
(*env)->ExceptionDescribe(env);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

instead of ExceptionDescribe which puts the message unconditionally to stderr and implicit frees the exception, the message should be extracted and setup into last error - see call.c for the later;

no idea if there's a cleaner way (please give it a short check), but ChatGPT said we need to use JNI to get the details, giving the following example (which at least needs to be converted to C89 compat [vars+comments]:

    // Find the Throwable class
    jclass throwableClass = (*env)->FindClass(env, "java/lang/Throwable");

    // Get the getMessage() method from Throwable class
    jmethodID getMessageMethod = (*env)->GetMethodID(env, throwableClass, "getMessage", "()Ljava/lang/String;");
    if (getMessageMethod != NULL) {
        // Call getMessage() on the exception object
        jstring message = (jstring)(*env)->CallObjectMethod(env, exception, getMessageMethod);

        // Convert the Java String (jstring) to a C string (char*)
        const char *messageChars = (*env)->GetStringUTFChars(env, message, NULL);
        printf("Exception message: %s\n", messageChars);
        (*env)->ReleaseStringUTFChars(env, message, messageChars); // Clean up
    }

    // Find java.io.StringWriter and java.io.PrintWriter classes
    jclass stringWriterClass = (*env)->FindClass(env, "java/io/StringWriter");
    jclass printWriterClass = (*env)->FindClass(env, "java/io/PrintWriter");

    // Create a new StringWriter object
    jobject stringWriter = (*env)->NewObject(env, stringWriterClass, (*env)->GetMethodID(env, stringWriterClass, "<init>", "()V"));

    // Create a new PrintWriter object that wraps the StringWriter
    jobject printWriter = (*env)->NewObject(env, printWriterClass, 
                                            (*env)->GetMethodID(env, printWriterClass, "<init>", "(Ljava/io/Writer;)V"), 
                                            stringWriter);

    // Call exception.printStackTrace(printWriter) to write the stack trace to the StringWriter
    jclass throwableClass = (*env)->GetObjectClass

instead of the convenience function ExceptionCheck use ExceptionOccurred which returns either NULL or the jthrowable we need for the code above

libcob/call.c Outdated
#else
static int first_java = 1;

COB_UNUSED (method_handle);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

instead of the unused: check if it is empty (before the #if WITH_JNI) and set the relevant exceptions then; scoping the complete#else in a separate block {}

output_line("cob_call_java(call_java_%s);\n", mangled);
output_newline();
output_block_close();
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do an exception check afterwards, as done for normal calls; also add the ON EXCEPTION/ NOT ON EXCEPTION codegen; possibly by moving those parts out of output_call() and executing them also in the 'CB_CONV_JAVA` part.

Comment on lines 70 to 87
AT_SETUP([CALL Java static void (void) (missing class)])
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.isAMissingClass"
STOP RUN.
])

AT_CHECK([$COMPILE prog.cob], [0], [], [])
AT_CHECK([$COBCRUN_DIRECT ./prog], [1], [],
[libcob: prog.cob:5: error: Java class 'Test' not found
])
AT_CLEANUP
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

duplicate this test with ON EXCEPTION and DISPLAY "java call not successful" in there; this duplicated test also doesn't need to be skipped if we don't have JNI as it should run in the same exception

PROGRAM-ID. prog.
PROCEDURE DIVISION.
CALL "Java.Test.printHelloWorld"
STOP RUN.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add NOT ON EXCEPTION DISPLAY "Java call worked" to this call

@GitMensch
Copy link
Collaborator

I've got word from the original author of the java related macros used and that's (part of) his answer:

I'm afraid I cannot help and that it would be better to make these macros as deprecated/abandoned.

I'm likely to adjust the part configure for that (but that likely takes some days until I get to this), @nberth can you please check the review related to the delay-load part and @xevor11 could you please check on the rest?

@nberth nberth force-pushed the jni-config branch 3 times, most recently from 45956df to 5aaa4a9 Compare November 6, 2024 11:25
cobc/codegen.c Outdated
const char *class_name, *method_name;
char mangled[COB_NORMAL_BUFF];

/* TODO: move that back into cb_check_conformance (*) */
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@xevor11 I think you have started moving this check and the one below back into cb_check_conformance; so I'll stop pushing changes to this branch to let you advance with that.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants