blob: 7c12590b314630046205be1e6a6dffeb6f4a8ae5 [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 com.android.tools.r8.graph.DexClass;
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.shaking.Enqueuer.AppInfoWithLiveness;
import com.android.tools.r8.shaking.RootSetBuilder.RootSet;
import com.android.tools.r8.utils.StringUtils;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
public class ClassNameMinifier {
private final AppInfoWithLiveness appInfo;
private final RootSet rootSet;
private final String packagePrefix;
private final Set<DexString> usedTypeNames = Sets.newIdentityHashSet();
private final Map<DexType, DexString> renaming = Maps.newIdentityHashMap();
private final Map<String, NamingState> states = new HashMap<>();
final List<String> dictionary;
public ClassNameMinifier(AppInfoWithLiveness appInfo, RootSet rootSet, String packagePrefix,
List<String> dictionary) {
this.appInfo = appInfo;
this.rootSet = rootSet;
this.packagePrefix = packagePrefix;
this.dictionary = dictionary;
}
public Map<DexType, DexString> computeRenaming() {
// Collect names we have to keep.
for (DexClass clazz : appInfo.classes()) {
if (rootSet.noObfuscation.contains(clazz)) {
assert !renaming.containsKey(clazz.type);
renaming.put(clazz.type, clazz.type.descriptor);
usedTypeNames.add(clazz.type.descriptor);
}
}
for (DexClass clazz : appInfo.classes()) {
if (!renaming.containsKey(clazz.type)) {
String packageName = getPackageNameFor(clazz);
NamingState state = getStateFor(packageName);
renaming.put(clazz.type, state.nextTypeName());
}
}
appInfo.dexItemFactory.forAllTypes(this::renameArrayTypeIfNeeded);
return Collections.unmodifiableMap(renaming);
}
private String getPackageNameFor(DexClass clazz) {
if ((packagePrefix == null) || rootSet.keepPackageName.contains(clazz)) {
return clazz.type.getPackageDescriptor();
} else {
return packagePrefix;
}
}
private NamingState getStateFor(String packageName) {
return states.computeIfAbsent(packageName, NamingState::new);
}
private void renameArrayTypeIfNeeded(DexType type) {
if (type.isArrayType()) {
DexType base = type.toBaseType(appInfo.dexItemFactory);
DexString value = renaming.get(base);
if (value != null) {
int dimensions = type.descriptor.numberOfLeadingSquareBrackets();
StringBuilder builder = new StringBuilder();
for (int i = 0; i < dimensions; i++) {
builder.append('[');
}
builder.append(value.toString());
DexString descriptor = appInfo.dexItemFactory.createString(builder.toString());
renaming.put(type, descriptor);
}
}
}
private class NamingState {
private final char[] packagePrefix;
private int typeCounter = 1;
private Iterator<String> dictionaryIterator;
NamingState(String packageName) {
this.packagePrefix = ("L" + packageName + (packageName.isEmpty() ? "" : "/")).toCharArray();
this.dictionaryIterator = dictionary.iterator();
}
public char[] getPackagePrefix() {
return packagePrefix;
}
protected String nextSuggestedName() {
StringBuilder nextName = new StringBuilder();
if (dictionaryIterator.hasNext()) {
nextName.append(getPackagePrefix()).append(dictionaryIterator.next()).append(';');
return nextName.toString();
} else {
return StringUtils.numberToIdentifier(packagePrefix, typeCounter++, true);
}
}
private DexString nextTypeName() {
DexString candidate;
do {
candidate = appInfo.dexItemFactory.createString(nextSuggestedName());
} while (usedTypeNames.contains(candidate));
return candidate;
}
}
}