blob: b4ec7c9a781e8cb09d487bbe4e41b2330b3f2e02 [file] [log] [blame]
// 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.naming.mappinginformation.ResidualSignatureMappingInformation.ResidualFieldSignatureMappingInformation.fromDexField;
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.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;
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.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.OneShotCollectionConsumer;
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.Arrays;
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;
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;
classNameMapperBuilder = ClassNameMapper.builder();
classNameMapperBuilder.setCurrentMapVersion(
appView.options().getMapFileVersion().toMapVersionMappingInformation());
}
public static int getMaxLineNumber() {
return MAX_LINE_NUMBER;
}
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();
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) {
getBuilder()
.addMappingInformation(
FileNameInformation.build(originalSourceFile.toSourceString()), Unreachable::raise);
}
return this;
}
public MappedPositionToClassNamingBuilder addSynthetic(SyntheticItems syntheticItems) {
if (syntheticItems.isSyntheticClass(clazz)) {
getBuilder()
.addMappingInformation(
CompilerSynthesizedMappingInformation.getInstance(), Unreachable::raise);
}
return this;
}
private MappedPositionToClassNamingBuilder addFields() {
MapVersion mapFileVersion = appView.options().getMapFileVersion();
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);
FieldSignature residualSignature = FieldSignature.fromDexField(residualField);
MemberNaming memberNaming = new MemberNaming(originalSignature, residualSignature);
if (ResidualSignatureMappingInformation.isSupported(mapFileVersion)
&& !originalSignature.type.equals(residualSignature.type)) {
memberNaming.addMappingInformation(fromDexField(residualField), Unreachable::raise);
}
if (dexEncodedField.isD8R8Synthesized()) {
memberNaming.addMappingInformation(
CompilerSynthesizedMappingInformation.getInstance(), Unreachable::raise);
}
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().getOriginalMethodSignatureForMapping(method.getReference());
MethodSignature originalSignature =
MethodSignature.fromDexMethod(originalMethod, originalMethod.holder != originalType);
OneShotCollectionConsumer<MappingInformation> methodSpecificMappingInformation =
OneShotCollectionConsumer.wrap(new ArrayList<>());
// We only do global synthetic classes when using names from the library. For such classes it
// is important that we do not filter out stack frames since they could appear from concrete
// classes in the library. Additionally, this is one place where it is helpful for developers
// to also get reported synthesized frames since stubbing can change control-flow and
// exceptions.
if (isD8R8Synthesized(method, mappedPositions)
&& !appView.getSyntheticItems().isGlobalSyntheticClass(method.getHolder())) {
methodSpecificMappingInformation.add(CompilerSynthesizedMappingInformation.getInstance());
}
DexMethod residualMethod =
appView.getNamingLens().lookupMethod(method.getReference(), appView.dexItemFactory());
MapVersion mapFileVersion = appView.options().getMapFileVersion();
if (isIdentityMapping(
mapFileVersion,
mappedPositions,
methodSpecificMappingInformation,
residualMethod,
originalMethod,
originalType)) {
assert appView.options().lineNumberOptimization == LineNumberOptimization.OFF
|| hasAtMostOnePosition(appView, definition)
|| appView.isCfByteCodePassThrough(definition);
return this;
}
MethodSignature residualSignature = MethodSignature.fromDexMethod(residualMethod);
if (ResidualSignatureMappingInformation.isSupported(mapFileVersion)
&& (!originalSignature.type.equals(residualSignature.type)
|| !Arrays.equals(originalSignature.parameters, residualSignature.parameters))) {
methodSpecificMappingInformation.add(
ResidualMethodSignatureMappingInformation.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.getName());
methodSpecificMappingInformation.consume(
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).getPosition());
if (outlineMethod != null) {
outlinesToFix
.computeIfAbsent(
outlineMethod,
outline -> new OutlineFixupBuilder(computeMappedMethod(outline, appView)))
.setMappedPositionsOutline(mappedPositions);
methodSpecificMappingInformation.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 firstMappedPosition = mappedPositions.get(i);
int j = i + 1;
MappedPosition lastMappedPosition = firstMappedPosition;
MappedPositionRange mappedPositionRange = MappedPositionRange.SINGLE_LINE;
for (; j < mappedPositions.size(); j++) {
// Break if this position cannot be merged with lastPosition.
MappedPosition currentMappedPosition = mappedPositions.get(j);
mappedPositionRange =
mappedPositionRange.canAddNextMappingToRange(
lastMappedPosition, currentMappedPosition);
// Note that currentPosition.caller and lastPosition.class must be deep-compared since
// multiple inlining passes lose the canonical property of the positions.
Position currentPosition = currentMappedPosition.getPosition();
Position lastPosition = lastMappedPosition.getPosition();
if (currentPosition.getMethod() != lastPosition.getMethod()
|| mappedPositionRange.isOutOfRange()
|| !Objects.equals(
currentPosition.getCallerPosition(), lastPosition.getCallerPosition())
// Break when we see a mapped outline
|| currentPosition.getOutlineCallee() != null
// Ensure that we break when we start iterating with an outline caller again.
|| firstMappedPosition.getPosition().getOutlineCallee() != null) {
break;
}
// The mapped positions are not guaranteed to be in order, so maintain first and last
// position.
if (firstMappedPosition.getObfuscatedLine() > currentMappedPosition.getObfuscatedLine()) {
firstMappedPosition = currentMappedPosition;
}
if (lastMappedPosition.getObfuscatedLine() < currentMappedPosition.getObfuscatedLine()) {
lastMappedPosition = currentMappedPosition;
}
}
Range obfuscatedRange =
nonCardinalRangeCache.get(
firstMappedPosition.getObfuscatedLine(), lastMappedPosition.getObfuscatedLine());
Position firstPosition = firstMappedPosition.getPosition();
Position lastPosition = lastMappedPosition.getPosition();
Range originalRange =
nonCardinalRangeCache.get(firstPosition.getLine(), lastPosition.getLine());
MappedRange lastMappedRange =
getMappedRangesForPosition(
appView,
getOriginalMethodSignature,
getBuilder(),
firstPosition,
residualSignature,
obfuscatedRange,
originalRange,
prunedInlinedClasses,
cardinalRangeCache);
methodSpecificMappingInformation.consume(
info -> 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,
residualSignature,
nonCardinalRangeCache.get(
placeHolderLineToBeFixed, placeHolderLineToBeFixed),
nonCardinalRangeCache.get(position.getLine(), position.getLine()),
prunedInlinedClasses,
cardinalRangeCache);
});
outlinesToFix
.computeIfAbsent(
firstPosition.getOutlineCallee(),
outline -> new OutlineFixupBuilder(computeMappedMethod(outline, appView)))
.addMappedRangeForOutlineCallee(lastMappedRange, positionMap);
}
i = j;
}
return this;
}
private boolean isD8R8Synthesized(ProgramMethod method, List<MappedPosition> mappedPositions) {
return method.getDefinition().isD8R8Synthesized()
|| (!mappedPositions.isEmpty()
&& mappedPositions.get(0).getPosition().isD8R8Synthesized());
}
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,
Position position,
MethodSignature residualSignature,
Range obfuscatedRange,
Range originalLine,
Map<DexType, String> prunedInlineHolder,
CardinalPositionRangeAllocator cardinalRangeCache) {
MappedRange lastMappedRange = null;
int inlineFramesCount = -1;
do {
if (position.isD8R8Synthesized() && position.hasCallerPosition()) {
position = position.getCallerPosition();
continue;
}
inlineFramesCount += 1;
DexType holderType = position.getMethod().getHolderType();
String prunedClassSourceFileInfo = appView.getPrunedClassSourceFileInfo(holderType);
if (prunedClassSourceFileInfo != null) {
String originalValue = prunedInlineHolder.put(holderType, prunedClassSourceFileInfo);
assert originalValue == null || originalValue.equals(prunedClassSourceFileInfo);
}
lastMappedRange =
classNamingBuilder.addMappedRange(
obfuscatedRange,
getOriginalMethodSignature.apply(position.getMethod()),
inlineFramesCount == 0
? originalLine
: cardinalRangeCache.get(
Math.max(position.getLine(), 0)), // Prevent against "no-position".
residualSignature.getName());
if (position.isRemoveInnerFramesIfThrowingNpe()) {
lastMappedRange.addMappingInformation(
RewriteFrameMappingInformation.builder()
.addCondition(
ThrowsCondition.create(
Reference.classFromDescriptor(
appView.dexItemFactory().npeDescriptor.toString())))
.addRewriteAction(RemoveInnerFramesAction.create(inlineFramesCount))
.build(),
Unreachable::raise);
}
position = position.getCallerPosition();
} while (position != null);
assert lastMappedRange != null;
return lastMappedRange;
}
private DexMethod getOutlineMethod(Position mappedPosition) {
if (mappedPosition.isOutline()) {
return mappedPosition.getMethod();
}
Position caller = mappedPosition.getCallerPosition();
if (caller == null) {
return null;
}
Position outermostCaller = caller.getOutermostCaller();
return outermostCaller.isOutline() ? outermostCaller.getMethod() : null;
}
private boolean isIdentityMapping(
MapVersion mapFileVersion,
List<MappedPosition> mappedPositions,
OneShotCollectionConsumer<MappingInformation> methodMappingInfo,
DexMethod residualMethod,
DexMethod originalMethod,
DexType originalType) {
if (ResidualSignatureMappingInformation.isSupported(mapFileVersion)) {
// Don't emit pure identity mappings.
return mappedPositions.isEmpty()
&& methodMappingInfo.isEmpty()
&& originalMethod == residualMethod;
} else {
// Don't emit pure identity mappings.
return mappedPositions.isEmpty()
&& methodMappingInfo.isEmpty()
&& residualMethod.getName() == 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;
}
private boolean isSameDelta() {
return this == SAME_DELTA;
}
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.
int currentOriginalLine = currentPosition.getPosition().getLine();
int lastOriginalLine = lastPosition.getPosition().getLine();
boolean hasSameRightHandSide = lastOriginalLine == currentOriginalLine;
if (hasSameRightHandSide) {
if (isSameDelta()) {
return OUT_OF_RANGE;
}
boolean hasSameLeftHandSide =
lastPosition.getObfuscatedLine() == currentPosition.getObfuscatedLine();
return (hasSameLeftHandSide && isSingleLine()) ? 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 =
currentOriginalLine - lastOriginalLine
== 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.getPosition().getLine() == originalPosition) {
return mappedPosition.getObfuscatedLine();
}
}
return MINIFIED_POSITION_REMOVED;
}
}
}