Called for method parameters (including this
), exception handler variable and
+ * with null
type for variables reserved by long and double types.
+ *
+ * @param type a primitive or reference type, or null to represent an uninitialized
+ * value.
+ * @return a value that represents the given type. The size of the returned value must be equal to
+ * the size of the given type.
+ */
+ public abstract V newValue(Type type);
+
+ /**
+ * Interprets a bytecode instruction without arguments. This method is called for the following
+ * opcodes:
+ *
+ *
ACONST_NULL, ICONST_M1, ICONST_0, ICONST_1, ICONST_2, ICONST_3, ICONST_4, ICONST_5, + * LCONST_0, LCONST_1, FCONST_0, FCONST_1, FCONST_2, DCONST_0, DCONST_1, BIPUSH, SIPUSH, LDC, JSR, + * GETSTATIC, NEW + * + * @param insn the bytecode instruction to be interpreted. + * @return the result of the interpretation of the given instruction. + * @throws AnalyzerException if an error occured during the interpretation. + */ + public abstract V newOperation(AbstractInsnNode insn) throws AnalyzerException; + + /** + * Interprets a bytecode instruction that moves a value on the stack or to or from local + * variables. This method is called for the following opcodes: + * + *
ILOAD, LLOAD, FLOAD, DLOAD, ALOAD, ISTORE, LSTORE, FSTORE, DSTORE, ASTORE, DUP, DUP_X1, + * DUP_X2, DUP2, DUP2_X1, DUP2_X2, SWAP + * + * @param insn the bytecode instruction to be interpreted. + * @param value the value that must be moved by the instruction. + * @return the result of the interpretation of the given instruction. The returned value must be + * equal to the given value. + * @throws AnalyzerException if an error occured during the interpretation. + */ + public abstract V copyOperation(AbstractInsnNode insn, V value) throws AnalyzerException; + + /** + * Interprets a bytecode instruction with a single argument. This method is called for the + * following opcodes: + * + *
INEG, LNEG, FNEG, DNEG, IINC, I2L, I2F, I2D, L2I, L2F, L2D, F2I, F2L, F2D, D2I, D2L, D2F, + * I2B, I2C, I2S, IFEQ, IFNE, IFLT, IFGE, IFGT, IFLE, TABLESWITCH, LOOKUPSWITCH, IRETURN, LRETURN, + * FRETURN, DRETURN, ARETURN, PUTSTATIC, GETFIELD, NEWARRAY, ANEWARRAY, ARRAYLENGTH, ATHROW, + * CHECKCAST, INSTANCEOF, MONITORENTER, MONITOREXIT, IFNULL, IFNONNULL + * + * @param insn the bytecode instruction to be interpreted. + * @param value the argument of the instruction to be interpreted. + * @return the result of the interpretation of the given instruction. + * @throws AnalyzerException if an error occured during the interpretation. + */ + public abstract V unaryOperation(AbstractInsnNode insn, V value) throws AnalyzerException; + + /** + * Interprets a bytecode instruction with two arguments. This method is called for the following + * opcodes: + * + *
IALOAD, LALOAD, FALOAD, DALOAD, AALOAD, BALOAD, CALOAD, SALOAD, IADD, LADD, FADD, DADD, + * ISUB, LSUB, FSUB, DSUB, IMUL, LMUL, FMUL, DMUL, IDIV, LDIV, FDIV, DDIV, IREM, LREM, FREM, DREM, + * ISHL, LSHL, ISHR, LSHR, IUSHR, LUSHR, IAND, LAND, IOR, LOR, IXOR, LXOR, LCMP, FCMPL, FCMPG, + * DCMPL, DCMPG, IF_ICMPEQ, IF_ICMPNE, IF_ICMPLT, IF_ICMPGE, IF_ICMPGT, IF_ICMPLE, IF_ACMPEQ, + * IF_ACMPNE, PUTFIELD + * + * @param insn the bytecode instruction to be interpreted. + * @param value1 the first argument of the instruction to be interpreted. + * @param value2 the second argument of the instruction to be interpreted. + * @return the result of the interpretation of the given instruction. + * @throws AnalyzerException if an error occured during the interpretation. + */ + public abstract V binaryOperation(AbstractInsnNode insn, V value1, V value2) + throws AnalyzerException; + + /** + * Interprets a bytecode instruction with three arguments. This method is called for the following + * opcodes: + * + *
IASTORE, LASTORE, FASTORE, DASTORE, AASTORE, BASTORE, CASTORE, SASTORE + * + * @param insn the bytecode instruction to be interpreted. + * @param value1 the first argument of the instruction to be interpreted. + * @param value2 the second argument of the instruction to be interpreted. + * @param value3 the third argument of the instruction to be interpreted. + * @return the result of the interpretation of the given instruction. + * @throws AnalyzerException if an error occured during the interpretation. + */ + public abstract V ternaryOperation(AbstractInsnNode insn, V value1, V value2, V value3) + throws AnalyzerException; + + /** + * Interprets a bytecode instruction with a variable number of arguments. This method is called + * for the following opcodes: + * + *
INVOKEVIRTUAL, INVOKESPECIAL, INVOKESTATIC, INVOKEINTERFACE, MULTIANEWARRAY and + * INVOKEDYNAMIC + * + * @param insn the bytecode instruction to be interpreted. + * @param values the arguments of the instruction to be interpreted. + * @return the result of the interpretation of the given instruction. + * @throws AnalyzerException if an error occured during the interpretation. + */ + public abstract V naryOperation(AbstractInsnNode insn, List extends V> values) + throws AnalyzerException; + + /** + * Interprets a bytecode return instruction. This method is called for the following opcodes: + * + *
IRETURN, LRETURN, FRETURN, DRETURN, ARETURN
+ *
+ * @param insn the bytecode instruction to be interpreted.
+ * @param value the argument of the instruction to be interpreted.
+ * @param expected the expected return type of the analyzed method.
+ * @throws AnalyzerException if an error occured during the interpretation.
+ */
+ public abstract void returnOperation(AbstractInsnNode insn, V value, V expected)
+ throws AnalyzerException;
+
+ /**
+ * Merges two values. The merge operation must return a value that represents both values (for
+ * instance, if the two values are two types, the merged value must be a common super type of the
+ * two types. If the two values are integer intervals, the merged value must be an interval that
+ * contains the previous ones. Likewise for other types of values).
+ *
+ * @param value1 a value.
+ * @param value2 another value.
+ * @return the merged value. If the merged value is equal to value1, this method
+ * must return value1.
+ */
+ public abstract V merge(V value1, V value2);
+}
diff --git a/asm-analysis/src/main/java/org/objectweb/asm/tree/analysis/SimpleVerifier.java b/asm-analysis/src/main/java/org/objectweb/asm/tree/analysis/SimpleVerifier.java
new file mode 100644
index 000000000..3df6b5097
--- /dev/null
+++ b/asm-analysis/src/main/java/org/objectweb/asm/tree/analysis/SimpleVerifier.java
@@ -0,0 +1,372 @@
+// ASM: a very small and fast Java bytecode manipulation framework
+// 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.tree.analysis;
+
+import java.util.List;
+
+import org.objectweb.asm.Type;
+
+/**
+ * An extended {@link BasicVerifier} that performs more precise verifications. This verifier
+ * computes exact class types, instead of using a single "object reference" type (as done in {@link
+ * BasicVerifier}).
+ *
+ * @author Eric Bruneton
+ * @author Bing Ran
+ */
+public class SimpleVerifier extends BasicVerifier {
+
+ /** The type of the class that is verified. */
+ private final Type currentClass;
+
+ /** The type of the super class of the class that is verified. */
+ private final Type currentSuperClass;
+
+ /** The types of the interfaces directly implemented by the class that is verified. */
+ private final ListClassLoader
to be used in {@link #getClass}.
+ *
+ * @param loader the ClassLoader
to use.
+ */
+ public void setClassLoader(final ClassLoader loader) {
+ this.loader = loader;
+ }
+
+ @Override
+ public BasicValue newValue(final Type type) {
+ if (type == null) {
+ return BasicValue.UNINITIALIZED_VALUE;
+ }
+
+ boolean isArray = type.getSort() == Type.ARRAY;
+ if (isArray) {
+ switch (type.getElementType().getSort()) {
+ case Type.BOOLEAN:
+ case Type.CHAR:
+ case Type.BYTE:
+ case Type.SHORT:
+ return new BasicValue(type);
+ default:
+ break;
+ }
+ }
+
+ BasicValue value = super.newValue(type);
+ if (BasicValue.REFERENCE_VALUE.equals(value)) {
+ if (isArray) {
+ value = newValue(type.getElementType());
+ StringBuilder descriptor = new StringBuilder();
+ for (int i = 0; i < type.getDimensions(); ++i) {
+ descriptor.append('[');
+ }
+ descriptor.append(value.getType().getDescriptor());
+ value = new BasicValue(Type.getType(descriptor.toString()));
+ } else {
+ value = new BasicValue(type);
+ }
+ }
+ return value;
+ }
+
+ @Override
+ protected boolean isArrayValue(final BasicValue value) {
+ Type type = value.getType();
+ return type != null && (type.getSort() == Type.ARRAY || type.equals(NULL_TYPE));
+ }
+
+ @Override
+ protected BasicValue getElementValue(final BasicValue objectArrayValue) throws AnalyzerException {
+ Type arrayType = objectArrayValue.getType();
+ if (arrayType != null) {
+ if (arrayType.getSort() == Type.ARRAY) {
+ return newValue(Type.getType(arrayType.getDescriptor().substring(1)));
+ } else if (arrayType.equals(NULL_TYPE)) {
+ return objectArrayValue;
+ }
+ }
+ throw new AssertionError();
+ }
+
+ @Override
+ protected boolean isSubTypeOf(final BasicValue value, final BasicValue expected) {
+ Type expectedType = expected.getType();
+ Type type = value.getType();
+ switch (expectedType.getSort()) {
+ case Type.INT:
+ case Type.FLOAT:
+ case Type.LONG:
+ case Type.DOUBLE:
+ return type.equals(expectedType);
+ case Type.ARRAY:
+ case Type.OBJECT:
+ if (type.equals(NULL_TYPE)) {
+ return true;
+ } else if (type.getSort() == Type.OBJECT || type.getSort() == Type.ARRAY) {
+ return isAssignableFrom(expectedType, type);
+ } else {
+ return false;
+ }
+ default:
+ throw new AssertionError();
+ }
+ }
+
+ @Override
+ public BasicValue merge(final BasicValue value1, final BasicValue value2) {
+ if (!value1.equals(value2)) {
+ Type type1 = value1.getType();
+ Type type2 = value2.getType();
+ if (type1 != null
+ && (type1.getSort() == Type.OBJECT || type1.getSort() == Type.ARRAY)
+ && type2 != null
+ && (type2.getSort() == Type.OBJECT || type2.getSort() == Type.ARRAY)) {
+ if (type1.equals(NULL_TYPE)) {
+ return value2;
+ }
+ if (type2.equals(NULL_TYPE)) {
+ return value1;
+ }
+ if (isAssignableFrom(type1, type2)) {
+ return value1;
+ }
+ if (isAssignableFrom(type2, type1)) {
+ return value2;
+ }
+ int numDimensions = 0;
+ if (type1.getSort() == Type.ARRAY
+ && type2.getSort() == Type.ARRAY
+ && type1.getDimensions() == type2.getDimensions()
+ && type1.getElementType().getSort() == Type.OBJECT
+ && type2.getElementType().getSort() == Type.OBJECT) {
+ numDimensions = type1.getDimensions();
+ type1 = type1.getElementType();
+ type2 = type2.getElementType();
+ }
+ do {
+ if (type1 == null || isInterface(type1)) {
+ return newValue(Type.getObjectType("java/lang/Object"), numDimensions);
+ }
+ type1 = getSuperClass(type1);
+ if (isAssignableFrom(type1, type2)) {
+ return newValue(type1, numDimensions);
+ }
+ } while (true);
+ }
+ return BasicValue.UNINITIALIZED_VALUE;
+ }
+ return value1;
+ }
+
+ private BasicValue newValue(final Type type, final int dimensions) {
+ if (dimensions == 0) {
+ return newValue(type);
+ } else {
+ StringBuilder descriptor = new StringBuilder();
+ for (int i = 0; i < dimensions; ++i) {
+ descriptor.append('[');
+ }
+ descriptor.append(type.getDescriptor());
+ return newValue(Type.getType(descriptor.toString()));
+ }
+ }
+
+ /**
+ * Returns whether the given type corresponds to the type of an interface. The default
+ * implementation of this method loads the class and uses the reflection API to return its result
+ * (unless the given type corresponds to the class being verified).
+ *
+ * @param type a type.
+ * @return whether 'type' corresponds to an interface.
+ */
+ protected boolean isInterface(final Type type) {
+ if (currentClass != null && type.equals(currentClass)) {
+ return isInterface;
+ }
+ return getClass(type).isInterface();
+ }
+
+ /**
+ * Returns the type corresponding to the super class of the given type. The default implementation
+ * of this method loads the class and uses the reflection API to return its result (unless the
+ * given type corresponds to the class being verified).
+ *
+ * @param type a type.
+ * @return the type corresponding to the super class of 'type'.
+ */
+ protected Type getSuperClass(final Type type) {
+ if (currentClass != null && type.equals(currentClass)) {
+ return currentSuperClass;
+ }
+ Class> superClass = getClass(type).getSuperclass();
+ return superClass == null ? null : Type.getType(superClass);
+ }
+
+ /**
+ * Returns whether the class corresponding to the first argument is either the same as, or is a
+ * superclass or superinterface of the class corresponding to the second argument. The default
+ * implementation of this method loads the classes and uses the reflection API to return its
+ * result (unless the result can be computed from the class being verified, and the types of its
+ * super classes and implemented interfaces).
+ *
+ * @param type1 a type.
+ * @param type2 another type.
+ * @return whether the class corresponding to 'type1' is either the same as, or is a superclass or
+ * superinterface of the class corresponding to 'type2'.
+ */
+ protected boolean isAssignableFrom(final Type type1, final Type type2) {
+ if (type1.equals(type2)) {
+ return true;
+ }
+ if (currentClass != null && type1.equals(currentClass)) {
+ if (getSuperClass(type2) == null) {
+ return false;
+ } else {
+ if (isInterface) {
+ return type2.getSort() == Type.OBJECT || type2.getSort() == Type.ARRAY;
+ }
+ return isAssignableFrom(type1, getSuperClass(type2));
+ }
+ }
+ if (currentClass != null && type2.equals(currentClass)) {
+ if (isAssignableFrom(type1, currentSuperClass)) {
+ return true;
+ }
+ if (currentClassInterfaces != null) {
+ for (int i = 0; i < currentClassInterfaces.size(); ++i) {
+ Type currentClassInterface = currentClassInterfaces.get(i);
+ if (isAssignableFrom(type1, currentClassInterface)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+ Class> class1 = getClass(type1);
+ if (class1.isInterface()) {
+ class1 = Object.class;
+ }
+ return class1.isAssignableFrom(getClass(type2));
+ }
+
+ /**
+ * Loads the class corresponding to the given type. The class is loaded with the class loader
+ * specified with {@link #setClassLoader}, or with the class loader of this class if no class
+ * loader was specified.
+ *
+ * @param type a type.
+ * @return the class corresponding to 'type'.
+ */
+ protected Class> getClass(final Type type) {
+ try {
+ if (type.getSort() == Type.ARRAY) {
+ return Class.forName(type.getDescriptor().replace('/', '.'), false, loader);
+ }
+ return Class.forName(type.getClassName(), false, loader);
+ } catch (ClassNotFoundException e) {
+ throw new TypeNotPresentException(e.toString(), e);
+ }
+ }
+}
diff --git a/asm-analysis/src/main/java/org/objectweb/asm/tree/analysis/SmallSet.java b/asm-analysis/src/main/java/org/objectweb/asm/tree/analysis/SmallSet.java
new file mode 100644
index 000000000..3ca92e7ee
--- /dev/null
+++ b/asm-analysis/src/main/java/org/objectweb/asm/tree/analysis/SmallSet.java
@@ -0,0 +1,190 @@
+// ASM: a very small and fast Java bytecode manipulation framework
+// 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.tree.analysis;
+
+import java.util.AbstractSet;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+import java.util.Set;
+
+/**
+ * An immutable set of at most two elements, optimized for speed compared to a generic set
+ * implementation.
+ *
+ * @author Eric Bruneton
+ */
+final class SmallSet
+ * 1: i = 0;
+ * 2: if (...) {
+ * 3: i = 1;
+ * 4: }
+ * 5: return i;
+ *
+ */
+ public final Set
-ClassReader cr = new ClassReader(bytecode); -ClassNode cn = new ClassNode(); -cr.accept(cn, ClassReader.SKIP_DEBUG); +ClassReader classReader = new ClassReader(bytecode); +ClassNode classNode = new ClassNode(); +classReader.accept(classNode, ClassReader.SKIP_DEBUG); -List methods = cn.methods; -for (int i = 0; i < methods.size(); ++i) { - MethodNode method = (MethodNode) methods.get(i); - if (method.instructions.size() > 0) { - Analyzer a = new Analyzer(new BasicInterpreter()); - a.analyze(cn.name, method); - Frame[] frames = a.getFrames(); - // Elements of the frames arrray now contains info for each instruction - // from the analyzed method. BasicInterpreter creates BasicValue, that - // is using simplified type system that distinguishes the UNINITIALZED, - // INT, FLOAT, LONG, DOUBLE, REFERENCE and RETURNADDRESS types. - ... - } +for (MethodNode method : classNode.methods) { + if (method.instructions.size() > 0) { + Analyzer analyzer = new Analyzer(new BasicInterpreter()); + analyzer.analyze(classNode.name, method); + Frame[] frames = analyzer.getFrames(); + // Elements of the frames array now contains info for each instruction + // from the analyzed method. BasicInterpreter creates BasicValue, that + // is using simplified type system that distinguishes the UNINITIALZED, + // INT, FLOAT, LONG, DOUBLE, REFERENCE and RETURNADDRESS types. + ... + } }diff --git a/asm-analysis/src/test/java/org/objectweb/asm/tree/analysis/AnalyzerTest.java b/asm-analysis/src/test/java/org/objectweb/asm/tree/analysis/AnalyzerTest.java new file mode 100644 index 000000000..4cbb8c4e8 --- /dev/null +++ b/asm-analysis/src/test/java/org/objectweb/asm/tree/analysis/AnalyzerTest.java @@ -0,0 +1,946 @@ +// ASM: a very small and fast Java bytecode manipulation framework +// 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.tree.analysis; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.fail; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.Label; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.tree.MethodNode; + +/** + * Analyzer tests. + * + * @author Eric Bruneton + */ +public class AnalyzerTest { + + protected ClassWriter classWriter; + + protected MethodVisitor methodVisitor; + + private Label startLabel; + + @BeforeEach + public void setUp() throws Exception { + classWriter = new ClassWriter(0); + classWriter.visit(Opcodes.V1_1, Opcodes.ACC_PUBLIC, "C", null, "java/lang/Object", null); + methodVisitor = classWriter.visitMethod(Opcodes.ACC_PUBLIC, "
try{}finally
form imaginable:
+ *
+ * + * public void a() { + * int a = 0; + * try { + * a++; + * } finally { + * a--; + * } + * } + *+ */ + @Test + 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(); + ISTORE(1); + + /* L0: body of try block. */ + LABEL(L0); + IINC(1, 1); + GOTO(L1); + + /* L2: exception handler. */ + LABEL(L2); + ASTORE(3); + JSR(L3); + ALOAD(3); + ATHROW(); + + /* L3: subroutine. */ + LABEL(L3); + ASTORE(2); + IINC(1, -1); + PUSH(); + PUSH(); + RET(2); + + /* L1: non-exceptional exit from try block. */ + LABEL(L1); + JSR(L3); + PUSH(); + PUSH(); + LABEL(L4); + RETURN(); + + TRYCATCH(L0, L2, L2); + TRYCATCH(L1, L4, L2); + + assertMaxs(4, 4); + } + + /** + * Tests a method which has an if/else-if w/in the finally clause: + * + *
+ * public void a() { + * int a = 0; + * try { + * a++; + * } finally { + * if (a == 0) + * a += 2; + * else + * a += 3; + * } + * } + *+ */ + @Test + 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(); + ISTORE(1); + + /* L0: body of try block. */ + LABEL(L0); + IINC(1, 1); + GOTO(L1); + + /* L2: exception handler. */ + LABEL(L2); + ASTORE(3); + JSR(L3); + PUSH(); + PUSH(); + ALOAD(3); + ATHROW(); + + /* L3: subroutine. */ + LABEL(L3); + ASTORE(2); + PUSH(); + PUSH(); + ILOAD(1); + IFNE(L4); + IINC(1, 2); + GOTO(L5); + + LABEL(L4); + IINC(1, 3); + + LABEL(L5); + RET(2); + + /* L1: non-exceptional exit from try block. */ + LABEL(L1); + JSR(L3); + LABEL(L6); + RETURN(); + + TRYCATCH(L0, L2, L2); + TRYCATCH(L1, L6, L2); + + assertMaxs(5, 4); + } + + /** + * Tests a simple nested finally: + * + *
+ * public void a1() { + * int a = 0; + * try { + * a += 1; + * } finally { + * try { + * a += 2; + * } finally { + * a += 3; + * } + * } + * } + *+ */ + @Test + 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(); + ISTORE(1); + + // L0: Body of try block. + LABEL(L0); + IINC(1, 1); + JSR(L3); + GOTO(L1); + + // L2: First exception handler. + LABEL(L2); + ASTORE(4); + JSR(L3); + ALOAD(4); + ATHROW(); + + // L3: First subroutine. + LABEL(L3); + ASTORE(2); + IINC(1, 2); + JSR(L4); + PUSH(); + PUSH(); + RET(2); + + // L5: Second exception handler. + LABEL(L5); + ASTORE(5); + JSR(L4); + ALOAD(5); + ATHROW(); + + // L4: Second subroutine. + LABEL(L4); + ASTORE(3); + PUSH(); + PUSH(); + IINC(1, 3); + RET(3); + + // L1: On normal exit, try block jumps here. + LABEL(L1); + RETURN(); + + TRYCATCH(L0, L2, L2); + TRYCATCH(L3, L5, L5); + + assertMaxs(5, 6); + } + + /** + * 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. + * + *
+ * public void a1() { + * int a = 0; + * while (true) { + * try { + * a += 1; + * } finally { + * a += 2; + * break; + * } + * } + * } + *+ */ + @Test + 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(); + ISTORE(1); + + // L0: while loop header/try block. + LABEL(L0); + IINC(1, 1); + JSR(L1); + GOTO(L2); + + // L3: implicit catch block. + LABEL(L3); + ASTORE(2); + JSR(L1); + PUSH(); + PUSH(); + ALOAD(2); + ATHROW(); + + // L1: subroutine which does not return. + LABEL(L1); + ASTORE(3); + IINC(1, 2); + GOTO(L4); + + // L2: end of the loop, goes back to the top. + LABEL(L2); + GOTO(L0); + + // L4: + LABEL(L4); + RETURN(); + + TRYCATCH(L0, L3, L3); + + assertMaxs(1, 4); + } + + /** + * This tests a subroutine which has no ret statement, but ends in a "return" instead. + * + *
+ * ACONST_NULL + * JSR L0 + * L0: + * ASTORE 0 + * ASTORE 0 + * RETURN + *+ */ + @Test + public void testSubroutineWithNoRet2() { + Label L0 = new Label(); + Label L1 = new Label(); + + ACONST_NULL(); + JSR(L0); + NOP(); + LABEL(L0); + ASTORE(0); + ASTORE(0); + RETURN(); + LABEL(L1); + methodVisitor.visitLocalVariable("i", "I", null, L0, L1, 1); + + assertMaxs(2, 2); + } + + /** + * 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. + * + *
+ * public void a1() { + * int a = 0; + * while (null == null) { + * try { + * a += 1; + * } finally { + * a += 2; + * break; + * } + * } + * } + *+ */ + @Test + 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(); + ISTORE(1); + + // L5: while loop header. + LABEL(L5); + ACONST_NULL(); + IFNONNULL(L4); + + // L0: try block. + LABEL(L0); + IINC(1, 1); + JSR(L1); + GOTO(L2); + + // L3: implicit catch block. + LABEL(L3); + ASTORE(2); + JSR(L1); + ALOAD(2); + PUSH(); + PUSH(); + ATHROW(); + + // L1: subroutine which does not return. + LABEL(L1); + ASTORE(3); + IINC(1, 2); + GOTO(L4); + + // L2: end of the loop, goes back to the top. + LABEL(L2); + GOTO(L0); + + // L4: + LABEL(L4); + RETURN(); + + TRYCATCH(L0, L3, L3); + + assertMaxs(1, 4); + } + + /** + * Tests a nested try/finally with implicit exit from one subroutine to the other subroutine. + * Equivalent to the following java code: + * + *
+ * void m(boolean b) { + * try { + * return; + * } finally { + * while (b) { + * try { + * return; + * } finally { + * // NOTE --- this break avoids the second return above (weird) + * if (b) + * break; + * } + * } + * } + * } + *+ * + * This example is from the paper, "Subroutine Inlining and Bytecode Abstraction to Simplify + * Static and Dynamic Analysis" by Cyrille Artho and Armin Biere. + */ + @Test + 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(); + ISTORE(1); + + // T1: first try. + LABEL(T1); + JSR(S1); + RETURN(); + + // C1: exception handler for first try. + LABEL(C1); + ASTORE(e1); + JSR(S1); + PUSH(); + PUSH(); + ALOAD(e1); + ATHROW(); + + // S1: first finally handler. + LABEL(S1); + ASTORE(r1); + PUSH(); + PUSH(); + GOTO(W); + + // L: body of while loop, also second try. + LABEL(L); + JSR(S2); + RETURN(); + + // C2: exception handler for second try. + LABEL(C2); + ASTORE(e2); + PUSH(); + PUSH(); + JSR(S2); + ALOAD(e2); + ATHROW(); + + // S2: second finally handler. + LABEL(S2); + ASTORE(r2); + ILOAD(b); + IFNE(X); + RET(r2); + + // W: test for the while loop. + LABEL(W); + ILOAD(b); + IFNE(L); // falls through to X. + + // X: exit from finally{} block. + LABEL(X); + RET(r1); + + TRYCATCH(T1, C1, C1); + TRYCATCH(L, C2, C2); + + assertMaxs(5, 6); + } + + @Test + public void testImplicitExitToAnotherSubroutine2() { + Label L1 = new Label(); + Label L2 = new Label(); + Label L3 = new Label(); + + ICONST_0(); + ISTORE(1); + JSR(L1); + RETURN(); + + LABEL(L1); + ASTORE(2); + JSR(L2); + GOTO(L3); + + LABEL(L2); + ASTORE(3); + ILOAD(1); + IFNE(L3); + RET(3); + + LABEL(L3); + RET(2); + + assertMaxs(1, 4); + } + + /** + * 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. + */ + @Test + public void testInterleavedCode() { + Label L1 = new Label(); + Label L2 = new Label(); + Label L3 = new Label(); + Label L4 = new Label(); + + ICONST_0(); + ISTORE(1); + JSR(L1); + GOTO(L2); + + // L1: subroutine 1. + LABEL(L1); + ASTORE(2); + IINC(1, 1); + GOTO(L3); + + // L2: second part of main subroutine. + LABEL(L2); + IINC(1, 2); + GOTO(L4); + + // L3: second part of subroutine 1. + LABEL(L3); + IINC(1, 4); + PUSH(); + PUSH(); + RET(2); + + // L4: third part of main subroutine. + LABEL(L4); + PUSH(); + PUSH(); + RETURN(); + + assertMaxs(4, 3); + } + + /** + * 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: + * + *
+ * 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; + * } + * } + *+ */ + @Test + 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(); + ISTORE(1); + + // T1: first try. + LABEL(T1); + JSR(S1); + RETURN(); + + // C1: exception handler for first try. + LABEL(C1); + ASTORE(e1); + JSR(S1); + ALOAD(e1); + ATHROW(); + + // S1: first finally handler. + LABEL(S1); + ASTORE(r1); + GOTO(W); + + // L: body of while loop, also second try. + LABEL(L); + JSR(S2); + PUSH(); + PUSH(); + RETURN(); + + // C2: exception handler for second try. + LABEL(C2); + ASTORE(e2); + JSR(S2); + ALOAD(e2); + ATHROW(); + + // S2: second finally handler. + LABEL(S2); + ASTORE(r2); + ILOAD(b); + IFNE(X); + PUSH(); + PUSH(); + RET(r2); + + // W: test for the while loop. + LABEL(W); + ILOAD(b); + IFNE(L); // falls through to X. + + // X: exit from finally{} block. + LABEL(X); + RET(r1); + + // OC: outermost catch. + LABEL(OC); + IINC(b, 3); + RETURN(); + + TRYCATCH(T1, C1, C1); + TRYCATCH(L, C2, C2); + TRYCATCH(T1, OC, OC); + + assertMaxs(4, 6); + } + + /** + * Tests an example coming from distilled down version of + * com/sun/corba/ee/impl/protocol/CorbaClientDelegateImpl from GlassFish 2. See issue #317823. + */ + @Test + public void testGlassFish2CorbaClientDelegateImplExample() { + 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(); + Label l7 = new Label(); + Label l8 = new Label(); + Label l9 = new Label(); + Label l10 = new Label(); + Label l11 = new Label(); + Label l12 = new Label(); + + LABEL(l0); + JSR(l9); + LABEL(l1); + GOTO(l10); + LABEL(l2); + POP(); + JSR(l9); + LABEL(l3); + ACONST_NULL(); + ATHROW(); + LABEL(l9); + ASTORE(1); + RET(1); + LABEL(l10); + ACONST_NULL(); + ACONST_NULL(); + ACONST_NULL(); + POP(); + POP(); + POP(); + LABEL(l4); + GOTO(l11); + LABEL(l5); + POP(); + GOTO(l11); + ACONST_NULL(); + ATHROW(); + LABEL(l11); + ICONST_0(); + IFNE(l0); + JSR(l12); + LABEL(l6); + RETURN(); + LABEL(l7); + POP(); + JSR(l12); + LABEL(l8); + ACONST_NULL(); + ATHROW(); + LABEL(l12); + ASTORE(2); + RET(2); + + TRYCATCH(l0, l1, l2); + TRYCATCH(l2, l3, l2); + TRYCATCH(l0, l4, l5); + TRYCATCH(l0, l6, l7); + TRYCATCH(l7, l8, l7); + + assertMaxs(3, 3); + } + + protected void assertMaxs(final int maxStack, final int maxLocals) { + methodVisitor.visitMaxs(maxStack, maxLocals); + methodVisitor.visitEnd(); + classWriter.visitEnd(); + byte[] classFile = classWriter.toByteArray(); + ClassReader classReader = new ClassReader(classFile); + classReader.accept( + new ClassVisitor(Opcodes.ASM5) { + @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 MethodNode(Opcodes.ASM5, access, name, desc, signature, exceptions) { + @Override + public void visitEnd() { + Analyzer