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