diff options
92 files changed, 2164 insertions, 621 deletions
diff --git a/build.gradle b/build.gradle index 9299592c7..c97aa9dfb 100644 --- a/build.gradle +++ b/build.gradle @@ -23,6 +23,7 @@ def errorProneConfiguration = [ '-Xep:OvershadowingSubclassFields:WARN', '-Xep:IntLongMath:WARN', '-Xep:EqualsHashCode:WARN', + '-Xep:InconsistentOverloads:WARN', '-Xep:ArrayHashCode:WARN', '-Xep:EqualsIncompatibleType:WARN', '-Xep:NonOverridingEquals:WARN', diff --git a/src/main/java/com/android/tools/r8/BaseCompilerCommand.java b/src/main/java/com/android/tools/r8/BaseCompilerCommand.java index 6c4b9bcd0..6961024fd 100644 --- a/src/main/java/com/android/tools/r8/BaseCompilerCommand.java +++ b/src/main/java/com/android/tools/r8/BaseCompilerCommand.java @@ -109,19 +109,19 @@ abstract class BaseCompilerCommand extends BaseCommand { private boolean enableDesugaring = true; protected Builder(CompilationMode mode) { - this(AndroidApp.builder(), mode, false); + this(mode, false, AndroidApp.builder()); } protected Builder(CompilationMode mode, boolean ignoreDexInArchive) { - this(AndroidApp.builder(), mode, ignoreDexInArchive); + this(mode, ignoreDexInArchive, AndroidApp.builder()); } // Internal constructor for testing. - Builder(AndroidApp app, CompilationMode mode) { - this(AndroidApp.builder(app), mode, false); + Builder(CompilationMode mode, AndroidApp app) { + this(mode, false, AndroidApp.builder(app)); } - private Builder(AndroidApp.Builder builder, CompilationMode mode, boolean ignoreDexInArchive) { + private Builder(CompilationMode mode, boolean ignoreDexInArchive, AndroidApp.Builder builder) { super(builder, ignoreDexInArchive); assert mode != null; this.mode = mode; diff --git a/src/main/java/com/android/tools/r8/D8.java b/src/main/java/com/android/tools/r8/D8.java index a5213c5b7..ae370ae38 100644 --- a/src/main/java/com/android/tools/r8/D8.java +++ b/src/main/java/com/android/tools/r8/D8.java @@ -199,7 +199,7 @@ public final class D8 { throws IOException, ExecutionException, ApiLevelException { final CfgPrinter printer = options.printCfg ? new CfgPrinter() : null; - IRConverter converter = new IRConverter(timing, appInfo, options, printer); + IRConverter converter = new IRConverter(appInfo, options, timing, printer); application = converter.convertToDex(application, executor); if (options.printCfg) { diff --git a/src/main/java/com/android/tools/r8/D8Command.java b/src/main/java/com/android/tools/r8/D8Command.java index b96dfb73d..d0c99d503 100644 --- a/src/main/java/com/android/tools/r8/D8Command.java +++ b/src/main/java/com/android/tools/r8/D8Command.java @@ -44,7 +44,7 @@ public class D8Command extends BaseCompilerCommand { } private Builder(AndroidApp app) { - super(app, CompilationMode.DEBUG); + super(CompilationMode.DEBUG, app); } /** Add classpath file resources. */ diff --git a/src/main/java/com/android/tools/r8/GenerateMainDexListCommand.java b/src/main/java/com/android/tools/r8/GenerateMainDexListCommand.java index 9d7478d59..d29d9ae19 100644 --- a/src/main/java/com/android/tools/r8/GenerateMainDexListCommand.java +++ b/src/main/java/com/android/tools/r8/GenerateMainDexListCommand.java @@ -68,7 +68,7 @@ public class GenerateMainDexListCommand extends BaseCommand { * Add proguard configuration for automatic main dex list calculation. */ public GenerateMainDexListCommand.Builder addMainDexRules(List<String> lines) { - mainDexRules.add(new ProguardConfigurationSourceStrings(Paths.get("."), lines)); + mainDexRules.add(new ProguardConfigurationSourceStrings(lines, Paths.get("."))); return self(); } diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java index d9066bdf1..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; @@ -129,7 +131,7 @@ public class R8 { timing.begin("Create IR"); try { IRConverter converter = new IRConverter( - timing, appInfo, options, printer, graphLense); + appInfo, options, timing, printer, graphLense); application = converter.optimize(application, executorService); } finally { timing.end(); @@ -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 1df56dbe7..ad23c038b 100644 --- a/src/main/java/com/android/tools/r8/R8Command.java +++ b/src/main/java/com/android/tools/r8/R8Command.java @@ -35,6 +35,7 @@ public class R8Command extends BaseCompilerCommand { private Optional<Boolean> treeShaking = Optional.empty(); private Optional<Boolean> discardedChecker = Optional.empty(); private Optional<Boolean> minification = Optional.empty(); + private boolean ignoreMissingClassesWhenNotShrinking = false; private boolean ignoreMissingClasses = false; private boolean forceProguardCompatibility = false; private Path proguardMapOutput = null; @@ -43,13 +44,16 @@ public class R8Command extends BaseCompilerCommand { super(CompilationMode.RELEASE); } - protected Builder(boolean ignoreDexInArchive, boolean forceProguardCompatibility) { + protected Builder(boolean ignoreDexInArchive, boolean forceProguardCompatibility, + boolean ignoreMissingClassesWhenNotShrinking, boolean ignoreMissingClasses) { super(CompilationMode.RELEASE, ignoreDexInArchive); this.forceProguardCompatibility = forceProguardCompatibility; + this.ignoreMissingClassesWhenNotShrinking = ignoreMissingClassesWhenNotShrinking; + this.ignoreMissingClasses = ignoreMissingClasses; } private Builder(AndroidApp app) { - super(app, CompilationMode.RELEASE); + super(CompilationMode.RELEASE, app); } @Override @@ -105,7 +109,7 @@ public class R8Command extends BaseCompilerCommand { * Add proguard configuration for automatic main dex list calculation. */ public Builder addMainDexRules(List<String> lines) { - mainDexRules.add(new ProguardConfigurationSourceStrings(Paths.get("."), lines)); + mainDexRules.add(new ProguardConfigurationSourceStrings(lines, Paths.get("."))); return self(); } @@ -138,7 +142,7 @@ public class R8Command extends BaseCompilerCommand { * Add proguard configuration. */ public Builder addProguardConfiguration(List<String> lines) { - proguardConfigs.add(new ProguardConfigurationSourceStrings(Paths.get("."), lines)); + proguardConfigs.add(new ProguardConfigurationSourceStrings(lines, Paths.get("."))); return self(); } @@ -234,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()); @@ -256,6 +258,7 @@ public class R8Command extends BaseCompilerCommand { useMinification, ignoreMissingClasses, forceProguardCompatibility, + ignoreMissingClassesWhenNotShrinking, proguardMapOutput); } } @@ -297,6 +300,7 @@ public class R8Command extends BaseCompilerCommand { private final boolean useMinification; private final boolean ignoreMissingClasses; private final boolean forceProguardCompatibility; + private final boolean ignoreMissingClassesWhenNotShrinking; private final Path proguardMapOutput; public static Builder builder() { @@ -415,6 +419,7 @@ public class R8Command extends BaseCompilerCommand { boolean useMinification, boolean ignoreMissingClasses, boolean forceProguardCompatibility, + boolean ignoreMissingClassesWhenNotShrinking, Path proguardMapOutput) { super(inputApp, outputPath, outputMode, mode, minApiLevel, diagnosticsHandler, enableDesugaring); @@ -429,6 +434,7 @@ public class R8Command extends BaseCompilerCommand { this.useMinification = useMinification; this.ignoreMissingClasses = ignoreMissingClasses; this.forceProguardCompatibility = forceProguardCompatibility; + this.ignoreMissingClassesWhenNotShrinking = ignoreMissingClassesWhenNotShrinking; this.proguardMapOutput = proguardMapOutput; } @@ -442,6 +448,7 @@ public class R8Command extends BaseCompilerCommand { useMinification = false; ignoreMissingClasses = false; forceProguardCompatibility = false; + ignoreMissingClassesWhenNotShrinking = false; proguardMapOutput = null; } public boolean useTreeShaking() { @@ -479,6 +486,9 @@ public class R8Command extends BaseCompilerCommand { assert !internal.ignoreMissingClasses; internal.ignoreMissingClasses = ignoreMissingClasses; internal.ignoreMissingClasses |= proguardConfiguration.isIgnoreWarnings(); + internal.ignoreMissingClasses |= + ignoreMissingClassesWhenNotShrinking && !proguardConfiguration.isShrinking(); + assert !internal.verbose; internal.mainDexKeepRules = mainDexKeepRules; internal.minimalMainDex = internal.debug; 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/benchmarks/FrameworkIncrementalDexingBenchmark.java b/src/main/java/com/android/tools/r8/benchmarks/FrameworkIncrementalDexingBenchmark.java index 2120714ba..e904e53e9 100644 --- a/src/main/java/com/android/tools/r8/benchmarks/FrameworkIncrementalDexingBenchmark.java +++ b/src/main/java/com/android/tools/r8/benchmarks/FrameworkIncrementalDexingBenchmark.java @@ -22,6 +22,7 @@ import com.android.tools.r8.utils.ZipUtils; import com.google.common.collect.ImmutableMap; import com.google.common.io.ByteStreams; import java.io.IOException; +import java.io.InputStream; import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; @@ -174,7 +175,9 @@ public class FrameworkIncrementalDexingBenchmark { throws IOException, CompilationException { List<byte[]> bytes = new ArrayList<>(outputs.size()); for (Resource input : outputs.values()) { - bytes.add(ByteStreams.toByteArray(input.getStream())); + try (InputStream inputStream = input.getStream()) { + bytes.add(ByteStreams.toByteArray(inputStream)); + } } long start = System.nanoTime(); D8Output out = diff --git a/src/main/java/com/android/tools/r8/code/Format45cc.java b/src/main/java/com/android/tools/r8/code/Format45cc.java index c269fad73..f5123cf70 100644 --- a/src/main/java/com/android/tools/r8/code/Format45cc.java +++ b/src/main/java/com/android/tools/r8/code/Format45cc.java @@ -117,13 +117,13 @@ public abstract class Format45cc extends Base4Format { StringBuilder builder = new StringBuilder(); appendRegisterArguments(builder, " "); builder.append(" "); - builder.append(toString(BBBB, naming)); + builder.append(itemToString(BBBB, naming)); builder.append(", "); - builder.append(toString(HHHH, naming)); + builder.append(itemToString(HHHH, naming)); return formatString(builder.toString()); } - private String toString(IndexedDexItem indexedDexItem, ClassNameMapper naming) { + private String itemToString(IndexedDexItem indexedDexItem, ClassNameMapper naming) { String str; if (naming == null) { str = indexedDexItem.toSmaliString(); diff --git a/src/main/java/com/android/tools/r8/compatproguard/CompatProguard.java b/src/main/java/com/android/tools/r8/compatproguard/CompatProguard.java index 4c49a6a1b..a6b45ae17 100644 --- a/src/main/java/com/android/tools/r8/compatproguard/CompatProguard.java +++ b/src/main/java/com/android/tools/r8/compatproguard/CompatProguard.java @@ -33,14 +33,16 @@ public class CompatProguard { public final String output; public final int minApi; public final boolean forceProguardCompatibility; + public final boolean ignoreMissingClasses; public final boolean multiDex; public final List<String> proguardConfig; CompatProguardOptions(List<String> proguardConfig, String output, int minApi, - boolean multiDex, boolean forceProguardCompatibility) { + boolean multiDex, boolean forceProguardCompatibility, boolean ignoreMissingClasses) { this.output = output; this.minApi = minApi; this.forceProguardCompatibility = forceProguardCompatibility; + this.ignoreMissingClasses = ignoreMissingClasses; this.multiDex = multiDex; this.proguardConfig = proguardConfig; } @@ -49,6 +51,7 @@ public class CompatProguard { String output = null; int minApi = 1; boolean forceProguardCompatibility = false; + boolean ignoreMissingClasses = false; boolean multiDex = false; boolean coreLibrary = false; @@ -62,6 +65,8 @@ public class CompatProguard { minApi = Integer.valueOf(args[++i]); } else if (arg.equals("--force-proguard-compatibility")) { forceProguardCompatibility = true; + } else if (arg.equals("--ignore-missing-classes")) { + ignoreMissingClasses = true; } else if (arg.equals("--output")) { output = args[++i]; } else if (arg.equals("--multi-dex")) { @@ -82,7 +87,7 @@ public class CompatProguard { builder.add(currentLine.toString()); } return new CompatProguardOptions(builder.build(), output, minApi, multiDex, - forceProguardCompatibility); + forceProguardCompatibility, ignoreMissingClasses); } } @@ -95,7 +100,8 @@ public class CompatProguard { // Run R8 passing all the options from the command line as a Proguard configuration. CompatProguardOptions options = CompatProguardOptions.parse(args); R8Command.Builder builder = - new CompatProguardCommandBuilder(options.forceProguardCompatibility); + new CompatProguardCommandBuilder( + options.forceProguardCompatibility, options.ignoreMissingClasses); builder.setOutputPath(Paths.get(options.output)) .addProguardConfiguration(options.proguardConfig) .setMinApiLevel(options.minApi); diff --git a/src/main/java/com/android/tools/r8/compatproguard/CompatProguardCommandBuilder.java b/src/main/java/com/android/tools/r8/compatproguard/CompatProguardCommandBuilder.java index 71bc6eb0d..18ec474ac 100644 --- a/src/main/java/com/android/tools/r8/compatproguard/CompatProguardCommandBuilder.java +++ b/src/main/java/com/android/tools/r8/compatproguard/CompatProguardCommandBuilder.java @@ -7,8 +7,9 @@ package com.android.tools.r8.compatproguard; import com.android.tools.r8.R8Command; public class CompatProguardCommandBuilder extends R8Command.Builder { - public CompatProguardCommandBuilder(boolean forceProguardCompatibility) { - super(true, forceProguardCompatibility); + public CompatProguardCommandBuilder(boolean forceProguardCompatibility, + boolean ignoreMissingClasses) { + super(true, forceProguardCompatibility, true, ignoreMissingClasses); setEnableDesugaring(false); } } 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/dex/BaseFile.java b/src/main/java/com/android/tools/r8/dex/BaseFile.java index dd9b325c9..86143c84a 100644 --- a/src/main/java/com/android/tools/r8/dex/BaseFile.java +++ b/src/main/java/com/android/tools/r8/dex/BaseFile.java @@ -18,7 +18,9 @@ public abstract class BaseFile { protected final ByteBuffer buffer; protected BaseFile(Resource resource) throws IOException { - buffer = ByteBuffer.wrap(ByteStreams.toByteArray(resource.getStream())); + try (InputStream input = resource.getStream()) { + buffer = ByteBuffer.wrap(ByteStreams.toByteArray(input)); + } } protected BaseFile(String name) throws IOException { diff --git a/src/main/java/com/android/tools/r8/dex/DexFileReader.java b/src/main/java/com/android/tools/r8/dex/DexFileReader.java index 770afdf06..64909cc3a 100644 --- a/src/main/java/com/android/tools/r8/dex/DexFileReader.java +++ b/src/main/java/com/android/tools/r8/dex/DexFileReader.java @@ -983,7 +983,7 @@ public class DexFileReader { DexString shorty = indexedItems.getString(shortyIndex); DexType returnType = indexedItems.getType(returnTypeIndex); DexTypeList parameters = typeListAt(parametersOffsetIndex); - return dexItemFactory.createProto(shorty, returnType, parameters); + return dexItemFactory.createProto(returnType, shorty, parameters); } private DexMethod methodAt(int index) { diff --git a/src/main/java/com/android/tools/r8/errors/InternalCompilerError.java b/src/main/java/com/android/tools/r8/errors/InternalCompilerError.java index b51c7ecae..a5080d39a 100644 --- a/src/main/java/com/android/tools/r8/errors/InternalCompilerError.java +++ b/src/main/java/com/android/tools/r8/errors/InternalCompilerError.java @@ -14,8 +14,8 @@ public class InternalCompilerError extends IllegalStateException { public InternalCompilerError() { } - public InternalCompilerError(String s) { - super(s); + public InternalCompilerError(String message) { + super(message); } public InternalCompilerError(String message, Throwable cause) { 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/DexCode.java b/src/main/java/com/android/tools/r8/graph/DexCode.java index 6a2fcbf51..a05299c77 100644 --- a/src/main/java/com/android/tools/r8/graph/DexCode.java +++ b/src/main/java/com/android/tools/r8/graph/DexCode.java @@ -168,11 +168,10 @@ public class DexCode extends Code { public IRCode buildIR( DexEncodedMethod encodedMethod, - ValueNumberGenerator valueNumberGenerator, - InternalOptions options) + InternalOptions options, ValueNumberGenerator valueNumberGenerator) throws ApiLevelException { DexSourceCode source = new DexSourceCode(this, encodedMethod); - IRBuilder builder = new IRBuilder(encodedMethod, source, valueNumberGenerator, options); + IRBuilder builder = new IRBuilder(encodedMethod, source, options, valueNumberGenerator); return builder.build(); } diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java index 99325f908..19743978f 100644 --- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java +++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java @@ -265,11 +265,11 @@ public class DexEncodedMethod extends KeyedDexItem<DexMethod> { return code == null ? null : code.buildIR(this, options); } - public IRCode buildIR(ValueNumberGenerator valueNumberGenerator, InternalOptions options) + public IRCode buildIR(InternalOptions options, ValueNumberGenerator valueNumberGenerator) throws ApiLevelException { return code == null ? null - : code.asDexCode().buildIR(this, valueNumberGenerator, options); + : code.asDexCode().buildIR(this, options, valueNumberGenerator); } public void setCode( diff --git a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java index 153c2e651..a7d44ba31 100644 --- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java +++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java @@ -346,23 +346,19 @@ public class DexItemFactory { return createField(clazz, type, createString(name)); } - public DexProto createProto(DexString shorty, DexType returnType, DexTypeList parameters) { + public DexProto createProto(DexType returnType, DexString shorty, DexTypeList parameters) { assert !sorted; DexProto proto = new DexProto(shorty, returnType, parameters); return canonicalize(protos, proto); } - public DexProto createProto(DexString shorty, DexType returnType, DexType[] parameters) { + public DexProto createProto(DexType returnType, DexType... parameters) { assert !sorted; - return createProto(shorty, returnType, + return createProto(returnType, createShorty(returnType, parameters), parameters.length == 0 ? DexTypeList.empty() : new DexTypeList(parameters)); } - public DexProto createProto(DexType returnType, DexType... parameters) { - return createProto(createShorty(returnType, parameters), returnType, parameters); - } - - public DexString createShorty(DexType returnType, DexType[] argumentTypes) { + private DexString createShorty(DexType returnType, DexType[] argumentTypes) { StringBuilder shortyBuilder = new StringBuilder(); shortyBuilder.append(returnType.toShorty()); for (DexType argumentType : argumentTypes) { @@ -407,7 +403,7 @@ public class DexItemFactory { for (int i = 0; i < parameterDescriptors.length; i++) { parameterTypes[i] = createType(parameterDescriptors[i]); } - DexProto proto = createProto(shorty(returnType, parameterTypes), returnType, parameterTypes); + DexProto proto = createProto(returnType, parameterTypes); return createMethod(clazz, proto, name); } @@ -464,20 +460,6 @@ public class DexItemFactory { return method.name == classConstructorMethodName; } - private DexString shorty(DexType returnType, DexType[] parameters) { - StringBuilder builder = new StringBuilder(); - addToShorty(builder, returnType); - for (DexType parameter : parameters) { - addToShorty(builder, parameter); - } - return createString(builder.toString()); - } - - private void addToShorty(StringBuilder builder, DexType type) { - char first = type.toDescriptorString().charAt(0); - builder.append(first == '[' ? 'L' : first); - } - private static <S extends PresortedComparable<S>> void assignSortedIndices(Collection<S> items, NamingLens namingLens) { List<S> sorted = new ArrayList<>(items); 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 3abe7c5bb..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); @@ -44,10 +43,13 @@ public abstract class GraphLense { } public GraphLense build(DexItemFactory dexItemFactory) { - return build(new IdentityGraphLense(), dexItemFactory); + return build(dexItemFactory, new IdentityGraphLense()); } - public GraphLense build(GraphLense previousLense, DexItemFactory dexItemFactory) { + 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; @@ -118,7 +120,7 @@ public abstract class GraphLense { @Override public DexType lookupType(DexType type, DexEncodedMethod context) { if (type.isArrayType()) { - synchronized(this) { + synchronized (this) { // This block need to be synchronized due to arrayTypeCache. DexType result = arrayTypeCache.get(type); if (result == null) { @@ -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/graph/JarApplicationReader.java b/src/main/java/com/android/tools/r8/graph/JarApplicationReader.java index 757ae71fb..6bf1a1772 100644 --- a/src/main/java/com/android/tools/r8/graph/JarApplicationReader.java +++ b/src/main/java/com/android/tools/r8/graph/JarApplicationReader.java @@ -18,6 +18,7 @@ import org.objectweb.asm.Type; * It does not currently support multithreaded reading. */ public class JarApplicationReader { + public final InternalOptions options; ConcurrentHashMap<String, DexString> stringCache = new ConcurrentHashMap<>(); @@ -111,8 +112,8 @@ public class JarApplicationReader { argumentDescriptors[i] = arguments[i].getDescriptor(); } DexProto proto = options.itemFactory.createProto( - getString(shortyDescriptor.toString()), getTypeFromDescriptor(returnType.getDescriptor()), + getString(shortyDescriptor.toString()), getTypeListFromDescriptors(argumentDescriptors)); return proto; } diff --git a/src/main/java/com/android/tools/r8/graph/JarCode.java b/src/main/java/com/android/tools/r8/graph/JarCode.java index e8420284f..44bc52926 100644 --- a/src/main/java/com/android/tools/r8/graph/JarCode.java +++ b/src/main/java/com/android/tools/r8/graph/JarCode.java @@ -94,34 +94,34 @@ public class JarCode extends Code { throws ApiLevelException { triggerDelayedParsingIfNeccessary(); return options.debug - ? internalBuildWithLocals(encodedMethod, null, options) - : internalBuild(encodedMethod, null, options); + ? internalBuildWithLocals(encodedMethod, options, null) + : internalBuild(encodedMethod, options, null); } public IRCode buildIR( - DexEncodedMethod encodedMethod, ValueNumberGenerator generator, InternalOptions options) + DexEncodedMethod encodedMethod, InternalOptions options, ValueNumberGenerator generator) throws ApiLevelException { assert generator != null; triggerDelayedParsingIfNeccessary(); return options.debug - ? internalBuildWithLocals(encodedMethod, generator, options) - : internalBuild(encodedMethod, generator, options); + ? internalBuildWithLocals(encodedMethod, options, generator) + : internalBuild(encodedMethod, options, generator); } private IRCode internalBuildWithLocals( - DexEncodedMethod encodedMethod, ValueNumberGenerator generator, InternalOptions options) + DexEncodedMethod encodedMethod, InternalOptions options, ValueNumberGenerator generator) throws ApiLevelException { try { - return internalBuild(encodedMethod, generator, options); + return internalBuild(encodedMethod, options, generator); } catch (InvalidDebugInfoException e) { options.warningInvalidDebugInfo(encodedMethod, origin, e); node.localVariables.clear(); - return internalBuild(encodedMethod, generator, options); + return internalBuild(encodedMethod, options, generator); } } private IRCode internalBuild( - DexEncodedMethod encodedMethod, ValueNumberGenerator generator, InternalOptions options) + DexEncodedMethod encodedMethod, InternalOptions options, ValueNumberGenerator generator) throws ApiLevelException { if (!options.debug) { node.localVariables.clear(); @@ -130,7 +130,7 @@ public class JarCode extends Code { IRBuilder builder = (generator == null) ? new IRBuilder(encodedMethod, source, options) - : new IRBuilder(encodedMethod, source, generator, options); + : new IRBuilder(encodedMethod, source, options, generator); return builder.build(); } diff --git a/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java b/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java index bfbfb02c6..37b311962 100644 --- a/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java +++ b/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java @@ -959,10 +959,10 @@ public class BasicBlock { * <p>The constructed basic block has no predecessors and has one * successors which is the target block. * - * @param target the target of the goto block * @param blockNumber the block number of the goto block + * @param target the target of the goto block */ - public static BasicBlock createGotoBlock(BasicBlock target, int blockNumber) { + public static BasicBlock createGotoBlock(int blockNumber, BasicBlock target) { BasicBlock block = createGotoBlock(blockNumber); block.getSuccessors().add(target); return block; diff --git a/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionIterator.java b/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionIterator.java index ff9e25732..7bf8ec102 100644 --- a/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionIterator.java +++ b/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionIterator.java @@ -219,7 +219,7 @@ public class BasicBlockInstructionIterator implements InstructionIterator, Instr } @Override - public BasicBlock split(int instructions, IRCode code, ListIterator<BasicBlock> blocksIterator) { + public BasicBlock split(IRCode code, int instructions, ListIterator<BasicBlock> blocksIterator) { // Split at the current cursor position. BasicBlock newBlock = split(code, blocksIterator); assert blocksIterator == null || IteratorUtils.peekPrevious(blocksIterator) == newBlock; @@ -341,7 +341,7 @@ public class BasicBlockInstructionIterator implements InstructionIterator, Instr List<BasicBlock> blocksToRemove, DexType downcast) { assert blocksToRemove != null; boolean inlineeCanThrow = canThrow(inlinee); - BasicBlock invokeBlock = split(1, code, blocksIterator); + BasicBlock invokeBlock = split(code, 1, blocksIterator); assert invokeBlock.getInstructions().size() == 2; assert invokeBlock.getInstructions().getFirst().isInvoke(); diff --git a/src/main/java/com/android/tools/r8/ir/code/IRCode.java b/src/main/java/com/android/tools/r8/ir/code/IRCode.java index 5e0fe8ebf..e5fb6edfb 100644 --- a/src/main/java/com/android/tools/r8/ir/code/IRCode.java +++ b/src/main/java/com/android/tools/r8/ir/code/IRCode.java @@ -74,7 +74,7 @@ public class IRCode { // correct predecessor and successor structure. It is inserted // at the end of the list of blocks disregarding branching // structure. - BasicBlock newBlock = BasicBlock.createGotoBlock(block, nextBlockNumber++); + BasicBlock newBlock = BasicBlock.createGotoBlock(nextBlockNumber++, block); newBlocks.add(newBlock); pred.replaceSuccessor(block, newBlock); newBlock.getPredecessors().add(pred); @@ -108,7 +108,7 @@ public class IRCode { fallthrough = fallthrough.exit().fallthroughBlock(); } if (fallthrough != null) { - BasicBlock newFallthrough = BasicBlock.createGotoBlock(fallthrough, nextBlockNumber++); + BasicBlock newFallthrough = BasicBlock.createGotoBlock(nextBlockNumber++, fallthrough); current.exit().setFallthroughBlock(newFallthrough); newFallthrough.getPredecessors().add(current); fallthrough.replacePredecessor(current, newFallthrough); diff --git a/src/main/java/com/android/tools/r8/ir/code/InstructionListIterator.java b/src/main/java/com/android/tools/r8/ir/code/InstructionListIterator.java index fc7817b57..603ce6ecb 100644 --- a/src/main/java/com/android/tools/r8/ir/code/InstructionListIterator.java +++ b/src/main/java/com/android/tools/r8/ir/code/InstructionListIterator.java @@ -126,8 +126,8 @@ public interface InstructionListIterator extends ListIterator<Instruction>, * If the current block have catch handlers these catch handlers will be attached to the block * containing the throwing instruction after the split. * - * @param instructions the number of instructions to include in the second block. * @param code the IR code for the block this iterator originates from. + * @param instructions the number of instructions to include in the second block. * @param blockIterator basic block iterator used to iterate the blocks. This must be positioned * just after the block for this is the instruction iterator. After this method returns it will be * positioned just after the second block inserted. Calling {@link #remove} without further @@ -135,13 +135,13 @@ public interface InstructionListIterator extends ListIterator<Instruction>, * @return Returns the new block with the instructions after the cursor. */ // TODO(sgjesse): Refactor to avoid the need for passing code and blockIterator. - BasicBlock split(int instructions, IRCode code, ListIterator<BasicBlock> blockIterator); + BasicBlock split(IRCode code, int instructions, ListIterator<BasicBlock> blockIterator); /** - * See {@link #split(int, IRCode, ListIterator)}. + * See {@link #split(IRCode, int, ListIterator)}. */ - default BasicBlock split(int instructions, IRCode code) { - return split(instructions, code, null); + default BasicBlock split(IRCode code, int instructions) { + return split(code, instructions, null); } /** diff --git a/src/main/java/com/android/tools/r8/ir/code/Phi.java b/src/main/java/com/android/tools/r8/ir/code/Phi.java index 78c5c4e45..dc6d50067 100644 --- a/src/main/java/com/android/tools/r8/ir/code/Phi.java +++ b/src/main/java/com/android/tools/r8/ir/code/Phi.java @@ -70,7 +70,7 @@ public class Phi extends Value { for (BasicBlock pred : block.getPredecessors()) { EdgeType edgeType = pred.getEdgeType(block); // Since this read has been delayed we must provide the local info for the value. - Value operand = builder.readRegister(register, pred, edgeType, type, getLocalInfo()); + Value operand = builder.readRegister(register, type, pred, edgeType, getLocalInfo()); canBeNull |= operand.canBeNull(); appendOperand(operand); } diff --git a/src/main/java/com/android/tools/r8/ir/code/Value.java b/src/main/java/com/android/tools/r8/ir/code/Value.java index 1915155fb..ea43cf88c 100644 --- a/src/main/java/com/android/tools/r8/ir/code/Value.java +++ b/src/main/java/com/android/tools/r8/ir/code/Value.java @@ -593,10 +593,10 @@ public class Value { public boolean isDead(InternalOptions options) { // Totally unused values are trivially dead. - return !isUsed() || isDead(new HashSet<>(), options); + return !isUsed() || isDead(options, new HashSet<>()); } - protected boolean isDead(Set<Value> active, InternalOptions options) { + protected boolean isDead(InternalOptions options, Set<Value> active) { // If the value has debug users we cannot eliminate it since it represents a value in a local // variable that should be visible in the debugger. if (numberOfDebugUsers() != 0) { @@ -613,12 +613,12 @@ public class Value { // Instructions with no out value cannot be dead code by the current definition // (unused out value). They typically side-effect input values or deals with control-flow. assert outValue != null; - if (!active.contains(outValue) && !outValue.isDead(active, options)) { + if (!active.contains(outValue) && !outValue.isDead(options, active)) { return false; } } for (Phi phi : uniquePhiUsers()) { - if (!active.contains(phi) && !phi.isDead(active, options)) { + if (!active.contains(phi) && !phi.isDead(options, active)) { return false; } } diff --git a/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java index 042232b82..2335f2b46 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java @@ -386,7 +386,7 @@ public class DexBuilder { if (ifsNeedingRewrite.contains(block)) { If theIf = block.exit().asIf(); BasicBlock trueTarget = theIf.getTrueTarget(); - BasicBlock newBlock = BasicBlock.createGotoBlock(trueTarget, ir.blocks.size()); + BasicBlock newBlock = BasicBlock.createGotoBlock(ir.blocks.size(), trueTarget); theIf.setTrueTarget(newBlock); theIf.invert(); it.add(newBlock); @@ -641,7 +641,8 @@ public class DexBuilder { item = tryItems.get(i); coalescedTryItems.add(item); // Trim the range start for non-throwing instructions when starting a new range. - List<com.android.tools.r8.ir.code.Instruction> instructions = blocksWithHandlers.get(i).getInstructions(); + List<com.android.tools.r8.ir.code.Instruction> instructions = blocksWithHandlers.get(i) + .getInstructions(); for (com.android.tools.r8.ir.code.Instruction insn : instructions) { if (insn.instructionTypeCanThrow()) { item.start = getInfo(insn).getOffset(); @@ -819,7 +820,8 @@ public class DexBuilder { private Instruction[] instructions; private final int size; - public MultiFixedSizeInfo(com.android.tools.r8.ir.code.Instruction ir, Instruction[] instructions) { + public MultiFixedSizeInfo(com.android.tools.r8.ir.code.Instruction ir, + Instruction[] instructions) { super(ir); this.instructions = instructions; int size = 0; diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java index 2dceeab8f..4a96ae664 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java @@ -291,14 +291,13 @@ public class IRBuilder { private int nextBlockNumber = 0; public IRBuilder(DexEncodedMethod method, SourceCode source, InternalOptions options) { - this(method, source, new ValueNumberGenerator(), options); + this(method, source, options, new ValueNumberGenerator()); } public IRBuilder( DexEncodedMethod method, SourceCode source, - ValueNumberGenerator valueNumberGenerator, - InternalOptions options) { + InternalOptions options, ValueNumberGenerator valueNumberGenerator) { assert source != null; this.method = method; this.source = source; @@ -1624,7 +1623,7 @@ public class IRBuilder { public Value readRegister(int register, MoveType type) { DebugLocalInfo local = getCurrentLocal(register); - Value value = readRegister(register, currentBlock, EdgeType.NON_EDGE, type, local); + Value value = readRegister(register, type, currentBlock, EdgeType.NON_EDGE, local); // Check that any information about a current-local is consistent with the read. if (local != null && value.getLocalInfo() != local && !value.isUninitializedLocal()) { throw new InvalidDebugInfoException( @@ -1643,10 +1642,10 @@ public class IRBuilder { public Value readRegisterIgnoreLocal(int register, MoveType type) { DebugLocalInfo local = getCurrentLocal(register); - return readRegister(register, currentBlock, EdgeType.NON_EDGE, type, local); + return readRegister(register, type, currentBlock, EdgeType.NON_EDGE, local); } - public Value readRegister(int register, BasicBlock block, EdgeType readingEdge, MoveType type, + public Value readRegister(int register, MoveType type, BasicBlock block, EdgeType readingEdge, DebugLocalInfo local) { checkRegister(register); Value value = block.readCurrentDefinition(register, readingEdge); @@ -1665,7 +1664,7 @@ public class IRBuilder { assert block.verifyFilledPredecessors(); BasicBlock pred = block.getPredecessors().get(0); EdgeType edgeType = pred.getEdgeType(block); - value = readRegister(register, pred, edgeType, type, local); + value = readRegister(register, type, pred, edgeType, local); } else { Phi phi = new Phi(valueNumberGenerator.next(), block, type, local); // We need to write the phi before adding operands to break cycles. If the phi is trivial @@ -1939,7 +1938,7 @@ public class IRBuilder { BasicBlock target = pair.second; // New block with one unfilled predecessor. - BasicBlock newBlock = BasicBlock.createGotoBlock(target, nextBlockNumber++); + BasicBlock newBlock = BasicBlock.createGotoBlock(nextBlockNumber++, target); blocks.add(newBlock); newBlock.incrementUnfilledPredecessorCount(); @@ -2018,7 +2017,7 @@ public class IRBuilder { int otherPredecessorIndex = values.get(v); BasicBlock joinBlock = joinBlocks.get(otherPredecessorIndex); if (joinBlock == null) { - joinBlock = BasicBlock.createGotoBlock(block, blocks.size() + blocksToAdd.size()); + joinBlock = BasicBlock.createGotoBlock(blocks.size() + blocksToAdd.size(), block); joinBlocks.put(otherPredecessorIndex, joinBlock); blocksToAdd.add(joinBlock); BasicBlock otherPredecessor = block.getPredecessors().get(otherPredecessorIndex); diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java index dda8031a6..da022fe86 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java @@ -82,11 +82,8 @@ public class IRConverter { private DexString highestSortingString; private IRConverter( - Timing timing, - AppInfo appInfo, - GraphLense graphLense, - InternalOptions options, - CfgPrinter printer, + AppInfo appInfo, InternalOptions options, Timing timing, + CfgPrinter printer, GraphLense graphLense, boolean enableWholeProgramOptimizations) { assert appInfo != null; assert options != null; @@ -127,30 +124,26 @@ public class IRConverter { public IRConverter( AppInfo appInfo, InternalOptions options) { - this(null, appInfo, null, options, null, false); + this(appInfo, options, null, null, null, false); } /** * Create an IR converter for processing methods with full program optimization disabled. */ public IRConverter( - Timing timing, - AppInfo appInfo, - InternalOptions options, + AppInfo appInfo, InternalOptions options, Timing timing, CfgPrinter printer) { - this(timing, appInfo, null, options, printer, false); + this(appInfo, options, timing, printer, null, false); } /** * Create an IR converter for processing methods with full program optimization enabled. */ public IRConverter( - Timing timing, - AppInfoWithSubtyping appInfo, - InternalOptions options, + AppInfoWithSubtyping appInfo, InternalOptions options, Timing timing, CfgPrinter printer, GraphLense graphLense) { - this(timing, appInfo, graphLense, options, printer, true); + this(appInfo, options, timing, printer, graphLense, true); } private boolean enableInterfaceMethodDesugaring() { 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 ab3efdbd4..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 @@ -116,7 +116,7 @@ public class LensCodeRewriter { // If the current block has catch handlers split the check cast into its own block. if (newInvoke.getBlock().hasCatchHandlers()) { iterator.previous(); - iterator.split(1, code, blocks); + iterator.split(code, 1, blocks); } } } @@ -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/ir/desugar/AccessorMethodSourceCode.java b/src/main/java/com/android/tools/r8/ir/desugar/AccessorMethodSourceCode.java index 160da3c16..1cf64fd2b 100644 --- a/src/main/java/com/android/tools/r8/ir/desugar/AccessorMethodSourceCode.java +++ b/src/main/java/com/android/tools/r8/ir/desugar/AccessorMethodSourceCode.java @@ -20,7 +20,7 @@ import java.util.List; final class AccessorMethodSourceCode extends SynthesizedLambdaSourceCode { AccessorMethodSourceCode(LambdaClass lambda) { - super(/* no receiver for static method */ null, lambda, lambda.target.callTarget); + super(lambda, lambda.target.callTarget, null /* no receiver for static method */); // We should never need an accessor for interface methods since // they are supposed to be public. assert !descriptor().implHandle.type.isInvokeInterface(); diff --git a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java index 31c6de175..408f53b60 100644 --- a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java +++ b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java @@ -58,6 +58,7 @@ import java.util.Set; // forward the call to an appropriate method in interface companion class. // public final class InterfaceMethodRewriter { + // Public for testing. public static final String COMPANION_CLASS_NAME_SUFFIX = "-CC"; private static final String DEFAULT_METHOD_PREFIX = "$default$"; @@ -75,11 +76,17 @@ public final class InterfaceMethodRewriter { */ private Set<DexItem> reportedMissing = Sets.newIdentityHashSet(); - /** Defines a minor variation in desugaring. */ + /** + * Defines a minor variation in desugaring. + */ public enum Flavor { - /** Process all application resources. */ + /** + * Process all application resources. + */ IncludeAllResources, - /** Process all but DEX application resources. */ + /** + * Process all but DEX application resources. + */ ExcludeDexResources } @@ -174,13 +181,14 @@ public final class InterfaceMethodRewriter { } else if (holderClass.isInterface()) { throw new Unimplemented( "Desugaring of static interface method handle as in `" - + referencedFrom.toSourceString() + "` in is not yet supported."); + + referencedFrom.toSourceString() + "` in is not yet supported."); } } } /** * Returns the class definition for the specified type. + * * @return may return null if no definition for the given type is available. */ final DexClass findDefinitionFor(DexType type) { @@ -313,7 +321,7 @@ public final class InterfaceMethodRewriter { .append("`"); } options.diagnosticsHandler.warning( - new StringDiagnostic(classToDesugar.getOrigin(), builder.toString())); + new StringDiagnostic(builder.toString(), classToDesugar.getOrigin())); } private void warnMissingType(DexMethod referencedFrom, DexType missing) { @@ -331,6 +339,6 @@ public final class InterfaceMethodRewriter { .append("`"); DexClass referencedFromClass = converter.appInfo.definitionFor(referencedFrom.getHolder()); options.diagnosticsHandler.warning( - new StringDiagnostic(referencedFromClass.getOrigin(), builder.toString())); + new StringDiagnostic(builder.toString(), referencedFromClass.getOrigin())); } } diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaClassConstructorSourceCode.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaClassConstructorSourceCode.java index 539fcf07d..411ab4ced 100644 --- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaClassConstructorSourceCode.java +++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaClassConstructorSourceCode.java @@ -15,7 +15,7 @@ import java.util.Collections; final class LambdaClassConstructorSourceCode extends SynthesizedLambdaSourceCode { LambdaClassConstructorSourceCode(LambdaClass lambda) { - super(null /* Class initializer is static */, lambda, lambda.classConstructor); + super(lambda, lambda.classConstructor, null /* Class initializer is static */); assert lambda.instanceField != null; } diff --git a/src/main/java/com/android/tools/r8/ir/desugar/SynthesizedLambdaSourceCode.java b/src/main/java/com/android/tools/r8/ir/desugar/SynthesizedLambdaSourceCode.java index 9a3531f14..bd38e1acb 100644 --- a/src/main/java/com/android/tools/r8/ir/desugar/SynthesizedLambdaSourceCode.java +++ b/src/main/java/com/android/tools/r8/ir/desugar/SynthesizedLambdaSourceCode.java @@ -16,14 +16,14 @@ abstract class SynthesizedLambdaSourceCode extends SingleBlockSourceCode { final DexMethod currentMethod; final LambdaClass lambda; - SynthesizedLambdaSourceCode(DexType receiver, LambdaClass lambda, DexMethod currentMethod) { + SynthesizedLambdaSourceCode(LambdaClass lambda, DexMethod currentMethod, DexType receiver) { super(receiver, currentMethod.proto); this.lambda = lambda; this.currentMethod = currentMethod; } SynthesizedLambdaSourceCode(LambdaClass lambda, DexMethod currentMethod) { - this(lambda.type, lambda, currentMethod); + this(lambda, currentMethod, lambda.type); } final LambdaDescriptor descriptor() { diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java index 9699ec16b..ad4639e7f 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java @@ -232,14 +232,14 @@ public class Inliner { GraphLense graphLense, InternalOptions options) throws ApiLevelException { if (target.isProcessed()) { assert target.getCode().isDexCode(); - return target.buildIR(generator, options); + return target.buildIR(options, generator); } else { // Build the IR for a yet not processed method, and perform minimal IR processing. IRCode code; if (target.getCode().isJarCode()) { - code = target.getCode().asJarCode().buildIR(target, generator, options); + code = target.getCode().asJarCode().buildIR(target, options, generator); } else { - code = target.getCode().asDexCode().buildIR(target, generator, options); + code = target.getCode().asDexCode().buildIR(target, options, generator); } new LensCodeRewriter(graphLense, appInfo).rewrite(code, target); return code; diff --git a/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java b/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java index 4662cd0a8..83788c7c2 100644 --- a/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java +++ b/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java @@ -16,9 +16,9 @@ import com.android.tools.r8.utils.InternalOptions; import java.io.IOException; import java.util.Collections; import java.util.concurrent.ExecutorService; -import jdk.internal.org.objectweb.asm.Type; import org.objectweb.asm.ClassWriter; import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Type; public class CfApplicationWriter { private final DexApplication application; diff --git a/src/main/java/com/android/tools/r8/naming/ClassNameMapper.java b/src/main/java/com/android/tools/r8/naming/ClassNameMapper.java index 1360964ad..2e654a869 100644 --- a/src/main/java/com/android/tools/r8/naming/ClassNameMapper.java +++ b/src/main/java/com/android/tools/r8/naming/ClassNameMapper.java @@ -3,6 +3,8 @@ // BSD-style license that can be found in the LICENSE file. package com.android.tools.r8.naming; +import static com.android.tools.r8.utils.DescriptorUtils.descriptorToJavaType; + import com.android.tools.r8.graph.DexField; import com.android.tools.r8.graph.DexMethod; import com.android.tools.r8.graph.DexType; @@ -10,26 +12,79 @@ import com.android.tools.r8.graph.IndexedDexItem; import com.android.tools.r8.naming.MemberNaming.FieldSignature; import com.android.tools.r8.naming.MemberNaming.MethodSignature; import com.android.tools.r8.naming.MemberNaming.Signature; -import com.android.tools.r8.utils.DescriptorUtils; import com.google.common.collect.BiMap; import com.google.common.collect.ImmutableBiMap; import com.google.common.collect.ImmutableMap; +import java.io.BufferedReader; +import java.io.ByteArrayInputStream; import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; import java.io.StringWriter; import java.io.Writer; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; import java.util.HashMap; import java.util.Map; import java.util.function.Consumer; -public class ClassNameMapper { +public class ClassNameMapper implements ProguardMap { + + static class Builder extends ProguardMap.Builder { + final ImmutableMap.Builder<String, ClassNamingForNameMapper.Builder> mapBuilder; + + private Builder() { + mapBuilder = ImmutableMap.builder(); + } + + @Override + ClassNamingForNameMapper.Builder classNamingBuilder(String renamedName, String originalName) { + ClassNamingForNameMapper.Builder classNamingBuilder = + ClassNamingForNameMapper.builder(renamedName, originalName); + mapBuilder.put(renamedName, classNamingBuilder); + return classNamingBuilder; + } + + @Override + ClassNameMapper build(){ + return new ClassNameMapper(mapBuilder.build()); + } + } + + static Builder builder() { + return new Builder(); + } + + public static ClassNameMapper mapperFromInputStream(InputStream in) throws IOException { + BufferedReader reader = new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8)); + try (ProguardMapReader proguardReader = new ProguardMapReader(reader)) { + ClassNameMapper.Builder builder = ClassNameMapper.builder(); + proguardReader.parse(builder); + return builder.build(); + } + } + + public static ClassNameMapper mapperFromFile(Path path) throws IOException { + return mapperFromInputStream(Files.newInputStream(path)); + } - private final ImmutableMap<String, ClassNaming> classNameMappings; + static ClassNameMapper mapperFromString(String contents) throws IOException { + return mapperFromInputStream( + new ByteArrayInputStream(contents.getBytes(StandardCharsets.UTF_8))); + } + + private final ImmutableMap<String, ClassNamingForNameMapper> classNameMappings; private ImmutableBiMap<String, String> nameMapping; private Map<Signature, Signature> signatureMap = new HashMap<>(); - ClassNameMapper(Map<String, ClassNaming> classNameMappings) { - this.classNameMappings = ImmutableMap.copyOf(classNameMappings); + private ClassNameMapper(Map<String, ClassNamingForNameMapper.Builder> classNameMappings) { + ImmutableMap.Builder<String, ClassNamingForNameMapper> builder = ImmutableMap.builder(); + for(Map.Entry<String, ClassNamingForNameMapper.Builder> entry : classNameMappings.entrySet()) { + builder.put(entry.getKey(), entry.getValue().build()); + } + this.classNameMappings = builder.build(); } private Signature canonicalizeSignature(Signature signature) { @@ -65,7 +120,7 @@ public class ClassNameMapper { * Returns the deobfuscated name if a mapping was found. Otherwise it returns the passed in name. */ public String deobfuscateClassName(String name) { - ClassNaming classNaming = classNameMappings.get(name); + ClassNamingForNameMapper classNaming = classNameMappings.get(name); if (classNaming == null) { return name; } @@ -73,15 +128,27 @@ public class ClassNameMapper { } private String deobfuscateType(String asString) { - return DescriptorUtils.descriptorToJavaType(asString, this); + return descriptorToJavaType(asString, this); } - public ClassNaming getClassNaming(String name) { + @Override + public boolean hasMapping(DexType type) { + String decoded = descriptorToJavaType(type.descriptor.toString()); + return classNameMappings.containsKey(decoded); + } + + @Override + public ClassNamingForNameMapper getClassNaming(DexType type) { + String decoded = descriptorToJavaType(type.descriptor.toString()); + return classNameMappings.get(decoded); + } + + public ClassNamingForNameMapper getClassNaming(String name) { return classNameMappings.get(name); } public void write(Writer writer, boolean collapseRanges) throws IOException { - for (ClassNaming naming : classNameMappings.values()) { + for (ClassNamingForNameMapper naming : classNameMappings.values()) { naming.write(writer, collapseRanges); } } @@ -129,15 +196,15 @@ public class ClassNameMapper { } else if (item instanceof DexMethod) { return lookupName(getRenamedMethodSignature((DexMethod) item), ((DexMethod) item).holder); } else if (item instanceof DexType) { - return DescriptorUtils.descriptorToJavaType(((DexType) item).toDescriptorString(), this); + return descriptorToJavaType(((DexType) item).toDescriptorString(), this); } else { return item.toString(); } } private String lookupName(Signature signature, DexType clazz) { - String decoded = DescriptorUtils.descriptorToJavaType(clazz.descriptor.toString()); - ClassNaming classNaming = getClassNaming(decoded); + String decoded = descriptorToJavaType(clazz.descriptor.toString()); + ClassNamingForNameMapper classNaming = getClassNaming(decoded); if (classNaming == null) { return decoded + " " + signature.toString(); } @@ -149,8 +216,7 @@ public class ClassNameMapper { } public Signature originalSignatureOf(DexMethod method) { - String decoded = DescriptorUtils - .descriptorToJavaType(method.holder.descriptor.toString()); + String decoded = descriptorToJavaType(method.holder.descriptor.toString()); MethodSignature memberSignature = getRenamedMethodSignature(method); ClassNaming classNaming = getClassNaming(decoded); if (classNaming == null) { diff --git a/src/main/java/com/android/tools/r8/naming/ClassNaming.java b/src/main/java/com/android/tools/r8/naming/ClassNaming.java index 74c50068c..d211e7b67 100644 --- a/src/main/java/com/android/tools/r8/naming/ClassNaming.java +++ b/src/main/java/com/android/tools/r8/naming/ClassNaming.java @@ -4,114 +4,30 @@ package com.android.tools.r8.naming; import com.android.tools.r8.naming.MemberNaming.Signature; -import java.io.IOException; -import java.io.StringWriter; -import java.io.Writer; -import java.util.ArrayList; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.function.Consumer; +import com.android.tools.r8.utils.ThrowingConsumer; /** * Stores name information for a class. * <p> - * This includes how the class was renamed and information on the classes members. + * Implementers will include how the class was renamed and information on the class's members. */ -public class ClassNaming { +public interface ClassNaming { - public final String originalName; - public final String renamedName; - - /** - * Mapping from the renamed signature to the naming information for a member. - * <p> - * A renamed signature is a signature where the member's name has been obfuscated but not the type - * information. - **/ - final Map<Signature, MemberNaming> members = new LinkedHashMap<>(); - - ClassNaming(String renamedName, String originalName) { - this.renamedName = renamedName; - this.originalName = originalName; - } - - void addMemberEntry(MemberNaming entry) { - Signature renamedSignature = entry.renamedSignature; - members.put(renamedSignature, entry); - } - - public MemberNaming lookup(Signature renamedSignature) { - return members.get(renamedSignature); + abstract class Builder { + abstract Builder addMemberEntry(MemberNaming entry); + abstract ClassNaming build(); } - public MemberNaming lookupByOriginalSignature(Signature original) { - for (MemberNaming naming : members.values()) { - if (naming.signature.equals(original)) { - return naming; - } - } - return null; - } + MemberNaming lookup(Signature renamedSignature); - public List<MemberNaming> lookupByOriginalName(String originalName) { - List<MemberNaming> result = new ArrayList<>(); - for (MemberNaming naming : members.values()) { - if (naming.signature.name.equals(originalName)) { - result.add(naming); - } - } - return result; - } + MemberNaming lookupByOriginalSignature(Signature original); - public void forAllMemberNaming(Consumer<MemberNaming> consumer) { - members.values().forEach(consumer); - } + <T extends Throwable> void forAllMemberNaming( + ThrowingConsumer<MemberNaming, T> consumer) throws T; - void write(Writer writer, boolean collapseRanges) throws IOException { - writer.append(originalName); - writer.append(" -> "); - writer.append(renamedName); - writer.append(":\n"); - for (MemberNaming member : members.values()) { - member.write(writer, collapseRanges, true); - } - } + <T extends Throwable> void forAllFieldNaming( + ThrowingConsumer<MemberNaming, T> consumer) throws T; - @Override - public String toString() { - try { - StringWriter writer = new StringWriter(); - write(writer, false); - return writer.toString(); - } catch (IOException e) { - return e.toString(); - } - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (!(o instanceof ClassNaming)) { - return false; - } - - ClassNaming that = (ClassNaming) o; - - return originalName.equals(that.originalName) - && renamedName.equals(that.renamedName) - && members.equals(that.members); - - } - - @Override - public int hashCode() { - int result = originalName.hashCode(); - result = 31 * result + renamedName.hashCode(); - result = 31 * result + members.hashCode(); - return result; - } + <T extends Throwable> void forAllMethodNaming( + ThrowingConsumer<MemberNaming, T> consumer) throws T; } - diff --git a/src/main/java/com/android/tools/r8/naming/ClassNamingForMapApplier.java b/src/main/java/com/android/tools/r8/naming/ClassNamingForMapApplier.java new file mode 100644 index 000000000..d94fe57c0 --- /dev/null +++ b/src/main/java/com/android/tools/r8/naming/ClassNamingForMapApplier.java @@ -0,0 +1,193 @@ +// Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. +package com.android.tools.r8.naming; + +import com.android.tools.r8.graph.DexField; +import com.android.tools.r8.graph.DexMethod; +import com.android.tools.r8.graph.DexType; +import com.android.tools.r8.naming.MemberNaming.FieldSignature; +import com.android.tools.r8.naming.MemberNaming.MethodSignature; +import com.android.tools.r8.naming.MemberNaming.Signature; +import com.android.tools.r8.naming.MemberNaming.Signature.SignatureKind; +import com.android.tools.r8.utils.ThrowingConsumer; +import com.google.common.collect.ImmutableMap; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +/** + * Stores name information for a class. + * <p> + * The main differences of this against {@link ClassNamingForNameMapper} are: + * 1) field and method mappings are maintained and searched separately for faster lookup; + * 2) similar to the relation between {@link ClassNameMapper} and {@link SeedMapper}, this one + * uses original {@link Signature} as a key to look up {@link MemberNaming}, + * whereas {@link ClassNamingForNameMapper} uses renamed {@link Signature} as a key; and thus + * 3) logic of {@link #lookup} and {@link #lookupByOriginalSignature} are inverted; and + * 4) {@link #lookupByOriginalItem}'s are introduced for lightweight lookup. + */ +public class ClassNamingForMapApplier implements ClassNaming { + + public static class Builder extends ClassNaming.Builder { + private final String originalName; + private final String renamedName; + private final Map<MethodSignature, MemberNaming> methodMembers = new HashMap<>(); + private final Map<FieldSignature, MemberNaming> fieldMembers = new HashMap<>(); + + private Builder(String renamedName, String originalName) { + this.originalName = originalName; + this.renamedName = renamedName; + } + + @Override + ClassNaming.Builder addMemberEntry(MemberNaming entry) { + // Unlike {@link ClassNamingForNameMapper.Builder#addMemberEntry}, + // the key is original signature. + if (entry.isMethodNaming()) { + methodMembers.put((MethodSignature) entry.getOriginalSignature(), entry); + } else { + fieldMembers.put((FieldSignature) entry.getOriginalSignature(), entry); + } + return this; + } + + @Override + ClassNamingForMapApplier build() { + return new ClassNamingForMapApplier(renamedName, originalName, methodMembers, fieldMembers); + } + } + + static Builder builder(String renamedName, String originalName) { + return new Builder(renamedName, originalName); + } + + private final String originalName; + final String renamedName; + + private final ImmutableMap<MethodSignature, MemberNaming> methodMembers; + private final ImmutableMap<FieldSignature, MemberNaming> fieldMembers; + + // Constructor to help chaining {@link ClassNamingForMapApplier} according to class hierarchy. + ClassNamingForMapApplier(ClassNamingForMapApplier proxy) { + this(proxy.renamedName, proxy.originalName, proxy.methodMembers, proxy.fieldMembers); + } + + private ClassNamingForMapApplier( + String renamedName, + String originalName, + Map<MethodSignature, MemberNaming> methodMembers, + Map<FieldSignature, MemberNaming> fieldMembers) { + this.renamedName = renamedName; + this.originalName = originalName; + this.methodMembers = ImmutableMap.copyOf(methodMembers); + this.fieldMembers = ImmutableMap.copyOf(fieldMembers); + } + + @Override + public <T extends Throwable> void forAllMemberNaming( + ThrowingConsumer<MemberNaming, T> consumer) throws T { + forAllFieldNaming(consumer); + forAllMethodNaming(consumer); + } + + @Override + public <T extends Throwable> void forAllFieldNaming( + ThrowingConsumer<MemberNaming, T> consumer) throws T { + for (MemberNaming naming : fieldMembers.values()) { + consumer.accept(naming); + } + } + + @Override + public <T extends Throwable> void forAllMethodNaming( + ThrowingConsumer<MemberNaming, T> consumer) throws T { + for (MemberNaming naming : methodMembers.values()) { + consumer.accept(naming); + } + } + + @Override + public MemberNaming lookup(Signature renamedSignature) { + // As the key is inverted, this looks a lot like + // {@link ClassNamingForNameMapper#lookupByOriginalSignature}. + if (renamedSignature.kind() == SignatureKind.METHOD) { + for (MemberNaming memberNaming : methodMembers.values()) { + if (memberNaming.getRenamedSignature().equals(renamedSignature)) { + return memberNaming; + } + } + return null; + } else { + assert renamedSignature.kind() == SignatureKind.FIELD; + for (MemberNaming memberNaming : fieldMembers.values()) { + if (memberNaming.getRenamedSignature().equals(renamedSignature)) { + return memberNaming; + } + } + return null; + } + } + + @Override + public MemberNaming lookupByOriginalSignature(Signature original) { + // As the key is inverted, this looks a lot like {@link ClassNamingForNameMapper#lookup}. + if (original.kind() == SignatureKind.METHOD) { + return methodMembers.get(original); + } else { + assert original.kind() == SignatureKind.FIELD; + return fieldMembers.get(original); + } + } + + MemberNaming lookupByOriginalItem(DexField field) { + for (Map.Entry<FieldSignature, MemberNaming> entry : fieldMembers.entrySet()) { + FieldSignature signature = entry.getKey(); + if (signature.name.equals(field.name.toString()) + && signature.type.equals(field.type.getName())) { + return entry.getValue(); + } + } + return null; + } + + protected MemberNaming lookupByOriginalItem(DexMethod method) { + for (Map.Entry<MethodSignature, MemberNaming> entry : methodMembers.entrySet()) { + MethodSignature signature = entry.getKey(); + if (signature.name.equals(method.name.toString()) + && signature.type.equals(method.proto.returnType.toString()) + && Arrays.equals(signature.parameters, + Arrays.stream(method.proto.parameters.values) + .map(DexType::toString).toArray(String[]::new))) { + return entry.getValue(); + } + } + return null; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof ClassNamingForMapApplier)) { + return false; + } + + ClassNamingForMapApplier that = (ClassNamingForMapApplier) o; + + return originalName.equals(that.originalName) + && renamedName.equals(that.renamedName) + && methodMembers.equals(that.methodMembers) + && fieldMembers.equals(that.fieldMembers); + } + + @Override + public int hashCode() { + int result = originalName.hashCode(); + result = 31 * result + renamedName.hashCode(); + result = 31 * result + methodMembers.hashCode(); + result = 31 * result + fieldMembers.hashCode(); + return result; + } +} diff --git a/src/main/java/com/android/tools/r8/naming/ClassNamingForNameMapper.java b/src/main/java/com/android/tools/r8/naming/ClassNamingForNameMapper.java new file mode 100644 index 000000000..158549293 --- /dev/null +++ b/src/main/java/com/android/tools/r8/naming/ClassNamingForNameMapper.java @@ -0,0 +1,194 @@ +// Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. +package com.android.tools.r8.naming; + +import com.android.tools.r8.naming.MemberNaming.FieldSignature; +import com.android.tools.r8.naming.MemberNaming.MethodSignature; +import com.android.tools.r8.naming.MemberNaming.Signature; +import com.android.tools.r8.naming.MemberNaming.Signature.SignatureKind; +import com.android.tools.r8.utils.ThrowingConsumer; +import com.google.common.collect.ImmutableMap; +import java.io.IOException; +import java.io.StringWriter; +import java.io.Writer; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Stores name information for a class. + * <p> + * This includes how the class was renamed and information on the classes members. + */ +public class ClassNamingForNameMapper implements ClassNaming { + + public static class Builder extends ClassNaming.Builder { + private final String originalName; + private final String renamedName; + private final Map<MethodSignature, MemberNaming> methodMembers = new HashMap<>(); + private final Map<FieldSignature, MemberNaming> fieldMembers = new HashMap<>(); + + private Builder(String renamedName, String originalName) { + this.originalName = originalName; + this.renamedName = renamedName; + } + + @Override + ClassNaming.Builder addMemberEntry(MemberNaming entry) { + if (entry.isMethodNaming()) { + methodMembers.put((MethodSignature) entry.getRenamedSignature(), entry); + } else { + fieldMembers.put((FieldSignature) entry.getRenamedSignature(), entry); + } + return this; + } + + @Override + ClassNamingForNameMapper build() { + return new ClassNamingForNameMapper(renamedName, originalName, methodMembers, fieldMembers); + } + } + + static Builder builder(String renamedName, String originalName) { + return new Builder(renamedName, originalName); + } + + public final String originalName; + private final String renamedName; + + /** + * Mapping from the renamed signature to the naming information for a member. + * <p> + * A renamed signature is a signature where the member's name has been obfuscated but not the type + * information. + **/ + private final ImmutableMap<MethodSignature, MemberNaming> methodMembers; + private final ImmutableMap<FieldSignature, MemberNaming> fieldMembers; + + private ClassNamingForNameMapper( + String renamedName, + String originalName, + Map<MethodSignature, MemberNaming> methodMembers, + Map<FieldSignature, MemberNaming> fieldMembers) { + this.renamedName = renamedName; + this.originalName = originalName; + this.methodMembers = ImmutableMap.copyOf(methodMembers); + this.fieldMembers = ImmutableMap.copyOf(fieldMembers); + } + + @Override + public MemberNaming lookup(Signature renamedSignature) { + if (renamedSignature.kind() == SignatureKind.METHOD) { + return methodMembers.get(renamedSignature); + } else { + assert renamedSignature.kind() == SignatureKind.FIELD; + return fieldMembers.get(renamedSignature); + } + } + + @Override + public MemberNaming lookupByOriginalSignature(Signature original) { + if (original.kind() == SignatureKind.METHOD) { + for (MemberNaming memberNaming: methodMembers.values()) { + if (memberNaming.signature.equals(original)) { + return memberNaming; + } + } + return null; + } else { + assert original.kind() == SignatureKind.FIELD; + for (MemberNaming memberNaming : fieldMembers.values()) { + if (memberNaming.signature.equals(original)) { + return memberNaming; + } + } + return null; + } + } + + public List<MemberNaming> lookupByOriginalName(String originalName) { + List<MemberNaming> result = new ArrayList<>(); + for (MemberNaming naming : methodMembers.values()) { + if (naming.signature.name.equals(originalName)) { + result.add(naming); + } + } + for (MemberNaming naming : fieldMembers.values()) { + if (naming.signature.name.equals(originalName)) { + result.add(naming); + } + } + return result; + } + + @Override + public <T extends Throwable> void forAllMemberNaming( + ThrowingConsumer<MemberNaming, T> consumer) throws T { + forAllFieldNaming(consumer); + forAllMethodNaming(consumer); + } + + @Override + public <T extends Throwable> void forAllFieldNaming( + ThrowingConsumer<MemberNaming, T> consumer) throws T { + for (MemberNaming naming : fieldMembers.values()) { + consumer.accept(naming); + } + } + + @Override + public <T extends Throwable> void forAllMethodNaming( + ThrowingConsumer<MemberNaming, T> consumer) throws T { + for (MemberNaming naming : methodMembers.values()) { + consumer.accept(naming); + } + } + + void write(Writer writer, boolean collapseRanges) throws IOException { + writer.append(originalName); + writer.append(" -> "); + writer.append(renamedName); + writer.append(":\n"); + forAllMemberNaming(memberNaming -> memberNaming.write(writer, collapseRanges, true)); + } + + @Override + public String toString() { + try { + StringWriter writer = new StringWriter(); + write(writer, false); + return writer.toString(); + } catch (IOException e) { + return e.toString(); + } + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof ClassNamingForNameMapper)) { + return false; + } + + ClassNamingForNameMapper that = (ClassNamingForNameMapper) o; + + return originalName.equals(that.originalName) + && renamedName.equals(that.renamedName) + && methodMembers.equals(that.methodMembers) + && fieldMembers.equals(that.fieldMembers); + } + + @Override + public int hashCode() { + int result = originalName.hashCode(); + result = 31 * result + renamedName.hashCode(); + result = 31 * result + methodMembers.hashCode(); + result = 31 * result + fieldMembers.hashCode(); + return result; + } +} + diff --git a/src/main/java/com/android/tools/r8/naming/MemberNaming.java b/src/main/java/com/android/tools/r8/naming/MemberNaming.java index 5f2ea9391..c494c0881 100644 --- a/src/main/java/com/android/tools/r8/naming/MemberNaming.java +++ b/src/main/java/com/android/tools/r8/naming/MemberNaming.java @@ -3,8 +3,11 @@ // BSD-style license that can be found in the LICENSE file. package com.android.tools.r8.naming; +import static com.android.tools.r8.utils.DescriptorUtils.javaTypeToDescriptor; + import com.android.tools.r8.dex.Constants; import com.android.tools.r8.graph.DexField; +import com.android.tools.r8.graph.DexItemFactory; import com.android.tools.r8.graph.DexMethod; import com.android.tools.r8.graph.DexType; import com.android.tools.r8.naming.MemberNaming.Signature.SignatureKind; @@ -90,6 +93,14 @@ public class MemberNaming { return signature; } + public String getOriginalName() { + return signature.name; + } + + public Signature getRenamedSignature() { + return renamedSignature; + } + public String getRenamedName() { return renamedSignature.name; } @@ -222,6 +233,13 @@ public class MemberNaming { field.type.toSourceString()); } + DexField toDexField(DexItemFactory factory, DexType clazz) { + return factory.createField( + clazz, + factory.createType(javaTypeToDescriptor(type)), + factory.createString(name)); + } + @Override Signature asRenamed(String renamedName) { return new FieldSignature(renamedName, type); @@ -297,6 +315,18 @@ public class MemberNaming { parameterTypes); } + DexMethod toDexMethod(DexItemFactory factory, DexType clazz) { + DexType[] paramTypes = new DexType[parameters.length]; + for (int i = 0; i < parameters.length; i++) { + paramTypes[i] = factory.createType(javaTypeToDescriptor(parameters[i])); + } + DexType returnType = factory.createType(javaTypeToDescriptor(type)); + return factory.createMethod( + clazz, + factory.createProto(returnType, paramTypes), + factory.createString(name)); + } + public static MethodSignature initializer(String[] parameters) { return new MethodSignature(Constants.INSTANCE_INITIALIZER_NAME, "void", parameters); } diff --git a/src/main/java/com/android/tools/r8/naming/MinifiedNameMapPrinter.java b/src/main/java/com/android/tools/r8/naming/MinifiedNameMapPrinter.java index e9ec08c3c..0e971c2e5 100644 --- a/src/main/java/com/android/tools/r8/naming/MinifiedNameMapPrinter.java +++ b/src/main/java/com/android/tools/r8/naming/MinifiedNameMapPrinter.java @@ -43,24 +43,24 @@ public class MinifiedNameMapPrinter { return copy; } - private void write(DexProgramClass clazz, PrintStream out) { + private void writeClass(DexProgramClass clazz, PrintStream out) { seenTypes.add(clazz.type); DexString descriptor = namingLens.lookupDescriptor(clazz.type); out.print(DescriptorUtils.descriptorToJavaType(clazz.type.descriptor.toSourceString())); out.print(" -> "); out.print(DescriptorUtils.descriptorToJavaType(descriptor.toSourceString())); out.println(":"); - write(sortedCopy( + writeFields(sortedCopy( clazz.instanceFields(), Comparator.comparing(DexEncodedField::toSourceString)), out); - write(sortedCopy( + writeFields(sortedCopy( clazz.staticFields(), Comparator.comparing(DexEncodedField::toSourceString)), out); - write(sortedCopy( + writeMethods(sortedCopy( clazz.directMethods(), Comparator.comparing(DexEncodedMethod::toSourceString)), out); - write(sortedCopy( + writeMethods(sortedCopy( clazz.virtualMethods(), Comparator.comparing(DexEncodedMethod::toSourceString)), out); } - private void write(DexType type, PrintStream out) { + private void writeType(DexType type, PrintStream out) { if (type.isClassType() && seenTypes.add(type)) { DexString descriptor = namingLens.lookupDescriptor(type); out.print(DescriptorUtils.descriptorToJavaType(type.descriptor.toSourceString())); @@ -70,7 +70,7 @@ public class MinifiedNameMapPrinter { } } - private void write(DexEncodedField[] fields, PrintStream out) { + private void writeFields(DexEncodedField[] fields, PrintStream out) { for (DexEncodedField encodedField : fields) { DexField field = encodedField.field; DexString renamed = namingLens.lookupName(field); @@ -102,7 +102,7 @@ public class MinifiedNameMapPrinter { out.println(renamed); } - private void write(DexEncodedMethod[] methods, PrintStream out) { + private void writeMethods(DexEncodedMethod[] methods, PrintStream out) { for (DexEncodedMethod encodedMethod : methods) { DexMethod method = encodedMethod.method; DexString renamed = namingLens.lookupName(method); @@ -125,9 +125,9 @@ public class MinifiedNameMapPrinter { // First write out all classes that have been renamed. List<DexProgramClass> classes = new ArrayList<>(application.classes()); classes.sort(Comparator.comparing(DexProgramClass::toSourceString)); - classes.forEach(clazz -> write(clazz, out)); + classes.forEach(clazz -> writeClass(clazz, out)); // Now write out all types only mentioned in descriptors that have been renamed. - namingLens.forAllRenamedTypes(type -> write(type, out)); + namingLens.forAllRenamedTypes(type -> writeType(type, out)); } public void write(Path destination) throws IOException { 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/optimize/MemberRebindingAnalysis.java b/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java index 17da59b6c..71d6e468d 100644 --- a/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java +++ b/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java @@ -219,6 +219,6 @@ public class MemberRebindingAnalysis { appInfo::lookupStaticTarget, DexClass::findStaticTarget); computeFieldRebinding(Sets.union(appInfo.instanceFieldReads, appInfo.instanceFieldWrites), appInfo::lookupInstanceTarget, DexClass::findInstanceTarget); - return builder.build(lense, appInfo.dexItemFactory); + return builder.build(appInfo.dexItemFactory, lense); } } 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 a24bdb9b7..3f64afdaf 100644 --- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java +++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java @@ -256,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/shaking/ProguardConfigurationSourceStrings.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationSourceStrings.java index 44b3393f0..cb69320e8 100644 --- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationSourceStrings.java +++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationSourceStrings.java @@ -12,6 +12,7 @@ import java.util.List; import joptsimple.internal.Strings; public class ProguardConfigurationSourceStrings implements ProguardConfigurationSource { + private final Path basePath; private final List<String> config; @@ -20,13 +21,13 @@ public class ProguardConfigurationSourceStrings implements ProguardConfiguration * {@param basePath}, which allows all other options that use a relative path to reach out * to desired paths appropriately. */ - public ProguardConfigurationSourceStrings(Path basePath, List<String> config) { + public ProguardConfigurationSourceStrings(List<String> config, Path basePath) { this.basePath = basePath; this.config = config; } private ProguardConfigurationSourceStrings(List<String> config) { - this(Paths.get("."), config); + this(config, Paths.get(".")); } @VisibleForTesting @@ -36,7 +37,7 @@ public class ProguardConfigurationSourceStrings implements ProguardConfiguration } @Override - public String get() throws IOException{ + public String get() throws IOException { return Strings.join(config, System.lineSeparator()); } diff --git a/src/main/java/com/android/tools/r8/shaking/SimpleClassMerger.java b/src/main/java/com/android/tools/r8/shaking/SimpleClassMerger.java index 9affabf24..b50d78ea4 100644 --- a/src/main/java/com/android/tools/r8/shaking/SimpleClassMerger.java +++ b/src/main/java/com/android/tools/r8/shaking/SimpleClassMerger.java @@ -181,7 +181,7 @@ public class SimpleClassMerger { if (Log.ENABLED) { Log.debug(getClass(), "Merged %d classes.", numberOfMerges); } - return renamedMembersLense.build(graphLense, application.dexItemFactory); + return renamedMembersLense.build(application.dexItemFactory, graphLense); } private class ClassMerger { @@ -438,7 +438,7 @@ public class SimpleClassMerger { DexType fixed = fixupType(type); lense.map(type, fixed); } - return lense.build(graphLense, application.dexItemFactory); + return lense.build(application.dexItemFactory, graphLense); } private DexEncodedMethod[] removeDupes(DexEncodedMethod[] methods) { 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 24f5c08b2..67aa28822 100644 --- a/src/main/java/com/android/tools/r8/utils/AndroidApp.java +++ b/src/main/java/com/android/tools/r8/utils/AndroidApp.java @@ -596,11 +596,11 @@ public class AndroidApp { /** * Add Java-bytecode program data. */ - public Builder addClassProgramData(Origin origin, byte[] data) { + public Builder addClassProgramData(byte[] data, Origin origin) { return addProgramResources(Kind.CLASS, Resource.fromBytes(origin, data)); } - public Builder addClassProgramData(Origin origin, byte[] data, Set<String> classDescriptors) { + public Builder addClassProgramData(byte[] data, Origin origin, Set<String> classDescriptors) { return addProgramResources(Kind.CLASS, Resource.fromBytes(origin, data, classDescriptors)); } @@ -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/AndroidAppOutputSink.java b/src/main/java/com/android/tools/r8/utils/AndroidAppOutputSink.java index a4f223663..0f85cacec 100644 --- a/src/main/java/com/android/tools/r8/utils/AndroidAppOutputSink.java +++ b/src/main/java/com/android/tools/r8/utils/AndroidAppOutputSink.java @@ -92,7 +92,7 @@ public class AndroidAppOutputSink extends ForwardingOutputSink { } else if (!classFiles.isEmpty()) { assert dexFilesWithPrimary.isEmpty() && dexFilesWithId.isEmpty(); classFiles.forEach( - d -> builder.addClassProgramData(Origin.unknown(), d.contents, d.descriptors)); + d -> builder.addClassProgramData(d.contents, Origin.unknown(), d.descriptors)); } closed = true; super.close(); 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/main/java/com/android/tools/r8/utils/InternalOptions.java b/src/main/java/com/android/tools/r8/utils/InternalOptions.java index a04e28fe5..1c0cc61bb 100644 --- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java +++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java @@ -125,6 +125,7 @@ public class InternalOptions { public boolean minimalMainDex; public static class InvalidParameterAnnotationInfo { + final DexMethod method; final int expectedParameterCount; final int actualParameterCount; @@ -190,7 +191,7 @@ public class InternalOptions { .append(" actual count: ") .append(info.actualParameterCount); } - diagnosticsHandler.info(new StringDiagnostic(origin, builder.toString())); + diagnosticsHandler.info(new StringDiagnostic(builder.toString(), origin)); } printed = true; } @@ -209,7 +210,7 @@ public class InternalOptions { for (DexEncodedMethod method : warningInvalidDebugInfo.get(origin)) { builder.append("\n ").append(method.toSourceString()); } - diagnosticsHandler.info(new StringDiagnostic(origin, builder.toString())); + diagnosticsHandler.info(new StringDiagnostic(builder.toString(), origin)); } printed = true; printOutdatedToolchain = true; diff --git a/src/main/java/com/android/tools/r8/utils/StringDiagnostic.java b/src/main/java/com/android/tools/r8/utils/StringDiagnostic.java index b2add4aaa..d2c6560a5 100644 --- a/src/main/java/com/android/tools/r8/utils/StringDiagnostic.java +++ b/src/main/java/com/android/tools/r8/utils/StringDiagnostic.java @@ -7,14 +7,15 @@ import com.android.tools.r8.Diagnostic; import com.android.tools.r8.Resource.Origin; public class StringDiagnostic implements Diagnostic { + private final Origin origin; private final String message; public StringDiagnostic(String message) { - this(Origin.unknown(), message); + this(message, Origin.unknown()); } - public StringDiagnostic(Origin origin, String message) { + public StringDiagnostic(String message, Origin origin) { this.origin = origin; this.message = message; } diff --git a/src/main/java/com/android/tools/r8/utils/ZipUtils.java b/src/main/java/com/android/tools/r8/utils/ZipUtils.java index ca9a40433..76d84c2fa 100644 --- a/src/main/java/com/android/tools/r8/utils/ZipUtils.java +++ b/src/main/java/com/android/tools/r8/utils/ZipUtils.java @@ -9,6 +9,7 @@ import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; +import java.io.OutputStream; import java.nio.file.Path; import java.util.ArrayList; import java.util.Enumeration; @@ -53,8 +54,9 @@ public class ZipUtils { Path outPath = outDirectoryPath.resolve(name); File outFile = outPath.toFile(); outFile.getParentFile().mkdirs(); - FileOutputStream output = new FileOutputStream(outFile); - ByteStreams.copy(input, output); + try (OutputStream output = new FileOutputStream(outFile)) { + ByteStreams.copy(input, output); + } outFiles.add(outFile); } }); 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/examplesAndroidO/invokecustom/TestGenerator.java b/src/test/examplesAndroidO/invokecustom/TestGenerator.java index 574fe1431..e42b9c9c9 100644 --- a/src/test/examplesAndroidO/invokecustom/TestGenerator.java +++ b/src/test/examplesAndroidO/invokecustom/TestGenerator.java @@ -3,13 +3,14 @@ // BSD-style license that can be found in the LICENSE file. package invokecustom; -import java.io.FileInputStream; -import java.io.FileOutputStream; import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; import java.lang.invoke.CallSite; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodType; +import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import org.objectweb.asm.ClassReader; @@ -36,30 +37,34 @@ public class TestGenerator { } private void generateTests() throws IOException { - ClassReader cr = new ClassReader(new FileInputStream(classNamePath.toFile())); - ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES); - cr.accept( - new ClassVisitor(Opcodes.ASM6, cw) { - @Override - public void visitEnd() { - generateMethodTest1(cw); - generateMethodTest2(cw); - generateMethodTest3(cw); - generateMethodTest4(cw); - generateMethodTest5(cw); - generateMethodTest6(cw); - generateMethodTest7(cw); - generateMethodTest8(cw); - generateMethodTest9(cw); - generateMethodTest10(cw); - generateMethodTest11(cw); - generateMethodTest12(cw); - generateMethodTest13(cw); - generateMethodMain(cw); - super.visitEnd(); - } - }, 0); - new FileOutputStream(classNamePath.toFile()).write(cw.toByteArray()); + try (InputStream input = Files.newInputStream(classNamePath)) { + ClassReader cr = new ClassReader(input); + ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES); + cr.accept( + new ClassVisitor(Opcodes.ASM6, cw) { + @Override + public void visitEnd() { + generateMethodTest1(cw); + generateMethodTest2(cw); + generateMethodTest3(cw); + generateMethodTest4(cw); + generateMethodTest5(cw); + generateMethodTest6(cw); + generateMethodTest7(cw); + generateMethodTest8(cw); + generateMethodTest9(cw); + generateMethodTest10(cw); + generateMethodTest11(cw); + generateMethodTest12(cw); + generateMethodTest13(cw); + generateMethodMain(cw); + super.visitEnd(); + } + }, 0); + try (OutputStream output = Files.newOutputStream(classNamePath)) { + output.write(cw.toByteArray()); + } + } } /* generate main method that only call all test methods. */ diff --git a/src/test/examplesAndroidO/invokecustom2/TestGenerator.java b/src/test/examplesAndroidO/invokecustom2/TestGenerator.java index ab77d32a1..9bbc03a59 100644 --- a/src/test/examplesAndroidO/invokecustom2/TestGenerator.java +++ b/src/test/examplesAndroidO/invokecustom2/TestGenerator.java @@ -4,13 +4,14 @@ package invokecustom2; -import java.io.FileInputStream; -import java.io.FileOutputStream; import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; import java.lang.invoke.CallSite; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodType; +import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import org.objectweb.asm.ClassReader; @@ -37,26 +38,30 @@ public class TestGenerator { } private void generateTests() throws IOException { - ClassReader cr = new ClassReader(new FileInputStream(classNamePath.toFile())); - ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES); - cr.accept( - new ClassVisitor(Opcodes.ASM6, cw) { - @Override - public void visitEnd() { - generateMethodTest1(cw); - generateMethodTest2(cw); - generateMethodTest3(cw); - generateMethodTest4(cw); - generateMethodTest5(cw); - generateMethodTest6(cw); - generateMethodTest7(cw); - generateMethodTest8(cw); - generateMethodTest9(cw); - generateMethodMain(cw); - super.visitEnd(); - } - }, 0); - new FileOutputStream(classNamePath.toFile()).write(cw.toByteArray()); + try (InputStream input = Files.newInputStream(classNamePath)) { + ClassReader cr = new ClassReader(input); + ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES); + cr.accept( + new ClassVisitor(Opcodes.ASM6, cw) { + @Override + public void visitEnd() { + generateMethodTest1(cw); + generateMethodTest2(cw); + generateMethodTest3(cw); + generateMethodTest4(cw); + generateMethodTest5(cw); + generateMethodTest6(cw); + generateMethodTest7(cw); + generateMethodTest8(cw); + generateMethodTest9(cw); + generateMethodMain(cw); + super.visitEnd(); + } + }, 0); + try (OutputStream output = Files.newOutputStream(classNamePath)) { + output.write(cw.toByteArray()); + } + } } /* generate main method that only call all test methods. */ diff --git a/src/test/examplesAndroidO/stringconcat/TestGenerator.java b/src/test/examplesAndroidO/stringconcat/TestGenerator.java index 6a837a4f4..e72f43813 100644 --- a/src/test/examplesAndroidO/stringconcat/TestGenerator.java +++ b/src/test/examplesAndroidO/stringconcat/TestGenerator.java @@ -3,12 +3,13 @@ // BSD-style license that can be found in the LICENSE file. package stringconcat; -import java.io.FileInputStream; -import java.io.FileOutputStream; import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; import java.lang.invoke.CallSite; import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodType; +import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; @@ -46,177 +47,181 @@ public class TestGenerator { } private static void generateTests(Path classNamePath) throws IOException { - ClassReader cr = new ClassReader(new FileInputStream(classNamePath.toFile())); - ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES); - cr.accept( - new ClassVisitor(Opcodes.ASM6, cw) { - @Override - public MethodVisitor visitMethod(int access, - final String methodName, String desc, String signature, String[] exceptions) { - MethodVisitor mv = super.visitMethod(access, methodName, desc, signature, exceptions); - return new MethodVisitor(Opcodes.ASM6, mv) { - private List<Object> recentConstants = new ArrayList<>(); - - @Override - public void visitLdcInsn(Object cst) { - if (!recentConstants.isEmpty() || - (cst instanceof String && ((String) cst).startsWith(RECIPE_PREFIX))) { - // Add the constant, don't push anything on stack. - recentConstants.add(cst); - return; + try (InputStream input = Files.newInputStream(classNamePath)) { + ClassReader cr = new ClassReader(input); + ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES); + cr.accept( + new ClassVisitor(Opcodes.ASM6, cw) { + @Override + public MethodVisitor visitMethod(int access, + final String methodName, String desc, String signature, String[] exceptions) { + MethodVisitor mv = super.visitMethod(access, methodName, desc, signature, exceptions); + return new MethodVisitor(Opcodes.ASM6, mv) { + private List<Object> recentConstants = new ArrayList<>(); + + @Override + public void visitLdcInsn(Object cst) { + if (!recentConstants.isEmpty() || + (cst instanceof String && ((String) cst).startsWith(RECIPE_PREFIX))) { + // Add the constant, don't push anything on stack. + recentConstants.add(cst); + return; + } + super.visitLdcInsn(cst); } - super.visitLdcInsn(cst); - } - - @Override - public void visitMethodInsn( - int opcode, String owner, String name, String desc, boolean itf) { - // Replace calls to 'makeConcat(...)' with appropriate `invokedynamic`. - if (opcode == Opcodes.INVOKESTATIC && name.equals("makeConcat")) { - mv.visitInvokeDynamicInsn(MAKE_CONCAT.getName(), desc, MAKE_CONCAT); - recentConstants.clear(); - return; + + @Override + public void visitMethodInsn( + int opcode, String owner, String name, String desc, boolean itf) { + // Replace calls to 'makeConcat(...)' with appropriate `invokedynamic`. + if (opcode == Opcodes.INVOKESTATIC && name.equals("makeConcat")) { + mv.visitInvokeDynamicInsn(MAKE_CONCAT.getName(), desc, MAKE_CONCAT); + recentConstants.clear(); + return; + } + + // Replace calls to 'makeConcat(...)' with appropriate `invokedynamic`. + if (opcode == Opcodes.INVOKESTATIC && name.equals("makeConcatWithConstants")) { + if (recentConstants.isEmpty()) { + throw new AssertionError("No constants detected in `" + + methodName + "`: call to " + name + desc); + } + recentConstants.set(0, + ((String) recentConstants.get(0)).substring(RECIPE_PREFIX.length())); + + mv.visitInvokeDynamicInsn(MAKE_CONCAT_WITH_CONSTANTS.getName(), + removeLastParams(desc, recentConstants.size()), MAKE_CONCAT_WITH_CONSTANTS, + recentConstants.toArray(new Object[recentConstants.size()])); + recentConstants.clear(); + return; + } + + // Otherwise fall back to default implementation. + super.visitMethodInsn(opcode, owner, name, desc, itf); } - // Replace calls to 'makeConcat(...)' with appropriate `invokedynamic`. - if (opcode == Opcodes.INVOKESTATIC && name.equals("makeConcatWithConstants")) { - if (recentConstants.isEmpty()) { - throw new AssertionError("No constants detected in `" + - methodName + "`: call to " + name + desc); + private String removeLastParams(String descr, int paramsToRemove) { + MethodType methodType = + MethodType.fromMethodDescriptorString( + descr, this.getClass().getClassLoader()); + return methodType + .dropParameterTypes( + methodType.parameterCount() - paramsToRemove, + methodType.parameterCount()) + .toMethodDescriptorString(); + } + + @Override + public void visitInsn(int opcode) { + switch (opcode) { + case Opcodes.ICONST_0: + if (!recentConstants.isEmpty()) { + recentConstants.add(0); + return; + } + break; + case Opcodes.ICONST_1: + if (!recentConstants.isEmpty()) { + recentConstants.add(1); + return; + } + break; + case Opcodes.ICONST_2: + if (!recentConstants.isEmpty()) { + recentConstants.add(2); + return; + } + break; + case Opcodes.ICONST_3: + if (!recentConstants.isEmpty()) { + recentConstants.add(3); + return; + } + break; + case Opcodes.ICONST_4: + if (!recentConstants.isEmpty()) { + recentConstants.add(4); + return; + } + break; + case Opcodes.ICONST_5: + if (!recentConstants.isEmpty()) { + recentConstants.add(5); + return; + } + break; + case Opcodes.ICONST_M1: + if (!recentConstants.isEmpty()) { + recentConstants.add(-1); + return; + } + break; + default: + recentConstants.clear(); + break; } - recentConstants.set(0, - ((String) recentConstants.get(0)).substring(RECIPE_PREFIX.length())); + super.visitInsn(opcode); + } - mv.visitInvokeDynamicInsn(MAKE_CONCAT_WITH_CONSTANTS.getName(), - removeLastParams(desc, recentConstants.size()), MAKE_CONCAT_WITH_CONSTANTS, - recentConstants.toArray(new Object[recentConstants.size()])); + @Override + public void visitIntInsn(int opcode, int operand) { recentConstants.clear(); - return; + super.visitIntInsn(opcode, operand); } - // Otherwise fall back to default implementation. - super.visitMethodInsn(opcode, owner, name, desc, itf); - } - - private String removeLastParams(String descr, int paramsToRemove) { - MethodType methodType = - MethodType.fromMethodDescriptorString( - descr, this.getClass().getClassLoader()); - return methodType - .dropParameterTypes( - methodType.parameterCount() - paramsToRemove, - methodType.parameterCount()) - .toMethodDescriptorString(); - } - - @Override - public void visitInsn(int opcode) { - switch (opcode) { - case Opcodes.ICONST_0: - if (!recentConstants.isEmpty()) { - recentConstants.add(0); - return; - } - break; - case Opcodes.ICONST_1: - if (!recentConstants.isEmpty()) { - recentConstants.add(1); - return; - } - break; - case Opcodes.ICONST_2: - if (!recentConstants.isEmpty()) { - recentConstants.add(2); - return; - } - break; - case Opcodes.ICONST_3: - if (!recentConstants.isEmpty()) { - recentConstants.add(3); - return; - } - break; - case Opcodes.ICONST_4: - if (!recentConstants.isEmpty()) { - recentConstants.add(4); - return; - } - break; - case Opcodes.ICONST_5: - if (!recentConstants.isEmpty()) { - recentConstants.add(5); - return; - } - break; - case Opcodes.ICONST_M1: - if (!recentConstants.isEmpty()) { - recentConstants.add(-1); - return; - } - break; - default: - recentConstants.clear(); - break; + @Override + public void visitVarInsn(int opcode, int var) { + recentConstants.clear(); + super.visitVarInsn(opcode, var); + } + + @Override + public void visitTypeInsn(int opcode, String type) { + recentConstants.clear(); + super.visitTypeInsn(opcode, type); + } + + @Override + public void visitFieldInsn(int opcode, String owner, String name, String desc) { + recentConstants.clear(); + super.visitFieldInsn(opcode, owner, name, desc); + } + + @Override + public void visitJumpInsn(int opcode, Label label) { + recentConstants.clear(); + super.visitJumpInsn(opcode, label); + } + + @Override + public void visitIincInsn(int var, int increment) { + recentConstants.clear(); + super.visitIincInsn(var, increment); + } + + @Override + public void visitTableSwitchInsn(int min, int max, Label dflt, Label... labels) { + recentConstants.clear(); + super.visitTableSwitchInsn(min, max, dflt, labels); + } + + @Override + public void visitLookupSwitchInsn(Label dflt, int[] keys, Label[] labels) { + recentConstants.clear(); + super.visitLookupSwitchInsn(dflt, keys, labels); + } + + @Override + public void visitMultiANewArrayInsn(String desc, int dims) { + recentConstants.clear(); + super.visitMultiANewArrayInsn(desc, dims); } - super.visitInsn(opcode); - } - - @Override - public void visitIntInsn(int opcode, int operand) { - recentConstants.clear(); - super.visitIntInsn(opcode, operand); - } - - @Override - public void visitVarInsn(int opcode, int var) { - recentConstants.clear(); - super.visitVarInsn(opcode, var); - } - - @Override - public void visitTypeInsn(int opcode, String type) { - recentConstants.clear(); - super.visitTypeInsn(opcode, type); - } - - @Override - public void visitFieldInsn(int opcode, String owner, String name, String desc) { - recentConstants.clear(); - super.visitFieldInsn(opcode, owner, name, desc); - } - - @Override - public void visitJumpInsn(int opcode, Label label) { - recentConstants.clear(); - super.visitJumpInsn(opcode, label); - } - - @Override - public void visitIincInsn(int var, int increment) { - recentConstants.clear(); - super.visitIincInsn(var, increment); - } - - @Override - public void visitTableSwitchInsn(int min, int max, Label dflt, Label... labels) { - recentConstants.clear(); - super.visitTableSwitchInsn(min, max, dflt, labels); - } - - @Override - public void visitLookupSwitchInsn(Label dflt, int[] keys, Label[] labels) { - recentConstants.clear(); - super.visitLookupSwitchInsn(dflt, keys, labels); - } - - @Override - public void visitMultiANewArrayInsn(String desc, int dims) { - recentConstants.clear(); - super.visitMultiANewArrayInsn(desc, dims); - } - }; - } - }, 0); - new FileOutputStream(classNamePath.toFile()).write(cw.toByteArray()); + }; + } + }, 0); + try (OutputStream output = Files.newOutputStream(classNamePath)) { + output.write(cw.toByteArray()); + } + } } } diff --git a/src/test/examplesAndroidP/invokecustom/TestGenerator.java b/src/test/examplesAndroidP/invokecustom/TestGenerator.java index 375dc3413..7ee4bdc0b 100644 --- a/src/test/examplesAndroidP/invokecustom/TestGenerator.java +++ b/src/test/examplesAndroidP/invokecustom/TestGenerator.java @@ -3,13 +3,14 @@ // BSD-style license that can be found in the LICENSE file. package invokecustom; -import java.io.FileInputStream; -import java.io.FileOutputStream; + import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; import java.lang.invoke.CallSite; -import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodType; +import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import org.objectweb.asm.ClassReader; @@ -36,19 +37,23 @@ public class TestGenerator { } private void generateTests() throws IOException { - ClassReader cr = new ClassReader(new FileInputStream(classNamePath.toFile())); - ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES); - cr.accept( - new ClassVisitor(Opcodes.ASM6, cw) { - @Override - public void visitEnd() { - generateMethodTest1(cw); - generateMethodTest2(cw); - generateMethodMain(cw); - super.visitEnd(); - } - }, 0); - new FileOutputStream(classNamePath.toFile()).write(cw.toByteArray()); + try (InputStream inputStream = Files.newInputStream(classNamePath)) { + ClassReader cr = new ClassReader(inputStream); + ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES); + cr.accept( + new ClassVisitor(Opcodes.ASM6, cw) { + @Override + public void visitEnd() { + generateMethodTest1(cw); + generateMethodTest2(cw); + generateMethodMain(cw); + super.visitEnd(); + } + }, 0); + try (OutputStream output = Files.newOutputStream(classNamePath)) { + output.write(cw.toByteArray()); + } + } } /* Generate main method that only call all test methods. */ 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/debug/JasminDebugTest.java b/src/test/java/com/android/tools/r8/debug/JasminDebugTest.java index 2b0084d55..ddcbf8cf5 100644 --- a/src/test/java/com/android/tools/r8/debug/JasminDebugTest.java +++ b/src/test/java/com/android/tools/r8/debug/JasminDebugTest.java @@ -9,7 +9,7 @@ import com.android.tools.r8.jasmin.JasminBuilder.ClassBuilder; import com.google.common.collect.ImmutableList; import jasmin.ClassFile; import java.io.File; -import java.io.FileOutputStream; +import java.io.OutputStream; import java.io.StringReader; import java.nio.file.Files; import java.nio.file.Path; @@ -88,7 +88,9 @@ public class JasminDebugTest extends DebugTestBase { file.readJasmin(new StringReader(clazz.toString()), clazz.name, false); Path path = out.toPath().resolve(clazz.name + ".class"); Files.createDirectories(path.getParent()); - file.write(new FileOutputStream(path.toFile())); + try (OutputStream outputStream = Files.newOutputStream(path)) { + file.write(outputStream); + } if (isRunningJava()) { extraPaths.add(path); } else { diff --git a/src/test/java/com/android/tools/r8/ir/BasicBlockIteratorTest.java b/src/test/java/com/android/tools/r8/ir/BasicBlockIteratorTest.java index cf191de53..ad9a26ae5 100644 --- a/src/test/java/com/android/tools/r8/ir/BasicBlockIteratorTest.java +++ b/src/test/java/com/android/tools/r8/ir/BasicBlockIteratorTest.java @@ -4,7 +4,6 @@ package com.android.tools.r8.ir; -import com.android.tools.r8.CompilationException; import com.android.tools.r8.graph.DexApplication; import com.android.tools.r8.graph.DexEncodedMethod; import com.android.tools.r8.ir.code.BasicBlock; @@ -52,12 +51,12 @@ public class BasicBlockIteratorTest extends SmaliTestBase { // Build the code, and split the code into three blocks. ValueNumberGenerator valueNumberGenerator = new ValueNumberGenerator(); DexEncodedMethod method = getMethod(application, signature); - IRCode code = method.buildIR(valueNumberGenerator, new InternalOptions()); + IRCode code = method.buildIR(new InternalOptions(), valueNumberGenerator); ListIterator<BasicBlock> blocks = code.listIterator(); InstructionListIterator iter = blocks.next().listIterator(); iter.nextUntil(i -> !i.isArgument()); iter.previous(); - iter.split(1, code, blocks); + iter.split(code, 1, blocks); return code; } diff --git a/src/test/java/com/android/tools/r8/ir/InlineTest.java b/src/test/java/com/android/tools/r8/ir/InlineTest.java index 825832416..27a3ce979 100644 --- a/src/test/java/com/android/tools/r8/ir/InlineTest.java +++ b/src/test/java/com/android/tools/r8/ir/InlineTest.java @@ -76,13 +76,13 @@ public class InlineTest extends SmaliTestBase { // Return the processed method for inspection. ValueNumberGenerator valueNumberGenerator = new ValueNumberGenerator(); DexEncodedMethod method = getMethod(application, signature); - IRCode code = method.buildIR(valueNumberGenerator, new InternalOptions()); + IRCode code = method.buildIR(new InternalOptions(), valueNumberGenerator); DexEncodedMethod methodA = getMethod(application, signatureA); - IRCode codeA = methodA.buildIR(valueNumberGenerator, new InternalOptions()); + IRCode codeA = methodA.buildIR(new InternalOptions(), valueNumberGenerator); DexEncodedMethod methodB = getMethod(application, signatureB); - IRCode codeB = methodB.buildIR(valueNumberGenerator, new InternalOptions()); + IRCode codeB = methodB.buildIR(new InternalOptions(), valueNumberGenerator); return new SmaliTestBase.TestApplication(application, method, code, ImmutableList.of(codeA, codeB), valueNumberGenerator, options); @@ -159,10 +159,10 @@ public class InlineTest extends SmaliTestBase { // Return the processed method for inspection. ValueNumberGenerator valueNumberGenerator = new ValueNumberGenerator(); DexEncodedMethod method = getMethod(application, signature); - IRCode code = method.buildIR(valueNumberGenerator, new InternalOptions()); + IRCode code = method.buildIR(new InternalOptions(), valueNumberGenerator); DexEncodedMethod methodA = getMethod(application, signatureA); - IRCode codeA = methodA.buildIR(valueNumberGenerator, new InternalOptions()); + IRCode codeA = methodA.buildIR(new InternalOptions(), valueNumberGenerator); return new TestApplication(application, method, code, ImmutableList.of(codeA), valueNumberGenerator, options); @@ -239,19 +239,19 @@ public class InlineTest extends SmaliTestBase { // Return the processed method for inspection. ValueNumberGenerator valueNumberGenerator = new ValueNumberGenerator(); DexEncodedMethod method = getMethod(application, signature); - IRCode code = method.buildIR(valueNumberGenerator, new InternalOptions()); + IRCode code = method.buildIR(new InternalOptions(), valueNumberGenerator); // Build three copies of a and b for inlining three times. List<IRCode> additionalCode = new ArrayList<>(); for (int i = 0; i < 3; i++) { DexEncodedMethod methodA = getMethod(application, signatureA); - IRCode codeA = methodA.buildIR(valueNumberGenerator, new InternalOptions()); + IRCode codeA = methodA.buildIR(new InternalOptions(), valueNumberGenerator); additionalCode.add(codeA); } for (int i = 0; i < 3; i++) { DexEncodedMethod methodB = getMethod(application, signatureB); - IRCode codeB = methodB.buildIR(valueNumberGenerator, new InternalOptions()); + IRCode codeB = methodB.buildIR(new InternalOptions(), valueNumberGenerator); additionalCode.add(codeB); } @@ -373,13 +373,13 @@ public class InlineTest extends SmaliTestBase { // Return the processed method for inspection. ValueNumberGenerator valueNumberGenerator = new ValueNumberGenerator(); DexEncodedMethod method = getMethod(application, signature); - IRCode code = method.buildIR(valueNumberGenerator, new InternalOptions()); + IRCode code = method.buildIR(new InternalOptions(), valueNumberGenerator); DexEncodedMethod methodA = getMethod(application, signatureA); - IRCode codeA = methodA.buildIR(valueNumberGenerator, new InternalOptions()); + IRCode codeA = methodA.buildIR(new InternalOptions(), valueNumberGenerator); DexEncodedMethod methodB = getMethod(application, signatureB); - IRCode codeB = methodB.buildIR(valueNumberGenerator, new InternalOptions()); + IRCode codeB = methodB.buildIR(new InternalOptions(), valueNumberGenerator); return new TestApplication(application, method, code, ImmutableList.of(codeA, codeB), valueNumberGenerator, options); @@ -487,13 +487,13 @@ public class InlineTest extends SmaliTestBase { // Return the processed method for inspection. ValueNumberGenerator valueNumberGenerator = new ValueNumberGenerator(); DexEncodedMethod method = getMethod(application, signature); - IRCode code = method.buildIR(valueNumberGenerator, new InternalOptions()); + IRCode code = method.buildIR(new InternalOptions(), valueNumberGenerator); DexEncodedMethod methodA = getMethod(application, signatureA); - IRCode codeA = methodA.buildIR(valueNumberGenerator, new InternalOptions()); + IRCode codeA = methodA.buildIR(new InternalOptions(), valueNumberGenerator); DexEncodedMethod methodB = getMethod(application, signatureB); - IRCode codeB = methodB.buildIR(valueNumberGenerator, new InternalOptions()); + IRCode codeB = methodB.buildIR(new InternalOptions(), valueNumberGenerator); return new TestApplication(application, method, code, ImmutableList.of(codeA, codeB), valueNumberGenerator, options); @@ -600,13 +600,13 @@ public class InlineTest extends SmaliTestBase { // Return the processed method for inspection. ValueNumberGenerator valueNumberGenerator = new ValueNumberGenerator(); DexEncodedMethod method = getMethod(application, signature); - IRCode code = method.buildIR(valueNumberGenerator, new InternalOptions()); + IRCode code = method.buildIR(new InternalOptions(), valueNumberGenerator); DexEncodedMethod methodA = getMethod(application, signatureA); - IRCode codeA = methodA.buildIR(valueNumberGenerator, new InternalOptions()); + IRCode codeA = methodA.buildIR(new InternalOptions(), valueNumberGenerator); DexEncodedMethod methodB = getMethod(application, signatureB); - IRCode codeB = methodB.buildIR(valueNumberGenerator, new InternalOptions()); + IRCode codeB = methodB.buildIR(new InternalOptions(), valueNumberGenerator); return new TestApplication(application, method, code, ImmutableList.of(codeA, codeB), valueNumberGenerator, options); @@ -714,19 +714,19 @@ public class InlineTest extends SmaliTestBase { // Return the processed method for inspection. ValueNumberGenerator valueNumberGenerator = new ValueNumberGenerator(); DexEncodedMethod method = getMethod(application, signature); - IRCode code = method.buildIR(valueNumberGenerator, new InternalOptions()); + IRCode code = method.buildIR(new InternalOptions(), valueNumberGenerator); // Build three copies of a and b for inlining three times. List<IRCode> additionalCode = new ArrayList<>(); for (int i = 0; i < 3; i++) { DexEncodedMethod methodA = getMethod(application, signatureA); - IRCode codeA = methodA.buildIR(valueNumberGenerator, new InternalOptions()); + IRCode codeA = methodA.buildIR(new InternalOptions(), valueNumberGenerator); additionalCode.add(codeA); } for (int i = 0; i < 3; i++) { DexEncodedMethod methodB = getMethod(application, signatureB); - IRCode codeB = methodB.buildIR(valueNumberGenerator, new InternalOptions()); + IRCode codeB = methodB.buildIR(new InternalOptions(), valueNumberGenerator); additionalCode.add(codeB); } @@ -871,19 +871,19 @@ public class InlineTest extends SmaliTestBase { // Return the processed method for inspection. ValueNumberGenerator valueNumberGenerator = new ValueNumberGenerator(); DexEncodedMethod method = getMethod(application, signature); - IRCode code = method.buildIR(valueNumberGenerator, new InternalOptions()); + IRCode code = method.buildIR(new InternalOptions(), valueNumberGenerator); // Build three copies of a and b for inlining three times. List<IRCode> additionalCode = new ArrayList<>(); for (int i = 0; i < 3; i++) { DexEncodedMethod methodA = getMethod(application, signatureA); - IRCode codeA = methodA.buildIR(valueNumberGenerator, new InternalOptions()); + IRCode codeA = methodA.buildIR(new InternalOptions(), valueNumberGenerator); additionalCode.add(codeA); } for (int i = 0; i < 3; i++) { DexEncodedMethod methodB = getMethod(application, signatureB); - IRCode codeB = methodB.buildIR(valueNumberGenerator, new InternalOptions()); + IRCode codeB = methodB.buildIR(new InternalOptions(), valueNumberGenerator); additionalCode.add(codeB); } @@ -1118,13 +1118,13 @@ public class InlineTest extends SmaliTestBase { // Return the processed method for inspection. ValueNumberGenerator valueNumberGenerator = new ValueNumberGenerator(); DexEncodedMethod method = getMethod(application, signature); - IRCode code = method.buildIR(valueNumberGenerator, new InternalOptions()); + IRCode code = method.buildIR(new InternalOptions(), valueNumberGenerator); DexEncodedMethod methodA = getMethod(application, signatureA); - IRCode codeA = methodA.buildIR(valueNumberGenerator, new InternalOptions()); + IRCode codeA = methodA.buildIR(new InternalOptions(), valueNumberGenerator); DexEncodedMethod methodB = getMethod(application, signatureB); - IRCode codeB = methodB.buildIR(valueNumberGenerator, new InternalOptions()); + IRCode codeB = methodB.buildIR(new InternalOptions(), valueNumberGenerator); return new TestApplication(application, method, code, ImmutableList.of(codeA, codeB), valueNumberGenerator, options); diff --git a/src/test/java/com/android/tools/r8/ir/InstructionIteratorTest.java b/src/test/java/com/android/tools/r8/ir/InstructionIteratorTest.java index b9eef9cf3..a688ffe21 100644 --- a/src/test/java/com/android/tools/r8/ir/InstructionIteratorTest.java +++ b/src/test/java/com/android/tools/r8/ir/InstructionIteratorTest.java @@ -52,12 +52,12 @@ public class InstructionIteratorTest extends SmaliTestBase { // Build the code, and split the code into three blocks. ValueNumberGenerator valueNumberGenerator = new ValueNumberGenerator(); DexEncodedMethod method = getMethod(application, signature); - IRCode code = method.buildIR(valueNumberGenerator, new InternalOptions()); + IRCode code = method.buildIR(new InternalOptions(), valueNumberGenerator); ListIterator<BasicBlock> blocks = code.listIterator(); InstructionListIterator iter = blocks.next().listIterator(); iter.nextUntil(i -> !i.isArgument()); iter.previous(); - iter.split(1, code, blocks); + iter.split(code, 1, blocks); return code; } diff --git a/src/test/java/com/android/tools/r8/ir/SplitBlockTest.java b/src/test/java/com/android/tools/r8/ir/SplitBlockTest.java index 735120621..9192b2fad 100644 --- a/src/test/java/com/android/tools/r8/ir/SplitBlockTest.java +++ b/src/test/java/com/android/tools/r8/ir/SplitBlockTest.java @@ -64,7 +64,7 @@ public class SplitBlockTest extends SmaliTestBase { // Return the processed method for inspection. ValueNumberGenerator valueNumberGenerator = new ValueNumberGenerator(); DexEncodedMethod method = getMethod(application, signature); - IRCode code = method.buildIR(valueNumberGenerator, new InternalOptions()); + IRCode code = method.buildIR(new InternalOptions(), valueNumberGenerator); return new TestApplication(application, method, code, valueNumberGenerator, options); } @@ -123,7 +123,7 @@ public class SplitBlockTest extends SmaliTestBase { assertTrue(!block.getInstructions().get(i).isArgument()); InstructionListIterator iterator = test.listIteratorAt(block, i); - BasicBlock newBlock = iterator.split(1, code); + BasicBlock newBlock = iterator.split(code, 1); assertTrue(code.isConsistentSSA()); assertEquals(initialBlockCount + 2, code.blocks.size()); @@ -182,7 +182,7 @@ public class SplitBlockTest extends SmaliTestBase { // Return the processed method for inspection. ValueNumberGenerator valueNumberGenerator = new ValueNumberGenerator(); DexEncodedMethod method = getMethod(application, signature); - IRCode code = method.buildIR(valueNumberGenerator, new InternalOptions()); + IRCode code = method.buildIR(new InternalOptions(), valueNumberGenerator); return new TestApplication(application, method, code, valueNumberGenerator, options); } @@ -247,7 +247,7 @@ public class SplitBlockTest extends SmaliTestBase { assertEquals(secondBlockInstructions, instructionCount); InstructionListIterator iterator = test.listIteratorAt(block, i); - BasicBlock newBlock = iterator.split(1, code); + BasicBlock newBlock = iterator.split(code, 1); assertTrue(code.isConsistentSSA()); assertEquals(initialBlockCount + 2, code.blocks.size()); @@ -307,7 +307,7 @@ public class SplitBlockTest extends SmaliTestBase { // Return the processed method for inspection. ValueNumberGenerator valueNumberGenerator = new ValueNumberGenerator(); DexEncodedMethod method = getMethod(application, signature); - IRCode code = method.buildIR(valueNumberGenerator, new InternalOptions()); + IRCode code = method.buildIR(new InternalOptions(), valueNumberGenerator); return new TestApplication(application, method, code, valueNumberGenerator, options); } @@ -431,7 +431,7 @@ public class SplitBlockTest extends SmaliTestBase { // Return the processed method for inspection. ValueNumberGenerator valueNumberGenerator = new ValueNumberGenerator(); DexEncodedMethod method = getMethod(application, signature); - IRCode code = method.buildIR(valueNumberGenerator, new InternalOptions()); + IRCode code = method.buildIR(new InternalOptions(), valueNumberGenerator); return new TestApplication(application, method, code, valueNumberGenerator, options); } diff --git a/src/test/java/com/android/tools/r8/ir/regalloc/RegisterMoveSchedulerTest.java b/src/test/java/com/android/tools/r8/ir/regalloc/RegisterMoveSchedulerTest.java index c8ea3bbfc..a1046dbe5 100644 --- a/src/test/java/com/android/tools/r8/ir/regalloc/RegisterMoveSchedulerTest.java +++ b/src/test/java/com/android/tools/r8/ir/regalloc/RegisterMoveSchedulerTest.java @@ -99,7 +99,8 @@ public class RegisterMoveSchedulerTest { } @Override - public BasicBlock split(int instructions, IRCode code, ListIterator<BasicBlock> blockIterator) { + public BasicBlock split(IRCode code, int instructions, + ListIterator<BasicBlock> blockIterator) { throw new Unimplemented(); } diff --git a/src/test/java/com/android/tools/r8/jasmin/JasminBuilder.java b/src/test/java/com/android/tools/r8/jasmin/JasminBuilder.java index 9baed4897..caada82c7 100644 --- a/src/test/java/com/android/tools/r8/jasmin/JasminBuilder.java +++ b/src/test/java/com/android/tools/r8/jasmin/JasminBuilder.java @@ -191,7 +191,7 @@ public class JasminBuilder { } }; builder.addClassProgramData( - origin, compile(clazz), Collections.singleton(clazz.getDescriptor())); + compile(clazz), origin, Collections.singleton(clazz.getDescriptor())); } return builder.build(); } diff --git a/src/test/java/com/android/tools/r8/jasmin/JasminTestBase.java b/src/test/java/com/android/tools/r8/jasmin/JasminTestBase.java index 67decf2a7..15cf848b2 100644 --- a/src/test/java/com/android/tools/r8/jasmin/JasminTestBase.java +++ b/src/test/java/com/android/tools/r8/jasmin/JasminTestBase.java @@ -132,7 +132,10 @@ public class JasminTestBase { for (ClassBuilder clazz : builder.getClasses()) { ClassFile file = new ClassFile(); file.readJasmin(new StringReader(clazz.toString()), clazz.name, false); - file.write(new FileOutputStream(classes.toPath().resolve(clazz.name + ".class").toFile())); + try (OutputStream outputStream = + Files.newOutputStream(classes.toPath().resolve(clazz.name + ".class"))) { + file.write(outputStream); + } } List<String> args = new ArrayList<>(); args.add("--output=" + dex.toString()); diff --git a/src/test/java/com/android/tools/r8/naming/ApplyMappingTest.java b/src/test/java/com/android/tools/r8/naming/ApplyMappingTest.java new file mode 100644 index 000000000..dac440a4d --- /dev/null +++ b/src/test/java/com/android/tools/r8/naming/ApplyMappingTest.java @@ -0,0 +1,238 @@ +// Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +package com.android.tools.r8.naming; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import com.android.tools.r8.CompilationException; +import com.android.tools.r8.R8Command; +import com.android.tools.r8.ToolHelper; +import com.android.tools.r8.graph.DexMethod; +import com.android.tools.r8.graph.DexType; +import com.android.tools.r8.shaking.ProguardRuleParserException; +import com.android.tools.r8.utils.AndroidApp; +import com.android.tools.r8.utils.DexInspector; +import com.android.tools.r8.utils.DexInspector.InstructionSubject; +import com.android.tools.r8.utils.DexInspector.InvokeInstructionSubject; +import com.android.tools.r8.utils.DexInspector.MethodSubject; +import com.android.tools.r8.utils.FileUtils; +import java.io.IOException; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Iterator; +import java.util.concurrent.ExecutionException; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; + +public class ApplyMappingTest { + + private static final String MAPPING = "test-mapping.txt"; + + private static final Path MINIFICATION_JAR = + Paths.get(ToolHelper.EXAMPLES_BUILD_DIR, "minification" + FileUtils.JAR_EXTENSION); + + private static final Path NAMING001_JAR = + Paths.get(ToolHelper.EXAMPLES_BUILD_DIR, "naming001" + FileUtils.JAR_EXTENSION); + + private static final Path NAMING044_JAR = + Paths.get(ToolHelper.EXAMPLES_BUILD_DIR, "naming044" + FileUtils.JAR_EXTENSION); + + private static final Path APPLYMAPPING044_JAR = + Paths.get(ToolHelper.EXAMPLES_BUILD_DIR, "applymapping044" + FileUtils.JAR_EXTENSION); + + @Rule + public TemporaryFolder temp = new TemporaryFolder(); + + private Path out; + + @Before + public void setup() throws IOException { + out = temp.newFolder("outdex").toPath(); + } + + @Test + public void test044_obfuscate_and_apply() + throws IOException, CompilationException, ProguardRuleParserException, ExecutionException { + // keep rules that allow obfuscations while keeping everything. + Path flagForObfuscation = + Paths.get(ToolHelper.EXAMPLES_DIR, "naming044", "keep-rules-005.txt"); + Path proguardMap = out.resolve(MAPPING); + AndroidApp obfuscatedApp = runR8( + getCommandForApps(out, flagForObfuscation, NAMING044_JAR) + .addProguardConfigurationConsumer(c -> { + c.setPrintMapping(true); + c.setPrintMappingFile(proguardMap); + }).build()); + + // Obviously, dumped map and resource inside the app should be *identical*. + ClassNameMapper mapperFromFile = ClassNameMapper.mapperFromFile(proguardMap); + ClassNameMapper mapperFromApp = + ClassNameMapper.mapperFromInputStream(obfuscatedApp.getProguardMap()); + assertEquals(mapperFromFile, mapperFromApp); + + Path instrOut = temp.newFolder("instr").toPath(); + Path flag = Paths.get(ToolHelper.EXAMPLES_DIR, "applymapping044", "keep-rules.txt"); + AndroidApp instrApp = runR8( + getCommandForInstrumentation(instrOut, flag, NAMING044_JAR, APPLYMAPPING044_JAR) + .addProguardConfigurationConsumer(c -> { + c.setApplyMappingFile(proguardMap); + }) + .setMinification(false) + .build()); + + DexInspector inspector = new DexInspector(instrApp); + MethodSubject main = inspector.clazz("applymapping044.Main").method(DexInspector.MAIN); + Iterator<InvokeInstructionSubject> iterator = + main.iterateInstructions(InstructionSubject::isInvoke); + // B#m() + String b = iterator.next().holder().toString(); + assertEquals("naming044.B", mapperFromApp.deobfuscateClassName(b)); + // sub.SubB#n() + String subB = iterator.next().holder().toString(); + assertEquals("naming044.sub.SubB", mapperFromApp.deobfuscateClassName(subB)); + // Skip A#<init> + iterator.next(); + // Skip B#<init> + iterator.next(); + // B#f(A) + InvokeInstructionSubject f = iterator.next(); + DexType a1 = f.invokedMethod().proto.parameters.values[0]; + assertNotEquals("naming044.A", a1.toString()); + assertEquals("naming044.A", mapperFromApp.deobfuscateClassName(a1.toString())); + assertNotEquals("f", f.invokedMethod().name.toSourceString()); + // Skip AsubB#<init> + iterator.next(); + // AsubB#f(A) + InvokeInstructionSubject overloaded_f = iterator.next(); + DexMethod aSubB_f = overloaded_f.invokedMethod(); + DexType a2 = aSubB_f.proto.parameters.values[0]; + assertNotEquals("naming044.A", a2.toString()); + assertEquals("naming044.A", mapperFromApp.deobfuscateClassName(a2.toString())); + assertNotEquals("f", overloaded_f.invokedMethod().name.toSourceString()); + // B#f == AsubB#f + assertEquals(f.invokedMethod().name.toString(), aSubB_f.name.toString()); + } + + @Test + public void test044_apply() + throws IOException, CompilationException, ProguardRuleParserException, ExecutionException { + Path flag = + Paths.get(ToolHelper.EXAMPLES_DIR, "applymapping044", "keep-rules-apply-mapping.txt"); + AndroidApp outputApp = runR8( + getCommandForInstrumentation(out, flag, NAMING044_JAR, APPLYMAPPING044_JAR) + .setMinification(false) + .build()); + + // Make sure the given proguard map is indeed applied. + DexInspector inspector = new DexInspector(outputApp); + MethodSubject main = inspector.clazz("applymapping044.Main").method(DexInspector.MAIN); + Iterator<InvokeInstructionSubject> iterator = + main.iterateInstructions(InstructionSubject::isInvoke); + // B#m() -> y#n() + InvokeInstructionSubject m = iterator.next(); + assertEquals("naming044.y", m.holder().toString()); + assertEquals("n", m.invokedMethod().name.toSourceString()); + // sub.SubB#n() -> z.y#m() + InvokeInstructionSubject n = iterator.next(); + assertEquals("naming044.z.y", n.holder().toString()); + assertEquals("m", n.invokedMethod().name.toSourceString()); + // Skip A#<init> + iterator.next(); + // Skip B#<init> + iterator.next(); + // B#f(A) -> y#p(x) + InvokeInstructionSubject f = iterator.next(); + DexType a1 = f.invokedMethod().proto.parameters.values[0]; + assertEquals("naming044.x", a1.toString()); + assertEquals("p", f.invokedMethod().name.toSourceString()); + // Skip AsubB#<init> + iterator.next(); + // AsubB#f(A) -> AsubB#p(x) + InvokeInstructionSubject overloaded_f = iterator.next(); + DexMethod aSubB_f = overloaded_f.invokedMethod(); + DexType a2 = aSubB_f.proto.parameters.values[0]; + assertEquals("naming044.x", a2.toString()); + assertEquals("p", aSubB_f.name.toSourceString()); + // B#f == AsubB#f + assertEquals(f.invokedMethod().name.toString(), aSubB_f.name.toString()); + } + + @Test + public void test_naming001_rule105() + throws IOException, CompilationException, ProguardRuleParserException, ExecutionException { + // keep rules to reserve D and E, along with a proguard map. + Path flag = Paths.get(ToolHelper.EXAMPLES_DIR, "naming001", "keep-rules-105.txt"); + Path proguardMap = out.resolve(MAPPING); + AndroidApp outputApp = runR8( + getCommandForApps(out, flag, NAMING001_JAR) + .addProguardConfigurationConsumer(c -> { + c.setPrintMapping(true); + c.setPrintMappingFile(proguardMap); + }) + .setMinification(false) + .build()); + + // Make sure the given proguard map is indeed applied. + DexInspector inspector = new DexInspector(outputApp); + MethodSubject main = inspector.clazz("naming001.D").method(DexInspector.MAIN); + Iterator<InvokeInstructionSubject> iterator = + main.iterateInstructions(InstructionSubject::isInvoke); + // mapping-105 simply includes: naming001.D#keep -> peek + // naming001.E extends D, hence its keep() should be renamed to peek as well. + // Skip E#<init> + iterator.next(); + // E#keep() should be replaced with peek by applying the map. + InvokeInstructionSubject m = iterator.next(); + assertEquals("peek", m.invokedMethod().name.toSourceString()); + // E could be renamed randomly, though. + assertNotEquals("naming001.E", m.holder().toString()); + } + + @Test + public void test_minification_conflict_mapping() + throws IOException, CompilationException, ExecutionException, ProguardRuleParserException { + Path flag = Paths.get( + ToolHelper.EXAMPLES_DIR, "minification", "keep-rules-apply-conflict-mapping.txt"); + try { + runR8(getCommandForApps(out, flag, MINIFICATION_JAR).build()); + fail("Expect to detect renaming conflict"); + } catch (ProguardMapError e) { + assertTrue(e.getMessage().contains("functionFromIntToInt")); + } + } + + private R8Command.Builder getCommandForInstrumentation( + Path out, Path flag, Path mainApp, Path instrApp) + throws CompilationException, IOException { + return R8Command.builder() + .addLibraryFiles(Paths.get(ToolHelper.getDefaultAndroidJar()), mainApp) + .addProgramFiles(instrApp) + .setOutputPath(out) + .addProguardConfigurationFiles(flag); + } + + private R8Command.Builder getCommandForApps( + Path out, Path flag, Path... jars) + throws CompilationException, IOException { + return R8Command.builder() + .addLibraryFiles(Paths.get(ToolHelper.getDefaultAndroidJar())) + .addProgramFiles(jars) + .setOutputPath(out) + .addProguardConfigurationFiles(flag); + } + + private static AndroidApp runR8(R8Command command) + throws ProguardRuleParserException, ExecutionException, CompilationException, IOException { + return ToolHelper.runR8(command, options -> { + // Disable inlining to make this test not depend on inlining decisions. + options.inlineAccessors = false; + }); + } +} diff --git a/src/test/java/com/android/tools/r8/naming/ProguardMapReaderTest.java b/src/test/java/com/android/tools/r8/naming/ProguardMapReaderTest.java index dccfd8a38..a818a58e6 100644 --- a/src/test/java/com/android/tools/r8/naming/ProguardMapReaderTest.java +++ b/src/test/java/com/android/tools/r8/naming/ProguardMapReaderTest.java @@ -19,19 +19,19 @@ public class ProguardMapReaderTest { @Test public void parseThrowingMap() throws IOException { - ProguardMapReader.mapperFromFile(Paths.get(ROOT, EXAMPLE_MAP)); + ClassNameMapper.mapperFromFile(Paths.get(ROOT, EXAMPLE_MAP)); } @Test public void roundTripTest() throws IOException { - ClassNameMapper firstMapper = ProguardMapReader.mapperFromFile(Paths.get(ROOT, EXAMPLE_MAP)); - ClassNameMapper secondMapper = ProguardMapReader.mapperFromString(firstMapper.toString()); + ClassNameMapper firstMapper = ClassNameMapper.mapperFromFile(Paths.get(ROOT, EXAMPLE_MAP)); + ClassNameMapper secondMapper = ClassNameMapper.mapperFromString(firstMapper.toString()); Assert.assertEquals(firstMapper, secondMapper); } @Test public void parseMapWithPackageInfo() throws IOException { - ClassNameMapper mapper = ProguardMapReader.mapperFromString(EXAMPLE_MAP_WITH_PACKAGE_INFO); + ClassNameMapper mapper = ClassNameMapper.mapperFromString(EXAMPLE_MAP_WITH_PACKAGE_INFO); Assert.assertTrue(mapper.getObfuscatedToOriginalMapping().isEmpty()); } } diff --git a/src/test/java/com/android/tools/r8/shaking/TreeShakingTest.java b/src/test/java/com/android/tools/r8/shaking/TreeShakingTest.java index b285ac060..c45ae376e 100644 --- a/src/test/java/com/android/tools/r8/shaking/TreeShakingTest.java +++ b/src/test/java/com/android/tools/r8/shaking/TreeShakingTest.java @@ -61,8 +61,11 @@ public class TreeShakingTest { private static final List<Path> JAR_LIBRARIES = ListUtils.map(ImmutableList .of(ANDROID_JAR, ToolHelper.EXAMPLES_BUILD_DIR + "shakinglib.jar"), Paths::get); private static final String EMPTY_FLAGS = "src/test/proguard/valid/empty.flags"; - - private static Set<String> IGNORED = ImmutableSet.of( + private static final Set<String> IGNORED_FLAGS = ImmutableSet.of( + "minification:conflict-mapping.txt", + "minification:keep-rules-apply-conflict-mapping.txt" + ); + private static final Set<String> IGNORED = ImmutableSet.of( // there's no point in running those without obfuscation "shaking1:keep-rules-repackaging.txt:DEX:false", "shaking1:keep-rules-repackaging.txt:JAR:false", @@ -844,7 +847,8 @@ public class TreeShakingTest { String mainClass, String keepName, List<String> keepList, boolean minify, Consumer<DexInspector> inspection, BiConsumer<String, String> outputComparator, BiConsumer<DexInspector, DexInspector> dexComparator) { - if (!IGNORED.contains(test + ":" + keepName + ":" + kind + ":" + minify)) { + if (!IGNORED_FLAGS.contains(test + ":" + keepName) + && !IGNORED.contains(test + ":" + keepName + ":" + kind + ":" + minify)) { testCases.add(new Object[]{ test, kind, mainClass, keepList, minify, inspection, outputComparator, dexComparator}); } diff --git a/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ForceProguardCompatibilityTest.java b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ForceProguardCompatibilityTest.java index 1519f6c8a..298876ff3 100644 --- a/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ForceProguardCompatibilityTest.java +++ b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ForceProguardCompatibilityTest.java @@ -55,7 +55,7 @@ public class ForceProguardCompatibilityTest extends TestBase { private void runAnnotationsTest(boolean forceProguardCompatibility, boolean keepAnnotations) throws Exception { R8Command.Builder builder = - new CompatProguardCommandBuilder(forceProguardCompatibility); + new CompatProguardCommandBuilder(forceProguardCompatibility, false); // Add application classes including the annotation class. Class mainClass = TestMain.class; Class mentionedClassWithAnnotations = TestMain.MentionedClassWithAnnotation.class; 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; } |