| // 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.graph; | |
| import static com.android.tools.r8.graph.DexCode.FAKE_THIS_PREFIX; | |
| import static com.android.tools.r8.graph.DexCode.FAKE_THIS_SUFFIX; | |
| import com.android.tools.r8.cf.CfPrinter; | |
| import com.android.tools.r8.cf.CfVersion; | |
| import com.android.tools.r8.cf.code.CfFrameVerifier; | |
| import com.android.tools.r8.cf.code.CfFrameVerifier.StackMapStatus; | |
| import com.android.tools.r8.cf.code.CfFrameVerifierEventConsumer; | |
| import com.android.tools.r8.cf.code.CfIinc; | |
| import com.android.tools.r8.cf.code.CfInstruction; | |
| import com.android.tools.r8.cf.code.CfLabel; | |
| import com.android.tools.r8.cf.code.CfLoad; | |
| import com.android.tools.r8.cf.code.CfMonitor; | |
| import com.android.tools.r8.cf.code.CfPosition; | |
| import com.android.tools.r8.cf.code.CfReturnVoid; | |
| import com.android.tools.r8.cf.code.CfTryCatch; | |
| import com.android.tools.r8.dex.code.CfOrDexInstruction; | |
| import com.android.tools.r8.dex.code.DexBase5Format; | |
| import com.android.tools.r8.errors.InvalidDebugInfoException; | |
| import com.android.tools.r8.errors.Unimplemented; | |
| import com.android.tools.r8.errors.Unreachable; | |
| 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.lens.InitClassLens; | |
| 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.SyntheticPosition; | |
| import com.android.tools.r8.ir.conversion.CfSourceCode; | |
| 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.naming.NamingLens; | |
| import com.android.tools.r8.utils.InternalOptions; | |
| import com.android.tools.r8.utils.RetracerForCodePrinting; | |
| import com.android.tools.r8.utils.structural.CompareToVisitor; | |
| 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.google.common.collect.Sets; | |
| import it.unimi.dsi.fastutil.ints.Int2IntArrayMap; | |
| import it.unimi.dsi.fastutil.ints.Int2ReferenceArrayMap; | |
| import it.unimi.dsi.fastutil.ints.Int2ReferenceMap; | |
| import java.util.ArrayList; | |
| import java.util.BitSet; | |
| import java.util.Collections; | |
| import java.util.List; | |
| import java.util.ListIterator; | |
| import java.util.Set; | |
| import java.util.function.Consumer; | |
| import org.objectweb.asm.Label; | |
| import org.objectweb.asm.MethodVisitor; | |
| public class CfCode extends Code implements CfWritableCode, StructuralItem<CfCode> { | |
| public static class LocalVariableInfo { | |
| private final int index; | |
| private final DebugLocalInfo local; | |
| private final CfLabel start; | |
| private CfLabel end; | |
| public LocalVariableInfo(int index, DebugLocalInfo local, CfLabel start) { | |
| this.index = index; | |
| this.local = local; | |
| this.start = start; | |
| } | |
| public LocalVariableInfo(int index, DebugLocalInfo local, CfLabel start, CfLabel end) { | |
| this(index, local, start); | |
| setEnd(end); | |
| } | |
| public void setEnd(CfLabel end) { | |
| assert this.end == null; | |
| assert end != null; | |
| this.end = end; | |
| } | |
| public int getIndex() { | |
| return index; | |
| } | |
| public DebugLocalInfo getLocal() { | |
| return local; | |
| } | |
| public CfLabel getStart() { | |
| return start; | |
| } | |
| public CfLabel getEnd() { | |
| return end; | |
| } | |
| public int acceptCompareTo( | |
| LocalVariableInfo other, CompareToVisitor visitor, CfCompareHelper helper) { | |
| return visitor.visit( | |
| this, | |
| other, | |
| spec -> | |
| spec.withInt(LocalVariableInfo::getIndex) | |
| .withCustomItem(LocalVariableInfo::getStart, helper.labelAcceptor()) | |
| .withCustomItem(LocalVariableInfo::getEnd, helper.labelAcceptor()) | |
| .withItem(LocalVariableInfo::getLocal)); | |
| } | |
| @Override | |
| public String toString() { | |
| return "" + index + " => " + local; | |
| } | |
| } | |
| // The original holder is a reference to the holder type that the method was in from input and | |
| // for which the invokes still refer to. The holder is needed to determine if an invoke-special | |
| // maps to an invoke-direct or invoke-super. | |
| // TODO(b/135969130): Make IR building lens aware and avoid caching the holder type. | |
| private final DexType originalHolder; | |
| private int maxLocals; | |
| private int maxStack; | |
| private List<CfInstruction> instructions; | |
| private final List<CfTryCatch> tryCatchRanges; | |
| private final List<LocalVariableInfo> localVariables; | |
| private StackMapStatus stackMapStatus = CfFrameVerifier.StackMapStatus.NOT_VERIFIED; | |
| private final com.android.tools.r8.position.Position diagnosticPosition; | |
| private final BytecodeMetadata<CfInstruction> metadata; | |
| public CfCode(CfCode code) { | |
| this( | |
| code.originalHolder, | |
| code.maxStack, | |
| code.maxLocals, | |
| code.instructions, | |
| code.tryCatchRanges, | |
| code.localVariables, | |
| code.diagnosticPosition, | |
| code.metadata); | |
| this.stackMapStatus = code.stackMapStatus; | |
| } | |
| public CfCode( | |
| DexType originalHolder, int maxStack, int maxLocals, List<CfInstruction> instructions) { | |
| this( | |
| originalHolder, | |
| maxStack, | |
| maxLocals, | |
| instructions, | |
| Collections.emptyList(), | |
| Collections.emptyList()); | |
| } | |
| public CfCode( | |
| DexType originalHolder, | |
| int maxStack, | |
| int maxLocals, | |
| List<CfInstruction> instructions, | |
| List<CfTryCatch> tryCatchRanges, | |
| List<LocalVariableInfo> localVariables) { | |
| this( | |
| originalHolder, | |
| maxStack, | |
| maxLocals, | |
| instructions, | |
| tryCatchRanges, | |
| localVariables, | |
| com.android.tools.r8.position.Position.UNKNOWN); | |
| } | |
| public CfCode( | |
| DexType originalHolder, | |
| int maxStack, | |
| int maxLocals, | |
| List<CfInstruction> instructions, | |
| List<CfTryCatch> tryCatchRanges, | |
| List<LocalVariableInfo> localVariables, | |
| com.android.tools.r8.position.Position diagnosticPosition) { | |
| this( | |
| originalHolder, | |
| maxStack, | |
| maxLocals, | |
| instructions, | |
| tryCatchRanges, | |
| localVariables, | |
| diagnosticPosition, | |
| BytecodeMetadata.empty()); | |
| } | |
| public CfCode( | |
| DexType originalHolder, | |
| int maxStack, | |
| int maxLocals, | |
| List<CfInstruction> instructions, | |
| List<CfTryCatch> tryCatchRanges, | |
| List<LocalVariableInfo> localVariables, | |
| com.android.tools.r8.position.Position diagnosticPosition, | |
| BytecodeMetadata<CfInstruction> metadata) { | |
| this.originalHolder = originalHolder; | |
| this.maxStack = maxStack; | |
| this.maxLocals = maxLocals; | |
| this.instructions = instructions; | |
| this.tryCatchRanges = tryCatchRanges; | |
| this.localVariables = localVariables; | |
| this.diagnosticPosition = diagnosticPosition; | |
| this.metadata = metadata; | |
| } | |
| @Override | |
| public CfCode self() { | |
| return this; | |
| } | |
| @Override | |
| public CfWritableCodeKind getCfWritableCodeKind() { | |
| return CfWritableCodeKind.DEFAULT; | |
| } | |
| @Override | |
| public BytecodeMetadata<CfInstruction> getMetadata() { | |
| return metadata; | |
| } | |
| @Override | |
| public BytecodeInstructionMetadata getMetadata(CfOrDexInstruction instruction) { | |
| return getMetadata(instruction.asCfInstruction()); | |
| } | |
| public BytecodeInstructionMetadata getMetadata(CfInstruction instruction) { | |
| return metadata.getMetadata(instruction); | |
| } | |
| @Override | |
| public StructuralMapping<CfCode> getStructuralMapping() { | |
| throw new Unreachable(); | |
| } | |
| public DexType getOriginalHolder() { | |
| return originalHolder; | |
| } | |
| public int getMaxStack() { | |
| return maxStack; | |
| } | |
| public int getMaxLocals() { | |
| return maxLocals; | |
| } | |
| public StackMapStatus getStackMapStatus() { | |
| assert stackMapStatus != CfFrameVerifier.StackMapStatus.NOT_VERIFIED; | |
| return stackMapStatus; | |
| } | |
| public void setStackMapStatus(StackMapStatus stackMapStatus) { | |
| this.stackMapStatus = stackMapStatus; | |
| } | |
| public com.android.tools.r8.position.Position getDiagnosticPosition() { | |
| return diagnosticPosition; | |
| } | |
| public void setMaxLocals(int newMaxLocals) { | |
| maxLocals = newMaxLocals; | |
| } | |
| public void setMaxStack(int newMaxStack) { | |
| maxStack = newMaxStack; | |
| } | |
| public List<CfTryCatch> getTryCatchRanges() { | |
| return tryCatchRanges; | |
| } | |
| public Set<CfLabel> getTryCatchRangeLabels() { | |
| Set<CfLabel> tryCatchRangeLabels = Sets.newIdentityHashSet(); | |
| for (CfTryCatch tryCatchRange : getTryCatchRanges()) { | |
| tryCatchRangeLabels.add(tryCatchRange.start); | |
| tryCatchRangeLabels.add(tryCatchRange.end); | |
| } | |
| return tryCatchRangeLabels; | |
| } | |
| public CfInstruction getInstruction(int index) { | |
| return instructions.get(index); | |
| } | |
| public List<CfInstruction> getInstructions() { | |
| return Collections.unmodifiableList(instructions); | |
| } | |
| public void setInstructions(List<CfInstruction> instructions) { | |
| this.instructions = instructions; | |
| } | |
| public List<LocalVariableInfo> getLocalVariables() { | |
| return Collections.unmodifiableList(localVariables); | |
| } | |
| @Override | |
| public int getEstimatedSizeForInliningIfLessThanOrEquals(int threshold) { | |
| int estimatedSizeForInlining = countNonStackOperations(threshold); | |
| if (estimatedSizeForInlining <= threshold) { | |
| return estimatedSizeForInlining; | |
| } | |
| return -1; | |
| } | |
| @Override | |
| public int estimatedDexCodeSizeUpperBoundInBytes() { | |
| return estimatedSizeForInlining() * DexBase5Format.SIZE; | |
| } | |
| public int bytecodeSizeUpperBound() { | |
| int result = 0; | |
| for (CfInstruction instruction : instructions) { | |
| int delta = instruction.bytecodeSizeUpperBound(); | |
| assert delta > 0 || !instruction.emitsIR(); | |
| result += delta; | |
| } | |
| return result; | |
| } | |
| private int countNonStackOperations(int threshold) { | |
| int result = 0; | |
| for (CfInstruction instruction : instructions) { | |
| if (instruction.emitsIR()) { | |
| result++; | |
| if (result > threshold) { | |
| break; | |
| } | |
| } | |
| } | |
| return result; | |
| } | |
| @Override | |
| public boolean isCfCode() { | |
| return true; | |
| } | |
| @Override | |
| public boolean isCfWritableCode() { | |
| return true; | |
| } | |
| @Override | |
| public CfCode asCfCode() { | |
| return this; | |
| } | |
| @Override | |
| public CfWritableCode asCfWritableCode() { | |
| return this; | |
| } | |
| @Override | |
| public void acceptHashing(HashingVisitor visitor) { | |
| visitor.visitInt(instructions.size()); | |
| visitor.visitInt(tryCatchRanges.size()); | |
| visitor.visitInt(localVariables.size()); | |
| instructions.forEach(i -> i.acceptHashing(visitor)); | |
| } | |
| @Override | |
| public int acceptCompareTo(CfCode other, CompareToVisitor visitor) { | |
| CfCompareHelper helper = new CfCompareHelper(this, other); | |
| return visitor.visit( | |
| this, | |
| other, | |
| spec -> | |
| spec.withCustomItemCollection(c -> c.instructions, helper.instructionAcceptor()) | |
| .withCustomItemCollection(c -> c.tryCatchRanges, helper.tryCatchRangeAcceptor()) | |
| .withCustomItemCollection(c -> c.localVariables, helper.localVariableAcceptor())); | |
| } | |
| private boolean shouldAddParameterNames(DexEncodedMethod method, AppView<?> appView) { | |
| // In cf to cf desugar we do pass through of code and don't move around methods. | |
| // TODO(b/169115389): Remove when we have a way to determine if we need parameter names per | |
| // method. | |
| if (appView.options().isCfDesugaring()) { | |
| return false; | |
| } | |
| // Don't add parameter information if the code already has full debug information. | |
| // Note: This fast path can cause a method to loose its parameter info, if the debug info turned | |
| // out to be invalid during IR building. | |
| if (appView.options().debug || appView.isCfByteCodePassThrough(method)) { | |
| return false; | |
| } | |
| assert localVariables.isEmpty(); | |
| if (!method.hasParameterInfo()) { | |
| return false; | |
| } | |
| // If tree shaking, only keep annotations on kept methods. | |
| if (appView.appInfo().hasLiveness() && !appView.appInfo().withLiveness().isPinned(method)) { | |
| return false; | |
| } | |
| return true; | |
| } | |
| public Position getPreamblePosition() { | |
| Position preamble = null; | |
| for (CfInstruction instruction : instructions) { | |
| if (instruction.isLabel()) { | |
| continue; | |
| } | |
| if (instruction.isPosition()) { | |
| Position candidate = instruction.asPosition().getPosition(); | |
| if (candidate.getLine() == 0) { | |
| preamble = candidate; | |
| } | |
| } | |
| break; | |
| } | |
| return preamble; | |
| } | |
| @Override | |
| @SuppressWarnings("ReferenceEquality") | |
| public void writeCf( | |
| ProgramMethod method, | |
| CfVersion classFileVersion, | |
| AppView<?> appView, | |
| NamingLens namingLens, | |
| LensCodeRewriterUtils rewriter, | |
| MethodVisitor visitor) { | |
| GraphLens graphLens = appView.graphLens(); | |
| GraphLens codeLens = getCodeLens(appView); | |
| assert getOrComputeStackMapStatus(method, appView).isValidOrNotPresent() | |
| : "Could not validate stack map frames"; | |
| DexItemFactory dexItemFactory = appView.dexItemFactory(); | |
| InitClassLens initClassLens = appView.initClassLens(); | |
| InternalOptions options = appView.options(); | |
| CfLabel parameterLabel = null; | |
| if (shouldAddParameterNames(method.getDefinition(), appView)) { | |
| parameterLabel = new CfLabel(); | |
| parameterLabel.write( | |
| appView, | |
| method, | |
| dexItemFactory, | |
| graphLens, | |
| codeLens, | |
| initClassLens, | |
| namingLens, | |
| rewriter, | |
| visitor); | |
| } | |
| boolean discardFrames = | |
| classFileVersion.isLessThan(CfVersion.V1_6) | |
| || (appView.enableWholeProgramOptimizations() | |
| && classFileVersion.isEqualTo(CfVersion.V1_6) | |
| && !options.shouldKeepStackMapTable()); | |
| Position preamblePosition = getPreamblePosition(); | |
| boolean discardPreamble = preamblePosition != null && preamblePosition.isSyntheticPosition(); | |
| for (CfInstruction instruction : instructions) { | |
| if (discardFrames && instruction.isFrame()) { | |
| continue; | |
| } | |
| if (discardPreamble | |
| && instruction.isPosition() | |
| && instruction.asPosition().getPosition().equals(preamblePosition)) { | |
| discardPreamble = false; | |
| continue; | |
| } | |
| instruction.write( | |
| appView, | |
| method, | |
| dexItemFactory, | |
| graphLens, | |
| codeLens, | |
| initClassLens, | |
| namingLens, | |
| rewriter, | |
| visitor); | |
| } | |
| visitor.visitEnd(); | |
| visitor.visitMaxs(maxStack, maxLocals); | |
| for (CfTryCatch tryCatch : tryCatchRanges) { | |
| Label start = tryCatch.start.getLabel(); | |
| Label end = tryCatch.end.getLabel(); | |
| for (int i = 0; i < tryCatch.guards.size(); i++) { | |
| DexType guard = tryCatch.guards.get(i); | |
| DexType rewrittenGuard = graphLens.lookupType(guard, codeLens); | |
| Label target = tryCatch.targets.get(i).getLabel(); | |
| visitor.visitTryCatchBlock( | |
| start, | |
| end, | |
| target, | |
| rewrittenGuard == options.itemFactory.throwableType | |
| ? null | |
| : namingLens.lookupInternalName(rewrittenGuard)); | |
| } | |
| } | |
| if (parameterLabel != null) { | |
| assert localVariables.isEmpty(); | |
| Int2ReferenceMap<DebugLocalInfo> parameterInfo = method.getDefinition().getParameterInfo(); | |
| for (Int2ReferenceMap.Entry<DebugLocalInfo> entry : parameterInfo.int2ReferenceEntrySet()) { | |
| writeLocalVariableEntry( | |
| visitor, | |
| graphLens, | |
| codeLens, | |
| namingLens, | |
| entry.getValue(), | |
| parameterLabel, | |
| parameterLabel, | |
| entry.getIntKey()); | |
| } | |
| } else { | |
| for (LocalVariableInfo local : localVariables) { | |
| writeLocalVariableEntry( | |
| visitor, | |
| graphLens, | |
| codeLens, | |
| namingLens, | |
| local.local, | |
| local.start, | |
| local.end, | |
| local.index); | |
| } | |
| } | |
| } | |
| private void writeLocalVariableEntry( | |
| MethodVisitor visitor, | |
| GraphLens graphLens, | |
| GraphLens codeLens, | |
| NamingLens namingLens, | |
| DebugLocalInfo info, | |
| CfLabel start, | |
| CfLabel end, | |
| int index) { | |
| DexType rewrittenType = graphLens.lookupType(info.type, codeLens); | |
| visitor.visitLocalVariable( | |
| info.name.toString(), | |
| namingLens.lookupDescriptor(rewrittenType).toString(), | |
| info.signature == null ? null : info.signature.toString(), | |
| start.getLabel(), | |
| end.getLabel(), | |
| index); | |
| } | |
| @Override | |
| protected int computeHashCode() { | |
| throw new Unimplemented(); | |
| } | |
| @Override | |
| protected boolean computeEquals(Object other) { | |
| throw new Unimplemented(); | |
| } | |
| @Override | |
| public boolean isEmptyVoidMethod() { | |
| for (CfInstruction insn : instructions) { | |
| if (!(insn instanceof CfReturnVoid) | |
| && !(insn instanceof CfLabel) | |
| && !(insn instanceof CfPosition)) { | |
| return false; | |
| } | |
| } | |
| return true; | |
| } | |
| @Override | |
| public boolean hasMonitorInstructions() { | |
| for (CfInstruction instruction : getInstructions()) { | |
| if (instruction instanceof CfMonitor) { | |
| return true; | |
| } | |
| } | |
| return false; | |
| } | |
| @Override | |
| public IRCode buildIR( | |
| ProgramMethod method, | |
| AppView<?> appView, | |
| MutableMethodConversionOptions conversionOptions) { | |
| verifyFramesOrRemove(method, appView); | |
| return internalBuildPossiblyWithLocals( | |
| method, method, appView, appView.codeLens(), null, null, null, conversionOptions); | |
| } | |
| @Override | |
| public IRCode buildInliningIR( | |
| ProgramMethod context, | |
| ProgramMethod method, | |
| AppView<?> appView, | |
| GraphLens codeLens, | |
| NumberGenerator valueNumberGenerator, | |
| Position callerPosition, | |
| RewrittenPrototypeDescription protoChanges) { | |
| assert valueNumberGenerator != null; | |
| assert callerPosition != null; | |
| assert protoChanges != null; | |
| verifyFramesOrRemove(method, appView); | |
| return internalBuildPossiblyWithLocals( | |
| context, | |
| method, | |
| appView, | |
| codeLens, | |
| valueNumberGenerator, | |
| callerPosition, | |
| protoChanges, | |
| MethodConversionOptions.nonConverting()); | |
| } | |
| private void verifyFramesOrRemove(ProgramMethod method, AppView<?> appView) { | |
| stackMapStatus = getOrComputeStackMapStatus(method, appView); | |
| if (!stackMapStatus.isValidOrNotPresent()) { | |
| ArrayList<CfInstruction> copy = new ArrayList<>(instructions); | |
| copy.removeIf(CfInstruction::isFrame); | |
| setInstructions(copy); | |
| } | |
| } | |
| // First build entry. Will either strip locals or build with locals. | |
| private IRCode internalBuildPossiblyWithLocals( | |
| ProgramMethod context, | |
| ProgramMethod method, | |
| AppView<?> appView, | |
| GraphLens codeLens, | |
| NumberGenerator valueNumberGenerator, | |
| Position callerPosition, | |
| RewrittenPrototypeDescription protoChanges, | |
| MutableMethodConversionOptions conversionOptions) { | |
| if (!method.keepLocals(appView)) { | |
| return internalBuild( | |
| Collections.emptyList(), | |
| context, | |
| method, | |
| appView, | |
| codeLens, | |
| valueNumberGenerator, | |
| callerPosition, | |
| protoChanges, | |
| conversionOptions); | |
| } else { | |
| return internalBuildWithLocals( | |
| context, | |
| method, | |
| appView, | |
| codeLens, | |
| valueNumberGenerator, | |
| callerPosition, | |
| protoChanges, | |
| conversionOptions); | |
| } | |
| } | |
| // When building with locals, on invalid debug info, retry build without locals info. | |
| private IRCode internalBuildWithLocals( | |
| ProgramMethod context, | |
| ProgramMethod method, | |
| AppView<?> appView, | |
| GraphLens codeLens, | |
| NumberGenerator valueNumberGenerator, | |
| Position callerPosition, | |
| RewrittenPrototypeDescription protoChanges, | |
| MutableMethodConversionOptions conversionOptions) { | |
| try { | |
| return internalBuild( | |
| Collections.unmodifiableList(localVariables), | |
| context, | |
| method, | |
| appView, | |
| codeLens, | |
| valueNumberGenerator, | |
| callerPosition, | |
| protoChanges, | |
| conversionOptions); | |
| } catch (InvalidDebugInfoException e) { | |
| appView.options().warningInvalidDebugInfo(method, e); | |
| return internalBuild( | |
| Collections.emptyList(), | |
| context, | |
| method, | |
| appView, | |
| codeLens, | |
| valueNumberGenerator, | |
| callerPosition, | |
| protoChanges, | |
| conversionOptions); | |
| } | |
| } | |
| // Inner-most subroutine for building. Must only be called by the two internalBuildXYZ above. | |
| private IRCode internalBuild( | |
| List<LocalVariableInfo> localVariables, | |
| ProgramMethod context, | |
| ProgramMethod method, | |
| AppView<?> appView, | |
| GraphLens codeLens, | |
| NumberGenerator valueNumberGenerator, | |
| Position callerPosition, | |
| RewrittenPrototypeDescription protoChanges, | |
| MutableMethodConversionOptions conversionOptions) { | |
| CfSourceCode source = | |
| new CfSourceCode( | |
| this, | |
| localVariables, | |
| method, | |
| callerPosition, | |
| appView); | |
| IRBuilder builder; | |
| if (valueNumberGenerator == null) { | |
| assert protoChanges == null; | |
| builder = IRBuilder.create(method, appView, source); | |
| } else { | |
| builder = | |
| IRBuilder.createForInlining( | |
| method, appView, codeLens, source, valueNumberGenerator, protoChanges); | |
| } | |
| return builder.build(context, conversionOptions); | |
| } | |
| @Override | |
| public void registerCodeReferences(ProgramMethod method, UseRegistry registry) { | |
| assert registry.getTraversalContinuation().shouldContinue(); | |
| ListIterator<CfInstruction> iterator = instructions.listIterator(); | |
| while (iterator.hasNext()) { | |
| CfInstruction instruction = iterator.next(); | |
| instruction.registerUse(registry, method, iterator); | |
| if (registry.getTraversalContinuation().shouldBreak()) { | |
| return; | |
| } | |
| } | |
| for (CfTryCatch tryCatch : tryCatchRanges) { | |
| tryCatch.internalRegisterUse(registry, method); | |
| if (registry.getTraversalContinuation().shouldBreak()) { | |
| return; | |
| } | |
| } | |
| } | |
| @Override | |
| public void registerCodeReferencesForDesugaring(ClasspathMethod method, UseRegistry registry) { | |
| ListIterator<CfInstruction> iterator = instructions.listIterator(); | |
| while (iterator.hasNext()) { | |
| CfInstruction instruction = iterator.next(); | |
| instruction.registerUseForDesugaring(registry, method, iterator); | |
| } | |
| tryCatchRanges.forEach(tryCatch -> tryCatch.guards.forEach(registry::registerTypeReference)); | |
| } | |
| @Override | |
| public Int2ReferenceMap<DebugLocalInfo> collectParameterInfo( | |
| DexEncodedMethod encodedMethod, AppView<?> appView) { | |
| CfLabel firstLabel = null; | |
| for (CfInstruction instruction : instructions) { | |
| if (instruction instanceof CfLabel) { | |
| firstLabel = (CfLabel) instruction; | |
| break; | |
| } | |
| } | |
| if (firstLabel == null) { | |
| return DexEncodedMethod.NO_PARAMETER_INFO; | |
| } | |
| if (!appView.options().hasProguardConfiguration() | |
| || !appView.options().getProguardConfiguration().isKeepParameterNames()) { | |
| return DexEncodedMethod.NO_PARAMETER_INFO; | |
| } | |
| // The enqueuer might build IR to trace reflective behaviour. At that point liveness is not | |
| // known, so be conservative with collection parameter name information. | |
| if (appView.appInfo().hasLiveness() | |
| && !appView.appInfo().withLiveness().isPinned(encodedMethod)) { | |
| return DexEncodedMethod.NO_PARAMETER_INFO; | |
| } | |
| // Collect the local slots used for parameters. | |
| BitSet localSlotsForParameters = new BitSet(0); | |
| int nextLocalSlotsForParameters = 0; | |
| if (!encodedMethod.isStatic()) { | |
| localSlotsForParameters.set(nextLocalSlotsForParameters++); | |
| } | |
| for (DexType type : encodedMethod.getReference().proto.parameters.values) { | |
| localSlotsForParameters.set(nextLocalSlotsForParameters); | |
| nextLocalSlotsForParameters += type.isLongType() || type.isDoubleType() ? 2 : 1; | |
| } | |
| // Collect the first piece of local variable information for each argument local slot, | |
| // assuming that that does actually describe the parameter (name, type and possibly | |
| // signature). | |
| Int2ReferenceMap<DebugLocalInfo> parameterInfo = | |
| new Int2ReferenceArrayMap<>(localSlotsForParameters.cardinality()); | |
| for (LocalVariableInfo node : localVariables) { | |
| if (node.start == firstLabel | |
| && localSlotsForParameters.get(node.index) | |
| && !parameterInfo.containsKey(node.index)) { | |
| parameterInfo.put( | |
| node.index, new DebugLocalInfo(node.local.name, node.local.type, node.local.signature)); | |
| } | |
| } | |
| return parameterInfo; | |
| } | |
| @Override | |
| public void registerArgumentReferences(DexEncodedMethod method, ArgumentUse registry) { | |
| DexProto proto = method.getReference().proto; | |
| boolean isStatic = method.accessFlags.isStatic(); | |
| int argumentCount = proto.parameters.values.length + (isStatic ? 0 : 1); | |
| Int2IntArrayMap indexToNumber = new Int2IntArrayMap(argumentCount); | |
| { | |
| int index = 0; | |
| int number = 0; | |
| if (!isStatic) { | |
| indexToNumber.put(index++, number++); | |
| } | |
| for (DexType value : proto.parameters.values) { | |
| indexToNumber.put(index++, number++); | |
| if (value.isLongType() || value.isDoubleType()) { | |
| index++; | |
| } | |
| } | |
| } | |
| assert indexToNumber.size() == argumentCount; | |
| for (CfInstruction instruction : instructions) { | |
| int index = -1; | |
| if (instruction instanceof CfLoad) { | |
| index = ((CfLoad) instruction).getLocalIndex(); | |
| } else if (instruction instanceof CfIinc) { | |
| index = ((CfIinc) instruction).getLocalIndex(); | |
| } else { | |
| continue; | |
| } | |
| if (index >= 0 && indexToNumber.containsKey(index)) { | |
| registry.register(indexToNumber.get(index)); | |
| } | |
| } | |
| } | |
| @Override | |
| public String toString() { | |
| return new CfPrinter(this).toString(); | |
| } | |
| @Override | |
| public String toString(DexEncodedMethod method, RetracerForCodePrinting retracer) { | |
| return new CfPrinter(this, method, retracer).toString(); | |
| } | |
| void addFakeThisParameter(DexItemFactory factory) { | |
| if (localVariables == null || localVariables.isEmpty()) { | |
| // We have no debugging info in the code. | |
| return; | |
| } | |
| int largestPrefix = 0; | |
| int existingThisIndex = -1; | |
| for (int i = 0; i < localVariables.size(); i++) { | |
| LocalVariableInfo localVariable = localVariables.get(i); | |
| largestPrefix = | |
| Math.max(largestPrefix, DexCode.getLargestPrefix(factory, localVariable.local.name)); | |
| if (localVariable.local.name.toString().equals("this")) { | |
| existingThisIndex = i; | |
| } | |
| } | |
| if (existingThisIndex < 0) { | |
| return; | |
| } | |
| String fakeThisName = FAKE_THIS_PREFIX.repeat(largestPrefix + 1) + FAKE_THIS_SUFFIX; | |
| DebugLocalInfo debugLocalInfo = | |
| new DebugLocalInfo(factory.createString(fakeThisName), this.originalHolder, null); | |
| LocalVariableInfo thisLocalInfo = localVariables.get(existingThisIndex); | |
| this.localVariables.set( | |
| existingThisIndex, | |
| new LocalVariableInfo( | |
| thisLocalInfo.index, debugLocalInfo, thisLocalInfo.start, thisLocalInfo.end)); | |
| } | |
| @Override | |
| public CfCode getCodeAsInlining( | |
| DexMethod caller, | |
| boolean isCallerD8R8Synthesized, | |
| DexMethod callee, | |
| boolean isCalleeD8R8Synthesized, | |
| DexItemFactory factory) { | |
| Position callerPosition = | |
| SyntheticPosition.builder() | |
| .setLine(0) | |
| .setMethod(caller) | |
| .setIsD8R8Synthesized(isCallerD8R8Synthesized) | |
| .build(); | |
| List<CfInstruction> newInstructions = new ArrayList<>(instructions.size() + 2); | |
| CfLabel firstLabel; | |
| if (instructions.get(0).isLabel()) { | |
| firstLabel = instructions.get(0).asLabel(); | |
| } else { | |
| firstLabel = new CfLabel(); | |
| newInstructions.add(firstLabel); | |
| } | |
| boolean seenPosition = false; | |
| for (CfInstruction instruction : instructions) { | |
| if (instruction.isPosition()) { | |
| seenPosition = true; | |
| CfPosition oldPosition = instruction.asPosition(); | |
| newInstructions.add( | |
| new CfPosition( | |
| oldPosition.getLabel(), | |
| newInlineePosition( | |
| callerPosition, oldPosition.getPosition(), isCalleeD8R8Synthesized))); | |
| } else { | |
| if (!instruction.isLabel() && !seenPosition) { | |
| Position preamblePosition = | |
| isCalleeD8R8Synthesized | |
| ? callerPosition | |
| : SyntheticPosition.builder() | |
| .setMethod(callee) | |
| .setCallerPosition(callerPosition) | |
| .setLine(0) | |
| .build(); | |
| newInstructions.add(new CfPosition(firstLabel, preamblePosition)); | |
| seenPosition = true; | |
| } | |
| newInstructions.add(instruction); | |
| } | |
| } | |
| return new CfCode( | |
| originalHolder, maxStack, maxLocals, newInstructions, tryCatchRanges, localVariables); | |
| } | |
| public StackMapStatus getOrComputeStackMapStatus(ProgramMethod method, AppView<?> appView) { | |
| if (stackMapStatus.isNotVerified()) { | |
| setStackMapStatus(computeStackMapStatus(method, appView)); | |
| } | |
| return stackMapStatus; | |
| } | |
| private StackMapStatus computeStackMapStatus(ProgramMethod method, AppView<?> appView) { | |
| CfFrameVerifierEventConsumer eventConsumer = | |
| new CfFrameVerifierEventConsumer() { | |
| @Override | |
| public void acceptError(CfCodeDiagnostics diagnostics) { | |
| // Stack maps was required from version V1_6 (50), but the JVM gave a grace-period and | |
| // only started enforcing stack maps from 51 in JVM 8. As a consequence, we have | |
| // different android libraries that has V1_7 code but has no stack maps. To not fail on | |
| // compilations we only report a warning. | |
| appView.options().reporter.warning(diagnostics); | |
| } | |
| }; | |
| CfFrameVerifier helper = | |
| CfFrameVerifier.builder(appView, this, method) | |
| .setEventConsumer(eventConsumer) | |
| .build(); | |
| return helper.run(); | |
| } | |
| @Override | |
| public void forEachPosition( | |
| DexMethod method, boolean isD8R8Synthesized, Consumer<Position> positionConsumer) { | |
| for (CfInstruction instruction : getInstructions()) { | |
| if (instruction.isPosition()) { | |
| positionConsumer.accept(instruction.asPosition().getPosition()); | |
| } | |
| } | |
| } | |
| } |