blob: 9d6d32e80563180bbeecda4ac3262f77a4d7c558 [file] [log] [blame]
// Copyright (c) 2016, 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.graph;
import static com.android.tools.r8.graph.DexDebugEventBuilder.addDefaultEventWithAdvancePcIfNecessary;
import static com.android.tools.r8.utils.DexDebugUtils.computePreamblePosition;
import static com.android.tools.r8.utils.DexDebugUtils.verifySetPositionFramesFollowedByDefaultEvent;
import com.android.tools.r8.dex.CodeToKeep;
import com.android.tools.r8.dex.IndexedItemCollection;
import com.android.tools.r8.dex.JumboStringRewriter;
import com.android.tools.r8.dex.MixedSectionCollection;
import com.android.tools.r8.dex.code.CfOrDexInstruction;
import com.android.tools.r8.dex.code.DexInstruction;
import com.android.tools.r8.dex.code.DexMonitorEnter;
import com.android.tools.r8.dex.code.DexReturnVoid;
import com.android.tools.r8.dex.code.DexSwitchPayload;
import com.android.tools.r8.graph.DexCode.TryHandler.TypeAddrPair;
import com.android.tools.r8.graph.DexDebugEvent.AdvanceLine;
import com.android.tools.r8.graph.DexDebugEvent.Default;
import com.android.tools.r8.graph.DexDebugEvent.SetPositionFrame;
import com.android.tools.r8.graph.DexDebugEvent.StartLocal;
import com.android.tools.r8.graph.DexDebugInfo.EventBasedDebugInfo;
import com.android.tools.r8.graph.DexWritableCode.DexWritableCacheKey;
import com.android.tools.r8.graph.bytecodemetadata.BytecodeInstructionMetadata;
import com.android.tools.r8.graph.bytecodemetadata.BytecodeMetadata;
import com.android.tools.r8.graph.lens.GraphLens;
import com.android.tools.r8.graph.proto.RewrittenPrototypeDescription;
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.code.NumberGenerator;
import com.android.tools.r8.ir.code.Position;
import com.android.tools.r8.ir.code.Position.PositionBuilder;
import com.android.tools.r8.ir.code.Position.SourcePosition;
import com.android.tools.r8.ir.code.Position.SyntheticPosition;
import com.android.tools.r8.ir.conversion.DexSourceCode;
import com.android.tools.r8.ir.conversion.IRBuilder;
import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
import com.android.tools.r8.ir.conversion.MethodConversionOptions;
import com.android.tools.r8.ir.conversion.MethodConversionOptions.MutableMethodConversionOptions;
import com.android.tools.r8.lightir.ByteUtils;
import com.android.tools.r8.utils.ArrayUtils;
import com.android.tools.r8.utils.DexDebugUtils.PositionInfo;
import com.android.tools.r8.utils.RetracerForCodePrinting;
import com.android.tools.r8.utils.StringUtils;
import com.android.tools.r8.utils.structural.Equatable;
import com.android.tools.r8.utils.structural.HashCodeVisitor;
import com.android.tools.r8.utils.structural.HashingVisitor;
import com.android.tools.r8.utils.structural.StructuralItem;
import com.android.tools.r8.utils.structural.StructuralMapping;
import com.android.tools.r8.utils.structural.StructuralSpecification;
import it.unimi.dsi.fastutil.ints.Int2IntMap;
import java.nio.ShortBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Consumer;
// DexCode corresponds to code item in dalvik/dex-format.html
public class DexCode extends Code
implements DexWritableCode, StructuralItem<DexCode>, DexWritableCacheKey {
public static final String FAKE_THIS_PREFIX = "_";
public static final String FAKE_THIS_SUFFIX = "this";
public final int registerSize;
public final int incomingRegisterSize;
public final int outgoingRegisterSize;
public final Try[] tries;
public final TryHandler[] handlers;
public final DexInstruction[] instructions;
private DexString highestSortingString;
private DexDebugInfo debugInfo;
private DexDebugInfoForWriting debugInfoForWriting;
private final BytecodeMetadata<DexInstruction> metadata;
private static void specify(StructuralSpecification<DexCode, ?> spec) {
spec.withInt(c -> c.registerSize)
.withInt(c -> c.incomingRegisterSize)
.withInt(c -> c.outgoingRegisterSize)
.withItemArray(c -> c.tries)
.withItemArray(c -> c.handlers)
.withNullableItem(c -> c.debugInfo)
.withItemArray(c -> c.instructions);
}
private DexCode(DexCode code) {
this(
code.registerSize,
code.incomingRegisterSize,
code.outgoingRegisterSize,
code.instructions,
code.tries,
code.handlers,
code.debugInfo,
code.metadata);
this.debugInfoForWriting = code.debugInfoForWriting;
this.highestSortingString = code.highestSortingString;
}
public DexCode(int registerSize, int insSize, int outsSize, DexInstruction[] instructions) {
this(
registerSize,
insSize,
outsSize,
instructions,
Try.EMPTY_ARRAY,
TryHandler.EMPTY_ARRAY,
null);
}
public DexCode(
int registerSize,
int insSize,
int outsSize,
DexInstruction[] instructions,
Try[] tries,
TryHandler[] handlers,
DexDebugInfo debugInfo) {
this(
registerSize,
insSize,
outsSize,
instructions,
tries,
handlers,
debugInfo,
BytecodeMetadata.empty());
}
public DexCode(
int registerSize,
int insSize,
int outsSize,
DexInstruction[] instructions,
Try[] tries,
TryHandler[] handlers,
DexDebugInfo debugInfo,
BytecodeMetadata<DexInstruction> metadata) {
this.incomingRegisterSize = insSize;
this.registerSize = registerSize;
this.outgoingRegisterSize = outsSize;
this.instructions = instructions;
this.tries = tries;
this.handlers = handlers;
this.debugInfo = debugInfo;
this.metadata = metadata;
assert tries != null;
assert handlers != null;
assert instructions != null;
assert verifySetPositionFramesFollowedByDefaultEvent(debugInfo);
int unused = hashCode(); // Cache the hash code eagerly.
}
public DexCode withCodeLens(GraphLens codeLens) {
return new DexCode(this) {
@Override
public boolean hasExplicitCodeLens() {
return true;
}
@Override
public GraphLens getCodeLens(AppView<?> appView) {
return codeLens;
}
};
}
public DexCode withNewInstructions(DexInstruction[] newInstructions) {
return new DexCode(
this.registerSize,
this.incomingRegisterSize,
this.outgoingRegisterSize,
newInstructions,
this.tries,
this.handlers,
this.getDebugInfo());
}
@Override
public DexCode self() {
return this;
}
@Override
public BytecodeMetadata<DexInstruction> getMetadata() {
return metadata;
}
@Override
public BytecodeInstructionMetadata getMetadata(CfOrDexInstruction instruction) {
return getMetadata(instruction.asDexInstruction());
}
public BytecodeInstructionMetadata getMetadata(DexInstruction instruction) {
return metadata.getMetadata(instruction);
}
@Override
public DexWritableCodeKind getDexWritableCodeKind() {
return DexWritableCodeKind.DEFAULT;
}
@Override
public StructuralMapping<DexCode> getStructuralMapping() {
return DexCode::specify;
}
public void setHighestSortingStringForJumboProcessedCode(DexString nonJumboString) {
// The call of this method marks this code object as properly jumbo-string processed.
// In principle, it should be possible to mark as such and assert that we do not reattempt
// processing in rewriteCodeWithJumboStrings.
highestSortingString = nonJumboString;
}
@Override
public DexWritableCode rewriteCodeWithJumboStrings(
ProgramMethod method, ObjectToOffsetMapping mapping, AppView<?> appView, boolean force) {
DexString firstJumboString = null;
if (force) {
firstJumboString = mapping.getFirstString();
} else {
assert highestSortingString != null
|| Arrays.stream(instructions).noneMatch(DexInstruction::isConstString);
assert Arrays.stream(instructions).noneMatch(DexInstruction::isDexItemBasedConstString);
if (highestSortingString != null
&& highestSortingString.isGreaterThanOrEqualTo(mapping.getFirstJumboString())) {
firstJumboString = mapping.getFirstJumboString();
}
}
return firstJumboString != null
? new JumboStringRewriter(
method.getDefinition(),
firstJumboString,
() -> appView.options().shouldMaterializeLineInfoForNativePcEncoding(method),
appView.dexItemFactory())
.rewrite()
: this;
}
@Override
public void setCallSiteContexts(ProgramMethod method) {
for (DexInstruction instruction : instructions) {
DexCallSite callSite = instruction.getCallSite();
if (callSite != null) {
callSite.setContext(method.getReference(), instruction.getOffset());
}
}
}
public DexCode withoutThisParameter(DexItemFactory factory) {
// Note that we assume the original code has a register associated with 'this'
// argument of the (former) instance method. We also assume (but do not check)
// that 'this' register is never used, so when we decrease incoming register size
// by 1, it becomes just a regular register which is never used, and thus will be
// gone when we build an IR from this code. Rebuilding IR for methods 'staticized'
// this way is highly recommended to improve register allocation.
return new DexCode(
registerSize,
incomingRegisterSize - 1,
outgoingRegisterSize,
instructions,
tries,
handlers,
debugInfoWithoutFirstParameter(factory));
}
@Override
public boolean isDexCode() {
return true;
}
@Override
public boolean isDexWritableCode() {
return true;
}
@Override
public DexWritableCode asDexWritableCode() {
return this;
}
@Override
public int estimatedDexCodeSizeUpperBoundInBytes() {
return codeSizeInBytes();
}
@Override
public DexCode asDexCode() {
return this;
}
public DexDebugInfo getDebugInfo() {
return debugInfo;
}
public void setDebugInfo(DexDebugInfo debugInfo) {
this.debugInfo = debugInfo;
if (debugInfoForWriting != null) {
debugInfoForWriting = null;
}
flushCachedValues();
}
public DexDebugInfo debugInfoWithFakeThisParameter(DexItemFactory factory) {
EventBasedDebugInfo eventBasedInfo = DexDebugInfo.convertToEventBased(this, factory);
if (eventBasedInfo == null) {
return eventBasedInfo;
}
// User code may already have variables named '_*this'. Use one more than the largest number of
// underscores present as a prefix to 'this'.
int largestPrefix = 0;
for (DexString parameter : eventBasedInfo.parameters) {
largestPrefix = Integer.max(largestPrefix, getLargestPrefix(factory, parameter));
}
for (DexDebugEvent event : eventBasedInfo.events) {
if (event instanceof DexDebugEvent.StartLocal) {
DexString name = ((StartLocal) event).name;
largestPrefix = Integer.max(largestPrefix, getLargestPrefix(factory, name));
}
}
String fakeThisName = FAKE_THIS_PREFIX.repeat(largestPrefix + 1) + FAKE_THIS_SUFFIX;
DexString[] parameters = eventBasedInfo.parameters;
DexString[] newParameters = new DexString[parameters.length + 1];
newParameters[0] = factory.createString(fakeThisName);
System.arraycopy(parameters, 0, newParameters, 1, parameters.length);
return new EventBasedDebugInfo(eventBasedInfo.startLine, newParameters, eventBasedInfo.events);
}
public DexDebugInfo debugInfoWithExtraParameters(DexItemFactory factory, int extraParameters) {
EventBasedDebugInfo eventBasedInfo = DexDebugInfo.convertToEventBased(this, factory);
if (eventBasedInfo == null) {
return eventBasedInfo;
}
DexString[] parameters = eventBasedInfo.parameters;
DexString[] newParameters = new DexString[parameters.length + extraParameters];
System.arraycopy(parameters, 0, newParameters, 0, parameters.length);
return new EventBasedDebugInfo(eventBasedInfo.startLine, newParameters, eventBasedInfo.events);
}
@Override
public Code getCodeAsInlining(
DexMethod caller,
boolean isCallerD8R8Synthesized,
DexMethod callee,
boolean isCalleeD8R8Synthesized,
DexItemFactory factory) {
return new DexCode(
registerSize,
incomingRegisterSize,
outgoingRegisterSize,
instructions,
tries,
handlers,
debugInfoAsInlining(caller, callee, isCalleeD8R8Synthesized, factory));
}
private DexDebugInfo debugInfoAsInlining(
DexMethod caller, DexMethod callee, boolean isCalleeD8R8Synthesized, DexItemFactory factory) {
Position callerPosition =
SyntheticPosition.builder().setLine(0).setMethod(caller).setIsD8R8Synthesized(true).build();
EventBasedDebugInfo eventBasedInfo = DexDebugInfo.convertToEventBased(this, factory);
if (eventBasedInfo == null) {
// If the method has no debug info we generate a preamble position to denote the inlining.
// This is consistent with the building IR for inlining which will always ensure the method
// has a position.
Position preamblePosition =
isCalleeD8R8Synthesized
? callerPosition
: SyntheticPosition.builder()
.setMethod(callee)
.setCallerPosition(callerPosition)
.setLine(0)
.build();
return new EventBasedDebugInfo(
0,
new DexString[callee.getArity()],
new DexDebugEvent[] {
factory.createPositionFrame(preamblePosition), factory.zeroChangeDefaultEvent
});
}
// At this point we know we had existing debug information:
// 1) There is an already existing SET_POSITION_FRAME before a default event and the default
// event sets a position for PC 0
// => Nothing to do except append caller.
// 2) There is no SET_POSITION_FRAME before a default event and a default event covers PC 0.
// => Insert a SET_POSITION_FRAME
// 3) There is a SET_POSITION_FRAME and no default event setting a position for PC 0.
// => Insert a default event and potentially advance line.
// 4) There is no SET_POSITION_FRAME and no default event setting a position for PC 0..
// => Insert a SET_POSITION_FRAME and a default event and potentially advance line.
PositionInfo positionInfo =
computePreamblePosition(callee, isCalleeD8R8Synthesized, eventBasedInfo);
DexDebugEvent[] oldEvents = eventBasedInfo.events;
boolean adjustStartPosition =
!positionInfo.hasLinePositionAtPcZero() && debugInfo.getStartLine() > 0;
List<DexDebugEvent> newEvents =
new ArrayList<>(
oldEvents.length
+ (positionInfo.hasFramePosition() ? 0 : 1)
+ (positionInfo.hasLinePositionAtPcZero() ? 0 : 1)
+ (adjustStartPosition ? 1 : 0)); // Potentially an advance line.
if (!positionInfo.hasFramePosition()) {
PositionBuilder<?, ?> calleePositionBuilder =
isCalleeD8R8Synthesized ? SyntheticPosition.builder() : SourcePosition.builder();
newEvents.add(
factory.createPositionFrame(
newInlineePosition(
callerPosition,
calleePositionBuilder
.setLine(
positionInfo.hasLinePositionAtPcZero()
? positionInfo.getLinePositionAtPcZero()
: 0)
.setMethod(callee)
.setIsD8R8Synthesized(isCalleeD8R8Synthesized)
.build(),
isCalleeD8R8Synthesized)));
}
if (!positionInfo.hasLinePositionAtPcZero()) {
newEvents.add(factory.zeroChangeDefaultEvent);
}
for (DexDebugEvent event : oldEvents) {
if (event.isAdvanceLine() && adjustStartPosition) {
AdvanceLine advanceLine = event.asAdvanceLine();
newEvents.add(factory.createAdvanceLine(debugInfo.getStartLine() + advanceLine.delta));
adjustStartPosition = false;
} else if (event.isDefaultEvent() && adjustStartPosition) {
Default oldDefaultEvent = event.asDefaultEvent();
addDefaultEventWithAdvancePcIfNecessary(
oldDefaultEvent.getLineDelta() + debugInfo.getStartLine(),
oldDefaultEvent.getPCDelta(),
newEvents,
factory);
adjustStartPosition = false;
} else if (event.isPositionFrame()) {
SetPositionFrame oldFrame = event.asSetPositionFrame();
assert oldFrame.getPosition() != null;
newEvents.add(
new SetPositionFrame(
newInlineePosition(
callerPosition, oldFrame.getPosition(), isCalleeD8R8Synthesized)));
} else {
newEvents.add(event);
}
}
if (adjustStartPosition) {
// This only happens if we have no default event and the debug start line is > 0.
newEvents.add(factory.createAdvanceLine(debugInfo.getStartLine()));
}
return new EventBasedDebugInfo(
positionInfo.hasLinePositionAtPcZero() ? eventBasedInfo.getStartLine() : 0,
eventBasedInfo.parameters,
newEvents.toArray(DexDebugEvent.EMPTY_ARRAY));
}
public static int getLargestPrefix(DexItemFactory factory, DexString name) {
if (name != null && name.endsWith(factory.thisName)) {
String string = name.toString();
for (int i = 0; i < string.length(); i++) {
if (string.charAt(i) != '_') {
return i;
}
}
}
return 0;
}
public DexDebugInfo debugInfoWithoutFirstParameter(DexItemFactory factory) {
EventBasedDebugInfo eventBasedInfo = DexDebugInfo.convertToEventBased(this, factory);
if (eventBasedInfo == null) {
return eventBasedInfo;
}
DexString[] parameters = eventBasedInfo.parameters;
if(parameters.length == 0) {
return eventBasedInfo;
}
DexString[] newParameters = new DexString[parameters.length - 1];
System.arraycopy(parameters, 1, newParameters, 0, parameters.length - 1);
return new EventBasedDebugInfo(eventBasedInfo.startLine, newParameters, eventBasedInfo.events);
}
@Override
public void acceptHashing(HashingVisitor visitor) {
visitor.visit(this, getStructuralMapping());
}
@Override
public int computeHashCode() {
return incomingRegisterSize * 2
+ registerSize * 3
+ outgoingRegisterSize * 5
+ Arrays.hashCode(instructions) * 7
+ ((debugInfo == null) ? 0 : debugInfo.hashCode()) * 11
+ Arrays.hashCode(tries) * 13
+ Arrays.hashCode(handlers) * 17;
}
@Override
public boolean computeEquals(Object other) {
return Equatable.equalsImpl(this, other);
}
@Override
public boolean isEmptyVoidMethod() {
return instructions.length == 1 && instructions[0] instanceof DexReturnVoid;
}
@Override
public boolean hasMonitorInstructions() {
for (DexInstruction instruction : instructions) {
if (instruction instanceof DexMonitorEnter) {
return true;
}
}
return false;
}
public boolean hasThrowingInstructions() {
for (DexInstruction instruction : instructions) {
if (instruction.canThrow()) {
return true;
}
}
return false;
}
@Override
public Code asCode() {
return this;
}
@Override
public IRCode buildIR(
ProgramMethod method,
AppView<?> appView,
MutableMethodConversionOptions conversionOptions) {
DexSourceCode source =
new DexSourceCode(
this,
method,
null,
appView.dexItemFactory());
return IRBuilder.create(method, appView, source).build(method, conversionOptions);
}
@Override
@SuppressWarnings("UnusedVariable")
public IRCode buildInliningIR(
ProgramMethod context,
ProgramMethod method,
AppView<?> appView,
GraphLens codeLens,
NumberGenerator valueNumberGenerator,
Position callerPosition,
RewrittenPrototypeDescription protoChanges) {
DexSourceCode source =
new DexSourceCode(
this,
method,
callerPosition,
appView.dexItemFactory());
return IRBuilder.createForInlining(
method, appView, codeLens, source, valueNumberGenerator, protoChanges)
.build(context, MethodConversionOptions.nonConverting());
}
@Override
public void registerCodeReferences(ProgramMethod method, UseRegistry registry) {
internalRegisterCodeReferences(registry);
}
@Override
public void registerCodeReferencesForDesugaring(ClasspathMethod method, UseRegistry registry) {
internalRegisterCodeReferences(registry);
}
private void internalRegisterCodeReferences(UseRegistry<?> registry) {
assert registry.getTraversalContinuation().shouldContinue();
for (DexInstruction insn : instructions) {
insn.registerUse(registry);
if (registry.getTraversalContinuation().shouldBreak()) {
return;
}
}
for (TryHandler handler : handlers) {
for (TypeAddrPair pair : handler.pairs) {
registry.registerExceptionGuard(pair.type);
if (registry.getTraversalContinuation().shouldBreak()) {
return;
}
}
}
}
@Override
public String toString() {
return toString(null, RetracerForCodePrinting.empty());
}
@Override
public String toString(DexEncodedMethod method, RetracerForCodePrinting retracer) {
StringBuilder builder = new StringBuilder();
if (method != null) {
builder.append(retracer.toSourceString(method.getReference())).append("\n");
}
builder.append("registers: ").append(registerSize);
builder.append(", inputs: ").append(incomingRegisterSize);
builder.append(", outputs: ").append(outgoingRegisterSize).append("\n");
builder.append("------------------------------------------------------------\n");
builder.append("inst# offset instruction arguments\n");
builder.append("------------------------------------------------------------\n");
// Collect payload users.
Map<Integer, DexInstruction> payloadUsers = new HashMap<>();
for (DexInstruction dex : instructions) {
if (dex.hasPayload()) {
payloadUsers.put(dex.getOffset() + dex.getPayloadOffset(), dex);
}
}
DexDebugEntry debugInfo = null;
Iterator<DexDebugEntry> debugInfoIterator = Collections.emptyIterator();
boolean isPcBasedInfo = getDebugInfo() != null && getDebugInfo().isPcBasedInfo();
if (!isPcBasedInfo && getDebugInfo() != null && method != null) {
debugInfoIterator = new DexDebugEntryBuilder(method, new DexItemFactory()).build().iterator();
debugInfo = debugInfoIterator.hasNext() ? debugInfoIterator.next() : null;
}
int instructionNumber = 0;
Map<Integer, DebugLocalInfo> locals = Collections.emptyMap();
for (DexInstruction insn : instructions) {
debugInfo = advanceToOffset(insn.getOffset() - 1, debugInfo, debugInfoIterator);
while (debugInfo != null && debugInfo.address == insn.getOffset()) {
if (debugInfo.lineEntry || !locals.equals(debugInfo.locals)) {
builder.append(" ").append(debugInfo.toString(false)).append("\n");
}
locals = debugInfo.locals;
debugInfo = debugInfoIterator.hasNext() ? debugInfoIterator.next() : null;
}
StringUtils.appendLeftPadded(builder, Integer.toString(instructionNumber++), 5);
builder.append(": ");
if (insn.isSwitchPayload()) {
DexInstruction payloadUser = payloadUsers.get(insn.getOffset());
builder.append(insn.toString(retracer, payloadUser));
} else {
builder.append(insn.toString(retracer));
}
builder.append('\n');
}
if (isPcBasedInfo) {
builder.append(getDebugInfo()).append("\n");
} else if (debugInfoIterator.hasNext()) {
DexInstruction lastInstruction = ArrayUtils.last(instructions);
debugInfo = advanceToOffset(lastInstruction.getOffset(), debugInfo, debugInfoIterator);
if (debugInfo != null) {
builder
.append("(warning: has unhandled debug events @ pc:")
.append(debugInfo.address)
.append(", line:")
.append(debugInfo.getPosition().getLine());
} else {
builder.append("(has debug events past last pc)\n");
}
}
if (tries.length > 0) {
builder.append("Tries (numbers are offsets)\n");
for (Try atry : tries) {
builder.append(" ");
builder.append(atry.toString());
builder.append('\n');
}
builder.append("Handlers (numbers are offsets)\n");
for (int handlerIndex = 0; handlerIndex < handlers.length; handlerIndex++) {
TryHandler handler = handlers[handlerIndex];
builder.append(" ").append(handlerIndex).append(": ");
builder.append(handler.toString());
builder.append('\n');
}
}
return builder.toString();
}
DexDebugEntry advanceToOffset(
int offset, DexDebugEntry current, Iterator<DexDebugEntry> iterator) {
while (current != null && current.address <= offset) {
current = iterator.hasNext() ? iterator.next() : null;
}
return current;
}
public String toSmaliString(RetracerForCodePrinting retracer) {
StringBuilder builder = new StringBuilder();
// Find labeled targets.
Map<Integer, DexInstruction> payloadUsers = new HashMap<>();
Set<Integer> labledTargets = new HashSet<>();
// Collect payload users and labeled targets for non-payload instructions.
for (DexInstruction dex : instructions) {
int[] targets = dex.getTargets();
if (targets != DexInstruction.NO_TARGETS && targets != DexInstruction.EXIT_TARGET) {
assert targets.length <= 2;
// For if instructions the second target is the fallthrough, for which no label is needed.
labledTargets.add(dex.getOffset() + targets[0]);
} else if (dex.hasPayload()) {
labledTargets.add(dex.getOffset() + dex.getPayloadOffset());
payloadUsers.put(dex.getOffset() + dex.getPayloadOffset(), dex);
}
}
// Collect labeled targets for payload instructions.
for (DexInstruction dex : instructions) {
if (dex.isSwitchPayload()) {
DexInstruction payloadUser = payloadUsers.get(dex.getOffset());
if (dex instanceof DexSwitchPayload) {
DexSwitchPayload payload = (DexSwitchPayload) dex;
for (int target : payload.switchTargetOffsets()) {
labledTargets.add(payloadUser.getOffset() + target);
}
}
}
}
// Generate smali for all instructions.
for (DexInstruction dex : instructions) {
if (labledTargets.contains(dex.getOffset())) {
builder.append(" :label_");
builder.append(dex.getOffset());
builder.append("\n");
}
if (dex.isSwitchPayload()) {
DexInstruction payloadUser = payloadUsers.get(dex.getOffset());
builder.append(dex.toSmaliString(payloadUser)).append('\n');
} else {
builder.append(dex.toSmaliString(retracer)).append('\n');
}
}
if (tries.length > 0) {
builder.append("Tries (numbers are offsets)\n");
for (Try atry : tries) {
builder.append(" ");
builder.append(atry.toString());
builder.append('\n');
}
builder.append("Handlers (numbers are offsets)\n");
for (TryHandler handler : handlers) {
builder.append(handler.toString());
builder.append('\n');
}
}
return builder.toString();
}
@Override
public void collectIndexedItems(
AppView<?> appView,
GraphLens codeLens,
IndexedItemCollection indexedItems,
ProgramMethod context,
LensCodeRewriterUtils rewriter) {
highestSortingString = null;
for (DexInstruction insn : instructions) {
assert !insn.isDexItemBasedConstString();
insn.collectIndexedItems(appView, codeLens, indexedItems, context, rewriter);
if (insn.isConstString()) {
updateHighestSortingString(insn.asConstString().getString());
} else if (insn.isConstStringJumbo()) {
updateHighestSortingString(insn.asConstStringJumbo().getString());
}
}
if (debugInfo != null) {
getDebugInfoForWriting().collectIndexedItems(appView, codeLens, indexedItems);
}
for (TryHandler handler : handlers) {
handler.collectIndexedItems(appView, codeLens, indexedItems);
}
}
@Override
public DexDebugInfoForWriting getDebugInfoForWriting() {
if (debugInfoForWriting == null) {
debugInfoForWriting = DexDebugInfo.convertToWritable(debugInfo);
}
return debugInfoForWriting;
}
@Override
public TryHandler[] getHandlers() {
return handlers;
}
@Override
public DexString getHighestSortingString() {
return highestSortingString;
}
@Override
public Try[] getTries() {
return tries;
}
@Override
public int getRegisterSize(ProgramMethod method) {
return registerSize;
}
@Override
public int getIncomingRegisterSize(ProgramMethod method) {
return incomingRegisterSize;
}
@Override
public int getOutgoingRegisterSize() {
return outgoingRegisterSize;
}
private void updateHighestSortingString(DexString candidate) {
assert candidate != null;
if (highestSortingString == null || highestSortingString.compareTo(candidate) < 0) {
highestSortingString = candidate;
}
}
@Override
public void collectMixedSectionItems(MixedSectionCollection mixedItems) {
if (debugInfo != null) {
getDebugInfoForWriting().collectMixedSectionItems(mixedItems);
}
}
@Override
public int codeSizeInBytes() {
DexInstruction last = instructions[instructions.length - 1];
assert last.hasOffset();
int result = last.getOffset() + last.getSize();
assert result == computeCodeSizeInBytes();
return result;
}
private int computeCodeSizeInBytes() {
int size = 0;
for (DexInstruction insn : instructions) {
size += insn.getSize();
}
return size;
}
@Override
public void writeKeepRulesForDesugaredLibrary(CodeToKeep desugaredLibraryCodeToKeep) {
for (DexInstruction instruction : instructions) {
DexMethod method = instruction.getMethod();
DexField field = instruction.getField();
if (field != null) {
assert method == null;
desugaredLibraryCodeToKeep.recordField(field);
} else if (method != null) {
desugaredLibraryCodeToKeep.recordMethod(method);
} else if (instruction.isConstClass()) {
desugaredLibraryCodeToKeep.recordClass(instruction.asConstClass().getType());
} else if (instruction.isInstanceOf()) {
desugaredLibraryCodeToKeep.recordClass(instruction.asInstanceOf().getType());
} else if (instruction.isCheckCast()) {
desugaredLibraryCodeToKeep.recordClass(instruction.asCheckCast().getType());
}
}
}
@Override
public void writeDex(
ShortBuffer shortBuffer,
ProgramMethod context,
GraphLens graphLens,
GraphLens codeLens,
LensCodeRewriterUtils lensCodeRewriter,
ObjectToOffsetMapping mapping) {
for (DexInstruction instruction : instructions) {
instruction.write(shortBuffer, context, graphLens, codeLens, mapping, lensCodeRewriter);
}
}
@Override
public void forEachPosition(
DexMethod method, boolean isD8R8Synthesized, Consumer<Position> positionConsumer) {
if (getDebugInfo() == null || getDebugInfo().isPcBasedInfo()) {
return;
}
for (DexDebugEvent event : getDebugInfo().asEventBasedInfo().events) {
if (event.isPositionFrame()) {
positionConsumer.accept(event.asSetPositionFrame().getPosition());
}
}
}
@Override
public DexWritableCacheKey getCacheLookupKey(ProgramMethod method, DexItemFactory factory) {
return this;
}
public static class Try extends DexItem implements StructuralItem<Try> {
public static final Try[] EMPTY_ARRAY = new Try[0];
public static final int NO_INDEX = -1;
public final int handlerOffset;
public /* offset */ int startAddress;
public /* offset */ int instructionCount;
public int handlerIndex;
private static void specify(StructuralSpecification<Try, ?> spec) {
// The handler offset is the offset given by the dex input and does not determine the item.
spec.withInt(t -> t.startAddress)
.withInt(t -> t.instructionCount)
.withInt(t -> t.handlerIndex);
}
public Try(int startAddress, int instructionCount, int handlerOffset) {
this.startAddress = startAddress;
this.instructionCount = instructionCount;
this.handlerOffset = handlerOffset;
this.handlerIndex = NO_INDEX;
assert ByteUtils.isU2(instructionCount);
}
@Override
public Try self() {
return this;
}
@Override
public StructuralMapping<Try> getStructuralMapping() {
return Try::specify;
}
public void setHandlerIndex(Int2IntMap map) {
handlerIndex = map.get(handlerOffset);
}
@Override
public int hashCode() {
return startAddress * 2 + instructionCount * 3 + handlerIndex * 5;
}
@Override
public boolean equals(Object other) {
return Equatable.equalsImpl(this, other);
}
@Override
public String toString() {
return "["
+ StringUtils.hexString(startAddress, 2)
+ " .. "
+ StringUtils.hexString(startAddress + instructionCount, 2)
+ "[ -> "
+ handlerIndex;
}
@Override
protected void collectMixedSectionItems(MixedSectionCollection mixedItems) {
// Should never be visited.
assert false;
}
}
public static class TryHandler extends DexItem implements StructuralItem<TryHandler> {
public static final TryHandler[] EMPTY_ARRAY = new TryHandler[0];
public static final int NO_HANDLER = -1;
public final TypeAddrPair[] pairs;
public final /* offset */ int catchAllAddr;
private static void specify(StructuralSpecification<TryHandler, ?> spec) {
spec.withInt(h -> h.catchAllAddr).withItemArray(h -> h.pairs);
}
public TryHandler(TypeAddrPair[] pairs, int catchAllAddr) {
this.pairs = pairs;
this.catchAllAddr = catchAllAddr;
}
@Override
public TryHandler self() {
return this;
}
@Override
public StructuralMapping<TryHandler> getStructuralMapping() {
return TryHandler::specify;
}
@Override
public int hashCode() {
return HashCodeVisitor.run(this);
}
@Override
public boolean equals(Object other) {
return Equatable.equalsImpl(this, other);
}
public void collectIndexedItems(
AppView<?> appView, GraphLens codeLens, IndexedItemCollection indexedItems) {
for (TypeAddrPair pair : pairs) {
pair.collectIndexedItems(appView, codeLens, indexedItems);
}
}
@Override
protected void collectMixedSectionItems(MixedSectionCollection mixedItems) {
// Should never be visited.
assert false;
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("[\n");
for (TypeAddrPair pair : pairs) {
builder.append(" ");
builder.append(pair.type);
builder.append(" -> ");
builder.append(StringUtils.hexString(pair.addr, 2));
builder.append("\n");
}
if (catchAllAddr != NO_HANDLER) {
builder.append(" default -> ");
builder.append(StringUtils.hexString(catchAllAddr, 2));
builder.append("\n");
}
builder.append(" ]");
return builder.toString();
}
public static class TypeAddrPair extends DexItem implements StructuralItem<TypeAddrPair> {
private final DexType type;
public final /* offset */ int addr;
private static void specify(StructuralSpecification<TypeAddrPair, ?> spec) {
spec.withItem(p -> p.type).withInt(p -> p.addr);
}
public TypeAddrPair(DexType type, int addr) {
this.type = type;
this.addr = addr;
}
@Override
public TypeAddrPair self() {
return this;
}
@Override
public StructuralMapping<TypeAddrPair> getStructuralMapping() {
return TypeAddrPair::specify;
}
public DexType getType() {
return type;
}
public DexType getType(GraphLens lens, GraphLens codeLens) {
return lens.lookupType(type, codeLens);
}
public void collectIndexedItems(
AppView<?> appView, GraphLens codeLens, IndexedItemCollection indexedItems) {
DexType rewritten = getType(appView.graphLens(), codeLens);
rewritten.collectIndexedItems(appView, indexedItems);
}
@Override
protected void collectMixedSectionItems(MixedSectionCollection mixedItems) {
// Should never be visited.
assert false;
}
@Override
public int hashCode() {
return type.hashCode() * 7 + addr;
}
@Override
public boolean equals(Object other) {
return Equatable.equalsImpl(this, other);
}
}
}
}