Separate code in line optimizer into logical parts
Change-Id: I2ab0fd89b1d18c6bddaacae0ff9ea6b1899b9e19
diff --git a/src/main/java/com/android/tools/r8/debuginfo/DebugRepresentation.java b/src/main/java/com/android/tools/r8/debuginfo/DebugRepresentation.java
index 3d4c3b2..ed04432 100644
--- a/src/main/java/com/android/tools/r8/debuginfo/DebugRepresentation.java
+++ b/src/main/java/com/android/tools/r8/debuginfo/DebugRepresentation.java
@@ -16,8 +16,9 @@
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.IterableUtils;
import com.android.tools.r8.utils.LebUtils;
-import com.android.tools.r8.utils.LineNumberOptimizer;
import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.positions.LineNumberOptimizer;
+import com.android.tools.r8.utils.positions.PositionUtils;
import it.unimi.dsi.fastutil.ints.Int2ReferenceAVLTreeMap;
import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
import it.unimi.dsi.fastutil.ints.Int2ReferenceOpenHashMap;
@@ -110,7 +111,7 @@
// If debug info is "null" then the cost of representing it as normal events will be a
// single default event to ensure its source file content is active.
debugInfo =
- LineNumberOptimizer.createEventBasedInfoForMethodWithoutDebugInfo(
+ DexDebugInfo.createEventBasedInfoForMethodWithoutDebugInfo(
definition, options.dexItemFactory());
}
assert debugInfo.getParameterCount() == method.getParameters().size();
@@ -179,8 +180,7 @@
if (!method.hasCode() || !method.getCode().isDexCode()) {
return false;
}
- DexCode code = method.getCode().asDexCode();
- return LineNumberOptimizer.mustHaveResidualDebugInfo(code, options);
+ return PositionUtils.mustHaveResidualDebugInfo(options, method);
}
/** Cost information for debug info at a given PC. */
diff --git a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
index bb281b3..96b00bf 100644
--- a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
@@ -3,7 +3,7 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.dex;
-import static com.android.tools.r8.utils.LineNumberOptimizer.runAndWriteMap;
+import static com.android.tools.r8.utils.positions.LineNumberOptimizer.runAndWriteMap;
import com.android.tools.r8.ByteBufferProvider;
import com.android.tools.r8.ByteDataView;
diff --git a/src/main/java/com/android/tools/r8/graph/DexDebugInfo.java b/src/main/java/com/android/tools/r8/graph/DexDebugInfo.java
index 0300502..a52374e 100644
--- a/src/main/java/com/android/tools/r8/graph/DexDebugInfo.java
+++ b/src/main/java/com/android/tools/r8/graph/DexDebugInfo.java
@@ -290,6 +290,14 @@
}
}
+ public static EventBasedDebugInfo createEventBasedInfoForMethodWithoutDebugInfo(
+ DexEncodedMethod method, DexItemFactory factory) {
+ return new EventBasedDebugInfo(
+ 0,
+ new DexString[method.getParameters().size()],
+ new DexDebugEvent[] {factory.zeroChangeDefaultEvent});
+ }
+
public static EventBasedDebugInfo convertToEventBased(DexCode code, DexItemFactory factory) {
if (code.getDebugInfo() == null) {
return null;
diff --git a/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java b/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
index 461dc2a..acc9161 100644
--- a/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
@@ -4,7 +4,7 @@
package com.android.tools.r8.jar;
import static com.android.tools.r8.utils.InternalOptions.ASM_VERSION;
-import static com.android.tools.r8.utils.LineNumberOptimizer.runAndWriteMap;
+import static com.android.tools.r8.utils.positions.LineNumberOptimizer.runAndWriteMap;
import com.android.tools.r8.ByteDataView;
import com.android.tools.r8.ClassFileConsumer;
diff --git a/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java b/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
deleted file mode 100644
index 4885cb5..0000000
--- a/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
+++ /dev/null
@@ -1,1548 +0,0 @@
-// 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.utils;
-
-import com.android.tools.r8.ResourceException;
-import com.android.tools.r8.cf.code.CfInstruction;
-import com.android.tools.r8.cf.code.CfPosition;
-import com.android.tools.r8.debuginfo.DebugRepresentation;
-import com.android.tools.r8.debuginfo.DebugRepresentation.DebugRepresentationPredicate;
-import com.android.tools.r8.dex.code.DexInstruction;
-import com.android.tools.r8.errors.Unreachable;
-import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
-import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.CfCode;
-import com.android.tools.r8.graph.Code;
-import com.android.tools.r8.graph.DexApplication;
-import com.android.tools.r8.graph.DexClass;
-import com.android.tools.r8.graph.DexClassAndMethod;
-import com.android.tools.r8.graph.DexCode;
-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.DexDebugEvent.EndLocal;
-import com.android.tools.r8.graph.DexDebugEvent.RestartLocal;
-import com.android.tools.r8.graph.DexDebugEvent.SetEpilogueBegin;
-import com.android.tools.r8.graph.DexDebugEvent.SetFile;
-import com.android.tools.r8.graph.DexDebugEvent.SetPrologueEnd;
-import com.android.tools.r8.graph.DexDebugEvent.StartLocal;
-import com.android.tools.r8.graph.DexDebugEventBuilder;
-import com.android.tools.r8.graph.DexDebugEventVisitor;
-import com.android.tools.r8.graph.DexDebugInfo;
-import com.android.tools.r8.graph.DexDebugInfo.EventBasedDebugInfo;
-import com.android.tools.r8.graph.DexDebugInfoForSingleLineMethod;
-import com.android.tools.r8.graph.DexDebugPositionState;
-import com.android.tools.r8.graph.DexEncodedMethod;
-import com.android.tools.r8.graph.DexField;
-import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.graph.DexMethod;
-import com.android.tools.r8.graph.DexProgramClass;
-import com.android.tools.r8.graph.DexString;
-import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.DexValue.DexValueString;
-import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.ir.code.Position;
-import com.android.tools.r8.ir.code.Position.OutlineCallerPosition;
-import com.android.tools.r8.ir.code.Position.OutlineCallerPosition.OutlineCallerPositionBuilder;
-import com.android.tools.r8.ir.code.Position.OutlinePosition;
-import com.android.tools.r8.ir.code.Position.PositionBuilder;
-import com.android.tools.r8.ir.code.Position.SourcePosition;
-import com.android.tools.r8.kotlin.KotlinSourceDebugExtensionParser;
-import com.android.tools.r8.kotlin.KotlinSourceDebugExtensionParser.Result;
-import com.android.tools.r8.naming.ClassNameMapper;
-import com.android.tools.r8.naming.ClassNaming;
-import com.android.tools.r8.naming.ClassNaming.Builder;
-import com.android.tools.r8.naming.ClassNamingForNameMapper.MappedRange;
-import com.android.tools.r8.naming.MapVersion;
-import com.android.tools.r8.naming.MemberNaming;
-import com.android.tools.r8.naming.MemberNaming.FieldSignature;
-import com.android.tools.r8.naming.MemberNaming.MethodSignature;
-import com.android.tools.r8.naming.NamingLens;
-import com.android.tools.r8.naming.PositionRangeAllocator;
-import com.android.tools.r8.naming.PositionRangeAllocator.CardinalPositionRangeAllocator;
-import com.android.tools.r8.naming.PositionRangeAllocator.NonCardinalPositionRangeAllocator;
-import com.android.tools.r8.naming.ProguardMapSupplier;
-import com.android.tools.r8.naming.ProguardMapSupplier.ProguardMapId;
-import com.android.tools.r8.naming.Range;
-import com.android.tools.r8.naming.mappinginformation.CompilerSynthesizedMappingInformation;
-import com.android.tools.r8.naming.mappinginformation.FileNameInformation;
-import com.android.tools.r8.naming.mappinginformation.MappingInformation;
-import com.android.tools.r8.naming.mappinginformation.OutlineCallsiteMappingInformation;
-import com.android.tools.r8.naming.mappinginformation.OutlineMappingInformation;
-import com.android.tools.r8.naming.mappinginformation.ResidualSignatureMappingInformation.ResidualMethodSignatureMappingInformation;
-import com.android.tools.r8.naming.mappinginformation.RewriteFrameMappingInformation;
-import com.android.tools.r8.naming.mappinginformation.RewriteFrameMappingInformation.RemoveInnerFramesAction;
-import com.android.tools.r8.naming.mappinginformation.RewriteFrameMappingInformation.ThrowsCondition;
-import com.android.tools.r8.references.MethodReference;
-import com.android.tools.r8.references.Reference;
-import com.android.tools.r8.retrace.internal.RetraceUtils;
-import com.android.tools.r8.shaking.KeepInfoCollection;
-import com.android.tools.r8.utils.InternalOptions.LineNumberOptimization;
-import it.unimi.dsi.fastutil.ints.Int2IntArrayMap;
-import it.unimi.dsi.fastutil.ints.Int2IntLinkedOpenHashMap;
-import it.unimi.dsi.fastutil.ints.Int2IntMap;
-import it.unimi.dsi.fastutil.ints.Int2IntSortedMap;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.IdentityHashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.Objects;
-import java.util.function.Function;
-
-public class LineNumberOptimizer {
-
- private static final int MAX_LINE_NUMBER = 65535;
-
- // PositionRemapper is a stateful function which takes a position (represented by a
- // DexDebugPositionState) and returns a remapped Position.
- private interface PositionRemapper {
- Pair<Position, Position> createRemappedPosition(Position position);
- }
-
- private static class IdentityPositionRemapper implements PositionRemapper {
-
- @Override
- public Pair<Position, Position> createRemappedPosition(Position position) {
- // If we create outline calls we have to map them.
- assert position.getOutlineCallee() == null;
- return new Pair<>(position, position);
- }
- }
-
- private static class OptimizingPositionRemapper implements PositionRemapper {
- private final int maxLineDelta;
- private DexMethod previousMethod = null;
- private int previousSourceLine = -1;
- private int nextOptimizedLineNumber = 1;
-
- OptimizingPositionRemapper(InternalOptions options) {
- // TODO(113198295): For dex using "Constants.DBG_LINE_RANGE + Constants.DBG_LINE_BASE"
- // instead of 1 creates a ~30% smaller map file but the dex files gets larger due to reduced
- // debug info canonicalization.
- maxLineDelta = options.isGeneratingClassFiles() ? Integer.MAX_VALUE : 1;
- }
-
- @Override
- public Pair<Position, Position> createRemappedPosition(Position position) {
- assert position.getMethod() != null;
- if (previousMethod == position.getMethod()) {
- assert previousSourceLine >= 0;
- if (position.getLine() > previousSourceLine
- && position.getLine() - previousSourceLine <= maxLineDelta) {
- nextOptimizedLineNumber += (position.getLine() - previousSourceLine) - 1;
- }
- }
-
- Position newPosition =
- position
- .builderWithCopy()
- .setLine(nextOptimizedLineNumber++)
- .setCallerPosition(null)
- .build();
- previousSourceLine = position.getLine();
- previousMethod = position.getMethod();
- return new Pair<>(position, newPosition);
- }
- }
-
- private static class KotlinInlineFunctionPositionRemapper implements PositionRemapper {
-
- private final AppView<?> appView;
- private final DexItemFactory factory;
- private final Map<DexType, Result> parsedKotlinSourceDebugExtensions = new IdentityHashMap<>();
- private final CfLineToMethodMapper lineToMethodMapper;
- private final PositionRemapper baseRemapper;
-
- // Fields for the current context.
- private DexEncodedMethod currentMethod;
- private Result parsedData = null;
-
- private KotlinInlineFunctionPositionRemapper(
- AppView<?> appView,
- PositionRemapper baseRemapper,
- CfLineToMethodMapper lineToMethodMapper) {
- this.appView = appView;
- this.factory = appView.dexItemFactory();
- this.baseRemapper = baseRemapper;
- this.lineToMethodMapper = lineToMethodMapper;
- }
-
- @Override
- public Pair<Position, Position> createRemappedPosition(Position position) {
- assert currentMethod != null;
- int line = position.getLine();
- Result parsedData = getAndParseSourceDebugExtension(position.getMethod().holder);
- if (parsedData == null) {
- return baseRemapper.createRemappedPosition(position);
- }
- Map.Entry<Integer, KotlinSourceDebugExtensionParser.Position> inlinedPosition =
- parsedData.lookupInlinedPosition(line);
- if (inlinedPosition == null) {
- return baseRemapper.createRemappedPosition(position);
- }
- int inlineeLineDelta = line - inlinedPosition.getKey();
- int originalInlineeLine = inlinedPosition.getValue().getRange().from + inlineeLineDelta;
- try {
- String binaryName = inlinedPosition.getValue().getSource().getPath();
- String nameAndDescriptor =
- lineToMethodMapper.lookupNameAndDescriptor(binaryName, originalInlineeLine);
- if (nameAndDescriptor == null) {
- return baseRemapper.createRemappedPosition(position);
- }
- String clazzDescriptor = DescriptorUtils.getDescriptorFromClassBinaryName(binaryName);
- String methodName = CfLineToMethodMapper.getName(nameAndDescriptor);
- String methodDescriptor = CfLineToMethodMapper.getDescriptor(nameAndDescriptor);
- String returnTypeDescriptor = DescriptorUtils.getReturnTypeDescriptor(methodDescriptor);
- String[] argumentDescriptors = DescriptorUtils.getArgumentTypeDescriptors(methodDescriptor);
- DexString[] argumentDexStringDescriptors = new DexString[argumentDescriptors.length];
- for (int i = 0; i < argumentDescriptors.length; i++) {
- argumentDexStringDescriptors[i] = factory.createString(argumentDescriptors[i]);
- }
- DexMethod inlinee =
- factory.createMethod(
- factory.createString(clazzDescriptor),
- factory.createString(methodName),
- factory.createString(returnTypeDescriptor),
- argumentDexStringDescriptors);
- if (!inlinee.equals(position.getMethod())) {
- // We have an inline from a different method than the current position.
- Entry<Integer, KotlinSourceDebugExtensionParser.Position> calleePosition =
- parsedData.lookupCalleePosition(line);
- if (calleePosition != null) {
- // Take the first line as the callee position
- position =
- position
- .builderWithCopy()
- .setLine(calleePosition.getValue().getRange().from)
- .build();
- }
- return baseRemapper.createRemappedPosition(
- SourcePosition.builder()
- .setLine(originalInlineeLine)
- .setMethod(inlinee)
- .setCallerPosition(position)
- .build());
- }
- // This is the same position, so we should really not mark this as an inline position. Fall
- // through to the default case.
- } catch (ResourceException ignored) {
- // Intentionally left empty. Remapping of kotlin functions utility is a best effort mapping.
- }
- return baseRemapper.createRemappedPosition(position);
- }
-
- private Result getAndParseSourceDebugExtension(DexType holder) {
- if (parsedData == null) {
- parsedData = parsedKotlinSourceDebugExtensions.get(holder);
- }
- if (parsedData != null || parsedKotlinSourceDebugExtensions.containsKey(holder)) {
- return parsedData;
- }
- DexClass clazz = appView.definitionFor(currentMethod.getHolderType());
- DexValueString dexValueString = appView.getSourceDebugExtensionForType(clazz);
- if (dexValueString != null) {
- parsedData = KotlinSourceDebugExtensionParser.parse(dexValueString.value.toString());
- }
- parsedKotlinSourceDebugExtensions.put(holder, parsedData);
- return parsedData;
- }
-
- public void setMethod(DexEncodedMethod method) {
- this.currentMethod = method;
- this.parsedData = null;
- }
- }
-
- // PositionEventEmitter is a stateful function which converts a Position into series of
- // position-related DexDebugEvents and puts them into a processedEvents list.
- private static class PositionEventEmitter {
- private final DexItemFactory dexItemFactory;
- private int startLine = -1;
- private final DexMethod method;
- private int previousPc = 0;
- private Position previousPosition = null;
- private final List<DexDebugEvent> processedEvents;
-
- private PositionEventEmitter(
- DexItemFactory dexItemFactory, DexMethod method, List<DexDebugEvent> processedEvents) {
- this.dexItemFactory = dexItemFactory;
- this.method = method;
- this.processedEvents = processedEvents;
- }
-
- private void emitAdvancePc(int pc) {
- processedEvents.add(new AdvancePC(pc - previousPc));
- previousPc = pc;
- }
-
- private void emitPositionEvents(int currentPc, Position currentPosition) {
- if (previousPosition == null) {
- startLine = currentPosition.getLine();
- previousPosition = SourcePosition.builder().setLine(startLine).setMethod(method).build();
- }
- DexDebugEventBuilder.emitAdvancementEvents(
- previousPc,
- previousPosition,
- currentPc,
- currentPosition,
- processedEvents,
- dexItemFactory,
- true);
- previousPc = currentPc;
- previousPosition = currentPosition;
- }
-
- private int getStartLine() {
- assert (startLine >= 0);
- return startLine;
- }
- }
-
- // We will be remapping positional debug events and collect them as MappedPositions.
- private static class MappedPosition {
-
- private final DexMethod method;
- private final int originalLine;
- private final Position caller;
- private final int obfuscatedLine;
- private final boolean isOutline;
- private final DexMethod outlineCallee;
- private final Int2StructuralItemArrayMap<Position> outlinePositions;
-
- private MappedPosition(
- DexMethod method,
- int originalLine,
- Position caller,
- int obfuscatedLine,
- boolean isOutline,
- DexMethod outlineCallee,
- Int2StructuralItemArrayMap<Position> outlinePositions) {
- this.method = method;
- this.originalLine = originalLine;
- this.caller = caller;
- this.obfuscatedLine = obfuscatedLine;
- this.isOutline = isOutline;
- this.outlineCallee = outlineCallee;
- this.outlinePositions = outlinePositions;
- }
-
- public boolean isOutlineCaller() {
- return outlineCallee != null;
- }
- }
-
- public static ProguardMapId runAndWriteMap(
- AndroidApp inputApp,
- AppView<?> appView,
- Timing timing,
- OriginalSourceFiles originalSourceFiles,
- DebugRepresentationPredicate representation) {
- assert appView.options().proguardMapConsumer != null;
- // When line number optimization is turned off the identity mapping for line numbers is
- // used. We still run the line number optimizer to collect line numbers and inline frame
- // information for the mapping file.
- timing.begin("Line number remapping");
- ClassNameMapper mapper =
- run(
- appView,
- inputApp,
- originalSourceFiles,
- representation);
- timing.end();
- timing.begin("Write proguard map");
- ProguardMapId mapId = ProguardMapSupplier.create(mapper, appView.options()).writeProguardMap();
- timing.end();
- return mapId;
- }
-
- private interface PcBasedDebugInfoRecorder {
- /** Callback to record a code object with a given max instruction PC and parameter count. */
- void recordPcMappingFor(ProgramMethod method, int maxEncodingPc);
-
- /** Callback to record a code object with only a single "line". */
- void recordSingleLineFor(ProgramMethod method, int maxEncodingPc);
-
- /**
- * Install the correct debug info objects.
- *
- * <p>Must be called after all recordings have been given to allow computing the debug info
- * items to be installed.
- */
- void updateDebugInfoInCodeObjects();
-
- int getPcEncoding(int pc);
- }
-
- private static class Pc2PcMappingSupport implements PcBasedDebugInfoRecorder {
-
- private static class UpdateInfo {
- final DexCode code;
- final int paramCount;
- final int maxEncodingPc;
-
- public UpdateInfo(DexCode code, int paramCount, int maxEncodingPc) {
- this.code = code;
- this.paramCount = paramCount;
- this.maxEncodingPc = maxEncodingPc;
- }
-
- // Used as key when building the shared debug info map.
- // Only param and max-pc are part of the key.
-
- @Override
- public boolean equals(Object o) {
- UpdateInfo that = (UpdateInfo) o;
- return paramCount == that.paramCount && maxEncodingPc == that.maxEncodingPc;
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(paramCount, maxEncodingPc);
- }
- }
-
- private final List<UpdateInfo> codesToUpdate = new ArrayList<>();
-
- // We can only drop single-line debug info if it is OK to lose the source-file info.
- // This list is null if we must retain single-line entries.
- private final List<DexCode> singleLineCodesToClear;
-
- public Pc2PcMappingSupport(boolean allowDiscardingSourceFile) {
- singleLineCodesToClear = allowDiscardingSourceFile ? new ArrayList<>() : null;
- }
-
- @Override
- public int getPcEncoding(int pc) {
- assert pc >= 0;
- return pc + 1;
- }
-
- private boolean cantAddToClearSet(ProgramMethod method) {
- assert method.getDefinition().getCode().isDexCode();
- if (singleLineCodesToClear == null) {
- return true;
- }
- singleLineCodesToClear.add(method.getDefinition().getCode().asDexCode());
- return false;
- }
-
- @Override
- public void recordPcMappingFor(ProgramMethod method, int maxEncodingPc) {
- assert method.getDefinition().getCode().isDexCode();
- int parameterCount = method.getParameters().size();
- DexCode code = method.getDefinition().getCode().asDexCode();
- assert DebugRepresentation.verifyLastExecutableInstructionWithinBound(code, maxEncodingPc);
- codesToUpdate.add(new UpdateInfo(code, parameterCount, maxEncodingPc));
- }
-
- @Override
- public void recordSingleLineFor(ProgramMethod method, int maxEncodingPc) {
- if (cantAddToClearSet(method)) {
- recordPcMappingFor(method, maxEncodingPc);
- }
- }
-
- @Override
- public void updateDebugInfoInCodeObjects() {
- Map<UpdateInfo, DexDebugInfo> debugInfos = new HashMap<>();
- codesToUpdate.forEach(
- entry -> {
- assert DebugRepresentation.verifyLastExecutableInstructionWithinBound(
- entry.code, entry.maxEncodingPc);
- DexDebugInfo debugInfo =
- debugInfos.computeIfAbsent(entry, Pc2PcMappingSupport::buildPc2PcDebugInfo);
- assert debugInfo.asPcBasedInfo().getMaxPc() == entry.maxEncodingPc;
- entry.code.setDebugInfo(debugInfo);
- });
- if (singleLineCodesToClear != null) {
- singleLineCodesToClear.forEach(c -> c.setDebugInfo(null));
- }
- }
-
- private static DexDebugInfo buildPc2PcDebugInfo(UpdateInfo info) {
- return new DexDebugInfo.PcBasedDebugInfo(info.paramCount, info.maxEncodingPc);
- }
- }
-
- private static class NativePcSupport implements PcBasedDebugInfoRecorder {
-
- @Override
- public int getPcEncoding(int pc) {
- assert pc >= 0;
- return pc;
- }
-
- private void clearDebugInfo(ProgramMethod method) {
- // Always strip the info in full as the runtime will emit the PC directly.
- method.getDefinition().getCode().asDexCode().setDebugInfo(null);
- }
-
- @Override
- public void recordPcMappingFor(ProgramMethod method, int maxEncodingPc) {
- clearDebugInfo(method);
- }
-
- @Override
- public void recordSingleLineFor(ProgramMethod method, int maxEncodingPc) {
- clearDebugInfo(method);
- }
-
- @Override
- public void updateDebugInfoInCodeObjects() {
- // Already null out the info so nothing to do.
- }
- }
-
- public static ClassNameMapper run(
- AppView<?> appView,
- AndroidApp inputApp,
- OriginalSourceFiles originalSourceFiles,
- DebugRepresentationPredicate representation) {
- // For finding methods in kotlin files based on SourceDebugExtensions, we use a line method map.
- // We create it here to ensure it is only reading class files once.
- // TODO(b/220999985): Make this threaded per virtual file. Possibly pull the kotlin line mapping
- // onto main thread before threading.
- CfLineToMethodMapper cfLineToMethodMapper = new CfLineToMethodMapper(inputApp);
- ClassNameMapper.Builder classNameMapperBuilder = ClassNameMapper.builder();
-
- Map<DexMethod, OutlineFixupBuilder> outlinesToFix = new IdentityHashMap<>();
- Map<DexType, String> prunedInlinedClasses = new IdentityHashMap<>();
-
- PcBasedDebugInfoRecorder pcBasedDebugInfo =
- appView.options().canUseNativeDexPcInsteadOfDebugInfo()
- ? new NativePcSupport()
- : new Pc2PcMappingSupport(appView.options().allowDiscardingResidualDebugInfo());
-
- CardinalPositionRangeAllocator cardinalRangeCache =
- PositionRangeAllocator.createCardinalPositionRangeAllocator();
- NonCardinalPositionRangeAllocator nonCardinalRangeCache =
- PositionRangeAllocator.createNonCardinalPositionRangeAllocator();
-
- // Collect which files contain which classes that need to have their line numbers optimized.
- for (DexProgramClass clazz : appView.appInfo().classes()) {
- boolean isSyntheticClass = appView.getSyntheticItems().isSyntheticClass(clazz);
-
- IdentityHashMap<DexString, List<ProgramMethod>> methodsByRenamedName =
- groupMethodsByRenamedName(appView, clazz);
-
- // At this point we don't know if we really need to add this class to the builder.
- // It depends on whether any methods/fields are renamed or some methods contain positions.
- // Create a supplier which creates a new, cached ClassNaming.Builder on-demand.
- DexType originalType = appView.graphLens().getOriginalType(clazz.type);
- DexString renamedDescriptor = appView.getNamingLens().lookupDescriptor(clazz.getType());
- LazyBox<ClassNaming.Builder> onDemandClassNamingBuilder =
- new LazyBox<>(
- () ->
- classNameMapperBuilder.classNamingBuilder(
- DescriptorUtils.descriptorToJavaType(renamedDescriptor.toString()),
- originalType.toSourceString(),
- com.android.tools.r8.position.Position.UNKNOWN));
-
- // Check if source file should be added to the map
- DexString originalSourceFile = originalSourceFiles.getOriginalSourceFile(clazz);
- if (originalSourceFile != null) {
- String sourceFile = originalSourceFile.toString();
- if (!RetraceUtils.hasPredictableSourceFileName(clazz.toSourceString(), sourceFile)) {
- onDemandClassNamingBuilder
- .computeIfAbsent()
- .addMappingInformation(FileNameInformation.build(sourceFile), Unreachable::raise);
- }
- }
-
- if (isSyntheticClass) {
- onDemandClassNamingBuilder
- .computeIfAbsent()
- .addMappingInformation(
- CompilerSynthesizedMappingInformation.builder().build(), Unreachable::raise);
- }
-
- // If the class is renamed add it to the classNamingBuilder.
- addClassToClassNaming(originalType, renamedDescriptor, onDemandClassNamingBuilder);
-
- // First transfer renamed fields to classNamingBuilder.
- addFieldsToClassNaming(appView, clazz, originalType, onDemandClassNamingBuilder);
-
- // Then process the methods, ordered by renamed name.
- List<DexString> renamedMethodNames = new ArrayList<>(methodsByRenamedName.keySet());
- renamedMethodNames.sort(DexString::compareTo);
- for (DexString methodName : renamedMethodNames) {
- List<ProgramMethod> methods = methodsByRenamedName.get(methodName);
- if (methods.size() > 1) {
- // If there are multiple methods with the same name (overloaded) then sort them for
- // deterministic behaviour: the algorithm will assign new line numbers in this order.
- // Methods with different names can share the same line numbers, that's why they don't
- // need to be sorted.
- // If we are compiling to DEX we will try to not generate overloaded names. This saves
- // space by allowing more debug-information to be canonicalized. If we have overloaded
- // methods, we either did not rename them, we renamed them according to a supplied map or
- // they may be bridges for interface methods with covariant return types.
- sortMethods(methods);
- assert verifyMethodsAreKeptDirectlyOrIndirectly(appView, methods);
- }
-
- boolean identityMapping =
- appView.options().lineNumberOptimization == LineNumberOptimization.OFF;
- PositionRemapper positionRemapper =
- identityMapping
- ? new IdentityPositionRemapper()
- : new OptimizingPositionRemapper(appView.options());
-
- // Kotlin inline functions and arguments have their inlining information stored in the
- // source debug extension annotation. Instantiate the kotlin remapper on top of the original
- // remapper to allow for remapping original positions to kotlin inline positions.
- KotlinInlineFunctionPositionRemapper kotlinRemapper =
- new KotlinInlineFunctionPositionRemapper(
- appView, positionRemapper, cfLineToMethodMapper);
-
- for (ProgramMethod method : methods) {
- DexEncodedMethod definition = method.getDefinition();
- kotlinRemapper.currentMethod = definition;
- List<MappedPosition> mappedPositions;
- Code code = definition.getCode();
- int pcEncodingCutoff =
- methods.size() == 1 ? representation.getDexPcEncodingCutoff(method) : -1;
- boolean canUseDexPc = pcEncodingCutoff > 0;
- if (code != null) {
- if (code.isDexCode()
- && mustHaveResidualDebugInfo(code.asDexCode(), appView.options())) {
- if (canUseDexPc) {
- mappedPositions =
- optimizeDexCodePositionsForPc(
- method, pcEncodingCutoff, appView, kotlinRemapper, pcBasedDebugInfo);
- } else {
- mappedPositions =
- optimizeDexCodePositions(
- definition, appView, kotlinRemapper, identityMapping, methods.size() != 1);
- }
- } else if (code.isCfCode()
- && mustHaveResidualDebugInfo(code.asCfCode())
- && !appView.isCfByteCodePassThrough(definition)) {
- mappedPositions = optimizeCfCodePositions(method, kotlinRemapper, appView);
- } else {
- mappedPositions = new ArrayList<>();
- }
- } else {
- mappedPositions = new ArrayList<>();
- }
-
- DexMethod originalMethod =
- appView.graphLens().getOriginalMethodSignature(method.getReference());
- MethodSignature originalSignature =
- MethodSignature.fromDexMethod(originalMethod, originalMethod.holder != originalType);
-
- List<MappingInformation> methodMappingInfo = new ArrayList<>();
- if (definition.isD8R8Synthesized()) {
- methodMappingInfo.add(CompilerSynthesizedMappingInformation.builder().build());
- }
-
- DexMethod residualMethod =
- appView.getNamingLens().lookupMethod(method.getReference(), appView.dexItemFactory());
-
- MapVersion mapFileVersion = appView.options().getMapFileVersion();
- if (isIdentityMapping(
- mapFileVersion,
- mappedPositions,
- methodMappingInfo,
- method,
- residualMethod.getName(),
- originalMethod,
- originalType)) {
- assert appView.options().lineNumberOptimization == LineNumberOptimization.OFF
- || hasAtMostOnePosition(definition, appView.options())
- || appView.isCfByteCodePassThrough(definition);
- continue;
- }
- // TODO(b/169953605): Ensure we emit the residual signature information.
- if (mapFileVersion.isGreaterThan(MapVersion.MAP_VERSION_2_1)
- && originalMethod != method.getReference()) {
- methodMappingInfo.add(
- ResidualMethodSignatureMappingInformation.fromDexMethod(residualMethod));
- }
-
- MethodSignature residualSignature = MethodSignature.fromDexMethod(residualMethod);
-
- MemberNaming memberNaming = new MemberNaming(originalSignature, residualSignature);
- onDemandClassNamingBuilder.computeIfAbsent().addMemberEntry(memberNaming);
-
- // Add simple "a() -> b" mapping if we won't have any other with concrete line numbers
- if (mappedPositions.isEmpty()) {
- MappedRange range =
- onDemandClassNamingBuilder
- .computeIfAbsent()
- .addMappedRange(null, originalSignature, null, residualSignature);
- methodMappingInfo.forEach(
- info -> range.addMappingInformation(info, Unreachable::raise));
- continue;
- }
-
- Map<DexMethod, MethodSignature> signatures = new IdentityHashMap<>();
- signatures.put(originalMethod, originalSignature);
- Function<DexMethod, MethodSignature> getOriginalMethodSignature =
- m ->
- signatures.computeIfAbsent(
- m, key -> MethodSignature.fromDexMethod(m, m.holder != clazz.getType()));
-
- // Check if mapped position is an outline
- DexMethod outlineMethod = getOutlineMethod(mappedPositions.get(0));
- if (outlineMethod != null) {
- outlinesToFix
- .computeIfAbsent(
- outlineMethod,
- outline -> new OutlineFixupBuilder(computeMappedMethod(outline, appView)))
- .setMappedPositionsOutline(mappedPositions);
- methodMappingInfo.add(OutlineMappingInformation.builder().build());
- }
-
- // Update memberNaming with the collected positions, merging multiple positions into a
- // single region whenever possible.
- for (int i = 0; i < mappedPositions.size(); /* updated in body */ ) {
- MappedPosition firstPosition = mappedPositions.get(i);
- int j = i + 1;
- MappedPosition lastPosition = firstPosition;
- MappedPositionRange mappedPositionRange = MappedPositionRange.SINGLE_LINE;
- for (; j < mappedPositions.size(); j++) {
- // Break if this position cannot be merged with lastPosition.
- MappedPosition currentPosition = mappedPositions.get(j);
- mappedPositionRange =
- mappedPositionRange.canAddNextMappingToRange(lastPosition, currentPosition);
- // Note that currentPosition.caller and lastPosition.class must be deep-compared since
- // multiple inlining passes lose the canonical property of the positions.
- if (currentPosition.method != lastPosition.method
- || mappedPositionRange.isOutOfRange()
- || !Objects.equals(currentPosition.caller, lastPosition.caller)
- // Break when we see a mapped outline
- || currentPosition.outlineCallee != null
- // Ensure that we break when we start iterating with an outline caller again.
- || firstPosition.outlineCallee != null) {
- break;
- }
- // The mapped positions are not guaranteed to be in order, so maintain first and last
- // position.
- if (firstPosition.obfuscatedLine > currentPosition.obfuscatedLine) {
- firstPosition = currentPosition;
- }
- if (lastPosition.obfuscatedLine < currentPosition.obfuscatedLine) {
- lastPosition = currentPosition;
- }
- }
- Range obfuscatedRange;
- if (definition.getCode().isDexCode()
- && definition.getCode().asDexCode().getDebugInfo()
- == DexDebugInfoForSingleLineMethod.getInstance()) {
- assert firstPosition.originalLine == lastPosition.originalLine;
- obfuscatedRange = nonCardinalRangeCache.get(0, MAX_LINE_NUMBER);
- } else {
- obfuscatedRange =
- nonCardinalRangeCache.get(
- firstPosition.obfuscatedLine, lastPosition.obfuscatedLine);
- }
- ClassNaming.Builder classNamingBuilder = onDemandClassNamingBuilder.computeIfAbsent();
- MappedRange lastMappedRange =
- getMappedRangesForPosition(
- appView,
- getOriginalMethodSignature,
- classNamingBuilder,
- firstPosition.method,
- residualSignature,
- obfuscatedRange,
- nonCardinalRangeCache.get(
- firstPosition.originalLine, lastPosition.originalLine),
- firstPosition.caller,
- prunedInlinedClasses,
- cardinalRangeCache);
- for (MappingInformation info : methodMappingInfo) {
- lastMappedRange.addMappingInformation(info, Unreachable::raise);
- }
- // firstPosition will contain a potential outline caller.
- if (firstPosition.outlineCallee != null) {
- Int2IntMap positionMap = new Int2IntArrayMap();
- int maxPc = ListUtils.last(mappedPositions).obfuscatedLine;
- firstPosition.outlinePositions.forEach(
- (line, position) -> {
- int placeHolderLineToBeFixed;
- if (canUseDexPc) {
- placeHolderLineToBeFixed = maxPc + line + 1;
- } else {
- placeHolderLineToBeFixed =
- positionRemapper.createRemappedPosition(position).getSecond().getLine();
- }
- positionMap.put((int) line, placeHolderLineToBeFixed);
- getMappedRangesForPosition(
- appView,
- getOriginalMethodSignature,
- classNamingBuilder,
- position.getMethod(),
- residualSignature,
- nonCardinalRangeCache.get(
- placeHolderLineToBeFixed, placeHolderLineToBeFixed),
- nonCardinalRangeCache.get(position.getLine(), position.getLine()),
- position.getCallerPosition(),
- prunedInlinedClasses,
- cardinalRangeCache);
- });
- outlinesToFix
- .computeIfAbsent(
- firstPosition.outlineCallee,
- outline -> new OutlineFixupBuilder(computeMappedMethod(outline, appView)))
- .addMappedRangeForOutlineCallee(lastMappedRange, positionMap);
- }
- i = j;
- }
- if (definition.getCode().isDexCode()
- && definition.getCode().asDexCode().getDebugInfo()
- == DexDebugInfoForSingleLineMethod.getInstance()) {
- pcBasedDebugInfo.recordSingleLineFor(method, pcEncodingCutoff);
- }
- } // for each method of the group
- } // for each method group, grouped by name
- } // for each class
-
- // Fixup all outline positions
- outlinesToFix.values().forEach(OutlineFixupBuilder::fixup);
-
- // Update all the debug-info objects.
- pcBasedDebugInfo.updateDebugInfoInCodeObjects();
-
- // Add all pruned inline classes to the mapping to recover source files.
- List<Entry<DexType, String>> prunedEntries = new ArrayList<>(prunedInlinedClasses.entrySet());
- prunedEntries.sort(Entry.comparingByKey());
- prunedEntries.forEach(
- entry -> {
- DexType holder = entry.getKey();
- assert appView.appInfo().definitionForWithoutExistenceAssert(holder) == null;
- String typeName = holder.toSourceString();
- String sourceFile = entry.getValue();
- assert !RetraceUtils.hasPredictableSourceFileName(typeName, sourceFile);
- classNameMapperBuilder
- .classNamingBuilder(
- typeName, typeName, com.android.tools.r8.position.Position.UNKNOWN)
- .addMappingInformation(FileNameInformation.build(sourceFile), Unreachable::raise);
- });
-
- return classNameMapperBuilder.build();
- }
-
- private static boolean isIdentityMapping(
- MapVersion mapFileVersion,
- List<MappedPosition> mappedPositions,
- List<MappingInformation> methodMappingInfo,
- ProgramMethod method,
- DexString obfuscatedNameDexString,
- DexMethod originalMethod,
- DexType originalType) {
- if (mapFileVersion.isGreaterThan(MapVersion.MAP_VERSION_2_1)) {
- // Don't emit pure identity mappings.
- return mappedPositions.isEmpty()
- && methodMappingInfo.isEmpty()
- && originalMethod == method.getReference();
- } else {
- // Don't emit pure identity mappings.
- return mappedPositions.isEmpty()
- && methodMappingInfo.isEmpty()
- && obfuscatedNameDexString == originalMethod.name
- && originalMethod.holder == originalType;
- }
- }
-
- private static boolean hasAtMostOnePosition(
- DexEncodedMethod definition, InternalOptions options) {
- if (!mustHaveResidualDebugInfo(definition, options)) {
- return true;
- }
- Code code = definition.getCode();
- if (code.isDexCode() && code.asDexCode().instructions.length == 1) {
- // If the dex code is a single PC code then that also qualifies as having at most one
- // position.
- return true;
- }
- return false;
- }
-
- private static MethodReference computeMappedMethod(DexMethod current, AppView<?> appView) {
- NamingLens namingLens = appView.getNamingLens();
- DexMethod renamedMethodSignature =
- namingLens.lookupMethod(
- appView.graphLens().getRenamedMethodSignature(current), appView.dexItemFactory());
- return renamedMethodSignature.asMethodReference();
- }
-
- private static DexMethod getOutlineMethod(MappedPosition mappedPosition) {
- if (mappedPosition.isOutline) {
- return mappedPosition.method;
- }
- if (mappedPosition.caller == null) {
- return null;
- }
- Position outermostCaller = mappedPosition.caller.getOutermostCaller();
- return outermostCaller.isOutline() ? outermostCaller.getMethod() : null;
- }
-
- private static MappedRange getMappedRangesForPosition(
- AppView<?> appView,
- Function<DexMethod, MethodSignature> getOriginalMethodSignature,
- Builder classNamingBuilder,
- DexMethod method,
- MethodSignature residualSignature,
- Range obfuscatedRange,
- Range originalLine,
- Position caller,
- Map<DexType, String> prunedInlineHolder,
- CardinalPositionRangeAllocator cardinalRangeCache) {
- MappedRange lastMappedRange =
- classNamingBuilder.addMappedRange(
- obfuscatedRange,
- getOriginalMethodSignature.apply(method),
- originalLine,
- residualSignature);
- int inlineFramesCount = 0;
- while (caller != null) {
- inlineFramesCount += 1;
- String prunedClassSourceFileInfo =
- appView.getPrunedClassSourceFileInfo(method.getHolderType());
- if (prunedClassSourceFileInfo != null) {
- String originalValue =
- prunedInlineHolder.put(method.getHolderType(), prunedClassSourceFileInfo);
- assert originalValue == null || originalValue.equals(prunedClassSourceFileInfo);
- }
- lastMappedRange =
- classNamingBuilder.addMappedRange(
- obfuscatedRange,
- getOriginalMethodSignature.apply(caller.getMethod()),
- cardinalRangeCache.get(
- Math.max(caller.getLine(), 0)), // Prevent against "no-position".
- residualSignature);
- if (caller.isRemoveInnerFramesIfThrowingNpe()) {
- lastMappedRange.addMappingInformation(
- RewriteFrameMappingInformation.builder()
- .addCondition(
- ThrowsCondition.create(
- Reference.classFromDescriptor(
- appView.dexItemFactory().npeDescriptor.toString())))
- .addRewriteAction(RemoveInnerFramesAction.create(inlineFramesCount))
- .build(),
- Unreachable::raise);
- }
- caller = caller.getCallerPosition();
- }
- return lastMappedRange;
- }
-
- private static boolean verifyMethodsAreKeptDirectlyOrIndirectly(
- AppView<?> appView, List<ProgramMethod> methods) {
- if (appView.options().isGeneratingClassFiles() || !appView.appInfo().hasClassHierarchy()) {
- return true;
- }
- AppInfoWithClassHierarchy appInfo = appView.appInfo().withClassHierarchy();
- KeepInfoCollection keepInfo = appView.getKeepInfo();
- boolean allSeenAreInstanceInitializers = true;
- DexString originalName = null;
- for (ProgramMethod method : methods) {
- // We cannot rename instance initializers.
- if (method.getDefinition().isInstanceInitializer()) {
- assert allSeenAreInstanceInitializers;
- continue;
- }
- allSeenAreInstanceInitializers = false;
- // If the method is pinned, we cannot minify it.
- if (!keepInfo.isMinificationAllowed(method.getReference(), appView, appView.options())) {
- continue;
- }
- // With desugared library, call-backs names are reserved here.
- if (method.getDefinition().isLibraryMethodOverride().isTrue()) {
- continue;
- }
- // We use the same name for interface names even if it has different types.
- DexProgramClass clazz = appView.definitionForProgramType(method.getHolderType());
- DexClassAndMethod lookupResult =
- appInfo.lookupMaximallySpecificMethod(clazz, method.getReference());
- if (lookupResult == null) {
- // We cannot rename methods we cannot look up.
- continue;
- }
- String errorString = method.getReference().qualifiedName() + " is not kept but is overloaded";
- assert lookupResult.getHolder().isInterface() : errorString;
- // TODO(b/159113601): Reenable assert.
- assert true || originalName == null || originalName.equals(method.getReference().name)
- : errorString;
- originalName = method.getReference().name;
- }
- return true;
- }
-
- private static int getMethodStartLine(ProgramMethod method) {
- Code code = method.getDefinition().getCode();
- if (code == null) {
- return 0;
- }
- if (code.isDexCode()) {
- DexDebugInfo dexDebugInfo = code.asDexCode().getDebugInfo();
- return dexDebugInfo == null ? 0 : dexDebugInfo.getStartLine();
- } else if (code.isCfCode()) {
- List<CfInstruction> instructions = code.asCfCode().getInstructions();
- for (CfInstruction instruction : instructions) {
- if (!(instruction instanceof CfPosition)) {
- continue;
- }
- return ((CfPosition) instruction).getPosition().getLine();
- }
- }
- return 0;
- }
-
- // Sort by startline, then DexEncodedMethod.slowCompare.
- // Use startLine = 0 if no debuginfo.
- private static void sortMethods(List<ProgramMethod> methods) {
- methods.sort(
- (lhs, rhs) -> {
- int lhsStartLine = getMethodStartLine(lhs);
- int rhsStartLine = getMethodStartLine(rhs);
- int startLineDiff = lhsStartLine - rhsStartLine;
- if (startLineDiff != 0) return startLineDiff;
- return DexEncodedMethod.slowCompare(lhs.getDefinition(), rhs.getDefinition());
- });
- }
-
- @SuppressWarnings("ReturnValueIgnored")
- private static void addClassToClassNaming(
- DexType originalType,
- DexString renamedClassName,
- LazyBox<Builder> onDemandClassNamingBuilder) {
- // We do know we need to create a ClassNaming.Builder if the class itself had been renamed.
- if (originalType.descriptor != renamedClassName) {
- // Not using return value, it's registered in classNameMapperBuilder
- onDemandClassNamingBuilder.computeIfAbsent();
- }
- }
-
- private static void addFieldsToClassNaming(
- AppView<?> appView,
- DexProgramClass clazz,
- DexType originalType,
- LazyBox<Builder> onDemandClassNamingBuilder) {
- clazz.forEachField(
- dexEncodedField -> {
- DexField dexField = dexEncodedField.getReference();
- DexField originalField = appView.graphLens().getOriginalFieldSignature(dexField);
- DexField residualField =
- appView.getNamingLens().lookupField(dexField, appView.dexItemFactory());
- if (residualField.name != originalField.name || originalField.holder != originalType) {
- FieldSignature originalSignature =
- FieldSignature.fromDexField(originalField, originalField.holder != originalType);
- MemberNaming memberNaming =
- new MemberNaming(originalSignature, FieldSignature.fromDexField(residualField));
- onDemandClassNamingBuilder.computeIfAbsent().addMemberEntry(memberNaming);
- }
- });
- }
-
- public static IdentityHashMap<DexString, List<ProgramMethod>> groupMethodsByRenamedName(
- AppView<?> appView, DexProgramClass clazz) {
- IdentityHashMap<DexString, List<ProgramMethod>> methodsByRenamedName =
- new IdentityHashMap<>(clazz.getMethodCollection().size());
- for (ProgramMethod programMethod : clazz.programMethods()) {
- // Add method only if renamed, moved, or if it has debug info to map.
- DexEncodedMethod definition = programMethod.getDefinition();
- DexMethod method = programMethod.getReference();
- DexString renamedName = appView.getNamingLens().lookupName(method);
- if (renamedName != method.name
- || appView.graphLens().getOriginalMethodSignature(method) != method
- || mustHaveResidualDebugInfo(definition, appView.options())
- || definition.isD8R8Synthesized()) {
- methodsByRenamedName
- .computeIfAbsent(renamedName, key -> new ArrayList<>())
- .add(programMethod);
- }
- }
- return methodsByRenamedName;
- }
-
- private static boolean mustHaveResidualDebugInfo(
- DexEncodedMethod method, InternalOptions options) {
- Code code = method.getCode();
- if (code == null) {
- return false;
- }
- if (code.isDexCode()) {
- return mustHaveResidualDebugInfo(code.asDexCode(), options);
- } else if (code.isCfCode()) {
- return mustHaveResidualDebugInfo(code.asCfCode());
- }
- return false;
- }
-
- public static boolean mustHaveResidualDebugInfo(DexCode dexCode, InternalOptions options) {
- // All code objects must have debug info if discarding it is not allowed.
- if (!options.allowDiscardingResidualDebugInfo()) {
- return true;
- }
- // Otherwise debug info is only needed for code sequences with at least one position.
- DexDebugInfo debugInfo = dexCode.getDebugInfo();
- if (debugInfo == null) {
- return false;
- }
- if (debugInfo.isPcBasedInfo()) {
- return true;
- }
- for (DexDebugEvent event : debugInfo.asEventBasedInfo().events) {
- if (event instanceof DexDebugEvent.Default) {
- return true;
- }
- }
- return false;
- }
-
- private static boolean mustHaveResidualDebugInfo(CfCode cfCode) {
- List<CfInstruction> instructions = cfCode.getInstructions();
- for (CfInstruction instruction : instructions) {
- if (instruction instanceof CfPosition) {
- return true;
- }
- }
- return false;
- }
-
- private static List<MappedPosition> optimizeDexCodePositions(
- DexEncodedMethod method,
- AppView<?> appView,
- PositionRemapper positionRemapper,
- boolean identityMapping,
- boolean hasOverloads) {
- List<MappedPosition> mappedPositions = new ArrayList<>();
- // Do the actual processing for each method.
- DexApplication application = appView.appInfo().app();
- DexCode dexCode = method.getCode().asDexCode();
- EventBasedDebugInfo debugInfo = getEventBasedDebugInfo(method, dexCode, appView);
-
- List<DexDebugEvent> processedEvents = new ArrayList<>();
-
- PositionEventEmitter positionEventEmitter =
- new PositionEventEmitter(
- application.dexItemFactory,
- appView.graphLens().getOriginalMethodSignature(method.getReference()),
- processedEvents);
-
- Box<Boolean> inlinedOriginalPosition = new Box<>(false);
-
- // Debug event visitor to map line numbers.
- DexDebugPositionState visitor =
- new DexDebugPositionState(
- debugInfo.startLine,
- appView.graphLens().getOriginalMethodSignature(method.getReference())) {
-
- // Keep track of what PC has been emitted.
- private int emittedPc = 0;
-
- // Force the current PC to emitted.
- private void flushPc() {
- if (emittedPc != getCurrentPc()) {
- positionEventEmitter.emitAdvancePc(getCurrentPc());
- emittedPc = getCurrentPc();
- }
- }
-
- // A default event denotes a line table entry and must always be emitted. Remap its line.
- @Override
- public void visit(Default defaultEvent) {
- super.visit(defaultEvent);
- assert getCurrentLine() >= 0;
- Position position = getPositionFromPositionState(this);
- Position currentPosition = remapAndAdd(position, positionRemapper, mappedPositions);
- positionEventEmitter.emitPositionEvents(getCurrentPc(), currentPosition);
- if (currentPosition != position) {
- inlinedOriginalPosition.set(true);
- }
- emittedPc = getCurrentPc();
- resetOutlineInformation();
- }
-
- // Non-materializing events use super, ie, AdvancePC, AdvanceLine and SetInlineFrame.
-
- // Materializing events are just amended to the stream.
-
- @Override
- public void visit(SetFile setFile) {
- processedEvents.add(setFile);
- }
-
- @Override
- public void visit(SetPrologueEnd setPrologueEnd) {
- processedEvents.add(setPrologueEnd);
- }
-
- @Override
- public void visit(SetEpilogueBegin setEpilogueBegin) {
- processedEvents.add(setEpilogueBegin);
- }
-
- // Local changes must force flush the PC ensuing they pertain to the correct point.
-
- @Override
- public void visit(StartLocal startLocal) {
- flushPc();
- processedEvents.add(startLocal);
- }
-
- @Override
- public void visit(EndLocal endLocal) {
- flushPc();
- processedEvents.add(endLocal);
- }
-
- @Override
- public void visit(RestartLocal restartLocal) {
- flushPc();
- processedEvents.add(restartLocal);
- }
- };
-
- for (DexDebugEvent event : debugInfo.events) {
- event.accept(visitor);
- }
-
- // If we only have one line event we can always retrace back uniquely.
- if (mappedPositions.size() <= 1
- && !hasOverloads
- && !appView.options().debug
- && appView.options().lineNumberOptimization != LineNumberOptimization.OFF
- && appView.options().allowDiscardingResidualDebugInfo()
- && (mappedPositions.isEmpty() || !mappedPositions.get(0).isOutlineCaller())) {
- dexCode.setDebugInfo(DexDebugInfoForSingleLineMethod.getInstance());
- return mappedPositions;
- }
-
- EventBasedDebugInfo optimizedDebugInfo =
- new EventBasedDebugInfo(
- positionEventEmitter.getStartLine(),
- debugInfo.parameters,
- processedEvents.toArray(DexDebugEvent.EMPTY_ARRAY));
-
- assert !identityMapping
- || inlinedOriginalPosition.get()
- || verifyIdentityMapping(debugInfo, optimizedDebugInfo);
-
- dexCode.setDebugInfo(optimizedDebugInfo);
- return mappedPositions;
- }
-
- // This conversion *always* creates an event based debug info encoding as any non-info will
- // be created as an implicit PC encoding.
- private static EventBasedDebugInfo getEventBasedDebugInfo(
- DexEncodedMethod method, DexCode dexCode, AppView<?> appView) {
- // TODO(b/213411850): Do we need to reconsider conversion here to support pc-based D8 merging?
- if (dexCode.getDebugInfo() == null) {
- return createEventBasedInfoForMethodWithoutDebugInfo(method, appView.dexItemFactory());
- }
- assert method.getParameters().size() == dexCode.getDebugInfo().getParameterCount();
- EventBasedDebugInfo debugInfo =
- DexDebugInfo.convertToEventBased(dexCode, appView.dexItemFactory());
- assert debugInfo != null;
- return debugInfo;
- }
-
- public static EventBasedDebugInfo createEventBasedInfoForMethodWithoutDebugInfo(
- DexEncodedMethod method, DexItemFactory factory) {
- return new EventBasedDebugInfo(
- 0,
- new DexString[method.getParameters().size()],
- new DexDebugEvent[] {factory.zeroChangeDefaultEvent});
- }
-
- private static Position getPositionFromPositionState(DexDebugPositionState state) {
- PositionBuilder<?, ?> positionBuilder;
- if (state.getOutlineCallee() != null) {
- OutlineCallerPositionBuilder outlineCallerPositionBuilder =
- OutlineCallerPosition.builder()
- .setOutlineCallee(state.getOutlineCallee())
- .setIsOutline(state.isOutline());
- state.getOutlineCallerPositions().forEach(outlineCallerPositionBuilder::addOutlinePosition);
- positionBuilder = outlineCallerPositionBuilder;
- } else if (state.isOutline()) {
- positionBuilder = OutlinePosition.builder();
- } else {
- positionBuilder = SourcePosition.builder().setFile(state.getCurrentFile());
- }
- return positionBuilder
- .setLine(state.getCurrentLine())
- .setMethod(state.getCurrentMethod())
- .setCallerPosition(state.getCurrentCallerPosition())
- .build();
- }
-
- private static List<MappedPosition> optimizeDexCodePositionsForPc(
- ProgramMethod method,
- int pcEncodingCutoff,
- AppView<?> appView,
- PositionRemapper positionRemapper,
- PcBasedDebugInfoRecorder debugInfoProvider) {
- List<MappedPosition> mappedPositions = new ArrayList<>();
- // Do the actual processing for each method.
- DexCode dexCode = method.getDefinition().getCode().asDexCode();
- EventBasedDebugInfo debugInfo =
- getEventBasedDebugInfo(method.getDefinition(), dexCode, appView);
- IntBox firstDefaultEventPc = new IntBox(-1);
- BooleanBox singleOriginalLine = new BooleanBox(true);
- Pair<Integer, Position> lastPosition = new Pair<>();
- DexDebugEventVisitor visitor =
- new DexDebugPositionState(
- debugInfo.startLine,
- appView.graphLens().getOriginalMethodSignature(method.getReference())) {
- @Override
- public void visit(Default defaultEvent) {
- super.visit(defaultEvent);
- assert getCurrentLine() >= 0;
- if (firstDefaultEventPc.get() < 0) {
- firstDefaultEventPc.set(getCurrentPc());
- }
- Position currentPosition = getPositionFromPositionState(this);
- if (lastPosition.getSecond() != null) {
- if (singleOriginalLine.isTrue()
- && !currentPosition.equals(lastPosition.getSecond())) {
- singleOriginalLine.set(false);
- }
- remapAndAddForPc(
- debugInfoProvider,
- lastPosition.getFirst(),
- getCurrentPc(),
- lastPosition.getSecond(),
- positionRemapper,
- mappedPositions);
- }
- lastPosition.setFirst(getCurrentPc());
- lastPosition.setSecond(currentPosition);
- resetOutlineInformation();
- }
- };
-
- for (DexDebugEvent event : debugInfo.events) {
- event.accept(visitor);
- }
-
- // If the method has a single non-preamble line, check that the preamble is not active on any
- // throwing instruction before the single line becomes active.
- if (singleOriginalLine.isTrue() && firstDefaultEventPc.get() > 0) {
- for (DexInstruction instruction : dexCode.instructions) {
- if (instruction.getOffset() < firstDefaultEventPc.get()) {
- if (instruction.canThrow()) {
- singleOriginalLine.set(false);
- }
- } else {
- break;
- }
- }
- }
-
- int lastInstructionPc = DebugRepresentation.getLastExecutableInstruction(dexCode).getOffset();
- if (lastPosition.getSecond() != null) {
- remapAndAddForPc(
- debugInfoProvider,
- lastPosition.getFirst(),
- lastInstructionPc + 1,
- lastPosition.getSecond(),
- positionRemapper,
- mappedPositions);
- }
-
- assert !mappedPositions.isEmpty() || dexCode.instructions.length == 1;
- if (singleOriginalLine.isTrue()
- && lastPosition.getSecond() != null
- && (mappedPositions.isEmpty() || !mappedPositions.get(0).isOutlineCaller())) {
- dexCode.setDebugInfo(DexDebugInfoForSingleLineMethod.getInstance());
- debugInfoProvider.recordSingleLineFor(method, pcEncodingCutoff);
- } else {
- debugInfoProvider.recordPcMappingFor(method, pcEncodingCutoff);
- }
- return mappedPositions;
- }
-
- private static boolean verifyIdentityMapping(
- EventBasedDebugInfo originalDebugInfo, EventBasedDebugInfo optimizedDebugInfo) {
- assert optimizedDebugInfo.startLine == originalDebugInfo.startLine;
- assert optimizedDebugInfo.events.length == originalDebugInfo.events.length;
- for (int i = 0; i < originalDebugInfo.events.length; ++i) {
- assert optimizedDebugInfo.events[i].equals(originalDebugInfo.events[i]);
- }
- return true;
- }
-
- private static List<MappedPosition> optimizeCfCodePositions(
- ProgramMethod method, PositionRemapper positionRemapper, AppView<?> appView) {
- List<MappedPosition> mappedPositions = new ArrayList<>();
- // Do the actual processing for each method.
- CfCode oldCode = method.getDefinition().getCode().asCfCode();
- List<CfInstruction> oldInstructions = oldCode.getInstructions();
- List<CfInstruction> newInstructions = new ArrayList<>(oldInstructions.size());
- for (CfInstruction oldInstruction : oldInstructions) {
- CfInstruction newInstruction;
- if (oldInstruction instanceof CfPosition) {
- CfPosition cfPosition = (CfPosition) oldInstruction;
- newInstruction =
- new CfPosition(
- cfPosition.getLabel(),
- remapAndAdd(cfPosition.getPosition(), positionRemapper, mappedPositions));
- } else {
- newInstruction = oldInstruction;
- }
- newInstructions.add(newInstruction);
- }
- method.setCode(
- new CfCode(
- method.getHolderType(),
- oldCode.getMaxStack(),
- oldCode.getMaxLocals(),
- newInstructions,
- oldCode.getTryCatchRanges(),
- oldCode.getLocalVariables()),
- appView);
- return mappedPositions;
- }
-
- private static Position remapAndAdd(
- Position position, PositionRemapper remapper, List<MappedPosition> mappedPositions) {
- Pair<Position, Position> remappedPosition = remapper.createRemappedPosition(position);
- Position oldPosition = remappedPosition.getFirst();
- Position newPosition = remappedPosition.getSecond();
- mappedPositions.add(
- new MappedPosition(
- oldPosition.getMethod(),
- oldPosition.getLine(),
- oldPosition.getCallerPosition(),
- newPosition.getLine(),
- oldPosition.isOutline(),
- oldPosition.getOutlineCallee(),
- oldPosition.getOutlinePositions()));
- return newPosition;
- }
-
- private static void remapAndAddForPc(
- PcBasedDebugInfoRecorder debugInfoProvider,
- int startPc,
- int endPc,
- Position position,
- PositionRemapper remapper,
- List<MappedPosition> mappedPositions) {
- Pair<Position, Position> remappedPosition = remapper.createRemappedPosition(position);
- Position oldPosition = remappedPosition.getFirst();
- for (int currentPc = startPc; currentPc < endPc; currentPc++) {
- boolean firstEntry = currentPc == startPc;
- mappedPositions.add(
- new MappedPosition(
- oldPosition.getMethod(),
- oldPosition.getLine(),
- oldPosition.getCallerPosition(),
- debugInfoProvider.getPcEncoding(currentPc),
- // Outline info is placed exactly on the positions that relate to it so we should
- // only emit it for the first entry.
- firstEntry && oldPosition.isOutline(),
- firstEntry ? oldPosition.getOutlineCallee() : null,
- firstEntry ? oldPosition.getOutlinePositions() : null));
- }
- }
-
- private enum MappedPositionRange {
- // Single line represent a mapping on the form X:X:<method>:Y:Y.
- SINGLE_LINE,
- // Range to single line allows for a range on the left hand side, X:X':<method>:Y:Y
- RANGE_TO_SINGLE,
- // Same delta is when we have a range on both sides and the delta (line mapping between them)
- // is the same: X:X':<method>:Y:Y' and delta(X,X') = delta(Y,Y')
- SAME_DELTA,
- // Out of range encodes a mapping range that cannot be encoded.
- OUT_OF_RANGE;
-
- private boolean isSingleLine() {
- return this == SINGLE_LINE;
- }
-
- private boolean isRangeToSingle() {
- return this == RANGE_TO_SINGLE;
- }
-
- private boolean isOutOfRange() {
- return this == OUT_OF_RANGE;
- }
-
- public MappedPositionRange canAddNextMappingToRange(
- MappedPosition lastPosition, MappedPosition currentPosition) {
- if (isOutOfRange()) {
- return this;
- }
- // We allow for ranges being mapped to the same line but not to other ranges:
- // 1:10:void foo():42:42 -> a
- // is OK since retrace(a(:7)) = 42, however, the following is not OK:
- // 1:10:void foo():42:43 -> a
- // since retrace(a(:7)) = 49, which is not correct.
- boolean hasSameRightHandSide = lastPosition.originalLine == currentPosition.originalLine;
- if (hasSameRightHandSide) {
- boolean hasSameLeftHandSide = lastPosition.obfuscatedLine == currentPosition.obfuscatedLine;
- return hasSameLeftHandSide ? SINGLE_LINE : RANGE_TO_SINGLE;
- }
- if (isRangeToSingle()) {
- // We cannot recover a delta encoding if we have had range to single encoding.
- return OUT_OF_RANGE;
- }
- boolean sameDelta =
- currentPosition.originalLine - lastPosition.originalLine
- == currentPosition.obfuscatedLine - lastPosition.obfuscatedLine;
- return sameDelta ? SAME_DELTA : OUT_OF_RANGE;
- }
- }
-
- private static class OutlineFixupBuilder {
-
- private static final int MINIFIED_POSITION_REMOVED = -1;
-
- private final MethodReference outlineMethod;
- private List<MappedPosition> mappedOutlinePositions = null;
- private final List<Pair<MappedRange, Int2IntMap>> mappedOutlineCalleePositions =
- new ArrayList<>();
-
- private OutlineFixupBuilder(MethodReference outlineMethod) {
- this.outlineMethod = outlineMethod;
- }
-
- public void setMappedPositionsOutline(List<MappedPosition> mappedPositionsOutline) {
- this.mappedOutlinePositions = mappedPositionsOutline;
- }
-
- public void addMappedRangeForOutlineCallee(
- MappedRange mappedRangeForOutline, Int2IntMap calleePositions) {
- mappedOutlineCalleePositions.add(Pair.create(mappedRangeForOutline, calleePositions));
- }
-
- public void fixup() {
- if (mappedOutlinePositions == null || mappedOutlineCalleePositions.isEmpty()) {
- assert mappedOutlinePositions != null : "Mapped outline positions is null";
- assert false : "Mapped outline positions is empty";
- return;
- }
- for (Pair<MappedRange, Int2IntMap> mappingInfo : mappedOutlineCalleePositions) {
- MappedRange mappedRange = mappingInfo.getFirst();
- Int2IntMap positions = mappingInfo.getSecond();
- Int2IntSortedMap map = new Int2IntLinkedOpenHashMap();
- positions.forEach(
- (outlinePosition, calleePosition) -> {
- int minifiedLinePosition =
- getMinifiedLinePosition(outlinePosition, mappedOutlinePositions);
- if (minifiedLinePosition != MINIFIED_POSITION_REMOVED) {
- map.put(minifiedLinePosition, (int) calleePosition);
- }
- });
- mappedRange.addMappingInformation(
- OutlineCallsiteMappingInformation.create(map, outlineMethod), Unreachable::raise);
- }
- }
-
- private int getMinifiedLinePosition(
- int originalPosition, List<MappedPosition> mappedPositions) {
- for (MappedPosition mappedPosition : mappedPositions) {
- if (mappedPosition.originalLine == originalPosition) {
- return mappedPosition.obfuscatedLine;
- }
- }
- return MINIFIED_POSITION_REMOVED;
- }
- }
-}
diff --git a/src/main/java/com/android/tools/r8/utils/positions/ClassFilePositionToMappedRangeMapper.java b/src/main/java/com/android/tools/r8/utils/positions/ClassFilePositionToMappedRangeMapper.java
new file mode 100644
index 0000000..b84e916
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/positions/ClassFilePositionToMappedRangeMapper.java
@@ -0,0 +1,66 @@
+// Copyright (c) 2022, 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.utils.positions;
+
+import static com.android.tools.r8.utils.positions.PositionUtils.remapAndAdd;
+
+import com.android.tools.r8.cf.code.CfInstruction;
+import com.android.tools.r8.cf.code.CfPosition;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.CfCode;
+import com.android.tools.r8.graph.ProgramMethod;
+import java.util.ArrayList;
+import java.util.List;
+
+public class ClassFilePositionToMappedRangeMapper implements PositionToMappedRangeMapper {
+
+ private final AppView<?> appView;
+
+ public ClassFilePositionToMappedRangeMapper(AppView<?> appView) {
+ this.appView = appView;
+ }
+
+ @Override
+ public List<MappedPosition> getMappedPositions(
+ ProgramMethod method,
+ PositionRemapper positionRemapper,
+ boolean hasOverloads,
+ boolean canUseDexPc,
+ int pcEncodingCutoff) {
+ List<MappedPosition> mappedPositions = new ArrayList<>();
+ // Do the actual processing for each method.
+ CfCode oldCode = method.getDefinition().getCode().asCfCode();
+ List<CfInstruction> oldInstructions = oldCode.getInstructions();
+ List<CfInstruction> newInstructions = new ArrayList<>(oldInstructions.size());
+ for (CfInstruction oldInstruction : oldInstructions) {
+ CfInstruction newInstruction;
+ if (oldInstruction instanceof CfPosition) {
+ CfPosition cfPosition = (CfPosition) oldInstruction;
+ newInstruction =
+ new CfPosition(
+ cfPosition.getLabel(),
+ remapAndAdd(cfPosition.getPosition(), positionRemapper, mappedPositions));
+ } else {
+ newInstruction = oldInstruction;
+ }
+ newInstructions.add(newInstruction);
+ }
+ method.setCode(
+ new CfCode(
+ method.getHolderType(),
+ oldCode.getMaxStack(),
+ oldCode.getMaxLocals(),
+ newInstructions,
+ oldCode.getTryCatchRanges(),
+ oldCode.getLocalVariables()),
+ appView);
+ return mappedPositions;
+ }
+
+ @Override
+ public void updateDebugInfoInCodeObjects() {
+ // Intentionally empty.
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/utils/positions/DexPositionToNoPcMappedRangeMapper.java b/src/main/java/com/android/tools/r8/utils/positions/DexPositionToNoPcMappedRangeMapper.java
new file mode 100644
index 0000000..d83d7e0
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/positions/DexPositionToNoPcMappedRangeMapper.java
@@ -0,0 +1,261 @@
+// Copyright (c) 2022, 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.utils.positions;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexApplication;
+import com.android.tools.r8.graph.DexCode;
+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.DexDebugEvent.EndLocal;
+import com.android.tools.r8.graph.DexDebugEvent.RestartLocal;
+import com.android.tools.r8.graph.DexDebugEvent.SetEpilogueBegin;
+import com.android.tools.r8.graph.DexDebugEvent.SetFile;
+import com.android.tools.r8.graph.DexDebugEvent.SetPrologueEnd;
+import com.android.tools.r8.graph.DexDebugEvent.StartLocal;
+import com.android.tools.r8.graph.DexDebugEventBuilder;
+import com.android.tools.r8.graph.DexDebugInfo;
+import com.android.tools.r8.graph.DexDebugInfo.EventBasedDebugInfo;
+import com.android.tools.r8.graph.DexDebugInfoForSingleLineMethod;
+import com.android.tools.r8.graph.DexDebugPositionState;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.code.Position;
+import com.android.tools.r8.ir.code.Position.OutlineCallerPosition;
+import com.android.tools.r8.ir.code.Position.OutlineCallerPosition.OutlineCallerPositionBuilder;
+import com.android.tools.r8.ir.code.Position.OutlinePosition;
+import com.android.tools.r8.ir.code.Position.PositionBuilder;
+import com.android.tools.r8.ir.code.Position.SourcePosition;
+import com.android.tools.r8.utils.Box;
+import com.android.tools.r8.utils.InternalOptions.LineNumberOptimization;
+import java.util.ArrayList;
+import java.util.List;
+
+public class DexPositionToNoPcMappedRangeMapper {
+
+ // PositionEventEmitter is a stateful function which converts a Position into series of
+ // position-related DexDebugEvents and puts them into a processedEvents list.
+ private static class PositionEventEmitter {
+ private final DexItemFactory dexItemFactory;
+ private int startLine = -1;
+ private final DexMethod method;
+ private int previousPc = 0;
+ private Position previousPosition = null;
+ private final List<DexDebugEvent> processedEvents;
+
+ private PositionEventEmitter(
+ DexItemFactory dexItemFactory, DexMethod method, List<DexDebugEvent> processedEvents) {
+ this.dexItemFactory = dexItemFactory;
+ this.method = method;
+ this.processedEvents = processedEvents;
+ }
+
+ private void emitAdvancePc(int pc) {
+ processedEvents.add(new AdvancePC(pc - previousPc));
+ previousPc = pc;
+ }
+
+ private void emitPositionEvents(int currentPc, Position currentPosition) {
+ if (previousPosition == null) {
+ startLine = currentPosition.getLine();
+ previousPosition = SourcePosition.builder().setLine(startLine).setMethod(method).build();
+ }
+ DexDebugEventBuilder.emitAdvancementEvents(
+ previousPc,
+ previousPosition,
+ currentPc,
+ currentPosition,
+ processedEvents,
+ dexItemFactory,
+ true);
+ previousPc = currentPc;
+ previousPosition = currentPosition;
+ }
+
+ private int getStartLine() {
+ assert (startLine >= 0);
+ return startLine;
+ }
+ }
+
+ private final AppView<?> appView;
+ private final boolean isIdentityMapping;
+
+ public DexPositionToNoPcMappedRangeMapper(AppView<?> appView) {
+ this.appView = appView;
+ isIdentityMapping = appView.options().lineNumberOptimization == LineNumberOptimization.OFF;
+ }
+
+ public List<MappedPosition> optimizeDexCodePositions(
+ ProgramMethod method, PositionRemapper positionRemapper, boolean hasOverloads) {
+ List<MappedPosition> mappedPositions = new ArrayList<>();
+ // Do the actual processing for each method.
+ DexApplication application = appView.appInfo().app();
+ DexCode dexCode = method.getDefinition().getCode().asDexCode();
+ EventBasedDebugInfo debugInfo =
+ getEventBasedDebugInfo(method.getDefinition(), dexCode, appView);
+
+ List<DexDebugEvent> processedEvents = new ArrayList<>();
+
+ PositionEventEmitter positionEventEmitter =
+ new PositionEventEmitter(
+ application.dexItemFactory,
+ appView.graphLens().getOriginalMethodSignature(method.getReference()),
+ processedEvents);
+
+ Box<Boolean> inlinedOriginalPosition = new Box<>(false);
+
+ // Debug event visitor to map line numbers.
+ DexDebugPositionState visitor =
+ new DexDebugPositionState(
+ debugInfo.startLine,
+ appView.graphLens().getOriginalMethodSignature(method.getReference())) {
+
+ // Keep track of what PC has been emitted.
+ private int emittedPc = 0;
+
+ // Force the current PC to emitted.
+ private void flushPc() {
+ if (emittedPc != getCurrentPc()) {
+ positionEventEmitter.emitAdvancePc(getCurrentPc());
+ emittedPc = getCurrentPc();
+ }
+ }
+
+ // A default event denotes a line table entry and must always be emitted. Remap its line.
+ @Override
+ public void visit(Default defaultEvent) {
+ super.visit(defaultEvent);
+ assert getCurrentLine() >= 0;
+ Position position = getPositionFromPositionState(this);
+ Position currentPosition =
+ PositionUtils.remapAndAdd(position, positionRemapper, mappedPositions);
+ positionEventEmitter.emitPositionEvents(getCurrentPc(), currentPosition);
+ if (currentPosition != position) {
+ inlinedOriginalPosition.set(true);
+ }
+ emittedPc = getCurrentPc();
+ resetOutlineInformation();
+ }
+
+ // Non-materializing events use super, ie, AdvancePC, AdvanceLine and SetInlineFrame.
+
+ // Materializing events are just amended to the stream.
+
+ @Override
+ public void visit(SetFile setFile) {
+ processedEvents.add(setFile);
+ }
+
+ @Override
+ public void visit(SetPrologueEnd setPrologueEnd) {
+ processedEvents.add(setPrologueEnd);
+ }
+
+ @Override
+ public void visit(SetEpilogueBegin setEpilogueBegin) {
+ processedEvents.add(setEpilogueBegin);
+ }
+
+ // Local changes must force flush the PC ensuing they pertain to the correct point.
+
+ @Override
+ public void visit(StartLocal startLocal) {
+ flushPc();
+ processedEvents.add(startLocal);
+ }
+
+ @Override
+ public void visit(EndLocal endLocal) {
+ flushPc();
+ processedEvents.add(endLocal);
+ }
+
+ @Override
+ public void visit(RestartLocal restartLocal) {
+ flushPc();
+ processedEvents.add(restartLocal);
+ }
+ };
+
+ for (DexDebugEvent event : debugInfo.events) {
+ event.accept(visitor);
+ }
+
+ // If we only have one line event we can always retrace back uniquely.
+ if (mappedPositions.size() <= 1
+ && !hasOverloads
+ && !appView.options().debug
+ && appView.options().lineNumberOptimization != LineNumberOptimization.OFF
+ && appView.options().allowDiscardingResidualDebugInfo()
+ && (mappedPositions.isEmpty() || !mappedPositions.get(0).isOutlineCaller())) {
+ dexCode.setDebugInfo(DexDebugInfoForSingleLineMethod.getInstance());
+ return mappedPositions;
+ }
+
+ EventBasedDebugInfo optimizedDebugInfo =
+ new EventBasedDebugInfo(
+ positionEventEmitter.getStartLine(),
+ debugInfo.parameters,
+ processedEvents.toArray(DexDebugEvent.EMPTY_ARRAY));
+
+ assert !isIdentityMapping
+ || inlinedOriginalPosition.get()
+ || verifyIdentityMapping(debugInfo, optimizedDebugInfo);
+
+ dexCode.setDebugInfo(optimizedDebugInfo);
+ return mappedPositions;
+ }
+
+ // This conversion *always* creates an event based debug info encoding as any non-info will
+ // be created as an implicit PC encoding.
+ private static EventBasedDebugInfo getEventBasedDebugInfo(
+ DexEncodedMethod method, DexCode dexCode, AppView<?> appView) {
+ // TODO(b/213411850): Do we need to reconsider conversion here to support pc-based D8 merging?
+ if (dexCode.getDebugInfo() == null) {
+ return DexDebugInfo.createEventBasedInfoForMethodWithoutDebugInfo(
+ method, appView.dexItemFactory());
+ }
+ assert method.getParameters().size() == dexCode.getDebugInfo().getParameterCount();
+ EventBasedDebugInfo debugInfo =
+ DexDebugInfo.convertToEventBased(dexCode, appView.dexItemFactory());
+ assert debugInfo != null;
+ return debugInfo;
+ }
+
+ private static Position getPositionFromPositionState(DexDebugPositionState state) {
+ PositionBuilder<?, ?> positionBuilder;
+ if (state.getOutlineCallee() != null) {
+ OutlineCallerPositionBuilder outlineCallerPositionBuilder =
+ OutlineCallerPosition.builder()
+ .setOutlineCallee(state.getOutlineCallee())
+ .setIsOutline(state.isOutline());
+ state.getOutlineCallerPositions().forEach(outlineCallerPositionBuilder::addOutlinePosition);
+ positionBuilder = outlineCallerPositionBuilder;
+ } else if (state.isOutline()) {
+ positionBuilder = OutlinePosition.builder();
+ } else {
+ positionBuilder = SourcePosition.builder().setFile(state.getCurrentFile());
+ }
+ return positionBuilder
+ .setLine(state.getCurrentLine())
+ .setMethod(state.getCurrentMethod())
+ .setCallerPosition(state.getCurrentCallerPosition())
+ .build();
+ }
+
+ private static boolean verifyIdentityMapping(
+ EventBasedDebugInfo originalDebugInfo, EventBasedDebugInfo optimizedDebugInfo) {
+ assert optimizedDebugInfo.startLine == originalDebugInfo.startLine;
+ assert optimizedDebugInfo.events.length == originalDebugInfo.events.length;
+ for (int i = 0; i < originalDebugInfo.events.length; ++i) {
+ assert optimizedDebugInfo.events[i].equals(originalDebugInfo.events[i]);
+ }
+ return true;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/utils/positions/DexPositionToPcMappedRangeMapper.java b/src/main/java/com/android/tools/r8/utils/positions/DexPositionToPcMappedRangeMapper.java
new file mode 100644
index 0000000..f29f9cb
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/positions/DexPositionToPcMappedRangeMapper.java
@@ -0,0 +1,197 @@
+// Copyright (c) 2022, 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.utils.positions;
+
+import com.android.tools.r8.debuginfo.DebugRepresentation;
+import com.android.tools.r8.dex.code.DexInstruction;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexCode;
+import com.android.tools.r8.graph.DexDebugEvent;
+import com.android.tools.r8.graph.DexDebugEvent.Default;
+import com.android.tools.r8.graph.DexDebugEventVisitor;
+import com.android.tools.r8.graph.DexDebugInfo;
+import com.android.tools.r8.graph.DexDebugInfo.EventBasedDebugInfo;
+import com.android.tools.r8.graph.DexDebugInfoForSingleLineMethod;
+import com.android.tools.r8.graph.DexDebugPositionState;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.code.Position;
+import com.android.tools.r8.ir.code.Position.OutlineCallerPosition;
+import com.android.tools.r8.ir.code.Position.OutlineCallerPosition.OutlineCallerPositionBuilder;
+import com.android.tools.r8.ir.code.Position.OutlinePosition;
+import com.android.tools.r8.ir.code.Position.PositionBuilder;
+import com.android.tools.r8.ir.code.Position.SourcePosition;
+import com.android.tools.r8.utils.BooleanBox;
+import com.android.tools.r8.utils.IntBox;
+import com.android.tools.r8.utils.Pair;
+import com.android.tools.r8.utils.positions.PositionToMappedRangeMapper.PcBasedDebugInfoRecorder;
+import java.util.ArrayList;
+import java.util.List;
+
+public class DexPositionToPcMappedRangeMapper {
+
+ private final AppView<?> appView;
+ private final PcBasedDebugInfoRecorder pcBasedDebugInfo;
+
+ public DexPositionToPcMappedRangeMapper(
+ AppView<?> appView, PcBasedDebugInfoRecorder pcBasedDebugInfo) {
+ this.appView = appView;
+ this.pcBasedDebugInfo = pcBasedDebugInfo;
+ }
+
+ public List<MappedPosition> optimizeDexCodePositionsForPc(
+ ProgramMethod method, PositionRemapper positionRemapper, int pcEncodingCutoff) {
+ List<MappedPosition> mappedPositions = new ArrayList<>();
+ // Do the actual processing for each method.
+ DexCode dexCode = method.getDefinition().getCode().asDexCode();
+ EventBasedDebugInfo debugInfo =
+ getEventBasedDebugInfo(method.getDefinition(), dexCode, appView);
+ IntBox firstDefaultEventPc = new IntBox(-1);
+ BooleanBox singleOriginalLine = new BooleanBox(true);
+ Pair<Integer, Position> lastPosition = new Pair<>();
+ DexDebugEventVisitor visitor =
+ new DexDebugPositionState(
+ debugInfo.startLine,
+ appView.graphLens().getOriginalMethodSignature(method.getReference())) {
+ @Override
+ public void visit(Default defaultEvent) {
+ super.visit(defaultEvent);
+ assert getCurrentLine() >= 0;
+ if (firstDefaultEventPc.get() < 0) {
+ firstDefaultEventPc.set(getCurrentPc());
+ }
+ Position currentPosition = getPositionFromPositionState(this);
+ if (lastPosition.getSecond() != null) {
+ if (singleOriginalLine.isTrue()
+ && !currentPosition.equals(lastPosition.getSecond())) {
+ singleOriginalLine.set(false);
+ }
+ remapAndAddForPc(
+ pcBasedDebugInfo,
+ lastPosition.getFirst(),
+ getCurrentPc(),
+ lastPosition.getSecond(),
+ positionRemapper,
+ mappedPositions);
+ }
+ lastPosition.setFirst(getCurrentPc());
+ lastPosition.setSecond(currentPosition);
+ resetOutlineInformation();
+ }
+ };
+
+ for (DexDebugEvent event : debugInfo.events) {
+ event.accept(visitor);
+ }
+
+ // If the method has a single non-preamble line, check that the preamble is not active on any
+ // throwing instruction before the single line becomes active.
+ if (singleOriginalLine.isTrue() && firstDefaultEventPc.get() > 0) {
+ for (DexInstruction instruction : dexCode.instructions) {
+ if (instruction.getOffset() < firstDefaultEventPc.get()) {
+ if (instruction.canThrow()) {
+ singleOriginalLine.set(false);
+ }
+ } else {
+ break;
+ }
+ }
+ }
+
+ int lastInstructionPc = DebugRepresentation.getLastExecutableInstruction(dexCode).getOffset();
+ if (lastPosition.getSecond() != null) {
+ remapAndAddForPc(
+ pcBasedDebugInfo,
+ lastPosition.getFirst(),
+ lastInstructionPc + 1,
+ lastPosition.getSecond(),
+ positionRemapper,
+ mappedPositions);
+ }
+
+ assert !mappedPositions.isEmpty() || dexCode.instructions.length == 1;
+ if (singleOriginalLine.isTrue()
+ && lastPosition.getSecond() != null
+ && (mappedPositions.isEmpty() || !mappedPositions.get(0).isOutlineCaller())) {
+ dexCode.setDebugInfo(DexDebugInfoForSingleLineMethod.getInstance());
+ pcBasedDebugInfo.recordSingleLineFor(method, pcEncodingCutoff);
+ } else {
+ pcBasedDebugInfo.recordPcMappingFor(method, pcEncodingCutoff);
+ }
+ return mappedPositions;
+ }
+
+ private static void remapAndAddForPc(
+ PcBasedDebugInfoRecorder debugInfoProvider,
+ int startPc,
+ int endPc,
+ Position position,
+ PositionRemapper remapper,
+ List<MappedPosition> mappedPositions) {
+ Pair<Position, Position> remappedPosition = remapper.createRemappedPosition(position);
+ Position oldPosition = remappedPosition.getFirst();
+ for (int currentPc = startPc; currentPc < endPc; currentPc++) {
+ boolean firstEntry = currentPc == startPc;
+ mappedPositions.add(
+ new MappedPosition(
+ oldPosition.getMethod(),
+ oldPosition.getLine(),
+ oldPosition.getCallerPosition(),
+ debugInfoProvider.getPcEncoding(currentPc),
+ // Outline info is placed exactly on the positions that relate to it so we should
+ // only emit it for the first entry.
+ firstEntry && oldPosition.isOutline(),
+ firstEntry ? oldPosition.getOutlineCallee() : null,
+ firstEntry ? oldPosition.getOutlinePositions() : null));
+ }
+ }
+
+ // This conversion *always* creates an event based debug info encoding as any non-info will
+ // be created as an implicit PC encoding.
+ private static EventBasedDebugInfo getEventBasedDebugInfo(
+ DexEncodedMethod method, DexCode dexCode, AppView<?> appView) {
+ // TODO(b/213411850): Do we need to reconsider conversion here to support pc-based D8 merging?
+ if (dexCode.getDebugInfo() == null) {
+ return DexDebugInfo.createEventBasedInfoForMethodWithoutDebugInfo(
+ method, appView.dexItemFactory());
+ }
+ assert method.getParameters().size() == dexCode.getDebugInfo().getParameterCount();
+ EventBasedDebugInfo debugInfo =
+ DexDebugInfo.convertToEventBased(dexCode, appView.dexItemFactory());
+ assert debugInfo != null;
+ return debugInfo;
+ }
+
+ private static Position getPositionFromPositionState(DexDebugPositionState state) {
+ PositionBuilder<?, ?> positionBuilder;
+ if (state.getOutlineCallee() != null) {
+ OutlineCallerPositionBuilder outlineCallerPositionBuilder =
+ OutlineCallerPosition.builder()
+ .setOutlineCallee(state.getOutlineCallee())
+ .setIsOutline(state.isOutline());
+ state.getOutlineCallerPositions().forEach(outlineCallerPositionBuilder::addOutlinePosition);
+ positionBuilder = outlineCallerPositionBuilder;
+ } else if (state.isOutline()) {
+ positionBuilder = OutlinePosition.builder();
+ } else {
+ positionBuilder = SourcePosition.builder().setFile(state.getCurrentFile());
+ }
+ return positionBuilder
+ .setLine(state.getCurrentLine())
+ .setMethod(state.getCurrentMethod())
+ .setCallerPosition(state.getCurrentCallerPosition())
+ .build();
+ }
+
+ private static boolean verifyIdentityMapping(
+ EventBasedDebugInfo originalDebugInfo, EventBasedDebugInfo optimizedDebugInfo) {
+ assert optimizedDebugInfo.startLine == originalDebugInfo.startLine;
+ assert optimizedDebugInfo.events.length == originalDebugInfo.events.length;
+ for (int i = 0; i < originalDebugInfo.events.length; ++i) {
+ assert optimizedDebugInfo.events[i].equals(originalDebugInfo.events[i]);
+ }
+ return true;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/utils/positions/LineNumberOptimizer.java b/src/main/java/com/android/tools/r8/utils/positions/LineNumberOptimizer.java
new file mode 100644
index 0000000..de478a0
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/positions/LineNumberOptimizer.java
@@ -0,0 +1,224 @@
+// Copyright (c) 2022, 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.utils.positions;
+
+import static com.android.tools.r8.utils.positions.PositionUtils.mustHaveResidualDebugInfo;
+
+import com.android.tools.r8.cf.code.CfInstruction;
+import com.android.tools.r8.cf.code.CfPosition;
+import com.android.tools.r8.debuginfo.DebugRepresentation.DebugRepresentationPredicate;
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.Code;
+import com.android.tools.r8.graph.DexClassAndMethod;
+import com.android.tools.r8.graph.DexDebugInfo;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexString;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.naming.ClassNameMapper;
+import com.android.tools.r8.naming.ProguardMapSupplier;
+import com.android.tools.r8.naming.ProguardMapSupplier.ProguardMapId;
+import com.android.tools.r8.shaking.KeepInfoCollection;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.CfLineToMethodMapper;
+import com.android.tools.r8.utils.OriginalSourceFiles;
+import com.android.tools.r8.utils.Timing;
+import com.android.tools.r8.utils.positions.MappedPositionToClassNameMapperBuilder.MappedPositionToClassNamingBuilder;
+import java.util.ArrayList;
+import java.util.IdentityHashMap;
+import java.util.List;
+
+public class LineNumberOptimizer {
+
+ public static ProguardMapId runAndWriteMap(
+ AndroidApp inputApp,
+ AppView<?> appView,
+ Timing timing,
+ OriginalSourceFiles originalSourceFiles,
+ DebugRepresentationPredicate representation) {
+ assert appView.options().proguardMapConsumer != null;
+ // When line number optimization is turned off the identity mapping for line numbers is
+ // used. We still run the line number optimizer to collect line numbers and inline frame
+ // information for the mapping file.
+ timing.begin("Line number remapping");
+ ClassNameMapper mapper = run(appView, inputApp, originalSourceFiles, representation);
+ timing.end();
+ timing.begin("Write proguard map");
+ ProguardMapId mapId = ProguardMapSupplier.create(mapper, appView.options()).writeProguardMap();
+ timing.end();
+ return mapId;
+ }
+
+ public static ClassNameMapper run(
+ AppView<?> appView,
+ AndroidApp inputApp,
+ OriginalSourceFiles originalSourceFiles,
+ DebugRepresentationPredicate representation) {
+ // For finding methods in kotlin files based on SourceDebugExtensions, we use a line method map.
+ // We create it here to ensure it is only reading class files once.
+ // TODO(b/220999985): Make this threaded per virtual file. Possibly pull the kotlin line mapping
+ // onto main thread before threading.
+ CfLineToMethodMapper cfLineToMethodMapper = new CfLineToMethodMapper(inputApp);
+
+ PositionToMappedRangeMapper positionToMappedRangeMapper =
+ PositionToMappedRangeMapper.create(appView);
+
+ MappedPositionToClassNameMapperBuilder builder =
+ MappedPositionToClassNameMapperBuilder.builder(appView, originalSourceFiles);
+
+ // Collect which files contain which classes that need to have their line numbers optimized.
+ for (DexProgramClass clazz : appView.appInfo().classes()) {
+ IdentityHashMap<DexString, List<ProgramMethod>> methodsByRenamedName =
+ groupMethodsByRenamedName(appView, clazz);
+
+ MappedPositionToClassNamingBuilder classNamingBuilder = builder.addClassNaming(clazz);
+
+ // Process methods ordered by renamed name.
+ List<DexString> renamedMethodNames = new ArrayList<>(methodsByRenamedName.keySet());
+ renamedMethodNames.sort(DexString::compareTo);
+ for (DexString methodName : renamedMethodNames) {
+ List<ProgramMethod> methods = methodsByRenamedName.get(methodName);
+ if (methods.size() > 1) {
+ // If there are multiple methods with the same name (overloaded) then sort them for
+ // deterministic behaviour: the algorithm will assign new line numbers in this order.
+ // Methods with different names can share the same line numbers, that's why they don't
+ // need to be sorted.
+ // If we are compiling to DEX we will try to not generate overloaded names. This saves
+ // space by allowing more debug-information to be canonicalized. If we have overloaded
+ // methods, we either did not rename them, we renamed them according to a supplied map or
+ // they may be bridges for interface methods with covariant return types.
+ sortMethods(methods);
+ assert verifyMethodsAreKeptDirectlyOrIndirectly(appView, methods);
+ }
+
+ PositionRemapper positionRemapper =
+ PositionRemapper.getPositionRemapper(appView, cfLineToMethodMapper);
+
+ for (ProgramMethod method : methods) {
+ DexEncodedMethod definition = method.getDefinition();
+ positionRemapper.setCurrentMethod(definition);
+ List<MappedPosition> mappedPositions;
+ int pcEncodingCutoff =
+ methods.size() == 1 ? representation.getDexPcEncodingCutoff(method) : -1;
+ boolean canUseDexPc = pcEncodingCutoff > 0;
+ if (definition.getCode() != null
+ && mustHaveResidualDebugInfo(appView.options(), definition)
+ && !appView.isCfByteCodePassThrough(definition)) {
+ mappedPositions =
+ positionToMappedRangeMapper.getMappedPositions(
+ method, positionRemapper, methods.size() > 1, canUseDexPc, pcEncodingCutoff);
+ } else {
+ mappedPositions = new ArrayList<>();
+ }
+
+ classNamingBuilder.addMappedPositions(
+ method, mappedPositions, positionRemapper, canUseDexPc);
+ } // for each method of the group
+ } // for each method group, grouped by name
+ } // for each class
+
+ // Update all the debug-info objects.
+ positionToMappedRangeMapper.updateDebugInfoInCodeObjects();
+
+ return builder.build();
+ }
+
+ private static boolean verifyMethodsAreKeptDirectlyOrIndirectly(
+ AppView<?> appView, List<ProgramMethod> methods) {
+ if (appView.options().isGeneratingClassFiles() || !appView.appInfo().hasClassHierarchy()) {
+ return true;
+ }
+ AppInfoWithClassHierarchy appInfo = appView.appInfo().withClassHierarchy();
+ KeepInfoCollection keepInfo = appView.getKeepInfo();
+ boolean allSeenAreInstanceInitializers = true;
+ DexString originalName = null;
+ for (ProgramMethod method : methods) {
+ // We cannot rename instance initializers.
+ if (method.getDefinition().isInstanceInitializer()) {
+ assert allSeenAreInstanceInitializers;
+ continue;
+ }
+ allSeenAreInstanceInitializers = false;
+ // If the method is pinned, we cannot minify it.
+ if (!keepInfo.isMinificationAllowed(method.getReference(), appView, appView.options())) {
+ continue;
+ }
+ // With desugared library, call-backs names are reserved here.
+ if (method.getDefinition().isLibraryMethodOverride().isTrue()) {
+ continue;
+ }
+ // We use the same name for interface names even if it has different types.
+ DexProgramClass clazz = appView.definitionForProgramType(method.getHolderType());
+ DexClassAndMethod lookupResult =
+ appInfo.lookupMaximallySpecificMethod(clazz, method.getReference());
+ if (lookupResult == null) {
+ // We cannot rename methods we cannot look up.
+ continue;
+ }
+ String errorString = method.getReference().qualifiedName() + " is not kept but is overloaded";
+ assert lookupResult.getHolder().isInterface() : errorString;
+ // TODO(b/159113601): Reenable assert.
+ assert true || originalName == null || originalName.equals(method.getReference().name)
+ : errorString;
+ originalName = method.getReference().name;
+ }
+ return true;
+ }
+
+ private static int getMethodStartLine(ProgramMethod method) {
+ Code code = method.getDefinition().getCode();
+ if (code == null) {
+ return 0;
+ }
+ if (code.isDexCode()) {
+ DexDebugInfo dexDebugInfo = code.asDexCode().getDebugInfo();
+ return dexDebugInfo == null ? 0 : dexDebugInfo.getStartLine();
+ } else if (code.isCfCode()) {
+ List<CfInstruction> instructions = code.asCfCode().getInstructions();
+ for (CfInstruction instruction : instructions) {
+ if (!(instruction instanceof CfPosition)) {
+ continue;
+ }
+ return ((CfPosition) instruction).getPosition().getLine();
+ }
+ }
+ return 0;
+ }
+
+ // Sort by startline, then DexEncodedMethod.slowCompare.
+ // Use startLine = 0 if no debuginfo.
+ private static void sortMethods(List<ProgramMethod> methods) {
+ methods.sort(
+ (lhs, rhs) -> {
+ int lhsStartLine = getMethodStartLine(lhs);
+ int rhsStartLine = getMethodStartLine(rhs);
+ int startLineDiff = lhsStartLine - rhsStartLine;
+ if (startLineDiff != 0) return startLineDiff;
+ return DexEncodedMethod.slowCompare(lhs.getDefinition(), rhs.getDefinition());
+ });
+ }
+
+ public static IdentityHashMap<DexString, List<ProgramMethod>> groupMethodsByRenamedName(
+ AppView<?> appView, DexProgramClass clazz) {
+ IdentityHashMap<DexString, List<ProgramMethod>> methodsByRenamedName =
+ new IdentityHashMap<>(clazz.getMethodCollection().size());
+ for (ProgramMethod programMethod : clazz.programMethods()) {
+ // Add method only if renamed, moved, or if it has debug info to map.
+ DexEncodedMethod definition = programMethod.getDefinition();
+ DexMethod method = programMethod.getReference();
+ DexString renamedName = appView.getNamingLens().lookupName(method);
+ if (renamedName != method.name
+ || appView.graphLens().getOriginalMethodSignature(method) != method
+ || mustHaveResidualDebugInfo(appView.options(), definition)
+ || definition.isD8R8Synthesized()) {
+ methodsByRenamedName
+ .computeIfAbsent(renamedName, key -> new ArrayList<>())
+ .add(programMethod);
+ }
+ }
+ return methodsByRenamedName;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/utils/positions/MappedPosition.java b/src/main/java/com/android/tools/r8/utils/positions/MappedPosition.java
new file mode 100644
index 0000000..a156c80
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/positions/MappedPosition.java
@@ -0,0 +1,69 @@
+// Copyright (c) 2022, 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.utils.positions;
+
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.ir.code.Position;
+import com.android.tools.r8.utils.Int2StructuralItemArrayMap;
+
+public class MappedPosition {
+
+ private final DexMethod method;
+ private final int originalLine;
+ private final Position caller;
+ private final int obfuscatedLine;
+ private final boolean isOutline;
+ private final DexMethod outlineCallee;
+ private final Int2StructuralItemArrayMap<Position> outlinePositions;
+
+ public MappedPosition(
+ DexMethod method,
+ int originalLine,
+ Position caller,
+ int obfuscatedLine,
+ boolean isOutline,
+ DexMethod outlineCallee,
+ Int2StructuralItemArrayMap<Position> outlinePositions) {
+ this.method = method;
+ this.originalLine = originalLine;
+ this.caller = caller;
+ this.obfuscatedLine = obfuscatedLine;
+ this.isOutline = isOutline;
+ this.outlineCallee = outlineCallee;
+ this.outlinePositions = outlinePositions;
+ }
+
+ public DexMethod getMethod() {
+ return method;
+ }
+
+ public int getOriginalLine() {
+ return originalLine;
+ }
+
+ public Position getCaller() {
+ return caller;
+ }
+
+ public int getObfuscatedLine() {
+ return obfuscatedLine;
+ }
+
+ public boolean isOutline() {
+ return isOutline;
+ }
+
+ public DexMethod getOutlineCallee() {
+ return outlineCallee;
+ }
+
+ public Int2StructuralItemArrayMap<Position> getOutlinePositions() {
+ return outlinePositions;
+ }
+
+ public boolean isOutlineCaller() {
+ return outlineCallee != null;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/utils/positions/MappedPositionToClassNameMapperBuilder.java b/src/main/java/com/android/tools/r8/utils/positions/MappedPositionToClassNameMapperBuilder.java
new file mode 100644
index 0000000..2106531
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/positions/MappedPositionToClassNameMapperBuilder.java
@@ -0,0 +1,575 @@
+// Copyright (c) 2022, 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.utils.positions;
+
+import static com.android.tools.r8.utils.positions.PositionUtils.mustHaveResidualDebugInfo;
+
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.Code;
+import com.android.tools.r8.graph.DexDebugInfoForSingleLineMethod;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexString;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.code.Position;
+import com.android.tools.r8.naming.ClassNameMapper;
+import com.android.tools.r8.naming.ClassNaming;
+import com.android.tools.r8.naming.ClassNamingForNameMapper.MappedRange;
+import com.android.tools.r8.naming.MapVersion;
+import com.android.tools.r8.naming.MemberNaming;
+import com.android.tools.r8.naming.MemberNaming.FieldSignature;
+import com.android.tools.r8.naming.MemberNaming.MethodSignature;
+import com.android.tools.r8.naming.NamingLens;
+import com.android.tools.r8.naming.PositionRangeAllocator;
+import com.android.tools.r8.naming.PositionRangeAllocator.CardinalPositionRangeAllocator;
+import com.android.tools.r8.naming.PositionRangeAllocator.NonCardinalPositionRangeAllocator;
+import com.android.tools.r8.naming.Range;
+import com.android.tools.r8.naming.mappinginformation.CompilerSynthesizedMappingInformation;
+import com.android.tools.r8.naming.mappinginformation.FileNameInformation;
+import com.android.tools.r8.naming.mappinginformation.MappingInformation;
+import com.android.tools.r8.naming.mappinginformation.OutlineCallsiteMappingInformation;
+import com.android.tools.r8.naming.mappinginformation.OutlineMappingInformation;
+import com.android.tools.r8.naming.mappinginformation.ResidualSignatureMappingInformation.ResidualMethodSignatureMappingInformation;
+import com.android.tools.r8.naming.mappinginformation.RewriteFrameMappingInformation;
+import com.android.tools.r8.naming.mappinginformation.RewriteFrameMappingInformation.RemoveInnerFramesAction;
+import com.android.tools.r8.naming.mappinginformation.RewriteFrameMappingInformation.ThrowsCondition;
+import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.retrace.internal.RetraceUtils;
+import com.android.tools.r8.synthesis.SyntheticItems;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.InternalOptions.LineNumberOptimization;
+import com.android.tools.r8.utils.ListUtils;
+import com.android.tools.r8.utils.OriginalSourceFiles;
+import com.android.tools.r8.utils.Pair;
+import it.unimi.dsi.fastutil.ints.Int2IntArrayMap;
+import it.unimi.dsi.fastutil.ints.Int2IntLinkedOpenHashMap;
+import it.unimi.dsi.fastutil.ints.Int2IntMap;
+import it.unimi.dsi.fastutil.ints.Int2IntSortedMap;
+import java.util.ArrayList;
+import java.util.IdentityHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Objects;
+import java.util.function.Function;
+
+public class MappedPositionToClassNameMapperBuilder {
+
+ private static final int MAX_LINE_NUMBER = 65535;
+
+ private final OriginalSourceFiles originalSourceFiles;
+ private final AppView<?> appView;
+
+ private final ClassNameMapper.Builder classNameMapperBuilder = ClassNameMapper.builder();
+ private final Map<DexMethod, OutlineFixupBuilder> outlinesToFix = new IdentityHashMap<>();
+ private final Map<DexType, String> prunedInlinedClasses = new IdentityHashMap<>();
+
+ private final CardinalPositionRangeAllocator cardinalRangeCache =
+ PositionRangeAllocator.createCardinalPositionRangeAllocator();
+ private final NonCardinalPositionRangeAllocator nonCardinalRangeCache =
+ PositionRangeAllocator.createNonCardinalPositionRangeAllocator();
+
+ private MappedPositionToClassNameMapperBuilder(
+ AppView<?> appView, OriginalSourceFiles originalSourceFiles) {
+ this.appView = appView;
+ this.originalSourceFiles = originalSourceFiles;
+ }
+
+ public static MappedPositionToClassNameMapperBuilder builder(
+ AppView<?> appView, OriginalSourceFiles originalSourceFiles) {
+ return new MappedPositionToClassNameMapperBuilder(appView, originalSourceFiles);
+ }
+
+ public ClassNameMapper build() {
+ // Fixup all outline positions
+ outlinesToFix.values().forEach(OutlineFixupBuilder::fixup);
+ addSourceFileLinesForPrunedClasses();
+ return classNameMapperBuilder.build();
+ }
+
+ private void addSourceFileLinesForPrunedClasses() {
+ // Add all pruned inline classes to the mapping to recover source files.
+ List<Entry<DexType, String>> prunedEntries = new ArrayList<>(prunedInlinedClasses.entrySet());
+ prunedEntries.sort(Entry.comparingByKey());
+ prunedEntries.forEach(
+ entry -> {
+ DexType holder = entry.getKey();
+ assert appView.appInfo().definitionForWithoutExistenceAssert(holder) == null;
+ String typeName = holder.toSourceString();
+ String sourceFile = entry.getValue();
+ assert !RetraceUtils.hasPredictableSourceFileName(typeName, sourceFile);
+ classNameMapperBuilder
+ .classNamingBuilder(
+ typeName, typeName, com.android.tools.r8.position.Position.UNKNOWN)
+ .addMappingInformation(FileNameInformation.build(sourceFile), Unreachable::raise);
+ });
+ }
+
+ public MappedPositionToClassNamingBuilder addClassNaming(DexProgramClass clazz) {
+ DexType originalType = appView.graphLens().getOriginalType(clazz.type);
+ DexString renamedDescriptor = appView.getNamingLens().lookupDescriptor(clazz.getType());
+ return new MappedPositionToClassNamingBuilder(
+ clazz, originalType, DescriptorUtils.descriptorToJavaType(renamedDescriptor.toString()))
+ .addSourceFile(originalSourceFiles)
+ .addSynthetic(appView.getSyntheticItems())
+ .addFields();
+ }
+
+ public class MappedPositionToClassNamingBuilder {
+
+ private final DexProgramClass clazz;
+ private final DexType originalType;
+ private final String renamedName;
+
+ private ClassNaming.Builder builder;
+
+ private MappedPositionToClassNamingBuilder(
+ DexProgramClass clazz, DexType originalType, String renamedName) {
+ this.clazz = clazz;
+ this.originalType = originalType;
+ this.renamedName = renamedName;
+ // If the class is renamed trigger an entry in the builder.
+ if (!originalType.toSourceString().equals(renamedName)) {
+ getBuilder();
+ }
+ }
+
+ public MappedPositionToClassNamingBuilder addSourceFile(
+ OriginalSourceFiles originalSourceFiles) {
+ // Check if source file should be added to the map
+ DexString originalSourceFile = originalSourceFiles.getOriginalSourceFile(clazz);
+ if (originalSourceFile != null) {
+ String sourceFile = originalSourceFile.toString();
+ if (!RetraceUtils.hasPredictableSourceFileName(clazz.toSourceString(), sourceFile)) {
+ getBuilder()
+ .addMappingInformation(FileNameInformation.build(sourceFile), Unreachable::raise);
+ }
+ }
+ return this;
+ }
+
+ public MappedPositionToClassNamingBuilder addSynthetic(SyntheticItems syntheticItems) {
+ if (syntheticItems.isSyntheticClass(clazz)) {
+ getBuilder()
+ .addMappingInformation(
+ CompilerSynthesizedMappingInformation.builder().build(), Unreachable::raise);
+ }
+ return this;
+ }
+
+ private MappedPositionToClassNamingBuilder addFields() {
+ clazz.forEachField(
+ dexEncodedField -> {
+ DexField dexField = dexEncodedField.getReference();
+ DexField originalField = appView.graphLens().getOriginalFieldSignature(dexField);
+ DexField residualField =
+ appView.getNamingLens().lookupField(dexField, appView.dexItemFactory());
+ if (residualField.name != originalField.name || originalField.holder != originalType) {
+ FieldSignature originalSignature =
+ FieldSignature.fromDexField(originalField, originalField.holder != originalType);
+ MemberNaming memberNaming =
+ new MemberNaming(originalSignature, FieldSignature.fromDexField(residualField));
+ getBuilder().addMemberEntry(memberNaming);
+ }
+ });
+ return this;
+ }
+
+ public MappedPositionToClassNamingBuilder addMappedPositions(
+ ProgramMethod method,
+ List<MappedPosition> mappedPositions,
+ PositionRemapper positionRemapper,
+ boolean canUseDexPc) {
+ DexEncodedMethod definition = method.getDefinition();
+ DexMethod originalMethod =
+ appView.graphLens().getOriginalMethodSignature(method.getReference());
+ MethodSignature originalSignature =
+ MethodSignature.fromDexMethod(originalMethod, originalMethod.holder != originalType);
+
+ List<MappingInformation> methodMappingInfo = new ArrayList<>();
+ if (method.getDefinition().isD8R8Synthesized()) {
+ methodMappingInfo.add(CompilerSynthesizedMappingInformation.builder().build());
+ }
+
+ DexMethod residualMethod =
+ appView.getNamingLens().lookupMethod(method.getReference(), appView.dexItemFactory());
+
+ MapVersion mapFileVersion = appView.options().getMapFileVersion();
+ if (isIdentityMapping(
+ mapFileVersion,
+ mappedPositions,
+ methodMappingInfo,
+ method,
+ residualMethod.getName(),
+ originalMethod,
+ originalType)) {
+ assert appView.options().lineNumberOptimization == LineNumberOptimization.OFF
+ || hasAtMostOnePosition(appView, definition)
+ || appView.isCfByteCodePassThrough(definition);
+ return this;
+ }
+ // TODO(b/169953605): Ensure we emit the residual signature information.
+ if (mapFileVersion.isGreaterThan(MapVersion.MAP_VERSION_2_1)
+ && originalMethod != method.getReference()) {
+ methodMappingInfo.add(
+ ResidualMethodSignatureMappingInformation.fromDexMethod(residualMethod));
+ }
+
+ MethodSignature residualSignature = MethodSignature.fromDexMethod(residualMethod);
+
+ MemberNaming memberNaming = new MemberNaming(originalSignature, residualSignature);
+ getBuilder().addMemberEntry(memberNaming);
+
+ // Add simple "a() -> b" mapping if we won't have any other with concrete line numbers
+ if (mappedPositions.isEmpty()) {
+ MappedRange range =
+ getBuilder().addMappedRange(null, originalSignature, null, residualSignature);
+ methodMappingInfo.forEach(info -> range.addMappingInformation(info, Unreachable::raise));
+ return this;
+ }
+
+ Map<DexMethod, MethodSignature> signatures = new IdentityHashMap<>();
+ signatures.put(originalMethod, originalSignature);
+ Function<DexMethod, MethodSignature> getOriginalMethodSignature =
+ m ->
+ signatures.computeIfAbsent(
+ m, key -> MethodSignature.fromDexMethod(m, m.holder != clazz.getType()));
+
+ // Check if mapped position is an outline
+ DexMethod outlineMethod = getOutlineMethod(mappedPositions.get(0));
+ if (outlineMethod != null) {
+ outlinesToFix
+ .computeIfAbsent(
+ outlineMethod,
+ outline -> new OutlineFixupBuilder(computeMappedMethod(outline, appView)))
+ .setMappedPositionsOutline(mappedPositions);
+ methodMappingInfo.add(OutlineMappingInformation.builder().build());
+ }
+
+ // Update memberNaming with the collected positions, merging multiple positions into a
+ // single region whenever possible.
+ for (int i = 0; i < mappedPositions.size(); /* updated in body */ ) {
+ MappedPosition firstPosition = mappedPositions.get(i);
+ int j = i + 1;
+ MappedPosition lastPosition = firstPosition;
+ MappedPositionRange mappedPositionRange = MappedPositionRange.SINGLE_LINE;
+ for (; j < mappedPositions.size(); j++) {
+ // Break if this position cannot be merged with lastPosition.
+ MappedPosition currentPosition = mappedPositions.get(j);
+ mappedPositionRange =
+ mappedPositionRange.canAddNextMappingToRange(lastPosition, currentPosition);
+ // Note that currentPosition.caller and lastPosition.class must be deep-compared since
+ // multiple inlining passes lose the canonical property of the positions.
+ if (currentPosition.getMethod() != lastPosition.getMethod()
+ || mappedPositionRange.isOutOfRange()
+ || !Objects.equals(currentPosition.getCaller(), lastPosition.getCaller())
+ // Break when we see a mapped outline
+ || currentPosition.getOutlineCallee() != null
+ // Ensure that we break when we start iterating with an outline caller again.
+ || firstPosition.getOutlineCallee() != null) {
+ break;
+ }
+ // The mapped positions are not guaranteed to be in order, so maintain first and last
+ // position.
+ if (firstPosition.getObfuscatedLine() > currentPosition.getObfuscatedLine()) {
+ firstPosition = currentPosition;
+ }
+ if (lastPosition.getObfuscatedLine() < currentPosition.getObfuscatedLine()) {
+ lastPosition = currentPosition;
+ }
+ }
+ Range obfuscatedRange;
+ if (definition.getCode().isDexCode()
+ && definition.getCode().asDexCode().getDebugInfo()
+ == DexDebugInfoForSingleLineMethod.getInstance()) {
+ assert firstPosition.getOriginalLine() == lastPosition.getOriginalLine();
+ obfuscatedRange = nonCardinalRangeCache.get(0, MAX_LINE_NUMBER);
+ } else {
+ obfuscatedRange =
+ nonCardinalRangeCache.get(
+ firstPosition.getObfuscatedLine(), lastPosition.getObfuscatedLine());
+ }
+ MappedRange lastMappedRange =
+ getMappedRangesForPosition(
+ appView,
+ getOriginalMethodSignature,
+ getBuilder(),
+ firstPosition.getMethod(),
+ residualSignature,
+ obfuscatedRange,
+ nonCardinalRangeCache.get(
+ firstPosition.getOriginalLine(), lastPosition.getOriginalLine()),
+ firstPosition.getCaller(),
+ prunedInlinedClasses,
+ cardinalRangeCache);
+ for (MappingInformation info : methodMappingInfo) {
+ lastMappedRange.addMappingInformation(info, Unreachable::raise);
+ }
+ // firstPosition will contain a potential outline caller.
+ if (firstPosition.getOutlineCallee() != null) {
+ Int2IntMap positionMap = new Int2IntArrayMap();
+ int maxPc = ListUtils.last(mappedPositions).getObfuscatedLine();
+ firstPosition
+ .getOutlinePositions()
+ .forEach(
+ (line, position) -> {
+ int placeHolderLineToBeFixed;
+ if (canUseDexPc) {
+ placeHolderLineToBeFixed = maxPc + line + 1;
+ } else {
+ placeHolderLineToBeFixed =
+ positionRemapper.createRemappedPosition(position).getSecond().getLine();
+ }
+ positionMap.put((int) line, placeHolderLineToBeFixed);
+ getMappedRangesForPosition(
+ appView,
+ getOriginalMethodSignature,
+ getBuilder(),
+ position.getMethod(),
+ residualSignature,
+ nonCardinalRangeCache.get(
+ placeHolderLineToBeFixed, placeHolderLineToBeFixed),
+ nonCardinalRangeCache.get(position.getLine(), position.getLine()),
+ position.getCallerPosition(),
+ prunedInlinedClasses,
+ cardinalRangeCache);
+ });
+ outlinesToFix
+ .computeIfAbsent(
+ firstPosition.getOutlineCallee(),
+ outline -> new OutlineFixupBuilder(computeMappedMethod(outline, appView)))
+ .addMappedRangeForOutlineCallee(lastMappedRange, positionMap);
+ }
+ i = j;
+ }
+ return this;
+ }
+
+ private MethodReference computeMappedMethod(DexMethod current, AppView<?> appView) {
+ NamingLens namingLens = appView.getNamingLens();
+ DexMethod renamedMethodSignature =
+ namingLens.lookupMethod(
+ appView.graphLens().getRenamedMethodSignature(current), appView.dexItemFactory());
+ return renamedMethodSignature.asMethodReference();
+ }
+
+ private MappedRange getMappedRangesForPosition(
+ AppView<?> appView,
+ Function<DexMethod, MethodSignature> getOriginalMethodSignature,
+ ClassNaming.Builder classNamingBuilder,
+ DexMethod method,
+ MethodSignature residualSignature,
+ Range obfuscatedRange,
+ Range originalLine,
+ Position caller,
+ Map<DexType, String> prunedInlineHolder,
+ CardinalPositionRangeAllocator cardinalRangeCache) {
+ MappedRange lastMappedRange =
+ classNamingBuilder.addMappedRange(
+ obfuscatedRange,
+ getOriginalMethodSignature.apply(method),
+ originalLine,
+ residualSignature);
+ int inlineFramesCount = 0;
+ while (caller != null) {
+ inlineFramesCount += 1;
+ String prunedClassSourceFileInfo =
+ appView.getPrunedClassSourceFileInfo(method.getHolderType());
+ if (prunedClassSourceFileInfo != null) {
+ String originalValue =
+ prunedInlineHolder.put(method.getHolderType(), prunedClassSourceFileInfo);
+ assert originalValue == null || originalValue.equals(prunedClassSourceFileInfo);
+ }
+ lastMappedRange =
+ classNamingBuilder.addMappedRange(
+ obfuscatedRange,
+ getOriginalMethodSignature.apply(caller.getMethod()),
+ cardinalRangeCache.get(
+ Math.max(caller.getLine(), 0)), // Prevent against "no-position".
+ residualSignature);
+ if (caller.isRemoveInnerFramesIfThrowingNpe()) {
+ lastMappedRange.addMappingInformation(
+ RewriteFrameMappingInformation.builder()
+ .addCondition(
+ ThrowsCondition.create(
+ Reference.classFromDescriptor(
+ appView.dexItemFactory().npeDescriptor.toString())))
+ .addRewriteAction(RemoveInnerFramesAction.create(inlineFramesCount))
+ .build(),
+ Unreachable::raise);
+ }
+ caller = caller.getCallerPosition();
+ }
+ return lastMappedRange;
+ }
+
+ private DexMethod getOutlineMethod(MappedPosition mappedPosition) {
+ if (mappedPosition.isOutline()) {
+ return mappedPosition.getMethod();
+ }
+ Position caller = mappedPosition.getCaller();
+ if (caller == null) {
+ return null;
+ }
+ Position outermostCaller = caller.getOutermostCaller();
+ return outermostCaller.isOutline() ? outermostCaller.getMethod() : null;
+ }
+
+ private boolean isIdentityMapping(
+ MapVersion mapFileVersion,
+ List<MappedPosition> mappedPositions,
+ List<MappingInformation> methodMappingInfo,
+ ProgramMethod method,
+ DexString obfuscatedNameDexString,
+ DexMethod originalMethod,
+ DexType originalType) {
+ if (mapFileVersion.isGreaterThan(MapVersion.MAP_VERSION_2_1)) {
+ // Don't emit pure identity mappings.
+ return mappedPositions.isEmpty()
+ && methodMappingInfo.isEmpty()
+ && originalMethod == method.getReference();
+ } else {
+ // Don't emit pure identity mappings.
+ return mappedPositions.isEmpty()
+ && methodMappingInfo.isEmpty()
+ && obfuscatedNameDexString == originalMethod.name
+ && originalMethod.holder == originalType;
+ }
+ }
+
+ private boolean hasAtMostOnePosition(AppView<?> appView, DexEncodedMethod definition) {
+ if (!mustHaveResidualDebugInfo(appView.options(), definition)) {
+ return true;
+ }
+ Code code = definition.getCode();
+ // If the dex code is a single PC code then that also qualifies as having at most one
+ // position.
+ return code.isDexCode() && code.asDexCode().instructions.length == 1;
+ }
+
+ private ClassNaming.Builder getBuilder() {
+ if (builder == null) {
+ builder =
+ classNameMapperBuilder.classNamingBuilder(
+ renamedName,
+ originalType.toSourceString(),
+ com.android.tools.r8.position.Position.UNKNOWN);
+ }
+ return builder;
+ }
+ }
+
+ private enum MappedPositionRange {
+ // Single line represent a mapping on the form X:X:<method>:Y:Y.
+ SINGLE_LINE,
+ // Range to single line allows for a range on the left hand side, X:X':<method>:Y:Y
+ RANGE_TO_SINGLE,
+ // Same delta is when we have a range on both sides and the delta (line mapping between them)
+ // is the same: X:X':<method>:Y:Y' and delta(X,X') = delta(Y,Y')
+ SAME_DELTA,
+ // Out of range encodes a mapping range that cannot be encoded.
+ OUT_OF_RANGE;
+
+ private boolean isSingleLine() {
+ return this == SINGLE_LINE;
+ }
+
+ private boolean isRangeToSingle() {
+ return this == RANGE_TO_SINGLE;
+ }
+
+ private boolean isOutOfRange() {
+ return this == OUT_OF_RANGE;
+ }
+
+ public MappedPositionRange canAddNextMappingToRange(
+ MappedPosition lastPosition, MappedPosition currentPosition) {
+ if (isOutOfRange()) {
+ return this;
+ }
+ // We allow for ranges being mapped to the same line but not to other ranges:
+ // 1:10:void foo():42:42 -> a
+ // is OK since retrace(a(:7)) = 42, however, the following is not OK:
+ // 1:10:void foo():42:43 -> a
+ // since retrace(a(:7)) = 49, which is not correct.
+ boolean hasSameRightHandSide =
+ lastPosition.getOriginalLine() == currentPosition.getOriginalLine();
+ if (hasSameRightHandSide) {
+ boolean hasSameLeftHandSide =
+ lastPosition.getObfuscatedLine() == currentPosition.getObfuscatedLine();
+ return hasSameLeftHandSide ? SINGLE_LINE : RANGE_TO_SINGLE;
+ }
+ if (isRangeToSingle()) {
+ // We cannot recover a delta encoding if we have had range to single encoding.
+ return OUT_OF_RANGE;
+ }
+ boolean sameDelta =
+ currentPosition.getOriginalLine() - lastPosition.getOriginalLine()
+ == currentPosition.getObfuscatedLine() - lastPosition.getObfuscatedLine();
+ return sameDelta ? SAME_DELTA : OUT_OF_RANGE;
+ }
+ }
+
+ private static class OutlineFixupBuilder {
+
+ private static final int MINIFIED_POSITION_REMOVED = -1;
+
+ private final MethodReference outlineMethod;
+ private List<MappedPosition> mappedOutlinePositions = null;
+ private final List<Pair<MappedRange, Int2IntMap>> mappedOutlineCalleePositions =
+ new ArrayList<>();
+
+ private OutlineFixupBuilder(MethodReference outlineMethod) {
+ this.outlineMethod = outlineMethod;
+ }
+
+ public void setMappedPositionsOutline(List<MappedPosition> mappedPositionsOutline) {
+ this.mappedOutlinePositions = mappedPositionsOutline;
+ }
+
+ public void addMappedRangeForOutlineCallee(
+ MappedRange mappedRangeForOutline, Int2IntMap calleePositions) {
+ mappedOutlineCalleePositions.add(Pair.create(mappedRangeForOutline, calleePositions));
+ }
+
+ public void fixup() {
+ if (mappedOutlinePositions == null || mappedOutlineCalleePositions.isEmpty()) {
+ assert mappedOutlinePositions != null : "Mapped outline positions is null";
+ assert false : "Mapped outline positions is empty";
+ return;
+ }
+ for (Pair<MappedRange, Int2IntMap> mappingInfo : mappedOutlineCalleePositions) {
+ MappedRange mappedRange = mappingInfo.getFirst();
+ Int2IntMap positions = mappingInfo.getSecond();
+ Int2IntSortedMap map = new Int2IntLinkedOpenHashMap();
+ positions.forEach(
+ (outlinePosition, calleePosition) -> {
+ int minifiedLinePosition =
+ getMinifiedLinePosition(outlinePosition, mappedOutlinePositions);
+ if (minifiedLinePosition != MINIFIED_POSITION_REMOVED) {
+ map.put(minifiedLinePosition, (int) calleePosition);
+ }
+ });
+ mappedRange.addMappingInformation(
+ OutlineCallsiteMappingInformation.create(map, outlineMethod), Unreachable::raise);
+ }
+ }
+
+ private int getMinifiedLinePosition(
+ int originalPosition, List<MappedPosition> mappedPositions) {
+ for (MappedPosition mappedPosition : mappedPositions) {
+ if (mappedPosition.getOriginalLine() == originalPosition) {
+ return mappedPosition.getObfuscatedLine();
+ }
+ }
+ return MINIFIED_POSITION_REMOVED;
+ }
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/utils/positions/PositionRemapper.java b/src/main/java/com/android/tools/r8/utils/positions/PositionRemapper.java
new file mode 100644
index 0000000..3f0288f
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/positions/PositionRemapper.java
@@ -0,0 +1,217 @@
+// Copyright (c) 2022, 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.utils.positions;
+
+import com.android.tools.r8.ResourceException;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexString;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.DexValue.DexValueString;
+import com.android.tools.r8.ir.code.Position;
+import com.android.tools.r8.ir.code.Position.SourcePosition;
+import com.android.tools.r8.kotlin.KotlinSourceDebugExtensionParser;
+import com.android.tools.r8.kotlin.KotlinSourceDebugExtensionParser.Result;
+import com.android.tools.r8.utils.CfLineToMethodMapper;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.InternalOptions.LineNumberOptimization;
+import com.android.tools.r8.utils.Pair;
+import java.util.IdentityHashMap;
+import java.util.Map;
+import java.util.Map.Entry;
+
+// PositionRemapper is a stateful function which takes a position (represented by a
+// DexDebugPositionState) and returns a remapped Position.
+public interface PositionRemapper {
+
+ Pair<Position, Position> createRemappedPosition(Position position);
+
+ static PositionRemapper getPositionRemapper(
+ AppView<?> appView, CfLineToMethodMapper cfLineToMethodMapper) {
+ boolean identityMapping =
+ appView.options().lineNumberOptimization == LineNumberOptimization.OFF;
+ PositionRemapper positionRemapper =
+ identityMapping
+ ? new IdentityPositionRemapper()
+ : new OptimizingPositionRemapper(appView.options());
+
+ // Kotlin inline functions and arguments have their inlining information stored in the
+ // source debug extension annotation. Instantiate the kotlin remapper on top of the original
+ // remapper to allow for remapping original positions to kotlin inline positions.
+ return new KotlinInlineFunctionPositionRemapper(
+ appView, positionRemapper, cfLineToMethodMapper);
+ }
+
+ void setCurrentMethod(DexEncodedMethod definition);
+
+ class IdentityPositionRemapper implements PositionRemapper {
+
+ @Override
+ public Pair<Position, Position> createRemappedPosition(Position position) {
+ // If we create outline calls we have to map them.
+ assert position.getOutlineCallee() == null;
+ return new Pair<>(position, position);
+ }
+
+ @Override
+ public void setCurrentMethod(DexEncodedMethod definition) {
+ // This has no effect.
+ }
+ }
+
+ class OptimizingPositionRemapper implements PositionRemapper {
+ private final int maxLineDelta;
+ private DexMethod previousMethod = null;
+ private int previousSourceLine = -1;
+ private int nextOptimizedLineNumber = 1;
+
+ OptimizingPositionRemapper(InternalOptions options) {
+ // TODO(113198295): For dex using "Constants.DBG_LINE_RANGE + Constants.DBG_LINE_BASE"
+ // instead of 1 creates a ~30% smaller map file but the dex files gets larger due to reduced
+ // debug info canonicalization.
+ maxLineDelta = options.isGeneratingClassFiles() ? Integer.MAX_VALUE : 1;
+ }
+
+ @Override
+ public Pair<Position, Position> createRemappedPosition(Position position) {
+ assert position.getMethod() != null;
+ if (previousMethod == position.getMethod()) {
+ assert previousSourceLine >= 0;
+ if (position.getLine() > previousSourceLine
+ && position.getLine() - previousSourceLine <= maxLineDelta) {
+ nextOptimizedLineNumber += (position.getLine() - previousSourceLine) - 1;
+ }
+ }
+
+ Position newPosition =
+ position
+ .builderWithCopy()
+ .setLine(nextOptimizedLineNumber++)
+ .setCallerPosition(null)
+ .build();
+ previousSourceLine = position.getLine();
+ previousMethod = position.getMethod();
+ return new Pair<>(position, newPosition);
+ }
+
+ @Override
+ public void setCurrentMethod(DexEncodedMethod definition) {
+ // This has no effect.
+ }
+ }
+
+ class KotlinInlineFunctionPositionRemapper implements PositionRemapper {
+
+ private final AppView<?> appView;
+ private final DexItemFactory factory;
+ private final Map<DexType, Result> parsedKotlinSourceDebugExtensions = new IdentityHashMap<>();
+ private final CfLineToMethodMapper lineToMethodMapper;
+ private final PositionRemapper baseRemapper;
+
+ // Fields for the current context.
+ private DexEncodedMethod currentMethod;
+ private Result parsedData = null;
+
+ private KotlinInlineFunctionPositionRemapper(
+ AppView<?> appView,
+ PositionRemapper baseRemapper,
+ CfLineToMethodMapper lineToMethodMapper) {
+ this.appView = appView;
+ this.factory = appView.dexItemFactory();
+ this.baseRemapper = baseRemapper;
+ this.lineToMethodMapper = lineToMethodMapper;
+ }
+
+ @Override
+ public Pair<Position, Position> createRemappedPosition(Position position) {
+ assert currentMethod != null;
+ int line = position.getLine();
+ Result parsedData = getAndParseSourceDebugExtension(position.getMethod().holder);
+ if (parsedData == null) {
+ return baseRemapper.createRemappedPosition(position);
+ }
+ Map.Entry<Integer, KotlinSourceDebugExtensionParser.Position> inlinedPosition =
+ parsedData.lookupInlinedPosition(line);
+ if (inlinedPosition == null) {
+ return baseRemapper.createRemappedPosition(position);
+ }
+ int inlineeLineDelta = line - inlinedPosition.getKey();
+ int originalInlineeLine = inlinedPosition.getValue().getRange().from + inlineeLineDelta;
+ try {
+ String binaryName = inlinedPosition.getValue().getSource().getPath();
+ String nameAndDescriptor =
+ lineToMethodMapper.lookupNameAndDescriptor(binaryName, originalInlineeLine);
+ if (nameAndDescriptor == null) {
+ return baseRemapper.createRemappedPosition(position);
+ }
+ String clazzDescriptor = DescriptorUtils.getDescriptorFromClassBinaryName(binaryName);
+ String methodName = CfLineToMethodMapper.getName(nameAndDescriptor);
+ String methodDescriptor = CfLineToMethodMapper.getDescriptor(nameAndDescriptor);
+ String returnTypeDescriptor = DescriptorUtils.getReturnTypeDescriptor(methodDescriptor);
+ String[] argumentDescriptors = DescriptorUtils.getArgumentTypeDescriptors(methodDescriptor);
+ DexString[] argumentDexStringDescriptors = new DexString[argumentDescriptors.length];
+ for (int i = 0; i < argumentDescriptors.length; i++) {
+ argumentDexStringDescriptors[i] = factory.createString(argumentDescriptors[i]);
+ }
+ DexMethod inlinee =
+ factory.createMethod(
+ factory.createString(clazzDescriptor),
+ factory.createString(methodName),
+ factory.createString(returnTypeDescriptor),
+ argumentDexStringDescriptors);
+ if (!inlinee.equals(position.getMethod())) {
+ // We have an inline from a different method than the current position.
+ Entry<Integer, KotlinSourceDebugExtensionParser.Position> calleePosition =
+ parsedData.lookupCalleePosition(line);
+ if (calleePosition != null) {
+ // Take the first line as the callee position
+ position =
+ position
+ .builderWithCopy()
+ .setLine(calleePosition.getValue().getRange().from)
+ .build();
+ }
+ return baseRemapper.createRemappedPosition(
+ SourcePosition.builder()
+ .setLine(originalInlineeLine)
+ .setMethod(inlinee)
+ .setCallerPosition(position)
+ .build());
+ }
+ // This is the same position, so we should really not mark this as an inline position. Fall
+ // through to the default case.
+ } catch (ResourceException ignored) {
+ // Intentionally left empty. Remapping of kotlin functions utility is a best effort mapping.
+ }
+ return baseRemapper.createRemappedPosition(position);
+ }
+
+ private Result getAndParseSourceDebugExtension(DexType holder) {
+ if (parsedData == null) {
+ parsedData = parsedKotlinSourceDebugExtensions.get(holder);
+ }
+ if (parsedData != null || parsedKotlinSourceDebugExtensions.containsKey(holder)) {
+ return parsedData;
+ }
+ DexClass clazz = appView.definitionFor(currentMethod.getHolderType());
+ DexValueString dexValueString = appView.getSourceDebugExtensionForType(clazz);
+ if (dexValueString != null) {
+ parsedData = KotlinSourceDebugExtensionParser.parse(dexValueString.value.toString());
+ }
+ parsedKotlinSourceDebugExtensions.put(holder, parsedData);
+ return parsedData;
+ }
+
+ @Override
+ public void setCurrentMethod(DexEncodedMethod method) {
+ this.currentMethod = method;
+ this.parsedData = null;
+ }
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/utils/positions/PositionToMappedRangeMapper.java b/src/main/java/com/android/tools/r8/utils/positions/PositionToMappedRangeMapper.java
new file mode 100644
index 0000000..e4cb801
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/positions/PositionToMappedRangeMapper.java
@@ -0,0 +1,216 @@
+// Copyright (c) 2022, 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.utils.positions;
+
+import com.android.tools.r8.debuginfo.DebugRepresentation;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexCode;
+import com.android.tools.r8.graph.DexDebugInfo;
+import com.android.tools.r8.graph.DexDebugInfoForSingleLineMethod;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.ProgramMethod;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+
+public interface PositionToMappedRangeMapper {
+
+ List<MappedPosition> getMappedPositions(
+ ProgramMethod method,
+ PositionRemapper positionRemapper,
+ boolean hasOverloads,
+ boolean canUseDexPc,
+ int pcEncodingCutoff);
+
+ void updateDebugInfoInCodeObjects();
+
+ static PositionToMappedRangeMapper create(AppView<?> appView) {
+ return appView.options().isGeneratingClassFiles()
+ ? new ClassFilePositionToMappedRangeMapper(appView)
+ : new DexPositionToMappedRangeMapper(appView);
+ }
+
+ class DexPositionToMappedRangeMapper implements PositionToMappedRangeMapper {
+
+ private final DexPositionToNoPcMappedRangeMapper noPcMapper;
+ private final DexPositionToPcMappedRangeMapper pcMapper;
+
+ private final PcBasedDebugInfoRecorder pcBasedDebugInfoRecorder;
+
+ private DexPositionToMappedRangeMapper(AppView<?> appView) {
+ pcBasedDebugInfoRecorder =
+ appView.options().canUseNativeDexPcInsteadOfDebugInfo()
+ ? new NativePcSupport()
+ : new Pc2PcMappingSupport(appView.options().allowDiscardingResidualDebugInfo());
+ noPcMapper = new DexPositionToNoPcMappedRangeMapper(appView);
+ pcMapper = new DexPositionToPcMappedRangeMapper(appView, pcBasedDebugInfoRecorder);
+ }
+
+ @Override
+ public List<MappedPosition> getMappedPositions(
+ ProgramMethod method,
+ PositionRemapper positionRemapper,
+ boolean hasOverloads,
+ boolean canUseDexPc,
+ int pcEncodingCutoff) {
+ List<MappedPosition> mappedPositions =
+ canUseDexPc
+ ? pcMapper.optimizeDexCodePositionsForPc(method, positionRemapper, pcEncodingCutoff)
+ : noPcMapper.optimizeDexCodePositions(method, positionRemapper, hasOverloads);
+ DexEncodedMethod definition = method.getDefinition();
+ if (definition.getCode().isDexCode()
+ && definition.getCode().asDexCode().getDebugInfo()
+ == DexDebugInfoForSingleLineMethod.getInstance()) {
+ pcBasedDebugInfoRecorder.recordSingleLineFor(method, pcEncodingCutoff);
+ }
+ return mappedPositions;
+ }
+
+ @Override
+ public void updateDebugInfoInCodeObjects() {
+ pcBasedDebugInfoRecorder.updateDebugInfoInCodeObjects();
+ }
+ }
+
+ interface PcBasedDebugInfoRecorder {
+ /** Callback to record a code object with a given max instruction PC and parameter count. */
+ void recordPcMappingFor(ProgramMethod method, int maxEncodingPc);
+
+ /** Callback to record a code object with only a single "line". */
+ void recordSingleLineFor(ProgramMethod method, int maxEncodingPc);
+
+ /**
+ * Install the correct debug info objects.
+ *
+ * <p>Must be called after all recordings have been given to allow computing the debug info
+ * items to be installed.
+ */
+ void updateDebugInfoInCodeObjects();
+
+ int getPcEncoding(int pc);
+ }
+
+ class Pc2PcMappingSupport implements PcBasedDebugInfoRecorder {
+
+ private static class UpdateInfo {
+ final DexCode code;
+ final int paramCount;
+ final int maxEncodingPc;
+
+ public UpdateInfo(DexCode code, int paramCount, int maxEncodingPc) {
+ this.code = code;
+ this.paramCount = paramCount;
+ this.maxEncodingPc = maxEncodingPc;
+ }
+
+ // Used as key when building the shared debug info map.
+ // Only param and max-pc are part of the key.
+
+ @Override
+ public boolean equals(Object o) {
+ UpdateInfo that = (UpdateInfo) o;
+ return paramCount == that.paramCount && maxEncodingPc == that.maxEncodingPc;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(paramCount, maxEncodingPc);
+ }
+ }
+
+ private final List<UpdateInfo> codesToUpdate = new ArrayList<>();
+
+ // We can only drop single-line debug info if it is OK to lose the source-file info.
+ // This list is null if we must retain single-line entries.
+ private final List<DexCode> singleLineCodesToClear;
+
+ public Pc2PcMappingSupport(boolean allowDiscardingSourceFile) {
+ singleLineCodesToClear = allowDiscardingSourceFile ? new ArrayList<>() : null;
+ }
+
+ @Override
+ public int getPcEncoding(int pc) {
+ assert pc >= 0;
+ return pc + 1;
+ }
+
+ private boolean cantAddToClearSet(ProgramMethod method) {
+ assert method.getDefinition().getCode().isDexCode();
+ if (singleLineCodesToClear == null) {
+ return true;
+ }
+ singleLineCodesToClear.add(method.getDefinition().getCode().asDexCode());
+ return false;
+ }
+
+ @Override
+ public void recordPcMappingFor(ProgramMethod method, int maxEncodingPc) {
+ assert method.getDefinition().getCode().isDexCode();
+ int parameterCount = method.getParameters().size();
+ DexCode code = method.getDefinition().getCode().asDexCode();
+ assert DebugRepresentation.verifyLastExecutableInstructionWithinBound(code, maxEncodingPc);
+ codesToUpdate.add(new UpdateInfo(code, parameterCount, maxEncodingPc));
+ }
+
+ @Override
+ public void recordSingleLineFor(ProgramMethod method, int maxEncodingPc) {
+ if (cantAddToClearSet(method)) {
+ recordPcMappingFor(method, maxEncodingPc);
+ }
+ }
+
+ @Override
+ public void updateDebugInfoInCodeObjects() {
+ Map<UpdateInfo, DexDebugInfo> debugInfos = new HashMap<>();
+ codesToUpdate.forEach(
+ entry -> {
+ assert DebugRepresentation.verifyLastExecutableInstructionWithinBound(
+ entry.code, entry.maxEncodingPc);
+ DexDebugInfo debugInfo =
+ debugInfos.computeIfAbsent(entry, Pc2PcMappingSupport::buildPc2PcDebugInfo);
+ assert debugInfo.asPcBasedInfo().getMaxPc() == entry.maxEncodingPc;
+ entry.code.setDebugInfo(debugInfo);
+ });
+ if (singleLineCodesToClear != null) {
+ singleLineCodesToClear.forEach(c -> c.setDebugInfo(null));
+ }
+ }
+
+ private static DexDebugInfo buildPc2PcDebugInfo(UpdateInfo info) {
+ return new DexDebugInfo.PcBasedDebugInfo(info.paramCount, info.maxEncodingPc);
+ }
+ }
+
+ class NativePcSupport implements PcBasedDebugInfoRecorder {
+
+ @Override
+ public int getPcEncoding(int pc) {
+ assert pc >= 0;
+ return pc;
+ }
+
+ private void clearDebugInfo(ProgramMethod method) {
+ // Always strip the info in full as the runtime will emit the PC directly.
+ method.getDefinition().getCode().asDexCode().setDebugInfo(null);
+ }
+
+ @Override
+ public void recordPcMappingFor(ProgramMethod method, int maxEncodingPc) {
+ clearDebugInfo(method);
+ }
+
+ @Override
+ public void recordSingleLineFor(ProgramMethod method, int maxEncodingPc) {
+ clearDebugInfo(method);
+ }
+
+ @Override
+ public void updateDebugInfoInCodeObjects() {
+ // Already null out the info so nothing to do.
+ }
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/utils/positions/PositionUtils.java b/src/main/java/com/android/tools/r8/utils/positions/PositionUtils.java
new file mode 100644
index 0000000..b7247e7
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/positions/PositionUtils.java
@@ -0,0 +1,83 @@
+// Copyright (c) 2022, 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.utils.positions;
+
+import com.android.tools.r8.cf.code.CfInstruction;
+import com.android.tools.r8.cf.code.CfPosition;
+import com.android.tools.r8.graph.CfCode;
+import com.android.tools.r8.graph.Code;
+import com.android.tools.r8.graph.DexCode;
+import com.android.tools.r8.graph.DexDebugEvent;
+import com.android.tools.r8.graph.DexDebugInfo;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.ir.code.Position;
+import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.Pair;
+import java.util.List;
+
+public class PositionUtils {
+
+ public static Position remapAndAdd(
+ Position position, PositionRemapper remapper, List<MappedPosition> mappedPositions) {
+ Pair<Position, Position> remappedPosition = remapper.createRemappedPosition(position);
+ Position oldPosition = remappedPosition.getFirst();
+ Position newPosition = remappedPosition.getSecond();
+ mappedPositions.add(
+ new MappedPosition(
+ oldPosition.getMethod(),
+ oldPosition.getLine(),
+ oldPosition.getCallerPosition(),
+ newPosition.getLine(),
+ oldPosition.isOutline(),
+ oldPosition.getOutlineCallee(),
+ oldPosition.getOutlinePositions()));
+ return newPosition;
+ }
+
+ public static boolean mustHaveResidualDebugInfo(
+ InternalOptions options, DexEncodedMethod method) {
+ Code code = method.getCode();
+ if (code == null) {
+ return false;
+ }
+ if (code.isDexCode()) {
+ return mustHaveResidualDebugInfo(options, code.asDexCode());
+ } else if (code.isCfCode()) {
+ return mustHaveResidualDebugInfo(code.asCfCode());
+ }
+ return false;
+ }
+
+ private static boolean mustHaveResidualDebugInfo(InternalOptions options, DexCode code) {
+ // All code objects must have debug info if discarding it is not allowed.
+ if (!options.allowDiscardingResidualDebugInfo()) {
+ return true;
+ }
+ // Otherwise debug info is only needed for code sequences with at least one position.
+ DexDebugInfo debugInfo = code.getDebugInfo();
+ if (debugInfo == null) {
+ return false;
+ }
+ if (debugInfo.isPcBasedInfo()) {
+ return true;
+ }
+ for (DexDebugEvent event : debugInfo.asEventBasedInfo().events) {
+ if (event instanceof DexDebugEvent.Default) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private static boolean mustHaveResidualDebugInfo(CfCode code) {
+ List<CfInstruction> instructions = code.getInstructions();
+ for (CfInstruction instruction : instructions) {
+ if (instruction instanceof CfPosition) {
+ return true;
+ }
+ }
+ return false;
+ }
+}