• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.rop.cst.CstType;
23 import com.android.dx.rop.type.Type;
24 import com.android.dx.util.AnnotatedOutput;
25 import com.android.dx.util.ByteArrayAnnotatedOutput;
26 import com.android.dx.util.Hex;
27 
28 import java.io.PrintWriter;
29 import java.util.Map;
30 import 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[]} and a
36  * {@code catch_handler_item[]}.
37  */
38 public final class CatchStructs {
39     /**
40      * the size of a {@code try_item}: a {@code uint}
41      * and two {@code ushort}s
42      */
43     private static final int TRY_ITEM_WRITE_SIZE = 4 + (2 * 2);
44 
45     /** {@code non-null;} code that contains the catches */
46     private final DalvCode code;
47 
48     /**
49      * {@code null-ok;} the underlying table; set in
50      * {@link #finishProcessingIfNecessary}
51      */
52     private CatchTable table;
53 
54     /**
55      * {@code 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      * {@code 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 {@code non-null;} code that contains the catches
76      */
CatchStructs(DalvCode code)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      */
finishProcessingIfNecessary()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 {@code >= 0;} the tries list size
98      */
triesSize()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 {@code non-null;} where to dump
108      * @param prefix {@code non-null;} prefix to attach to each line of output
109      */
debugPrint(PrintWriter out, String prefix)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 {@code non-null;} file this instance is part of
118      */
encode(DexFile file)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 {@code >= 0;} the write size
183      */
writeSize()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 {@code non-null;} file this instance is part of
193      * @param out {@code non-null;} where to write to
194      */
writeTo(DexFile file, AnnotatedOutput out)195     public void writeTo(DexFile file, AnnotatedOutput out) {
196         finishProcessingIfNecessary();
197 
198         if (out.annotates()) {
199             annotateEntries("  ", null, out);
200         }
201 
202         int tableSize = table.size();
203         for (int i = 0; i < tableSize; i++) {
204             CatchTable.Entry one = table.get(i);
205             int start = one.getStart();
206             int end = one.getEnd();
207             int insnCount = end - start;
208 
209             if (insnCount >= 65536) {
210                 throw new UnsupportedOperationException(
211                         "bogus exception range: " + Hex.u4(start) + ".." +
212                         Hex.u4(end));
213             }
214 
215             out.writeInt(start);
216             out.writeShort(insnCount);
217             out.writeShort(handlerOffsets.get(one.getHandlers()));
218         }
219 
220         out.write(encodedHandlers);
221     }
222 
223     /**
224      * Helper method to annotate or simply print the exception handlers.
225      * Only one of {@code printTo} or {@code annotateTo} should
226      * be non-null.
227      *
228      * @param prefix {@code non-null;} prefix for each line
229      * @param printTo {@code null-ok;} where to print to
230      * @param annotateTo {@code null-ok;} where to consume bytes and annotate to
231      */
annotateEntries(String prefix, PrintWriter printTo, AnnotatedOutput annotateTo)232     private void annotateEntries(String prefix, PrintWriter printTo,
233             AnnotatedOutput annotateTo) {
234         finishProcessingIfNecessary();
235 
236         boolean consume = (annotateTo != null);
237         int amt1 = consume ? 6 : 0;
238         int amt2 = consume ? 2 : 0;
239         int size = table.size();
240         String subPrefix = prefix + "  ";
241 
242         if (consume) {
243             annotateTo.annotate(0, prefix + "tries:");
244         } else {
245             printTo.println(prefix + "tries:");
246         }
247 
248         for (int i = 0; i < size; i++) {
249             CatchTable.Entry entry = table.get(i);
250             CatchHandlerList handlers = entry.getHandlers();
251             String s1 = subPrefix + "try " + Hex.u2or4(entry.getStart())
252                 + ".." + Hex.u2or4(entry.getEnd());
253             String s2 = handlers.toHuman(subPrefix, "");
254 
255             if (consume) {
256                 annotateTo.annotate(amt1, s1);
257                 annotateTo.annotate(amt2, s2);
258             } else {
259                 printTo.println(s1);
260                 printTo.println(s2);
261             }
262         }
263 
264         if (! consume) {
265             // Only emit the handler lists if we are consuming bytes.
266             return;
267         }
268 
269         annotateTo.annotate(0, prefix + "handlers:");
270         annotateTo.annotate(encodedHandlerHeaderSize,
271                 subPrefix + "size: " + Hex.u2(handlerOffsets.size()));
272 
273         int lastOffset = 0;
274         CatchHandlerList lastList = null;
275 
276         for (Map.Entry<CatchHandlerList, Integer> mapping :
277                  handlerOffsets.entrySet()) {
278             CatchHandlerList list = mapping.getKey();
279             int offset = mapping.getValue();
280 
281             if (lastList != null) {
282                 annotateAndConsumeHandlers(lastList, lastOffset,
283                         offset - lastOffset, subPrefix, printTo, annotateTo);
284             }
285 
286             lastList = list;
287             lastOffset = offset;
288         }
289 
290         annotateAndConsumeHandlers(lastList, lastOffset,
291                 encodedHandlers.length - lastOffset,
292                 subPrefix, printTo, annotateTo);
293     }
294 
295     /**
296      * Helper for {@link #annotateEntries} to annotate a catch handler list
297      * while consuming it.
298      *
299      * @param handlers {@code non-null;} handlers to annotate
300      * @param offset {@code >= 0;} the offset of this handler
301      * @param size {@code >= 1;} the number of bytes the handlers consume
302      * @param prefix {@code non-null;} prefix for each line
303      * @param printTo {@code null-ok;} where to print to
304      * @param annotateTo {@code non-null;} where to annotate to
305      */
annotateAndConsumeHandlers(CatchHandlerList handlers, int offset, int size, String prefix, PrintWriter printTo, AnnotatedOutput annotateTo)306     private static void annotateAndConsumeHandlers(CatchHandlerList handlers,
307             int offset, int size, String prefix, PrintWriter printTo,
308             AnnotatedOutput annotateTo) {
309         String s = handlers.toHuman(prefix, Hex.u2(offset) + ": ");
310 
311         if (printTo != null) {
312             printTo.println(s);
313         }
314 
315         annotateTo.annotate(size, s);
316     }
317 }
318