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