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