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