7
$\begingroup$

If I call into Java code via JLink`, and there is an uncaught exception, Mathematica produces a Java::excptn message, and prints a short snippet of the stack trace (3 or 4 lines). Unfortunately I can't decipher from the few lines that it prints what's going on.

Can I make Mathematica print the entire stack trace when an exception is thrown in JLink`?

$\endgroup$
5
$\begingroup$

Mathematica usually shows all of the stack trace elements that are due to user code. It omits stack frames that belongs to JLink itself. This is rarely a problem but, as we shall see, there is a small possibility that we could lose some frames of interest.

Example

Before we talk about the missing frames, let's look at an example. The following code is contrived to throw an exception from a fairly deep call stack:

Needs["JLink`"]
InstallJava[];

JavaNew["javax.script.ScriptEngineManager"] @
  getEngineByName["JavaScript"] @
  eval["throw 'bad news!'"]

stack trace

We can see that many stack frames are shown. In fact, all of the frames related to our code are shown. The outermost frame (AbstractScriptEngine.eval) corresponds directly to our eval[...] expression.

However, there is a subtle clue that some frames are missing from this trace. Notice the last line of the causative exception, where it says "... 13 more". This implies that the main stack trace should have 13 lines, but we only see three. Which frames are missing?

Missing Frames

When JLink assembles a message reporting a stack trace, it quietly omits any frames that it thinks belong to JLink itself. In the case at hand, this amounts to the ten missing frames. We can peek at the JLink frames like this:

JavaBlock @ Module[{s}
, s = JavaNew["java.io.StringWriter"]
; JavaNew["java.lang.RuntimeException", "bad news!"] @
    printStackTrace[JavaNew["java.io.PrintWriter", s]]
; s @ toString[]
]

(*
java.lang.RuntimeException: bad news!
    at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
    at sun.reflect.NativeConstructorAccessorImpl.newInstance(Unknown Source)
    at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(Unknown Source)
    at java.lang.reflect.Constructor.newInstance(Unknown Source)
    at com.wolfram.jlink.ClassRecord.callBestCtor(ObjectHandler.java:749)
    at com.wolfram.jlink.ObjectHandler.callCtor(ObjectHandler.java:125)
    at com.wolfram.jlink.KernelLinkImpl.callJava(KernelLinkImpl.java:942)
    at com.wolfram.jlink.KernelLinkImpl.handleCallPacket(KernelLinkImpl.java:628)
    at com.wolfram.jlink.KernelLinkImpl.handlePacket(KernelLinkImpl.java:303)
    at com.wolfram.jlink.Reader.run(Reader.java:154)
*)

This code captures a stack trace and converts it to a string before JLink can get its hands on it. It shows us ten JLink-related frames.

What Frames Will Be Omitted?

The code for JLink is shipped with the Mathematica distribution. We can inspect it to see exactly which frames are dropped:

{ $InstallationDirectory
, "SystemFiles","Links","JLink","Source","Java","com","wolfram","jlink","Utils.java"
} // FileNameJoin // FilePrint

(*
// ... snip ... 442 lines omitted ...
static String createExceptionMessage(Throwable t) {
    // ... snip ...
    if (t instanceof java.lang.reflect.InvocationTargetException)
        t = ((java.lang.reflect.InvocationTargetException) t).getTargetException();
    // ... snip ...
        if (line.indexOf("at com.wolfram.jlink") == -1 &&
            line.indexOf("at java.lang.reflect.Method.invoke") == -1 &&
            line.indexOf("at sun.reflect.NativeMethodAccessorImpl") == -1 &&
            line.indexOf("at sun.reflect.DelegatingMethodAccessorImpl") == -1 &&
            line.indexOf("at sun.reflect.GeneratedMethodAccessor1") == -1 &&
            line.indexOf("at sun.reflect.GeneratedMethodAccessor2") == -1)
            linesToKeep.add(line);
    }
    // ... snip ...
*)

We can see that JLink will drop not only any frames sourced from the package com.wolfram.jlink, but also from various components related to Java reflection. Furthermore, it quietly unwraps InvocationTargetException. Almost all of the time, none of these missing stack frames will matter for stack trace diagnosis. But in the unusual circumstance that the component being debugged uses reflection above and beyond that used by JLink, then some useful frames might be missing.

But I Need Those Frames!!!

In the unlikely and unfortunate event that the missing frames are required for debugging, some possible strategies to recover them include:

  • Adjust the target Java code, or the Mathematica code that invokes it, to capture the full stack trace before JLink sees it, along the lines of what was done in one of the examples above.
  • Attach a remote debugging session to the Java process (e.g. using IDEA or Eclipse) and set a breakpoint at a convenient location.
  • Attach VisualVM to the Java process and try to get a thread stack dump at an opportune moment.
  • Build a new version of JLink.jar from the supplied source, with suitable modifications to the function createExceptionMessage (not recommended, almost certainly overkill).

These are likely ordered by increasing ambition. Details concerning any of these options are well out of scope for this forum.

$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.