1/*
2 * Copyright (C) 2007 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.dx.cf.cst;
18
19import static com.android.dx.cf.cst.ConstantTags.CONSTANT_Class;
20import static com.android.dx.cf.cst.ConstantTags.CONSTANT_Double;
21import static com.android.dx.cf.cst.ConstantTags.CONSTANT_Fieldref;
22import static com.android.dx.cf.cst.ConstantTags.CONSTANT_Float;
23import static com.android.dx.cf.cst.ConstantTags.CONSTANT_Integer;
24import static com.android.dx.cf.cst.ConstantTags.CONSTANT_InterfaceMethodref;
25import static com.android.dx.cf.cst.ConstantTags.CONSTANT_Long;
26import static com.android.dx.cf.cst.ConstantTags.CONSTANT_Methodref;
27import static com.android.dx.cf.cst.ConstantTags.CONSTANT_NameAndType;
28import static com.android.dx.cf.cst.ConstantTags.CONSTANT_String;
29import static com.android.dx.cf.cst.ConstantTags.CONSTANT_Utf8;
30import static com.android.dx.cf.cst.ConstantTags.CONSTANT_MethodHandle;
31import static com.android.dx.cf.cst.ConstantTags.CONSTANT_MethodType;
32import static com.android.dx.cf.cst.ConstantTags.CONSTANT_InvokeDynamic;
33import com.android.dx.cf.iface.ParseException;
34import com.android.dx.cf.iface.ParseObserver;
35import com.android.dx.rop.cst.Constant;
36import com.android.dx.rop.cst.CstDouble;
37import com.android.dx.rop.cst.CstFieldRef;
38import com.android.dx.rop.cst.CstFloat;
39import com.android.dx.rop.cst.CstInteger;
40import com.android.dx.rop.cst.CstInterfaceMethodRef;
41import com.android.dx.rop.cst.CstLong;
42import com.android.dx.rop.cst.CstMethodRef;
43import com.android.dx.rop.cst.CstNat;
44import com.android.dx.rop.cst.CstString;
45import com.android.dx.rop.cst.CstType;
46import com.android.dx.rop.cst.StdConstantPool;
47import com.android.dx.rop.type.Type;
48import com.android.dx.util.ByteArray;
49import com.android.dx.util.Hex;
50import java.util.BitSet;
51
52/**
53 * Parser for a constant pool embedded in a class file.
54 */
55public final class ConstantPoolParser {
56    /** {@code non-null;} the bytes of the constant pool */
57    private final ByteArray bytes;
58
59    /** {@code non-null;} actual parsed constant pool contents */
60    private final StdConstantPool pool;
61
62    /** {@code non-null;} byte offsets to each cst */
63    private final int[] offsets;
64
65    /**
66     * -1 || >= 10; the end offset of this constant pool in the
67     * {@code byte[]} which it came from or {@code -1} if not
68     * yet parsed
69     */
70    private int endOffset;
71
72    /** {@code null-ok;} parse observer, if any */
73    private ParseObserver observer;
74
75    /**
76     * Constructs an instance.
77     *
78     * @param bytes {@code non-null;} the bytes of the file
79     */
80    public ConstantPoolParser(ByteArray bytes) {
81        int size = bytes.getUnsignedShort(8); // constant_pool_count
82
83        this.bytes = bytes;
84        this.pool = new StdConstantPool(size);
85        this.offsets = new int[size];
86        this.endOffset = -1;
87    }
88
89    /**
90     * Sets the parse observer for this instance.
91     *
92     * @param observer {@code null-ok;} the observer
93     */
94    public void setObserver(ParseObserver observer) {
95        this.observer = observer;
96    }
97
98    /**
99     * Gets the end offset of this constant pool in the {@code byte[]}
100     * which it came from.
101     *
102     * @return {@code >= 10;} the end offset
103     */
104    public int getEndOffset() {
105        parseIfNecessary();
106        return endOffset;
107    }
108
109    /**
110     * Gets the actual constant pool.
111     *
112     * @return {@code non-null;} the constant pool
113     */
114    public StdConstantPool getPool() {
115        parseIfNecessary();
116        return pool;
117    }
118
119    /**
120     * Runs {@link #parse} if it has not yet been run successfully.
121     */
122    private void parseIfNecessary() {
123        if (endOffset < 0) {
124            parse();
125        }
126    }
127
128    /**
129     * Does the actual parsing.
130     */
131    private void parse() {
132        determineOffsets();
133
134        if (observer != null) {
135            observer.parsed(bytes, 8, 2,
136                            "constant_pool_count: " + Hex.u2(offsets.length));
137            observer.parsed(bytes, 10, 0, "\nconstant_pool:");
138            observer.changeIndent(1);
139        }
140
141        /*
142         * Track the constant value's original string type. True if constants[i] was
143         * a CONSTANT_Utf8, false for any other type including CONSTANT_string.
144         */
145        BitSet wasUtf8 = new BitSet(offsets.length);
146
147        for (int i = 1; i < offsets.length; i++) {
148            int offset = offsets[i];
149            if ((offset != 0) && (pool.getOrNull(i) == null)) {
150                parse0(i, wasUtf8);
151            }
152        }
153
154        if (observer != null) {
155            for (int i = 1; i < offsets.length; i++) {
156                Constant cst = pool.getOrNull(i);
157                if (cst == null) {
158                    continue;
159                }
160                int offset = offsets[i];
161                int nextOffset = endOffset;
162                for (int j = i + 1; j < offsets.length; j++) {
163                    int off = offsets[j];
164                    if (off != 0) {
165                        nextOffset = off;
166                        break;
167                    }
168                }
169                String human = wasUtf8.get(i)
170                        ? Hex.u2(i) + ": utf8{\"" + cst.toHuman() + "\"}"
171                        : Hex.u2(i) + ": " + cst.toString();
172                observer.parsed(bytes, offset, nextOffset - offset, human);
173            }
174
175            observer.changeIndent(-1);
176            observer.parsed(bytes, endOffset, 0, "end constant_pool");
177        }
178    }
179
180    /**
181     * Populates {@link #offsets} and also completely parse utf8 constants.
182     */
183    private void determineOffsets() {
184        int at = 10; // offset from the start of the file to the first cst
185        int lastCategory;
186
187        for (int i = 1; i < offsets.length; i += lastCategory) {
188            offsets[i] = at;
189            int tag = bytes.getUnsignedByte(at);
190            try {
191                switch (tag) {
192                    case CONSTANT_Integer:
193                    case CONSTANT_Float:
194                    case CONSTANT_Fieldref:
195                    case CONSTANT_Methodref:
196                    case CONSTANT_InterfaceMethodref:
197                    case CONSTANT_NameAndType: {
198                        lastCategory = 1;
199                        at += 5;
200                        break;
201                    }
202                    case CONSTANT_Long:
203                    case CONSTANT_Double: {
204                        lastCategory = 2;
205                        at += 9;
206                        break;
207                    }
208                    case CONSTANT_Class:
209                    case CONSTANT_String: {
210                        lastCategory = 1;
211                        at += 3;
212                        break;
213                    }
214                    case CONSTANT_Utf8: {
215                        lastCategory = 1;
216                        at += bytes.getUnsignedShort(at + 1) + 3;
217                        break;
218                    }
219                    case CONSTANT_MethodHandle: {
220                        throw new ParseException("MethodHandle not supported");
221                    }
222                    case CONSTANT_MethodType: {
223                        throw new ParseException("MethodType not supported");
224                    }
225                    case CONSTANT_InvokeDynamic: {
226                        throw new ParseException("InvokeDynamic not supported");
227                    }
228                    default: {
229                        throw new ParseException("unknown tag byte: " + Hex.u1(tag));
230                    }
231                }
232            } catch (ParseException ex) {
233                ex.addContext("...while preparsing cst " + Hex.u2(i) + " at offset " + Hex.u4(at));
234                throw ex;
235            }
236        }
237
238        endOffset = at;
239    }
240
241    /**
242     * Parses the constant for the given index if it hasn't already been
243     * parsed, also storing it in the constant pool. This will also
244     * have the side effect of parsing any entries the indicated one
245     * depends on.
246     *
247     * @param idx which constant
248     * @return {@code non-null;} the parsed constant
249     */
250    private Constant parse0(int idx, BitSet wasUtf8) {
251        Constant cst = pool.getOrNull(idx);
252        if (cst != null) {
253            return cst;
254        }
255
256        int at = offsets[idx];
257
258        try {
259            int tag = bytes.getUnsignedByte(at);
260            switch (tag) {
261                case CONSTANT_Utf8: {
262                    cst = parseUtf8(at);
263                    wasUtf8.set(idx);
264                    break;
265                }
266                case CONSTANT_Integer: {
267                    int value = bytes.getInt(at + 1);
268                    cst = CstInteger.make(value);
269                    break;
270                }
271                case CONSTANT_Float: {
272                    int bits = bytes.getInt(at + 1);
273                    cst = CstFloat.make(bits);
274                    break;
275                }
276                case CONSTANT_Long: {
277                    long value = bytes.getLong(at + 1);
278                    cst = CstLong.make(value);
279                    break;
280                }
281                case CONSTANT_Double: {
282                    long bits = bytes.getLong(at + 1);
283                    cst = CstDouble.make(bits);
284                    break;
285                }
286                case CONSTANT_Class: {
287                    int nameIndex = bytes.getUnsignedShort(at + 1);
288                    CstString name = (CstString) parse0(nameIndex, wasUtf8);
289                    cst = new CstType(Type.internClassName(name.getString()));
290                    break;
291                }
292                case CONSTANT_String: {
293                    int stringIndex = bytes.getUnsignedShort(at + 1);
294                    cst = parse0(stringIndex, wasUtf8);
295                    break;
296                }
297                case CONSTANT_Fieldref: {
298                    int classIndex = bytes.getUnsignedShort(at + 1);
299                    CstType type = (CstType) parse0(classIndex, wasUtf8);
300                    int natIndex = bytes.getUnsignedShort(at + 3);
301                    CstNat nat = (CstNat) parse0(natIndex, wasUtf8);
302                    cst = new CstFieldRef(type, nat);
303                    break;
304                }
305                case CONSTANT_Methodref: {
306                    int classIndex = bytes.getUnsignedShort(at + 1);
307                    CstType type = (CstType) parse0(classIndex, wasUtf8);
308                    int natIndex = bytes.getUnsignedShort(at + 3);
309                    CstNat nat = (CstNat) parse0(natIndex, wasUtf8);
310                    cst = new CstMethodRef(type, nat);
311                    break;
312                }
313                case CONSTANT_InterfaceMethodref: {
314                    int classIndex = bytes.getUnsignedShort(at + 1);
315                    CstType type = (CstType) parse0(classIndex, wasUtf8);
316                    int natIndex = bytes.getUnsignedShort(at + 3);
317                    CstNat nat = (CstNat) parse0(natIndex, wasUtf8);
318                    cst = new CstInterfaceMethodRef(type, nat);
319                    break;
320                }
321                case CONSTANT_NameAndType: {
322                    int nameIndex = bytes.getUnsignedShort(at + 1);
323                    CstString name = (CstString) parse0(nameIndex, wasUtf8);
324                    int descriptorIndex = bytes.getUnsignedShort(at + 3);
325                    CstString descriptor = (CstString) parse0(descriptorIndex, wasUtf8);
326                    cst = new CstNat(name, descriptor);
327                    break;
328                }
329                case CONSTANT_MethodHandle: {
330                    throw new ParseException("MethodHandle not supported");
331                }
332                case CONSTANT_MethodType: {
333                    throw new ParseException("MethodType not supported");
334                }
335                case CONSTANT_InvokeDynamic: {
336                    throw new ParseException("InvokeDynamic not supported");
337                }
338                default: {
339                    throw new ParseException("unknown tag byte: " + Hex.u1(tag));
340                }
341            }
342        } catch (ParseException ex) {
343            ex.addContext("...while parsing cst " + Hex.u2(idx) +
344                          " at offset " + Hex.u4(at));
345            throw ex;
346        } catch (RuntimeException ex) {
347            ParseException pe = new ParseException(ex);
348            pe.addContext("...while parsing cst " + Hex.u2(idx) +
349                          " at offset " + Hex.u4(at));
350            throw pe;
351        }
352
353        pool.set(idx, cst);
354        return cst;
355    }
356
357    /**
358     * Parses a utf8 constant.
359     *
360     * @param at offset to the start of the constant (where the tag byte is)
361     * @return {@code non-null;} the parsed value
362     */
363    private CstString parseUtf8(int at) {
364        int length = bytes.getUnsignedShort(at + 1);
365
366        at += 3; // Skip to the data.
367
368        ByteArray ubytes = bytes.slice(at, at + length);
369
370        try {
371            return new CstString(ubytes);
372        } catch (IllegalArgumentException ex) {
373            // Translate the exception
374            throw new ParseException(ex);
375        }
376    }
377}
378