From 250331a1d2f42de6f28c1c10ebd8a21c0b3414bc Mon Sep 17 00:00:00 2001 From: peter Date: Tue, 13 Sep 2016 17:48:16 +0200 Subject: [PATCH] generate runtime assertions for any default notnull annotation (IDEA-76782) --- .../NotNullVerifyingInstrumenter.java | 28 +++++---- .../javac2/src/com/intellij/ant/Javac2.java | 12 +++- .../NotNullVerifyingInstrumenterTest.java | 2 +- .../NotNullInstrumentingBuilder.java | 4 +- .../JpsJavaCompilerConfiguration.java | 3 + .../JpsJavaCompilerConfigurationImpl.java | 11 ++++ .../java/JpsJavaModelSerializerExtension.java | 1 + .../JpsJavaCompilerNotNullableSerializer.java | 59 +++++++++++++++++++ 8 files changed, 105 insertions(+), 15 deletions(-) create mode 100644 jps/model-serialization/src/org/jetbrains/jps/model/serialization/java/compiler/JpsJavaCompilerNotNullableSerializer.java diff --git a/java/compiler/instrumentation-util/src/com/intellij/compiler/notNullVerification/NotNullVerifyingInstrumenter.java b/java/compiler/instrumentation-util/src/com/intellij/compiler/notNullVerification/NotNullVerifyingInstrumenter.java index 777b4235337f..cd28b26bdb8a 100644 --- a/java/compiler/instrumentation-util/src/com/intellij/compiler/notNullVerification/NotNullVerifyingInstrumenter.java +++ b/java/compiler/instrumentation-util/src/com/intellij/compiler/notNullVerification/NotNullVerifyingInstrumenter.java @@ -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> 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); } diff --git a/java/compiler/javac2/src/com/intellij/ant/Javac2.java b/java/compiler/javac2/src/com/intellij/ant/Javac2.java index 69215864f096..af11c3849a6c 100644 --- a/java/compiler/javac2/src/com/intellij/ant/Javac2.java +++ b/java/compiler/javac2/src/com/intellij/ant/Javac2.java @@ -39,6 +39,7 @@ public class Javac2 extends Javac { private ArrayList myFormFiles; private List myNestedFormPathList; private boolean instrumentNotNull = true; + private String myNotNull; private List myClassFilterAnnotationRegexpList = new ArrayList(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()); diff --git a/java/java-tests/testSrc/com/intellij/compiler/notNullVerification/NotNullVerifyingInstrumenterTest.java b/java/java-tests/testSrc/com/intellij/compiler/notNullVerification/NotNullVerifyingInstrumenterTest.java index bcbe022e70d2..d9c3cc09cfd1 100644 --- a/java/java-tests/testSrc/com/intellij/compiler/notNullVerification/NotNullVerifyingInstrumenterTest.java +++ b/java/java-tests/testSrc/com/intellij/compiler/notNullVerification/NotNullVerifyingInstrumenterTest.java @@ -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); diff --git a/jps/jps-builders/src/org/jetbrains/jps/incremental/instrumentation/NotNullInstrumentingBuilder.java b/jps/jps-builders/src/org/jetbrains/jps/incremental/instrumentation/NotNullInstrumentingBuilder.java index a4e7873b373b..71a090df3927 100644 --- a/jps/jps-builders/src/org/jetbrains/jps/incremental/instrumentation/NotNullInstrumentingBuilder.java +++ b/jps/jps-builders/src/org/jetbrains/jps/incremental/instrumentation/NotNullInstrumentingBuilder.java @@ -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()); } } diff --git a/jps/model-api/src/org/jetbrains/jps/model/java/compiler/JpsJavaCompilerConfiguration.java b/jps/model-api/src/org/jetbrains/jps/model/java/compiler/JpsJavaCompilerConfiguration.java index 27f5e86053cd..ac4e30a5b25a 100644 --- a/jps/model-api/src/org/jetbrains/jps/model/java/compiler/JpsJavaCompilerConfiguration.java +++ b/jps/model-api/src/org/jetbrains/jps/model/java/compiler/JpsJavaCompilerConfiguration.java @@ -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); diff --git a/jps/model-impl/src/org/jetbrains/jps/model/java/impl/compiler/JpsJavaCompilerConfigurationImpl.java b/jps/model-impl/src/org/jetbrains/jps/model/java/impl/compiler/JpsJavaCompilerConfigurationImpl.java index dbf5eac3529f..a7c21f50fa78 100644 --- a/jps/model-impl/src/org/jetbrains/jps/model/java/impl/compiler/JpsJavaCompilerConfigurationImpl.java +++ b/jps/model-impl/src/org/jetbrains/jps/model/java/impl/compiler/JpsJavaCompilerConfigurationImpl.java @@ -35,6 +35,7 @@ import java.util.*; public class JpsJavaCompilerConfigurationImpl extends JpsCompositeElementBase implements JpsJavaCompilerConfiguration { public static final JpsElementChildRole 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 myResourcePatterns = new ArrayList(); @@ -65,6 +66,11 @@ public class JpsJavaCompilerConfigurationImpl extends JpsCompositeElementBase getProjectExtensionSerializers() { return Arrays.asList(new JavaProjectExtensionSerializer(), new JpsJavaCompilerConfigurationSerializer(), + new JpsJavaCompilerNotNullableSerializer(), new JpsJavaCompilerWorkspaceConfigurationSerializer(), new JpsJavaCompilerOptionsSerializer("JavacSettings", "Javac"), new JpsEclipseCompilerOptionsSerializer("EclipseCompilerSettings", "Eclipse"), diff --git a/jps/model-serialization/src/org/jetbrains/jps/model/serialization/java/compiler/JpsJavaCompilerNotNullableSerializer.java b/jps/model-serialization/src/org/jetbrains/jps/model/serialization/java/compiler/JpsJavaCompilerNotNullableSerializer.java new file mode 100644 index 000000000000..35663021935f --- /dev/null +++ b/jps/model-serialization/src/org/jetbrains/jps/model/serialization/java/compiler/JpsJavaCompilerNotNullableSerializer.java @@ -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) { + } +}