Merge "Revert "Loosen desugaring constraints""
diff --git a/build.gradle b/build.gradle
index 63513ce..7362238 100644
--- a/build.gradle
+++ b/build.gradle
@@ -565,6 +565,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/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 38b7acc..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;
@@ -55,7 +55,7 @@
*/
public final class D8 {
- private static final String VERSION = "v0.1.0";
+ private static final String VERSION = "v0.2.0";
private static final int STATUS_ERROR = 1;
private D8() {}
@@ -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
@@ -210,7 +207,7 @@
private static DexApplication optimize(
DexApplication application, AppInfo appInfo, InternalOptions options,
Timing timing, ExecutorService executor)
- throws IOException, ExecutionException {
+ throws IOException, ExecutionException, ApiLevelException {
final CfgPrinter printer = options.printCfg ? new CfgPrinter() : null;
IRConverter converter = new IRConverter(timing, application, appInfo, options, printer);
diff --git a/src/main/java/com/android/tools/r8/DexSegments.java b/src/main/java/com/android/tools/r8/DexSegments.java
index 3827cae..4e4a19b 100644
--- a/src/main/java/com/android/tools/r8/DexSegments.java
+++ b/src/main/java/com/android/tools/r8/DexSegments.java
@@ -5,7 +5,6 @@
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;
@@ -16,7 +15,6 @@
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 {
@@ -99,7 +97,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..a7bfdc9 100644
--- a/src/main/java/com/android/tools/r8/Disassemble.java
+++ b/src/main/java/com/android/tools/r8/Disassemble.java
@@ -3,7 +3,6 @@
// 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;
@@ -133,7 +132,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..0db183f 100644
--- a/src/main/java/com/android/tools/r8/ExtractMarker.java
+++ b/src/main/java/com/android/tools/r8/ExtractMarker.java
@@ -8,7 +8,6 @@
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;
@@ -100,7 +99,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/R8.java b/src/main/java/com/android/tools/r8/R8.java
index f28a07a..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;
@@ -71,7 +71,7 @@
public class R8 {
- private static final String VERSION = "v0.1.0";
+ private static final String VERSION = "v0.2.0";
private final Timing timing = new Timing("R8");
private final InternalOptions options;
@@ -99,7 +99,7 @@
byte[] proguardSeedsData,
PackageDistribution packageDistribution,
InternalOptions options)
- throws ExecutionException {
+ throws ExecutionException, DexOverflowException {
try {
Marker marker = getMarker(options);
return new ApplicationWriter(
@@ -114,12 +114,12 @@
DexApplication application,
AppInfoWithSubtyping appInfo,
InternalOptions options)
- throws ProguardRuleParserException, ExecutionException, IOException {
+ throws ApiLevelException, ExecutionException, IOException {
return new R8(options).optimize(application, appInfo);
}
private DexApplication optimize(DexApplication application, AppInfoWithSubtyping appInfo)
- throws IOException, ProguardRuleParserException, ExecutionException {
+ throws IOException, ApiLevelException, ExecutionException {
return optimize(application, appInfo, GraphLense.getIdentityLense(),
Executors.newSingleThreadExecutor());
}
@@ -129,7 +129,7 @@
AppInfoWithSubtyping appInfo,
GraphLense graphLense,
ExecutorService executorService)
- throws IOException, ProguardRuleParserException, ExecutionException {
+ throws IOException, ApiLevelException, ExecutionException {
final CfgPrinter printer = options.printCfg ? new CfgPrinter() : null;
timing.begin("Create IR");
@@ -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);
@@ -270,9 +271,10 @@
proguardSeedsData = bytes.toByteArray();
}
if (options.useTreeShaking) {
- application = new TreePruner(application, appInfo.withLiveness(), options).run();
+ TreePruner pruner = new TreePruner(application, appInfo.withLiveness(), options);
+ application = pruner.run();
// Recompute the subtyping information.
- appInfo = appInfo.withLiveness().prunedCopyFrom(application);
+ appInfo = appInfo.withLiveness().prunedCopyFrom(application, pruner.getRemovedClasses());
new AbstractMethodRemover(appInfo).run();
new AnnotationRemover(appInfo.withLiveness(), options).run();
}
@@ -295,11 +297,13 @@
// Class merging requires inlining.
if (!options.skipClassMerging && options.inlineAccessors) {
timing.begin("ClassMerger");
- graphLense = new SimpleClassMerger(application, appInfo.withLiveness(), graphLense,
- timing).run();
+ SimpleClassMerger classMerger = new SimpleClassMerger(application,
+ appInfo.withLiveness(), graphLense, timing);
+ graphLense = classMerger.run();
timing.end();
+ appInfo = appInfo.withLiveness()
+ .prunedCopyFrom(application, classMerger.getRemovedClasses());
}
- appInfo = appInfo.withLiveness().prunedCopyFrom(application);
appInfo = appInfo.withLiveness().rewrittenWithLense(graphLense);
// Collect switch maps and ordinals maps.
new SwitchMapCollector(appInfo.withLiveness(), options).run();
@@ -333,8 +337,10 @@
Enqueuer enqueuer = new Enqueuer(appInfo);
appInfo = enqueuer.traceApplication(rootSet, timing);
if (options.useTreeShaking) {
- application = new TreePruner(application, appInfo.withLiveness(), options).run();
- appInfo = appInfo.withLiveness().prunedCopyFrom(application);
+ TreePruner pruner = new TreePruner(application, appInfo.withLiveness(), options);
+ application = pruner.run();
+ appInfo = appInfo.withLiveness()
+ .prunedCopyFrom(application, pruner.getRemovedClasses());
// Print reasons on the application after pruning, so that we reflect the actual result.
ReasonPrinter reasonPrinter = enqueuer.getReasonPrinter(rootSet.reasonAsked);
reasonPrinter.run(application);
@@ -345,7 +351,8 @@
}
// Only perform discard-checking if tree-shaking is turned on.
- if (options.useTreeShaking && !rootSet.checkDiscarded.isEmpty()) {
+ if (options.useTreeShaking && !rootSet.checkDiscarded.isEmpty()
+ && options.useDiscardedChecker) {
new DiscardedChecker(rootSet, application).run();
}
@@ -386,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
@@ -439,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);
@@ -510,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;
@@ -556,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 0959167..318b3e6 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,7 +20,6 @@
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;
@@ -26,11 +28,12 @@
public static class Builder extends BaseCommand.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();
private boolean ignoreMissingClasses = false;
private Path packageDistributionFile = null;
@@ -57,6 +60,14 @@
}
/**
+ * Enable/disable discareded checker.
+ */
+ public Builder setDiscardedChecker(boolean useDiscardedChecker) {
+ discardedChecker = Optional.of(useDiscardedChecker);
+ return self();
+ }
+
+ /**
* Enable/disable minification. This overrides any settings in proguard configuration files.
*/
public Builder setMinification(boolean useMinification) {
@@ -67,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 file resources for automatic main dex list calculation.
+ */
+ public Builder addMainDexRules(List<String> lines) {
+ mainDexRules.add(new ProguardConfigurationSourceStrings(lines));
return self();
}
@@ -89,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();
}
@@ -97,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();
}
@@ -175,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());
}
@@ -200,6 +235,7 @@
}
boolean useTreeShaking = treeShaking.orElse(configuration.isShrinking());
+ boolean useDiscardedChecker = discardedChecker.orElse(true);
boolean useMinification = minification.orElse(configuration.isObfuscating());
return new R8Command(
@@ -212,6 +248,7 @@
getMode(),
getMinApiLevel(),
useTreeShaking,
+ useDiscardedChecker,
useMinification,
ignoreMissingClasses);
}
@@ -237,6 +274,7 @@
" # shaking/minification).",
" --pg-map <file> # Proguard map <file>.",
" --no-tree-shaking # Force disable tree shaking of unreachable classes.",
+ " --no-discarded-checker # Force disable the discarded checker (when tree shaking).",
" --no-minification # Force disable minification of names.",
" --main-dex-rules <file> # Proguard keep rules for classes to place in the",
" # primary dex file.",
@@ -249,6 +287,7 @@
private final Path mainDexListOutput;
private final ProguardConfiguration proguardConfiguration;
private final boolean useTreeShaking;
+ private final boolean useDiscardedChecker;
private final boolean useMinification;
private final boolean ignoreMissingClasses;
@@ -306,10 +345,12 @@
builder.setMinApiLevel(Integer.valueOf(args[++i]));
} else if (arg.equals("--no-tree-shaking")) {
builder.setTreeShaking(false);
+ } else if (arg.equals("--no-discarded-checker")) {
+ builder.setDiscardedChecker(false);
} 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")) {
@@ -360,6 +401,7 @@
CompilationMode mode,
int minApiLevel,
boolean useTreeShaking,
+ boolean useDiscardedChecker,
boolean useMinification,
boolean ignoreMissingClasses) {
super(inputApp, outputPath, outputMode, mode, minApiLevel);
@@ -370,6 +412,7 @@
this.mainDexListOutput = mainDexListOutput;
this.proguardConfiguration = proguardConfiguration;
this.useTreeShaking = useTreeShaking;
+ this.useDiscardedChecker = useDiscardedChecker;
this.useMinification = useMinification;
this.ignoreMissingClasses = ignoreMissingClasses;
}
@@ -380,6 +423,7 @@
mainDexListOutput = null;
proguardConfiguration = null;
useTreeShaking = false;
+ useDiscardedChecker = false;
useMinification = false;
ignoreMissingClasses = false;
}
@@ -388,6 +432,10 @@
return useTreeShaking;
}
+ public boolean useDiscardedChecker() {
+ return useDiscardedChecker;
+ }
+
public boolean useMinification() {
return useMinification;
}
@@ -402,6 +450,8 @@
internal.skipMinification = !useMinification() || !proguardConfiguration.isObfuscating();
assert internal.useTreeShaking;
internal.useTreeShaking = useTreeShaking();
+ assert internal.useDiscardedChecker;
+ internal.useDiscardedChecker = useDiscardedChecker();
assert !internal.ignoreMissingClasses;
internal.ignoreMissingClasses = ignoreMissingClasses;
for (String pattern : proguardConfiguration.getAttributesRemovalPatterns()) {
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/FilledNewArray.java b/src/main/java/com/android/tools/r8/code/FilledNewArray.java
index 085b6fa..21402a8 100644
--- a/src/main/java/com/android/tools/r8/code/FilledNewArray.java
+++ b/src/main/java/com/android/tools/r8/code/FilledNewArray.java
@@ -3,6 +3,7 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.code;
+import com.android.tools.r8.ApiLevelException;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.OffsetToObjectMapping;
import com.android.tools.r8.ir.conversion.IRBuilder;
@@ -38,7 +39,7 @@
}
@Override
- public void buildIR(IRBuilder builder) {
+ public void buildIR(IRBuilder builder) throws ApiLevelException {
builder.addInvokeNewArray(getType(), A, new int[]{C, D, E, F, G});
}
diff --git a/src/main/java/com/android/tools/r8/code/FilledNewArrayRange.java b/src/main/java/com/android/tools/r8/code/FilledNewArrayRange.java
index 25a941f..cb2bd6f 100644
--- a/src/main/java/com/android/tools/r8/code/FilledNewArrayRange.java
+++ b/src/main/java/com/android/tools/r8/code/FilledNewArrayRange.java
@@ -3,6 +3,7 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.code;
+import com.android.tools.r8.ApiLevelException;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.OffsetToObjectMapping;
import com.android.tools.r8.ir.conversion.IRBuilder;
@@ -38,7 +39,7 @@
}
@Override
- public void buildIR(IRBuilder builder) {
+ public void buildIR(IRBuilder builder) throws ApiLevelException {
builder.addInvokeRangeNewArray(getType(), AA, CCCC);
}
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 a87217b..504b605 100644
--- a/src/main/java/com/android/tools/r8/code/Instruction.java
+++ b/src/main/java/com/android/tools/r8/code/Instruction.java
@@ -3,6 +3,7 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.code;
+import com.android.tools.r8.ApiLevelException;
import com.android.tools.r8.dex.IndexedItemCollection;
import com.android.tools.r8.errors.InternalCompilerError;
import com.android.tools.r8.graph.DexCallSite;
@@ -179,7 +180,7 @@
return NO_TARGETS;
}
- public abstract void buildIR(IRBuilder builder);
+ public abstract void buildIR(IRBuilder builder) throws ApiLevelException;
public DexCallSite getCallSite() {
return null;
@@ -227,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/code/InvokeDirect.java b/src/main/java/com/android/tools/r8/code/InvokeDirect.java
index 58fb8c1..e52b1ed 100644
--- a/src/main/java/com/android/tools/r8/code/InvokeDirect.java
+++ b/src/main/java/com/android/tools/r8/code/InvokeDirect.java
@@ -3,6 +3,7 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.code;
+import com.android.tools.r8.ApiLevelException;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.IndexedDexItem;
import com.android.tools.r8.graph.OffsetToObjectMapping;
@@ -47,7 +48,7 @@
}
@Override
- public void buildIR(IRBuilder builder) {
+ public void buildIR(IRBuilder builder) throws ApiLevelException {
builder.addInvokeRegisters(Type.DIRECT, getMethod(), getProto(), A, new int[]{C, D, E, F, G});
}
diff --git a/src/main/java/com/android/tools/r8/code/InvokeDirectRange.java b/src/main/java/com/android/tools/r8/code/InvokeDirectRange.java
index 742546d..f7f0c93 100644
--- a/src/main/java/com/android/tools/r8/code/InvokeDirectRange.java
+++ b/src/main/java/com/android/tools/r8/code/InvokeDirectRange.java
@@ -3,6 +3,7 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.code;
+import com.android.tools.r8.ApiLevelException;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.OffsetToObjectMapping;
import com.android.tools.r8.graph.UseRegistry;
@@ -45,7 +46,7 @@
}
@Override
- public void buildIR(IRBuilder builder) {
+ public void buildIR(IRBuilder builder) throws ApiLevelException {
builder.addInvokeRange(Type.DIRECT, getMethod(), getProto(), AA, CCCC);
}
diff --git a/src/main/java/com/android/tools/r8/code/InvokeInterface.java b/src/main/java/com/android/tools/r8/code/InvokeInterface.java
index 512f6bc..e8cff27 100644
--- a/src/main/java/com/android/tools/r8/code/InvokeInterface.java
+++ b/src/main/java/com/android/tools/r8/code/InvokeInterface.java
@@ -3,6 +3,7 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.code;
+import com.android.tools.r8.ApiLevelException;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.IndexedDexItem;
import com.android.tools.r8.graph.OffsetToObjectMapping;
@@ -47,7 +48,7 @@
}
@Override
- public void buildIR(IRBuilder builder) {
+ public void buildIR(IRBuilder builder) throws ApiLevelException {
builder.addInvokeRegisters(
Type.INTERFACE, getMethod(), getProto(), A, new int[] {C, D, E, F, G});
}
diff --git a/src/main/java/com/android/tools/r8/code/InvokeInterfaceRange.java b/src/main/java/com/android/tools/r8/code/InvokeInterfaceRange.java
index 06eb69e..b40e177 100644
--- a/src/main/java/com/android/tools/r8/code/InvokeInterfaceRange.java
+++ b/src/main/java/com/android/tools/r8/code/InvokeInterfaceRange.java
@@ -3,6 +3,7 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.code;
+import com.android.tools.r8.ApiLevelException;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.OffsetToObjectMapping;
import com.android.tools.r8.graph.UseRegistry;
@@ -46,7 +47,7 @@
}
@Override
- public void buildIR(IRBuilder builder) {
+ public void buildIR(IRBuilder builder) throws ApiLevelException {
builder.addInvokeRange(Type.INTERFACE, getMethod(), getProto(), AA, CCCC);
}
diff --git a/src/main/java/com/android/tools/r8/code/InvokePolymorphic.java b/src/main/java/com/android/tools/r8/code/InvokePolymorphic.java
index d5ecd11..891dcca 100644
--- a/src/main/java/com/android/tools/r8/code/InvokePolymorphic.java
+++ b/src/main/java/com/android/tools/r8/code/InvokePolymorphic.java
@@ -3,6 +3,7 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.code;
+import com.android.tools.r8.ApiLevelException;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexProto;
import com.android.tools.r8.graph.OffsetToObjectMapping;
@@ -29,7 +30,7 @@
}
@Override
- public void buildIR(IRBuilder builder) {
+ public void buildIR(IRBuilder builder) throws ApiLevelException {
builder.addInvokeRegisters(
Type.POLYMORPHIC, getMethod(), getProto(), A, new int[] {C, D, E, F, G});
}
diff --git a/src/main/java/com/android/tools/r8/code/InvokePolymorphicRange.java b/src/main/java/com/android/tools/r8/code/InvokePolymorphicRange.java
index e6a16f5..4068d80 100644
--- a/src/main/java/com/android/tools/r8/code/InvokePolymorphicRange.java
+++ b/src/main/java/com/android/tools/r8/code/InvokePolymorphicRange.java
@@ -3,6 +3,7 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.code;
+import com.android.tools.r8.ApiLevelException;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexProto;
import com.android.tools.r8.graph.OffsetToObjectMapping;
@@ -48,7 +49,7 @@
}
@Override
- public void buildIR(IRBuilder builder) {
+ public void buildIR(IRBuilder builder) throws ApiLevelException {
builder.addInvokeRange(Type.POLYMORPHIC, getMethod(), getProto(), AA, CCCC);
}
diff --git a/src/main/java/com/android/tools/r8/code/InvokeStatic.java b/src/main/java/com/android/tools/r8/code/InvokeStatic.java
index 8fbf95c..27c3255 100644
--- a/src/main/java/com/android/tools/r8/code/InvokeStatic.java
+++ b/src/main/java/com/android/tools/r8/code/InvokeStatic.java
@@ -3,6 +3,7 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.code;
+import com.android.tools.r8.ApiLevelException;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.OffsetToObjectMapping;
import com.android.tools.r8.graph.UseRegistry;
@@ -46,7 +47,7 @@
}
@Override
- public void buildIR(IRBuilder builder) {
+ public void buildIR(IRBuilder builder) throws ApiLevelException {
builder.addInvokeRegisters(Type.STATIC, getMethod(), getProto(), A, new int[]{C, D, E, F, G});
}
diff --git a/src/main/java/com/android/tools/r8/code/InvokeStaticRange.java b/src/main/java/com/android/tools/r8/code/InvokeStaticRange.java
index 92a4047..69394b3 100644
--- a/src/main/java/com/android/tools/r8/code/InvokeStaticRange.java
+++ b/src/main/java/com/android/tools/r8/code/InvokeStaticRange.java
@@ -3,6 +3,7 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.code;
+import com.android.tools.r8.ApiLevelException;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.OffsetToObjectMapping;
import com.android.tools.r8.graph.UseRegistry;
@@ -46,7 +47,7 @@
}
@Override
- public void buildIR(IRBuilder builder) {
+ public void buildIR(IRBuilder builder) throws ApiLevelException {
builder.addInvokeRange(Type.STATIC, getMethod(), getProto(), AA, CCCC);
}
diff --git a/src/main/java/com/android/tools/r8/code/InvokeSuper.java b/src/main/java/com/android/tools/r8/code/InvokeSuper.java
index 7a6e32a..0382872 100644
--- a/src/main/java/com/android/tools/r8/code/InvokeSuper.java
+++ b/src/main/java/com/android/tools/r8/code/InvokeSuper.java
@@ -3,6 +3,7 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.code;
+import com.android.tools.r8.ApiLevelException;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.IndexedDexItem;
import com.android.tools.r8.graph.OffsetToObjectMapping;
@@ -47,7 +48,7 @@
}
@Override
- public void buildIR(IRBuilder builder) {
+ public void buildIR(IRBuilder builder) throws ApiLevelException {
builder.addInvokeRegisters(Type.SUPER, getMethod(), getProto(), A, new int[]{C, D, E, F, G});
}
diff --git a/src/main/java/com/android/tools/r8/code/InvokeSuperRange.java b/src/main/java/com/android/tools/r8/code/InvokeSuperRange.java
index 9ee05b7..e12bccb 100644
--- a/src/main/java/com/android/tools/r8/code/InvokeSuperRange.java
+++ b/src/main/java/com/android/tools/r8/code/InvokeSuperRange.java
@@ -3,6 +3,7 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.code;
+import com.android.tools.r8.ApiLevelException;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.OffsetToObjectMapping;
import com.android.tools.r8.graph.UseRegistry;
@@ -46,7 +47,7 @@
}
@Override
- public void buildIR(IRBuilder builder) {
+ public void buildIR(IRBuilder builder) throws ApiLevelException {
builder.addInvokeRange(Type.SUPER, getMethod(), getProto(), AA, CCCC);
}
diff --git a/src/main/java/com/android/tools/r8/code/InvokeVirtual.java b/src/main/java/com/android/tools/r8/code/InvokeVirtual.java
index 1fbf9d9..4de66f6 100644
--- a/src/main/java/com/android/tools/r8/code/InvokeVirtual.java
+++ b/src/main/java/com/android/tools/r8/code/InvokeVirtual.java
@@ -3,6 +3,7 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.code;
+import com.android.tools.r8.ApiLevelException;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.OffsetToObjectMapping;
import com.android.tools.r8.graph.UseRegistry;
@@ -46,7 +47,7 @@
}
@Override
- public void buildIR(IRBuilder builder) {
+ public void buildIR(IRBuilder builder) throws ApiLevelException {
builder.addInvokeRegisters(Type.VIRTUAL, getMethod(), getProto(), A, new int[]{C, D, E, F, G});
}
diff --git a/src/main/java/com/android/tools/r8/code/InvokeVirtualRange.java b/src/main/java/com/android/tools/r8/code/InvokeVirtualRange.java
index c5c8249..fbacf0b 100644
--- a/src/main/java/com/android/tools/r8/code/InvokeVirtualRange.java
+++ b/src/main/java/com/android/tools/r8/code/InvokeVirtualRange.java
@@ -3,6 +3,7 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.code;
+import com.android.tools.r8.ApiLevelException;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.OffsetToObjectMapping;
import com.android.tools.r8.graph.UseRegistry;
@@ -46,7 +47,7 @@
}
@Override
- public void buildIR(IRBuilder builder) {
+ public void buildIR(IRBuilder builder) throws ApiLevelException {
builder.addInvokeRange(Type.VIRTUAL, getMethod(), getProto(), AA, CCCC);
}
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 d47c57d..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,8 +47,10 @@
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;
import com.google.common.collect.Sets;
import it.unimi.dsi.fastutil.objects.Object2IntLinkedOpenHashMap;
import it.unimi.dsi.fastutil.objects.Object2IntMap;
@@ -295,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());
}
@@ -309,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());
}
@@ -321,8 +321,7 @@
return;
}
throw new ApiLevelException(
- Constants.ANDROID_N_API,
- "Android N",
+ AndroidApiLevel.N,
"Private interface methods",
method.method.toSourceString());
}
@@ -365,21 +364,13 @@
}
private <T extends DexItem> void writeFixedSectionItems(T[] items, int offset,
- ItemWriter<T> writer) throws ApiLevelException {
+ ThrowingConsumer<T, ApiLevelException> writer) throws ApiLevelException {
assert dest.position() == offset;
for (T item : items) {
writer.accept(item);
}
}
- /**
- * Similar to a {@link Consumer} but throws an {@link ApiLevelException}.
- */
- @FunctionalInterface
- private interface ItemWriter<T> {
- void accept(T t) throws ApiLevelException;
- }
-
private <T extends DexItem> void writeItems(Collection<T> items, Consumer<Integer> offsetSetter,
Consumer<T> writer) {
writeItems(items, offsetSetter, writer, 1);
@@ -452,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));
@@ -463,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));
@@ -701,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
}
@@ -1359,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/Code.java b/src/main/java/com/android/tools/r8/graph/Code.java
index 3845507..0ae2cbc 100644
--- a/src/main/java/com/android/tools/r8/graph/Code.java
+++ b/src/main/java/com/android/tools/r8/graph/Code.java
@@ -3,6 +3,7 @@
// 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.dex.IndexedItemCollection;
import com.android.tools.r8.dex.MixedSectionCollection;
import com.android.tools.r8.errors.Unreachable;
@@ -13,7 +14,8 @@
public abstract class Code extends CachedHashValueDexItem {
- public abstract IRCode buildIR(DexEncodedMethod encodedMethod, InternalOptions options);
+ public abstract IRCode buildIR(DexEncodedMethod encodedMethod, InternalOptions options)
+ throws ApiLevelException;
public abstract void registerReachableDefinitions(UseRegistry registry);
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 3f492b5..50c22b8 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClass.java
@@ -7,6 +7,7 @@
import com.android.tools.r8.dex.MixedSectionCollection;
import com.android.tools.r8.errors.CompilationError;
import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.utils.ThrowingConsumer;
import com.google.common.base.MoreObjects;
import java.util.Arrays;
import java.util.function.Consumer;
@@ -91,6 +92,16 @@
}
}
+ public <E extends Throwable> void forEachMethodThrowing(
+ ThrowingConsumer<DexEncodedMethod, E> consumer) throws E {
+ for (DexEncodedMethod method : directMethods()) {
+ consumer.accept(method);
+ }
+ for (DexEncodedMethod method : virtualMethods()) {
+ consumer.accept(method);
+ }
+ }
+
public DexEncodedMethod[] allMethodsSorted() {
int vLen = virtualMethods().length;
int dLen = directMethods().length;
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 f62dda0..8da2178 100644
--- a/src/main/java/com/android/tools/r8/graph/DexCode.java
+++ b/src/main/java/com/android/tools/r8/graph/DexCode.java
@@ -3,6 +3,7 @@
// 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.code.Instruction;
import com.android.tools.r8.code.ReturnVoid;
import com.android.tools.r8.code.SwitchPayload;
@@ -81,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;
@@ -142,12 +154,13 @@
return false;
}
- boolean isEmptyVoidMethod() {
+ public boolean isEmptyVoidMethod() {
return instructions.length == 1 && instructions[0] instanceof ReturnVoid;
}
@Override
- public IRCode buildIR(DexEncodedMethod encodedMethod, InternalOptions options) {
+ public IRCode buildIR(DexEncodedMethod encodedMethod, InternalOptions options)
+ throws ApiLevelException {
DexSourceCode source = new DexSourceCode(this, encodedMethod);
IRBuilder builder = new IRBuilder(encodedMethod, source, options);
return builder.build();
@@ -156,7 +169,8 @@
public IRCode buildIR(
DexEncodedMethod encodedMethod,
ValueNumberGenerator valueNumberGenerator,
- InternalOptions options) {
+ InternalOptions options)
+ throws ApiLevelException {
DexSourceCode source = new DexSourceCode(this, encodedMethod);
IRBuilder builder = new IRBuilder(encodedMethod, source, valueNumberGenerator, options);
return builder.build();
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 e66b984..b76177b 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -9,6 +9,7 @@
import static com.android.tools.r8.graph.DexEncodedMethod.CompilationState.PROCESSED_INLINING_CANDIDATE_SUBCLASS;
import static com.android.tools.r8.graph.DexEncodedMethod.CompilationState.PROCESSED_NOT_INLINING_CANDIDATE;
+import com.android.tools.r8.ApiLevelException;
import com.android.tools.r8.code.Const;
import com.android.tools.r8.code.ConstString;
import com.android.tools.r8.code.ConstStringJumbo;
@@ -25,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;
@@ -106,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) {
@@ -155,11 +157,12 @@
compilationState = CompilationState.NOT_PROCESSED;
}
- public IRCode buildIR(InternalOptions options) {
+ public IRCode buildIR(InternalOptions options) throws ApiLevelException {
return code == null ? null : code.buildIR(this, options);
}
- public IRCode buildIR(ValueNumberGenerator valueNumberGenerator, InternalOptions options) {
+ public IRCode buildIR(ValueNumberGenerator valueNumberGenerator, InternalOptions options)
+ throws ApiLevelException {
return code == null
? null
: code.asDexCode().buildIR(this, valueNumberGenerator, options);
@@ -206,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/graph/JarCode.java b/src/main/java/com/android/tools/r8/graph/JarCode.java
index 6b9c06d..1dc5964 100644
--- a/src/main/java/com/android/tools/r8/graph/JarCode.java
+++ b/src/main/java/com/android/tools/r8/graph/JarCode.java
@@ -3,6 +3,7 @@
// 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.errors.InvalidDebugInfoException;
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.code.ValueNumberGenerator;
@@ -80,7 +81,8 @@
}
@Override
- public IRCode buildIR(DexEncodedMethod encodedMethod, InternalOptions options) {
+ public IRCode buildIR(DexEncodedMethod encodedMethod, InternalOptions options)
+ throws ApiLevelException {
triggerDelayedParsingIfNeccessary();
return options.debug
? internalBuildWithLocals(encodedMethod, null, options)
@@ -88,7 +90,8 @@
}
public IRCode buildIR(
- DexEncodedMethod encodedMethod, ValueNumberGenerator generator, InternalOptions options) {
+ DexEncodedMethod encodedMethod, ValueNumberGenerator generator, InternalOptions options)
+ throws ApiLevelException {
assert generator != null;
triggerDelayedParsingIfNeccessary();
return options.debug
@@ -97,7 +100,8 @@
}
private IRCode internalBuildWithLocals(
- DexEncodedMethod encodedMethod, ValueNumberGenerator generator, InternalOptions options) {
+ DexEncodedMethod encodedMethod, ValueNumberGenerator generator, InternalOptions options)
+ throws ApiLevelException {
try {
return internalBuild(encodedMethod, generator, options);
} catch (InvalidDebugInfoException e) {
@@ -108,7 +112,8 @@
}
private IRCode internalBuild(
- DexEncodedMethod encodedMethod, ValueNumberGenerator generator, InternalOptions options) {
+ DexEncodedMethod encodedMethod, ValueNumberGenerator generator, InternalOptions options)
+ throws ApiLevelException {
if (!options.debug) {
node.localVariables.clear();
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/Argument.java b/src/main/java/com/android/tools/r8/ir/code/Argument.java
index c7b55d5..fbf6658 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Argument.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Argument.java
@@ -17,7 +17,7 @@
public Argument(Value outValue) {
super(outValue);
- outValue.markAsArgument();;
+ outValue.markAsArgument();
}
@Override
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 661a602..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;
}
@@ -77,6 +76,14 @@
return value == 0;
}
+ public boolean isIntegerZero() {
+ return type == ConstType.INT && getIntValue() == 0;
+ }
+
+ public boolean isIntegerOne() {
+ return type == ConstType.INT && getIntValue() == 1;
+ }
+
public boolean isIntegerNegativeOne(NumericType type) {
assert type == NumericType.INT || type == NumericType.LONG;
if (type == NumericType.INT) {
@@ -93,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 13325d8..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
@@ -14,9 +14,11 @@
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.StringUtils;
import com.android.tools.r8.utils.StringUtils.BraceType;
-import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
import java.util.ArrayList;
+import java.util.HashSet;
import java.util.List;
+import java.util.Set;
public abstract class Instruction {
@@ -24,7 +26,7 @@
protected final List<Value> inValues = new ArrayList<>();
private BasicBlock block = null;
private int number = -1;
- private List<Value> debugValues = null;
+ private Set<Value> debugValues = null;
protected Instruction(Value outValue) {
setOutValue(outValue);
@@ -70,10 +72,11 @@
public void addDebugValue(Value value) {
assert value.getLocalInfo() != null;
if (debugValues == null) {
- debugValues = new ArrayList<>();
+ debugValues = new HashSet<>();
}
- debugValues.add(value);
- value.addDebugUser(this);
+ if (debugValues.add(value)) {
+ value.addDebugUser(this);
+ }
}
public static void clearUserInfo(Instruction instruction) {
@@ -97,34 +100,34 @@
if (oldValue == inValues.get(i)) {
inValues.set(i, newValue);
newValue.addUser(this);
- oldValue.removeUser(this);
}
}
}
- // Similar to Phi::replaceTrivialPhi, removal can cause a concurrent modification error.
- // TODO(ager): Consider unifying with other replace methods and avoid the C.M.E.
- public boolean replaceDebugValue(Value oldValue, Value newValue) {
- if (debugValues == null) {
- return false;
- }
- int found = -1;
- for (int i = 0; i < debugValues.size(); i++) {
- if (oldValue == debugValues.get(i)) {
- assert found == -1;
- found = i;
- if (newValue.getLocalInfo() != null) {
- // TODO(zerny): Insert a write if replacing a phi with different debug-local info.
- debugValues.set(i, newValue);
- newValue.addDebugUser(this);
- }
+ public void replaceDebugValue(Value oldValue, Value newValue) {
+ if (debugValues.remove(oldValue)) {
+ if (newValue.getLocalInfo() != null) {
+ // TODO(zerny): Insert a write if replacing a phi with different debug-local info.
+ addDebugValue(newValue);
}
+ // TODO(zerny): Else: Insert a write if replacing a phi with associated debug-local info.
}
- if (found >= 0 && newValue.getLocalInfo() == null) {
- // TODO(zerny): Insert a write if replacing a phi with associated debug-local info.
- debugValues.remove(found);
+ }
+
+ public void moveDebugValues(Instruction target) {
+ if (debugValues == null) {
+ return;
}
- return found >= 0;
+ 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);
}
/**
@@ -324,8 +327,8 @@
return outValue == null ? null : outValue.getLocalInfo();
}
- public List<Value> getDebugValues() {
- return debugValues != null ? debugValues : ImmutableList.of();
+ public Set<Value> getDebugValues() {
+ return debugValues != null ? debugValues : ImmutableSet.of();
}
public boolean isArrayGet() {
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 185d2fd..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
@@ -10,18 +10,19 @@
import com.android.tools.r8.utils.CfgPrinter;
import com.android.tools.r8.utils.ListUtils;
import com.android.tools.r8.utils.StringUtils;
-import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
+import java.util.Map.Entry;
import java.util.Set;
public class Phi extends Value {
private final BasicBlock block;
private final List<Value> operands = new ArrayList<>();
- private List<Value> debugValues = null;
+ private Set<Value> debugValues = null;
// Trivial phis are eliminated during IR construction. When a trivial phi is eliminated
// we need to update all references to it. A phi can be referenced from phis, instructions
@@ -101,7 +102,7 @@
public void addDebugValue(Value value) {
assert value.getLocalInfo() != null;
if (debugValues == null) {
- debugValues = new ArrayList<>();
+ debugValues = new HashSet<>();
}
debugValues.add(value);
value.addDebugPhiUser(this);
@@ -154,12 +155,7 @@
current.removePhiUser(this);
}
- // Removing the phi user from the current value leads to concurrent modification errors
- // during trivial phi elimination. It is safe to not remove the phi user from current
- // since current will be unreachable after trivial phi elimination.
- // TODO(ager): can we unify the these replace methods and avoid the concurrent modification
- // issue?
- private void replaceTrivialPhi(Value current, Value newValue) {
+ void replaceOperand(Value current, Value newValue) {
for (int i = 0; i < operands.size(); i++) {
if (operands.get(i) == current) {
operands.set(i, newValue);
@@ -168,17 +164,11 @@
}
}
- private void replaceTrivialDebugPhi(Value current, Value newValue) {
- if (debugValues == null) {
- return;
- }
+ void replaceDebugValue(Value current, Value newValue) {
assert current.getLocalInfo() != null;
assert current.getLocalInfo() == newValue.getLocalInfo();
- for (int i = 0; i < debugValues.size(); i++) {
- if (debugValues.get(i) == current) {
- debugValues.set(i, newValue);
- newValue.addDebugPhiUser(this);
- }
+ if (debugValues.remove(current)) {
+ addDebugValue(newValue);
}
}
@@ -213,35 +203,17 @@
same = op;
}
assert isTrivialPhi();
+ assert same != null : "ill-defined phi";
// Removing this phi, so get rid of it as a phi user from all of the operands to avoid
// recursively getting back here with the same phi. If the phi has itself as an operand
// that also removes the self-reference.
for (Value op : operands) {
op.removePhiUser(this);
}
- // Replace this phi with the unique value in all users.
- for (Instruction user : uniqueUsers()) {
- user.replaceValue(this, same);
- }
- for (Phi user : uniquePhiUsers()) {
- user.replaceTrivialPhi(this, same);
- }
- if (debugUsers() != null) {
- List<Instruction> removed = new ArrayList<>();
- for (Instruction user : debugUsers()) {
- if (user.replaceDebugValue(this, same)) {
- removed.add(user);
- }
- }
- removed.forEach(this::removeDebugUser);
- for (Phi user : debugPhiUsers()) {
- user.replaceTrivialDebugPhi(this, same);
- }
- }
// If IR construction is taking place, update the definition users.
if (definitionUsers != null) {
for (Map<Integer, Value> user : definitionUsers) {
- for (Map.Entry<Integer, Value> entry : user.entrySet()) {
+ for (Entry<Integer, Value> entry : user.entrySet()) {
if (entry.getValue() == this) {
entry.setValue(same);
if (same.isPhi()) {
@@ -251,9 +223,14 @@
}
}
}
- // Try to simplify phi users that might now have become trivial.
- for (Phi user : uniquePhiUsers()) {
- user.removeTrivialPhi();
+ {
+ Set<Phi> phiUsersToSimplify = uniquePhiUsers();
+ // Replace this phi with the unique value in all users.
+ replaceUsers(same);
+ // Try to simplify phi users that might now have become trivial.
+ for (Phi user : phiUsersToSimplify) {
+ user.removeTrivialPhi();
+ }
}
// Get rid of the phi itself.
block.removePhi(this);
@@ -296,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) {
@@ -349,7 +326,7 @@
return true;
}
- public List<Value> getDebugValues() {
- return debugValues != null ? debugValues : ImmutableList.of();
+ public Set<Value> getDebugValues() {
+ return debugValues != null ? debugValues : ImmutableSet.of();
}
}
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 78cb4a5..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;
@@ -48,6 +84,7 @@
private boolean neverNull = false;
private boolean isThis = false;
private boolean isArgument = false;
+ private boolean knownToBeBoolean = false;
private LongInterval valueRange;
private final DebugData debugData;
@@ -78,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) {
@@ -151,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() {
@@ -243,7 +308,7 @@
if (isUninitializedLocal()) {
return;
}
- debugData.users.add(user);
+ debugData.users.putIfAbsent(user, DebugUse.LIVE);
}
public void addDebugPhiUser(Phi user) {
@@ -262,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() {
@@ -289,46 +362,30 @@
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()) {
- user.getDebugValues().replaceAll(v -> {
- if (v == this) {
- newValue.addDebugUser(user);
- return newValue;
- }
- return v;
- });
+ user.replaceDebugValue(this, newValue);
}
for (Phi user : debugPhiUsers()) {
- user.getDebugValues().replaceAll(v -> {
- if (v == this) {
- newValue.addDebugPhiUser(user);
- return newValue;
- }
- return v;
- });
+ user.replaceDebugValue(this, newValue);
}
}
clearUsers();
}
+ public void replaceDebugUser(Instruction oldUser, Instruction newUser) {
+ DebugUse use = debugData.users.remove(oldUser);
+ if (use != null) {
+ newUser.addDebugValue(this);
+ debugData.users.put(newUser, use);
+ }
+ }
+
public void setLiveIntervals(LiveIntervals intervals) {
assert liveIntervals == null;
liveIntervals = intervals;
@@ -469,6 +526,14 @@
return isArgument;
}
+ public void setKnownToBeBoolean(boolean knownToBeBoolean) {
+ this.knownToBeBoolean = knownToBeBoolean;
+ }
+
+ public boolean knownToBeBoolean() {
+ return knownToBeBoolean;
+ }
+
public void markAsThis() {
assert isArgument;
assert !isThis;
@@ -549,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 dad58d8..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,11 +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.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;
@@ -31,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;
/**
@@ -50,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;
@@ -131,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();
@@ -169,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);
}
@@ -210,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());
@@ -223,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) {
@@ -245,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);
}
@@ -263,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();
@@ -293,15 +283,24 @@
return nodes.size() == 0;
}
- public void forEachMethod(Consumer<DexEncodedMethod> consumer, ExecutorService executorService)
+ /**
+ * 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(
+ 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
}));
}
ThreadUtils.awaitFutures(futures);
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 9e07ad6..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
@@ -4,6 +4,7 @@
package com.android.tools.r8.ir.conversion;
+import com.android.tools.r8.ApiLevelException;
import com.android.tools.r8.code.FillArrayData;
import com.android.tools.r8.code.FillArrayDataPayload;
import com.android.tools.r8.code.FilledNewArray;
@@ -26,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;
@@ -39,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;
@@ -148,7 +151,7 @@
}
@Override
- public void buildInstruction(IRBuilder builder, int instructionIndex) {
+ public void buildInstruction(IRBuilder builder, int instructionIndex) throws ApiLevelException {
updateCurrentCatchHandlers(instructionIndex);
emitDebugPosition(instructionIndex, builder);
currentDexInstruction = code.instructions[instructionIndex];
@@ -161,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 1f29d26..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
@@ -4,6 +4,7 @@
package com.android.tools.r8.ir.conversion;
+import com.android.tools.r8.ApiLevelException;
import com.android.tools.r8.dex.Constants;
import com.android.tools.r8.errors.CompilationError;
import com.android.tools.r8.errors.InternalCompilerError;
@@ -77,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;
@@ -92,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;
@@ -123,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
@@ -311,7 +324,7 @@
*
* @return The list of basic blocks. First block is the main entry.
*/
- public IRCode build() {
+ public IRCode build() throws ApiLevelException {
assert source != null;
source.setUp();
@@ -441,7 +454,7 @@
return true;
}
- private void processWorklist() {
+ private void processWorklist() throws ApiLevelException {
for (WorklistItem item = ssaWorklist.poll(); item != null; item = ssaWorklist.poll()) {
if (item.block.isFilled()) {
continue;
@@ -449,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) {
@@ -467,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);
@@ -500,6 +534,13 @@
addInstruction(new Argument(value));
}
+ public void addBooleanNonThisArgument(int register) {
+ DebugLocalInfo local = getCurrentLocal(register);
+ Value value = writeRegister(register, MoveType.SINGLE, ThrowingInfo.NO_THROW, local);
+ value.setKnownToBeBoolean(true);
+ addInstruction(new Argument(value));
+ }
+
public void addDebugUninitialized(int register, ConstType type) {
if (!options.debug) {
return;
@@ -613,6 +654,7 @@
Value in1 = readRegister(array, MoveType.OBJECT);
Value in2 = readRegister(index, MoveType.SINGLE);
Value out = writeRegister(dest, MoveType.fromMemberType(type), ThrowingInfo.CAN_THROW);
+ out.setKnownToBeBoolean(type == MemberType.BOOLEAN);
ArrayGet instruction = new ArrayGet(type, out, in1, in2);
assert instruction.instructionTypeCanThrow();
add(instruction);
@@ -714,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) {
@@ -883,6 +925,7 @@
DexField field) {
Value in = readRegister(object, MoveType.OBJECT);
Value out = writeRegister(dest, MoveType.fromMemberType(type), ThrowingInfo.CAN_THROW);
+ out.setKnownToBeBoolean(type == MemberType.BOOLEAN);
InstanceGet instruction = new InstanceGet(type, out, in, field);
assert instruction.instructionTypeCanThrow();
addInstruction(instruction);
@@ -908,12 +951,13 @@
add(instruction);
}
- public void addInvoke(
- Type type, DexItem item, DexProto callSiteProto, List<Value> arguments) {
+ public void addInvoke(Type type, DexItem item, DexProto callSiteProto, List<Value> arguments)
+ throws ApiLevelException {
if (type == Invoke.Type.POLYMORPHIC && !options.canUseInvokePolymorphic()) {
- throw new CompilationError(
- "MethodHandle.invoke and MethodHandle.invokeExact is unsupported before "
- + "Android O (--min-api " + Constants.ANDROID_O_API + ")");
+ throw new ApiLevelException(
+ AndroidApiLevel.O,
+ "MethodHandle.invoke and MethodHandle.invokeExact",
+ null /* sourceString */);
}
add(Invoke.create(type, item, callSiteProto, null, arguments));
}
@@ -923,7 +967,8 @@
DexItem item,
DexProto callSiteProto,
List<MoveType> types,
- List<Integer> registers) {
+ List<Integer> registers)
+ throws ApiLevelException {
assert types.size() == registers.size();
List<Value> arguments = new ArrayList<>(types.size());
for (int i = 0; i < types.size(); i++) {
@@ -991,7 +1036,8 @@
DexMethod method,
DexProto callSiteProto,
int argumentRegisterCount,
- int[] argumentRegisters) {
+ int[] argumentRegisters)
+ throws ApiLevelException {
// The value of argumentRegisterCount is the number of registers - not the number of values,
// but it is an upper bound on the number of arguments.
List<Value> arguments = new ArrayList<>(argumentRegisterCount);
@@ -1018,7 +1064,8 @@
addInvoke(type, method, callSiteProto, arguments);
}
- public void addInvokeNewArray(DexType type, int argumentCount, int[] argumentRegisters) {
+ public void addInvokeNewArray(DexType type, int argumentCount, int[] argumentRegisters)
+ throws ApiLevelException {
String descriptor = type.descriptor.toString();
assert descriptor.charAt(0) == '[';
assert descriptor.length() >= 2;
@@ -1042,7 +1089,8 @@
DexMethod method,
DexProto callSiteProto,
int argumentCount,
- int firstArgumentRegister) {
+ int firstArgumentRegister)
+ throws ApiLevelException {
// The value of argumentCount is the number of registers - not the number of values, but it
// is an upper bound on the number of arguments.
List<Value> arguments = new ArrayList<>(argumentCount);
@@ -1069,7 +1117,8 @@
addInvoke(type, method, callSiteProto, arguments);
}
- public void addInvokeRangeNewArray(DexType type, int argumentCount, int firstArgumentRegister) {
+ public void addInvokeRangeNewArray(DexType type, int argumentCount, int firstArgumentRegister)
+ throws ApiLevelException {
String descriptor = type.descriptor.toString();
assert descriptor.charAt(0) == '[';
assert descriptor.length() >= 2;
@@ -1114,6 +1163,16 @@
invoke.setOutValue(writeRegister(dest, type, ThrowingInfo.CAN_THROW));
}
+ public void addBooleanMoveResult(MoveType type, int dest) {
+ List<Instruction> instructions = currentBlock.getInstructions();
+ Invoke invoke = instructions.get(instructions.size() - 1).asInvoke();
+ assert invoke.outValue() == null;
+ assert invoke.instructionTypeCanThrow();
+ Value outValue = writeRegister(dest, type, ThrowingInfo.CAN_THROW);
+ outValue.setKnownToBeBoolean(true);
+ invoke.setOutValue(outValue);
+ }
+
public void addNeg(NumericType type, int dest, int value) {
Value in = readNumericRegister(value, type);
Value out = writeNumericRegister(dest, type, ThrowingInfo.NO_THROW);
@@ -1165,6 +1224,7 @@
public void addStaticGet(MemberType type, int dest, DexField field) {
Value out = writeRegister(dest, MoveType.fromMemberType(type), ThrowingInfo.CAN_THROW);
+ out.setKnownToBeBoolean(type == MemberType.BOOLEAN);
StaticGet instruction = new StaticGet(type, out, field);
assert instruction.instructionTypeCanThrow();
addInstruction(instruction);
@@ -1562,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();
@@ -1573,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) {
@@ -1761,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) {
@@ -1772,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);
@@ -1977,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 fad378e..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
@@ -6,6 +6,7 @@
import static com.android.tools.r8.ir.desugar.InterfaceMethodRewriter.Flavor.ExcludeDexResources;
import static com.android.tools.r8.ir.desugar.InterfaceMethodRewriter.Flavor.IncludeAllResources;
+import com.android.tools.r8.ApiLevelException;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.AppInfo;
import com.android.tools.r8.graph.AppInfoWithSubtyping;
@@ -47,6 +48,7 @@
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.function.BiConsumer;
+import java.util.function.Predicate;
public class IRConverter {
@@ -66,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;
@@ -196,7 +197,7 @@
}
}
- private void synthesizeLambdaClasses(Builder builder) {
+ private void synthesizeLambdaClasses(Builder builder) throws ApiLevelException {
if (lambdaRewriter != null) {
lambdaRewriter.adjustAccessibility();
lambdaRewriter.synthesizeLambdaClasses(builder);
@@ -204,13 +205,15 @@
}
private void desugarInterfaceMethods(
- Builder builder, InterfaceMethodRewriter.Flavor includeAllResources) {
+ Builder builder, InterfaceMethodRewriter.Flavor includeAllResources)
+ throws ApiLevelException {
if (interfaceMethodRewriter != null) {
interfaceMethodRewriter.desugarInterfaceMethods(builder, includeAllResources);
}
}
- public DexApplication convertToDex(ExecutorService executor) throws ExecutionException {
+ public DexApplication convertToDex(ExecutorService executor)
+ throws ExecutionException, ApiLevelException {
removeLambdaDeserializationMethods();
convertClassesToDex(application.classes(), executor);
@@ -229,28 +232,31 @@
ExecutorService executor) throws ExecutionException {
List<Future<?>> futures = new ArrayList<>();
for (DexProgramClass clazz : classes) {
- futures.add(executor.submit(() -> clazz.forEachMethod(this::convertMethodToDex)));
+ futures.add(
+ executor.submit(
+ () -> {
+ clazz.forEachMethodThrowing(this::convertMethodToDex);
+ return null; // we want a Callable not a Runnable to be able to throw
+ }));
}
-
ThreadUtils.awaitFutures(futures);
-
- // Get rid of <clinit> methods with no code.
- removeEmptyClassInitializers();
}
- private void convertMethodToDex(DexEncodedMethod method) {
+ void convertMethodToDex(DexEncodedMethod method) throws ApiLevelException {
if (method.getCode() != null) {
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);
}
}
}
- public DexApplication optimize() throws ExecutionException {
+ public DexApplication optimize() throws ExecutionException, ApiLevelException {
ExecutorService executor = Executors.newSingleThreadExecutor();
try {
return optimize(executor);
@@ -259,13 +265,10 @@
}
}
- public DexApplication optimize(ExecutorService executorService) throws ExecutionException {
+ public DexApplication optimize(ExecutorService executorService)
+ 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.
@@ -273,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);
@@ -304,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);
@@ -325,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);
}
@@ -380,7 +377,7 @@
return result;
}
- private DexProgramClass prepareOutlining() {
+ private DexProgramClass prepareOutlining() throws ApiLevelException {
if (!outliner.selectMethodsForOutlining()) {
return null;
}
@@ -389,14 +386,15 @@
return outlineClass;
}
- public void optimizeSynthesizedClass(DexProgramClass clazz) {
+ public void optimizeSynthesizedClass(DexProgramClass clazz) throws ApiLevelException {
// Process the generated class, but don't apply any outlining.
- clazz.forEachMethod(this::optimizeSynthesizedMethod);
+ clazz.forEachMethodThrowing(this::optimizeSynthesizedMethod);
}
- public void optimizeSynthesizedMethod(DexEncodedMethod method) {
+ 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) {
@@ -405,11 +403,14 @@
public void processMethod(DexEncodedMethod method,
OptimizationFeedback feedback,
- BiConsumer<IRCode, DexEncodedMethod> outlineHandler) {
+ 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);
@@ -418,7 +419,10 @@
private void rewriteCode(DexEncodedMethod method,
OptimizationFeedback feedback,
- BiConsumer<IRCode, DexEncodedMethod> outlineHandler) {
+ Predicate<DexEncodedMethod> isProcessedConcurrently,
+ CallSiteInformation callSiteInformation,
+ BiConsumer<IRCode, DexEncodedMethod> outlineHandler)
+ throws ApiLevelException {
if (options.verbose) {
System.out.println("Processing: " + method.toSourceString());
}
@@ -462,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);
@@ -473,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);
@@ -549,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);
}
@@ -584,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 dc49630..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
@@ -3,6 +3,7 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.ir.conversion;
+import com.android.tools.r8.ApiLevelException;
import com.android.tools.r8.dex.Constants;
import com.android.tools.r8.errors.CompilationError;
import com.android.tools.r8.errors.Unreachable;
@@ -22,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;
@@ -32,7 +34,9 @@
import com.android.tools.r8.ir.conversion.JarState.Local;
import com.android.tools.r8.ir.conversion.JarState.Slot;
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;
@@ -46,7 +50,6 @@
import java.util.Map;
import java.util.Queue;
import java.util.Set;
-import java.util.function.BiConsumer;
import org.objectweb.asm.Handle;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
@@ -135,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 = {};
@@ -263,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;
@@ -291,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());
@@ -337,7 +355,11 @@
for (Type type : parameterTypes) {
MoveType moveType = moveType(type);
Slot slot = state.readLocal(argumentRegister, type);
- builder.addNonThisArgument(slot.register, moveType);
+ if (type == Type.BOOLEAN_TYPE) {
+ builder.addBooleanNonThisArgument(slot.register);
+ } else {
+ builder.addNonThisArgument(slot.register, moveType);
+ }
argumentRegister += moveType.requiredRegisters();
}
}
@@ -371,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
@@ -436,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;
}
@@ -457,7 +469,7 @@
}
@Override
- public void buildInstruction(IRBuilder builder, int instructionIndex) {
+ public void buildInstruction(IRBuilder builder, int instructionIndex) throws ApiLevelException {
if (instructionIndex == EXCEPTIONAL_SYNC_EXIT_OFFSET) {
buildExceptionalPostlude(builder);
return;
@@ -469,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;
@@ -547,6 +551,11 @@
}
@Override
+ public int getMoveExceptionRegister() {
+ return state.startOfStack;
+ }
+
+ @Override
public boolean verifyCurrentInstructionCanThrow() {
return generatingMethodSynchronization || canThrow(currentInstruction);
}
@@ -1773,7 +1782,7 @@
// IR instruction building procedures.
- private void build(AbstractInsnNode insn, IRBuilder builder) {
+ private void build(AbstractInsnNode insn, IRBuilder builder) throws ApiLevelException {
switch (insn.getType()) {
case AbstractInsnNode.INSN:
build((InsnNode) insn, builder);
@@ -2286,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);
@@ -2501,7 +2521,7 @@
}
}
- private void build(MethodInsnNode insn, IRBuilder builder) {
+ private void build(MethodInsnNode insn, IRBuilder builder) throws ApiLevelException {
// Resolve the target method of the invoke.
DexMethod method = application.getMethod(insn.owner, insn.name, insn.desc);
@@ -2524,8 +2544,12 @@
}
private void buildInvoke(
- String methodDesc, Type methodOwner, boolean addImplicitReceiver,
- IRBuilder builder, BiConsumer<List<MoveType>, List<Integer>> creator) {
+ String methodDesc,
+ Type methodOwner,
+ boolean addImplicitReceiver,
+ IRBuilder builder,
+ ThrowingBiConsumer<List<MoveType>, List<Integer>, ApiLevelException> creator)
+ throws ApiLevelException {
// Build the argument list of the form [owner, param1, ..., paramN].
// The arguments are in reverse order on the stack, so we pop off the parameters here.
@@ -2551,7 +2575,11 @@
// Move the result to the "top of stack".
Type returnType = Type.getReturnType(methodDesc);
if (returnType != Type.VOID_TYPE) {
- builder.addMoveResult(moveType(returnType), state.push(returnType));
+ if (returnType == Type.BOOLEAN_TYPE) {
+ builder.addBooleanMoveResult(moveType(returnType), state.push(returnType));
+ } else {
+ builder.addMoveResult(moveType(returnType), state.push(returnType));
+ }
}
}
@@ -2562,7 +2590,7 @@
registers.add(slot.register);
}
- private void build(InvokeDynamicInsnNode insn, IRBuilder builder) {
+ private void build(InvokeDynamicInsnNode insn, IRBuilder builder) throws ApiLevelException {
// Bootstrap method
Handle bsmHandle = insn.bsm;
if (bsmHandle.getTag() != Opcodes.H_INVOKESTATIC &&
@@ -2640,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;
@@ -2762,7 +2792,7 @@
builder.addSwitch(index, keys, fallthroughOffset, labelOffsets);
}
- private void build(MultiANewArrayInsnNode insn, IRBuilder builder) {
+ private void build(MultiANewArrayInsnNode insn, IRBuilder builder) throws ApiLevelException {
// Type of the full array.
Type arrayType = Type.getObjectType(insn.desc);
DexType dexArrayType = application.getType(arrayType);
@@ -2804,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 b1f9a14..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
@@ -4,8 +4,10 @@
package com.android.tools.r8.ir.conversion;
+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.
@@ -26,6 +28,8 @@
DebugLocalInfo getCurrentLocal(int register);
+ DebugPosition getDebugPositionAtOffset(int offset);
+
/**
* Trace block structure of the source-program.
*
@@ -46,7 +50,7 @@
// Delegates for IR building.
void buildPrelude(IRBuilder builder);
- void buildInstruction(IRBuilder builder, int instructionIndex);
+ void buildInstruction(IRBuilder builder, int instructionIndex) throws ApiLevelException;
void buildPostlude(IRBuilder builder);
// Helper to resolve switch payloads and build switch instructions (dex code only).
@@ -57,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/InterfaceMethodRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
index c3f2d08..cbeda1e 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
@@ -4,6 +4,7 @@
package com.android.tools.r8.ir.desugar;
+import com.android.tools.r8.ApiLevelException;
import com.android.tools.r8.Resource;
import com.android.tools.r8.errors.CompilationError;
import com.android.tools.r8.errors.Unimplemented;
@@ -193,7 +194,7 @@
* Move static and default interface methods to companion classes,
* add missing methods to forward to moved default methods implementation.
*/
- public void desugarInterfaceMethods(Builder builder, Flavor flavour) {
+ public void desugarInterfaceMethods(Builder builder, Flavor flavour) throws ApiLevelException {
// Process all classes first. Add missing forwarding methods to
// replace desugared default interface methods.
forwardingMethods.addAll(processClasses(builder, flavour));
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
index 353f866..f03a619 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
@@ -4,6 +4,7 @@
package com.android.tools.r8.ir.desugar;
+import com.android.tools.r8.ApiLevelException;
import com.android.tools.r8.dex.Constants;
import com.android.tools.r8.errors.Unimplemented;
import com.android.tools.r8.errors.Unreachable;
@@ -387,7 +388,7 @@
}
// Ensure access of the referenced symbol(s).
- abstract boolean ensureAccessibility();
+ abstract boolean ensureAccessibility() throws ApiLevelException;
DexClass definitionFor(DexType type) {
return rewriter.converter.appInfo.app.definitionFor(type);
@@ -482,7 +483,7 @@
}
@Override
- boolean ensureAccessibility() {
+ boolean ensureAccessibility() throws ApiLevelException {
// Create a static accessor with proper accessibility.
DexProgramClass accessorClass = programDefinitionFor(callTarget.holder);
assert accessorClass != null;
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/desugar/LambdaRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java
index f6f8f2c..86ad391 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java
@@ -4,6 +4,7 @@
package com.android.tools.r8.ir.desugar;
+import com.android.tools.r8.ApiLevelException;
import com.android.tools.r8.dex.Constants;
import com.android.tools.r8.graph.AppInfo;
import com.android.tools.r8.graph.DexApplication.Builder;
@@ -191,7 +192,7 @@
* Adjust accessibility of referenced application symbols or
* creates necessary accessors.
*/
- public void adjustAccessibility() {
+ public void adjustAccessibility() throws ApiLevelException {
// For each lambda class perform necessary adjustment of the
// referenced symbols to make them accessible. This can result in
// method access relaxation or creation of accessor method.
@@ -201,7 +202,7 @@
}
/** Generates lambda classes and adds them to the builder. */
- public void synthesizeLambdaClasses(Builder builder) {
+ public void synthesizeLambdaClasses(Builder builder) throws ApiLevelException {
for (LambdaClass lambdaClass : knownLambdaClasses.values()) {
DexProgramClass synthesizedClass = lambdaClass.synthesizeLambdaClass();
converter.optimizeSynthesizedClass(synthesizedClass);
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 3a89759..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
@@ -972,6 +1082,7 @@
while (it.hasNext()) {
Instruction current = it.next();
if (current.isCheckCast()
+ && current.getLocalInfo() == null
&& current.outValue() != null && current.outValue().isUsed()
&& current.outValue().numberOfPhiUsers() == 0) {
CheckCast checkCast = current.asCheckCast();
@@ -1020,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) {
@@ -1100,9 +1211,10 @@
// 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();
+ 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);
@@ -1110,6 +1222,7 @@
iterator.add(newCstNum);
user.replaceValue(constantValue, newCstNum.outValue());
}
+ constantValue.clearUsers();
}
}
} else {
@@ -1432,10 +1545,16 @@
// First rewrite zero comparison.
rewriteIfWithConstZero(block);
+ if (simplifyKnownBooleanCondition(dominator, block)) {
+ continue;
+ }
+
// Simplify if conditions when possible.
If theIf = block.exit().asIf();
List<Value> inValues = theIf.inValues();
+
int cond;
+
if (inValues.get(0).isConstNumber()
&& (theIf.isZeroTest() || inValues.get(1).isConstNumber())) {
// Zero test with a constant of comparison between between two constants.
@@ -1472,22 +1591,83 @@
BasicBlock target = theIf.targetFromCondition(cond);
BasicBlock deadTarget =
target == theIf.getTrueTarget() ? theIf.fallthroughBlock() : theIf.getTrueTarget();
- List<BasicBlock> removedBlocks = block.unlink(deadTarget, dominator);
- for (BasicBlock removedBlock : removedBlocks) {
- if (!removedBlock.isMarked()) {
- removedBlock.mark();
- }
- }
- assert theIf == block.exit();
- block.replaceLastInstruction(new Goto());
- assert block.exit().isGoto();
- assert block.exit().asGoto().getTarget() == target;
+ rewriteIfToGoto(dominator, block, theIf, target, deadTarget);
}
}
code.removeMarkedBlocks();
assert code.isConsistentSSA();
}
+
+ /* Identify simple diamond shapes converting boolean true/false to 1/0. We consider the forms:
+ *
+ * ifeqz booleanValue ifnez booleanValue
+ * / \ / \
+ * \ / \ /
+ * phi(0, 1) phi(1, 0)
+ *
+ * which can be replaced by a fallthrough and the phi value can be replaced
+ * with the boolean value itself.
+ */
+ private boolean simplifyKnownBooleanCondition(DominatorTree dominator, BasicBlock block) {
+ If theIf = block.exit().asIf();
+ Value testValue = theIf.inValues().get(0);
+ if (theIf.isZeroTest() && testValue.knownToBeBoolean()) {
+ BasicBlock trueBlock = theIf.getTrueTarget();
+ BasicBlock falseBlock = theIf.fallthroughBlock();
+ if (trueBlock.isTrivialGoto() &&
+ falseBlock.isTrivialGoto() &&
+ trueBlock.getSuccessors().get(0) == falseBlock.getSuccessors().get(0)) {
+ BasicBlock targetBlock = trueBlock.getSuccessors().get(0);
+ if (targetBlock.getPredecessors().size() == 2) {
+ int trueIndex = targetBlock.getPredecessors().indexOf(trueBlock);
+ int falseIndex = trueIndex == 0 ? 1 : 0;
+ int deadPhis = 0;
+ // Locate the phis that have the same value as the boolean and replace them
+ // by the boolean in all users.
+ for (Phi phi : targetBlock.getPhis()) {
+ Value trueValue = phi.getOperand(trueIndex);
+ Value falseValue = phi.getOperand(falseIndex);
+ if (trueValue.isConstNumber() && falseValue.isConstNumber()) {
+ ConstNumber trueNumber = trueValue.getConstInstruction().asConstNumber();
+ ConstNumber falseNumber = falseValue.getConstInstruction().asConstNumber();
+ if ((theIf.getType() == Type.EQ &&
+ trueNumber.isIntegerZero() &&
+ falseNumber.isIntegerOne()) ||
+ (theIf.getType() == Type.NE &&
+ trueNumber.isIntegerOne() &&
+ falseNumber.isIntegerZero())) {
+ phi.replaceUsers(testValue);
+ deadPhis++;
+ }
+ }
+ }
+ // If all phis were removed, there is no need for the diamond shape anymore
+ // and it can be rewritten to a goto to one of the branches.
+ if (deadPhis == targetBlock.getPhis().size()) {
+ rewriteIfToGoto(dominator, block, theIf, trueBlock, falseBlock);
+ return true;
+ }
+ }
+ }
+ }
+ return false;
+ }
+
+ private void rewriteIfToGoto(DominatorTree dominator, BasicBlock block, If theIf,
+ BasicBlock target, BasicBlock deadTarget) {
+ List<BasicBlock> removedBlocks = block.unlink(deadTarget, dominator);
+ for (BasicBlock removedBlock : removedBlocks) {
+ if (!removedBlock.isMarked()) {
+ removedBlock.mark();
+ }
+ }
+ assert theIf == block.exit();
+ block.replaceLastInstruction(new Goto());
+ assert block.exit().isGoto();
+ assert block.exit().asGoto().getTarget() == target;
+ }
+
private void rewriteIfWithConstZero(BasicBlock block) {
If theIf = block.exit().asIf();
if (theIf.isZeroTest()) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/EnumOrdinalMapCollector.java b/src/main/java/com/android/tools/r8/ir/optimize/EnumOrdinalMapCollector.java
index b2ebecc..38c8da8 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/EnumOrdinalMapCollector.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/EnumOrdinalMapCollector.java
@@ -3,6 +3,7 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.ir.optimize;
+import com.android.tools.r8.ApiLevelException;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexProgramClass;
@@ -47,14 +48,16 @@
return ordinalsMaps.get(enumClass);
}
- public void run() {
- appInfo.classes().forEach(this::processClasses);
+ public void run() throws ApiLevelException {
+ for (DexProgramClass clazz : appInfo.classes()) {
+ processClasses(clazz);
+ }
if (!ordinalsMaps.isEmpty()) {
appInfo.setExtension(EnumOrdinalMapCollector.class, ordinalsMaps);
}
}
- private void processClasses(DexProgramClass clazz) {
+ private void processClasses(DexProgramClass clazz) throws ApiLevelException {
// Enum classes are flagged as such. Also, for library classes, the ordinals are not known.
if (!clazz.accessFlags.isEnum() || clazz.isLibraryClass() || !clazz.hasClassInitializer()) {
return;
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 814812c..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
@@ -3,6 +3,7 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.ir.optimize;
+import com.android.tools.r8.ApiLevelException;
import com.android.tools.r8.graph.AppInfoWithSubtyping;
import com.android.tools.r8.graph.DexAccessFlags;
import com.android.tools.r8.graph.DexClass;
@@ -22,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;
@@ -35,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 {
@@ -127,7 +129,7 @@
}
public synchronized void processDoubleInlineCallers(IRConverter converter,
- OptimizationFeedback feedback) {
+ OptimizationFeedback feedback) throws ApiLevelException {
if (doubleInlineCallers.size() > 0) {
applyDoubleInlining = true;
List<DexEncodedMethod> methods = doubleInlineCallers
@@ -135,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();
}
}
@@ -221,12 +224,12 @@
this.reason = reason;
}
- boolean forceInline() {
+ boolean ignoreInstructionBudget() {
return reason != Reason.SIMPLE;
}
public IRCode buildIR(ValueNumberGenerator generator, AppInfoWithSubtyping appInfo,
- GraphLense graphLense, InternalOptions options) {
+ GraphLense graphLense, InternalOptions options) throws ApiLevelException {
if (target.isProcessed()) {
assert target.getCode().isDexCode();
return target.buildIR(generator, options);
@@ -314,14 +317,18 @@
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);
if (instruction_allowance < 0) {
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();
@@ -337,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) {
@@ -353,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()) {
@@ -364,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();
@@ -376,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();
}
}
@@ -384,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 3857c1f..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
@@ -4,6 +4,7 @@
package com.android.tools.r8.ir.optimize;
+import com.android.tools.r8.ApiLevelException;
import com.android.tools.r8.dex.Constants;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.AppInfo;
@@ -27,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;
@@ -928,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;
@@ -969,7 +981,8 @@
}
@Override
- public IRCode buildIR(DexEncodedMethod encodedMethod, InternalOptions options) {
+ public IRCode buildIR(DexEncodedMethod encodedMethod, InternalOptions options)
+ throws ApiLevelException {
OutlineSourceCode source = new OutlineSourceCode(outline);
IRBuilder builder = new IRBuilder(encodedMethod, source, options);
return builder.build();
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/optimize/SwitchMapCollector.java b/src/main/java/com/android/tools/r8/ir/optimize/SwitchMapCollector.java
index 44f0759..58bf0bb 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/SwitchMapCollector.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/SwitchMapCollector.java
@@ -3,6 +3,7 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.ir.optimize;
+import com.android.tools.r8.ApiLevelException;
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexField;
@@ -75,8 +76,10 @@
intArrayType = appInfo.dexItemFactory.createType("[I");
}
- public void run() {
- appInfo.classes().forEach(this::processClasses);
+ public void run() throws ApiLevelException {
+ for (DexProgramClass clazz : appInfo.classes()) {
+ processClasses(clazz);
+ }
if (!switchMaps.isEmpty()) {
appInfo.setExtension(SwitchMapCollector.class, switchMaps);
}
@@ -89,7 +92,7 @@
return switchMaps.get(field);
}
- private void processClasses(DexProgramClass clazz) {
+ private void processClasses(DexProgramClass clazz) throws ApiLevelException {
// Switchmap classes are synthetic and have a class initializer.
if (!clazz.accessFlags.isSynthetic() && !clazz.hasClassInitializer()) {
return;
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 bd7efce..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
@@ -6,19 +6,21 @@
import static com.android.tools.r8.ir.code.BasicBlock.ThrowingInfo.NO_THROW;
+import com.android.tools.r8.ApiLevelException;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.DebugLocalInfo;
import com.android.tools.r8.graph.DexProto;
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;
import com.android.tools.r8.ir.conversion.SourceCode;
+import com.android.tools.r8.utils.ThrowingConsumer;
import java.util.ArrayList;
import java.util.List;
-import java.util.function.Consumer;
public abstract class SingleBlockSourceCode implements SourceCode {
@@ -38,7 +40,7 @@
private Value[] paramValues;
// Instruction constructors
- private List<Consumer<IRBuilder>> constructors = new ArrayList<>();
+ private List<ThrowingConsumer<IRBuilder, ApiLevelException>> constructors = new ArrayList<>();
protected SingleBlockSourceCode(DexType receiver, DexProto proto) {
assert proto != null;
@@ -57,7 +59,7 @@
}
}
- protected final void add(Consumer<IRBuilder> constructor) {
+ protected final void add(ThrowingConsumer<IRBuilder, ApiLevelException> constructor) {
constructors.add(constructor);
}
@@ -174,7 +176,8 @@
}
@Override
- public final void buildInstruction(IRBuilder builder, int instructionIndex) {
+ public final void buildInstruction(IRBuilder builder, int instructionIndex)
+ throws ApiLevelException {
constructors.get(instructionIndex).accept(builder);
}
@@ -196,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/ir/synthetic/SynthesizedCode.java b/src/main/java/com/android/tools/r8/ir/synthetic/SynthesizedCode.java
index 3d3f9f1..0de034a 100644
--- a/src/main/java/com/android/tools/r8/ir/synthetic/SynthesizedCode.java
+++ b/src/main/java/com/android/tools/r8/ir/synthetic/SynthesizedCode.java
@@ -4,6 +4,7 @@
package com.android.tools.r8.ir.synthetic;
+import com.android.tools.r8.ApiLevelException;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.Code;
import com.android.tools.r8.graph.DexEncodedMethod;
@@ -31,7 +32,8 @@
}
@Override
- public final IRCode buildIR(DexEncodedMethod encodedMethod, InternalOptions options) {
+ public final IRCode buildIR(DexEncodedMethod encodedMethod, InternalOptions options)
+ throws ApiLevelException {
return new IRBuilder(encodedMethod, sourceCode, options).build();
}
diff --git a/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java b/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java
index 3ef67e6..f4bd577 100644
--- a/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java
@@ -9,7 +9,10 @@
import com.android.tools.r8.graph.DexAnnotation;
import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexProto;
import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.DexValue;
@@ -81,7 +84,7 @@
// Collect names we have to keep.
timing.begin("reserve");
for (DexClass clazz : classes) {
- if (rootSet.noObfuscation.contains(clazz)) {
+ if (rootSet.noObfuscation.contains(clazz.type)) {
assert !renaming.containsKey(clazz.type);
registerClassAsUsed(clazz.type);
}
@@ -97,6 +100,12 @@
}
timing.end();
+ timing.begin("rename-dangling-types");
+ for (DexClass clazz : classes) {
+ renameDanglingTypes(clazz);
+ }
+ timing.end();
+
timing.begin("rename-generic");
renameTypesInGenericSignatures();
timing.end();
@@ -108,6 +117,35 @@
return Collections.unmodifiableMap(renaming);
}
+ private void renameDanglingTypes(DexClass clazz) {
+ clazz.forEachMethod(this::renameDanglingTypesInMethod);
+ clazz.forEachField(this::renameDanglingTypesInField);
+ }
+
+ private void renameDanglingTypesInField(DexEncodedField field) {
+ renameDanglingType(field.field.type);
+ }
+
+ private void renameDanglingTypesInMethod(DexEncodedMethod method) {
+ DexProto proto = method.method.proto;
+ renameDanglingType(proto.returnType);
+ for (DexType type : proto.parameters.values) {
+ renameDanglingType(type);
+ }
+ }
+
+ private void renameDanglingType(DexType type) {
+ if (appInfo.wasPruned(type)
+ && !renaming.containsKey(type)
+ && !rootSet.noObfuscation.contains(type)) {
+ // We have a type that is defined in the program source but is only used in a proto or
+ // return type. As we don't need the class, we can rename it to anything as long as it is
+ // unique.
+ assert appInfo.definitionFor(type) == null;
+ renaming.put(type, topLevelState.nextTypeName());
+ }
+ }
+
private void renameTypesInGenericSignatures() {
for (DexClass clazz : appInfo.classes()) {
rewriteGenericSignatures(clazz.annotations.annotations,
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/Enqueuer.java b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
index 524207e..ff8adee 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -41,6 +41,7 @@
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collection;
+import java.util.Collections;
import java.util.Deque;
import java.util.HashMap;
import java.util.HashSet;
@@ -927,13 +928,13 @@
SortedSet<DexField> collectFieldsRead() {
return ImmutableSortedSet.copyOf(PresortedComparable::slowCompareTo,
Sets.union(collectReachedFields(instanceFieldsRead, this::tryLookupInstanceField),
- collectReachedFields(staticFieldsRead, this::tryLookupStaticField)));
+ collectReachedFields(staticFieldsRead, this::tryLookupStaticField)));
}
SortedSet<DexField> collectFieldsWritten() {
return ImmutableSortedSet.copyOf(PresortedComparable::slowCompareTo,
Sets.union(collectReachedFields(instanceFieldsWritten, this::tryLookupInstanceField),
- collectReachedFields(staticFieldsWritten, this::tryLookupStaticField)));
+ collectReachedFields(staticFieldsWritten, this::tryLookupStaticField)));
}
private static class Action {
@@ -1099,6 +1100,10 @@
* Map from the class of an extension to the state it produced.
*/
public final Map<Class, Object> extensions;
+ /**
+ * A set of types that have been removed by the {@link TreePruner}.
+ */
+ public final Set<DexType> prunedTypes;
private AppInfoWithLiveness(AppInfoWithSubtyping appInfo, Enqueuer enqueuer) {
super(appInfo);
@@ -1124,11 +1129,13 @@
this.assumedValues = enqueuer.rootSet.assumedValues;
this.alwaysInline = enqueuer.rootSet.alwaysInline;
this.extensions = enqueuer.extensionsState;
+ this.prunedTypes = Collections.emptySet();
assert Sets.intersection(instanceFieldReads, staticFieldReads).size() == 0;
assert Sets.intersection(instanceFieldWrites, staticFieldWrites).size() == 0;
}
- private AppInfoWithLiveness(AppInfoWithLiveness previous, DexApplication application) {
+ private AppInfoWithLiveness(AppInfoWithLiveness previous, DexApplication application,
+ Collection<DexType> removedClasses) {
super(application);
this.liveTypes = previous.liveTypes;
this.instantiatedTypes = previous.instantiatedTypes;
@@ -1151,6 +1158,7 @@
this.staticInvokes = previous.staticInvokes;
this.extensions = previous.extensions;
this.alwaysInline = previous.alwaysInline;
+ this.prunedTypes = mergeSets(previous.prunedTypes, removedClasses);
assert Sets.intersection(instanceFieldReads, staticFieldReads).size() == 0;
assert Sets.intersection(instanceFieldWrites, staticFieldWrites).size() == 0;
}
@@ -1178,6 +1186,7 @@
this.staticInvokes = rewriteItems(previous.staticInvokes, lense::lookupMethod);
this.alwaysInline = previous.alwaysInline;
this.extensions = previous.extensions;
+ this.prunedTypes = rewriteItems(previous.prunedTypes, lense::lookupType);
assert Sets.intersection(instanceFieldReads, staticFieldReads).size() == 0;
assert Sets.intersection(instanceFieldWrites, staticFieldWrites).size() == 0;
}
@@ -1209,6 +1218,13 @@
return builder.build();
}
+ private static <T> Set<T> mergeSets(Collection<T> first, Collection<T> second) {
+ ImmutableSet.Builder<T> builder = ImmutableSet.builder();
+ builder.addAll(first);
+ builder.addAll(second);
+ return builder.build();
+ }
+
@SuppressWarnings("unchecked")
public <T> T getExtension(Class extension, T defaultValue) {
if (extensions.containsKey(extension)) {
@@ -1237,14 +1253,23 @@
* Returns a copy of this AppInfoWithLiveness where the set of classes is pruned using the
* given DexApplication object.
*/
- public AppInfoWithLiveness prunedCopyFrom(DexApplication application) {
- return new AppInfoWithLiveness(this, application);
+ public AppInfoWithLiveness prunedCopyFrom(DexApplication application,
+ Collection<DexType> removedClasses) {
+ return new AppInfoWithLiveness(this, application, removedClasses);
}
public AppInfoWithLiveness rewrittenWithLense(GraphLense lense) {
assert lense.isContextFree();
return new AppInfoWithLiveness(this, lense);
}
+
+ /**
+ * Returns true if the given type was part of the original program but has been removed during
+ * tree shaking.
+ */
+ public boolean wasPruned(DexType type) {
+ return prunedTypes.contains(type);
+ }
}
private static class SetWithReason<T> {
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 0fa5051..5e7b42d 100644
--- a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
+++ b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
@@ -452,7 +452,7 @@
dependentNoShrinking.computeIfAbsent(item, x -> new IdentityHashMap<>())
.put(definition, context);
// Unconditionally add to no-obfuscation, as that is only checked for surviving items.
- noObfuscation.add(definition);
+ noObfuscation.add(type);
}
private void includeDescriptorClasses(DexItem item, ProguardKeepRule context) {
@@ -487,7 +487,11 @@
noOptimization.add(item);
}
if (!modifiers.allowsObfuscation) {
- noObfuscation.add(item);
+ if (item instanceof DexClass) {
+ noObfuscation.add(((DexClass) item).type);
+ } else {
+ noObfuscation.add(item);
+ }
}
if (modifiers.includeDescriptorClasses) {
includeDescriptorClasses(item, keepRule);
@@ -520,38 +524,24 @@
public final Map<DexItem, ProguardMemberRule> assumedValues;
private final Map<DexItem, Map<DexItem, ProguardKeepRule>> dependentNoShrinking;
- private boolean legalNoObfuscationItem(DexItem item) {
- if (!(item instanceof DexProgramClass
- || item instanceof DexLibraryClass
- || item instanceof DexEncodedMethod
- || item instanceof DexEncodedField)) {
- }
- assert item instanceof DexProgramClass
- || item instanceof DexLibraryClass
- || item instanceof DexEncodedMethod
- || item instanceof DexEncodedField;
- return true;
- }
-
- private boolean legalNoObfuscationItems(Set<DexItem> items) {
- items.forEach(this::legalNoObfuscationItem);
- return true;
- }
-
- private boolean legalDependentNoShrinkingItem(DexItem item) {
- if (!(item instanceof DexType
- || item instanceof DexEncodedMethod
- || item instanceof DexEncodedField)) {
- }
+ private boolean isTypeEncodedMethodOrEncodedField(DexItem item) {
assert item instanceof DexType
|| item instanceof DexEncodedMethod
|| item instanceof DexEncodedField;
+ return item instanceof DexType
+ || item instanceof DexEncodedMethod
+ || item instanceof DexEncodedField;
+ }
+
+ private boolean legalNoObfuscationItems(Set<DexItem> items) {
+ assert items.stream().allMatch(this::isTypeEncodedMethodOrEncodedField);
return true;
}
private boolean legalDependentNoShrinkingItems(
Map<DexItem, Map<DexItem, ProguardKeepRule>> dependentNoShrinking) {
- dependentNoShrinking.keySet().forEach(this::legalDependentNoShrinkingItem);
+ assert dependentNoShrinking.keySet().stream()
+ .allMatch(this::isTypeEncodedMethodOrEncodedField);
return true;
}
diff --git a/src/main/java/com/android/tools/r8/shaking/SimpleClassMerger.java b/src/main/java/com/android/tools/r8/shaking/SimpleClassMerger.java
index c04b29a..193ebdf 100644
--- a/src/main/java/com/android/tools/r8/shaking/SimpleClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/SimpleClassMerger.java
@@ -655,4 +655,8 @@
return result;
}
}
+
+ public Collection<DexType> getRemovedClasses() {
+ return Collections.unmodifiableCollection(mergedClasses.keySet());
+ }
}
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 64294bd..3decec1 100644
--- a/src/main/java/com/android/tools/r8/shaking/TreePruner.java
+++ b/src/main/java/com/android/tools/r8/shaking/TreePruner.java
@@ -8,13 +8,16 @@
import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.KeyedDexItem;
import com.android.tools.r8.graph.PresortedComparable;
import com.android.tools.r8.logging.Log;
import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
import com.android.tools.r8.utils.InternalOptions;
-import java.io.IOException;
+import com.google.common.collect.Sets;
import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
import java.util.List;
import java.util.Set;
@@ -23,7 +26,8 @@
private DexApplication application;
private final AppInfoWithLiveness appInfo;
private final InternalOptions options;
- private UsagePrinter usagePrinter;
+ private final UsagePrinter usagePrinter;
+ private final Set<DexType> prunedTypes = Sets.newIdentityHashSet();
public TreePruner(
DexApplication application, AppInfoWithLiveness appInfo, InternalOptions options) {
@@ -34,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(
@@ -64,6 +68,7 @@
if (Log.ENABLED) {
Log.debug(getClass(), "Removing class: " + clazz);
}
+ prunedTypes.add(clazz.type);
usagePrinter.printUnusedClass(clazz);
} else {
newClasses.add(clazz);
@@ -190,4 +195,8 @@
}
return reachableFields.toArray(new DexEncodedField[reachableFields.size()]);
}
+
+ public Collection<DexType> getRemovedClasses() {
+ return Collections.unmodifiableCollection(prunedTypes);
+ }
}
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 b03721f..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 {
@@ -91,6 +92,7 @@
public OutputMode outputMode = OutputMode.Indexed;
public boolean useTreeShaking = true;
+ public boolean useDiscardedChecker = true;
public boolean printCfg = false;
public String printCfgFile;
@@ -196,7 +198,7 @@
public static class TestingOptions {
- public Function<List<DexEncodedMethod>, List<DexEncodedMethod>> irOrdering =
+ public Function<Set<DexEncodedMethod>, Set<DexEncodedMethod>> irOrdering =
Function.identity();
}
@@ -320,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/ThrowingBiConsumer.java b/src/main/java/com/android/tools/r8/utils/ThrowingBiConsumer.java
new file mode 100644
index 0000000..d7da332
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/ThrowingBiConsumer.java
@@ -0,0 +1,15 @@
+package com.android.tools.r8.utils;
+
+import java.util.function.BiConsumer;
+
+/**
+ * Similar to a {@link BiConsumer} but throws a single {@link Throwable}.
+ *
+ * @param <T> the type of the first argument
+ * @param <U> the type of the second argument
+ * @param <E> the type of the {@link Throwable}
+ */
+@FunctionalInterface
+public interface ThrowingBiConsumer<T, U, E extends Throwable> {
+ void accept(T t, U u) throws E;
+}
diff --git a/src/main/java/com/android/tools/r8/utils/ThrowingConsumer.java b/src/main/java/com/android/tools/r8/utils/ThrowingConsumer.java
new file mode 100644
index 0000000..8fb8c84
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/ThrowingConsumer.java
@@ -0,0 +1,14 @@
+package com.android.tools.r8.utils;
+
+import java.util.function.Consumer;
+
+/**
+ * Similar to a {@link Consumer} but throws a single {@link Throwable}.
+ *
+ * @param <T> the type of the input
+ * @param <E> the type of the {@link Throwable}
+ */
+@FunctionalInterface
+public interface ThrowingConsumer<T, E extends Throwable> {
+ void accept(T t) throws 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/R8UnreachableCodeTest.java b/src/test/java/com/android/tools/r8/R8UnreachableCodeTest.java
index 66853cb..61a049e 100644
--- a/src/test/java/com/android/tools/r8/R8UnreachableCodeTest.java
+++ b/src/test/java/com/android/tools/r8/R8UnreachableCodeTest.java
@@ -28,7 +28,7 @@
private static final Path SMALI_DIR = Paths.get(ToolHelper.SMALI_BUILD_DIR);
@Test
- public void UnreachableCode() throws IOException, ExecutionException {
+ public void UnreachableCode() throws IOException, ExecutionException, CompilationException {
String name = "unreachable-code-1";
AndroidApp input = AndroidApp.fromProgramFiles(SMALI_DIR.resolve(name).resolve(name + ".dex"));
ExecutorService executorService = Executors.newSingleThreadExecutor();
diff --git a/src/test/java/com/android/tools/r8/RunExamplesAndroidOTest.java b/src/test/java/com/android/tools/r8/RunExamplesAndroidOTest.java
index 937f55c..0ca84a1 100644
--- a/src/test/java/com/android/tools/r8/RunExamplesAndroidOTest.java
+++ b/src/test/java/com/android/tools/r8/RunExamplesAndroidOTest.java
@@ -106,9 +106,6 @@
}
void run() throws Throwable {
- if (compilationErrorExpected(testName)) {
- thrown.expect(CompilationError.class);
- }
if (minSdkErrorExpected(testName)) {
thrown.expect(ApiLevelException.class);
}
@@ -154,11 +151,9 @@
abstract void build(Path inputFile, Path out) throws Throwable;
}
- private static List<String> compilationErrorExpected =
- ImmutableList.of("invokepolymorphic-error-due-to-min-sdk");
-
private static List<String> minSdkErrorExpected =
- ImmutableList.of("invokecustom-error-due-to-min-sdk");
+ ImmutableList.of(
+ "invokepolymorphic-error-due-to-min-sdk", "invokecustom-error-due-to-min-sdk");
private static Map<DexVm, List<String>> failsOn =
ImmutableMap.of(
@@ -216,10 +211,6 @@
return failsOn(failsOn, name);
}
- boolean compilationErrorExpected(String testName) {
- return compilationErrorExpected.contains(testName);
- }
-
boolean minSdkErrorExpected(String testName) {
return minSdkErrorExpected.contains(testName);
}
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index 663d6bd..9952f70 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();
}
@@ -523,7 +529,7 @@
DexApplication application,
AppInfoWithSubtyping appInfo,
InternalOptions options)
- throws ProguardRuleParserException, ExecutionException, IOException {
+ throws CompilationException, ExecutionException, IOException {
return R8.optimize(application, appInfo, options);
}
diff --git a/src/test/java/com/android/tools/r8/d8/D8FrameworkTest.java b/src/test/java/com/android/tools/r8/d8/D8FrameworkDexPassthroughMarkerTest.java
similarity index 84%
rename from src/test/java/com/android/tools/r8/d8/D8FrameworkTest.java
rename to src/test/java/com/android/tools/r8/d8/D8FrameworkDexPassthroughMarkerTest.java
index a449e21..3aadc15 100644
--- a/src/test/java/com/android/tools/r8/d8/D8FrameworkTest.java
+++ b/src/test/java/com/android/tools/r8/d8/D8FrameworkDexPassthroughMarkerTest.java
@@ -16,7 +16,12 @@
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.Timing;
-
+import java.io.IOException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.concurrent.ExecutionException;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
@@ -24,20 +29,12 @@
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
-import java.io.IOException;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.concurrent.ExecutionException;
-
/**
- * Simple test that compiles framework.jar with D8 a number of times with
- * various number of threads available to the compiler.
- * This test also tests the hidden marker inserted into classes.dex.
+ * Simple test that compiles framework.jar with D8 a number of times with various number of threads
+ * available to the compiler. This test also tests the hidden marker inserted into classes.dex.
*/
-@RunWith( Parameterized.class )
-public class D8FrameworkTest {
+@RunWith(Parameterized.class)
+public class D8FrameworkDexPassthroughMarkerTest {
private static final Path FRAMEWORK_JAR =
Paths.get("tools/linux/art-5.1.1/product/mako/system/framework/framework.jar");
@@ -52,7 +49,7 @@
private final int threads;
- public D8FrameworkTest(int threads) {
+ public D8FrameworkDexPassthroughMarkerTest(int threads) {
this.threads = threads;
}
@@ -72,7 +69,9 @@
options.numberOfThreads = threads;
});
DexApplication dexApp =
- new ApplicationReader(app, new InternalOptions(), new Timing("D8FrameworkTest")).read();
+ new ApplicationReader(
+ app, new InternalOptions(), new Timing("D8FrameworkDexPassthroughMarkerTest"))
+ .read();
Marker readMarker = dexApp.dexItemFactory.extractMarker();
assertEquals(marker, readMarker);
}
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/D8Framework14082017DesugaredVerificationTest.java b/src/test/java/com/android/tools/r8/internal/D8Framework14082017DesugaredVerificationTest.java
new file mode 100644
index 0000000..4cc5818
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/internal/D8Framework14082017DesugaredVerificationTest.java
@@ -0,0 +1,42 @@
+// 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.shaking.ProguardRuleParserException;
+import java.io.IOException;
+import java.nio.file.Paths;
+import java.util.concurrent.ExecutionException;
+import org.junit.Test;
+
+public class D8Framework14082017DesugaredVerificationTest extends CompilationTestBase {
+ private static final int MIN_SDK = 24;
+ private static final String JAR = "third_party/framework/framework_14082017_desugared.jar";
+
+ @Test
+ public void verifyDebugBuild()
+ throws ExecutionException, IOException, ProguardRuleParserException, CompilationException {
+ runAndCheckVerification(
+ D8Command.builder()
+ .addProgramFiles(Paths.get(JAR))
+ .setMode(CompilationMode.DEBUG)
+ .setMinApiLevel(MIN_SDK)
+ .build(),
+ JAR);
+ }
+
+ @Test
+ public void verifyReleaseBuild()
+ throws ExecutionException, IOException, ProguardRuleParserException, CompilationException {
+ runAndCheckVerification(
+ D8Command.builder()
+ .addProgramFiles(Paths.get(JAR))
+ .setMode(CompilationMode.RELEASE)
+ .setMinApiLevel(MIN_SDK)
+ .build(),
+ JAR);
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/internal/D8Framework14082017VerificationTest.java b/src/test/java/com/android/tools/r8/internal/D8Framework14082017VerificationTest.java
new file mode 100644
index 0000000..87786df
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/internal/D8Framework14082017VerificationTest.java
@@ -0,0 +1,42 @@
+// 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.shaking.ProguardRuleParserException;
+import java.io.IOException;
+import java.nio.file.Paths;
+import java.util.concurrent.ExecutionException;
+import org.junit.Test;
+
+public class D8Framework14082017VerificationTest extends CompilationTestBase {
+ private static final int MIN_SDK = 24;
+ private static final String JAR = "third_party/framework/framework_14082017.jar";
+
+ @Test
+ public void verifyDebugBuild()
+ throws ExecutionException, IOException, ProguardRuleParserException, CompilationException {
+ runAndCheckVerification(
+ D8Command.builder()
+ .addProgramFiles(Paths.get(JAR))
+ .setMode(CompilationMode.DEBUG)
+ .setMinApiLevel(MIN_SDK)
+ .build(),
+ JAR);
+ }
+
+ @Test
+ public void verifyReleaseBuild()
+ throws ExecutionException, IOException, ProguardRuleParserException, CompilationException {
+ runAndCheckVerification(
+ D8Command.builder()
+ .addProgramFiles(Paths.get(JAR))
+ .setMode(CompilationMode.RELEASE)
+ .setMinApiLevel(MIN_SDK)
+ .build(),
+ JAR);
+ }
+}
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/ir/BasicBlockIteratorTest.java b/src/test/java/com/android/tools/r8/ir/BasicBlockIteratorTest.java
index 50cee1b..cf191de 100644
--- a/src/test/java/com/android/tools/r8/ir/BasicBlockIteratorTest.java
+++ b/src/test/java/com/android/tools/r8/ir/BasicBlockIteratorTest.java
@@ -4,6 +4,7 @@
package com.android.tools.r8.ir;
+import com.android.tools.r8.CompilationException;
import com.android.tools.r8.graph.DexApplication;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.ir.code.BasicBlock;
@@ -31,7 +32,7 @@
* Third block: Return instruction
*
*/
- IRCode simpleCode() {
+ IRCode simpleCode() throws Exception {
SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
String returnType = "int";
@@ -61,7 +62,7 @@
}
@Test
- public void removeBeforeNext() {
+ public void removeBeforeNext() throws Exception {
IRCode code = simpleCode();
ListIterator<BasicBlock> blocks = code.listIterator();
@@ -70,7 +71,7 @@
}
@Test
- public void removeTwice() {
+ public void removeTwice() throws Exception {
IRCode code = simpleCode();
ListIterator<BasicBlock> blocks = code.listIterator();
diff --git a/src/test/java/com/android/tools/r8/ir/InlineTest.java b/src/test/java/com/android/tools/r8/ir/InlineTest.java
index f4206ea..6fb5db1 100644
--- a/src/test/java/com/android/tools/r8/ir/InlineTest.java
+++ b/src/test/java/com/android/tools/r8/ir/InlineTest.java
@@ -24,7 +24,7 @@
public class InlineTest extends SmaliTestBase {
- TestApplication codeForMethodReplaceTest(int a, int b) {
+ TestApplication codeForMethodReplaceTest(int a, int b) throws Exception {
SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
MethodSignature signature = builder.addStaticMethod(
@@ -88,7 +88,7 @@
ImmutableList.of(codeA, codeB), valueNumberGenerator, options);
}
- public void runInlineTest(int a, int b, int expectedA, int expectedB) {
+ public void runInlineTest(int a, int b, int expectedA, int expectedB) throws Exception {
// Run code without inlining.
TestApplication test = codeForMethodReplaceTest(a, b);
String result = test.run();
@@ -116,12 +116,12 @@
}
@Test
- public void inline() {
+ public void inline() throws Exception {
runInlineTest(1, 1, 2, 0);
runInlineTest(1, 2, 3, 1);
}
- TestApplication codeForMethodReplaceReturnVoidTest(int a, int b) {
+ TestApplication codeForMethodReplaceReturnVoidTest(int a, int b) throws Exception {
SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
MethodSignature signature = builder.addStaticMethod(
@@ -169,7 +169,7 @@
}
@Test
- public void inlineReturnVoid() {
+ public void inlineReturnVoid() throws Exception {
// Run code without inlining.
TestApplication test = codeForMethodReplaceReturnVoidTest(1, 2);
String result = test.run();
@@ -187,7 +187,7 @@
assertEquals(Integer.toString(1), result);
}
- TestApplication codeForMultipleMethodReplaceTest(int a, int b) {
+ TestApplication codeForMultipleMethodReplaceTest(int a, int b) throws Exception {
SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
MethodSignature signature = builder.addStaticMethod(
@@ -259,7 +259,7 @@
additionalCode, valueNumberGenerator, options);
}
- public void runInlineMultipleTest(int a, int b, int expectedA, int expectedB) {
+ public void runInlineMultipleTest(int a, int b, int expectedA, int expectedB) throws Exception {
// Run code without inlining.
TestApplication test = codeForMultipleMethodReplaceTest(a, b);
String result = test.run();
@@ -304,12 +304,13 @@
}
@Test
- public void inlineMultiple() {
+ public void inlineMultiple() throws Exception {
runInlineMultipleTest(1, 1, 4, 1);
runInlineMultipleTest(1, 2, 7, 8);
}
- TestApplication codeForMethodReplaceTestWithCatchHandler(int a, int b, boolean twoGuards) {
+ TestApplication codeForMethodReplaceTestWithCatchHandler(int a, int b, boolean twoGuards)
+ throws Exception {
SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
String secondGuard = twoGuards ?
@@ -385,7 +386,7 @@
}
public void runInlineCallerHasCatchHandlersTest(
- int a, int b, boolean twoGuards, int expectedA, int expectedB) {
+ int a, int b, boolean twoGuards, int expectedA, int expectedB) throws Exception {
// Run code without inlining.
TestApplication test = codeForMethodReplaceTestWithCatchHandler(a, b, twoGuards);
String result = test.run();
@@ -413,14 +414,14 @@
}
@Test
- public void inlineCallerHasCatchHandlers() {
+ public void inlineCallerHasCatchHandlers() throws Exception {
runInlineCallerHasCatchHandlersTest(1, 1, false, 2, 0);
runInlineCallerHasCatchHandlersTest(1, 2, false, 3, 1);
runInlineCallerHasCatchHandlersTest(1, 1, true, 2, 0);
runInlineCallerHasCatchHandlersTest(1, 2, true, 3, 1);
}
- TestApplication codeForInlineCanThrow(int a, int b, boolean twoGuards) {
+ TestApplication codeForInlineCanThrow(int a, int b, boolean twoGuards) throws Exception {
SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
String secondGuard = twoGuards ?
@@ -499,7 +500,7 @@
}
public void runInlineCanThrow(
- int a, int b, boolean twoGuards, int expectedA, int expectedB) {
+ int a, int b, boolean twoGuards, int expectedA, int expectedB) throws Exception {
// Run code without inlining.
TestApplication test = codeForInlineCanThrow(a, b, twoGuards);
String result = test.run();
@@ -527,14 +528,14 @@
}
@Test
- public void inlineCanThrow() {
+ public void inlineCanThrow() throws Exception {
runInlineCanThrow(2, 2, false, 1, 1);
runInlineCanThrow(2, 0, false, -2, -1);
runInlineCanThrow(2, 2, true, 1, 1);
runInlineCanThrow(2, 0, true, -2, -1);
}
- private TestApplication codeForInlineAlwaysThrows(boolean twoGuards) {
+ private TestApplication codeForInlineAlwaysThrows(boolean twoGuards) throws Exception {
SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
String secondGuard = twoGuards ?
@@ -611,7 +612,8 @@
ImmutableList.of(codeA, codeB), valueNumberGenerator, options);
}
- private void runInlineAlwaysThrows(boolean twoGuards, int expectedA, int expectedB) {
+ private void runInlineAlwaysThrows(boolean twoGuards, int expectedA, int expectedB)
+ throws Exception {
// Run code without inlining.
TestApplication test = codeForInlineAlwaysThrows(twoGuards);
String result = test.run();
@@ -640,12 +642,12 @@
}
@Test
- public void inlineAlwaysThrows() {
+ public void inlineAlwaysThrows() throws Exception {
runInlineAlwaysThrows(false, -2, -2);
runInlineAlwaysThrows(true, -2, -1);
}
- private TestApplication codeForInlineAlwaysThrowsMultiple(boolean twoGuards) {
+ private TestApplication codeForInlineAlwaysThrowsMultiple(boolean twoGuards) throws Exception {
SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
String secondGuard = twoGuards ?
@@ -732,7 +734,8 @@
application, method, code, additionalCode, valueNumberGenerator, options);
}
- private void runInlineAlwaysThrowsMultiple(boolean twoGuards, int expectedA, int expectedB) {
+ private void runInlineAlwaysThrowsMultiple(boolean twoGuards, int expectedA, int expectedB)
+ throws Exception {
// Run code without inlining.
TestApplication test = codeForInlineAlwaysThrows(twoGuards);
String result = test.run();
@@ -785,13 +788,13 @@
}
@Test
- public void inlineAlwaysThrowsMultiple() {
+ public void inlineAlwaysThrowsMultiple() throws Exception {
runInlineAlwaysThrowsMultiple(false, -2, -2);
runInlineAlwaysThrowsMultiple(true, -2, -1);
}
private TestApplication codeForInlineAlwaysThrowsMultipleWithControlFlow(
- int a, boolean twoGuards) {
+ int a, boolean twoGuards) throws Exception {
SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
String secondGuard = twoGuards ?
@@ -886,7 +889,7 @@
}
private void runInlineAlwaysThrowsMultipleWithControlFlow(
- int a, boolean twoGuards, int expectedA, int expectedB) {
+ int a, boolean twoGuards, int expectedA, int expectedB) throws Exception {
// Run code without inlining.
TestApplication test = codeForInlineAlwaysThrows(twoGuards);
String result = test.run();
@@ -939,7 +942,7 @@
}
@Test
- public void inlineAlwaysThrowsMultipleWithControlFlow() {
+ public void inlineAlwaysThrowsMultipleWithControlFlow() throws Exception {
runInlineAlwaysThrowsMultipleWithControlFlow(0, false, -2, -2);
runInlineAlwaysThrowsMultipleWithControlFlow(0, true, -2, -1);
runInlineAlwaysThrowsMultipleWithControlFlow(1, false, -2, -2);
@@ -948,8 +951,9 @@
runInlineAlwaysThrowsMultipleWithControlFlow(2, true, -2, -1);
}
- private TestApplication codeForInlineWithHandlersCanThrow(int a, int b, int c,
- boolean twoGuards, boolean callerHasCatchAll, boolean inlineeHasCatchAll) {
+ private TestApplication codeForInlineWithHandlersCanThrow(
+ int a, int b, int c, boolean twoGuards, boolean callerHasCatchAll, boolean inlineeHasCatchAll)
+ throws Exception {
SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
String secondGuard = "";
@@ -1122,7 +1126,7 @@
private void runInlineWithHandlersCanThrow(int a, int b, int c,
boolean twoGuards, boolean callerHasCatchAll, boolean inlineeHasCatchAll,
- int expectedA, int expectedB) {
+ int expectedA, int expectedB) throws Exception {
// Run code without inlining.
TestApplication test = codeForInlineWithHandlersCanThrow(
a, b, c, twoGuards, callerHasCatchAll, inlineeHasCatchAll);
@@ -1153,7 +1157,7 @@
}
@Test
- public void inlineCanWithHandlersThrow() {
+ public void inlineCanWithHandlersThrow() throws Exception {
// The base generated code will be:
//
// int method(int a, int b, int c) {
diff --git a/src/test/java/com/android/tools/r8/ir/InstructionIteratorTest.java b/src/test/java/com/android/tools/r8/ir/InstructionIteratorTest.java
index 1e35475..b9eef9c 100644
--- a/src/test/java/com/android/tools/r8/ir/InstructionIteratorTest.java
+++ b/src/test/java/com/android/tools/r8/ir/InstructionIteratorTest.java
@@ -32,7 +32,7 @@
* Third block: Return instruction
*
*/
- IRCode simpleCode() {
+ IRCode simpleCode() throws Exception {
SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
String returnType = "int";
@@ -62,7 +62,7 @@
}
@Test
- public void removeBeforeNext() {
+ public void removeBeforeNext() throws Exception {
IRCode code = simpleCode();
ListIterator<BasicBlock> blocks = code.listIterator();
@@ -72,7 +72,7 @@
}
@Test
- public void removeTwice() {
+ public void removeTwice() throws Exception {
IRCode code = simpleCode();
ListIterator<BasicBlock> blocks = code.listIterator();
diff --git a/src/test/java/com/android/tools/r8/ir/SplitBlockTest.java b/src/test/java/com/android/tools/r8/ir/SplitBlockTest.java
index 90e2089..04a55b5 100644
--- a/src/test/java/com/android/tools/r8/ir/SplitBlockTest.java
+++ b/src/test/java/com/android/tools/r8/ir/SplitBlockTest.java
@@ -29,7 +29,7 @@
public class SplitBlockTest extends SmaliTestBase {
- TestApplication codeWithoutCatchHandlers() {
+ TestApplication codeWithoutCatchHandlers() throws Exception {
SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
String returnType = "int";
@@ -68,7 +68,7 @@
}
@Test
- public void noCatchHandlers() {
+ public void noCatchHandlers() throws Exception {
final int initialBlockCount = 1;
final int argumentInstructions = 2;
final int firstBlockInstructions = 6;
@@ -102,7 +102,7 @@
}
@Test
- public void noCatchHandlersSplitThree() {
+ public void noCatchHandlersSplitThree() throws Exception {
final int initialBlockCount = 1;
final int argumentInstructions = 2;
final int firstBlockInstructions = 6;
@@ -136,7 +136,7 @@
}
}
- TestApplication codeWithCatchHandlers(boolean shouldThrow, boolean twoGuards) {
+ TestApplication codeWithCatchHandlers(boolean shouldThrow, boolean twoGuards) throws Exception {
SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
String secondGuard = twoGuards ?
@@ -192,7 +192,7 @@
assertEquals(throwing, block.hasCatchHandlers());
}
- public void runCatchHandlerTest(boolean codeThrows, boolean twoGuards) {
+ public void runCatchHandlerTest(boolean codeThrows, boolean twoGuards) throws Exception {
final int secondBlockInstructions = 4;
final int initialBlockCount = 5;
// Try split between all instructions in second block.
@@ -223,14 +223,15 @@
}
@Test
- public void catchHandlers() {
+ public void catchHandlers() throws Exception {
runCatchHandlerTest(false, false);
runCatchHandlerTest(true, false);
runCatchHandlerTest(false, true);
runCatchHandlerTest(true, true);
}
- public void runCatchHandlerSplitThreeTest(boolean codeThrows, boolean twoGuards) {
+ public void runCatchHandlerSplitThreeTest(boolean codeThrows, boolean twoGuards)
+ throws Exception {
final int secondBlockInstructions = 4;
final int initialBlockCount = 5;
// Try split out all instructions in second block.
@@ -262,14 +263,14 @@
}
@Test
- public void catchHandlersSplitThree() {
+ public void catchHandlersSplitThree() throws Exception {
runCatchHandlerSplitThreeTest(false, false);
runCatchHandlerSplitThreeTest(true, false);
runCatchHandlerSplitThreeTest(false, true);
runCatchHandlerSplitThreeTest(true, true);
}
- TestApplication codeWithIf(boolean hitTrueBranch) {
+ TestApplication codeWithIf(boolean hitTrueBranch) throws Exception {
SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
String returnType = "int";
@@ -309,7 +310,7 @@
return new TestApplication(application, method, code, valueNumberGenerator, options);
}
- public void runWithIfTest(boolean hitTrueBranch) {
+ public void runWithIfTest(boolean hitTrueBranch) throws Exception {
final int initialBlockCount = 4;
final int argumentInstructions = 2;
final int firstBlockInstructions = 3;
@@ -343,12 +344,12 @@
}
@Test
- public void withIf() {
+ public void withIf() throws Exception {
runWithIfTest(false);
runWithIfTest(true);
}
- public void splitBeforeReturn(boolean hitTrueBranch) {
+ public void splitBeforeReturn(boolean hitTrueBranch) throws Exception {
TestApplication test = codeWithIf(hitTrueBranch);
IRCode code = test.code;
// Locate the exit block and split before the return (the first instruction in the block).
@@ -375,12 +376,12 @@
}
@Test
- public void splitBeforeReturn() {
+ public void splitBeforeReturn() throws Exception {
splitBeforeReturn(false);
splitBeforeReturn(true);
}
- TestApplication codeWithSwitch(boolean hitCase) {
+ TestApplication codeWithSwitch(boolean hitCase) throws Exception {
SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
String returnType = "int";
@@ -428,7 +429,7 @@
return new TestApplication(application, method, code, valueNumberGenerator, options);
}
- public void runWithSwitchTest(boolean hitCase) {
+ public void runWithSwitchTest(boolean hitCase) throws Exception {
final int initialBlockCount = 5;
final int argumentInstructions = 1;
final int firstBlockInstructions = 2;
@@ -462,7 +463,7 @@
}
@Test
- public void withSwitch() {
+ public void withSwitch() throws Exception {
runWithSwitchTest(false);
runWithSwitchTest(true);
}
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/JasminTestBase.java b/src/test/java/com/android/tools/r8/jasmin/JasminTestBase.java
index 04d84e4..1573b34 100644
--- a/src/test/java/com/android/tools/r8/jasmin/JasminTestBase.java
+++ b/src/test/java/com/android/tools/r8/jasmin/JasminTestBase.java
@@ -5,6 +5,7 @@
import static org.junit.Assert.assertEquals;
+import com.android.tools.r8.CompilationException;
import com.android.tools.r8.R8;
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.ToolHelper.ProcessResult;
@@ -131,7 +132,7 @@
}
protected static DexApplication process(DexApplication app, InternalOptions options)
- throws IOException, ProguardRuleParserException, ExecutionException {
+ throws IOException, CompilationException, ExecutionException {
return ToolHelper.optimizeWithR8(app, new AppInfoWithSubtyping(app), options);
}
}
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 a296a3e..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.
@@ -302,7 +303,7 @@
Path secondaryDexFile = testDir.resolve("classes2.dex");
assertTrue(Files.exists(primaryDexFile));
boolean hasSecondaryDexFile = !allClasses && mode == CompilationMode.DEBUG;
- assertEquals(hasSecondaryDexFile, Files.exists(testDir.resolve(secondaryDexFile)));
+ assertEquals(hasSecondaryDexFile, Files.exists(secondaryDexFile));
byte[] content = Files.readAllBytes(primaryDexFile);
if (ref == null) {
ref = content;
@@ -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.
@@ -538,13 +539,13 @@
}
public static AndroidApp generateApplication(List<String> classes, int minApi, int methodCount)
- throws IOException, ExecutionException {
+ throws IOException, ExecutionException, CompilationException {
return generateApplication(classes, minApi, false, methodCount);
}
private static AndroidApp generateApplication(
List<String> classes, int minApi, boolean intermediate, int methodCount)
- throws IOException, ExecutionException {
+ throws IOException, ExecutionException, CompilationException {
Timing timing = new Timing("MainDexListTests");
InternalOptions options = new InternalOptions();
options.minApiLevel = minApi;
@@ -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..c7e0c18 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexTracingTest.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexTracingTest.java
@@ -133,7 +133,7 @@
.addLibraryFiles(Paths.get(EXAMPLE_BUILD_DIR, "multidexfakeframeworks" + JAR_EXTENSION),
Paths.get(ToolHelper.getAndroidJar(minSdk)))
.setOutputPath(out)
- .addMainDexRules(mainDexRules)
+ .addMainDexRulesFiles(mainDexRules)
.build();
CompilationResult result = ToolHelper.runR8WithFullResult(command, optionsConsumer);
List<String> resultMainDexList =
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/CatchSuccessorFallthroughTest.java b/src/test/java/com/android/tools/r8/smali/CatchSuccessorFallthroughTest.java
index ca30f76..909f574 100644
--- a/src/test/java/com/android/tools/r8/smali/CatchSuccessorFallthroughTest.java
+++ b/src/test/java/com/android/tools/r8/smali/CatchSuccessorFallthroughTest.java
@@ -26,7 +26,7 @@
public class CatchSuccessorFallthroughTest extends SmaliTestBase {
@Test
- public void catchSuccessorFallthroughTest() {
+ public void catchSuccessorFallthroughTest() throws Exception {
SmaliBuilder builder = new SmaliBuilder("Test");
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 0322407..e2006d0 100644
--- a/src/test/java/com/android/tools/r8/smali/SmaliTestBase.java
+++ b/src/test/java/com/android/tools/r8/smali/SmaliTestBase.java
@@ -6,10 +6,12 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
+import com.android.tools.r8.CompilationException;
import com.android.tools.r8.R8;
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;
@@ -386,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);
@@ -431,7 +433,7 @@
protected DexApplication processApplication(DexApplication application, InternalOptions options) {
try {
return ToolHelper.optimizeWithR8(application, new AppInfoWithSubtyping(application), options);
- } catch (IOException | ProguardRuleParserException | ExecutionException e) {
+ } catch (IOException | CompilationException | ExecutionException e) {
throw new RuntimeException(e);
}
}
@@ -513,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");
@@ -529,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");
@@ -541,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/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/gmscore_data.py b/tools/gmscore_data.py
index 47d9024..80e7853 100644
--- a/tools/gmscore_data.py
+++ b/tools/gmscore_data.py
@@ -90,6 +90,7 @@
},
'proguarded' : {
'inputs': ['%s_proguard.jar' % V9_PREFIX],
+ 'main-dex-list': os.path.join(V9_BASE, 'main_dex_list.txt'),
'pgmap': '%s_proguard.map' % V9_PREFIX,
'min-api' : ANDROID_L_API,
}
@@ -109,6 +110,7 @@
},
'proguarded' : {
'inputs': ['%s_proguard.jar' % V10_PREFIX],
+ 'main-dex-list': os.path.join(V10_BASE, 'main_dex_list.txt') ,
'pgmap': '%s_proguard.map' % V10_PREFIX,
'min-api' : ANDROID_L_API,
}
@@ -121,5 +123,11 @@
'%s/proguardsettings/GmsCore_proguard.config' % THIRD_PARTY],
'min-api' : ANDROID_L_API,
},
+ 'proguarded' : {
+ 'inputs': ['%s_proguard.jar' % LATEST_PREFIX],
+ 'main-dex-list': os.path.join(LATEST_BASE, 'main_dex_list.txt') ,
+ 'pgmap': '%s_proguard.map' % LATEST_PREFIX,
+ 'min-api' : ANDROID_L_API,
+ }
},
}
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/run-d8-on-gmscore.py b/tools/run-d8-on-gmscore.py
index e150278..63865d0 100755
--- a/tools/run-d8-on-gmscore.py
+++ b/tools/run-d8-on-gmscore.py
@@ -25,7 +25,7 @@
result.add_option('--version',
help = '',
default = 'v9',
- choices = ['v9', 'v10'])
+ choices = ['v9', 'v10', 'latest'])
result.add_option('--type',
help = '',
default = 'proguarded',
@@ -58,6 +58,9 @@
if not os.path.exists(outdir):
os.makedirs(outdir)
+ if 'main-dex-list' in values:
+ args.extend(['--main-dex-list', values['main-dex-list']])
+
if options.d8_flags:
args.extend(options.d8_flags.split(' '))
diff --git a/tools/test.py b/tools/test.py
index b0255cb..ab4b0d6 100755
--- a/tools/test.py
+++ b/tools/test.py
@@ -76,7 +76,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