IDEA-40780 When code is compiled with -g, use the argument name for the @NotNull error.

This commit is contained in:
peter
2013-10-17 21:54:34 +02:00
parent 99e97f3207
commit 293e4c55a7
6 changed files with 99 additions and 19 deletions
@@ -925,9 +925,7 @@ public class BackendCompilerWrapper {
ClassReader reader = new ClassReader(fileContent, 0, fileContent.length);
ClassWriter writer = new PsiClassWriter(myProject, myIsJdk16);
final NotNullVerifyingInstrumenter instrumenter = new NotNullVerifyingInstrumenter(writer);
reader.accept(instrumenter, 0);
if (instrumenter.isModification()) {
if (NotNullVerifyingInstrumenter.processClassFile(reader, writer)) {
fileObject = new FileObject(file, writer.toByteArray());
}
}
@@ -18,7 +18,9 @@ package com.intellij.compiler.notNullVerification;
import org.jetbrains.asm4.*;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
* @author ven
@@ -37,16 +39,55 @@ public class NotNullVerifyingInstrumenter extends ClassVisitor implements Opcode
private static final String ANNOTATION_DEFAULT_METHOD = "value";
private static final String NULL_ARG_MESSAGE = "Argument %s for @NotNull parameter of %s.%s must not be null";
private static final String NULL_ARG_MESSAGE_INDEXED = "Argument %s for @NotNull parameter of %s.%s must not be null";
private static final String NULL_ARG_MESSAGE_NAMED = "Argument for @NotNull parameter '%s' of %s.%s must not be null";
private static final String NULL_RESULT_MESSAGE = "@NotNull method %s.%s must not return null";
@SuppressWarnings("SSBasedInspection") private static final String[] EMPTY_STRING_ARRAY = new String[0];
private final Map<String, Map<Integer, String>> myMethodParamNames;
private String myClassName;
private boolean myIsModification = false;
private RuntimeException myPostponedError;
public NotNullVerifyingInstrumenter(final ClassVisitor classVisitor) {
private NotNullVerifyingInstrumenter(final ClassVisitor classVisitor, ClassReader reader) {
super(Opcodes.ASM4, classVisitor);
myMethodParamNames = getAllParameterNames(reader);
}
public static boolean processClassFile(final ClassReader reader, final ClassVisitor writer) {
final NotNullVerifyingInstrumenter instrumenter = new NotNullVerifyingInstrumenter(writer, reader);
reader.accept(instrumenter, 0);
return instrumenter.isModification();
}
private static Map<String, Map<Integer, String>> getAllParameterNames(ClassReader reader) {
final Map<String, Map<Integer, String>> methodParamNames = new LinkedHashMap<String, Map<Integer, String>>();
reader.accept(new ClassVisitor(Opcodes.ASM4) {
private String myClassName = null;
public void visit(final int version, final int access, final String name, final String signature, final String superName, final String[] interfaces) {
myClassName = name;
}
public MethodVisitor visitMethod(final int access, final String name, final String desc, final String signature, final String[] exceptions) {
final String methodName = myClassName + '.' + name + desc;
final Map<Integer, String> names = new LinkedHashMap<Integer, String>();
final Type[] args = Type.getArgumentTypes(desc);
methodParamNames.put(methodName, names);
return new MethodVisitor(api) {
@Override
public void visitLocalVariable(String name2, String desc, String signature, Label start, Label end, int index) {
int parameterIndex = getParameterIndex(index, access, args);
if (parameterIndex >= 0) {
names.put(parameterIndex, name2);
}
}
};
}
}, 0);
return methodParamNames;
}
public boolean isModification() {
@@ -64,6 +105,7 @@ public class NotNullVerifyingInstrumenter extends ClassVisitor implements Opcode
final Type[] args = Type.getArgumentTypes(desc);
final Type returnType = Type.getReturnType(desc);
final MethodVisitor v = cv.visitMethod(access, name, desc, signature, exceptions);
final Map<Integer, String> paramNames = myMethodParamNames.get(myClassName + '.' + name + desc);
return new MethodVisitor(Opcodes.ASM4, v) {
private final List<Integer> myNotNullParams = new ArrayList<Integer>();
@@ -135,16 +177,20 @@ public class NotNullVerifyingInstrumenter extends ClassVisitor implements Opcode
Label end = new Label();
mv.visitJumpInsn(IFNONNULL, end);
String descrPattern = myMessage != null ? myMessage : NULL_ARG_MESSAGE;
String[] args = myMessage != null ? EMPTY_STRING_ARRAY : new String[]{String.valueOf(param - mySyntheticCount), myClassName, name};
String paramName = paramNames == null ? null : paramNames.get(param);
String descrPattern = myMessage != null
? myMessage
: paramName != null ? NULL_ARG_MESSAGE_NAMED : NULL_ARG_MESSAGE_INDEXED;
String[] args = myMessage != null
? EMPTY_STRING_ARRAY
: new String[]{paramName != null ? paramName : String.valueOf(param - mySyntheticCount), myClassName, name};
generateThrow(IAE_CLASS_NAME, end, descrPattern, args);
}
}
@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;
final boolean isParameter = getParameterIndex(index, access, args) >= 0;
final Label label = (isParameter && myStartGeneratedCodeLabel != null) ? myStartGeneratedCodeLabel : start;
mv.visitLocalVariable(name, desc, signature, label, end, index);
}
@@ -205,6 +251,15 @@ public class NotNullVerifyingInstrumenter extends ClassVisitor implements Opcode
};
}
private static int getParameterIndex(int localVarIndex, int methodAccess, Type[] paramTypes) {
final boolean isStatic = (methodAccess & ACC_STATIC) != 0;
int parameterIndex = isStatic ? localVarIndex : localVarIndex - 1;
if (parameterIndex >= paramTypes.length) {
parameterIndex = -1;
}
return parameterIndex;
}
private static boolean isReferenceType(final Type type) {
return type.getSort() == Type.OBJECT || type.getSort() == Type.ARRAY;
}
@@ -230,3 +285,4 @@ public class NotNullVerifyingInstrumenter extends ClassVisitor implements Opcode
}
}
}
@@ -429,9 +429,7 @@ public class Javac2 extends Javac {
if (version >= Opcodes.V1_5) {
ClassWriter writer = new InstrumenterClassWriter(getAsmClassWriterFlags(version), finder);
final NotNullVerifyingInstrumenter instrumenter = new NotNullVerifyingInstrumenter(writer);
reader.accept(instrumenter, 0);
if (instrumenter.isModification()) {
if (NotNullVerifyingInstrumenter.processClassFile(reader, writer)) {
final FileOutputStream fileOutputStream = new FileOutputStream(path);
try {
fileOutputStream.write(writer.toByteArray());
@@ -0,0 +1,7 @@
import org.jetbrains.annotations.NotNull;
public class UseParameterNames {
public UseParameterNames(Object obj, @NotNull Object obj2) { }
public void instanceMethod(@NotNull Object x) {}
public static void staticMethod(@NotNull Object y) {}
}
@@ -24,6 +24,8 @@ import com.intellij.testFramework.UsefulTestCase;
import com.intellij.testFramework.fixtures.IdeaProjectTestFixture;
import com.intellij.testFramework.fixtures.JavaTestFixtureFactory;
import com.intellij.testFramework.fixtures.TestFixtureBuilder;
import com.intellij.util.ArrayUtil;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.asm4.ClassReader;
import org.jetbrains.asm4.ClassWriter;
@@ -34,6 +36,7 @@ import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.util.List;
/**
* @author yole
@@ -110,6 +113,19 @@ public class NotNullVerifyingInstrumenterTest extends UsefulTestCase {
verifyCallThrowsException("ConstructorParam.ConstructorParam.o cant be null", null, method, (Object)null);
}
public void testUseParameterNames() throws Exception {
Class<?> testClass = prepareTest(true);
Constructor constructor = testClass.getConstructor(Object.class, Object.class);
verifyCallThrowsException("Argument for @NotNull parameter 'obj2' of UseParameterNames.<init> must not be null", null, constructor, null, null);
Method staticMethod = testClass.getMethod("staticMethod", Object.class);
verifyCallThrowsException("Argument for @NotNull parameter 'y' of UseParameterNames.staticMethod must not be null", null, staticMethod, (Object)null);
Object instance = constructor.newInstance("", "");
Method instanceMethod = testClass.getMethod("instanceMethod", Object.class);
verifyCallThrowsException("Argument for @NotNull parameter 'x' of UseParameterNames.instanceMethod must not be null", instance, instanceMethod, (Object)null);
}
public void testEnumConstructor() throws Exception {
Class testClass = prepareTest();
Object field = testClass.getField("Value");
@@ -152,6 +168,10 @@ public class NotNullVerifyingInstrumenterTest extends UsefulTestCase {
}
private Class prepareTest() throws IOException {
return prepareTest(false);
}
private Class prepareTest(boolean withDebugInfo) throws IOException {
String base = JavaTestUtil.getJavaTestDataPath() + "/compiler/notNullVerification/";
final String baseClassName = getTestName(false);
String path = base + baseClassName;
@@ -159,7 +179,12 @@ public class NotNullVerifyingInstrumenterTest extends UsefulTestCase {
File classesDir = FileUtil.createTempDirectory(baseClassName, "output");
try {
com.sun.tools.javac.Main.compile(new String[]{"-classpath", base + "annotations.jar", "-d", classesDir.getAbsolutePath(), javaPath});
List<String> cmdLine = ContainerUtil.newArrayList("-classpath", base + "annotations.jar", "-d", classesDir.getAbsolutePath());
if (withDebugInfo) {
cmdLine.add("-g");
}
cmdLine.add(javaPath);
com.sun.tools.javac.Main.compile(ArrayUtil.toStringArray(cmdLine));
Class mainClass = null;
final File[] files = classesDir.listFiles();
@@ -172,9 +197,7 @@ public class NotNullVerifyingInstrumenterTest extends UsefulTestCase {
ClassReader reader = new ClassReader(content, 0, content.length);
ClassWriter writer = new PsiClassWriter(myFixture.getProject(), myJava6);
final NotNullVerifyingInstrumenter instrumenter = new NotNullVerifyingInstrumenter(writer);
reader.accept(instrumenter, 0);
modified |= instrumenter.isModification();
modified |= NotNullVerifyingInstrumenter.processClassFile(reader, writer);
byte[] instrumented = writer.toByteArray();
final String className = FileUtil.getNameWithoutExtension(fileName);
@@ -73,9 +73,7 @@ public class NotNullInstrumentingBuilder extends BaseInstrumentingBuilder{
ClassWriter writer,
InstrumentationClassFinder finder) {
try {
final NotNullVerifyingInstrumenter instrumenter = new NotNullVerifyingInstrumenter(writer);
reader.accept(instrumenter, 0);
if (instrumenter.isModification()) {
if (NotNullVerifyingInstrumenter.processClassFile(reader, writer)) {
return new BinaryContent(writer.toByteArray());
}
}