2 * Copyright 2000-2009 JetBrains s.r.o.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
16 package com
.intellij
.compiler
.notNullVerification
;
18 import org
.objectweb
.asm
.*;
20 import java
.util
.ArrayList
;
24 * @noinspection HardCodedStringLiteral
26 public class NotNullVerifyingInstrumenter
extends ClassAdapter
{
28 private boolean myIsModification
= false;
29 private boolean myIsNotStaticInner
= false;
30 private String myClassName
;
31 private String mySuperName
;
32 private static final String ENUM_CLASS_NAME
= "java/lang/Enum";
33 private static final String CONSTRUCTOR_NAME
= "<init>";
35 public NotNullVerifyingInstrumenter(final ClassVisitor classVisitor
) {
39 public boolean isModification() {
40 return myIsModification
;
43 public void visit(final int version
,
46 final String signature
,
47 final String superName
,
48 final String
[] interfaces
) {
49 super.visit(version
, access
, name
, signature
, superName
, interfaces
);
51 mySuperName
= superName
;
54 public void visitInnerClass(final String name
, final String outerName
, final String innerName
, final int access
) {
55 super.visitInnerClass(name
, outerName
, innerName
, access
);
56 if (myClassName
.equals(name
)) {
57 myIsNotStaticInner
= (access
& Opcodes
.ACC_STATIC
) == 0;
61 public MethodVisitor
visitMethod(
65 final String signature
,
66 final String
[] exceptions
) {
67 final Type
[] args
= Type
.getArgumentTypes(desc
);
68 final Type returnType
= Type
.getReturnType(desc
);
69 final int startParameter
= getStartParameterIndex(name
);
70 MethodVisitor v
= cv
.visitMethod(access
,
75 return new MethodAdapter(v
) {
77 private final ArrayList myNotNullParams
= new ArrayList();
78 private boolean myIsNotNull
= false;
79 public Label myThrowLabel
;
80 private Label myStartGeneratedCodeLabel
;
82 public AnnotationVisitor
visitParameterAnnotation(
85 final boolean visible
) {
87 av
= mv
.visitParameterAnnotation(parameter
,
90 if (isReferenceType(args
[parameter
]) &&
91 anno
.equals("Lorg/jetbrains/annotations/NotNull;")) {
92 myNotNullParams
.add(new Integer(parameter
));
97 public AnnotationVisitor
visitAnnotation(String anno
,
99 final AnnotationVisitor av
= mv
.visitAnnotation(anno
, isRuntime
);
100 if (isReferenceType(returnType
) &&
101 anno
.equals("Lorg/jetbrains/annotations/NotNull;")) {
108 public void visitCode() {
109 if (myNotNullParams
.size() > 0) {
110 myStartGeneratedCodeLabel
= new Label();
111 mv
.visitLabel(myStartGeneratedCodeLabel
);
113 for (int p
= 0; p
< myNotNullParams
.size(); ++p
) {
114 int var
= ((access
& Opcodes
.ACC_STATIC
) == 0) ?
1 : 0;
115 int param
= ((Integer
)myNotNullParams
.get(p
)).intValue();
116 for (int i
= 0; i
< param
+ startParameter
; ++i
) {
117 var
+= args
[i
].getSize();
119 mv
.visitVarInsn(Opcodes
.ALOAD
, var
);
121 Label end
= new Label();
122 mv
.visitJumpInsn(Opcodes
.IFNONNULL
, end
);
124 generateThrow("java/lang/IllegalArgumentException",
125 "Argument " + param
+ " for @NotNull parameter of " + myClassName
+ "." + name
+ " must not be null", end
);
129 public void visitLocalVariable(final String name
, final String desc
, final String signature
, final Label start
, final Label end
,
131 final boolean isStatic
= (access
& Opcodes
.ACC_STATIC
) != 0;
132 final boolean isParameter
= isStatic ? index
< args
.length
: index
<= args
.length
;
133 mv
.visitLocalVariable(name
, desc
, signature
, (isParameter
&& myStartGeneratedCodeLabel
!= null) ? myStartGeneratedCodeLabel
: start
, end
, index
);
136 public void visitInsn(int opcode
) {
137 if (opcode
== Opcodes
.ARETURN
&& myIsNotNull
) {
138 mv
.visitInsn(Opcodes
.DUP
);
139 /*generateConditionalThrow("@NotNull method " + myClassName + "." + name + " must not return null",
140 "java/lang/IllegalStateException");*/
141 if (myThrowLabel
== null) {
142 Label skipLabel
= new Label();
143 mv
.visitJumpInsn(Opcodes
.IFNONNULL
, skipLabel
);
144 myThrowLabel
= new Label();
145 mv
.visitLabel(myThrowLabel
);
146 generateThrow("java/lang/IllegalStateException", "@NotNull method " + myClassName
+ "." + name
+ " must not return null",
150 mv
.visitJumpInsn(Opcodes
.IFNULL
, myThrowLabel
);
154 mv
.visitInsn(opcode
);
157 private void generateThrow(final String exceptionClass
, final String descr
, final Label end
) {
158 String exceptionParamClass
= "(Ljava/lang/String;)V";
159 mv
.visitTypeInsn(Opcodes
.NEW
, exceptionClass
);
160 mv
.visitInsn(Opcodes
.DUP
);
161 mv
.visitLdcInsn(descr
);
162 mv
.visitMethodInsn(Opcodes
.INVOKESPECIAL
,
165 exceptionParamClass
);
166 mv
.visitInsn(Opcodes
.ATHROW
);
169 myIsModification
= true;
172 public void visitMaxs(final int maxStack
, final int maxLocals
) {
174 super.visitMaxs(maxStack
, maxLocals
);
176 catch (ArrayIndexOutOfBoundsException e
) {
177 throw new ArrayIndexOutOfBoundsException("maxs processing failed for method " + name
+ ": " + e
.getMessage());
183 private int getStartParameterIndex(final String name
) {
185 if (CONSTRUCTOR_NAME
.equals(name
)) {
186 if (mySuperName
.equals(ENUM_CLASS_NAME
)) {
189 if (myIsNotStaticInner
) {
196 private static boolean isReferenceType(final Type type
) {
197 return type
.getSort() == Type
.OBJECT
|| type
.getSort() == Type
.ARRAY
;