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