Getting the generic signature of a Java 8 lambda

Posted to StackOverflow

This is currently possible to solve but only in a pretty hackie way, but let me first explain a few things:

When you write a lambda, the compiler inserts a dynamic invoke instruction pointing to the LambdaMetafactory and a private static synthetic method with the body of the lambda. The synthetic method and the method handle in the constant pool both contain the generic type (if the lambda uses the type or is explicit as in your examples).

Now at runtime the LambdaMetaFactory is called and a class is generated using ASM that implements the functional interface and the body of the method then calls the private static method with any arguments passed. It is then injected into the original class using Unsafe.defineAnonymousClass
(see John Rose post) so it can access the private members etc.

Unfortunately the generated Class does not store the generic signatures (it could) so you can’t use the usual reflection methods that allow you to get around erasure

For a normal Class you could inspect the bytecode using Class.getResource(ClassName + “.class”)
but for anonymous classes defined using Unsafe you are out of luck. However you can make the LambdaMetaFactory
dump them out with the JVM argument:

java -Djdk.internal.lambda.dumpProxyClasses=/some/folder

By looking at the dumped class file (using javap -p -s -v), one can see that it does indeed call the static method. But the problem remains how to get the bytecode from within Java itself.

This unfortunately is where it gets hackie:

Using reflection we can call Class.getConstantPool and then access the MethodRefInfo to get the type descriptors. When can then use ASM to parse this and return the argument types. Putting it all together:

Method getConstantPool = Class.class.getDeclaredMethod("getConstantPool");
getConstantPool.setAccessible(true);
ConstantPool constantPool = (ConstantPool) getConstantPool.invoke(lambda.getClass());
String[] methodRefInfo = constantPool.getMemberRefInfoAt(constantPool.size() - 2);

int argumentIndex = 0;
String argumentType = jdk.internal.org.objectweb.asm.Type.getArgumentTypes(methodRef[2])[argumentIndex].getClassName();
Class<?> type = (Class<?>) Class.forName(argumentType);

Now ideally the classes generated by LambdaMetaFactory should store the generic type signatures (I might see if I can submit a patch to the OpenJDK) but currently this is the best we can do. The code above has the following problems:

  • It uses undocumented methods and classes
  • It is extremely vulnerable to code changes in the JDK
  • It doesn’t preserve the generic types, so if you pass List into a lambda it will come out as List

Where did I download that file from?

Assuming you used Chrome and a modern Linux file system…

$ attr -g xdg.origin.url Downloads/google-chrome-stable_current_x86_64.rpm
Attribute "xdg.origin.url" had a 74 byte value for /home/dan/Downloads/google-chrome-stable_current_x86_64.rpm:

https://dl.google.com/linux/direct/google-chrome-stable_current_x86_64.rpm

Compiling RunIt on Fedora 20

I had dowloaded RunIt. Then ran

package/compile

It errored with:

./compile runit.c
./load runit unix.a byte.a -static
/usr/bin/ld: cannot find -lc
collect2: error: ld returned 1 exit status
make: *** [runit] Error 1

To fix I needed to run

sudo yum install glibc-static

Bash function to switch Java versions

setjava() {
	if [ "$1" = "-q" ]; then
                local quiet=true
                shift
        fi

	local jdk=~/Applications/Java/jdk1.$1
        if [ ! -d "${jdk}" ]; then
                echo Jdk not found: ${jdk}
                return 1
        fi
	export JAVA_HOME=${jdk}
        export PATH=${JAVA_HOME}/bin:${PATH}
        if [ -z "${quiet}" ]; then
                java -version
        fi
}

export -f setjava

I have symlinks for all major versions of Java so that in IntelliJ and the command line I can upgrade minor Java versions just by changing the symlink:

