Skip to content
This repository was archived by the owner on Jul 6, 2018. It is now read-only.

Commit 2c0f724

Browse files
committed
Added support for JSR 305 @nonnull annotations
1 parent 9db36d8 commit 2c0f724

File tree

5 files changed

+254
-34
lines changed

5 files changed

+254
-34
lines changed

README

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
A Maven plugin that uses IntelliJ's javac2 byte-code instrumentation.
1+
A Maven plugin that uses IntelliJ's javac2 byte-code instrumentation to insert checks for @NotNull/@Nonnull annotations
22

33
INSTALLATION
44

pom.xml

+2-2
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,11 @@
55

66
<groupId>com.electriccloud</groupId>
77
<artifactId>javac2-maven-plugin</artifactId>
8-
<version>1.0.1</version>
8+
<version>1.0.2</version>
99
<packaging>maven-plugin</packaging>
1010

1111
<name>Javac2 Maven Plugin</name>
12-
<description>Instruments class files using IntelliJ's javac2 instrumenter</description>
12+
<description>Instruments NotNull/Nonnull class files using IntelliJ's javac2 instrumenter</description>
1313

1414
<properties>
1515
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>

src/main/java/com/electriccloud/maven/javac2/Javac2MojoSupport.java

+59-21
Original file line numberDiff line numberDiff line change
@@ -23,31 +23,27 @@
2323

2424
package com.electriccloud.maven.javac2;
2525

26-
import java.io.File;
27-
import java.io.FileInputStream;
28-
import java.io.FileOutputStream;
29-
import java.io.IOException;
30-
import java.net.MalformedURLException;
31-
26+
import com.intellij.ant.AntClassWriter;
27+
import com.intellij.ant.InstrumentationUtil;
28+
import com.intellij.ant.PseudoClassLoader;
29+
import com.intellij.compiler.notNullVerification.NonnullVerifyingInstrumenter;
30+
import com.intellij.compiler.notNullVerification.NotNullVerifyingInstrumenter;
3231
import org.apache.maven.plugin.AbstractMojo;
3332
import org.apache.maven.plugin.MojoExecutionException;
3433
import org.apache.maven.project.MavenProject;
35-
3634
import org.jetbrains.annotations.NonNls;
3735
import org.jetbrains.annotations.NotNull;
38-
3936
import org.jfrog.maven.annomojo.annotations.MojoParameter;
40-
4137
import org.objectweb.asm.ClassReader;
4238
import org.objectweb.asm.ClassWriter;
4339
import org.objectweb.asm.Opcodes;
4440
import org.objectweb.asm.commons.EmptyVisitor;
4541

46-
import com.intellij.ant.AntClassWriter;
47-
import com.intellij.ant.InstrumentationUtil;
48-
import com.intellij.ant.PseudoClassLoader;
49-
50-
import com.intellij.compiler.notNullVerification.NotNullVerifyingInstrumenter;
42+
import java.io.File;
43+
import java.io.FileInputStream;
44+
import java.io.FileOutputStream;
45+
import java.io.IOException;
46+
import java.net.MalformedURLException;
5147

5248
import static java.io.File.pathSeparator;
5349

