TryListBuilder.java revision ab73502b60fadc966ba3ace0aa4b62592cf2ae86
1/*
2 * Copyright 2013, Google Inc.
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are
7 * met:
8 *
9 *     * Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 *     * Redistributions in binary form must reproduce the above
12 * copyright notice, this list of conditions and the following disclaimer
13 * in the documentation and/or other materials provided with the
14 * distribution.
15 *     * Neither the name of Google Inc. nor the names of its
16 * contributors may be used to endorse or promote products derived from
17 * this software without specific prior written permission.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 */
31
32package org.jf.dexlib2.writer.util;
33
34import com.google.common.collect.Lists;
35import org.jf.dexlib2.base.BaseTryBlock;
36import org.jf.dexlib2.iface.ExceptionHandler;
37import org.jf.dexlib2.iface.TryBlock;
38import org.jf.dexlib2.immutable.ImmutableExceptionHandler;
39import org.jf.util.ExceptionWithContext;
40
41import javax.annotation.Nonnull;
42import javax.annotation.Nullable;
43import java.util.Iterator;
44import java.util.List;
45import java.util.NoSuchElementException;
46
47public class TryListBuilder
48{
49    /*TODO: add logic to merge adjacent, identical try blocks, and remove superflous handlers
50      Also provide a "strict" mode, where the above isn't performed, which will be useful to be able to
51      exactly reproduce the original .dex file (for testing/verification purposes)*/
52
53    // Linked list sentinels that don't represent an actual try block
54    // Their values are never modified, only their links
55    private final MutableTryBlock listStart;
56    private final MutableTryBlock listEnd;
57
58    public TryListBuilder() {
59        listStart = new MutableTryBlock(0, 0);
60        listEnd = new MutableTryBlock(0, 0);
61        listStart.next = listEnd;
62        listEnd.prev = listStart;
63    }
64
65    public static List<TryBlock> massageTryBlocks(List<? extends TryBlock> tryBlocks) {
66        TryListBuilder tlb = new TryListBuilder();
67        for (TryBlock tryBlock: tryBlocks) {
68            int startAddress = tryBlock.getStartCodeAddress();
69            int endAddress = startAddress + tryBlock.getCodeUnitCount();
70
71            for (ExceptionHandler exceptionHandler: tryBlock.getExceptionHandlers()) {
72                tlb.addHandler(exceptionHandler.getExceptionType(), startAddress, endAddress,
73                        exceptionHandler.getHandlerCodeAddress());
74            }
75        }
76        return tlb.getTryBlocks();
77    }
78
79    private static class TryBounds {
80        @Nonnull public final MutableTryBlock start;
81        @Nonnull public final MutableTryBlock end;
82
83        public TryBounds(@Nonnull MutableTryBlock start, @Nonnull MutableTryBlock end) {
84            this.start = start;
85            this.end = end;
86        }
87    }
88
89    public static class InvalidTryException extends ExceptionWithContext {
90        public InvalidTryException(Throwable cause) {
91            super(cause);
92        }
93
94        public InvalidTryException(Throwable cause, String message, Object... formatArgs) {
95            super(cause, message, formatArgs);
96        }
97
98        public InvalidTryException(String message, Object... formatArgs) {
99            super(message, formatArgs);
100        }
101    }
102
103    private static class MutableTryBlock extends BaseTryBlock implements TryBlock {
104        public MutableTryBlock prev = null;
105        public MutableTryBlock next = null;
106
107        public int startCodeAddress;
108        public int endCodeAddress;
109        @Nonnull public List<ExceptionHandler> exceptionHandlers = Lists.newArrayList();
110
111        public MutableTryBlock(int startCodeAddress, int endCodeAddress) {
112            this.startCodeAddress = startCodeAddress;
113            this.endCodeAddress = endCodeAddress;
114        }
115
116        public MutableTryBlock(int startCodeAddress, int endCodeAddress,
117                               @Nonnull List<? extends ExceptionHandler> exceptionHandlers) {
118            this.startCodeAddress = startCodeAddress;
119            this.endCodeAddress = endCodeAddress;
120            this.exceptionHandlers = Lists.newArrayList(exceptionHandlers);
121        }
122
123        @Override public int getStartCodeAddress() {
124            return startCodeAddress;
125        }
126
127        @Override public int getCodeUnitCount() {
128            return endCodeAddress - startCodeAddress;
129        }
130
131        @Nonnull @Override public List<? extends ExceptionHandler> getExceptionHandlers() {
132            return exceptionHandlers;
133        }
134
135        @Nonnull
136        public MutableTryBlock split(int splitAddress) {
137            MutableTryBlock newTryBlock = new MutableTryBlock(splitAddress, endCodeAddress, exceptionHandlers);
138            endCodeAddress = splitAddress;
139            append(newTryBlock);
140            return newTryBlock;
141        }
142
143        public void delete() {
144            next.prev = prev;
145            prev.next = next;
146        }
147
148        public void mergeNext() {
149            //assert next.startCodeAddress == this.endCodeAddress;
150            this.endCodeAddress = next.endCodeAddress;
151            next.delete();
152        }
153
154        public void append(@Nonnull MutableTryBlock tryBlock) {
155            next.prev = tryBlock;
156            tryBlock.next = next;
157            tryBlock.prev = this;
158            next = tryBlock;
159        }
160
161        public void prepend(@Nonnull MutableTryBlock tryBlock) {
162            prev.next = tryBlock;
163            tryBlock.prev = prev;
164            tryBlock.next = this;
165            prev = tryBlock;
166        }
167
168        public void addHandler(@Nonnull ExceptionHandler handler) {
169            for (ExceptionHandler existingHandler: exceptionHandlers) {
170                String existingType = existingHandler.getExceptionType();
171                String newType = handler.getExceptionType();
172
173                // Don't add it if we already have a handler of the same type
174                if (existingType == null) {
175                    if (newType == null) {
176                        if (existingHandler.getHandlerCodeAddress() != handler.getHandlerCodeAddress()) {
177                            throw new InvalidTryException(
178                                    "Multiple overlapping catch all handlers with different handlers");
179                        }
180                        return;
181                    }
182                } else if (existingType.equals(newType)) {
183                    if (existingHandler.getHandlerCodeAddress() != handler.getHandlerCodeAddress()) {
184                        throw new InvalidTryException(
185                                "Multiple overlapping catches for %s with different handlers", existingType);
186                    }
187                    return;
188                }
189            }
190
191            exceptionHandlers.add(handler);
192        }
193    }
194
195    private TryBounds getBoundingRanges(int startAddress, int endAddress) {
196        MutableTryBlock startBlock = null;
197
198        MutableTryBlock tryBlock = listStart.next;
199        while (tryBlock != listEnd) {
200            int currentStartAddress = tryBlock.startCodeAddress;
201            int currentEndAddress = tryBlock.endCodeAddress;
202
203            if (startAddress == currentStartAddress) {
204                //|-----|
205                //^------
206                /*Bam. We hit the start of the range right on the head*/
207                startBlock = tryBlock;
208                break;
209            } else if (startAddress > currentStartAddress && startAddress < currentEndAddress) {
210                //|-----|
211                //  ^----
212                /*Almost. The start of the range being added is in the middle
213                of an existing try range. We need to split the existing range
214                at the start address of the range being added*/
215                startBlock = tryBlock.split(startAddress);
216                break;
217            }else if (startAddress < currentStartAddress) {
218                if (endAddress <= currentStartAddress) {
219                    //      |-----|
220                    //^--^
221                    /*Oops, totally too far! The new range doesn't overlap any existing
222                    ones, so we just add it and return*/
223                    startBlock = new MutableTryBlock(startAddress, endAddress);
224                    tryBlock.prepend(startBlock);
225                    return new TryBounds(startBlock, startBlock);
226                } else {
227                    //   |-----|
228                    //^---------
229                    /*Oops, too far! We've passed the start of the range being added, but
230                     the new range does overlap this one. We need to add a new range just
231                     before this one*/
232                    startBlock = new MutableTryBlock(startAddress, currentStartAddress);
233                    tryBlock.prepend(startBlock);
234                    break;
235                }
236            }
237
238            tryBlock = tryBlock.next;
239        }
240
241        //|-----|
242        //        ^-----
243        /*Either the list of tries is blank, or all the tries in the list
244        end before the range being added starts. In either case, we just need
245        to add a new range at the end of the list*/
246        if (startBlock == null) {
247            startBlock = new MutableTryBlock(startAddress, endAddress);
248            listEnd.prepend(startBlock);
249            return new TryBounds(startBlock, startBlock);
250        }
251
252        tryBlock = startBlock;
253        while (tryBlock != listEnd) {
254            int currentStartAddress = tryBlock.startCodeAddress;
255            int currentEndAddress = tryBlock.endCodeAddress;
256
257            if (endAddress == currentEndAddress) {
258                //|-----|
259                //------^
260                /*Bam! We hit the end right on the head... err, tail.*/
261                return new TryBounds(startBlock, tryBlock);
262            } else if (endAddress > currentStartAddress && endAddress < currentEndAddress) {
263                //|-----|
264                //--^
265                /*Almost. The range being added ends in the middle of an
266                existing range. We need to split the existing range
267                at the end of the range being added.*/
268                tryBlock.split(endAddress);
269                return new TryBounds(startBlock, tryBlock);
270            } else if (endAddress <= currentStartAddress) {
271                //|-----|       |-----|
272                //-----------^
273                /*Oops, too far! The current range starts after the range being added
274                ends. We need to create a new range that starts at the end of the
275                previous range, and ends at the end of the range being added*/
276                MutableTryBlock endBlock = new MutableTryBlock(tryBlock.prev.endCodeAddress, endAddress);
277                tryBlock.prepend(endBlock);
278                return new TryBounds(startBlock, endBlock);
279            }
280            tryBlock = tryBlock.next;
281        }
282
283        //|-----|
284        //--------^
285        /*The last range in the list ended before the end of the range being added.
286        We need to add a new range that starts at the end of the last range in the
287        list, and ends at the end of the range being added.*/
288        MutableTryBlock endBlock = new MutableTryBlock(listEnd.prev.endCodeAddress, endAddress);
289        listEnd.prepend(endBlock);
290        return new TryBounds(startBlock, endBlock);
291    }
292
293    public void addHandler(String type, int startAddress, int endAddress, int handlerAddress) {
294        TryBounds bounds = getBoundingRanges(startAddress, endAddress);
295
296        MutableTryBlock startBlock = bounds.start;
297        MutableTryBlock endBlock = bounds.end;
298
299        ExceptionHandler handler = new ImmutableExceptionHandler(type, handlerAddress);
300
301        int previousEnd = startAddress;
302        MutableTryBlock tryBlock = startBlock;
303
304        /*Now we have the start and end ranges that exactly match the start and end
305        of the range being added. We need to iterate over all the ranges from the start
306        to end range inclusively, and append the handler to the end of each range's handler
307        list. We also need to create a new range for any "holes" in the existing ranges*/
308        do
309        {
310            //is there a hole? If so, add a new range to fill the hole
311            if (tryBlock.startCodeAddress > previousEnd) {
312                MutableTryBlock newBlock = new MutableTryBlock(previousEnd, tryBlock.startCodeAddress);
313                tryBlock.prepend(newBlock);
314                tryBlock = newBlock;
315            }
316
317            tryBlock.addHandler(handler);
318            previousEnd = tryBlock.endCodeAddress;
319            tryBlock = tryBlock.next;
320        } while (tryBlock.prev != endBlock);
321    }
322
323    public List<TryBlock> getTryBlocks() {
324        return Lists.newArrayList(new Iterator<TryBlock>() {
325            // The next TryBlock to return. This has already been merged, if needed.
326            @Nullable private MutableTryBlock next;
327
328            {
329                next = listStart;
330                next = readNextItem();
331            }
332
333            /**
334             * Read the item that comes after the current value of the next field.
335             * @return The next item, or null if there is no next item
336             */
337            @Nullable protected MutableTryBlock readNextItem() {
338                // We can assume that next is not null
339                MutableTryBlock ret = next.next;
340
341                if (ret == listEnd) {
342                    return null;
343                }
344
345                while (ret.next != listEnd) {
346                    if (ret.endCodeAddress == ret.next.startCodeAddress &&
347                            ret.getExceptionHandlers().equals(ret.next.getExceptionHandlers())) {
348                        ret.mergeNext();
349                    } else {
350                        break;
351                    }
352                }
353                return ret;
354            }
355
356            @Override public boolean hasNext() {
357                return next != null;
358            }
359
360            @Override public TryBlock next() {
361                if (!hasNext()) {
362                    throw new NoSuchElementException();
363                }
364                TryBlock ret = next;
365                next = readNextItem();
366                return ret;
367            }
368
369            @Override public void remove() {
370                throw new UnsupportedOperationException();
371            }
372        });
373    }
374}
375