Fix for issues when mixing @NonNull on params with @SneakyThrows or @Synchronized [Issue #588]

This commit is contained in:
Reinier Zwitserloot committed 2013-10-10 23:25:09 +02:00
1 parent 08961edcfe
commit a9b4fb0c68
12 files changed
+125 -31

No files matched your search

+7 -2
View File
@@ -1,8 +1,13 @@
Lombok Changelog
----------------
### v0.12.1 "Edgy Guinea Pig"
* PLATFORM: Initial (not quite totally finished) JDK8 support, without affecting existing support for JDK6 and 7. [Issue #451](https://code.google.com/p/projectlombok/issues/detail?id=451).
### v1.12.2 (October 10th, 2013)
* PLATFORM: Initial JDK8 support, without affecting existing support for JDK6 and 7. [Issue #451](https://code.google.com/p/projectlombok/issues/detail?id=451). While lombok will now work on JDK8 / javac8, and netbeans 7.4 and up, lombok does not (yet) support new language features introduced with java8, such as lambda expressions. Support for these features will be added in a future version.
* PLATFORM: Running javac on IBM J9 VM would cause NullPointerExceptions when compiling with lombok. These issues should be fixed. [Issue #554](https://code.google.com/p/projectlombok/issues/detail?id=554).
* CHANGE: [JDK8-related] The canonical way to write onMethod / onParameter / onConstructor annotation now uses a double underscore instead of a single underscore, so, now, the proper way to use this feature is `@RequiredArgsConstructor(onConstructor=@__(@Inject))`. The old way (single underscore) still works, but generates warnings on javac 8.
* BUGFIX: Using `@NonNull` on an abstract method used to cause exceptions during compilation. [Issue #559](https://code.google.com/p/projectlombok/issues/detail?id=559).
* BUGFIX: Using `@NonNull` on methods that also have `@SneakyThrows` or `@Synchronized` caused arbitrary behaviour. [Issue #588](https://code.google.com/p/projectlombok/issues/detail?id=588).
* GERMANY: Major version bumped from 0 to 1, because allegedly this is important. Rest assured, this change is nevertheless backwards compatible.
### v0.12.0 "Angry Butterfly" (July 16th, 2013)
* FEATURE: javadoc on fields will now be copied to generated getters / setters / withers. There are ways to specify separate javadoc for the field, the setter, and the getter, and `@param` and `@return` are handled appropriately. Addresses feature request [Issue #59](https://code.google.com/p/projectlombok/issues/detail?id=59). [@Getter and @Setter documentation](http://projectlombok.org/features/GetterSetter.html). [@Wither documentation](http://projectlombok.org/features/experimental/Wither.html).
+1 -1
View File
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2009-2011 The Project Lombok Authors.
* Copyright (C) 2009-2013 The Project Lombok Authors.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
@@ -21,8 +21,19 @@
*/
package lombok.eclipse.handlers;
import static lombok.eclipse.Eclipse.isPrimitive;
import static lombok.eclipse.handlers.EclipseHandlerUtil.*;
import java.util.Arrays;
import lombok.NonNull;
import lombok.core.AST.Kind;
import lombok.core.AnnotationValues;
import lombok.core.HandlerPriority;
import lombok.eclipse.DeferUntilPostDiet;
import lombok.eclipse.EclipseAnnotationHandler;
import lombok.eclipse.EclipseNode;
import org.eclipse.jdt.internal.compiler.ast.ASTNode;
import org.eclipse.jdt.internal.compiler.ast.AbstractMethodDeclaration;
import org.eclipse.jdt.internal.compiler.ast.AbstractVariableDeclaration;
@@ -36,22 +47,15 @@ import org.eclipse.jdt.internal.compiler.ast.NullLiteral;
import org.eclipse.jdt.internal.compiler.ast.OperatorIds;
import org.eclipse.jdt.internal.compiler.ast.SingleNameReference;
import org.eclipse.jdt.internal.compiler.ast.Statement;
import org.eclipse.jdt.internal.compiler.ast.SynchronizedStatement;
import org.eclipse.jdt.internal.compiler.ast.ThrowStatement;
import org.eclipse.jdt.internal.compiler.ast.TryStatement;
import org.mangosdk.spi.ProviderFor;
import lombok.NonNull;
import lombok.core.AST.Kind;
import lombok.core.AnnotationValues;
import lombok.eclipse.DeferUntilPostDiet;
import lombok.eclipse.EclipseAnnotationHandler;
import lombok.eclipse.EclipseNode;
import static lombok.eclipse.Eclipse.*;
import static lombok.eclipse.handlers.EclipseHandlerUtil.*;
@DeferUntilPostDiet
@ProviderFor(EclipseAnnotationHandler.class)
public class NonNullHandler extends EclipseAnnotationHandler<NonNull> {
@HandlerPriority(value = 512) // 2^9; onParameter=@__(@NonNull) has to run first.
public class HandleNonNull extends EclipseAnnotationHandler<NonNull> {
@Override public void handle(AnnotationValues<NonNull> annotation, Annotation ast, EclipseNode annotationNode) {
if (annotationNode.up().getKind() == Kind.FIELD) {
// This is meaningless unless the field is used to generate a method (@Setter, @RequiredArgsConstructor, etc),
@@ -86,7 +90,7 @@ public class NonNullHandler extends EclipseAnnotationHandler<NonNull> {
annotationNode.addWarning("@NonNull is meaningless on a parameter of an abstract method.");
return;
}
// Possibly, if 'declaration instanceof ConstructorDeclaration', fetch declaration.constructorCall, search it for any references to our parameter,
// and if they exist, create a new method in the class: 'private static <T> T lombok$nullCheck(T expr, String msg) {if (expr == null) throw NPE; return expr;}' and
// wrap all references to it in the super/this to a call to this method.
@@ -103,16 +107,31 @@ public class NonNullHandler extends EclipseAnnotationHandler<NonNull> {
declaration.statements = new Statement[] {nullCheck};
} else {
char[] expectedName = arg.name;
for (Statement stat : declaration.statements) {
char[] varNameOfNullCheck = returnVarNameIfNullCheck(stat);
if (varNameOfNullCheck == null) break;
if (Arrays.equals(expectedName, varNameOfNullCheck)) return;
/* Abort if the null check is already there, delving into try and synchronized statements */ {
Statement[] stats = declaration.statements;
int idx = 0;
while (stats != null && stats.length > idx) {
Statement stat = stats[idx++];
if (stat instanceof TryStatement) {
stats = ((TryStatement) stat).tryBlock.statements;
idx = 0;
continue;
}
if (stat instanceof SynchronizedStatement) {
stats = ((SynchronizedStatement) stat).block.statements;
idx = 0;
continue;
}
char[] varNameOfNullCheck = returnVarNameIfNullCheck(stat);
if (varNameOfNullCheck == null) break;
if (Arrays.equals(varNameOfNullCheck, expectedName)) return;
}
}
Statement[] newStatements = new Statement[declaration.statements.length + 1];
int skipOver = 0;
for (Statement stat : declaration.statements) {
if (isGenerated(stat)) skipOver++;
if (isGenerated(stat) && isNullCheck(stat)) skipOver++;
else break;
}
System.arraycopy(declaration.statements, 0, newStatements, 0, skipOver);
@@ -123,6 +142,10 @@ public class NonNullHandler extends EclipseAnnotationHandler<NonNull> {
annotationNode.up().up().rebuild();
}
private boolean isNullCheck(Statement stat) {
return returnVarNameIfNullCheck(stat) != null;
}
private char[] returnVarNameIfNullCheck(Statement stat) {
if (!(stat instanceof IfStatement)) return null;
@@ -30,6 +30,7 @@ import java.util.List;
import lombok.SneakyThrows;
import lombok.core.AnnotationValues;
import lombok.core.HandlerPriority;
import lombok.eclipse.DeferUntilPostDiet;
import lombok.eclipse.EclipseAnnotationHandler;
import lombok.eclipse.EclipseNode;
@@ -60,6 +61,7 @@ import org.mangosdk.spi.ProviderFor;
*/
@ProviderFor(EclipseAnnotationHandler.class)
@DeferUntilPostDiet
@HandlerPriority(value = 1024) // 2^10; @NonNull must have run first, so that we wrap around the statements generated by it.
public class HandleSneakyThrows extends EclipseAnnotationHandler<SneakyThrows> {
private static class DeclaredException {
@@ -27,6 +27,7 @@ import java.lang.reflect.Modifier;
import lombok.Synchronized;
import lombok.core.AnnotationValues;
import lombok.core.HandlerPriority;
import lombok.core.AST.Kind;
import lombok.eclipse.DeferUntilPostDiet;
import lombok.eclipse.EclipseAnnotationHandler;
@@ -52,6 +53,7 @@ import org.mangosdk.spi.ProviderFor;
*/
@ProviderFor(EclipseAnnotationHandler.class)
@DeferUntilPostDiet
@HandlerPriority(value = 1024) // 2^10; @NonNull must have run first, so that we wrap around the statements generated by it.
public class HandleSynchronized extends EclipseAnnotationHandler<Synchronized> {
private static final char[] INSTANCE_LOCK_NAME = "$lock".toCharArray();
private static final char[] STATIC_LOCK_NAME = "$LOCK".toCharArray();
@@ -36,21 +36,24 @@ import com.sun.tools.javac.tree.JCTree.JCLiteral;
import com.sun.tools.javac.tree.JCTree.JCMethodDecl;
import com.sun.tools.javac.tree.JCTree.JCParens;
import com.sun.tools.javac.tree.JCTree.JCStatement;
import com.sun.tools.javac.tree.JCTree.JCSynchronized;
import com.sun.tools.javac.tree.JCTree.JCThrow;
import com.sun.tools.javac.tree.JCTree.JCTry;
import com.sun.tools.javac.tree.JCTree.JCVariableDecl;
import com.sun.tools.javac.util.List;
import lombok.NonNull;
import lombok.core.AnnotationValues;
import lombok.core.HandlerPriority;
import lombok.core.AST.Kind;
import lombok.javac.JavacAnnotationHandler;
import lombok.javac.JavacNode;
import static lombok.javac.JavacTreeMaker.TypeTag.*;
import static lombok.javac.JavacTreeMaker.TreeTag.*;
@ProviderFor(JavacAnnotationHandler.class)
public class NonNullHandler extends JavacAnnotationHandler<NonNull> {
@HandlerPriority(value = 512) // 2^9; onParameter=@__(@NonNull) has to run first.
public class HandleNonNull extends JavacAnnotationHandler<NonNull> {
@Override public void handle(AnnotationValues<NonNull> annotation, JCAnnotation ast, JavacNode annotationNode) {
if (annotationNode.up().getKind() == Kind.FIELD) {
// This is meaningless unless the field is used to generate a method (@Setter, @RequiredArgsConstructor, etc),
@@ -77,7 +80,7 @@ public class NonNullHandler extends JavacAnnotationHandler<NonNull> {
return;
}
if (JavacHandlerUtil.isGenerated(declaration)) return;
// if (JavacHandlerUtil.isGenerated(declaration)) return;
if (declaration.body == null) {
annotationNode.addWarning("@NonNull is meaningless on a parameter of an abstract method.");
@@ -99,17 +102,33 @@ public class NonNullHandler extends JavacAnnotationHandler<NonNull> {
List<JCStatement> statements = declaration.body.stats;
String expectedName = annotationNode.up().getName();
for (JCStatement stat : statements) {
if (JavacHandlerUtil.isConstructorCall(stat)) continue;
String varNameOfNullCheck = returnVarNameIfNullCheck(stat);
if (varNameOfNullCheck == null) break;
if (varNameOfNullCheck.equals(expectedName)) return;
/* Abort if the null check is already there, delving into try and synchronized statements */ {
List<JCStatement> stats = statements;
int idx = 0;
while (stats.size() > idx) {
JCStatement stat = stats.get(idx++);
if (JavacHandlerUtil.isConstructorCall(stat)) continue;
if (stat instanceof JCTry) {
stats = ((JCTry) stat).body.stats;
idx = 0;
continue;
}
if (stat instanceof JCSynchronized) {
stats = ((JCSynchronized) stat).body.stats;
idx = 0;
continue;
}
String varNameOfNullCheck = returnVarNameIfNullCheck(stat);
if (varNameOfNullCheck == null) break;
if (varNameOfNullCheck.equals(expectedName)) return;
}
}
List<JCStatement> tail = statements;
List<JCStatement> head = List.nil();
for (JCStatement stat : statements) {
if (JavacHandlerUtil.isConstructorCall(stat) || JavacHandlerUtil.isGenerated(stat)) {
if (JavacHandlerUtil.isConstructorCall(stat) || (JavacHandlerUtil.isGenerated(stat) && isNullCheck(stat))) {
tail = tail.tail;
head = head.prepend(stat);
continue;
@@ -122,6 +141,10 @@ public class NonNullHandler extends JavacAnnotationHandler<NonNull> {
declaration.body.stats = newList;
}
private boolean isNullCheck(JCStatement stat) {
return returnVarNameIfNullCheck(stat) != null;
}
/**
* Checks if the statement is of the form 'if (x == null) {throw WHATEVER;},
* where the block braces are optional. If it is of this form, returns "x".
@@ -29,6 +29,7 @@ import java.util.Collections;
import lombok.SneakyThrows;
import lombok.core.AnnotationValues;
import lombok.core.HandlerPriority;
import lombok.javac.JavacAnnotationHandler;
import lombok.javac.JavacNode;
import lombok.javac.JavacTreeMaker;
@@ -49,6 +50,7 @@ import com.sun.tools.javac.util.List;
* Handles the {@code lombok.SneakyThrows} annotation for javac.
*/
@ProviderFor(JavacAnnotationHandler.class)
@HandlerPriority(value = 1024) // 2^10; @NonNull must have run first, so that we wrap around the statements generated by it.
public class HandleSneakyThrows extends JavacAnnotationHandler<SneakyThrows> {
@Override public void handle(AnnotationValues<SneakyThrows> annotation, JCAnnotation ast, JavacNode annotationNode) {
deleteAnnotationIfNeccessary(annotationNode, SneakyThrows.class);
@@ -26,6 +26,7 @@ import static lombok.javac.handlers.JavacHandlerUtil.*;
import lombok.Synchronized;
import lombok.core.AST.Kind;
import lombok.core.AnnotationValues;
import lombok.core.HandlerPriority;
import lombok.javac.JavacAnnotationHandler;
import lombok.javac.JavacNode;
import lombok.javac.JavacTreeMaker;
@@ -46,6 +47,7 @@ import com.sun.tools.javac.util.List;
* Handles the {@code lombok.Synchronized} annotation for javac.
*/
@ProviderFor(JavacAnnotationHandler.class)
@HandlerPriority(value = 1024) // 2^10; @NonNull must have run first, so that we wrap around the statements generated by it.
public class HandleSynchronized extends JavacAnnotationHandler<Synchronized> {
private static final String INSTANCE_LOCK_NAME = "$lock";
private static final String STATIC_LOCK_NAME = "$LOCK";
@@ -0,0 +1,12 @@
class NonNullWithSneakyThrows {
void test(@lombok.NonNull String in) {
try {
if (in == null) {
throw new java.lang.NullPointerException("in");
}
System.out.println(in);
} catch (final java.lang.Throwable $ex) {
throw lombok.Lombok.sneakyThrow($ex);
}
}
}
@@ -4,9 +4,9 @@ import lombok.Synchronized;
@Log enum InjectField1 {
A(),
B(),
private static final java.util.logging.Logger log = java.util.logging.Logger.getLogger(InjectField1.class.getName());
private final java.lang.Object $lock = new java.lang.Object[0];
private static final java.lang.Object $LOCK = new java.lang.Object[0];
private static final java.util.logging.Logger log = java.util.logging.Logger.getLogger(InjectField1.class.getName());
private static final String LOG_MESSAGE = "static initializer";
private String fieldA;
static {
@@ -32,8 +32,8 @@ import lombok.Synchronized;
}
}
@Log class InjectField2 {
private final java.lang.Object $lock = new java.lang.Object[0];
private static final java.util.logging.Logger log = java.util.logging.Logger.getLogger(InjectField2.class.getName());
private final java.lang.Object $lock = new java.lang.Object[0];
private static final String LOG_MESSAGE = "static initializer";
static {
log.log(Level.FINE, LOG_MESSAGE);
@@ -0,0 +1,18 @@
class NonNullWithSneakyThrows {
NonNullWithSneakyThrows() {
super();
}
@lombok.SneakyThrows void test(@lombok.NonNull String in) {
try
{
if ((in == null))
{
throw new java.lang.NullPointerException("in");
}
System.out.println(in);
}
catch (final java.lang.Throwable $ex) {
throw lombok.Lombok.sneakyThrow($ex);
}
}
}
@@ -0,0 +1,5 @@
class NonNullWithSneakyThrows {
@lombok.SneakyThrows void test(@lombok.NonNull String in) {
System.out.println(in);
}
}