|  | // 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); | 
|  | } | 
|  | } |