Instrumenter cleanup + couple more tests

This commit is contained in:
Roman Shevchenko
2012-09-24 19:06:40 +02:00
parent dc9f14427e
commit eed7f584d3
4 changed files with 77 additions and 40 deletions
@@ -1,5 +1,5 @@
/*
* Copyright 2000-2009 JetBrains s.r.o.
* Copyright 2000-2012 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,19 +18,27 @@ package com.intellij.compiler.notNullVerification;
import org.jetbrains.asm4.*;
import java.util.ArrayList;
import java.util.List;
/**
* @author ven
* @noinspection HardCodedStringLiteral
*/
public class NotNullVerifyingInstrumenter extends ClassVisitor implements Opcodes {
private boolean myIsModification = false;
private String myClassName;
public static final String NOT_NULL = "org/jetbrains/annotations/NotNull";
public static final String NOT_NULL_ANNO = "L"+ NOT_NULL + ";";
public static final String IAE_CLASS_NAME = "java/lang/IllegalArgumentException";
public static final String ISE_CLASS_NAME = "java/lang/IllegalStateException";
private static final String NOT_NULL_CLASS_NAME = "org/jetbrains/annotations/NotNull";
private static final String NOT_NULL_TYPE = "L"+ NOT_NULL_CLASS_NAME + ";";
private static final String SYNTHETIC_CLASS_NAME = "java/lang/Synthetic";
private static final String SYNTHETIC_TYPE = "L" + SYNTHETIC_CLASS_NAME + ";";
private static final String IAE_CLASS_NAME = "java/lang/IllegalArgumentException";
private static final String ISE_CLASS_NAME = "java/lang/IllegalStateException";
private static final String STRING_CLASS_NAME = "java/lang/String";
private static final String CONSTRUCTOR_NAME = "<init>";
private static final String EXCEPTION_INIT_SIGNATURE = "(L" + STRING_CLASS_NAME + ";)V";
private static final String NULL_ARG_MESSAGE = "Argument %d for @NotNull parameter of %s.%s must not be null";
private static final String NULL_RESULT_MESSAGE = "@NotNull method %s.%s must not return null";
private String myClassName;
private boolean myIsModification = false;
public NotNullVerifyingInstrumenter(final ClassVisitor classVisitor) {
super(Opcodes.ASM4, classVisitor);
@@ -40,27 +48,22 @@ public class NotNullVerifyingInstrumenter extends ClassVisitor implements Opcode
return myIsModification;
}
public void visit(final int version,
final int access,
final String name,
final String signature,
final String superName,
final String[] interfaces) {
@Override
public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
super.visit(version, access, name, signature, superName, interfaces);
myClassName = name;
}
public MethodVisitor visitMethod(final int access, final String name, final String desc, final String signature, final String[] exceptions) {
@Override
public MethodVisitor visitMethod(final int access, final String name, String desc, String signature, String[] exceptions) {
final Type[] args = Type.getArgumentTypes(desc);
final Type returnType = Type.getReturnType(desc);
MethodVisitor v = cv.visitMethod(access, name, desc, signature, exceptions);
final MethodVisitor v = cv.visitMethod(access, name, desc, signature, exceptions);
return new MethodVisitor(Opcodes.ASM4, v) {
private final ArrayList myNotNullParams = new ArrayList();
private final List<Integer> myNotNullParams = new ArrayList<Integer>();
private int mySyntheticCount = 0;
private boolean myIsNotNull = false;
//private boolean myIsUnmodifiable = false;
//public Label myWrapLabel;
private Label myStartGeneratedCodeLabel;
public AnnotationVisitor visitParameterAnnotation(
@@ -69,35 +72,35 @@ public class NotNullVerifyingInstrumenter extends ClassVisitor implements Opcode
final boolean visible) {
AnnotationVisitor av;
av = mv.visitParameterAnnotation(parameter, anno, visible);
if (isReferenceType(args[parameter]) && anno.equals(NOT_NULL_ANNO)) {
if (isReferenceType(args[parameter]) && anno.equals(NOT_NULL_TYPE)) {
myNotNullParams.add(new Integer(parameter));
}
else if (anno.equals("Ljava/lang/Synthetic;")) {
// See asm r1278 for what we do this,
// http://forge.objectweb.org/tracker/index.php?func=detail&aid=307392&group_id=23&atid=100023
else if (anno.equals(SYNTHETIC_TYPE)) {
// see http://forge.ow2.org/tracker/?aid=307392&group_id=23&atid=100023&func=detail
mySyntheticCount++;
}
return av;
}
@Override
public AnnotationVisitor visitAnnotation(String anno, boolean isRuntime) {
final AnnotationVisitor av = mv.visitAnnotation(anno, isRuntime);
if (isReferenceType(returnType) &&
anno.equals(NOT_NULL_ANNO)) {
anno.equals(NOT_NULL_TYPE)) {
myIsNotNull = true;
}
return av;
}
@Override
public void visitCode() {
if (myNotNullParams.size() > 0) {
myStartGeneratedCodeLabel = new Label();
mv.visitLabel(myStartGeneratedCodeLabel);
}
for (int p = 0; p < myNotNullParams.size(); ++p) {
for (Integer param : myNotNullParams) {
int var = ((access & ACC_STATIC) == 0) ? 1 : 0;
int param = ((Integer)myNotNullParams.get(p)).intValue();
for (int i = 0; i < param; ++i) {
var += args[i].getSize();
}
@@ -106,26 +109,26 @@ public class NotNullVerifyingInstrumenter extends ClassVisitor implements Opcode
Label end = new Label();
mv.visitJumpInsn(IFNONNULL, end);
generateThrow(IAE_CLASS_NAME, "Argument " + (param - mySyntheticCount) + " for @NotNull parameter of " + myClassName + "." + name + " must not be null", end);
generateThrow(IAE_CLASS_NAME, String.format(NULL_ARG_MESSAGE, param - mySyntheticCount, myClassName, name), end);
}
}
public void visitLocalVariable(final String name, final String desc, final String signature, final Label start, final Label end,
final int index) {
@Override
public void visitLocalVariable(String name, String desc, String signature, Label start, Label end, int index) {
final boolean isStatic = (access & ACC_STATIC) != 0;
final boolean isParameter = isStatic ? index < args.length : index <= args.length;
mv.visitLocalVariable(name, desc, signature, (isParameter && myStartGeneratedCodeLabel != null) ? myStartGeneratedCodeLabel : start, end, index);
final Label label = (isParameter && myStartGeneratedCodeLabel != null) ? myStartGeneratedCodeLabel : start;
mv.visitLocalVariable(name, desc, signature, label, end, index);
}
@Override
public void visitInsn(int opcode) {
if (opcode == ARETURN) {
if (myIsNotNull) {
mv.visitInsn(DUP);
/*generateConditionalThrow("@NotNull method " + myClassName + "." + name + " must not return null",
"java/lang/IllegalStateException");*/
final Label skipLabel = new Label();
mv.visitJumpInsn(IFNONNULL, skipLabel);
generateThrow(ISE_CLASS_NAME, "@NotNull method " + myClassName + "." + name + " must not return null", skipLabel);
generateThrow(ISE_CLASS_NAME, String.format(NULL_RESULT_MESSAGE, myClassName, name), skipLabel);
}
}
@@ -133,23 +136,23 @@ public class NotNullVerifyingInstrumenter extends ClassVisitor implements Opcode
}
private void generateThrow(final String exceptionClass, final String descr, final Label end) {
String exceptionParamClass = "(Ljava/lang/String;)V";
mv.visitTypeInsn(NEW, exceptionClass);
mv.visitInsn(DUP);
mv.visitLdcInsn(descr);
mv.visitMethodInsn(INVOKESPECIAL, exceptionClass, CONSTRUCTOR_NAME, exceptionParamClass);
mv.visitMethodInsn(INVOKESPECIAL, exceptionClass, CONSTRUCTOR_NAME, EXCEPTION_INIT_SIGNATURE);
mv.visitInsn(ATHROW);
mv.visitLabel(end);
myIsModification = true;
}
@Override
public void visitMaxs(final int maxStack, final int maxLocals) {
try {
super.visitMaxs(maxStack, maxLocals);
}
catch (ArrayIndexOutOfBoundsException e) {
throw new ArrayIndexOutOfBoundsException("maxs processing failed for method " + name + ": " + e.getMessage());
throw new ArrayIndexOutOfBoundsException("Maximums processing failed for " + myClassName + "." + name + ": " + e.getMessage());
}
}
};
@@ -0,0 +1,6 @@
import org.jetbrains.annotations.NotNull;
public class ConstructorParam {
public ConstructorParam(@NotNull Object o) {
}
}
@@ -0,0 +1,6 @@
import org.jetbrains.annotations.NotNull;
public class SimpleParam {
public void test(@NotNull Object o) {
}
}
@@ -24,19 +24,22 @@ import com.intellij.testFramework.UsefulTestCase;
import com.intellij.testFramework.fixtures.IdeaProjectTestFixture;
import com.intellij.testFramework.fixtures.JavaTestFixtureFactory;
import com.intellij.testFramework.fixtures.TestFixtureBuilder;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.asm4.ClassReader;
import org.jetbrains.asm4.ClassWriter;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
/**
* @author yole
*/
public class NotNullVerifyingInstrumenterTest extends UsefulTestCase {
private final boolean myJava6 = SystemInfo.isJavaVersionAtLeast("1.6");
private boolean myJava6;
private IdeaProjectTestFixture myFixture;
@SuppressWarnings({"JUnitTestCaseWithNonTrivialConstructors"})
@@ -51,6 +54,7 @@ public class NotNullVerifyingInstrumenterTest extends UsefulTestCase {
final TestFixtureBuilder<IdeaProjectTestFixture> testFixtureBuilder = fixtureFactory.createLightFixtureBuilder();
myFixture = testFixtureBuilder.getFixture();
myFixture.setUp();
myJava6 = SystemInfo.isJavaVersionAtLeast("1.6");
}
@Override
@@ -73,6 +77,19 @@ public class NotNullVerifyingInstrumenterTest extends UsefulTestCase {
verifyCallThrowsException("@NotNull method MultipleReturns.test must not return null", instance, method, 1);
}
public void testSimpleParam() throws Exception {
Class testClass = prepareTest();
Object instance = testClass.newInstance();
Method method = testClass.getMethod("test", Object.class);
verifyCallThrowsException("Argument 0 for @NotNull parameter of SimpleParam.test must not be null", instance, method, (Object)null);
}
public void testConstructorParam() throws Exception {
Class testClass = prepareTest();
Constructor method = testClass.getConstructor(Object.class);
verifyCallThrowsException("Argument 0 for @NotNull parameter of ConstructorParam.<init> must not be null", null, method, (Object)null);
}
public void testEnumConstructor() throws Exception {
Class testClass = prepareTest();
Object field = testClass.getField("Value");
@@ -95,14 +112,19 @@ public class NotNullVerifyingInstrumenterTest extends UsefulTestCase {
assertNotNull(aClass.newInstance());
}
private static void verifyCallThrowsException(final String expectedError, final Object instance, final Method method, final Object... args) throws IllegalAccessException {
private static void verifyCallThrowsException(String expectedError, @Nullable Object instance, Member member, @Nullable Object... args) throws Exception {
String exceptionText = null;
try {
method.invoke(instance, args);
if (member instanceof Constructor) {
((Constructor)member).newInstance(args);
}
else {
((Method)member).invoke(instance, args);
}
}
catch(InvocationTargetException ex) {
Throwable cause = ex.getCause();
if (cause instanceof IllegalStateException) {
if (cause instanceof IllegalStateException || cause instanceof IllegalArgumentException) {
exceptionText = cause.getMessage();
}
}