package test.javassist.bytecode.analysis; import java.io.Serializable; import java.util.ArrayList; import java.util.Iterator; import javassist.ClassPool; import javassist.CtClass; import javassist.CtMethod; import javassist.bytecode.AccessFlag; import javassist.bytecode.BadBytecode; import javassist.bytecode.Bytecode; import javassist.bytecode.CodeIterator; import javassist.bytecode.MethodInfo; import javassist.bytecode.Opcode; import javassist.bytecode.analysis.Analyzer; import javassist.bytecode.analysis.Frame; import javassist.bytecode.analysis.Type; import junit.framework.TestCase; /** * Tests Analyzer * * @author Jason T. Greene */ public class AnalyzerTest extends TestCase { public void testCommonSupperArray() throws Exception { ClassPool pool = ClassPool.getDefault(); CtClass clazz = pool.get(getClass().getName() + "$Dummy"); CtMethod method = clazz.getDeclaredMethod("commonSuperArray"); verifyArrayLoad(clazz, method, "java.lang.Number"); } public void testCommonInterfaceArray() throws Exception { ClassPool pool = ClassPool.getDefault(); CtClass clazz = pool.get(getClass().getName() + "$Dummy"); CtMethod method = clazz.getDeclaredMethod("commonInterfaceArray"); verifyArrayLoad(clazz, method, "java.io.Serializable"); } public void testSharedInterfaceAndSuperClass() throws Exception { CtMethod method = ClassPool.getDefault().getMethod( getClass().getName() + "$Dummy", "sharedInterfaceAndSuperClass"); verifyReturn(method, "java.io.Serializable"); method = ClassPool.getDefault().getMethod( getClass().getName() + "$Dummy", "sharedOffsetInterfaceAndSuperClass"); verifyReturn(method, "java.io.Serializable"); method = ClassPool.getDefault().getMethod( getClass().getName() + "$Dummy", "sharedSuperWithSharedInterface"); verifyReturn(method, getClass().getName() + "$Dummy$A"); } public void testArrayDifferentDims() throws Exception { CtMethod method = ClassPool.getDefault().getMethod( getClass().getName() + "$Dummy", "arrayDifferentDimensions1"); verifyReturn(method, "java.lang.Cloneable[]"); method = ClassPool.getDefault().getMethod( getClass().getName() + "$Dummy", "arrayDifferentDimensions2"); verifyReturn(method, "java.lang.Object[][]"); } public void testReusedLocalMerge() throws Exception { CtMethod method = ClassPool.getDefault().getMethod( getClass().getName() + "$Dummy", "reusedLocalMerge"); MethodInfo info = method.getMethodInfo2(); Analyzer analyzer = new Analyzer(); Frame[] frames = analyzer.analyze(method.getDeclaringClass(), info); assertNotNull(frames); int pos = findOpcode(info, Opcode.RETURN); Frame frame = frames[pos]; assertEquals("java.lang.Object", frame.getLocal(2).getCtClass().getName()); } private static int findOpcode(MethodInfo info, int opcode) throws BadBytecode { CodeIterator iter = info.getCodeAttribute().iterator(); // find return int pos = 0; while (iter.hasNext()) { pos = iter.next(); if (iter.byteAt(pos) == opcode) break; } return pos; } private static void verifyReturn(CtMethod method, String expected) throws BadBytecode { MethodInfo info = method.getMethodInfo2(); CodeIterator iter = info.getCodeAttribute().iterator(); // find areturn int pos = 0; while (iter.hasNext()) { pos = iter.next(); if (iter.byteAt(pos) == Opcode.ARETURN) break; } Analyzer analyzer = new Analyzer(); Frame[] frames = analyzer.analyze(method.getDeclaringClass(), info); assertNotNull(frames); Frame frame = frames[pos]; assertEquals(expected, frame.peek().getCtClass().getName()); } private static void verifyArrayLoad(CtClass clazz, CtMethod method, String component) throws BadBytecode { MethodInfo info = method.getMethodInfo2(); CodeIterator iter = info.getCodeAttribute().iterator(); // find aaload int pos = 0; while (iter.hasNext()) { pos = iter.next(); if (iter.byteAt(pos) == Opcode.AALOAD) break; } Analyzer analyzer = new Analyzer(); Frame[] frames = analyzer.analyze(clazz, info); assertNotNull(frames); Frame frame = frames[pos]; assertNotNull(frame); Type type = frame.getStack(frame.getTopIndex() - 1); assertEquals(component + "[]", type.getCtClass().getName()); pos = iter.next(); frame = frames[pos]; assertNotNull(frame); type = frame.getStack(frame.getTopIndex()); assertEquals(component, type.getCtClass().getName()); } private static void addJump(Bytecode code, int opcode, int pos) { int current = code.currentPc(); code.addOpcode(opcode); code.addIndex(pos - current); } public void testDeadCode() throws Exception { CtMethod method = generateDeadCode(ClassPool.getDefault()); Analyzer analyzer = new Analyzer(); Frame[] frames = analyzer.analyze(method.getDeclaringClass(), method.getMethodInfo2()); assertNotNull(frames); assertNull(frames[4]); assertNotNull(frames[5]); verifyReturn(method, "java.lang.String"); } public void testInvalidCode() throws Exception { CtMethod method = generateInvalidCode(ClassPool.getDefault()); Analyzer analyzer = new Analyzer(); try { analyzer.analyze(method.getDeclaringClass(), method.getMethodInfo2()); } catch (BadBytecode e) { return; } fail("Invalid code should have triggered a BadBytecode exception"); } public void testCodeFalloff() throws Exception { CtMethod method = generateCodeFalloff(ClassPool.getDefault()); Analyzer analyzer = new Analyzer(); try { analyzer.analyze(method.getDeclaringClass(), method.getMethodInfo2()); } catch (BadBytecode e) { return; } fail("Code falloff should have triggered a BadBytecode exception"); } public void testJsrMerge() throws Exception { CtMethod method = generateJsrMerge(ClassPool.getDefault()); Analyzer analyzer = new Analyzer(); analyzer.analyze(method.getDeclaringClass(), method.getMethodInfo2()); verifyReturn(method, "java.lang.String"); } public void testJsrMerge2() throws Exception { CtMethod method = generateJsrMerge2(ClassPool.getDefault()); Analyzer analyzer = new Analyzer(); analyzer.analyze(method.getDeclaringClass(), method.getMethodInfo2()); verifyReturn(method, "java.lang.String"); } private CtMethod generateDeadCode(ClassPool pool) throws Exception { CtClass clazz = pool.makeClass(getClass().getName() + "$Generated0"); CtClass stringClass = pool.get("java.lang.String"); CtMethod method = new CtMethod(stringClass, "foo", new CtClass[0], clazz); MethodInfo info = method.getMethodInfo2(); info.setAccessFlags(AccessFlag.PUBLIC | AccessFlag.STATIC); Bytecode code = new Bytecode(info.getConstPool(), 1, 2); /* 0 */ code.addIconst(1); /* 1 */ addJump(code, Opcode.GOTO, 5); /* 4 */ code.addIconst(0); // DEAD /* 5 */ code.addIconst(1); /* 6 */ code.addInvokestatic(stringClass, "valueOf", stringClass, new CtClass[]{CtClass.intType}); /* 9 */ code.addOpcode(Opcode.ARETURN); info.setCodeAttribute(code.toCodeAttribute()); clazz.addMethod(method); return method; } private CtMethod generateInvalidCode(ClassPool pool) throws Exception { CtClass clazz = pool.makeClass(getClass().getName() + "$Generated4"); CtClass intClass = pool.get("java.lang.Integer"); CtClass stringClass = pool.get("java.lang.String"); CtMethod method = new CtMethod(stringClass, "foo", new CtClass[0], clazz); MethodInfo info = method.getMethodInfo2(); info.setAccessFlags(AccessFlag.PUBLIC | AccessFlag.STATIC); Bytecode code = new Bytecode(info.getConstPool(), 1, 2); /* 0 */ code.addIconst(1); /* 1 */ code.addInvokestatic(intClass, "valueOf", intClass, new CtClass[]{CtClass.intType}); /* 4 */ code.addOpcode(Opcode.ARETURN); info.setCodeAttribute(code.toCodeAttribute()); clazz.addMethod(method); return method; } private CtMethod generateCodeFalloff(ClassPool pool) throws Exception { CtClass clazz = pool.makeClass(getClass().getName() + "$Generated3"); CtClass stringClass = pool.get("java.lang.String"); CtMethod method = new CtMethod(stringClass, "foo", new CtClass[0], clazz); MethodInfo info = method.getMethodInfo2(); info.setAccessFlags(AccessFlag.PUBLIC | AccessFlag.STATIC); Bytecode code = new Bytecode(info.getConstPool(), 1, 2); /* 0 */ code.addIconst(1); /* 1 */ code.addInvokestatic(stringClass, "valueOf", stringClass, new CtClass[]{CtClass.intType}); info.setCodeAttribute(code.toCodeAttribute()); clazz.addMethod(method); return method; } private CtMethod generateJsrMerge(ClassPool pool) throws Exception { CtClass clazz = pool.makeClass(getClass().getName() + "$Generated1"); CtClass stringClass = pool.get("java.lang.String"); CtMethod method = new CtMethod(stringClass, "foo", new CtClass[0], clazz); MethodInfo info = method.getMethodInfo2(); info.setAccessFlags(AccessFlag.PUBLIC | AccessFlag.STATIC); Bytecode code = new Bytecode(info.getConstPool(), 1, 2); /* 0 */ code.addIconst(5); /* 1 */ code.addIstore(0); /* 2 */ addJump(code, Opcode.JSR, 7); /* 5 */ code.addAload(0); /* 6 */ code.addOpcode(Opcode.ARETURN); /* 7 */ code.addAstore(1); /* 8 */ code.addIconst(3); /* 9 */ code.addInvokestatic(stringClass, "valueOf", stringClass, new CtClass[]{CtClass.intType}); /* 12 */ code.addAstore(0); /* 12 */ code.addRet(1); info.setCodeAttribute(code.toCodeAttribute()); clazz.addMethod(method); //System.out.println(clazz.toClass().getMethod("foo", new Class[0]).invoke(null, new Object[0])); return method; } private CtMethod generateJsrMerge2(ClassPool pool) throws Exception { CtClass clazz = pool.makeClass(getClass().getName() + "$Generated2"); CtClass stringClass = pool.get("java.lang.String"); CtMethod method = new CtMethod(stringClass, "foo", new CtClass[0], clazz); MethodInfo info = method.getMethodInfo2(); info.setAccessFlags(AccessFlag.PUBLIC | AccessFlag.STATIC); Bytecode code = new Bytecode(info.getConstPool(), 1, 2); /* 0 */ addJump(code, Opcode.JSR, 5); /* 3 */ code.addAload(0); /* 4 */ code.addOpcode(Opcode.ARETURN); /* 5 */ code.addAstore(1); /* 6 */ code.addIconst(4); /* 7 */ code.addInvokestatic(stringClass, "valueOf", stringClass, new CtClass[]{CtClass.intType}); /* 10 */ code.addAstore(0); /* 11 */ code.addRet(1); info.setCodeAttribute(code.toCodeAttribute()); clazz.addMethod(method); return method; } public static class Dummy { public Serializable commonSuperArray(int x) { Number[] n; if (x > 5) { n = new Long[10]; } else { n = new Double[5]; } return n[x]; } public Serializable commonInterfaceArray(int x) { Serializable[] n; if (x > 5) { n = new Long[10]; } else if (x > 3) { n = new Double[5]; } else { n = new String[3]; } return n[x]; } public static class A {}; public static class B1 extends A implements Serializable {}; public static class B2 extends A implements Serializable {}; public static class A2 implements Serializable, Cloneable {}; public static class A3 implements Serializable, Cloneable {}; public static class B3 extends A {}; public static class C31 extends B3 implements Serializable {}; public void dummy(Serializable s) {} public Object sharedInterfaceAndSuperClass(int x) { Serializable s; if (x > 5) { s = new B1(); } else { s = new B2(); } dummy(s); return s; } public A sharedSuperWithSharedInterface(int x) { A a; if (x > 5) { a = new B1(); } else if (x > 3) { a = new B2(); } else { a = new C31(); } return a; } public void reusedLocalMerge() { ArrayList list = new ArrayList(); try { Iterator i = list.iterator(); i.hasNext(); } catch (Exception e) { } } public Object sharedOffsetInterfaceAndSuperClass(int x) { Serializable s; if (x > 5) { s = new B1(); } else { s = new C31(); } dummy(s); return s; } public Object arrayDifferentDimensions1(int x) { Object[] n; if ( x > 5) { n = new Number[1][1]; } else { n = new Cloneable[1]; } return n; } public Object arrayDifferentDimensions2(int x) { Object[] n; if ( x> 5) { n = new String[1][1]; } else { n = new Number[1][1][1][1]; } return n; } } }