How to avoid "illegal reflective access" warning in Clojure interop. (Cast?) - clojure

Although I believe I have a solid beginner's understanding of Clojure, I am fairly at sea when interoperating with Java.
Thus I am struggling to fix some (unavoidable) interop code that generates JVM warnings. This is the code, it takes a W3C dom node and creates an LS serializer from it so I can spit out the corresponding XML string:
(defn serializer
[node]
(let [serializer (-> node
.getOwnerDocument
.getImplementation
.createLSSerializer)]
(.setParameter (.getDomConfig serializer) "xml-declaration" false)
serializer))
The code works fine and did not result in warnings until a recent system update.
But JVM (for openjdk 11.0.10) does not seem to like the call to .getOwnerDocument and it also does not like the call to .getImplementation as I discovered when I rewrote to avoid the call to .getOwnerDocument. The exact warning (arrows/linebreaks added):
WARNING: Illegal reflective access by clojure.lang.InjectedInvoker/0x00000008401e8040
(file:/home/user/.m2/repository/org/clojure/clojure/1.10.1/clojure-1.10.1.jar)
-->to method com.sun.org.apache.xerces.internal.dom.NodeImpl.getOwnerDocument()<--
WARNING: Please consider reporting this to the maintainers of clojure.lang.InjectedInvoker/0x00000008401e8040
WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations
WARNING: All illegal access operations will be denied in a future release
From this StackOverflow discussion it appears casting should be involved in the call to .getImplementation, like so:
DOMImplementationLS domImplementation = (DOMImplementationLS) doc.getImplementation();
LSSerializer lsSerializer = domImplementation.createLSSerializer();
This discussion seems to indicate that casting is not needed in Clojure and you can avoid reflection by type hinting:
(defn bar [^String x]
(.toCharArray x))
So I rewrote my function to use type hinting:
(defn serializer
[^com.sun.org.apache.xerces.internal.dom.NodeImpl node]
(let [serializer (-> node
.getOwnerDocument
.getImplementation
.createLSSerializer)]
(.setParameter (.getDomConfig serializer) "xml-declaration" false)
serializer))
This seems to get me past warnings on .getOwnerDocument and .getImplementation. But now it's warning me about the call to .createLSSerializer, which is a bit frustrating as I seem to be calling this last method exactly and on the same type of object as the well upvoted answer I quoted from this discussion above. This is the error I get with the type hinting (arrows/linebreaks added):
WARNING: Illegal reflective access by clojure.lang.InjectedInvoker/0x00000008401e8040
(file:/home/user/.m2/repository/org/clojure/clojure/1.10.1/clojure-1.10.1.jar)
-->to method com.sun.org.apache.xerces.internal.dom.CoreDOMImplementationImpl.createLSSerializer()<--
WARNING: Please consider reporting this to the maintainers of clojure.lang.InjectedInvoker/0x00000008401e8040
WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations
WARNING: All illegal access operations will be denied in a future release
If anyone has ideas on how to resolve this, or suggested areas for me to read up on in Java, I'd be very grateful. Thanks for any help.

Apparently, the answer was to provide an additional type hint, like so:
(defn serializer
[^com.sun.org.apache.xerces.internal.dom.NodeImpl node]
(let [serializer (-> node
.getOwnerDocument
^com.sun.org.apache.xerces.internal.dom.CoreDOMImplementationImpl
(.getImplementation)
.createLSSerializer)]
(.setParameter (.getDomConfig serializer) "xml-declaration" false)
serializer))
I discovered this thanks to a "related" question/answer Stackoverflow found and placed next to my question, specifically these. Apologies for not finding it earlier, perhaps this thread will provide some help if someone hits the same issue.

Related

How do I prevent a ClassNotFoundException when calling Java interop from Clojure in a JNI-created thread?

