You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@commons.apache.org by gg...@apache.org on 2019/08/30 22:49:57 UTC

[commons-bcel] branch master updated: Update signature scanning in Utility to support TypeParameters. (#32)

This is an automated email from the ASF dual-hosted git repository.

ggregory pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/commons-bcel.git


The following commit(s) were added to refs/heads/master by this push:
     new 04a21a2  Update signature scanning in Utility to support TypeParameters. (#32)
04a21a2 is described below

commit 04a21a2dfb1c95563224b166c21d746d7dda18dc
Author: Mark Roberts <ma...@users.noreply.github.com>
AuthorDate: Fri Aug 30 15:49:53 2019 -0700

    Update signature scanning in Utility to support TypeParameters. (#32)
    
    * Update signature scanning in Utility to support TypeParameters.
    
    * Add @since tag to new public method
---
 .../org/apache/bcel/classfile/LocalVariable.java   |  11 +-
 .../java/org/apache/bcel/classfile/Utility.java    | 369 +++++++++++++++------
 src/main/java/org/apache/bcel/generic/Type.java    |  19 +-
 .../org/apache/bcel/classfile/UtilityTestCase.java |  24 ++
 4 files changed, 303 insertions(+), 120 deletions(-)

diff --git a/src/main/java/org/apache/bcel/classfile/LocalVariable.java b/src/main/java/org/apache/bcel/classfile/LocalVariable.java
index 1d68dd8..5c8fa72 100644
--- a/src/main/java/org/apache/bcel/classfile/LocalVariable.java
+++ b/src/main/java/org/apache/bcel/classfile/LocalVariable.java
@@ -26,15 +26,24 @@ import org.apache.bcel.Constants;
 
 /**
  * This class represents a local variable within a method. It contains its
- * scope, name, signature and index on the method's frame.
+ * scope, name, signature and index on the method's frame.  It is used both
+ * to represent an element of the LocalVariableTable as well as an element
+ * of the LocalVariableTypeTable.  The nomenclature used here may be a bit confusing;
+ * while the two items have the same layout in a class file, a LocalVariableTable
+ * attribute contains a descriptor_index, not a signature_index.  The
+ * LocalVariableTypeTable attribute does have a signature_index.
+ * @see org.apache.bcel.classfile.Utility for more details on the difference.
  *
  * @see     LocalVariableTable
+ * @see     LocalVariableTypeTable
  */
 public final class LocalVariable implements Cloneable, Node, Constants {
 
     private int start_pc; // Range in which the variable is valid
     private int length;
     private int name_index; // Index in constant pool of variable name
+    // Technically, a decscriptor_index for a local variable table entry
+    // and a signature_index for a local variable type table entry.
     private int signature_index; // Index of variable signature
     private int index; /* Variable is `index'th local variable on
      * this method's frame.
diff --git a/src/main/java/org/apache/bcel/classfile/Utility.java b/src/main/java/org/apache/bcel/classfile/Utility.java
index 4fca43f..5072c34 100644
--- a/src/main/java/org/apache/bcel/classfile/Utility.java
+++ b/src/main/java/org/apache/bcel/classfile/Utility.java
@@ -48,23 +48,23 @@ public abstract class Utility {
         return tl.get().intValue();
     }
 
-
     private static void wrap( final ThreadLocal<Integer> tl, final int value ) {
         tl.set(Integer.valueOf(value));
     }
 
+    /* How many chars have been consumed
+     * during parsing in typeSignatureToString().
+     * Read by methodSignatureToString().
+     * Set by side effect, but only internally.
+     */
     private static ThreadLocal<Integer> consumed_chars = new ThreadLocal<Integer>() {
-
         @Override
         protected Integer initialValue() {
             return Integer.valueOf(0);
         }
-    };/* How many chars have been consumed
-     * during parsing in signatureToString().
-     * Read by methodSignatureToString().
-     * Set by side effect,but only internally.
-     */
-    private static boolean wide = false; /* The `WIDE' instruction is used in the
+    };
+
+    /* The `WIDE' instruction is used in the
      * byte code to allow 16-bit wide indices
      * for local variables. This opcode
      * precedes an `ILOAD', e.g.. The opcode
@@ -73,6 +73,7 @@ public abstract class Utility {
      * following byte to form a
      * 16-bit value.
      */
+    private static boolean wide = false;
 
 
     /**
@@ -465,6 +466,21 @@ public abstract class Utility {
 
 
     /**
+     * Shorten long class names, <em>java/lang/String</em> becomes
+     * <em>java.lang.String</em>,
+     * e.g.. If <em>chopit</em> is <em>true</em> the prefix <em>java.lang</em>
+     * is also removed.
+     *
+     * @param str The long class name
+     * @param chopit flag that determines whether chopping is executed or not
+     * @return Compacted class name
+     */
+    public static String compactClassName( final String str, final boolean chopit ) {
+        return compactClassName(str, "java.lang.", chopit);
+    }
+
+
+    /**
      * Shorten long class name <em>str</em>, i.e., chop off the <em>prefix</em>,
      * if the
      * class name starts with this string and the flag <em>chopit</em> is true.
@@ -472,7 +488,7 @@ public abstract class Utility {
      *
      * @param str The long class name
      * @param prefix The prefix the get rid off
-     * @param chopit Flag that determines whether chopping is executed or not
+     * @param chopit flag that determines whether chopping is executed or not
      * @return Compacted class name
      */
     public static String compactClassName( String str, final String prefix, final boolean chopit ) {
@@ -489,21 +505,6 @@ public abstract class Utility {
 
 
     /**
-     * Shorten long class names, <em>java/lang/String</em> becomes
-     * <em>java.lang.String</em>,
-     * e.g.. If <em>chopit</em> is <em>true</em> the prefix <em>java.lang</em>
-     * is also removed.
-     *
-     * @param str The long class name
-     * @param chopit Flag that determines whether chopping is executed or not
-     * @return Compacted class name
-     */
-    public static String compactClassName( final String str, final boolean chopit ) {
-        return compactClassName(str, "java.lang.", chopit);
-    }
-
-
-    /**
      * @return `flag' with bit `i' set to 1
      */
     public static int setBit( final int flag, final int i ) {
@@ -558,9 +559,11 @@ public abstract class Utility {
 
 
     /**
+     * Converts argument list portion of method signature to string with all class names compacted.
+     *
      * @param  signature    Method signature
-     * @return Array of argument types
-     * @throws  ClassFormatException
+     * @return String Array of argument types
+     * @throws ClassFormatException
      */
     public static String[] methodSignatureArgumentTypes( final String signature )
             throws ClassFormatException {
@@ -569,22 +572,25 @@ public abstract class Utility {
 
 
     /**
+     * Converts argument list portion of method signature to string.
+     *
      * @param  signature    Method signature
-     * @param chopit Shorten class names ?
-     * @return Array of argument types
-     * @throws  ClassFormatException
+     * @param  chopit flag that determines whether chopping is executed or not
+     * @return String Array of argument types
+     * @throws ClassFormatException
      */
     public static String[] methodSignatureArgumentTypes( final String signature, final boolean chopit )
             throws ClassFormatException {
         final List<String> vec = new ArrayList<>();
         int index;
-        try { // Read all declarations between for `(' and `)'
-            if (signature.charAt(0) != '(') {
+        try {
+            // Skip any type arguments to read argument declarations between `(' and `)'
+            index = signature.indexOf('(') + 1;
+            if (index <= 0) {
                 throw new ClassFormatException("Invalid method signature: " + signature);
             }
-            index = 1; // current string position
             while (signature.charAt(index) != ')') {
-                vec.add(signatureToString(signature.substring(index), chopit));
+                vec.add(typeSignatureToString(signature.substring(index), chopit));
                 //corrected concurrent private static field acess
                 index += unwrap(consumed_chars); // update position
             }
@@ -596,9 +602,11 @@ public abstract class Utility {
 
 
     /**
+     * Converts return type portion of method signature to string with all class names compacted.
+     *
      * @param  signature    Method signature
-     * @return return type of method
-     * @throws  ClassFormatException
+     * @return String representation of method return type
+     * @throws ClassFormatException
      */
     public static String methodSignatureReturnType( final String signature ) throws ClassFormatException {
         return methodSignatureReturnType(signature, true);
@@ -606,10 +614,12 @@ public abstract class Utility {
 
 
     /**
+     * Converts return type portion of method signature to string.
+     *
      * @param  signature    Method signature
-     * @param chopit Shorten class names ?
-     * @return return type of method
-     * @throws  ClassFormatException
+     * @param  chopit flag that determines whether chopping is executed or not
+     * @return String representation of method return type
+     * @throws ClassFormatException
      */
     public static String methodSignatureReturnType( final String signature, final boolean chopit ) throws ClassFormatException {
         int index;
@@ -617,7 +627,10 @@ public abstract class Utility {
         try {
             // Read return type after `)'
             index = signature.lastIndexOf(')') + 1;
-            type = signatureToString(signature.substring(index), chopit);
+            if (index <= 0) {
+                throw new ClassFormatException("Invalid method signature: " + signature);
+            }
+            type = typeSignatureToString(signature.substring(index), chopit);
         } catch (final StringIndexOutOfBoundsException e) { // Should never occur
             throw new ClassFormatException("Invalid method signature: " + signature, e);
         }
@@ -628,9 +641,9 @@ public abstract class Utility {
     /**
      * Converts method signature to string with all class names compacted.
      *
-     * @param signature to convert
-     * @param name of method
-     * @param access flags of method
+     * @param  signature to convert
+     * @param  name of method
+     * @param  access flags of method
      * @return Human readable signature
      */
     public static String methodSignatureToString( final String signature, final String name, final String access ) {
@@ -638,45 +651,32 @@ public abstract class Utility {
     }
 
 
+    /**
+     * Converts method signature to string.
+     *
+     * @param  signature to convert
+     * @param  name of method
+     * @param  access flags of method
+     * @param  chopit flag that determines whether chopping is executed or not
+     * @return Human readable signature
+     */
     public static String methodSignatureToString( final String signature, final String name, final String access, final boolean chopit ) {
         return methodSignatureToString(signature, name, access, chopit, null);
     }
 
 
     /**
-     * A returntype signature represents the return value from a method.
-     * It is a series of bytes in the following grammar:
-     *
-     * <pre>
-     * &lt;return_signature&gt; ::= &lt;field_type&gt; | V
-     * </pre>
-     *
-     * The character V indicates that the method returns no value. Otherwise, the
-     * signature indicates the type of the return value.
-     * An argument signature represents an argument passed to a method:
-     *
-     * <pre>
-     * &lt;argument_signature&gt; ::= &lt;field_type&gt;
-     * </pre>
-     *
-     * A method signature represents the arguments that the method expects, and
-     * the value that it returns.
-     * <pre>
-     * &lt;method_signature&gt; ::= (&lt;arguments_signature&gt;) &lt;return_signature&gt;
-     * &lt;arguments_signature&gt;::= &lt;argument_signature&gt;*
-     * </pre>
-     *
-     * This method converts such a string into a Java type declaration like
+     * This method converts a method signature string into a Java type declaration like
      * `void main(String[])' and throws a `ClassFormatException' when the parsed
      * type is invalid.
      *
      * @param  signature    Method signature
      * @param  name         Method name
      * @param  access       Method access rights
-     * @param chopit
-     * @param vars
+     * @param  chopit flag that determines whether chopping is executed or not
+     * @param  vars the LocalVariableTable for the method
      * @return Java type declaration
-     * @throws  ClassFormatException
+     * @throws ClassFormatException
      */
     public static String methodSignatureToString( final String signature, final String name,
             final String access, final boolean chopit, final LocalVariableTable vars ) throws ClassFormatException {
@@ -684,13 +684,14 @@ public abstract class Utility {
         String type;
         int index;
         int var_index = access.contains("static") ? 0 : 1;
-        try { // Read all declarations between for `(' and `)'
-            if (signature.charAt(0) != '(') {
+        try {
+            // Skip any type arguments to read argument declarations between `(' and `)'
+            index = signature.indexOf('(') + 1;
+            if (index <= 0) {
                 throw new ClassFormatException("Invalid method signature: " + signature);
             }
-            index = 1; // current string position
             while (signature.charAt(index) != ')') {
-                final String param_type = signatureToString(signature.substring(index), chopit);
+                final String param_type = typeSignatureToString(signature.substring(index), chopit);
                 buf.append(param_type);
                 if (vars != null) {
                     final LocalVariable l = vars.getLocalVariable(var_index, 0);
@@ -711,10 +712,11 @@ public abstract class Utility {
             }
             index++; // update position
             // Read return type after `)'
-            type = signatureToString(signature.substring(index), chopit);
+            type = typeSignatureToString(signature.substring(index), chopit);
         } catch (final StringIndexOutOfBoundsException e) { // Should never occur
             throw new ClassFormatException("Invalid method signature: " + signature, e);
         }
+        // ignore any throws information in the signature
         if (buf.length() > 1) {
             buf.setLength(buf.length() - 2);
         }
@@ -724,7 +726,6 @@ public abstract class Utility {
     }
 
 
-    // Guess what this does
     private static int pow2( final int n ) {
         return 1 << n;
     }
@@ -762,10 +763,40 @@ public abstract class Utility {
 
 
     /**
-     * Converts signature to string with all class names compacted.
+     * WARNING:
      *
-     * @param signature to convert
-     * @return Human readable signature
+     * There is some nomenclature confusion through much of the BCEL code base with
+     * respect to the terms Descriptor and Signature.  For the offical definitions see:
+     *
+     * @see <a href="http://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.3">
+     * Descriptors in The Java Virtual Machine Specification</a>
+     *
+     * @see <a href="http://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.7.9.1">
+     * Signatures in The Java Virtual Machine Specification</a>
+     *
+     * In brief, a descriptor is a string representing the type of a field or method.
+     * Signatures are similar, but more complex.  Signatures are used to encode declarations
+     * written in the Java programming language that use types outside the type system of the
+     * Java Virtual Machine.  They are used to describe the type of any class, interface,
+     * constructor, method or field whose declaration uses type variables or parameterized types.
+     *
+     * To parse a descriptor, call typeSignatureToString.
+     * To parse a signature, call signatureToString.
+     *
+     * Note that if the signature string is a single, non-generic item, the call to
+     * signatureToString reduces to a call to typeSignatureToString.
+     * Also note, that if you only wish to parse the first item in a longer signature
+     * string, you should call typeSignatureToString directly.
+     */
+
+
+    /**
+     * Converts a signature to a string with all class names compacted.
+     * Class, Method and Type signatures are supported.
+     * Enum and Interface signatures are not supported.
+     *
+     * @param  signature signature to convert
+     * @return String containg human readable signature
      */
     public static String signatureToString( final String signature ) {
         return signatureToString(signature, true);
@@ -773,40 +804,158 @@ public abstract class Utility {
 
 
     /**
-     * The field signature represents the value of an argument to a function or
-     * the value of a variable. It is a series of bytes generated by the
-     * following grammar:
+     * Converts a signature to a string.
+     * Class, Method and Type signatures are supported.
+     * Enum and Interface signatures are not supported.
+     *
+     * @param  signature signature to convert
+     * @param  chopit flag that determines whether chopping is executed or not
+     * @return String containg human readable signature
+     */
+    public static String signatureToString( final String signature, final boolean chopit ) {
+        String type = "";
+        String typeParams = "";
+        int index = 0;
+        if (signature.charAt(0) == '<') {
+            // we have type paramters
+            typeParams = typeParamTypesToString(signature, chopit);
+            index += unwrap(consumed_chars); // update position
+        }
+        if (signature.charAt(index) == '(') {
+            // We have a Method signature.
+            // add types of arguments
+            type = typeParams + typeSignaturesToString(signature.substring(index), chopit, ')');
+            index += unwrap(consumed_chars); // update position
+            // add return type
+            type = type + typeSignatureToString(signature.substring(index), chopit);
+            index += unwrap(consumed_chars); // update position
+            // ignore any throws information in the signature
+            return type;
+        } else {
+            // Could be Class or Type...
+            type = typeSignatureToString(signature.substring(index), chopit);
+            index += unwrap(consumed_chars); // update position
+            if ((typeParams.length() == 0) && (index == signature.length())) {
+                // We have a Type signature.
+                return type;
+            }
+            // We have a Class signature.
+            StringBuilder typeClass = new StringBuilder(typeParams);
+            typeClass.append(" extends ");
+            typeClass.append(type);
+            if (index < signature.length()) {
+                typeClass.append(" implements ");
+                typeClass.append(typeSignatureToString(signature.substring(index), chopit));
+                index += unwrap(consumed_chars); // update position
+            }
+            while (index < signature.length()) {
+                typeClass.append(", ");
+                typeClass.append(typeSignatureToString(signature.substring(index), chopit));
+                index += unwrap(consumed_chars); // update position
+            }
+            return typeClass.toString();
+        }
+    }
+
+
+    /**
+     * Converts a type parameter list signature to a string.
      *
-     * <PRE>
-     * &lt;field_signature&gt; ::= &lt;field_type&gt;
-     * &lt;field_type&gt;      ::= &lt;base_type&gt;|&lt;object_type&gt;|&lt;array_type&gt;
-     * &lt;base_type&gt;       ::= B|C|D|F|I|J|S|Z
-     * &lt;object_type&gt;     ::= L&lt;fullclassname&gt;;
-     * &lt;array_type&gt;      ::= [&lt;field_type&gt;
+     * @param  signature signature to convert
+     * @param  chopit flag that determines whether chopping is executed or not
+     * @return String containg human readable signature
+     */
+    private static String typeParamTypesToString( final String signature, final boolean chopit ) {
+        // The first character is guranteed to be '<'
+        StringBuilder typeParams = new StringBuilder("<");
+        int index = 1;  // skip the '<'
+        // get the first TypeParameter
+        typeParams.append(typeParamTypeToString(signature.substring(index), chopit));
+        index += unwrap(consumed_chars); // update position
+        // are there more TypeParameters?
+        while (signature.charAt(index) != '>') {
+            typeParams.append(", ");
+            typeParams.append(typeParamTypeToString(signature.substring(index), chopit));
+            index += unwrap(consumed_chars); // update position
+        }
+        wrap(consumed_chars, index + 1); // account for the '>' char
+        return typeParams.append(">").toString();
+    }
+
+
+    /**
+     * Converts a type parameter signature to a string.
      *
-     * The meaning of the base types is as follows:
-     * B byte signed byte
-     * C char character
-     * D double double precision IEEE float
-     * F float single precision IEEE float
-     * I int integer
-     * J long long integer
-     * L&lt;fullclassname&gt;; ... an object of the given class
-     * S short signed short
-     * Z boolean true or false
-     * [&lt;field sig&gt; ... array
-     * </PRE>
+     * @param  signature signature to convert
+     * @param  chopit flag that determines whether chopping is executed or not
+     * @return String containg human readable signature
+     */
+    private static String typeParamTypeToString( final String signature, final boolean chopit ) {
+        int index = signature.indexOf(':');
+        if (index <= 0) {
+            throw new ClassFormatException("Invalid type parameter signature: " + signature);
+        }
+        // get the TypeParameter identifier
+        StringBuilder typeParam = new StringBuilder(signature.substring(0, index));
+        index++;  // account for the ':'
+        if (signature.charAt(index) != ':') {
+            // we have a class bound
+            typeParam.append(" extends ");
+            typeParam.append(typeSignatureToString(signature.substring(index), chopit));
+            index += unwrap(consumed_chars); // update position
+        }
+        // look for interface bounds
+        while (signature.charAt(index) == ':') {
+            index++;  // skip over the ':'
+            typeParam.append(" & ");
+            typeParam.append(typeSignatureToString(signature.substring(index), chopit));
+            index += unwrap(consumed_chars); // update position
+        }
+        wrap(consumed_chars, index);
+        return typeParam.toString();
+    }
+
+
+    /**
+     * Converts a list of type signatures to a string.
      *
-     * This method converts this string into a Java type declaration such as
-     * `String[]' and throws a `ClassFormatException' when the parsed type is
-     * invalid.
+     * @param  signature signature to convert
+     * @param  chopit flag that determines whether chopping is executed or not
+     * @param  term character indicating the end of the list
+     * @return String containg human readable signature
+     */
+    private static String typeSignaturesToString( final String signature, final boolean chopit, final char term ) {
+        // The first character will be an 'open' that matches the 'close' contained in term.
+        StringBuilder typeList = new StringBuilder(signature.substring(0, 1));
+        int index = 1;  // skip the 'open' character
+        // get the first Type in the list
+        if (signature.charAt(index) != term) {
+            typeList.append(typeSignatureToString(signature.substring(index), chopit));
+            index += unwrap(consumed_chars); // update position
+        }
+        // are there more types in the list?
+        while (signature.charAt(index) != term) {
+            typeList.append(", ");
+            typeList.append(typeSignatureToString(signature.substring(index), chopit));
+            index += unwrap(consumed_chars); // update position
+        }
+        wrap(consumed_chars, index + 1); // account for the term char
+        return typeList.append(term).toString();
+    }
+
+
+    /**
      *
-     * @param  signature  Class signature
-     * @param chopit Flag that determines whether chopping is executed or not
-     * @return Java type declaration
+     * This method converts a type signature string into a Java type declaration such as
+     * `String[]' and throws a `ClassFormatException' when the parsed type is invalid.
+     *
+     * @param  signature type signature
+     * @param  chopit flag that determines whether chopping is executed or not
+     * @return string containing human readable type signature
      * @throws ClassFormatException
+     * @since 6.4.0
      */
-    public static String signatureToString( final String signature, final boolean chopit ) {
+    public static String typeSignatureToString( final String signature, final boolean chopit ) throws ClassFormatException {
         //corrected concurrent private static field acess
         wrap(consumed_chars, 1); // This is the default, read just one char like `B'
         try {
@@ -826,7 +975,7 @@ public abstract class Utility {
                 case 'T': { // TypeVariableSignature
                     final int index = signature.indexOf(';'); // Look for closing `;'
                     if (index < 0) {
-                        throw new ClassFormatException("Invalid signature: " + signature);
+                        throw new ClassFormatException("Invalid type variable signature: " + signature);
                     }
                     //corrected concurrent private static field acess
                     wrap(consumed_chars, index + 1); // "Tblabla;" `T' and `;' are removed
@@ -886,7 +1035,7 @@ public abstract class Utility {
                         type.append("?");
                         consumed_chars++;
                     } else {
-                        type.append(signatureToString(signature.substring(consumed_chars), chopit));
+                        type.append(typeSignatureToString(signature.substring(consumed_chars), chopit));
                         // update our consumed count by the number of characters the for type argument
                         consumed_chars = unwrap(Utility.consumed_chars) + consumed_chars;
                         wrap(Utility.consumed_chars, consumed_chars);
@@ -907,7 +1056,7 @@ public abstract class Utility {
                             type.append("?");
                             consumed_chars++;
                         } else {
-                            type.append(signatureToString(signature.substring(consumed_chars), chopit));
+                            type.append(typeSignatureToString(signature.substring(consumed_chars), chopit));
                             // update our consumed count by the number of characters the for type argument
                             consumed_chars = unwrap(Utility.consumed_chars) + consumed_chars;
                             wrap(Utility.consumed_chars, consumed_chars);
@@ -923,7 +1072,7 @@ public abstract class Utility {
                         type.append(".");
                         // convert SimpleClassTypeSignature to fake ClassTypeSignature
                         // and then recurse to parse it
-                        type.append(signatureToString("L" + signature.substring(consumed_chars+1), chopit));
+                        type.append(typeSignatureToString("L" + signature.substring(consumed_chars+1), chopit));
                         // update our consumed count by the number of characters the for type argument
                         // note that this count includes the "L" we added, but that is ok
                         // as it accounts for the "." we didn't consume
@@ -953,7 +1102,7 @@ public abstract class Utility {
                     }
                     consumed_chars = n; // Remember value
                     // The rest of the string denotes a `<field_type>'
-                    type = signatureToString(signature.substring(n), chopit);
+                    type = typeSignatureToString(signature.substring(n), chopit);
                     //corrected concurrent private static field acess
                     //Utility.consumed_chars += consumed_chars; is replaced by:
                     final int _temp = unwrap(Utility.consumed_chars) + consumed_chars;
diff --git a/src/main/java/org/apache/bcel/generic/Type.java b/src/main/java/org/apache/bcel/generic/Type.java
index d6e1fb8..3b9e604 100644
--- a/src/main/java/org/apache/bcel/generic/Type.java
+++ b/src/main/java/org/apache/bcel/generic/Type.java
@@ -211,9 +211,8 @@ public abstract class Type {
             wrap(consumed_chars, _temp);
             return new ArrayType(t, dim);
         } else { // type == T_REFERENCE
-            // Utility.signatureToString understands how to parse
-            // generic types.
-            final String parsedSignature = Utility.signatureToString(signature, false);
+            // Utility.typeSignatureToString understands how to parse generic types.
+            final String parsedSignature = Utility.typeSignatureToString(signature, false);
             wrap(consumed_chars, parsedSignature.length() + 2); // "Lblabla;" `L' and `;' are removed
             return ObjectType.getInstance(parsedSignature.replace('/', '.'));
         }
@@ -246,11 +245,12 @@ public abstract class Type {
         final List<Type> vec = new ArrayList<>();
         int index;
         Type[] types;
-        try { // Read all declarations between for `(' and `)'
-            if (signature.charAt(0) != '(') {
+        try {
+            // Skip any type arguments to read argument declarations between `(' and `)'
+            index = signature.indexOf('(') + 1;
+            if (index <= 0) {
                 throw new ClassFormatException("Invalid method signature: " + signature);
             }
-            index = 1; // current string position
             while (signature.charAt(index) != ')') {
                 vec.add(getType(signature.substring(index)));
                 //corrected concurrent private static field acess
@@ -348,11 +348,12 @@ public abstract class Type {
     static int getArgumentTypesSize( final String signature ) {
         int res = 0;
         int index;
-        try { // Read all declarations between for `(' and `)'
-            if (signature.charAt(0) != '(') {
+        try {
+            // Skip any type arguments to read argument declarations between `(' and `)'
+            index = signature.indexOf('(') + 1;
+            if (index <= 0) {
                 throw new ClassFormatException("Invalid method signature: " + signature);
             }
-            index = 1; // current string position
             while (signature.charAt(index) != ')') {
                 final int coded = getTypeSize(signature.substring(index));
                 res += size(coded);
diff --git a/src/test/java/org/apache/bcel/classfile/UtilityTestCase.java b/src/test/java/org/apache/bcel/classfile/UtilityTestCase.java
index 8058657..44b923f 100644
--- a/src/test/java/org/apache/bcel/classfile/UtilityTestCase.java
+++ b/src/test/java/org/apache/bcel/classfile/UtilityTestCase.java
@@ -32,6 +32,7 @@ public class UtilityTestCase extends TestCase {
         assertEquals("generic signature",
                 "java.nio.file.attribute.FileAttribute<?>[]",
                 Utility.signatureToString("[Ljava/nio/file/attribute/FileAttribute<*>;"));
+
     // tests for BCEL-286
         assertEquals("generic signature",
                 "boofcv.alg.tracker.tld.TldTracker<boofcv.struct.image.ImageGray<boofcv.struct.image.GrayU8>, boofcv.struct.image.GrayI<boofcv.struct.image.GrayU8>>",
@@ -42,5 +43,28 @@ public class UtilityTestCase extends TestCase {
         assertEquals("generic signature",
                 "com.jme3.util.IntMap<T>.IntMapIterator",
                 Utility.signatureToString("Lcom/jme3/util/IntMap<TT;>.IntMapIterator;"));
+
+    // tests for BCEL-279
+        assertEquals("type parameters signature",
+                "<T extends java.lang.Object>(com.google.common.io.ByteProcessor<T>, int)T",
+                Utility.signatureToString("<T:Ljava/lang/Object;>(Lcom/google/common/io/ByteProcessor<TT;>;I)TT;", false));
+        assertEquals("type parameters signature",
+                "<T extends Object>(com.google.common.io.ByteProcessor<T>, int)T",
+                Utility.signatureToString("<T:Ljava/lang/Object;>(Lcom/google/common/io/ByteProcessor<TT;>;I)TT;", true));
+        assertEquals("type parameters signature",
+                "<M extends java.lang.reflect.AccessibleObject & java.lang.reflect.Member>(M)void",
+                Utility.signatureToString("<M:Ljava/lang/reflect/AccessibleObject;:Ljava/lang/reflect/Member;>(TM;)V"));
+        assertEquals("type parameters signature",
+                "<K1 extends K, V1 extends V>()com.google.common.cache.Weigher<K1, V1>",
+                Utility.signatureToString("<K1:TK;V1:TV;>()Lcom/google/common/cache/Weigher<TK1;TV1;>;"));
+        assertEquals("type parameters signature",
+                "<K1 extends K, V1 extends V>(com.google.common.cache.Weigher<? super K1, ? super V1>)com.google.common.cache.CacheBuilder<K1, V1>",
+                Utility.signatureToString("<K1:TK;V1:TV;>(Lcom/google/common/cache/Weigher<-TK1;-TV1;>;)Lcom/google/common/cache/CacheBuilder<TK1;TV1;>;"));
+        assertEquals("class signature",
+                "<N extends java.lang.Object, E extends java.lang.Object> extends java.lang.Object implements com.google.common.graph.Network<N, E>",
+                Utility.signatureToString("<N:Ljava/lang/Object;E:Ljava/lang/Object;>Ljava/lang/Object;Lcom/google/common/graph/Network<TN;TE;>;", false));
+        assertEquals("class signature",
+                "<K extends Object, V extends Object> extends Object",
+                Utility.signatureToString("<K:Ljava/lang/Object;V:Ljava/lang/Object;>Ljava/lang/Object;"));
     }
 }