IDEA-175840 @NotNull generated code: wrong parameter is checked for null-value

This commit is contained in:
peter
2017-08-14 16:11:54 +02:00
parent 0aa2d4abc7
commit 879e9ed76d
3 changed files with 85 additions and 11 deletions
@@ -45,6 +45,8 @@ public class NotNullVerifyingInstrumenter extends ClassVisitor implements Opcode
private RuntimeException myPostponedError;
private final AuxiliaryMethodGenerator myAuxGenerator;
private final Set<String> myNotNullAnnos = new HashSet<String>();
private boolean myStatic;
private boolean myInner;
private NotNullVerifyingInstrumenter(final ClassVisitor classVisitor, ClassReader reader, String[] notNullAnnotations) {
super(Opcodes.API_VERSION, classVisitor);
@@ -78,10 +80,8 @@ public class NotNullVerifyingInstrumenter extends ClassVisitor implements Opcode
final Type[] args = Type.getArgumentTypes(desc);
methodParamNames.put(methodName, names);
final boolean isStatic = (access & ACC_STATIC) != 0;
final Map<Integer, Integer> paramSlots = new LinkedHashMap<Integer, Integer>(); // map: localVariableSlot -> methodParameterIndex
int slotIndex = isStatic? 0 : 1;
int slotIndex = isStatic(access) ? 0 : 1;
for (int paramIndex = 0; paramIndex < args.length; paramIndex++) {
final Type arg = args[paramIndex];
paramSlots.put(slotIndex, paramIndex);
@@ -111,6 +111,12 @@ public class NotNullVerifyingInstrumenter extends ClassVisitor implements Opcode
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;
myStatic = isStatic(access);
}
@Override
public void visitOuterClass(String owner, String name, String desc) {
myInner = true;
}
private static class NotNullState {
@@ -152,8 +158,13 @@ public class NotNullVerifyingInstrumenter extends ClassVisitor implements Opcode
final Type[] args = Type.getArgumentTypes(desc);
boolean hasOuterClassParameter = myInner && !myStatic && "<init>".equals(name);
// see http://forge.ow2.org/tracker/?aid=307392&group_id=23&atid=100023&func=detail
final int syntheticCount = signature == null ? 0 : Math.max(0, args.length - getSignatureParameterCount(signature));
final int syntheticCount = signature == null ? 0 : hasOuterClassParameter ? 1 : Math.max(0, args.length - getSignatureParameterCount(signature));
// workaround for ASM's workaround for javac bug: http://forge.ow2.org/tracker/?func=detail&aid=317788&group_id=23&atid=100023
final int paramAnnotationOffset = hasOuterClassParameter ? 1 : 0;
final Type returnType = Type.getReturnType(desc);
final MethodVisitor v = cv.visitMethod(access, name, desc, signature, exceptions);
@@ -194,7 +205,10 @@ public class NotNullVerifyingInstrumenter extends ClassVisitor implements Opcode
}
public AnnotationVisitor visitParameterAnnotation(final int parameter, final String anno, final boolean visible) {
return checkNotNullParameter(parameter, anno, mv.visitParameterAnnotation(parameter, anno, visible));
AnnotationVisitor base = mv.visitParameterAnnotation(parameter, anno, visible);
if (parameter < paramAnnotationOffset) return base;
return checkNotNullParameter(parameter - paramAnnotationOffset, anno, base);
}
private AnnotationVisitor checkNotNullParameter(int parameter, String anno, AnnotationVisitor av) {
@@ -249,9 +263,8 @@ public class NotNullVerifyingInstrumenter extends ClassVisitor implements Opcode
@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 isParameterOrThisRef = isStatic ? index < args.length : index <= args.length;
final Label label = (isParameterOrThisRef && myStartGeneratedCodeLabel != null) ? myStartGeneratedCodeLabel : start;
boolean isParameterOrThisRef = isStatic(access) ? index < args.length : index <= args.length;
Label label = (isParameterOrThisRef && myStartGeneratedCodeLabel != null) ? myStartGeneratedCodeLabel : start;
mv.visitLocalVariable(name, desc, signature, label, end, index);
}
@@ -293,6 +306,10 @@ public class NotNullVerifyingInstrumenter extends ClassVisitor implements Opcode
};
}
private static boolean isStatic(int access) {
return (access & ACC_STATIC) != 0;
}
private static int getSignatureParameterCount(String signature) {
final int[] count = {0};
new SignatureReader(signature).accept(new SignatureVisitor(Opcodes.ASM6) {
@@ -309,7 +326,7 @@ public class NotNullVerifyingInstrumenter extends ClassVisitor implements Opcode
return type.getSort() == Type.OBJECT || type.getSort() == Type.ARRAY;
}
private void registerError(String methodName, String operationName, Throwable e) {
private void registerError(String methodName, @SuppressWarnings("SameParameterValue") String operationName, Throwable e) {
if (myPostponedError == null) {
// throw the first error that occurred
Throwable err = e.getCause();
@@ -343,3 +360,4 @@ public class NotNullVerifyingInstrumenter extends ClassVisitor implements Opcode
}
}
@@ -0,0 +1,47 @@
import java.lang.annotation.*;
@Target({ElementType.TYPE_USE, ElementType.PARAMETER})
@interface NotNull {}
public class LocalClassImplicitParameters {
private void foo(String foo, final Integer bar) {
class Test {
private Test(@NotNull String test) {
if (test == null) {
System.out.println(bar);
}
}
}
new Test(foo);
}
public int ok() {
foo("a", 2);
return 42;
}
public void failLocal() {
foo(null, null);
}
public void failAnonymous() {
new _Super("a") {
void method(@NotNull java.util.List<?> test){}
}.method(null);
}
public void failInner() {
new Inner(null, "b");
}
private class Inner {
Inner(@NotNull String param, @NotNull String param2) {
}
}
}
class _Super {
_Super(@NotNull String f) {}
}
@@ -207,6 +207,15 @@ public class NotNullVerifyingInstrumenterTest extends UsefulTestCase {
verifyCallThrowsException("Argument 0 for @NotNull parameter of MalformedBytecode$NullTest2.handle must not be null", null, testClass.getMethod("main"));
}
public void testLocalClassImplicitParameters() throws Exception {
Class<?> test = prepareTest(true, "NotNull");
Object instance = test.newInstance();
assertEquals(42, test.getMethod("ok").invoke(instance));
verifyCallThrowsException("Argument for @NotNull parameter 'test' of LocalClassImplicitParameters$1Test.<init> must not be null", instance, test.getMethod("failLocal"));
verifyCallThrowsException("Argument for @NotNull parameter 'test' of LocalClassImplicitParameters$1.method must not be null", instance, test.getMethod("failAnonymous"));
verifyCallThrowsException("Argument for @NotNull parameter 'param' of LocalClassImplicitParameters$Inner.<init> must not be null", instance, test.getMethod("failInner"));
}
private static void verifyCallThrowsException(String expectedError, @Nullable Object instance, Member member, Object... args) throws Exception {
String exceptionText = null;
try {
@@ -226,11 +235,11 @@ public class NotNullVerifyingInstrumenterTest extends UsefulTestCase {
assertEquals(expectedError, exceptionText);
}
private Class prepareTest() throws IOException {
private Class<?> prepareTest() throws IOException {
return prepareTest(false, AnnotationUtil.NOT_NULL);
}
private Class prepareTest(boolean withDebugInfo, String... notNullAnnos) throws IOException {
private Class<?> prepareTest(boolean withDebugInfo, String... notNullAnnos) throws IOException {
String base = JavaTestUtil.getJavaTestDataPath() + "/compiler/notNullVerification/";
final String baseClassName = getTestName(false);
String path = base + baseClassName;