diff options
author | Søren Gjesse <sgjesse@google.com> | 2017-10-27 12:47:14 +0200 |
---|---|---|
committer | Søren Gjesse <sgjesse@google.com> | 2017-10-27 11:06:33 +0000 |
commit | e7caa2eaf6d67802151310e90fa069dd44206e04 (patch) | |
tree | 979b2034105bf4824a629c15704d04834f4260fa | |
parent | f0b4740f2f7296d09e8b73d841ced21a27b8e7c6 (diff) | |
parent | 2bfb29a43eba84ad3345b17f33960dc72bb97564 (diff) | |
download | r8-sdk-release.tar.gz |
Update external/r8 to 2bfb29asdk-release
Merge remote-tracking branch 'aosp/upstream-mirror' into merge-r8-2bfb29a
Test: m -j USE_D8=true USE_R8=true tests
Change-Id: I249f979f3e349859607d3d67930d01e7cb82b901
51 files changed, 1842 insertions, 284 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/FieldNameMinifier.java b/src/main/java/com/android/tools/r8/naming/FieldNameMinifier.java index 7cbdf9b74..a4f49122e 100644 --- a/src/main/java/com/android/tools/r8/naming/FieldNameMinifier.java +++ b/src/main/java/com/android/tools/r8/naming/FieldNameMinifier.java @@ -9,10 +9,12 @@ import com.android.tools.r8.graph.DexEncodedField; import com.android.tools.r8.graph.DexField; import com.android.tools.r8.graph.DexString; import com.android.tools.r8.graph.DexType; +import com.android.tools.r8.shaking.ProguardConfiguration; import com.android.tools.r8.shaking.RootSetBuilder.RootSet; import com.android.tools.r8.utils.InternalOptions; import com.android.tools.r8.utils.Timing; import java.util.Map; +import java.util.function.Function; class FieldNameMinifier extends MemberNameMinifier<DexField, DexType> { @@ -20,6 +22,17 @@ class FieldNameMinifier extends MemberNameMinifier<DexField, DexType> { super(appInfo, rootSet, options); } + @Override + Function<DexType, ?> getKeyTransform(ProguardConfiguration config) { + if (config.isOverloadAggressively()) { + // Use the type as the key, hence reuse names per type. + return a -> a; + } else { + // Always use the same key, hence do not reuse names per type. + return a -> Void.class; + } + } + Map<DexField, DexString> computeRenaming(Timing timing) { // Reserve names in all classes first. We do this in subtyping order so we do not // shadow a reserved field in subclasses. While there is no concept of virtual field @@ -41,19 +54,19 @@ class FieldNameMinifier extends MemberNameMinifier<DexField, DexType> { return renaming; } - private void reserveNamesInSubtypes(DexType type, NamingState<DexType> state) { + private void reserveNamesInSubtypes(DexType type, NamingState<DexType, ?> state) { DexClass holder = appInfo.definitionFor(type); if (holder == null) { return; } - NamingState<DexType> newState = computeStateIfAbsent(type, t -> state.createChild()); + NamingState<DexType, ?> newState = computeStateIfAbsent(type, t -> state.createChild()); holder.forEachField(field -> reserveFieldName(field, newState, holder.isLibraryClass())); type.forAllExtendsSubtypes(subtype -> reserveNamesInSubtypes(subtype, newState)); } private void reserveFieldName( DexEncodedField encodedField, - NamingState<DexType> state, + NamingState<DexType, ?> state, boolean isLibrary) { if (isLibrary || rootSet.noObfuscation.contains(encodedField)) { DexField field = encodedField.field; @@ -66,13 +79,13 @@ class FieldNameMinifier extends MemberNameMinifier<DexField, DexType> { if (clazz == null) { return; } - NamingState<DexType> state = getState(clazz.type); + NamingState<DexType, ?> state = getState(clazz.type); assert state != null; clazz.forEachField(field -> renameField(field, state)); type.forAllExtendsSubtypes(this::renameFieldsInSubtypes); } - private void renameField(DexEncodedField encodedField, NamingState<DexType> state) { + private void renameField(DexEncodedField encodedField, NamingState<DexType, ?> state) { DexField field = encodedField.field; if (!state.isReserved(field.name, field.type)) { renaming.put(field, state.assignNewNameFor(field.name, field.type, false)); diff --git a/src/main/java/com/android/tools/r8/naming/MemberNameMinifier.java b/src/main/java/com/android/tools/r8/naming/MemberNameMinifier.java index 37cdb34de..4a288bec3 100644 --- a/src/main/java/com/android/tools/r8/naming/MemberNameMinifier.java +++ b/src/main/java/com/android/tools/r8/naming/MemberNameMinifier.java @@ -7,6 +7,7 @@ import com.android.tools.r8.graph.AppInfoWithSubtyping; import com.android.tools.r8.graph.CachedHashValueDexItem; import com.android.tools.r8.graph.DexString; import com.android.tools.r8.graph.DexType; +import com.android.tools.r8.shaking.ProguardConfiguration; import com.android.tools.r8.shaking.RootSetBuilder.RootSet; import com.android.tools.r8.utils.InternalOptions; import com.google.common.collect.ImmutableList; @@ -21,25 +22,27 @@ abstract class MemberNameMinifier<MemberType, StateType extends CachedHashValueD protected final ImmutableList<String> dictionary; protected final Map<MemberType, DexString> renaming = new IdentityHashMap<>(); - protected final Map<DexType, NamingState<StateType>> states = new IdentityHashMap<>(); - protected final NamingState<StateType> globalState; + protected final Map<DexType, NamingState<StateType, ?>> states = new IdentityHashMap<>(); + protected final NamingState<StateType, ?> globalState; protected final boolean useUniqueMemberNames; MemberNameMinifier(AppInfoWithSubtyping appInfo, RootSet rootSet, InternalOptions options) { this.appInfo = appInfo; this.rootSet = rootSet; this.dictionary = options.proguardConfiguration.getObfuscationDictionary(); - - this.globalState = NamingState.createRoot(appInfo.dexItemFactory, dictionary); + this.globalState = NamingState.createRoot(appInfo.dexItemFactory, dictionary, + getKeyTransform(options.proguardConfiguration)); this.useUniqueMemberNames = options.proguardConfiguration.isUseUniqueClassMemberNames(); } - protected NamingState<StateType> computeStateIfAbsent( - DexType type, Function<DexType, NamingState<StateType>> f) { + abstract Function<StateType, ?> getKeyTransform(ProguardConfiguration config); + + protected NamingState<StateType, ?> computeStateIfAbsent( + DexType type, Function<DexType, NamingState<StateType, ?>> f) { return useUniqueMemberNames ? globalState : states.computeIfAbsent(type, f); } - protected NamingState<StateType> getState(DexType type) { + protected NamingState<StateType, ?> getState(DexType type) { return useUniqueMemberNames ? globalState : states.get(type); } } 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/MethodNameMinifier.java b/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java index a6499842a..7b939ae20 100644 --- a/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java +++ b/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java @@ -3,10 +3,6 @@ // BSD-style license that can be found in the LICENSE file. package com.android.tools.r8.naming; -import com.android.tools.r8.utils.InternalOptions; -import com.google.common.base.Equivalence.Wrapper; -import com.google.common.collect.Sets; - import com.android.tools.r8.graph.AppInfoWithSubtyping; import com.android.tools.r8.graph.DexClass; import com.android.tools.r8.graph.DexEncodedMethod; @@ -14,10 +10,15 @@ import com.android.tools.r8.graph.DexMethod; import com.android.tools.r8.graph.DexProto; import com.android.tools.r8.graph.DexString; import com.android.tools.r8.graph.DexType; +import com.android.tools.r8.shaking.ProguardConfiguration; import com.android.tools.r8.shaking.RootSetBuilder.RootSet; +import com.android.tools.r8.utils.InternalOptions; +import com.android.tools.r8.utils.MethodJavaSignatureEquivalence; import com.android.tools.r8.utils.MethodSignatureEquivalence; import com.android.tools.r8.utils.Timing; - +import com.google.common.base.Equivalence; +import com.google.common.base.Equivalence.Wrapper; +import com.google.common.collect.Sets; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; @@ -25,6 +26,7 @@ import java.util.IdentityHashMap; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.function.Function; /** * A pass to rename methods using common, short names. @@ -87,10 +89,26 @@ import java.util.Set; */ class MethodNameMinifier extends MemberNameMinifier<DexMethod, DexProto> { - private MethodSignatureEquivalence equivalence = MethodSignatureEquivalence.get(); + private final Equivalence<DexMethod> equivalence; + private final ProguardConfiguration config; MethodNameMinifier(AppInfoWithSubtyping appInfo, RootSet rootSet, InternalOptions options) { super(appInfo, rootSet, options); + this.config = options.proguardConfiguration; + equivalence = config.isOverloadAggressively() + ? MethodSignatureEquivalence.get() + : MethodJavaSignatureEquivalence.get(); + } + + @Override + Function<DexProto, ?> getKeyTransform(ProguardConfiguration config) { + if (config.isOverloadAggressively()) { + // Use the full proto as key, hence reuse names based on full signature. + return a -> a; + } else { + // Only use the parameters as key, hence do not reuse names on return type. + return proto -> proto.parameters; + } } Map<DexMethod, DexString> computeRenaming(Timing timing) { @@ -128,7 +146,7 @@ class MethodNameMinifier extends MemberNameMinifier<DexMethod, DexProto> { private void assignNamesToClassesMethods(DexType type, boolean doPrivates) { DexClass holder = appInfo.definitionFor(type); if (holder != null && !holder.isLibraryClass()) { - NamingState<DexProto> state = + NamingState<DexProto, ?> state = computeStateIfAbsent(type, k -> getState(holder.superType).createChild()); holder.forEachMethod(method -> assignNameToMethod(method, state, doPrivates)); } @@ -136,7 +154,7 @@ class MethodNameMinifier extends MemberNameMinifier<DexMethod, DexProto> { } private void assignNameToMethod( - DexEncodedMethod encodedMethod, NamingState<DexProto> state, boolean doPrivates) { + DexEncodedMethod encodedMethod, NamingState<DexProto, ?> state, boolean doPrivates) { if (encodedMethod.accessFlags.isPrivate() != doPrivates) { return; } @@ -147,19 +165,19 @@ class MethodNameMinifier extends MemberNameMinifier<DexMethod, DexProto> { } } - private Set<NamingState<DexProto>> getReachableStates(DexType type, + private Set<NamingState<DexProto, ?>> getReachableStates(DexType type, Map<DexType, DexType> frontierMap) { Set<DexType> interfaces = Sets.newIdentityHashSet(); interfaces.add(type); collectSuperInterfaces(type, interfaces); collectSubInterfaces(type, interfaces); - Set<NamingState<DexProto>> reachableStates = new HashSet<>(); + Set<NamingState<DexProto, ?>> reachableStates = new HashSet<>(); for (DexType iface : interfaces) { // Add the interface itself reachableStates.add(getState(iface)); // And the frontiers that correspond to the classes that implement the interface. iface.forAllImplementsSubtypes(t -> { - NamingState<DexProto> state = getState(frontierMap.get(t)); + NamingState<DexProto, ?> state = getState(frontierMap.get(t)); assert state != null; reachableStates.add(state); }); @@ -173,16 +191,16 @@ class MethodNameMinifier extends MemberNameMinifier<DexMethod, DexProto> { // reserve the names for later method naming. timing.begin("Compute map"); // A map from DexMethods to all the states linked to interfaces they appear in. - Map<Wrapper<DexMethod>, Set<NamingState<DexProto>>> globalStateMap = new HashMap<>(); + Map<Wrapper<DexMethod>, Set<NamingState<DexProto, ?>>> globalStateMap = new HashMap<>(); // A map from DexMethods to all the definitions seen. Needed as the Wrapper equalizes them all. Map<Wrapper<DexMethod>, Set<DexMethod>> sourceMethodsMap = new HashMap<>(); // A map from DexMethods to the first interface state it was seen in. Used to pick good names. - Map<Wrapper<DexMethod>, NamingState<DexProto>> originStates = new HashMap<>(); + Map<Wrapper<DexMethod>, NamingState<DexProto, ?>> originStates = new HashMap<>(); DexType.forAllInterfaces(appInfo.dexItemFactory, iface -> { assert iface.isInterface(); DexClass clazz = appInfo.definitionFor(iface); if (clazz != null) { - Set<NamingState<DexProto>> collectedStates = getReachableStates(iface, frontierMap); + Set<NamingState<DexProto, ?>> collectedStates = getReachableStates(iface, frontierMap); clazz.forEachMethod(method -> addStatesToGlobalMapForMethod( method, collectedStates, globalStateMap, sourceMethodsMap, originStates, iface)); } @@ -228,12 +246,12 @@ class MethodNameMinifier extends MemberNameMinifier<DexMethod, DexProto> { } private void addStatesToGlobalMapForMethod( - DexEncodedMethod method, Set<NamingState<DexProto>> collectedStates, - Map<Wrapper<DexMethod>, Set<NamingState<DexProto>>> globalStateMap, + DexEncodedMethod method, Set<NamingState<DexProto, ?>> collectedStates, + Map<Wrapper<DexMethod>, Set<NamingState<DexProto, ?>>> globalStateMap, Map<Wrapper<DexMethod>, Set<DexMethod>> sourceMethodsMap, - Map<Wrapper<DexMethod>, NamingState<DexProto>> originStates, DexType originInterface) { + Map<Wrapper<DexMethod>, NamingState<DexProto, ?>> originStates, DexType originInterface) { Wrapper<DexMethod> key = equivalence.wrap(method.method); - Set<NamingState<DexProto>> stateSet = + Set<NamingState<DexProto, ?>> stateSet = globalStateMap.computeIfAbsent(key, k -> new HashSet<>()); stateSet.addAll(collectedStates); sourceMethodsMap.computeIfAbsent(key, k -> new HashSet<>()).add(method.method); @@ -242,12 +260,12 @@ class MethodNameMinifier extends MemberNameMinifier<DexMethod, DexProto> { private void assignNameForInterfaceMethodInAllStates( DexMethod method, - Set<NamingState<DexProto>> collectedStates, + Set<NamingState<DexProto, ?>> collectedStates, Set<DexMethod> sourceMethods, - NamingState<DexProto> originState) { + NamingState<DexProto, ?> originState) { boolean isReserved = false; if (globalState.isReserved(method.name, method.proto)) { - for (NamingState<DexProto> state : collectedStates) { + for (NamingState<DexProto, ?> state : collectedStates) { if (state.isReserved(method.name, method.proto)) { isReserved = true; break; @@ -255,7 +273,7 @@ class MethodNameMinifier extends MemberNameMinifier<DexMethod, DexProto> { } if (isReserved) { // This method's name is reserved in at least on naming state, so reserve it everywhere. - for (NamingState<DexProto> state : collectedStates) { + for (NamingState<DexProto, ?> state : collectedStates) { state.reserveName(method.name, method.proto); } return; @@ -267,14 +285,14 @@ class MethodNameMinifier extends MemberNameMinifier<DexMethod, DexProto> { DexString candidate = null; do { candidate = originState.assignNewNameFor(method.name, method.proto, false); - for (NamingState<DexProto> state : collectedStates) { + for (NamingState<DexProto, ?> state : collectedStates) { if (!state.isAvailable(method.name, method.proto, candidate)) { candidate = null; break; } } } while (candidate == null); - for (NamingState<DexProto> state : collectedStates) { + for (NamingState<DexProto, ?> state : collectedStates) { state.addRenaming(method.name, method.proto, candidate); } // Rename all methods in interfaces that gave rise to this renaming. @@ -284,11 +302,11 @@ class MethodNameMinifier extends MemberNameMinifier<DexMethod, DexProto> { } private void reserveNamesInClasses(DexType type, DexType libraryFrontier, - NamingState<DexProto> parent, + NamingState<DexProto, ?> parent, Map<DexType, DexType> frontierMap) { assert !type.isInterface(); DexClass holder = appInfo.definitionFor(type); - NamingState<DexProto> state = allocateNamingStateAndReserve(holder, type, libraryFrontier, + NamingState<DexProto, ?> state = allocateNamingStateAndReserve(holder, type, libraryFrontier, parent, frontierMap); // If this is a library class (or effectively a library class as it is missing) move the // frontier forward. @@ -307,16 +325,17 @@ class MethodNameMinifier extends MemberNameMinifier<DexMethod, DexProto> { allocateNamingStateAndReserve(holder, type, type, null, frontierMap); } - private NamingState<DexProto> allocateNamingStateAndReserve(DexClass holder, DexType type, + private NamingState<DexProto, ?> allocateNamingStateAndReserve(DexClass holder, DexType type, DexType libraryFrontier, - NamingState<DexProto> parent, + NamingState<DexProto, ?> parent, Map<DexType, DexType> frontierMap) { frontierMap.put(type, libraryFrontier); - NamingState<DexProto> state = + NamingState<DexProto, ?> state = computeStateIfAbsent( libraryFrontier, ignore -> parent == null - ? NamingState.createRoot(appInfo.dexItemFactory, dictionary) + ? NamingState + .createRoot(appInfo.dexItemFactory, dictionary, getKeyTransform(config)) : parent.createChild()); if (holder != null) { boolean keepAll = holder.isLibraryClass() || holder.accessFlags.isAnnotation(); @@ -326,7 +345,7 @@ class MethodNameMinifier extends MemberNameMinifier<DexMethod, DexProto> { } private void reserveNamesForMethod(DexEncodedMethod method, - boolean keepAll, NamingState<DexProto> state) { + boolean keepAll, NamingState<DexProto, ?> state) { if (keepAll || rootSet.noObfuscation.contains(method)) { state.reserveName(method.method.name, method.method.proto); globalState.reserveName(method.method.name, method.method.proto); diff --git a/src/main/java/com/android/tools/r8/naming/NamingState.java b/src/main/java/com/android/tools/r8/naming/NamingState.java index 2011530eb..874d506d9 100644 --- a/src/main/java/com/android/tools/r8/naming/NamingState.java +++ b/src/main/java/com/android/tools/r8/naming/NamingState.java @@ -10,48 +10,54 @@ import com.android.tools.r8.utils.StringUtils; import com.google.common.collect.HashBiMap; import com.google.common.collect.ImmutableList; import com.google.common.collect.Sets; -import java.util.IdentityHashMap; +import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.function.Function; -class NamingState<T extends CachedHashValueDexItem> { +class NamingState<ProtoType extends CachedHashValueDexItem, KeyType> { - private final NamingState<T> parent; - private final Map<T, InternalState> usedNames = new IdentityHashMap<>(); + private final NamingState<ProtoType, KeyType> parent; + private final Map<KeyType, InternalState> usedNames = new HashMap<>(); private final DexItemFactory itemFactory; private final ImmutableList<String> dictionary; + private final Function<ProtoType, KeyType> keyTransform; - static <T extends CachedHashValueDexItem> NamingState<T> createRoot( - DexItemFactory itemFactory, ImmutableList<String> dictionary) { - return new NamingState<>(null, itemFactory, dictionary); + static <S, T extends CachedHashValueDexItem> NamingState<T, S> createRoot( + DexItemFactory itemFactory, ImmutableList<String> dictionary, Function<T, S> keyTransform) { + return new NamingState<>(null, itemFactory, dictionary, keyTransform); } private NamingState( - NamingState<T> parent, + NamingState<ProtoType, KeyType> parent, DexItemFactory itemFactory, - ImmutableList<String> dictionary) { + ImmutableList<String> dictionary, + Function<ProtoType, KeyType> keyTransform) { this.parent = parent; this.itemFactory = itemFactory; this.dictionary = dictionary; + this.keyTransform = keyTransform; } - public NamingState<T> createChild() { - return new NamingState<>(this, itemFactory, dictionary); + public NamingState<ProtoType, KeyType> createChild() { + return new NamingState<>(this, itemFactory, dictionary, keyTransform); } - private InternalState findInternalStateFor(T proto) { - InternalState result = usedNames.get(proto); + private InternalState findInternalStateFor(ProtoType proto) { + KeyType key = keyTransform.apply(proto); + InternalState result = usedNames.get(key); if (result == null && parent != null) { result = parent.findInternalStateFor(proto); } return result; } - private InternalState getOrCreateInternalStateFor(T proto) { + private InternalState getOrCreateInternalStateFor(ProtoType proto) { // TODO(herhut): Maybe allocate these sparsely and search via state chain. - InternalState result = usedNames.get(proto); + KeyType key = keyTransform.apply(proto); + InternalState result = usedNames.get(key); if (result == null) { if (parent != null) { InternalState parentState = parent.getOrCreateInternalStateFor(proto); @@ -59,12 +65,12 @@ class NamingState<T extends CachedHashValueDexItem> { } else { result = new InternalState(itemFactory, null, dictionary); } - usedNames.put(proto, result); + usedNames.put(key, result); } return result; } - public DexString getAssignedNameFor(DexString name, T proto) { + public DexString getAssignedNameFor(DexString name, ProtoType proto) { InternalState state = findInternalStateFor(proto); if (state == null) { return null; @@ -72,7 +78,7 @@ class NamingState<T extends CachedHashValueDexItem> { return state.getAssignedNameFor(name); } - public DexString assignNewNameFor(DexString original, T proto, boolean markAsUsed) { + public DexString assignNewNameFor(DexString original, ProtoType proto, boolean markAsUsed) { DexString result = getAssignedNameFor(original, proto); if (result == null) { InternalState state = getOrCreateInternalStateFor(proto); @@ -81,12 +87,12 @@ class NamingState<T extends CachedHashValueDexItem> { return result; } - public void reserveName(DexString name, T proto) { + public void reserveName(DexString name, ProtoType proto) { InternalState state = getOrCreateInternalStateFor(proto); state.reserveName(name); } - public boolean isReserved(DexString name, T proto) { + public boolean isReserved(DexString name, ProtoType proto) { InternalState state = findInternalStateFor(proto); if (state == null) { return false; @@ -94,7 +100,7 @@ class NamingState<T extends CachedHashValueDexItem> { return state.isReserved(name); } - public boolean isAvailable(DexString original, T proto, DexString candidate) { + public boolean isAvailable(DexString original, ProtoType proto, DexString candidate) { InternalState state = findInternalStateFor(proto); if (state == null) { return true; @@ -103,7 +109,7 @@ class NamingState<T extends CachedHashValueDexItem> { return state.isAvailable(candidate); } - public void addRenaming(DexString original, T proto, DexString newName) { + public void addRenaming(DexString original, ProtoType proto, DexString newName) { InternalState state = getOrCreateInternalStateFor(proto); state.addRenaming(original, newName); } 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/ProguardConfiguration.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java index b3cfcb2aa..bdf77489e 100644 --- a/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java +++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java @@ -45,6 +45,7 @@ public class ProguardConfiguration { private boolean keepParameterNames; private ProguardClassFilter.Builder adaptClassStrings = ProguardClassFilter.builder(); private boolean forceProguardCompatibility = false; + private boolean overloadAggressively; private Builder(DexItemFactory dexItemFactory) { this.dexItemFactory = dexItemFactory; @@ -211,10 +212,13 @@ public class ProguardConfiguration { this.forceProguardCompatibility = forceProguardCompatibility; } + public void setOverloadAggressively(boolean overloadAggressively) { + this.overloadAggressively = overloadAggressively; + } + public ProguardConfiguration build() throws CompilationException { ProguardKeepAttributes keepAttributes; - if (forceProguardCompatibility && !isObfuscating() && keepAttributePatterns.size() == 0) { @@ -246,6 +250,7 @@ public class ProguardConfiguration { rules, printSeeds, seedFile, + overloadAggressively, DictionaryReader.readAllNames(obfuscationDictionary), DictionaryReader.readAllNames(classObfuscationDictionary), DictionaryReader.readAllNames(packageObfuscationDictionary), @@ -277,6 +282,7 @@ public class ProguardConfiguration { protected final ImmutableList<ProguardConfigurationRule> rules; private final boolean printSeeds; private final Path seedFile; + private final boolean overloadAggressively; private final ImmutableList<String> obfuscationDictionary; private final ImmutableList<String> classObfuscationDictionary; private final ImmutableList<String> packageObfuscationDictionary; @@ -307,6 +313,7 @@ public class ProguardConfiguration { List<ProguardConfigurationRule> rules, boolean printSeeds, Path seedFile, + boolean overloadAggressively, ImmutableList<String> obfuscationDictionary, ImmutableList<String> classObfuscationDictionary, ImmutableList<String> packageObfuscationDictionary, @@ -335,6 +342,7 @@ public class ProguardConfiguration { this.rules = ImmutableList.copyOf(rules); this.printSeeds = printSeeds; this.seedFile = seedFile; + this.overloadAggressively = overloadAggressively; this.obfuscationDictionary = obfuscationDictionary; this.classObfuscationDictionary = classObfuscationDictionary; this.packageObfuscationDictionary = packageObfuscationDictionary; @@ -443,6 +451,10 @@ public class ProguardConfiguration { return rules; } + public boolean isOverloadAggressively() { + return overloadAggressively; + } + public ImmutableList<String> getObfuscationDictionary() { return obfuscationDictionary; } @@ -470,7 +482,7 @@ public class ProguardConfiguration { public static ProguardConfiguration defaultConfiguration(DexItemFactory dexItemFactory) { try { return builderInitializedWithDefaults(dexItemFactory).build(); - } catch(CompilationException e) { + } catch (CompilationException e) { // Building a builder initialized with defaults will not throw CompilationException because // DictionaryReader is called with empty lists. throw new RuntimeException(); 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..3f64afdaf 100644 --- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java +++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java @@ -51,7 +51,6 @@ public class ProguardConfigurationParser { "filterlibraryjarswithorginalprogramjars", "dontskipnonpubliclibraryclasses", "dontskipnonpubliclibraryclassmembers", - "overloadaggressively", "invokebasemethod"); private static final List<String> IGNORED_CLASS_DESCRIPTOR_OPTIONS = ImmutableList .of("isclassnamestring", @@ -245,6 +244,8 @@ public class ProguardConfigurationParser { configurationBuilder.setFlattenPackagePrefix(""); } } + } else if (acceptString("overloadaggressively")) { + configurationBuilder.setOverloadAggressively(true); } else if (acceptString("allowaccessmodification")) { configurationBuilder.setAllowAccessModification(true); } else if (acceptString("printmapping")) { @@ -255,8 +256,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/examples/uniquemembernames/BaseCls.java b/src/test/examples/uniquemembernames/BaseCls.java index 9843c7b48..2ee24fee1 100644 --- a/src/test/examples/uniquemembernames/BaseCls.java +++ b/src/test/examples/uniquemembernames/BaseCls.java @@ -5,17 +5,17 @@ package uniquemembernames; public abstract class BaseCls { - protected int a; + protected int c; protected double f2; - protected abstract int a(); + protected abstract int c(); protected int foo() { - return a * (int) f2; + return c * (int) f2; } protected double bar() { - return a * f2; + return c * f2; } } diff --git a/src/test/examples/uniquemembernames/ClsA.java b/src/test/examples/uniquemembernames/ClsA.java index 430a255cc..563a070c4 100644 --- a/src/test/examples/uniquemembernames/ClsA.java +++ b/src/test/examples/uniquemembernames/ClsA.java @@ -6,8 +6,8 @@ package uniquemembernames; public class ClsA extends BaseCls { @Override - protected int a() { - return foo() + a; + protected int c() { + return foo() + c; } @Override diff --git a/src/test/examples/uniquemembernames/ClsB.java b/src/test/examples/uniquemembernames/ClsB.java index 2659d5a0c..f9d4ecd4d 100644 --- a/src/test/examples/uniquemembernames/ClsB.java +++ b/src/test/examples/uniquemembernames/ClsB.java @@ -6,13 +6,13 @@ package uniquemembernames; public class ClsB extends BaseCls { @Override - protected int a() { - return foo() - a; + protected int c() { + return foo() - c; } @Override protected double bar() { - return f2 != 0 ? a / f2 : Double.MAX_VALUE; + return f2 != 0 ? c / f2 : Double.MAX_VALUE; } } diff --git a/src/test/examples/uniquemembernames/Shaking.java b/src/test/examples/uniquemembernames/Shaking.java index ce7ed5dc6..a1b90bae6 100644 --- a/src/test/examples/uniquemembernames/Shaking.java +++ b/src/test/examples/uniquemembernames/Shaking.java @@ -11,7 +11,7 @@ public class Shaking { public static void main(String[] args) { List<BaseCls> bases = Arrays.asList(new ClsA(), new ClsB()); for (BaseCls base : bases) { - base.a(); + base.c(); base.foo(); base.bar(); } diff --git a/src/test/examples/uniquemembernames/keep-rules-1.txt b/src/test/examples/uniquemembernames/keep-rules-1.txt index 2acd66a93..40057e457 100644 --- a/src/test/examples/uniquemembernames/keep-rules-1.txt +++ b/src/test/examples/uniquemembernames/keep-rules-1.txt @@ -7,8 +7,8 @@ # Keep test fields/methods for deterministic naming -keepclassmembers public class **.BaseCls { - *** a; - *** a(...); + *** c; + *** c(...); } -keepclassmembers public class **.AnotherCls { diff --git a/src/test/examples/uniquemembernames/keep-rules-2.txt b/src/test/examples/uniquemembernames/keep-rules-2.txt index 3f1bdadcc..b5c5dcf76 100644 --- a/src/test/examples/uniquemembernames/keep-rules-2.txt +++ b/src/test/examples/uniquemembernames/keep-rules-2.txt @@ -7,8 +7,8 @@ # Keep test fields/methods for deterministic naming -keepclassmembers public class **.BaseCls { - *** a; - *** a(...); + *** c; + *** c(...); } -keepclassmembers public class **.AnotherCls { 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/naming/UseUniqueMemberNameTest.java b/src/test/java/com/android/tools/r8/naming/UseUniqueMemberNameTest.java index 309475694..498f4dfec 100644 --- a/src/test/java/com/android/tools/r8/naming/UseUniqueMemberNameTest.java +++ b/src/test/java/com/android/tools/r8/naming/UseUniqueMemberNameTest.java @@ -103,9 +103,9 @@ public class UseUniqueMemberNameTest extends NamingTestBase { // With -useuniquemembernames, foo() with the same signature should be renamed to the same name. assertEquals(foo_renamed, another_foo_renamed); - // But, those cannot be renamed to a and b, as those are _globally_ reserved. - assertNotEquals("a", foo_renamed); - assertNotEquals("a", another_foo_renamed); + // But, those cannot be renamed to c and b, as those are _globally_ reserved. + assertNotEquals("c", foo_renamed); + assertNotEquals("c", another_foo_renamed); assertNotEquals("b", foo_renamed); assertNotEquals("b", another_foo_renamed); 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; } |