Clojure (let [frame (java.awt.Frame.)]) within un-invoked function causes AWT to be started

StackOverflow https://stackoverflow.com/questions/21803419

  •  12-10-2022
  •  | 
  •  

Question

Whilst going through the Joy of Clojure book, I've succeedding in defining a function that, when invoked, will create and draw on a java.awt.Frame.

(defn draw-frame [f x y]
  (let [frame (java.awt.Frame.)]))

However, simply defining the function in a new Leiningen REPL in a new empty Leiningen project causes the AWT framework to start. I say this, as entering the above function definition causes a new OS X 'window' to open with a 'main' menu option. If I close this window, the clojure REPL exits.

Otherwise, the function continues to behave as expected, but I'm keen to understand why this happens - creating a similar function in Java (referencing but not instantiating a java.awt.Frame) does not do exhibit the same symptoms.

Was it helpful?

Solution

Doing a bit of spelunking through rt.jar, I was able to track it down a bit.

The static initializer[1] for java.awt.Frame looks like:

 0: iconst_0
 1: putstatic     #20  // Field nameCounter:I
 4: invokestatic  #114 // Method java/awt/Toolkit.loadLibraries:()V
 7: invokestatic  #115 // Method java/awt/GraphicsEnvironment.isHeadless:()Z
10: ifne          16
13: invokestatic  #116 // Method initIDs:()V
16: new           #117 // class java/awt/Frame$1
19: dup
20: invokespecial #118 // Method java/awt/Frame$1."<init>":()V
23: invokestatic  #119 // Method sun/awt/AWTAccessor.setFrameAccessor:(Lsun/awt/AWTAccessor$FrameAccessor;)V
26: return

I was able to reproduce the behavior by executing java.awt.Toolkit at the REPL, so I dug into that static initializer too:

 0: ldc_w         #150 // class java/awt/Toolkit
 3: invokevirtual #151 // Method java/lang/Class.desiredAssertionStatus:()Z
 6: ifne          13
 9: iconst_1
10: goto          14
13: iconst_0
14: putstatic     #126 // Field $assertionsDisabled:Z
17: iconst_0
18: putstatic     #84  // Field loaded:Z
21: new           #152 // class java/awt/Toolkit$3
24: dup
25: invokespecial #153 // Method java/awt/Toolkit$3."<init>":()V
28: invokestatic  #32  // Method java/security/AccessController.doPrivileged:(Ljava/security/PrivilegedAction;)Ljava/lang/Object;
31: pop
32: invokestatic  #154 // Method loadLibraries:()V
35: invokestatic  #155 // Method initAssistiveTechnologies:()V
38: invokestatic  #156 // Method java/awt/GraphicsEnvironment.isHeadless:()Z
41: ifne          47
44: invokestatic  #157 // Method initIDs:()V
47: return

loadLibraries seemed interesting, so that disassembly looks like:

 0: getstatic     #84 // Field loaded:Z
 3: ifne          23
 6: new           #85 // class sun/security/action/LoadLibraryAction
 9: dup
10: ldc           #86 // String awt
12: invokespecial #87 // Method sun/security/action/LoadLibraryAction."<init>":(Ljava/lang/String;)V
15: invokestatic  #32 // Method java/security/AccessController.doPrivileged:(Ljava/security/PrivilegedAction;)Ljava/lang/Object;
18: pop
19: iconst_1
20: putstatic     #84 // Field loaded:Z
23: return

Rewriting the core bit of that in Clojure looks like:

(doto (sun.security.action.LoadLibraryAction. "awt")
  (java.security.AccessController/doPrivileged))

Running this causes the same application to pop up, so my suspicion is that the native library has initializer code that causes it. Unfortunately, I don't feel like diving into real disassembly, so someone else will have to figure it out from that point!

[1]: I unpacked rt.jar and then used javap -p -c Foo.class to see this.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top