1// Copyright (c) 2016, the R8 project authors. Please see the AUTHORS file
2// for details. All rights reserved. Use of this source code is governed by a
3// BSD-style license that can be found in the LICENSE file.
4package com.android.tools.r8.naming;
5
6import com.android.tools.r8.graph.DexAnnotation;
7import com.android.tools.r8.graph.DexClass;
8import com.android.tools.r8.graph.DexProgramClass;
9import com.android.tools.r8.graph.DexString;
10import com.android.tools.r8.graph.DexType;
11import com.android.tools.r8.graph.DexValue;
12import com.android.tools.r8.graph.DexValue.DexValueType;
13import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
14import com.android.tools.r8.shaking.RootSetBuilder.RootSet;
15import com.android.tools.r8.utils.DescriptorUtils;
16import com.android.tools.r8.utils.StringUtils;
17import com.google.common.collect.Maps;
18import com.google.common.collect.Sets;
19import java.util.Collections;
20import java.util.HashMap;
21import java.util.Iterator;
22import java.util.List;
23import java.util.Map;
24import java.util.Set;
25
26public class ClassNameMinifier {
27
28  private final AppInfoWithLiveness appInfo;
29  private final RootSet rootSet;
30  private final String packagePrefix;
31  private final Set<DexString> usedTypeNames = Sets.newIdentityHashSet();
32
33  private final Map<DexType, DexString> renaming = Maps.newIdentityHashMap();
34  private final Map<String, NamingState> states = new HashMap<>();
35  private final List<String> dictionary;
36  private final boolean keepInnerClassStructure;
37
38  public ClassNameMinifier(AppInfoWithLiveness appInfo, RootSet rootSet, String packagePrefix,
39      List<String> dictionary, boolean keepInnerClassStructure) {
40    this.appInfo = appInfo;
41    this.rootSet = rootSet;
42    this.packagePrefix = packagePrefix;
43    this.dictionary = dictionary;
44    this.keepInnerClassStructure = keepInnerClassStructure;
45  }
46
47  public Map<DexType, DexString> computeRenaming() {
48    Iterable<DexProgramClass> classes = appInfo.classes();
49    // Collect names we have to keep.
50    for (DexClass clazz : appInfo.classes()) {
51      if (rootSet.noObfuscation.contains(clazz)) {
52        assert !renaming.containsKey(clazz.type);
53        registerClassAsUsed(clazz.type);
54      }
55    }
56    for (DexClass clazz : appInfo.classes()) {
57      if (!renaming.containsKey(clazz.type)) {
58        DexString renamed = computeName(clazz);
59        renaming.put(clazz.type, renamed);
60      }
61    }
62    appInfo.dexItemFactory.forAllTypes(this::renameArrayTypeIfNeeded);
63
64    return Collections.unmodifiableMap(renaming);
65  }
66
67  /**
68   * Registers the given type as used.
69   * <p>
70   * When {@link #keepInnerClassStructure} is true, keeping the name of an inner class will
71   * automatically also keep the name of the outer class, as otherwise the structure would be
72   * invalidated.
73   */
74  private void registerClassAsUsed(DexType type) {
75    renaming.put(type, type.descriptor);
76    usedTypeNames.add(type.descriptor);
77    if (keepInnerClassStructure) {
78      DexType outerClass = getOutClassForType(type);
79      if (outerClass != null) {
80        if (!renaming.containsKey(outerClass)) {
81          // The outer class was not previously kept. We have to do this now.
82          registerClassAsUsed(outerClass);
83        }
84      }
85    }
86  }
87
88  private DexType getOutClassForType(DexType type) {
89    DexClass clazz = appInfo.definitionFor(type);
90    if (clazz == null) {
91      return null;
92    }
93    DexAnnotation annotation =
94        clazz.annotations.getFirstMatching(appInfo.dexItemFactory.annotationEnclosingClass);
95    if (annotation != null) {
96      assert annotation.annotation.elements.length == 1;
97      DexValue value = annotation.annotation.elements[0].value;
98      return ((DexValueType) value).value;
99    }
100    // We do not need to preserve the names for local or anonymous classes, as they do not result
101    // in a member type declaration and hence cannot be referenced as nested classes in
102    // method signatures.
103    // See https://docs.oracle.com/javase/specs/jls/se7/html/jls-8.html#jls-8.5.
104    return null;
105  }
106
107  private DexString computeName(DexClass clazz) {
108    NamingState state = null;
109    if (keepInnerClassStructure) {
110      // When keeping the nesting structure of inner classes, we have to insert the name
111      // of the outer class for the $ prefix.
112      DexType outerClass = getOutClassForType(clazz.type);
113      if (outerClass != null) {
114        state = getStateForOuterClass(outerClass);
115      }
116    }
117    if (state == null) {
118      String packageName = getPackageNameFor(clazz);
119      state = getStateFor(packageName);
120    }
121    return state.nextTypeName();
122  }
123
124  private String getPackageNameFor(DexClass clazz) {
125    if ((packagePrefix == null) || rootSet.keepPackageName.contains(clazz)) {
126      return clazz.type.getPackageDescriptor();
127    } else {
128      return packagePrefix;
129    }
130  }
131
132  private NamingState getStateFor(String packageName) {
133    return states.computeIfAbsent(packageName, NamingState::new);
134  }
135
136  private NamingState getStateForOuterClass(DexType outer) {
137    String prefix = DescriptorUtils
138        .getClassBinaryNameFromDescriptor(outer.toDescriptorString());
139    return states.computeIfAbsent(prefix, k -> {
140      // Create a naming state with this classes renaming as prefix.
141      DexString renamed = renaming.get(outer);
142      if (renamed == null) {
143        // The outer class has not been renamed yet, so rename the outer class first.
144        DexClass outerClass = appInfo.definitionFor(outer);
145        if (outerClass == null) {
146          renamed = outer.descriptor;
147        } else {
148          renamed = computeName(outerClass);
149          renaming.put(outer, renamed);
150        }
151      }
152      String binaryName = DescriptorUtils.getClassBinaryNameFromDescriptor(renamed.toString());
153      return new NamingState(binaryName, "$");
154    });
155  }
156
157  private void renameArrayTypeIfNeeded(DexType type) {
158    if (type.isArrayType()) {
159      DexType base = type.toBaseType(appInfo.dexItemFactory);
160      DexString value = renaming.get(base);
161      if (value != null) {
162        int dimensions = type.descriptor.numberOfLeadingSquareBrackets();
163        StringBuilder builder = new StringBuilder();
164        for (int i = 0; i < dimensions; i++) {
165          builder.append('[');
166        }
167        builder.append(value.toString());
168        DexString descriptor = appInfo.dexItemFactory.createString(builder.toString());
169        renaming.put(type, descriptor);
170      }
171    }
172  }
173
174  private class NamingState {
175
176    private final char[] packagePrefix;
177    private final String separator;
178    private int typeCounter = 1;
179    private Iterator<String> dictionaryIterator;
180
181    NamingState(String packageName) {
182      this(packageName, "/");
183    }
184
185    NamingState(String packageName, String separator) {
186      this.packagePrefix = ("L" + packageName + (packageName.isEmpty() ? "" : separator))
187          .toCharArray();
188      this.separator = separator;
189      this.dictionaryIterator = dictionary.iterator();
190    }
191
192    public char[] getPackagePrefix() {
193      return packagePrefix;
194    }
195
196    protected String nextSuggestedName() {
197      StringBuilder nextName = new StringBuilder();
198      if (dictionaryIterator.hasNext()) {
199        nextName.append(getPackagePrefix()).append(dictionaryIterator.next()).append(';');
200        return nextName.toString();
201      } else {
202        return StringUtils.numberToIdentifier(packagePrefix, typeCounter++, true);
203      }
204    }
205
206    private DexString nextTypeName() {
207      DexString candidate;
208      do {
209        candidate = appInfo.dexItemFactory.createString(nextSuggestedName());
210      } while (usedTypeNames.contains(candidate));
211      return candidate;
212    }
213  }
214}
215