diff options
author | Jinseong Jeon <jsjeon@google.com> | 2017-10-26 13:14:44 -0700 |
---|---|---|
committer | Jinseong Jeon <jsjeon@google.com> | 2017-10-26 13:14:44 -0700 |
commit | 7cec45be0c6c08de76fb9c7f6d0cdab41f703197 (patch) | |
tree | 54cd4f6886c10a7ac16bf35738afe422417de089 | |
parent | bcd54b7c890355e9b7cdf5ef1057b268588f669e (diff) | |
download | r8-7cec45be0c6c08de76fb9c7f6d0cdab41f703197.tar.gz |
Apply the given pg-map via graph lense.
While visiting types in a subtyping order, mappings in the pg-map will
be converted to a custom graph lense. One caveat is to detect mapping
conflicts, e.g., so-called diamond problem.
Another caveat is to apply class mappings on-the-fly. We should rename
all the occurrences of renamed types while applying member mappings.
Yet another caveat is uses of renamed lib classes inside pgr classes.
We have a separate step that fixes trees by explicitly substitute those
type appearances with applied names.
------
Note that, instead of way too general support (http://go/r8g/4880),
we decided to support only the simple use case: compile a new piece
of code (e.g., test) against an obfuscated app.
See http://b/64802420#comment7 for more details.
Bug: 64802420
Change-Id: Ib3cb35811061c046e94a4fc9a021a783f15050dc
39 files changed, 1703 insertions, 199 deletions
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java index c9ede5106..a9f3329f9 100644 --- a/src/main/java/com/android/tools/r8/R8.java +++ b/src/main/java/com/android/tools/r8/R8.java @@ -22,6 +22,8 @@ import com.android.tools.r8.ir.optimize.SwitchMapCollector; import com.android.tools.r8.jar.CfApplicationWriter; import com.android.tools.r8.naming.Minifier; import com.android.tools.r8.naming.NamingLens; +import com.android.tools.r8.naming.ProguardMapApplier; +import com.android.tools.r8.naming.SeedMapper; import com.android.tools.r8.naming.SourceFileRewriter; import com.android.tools.r8.optimize.BridgeMethodAnalysis; import com.android.tools.r8.optimize.MemberRebindingAnalysis; @@ -259,6 +261,14 @@ public class R8 { appInfo = appInfo.withLiveness() .prunedCopyFrom(application, classMerger.getRemovedClasses()); } + if (options.proguardConfiguration.hasApplyMappingFile()) { + SeedMapper seedMapper = SeedMapper.seedMapperFromFile( + options.proguardConfiguration.getApplyMappingFile()); + timing.begin("apply-mapping"); + graphLense = new ProguardMapApplier(appInfo.withLiveness(), graphLense, seedMapper) + .run(timing); + timing.end(); + } application = application.asDirect().rewrittenWithLense(graphLense); appInfo = appInfo.withLiveness().rewrittenWithLense(application.asDirect(), graphLense); // Collect switch maps and ordinals maps. diff --git a/src/main/java/com/android/tools/r8/R8Command.java b/src/main/java/com/android/tools/r8/R8Command.java index fef695eb8..ad23c038b 100644 --- a/src/main/java/com/android/tools/r8/R8Command.java +++ b/src/main/java/com/android/tools/r8/R8Command.java @@ -238,8 +238,6 @@ public class R8Command extends BaseCompilerCommand { getAppBuilder().addProgramFiles(configuration.getInjars()); getAppBuilder().addLibraryFiles(configuration.getLibraryjars()); - // TODO(b/64802420): setProguardMapFile if configuration.hasApplyMappingFile - boolean useTreeShaking = treeShaking.orElse(configuration.isShrinking()); boolean useDiscardedChecker = discardedChecker.orElse(true); boolean useMinification = minification.orElse(configuration.isObfuscating()); diff --git a/src/main/java/com/android/tools/r8/ReadMainDexList.java b/src/main/java/com/android/tools/r8/ReadMainDexList.java index a0c47895e..661e324af 100644 --- a/src/main/java/com/android/tools/r8/ReadMainDexList.java +++ b/src/main/java/com/android/tools/r8/ReadMainDexList.java @@ -4,7 +4,6 @@ package com.android.tools.r8; import com.android.tools.r8.naming.ClassNameMapper; -import com.android.tools.r8.naming.ProguardMapReader; import com.android.tools.r8.utils.FileUtils; import com.google.common.collect.Iterators; import java.nio.file.Path; @@ -57,7 +56,7 @@ public class ReadMainDexList { Path mainDexList = Paths.get(arg); final ClassNameMapper mapper = - arguments.hasNext() ? ProguardMapReader.mapperFromFile(Paths.get(arguments.next())) : null; + arguments.hasNext() ? ClassNameMapper.mapperFromFile(Paths.get(arguments.next())) : null; FileUtils.readTextFile(mainDexList) .stream() diff --git a/src/main/java/com/android/tools/r8/ReadProguardMap.java b/src/main/java/com/android/tools/r8/ReadProguardMap.java index 2ea2ca78b..a05537c26 100644 --- a/src/main/java/com/android/tools/r8/ReadProguardMap.java +++ b/src/main/java/com/android/tools/r8/ReadProguardMap.java @@ -3,7 +3,7 @@ // BSD-style license that can be found in the LICENSE file. package com.android.tools.r8; -import com.android.tools.r8.naming.ProguardMapReader; +import com.android.tools.r8.naming.ClassNameMapper; import com.android.tools.r8.utils.Timing; import java.io.IOException; import java.nio.file.Paths; @@ -22,7 +22,7 @@ public class ReadProguardMap { try { System.out.println(" - reading " + fileName); timing.begin("Reading " + fileName); - ProguardMapReader.mapperFromFile(Paths.get(fileName)); + ClassNameMapper.mapperFromFile(Paths.get(fileName)); timing.end(); } catch (IOException e) { System.err.print("Failed to parse Proguard mapping file: " + e.getMessage()); diff --git a/src/main/java/com/android/tools/r8/dex/ApplicationReader.java b/src/main/java/com/android/tools/r8/dex/ApplicationReader.java index 3e4d28db2..db5ebf20b 100644 --- a/src/main/java/com/android/tools/r8/dex/ApplicationReader.java +++ b/src/main/java/com/android/tools/r8/dex/ApplicationReader.java @@ -20,7 +20,7 @@ import com.android.tools.r8.graph.DexProgramClass; import com.android.tools.r8.graph.JarApplicationReader; import com.android.tools.r8.graph.JarClassFileReader; import com.android.tools.r8.graph.LazyLoadedDexApplication; -import com.android.tools.r8.naming.ProguardMapReader; +import com.android.tools.r8.naming.ClassNameMapper; import com.android.tools.r8.utils.AndroidApiLevel; import com.android.tools.r8.utils.AndroidApp; import com.android.tools.r8.utils.ClassProvider; @@ -111,7 +111,7 @@ public class ApplicationReader { if (inputApp.hasProguardMap()) { futures.add(executorService.submit(() -> { try (InputStream map = inputApp.getProguardMap()) { - builder.setProguardMap(ProguardMapReader.mapperFromInputStream(map)); + builder.setProguardMap(ClassNameMapper.mapperFromInputStream(map)); } catch (IOException e) { throw new RuntimeException(e); } diff --git a/src/main/java/com/android/tools/r8/graph/DexClass.java b/src/main/java/com/android/tools/r8/graph/DexClass.java index 505071166..dda559cd5 100644 --- a/src/main/java/com/android/tools/r8/graph/DexClass.java +++ b/src/main/java/com/android/tools/r8/graph/DexClass.java @@ -18,7 +18,7 @@ public abstract class DexClass extends DexItem { private static final DexEncodedField[] NO_FIELDS = {}; public final Origin origin; - public final DexType type; + public DexType type; public final DexAccessFlags accessFlags; public DexType superType; public DexTypeList interfaces; diff --git a/src/main/java/com/android/tools/r8/graph/GraphLense.java b/src/main/java/com/android/tools/r8/graph/GraphLense.java index 7d7d366ec..ed4106e0c 100644 --- a/src/main/java/com/android/tools/r8/graph/GraphLense.java +++ b/src/main/java/com/android/tools/r8/graph/GraphLense.java @@ -23,13 +23,12 @@ public abstract class GraphLense { public static class Builder { - private Builder() { - + protected Builder() { } - private final Map<DexType, DexType> typeMap = new IdentityHashMap<>(); - private final Map<DexMethod, DexMethod> methodMap = new IdentityHashMap<>(); - private final Map<DexField, DexField> fieldMap = new IdentityHashMap<>(); + protected final Map<DexType, DexType> typeMap = new IdentityHashMap<>(); + protected final Map<DexMethod, DexMethod> methodMap = new IdentityHashMap<>(); + protected final Map<DexField, DexField> fieldMap = new IdentityHashMap<>(); public void map(DexType from, DexType to) { typeMap.put(from, to); @@ -48,6 +47,9 @@ public abstract class GraphLense { } public GraphLense build(DexItemFactory dexItemFactory, GraphLense previousLense) { + if (typeMap.isEmpty() && methodMap.isEmpty() && fieldMap.isEmpty()) { + return previousLense; + } return new NestedGraphLense(typeMap, methodMap, fieldMap, previousLense, dexItemFactory); } @@ -96,17 +98,17 @@ public abstract class GraphLense { } } - private static class NestedGraphLense extends GraphLense { + public static class NestedGraphLense extends GraphLense { private final GraphLense previousLense; - private final DexItemFactory dexItemFactory; + protected final DexItemFactory dexItemFactory; private final Map<DexType, DexType> typeMap; private final Map<DexType, DexType> arrayTypeCache = new IdentityHashMap<>(); private final Map<DexMethod, DexMethod> methodMap; private final Map<DexField, DexField> fieldMap; - private NestedGraphLense(Map<DexType, DexType> typeMap, Map<DexMethod, DexMethod> methodMap, + public NestedGraphLense(Map<DexType, DexType> typeMap, Map<DexMethod, DexMethod> methodMap, Map<DexField, DexField> fieldMap, GraphLense previousLense, DexItemFactory dexItemFactory) { this.typeMap = typeMap; this.methodMap = methodMap; @@ -154,5 +156,24 @@ public abstract class GraphLense { public boolean isContextFree() { return previousLense.isContextFree(); } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + for (Map.Entry<DexType, DexType> entry : typeMap.entrySet()) { + builder.append(entry.getKey().toSourceString()).append(" -> "); + builder.append(entry.getValue().toSourceString()).append(System.lineSeparator()); + } + for (Map.Entry<DexMethod, DexMethod> entry : methodMap.entrySet()) { + builder.append(entry.getKey().toSourceString()).append(" -> "); + builder.append(entry.getValue().toSourceString()).append(System.lineSeparator()); + } + for (Map.Entry<DexField, DexField> entry : fieldMap.entrySet()) { + builder.append(entry.getKey().toSourceString()).append(" -> "); + builder.append(entry.getValue().toSourceString()).append(System.lineSeparator()); + } + builder.append(previousLense.toString()); + return builder.toString(); + } } } diff --git a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java index b5fbb0643..8a309e270 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java @@ -228,7 +228,9 @@ public class LensCodeRewriter { return methodHandle; } - private Type getInvokeType(InvokeMethod invoke, DexMethod actualTarget, + private Type getInvokeType( + InvokeMethod invoke, + DexMethod actualTarget, DexMethod originalTarget) { if (invoke.isInvokeVirtual() || invoke.isInvokeInterface()) { // Get the invoke type of the actual definition. @@ -237,7 +239,8 @@ public class LensCodeRewriter { return invoke.getType(); } else { DexClass originalTargetClass = appInfo.definitionFor(originalTarget.holder); - if (originalTargetClass.isInterface() ^ (invoke.getType() == Type.INTERFACE)) { + if ((originalTargetClass != null && originalTargetClass.isInterface()) + ^ (invoke.getType() == Type.INTERFACE)) { // The invoke was wrong to start with, so we keep it wrong. This is to ensure we get // the IncompatibleClassChangeError the original invoke would have triggered. return newTargetClass.accessFlags.isInterface() diff --git a/src/main/java/com/android/tools/r8/naming/ClassNameMapper.java b/src/main/java/com/android/tools/r8/naming/ClassNameMapper.java index 1360964ad..2e654a869 100644 --- a/src/main/java/com/android/tools/r8/naming/ClassNameMapper.java +++ b/src/main/java/com/android/tools/r8/naming/ClassNameMapper.java @@ -3,6 +3,8 @@ // BSD-style license that can be found in the LICENSE file. package com.android.tools.r8.naming; +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; @@ -10,26 +12,79 @@ 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.DescriptorUtils; 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.HashMap; import java.util.Map; import java.util.function.Consumer; -public class ClassNameMapper { +public class ClassNameMapper implements ProguardMap { + + static class Builder extends ProguardMap.Builder { + final ImmutableMap.Builder<String, ClassNamingForNameMapper.Builder> mapBuilder; + + private Builder() { + mapBuilder = ImmutableMap.builder(); + } + + @Override + ClassNamingForNameMapper.Builder classNamingBuilder(String renamedName, String originalName) { + ClassNamingForNameMapper.Builder classNamingBuilder = + ClassNamingForNameMapper.builder(renamedName, originalName); + mapBuilder.put(renamedName, classNamingBuilder); + return classNamingBuilder; + } + + @Override + ClassNameMapper build(){ + return new ClassNameMapper(mapBuilder.build()); + } + } + + 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 mapperFromInputStream(Files.newInputStream(path)); + } - private final ImmutableMap<String, ClassNaming> classNameMappings; + static ClassNameMapper mapperFromString(String contents) throws IOException { + return mapperFromInputStream( + new ByteArrayInputStream(contents.getBytes(StandardCharsets.UTF_8))); + } + + private final ImmutableMap<String, ClassNamingForNameMapper> classNameMappings; private ImmutableBiMap<String, String> nameMapping; private Map<Signature, Signature> signatureMap = new HashMap<>(); - ClassNameMapper(Map<String, ClassNaming> classNameMappings) { - this.classNameMappings = ImmutableMap.copyOf(classNameMappings); + 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) { @@ -65,7 +120,7 @@ public class ClassNameMapper { * Returns the deobfuscated name if a mapping was found. Otherwise it returns the passed in name. */ public String deobfuscateClassName(String name) { - ClassNaming classNaming = classNameMappings.get(name); + ClassNamingForNameMapper classNaming = classNameMappings.get(name); if (classNaming == null) { return name; } @@ -73,15 +128,27 @@ public class ClassNameMapper { } private String deobfuscateType(String asString) { - return DescriptorUtils.descriptorToJavaType(asString, this); + return descriptorToJavaType(asString, this); } - public ClassNaming getClassNaming(String name) { + @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 name) { return classNameMappings.get(name); } public void write(Writer writer, boolean collapseRanges) throws IOException { - for (ClassNaming naming : classNameMappings.values()) { + for (ClassNamingForNameMapper naming : classNameMappings.values()) { naming.write(writer, collapseRanges); } } @@ -129,15 +196,15 @@ public class ClassNameMapper { } else if (item instanceof DexMethod) { return lookupName(getRenamedMethodSignature((DexMethod) item), ((DexMethod) item).holder); } else if (item instanceof DexType) { - return DescriptorUtils.descriptorToJavaType(((DexType) item).toDescriptorString(), this); + return descriptorToJavaType(((DexType) item).toDescriptorString(), this); } else { return item.toString(); } } private String lookupName(Signature signature, DexType clazz) { - String decoded = DescriptorUtils.descriptorToJavaType(clazz.descriptor.toString()); - ClassNaming classNaming = getClassNaming(decoded); + String decoded = descriptorToJavaType(clazz.descriptor.toString()); + ClassNamingForNameMapper classNaming = getClassNaming(decoded); if (classNaming == null) { return decoded + " " + signature.toString(); } @@ -149,8 +216,7 @@ public class ClassNameMapper { } public Signature originalSignatureOf(DexMethod method) { - String decoded = DescriptorUtils - .descriptorToJavaType(method.holder.descriptor.toString()); + String decoded = descriptorToJavaType(method.holder.descriptor.toString()); MethodSignature memberSignature = getRenamedMethodSignature(method); ClassNaming classNaming = getClassNaming(decoded); if (classNaming == null) { diff --git a/src/main/java/com/android/tools/r8/naming/ClassNaming.java b/src/main/java/com/android/tools/r8/naming/ClassNaming.java index 74c50068c..d211e7b67 100644 --- a/src/main/java/com/android/tools/r8/naming/ClassNaming.java +++ b/src/main/java/com/android/tools/r8/naming/ClassNaming.java @@ -4,114 +4,30 @@ package com.android.tools.r8.naming; import com.android.tools.r8.naming.MemberNaming.Signature; -import java.io.IOException; -import java.io.StringWriter; -import java.io.Writer; -import java.util.ArrayList; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.function.Consumer; +import com.android.tools.r8.utils.ThrowingConsumer; /** * Stores name information for a class. * <p> - * This includes how the class was renamed and information on the classes members. + * Implementers will include how the class was renamed and information on the class's members. */ -public class ClassNaming { +public interface ClassNaming { - public final String originalName; - public final String renamedName; - - /** - * Mapping from the renamed signature to the naming information for a member. - * <p> - * A renamed signature is a signature where the member's name has been obfuscated but not the type - * information. - **/ - final Map<Signature, MemberNaming> members = new LinkedHashMap<>(); - - ClassNaming(String renamedName, String originalName) { - this.renamedName = renamedName; - this.originalName = originalName; - } - - void addMemberEntry(MemberNaming entry) { - Signature renamedSignature = entry.renamedSignature; - members.put(renamedSignature, entry); - } - - public MemberNaming lookup(Signature renamedSignature) { - return members.get(renamedSignature); + abstract class Builder { + abstract Builder addMemberEntry(MemberNaming entry); + abstract ClassNaming build(); } - public MemberNaming lookupByOriginalSignature(Signature original) { - for (MemberNaming naming : members.values()) { - if (naming.signature.equals(original)) { - return naming; - } - } - return null; - } + MemberNaming lookup(Signature renamedSignature); - public List<MemberNaming> lookupByOriginalName(String originalName) { - List<MemberNaming> result = new ArrayList<>(); - for (MemberNaming naming : members.values()) { - if (naming.signature.name.equals(originalName)) { - result.add(naming); - } - } - return result; - } + MemberNaming lookupByOriginalSignature(Signature original); - public void forAllMemberNaming(Consumer<MemberNaming> consumer) { - members.values().forEach(consumer); - } + <T extends Throwable> void forAllMemberNaming( + ThrowingConsumer<MemberNaming, T> consumer) throws T; - void write(Writer writer, boolean collapseRanges) throws IOException { - writer.append(originalName); - writer.append(" -> "); - writer.append(renamedName); - writer.append(":\n"); - for (MemberNaming member : members.values()) { - member.write(writer, collapseRanges, true); - } - } + <T extends Throwable> void forAllFieldNaming( + ThrowingConsumer<MemberNaming, T> consumer) throws T; - @Override - public String toString() { - try { - StringWriter writer = new StringWriter(); - write(writer, false); - return writer.toString(); - } catch (IOException e) { - return e.toString(); - } - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (!(o instanceof ClassNaming)) { - return false; - } - - ClassNaming that = (ClassNaming) o; - - return originalName.equals(that.originalName) - && renamedName.equals(that.renamedName) - && members.equals(that.members); - - } - - @Override - public int hashCode() { - int result = originalName.hashCode(); - result = 31 * result + renamedName.hashCode(); - result = 31 * result + members.hashCode(); - return result; - } + <T extends Throwable> void forAllMethodNaming( + ThrowingConsumer<MemberNaming, T> consumer) throws T; } - diff --git a/src/main/java/com/android/tools/r8/naming/ClassNamingForMapApplier.java b/src/main/java/com/android/tools/r8/naming/ClassNamingForMapApplier.java new file mode 100644 index 000000000..d94fe57c0 --- /dev/null +++ b/src/main/java/com/android/tools/r8/naming/ClassNamingForMapApplier.java @@ -0,0 +1,193 @@ +// Copyright (c) 2017, 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.DexField; +import com.android.tools.r8.graph.DexMethod; +import com.android.tools.r8.graph.DexType; +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.naming.MemberNaming.Signature.SignatureKind; +import com.android.tools.r8.utils.ThrowingConsumer; +import com.google.common.collect.ImmutableMap; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +/** + * Stores name information for a class. + * <p> + * The main differences of this against {@link ClassNamingForNameMapper} are: + * 1) field and method mappings are maintained and searched separately for faster lookup; + * 2) similar to the relation between {@link ClassNameMapper} and {@link SeedMapper}, this one + * uses original {@link Signature} as a key to look up {@link MemberNaming}, + * whereas {@link ClassNamingForNameMapper} uses renamed {@link Signature} as a key; and thus + * 3) logic of {@link #lookup} and {@link #lookupByOriginalSignature} are inverted; and + * 4) {@link #lookupByOriginalItem}'s are introduced for lightweight lookup. + */ +public class ClassNamingForMapApplier implements ClassNaming { + + public static class Builder extends ClassNaming.Builder { + private final String originalName; + private final String renamedName; + private final Map<MethodSignature, MemberNaming> methodMembers = new HashMap<>(); + private final Map<FieldSignature, MemberNaming> fieldMembers = new HashMap<>(); + + private Builder(String renamedName, String originalName) { + this.originalName = originalName; + this.renamedName = renamedName; + } + + @Override + ClassNaming.Builder addMemberEntry(MemberNaming entry) { + // Unlike {@link ClassNamingForNameMapper.Builder#addMemberEntry}, + // the key is original signature. + if (entry.isMethodNaming()) { + methodMembers.put((MethodSignature) entry.getOriginalSignature(), entry); + } else { + fieldMembers.put((FieldSignature) entry.getOriginalSignature(), entry); + } + return this; + } + + @Override + ClassNamingForMapApplier build() { + return new ClassNamingForMapApplier(renamedName, originalName, methodMembers, fieldMembers); + } + } + + static Builder builder(String renamedName, String originalName) { + return new Builder(renamedName, originalName); + } + + private final String originalName; + final String renamedName; + + private final ImmutableMap<MethodSignature, MemberNaming> methodMembers; + private final ImmutableMap<FieldSignature, MemberNaming> fieldMembers; + + // Constructor to help chaining {@link ClassNamingForMapApplier} according to class hierarchy. + ClassNamingForMapApplier(ClassNamingForMapApplier proxy) { + this(proxy.renamedName, proxy.originalName, proxy.methodMembers, proxy.fieldMembers); + } + + private ClassNamingForMapApplier( + String renamedName, + String originalName, + Map<MethodSignature, MemberNaming> methodMembers, + Map<FieldSignature, MemberNaming> fieldMembers) { + this.renamedName = renamedName; + this.originalName = originalName; + this.methodMembers = ImmutableMap.copyOf(methodMembers); + this.fieldMembers = ImmutableMap.copyOf(fieldMembers); + } + + @Override + public <T extends Throwable> void forAllMemberNaming( + ThrowingConsumer<MemberNaming, T> consumer) throws T { + forAllFieldNaming(consumer); + forAllMethodNaming(consumer); + } + + @Override + public <T extends Throwable> void forAllFieldNaming( + ThrowingConsumer<MemberNaming, T> consumer) throws T { + for (MemberNaming naming : fieldMembers.values()) { + consumer.accept(naming); + } + } + + @Override + public <T extends Throwable> void forAllMethodNaming( + ThrowingConsumer<MemberNaming, T> consumer) throws T { + for (MemberNaming naming : methodMembers.values()) { + consumer.accept(naming); + } + } + + @Override + public MemberNaming lookup(Signature renamedSignature) { + // As the key is inverted, this looks a lot like + // {@link ClassNamingForNameMapper#lookupByOriginalSignature}. + if (renamedSignature.kind() == SignatureKind.METHOD) { + for (MemberNaming memberNaming : methodMembers.values()) { + if (memberNaming.getRenamedSignature().equals(renamedSignature)) { + return memberNaming; + } + } + return null; + } else { + assert renamedSignature.kind() == SignatureKind.FIELD; + for (MemberNaming memberNaming : fieldMembers.values()) { + if (memberNaming.getRenamedSignature().equals(renamedSignature)) { + return memberNaming; + } + } + return null; + } + } + + @Override + public MemberNaming lookupByOriginalSignature(Signature original) { + // As the key is inverted, this looks a lot like {@link ClassNamingForNameMapper#lookup}. + if (original.kind() == SignatureKind.METHOD) { + return methodMembers.get(original); + } else { + assert original.kind() == SignatureKind.FIELD; + return fieldMembers.get(original); + } + } + + MemberNaming lookupByOriginalItem(DexField field) { + for (Map.Entry<FieldSignature, MemberNaming> entry : fieldMembers.entrySet()) { + FieldSignature signature = entry.getKey(); + if (signature.name.equals(field.name.toString()) + && signature.type.equals(field.type.getName())) { + return entry.getValue(); + } + } + return null; + } + + protected MemberNaming lookupByOriginalItem(DexMethod method) { + for (Map.Entry<MethodSignature, MemberNaming> entry : methodMembers.entrySet()) { + MethodSignature signature = entry.getKey(); + if (signature.name.equals(method.name.toString()) + && signature.type.equals(method.proto.returnType.toString()) + && Arrays.equals(signature.parameters, + Arrays.stream(method.proto.parameters.values) + .map(DexType::toString).toArray(String[]::new))) { + return entry.getValue(); + } + } + return null; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof ClassNamingForMapApplier)) { + return false; + } + + ClassNamingForMapApplier that = (ClassNamingForMapApplier) o; + + return originalName.equals(that.originalName) + && renamedName.equals(that.renamedName) + && methodMembers.equals(that.methodMembers) + && fieldMembers.equals(that.fieldMembers); + } + + @Override + public int hashCode() { + int result = originalName.hashCode(); + result = 31 * result + renamedName.hashCode(); + result = 31 * result + methodMembers.hashCode(); + result = 31 * result + fieldMembers.hashCode(); + return result; + } +} diff --git a/src/main/java/com/android/tools/r8/naming/ClassNamingForNameMapper.java b/src/main/java/com/android/tools/r8/naming/ClassNamingForNameMapper.java new file mode 100644 index 000000000..158549293 --- /dev/null +++ b/src/main/java/com/android/tools/r8/naming/ClassNamingForNameMapper.java @@ -0,0 +1,194 @@ +// Copyright (c) 2017, 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.naming.MemberNaming.FieldSignature; +import com.android.tools.r8.naming.MemberNaming.MethodSignature; +import com.android.tools.r8.naming.MemberNaming.Signature; +import com.android.tools.r8.naming.MemberNaming.Signature.SignatureKind; +import com.android.tools.r8.utils.ThrowingConsumer; +import com.google.common.collect.ImmutableMap; +import java.io.IOException; +import java.io.StringWriter; +import java.io.Writer; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Stores name information for a class. + * <p> + * This includes how the class was renamed and information on the classes members. + */ +public class ClassNamingForNameMapper implements ClassNaming { + + public static class Builder extends ClassNaming.Builder { + private final String originalName; + private final String renamedName; + private final Map<MethodSignature, MemberNaming> methodMembers = new HashMap<>(); + private final Map<FieldSignature, MemberNaming> fieldMembers = new HashMap<>(); + + private Builder(String renamedName, String originalName) { + this.originalName = originalName; + this.renamedName = renamedName; + } + + @Override + ClassNaming.Builder addMemberEntry(MemberNaming entry) { + if (entry.isMethodNaming()) { + methodMembers.put((MethodSignature) entry.getRenamedSignature(), entry); + } else { + fieldMembers.put((FieldSignature) entry.getRenamedSignature(), entry); + } + return this; + } + + @Override + ClassNamingForNameMapper build() { + return new ClassNamingForNameMapper(renamedName, originalName, methodMembers, fieldMembers); + } + } + + static Builder builder(String renamedName, String originalName) { + return new Builder(renamedName, originalName); + } + + public final String originalName; + private final String renamedName; + + /** + * Mapping from the renamed signature to the naming information for a member. + * <p> + * A renamed signature is a signature where the member's name has been obfuscated but not the type + * information. + **/ + private final ImmutableMap<MethodSignature, MemberNaming> methodMembers; + private final ImmutableMap<FieldSignature, MemberNaming> fieldMembers; + + private ClassNamingForNameMapper( + String renamedName, + String originalName, + Map<MethodSignature, MemberNaming> methodMembers, + Map<FieldSignature, MemberNaming> fieldMembers) { + this.renamedName = renamedName; + this.originalName = originalName; + this.methodMembers = ImmutableMap.copyOf(methodMembers); + this.fieldMembers = ImmutableMap.copyOf(fieldMembers); + } + + @Override + public MemberNaming lookup(Signature renamedSignature) { + if (renamedSignature.kind() == SignatureKind.METHOD) { + return methodMembers.get(renamedSignature); + } else { + assert renamedSignature.kind() == SignatureKind.FIELD; + return fieldMembers.get(renamedSignature); + } + } + + @Override + public MemberNaming lookupByOriginalSignature(Signature original) { + if (original.kind() == SignatureKind.METHOD) { + for (MemberNaming memberNaming: methodMembers.values()) { + if (memberNaming.signature.equals(original)) { + return memberNaming; + } + } + return null; + } else { + assert original.kind() == SignatureKind.FIELD; + for (MemberNaming memberNaming : fieldMembers.values()) { + if (memberNaming.signature.equals(original)) { + return memberNaming; + } + } + return null; + } + } + + public List<MemberNaming> lookupByOriginalName(String originalName) { + List<MemberNaming> result = new ArrayList<>(); + for (MemberNaming naming : methodMembers.values()) { + if (naming.signature.name.equals(originalName)) { + result.add(naming); + } + } + for (MemberNaming naming : fieldMembers.values()) { + if (naming.signature.name.equals(originalName)) { + result.add(naming); + } + } + return result; + } + + @Override + public <T extends Throwable> void forAllMemberNaming( + ThrowingConsumer<MemberNaming, T> consumer) throws T { + forAllFieldNaming(consumer); + forAllMethodNaming(consumer); + } + + @Override + public <T extends Throwable> void forAllFieldNaming( + ThrowingConsumer<MemberNaming, T> consumer) throws T { + for (MemberNaming naming : fieldMembers.values()) { + consumer.accept(naming); + } + } + + @Override + public <T extends Throwable> void forAllMethodNaming( + ThrowingConsumer<MemberNaming, T> consumer) throws T { + for (MemberNaming naming : methodMembers.values()) { + consumer.accept(naming); + } + } + + void write(Writer writer, boolean collapseRanges) throws IOException { + writer.append(originalName); + writer.append(" -> "); + writer.append(renamedName); + writer.append(":\n"); + forAllMemberNaming(memberNaming -> memberNaming.write(writer, collapseRanges, true)); + } + + @Override + public String toString() { + try { + StringWriter writer = new StringWriter(); + write(writer, false); + return writer.toString(); + } catch (IOException e) { + return e.toString(); + } + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof ClassNamingForNameMapper)) { + return false; + } + + ClassNamingForNameMapper that = (ClassNamingForNameMapper) o; + + return originalName.equals(that.originalName) + && renamedName.equals(that.renamedName) + && methodMembers.equals(that.methodMembers) + && fieldMembers.equals(that.fieldMembers); + } + + @Override + public int hashCode() { + int result = originalName.hashCode(); + result = 31 * result + renamedName.hashCode(); + result = 31 * result + methodMembers.hashCode(); + result = 31 * result + fieldMembers.hashCode(); + return result; + } +} + diff --git a/src/main/java/com/android/tools/r8/naming/MemberNaming.java b/src/main/java/com/android/tools/r8/naming/MemberNaming.java index 5f2ea9391..c494c0881 100644 --- a/src/main/java/com/android/tools/r8/naming/MemberNaming.java +++ b/src/main/java/com/android/tools/r8/naming/MemberNaming.java @@ -3,8 +3,11 @@ // BSD-style license that can be found in the LICENSE file. package com.android.tools.r8.naming; +import static com.android.tools.r8.utils.DescriptorUtils.javaTypeToDescriptor; + import com.android.tools.r8.dex.Constants; import com.android.tools.r8.graph.DexField; +import com.android.tools.r8.graph.DexItemFactory; import com.android.tools.r8.graph.DexMethod; import com.android.tools.r8.graph.DexType; import com.android.tools.r8.naming.MemberNaming.Signature.SignatureKind; @@ -90,6 +93,14 @@ public class MemberNaming { return signature; } + public String getOriginalName() { + return signature.name; + } + + public Signature getRenamedSignature() { + return renamedSignature; + } + public String getRenamedName() { return renamedSignature.name; } @@ -222,6 +233,13 @@ public class MemberNaming { field.type.toSourceString()); } + DexField toDexField(DexItemFactory factory, DexType clazz) { + return factory.createField( + clazz, + factory.createType(javaTypeToDescriptor(type)), + factory.createString(name)); + } + @Override Signature asRenamed(String renamedName) { return new FieldSignature(renamedName, type); @@ -297,6 +315,18 @@ public class MemberNaming { parameterTypes); } + DexMethod toDexMethod(DexItemFactory factory, DexType clazz) { + DexType[] paramTypes = new DexType[parameters.length]; + for (int i = 0; i < parameters.length; i++) { + paramTypes[i] = factory.createType(javaTypeToDescriptor(parameters[i])); + } + DexType returnType = factory.createType(javaTypeToDescriptor(type)); + return factory.createMethod( + clazz, + factory.createProto(returnType, paramTypes), + factory.createString(name)); + } + public static MethodSignature initializer(String[] parameters) { return new MethodSignature(Constants.INSTANCE_INITIALIZER_NAME, "void", parameters); } diff --git a/src/main/java/com/android/tools/r8/naming/ProguardMap.java b/src/main/java/com/android/tools/r8/naming/ProguardMap.java new file mode 100644 index 000000000..5b5c76e05 --- /dev/null +++ b/src/main/java/com/android/tools/r8/naming/ProguardMap.java @@ -0,0 +1,17 @@ +// Copyright (c) 2017, 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.DexType; + +public interface ProguardMap { + + abstract class Builder { + abstract ClassNaming.Builder classNamingBuilder(String renamedName, String originalName); + abstract ProguardMap build(); + } + + boolean hasMapping(DexType type); + ClassNaming getClassNaming(DexType type); +} diff --git a/src/main/java/com/android/tools/r8/naming/ProguardMapApplier.java b/src/main/java/com/android/tools/r8/naming/ProguardMapApplier.java new file mode 100644 index 000000000..1a2551ed0 --- /dev/null +++ b/src/main/java/com/android/tools/r8/naming/ProguardMapApplier.java @@ -0,0 +1,475 @@ +// Copyright (c) 2017, 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.DexAnnotation; +import com.android.tools.r8.graph.DexAnnotationSet; +import com.android.tools.r8.graph.DexClass; +import com.android.tools.r8.graph.DexEncodedAnnotation; +import com.android.tools.r8.graph.DexEncodedField; +import com.android.tools.r8.graph.DexEncodedMethod; +import com.android.tools.r8.graph.DexField; +import com.android.tools.r8.graph.DexMethod; +import com.android.tools.r8.graph.DexProto; +import com.android.tools.r8.graph.DexType; +import com.android.tools.r8.graph.DexTypeList; +import com.android.tools.r8.graph.GraphLense; +import com.android.tools.r8.naming.MemberNaming.FieldSignature; +import com.android.tools.r8.naming.MemberNaming.MethodSignature; +import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness; +import com.android.tools.r8.utils.ArrayUtils; +import com.android.tools.r8.utils.ThrowingConsumer; +import com.android.tools.r8.utils.Timing; +import it.unimi.dsi.fastutil.ints.Int2ObjectArrayMap; +import java.util.HashSet; +import java.util.IdentityHashMap; +import java.util.Map; +import java.util.Set; + +public class ProguardMapApplier { + + private final AppInfoWithLiveness appInfo; + private final GraphLense previousLense; + private final SeedMapper seedMapper; + + public ProguardMapApplier( + AppInfoWithLiveness appInfo, + GraphLense previousLense, + SeedMapper seedMapper) { + this.appInfo = appInfo; + this.previousLense = previousLense; + this.seedMapper = seedMapper; + } + + public GraphLense run(Timing timing) { + timing.begin("from-pg-map-to-lense"); + GraphLense lenseFromMap = new MapToLenseConverter().run(previousLense); + timing.end(); + timing.begin("fix-types-in-programs"); + GraphLense typeFixedLense = new TreeFixer(lenseFromMap).run(); + timing.end(); + return typeFixedLense; + } + + class MapToLenseConverter { + + private final ConflictFreeBuilder lenseBuilder; + private final Map<DexProto, DexProto> protoFixupCache = new IdentityHashMap<>(); + + MapToLenseConverter() { + lenseBuilder = new ConflictFreeBuilder(); + } + + private GraphLense run(GraphLense previousLense) { + // To handle inherited yet undefined methods in library classes, we are traversing types in + // a subtyping order. That also helps us detect conflicted mappings in a diamond case: + // LibItfA#foo -> a, LibItfB#foo -> b, while PrgA implements LibItfA and LibItfB. + // For all type appearances in members, we apply class mappings on-the-fly, e.g., + // LibA -> a: + // ...foo(LibB) -> bar + // LibB -> b: + // Suppose PrgA extends LibA, and type map for LibB to b is not applied when visiting PrgA. + // Then, method map would look like: PrgA#foo(LibB) -> PrgA#bar(LibB), + // which should be: PrgA#foo(LibB) -> PrgA#bar(b). + // In this case, we should check class naming for LibB in the given pg-map, and if exist, + // should apply that naming at the time when making a mapping for PrgA#foo. + applyClassMappingForClasses(appInfo.dexItemFactory.objectType, null); + // TODO(b/64802420): maybe worklist-based visiting? + // Suppose PrgA implements LibItfA, LibItfB, and LibItfC; and PrgB extends PrgA. + // With interface hierarchy-based visiting, both program classes are visited three times. + DexType.forAllInterfaces( + appInfo.dexItemFactory, + itf -> applyClassMappingForInterfaces(itf, null)); + return lenseBuilder.build(appInfo.dexItemFactory, previousLense); + } + + private void applyClassMappingForClasses( + DexType type, ChainedClassNaming classNamingFromSuperType) { + ChainedClassNaming classNaming = chainClassNaming(type, classNamingFromSuperType); + applyClassMapping(type, classNaming); + type.forAllExtendsSubtypes(subtype -> { + applyClassMappingForClasses(subtype, classNaming); + }); + } + + private void applyClassMappingForInterfaces( + DexType type, ChainedClassNaming classNamingFromSuperType) { + ChainedClassNaming classNaming = chainClassNaming(type, classNamingFromSuperType); + DexClass clazz = appInfo.definitionFor(type); + // We account program classes that implement library interfaces (to be obfuscated). + if (clazz != null && clazz.isProgramClass()) { + applyClassMapping(type, classNaming); + } + type.forAllExtendsSubtypes(subtype -> { + applyClassMappingForInterfaces(subtype, classNaming); + }); + type.forAllImplementsSubtypes(subtype -> { + applyClassMappingForInterfaces(subtype, classNaming); + }); + } + + private ChainedClassNaming chainClassNaming( + DexType type, ChainedClassNaming classNamingFromSuperType) { + // Use super-type's mapping or update the mapping if the current type has its own mapping. + return seedMapper.hasMapping(type) + ? new ChainedClassNaming(classNamingFromSuperType, seedMapper.getClassNaming(type)) + : classNamingFromSuperType; + } + + private void applyClassMapping(DexType type, ChainedClassNaming classNaming) { + if (classNaming != null) { + if (seedMapper.hasMapping(type) && lenseBuilder.lookup(type) == type) { + DexType appliedType = appInfo.dexItemFactory.createType(classNaming.renamedName); + lenseBuilder.map(type, appliedType); + } + applyMemberMapping(type, classNaming); + } + } + + private void applyMemberMapping(DexType from, ChainedClassNaming classNaming) { + DexClass clazz = appInfo.definitionFor(from); + if (clazz == null) return; + + Set<MemberNaming> appliedMemberNaming = new HashSet<>(); + clazz.forEachField(encodedField -> { + MemberNaming memberNaming = classNaming.lookupByOriginalItem(encodedField.field); + if (memberNaming != null) { + appliedMemberNaming.add(memberNaming); + applyFieldMapping(encodedField.field, memberNaming); + } + }); + + clazz.forEachMethod(encodedMethod -> { + MemberNaming memberNaming = classNaming.lookupByOriginalItem(encodedMethod.method); + if (memberNaming != null) { + appliedMemberNaming.add(memberNaming); + applyMethodMapping(encodedMethod.method, memberNaming); + } + }); + + // We need to handle a lib class that extends another lib class where some members are not + // overridden, resulting in absence of definitions. References to those members need to be + // redirected via lense as well. + if (clazz.isLibraryClass()) { + classNaming.forAllFieldNaming(memberNaming -> { + if (!appliedMemberNaming.contains(memberNaming)) { + DexField pretendedOriginalField = + ((FieldSignature) memberNaming.getOriginalSignature()) + .toDexField(appInfo.dexItemFactory, from); + applyFieldMapping(pretendedOriginalField, memberNaming); + } + }); + classNaming.forAllMethodNaming(memberNaming -> { + if (!appliedMemberNaming.contains(memberNaming)) { + DexMethod pretendedOriginalMethod = + ((MethodSignature) memberNaming.getOriginalSignature()) + .toDexMethod(appInfo.dexItemFactory, from); + applyMethodMapping(pretendedOriginalMethod, memberNaming); + } + }); + } + } + + private void applyFieldMapping(DexField originalField, MemberNaming memberNaming) { + FieldSignature appliedSignature = (FieldSignature) memberNaming.getRenamedSignature(); + DexField appliedField = + appInfo.dexItemFactory.createField( + applyClassMappingOnTheFly(originalField.clazz), + applyClassMappingOnTheFly(originalField.type), + appInfo.dexItemFactory.createString(appliedSignature.name)); + lenseBuilder.map(originalField, appliedField); + } + + private void applyMethodMapping(DexMethod originalMethod, MemberNaming memberNaming) { + MethodSignature appliedSignature = (MethodSignature) memberNaming.getRenamedSignature(); + DexMethod appliedMethod = + appInfo.dexItemFactory.createMethod( + applyClassMappingOnTheFly(originalMethod.holder), + applyClassMappingOnTheFly(originalMethod.proto), + appInfo.dexItemFactory.createString(appliedSignature.name)); + lenseBuilder.map(originalMethod, appliedMethod); + } + + private DexType applyClassMappingOnTheFly(DexType from) { + if (seedMapper.hasMapping(from)) { + DexType appliedType = lenseBuilder.lookup(from); + if (appliedType != from) { + return appliedType; + } + // If not applied yet, build the type mapping here. + // Note that, unlike {@link #applyClassMapping}, we don't apply member mappings. + ClassNamingForMapApplier classNaming = seedMapper.getClassNaming(from); + appliedType = appInfo.dexItemFactory.createType(classNaming.renamedName); + lenseBuilder.map(from, appliedType); + return appliedType; + } + return from; + } + + private DexProto applyClassMappingOnTheFly(DexProto proto) { + DexProto result = protoFixupCache.get(proto); + if (result == null) { + DexType returnType = applyClassMappingOnTheFly(proto.returnType); + DexType[] arguments = applyClassMappingOnTheFly(proto.parameters.values); + if (arguments != null || returnType != proto.returnType) { + arguments = arguments == null ? proto.parameters.values : arguments; + result = appInfo.dexItemFactory.createProto(returnType, arguments); + } else { + result = proto; + } + protoFixupCache.put(proto, result); + } + return result; + } + + private DexType[] applyClassMappingOnTheFly(DexType[] types) { + Map<Integer, DexType> changed = new Int2ObjectArrayMap<>(); + for (int i = 0; i < types.length; i++) { + DexType applied = applyClassMappingOnTheFly(types[i]); + if (applied != types[i]) { + changed.put(i, applied); + } + } + return changed.isEmpty() + ? null + : ArrayUtils.copyWithSparseChanges(DexType[].class, types, changed); + } + } + + static class ChainedClassNaming extends ClassNamingForMapApplier { + final ChainedClassNaming superClassNaming; + + ChainedClassNaming( + ChainedClassNaming superClassNaming, + ClassNamingForMapApplier thisClassNaming) { + super(thisClassNaming); + this.superClassNaming = superClassNaming; + } + + @Override + public <T extends Throwable> void forAllMethodNaming( + ThrowingConsumer<MemberNaming, T> consumer) throws T { + super.forAllMethodNaming(consumer); + if (superClassNaming != null) { + superClassNaming.forAllMethodNaming(consumer); + } + } + + @Override + protected MemberNaming lookupByOriginalItem(DexMethod method) { + MemberNaming memberNaming = super.lookupByOriginalItem(method); + if (memberNaming != null) { + return memberNaming; + } + // Moving up if chained. + if (superClassNaming != null) { + return superClassNaming.lookupByOriginalItem(method); + } + return null; + } + } + + class TreeFixer { + private final ConflictFreeBuilder lenseBuilder; + private final GraphLense appliedLense; + private final Map<DexProto, DexProto> protoFixupCache = new IdentityHashMap<>(); + + TreeFixer(GraphLense appliedLense) { + this.lenseBuilder = new ConflictFreeBuilder(); + this.appliedLense = appliedLense; + } + + private GraphLense run() { + // Suppose PrgA extends LibA, and adds its own method, say foo that inputs LibA instance. + // If that library class and members are renamed somehow, all the inherited members in PrgA + // will be also renamed when applying those mappings. However, that newly added method, foo, + // with renamed LibA as an argument, won't be updated. Here at TreeFixer, we want to change + // PrgA#foo signature: from LibA to a renamed name. + appInfo.classes().forEach(this::fixClass); + appInfo.libraryClasses().forEach(this::fixClass); + return lenseBuilder.build(appInfo.dexItemFactory, appliedLense); + } + + private void fixClass(DexClass clazz) { + clazz.type = substituteType(clazz.type, null); + clazz.superType = substituteType(clazz.superType, null); + clazz.interfaces = substituteTypesIn(clazz.interfaces); + clazz.annotations = substituteTypesIn(clazz.annotations); + clazz.setDirectMethods(substituteTypesIn(clazz.directMethods())); + clazz.setVirtualMethods(substituteTypesIn(clazz.virtualMethods())); + clazz.setStaticFields(substituteTypesIn(clazz.staticFields())); + clazz.setInstanceFields(substituteTypesIn(clazz.instanceFields())); + } + + private DexEncodedMethod[] substituteTypesIn(DexEncodedMethod[] methods) { + if (methods == null) { + return null; + } + for (int i = 0; i < methods.length; i++) { + DexEncodedMethod encodedMethod = methods[i]; + DexMethod appliedMethod = appliedLense.lookupMethod(encodedMethod.method, encodedMethod); + DexProto newProto = substituteTypesIn(appliedMethod.proto, encodedMethod); + DexMethod newMethod; + if (newProto != appliedMethod.proto) { + newMethod = appInfo.dexItemFactory.createMethod( + substituteType(appliedMethod.holder, encodedMethod), newProto, appliedMethod.name); + lenseBuilder.map(encodedMethod.method, newMethod); + } else { + newMethod = appliedMethod; + } + // Explicitly fix members. + methods[i] = encodedMethod.toTypeSubstitutedMethod(newMethod); + } + return methods; + } + + private DexEncodedField[] substituteTypesIn(DexEncodedField[] fields) { + if (fields == null) { + return null; + } + for (int i = 0; i < fields.length; i++) { + DexEncodedField encodedField = fields[i]; + DexField appliedField = appliedLense.lookupField(encodedField.field, null); + DexType newType = substituteType(appliedField.type, null); + DexField newField; + if (newType != appliedField.type) { + newField = appInfo.dexItemFactory.createField( + substituteType(appliedField.clazz, null), newType, appliedField.name); + lenseBuilder.map(encodedField.field, newField); + } else { + newField = appliedField; + } + // Explicitly fix members. + fields[i] = encodedField.toTypeSubstitutedField(newField); + } + return fields; + } + + private DexProto substituteTypesIn(DexProto proto, DexEncodedMethod context) { + DexProto result = protoFixupCache.get(proto); + if (result == null) { + DexType returnType = substituteType(proto.returnType, context); + DexType[] arguments = substituteTypesIn(proto.parameters.values, context); + if (arguments != null || returnType != proto.returnType) { + arguments = arguments == null ? proto.parameters.values : arguments; + result = appInfo.dexItemFactory.createProto(returnType, arguments); + } else { + result = proto; + } + protoFixupCache.put(proto, result); + } + return result; + } + + private DexAnnotationSet substituteTypesIn(DexAnnotationSet annotations) { + if (annotations.isEmpty()) { + return annotations; + } + DexAnnotation[] result = substituteTypesIn(annotations.annotations); + return result == null ? annotations : new DexAnnotationSet(result); + } + + private DexAnnotation[] substituteTypesIn(DexAnnotation[] annotations) { + Map<Integer, DexAnnotation> changed = new Int2ObjectArrayMap<>(); + for (int i = 0; i < annotations.length; i++) { + DexAnnotation applied = substituteTypesIn(annotations[i]); + if (applied != annotations[i]) { + changed.put(i, applied); + } + } + return changed.isEmpty() + ? null + : ArrayUtils.copyWithSparseChanges(DexAnnotation[].class, annotations, changed); + } + + private DexAnnotation substituteTypesIn(DexAnnotation annotation) { + return new DexAnnotation(annotation.visibility, substituteTypesIn(annotation.annotation)); + } + + private DexEncodedAnnotation substituteTypesIn(DexEncodedAnnotation annotation) { + return new DexEncodedAnnotation(substituteType(annotation.type, null), annotation.elements); + } + + private DexTypeList substituteTypesIn(DexTypeList types) { + if (types.isEmpty()) { + return types; + } + DexType[] result = substituteTypesIn(types.values, null); + return result == null ? types : new DexTypeList(result); + } + + private DexType[] substituteTypesIn(DexType[] types, DexEncodedMethod context) { + Map<Integer, DexType> changed = new Int2ObjectArrayMap<>(); + for (int i = 0; i < types.length; i++) { + DexType applied = substituteType(types[i], context); + if (applied != types[i]) { + changed.put(i, applied); + } + } + return changed.isEmpty() + ? null + : ArrayUtils.copyWithSparseChanges(DexType[].class, types, changed); + } + + private DexType substituteType(DexType type, DexEncodedMethod context) { + if (type == null) { + return null; + } + if (type.isArrayType()) { + DexType base = type.toBaseType(appInfo.dexItemFactory); + DexType fixed = substituteType(base, context); + if (base == fixed) { + return type; + } else { + return type.replaceBaseType(fixed, appInfo.dexItemFactory); + } + } + return appliedLense.lookupType(type, context); + } + } + + private static class ConflictFreeBuilder extends GraphLense.Builder { + ConflictFreeBuilder() { + super(); + } + + @Override + public void map(DexType from, DexType to) { + if (typeMap.containsKey(from)) { + String keptName = typeMap.get(from).getName(); + if (!keptName.equals(to.getName())) { + throw ProguardMapError.keptTypeWasRenamed(from, keptName, to.getName()); + } + } + super.map(from, to); + } + + @Override + public void map(DexMethod from, DexMethod to) { + if (methodMap.containsKey(from)) { + String keptName = methodMap.get(from).name.toString(); + if (!keptName.equals(to.name.toString())) { + throw ProguardMapError.keptMethodWasRenamed(from, keptName, to.name.toString()); + } + } + super.map(from, to); + } + + @Override + public void map(DexField from, DexField to) { + if (fieldMap.containsKey(from)) { + String keptName = fieldMap.get(from).name.toString(); + if (!keptName.equals(to.name.toString())) { + throw ProguardMapError.keptFieldWasRenamed(from, keptName, to.name.toString()); + } + } + super.map(from, to); + } + + // Helper to determine whether to apply the class mapping on-the-fly or applied already. + DexType lookup(DexType from) { + return typeMap.getOrDefault(from, from); + } + } +} diff --git a/src/main/java/com/android/tools/r8/naming/ProguardMapError.java b/src/main/java/com/android/tools/r8/naming/ProguardMapError.java new file mode 100644 index 000000000..ce659e1c4 --- /dev/null +++ b/src/main/java/com/android/tools/r8/naming/ProguardMapError.java @@ -0,0 +1,38 @@ +// Copyright (c) 2017, 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.errors.CompilationError; +import com.android.tools.r8.graph.DexField; +import com.android.tools.r8.graph.DexMethod; +import com.android.tools.r8.graph.DexType; + +public class ProguardMapError extends CompilationError { + private ProguardMapError(String message) { + super(message); + } + + private ProguardMapError(String message, Throwable cause) { + super(message, cause); + } + + static ProguardMapError keptTypeWasRenamed(DexType type, String keptName, String rename) { + return new ProguardMapError( + "Warning: " + type + createMessageForConflict(keptName, rename)); + } + + static ProguardMapError keptMethodWasRenamed(DexMethod method, String keptName, String rename) { + return new ProguardMapError( + "Warning: " + method.toSourceString() + createMessageForConflict(keptName, rename)); + } + + static ProguardMapError keptFieldWasRenamed(DexField field, String keptName, String rename) { + return new ProguardMapError( + "Warning: " + field.toSourceString() + createMessageForConflict(keptName, rename)); + } + + private static String createMessageForConflict(String keptName, String rename) { + return " is not being kept as '" + keptName + "', but remapped to '" + rename + "'"; + } +} diff --git a/src/main/java/com/android/tools/r8/naming/ProguardMapReader.java b/src/main/java/com/android/tools/r8/naming/ProguardMapReader.java index 415b8a719..5f1e150f3 100644 --- a/src/main/java/com/android/tools/r8/naming/ProguardMapReader.java +++ b/src/main/java/com/android/tools/r8/naming/ProguardMapReader.java @@ -9,20 +9,12 @@ import com.android.tools.r8.naming.MemberNaming.MethodSignature; import com.android.tools.r8.naming.MemberNaming.Range; import com.android.tools.r8.naming.MemberNaming.Signature; import com.android.tools.r8.naming.MemberNaming.SingleLineRange; -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.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.Path; import java.util.ArrayList; import java.util.HashMap; import java.util.LinkedList; import java.util.List; -import java.util.Map; import java.util.function.Consumer; /** @@ -71,26 +63,10 @@ public class ProguardMapReader implements AutoCloseable { } } - private ProguardMapReader(BufferedReader reader) { + ProguardMapReader(BufferedReader reader) { this.reader = reader; } - public static ClassNameMapper mapperFromInputStream(InputStream in) throws IOException { - BufferedReader reader = new BufferedReader(new InputStreamReader(in, "UTF8")); - try (ProguardMapReader proguardReader = new ProguardMapReader(reader)) { - return proguardReader.parse(); - } - } - - public static ClassNameMapper mapperFromFile(Path path) throws IOException { - return mapperFromInputStream(Files.newInputStream(path)); - } - - public static ClassNameMapper mapperFromString(String contents) throws IOException { - return mapperFromInputStream( - new ByteArrayInputStream(contents.getBytes(StandardCharsets.UTF_8))); - } - // Internal parser state private int lineNo = 0; private int lineOffset = 0; @@ -146,17 +122,15 @@ public class ProguardMapReader implements AutoCloseable { return c; } - public ClassNameMapper parse() throws IOException { + void parse(ProguardMap.Builder mapBuilder) throws IOException { // Read the first line. line = reader.readLine(); - Map<String, ClassNaming> classNames = parseClassMappings(); - return new ClassNameMapper(classNames); + parseClassMappings(mapBuilder); } // Parsing of entries - private Map<String, ClassNaming> parseClassMappings() throws IOException { - ImmutableMap.Builder<String, ClassNaming> builder = ImmutableMap.builder(); + private void parseClassMappings(ProguardMap.Builder mapBuilder) throws IOException { while (hasLine()) { String before = parseType(false); skipWhitespace(); @@ -172,16 +146,14 @@ public class ProguardMapReader implements AutoCloseable { skipWhitespace(); String after = parseType(false); expect(':'); - ClassNaming currentClass = new ClassNaming(after, before); - builder.put(after, currentClass); + ClassNaming.Builder currentClassBuilder = mapBuilder.classNamingBuilder(after, before); if (nextLine()) { - parseMemberMappings(currentClass); + parseMemberMappings(currentClassBuilder); } } - return builder.build(); } - private void parseMemberMappings(ClassNaming currentClass) throws IOException { + private void parseMemberMappings(ClassNaming.Builder classNamingBuilder) throws IOException { MemberNaming current = null; Range previousInlineRange = null; Signature previousSignature = null; @@ -226,7 +198,7 @@ public class ProguardMapReader implements AutoCloseable { if (current == null || !previousSignature.equals(current.signature)) { if (collectedInfos.size() == 1) { current = new MemberNaming(previousSignature, previousRenamedName, previousInlineRange); - currentClass.addMemberEntry(current); + classNamingBuilder.addMemberEntry(current); } else { if (Log.ENABLED && !collectedInfos.isEmpty()) { Log.warn(getClass(), @@ -254,7 +226,7 @@ public class ProguardMapReader implements AutoCloseable { if (current == null || !previousSignature.equals(current.signature)) { if (collectedInfos.size() == 1) { current = new MemberNaming(previousSignature, previousRenamedName, previousInlineRange); - currentClass.addMemberEntry(current); + classNamingBuilder.addMemberEntry(current); } } else { MemberNaming finalCurrent = current; diff --git a/src/main/java/com/android/tools/r8/naming/SeedMapper.java b/src/main/java/com/android/tools/r8/naming/SeedMapper.java new file mode 100644 index 000000000..c4931f8d3 --- /dev/null +++ b/src/main/java/com/android/tools/r8/naming/SeedMapper.java @@ -0,0 +1,90 @@ +// Copyright (c) 2017, 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.DescriptorUtils.javaTypeToDescriptor; + +import com.android.tools.r8.graph.DexType; +import com.android.tools.r8.naming.MemberNaming.Signature; +import com.google.common.collect.ImmutableMap; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Map; + +/** + * Mappings read from the given ProGuard map. + * <p> + * The main differences of this against {@link ClassNameMapper} and + * {@link ClassNameMapper#getObfuscatedToOriginalMapping()} are: + * 1) the key is the original descriptor, not the obfuscated java name. Thus, it is much easier + * to look up what mapping to apply while traversing {@link DexType}s; and + * 2) the value is {@link ClassNamingForMapApplier}, another variant of {@link ClassNaming}, + * which also uses original {@link Signature} as a key, instead of renamed {@link Signature}. + */ +public class SeedMapper implements ProguardMap { + + static class Builder extends ProguardMap.Builder { + final ImmutableMap.Builder<String, ClassNamingForMapApplier.Builder> mapBuilder; + + private Builder() { + mapBuilder = ImmutableMap.builder(); + } + + @Override + ClassNamingForMapApplier.Builder classNamingBuilder(String renamedName, String originalName) { + String originalDescriptor = javaTypeToDescriptor(originalName); + ClassNamingForMapApplier.Builder classNamingBuilder = + ClassNamingForMapApplier.builder(javaTypeToDescriptor(renamedName), originalDescriptor); + mapBuilder.put(originalDescriptor, classNamingBuilder); + return classNamingBuilder; + } + + @Override + SeedMapper build() { + return new SeedMapper(mapBuilder.build()); + } + } + + static Builder builder() { + return new Builder(); + } + + private static SeedMapper seedMapperFromInputStream(InputStream in) throws IOException { + BufferedReader reader = new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8)); + try (ProguardMapReader proguardReader = new ProguardMapReader(reader)) { + SeedMapper.Builder builder = SeedMapper.builder(); + proguardReader.parse(builder); + return builder.build(); + } + } + + public static SeedMapper seedMapperFromFile(Path path) throws IOException { + return seedMapperFromInputStream(Files.newInputStream(path)); + } + + private final ImmutableMap<String, ClassNamingForMapApplier> mappings; + + private SeedMapper(Map<String, ClassNamingForMapApplier.Builder> mappings) { + ImmutableMap.Builder<String, ClassNamingForMapApplier> builder = ImmutableMap.builder(); + for(Map.Entry<String, ClassNamingForMapApplier.Builder> entry : mappings.entrySet()) { + builder.put(entry.getKey(), entry.getValue().build()); + } + this.mappings = builder.build(); + } + + @Override + public boolean hasMapping(DexType type) { + return mappings.containsKey(type.descriptor.toString()); + } + + @Override + public ClassNamingForMapApplier getClassNaming(DexType type) { + return mappings.get(type.descriptor.toString()); + } +} diff --git a/src/main/java/com/android/tools/r8/optimize/BridgeMethodAnalysis.java b/src/main/java/com/android/tools/r8/optimize/BridgeMethodAnalysis.java index 72ecedfbd..3fabf4705 100644 --- a/src/main/java/com/android/tools/r8/optimize/BridgeMethodAnalysis.java +++ b/src/main/java/com/android/tools/r8/optimize/BridgeMethodAnalysis.java @@ -112,5 +112,18 @@ public class BridgeMethodAnalysis { public boolean isContextFree() { return false; } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("------ BridgeMap ------").append(System.lineSeparator()); + for (Map.Entry<DexMethod, DexMethod> entry : bridgeTargetToBridgeMap.entrySet()) { + builder.append(entry.getKey().toSourceString()).append(" -> "); + builder.append(entry.getValue().toSourceString()).append(System.lineSeparator()); + } + builder.append("-----------------------").append(System.lineSeparator()); + builder.append(previousLense.toString()); + return builder.toString(); + } } } diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java index d24a3578f..71e7a734c 100644 --- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java +++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java @@ -255,8 +255,6 @@ public class ProguardConfigurationParser { } } else if (acceptString("applymapping")) { configurationBuilder.setApplyMappingFile(parseFileName()); - // TODO(b/64802420): warn until it is fully implemented. - warnIgnoringOptions("applymapping"); } else if (acceptString("assumenosideeffects")) { ProguardAssumeNoSideEffectRule rule = parseAssumeNoSideEffectsRule(); configurationBuilder.addRule(rule); diff --git a/src/main/java/com/android/tools/r8/utils/AndroidApp.java b/src/main/java/com/android/tools/r8/utils/AndroidApp.java index 980eb1957..67aa28822 100644 --- a/src/main/java/com/android/tools/r8/utils/AndroidApp.java +++ b/src/main/java/com/android/tools/r8/utils/AndroidApp.java @@ -613,15 +613,6 @@ public class AndroidApp { } /** - * Inform whether ProGuard map has already been set or not. - * - * ProGuard option -applymapping will override R8/Dissemble option -pg-map. - */ - public boolean hasProguardMap() { - return proguardMap != null; - } - - /** * Set proguard-map file. */ public Builder setProguardMapFile(Path file) { diff --git a/src/main/java/com/android/tools/r8/utils/ArrayUtils.java b/src/main/java/com/android/tools/r8/utils/ArrayUtils.java new file mode 100644 index 000000000..976768bd8 --- /dev/null +++ b/src/main/java/com/android/tools/r8/utils/ArrayUtils.java @@ -0,0 +1,36 @@ +// Copyright (c) 2017, 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.utils; + +import java.lang.reflect.Array; +import java.util.Map; + +public class ArrayUtils { + + /** + * Copies the input array and then applies specified sparse changes. + * + * @param clazz target type's Class to cast + * @param original an array of original elements + * @param changedElements sparse changes to apply + * @param <T> target type + * @return a copy of original arrays while sparse changes are applied + */ + public static <T> T[] copyWithSparseChanges( + Class<T[]> clazz, T[] original, Map<Integer, T> changedElements) { + T[] results = clazz.cast(Array.newInstance(clazz.getComponentType(), original.length)); + int pos = 0; + for (Map.Entry<Integer, T> entry : changedElements.entrySet()) { + int i = entry.getKey(); + System.arraycopy(original, pos, results, pos, i - pos); + results[i] = entry.getValue(); + pos = i + 1; + } + if (pos < original.length) { + System.arraycopy(original, pos, results, pos, original.length - pos); + } + return results; + } + +} diff --git a/src/test/examples/applymapping044/AsubB.java b/src/test/examples/applymapping044/AsubB.java new file mode 100644 index 000000000..2c5fd0cd2 --- /dev/null +++ b/src/test/examples/applymapping044/AsubB.java @@ -0,0 +1,13 @@ +// Copyright (c) 2017, 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 applymapping044; + +import naming044.A; +import naming044.sub.SubB; + +public class AsubB extends SubB { + public int boo(A a) { + return f(a) * 3; + } +} diff --git a/src/test/examples/applymapping044/Main.java b/src/test/examples/applymapping044/Main.java new file mode 100644 index 000000000..9d5f57752 --- /dev/null +++ b/src/test/examples/applymapping044/Main.java @@ -0,0 +1,20 @@ +// Copyright (c) 2017, 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 applymapping044; + +import naming044.A; +import naming044.B; +import naming044.sub.SubB; + +public class Main { + public static void main(String[] args) { + B.m(); + SubB.n(); + A a = new A(); + B b = new B(); + b.f(a); + AsubB subB = new AsubB(); + subB.f(a); + } +} diff --git a/src/test/examples/applymapping044/keep-rules-apply-mapping.txt b/src/test/examples/applymapping044/keep-rules-apply-mapping.txt new file mode 100644 index 000000000..56f43b2c8 --- /dev/null +++ b/src/test/examples/applymapping044/keep-rules-apply-mapping.txt @@ -0,0 +1,13 @@ +# Copyright (c) 2017, 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. + +-keep public class applymapping044.Main { + public static void main(...); +} + +-keep,allowobfuscation class * { + *; +} + +-applymapping test-mapping.txt diff --git a/src/test/examples/applymapping044/keep-rules.txt b/src/test/examples/applymapping044/keep-rules.txt new file mode 100644 index 000000000..efed0ec62 --- /dev/null +++ b/src/test/examples/applymapping044/keep-rules.txt @@ -0,0 +1,11 @@ +# Copyright (c) 2017, 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. + +-keep public class applymapping044.Main { + public static void main(...); +} + +-keep,allowobfuscation class * { + *; +} diff --git a/src/test/examples/applymapping044/test-mapping.txt b/src/test/examples/applymapping044/test-mapping.txt new file mode 100644 index 000000000..41c2c5c2a --- /dev/null +++ b/src/test/examples/applymapping044/test-mapping.txt @@ -0,0 +1,9 @@ +naming044.A -> naming044.x: + int f -> o +naming044.B -> naming044.y: + int m() -> n + int f(naming044.A) -> p +naming044.sub.SubA -> naming044.z.x: + int f -> q +naming044.sub.SubB -> naming044.z.y: + int n() -> m diff --git a/src/test/examples/minification/conflict-mapping.txt b/src/test/examples/minification/conflict-mapping.txt new file mode 100644 index 000000000..a4453da05 --- /dev/null +++ b/src/test/examples/minification/conflict-mapping.txt @@ -0,0 +1,4 @@ +minification.InterfaceA -> ItfA: + int functionFromIntToInt(int) -> foo +minification.InterfaceB -> ItfB: + int functionFromIntToInt(int) -> bar diff --git a/src/test/examples/minification/keep-rules-apply-conflict-mapping.txt b/src/test/examples/minification/keep-rules-apply-conflict-mapping.txt new file mode 100644 index 000000000..41f2261af --- /dev/null +++ b/src/test/examples/minification/keep-rules-apply-conflict-mapping.txt @@ -0,0 +1,14 @@ +# Copyright (c) 2017, 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. + +-applymapping conflict-mapping.txt + +# Keep the application entry point. Get rid of everything that is not +# reachable from there. +-keep public class minification.Minification { + public static void main(...); +} + +# allow access modification to enable minification +-allowaccessmodification diff --git a/src/test/examples/naming001/keep-rules-105.txt b/src/test/examples/naming001/keep-rules-105.txt new file mode 100644 index 000000000..f3bf7f6f9 --- /dev/null +++ b/src/test/examples/naming001/keep-rules-105.txt @@ -0,0 +1,11 @@ +# Copyright (c) 2017, 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. + +-allowaccessmodification + +-keep class naming001.D { + public static void main(...); +} + +-applymapping mapping-105.txt diff --git a/src/test/examples/naming001/mapping-105.txt b/src/test/examples/naming001/mapping-105.txt new file mode 100644 index 000000000..3437a9331 --- /dev/null +++ b/src/test/examples/naming001/mapping-105.txt @@ -0,0 +1,2 @@ +naming001.D -> naming001.D: + void keep() -> peek diff --git a/src/test/examples/naming044/B.java b/src/test/examples/naming044/B.java index 758094582..f723423d9 100644 --- a/src/test/examples/naming044/B.java +++ b/src/test/examples/naming044/B.java @@ -7,4 +7,7 @@ public class B { public static int m() { return A.f; } + public int f(A a) { + return a.f; + } } diff --git a/src/test/examples/naming044/sub/SubB.java b/src/test/examples/naming044/sub/SubB.java index badc5f2e6..823de8c2b 100644 --- a/src/test/examples/naming044/sub/SubB.java +++ b/src/test/examples/naming044/sub/SubB.java @@ -3,7 +3,9 @@ // BSD-style license that can be found in the LICENSE file. package naming044.sub; -public class SubB { +import naming044.B; + +public class SubB extends B { public static int n() { return SubA.f; } diff --git a/src/test/java/com/android/tools/r8/debug/DebugTestBase.java b/src/test/java/com/android/tools/r8/debug/DebugTestBase.java index de8c673d2..f3fd9d21a 100644 --- a/src/test/java/com/android/tools/r8/debug/DebugTestBase.java +++ b/src/test/java/com/android/tools/r8/debug/DebugTestBase.java @@ -14,11 +14,10 @@ import com.android.tools.r8.ToolHelper.DexVm.Version; import com.android.tools.r8.ToolHelper.ProcessResult; import com.android.tools.r8.errors.Unreachable; import com.android.tools.r8.naming.ClassNameMapper; -import com.android.tools.r8.naming.ClassNaming; +import com.android.tools.r8.naming.ClassNamingForNameMapper; import com.android.tools.r8.naming.MemberNaming; import com.android.tools.r8.naming.MemberNaming.MethodSignature; import com.android.tools.r8.naming.MemberNaming.Signature; -import com.android.tools.r8.naming.ProguardMapReader; import com.android.tools.r8.shaking.ProguardConfiguration; import com.android.tools.r8.shaking.ProguardRuleParserException; import com.android.tools.r8.utils.AndroidApiLevel; @@ -353,7 +352,7 @@ public abstract class DebugTestBase { paths[indexPath++] = languageFeatures.getDexPath().toString(); Path proguardMapPath = Paths.get(paths[indexPath - 1]).resolveSibling(PROGUARD_MAP_FILENAME); if (Files.exists(proguardMapPath)) { - classNameMapper = ProguardMapReader.mapperFromFile(proguardMapPath); + classNameMapper = ClassNameMapper.mapperFromFile(proguardMapPath); } } for (Path extraPath : extraPaths) { @@ -670,7 +669,7 @@ public abstract class DebugTestBase { @Override public String getObfuscatedMethodName( String originalClassName, String originalMethodName, String methodSignatureOrNull) { - ClassNaming naming; + ClassNamingForNameMapper naming; String obfuscatedClassName = classNameMapper.getObfuscatedToOriginalMapping().inverse().get(originalClassName); if (obfuscatedClassName != null) { @@ -705,7 +704,7 @@ public abstract class DebugTestBase { /** Assumes classNameMapper is valid. Return null if no member naming found. */ private MemberNaming getMemberNaming( String obfuscatedClassName, String obfuscatedMethodName, String genericMethodSignature) { - ClassNaming classNaming = classNameMapper.getClassNaming(obfuscatedClassName); + ClassNamingForNameMapper classNaming = classNameMapper.getClassNaming(obfuscatedClassName); if (classNaming == null) { return null; } diff --git a/src/test/java/com/android/tools/r8/naming/ApplyMappingTest.java b/src/test/java/com/android/tools/r8/naming/ApplyMappingTest.java new file mode 100644 index 000000000..dac440a4d --- /dev/null +++ b/src/test/java/com/android/tools/r8/naming/ApplyMappingTest.java @@ -0,0 +1,238 @@ +// Copyright (c) 2017, 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 org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import com.android.tools.r8.CompilationException; +import com.android.tools.r8.R8Command; +import com.android.tools.r8.ToolHelper; +import com.android.tools.r8.graph.DexMethod; +import com.android.tools.r8.graph.DexType; +import com.android.tools.r8.shaking.ProguardRuleParserException; +import com.android.tools.r8.utils.AndroidApp; +import com.android.tools.r8.utils.DexInspector; +import com.android.tools.r8.utils.DexInspector.InstructionSubject; +import com.android.tools.r8.utils.DexInspector.InvokeInstructionSubject; +import com.android.tools.r8.utils.DexInspector.MethodSubject; +import com.android.tools.r8.utils.FileUtils; +import java.io.IOException; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Iterator; +import java.util.concurrent.ExecutionException; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; + +public class ApplyMappingTest { + + private static final String MAPPING = "test-mapping.txt"; + + private static final Path MINIFICATION_JAR = + Paths.get(ToolHelper.EXAMPLES_BUILD_DIR, "minification" + FileUtils.JAR_EXTENSION); + + private static final Path NAMING001_JAR = + Paths.get(ToolHelper.EXAMPLES_BUILD_DIR, "naming001" + FileUtils.JAR_EXTENSION); + + private static final Path NAMING044_JAR = + Paths.get(ToolHelper.EXAMPLES_BUILD_DIR, "naming044" + FileUtils.JAR_EXTENSION); + + private static final Path APPLYMAPPING044_JAR = + Paths.get(ToolHelper.EXAMPLES_BUILD_DIR, "applymapping044" + FileUtils.JAR_EXTENSION); + + @Rule + public TemporaryFolder temp = new TemporaryFolder(); + + private Path out; + + @Before + public void setup() throws IOException { + out = temp.newFolder("outdex").toPath(); + } + + @Test + public void test044_obfuscate_and_apply() + throws IOException, CompilationException, ProguardRuleParserException, ExecutionException { + // keep rules that allow obfuscations while keeping everything. + Path flagForObfuscation = + Paths.get(ToolHelper.EXAMPLES_DIR, "naming044", "keep-rules-005.txt"); + Path proguardMap = out.resolve(MAPPING); + AndroidApp obfuscatedApp = runR8( + getCommandForApps(out, flagForObfuscation, NAMING044_JAR) + .addProguardConfigurationConsumer(c -> { + c.setPrintMapping(true); + c.setPrintMappingFile(proguardMap); + }).build()); + + // Obviously, dumped map and resource inside the app should be *identical*. + ClassNameMapper mapperFromFile = ClassNameMapper.mapperFromFile(proguardMap); + ClassNameMapper mapperFromApp = + ClassNameMapper.mapperFromInputStream(obfuscatedApp.getProguardMap()); + assertEquals(mapperFromFile, mapperFromApp); + + Path instrOut = temp.newFolder("instr").toPath(); + Path flag = Paths.get(ToolHelper.EXAMPLES_DIR, "applymapping044", "keep-rules.txt"); + AndroidApp instrApp = runR8( + getCommandForInstrumentation(instrOut, flag, NAMING044_JAR, APPLYMAPPING044_JAR) + .addProguardConfigurationConsumer(c -> { + c.setApplyMappingFile(proguardMap); + }) + .setMinification(false) + .build()); + + DexInspector inspector = new DexInspector(instrApp); + MethodSubject main = inspector.clazz("applymapping044.Main").method(DexInspector.MAIN); + Iterator<InvokeInstructionSubject> iterator = + main.iterateInstructions(InstructionSubject::isInvoke); + // B#m() + String b = iterator.next().holder().toString(); + assertEquals("naming044.B", mapperFromApp.deobfuscateClassName(b)); + // sub.SubB#n() + String subB = iterator.next().holder().toString(); + assertEquals("naming044.sub.SubB", mapperFromApp.deobfuscateClassName(subB)); + // Skip A#<init> + iterator.next(); + // Skip B#<init> + iterator.next(); + // B#f(A) + InvokeInstructionSubject f = iterator.next(); + DexType a1 = f.invokedMethod().proto.parameters.values[0]; + assertNotEquals("naming044.A", a1.toString()); + assertEquals("naming044.A", mapperFromApp.deobfuscateClassName(a1.toString())); + assertNotEquals("f", f.invokedMethod().name.toSourceString()); + // Skip AsubB#<init> + iterator.next(); + // AsubB#f(A) + InvokeInstructionSubject overloaded_f = iterator.next(); + DexMethod aSubB_f = overloaded_f.invokedMethod(); + DexType a2 = aSubB_f.proto.parameters.values[0]; + assertNotEquals("naming044.A", a2.toString()); + assertEquals("naming044.A", mapperFromApp.deobfuscateClassName(a2.toString())); + assertNotEquals("f", overloaded_f.invokedMethod().name.toSourceString()); + // B#f == AsubB#f + assertEquals(f.invokedMethod().name.toString(), aSubB_f.name.toString()); + } + + @Test + public void test044_apply() + throws IOException, CompilationException, ProguardRuleParserException, ExecutionException { + Path flag = + Paths.get(ToolHelper.EXAMPLES_DIR, "applymapping044", "keep-rules-apply-mapping.txt"); + AndroidApp outputApp = runR8( + getCommandForInstrumentation(out, flag, NAMING044_JAR, APPLYMAPPING044_JAR) + .setMinification(false) + .build()); + + // Make sure the given proguard map is indeed applied. + DexInspector inspector = new DexInspector(outputApp); + MethodSubject main = inspector.clazz("applymapping044.Main").method(DexInspector.MAIN); + Iterator<InvokeInstructionSubject> iterator = + main.iterateInstructions(InstructionSubject::isInvoke); + // B#m() -> y#n() + InvokeInstructionSubject m = iterator.next(); + assertEquals("naming044.y", m.holder().toString()); + assertEquals("n", m.invokedMethod().name.toSourceString()); + // sub.SubB#n() -> z.y#m() + InvokeInstructionSubject n = iterator.next(); + assertEquals("naming044.z.y", n.holder().toString()); + assertEquals("m", n.invokedMethod().name.toSourceString()); + // Skip A#<init> + iterator.next(); + // Skip B#<init> + iterator.next(); + // B#f(A) -> y#p(x) + InvokeInstructionSubject f = iterator.next(); + DexType a1 = f.invokedMethod().proto.parameters.values[0]; + assertEquals("naming044.x", a1.toString()); + assertEquals("p", f.invokedMethod().name.toSourceString()); + // Skip AsubB#<init> + iterator.next(); + // AsubB#f(A) -> AsubB#p(x) + InvokeInstructionSubject overloaded_f = iterator.next(); + DexMethod aSubB_f = overloaded_f.invokedMethod(); + DexType a2 = aSubB_f.proto.parameters.values[0]; + assertEquals("naming044.x", a2.toString()); + assertEquals("p", aSubB_f.name.toSourceString()); + // B#f == AsubB#f + assertEquals(f.invokedMethod().name.toString(), aSubB_f.name.toString()); + } + + @Test + public void test_naming001_rule105() + throws IOException, CompilationException, ProguardRuleParserException, ExecutionException { + // keep rules to reserve D and E, along with a proguard map. + Path flag = Paths.get(ToolHelper.EXAMPLES_DIR, "naming001", "keep-rules-105.txt"); + Path proguardMap = out.resolve(MAPPING); + AndroidApp outputApp = runR8( + getCommandForApps(out, flag, NAMING001_JAR) + .addProguardConfigurationConsumer(c -> { + c.setPrintMapping(true); + c.setPrintMappingFile(proguardMap); + }) + .setMinification(false) + .build()); + + // Make sure the given proguard map is indeed applied. + DexInspector inspector = new DexInspector(outputApp); + MethodSubject main = inspector.clazz("naming001.D").method(DexInspector.MAIN); + Iterator<InvokeInstructionSubject> iterator = + main.iterateInstructions(InstructionSubject::isInvoke); + // mapping-105 simply includes: naming001.D#keep -> peek + // naming001.E extends D, hence its keep() should be renamed to peek as well. + // Skip E#<init> + iterator.next(); + // E#keep() should be replaced with peek by applying the map. + InvokeInstructionSubject m = iterator.next(); + assertEquals("peek", m.invokedMethod().name.toSourceString()); + // E could be renamed randomly, though. + assertNotEquals("naming001.E", m.holder().toString()); + } + + @Test + public void test_minification_conflict_mapping() + throws IOException, CompilationException, ExecutionException, ProguardRuleParserException { + Path flag = Paths.get( + ToolHelper.EXAMPLES_DIR, "minification", "keep-rules-apply-conflict-mapping.txt"); + try { + runR8(getCommandForApps(out, flag, MINIFICATION_JAR).build()); + fail("Expect to detect renaming conflict"); + } catch (ProguardMapError e) { + assertTrue(e.getMessage().contains("functionFromIntToInt")); + } + } + + private R8Command.Builder getCommandForInstrumentation( + Path out, Path flag, Path mainApp, Path instrApp) + throws CompilationException, IOException { + return R8Command.builder() + .addLibraryFiles(Paths.get(ToolHelper.getDefaultAndroidJar()), mainApp) + .addProgramFiles(instrApp) + .setOutputPath(out) + .addProguardConfigurationFiles(flag); + } + + private R8Command.Builder getCommandForApps( + Path out, Path flag, Path... jars) + throws CompilationException, IOException { + return R8Command.builder() + .addLibraryFiles(Paths.get(ToolHelper.getDefaultAndroidJar())) + .addProgramFiles(jars) + .setOutputPath(out) + .addProguardConfigurationFiles(flag); + } + + private static AndroidApp runR8(R8Command command) + throws ProguardRuleParserException, ExecutionException, CompilationException, IOException { + return ToolHelper.runR8(command, options -> { + // Disable inlining to make this test not depend on inlining decisions. + options.inlineAccessors = false; + }); + } +} diff --git a/src/test/java/com/android/tools/r8/naming/ProguardMapReaderTest.java b/src/test/java/com/android/tools/r8/naming/ProguardMapReaderTest.java index dccfd8a38..a818a58e6 100644 --- a/src/test/java/com/android/tools/r8/naming/ProguardMapReaderTest.java +++ b/src/test/java/com/android/tools/r8/naming/ProguardMapReaderTest.java @@ -19,19 +19,19 @@ public class ProguardMapReaderTest { @Test public void parseThrowingMap() throws IOException { - ProguardMapReader.mapperFromFile(Paths.get(ROOT, EXAMPLE_MAP)); + ClassNameMapper.mapperFromFile(Paths.get(ROOT, EXAMPLE_MAP)); } @Test public void roundTripTest() throws IOException { - ClassNameMapper firstMapper = ProguardMapReader.mapperFromFile(Paths.get(ROOT, EXAMPLE_MAP)); - ClassNameMapper secondMapper = ProguardMapReader.mapperFromString(firstMapper.toString()); + ClassNameMapper firstMapper = ClassNameMapper.mapperFromFile(Paths.get(ROOT, EXAMPLE_MAP)); + ClassNameMapper secondMapper = ClassNameMapper.mapperFromString(firstMapper.toString()); Assert.assertEquals(firstMapper, secondMapper); } @Test public void parseMapWithPackageInfo() throws IOException { - ClassNameMapper mapper = ProguardMapReader.mapperFromString(EXAMPLE_MAP_WITH_PACKAGE_INFO); + ClassNameMapper mapper = ClassNameMapper.mapperFromString(EXAMPLE_MAP_WITH_PACKAGE_INFO); Assert.assertTrue(mapper.getObfuscatedToOriginalMapping().isEmpty()); } } diff --git a/src/test/java/com/android/tools/r8/shaking/TreeShakingTest.java b/src/test/java/com/android/tools/r8/shaking/TreeShakingTest.java index b285ac060..c45ae376e 100644 --- a/src/test/java/com/android/tools/r8/shaking/TreeShakingTest.java +++ b/src/test/java/com/android/tools/r8/shaking/TreeShakingTest.java @@ -61,8 +61,11 @@ public class TreeShakingTest { private static final List<Path> JAR_LIBRARIES = ListUtils.map(ImmutableList .of(ANDROID_JAR, ToolHelper.EXAMPLES_BUILD_DIR + "shakinglib.jar"), Paths::get); private static final String EMPTY_FLAGS = "src/test/proguard/valid/empty.flags"; - - private static Set<String> IGNORED = ImmutableSet.of( + private static final Set<String> IGNORED_FLAGS = ImmutableSet.of( + "minification:conflict-mapping.txt", + "minification:keep-rules-apply-conflict-mapping.txt" + ); + private static final Set<String> IGNORED = ImmutableSet.of( // there's no point in running those without obfuscation "shaking1:keep-rules-repackaging.txt:DEX:false", "shaking1:keep-rules-repackaging.txt:JAR:false", @@ -844,7 +847,8 @@ public class TreeShakingTest { String mainClass, String keepName, List<String> keepList, boolean minify, Consumer<DexInspector> inspection, BiConsumer<String, String> outputComparator, BiConsumer<DexInspector, DexInspector> dexComparator) { - if (!IGNORED.contains(test + ":" + keepName + ":" + kind + ":" + minify)) { + if (!IGNORED_FLAGS.contains(test + ":" + keepName) + && !IGNORED.contains(test + ":" + keepName + ":" + kind + ":" + minify)) { testCases.add(new Object[]{ test, kind, mainClass, keepList, minify, inspection, outputComparator, dexComparator}); } diff --git a/src/test/java/com/android/tools/r8/utils/ArrayUtilsTest.java b/src/test/java/com/android/tools/r8/utils/ArrayUtilsTest.java new file mode 100644 index 000000000..c44d1cf9e --- /dev/null +++ b/src/test/java/com/android/tools/r8/utils/ArrayUtilsTest.java @@ -0,0 +1,102 @@ +// Copyright (c) 2017, 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.utils; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertTrue; + +import it.unimi.dsi.fastutil.ints.Int2IntArrayMap; +import java.util.Map; +import org.junit.Test; + +public class ArrayUtilsTest { + + @Test + public void testCopyWithSparseChanges_identical() throws Exception { + int size = 3; + Integer[] input = new Integer[size]; + for (int i = 0; i < size; i++) { + input[i] = i; + } + Integer[] output = + ArrayUtils.copyWithSparseChanges(Integer[].class, input, new Int2IntArrayMap()); + assertNotEquals(input, output); + for (int i = 0; i < size; i++) { + assertTrue(i == output[i]); + } + } + + @Test + public void testCopyWithSparseChanges_oneChangeAtBeginning() throws Exception { + int size = 3; + Integer[] input = new Integer[size]; + for (int i = 0; i < size; i++) { + input[i] = i; + } + Map<Integer, Integer> changes = new Int2IntArrayMap(); + changes.put(0, size); + Integer[] output = ArrayUtils.copyWithSparseChanges(Integer[].class, input, changes); + assertNotEquals(input, output); + assertTrue(size == output[0]); + for (int i = 1; i < size; i++) { + assertTrue(i == output[i]); + } + } + + @Test + public void testCopyWithSparseChanges_oneChangeInMiddle() throws Exception { + int size = 3; + Integer[] input = new Integer[size]; + for (int i = 0; i < size; i++) { + input[i] = i; + } + Map<Integer, Integer> changes = new Int2IntArrayMap(); + changes.put(1, size); + Integer[] output = ArrayUtils.copyWithSparseChanges(Integer[].class, input, changes); + assertNotEquals(input, output); + assertTrue(size == output[1]); + for (int i = 0; i < size; i++) { + if (i == 1) { + continue; + } + assertTrue(i == output[i]); + } + } + + @Test + public void testCopyWithSparseChanges_oneChangeAtEnd() throws Exception { + int size = 3; + Integer[] input = new Integer[size]; + for (int i = 0; i < size; i++) { + input[i] = i; + } + Map<Integer, Integer> changes = new Int2IntArrayMap(); + changes.put(2, size); + Integer[] output = ArrayUtils.copyWithSparseChanges(Integer[].class, input, changes); + assertNotEquals(input, output); + assertTrue(size == output[2]); + for (int i = 0; i < size - 1; i++) { + assertTrue(i == output[i]); + } + } + + @Test + public void testCopyWithSparseChanges_twoChangesAtEnds() throws Exception { + int size = 3; + Integer[] input = new Integer[size]; + for (int i = 0; i < size; i++) { + input[i] = i; + } + Map<Integer, Integer> changes = new Int2IntArrayMap(); + changes.put(0, size); + changes.put(2, size); + Integer[] output = ArrayUtils.copyWithSparseChanges(Integer[].class, input, changes); + assertNotEquals(input, output); + assertTrue(size == output[0]); + assertFalse(size == output[1]); + assertTrue(size == output[2]); + } + +} diff --git a/src/test/java/com/android/tools/r8/utils/DexInspector.java b/src/test/java/com/android/tools/r8/utils/DexInspector.java index 612043efe..36e496192 100644 --- a/src/test/java/com/android/tools/r8/utils/DexInspector.java +++ b/src/test/java/com/android/tools/r8/utils/DexInspector.java @@ -58,12 +58,11 @@ import com.android.tools.r8.graph.DexProto; import com.android.tools.r8.graph.DexType; import com.android.tools.r8.graph.DexValue; import com.android.tools.r8.naming.ClassNameMapper; -import com.android.tools.r8.naming.ClassNaming; +import com.android.tools.r8.naming.ClassNamingForNameMapper; import com.android.tools.r8.naming.MemberNaming; 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.naming.ProguardMapReader; import com.google.common.collect.BiMap; import com.google.common.collect.ImmutableList; import java.io.IOException; @@ -111,7 +110,7 @@ public class DexInspector { throws IOException, ExecutionException { ExecutorService executor = Executors.newSingleThreadExecutor(); if (mappingFile != null) { - this.mapping = ProguardMapReader.mapperFromFile(Paths.get(mappingFile)); + this.mapping = ClassNameMapper.mapperFromFile(Paths.get(mappingFile)); originalToObfuscatedMapping = this.mapping.getObfuscatedToOriginalMapping().inverse(); } else { this.mapping = null; @@ -166,7 +165,7 @@ public class DexInspector { } public ClassSubject clazz(String name) { - ClassNaming naming = null; + ClassNamingForNameMapper naming = null; if (mapping != null) { String obfuscated = originalToObfuscatedMapping.get(name); if (obfuscated != null) { @@ -183,7 +182,7 @@ public class DexInspector { public void forAllClasses(Consumer<FoundClassSubject> inspection) { forAll(application.classes(), clazz -> { - ClassNaming naming = null; + ClassNamingForNameMapper naming = null; if (mapping != null) { String obfuscated = originalToObfuscatedMapping.get(clazz.type.toSourceString()); if (obfuscated != null) { @@ -360,9 +359,9 @@ public class DexInspector { public class FoundClassSubject extends ClassSubject { private final DexClass dexClass; - private final ClassNaming naming; + private final ClassNamingForNameMapper naming; - private FoundClassSubject(DexClass dexClass, ClassNaming naming) { + private FoundClassSubject(DexClass dexClass, ClassNamingForNameMapper naming) { this.dexClass = dexClass; this.naming = naming; } |