| // Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file |
| // for details. All rights reserved. Use of this source code is governed by a |
| // BSD-style license that can be found in the LICENSE file. |
| package com.android.tools.r8.dex; |
| |
| import static com.android.tools.r8.graph.DexCode.TryHandler.NO_HANDLER; |
| import static com.android.tools.r8.graph.DexDebugEventBuilder.addDefaultEventWithAdvancePcIfNecessary; |
| |
| import com.android.tools.r8.dex.code.DexConstString; |
| import com.android.tools.r8.dex.code.DexConstStringJumbo; |
| import com.android.tools.r8.dex.code.DexFormat21t; |
| import com.android.tools.r8.dex.code.DexFormat22t; |
| import com.android.tools.r8.dex.code.DexFormat31t; |
| import com.android.tools.r8.dex.code.DexGoto; |
| import com.android.tools.r8.dex.code.DexGoto16; |
| import com.android.tools.r8.dex.code.DexGoto32; |
| import com.android.tools.r8.dex.code.DexIfEq; |
| import com.android.tools.r8.dex.code.DexIfEqz; |
| import com.android.tools.r8.dex.code.DexIfGe; |
| import com.android.tools.r8.dex.code.DexIfGez; |
| import com.android.tools.r8.dex.code.DexIfGt; |
| import com.android.tools.r8.dex.code.DexIfGtz; |
| import com.android.tools.r8.dex.code.DexIfLe; |
| import com.android.tools.r8.dex.code.DexIfLez; |
| import com.android.tools.r8.dex.code.DexIfLt; |
| import com.android.tools.r8.dex.code.DexIfLtz; |
| import com.android.tools.r8.dex.code.DexIfNe; |
| import com.android.tools.r8.dex.code.DexIfNez; |
| import com.android.tools.r8.dex.code.DexInstruction; |
| import com.android.tools.r8.dex.code.DexNop; |
| import com.android.tools.r8.dex.code.DexSwitchPayload; |
| import com.android.tools.r8.graph.DexCode; |
| import com.android.tools.r8.graph.DexCode.Try; |
| import com.android.tools.r8.graph.DexCode.TryHandler; |
| import com.android.tools.r8.graph.DexCode.TryHandler.TypeAddrPair; |
| import com.android.tools.r8.graph.DexDebugEvent; |
| import com.android.tools.r8.graph.DexDebugEvent.AdvancePC; |
| import com.android.tools.r8.graph.DexDebugEvent.Default; |
| import com.android.tools.r8.graph.DexDebugInfo; |
| import com.android.tools.r8.graph.DexDebugInfo.EventBasedDebugInfo; |
| import com.android.tools.r8.graph.DexEncodedMethod; |
| import com.android.tools.r8.graph.DexItemFactory; |
| import com.android.tools.r8.graph.DexString; |
| import com.google.common.collect.Lists; |
| import it.unimi.dsi.fastutil.ints.Int2ReferenceMap; |
| import it.unimi.dsi.fastutil.ints.Int2ReferenceOpenHashMap; |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.IdentityHashMap; |
| import java.util.Iterator; |
| import java.util.LinkedList; |
| import java.util.List; |
| import java.util.ListIterator; |
| import java.util.Map; |
| import java.util.Map.Entry; |
| |
| public class JumboStringRewriter { |
| |
| private static class TryTargets { |
| private DexInstruction start; |
| private DexInstruction end; |
| private final boolean endsAfterLastInstruction; |
| |
| TryTargets(DexInstruction start, DexInstruction end, boolean endsAfterLastInstruction) { |
| assert start != null; |
| assert end != null; |
| this.start = start; |
| this.end = end; |
| this.endsAfterLastInstruction = endsAfterLastInstruction; |
| } |
| |
| void replaceTarget(DexInstruction target, DexInstruction newTarget) { |
| if (start == target) { |
| start = newTarget; |
| } |
| if (end == target) { |
| end = newTarget; |
| } |
| } |
| |
| int getStartOffset() { |
| return start.getOffset(); |
| } |
| |
| int getStartToEndDelta() { |
| if (endsAfterLastInstruction) { |
| return end.getOffset() + end.getSize() - start.getOffset(); |
| } |
| return end.getOffset() - start.getOffset(); |
| } |
| } |
| |
| private final DexEncodedMethod method; |
| private final DexString firstJumboString; |
| private final DexItemFactory factory; |
| private final Map<DexInstruction, List<DexInstruction>> instructionTargets = |
| new IdentityHashMap<>(); |
| private EventBasedDebugInfo debugEventBasedInfo = null; |
| private final Int2ReferenceMap<DexInstruction> debugEventTargets = |
| new Int2ReferenceOpenHashMap<>(); |
| private final Map<DexInstruction, DexInstruction> payloadToSwitch = new IdentityHashMap<>(); |
| private final Map<Try, TryTargets> tryTargets = new IdentityHashMap<>(); |
| private final Int2ReferenceMap<DexInstruction> tryRangeStartAndEndTargets = |
| new Int2ReferenceOpenHashMap<>(); |
| private final Map<TryHandler, List<DexInstruction>> handlerTargets = new IdentityHashMap<>(); |
| |
| public JumboStringRewriter( |
| DexEncodedMethod method, DexString firstJumboString, DexItemFactory factory) { |
| this.method = method; |
| this.firstJumboString = firstJumboString; |
| this.factory = factory; |
| } |
| |
| public DexCode rewrite() { |
| // Build maps from everything in the code that uses offsets or direct addresses to reference |
| // instructions to the actual instruction referenced. |
| recordTargets(); |
| // Expand the code by rewriting jumbo strings and branching instructions. |
| List<DexInstruction> newInstructions = expandCode(); |
| // Commit to the new instruction offsets and update instructions, try-catch structures |
| // and debug info with the new offsets. |
| rewriteInstructionOffsets(newInstructions); |
| Try[] newTries = rewriteTryOffsets(); |
| TryHandler[] newHandlers = rewriteHandlerOffsets(); |
| DexDebugInfo newDebugInfo = rewriteDebugInfoOffsets(); |
| // Set the new code on the method. |
| DexCode oldCode = method.getCode().asDexCode(); |
| DexCode newCode = |
| new DexCode( |
| oldCode.registerSize, |
| oldCode.incomingRegisterSize, |
| oldCode.outgoingRegisterSize, |
| newInstructions.toArray(DexInstruction.EMPTY_ARRAY), |
| newTries, |
| newHandlers, |
| newDebugInfo); |
| // As we have rewritten the code, we now know that its highest string index that is not |
| // a jumbo-string is firstJumboString (actually the previous string, but we do not have that). |
| newCode.highestSortingString = firstJumboString; |
| return newCode; |
| } |
| |
| private void rewriteInstructionOffsets(List<DexInstruction> instructions) { |
| for (DexInstruction instruction : instructions) { |
| if (instruction instanceof DexFormat22t) { // IfEq, IfGe, IfGt, IfLe, IfLt, IfNe |
| DexFormat22t condition = (DexFormat22t) instruction; |
| int offset = instructionTargets.get(condition).get(0).getOffset() - instruction.getOffset(); |
| assert Short.MIN_VALUE <= offset && offset <= Short.MAX_VALUE; |
| condition.CCCC = (short) offset; |
| } else if (instruction instanceof DexFormat21t) { // IfEqz, IfGez, IfGtz, IfLez, IfLtz, IfNez |
| DexFormat21t condition = (DexFormat21t) instruction; |
| int offset = instructionTargets.get(condition).get(0).getOffset() - instruction.getOffset(); |
| assert Short.MIN_VALUE <= offset && offset <= Short.MAX_VALUE; |
| condition.BBBB = (short) offset; |
| } else if (instruction instanceof DexGoto) { |
| DexGoto jump = (DexGoto) instruction; |
| int offset = instructionTargets.get(jump).get(0).getOffset() - instruction.getOffset(); |
| assert Byte.MIN_VALUE <= offset && offset <= Byte.MAX_VALUE; |
| jump.AA = (byte) offset; |
| } else if (instruction instanceof DexGoto16) { |
| DexGoto16 jump = (DexGoto16) instruction; |
| int offset = instructionTargets.get(jump).get(0).getOffset() - instruction.getOffset(); |
| assert Short.MIN_VALUE <= offset && offset <= Short.MAX_VALUE; |
| jump.AAAA = (short) offset; |
| } else if (instruction instanceof DexGoto32) { |
| DexGoto32 jump = (DexGoto32) instruction; |
| int offset = instructionTargets.get(jump).get(0).getOffset() - instruction.getOffset(); |
| jump.AAAAAAAA = offset; |
| } else if (instruction.hasPayload()) { // FillArrayData, SparseSwitch, PackedSwitch |
| DexFormat31t payloadUser = (DexFormat31t) instruction; |
| int offset = |
| instructionTargets.get(payloadUser).get(0).getOffset() - instruction.getOffset(); |
| payloadUser.setPayloadOffset(offset); |
| } else if (instruction instanceof DexSwitchPayload) { |
| DexSwitchPayload payload = (DexSwitchPayload) instruction; |
| DexInstruction switchInstruction = payloadToSwitch.get(payload); |
| List<DexInstruction> switchTargets = instructionTargets.get(payload); |
| int[] targets = payload.switchTargetOffsets(); |
| for (int i = 0; i < switchTargets.size(); i++) { |
| DexInstruction target = switchTargets.get(i); |
| targets[i] = target.getOffset() - switchInstruction.getOffset(); |
| } |
| } |
| } |
| } |
| |
| private Try[] rewriteTryOffsets() { |
| DexCode code = method.getCode().asDexCode(); |
| Try[] result = new Try[code.tries.length]; |
| for (int i = 0; i < code.tries.length; i++) { |
| Try theTry = code.tries[i]; |
| TryTargets targets = tryTargets.get(theTry); |
| result[i] = new Try(targets.getStartOffset(), targets.getStartToEndDelta(), -1); |
| result[i].handlerIndex = theTry.handlerIndex; |
| } |
| return result; |
| } |
| |
| private TryHandler[] rewriteHandlerOffsets() { |
| DexCode code = method.getCode().asDexCode(); |
| TryHandler[] result = new TryHandler[code.handlers.length]; |
| for (int i = 0; i < code.handlers.length; i++) { |
| TryHandler handler = code.handlers[i]; |
| List<DexInstruction> targets = handlerTargets.get(handler); |
| Iterator<DexInstruction> it = targets.iterator(); |
| int catchAllAddr = NO_HANDLER; |
| if (handler.catchAllAddr != NO_HANDLER) { |
| catchAllAddr = it.next().getOffset(); |
| } |
| TypeAddrPair[] newPairs = new TypeAddrPair[handler.pairs.length]; |
| for (int j = 0; j < handler.pairs.length; j++) { |
| TypeAddrPair pair = handler.pairs[j]; |
| newPairs[j] = new TypeAddrPair(pair.getType(), it.next().getOffset()); |
| } |
| result[i] = new TryHandler(newPairs, catchAllAddr); |
| } |
| return result; |
| } |
| |
| private DexDebugInfo rewriteDebugInfoOffsets() { |
| DexCode code = method.getCode().asDexCode(); |
| if (!debugEventTargets.isEmpty()) { |
| assert debugEventBasedInfo != null; |
| int lastOriginalOffset = 0; |
| int lastNewOffset = 0; |
| List<DexDebugEvent> events = new ArrayList<>(); |
| for (DexDebugEvent event : debugEventBasedInfo.events) { |
| if (event instanceof AdvancePC) { |
| AdvancePC advance = (AdvancePC) event; |
| lastOriginalOffset += advance.delta; |
| DexInstruction target = debugEventTargets.get(lastOriginalOffset); |
| int pcDelta = target.getOffset() - lastNewOffset; |
| events.add(factory.createAdvancePC(pcDelta)); |
| lastNewOffset = target.getOffset(); |
| } else if (event instanceof Default) { |
| Default defaultEvent = (Default) event; |
| lastOriginalOffset += defaultEvent.getPCDelta(); |
| DexInstruction target = debugEventTargets.get(lastOriginalOffset); |
| int lineDelta = defaultEvent.getLineDelta(); |
| int pcDelta = target.getOffset() - lastNewOffset; |
| addDefaultEventWithAdvancePcIfNecessary(lineDelta, pcDelta, events, factory); |
| lastNewOffset = target.getOffset(); |
| } else { |
| events.add(event); |
| } |
| } |
| return new EventBasedDebugInfo( |
| debugEventBasedInfo.startLine, |
| debugEventBasedInfo.parameters, |
| events.toArray(DexDebugEvent.EMPTY_ARRAY)); |
| } |
| return code.getDebugInfo(); |
| } |
| |
| private List<DexInstruction> expandCode() { |
| LinkedList<DexInstruction> instructions = new LinkedList<>(); |
| Collections.addAll(instructions, method.getCode().asDexCode().instructions); |
| int offsetDelta; |
| do { |
| ListIterator<DexInstruction> it = instructions.listIterator(); |
| offsetDelta = 0; |
| while (it.hasNext()) { |
| DexInstruction instruction = it.next(); |
| int orignalOffset = instruction.getOffset(); |
| instruction.setOffset(orignalOffset + offsetDelta); |
| if (instruction instanceof DexConstString) { |
| DexConstString string = (DexConstString) instruction; |
| if (string.getString().compareTo(firstJumboString) >= 0) { |
| DexConstStringJumbo jumboString = |
| new DexConstStringJumbo(string.AA, string.getString()); |
| jumboString.setOffset(string.getOffset()); |
| offsetDelta++; |
| it.set(jumboString); |
| replaceTarget(instruction, jumboString); |
| } |
| } else if (instruction instanceof DexFormat22t) { // IfEq, IfGe, IfGt, IfLe, IfLt, IfNe |
| DexFormat22t condition = (DexFormat22t) instruction; |
| int offset = |
| instructionTargets.get(condition).get(0).getOffset() - instruction.getOffset(); |
| if (Short.MIN_VALUE > offset || offset > Short.MAX_VALUE) { |
| DexFormat22t newCondition = null; |
| switch (condition.getType().inverted()) { |
| case EQ: |
| newCondition = new DexIfEq(condition.A, condition.B, 0); |
| break; |
| case GE: |
| newCondition = new DexIfGe(condition.A, condition.B, 0); |
| break; |
| case GT: |
| newCondition = new DexIfGt(condition.A, condition.B, 0); |
| break; |
| case LE: |
| newCondition = new DexIfLe(condition.A, condition.B, 0); |
| break; |
| case LT: |
| newCondition = new DexIfLt(condition.A, condition.B, 0); |
| break; |
| case NE: |
| newCondition = new DexIfNe(condition.A, condition.B, 0); |
| break; |
| } |
| offsetDelta = rewriteIfToIfAndGoto(offsetDelta, it, condition, newCondition); |
| } |
| } else if (instruction |
| instanceof DexFormat21t) { // IfEqz, IfGez, IfGtz, IfLez, IfLtz, IfNez |
| DexFormat21t condition = (DexFormat21t) instruction; |
| int offset = |
| instructionTargets.get(condition).get(0).getOffset() - instruction.getOffset(); |
| if (Short.MIN_VALUE > offset || offset > Short.MAX_VALUE) { |
| DexFormat21t newCondition = null; |
| switch (condition.getType().inverted()) { |
| case EQ: |
| newCondition = new DexIfEqz(condition.AA, 0); |
| break; |
| case GE: |
| newCondition = new DexIfGez(condition.AA, 0); |
| break; |
| case GT: |
| newCondition = new DexIfGtz(condition.AA, 0); |
| break; |
| case LE: |
| newCondition = new DexIfLez(condition.AA, 0); |
| break; |
| case LT: |
| newCondition = new DexIfLtz(condition.AA, 0); |
| break; |
| case NE: |
| newCondition = new DexIfNez(condition.AA, 0); |
| break; |
| } |
| offsetDelta = rewriteIfToIfAndGoto(offsetDelta, it, condition, newCondition); |
| } |
| } else if (instruction instanceof DexGoto) { |
| DexGoto jump = (DexGoto) instruction; |
| int offset = instructionTargets.get(jump).get(0).getOffset() - instruction.getOffset(); |
| if (Byte.MIN_VALUE > offset || offset > Byte.MAX_VALUE) { |
| DexInstruction newJump; |
| if (Short.MIN_VALUE > offset || offset > Short.MAX_VALUE) { |
| newJump = new DexGoto32(offset); |
| } else { |
| newJump = new DexGoto16(offset); |
| } |
| newJump.setOffset(jump.getOffset()); |
| it.set(newJump); |
| offsetDelta += (newJump.getSize() - jump.getSize()); |
| replaceTarget(jump, newJump); |
| List<DexInstruction> targets = instructionTargets.remove(jump); |
| instructionTargets.put(newJump, targets); |
| } |
| } else if (instruction instanceof DexGoto16) { |
| DexGoto16 jump = (DexGoto16) instruction; |
| int offset = instructionTargets.get(jump).get(0).getOffset() - instruction.getOffset(); |
| if (Short.MIN_VALUE > offset || offset > Short.MAX_VALUE) { |
| DexInstruction newJump = new DexGoto32(offset); |
| newJump.setOffset(jump.getOffset()); |
| it.set(newJump); |
| offsetDelta += (newJump.getSize() - jump.getSize()); |
| replaceTarget(jump, newJump); |
| List<DexInstruction> targets = instructionTargets.remove(jump); |
| instructionTargets.put(newJump, targets); |
| } |
| } else if (instruction instanceof DexGoto32) { |
| // Instruction big enough for any offset. |
| } else if (instruction.hasPayload()) { // FillArrayData, SparseSwitch, PackedSwitch |
| // Instruction big enough for any offset. |
| } else if (instruction.isPayload()) { |
| // Payload instructions must be 4 byte aligned (instructions are 2 bytes). |
| if (instruction.getOffset() % 2 != 0) { |
| it.previous(); |
| // Check if the previous instruction was a simple nop. If that is the case, remove it |
| // to make the alignment instead of adding another one. Only allow removal if this |
| // instruction is not targeted by anything. See b/78072750. |
| DexInstruction instructionBeforePayload = it.hasPrevious() ? it.previous() : null; |
| if (instructionBeforePayload != null |
| && instructionBeforePayload.isSimpleNop() |
| && debugEventTargets.get(orignalOffset) == null |
| && tryRangeStartAndEndTargets.get(orignalOffset) == null) { |
| it.remove(); |
| offsetDelta--; |
| } else { |
| if (instructionBeforePayload != null) { |
| it.next(); |
| } |
| DexNop nop = new DexNop(); |
| nop.setOffset(instruction.getOffset()); |
| it.add(nop); |
| offsetDelta++; |
| } |
| instruction.setOffset(orignalOffset + offsetDelta); |
| it.next(); |
| } |
| // Instruction big enough for any offset. |
| } |
| } |
| } while (offsetDelta > 0); |
| return instructions; |
| } |
| |
| private int rewriteIfToIfAndGoto( |
| int offsetDelta, |
| ListIterator<DexInstruction> it, |
| DexInstruction condition, |
| DexInstruction newCondition) { |
| int jumpOffset = condition.getOffset() + condition.getSize(); |
| DexGoto32 jump = new DexGoto32(0); |
| jump.setOffset(jumpOffset); |
| newCondition.setOffset(condition.getOffset()); |
| it.set(newCondition); |
| replaceTarget(condition, newCondition); |
| it.add(jump); |
| offsetDelta += jump.getSize(); |
| instructionTargets.put(jump, instructionTargets.remove(condition)); |
| DexInstruction fallthroughInstruction = it.next(); |
| instructionTargets.put(newCondition, Lists.newArrayList(fallthroughInstruction)); |
| it.previous(); |
| return offsetDelta; |
| } |
| |
| private void replaceTarget(DexInstruction target, DexInstruction newTarget) { |
| for (List<DexInstruction> instructions : instructionTargets.values()) { |
| instructions.replaceAll((i) -> i == target ? newTarget : i); |
| } |
| for (Int2ReferenceMap.Entry<DexInstruction> entry : debugEventTargets.int2ReferenceEntrySet()) { |
| if (entry.getValue() == target) { |
| entry.setValue(newTarget); |
| } |
| } |
| for (Entry<Try, TryTargets> entry : tryTargets.entrySet()) { |
| entry.getValue().replaceTarget(target, newTarget); |
| } |
| for (List<DexInstruction> instructions : handlerTargets.values()) { |
| instructions.replaceAll((i) -> i == target ? newTarget : i); |
| } |
| } |
| |
| private void recordInstructionTargets(Int2ReferenceMap<DexInstruction> offsetToInstruction) { |
| DexInstruction[] instructions = method.getCode().asDexCode().instructions; |
| for (DexInstruction instruction : instructions) { |
| if (instruction instanceof DexFormat22t) { // IfEq, IfGe, IfGt, IfLe, IfLt, IfNe |
| DexFormat22t condition = (DexFormat22t) instruction; |
| DexInstruction target = offsetToInstruction.get(condition.getOffset() + condition.CCCC); |
| assert target != null; |
| instructionTargets.put(instruction, Lists.newArrayList(target)); |
| } else if (instruction instanceof DexFormat21t) { // IfEqz, IfGez, IfGtz, IfLez, IfLtz, IfNez |
| DexFormat21t condition = (DexFormat21t) instruction; |
| DexInstruction target = offsetToInstruction.get(condition.getOffset() + condition.BBBB); |
| assert target != null; |
| instructionTargets.put(instruction, Lists.newArrayList(target)); |
| } else if (instruction instanceof DexGoto) { |
| DexGoto jump = (DexGoto) instruction; |
| DexInstruction target = offsetToInstruction.get(jump.getOffset() + jump.AA); |
| assert target != null; |
| instructionTargets.put(instruction, Lists.newArrayList(target)); |
| } else if (instruction instanceof DexGoto16) { |
| DexGoto16 jump = (DexGoto16) instruction; |
| DexInstruction target = offsetToInstruction.get(jump.getOffset() + jump.AAAA); |
| assert target != null; |
| instructionTargets.put(instruction, Lists.newArrayList(target)); |
| } else if (instruction instanceof DexGoto32) { |
| DexGoto32 jump = (DexGoto32) instruction; |
| DexInstruction target = offsetToInstruction.get(jump.getOffset() + jump.AAAAAAAA); |
| assert target != null; |
| instructionTargets.put(instruction, Lists.newArrayList(target)); |
| } else if (instruction.hasPayload()) { // FillArrayData, SparseSwitch, PackedSwitch |
| DexFormat31t offsetInstruction = (DexFormat31t) instruction; |
| DexInstruction target = |
| offsetToInstruction.get( |
| offsetInstruction.getOffset() + offsetInstruction.getPayloadOffset()); |
| assert target != null; |
| instructionTargets.put(instruction, Lists.newArrayList(target)); |
| } else if (instruction instanceof DexSwitchPayload) { |
| DexSwitchPayload payload = (DexSwitchPayload) instruction; |
| int[] targetOffsets = payload.switchTargetOffsets(); |
| int switchOffset = payloadToSwitch.get(instruction).getOffset(); |
| List<DexInstruction> targets = new ArrayList<>(); |
| for (int i = 0; i < targetOffsets.length; i++) { |
| DexInstruction target = offsetToInstruction.get(switchOffset + targetOffsets[i]); |
| assert target != null; |
| targets.add(target); |
| } |
| instructionTargets.put(instruction, targets); |
| } |
| } |
| } |
| |
| private void recordDebugEventTargets(Int2ReferenceMap<DexInstruction> offsetToInstruction) { |
| // TODO(b/213411850): Merging pc based D8 builds will map out of PC for any jumbo processed |
| // method. Instead we should rather retain the PC encoding by bumping the max-pc and recording |
| // the line number translation. We actually need to do so to support merging with native PC |
| // support as in that case we can't reflect the change in the line table, only in the mapping. |
| EventBasedDebugInfo eventBasedInfo = |
| DexDebugInfo.convertToEventBased(method.getCode().asDexCode(), factory); |
| if (eventBasedInfo == null) { |
| return; |
| } |
| debugEventBasedInfo = eventBasedInfo; |
| int address = 0; |
| for (DexDebugEvent event : eventBasedInfo.events) { |
| if (event instanceof AdvancePC) { |
| AdvancePC advance = (AdvancePC) event; |
| address += advance.delta; |
| DexInstruction target = offsetToInstruction.get(address); |
| assert target != null; |
| debugEventTargets.put(address, target); |
| } else if (event instanceof Default) { |
| Default defaultEvent = (Default) event; |
| address += defaultEvent.getPCDelta(); |
| DexInstruction target = offsetToInstruction.get(address); |
| assert target != null; |
| debugEventTargets.put(address, target); |
| } |
| } |
| } |
| |
| private void recordTryAndHandlerTargets( |
| Int2ReferenceMap<DexInstruction> offsetToInstruction, DexInstruction lastInstruction) { |
| DexCode code = method.getCode().asDexCode(); |
| for (Try theTry : code.tries) { |
| DexInstruction start = offsetToInstruction.get(theTry.startAddress); |
| DexInstruction end = null; |
| int endAddress = theTry.startAddress + theTry.instructionCount; |
| TryTargets targets; |
| if (endAddress > lastInstruction.getOffset()) { |
| end = lastInstruction; |
| targets = new TryTargets(start, lastInstruction, true); |
| } else { |
| end = offsetToInstruction.get(endAddress); |
| targets = new TryTargets(start, end, false); |
| } |
| assert theTry.startAddress == targets.getStartOffset(); |
| assert theTry.instructionCount == targets.getStartToEndDelta(); |
| tryTargets.put(theTry, targets); |
| tryRangeStartAndEndTargets.put(start.getOffset(), start); |
| tryRangeStartAndEndTargets.put(end.getOffset(), end); |
| } |
| for (TryHandler handler : code.handlers) { |
| List<DexInstruction> targets = new ArrayList<>(); |
| if (handler.catchAllAddr != NO_HANDLER) { |
| DexInstruction target = offsetToInstruction.get(handler.catchAllAddr); |
| assert target != null; |
| targets.add(target); |
| } |
| for (TypeAddrPair pair : handler.pairs) { |
| DexInstruction target = offsetToInstruction.get(pair.addr); |
| assert target != null; |
| targets.add(target); |
| } |
| handlerTargets.put(handler, targets); |
| } |
| } |
| |
| private void recordTargets() { |
| Int2ReferenceMap<DexInstruction> offsetToInstruction = new Int2ReferenceOpenHashMap<>(); |
| DexInstruction[] instructions = method.getCode().asDexCode().instructions; |
| boolean containsPayloads = false; |
| for (DexInstruction instruction : instructions) { |
| offsetToInstruction.put(instruction.getOffset(), instruction); |
| if (instruction.hasPayload()) { // FillArrayData, SparseSwitch, PackedSwitch |
| containsPayloads = true; |
| } |
| } |
| if (containsPayloads) { |
| for (DexInstruction instruction : instructions) { |
| if (instruction.hasPayload()) { // FillArrayData, SparseSwitch, PackedSwitch |
| DexInstruction payload = |
| offsetToInstruction.get(instruction.getOffset() + instruction.getPayloadOffset()); |
| assert payload != null; |
| payloadToSwitch.put(payload, instruction); |
| } |
| } |
| } |
| recordInstructionTargets(offsetToInstruction); |
| recordDebugEventTargets(offsetToInstruction); |
| DexInstruction lastInstruction = instructions[instructions.length - 1]; |
| recordTryAndHandlerTargets(offsetToInstruction, lastInstruction); |
| } |
| } |