generate runtime assertions for any default notnull annotation (IDEA-76782)

This commit is contained in:
peter
2016-09-13 17:55:46 +02:00
parent 4ca9fb5197
commit 250331a1d2
8 changed files with 105 additions and 15 deletions
@@ -29,7 +29,6 @@ import java.util.Map;
*/
public class NotNullVerifyingInstrumenter extends ClassVisitor implements Opcodes {
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";
@@ -37,9 +36,9 @@ public class NotNullVerifyingInstrumenter extends ClassVisitor implements Opcode
private static final String ANNOTATION_DEFAULT_METHOD = "value";
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";
private final String myNullArgMessageIndexed;
private final String myNullArgMessageNamed;
private final String myNullResultMessage;
@SuppressWarnings("SSBasedInspection") private static final String[] EMPTY_STRING_ARRAY = new String[0];
private final Map<String, Map<Integer, String>> myMethodParamNames;
@@ -47,15 +46,22 @@ public class NotNullVerifyingInstrumenter extends ClassVisitor implements Opcode
private boolean myIsModification = false;
private RuntimeException myPostponedError;
private final AuxiliaryMethodGenerator myAuxGenerator;
private final String myNotNullAnno;
private NotNullVerifyingInstrumenter(final ClassVisitor classVisitor, ClassReader reader) {
private NotNullVerifyingInstrumenter(final ClassVisitor classVisitor, ClassReader reader, String notNullAnnotation) {
super(Opcodes.API_VERSION, classVisitor);
final String fullName = notNullAnnotation != null ? notNullAnnotation.replace('.', '/') : NOT_NULL_CLASS_NAME;
final String shortName = fullName.substring(fullName.lastIndexOf('/') + 1);
myNotNullAnno = "L" + fullName + ";";
myNullArgMessageIndexed = "Argument %s for @" + shortName + " parameter of %s.%s must not be null";
myNullArgMessageNamed = "Argument for @" + shortName + " parameter '%s' of %s.%s must not be null";
myNullResultMessage = "@" + shortName + " method %s.%s must not return null";
myMethodParamNames = getAllParameterNames(reader);
myAuxGenerator = new AuxiliaryMethodGenerator(reader);
}
public static boolean processClassFile(final FailSafeClassReader reader, final ClassVisitor writer) {
final NotNullVerifyingInstrumenter instrumenter = new NotNullVerifyingInstrumenter(writer, reader);
public static boolean processClassFile(final FailSafeClassReader reader, final ClassVisitor writer, String notNullAnnotation) {
NotNullVerifyingInstrumenter instrumenter = new NotNullVerifyingInstrumenter(writer, reader, notNullAnnotation);
reader.accept(instrumenter, 0);
instrumenter.myAuxGenerator.generateReportingMethod(writer);
return instrumenter.isModification();
@@ -154,7 +160,7 @@ public class NotNullVerifyingInstrumenter extends ClassVisitor implements Opcode
public AnnotationVisitor visitParameterAnnotation(final int parameter, final String anno, final boolean visible) {
AnnotationVisitor av = mv.visitParameterAnnotation(parameter, anno, visible);
if (isReferenceType(args[parameter]) && anno.equals(NOT_NULL_TYPE)) {
if (isReferenceType(args[parameter]) && anno.equals(myNotNullAnno)) {
NotNullState state = new NotNullState(IAE_CLASS_NAME);
myNotNullParams.put(new Integer(parameter), state);
av = collectNotNullArgs(av, state);
@@ -170,7 +176,7 @@ public class NotNullVerifyingInstrumenter extends ClassVisitor implements Opcode
@Override
public AnnotationVisitor visitAnnotation(String anno, boolean isRuntime) {
AnnotationVisitor av = mv.visitAnnotation(anno, isRuntime);
if (isReferenceType(returnType) && anno.equals(NOT_NULL_TYPE)) {
if (isReferenceType(returnType) && anno.equals(myNotNullAnno)) {
myMethodNotNull = new NotNullState(ISE_CLASS_NAME);
av = collectNotNullArgs(av, myMethodNotNull);
}
@@ -199,7 +205,7 @@ public class NotNullVerifyingInstrumenter extends ClassVisitor implements Opcode
String paramName = paramNames == null ? null : paramNames.get(param);
String descrPattern = state.message != null
? state.message
: paramName != null ? NULL_ARG_MESSAGE_NAMED : NULL_ARG_MESSAGE_INDEXED;
: paramName != null ? myNullArgMessageNamed : myNullArgMessageIndexed;
String[] args = state.message != null
? EMPTY_STRING_ARRAY
: new String[]{paramName != null ? paramName : String.valueOf(param - mySyntheticCount), myClassName, name};
@@ -222,7 +228,7 @@ public class NotNullVerifyingInstrumenter extends ClassVisitor implements Opcode
mv.visitInsn(DUP);
final Label skipLabel = new Label();
mv.visitJumpInsn(IFNONNULL, skipLabel);
String descrPattern = myMethodNotNull.message != null ? myMethodNotNull.message : NULL_RESULT_MESSAGE;
String descrPattern = myMethodNotNull.message != null ? myMethodNotNull.message : myNullResultMessage;
String[] args = myMethodNotNull.message != null ? EMPTY_STRING_ARRAY : new String[]{myClassName, name};
reportError(myMethodNotNull.exceptionType, skipLabel, descrPattern, args);
}
@@ -39,6 +39,7 @@ public class Javac2 extends Javac {
private ArrayList myFormFiles;
private List myNestedFormPathList;
private boolean instrumentNotNull = true;
private String myNotNull;
private List<Regexp> myClassFilterAnnotationRegexpList = new ArrayList<Regexp>(0);
public Javac2() {
@@ -75,6 +76,14 @@ public class Javac2 extends Javac {
this.instrumentNotNull = instrumentNotNull;
}
public String getNotNull() {
return myNotNull;
}
public void setNotNull(String notNull) {
myNotNull = notNull;
}
/**
* Allows to specify patterns of annotation class names to skip NotNull instrumentation on classes which have at least one
* annotation matching at least one of the given patterns
@@ -240,7 +249,6 @@ public class Javac2 extends Javac {
final int instrumented = instrumentNotNull(getDestdir(), finder);
log("Added @NotNull assertions to " + instrumented + " files", Project.MSG_INFO);
}
}
finally {
finder.releaseResources();
@@ -439,7 +447,7 @@ public class Javac2 extends Javac {
if (version >= Opcodes.V1_5 && !shouldBeSkippedByAnnotationPattern(reader)) {
ClassWriter writer = new InstrumenterClassWriter(reader, getAsmClassWriterFlags(version), finder);
if (NotNullVerifyingInstrumenter.processClassFile(reader, writer)) {
if (NotNullVerifyingInstrumenter.processClassFile(reader, writer, myNotNull)) {
final FileOutputStream fileOutputStream = new FileOutputStream(path);
try {
fileOutputStream.write(writer.toByteArray());
@@ -228,7 +228,7 @@ public class NotNullVerifyingInstrumenterTest extends UsefulTestCase {
FailSafeClassReader reader = new FailSafeClassReader(content, 0, content.length);
ClassWriter writer = new ClassWriter(reader, ClassWriter.COMPUTE_FRAMES);
modified |= NotNullVerifyingInstrumenter.processClassFile(reader, writer);
modified |= NotNullVerifyingInstrumenter.processClassFile(reader, writer, null);
byte[] instrumented = writer.toByteArray();
final String className = FileUtil.getNameWithoutExtension(fileName);
@@ -79,7 +79,9 @@ public class NotNullInstrumentingBuilder extends BaseInstrumentingBuilder{
ClassWriter writer,
InstrumentationClassFinder finder) {
try {
if (NotNullVerifyingInstrumenter.processClassFile((FailSafeClassReader)reader, writer)) {
final ProjectDescriptor pd = context.getProjectDescriptor();
final String notNullAnnotation = JpsJavaExtensionService.getInstance().getOrCreateCompilerConfiguration(pd.getProject()).getNotNullAnnotation();
if (NotNullVerifyingInstrumenter.processClassFile((FailSafeClassReader)reader, writer, notNullAnnotation)) {
return new BinaryContent(writer.toByteArray());
}
}
@@ -31,6 +31,9 @@ public interface JpsJavaCompilerConfiguration extends JpsElement {
boolean isAddNotNullAssertions();
void setAddNotNullAssertions(boolean addNotNullAssertions);
String getNotNullAnnotation();
void setNotNullAnnotation(String notNullAnnotation);
boolean isClearOutputDirectoryOnRebuild();
void setClearOutputDirectoryOnRebuild(boolean clearOutputDirectoryOnRebuild);
@@ -35,6 +35,7 @@ import java.util.*;
public class JpsJavaCompilerConfigurationImpl extends JpsCompositeElementBase<JpsJavaCompilerConfigurationImpl> implements JpsJavaCompilerConfiguration {
public static final JpsElementChildRole<JpsJavaCompilerConfiguration> ROLE = JpsElementChildRoleBase.create("compiler configuration");
private boolean myAddNotNullAssertions = true;
private String myNotNullAnnotation = NotNull.class.getName();
private boolean myClearOutputDirectoryOnRebuild = true;
private final JpsCompilerExcludes myCompilerExcludes = new JpsCompilerExcludesImpl();
private final List<String> myResourcePatterns = new ArrayList<String>();
@@ -65,6 +66,11 @@ public class JpsJavaCompilerConfigurationImpl extends JpsCompositeElementBase<Jp
return myAddNotNullAssertions;
}
@Override
public String getNotNullAnnotation() {
return myNotNullAnnotation;
}
@Override
public boolean isClearOutputDirectoryOnRebuild() {
return myClearOutputDirectoryOnRebuild;
@@ -75,6 +81,11 @@ public class JpsJavaCompilerConfigurationImpl extends JpsCompositeElementBase<Jp
myAddNotNullAssertions = addNotNullAssertions;
}
@Override
public void setNotNullAnnotation(String notNullAnnotation) {
myNotNullAnnotation = notNullAnnotation;
}
@Override
public void setClearOutputDirectoryOnRebuild(boolean clearOutputDirectoryOnRebuild) {
myClearOutputDirectoryOnRebuild = clearOutputDirectoryOnRebuild;
@@ -91,6 +91,7 @@ public class JpsJavaModelSerializerExtension extends JpsModelSerializerExtension
public List<? extends JpsProjectExtensionSerializer> getProjectExtensionSerializers() {
return Arrays.asList(new JavaProjectExtensionSerializer(),
new JpsJavaCompilerConfigurationSerializer(),
new JpsJavaCompilerNotNullableSerializer(),
new JpsJavaCompilerWorkspaceConfigurationSerializer(),
new JpsJavaCompilerOptionsSerializer("JavacSettings", "Javac"),
new JpsEclipseCompilerOptionsSerializer("EclipseCompilerSettings", "Eclipse"),
@@ -0,0 +1,59 @@
/*
* Copyright 2000-2016 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.jps.model.serialization.java.compiler;
import org.jdom.Element;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.jps.model.JpsProject;
import org.jetbrains.jps.model.java.JpsJavaExtensionService;
import org.jetbrains.jps.model.java.compiler.JpsJavaCompilerConfiguration;
import org.jetbrains.jps.model.serialization.JpsProjectExtensionSerializer;
/**
* @author vladimir.dolzhenko
*/
public class JpsJavaCompilerNotNullableSerializer extends JpsProjectExtensionSerializer {
private static final String DEFAULT_VALUE = NotNull.class.getName();
private static final String NOTNULL_ANNOTATION = "myDefaultNotNull";
private static final String VALUE = "value";
public JpsJavaCompilerNotNullableSerializer() {
super("misc.xml", "NullableNotNullManager");
}
@Override
public void loadExtension(@NotNull JpsProject project, @NotNull Element componentTag) {
JpsJavaCompilerConfiguration configuration = JpsJavaExtensionService.getInstance().getOrCreateCompilerConfiguration(project);
String value = DEFAULT_VALUE;
for (Element element : componentTag.getChildren("option")) {
if (NOTNULL_ANNOTATION.equals(element.getAttributeValue("name"))){
value = element.getAttributeValue(VALUE, DEFAULT_VALUE);
break;
}
}
configuration.setNotNullAnnotation(value);
}
@Override
public void loadExtensionWithDefaultSettings(@NotNull JpsProject project) {
JpsJavaCompilerConfiguration configuration = JpsJavaExtensionService.getInstance().getOrCreateCompilerConfiguration(project);
configuration.setNotNullAnnotation(DEFAULT_VALUE);
}
@Override
public void saveExtension(@NotNull JpsProject project, @NotNull Element componentTag) {
}
}