• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
32 package org.jf.dexlib2.writer.util;
33 
34 import com.google.common.collect.Lists;
35 import org.jf.dexlib2.base.BaseTryBlock;
36 import org.jf.dexlib2.iface.ExceptionHandler;
37 import org.jf.dexlib2.iface.TryBlock;
38 import org.jf.util.ExceptionWithContext;
39 
40 import javax.annotation.Nonnull;
41 import javax.annotation.Nullable;
42 import java.util.Iterator;
43 import java.util.List;
44 import java.util.NoSuchElementException;
45 
46 public class TryListBuilder<EH extends ExceptionHandler>
47 {
48     // Linked list sentinels that don't represent an actual try block
49     // Their values are never modified, only their links
50     private final MutableTryBlock<EH> listStart;
51     private final MutableTryBlock<EH> listEnd;
52 
TryListBuilder()53     public TryListBuilder() {
54         listStart = new MutableTryBlock<EH>(0, 0);
55         listEnd = new MutableTryBlock<EH>(0, 0);
56         listStart.next = listEnd;
57         listEnd.prev = listStart;
58     }
59 
massageTryBlocks( List<? extends TryBlock<? extends EH>> tryBlocks)60     public static <EH extends ExceptionHandler> List<TryBlock<EH>> massageTryBlocks(
61             List<? extends TryBlock<? extends EH>> tryBlocks) {
62         TryListBuilder<EH> tlb = new TryListBuilder<EH>();
63 
64         for (TryBlock<? extends EH> tryBlock: tryBlocks) {
65             int startAddress = tryBlock.getStartCodeAddress();
66             int endAddress = startAddress + tryBlock.getCodeUnitCount();
67 
68             for (EH exceptionHandler: tryBlock.getExceptionHandlers()) {
69                 tlb.addHandler(startAddress, endAddress, exceptionHandler);
70             }
71         }
72         return tlb.getTryBlocks();
73     }
74 
75     private static class TryBounds<EH extends ExceptionHandler> {
76         @Nonnull public final MutableTryBlock<EH> start;
77         @Nonnull public final MutableTryBlock<EH> end;
78 
TryBounds(@onnull MutableTryBlock<EH> start, @Nonnull MutableTryBlock<EH> end)79         public TryBounds(@Nonnull MutableTryBlock<EH> start, @Nonnull MutableTryBlock<EH> end) {
80             this.start = start;
81             this.end = end;
82         }
83     }
84 
85     public static class InvalidTryException extends ExceptionWithContext {
InvalidTryException(Throwable cause)86         public InvalidTryException(Throwable cause) {
87             super(cause);
88         }
89 
InvalidTryException(Throwable cause, String message, Object... formatArgs)90         public InvalidTryException(Throwable cause, String message, Object... formatArgs) {
91             super(cause, message, formatArgs);
92         }
93 
InvalidTryException(String message, Object... formatArgs)94         public InvalidTryException(String message, Object... formatArgs) {
95             super(message, formatArgs);
96         }
97     }
98 
99     private static class MutableTryBlock<EH extends ExceptionHandler> extends BaseTryBlock<EH> {
100         public MutableTryBlock<EH> prev = null;
101         public MutableTryBlock<EH> next = null;
102 
103         public int startCodeAddress;
104         public int endCodeAddress;
105         @Nonnull public List<EH> exceptionHandlers = Lists.newArrayList();
106 
MutableTryBlock(int startCodeAddress, int endCodeAddress)107         public MutableTryBlock(int startCodeAddress, int endCodeAddress) {
108             this.startCodeAddress = startCodeAddress;
109             this.endCodeAddress = endCodeAddress;
110         }
111 
MutableTryBlock(int startCodeAddress, int endCodeAddress, @Nonnull List<EH> exceptionHandlers)112         public MutableTryBlock(int startCodeAddress, int endCodeAddress,
113                                @Nonnull List<EH> exceptionHandlers) {
114             this.startCodeAddress = startCodeAddress;
115             this.endCodeAddress = endCodeAddress;
116             this.exceptionHandlers = Lists.newArrayList(exceptionHandlers);
117         }
118 
getStartCodeAddress()119         @Override public int getStartCodeAddress() {
120             return startCodeAddress;
121         }
122 
getCodeUnitCount()123         @Override public int getCodeUnitCount() {
124             return endCodeAddress - startCodeAddress;
125         }
126 
getExceptionHandlers()127         @Nonnull @Override public List<EH> getExceptionHandlers() {
128             return exceptionHandlers;
129         }
130 
131         @Nonnull
split(int splitAddress)132         public MutableTryBlock<EH> split(int splitAddress) {
133             MutableTryBlock<EH> newTryBlock = new MutableTryBlock<EH>(splitAddress, endCodeAddress, exceptionHandlers);
134             endCodeAddress = splitAddress;
135             append(newTryBlock);
136             return newTryBlock;
137         }
138 
delete()139         public void delete() {
140             next.prev = prev;
141             prev.next = next;
142         }
143 
mergeNext()144         public void mergeNext() {
145             //assert next.startCodeAddress == this.endCodeAddress;
146             this.endCodeAddress = next.endCodeAddress;
147             next.delete();
148         }
149 
append(@onnull MutableTryBlock<EH> tryBlock)150         public void append(@Nonnull MutableTryBlock<EH> tryBlock) {
151             next.prev = tryBlock;
152             tryBlock.next = next;
153             tryBlock.prev = this;
154             next = tryBlock;
155         }
156 
prepend(@onnull MutableTryBlock<EH> tryBlock)157         public void prepend(@Nonnull MutableTryBlock<EH> tryBlock) {
158             prev.next = tryBlock;
159             tryBlock.prev = prev;
160             tryBlock.next = this;
161             prev = tryBlock;
162         }
163 
addHandler(@onnull EH handler)164         public void addHandler(@Nonnull EH handler) {
165             for (ExceptionHandler existingHandler: exceptionHandlers) {
166                 String existingType = existingHandler.getExceptionType();
167                 String newType = handler.getExceptionType();
168 
169                 if (existingType == null) {
170                     if (newType == null) {
171                         if (existingHandler.getHandlerCodeAddress() != handler.getHandlerCodeAddress()) {
172                             throw new InvalidTryException(
173                                     "Multiple overlapping catch all handlers with different handlers");
174                         }
175                         return;
176                     }
177                 } else if (existingType.equals(newType)) {
178                     // dalvik doesn't reject cases when there are multiple catches with the same exception
179                     // but different handlers. In practice, the first handler "wins". Since the later
180                     // handler will never be used, we don't add it.
181                     return;
182                 }
183             }
184 
185             exceptionHandlers.add(handler);
186         }
187     }
188 
getBoundingRanges(int startAddress, int endAddress)189     private TryBounds<EH> getBoundingRanges(int startAddress, int endAddress) {
190         MutableTryBlock<EH> startBlock = null;
191 
192         MutableTryBlock<EH> tryBlock = listStart.next;
193         while (tryBlock != listEnd) {
194             int currentStartAddress = tryBlock.startCodeAddress;
195             int currentEndAddress = tryBlock.endCodeAddress;
196 
197             if (startAddress == currentStartAddress) {
198                 //|-----|
199                 //^------
200                 /*Bam. We hit the start of the range right on the head*/
201                 startBlock = tryBlock;
202                 break;
203             } else if (startAddress > currentStartAddress && startAddress < currentEndAddress) {
204                 //|-----|
205                 //  ^----
206                 /*Almost. The start of the range being added is in the middle
207                 of an existing try range. We need to split the existing range
208                 at the start address of the range being added*/
209                 startBlock = tryBlock.split(startAddress);
210                 break;
211             }else if (startAddress < currentStartAddress) {
212                 if (endAddress <= currentStartAddress) {
213                     //      |-----|
214                     //^--^
215                     /*Oops, totally too far! The new range doesn't overlap any existing
216                     ones, so we just add it and return*/
217                     startBlock = new MutableTryBlock<EH>(startAddress, endAddress);
218                     tryBlock.prepend(startBlock);
219                     return new TryBounds<EH>(startBlock, startBlock);
220                 } else {
221                     //   |-----|
222                     //^---------
223                     /*Oops, too far! We've passed the start of the range being added, but
224                      the new range does overlap this one. We need to add a new range just
225                      before this one*/
226                     startBlock = new MutableTryBlock<EH>(startAddress, currentStartAddress);
227                     tryBlock.prepend(startBlock);
228                     break;
229                 }
230             }
231 
232             tryBlock = tryBlock.next;
233         }
234 
235         //|-----|
236         //        ^-----
237         /*Either the list of tries is blank, or all the tries in the list
238         end before the range being added starts. In either case, we just need
239         to add a new range at the end of the list*/
240         if (startBlock == null) {
241             startBlock = new MutableTryBlock<EH>(startAddress, endAddress);
242             listEnd.prepend(startBlock);
243             return new TryBounds<EH>(startBlock, startBlock);
244         }
245 
246         tryBlock = startBlock;
247         while (tryBlock != listEnd) {
248             int currentStartAddress = tryBlock.startCodeAddress;
249             int currentEndAddress = tryBlock.endCodeAddress;
250 
251             if (endAddress == currentEndAddress) {
252                 //|-----|
253                 //------^
254                 /*Bam! We hit the end right on the head... err, tail.*/
255                 return new TryBounds<EH>(startBlock, tryBlock);
256             } else if (endAddress > currentStartAddress && endAddress < currentEndAddress) {
257                 //|-----|
258                 //--^
259                 /*Almost. The range being added ends in the middle of an
260                 existing range. We need to split the existing range
261                 at the end of the range being added.*/
262                 tryBlock.split(endAddress);
263                 return new TryBounds<EH>(startBlock, tryBlock);
264             } else if (endAddress <= currentStartAddress) {
265                 //|-----|       |-----|
266                 //-----------^
267                 /*Oops, too far! The current range starts after the range being added
268                 ends. We need to create a new range that starts at the end of the
269                 previous range, and ends at the end of the range being added*/
270                 MutableTryBlock<EH> endBlock = new MutableTryBlock<EH>(tryBlock.prev.endCodeAddress, endAddress);
271                 tryBlock.prepend(endBlock);
272                 return new TryBounds<EH>(startBlock, endBlock);
273             }
274             tryBlock = tryBlock.next;
275         }
276 
277         //|-----|
278         //--------^
279         /*The last range in the list ended before the end of the range being added.
280         We need to add a new range that starts at the end of the last range in the
281         list, and ends at the end of the range being added.*/
282         MutableTryBlock<EH> endBlock = new MutableTryBlock<EH>(listEnd.prev.endCodeAddress, endAddress);
283         listEnd.prepend(endBlock);
284         return new TryBounds<EH>(startBlock, endBlock);
285     }
286 
addHandler(int startAddress, int endAddress, EH handler)287     public void addHandler(int startAddress, int endAddress, EH handler) {
288         TryBounds<EH> bounds = getBoundingRanges(startAddress, endAddress);
289 
290         MutableTryBlock<EH> startBlock = bounds.start;
291         MutableTryBlock<EH> endBlock = bounds.end;
292 
293         int previousEnd = startAddress;
294         MutableTryBlock<EH> tryBlock = startBlock;
295 
296         /*Now we have the start and end ranges that exactly match the start and end
297         of the range being added. We need to iterate over all the ranges from the start
298         to end range inclusively, and append the handler to the end of each range's handler
299         list. We also need to create a new range for any "holes" in the existing ranges*/
300         do
301         {
302             //is there a hole? If so, add a new range to fill the hole
303             if (tryBlock.startCodeAddress > previousEnd) {
304                 MutableTryBlock<EH> newBlock = new MutableTryBlock<EH>(previousEnd, tryBlock.startCodeAddress);
305                 tryBlock.prepend(newBlock);
306                 tryBlock = newBlock;
307             }
308 
309             tryBlock.addHandler(handler);
310             previousEnd = tryBlock.endCodeAddress;
311             tryBlock = tryBlock.next;
312         } while (tryBlock.prev != endBlock);
313     }
314 
getTryBlocks()315     public List<TryBlock<EH>> getTryBlocks() {
316         return Lists.newArrayList(new Iterator<TryBlock<EH>>() {
317             // The next TryBlock to return. This has already been merged, if needed.
318             @Nullable private MutableTryBlock<EH> next;
319 
320             {
321                 next = listStart;
322                 next = readNextItem();
323             }
324 
325             /**
326              * Read the item that comes after the current value of the next field.
327              * @return The next item, or null if there is no next item
328              */
329             @Nullable protected MutableTryBlock<EH> readNextItem() {
330                 // We can assume that next is not null, due to the way iteration happens
331                 MutableTryBlock<EH> ret = next.next;
332 
333                 if (ret == listEnd) {
334                     return null;
335                 }
336 
337                 while (ret.next != listEnd) {
338                     if (ret.endCodeAddress == ret.next.startCodeAddress &&
339                             ret.getExceptionHandlers().equals(ret.next.getExceptionHandlers())) {
340                         ret.mergeNext();
341                     } else {
342                         break;
343                     }
344                 }
345                 return ret;
346             }
347 
348             @Override public boolean hasNext() {
349                 return next != null;
350             }
351 
352             @Override @Nonnull public TryBlock<EH> next() {
353                 if (!hasNext()) {
354                     throw new NoSuchElementException();
355                 }
356                 TryBlock<EH> ret = next;
357                 next = readNextItem();
358                 // ret can't be null (ret=next and hasNext returned true)
359                 return ret;
360             }
361 
362             @Override public void remove() {
363                 throw new UnsupportedOperationException();
364             }
365         });
366     }
367 }
368