Home | History | Annotate | Download | only in util
      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 
     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 
     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 
     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 {
     86         public InvalidTryException(Throwable cause) {
     87             super(cause);
     88         }
     89 
     90         public InvalidTryException(Throwable cause, String message, Object... formatArgs) {
     91             super(cause, message, formatArgs);
     92         }
     93 
     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 
    107         public MutableTryBlock(int startCodeAddress, int endCodeAddress) {
    108             this.startCodeAddress = startCodeAddress;
    109             this.endCodeAddress = endCodeAddress;
    110         }
    111 
    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 
    119         @Override public int getStartCodeAddress() {
    120             return startCodeAddress;
    121         }
    122 
    123         @Override public int getCodeUnitCount() {
    124             return endCodeAddress - startCodeAddress;
    125         }
    126 
    127         @Nonnull @Override public List<EH> getExceptionHandlers() {
    128             return exceptionHandlers;
    129         }
    130 
    131         @Nonnull
    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 
    139         public void delete() {
    140             next.prev = prev;
    141             prev.next = next;
    142         }
    143 
    144         public void mergeNext() {
    145             //assert next.startCodeAddress == this.endCodeAddress;
    146             this.endCodeAddress = next.endCodeAddress;
    147             next.delete();
    148         }
    149 
    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 
    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 
    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 
    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 
    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 
    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