I have a project that's using Clojure + JNI in order to access some operating-system specific APIs. The native code in this process runs some of its work on background threads, and when doing so occasionally calls a Clojure function. I quickly learned the importance of calling javaVm->AttachCurrentThread on these background threads in order to be able to call the Clojure function in the first place, but now I'm hitting another roadblock.
It looks like any Clojure function that uses the clojure.lang.RT library fails to run in this setup, with an error that says Exception in thread "Thread-3" java.lang.ClassNotFoundException: clojure/lang/RT. A great example are the array functions (e.g. alength and friends), which are used to pass data back and forth between the native side and Clojure side.
Edit: I've put together a condensed reproducible example in this repository: clojure-jni-threading-example. It appears that the error is related to type inference; the following Clojure excerpt leads to the error:
(defn alength-wrapper [x]
(alength x))
(JNIClass/callFromJNI
(reify JNIFunctionClass
(callMe [this]
(let [c (byte-array 6)
(println "c" (alength-wrapper c))))))
The error can be prevented by added a type hint:
(defn alength-wrapper [x]
(alength ^bytes x))
I also tried to reproduce this error by creating a separate thread in Clojure when calling these functions (e.g. using future), but of course that works. This suggests there may be some initialization step that I'm missing when using JNI.
How can I prevent this error from happening so that these Clojure functions can be called from JNI background threads, without needing type hints?

Why doesn't the Clojure Compiler throw an error for an incorrect type hint?

Assumptions:
I get that type hints are about performance optimisations, and not type checking. I'm trying to work out when a performance optimisation is ineffective.
Suppose I have the following code:
(ns test.core)
(defrecord SquarePeg [width length])
(defrecord RoundHole [radius])
(def square-peg (SquarePeg. 5 50))
(defn insert-peg [^test.core.RoundHole peg]
(println "insert-peg inserted with: " peg))
(defn -main
"Insert a square peg in a round hole"
[& args]
(insert-peg square-peg))
When I run it I get:
insert-peg inserted with: #direct_linking_test.core.SquarePeg{:width 5, :length 50}
Now I expect this to have some kind of indication that the type hint was wrong, but it doesn't.
Now I'm taking a look at the Clojure Compiler code - and I'm seeing the following hint handling code:
hinted arg list
emit static value from hinted fields
get the class of the hinted field
if hinted match method
But I'm not seeing the bit where it handles the type hint failure.
My question is: Why doesn't the Clojure Compiler throw an error for an incorrect type hint?
Type hints mostly[1] only affect code that would otherwise use reflection -- i.e., interop code.
Since your insert-peg function doesn't do any interop, there's no use for the type hint and it's ignored.
Type errors happen when your type hint caused the clojure compiler to write bytecode for calling the method of one type, but at runtime the instance turns out to be another type.
[1] see exception in Alex's comment below

Clojure: Why a function should be `declare` if it is called before definition in the source code

In Clojure, if you call a function before its definition, e.g.
(foo (bar 'a))
(defn bar [] ...)
it is not compiled. One should add
(declare bar)
before (foo (bar 'a)). Why Clojure is designed as this? I mean, in most languages, except C/C++, such as Java, Python, PHP, Scala, Haskell or even other Lisps, especially in dynamic-type languages, function declaration is not needed, that is, function definition could be put either before or after a call. I feel it uncomfortable to use.
Clojure does a single-pass compilation (well I simplify, read the following links) :
https://news.ycombinator.com/item?id=2467359
https://news.ycombinator.com/item?id=2466912
So it seems logical that if you read the source only one time, from top to bottom you cannot have things like forward declaration and do it safely.
To quote Rich (first link) :
But, what should happen here, when the compiler has never before seen
bar?
`(defn foo [] (bar))`
or in CL:
`(defun foo () (bar))`
CL happily compiles it, and if bar is never defined, a runtime error will occur. Ok, but, what reified thing
(symbol) did it use for bar during compilation? The symbol it interned
when the form was read. So, what happens when you get the runtime
error and realize that bar is defined in another package you forgot to
import. You try to import other-package and, BAM!, another error -
conflict, other-package:bar conflicts with read-in-package:bar. Then
you go learn about uninterning.
In Clojure, the form doesn't compile,
you get a message, and no var is interned for bar. You require
other-namespace and continue.
I vastly prefer this experience, and so
made these tradeoffs. Many other benefits came about from using a
non-interning reader, and interning only on definition/declaration.
I'm not inclined to give them up, nor the benefits mentioned earlier,
in order to support circular reference.

How to validate refactoring in Clojure?

I found the following code in enlive:
net.cgrand.enlive-html=> (source node-selector?)
(defn node-selector? [selector]
(not (fragment-selector? selector)))
and thought about refactoring it to the following code:
(def node-selector? (complement fragment-selector?))
How can I validate the completeness of the refactoring so all cases are properly handled with the change?
cough tests cough and thinking hard.
You have to ensure that the arguments and results domains haven't changed (special care when dealing with truthy values).
In this case the change seems innocuous BUT you lose something: doc doesn't document the arglist anymore.
Another subtle consequence: when you redefine fragment-selector?, node-selector? still refers to the old fragment-selector?.

General syntax of multimethods

I apologize if the question is trivial, but some googling is not leading me anywhere. What is the general syntax of defmulti and defmethod? I can write simple multimethods, but I am not sure where I can put the docstring, pre and post conditions, metadata and so on.
I am actually interested in ClojureScript more than in Clojure, so if there are differences between the two, please tell me.
In a REPL you can use the doc function to get the function arguments and (most of the time) an explanation of the options. As for ClojureScript, these two functions are macros, which means they are expanded at compile time and should behave exactly as they do in regular Clojure. That is, as long as ClojureScript can handle the code the macro generates.
user=> (doc defmulti)
-------------------------
clojure.core/defmulti
([name docstring? attr-map? dispatch-fn & options])
Macro
Creates a new multimethod with the associated dispatch function.
The docstring and attribute-map are optional.
Options are key-value pairs and may be one of:
:default the default dispatch value, defaults to :default
:hierarchy the isa? hierarchy to use for dispatching
defaults to the global hierarchy
nil
user=> (doc defmethod)
-------------------------
clojure.core/defmethod
([multifn dispatch-val & fn-tail])
Macro
Creates and installs a new method of multimethod associated with dispatch-value.
nil
At Clojuredocs: defmulti, defmethod.
If you don't find the examples there detailed enough, you might consider adding your own (once you've gotten all your questions answered).