Hi all

I'am about BCEL bug https://issues.apache.org/jira/browse/BCEL-170

The bug is about signature for a method with generics in signature. In this 
case we may extract this signature from method attributes so the we will be 
able to call Type.getArgumentTypes on this signature that leads to 
ClassFormatException.

I'am not sure what behaviour right exactly. But for now it looks like Type is 
only represents class name and generics couldn't be stored in so I have patch 
to avoid exception and make it work.

See patch in attachment with fix + tests

-- 
Kind regards
Sergey Mashkov
Index: src/test/java/org/apache/bcel/TestSignaturesWithGenericsTestCase.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>ISO-8859-1
===================================================================
--- src/test/java/org/apache/bcel/TestSignaturesWithGenericsTestCase.java	(revision )
+++ src/test/java/org/apache/bcel/TestSignaturesWithGenericsTestCase.java	(revision )
@@ -0,0 +1,102 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.apache.bcel;
+
+import org.apache.bcel.classfile.Attribute;
+import org.apache.bcel.classfile.JavaClass;
+import org.apache.bcel.classfile.Method;
+import org.apache.bcel.classfile.Signature;
+import org.apache.bcel.generic.ObjectType;
+import org.apache.bcel.generic.Type;
+
+import java.util.Arrays;
+import java.util.NoSuchElementException;
+
+/**
+ * Test for BCEL-170
+ */
+public class TestSignaturesWithGenericsTestCase extends AbstractTestCase {
+
+    public void testReturnTypes() throws Exception {
+        JavaClass clazz = getTestClass("org.apache.bcel.data.ClassWithGenerics");
+
+        assertNotNull(findMethod(clazz, "method1").getReturnType());
+        assertNotNull(findMethod(clazz, "method2").getReturnType());
+
+        assertEquals(type("java.util.List"), findMethod(clazz, "method1").getReturnType());
+        assertEquals(type("java.util.List"), findMethod(clazz, "method2").getReturnType());
+
+        assertNotNull(Type.getReturnType(findSignature(findMethod(clazz, "method1")).getSignature()));
+        assertNotNull(Type.getReturnType(findSignature(findMethod(clazz, "method2")).getSignature()));
+
+        assertEquals(type("java.util.List"), Type.getReturnType(findSignature(clazz, "method1").getSignature()));
+        assertEquals(type("java.util.List"), Type.getReturnType(findSignature(clazz, "method2").getSignature()));
+    }
+
+    public void testArgumentTypes() throws Exception {
+        JavaClass clazz = getTestClass("org.apache.bcel.data.ClassWithGenerics");
+
+        assertNotNull(findMethod(clazz, "method1").getArgumentTypes());
+        assertNotNull(findMethod(clazz, "method2").getArgumentTypes());
+
+        assertEquals(Arrays.asList(type("java.util.List")), Arrays.asList(findMethod(clazz, "method1").getArgumentTypes()));
+        assertEquals(Arrays.asList(type("java.util.List")), Arrays.asList(findMethod(clazz, "method2").getArgumentTypes()));
+
+        assertNotNull(Type.getArgumentTypes(findSignature(findMethod(clazz, "method1")).getSignature()));
+        assertNotNull(Type.getArgumentTypes(findSignature(findMethod(clazz, "method2")).getSignature()));
+
+        assertEquals(Arrays.asList(type("java.util.List")),
+                Arrays.asList(Type.getArgumentTypes(findSignature(clazz, "method1").getSignature())));
+        assertEquals(Arrays.asList(type("java.util.List")),
+                Arrays.asList(Type.getArgumentTypes(findSignature(clazz, "method2").getSignature())));
+
+    }
+
+    private static Type type(String className) {
+        return ObjectType.getInstance(className);
+    }
+
+    private static Method findMethod(JavaClass clazz, String name) {
+        return findMethod(clazz.getMethods(), name);
+    }
+
+    private static Method findMethod(Method[] methods, String name) {
+        for (Method method : methods) {
+            if (method.getName().equals(name)) {
+                return method;
+            }
+        }
+
+        throw new NoSuchElementException("method not found: " + name);
+    }
+
+    private static Signature findSignature(Method method) {
+        for (Attribute attribute : method.getAttributes()) {
+            if (attribute instanceof Signature) {
+                return (Signature) attribute;
+            }
+        }
+
+        throw new NoSuchElementException("no signatures for method: " + method.getName());
+    }
+
+    private static Signature findSignature(JavaClass clazz, String methodName) {
+        return findSignature(findMethod(clazz, methodName));
+    }
+}
Index: src/test/java/org/apache/bcel/data/ClassWithGenerics.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>ISO-8859-1
===================================================================
--- src/test/java/org/apache/bcel/data/ClassWithGenerics.java	(revision )
+++ src/test/java/org/apache/bcel/data/ClassWithGenerics.java	(revision )
@@ -0,0 +1,35 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.apache.bcel.data;
+
+import java.util.List;
+
+public class ClassWithGenerics<T> {
+    public List<String> method1(List<String> arg) {
+        return null;
+    }
+
+    public List<T> method2(List<T> arg) {
+        return arg;
+    }
+
+    public static <K> List<K> method3(List<K> arg) {
+        return arg;
+    }
+}
Index: src/main/java/org/apache/bcel/generic/Type.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>ISO-8859-1
Subsystem: com.intellij.openapi.diff.impl.patch.BaseRevisionTextPatchEP
<+>/*\n * Licensed to the Apache Software Foundation (ASF) under one or more\n * contributor license agreements.  See the NOTICE file distributed with\n * this work for additional information regarding copyright ownership.\n * The ASF licenses this file to You under the Apache License, Version 2.0\n * (the \"License\"); you may not use this file except in compliance with\n * the License.  You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n *  Unless required by applicable law or agreed to in writing, software\n *  distributed under the License is distributed on an \"AS IS\" BASIS,\n *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *  See the License for the specific language governing permissions and\n *  limitations under the License. \n *\n */\npackage org.apache.bcel.generic;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\nimport org.apache.bcel.Constants;\nimport org.apache.bcel.classfile.ClassFormatException;\nimport org.apache.bcel.classfile.Utility;\n\n/** \n * Abstract super class for all possible java types, namely basic types\n * such as int, object types like String and array types, e.g. int[]\n *\n * @version $Id: Type.java 1481383 2013-06-28 16:06:24Z dbrosius $\n * @author  <A HREF=\"mailto:m.d...@gmx.de\">M. Dahm</A>\n */\npublic abstract class Type implements java.io.Serializable {\n\n    private static final long serialVersionUID = -1985077286871826895L;\n    protected final byte type;\n    protected String signature; // signature for the type\n    /** Predefined constants\n     */\n    public static final BasicType VOID = new BasicType(Constants.T_VOID);\n    public static final BasicType BOOLEAN = new BasicType(Constants.T_BOOLEAN);\n    public static final BasicType INT = new BasicType(Constants.T_INT);\n    public static final BasicType SHORT = new BasicType(Constants.T_SHORT);\n    public static final BasicType BYTE = new BasicType(Constants.T_BYTE);\n    public static final BasicType LONG = new BasicType(Constants.T_LONG);\n    public static final BasicType DOUBLE = new BasicType(Constants.T_DOUBLE);\n    public static final BasicType FLOAT = new BasicType(Constants.T_FLOAT);\n    public static final BasicType CHAR = new BasicType(Constants.T_CHAR);\n    public static final ObjectType OBJECT = ObjectType.getInstance(\"java.lang.Object\");\n    public static final ObjectType CLASS = ObjectType.getInstance(\"java.lang.Class\");\n    public static final ObjectType STRING = ObjectType.getInstance(\"java.lang.String\");\n    public static final ObjectType STRINGBUFFER = ObjectType.getInstance(\"java.lang.StringBuffer\");\n    public static final ObjectType THROWABLE = ObjectType.getInstance(\"java.lang.Throwable\");\n    public static final Type[] NO_ARGS = new Type[0];\n    public static final ReferenceType NULL = new ReferenceType() {\n\n        private static final long serialVersionUID = 4526765862386946282L;\n    };\n    public static final Type UNKNOWN = new Type(Constants.T_UNKNOWN, \"<unknown object>\") {\n\n        private static final long serialVersionUID = 1321113605813486066L;\n    };\n\n\n    protected Type(byte t, String s) {\n        type = t;\n        signature = s;\n    }\n\n\n    /**\n     * @return hashcode of Type\n     */\n    @Override\n    public int hashCode() {\n    \treturn type ^ signature.hashCode();\n    }\n    \n    \n    /**\n     * @return whether the Types are equal\n     */\n    @Override\n    public boolean equals(Object o) {\n  \t\tif (o instanceof Type) {\n  \t\t\tType t = (Type)o;\n  \t\t\treturn (type == t.type) && signature.equals(t.signature);\n  \t\t}\n  \t\treturn false;\n    }\n    \n    \n    /**\n     * @return signature for given type.\n     */\n    public String getSignature() {\n        return signature;\n    }\n\n\n    /**\n     * @return type as defined in Constants\n     */\n    public byte getType() {\n        return type;\n    }\n\n\n    /**\n     * @return stack size of this type (2 for long and double, 0 for void, 1 otherwise)\n     */\n    public int getSize() {\n        switch (type) {\n            case Constants.T_DOUBLE:\n            case Constants.T_LONG:\n                return 2;\n            case Constants.T_VOID:\n                return 0;\n            default:\n                return 1;\n        }\n    }\n\n\n    /**\n     * @return Type string, e.g. `int[]'\n     */\n    @Override\n    public String toString() {\n        return ((this.equals(Type.NULL) || (type >= Constants.T_UNKNOWN))) ? signature : Utility\n                .signatureToString(signature, false);\n    }\n\n\n    /**\n     * Convert type to Java method signature, e.g. int[] f(java.lang.String x)\n     * becomes (Ljava/lang/String;)[I\n     *\n     * @param return_type what the method returns\n     * @param arg_types what are the argument types\n     * @return method signature for given type(s).\n     */\n    public static String getMethodSignature( Type return_type, Type[] arg_types ) {\n        StringBuilder buf = new StringBuilder(\"(\");\n        if (arg_types != null) {\n            for (Type arg_type : arg_types) {\n                buf.append(arg_type.getSignature());\n            }\n        }\n        buf.append(')');\n        buf.append(return_type.getSignature());\n        return buf.toString();\n    }\n\n    private static final ThreadLocal<Integer> consumed_chars = new ThreadLocal<Integer>() {\n\n        @Override\n        protected Integer initialValue() {\n            return Integer.valueOf(0);\n        }\n    };//int consumed_chars=0; // Remember position in string, see getArgumentTypes\n\n\n    private static int unwrap( ThreadLocal<Integer> tl ) {\n        return tl.get().intValue();\n    }\n\n\n    private static void wrap( ThreadLocal<Integer> tl, int value ) {\n        tl.set(Integer.valueOf(value));\n    }\n\n\n    /**\n     * Convert signature to a Type object.\n     * @param signature signature string such as Ljava/lang/String;\n     * @return type object\n     */\n    public static final Type getType( String signature ) throws StringIndexOutOfBoundsException {\n        byte type = Utility.typeOfSignature(signature);\n        if (type <= Constants.T_VOID) {\n            //corrected concurrent private static field acess\n            wrap(consumed_chars, 1);\n            return BasicType.getType(type);\n        } else if (type == Constants.T_ARRAY) {\n            int dim = 0;\n            do { // Count dimensions\n                dim++;\n            } while (signature.charAt(dim) == '[');\n            // Recurse, but just once, if the signature is ok\n            Type t = getType(signature.substring(dim));\n            //corrected concurrent private static field acess\n            //  consumed_chars += dim; // update counter - is replaced by\n            int _temp = unwrap(consumed_chars) + dim;\n            wrap(consumed_chars, _temp);\n            return new ArrayType(t, dim);\n        } else { // type == T_REFERENCE\n            int index = signature.indexOf(';'); // Look for closing `;'\n            if (index < 0) {\n                throw new ClassFormatException(\"Invalid signature: \" + signature);\n            }\n            //corrected concurrent private static field acess\n            wrap(consumed_chars, index + 1); // \"Lblabla;\" `L' and `;' are removed\n            return ObjectType.getInstance(signature.substring(1, index).replace('/', '.'));\n        }\n    }\n\n\n    /**\n     * Convert return value of a method (signature) to a Type object.\n     *\n     * @param signature signature string such as (Ljava/lang/String;)V\n     * @return return type\n     */\n    public static Type getReturnType( String signature ) {\n        try {\n            // Read return type after `)'\n            int index = signature.lastIndexOf(')') + 1;\n            return getType(signature.substring(index));\n        } catch (StringIndexOutOfBoundsException e) { // Should never occur\n            throw new ClassFormatException(\"Invalid method signature: \" + signature, e);\n        }\n    }\n\n\n    /**\n     * Convert arguments of a method (signature) to an array of Type objects.\n     * @param signature signature string such as (Ljava/lang/String;)V\n     * @return array of argument types\n     */\n    public static Type[] getArgumentTypes( String signature ) {\n        List<Type> vec = new ArrayList<Type>();\n        int index;\n        Type[] types;\n        try { // Read all declarations between for `(' and `)'\n            if (signature.charAt(0) != '(') {\n                throw new ClassFormatException(\"Invalid method signature: \" + signature);\n            }\n            index = 1; // current string position\n            while (signature.charAt(index) != ')') {\n                vec.add(getType(signature.substring(index)));\n                //corrected concurrent private static field acess\n                index += unwrap(consumed_chars); // update position\n            }\n        } catch (StringIndexOutOfBoundsException e) { // Should never occur\n            throw new ClassFormatException(\"Invalid method signature: \" + signature, e);\n        }\n        types = new Type[vec.size()];\n        vec.toArray(types);\n        return types;\n    }\n\n\n    /** Convert runtime java.lang.Class to BCEL Type object.\n     * @param cl Java class\n     * @return corresponding Type object\n     */\n    public static Type getType( java.lang.Class<?> cl ) {\n        if (cl == null) {\n            throw new IllegalArgumentException(\"Class must not be null\");\n        }\n        /* That's an amzingly easy case, because getName() returns\n         * the signature. That's what we would have liked anyway.\n         */\n        if (cl.isArray()) {\n            return getType(cl.getName());\n        } else if (cl.isPrimitive()) {\n            if (cl == Integer.TYPE) {\n                return INT;\n            } else if (cl == Void.TYPE) {\n                return VOID;\n            } else if (cl == Double.TYPE) {\n                return DOUBLE;\n            } else if (cl == Float.TYPE) {\n                return FLOAT;\n            } else if (cl == Boolean.TYPE) {\n                return BOOLEAN;\n            } else if (cl == Byte.TYPE) {\n                return BYTE;\n            } else if (cl == Short.TYPE) {\n                return SHORT;\n            } else if (cl == Byte.TYPE) {\n                return BYTE;\n            } else if (cl == Long.TYPE) {\n                return LONG;\n            } else if (cl == Character.TYPE) {\n                return CHAR;\n            } else {\n                throw new IllegalStateException(\"Ooops, what primitive type is \" + cl);\n            }\n        } else { // \"Real\" class\n            return ObjectType.getInstance(cl.getName());\n        }\n    }\n\n\n    /**\n     * Convert runtime java.lang.Class[] to BCEL Type objects.\n     * @param classes an array of runtime class objects\n     * @return array of corresponding Type objects\n     */\n    public static Type[] getTypes( java.lang.Class<?>[] classes ) {\n        Type[] ret = new Type[classes.length];\n        for (int i = 0; i < ret.length; i++) {\n            ret[i] = getType(classes[i]);\n        }\n        return ret;\n    }\n\n\n    public static String getSignature( java.lang.reflect.Method meth ) {\n        StringBuilder sb = new StringBuilder(\"(\");\n        Class<?>[] params = meth.getParameterTypes(); // avoid clone\n        for (int j = 0; j < params.length; j++) {\n            sb.append(getType(params[j]).getSignature());\n        }\n        sb.append(\")\");\n        sb.append(getType(meth.getReturnType()).getSignature());\n        return sb.toString();\n    }\n    \n    static int size(int coded) {\n    \treturn coded & 3;\n    }\n    \n    static int consumed(int coded) {\n    \treturn coded >> 2;\n    }\n    \n    static int encode(int size, int consumed) {\n    \treturn consumed << 2 | size;\n    }\n    \n    static int getArgumentTypesSize( String signature ) {\n        int res = 0;\n        int index;\n        try { // Read all declarations between for `(' and `)'\n            if (signature.charAt(0) != '(') {\n                throw new ClassFormatException(\"Invalid method signature: \" + signature);\n            }\n            index = 1; // current string position\n            while (signature.charAt(index) != ')') {\n                int coded = getTypeSize(signature.substring(index));\n                res += size(coded);\n                index += consumed(coded);\n            }\n        } catch (StringIndexOutOfBoundsException e) { // Should never occur\n            throw new ClassFormatException(\"Invalid method signature: \" + signature, e);\n        }\n        return res;\n    }\n    \n    static final int getTypeSize( String signature ) throws StringIndexOutOfBoundsException {\n        byte type = Utility.typeOfSignature(signature);\n        if (type <= Constants.T_VOID) {\n            return encode(BasicType.getType(type).getSize(), 1);\n        } else if (type == Constants.T_ARRAY) {\n            int dim = 0;\n            do { // Count dimensions\n                dim++;\n            } while (signature.charAt(dim) == '[');\n            // Recurse, but just once, if the signature is ok\n            int consumed = consumed(getTypeSize(signature.substring(dim)));\n            return encode(1, dim + consumed);\n        } else { // type == T_REFERENCE\n            int index = signature.indexOf(';'); // Look for closing `;'\n            if (index < 0) {\n                throw new ClassFormatException(\"Invalid signature: \" + signature);\n            }\n            return encode(1, index + 1);\n        }\n    }\n\n\n\tstatic int getReturnTypeSize(String signature) {\n\t\tint index = signature.lastIndexOf(')') + 1;\n\t\treturn Type.size(getTypeSize(signature.substring(index)));\n\t}\n}\n
===================================================================
--- src/main/java/org/apache/bcel/generic/Type.java	(revision 1497825)
+++ src/main/java/org/apache/bcel/generic/Type.java	(revision )
@@ -17,13 +17,13 @@
  */
 package org.apache.bcel.generic;
 
-import java.util.ArrayList;
-import java.util.List;
-
 import org.apache.bcel.Constants;
 import org.apache.bcel.classfile.ClassFormatException;
 import org.apache.bcel.classfile.Utility;
 
+import java.util.ArrayList;
+import java.util.List;
+
 /** 
  * Abstract super class for all possible java types, namely basic types
  * such as int, object types like String and array types, e.g. int[]
@@ -200,12 +200,48 @@
             if (index < 0) {
                 throw new ClassFormatException("Invalid signature: " + signature);
             }
+
+            String className = signature.substring(1, index);
+            int genericIndex;
+            if ((genericIndex = className.indexOf('<')) != -1) {
+                className = className.substring(0, genericIndex);
+                index = skipGenerics(signature, genericIndex + 1);
+            }
+
             //corrected concurrent private static field acess
             wrap(consumed_chars, index + 1); // "Lblabla;" `L' and `;' are removed
-            return ObjectType.getInstance(signature.substring(1, index).replace('/', '.'));
+            return ObjectType.getInstance(className.replace('/', '.'));
         }
     }
 
+    private static int skipGenerics(String signature, int startIndex) {
+        if (signature.charAt(startIndex) != '<') {
+            throw new IllegalArgumentException();
+        }
+
+        int index = startIndex + 1;
+        int count = 1;
+        int len = signature.length();
+
+        while (count > 0 && index < len) {
+            switch (signature.charAt(index)) {
+                case '<':
+                    count++;
+                    break;
+                case '>':
+                    count--;
+                    break;
+            }
+
+            index++;
+        }
+
+        if (count > 0) {
+            throw new ClassFormatException("Invalid signature: < and > are unbalanced");
+        }
+
+        return index;
+    }
 
     /**
      * Convert return value of a method (signature) to a Type object.

Attachment: signature.asc
Description: This is a digitally signed message part.

Reply via email to