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