mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
IDEA-40780 When code is compiled with -g, use the argument name for the @NotNull error.
This commit is contained in:
+1
-3
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
+62
-6
@@ -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) {}
|
||||
}
|
||||
+27
-4
@@ -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);
|
||||
|
||||
+1
-3
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user