Merge "Show desktop notifications when tests complete."
diff --git a/build.gradle b/build.gradle
index 63513ce..82feb00 100644
--- a/build.gradle
+++ b/build.gradle
@@ -435,6 +435,20 @@
}
}
+task maindex(type: Jar) {
+ from sourceSets.main.output
+ baseName 'maindex'
+ manifest {
+ attributes 'Main-Class': 'com.android.tools.r8.GenerateMainDexList'
+ }
+ // In order to build without dependencies, pass the exclude_deps property using:
+ // gradle -Pexclude_deps maindex
+ if (!project.hasProperty('exclude_deps')) {
+ // Also include dependencies
+ from { configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } }
+ }
+}
+
task ExtractMarker(type: Jar) {
from sourceSets.main.output
baseName 'extractmarker'
@@ -565,6 +579,7 @@
executable file("third_party/kotlin/kotlinc/bin/kotlinc");
}
args "-include-runtime"
+ args "-nowarn"
args "-d"
args "build/test/${kotlinHostJar}"
args fileTree(dir: kotlinResourcesDir, include: '**/*.kt')
diff --git a/src/main/java/com/android/tools/r8/ApiLevelException.java b/src/main/java/com/android/tools/r8/ApiLevelException.java
index ec9fe68..14a530c 100644
--- a/src/main/java/com/android/tools/r8/ApiLevelException.java
+++ b/src/main/java/com/android/tools/r8/ApiLevelException.java
@@ -3,27 +3,28 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8;
+import com.android.tools.r8.utils.AndroidApiLevel;
+
/**
* Exception to signal features that are not supported until a given API level.
*/
public class ApiLevelException extends CompilationException {
public ApiLevelException(
- int minApiLevel, String minApiLevelString, String unsupportedFeatures, String sourceString) {
- super(makeMessage(minApiLevel, minApiLevelString, unsupportedFeatures, sourceString));
- assert minApiLevel > 0;
- assert minApiLevelString != null;
+ AndroidApiLevel minApiLevel, String unsupportedFeatures, String sourceString) {
+ super(makeMessage(minApiLevel, unsupportedFeatures, sourceString));
+ assert minApiLevel != null;
assert unsupportedFeatures != null;
}
private static String makeMessage(
- int minApiLevel, String minApiLevelString, String unsupportedFeatures, String sourceString) {
+ AndroidApiLevel minApiLevel, String unsupportedFeatures, String sourceString) {
String message =
unsupportedFeatures
+ " are only supported starting with "
- + minApiLevelString
+ + minApiLevel.getName()
+ " (--min-api "
- + minApiLevel
+ + minApiLevel.getLevel()
+ ")";
message = (sourceString != null) ? message + ": " + sourceString : message;
return message;
diff --git a/src/main/java/com/android/tools/r8/BaseCommand.java b/src/main/java/com/android/tools/r8/BaseCommand.java
index 9ddbd9e..cbd8dd6 100644
--- a/src/main/java/com/android/tools/r8/BaseCommand.java
+++ b/src/main/java/com/android/tools/r8/BaseCommand.java
@@ -3,51 +3,33 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8;
-import com.android.tools.r8.dex.Constants;
import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.FileUtils;
import com.android.tools.r8.utils.InternalOptions;
-import com.android.tools.r8.utils.OutputMode;
import java.io.IOException;
import java.nio.file.Path;
import java.util.Collection;
+/**
+ * Base class for commands and command builders for applications/tools which take an Android
+ * application (and a main-dex list) as input.
+ */
abstract class BaseCommand {
private final boolean printHelp;
private final boolean printVersion;
private final AndroidApp app;
- private final Path outputPath;
- private final OutputMode outputMode;
- private final CompilationMode mode;
- private final int minApiLevel;
BaseCommand(boolean printHelp, boolean printVersion) {
this.printHelp = printHelp;
this.printVersion = printVersion;
// All other fields are initialized with stub/invalid values.
this.app = null;
- this.outputPath = null;
- this.outputMode = OutputMode.Indexed;
- this.mode = null;
- this.minApiLevel = 0;
}
- BaseCommand(
- AndroidApp app,
- Path outputPath,
- OutputMode outputMode,
- CompilationMode mode,
- int minApiLevel) {
+ BaseCommand(AndroidApp app) {
assert app != null;
- assert mode != null;
- assert minApiLevel > 0;
this.app = app;
- this.outputPath = outputPath;
- this.outputMode = outputMode;
- this.mode = mode;
- this.minApiLevel = minApiLevel;
// Print options are not set.
printHelp = false;
printVersion = false;
@@ -69,52 +51,27 @@
// Internal access to the internal options.
abstract InternalOptions getInternalOptions();
- public Path getOutputPath() {
- return outputPath;
- }
-
- public CompilationMode getMode() {
- return mode;
- }
-
- public int getMinApiLevel() {
- return minApiLevel;
- }
-
- public OutputMode getOutputMode() {
- return outputMode;
- }
-
- abstract static class Builder<C extends BaseCommand, B extends Builder<C, B>> {
+ abstract public static class Builder<C extends BaseCommand, B extends Builder<C, B>> {
private boolean printHelp = false;
private boolean printVersion = false;
private final AndroidApp.Builder app;
- private Path outputPath = null;
- private OutputMode outputMode = OutputMode.Indexed;
- private CompilationMode mode;
- private int minApiLevel = Constants.DEFAULT_ANDROID_API;
- // Internal flag used by CompatDx to ignore dex files in archives.
- protected boolean ignoreDexInArchive = false;
-
- protected Builder(CompilationMode mode) {
- this(AndroidApp.builder(), mode, false);
+ protected Builder() {
+ this(AndroidApp.builder(), false);
}
- protected Builder(CompilationMode mode, boolean ignoreDexInArchive) {
- this(AndroidApp.builder(), mode, ignoreDexInArchive);
+ protected Builder(boolean ignoreDexInArchive) {
+ this(AndroidApp.builder(), ignoreDexInArchive);
}
// Internal constructor for testing.
Builder(AndroidApp app, CompilationMode mode) {
- this(AndroidApp.builder(app), mode, false);
+ this(AndroidApp.builder(app), false);
}
- private Builder(AndroidApp.Builder builder, CompilationMode mode, boolean ignoreDexInArchive) {
- assert mode != null;
+ protected Builder(AndroidApp.Builder builder, boolean ignoreDexInArchive) {
this.app = builder;
- this.mode = mode;
app.setIgnoreDexInArchive(ignoreDexInArchive);
}
@@ -181,52 +138,6 @@
return self();
}
- /** Get current compilation mode. */
- public CompilationMode getMode() {
- return mode;
- }
-
- /** Set compilation mode. */
- public B setMode(CompilationMode mode) {
- assert mode != null;
- this.mode = mode;
- return self();
- }
-
- /** Get the output path. Null if not set. */
- public Path getOutputPath() {
- return outputPath;
- }
-
- /** Get the output mode. */
- public OutputMode getOutputMode() {
- return outputMode;
- }
-
- /** Set an output path. Must be an existing directory or a zip file. */
- public B setOutputPath(Path outputPath) {
- this.outputPath = outputPath;
- return self();
- }
-
- /** Set an output mode. */
- public B setOutputMode(OutputMode outputMode) {
- this.outputMode = outputMode;
- return self();
- }
-
- /** Get the minimum API level (aka SDK version). */
- public int getMinApiLevel() {
- return minApiLevel;
- }
-
- /** Set the minimum required API level (aka SDK version). */
- public B setMinApiLevel(int minApiLevel) {
- assert minApiLevel > 0;
- this.minApiLevel = minApiLevel;
- return self();
- }
-
/**
* Add main-dex list files.
*
@@ -300,11 +211,7 @@
}
protected void validate() throws CompilationException {
- if (app.hasMainDexList() && outputMode == OutputMode.FilePerClass) {
- throw new CompilationException(
- "Option --main-dex-list cannot be used with --file-per-class");
- }
- FileUtils.validateOutputFile(outputPath);
+ // Currently does nothing.
}
}
}
diff --git a/src/main/java/com/android/tools/r8/BaseCompilerCommand.java b/src/main/java/com/android/tools/r8/BaseCompilerCommand.java
new file mode 100644
index 0000000..5e8f975
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/BaseCompilerCommand.java
@@ -0,0 +1,146 @@
+// 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;
+
+import com.android.tools.r8.dex.Constants;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.FileUtils;
+import com.android.tools.r8.utils.OutputMode;
+import java.nio.file.Path;
+
+/**
+ * Base class for commands and command builders for compiler applications/tools which besides an
+ * Android application (and a main-dex list) also takes compilation output, compilation mode and
+ * min API level as input.
+ */
+abstract class BaseCompilerCommand extends BaseCommand {
+
+ private final Path outputPath;
+ private final OutputMode outputMode;
+ private final CompilationMode mode;
+ private final int minApiLevel;
+
+ BaseCompilerCommand(boolean printHelp, boolean printVersion) {
+ super(printHelp, printVersion);
+
+ this.outputPath = null;
+ this.outputMode = OutputMode.Indexed;
+ this.mode = null;
+ this.minApiLevel = 0;
+ }
+
+ BaseCompilerCommand(
+ AndroidApp app,
+ Path outputPath,
+ OutputMode outputMode,
+ CompilationMode mode,
+ int minApiLevel) {
+ super(app);
+ assert mode != null;
+ assert minApiLevel > 0;
+ this.outputPath = outputPath;
+ this.outputMode = outputMode;
+ this.mode = mode;
+ this.minApiLevel = minApiLevel;
+ }
+
+ public Path getOutputPath() {
+ return outputPath;
+ }
+
+ public CompilationMode getMode() {
+ return mode;
+ }
+
+ public int getMinApiLevel() {
+ return minApiLevel;
+ }
+
+ public OutputMode getOutputMode() {
+ return outputMode;
+ }
+
+ abstract public static class Builder<C extends BaseCompilerCommand, B extends Builder<C, B>>
+ extends BaseCommand.Builder<C, B> {
+
+ private Path outputPath = null;
+ private OutputMode outputMode = OutputMode.Indexed;
+ private CompilationMode mode;
+ private int minApiLevel = Constants.DEFAULT_ANDROID_API;
+
+ protected Builder(CompilationMode mode) {
+ this(AndroidApp.builder(), mode, false);
+ }
+
+ protected Builder(CompilationMode mode, boolean ignoreDexInArchive) {
+ this(AndroidApp.builder(), mode, ignoreDexInArchive);
+ }
+
+ // Internal constructor for testing.
+ Builder(AndroidApp app, CompilationMode mode) {
+ this(AndroidApp.builder(app), mode, false);
+ }
+
+ private Builder(AndroidApp.Builder builder, CompilationMode mode, boolean ignoreDexInArchive) {
+ super(builder, ignoreDexInArchive);
+ assert mode != null;
+ this.mode = mode;
+ }
+
+ /** Get current compilation mode. */
+ public CompilationMode getMode() {
+ return mode;
+ }
+
+ /** Set compilation mode. */
+ public B setMode(CompilationMode mode) {
+ assert mode != null;
+ this.mode = mode;
+ return self();
+ }
+
+ /** Get the output path. Null if not set. */
+ public Path getOutputPath() {
+ return outputPath;
+ }
+
+ /** Get the output mode. */
+ public OutputMode getOutputMode() {
+ return outputMode;
+ }
+
+ /** Set an output path. Must be an existing directory or a zip file. */
+ public B setOutputPath(Path outputPath) {
+ this.outputPath = outputPath;
+ return self();
+ }
+
+ /** Set an output mode. */
+ public B setOutputMode(OutputMode outputMode) {
+ this.outputMode = outputMode;
+ return self();
+ }
+
+ /** Get the minimum API level (aka SDK version). */
+ public int getMinApiLevel() {
+ return minApiLevel;
+ }
+
+ /** Set the minimum required API level (aka SDK version). */
+ public B setMinApiLevel(int minApiLevel) {
+ assert minApiLevel > 0;
+ this.minApiLevel = minApiLevel;
+ return self();
+ }
+
+ protected void validate() throws CompilationException {
+ super.validate();
+ if (getAppBuilder().hasMainDexList() && outputMode == OutputMode.FilePerClass) {
+ throw new CompilationException(
+ "Option --main-dex-list cannot be used with --file-per-class");
+ }
+ FileUtils.validateOutputFile(outputPath);
+ }
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/CompilationException.java b/src/main/java/com/android/tools/r8/CompilationException.java
index 4fd3654..8e1e56c 100644
--- a/src/main/java/com/android/tools/r8/CompilationException.java
+++ b/src/main/java/com/android/tools/r8/CompilationException.java
@@ -36,5 +36,17 @@
public CompilationException(Throwable cause) {
super(cause.getMessage(), cause);
}
+
+ protected CompilationException() {
+ super();
+ }
+
+ public String getMessageForD8() {
+ return super.getMessage();
+ }
+
+ public String getMessageForR8() {
+ return super.getMessage();
+ }
}
diff --git a/src/main/java/com/android/tools/r8/D8.java b/src/main/java/com/android/tools/r8/D8.java
index 5321808..6598ae4 100644
--- a/src/main/java/com/android/tools/r8/D8.java
+++ b/src/main/java/com/android/tools/r8/D8.java
@@ -10,7 +10,7 @@
import com.android.tools.r8.dex.Marker;
import com.android.tools.r8.dex.Marker.Tool;
import com.android.tools.r8.errors.CompilationError;
-import com.android.tools.r8.errors.MainDexError;
+import com.android.tools.r8.errors.DexOverflowException;
import com.android.tools.r8.graph.AppInfo;
import com.android.tools.r8.graph.DexApplication;
import com.android.tools.r8.ir.conversion.IRConverter;
@@ -118,7 +118,7 @@
}
/** Command-line entry to D8. */
- public static void main(String[] args) throws IOException {
+ public static void main(String[] args) {
if (args.length == 0) {
System.err.println(USAGE_MESSAGE);
System.exit(STATUS_ERROR);
@@ -140,8 +140,7 @@
cause.printStackTrace();
System.exit(STATUS_ERROR);
} catch (CompilationException e) {
- System.err.println("Compilation failed: " + e.getMessage());
- System.err.println(USAGE_MESSAGE);
+ System.err.println("Compilation failed: " + e.getMessageForD8());
System.exit(STATUS_ERROR);
}
}
@@ -199,8 +198,6 @@
options.printWarnings();
return output;
- } catch (MainDexError mainDexError) {
- throw new CompilationError(mainDexError.getMessageForD8());
} catch (ExecutionException e) {
R8.unwrapExecutionException(e);
throw new AssertionError(e); // unwrapping method should have thrown
diff --git a/src/main/java/com/android/tools/r8/D8Command.java b/src/main/java/com/android/tools/r8/D8Command.java
index 3618ab5..5b2e5cf 100644
--- a/src/main/java/com/android/tools/r8/D8Command.java
+++ b/src/main/java/com/android/tools/r8/D8Command.java
@@ -27,12 +27,12 @@
* .build();
* </pre>
*/
-public class D8Command extends BaseCommand {
+public class D8Command extends BaseCompilerCommand {
/**
* Builder for constructing a D8Command.
*/
- public static class Builder extends BaseCommand.Builder<D8Command, Builder> {
+ public static class Builder extends BaseCompilerCommand.Builder<D8Command, Builder> {
private boolean intermediate = false;
diff --git a/src/main/java/com/android/tools/r8/DexSegments.java b/src/main/java/com/android/tools/r8/DexSegments.java
index 3827cae..c27deb5 100644
--- a/src/main/java/com/android/tools/r8/DexSegments.java
+++ b/src/main/java/com/android/tools/r8/DexSegments.java
@@ -5,18 +5,14 @@
import com.android.tools.r8.dex.DexFileReader;
import com.android.tools.r8.dex.Segment;
-import com.android.tools.r8.shaking.ProguardRuleParserException;
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.InternalOptions;
-import com.android.tools.r8.utils.OutputMode;
import com.google.common.collect.ImmutableList;
import com.google.common.io.Closer;
import java.io.IOException;
-import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;
-import java.util.concurrent.ExecutionException;
public class DexSegments {
private static class Command extends BaseCommand {
@@ -24,10 +20,6 @@
public static class Builder
extends BaseCommand.Builder<Command, Builder> {
- private Builder() {
- super(CompilationMode.RELEASE);
- }
-
@Override
Command.Builder self() {
return this;
@@ -40,8 +32,7 @@
return new Command(isPrintHelp());
}
validate();
- return new Command(
- getAppBuilder().build(), getOutputPath(), getOutputMode(), getMode(), getMinApiLevel());
+ return new Command(getAppBuilder().build());
}
}
@@ -79,13 +70,8 @@
}
}
- private Command(
- AndroidApp inputApp,
- Path outputPath,
- OutputMode outputMode,
- CompilationMode mode,
- int minApiLevel) {
- super(inputApp, outputPath, outputMode, mode, minApiLevel);
+ private Command(AndroidApp inputApp) {
+ super(inputApp);
}
private Command(boolean printHelp) {
@@ -99,7 +85,7 @@
}
public static void main(String[] args)
- throws IOException, ProguardRuleParserException, CompilationException, ExecutionException {
+ throws IOException, CompilationException {
Command.Builder builder = Command.parse(args);
Command command = builder.build();
if (command.isPrintHelp()) {
diff --git a/src/main/java/com/android/tools/r8/Disassemble.java b/src/main/java/com/android/tools/r8/Disassemble.java
index 2f1d523..bb72d5d 100644
--- a/src/main/java/com/android/tools/r8/Disassemble.java
+++ b/src/main/java/com/android/tools/r8/Disassemble.java
@@ -3,10 +3,8 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8;
-import com.android.tools.r8.shaking.ProguardRuleParserException;
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.InternalOptions;
-import com.android.tools.r8.utils.OutputMode;
import com.google.common.collect.ImmutableList;
import java.io.IOException;
import java.nio.file.Path;
@@ -16,13 +14,13 @@
public class Disassemble {
public static class DisassembleCommand extends BaseCommand {
+ private final Path outputPath;
+
public static class Builder
extends BaseCommand.Builder<DisassembleCommand, DisassembleCommand.Builder> {
- private boolean useSmali = false;
- private Builder() {
- super(CompilationMode.RELEASE);
- }
+ private Path outputPath = null;
+ private boolean useSmali = false;
@Override
DisassembleCommand.Builder self() {
@@ -34,6 +32,15 @@
return this;
}
+ public Path getOutputPath() {
+ return outputPath;
+ }
+
+ public DisassembleCommand.Builder setOutputPath(Path outputPath) {
+ this.outputPath = outputPath;
+ return this;
+ }
+
public DisassembleCommand.Builder setUseSmali(boolean useSmali) {
this.useSmali = useSmali;
return this;
@@ -47,13 +54,7 @@
}
validate();
- return new DisassembleCommand(
- getAppBuilder().build(),
- getOutputPath(),
- getOutputMode(),
- getMode(),
- getMinApiLevel(),
- useSmali);
+ return new DisassembleCommand(getAppBuilder().build(), getOutputPath(), useSmali);
}
}
@@ -94,6 +95,9 @@
builder.setUseSmali(true);
} else if (arg.equals("--pg-map")) {
builder.setProguardMapFile(Paths.get(args[++i]));
+ } else if (arg.equals("--output")) {
+ String outputPath = args[++i];
+ builder.setOutputPath(Paths.get(outputPath));
} else {
if (arg.startsWith("--")) {
throw new CompilationException("Unknown option: " + arg);
@@ -103,23 +107,22 @@
}
}
- private DisassembleCommand(
- AndroidApp inputApp,
- Path outputPath,
- OutputMode outputMode,
- CompilationMode mode,
- int minApiLevel,
- boolean useSmali) {
- super(inputApp, outputPath, outputMode, mode, minApiLevel);
- assert getOutputMode() == OutputMode.Indexed : "Only regular mode is supported in R8";
+ private DisassembleCommand(AndroidApp inputApp, Path outputPath, boolean useSmali) {
+ super(inputApp);
+ this.outputPath = outputPath;
this.useSmali = useSmali;
}
private DisassembleCommand(boolean printHelp, boolean printVersion) {
super(printHelp, printVersion);
+ this.outputPath = null;
this.useSmali = false;
}
+ public Path getOutputPath() {
+ return outputPath;
+ }
+
public boolean useSmali() {
return useSmali;
}
@@ -133,7 +136,7 @@
}
public static void main(String[] args)
- throws IOException, ProguardRuleParserException, CompilationException, ExecutionException {
+ throws IOException, CompilationException, ExecutionException {
DisassembleCommand.Builder builder = DisassembleCommand.parse(args);
DisassembleCommand command = builder.build();
if (command.isPrintHelp()) {
diff --git a/src/main/java/com/android/tools/r8/ExtractMarker.java b/src/main/java/com/android/tools/r8/ExtractMarker.java
index c1234b8..d798781 100644
--- a/src/main/java/com/android/tools/r8/ExtractMarker.java
+++ b/src/main/java/com/android/tools/r8/ExtractMarker.java
@@ -3,19 +3,14 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8;
-import com.google.common.collect.ImmutableList;
-
import com.android.tools.r8.dex.ApplicationReader;
import com.android.tools.r8.dex.Marker;
import com.android.tools.r8.graph.DexApplication;
-import com.android.tools.r8.shaking.ProguardRuleParserException;
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.InternalOptions;
-import com.android.tools.r8.utils.OutputMode;
import com.android.tools.r8.utils.Timing;
-
+import com.google.common.collect.ImmutableList;
import java.io.IOException;
-import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.concurrent.ExecutionException;
@@ -25,10 +20,6 @@
public static class Builder
extends BaseCommand.Builder<ExtractMarker.Command, ExtractMarker.Command.Builder> {
- private Builder() {
- super(CompilationMode.RELEASE);
- }
-
@Override
ExtractMarker.Command.Builder self() {
return this;
@@ -41,8 +32,7 @@
return new ExtractMarker.Command(isPrintHelp());
}
validate();
- return new ExtractMarker.Command(
- getAppBuilder().build(), getOutputPath(), getOutputMode(), getMode(), getMinApiLevel());
+ return new ExtractMarker.Command(getAppBuilder().build());
}
}
@@ -80,13 +70,8 @@
}
}
- private Command(
- AndroidApp inputApp,
- Path outputPath,
- OutputMode outputMode,
- CompilationMode mode,
- int minApiLevel) {
- super(inputApp, outputPath, outputMode, mode, minApiLevel);
+ private Command(AndroidApp inputApp) {
+ super(inputApp);
}
private Command(boolean printHelp) {
@@ -100,7 +85,7 @@
}
public static void main(String[] args)
- throws IOException, ProguardRuleParserException, CompilationException, ExecutionException {
+ throws IOException, CompilationException, ExecutionException {
ExtractMarker.Command.Builder builder = ExtractMarker.Command.parse(args);
ExtractMarker.Command command = builder.build();
if (command.isPrintHelp()) {
diff --git a/src/main/java/com/android/tools/r8/GenerateMainDexList.java b/src/main/java/com/android/tools/r8/GenerateMainDexList.java
new file mode 100644
index 0000000..44d523e
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/GenerateMainDexList.java
@@ -0,0 +1,124 @@
+// 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;
+
+import com.android.tools.r8.dex.ApplicationReader;
+import com.android.tools.r8.graph.AppInfoWithSubtyping;
+import com.android.tools.r8.graph.DexApplication;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.shaking.Enqueuer;
+import com.android.tools.r8.shaking.MainDexListBuilder;
+import com.android.tools.r8.shaking.ProguardRuleParserException;
+import com.android.tools.r8.shaking.RootSetBuilder;
+import com.android.tools.r8.shaking.RootSetBuilder.RootSet;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.ThreadUtils;
+import com.android.tools.r8.utils.Timing;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.PrintWriter;
+import java.nio.file.Files;
+import java.nio.file.StandardOpenOption;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.stream.Collectors;
+
+public class GenerateMainDexList {
+ private static final String VERSION = "v0.2.0";
+ private final Timing timing = new Timing("maindex");
+ private final InternalOptions options;
+
+ private GenerateMainDexList(InternalOptions options) {
+ this.options = options;
+ }
+
+ private List<String> run(AndroidApp app) throws IOException, ExecutionException {
+ ExecutorService executor = ThreadUtils.getExecutorService(options);
+ DexApplication application = new ApplicationReader(app, options, timing).read(executor);
+ AppInfoWithSubtyping appInfo = new AppInfoWithSubtyping(application);
+ RootSet mainDexRootSet =
+ new RootSetBuilder(application, appInfo, options.mainDexKeepRules).run(executor);
+ Set<DexType> mainDexBaseClasses = new Enqueuer(appInfo).traceMainDex(mainDexRootSet, timing);
+ Set<DexType> mainDexClasses = new MainDexListBuilder(mainDexBaseClasses, application).run();
+
+ List<String> result = mainDexClasses.stream()
+ .map(c -> c.toSourceString().replace('.', '/') + ".class")
+ .sorted()
+ .collect(Collectors.toList());
+
+ if (options.printMainDexListFile != null) {
+ try (OutputStream mainDexOut = Files.newOutputStream(options.printMainDexListFile,
+ StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING)) {
+ PrintWriter writer = new PrintWriter(mainDexOut);
+ result.forEach(writer::println);
+ writer.flush();
+ }
+ }
+
+ return result;
+ }
+
+ /**
+ * Main API entry for computing the main-dex list.
+ *
+ * The main-dex list is represented as a list of strings, each string specifies one class to
+ * keep in the primary dex file (<code>classes.dex</code>).
+ *
+ * A class is specified using the following format: "com/example/MyClass.class". That is
+ * "/" as separator between package components, and a trailing ".class".
+ *
+ * @param command main dex-list generator command.
+ * @return classes to keep in the primary dex file.
+ */
+ public static List<String> run(GenerateMainDexListCommand command)
+ throws IOException, ExecutionException {
+ ExecutorService executorService = ThreadUtils.getExecutorService(command.getInternalOptions());
+ try {
+ return run(command, executorService);
+ } finally {
+ executorService.shutdown();
+ }
+ }
+
+ /**
+ * Main API entry for computing the main-dex list.
+ *
+ * The main-dex list is represented as a list of strings, each string specifies one class to
+ * keep in the primary dex file (<code>classes.dex</code>).
+ *
+ * A class is specified using the following format: "com/example/MyClass.class". That is
+ * "/" as separator between package components, and a trailing ".class".
+ *
+ * @param command main dex-list generator command.
+ * @param executor executor service from which to get threads for multi-threaded processing.
+ * @return classes to keep in the primary dex file.
+ */
+ public static List<String> run(GenerateMainDexListCommand command, ExecutorService executor)
+ throws IOException, ExecutionException {
+ AndroidApp app = command.getInputApp();
+ InternalOptions options = command.getInternalOptions();
+ return new GenerateMainDexList(options).run(app);
+ }
+
+ public static void main(String[] args)
+ throws IOException, ProguardRuleParserException, CompilationException, ExecutionException {
+ GenerateMainDexListCommand.Builder builder = GenerateMainDexListCommand.parse(args);
+ GenerateMainDexListCommand command = builder.build();
+ if (command.isPrintHelp()) {
+ System.out.println(GenerateMainDexListCommand.USAGE_MESSAGE);
+ return;
+ }
+ if (command.isPrintVersion()) {
+ System.out.println("MainDexListGenerator " + VERSION);
+ return;
+ }
+ List<String> result = run(command);
+ if (command.getMainDexListOutputPath() == null) {
+ result.forEach(System.out::println);
+ }
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/GenerateMainDexListCommand.java b/src/main/java/com/android/tools/r8/GenerateMainDexListCommand.java
new file mode 100644
index 0000000..76d7d56
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/GenerateMainDexListCommand.java
@@ -0,0 +1,200 @@
+// 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;
+
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.shaking.ProguardConfigurationParser;
+import com.android.tools.r8.shaking.ProguardConfigurationRule;
+import com.android.tools.r8.shaking.ProguardConfigurationSource;
+import com.android.tools.r8.shaking.ProguardConfigurationSourceFile;
+import com.android.tools.r8.shaking.ProguardConfigurationSourceStrings;
+import com.android.tools.r8.shaking.ProguardRuleParserException;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.InternalOptions;
+import com.google.common.collect.ImmutableList;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.List;
+
+public class GenerateMainDexListCommand extends BaseCommand {
+
+ private final ImmutableList<ProguardConfigurationRule> mainDexKeepRules;
+ private final Path mainDexListOutput;
+ private final DexItemFactory factory;
+
+ /**
+ * Get the output path for the main-dex list. Null if not set.
+ */
+ public Path getMainDexListOutputPath() {
+ return mainDexListOutput;
+ }
+
+ public static class Builder extends BaseCommand.Builder<GenerateMainDexListCommand, Builder> {
+
+ private final DexItemFactory factory = new DexItemFactory();
+ private final List<ProguardConfigurationSource> mainDexRules = new ArrayList<>();
+ private Path mainDexListOutput = null;
+
+ @Override
+ GenerateMainDexListCommand.Builder self() {
+ return this;
+ }
+
+ /**
+ * Add proguard configuration file resources for automatic main dex list calculation.
+ */
+ public GenerateMainDexListCommand.Builder addMainDexRulesFiles(Path... paths) {
+ for (Path path : paths) {
+ mainDexRules.add(new ProguardConfigurationSourceFile(path));
+ }
+ return self();
+ }
+
+ /**
+ * Add proguard configuration file resources for automatic main dex list calculation.
+ */
+ public GenerateMainDexListCommand.Builder addMainDexRulesFiles(List<Path> paths) {
+ for (Path path : paths) {
+ mainDexRules.add(new ProguardConfigurationSourceFile(path));
+ }
+ return self();
+ }
+
+ /**
+ * Add proguard configuration for automatic main dex list calculation.
+ */
+ public GenerateMainDexListCommand.Builder addMainDexRules(List<String> lines) {
+ mainDexRules.add(new ProguardConfigurationSourceStrings(lines));
+ return self();
+ }
+
+ /**
+ * Get the output path for the main-dex list. Null if not set.
+ */
+ public Path getMainDexListOutputPath() {
+ return mainDexListOutput;
+ }
+
+ /**
+ * Set the output file for the main-dex list.
+ *
+ * If the file exists it will be overwritten.
+ */
+ public GenerateMainDexListCommand.Builder setMainDexListOutputPath(Path mainDexListOutputPath) {
+ mainDexListOutput = mainDexListOutputPath;
+ return self();
+ }
+
+
+ @Override
+ public GenerateMainDexListCommand build() throws CompilationException, IOException {
+ // If printing versions ignore everything else.
+ if (isPrintHelp() || isPrintVersion()) {
+ return new GenerateMainDexListCommand(isPrintHelp(), isPrintVersion());
+ }
+
+ validate();
+ ImmutableList<ProguardConfigurationRule> mainDexKeepRules;
+ if (this.mainDexRules.isEmpty()) {
+ mainDexKeepRules = ImmutableList.of();
+ } else {
+ ProguardConfigurationParser parser = new ProguardConfigurationParser(factory);
+ try {
+ parser.parse(mainDexRules);
+ } catch (ProguardRuleParserException e) {
+ throw new CompilationException(e.getMessage(), e.getCause());
+ }
+ mainDexKeepRules = parser.getConfig().getRules();
+ }
+
+ return new GenerateMainDexListCommand(
+ factory,
+ getAppBuilder().build(),
+ mainDexKeepRules,
+ mainDexListOutput);
+ }
+ }
+
+ static final String USAGE_MESSAGE = String.join("\n", ImmutableList.of(
+ "Usage: maindex [options] <input-files>",
+ " where <input-files> are JAR files",
+ " and options are:",
+ " --main-dex-rules <file> # Proguard keep rules for classes to place in the",
+ " # primary dex file.",
+ " --main-dex-list <file> # List of classes to place in the primary dex file.",
+ " --main-dex-list-output <file> # Output the full main-dex list in <file>.",
+ " --version # Print the version.",
+ " --help # Print this message."));
+
+
+ public static GenerateMainDexListCommand.Builder builder() {
+ return new GenerateMainDexListCommand.Builder();
+ }
+
+ public static GenerateMainDexListCommand.Builder parse(String[] args)
+ throws CompilationException, IOException {
+ GenerateMainDexListCommand.Builder builder = builder();
+ parse(args, builder);
+ return builder;
+ }
+
+ private static void parse(String[] args, GenerateMainDexListCommand.Builder builder)
+ throws CompilationException, IOException {
+ for (int i = 0; i < args.length; i++) {
+ String arg = args[i].trim();
+ if (arg.length() == 0) {
+ continue;
+ } else if (arg.equals("--help")) {
+ builder.setPrintHelp(true);
+ } else if (arg.equals("--version")) {
+ builder.setPrintVersion(true);
+ } else if (arg.equals("--main-dex-rules")) {
+ builder.addMainDexRulesFiles(Paths.get(args[++i]));
+ } else if (arg.equals("--main-dex-list")) {
+ builder.addMainDexListFiles(Paths.get(args[++i]));
+ } else if (arg.equals("--main-dex-list-output")) {
+ builder.setMainDexListOutputPath(Paths.get(args[++i]));
+ } else {
+ if (arg.startsWith("--")) {
+ throw new CompilationException("Unknown option: " + arg);
+ }
+ builder.addProgramFiles(Paths.get(arg));
+ }
+ }
+ }
+
+ private GenerateMainDexListCommand(
+ DexItemFactory factory,
+ AndroidApp inputApp,
+ ImmutableList<ProguardConfigurationRule> mainDexKeepRules,
+ Path mainDexListOutput) {
+ super(inputApp);
+ this.factory = factory;
+ this.mainDexKeepRules = mainDexKeepRules;
+ this.mainDexListOutput = mainDexListOutput;
+ }
+
+ private GenerateMainDexListCommand(boolean printHelp, boolean printVersion) {
+ super(printHelp, printVersion);
+ this.factory = new DexItemFactory();
+ this.mainDexKeepRules = ImmutableList.of();
+ this.mainDexListOutput = null;
+ }
+
+ @Override
+ InternalOptions getInternalOptions() {
+ InternalOptions internal = new InternalOptions(factory);
+ internal.mainDexKeepRules = mainDexKeepRules;
+ if (mainDexListOutput != null) {
+ internal.printMainDexListFile = mainDexListOutput;
+ }
+ internal.minimalMainDex = internal.debug;
+ internal.removeSwitchMaps = false;
+ internal.inlineAccessors = false;
+ return internal;
+ }
+}
+
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 328f215..b4c19ea 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -7,11 +7,10 @@
import com.android.tools.r8.dex.ApplicationReader;
import com.android.tools.r8.dex.ApplicationWriter;
-import com.android.tools.r8.dex.Constants;
import com.android.tools.r8.dex.Marker;
import com.android.tools.r8.dex.Marker.Tool;
import com.android.tools.r8.errors.CompilationError;
-import com.android.tools.r8.errors.MainDexError;
+import com.android.tools.r8.errors.DexOverflowException;
import com.android.tools.r8.graph.AppInfo;
import com.android.tools.r8.graph.AppInfoWithSubtyping;
import com.android.tools.r8.graph.ClassAndMemberPublicizer;
@@ -41,6 +40,7 @@
import com.android.tools.r8.shaking.SimpleClassMerger;
import com.android.tools.r8.shaking.TreePruner;
import com.android.tools.r8.shaking.protolite.ProtoLiteExtension;
+import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.CfgPrinter;
import com.android.tools.r8.utils.FileUtils;
@@ -99,7 +99,7 @@
byte[] proguardSeedsData,
PackageDistribution packageDistribution,
InternalOptions options)
- throws ExecutionException {
+ throws ExecutionException, DexOverflowException {
try {
Marker marker = getMarker(options);
return new ApplicationWriter(
@@ -207,7 +207,7 @@
}
static CompilationResult runForTesting(AndroidApp app, InternalOptions options)
- throws ProguardRuleParserException, IOException, CompilationException {
+ throws IOException, CompilationException {
ExecutorService executor = ThreadUtils.getExecutorService(options);
try {
return runForTesting(app, options, executor);
@@ -220,20 +220,21 @@
AndroidApp app,
InternalOptions options,
ExecutorService executor)
- throws ProguardRuleParserException, IOException, CompilationException {
+ throws IOException, CompilationException {
return new R8(options).run(app, executor);
}
private CompilationResult run(AndroidApp inputApp, ExecutorService executorService)
- throws IOException, ProguardRuleParserException, CompilationException {
+ throws IOException, CompilationException {
if (options.quiet) {
System.setOut(new PrintStream(ByteStreams.nullOutputStream()));
}
try {
- if (options.minApiLevel >= Constants.ANDROID_O_API
+ AndroidApiLevel oLevel = AndroidApiLevel.O;
+ if (options.minApiLevel >= oLevel.getLevel()
&& !options.mainDexKeepRules.isEmpty()) {
- throw new CompilationError("Automatic main dex list is not supported when compiling for"
- + " android O and later (--min-api " + Constants.ANDROID_O_API + ")");
+ throw new CompilationError("Automatic main dex list is not supported when compiling for "
+ + oLevel.getName() + " and later (--min-api " + oLevel.getLevel() + ")");
}
DexApplication application =
new ApplicationReader(inputApp, options, timing).read(executorService);
@@ -392,8 +393,6 @@
options.printWarnings();
return new CompilationResult(androidApp, application, appInfo);
- } catch (MainDexError mainDexError) {
- throw new CompilationError(mainDexError.getMessageForR8());
} catch (ExecutionException e) {
unwrapExecutionException(e);
throw new AssertionError(e); // unwrapping method should have thrown
@@ -445,7 +444,7 @@
* @return the compilation result.
*/
public static AndroidApp run(R8Command command)
- throws IOException, CompilationException, ProguardRuleParserException {
+ throws IOException, CompilationException {
ExecutorService executorService = ThreadUtils.getExecutorService(command.getInternalOptions());
try {
return run(command, executorService);
@@ -516,7 +515,7 @@
* @return the compilation result.
*/
public static AndroidApp run(R8Command command, ExecutorService executor)
- throws IOException, CompilationException, ProguardRuleParserException {
+ throws IOException, CompilationException {
InternalOptions options = command.getInternalOptions();
AndroidApp outputApp =
runForTesting(command.getInputApp(), options, executor).androidApp;
@@ -562,8 +561,7 @@
cause.printStackTrace();
System.exit(1);
} catch (CompilationException e) {
- System.err.println("Compilation failed: " + e.getMessage());
- System.err.println(USAGE_MESSAGE);
+ System.err.println("Compilation failed: " + e.getMessageForR8());
System.exit(1);
}
}
diff --git a/src/main/java/com/android/tools/r8/R8Command.java b/src/main/java/com/android/tools/r8/R8Command.java
index 775c1b3..890d570 100644
--- a/src/main/java/com/android/tools/r8/R8Command.java
+++ b/src/main/java/com/android/tools/r8/R8Command.java
@@ -7,6 +7,9 @@
import com.android.tools.r8.shaking.ProguardConfiguration;
import com.android.tools.r8.shaking.ProguardConfigurationParser;
import com.android.tools.r8.shaking.ProguardConfigurationRule;
+import com.android.tools.r8.shaking.ProguardConfigurationSource;
+import com.android.tools.r8.shaking.ProguardConfigurationSourceFile;
+import com.android.tools.r8.shaking.ProguardConfigurationSourceStrings;
import com.android.tools.r8.shaking.ProguardRuleParserException;
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.FileUtils;
@@ -17,19 +20,18 @@
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
-import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.function.Consumer;
-public class R8Command extends BaseCommand {
+public class R8Command extends BaseCompilerCommand {
- public static class Builder extends BaseCommand.Builder<R8Command, Builder> {
+ public static class Builder extends BaseCompilerCommand.Builder<R8Command, Builder> {
- private final List<Path> mainDexRules = new ArrayList<>();
+ private final List<ProguardConfigurationSource> mainDexRules = new ArrayList<>();
private Path mainDexListOutput = null;
private Consumer<ProguardConfiguration.Builder> proguardConfigurationConsumer = null;
- private final List<Path> proguardConfigFiles = new ArrayList<>();
+ private final List<ProguardConfigurationSource> proguardConfigs = new ArrayList<>();
private Optional<Boolean> treeShaking = Optional.empty();
private Optional<Boolean> discardedChecker = Optional.empty();
private Optional<Boolean> minification = Optional.empty();
@@ -76,16 +78,28 @@
/**
* Add proguard configuration file resources for automatic main dex list calculation.
*/
- public Builder addMainDexRules(Path... paths) {
- Collections.addAll(mainDexRules, paths);
+ public Builder addMainDexRulesFiles(Path... paths) {
+ for (Path path : paths) {
+ mainDexRules.add(new ProguardConfigurationSourceFile(path));
+ }
return self();
}
/**
* Add proguard configuration file resources for automatic main dex list calculation.
*/
- public Builder addMainDexRules(List<Path> paths) {
- mainDexRules.addAll(paths);
+ public Builder addMainDexRulesFiles(List<Path> paths) {
+ for (Path path : paths) {
+ mainDexRules.add(new ProguardConfigurationSourceFile(path));
+ }
+ return self();
+ }
+
+ /**
+ * Add proguard configuration for automatic main dex list calculation.
+ */
+ public Builder addMainDexRules(List<String> lines) {
+ mainDexRules.add(new ProguardConfigurationSourceStrings(lines));
return self();
}
@@ -98,7 +112,9 @@
* Add proguard configuration file resources.
*/
public Builder addProguardConfigurationFiles(Path... paths) {
- Collections.addAll(proguardConfigFiles, paths);
+ for (Path path : paths) {
+ proguardConfigs.add(new ProguardConfigurationSourceFile(path));
+ }
return self();
}
@@ -106,7 +122,17 @@
* Add proguard configuration file resources.
*/
public Builder addProguardConfigurationFiles(List<Path> paths) {
- proguardConfigFiles.addAll(paths);
+ for (Path path : paths) {
+ proguardConfigs.add(new ProguardConfigurationSourceFile(path));
+ }
+ return self();
+ }
+
+ /**
+ * Add proguard configuration.
+ */
+ public Builder addProguardConfiguration(List<String> lines) {
+ proguardConfigs.add(new ProguardConfigurationSourceStrings(lines));
return self();
}
@@ -184,12 +210,12 @@
mainDexKeepRules = parser.getConfig().getRules();
}
ProguardConfiguration configuration;
- if (proguardConfigFiles.isEmpty()) {
+ if (proguardConfigs.isEmpty()) {
configuration = ProguardConfiguration.defaultConfiguration(factory);
} else {
ProguardConfigurationParser parser = new ProguardConfigurationParser(factory);
try {
- parser.parse(proguardConfigFiles);
+ parser.parse(proguardConfigs);
} catch (ProguardRuleParserException e) {
throw new CompilationException(e.getMessage(), e.getCause());
}
@@ -324,7 +350,7 @@
} else if (arg.equals("--no-minification")) {
builder.setMinification(false);
} else if (arg.equals("--main-dex-rules")) {
- builder.addMainDexRules(Paths.get(args[++i]));
+ builder.addMainDexRulesFiles(Paths.get(args[++i]));
} else if (arg.equals("--main-dex-list")) {
builder.addMainDexListFiles(Paths.get(args[++i]));
} else if (arg.equals("--main-dex-list-output")) {
diff --git a/src/main/java/com/android/tools/r8/bisect/Bisect.java b/src/main/java/com/android/tools/r8/bisect/Bisect.java
index ee4477d..81d5f9a 100644
--- a/src/main/java/com/android/tools/r8/bisect/Bisect.java
+++ b/src/main/java/com/android/tools/r8/bisect/Bisect.java
@@ -7,6 +7,7 @@
import com.android.tools.r8.dex.ApplicationReader;
import com.android.tools.r8.dex.ApplicationWriter;
import com.android.tools.r8.errors.CompilationError;
+import com.android.tools.r8.errors.DexOverflowException;
import com.android.tools.r8.graph.AppInfo;
import com.android.tools.r8.graph.DexApplication;
import com.android.tools.r8.graph.DexProgramClass;
@@ -174,7 +175,7 @@
}
private static void writeApp(DexApplication app, Path output, ExecutorService executor)
- throws IOException, ExecutionException {
+ throws IOException, ExecutionException, DexOverflowException {
InternalOptions options = new InternalOptions();
AppInfo appInfo = new AppInfo(app);
ApplicationWriter writer = new ApplicationWriter(app, appInfo, options, null, null, null, null);
diff --git a/src/main/java/com/android/tools/r8/bisect/BisectOptions.java b/src/main/java/com/android/tools/r8/bisect/BisectOptions.java
index d0051dc..8bd2c90 100644
--- a/src/main/java/com/android/tools/r8/bisect/BisectOptions.java
+++ b/src/main/java/com/android/tools/r8/bisect/BisectOptions.java
@@ -121,8 +121,7 @@
return new BisectOptions(goodBuild, badBuild, stateFile, command, output, result);
}
- private static <T> T require(OptionSet options, OptionSpec<T> option, String flag)
- throws IOException {
+ private static <T> T require(OptionSet options, OptionSpec<T> option, String flag) {
T value = options.valueOf(option);
if (value != null) {
return value;
@@ -130,7 +129,7 @@
throw new CompilationError("Missing required option: --" + flag);
}
- private static File exists(String path, String flag) throws IOException {
+ private static File exists(String path, String flag) {
File file = new File(path);
if (file.exists()) {
return file;
@@ -138,7 +137,7 @@
throw new CompilationError("File --" + flag + ": " + file + " does not exist");
}
- private static File directoryExists(String path, String flag) throws IOException {
+ private static File directoryExists(String path, String flag) {
File file = new File(path);
if (file.exists() && file.isDirectory()) {
return file;
diff --git a/src/main/java/com/android/tools/r8/code/Instruction.java b/src/main/java/com/android/tools/r8/code/Instruction.java
index 85e0318..504b605 100644
--- a/src/main/java/com/android/tools/r8/code/Instruction.java
+++ b/src/main/java/com/android/tools/r8/code/Instruction.java
@@ -228,6 +228,7 @@
throw new InternalCompilerError("Instruction " + payloadUser + " is not a payload user");
}
+ @Override
public String toString() {
return toString(null);
}
diff --git a/src/main/java/com/android/tools/r8/compatdx/CompatDx.java b/src/main/java/com/android/tools/r8/compatdx/CompatDx.java
index 9bcf510..fdd5964 100644
--- a/src/main/java/com/android/tools/r8/compatdx/CompatDx.java
+++ b/src/main/java/com/android/tools/r8/compatdx/CompatDx.java
@@ -36,11 +36,11 @@
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.util.ArrayList;
+import java.util.Enumeration;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.zip.ZipEntry;
-import java.util.zip.ZipException;
-import java.util.zip.ZipInputStream;
+import java.util.zip.ZipFile;
import java.util.zip.ZipOutputStream;
import joptsimple.OptionParser;
import joptsimple.OptionSet;
@@ -250,7 +250,7 @@
}
}
- private DxCompatOptions(OptionSet options, Spec spec) throws DxParseError {
+ private DxCompatOptions(OptionSet options, Spec spec) {
help = options.has(spec.help);
debug = options.has(spec.debug);
verbose = options.has(spec.verbose);
@@ -383,7 +383,7 @@
}
if (dexArgs.dumpTo != null) {
- throw new Unimplemented("dump-to file not yet supported");
+ System.out.println("dump-to file not yet supported");
}
if (dexArgs.positions == PositionInfo.NONE) {
@@ -529,16 +529,16 @@
// For each input archive file, add all class files within.
for (Path input : inputs) {
if (isArchive(input)) {
- try (ZipInputStream in = new ZipInputStream(Files.newInputStream(input))) {
- ZipEntry entry;
- while ((entry = in.getNextEntry()) != null) {
+ try (ZipFile zipFile = new ZipFile(input.toFile())) {
+ final Enumeration<? extends ZipEntry> entries = zipFile.entries();
+ while (entries.hasMoreElements()) {
+ ZipEntry entry = entries.nextElement();
if (isClassFile(Paths.get(entry.getName()))) {
- addEntry(entry.getName(), in, out);
+ try (InputStream entryStream = zipFile.getInputStream(entry)) {
+ addEntry(entry.getName(), entryStream, out);
+ }
}
}
- } catch (ZipException e) {
- throw new CompilationError(
- "Zip error while reading '" + input + "': " + e.getMessage(), e);
}
}
}
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 cca3520..a07a4a3 100644
--- a/src/main/java/com/android/tools/r8/dex/ApplicationReader.java
+++ b/src/main/java/com/android/tools/r8/dex/ApplicationReader.java
@@ -35,7 +35,6 @@
import com.android.tools.r8.utils.MainDexList;
import com.android.tools.r8.utils.ThreadUtils;
import com.android.tools.r8.utils.Timing;
-import com.google.common.io.Closer;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
@@ -130,8 +129,7 @@
}
private void readProguardMap(DexApplication.Builder builder, ExecutorService executorService,
- List<Future<?>> futures)
- throws IOException {
+ List<Future<?>> futures) {
// Read the Proguard mapping file in parallel with DexCode and DexProgramClass items.
if (inputApp.hasProguardMap()) {
futures.add(executorService.submit(() -> {
@@ -145,8 +143,7 @@
}
private void readMainDexList(DexApplication.Builder builder, ExecutorService executorService,
- List<Future<?>> futures)
- throws IOException {
+ List<Future<?>> futures) {
if (inputApp.hasMainDexList()) {
futures.add(executorService.submit(() -> {
for (Resource resource : inputApp.getMainDexListResources()) {
@@ -184,7 +181,7 @@
}
private <T extends DexClass> void readDexSources(List<Resource> dexSources,
- ClassKind classKind, Queue<T> classes) throws IOException, ExecutionException {
+ ClassKind classKind, Queue<T> classes) throws IOException {
if (dexSources.size() > 0) {
List<DexFileReader> fileReaders = new ArrayList<>(dexSources.size());
int computedMinApiLevel = options.minApiLevel;
@@ -211,7 +208,7 @@
}
private <T extends DexClass> void readClassSources(List<Resource> classSources,
- ClassKind classKind, Queue<T> classes) throws IOException, ExecutionException {
+ ClassKind classKind, Queue<T> classes) {
JarClassFileReader reader = new JarClassFileReader(
application, classKind.bridgeConsumer(classes::add));
// Read classes in parallel.
@@ -227,7 +224,7 @@
}
}
- void readSources() throws IOException, ExecutionException {
+ void readSources() throws IOException {
readDexSources(inputApp.getDexProgramResources(), PROGRAM, programClasses);
readClassSources(inputApp.getClassProgramResources(), PROGRAM, programClasses);
}
diff --git a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
index 412b291..46ee85c 100644
--- a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
@@ -4,10 +4,8 @@
package com.android.tools.r8.dex;
import com.android.tools.r8.ApiLevelException;
-import com.android.tools.r8.dex.VirtualFile.FilePerClassDistributor;
-import com.android.tools.r8.dex.VirtualFile.FillFilesDistributor;
-import com.android.tools.r8.dex.VirtualFile.PackageMapDistributor;
import com.android.tools.r8.errors.CompilationError;
+import com.android.tools.r8.errors.DexOverflowException;
import com.android.tools.r8.graph.AppInfo;
import com.android.tools.r8.graph.DexAnnotation;
import com.android.tools.r8.graph.DexAnnotationDirectory;
@@ -129,7 +127,7 @@
}
public AndroidApp write(PackageDistribution packageDistribution, ExecutorService executorService)
- throws IOException, ExecutionException {
+ throws IOException, ExecutionException, DexOverflowException {
application.timing.begin("DexApplication.write");
try {
application.dexItemFactory.sort(namingLens);
@@ -243,7 +241,7 @@
.replace('.', '/') + ".class";
}
- private byte[] writeMainDexList() throws IOException {
+ private byte[] writeMainDexList() {
if (application.mainDexList.isEmpty()) {
return null;
}
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 09b3c33..7bf5e8c 100644
--- a/src/main/java/com/android/tools/r8/dex/DexFileReader.java
+++ b/src/main/java/com/android/tools/r8/dex/DexFileReader.java
@@ -81,7 +81,7 @@
return parseMapFrom(new DexFile(stream));
}
- private static Segment[] parseMapFrom(DexFile dex) throws IOException {
+ private static Segment[] parseMapFrom(DexFile dex) {
DexFileReader reader = new DexFileReader(dex, ClassKind.PROGRAM, new DexItemFactory());
return reader.segments;
}
diff --git a/src/main/java/com/android/tools/r8/dex/FileWriter.java b/src/main/java/com/android/tools/r8/dex/FileWriter.java
index 38e292c..d820747 100644
--- a/src/main/java/com/android/tools/r8/dex/FileWriter.java
+++ b/src/main/java/com/android/tools/r8/dex/FileWriter.java
@@ -47,6 +47,7 @@
import com.android.tools.r8.naming.MemberNaming.MethodSignature;
import com.android.tools.r8.naming.MemberNaming.Signature;
import com.android.tools.r8.naming.NamingLens;
+import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.LebUtils;
import com.android.tools.r8.utils.ThrowingConsumer;
@@ -296,8 +297,7 @@
if (method.accessFlags.isStatic()) {
if (!options.canUseDefaultAndStaticInterfaceMethods()) {
throw new ApiLevelException(
- Constants.ANDROID_N_API,
- "Android N",
+ AndroidApiLevel.N,
"Static interface methods",
method.method.toSourceString());
}
@@ -310,8 +310,7 @@
if (!method.accessFlags.isAbstract() && !method.accessFlags.isPrivate() &&
!options.canUseDefaultAndStaticInterfaceMethods()) {
throw new ApiLevelException(
- Constants.ANDROID_N_API,
- "Android N",
+ AndroidApiLevel.N,
"Default interface methods",
method.method.toSourceString());
}
@@ -322,8 +321,7 @@
return;
}
throw new ApiLevelException(
- Constants.ANDROID_N_API,
- "Android N",
+ AndroidApiLevel.N,
"Private interface methods",
method.method.toSourceString());
}
@@ -445,10 +443,10 @@
private void writeFieldItem(DexField field) {
int classIdx = mapping.getOffsetFor(field.clazz);
- assert (short) classIdx == classIdx;
+ assert (classIdx & 0xFFFF) == classIdx;
dest.putShort((short) classIdx);
int typeIdx = mapping.getOffsetFor(field.type);
- assert (short) typeIdx == typeIdx;
+ assert (typeIdx & 0xFFFF) == typeIdx;
dest.putShort((short) typeIdx);
DexString name = namingLens.lookupName(field);
dest.putInt(mapping.getOffsetFor(name));
@@ -456,10 +454,10 @@
private void writeMethodItem(DexMethod method) {
int classIdx = mapping.getOffsetFor(method.holder);
- assert (short) classIdx == classIdx;
+ assert (classIdx & 0xFFFF) == classIdx;
dest.putShort((short) classIdx);
int protoIdx = mapping.getOffsetFor(method.proto);
- assert (short) protoIdx == protoIdx;
+ assert (protoIdx & 0xFFFF) == protoIdx;
dest.putShort((short) protoIdx);
DexString name = namingLens.lookupName(method);
dest.putInt(mapping.getOffsetFor(name));
@@ -694,7 +692,7 @@
assert methodHandle.isFieldHandle();
fieldOrMethodIdx = mapping.getOffsetFor(methodHandle.asField());
}
- assert (short) fieldOrMethodIdx == fieldOrMethodIdx;
+ assert (fieldOrMethodIdx & 0xFFFF) == fieldOrMethodIdx;
dest.putShort((short) fieldOrMethodIdx);
dest.putShort((short) 0); // unused
}
@@ -1352,8 +1350,7 @@
private void checkThatInvokeCustomIsAllowed() throws ApiLevelException {
if (!options.canUseInvokeCustom()) {
throw new ApiLevelException(
- Constants.ANDROID_O_API,
- "Android O",
+ AndroidApiLevel.O,
"Invoke-customs",
null /* sourceString */);
}
diff --git a/src/main/java/com/android/tools/r8/dex/Segment.java b/src/main/java/com/android/tools/r8/dex/Segment.java
index 0d782d6..7836438 100644
--- a/src/main/java/com/android/tools/r8/dex/Segment.java
+++ b/src/main/java/com/android/tools/r8/dex/Segment.java
@@ -71,6 +71,7 @@
}
}
+ @Override
public String toString() {
return typeName() + " @" + offset + " " + length;
}
diff --git a/src/main/java/com/android/tools/r8/dex/VirtualFile.java b/src/main/java/com/android/tools/r8/dex/VirtualFile.java
index cbca177..c40d5a5 100644
--- a/src/main/java/com/android/tools/r8/dex/VirtualFile.java
+++ b/src/main/java/com/android/tools/r8/dex/VirtualFile.java
@@ -5,7 +5,7 @@
import com.android.tools.r8.errors.CompilationError;
import com.android.tools.r8.errors.InternalCompilerError;
-import com.android.tools.r8.errors.MainDexError;
+import com.android.tools.r8.errors.DexOverflowException;
import com.android.tools.r8.graph.DexApplication;
import com.android.tools.r8.graph.DexCallSite;
import com.android.tools.r8.graph.DexClass;
@@ -173,11 +173,11 @@
return isFull(transaction.getNumberOfMethods(), transaction.getNumberOfFields(), MAX_ENTRIES);
}
- void throwIfFull(boolean hasMainDexList) {
+ void throwIfFull(boolean hasMainDexList) throws DexOverflowException {
if (!isFull()) {
return;
}
- throw new MainDexError(
+ throw new DexOverflowException(
hasMainDexList,
transaction.getNumberOfMethods(),
transaction.getNumberOfFields(),
@@ -217,7 +217,8 @@
this.writer = writer;
}
- public abstract Map<Integer, VirtualFile> run() throws ExecutionException, IOException;
+ public abstract Map<Integer, VirtualFile> run()
+ throws ExecutionException, IOException, DexOverflowException;
}
public static class FilePerClassDistributor extends Distributor {
@@ -226,7 +227,8 @@
super(writer);
}
- public Map<Integer, VirtualFile> run() throws ExecutionException, IOException {
+ @Override
+ public Map<Integer, VirtualFile> run() {
// Assign dedicated virtual files for all program classes.
for (DexProgramClass clazz : application.classes()) {
VirtualFile file = new VirtualFile(nameToFileMap.size(), writer.namingLens);
@@ -258,7 +260,7 @@
originalNames = computeOriginalNameMapping(classes, application.getProguardMap());
}
- protected void fillForMainDexList(Set<DexProgramClass> classes) {
+ protected void fillForMainDexList(Set<DexProgramClass> classes) throws DexOverflowException {
if (!application.mainDexList.isEmpty()) {
VirtualFile mainDexFile = nameToFileMap.get(0);
for (DexType type : application.mainDexList) {
@@ -322,7 +324,8 @@
this.fillStrategy = FillStrategy.FILL_MAX;
}
- public Map<Integer, VirtualFile> run() throws ExecutionException, IOException {
+ @Override
+ public Map<Integer, VirtualFile> run() throws IOException, DexOverflowException {
// First fill required classes into the main dex file.
fillForMainDexList(classes);
if (classes.isEmpty()) {
@@ -357,7 +360,8 @@
}
@Override
- public Map<Integer, VirtualFile> run() throws ExecutionException, IOException {
+ public Map<Integer, VirtualFile> run()
+ throws ExecutionException, IOException, DexOverflowException {
// Add all classes to the main dex file.
for (DexProgramClass programClass : classes) {
mainDexFile.addClass(programClass);
@@ -381,7 +385,9 @@
this.executorService = executorService;
}
- public Map<Integer, VirtualFile> run() throws ExecutionException, IOException {
+ @Override
+ public Map<Integer, VirtualFile> run()
+ throws ExecutionException, IOException, DexOverflowException {
// Strategy for distributing classes for write out:
// 1. Place all files in the package distribution file in the proposed files (if any).
// 2. Place the remaining files based on their packages in sorted order.
diff --git a/src/main/java/com/android/tools/r8/errors/DexOverflowException.java b/src/main/java/com/android/tools/r8/errors/DexOverflowException.java
new file mode 100644
index 0000000..a94d206
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/errors/DexOverflowException.java
@@ -0,0 +1,91 @@
+// 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.errors;
+
+import com.android.tools.r8.CompilationException;
+
+/**
+ * Signals when there were too many items to fit in a given dex file.
+ */
+public class DexOverflowException extends CompilationException {
+
+ private final boolean hasMainDexList;
+ private final long numOfMethods;
+ private final long numOfFields;
+ private final long maxNumOfEntries;
+
+ public DexOverflowException(
+ boolean hasMainDexList, long numOfMethods, long numOfFields, long maxNumOfEntries) {
+ super();
+ this.hasMainDexList = hasMainDexList;
+ this.numOfMethods = numOfMethods;
+ this.numOfFields = numOfFields;
+ this.maxNumOfEntries = maxNumOfEntries;
+ }
+
+ private StringBuilder getGeneralMessage() {
+ StringBuilder messageBuilder = new StringBuilder();
+ // General message: Cannot fit.
+ messageBuilder.append("Cannot fit requested classes in ");
+ messageBuilder.append(hasMainDexList ? "the main-" : "a single ");
+ messageBuilder.append("dex file");
+
+ return messageBuilder;
+ }
+
+ private String getNumberRelatedMessage() {
+ StringBuilder messageBuilder = new StringBuilder();
+ // Show the numbers of methods and/or fields that exceed the limit.
+ if (numOfMethods > maxNumOfEntries) {
+ messageBuilder.append("# methods: ");
+ messageBuilder.append(numOfMethods);
+ messageBuilder.append(" > ").append(maxNumOfEntries);
+ if (numOfFields > maxNumOfEntries) {
+ messageBuilder.append(" ; ");
+ }
+ }
+ if (numOfFields > maxNumOfEntries) {
+ messageBuilder.append("# fields: ");
+ messageBuilder.append(numOfFields);
+ messageBuilder.append(" > ").append(maxNumOfEntries);
+ }
+
+ return messageBuilder.toString();
+ }
+
+ @Override
+ public String getMessage() {
+ // Default message
+ return getGeneralMessage()
+ .append(" (")
+ .append(getNumberRelatedMessage())
+ .append(")")
+ .toString();
+ }
+
+ @Override
+ public String getMessageForD8() {
+ StringBuilder messageBuilder = getGeneralMessage();
+ if (!hasMainDexList) {
+ messageBuilder.append(". ");
+ messageBuilder.append("Try supplying a main-dex list");
+ }
+ messageBuilder.append(".").append(System.getProperty("line.separator"));
+ messageBuilder.append(getNumberRelatedMessage());
+ return messageBuilder.toString();
+ }
+
+ @Override
+ public String getMessageForR8() {
+ StringBuilder messageBuilder = getGeneralMessage();
+ if (!hasMainDexList) {
+ messageBuilder.append(". ");
+ messageBuilder.append("Try supplying a main-dex list or main dex rules");
+ }
+ messageBuilder.append(".").append(System.getProperty("line.separator"));
+ messageBuilder.append(getNumberRelatedMessage());
+ return messageBuilder.toString();
+ }
+
+}
diff --git a/src/main/java/com/android/tools/r8/errors/MainDexError.java b/src/main/java/com/android/tools/r8/errors/MainDexError.java
deleted file mode 100644
index 4c23aca..0000000
--- a/src/main/java/com/android/tools/r8/errors/MainDexError.java
+++ /dev/null
@@ -1,89 +0,0 @@
-// 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.errors;
-
-/**
- * Exception regarding main-dex list and main dex rules.
- *
- * Depending on tool kind, this exception should be massaged, e.g., adding appropriate suggestions,
- * and re-thrown as {@link CompilationError}, which will be in turn informed to the user as an
- * expected compilation error.
- */
-public class MainDexError extends RuntimeException {
-
- private final boolean hasMainDexList;
- private final long numOfMethods;
- private final long numOfFields;
- private final long maxNumOfEntries;
-
- public MainDexError(
- boolean hasMainDexList, long numOfMethods, long numOfFields, long maxNumOfEntries) {
- this.hasMainDexList = hasMainDexList;
- this.numOfMethods = numOfMethods;
- this.numOfFields = numOfFields;
- this.maxNumOfEntries = maxNumOfEntries;
- }
-
- private String getGeneralMessage() {
- StringBuilder messageBuilder = new StringBuilder();
- // General message: Cannot fit.
- messageBuilder.append("Cannot fit requested classes in ");
- messageBuilder.append(hasMainDexList ? "the main-" : "a single ");
- messageBuilder.append("dex file.\n");
-
- return messageBuilder.toString();
- }
-
- private String getNumberRelatedMessage() {
- StringBuilder messageBuilder = new StringBuilder();
- // Show the numbers of methods and/or fields that exceed the limit.
- if (numOfMethods > maxNumOfEntries) {
- messageBuilder.append("# methods: ");
- messageBuilder.append(numOfMethods);
- messageBuilder.append(" > ").append(maxNumOfEntries).append('\n');
- }
- if (numOfFields > maxNumOfEntries) {
- messageBuilder.append("# fields: ");
- messageBuilder.append(numOfFields);
- messageBuilder.append(" > ").append(maxNumOfEntries).append('\n');
- }
-
- return messageBuilder.toString();
- }
-
- @Override
- public String getMessage() {
- // Placeholder to generate a general error message for other (minor) utilities:
- // Bisect, disassembler, dexsegments.
- // Implement tool-specific error message generator, like D8 and R8 below, if necessary.
- return getGeneralMessage() + getNumberRelatedMessage();
- }
-
- public String getMessageForD8() {
- StringBuilder messageBuilder = new StringBuilder();
- messageBuilder.append(getGeneralMessage());
- if (hasMainDexList) {
- messageBuilder.append("Classes required by the main-dex list ");
- messageBuilder.append("do not fit in one dex.\n");
- } else {
- messageBuilder.append("Try supplying a main-dex list.\n");
- }
- messageBuilder.append(getNumberRelatedMessage());
- return messageBuilder.toString();
- }
-
- public String getMessageForR8() {
- StringBuilder messageBuilder = new StringBuilder();
- messageBuilder.append(getGeneralMessage());
- if (hasMainDexList) {
- messageBuilder.append("Classes required by main dex rules and the main-dex list ");
- messageBuilder.append("do not fit in one dex.\n");
- } else {
- messageBuilder.append("Try supplying a main-dex list or main dex rules.\n");
- }
- messageBuilder.append(getNumberRelatedMessage());
- return messageBuilder.toString();
- }
-
-}
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 82febb9..50c22b8 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClass.java
@@ -3,7 +3,6 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.graph;
-import com.android.tools.r8.ApiLevelException;
import com.android.tools.r8.Resource;
import com.android.tools.r8.dex.MixedSectionCollection;
import com.android.tools.r8.errors.CompilationError;
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 1a8602c..8da2178 100644
--- a/src/main/java/com/android/tools/r8/graph/DexCode.java
+++ b/src/main/java/com/android/tools/r8/graph/DexCode.java
@@ -82,6 +82,17 @@
this.debugInfo = debugInfo;
}
+ public boolean hasDebugPositions() {
+ if (debugInfo != null) {
+ for (DexDebugEvent event : debugInfo.events) {
+ if (event instanceof DexDebugEvent.Default) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
public DexDebugInfo debugInfoWithAdditionalFirstParameter(DexString name) {
if (debugInfo == null) {
return null;
@@ -143,7 +154,7 @@
return false;
}
- boolean isEmptyVoidMethod() {
+ public boolean isEmptyVoidMethod() {
return instructions.length == 1 && instructions[0] instanceof ReturnVoid;
}
diff --git a/src/main/java/com/android/tools/r8/graph/DexDebugEventBuilder.java b/src/main/java/com/android/tools/r8/graph/DexDebugEventBuilder.java
index ffaf42b..3d68ace 100644
--- a/src/main/java/com/android/tools/r8/graph/DexDebugEventBuilder.java
+++ b/src/main/java/com/android/tools/r8/graph/DexDebugEventBuilder.java
@@ -91,8 +91,8 @@
// If this is the end of the block clear out the pending state.
pendingLocals = null;
pendingLocalChanges = false;
- } else {
- // For non-exit / pc-advancing instructions emit any pending changes.
+ } else if (pc != emittedPc) {
+ // For non-exit / pc-advancing instructions emit any pending changes once possible.
emitLocalChanges(pc);
}
}
@@ -224,6 +224,7 @@
DexString nextFile,
List<DexDebugEvent> events,
DexItemFactory factory) {
+ assert previousPc != nextPc;
int pcDelta = previousPc == NO_PC_INFO ? nextPc : nextPc - previousPc;
int lineDelta = nextLine == NO_LINE_INFO ? 0 : nextLine - previousLine;
assert pcDelta >= 0;
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 8d28546..b76177b 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -26,6 +26,7 @@
import com.android.tools.r8.ir.code.ValueNumberGenerator;
import com.android.tools.r8.ir.conversion.DexBuilder;
import com.android.tools.r8.ir.optimize.Inliner.Constraint;
+import com.android.tools.r8.ir.optimize.Inliner.Reason;
import com.android.tools.r8.ir.regalloc.RegisterAllocator;
import com.android.tools.r8.ir.synthetic.ForwardMethodSourceCode;
import com.android.tools.r8.ir.synthetic.SynthesizedCode;
@@ -107,13 +108,13 @@
return accessFlags.isConstructor() && accessFlags.isStatic();
}
- public boolean isInliningCandidate(DexEncodedMethod container, boolean alwaysInline,
+ public boolean isInliningCandidate(DexEncodedMethod container, Reason inliningReason,
AppInfoWithSubtyping appInfo) {
if (isClassInitializer()) {
// This will probably never happen but never inline a class initializer.
return false;
}
- if (alwaysInline) {
+ if (inliningReason == Reason.FORCE) {
return true;
}
switch (compilationState) {
@@ -208,6 +209,11 @@
code = null;
}
+ public boolean hasDebugPositions() {
+ assert code != null && code.isDexCode();
+ return code.asDexCode().hasDebugPositions();
+ }
+
public String qualifiedName() {
return method.qualifiedName();
}
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 7dc77de..527f27b 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -162,7 +162,7 @@
"Ldalvik/annotation/SourceDebugExtension;");
public final DexType annotationThrows = createType("Ldalvik/annotation/Throws;");
- public void clearSubtypeInformation() {
+ public synchronized void clearSubtypeInformation() {
types.values().forEach(DexType::clearSubtypeInformation);
}
diff --git a/src/main/java/com/android/tools/r8/graph/DexValue.java b/src/main/java/com/android/tools/r8/graph/DexValue.java
index 08c8a1c..73b3242 100644
--- a/src/main/java/com/android/tools/r8/graph/DexValue.java
+++ b/src/main/java/com/android/tools/r8/graph/DexValue.java
@@ -151,6 +151,10 @@
return value == DEFAULT.value ? DEFAULT : new DexValueByte(value);
}
+ public byte getValue() {
+ return value;
+ }
+
@Override
public void writeTo(DexOutputBuffer dest, ObjectToOffsetMapping mapping) {
writeHeader(VALUE_BYTE, 0, dest);
@@ -196,6 +200,10 @@
return value == DEFAULT.value ? DEFAULT : new DexValueShort(value);
}
+ public short getValue() {
+ return value;
+ }
+
@Override
public void writeTo(DexOutputBuffer dest, ObjectToOffsetMapping mapping) {
writeIntegerTo(VALUE_SHORT, value, Short.BYTES, dest);
@@ -240,6 +248,10 @@
return value == DEFAULT.value ? DEFAULT : new DexValueChar(value);
}
+ public char getValue() {
+ return value;
+ }
+
@Override
public void writeTo(DexOutputBuffer dest, ObjectToOffsetMapping mapping) {
dest.forward(1);
@@ -288,6 +300,10 @@
return value == DEFAULT.value ? DEFAULT : new DexValueInt(value);
}
+ public int getValue() {
+ return value;
+ }
+
@Override
public void writeTo(DexOutputBuffer dest, ObjectToOffsetMapping mapping) {
writeIntegerTo(VALUE_INT, value, Integer.BYTES, dest);
@@ -332,6 +348,10 @@
return value == DEFAULT.value ? DEFAULT : new DexValueLong(value);
}
+ public long getValue() {
+ return value;
+ }
+
@Override
public void writeTo(DexOutputBuffer dest, ObjectToOffsetMapping mapping) {
writeIntegerTo(VALUE_LONG, value, Long.BYTES, dest);
@@ -376,6 +396,10 @@
return Float.compare(value, DEFAULT.value) == 0 ? DEFAULT : new DexValueFloat(value);
}
+ public float getValue() {
+ return value;
+ }
+
@Override
public void writeTo(DexOutputBuffer dest, ObjectToOffsetMapping mapping) {
dest.forward(1);
@@ -420,6 +444,10 @@
return Double.compare(value, DEFAULT.value) == 0 ? DEFAULT : new DexValueDouble(value);
}
+ public double getValue() {
+ return value;
+ }
+
@Override
public void writeTo(DexOutputBuffer dest, ObjectToOffsetMapping mapping) {
dest.forward(1);
@@ -508,6 +536,10 @@
super(value);
}
+ public DexString getValue() {
+ return value;
+ }
+
@Override
protected byte getValueKind() {
return VALUE_STRING;
@@ -710,6 +742,10 @@
private DexValueNull() {
}
+ public Object getValue() {
+ return null;
+ }
+
@Override
public void writeTo(DexOutputBuffer dest, ObjectToOffsetMapping mapping) {
writeHeader(VALUE_NULL, 0, dest);
@@ -751,6 +787,10 @@
return value ? TRUE : FALSE;
}
+ public boolean getValue() {
+ return value;
+ }
+
@Override
public void writeTo(DexOutputBuffer dest, ObjectToOffsetMapping mapping) {
writeHeader(VALUE_BOOLEAN, value ? 1 : 0, dest);
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 b9173dd..3abe7c5 100644
--- a/src/main/java/com/android/tools/r8/graph/GraphLense.java
+++ b/src/main/java/com/android/tools/r8/graph/GraphLense.java
@@ -118,18 +118,21 @@
@Override
public DexType lookupType(DexType type, DexEncodedMethod context) {
if (type.isArrayType()) {
- DexType result = arrayTypeCache.get(type);
- if (result == null) {
- DexType baseType = type.toBaseType(dexItemFactory);
- DexType newType = lookupType(baseType, context);
- if (baseType == newType) {
- result = type;
- } else {
- result = type.replaceBaseType(newType, dexItemFactory);
+ synchronized(this) {
+ // This block need to be synchronized due to arrayTypeCache.
+ DexType result = arrayTypeCache.get(type);
+ if (result == null) {
+ DexType baseType = type.toBaseType(dexItemFactory);
+ DexType newType = lookupType(baseType, context);
+ if (baseType == newType) {
+ result = type;
+ } else {
+ result = type.replaceBaseType(newType, dexItemFactory);
+ }
+ arrayTypeCache.put(type, result);
}
- arrayTypeCache.put(type, result);
+ return result;
}
- return result;
}
DexType previous = previousLense.lookupType(type, context);
return typeMap.getOrDefault(previous, previous);
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 f5bc204..40a4776 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
@@ -140,29 +140,13 @@
assert newInstruction.outValue() != null;
current.outValue().replaceUsers(newInstruction.outValue());
}
- for (Value value : current.getDebugValues()) {
- replaceInstructionInList(current, newInstruction, value.getDebugLocalStarts());
- replaceInstructionInList(current, newInstruction, value.getDebugLocalEnds());
- value.removeDebugUser(current);
- newInstruction.addDebugValue(value);
- }
+ current.moveDebugValues(newInstruction);
newInstruction.setBlock(block);
listIterator.remove();
listIterator.add(newInstruction);
current.clearBlock();
}
- private static void replaceInstructionInList(
- Instruction instruction,
- Instruction newInstruction,
- List<Instruction> instructions) {
- for (int i = 0; i < instructions.size(); i++) {
- if (instructions.get(i) == instruction) {
- instructions.set(i, newInstruction);
- }
- }
- }
-
public BasicBlock split(IRCode code, ListIterator<BasicBlock> blocksIterator) {
List<BasicBlock> blocks = code.blocks;
assert blocksIterator == null || IteratorUtils.peekPrevious(blocksIterator) == block;
diff --git a/src/main/java/com/android/tools/r8/ir/code/ConstNumber.java b/src/main/java/com/android/tools/r8/ir/code/ConstNumber.java
index e376361..ef5a025 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ConstNumber.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ConstNumber.java
@@ -26,7 +26,6 @@
// are all for fixed register values. All other values that are used as the destination for
// const number instructions should be marked as constants.
assert dest.isFixedRegisterValue() || dest.definition.isConstNumber();
- assert type != ConstType.OBJECT;
this.type = type;
this.value = value;
}
@@ -50,7 +49,7 @@
}
public int getIntValue() {
- assert type == ConstType.INT || type == ConstType.INT_OR_FLOAT;
+ assert type == ConstType.INT || type == ConstType.INT_OR_FLOAT || type == ConstType.OBJECT;
return (int) value;
}
@@ -101,7 +100,7 @@
}
int register = builder.allocatedRegister(dest(), getNumber());
- if (MoveType.fromConstType(type) == MoveType.SINGLE) {
+ if (MoveType.fromConstType(type) == MoveType.SINGLE || type == ConstType.OBJECT) {
assert NumberUtils.is32Bit(value);
if ((register & 0xf) == register && NumberUtils.is4Bit(value)) {
builder.add(this, new Const4(register, (int) value));
diff --git a/src/main/java/com/android/tools/r8/ir/code/Div.java b/src/main/java/com/android/tools/r8/ir/code/Div.java
index 8fd9bf0..7dc5e75 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Div.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Div.java
@@ -88,7 +88,7 @@
@Override
public boolean canBeFolded() {
- return super.canBeFolded() && !rightValue().getConstInstruction().asConstNumber().isZero();
+ return super.canBeFolded() && !rightValue().isZero();
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/Instruction.java b/src/main/java/com/android/tools/r8/ir/code/Instruction.java
index e99dbf1..1cc21a3 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Instruction.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Instruction.java
@@ -74,8 +74,9 @@
if (debugValues == null) {
debugValues = new HashSet<>();
}
- debugValues.add(value);
- value.addDebugUser(this);
+ if (debugValues.add(value)) {
+ value.addDebugUser(this);
+ }
}
public static void clearUserInfo(Instruction instruction) {
@@ -94,19 +95,17 @@
public abstract void buildDex(DexBuilder builder);
- public void replaceValue(Value oldValue, Value newValue, List<Instruction> toRemove) {
+ public void replaceValue(Value oldValue, Value newValue) {
for (int i = 0; i < inValues.size(); i++) {
if (oldValue == inValues.get(i)) {
inValues.set(i, newValue);
newValue.addUser(this);
- toRemove.add(this);
}
}
}
- public void replaceDebugValue(Value oldValue, Value newValue, List<Instruction> toRemove) {
+ public void replaceDebugValue(Value oldValue, Value newValue) {
if (debugValues.remove(oldValue)) {
- toRemove.add(this);
if (newValue.getLocalInfo() != null) {
// TODO(zerny): Insert a write if replacing a phi with different debug-local info.
addDebugValue(newValue);
@@ -115,6 +114,22 @@
}
}
+ public void moveDebugValues(Instruction target) {
+ if (debugValues == null) {
+ return;
+ }
+ for (Value value : debugValues) {
+ value.replaceDebugUser(this, target);
+ }
+ debugValues.clear();
+ }
+
+ public void moveDebugValue(Value value, Instruction target) {
+ assert debugValues.contains(value);
+ value.replaceDebugUser(this, target);
+ debugValues.remove(value);
+ }
+
/**
* Returns the basic block containing this instruction.
*/
diff --git a/src/main/java/com/android/tools/r8/ir/code/MoveException.java b/src/main/java/com/android/tools/r8/ir/code/MoveException.java
index 3dff4f3..61ed928 100644
--- a/src/main/java/com/android/tools/r8/ir/code/MoveException.java
+++ b/src/main/java/com/android/tools/r8/ir/code/MoveException.java
@@ -27,6 +27,7 @@
}
public void setPosition(DebugPosition position) {
+ assert this.position == null;
this.position = position;
}
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 96069bf..65995ff 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
@@ -155,22 +155,20 @@
current.removePhiUser(this);
}
- void replaceTrivialPhi(Value current, Value newValue, List<Phi> toRemove) {
+ void replaceOperand(Value current, Value newValue) {
for (int i = 0; i < operands.size(); i++) {
if (operands.get(i) == current) {
operands.set(i, newValue);
newValue.addPhiUser(this);
- toRemove.add(this);
}
}
}
- void replaceTrivialDebugPhi(Value current, Value newValue, List<Phi> toRemove) {
+ void replaceDebugValue(Value current, Value newValue) {
assert current.getLocalInfo() != null;
assert current.getLocalInfo() == newValue.getLocalInfo();
if (debugValues.remove(current)) {
addDebugValue(newValue);
- toRemove.add(this);
}
}
@@ -228,7 +226,7 @@
{
Set<Phi> phiUsersToSimplify = uniquePhiUsers();
// Replace this phi with the unique value in all users.
- replaceInUsers(same);
+ replaceUsers(same);
// Try to simplify phi users that might now have become trivial.
for (Phi user : phiUsersToSimplify) {
user.removeTrivialPhi();
@@ -275,9 +273,9 @@
}
private boolean isSingleConstZero(Value value) {
- return value.definition != null && value.definition.isConstNumber() &&
- value.definition.asConstNumber().isZero() &&
- value.outType() == MoveType.SINGLE;
+ return value.definition != null
+ && value.outType() == MoveType.SINGLE
+ && value.isZero();
}
private MoveType computeOutType(Set<Phi> active) {
diff --git a/src/main/java/com/android/tools/r8/ir/code/Rem.java b/src/main/java/com/android/tools/r8/ir/code/Rem.java
index 6521ae7..a257109 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Rem.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Rem.java
@@ -88,7 +88,7 @@
@Override
public boolean canBeFolded() {
- return super.canBeFolded() && !rightValue().getConstInstruction().asConstNumber().isZero();
+ return super.canBeFolded() && !rightValue().isZero();
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/Switch.java b/src/main/java/com/android/tools/r8/ir/code/Switch.java
index 6def63a..08ab002 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Switch.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Switch.java
@@ -13,6 +13,8 @@
import com.android.tools.r8.ir.conversion.DexBuilder;
import com.android.tools.r8.utils.CfgPrinter;
import com.google.common.primitives.Ints;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceAVLTreeMap;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceSortedMap;
import java.util.List;
public class Switch extends JumpInstruction {
@@ -159,6 +161,11 @@
}
}
+ // Estimated size of the resulting dex instruction in code units (excluding the payload).
+ public static int estimatedDexSize() {
+ return 3;
+ }
+
public int numberOfKeys() {
return keys.length;
}
@@ -175,6 +182,14 @@
return targetBlockIndices;
}
+ public Int2ReferenceSortedMap<BasicBlock> getKeyToTargetMap() {
+ Int2ReferenceSortedMap<BasicBlock> result = new Int2ReferenceAVLTreeMap<>();
+ for (int i = 0; i < keys.length; i++) {
+ result.put(getKey(i), targetBlock(i));
+ }
+ return result;
+ }
+
@Override
public BasicBlock fallthroughBlock() {
return getBlock().getSuccessors().get(fallthroughBlockIndex);
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 dfa8548..2880c87 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
@@ -4,6 +4,7 @@
package com.android.tools.r8.ir.code;
import com.android.tools.r8.dex.Constants;
+import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.DebugLocalInfo;
import com.android.tools.r8.ir.regalloc.LiveIntervals;
import com.android.tools.r8.utils.InternalOptions;
@@ -11,9 +12,12 @@
import com.google.common.collect.ImmutableSet;
import java.util.ArrayList;
import java.util.Collections;
+import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
import java.util.Set;
public class Value {
@@ -21,17 +25,49 @@
// Lazily allocated internal data for the debug information of locals.
// This is wrapped in a class to avoid multiple pointers in the value structure.
private static class DebugData {
+
final DebugLocalInfo local;
- Set<Instruction> users = new HashSet<>();
+ Map<Instruction, DebugUse> users = new HashMap<>();
Set<Phi> phiUsers = new HashSet<>();
- List<Instruction> localStarts = new ArrayList<>();
- List<Instruction> localEnds = new ArrayList<>();
DebugData(DebugLocalInfo local) {
this.local = local;
}
}
+ // A debug-value user represents a point where the value is live, ends or starts.
+ // If a point is marked as both ending and starting then it is simply live, but we maintain
+ // the marker so as not to unintentionally end it if marked again.
+ private enum DebugUse {
+ LIVE, START, END, LIVE_FINAL;
+
+ DebugUse start() {
+ switch (this) {
+ case LIVE:
+ case START:
+ return START;
+ case END:
+ case LIVE_FINAL:
+ return LIVE_FINAL;
+ default:
+ throw new Unreachable();
+ }
+ }
+
+ DebugUse end() {
+ switch (this) {
+ case LIVE:
+ case END:
+ return END;
+ case START:
+ case LIVE_FINAL:
+ return LIVE_FINAL;
+ default:
+ throw new Unreachable();
+ }
+ }
+ }
+
public static final Value UNDEFINED = new Value(-1, MoveType.OBJECT, null);
protected final int number;
@@ -79,21 +115,49 @@
}
public List<Instruction> getDebugLocalStarts() {
- return debugData.localStarts;
+ if (debugData == null) {
+ return Collections.emptyList();
+ }
+ List<Instruction> starts = new ArrayList<>(debugData.users.size());
+ for (Entry<Instruction, DebugUse> entry : debugData.users.entrySet()) {
+ if (entry.getValue() == DebugUse.START) {
+ starts.add(entry.getKey());
+ }
+ }
+ return starts;
}
public List<Instruction> getDebugLocalEnds() {
- return debugData.localEnds;
+ if (debugData == null) {
+ return Collections.emptyList();
+ }
+ List<Instruction> ends = new ArrayList<>(debugData.users.size());
+ for (Entry<Instruction, DebugUse> entry : debugData.users.entrySet()) {
+ if (entry.getValue() == DebugUse.END) {
+ ends.add(entry.getKey());
+ }
+ }
+ return ends;
}
public void addDebugLocalStart(Instruction start) {
assert start != null;
- debugData.localStarts.add(start);
+ debugData.users.put(start, markStart(debugData.users.get(start)));
+ }
+
+ private DebugUse markStart(DebugUse use) {
+ assert use != null;
+ return use == null ? DebugUse.START : use.start();
}
public void addDebugLocalEnd(Instruction end) {
assert end != null;
- debugData.localEnds.add(end);
+ debugData.users.put(end, markEnd(debugData.users.get(end)));
+ }
+
+ private DebugUse markEnd(DebugUse use) {
+ assert use != null;
+ return use == null ? DebugUse.END : use.end();
}
public void linkTo(Value other) {
@@ -152,7 +216,7 @@
}
public Set<Instruction> debugUsers() {
- return debugData == null ? null : Collections.unmodifiableSet(debugData.users);
+ return debugData == null ? null : Collections.unmodifiableSet(debugData.users.keySet());
}
public Set<Phi> debugPhiUsers() {
@@ -244,7 +308,7 @@
if (isUninitializedLocal()) {
return;
}
- debugData.users.add(user);
+ debugData.users.putIfAbsent(user, DebugUse.LIVE);
}
public void addDebugPhiUser(Phi user) {
@@ -263,11 +327,19 @@
}
public void removeDebugUser(Instruction user) {
- debugData.users.remove(user);
+ if (debugData != null && debugData.users != null) {
+ debugData.users.remove(user);
+ return;
+ }
+ assert false;
}
public void removeDebugPhiUser(Phi user) {
- debugData.phiUsers.remove(user);
+ if (debugData != null && debugData.phiUsers != null) {
+ debugData.phiUsers.remove(user);
+ return;
+ }
+ assert false;
}
public boolean hasUsersInfo() {
@@ -290,68 +362,27 @@
return;
}
for (Instruction user : uniqueUsers()) {
- user.inValues.replaceAll(v -> {
- if (v == this) {
- newValue.addUser(user);
- return newValue;
- }
- return v;
- });
+ user.replaceValue(this, newValue);
}
for (Phi user : uniquePhiUsers()) {
- user.getOperands().replaceAll(v -> {
- if (v == this) {
- newValue.addPhiUser(user);
- return newValue;
- }
- return v;
- });
+ user.replaceOperand(this, newValue);
}
if (debugData != null) {
for (Instruction user : debugUsers()) {
- if (user.getDebugValues().remove(this)) {
- user.addDebugValue(newValue);
- }
+ user.replaceDebugValue(this, newValue);
}
for (Phi user : debugPhiUsers()) {
- if (user.getDebugValues().remove(this)) {
- user.addDebugValue(newValue);
- }
+ user.replaceDebugValue(this, newValue);
}
}
clearUsers();
}
- public void replaceInUsers(Value newValue) {
- if (!uniqueUsers().isEmpty()) {
- List<Instruction> toRemove = new ArrayList<>(uniqueUsers().size());
- for (Instruction user : uniqueUsers()) {
- user.replaceValue(this, newValue, toRemove);
- }
- toRemove.forEach(this::removeUser);
- }
- if (!uniquePhiUsers().isEmpty()) {
- List<Phi> toRemove = new ArrayList<>(uniquePhiUsers().size());
- for (Phi user : uniquePhiUsers()) {
- user.replaceTrivialPhi(this, newValue, toRemove);
- }
- toRemove.forEach(this::removePhiUser);
- }
- if (debugData != null) {
- if (!debugUsers().isEmpty()) {
- List<Instruction> toRemove = new ArrayList<>(debugUsers().size());
- for (Instruction user : debugUsers()) {
- user.replaceDebugValue(this, newValue, toRemove);
- }
- toRemove.forEach(this::removeDebugUser);
- }
- if (!debugPhiUsers().isEmpty()) {
- List<Phi> toRemove = new ArrayList<>(debugPhiUsers().size());
- for (Phi user : debugPhiUsers()) {
- user.replaceTrivialDebugPhi(this, newValue, toRemove);
- }
- toRemove.forEach(this::removeDebugPhiUser);
- }
+ public void replaceDebugUser(Instruction oldUser, Instruction newUser) {
+ DebugUse use = debugData.users.remove(oldUser);
+ if (use != null) {
+ newUser.addDebugValue(this);
+ debugData.users.put(newUser, use);
}
}
@@ -583,4 +614,10 @@
}
return true;
}
+
+ public boolean isZero() {
+ return isConstant()
+ && getConstInstruction().isConstNumber()
+ && getConstInstruction().asConstNumber().isZero();
+ }
}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CallGraph.java b/src/main/java/com/android/tools/r8/ir/conversion/CallGraph.java
index 585260b..b32f34d 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/CallGraph.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CallGraph.java
@@ -18,12 +18,11 @@
import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.ThreadUtils;
-import com.android.tools.r8.utils.ThrowingConsumer;
+import com.android.tools.r8.utils.ThrowingBiConsumer;
import com.google.common.collect.Sets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
-import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
@@ -32,8 +31,8 @@
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
-import java.util.function.Consumer;
import java.util.function.Function;
+import java.util.function.Predicate;
import java.util.stream.Collectors;
/**
@@ -51,7 +50,7 @@
* <p>
* Recursive calls are not present.
*/
-public class CallGraph {
+public class CallGraph extends CallSiteInformation {
private CallGraph(InternalOptions options) {
this.shuffle = options.testing.irOrdering;
@@ -132,15 +131,7 @@
}
private final Map<DexEncodedMethod, Node> nodes = new LinkedHashMap<>();
- private final Map<DexEncodedMethod, Set<DexEncodedMethod>> breakers = new HashMap<>();
- private final Function<List<DexEncodedMethod>, List<DexEncodedMethod>> shuffle;
-
- // Returns whether the method->callee edge has been removed from the call graph
- // to break a cycle in the call graph.
- public boolean isBreaker(DexEncodedMethod method, DexEncodedMethod callee) {
- Set<DexEncodedMethod> value = breakers.get(method);
- return (value != null) && value.contains(callee);
- }
+ private final Function<Set<DexEncodedMethod>, Set<DexEncodedMethod>> shuffle;
private Set<DexEncodedMethod> singleCallSite = Sets.newIdentityHashSet();
private Set<DexEncodedMethod> doubleCallSite = Sets.newIdentityHashSet();
@@ -170,10 +161,12 @@
* For pinned methods (methods kept through Proguard keep rules) this will always answer
* <code>false</code>.
*/
+ @Override
public boolean hasSingleCallSite(DexEncodedMethod method) {
return singleCallSite.contains(method);
}
+ @Override
public boolean hasDoubleCallSite(DexEncodedMethod method) {
return doubleCallSite.contains(method);
}
@@ -211,12 +204,10 @@
* All nodes in the graph are extracted if called repeatedly until null is returned.
* Please note that there are no cycles in this graph (see {@link #breakCycles}).
* <p>
- *
- * @return List of {@link DexEncodedMethod}.
*/
- private List<DexEncodedMethod> extractLeaves() {
+ private Set<DexEncodedMethod> extractLeaves() {
if (isEmpty()) {
- return Collections.emptyList();
+ return Collections.emptySet();
}
// First identify all leaves before removing them from the graph.
List<Node> leaves = nodes.values().stream().filter(Node::isLeaf).collect(Collectors.toList());
@@ -224,7 +215,8 @@
leaf.callers.forEach(caller -> caller.callees.remove(leaf));
nodes.remove(leaf.method);
});
- return shuffle.apply(leaves.stream().map(leaf -> leaf.method).collect(Collectors.toList()));
+ return shuffle.apply(leaves.stream().map(leaf -> leaf.method)
+ .collect(Collectors.toCollection(LinkedHashSet::new)));
}
private int traverse(Node node, Set<Node> stack, Set<Node> marked) {
@@ -246,8 +238,6 @@
// We have a cycle; break it by removing node->callee.
toBeRemoved.add(callee);
callee.callers.remove(node);
- breakers.computeIfAbsent(node.method,
- ignore -> Sets.newIdentityHashSet()).add(callee.method);
} else {
numberOfCycles += traverse(callee, stack, marked);
}
@@ -264,7 +254,6 @@
private int breakCycles() {
// Break cycles in this call graph by removing edges causing cycles.
- // The remove edges are stored in @breakers.
int numberOfCycles = 0;
Set<Node> stack = Sets.newIdentityHashSet();
Set<Node> marked = Sets.newIdentityHashSet();
@@ -294,16 +283,23 @@
return nodes.size() == 0;
}
+ /**
+ * Applies the given method to all leaf nodes of the graph.
+ * <p>
+ * As second parameter, a predicate that can be used to decide whether another method is
+ * processed at the same time is passed. This can be used to avoid races in concurrent processing.
+ */
public <E extends Exception> void forEachMethod(
- ThrowingConsumer<DexEncodedMethod, E> consumer, ExecutorService executorService)
+ ThrowingBiConsumer<DexEncodedMethod, Predicate<DexEncodedMethod>, E> consumer,
+ ExecutorService executorService)
throws ExecutionException {
while (!isEmpty()) {
- List<DexEncodedMethod> methods = extractLeaves();
+ Set<DexEncodedMethod> methods = extractLeaves();
assert methods.size() > 0;
List<Future<?>> futures = new ArrayList<>();
for (DexEncodedMethod method : methods) {
futures.add(executorService.submit(() -> {
- consumer.accept(method);
+ consumer.accept(method, methods::contains);
return null; // we want a Callable not a Runnable to be able to throw
}));
}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CallSiteInformation.java b/src/main/java/com/android/tools/r8/ir/conversion/CallSiteInformation.java
new file mode 100644
index 0000000..651fb6f
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CallSiteInformation.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.ir.conversion;
+
+import com.android.tools.r8.graph.DexEncodedMethod;
+
+public abstract class CallSiteInformation {
+
+ /**
+ * Check if the <code>method</code> is guaranteed to only have a single call site.
+ * <p>
+ * For pinned methods (methods kept through Proguard keep rules) this will always answer
+ * <code>false</code>.
+ */
+ public abstract boolean hasSingleCallSite(DexEncodedMethod method);
+
+ public abstract boolean hasDoubleCallSite(DexEncodedMethod method);
+
+ public static CallSiteInformation empty() {
+ return EmptyCallSiteInformation.EMPTY_INFO;
+ }
+
+ private static class EmptyCallSiteInformation extends CallSiteInformation {
+
+ private static EmptyCallSiteInformation EMPTY_INFO = new EmptyCallSiteInformation();
+
+ @Override
+ public boolean hasSingleCallSite(DexEncodedMethod method) {
+ return false;
+ }
+
+ @Override
+ public boolean hasDoubleCallSite(DexEncodedMethod method) {
+ return false;
+ }
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/DexSourceCode.java b/src/main/java/com/android/tools/r8/ir/conversion/DexSourceCode.java
index bc821d3..17b0e36 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/DexSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/DexSourceCode.java
@@ -27,6 +27,7 @@
import com.android.tools.r8.code.MoveResultWide;
import com.android.tools.r8.code.SwitchPayload;
import com.android.tools.r8.code.Throw;
+import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.DebugLocalInfo;
import com.android.tools.r8.graph.DexAccessFlags;
import com.android.tools.r8.graph.DexCode;
@@ -40,6 +41,7 @@
import com.android.tools.r8.graph.DexProto;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.ir.code.CatchHandlers;
+import com.android.tools.r8.ir.code.DebugPosition;
import com.android.tools.r8.ir.code.MoveType;
import java.util.ArrayList;
import java.util.HashMap;
@@ -162,6 +164,17 @@
}
@Override
+ public int getMoveExceptionRegister() {
+ // No register, move-exception is manually entered during construction.
+ return -1;
+ }
+
+ @Override
+ public DebugPosition getDebugPositionAtOffset(int offset) {
+ throw new Unreachable();
+ }
+
+ @Override
public boolean verifyCurrentInstructionCanThrow() {
return currentDexInstruction.canThrow();
}
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 e10c690..245a75a 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
@@ -78,7 +78,9 @@
import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.ir.code.ValueNumberGenerator;
import com.android.tools.r8.ir.code.Xor;
+import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.InternalOptions;
+import com.google.common.collect.Sets;
import it.unimi.dsi.fastutil.ints.Int2ReferenceAVLTreeMap;
import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
import it.unimi.dsi.fastutil.ints.Int2ReferenceSortedMap;
@@ -93,6 +95,7 @@
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
+import java.util.IdentityHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
@@ -124,6 +127,15 @@
}
}
+ private static class MoveExceptionWorklistItem extends WorklistItem {
+ private final int targetOffset;
+
+ private MoveExceptionWorklistItem(BasicBlock block, int targetOffset) {
+ super(block, -1);
+ this.targetOffset = targetOffset;
+ }
+ }
+
/**
* Representation of lists of values that can be used as keys in maps. A list of
* values is equal to another list of values if it contains exactly the same values
@@ -450,6 +462,11 @@
setCurrentBlock(item.block);
blocks.add(currentBlock);
currentBlock.setNumber(nextBlockNumber++);
+ // Process synthesized move-exception block specially.
+ if (item instanceof MoveExceptionWorklistItem) {
+ processMoveExceptionItem((MoveExceptionWorklistItem) item);
+ continue;
+ }
// Build IR for each dex instruction in the block.
for (int i = item.firstInstructionIndex; i < source.instructionCount(); ++i) {
if (currentBlock == null) {
@@ -468,6 +485,22 @@
}
}
+ private void processMoveExceptionItem(MoveExceptionWorklistItem moveExceptionItem) {
+ // TODO(zerny): Link with outer try-block handlers, if any. b/65203529
+ int moveExceptionDest = source.getMoveExceptionRegister();
+ assert moveExceptionDest >= 0;
+ int targetIndex = source.instructionIndex(moveExceptionItem.targetOffset);
+ Value out = writeRegister(moveExceptionDest, MoveType.OBJECT, ThrowingInfo.NO_THROW, null);
+ MoveException moveException = new MoveException(out);
+ moveException.setPosition(source.getDebugPositionAtOffset(moveExceptionItem.targetOffset));
+ currentBlock.add(moveException);
+ currentBlock.add(new Goto());
+ BasicBlock targetBlock = getTarget(moveExceptionItem.targetOffset);
+ currentBlock.link(targetBlock);
+ addToWorklist(targetBlock, targetIndex);
+ closeCurrentBlock();
+ }
+
// Helper to resolve switch payloads and build switch instructions (dex code only).
public void resolveAndBuildSwitch(int value, int fallthroughOffset, int payloadOffset) {
source.resolveAndBuildSwitch(value, fallthroughOffset, payloadOffset, this);
@@ -723,7 +756,7 @@
}
public void addNullConst(int dest, long value) {
- canonicalizeAndAddConst(ConstType.INT, dest, value, nullConstants);
+ canonicalizeAndAddConst(ConstType.OBJECT, dest, value, nullConstants);
}
public void addConstClass(int dest, DexType type) {
@@ -922,8 +955,7 @@
throws ApiLevelException {
if (type == Invoke.Type.POLYMORPHIC && !options.canUseInvokePolymorphic()) {
throw new ApiLevelException(
- Constants.ANDROID_O_API,
- "Android O",
+ AndroidApiLevel.O,
"MethodHandle.invoke and MethodHandle.invokeExact",
null /* sourceString */);
}
@@ -1590,9 +1622,7 @@
// Private instruction helpers.
private void addInstruction(Instruction ir) {
attachLocalChanges(ir);
- if (currentDebugPosition != null && !ir.isMoveException()) {
- flushCurrentDebugPosition();
- }
+ flushCurrentDebugPosition(ir);
currentBlock.add(ir);
if (ir.instructionTypeCanThrow()) {
assert source.verifyCurrentInstructionCanThrow();
@@ -1601,18 +1631,33 @@
assert !throwingInstructionInCurrentBlock;
throwingInstructionInCurrentBlock = true;
List<BasicBlock> targets = new ArrayList<>(catchHandlers.getAllTargets().size());
- for (int targetOffset : catchHandlers.getAllTargets()) {
- BasicBlock target = getTarget(targetOffset);
- addToWorklist(target, source.instructionIndex(targetOffset));
- targets.add(target);
+ int moveExceptionDest = source.getMoveExceptionRegister();
+ if (moveExceptionDest < 0) {
+ for (int targetOffset : catchHandlers.getAllTargets()) {
+ BasicBlock target = getTarget(targetOffset);
+ addToWorklist(target, source.instructionIndex(targetOffset));
+ targets.add(target);
+ }
+ } else {
+ // If there is a well-defined move-exception destination register (eg, compiling from
+ // Java-bytecode) then we construct move-exception header blocks for each unique target.
+ Map<BasicBlock, BasicBlock> moveExceptionHeaders =
+ new IdentityHashMap<>(catchHandlers.getUniqueTargets().size());
+ for (int targetOffset : catchHandlers.getAllTargets()) {
+ BasicBlock target = getTarget(targetOffset);
+ BasicBlock header = moveExceptionHeaders.get(target);
+ if (header == null) {
+ header = new BasicBlock();
+ header.incrementUnfilledPredecessorCount();
+ moveExceptionHeaders.put(target, header);
+ ssaWorklist.add(new MoveExceptionWorklistItem(header, targetOffset));
+ }
+ targets.add(header);
+ }
}
currentBlock.linkCatchSuccessors(catchHandlers.getGuards(), targets);
}
}
- if (currentDebugPosition != null) {
- assert ir.isMoveException();
- flushCurrentDebugPosition();
- }
}
private void attachLocalChanges(Instruction ir) {
@@ -1789,6 +1834,26 @@
assert returnType == MoveType.fromDexType(method.method.proto.returnType);
}
closeCurrentBlock();
+
+ // Collect the debug values which are live on all returns.
+ Set<Value> debugValuesForReturn = Sets.newIdentityHashSet();
+ for (Value value : exitBlocks.get(0).exit().getDebugValues()) {
+ boolean include = true;
+ for (int i = 1; i < exitBlocks.size() && include; i++) {
+ include = exitBlocks.get(i).exit().getDebugValues().contains(value);
+ }
+ if (include) {
+ debugValuesForReturn.add(value);
+ }
+ }
+
+ // Move all these debug values to the new return.
+ for (Value value : debugValuesForReturn) {
+ for (BasicBlock block : exitBlocks) {
+ block.exit().moveDebugValue(value, normalExitBlock.exit());
+ }
+ }
+
// Replace each return instruction with a goto to the new exit block.
List<Value> operands = new ArrayList<>();
for (BasicBlock block : exitBlocks) {
@@ -1800,12 +1865,7 @@
}
Goto gotoExit = new Goto();
gotoExit.setBlock(block);
- if (options.debug) {
- for (Value value : ret.getDebugValues()) {
- gotoExit.addDebugValue(value);
- value.removeDebugUser(ret);
- }
- }
+ ret.moveDebugValues(gotoExit);
instructions.set(instructions.size() - 1, gotoExit);
block.link(normalExitBlock);
gotoExit.setTarget(normalExitBlock);
@@ -2005,26 +2065,23 @@
public void updateCurrentDebugPosition(int line, DexString file) {
// Stack-trace support requires position information in both debug and release mode.
- flushCurrentDebugPosition();
+ flushCurrentDebugPosition(null);
currentDebugPosition = new DebugPosition(line, file);
attachLocalChanges(currentDebugPosition);
}
- private void flushCurrentDebugPosition() {
+ private void flushCurrentDebugPosition(Instruction instruction) {
if (currentDebugPosition != null) {
DebugPosition position = currentDebugPosition;
currentDebugPosition = null;
- if (!currentBlock.getInstructions().isEmpty()) {
- MoveException move = currentBlock.getInstructions().getLast().asMoveException();
- if (move != null && move.getPosition() == null) {
- // Set the position on the move-exception instruction.
- // ART/DX associates the move-exception pc with the catch-declaration line.
- // See debug.ExceptionTest.testStepOnCatch().
- move.setPosition(position);
- return;
- }
+ if (instruction != null && instruction.isMoveException()) {
+ // Set the position on the move-exception instruction.
+ // ART/DX associates the move-exception pc with the catch-declaration line.
+ // See debug.ExceptionTest.testStepOnCatch().
+ instruction.asMoveException().setPosition(position);
+ } else {
+ addInstruction(position);
}
- addInstruction(position);
}
}
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 82cc92f..5b5d382 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
@@ -43,12 +43,12 @@
import java.util.HashSet;
import java.util.List;
import java.util.Set;
-import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.function.BiConsumer;
+import java.util.function.Predicate;
public class IRConverter {
@@ -68,7 +68,6 @@
private final LensCodeRewriter lensCodeRewriter;
private final Inliner inliner;
private final ProtoLitePruner protoLiteRewriter;
- private CallGraph callGraph;
private OptimizationFeedback ignoreOptimizationFeedback = new OptimizationFeedbackIgnore();
private DexString highestSortingString;
@@ -241,9 +240,6 @@
}));
}
ThreadUtils.awaitFutures(futures);
-
- // Get rid of <clinit> methods with no code.
- removeEmptyClassInitializers();
}
void convertMethodToDex(DexEncodedMethod method) throws ApiLevelException {
@@ -251,7 +247,9 @@
boolean matchesMethodFilter = options.methodMatchesFilter(method);
if (matchesMethodFilter) {
if (method.getCode().isJarCode()) {
- rewriteCode(method, ignoreOptimizationFeedback, Outliner::noProcessing);
+ // We do not process in call graph order, so anything could be a leaf.
+ rewriteCode(method, ignoreOptimizationFeedback, x -> true, CallSiteInformation.empty(),
+ Outliner::noProcessing);
}
updateHighestSortingStrings(method);
}
@@ -271,10 +269,6 @@
throws ExecutionException, ApiLevelException {
removeLambdaDeserializationMethods();
- timing.begin("Build call graph");
- callGraph = CallGraph.build(application, appInfo.withSubtyping(), graphLense, options);
- timing.end();
-
// The process is in two phases.
// 1) Subject all DexEncodedMethods to optimization (except outlining).
// - a side effect is candidates for outlining are identified.
@@ -282,16 +276,19 @@
// Ideally, we should outline eagerly when threshold for a template has been reached.
// Process the application identifying outlining candidates.
- timing.begin("IR conversion phase 1");
OptimizationFeedback directFeedback = new OptimizationFeedbackDirect();
- callGraph.forEachMethod(method -> {
- processMethod(method, directFeedback,
- outliner == null ? Outliner::noProcessing : outliner::identifyCandidates);
- }, executorService);
- timing.end();
-
- // Get rid of <clinit> methods with no code.
- removeEmptyClassInitializers();
+ {
+ timing.begin("Build call graph");
+ CallGraph callGraph = CallGraph
+ .build(application, appInfo.withSubtyping(), graphLense, options);
+ timing.end();
+ timing.begin("IR conversion phase 1");
+ callGraph.forEachMethod((method, isProcessedConcurrently) -> {
+ processMethod(method, directFeedback, isProcessedConcurrently, callGraph,
+ outliner == null ? Outliner::noProcessing : outliner::identifyCandidates);
+ }, executorService);
+ timing.end();
+ }
// Build a new application with jumbo string info.
Builder builder = new Builder(application);
@@ -313,16 +310,17 @@
if (outlineClass != null) {
// We need a new call graph to ensure deterministic order and also processing inside out
// to get maximal inlining. Use a identity lense, as the code has been rewritten.
- callGraph = CallGraph
+ CallGraph callGraph = CallGraph
.build(application, appInfo.withSubtyping(), GraphLense.getIdentityLense(), options);
Set<DexEncodedMethod> outlineMethods = outliner.getMethodsSelectedForOutlining();
- callGraph.forEachMethod(method -> {
+ callGraph.forEachMethod((method, isProcessedConcurrently) -> {
if (!outlineMethods.contains(method)) {
return;
}
// This is the second time we compile this method first mark it not processed.
assert !method.getCode().isOutlineCode();
- processMethod(method, ignoreOptimizationFeedback, outliner::applyOutliningCandidate);
+ processMethod(method, ignoreOptimizationFeedback, isProcessedConcurrently, callGraph,
+ outliner::applyOutliningCandidate);
assert method.isProcessed();
}, executorService);
builder.addSynthesizedClass(outlineClass, true);
@@ -334,16 +332,6 @@
return builder.build();
}
- private void removeEmptyClassInitializers() {
- application.classes().forEach(this::removeEmptyClassInitializer);
- }
-
- private void removeEmptyClassInitializer(DexProgramClass clazz) {
- if (clazz.hasTrivialClassInitializer()) {
- clazz.removeStaticMethod(clazz.getClassInitializer());
- }
- }
-
private void clearDexMethodCompilationState() {
application.classes().forEach(this::clearDexMethodCompilationState);
}
@@ -405,7 +393,8 @@
public void optimizeSynthesizedMethod(DexEncodedMethod method) throws ApiLevelException {
// Process the generated method, but don't apply any outlining.
- processMethod(method, ignoreOptimizationFeedback, Outliner::noProcessing);
+ processMethod(method, ignoreOptimizationFeedback, x -> false, CallSiteInformation.empty(),
+ Outliner::noProcessing);
}
private String logCode(InternalOptions options, DexEncodedMethod method) {
@@ -414,12 +403,14 @@
public void processMethod(DexEncodedMethod method,
OptimizationFeedback feedback,
+ Predicate<DexEncodedMethod> isProcessedConcurrently,
+ CallSiteInformation callSiteInformation,
BiConsumer<IRCode, DexEncodedMethod> outlineHandler)
throws ApiLevelException {
Code code = method.getCode();
boolean matchesMethodFilter = options.methodMatchesFilter(method);
if (code != null && matchesMethodFilter) {
- rewriteCode(method, feedback, outlineHandler);
+ rewriteCode(method, feedback, isProcessedConcurrently, callSiteInformation, outlineHandler);
} else {
// Mark abstract methods as processed as well.
method.markProcessed(Constraint.NEVER);
@@ -428,6 +419,8 @@
private void rewriteCode(DexEncodedMethod method,
OptimizationFeedback feedback,
+ Predicate<DexEncodedMethod> isProcessedConcurrently,
+ CallSiteInformation callSiteInformation,
BiConsumer<IRCode, DexEncodedMethod> outlineHandler)
throws ApiLevelException {
if (options.verbose) {
@@ -473,7 +466,7 @@
if (options.inlineAccessors && inliner != null) {
// TODO(zerny): Should we support inlining in debug mode? b/62937285
assert !options.debug;
- inliner.performInlining(method, code, callGraph);
+ inliner.performInlining(method, code, isProcessedConcurrently, callSiteInformation);
}
codeRewriter.removeCastChains(code);
codeRewriter.rewriteLongCompareAndRequireNonNull(code, options);
@@ -484,7 +477,9 @@
codeRewriter.foldConstants(code);
codeRewriter.rewriteSwitch(code);
codeRewriter.simplifyIf(code);
- codeRewriter.collectClassInitializerDefaults(method, code);
+ if (!options.debug) {
+ codeRewriter.collectClassInitializerDefaults(method, code);
+ }
if (Log.ENABLED) {
Log.debug(getClass(), "Intermediate (SSA) flow graph for %s:\n%s",
method.toSourceString(), code);
@@ -560,7 +555,6 @@
LinearScanRegisterAllocator registerAllocator = new LinearScanRegisterAllocator(code, options);
registerAllocator.allocateRegisters(options.debug);
printMethod(code, "After register allocation (non-SSA)");
- printLiveRanges(registerAllocator, "Final live ranges.");
if (!options.debug) {
CodeRewriter.removedUnneededDebugPositions(code);
}
@@ -595,10 +589,4 @@
printer.end("cfg");
}
}
-
- private void printLiveRanges(LinearScanRegisterAllocator allocator, String title) {
- if (printer != null) {
- allocator.print(printer, title);
- }
- }
}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/JarSourceCode.java b/src/main/java/com/android/tools/r8/ir/conversion/JarSourceCode.java
index 668e220..2ab087c 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/JarSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/JarSourceCode.java
@@ -23,6 +23,7 @@
import com.android.tools.r8.ir.code.CatchHandlers;
import com.android.tools.r8.ir.code.Cmp.Bias;
import com.android.tools.r8.ir.code.ConstType;
+import com.android.tools.r8.ir.code.DebugPosition;
import com.android.tools.r8.ir.code.If;
import com.android.tools.r8.ir.code.Invoke;
import com.android.tools.r8.ir.code.MemberType;
@@ -35,6 +36,7 @@
import com.android.tools.r8.logging.Log;
import com.android.tools.r8.utils.ThrowingBiConsumer;
import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceMap.Entry;
import it.unimi.dsi.fastutil.ints.Int2ReferenceOpenHashMap;
import it.unimi.dsi.fastutil.ints.Int2ReferenceSortedMap;
import java.io.PrintWriter;
@@ -136,10 +138,10 @@
"([Ljava/lang/Object;)Ljava/lang/Object;";
// Language types.
- private static final Type CLASS_TYPE = Type.getObjectType("java/lang/Class");
- private static final Type STRING_TYPE = Type.getObjectType("java/lang/String");
- private static final Type INT_ARRAY_TYPE = Type.getObjectType(INT_ARRAY_DESC);
- private static final Type THROWABLE_TYPE = Type.getObjectType("java/lang/Throwable");
+ static final Type CLASS_TYPE = Type.getObjectType("java/lang/Class");
+ static final Type STRING_TYPE = Type.getObjectType("java/lang/String");
+ static final Type INT_ARRAY_TYPE = Type.getObjectType(INT_ARRAY_DESC);
+ static final Type THROWABLE_TYPE = Type.getObjectType("java/lang/Throwable");
private static final int[] NO_TARGETS = {};
@@ -264,6 +266,7 @@
// Record types for arguments.
Int2ReferenceMap<MoveType> argumentLocals = recordArgumentTypes();
Int2ReferenceMap<MoveType> initializedLocals = new Int2ReferenceOpenHashMap<>(argumentLocals);
+ Int2ReferenceMap<ConstType> uninitializedLocals = new Int2ReferenceOpenHashMap<>();
// Initialize all non-argument locals to ensure safe insertion of debug-local instructions.
for (Object o : node.localVariables) {
LocalVariableNode local = (LocalVariableNode) o;
@@ -292,20 +295,34 @@
int localRegister2 = state.writeLocal(local.index, localType);
assert localRegister == localRegister2;
initializedLocals.put(localRegister, moveType(localType));
- builder.addDebugUninitialized(localRegister, constType(localType));
+ uninitializedLocals.put(localRegister, constType(localType));
}
}
+
+ // TODO(zerny): This is getting a little out of hands. Clean it up.
+
// Add debug information for all locals at the initial label.
+ List<Local> locals = null;
if (initialLabel != null) {
- for (Local local : state.openLocals(initialLabel)) {
+ locals = state.openLocals(initialLabel);
+ }
+
+ // Build the actual argument instructions now that type and debug information is known
+ // for arguments.
+ buildArgumentInstructions(builder);
+
+ for (Entry<ConstType> entry : uninitializedLocals.int2ReferenceEntrySet()) {
+ builder.addDebugUninitialized(entry.getIntKey(), entry.getValue());
+ }
+
+ if (locals != null) {
+ for (Local local : locals) {
if (!argumentLocals.containsKey(local.slot.register)) {
builder.addDebugLocalStart(local.slot.register, local.info);
}
}
}
- // Build the actual argument instructions now that type and debug information is known
- // for arguments.
- buildArgumentInstructions(builder);
+
if (isSynchronized()) {
generatingMethodSynchronization = true;
Type clazzType = Type.getType(clazz.toDescriptorString());
@@ -376,14 +393,6 @@
state.recordStateForTarget(0, this);
for (JarStateWorklistItem item = worklist.poll(); item != null; item = worklist.poll()) {
state.restoreState(item.instructionIndex);
- // If the block being restored is a try-catch handler push the exception on the stack.
- for (int i = 0; i < node.tryCatchBlocks.size(); i++) {
- TryCatchBlockNode tryCatchBlockNode = (TryCatchBlockNode) node.tryCatchBlocks.get(i);
- if (tryCatchBlockNode.handler == getInstruction(item.instructionIndex)) {
- state.push(THROWABLE_TYPE);
- break;
- }
- }
// Iterate each of the instructions in the block to compute the outgoing JarState.
for (int i = item.instructionIndex; i <= instructionCount(); ++i) {
// If we are at the end of the instruction stream or if we have reached the start
@@ -441,10 +450,8 @@
private void buildExceptionalPostlude(IRBuilder builder) {
assert isSynchronized();
generatingMethodSynchronization = true;
- int exceptionRegister = 0; // We are exiting the method so we just overwrite register 0.
- builder.addMoveException(exceptionRegister);
buildMonitorExit(builder);
- builder.addThrow(exceptionRegister);
+ builder.addThrow(getMoveExceptionRegister());
generatingMethodSynchronization = false;
}
@@ -474,14 +481,6 @@
// If a new block is starting here, we restore the computed JarState.
if (builder.getCFG().containsKey(instructionIndex) || instructionIndex == 0) {
state.restoreState(instructionIndex);
- // If the block being restored is a try-catch handler push the exception on the stack.
- for (int i = 0; i < node.tryCatchBlocks.size(); i++) {
- TryCatchBlockNode tryCatchBlockNode = (TryCatchBlockNode) node.tryCatchBlocks.get(i);
- if (tryCatchBlockNode.handler == insn) {
- builder.addMoveException(state.push(THROWABLE_TYPE));
- break;
- }
- }
}
String preInstructionState;
@@ -552,6 +551,11 @@
}
@Override
+ public int getMoveExceptionRegister() {
+ return state.startOfStack;
+ }
+
+ @Override
public boolean verifyCurrentInstructionCanThrow() {
return generatingMethodSynchronization || canThrow(currentInstruction);
}
@@ -2291,8 +2295,19 @@
}
}
+ private boolean isExitingThrow(InsnNode insn) {
+ List<TryCatchBlock> handlers = getTryHandlers(insn);
+ if (handlers.isEmpty()) {
+ return true;
+ }
+ if (!isSynchronized() || handlers.size() > 1) {
+ return false;
+ }
+ return handlers.get(0) == EXCEPTIONAL_SYNC_EXIT;
+ }
+
private void addThrow(InsnNode insn, int register, IRBuilder builder) {
- if (getTryHandlers(insn).isEmpty()) {
+ if (isExitingThrow(insn)) {
processLocalVariablesAtExit(insn, builder);
} else {
processLocalVariablesAtControlEdge(insn, builder);
@@ -2653,8 +2668,10 @@
case Opcodes.H_PUTSTATIC:
return MethodHandleType.STATIC_PUT;
case Opcodes.H_INVOKESPECIAL:
+ assert !handle.getName().equals(Constants.INSTANCE_INITIALIZER_NAME);
+ assert !handle.getName().equals(Constants.CLASS_INITIALIZER_NAME);
DexType owner = application.getTypeFromName(handle.getOwner());
- if (owner == clazz || handle.getName().equals(Constants.INSTANCE_INITIALIZER_NAME)) {
+ if (owner == clazz) {
return MethodHandleType.INVOKE_DIRECT;
} else {
return MethodHandleType.INVOKE_SUPER;
@@ -2817,6 +2834,23 @@
builder.updateCurrentDebugPosition(insn.line, null);
}
+ @Override
+ public DebugPosition getDebugPositionAtOffset(int offset) {
+ int index = instructionIndex(offset);
+ if (index < 0 || instructionCount() <= index) {
+ return null;
+ }
+ AbstractInsnNode insn = node.instructions.get(index);
+ if (insn instanceof LabelNode) {
+ insn = insn.getNext();
+ }
+ if (insn instanceof LineNumberNode) {
+ LineNumberNode line = (LineNumberNode) insn;
+ return new DebugPosition(line.line, null);
+ }
+ return null;
+ }
+
// Printing helpers.
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/JarState.java b/src/main/java/com/android/tools/r8/ir/conversion/JarState.java
index 03c614a..12b62ad 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/JarState.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/JarState.java
@@ -134,8 +134,7 @@
return (sort == Type.OBJECT && otherSort == Type.ARRAY)
|| (sort == Type.ARRAY && otherSort == Type.OBJECT)
|| (sort == Type.OBJECT && otherSort == Type.OBJECT)
- || (sort == Type.ARRAY && otherSort == Type.ARRAY
- && isReferenceCompatible(getArrayElementType(type), getArrayElementType(other)));
+ || (sort == Type.ARRAY && otherSort == Type.ARRAY);
}
}
@@ -166,7 +165,7 @@
}
}
- private final int startOfStack;
+ final int startOfStack;
private int topOfStack;
// Locals are split into three parts based on types:
@@ -344,6 +343,11 @@
private Local setLocalInfoForRegister(int register, DebugLocalInfo info) {
Local existingLocal = getLocalForRegister(register);
+ Type type = Type.getType(info.type.toDescriptorString());
+ if (!existingLocal.slot.isCompatibleWith(type)) {
+ throw new InvalidDebugInfoException(
+ "Attempt to define local of type " + prettyType(existingLocal.slot.type) + " as " + info);
+ }
Local local = new Local(existingLocal.slot, info);
locals[register] = local;
return local;
@@ -420,12 +424,7 @@
public Slot pop(Type type) {
Slot slot = pop();
- boolean compatible = slot.isCompatibleWith(type);
- if (!compatible && !localVariables.isEmpty()) {
- throw new InvalidDebugInfoException("Expected to read stack value of type " + prettyType(type)
- + " but found value of type " + prettyType(slot.type));
- }
- assert compatible;
+ assert slot.isCompatibleWith(type);
return slot;
}
@@ -464,7 +463,10 @@
}
public boolean recordStateForExceptionalTarget(int target, JarSourceCode source) {
- return recordStateForTarget(target, locals.clone(), ImmutableList.of(), source);
+ return recordStateForTarget(target,
+ locals.clone(),
+ ImmutableList.of(new Slot(startOfStack, JarSourceCode.THROWABLE_TYPE)),
+ source);
}
private boolean recordStateForTarget(int target, Local[] locals, ImmutableList<Slot> stack,
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/SourceCode.java b/src/main/java/com/android/tools/r8/ir/conversion/SourceCode.java
index cf1e669..16e124a 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/SourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/SourceCode.java
@@ -7,6 +7,7 @@
import com.android.tools.r8.ApiLevelException;
import com.android.tools.r8.graph.DebugLocalInfo;
import com.android.tools.r8.ir.code.CatchHandlers;
+import com.android.tools.r8.ir.code.DebugPosition;
/**
* Abstraction of the input/source code for the IRBuilder.
@@ -27,6 +28,8 @@
DebugLocalInfo getCurrentLocal(int register);
+ DebugPosition getDebugPositionAtOffset(int offset);
+
/**
* Trace block structure of the source-program.
*
@@ -58,6 +61,7 @@
void resolveAndBuildNewArrayFilledData(int arrayRef, int payloadOffset, IRBuilder builder);
CatchHandlers<Integer> getCurrentCatchHandlers();
+ int getMoveExceptionRegister();
// For debugging/verification purpose.
boolean verifyRegister(int register);
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaDescriptor.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaDescriptor.java
index e322717..bba7b23 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaDescriptor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaDescriptor.java
@@ -12,7 +12,6 @@
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexMethodHandle;
-import com.android.tools.r8.graph.DexMethodHandle.MethodHandleType;
import com.android.tools.r8.graph.DexProto;
import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.graph.DexType;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/BasicBlockInstructionsEquivalence.java b/src/main/java/com/android/tools/r8/ir/optimize/BasicBlockInstructionsEquivalence.java
index 9f833f0..8820c61 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/BasicBlockInstructionsEquivalence.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/BasicBlockInstructionsEquivalence.java
@@ -11,7 +11,6 @@
import com.android.tools.r8.ir.regalloc.RegisterAllocator;
import com.google.common.base.Equivalence;
import java.util.Arrays;
-import java.util.Comparator;
import java.util.List;
class BasicBlockInstructionsEquivalence extends Equivalence<BasicBlock> {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
index d44ecd9..8ae2147 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
@@ -64,27 +64,32 @@
import com.android.tools.r8.ir.conversion.OptimizationFeedback;
import com.android.tools.r8.ir.optimize.SwitchUtils.EnumSwitchInfo;
import com.android.tools.r8.utils.InternalOptions;
-import com.android.tools.r8.utils.IteratorUtils;
import com.android.tools.r8.utils.LongInterval;
import com.google.common.base.Equivalence;
import com.google.common.base.Equivalence.Wrapper;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
import com.google.common.collect.ListMultimap;
import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
import it.unimi.dsi.fastutil.ints.Int2IntArrayMap;
import it.unimi.dsi.fastutil.ints.Int2IntMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectAVLTreeMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectSortedMap;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceSortedMap;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import it.unimi.dsi.fastutil.ints.IntIterator;
import it.unimi.dsi.fastutil.ints.IntList;
+import it.unimi.dsi.fastutil.ints.IntLists;
import it.unimi.dsi.fastutil.objects.Object2IntLinkedOpenHashMap;
import it.unimi.dsi.fastutil.objects.Object2IntMap;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
+import java.util.IdentityHashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
@@ -344,11 +349,25 @@
}
// TODO(sgjesse); Move this somewhere else, and reuse it for some of the other switch rewritings.
- public static class SwitchBuilder {
+ public abstract static class InstructionBuilder<T> {
+ protected int blockNumber;
+
+ public abstract T self();
+
+ public T setBlockNumber(int blockNumber) {
+ this.blockNumber = blockNumber;
+ return self();
+ }
+ }
+
+ public static class SwitchBuilder extends InstructionBuilder<SwitchBuilder> {
private Value value;
private Int2ObjectSortedMap<BasicBlock> keyToTarget = new Int2ObjectAVLTreeMap<>();
private BasicBlock fallthrough;
- private int blockNumber;
+
+ public SwitchBuilder self() {
+ return this;
+ }
public SwitchBuilder setValue(Value value) {
this.value = value;
@@ -365,11 +384,6 @@
return this;
}
- public SwitchBuilder setBlockNumber(int blockNumber) {
- this.blockNumber = blockNumber;
- return this;
- }
-
public BasicBlock build() {
final int NOT_FOUND = -1;
Object2IntMap<BasicBlock> targetToSuccessorIndex = new Object2IntLinkedOpenHashMap<>();
@@ -400,65 +414,134 @@
}
}
+ public static class IfBuilder extends InstructionBuilder<IfBuilder> {
+ private final IRCode code;
+ private Value left;
+ private int right;
+ private BasicBlock target;
+ private BasicBlock fallthrough;
+ private int blockNumber;
+
+ public IfBuilder(IRCode code) {
+ this.code = code;
+ }
+
+ public IfBuilder self() {
+ return this;
+ }
+
+ public IfBuilder setLeft(Value left) {
+ this.left = left;
+ return this;
+ }
+
+ public IfBuilder setRight(int right) {
+ this.right = right;
+ return this;
+ }
+
+ public IfBuilder setTarget(BasicBlock target) {
+ this.target = target;
+ return this;
+ }
+
+ public IfBuilder setFallthrough(BasicBlock fallthrough) {
+ this.fallthrough = fallthrough;
+ return this;
+ }
+
+ public BasicBlock build() {
+ assert target != null;
+ assert fallthrough != null;
+ If newIf;
+ BasicBlock ifBlock;
+ if (right != 0) {
+ ConstNumber rightConst = code.createIntConstant(right);
+ newIf = new If(Type.EQ, ImmutableList.of(left, rightConst.dest()));
+ ifBlock = BasicBlock.createIfBlock(blockNumber, newIf, rightConst);
+ } else {
+ newIf = new If(Type.EQ, left);
+ ifBlock = BasicBlock.createIfBlock(blockNumber, newIf);
+ }
+ ifBlock.link(target);
+ ifBlock.link(fallthrough);
+ return ifBlock;
+ }
+ }
+
/**
* Covert the switch instruction to a sequence of if instructions checking for a specified
* set of keys, followed by a new switch with the remaining keys.
*/
private void convertSwitchToSwitchAndIfs(
IRCode code, ListIterator<BasicBlock> blocksIterator, BasicBlock originalBlock,
- InstructionListIterator iterator, Switch theSwitch, List<Integer> keysToRemove) {
+ InstructionListIterator iterator, Switch theSwitch,
+ List<IntList> switches, IntList keysToRemove) {
+
+ // Extract the information from the switch before removing it.
+ Int2ReferenceSortedMap<BasicBlock> keyToTarget = theSwitch.getKeyToTargetMap();
+ Set<Value> originalDebugValues = ImmutableSet.copyOf(theSwitch.getDebugValues());
+
+ // Keep track of the current fallthrough, starting with the original.
+ BasicBlock fallthroughBlock = theSwitch.fallthroughBlock();
+
// Split the switch instruction into its own block and remove it.
iterator.previous();
BasicBlock originalSwitchBlock = iterator.split(code, blocksIterator);
assert !originalSwitchBlock.hasCatchHandlers();
assert originalSwitchBlock.getInstructions().size() == 1;
blocksIterator.remove();
-
- int nextBlockNumber = code.getHighestBlockNumber() + 1;
-
- // Collect targets for the keys to peel off, and create a new switch instruction without
- // these keys.
- SwitchBuilder switchBuilder = new SwitchBuilder();
- List<BasicBlock> peeledOffTargets = new ArrayList<>();
- for (int i = 0; i < theSwitch.numberOfKeys(); i++) {
- BasicBlock target = theSwitch.targetBlock(i);
- if (!keysToRemove.contains(theSwitch.getKey(i))) {
- switchBuilder.addKeyAndTarget(theSwitch.getKey(i), theSwitch.targetBlock(i));
- } else {
- peeledOffTargets.add(target);
- }
- }
- assert peeledOffTargets.size() == keysToRemove.size();
- switchBuilder.setValue(theSwitch.value());
- switchBuilder.setFallthrough(theSwitch.fallthroughBlock());
- switchBuilder.setBlockNumber(nextBlockNumber++);
theSwitch.getBlock().detachAllSuccessors();
BasicBlock block = theSwitch.getBlock().unlinkSinglePredecessor();
assert theSwitch.getBlock().getPredecessors().size() == 0;
assert theSwitch.getBlock().getSuccessors().size() == 0;
assert block == originalBlock;
- BasicBlock newSwitchBlock = switchBuilder.build();
+ // Collect the new blocks for adding to the block list.
+ int nextBlockNumber = code.getHighestBlockNumber() + 1;
+ LinkedList<BasicBlock> newBlocks = new LinkedList<>();
- // Create if blocks for each of the peeled off keys, and link them into the graph.
- BasicBlock predecessor = originalBlock;
- for (int i = 0; i < keysToRemove.size(); i++) {
- int key = keysToRemove.get(i);
- BasicBlock peeledOffTarget = peeledOffTargets.get(i);
- ConstNumber keyConst = code.createIntConstant(key);
- If theIf = new If(Type.EQ, ImmutableList.of(keyConst.dest(), theSwitch.value()));
- BasicBlock ifBlock = BasicBlock.createIfBlock(nextBlockNumber++, theIf, keyConst);
- predecessor.link(ifBlock);
- ifBlock.link(peeledOffTarget);
- predecessor = ifBlock;
- blocksIterator.add(ifBlock);
- assert !peeledOffTarget.getPredecessors().contains(theSwitch.getBlock());
+ // Build the switch-blocks backwards, to always have the fallthrough block in hand.
+ for (int i = switches.size() - 1; i >= 0; i--) {
+ SwitchBuilder switchBuilder = new SwitchBuilder();
+ switchBuilder.setValue(theSwitch.value());
+ IntList keys = switches.get(i);
+ for (int j = 0; j < keys.size(); j++) {
+ int key = keys.getInt(j);
+ switchBuilder.addKeyAndTarget(key, keyToTarget.get(key));
+ }
+ switchBuilder
+ .setFallthrough(fallthroughBlock)
+ .setBlockNumber(nextBlockNumber++);
+ BasicBlock newSwitchBlock = switchBuilder.build();
+ newBlocks.addFirst(newSwitchBlock);
+ fallthroughBlock = newSwitchBlock;
}
- predecessor.link(newSwitchBlock);
- blocksIterator.add(newSwitchBlock);
- // The switch fallthrough block is still the same, and it is right after the new switch block.
- assert newSwitchBlock.exit().fallthroughBlock() == IteratorUtils.peekNext(blocksIterator);
+ // Build the if-blocks backwards, to always have the fallthrough block in hand.
+ for (int i = keysToRemove.size() - 1; i >= 0; i--) {
+ int key = keysToRemove.getInt(i);
+ BasicBlock peeledOffTarget = keyToTarget.get(key);
+ IfBuilder ifBuilder = new IfBuilder(code);
+ ifBuilder
+ .setLeft(theSwitch.value())
+ .setRight(key)
+ .setTarget(peeledOffTarget)
+ .setFallthrough(fallthroughBlock)
+ .setBlockNumber(nextBlockNumber++);
+ BasicBlock ifBlock = ifBuilder.build();
+ newBlocks.addFirst(ifBlock);
+ fallthroughBlock = ifBlock;
+ }
+
+ // Attach the debug values from the original switch to the first of the new instructions,
+ originalDebugValues.forEach(fallthroughBlock.exit()::addDebugValue);
+
+ // Finally link the block before the original switch to the new block sequence.
+ originalBlock.link(fallthroughBlock);
+
+ // Finally add the blocks.
+ newBlocks.forEach(blocksIterator::add);
}
public void rewriteSwitch(IRCode code) {
@@ -490,16 +573,16 @@
}
} else {
// Split keys into outliers and sequences.
- List<List<Integer>> sequences = new ArrayList<>();
- List<Integer> outliers = new ArrayList<>();
+ List<IntList> sequences = new ArrayList<>();
+ IntList outliers = new IntArrayList();
- List<Integer> current = new ArrayList<>();
+ IntList current = new IntArrayList();
int[] keys = theSwitch.getKeys();
int previousKey = keys[0];
current.add(previousKey);
for (int i = 1; i < keys.length; i++) {
assert current.size() > 0;
- assert current.get(current.size() - 1) == previousKey;
+ assert current.getInt(current.size() - 1) == previousKey;
int key = keys[i];
if (((long) key - (long) previousKey) > 1) {
if (current.size() == 1) {
@@ -507,7 +590,7 @@
} else {
sequences.add(current);;
}
- current = new ArrayList<>();
+ current = new IntArrayList();
}
current.add(key);
previousKey = key;
@@ -518,22 +601,45 @@
sequences.add(current);
}
- // Only check for rewrite if there is one sequence and one or two outliers.
- if (sequences.size() == 1 && outliers.size() <= 2) {
- // Get the existing dex size for the payload (excluding the switch itself).
- long currentSize = Switch.payloadSize(keys);
- // Estimate the dex size of the rewritten payload and the additional if instructions.
- long rewrittenSize = Switch.payloadSize(sequences.get(0));
+ // Get the existing dex size for the payload and switch.
+ long currentSize = Switch.payloadSize(keys) + Switch.estimatedDexSize();
+
+ // Never replace with more than 10 if/switch instructions.
+ if (outliers.size() + sequences.size() <= 10) {
+ // Calculate estimated size for splitting into ifs and switches.
+ long rewrittenSize = 0;
for (Integer outlier : outliers) {
- rewrittenSize += ConstNumber.estimatedDexSize(
- ConstType.fromMoveType(theSwitch.value().outType()), outlier);
+ if (outlier != 0) {
+ rewrittenSize += ConstNumber.estimatedDexSize(
+ ConstType.fromMoveType(theSwitch.value().outType()), outlier);
+ }
rewrittenSize += If.estimatedDexSize();
}
- // Rewrite if smaller.
+ for (List<Integer> sequence : sequences) {
+ rewrittenSize += Switch.payloadSize(sequence);
+ }
+ rewrittenSize += Switch.estimatedDexSize() * sequences.size();
+
if (rewrittenSize < currentSize) {
convertSwitchToSwitchAndIfs(
- code, blocksIterator, block, iterator, theSwitch, outliers);
- assert code.isConsistentSSA();
+ code, blocksIterator, block, iterator, theSwitch, sequences, outliers);
+ }
+ } else if (outliers.size() > 1) {
+ // Calculate estimated size for splitting into switches (packed for the sequences
+ // and sparse for the outliers).
+ long rewrittenSize = 0;
+ for (List<Integer> sequence : sequences) {
+ rewrittenSize += Switch.payloadSize(sequence);
+ }
+ rewrittenSize += Switch.payloadSize(outliers);
+ rewrittenSize += Switch.estimatedDexSize() * (sequences.size() + 1);
+
+ if (rewrittenSize < currentSize) {
+ // Create a copy to not modify sequences.
+ List<IntList> seqs = new ArrayList<>(sequences);
+ seqs.add(outliers);
+ convertSwitchToSwitchAndIfs(
+ code, blocksIterator, block, iterator, theSwitch, seqs, IntLists.EMPTY_LIST);
}
}
}
@@ -722,7 +828,7 @@
assert (invoke.outType() == argument.outType()) ||
(invoke.outType() == MoveType.OBJECT
&& argument.outType() == MoveType.SINGLE
- && argument.getConstInstruction().asConstNumber().isZero());
+ && argument.isZero());
invoke.outValue().replaceUsers(argument);
invoke.setOutValue(null);
}
@@ -834,7 +940,8 @@
if (exit == null) {
return;
}
- Map<DexField, StaticPut> puts = Maps.newIdentityHashMap();
+ Set<StaticPut> puts = Sets.newIdentityHashSet();
+ Map<DexField, StaticPut> dominatingPuts = Maps.newIdentityHashMap();
for (BasicBlock block : dominatorTree.dominatorBlocks(exit)) {
InstructionListIterator iterator = block.listIterator(block.getInstructions().size());
while (iterator.hasPrevious()) {
@@ -845,25 +952,29 @@
if (field.getHolder() == method.method.getHolder()) {
if (put.inValue().isConstant()) {
if ((field.type.isClassType() || field.type.isArrayType())
- && put.inValue().getConstInstruction().isConstNumber() &&
- put.inValue().getConstInstruction().asConstNumber().isZero()) {
+ && put.inValue().isZero()) {
// Collect put of zero as a potential default value.
- puts.put(put.getField(), put);
+ dominatingPuts.putIfAbsent(put.getField(), put);
+ puts.add(put);
} else if (field.type.isPrimitiveType() || field.type == dexItemFactory.stringType) {
// Collect put as a potential default value.
- puts.put(put.getField(), put);
+ dominatingPuts.putIfAbsent(put.getField(), put);
+ puts.add(put);
}
} else if (isClassNameConstant(method, put)) {
// Collect put of class name constant as a potential default value.
- puts.put(put.getField(), put);
+ dominatingPuts.putIfAbsent(put.getField(), put);
+ puts.add(put);
}
}
}
if (current.isStaticGet()) {
// If a static field is read, any collected potential default value cannot be a
// default value.
- if (puts.containsKey(current.asStaticGet().getField())) {
- puts.remove(current.asStaticGet().getField());
+ DexField field = current.asStaticGet().getField();
+ if (dominatingPuts.containsKey(field)) {
+ dominatingPuts.remove(field);
+ Iterables.removeIf(puts, put -> put.getField() == field);
}
}
}
@@ -871,13 +982,13 @@
if (!puts.isEmpty()) {
// Set initial values for static fields from the definitive static put instructions collected.
- for (StaticPut put : puts.values()) {
+ for (StaticPut put : dominatingPuts.values()) {
DexField field = put.getField();
DexEncodedField encodedField = appInfo.definitionFor(field);
if (field.type == dexItemFactory.stringType) {
if (put.inValue().isConstant()) {
- if (put.inValue().getConstInstruction().isConstNumber()) {
- assert put.inValue().getConstInstruction().asConstNumber().isZero();
+ if (put.inValue().isConstNumber()) {
+ assert put.inValue().isZero();
encodedField.staticValue = DexValueNull.NULL;
} else {
ConstString cnst = put.inValue().getConstInstruction().asConstString();
@@ -896,8 +1007,7 @@
}
}
} else if (field.type.isClassType() || field.type.isArrayType()) {
- if (put.inValue().getConstInstruction().isConstNumber()
- && put.inValue().getConstInstruction().asConstNumber().isZero()) {
+ if (put.inValue().isZero()) {
encodedField.staticValue = DexValueNull.NULL;
} else {
throw new Unreachable("Unexpected default value for field type " + field.type + ".");
@@ -932,7 +1042,7 @@
InstructionListIterator iterator = block.listIterator();
while (iterator.hasNext()) {
Instruction current = iterator.next();
- if (current.isStaticPut() && puts.values().contains(current.asStaticPut())) {
+ if (current.isStaticPut() && puts.contains(current.asStaticPut())) {
iterator.remove();
// Collect, for removal, the instruction that created the value for the static put,
// if all users are gone. This is done even if these instructions can throw as for
@@ -1021,7 +1131,7 @@
if (current.isInvoke() && current.asInvoke().requiredArgumentRegisters() > 5) {
Invoke invoke = current.asInvoke();
it.previous();
- Map<ConstNumber, ConstNumber> oldToNew = new HashMap<>();
+ Map<ConstNumber, ConstNumber> oldToNew = new IdentityHashMap<>();
for (int i = 0; i < invoke.inValues().size(); i++) {
Value value = invoke.inValues().get(i);
if (value.isConstNumber() && value.numberOfUsers() > 1) {
@@ -1101,18 +1211,18 @@
// Add constant into the dominator block of usages.
insertConstantInBlock(instruction, entry.getKey());
} else {
- assert instruction.outValue().numberOfUsers() != 0;
ConstNumber constNumber = instruction.asConstNumber();
Value constantValue = instruction.outValue();
- List<Instruction> toRemove = new ArrayList<>(constantValue.uniqueUsers().size());
+ assert constantValue.numberOfUsers() != 0;
+ assert constantValue.numberOfUsers() == constantValue.numberOfAllUsers();
for (Instruction user : constantValue.uniqueUsers()) {
ConstNumber newCstNum = ConstNumber.copyOf(code, constNumber);
InstructionListIterator iterator = user.getBlock().listIterator(user);
iterator.previous();
iterator.add(newCstNum);
- user.replaceValue(constantValue, newCstNum.outValue(), toRemove);
+ user.replaceValue(constantValue, newCstNum.outValue());
}
- toRemove.forEach(constantValue::removeUser);
+ constantValue.clearUsers();
}
}
} else {
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 b90d2ad..a11d924 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
@@ -23,7 +23,7 @@
import com.android.tools.r8.ir.code.InvokeMethod;
import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.ir.code.ValueNumberGenerator;
-import com.android.tools.r8.ir.conversion.CallGraph;
+import com.android.tools.r8.ir.conversion.CallSiteInformation;
import com.android.tools.r8.ir.conversion.IRConverter;
import com.android.tools.r8.ir.conversion.LensCodeRewriter;
import com.android.tools.r8.ir.conversion.OptimizationFeedback;
@@ -36,6 +36,7 @@
import java.util.ListIterator;
import java.util.Map;
import java.util.Set;
+import java.util.function.Predicate;
import java.util.stream.Collectors;
public class Inliner {
@@ -136,7 +137,8 @@
.sorted(DexEncodedMethod::slowCompare)
.collect(Collectors.toList());
for (DexEncodedMethod method : methods) {
- converter.processMethod(method, feedback, Outliner::noProcessing);
+ converter.processMethod(method, feedback, x -> false, CallSiteInformation.empty(),
+ Outliner::noProcessing);
assert method.isProcessed();
}
}
@@ -222,7 +224,7 @@
this.reason = reason;
}
- boolean forceInline() {
+ boolean ignoreInstructionBudget() {
return reason != Reason.SIMPLE;
}
@@ -315,7 +317,9 @@
return true;
}
- public void performInlining(DexEncodedMethod method, IRCode code, CallGraph callGraph)
+ public void performInlining(DexEncodedMethod method, IRCode code,
+ Predicate<DexEncodedMethod> isProcessedConcurrently,
+ CallSiteInformation callSiteInformation)
throws ApiLevelException {
int instruction_allowance = 1500;
instruction_allowance -= numberOfInstructions(code);
@@ -323,7 +327,8 @@
return;
}
computeReceiverMustBeNonNull(code);
- InliningOracle oracle = new InliningOracle(this, method, callGraph);
+ InliningOracle oracle = new InliningOracle(this, method, callSiteInformation,
+ isProcessedConcurrently);
List<BasicBlock> blocksToRemove = new ArrayList<>();
ListIterator<BasicBlock> blockIterator = code.listIterator();
@@ -339,15 +344,7 @@
InvokeMethod invoke = current.asInvokeMethod();
InlineAction result = invoke.computeInlining(oracle);
if (result != null) {
- DexEncodedMethod target = appInfo.lookup(invoke.getType(), invoke.getInvokedMethod());
- if (target == null) {
- // The declared target cannot be found so skip inlining.
- continue;
- }
- if (!(target.isProcessed() || result.reason == Reason.FORCE)) {
- // Do not inline code that was not processed unless we have to force inline.
- continue;
- }
+ DexEncodedMethod target = result.target;
IRCode inlinee = result
.buildIR(code.valueNumberGenerator, appInfo, graphLense, options);
if (inlinee != null) {
@@ -355,10 +352,6 @@
if (block.hasCatchHandlers() && inlinee.getNormalExitBlock() == null) {
continue;
}
- if (callGraph.isBreaker(method, target)) {
- // Make sure we don't inline a call graph breaker.
- continue;
- }
// If this code did not go through the full pipeline, apply inlining to make sure
// that force inline targets get processed.
if (!target.isProcessed()) {
@@ -366,7 +359,8 @@
if (Log.ENABLED) {
Log.verbose(getClass(), "Forcing extra inline on " + target.toSourceString());
}
- performInlining(target, inlinee, callGraph);
+ performInlining(target, inlinee, isProcessedConcurrently,
+ callSiteInformation);
}
// Make sure constructor inlining is legal.
assert !target.isClassInitializer();
@@ -378,7 +372,7 @@
if (invoke.isInvokeMethodWithReceiver()) {
// If the invoke has a receiver but the declared method holder is different
// from the computed target holder, inlining requires a downcast of the receiver.
- if (result.target.method.getHolder() != target.method.getHolder()) {
+ if (target.method.getHolder() != invoke.getInvokedMethod().getHolder()) {
downcast = result.target.method.getHolder();
}
}
@@ -386,7 +380,7 @@
// Back up before the invoke instruction.
iterator.previous();
instruction_allowance -= numberOfInstructions(inlinee);
- if (instruction_allowance >= 0 || result.forceInline()) {
+ if (instruction_allowance >= 0 || result.ignoreInstructionBudget()) {
iterator.inlineInvoke(code, inlinee, blockIterator, blocksToRemove, downcast);
}
// If we inlined the invoke from a bridge method, it is no longer a bridge method.
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/InliningOracle.java b/src/main/java/com/android/tools/r8/ir/optimize/InliningOracle.java
index 91f0a61..8b7a05e 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/InliningOracle.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/InliningOracle.java
@@ -11,10 +11,11 @@
import com.android.tools.r8.ir.code.InvokeMethodWithReceiver;
import com.android.tools.r8.ir.code.InvokePolymorphic;
import com.android.tools.r8.ir.code.InvokeStatic;
-import com.android.tools.r8.ir.conversion.CallGraph;
+import com.android.tools.r8.ir.conversion.CallSiteInformation;
import com.android.tools.r8.ir.optimize.Inliner.InlineAction;
import com.android.tools.r8.ir.optimize.Inliner.Reason;
import com.android.tools.r8.logging.Log;
+import java.util.function.Predicate;
/**
* The InliningOracle contains information needed for when inlining
@@ -26,16 +27,19 @@
private final Inliner inliner;
private final DexEncodedMethod method;
- private final CallGraph callGraph;
+ private final CallSiteInformation callSiteInformation;
+ private final Predicate<DexEncodedMethod> isProcessedConcurrently;
private final InliningInfo info;
InliningOracle(
Inliner inliner,
DexEncodedMethod method,
- CallGraph callGraph) {
+ CallSiteInformation callSiteInformation,
+ Predicate<DexEncodedMethod> isProcessedConcurrently) {
this.inliner = inliner;
this.method = method;
- this.callGraph = callGraph;
+ this.callSiteInformation = callSiteInformation;
+ this.isProcessedConcurrently = isProcessedConcurrently;
info = Log.ENABLED ? new InliningInfo(method) : null;
}
@@ -66,7 +70,7 @@
&& inliner.appInfo.withLiveness().alwaysInline.contains(target)) {
return Reason.ALWAYS;
}
- if (callGraph.hasSingleCallSite(target)) {
+ if (callSiteInformation.hasSingleCallSite(target)) {
return Reason.SINGLE_CALLER;
}
if (isDoubleInliningTarget(target)) {
@@ -91,18 +95,13 @@
private synchronized boolean isDoubleInliningTarget(DexEncodedMethod candidate) {
// 10 is found from measuring.
- return callGraph.hasDoubleCallSite(candidate)
+ return callSiteInformation.hasDoubleCallSite(candidate)
&& candidate.getCode().isDexCode()
&& (candidate.getCode().asDexCode().instructions.length <= 10);
}
private boolean passesInliningConstraints(InvokeMethod invoke, DexEncodedMethod candidate,
Reason reason) {
- if (callGraph.isBreaker(method, candidate)) {
- // Cycle breaker so abort to preserve compilation order.
- return false;
- }
-
if (method == candidate) {
// Cannot handle recursive inlining at this point.
// Force inlined method should never be recursive.
@@ -113,6 +112,13 @@
return false;
}
+ if (reason != Reason.FORCE && isProcessedConcurrently.test(candidate)) {
+ if (info != null) {
+ info.exclude(invoke, "is processed in parallel");
+ }
+ return false;
+ }
+
// Abort inlining attempt if method -> target access is not right.
if (!inliner.hasInliningAccess(method, candidate)) {
if (info != null) {
@@ -176,7 +182,7 @@
}
Reason reason = computeInliningReason(candidate);
- if (!candidate.isInliningCandidate(method, reason == Reason.FORCE, inliner.appInfo)) {
+ if (!candidate.isInliningCandidate(method, reason, inliner.appInfo)) {
// Abort inlining attempt if the single target is not an inlining candidate.
if (info != null) {
info.exclude(invoke, "target is not identified for inlining");
@@ -202,7 +208,7 @@
Reason reason = computeInliningReason(candidate);
// Determine if this should be inlined no matter how big it is.
- if (!candidate.isInliningCandidate(method, reason == Reason.FORCE, inliner.appInfo)) {
+ if (!candidate.isInliningCandidate(method, reason, inliner.appInfo)) {
// Abort inlining attempt if the single target is not an inlining candidate.
if (info != null) {
info.exclude(invoke, "target is not identified for inlining");
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java b/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
index f6d9c26..ade7537 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
@@ -28,6 +28,7 @@
import com.android.tools.r8.ir.code.BasicBlock;
import com.android.tools.r8.ir.code.BasicBlock.ThrowingInfo;
import com.android.tools.r8.ir.code.CatchHandlers;
+import com.android.tools.r8.ir.code.DebugPosition;
import com.android.tools.r8.ir.code.Div;
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.code.Instruction;
@@ -929,6 +930,16 @@
}
@Override
+ public int getMoveExceptionRegister() {
+ throw new Unreachable();
+ }
+
+ @Override
+ public DebugPosition getDebugPositionAtOffset(int offset) {
+ throw new Unreachable();
+ }
+
+ @Override
public boolean verifyCurrentInstructionCanThrow() {
// TODO(sgjesse): Check more here?
return true;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/PeepholeOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/PeepholeOptimizer.java
index a7d6e8d..35cadf2 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/PeepholeOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/PeepholeOptimizer.java
@@ -84,7 +84,9 @@
commonSuffixSize = Math.min(
commonSuffixSize, sharedSuffixSizeExcludingExit(firstPred, pred, allocator));
}
- assert commonSuffixSize >= 1;
+ if (commonSuffixSize == 0) {
+ continue;
+ }
int blockNumber = startNumberOfNewBlock + newBlocks.size();
BasicBlock newBlock = createAndInsertBlockForSuffix(
blockNumber, commonSuffixSize, predsWithSameLastInstruction, block);
@@ -160,7 +162,9 @@
Instruction i0 = it0.previous();
Instruction i1 = it1.previous();
if (!i0.identicalAfterRegisterAllocation(i1, allocator)) {
- return suffixSize;
+ // If the shared suffix follows a debug position at least one instruction must remain
+ // unshared to ensure the debug position is at a different pc than the shared suffix.
+ return i0.isDebugPosition() || i1.isDebugPosition() ? suffixSize - 1 : suffixSize;
}
suffixSize++;
}
diff --git a/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java b/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
index 6cd453b..52653a7 100644
--- a/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
+++ b/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
@@ -8,6 +8,7 @@
import com.android.tools.r8.graph.DebugLocalInfo;
import com.android.tools.r8.ir.code.Add;
import com.android.tools.r8.ir.code.And;
+import com.android.tools.r8.ir.code.ArithmeticBinop;
import com.android.tools.r8.ir.code.BasicBlock;
import com.android.tools.r8.ir.code.DebugLocalsChange;
import com.android.tools.r8.ir.code.IRCode;
@@ -24,7 +25,6 @@
import com.android.tools.r8.ir.code.Xor;
import com.android.tools.r8.ir.regalloc.RegisterPositions.Type;
import com.android.tools.r8.logging.Log;
-import com.android.tools.r8.utils.CfgPrinter;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.ListUtils;
import com.android.tools.r8.utils.StringUtils;
@@ -39,7 +39,6 @@
import it.unimi.dsi.fastutil.ints.IntIterator;
import it.unimi.dsi.fastutil.ints.IntSet;
import java.util.ArrayList;
-import java.util.Comparator;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.Iterator;
@@ -139,8 +138,7 @@
// List of intervals where the current instruction falls into one of their live range holes.
private List<LiveIntervals> inactive = new LinkedList<>();
// List of intervals that no register has been allocated to sorted by first live range.
- private PriorityQueue<LiveIntervals> unhandled =
- new PriorityQueue<>(Comparator.comparingInt(LiveIntervals::getStart));
+ private PriorityQueue<LiveIntervals> unhandled = new PriorityQueue<>();
// The first register used for parallel moves. After register allocation the parallel move
// temporary registers are [firstParallelMoveTemporary, maxRegisterNumber].
@@ -197,7 +195,6 @@
}
}
- clearUserInfo();
assert code.isConsistentGraph();
if (Log.ENABLED) {
Log.debug(this.getClass(), toString());
@@ -206,6 +203,7 @@
if (debug) {
computeDebugInfo(blocks);
}
+ clearUserInfo();
clearState();
}
@@ -814,6 +812,9 @@
// Go through each unhandled live interval and find a register for it.
while (!unhandled.isEmpty()) {
LiveIntervals unhandledInterval = unhandled.poll();
+
+ setHintToPromote2AddrInstruction(unhandledInterval);
+
// If this interval value is the src of an argument move. Fix the registers for the
// consecutive arguments now and add hints to the move sources. This looks forward
// and propagate hints backwards to avoid many moves in connection with ranged invokes.
@@ -865,6 +866,31 @@
return true;
}
+ /*
+ * This method tries to promote arithmetic binary instruction to use the 2Addr form.
+ * To achieve this goal the output interval of the binary instruction is set with an hint
+ * that is the left interval or the right interval if possible when intervals do not overlap.
+ */
+ private void setHintToPromote2AddrInstruction(LiveIntervals unhandledInterval) {
+ if (unhandledInterval.getHint() == null &&
+ unhandledInterval.getValue().definition instanceof ArithmeticBinop) {
+ ArithmeticBinop binOp = unhandledInterval.getValue().definition.asArithmeticBinop();
+ Value left = binOp.leftValue();
+ assert left != null;
+ if (left.getLiveIntervals() != null &&
+ !left.getLiveIntervals().overlaps(unhandledInterval)) {
+ unhandledInterval.setHint(left.getLiveIntervals());
+ } else {
+ Value right = binOp.rightValue();
+ assert right != null;
+ if (binOp.isCommutative() && right.getLiveIntervals() != null &&
+ !right.getLiveIntervals().overlaps(unhandledInterval)) {
+ unhandledInterval.setHint(right.getLiveIntervals());
+ }
+ }
+ }
+ }
+
/**
* Perform look-ahead and allocate registers for linked argument chains that have the argument
* interval as an argument move source.
@@ -2216,25 +2242,6 @@
return true;
}
- public void print(CfgPrinter printer, String title) {
- printer.begin("intervals");
- printer.print("name \"").append(title).append("\"").ln();
- PriorityQueue<LiveIntervals> sortedIntervals =
- new PriorityQueue<>((o1, o2) -> Integer.compare(o1.getStart(), o2.getStart()));
- sortedIntervals.addAll(liveIntervals);
- for (LiveIntervals interval = sortedIntervals.poll();
- interval != null;
- interval = sortedIntervals.poll()) {
- Value value = interval.getValue();
- if (interval.getRanges().get(0).isInfinite()) {
- // Skip argument sentinels.
- continue;
- }
- interval.print(printer, value.getNumber(), value.getNumber());
- }
- printer.end("intervals");
- }
-
@Override
public String toString() {
StringBuilder builder = new StringBuilder("Live ranges:\n");
diff --git a/src/main/java/com/android/tools/r8/ir/regalloc/LiveIntervals.java b/src/main/java/com/android/tools/r8/ir/regalloc/LiveIntervals.java
index 65628c3..0d8c6b8 100644
--- a/src/main/java/com/android/tools/r8/ir/regalloc/LiveIntervals.java
+++ b/src/main/java/com/android/tools/r8/ir/regalloc/LiveIntervals.java
@@ -15,7 +15,7 @@
import java.util.List;
import java.util.TreeSet;
-public class LiveIntervals {
+public class LiveIntervals implements Comparable<LiveIntervals> {
private final Value value;
private LiveIntervals nextConsecutive;
@@ -454,6 +454,12 @@
}
@Override
+ public int compareTo(LiveIntervals other) {
+ int startDiff = getStart() - other.getStart();
+ return startDiff != 0 ? startDiff : (value.getNumber() - other.value.getNumber());
+ }
+
+ @Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("(cons ");
diff --git a/src/main/java/com/android/tools/r8/ir/regalloc/RegisterMove.java b/src/main/java/com/android/tools/r8/ir/regalloc/RegisterMove.java
index 9cb873e..4a21e8a 100644
--- a/src/main/java/com/android/tools/r8/ir/regalloc/RegisterMove.java
+++ b/src/main/java/com/android/tools/r8/ir/regalloc/RegisterMove.java
@@ -10,7 +10,7 @@
// Register moves used by the spilling register allocator. These are used both for spill and
// for phi moves and they are moves between actual registers represented by their register number.
-public class RegisterMove {
+public class RegisterMove implements Comparable<RegisterMove> {
MoveType type;
int dst;
int src;
@@ -70,4 +70,30 @@
RegisterMove o = (RegisterMove) other;
return o.src == src && o.dst == dst && o.type == type && o.definition == definition;
}
+
+ @Override
+ public int compareTo(RegisterMove o) {
+ int srcDiff = src - o.src;
+ if (srcDiff != 0) {
+ return srcDiff;
+ }
+ int dstDiff = dst - o.dst;
+ if (dstDiff != 0) {
+ return dstDiff;
+ }
+ int typeDiff = o.type.ordinal() - type.ordinal();
+ if (typeDiff != 0) {
+ return typeDiff;
+ }
+ if (definition == null) {
+ if (o.definition != null) {
+ return -1;
+ }
+ return 0;
+ }
+ if (o.definition == null) {
+ return 1;
+ }
+ return definition.getNumber() - o.definition.getNumber();
+ }
}
diff --git a/src/main/java/com/android/tools/r8/ir/regalloc/RegisterMoveScheduler.java b/src/main/java/com/android/tools/r8/ir/regalloc/RegisterMoveScheduler.java
index cc26729..007aa6f 100644
--- a/src/main/java/com/android/tools/r8/ir/regalloc/RegisterMoveScheduler.java
+++ b/src/main/java/com/android/tools/r8/ir/regalloc/RegisterMoveScheduler.java
@@ -14,15 +14,15 @@
import java.util.Deque;
import java.util.HashMap;
import java.util.Iterator;
-import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.TreeSet;
public class RegisterMoveScheduler {
// The set of moves to schedule.
- private Set<RegisterMove> moveSet = new LinkedHashSet<>();
+ private Set<RegisterMove> moveSet = new TreeSet<>();
// Mapping to keep track of which values currently corresponds to each other.
// This is initially an identity map but changes as we insert moves.
private Map<Integer, Integer> valueMap = new HashMap<>();
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/SingleBlockSourceCode.java b/src/main/java/com/android/tools/r8/ir/synthetic/SingleBlockSourceCode.java
index 5fa247e..391b479 100644
--- a/src/main/java/com/android/tools/r8/ir/synthetic/SingleBlockSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/synthetic/SingleBlockSourceCode.java
@@ -13,6 +13,7 @@
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.ir.code.Argument;
import com.android.tools.r8.ir.code.CatchHandlers;
+import com.android.tools.r8.ir.code.DebugPosition;
import com.android.tools.r8.ir.code.MoveType;
import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.ir.conversion.IRBuilder;
@@ -198,6 +199,16 @@
}
@Override
+ public int getMoveExceptionRegister() {
+ throw new Unreachable();
+ }
+
+ @Override
+ public DebugPosition getDebugPositionAtOffset(int offset) {
+ throw new Unreachable();
+ }
+
+ @Override
public final boolean verifyCurrentInstructionCanThrow() {
return true;
}
diff --git a/src/main/java/com/android/tools/r8/naming/DictionaryReader.java b/src/main/java/com/android/tools/r8/naming/DictionaryReader.java
index 146d531..1d05d25 100644
--- a/src/main/java/com/android/tools/r8/naming/DictionaryReader.java
+++ b/src/main/java/com/android/tools/r8/naming/DictionaryReader.java
@@ -9,7 +9,6 @@
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
-import java.util.List;
public class DictionaryReader implements AutoCloseable {
diff --git a/src/main/java/com/android/tools/r8/naming/Minifier.java b/src/main/java/com/android/tools/r8/naming/Minifier.java
index a26e135..6a58866 100644
--- a/src/main/java/com/android/tools/r8/naming/Minifier.java
+++ b/src/main/java/com/android/tools/r8/naming/Minifier.java
@@ -3,7 +3,6 @@
// 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.AppInfo;
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexEncodedMethod;
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 564e6de..0b683e2 100644
--- a/src/main/java/com/android/tools/r8/naming/ProguardMapReader.java
+++ b/src/main/java/com/android/tools/r8/naming/ProguardMapReader.java
@@ -64,13 +64,14 @@
private final BufferedReader reader;
+ @Override
public void close() throws IOException {
if (reader != null) {
reader.close();
}
}
- private ProguardMapReader(BufferedReader reader) throws IOException {
+ private ProguardMapReader(BufferedReader reader) {
this.reader = reader;
}
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 5292a45..22fdf45 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
@@ -22,11 +22,8 @@
import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.CharBuffer;
-import java.nio.charset.StandardCharsets;
-import java.nio.file.Files;
import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
-import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@@ -85,31 +82,32 @@
}
public void parse(Path path) throws ProguardRuleParserException, IOException {
- parse(Collections.singletonList(path));
+ parse(ImmutableList.of(new ProguardConfigurationSourceFile(path)));
}
- public void parse(List<Path> pathList) throws ProguardRuleParserException, IOException {
- for (Path path : pathList) {
- new ProguardFileParser(path).parse();
+ public void parse(ProguardConfigurationSource source)
+ throws ProguardRuleParserException, IOException {
+ parse(ImmutableList.of(source));
+ }
+
+ public void parse(List<ProguardConfigurationSource> sources)
+ throws ProguardRuleParserException, IOException {
+ for (ProguardConfigurationSource source : sources) {
+ new ProguardFileParser(source).parse();
}
}
private class ProguardFileParser {
- private final Path path;
+ private final String name;
private final String contents;
private int position = 0;
private Path baseDirectory;
- ProguardFileParser(Path path) throws IOException {
- this.path = path;
- contents = new String(Files.readAllBytes(path), StandardCharsets.UTF_8);
- baseDirectory = path.getParent();
- if (baseDirectory == null) {
- // path parent can be null only if it's root dir or if its a one element path relative to
- // current directory.
- baseDirectory = Paths.get(".");
- }
+ ProguardFileParser(ProguardConfigurationSource source) throws IOException {
+ contents = source.get();
+ baseDirectory = source.getBaseDirectory();
+ name = source.getName();
}
public void parse() throws ProguardRuleParserException {
@@ -271,7 +269,7 @@
private void parseInclude() throws ProguardRuleParserException {
Path included = parseFileName();
try {
- new ProguardFileParser(included).parse();
+ new ProguardFileParser(new ProguardConfigurationSourceFile(included)).parse();
} catch (FileNotFoundException | NoSuchFileException e) {
throw parseError("Included file '" + included.toString() + "' not found", e);
} catch (IOException e) {
@@ -1037,12 +1035,12 @@
String line = lines[lineNumber];
if (remaining <= line.length() || lineNumber == lines.length - 1) {
String arrow = CharBuffer.allocate(remaining).toString().replace( '\0', ' ' ) + '^';
- return path.toString() + ":" + (lineNumber + 1) + ":" + remaining + "\n" + line
+ return name + ":" + (lineNumber + 1) + ":" + remaining + "\n" + line
+ '\n' + arrow;
}
remaining -= (line.length() + 1); // Include newline.
}
- return path.toString();
+ return name;
}
private ProguardRuleParserException parseError(String message) {
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationSource.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationSource.java
new file mode 100644
index 0000000..b0375c0
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationSource.java
@@ -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.
+
+package com.android.tools.r8.shaking;
+
+import java.io.IOException;
+import java.nio.file.Path;
+
+public interface ProguardConfigurationSource {
+ String get() throws IOException;
+ Path getBaseDirectory();
+ String getName();
+}
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationSourceFile.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationSourceFile.java
new file mode 100644
index 0000000..058c14f
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationSourceFile.java
@@ -0,0 +1,37 @@
+// 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.shaking;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+
+public class ProguardConfigurationSourceFile implements ProguardConfigurationSource {
+ private final Path path;
+
+ public ProguardConfigurationSourceFile(Path path) {
+ this.path = path;
+ }
+
+ public String get() throws IOException{
+ return new String(Files.readAllBytes(path), StandardCharsets.UTF_8);
+ }
+
+ public Path getBaseDirectory() {
+ Path baseDirectory = path.getParent();
+ if (baseDirectory == null) {
+ // Path parent can be null only if it's root dir or if its a one element path relative to
+ // current directory.
+ baseDirectory = Paths.get(".");
+ }
+ return baseDirectory;
+ }
+
+ public String getName() {
+ return path.toString();
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationSourceStrings.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationSourceStrings.java
new file mode 100644
index 0000000..ac5f0ab
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationSourceStrings.java
@@ -0,0 +1,31 @@
+// 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.shaking;
+
+import java.io.IOException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.List;
+import joptsimple.internal.Strings;
+
+public class ProguardConfigurationSourceStrings implements ProguardConfigurationSource {
+ private final List<String> config;
+
+ public ProguardConfigurationSourceStrings(List<String> config) {
+ this.config = config;
+ }
+
+ public String get() throws IOException{
+ return Strings.join(config, "\n");
+ }
+
+ public Path getBaseDirectory() {
+ return Paths.get(".");
+ }
+
+ public String getName() {
+ return "<no file>";
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
index 5e7b42d..4dd1880 100644
--- a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
+++ b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
@@ -28,6 +28,7 @@
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
+import java.util.Comparator;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.List;
@@ -572,5 +573,29 @@
return Collections
.unmodifiableMap(dependentNoShrinking.getOrDefault(item, Collections.emptyMap()));
}
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder();
+ builder.append("RootSet");
+
+ builder.append("\nnoShrinking: " + noShrinking.size());
+ builder.append("\nnoOptimization: " + noOptimization.size());
+ builder.append("\nnoObfuscation: " + noObfuscation.size());
+ builder.append("\nreasonAsked: " + reasonAsked.size());
+ builder.append("\nkeepPackageName: " + keepPackageName.size());
+ builder.append("\ncheckDiscarded: " + checkDiscarded.size());
+ builder.append("\nnoSideEffects: " + noSideEffects.size());
+ builder.append("\nassumedValues: " + assumedValues.size());
+ builder.append("\ndependentNoShrinking: " + dependentNoShrinking.size());
+
+ builder.append("\n\nNo Shrinking:");
+ noShrinking.keySet().stream()
+ .sorted(Comparator.comparing(DexItem::toSourceString))
+ .forEach(a -> builder
+ .append("\n").append(a.toSourceString()).append(" ").append(noShrinking.get(a)));
+ builder.append("\n");
+ return builder.toString();
+ }
}
}
diff --git a/src/main/java/com/android/tools/r8/shaking/TreePruner.java b/src/main/java/com/android/tools/r8/shaking/TreePruner.java
index 153f604..3decec1 100644
--- a/src/main/java/com/android/tools/r8/shaking/TreePruner.java
+++ b/src/main/java/com/android/tools/r8/shaking/TreePruner.java
@@ -15,7 +15,6 @@
import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
import com.android.tools.r8.utils.InternalOptions;
import com.google.common.collect.Sets;
-import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
@@ -39,7 +38,7 @@
? new UsagePrinter() : UsagePrinter.DONT_PRINT;
}
- public DexApplication run() throws IOException {
+ public DexApplication run() {
application.timing.begin("Pruning application...");
if (options.debugKeepRules && !options.skipMinification) {
System.out.println(
diff --git a/src/main/java/com/android/tools/r8/shaking/protolite/ProtoLitePruner.java b/src/main/java/com/android/tools/r8/shaking/protolite/ProtoLitePruner.java
index 4610f41..09bbad6 100644
--- a/src/main/java/com/android/tools/r8/shaking/protolite/ProtoLitePruner.java
+++ b/src/main/java/com/android/tools/r8/shaking/protolite/ProtoLitePruner.java
@@ -137,8 +137,7 @@
}
private boolean isDefinedAsNull(Value value) {
- return value.definition != null && value.definition.isConstNumber()
- && value.definition.asConstNumber().isZero();
+ return value.definition != null && value.isZero();
}
private boolean isComputeSizeMethod(DexMethod invokedMethod) {
diff --git a/src/main/java/com/android/tools/r8/utils/AndroidApiLevel.java b/src/main/java/com/android/tools/r8/utils/AndroidApiLevel.java
new file mode 100644
index 0000000..d72fb2b
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/AndroidApiLevel.java
@@ -0,0 +1,50 @@
+// 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;
+
+/**
+ * Android API level description
+ */
+public enum AndroidApiLevel {
+ O(26),
+ N_MR1(25),
+ N(24),
+ M(23),
+ L_MR1(22),
+ L(21),
+ K_WATCH(20),
+ K(19),
+ J_MR2(18),
+ J_MR1(17),
+ J(16),
+ I_MR1(15),
+ I(14),
+ H_MR2(13),
+ H_MR1(12),
+ H(11),
+ G_MR1(10),
+ G(9),
+ F(8),
+ E_MR1(7),
+ E_0_1(6),
+ E(5),
+ D(4),
+ C(3),
+ B_1_1(2),
+ B(1);
+
+ private final int level;
+
+ AndroidApiLevel(int level) {
+ this.level = level;
+ }
+
+ public int getLevel() {
+ return level;
+ }
+
+ public String getName() {
+ return "Android " + name();
+ }
+}
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 4c6c56f..a4c2c62 100644
--- a/src/main/java/com/android/tools/r8/utils/AndroidApp.java
+++ b/src/main/java/com/android/tools/r8/utils/AndroidApp.java
@@ -32,6 +32,7 @@
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
+import java.util.stream.Stream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
@@ -302,9 +303,11 @@
*/
public void writeToDirectory(Path directory, OutputMode outputMode) throws IOException {
if (outputMode == OutputMode.Indexed) {
- for (Path path : Files.list(directory).collect(Collectors.toList())) {
- if (isClassesDexFile(path)) {
- Files.delete(path);
+ try (Stream<Path> filesInDir = Files.list(directory)) {
+ for (Path path : filesInDir.collect(Collectors.toList())) {
+ if (isClassesDexFile(path)) {
+ Files.delete(path);
+ }
}
}
}
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 9f3488c..c241bd7 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -14,6 +14,7 @@
import com.google.common.collect.ImmutableList;
import java.nio.file.Path;
import java.util.List;
+import java.util.Set;
import java.util.function.Function;
public class InternalOptions {
@@ -197,7 +198,7 @@
public static class TestingOptions {
- public Function<List<DexEncodedMethod>, List<DexEncodedMethod>> irOrdering =
+ public Function<Set<DexEncodedMethod>, Set<DexEncodedMethod>> irOrdering =
Function.identity();
}
@@ -321,37 +322,37 @@
}
public boolean canUseInvokePolymorphic() {
- return minApiLevel >= Constants.ANDROID_O_API;
+ return minApiLevel >= AndroidApiLevel.O.getLevel();
}
public boolean canUseInvokeCustom() {
- return minApiLevel >= Constants.ANDROID_O_API;
+ return minApiLevel >= AndroidApiLevel.O.getLevel();
}
public boolean canUseDefaultAndStaticInterfaceMethods() {
- return minApiLevel >= Constants.ANDROID_N_API;
+ return minApiLevel >= AndroidApiLevel.N.getLevel();
}
public boolean canUsePrivateInterfaceMethods() {
- return minApiLevel >= Constants.ANDROID_N_API;
+ return minApiLevel >= AndroidApiLevel.N.getLevel();
}
public boolean canUseMultidex() {
- return intermediate || minApiLevel >= Constants.ANDROID_L_API;
+ return intermediate || minApiLevel >= AndroidApiLevel.L.getLevel();
}
public boolean canUseLongCompareAndObjectsNonNull() {
- return minApiLevel >= Constants.ANDROID_K_API;
+ return minApiLevel >= AndroidApiLevel.K.getLevel();
}
public boolean canUseSuppressedExceptions() {
- return minApiLevel >= Constants.ANDROID_K_API;
+ return minApiLevel >= AndroidApiLevel.K.getLevel();
}
// APIs for accessing parameter names annotations are not available before Android O, thus does
// not emit them to avoid wasting space in Dex files because runtimes before Android O will ignore
// them.
public boolean canUseParameterNameAnnotations() {
- return minApiLevel >= Constants.ANDROID_O_API;
+ return minApiLevel >= AndroidApiLevel.O.getLevel();
}
}
diff --git a/src/main/java/com/android/tools/r8/utils/PreloadedClassFileProvider.java b/src/main/java/com/android/tools/r8/utils/PreloadedClassFileProvider.java
index 07c136d..5ea2a30 100644
--- a/src/main/java/com/android/tools/r8/utils/PreloadedClassFileProvider.java
+++ b/src/main/java/com/android/tools/r8/utils/PreloadedClassFileProvider.java
@@ -16,15 +16,16 @@
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
+import java.io.InputStream;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Collections;
+import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.zip.ZipEntry;
-import java.util.zip.ZipException;
-import java.util.zip.ZipInputStream;
+import java.util.zip.ZipFile;
/** Lazy Java class file resource provider based on preloaded/prebuilt context. */
public final class PreloadedClassFileProvider implements ClassFileResourceProvider {
@@ -52,17 +53,17 @@
public static ClassFileResourceProvider fromArchive(Path archive) throws IOException {
assert isArchive(archive);
Builder builder = builder();
- try (ZipInputStream stream = new ZipInputStream(new FileInputStream(archive.toFile()))) {
- ZipEntry entry;
- while ((entry = stream.getNextEntry()) != null) {
+ try (ZipFile zipFile = new ZipFile(archive.toFile())) {
+ final Enumeration<? extends ZipEntry> entries = zipFile.entries();
+ while (entries.hasMoreElements()) {
+ ZipEntry entry = entries.nextElement();
String name = entry.getName();
if (isClassFile(Paths.get(name))) {
- builder.addResource(guessTypeDescriptor(name), ByteStreams.toByteArray(stream));
+ try (InputStream entryStream = zipFile.getInputStream(entry)) {
+ builder.addResource(guessTypeDescriptor(name), ByteStreams.toByteArray(entryStream));
+ }
}
}
- } catch (ZipException e) {
- throw new CompilationError(
- "Zip error while reading '" + archive + "': " + e.getMessage(), e);
}
return builder.build();
diff --git a/src/main/java/com/android/tools/r8/utils/ProgramFileArchiveReader.java b/src/main/java/com/android/tools/r8/utils/ProgramFileArchiveReader.java
index 8341921..79ab853 100644
--- a/src/main/java/com/android/tools/r8/utils/ProgramFileArchiveReader.java
+++ b/src/main/java/com/android/tools/r8/utils/ProgramFileArchiveReader.java
@@ -10,17 +10,18 @@
import com.android.tools.r8.Resource;
import com.android.tools.r8.errors.CompilationError;
import com.google.common.io.ByteStreams;
-import java.io.FileInputStream;
import java.io.IOException;
+import java.io.InputStream;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
+import java.util.Enumeration;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipException;
-import java.util.zip.ZipInputStream;
+import java.util.zip.ZipFile;
class ProgramFileArchiveReader {
@@ -38,21 +39,24 @@
assert isArchive(archive);
dexResources = new ArrayList<>();
classResources = new ArrayList<>();
- try (ZipInputStream stream = new ZipInputStream(new FileInputStream(archive.toFile()))) {
- ZipEntry entry;
- while ((entry = stream.getNextEntry()) != null) {
- Path name = Paths.get(entry.getName());
- if (isDexFile(name)) {
- if (!ignoreDexInArchive) {
- Resource resource =
- new OneShotByteResource(Resource.Kind.DEX, ByteStreams.toByteArray(stream), null);
- dexResources.add(resource);
+ try (ZipFile zipFile = new ZipFile(archive.toFile())) {
+ final Enumeration<? extends ZipEntry> entries = zipFile.entries();
+ while (entries.hasMoreElements()) {
+ ZipEntry entry = entries.nextElement();
+ try (InputStream stream = zipFile.getInputStream(entry)) {
+ Path name = Paths.get(entry.getName());
+ if (isDexFile(name)) {
+ if (!ignoreDexInArchive) {
+ Resource resource =
+ new OneShotByteResource(Resource.Kind.DEX, ByteStreams.toByteArray(stream), null);
+ dexResources.add(resource);
+ }
+ } else if (isClassFile(name)) {
+ String descriptor = PreloadedClassFileProvider.guessTypeDescriptor(name);
+ Resource resource = new OneShotByteResource(Resource.Kind.CLASSFILE,
+ ByteStreams.toByteArray(stream), Collections.singleton(descriptor));
+ classResources.add(resource);
}
- } else if (isClassFile(name)) {
- String descriptor = PreloadedClassFileProvider.guessTypeDescriptor(name);
- Resource resource = new OneShotByteResource(Resource.Kind.CLASSFILE,
- ByteStreams.toByteArray(stream), Collections.singleton(descriptor));
- classResources.add(resource);
}
}
} catch (ZipException e) {
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 47d8e01..ca9a404 100644
--- a/src/main/java/com/android/tools/r8/utils/ZipUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/ZipUtils.java
@@ -6,27 +6,31 @@
import com.android.tools.r8.errors.CompilationError;
import com.google.common.io.ByteStreams;
import java.io.File;
-import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
+import java.io.InputStream;
import java.nio.file.Path;
import java.util.ArrayList;
+import java.util.Enumeration;
import java.util.List;
import java.util.function.Predicate;
import java.util.zip.ZipEntry;
-import java.util.zip.ZipInputStream;
+import java.util.zip.ZipFile;
public class ZipUtils {
public interface OnEntryHandler {
- void onEntry(ZipEntry entry, ZipInputStream input) throws IOException;
+ void onEntry(ZipEntry entry, InputStream input) throws IOException;
}
- public static void iter(String zipFile, OnEntryHandler handler) throws IOException {
- try (ZipInputStream input = new ZipInputStream(new FileInputStream(zipFile))){
- ZipEntry entry;
- while ((entry = input.getNextEntry()) != null) {
- handler.onEntry(entry, input);
+ public static void iter(String zipFileStr, OnEntryHandler handler) throws IOException {
+ try (ZipFile zipFile = new ZipFile(zipFileStr)) {
+ final Enumeration<? extends ZipEntry> entries = zipFile.entries();
+ while (entries.hasMoreElements()) {
+ ZipEntry entry = entries.nextElement();
+ try (InputStream entryStream = zipFile.getInputStream(entry)) {
+ handler.onEntry(entry, entryStream);
+ }
}
}
}
diff --git a/src/test/debugTestResources/ClassInitializerAssignmentInitialization.java b/src/test/debugTestResources/ClassInitializerAssignmentInitialization.java
new file mode 100644
index 0000000..fa6078a
--- /dev/null
+++ b/src/test/debugTestResources/ClassInitializerAssignmentInitialization.java
@@ -0,0 +1,16 @@
+// 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.
+
+public class ClassInitializerAssignmentInitialization {
+
+ static int x = 1;
+ static int y;
+
+ static int z = 2;
+
+ public static void main(String[] args) {
+ System.out.println("x=" + x);
+ System.out.println("y=" + y);
+ }
+}
diff --git a/src/test/debugTestResources/ClassInitializerEmpty.java b/src/test/debugTestResources/ClassInitializerEmpty.java
new file mode 100644
index 0000000..9062d62
--- /dev/null
+++ b/src/test/debugTestResources/ClassInitializerEmpty.java
@@ -0,0 +1,12 @@
+// 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.
+
+public class ClassInitializerEmpty {
+
+ static {
+ }
+
+ public static void main(String[] args) {
+ }
+}
diff --git a/src/test/debugTestResources/ClassInitializerMixedInitialization.java b/src/test/debugTestResources/ClassInitializerMixedInitialization.java
new file mode 100644
index 0000000..504db6c
--- /dev/null
+++ b/src/test/debugTestResources/ClassInitializerMixedInitialization.java
@@ -0,0 +1,25 @@
+// 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.
+
+public class ClassInitializerMixedInitialization {
+
+ static boolean b;
+ static int x = 1;
+ static int y;
+
+ static {
+ x = 2;
+ if (b) {
+ y = 1;
+ } else {
+ y = 2;
+ }
+ x = 3;
+ }
+
+ public static void main(String[] args) {
+ System.out.println("x=" + x);
+ System.out.println("y=" + y);
+ }
+}
diff --git a/src/test/debugTestResources/ClassInitializerStaticBlockInitialization.java b/src/test/debugTestResources/ClassInitializerStaticBlockInitialization.java
new file mode 100644
index 0000000..78fe9a0
--- /dev/null
+++ b/src/test/debugTestResources/ClassInitializerStaticBlockInitialization.java
@@ -0,0 +1,26 @@
+// 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.
+
+public class ClassInitializerStaticBlockInitialization {
+
+ static boolean b;
+ static int x;
+ static int y;
+
+ static {
+ x = 1;
+ x = 2;
+ if (b) {
+ y = 1;
+ } else {
+ y = 2;
+ }
+ x = 3;
+ }
+
+ public static void main(String[] args) {
+ System.out.println("x=" + x);
+ System.out.println("y=" + y);
+ }
+}
diff --git a/src/test/debugTestResources/Locals.java b/src/test/debugTestResources/Locals.java
index d42dd61..5afcabb 100644
--- a/src/test/debugTestResources/Locals.java
+++ b/src/test/debugTestResources/Locals.java
@@ -244,6 +244,80 @@
return sum + x + y;
}
+ public static int argumentLiveAtReturn(int x) {
+ switch (x) {
+ case 0:
+ return 0;
+ case 1:
+ return 0;
+ case 2:
+ return 0;
+ case 100:
+ return 1;
+ case 101:
+ return 1;
+ case 102:
+ return 1;
+ }
+ return -1;
+ }
+
+ public static int switchRewriteToIfs(int x) {
+ {
+ int t = x + 1;
+ x = t;
+ x = x + x;
+ }
+ switch (x) {
+ case 0:
+ return 0;
+ case 100:
+ return 1;
+ }
+ return -1;
+ }
+
+ public static int switchRewriteToSwitches(int x) {
+ {
+ int t = x + 1;
+ x = t;
+ x = x + x;
+ }
+ switch (x) {
+ case 0:
+ return 0;
+ case 1:
+ return 0;
+ case 2:
+ return 0;
+ case 100:
+ return 1;
+ case 101:
+ return 1;
+ case 102:
+ return 1;
+ }
+ return -1;
+ }
+
+ public static String regression65039701(boolean createIntNotLong) {
+ Object a = createIntNotLong ? new int[1] : new long[1];
+ if (a instanceof int []) {
+ ((int [])a)[0] = 0;
+ }
+ return "OK";
+ }
+
+ public static void regression65066975(boolean bit) {
+ nop();
+ if (bit) {
+ nop();
+ } else {
+ nop();
+ }
+ nop();
+ }
+
public static void main(String[] args) {
noLocals();
unusedLocals();
@@ -259,5 +333,10 @@
stepNonEmptyForLoopBody(3);
tempInCase(42);
localSwap(1, 2);
+ argumentLiveAtReturn(-1);
+ switchRewriteToIfs(1);
+ switchRewriteToSwitches(1);
+ regression65039701(true);
+ regression65066975(false);
}
}
diff --git a/src/test/debugTestResourcesKotlin/KotlinApp.kt b/src/test/debugTestResourcesKotlin/KotlinApp.kt
index 7fa2be1..094407b 100644
--- a/src/test/debugTestResourcesKotlin/KotlinApp.kt
+++ b/src/test/debugTestResourcesKotlin/KotlinApp.kt
@@ -3,42 +3,27 @@
// BSD-style license that can be found in the LICENSE file.
class KotlinApp {
+
+ fun ifElse(cond: Boolean) {
+ val a = 10
+ if (cond) {
+ val b = a * 2
+ printInt(b)
+ } else {
+ val c = a / 2
+ print(c)
+ }
+ }
+
+ fun printInt(i: Int) {
+ println(i)
+ }
+
companion object {
@JvmStatic fun main(args: Array<String>) {
- println("Hello world!")
val instance = KotlinApp()
- instance.processObject(instance, instance::printObject)
- instance.invokeInlinedFunctions()
+ instance.ifElse(true)
+ instance.ifElse(false)
}
}
-
- fun processObject(obj: Any, func: (Any) -> Unit) {
- func(obj)
- }
-
- fun printObject(obj: Any) {
- println(obj)
- }
-
- fun invokeInlinedFunctions() {
- inlinedA {
- val inA = 1
- inlinedB {
- val inB = 2
- foo(inA, inB)
- }
- }
- }
-
- inline fun inlinedA(f: () -> Unit) {
- f()
- }
-
- inline fun inlinedB(f: () -> Unit) {
- f()
- }
-
- fun foo(a: Int, b: Int) {
- println("a=$a, b=$b")
- }
}
\ No newline at end of file
diff --git a/src/test/debugTestResourcesKotlin/KotlinInline.kt b/src/test/debugTestResourcesKotlin/KotlinInline.kt
new file mode 100644
index 0000000..7f914e4
--- /dev/null
+++ b/src/test/debugTestResourcesKotlin/KotlinInline.kt
@@ -0,0 +1,59 @@
+// 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.
+
+class KotlinInline {
+
+ fun processObject(obj: Any, func: (Any) -> Unit) {
+ func(obj)
+ }
+
+ fun printObject(obj: Any) {
+ println(obj)
+ }
+
+ fun invokeInlinedFunctions() {
+ inlinedA {
+ val inA = 1
+ inlinedB {
+ val inB = 2
+ foo(inA, inB)
+ }
+ }
+ }
+
+ inline fun inlinedA(f: () -> Unit) {
+ f()
+ }
+
+ inline fun inlinedB(f: () -> Unit) {
+ f()
+ }
+
+ fun foo(a: Int, b: Int) {
+ println("a=$a, b=$b")
+ }
+
+ fun emptyMethod(unused: Int) {
+ }
+
+ fun singleInline() {
+ emptyMethod(0)
+ inlined()
+ emptyMethod(1)
+ }
+
+ inline fun inlined() {
+ emptyMethod(-1)
+ }
+
+ companion object {
+ @JvmStatic fun main(args: Array<String>) {
+ println("Hello world!")
+ val instance = KotlinInline()
+ instance.processObject(instance, instance::printObject)
+ instance.invokeInlinedFunctions()
+ instance.singleInline()
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java b/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
index aa30615..8ffab3c 100644
--- a/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
@@ -3,11 +3,11 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8;
-import static com.android.tools.r8.TestCondition.compilers;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
import com.android.tools.r8.JctfTestSpecifications.Outcome;
+import com.android.tools.r8.TestCondition.RuntimeSet;
import com.android.tools.r8.ToolHelper.ArtCommandBuilder;
import com.android.tools.r8.ToolHelper.DexVm;
import com.android.tools.r8.ToolHelper.ProcessResult;
@@ -82,6 +82,12 @@
private static final String ART_TESTS_NATIVE_LIBRARY_DIR = "tests/2017-07-27/art/lib64";
private static final String ART_LEGACY_TESTS_NATIVE_LIBRARY_DIR = "tests/2016-12-19/art/lib64";
+ private static final RuntimeSet LEGACY_RUNTIME = TestCondition.runtimes(
+ DexVm.ART_4_4_4,
+ DexVm.ART_5_1_1,
+ DexVm.ART_6_0_1,
+ DexVm.ART_7_0_0);
+
// Input jar for jctf tests.
private static final String JCTF_COMMON_JAR = "build/libs/jctfCommon.jar";
@@ -527,8 +533,6 @@
// keeps the instances alive is dead and could be garbage collected. The compiler reuses
// the register for the list and therefore there are no live instances.
.put("099-vmdebug", TestCondition.any())
- // This test relies on output on stderr, which we currently do not collect.
- .put("143-string-value", TestCondition.any())
.put(
"800-smali",
TestCondition.match(
@@ -577,7 +581,7 @@
"370-dex-v37",
TestCondition.match(
TestCondition.tools(DexTool.JACK, DexTool.DX),
- compilers(CompilerUnderTest.R8, CompilerUnderTest.D8),
+ TestCondition.compilers(CompilerUnderTest.R8, CompilerUnderTest.D8),
TestCondition.runtimes(DexVm.ART_4_4_4, DexVm.ART_5_1_1, DexVm.ART_6_0_1)))
// Array index out of bounds exception.
.put("449-checker-bce", TestCondition.any())
@@ -681,21 +685,15 @@
// iput on a static field.
.put("600-verifier-fails", TestCondition.match(TestCondition.R8_COMPILER))
// Contains a method that falls off the end without a return.
- .put("606-erroneous-class", TestCondition
- .match(TestCondition.tools(DexTool.DX, DexTool.JACK),
- TestCondition.R8_NOT_AFTER_D8_COMPILER))
- .put("064-field-access", TestCondition
- .match(TestCondition.tools(DexTool.NONE), TestCondition.D8_COMPILER))
+ .put("606-erroneous-class", TestCondition.match(
+ TestCondition.tools(DexTool.DX, DexTool.JACK),
+ TestCondition.R8_NOT_AFTER_D8_COMPILER,
+ LEGACY_RUNTIME))
.build();
// Tests that are invalid dex files and on which R8/D8 fails and that is OK.
private static final Multimap<String, TestCondition> expectedToFailWithCompiler =
new ImmutableListMultimap.Builder<String, TestCondition>()
- // When starting from the Dex frontend we see two definitions of the same class coming
- // from two differents dex files.
- .put("064-field-access",
- TestCondition.match(TestCondition.tools(DexTool.DX, DexTool.JACK),
- TestCondition.runtimes(DexVm.ART_DEFAULT)))
// When starting from the Jar frontend we see the A$B class both from the Java source
// code and from the smali dex code. We reject that because there are then two definitions
// of the same class in the application. When running from the final dex files there is
@@ -703,18 +701,8 @@
.put("121-modifiers", TestCondition.match(TestCondition.tools(DexTool.NONE)))
// This test uses register r1 in method that is declared to only use 1 register (r0).
.put("142-classloader2", TestCondition.match(TestCondition.R8_COMPILER))
- // When starting from the Dex frontend we see two definitions of the same class coming
- // from two differents dex files.
- .put("162-method-resolution",
- TestCondition.match(TestCondition.tools(DexTool.DX, DexTool.JACK)))
// This test uses an uninitialized register.
.put("471-uninitialized-locals", TestCondition.match(TestCondition.R8_COMPILER))
- // When starting from the Dex frontend we see two definitions of the same class coming
- // from two differents dex files.
- .put("606-erroneous-class",
- TestCondition
- .match(TestCondition.tools(DexTool.DX, DexTool.JACK), TestCondition.D8_COMPILER,
- TestCondition.runtimes(DexVm.ART_DEFAULT)))
// This test is starting from invalid dex code. It splits up a double value and uses
// the first register of a double with the second register of another double.
.put("800-smali", TestCondition.match(TestCondition.R8_COMPILER))
@@ -730,9 +718,7 @@
// Tests that does not have valid input for us to be compatible with jack/dx running.
private static List<String> noInputJar = ImmutableList.of(
- "064-field-access", // Missing classes2 dir (has src2)
"097-duplicate-method", // No input class files.
- "162-method-resolution", // Based on multiple jasmin files
"630-safecast-array", // No input class files.
"801-VoidCheckCast", // No input class files.
"804-class-extends-itself", // No input class files.
@@ -768,12 +754,17 @@
// Tests to skip on some conditions
private static final Multimap<String, TestCondition> testToSkip =
new ImmutableListMultimap.Builder<String, TestCondition>()
+ // When running R8 on dex input (D8, DX or JACK) this test non-deterministically fails
+ // with a compiler exception, due to invoke-virtual on an interface, or it completes but
+ // the output when run on Art is not as expected. b/65233869
+ .put("162-method-resolution",
+ TestCondition.match(
+ TestCondition.tools(DexTool.DX, DexTool.JACK), TestCondition.R8_COMPILER))
// Old runtimes used the legacy test directory which does not contain input for tools
// NONE and DX.
.put("952-invoke-custom", TestCondition.match(
TestCondition.tools(DexTool.NONE, DexTool.DX),
- TestCondition.runtimes(
- DexVm.ART_4_4_4, DexVm.ART_5_1_1, DexVm.ART_6_0_1, DexVm.ART_7_0_0)))
+ LEGACY_RUNTIME))
// No input dex files for DX
.put("952-invoke-custom-kinds", TestCondition.match(TestCondition.tools(DexTool.DX)))
.build();
@@ -955,7 +946,7 @@
private static Map<SpecificationKey, TestSpecification> getTestsMap(
CompilerUnderTest compilerUnderTest, CompilationMode compilationMode, DexVm dexVm) {
File artTestDir = new File(ART_TESTS_DIR);
- if (dexVm != DexVm.ART_DEFAULT) {
+ if (LEGACY_RUNTIME.set.contains(dexVm)) {
artTestDir = new File(ART_LEGACY_TESTS_DIR);
}
if (!artTestDir.exists()) {
@@ -1446,25 +1437,20 @@
File[] inputFiles;
if (toolchain == DexTool.NONE) {
- File classes = new File(specification.directory, "classes");
- inputFiles =
- com.google.common.io.Files.fileTreeTraverser().breadthFirstTraversal(classes).filter(
- (File f) -> !f.isDirectory()).toArray(File.class);
+ inputFiles = addFileTree(new File[0], new File(specification.directory, "classes"));
+ inputFiles = addFileTree(inputFiles, new File(specification.directory, "jasmin_classes"));
File smali = new File(specification.directory, "smali");
if (smali.exists()) {
File smaliDex = new File(smali, "out.dex");
assert smaliDex.exists();
inputFiles = ObjectArrays.concat(inputFiles, smaliDex);
}
- File classes2 = new File(specification.directory, "classes2");
- if (classes2.exists()) {
- inputFiles = ObjectArrays.concat(inputFiles,
- com.google.common.io.Files.fileTreeTraverser().breadthFirstTraversal(classes2).filter(
- (File f) -> !f.isDirectory()).toArray(File.class), File.class);
- }
+ inputFiles = addFileTree(inputFiles, new File(specification.directory, "classes2"));
+ inputFiles = addFileTree(inputFiles, new File(specification.directory, "jasmin_classes2"));
} else {
inputFiles =
- specification.directory.listFiles((File file) -> file.getName().endsWith(".dex"));
+ specification.directory.listFiles((File file) ->
+ file.getName().endsWith(".dex") && !file.getName().startsWith("jasmin"));
}
List<String> fileNames = new ArrayList<>();
for (File file : inputFiles) {
@@ -1503,6 +1489,18 @@
}
}
+ private File[] addFileTree(File[] files, File directory) {
+ if (!directory.exists()) {
+ return files;
+ }
+ return ObjectArrays.concat(
+ files,
+ com.google.common.io.Files.fileTreeTraverser().breadthFirstTraversal(directory)
+ .filter(f -> !f.isDirectory())
+ .toArray(File.class),
+ File.class);
+ }
+
private void runArtTestDoRunOnArt(
DexVm version,
CompilerUnderTest compilerUnderTest,
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index cb8e307..4f84303 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -93,6 +93,10 @@
return compareTo(other) > 0;
}
+ public boolean isOlderThanOrEqual(DexVm other) {
+ return compareTo(other) <= 0;
+ }
+
private DexVm(String shortName) {
this.shortName = shortName;
}
@@ -448,7 +452,9 @@
return ProguardConfiguration.defaultConfiguration(factory);
}
ProguardConfigurationParser parser = new ProguardConfigurationParser(factory);
- parser.parse(configPaths);
+ for (Path configPath : configPaths) {
+ parser.parse(configPath);
+ }
return parser.getConfig();
}
@@ -597,13 +603,31 @@
.toArray(new String[0]));
}
+ public static ProcessResult forkGenerateMainDexList(Path dir, List<String> args1, String... args2)
+ throws IOException, InterruptedException {
+ List<String> args = new ArrayList<>();
+ args.addAll(args1);
+ args.addAll(Arrays.asList(args2));
+ return forkJava(dir, GenerateMainDexList.class, args);
+ }
+
+ public static ProcessResult forkGenerateMainDexList(Path dir, String... args)
+ throws IOException, InterruptedException {
+ return forkJava(dir, GenerateMainDexList.class, args);
+ }
+
private static ProcessResult forkJava(Path dir, Class clazz, String... args)
throws IOException, InterruptedException {
+ return forkJava(dir, clazz, Arrays.asList(args));
+ }
+
+ private static ProcessResult forkJava(Path dir, Class clazz, List<String> args)
+ throws IOException, InterruptedException {
List<String> command = new ImmutableList.Builder<String>()
.add(getJavaExecutable())
.add("-cp").add(System.getProperty("java.class.path"))
.add(clazz.getCanonicalName())
- .addAll(Arrays.asList(args))
+ .addAll(args)
.build();
return runProcess(new ProcessBuilder(command).directory(dir.toFile()));
}
diff --git a/src/test/java/com/android/tools/r8/debug/ClassInitializationTest.java b/src/test/java/com/android/tools/r8/debug/ClassInitializationTest.java
new file mode 100644
index 0000000..e90bca7
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/debug/ClassInitializationTest.java
@@ -0,0 +1,110 @@
+// 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.debug;
+
+import org.apache.harmony.jpda.tests.framework.jdwp.Value;
+import org.junit.Test;
+
+public class ClassInitializationTest extends DebugTestBase {
+
+ @Test
+ public void testStaticAssingmentInitialization() throws Throwable {
+ final String SOURCE_FILE = "ClassInitializerAssignmentInitialization.java";
+ final String CLASS = "ClassInitializerAssignmentInitialization";
+
+ runDebugTest(CLASS,
+ breakpoint(CLASS, "<clinit>"),
+ run(),
+ checkLine(SOURCE_FILE, 7),
+ checkStaticFieldClinitSafe(CLASS, "x", null, Value.createInt(0)),
+ checkStaticFieldClinitSafe(CLASS, "y", null, Value.createInt(0)),
+ checkStaticFieldClinitSafe(CLASS, "z", null, Value.createInt(0)),
+ stepOver(),
+ checkLine(SOURCE_FILE, 10),
+ checkStaticFieldClinitSafe(CLASS, "x", null, Value.createInt(1)),
+ checkStaticFieldClinitSafe(CLASS, "y", null, Value.createInt(0)),
+ checkStaticFieldClinitSafe(CLASS, "z", null, Value.createInt(0)),
+ breakpoint(CLASS, "main"),
+ run(),
+ checkStaticField(CLASS, "x", null, Value.createInt(1)),
+ checkStaticField(CLASS, "y", null, Value.createInt(0)),
+ checkStaticField(CLASS, "z", null, Value.createInt(2)),
+ run());
+ }
+
+ @Test
+ public void testBreakpointInEmptyClassInitializer() throws Throwable {
+ final String SOURCE_FILE = "ClassInitializerEmpty.java";
+ final String CLASS = "ClassInitializerEmpty";
+
+ runDebugTest(CLASS,
+ breakpoint(CLASS, "<clinit>"),
+ run(),
+ checkLine(SOURCE_FILE, 8),
+ run());
+ }
+
+ @Test
+ public void testStaticBlockInitialization() throws Throwable {
+ final String SOURCE_FILE = "ClassInitializerStaticBlockInitialization.java";
+ final String CLASS = "ClassInitializerStaticBlockInitialization";
+
+ runDebugTest(CLASS,
+ breakpoint(CLASS, "<clinit>"),
+ run(),
+ checkLine(SOURCE_FILE, 12),
+ checkStaticFieldClinitSafe(CLASS, "x", null, Value.createInt(0)),
+ stepOver(),
+ checkLine(SOURCE_FILE, 13),
+ checkStaticFieldClinitSafe(CLASS, "x", null, Value.createInt(1)),
+ stepOver(),
+ checkLine(SOURCE_FILE, 14),
+ checkStaticFieldClinitSafe(CLASS, "x", null, Value.createInt(2)),
+ stepOver(),
+ checkLine(SOURCE_FILE, 17),
+ stepOver(),
+ checkLine(SOURCE_FILE, 19),
+ breakpoint(CLASS, "main"),
+ run(),
+ checkLine(SOURCE_FILE, 23),
+ checkStaticField(CLASS, "x", null, Value.createInt(3)),
+ run());
+ }
+
+ @Test
+ public void testStaticMixedInitialization() throws Throwable {
+ final String SOURCE_FILE = "ClassInitializerMixedInitialization.java";
+ final String CLASS = "ClassInitializerMixedInitialization";
+
+ runDebugTest(CLASS,
+ breakpoint(CLASS, "<clinit>"),
+ run(),
+ checkLine(SOURCE_FILE, 8),
+ checkStaticFieldClinitSafe(CLASS, "x", null, Value.createInt(0)),
+ checkStaticFieldClinitSafe(CLASS, "y", null, Value.createInt(0)),
+ stepOver(),
+ checkLine(SOURCE_FILE, 12),
+ checkStaticFieldClinitSafe(CLASS, "x", null, Value.createInt(1)),
+ checkStaticFieldClinitSafe(CLASS, "y", null, Value.createInt(0)),
+ stepOver(),
+ checkLine(SOURCE_FILE, 13),
+ checkStaticFieldClinitSafe(CLASS, "x", null, Value.createInt(2)),
+ checkStaticFieldClinitSafe(CLASS, "y", null, Value.createInt(0)),
+ stepOver(),
+ checkLine(SOURCE_FILE, 16),
+ checkStaticFieldClinitSafe(CLASS, "x", null, Value.createInt(2)),
+ checkStaticFieldClinitSafe(CLASS, "y", null, Value.createInt(0)),
+ stepOver(),
+ checkLine(SOURCE_FILE, 18),
+ checkStaticFieldClinitSafe(CLASS, "x", null, Value.createInt(2)),
+ checkStaticFieldClinitSafe(CLASS, "y", null, Value.createInt(2)),
+ breakpoint(CLASS, "main"),
+ run(),
+ checkLine(SOURCE_FILE, 22),
+ checkStaticField(CLASS, "x", null, Value.createInt(3)),
+ checkStaticField(CLASS, "y", null, Value.createInt(2)),
+ run());
+ }
+}
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 6340728..fa78fd0 100644
--- a/src/test/java/com/android/tools/r8/debug/DebugTestBase.java
+++ b/src/test/java/com/android/tools/r8/debug/DebugTestBase.java
@@ -10,9 +10,12 @@
import com.android.tools.r8.ToolHelper.ArtCommandBuilder;
import com.android.tools.r8.ToolHelper.DexVm;
import com.android.tools.r8.dex.Constants;
+import com.android.tools.r8.utils.DescriptorUtils;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.OffOrAuto;
import com.google.common.collect.ImmutableList;
+import it.unimi.dsi.fastutil.longs.LongArrayList;
+import it.unimi.dsi.fastutil.longs.LongList;
import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
@@ -62,8 +65,10 @@
import org.junit.rules.TestName;
/**
+ * Base class for debugging tests.
*
- * Base class for debugging tests
+ * The protocol messages are described here:
+ * https://docs.oracle.com/javase/8/docs/platform/jpda/jdwp/jdwp-protocol.html
*/
public abstract class DebugTestBase {
@@ -347,6 +352,27 @@
});
}
+ protected final JUnit3Wrapper.Command checkStaticFieldClinitSafe(
+ String className, String fieldName, String fieldSignature, Value expectedValue) {
+ return inspect(t -> {
+ // TODO(65148874): The current Art from AOSP master hangs when requesting static fields
+ // when breaking in <clinit>. Last known good version is 7.0.0.
+ Assume.assumeTrue(
+ "Skipping test " + testName.getMethodName() + " because ART version is not supported",
+ isRunningJava() || ToolHelper.getDexVm().isOlderThanOrEqual(DexVm.ART_7_0_0));
+ checkStaticField(className, fieldName, fieldSignature, expectedValue);
+ });
+ }
+
+ protected final JUnit3Wrapper.Command checkStaticField(
+ String className, String fieldName, String fieldSignature, Value expectedValue) {
+ return inspect(t -> {
+ Value value = t.getStaticField(className, fieldName, fieldSignature);
+ Assert.assertEquals("Incorrect value for static '" + className + "." + fieldName + "'",
+ expectedValue, value);
+ });
+ }
+
protected final JUnit3Wrapper.Command inspect(Consumer<JUnit3Wrapper.DebuggeeState> inspector) {
return t -> inspector.accept(t.debuggeeState);
}
@@ -561,6 +587,7 @@
public static class DebuggeeState implements FrameInspector {
private class DebuggeeFrame implements FrameInspector {
+
private final long frameId;
private final Location location;
@@ -819,6 +846,77 @@
public String getMethodSignature() {
return getTopFrame().getMethodSignature();
}
+
+ public Value getStaticField(String className, String fieldName, String fieldSignature) {
+ String classSignature = DescriptorUtils.javaTypeToDescriptor(className);
+ byte typeTag = TypeTag.CLASS;
+ long classId = getMirror().getClassID(classSignature);
+ Assert.assertFalse("No class named " + className + " found", classId == -1);
+
+ // The class is available, lookup and read the field.
+ long fieldId = findField(getMirror(), classId, fieldName, fieldSignature);
+ return getField(getMirror(), classId, fieldId);
+ }
+
+ private long findField(VmMirror mirror, long classId, String fieldName,
+ String fieldSignature) {
+
+ boolean withGenericSignature = true;
+ CommandPacket commandPacket = new CommandPacket(ReferenceTypeCommandSet.CommandSetID,
+ ReferenceTypeCommandSet.FieldsWithGenericCommand);
+ commandPacket.setNextValueAsReferenceTypeID(classId);
+ ReplyPacket replyPacket = mirror.performCommand(commandPacket);
+ if (replyPacket.getErrorCode() != Error.NONE) {
+ // Retry with older command ReferenceType.Fields.
+ withGenericSignature = false;
+ commandPacket.setCommand(ReferenceTypeCommandSet.FieldsCommand);
+ replyPacket = mirror.performCommand(commandPacket);
+ assert replyPacket.getErrorCode() == Error.NONE;
+ }
+
+ int fieldsCount = replyPacket.getNextValueAsInt();
+ LongList matchingFieldIds = new LongArrayList();
+ for (int i = 0; i < fieldsCount; ++i) {
+ long currentFieldId = replyPacket.getNextValueAsFieldID();
+ String currentFieldName = replyPacket.getNextValueAsString();
+ String currentFieldSignature = replyPacket.getNextValueAsString();
+ if (withGenericSignature) {
+ replyPacket.getNextValueAsString(); // Skip generic signature.
+ }
+ replyPacket.getNextValueAsInt(); // Skip modifiers.
+
+ // Filter fields based on name (and signature if there is).
+ if (fieldName.equals(currentFieldName)) {
+ if (fieldSignature == null || fieldSignature.equals(currentFieldSignature)) {
+ matchingFieldIds.add(currentFieldId);
+ }
+ }
+ }
+ Assert.assertTrue(replyPacket.isAllDataRead());
+
+ Assert.assertFalse("No field named " + fieldName + " found", matchingFieldIds.isEmpty());
+ // There must be only one matching field.
+ Assert.assertEquals("More than 1 field found: please specify a signature", 1,
+ matchingFieldIds.size());
+ return matchingFieldIds.getLong(0);
+ }
+
+ private Value getField(VmMirror mirror, long classId, long fieldId) {
+
+ CommandPacket commandPacket = new CommandPacket(ReferenceTypeCommandSet.CommandSetID,
+ ReferenceTypeCommandSet.GetValuesCommand);
+ commandPacket.setNextValueAsReferenceTypeID(classId);
+ commandPacket.setNextValueAsInt(1);
+ commandPacket.setNextValueAsFieldID(fieldId);
+ ReplyPacket replyPacket = mirror.performCommand(commandPacket);
+ assert replyPacket.getErrorCode() == Error.NONE;
+
+ int fieldsCount = replyPacket.getNextValueAsInt();
+ assert fieldsCount == 1;
+ Value result = replyPacket.getNextValueAsValue();
+ Assert.assertTrue(replyPacket.isAllDataRead());
+ return result;
+ }
}
private static boolean inScope(long index, Variable var) {
@@ -886,100 +984,6 @@
setState(State.WaitForEvent);
}
- private boolean installBreakpoint(BreakpointInfo breakpointInfo) {
- String classSignature = getClassSignature(breakpointInfo.className);
- byte typeTag = TypeTag.CLASS;
- long classId = getMirror().getClassID(classSignature);
- if (classId == -1) {
- // Is it an interface ?
- classId = getMirror().getInterfaceID(classSignature);
- typeTag = TypeTag.INTERFACE;
- }
- if (classId == -1) {
- // The class is not ready yet. Request a CLASS_PREPARE to delay the installation of the
- // breakpoint.
- ReplyPacket replyPacket = getMirror().setClassPrepared(breakpointInfo.className);
- int classPrepareRequestId = replyPacket.getNextValueAsInt();
- assertAllDataRead(replyPacket);
- events.put(Integer.valueOf(classPrepareRequestId),
- new ClassPrepareHandler(breakpointInfo, classPrepareRequestId));
- return false;
- } else {
- // Find the method.
- long breakpointMethodId = findMethod(classId, breakpointInfo.methodName,
- breakpointInfo.methodSignature);
- long index = getMethodFirstCodeIndex(classId, breakpointMethodId);
- Assert.assertTrue("No code in method", index >= 0);
- // Install the breakpoint.
- ReplyPacket replyPacket = getMirror()
- .setBreakpoint(new Location(typeTag, classId, breakpointMethodId, index),
- SuspendPolicy.ALL);
- checkReplyPacket(replyPacket, "Breakpoint");
- int breakpointId = replyPacket.getNextValueAsInt();
- // Nothing to do on breakpoint
- events.put(Integer.valueOf(breakpointId), new DefaultEventHandler());
- return true;
- }
- }
-
- private long findMethod(long classId, String methodName, String methodSignature) {
- class MethodInfo {
-
- final long methodId;
- final String methodName;
- final String methodSignature;
-
- MethodInfo(long methodId, String methodName, String methodSignature) {
- this.methodId = methodId;
- this.methodName = methodName;
- this.methodSignature = methodSignature;
- }
- }
-
- boolean withGenericSignature = true;
- CommandPacket commandPacket = new CommandPacket(ReferenceTypeCommandSet.CommandSetID,
- ReferenceTypeCommandSet.MethodsWithGenericCommand);
- commandPacket.setNextValueAsReferenceTypeID(classId);
- ReplyPacket replyPacket = getMirror().performCommand(commandPacket);
- if (replyPacket.getErrorCode() != Error.NONE) {
- // Retry with older command ReferenceType.Methods
- withGenericSignature = false;
- commandPacket.setCommand(ReferenceTypeCommandSet.MethodsCommand);
- replyPacket = getMirror().performCommand(commandPacket);
- assert replyPacket.getErrorCode() == Error.NONE;
- }
-
- int methodsCount = replyPacket.getNextValueAsInt();
- List<MethodInfo> methodInfos = new ArrayList<>(methodsCount);
- for (int i = 0; i < methodsCount; ++i) {
- long currentMethodId = replyPacket.getNextValueAsMethodID();
- String currentMethodName = replyPacket.getNextValueAsString();
- String currentMethodSignature = replyPacket.getNextValueAsString();
- if (withGenericSignature) {
- replyPacket.getNextValueAsString(); // skip generic signature
- }
- replyPacket.getNextValueAsInt(); // skip modifiers
- methodInfos
- .add(new MethodInfo(currentMethodId, currentMethodName, currentMethodSignature));
- }
- Assert.assertTrue(replyPacket.isAllDataRead());
-
- // Only keep methods with the expected name.
- methodInfos = methodInfos.stream()
- .filter(m -> m.methodName.equals(methodName)).collect(
- Collectors.toList());
- if (methodSignature != null) {
- methodInfos = methodInfos.stream()
- .filter(m -> methodSignature.equals(m.methodSignature)).collect(
- Collectors.toList());
- }
- Assert.assertFalse("No method found", methodInfos.isEmpty());
- // There must be only one matching method
- Assert.assertEquals("More than 1 method found: please specify a signature", 1,
- methodInfos.size());
- return methodInfos.get(0).methodId;
- }
-
private long getMethodFirstCodeIndex(long classId, long breakpointMethodId) {
ReplyPacket replyPacket = getMirror().getLineTable(classId, breakpointMethodId);
checkReplyPacket(replyPacket, "Failed to get method line table");
@@ -1019,6 +1023,7 @@
private final String className;
private final String methodName;
private final String methodSignature;
+ private boolean requestedClassPrepare = false;
public BreakpointCommand(String className, String methodName,
String methodSignature) {
@@ -1031,7 +1036,91 @@
@Override
public void perform(JUnit3Wrapper testBase) {
- testBase.installBreakpoint(new BreakpointInfo(className, methodName, methodSignature));
+ VmMirror mirror = testBase.getMirror();
+ String classSignature = getClassSignature(className);
+ byte typeTag = TypeTag.CLASS;
+ long classId = mirror.getClassID(classSignature);
+ if (classId == -1) {
+ // Is it an interface ?
+ classId = mirror.getInterfaceID(classSignature);
+ typeTag = TypeTag.INTERFACE;
+ }
+ if (classId == -1) {
+ // The class is not ready yet. Request a CLASS_PREPARE to delay the installation of the
+ // breakpoint.
+ assert requestedClassPrepare == false : "Already requested class prepare";
+ requestedClassPrepare = true;
+ ReplyPacket replyPacket = mirror.setClassPrepared(className);
+ final int classPrepareRequestId = replyPacket.getNextValueAsInt();
+ testBase.events.put(Integer.valueOf(classPrepareRequestId), wrapper -> {
+ // Remove the CLASS_PREPARE
+ wrapper.events.remove(Integer.valueOf(classPrepareRequestId));
+ wrapper.getMirror().clearEvent(JDWPConstants.EventKind.CLASS_PREPARE,
+ classPrepareRequestId);
+
+ // Breakpoint then resume. Note: we add them at the beginning of the queue (to be the
+ // next commands to be processed), thus they need to be pushed in reverse order.
+ wrapper.commandsQueue.addFirst(new JUnit3Wrapper.Command.RunCommand());
+ wrapper.commandsQueue.addFirst(BreakpointCommand.this);
+
+ // Set wrapper ready to process next command.
+ wrapper.setState(State.ProcessCommand);
+ });
+ } else {
+ // The class is available: lookup the method then set the breakpoint.
+ long breakpointMethodId = findMethod(mirror, classId, methodName, methodSignature);
+ long index = testBase.getMethodFirstCodeIndex(classId, breakpointMethodId);
+ Assert.assertTrue("No code in method", index >= 0);
+ ReplyPacket replyPacket = testBase.getMirror().setBreakpoint(
+ new Location(typeTag, classId, breakpointMethodId, index), SuspendPolicy.ALL);
+ assert replyPacket.getErrorCode() == Error.NONE;
+ int breakpointId = replyPacket.getNextValueAsInt();
+ testBase.events.put(Integer.valueOf(breakpointId), new DefaultEventHandler());
+ }
+ }
+
+ private static long findMethod(VmMirror mirror, long classId, String methodName,
+ String methodSignature) {
+
+ boolean withGenericSignature = true;
+ CommandPacket commandPacket = new CommandPacket(ReferenceTypeCommandSet.CommandSetID,
+ ReferenceTypeCommandSet.MethodsWithGenericCommand);
+ commandPacket.setNextValueAsReferenceTypeID(classId);
+ ReplyPacket replyPacket = mirror.performCommand(commandPacket);
+ if (replyPacket.getErrorCode() != Error.NONE) {
+ // Retry with older command ReferenceType.Methods
+ withGenericSignature = false;
+ commandPacket.setCommand(ReferenceTypeCommandSet.MethodsCommand);
+ replyPacket = mirror.performCommand(commandPacket);
+ assert replyPacket.getErrorCode() == Error.NONE;
+ }
+
+ int methodsCount = replyPacket.getNextValueAsInt();
+ List<Long> matchingMethodIds = new ArrayList<>();
+ for (int i = 0; i < methodsCount; ++i) {
+ long currentMethodId = replyPacket.getNextValueAsMethodID();
+ String currentMethodName = replyPacket.getNextValueAsString();
+ String currentMethodSignature = replyPacket.getNextValueAsString();
+ if (withGenericSignature) {
+ replyPacket.getNextValueAsString(); // skip generic signature
+ }
+ replyPacket.getNextValueAsInt(); // skip modifiers
+
+ // Filter methods based on name (and signature if there is).
+ if (methodName.equals(currentMethodName)) {
+ if (methodSignature == null || methodSignature.equals(currentMethodSignature)) {
+ matchingMethodIds.add(Long.valueOf(currentMethodId));
+ }
+ }
+ }
+ Assert.assertTrue(replyPacket.isAllDataRead());
+
+ Assert
+ .assertFalse("No method named " + methodName + " found", matchingMethodIds.isEmpty());
+ // There must be only one matching method
+ Assert.assertEquals("More than 1 method found: please specify a signature", 1,
+ matchingMethodIds.size());
+ return matchingMethodIds.get(0);
}
@Override
@@ -1178,47 +1267,6 @@
}
}
- private static class BreakpointInfo {
-
- private final String className;
- private final String methodName;
- private final String methodSignature;
-
- private BreakpointInfo(String className, String methodName, String methodSignature) {
- this.className = className;
- this.methodName = methodName;
- this.methodSignature = methodSignature;
- }
- }
-
- /**
- * CLASS_PREPARE signals us that we can install a breakpoint
- */
- private static class ClassPrepareHandler implements EventHandler {
-
- private final BreakpointInfo breakpointInfo;
- private final int classPrepareRequestId;
-
- private ClassPrepareHandler(BreakpointInfo breakpointInfo, int classPrepareRequestId) {
- this.breakpointInfo = breakpointInfo;
- this.classPrepareRequestId = classPrepareRequestId;
- }
-
- @Override
- public void handle(JUnit3Wrapper testBase) {
- // Remove the CLASS_PREPARE
- testBase.events.remove(Integer.valueOf(classPrepareRequestId));
- testBase.getMirror().clearEvent(JDWPConstants.EventKind.CLASS_PREPARE,
- classPrepareRequestId);
-
- // Install breakpoint now.
- boolean success = testBase.installBreakpoint(breakpointInfo);
- Assert.assertTrue("Failed to insert breakpoint after class has been prepared", success);
-
- // Resume now
- testBase.resume();
- }
- }
}
//
diff --git a/src/test/java/com/android/tools/r8/debug/KotlinInlineTest.java b/src/test/java/com/android/tools/r8/debug/KotlinInlineTest.java
new file mode 100644
index 0000000..e62a17b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/debug/KotlinInlineTest.java
@@ -0,0 +1,172 @@
+// 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.debug;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import org.apache.harmony.jpda.tests.framework.jdwp.Value;
+import org.junit.Ignore;
+import org.junit.Test;
+
+public class KotlinInlineTest extends DebugTestBase {
+
+ @Ignore("Requires kotlin-specific stepping behavior")
+ @Test
+ public void testStepOverInline() throws Throwable {
+ String methodName = "singleInline";
+ runDebugTestKotlin("KotlinInline",
+ breakpoint("KotlinInline", methodName),
+ run(),
+ inspect(s -> {
+ assertEquals("KotlinInline", s.getClassName());
+ assertEquals(methodName, s.getMethodName());
+ assertEquals("KotlinInline.kt", s.getSourceFile());
+ assertEquals(41, s.getLineNumber());
+ s.checkLocal("this");
+ }),
+ // TODO(shertz) stepping over must take kotlin inline range into account.
+ stepOver(),
+ inspect(s -> {
+ assertEquals("KotlinInline", s.getClassName());
+ assertEquals(methodName, s.getMethodName());
+ assertEquals("KotlinInline.kt", s.getSourceFile());
+ assertEquals(42, s.getLineNumber());
+ s.checkLocal("this");
+ }),
+ stepOver(),
+ inspect(s -> {
+ assertEquals("KotlinInline", s.getClassName());
+ assertEquals(methodName, s.getMethodName());
+ assertEquals("KotlinInline.kt", s.getSourceFile());
+ assertEquals(43, s.getLineNumber());
+ s.checkLocal("this");
+ }),
+ run());
+ }
+
+ @Ignore("Requires kotlin-specific stepping behavior")
+ @Test
+ public void testStepIntoInline() throws Throwable {
+ String methodName = "singleInline";
+ runDebugTestKotlin("KotlinInline",
+ breakpoint("KotlinInline", methodName),
+ run(),
+ inspect(s -> {
+ assertEquals("KotlinInline", s.getClassName());
+ assertEquals(methodName, s.getMethodName());
+ assertEquals("KotlinInline.kt", s.getSourceFile());
+ assertEquals(41, s.getLineNumber());
+ s.checkLocal("this");
+ }),
+ // TODO(shertz) stepping over must take kotlin inline range into account.
+ stepInto(),
+ inspect(s -> {
+ assertEquals("KotlinInline", s.getClassName());
+ assertEquals(methodName, s.getMethodName());
+ assertEquals("KotlinInline.kt", s.getSourceFile());
+ // The actual line number (the one encoded in debug information) is different than the
+ // source file one.
+ // TODO(shertz) extract original line number from JSR-45's SMAP (only supported on
+ // Android O+).
+ assertTrue(42 != s.getLineNumber());
+ s.checkLocal("this");
+ }),
+ run());
+ }
+
+ @Ignore("Requires kotlin-specific stepping behavior")
+ @Test
+ public void testStepOutInline() throws Throwable {
+ String methodName = "singleInline";
+ runDebugTestKotlin("KotlinInline",
+ breakpoint("KotlinInline", methodName),
+ run(),
+ inspect(s -> {
+ assertEquals("KotlinInline", s.getClassName());
+ assertEquals(methodName, s.getMethodName());
+ assertEquals("KotlinInline.kt", s.getSourceFile());
+ assertEquals(41, s.getLineNumber());
+ s.checkLocal("this");
+ }),
+ // TODO(shertz) stepping out must take kotlin inline range into account.
+ stepInto(),
+ inspect(s -> {
+ assertEquals("KotlinInline", s.getClassName());
+ assertEquals(methodName, s.getMethodName());
+ }),
+ stepOut(),
+ inspect(s -> {
+ assertEquals("KotlinInline", s.getClassName());
+ assertEquals(methodName, s.getMethodName());
+ assertEquals("KotlinInline.kt", s.getSourceFile());
+ assertEquals(43, s.getLineNumber());
+ s.checkLocal("this");
+ }),
+ run());
+ }
+
+ @Test
+ public void testKotlinInline() throws Throwable {
+ final String inliningMethodName = "invokeInlinedFunctions";
+ runDebugTestKotlin("KotlinInline",
+ breakpoint("KotlinInline", inliningMethodName),
+ run(),
+ inspect(s -> {
+ assertEquals(inliningMethodName, s.getMethodName());
+ assertEquals(16, s.getLineNumber());
+ s.checkLocal("this");
+ }),
+ stepInto(),
+ inspect(s -> {
+ // We must have stepped into the code of the inlined method but the actual current method
+ // did not change.
+ assertEquals(inliningMethodName, s.getMethodName());
+ // TODO(shertz) get the original line if JSR45 is supported by the targeted ART runtime.
+ s.checkLocal("this");
+ }),
+ stepInto(),
+ inspect(s -> {
+ assertEquals(inliningMethodName, s.getMethodName());
+ assertEquals(17, s.getLineNumber());
+ s.checkLocal("this");
+ }),
+ stepInto(),
+ inspect(s -> {
+ assertEquals(inliningMethodName, s.getMethodName());
+ assertEquals(18, s.getLineNumber());
+ s.checkLocal("this");
+ s.checkLocal("inA", Value.createInt(1));
+ // This is a "hidden" lv added by Kotlin (which is neither initialized nor used).
+ s.checkLocal("$i$f$inlinedA");
+ s.checkLocal("$i$a$1$inlinedA");
+ }),
+ stepInto(),
+ inspect(s -> {
+ // We must have stepped into the code of the second inlined method but the actual current
+ // method did not change.
+ assertEquals(inliningMethodName, s.getMethodName());
+ // TODO(shertz) get the original line if JSR45 is supported by the targeted ART runtime.
+ s.checkLocal("this");
+ }),
+ stepInto(),
+ inspect(s -> {
+ assertEquals(inliningMethodName, s.getMethodName());
+ assertEquals(19, s.getLineNumber());
+ s.checkLocal("this");
+ }),
+ stepInto(),
+ inspect(s -> {
+ assertEquals(inliningMethodName, s.getMethodName());
+ assertEquals(20, s.getLineNumber());
+ s.checkLocal("this");
+ s.checkLocal("inB", Value.createInt(2));
+ // This is a "hidden" lv added by Kotlin (which is neither initialized nor used).
+ s.checkLocal("$i$f$inlinedB");
+ s.checkLocal("$i$a$1$inlinedB");
+ }),
+ run());
+ }
+
+}
diff --git a/src/test/java/com/android/tools/r8/debug/KotlinTest.java b/src/test/java/com/android/tools/r8/debug/KotlinTest.java
index 8caa3c1..a6b57b8 100644
--- a/src/test/java/com/android/tools/r8/debug/KotlinTest.java
+++ b/src/test/java/com/android/tools/r8/debug/KotlinTest.java
@@ -3,97 +3,160 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.debug;
+import static org.junit.Assert.assertEquals;
+
import org.apache.harmony.jpda.tests.framework.jdwp.Value;
-import org.junit.Assert;
import org.junit.Test;
public class KotlinTest extends DebugTestBase {
+ // TODO(shertz) simplify test
+ // TODO(shertz) add more variables ?
@Test
- public void testKotlinApp() throws Throwable {
- final String inliningMethodName = "invokeInlinedFunctions";
+ public void testStepOver() throws Throwable {
runDebugTestKotlin("KotlinApp",
breakpoint("KotlinApp$Companion", "main"),
run(),
inspect(s -> {
- Assert.assertEquals("KotlinApp.kt", s.getSourceFile());
- Assert.assertEquals(8, s.getLineNumber());
+ assertEquals("KotlinApp$Companion", s.getClassName());
+ assertEquals("KotlinApp.kt", s.getSourceFile());
+ assertEquals(24, s.getLineNumber());
s.checkLocal("this");
s.checkLocal("args");
+ checkNoLocal("instance");
}),
stepOver(),
inspect(s -> {
- Assert.assertEquals(9, s.getLineNumber());
- s.checkLocal("this");
- s.checkLocal("args");
- }),
- stepOver(),
- inspect(s -> {
- Assert.assertEquals(10, s.getLineNumber());
+ assertEquals(25, s.getLineNumber());
s.checkLocal("this");
s.checkLocal("args");
s.checkLocal("instance");
}),
stepOver(),
inspect(s -> {
- Assert.assertEquals(11, s.getLineNumber());
+ assertEquals(26, s.getLineNumber());
s.checkLocal("this");
s.checkLocal("args");
s.checkLocal("instance");
}),
+ run());
+ }
+
+ @Test
+ public void testStepIntoAndOut() throws Throwable {
+ runDebugTestKotlin("KotlinApp",
+ breakpoint("KotlinApp$Companion", "main"),
+ run(),
+ inspect(s -> {
+ assertEquals("KotlinApp$Companion", s.getClassName());
+ assertEquals("KotlinApp.kt", s.getSourceFile());
+ assertEquals(24, s.getLineNumber());
+ s.checkLocal("this");
+ s.checkLocal("args");
+ checkNoLocal("instance");
+ }),
+ stepOver(),
+ inspect(s -> {
+ assertEquals(25, s.getLineNumber());
+ s.checkLocal("this");
+ s.checkLocal("args");
+ s.checkLocal("instance");
+ }),
+ // Step into 1st invoke of ifElse
stepInto(),
inspect(s -> {
- Assert.assertEquals(inliningMethodName, s.getMethodName());
- Assert.assertEquals(24, s.getLineNumber());
+ assertEquals("KotlinApp", s.getClassName());
+ assertEquals("KotlinApp.kt", s.getSourceFile());
+ assertEquals(8, s.getLineNumber());
s.checkLocal("this");
+ s.checkLocal("cond", Value.createBoolean(true));
+ checkNoLocal("a");
+ checkNoLocal("b");
+ checkNoLocal("c");
}),
stepInto(),
inspect(s -> {
- // We must have stepped into the code of the inlined method but the actual current method
- // did not change.
- Assert.assertEquals(inliningMethodName, s.getMethodName());
- // TODO(shertz) get the original line if JSR45 is supported by the targeted ART runtime.
+ assertEquals("KotlinApp", s.getClassName());
+ assertEquals(9, s.getLineNumber());
s.checkLocal("this");
+ s.checkLocal("cond", Value.createBoolean(true));
+ s.checkLocal("a", Value.createInt(10));
+ checkNoLocal("b");
+ checkNoLocal("c");
}),
stepInto(),
inspect(s -> {
- Assert.assertEquals(inliningMethodName, s.getMethodName());
- Assert.assertEquals(25, s.getLineNumber());
+ // We should be into the 'then' statement.
+ assertEquals("KotlinApp", s.getClassName());
+ assertEquals(10, s.getLineNumber());
s.checkLocal("this");
+ s.checkLocal("cond", Value.createBoolean(true));
+ s.checkLocal("a", Value.createInt(10));
+ checkNoLocal("b");
+ checkNoLocal("c");
}),
stepInto(),
inspect(s -> {
- Assert.assertEquals(inliningMethodName, s.getMethodName());
- Assert.assertEquals(26, s.getLineNumber());
+ assertEquals("KotlinApp", s.getClassName());
+ assertEquals(11, s.getLineNumber());
s.checkLocal("this");
- s.checkLocal("inA", Value.createInt(1));
- // This is a "hidden" lv added by Kotlin (which is neither initialized nor used).
- s.checkLocal("$i$f$inlinedA");
- s.checkLocal("$i$a$1$inlinedA");
+ s.checkLocal("cond", Value.createBoolean(true));
+ s.checkLocal("a", Value.createInt(10));
+ s.checkLocal("b", Value.createInt(20));
+ checkNoLocal("c");
+ }),
+ // Go back to the main method
+ stepOut(),
+ inspect(s -> {
+ assertEquals("KotlinApp$Companion", s.getClassName());
+ assertEquals("KotlinApp.kt", s.getSourceFile());
+ assertEquals(26, s.getLineNumber());
+ s.checkLocal("this");
+ s.checkLocal("args");
+ checkNoLocal("instance");
+ }),
+ // Step into 2nd invoke of ifElse
+ stepInto(),
+ inspect(s -> {
+ assertEquals("KotlinApp", s.getClassName());
+ assertEquals("KotlinApp.kt", s.getSourceFile());
+ assertEquals(8, s.getLineNumber());
+ s.checkLocal("this");
+ s.checkLocal("cond", Value.createBoolean(false));
+ checkNoLocal("a");
+ checkNoLocal("b");
+ checkNoLocal("c");
}),
stepInto(),
inspect(s -> {
- // We must have stepped into the code of the second inlined method but the actual current
- // method did not change.
- Assert.assertEquals(inliningMethodName, s.getMethodName());
- // TODO(shertz) get the original line if JSR45 is supported by the targeted ART runtime.
+ assertEquals("KotlinApp", s.getClassName());
+ assertEquals(9, s.getLineNumber());
s.checkLocal("this");
+ s.checkLocal("cond", Value.createBoolean(false));
+ s.checkLocal("a", Value.createInt(10));
+ checkNoLocal("b");
+ checkNoLocal("c");
}),
stepInto(),
inspect(s -> {
- Assert.assertEquals(inliningMethodName, s.getMethodName());
- Assert.assertEquals(27, s.getLineNumber());
+ // We should be into the 'else' statement this time.
+ assertEquals("KotlinApp", s.getClassName());
+ assertEquals(13, s.getLineNumber());
s.checkLocal("this");
+ s.checkLocal("cond", Value.createBoolean(false));
+ s.checkLocal("a", Value.createInt(10));
+ checkNoLocal("b");
+ checkNoLocal("c");
}),
stepInto(),
inspect(s -> {
- Assert.assertEquals(inliningMethodName, s.getMethodName());
- Assert.assertEquals(28, s.getLineNumber());
+ assertEquals("KotlinApp", s.getClassName());
+ assertEquals(14, s.getLineNumber());
s.checkLocal("this");
- s.checkLocal("inB", Value.createInt(2));
- // This is a "hidden" lv added by Kotlin (which is neither initialized nor used).
- s.checkLocal("$i$f$inlinedB");
- s.checkLocal("$i$a$1$inlinedB");
+ s.checkLocal("cond", Value.createBoolean(false));
+ s.checkLocal("a", Value.createInt(10));
+ checkNoLocal("b");
+ s.checkLocal("c", Value.createInt(5));
}),
run());
}
diff --git a/src/test/java/com/android/tools/r8/debug/LocalsTest.java b/src/test/java/com/android/tools/r8/debug/LocalsTest.java
index 02fb694..12fd23d 100644
--- a/src/test/java/com/android/tools/r8/debug/LocalsTest.java
+++ b/src/test/java/com/android/tools/r8/debug/LocalsTest.java
@@ -470,4 +470,99 @@
checkNoLocal("t"),
run());
}
+
+ @Test
+ public void argumentLiveAtReturn() throws Throwable {
+ runDebugTest(
+ "Locals",
+ breakpoint("Locals", "argumentLiveAtReturn"),
+ run(),
+ checkLine(SOURCE_FILE, 248),
+ stepOver(),
+ checkLine(SOURCE_FILE, 262),
+ checkLocal("x", Value.createInt(-1)),
+ checkNoLocal("t"),
+ run());
+ }
+
+ @Test
+ public void switchRewriteToIfs() throws Throwable {
+ runDebugTest(
+ "Locals",
+ breakpoint("Locals", "switchRewriteToIfs"),
+ run(),
+ checkLine(SOURCE_FILE, 267),
+ stepOver(),
+ checkLine(SOURCE_FILE, 268),
+ checkLocal("x", Value.createInt(1)),
+ checkLocal("t", Value.createInt(2)),
+ stepOver(),
+ checkLine(SOURCE_FILE, 269),
+ checkLocal("x", Value.createInt(2)),
+ checkLocal("t", Value.createInt(2)),
+ stepOver(),
+ checkLine(SOURCE_FILE, 271),
+ checkLocal("x", Value.createInt(4)),
+ checkNoLocal("t"),
+ run());
+ }
+
+ @Test
+ public void switchRewriteToSwitches() throws Throwable {
+ runDebugTest(
+ "Locals",
+ breakpoint("Locals", "switchRewriteToSwitches"),
+ run(),
+ checkLine(SOURCE_FILE, 282),
+ stepOver(),
+ checkLine(SOURCE_FILE, 283),
+ checkLocal("x", Value.createInt(1)),
+ checkLocal("t", Value.createInt(2)),
+ stepOver(),
+ checkLine(SOURCE_FILE, 284),
+ checkLocal("x", Value.createInt(2)),
+ checkLocal("t", Value.createInt(2)),
+ stepOver(),
+ checkLine(SOURCE_FILE, 286),
+ checkLocal("x", Value.createInt(4)),
+ checkNoLocal("t"),
+ run());
+ }
+
+ @Test
+ public void regression65039701() throws Throwable {
+ runDebugTest(
+ "Locals",
+ breakpoint("Locals", "regression65039701"),
+ run(),
+ checkLine(SOURCE_FILE, 304),
+ checkLocal("createIntNotLong", Value.createBoolean(true)),
+ stepOver(),
+ checkLine(SOURCE_FILE, 305),
+ checkLocal("a"),
+ stepOver(),
+ checkLine(SOURCE_FILE, 306),
+ stepOver(),
+ checkLine(SOURCE_FILE, 308),
+ run());
+ }
+
+ @Test
+ public void regression65066975() throws Throwable {
+ runDebugTest(
+ "Locals",
+ breakpoint("Locals", "regression65066975"),
+ run(),
+ checkLine(SOURCE_FILE, 312),
+ checkLocal("bit", Value.createBoolean(false)),
+ stepOver(),
+ checkLine(SOURCE_FILE, 313),
+ stepOver(),
+ checkLine(SOURCE_FILE, 316),
+ stepOver(),
+ checkLine(SOURCE_FILE, 318),
+ stepOver(),
+ checkLine(SOURCE_FILE, 319),
+ run());
+ }
}
diff --git a/src/test/java/com/android/tools/r8/debuginfo/DebugInfoInspector.java b/src/test/java/com/android/tools/r8/debuginfo/DebugInfoInspector.java
index ad6f925..4eae243 100644
--- a/src/test/java/com/android/tools/r8/debuginfo/DebugInfoInspector.java
+++ b/src/test/java/com/android/tools/r8/debuginfo/DebugInfoInspector.java
@@ -11,14 +11,18 @@
import com.android.tools.r8.graph.DebugLocalInfo;
import com.android.tools.r8.graph.DexDebugEntry;
import com.android.tools.r8.graph.DexDebugEntryBuilder;
+import com.android.tools.r8.graph.DexDebugEvent;
import com.android.tools.r8.graph.DexDebugInfo;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.naming.MemberNaming.MethodSignature;
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.DescriptorUtils;
import com.android.tools.r8.utils.DexInspector;
+import com.android.tools.r8.utils.StringUtils;
import java.io.IOException;
+import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@@ -37,8 +41,12 @@
public DebugInfoInspector(DexEncodedMethod method, DexItemFactory factory) {
this.method = method;
info = method.getCode().asDexCode().getDebugInfo();
- entries = new DexDebugEntryBuilder(method, factory).build();
- checkConsistentEntries();
+ if (info != null) {
+ entries = new DexDebugEntryBuilder(method, factory).build();
+ checkConsistentEntries();
+ } else {
+ entries = Collections.emptyList();
+ }
}
public DebugInfoInspector(DexInspector inspector, String clazz, MethodSignature method) {
@@ -50,6 +58,24 @@
this(new DexInspector(app), clazz, method);
}
+ public boolean hasLocalsInfo() {
+ DexDebugInfo dexInfo = method.getCode().asDexCode().getDebugInfo();
+ if (info == null || dexInfo == null) {
+ return false;
+ }
+ for (DexString parameter : dexInfo.parameters) {
+ if (parameter != null) {
+ return true;
+ }
+ }
+ for (DexDebugEvent event : dexInfo.events) {
+ if (event instanceof DexDebugEvent.StartLocal) {
+ return true;
+ }
+ }
+ return false;
+ }
+
public void checkStartLine(int i) {
assertEquals(i, info.startLine);
}
@@ -102,7 +128,8 @@
DexDebugEntry previousEntry = null;
for (DexDebugEntry entry : entries) {
if (previousEntry != null) {
- assertTrue("More than one entry defined for PC " + entry.address,
+ assertTrue(
+ "More than one entry defined for PC " + StringUtils.hexString(entry.address, 2),
entry.address > previousEntry.address);
}
previousEntry = entry;
diff --git a/src/test/java/com/android/tools/r8/debuginfo/SynchronizedMethodTest.java b/src/test/java/com/android/tools/r8/debuginfo/SynchronizedMethodTest.java
index be0a122..74992e9 100644
--- a/src/test/java/com/android/tools/r8/debuginfo/SynchronizedMethodTest.java
+++ b/src/test/java/com/android/tools/r8/debuginfo/SynchronizedMethodTest.java
@@ -19,8 +19,43 @@
return -Math.abs(x);
}
+ private static synchronized int throwing(int cond) {
+ int x = 42;
+ if (cond < 0) {
+ throw new IllegalStateException();
+ }
+ return 2;
+ }
+
+ private static synchronized int monitorExitRegression(int cond) {
+ int x = 42;
+ switch (cond) {
+ case 1:
+ return 1;
+ case 2:
+ throw new IllegalStateException();
+ case 3:
+ throw new RuntimeException();
+ case 4:
+ return 2;
+ case 5:
+ x = 7;
+ case 6:
+ return 3;
+ default:
+ }
+ if (cond > 0) {
+ x = cond + cond;
+ } else {
+ throw new ArithmeticException();
+ }
+ return 2;
+ }
+
public static void main(String[] args) {
System.out.println(syncStatic(1234));
System.out.println(new SynchronizedMethodTest().syncInstance(1234));
+ System.out.println(throwing(1234));
+ System.out.println(monitorExitRegression(1234));
}
}
diff --git a/src/test/java/com/android/tools/r8/debuginfo/SynchronizedMethodTestRunner.java b/src/test/java/com/android/tools/r8/debuginfo/SynchronizedMethodTestRunner.java
index 7cee4a3..ba0849b 100644
--- a/src/test/java/com/android/tools/r8/debuginfo/SynchronizedMethodTestRunner.java
+++ b/src/test/java/com/android/tools/r8/debuginfo/SynchronizedMethodTestRunner.java
@@ -5,8 +5,9 @@
import static org.junit.Assert.assertEquals;
-import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.StringUtils;
+import java.util.Arrays;
import org.junit.Test;
public class SynchronizedMethodTestRunner extends DebugInfoTestBase {
@@ -18,7 +19,7 @@
AndroidApp d8App = compileWithD8(clazz);
AndroidApp dxApp = getDxCompiledSources();
- String expected = "42" + ToolHelper.LINE_SEPARATOR + "42" + ToolHelper.LINE_SEPARATOR;
+ String expected = StringUtils.lines("42", "42", "2", "2");
assertEquals(expected, runOnJava(clazz));
assertEquals(expected, runOnArt(d8App, clazz.getCanonicalName()));
assertEquals(expected, runOnArt(dxApp, clazz.getCanonicalName()));
@@ -28,6 +29,14 @@
checkSyncInstance(inspectMethod(d8App, clazz, "int", "syncInstance", "int"));
checkSyncInstance(inspectMethod(dxApp, clazz, "int", "syncInstance", "int"));
+
+ checkThrowing(inspectMethod(d8App, clazz, "int", "throwing", "int"), false);
+ checkThrowing(inspectMethod(dxApp, clazz, "int", "throwing", "int"), true);
+
+ checkMonitorExitRegression(
+ inspectMethod(d8App, clazz, "int", "monitorExitRegression", "int"), false);
+ checkMonitorExitRegression(
+ inspectMethod(dxApp, clazz, "int", "monitorExitRegression", "int"), true);
}
private void checkSyncStatic(DebugInfoInspector info) {
@@ -48,4 +57,25 @@
info.checkLineHasExactLocals(19, locals);
info.checkNoLine(20);
}
+
+ private void checkThrowing(DebugInfoInspector info, boolean dx) {
+ info.checkStartLine(23);
+ if (!dx) {
+ info.checkLineHasExactLocals(23, "cond", "int");
+ }
+ info.checkLineHasExactLocals(24, "cond", "int", "x", "int");
+ info.checkLineHasExactLocals(25, "cond", "int", "x", "int");
+ info.checkNoLine(26);
+ info.checkLineHasExactLocals(27, "cond", "int", "x", "int");
+ }
+
+ private void checkMonitorExitRegression(DebugInfoInspector info, boolean dx) {
+ info.checkStartLine(31);
+ for (int line : Arrays.asList(32, 34, 36, 38, 40, 42, 44, 48, 50, 52)) {
+ if (dx && line == 40) {
+ continue;
+ }
+ info.checkLineHasExactLocals(line, "cond", "int", "x", "int");
+ }
+ }
}
diff --git a/src/test/java/com/android/tools/r8/graph/TargetLookupTest.java b/src/test/java/com/android/tools/r8/graph/TargetLookupTest.java
index f731e39..e7718e1 100644
--- a/src/test/java/com/android/tools/r8/graph/TargetLookupTest.java
+++ b/src/test/java/com/android/tools/r8/graph/TargetLookupTest.java
@@ -16,7 +16,7 @@
public class TargetLookupTest extends SmaliTestBase {
@Test
- public void lookupDirect() {
+ public void lookupDirect() throws Exception {
SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
builder.addDefaultConstructor();
@@ -71,7 +71,7 @@
}
@Test
- public void lookupDirectSuper() {
+ public void lookupDirectSuper() throws Exception {
SmaliBuilder builder = new SmaliBuilder("TestSuper");
builder.addDefaultConstructor();
diff --git a/src/test/java/com/android/tools/r8/internal/D8FrameworkDeterministicTest.java b/src/test/java/com/android/tools/r8/internal/D8FrameworkDeterministicTest.java
new file mode 100644
index 0000000..2cbd60a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/internal/D8FrameworkDeterministicTest.java
@@ -0,0 +1,50 @@
+// 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.internal;
+
+import com.android.tools.r8.CompilationException;
+import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.D8Command;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.shaking.ProguardRuleParserException;
+import com.android.tools.r8.utils.AndroidApp;
+import java.io.IOException;
+import java.nio.file.Paths;
+import java.util.concurrent.ExecutionException;
+import org.junit.Test;
+
+public class D8FrameworkDeterministicTest extends CompilationTestBase {
+ private static final int MIN_SDK = 24;
+ private static final String JAR = "third_party/framework/framework_160115954.jar";
+
+ private AndroidApp doRun(D8Command command) throws IOException, CompilationException {
+ return ToolHelper.runD8(command);
+ }
+
+ @Test
+ public void verifyDebugBuild()
+ throws ExecutionException, IOException, ProguardRuleParserException, CompilationException {
+ D8Command command = D8Command.builder()
+ .addProgramFiles(Paths.get(JAR))
+ .setMode(CompilationMode.DEBUG)
+ .setMinApiLevel(MIN_SDK)
+ .build();
+ AndroidApp app1 = doRun(command);
+ AndroidApp app2 = doRun(command);
+ assertIdenticalApplications(app1, app2);
+ }
+
+ @Test
+ public void verifyReleaseBuild()
+ throws ExecutionException, IOException, ProguardRuleParserException, CompilationException {
+ D8Command command = D8Command.builder()
+ .addProgramFiles(Paths.get(JAR))
+ .setMode(CompilationMode.RELEASE)
+ .setMinApiLevel(MIN_SDK)
+ .build();
+ AndroidApp app1 = doRun(command);
+ AndroidApp app2 = doRun(command);
+ assertIdenticalApplications(app1, app2);
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/internal/R8GMSCoreDeterministicTest.java b/src/test/java/com/android/tools/r8/internal/R8GMSCoreDeterministicTest.java
index bc3d758..75a0275 100644
--- a/src/test/java/com/android/tools/r8/internal/R8GMSCoreDeterministicTest.java
+++ b/src/test/java/com/android/tools/r8/internal/R8GMSCoreDeterministicTest.java
@@ -11,19 +11,23 @@
import com.android.tools.r8.shaking.ProguardRuleParserException;
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.OutputMode;
+import com.beust.jcommander.internal.Lists;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Collections;
+import java.util.LinkedHashSet;
import java.util.List;
+import java.util.Set;
import java.util.concurrent.ExecutionException;
import org.junit.Test;
public class R8GMSCoreDeterministicTest extends GMSCoreCompilationTestBase {
- public List<DexEncodedMethod> shuffle(List<DexEncodedMethod> methods) {
- Collections.shuffle(methods);
- return methods;
+ public Set<DexEncodedMethod> shuffle(Set<DexEncodedMethod> methods) {
+ List<DexEncodedMethod> toShuffle = Lists.newArrayList(methods);
+ Collections.shuffle(toShuffle);
+ return new LinkedHashSet<>(toShuffle);
}
private AndroidApp doRun()
diff --git a/src/test/java/com/android/tools/r8/jasmin/DebugLocalTests.java b/src/test/java/com/android/tools/r8/jasmin/DebugLocalTests.java
index 2782c91..531831b 100644
--- a/src/test/java/com/android/tools/r8/jasmin/DebugLocalTests.java
+++ b/src/test/java/com/android/tools/r8/jasmin/DebugLocalTests.java
@@ -336,4 +336,329 @@
info.checkLineHasExactLocals(8, "param", "int", "x", "int");
info.checkLineHasExactLocals(9, "param", "int");
}
+
+ @Test
+ public void argumentLiveAtReturn() throws Exception {
+ JasminBuilder builder = new JasminBuilder();
+ JasminBuilder.ClassBuilder clazz = builder.addClass("Test");
+
+ /*
+ This is the original Java source code.
+
+ public static int argumentLiveAtReturn(int x) { // Line 1
+ switch (x) {
+ case 0:
+ return 0;
+ case 1:
+ return 0;
+ case 2:
+ return 0;
+ case 100:
+ return 1;
+ case 101:
+ return 1;
+ case 102:
+ return 1;
+ }
+ return -1;
+ }
+ */
+ MethodSignature foo = clazz.addStaticMethod("argumentLiveAtReturn", ImmutableList.of("I"), "I",
+ ".limit stack 2",
+ ".limit locals 1",
+ ".var 0 is x I from L0 to L8",
+ "L0:",
+ ".line 2",
+ " iload 0",
+ "lookupswitch",
+ " 0: L1",
+ " 1: L2",
+ " 2: L3",
+ " 100: L4",
+ " 101: L5",
+ " 102: L6",
+ " default: L7",
+ "L1:",
+ ".line 4",
+ " iconst_0",
+ " ireturn",
+ "L2:",
+ ".line 6",
+ " iconst_0",
+ " ireturn",
+ "L3:",
+ ".line 8",
+ " iconst_0",
+ " ireturn",
+ "L4:",
+ ".line 10",
+ " iconst_1",
+ " ireturn",
+ "L5:",
+ ".line 12",
+ " iconst_1",
+ " ireturn",
+ "L6:",
+ ".line 14",
+ " iconst_1",
+ " ireturn",
+ "L7:",
+ ".line 16",
+ " iconst_m1",
+ " ireturn",
+ "L8:"
+ );
+
+ clazz.addMainMethod(
+ ".limit stack 2",
+ ".limit locals 1",
+ " getstatic java/lang/System/out Ljava/io/PrintStream;",
+ " ldc -1",
+ " invokestatic Test/argumentLiveAtReturn(I)I",
+ " invokevirtual java/io/PrintStream/print(I)V",
+ " return");
+
+ String expected = "-1";
+ String javaResult = runOnJava(builder, clazz.name);
+ assertEquals(expected, javaResult);
+
+ AndroidApp jasminApp = builder.build();
+ AndroidApp d8App = ToolHelper.runD8(jasminApp);
+ String artResult = runOnArt(d8App, clazz.name);
+ assertEquals(expected, artResult);
+ DebugInfoInspector info = new DebugInfoInspector(d8App, clazz.name, foo);
+ info.checkStartLine(2);
+ info.checkLineHasExactLocals(2, "x", "int");
+ info.checkLineHasExactLocals(4, "x", "int");
+ info.checkLineHasExactLocals(6, "x", "int");
+ info.checkLineHasExactLocals(8, "x", "int");
+ info.checkLineHasExactLocals(10, "x", "int");
+ info.checkLineHasExactLocals(12, "x", "int");
+ info.checkLineHasExactLocals(14, "x", "int");
+ info.checkLineHasExactLocals(16, "x", "int");
+ }
+
+ @Test
+ public void testLocalSwitchRewriteToIfs() throws Exception {
+ JasminBuilder builder = new JasminBuilder();
+ JasminBuilder.ClassBuilder clazz = builder.addClass("Test");
+
+ /*
+ This is the original Java source code. The code generated by javac have been
+ slightly modified below to end local t on the lookupswitch instruction.
+
+ public static int switchRewrite(int x) { // Line 1
+ {
+ int t = x + 1;
+ x = t;
+ x = x + x;
+ }
+ switch (x) {
+ case 1:
+ return 7;
+ case 100:
+ return 8;
+ }
+ return -1;
+ }
+ */
+ MethodSignature foo = clazz.addStaticMethod("switchRewrite", ImmutableList.of("I"), "I",
+ ".limit stack 2",
+ ".limit locals 2",
+ ".var 0 is x I from L0 to L7",
+ ".var 1 is t I from L1 to L3",
+
+ "L0:",
+ ".line 3",
+ " iload 0",
+ " iconst_1",
+ " iadd",
+ " istore 1",
+ "L1:",
+ ".line 4",
+ " iload 1",
+ " istore 0",
+ "L2:",
+ ".line 5",
+ " iload 0",
+ " iload 0",
+ " iadd",
+ " istore 0",
+ "L3_ORIGINAL:", // This is where javac normally ends t.
+ ".line 7",
+ " iload 0",
+ "L3:", // Moved L3 here to end t on the switch instruction.
+ "lookupswitch",
+ " 0: L4",
+ " 100: L5",
+ " default: L6",
+ "L4:",
+ ".line 9",
+ " iconst_0",
+ " ireturn",
+ "L5:",
+ ".line 11",
+ " iconst_1",
+ " ireturn",
+ "L6:",
+ ".line 13",
+ " iconst_m1",
+ " ireturn",
+ "L7:"
+ );
+
+ clazz.addMainMethod(
+ ".limit stack 2",
+ ".limit locals 1",
+ " getstatic java/lang/System/out Ljava/io/PrintStream;",
+ " ldc 1",
+ " invokestatic Test/switchRewrite(I)I",
+ " invokevirtual java/io/PrintStream/print(I)V",
+ " return");
+
+ String expected = "-1";
+ String javaResult = runOnJava(builder, clazz.name);
+ assertEquals(expected, javaResult);
+
+ AndroidApp jasminApp = builder.build();
+ AndroidApp d8App = ToolHelper.runD8(jasminApp);
+ String artResult = runOnArt(d8App, clazz.name);
+ assertEquals(expected, artResult);
+
+ DebugInfoInspector info = new DebugInfoInspector(d8App, clazz.name, foo);
+ info.checkStartLine(3);
+ info.checkLineHasExactLocals(3, "x", "int");
+ info.checkLineHasExactLocals(4, "x", "int", "t", "int");
+ info.checkLineHasExactLocals(5, "x", "int", "t", "int");
+ info.checkLineHasExactLocals(7, "x", "int", "t", "int");
+ info.checkLineHasExactLocals(9, "x", "int");
+ info.checkLineHasExactLocals(11, "x", "int");
+ info.checkLineHasExactLocals(13, "x", "int");
+ }
+
+ @Test
+ public void testLocalSwitchRewriteToSwitches() throws Exception {
+ JasminBuilder builder = new JasminBuilder();
+ JasminBuilder.ClassBuilder clazz = builder.addClass("Test");
+
+ /*
+ This is the original Java source code. The code generated by javac have been
+ slightly modified below to end local t on the lookupswitch instruction.
+
+ public static int switchRewrite(int x) { // Line 1
+ {
+ int t = x + 1;
+ x = t;
+ x = x + x;
+ }
+ switch (x) {
+ case 0:
+ return 0;
+ case 1:
+ return 0;
+ case 2:
+ return 0;
+ case 100:
+ return 1;
+ case 101:
+ return 1;
+ case 102:
+ return 1;
+ }
+ return -1;
+ }
+ */
+ MethodSignature foo = clazz.addStaticMethod("switchRewrite", ImmutableList.of("I"), "I",
+ ".limit stack 2",
+ ".limit locals 2",
+ ".var 0 is x I from L0 to L11",
+ ".var 1 is t I from L1 to L3",
+
+ "L0:",
+ ".line 3",
+ " iload 0",
+ " iconst_1",
+ " iadd",
+ " istore 1",
+ "L1:",
+ ".line 4",
+ " iload 1",
+ " istore 0",
+ "L2:",
+ ".line 5",
+ " iload 0",
+ " iload 0",
+ " iadd",
+ " istore 0",
+ "L3_ORIGINAL:", // This is where javac normally ends t.
+ ".line 7",
+ " iload 0",
+ "L3:", // Moved L3 here to end t on the switch instruction.
+ "lookupswitch",
+ " 0: L4",
+ " 1: L5",
+ " 2: L6",
+ " 100: L7",
+ " 101: L8",
+ " 102: L9",
+ " default: L10",
+ "L4:",
+ ".line 9",
+ " iconst_0",
+ " ireturn",
+ "L5:",
+ ".line 11",
+ " iconst_0",
+ " ireturn",
+ "L6:",
+ ".line 13",
+ " iconst_0",
+ " ireturn",
+ "L7:",
+ ".line 15",
+ " iconst_1",
+ " ireturn",
+ "L8:",
+ ".line 17",
+ " iconst_1",
+ " ireturn",
+ "L9:",
+ ".line 19",
+ " iconst_1",
+ " ireturn",
+ "L10:",
+ ".line 21",
+ " iconst_m1",
+ " ireturn",
+ "L11:"
+ );
+
+ clazz.addMainMethod(
+ ".limit stack 2",
+ ".limit locals 1",
+ " getstatic java/lang/System/out Ljava/io/PrintStream;",
+ " ldc 1",
+ " invokestatic Test/switchRewrite(I)I",
+ " invokevirtual java/io/PrintStream/print(I)V",
+ " return");
+
+ String expected = "-1";
+ String javaResult = runOnJava(builder, clazz.name);
+ assertEquals(expected, javaResult);
+
+ AndroidApp jasminApp = builder.build();
+ AndroidApp d8App = ToolHelper.runD8(jasminApp);
+ String artResult = runOnArt(d8App, clazz.name);
+ assertEquals(expected, artResult);
+
+ DebugInfoInspector info = new DebugInfoInspector(d8App, clazz.name, foo);
+ info.checkStartLine(3);
+ info.checkLineHasExactLocals(3, "x", "int");
+ info.checkLineHasExactLocals(4, "x", "int", "t", "int");
+ info.checkLineHasExactLocals(5, "x", "int", "t", "int");
+ info.checkLineHasExactLocals(7, "x", "int", "t", "int");
+ info.checkLineHasExactLocals(9, "x", "int");
+ info.checkLineHasExactLocals(11, "x", "int");
+ info.checkLineHasExactLocals(13, "x", "int");
+ }
}
diff --git a/src/test/java/com/android/tools/r8/jasmin/InvalidDebugInfoTests.java b/src/test/java/com/android/tools/r8/jasmin/InvalidDebugInfoTests.java
index 81d299e..2551941 100644
--- a/src/test/java/com/android/tools/r8/jasmin/InvalidDebugInfoTests.java
+++ b/src/test/java/com/android/tools/r8/jasmin/InvalidDebugInfoTests.java
@@ -4,8 +4,13 @@
package com.android.tools.r8.jasmin;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.debuginfo.DebugInfoInspector;
+import com.android.tools.r8.naming.MemberNaming.MethodSignature;
+import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.StringUtils;
import com.google.common.collect.ImmutableList;
import org.junit.Test;
@@ -128,7 +133,7 @@
public void invalidInfoBug63412730_onWrite() throws Throwable {
JasminBuilder builder = new JasminBuilder();
JasminBuilder.ClassBuilder clazz = builder.addClass("Test");
- clazz.addStaticMethod("bar", ImmutableList.of(), "V",
+ MethodSignature method = clazz.addStaticMethod("bar", ImmutableList.of(), "V",
".limit stack 3",
".limit locals 2",
".var 1 is i I from LI to End",
@@ -136,9 +141,11 @@
"Init:",
" ldc 42",
" istore 0",
+ ".line 1",
"LI:",
" ldc 7.5",
" fstore 1",
+ ".line 2",
"LF:",
" getstatic java/lang/System/out Ljava/io/PrintStream;",
" dup",
@@ -146,6 +153,7 @@
" invokevirtual java/io/PrintStream/println(I)V",
" fload 1",
" invokevirtual java/io/PrintStream/println(F)V",
+ ".line 3",
" return",
"End:");
@@ -158,15 +166,18 @@
String expected = StringUtils.lines("42", "7.5");
String javaResult = runOnJava(builder, clazz.name);
assertEquals(expected, javaResult);
- String artResult = runOnArtD8(builder, clazz.name);
+ AndroidApp app = compileWithD8(builder);
+ String artResult = runOnArt(app, clazz.name);
assertEquals(expected, artResult);
+ DebugInfoInspector info = new DebugInfoInspector(app, clazz.name, method);
+ assertFalse(info.hasLocalsInfo());
}
@Test
public void invalidInfoBug63412730_onRead() throws Throwable {
JasminBuilder builder = new JasminBuilder();
JasminBuilder.ClassBuilder clazz = builder.addClass("Test");
- clazz.addStaticMethod("bar", ImmutableList.of(), "V",
+ MethodSignature method = clazz.addStaticMethod("bar", ImmutableList.of(), "V",
".limit stack 3",
".limit locals 2",
".var 1 is i I from Locals to End",
@@ -174,8 +185,10 @@
"Init:",
" ldc 42",
" istore 0",
+ ".line 1",
" ldc 7.5",
" fstore 1",
+ ".line 2",
"Locals:",
" getstatic java/lang/System/out Ljava/io/PrintStream;",
" dup",
@@ -183,6 +196,7 @@
" invokevirtual java/io/PrintStream/println(I)V",
" fload 1",
" invokevirtual java/io/PrintStream/println(F)V",
+ ".line 3",
" return",
"End:");
@@ -195,15 +209,18 @@
String expected = StringUtils.lines("42", "7.5");
String javaResult = runOnJava(builder, clazz.name);
assertEquals(expected, javaResult);
- String artResult = runOnArtD8(builder, clazz.name);
+ AndroidApp app = compileWithD8(builder);
+ String artResult = runOnArt(app, clazz.name);
assertEquals(expected, artResult);
+ DebugInfoInspector info = new DebugInfoInspector(app, clazz.name, method);
+ assertFalse(info.hasLocalsInfo());
}
@Test
public void invalidInfoBug63412730_onMove() throws Throwable {
JasminBuilder builder = new JasminBuilder();
JasminBuilder.ClassBuilder clazz = builder.addClass("Test");
- clazz.addStaticMethod("bar", ImmutableList.of(), "V",
+ MethodSignature method = clazz.addStaticMethod("bar", ImmutableList.of(), "V",
".limit stack 3",
".limit locals 2",
".var 1 is i I from LI to End",
@@ -211,21 +228,25 @@
"Init:",
" ldc 42",
" istore 0",
+ ".line 1",
"LI:",
" ldc 0",
" ldc 0",
" ifeq LZ",
" ldc 75",
" istore 1",
+ ".line 2",
"LJ:",
" getstatic java/lang/System/out Ljava/io/PrintStream;",
" iload 1",
" invokevirtual java/io/PrintStream/println(I)V",
+ ".line 3",
" return",
"LZ:",
" getstatic java/lang/System/out Ljava/io/PrintStream;",
" iload 0",
" invokevirtual java/io/PrintStream/println(I)V",
+ ".line 4",
" return",
"End:");
@@ -238,15 +259,18 @@
String expected = StringUtils.lines("42");
String javaResult = runOnJava(builder, clazz.name);
assertEquals(expected, javaResult);
- String artResult = runOnArtD8(builder, clazz.name);
+ AndroidApp app = compileWithD8(builder);
+ String artResult = runOnArt(app, clazz.name);
assertEquals(expected, artResult);
+ DebugInfoInspector info = new DebugInfoInspector(app, clazz.name, method);
+ assertFalse(info.hasLocalsInfo());
}
@Test
public void invalidInfoBug63412730_onPop() throws Throwable {
JasminBuilder builder = new JasminBuilder();
JasminBuilder.ClassBuilder clazz = builder.addClass("Test");
- clazz.addStaticMethod("bar", ImmutableList.of(), "V",
+ MethodSignature method = clazz.addStaticMethod("bar", ImmutableList.of(), "V",
".limit stack 3",
".limit locals 2",
".var 1 is a [Ljava/lang/Object; from Locals to End",
@@ -255,21 +279,25 @@
" ldc 1",
" anewarray java/lang/Object",
" astore 0",
+ ".line 1",
" new java/lang/Integer",
" dup",
" ldc 42",
" invokespecial java/lang/Integer/<init>(I)V",
" astore 1",
+ ".line 2",
"Locals:",
" aload 0",
" ldc 0",
" aload 1",
" aastore",
+ ".line 3",
" getstatic java/lang/System/out Ljava/io/PrintStream;",
" aload 0",
" ldc 0",
" aaload",
" invokevirtual java/io/PrintStream/println(Ljava/lang/Object;)V",
+ ".line 4",
" return",
"End:");
@@ -282,7 +310,12 @@
String expected = StringUtils.lines("42");
String javaResult = runOnJava(builder, clazz.name);
assertEquals(expected, javaResult);
- String artResult = runOnArtD8(builder, clazz.name);
+ AndroidApp app = compileWithD8(builder);
+ String artResult = runOnArt(app, clazz.name);
assertEquals(expected, artResult);
+ DebugInfoInspector info = new DebugInfoInspector(app, clazz.name, method);
+ // Note: This code is actually invalid debug info, but we do not reject it because both types
+ // are reference types. If we ever change that we should update this test.
+ assertTrue(info.hasLocalsInfo());
}
}
diff --git a/src/test/java/com/android/tools/r8/jasmin/Regress65007724.java b/src/test/java/com/android/tools/r8/jasmin/Regress65007724.java
new file mode 100644
index 0000000..1eb0586
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/jasmin/Regress65007724.java
@@ -0,0 +1,35 @@
+// 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.jasmin;
+
+import static org.junit.Assert.assertEquals;
+
+import org.junit.Test;
+
+public class Regress65007724 extends JasminTestBase {
+ @Test
+ public void testThat16BitsIndexAreAllowed() throws Exception {
+ JasminBuilder builder = new JasminBuilder();
+
+ for (int i = 0; i < 35000; i++) {
+ builder.addClass("C" + i);
+ }
+
+ JasminBuilder.ClassBuilder clazz = builder.addClass("Test");
+
+ clazz.addStaticField("f", "LC34000;", null);
+
+ clazz.addMainMethod(
+ ".limit stack 2",
+ ".limit locals 1",
+ "getstatic java/lang/System/out Ljava/io/PrintStream;",
+ "ldc \"Hello World!\"",
+ "invokevirtual java/io/PrintStream/print(Ljava/lang/String;)V",
+ "return");
+
+ String expected = runOnJava(builder, clazz.name);
+ String artResult = runOnArtD8(builder, clazz.name);
+ assertEquals(expected, artResult);
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/jsr45/JSR45Tests.java b/src/test/java/com/android/tools/r8/jsr45/JSR45Tests.java
index d5f9c6a..601ca23 100644
--- a/src/test/java/com/android/tools/r8/jsr45/JSR45Tests.java
+++ b/src/test/java/com/android/tools/r8/jsr45/JSR45Tests.java
@@ -60,7 +60,7 @@
}
void compileWithR8(Path inputPath, Path outputPath, Path keepRulesPath)
- throws IOException, CompilationException, ExecutionException, ProguardRuleParserException {
+ throws IOException, CompilationException, ProguardRuleParserException {
AndroidApp androidApp =
R8.run(
R8Command.builder()
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexListOutputTest.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexListOutputTest.java
index dfc20f9..1039475 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexListOutputTest.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexListOutputTest.java
@@ -38,7 +38,7 @@
Path mainDexListOutput = temp.getRoot().toPath().resolve("main-dex-output.txt");
R8Command command =
ToolHelper.prepareR8CommandBuilder(readClasses(HelloWorldMain.class))
- .addMainDexRules(mainDexRules)
+ .addMainDexRulesFiles(mainDexRules)
.setMainDexListOutputPath(mainDexListOutput)
.build();
ToolHelper.runR8(command);
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
index 619f808..4122416 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
@@ -19,7 +19,7 @@
import com.android.tools.r8.dex.ApplicationWriter;
import com.android.tools.r8.dex.Constants;
import com.android.tools.r8.errors.CompilationError;
-import com.android.tools.r8.errors.MainDexError;
+import com.android.tools.r8.errors.DexOverflowException;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.AppInfoWithSubtyping;
import com.android.tools.r8.graph.Code;
@@ -36,6 +36,7 @@
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.DexTypeList;
import com.android.tools.r8.ir.code.CatchHandlers;
+import com.android.tools.r8.ir.code.DebugPosition;
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.conversion.IRBuilder;
import com.android.tools.r8.ir.conversion.SourceCode;
@@ -161,7 +162,7 @@
try {
verifyMainDexContains(TWO_LARGE_CLASSES, getTwoLargeClassesAppPath(), false);
fail("Expect to fail, for there are too many classes for the main-dex list.");
- } catch (CompilationError e) {
+ } catch (DexOverflowException e) {
// Make sure {@link MonoDexDistributor} was _not_ used.
assertFalse(e.getMessage().contains("single dex file"));
// Make sure what exceeds the limit is the number of methods.
@@ -202,7 +203,7 @@
try {
verifyMainDexContains(MANY_CLASSES, getManyClassesMultiDexAppPath(), false);
fail("Expect to fail, for there are too many classes for the main-dex list.");
- } catch (CompilationError e) {
+ } catch (DexOverflowException e) {
// Make sure {@link MonoDexDistributor} was _not_ used.
assertFalse(e.getMessage().contains("single dex file"));
// Make sure what exceeds the limit is the number of methods.
@@ -402,7 +403,7 @@
generateApplication(
MANY_CLASSES, Constants.ANDROID_K_API, false, MANY_CLASSES_MULTI_DEX_METHODS_PER_CLASS);
fail("Expect to fail, for there are many classes while multidex is not enabled.");
- } catch (MainDexError e) {
+ } catch (DexOverflowException e) {
// Make sure {@link MonoDexDistributor} was used.
assertTrue(e.getMessage().contains("single dex file"));
// Make sure what exceeds the limit is the number of methods.
@@ -689,6 +690,16 @@
}
@Override
+ public int getMoveExceptionRegister() {
+ throw new Unreachable();
+ }
+
+ @Override
+ public DebugPosition getDebugPositionAtOffset(int offset) {
+ throw new Unreachable();
+ }
+
+ @Override
public boolean verifyRegister(int register) {
throw new Unreachable();
}
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexTracingTest.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexTracingTest.java
index 57c8cf6..ba5092f 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexTracingTest.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexTracingTest.java
@@ -9,16 +9,18 @@
import static com.android.tools.r8.utils.FileUtils.ZIP_EXTENSION;
import com.android.tools.r8.CompilationResult;
+import com.android.tools.r8.GenerateMainDexList;
+import com.android.tools.r8.GenerateMainDexListCommand;
import com.android.tools.r8.R8Command;
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.utils.DescriptorUtils;
import com.android.tools.r8.utils.InternalOptions;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
-import java.util.Collections;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.function.Consumer;
@@ -107,7 +109,6 @@
mainDexRules,
expectedMainDexList,
minSdk,
- R8Command.builder(),
(options) -> {
options.inlineAccessors = false;
});
@@ -120,48 +121,86 @@
Path mainDexRules,
Path expectedMainDexList,
int minSdk,
- R8Command.Builder builder,
Consumer<InternalOptions> optionsConsumer)
throws Throwable {
Path out = temp.getRoot().toPath().resolve(testName + ZIP_EXTENSION);
Path inputJar = Paths.get(buildDir, packageName + JAR_EXTENSION);
- builder.setMinApiLevel(minSdk);
try {
- R8Command command = builder
+ // Build main-dex list using GenerateMainDexList.
+ GenerateMainDexListCommand.Builder mdlCommandBuilder = GenerateMainDexListCommand.builder();
+ GenerateMainDexListCommand command2 = mdlCommandBuilder
.addProgramFiles(inputJar)
- .addLibraryFiles(Paths.get(EXAMPLE_BUILD_DIR, "multidexfakeframeworks" + JAR_EXTENSION),
- Paths.get(ToolHelper.getAndroidJar(minSdk)))
+ .addProgramFiles(Paths.get(EXAMPLE_BUILD_DIR, "multidexfakeframeworks" + JAR_EXTENSION))
+ .addMainDexRulesFiles(mainDexRules)
+ .build();
+ List<String> mainDexGeneratorMainDexList =
+ GenerateMainDexList.run(command2).stream()
+ .map(this::mainDexStringToDescriptor)
+ .sorted()
+ .collect(Collectors.toList());
+
+ // Build main-dex list using R8.
+ R8Command.Builder r8CommandBuilder = R8Command.builder();
+ R8Command command = r8CommandBuilder
+ .setMinApiLevel(minSdk)
+ .addProgramFiles(inputJar)
+ .addProgramFiles(Paths.get(EXAMPLE_BUILD_DIR, "multidexfakeframeworks" + JAR_EXTENSION))
+ .addLibraryFiles(Paths.get(ToolHelper.getAndroidJar(minSdk)))
.setOutputPath(out)
- .addMainDexRules(mainDexRules)
+ .addMainDexRulesFiles(mainDexRules)
.build();
CompilationResult result = ToolHelper.runR8WithFullResult(command, optionsConsumer);
- List<String> resultMainDexList =
+ List<String> r8MainDexList =
result.dexApplication.mainDexList.stream()
.filter(dexType -> isApplicationClass(dexType, result))
.map(dexType -> dexType.descriptor.toString())
+ .sorted()
.collect(Collectors.toList());
- Collections.sort(resultMainDexList);
+
+ // Check that both generated lists are the same as the reference list, except for lambda
+ // classes which are only produced when running R8.
String[] refList = new String(Files.readAllBytes(
expectedMainDexList), StandardCharsets.UTF_8).split("\n");
+ int nonLambdaOffset = 0;
for (int i = 0; i < refList.length; i++) {
String reference = refList[i].trim();
- String computed = resultMainDexList.get(i);
- if (reference.contains("-$$Lambda$")) {
- // For lambda classes we check that there is a lambda class for the right containing
- // class. However, we do not check the hash for the generated lambda class. The hash
- // changes for different compiler versions because different compiler versions generate
- // different lambda implementation method names.
- reference = reference.substring(0, reference.lastIndexOf('$'));
- computed = computed.substring(0, computed.lastIndexOf('$'));
+ checkSameMainDexEntry(reference, r8MainDexList.get(i));
+ // The main dex list generator does not do any lambda desugaring.
+ if (!isLambda(reference)) {
+ checkSameMainDexEntry(reference, mainDexGeneratorMainDexList.get(i - nonLambdaOffset));
+ } else {
+ nonLambdaOffset++;
}
- Assert.assertEquals(reference, computed);
}
} catch (ExecutionException e) {
throw e.getCause();
}
}
+ private boolean isLambda(String mainDexEntry) {
+ return mainDexEntry.contains("-$$Lambda$");
+ }
+
+ private String mainDexStringToDescriptor(String mainDexString) {
+ final String CLASS_EXTENSION = ".class";
+ Assert.assertTrue(mainDexString.endsWith(CLASS_EXTENSION));
+ return DescriptorUtils.getDescriptorFromClassBinaryName(
+ mainDexString.substring(0, mainDexString.length() - CLASS_EXTENSION.length()));
+ }
+
+ private void checkSameMainDexEntry(String reference, String computed) {
+ if (isLambda(reference)) {
+ // For lambda classes we check that there is a lambda class for the right containing
+ // class. However, we do not check the hash for the generated lambda class. The hash
+ // changes for different compiler versions because different compiler versions generate
+ // different lambda implementation method names.
+ reference = reference.substring(0, reference.lastIndexOf('$'));
+ computed = computed.substring(0, computed.lastIndexOf('$'));
+ }
+ Assert.assertEquals(reference, computed);
+ }
+
private boolean isApplicationClass(DexType dexType, CompilationResult result) {
DexClass clazz = result.appInfo.definitionFor(dexType);
return clazz != null && clazz.isProgramClass();
diff --git a/src/test/java/com/android/tools/r8/rewrite/staticvalues/StaticValuesTest.java b/src/test/java/com/android/tools/r8/rewrite/staticvalues/StaticValuesTest.java
index 195c3aa..b8d8e58 100644
--- a/src/test/java/com/android/tools/r8/rewrite/staticvalues/StaticValuesTest.java
+++ b/src/test/java/com/android/tools/r8/rewrite/staticvalues/StaticValuesTest.java
@@ -7,7 +7,21 @@
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
+import com.android.tools.r8.code.Instruction;
+import com.android.tools.r8.code.Sput;
+import com.android.tools.r8.code.SputObject;
import com.android.tools.r8.graph.DexApplication;
+import com.android.tools.r8.graph.DexCode;
+import com.android.tools.r8.graph.DexValue;
+import com.android.tools.r8.graph.DexValue.DexValueBoolean;
+import com.android.tools.r8.graph.DexValue.DexValueByte;
+import com.android.tools.r8.graph.DexValue.DexValueChar;
+import com.android.tools.r8.graph.DexValue.DexValueDouble;
+import com.android.tools.r8.graph.DexValue.DexValueFloat;
+import com.android.tools.r8.graph.DexValue.DexValueInt;
+import com.android.tools.r8.graph.DexValue.DexValueLong;
+import com.android.tools.r8.graph.DexValue.DexValueShort;
+import com.android.tools.r8.graph.DexValue.DexValueString;
import com.android.tools.r8.smali.SmaliTestBase;
import com.android.tools.r8.utils.DexInspector;
import com.android.tools.r8.utils.DexInspector.MethodSubject;
@@ -17,7 +31,7 @@
public class StaticValuesTest extends SmaliTestBase {
@Test
- public void testAllTypes() {
+ public void testAllTypes() throws Exception {
SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
builder.addStaticField("booleanField", "Z");
@@ -80,7 +94,55 @@
DexApplication processedApplication = processApplication(originalApplication, options);
DexInspector inspector = new DexInspector(processedApplication);
- assertFalse(inspector.clazz("Test").clinit().isPresent());
+ // Test is running without tree-shaking, so the empty <clinit> is not removed.
+ assertTrue(
+ inspector.clazz("Test").clinit().getMethod().getCode().asDexCode().isEmptyVoidMethod());
+
+ DexValue value;
+ assertTrue(inspector.clazz("Test").field("boolean", "booleanField").hasStaticValue());
+ value = inspector.clazz("Test").field("boolean", "booleanField").getStaticValue();
+ assertTrue(value instanceof DexValueBoolean);
+ assertEquals(true, ((DexValueBoolean) value).getValue());
+
+ assertTrue(inspector.clazz("Test").field("byte", "byteField").hasStaticValue());
+ value = inspector.clazz("Test").field("byte", "byteField").getStaticValue();
+ assertTrue(value instanceof DexValueByte);
+ assertEquals(1, ((DexValueByte) value).getValue());
+
+ assertTrue(inspector.clazz("Test").field("short", "shortField").hasStaticValue());
+ value = inspector.clazz("Test").field("short", "shortField").getStaticValue();
+ assertTrue(value instanceof DexValueShort);
+ assertEquals(2, ((DexValueShort) value).getValue());
+
+ assertTrue(inspector.clazz("Test").field("int", "intField").hasStaticValue());
+ value = inspector.clazz("Test").field("int", "intField").getStaticValue();
+ assertTrue(value instanceof DexValueInt);
+ assertEquals(3, ((DexValueInt) value).getValue());
+
+ assertTrue(inspector.clazz("Test").field("long", "longField").hasStaticValue());
+ value = inspector.clazz("Test").field("long", "longField").getStaticValue();
+ assertTrue(value instanceof DexValueLong);
+ assertEquals(4, ((DexValueLong) value).getValue());
+
+ assertTrue(inspector.clazz("Test").field("float", "floatField").hasStaticValue());
+ value = inspector.clazz("Test").field("float", "floatField").getStaticValue();
+ assertTrue(value instanceof DexValueFloat);
+ assertEquals(5.0f, ((DexValueFloat) value).getValue(), 0.0);
+
+ assertTrue(inspector.clazz("Test").field("double", "doubleField").hasStaticValue());
+ value = inspector.clazz("Test").field("double", "doubleField").getStaticValue();
+ assertTrue(value instanceof DexValueDouble);
+ assertEquals(6.0f, ((DexValueDouble) value).getValue(), 0.0);
+
+ assertTrue(inspector.clazz("Test").field("char", "charField").hasStaticValue());
+ value = inspector.clazz("Test").field("char", "charField").getStaticValue();
+ assertTrue(value instanceof DexValueChar);
+ assertEquals(0x30 + 7, ((DexValueChar) value).getValue());
+
+ assertTrue(inspector.clazz("Test").field("java.lang.String", "stringField").hasStaticValue());
+ value = inspector.clazz("Test").field("java.lang.String", "stringField").getStaticValue();
+ assertTrue(value instanceof DexValueString);
+ assertEquals(("8"), ((DexValueString) value).getValue().toString());
String result = runArt(processedApplication, options);
@@ -88,7 +150,7 @@
}
@Test
- public void getBeforePut() {
+ public void getBeforePut() throws Exception {
SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
builder.addStaticField("field1", "I", "1");
@@ -127,7 +189,7 @@
}
@Test
- public void testNull() {
+ public void testNull() throws Exception {
SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
builder.addStaticField("stringField", "Ljava/lang/String;", "Hello");
@@ -159,7 +221,9 @@
DexApplication processedApplication = processApplication(originalApplication, options);
DexInspector inspector = new DexInspector(processedApplication);
- assertFalse(inspector.clazz("Test").clinit().isPresent());
+ // Test is running without tree-shaking, so the empty <clinit> is not removed.
+ assertTrue(
+ inspector.clazz("Test").clinit().getMethod().getCode().asDexCode().isEmptyVoidMethod());
String result = runArt(processedApplication, options);
@@ -167,7 +231,7 @@
}
@Test
- public void testString() {
+ public void testString() throws Exception {
SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
builder.addStaticField("stringField1", "Ljava/lang/String;", "Hello");
@@ -200,7 +264,9 @@
DexApplication processedApplication = processApplication(originalApplication, options);
DexInspector inspector = new DexInspector(processedApplication);
- assertFalse(inspector.clazz("Test").clinit().isPresent());
+ // Test is running without tree-shaking, so the empty <clinit> is not removed.
+ assertTrue(
+ inspector.clazz("Test").clinit().getMethod().getCode().asDexCode().isEmptyVoidMethod());
String result = runArt(processedApplication, options);
@@ -208,7 +274,153 @@
}
@Test
- public void testInitializationToOwnClassName() {
+ public void testMultiplePuts() throws Exception {
+ SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
+
+ builder.addStaticField("intField", "I");
+ builder.addStaticField("stringField", "Ljava/lang/String;");
+
+ builder.addStaticInitializer(
+ 1,
+ "const v0, 0",
+ "sput v0, LTest;->intField:I",
+ "const-string v0, \"4\"",
+ "sput-object v0, LTest;->stringField:Ljava/lang/String;",
+ "const v0, 1",
+ "sput v0, LTest;->intField:I",
+ "const-string v0, \"5\"",
+ "sput-object v0, LTest;->stringField:Ljava/lang/String;",
+ "const v0, 2",
+ "sput v0, LTest;->intField:I",
+ "const-string v0, \"6\"",
+ "sput-object v0, LTest;->stringField:Ljava/lang/String;",
+ "const v0, 3",
+ "sput v0, LTest;->intField:I",
+ "const-string v0, \"7\"",
+ "sput-object v0, LTest;->stringField:Ljava/lang/String;",
+ "return-void"
+ );
+ builder.addMainMethod(
+ 2,
+ "sget-object v0, Ljava/lang/System;->out:Ljava/io/PrintStream;",
+ "sget v1, LTest;->intField:I",
+ "invoke-virtual { v0, v1 }, Ljava/io/PrintStream;->println(I)V",
+ "sget-object v1, LTest;->stringField:Ljava/lang/String;",
+ "invoke-virtual { v0, v1 }, Ljava/io/PrintStream;->println(Ljava/lang/String;)V",
+ "return-void"
+ );
+
+ InternalOptions options = new InternalOptions();
+ DexApplication originalApplication = buildApplication(builder, options);
+ DexApplication processedApplication = processApplication(originalApplication, options);
+
+ DexInspector inspector = new DexInspector(processedApplication);
+ // Test is running without tree-shaking, so the empty <clinit> is not removed.
+ assertTrue(
+ inspector.clazz("Test").clinit().getMethod().getCode().asDexCode().isEmptyVoidMethod());
+
+ DexValue value;
+ assertTrue(inspector.clazz("Test").field("int", "intField").hasStaticValue());
+ value = inspector.clazz("Test").field("int", "intField").getStaticValue();
+ assertTrue(value instanceof DexValueInt);
+ assertEquals(3, ((DexValueInt) value).getValue());
+
+ assertTrue(inspector.clazz("Test").field("java.lang.String", "stringField").hasStaticValue());
+ value = inspector.clazz("Test").field("java.lang.String", "stringField").getStaticValue();
+ assertTrue(value instanceof DexValueString);
+ assertEquals(("7"), ((DexValueString) value).getValue().toString());
+
+ String result = runArt(processedApplication, options);
+
+ assertEquals("3\n7\n", result);
+ }
+
+
+ @Test
+ public void testMultiplePutsWithControlFlow() throws Exception {
+ SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
+
+ builder.addStaticField("booleanField", "Z");
+ builder.addStaticField("intField", "I");
+ builder.addStaticField("intField2", "I");
+ builder.addStaticField("stringField", "Ljava/lang/String;");
+
+ builder.addStaticInitializer(
+ 1,
+ "const v0, 0",
+ "sput v0, LTest;->intField:I",
+ "const-string v0, \"4\"",
+ "sput-object v0, LTest;->stringField:Ljava/lang/String;",
+ "const v0, 1",
+ "sput v0, LTest;->intField:I",
+ "const-string v0, \"5\"",
+ "sput-object v0, LTest;->stringField:Ljava/lang/String;",
+ "sget-boolean v0, LTest;->booleanField:Z",
+ "if-eqz v0, :label_1",
+ "const v0, 8",
+ "sput v0, LTest;->intField2:I",
+ ":label_1",
+ "const v0, 9",
+ "sput v0, LTest;->intField2:I",
+ "goto :label_2",
+ ":label_2",
+ "const v0, 2",
+ "sput v0, LTest;->intField:I",
+ "const-string v0, \"6\"",
+ "sput-object v0, LTest;->stringField:Ljava/lang/String;",
+ "const v0, 3",
+ "sput v0, LTest;->intField:I",
+ "const-string v0, \"7\"",
+ "sput-object v0, LTest;->stringField:Ljava/lang/String;",
+ "return-void"
+ );
+ builder.addMainMethod(
+ 2,
+ "sget-object v0, Ljava/lang/System;->out:Ljava/io/PrintStream;",
+ "sget v1, LTest;->intField:I",
+ "invoke-virtual { v0, v1 }, Ljava/io/PrintStream;->println(I)V",
+ "sget-object v1, LTest;->stringField:Ljava/lang/String;",
+ "invoke-virtual { v0, v1 }, Ljava/io/PrintStream;->println(Ljava/lang/String;)V",
+ "return-void"
+ );
+
+ InternalOptions options = new InternalOptions();
+ DexApplication originalApplication = buildApplication(builder, options);
+ DexApplication processedApplication = processApplication(originalApplication, options);
+
+ DexInspector inspector = new DexInspector(processedApplication);
+ assertTrue(inspector.clazz("Test").clinit().isPresent());
+
+ DexValue value;
+ assertTrue(inspector.clazz("Test").field("int", "intField").hasStaticValue());
+ value = inspector.clazz("Test").field("int", "intField").getStaticValue();
+ assertTrue(value instanceof DexValueInt);
+ assertEquals(3, ((DexValueInt) value).getValue());
+
+ assertTrue(inspector.clazz("Test").field("java.lang.String", "stringField").hasStaticValue());
+ value = inspector.clazz("Test").field("java.lang.String", "stringField").getStaticValue();
+ assertTrue(value instanceof DexValueString);
+ assertEquals(("7"), ((DexValueString) value).getValue().toString());
+
+ DexCode code = inspector.clazz("Test").clinit().getMethod().getCode().asDexCode();
+ for (Instruction instruction : code.instructions) {
+ if (instruction instanceof Sput) {
+ Sput put = (Sput) instruction;
+ // Only int put ot intField2.
+ assertEquals(put.getField().name.toString(), "intField2");
+ } else {
+ // No Object (String) puts.
+ assertFalse(instruction instanceof SputObject);
+ }
+ }
+
+ String result = runArt(processedApplication, options);
+
+ assertEquals("3\n7\n", result);
+ }
+
+ @Test
+ public void testInitializationToOwnClassName() throws Exception {
String className = "org.example.Test";
SmaliBuilder builder = new SmaliBuilder(className);
@@ -266,7 +478,9 @@
DexInspector inspector = new DexInspector(processedApplication);
assertTrue(inspector.clazz(className).isPresent());
- assertFalse(inspector.clazz(className).clinit().isPresent());
+ // Test is running without tree-shaking, so the empty <clinit> is not removed.
+ assertTrue(
+ inspector.clazz(className).clinit().getMethod().getCode().asDexCode().isEmptyVoidMethod());
String result = runArt(processedApplication, options, className);
@@ -275,7 +489,7 @@
}
@Test
- public void testInitializationToOtherClassName() {
+ public void testInitializationToOtherClassName() throws Exception {
String className = "org.example.Test";
SmaliBuilder builder = new SmaliBuilder(className);
@@ -322,7 +536,7 @@
}
@Test
- public void fieldOnOtherClass() {
+ public void fieldOnOtherClass() throws Exception {
SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
builder.addStaticInitializer(
diff --git a/src/test/java/com/android/tools/r8/shaking/PrintUsageTest.java b/src/test/java/com/android/tools/r8/shaking/PrintUsageTest.java
index 03df65c..4cb6736 100644
--- a/src/test/java/com/android/tools/r8/shaking/PrintUsageTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/PrintUsageTest.java
@@ -126,7 +126,9 @@
private static void inspectShaking1(PrintUsageInspector inspector) {
assertTrue(inspector.clazz("shaking1.Unused").isPresent());
- assertFalse(inspector.clazz("shaking1.Used").isPresent());
+ assertTrue(inspector.clazz("shaking1.Used").isPresent());
+ ClassSubject used = inspector.clazz("shaking1.Used").get();
+ assertTrue(used.method("void", "<clinit>", ImmutableList.of()));
}
private static void inspectShaking2(PrintUsageInspector inspector) {
diff --git a/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java b/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
index ac0178c..0f2e456 100644
--- a/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
@@ -14,6 +14,7 @@
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.graph.DexAccessFlags;
import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.utils.FileUtils;
import com.android.tools.r8.utils.InternalOptions.PackageObfuscationMode;
import java.io.IOException;
import java.nio.file.Path;
@@ -102,11 +103,22 @@
@Test
public void parse() throws IOException, ProguardRuleParserException {
- ProguardConfigurationParser parser = new ProguardConfigurationParser(new DexItemFactory());
+ ProguardConfigurationParser parser;
+
+ // Parse from file.
+ parser = new ProguardConfigurationParser(new DexItemFactory());
parser.parse(Paths.get(PROGUARD_SPEC_FILE));
List<ProguardConfigurationRule> rules = parser.getConfig().getRules();
assertEquals(24, rules.size());
assertEquals(1, rules.get(0).getMemberRules().size());
+
+ // Parse from strings.
+ parser = new ProguardConfigurationParser(new DexItemFactory());
+ List<String> lines = FileUtils.readTextFile(Paths.get(PROGUARD_SPEC_FILE));
+ parser.parse(new ProguardConfigurationSourceStrings(lines));
+ rules = parser.getConfig().getRules();
+ assertEquals(24, rules.size());
+ assertEquals(1, rules.get(0).getMemberRules().size());
}
@Test
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 ab6d6e2..66082fc 100644
--- a/src/test/java/com/android/tools/r8/shaking/TreeShakingTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/TreeShakingTest.java
@@ -150,6 +150,9 @@
used.method("java.lang.String", "aMethodThatIsNotUsedButKept", Collections.emptyList())
.isPresent());
Assert.assertTrue(used.field("int", "aStaticFieldThatIsNotUsedButKept").isPresent());
+ // Rewriting of <clinit> moves the initialization of aStaticFieldThatIsNotUsedButKept
+ // from <clinit> code into statics value section of the dex file.
+ Assert.assertFalse(used.clinit().isPresent());
}
public static void shaking1IsCorrectlyRepackaged(DexInspector inspector) {
diff --git a/src/test/java/com/android/tools/r8/shaking/includedescriptorclasses/IncludeDescriptorClassesTest.java b/src/test/java/com/android/tools/r8/shaking/includedescriptorclasses/IncludeDescriptorClassesTest.java
index a02a186..5ad5787 100644
--- a/src/test/java/com/android/tools/r8/shaking/includedescriptorclasses/IncludeDescriptorClassesTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/includedescriptorclasses/IncludeDescriptorClassesTest.java
@@ -13,15 +13,15 @@
import com.android.tools.r8.utils.DexInspector;
import com.google.common.collect.ImmutableList;
import java.io.File;
-import java.io.FileInputStream;
import java.io.IOException;
import java.nio.file.Path;
import java.util.ArrayList;
+import java.util.Enumeration;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.zip.ZipEntry;
-import java.util.zip.ZipInputStream;
+import java.util.zip.ZipFile;
import org.junit.Test;
public class IncludeDescriptorClassesTest extends TestBase {
@@ -51,14 +51,14 @@
private Set<String> readJarClasses(Path jar) throws IOException {
Set<String> result = new HashSet<>();
- try (ZipInputStream in = new ZipInputStream(new FileInputStream(jar.toFile()))) {
- ZipEntry entry = in.getNextEntry();
- while (entry != null) {
+ try (ZipFile zipFile = new ZipFile(jar.toFile())) {
+ final Enumeration<? extends ZipEntry> entries = zipFile.entries();
+ while (entries.hasMoreElements()) {
+ ZipEntry entry = entries.nextElement();
String name = entry.getName();
if (name.endsWith(".class")) {
result.add(name.substring(0, name.length() - ".class".length()).replace('/', '.'));
}
- entry = in.getNextEntry();
}
}
return result;
diff --git a/src/test/java/com/android/tools/r8/smali/JumboStringTest.java b/src/test/java/com/android/tools/r8/smali/JumboStringTest.java
index 1d256e0..a52c68b 100644
--- a/src/test/java/com/android/tools/r8/smali/JumboStringTest.java
+++ b/src/test/java/com/android/tools/r8/smali/JumboStringTest.java
@@ -15,7 +15,7 @@
public class JumboStringTest extends SmaliTestBase {
@Test
- public void test() {
+ public void test() throws Exception {
StringBuilder builder = new StringBuilder();
StringBuilder expectedBuilder = new StringBuilder();
builder.append(" new-instance v0, Ljava/lang/StringBuilder;\n");
diff --git a/src/test/java/com/android/tools/r8/smali/OutlineTest.java b/src/test/java/com/android/tools/r8/smali/OutlineTest.java
index c726aad..c64e39c 100644
--- a/src/test/java/com/android/tools/r8/smali/OutlineTest.java
+++ b/src/test/java/com/android/tools/r8/smali/OutlineTest.java
@@ -12,6 +12,7 @@
import com.android.tools.r8.code.ConstWide;
import com.android.tools.r8.code.ConstWideHigh16;
import com.android.tools.r8.code.DivInt;
+import com.android.tools.r8.code.DivInt2Addr;
import com.android.tools.r8.code.InvokeStatic;
import com.android.tools.r8.code.InvokeVirtual;
import com.android.tools.r8.code.MoveResult;
@@ -20,6 +21,7 @@
import com.android.tools.r8.code.ReturnObject;
import com.android.tools.r8.code.ReturnVoid;
import com.android.tools.r8.code.ReturnWide;
+import com.android.tools.r8.errors.DexOverflowException;
import com.android.tools.r8.graph.DexApplication;
import com.android.tools.r8.graph.DexCode;
import com.android.tools.r8.graph.DexEncodedMethod;
@@ -88,7 +90,7 @@
}
@Test
- public void a() {
+ public void a() throws Exception {
SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
String returnType = "java.lang.String";
@@ -150,7 +152,7 @@
}
@Test
- public void b() {
+ public void b() throws Exception {
SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
String returnType = "java.lang.String";
@@ -220,7 +222,7 @@
}
@Test
- public void c() {
+ public void c() throws Exception {
SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
// Method with const instructions after the outline.
@@ -277,7 +279,7 @@
}
@Test
- public void d() {
+ public void d() throws Exception {
SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
// Method with mixed use of arguments and locals.
@@ -416,7 +418,7 @@
}
@Test
- public void doubleArguments() {
+ public void doubleArguments() throws Exception {
SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
String returnType = "java.lang.String";
@@ -490,7 +492,7 @@
}
@Test
- public void invokeStatic() {
+ public void invokeStatic() throws Exception {
SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
String returnType = "void";
@@ -563,7 +565,7 @@
}
@Test
- public void constructor() throws IOException, RecognitionException {
+ public void constructor() throws Exception {
SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
MethodSignature signature1 = builder.addStaticMethod(
@@ -648,7 +650,7 @@
}
@Test
- public void constructorDontSplitNewInstanceAndInit() {
+ public void constructorDontSplitNewInstanceAndInit() throws Exception {
SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
MethodSignature signature = builder.addStaticMethod(
@@ -718,7 +720,7 @@
}
@Test
- public void outlineWithoutArguments() {
+ public void outlineWithoutArguments() throws Exception {
SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
MethodSignature signature1 = builder.addStaticMethod(
@@ -763,7 +765,7 @@
}
@Test
- public void outlineDifferentReturnType() {
+ public void outlineDifferentReturnType() throws Exception {
SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
List<String> parameters = Collections.singletonList("java.lang.StringBuilder");
@@ -850,7 +852,7 @@
}
@Test
- public void outlineMultipleTimes() {
+ public void outlineMultipleTimes() throws Exception {
SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
String returnType = "java.lang.String";
@@ -923,7 +925,7 @@
}
@Test
- public void outlineReturnLong() {
+ public void outlineReturnLong() throws Exception {
SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
MethodSignature signature = builder.addStaticMethod(
@@ -976,7 +978,7 @@
}
@Test
- public void outlineArrayType() {
+ public void outlineArrayType() throws Exception {
SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
builder.addStaticMethod(
@@ -1034,7 +1036,7 @@
}
@Test
- public void outlineArithmeticBinop() {
+ public void outlineArithmeticBinop() throws Exception {
SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
builder.addStaticMethod(
@@ -1115,7 +1117,7 @@
}
@Test
- public void outlineWithHandler() {
+ public void outlineWithHandler() throws Exception {
SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
builder.addStaticMethod(
@@ -1175,7 +1177,7 @@
assertTrue(code.instructions[0] instanceof DivInt);
assertTrue(code.instructions[1] instanceof InvokeStatic);
assertTrue(code.instructions[2] instanceof MoveResult);
- assertTrue(code.instructions[3] instanceof DivInt);
+ assertTrue(code.instructions[3] instanceof DivInt2Addr);
assertTrue(code.instructions[4] instanceof Return);
assertTrue(code.instructions[5] instanceof Const4);
assertTrue(code.instructions[6] instanceof Return);
@@ -1188,7 +1190,7 @@
}
@Test
- public void outlineUnusedOutValue() {
+ public void outlineUnusedOutValue() throws Exception {
SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
// The result from neither the div-int is never used.
@@ -1235,7 +1237,7 @@
}
@Test
- public void outlineUnusedNewInstanceOutValue() {
+ public void outlineUnusedNewInstanceOutValue() throws Exception {
SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
// The result from the new-instance instructions are never used (<init> is not even called).
@@ -1280,7 +1282,7 @@
}
@Test
- public void regress33733666() {
+ public void regress33733666() throws Exception {
SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
builder.addStaticMethod(
diff --git a/src/test/java/com/android/tools/r8/smali/Regress38014736.java b/src/test/java/com/android/tools/r8/smali/Regress38014736.java
index 91d8023..c97d412 100644
--- a/src/test/java/com/android/tools/r8/smali/Regress38014736.java
+++ b/src/test/java/com/android/tools/r8/smali/Regress38014736.java
@@ -12,7 +12,7 @@
public class Regress38014736 extends SmaliTestBase {
@Test
- public void handlerRangeStartingOnMoveResult() {
+ public void handlerRangeStartingOnMoveResult() throws Exception {
SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
builder.addMainMethod(
diff --git a/src/test/java/com/android/tools/r8/smali/RunArtSmokeTest.java b/src/test/java/com/android/tools/r8/smali/RunArtSmokeTest.java
index 45cd8cd..e1dc5c4 100644
--- a/src/test/java/com/android/tools/r8/smali/RunArtSmokeTest.java
+++ b/src/test/java/com/android/tools/r8/smali/RunArtSmokeTest.java
@@ -21,7 +21,7 @@
public class RunArtSmokeTest extends SmaliTestBase {
@Test
- public void test() {
+ public void test() throws Exception {
// Build simple "Hello, world!" application.
SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
MethodSignature mainSignature = builder.addMainMethod(
diff --git a/src/test/java/com/android/tools/r8/smali/SmaliTestBase.java b/src/test/java/com/android/tools/r8/smali/SmaliTestBase.java
index f35dd21..e2006d0 100644
--- a/src/test/java/com/android/tools/r8/smali/SmaliTestBase.java
+++ b/src/test/java/com/android/tools/r8/smali/SmaliTestBase.java
@@ -11,6 +11,7 @@
import com.android.tools.r8.TestBase;
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.dex.ApplicationReader;
+import com.android.tools.r8.errors.DexOverflowException;
import com.android.tools.r8.graph.AppInfo;
import com.android.tools.r8.graph.AppInfoWithSubtyping;
import com.android.tools.r8.graph.DexApplication;
@@ -387,7 +388,7 @@
return iterator;
}
- public String run() {
+ public String run() throws DexOverflowException {
AppInfo appInfo = new AppInfo(application);
IRConverter converter = new IRConverter(application, appInfo, options);
converter.replaceCodeForTesting(method, code);
@@ -514,11 +515,13 @@
processdApplication, DEFAULT_CLASS_NAME, returnType, DEFAULT_METHOD_NAME, parameters);
}
- public String runArt(DexApplication application, InternalOptions options) {
+ public String runArt(DexApplication application, InternalOptions options)
+ throws DexOverflowException {
return runArt(application, options, DEFAULT_MAIN_CLASS_NAME);
}
- public String runArt(DexApplication application, InternalOptions options, String mainClass) {
+ public String runArt(DexApplication application, InternalOptions options, String mainClass)
+ throws DexOverflowException {
try {
AndroidApp app = writeDex(application, options);
Path out = temp.getRoot().toPath().resolve("run-art-input.zip");
@@ -530,7 +533,8 @@
}
}
- public void runDex2Oat(DexApplication application, InternalOptions options) {
+ public void runDex2Oat(DexApplication application, InternalOptions options)
+ throws DexOverflowException {
try {
AndroidApp app = writeDex(application, options);
Path dexOut = temp.getRoot().toPath().resolve("run-dex2oat-input.zip");
@@ -542,7 +546,8 @@
}
}
- public AndroidApp writeDex(DexApplication application, InternalOptions options) {
+ public AndroidApp writeDex(DexApplication application, InternalOptions options)
+ throws DexOverflowException {
AppInfo appInfo = new AppInfo(application);
try {
return R8.writeApplication(
diff --git a/src/test/java/com/android/tools/r8/smali/SwitchRewritingTest.java b/src/test/java/com/android/tools/r8/smali/SwitchRewritingTest.java
index c256920..3750a86 100644
--- a/src/test/java/com/android/tools/r8/smali/SwitchRewritingTest.java
+++ b/src/test/java/com/android/tools/r8/smali/SwitchRewritingTest.java
@@ -34,7 +34,8 @@
public class SwitchRewritingTest extends SmaliTestBase {
private boolean twoCaseWillUsePackedSwitch(int key1, int key2) {
- return Math.abs((long) key1 - (long) key2) <= 2;
+ assert key1 != key2;
+ return Math.abs((long) key1 - (long) key2) == 1;
}
private boolean some16BitConst(Instruction instruction) {
@@ -114,7 +115,7 @@
}
}
- private void runTwoCaseSparseToPackedDexTest(int key1, int key2) {
+ private void runTwoCaseSparseToPackedOrIfsDexTest(int key1, int key2) {
SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
MethodSignature signature = builder.addStaticMethod(
@@ -156,24 +157,29 @@
if (twoCaseWillUsePackedSwitch(key1, key2)) {
assertTrue(code.instructions[0] instanceof PackedSwitch);
} else {
- assertTrue(code.instructions[0] instanceof SparseSwitch);
+ if (key1 == 0) {
+ assertTrue(code.instructions[0] instanceof IfEqz);
+ } else {
+ // Const instruction before if.
+ assertTrue(code.instructions[1] instanceof IfEq);
+ }
}
}
@Test
- public void twoCaseSparseToPackedDex() {
+ public void twoCaseSparseToPackedOrIfsDex() {
for (int delta = 1; delta <= 3; delta++) {
- runTwoCaseSparseToPackedDexTest(0, delta);
- runTwoCaseSparseToPackedDexTest(-delta, 0);
- runTwoCaseSparseToPackedDexTest(Integer.MIN_VALUE, Integer.MIN_VALUE + delta);
- runTwoCaseSparseToPackedDexTest(Integer.MAX_VALUE - delta, Integer.MAX_VALUE);
+ runTwoCaseSparseToPackedOrIfsDexTest(0, delta);
+ runTwoCaseSparseToPackedOrIfsDexTest(-delta, 0);
+ runTwoCaseSparseToPackedOrIfsDexTest(Integer.MIN_VALUE, Integer.MIN_VALUE + delta);
+ runTwoCaseSparseToPackedOrIfsDexTest(Integer.MAX_VALUE - delta, Integer.MAX_VALUE);
}
- runTwoCaseSparseToPackedDexTest(-1, 1);
- runTwoCaseSparseToPackedDexTest(-2, 1);
- runTwoCaseSparseToPackedDexTest(-1, 2);
- runTwoCaseSparseToPackedDexTest(Integer.MIN_VALUE, Integer.MAX_VALUE);
- runTwoCaseSparseToPackedDexTest(Integer.MIN_VALUE + 1, Integer.MAX_VALUE);
- runTwoCaseSparseToPackedDexTest(Integer.MIN_VALUE, Integer.MAX_VALUE - 1);
+ runTwoCaseSparseToPackedOrIfsDexTest(-1, 1);
+ runTwoCaseSparseToPackedOrIfsDexTest(-2, 1);
+ runTwoCaseSparseToPackedOrIfsDexTest(-1, 2);
+ runTwoCaseSparseToPackedOrIfsDexTest(Integer.MIN_VALUE, Integer.MAX_VALUE);
+ runTwoCaseSparseToPackedOrIfsDexTest(Integer.MIN_VALUE + 1, Integer.MAX_VALUE);
+ runTwoCaseSparseToPackedOrIfsDexTest(Integer.MIN_VALUE, Integer.MAX_VALUE - 1);
}
private void runLargerSwitchDexTest(int firstKey, int keyStep, int totalKeys,
@@ -356,7 +362,12 @@
if (twoCaseWillUsePackedSwitch(key1, key2)) {
assertTrue(code.instructions[3] instanceof PackedSwitch);
} else {
- assertTrue(code.instructions[3] instanceof SparseSwitch);
+ if (key1 == 0) {
+ assertTrue(code.instructions[3] instanceof IfEqz);
+ } else {
+ // Const instruction before if.
+ assertTrue(code.instructions[4] instanceof IfEq);
+ }
}
}
@@ -462,8 +473,8 @@
runLargerSwitchJarTest(0, 1, 5503, null);
}
- private void runConvertCasesToIf(List<Integer> keys, int defaultValue, int expectedIfs)
- throws Exception {
+ private void runConvertCasesToIf(List<Integer> keys, int defaultValue, int expectedIfs,
+ int expectedPackedSwitches, int expectedSparceSwitches) throws Exception {
JasminBuilder builder = new JasminBuilder();
JasminBuilder.ClassBuilder clazz = builder.addClass("Test");
@@ -514,8 +525,8 @@
}
}
- assertEquals(1, packedSwitches);
- assertEquals(0, sparseSwitches);
+ assertEquals(expectedPackedSwitches, packedSwitches);
+ assertEquals(expectedSparceSwitches, sparseSwitches);
assertEquals(expectedIfs, ifs);
// Run the code
@@ -526,12 +537,34 @@
@Test
public void convertCasesToIf() throws Exception {
- runConvertCasesToIf(ImmutableList.of(0, 1000, 1001, 1002, 1003, 1004), -100, 1);
- runConvertCasesToIf(ImmutableList.of(1000, 1001, 1002, 1003, 1004, 2000), -100, 1);
- runConvertCasesToIf(ImmutableList.of(Integer.MIN_VALUE, 1000, 1001, 1002, 1003, 1004), -100, 1);
- runConvertCasesToIf(ImmutableList.of(1000, 1001, 1002, 1003, 1004, Integer.MAX_VALUE), -100, 1);
- runConvertCasesToIf(ImmutableList.of(0, 1000, 1001, 1002, 1003, 1004, 2000), -100, 2);
+ // Switches that are completely converted to ifs.
+ runConvertCasesToIf(ImmutableList.of(0, 1000), -100, 2, 0, 0);
+ runConvertCasesToIf(ImmutableList.of(0, 1000, 2000), -100, 3, 0, 0);
+ runConvertCasesToIf(ImmutableList.of(0, 1000, 2000, 3000), -100, 4, 0, 0);
+
+ // Switches that are completely converted to ifs and one switch.
+ runConvertCasesToIf(ImmutableList.of(0, 1000, 1001, 1002, 1003, 1004), -100, 1, 1, 0);
+ runConvertCasesToIf(ImmutableList.of(1000, 1001, 1002, 1003, 1004, 2000), -100, 1, 1, 0);
runConvertCasesToIf(ImmutableList.of(
- Integer.MIN_VALUE, 1000, 1001, 1002, 1003, 1004, Integer.MAX_VALUE), -100, 2);
+ Integer.MIN_VALUE, 1000, 1001, 1002, 1003, 1004), -100, 1, 1, 0);
+ runConvertCasesToIf(ImmutableList.of(
+ 1000, 1001, 1002, 1003, 1004, Integer.MAX_VALUE), -100, 1, 1, 0);
+ runConvertCasesToIf(ImmutableList.of(0, 1000, 1001, 1002, 1003, 1004, 2000), -100, 2, 1, 0);
+ runConvertCasesToIf(ImmutableList.of(
+ Integer.MIN_VALUE, 1000, 1001, 1002, 1003, 1004, Integer.MAX_VALUE), -100, 2, 1, 0);
+
+ // Switches that are completely converted to ifs and two switches.
+ runConvertCasesToIf(ImmutableList.of(
+ 0, 1, 2, 3, 4, 1000, 1001, 1002, 1003, 1004), -100, 0, 2, 0);
+ runConvertCasesToIf(ImmutableList.of(
+ -1000, 0, 1, 2, 3, 4, 1000, 1001, 1002, 1003, 1004), -100, 1, 2, 0);
+ runConvertCasesToIf(ImmutableList.of(
+ -1000, 0, 1, 2, 3, 4, 1000, 1001, 1002, 1003, 1004, 2000), -100, 2, 2, 0);
+
+ // Switches that are completely converted two switches (one sparse and one packed).
+ runConvertCasesToIf(ImmutableList.of(
+ -1000, -900, -800, -700, -600, -500, -400, -300,
+ 1000, 1001, 1002, 1003, 1004,
+ 2000, 2100, 2200, 2300, 2400, 2500), -100, 0, 1, 1);
}
}
\ No newline at end of file
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 0ad240c..26de96c 100644
--- a/src/test/java/com/android/tools/r8/utils/DexInspector.java
+++ b/src/test/java/com/android/tools/r8/utils/DexInspector.java
@@ -56,6 +56,7 @@
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.DexValue;
import com.android.tools.r8.naming.ClassNameMapper;
import com.android.tools.r8.naming.ClassNaming;
import com.android.tools.r8.naming.MemberNaming;
@@ -691,8 +692,11 @@
}
public abstract class FieldSubject extends MemberSubject {
+ public abstract boolean hasStaticValue();
public abstract DexEncodedField getField();
+
+ public abstract DexValue getStaticValue();
}
public class AbsentFieldSubject extends FieldSubject {
@@ -733,6 +737,16 @@
}
@Override
+ public boolean hasStaticValue() {
+ return false;
+ }
+
+ @Override
+ public DexValue getStaticValue() {
+ return null;
+ }
+
+ @Override
public DexEncodedField getField() {
return null;
}
@@ -791,6 +805,16 @@
}
@Override
+ public boolean hasStaticValue() {
+ return dexField.staticValue != null;
+ }
+
+ @Override
+ public DexValue getStaticValue() {
+ return dexField.staticValue;
+ }
+
+ @Override
public DexEncodedField getField() {
return dexField;
}
diff --git a/src/test/java/com/android/tools/r8/utils/GenerateMainDexListCommandTest.java b/src/test/java/com/android/tools/r8/utils/GenerateMainDexListCommandTest.java
new file mode 100644
index 0000000..64e7a5b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/GenerateMainDexListCommandTest.java
@@ -0,0 +1,144 @@
+// 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 com.android.tools.r8.utils.FileUtils.JAR_EXTENSION;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.GenerateMainDexListCommand;
+import com.android.tools.r8.ToolHelper;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.PrintWriter;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.StandardOpenOption;
+import java.util.ArrayList;
+import java.util.List;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.rules.TemporaryFolder;
+
+public class GenerateMainDexListCommandTest {
+
+ @Rule
+ public ExpectedException thrown = ExpectedException.none();
+
+ @Rule
+ public TemporaryFolder temp = ToolHelper.getTemporaryFolderForTest();
+
+ @Test
+ public void emptyCommand() throws Throwable {
+ verifyEmptyCommand(GenerateMainDexListCommand.builder().build());
+ verifyEmptyCommand(parse());
+ verifyEmptyCommand(parse(""));
+ verifyEmptyCommand(parse("", ""));
+ verifyEmptyCommand(parse(" "));
+ verifyEmptyCommand(parse(" ", " "));
+ verifyEmptyCommand(parse("\t"));
+ verifyEmptyCommand(parse("\t", "\t"));
+ }
+
+ private void verifyEmptyCommand(GenerateMainDexListCommand command) throws IOException {
+ assertEquals(0, ToolHelper.getApp(command).getDexProgramResources().size());
+ assertEquals(0, ToolHelper.getApp(command).getClassProgramResources().size());
+ assertFalse(ToolHelper.getApp(command).hasMainDexListResources());
+ }
+
+ // Add the jars used in the com.android.tools.r8.maindexlist.MainDexTracingTest test.
+ private void addInputJarsToCommandLine(List<String> args) {
+ args.add(Paths.get(ToolHelper.EXAMPLES_BUILD_DIR, "multidex001" + JAR_EXTENSION)
+ .toAbsolutePath().toString());
+ args.add(Paths.get(ToolHelper.EXAMPLES_BUILD_DIR, "multidexfakeframeworks" + JAR_EXTENSION)
+ .toAbsolutePath().toString());
+ }
+
+ // Add main-dex rules used in the com.android.tools.r8.maindexlist.MainDexTracingTest test.
+ private void addMainDexRuleToCommandLine(List<String> args) {
+ args.add("--main-dex-rules");
+ args.add(Paths.get(ToolHelper.EXAMPLES_DIR, "multidex", "main-dex-rules.txt")
+ .toAbsolutePath().toString());
+ }
+
+ @Test
+ public void defaultOutIsCwd() throws Throwable {
+ Path working = temp.getRoot().toPath();
+ String mainDexListOutput = "main-dex-list.txt";
+ Path output = working.resolve(mainDexListOutput);
+ assertFalse(Files.exists(output));
+ List<String> args = new ArrayList<>();
+ addInputJarsToCommandLine(args);
+ addMainDexRuleToCommandLine(args);
+ assertEquals(0, ToolHelper.forkGenerateMainDexList(
+ working, args, "--main-dex-list-output", mainDexListOutput).exitCode);
+ assertTrue(Files.exists(output));
+ assertTrue(Files.size(output) > 0);
+ }
+
+ @Test
+ public void validOutputPath() throws Throwable {
+ Path existingFile = temp.getRoot().toPath().resolve("existing_output");
+ try (OutputStream existingFileOut = Files.newOutputStream(existingFile,
+ StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING)) {
+ PrintWriter writer = new PrintWriter(existingFileOut);
+ writer.println("Hello, world!");
+ writer.flush();
+ }
+ Path nonExistingFile = temp.getRoot().toPath().resolve("non_existing_output");
+ assertEquals(
+ existingFile,
+ GenerateMainDexListCommand.builder().setMainDexListOutputPath(existingFile).build()
+ .getMainDexListOutputPath());
+ assertEquals(
+ nonExistingFile,
+ GenerateMainDexListCommand.builder().setMainDexListOutputPath(nonExistingFile).build()
+ .getMainDexListOutputPath());
+ assertEquals(
+ existingFile,
+ parse("--main-dex-list-output", existingFile.toString()).getMainDexListOutputPath());
+ assertEquals(
+ nonExistingFile,
+ parse("--main-dex-list-output", nonExistingFile.toString()).getMainDexListOutputPath());
+ }
+
+ @Test
+ public void nonExistingOutputFileInNonExistingDir() throws Throwable {
+ Path nonExistingFileInNonExistingDir =
+ temp.getRoot().toPath().resolve("a/path/that/does/not/exist");
+ assertEquals(
+ nonExistingFileInNonExistingDir,
+ GenerateMainDexListCommand.builder()
+ .setMainDexListOutputPath(nonExistingFileInNonExistingDir).build()
+ .getMainDexListOutputPath());
+ assertEquals(
+ nonExistingFileInNonExistingDir,
+ parse("--main-dex-list-output",
+ nonExistingFileInNonExistingDir.toString()).getMainDexListOutputPath());
+ }
+
+ @Test
+ public void mainDexRules() throws Throwable {
+ Path mainDexRules1 = temp.newFile("main-dex-1.rules").toPath();
+ Path mainDexRules2 = temp.newFile("main-dex-2.rules").toPath();
+ parse("--main-dex-rules", mainDexRules1.toString());
+ parse(
+ "--main-dex-rules", mainDexRules1.toString(), "--main-dex-rules", mainDexRules2.toString());
+ }
+
+ @Test
+ public void mainDexList() throws Throwable {
+ Path mainDexList1 = temp.newFile("main-dex-list-1.txt").toPath();
+ Path mainDexList2 = temp.newFile("main-dex-list-2.txt").toPath();
+ parse("--main-dex-list", mainDexList1.toString());
+ parse("--main-dex-list", mainDexList1.toString(), "--main-dex-list", mainDexList2.toString());
+ }
+
+ private GenerateMainDexListCommand parse(String... args) throws Throwable {
+ return GenerateMainDexListCommand.parse(args).build();
+ }
+}
diff --git a/third_party/kotlin.tar.gz.sha1 b/third_party/kotlin.tar.gz.sha1
index 942c691..f9d8e24 100644
--- a/third_party/kotlin.tar.gz.sha1
+++ b/third_party/kotlin.tar.gz.sha1
@@ -1 +1 @@
-21b6244cb0a5bca1f1d046d00540a610c02cd714
\ No newline at end of file
+28f3708659a1daa879bc8c707da54e3617a2070f
\ No newline at end of file
diff --git a/tools/archive.py b/tools/archive.py
new file mode 100755
index 0000000..ee1053d
--- /dev/null
+++ b/tools/archive.py
@@ -0,0 +1,47 @@
+#!/usr/bin/env python
+# 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.
+
+import d8
+import os
+import r8
+import sys
+import utils
+
+ARCHIVE_BUCKET = 'r8-releases'
+
+def GetVersion():
+ r8_version = r8.run(['--version'], build = False).strip()
+ d8_version = d8.run(['--version'], build = False).strip()
+ # The version printed is "D8 vVERSION_NUMBER" and "R8 vVERSION_NUMBER"
+ # Sanity check that versions match.
+ if d8_version.split()[1] != r8_version.split()[1]:
+ raise Exception(
+ 'Version mismatch: \n%s\n%s' % (d8_version, r8_version))
+ return d8_version.split()[1]
+
+def GetStorageDestination(storage_prefix, version, file_name):
+ return '%s%s/raw/%s/%s' % (storage_prefix, ARCHIVE_BUCKET, version, file_name)
+
+def GetUploadDestination(version, file_name):
+ return GetStorageDestination('gs://', version, file_name)
+
+def GetUrl(version, file_name):
+ return GetStorageDestination('http://storage.googleapis.com/',
+ version,
+ file_name)
+
+def Main():
+ if not 'BUILDBOT_BUILDERNAME' in os.environ:
+ raise Exception('You are not a bot, don\'t archive builds')
+ version = GetVersion()
+ for jar in [utils.D8_JAR, utils.R8_JAR]:
+ file_name = os.path.basename(jar)
+ destination = GetUploadDestination(version, file_name)
+ print('Uploading %s to %s' % (jar, destination))
+ utils.upload_file_to_cloud_storage(jar, destination)
+ print('File available at: %s' % GetUrl(version, file_name))
+
+if __name__ == '__main__':
+ sys.exit(Main())
diff --git a/tools/d8.py b/tools/d8.py
index d214cf5..5478897 100755
--- a/tools/d8.py
+++ b/tools/d8.py
@@ -9,9 +9,8 @@
import sys
import utils
-D8_JAR = os.path.join(utils.REPO_ROOT, 'build', 'libs', 'd8.jar')
-
-def run(args, build = True, debug = True, profile = False, track_memory_file=None):
+def run(args, build = True, debug = True, profile = False,
+ track_memory_file=None):
if build:
gradle.RunGradle(['D8'])
cmd = []
@@ -22,10 +21,12 @@
cmd.append('-ea')
if profile:
cmd.append('-agentlib:hprof=cpu=samples,interval=1,depth=8')
- cmd.extend(['-jar', D8_JAR])
+ cmd.extend(['-jar', utils.D8_JAR])
cmd.extend(args)
utils.PrintCmd(cmd)
- subprocess.check_call(cmd)
+ result = subprocess.check_output(cmd)
+ print(result)
+ return result
def main():
build = True
diff --git a/tools/r8.py b/tools/r8.py
index b453227e..7de2c94 100755
--- a/tools/r8.py
+++ b/tools/r8.py
@@ -9,9 +9,8 @@
import sys
import utils
-R8_JAR = os.path.join(utils.REPO_ROOT, 'build', 'libs', 'r8.jar')
-
-def run(args, build = True, debug = True, profile = False, track_memory_file=None):
+def run(args, build = True, debug = True, profile = False,
+ track_memory_file=None):
if build:
gradle.RunGradle(['r8'])
cmd = []
@@ -22,10 +21,12 @@
cmd.append('-ea')
if profile:
cmd.append('-agentlib:hprof=cpu=samples,interval=1,depth=8')
- cmd.extend(['-jar', R8_JAR])
+ cmd.extend(['-jar', utils.R8_JAR])
cmd.extend(args)
utils.PrintCmd(cmd)
- subprocess.check_call(cmd)
+ result = subprocess.check_output(cmd)
+ print(result)
+ return result
def main():
build = True
diff --git a/tools/test.py b/tools/test.py
index 2b68c88..c0a1e02 100755
--- a/tools/test.py
+++ b/tools/test.py
@@ -77,7 +77,7 @@
upload_dir = os.path.join(utils.REPO_ROOT, 'build', 'reports', 'tests')
u_dir = uuid.uuid4()
destination = 'gs://%s/%s' % (BUCKET, u_dir)
- utils.upload_html_to_cloud_storage(upload_dir, destination)
+ utils.upload_dir_to_cloud_storage(upload_dir, destination)
url = 'http://storage.googleapis.com/%s/%s/test/index.html' % (BUCKET, u_dir)
print 'Test results available at: %s' % url
print '@@@STEP_LINK@Test failures@%s@@@' % url
diff --git a/tools/test_android_cts.py b/tools/test_android_cts.py
index 3f66302..887e3ae 100755
--- a/tools/test_android_cts.py
+++ b/tools/test_android_cts.py
@@ -39,7 +39,6 @@
'aosp_manifest.xml')
AOSP_HELPER_SH = join(utils.REPO_ROOT, 'scripts', 'aosp_helper.sh')
-D8_JAR = join(utils.REPO_ROOT, 'build/libs/d8.jar')
D8LOGGER_JAR = join(utils.REPO_ROOT, 'build/libs/d8logger.jar')
AOSP_ROOT = join(utils.REPO_ROOT, 'build/aosp')
@@ -133,7 +132,7 @@
counter = 0
if tool_is_d8 or clean_dex:
if not clean_dex:
- d8jar_mtime = os.path.getmtime(D8_JAR)
+ d8jar_mtime = os.path.getmtime(utils.D8_JAR)
dex_files = (chain.from_iterable(glob(join(x[0], '*.dex'))
for x in os.walk(OUT_IMG)))
for f in dex_files:
diff --git a/tools/test_framework.py b/tools/test_framework.py
index 9e5f63f..51c870a 100755
--- a/tools/test_framework.py
+++ b/tools/test_framework.py
@@ -30,7 +30,6 @@
DX_JAR = os.path.join(utils.REPO_ROOT, 'tools', 'linux', 'dx', 'framework',
'dx.jar')
-D8_JAR = os.path.join(utils.REPO_ROOT, 'build', 'libs', 'd8.jar')
GOYT_EXE = os.path.join('third_party', 'goyt',
'goyt_164843480')
FRAMEWORK_JAR = os.path.join('third_party', 'framework',
@@ -57,16 +56,22 @@
' peak resident set size (VmHWM) in bytes.',
default = False,
action = 'store_true')
+ parser.add_argument('--output',
+ help = 'Output directory to keep the generated files')
return parser.parse_args()
def Main():
utils.check_java_version()
args = parse_arguments()
+ output_dir = args.output
with utils.TempDir() as temp_dir:
+ if not output_dir:
+ output_dir = temp_dir
+
if args.tool in ['dx', 'goyt', 'goyt-release']:
- tool_args = ['--dex', '--output=' + temp_dir, '--multi-dex',
+ tool_args = ['--dex', '--output=' + output_dir, '--multi-dex',
'--min-sdk-version=' + MIN_SDK_VERSION]
xmx = None
@@ -79,8 +84,8 @@
tool_file = DX_JAR
xmx = '-Xmx1600m'
else:
- tool_file = D8_JAR
- tool_args = ['--output', temp_dir, '--min-api', MIN_SDK_VERSION]
+ tool_file = utils.D8_JAR
+ tool_args = ['--output', output_dir, '--min-api', MIN_SDK_VERSION]
if args.tool == 'd8-release':
tool_args.append('--release')
xmx = '-Xmx600m'
@@ -89,7 +94,7 @@
track_memory_file = None
if args.print_memoryuse:
- track_memory_file = os.path.join(temp_dir, utils.MEMORY_USE_TMP_FILE)
+ track_memory_file = os.path.join(output_dir, utils.MEMORY_USE_TMP_FILE)
cmd.extend(['tools/track_memory.sh', track_memory_file])
if tool_file.endswith('.jar'):
@@ -108,7 +113,7 @@
print('{}-Total(MemoryUse): {}'
.format(args.name, utils.grep_memoryuse(track_memory_file)))
- dex_files = [f for f in glob(os.path.join(temp_dir, '*.dex'))]
+ dex_files = [f for f in glob(os.path.join(output_dir, '*.dex'))]
code_size = 0
for dex_file in dex_files:
code_size += os.path.getsize(dex_file)
diff --git a/tools/utils.py b/tools/utils.py
index e281ccd..44e608d 100644
--- a/tools/utils.py
+++ b/tools/utils.py
@@ -19,6 +19,8 @@
'dexsegments.jar')
DEX_SEGMENTS_RESULT_PATTERN = re.compile('- ([^:]+): ([0-9]+)')
COMPATDX_JAR = os.path.join(REPO_ROOT, 'build', 'libs', 'compatdx.jar')
+D8_JAR = os.path.join(REPO_ROOT, 'build', 'libs', 'd8.jar')
+R8_JAR = os.path.join(REPO_ROOT, 'build', 'libs', 'r8.jar')
def PrintCmd(s):
if type(s) is list:
@@ -55,13 +57,18 @@
if not os.path.isdir(path):
raise
-def upload_html_to_cloud_storage(directory, destination):
+def upload_dir_to_cloud_storage(directory, destination):
# Upload and make the content encoding right for viewing directly
cmd = ['gsutil.py', 'cp', '-z', 'html', '-a',
'public-read', '-R', directory, destination]
PrintCmd(cmd)
subprocess.check_call(cmd)
+def upload_file_to_cloud_storage(source, destination):
+ cmd = ['gsutil.py', 'cp', '-a', 'public-read', source, destination]
+ PrintCmd(cmd)
+ subprocess.check_call(cmd)
+
class TempDir(object):
def __init__(self, prefix=''):
self._temp_dir = None