1 // Copyright (c) 2016, the R8 project authors. Please see the AUTHORS file 2 // for details. All rights reserved. Use of this source code is governed by a 3 // BSD-style license that can be found in the LICENSE file. 4 5 package com.android.tools.r8.ir.conversion; 6 7 import com.android.tools.r8.code.FillArrayData; 8 import com.android.tools.r8.code.FillArrayDataPayload; 9 import com.android.tools.r8.code.FilledNewArray; 10 import com.android.tools.r8.code.FilledNewArrayRange; 11 import com.android.tools.r8.code.Instruction; 12 import com.android.tools.r8.code.InvokeDirect; 13 import com.android.tools.r8.code.InvokeDirectRange; 14 import com.android.tools.r8.code.InvokeInterface; 15 import com.android.tools.r8.code.InvokeInterfaceRange; 16 import com.android.tools.r8.code.InvokePolymorphic; 17 import com.android.tools.r8.code.InvokePolymorphicRange; 18 import com.android.tools.r8.code.InvokeStatic; 19 import com.android.tools.r8.code.InvokeStaticRange; 20 import com.android.tools.r8.code.InvokeSuper; 21 import com.android.tools.r8.code.InvokeSuperRange; 22 import com.android.tools.r8.code.InvokeVirtual; 23 import com.android.tools.r8.code.InvokeVirtualRange; 24 import com.android.tools.r8.code.MoveResult; 25 import com.android.tools.r8.code.MoveResultObject; 26 import com.android.tools.r8.code.MoveResultWide; 27 import com.android.tools.r8.code.SwitchPayload; 28 import com.android.tools.r8.code.Throw; 29 import com.android.tools.r8.graph.DebugLocalInfo; 30 import com.android.tools.r8.graph.DexAccessFlags; 31 import com.android.tools.r8.graph.DexCode; 32 import com.android.tools.r8.graph.DexCode.Try; 33 import com.android.tools.r8.graph.DexCode.TryHandler; 34 import com.android.tools.r8.graph.DexCode.TryHandler.TypeAddrPair; 35 import com.android.tools.r8.graph.DexDebugEntry; 36 import com.android.tools.r8.graph.DexDebugInfo; 37 import com.android.tools.r8.graph.DexEncodedMethod; 38 import com.android.tools.r8.graph.DexItemFactory; 39 import com.android.tools.r8.graph.DexProto; 40 import com.android.tools.r8.graph.DexType; 41 import com.android.tools.r8.ir.code.CatchHandlers; 42 import com.android.tools.r8.ir.code.MoveType; 43 import java.util.ArrayList; 44 import java.util.HashMap; 45 import java.util.HashSet; 46 import java.util.List; 47 import java.util.Map; 48 import java.util.Set; 49 50 public class DexSourceCode implements SourceCode { 51 52 private final DexCode code; 53 private final DexAccessFlags accessFlags; 54 private final DexProto proto; 55 56 // Mapping from instruction offset to instruction index in the DexCode instruction array. 57 private final Map<Integer, Integer> offsetToInstructionIndex = new HashMap<>(); 58 59 private final SwitchPayloadResolver switchPayloadResolver = new SwitchPayloadResolver(); 60 private final ArrayFilledDataPayloadResolver arrayFilledDataPayloadResolver = 61 new ArrayFilledDataPayloadResolver(); 62 63 private Try currentTryRange = null; 64 private CatchHandlers<Integer> currentCatchHandlers = null; 65 private Instruction currentDexInstruction = null; 66 67 private final List<MoveType> argumentTypes; 68 69 private List<DexDebugEntry> debugEntries = null; 70 71 public DexSourceCode(DexCode code, DexEncodedMethod method) { 72 this.code = code; 73 this.proto = method.method.proto; 74 this.accessFlags = method.accessFlags; 75 argumentTypes = computeArgumentTypes(); 76 DexDebugInfo info = code.getDebugInfo(); 77 if (info != null) { 78 debugEntries = info.computeEntries(); 79 } 80 } 81 82 @Override 83 public boolean verifyRegister(int register) { 84 return register < code.registerSize; 85 } 86 87 @Override 88 public boolean needsPrelude() { 89 return !accessFlags.isStatic() || !argumentTypes.isEmpty(); 90 } 91 92 @Override 93 public int instructionCount() { 94 return code.instructions.length; 95 } 96 97 @Override 98 public DebugLocalInfo getCurrentLocal(int register) { 99 // TODO(zerny): Support locals in the dex front-end. b/36378142 100 return null; 101 } 102 103 @Override 104 public void setUp() { 105 // Collect all payloads in the instruction stream. 106 for (int index = 0; index < code.instructions.length; index++) { 107 Instruction insn = code.instructions[index]; 108 offsetToInstructionIndex.put(insn.getOffset(), index); 109 if (insn.isPayload()) { 110 if (insn.isSwitchPayload()) { 111 switchPayloadResolver.resolve((SwitchPayload) insn); 112 } else { 113 arrayFilledDataPayloadResolver.resolve((FillArrayDataPayload) insn); 114 } 115 } 116 } 117 } 118 119 @Override 120 public void buildPrelude(IRBuilder builder) { 121 if (code.incomingRegisterSize == 0) { 122 return; 123 } 124 // Fill in the Argument instructions (incomingRegisterSize last registers) in the argument 125 // block. 126 int register = code.registerSize - code.incomingRegisterSize; 127 if (!accessFlags.isStatic()) { 128 builder.addThisArgument(register); 129 ++register; 130 } 131 for (MoveType type : argumentTypes) { 132 builder.addNonThisArgument(register, type); 133 register += type.requiredRegisters(); 134 } 135 } 136 137 @Override 138 public void buildPostlude(IRBuilder builder) { 139 // Intentionally left empty. (Needed in the Java bytecode frontend for synchronization support.) 140 } 141 142 @Override 143 public void closedCurrentBlockWithFallthrough(int fallthroughInstructionIndex) { 144 } 145 146 @Override 147 public void closedCurrentBlock() { 148 } 149 150 @Override 151 public void buildInstruction(IRBuilder builder, int instructionIndex) { 152 updateCurrentCatchHandlers(instructionIndex); 153 emitDebugPosition(instructionIndex, builder); 154 currentDexInstruction = code.instructions[instructionIndex]; 155 currentDexInstruction.buildIR(builder); 156 } 157 158 @Override 159 public CatchHandlers<Integer> getCurrentCatchHandlers() { 160 return currentCatchHandlers; 161 } 162 163 @Override 164 public boolean verifyCurrentInstructionCanThrow() { 165 return currentDexInstruction.canThrow(); 166 } 167 168 @Override 169 public boolean verifyLocalInScope(DebugLocalInfo local) { 170 return true; 171 } 172 173 private void updateCurrentCatchHandlers(int instructionIndex) { 174 Try tryRange = getTryForOffset(instructionOffset(instructionIndex)); 175 if (tryRange == currentTryRange) { 176 return; 177 } 178 currentTryRange = tryRange; 179 if (tryRange == null) { 180 currentCatchHandlers = null; 181 } else { 182 currentCatchHandlers = new CatchHandlers<>( 183 getTryHandlerGuards(tryRange), 184 getTryHandlerOffsets(tryRange)); 185 } 186 } 187 188 private void emitDebugPosition(int instructionIndex, IRBuilder builder) { 189 if (debugEntries == null || debugEntries.isEmpty()) { 190 return; 191 } 192 int offset = instructionOffset(instructionIndex); 193 for (DexDebugEntry entry : debugEntries) { 194 if (entry.address == offset) { 195 builder.updateCurrentDebugPosition(entry.line, entry.sourceFile); 196 return; 197 } 198 if (entry.address > offset) { 199 return; 200 } 201 } 202 } 203 204 @Override 205 public void clear() { 206 switchPayloadResolver.clear(); 207 arrayFilledDataPayloadResolver.clear(); 208 } 209 210 @Override 211 public int instructionIndex(int instructionOffset) { 212 return offsetToInstructionIndex.get(instructionOffset); 213 } 214 215 @Override 216 public int instructionOffset(int instructionIndex) { 217 return code.instructions[instructionIndex].getOffset(); 218 } 219 220 @Override 221 public void resolveAndBuildSwitch(int value, int fallthroughOffset, int payloadOffset, 222 IRBuilder builder) { 223 builder.addSwitch(value, switchPayloadResolver.getKeys(payloadOffset), fallthroughOffset, 224 switchPayloadResolver.absoluteTargets(payloadOffset)); 225 } 226 227 @Override 228 public void resolveAndBuildNewArrayFilledData(int arrayRef, int payloadOffset, 229 IRBuilder builder) { 230 builder.addNewArrayFilledData(arrayRef, 231 arrayFilledDataPayloadResolver.getElementWidth(payloadOffset), 232 arrayFilledDataPayloadResolver.getSize(payloadOffset), 233 arrayFilledDataPayloadResolver.getData(payloadOffset)); 234 } 235 236 private List<MoveType> computeArgumentTypes() { 237 List<MoveType> types = new ArrayList<>(proto.parameters.values.length); 238 String shorty = proto.shorty.toString(); 239 for (int i = 1; i < proto.shorty.size; i++) { 240 MoveType moveType = MoveType.fromTypeDescriptorChar(shorty.charAt(i)); 241 types.add(moveType); 242 } 243 return types; 244 } 245 246 private boolean isInvoke(Instruction dex) { 247 return dex instanceof InvokeDirect 248 || dex instanceof InvokeDirectRange 249 || dex instanceof InvokeVirtual 250 || dex instanceof InvokeVirtualRange 251 || dex instanceof InvokeInterface 252 || dex instanceof InvokeInterfaceRange 253 || dex instanceof InvokeStatic 254 || dex instanceof InvokeStaticRange 255 || dex instanceof InvokeSuper 256 || dex instanceof InvokeSuperRange 257 || dex instanceof InvokePolymorphic 258 || dex instanceof InvokePolymorphicRange 259 || dex instanceof FilledNewArray 260 || dex instanceof FilledNewArrayRange; 261 } 262 263 private boolean isMoveResult(Instruction dex) { 264 return dex instanceof MoveResult 265 || dex instanceof MoveResultObject 266 || dex instanceof MoveResultWide; 267 } 268 269 @Override 270 public int traceInstruction(int index, IRBuilder builder) { 271 Instruction dex = code.instructions[index]; 272 int offset = dex.getOffset(); 273 assert !dex.isPayload(); 274 int[] targets = dex.getTargets(); 275 if (targets != Instruction.NO_TARGETS) { 276 // Check that we don't ever have instructions that can throw and have targets. 277 assert !dex.canThrow(); 278 for (int relativeOffset : targets) { 279 builder.ensureNormalSuccessorBlock(offset, offset + relativeOffset); 280 } 281 return index; 282 } 283 if (dex.canThrow()) { 284 // If the instruction can throw and is in a try block, add edges to its catch successors. 285 Try tryRange = getTryForOffset(offset); 286 if (tryRange != null) { 287 // Ensure the block starts at the start of the try-range (don't enqueue, not a target). 288 int tryRangeStartAddress = tryRange.startAddress; 289 if (isMoveResult(code.instructions[offsetToInstructionIndex.get(tryRangeStartAddress)])) { 290 // If a handler range starts at a move result instruction it is safe to start it at 291 // the following instruction since the move-result cannot throw an exception. Doing so 292 // makes sure that we do not split an invoke and its move result instruction across 293 // two blocks. 294 ++tryRangeStartAddress; 295 } 296 builder.ensureBlockWithoutEnqueuing(tryRangeStartAddress); 297 // Edge to exceptional successors. 298 for (Integer handlerOffset : getUniqueTryHandlerOffsets(tryRange)) { 299 builder.ensureExceptionalSuccessorBlock(offset, handlerOffset); 300 } 301 // If the following instruction is a move-result include it in this (the invokes) block. 302 if (index + 1 < code.instructions.length && isMoveResult(code.instructions[index + 1])) { 303 assert isInvoke(dex); 304 ++index; 305 dex = code.instructions[index]; 306 } 307 // Edge to normal successor if any (fallthrough). 308 if (!(dex instanceof Throw)) { 309 builder.ensureNormalSuccessorBlock(offset, dex.getOffset() + dex.getSize()); 310 } 311 return index; 312 } 313 // Close the block if the instruction is a throw, otherwise the block remains open. 314 return dex instanceof Throw ? index : -1; 315 } 316 if (dex.isSwitch()) { 317 // TODO(zerny): Remove this from block computation. 318 switchPayloadResolver.addPayloadUser(dex); 319 320 for (int target : switchPayloadResolver.absoluteTargets(dex)) { 321 builder.ensureNormalSuccessorBlock(offset, target); 322 } 323 builder.ensureNormalSuccessorBlock(offset, offset + dex.getSize()); 324 return index; 325 } 326 // TODO(zerny): Remove this from block computation. 327 if (dex.hasPayload()) { 328 arrayFilledDataPayloadResolver.addPayloadUser((FillArrayData) dex); 329 } 330 // This instruction does not close the block. 331 return -1; 332 } 333 334 private boolean inTryRange(Try tryItem, int offset) { 335 return tryItem.startAddress <= offset 336 && offset < tryItem.startAddress + tryItem.instructionCount; 337 } 338 339 private Try getTryForOffset(int offset) { 340 for (Try tryRange : code.tries) { 341 if (inTryRange(tryRange, offset)) { 342 return tryRange; 343 } 344 } 345 return null; 346 } 347 348 private Set<Integer> getUniqueTryHandlerOffsets(Try tryRange) { 349 return new HashSet<>(getTryHandlerOffsets(tryRange)); 350 } 351 352 private List<Integer> getTryHandlerOffsets(Try tryRange) { 353 List<Integer> handlerOffsets = new ArrayList<>(); 354 TryHandler handler = code.handlers[tryRange.handlerIndex]; 355 for (TypeAddrPair pair : handler.pairs) { 356 handlerOffsets.add(pair.addr); 357 } 358 if (handler.catchAllAddr != TryHandler.NO_HANDLER) { 359 handlerOffsets.add(handler.catchAllAddr); 360 } 361 return handlerOffsets; 362 } 363 364 private List<DexType> getTryHandlerGuards(Try tryRange) { 365 List<DexType> handlerGuards = new ArrayList<>(); 366 TryHandler handler = code.handlers[tryRange.handlerIndex]; 367 for (TypeAddrPair pair : handler.pairs) { 368 handlerGuards.add(pair.type); 369 } 370 if (handler.catchAllAddr != TryHandler.NO_HANDLER) { 371 handlerGuards.add(DexItemFactory.catchAllType); 372 } 373 return handlerGuards; 374 } 375 } 376