Description

Problem: When classes are imported in Clojure, the class is loaded using Class.forName(), which causes its static initialisers to be executed. This differs from Java where compilation does not cause classes to be loaded.

Motivation: In many cases when those classes are normally loaded by Java code during execution of a framework of some kind (IntelliJ in my case, and RoboVM is another culprit mentioned in that thread) the initialiser expects some infrastructure to be in place and will fail when it's not. This means that it's impossible to AOT compile namespaces importing these classes, which is a fairly serious limitation.

Approach: Modify ImportExpr to call RT.classForNameNonLoading() instead of Class.forName(), which will load the class but not initialise it. This change causes the Clojure test suite to fail, since clojure.test-clojure.genclass imports a gen-class'ed class which no longer loads its namespace on initialisation. I'm not sure if this is considered an incorrect use of such a class (IIRC with records it's required to import the class and require its namespace), but given that it's in the Clojure test case it's reasonable to assume that this fix would be a breaking change for more code out there. This test failure is also corrected in the attached patch.

Patch: 0001-Don-t-initialize-classes-during-import.patch

Screened by: Alex Miller - I have tested many open source Clojure projects with this change (particularly seeking out large, complicated, or known users of genclass/deftype/etc) and have found no projects adversely impacted. I know that Cursive has been running with this modification for a long time with no known issues. I am ok with unconditionally enabling this change (re the comment below). The impact is described in more detail in the suggested changelog diff in the comments below.

Alternative: This patch enables the change unconditionally, but depending on the extent of breakage it causes, it might need to be enabled with a configuration flag. I propose we make it unconditional in an early 1.7 beta and monitor the fall-out.

Activity

This issue was originally reported by Zach Oakes and Colin Fleming and this patch was also tested by Colin.

I'm duplicating here my suggested release notes for this issue, which includes my current thoughts on potential breakage (it's also in the commit message of the patch):

"import" no longer causes the imported class to be initialized. This
change better matches Java's import behavior and allows the importing of
classes that do significant work at initialization time which may fail.
This semantics change is not expected to effect most code, but certain
code may have depended on behavior that is no longer true.
1) importing a Class defined via gen-class no longer causes its defining
namespace to be loaded, loading is now deferred until first reference. If
immediate loading of the namespace is needed, "require" it directly.
2) Some code may have depended on import to initialize the class before it
was used. It may now be necessary to manually call (Class/forName
"org.example.Class") when initialization is needed. In most cases, this
should not be necessary because the Class will be initialized
automatically before first use.

Alex Miller
added a comment - 29/Dec/13 12:23 PM From original post:
This issue was originally reported by Zach Oakes and Colin Fleming and this patch was also tested by Colin.
I'm duplicating here my suggested release notes for this issue, which includes my current thoughts on potential breakage (it's also in the commit message of the patch):

"import" no longer causes the imported class to be initialized. This
change better matches Java's import behavior and allows the importing of
classes that do significant work at initialization time which may fail.
This semantics change is not expected to effect most code, but certain
code may have depended on behavior that is no longer true.
1) importing a Class defined via gen-class no longer causes its defining
namespace to be loaded, loading is now deferred until first reference. If
immediate loading of the namespace is needed, "require" it directly.
2) Some code may have depended on import to initialize the class before it
was used. It may now be necessary to manually call (Class/forName
"org.example.Class") when initialization is needed. In most cases, this
should not be necessary because the Class will be initialized
automatically before first use.

I'm not sure if this should also be fixed, but it would be nice if you could emit the code for a proxy of one of these non-initialized classes without forcing initialization. For example, the following raises an exception (I'm using Java 8):

Greg Chapman
added a comment - 13/May/14 6:25 PM I'm not sure if this should also be fixed, but it would be nice if you could emit the code for a proxy of one of these non-initialized classes without forcing initialization. For example, the following raises an exception (I'm using Java 8):