blob: edca8359a7d8856708ca0918ff161d9c8379751c [file] [log] [blame]
/***
* ASM tests
* Copyright (c) 2000-2011 INRIA, France Telecom
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of the copyright holders nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
package org.objectweb.asm;
import java.io.ByteArrayInputStream;
import java.io.FileInputStream;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.StringTokenizer;
import junit.framework.TestCase;
/**
* ClassWriter unit tests for COMPUTE_MAXS option with JSR instructions.
*
* @author Eric Bruneton
*/
public class ClassWriterComputeMaxsUnitTest extends TestCase {
private Field successors;
private Field successor;
private Field succ;
private Field next;
protected ClassWriter cw;
protected MethodVisitor mv;
private Label start;
@Override
protected void setUp() throws Exception {
Class<?> lClass = Label.class;
Class<?> eClass = Edge.class;
try {
successors = lClass.getDeclaredField("successors");
successor = lClass.getDeclaredField("successor");
succ = eClass.getDeclaredField("successor");
next = eClass.getDeclaredField("next");
} catch (RuntimeException exception) {
String f = "src/org/objectweb/asm/optimizer/shrink.properties";
Properties p = new Properties();
FileInputStream is = new FileInputStream(f);
try {
p.load(is);
} finally {
is.close();
}
String l = Type.getInternalName(lClass) + ".";
String e = Type.getInternalName(eClass) + ".";
successors = lClass.getDeclaredField(p
.getProperty(l + "successors"));
successor = lClass.getDeclaredField(p.getProperty(l + "successor"));
succ = eClass.getDeclaredField(p.getProperty(e + "successor"));
next = eClass.getDeclaredField(p.getProperty(e + "next"));
}
cw = new ClassWriter(isComputeMaxs() ? ClassWriter.COMPUTE_MAXS : 0);
cw.visit(Opcodes.V1_1, Opcodes.ACC_PUBLIC, "C", null,
"java/lang/Object", null);
mv = cw.visitMethod(Opcodes.ACC_PUBLIC, "<init>", "()V", null, null);
mv.visitCode();
mv.visitVarInsn(Opcodes.ALOAD, 0);
mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/Object", "<init>",
"()V");
mv.visitInsn(Opcodes.RETURN);
mv.visitMaxs(1, 1);
mv.visitEnd();
mv = cw.visitMethod(Opcodes.ACC_PUBLIC, "m", "()V", null, null);
mv.visitCode();
start = new Label();
LABEL(start);
}
protected boolean isComputeMaxs() {
return true;
}
private void NOP() {
mv.visitInsn(Opcodes.NOP);
}
private void PUSH() {
mv.visitInsn(Opcodes.ICONST_0);
}
private void ICONST_0() {
mv.visitInsn(Opcodes.ICONST_0);
}
private void ISTORE(final int var) {
mv.visitVarInsn(Opcodes.ISTORE, var);
}
private void ALOAD(final int var) {
mv.visitVarInsn(Opcodes.ALOAD, var);
}
private void ILOAD(final int var) {
mv.visitVarInsn(Opcodes.ILOAD, var);
}
private void ASTORE(final int var) {
mv.visitVarInsn(Opcodes.ASTORE, var);
}
private void RET(final int var) {
mv.visitVarInsn(Opcodes.RET, var);
}
private void ATHROW() {
mv.visitInsn(Opcodes.ATHROW);
}
private void ACONST_NULL() {
mv.visitInsn(Opcodes.ACONST_NULL);
}
private void RETURN() {
mv.visitInsn(Opcodes.RETURN);
}
private void LABEL(final Label l) {
mv.visitLabel(l);
}
private void IINC(final int var, final int amnt) {
mv.visitIincInsn(var, amnt);
}
private void GOTO(final Label l) {
mv.visitJumpInsn(Opcodes.GOTO, l);
}
private void JSR(final Label l) {
mv.visitJumpInsn(Opcodes.JSR, l);
}
private void IFNONNULL(final Label l) {
mv.visitJumpInsn(Opcodes.IFNONNULL, l);
}
private void IFNE(final Label l) {
mv.visitJumpInsn(Opcodes.IFNE, l);
}
private void TRYCATCH(final Label start, final Label end,
final Label handler) {
mv.visitTryCatchBlock(start, end, handler, null);
}
protected void assertMaxs(final int maxStack, final int maxLocals) {
mv.visitMaxs(0, 0);
mv.visitEnd();
cw.visitEnd();
byte[] b = cw.toByteArray();
ClassReader cr = new ClassReader(b);
cr.accept(new ClassVisitor(Opcodes.ASM4) {
@Override
public MethodVisitor visitMethod(final int access,
final String name, final String desc,
final String signature, final String[] exceptions) {
if (name.equals("m")) {
return new MethodVisitor(Opcodes.ASM4) {
@Override
public void visitMaxs(final int realMaxStack,
final int realMaxLocals) {
assertEquals("maxStack", maxStack, realMaxStack);
assertEquals("maxLocals", maxLocals, realMaxLocals);
}
};
} else {
return null;
}
}
}, 0);
try {
TestClassLoader loader = new TestClassLoader();
Class<?> c = loader.defineClass("C", b);
c.newInstance();
} catch (Throwable t) {
fail(t.getMessage());
}
}
protected void assertGraph(final String graph) {
Map<String, Set<String>> expected = new HashMap<String, Set<String>>();
Properties p = new Properties();
try {
p.load(new ByteArrayInputStream(graph.getBytes()));
} catch (Exception e) {
fail();
}
Iterator<Map.Entry<Object, Object>> i = p.entrySet().iterator();
while (i.hasNext()) {
Map.Entry<Object, Object> entry = i.next();
String key = (String) entry.getKey();
String value = (String) entry.getValue();
StringTokenizer st = new StringTokenizer(value, ",");
Set<String> s = new HashSet<String>();
while (st.hasMoreTokens()) {
s.add(st.nextToken());
}
expected.put(key, s);
}
Map<String, Set<String>> actual = new HashMap<String, Set<String>>();
try {
Label l = start;
while (l != null) {
String key = "N" + l.getOffset();
Set<String> value = new HashSet<String>();
Edge e = (Edge) successors.get(l);
while (e != null) {
value.add("N" + ((Label) succ.get(e)).getOffset());
e = (Edge) next.get(e);
}
actual.put(key, value);
l = (Label) successor.get(l);
}
} catch (IllegalAccessException e) {
fail();
}
assertEquals(expected, actual);
}
protected static class TestClassLoader extends ClassLoader {
public TestClassLoader() {
}
public Class<?> defineClass(final String name, final byte[] b) {
return defineClass(name, b, 0, b.length);
}
}
/**
* Tests a method which has the most basic <code>try{}finally</code> form
* imaginable:
*
* <pre>
* public void a() {
* int a = 0;
* try {
* a++;
* } finally {
* a--;
* }
* }
* </pre>
*/
public void testBasic() {
Label L0 = new Label();
Label L1 = new Label();
Label L2 = new Label();
Label L3 = new Label();
Label L4 = new Label();
ICONST_0(); // N0
ISTORE(1);
/* L0: body of try block */
LABEL(L0); // N2
IINC(1, 1);
GOTO(L1);
/* L2: exception handler */
LABEL(L2); // N8
ASTORE(3);
JSR(L3);
ALOAD(3); // N12
ATHROW();
/* L3: subroutine */
LABEL(L3); // N14
ASTORE(2);
IINC(1, -1);
PUSH();
PUSH();
RET(2);
/* L1: non-exceptional exit from try block */
LABEL(L1); // N22
JSR(L3);
PUSH(); // N25
PUSH();
LABEL(L4); // N27
RETURN();
TRYCATCH(L0, L2, L2);
TRYCATCH(L1, L4, L2);
assertMaxs(4, 4);
assertGraph("N0=N2\n" + "N2=N22,N8\n" + "N8=N14,N12\n" + "N12=\n"
+ "N14=N12,N25\n" + "N22=N14,N25,N8\n" + "N25=N27,N8\n"
+ "N27=\n");
}
/**
* Tests a method which has an if/else-if w/in the finally clause:
*
* <pre>
* public void a() {
* int a = 0;
* try {
* a++;
* } finally {
* if (a == 0)
* a += 2;
* else
* a += 3;
* }
* }
* </pre>
*/
public void testIfElseInFinally() {
Label L0 = new Label();
Label L1 = new Label();
Label L2 = new Label();
Label L3 = new Label();
Label L4 = new Label();
Label L5 = new Label();
Label L6 = new Label();
ICONST_0(); // N0
ISTORE(1);
/* L0: body of try block */
LABEL(L0); // N2
IINC(1, 1);
GOTO(L1);
/* L2: exception handler */
LABEL(L2); // N8
ASTORE(3);
JSR(L3);
PUSH(); // N12
PUSH();
ALOAD(3);
ATHROW();
/* L3: subroutine */
LABEL(L3); // N16
ASTORE(2);
PUSH();
PUSH();
ILOAD(1);
IFNE(L4);
IINC(1, 2);
GOTO(L5);
LABEL(L4); // N29
IINC(1, 3);
LABEL(L5); // N32 common exit
RET(2);
/* L1: non-exceptional exit from try block */
LABEL(L1); // N34
JSR(L3);
LABEL(L6); // N37
RETURN();
TRYCATCH(L0, L2, L2);
TRYCATCH(L1, L6, L2);
assertMaxs(5, 4);
assertGraph("N0=N2\n" + "N2=N34,N8\n" + "N8=N16,N12\n" + "N12=\n"
+ "N16=N29,N32\n" + "N29=N32\n" + "N32=N37,N12\n"
+ "N34=N16,N37,N8\n" + "N37=\n");
}
/**
* Tests a simple nested finally:
*
* <pre>
* public void a1() {
* int a = 0;
* try {
* a += 1;
* } finally {
* try {
* a += 2;
* } finally {
* a += 3;
* }
* }
* }
* </pre>
*/
public void testSimpleNestedFinally() {
Label L0 = new Label();
Label L1 = new Label();
Label L2 = new Label();
Label L3 = new Label();
Label L4 = new Label();
Label L5 = new Label();
ICONST_0(); // N0
ISTORE(1);
// L0: Body of try block:
LABEL(L0); // N2
IINC(1, 1);
JSR(L3);
GOTO(L1); // N8
// L2: First exception handler:
LABEL(L2); // N11
ASTORE(4);
JSR(L3);
ALOAD(4); // N16
ATHROW();
// L3: First subroutine:
LABEL(L3); // N19
ASTORE(2);
IINC(1, 2);
JSR(L4);
PUSH(); // N26
PUSH();
RET(2);
// L5: Second exception handler:
LABEL(L5); // N30
ASTORE(5);
JSR(L4);
ALOAD(5); // N35
ATHROW();
// L4: Second subroutine:
LABEL(L4); // N38
ASTORE(3);
PUSH();
PUSH();
IINC(1, 3);
RET(3);
// L1: On normal exit, try block jumps here:
LABEL(L1); // N46
RETURN();
TRYCATCH(L0, L2, L2);
TRYCATCH(L3, L5, L5);
assertMaxs(5, 6);
assertGraph("N0=N2\n" + "N2=N11,N19,N8\n" + "N8=N11,N46\n"
+ "N11=N19,N16\n" + "N16=\n" + "N19=N26,N30,N38\n"
+ "N26=N16,N30,N8\n" + "N30=N38,N35\n" + "N35=\n"
+ "N38=N26,N35\n" + "N46=\n");
}
/**
* This tests a subroutine which has no ret statement, but ends in a
* "return" instead.
*
* We structure this as a try/finally with a break in the finally. Because
* the while loop is infinite, it's clear from the byte code that the only
* path which reaches the RETURN instruction is through the subroutine.
*
* <pre>
* public void a1() {
* int a = 0;
* while (true) {
* try {
* a += 1;
* } finally {
* a += 2;
* break;
* }
* }
* }
* </pre>
*/
public void testSubroutineWithNoRet() {
Label L0 = new Label();
Label L1 = new Label();
Label L2 = new Label();
Label L3 = new Label();
Label L4 = new Label();
ICONST_0(); // N0
ISTORE(1);
// L0: while loop header/try block
LABEL(L0); // N2
IINC(1, 1);
JSR(L1);
GOTO(L2); // N8
// L3: implicit catch block
LABEL(L3); // N11
ASTORE(2);
JSR(L1);
PUSH(); // N15
PUSH();
ALOAD(2);
ATHROW();
// L1: subroutine ...
LABEL(L1); // N19
ASTORE(3);
IINC(1, 2);
GOTO(L4); // ...not that it does not return!
// L2: end of the loop... goes back to the top!
LABEL(L2); // N26
GOTO(L0);
// L4:
LABEL(L4); // N29
RETURN();
TRYCATCH(L0, L3, L3);
assertMaxs(1, 4);
assertGraph("N0=N2\n" + "N2=N11,N19,N8\n" + "N8=N11,N26\n"
+ "N11=N19,N15\n" + "N15=\n" + "N19=N29\n" + "N26=N2\n"
+ "N29=\n");
}
/**
* This tests a subroutine which has no ret statement, but ends in a
* "return" instead.
*
* <pre>
* ACONST_NULL
* JSR L0
* L0:
* ASTORE 0
* ASTORE 0
* RETURN
* </pre>
*/
public void testSubroutineWithNoRet2() {
Label L0 = new Label();
Label L1 = new Label();
ACONST_NULL(); // N0
JSR(L0);
NOP(); // N4
LABEL(L0); // N5
ASTORE(0);
ASTORE(0);
RETURN();
LABEL(L1); // N8
mv.visitLocalVariable("i", "I", null, L0, L1, 1);
assertMaxs(2, 2);
assertGraph("N0=N4,N5\n" + "N4=N5\n" + "N5=\n" + "N8=\n");
}
/**
* This tests a subroutine which has no ret statement, but instead exits
* implicitely by branching to code which is not part of the subroutine.
* (Sadly, this is legal)
*
* We structure this as a try/finally in a loop with a break in the finally.
* The loop is not trivially infinite, so the RETURN statement is reachable
* both from the JSR subroutine and from the main entry point.
*
* <pre>
* public void a1() {
* int a = 0;
* while (null == null) {
* try {
* a += 1;
* } finally {
* a += 2;
* break;
* }
* }
* }
* </pre>
*/
public void testImplicitExit() {
Label L0 = new Label();
Label L1 = new Label();
Label L2 = new Label();
Label L3 = new Label();
Label L4 = new Label();
Label L5 = new Label();
ICONST_0(); // N0
ISTORE(1);
// L5: while loop header
LABEL(L5); // N2
ACONST_NULL();
IFNONNULL(L4);
// L0: try block
LABEL(L0); // N6
IINC(1, 1);
JSR(L1);
GOTO(L2); // N12
// L3: implicit catch block
LABEL(L3); // N15
ASTORE(2);
JSR(L1);
ALOAD(2); // N19
PUSH();
PUSH();
ATHROW();
// L1: subroutine ...
LABEL(L1); // N23
ASTORE(3);
IINC(1, 2);
GOTO(L4); // ...not that it does not return!
// L2: end of the loop... goes back to the top!
LABEL(L2); // N30
GOTO(L0);
// L4:
LABEL(L4); // N33
RETURN();
TRYCATCH(L0, L3, L3);
assertMaxs(1, 4);
assertGraph("N0=N2\n" + "N2=N6,N33\n" + "N6=N23,N12,N15\n"
+ "N12=N30,N15\n" + "N15=N23,N19\n" + "N19=\n" + "N23=N33\n"
+ "N30=N6\n" + "N33=\n");
}
/**
* Tests a nested try/finally with implicit exit from one subroutine to the
* other subroutine. Equivalent to the following java code:
*
* <pre>
* void m(boolean b) {
* try {
* return;
* } finally {
* while (b) {
* try {
* return;
* } finally {
* // NOTE --- this break avoids the second return above (weird)
* if (b)
* break;
* }
* }
* }
* }
* </pre>
*
* This example is from the paper, "Subroutine Inlining and Bytecode
* Abstraction to Simplify Static and Dynamic Analysis" by Cyrille Artho and
* Armin Biere.
*/
public void testImplicitExitToAnotherSubroutine() {
Label T1 = new Label();
Label C1 = new Label();
Label S1 = new Label();
Label L = new Label();
Label C2 = new Label();
Label S2 = new Label();
Label W = new Label();
Label X = new Label();
// variable numbers:
int b = 1;
int e1 = 2;
int e2 = 3;
int r1 = 4;
int r2 = 5;
ICONST_0(); // N0
ISTORE(1);
// T1: first try:
LABEL(T1); // N2
JSR(S1);
RETURN(); // N5
// C1: exception handler for first try
LABEL(C1); // N6
ASTORE(e1);
JSR(S1);
PUSH(); // N10
PUSH();
ALOAD(e1);
ATHROW();
// S1: first finally handler
LABEL(S1); // N14
ASTORE(r1);
PUSH();
PUSH();
GOTO(W);
// L: body of while loop, also second try
LABEL(L); // N21
JSR(S2);
RETURN(); // N24
// C2: exception handler for second try
LABEL(C2); // N25
ASTORE(e2);
PUSH();
PUSH();
JSR(S2);
ALOAD(e2); // N31
ATHROW();
// S2: second finally handler
LABEL(S2); // N33
ASTORE(r2);
ILOAD(b);
IFNE(X);
RET(r2);
// W: test for the while loop
LABEL(W); // N41
ILOAD(b);
IFNE(L); // falls through to X
// X: exit from finally{} block
LABEL(X); // N45
RET(r1);
TRYCATCH(T1, C1, C1);
TRYCATCH(L, C2, C2);
assertMaxs(5, 6);
assertGraph("N0=N2\n" + "N2=N6,N5,N14\n" + "N5=N6\n" + "N6=N14,N10\n"
+ "N10=\n" + "N14=N41\n" + "N21=N24,N25,N33\n" + "N24=N25\n"
+ "N25=N31,N33\n" + "N31=\n" + "N33=N31,N45,N24\n"
+ "N41=N45,N21\n" + "N45=N5,N10\n");
}
public void testImplicitExitToAnotherSubroutine2() {
Label L1 = new Label();
Label L2 = new Label();
Label L3 = new Label();
ICONST_0(); // N0
ISTORE(1);
JSR(L1);
RETURN(); // N5
LABEL(L1); // N6
ASTORE(2);
JSR(L2);
GOTO(L3); // N10
LABEL(L2); // N13
ASTORE(3);
ILOAD(1);
IFNE(L3);
RET(3);
LABEL(L3); // N20
RET(2);
assertMaxs(1, 4);
assertGraph("N0=N6,N5\n" + "N5=\n" + "N6=N10,N13\n" + "N10=N20\n"
+ "N13=N20,N10\n" + "N20=N5\n");
}
/**
* This tests a simple subroutine where the control flow jumps back and
* forth between the subroutine and the caller.
*
* This would not normally be produced by a java compiler.
*/
public void testInterleavedCode() {
Label L1 = new Label();
Label L2 = new Label();
Label L3 = new Label();
Label L4 = new Label();
ICONST_0(); // N0
ISTORE(1);
JSR(L1);
GOTO(L2); // N5
// L1: subroutine 1
LABEL(L1); // N8
ASTORE(2);
IINC(1, 1);
GOTO(L3);
// L2: second part of main subroutine
LABEL(L2); // N15
IINC(1, 2);
GOTO(L4);
// L3: second part of subroutine 1
LABEL(L3); // N21
IINC(1, 4);
PUSH();
PUSH();
RET(2);
// L4: third part of main subroutine
LABEL(L4); // N28
PUSH();
PUSH();
RETURN();
assertMaxs(4, 3);
assertGraph("N0=N5,N8\n" + "N5=N15\n" + "N8=N21\n" + "N15=N28\n"
+ "N21=N5\n" + "N28=\n");
}
/**
* Tests a nested try/finally with implicit exit from one subroutine to the
* other subroutine, and with a surrounding try/catch thrown in the mix.
* Equivalent to the following java code:
*
* <pre>
* void m(int b) {
* try {
* try {
* return;
* } finally {
* while (b) {
* try {
* return;
* } finally {
* // NOTE --- this break avoids the second return above
* // (weird)
* if (b)
* break;
* }
* }
* }
* } catch (Exception e) {
* b += 3;
* return;
* }
* }
* </pre>
*/
public void testImplicitExitInTryCatch() {
Label T1 = new Label();
Label C1 = new Label();
Label S1 = new Label();
Label L = new Label();
Label C2 = new Label();
Label S2 = new Label();
Label W = new Label();
Label X = new Label();
Label OC = new Label();
// variable numbers:
int b = 1;
int e1 = 2;
int e2 = 3;
int r1 = 4;
int r2 = 5;
ICONST_0(); // N0
ISTORE(1);
// T1: first try:
LABEL(T1); // N2
JSR(S1);
RETURN(); // N5
// C1: exception handler for first try
LABEL(C1); // N6
ASTORE(e1);
JSR(S1);
ALOAD(e1); // N10
ATHROW();
// S1: first finally handler
LABEL(S1); // N12
ASTORE(r1);
GOTO(W);
// L: body of while loop, also second try
LABEL(L); // N17
JSR(S2);
PUSH(); // N20
PUSH();
RETURN();
// C2: exception handler for second try
LABEL(C2); // N23
ASTORE(e2);
JSR(S2);
ALOAD(e2); // N27
ATHROW();
// S2: second finally handler
LABEL(S2); // N29
ASTORE(r2);
ILOAD(b);
IFNE(X);
PUSH();
PUSH();
RET(r2);
// W: test for the while loop
LABEL(W); // N39
ILOAD(b);
IFNE(L); // falls through to X
// X: exit from finally{} block
LABEL(X); // N43
RET(r1);
// OC: outermost catch
LABEL(OC); // N45
IINC(b, 3);
RETURN();
TRYCATCH(T1, C1, C1);
TRYCATCH(L, C2, C2);
TRYCATCH(T1, OC, OC);
assertMaxs(4, 6);
assertGraph("N0=N2\n" + "N2=N6,N45,N5,N12\n" + "N5=N6,N45\n"
+ "N6=N45,N12,N10\n" + "N10=N45\n" + "N12=N39,N45\n"
+ "N17=N23,N45,N20,N29\n" + "N20=N23,N45\n"
+ "N23=N45,N27,N29\n" + "N27=N45\n" + "N29=N43,N45,N20,N27\n"
+ "N39=N43,N45,N17\n" + "N43=N45,N5,N10\n" + "N45=\n");
}
}