summaryrefslogtreecommitdiffstats
path: root/dx/src/com/android/dx/dex/file/CatchStructs.java
blob: 8b0f1bdd7bbd4567db49735a6d9c87494d69a7e3 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
/*
 * Copyright (C) 2008 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.dx.dex.file;

import com.android.dx.dex.code.CatchHandlerList;
import com.android.dx.dex.code.CatchTable;
import com.android.dx.dex.code.DalvCode;
import com.android.dx.util.AnnotatedOutput;
import com.android.dx.util.ByteArrayAnnotatedOutput;
import com.android.dx.util.Hex;

import java.io.PrintWriter;
import java.util.Map;
import java.util.TreeMap;

/**
 * List of exception handlers (tuples of covered range, catch type,
 * handler address) for a particular piece of code. Instances of this
 * class correspond to a {@code try_item[]} and a
 * {@code catch_handler_item[]}.
 */
public final class CatchStructs {
    /**
     * the size of a {@code try_item}: a {@code uint}
     * and two {@code ushort}s
     */
    private static final int TRY_ITEM_WRITE_SIZE = 4 + (2 * 2);

    /** {@code non-null;} code that contains the catches */
    private final DalvCode code;

    /**
     * {@code null-ok;} the underlying table; set in
     * {@link #finishProcessingIfNecessary}
     */
    private CatchTable table;

    /**
     * {@code null-ok;} the encoded handler list, if calculated; set in
     * {@link #encode}
     */
    private byte[] encodedHandlers;

    /**
     * length of the handlers header (encoded size), if known; used for
     * annotation
     */
    private int encodedHandlerHeaderSize;

    /**
     * {@code null-ok;} map from handler lists to byte offsets, if calculated; set in
     * {@link #encode}
     */
    private TreeMap<CatchHandlerList, Integer> handlerOffsets;

    /**
     * Constructs an instance.
     *
     * @param code {@code non-null;} code that contains the catches
     */
    public CatchStructs(DalvCode code) {
        this.code = code;
        this.table = null;
        this.encodedHandlers = null;
        this.encodedHandlerHeaderSize = 0;
        this.handlerOffsets = null;
    }

    /**
     * Finish processing the catches, if necessary.
     */
    private void finishProcessingIfNecessary() {
        if (table == null) {
            table = code.getCatches();
        }
    }

    /**
     * Gets the size of the tries list, in entries.
     *
     * @return {@code >= 0;} the tries list size
     */
    public int triesSize() {
        finishProcessingIfNecessary();
        return table.size();
    }

    /**
     * Does a human-friendly dump of this instance.
     *
     * @param out {@code non-null;} where to dump
     * @param prefix {@code non-null;} prefix to attach to each line of output
     */
    public void debugPrint(PrintWriter out, String prefix) {
        annotateEntries(prefix, out, null);
    }

    /**
     * Encodes the handler lists.
     *
     * @param file {@code non-null;} file this instance is part of
     */
    public void encode(DexFile file) {
        finishProcessingIfNecessary();

        TypeIdsSection typeIds = file.getTypeIds();
        int size = table.size();

        handlerOffsets = new TreeMap<CatchHandlerList, Integer>();

        /*
         * First add a map entry for each unique list. The tree structure
         * will ensure they are sorted when we reiterate later.
         */
        for (int i = 0; i < size; i++) {
            handlerOffsets.put(table.get(i).getHandlers(), null);
        }

        if (handlerOffsets.size() > 65535) {
            throw new UnsupportedOperationException(
                    "too many catch handlers");
        }

        ByteArrayAnnotatedOutput out = new ByteArrayAnnotatedOutput();

        // Write out the handlers "header" consisting of its size in entries.
        encodedHandlerHeaderSize =
            out.writeUleb128(handlerOffsets.size());

        // Now write the lists out in order, noting the offset of each.
        for (Map.Entry<CatchHandlerList, Integer> mapping :
                 handlerOffsets.entrySet()) {
            CatchHandlerList list = mapping.getKey();
            int listSize = list.size();
            boolean catchesAll = list.catchesAll();

            // Set the offset before we do any writing.
            mapping.setValue(out.getCursor());

            if (catchesAll) {
                // A size <= 0 means that the list ends with a catch-all.
                out.writeSleb128(-(listSize - 1));
                listSize--;
            } else {
                out.writeSleb128(listSize);
            }

            for (int i = 0; i < listSize; i++) {
                CatchHandlerList.Entry entry = list.get(i);
                out.writeUleb128(
                        typeIds.indexOf(entry.getExceptionType()));
                out.writeUleb128(entry.getHandler());
            }

            if (catchesAll) {
                out.writeUleb128(list.get(listSize).getHandler());
            }
        }

        encodedHandlers = out.toByteArray();
    }