$ ls -la ~/Applications/Java/
total 20
drwxrwxr-x. 5 dan dan 4096 Mar 27 10:12 .
drwxrwxr-x. 8 dan dan 4096 Mar 27 10:13 ..
lrwxrwxrwx. 1 dan dan   11 Mar 27 10:12 jdk1.6 -> jdk1.6.0_45
drwxr-xr-x. 8 dan dan 4096 Mar 26  2013 jdk1.6.0_45
lrwxrwxrwx. 1 dan dan   11 Mar 18 13:55 jdk1.7 -> jdk1.7.0_51
drwxr-xr-x. 8 dan dan 4096 Dec 19 03:24 jdk1.7.0_51
lrwxrwxrwx. 1 dan dan    8 Mar 27 10:12 jdk1.8 -> jdk1.8.0
drwxr-xr-x. 8 dan dan 4096 Mar  4 11:18 jdk1.8.0

Then I can switch version by either just saying the major version:

$ setjava 8
java version "1.8.0"
Java(TM) SE Runtime Environment (build 1.8.0-b132)
Java HotSpot(TM) 64-Bit Server VM (build 25.0-b70, mixed mode)

or I can be specific about minor version:

$ setjava 6.0_45
java version "1.6.0_45"
Java(TM) SE Runtime Environment (build 1.6.0_45-b06)
Java HotSpot(TM) 64-Bit Server VM (build 20.45-b01, mixed mode)

I’ve also started adding the following to my build and execution scripts so they can specify the Java version themselves:

type -t setjava > /dev/null && setjava -q 8 || if [ -n "${JAVA_HOME}" ]; then PATH=${JAVA_HOME}/bin:${PATH}; fi

Currently this is designed to be optional and will fallback to JAVA_HOME etc.

How to “login to windows for first time” or change password from Linux

smbpasswd -r YOUR.DOMAIN.COM -U YOUR_USERNAME

This will mean you don’t need to find a windows box every time your password expires if you only use say Outlook webmail in a windows environment.

How to list Active Directory controllers from Linux command line

nslookup -type=srv _ldap._tcp.dc._msdcs.YOUR.DOMAIN.COM

Error: Invalid or corrupt jarfile

I was creating a Jar via the Java API’s and I couldn’t get it to run my main class:

 $ java -jar foo.jar
Error: Invalid or corrupt jarfile foo.jar

Running it via the class path worked fine:

 $ java -cp foo.jar Bar
Hello world!

So now I knew it was something to do with the manifest file but it wasn’t being caused by

  • The 65535 file limit (See Zip64 and Java 7 ).
  • The 72 bytes limit per line
  • Missing newline at the end of the Main-Class (Displays “no main manifest attribute, in foo.jar”
  • A blank line before Main-Class (Displays “Error: An unexpected error occurred while trying to open file foo.jar”

So after scratching my head for a while I tried comparing a working jar with the failing jar:

$ unzip -lv foo.jar
Archive:  foo.jar
 Length   Method    Size  Cmpr    Date    Time   CRC-32   Name
--------  ------  ------- ---- ---------- ----- --------  ----
      75  Defl:N       75   0% 2014-02-17 16:01 b1eac370  META-INF/MANIFEST.MF
     825  Defl:N      464  44% 2014-02-17 16:01 942f187c  Working.class
$ unzip -lv foo.jar
Archive:  foo.jar
 Length   Method    Size  Cmpr    Date    Time   CRC-32   Name
--------  ------  ------- ---- ---------- ----- --------  ----
      75  Defl:N       75   0% 2014-02-17 16:01 b1eac370  /META-INF/MANIFEST.MF
     825  Defl:N      464  44% 2014-02-17 16:01 942f187c  Failing.class

So don’t prefix the META-INF folder with a slash! Also note it is case sensitive!

Shortcuts to position screens on ultra wide screens

left (Ctrl+Super+Left):

wmctrl -r :ACTIVE: -e 0,0,0,1280,1032

right (Ctrl+Super+Right):

wmctrl -r :ACTIVE: -e 0,1280,0,1280,1032

Git Config

[user]
        email = dan@bodar.com
        name = Daniel Worthington-Bodart
[color]
        ui = true
[alias]
        ci = commit
        co = checkout
        st = status -sb
        nuke = !git checkout -f && git clean -f -d
[push]
        default = simple

Ubuntu 13.10 Reset Panels

So after upgrading to 13.10 (clean install or upgrade) I seem randomly loose all the menus and icons on the top panels when using gnome session fallback.

Try this:

dconf reset -f /org/gnome/gnome-panel/