2
19
Fork
You've already forked array
3

Kap jvm: needs exception handling similar to Dyalog JNI #36

Closed
opened 2025年11月09日 11:17:24 +01:00 by kapitaali · 11 comments
Contributor
Copy link

Current jvm: functions (at least that I'm aware of) do not allow one to handle exceptions on the JVM side. Dyalog has exception handling and you can do something like

:If ⎕SE.Dyalog.JNI.ExceptionCheck env
 ⎕SE.Dyalog.JNI.ExceptionClear env
 'Skipping: ', className
 →nextIteration
:EndIf

I wrote a JAR loader, and it works for small JAR files:

loadJar "../classes/helloworld.jar"
┌Map: 1───────────────────────┐
│"HelloWorld" class HelloWorld│
└─────────────────────────────┘

But bigger JAR files give various errors that relate to the internal dependency structure:

loadJar "../classes/cobaltstrike.jar"
Error at: 40:21: callMethod: JVM Exception: superclass access check failed: class de.javasoft.plaf.synthetica.SyntheticaDefaultLookup (in unnamed module @0x3e8691d7) cannot access class sun.swing.DefaultLookup (in module java.desktop) because module java.desktop does not export sun.swing to unnamed module @0x3e8691d7
loadJar "../classes/shadow-cljs.jar"
Error at: 40:21: callMethod: JVM Exception: class org.apache.commons.io.comparator.ReverseComparator cannot access its abstract superclass org.apache.commons.io.comparator.AbstractFileComparator (org.apache.commons.io.comparator.ReverseComparator is in unnamed module of loader java.net.URLClassLoader @4ed05b28; org.apache.commons.io.comparator.AbstractFileComparator is in unnamed module of loader 'app')

The point is just to be able to skip loading those classes that cause exceptions.

Current jvm: functions (at least that I'm aware of) do not allow one to handle exceptions on the JVM side. Dyalog has exception handling and you can do something like ``` :If ⎕SE.Dyalog.JNI.ExceptionCheck env ⎕SE.Dyalog.JNI.ExceptionClear env 'Skipping: ', className →nextIteration :EndIf ``` I wrote a JAR loader, and it works for small JAR files: ``` loadJar "../classes/helloworld.jar" ┌Map: 1───────────────────────┐ │"HelloWorld" class HelloWorld│ └─────────────────────────────┘ ``` But bigger JAR files give various errors that relate to the internal dependency structure: ``` loadJar "../classes/cobaltstrike.jar" Error at: 40:21: callMethod: JVM Exception: superclass access check failed: class de.javasoft.plaf.synthetica.SyntheticaDefaultLookup (in unnamed module @0x3e8691d7) cannot access class sun.swing.DefaultLookup (in module java.desktop) because module java.desktop does not export sun.swing to unnamed module @0x3e8691d7 loadJar "../classes/shadow-cljs.jar" Error at: 40:21: callMethod: JVM Exception: class org.apache.commons.io.comparator.ReverseComparator cannot access its abstract superclass org.apache.commons.io.comparator.AbstractFileComparator (org.apache.commons.io.comparator.ReverseComparator is in unnamed module of loader java.net.URLClassLoader @4ed05b28; org.apache.commons.io.comparator.AbstractFileComparator is in unnamed module of loader 'app') ``` The point is just to be able to skip loading those classes that cause exceptions.
Author
Contributor
Copy link

Here's the code if you want to try it out:

∇ loadJar (jarName) {
 ⍝ jarName ← "../classes/myJar.jar" relative dir + jarfile name
 fullpath ← { ~("/" ∊ ̄1↑⍵) → ⍵←⍵, "/" } jarName
 fileClass ← jvm:findClass "java.io.File"
 stringClass ← jvm:findClass "java.lang.String"
 fileConstructor ← jvm:findConstructor⟦fileClass; stringClass⟧
 filePathObj ← jvm:createInstance⟦fileConstructor; fullpath⟧
 toURI ← jvm:findMethod⟦fileClass; "toURI"⟧
 uri ← jvm:callMethod⟦toURI; filePathObj⟧
 toURL ← jvm:findMethod⟦fileClass; "toURL"⟧
 url ← jvm:callMethod⟦toURL; filePathObj⟧
 urlArray ← jvm:createArrayInstance⟦jvm:findClass "java.net.URL"; 1⟧
 jvm:arraySetElement (urlArray; 0; url)
 classloaderClass ← jvm:findClass "java.net.URLClassLoader" 
 getClassMethod ← jvm:findMethod⟦jvm:findClass "java.lang.Object"; "getClass"⟧
 classloaderConstructor ← jvm:findConstructor⟦classloaderClass; jvm:callMethod⟦getClassMethod; jvm:createArrayInstance⟦jvm:findClass "java.net.URL"; 0⟧⟧⟧
 classloaderObj ← jvm:createInstance⟦classloaderConstructor; urlArray⟧
 jarloaderClass ← jvm:findClass "java.util.jar.JarFile" 
 jarloaderConstructor ← jvm:findConstructor⟦jarloaderClass; stringClass⟧
 jarloaderObj ← jvm:createInstance⟦jarloaderConstructor; jarName⟧
 entriesMethod ← jvm:findMethod⟦jarloaderClass; "entries"⟧
 entriesObj ← jvm:callMethod⟦entriesMethod; jarloaderObj⟧
 classMap ← map ⍬
 classNames ← ⍬
 enumClass ← jvm:findClass "java.util.Enumeration"
 hasmoreMethod ← jvm:findMethod⟦enumClass; "hasMoreElements"⟧
 nextMethod ← jvm:findMethod⟦enumClass; "nextElement"⟧
 entryClass ← jvm:findClass "java.util.jar.JarEntry"
 getnameMethod ← jvm:findMethod⟦entryClass; "getRealName"⟧
 while ("true"≡jvm:fromJvm jvm:callMethod⟦jvm:findMethod⟦jvm:findClass "java.lang.Boolean"; "toString"⟧; jvm:callMethod⟦hasmoreMethod; entriesObj⟧⟧) {
 entryObj ← jvm:callMethod⟦nextMethod; entriesObj⟧
 entryName ← jvm:callMethod⟦getnameMethod; entryObj⟧
 n←jvm:fromJvm entryName
 if ((".class"≡ ̄6↑n)∧("META-"≢5↑n)) {
 className ← ̄6↓n
 className ← "/" regex:replace (className; ".")
 classNames ← classNames, ⊂className
 classNameJ ← jvm:toJvmString className
 loadclassMethod ← jvm:findMethod⟦classloaderClass; "loadClass"; stringClass⟧
 { 
 loadedClass ← jvm:callMethod⟦loadclassMethod; classloaderObj; classNameJ⟧ 
 classMap ← classMap mapPut className loadedClass
 } catch 'jvm:jvmMethodCallException λ{ ⍬ }
 }
 }
 classMap
}
Here's the code if you want to try it out: ``` ∇ loadJar (jarName) { ⍝ jarName ← "../classes/myJar.jar" relative dir + jarfile name fullpath ← { ~("/" ∊ ̄1↑⍵) → ⍵←⍵, "/" } jarName fileClass ← jvm:findClass "java.io.File" stringClass ← jvm:findClass "java.lang.String" fileConstructor ← jvm:findConstructor⟦fileClass; stringClass⟧ filePathObj ← jvm:createInstance⟦fileConstructor; fullpath⟧ toURI ← jvm:findMethod⟦fileClass; "toURI"⟧ uri ← jvm:callMethod⟦toURI; filePathObj⟧ toURL ← jvm:findMethod⟦fileClass; "toURL"⟧ url ← jvm:callMethod⟦toURL; filePathObj⟧ urlArray ← jvm:createArrayInstance⟦jvm:findClass "java.net.URL"; 1⟧ jvm:arraySetElement (urlArray; 0; url) classloaderClass ← jvm:findClass "java.net.URLClassLoader" getClassMethod ← jvm:findMethod⟦jvm:findClass "java.lang.Object"; "getClass"⟧ classloaderConstructor ← jvm:findConstructor⟦classloaderClass; jvm:callMethod⟦getClassMethod; jvm:createArrayInstance⟦jvm:findClass "java.net.URL"; 0⟧⟧⟧ classloaderObj ← jvm:createInstance⟦classloaderConstructor; urlArray⟧ jarloaderClass ← jvm:findClass "java.util.jar.JarFile" jarloaderConstructor ← jvm:findConstructor⟦jarloaderClass; stringClass⟧ jarloaderObj ← jvm:createInstance⟦jarloaderConstructor; jarName⟧ entriesMethod ← jvm:findMethod⟦jarloaderClass; "entries"⟧ entriesObj ← jvm:callMethod⟦entriesMethod; jarloaderObj⟧ classMap ← map ⍬ classNames ← ⍬ enumClass ← jvm:findClass "java.util.Enumeration" hasmoreMethod ← jvm:findMethod⟦enumClass; "hasMoreElements"⟧ nextMethod ← jvm:findMethod⟦enumClass; "nextElement"⟧ entryClass ← jvm:findClass "java.util.jar.JarEntry" getnameMethod ← jvm:findMethod⟦entryClass; "getRealName"⟧ while ("true"≡jvm:fromJvm jvm:callMethod⟦jvm:findMethod⟦jvm:findClass "java.lang.Boolean"; "toString"⟧; jvm:callMethod⟦hasmoreMethod; entriesObj⟧⟧) { entryObj ← jvm:callMethod⟦nextMethod; entriesObj⟧ entryName ← jvm:callMethod⟦getnameMethod; entryObj⟧ n←jvm:fromJvm entryName if ((".class"≡ ̄6↑n)∧("META-"≢5↑n)) { className ← ̄6↓n className ← "/" regex:replace (className; ".") classNames ← classNames, ⊂className classNameJ ← jvm:toJvmString className loadclassMethod ← jvm:findMethod⟦classloaderClass; "loadClass"; stringClass⟧ { loadedClass ← jvm:callMethod⟦loadclassMethod; classloaderObj; classNameJ⟧ classMap ← classMap mapPut className loadedClass } catch 'jvm:jvmMethodCallException λ{ ⍬ } } } classMap } ```
Owner
Copy link

Did you try catching jvmMethodCallException? You can find an example here:

https://codeberg.org/loke/array/src/branch/master/array/standard-lib/xml.kap#L73

Note that as of the most recent commit, this symbol was moved from the kap to the jvm namespace.

Did you try catching `jvmMethodCallException`? You can find an example here: https://codeberg.org/loke/array/src/branch/master/array/standard-lib/xml.kap#L73 Note that as of the most recent commit, this symbol was moved from the `kap` to the `jvm` namespace.
Author
Contributor
Copy link

Thanks, I added catching to the code above. It still fails. It seems a bit similar to the thing where JVM method returns an instance of java.lang.Boolean, but then when you do jvm:fromJvm, it says fromJvm: Unexpected JVM type: kotlin.Boolean.

Thanks, I added catching to the code above. It still fails. It seems a bit similar to the thing where JVM method returns an instance of java.lang.Boolean, but then when you do jvm:fromJvm, it says `fromJvm: Unexpected JVM type: kotlin.Boolean`.
Owner
Copy link

As best as I can tell, catching that exception should work. Can you show the code you used?

As best as I can tell, catching that exception should work. Can you show the code you used?
Author
Contributor
Copy link
code https://codeberg.org/loke/array/issues/36#issuecomment-8191205
Author
Contributor
Copy link

For example loading Kap's own internal JARs work, eg.

loadJar "../contrib/httpserver-mod/build/libs/httpserver-mod-jvm.jar"

but if try with this one https://repo.clojars.org/thheller/shadow-cljs/3.0.4/shadow-cljs-3.0.4.jar it will say

loadJar "../classes/shadow-cljs-3.0.4.jar"
Error at: 41:23: callMethod: JVM Exception: io/methvin/watchservice/MacOSXListeningWatchService$Config
For example loading Kap's own internal JARs work, eg. ``` loadJar "../contrib/httpserver-mod/build/libs/httpserver-mod-jvm.jar" ``` but if try with this one https://repo.clojars.org/thheller/shadow-cljs/3.0.4/shadow-cljs-3.0.4.jar it will say ``` loadJar "../classes/shadow-cljs-3.0.4.jar" Error at: 41:23: callMethod: JVM Exception: io/methvin/watchservice/MacOSXListeningWatchService$Config ```
Author
Contributor
Copy link

I just realized that this error might be due to me trying to open JAR files that themselves are executable, and that they error if I try to java -jar them from terminal (both cobaltsrike and shadow-cljs are executable).

However, catching those exceptions still would be nice. Then it would not error on Kap's end. Opening the JAR would just not do much anything (I guess) and I could print an error message of my own.

I just realized that this error might be due to me trying to open JAR files that themselves are executable, and that they error if I try to `java -jar ` them from terminal (both cobaltsrike and shadow-cljs are executable). However, catching those exceptions still would be nice. Then it would not error on Kap's end. Opening the JAR would just not do much anything (I guess) and I could print an error message of my own.
Owner
Copy link

The catching really should be working. When you tried, did you check out the latest version of the code first, since the name of the tag that you should catch had changed.

The catching really should be working. When you tried, did you check out the latest version of the code first, since the name of the tag that you should catch had changed.
Author
Contributor
Copy link

Yes, I have checked out master. I can see the exception type being used in xml.kap.

Here is a minimal test example, it should return custom error message:

{
 mathClass ← jvm:findClass "java.lang.Math"
 intClass ← jvm:findPrimitiveTypeClass 'jvm:int
 divideMethod ← jvm:findMethod⟦mathClass; "divideExact"; intClass; intClass⟧
 test ← jvm:callMethod⟦divideMethod; null; jvm:toJvmInt 1; jvm:toJvmInt 0⟧
} catch 'jvm:jvmMethodCallException λ{ "testing catching exceptions, tried to divide by zero" }

Running this gives me

Error at: 5:9: callMethod: JVM Exception: / by zero

so the exception is not caught.

Yes, I have checked out master. I can see the exception type being used in xml.kap. Here is a minimal test example, it should return custom error message: ``` { mathClass ← jvm:findClass "java.lang.Math" intClass ← jvm:findPrimitiveTypeClass 'jvm:int divideMethod ← jvm:findMethod⟦mathClass; "divideExact"; intClass; intClass⟧ test ← jvm:callMethod⟦divideMethod; null; jvm:toJvmInt 1; jvm:toJvmInt 0⟧ } catch 'jvm:jvmMethodCallException λ{ "testing catching exceptions, tried to divide by zero" } ``` Running this gives me ``` Error at: 5:9: callMethod: JVM Exception: / by zero ``` so the exception is not caught.
Owner
Copy link

I tested your code, and I get the message: "testing catching...", so it works for me. The only explanation I have is that for some reason you're using the old version. Can you look in jvm-module.kt and check what the JvmFunctionCallException constructor looks like.

It should contain this:

 tag = ThrowableTag(APLSymbol(module.jvmMethodCallExceptionSymbol), JvmInstanceValue(originException))
I tested your code, and I get the message: "testing catching...", so it works for me. The only explanation I have is that for some reason you're using the old version. Can you look in `jvm-module.kt` and check what the `JvmFunctionCallException` constructor looks like. It should contain this: ``` tag = ThrowableTag(APLSymbol(module.jvmMethodCallExceptionSymbol), JvmInstanceValue(originException)) ```
Author
Contributor
Copy link

Thanks. Looks like trying with another system works.

I have two laptops here, tested with both of them. The other one is older Lenovo. That test code does work on the older Lenovo, custom error message is returned and exception is caught.

I'm closing this now since with my older laptop the above code does in fact load shadow-cljs.jar without any kind of errors.

Thanks. Looks like trying with another system works. I have two laptops here, tested with both of them. The other one is older Lenovo. That test code does work on the older Lenovo, custom error message is returned and exception is caught. I'm closing this now since with my older laptop the above code does in fact load shadow-cljs.jar without any kind of errors.
Sign in to join this conversation.
No Branch/Tag specified
master
jdk25
tmp-generic-window-management
experiment-assign-to-function-expression
feature-error-highlight
feature-mosaic-ui
feature-sdl
feature-rationalise-reduced
adaptive-experiment
bugfix-linux-charconversions
vector-ops
vector-jvm
sane-reduce
formatter
bitarrays
array-builder
jvm-android-split-new
nested-function-calls-bug
multi-dimensional-string-formatting
ride-impl
codeberg-test
jvm-android-split
libreoffice-module-classloader
optimise-single-element-array
parser-callbacks
new-jline-completion
java-readline
test-tools
int-list-opt
linux-ffi
ffi
isqrt
inter-thread-datatransfer
metadata-highlight
numeric-total-ordering
libreoffice
langbar-javafx
port-mpbignum-to-wasmjs
aesh-test
zero-parser
java21
fxcontrols-spreadsheet-fork
javafx-reporting
new-http
new-optimiser
sixel-renderer
native-input
lambda-scope
custom-renderer
formatter-test
local-functions-fix
kap-rational
bigint2
bigint
reporting
dynamic-assign
structure-viewer2
return-impl
escape-analysis
fork-new-syntax
structural-under
axis-eval
binomial-impl
new-enclose
short-fn-definition
compose-impl
specialised-arrays
ops
domino
clientweb
lcm
gui-experiments
suspend
thread-support
closures_wip
inner-join
maths_axis
keyboard
resultlist3
error-locations
axis-work
No results found.
Labels
Clear labels
No items
No labels
Milestone
Clear milestone
No items
No milestone
Projects
Clear projects
No items
No project
Assignees
Clear assignees
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
loke/array#36
Reference in a new issue
loke/array
No description provided.
Delete branch "%!s()"

Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?