CatchStructs.java revision f6c387128427e121477c1b32ad35cdcaa5101ba3
1/*
2 * Copyright (C) 2008 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.dex.file;
18
19import com.android.dx.dex.code.CatchHandlerList;
20import com.android.dx.dex.code.CatchTable;
21import com.android.dx.dex.code.DalvCode;
22import com.android.dx.rop.cst.CstType;
23import com.android.dx.rop.type.Type;
24import com.android.dx.util.AnnotatedOutput;
25import com.android.dx.util.ByteArrayAnnotatedOutput;
26import com.android.dx.util.Hex;
27
28import java.io.PrintWriter;
29import java.util.Map;
30import java.util.TreeMap;
31
32/**
33 * List of exception handlers (tuples of covered range, catch type,
34 * handler address) for a particular piece of code. Instances of this
35 * class correspond to a <code>try_item[]</code> and a
36 * <code>catch_handler_item[]</code>.
37 */
38public final class CatchStructs {
39    /**
40     * the size of a <code>try_item</code>: a <code>uint</code>
41     * and two <code>ushort</code>s
42     */
43    private static final int TRY_ITEM_WRITE_SIZE = 4 + (2 * 2);
44
45    /** non-null; code that contains the catches */
46    private final DalvCode code;
47
48    /**
49     * null-ok; the underlying table; set in
50     * {@link #finishProcessingIfNecessary}
51     */
52    private CatchTable table;
53
54    /**
55     * null-ok; the encoded handler list, if calculated; set in
56     * {@link #encode}
57     */
58    private byte[] encodedHandlers;
59
60    /**
61     * length of the handlers header (encoded size), if known; used for
62     * annotation
63     */
64    private int encodedHandlerHeaderSize;
65
66    /**
67     * null-ok; map from handler lists to byte offsets, if calculated; set in
68     * {@link #encode}
69     */
70    private TreeMap<CatchHandlerList, Integer> handlerOffsets;
71
72    /**
73     * Constructs an instance.
74     *
75     * @param code non-null; code that contains the catches
76     */
77    public CatchStructs(DalvCode code) {
78        this.code = code;
79        this.table = null;
80        this.encodedHandlers = null;
81        this.encodedHandlerHeaderSize = 0;
82        this.handlerOffsets = null;
83    }
84
85    /**
86     * Finish processing the catches, if necessary.
87     */
88    private void finishProcessingIfNecessary() {
89        if (table == null) {
90            table = code.getCatches();
91        }
92    }
93
94    /**
95     * Gets the size of the tries list, in entries.
96     *
97     * @return &gt;= 0; the tries list size
98     */
99    public int triesSize() {
100        finishProcessingIfNecessary();
101        return table.size();
102    }
103
104    /**
105     * Does a human-friendly dump of this instance.
106     *
107     * @param out non-null; where to dump
108     * @param prefix non-null; prefix to attach to each line of output
109     */
110    public void debugPrint(PrintWriter out, String prefix) {
111        annotateEntries(prefix, out, null);
112    }
113
114    /**
115     * Encodes the handler lists.
116     *
117     * @param file non-null; file this instance is part of
118     */
119    public void encode(DexFile file) {
120        finishProcessingIfNecessary();
121
122        TypeIdsSection typeIds = file.getTypeIds();
123        int size = table.size();
124
125        handlerOffsets = new TreeMap<CatchHandlerList, Integer>();
126
127        /*
128         * First add a map entry for each unique list. The tree structure
129         * will ensure they are sorted when we reiterate later.
130         */
131        for (int i = 0; i < size; i++) {
132            handlerOffsets.put(table.get(i).getHandlers(), null);
133        }
134
135        if (handlerOffsets.size() > 65535) {
136            throw new UnsupportedOperationException(
137                    "too many catch handlers");
138        }
139
140        ByteArrayAnnotatedOutput out = new ByteArrayAnnotatedOutput();
141
142        // Write out the handlers "header" consisting of its size in entries.
143        encodedHandlerHeaderSize =
144            out.writeUnsignedLeb128(handlerOffsets.size());
145
146        // Now write the lists out in order, noting the offset of each.
147        for (Map.Entry<CatchHandlerList, Integer> mapping :
148                 handlerOffsets.entrySet()) {
149            CatchHandlerList list = mapping.getKey();
150            int listSize = list.size();
151            boolean catchesAll = list.catchesAll();
152
153            // Set the offset before we do any writing.
154            mapping.setValue(out.getCursor());
155
156            if (catchesAll) {
157                // A size <= 0 means that the list ends with a catch-all.
158                out.writeSignedLeb128(-(listSize - 1));
159                listSize--;
160            } else {
161                out.writeSignedLeb128(listSize);
162            }
163
164            for (int i = 0; i < listSize; i++) {
165                CatchHandlerList.Entry entry = list.get(i);
166                out.writeUnsignedLeb128(
167                        typeIds.indexOf(entry.getExceptionType()));
168                out.writeUnsignedLeb128(entry.getHandler());
169            }
170
171            if (catchesAll) {
172                out.writeUnsignedLeb128(list.get(listSize).getHandler());
173            }
174        }
175
176        encodedHandlers = out.toByteArray();
177    }
178
179    /**
180     * Gets the write size of this instance, in bytes.
181     *
182     * @return &gt;= 0; the write size
183     */
184    public int writeSize() {
185        return (triesSize() * TRY_ITEM_WRITE_SIZE) +
186                + encodedHandlers.length;
187    }
188
189    /**
190     * Writes this instance to the given stream.
191     *
192     * @param file non-null; file this instance is part of
193     * @param out non-null; where to write to
194     */
195    public void writeTo(DexFile file, AnnotatedOutput out) {
196        finishProcessingIfNecessary();
197
198        TypeIdsSection typeIds = file.getTypeIds();
199        int tableSize = table.size();
200        int handlersSize = handlerOffsets.size();
201
202        if (out.annotates()) {
203            annotateEntries("  ", null, out);
204        }
205
206        for (int i = 0; i < tableSize; i++) {
207            CatchTable.Entry one = table.get(i);
208            int start = one.getStart();
209            int end = one.getEnd();
210            int insnCount = end - start;
211
212            if (insnCount >= 65536) {
213                throw new UnsupportedOperationException(
214                        "bogus exception range: " + Hex.u4(start) + ".." +
215                        Hex.u4(end));
216            }
217
218            out.writeInt(start);
219            out.writeShort(insnCount);
220            out.writeShort(handlerOffsets.get(one.getHandlers()));
221        }
222
223        out.write(encodedHandlers);
224    }
225
226    /**
227     * Helper method to annotate or simply print the exception handlers.
228     * Only one of <code>printTo</code> or <code>annotateTo</code> should
229     * be non-null.
230     *
231     * @param prefix non-null; prefix for each line
232     * @param printTo null-ok; where to print to
233     * @param annotateTo null-ok; where to consume bytes and annotate to
234     */
235    private void annotateEntries(String prefix, PrintWriter printTo,
236            AnnotatedOutput annotateTo) {
237        finishProcessingIfNecessary();
238
239        boolean consume = (annotateTo != null);
240        int amt1 = consume ? 6 : 0;
241        int amt2 = consume ? 2 : 0;
242        int size = table.size();
243        String subPrefix = prefix + "  ";
244
245        if (consume) {
246            annotateTo.annotate(0, prefix + "tries:");
247        } else {
248            printTo.println(prefix + "tries:");
249        }
250
251        for (int i = 0; i < size; i++) {
252            CatchTable.Entry entry = table.get(i);
253            CatchHandlerList handlers = entry.getHandlers();
254            String s1 = subPrefix + "try " + Hex.u2or4(entry.getStart())
255                + ".." + Hex.u2or4(entry.getEnd());
256            String s2 = handlers.toHuman(subPrefix, "");
257
258            if (consume) {
259                annotateTo.annotate(amt1, s1);
260                annotateTo.annotate(amt2, s2);
261            } else {
262                printTo.println(s1);
263                printTo.println(s2);
264            }
265        }
266
267        if (! consume) {
268            // Only emit the handler lists if we are consuming bytes.
269            return;
270        }
271
272        annotateTo.annotate(0, prefix + "handlers:");
273        annotateTo.annotate(encodedHandlerHeaderSize,
274                subPrefix + "size: " + Hex.u2(handlerOffsets.size()));
275
276        int lastOffset = 0;
277        CatchHandlerList lastList = null;
278
279        for (Map.Entry<CatchHandlerList, Integer> mapping :
280                 handlerOffsets.entrySet()) {
281            CatchHandlerList list = mapping.getKey();
282            int offset = mapping.getValue();
283
284            if (lastList != null) {
285                annotateAndConsumeHandlers(lastList, lastOffset,
286                        offset - lastOffset, subPrefix, printTo, annotateTo);
287            }
288
289            lastList = list;
290            lastOffset = offset;
291        }
292
293        annotateAndConsumeHandlers(lastList, lastOffset,
294                encodedHandlers.length - lastOffset,
295                subPrefix, printTo, annotateTo);
296    }
297
298    /**
299     * Helper for {@link #annotateEntries} to annotate a catch handler list
300     * while consuming it.
301     *
302     * @param handlers non-null; handlers to annotate
303     * @param offset &gt;= 0; the offset of this handler
304     * @param size &gt;= 1; the number of bytes the handlers consume
305     * @param prefix non-null; prefix for each line
306     * @param printTo null-ok; where to print to
307     * @param annotateTo non-null; where to annotate to
308     */
309    private static void annotateAndConsumeHandlers(CatchHandlerList handlers,
310            int offset, int size, String prefix, PrintWriter printTo,
311            AnnotatedOutput annotateTo) {
312        String s = handlers.toHuman(prefix, Hex.u2(offset) + ": ");
313
314        if (printTo != null) {
315            printTo.println(s);
316        }
317
318        annotateTo.annotate(size, s);
319    }
320}
321