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.builder; 33 34 import com.google.common.collect.ImmutableList; 35 import org.jf.dexlib2.builder.debug.*; 36 import org.jf.dexlib2.iface.instruction.Instruction; 37 import org.jf.dexlib2.iface.reference.StringReference; 38 import org.jf.dexlib2.iface.reference.TypeReference; 39 40 import javax.annotation.Nonnull; 41 import javax.annotation.Nullable; 42 import java.util.*; 43 44 public class MethodLocation { 45 @Nullable BuilderInstruction instruction; 46 int codeAddress; 47 int index; 48 49 // We end up creating and keeping around a *lot* of MethodLocation objects 50 // when building a new dex file, so it's worth the trouble of lazily creating 51 // the labels and debugItems lists only when they are needed 52 53 @Nullable 54 private List<Label> labels = null; 55 @Nullable 56 private List<BuilderDebugItem> debugItems = null; 57 58 MethodLocation(@Nullable BuilderInstruction instruction, int codeAddress, int index) { 59 this.instruction = instruction; 60 this.codeAddress = codeAddress; 61 this.index = index; 62 } 63 64 @Nullable 65 public Instruction getInstruction() { 66 return instruction; 67 } 68 69 public int getCodeAddress() { 70 return codeAddress; 71 } 72 73 public int getIndex() { 74 return index; 75 } 76 77 @Nonnull 78 private List<Label> getLabels(boolean mutable) { 79 if (labels == null) { 80 if (mutable) { 81 labels = new ArrayList<Label>(1); 82 return labels; 83 } 84 return ImmutableList.of(); 85 } 86 return labels; 87 } 88 89 @Nonnull 90 private List<BuilderDebugItem> getDebugItems(boolean mutable) { 91 if (debugItems == null) { 92 if (mutable) { 93 debugItems = new ArrayList<BuilderDebugItem>(1); 94 return debugItems; 95 } 96 return ImmutableList.of(); 97 } 98 return debugItems; 99 } 100 101 void mergeInto(@Nonnull MethodLocation other) { 102 if (this.labels != null || other.labels != null) { 103 List<Label> otherLabels = other.getLabels(true); 104 for (Label label: this.getLabels(false)) { 105 label.location = other; 106 otherLabels.add(label); 107 } 108 this.labels = null; 109 } 110 111 if (this.debugItems != null || other.labels != null) { 112 // We need to keep the debug items in the same order. We add the other debug items to this list, then reassign 113 // the list. 114 List<BuilderDebugItem> debugItems = getDebugItems(true); 115 for (BuilderDebugItem debugItem: debugItems) { 116 debugItem.location = other; 117 } 118 debugItems.addAll(other.getDebugItems(false)); 119 other.debugItems = debugItems; 120 this.debugItems = null; 121 } 122 } 123 124 @Nonnull 125 public Set<Label> getLabels() { 126 return new AbstractSet<Label>() { 127 @Nonnull 128 @Override public Iterator<Label> iterator() { 129 final Iterator<Label> it = getLabels(false).iterator(); 130 131 return new Iterator<Label>() { 132 private @Nullable Label currentLabel = null; 133 134 @Override public boolean hasNext() { 135 return it.hasNext(); 136 } 137 138 @Override public Label next() { 139 currentLabel = it.next(); 140 return currentLabel; 141 } 142 143 @Override public void remove() { 144 if (currentLabel != null) { 145 currentLabel.location = null; 146 } 147 it.remove(); 148 } 149 }; 150 } 151 152 @Override public int size() { 153 return getLabels(false).size(); 154 } 155 156 @Override public boolean add(@Nonnull Label label) { 157 if (label.isPlaced()) { 158 throw new IllegalArgumentException("Cannot add a label that is already placed. You must remove " + 159 "it from its current location first."); 160 } 161 label.location = MethodLocation.this; 162 getLabels(true).add(label); 163 return true; 164 } 165 }; 166 } 167 168 @Nonnull 169 public Label addNewLabel() { 170 Label label = new Label(this); 171 getLabels(true).add(label); 172 return label; 173 } 174 175 @Nonnull 176 public Set<BuilderDebugItem> getDebugItems() { 177 return new AbstractSet<BuilderDebugItem>() { 178 @Nonnull 179 @Override public Iterator<BuilderDebugItem> iterator() { 180 final Iterator<BuilderDebugItem> it = getDebugItems(false).iterator(); 181 182 return new Iterator<BuilderDebugItem>() { 183 private @Nullable BuilderDebugItem currentDebugItem = null; 184 185 @Override public boolean hasNext() { 186 return it.hasNext(); 187 } 188 189 @Override public BuilderDebugItem next() { 190 currentDebugItem = it.next(); 191 return currentDebugItem; 192 } 193 194 @Override public void remove() { 195 if (currentDebugItem != null) { 196 currentDebugItem.location = null; 197 } 198 it.remove(); 199 } 200 }; 201 } 202 203 @Override public int size() { 204 return getDebugItems(false).size(); 205 } 206 207 @Override public boolean add(@Nonnull BuilderDebugItem debugItem) { 208 if (debugItem.location != null) { 209 throw new IllegalArgumentException("Cannot add a debug item that has already been added to a " + 210 "method. You must remove it from its current location first."); 211 } 212 debugItem.location = MethodLocation.this; 213 getDebugItems(true).add(debugItem); 214 return true; 215 } 216 }; 217 } 218 219 public void addLineNumber(int lineNumber) { 220 getDebugItems().add(new BuilderLineNumber(lineNumber)); 221 } 222 223 public void addStartLocal(int registerNumber, @Nullable StringReference name, @Nullable TypeReference type, 224 @Nullable StringReference signature) { 225 getDebugItems().add(new BuilderStartLocal(registerNumber, name, type, signature)); 226 } 227 228 public void addEndLocal(int registerNumber) { 229 getDebugItems().add(new BuilderEndLocal(registerNumber)); 230 } 231 232 public void addRestartLocal(int registerNumber) { 233 getDebugItems().add(new BuilderRestartLocal(registerNumber)); 234 } 235 236 public void addPrologue() { 237 getDebugItems().add(new BuilderPrologueEnd()); 238 } 239 240 public void addEpilogue() { 241 getDebugItems().add(new BuilderEpilogueBegin()); 242 } 243 244 public void addSetSourceFile(@Nullable StringReference sourceFile) { 245 getDebugItems().add(new BuilderSetSourceFile(sourceFile)); 246 } 247 } 248