@@ -205,7 +201,7 @@ private InstrumentResult instrumentNotNull(
205201
if (shadowFile.lastModified() >= file.lastModified()) {
206202

207203
if (getLog().isDebugEnabled()) {
208-
getLog().debug("Up-to-date @NotNull assertions: "
204+
getLog().debug("Up-to-date @NotNull/@Nonnull assertions: "
209205
+ file.getPath());
210206
}
211207

@@ -242,7 +238,8 @@ private InstrumentResult instrumentNotNull(
242238
}
243239

244240
try {
245-
instrumentClassFile(loader, file);
241+
instrumentClassFileNotNull(loader, file);
242+
instrumentClassFileNonnull(loader, file);
246243

247244
// Set the timestamp of the shadow file
248245
if (!shadowFile.setLastModified(file.lastModified())) {
@@ -252,16 +249,16 @@ private InstrumentResult instrumentNotNull(
252249
}
253250
}
254251
catch (IOException e) {
255-
getLog().warn("Failed to instrument @NotNull assertion for "
252+
getLog().warn("Failed to instrument @NotNull/@Nonnull assertion for "
256253
+ file.getPath() + ": " + e.getMessage(), e);
257254
}
258255
catch (MojoExecutionException e) {
259-
getLog().warn("Failed to instrument @NotNull assertion for "
256+
getLog().warn("Failed to instrument @NotNull/@Nonnull assertion for "
260257
+ file.getPath() + ": " + e.getMessage(), e);
261258
}
262259
catch (Exception e) {
263260
throw new MojoExecutionException(
264-
"@NotNull instrumentation failed for " + file.getPath()
261+
"@NotNull/@Nonnull instrumentation failed for " + file.getPath()
265262
+ ": " + e.toString());
266263
}
267264
}
@@ -271,9 +268,9 @@ private InstrumentResult instrumentNotNull(
271268

272269
//~ Methods ----------------------------------------------------------------
273270

274-
private static void instrumentClassFile(
271+
private static void instrumentClassFileNotNull(
275272
@NotNull PseudoClassLoader loader,
276-
@NotNull File file)
273+
@NotNull File file)
277274
throws IOException
278275
{
279276
FileInputStream inputStream = new FileInputStream(file);
@@ -307,6 +304,47 @@ private static void instrumentClassFile(
307304
}
308305
}
309306

307+
/**
308+
* Copy of {@link Javac2MojoSupport#instrumentClassFileNotNull(com.intellij.ant.PseudoClassLoader, java.io.File)} method
309+
* which instruments JSR 205 Nonnull annotation
310+
*/
311+
private static void instrumentClassFileNonnull(
312+
@NotNull PseudoClassLoader loader,
313+
@NotNull File file)
314+
throws IOException
315+
{
316+
FileInputStream inputStream = new FileInputStream(file);
317+
318+
try {
319+
ClassReader reader = new ClassReader(inputStream);
320+
ClassWriter writer = new AntClassWriter(getAsmClassWriterFlags(
321+
getClassFileVersion(reader)), loader);
322+
323+
//
324+
NonnullVerifyingInstrumenter instrumenter =
325+
new NonnullVerifyingInstrumenter(writer);
326+
327+
reader.accept(instrumenter, 0);
328+
329+
if (!instrumenter.isModification()) {
330+
return;
331+
}
332+
333+
FileOutputStream fileOutputStream = new FileOutputStream(file);
334+
335+
try {
336+
fileOutputStream.write(writer.toByteArray());
337+
}
338+
finally {
339+
fileOutputStream.close();
340+
}
341+
}
342+
finally {
343+
inputStream.close();
344+
}
345+
}
346+
347+
310348
/**
311349
* @param version
312350
*

src/main/java/com/electriccloud/maven/javac2/TestInstrumentMojo.java

+3-10
Original file line numberDiff line numberDiff line change
@@ -23,17 +23,11 @@
2323

2424
package com.electriccloud.maven.javac2;
2525

26-
import java.io.File;
27-
2826
import org.apache.maven.artifact.DependencyResolutionRequiredException;
2927
import org.apache.maven.plugin.MojoExecutionException;
28+
import org.jfrog.maven.annomojo.annotations.*;
3029

31-
import org.jfrog.maven.annomojo.annotations.MojoGoal;
32-
import org.jfrog.maven.annomojo.annotations.MojoParameter;
33-
import org.jfrog.maven.annomojo.annotations.MojoPhase;
34-
import org.jfrog.maven.annomojo.annotations.MojoRequiresDependencyResolution;
35-
import org.jfrog.maven.annomojo.annotations.MojoRequiresOnline;
36-
import org.jfrog.maven.annomojo.annotations.MojoRequiresProject;
30+
import java.io.File;
3731

3832
/**
3933
* Run @NotNull bytecode instrumentation on
@@ -64,8 +58,7 @@ public class TestInstrumentMojo
6458
{
6559

6660
try {
67-
instrumentNotNull(m_outputDirectory,
68-
m_project.getTestClasspathElements());
61+
instrumentNotNull(m_outputDirectory, m_project.getTestClasspathElements());
6962
}
7063
catch (DependencyResolutionRequiredException e) {
7164
throw new MojoExecutionException(e.getMessage(), e);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
2+
/*
3+
* Copyright 2000-2009 JetBrains s.r.o.
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
package com.intellij.compiler.notNullVerification;
18+
19+
import org.objectweb.asm.*;
20+
21+
import java.util.ArrayList;
22+
23+
/**
24+
* @author ven
25+
* @noinspection HardCodedStringLiteral
26+
*
27+
* Note: this is copy of {@link NotNullVerifyingInstrumenter} which supported JSR 305 annotation
28+
*/
29+
public class NonnullVerifyingInstrumenter extends ClassAdapter implements Opcodes {
30+
31+
private boolean myIsModification = false;
32+
private String myClassName;
33+
34+
public static final String NOT_NULL = "javax/annotation/Nonnull";
35+
public static final String NOT_NULL_ANNO = "L" + NOT_NULL + ";";
36+
public static final String IAE_CLASS_NAME = "java/lang/IllegalArgumentException";
37+
public static final String ISE_CLASS_NAME = "java/lang/IllegalStateException";
38+
private static final String CONSTRUCTOR_NAME = "<init>";
39+
40+
public NonnullVerifyingInstrumenter(final ClassVisitor classVisitor) {
41+
super(classVisitor);
42+
}
43+
44+
public boolean isModification() {
45+
return myIsModification;
46+
}
47+
48+
public void visit(final int version,
49+
final int access,
50+
final String name,
51+
final String signature,
52+
final String superName,
53+
final String[] interfaces) {
54+
super.visit(version, access, name, signature, superName, interfaces);
55+
myClassName = name;
56+
}
57+
58+
public MethodVisitor visitMethod(
59+
final int access,
60+
final String name,
61+
final String desc,
62+
final String signature,
63+
final String[] exceptions) {
64+
final Type[] args = Type.getArgumentTypes(desc);
65+
final Type returnType = Type.getReturnType(desc);
66+
MethodVisitor v = cv.visitMethod(access,
67+
name,
68+
desc,
69+
signature,
70+
exceptions);
71+
return new MethodAdapter(v) {
72+
73+
private final ArrayList myNotNullParams = new ArrayList();
74+
private int mySyntheticCount = 0;
75+
private boolean myIsNotNull = false;
76+
//private boolean myIsUnmodifiable = false;
77+
public Label myThrowLabel;
78+
//public Label myWrapLabel;
79+
private Label myStartGeneratedCodeLabel;
80+
81+
public AnnotationVisitor visitParameterAnnotation(
82+
final int parameter,
83+
final String anno,
84+
final boolean visible) {
85+
AnnotationVisitor av;
86+
av = mv.visitParameterAnnotation(parameter,
87+
anno,
88+
visible);
89+
if (isReferenceType(args[parameter]) && anno.equals(NOT_NULL_ANNO)) {
90+
myNotNullParams.add(new Integer(parameter));
91+
} else if (anno.equals("Ljava/lang/Synthetic;")) {
92+
// See asm r1278 for what we do this,
93+
// http://forge.objectweb.org/tracker/index.php?func=detail&aid=307392&group_id=23&atid=100023
94+
mySyntheticCount++;
95+
}
96+
return av;
97+
}
98+
99+
public AnnotationVisitor visitAnnotation(String anno,
100+
boolean isRuntime) {
101+
final AnnotationVisitor av = mv.visitAnnotation(anno, isRuntime);
102+
if (isReferenceType(returnType) &&
103+
anno.equals(NOT_NULL_ANNO)) {
104+
myIsNotNull = true;
105+
}
106+
107+
return av;
108+
}
109+
110+
public void visitCode() {
111+
if (myNotNullParams.size() > 0) {
112+
myStartGeneratedCodeLabel = new Label();
113+
mv.visitLabel(myStartGeneratedCodeLabel);
114+
}
115+
for (int p = 0; p < myNotNullParams.size(); ++p) {
116+
int var = ((access & ACC_STATIC) == 0) ? 1 : 0;
117+
int param = ((Integer) myNotNullParams.get(p)).intValue();
118+
for (int i = 0; i < param; ++i) {
119+
var += args[i].getSize();
120+
}
121+
mv.visitVarInsn(ALOAD, var);
122+
123+
Label end = new Label();
124+
mv.visitJumpInsn(IFNONNULL, end);
125+
126+
generateThrow(IAE_CLASS_NAME,
127+
"Argument " + (param - mySyntheticCount) + " for @NotNull parameter of " + myClassName + "." + name + " must not be null", end);
128+
}
129+
}
130+
131+
public void visitLocalVariable(final String name, final String desc, final String signature, final Label start, final Label end,
132+
final int index) {
133+
final boolean isStatic = (access & ACC_STATIC) != 0;
134+
final boolean isParameter = isStatic ? index < args.length : index <= args.length;
135+
mv.visitLocalVariable(name, desc, signature, (isParameter && myStartGeneratedCodeLabel != null) ? myStartGeneratedCodeLabel : start, end, index);
136+
}
137+
138+
public void visitInsn(int opcode) {
139+
if (opcode == ARETURN) {
140+
if (myIsNotNull) {
141+
mv.visitInsn(DUP);
142+
/*generateConditionalThrow("@NotNull method " + myClassName + "." + name + " must not return null",
143+
"java/lang/IllegalStateException");*/
144+
if (myThrowLabel == null) {
145+
Label skipLabel = new Label();
146+
mv.visitJumpInsn(IFNONNULL, skipLabel);
147+
myThrowLabel = new Label();
148+
mv.visitLabel(myThrowLabel);
149+
generateThrow(ISE_CLASS_NAME, "@NotNull method " + myClassName + "." + name + " must not return null",
150+
skipLabel);
151+
} else {
152+
mv.visitJumpInsn(IFNULL, myThrowLabel);
153+
}
154+
}
155+
}
156+
157+
mv.visitInsn(opcode);
158+
}
159+
160+
private void generateThrow(final String exceptionClass, final String descr, final Label end) {
161+
String exceptionParamClass = "(Ljava/lang/String;)V";
162+
mv.visitTypeInsn(NEW, exceptionClass);
163+
mv.visitInsn(DUP);
164+
mv.visitLdcInsn(descr);
165+
mv.visitMethodInsn(INVOKESPECIAL,
166+
exceptionClass,
167+
CONSTRUCTOR_NAME,
168+
exceptionParamClass);
169+
mv.visitInsn(ATHROW);
170+
mv.visitLabel(end);
171+
172+
myIsModification = true;
173+
}
174+
175+
public void visitMaxs(final int maxStack, final int maxLocals) {
176+
try {
177+
super.visitMaxs(maxStack, maxLocals);
178+
} catch (ArrayIndexOutOfBoundsException e) {
179+
throw new ArrayIndexOutOfBoundsException("maxs processing failed for method " + name + ": " + e.getMessage());
180+
}
181+
}
182+
};
183+
}
184+
185+
private static boolean isReferenceType(final Type type) {
186+
return type.getSort() == Type.OBJECT || type.getSort() == Type.ARRAY;
187+
}
188+
}
189+

0 commit comments

Comments
 (0)