blob: 2fcbd3dbcf3f85216c9a529d9234a967aaecf7c3 [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.naming;
import static com.android.tools.r8.utils.FunctionUtils.ignoreArgument;
import com.android.tools.r8.naming.ClassNamingForNameMapper.MappedRange;
import com.android.tools.r8.naming.ClassNamingForNameMapper.MappedRangesOfName;
import com.android.tools.r8.naming.MemberNaming.MethodSignature;
import com.android.tools.r8.utils.ChainableStringConsumer;
import com.android.tools.r8.utils.ListUtils;
import com.android.tools.r8.utils.SegmentTree;
import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
import it.unimi.dsi.fastutil.ints.Int2ReferenceOpenHashMap;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
public class ComposingBuilder {
private final Map<String, ComposingClassBuilder> mapping = new HashMap<>();
public void compose(ClassNamingForNameMapper classMapping) throws MappingComposeException {
String originalName = classMapping.originalName;
ComposingClassBuilder composingClassBuilder = mapping.get(originalName);
String renamedName = classMapping.renamedName;
if (composingClassBuilder == null) {
composingClassBuilder = new ComposingClassBuilder(originalName, renamedName);
} else {
composingClassBuilder.setRenamedName(renamedName);
mapping.remove(originalName);
}
ComposingClassBuilder previousMapping = mapping.put(renamedName, composingClassBuilder);
if (previousMapping != null) {
throw new MappingComposeException(
"Duplicate class mapping. Both '"
+ previousMapping.getOriginalName()
+ "' and '"
+ originalName
+ "' maps to '"
+ renamedName
+ "'.");
}
composingClassBuilder.compose(classMapping);
}
@Override
public String toString() {
List<ComposingClassBuilder> classBuilders = new ArrayList<>(mapping.values());
classBuilders.sort(Comparator.comparing(ComposingClassBuilder::getOriginalName));
StringBuilder sb = new StringBuilder();
ChainableStringConsumer wrap = ChainableStringConsumer.wrap(sb::append);
for (ComposingClassBuilder classBuilder : classBuilders) {
classBuilder.write(wrap);
}
return sb.toString();
}
public static class ComposingClassBuilder {
private static final String INDENTATION = " ";
private static final int NO_RANGE_FROM = -1;
private final String originalName;
private String renamedName;
private final Map<String, List<MemberNaming>> fieldMembers = new HashMap<>();
// Ideally we would have liked to use the signature as a key since this uniquely specifies a
// method. However, because a mapping file has no right hand side, we therefore assume that a
// starting position uniquely identifies a method. If no position is given there can be only
// one method since any shrinker should put in line numbers for overloads.
private final Map<String, SegmentTree<List<MappedRange>>> methodMembers = new HashMap<>();
private ComposingClassBuilder(String originalName, String renamedName) {
this.originalName = originalName;
this.renamedName = renamedName;
}
public void setRenamedName(String renamedName) {
this.renamedName = renamedName;
}
public String getOriginalName() {
return originalName;
}
public String getRenamedName() {
return renamedName;
}
public void compose(ClassNamingForNameMapper mapper) throws MappingComposeException {
composeFieldNamings(mapper);
composeMethodNamings(mapper);
}
private void composeFieldNamings(ClassNamingForNameMapper mapper)
throws MappingComposeException {
mapper.forAllFieldNaming(
fieldNaming -> {
List<MemberNaming> memberNamings = fieldMembers.get(fieldNaming.getOriginalName());
if (memberNamings == null) {
fieldMembers
.computeIfAbsent(fieldNaming.getRenamedName(), ignoreArgument(ArrayList::new))
.add(fieldNaming);
return;
}
// There is no right-hand side of field mappings thus if we have seen an existing
// mapping we cannot compose the type. For fields we check that the original type is
// the same or we throw an error since we cannot guarantee a proper composition.
for (int i = 0; i < memberNamings.size(); i++) {
MemberNaming memberNaming = memberNamings.get(i);
assert memberNaming.getRenamedName().equals(fieldNaming.getOriginalName());
if (memberNaming.renamedSignature.equals(fieldNaming.getOriginalSignature())) {
memberNamings.set(
i,
new MemberNaming(
memberNaming.getOriginalSignature(), fieldNaming.getRenamedName()));
return;
}
}
throw new MappingComposeException(
"Unable to compose field naming '"
+ fieldNaming
+ "' since the original type has changed.");
});
}
private void composeMethodNamings(ClassNamingForNameMapper mapper)
throws MappingComposeException {
for (Entry<String, MappedRangesOfName> entry : mapper.mappedRangesByRenamedName.entrySet()) {
MappedRangesOfName value = entry.getValue();
List<MappedRange> mappedRanges = value.getMappedRanges();
MappedRangeResult mappedRangeResult;
int index = 0;
while ((mappedRangeResult = getMappedRangesForMethod(mappedRanges, index)) != null) {
index = mappedRangeResult.endIndex;
MappedRange newMappedRange = mappedRangeResult.lastRange;
String originalName = newMappedRange.signature.getName();
SegmentTree<List<MappedRange>> listSegmentTree = methodMembers.get(originalName);
List<MappedRange> existingMappedRanges = null;
if (listSegmentTree != null) {
Entry<Integer, List<MappedRange>> existingEntry =
listSegmentTree.findEntry(mappedRangeResult.startOriginalPosition);
// We assume that all new minified ranges for a method are rewritten in the new map
// such that no previous existing positions exists.
if (existingEntry != null) {
listSegmentTree.removeSegment(existingEntry.getKey());
existingMappedRanges = existingEntry.getValue();
}
}
Range minifiedRange = mappedRangeResult.lastRange.minifiedRange;
int endMinifiedPosition = minifiedRange == null ? NO_RANGE_FROM : minifiedRange.to;
methodMembers
.computeIfAbsent(newMappedRange.renamedName, ignored -> new SegmentTree<>(false))
.add(
mappedRangeResult.startMinifiedPosition,
endMinifiedPosition,
composeMappedRangesForMethod(existingMappedRanges, mappedRangeResult.allRanges));
}
}
}
/***
* Iterates over mapped ranges in order, starting from index, and adds to an internal result as
* long as the current mapped range is the same method and return a mapped range result
* containing all ranges for a method along with some additional information.
*/
private MappedRangeResult getMappedRangesForMethod(List<MappedRange> mappedRanges, int index) {
if (index >= mappedRanges.size()) {
return null;
}
List<MappedRange> seenMappedRanges = new ArrayList<>();
MappedRange lastSeen = mappedRanges.get(index);
seenMappedRanges.add(lastSeen);
if (lastSeen.minifiedRange == null) {
return new MappedRangeResult(
NO_RANGE_FROM, NO_RANGE_FROM, index + 1, lastSeen, seenMappedRanges);
}
int startMinifiedPosition = lastSeen.minifiedRange.from;
int startOriginalPosition =
lastSeen.originalRange == null ? NO_RANGE_FROM : lastSeen.originalRange.from;
for (int i = index + 1; i < mappedRanges.size(); i++) {
MappedRange thisMappedRange = mappedRanges.get(i);
// We assume that if we see a mapping where the minified range is 0 then we will not find
// another mapping where it is not null.
if (thisMappedRange.minifiedRange == null) {
assert !lastSeen.signature.equals(thisMappedRange.signature);
break;
}
// Otherwise break if we see a signature that is not equal to the current one and it
// has another minified range meaning it is not an inlinee.
if (!thisMappedRange.signature.equals(lastSeen.signature)
&& !thisMappedRange.minifiedRange.equals(lastSeen.minifiedRange)) {
break;
}
seenMappedRanges.add(thisMappedRange);
lastSeen = thisMappedRange;
}
return new MappedRangeResult(
startMinifiedPosition,
startOriginalPosition,
index + seenMappedRanges.size(),
lastSeen,
seenMappedRanges);
}
private List<MappedRange> composeMappedRangesForMethod(
List<MappedRange> existingRanges, List<MappedRange> newRanges)
throws MappingComposeException {
assert !newRanges.isEmpty();
if (existingRanges == null || existingRanges.isEmpty()) {
return newRanges;
}
// The new minified ranges may have ranges that range over positions that has additional
// information, such as inlinees:
// Existing:
// 1:2:void caller():14:15 -> x
// 3:3:void inlinee():42:42 -> x
// 3:3:void caller():16:16 -> x
// 4:10:void caller():17:23 -> x
// ...
// New mapping:
// 1:5:void x():1:5 -> y
// 6:10:void x():6:6 -> y
// ...
// It is important that we therefore split up the new ranges to map everything back to the
// existing original mappings:
// Resulting mapping:
// 1:2:void caller():14:15 -> y
// 3:3:void inlinee():42:42 -> y
// 3:3:void caller():16:16 -> y
// 4:5:void caller():17:18 -> y
// 6:10:void caller():19:19 -> y
// ...
Int2ReferenceMap<List<MappedRange>> mappedRangesForPosition =
getExistingMapping(existingRanges);
List<MappedRange> newComposedRanges = new ArrayList<>();
for (int i = 0; i < newRanges.size(); i++) {
if (isInlineMappedRange(newRanges, i)) {
throw new MappingComposeException(
"Unexpected inline frame '" + existingRanges.get(i) + "' in composing map.");
}
MappedRange newRange = newRanges.get(i);
if (newRange.originalRange == null) {
List<MappedRange> existingMappedRanges = mappedRangesForPosition.get(NO_RANGE_FROM);
assert existingMappedRanges.size() <= 1;
if (existingMappedRanges.isEmpty()) {
newComposedRanges.add(newRange);
} else {
MappedRange existingRange = existingMappedRanges.get(0);
newComposedRanges.add(
new MappedRange(
newRange.minifiedRange,
existingRange.signature,
existingRange.originalRange,
newRange.renamedName));
}
} else {
// First check if the original range matches the existing minified range.
List<MappedRange> existingMappedRanges =
mappedRangesForPosition.get(newRange.originalRange.from);
if (existingMappedRanges == null) {
throw new MappingComposeException(
"Unexpected missing original position for '" + newRange + "'.");
}
if (ListUtils.last(existingMappedRanges).minifiedRange.equals(newRange.originalRange)) {
computeComposedMappedRange(
newComposedRanges,
newRange,
existingMappedRanges,
newRange.minifiedRange.from,
newRange.minifiedRange.to);
} else {
// Otherwise, we have a situation where the minified range references over multiple
// existing ranges. We simply chop op when the range changes on the right hand side. To
// ensure we do not mess up the spans from the original range, we have to check if the
// current starting position is inside an original range, and then chop it off.
// Similarly, when writing the last block, we have to cut it off to match.
int lastStartingMinifiedFrom = newRange.minifiedRange.from;
for (int position = newRange.minifiedRange.from;
position <= newRange.minifiedRange.to;
position++) {
List<MappedRange> existingMappedRangesForPosition =
mappedRangesForPosition.get(newRange.getOriginalLineNumber(position));
if (existingMappedRangesForPosition == null) {
throw new MappingComposeException(
"Unexpected missing original position for '" + newRange + "'.");
}
if (!ListUtils.last(existingMappedRanges)
.minifiedRange
.equals(ListUtils.last(existingMappedRangesForPosition).minifiedRange)) {
computeComposedMappedRange(
newComposedRanges,
newRange,
existingMappedRanges,
lastStartingMinifiedFrom,
position - 1);
lastStartingMinifiedFrom = position;
existingMappedRanges = existingMappedRangesForPosition;
}
}
computeComposedMappedRange(
newComposedRanges,
newRange,
existingMappedRanges,
lastStartingMinifiedFrom,
newRange.minifiedRange.to);
}
}
}
return newComposedRanges;
}
/***
* Builds a position to mapped ranges for mappings for looking up all mapped ranges for a given
* position.
*/
private Int2ReferenceMap<List<MappedRange>> getExistingMapping(
List<MappedRange> existingRanges) {
Int2ReferenceMap<List<MappedRange>> mappedRangesForPosition =
new Int2ReferenceOpenHashMap<>();
List<MappedRange> currentRangesForPosition = new ArrayList<>();
for (int i = 0; i < existingRanges.size(); i++) {
MappedRange mappedRange = existingRanges.get(i);
currentRangesForPosition.add(mappedRange);
if (!isInlineMappedRange(existingRanges, i)) {
if (mappedRange.minifiedRange == null) {
mappedRangesForPosition.put(NO_RANGE_FROM, currentRangesForPosition);
} else {
for (int position = mappedRange.minifiedRange.from;
position <= mappedRange.minifiedRange.to;
position++) {
mappedRangesForPosition.put(position, currentRangesForPosition);
}
}
currentRangesForPosition = new ArrayList<>();
}
}
return mappedRangesForPosition;
}
private void computeComposedMappedRange(
List<MappedRange> newComposedRanges,
MappedRange newMappedRange,
List<MappedRange> existingMappedRanges,
int lastStartingMinifiedFrom,
int position) {
Range existingRange = existingMappedRanges.get(0).minifiedRange;
assert existingMappedRanges.stream().allMatch(x -> x.minifiedRange.equals(existingRange));
Range newMinifiedRange = new Range(lastStartingMinifiedFrom, position);
boolean copyOriginalRange = existingRange.equals(newMappedRange.originalRange);
for (MappedRange existingMappedRange : existingMappedRanges) {
Range existingOriginalRange = existingMappedRange.originalRange;
Range newOriginalRange;
if (copyOriginalRange || existingOriginalRange.span() == 1) {
newOriginalRange = existingOriginalRange;
} else {
// Find the window that the new range points to into the original range.
int existingMinifiedPos = newMappedRange.getOriginalLineNumber(lastStartingMinifiedFrom);
int newOriginalStart = existingMappedRange.getOriginalLineNumber(existingMinifiedPos);
if (newMappedRange.originalRange.span() == 1) {
newOriginalRange = new Range(newOriginalStart, newOriginalStart);
} else {
assert newMinifiedRange.span() <= existingOriginalRange.span();
newOriginalRange =
new Range(newOriginalStart, newOriginalStart + newMinifiedRange.span() - 1);
}
}
newComposedRanges.add(
new MappedRange(
newMinifiedRange,
existingMappedRange.signature,
newOriginalRange,
newMappedRange.renamedName));
}
}
private boolean isInlineMappedRange(List<MappedRange> mappedRanges, int index) {
if (index + 1 >= mappedRanges.size()) {
return false;
}
return mappedRanges
.get(index)
.minifiedRange
.equals(mappedRanges.get(index + 1).minifiedRange);
}
public void write(ChainableStringConsumer consumer) {
consumer.accept(originalName).accept(" -> ").accept(renamedName).accept(":\n");
// TODO(b/241763080): Support mapping information.
writeFields(consumer);
writeMethods(consumer);
}
private void writeFields(ChainableStringConsumer consumer) {
ArrayList<MemberNaming> fieldNamings = new ArrayList<>();
for (List<MemberNaming> namingsForKey : fieldMembers.values()) {
fieldNamings.addAll(namingsForKey);
}
fieldNamings.sort(Comparator.comparing(MemberNaming::getOriginalName));
fieldNamings.forEach(
naming -> consumer.accept(INDENTATION).accept(naming.toString()).accept("\n"));
}
private void writeMethods(ChainableStringConsumer consumer) {
Map<String, List<MappedRange>> signatureToMappedRanges = new HashMap<>();
methodMembers
.values()
.forEach(
segmentTree -> {
segmentTree.visitSegments(
mappedRanges -> {
MethodSignature originalSignature = ListUtils.last(mappedRanges).signature;
List<MappedRange> put =
signatureToMappedRanges.put(
originalSignature.getName() + "_" + originalSignature, mappedRanges);
assert put == null;
});
});
ArrayList<String> strings = new ArrayList<>(signatureToMappedRanges.keySet());
Collections.sort(strings);
for (String key : strings) {
signatureToMappedRanges
.get(key)
.forEach(
mappedRange ->
consumer.accept(INDENTATION).accept(mappedRange.toString()).accept("\n"));
}
}
private static class MappedRangeResult {
private final int startMinifiedPosition;
private final int startOriginalPosition;
private final int endIndex;
private final MappedRange lastRange;
private final List<MappedRange> allRanges;
public MappedRangeResult(
int startMinifiedPosition,
int startOriginalPosition,
int endIndex,
MappedRange lastRange,
List<MappedRange> allRanges) {
this.startMinifiedPosition = startMinifiedPosition;
this.startOriginalPosition = startOriginalPosition;
this.endIndex = endIndex;
this.lastRange = lastRange;
this.allRanges = allRanges;
}
}
}
}