    001/*
    002 * Licensed to the Apache Software Foundation (ASF) under one or more
    003 * contributor license agreements.  See the NOTICE file distributed with
    004 * this work for additional information regarding copyright ownership.
    005 * The ASF licenses this file to You under the Apache License, Version 2.0
    006 * (the "License"); you may not use this file except in compliance with
    007 * the License.  You may obtain a copy of the License at
    008 *
    009 *      http://www.apache.org/licenses/LICENSE-2.0
    010 *
    011 *  Unless required by applicable law or agreed to in writing, software
    012 *  distributed under the License is distributed on an "AS IS" BASIS,
    013 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    014 *  See the License for the specific language governing permissions and
    015 *  limitations under the License.
    016 */
    017package org.apache.bcel.generic;
    018
    019import java.util.ArrayList;
    020import java.util.Arrays;
    021import java.util.List;
    022import java.util.Objects;
    023
    024import org.apache.bcel.Const;
    025import org.apache.bcel.classfile.ClassFormatException;
    026import org.apache.bcel.classfile.InvalidMethodSignatureException;
    027import org.apache.bcel.classfile.Utility;
    028import org.apache.commons.lang3.StringUtils;
    029
    030/**
    031 * Abstract super class for all possible Java types, namely basic types such as int, object types like String and array
    032 * types, e.g. int[]
    033 */
    034public abstract class Type {
    035
    036    /**
    037     * Predefined constants
    038     */
    039    public static final BasicType VOID = new BasicType(Const.T_VOID);
    040
    041    public static final BasicType BOOLEAN = new BasicType(Const.T_BOOLEAN);
    042    public static final BasicType INT = new BasicType(Const.T_INT);
    043    public static final BasicType SHORT = new BasicType(Const.T_SHORT);
    044    public static final BasicType BYTE = new BasicType(Const.T_BYTE);
    045    public static final BasicType LONG = new BasicType(Const.T_LONG);
    046    public static final BasicType DOUBLE = new BasicType(Const.T_DOUBLE);
    047    public static final BasicType FLOAT = new BasicType(Const.T_FLOAT);
    048    public static final BasicType CHAR = new BasicType(Const.T_CHAR);
    049    public static final ObjectType OBJECT = new ObjectType("java.lang.Object");
    050    public static final ObjectType CLASS = new ObjectType("java.lang.Class");
    051    public static final ObjectType STRING = new ObjectType("java.lang.String");
    052    public static final ObjectType STRINGBUFFER = new ObjectType("java.lang.StringBuffer");
    053    public static final ObjectType THROWABLE = new ObjectType("java.lang.Throwable");
    054
    055    /**
    056     * Empty array.
    057     */
    058    public static final Type[] NO_ARGS = {};
    059    public static final ReferenceType NULL = new ReferenceType() {
    060    };
    061
    062    public static final Type UNKNOWN = new Type(Const.T_UNKNOWN, "<unknown object>") {
    063    };
    064
    065    private static final ThreadLocal<Integer> CONSUMED_CHARS = ThreadLocal.withInitial(() -> Integer.valueOf(0));
    066
    067    // int consumed_chars=0; // Remember position in string, see getArgumentTypes
    068    static int consumed(final int coded) {
    069        return coded >> 2;
    070    }
    071
    072    static int encode(final int size, final int consumed) {
    073        return consumed << 2 | size;
    074    }
    075
    076    /**
    077     * Convert arguments of a method (signature) to an array of Type objects.
    078     *
    079     * @param signature signature string such as (Ljava/lang/String;)V
    080     * @return array of argument types
    081     */
    082    public static Type[] getArgumentTypes(final String signature) {
    083        final List<Type> vec = new ArrayList<>();
    084        int index;
    085        try {
    086            // Skip any type arguments to read argument declarations between '(' and ')'
    087            index = signature.indexOf('(') + 1;
    088            if (index <= 0) {
    089                throw new InvalidMethodSignatureException(signature);
    090            }
    091            while (signature.charAt(index) != ')') {
    092                vec.add(getType(signature.substring(index)));
    093                // corrected concurrent private static field access
    094                index += unwrap(CONSUMED_CHARS); // update position
    095            }
    096        } catch (final StringIndexOutOfBoundsException e) { // Should never occur
    097            throw new InvalidMethodSignatureException(signature, e);
    098        }
    099        final Type[] types = new Type[vec.size()];
    100        vec.toArray(types);
    101        return types;
    102    }
    103
    104    static int getArgumentTypesSize(final String signature) {
    105        int res = 0;
    106        int index;
    107        try {
    108            // Skip any type arguments to read argument declarations between '(' and ')'
    109            index = signature.indexOf('(') + 1;
    110            if (index <= 0) {
    111                throw new InvalidMethodSignatureException(signature);
    112            }
    113            while (signature.charAt(index) != ')') {
    114                final int coded = getTypeSize(signature.substring(index));
    115                res += size(coded);
    116                index += consumed(coded);
    117            }
    118        } catch (final StringIndexOutOfBoundsException e) { // Should never occur
    119            throw new InvalidMethodSignatureException(signature, e);
    120        }
    121        return res;
    122    }
    123
    124    /**
    125     * Convert type to Java method signature, e.g. int[] f(java.lang.String x) becomes (Ljava/lang/String;)[I
    126     *
    127     * @param returnType what the method returns
    128     * @param argTypes what are the argument types
    129     * @return method signature for given type(s).
    130     */
    131    public static String getMethodSignature(final Type returnType, final Type[] argTypes) {
    132        final StringBuilder buf = new StringBuilder("(");
    133        if (argTypes != null) {
    134            for (final Type argType : argTypes) {
    135                buf.append(argType.getSignature());
    136            }
    137        }
    138        buf.append(')');
    139        buf.append(returnType.getSignature());
    140        return buf.toString();
    141    }
    142
    143    /**
    144     * Convert return value of a method (signature) to a Type object.
    145     *
    146     * @param signature signature string such as (Ljava/lang/String;)V
    147     * @return return type
    148     */
    149    public static Type getReturnType(final String signature) {
    150        try {
    151            // Read return type after ')'
    152            final int index = signature.lastIndexOf(')') + 1;
    153            return getType(signature.substring(index));
    154        } catch (final StringIndexOutOfBoundsException e) { // Should never occur
    155            throw new InvalidMethodSignatureException(signature, e);
    156        }
    157    }
    158
    159    static int getReturnTypeSize(final String signature) {
    160        final int index = signature.lastIndexOf(')') + 1;
    161        return Type.size(getTypeSize(signature.substring(index)));
    162    }
    163
    164    public static String getSignature(final java.lang.reflect.Method meth) {
    165        final StringBuilder sb = new StringBuilder("(");
    166        final Class<?>[] params = meth.getParameterTypes(); // avoid clone
    167        for (final Class<?> param : params) {
    168            sb.append(getType(param).getSignature());
    169        }
    170        sb.append(")");
    171        sb.append(getType(meth.getReturnType()).getSignature());
    172        return sb.toString();
    173    }
    174
    175    /**
    176     * Convert runtime {@link Class} to BCEL Type object.
    177     *
    178     * @param cls Java class
    179     * @return corresponding Type object
    180     */
    181    public static Type getType(final Class<?> cls) {
    182        Objects.requireNonNull(cls, "cls");
    183        /*
    184         * That's an amazingly easy case, because getName() returns the signature. That's what we would have liked anyway.
    185         */
    186        if (cls.isArray()) {
    187            return getType(cls.getName());
    188        }
    189        if (!cls.isPrimitive()) { // "Real" class
    190            return ObjectType.getInstance(cls.getName());
    191        }
    192        if (cls == Integer.TYPE) {
    193            return INT;
    194        }
    195        if (cls == Void.TYPE) {
    196            return VOID;
    197        }
    198        if (cls == Double.TYPE) {
    199            return DOUBLE;
    200        }
    201        if (cls == Float.TYPE) {
    202            return FLOAT;
    203        }
    204        if (cls == Boolean.TYPE) {
    205            return BOOLEAN;
    206        }
    207        if (cls == Byte.TYPE) {
    208            return BYTE;
    209        }
    210        if (cls == Short.TYPE) {
    211            return SHORT;
    212        }
    213        if (cls == Long.TYPE) {
    214            return LONG;
    215        }
    216        if (cls == Character.TYPE) {
    217            return CHAR;
    218        }
    219        throw new IllegalStateException("Unknown primitive type " + cls);
    220    }
    221
    222    /**
    223     * Convert signature to a Type object.
    224     *
    225     * @param signature signature string such as Ljava/lang/String;
    226     * @return type object
    227     */
    228    public static Type getType(final String signature) throws StringIndexOutOfBoundsException {
    229        final byte type = Utility.typeOfSignature(signature);
    230        if (type <= Const.T_VOID) {
    231            // corrected concurrent private static field access
    232            wrap(CONSUMED_CHARS, 1);
    233            return BasicType.getType(type);
    234        }
    235        if (type != Const.T_ARRAY) { // type == T_REFERENCE
    236            // Utility.typeSignatureToString understands how to parse generic types.
    237            final String parsedSignature = Utility.typeSignatureToString(signature, false);
    238            wrap(CONSUMED_CHARS, parsedSignature.length() + 2); // "Lblabla;" 'L' and ';' are removed
    239            return ObjectType.getInstance(Utility.pathToPackage(parsedSignature));
    240        }
    241        int dim = 0;
    242        do { // Count dimensions
    243            dim++;
    244        } while (signature.charAt(dim) == '[');
    245        // Recurse, but just once, if the signature is ok
    246        final Type t = getType(signature.substring(dim));
    247        // corrected concurrent private static field access
    248        // consumed_chars += dim; // update counter - is replaced by
    249        final int temp = unwrap(CONSUMED_CHARS) + dim;
    250        wrap(CONSUMED_CHARS, temp);
    251        return new ArrayType(t, dim);
    252    }
    253
    254    /**
    255     * Convert runtime {@code java.lang.Class[]} to BCEL Type objects.
    256     *
    257     * @param classes an array of runtime class objects
    258     * @return array of corresponding Type objects
    259     */
    260    public static Type[] getTypes(final Class<?>[] classes) {
    261        final Type[] ret = new Type[classes.length];
    262        Arrays.setAll(ret, i -> getType(classes[i]));
    263        return ret;
    264    }
    265
    266    static int getTypeSize(final String signature) throws StringIndexOutOfBoundsException {
    267        final byte type = Utility.typeOfSignature(signature);
    268        if (type <= Const.T_VOID) {
    269            return encode(BasicType.getType(type).getSize(), 1);
    270        }
    271        if (type == Const.T_ARRAY) {
    272            int dim = 0;
    273            do { // Count dimensions
    274                dim++;
    275            } while (signature.charAt(dim) == '[');
    276            // Recurse, but just once, if the signature is ok
    277            final int consumed = consumed(getTypeSize(signature.substring(dim)));
    278            return encode(1, dim + consumed);
    279        }
    280        final int index = signature.indexOf(';'); // Look for closing ';'
    281        if (index < 0) {
    282            throw new ClassFormatException("Invalid signature: " + signature);
    283        }
    284        return encode(1, index + 1);
    285    }
    286
    287    static String internalTypeNameToSignature(final String internalTypeName) {
    288        if (StringUtils.isEmpty(internalTypeName) || StringUtils.equalsAny(internalTypeName, Const.SHORT_TYPE_NAMES)) {
    289            return internalTypeName;
    290        }
    291        switch (internalTypeName.charAt(0)) {
    292            case '[':
    293                return internalTypeName;
    294            case 'L':
    295            case 'T':
    296                if (internalTypeName.charAt(internalTypeName.length() - 1) == ';') {
    297                    return internalTypeName;
    298                }
    299                return 'L' + internalTypeName + ';';
    300            default:
    301                return 'L' + internalTypeName + ';';
    302        }
    303    }
    304
    305    static int size(final int coded) {
    306        return coded & 3;
    307    }
    308
    309    private static int unwrap(final ThreadLocal<Integer> tl) {
    310        return tl.get().intValue();
    311    }
    312
    313    private static void wrap(final ThreadLocal<Integer> tl, final int value) {
    314        tl.set(Integer.valueOf(value));
    315    }
    316
    317    /**
    318     * @deprecated (since 6.0) will be made private; do not access directly, use getter/setter
    319     */
    320    @Deprecated
    321    protected byte type; // TODO should be final (and private)
    322
    323    /**
    324     * @deprecated (since 6.0) will be made private; do not access directly, use getter/setter
    325     */
    326    @Deprecated
    327    protected String signature; // signature for the type TODO should be private
    328
    329    protected Type(final byte type, final String signature) {
    330        this.type = type;
    331        this.signature = signature;
    332    }
    333
    334    /**
    335     * @return whether the Types are equal
    336     */
    337    @Override
    338    public boolean equals(final Object o) {
    339        if (o instanceof Type) {
    340            final Type t = (Type) o;
    341            return type == t.type && signature.equals(t.signature);
    342        }
    343        return false;
    344    }
    345
    346    public String getClassName() {
    347        return toString();
    348    }
    349
    350    /**
    351     * @return signature for given type.
    352     */
    353    public String getSignature() {
    354        return signature;
    355    }
    356
    357    /**
    358     * @return stack size of this type (2 for long and double, 0 for void, 1 otherwise)
    359     */
    360    public int getSize() {
    361        switch (type) {
    362        case Const.T_DOUBLE:
    363        case Const.T_LONG:
    364            return 2;
    365        case Const.T_VOID:
    366            return 0;
    367        default:
    368            return 1;
    369        }
    370    }
    371
    372    /**
    373     * @return type as defined in Constants
    374     */
    375    public byte getType() {
    376        return type;
    377    }
    378
    379    /**
    380     * @return hash code of Type
    381     */
    382    @Override
    383    public int hashCode() {
    384        return type ^ signature.hashCode();
    385    }
    386
    387    /**
    388     * boolean, short and char variable are considered as int in the stack or local variable area. Returns {@link Type#INT}
    389     * for {@link Type#BOOLEAN}, {@link Type#SHORT} or {@link Type#CHAR}, otherwise returns the given type.
    390     *
    391     * @since 6.0
    392     */
    393    public Type normalizeForStackOrLocal() {
    394        if (this == Type.BOOLEAN || this == Type.BYTE || this == Type.SHORT || this == Type.CHAR) {
    395            return Type.INT;
    396        }
    397        return this;
    398    }
    399
    400    /**
    401     * @return Type string, e.g. 'int[]'
    402     */
    403    @Override
    404    public String toString() {
    405        return this.equals(Type.NULL) || type >= Const.T_UNKNOWN ? signature : Utility.signatureToString(signature, false);
    406    }
    407}