diff --git a/end-to-end-tests/src/test/java/net/orfjackal/retrolambda/test/RequireNonNullTest.java b/end-to-end-tests/src/test/java/net/orfjackal/retrolambda/test/RequireNonNullTest.java new file mode 100644 index 00000000..faeda119 --- /dev/null +++ b/end-to-end-tests/src/test/java/net/orfjackal/retrolambda/test/RequireNonNullTest.java @@ -0,0 +1,95 @@ +// Copyright 2016 The Retrolambda Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package net.orfjackal.retrolambda.test; + +import com.google.common.base.*; +import com.google.common.io.ByteStreams; +import org.apache.commons.lang.SystemUtils; +import org.junit.Test; +import org.objectweb.asm.*; +import org.objectweb.asm.util.*; + +import java.io.*; +import java.util.Objects; + +import static org.junit.Assert.assertEquals; + +public class RequireNonNullTest { + + static class Foo { + Object test(Object x) { + return Objects.requireNonNull(x); + } + } + + @Test + public void throwsNPE() throws Exception { + byte[] bytes; + String path = Foo.class.getName().replace('.', '/') + ".class"; + try (InputStream is = getClass().getClassLoader().getResourceAsStream(path)) { + bytes = ByteStreams.toByteArray(is); + } + String actual = dumpMethod(bytes, "test"); + if (SystemUtils.isJavaVersionAtLeast(1.7f)) { + assertEquals( + "// access flags 0x0\n" + + "test(Ljava/lang/Object;)Ljava/lang/Object;\n" + + "ALOAD 1\n" + + "INVOKESTATIC java/util/Objects.requireNonNull (Ljava/lang/Object;)Ljava/lang/Object;\n" + + "ARETURN\n" + + "MAXSTACK = 1\n" + + "MAXLOCALS = 2", + actual); + } else { + assertEquals( + "// access flags 0x0\n" + + "test(Ljava/lang/Object;)Ljava/lang/Object;\n" + + "ALOAD 1\n" + + "DUP\n" + + "INVOKEVIRTUAL java/lang/Object.getClass ()Ljava/lang/Class;\n" + + "POP\n" + + "ARETURN\n" + + "MAXSTACK = 2\n" + + "MAXLOCALS = 2", + actual); + } + } + + static String dumpMethod(byte[] bytes, final String methodName) throws Exception { + Textifier textifier = new Textifier(); + StringWriter sw = new StringWriter(); + final ClassVisitor tcv = new TraceClassVisitor(null, textifier, new PrintWriter(sw, true)); + ClassVisitor cv = + new ClassVisitor(Opcodes.ASM5) { + @Override + public MethodVisitor visitMethod( + int access, + String name, + String desc, + String signature, + String[] exceptions) { + if (!name.equals(methodName)) { + return super.visitMethod(access, name, desc, signature, exceptions); + } + return tcv.visitMethod(access, name, desc, signature, exceptions); + } + }; + ClassReader cr = new ClassReader(bytes); + cr.accept(cv, ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES); + textifier.print(new PrintWriter(sw, true)); + return Joiner.on('\n') + .join(Splitter.on('\n').omitEmptyStrings().trimResults().split(sw.toString())); + } +} diff --git a/retrolambda/src/main/java/net/orfjackal/retrolambda/Transformers.java b/retrolambda/src/main/java/net/orfjackal/retrolambda/Transformers.java index 425b9931..a05100b6 100644 --- a/retrolambda/src/main/java/net/orfjackal/retrolambda/Transformers.java +++ b/retrolambda/src/main/java/net/orfjackal/retrolambda/Transformers.java @@ -6,6 +6,7 @@ import net.orfjackal.retrolambda.interfaces.*; import net.orfjackal.retrolambda.lambdas.*; +import net.orfjackal.retrolambda.requirenonnull.RequireNonNull; import net.orfjackal.retrolambda.trywithresources.SwallowSuppressedExceptions; import org.objectweb.asm.*; import org.objectweb.asm.tree.ClassNode; @@ -115,6 +116,7 @@ private byte[] transform(String className, Consumer reader, ClassV if (targetVersion < Opcodes.V1_7) { next = new SwallowSuppressedExceptions(next); next = new RemoveMethodHandlesLookupReferences(next); + next = new RequireNonNull(next); } next = new FixInvokeStaticOnInterfaceMethod(next); next = chain.wrap(next); diff --git a/retrolambda/src/main/java/net/orfjackal/retrolambda/requirenonnull/RequireNonNull.java b/retrolambda/src/main/java/net/orfjackal/retrolambda/requirenonnull/RequireNonNull.java new file mode 100644 index 00000000..b4de2b21 --- /dev/null +++ b/retrolambda/src/main/java/net/orfjackal/retrolambda/requirenonnull/RequireNonNull.java @@ -0,0 +1,57 @@ +// Copyright 2016 The Retrolambda Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package net.orfjackal.retrolambda.requirenonnull; + +import org.objectweb.asm.*; + +/** + * Rewrites calls to {@code Objects.requireNonNull}, which is only available in JDK 7 and above. + * + *

Starting in JDK 9, javac uses {@code requireNonNull} for synthetic null-checks + * (see JDK-8074306). + */ +public class RequireNonNull extends ClassVisitor { + + public RequireNonNull(ClassVisitor next) { + super(Opcodes.ASM5, next); + } + + @Override + public MethodVisitor visitMethod( + int access, String name, String desc, String signature, String[] exceptions) { + MethodVisitor next = super.visitMethod(access, name, desc, signature, exceptions); + return new MethodVisitor(Opcodes.ASM5, next) { + @Override + public void visitMethodInsn( + int opcode, String owner, String name, String desc, boolean itf) { + if (opcode == Opcodes.INVOKESTATIC + && owner.equals("java/util/Objects") + && name.equals("requireNonNull") + && desc.equals("(Ljava/lang/Object;)Ljava/lang/Object;")) { + super.visitInsn(Opcodes.DUP); + super.visitMethodInsn( + Opcodes.INVOKEVIRTUAL, + "java/lang/Object", + "getClass", + "()Ljava/lang/Class;", + false); + super.visitInsn(Opcodes.POP); + } else { + super.visitMethodInsn(opcode, owner, name, desc, itf); + } + } + }; + } +}