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