blob: bcc0af3696c554822543c01dc689b36ba950f216 [file] [log] [blame]
// Copyright (c) 2016, 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.naming.ClassNameMapper.MissingFileAction.MISSING_FILE_IS_ERROR;
import static com.android.tools.r8.utils.DescriptorUtils.descriptorToJavaType;
import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.IndexedDexItem;
import com.android.tools.r8.naming.MemberNaming.FieldSignature;
import com.android.tools.r8.naming.MemberNaming.MethodSignature;
import com.android.tools.r8.naming.MemberNaming.Signature;
import com.android.tools.r8.utils.BiMapContainer;
import com.google.common.collect.BiMap;
import com.google.common.collect.ImmutableBiMap;
import com.google.common.collect.ImmutableMap;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.StringWriter;
import java.io.Writer;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
public class ClassNameMapper implements ProguardMap {
public enum MissingFileAction {
MISSING_FILE_IS_EMPTY_MAP,
MISSING_FILE_IS_ERROR
}
public static class Builder extends ProguardMap.Builder {
private final ImmutableMap.Builder<String, ClassNamingForNameMapper.Builder> mapBuilder;
private Builder() {
this.mapBuilder = ImmutableMap.builder();
}
@Override
public ClassNamingForNameMapper.Builder classNamingBuilder(
String renamedName, String originalName) {
ClassNamingForNameMapper.Builder classNamingBuilder =
ClassNamingForNameMapper.builder(renamedName, originalName);
mapBuilder.put(renamedName, classNamingBuilder);
return classNamingBuilder;
}
@Override
public ClassNameMapper build() {
return new ClassNameMapper(mapBuilder.build());
}
}
public static Builder builder() {
return new Builder();
}
public static ClassNameMapper mapperFromInputStream(InputStream in) throws IOException {
BufferedReader reader = new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8));
try (ProguardMapReader proguardReader = new ProguardMapReader(reader)) {
ClassNameMapper.Builder builder = ClassNameMapper.builder();
proguardReader.parse(builder);
return builder.build();
}
}
public static ClassNameMapper mapperFromFile(Path path) throws IOException {
return mapperFromFile(path, MISSING_FILE_IS_ERROR);
}
public static ClassNameMapper mapperFromFile(Path path, MissingFileAction missingFileAction)
throws IOException {
assert missingFileAction == MissingFileAction.MISSING_FILE_IS_EMPTY_MAP
|| missingFileAction == MISSING_FILE_IS_ERROR;
if (missingFileAction == MissingFileAction.MISSING_FILE_IS_EMPTY_MAP
&& !path.toFile().exists()) {
return mapperFromString("");
}
return mapperFromInputStream(Files.newInputStream(path));
}
public static ClassNameMapper mapperFromString(String contents) throws IOException {
return mapperFromInputStream(
new ByteArrayInputStream(contents.getBytes(StandardCharsets.UTF_8)));
}
private final ImmutableMap<String, ClassNamingForNameMapper> classNameMappings;
private BiMapContainer<String, String> nameMapping;
private final Map<Signature, Signature> signatureMap = new HashMap<>();
private ClassNameMapper(Map<String, ClassNamingForNameMapper.Builder> classNameMappings) {
ImmutableMap.Builder<String, ClassNamingForNameMapper> builder = ImmutableMap.builder();
for(Map.Entry<String, ClassNamingForNameMapper.Builder> entry : classNameMappings.entrySet()) {
builder.put(entry.getKey(), entry.getValue().build());
}
this.classNameMappings = builder.build();
}
private Signature canonicalizeSignature(Signature signature) {
Signature result = signatureMap.get(signature);
if (result != null) {
return result;
}
signatureMap.put(signature, signature);
return signature;
}
public MethodSignature getRenamedMethodSignature(DexMethod method) {
DexType[] parameters = method.proto.parameters.values;
String[] parameterTypes = new String[parameters.length];
for (int i = 0; i < parameters.length; i++) {
parameterTypes[i] = deobfuscateType(parameters[i].toDescriptorString());
}
String returnType = deobfuscateType(method.proto.returnType.toDescriptorString());
MethodSignature signature = new MethodSignature(method.name.toString(), returnType,
parameterTypes);
return (MethodSignature) canonicalizeSignature(signature);
}
public FieldSignature getRenamedFieldSignature(DexField field) {
String type = deobfuscateType(field.type.toDescriptorString());
return (FieldSignature) canonicalizeSignature(new FieldSignature(field.name.toString(), type));
}
/**
* Deobfuscate a class name.
*
* <p>Returns the deobfuscated name if a mapping was found. Otherwise it returns the passed in
* name.
*/
public String deobfuscateClassName(String obfuscatedName) {
ClassNamingForNameMapper classNaming = classNameMappings.get(obfuscatedName);
if (classNaming == null) {
return obfuscatedName;
}
return classNaming.originalName;
}
private String deobfuscateType(String asString) {
return descriptorToJavaType(asString, this);
}
@Override
public boolean hasMapping(DexType type) {
String decoded = descriptorToJavaType(type.descriptor.toString());
return classNameMappings.containsKey(decoded);
}
@Override
public ClassNamingForNameMapper getClassNaming(DexType type) {
String decoded = descriptorToJavaType(type.descriptor.toString());
return classNameMappings.get(decoded);
}
public ClassNamingForNameMapper getClassNaming(String obfuscatedName) {
return classNameMappings.get(obfuscatedName);
}
public void write(Writer writer) throws IOException {
// Sort classes by their original name such that the generated Proguard map is deterministic
// (and easy to navigate manually).
List<ClassNamingForNameMapper> classNamingForNameMappers =
new ArrayList<>(classNameMappings.values());
classNamingForNameMappers.sort(Comparator.comparing(x -> x.originalName));
for (ClassNamingForNameMapper naming : classNamingForNameMappers) {
naming.write(writer);
}
}
public void forAllClassNamings(Consumer<ClassNaming> consumer) {
classNameMappings.values().forEach(consumer);
}
@Override
public String toString() {
try {
StringWriter writer = new StringWriter();
write(writer);
return writer.toString();
} catch (IOException e) {
return e.toString();
}
}
public BiMapContainer<String, String> getObfuscatedToOriginalMapping() {
if (nameMapping == null) {
ImmutableBiMap.Builder<String, String> builder = ImmutableBiMap.builder();
for (String name : classNameMappings.keySet()) {
builder.put(name, classNameMappings.get(name).originalName);
}
BiMap<String, String> classNameMappings = builder.build();
nameMapping = new BiMapContainer<>(classNameMappings, classNameMappings.inverse());
}
return nameMapping;
}
@Override
public boolean equals(Object o) {
return o instanceof ClassNameMapper
&& classNameMappings.equals(((ClassNameMapper) o).classNameMappings);
}
@Override
public int hashCode() {
return 31 * classNameMappings.hashCode();
}
public String originalNameOf(IndexedDexItem item) {
if (item instanceof DexField) {
return lookupName(getRenamedFieldSignature((DexField) item), ((DexField) item).clazz);
} else if (item instanceof DexMethod) {
return lookupName(getRenamedMethodSignature((DexMethod) item), ((DexMethod) item).holder);
} else if (item instanceof DexType) {
return descriptorToJavaType(((DexType) item).toDescriptorString(), this);
} else {
return item.toString();
}
}
private String lookupName(Signature signature, DexType clazz) {
String decoded = descriptorToJavaType(clazz.descriptor.toString());
ClassNamingForNameMapper classNaming = getClassNaming(decoded);
if (classNaming == null) {
return decoded + " " + signature.toString();
}
MemberNaming memberNaming = classNaming.lookup(signature);
if (memberNaming == null) {
return classNaming.originalName + " " + signature.toString();
}
return classNaming.originalName + " " + memberNaming.signature.toString();
}
public MethodSignature originalSignatureOf(DexMethod method) {
String decoded = descriptorToJavaType(method.holder.descriptor.toString());
MethodSignature memberSignature = getRenamedMethodSignature(method);
ClassNaming classNaming = getClassNaming(decoded);
if (classNaming == null) {
return memberSignature;
}
MemberNaming memberNaming = classNaming.lookup(memberSignature);
if (memberNaming == null) {
return memberSignature;
}
return (MethodSignature) memberNaming.signature;
}
public FieldSignature originalSignatureOf(DexField field) {
String decoded = descriptorToJavaType(field.clazz.descriptor.toString());
FieldSignature memberSignature = getRenamedFieldSignature(field);
ClassNaming classNaming = getClassNaming(decoded);
if (classNaming == null) {
return memberSignature;
}
MemberNaming memberNaming = classNaming.lookup(memberSignature);
if (memberNaming == null) {
return memberSignature;
}
return (FieldSignature) memberNaming.signature;
}
public String originalNameOf(DexType clazz) {
return deobfuscateType(clazz.descriptor.toString());
}
}