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 
17 package com.android.dx.dex.file;
18 
19 import com.android.dx.dex.code.CatchHandlerList;
20 import com.android.dx.dex.code.CatchTable;
21 import com.android.dx.dex.code.DalvCode;
22 import com.android.dx.util.AnnotatedOutput;
23 import com.android.dx.util.ByteArrayAnnotatedOutput;
24 import com.android.dx.util.Hex;
25 import java.io.PrintWriter;
26 import java.util.Map;
27 import 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  */
35 public 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      */
CatchStructs(DalvCode code)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      */
finishProcessingIfNecessary()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      */
triesSize()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      */
debugPrint(PrintWriter out, String prefix)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      */
encode(DexFile file)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      */
writeSize()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      */
writeTo(DexFile file, AnnotatedOutput out)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      */
annotateEntries(String prefix, PrintWriter printTo, AnnotatedOutput annotateTo)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      */
annotateAndConsumeHandlers(CatchHandlerList handlers, int offset, int size, String prefix, PrintWriter printTo, AnnotatedOutput annotateTo)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