blob: 98ac26605521db21aea4810766c7051642957adc [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.retrace.internal;
import static com.google.common.base.Predicates.alwaysTrue;
import com.android.tools.r8.DiagnosticsHandler;
import com.android.tools.r8.naming.ClassNameMapper;
import com.android.tools.r8.naming.ClassNamingForNameMapper;
import com.android.tools.r8.naming.LineReader;
import com.android.tools.r8.naming.MapVersion;
import com.android.tools.r8.naming.mappinginformation.FileNameInformation;
import com.android.tools.r8.naming.mappinginformation.MapVersionMappingInformation;
import com.android.tools.r8.retrace.MappingPartition;
import com.android.tools.r8.retrace.MappingPartitionMetadata;
import com.android.tools.r8.retrace.ProguardMapPartitioner;
import com.android.tools.r8.retrace.ProguardMapPartitionerBuilder;
import com.android.tools.r8.retrace.ProguardMapProducer;
import com.android.tools.r8.retrace.RetracePartitionException;
import com.android.tools.r8.retrace.internal.MappingPartitionMetadataInternal.ObfuscatedTypeNameAsKeyMetadata;
import com.android.tools.r8.retrace.internal.MappingPartitionMetadataInternal.ObfuscatedTypeNameAsKeyMetadataWithPartitionNames;
import com.android.tools.r8.retrace.internal.ProguardMapReaderWithFiltering.ProguardMapReaderWithFilteringInputBuffer;
import com.android.tools.r8.retrace.internal.ProguardMapReaderWithFiltering.ProguardMapReaderWithFilteringMappedBuffer;
import com.android.tools.r8.utils.ExceptionDiagnostic;
import com.android.tools.r8.utils.StringDiagnostic;
import com.android.tools.r8.utils.StringUtils;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
public class ProguardMapPartitionerOnClassNameToText implements ProguardMapPartitioner {
private final ProguardMapProducer proguardMapProducer;
private final Consumer<MappingPartition> mappingPartitionConsumer;
private final DiagnosticsHandler diagnosticsHandler;
private final boolean allowEmptyMappedRanges;
private final boolean allowExperimentalMapping;
private final MappingPartitionKeyStrategy mappingPartitionKeyStrategy;
private ProguardMapPartitionerOnClassNameToText(
ProguardMapProducer proguardMapProducer,
Consumer<MappingPartition> mappingPartitionConsumer,
DiagnosticsHandler diagnosticsHandler,
boolean allowEmptyMappedRanges,
boolean allowExperimentalMapping,
MappingPartitionKeyStrategy mappingPartitionKeyStrategy) {
this.proguardMapProducer = proguardMapProducer;
this.mappingPartitionConsumer = mappingPartitionConsumer;
this.diagnosticsHandler = diagnosticsHandler;
this.allowEmptyMappedRanges = allowEmptyMappedRanges;
this.allowExperimentalMapping = allowExperimentalMapping;
this.mappingPartitionKeyStrategy = mappingPartitionKeyStrategy;
}
@Override
public MappingPartitionMetadata run() throws IOException {
PartitionLineReader reader =
new PartitionLineReader(
proguardMapProducer.isFileBacked()
? new ProguardMapReaderWithFilteringMappedBuffer(
proguardMapProducer.getPath(), alwaysTrue(), true)
: new ProguardMapReaderWithFilteringInputBuffer(
proguardMapProducer.get(), alwaysTrue(), true));
// Produce a global mapper to read all from the reader but also to capture all source file
// mappings.
ClassNameMapper classMapper =
ClassNameMapper.mapperFromLineReaderWithFiltering(
reader, MapVersion.MAP_VERSION_UNKNOWN, diagnosticsHandler, true, true);
HashSet<String> keys = new LinkedHashSet<>();
// We can then iterate over all sections.
reader.forEachClassMapping(
(classMapping, entries) -> {
try {
String payload = StringUtils.join("\n", entries);
ClassNameMapper classNameMapper =
ClassNameMapper.mapperFromString(
payload, null, allowEmptyMappedRanges, allowExperimentalMapping, false);
if (classNameMapper.getClassNameMappings().size() != 1) {
diagnosticsHandler.error(
new StringDiagnostic("Multiple class names in payload\n: " + payload));
return;
}
Entry<String, ClassNamingForNameMapper> currentClassMapping =
classNameMapper.getClassNameMappings().entrySet().iterator().next();
ClassNamingForNameMapper value = currentClassMapping.getValue();
Set<String> seenMappings = new HashSet<>();
StringBuilder payloadWithClassReferences = new StringBuilder();
value.visitAllFullyQualifiedReferences(
holder -> {
if (seenMappings.add(holder)) {
payloadWithClassReferences.append(
getSourceFileMapping(holder, classMapper.getSourceFile(holder)));
}
});
payloadWithClassReferences.append(payload);
mappingPartitionConsumer.accept(
new MappingPartitionImpl(
currentClassMapping.getKey(),
payloadWithClassReferences.toString().getBytes(StandardCharsets.UTF_8)));
keys.add(currentClassMapping.getKey());
} catch (IOException e) {
diagnosticsHandler.error(new ExceptionDiagnostic(e));
}
});
MapVersion mapVersion = MapVersion.MAP_VERSION_UNKNOWN;
MapVersionMappingInformation mapVersionInfo = classMapper.getFirstMapVersionInformation();
if (mapVersionInfo != null) {
mapVersion = mapVersionInfo.getMapVersion();
}
if (mappingPartitionKeyStrategy == MappingPartitionKeyStrategy.OBFUSCATED_TYPE_NAME_AS_KEY) {
return ObfuscatedTypeNameAsKeyMetadata.create(mapVersion);
} else if (mappingPartitionKeyStrategy
== MappingPartitionKeyStrategy.OBFUSCATED_TYPE_NAME_AS_KEY_WITH_PARTITIONS) {
return ObfuscatedTypeNameAsKeyMetadataWithPartitionNames.create(
mapVersion, MetadataPartitionCollection.create(keys));
} else {
RetracePartitionException retraceError =
new RetracePartitionException("Unknown mapping partitioning strategy");
diagnosticsHandler.error(new ExceptionDiagnostic(retraceError));
throw retraceError;
}
}
private String getSourceFileMapping(String className, String sourceFile) {
if (sourceFile == null) {
return "";
}
return className
+ " -> "
+ className
+ ":"
+ "\n # "
+ FileNameInformation.build(sourceFile).serialize()
+ "\n";
}
public static class PartitionLineReader implements LineReader {
private final ProguardMapReaderWithFiltering lineReader;
private final Map<String, List<String>> readSections = new LinkedHashMap<>();
private List<String> currentList;
public PartitionLineReader(ProguardMapReaderWithFiltering lineReader) {
this.lineReader = lineReader;
currentList = new ArrayList<>();
}
@Override
public String readLine() throws IOException {
String readLine = lineReader.readLine();
if (readLine == null) {
return null;
}
if (lineReader.isClassMapping()) {
currentList = new ArrayList<>();
readSections.put(readLine, currentList);
}
currentList.add(readLine);
return readLine;
}
@Override
public void close() throws IOException {
lineReader.close();
}
public void forEachClassMapping(BiConsumer<String, List<String>> consumer) {
readSections.forEach(consumer);
}
}
public static class ProguardMapPartitionerBuilderImpl
implements ProguardMapPartitionerBuilder<
ProguardMapPartitionerBuilderImpl, ProguardMapPartitionerOnClassNameToText> {
protected ProguardMapProducer proguardMapProducer;
protected Consumer<MappingPartition> mappingPartitionConsumer;
protected final DiagnosticsHandler diagnosticsHandler;
protected boolean allowEmptyMappedRanges = false;
protected boolean allowExperimentalMapping = false;
public ProguardMapPartitionerBuilderImpl(DiagnosticsHandler diagnosticsHandler) {
this.diagnosticsHandler = diagnosticsHandler;
}
@Override
public ProguardMapPartitionerBuilderImpl setPartitionConsumer(
Consumer<MappingPartition> consumer) {
this.mappingPartitionConsumer = consumer;
return this;
}
@Override
public ProguardMapPartitionerBuilderImpl setProguardMapProducer(
ProguardMapProducer proguardMapProducer) {
this.proguardMapProducer = proguardMapProducer;
return this;
}
@Override
public ProguardMapPartitionerBuilderImpl setAllowEmptyMappedRanges(
boolean allowEmptyMappedRanges) {
this.allowEmptyMappedRanges = allowEmptyMappedRanges;
return this;
}
@Override
public ProguardMapPartitionerBuilderImpl setAllowExperimentalMapping(
boolean allowExperimentalMapping) {
this.allowExperimentalMapping = allowExperimentalMapping;
return this;
}
@Override
public ProguardMapPartitionerOnClassNameToText build() {
return new ProguardMapPartitionerOnClassNameToText(
proguardMapProducer,
mappingPartitionConsumer,
diagnosticsHandler,
allowEmptyMappedRanges,
allowExperimentalMapping,
MappingPartitionKeyStrategy.getDefaultStrategy());
}
}
// This class should not be exposed to clients and is only used from tests to control the
// partitioning strategy.
public static class ProguardMapPartitionerBuilderImplInternal
extends ProguardMapPartitionerBuilderImpl {
private MappingPartitionKeyStrategy mappingPartitionKeyStrategy =
MappingPartitionKeyStrategy.OBFUSCATED_TYPE_NAME_AS_KEY_WITH_PARTITIONS;
public ProguardMapPartitionerBuilderImplInternal(DiagnosticsHandler diagnosticsHandler) {
super(diagnosticsHandler);
}
public ProguardMapPartitionerBuilderImplInternal setMappingPartitionKeyStrategy(
MappingPartitionKeyStrategy mappingPartitionKeyStrategy) {
this.mappingPartitionKeyStrategy = mappingPartitionKeyStrategy;
return this;
}
@Override
public ProguardMapPartitionerOnClassNameToText build() {
return new ProguardMapPartitionerOnClassNameToText(
proguardMapProducer,
mappingPartitionConsumer,
diagnosticsHandler,
allowEmptyMappedRanges,
allowExperimentalMapping,
mappingPartitionKeyStrategy);
}
}
}