    /**
     * Gets the write size of this instance, in bytes.
     *
     * @return {@code >= 0;} the write size
     */
    public int writeSize() {
        return (triesSize() * TRY_ITEM_WRITE_SIZE) +
                + encodedHandlers.length;
    }

    /**
     * Writes this instance to the given stream.
     *
     * @param file {@code non-null;} file this instance is part of
     * @param out {@code non-null;} where to write to
     */
    public void writeTo(DexFile file, AnnotatedOutput out) {
        finishProcessingIfNecessary();

        if (out.annotates()) {
            annotateEntries("  ", null, out);
        }

        int tableSize = table.size();
        for (int i = 0; i < tableSize; i++) {
            CatchTable.Entry one = table.get(i);
            int start = one.getStart();
            int end = one.getEnd();
            int insnCount = end - start;

            if (insnCount >= 65536) {
                throw new UnsupportedOperationException(
                        "bogus exception range: " + Hex.u4(start) + ".." +
                        Hex.u4(end));
            }

            out.writeInt(start);
            out.writeShort(insnCount);
            out.writeShort(handlerOffsets.get(one.getHandlers()));
        }

        out.write(encodedHandlers);
    }

    /**
     * Helper method to annotate or simply print the exception handlers.
     * Only one of {@code printTo} or {@code annotateTo} should
     * be non-null.
     *
     * @param prefix {@code non-null;} prefix for each line
     * @param printTo {@code null-ok;} where to print to
     * @param annotateTo {@code null-ok;} where to consume bytes and annotate to
     */
    private void annotateEntries(String prefix, PrintWriter printTo,
            AnnotatedOutput annotateTo) {
        finishProcessingIfNecessary();

        boolean consume = (annotateTo != null);
        int amt1 = consume ? 6 : 0;
        int amt2 = consume ? 2 : 0;
        int size = table.size();
        String subPrefix = prefix + "  ";

        if (consume) {
            annotateTo.annotate(0, prefix + "tries:");
        } else {
            printTo.println(prefix + "tries:");
        }

        for (int i = 0; i < size; i++) {
            CatchTable.Entry entry = table.get(i);
            CatchHandlerList handlers = entry.getHandlers();
            String s1 = subPrefix + "try " + Hex.u2or4(entry.getStart())
                + ".." + Hex.u2or4(entry.getEnd());
            String s2 = handlers.toHuman(subPrefix, "");

            if (consume) {
                annotateTo.annotate(amt1, s1);
                annotateTo.annotate(amt2, s2);
            } else {
                printTo.println(s1);
                printTo.println(s2);
            }
        }

        if (! consume) {
            // Only emit the handler lists if we are consuming bytes.
            return;
        }

        annotateTo.annotate(0, prefix + "handlers:");
        annotateTo.annotate(encodedHandlerHeaderSize,
                subPrefix + "size: " + Hex.u2(handlerOffsets.size()));

        int lastOffset = 0;
        CatchHandlerList lastList = null;

        for (Map.Entry<CatchHandlerList, Integer> mapping :
                 handlerOffsets.entrySet()) {
            CatchHandlerList list = mapping.getKey();
            int offset = mapping.getValue();

            if (lastList != null) {
                annotateAndConsumeHandlers(lastList, lastOffset,
                        offset - lastOffset, subPrefix, printTo, annotateTo);
            }

            lastList = list;
            lastOffset = offset;
        }

        annotateAndConsumeHandlers(lastList, lastOffset,
                encodedHandlers.length - lastOffset,
                subPrefix, printTo, annotateTo);
    }

    /**
     * Helper for {@link #annotateEntries} to annotate a catch handler list
     * while consuming it.
     *
     * @param handlers {@code non-null;} handlers to annotate
     * @param offset {@code >= 0;} the offset of this handler
     * @param size {@code >= 1;} the number of bytes the handlers consume
     * @param prefix {@code non-null;} prefix for each line
     * @param printTo {@code null-ok;} where to print to
     * @param annotateTo {@code non-null;} where to annotate to
     */
    private static void annotateAndConsumeHandlers(CatchHandlerList handlers,
            int offset, int size, String prefix, PrintWriter printTo,
            AnnotatedOutput annotateTo) {
        String s = handlers.toHuman(prefix, Hex.u2(offset) + ": ");

        if (printTo != null) {
            printTo.println(s);
        }

        annotateTo.annotate(size, s);
    }
}