Merge commit '7910f19ab8d52e9969806c1eb99e922480131c50' into dev-release
diff --git a/src/main/java/com/android/tools/r8/JdkClassFileProvider.java b/src/main/java/com/android/tools/r8/JdkClassFileProvider.java
new file mode 100644
index 0000000..5e35cf0
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/JdkClassFileProvider.java
@@ -0,0 +1,172 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8;
+
+import com.android.tools.r8.errors.CompilationError;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.origin.PathOrigin;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.FileUtils;
+import java.io.Closeable;
+import java.io.IOException;
+import java.net.URI;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.nio.file.FileSystem;
+import java.nio.file.FileSystems;
+import java.nio.file.Files;
+import java.nio.file.NoSuchFileException;
+import java.nio.file.Path;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Lazy Java class file resource provider loading class files from a JDK.
+ *
+ * <p>The descriptor index is built eagerly upon creating the provider and subsequent requests for
+ * resources in the descriptor set will then force the read of JDK content.
+ *
+ * <p>Currently only JDK's of version 9 or higher with a lib/jrt-fs.jar file present is supported.
+ */
+@Keep
+public class JdkClassFileProvider implements ClassFileResourceProvider, Closeable {
+ private Origin origin;
+ private final Set<String> descriptors = new HashSet<>();
+ private final Map<String, String> descriptorToModule = new HashMap<>();
+ private URLClassLoader jrtFsJarLoader;
+ private FileSystem jrtFs;
+
+ /**
+ * Creates a lazy class-file program-resource provider for the system modules of the running Java
+ * VM.
+ *
+ * <p>Only supported if the current VM is of version 9 or higher.
+ */
+ public static ClassFileResourceProvider fromSystemJdk() throws IOException {
+ return new JdkClassFileProvider();
+ }
+
+ /**
+ * Creates a lazy class-file program-resource provider for the system modules of a JDK.
+ *
+ * @param home Location of the JDK to read the program-resources from.
+ * <p>Only supported if the JDK to read is of version 9 or higher.
+ */
+ public static ClassFileResourceProvider fromSystemModulesJdk(Path home) throws IOException {
+ Path jrtFsJar = home.resolve("lib").resolve("jrt-fs.jar");
+ if (!Files.exists(jrtFsJar)) {
+ throw new NoSuchFileException(jrtFsJar.toString());
+ }
+ return new JdkClassFileProvider(home);
+ }
+
+ /**
+ * Creates a lazy class-file program-resource provider for the runtime of a JDK.
+ *
+ * <p>This will load the program-resources form the system modules for JDK of version 9 or higher.
+ *
+ * <p>This will load <code>rt.jar</code> for JDK of version 8 and lower.
+ *
+ * @param home Location of the JDK to read the program-resources from.
+ */
+ public static ClassFileResourceProvider fromJdkHome(Path home) throws IOException {
+ Path jrtFsJar = home.resolve("lib").resolve("jrt-fs.jar");
+ if (Files.exists(jrtFsJar)) {
+ return JdkClassFileProvider.fromSystemModulesJdk(home);
+ }
+ // JDK has rt.jar in jre/lib/rt.jar.
+ Path rtJar = home.resolve("jre").resolve("lib").resolve("rt.jar");
+ if (Files.exists(rtJar)) {
+ return fromJavaRuntimeJar(rtJar);
+ }
+ // JRE has rt.jar in lib/rt.jar.
+ rtJar = home.resolve("lib").resolve("rt.jar");
+ if (Files.exists(rtJar)) {
+ return fromJavaRuntimeJar(rtJar);
+ }
+ throw new IOException("Path " + home + " does not look like a Java home");
+ }
+
+ public static ClassFileResourceProvider fromJavaRuntimeJar(Path archive) throws IOException {
+ return new ArchiveClassFileProvider(archive);
+ }
+
+ private JdkClassFileProvider() throws IOException {
+ origin = Origin.unknown();
+ collectDescriptors(FileSystems.newFileSystem(URI.create("jrt:/"), Collections.emptyMap()));
+ }
+
+ /**
+ * Creates a lazy class-file program-resource provider for the system modules of a JDK.
+ *
+ * @param home Location of the JDK to read the program-resources from.
+ * <p>Only supported if the JDK to read is of version 9 or higher.
+ */
+ private JdkClassFileProvider(Path home) throws IOException {
+ origin = new PathOrigin(home);
+ Path jrtFsJar = home.resolve("lib").resolve("jrt-fs.jar");
+ assert Files.exists(jrtFsJar);
+ jrtFsJarLoader = new URLClassLoader(new URL[] {jrtFsJar.toUri().toURL()});
+ FileSystem jrtFs =
+ FileSystems.newFileSystem(URI.create("jrt:/"), Collections.emptyMap(), jrtFsJarLoader);
+ collectDescriptors(jrtFs);
+ }
+
+ private void collectDescriptors(FileSystem jrtFs) throws IOException {
+ this.jrtFs = jrtFs;
+ Files.walk(jrtFs.getPath("/modules"))
+ .forEach(
+ path -> {
+ if (FileUtils.isClassFile(path)) {
+ DescriptorUtils.ModuleAndDescriptor moduleAndDescriptor =
+ DescriptorUtils.guessJrtModuleAndTypeDescriptor(path.toString());
+ descriptorToModule.put(
+ moduleAndDescriptor.getDescriptor(), moduleAndDescriptor.getModule());
+ descriptors.add(moduleAndDescriptor.getDescriptor());
+ }
+ });
+ }
+
+ @Override
+ public Set<String> getClassDescriptors() {
+ return Collections.unmodifiableSet(descriptors);
+ }
+
+ @Override
+ public ProgramResource getProgramResource(String descriptor) {
+ if (!descriptors.contains(descriptor)) {
+ return null;
+ }
+ try {
+ return ProgramResource.fromBytes(
+ Origin.unknown(),
+ ProgramResource.Kind.CF,
+ Files.readAllBytes(
+ jrtFs.getPath(
+ "modules",
+ descriptorToModule.get(descriptor),
+ DescriptorUtils.getPathFromDescriptor(descriptor))),
+ Collections.singleton(descriptor));
+ } catch (IOException e) {
+ throw new CompilationError("Failed to read '" + descriptor, origin);
+ }
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ close();
+ super.finalize();
+ }
+
+ @Override
+ public void close() throws IOException {
+ jrtFs.close();
+ if (jrtFsJarLoader != null) {
+ jrtFsJarLoader.close();
+ }
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/L8.java b/src/main/java/com/android/tools/r8/L8.java
index 4403375..dcb4f9a 100644
--- a/src/main/java/com/android/tools/r8/L8.java
+++ b/src/main/java/com/android/tools/r8/L8.java
@@ -20,7 +20,6 @@
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.ExceptionUtils;
import com.android.tools.r8.utils.InternalOptions;
-import com.android.tools.r8.utils.StringDiagnostic;
import com.android.tools.r8.utils.ThreadUtils;
import com.android.tools.r8.utils.Timing;
import java.io.IOException;
@@ -90,11 +89,10 @@
desugar(app, options, executorService);
});
if (shrink) {
- options.reporter.warning(
- new StringDiagnostic("Shrinking of desugared library is work in progress."));
InternalOptions r8Options = r8Command.getInternalOptions();
- r8Options.testing.keepInheritedInterfaceMethods =
- options.testing.keepInheritedInterfaceMethods;
+ // TODO(b/143590191): Remove the hack and make it work by default.
+ // Temporary hack so that keeping an interface keeps the superinterfaces.
+ r8Options.testing.keepInheritedInterfaceMethods = true;
// Disable outlining for R8 when called from L8.
r8Options.outline.enabled = false;
R8.runForTesting(r8Command.getInputApp(), r8Options);
diff --git a/src/main/java/com/android/tools/r8/L8Command.java b/src/main/java/com/android/tools/r8/L8Command.java
index 8fb3ac2..d96a5a1 100644
--- a/src/main/java/com/android/tools/r8/L8Command.java
+++ b/src/main/java/com/android/tools/r8/L8Command.java
@@ -163,10 +163,8 @@
}
public boolean isShrinking() {
- // TODO(b/143431384): Re-enable shrinking.
- return false;
// Answers true if keep rules, even empty, are provided.
- // return !proguardConfigStrings.isEmpty() || !proguardConfigFiles.isEmpty();
+ return !proguardConfigStrings.isEmpty() || !proguardConfigFiles.isEmpty();
}
@Override
diff --git a/src/main/java/com/android/tools/r8/cf/CfRegisterAllocator.java b/src/main/java/com/android/tools/r8/cf/CfRegisterAllocator.java
index ec6710b..93fc650 100644
--- a/src/main/java/com/android/tools/r8/cf/CfRegisterAllocator.java
+++ b/src/main/java/com/android/tools/r8/cf/CfRegisterAllocator.java
@@ -229,8 +229,7 @@
// Find a free register that is not used by an inactive interval that overlaps with
// unhandledInterval.
if (tryHint(unhandledInterval)) {
- assignRegisterToUnhandledInterval(
- unhandledInterval, unhandledInterval.getHint().getRegister());
+ assignRegisterToUnhandledInterval(unhandledInterval, unhandledInterval.getHint());
} else {
boolean wide = unhandledInterval.getType().isWide();
int register;
@@ -304,9 +303,9 @@
private void updateHints(LiveIntervals intervals) {
for (Phi phi : intervals.getValue().uniquePhiUsers()) {
if (!phi.isValueOnStack()) {
- phi.getLiveIntervals().setHint(intervals);
+ phi.getLiveIntervals().setHint(intervals, unhandled);
for (Value value : phi.getOperands()) {
- value.getLiveIntervals().setHint(intervals);
+ value.getLiveIntervals().setHint(intervals, unhandled);
}
}
}
@@ -317,7 +316,7 @@
return false;
}
boolean isWide = unhandled.getType().isWide();
- int hintRegister = unhandled.getHint().getRegister();
+ int hintRegister = unhandled.getHint();
if (freeRegisters.contains(hintRegister)
&& (!isWide || freeRegisters.contains(hintRegister + 1))) {
for (LiveIntervals inactive : inactive) {
diff --git a/src/main/java/com/android/tools/r8/graph/AppInfo.java b/src/main/java/com/android/tools/r8/graph/AppInfo.java
index 6c8d4cf..3066b4d 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfo.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfo.java
@@ -6,13 +6,11 @@
import com.android.tools.r8.graph.ResolutionResult.ArrayCloneMethodResult;
import com.android.tools.r8.graph.ResolutionResult.ClassNotFoundResult;
import com.android.tools.r8.graph.ResolutionResult.IncompatibleClassResult;
-import com.android.tools.r8.graph.ResolutionResult.MultiResolutionResult;
import com.android.tools.r8.graph.ResolutionResult.NoSuchMethodResult;
import com.android.tools.r8.graph.ResolutionResult.SingleResolutionResult;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.utils.InternalOptions;
-import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableMap.Builder;
import java.util.ArrayList;
@@ -211,8 +209,7 @@
public DexEncodedMethod lookupSuperTarget(DexMethod method, DexType invocationContext) {
assert checkIfObsolete();
// Make sure we are not chasing NotFoundError.
- ResolutionResult resolutionResult = resolveMethod(method.holder, method);
- if (resolutionResult.asListOfTargets().isEmpty()) {
+ if (resolveMethod(method.holder, method).getSingleTarget() == null) {
return null;
}
// According to
@@ -226,7 +223,7 @@
if (contextClass == null || contextClass.superType == null) {
return null;
}
- resolutionResult = resolveMethod(contextClass.superType, method);
+ ResolutionResult resolutionResult = resolveMethod(contextClass.superType, method);
DexEncodedMethod target = resolutionResult.getSingleTarget();
return target == null || !target.isStatic() ? target : null;
}
@@ -699,8 +696,7 @@
if (nonAbstractMethods.size() == 1) {
return new SingleResolutionResult(nonAbstractMethods.get(0));
}
- // TODO(b/144085169): In the case of multiple non-abstract methods resolution should fail.
- return new MultiResolutionResult(ImmutableList.copyOf(nonAbstractMethods));
+ return new IncompatibleClassResult(Collections.emptyList(), nonAbstractMethods);
}
}
diff --git a/src/main/java/com/android/tools/r8/graph/AppView.java b/src/main/java/com/android/tools/r8/graph/AppView.java
index 078410a..90cc0f4 100644
--- a/src/main/java/com/android/tools/r8/graph/AppView.java
+++ b/src/main/java/com/android/tools/r8/graph/AppView.java
@@ -77,7 +77,7 @@
this.options = options;
this.rewritePrefix = mapper;
- if (enableWholeProgramOptimizations() && options.enableCallSiteOptimizationInfoPropagation) {
+ if (enableWholeProgramOptimizations() && options.enablePropagationOfDynamicTypesAtCallSites) {
this.callSiteOptimizationInfoPropagator =
new CallSiteOptimizationInfoPropagator(withLiveness());
} else {
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 988bf75..0acef70 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClass.java
@@ -560,6 +560,11 @@
return lookupTarget(directMethods, method);
}
+ /** Find direct method in this class matching {@param predicate}. */
+ public DexEncodedMethod lookupDirectMethod(Predicate<DexEncodedMethod> predicate) {
+ return PredicateUtils.findFirst(directMethods, predicate);
+ }
+
/** Find virtual method in this class matching {@param method}. */
public DexEncodedMethod lookupVirtualMethod(DexMethod method) {
return lookupTarget(virtualMethods, method);
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedField.java b/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
index b1a96c1..1764634 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
@@ -3,10 +3,16 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.graph;
+import static com.android.tools.r8.ir.analysis.type.Nullability.maybeNull;
+
import com.android.tools.r8.dex.IndexedItemCollection;
import com.android.tools.r8.dex.MixedSectionCollection;
-import com.android.tools.r8.ir.code.ConstInstruction;
+import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.value.AbstractValue;
+import com.android.tools.r8.ir.analysis.value.SingleValue;
import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.TypeAndLocalInfoSupplier;
import com.android.tools.r8.ir.optimize.info.DefaultFieldOptimizationInfo;
import com.android.tools.r8.ir.optimize.info.FieldOptimizationInfo;
import com.android.tools.r8.ir.optimize.info.MutableFieldOptimizationInfo;
@@ -152,7 +158,7 @@
*
* <p>NOTE: It is the responsibility of the caller to check if this field is pinned or not.
*/
- public ConstInstruction valueAsConstInstruction(
+ public Instruction valueAsConstInstruction(
IRCode code, DebugLocalInfo local, AppView<AppInfoWithLiveness> appView) {
boolean isWritten = appView.appInfo().isFieldWrittenByFieldPutInstruction(this);
if (!isWritten) {
@@ -161,8 +167,19 @@
return value.asConstInstruction(appView, code, local);
}
- // The only way to figure out whether the DexValue contains the final value is ensure the value
- // is not the default or check that <clinit> is not present.
+ // Check if we have a single value for the field according to the field optimization info.
+ AbstractValue abstractValue = getOptimizationInfo().getAbstractValue();
+ if (abstractValue.isSingleValue()) {
+ SingleValue singleValue = abstractValue.asSingleValue();
+ if (singleValue.isMaterializableInContext(appView, code.method.method.holder)) {
+ TypeLatticeElement type = TypeLatticeElement.fromDexType(field.type, maybeNull(), appView);
+ return singleValue.createMaterializingInstruction(
+ appView, code, TypeAndLocalInfoSupplier.create(type, local));
+ }
+ }
+
+ // The only way to figure out whether the static value contains the final value is ensure the
+ // value is not the default or check that <clinit> is not present.
if (accessFlags.isFinal() && isStatic()) {
DexClass clazz = appView.definitionFor(field.holder);
if (clazz == null || clazz.hasClassInitializer()) {
diff --git a/src/main/java/com/android/tools/r8/graph/ResolutionResult.java b/src/main/java/com/android/tools/r8/graph/ResolutionResult.java
index 23b8d02..acc5942 100644
--- a/src/main/java/com/android/tools/r8/graph/ResolutionResult.java
+++ b/src/main/java/com/android/tools/r8/graph/ResolutionResult.java
@@ -6,6 +6,7 @@
import com.android.tools.r8.utils.InternalOptions;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Sets;
+import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Set;
@@ -17,6 +18,10 @@
return false;
}
+ public FailedResolutionResult asFailedResolution() {
+ return null;
+ }
+
// TODO(b/140214802): Remove this method as its usage is questionable.
public abstract DexEncodedMethod asResultOfResolve();
@@ -304,6 +309,17 @@
}
@Override
+ public FailedResolutionResult asFailedResolution() {
+ return this;
+ }
+
+ public void forEachFailureDependency(
+ Consumer<DexProgramClass> classesCausingFailure,
+ Consumer<DexEncodedMethod> methodsCausingFailure) {
+ // Default failure has no dependencies.
+ }
+
+ @Override
public boolean isValidVirtualTarget(InternalOptions options) {
return false;
}
@@ -325,8 +341,26 @@
public static class IncompatibleClassResult extends FailedResolutionResult {
static final IncompatibleClassResult INSTANCE = new IncompatibleClassResult();
+ private final Collection<DexProgramClass> classesCausingError;
+ private final Collection<DexEncodedMethod> methodsCausingError;
+
private IncompatibleClassResult() {
- // Intentionally left empty.
+ this(Collections.emptyList(), Collections.emptyList());
+ }
+
+ IncompatibleClassResult(
+ Collection<DexProgramClass> classesCausingError,
+ Collection<DexEncodedMethod> methodsCausingError) {
+ this.classesCausingError = classesCausingError;
+ this.methodsCausingError = methodsCausingError;
+ }
+
+ @Override
+ public void forEachFailureDependency(
+ Consumer<DexProgramClass> classesCausingFailure,
+ Consumer<DexEncodedMethod> methodsCausingFailure) {
+ this.classesCausingError.forEach(classesCausingFailure);
+ this.methodsCausingError.forEach(methodsCausingFailure);
}
}
@@ -336,5 +370,8 @@
private NoSuchMethodResult() {
// Intentionally left empty.
}
+
+ // TODO(b/144085169): Consider if the resolution resulting in a NoSuchMethodError should
+ // be preserved by ensuring its class is marked. Otherwise, the error may become ClassNotFound.
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/FieldValueAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/FieldValueAnalysis.java
index 1079da1..35f6661 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/FieldValueAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/FieldValueAnalysis.java
@@ -25,12 +25,12 @@
import com.android.tools.r8.ir.code.BasicBlock;
import com.android.tools.r8.ir.code.DominatorTree;
import com.android.tools.r8.ir.code.DominatorTree.Assumption;
+import com.android.tools.r8.ir.code.FieldInstruction;
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.code.Instruction;
import com.android.tools.r8.ir.code.InstructionIterator;
import com.android.tools.r8.ir.code.InvokeDirect;
import com.android.tools.r8.ir.code.NewInstance;
-import com.android.tools.r8.ir.code.StaticPut;
import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
@@ -56,9 +56,11 @@
AppView<AppInfoWithLiveness> appView,
IRCode code,
OptimizationFeedback feedback,
+ DexProgramClass clazz,
DexEncodedMethod method) {
+ assert clazz.type == method.method.holder;
this.appView = appView;
- this.clazz = appView.definitionFor(method.method.holder).asProgramClass();
+ this.clazz = clazz;
this.code = code;
this.feedback = feedback;
this.method = method;
@@ -67,11 +69,26 @@
public static void run(
AppView<?> appView, IRCode code, OptimizationFeedback feedback, DexEncodedMethod method) {
- if (appView.enableWholeProgramOptimizations() && method.isClassInitializer()) {
- assert appView.appInfo().hasLiveness();
- new FieldValueAnalysis(appView.withLiveness(), code, feedback, method)
- .computeFieldOptimizationInfo();
+ if (!appView.enableWholeProgramOptimizations()) {
+ return;
}
+ assert appView.appInfo().hasLiveness();
+ if (!method.isInitializer()) {
+ return;
+ }
+ DexProgramClass clazz = appView.definitionFor(method.method.holder).asProgramClass();
+ if (method.isInstanceInitializer()) {
+ DexEncodedMethod otherInstanceInitializer =
+ clazz.lookupDirectMethod(other -> other.isInstanceInitializer() && other != method);
+ if (otherInstanceInitializer != null) {
+ // Conservatively bail out.
+ // TODO(b/125282093): Handle multiple instance initializers on the same class.
+ return;
+ }
+ }
+
+ new FieldValueAnalysis(appView.withLiveness(), code, feedback, clazz, method)
+ .computeFieldOptimizationInfo();
}
private Map<BasicBlock, AbstractFieldSet> getOrCreateFieldsMaybeReadBeforeBlockInclusive() {
@@ -86,55 +103,51 @@
AppInfoWithLiveness appInfo = appView.appInfo();
DominatorTree dominatorTree = null;
- if (method.isClassInitializer()) {
- DexType context = method.method.holder;
+ DexType context = method.method.holder;
- // Find all the static-put instructions that assign a field in the enclosing class which is
- // guaranteed to be assigned only in the current initializer.
- boolean isStraightLineCode = true;
- Map<DexEncodedField, LinkedList<StaticPut>> staticPutsPerField = new IdentityHashMap<>();
- for (Instruction instruction : code.instructions()) {
- if (instruction.isStaticPut()) {
- StaticPut staticPut = instruction.asStaticPut();
- DexField field = staticPut.getField();
- DexEncodedField encodedField = appInfo.resolveField(field);
- if (encodedField != null
- && encodedField.field.holder == context
- && appInfo.isStaticFieldWrittenOnlyInEnclosingStaticInitializer(encodedField)) {
- staticPutsPerField
- .computeIfAbsent(encodedField, ignore -> new LinkedList<>())
- .add(staticPut);
- }
- }
- if (instruction.isJumpInstruction()) {
- if (!instruction.isGoto() && !instruction.isReturn()) {
- isStraightLineCode = false;
- }
+ // Find all the static-put instructions that assign a field in the enclosing class which is
+ // guaranteed to be assigned only in the current initializer.
+ boolean isStraightLineCode = true;
+ Map<DexEncodedField, LinkedList<FieldInstruction>> putsPerField = new IdentityHashMap<>();
+ for (Instruction instruction : code.instructions()) {
+ if (instruction.isFieldPut()) {
+ FieldInstruction fieldPut = instruction.asFieldInstruction();
+ DexField field = fieldPut.getField();
+ DexEncodedField encodedField = appInfo.resolveField(field);
+ if (encodedField != null
+ && encodedField.field.holder == context
+ && appInfo.isFieldOnlyWrittenInMethod(encodedField, method)) {
+ putsPerField.computeIfAbsent(encodedField, ignore -> new LinkedList<>()).add(fieldPut);
}
}
-
- List<BasicBlock> normalExitBlocks = code.computeNormalExitBlocks();
- for (Entry<DexEncodedField, LinkedList<StaticPut>> entry : staticPutsPerField.entrySet()) {
- DexEncodedField encodedField = entry.getKey();
- LinkedList<StaticPut> staticPuts = entry.getValue();
- if (staticPuts.size() > 1) {
- continue;
+ if (instruction.isJumpInstruction()) {
+ if (!instruction.isGoto() && !instruction.isReturn()) {
+ isStraightLineCode = false;
}
- StaticPut staticPut = staticPuts.getFirst();
- if (!isStraightLineCode) {
- if (dominatorTree == null) {
- dominatorTree = new DominatorTree(code, Assumption.NO_UNREACHABLE_BLOCKS);
- }
- if (!dominatorTree.dominatesAllOf(staticPut.getBlock(), normalExitBlocks)) {
- continue;
- }
- }
- if (fieldMaybeReadBeforeInstruction(encodedField, staticPut)) {
- continue;
- }
- updateFieldOptimizationInfo(encodedField, staticPut.value());
}
}
+
+ List<BasicBlock> normalExitBlocks = code.computeNormalExitBlocks();
+ for (Entry<DexEncodedField, LinkedList<FieldInstruction>> entry : putsPerField.entrySet()) {
+ DexEncodedField encodedField = entry.getKey();
+ LinkedList<FieldInstruction> fieldPuts = entry.getValue();
+ if (fieldPuts.size() > 1) {
+ continue;
+ }
+ FieldInstruction fieldPut = fieldPuts.getFirst();
+ if (!isStraightLineCode) {
+ if (dominatorTree == null) {
+ dominatorTree = new DominatorTree(code, Assumption.NO_UNREACHABLE_BLOCKS);
+ }
+ if (!dominatorTree.dominatesAllOf(fieldPut.getBlock(), normalExitBlocks)) {
+ continue;
+ }
+ }
+ if (fieldMaybeReadBeforeInstruction(encodedField, fieldPut)) {
+ continue;
+ }
+ updateFieldOptimizationInfo(encodedField, fieldPut.value());
+ }
}
private boolean fieldMaybeReadBeforeInstruction(
@@ -263,7 +276,7 @@
Value root = value.getAliasedValue();
AbstractValue abstractValue = computeAbstractValue(root);
if (!abstractValue.isUnknown()) {
- feedback.recordFieldHasAbstractValue(field, abstractValue);
+ feedback.recordFieldHasAbstractValue(field, appView, abstractValue);
}
// Dynamic upper bound type.
@@ -290,6 +303,9 @@
return singleEnumValue;
}
}
+ if (!value.isPhi()) {
+ return value.definition.getAbstractValue(appView, clazz.type);
+ }
return UnknownValue.getInstance();
}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValue.java
index eae4fc8..f560b29 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValue.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValue.java
@@ -6,6 +6,8 @@
public abstract class AbstractValue {
+ public abstract boolean isNonTrivial();
+
/**
* Returns true if this abstract value represents a single concrete value (i.e., the
* concretization of this abstract value has size 1).
@@ -46,9 +48,19 @@
return false;
}
+ public AbstractValue join(AbstractValue other) {
+ if (this.equals(other)) {
+ return this;
+ }
+ return UnknownValue.getInstance();
+ }
+
@Override
public abstract boolean equals(Object o);
@Override
public abstract int hashCode();
+
+ @Override
+ public abstract String toString();
}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleEnumValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleEnumValue.java
index 8573c36..1f7a76b 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleEnumValue.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleEnumValue.java
@@ -4,13 +4,20 @@
package com.android.tools.r8.ir.analysis.value;
-import com.android.tools.r8.errors.Unreachable;
+import static com.android.tools.r8.ir.analysis.type.Nullability.maybeNull;
+import static com.android.tools.r8.optimize.MemberRebindingAnalysis.isMemberVisibleFromOriginalContext;
+
import com.android.tools.r8.graph.AppInfoWithSubtyping;
import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.StaticGet;
import com.android.tools.r8.ir.code.TypeAndLocalInfoSupplier;
+import com.android.tools.r8.ir.code.Value;
public class SingleEnumValue extends SingleValue {
@@ -42,10 +49,25 @@
}
@Override
+ public String toString() {
+ return "SingleEnumValue(" + field.toSourceString() + ")";
+ }
+
+ @Override
public Instruction createMaterializingInstruction(
AppView<? extends AppInfoWithSubtyping> appView,
IRCode code,
TypeAndLocalInfoSupplier info) {
- throw new Unreachable("unless we store single enum as a method's returned value.");
+ TypeLatticeElement type = TypeLatticeElement.fromDexType(field.type, maybeNull(), appView);
+ assert type.lessThanOrEqual(info.getTypeLattice(), appView);
+ Value outValue = code.createValue(type, info.getLocalInfo());
+ return new StaticGet(outValue, field);
+ }
+
+ @Override
+ public boolean isMaterializableInContext(AppView<?> appView, DexType context) {
+ DexEncodedField encodedField = appView.appInfo().resolveField(field);
+ return isMemberVisibleFromOriginalContext(
+ appView, context, encodedField.field.holder, encodedField.accessFlags);
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleNumberValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleNumberValue.java
index 949dbb5..28ac1a2 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleNumberValue.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleNumberValue.java
@@ -7,6 +7,7 @@
import com.android.tools.r8.graph.AppInfoWithSubtyping;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DebugLocalInfo;
+import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
import com.android.tools.r8.ir.code.ConstNumber;
import com.android.tools.r8.ir.code.IRCode;
@@ -48,6 +49,11 @@
}
@Override
+ public String toString() {
+ return "SingleNumberValue(" + value + ")";
+ }
+
+ @Override
public Instruction createMaterializingInstruction(
AppView<? extends AppInfoWithSubtyping> appView, IRCode code, TypeAndLocalInfoSupplier info) {
TypeLatticeElement typeLattice = info.getTypeLattice();
@@ -58,4 +64,9 @@
typeLattice.isReference() ? TypeLatticeElement.NULL : typeLattice, debugLocalInfo);
return new ConstNumber(returnedValue, value);
}
+
+ @Override
+ public boolean isMaterializableInContext(AppView<?> appView, DexType context) {
+ return true;
+ }
}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleStringValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleStringValue.java
index d2a8726..417fe19 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleStringValue.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleStringValue.java
@@ -11,6 +11,7 @@
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DebugLocalInfo;
import com.android.tools.r8.graph.DexString;
+import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
import com.android.tools.r8.ir.code.BasicBlock.ThrowingInfo;
import com.android.tools.r8.ir.code.ConstString;
@@ -53,6 +54,11 @@
}
@Override
+ public String toString() {
+ return "SingleStringValue(" + string + ")";
+ }
+
+ @Override
public Instruction createMaterializingInstruction(
AppView<? extends AppInfoWithSubtyping> appView,
IRCode code,
@@ -73,4 +79,9 @@
assert !instruction.instructionInstanceCanThrow();
return instruction;
}
+
+ @Override
+ public boolean isMaterializableInContext(AppView<?> appView, DexType context) {
+ return true;
+ }
}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleValue.java
index 9972328..44aa581 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleValue.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleValue.java
@@ -6,6 +6,7 @@
import com.android.tools.r8.graph.AppInfoWithSubtyping;
import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.code.Instruction;
import com.android.tools.r8.ir.code.TypeAndLocalInfoSupplier;
@@ -13,6 +14,11 @@
public abstract class SingleValue extends AbstractValue {
@Override
+ public boolean isNonTrivial() {
+ return true;
+ }
+
+ @Override
public boolean isSingleValue() {
return true;
}
@@ -22,8 +28,12 @@
return this;
}
+ /**
+ * Note that calls to this method should generally be guarded by {@link
+ * #isMaterializableInContext}.
+ */
public abstract Instruction createMaterializingInstruction(
- AppView<? extends AppInfoWithSubtyping> appView,
- IRCode code,
- TypeAndLocalInfoSupplier info);
+ AppView<? extends AppInfoWithSubtyping> appView, IRCode code, TypeAndLocalInfoSupplier info);
+
+ public abstract boolean isMaterializableInContext(AppView<?> appView, DexType context);
}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/UnknownValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/UnknownValue.java
index bbae7bd..216ea99 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/UnknownValue.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/UnknownValue.java
@@ -15,6 +15,11 @@
}
@Override
+ public boolean isNonTrivial() {
+ return false;
+ }
+
+ @Override
public boolean isUnknown() {
return true;
}
@@ -28,4 +33,9 @@
public int hashCode() {
return System.identityHashCode(this);
}
+
+ @Override
+ public String toString() {
+ return "UnknownValue";
+ }
}
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 e7eb6bd..84f33f3 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
@@ -22,6 +22,7 @@
import com.android.tools.r8.ir.analysis.constant.ConstLatticeElement;
import com.android.tools.r8.ir.analysis.constant.LatticeElement;
import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.value.AbstractValue;
import com.android.tools.r8.ir.conversion.CfBuilder;
import com.android.tools.r8.ir.conversion.DexBuilder;
import com.android.tools.r8.utils.InternalOutputMode;
@@ -330,4 +331,9 @@
public boolean outTypeKnownToBeBoolean(Set<Phi> seen) {
return this.value == 0 || this.value == 1;
}
+
+ @Override
+ public AbstractValue getAbstractValue(AppView<?> appView, DexType context) {
+ return appView.abstractValueFactory().createSingleNumberValue(value);
+ }
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/ConstString.java b/src/main/java/com/android/tools/r8/ir/code/ConstString.java
index a08ea04..a80bc66 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ConstString.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ConstString.java
@@ -12,6 +12,8 @@
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.ir.analysis.type.Nullability;
import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.value.AbstractValue;
+import com.android.tools.r8.ir.analysis.value.UnknownValue;
import com.android.tools.r8.ir.code.BasicBlock.ThrowingInfo;
import com.android.tools.r8.ir.conversion.CfBuilder;
import com.android.tools.r8.ir.conversion.DexBuilder;
@@ -159,4 +161,12 @@
public boolean instructionMayTriggerMethodInvocation(AppView<?> appView, DexType context) {
return false;
}
+
+ @Override
+ public AbstractValue getAbstractValue(AppView<?> appView, DexType context) {
+ if (!instructionInstanceCanThrow()) {
+ return appView.abstractValueFactory().createSingleStringValue(value);
+ }
+ return UnknownValue.getInstance();
+ }
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/DebugLocalWrite.java b/src/main/java/com/android/tools/r8/ir/code/DebugLocalWrite.java
index 4e1e00a..453a564 100644
--- a/src/main/java/com/android/tools/r8/ir/code/DebugLocalWrite.java
+++ b/src/main/java/com/android/tools/r8/ir/code/DebugLocalWrite.java
@@ -75,4 +75,11 @@
public boolean isAllowedAfterThrowingInstruction() {
return true;
}
+
+ @Override
+ public boolean verifyTypes(AppView<?> appView) {
+ super.verifyTypes(appView);
+ assert src().getTypeLattice().lessThanOrEqual(outValue().getTypeLattice(), appView);
+ return true;
+ }
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/FieldInstruction.java b/src/main/java/com/android/tools/r8/ir/code/FieldInstruction.java
index 0e0c975..3cae1e4 100644
--- a/src/main/java/com/android/tools/r8/ir/code/FieldInstruction.java
+++ b/src/main/java/com/android/tools/r8/ir/code/FieldInstruction.java
@@ -18,6 +18,8 @@
import com.android.tools.r8.ir.analysis.fieldvalueanalysis.EmptyFieldSet;
import com.android.tools.r8.ir.analysis.fieldvalueanalysis.UnknownFieldSet;
import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.value.AbstractValue;
+import com.android.tools.r8.ir.analysis.value.UnknownValue;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import java.util.Collections;
import java.util.List;
@@ -201,4 +203,14 @@
return false;
}
+
+ @Override
+ public AbstractValue getAbstractValue(AppView<?> appView, DexType context) {
+ assert isFieldGet();
+ DexEncodedField field = appView.appInfo().resolveField(getField());
+ if (field != null) {
+ return field.getOptimizationInfo().getAbstractValue();
+ }
+ return UnknownValue.getInstance();
+ }
}
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 7452a66..228dc2b 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
@@ -21,6 +21,8 @@
import com.android.tools.r8.ir.analysis.fieldvalueanalysis.EmptyFieldSet;
import com.android.tools.r8.ir.analysis.fieldvalueanalysis.UnknownFieldSet;
import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.value.AbstractValue;
+import com.android.tools.r8.ir.analysis.value.UnknownValue;
import com.android.tools.r8.ir.code.Assume.DynamicTypeAssumption;
import com.android.tools.r8.ir.code.Assume.NoAssumption;
import com.android.tools.r8.ir.code.Assume.NonNullAssumption;
@@ -138,6 +140,11 @@
return oldOutValue;
}
+ public AbstractValue getAbstractValue(AppView<?> appView, DexType context) {
+ assert hasOutValue();
+ return UnknownValue.getInstance();
+ }
+
@Override
public TypeLatticeElement getTypeLattice() {
if (hasOutValue()) {
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeMethod.java b/src/main/java/com/android/tools/r8/ir/code/InvokeMethod.java
index c6f919d..484c4e3 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeMethod.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeMethod.java
@@ -15,6 +15,8 @@
import com.android.tools.r8.ir.analysis.fieldvalueanalysis.AbstractFieldSet;
import com.android.tools.r8.ir.analysis.modeling.LibraryMethodReadSetModeling;
import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
+import com.android.tools.r8.ir.analysis.value.AbstractValue;
+import com.android.tools.r8.ir.analysis.value.UnknownValue;
import com.android.tools.r8.ir.optimize.DefaultInliningOracle;
import com.android.tools.r8.ir.optimize.Inliner.InlineAction;
import com.android.tools.r8.ir.optimize.Inliner.Reason;
@@ -187,4 +189,14 @@
public AbstractFieldSet readSet(AppView<?> appView, DexType context) {
return LibraryMethodReadSetModeling.getModeledReadSetOrUnknown(this, appView.dexItemFactory());
}
+
+ @Override
+ public AbstractValue getAbstractValue(AppView<?> appView, DexType context) {
+ assert hasOutValue();
+ DexEncodedMethod method = lookupSingleTarget(appView, context);
+ if (method != null) {
+ return method.getOptimizationInfo().getAbstractReturnValue();
+ }
+ return UnknownValue.getInstance();
+ }
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/Move.java b/src/main/java/com/android/tools/r8/ir/code/Move.java
index 9054d07..3478645 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Move.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Move.java
@@ -126,7 +126,10 @@
@Override
public boolean verifyTypes(AppView<?> appView) {
super.verifyTypes(appView);
- assert src().getTypeLattice().equals(outValue().getTypeLattice());
+ // DebugLocalWrite defines it's own verification of types but should be allowed to call super.
+ if (!this.isDebugLocalWrite()) {
+ assert src().getTypeLattice().equals(outValue().getTypeLattice());
+ }
return true;
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/TypeAndLocalInfoSupplier.java b/src/main/java/com/android/tools/r8/ir/code/TypeAndLocalInfoSupplier.java
index f5ca688..0d5cfd6 100644
--- a/src/main/java/com/android/tools/r8/ir/code/TypeAndLocalInfoSupplier.java
+++ b/src/main/java/com/android/tools/r8/ir/code/TypeAndLocalInfoSupplier.java
@@ -10,4 +10,19 @@
public interface TypeAndLocalInfoSupplier {
DebugLocalInfo getLocalInfo();
TypeLatticeElement getTypeLattice();
+
+ static TypeAndLocalInfoSupplier create(TypeLatticeElement type, DebugLocalInfo local) {
+ return new TypeAndLocalInfoSupplier() {
+
+ @Override
+ public DebugLocalInfo getLocalInfo() {
+ return local;
+ }
+
+ @Override
+ public TypeLatticeElement getTypeLattice() {
+ return type;
+ }
+ };
+ }
}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/FieldOptimizationFeedback.java b/src/main/java/com/android/tools/r8/ir/conversion/FieldOptimizationFeedback.java
index ceaf87e..99c2933 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/FieldOptimizationFeedback.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/FieldOptimizationFeedback.java
@@ -4,10 +4,12 @@
package com.android.tools.r8.ir.conversion;
+import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.ir.analysis.type.ClassTypeLatticeElement;
import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
import com.android.tools.r8.ir.analysis.value.AbstractValue;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
public interface FieldOptimizationFeedback {
@@ -21,5 +23,6 @@
void markFieldBitsRead(DexEncodedField field, int bitsRead);
- void recordFieldHasAbstractValue(DexEncodedField field, AbstractValue abstractValue);
+ void recordFieldHasAbstractValue(
+ DexEncodedField field, AppView<AppInfoWithLiveness> appView, AbstractValue abstractValue);
}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/MethodOptimizationFeedback.java b/src/main/java/com/android/tools/r8/ir/conversion/MethodOptimizationFeedback.java
index 1ffe1ad..6e96719 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/MethodOptimizationFeedback.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/MethodOptimizationFeedback.java
@@ -7,13 +7,14 @@
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexEncodedMethod.ClassInlinerEligibility;
-import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.ir.analysis.type.ClassTypeLatticeElement;
import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.value.AbstractValue;
import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
import com.android.tools.r8.ir.optimize.info.ParameterUsagesInfo;
import com.android.tools.r8.ir.optimize.info.initializer.InitializerInfo;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
import java.util.BitSet;
import java.util.Set;
@@ -30,11 +31,8 @@
void methodReturnsArgument(DexEncodedMethod method, int argument);
- void methodReturnsConstantNumber(
- DexEncodedMethod method, AppView<?> appView, long value);
-
- void methodReturnsConstantString(
- DexEncodedMethod method, AppView<?> appView, DexString value);
+ void methodReturnsAbstractValue(
+ DexEncodedMethod method, AppView<AppInfoWithLiveness> appView, AbstractValue abstractValue);
void methodReturnsObjectWithUpperBoundType(
DexEncodedMethod method, AppView<?> appView, TypeLatticeElement type);
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
index d52f02e..511b971 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
@@ -47,6 +47,7 @@
import java.util.Collection;
import java.util.Collections;
import java.util.IdentityHashMap;
+import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -1232,6 +1233,10 @@
if (!encodedMethod.isStatic()) {
virtualRewrites.putIfAbsent(encodedMethod.method.name, new ArrayList<>());
virtualRewrites.get(encodedMethod.method.name).add(encodedMethod.method);
+ if (isEmulatedInterfaceDispatch(appView, encodedMethod)) {
+ // In this case interface method rewriter takes care of it.
+ continue;
+ }
}
DexProto proto = encodedMethod.method.proto;
DexMethod method = appView.dexItemFactory().createMethod(inType, proto, methodName);
@@ -1244,6 +1249,37 @@
}
}
+ private boolean isEmulatedInterfaceDispatch(AppView<?> appView, DexEncodedMethod method) {
+ // Answers true if this method is already managed through emulated interface dispatch.
+ Map<DexType, DexType> emulateLibraryInterface =
+ appView.options().desugaredLibraryConfiguration.getEmulateLibraryInterface();
+ if (emulateLibraryInterface.isEmpty()) {
+ return false;
+ }
+ DexMethod methodToFind = method.method;
+
+ // Look-up all superclass and interfaces, if an emulated interface is found, and it implements
+ // the method, answers true.
+ LinkedList<DexType> workList = new LinkedList<>();
+ workList.add(methodToFind.holder);
+ while (!workList.isEmpty()) {
+ DexType dexType = workList.removeFirst();
+ DexClass dexClass = appView.definitionFor(dexType);
+ assert dexClass != null; // It is a library class, or we are doing L8 compilation.
+ if (dexClass.isInterface() && emulateLibraryInterface.containsKey(dexType)) {
+ DexEncodedMethod dexEncodedMethod = dexClass.lookupMethod(methodToFind);
+ if (dexEncodedMethod != null) {
+ return true;
+ }
+ }
+ Collections.addAll(workList, dexClass.interfaces.values);
+ if (dexClass.superType != appView.dexItemFactory().objectType) {
+ workList.add(dexClass.superType);
+ }
+ }
+ return false;
+ }
+
private List<DexEncodedMethod> findDexEncodedMethodsWithName(
DexString methodName, DexClass clazz) {
List<DexEncodedMethod> found = new ArrayList<>();
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/ClassProcessor.java b/src/main/java/com/android/tools/r8/ir/desugar/ClassProcessor.java
index 688e751..e321f56 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/ClassProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/ClassProcessor.java
@@ -6,19 +6,24 @@
import com.android.tools.r8.errors.CompilationError;
import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexAnnotationSet;
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.MethodAccessFlags;
+import com.android.tools.r8.graph.ParameterAnnotationsList;
import com.android.tools.r8.ir.code.Invoke;
+import com.android.tools.r8.ir.desugar.DefaultMethodsHelper.DefaultMethodCandidates;
+import com.android.tools.r8.ir.synthetic.ExceptionThrowingSourceCode;
import com.android.tools.r8.ir.synthetic.ForwardMethodSourceCode;
import com.android.tools.r8.ir.synthetic.SynthesizedCode;
import com.google.common.collect.Sets;
import java.util.ArrayList;
-import java.util.Collections;
+import java.util.Collection;
import java.util.IdentityHashMap;
import java.util.Iterator;
import java.util.List;
@@ -47,16 +52,8 @@
return createdMethods.keySet();
}
- final void process(DexClass clazz) {
+ final void process(DexProgramClass clazz) {
assert !clazz.isInterface();
- if (clazz.isNotProgramClass()) {
- // We assume that library classes don't need to be processed, since they
- // are provided by a runtime not supporting default interface methods.
- // We also skip classpath classes, which results in sub-optimal behavior
- // in case classpath superclass when processed adds a default method which
- // could have been reused in this class otherwise.
- return;
- }
if (!processedClasses.add(clazz)) {
return; // Has already been processed.
}
@@ -73,7 +70,14 @@
throw new CompilationError("Interface `" + superClass.toSourceString()
+ "` used as super class of `" + clazz.toSourceString() + "`.");
}
- process(superClass);
+ // We assume that library classes don't need to be processed, since they
+ // are provided by a runtime not supporting default interface methods. We
+ // also skip classpath classes, which results in sub-optimal behavior in
+ // case classpath superclass when processed adds a default method which
+ // could have been reused in this class otherwise.
+ if (superClass.isProgramClass()) {
+ process(superClass.asProgramClass());
+ }
}
// When inheriting from a library class, the library class may implement interfaces to
@@ -93,7 +97,7 @@
}
// Collect the default interface methods to be added to this class.
- List<DexEncodedMethod> methodsToImplement =
+ DefaultMethodCandidates methodsToImplement =
collectMethodsToImplement(clazz, desugaredLibraryLookup);
if (methodsToImplement.isEmpty()) {
return;
@@ -101,15 +105,35 @@
// Add the methods.
List<DexEncodedMethod> newForwardingMethods = new ArrayList<>(methodsToImplement.size());
- for (DexEncodedMethod method : methodsToImplement) {
+ for (DexEncodedMethod method : methodsToImplement.candidates) {
assert method.accessFlags.isPublic() && !method.accessFlags.isAbstract();
DexEncodedMethod newMethod = addForwardingMethod(method, clazz);
newForwardingMethods.add(newMethod);
createdMethods.put(newMethod, method);
}
+ for (DexEncodedMethod conflict : methodsToImplement.conflicts.keySet()) {
+ assert conflict.accessFlags.isPublic() && !conflict.accessFlags.isAbstract();
+ DexEncodedMethod newMethod = addICCEThrowingMethod(conflict, clazz);
+ newForwardingMethods.add(newMethod);
+ createdMethods.put(newMethod, conflict);
+ }
clazz.appendVirtualMethods(newForwardingMethods);
}
+ private DexEncodedMethod addICCEThrowingMethod(DexEncodedMethod method, DexClass clazz) {
+ DexMethod newMethod =
+ dexItemFactory.createMethod(clazz.type, method.method.proto, method.method.name);
+ return new DexEncodedMethod(
+ newMethod,
+ method.accessFlags.copy(),
+ DexAnnotationSet.empty(),
+ ParameterAnnotationsList.empty(),
+ new SynthesizedCode(
+ callerPosition ->
+ new ExceptionThrowingSourceCode(
+ clazz.type, method.method, callerPosition, dexItemFactory.icceType)));
+ }
+
private DexEncodedMethod addForwardingMethod(DexEncodedMethod defaultMethod, DexClass clazz) {
DexMethod method = defaultMethod.method;
DexClass target = appView.definitionFor(method.holder);
@@ -157,7 +181,7 @@
// For a given class `clazz` inspects all interfaces it implements directly or
// indirectly and collect a set of all default methods to be implemented
// in this class.
- private List<DexEncodedMethod> collectMethodsToImplement(
+ private DefaultMethodCandidates collectMethodsToImplement(
DexClass clazz, boolean desugaredLibraryLookup) {
DefaultMethodsHelper helper = new DefaultMethodsHelper();
DexClass current = clazz;
@@ -196,7 +220,7 @@
&& !desugaredLibraryLookup) {
// No interface with default in direct hierarchy, nothing to do: super already has all that
// is needed.
- return Collections.emptyList();
+ return DefaultMethodCandidates.empty();
}
if (current.superType == null) {
@@ -225,12 +249,13 @@
}
}
- List<DexEncodedMethod> candidates = helper.createCandidatesList();
- if (candidates.isEmpty()) {
- return candidates;
+ DefaultMethodCandidates candidateSet = helper.createCandidatesList();
+ if (candidateSet.isEmpty()) {
+ return candidateSet;
}
// Remove from candidates methods defined in class or any of its superclasses.
+ List<DexEncodedMethod> candidates = candidateSet.candidates;
List<DexEncodedMethod> toBeImplemented = new ArrayList<>(candidates.size());
current = clazz;
Map<DexString, Map<DexType, DexType>> retargetCoreLibMember =
@@ -262,7 +287,7 @@
// Hide candidates by virtual method of the class.
hideCandidates(current.virtualMethods(), candidates, toBeImplemented);
if (candidates.isEmpty()) {
- return toBeImplemented;
+ return new DefaultMethodCandidates(toBeImplemented, candidateSet.conflicts);
}
DexType superType = current.superType;
@@ -279,14 +304,16 @@
// Everything still in candidate list is not hidden.
toBeImplemented.addAll(candidates);
- return toBeImplemented;
+ return new DefaultMethodCandidates(toBeImplemented, candidateSet.conflicts);
}
current = superClass;
}
}
- private void hideCandidates(List<DexEncodedMethod> virtualMethods,
- List<DexEncodedMethod> candidates, List<DexEncodedMethod> toBeImplemented) {
+ private void hideCandidates(
+ List<DexEncodedMethod> virtualMethods,
+ Collection<DexEncodedMethod> candidates,
+ List<DexEncodedMethod> toBeImplemented) {
Iterator<DexEncodedMethod> it = candidates.iterator();
while (it.hasNext()) {
DexEncodedMethod candidate = it.next();
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/DefaultMethodsHelper.java b/src/main/java/com/android/tools/r8/ir/desugar/DefaultMethodsHelper.java
index fb051aa..2484113 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/DefaultMethodsHelper.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/DefaultMethodsHelper.java
@@ -6,17 +6,66 @@
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexMethod;
+import com.google.common.base.Equivalence;
+import com.google.common.base.Equivalence.Wrapper;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import java.util.ArrayList;
import java.util.Collections;
+import java.util.HashMap;
+import java.util.IdentityHashMap;
import java.util.Iterator;
-import java.util.LinkedList;
import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Objects;
import java.util.Set;
// Helper class implementing bunch of default interface method handling operations.
final class DefaultMethodsHelper {
+
+ // Collection of default methods that need to have generated forwarding methods.
+ public static class DefaultMethodCandidates {
+ final List<DexEncodedMethod> candidates;
+ final Map<DexEncodedMethod, List<DexEncodedMethod>> conflicts;
+
+ private static final DefaultMethodCandidates EMPTY =
+ new DefaultMethodCandidates(Collections.emptyList(), Collections.emptyMap());
+
+ public static DefaultMethodCandidates empty() {
+ return EMPTY;
+ }
+
+ public DefaultMethodCandidates(
+ List<DexEncodedMethod> candidates,
+ Map<DexEncodedMethod, List<DexEncodedMethod>> conflicts) {
+ this.candidates = candidates;
+ this.conflicts = conflicts;
+ }
+
+ public int size() {
+ return candidates.size() + conflicts.size();
+ }
+
+ public boolean isEmpty() {
+ return candidates.isEmpty() && conflicts.isEmpty();
+ }
+ }
+
+ // Equivalence wrapper for comparing two method signatures modulo holder type.
+ private static class SignatureEquivalence extends Equivalence<DexEncodedMethod> {
+
+ @Override
+ protected boolean doEquivalent(DexEncodedMethod method1, DexEncodedMethod method2) {
+ return method1.method.match(method2.method);
+ }
+
+ @Override
+ protected int doHash(DexEncodedMethod method) {
+ return Objects.hash(method.method.name, method.method.proto);
+ }
+ }
+
// Current set of default interface methods, may overlap with `hidden`.
private final Set<DexEncodedMethod> candidates = Sets.newIdentityHashSet();
// Current set of known hidden default interface methods.
@@ -78,45 +127,49 @@
candidates.add(encoded);
}
- // Creates a list of default method candidates to be implemented in the class.
- final List<DexEncodedMethod> createCandidatesList() {
- this.candidates.removeAll(hidden);
- if (this.candidates.isEmpty()) {
- return Collections.emptyList();
+ final DefaultMethodCandidates createCandidatesList() {
+ // The common cases is for no default methods or a single one.
+ if (candidates.isEmpty()) {
+ return DefaultMethodCandidates.empty();
}
-
- // The list of non-hidden default methods. The list is not expected to be big,
- // since it only consists of default methods which are maximally specific
- // interface method of a particular class.
- List<DexEncodedMethod> candidates = new LinkedList<>();
-
- // Note that it is possible for a class to have more than one maximally specific
- // interface method. But runtime requires that when a method is called, there must be
- // found *only one* maximally specific interface method and this method should be
- // non-abstract, otherwise a runtime error is generated.
- //
- // This code assumes that if such erroneous case exist for particular name/signature,
- // a method with this name/signature must be defined in class or one of its superclasses,
- // or otherwise it should never be called. This means that if we see two default method
- // candidates with same name/signature, it is safe to assume that we don't need to add
- // these method to the class, because if it was missing in class and its superclasses
- // but still called in the original code, this call would have resulted in runtime error.
- // So we are just leaving it unimplemented with the same effect (with a different runtime
- // exception though).
- for (DexEncodedMethod candidate : this.candidates) {
- Iterator<DexEncodedMethod> it = candidates.iterator();
- boolean conflict = false;
- while (it.hasNext()) {
- if (candidate.method.match(it.next())) {
- conflict = true;
- it.remove();
- }
+ if (candidates.size() == 1 && hidden.isEmpty()) {
+ return new DefaultMethodCandidates(new ArrayList<>(candidates), Collections.emptyMap());
+ }
+ // In case there are more we need to check for potential duplicates and treat them specially
+ // to preserve the IncompatibleClassChangeError that would arise at runtime.
+ int maxSize = candidates.size();
+ SignatureEquivalence equivalence = new SignatureEquivalence();
+ Map<Wrapper<DexEncodedMethod>, List<DexEncodedMethod>> groups = new HashMap<>(maxSize);
+ boolean foundConflicts = false;
+ for (DexEncodedMethod candidate : candidates) {
+ if (hidden.contains(candidate)) {
+ continue;
}
- if (!conflict) {
- candidates.add(candidate);
+ Wrapper<DexEncodedMethod> key = equivalence.wrap(candidate);
+ List<DexEncodedMethod> conflicts = groups.get(key);
+ if (conflicts != null) {
+ foundConflicts = true;
+ } else {
+ conflicts = new ArrayList<>(maxSize);
+ groups.put(key, conflicts);
+ }
+ conflicts.add(candidate);
+ }
+ // In the fast path we don't expect any conflicts or hidden candidates.
+ if (!foundConflicts && hidden.isEmpty()) {
+ return new DefaultMethodCandidates(new ArrayList<>(candidates), Collections.emptyMap());
+ }
+ // Slow case in the case of conflicts or hidden candidates build the result.
+ List<DexEncodedMethod> actualCandidates = new ArrayList<>(groups.size());
+ Map<DexEncodedMethod, List<DexEncodedMethod>> conflicts = new IdentityHashMap<>();
+ for (Entry<Wrapper<DexEncodedMethod>, List<DexEncodedMethod>> entry : groups.entrySet()) {
+ if (entry.getValue().size() == 1) {
+ actualCandidates.add(entry.getKey().get());
+ } else {
+ conflicts.put(entry.getKey().get(), entry.getValue());
}
}
- return candidates;
+ return new DefaultMethodCandidates(actualCandidates, conflicts);
}
final List<DexEncodedMethod> createFullList() {
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 54ac5c0..eb855d1 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
@@ -49,6 +49,7 @@
import java.util.Arrays;
import java.util.Collections;
import java.util.IdentityHashMap;
+import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
@@ -254,27 +255,6 @@
// exception but we can not report it as error since it can also be the intended
// behavior.
warnMissingType(encodedMethod.method, invokedMethod.holder);
- } else if (clazz.isInterface() && clazz.isLibraryClass() && isInDesugaredLibrary(clazz)) {
- // Here we try to avoid doing the expensive look-up on all invokes.
- boolean rewritten = false;
- if (emulatedMethods.contains(invokedMethod.name)) {
- DexType dexType = nearestEmulatedInterfaceImplementingWithCache(invokedMethod);
- if (dexType != null) {
- rewriteCurrentInstructionToEmulatedInterfaceCall(
- dexType, invokedMethod, invokeSuper, instructions);
- rewritten = true;
- }
- }
- if (!rewritten) {
- DexMethod amendedMethod =
- amendDefaultMethod(
- appInfo.definitionFor(encodedMethod.method.holder), invokedMethod);
- instructions.replaceCurrentInstruction(
- new InvokeStatic(
- defaultAsMethodOfCompanionClass(amendedMethod),
- invokeSuper.outValue(),
- invokeSuper.arguments()));
- }
} else if (clazz.isInterface() && !clazz.isLibraryClass()) {
// NOTE: we intentionally don't desugar super calls into interface methods
// coming from android.jar since it is only possible in case v24+ version
@@ -291,6 +271,54 @@
instructions.replaceCurrentInstruction(
new InvokeStatic(defaultAsMethodOfCompanionClass(amendedMethod),
invokeSuper.outValue(), invokeSuper.arguments()));
+ } else {
+ DexType dexType = nearestEmulatedInterfaceOrNull(invokedMethod);
+ if (dexType != null) {
+ // That invoke super may not resolve since the super method may not be present
+ // since it's in the emulated interface. We need to force resolution. If it resolves
+ // to a library method, then it needs to be rewritten.
+ // If it resolves to a program overrides, the invoke-super can remain.
+ DexEncodedMethod dexEncodedMethod =
+ appView
+ .appInfo()
+ .lookupSuperTarget(invokeSuper.getInvokedMethod(), code.method.method.holder);
+ if (dexEncodedMethod != null) {
+ DexClass dexClass = appView.definitionFor(dexEncodedMethod.method.holder);
+ if (dexClass != null && dexClass.isLibraryClass()) {
+ // Rewriting is required because the super invoke resolves into a missing
+ // method (method is on desugared library). Find out if it needs to be
+ // retarget or if it just calls a companion class method and rewrite.
+ Map<DexString, Map<DexType, DexType>> retargetCoreLibMember =
+ options.desugaredLibraryConfiguration.getRetargetCoreLibMember();
+ Map<DexType, DexType> typeMap =
+ retargetCoreLibMember.get(dexEncodedMethod.method.name);
+ if (typeMap == null || !typeMap.containsKey(dexEncodedMethod.method.holder)) {
+ DexMethod originalCompanionMethod =
+ instanceAsMethodOfCompanionClass(
+ dexEncodedMethod.method, DEFAULT_METHOD_PREFIX, factory);
+ DexMethod companionMethod =
+ factory.createMethod(
+ getCompanionClassType(dexType),
+ factory.protoWithDifferentFirstParameter(
+ originalCompanionMethod.proto, dexType),
+ originalCompanionMethod.name);
+ instructions.replaceCurrentInstruction(
+ new InvokeStatic(
+ companionMethod, invokeSuper.outValue(), invokeSuper.arguments()));
+ } else {
+ DexMethod retargetMethod =
+ factory.createMethod(
+ typeMap.get(dexEncodedMethod.method.holder),
+ factory.prependTypeToProto(
+ dexEncodedMethod.method.holder, dexEncodedMethod.method.proto),
+ dexEncodedMethod.method.name);
+ instructions.replaceCurrentInstruction(
+ new InvokeStatic(
+ retargetMethod, invokeSuper.outValue(), invokeSuper.arguments()));
+ }
+ }
+ }
+ }
}
continue;
}
@@ -345,39 +373,42 @@
if (instruction.isInvokeVirtual() || instruction.isInvokeInterface()) {
InvokeMethod invokeMethod = instruction.asInvokeMethod();
DexMethod invokedMethod = invokeMethod.getInvokedMethod();
- // Here we try to avoid doing the expensive look-up on all invokes.
- if (!emulatedMethods.contains(invokedMethod.name)) {
- continue;
+ DexType dexType = nearestEmulatedInterfaceOrNull(invokedMethod);
+ if (dexType != null) {
+ rewriteCurrentInstructionToEmulatedInterfaceCall(
+ dexType, invokedMethod, invokeMethod, instructions);
}
- DexClass dexClass = appView.definitionFor(invokedMethod.holder);
- // We cannot rewrite the invoke we do not know what the class is.
- if (dexClass == null) {
- continue;
- }
- // TODO(b/120884788): Make sure program class are looked up before library class for
- // CoreLib compilation or look again into all desugared library emulation.
- // Outside of core libraries, only library classes are rewritten. In core libraries,
- // some classes are present both as program and library class, and definitionFor
- // answers the program class so this is not true.
- if (!appView.options().isDesugaredLibraryCompilation() && !dexClass.isLibraryClass()) {
- continue;
- }
- // We always rewrite interfaces, but classes are rewritten only if they are not already
- // desugared (CoreLibrary classes efficient implementation).
- if (!dexClass.isInterface() && isInDesugaredLibrary(dexClass)) {
- continue;
- }
- DexType dexType = nearestEmulatedInterfaceImplementingWithCache(invokedMethod);
- if (dexType == null) {
- continue;
- }
- rewriteCurrentInstructionToEmulatedInterfaceCall(
- dexType, invokedMethod, invokeMethod, instructions);
}
}
}
}
+ private DexType nearestEmulatedInterfaceOrNull(DexMethod invokedMethod) {
+ // Here we try to avoid doing the expensive look-up on all invokes.
+ if (!emulatedMethods.contains(invokedMethod.name)) {
+ return null;
+ }
+ DexClass dexClass = appView.definitionFor(invokedMethod.holder);
+ // We cannot rewrite the invoke we do not know what the class is.
+ if (dexClass == null) {
+ return null;
+ }
+ // TODO(b/120884788): Make sure program class are looked up before library class for
+ // CoreLib compilation or look again into all desugared library emulation.
+ // Outside of core libraries, only library classes are rewritten. In core libraries,
+ // some classes are present both as program and library class, and definitionFor
+ // answers the program class so this is not true.
+ if (!appView.options().isDesugaredLibraryCompilation() && !dexClass.isLibraryClass()) {
+ return null;
+ }
+ // We always rewrite interfaces, but classes are rewritten only if they are not already
+ // desugared (CoreLibrary classes efficient implementation).
+ if (!dexClass.isInterface() && isInDesugaredLibrary(dexClass)) {
+ return null;
+ }
+ return nearestEmulatedInterfaceImplementingWithCache(invokedMethod);
+ }
+
private void rewriteCurrentInstructionToEmulatedInterfaceCall(
DexType emulatedItf,
DexMethod invokedMethod,
@@ -933,14 +964,29 @@
private void duplicateEmulatedInterfaces() {
// All classes implementing an emulated interface now implements the interface and the
- // emulated one.
+ // emulated one, as well as hidden overrides, for correct emulated dispatch.
for (DexClass clazz : appView.appInfo().classes()) {
+ if (clazz.type == appView.dexItemFactory().objectType) {
+ continue;
+ }
List<DexType> extraInterfaces = new ArrayList<>();
for (DexType type : clazz.interfaces.values) {
if (emulatedInterfaces.containsKey(type)) {
extraInterfaces.add(emulatedInterfaces.get(type));
}
}
+ if (!appView.options().isDesugaredLibraryCompilation()) {
+ assert clazz.superType != null;
+ DexClass superClazz = appView.definitionFor(clazz.superType);
+ if (superClazz != null && superClazz.isLibraryClass()) {
+ List<DexType> itfs = emulatedInterfacesOf(superClazz);
+ for (DexType itf : itfs) {
+ extraInterfaces.add(emulatedInterfaces.get(itf));
+ }
+ }
+ // Remove duplicates.
+ extraInterfaces = new ArrayList<>(new LinkedHashSet<>(extraInterfaces));
+ }
if (!extraInterfaces.isEmpty()) {
DexType[] newInterfaces =
Arrays.copyOf(
@@ -953,6 +999,32 @@
}
}
+ private List<DexType> emulatedInterfacesOf(DexClass superClazz) {
+ if (superClazz.type == factory.objectType) {
+ return Collections.emptyList();
+ }
+ ArrayList<DexType> itfs = new ArrayList<>();
+ LinkedList<DexType> workList = new LinkedList<>();
+ workList.add(superClazz.type);
+ while (!workList.isEmpty()) {
+ DexType dexType = workList.removeFirst();
+ DexClass dexClass = appView.definitionFor(dexType);
+ if (dexClass != null) {
+ if (dexClass.superType != factory.objectType) {
+ workList.add(dexClass.superType);
+ }
+ for (DexType itf : dexClass.interfaces.values) {
+ if (emulatedInterfaces.containsKey(itf)) {
+ itfs.add(itf);
+ } else {
+ workList.add(itf);
+ }
+ }
+ }
+ }
+ return itfs;
+ }
+
/**
* Move static and default interface methods to companion classes, add missing methods to forward
* to moved default methods implementation.
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/CallSiteOptimizationInfoPropagator.java b/src/main/java/com/android/tools/r8/ir/optimize/CallSiteOptimizationInfoPropagator.java
index ab931cc..4da2dcd 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/CallSiteOptimizationInfoPropagator.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/CallSiteOptimizationInfoPropagator.java
@@ -10,10 +10,11 @@
import com.android.tools.r8.graph.ResolutionResult;
import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.value.AbstractValue;
+import com.android.tools.r8.ir.analysis.value.SingleValue;
import com.android.tools.r8.ir.code.Assume;
import com.android.tools.r8.ir.code.Assume.DynamicTypeAssumption;
import com.android.tools.r8.ir.code.Assume.NonNullAssumption;
-import com.android.tools.r8.ir.code.ConstInstruction;
import com.android.tools.r8.ir.code.ConstNumber;
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.code.Instruction;
@@ -171,7 +172,7 @@
.hasUsefulOptimizationInfo(appView, code.method);
Set<Value> affectedValues = Sets.newIdentityHashSet();
List<Assume<?>> assumeInstructions = new LinkedList<>();
- List<ConstInstruction> constants = new LinkedList<>();
+ List<Instruction> constants = new LinkedList<>();
int argumentsSeen = 0;
InstructionListIterator iterator = code.entryBlock().listIterator(code);
while (iterator.hasNext()) {
@@ -184,8 +185,23 @@
if (originalArg.hasLocalInfo() || !originalArg.getTypeLattice().isReference()) {
continue;
}
+ int argIndex = argumentsSeen - 1;
+ AbstractValue abstractValue = callSiteOptimizationInfo.getAbstractArgumentValue(argIndex);
+ if (abstractValue.isSingleValue()) {
+ assert appView.options().enablePropagationOfConstantsAtCallSites;
+ SingleValue singleValue = abstractValue.asSingleValue();
+ if (singleValue.isMaterializableInContext(appView, code.method.method.holder)) {
+ Instruction replacement =
+ singleValue.createMaterializingInstruction(appView, code, instr);
+ replacement.setPosition(instr.getPosition());
+ affectedValues.addAll(originalArg.affectedValues());
+ originalArg.replaceUsers(replacement.outValue());
+ constants.add(replacement);
+ continue;
+ }
+ }
TypeLatticeElement dynamicUpperBoundType =
- callSiteOptimizationInfo.getDynamicUpperBoundType(argumentsSeen - 1);
+ callSiteOptimizationInfo.getDynamicUpperBoundType(argIndex);
if (dynamicUpperBoundType == null) {
continue;
}
@@ -197,7 +213,6 @@
constants.add(nullInstruction);
continue;
}
- // TODO(b/69963623): Handle other kinds of constants, e.g. number, string, or class.
Value specializedArg;
if (dynamicUpperBoundType.strictlyLessThan(originalArg.getTypeLattice(), appView)) {
specializedArg = code.createValue(originalArg.getTypeLattice());
@@ -261,6 +276,9 @@
assert callSiteOptimizationInfo.asConcreteCallSiteOptimizationInfo()
.hasUsefulOptimizationInfo(appView, method);
targetsToRevisit.add(method);
+ if (appView.options().testing.callSiteOptimizationInfoInspector != null) {
+ appView.options().testing.callSiteOptimizationInfoInspector.accept(method);
+ }
}
}
if (revisitedMethods != null) {
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 ccf312a..be007bb 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
@@ -99,7 +99,6 @@
import it.unimi.dsi.fastutil.ints.Int2IntMap;
import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
import it.unimi.dsi.fastutil.ints.Int2ReferenceAVLTreeMap;
-import it.unimi.dsi.fastutil.ints.Int2ReferenceSortedMap;
import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
import it.unimi.dsi.fastutil.ints.Int2ReferenceMap.Entry;
import it.unimi.dsi.fastutil.ints.Int2ReferenceOpenHashMap;
@@ -1370,6 +1369,10 @@
return;
}
+ if (!appView.options().testing.enableCheckCastAndInstanceOfRemoval) {
+ return;
+ }
+
IRMetadata metadata = code.metadata();
if (!metadata.mayHaveCheckCast() && !metadata.mayHaveInstanceOf()) {
return;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java b/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
index a2c60a3..5df7550 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
@@ -15,8 +15,8 @@
import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
import com.android.tools.r8.ir.analysis.value.AbstractValue;
+import com.android.tools.r8.ir.analysis.value.SingleValue;
import com.android.tools.r8.ir.code.BasicBlock;
-import com.android.tools.r8.ir.code.ConstInstruction;
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.code.IRMetadata;
import com.android.tools.r8.ir.code.InstanceGet;
@@ -148,7 +148,7 @@
return null;
}
- ConstInstruction replacement =
+ Instruction replacement =
staticField.valueAsConstInstruction(code, instruction.getLocalInfo(), appView);
if (replacement == null) {
reporter.warning(
@@ -275,21 +275,22 @@
AbstractValue abstractReturnValue = target.getOptimizationInfo().getAbstractReturnValue();
if (abstractReturnValue.isSingleValue()) {
- Instruction replacement =
- abstractReturnValue.asSingleValue()
- .createMaterializingInstruction(appView, code, current);
-
- affectedValues.addAll(current.outValue().affectedValues());
- current.outValue().replaceUsers(replacement.outValue());
- current.setOutValue(null);
- replacement.setPosition(current.getPosition());
- current.moveDebugValues(replacement);
- if (current.getBlock().hasCatchHandlers()) {
- iterator.split(code, blocks).listIterator(code).add(replacement);
- } else {
- iterator.add(replacement);
+ SingleValue singleReturnValue = abstractReturnValue.asSingleValue();
+ if (singleReturnValue.isMaterializableInContext(appView, callingContext)) {
+ Instruction replacement =
+ singleReturnValue.createMaterializingInstruction(appView, code, current);
+ affectedValues.addAll(current.outValue().affectedValues());
+ current.outValue().replaceUsers(replacement.outValue());
+ current.setOutValue(null);
+ replacement.setPosition(current.getPosition());
+ current.moveDebugValues(replacement);
+ if (current.getBlock().hasCatchHandlers()) {
+ iterator.split(code, blocks).listIterator(code).add(replacement);
+ } else {
+ iterator.add(replacement);
+ }
+ target.getMutableOptimizationInfo().markAsPropagated();
}
- target.getMutableOptimizationInfo().markAsPropagated();
}
}
@@ -325,12 +326,13 @@
return;
}
- // Check if a this value is known const.
+ // Check if the field is pinned. In that case, it could be written by reflection.
if (appView.appInfo().isPinned(target.field)) {
return;
}
- ConstInstruction replacement =
+ // Check if a this value is known const.
+ Instruction replacement =
target.valueAsConstInstruction(code, current.outValue().getLocalInfo(), appView);
if (replacement != null) {
affectedValues.addAll(current.outValue().affectedValues());
@@ -372,7 +374,7 @@
}
// Check if a this value is known const.
- ConstInstruction replacement =
+ Instruction replacement =
target.valueAsConstInstruction(code, current.outValue().getLocalInfo(), appView);
if (replacement != null) {
affectedValues.add(replacement.outValue());
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
index dfa2f0a..9bad45e 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
@@ -651,38 +651,51 @@
ClassInlinerEligibility eligibility,
InvokeMethodWithReceiver invoke,
Set<Instruction> indirectUsers) {
- if (!eligibility.returnsReceiver
- || invoke.outValue() == null
- || !invoke.outValue().hasAnyUsers()) {
+ if (!eligibility.returnsReceiver) {
return true;
}
+
+ Value outValue = invoke.outValue();
+ if (outValue == null || !outValue.hasAnyUsers()) {
+ return true;
+ }
+
// For CF we no longer perform the code-rewrite in CodeRewriter.rewriteMoveResult that removes
// out values if they alias to the receiver since that naively produces a lot of popping values
// from the stack.
- if (invoke.outValue().numberOfPhiUsers() > 0) {
+ if (outValue.hasPhiUsers() || outValue.hasDebugUsers()) {
return false;
}
- for (Instruction instruction : invoke.outValue().uniqueUsers()) {
- if (!instruction.isInvokeMethodWithReceiver()) {
- return false;
- }
- InvokeMethodWithReceiver user = instruction.asInvokeMethodWithReceiver();
- if (user.getReceiver() != invoke.outValue()) {
- return false;
- }
- int uses = 0;
- for (Value value : user.inValues()) {
- if (value == invoke.outValue()) {
- uses++;
- if (uses > 1) {
+
+ Set<Instruction> currentUsers = outValue.uniqueUsers();
+ while (!currentUsers.isEmpty()) {
+ Set<Instruction> indirectOutValueUsers = Sets.newIdentityHashSet();
+ for (Instruction instruction : currentUsers) {
+ if (instruction.isAssume()) {
+ Value outValueAlias = instruction.outValue();
+ if (outValueAlias.hasPhiUsers() || outValueAlias.hasDebugUsers()) {
+ return false;
+ }
+ indirectOutValueUsers.addAll(outValueAlias.uniqueUsers());
+ continue;
+ }
+ if (!instruction.isInvokeMethodWithReceiver()) {
+ return false;
+ }
+ InvokeMethodWithReceiver user = instruction.asInvokeMethodWithReceiver();
+ if (user.getReceiver().getAliasedValue() != outValue) {
+ return false;
+ }
+ for (int i = 1; i < user.inValues().size(); i++) {
+ if (user.inValues().get(i).getAliasedValue() == outValue) {
return false;
}
}
}
+ indirectUsers.addAll(currentUsers);
+ currentUsers = indirectOutValueUsers;
}
- indirectUsers.addAll(invoke.outValue().uniqueUsers());
-
return true;
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/CallSiteOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/CallSiteOptimizationInfo.java
index c99f24c..18c709c 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/CallSiteOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/CallSiteOptimizationInfo.java
@@ -6,6 +6,8 @@
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.value.AbstractValue;
+import com.android.tools.r8.ir.analysis.value.UnknownValue;
import com.android.tools.r8.ir.optimize.CallSiteOptimizationInfoPropagator;
// A flat lattice structure:
@@ -63,8 +65,12 @@
// TODO(b/139246447): dynamic lower bound type?
- // TODO(b/69963623): collect constants and if they're all same, propagate it to the callee.
- // then, we need to re-run unused argument removal?
+ // TODO(b/69963623): we need to re-run unused argument removal?
+
+ // The index exactly matches with in values of invocation, i.e., even including receiver.
+ public AbstractValue getAbstractArgumentValue(int argIndex) {
+ return UnknownValue.getInstance();
+ }
// TODO(b/139249918): propagate classes that are guaranteed to be initialized.
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/ConcreteCallSiteOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/ConcreteCallSiteOptimizationInfo.java
index be0bf19..c38c0ed 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/ConcreteCallSiteOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/ConcreteCallSiteOptimizationInfo.java
@@ -11,9 +11,13 @@
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.ir.analysis.type.Nullability;
import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.value.AbstractValue;
+import com.android.tools.r8.ir.analysis.value.UnknownValue;
import com.android.tools.r8.ir.code.Value;
import it.unimi.dsi.fastutil.ints.Int2ReferenceArrayMap;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
import java.util.List;
+import java.util.Objects;
// Accumulated optimization info from call sites.
public class ConcreteCallSiteOptimizationInfo extends CallSiteOptimizationInfo {
@@ -21,26 +25,39 @@
// inValues() size == DexMethod.arity + (isStatic ? 0 : 1) // receiver
// That is, this information takes into account the receiver as well.
private final int size;
- private final Int2ReferenceArrayMap<TypeLatticeElement> dynamicUpperBoundTypes;
- // TODO(b/69963623): sparse map from index to ConstantData if any.
+ private final Int2ReferenceMap<TypeLatticeElement> dynamicUpperBoundTypes;
+ private final Int2ReferenceMap<AbstractValue> constants;
- private ConcreteCallSiteOptimizationInfo(DexEncodedMethod encodedMethod) {
- assert encodedMethod.method.getArity() + (encodedMethod.isStatic() ? 0 : 1) > 0;
- this.size = encodedMethod.method.getArity() + (encodedMethod.isStatic() ? 0 : 1);
- this.dynamicUpperBoundTypes = new Int2ReferenceArrayMap<>(size);
+ private ConcreteCallSiteOptimizationInfo(
+ DexEncodedMethod encodedMethod, boolean allowConstantPropagation) {
+ this(encodedMethod.method.getArity() + (encodedMethod.isStatic() ? 0 : 1),
+ allowConstantPropagation);
}
- private ConcreteCallSiteOptimizationInfo(int size) {
+ private ConcreteCallSiteOptimizationInfo(int size, boolean allowConstantPropagation) {
+ assert size > 0;
this.size = size;
this.dynamicUpperBoundTypes = new Int2ReferenceArrayMap<>(size);
+ this.constants = allowConstantPropagation ? new Int2ReferenceArrayMap<>(size) : null;
}
CallSiteOptimizationInfo join(
ConcreteCallSiteOptimizationInfo other, AppView<?> appView, DexEncodedMethod encodedMethod) {
assert this.size == other.size;
- ConcreteCallSiteOptimizationInfo result = new ConcreteCallSiteOptimizationInfo(this.size);
+ boolean allowConstantPropagation = appView.options().enablePropagationOfConstantsAtCallSites;
+ ConcreteCallSiteOptimizationInfo result =
+ new ConcreteCallSiteOptimizationInfo(this.size, allowConstantPropagation);
assert result.dynamicUpperBoundTypes != null;
for (int i = 0; i < result.size; i++) {
+ if (allowConstantPropagation) {
+ assert result.constants != null;
+ AbstractValue abstractValue =
+ getAbstractArgumentValue(i).join(other.getAbstractArgumentValue(i));
+ if (abstractValue.isNonTrivial()) {
+ result.constants.put(i, abstractValue);
+ }
+ }
+
TypeLatticeElement thisUpperBoundType = getDynamicUpperBoundType(i);
if (thisUpperBoundType == null) {
// This means the corresponding argument is primitive. The counterpart should be too.
@@ -82,6 +99,12 @@
public boolean hasUsefulOptimizationInfo(AppView<?> appView, DexEncodedMethod encodedMethod) {
TypeLatticeElement[] staticTypes = getStaticTypes(appView, encodedMethod);
for (int i = 0; i < size; i++) {
+ AbstractValue abstractValue = getAbstractArgumentValue(i);
+ if (abstractValue.isNonTrivial()) {
+ assert appView.options().enablePropagationOfConstantsAtCallSites;
+ return true;
+ }
+
if (!staticTypes[i].isReference()) {
continue;
}
@@ -89,6 +112,7 @@
if (dynamicUpperBoundType == null) {
continue;
}
+ assert appView.options().enablePropagationOfDynamicTypesAtCallSites;
// To avoid the full join of type lattices below, separately check if the nullability of
// arguments is improved, and if so, we can eagerly conclude that we've collected useful
// call site information for this method.
@@ -109,18 +133,43 @@
@Override
public TypeLatticeElement getDynamicUpperBoundType(int argIndex) {
assert 0 <= argIndex && argIndex < size;
+ assert dynamicUpperBoundTypes != null;
return dynamicUpperBoundTypes.getOrDefault(argIndex, null);
}
+ @Override
+ public AbstractValue getAbstractArgumentValue(int argIndex) {
+ assert 0 <= argIndex && argIndex < size;
+ // TODO(b/69963623): Remove this once enabled.
+ if (constants == null) {
+ return UnknownValue.getInstance();
+ }
+ return constants.getOrDefault(argIndex, UnknownValue.getInstance());
+ }
+
public static CallSiteOptimizationInfo fromArguments(
AppView<? extends AppInfoWithSubtyping> appView,
DexEncodedMethod method,
List<Value> inValues) {
- ConcreteCallSiteOptimizationInfo newCallSiteInfo = new ConcreteCallSiteOptimizationInfo(method);
+ boolean allowConstantPropagation = appView.options().enablePropagationOfConstantsAtCallSites;
+ ConcreteCallSiteOptimizationInfo newCallSiteInfo =
+ new ConcreteCallSiteOptimizationInfo(method, allowConstantPropagation);
assert newCallSiteInfo.size == inValues.size();
+ assert newCallSiteInfo.dynamicUpperBoundTypes != null;
for (int i = 0; i < newCallSiteInfo.size; i++) {
Value arg = inValues.get(i);
- // TODO(b/69963623): may need different place to store constants.
+ if (allowConstantPropagation) {
+ assert newCallSiteInfo.constants != null;
+ Value aliasedValue = arg.getAliasedValue();
+ if (!aliasedValue.isPhi()) {
+ AbstractValue abstractValue =
+ aliasedValue.definition.getAbstractValue(appView, method.method.holder);
+ if (abstractValue.isNonTrivial()) {
+ newCallSiteInfo.constants.put(i, abstractValue);
+ }
+ }
+ }
+
if (arg.getTypeLattice().isPrimitive()) {
continue;
}
@@ -151,18 +200,19 @@
return false;
}
ConcreteCallSiteOptimizationInfo otherInfo = (ConcreteCallSiteOptimizationInfo) other;
- assert this.dynamicUpperBoundTypes != null;
- return this.dynamicUpperBoundTypes.equals(otherInfo.dynamicUpperBoundTypes);
+ return Objects.equals(this.dynamicUpperBoundTypes, otherInfo.dynamicUpperBoundTypes)
+ && Objects.equals(this.constants, otherInfo.constants);
}
@Override
public int hashCode() {
assert this.dynamicUpperBoundTypes != null;
- return System.identityHashCode(dynamicUpperBoundTypes);
+ return System.identityHashCode(dynamicUpperBoundTypes) * 7 + System.identityHashCode(constants);
}
@Override
public String toString() {
- return dynamicUpperBoundTypes.toString();
+ return dynamicUpperBoundTypes.toString()
+ + (constants == null ? "" : (System.lineSeparator() + constants.toString()));
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
index 7f8cfc4..9161f51 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
@@ -24,8 +24,8 @@
import com.android.tools.r8.ir.analysis.type.ClassTypeLatticeElement;
import com.android.tools.r8.ir.analysis.type.Nullability;
import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.value.AbstractValue;
import com.android.tools.r8.ir.code.BasicBlock;
-import com.android.tools.r8.ir.code.ConstString;
import com.android.tools.r8.ir.code.DominatorTree;
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.code.If;
@@ -242,22 +242,18 @@
}
if (returnValue != null) {
Value aliasedValue = returnValue.getAliasedValue();
- if (aliasedValue.isArgument()) {
- // Find the argument number.
- int index = aliasedValue.computeArgumentPosition(code);
- assert index >= 0;
- feedback.methodReturnsArgument(method, index);
- }
- if (aliasedValue.isConstant()) {
- if (aliasedValue.definition.isConstNumber()) {
- long value = aliasedValue.definition.asConstNumber().getRawValue();
- feedback.methodReturnsConstantNumber(method, appView, value);
- } else if (aliasedValue.definition.isConstString()) {
- ConstString constStringInstruction = aliasedValue.definition.asConstString();
- if (!constStringInstruction.instructionInstanceCanThrow()) {
- feedback.methodReturnsConstantString(
- method, appView, constStringInstruction.getValue());
- }
+ if (!aliasedValue.isPhi()) {
+ Instruction definition = aliasedValue.definition;
+ if (definition.isArgument()) {
+ // Find the argument number.
+ int index = aliasedValue.computeArgumentPosition(code);
+ assert index >= 0;
+ feedback.methodReturnsArgument(method, index);
+ }
+ DexType context = method.method.holder;
+ AbstractValue abstractReturnValue = definition.getAbstractValue(appView, context);
+ if (abstractReturnValue.isNonTrivial()) {
+ feedback.methodReturnsAbstractValue(method, appView, abstractReturnValue);
}
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackDelayed.java b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackDelayed.java
index 4c3578e..2f9c55b 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackDelayed.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackDelayed.java
@@ -8,13 +8,13 @@
import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexEncodedMethod.ClassInlinerEligibility;
-import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.ir.analysis.type.ClassTypeLatticeElement;
import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
import com.android.tools.r8.ir.analysis.value.AbstractValue;
import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
import com.android.tools.r8.ir.optimize.info.initializer.InitializerInfo;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.utils.IteratorUtils;
import com.android.tools.r8.utils.StringUtils;
import java.util.BitSet;
@@ -123,8 +123,11 @@
}
@Override
- public void recordFieldHasAbstractValue(DexEncodedField field, AbstractValue abstractValue) {
- getFieldOptimizationInfoForUpdating(field).setAbstractValue(abstractValue);
+ public void recordFieldHasAbstractValue(
+ DexEncodedField field, AppView<AppInfoWithLiveness> appView, AbstractValue abstractValue) {
+ if (appView.appInfo().mayPropagateValueFor(field.field)) {
+ getFieldOptimizationInfoForUpdating(field).setAbstractValue(abstractValue);
+ }
}
// METHOD OPTIMIZATION INFO:
@@ -157,15 +160,11 @@
}
@Override
- public synchronized void methodReturnsConstantNumber(
- DexEncodedMethod method, AppView<?> appView, long value) {
- getMethodOptimizationInfoForUpdating(method).markReturnsConstantNumber(appView, value);
- }
-
- @Override
- public synchronized void methodReturnsConstantString(
- DexEncodedMethod method, AppView<?> appView, DexString value) {
- getMethodOptimizationInfoForUpdating(method).markReturnsConstantString(appView, value);
+ public synchronized void methodReturnsAbstractValue(
+ DexEncodedMethod method, AppView<AppInfoWithLiveness> appView, AbstractValue value) {
+ if (appView.appInfo().mayPropagateValueFor(method.method)) {
+ getMethodOptimizationInfoForUpdating(method).markReturnsAbstractValue(value);
+ }
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackIgnore.java b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackIgnore.java
index 1b3e982..1be0c5c 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackIgnore.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackIgnore.java
@@ -8,13 +8,13 @@
import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexEncodedMethod.ClassInlinerEligibility;
-import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.ir.analysis.type.ClassTypeLatticeElement;
import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
import com.android.tools.r8.ir.analysis.value.AbstractValue;
import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
import com.android.tools.r8.ir.optimize.info.initializer.InitializerInfo;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
import java.util.BitSet;
import java.util.Set;
@@ -47,7 +47,8 @@
public void markFieldBitsRead(DexEncodedField field, int bitsRead) {}
@Override
- public void recordFieldHasAbstractValue(DexEncodedField field, AbstractValue abstractValue) {}
+ public void recordFieldHasAbstractValue(
+ DexEncodedField field, AppView<AppInfoWithLiveness> appView, AbstractValue abstractValue) {}
// METHOD OPTIMIZATION INFO:
@@ -68,12 +69,8 @@
public void methodReturnsArgument(DexEncodedMethod method, int argument) {}
@Override
- public void methodReturnsConstantNumber(
- DexEncodedMethod method, AppView<?> appView, long value) {}
-
- @Override
- public void methodReturnsConstantString(
- DexEncodedMethod method, AppView<?> appView, DexString value) {}
+ public void methodReturnsAbstractValue(
+ DexEncodedMethod method, AppView<AppInfoWithLiveness> appView, AbstractValue value) {}
@Override
public void methodReturnsObjectWithUpperBoundType(
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java
index ade0e2d..1d1dea5 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java
@@ -8,13 +8,13 @@
import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexEncodedMethod.ClassInlinerEligibility;
-import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.ir.analysis.type.ClassTypeLatticeElement;
import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
import com.android.tools.r8.ir.analysis.value.AbstractValue;
import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
import com.android.tools.r8.ir.optimize.info.initializer.InitializerInfo;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
import java.util.BitSet;
import java.util.Set;
@@ -57,7 +57,8 @@
}
@Override
- public void recordFieldHasAbstractValue(DexEncodedField field, AbstractValue abstractValue) {
+ public void recordFieldHasAbstractValue(
+ DexEncodedField field, AppView<AppInfoWithLiveness> appView, AbstractValue abstractValue) {
// Ignored.
}
@@ -90,14 +91,8 @@
}
@Override
- public void methodReturnsConstantNumber(
- DexEncodedMethod method, AppView<?> appView, long value) {
- // Ignored.
- }
-
- @Override
- public void methodReturnsConstantString(
- DexEncodedMethod method, AppView<?> appView, DexString value) {
+ public void methodReturnsAbstractValue(
+ DexEncodedMethod method, AppView<AppInfoWithLiveness> appView, AbstractValue value) {
// Ignored.
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/UpdatableMethodOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/UpdatableMethodOptimizationInfo.java
index 8bb6fc3..c5a12c2 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/UpdatableMethodOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/UpdatableMethodOptimizationInfo.java
@@ -7,7 +7,6 @@
import com.android.tools.r8.graph.AppInfoWithSubtyping;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexEncodedMethod.ClassInlinerEligibility;
-import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.ir.analysis.type.ClassTypeLatticeElement;
import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
@@ -339,22 +338,10 @@
neverReturnsNormally = true;
}
- void markReturnsConstantNumber(AppView<?> appView, long value) {
- assert !abstractReturnValue.isSingleStringValue();
- assert !abstractReturnValue.isSingleNumberValue()
- || abstractReturnValue.asSingleNumberValue().getValue() == value
- : "return constant number changed from "
- + abstractReturnValue.asSingleNumberValue().getValue() + " to " + value;
- abstractReturnValue = appView.abstractValueFactory().createSingleNumberValue(value);
- }
-
- void markReturnsConstantString(AppView<?> appView, DexString value) {
- assert !abstractReturnValue.isSingleNumberValue();
- assert !abstractReturnValue.isSingleStringValue()
- || abstractReturnValue.asSingleStringValue().getDexString() == value
- : "return constant string changed from "
- + abstractReturnValue.asSingleStringValue().getDexString() + " to " + value;
- abstractReturnValue = appView.abstractValueFactory().createSingleStringValue(value);
+ void markReturnsAbstractValue(AbstractValue value) {
+ assert !abstractReturnValue.isSingleValue() || abstractReturnValue.asSingleValue() == value
+ : "return single value changed from " + abstractReturnValue + " to " + value;
+ abstractReturnValue = value;
}
void markReturnsObjectWithUpperBoundType(AppView<?> appView, TypeLatticeElement type) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java b/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java
index b71d862..9a4565c 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java
@@ -4,6 +4,8 @@
package com.android.tools.r8.ir.optimize.staticizer;
+import static com.android.tools.r8.ir.analysis.type.Nullability.maybeNull;
+
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DebugLocalInfo;
import com.android.tools.r8.graph.DexClass;
@@ -564,7 +566,9 @@
? null
: code.createValue(
TypeLatticeElement.fromDexType(
- returnType, outValue.getTypeLattice().nullability(), appView),
+ returnType,
+ outValue == null ? maybeNull() : outValue.getTypeLattice().nullability(),
+ appView),
outValue == null ? null : outValue.getLocalInfo());
it.replaceCurrentInstruction(new InvokeStatic(newMethod, newOutValue, invoke.inValues()));
}
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 26fbcee..5dcdbd8 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
@@ -1059,7 +1059,7 @@
if (checkcastInput.getLiveIntervals() != null &&
!checkcastInput.getLiveIntervals().overlaps(unhandledInterval) &&
checkcastInput.getLocalInfo() == unhandledInterval.getValue().definition.getLocalInfo()) {
- unhandledInterval.setHint(checkcastInput.getLiveIntervals());
+ unhandledInterval.setHint(checkcastInput.getLiveIntervals(), unhandled);
}
}
}
@@ -1077,13 +1077,13 @@
assert left != null;
if (left.getLiveIntervals() != null &&
!left.getLiveIntervals().overlaps(unhandledInterval)) {
- unhandledInterval.setHint(left.getLiveIntervals());
+ unhandledInterval.setHint(left.getLiveIntervals(), unhandled);
} else {
Value right = binOp.rightValue();
assert right != null;
if (binOp.isCommutative() && right.getLiveIntervals() != null &&
!right.getLiveIntervals().overlaps(unhandledInterval)) {
- unhandledInterval.setHint(right.getLiveIntervals());
+ unhandledInterval.setHint(right.getLiveIntervals(), unhandled);
}
}
}
@@ -1212,7 +1212,7 @@
if (!value.isPhi() && value.definition.isMove()) {
Move move = value.definition.asMove();
LiveIntervals intervals = move.src().getLiveIntervals();
- intervals.setHint(current);
+ intervals.setHint(current, unhandled);
}
if (current != unhandledInterval) {
// Only the start of unhandledInterval has been reached at this point. All other live
@@ -1780,11 +1780,9 @@
// spilling. For phis we also use the hint before looking at the operand registers. The
// phi could have a hint from an argument moves which it seems more important to honor in
// practice.
- LiveIntervals hint = unhandledInterval.getHint();
+ Integer hint = unhandledInterval.getHint();
if (hint != null) {
- int register = hint.getRegister();
- if (tryHint(unhandledInterval, registerConstraint, freePositions, needsRegisterPair,
- register)) {
+ if (tryHint(unhandledInterval, registerConstraint, freePositions, needsRegisterPair, hint)) {
return true;
}
}
@@ -1865,13 +1863,14 @@
for (Phi phi : value.uniquePhiUsers()) {
LiveIntervals phiIntervals = phi.getLiveIntervals();
if (phiIntervals.getHint() == null) {
+ phiIntervals.setHint(intervals, unhandled);
for (int i = 0; i < phi.getOperands().size(); i++) {
Value operand = phi.getOperand(i);
LiveIntervals operandIntervals = operand.getLiveIntervals();
BasicBlock pred = phi.getBlock().getPredecessors().get(i);
operandIntervals = operandIntervals.getSplitCovering(pred.exit().getNumber());
if (operandIntervals.getHint() == null) {
- operandIntervals.setHint(intervals);
+ operandIntervals.setHint(intervals, unhandled);
}
}
}
@@ -1888,7 +1887,7 @@
BasicBlock pred = block.getPredecessors().get(i);
LiveIntervals operandIntervals =
operand.getLiveIntervals().getSplitCovering(pred.exit().getNumber());
- operandIntervals.setHint(intervals);
+ operandIntervals.setHint(intervals, unhandled);
}
}
}
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 0251687..9cb4c09 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
@@ -17,6 +17,7 @@
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
+import java.util.PriorityQueue;
import java.util.TreeSet;
import java.util.function.IntConsumer;
@@ -36,7 +37,7 @@
private final TreeSet<LiveIntervalsUse> uses = new TreeSet<>();
private int numberOfConsecutiveRegisters = -1;
private int register = NO_REGISTER;
- private LiveIntervals hint;
+ private Integer hint;
private boolean spilled = false;
private boolean usedInMonitorOperations = false;
@@ -82,11 +83,20 @@
return getType().requiredRegisters();
}
- public void setHint(LiveIntervals intervals) {
- hint = intervals;
+ public void setHint(LiveIntervals intervals, PriorityQueue<LiveIntervals> unhandled) {
+ // Do not set hints if they cannot be used anyway.
+ if (!overlaps(intervals)) {
+ // The hint is used in sorting the unhandled intervals. Therefore, if the hint changes
+ // we have to remove and reinsert the interval to get the sorting updated.
+ boolean removed = unhandled.remove(this);
+ hint = intervals.getRegister();
+ if (removed) {
+ unhandled.add(this);
+ }
+ }
}
- public LiveIntervals getHint() {
+ public Integer getHint() {
return hint;
}
@@ -537,8 +547,23 @@
@Override
public int compareTo(LiveIntervals other) {
+ // Sort by interval start instruction number.
int startDiff = getStart() - other.getStart();
- return startDiff != 0 ? startDiff : (value.getNumber() - other.value.getNumber());
+ if (startDiff != 0) return startDiff;
+ // Then sort by register number of hints to make sure that a phi
+ // does not take a low register that is the hint for another phi.
+ if (hint != null && other.hint != null) {
+ int registerDiff = hint - other.hint;
+ if (registerDiff != 0) return registerDiff;
+ }
+ // Intervals with hints go first so intervals without hints
+ // do not take registers from intervals with hints.
+ if (hint != null && other.hint == null) return -1;
+ if (hint == null && other.hint != null) return 1;
+ // Tie-breaker: no values have equal numbers.
+ int result = value.getNumber() - other.value.getNumber();
+ assert result != 0;
+ return result;
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/ExceptionThrowingSourceCode.java b/src/main/java/com/android/tools/r8/ir/synthetic/ExceptionThrowingSourceCode.java
new file mode 100644
index 0000000..bec9df8
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/synthetic/ExceptionThrowingSourceCode.java
@@ -0,0 +1,47 @@
+// Copyright (c) 2019, 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.synthetic;
+
+import com.android.tools.r8.graph.DexItemFactory;
+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.ir.code.Invoke.Type;
+import com.android.tools.r8.ir.code.Position;
+import com.android.tools.r8.ir.code.ValueType;
+import java.util.Collections;
+
+// Source code representing simple forwarding method.
+public final class ExceptionThrowingSourceCode extends SyntheticSourceCode {
+
+ private static final int register = 0;
+ private final DexType exceptionType;
+
+ public ExceptionThrowingSourceCode(
+ DexType receiver, DexMethod method, Position callerPosition, DexType exceptionType) {
+ super(receiver, method, callerPosition);
+ this.exceptionType = exceptionType;
+ }
+
+ @Override
+ protected void prepareInstructions() {
+ add(
+ builder -> {
+ DexItemFactory factory = builder.appView.dexItemFactory();
+ DexProto initProto = factory.createProto(factory.voidType);
+ DexMethod initMethod =
+ factory.createMethod(exceptionType, initProto, factory.constructorMethodName);
+ builder.addNewInstance(register, exceptionType);
+ builder.addInvoke(
+ Type.DIRECT,
+ initMethod,
+ initMethod.proto,
+ Collections.singletonList(ValueType.OBJECT),
+ Collections.singletonList(register),
+ false /* isInterface */);
+ builder.addThrow(register);
+ });
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/naming/MinifiedRenaming.java b/src/main/java/com/android/tools/r8/naming/MinifiedRenaming.java
index 53f457f..7423075 100644
--- a/src/main/java/com/android/tools/r8/naming/MinifiedRenaming.java
+++ b/src/main/java/com/android/tools/r8/naming/MinifiedRenaming.java
@@ -15,6 +15,7 @@
import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.InnerClassAttribute;
+import com.android.tools.r8.graph.ResolutionResult;
import com.android.tools.r8.naming.ClassNameMinifier.ClassRenaming;
import com.android.tools.r8.naming.FieldNameMinifier.FieldRenaming;
import com.android.tools.r8.naming.MethodNameMinifier.MethodRenaming;
@@ -22,7 +23,9 @@
import com.android.tools.r8.utils.DescriptorUtils;
import com.android.tools.r8.utils.InternalOptions;
import com.google.common.collect.ImmutableMap;
+import java.util.ArrayList;
import java.util.IdentityHashMap;
+import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
import java.util.function.Function;
@@ -91,7 +94,34 @@
@Override
public DexString lookupName(DexMethod method) {
- return renaming.getOrDefault(method, method.name);
+ DexString renamed = renaming.get(method);
+ if (renamed != null) {
+ return renamed;
+ }
+ // TODO(b/144339115): Don't allocate in the item factory during resolution!
+ if (method.holder == appView.dexItemFactory().methodHandleType) {
+ return method.name;
+ }
+ // If the method does not have a direct renaming, return the resolutions mapping.
+ ResolutionResult resolutionResult = appView.appInfo().resolveMethod(method.holder, method);
+ if (resolutionResult.hasSingleTarget()) {
+ return renaming.getOrDefault(resolutionResult.getSingleTarget().method, method.name);
+ }
+ // If resolution fails, the method must be renamed consistently with the targets that give rise
+ // to the failure.
+ if (resolutionResult.isFailedResolution()) {
+ List<DexEncodedMethod> targets = new ArrayList<>();
+ resolutionResult.asFailedResolution().forEachFailureDependency(clazz -> {}, targets::add);
+ if (!targets.isEmpty()) {
+ DexString firstRename = renaming.get(targets.get(0).method);
+ assert targets.stream().allMatch(target -> renaming.get(target.method) == firstRename);
+ if (firstRename != null) {
+ return firstRename;
+ }
+ }
+ }
+ // If no renaming can be found the default is the methods name.
+ return method.name;
}
@Override
@@ -160,7 +190,7 @@
directTarget != null ? appView.definitionFor(directTarget.method.holder) : null;
DexClass virtualTargetHolder =
virtualTarget != null ? appView.definitionFor(virtualTarget.method.holder) : null;
- return (directTarget == null && staticTarget == null && virtualTarget == null)
+ assert (directTarget == null && staticTarget == null && virtualTarget == null)
|| (virtualTarget != null && virtualTarget.method == item)
|| (directTarget != null && directTarget.method == item)
|| (staticTarget != null && staticTarget.method == item)
@@ -168,6 +198,7 @@
|| (virtualTargetHolder != null && virtualTargetHolder.isNotProgramClass())
|| (staticTargetHolder != null && staticTargetHolder.isNotProgramClass())
|| appView.unneededVisibilityBridgeMethods().contains(item);
+ return true;
}
@Override
diff --git a/src/main/java/com/android/tools/r8/references/MethodReference.java b/src/main/java/com/android/tools/r8/references/MethodReference.java
index e45ef21..0a89715 100644
--- a/src/main/java/com/android/tools/r8/references/MethodReference.java
+++ b/src/main/java/com/android/tools/r8/references/MethodReference.java
@@ -7,7 +7,6 @@
import com.android.tools.r8.utils.ListUtils;
import com.android.tools.r8.utils.StringUtils;
import com.android.tools.r8.utils.StringUtils.BraceType;
-import com.google.common.collect.ImmutableList;
import java.util.List;
import java.util.Objects;
@@ -21,13 +20,13 @@
public final class MethodReference {
private final ClassReference holderClass;
private final String methodName;
- private final ImmutableList<TypeReference> formalTypes;
+ private final List<TypeReference> formalTypes;
private final TypeReference returnType;
MethodReference(
ClassReference holderClass,
String methodName,
- ImmutableList<TypeReference> formalTypes,
+ List<TypeReference> formalTypes,
TypeReference returnType) {
assert holderClass != null;
assert methodName != null;
diff --git a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
index f5b0de8..319cbaf 100644
--- a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
+++ b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
@@ -758,22 +758,26 @@
return false;
}
- public boolean isStaticFieldWrittenOnlyInEnclosingStaticInitializer(DexEncodedField field) {
+ public boolean isFieldOnlyWrittenInMethod(DexEncodedField field, DexEncodedMethod method) {
assert checkIfObsolete();
assert isFieldWritten(field) : "Expected field `" + field.toSourceString() + "` to be written";
if (!isPinned(field.field)) {
- DexEncodedMethod staticInitializer =
- definitionFor(field.field.holder).asProgramClass().getClassInitializer();
- if (staticInitializer != null) {
- FieldAccessInfo fieldAccessInfo = fieldAccessInfoCollection.get(field.field);
- return fieldAccessInfo != null
- && fieldAccessInfo.isWritten()
- && !fieldAccessInfo.isWrittenOutside(staticInitializer);
- }
+ FieldAccessInfo fieldAccessInfo = fieldAccessInfoCollection.get(field.field);
+ return fieldAccessInfo != null
+ && fieldAccessInfo.isWritten()
+ && !fieldAccessInfo.isWrittenOutside(method);
}
return false;
}
+ public boolean isStaticFieldWrittenOnlyInEnclosingStaticInitializer(DexEncodedField field) {
+ assert checkIfObsolete();
+ assert isFieldWritten(field) : "Expected field `" + field.toSourceString() + "` to be written";
+ DexEncodedMethod staticInitializer =
+ definitionFor(field.field.holder).asProgramClass().getClassInitializer();
+ return staticInitializer != null && isFieldOnlyWrittenInMethod(field, staticInitializer);
+ }
+
public boolean mayPropagateValueFor(DexReference reference) {
assert checkIfObsolete();
return !isPinned(reference) && !neverPropagateValue.contains(reference);
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 400ce93..24108b7 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -11,6 +11,7 @@
import com.android.tools.r8.Diagnostic;
import com.android.tools.r8.dex.IndexedItemCollection;
+import com.android.tools.r8.errors.Unimplemented;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.experimental.graphinfo.GraphConsumer;
import com.android.tools.r8.graph.AppInfoWithSubtyping;
@@ -38,6 +39,7 @@
import com.android.tools.r8.graph.KeyedDexItem;
import com.android.tools.r8.graph.PresortedComparable;
import com.android.tools.r8.graph.ResolutionResult;
+import com.android.tools.r8.graph.ResolutionResult.FailedResolutionResult;
import com.android.tools.r8.graph.ResolutionResult.SingleResolutionResult;
import com.android.tools.r8.graph.UseRegistry.MethodHandleUse;
import com.android.tools.r8.graph.analysis.EnqueuerAnalysis;
@@ -60,6 +62,7 @@
import com.android.tools.r8.shaking.RootSetBuilder.ConsequentRootSet;
import com.android.tools.r8.shaking.RootSetBuilder.RootSet;
import com.android.tools.r8.shaking.ScopedDexMethodSet.AddMethodIfMoreVisibleResult;
+import com.android.tools.r8.utils.DequeUtils;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.SetUtils;
import com.android.tools.r8.utils.StringDiagnostic;
@@ -178,6 +181,9 @@
*/
private final SetWithReportedReason<DexProgramClass> liveTypes;
+ /** Set of types whose class initializer may execute. */
+ private final SetWithReportedReason<DexProgramClass> initializedTypes;
+
/** Set of live types defined in the library and classpath. Used to avoid duplicate tracing. */
private final Set<DexClass> liveNonProgramTypes = Sets.newIdentityHashSet();
@@ -314,6 +320,7 @@
liveTypes = new SetWithReportedReason<>();
liveAnnotations = new SetWithReason<>(graphReporter::registerAnnotation);
+ initializedTypes = new SetWithReportedReason<>();
instantiatedTypes = new SetWithReason<>(graphReporter::registerClass);
targetedMethods = new SetWithReason<>(graphReporter::registerMethod);
// This set is only populated in edge cases due to multiple default interface methods.
@@ -1186,16 +1193,6 @@
// CF libraries can be used by Android apps. See b/136698023 for more information.
ensureMethodsContinueToWidenAccess(holder, seen, reason);
- // We also need to add the corresponding <clinit> to the set of live methods, as otherwise
- // static field initialization (and other class-load-time sideeffects) will not happen.
- if (holder.hasClassInitializer()) {
- DexEncodedMethod clinit = holder.getClassInitializer();
- if (clinit != null && clinit.getOptimizationInfo().mayHaveSideEffects()) {
- assert clinit.method.holder == holder.type;
- markDirectStaticOrConstructorMethodAsLive(holder, clinit, reason);
- }
- }
-
if (holder.isSerializable(appView)) {
enqueueFirstNonSerializableClassInitializer(holder, reason);
}
@@ -1317,17 +1314,45 @@
// Only mark methods for which invocation will succeed at runtime live.
if (encodedMethod.isStatic()) {
- registerClassInitializer(clazz, reason);
+ markDirectAndIndirectClassInitializersAsLive(clazz);
markDirectStaticOrConstructorMethodAsLive(clazz, encodedMethod, reason);
}
}
- private void registerClassInitializer(DexProgramClass definition, KeepReason reason) {
- if (definition.hasClassInitializer()) {
- graphReporter.registerMethod(definition.getClassInitializer(), reason);
+ private void markDirectAndIndirectClassInitializersAsLive(DexProgramClass clazz) {
+ Deque<DexProgramClass> worklist = DequeUtils.newArrayDeque(clazz);
+ Set<DexProgramClass> visited = SetUtils.newIdentityHashSet(clazz);
+ while (!worklist.isEmpty()) {
+ DexProgramClass current = worklist.removeFirst();
+ assert visited.contains(current);
+
+ if (!markDirectClassInitializerAsLive(current)) {
+ continue;
+ }
+
+ // Mark all class initializers in all super types as live.
+ for (DexType superType : clazz.allImmediateSupertypes()) {
+ DexProgramClass superClass = getProgramClassOrNull(superType);
+ if (superClass != null && visited.add(superClass)) {
+ worklist.add(superClass);
+ }
+ }
}
}
+ /** Returns true if the class initializer became live for the first time. */
+ private boolean markDirectClassInitializerAsLive(DexProgramClass clazz) {
+ DexEncodedMethod clinit = clazz.getClassInitializer();
+ KeepReasonWitness witness = graphReporter.reportReachableClassInitializer(clazz, clinit);
+ if (!initializedTypes.add(clazz, witness)) {
+ return false;
+ }
+ if (clinit != null && clinit.getOptimizationInfo().mayHaveSideEffects()) {
+ markDirectStaticOrConstructorMethodAsLive(clazz, clinit, witness);
+ }
+ return true;
+ }
+
// Package protected due to entry point from worklist.
void markNonStaticDirectMethodAsReachable(DexMethod method, KeepReason reason) {
handleInvokeOfDirectTarget(method, reason);
@@ -1463,6 +1488,8 @@
}
// This class becomes live, so it and all its supertypes become live types.
markTypeAsLive(clazz, graphReporter.registerClass(clazz, reason));
+ // Instantiation triggers class initialization.
+ markDirectAndIndirectClassInitializersAsLive(clazz);
// For all methods of the class, if we have seen a call, mark the method live.
// We only do this for virtual calls, as the other ones will be done directly.
transitionMethodsForInstantiatedClass(clazz);
@@ -1708,7 +1735,7 @@
return;
}
- registerClassInitializer(clazz, reason);
+ markDirectAndIndirectClassInitializersAsLive(clazz);
// This field might be an instance field reachable from a static context, e.g. a getStatic that
// resolves to an instance field. We have to keep the instance field nonetheless, as otherwise
@@ -2014,13 +2041,18 @@
DexMethod method, boolean interfaceInvoke, KeepReason reason) {
ResolutionResult resolutionResult =
appInfo.resolveMethod(method.holder, method, interfaceInvoke);
- if (!resolutionResult.hasSingleTarget()) {
+ if (resolutionResult.isFailedResolution()) {
// If the resolution fails, mark each dependency causing a failure.
- markFailedResolutionTargets(resolutionResult, reason);
+ markFailedResolutionTargets(resolutionResult.asFailedResolution(), reason);
return MarkedResolutionTarget.unresolved();
}
DexEncodedMethod resolutionTarget = resolutionResult.getSingleTarget();
+ if (resolutionTarget == null) {
+ reportMissingMethod(method);
+ return MarkedResolutionTarget.unresolved();
+ }
+
DexClass resolutionTargetClass = appInfo.definitionFor(resolutionTarget.method.holder);
if (resolutionTargetClass == null) {
reportMissingClass(resolutionTarget.method.holder);
@@ -2042,8 +2074,12 @@
return new MarkedResolutionTarget(resolutionTargetClass, resolutionTarget);
}
- private void markFailedResolutionTargets(ResolutionResult failedResolution, KeepReason reason) {
- failedResolution.forEachTarget(
+ private void markFailedResolutionTargets(
+ FailedResolutionResult failedResolution, KeepReason reason) {
+ failedResolution.forEachFailureDependency(
+ clazz -> {
+ throw new Unimplemented();
+ },
method -> {
DexProgramClass clazz = getProgramClassOrNull(method.method.holder);
if (clazz != null) {
@@ -2460,6 +2496,10 @@
return;
}
+ if (method.isStatic()) {
+ markDirectAndIndirectClassInitializersAsLive(clazz);
+ }
+
Set<DexEncodedMethod> superCallTargets = superInvokeDependencies.get(method);
if (superCallTargets != null) {
for (DexEncodedMethod superCallTarget : superCallTargets) {
diff --git a/src/main/java/com/android/tools/r8/shaking/GraphReporter.java b/src/main/java/com/android/tools/r8/shaking/GraphReporter.java
index 521b90c..cf820bc 100644
--- a/src/main/java/com/android/tools/r8/shaking/GraphReporter.java
+++ b/src/main/java/com/android/tools/r8/shaking/GraphReporter.java
@@ -227,6 +227,22 @@
return KeepReasonWitness.INSTANCE;
}
+ public KeepReasonWitness reportReachableClassInitializer(
+ DexProgramClass clazz, DexEncodedMethod initializer) {
+ if (initializer != null) {
+ assert clazz.type == initializer.method.holder;
+ assert initializer.isClassInitializer();
+ if (keptGraphConsumer != null) {
+ ClassGraphNode source = getClassGraphNode(clazz.type);
+ MethodGraphNode target = getMethodGraphNode(initializer.method);
+ return reportEdge(source, target, EdgeKind.ReachableFromLiveType);
+ }
+ } else {
+ assert !clazz.hasClassInitializer();
+ }
+ return KeepReasonWitness.INSTANCE;
+ }
+
public KeepReasonWitness reportReachableMethodAsLive(
DexEncodedMethod encodedMethod, MarkedResolutionTarget reason) {
if (keptGraphConsumer != null) {
diff --git a/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java b/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java
index af056a8..adbaad4 100644
--- a/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java
@@ -5,6 +5,7 @@
package com.android.tools.r8.utils;
import static com.android.tools.r8.utils.FileUtils.CLASS_EXTENSION;
+import static com.android.tools.r8.utils.FileUtils.MODULES_PREFIX;
import com.android.tools.r8.errors.CompilationError;
import com.android.tools.r8.errors.Unreachable;
@@ -429,6 +430,46 @@
return 'L' + descriptor + ';';
}
+ public static class ModuleAndDescriptor {
+ private final String module;
+ private final String descriptor;
+
+ ModuleAndDescriptor(String module, String descriptor) {
+ this.module = module;
+ this.descriptor = descriptor;
+ }
+
+ public String getModule() {
+ return module;
+ }
+
+ public String getDescriptor() {
+ return descriptor;
+ }
+ }
+
+ /**
+ * Guess module and class descriptor from the location of a class file in a jrt file system.
+ *
+ * @param name the location in a jrt file system of the class file to convert to descriptor
+ * @return module and java class descriptor
+ */
+ public static ModuleAndDescriptor guessJrtModuleAndTypeDescriptor(String name) {
+ assert name != null;
+ assert name.endsWith(CLASS_EXTENSION)
+ : "Name " + name + " must have " + CLASS_EXTENSION + " suffix";
+ assert name.startsWith(MODULES_PREFIX)
+ : "Name " + name + " must have " + MODULES_PREFIX + " prefix";
+ assert name.charAt(MODULES_PREFIX.length()) == '/';
+ int moduleNameEnd = name.indexOf('/', MODULES_PREFIX.length() + 1);
+ String module = name.substring(MODULES_PREFIX.length() + 1, moduleNameEnd);
+ String descriptor = name.substring(moduleNameEnd + 1, name.length() - CLASS_EXTENSION.length());
+ if (descriptor.indexOf(JAVA_PACKAGE_SEPARATOR) != -1) {
+ throw new CompilationError("Unexpected class file name: " + name);
+ }
+ return new ModuleAndDescriptor(module, 'L' + descriptor + ';');
+ }
+
public static String getPathFromDescriptor(String descriptor) {
// We are quite loose on names here to support testing illegal names, too.
assert descriptor.startsWith("L");
diff --git a/src/main/java/com/android/tools/r8/utils/FileUtils.java b/src/main/java/com/android/tools/r8/utils/FileUtils.java
index 24c35fb..19fc10a 100644
--- a/src/main/java/com/android/tools/r8/utils/FileUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/FileUtils.java
@@ -30,6 +30,7 @@
public static final String JAVA_EXTENSION = ".java";
public static final String KT_EXTENSION = ".kt";
public static final String MODULE_INFO_CLASS = "module-info.class";
+ public static final String MODULES_PREFIX = "/modules";
public static final boolean isAndroid =
System.getProperty("java.vm.name").equalsIgnoreCase("Dalvik");
diff --git a/src/main/java/com/android/tools/r8/utils/HeapUtils.java b/src/main/java/com/android/tools/r8/utils/HeapUtils.java
new file mode 100644
index 0000000..6834f66
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/HeapUtils.java
@@ -0,0 +1,38 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.utils;
+
+import com.sun.management.HotSpotDiagnosticMXBean;
+import java.io.IOException;
+import java.lang.management.ManagementFactory;
+import java.nio.file.Path;
+import javax.management.MBeanServer;
+
+public class HeapUtils {
+
+ private static final String HOTSPOT_MBEAN_NAME = "com.sun.management:type=HotSpotDiagnostic";
+ private static volatile HotSpotDiagnosticMXBean hotSpotDiagnosticMXBean;
+
+ private static void initHotSpotMBean() throws IOException {
+ if (hotSpotDiagnosticMXBean == null) {
+ synchronized (HeapUtils.class) {
+ if (hotSpotDiagnosticMXBean == null) {
+ hotSpotDiagnosticMXBean = getHotSpotDiagnosticMXBean();
+ }
+ }
+ }
+ }
+
+ private static HotSpotDiagnosticMXBean getHotSpotDiagnosticMXBean() throws IOException {
+ MBeanServer server = ManagementFactory.getPlatformMBeanServer();
+ return ManagementFactory.newPlatformMXBeanProxy(
+ server, HOTSPOT_MBEAN_NAME, HotSpotDiagnosticMXBean.class);
+ }
+
+ public static void dumpHeap(Path fileName, boolean live) throws IOException {
+ initHotSpotMBean();
+ hotSpotDiagnosticMXBean.dumpHeap(fileName.toString(), live);
+ }
+}
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 2815cb8..595ac28 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -169,7 +169,8 @@
enableValuePropagation = false;
enableSideEffectAnalysis = false;
enableTreeShakingOfLibraryMethodOverrides = false;
- enableCallSiteOptimizationInfoPropagation = false;
+ enablePropagationOfDynamicTypesAtCallSites = false;
+ enablePropagationOfConstantsAtCallSites = false;
}
public boolean printTimes = System.getProperty("com.android.tools.r8.printtimes") != null;
@@ -216,7 +217,9 @@
public boolean enableNameReflectionOptimization = true;
public boolean enableStringConcatenationOptimization = true;
public boolean enableTreeShakingOfLibraryMethodOverrides = false;
- public boolean enableCallSiteOptimizationInfoPropagation = true;
+ public boolean enablePropagationOfDynamicTypesAtCallSites = true;
+ // TODO(b/69963623): enable if everything is ready, including signature rewriting at call sites.
+ public boolean enablePropagationOfConstantsAtCallSites = false;
public boolean encodeChecksums = false;
public BiPredicate<String, Long> dexClassChecksumFilter = (name, checksum) -> true;
@@ -971,6 +974,7 @@
public boolean allowUnusedProguardConfigurationRules = true;
public boolean reportUnusedProguardConfigurationRules = false;
public boolean alwaysUsePessimisticRegisterAllocation = false;
+ public boolean enableCheckCastAndInstanceOfRemoval = true;
public boolean enableDeadSwitchCaseElimination = true;
public boolean enableSwitchToIfRewriting = true;
public boolean forceRedundantConstNumberRemoval = false;
@@ -1037,6 +1041,8 @@
public int numberOfProguardIfRuleClassEvaluations = 0;
public int numberOfProguardIfRuleMemberEvaluations = 0;
}
+
+ public Consumer<DexEncodedMethod> callSiteOptimizationInfoInspector = null;
}
@VisibleForTesting
@@ -1046,6 +1052,13 @@
enableNameReflectionOptimization = false;
}
+ // TODO(b/69963623): Remove this once enabled.
+ @VisibleForTesting
+ public void enablePropagationOfConstantsAtCallSites() {
+ assert !enablePropagationOfConstantsAtCallSites;
+ enablePropagationOfConstantsAtCallSites = true;
+ }
+
private boolean hasMinApi(AndroidApiLevel level) {
assert isGeneratingDex();
return minApiLevel >= level.getLevel();
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 89ef8f3..acaf11f 100644
--- a/src/main/java/com/android/tools/r8/utils/ZipUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/ZipUtils.java
@@ -20,6 +20,7 @@
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Enumeration;
@@ -27,6 +28,7 @@
import java.util.List;
import java.util.Set;
import java.util.function.Predicate;
+import java.util.stream.Collectors;
import java.util.zip.CRC32;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
@@ -71,6 +73,21 @@
}
}
+ public static void zip(Path zipFile, Path inputDirectory) throws IOException {
+ try (ZipOutputStream stream = new ZipOutputStream(Files.newOutputStream(zipFile))) {
+ List<Path> files =
+ Files.walk(inputDirectory)
+ .filter(path -> !Files.isDirectory(path))
+ .collect(Collectors.toList());
+ for (Path path : files) {
+ ZipEntry zipEntry = new ZipEntry(inputDirectory.relativize(path).toString());
+ stream.putNextEntry(zipEntry);
+ Files.copy(path, stream);
+ stream.closeEntry();
+ }
+ }
+ }
+
public static List<File> unzip(String zipFile, File outDirectory) throws IOException {
return unzip(zipFile, outDirectory, (entry) -> true);
}
diff --git a/src/test/desugaredLibrary/conversions/DoubleSummaryStatisticsConversions.java b/src/test/desugaredLibrary/conversions/DoubleSummaryStatisticsConversions.java
new file mode 100644
index 0000000..1ad06e0
--- /dev/null
+++ b/src/test/desugaredLibrary/conversions/DoubleSummaryStatisticsConversions.java
@@ -0,0 +1,82 @@
+// Copyright (c) 2019, 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 java.util;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+
+public class DoubleSummaryStatisticsConversions {
+
+ private static final Field JAVA_LONG_COUNT_FIELD;
+ private static final Field JAVA_DOUBLE_SUM_FIELD;
+ private static final Field JAVA_DOUBLE_MIN_FIELD;
+ private static final Field JAVA_DOUBLE_MAX_FIELD;
+ private static final Field JD_LONG_COUNT_FIELD;
+ private static final Field JD_DOUBLE_SUM_FIELD;
+ private static final Field JD_DOUBLE_MIN_FIELD;
+ private static final Field JD_DOUBLE_MAX_FIELD;
+
+ static {
+ Class<?> javaDoubleSummaryStatisticsClass = java.util.DoubleSummaryStatistics.class;
+ JAVA_LONG_COUNT_FIELD = getField(javaDoubleSummaryStatisticsClass, "count");
+ JAVA_LONG_COUNT_FIELD.setAccessible(true);
+ JAVA_DOUBLE_SUM_FIELD = getField(javaDoubleSummaryStatisticsClass, "sum");
+ JAVA_DOUBLE_SUM_FIELD.setAccessible(true);
+ JAVA_DOUBLE_MIN_FIELD = getField(javaDoubleSummaryStatisticsClass, "min");
+ JAVA_DOUBLE_MIN_FIELD.setAccessible(true);
+ JAVA_DOUBLE_MAX_FIELD = getField(javaDoubleSummaryStatisticsClass, "max");
+ JAVA_DOUBLE_MAX_FIELD.setAccessible(true);
+
+ Class<?> jdDoubleSummaryStatisticsClass = j$.util.DoubleSummaryStatistics.class;
+ JD_LONG_COUNT_FIELD = getField(jdDoubleSummaryStatisticsClass, "count");
+ JD_LONG_COUNT_FIELD.setAccessible(true);
+ JD_DOUBLE_SUM_FIELD = getField(jdDoubleSummaryStatisticsClass, "sum");
+ JD_DOUBLE_SUM_FIELD.setAccessible(true);
+ JD_DOUBLE_MIN_FIELD = getField(jdDoubleSummaryStatisticsClass, "min");
+ JD_DOUBLE_MIN_FIELD.setAccessible(true);
+ JD_DOUBLE_MAX_FIELD = getField(jdDoubleSummaryStatisticsClass, "max");
+ JD_DOUBLE_MAX_FIELD.setAccessible(true);
+ }
+
+ private static Field getField(Class<?> clazz, String name) {
+ try {
+ return clazz.getDeclaredField(name);
+ } catch (NoSuchFieldException e) {
+ throw new Error("Failed summary statistics set-up.", e);
+ }
+ }
+
+ public static j$.util.DoubleSummaryStatistics convert(java.util.DoubleSummaryStatistics stats) {
+ if (stats == null) {
+ return null;
+ }
+ j$.util.DoubleSummaryStatistics newInstance = new j$.util.DoubleSummaryStatistics();
+ try {
+ JD_LONG_COUNT_FIELD.set(newInstance, stats.getCount());
+ JD_DOUBLE_SUM_FIELD.set(newInstance, stats.getSum());
+ JD_DOUBLE_MIN_FIELD.set(newInstance, stats.getMin());
+ JD_DOUBLE_MAX_FIELD.set(newInstance, stats.getMax());
+ } catch (IllegalAccessException e) {
+ throw new Error("Failed summary statistics conversion.", e);
+ }
+ return newInstance;
+ }
+
+ public static java.util.DoubleSummaryStatistics convert(j$.util.DoubleSummaryStatistics stats) {
+ if (stats == null) {
+ return null;
+ }
+ java.util.DoubleSummaryStatistics newInstance = new java.util.DoubleSummaryStatistics();
+ try {
+ JAVA_LONG_COUNT_FIELD.set(newInstance, stats.getCount());
+ JAVA_DOUBLE_SUM_FIELD.set(newInstance, stats.getSum());
+ JAVA_DOUBLE_MIN_FIELD.set(newInstance, stats.getMin());
+ JAVA_DOUBLE_MAX_FIELD.set(newInstance, stats.getMax());
+ } catch (IllegalAccessException e) {
+ throw new Error("Failed summary statistics conversion.", e);
+ }
+ return newInstance;
+ }
+}
diff --git a/src/test/desugaredLibrary/conversions/IntSummaryStatisticsConversions.java b/src/test/desugaredLibrary/conversions/IntSummaryStatisticsConversions.java
new file mode 100644
index 0000000..8351704
--- /dev/null
+++ b/src/test/desugaredLibrary/conversions/IntSummaryStatisticsConversions.java
@@ -0,0 +1,82 @@
+// Copyright (c) 2019, 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 java.util;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+
+public class IntSummaryStatisticsConversions {
+
+ private static final Field JAVA_LONG_COUNT_FIELD;
+ private static final Field JAVA_LONG_SUM_FIELD;
+ private static final Field JAVA_INT_MIN_FIELD;
+ private static final Field JAVA_INT_MAX_FIELD;
+ private static final Field JD_LONG_COUNT_FIELD;
+ private static final Field JD_LONG_SUM_FIELD;
+ private static final Field JD_INT_MIN_FIELD;
+ private static final Field JD_INT_MAX_FIELD;
+
+ static {
+ Class<?> javaIntSummaryStatisticsClass = java.util.IntSummaryStatistics.class;
+ JAVA_LONG_COUNT_FIELD = getField(javaIntSummaryStatisticsClass, "count");
+ JAVA_LONG_COUNT_FIELD.setAccessible(true);
+ JAVA_LONG_SUM_FIELD = getField(javaIntSummaryStatisticsClass, "sum");
+ JAVA_LONG_SUM_FIELD.setAccessible(true);
+ JAVA_INT_MIN_FIELD = getField(javaIntSummaryStatisticsClass, "min");
+ JAVA_INT_MIN_FIELD.setAccessible(true);
+ JAVA_INT_MAX_FIELD = getField(javaIntSummaryStatisticsClass, "max");
+ JAVA_INT_MAX_FIELD.setAccessible(true);
+
+ Class<?> jdIntSummaryStatisticsClass = j$.util.IntSummaryStatistics.class;
+ JD_LONG_COUNT_FIELD = getField(jdIntSummaryStatisticsClass, "count");
+ JD_LONG_COUNT_FIELD.setAccessible(true);
+ JD_LONG_SUM_FIELD = getField(jdIntSummaryStatisticsClass, "sum");
+ JD_LONG_SUM_FIELD.setAccessible(true);
+ JD_INT_MIN_FIELD = getField(jdIntSummaryStatisticsClass, "min");
+ JD_INT_MIN_FIELD.setAccessible(true);
+ JD_INT_MAX_FIELD = getField(jdIntSummaryStatisticsClass, "max");
+ JD_INT_MAX_FIELD.setAccessible(true);
+ }
+
+ private static Field getField(Class<?> clazz, String name) {
+ try {
+ return clazz.getDeclaredField(name);
+ } catch (NoSuchFieldException e) {
+ throw new Error("Failed summary statistics set-up.", e);
+ }
+ }
+
+ public static j$.util.IntSummaryStatistics convert(java.util.IntSummaryStatistics stats) {
+ if (stats == null) {
+ return null;
+ }
+ j$.util.IntSummaryStatistics newInstance = new j$.util.IntSummaryStatistics();
+ try {
+ JD_LONG_COUNT_FIELD.set(newInstance, stats.getCount());
+ JD_LONG_SUM_FIELD.set(newInstance, stats.getSum());
+ JD_INT_MIN_FIELD.set(newInstance, stats.getMin());
+ JD_INT_MAX_FIELD.set(newInstance, stats.getMax());
+ } catch (IllegalAccessException e) {
+ throw new Error("Failed summary statistics conversion.", e);
+ }
+ return newInstance;
+ }
+
+ public static java.util.IntSummaryStatistics convert(j$.util.IntSummaryStatistics stats) {
+ if (stats == null) {
+ return null;
+ }
+ java.util.IntSummaryStatistics newInstance = new java.util.IntSummaryStatistics();
+ try {
+ JAVA_LONG_COUNT_FIELD.set(newInstance, stats.getCount());
+ JAVA_LONG_SUM_FIELD.set(newInstance, stats.getSum());
+ JAVA_INT_MIN_FIELD.set(newInstance, stats.getMin());
+ JAVA_INT_MAX_FIELD.set(newInstance, stats.getMax());
+ } catch (IllegalAccessException e) {
+ throw new Error("Failed summary statistics conversion.", e);
+ }
+ return newInstance;
+ }
+}
diff --git a/src/test/desugaredLibrary/conversions/LongSummaryStatisticsConversions.java b/src/test/desugaredLibrary/conversions/LongSummaryStatisticsConversions.java
new file mode 100644
index 0000000..7096a2a
--- /dev/null
+++ b/src/test/desugaredLibrary/conversions/LongSummaryStatisticsConversions.java
@@ -0,0 +1,82 @@
+// Copyright (c) 2019, 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 java.util;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+
+public class LongSummaryStatisticsConversions {
+
+ private static final Field JAVA_LONG_COUNT_FIELD;
+ private static final Field JAVA_LONG_SUM_FIELD;
+ private static final Field JAVA_LONG_MIN_FIELD;
+ private static final Field JAVA_LONG_MAX_FIELD;
+ private static final Field JD_LONG_COUNT_FIELD;
+ private static final Field JD_LONG_SUM_FIELD;
+ private static final Field JD_LONG_MIN_FIELD;
+ private static final Field JD_LONG_MAX_FIELD;
+
+ static {
+ Class<?> javaLongSummaryStatisticsClass = java.util.LongSummaryStatistics.class;
+ JAVA_LONG_COUNT_FIELD = getField(javaLongSummaryStatisticsClass, "count");
+ JAVA_LONG_COUNT_FIELD.setAccessible(true);
+ JAVA_LONG_SUM_FIELD = getField(javaLongSummaryStatisticsClass, "sum");
+ JAVA_LONG_SUM_FIELD.setAccessible(true);
+ JAVA_LONG_MIN_FIELD = getField(javaLongSummaryStatisticsClass, "min");
+ JAVA_LONG_MIN_FIELD.setAccessible(true);
+ JAVA_LONG_MAX_FIELD = getField(javaLongSummaryStatisticsClass, "max");
+ JAVA_LONG_MAX_FIELD.setAccessible(true);
+
+ Class<?> jdLongSummaryStatisticsClass = j$.util.LongSummaryStatistics.class;
+ JD_LONG_COUNT_FIELD = getField(jdLongSummaryStatisticsClass, "count");
+ JD_LONG_COUNT_FIELD.setAccessible(true);
+ JD_LONG_SUM_FIELD = getField(jdLongSummaryStatisticsClass, "sum");
+ JD_LONG_SUM_FIELD.setAccessible(true);
+ JD_LONG_MIN_FIELD = getField(jdLongSummaryStatisticsClass, "min");
+ JD_LONG_MIN_FIELD.setAccessible(true);
+ JD_LONG_MAX_FIELD = getField(jdLongSummaryStatisticsClass, "max");
+ JD_LONG_MAX_FIELD.setAccessible(true);
+ }
+
+ private static Field getField(Class<?> clazz, String name) {
+ try {
+ return clazz.getDeclaredField(name);
+ } catch (NoSuchFieldException e) {
+ throw new Error("Failed summary statistics set-up.", e);
+ }
+ }
+
+ public static j$.util.LongSummaryStatistics convert(java.util.LongSummaryStatistics stats) {
+ if (stats == null) {
+ return null;
+ }
+ j$.util.LongSummaryStatistics newInstance = new j$.util.LongSummaryStatistics();
+ try {
+ JD_LONG_COUNT_FIELD.set(newInstance, stats.getCount());
+ JD_LONG_SUM_FIELD.set(newInstance, stats.getSum());
+ JD_LONG_MIN_FIELD.set(newInstance, stats.getMin());
+ JD_LONG_MAX_FIELD.set(newInstance, stats.getMax());
+ } catch (IllegalAccessException e) {
+ throw new Error("Failed summary statistics conversion.", e);
+ }
+ return newInstance;
+ }
+
+ public static java.util.LongSummaryStatistics convert(j$.util.LongSummaryStatistics stats) {
+ if (stats == null) {
+ return null;
+ }
+ java.util.LongSummaryStatistics newInstance = new java.util.LongSummaryStatistics();
+ try {
+ JAVA_LONG_COUNT_FIELD.set(newInstance, stats.getCount());
+ JAVA_LONG_SUM_FIELD.set(newInstance, stats.getSum());
+ JAVA_LONG_MIN_FIELD.set(newInstance, stats.getMin());
+ JAVA_LONG_MAX_FIELD.set(newInstance, stats.getMax());
+ } catch (IllegalAccessException e) {
+ throw new Error("Failed summary statistics conversion.", e);
+ }
+ return newInstance;
+ }
+}
diff --git a/src/test/desugaredLibrary/stubs/summarystatisticsstubs/DoubleSummaryStatistics.java b/src/test/desugaredLibrary/stubs/summarystatisticsstubs/DoubleSummaryStatistics.java
new file mode 100644
index 0000000..33710da
--- /dev/null
+++ b/src/test/desugaredLibrary/stubs/summarystatisticsstubs/DoubleSummaryStatistics.java
@@ -0,0 +1,24 @@
+// Copyright (c) 2019, 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 j$.util;
+
+public class DoubleSummaryStatistics {
+
+ public long getCount() {
+ return 0L;
+ }
+
+ public double getSum() {
+ return 0.0;
+ }
+
+ public double getMin() {
+ return 0.0;
+ }
+
+ public double getMax() {
+ return 0.0;
+ }
+}
diff --git a/src/test/desugaredLibrary/stubs/summarystatisticsstubs/IntSummaryStatistics.java b/src/test/desugaredLibrary/stubs/summarystatisticsstubs/IntSummaryStatistics.java
new file mode 100644
index 0000000..2359f8d
--- /dev/null
+++ b/src/test/desugaredLibrary/stubs/summarystatisticsstubs/IntSummaryStatistics.java
@@ -0,0 +1,24 @@
+// Copyright (c) 2019, 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 j$.util;
+
+public class IntSummaryStatistics {
+
+ public long getCount() {
+ return 0;
+ }
+
+ public long getSum() {
+ return 0;
+ }
+
+ public int getMin() {
+ return 0;
+ }
+
+ public int getMax() {
+ return 0;
+ }
+}
diff --git a/src/test/desugaredLibrary/stubs/summarystatisticsstubs/LongSummaryStatistics.java b/src/test/desugaredLibrary/stubs/summarystatisticsstubs/LongSummaryStatistics.java
new file mode 100644
index 0000000..bd993b0
--- /dev/null
+++ b/src/test/desugaredLibrary/stubs/summarystatisticsstubs/LongSummaryStatistics.java
@@ -0,0 +1,24 @@
+// Copyright (c) 2019, 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 j$.util;
+
+public class LongSummaryStatistics {
+
+ public long getCount() {
+ return 0L;
+ }
+
+ public long getSum() {
+ return 0L;
+ }
+
+ public long getMin() {
+ return 0L;
+ }
+
+ public long getMax() {
+ return 0L;
+ }
+}
diff --git a/src/test/examples/shaking18/Options.java b/src/test/examples/shaking18/Options.java
index edb9d77..7012340 100644
--- a/src/test/examples/shaking18/Options.java
+++ b/src/test/examples/shaking18/Options.java
@@ -4,9 +4,7 @@
package shaking18;
public class Options {
- // TODO(b/138913138): member value propagation can behave same with and without initialization.
- // public boolean alwaysFalse = false;
- public boolean alwaysFalse;
+ public boolean alwaysFalse = false;
public boolean dummy = false;
public Options() {}
diff --git a/src/test/java/com/android/tools/r8/CompileWithJdkClassFileProviderTest.java b/src/test/java/com/android/tools/r8/CompileWithJdkClassFileProviderTest.java
new file mode 100644
index 0000000..1754bff
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/CompileWithJdkClassFileProviderTest.java
@@ -0,0 +1,655 @@
+package com.android.tools.r8;
+
+import static org.hamcrest.CoreMatchers.anyOf;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.TestRuntime.CfVm;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import java.util.List;
+import org.hamcrest.core.StringContains;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.FieldVisitor;
+import org.objectweb.asm.Handle;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.Type;
+
+@RunWith(Parameterized.class)
+public class CompileWithJdkClassFileProviderTest extends TestBase implements Opcodes {
+
+ @Parameters(name = "{0}, library: {1}")
+ public static List<Object[]> data() {
+ return buildParameters(getTestParameters().withAllRuntimes().build(), CfVm.values());
+ }
+
+ private final TestParameters parameters;
+ private final CfVm library;
+
+ public CompileWithJdkClassFileProviderTest(TestParameters parameters, CfVm library) {
+ this.parameters = parameters;
+ this.library = library;
+ }
+
+ @Test
+ public void compileSimpleCodeWithJdkLibrary() throws Exception {
+ ClassFileResourceProvider provider =
+ JdkClassFileProvider.fromJdkHome(TestRuntime.getCheckedInJDKHome(library));
+
+ testForR8(parameters.getBackend())
+ .addLibraryProvider(provider)
+ .addProgramClasses(TestRunner.class)
+ .addKeepMainRule(TestRunner.class)
+ .setMinApi(AndroidApiLevel.B)
+ .run(parameters.getRuntime(), TestRunner.class)
+ .assertSuccessWithOutputLines("Hello, world!");
+
+ assert provider instanceof AutoCloseable;
+ ((AutoCloseable) provider).close();
+ }
+
+ @Test
+ public void compileSimpleCodeWithSystemJdk() throws Exception {
+ // Don't run duplicate tests (library is not used by the test).
+ assumeTrue(library == CfVm.JDK8);
+
+ ClassFileResourceProvider provider = JdkClassFileProvider.fromSystemJdk();
+
+ testForR8(parameters.getBackend())
+ .addLibraryProvider(provider)
+ .addProgramClasses(TestRunner.class)
+ .addKeepMainRule(TestRunner.class)
+ .setMinApi(AndroidApiLevel.B)
+ .run(parameters.getRuntime(), TestRunner.class)
+ .assertSuccessWithOutputLines("Hello, world!");
+
+ assert provider instanceof AutoCloseable;
+ ((AutoCloseable) provider).close();
+ }
+
+ @Test
+ public void compileCodeWithJava9APIUsage() throws Exception {
+ ClassFileResourceProvider provider =
+ JdkClassFileProvider.fromJdkHome(TestRuntime.getCheckedInJDKHome(library));
+
+ TestShrinkerBuilder<?, ?, ?, ?, ?> testBuilder =
+ testForR8(parameters.getBackend())
+ .addLibraryProvider(provider)
+ .addProgramClassFileData(dumpClassWhichUseJava9Flow())
+ .addKeepMainRule("MySubscriber");
+
+ if (library == CfVm.JDK8) {
+ try {
+ // java.util.concurrent.Flow$Subscriber is not present in JDK8 rt.jar.
+ testBuilder.compileWithExpectedDiagnostics(
+ diagnotics -> {
+ diagnotics.assertErrorsCount(1);
+ diagnotics.assertWarningsCount(1);
+ diagnotics.assertInfosCount(0);
+ assertThat(
+ diagnotics.getErrors().get(0).getDiagnosticMessage(),
+ StringContains.containsString("java.util.concurrent.Flow$Subscriber"));
+ assertThat(
+ diagnotics.getWarnings().get(0).getDiagnosticMessage(),
+ StringContains.containsString("java.util.concurrent.Flow$Subscriber"));
+ });
+ } catch (CompilationFailedException e) {
+ return;
+ }
+ fail("Expected compilation error");
+ } else {
+ if (parameters.getRuntime().isDex()) {
+ // java.util.concurrent.Flow$Subscriber is not present on Android.
+ testBuilder
+ .run(parameters.getRuntime(), "MySubscriber")
+ .assertFailureWithErrorThatMatches(
+ anyOf(
+ // Dalvik 4.0.4
+ containsString("java.lang.NoClassDefFoundError: MySubscriber"),
+ // Other Dalviks.
+ containsString(
+ "java.lang.ClassNotFoundException: Didn't find class \"MySubscriber\""),
+ // Art.
+ containsString(
+ "java.lang.NoClassDefFoundError: "
+ + "Failed resolution of: Ljava/util/concurrent/Flow$Subscriber;")));
+ } else {
+ if (parameters.getRuntime().asCf().getVm() == CfVm.JDK8) {
+ // java.util.concurrent.Flow$Subscriber not present in JDK8.
+ testBuilder
+ .run(parameters.getRuntime(), "MySubscriber")
+ .assertFailureWithErrorThatMatches(
+ containsString("Could not find or load main class MySubscriber"));
+
+ } else {
+ // java.util.concurrent.Flow$Subscriber present in JDK9+.
+ testBuilder
+ .run(parameters.getRuntime(), "MySubscriber")
+ .disassemble()
+ .assertSuccessWithOutputLines("Got : 1", "Got : 2", "Got : 3", "Done");
+ }
+ }
+ }
+
+ assert provider instanceof AutoCloseable;
+ ((AutoCloseable) provider).close();
+ }
+
+ /*
+ * The code below is from compiling the following source with javac from OpenJDK 9.0.4 with
+ * options:
+ *
+ * -source 1.8 -target 1.8
+ *
+ * Note that -source 1.8 on on Java 9 will use the builtin boot class path (including
+ * java.util.concurrent.Flow) if no explicit boot classpath is specified.
+ *
+ * import java.util.List;
+ * import java.lang.Thread;
+ * import java.util.concurrent.Flow.Subscriber;
+ * import java.util.concurrent.Flow.Subscription;
+ * import java.util.concurrent.SubmissionPublisher;
+ * import java.util.concurrent.locks.Condition;
+ * import java.util.concurrent.locks.Lock;
+ * import java.util.concurrent.locks.ReentrantLock;
+ *
+ * public class MySubscriber<T> implements Subscriber<T> {
+ * final static Lock lock = new ReentrantLock();
+ * final static Condition done = lock.newCondition();
+ *
+ * private Subscription subscription;
+ *
+ * @Override
+ * public void onSubscribe(Subscription subscription) {
+ * this.subscription = subscription;
+ * subscription.request(1);
+ * }
+ *
+ * @Override
+ * public void onNext(T item) {
+ * System.out.println("Got : " + item);
+ * subscription.request(1);
+ * }
+ * @Override
+ * public void onError(Throwable t) {
+ * t.printStackTrace();
+ * }
+ *
+ * @Override
+ * public void onComplete() {
+ * System.out.println("Done");
+ * signalCondition(done);
+ * }
+ *
+ * public static void awaitCondition(Condition condition) throws Exception {
+ * lock.lock();
+ * try {
+ * condition.await();
+ * } finally {
+ * lock.unlock();
+ * }
+ * }
+ *
+ * public static void signalCondition(Condition condition) {
+ * lock.lock();
+ * try {
+ * condition.signal();
+ * } finally {
+ * lock.unlock();
+ * }
+ * }
+ *
+ * public static void main(String[] args) throws Exception {
+ * SubmissionPublisher<String> publisher = new SubmissionPublisher<>();
+ * MySubscriber<String> subscriber = new MySubscriber<>();
+ * publisher.subscribe(subscriber);
+ * List<String> items = List.of("1", "2", "3");
+ *
+ * items.forEach(publisher::submit);
+ * publisher.close();
+ *
+ * awaitCondition(done);
+ * }
+ * }
+ *
+ */
+ public static byte[] dumpClassWhichUseJava9Flow() {
+
+ ClassWriter classWriter = new ClassWriter(0);
+ FieldVisitor fieldVisitor;
+ MethodVisitor methodVisitor;
+
+ classWriter.visit(
+ V1_8,
+ ACC_PUBLIC | ACC_SUPER,
+ "MySubscriber",
+ "<T:Ljava/lang/Object;>Ljava/lang/Object;Ljava/util/concurrent/Flow$Subscriber<TT;>;",
+ "java/lang/Object",
+ new String[] {"java/util/concurrent/Flow$Subscriber"});
+
+ classWriter.visitSource("MySubscriber.java", null);
+
+ classWriter.visitInnerClass(
+ "java/util/concurrent/Flow$Subscription",
+ "java/util/concurrent/Flow",
+ "Subscription",
+ ACC_PUBLIC | ACC_STATIC | ACC_ABSTRACT | ACC_INTERFACE);
+
+ classWriter.visitInnerClass(
+ "java/util/concurrent/Flow$Subscriber",
+ "java/util/concurrent/Flow",
+ "Subscriber",
+ ACC_PUBLIC | ACC_STATIC | ACC_ABSTRACT | ACC_INTERFACE);
+
+ classWriter.visitInnerClass(
+ "java/lang/invoke/MethodHandles$Lookup",
+ "java/lang/invoke/MethodHandles",
+ "Lookup",
+ ACC_PUBLIC | ACC_FINAL | ACC_STATIC);
+
+ {
+ fieldVisitor =
+ classWriter.visitField(
+ ACC_FINAL | ACC_STATIC, "lock", "Ljava/util/concurrent/locks/Lock;", null, null);
+ fieldVisitor.visitEnd();
+ }
+ {
+ fieldVisitor =
+ classWriter.visitField(
+ ACC_FINAL | ACC_STATIC, "done", "Ljava/util/concurrent/locks/Condition;", null, null);
+ fieldVisitor.visitEnd();
+ }
+ {
+ fieldVisitor =
+ classWriter.visitField(
+ ACC_PRIVATE, "subscription", "Ljava/util/concurrent/Flow$Subscription;", null, null);
+ fieldVisitor.visitEnd();
+ }
+ {
+ methodVisitor = classWriter.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
+ methodVisitor.visitCode();
+ Label label0 = new Label();
+ methodVisitor.visitLabel(label0);
+ methodVisitor.visitLineNumber(10, label0);
+ methodVisitor.visitVarInsn(ALOAD, 0);
+ methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+ methodVisitor.visitInsn(RETURN);
+ methodVisitor.visitMaxs(1, 1);
+ methodVisitor.visitEnd();
+ }
+ {
+ methodVisitor =
+ classWriter.visitMethod(
+ ACC_PUBLIC, "onSubscribe", "(Ljava/util/concurrent/Flow$Subscription;)V", null, null);
+ methodVisitor.visitCode();
+ Label label0 = new Label();
+ methodVisitor.visitLabel(label0);
+ methodVisitor.visitLineNumber(18, label0);
+ methodVisitor.visitVarInsn(ALOAD, 0);
+ methodVisitor.visitVarInsn(ALOAD, 1);
+ methodVisitor.visitFieldInsn(
+ PUTFIELD, "MySubscriber", "subscription", "Ljava/util/concurrent/Flow$Subscription;");
+ Label label1 = new Label();
+ methodVisitor.visitLabel(label1);
+ methodVisitor.visitLineNumber(19, label1);
+ methodVisitor.visitVarInsn(ALOAD, 1);
+ methodVisitor.visitInsn(LCONST_1);
+ methodVisitor.visitMethodInsn(
+ INVOKEINTERFACE, "java/util/concurrent/Flow$Subscription", "request", "(J)V", true);
+ Label label2 = new Label();
+ methodVisitor.visitLabel(label2);
+ methodVisitor.visitLineNumber(20, label2);
+ methodVisitor.visitInsn(RETURN);
+ methodVisitor.visitMaxs(3, 2);
+ methodVisitor.visitEnd();
+ }
+ {
+ methodVisitor =
+ classWriter.visitMethod(ACC_PUBLIC, "onNext", "(Ljava/lang/Object;)V", "(TT;)V", null);
+ methodVisitor.visitCode();
+ Label label0 = new Label();
+ methodVisitor.visitLabel(label0);
+ methodVisitor.visitLineNumber(24, label0);
+ methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
+ methodVisitor.visitTypeInsn(NEW, "java/lang/StringBuilder");
+ methodVisitor.visitInsn(DUP);
+ methodVisitor.visitMethodInsn(
+ INVOKESPECIAL, "java/lang/StringBuilder", "<init>", "()V", false);
+ methodVisitor.visitLdcInsn("Got : ");
+ methodVisitor.visitMethodInsn(
+ INVOKEVIRTUAL,
+ "java/lang/StringBuilder",
+ "append",
+ "(Ljava/lang/String;)Ljava/lang/StringBuilder;",
+ false);
+ methodVisitor.visitVarInsn(ALOAD, 1);
+ methodVisitor.visitMethodInsn(
+ INVOKEVIRTUAL,
+ "java/lang/StringBuilder",
+ "append",
+ "(Ljava/lang/Object;)Ljava/lang/StringBuilder;",
+ false);
+ methodVisitor.visitMethodInsn(
+ INVOKEVIRTUAL, "java/lang/StringBuilder", "toString", "()Ljava/lang/String;", false);
+ methodVisitor.visitMethodInsn(
+ INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
+ Label label1 = new Label();
+ methodVisitor.visitLabel(label1);
+ methodVisitor.visitLineNumber(25, label1);
+ methodVisitor.visitVarInsn(ALOAD, 0);
+ methodVisitor.visitFieldInsn(
+ GETFIELD, "MySubscriber", "subscription", "Ljava/util/concurrent/Flow$Subscription;");
+ methodVisitor.visitInsn(LCONST_1);
+ methodVisitor.visitMethodInsn(
+ INVOKEINTERFACE, "java/util/concurrent/Flow$Subscription", "request", "(J)V", true);
+ Label label2 = new Label();
+ methodVisitor.visitLabel(label2);
+ methodVisitor.visitLineNumber(26, label2);
+ methodVisitor.visitInsn(RETURN);
+ methodVisitor.visitMaxs(3, 2);
+ methodVisitor.visitEnd();
+ }
+ {
+ methodVisitor =
+ classWriter.visitMethod(ACC_PUBLIC, "onError", "(Ljava/lang/Throwable;)V", null, null);
+ methodVisitor.visitCode();
+ Label label0 = new Label();
+ methodVisitor.visitLabel(label0);
+ methodVisitor.visitLineNumber(29, label0);
+ methodVisitor.visitVarInsn(ALOAD, 1);
+ methodVisitor.visitMethodInsn(
+ INVOKEVIRTUAL, "java/lang/Throwable", "printStackTrace", "()V", false);
+ Label label1 = new Label();
+ methodVisitor.visitLabel(label1);
+ methodVisitor.visitLineNumber(30, label1);
+ methodVisitor.visitInsn(RETURN);
+ methodVisitor.visitMaxs(1, 2);
+ methodVisitor.visitEnd();
+ }
+ {
+ methodVisitor = classWriter.visitMethod(ACC_PUBLIC, "onComplete", "()V", null, null);
+ methodVisitor.visitCode();
+ Label label0 = new Label();
+ methodVisitor.visitLabel(label0);
+ methodVisitor.visitLineNumber(34, label0);
+ methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
+ methodVisitor.visitLdcInsn("Done");
+ methodVisitor.visitMethodInsn(
+ INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
+ Label label1 = new Label();
+ methodVisitor.visitLabel(label1);
+ methodVisitor.visitLineNumber(35, label1);
+ methodVisitor.visitFieldInsn(
+ GETSTATIC, "MySubscriber", "done", "Ljava/util/concurrent/locks/Condition;");
+ methodVisitor.visitMethodInsn(
+ INVOKESTATIC,
+ "MySubscriber",
+ "signalCondition",
+ "(Ljava/util/concurrent/locks/Condition;)V",
+ false);
+ Label label2 = new Label();
+ methodVisitor.visitLabel(label2);
+ methodVisitor.visitLineNumber(36, label2);
+ methodVisitor.visitInsn(RETURN);
+ methodVisitor.visitMaxs(2, 1);
+ methodVisitor.visitEnd();
+ }
+ {
+ methodVisitor =
+ classWriter.visitMethod(
+ ACC_PUBLIC | ACC_STATIC,
+ "awaitCondition",
+ "(Ljava/util/concurrent/locks/Condition;)V",
+ null,
+ new String[] {"java/lang/Exception"});
+ methodVisitor.visitCode();
+ Label label0 = new Label();
+ Label label1 = new Label();
+ Label label2 = new Label();
+ methodVisitor.visitTryCatchBlock(label0, label1, label2, null);
+ Label label3 = new Label();
+ methodVisitor.visitLabel(label3);
+ methodVisitor.visitLineNumber(39, label3);
+ methodVisitor.visitFieldInsn(
+ GETSTATIC, "MySubscriber", "lock", "Ljava/util/concurrent/locks/Lock;");
+ methodVisitor.visitMethodInsn(
+ INVOKEINTERFACE, "java/util/concurrent/locks/Lock", "lock", "()V", true);
+ methodVisitor.visitLabel(label0);
+ methodVisitor.visitLineNumber(41, label0);
+ methodVisitor.visitVarInsn(ALOAD, 0);
+ methodVisitor.visitMethodInsn(
+ INVOKEINTERFACE, "java/util/concurrent/locks/Condition", "await", "()V", true);
+ methodVisitor.visitLabel(label1);
+ methodVisitor.visitLineNumber(43, label1);
+ methodVisitor.visitFieldInsn(
+ GETSTATIC, "MySubscriber", "lock", "Ljava/util/concurrent/locks/Lock;");
+ methodVisitor.visitMethodInsn(
+ INVOKEINTERFACE, "java/util/concurrent/locks/Lock", "unlock", "()V", true);
+ Label label4 = new Label();
+ methodVisitor.visitLabel(label4);
+ methodVisitor.visitLineNumber(44, label4);
+ Label label5 = new Label();
+ methodVisitor.visitJumpInsn(GOTO, label5);
+ methodVisitor.visitLabel(label2);
+ methodVisitor.visitLineNumber(43, label2);
+ methodVisitor.visitFrame(Opcodes.F_SAME1, 0, null, 1, new Object[] {"java/lang/Throwable"});
+ methodVisitor.visitVarInsn(ASTORE, 1);
+ methodVisitor.visitFieldInsn(
+ GETSTATIC, "MySubscriber", "lock", "Ljava/util/concurrent/locks/Lock;");
+ methodVisitor.visitMethodInsn(
+ INVOKEINTERFACE, "java/util/concurrent/locks/Lock", "unlock", "()V", true);
+ methodVisitor.visitVarInsn(ALOAD, 1);
+ methodVisitor.visitInsn(ATHROW);
+ methodVisitor.visitLabel(label5);
+ methodVisitor.visitLineNumber(45, label5);
+ methodVisitor.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
+ methodVisitor.visitInsn(RETURN);
+ methodVisitor.visitMaxs(1, 2);
+ methodVisitor.visitEnd();
+ }
+ {
+ methodVisitor =
+ classWriter.visitMethod(
+ ACC_PUBLIC | ACC_STATIC,
+ "signalCondition",
+ "(Ljava/util/concurrent/locks/Condition;)V",
+ null,
+ null);
+ methodVisitor.visitCode();
+ Label label0 = new Label();
+ Label label1 = new Label();
+ Label label2 = new Label();
+ methodVisitor.visitTryCatchBlock(label0, label1, label2, null);
+ Label label3 = new Label();
+ methodVisitor.visitLabel(label3);
+ methodVisitor.visitLineNumber(48, label3);
+ methodVisitor.visitFieldInsn(
+ GETSTATIC, "MySubscriber", "lock", "Ljava/util/concurrent/locks/Lock;");
+ methodVisitor.visitMethodInsn(
+ INVOKEINTERFACE, "java/util/concurrent/locks/Lock", "lock", "()V", true);
+ methodVisitor.visitLabel(label0);
+ methodVisitor.visitLineNumber(50, label0);
+ methodVisitor.visitVarInsn(ALOAD, 0);
+ methodVisitor.visitMethodInsn(
+ INVOKEINTERFACE, "java/util/concurrent/locks/Condition", "signal", "()V", true);
+ methodVisitor.visitLabel(label1);
+ methodVisitor.visitLineNumber(52, label1);
+ methodVisitor.visitFieldInsn(
+ GETSTATIC, "MySubscriber", "lock", "Ljava/util/concurrent/locks/Lock;");
+ methodVisitor.visitMethodInsn(
+ INVOKEINTERFACE, "java/util/concurrent/locks/Lock", "unlock", "()V", true);
+ Label label4 = new Label();
+ methodVisitor.visitLabel(label4);
+ methodVisitor.visitLineNumber(53, label4);
+ Label label5 = new Label();
+ methodVisitor.visitJumpInsn(GOTO, label5);
+ methodVisitor.visitLabel(label2);
+ methodVisitor.visitLineNumber(52, label2);
+ methodVisitor.visitFrame(Opcodes.F_SAME1, 0, null, 1, new Object[] {"java/lang/Throwable"});
+ methodVisitor.visitVarInsn(ASTORE, 1);
+ methodVisitor.visitFieldInsn(
+ GETSTATIC, "MySubscriber", "lock", "Ljava/util/concurrent/locks/Lock;");
+ methodVisitor.visitMethodInsn(
+ INVOKEINTERFACE, "java/util/concurrent/locks/Lock", "unlock", "()V", true);
+ methodVisitor.visitVarInsn(ALOAD, 1);
+ methodVisitor.visitInsn(ATHROW);
+ methodVisitor.visitLabel(label5);
+ methodVisitor.visitLineNumber(54, label5);
+ methodVisitor.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
+ methodVisitor.visitInsn(RETURN);
+ methodVisitor.visitMaxs(1, 2);
+ methodVisitor.visitEnd();
+ }
+ {
+ methodVisitor =
+ classWriter.visitMethod(
+ ACC_PUBLIC | ACC_STATIC,
+ "main",
+ "([Ljava/lang/String;)V",
+ null,
+ new String[] {"java/lang/Exception"});
+ methodVisitor.visitCode();
+ Label label0 = new Label();
+ methodVisitor.visitLabel(label0);
+ methodVisitor.visitLineNumber(57, label0);
+ methodVisitor.visitTypeInsn(NEW, "java/util/concurrent/SubmissionPublisher");
+ methodVisitor.visitInsn(DUP);
+ methodVisitor.visitMethodInsn(
+ INVOKESPECIAL, "java/util/concurrent/SubmissionPublisher", "<init>", "()V", false);
+ methodVisitor.visitVarInsn(ASTORE, 1);
+ Label label1 = new Label();
+ methodVisitor.visitLabel(label1);
+ methodVisitor.visitLineNumber(58, label1);
+ methodVisitor.visitTypeInsn(NEW, "MySubscriber");
+ methodVisitor.visitInsn(DUP);
+ methodVisitor.visitMethodInsn(INVOKESPECIAL, "MySubscriber", "<init>", "()V", false);
+ methodVisitor.visitVarInsn(ASTORE, 2);
+ Label label2 = new Label();
+ methodVisitor.visitLabel(label2);
+ methodVisitor.visitLineNumber(59, label2);
+ methodVisitor.visitVarInsn(ALOAD, 1);
+ methodVisitor.visitVarInsn(ALOAD, 2);
+ methodVisitor.visitMethodInsn(
+ INVOKEVIRTUAL,
+ "java/util/concurrent/SubmissionPublisher",
+ "subscribe",
+ "(Ljava/util/concurrent/Flow$Subscriber;)V",
+ false);
+ Label label3 = new Label();
+ methodVisitor.visitLabel(label3);
+ methodVisitor.visitLineNumber(60, label3);
+ methodVisitor.visitLdcInsn("1");
+ methodVisitor.visitLdcInsn("2");
+ methodVisitor.visitLdcInsn("3");
+ methodVisitor.visitMethodInsn(
+ INVOKESTATIC,
+ "java/util/List",
+ "of",
+ "(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)Ljava/util/List;",
+ true);
+ methodVisitor.visitVarInsn(ASTORE, 3);
+ Label label4 = new Label();
+ methodVisitor.visitLabel(label4);
+ methodVisitor.visitLineNumber(62, label4);
+ methodVisitor.visitVarInsn(ALOAD, 3);
+ methodVisitor.visitVarInsn(ALOAD, 1);
+ methodVisitor.visitInsn(DUP);
+ methodVisitor.visitMethodInsn(
+ INVOKESTATIC,
+ "java/util/Objects",
+ "requireNonNull",
+ "(Ljava/lang/Object;)Ljava/lang/Object;",
+ false);
+ methodVisitor.visitInsn(POP);
+ methodVisitor.visitInvokeDynamicInsn(
+ "accept",
+ "(Ljava/util/concurrent/SubmissionPublisher;)Ljava/util/function/Consumer;",
+ new Handle(
+ Opcodes.H_INVOKESTATIC,
+ "java/lang/invoke/LambdaMetafactory",
+ "metafactory",
+ "(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;",
+ false),
+ new Object[] {
+ Type.getType("(Ljava/lang/Object;)V"),
+ new Handle(
+ Opcodes.H_INVOKEVIRTUAL,
+ "java/util/concurrent/SubmissionPublisher",
+ "submit",
+ "(Ljava/lang/Object;)I",
+ false),
+ Type.getType("(Ljava/lang/String;)V")
+ });
+ methodVisitor.visitMethodInsn(
+ INVOKEINTERFACE, "java/util/List", "forEach", "(Ljava/util/function/Consumer;)V", true);
+ Label label5 = new Label();
+ methodVisitor.visitLabel(label5);
+ methodVisitor.visitLineNumber(63, label5);
+ methodVisitor.visitVarInsn(ALOAD, 1);
+ methodVisitor.visitMethodInsn(
+ INVOKEVIRTUAL, "java/util/concurrent/SubmissionPublisher", "close", "()V", false);
+ Label label6 = new Label();
+ methodVisitor.visitLabel(label6);
+ methodVisitor.visitLineNumber(65, label6);
+ methodVisitor.visitFieldInsn(
+ GETSTATIC, "MySubscriber", "done", "Ljava/util/concurrent/locks/Condition;");
+ methodVisitor.visitMethodInsn(
+ INVOKESTATIC,
+ "MySubscriber",
+ "awaitCondition",
+ "(Ljava/util/concurrent/locks/Condition;)V",
+ false);
+ Label label7 = new Label();
+ methodVisitor.visitLabel(label7);
+ methodVisitor.visitLineNumber(66, label7);
+ methodVisitor.visitInsn(RETURN);
+ methodVisitor.visitMaxs(3, 4);
+ methodVisitor.visitEnd();
+ }
+ {
+ methodVisitor = classWriter.visitMethod(ACC_STATIC, "<clinit>", "()V", null, null);
+ methodVisitor.visitCode();
+ Label label0 = new Label();
+ methodVisitor.visitLabel(label0);
+ methodVisitor.visitLineNumber(11, label0);
+ methodVisitor.visitTypeInsn(NEW, "java/util/concurrent/locks/ReentrantLock");
+ methodVisitor.visitInsn(DUP);
+ methodVisitor.visitMethodInsn(
+ INVOKESPECIAL, "java/util/concurrent/locks/ReentrantLock", "<init>", "()V", false);
+ methodVisitor.visitFieldInsn(
+ PUTSTATIC, "MySubscriber", "lock", "Ljava/util/concurrent/locks/Lock;");
+ Label label1 = new Label();
+ methodVisitor.visitLabel(label1);
+ methodVisitor.visitLineNumber(12, label1);
+ methodVisitor.visitFieldInsn(
+ GETSTATIC, "MySubscriber", "lock", "Ljava/util/concurrent/locks/Lock;");
+ methodVisitor.visitMethodInsn(
+ INVOKEINTERFACE,
+ "java/util/concurrent/locks/Lock",
+ "newCondition",
+ "()Ljava/util/concurrent/locks/Condition;",
+ true);
+ methodVisitor.visitFieldInsn(
+ PUTSTATIC, "MySubscriber", "done", "Ljava/util/concurrent/locks/Condition;");
+ methodVisitor.visitInsn(RETURN);
+ methodVisitor.visitMaxs(2, 0);
+ methodVisitor.visitEnd();
+ }
+ classWriter.visitEnd();
+
+ return classWriter.toByteArray();
+ }
+
+ public static class TestRunner {
+ public static void main(String[] args) {
+ System.out.println("Hello, world!");
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/JavaCompilerTool.java b/src/test/java/com/android/tools/r8/JavaCompilerTool.java
new file mode 100644
index 0000000..d527670
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/JavaCompilerTool.java
@@ -0,0 +1,131 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8;
+
+import static com.android.tools.r8.ToolHelper.isWindows;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+import com.android.tools.r8.TestRuntime.CfVm;
+import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.utils.FileUtils;
+import com.android.tools.r8.utils.ZipUtils;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+import java.util.stream.Collectors;
+import org.junit.rules.TemporaryFolder;
+
+public class JavaCompilerTool {
+
+ private final CfVm jdk;
+ private final TestState state;
+ private final List<Path> sources = new ArrayList<>();
+ private final List<Path> classpath = new ArrayList<>();
+ private final List<String> options = new ArrayList<>();
+ private Path output = null;
+
+ private JavaCompilerTool(CfVm jdk, TestState state) {
+ this.jdk = jdk;
+ this.state = state;
+ }
+
+ public static JavaCompilerTool create(CfVm jdk, TemporaryFolder temp) {
+ assert temp != null;
+ return create(jdk, new TestState(temp));
+ }
+
+ public static JavaCompilerTool create(CfVm jdk, TestState state) {
+ assert state != null;
+ return new JavaCompilerTool(jdk, state);
+ }
+
+ public JavaCompilerTool addOptions(String... options) {
+ return addOptions(Arrays.asList(options));
+ }
+
+ public JavaCompilerTool addOptions(Collection<String> options) {
+ this.options.addAll(options);
+ return this;
+ }
+
+ public JavaCompilerTool addSourceFiles(Path... files) {
+ return addSourceFiles(Arrays.asList(files));
+ }
+
+ public JavaCompilerTool addSourceFiles(Collection<Path> files) {
+ sources.addAll(files);
+ return this;
+ }
+
+ public JavaCompilerTool addClasspathFiles(Path... files) {
+ return addClasspathFiles(Arrays.asList(files));
+ }
+
+ public JavaCompilerTool addClasspathFiles(Collection<Path> files) {
+ classpath.addAll(files);
+ return this;
+ }
+
+ /** Set the output. Must be to an existing directory or to an non-existing jar file. */
+ public JavaCompilerTool setOutputPath(Path file) {
+ assert (Files.exists(file) && Files.isDirectory(file))
+ || (!Files.exists(file) && FileUtils.isJarFile(file) && Files.exists(file.getParent()));
+ this.output = file;
+ return this;
+ }
+
+ private Path getOrCreateOutputPath() throws IOException {
+ return output != null ? output : state.getNewTempFolder().resolve("out.jar");
+ }
+
+ /** Compile and return the compilations process result object. */
+ public ProcessResult compileRaw() throws IOException {
+ assertNotNull("An output path must be specified prior to compilation.", output);
+ return compileInternal(output);
+ }
+
+ /**
+ * Compile asserting success and return the path to the output file.
+ *
+ * <p>If no output file has been set, the output is compiled to a zip archive in a temporary
+ * directory.
+ */
+ public Path compile() throws IOException {
+ Path output = getOrCreateOutputPath();
+ ProcessResult result = compileInternal(output);
+ assertEquals(result.toString(), result.exitCode, 0);
+ return output;
+ }
+
+ private ProcessResult compileInternal(Path output) throws IOException {
+ Path outdir = Files.isDirectory(output) ? output : state.getNewTempFolder();
+ List<String> cmdline = new ArrayList<>();
+ cmdline.add(ToolHelper.getJavaExecutable(jdk) + "c");
+ cmdline.addAll(options);
+ if (!classpath.isEmpty()) {
+ cmdline.add("-cp");
+ cmdline.add(
+ classpath.stream()
+ .map(Path::toString)
+ .collect(Collectors.joining(isWindows() ? ";" : ":")));
+ }
+ cmdline.add("-d");
+ cmdline.add(outdir.toString());
+ for (Path source : sources) {
+ cmdline.add(source.toString());
+ }
+ ProcessBuilder builder = new ProcessBuilder(cmdline);
+ ProcessResult javacResult = ToolHelper.runProcess(builder);
+ if (FileUtils.isJarFile(output)) {
+ assert !outdir.equals(output);
+ ZipUtils.zip(output, outdir);
+ }
+ return javacResult;
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/JdkClassFileProviderTest.java b/src/test/java/com/android/tools/r8/JdkClassFileProviderTest.java
new file mode 100644
index 0000000..d6f38af
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/JdkClassFileProviderTest.java
@@ -0,0 +1,120 @@
+package com.android.tools.r8;
+
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import com.google.common.io.ByteStreams;
+import java.io.IOException;
+import java.nio.file.NoSuchFileException;
+import java.nio.file.Path;
+import org.junit.Test;
+import org.objectweb.asm.Opcodes;
+
+public class JdkClassFileProviderTest extends TestBase implements Opcodes {
+
+ @Test
+ public void testInvalid8RuntimeClassPath() throws Exception {
+ Path path = temp.newFolder().toPath();
+ try {
+ JdkClassFileProvider.fromJdkHome(path);
+ fail("Not supposed to succeed");
+ } catch (IOException e) {
+ assertThat(e.toString(), containsString(path.toString()));
+ assertThat(e.toString(), containsString("does not look like a Java home"));
+ }
+ }
+
+ @Test
+ public void testJdk8JavHome() throws Exception {
+ ClassFileResourceProvider provider =
+ JdkClassFileProvider.fromJdkHome(ToolHelper.getJavaHome(TestRuntime.CfVm.JDK8));
+ assertJavaLangObject(provider);
+ assert provider instanceof AutoCloseable;
+ ((AutoCloseable) provider).close();
+ }
+
+ @Test
+ public void testJdk8RuntimeClassPath() throws Exception {
+ ClassFileResourceProvider provider =
+ JdkClassFileProvider.fromJavaRuntimeJar(
+ ToolHelper.getJavaHome(TestRuntime.CfVm.JDK8)
+ .resolve("jre")
+ .resolve("lib")
+ .resolve("rt.jar"));
+ assertJavaLangObject(provider);
+ assert provider instanceof AutoCloseable;
+ ((AutoCloseable) provider).close();
+ }
+
+ @Test
+ public void testJdk8SystemModules() throws Exception {
+ try {
+ JdkClassFileProvider.fromSystemModulesJdk(ToolHelper.getJavaHome(TestRuntime.CfVm.JDK8));
+ fail("Not supposed to succeed");
+ } catch (NoSuchFileException e) {
+ assertThat(e.toString(), containsString("lib/jrt-fs.jar"));
+ }
+ }
+
+ @Test
+ public void testJdk9JavaHome() throws Exception {
+ ClassFileResourceProvider provider =
+ JdkClassFileProvider.fromJdkHome(ToolHelper.getJavaHome(TestRuntime.CfVm.JDK9));
+ assertJavaLangObject(provider);
+ assertJavaUtilConcurrentFlowSubscriber(provider);
+ assert provider instanceof AutoCloseable;
+ ((AutoCloseable) provider).close();
+ }
+
+ @Test
+ public void testJdk9SystemModules() throws Exception {
+ ClassFileResourceProvider provider =
+ JdkClassFileProvider.fromSystemModulesJdk(ToolHelper.getJavaHome(TestRuntime.CfVm.JDK9));
+ assertJavaLangObject(provider);
+ assertJavaUtilConcurrentFlowSubscriber(provider);
+ assert provider instanceof AutoCloseable;
+ ((AutoCloseable) provider).close();
+ }
+
+ @Test
+ public void testJdk11JavaHome() throws Exception {
+ ClassFileResourceProvider provider =
+ JdkClassFileProvider.fromJdkHome(ToolHelper.getJavaHome(TestRuntime.CfVm.JDK11));
+ assertJavaLangObject(provider);
+ assertJavaUtilConcurrentFlowSubscriber(provider);
+ assert provider instanceof AutoCloseable;
+ ((AutoCloseable) provider).close();
+ }
+
+ @Test
+ public void testJdk11SystemModules() throws Exception {
+ ClassFileResourceProvider provider =
+ JdkClassFileProvider.fromSystemModulesJdk(ToolHelper.getJavaHome(TestRuntime.CfVm.JDK11));
+ assertJavaLangObject(provider);
+ assertJavaUtilConcurrentFlowSubscriber(provider);
+ assert provider instanceof AutoCloseable;
+ ((AutoCloseable) provider).close();
+ }
+
+ private void assertJavaLangObject(ClassFileResourceProvider provider) throws Exception {
+ assertTrue(provider.getClassDescriptors().contains("Ljava/lang/Object;"));
+ assertTrue(
+ ByteStreams.toByteArray(provider.getProgramResource("Ljava/lang/Object;").getByteStream())
+ .length
+ > 0);
+ }
+
+ private void assertJavaUtilConcurrentFlowSubscriber(ClassFileResourceProvider provider)
+ throws Exception {
+ assertTrue(provider.getClassDescriptors().contains("Ljava/util/concurrent/Flow$Subscriber;"));
+ assertTrue(
+ ByteStreams.toByteArray(
+ provider
+ .getProgramResource("Ljava/util/concurrent/Flow$Subscriber;")
+ .getByteStream())
+ .length
+ > 0);
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/KotlinCompilerTestBuilder.java b/src/test/java/com/android/tools/r8/KotlinCompilerTestBuilder.java
deleted file mode 100644
index 7fa1ba1..0000000
--- a/src/test/java/com/android/tools/r8/KotlinCompilerTestBuilder.java
+++ /dev/null
@@ -1,90 +0,0 @@
-// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-package com.android.tools.r8;
-
-import com.android.tools.r8.R8Command.Builder;
-import com.android.tools.r8.TestBase.Backend;
-import com.android.tools.r8.ToolHelper.ProcessResult;
-import com.android.tools.r8.errors.Unimplemented;
-import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.InternalOptions;
-import java.io.IOException;
-import java.nio.file.Path;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.List;
-import java.util.function.Consumer;
-import java.util.function.Supplier;
-
-public class KotlinCompilerTestBuilder
- extends TestCompilerBuilder<
- R8Command,
- Builder,
- KotlinTestCompileResult,
- KotlinTestRunResult,
- KotlinCompilerTestBuilder> {
-
- private List<Path> ktPaths;
- private List<Path> classPaths;
-
- private KotlinCompilerTestBuilder(TestState state, Builder builder) {
- super(state, builder, Backend.CF);
- }
-
- public static KotlinCompilerTestBuilder create(TestState state) {
- return new KotlinCompilerTestBuilder(state, R8Command.builder());
- }
-
- @Override
- KotlinCompilerTestBuilder self() {
- return this;
- }
-
- @Override
- KotlinTestCompileResult internalCompile(
- Builder builder,
- Consumer<InternalOptions> optionsConsumer,
- Supplier<AndroidApp> app)
- throws CompilationFailedException {
- try {
- Path outputFolder = getState().getNewTempFolder();
- Path outputJar = outputFolder.resolve("output.jar");
- ProcessResult processResult =
- ToolHelper.runKotlinc(
- null,
- classPaths,
- outputJar,
- null, // extra options
- ktPaths == null ? ToolHelper.EMPTY_PATH : ktPaths.toArray(ToolHelper.EMPTY_PATH)
- );
- return new KotlinTestCompileResult(getState(), outputJar, processResult);
- } catch (IOException e) {
- throw new CompilationFailedException(e);
- }
- }
-
- @Override
- public KotlinCompilerTestBuilder addClasspathClasses(Collection<Class<?>> classes) {
- throw new Unimplemented("No support for adding classpath classes directly");
- }
-
- @Override
- public KotlinCompilerTestBuilder addClasspathFiles(Collection<Path> files) {
- if (classPaths == null) {
- classPaths = new ArrayList<>();
- }
- classPaths.addAll(files);
- return self();
- }
-
- public KotlinCompilerTestBuilder addSourceFiles(Path... files) {
- if (ktPaths == null) {
- ktPaths = new ArrayList<>();
- }
- ktPaths.addAll(Arrays.asList(files));
- return self();
- }
-
-}
diff --git a/src/test/java/com/android/tools/r8/KotlinCompilerTool.java b/src/test/java/com/android/tools/r8/KotlinCompilerTool.java
new file mode 100644
index 0000000..e66a3de
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/KotlinCompilerTool.java
@@ -0,0 +1,124 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8;
+
+import static com.android.tools.r8.ToolHelper.KT_COMPILER;
+import static com.android.tools.r8.ToolHelper.KT_PRELOADER;
+import static com.android.tools.r8.ToolHelper.isWindows;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.TestRuntime.CfRuntime;
+import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.utils.FileUtils;
+import com.android.tools.r8.utils.ZipUtils;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+import java.util.stream.Collectors;
+import org.junit.rules.TemporaryFolder;
+
+public class KotlinCompilerTool {
+
+ private final CfRuntime jdk;
+ private final TestState state;
+ private final List<Path> sources = new ArrayList<>();
+ private final List<Path> classpath = new ArrayList<>();
+ private Path output = null;
+
+ private KotlinCompilerTool(CfRuntime jdk, TestState state) {
+ this.jdk = jdk;
+ this.state = state;
+ }
+
+ public static KotlinCompilerTool create(CfRuntime jdk, TemporaryFolder temp) {
+ return create(jdk, new TestState(temp));
+ }
+
+ public static KotlinCompilerTool create(CfRuntime jdk, TestState state) {
+ return new KotlinCompilerTool(jdk, state);
+ }
+
+ public KotlinCompilerTool addSourceFiles(Path files) {
+ return addSourceFiles(Arrays.asList(files));
+ }
+
+ public KotlinCompilerTool addSourceFiles(Collection<Path> files) {
+ sources.addAll(files);
+ return this;
+ }
+
+ public KotlinCompilerTool addClasspathFiles(Path... files) {
+ return addClasspathFiles(Arrays.asList(files));
+ }
+
+ public KotlinCompilerTool addClasspathFiles(Collection<Path> files) {
+ classpath.addAll(files);
+ return this;
+ }
+
+ public KotlinCompilerTool setOutputPath(Path file) {
+ assertTrue("Output path must be an existing directory or a non-existing jar file",
+ (!Files.exists(file) && FileUtils.isJarFile(file) && Files.exists(file.getParent()))
+ || (Files.exists(file) && Files.isDirectory(file)));
+ this.output = file;
+ return this;
+ }
+
+ private Path getOrCreateOutputPath() throws IOException {
+ return output != null
+ ? output
+ : state.getNewTempFolder().resolve("out.jar");
+ }
+
+ /** Compile and return the compilations process result object. */
+ public ProcessResult compileRaw() throws IOException {
+ assertNotNull("An output path must be specified prior to compilation.", output);
+ return compileInternal(output);
+ }
+
+ /** Compile asserting success and return the output path. */
+ public Path compile() throws IOException {
+ Path output = getOrCreateOutputPath();
+ ProcessResult result = compileInternal(output);
+ assertEquals(result.toString(), result.exitCode, 0);
+ return output;
+ }
+
+ private ProcessResult compileInternal(Path output) throws IOException {
+ Path outdir = Files.isDirectory(output) ? output : state.getNewTempFolder();
+ List<String> cmdline = new ArrayList<>();
+ cmdline.add(ToolHelper.getJavaExecutable(jdk.getVm()));
+ cmdline.add("-jar");
+ cmdline.add(KT_PRELOADER);
+ cmdline.add("org.jetbrains.kotlin.preloading.Preloader");
+ cmdline.add("-cp");
+ cmdline.add(KT_COMPILER);
+ cmdline.add("org.jetbrains.kotlin.cli.jvm.K2JVMCompiler");
+ for (Path source : sources) {
+ cmdline.add(source.toString());
+ }
+ cmdline.add("-d");
+ cmdline.add(output.toString());
+ if (!classpath.isEmpty()) {
+ cmdline.add("-cp");
+ cmdline.add(classpath
+ .stream()
+ .map(Path::toString)
+ .collect(Collectors.joining(isWindows() ? ";" : ":")));
+ }
+ ProcessBuilder builder = new ProcessBuilder(cmdline);
+ ProcessResult kotlincResult = ToolHelper.runProcess(builder);
+ if (FileUtils.isJarFile(output)) {
+ assert !outdir.equals(output);
+ ZipUtils.zip(output, outdir);
+ }
+ return kotlincResult;
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/KotlinTestCompileResult.java b/src/test/java/com/android/tools/r8/KotlinTestCompileResult.java
deleted file mode 100644
index 550e72c..0000000
--- a/src/test/java/com/android/tools/r8/KotlinTestCompileResult.java
+++ /dev/null
@@ -1,52 +0,0 @@
-// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-package com.android.tools.r8;
-
-import com.android.tools.r8.ToolHelper.ProcessResult;
-import com.android.tools.r8.utils.AndroidApp;
-import java.nio.file.Path;
-
-public class KotlinTestCompileResult
- extends TestCompileResult<KotlinTestCompileResult, KotlinTestRunResult> {
-
- private final Path outputJar;
- private final ProcessResult processResult;
-
- KotlinTestCompileResult(TestState state, Path outputJar, ProcessResult processResult) {
- super(state, AndroidApp.builder().addProgramFile(outputJar).build(), OutputMode.ClassFile);
- this.outputJar = outputJar;
- this.processResult = processResult;
- }
-
- public Path outputJar() {
- return outputJar;
- }
-
- public int exitCode() {
- return processResult.exitCode;
- }
-
- public String stdout() {
- return processResult.stdout;
- }
-
- public String stderr() {
- return processResult.stderr;
- }
-
- @Override
- public KotlinTestCompileResult self() {
- return this;
- }
-
- @Override
- public TestDiagnosticMessages getDiagnosticMessages() {
- throw new UnsupportedOperationException("No diagnostics messages from kotlinc");
- }
-
- @Override
- public KotlinTestRunResult createRunResult(TestRuntime runtime, ProcessResult result) {
- return new KotlinTestRunResult(app, runtime, result);
- }
-}
diff --git a/src/test/java/com/android/tools/r8/KotlinTestRunResult.java b/src/test/java/com/android/tools/r8/KotlinTestRunResult.java
deleted file mode 100644
index a4e413c..0000000
--- a/src/test/java/com/android/tools/r8/KotlinTestRunResult.java
+++ /dev/null
@@ -1,19 +0,0 @@
-// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-package com.android.tools.r8;
-
-import com.android.tools.r8.ToolHelper.ProcessResult;
-import com.android.tools.r8.utils.AndroidApp;
-
-public class KotlinTestRunResult extends TestRunResult<KotlinTestRunResult> {
-
- KotlinTestRunResult(AndroidApp app, TestRuntime runtime, ProcessResult result) {
- super(app, runtime, result);
- }
-
- @Override
- protected KotlinTestRunResult self() {
- return this;
- }
-}
diff --git a/src/test/java/com/android/tools/r8/TestBase.java b/src/test/java/com/android/tools/r8/TestBase.java
index 1fc315b..1d3ce80 100644
--- a/src/test/java/com/android/tools/r8/TestBase.java
+++ b/src/test/java/com/android/tools/r8/TestBase.java
@@ -11,6 +11,8 @@
import com.android.tools.r8.ClassFileConsumer.ArchiveConsumer;
import com.android.tools.r8.DataResourceProvider.Visitor;
+import com.android.tools.r8.TestRuntime.CfRuntime;
+import com.android.tools.r8.TestRuntime.CfVm;
import com.android.tools.r8.ToolHelper.ArtCommandBuilder;
import com.android.tools.r8.ToolHelper.DexVm;
import com.android.tools.r8.ToolHelper.ProcessResult;
@@ -26,6 +28,7 @@
import com.android.tools.r8.jasmin.JasminBuilder;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.shaking.serviceloader.ServiceLoaderMultipleTest.Greeter;
+import com.android.tools.r8.transformers.ClassFileTransformer;
import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.AndroidAppConsumers;
@@ -111,10 +114,6 @@
return JvmTestBuilder.create(new TestState(temp));
}
- public static KotlinCompilerTestBuilder testForKotlin(TemporaryFolder temp) {
- return KotlinCompilerTestBuilder.create(new TestState(temp));
- }
-
public static ProguardTestBuilder testForProguard(TemporaryFolder temp) {
return ProguardTestBuilder.create(new TestState(temp));
}
@@ -151,10 +150,6 @@
return testForJvm(temp);
}
- public KotlinCompilerTestBuilder testForKotlin() {
- return testForKotlin(temp);
- }
-
public TestBuilder<? extends TestRunResult<?>, ?> testForRuntime(
TestRuntime runtime, AndroidApiLevel apiLevel) {
if (runtime.isCf()) {
@@ -179,6 +174,26 @@
return testForMainDexListGenerator(temp);
}
+ public JavaCompilerTool javac(CfRuntime jdk) {
+ return JavaCompilerTool.create(jdk.getVm(), temp);
+ }
+
+ public static JavaCompilerTool javac(CfVm jdk, TemporaryFolder temp) {
+ return JavaCompilerTool.create(jdk, temp);
+ }
+
+ public KotlinCompilerTool kotlinc(CfRuntime jdk) {
+ return KotlinCompilerTool.create(jdk, temp);
+ }
+
+ public static KotlinCompilerTool kotlinc(CfRuntime jdk, TemporaryFolder temp) {
+ return KotlinCompilerTool.create(jdk, temp);
+ }
+
+ public static ClassFileTransformer transformer(Class<?> clazz) throws IOException {
+ return ClassFileTransformer.create(clazz);
+ }
+
// Actually running Proguard should only be during development.
private static final boolean RUN_PROGUARD = System.getProperty("run_proguard") != null;
// Actually running r8.jar in a forked process.
@@ -1192,6 +1207,7 @@
}
}
+ @Deprecated
public static Path runtimeJar(Backend backend) {
if (backend == Backend.DEX) {
return ToolHelper.getDefaultAndroidJar();
diff --git a/src/test/java/com/android/tools/r8/TestRuntime.java b/src/test/java/com/android/tools/r8/TestRuntime.java
index 4c17ac7..d834ebc 100644
--- a/src/test/java/com/android/tools/r8/TestRuntime.java
+++ b/src/test/java/com/android/tools/r8/TestRuntime.java
@@ -69,6 +69,10 @@
return this.ordinal() <= other.ordinal();
}
+ public boolean hasModularRuntime() {
+ return this != JDK8;
+ }
+
@Override
public String toString() {
return name;
@@ -109,10 +113,12 @@
return CHECKED_IN_JDKS.containsKey(jdk);
}
- public static Path getCheckInJDKPathFor(CfVm jdk) {
- return Paths.get("third_party", "openjdk")
- .resolve(CHECKED_IN_JDKS.get(jdk))
- .resolve(Paths.get("bin", "java"));
+ public static Path getCheckedInJDKHome(CfVm jdk) {
+ return Paths.get("third_party", "openjdk").resolve(CHECKED_IN_JDKS.get(jdk));
+ }
+
+ public static Path getCheckedInJDKPathFor(CfVm jdk) {
+ return getCheckedInJDKHome(jdk).resolve(Paths.get("bin", "java"));
}
public static TestRuntime getDefaultJavaRuntime() {
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index a84064a..0445166 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -1266,53 +1266,6 @@
return runJava(runtime, ImmutableList.of(), classpath, args);
}
- public static ProcessResult runJavac(
- CfVm runtime, List<Path> classPath, Path directoryToCompileInto, Path... classesToCompile)
- throws IOException {
- return runJavac(runtime, classPath, directoryToCompileInto, null, classesToCompile);
- }
-
- public static ProcessResult runJavac(
- CfVm runtime,
- List<Path> classPath,
- Path directoryToCompileInto,
- List<String> extraOptions,
- Path... classesToCompile)
- throws IOException {
- String[] strings = Arrays.stream(classesToCompile).map(Path::toString).toArray(String[]::new);
- List<String> cp = classPath == null ? null : classPath.stream().map(Path::toString).collect(
- Collectors.toList());
- return runJavac(runtime, cp, directoryToCompileInto.toString(), extraOptions, strings);
- }
-
- public static ProcessResult runJavac(
- CfVm runtime,
- List<String> classPath,
- String directoryToCompileInto,
- List<String> extraOptions,
- String... classesToCompile)
- throws IOException {
- List<String> cmdline =
- new ArrayList<>(
- Collections.singletonList(TestRuntime.getCheckInJDKPathFor(runtime).toString() + "c"));
- if (extraOptions != null) {
- cmdline.addAll(extraOptions);
- }
- if (classPath != null) {
- cmdline.add("-cp");
- if (isWindows()) {
- cmdline.add(String.join(";", classPath));
- } else {
- cmdline.add(String.join(":", classPath));
- }
- }
- cmdline.add("-d");
- cmdline.add(directoryToCompileInto);
- Collections.addAll(cmdline, classesToCompile);
- ProcessBuilder builder = new ProcessBuilder(cmdline);
- return ToolHelper.runProcess(builder);
- }
-
public static ProcessResult runKotlinc(
CfVm runtime,
List<Path> classPaths,
@@ -1320,8 +1273,7 @@
List<String> extraOptions,
Path... filesToCompile)
throws IOException {
- String jvm = runtime == null ? getSystemJavaExecutable() : getJavaExecutable(runtime);
- List<String> cmdline = new ArrayList<String>(Arrays.asList(jvm));
+ List<String> cmdline = new ArrayList<>(Arrays.asList(getJavaExecutable(runtime)));
cmdline.add("-jar");
cmdline.add(KT_PRELOADER);
cmdline.add("org.jetbrains.kotlin.preloading.Preloader");
@@ -1466,7 +1418,7 @@
public static String getJavaExecutable(CfVm runtime) {
if (TestRuntime.isCheckedInJDK(runtime)) {
- return TestRuntime.getCheckInJDKPathFor(runtime).toString();
+ return TestRuntime.getCheckedInJDKPathFor(runtime).toString();
} else {
// TODO(b/127785410): Always assume a non-null runtime.
assert runtime == null || TestParametersBuilder.isSystemJdk(runtime);
@@ -1474,6 +1426,10 @@
}
}
+ public static Path getJavaHome(CfVm runtime) {
+ return TestRuntime.getCheckedInJDKHome(runtime);
+ }
+
public static ProcessResult runArtRaw(ArtCommandBuilder builder) throws IOException {
return runArtProcessRaw(builder);
}
diff --git a/src/test/java/com/android/tools/r8/bridgeremoval/B77836766.java b/src/test/java/com/android/tools/r8/bridgeremoval/B77836766.java
index 4ff1850..9fe3bb4 100644
--- a/src/test/java/com/android/tools/r8/bridgeremoval/B77836766.java
+++ b/src/test/java/com/android/tools/r8/bridgeremoval/B77836766.java
@@ -16,6 +16,7 @@
import com.android.tools.r8.jasmin.JasminBuilder;
import com.android.tools.r8.jasmin.JasminBuilder.ClassBuilder;
import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.android.tools.r8.utils.codeinspector.MethodSubject;
@@ -431,9 +432,7 @@
ProcessResult javaResult = ToolHelper.runJava(outputDirectory, mainClassName);
assertEquals(0, javaResult.exitCode);
- AndroidApp processedApp = compileWithR8(jasminBuilder.build(), proguardConfig,
- // Disable inlining to avoid the (short) tested method from being inlined and then removed.
- internalOptions -> internalOptions.enableInlining = false);
+ AndroidApp processedApp = compileWithR8(jasminBuilder.build(), proguardConfig, this::configure);
// Run processed (output) program on ART
ProcessResult artResult = runOnArtRaw(processedApp, mainClassName);
@@ -442,4 +441,13 @@
return processedApp;
}
+
+ private void configure(InternalOptions options) {
+ // Callees are invoked with a simple constant, e.g., "Bar". Propagating it into the callees
+ // bothers what the tests want to check, such as exact instructions in the body that include
+ // invocation kinds, like virtual call to a bridge.
+ options.enablePropagationOfConstantsAtCallSites = false;
+ // Disable inlining to avoid the (short) tested method from being inlined and then removed.
+ options.enableInlining = false;
+ }
}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/CoreLibDesugarTestBase.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/CoreLibDesugarTestBase.java
index 9a08b11..e176fca 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/CoreLibDesugarTestBase.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/CoreLibDesugarTestBase.java
@@ -82,8 +82,6 @@
if (disableL8AnnotationRemovalForTesting) {
options.testing.disableL8AnnotationRemoval = true;
}
- // Temporary hack so that keeping an interface keeps the superinterfaces.
- options.testing.keepInheritedInterfaceMethods = true;
});
if (!disableL8AnnotationRemovalForTesting) {
assertTrue(
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/CustomCollectionSuperCallsTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/CustomCollectionSuperCallsTest.java
new file mode 100644
index 0000000..5b5b420
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/CustomCollectionSuperCallsTest.java
@@ -0,0 +1,155 @@
+// Copyright (c) 2019, 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.desugar.desugaredlibrary;
+
+import com.android.tools.r8.D8TestRunResult;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.BooleanUtils;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Spliterator;
+import java.util.Spliterators;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class CustomCollectionSuperCallsTest extends CoreLibDesugarTestBase {
+
+ private final TestParameters parameters;
+ private final boolean shrinkDesugaredLibrary;
+
+ @Parameters(name = "{1}, shrinkDesugaredLibrary: {0}")
+ public static List<Object[]> data() {
+ return buildParameters(
+ BooleanUtils.values(), getTestParameters().withDexRuntimes().withAllApiLevels().build());
+ }
+
+ public CustomCollectionSuperCallsTest(boolean shrinkDesugaredLibrary, TestParameters parameters) {
+ this.shrinkDesugaredLibrary = shrinkDesugaredLibrary;
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testCustomCollectionSuperCallsD8() throws Exception {
+ KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
+ D8TestRunResult d8TestRunResult =
+ testForD8()
+ .addInnerClasses(CustomCollectionSuperCallsTest.class)
+ .setMinApi(parameters.getApiLevel())
+ .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
+ .compile()
+ .addDesugaredCoreLibraryRunClassPath(
+ this::buildDesugaredLibrary,
+ parameters.getApiLevel(),
+ keepRuleConsumer.get(),
+ shrinkDesugaredLibrary)
+ .run(parameters.getRuntime(), Executor.class)
+ .assertSuccess();
+ assertLines2By2Correct(d8TestRunResult.getStdOut());
+ }
+
+ static class Executor {
+
+ // ArrayList spliterator is Spliterator.ORDERED | Spliterator.SIZED | Spliterator.SUBSIZED.
+
+ public static void main(String[] args) {
+ rawTypes();
+ inheritedTypes();
+ }
+
+ public static void rawTypes() {
+ Spliterator<String> stringSpliterator;
+
+ stringSpliterator = new MyArrayListOverride().superSpliterator();
+ System.out.println(stringSpliterator.hasCharacteristics(Spliterator.ORDERED));
+ System.out.println(true);
+ System.out.println(stringSpliterator.hasCharacteristics(Spliterator.IMMUTABLE));
+ System.out.println(false);
+
+ stringSpliterator = new MyArrayListOverrideSubclass().superSpliterator();
+ System.out.println(stringSpliterator.hasCharacteristics(Spliterator.ORDERED));
+ System.out.println(false);
+ System.out.println(stringSpliterator.hasCharacteristics(Spliterator.IMMUTABLE));
+ System.out.println(true);
+
+ stringSpliterator = new MyArrayListNoOverride().superSpliterator();
+ System.out.println(stringSpliterator.hasCharacteristics(Spliterator.ORDERED));
+ System.out.println(true);
+ System.out.println(stringSpliterator.hasCharacteristics(Spliterator.IMMUTABLE));
+ System.out.println(false);
+
+ stringSpliterator = new MyArrayListSubclassNoOverride().superSpliterator();
+ System.out.println(stringSpliterator.hasCharacteristics(Spliterator.ORDERED));
+ System.out.println(true);
+ System.out.println(stringSpliterator.hasCharacteristics(Spliterator.IMMUTABLE));
+ System.out.println(false);
+ }
+
+ public static void inheritedTypes() {
+ Spliterator<String> stringSpliterator;
+
+ stringSpliterator =
+ ((MyArrayListOverride) new MyArrayListOverrideSubclass()).superSpliterator();
+ System.out.println(stringSpliterator.hasCharacteristics(Spliterator.ORDERED));
+ System.out.println(false);
+ System.out.println(stringSpliterator.hasCharacteristics(Spliterator.IMMUTABLE));
+ System.out.println(true);
+
+ stringSpliterator =
+ ((MyArrayListNoOverride) new MyArrayListSubclassNoOverride()).superSpliterator();
+ System.out.println(stringSpliterator.hasCharacteristics(Spliterator.ORDERED));
+ System.out.println(true);
+ System.out.println(stringSpliterator.hasCharacteristics(Spliterator.IMMUTABLE));
+ System.out.println(false);
+ }
+ }
+
+ static class MyArrayListOverride extends ArrayList<String> {
+
+ @Override
+ public Spliterator<String> spliterator() {
+ return Spliterators.spliterator(this, Spliterator.IMMUTABLE);
+ }
+
+ public Spliterator<String> superSpliterator() {
+ return super.spliterator();
+ }
+ }
+
+ static class MyArrayListOverrideSubclass extends MyArrayListOverride {
+
+ @Override
+ public Spliterator<String> superSpliterator() {
+ return super.spliterator();
+ }
+
+ // Unused, but prove the super invoke won't resolve into it.
+ @Override
+ public Spliterator<String> spliterator() {
+ return Spliterators.spliterator(this, Spliterator.IMMUTABLE | Spliterator.ORDERED);
+ }
+ }
+
+ static class MyArrayListNoOverride extends ArrayList<String> {
+
+ public Spliterator<String> superSpliterator() {
+ return super.spliterator();
+ }
+ }
+
+ static class MyArrayListSubclassNoOverride extends MyArrayListNoOverride {
+ public Spliterator<String> superSpliterator() {
+ return super.spliterator();
+ }
+
+ // Unused, but prove the super invoke won't resolve into it.
+ @Override
+ public Spliterator<String> spliterator() {
+ return Spliterators.spliterator(this, Spliterator.IMMUTABLE | Spliterator.ORDERED);
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/LinkedHashSetTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/LinkedHashSetTest.java
index afae3b0..e9f221d 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/LinkedHashSetTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/LinkedHashSetTest.java
@@ -7,6 +7,7 @@
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
import java.util.LinkedHashSet;
+import java.util.Set;
import java.util.Spliterator;
import java.util.Spliterators;
import org.junit.Test;
@@ -43,15 +44,22 @@
assertLines2By2Correct(stdOut);
}
+ @SuppressWarnings("WeakerAccess")
static class Executor {
- @SuppressWarnings("RedundantOperationOnEmptyContainer")
- public static void main(String[] args) {
- Spliterator<String> spliterator;
+ public static void main(String[] args) {
// Spliterator of Set is only distinct.
// Spliterator of LinkedHashSet is distinct and ordered.
// Spliterator of CustomLinkedHashSetOverride is distinct, ordered and immutable.
// If an incorrect method is found, characteristics are incorrect.
+ rawTypes();
+ linkedHashSetType();
+ setType();
+ }
+
+ @SuppressWarnings("RedundantOperationOnEmptyContainer")
+ public static void rawTypes() {
+ Spliterator<String> spliterator;
spliterator = new LinkedHashSet<String>().spliterator();
System.out.println(spliterator.hasCharacteristics(Spliterator.DISTINCT));
@@ -92,6 +100,70 @@
System.out.println("true");
System.out.println(spliterator.hasCharacteristics(Spliterator.IMMUTABLE));
System.out.println("true");
+
+ spliterator = new SubclassNoOverride<String>().superSpliterator();
+ System.out.println(spliterator.hasCharacteristics(Spliterator.DISTINCT));
+ System.out.println("true");
+ System.out.println(spliterator.hasCharacteristics(Spliterator.ORDERED));
+ System.out.println("true");
+ System.out.println(spliterator.hasCharacteristics(Spliterator.IMMUTABLE));
+ System.out.println("false");
+ }
+
+ @SuppressWarnings("RedundantOperationOnEmptyContainer")
+ public static void linkedHashSetType() {
+ System.out.println("-");
+ System.out.println("-");
+ Spliterator<String> spliterator;
+
+ spliterator =
+ ((LinkedHashSet<String>) new CustomLinkedHashSetOverride<String>()).spliterator();
+ System.out.println(spliterator.hasCharacteristics(Spliterator.DISTINCT));
+ System.out.println("true");
+ System.out.println(spliterator.hasCharacteristics(Spliterator.ORDERED));
+ System.out.println("true");
+ System.out.println(spliterator.hasCharacteristics(Spliterator.IMMUTABLE));
+ System.out.println("true");
+
+ spliterator =
+ ((LinkedHashSet<String>) new CustomLinkedHashSetNoOverride<String>()).spliterator();
+ System.out.println(spliterator.hasCharacteristics(Spliterator.DISTINCT));
+ System.out.println("true");
+ System.out.println(spliterator.hasCharacteristics(Spliterator.ORDERED));
+ System.out.println("true");
+ System.out.println(spliterator.hasCharacteristics(Spliterator.IMMUTABLE));
+ System.out.println("false");
+ }
+
+ @SuppressWarnings("RedundantOperationOnEmptyContainer")
+ public static void setType() {
+ System.out.println("-");
+ System.out.println("-");
+ Spliterator<String> spliterator;
+
+ spliterator = ((Set<String>) new LinkedHashSet<String>()).spliterator();
+ System.out.println(spliterator.hasCharacteristics(Spliterator.DISTINCT));
+ System.out.println("true");
+ System.out.println(spliterator.hasCharacteristics(Spliterator.ORDERED));
+ System.out.println("true");
+ System.out.println(spliterator.hasCharacteristics(Spliterator.IMMUTABLE));
+ System.out.println("false");
+
+ spliterator = ((Set<String>) new CustomLinkedHashSetOverride<String>()).spliterator();
+ System.out.println(spliterator.hasCharacteristics(Spliterator.DISTINCT));
+ System.out.println("true");
+ System.out.println(spliterator.hasCharacteristics(Spliterator.ORDERED));
+ System.out.println("true");
+ System.out.println(spliterator.hasCharacteristics(Spliterator.IMMUTABLE));
+ System.out.println("true");
+
+ spliterator = ((Set<String>) new CustomLinkedHashSetNoOverride<String>()).spliterator();
+ System.out.println(spliterator.hasCharacteristics(Spliterator.DISTINCT));
+ System.out.println("true");
+ System.out.println(spliterator.hasCharacteristics(Spliterator.ORDERED));
+ System.out.println("true");
+ System.out.println(spliterator.hasCharacteristics(Spliterator.IMMUTABLE));
+ System.out.println("false");
}
}
@@ -109,6 +181,7 @@
}
static class SubclassOverride<E> extends CustomLinkedHashSetOverride<E> {
+
@Override
public Spliterator<E> superSpliterator() {
return super.spliterator();
@@ -117,4 +190,11 @@
@SuppressWarnings("WeakerAccess")
static class CustomLinkedHashSetNoOverride<E> extends LinkedHashSet<E> {}
+
+ static class SubclassNoOverride<E> extends CustomLinkedHashSetNoOverride<E> {
+
+ public Spliterator<E> superSpliterator() {
+ return super.spliterator();
+ }
+ }
}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ProgramRewritingTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ProgramRewritingTest.java
index f2889ca..59db52d 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ProgramRewritingTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ProgramRewritingTest.java
@@ -159,7 +159,7 @@
assertInvokeStaticMatching(invokes, 2, "Collection$-EL;->stream");
assertInvokeInterfaceMatching(invokes, 3, "Set;->iterator");
assertInvokeStaticMatching(invokes, 4, "Collection$-EL;->stream");
- assertInvokeStaticMatching(invokes, 5, "DesugarLinkedHashSet;->spliterator");
+ assertInvokeStaticMatching(invokes, 5, "Set$-EL;->spliterator");
assertInvokeInterfaceMatching(invokes, 9, "Iterator;->remove");
assertInvokeStaticMatching(invokes, 10, "DesugarArrays;->spliterator");
assertInvokeStaticMatching(invokes, 11, "DesugarArrays;->spliterator");
@@ -213,9 +213,6 @@
" j$.util.Spliterator spliterator(java.lang.Object[], int, int);",
"}",
"-keep class j$.util.stream.IntStream",
- "-keep class j$.util.DesugarLinkedHashSet {",
- " j$.util.Spliterator spliterator(java.util.LinkedHashSet);",
- "}",
"-keep class j$.util.stream.Stream",
"-keep class j$.util.Spliterator",
"-keep class j$.util.function.ToIntFunction { *; }");
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/RetargetOverrideTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/RetargetOverrideTest.java
index bc6054a..b20a716 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/RetargetOverrideTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/RetargetOverrideTest.java
@@ -6,9 +6,12 @@
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
+import java.time.Instant;
+import java.time.ZonedDateTime;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.IntUnaryOperator;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -65,6 +68,7 @@
public static void main(String[] args) {
java.sql.Date date = new java.sql.Date(123456789);
+ // The following one is not working on JVMs, but works on Android...
System.out.println(date.toInstant());
System.out.println("1970-01-02T10:17:36.789Z");
@@ -82,8 +86,12 @@
MyCalendarNoOverride myCalN = new MyCalendarNoOverride(1990, 2, 22);
System.out.println(myCalN.toZonedDateTime());
System.out.println("1990-03-22T00:00Z[GMT]");
+ System.out.println(myCalN.superToZonedDateTime());
+ System.out.println("1990-03-22T00:00Z[GMT]");
System.out.println(myCalN.toInstant());
System.out.println("1990-03-22T00:00:00Z");
+ System.out.println(myCalN.superToInstant());
+ System.out.println("1990-03-22T00:00:00Z");
// TODO(b/142846107): Enable overrides of retarget core members.
// MyDateOverride myDate = new MyDateOverride(123456789);
@@ -93,16 +101,16 @@
MyDateNoOverride myDateN = new MyDateNoOverride(123456789);
System.out.println(myDateN.toInstant());
System.out.println("1970-01-02T10:17:36.789Z");
+ System.out.println(myDateN.superToInstant());
+ System.out.println("1970-01-02T10:17:36.789Z");
MyAtomicInteger myAtomicInteger = new MyAtomicInteger(42);
System.out.println(myAtomicInteger.getAndUpdate(x -> x + 1));
System.out.println("42");
- System.out.println(myAtomicInteger.getAndUpdate(x -> x + 5));
+ System.out.println(myAtomicInteger.superGetAndUpdate(x -> x + 2));
System.out.println("43");
System.out.println(myAtomicInteger.updateAndGet(x -> x + 100));
- System.out.println("148");
- System.out.println(myAtomicInteger.updateAndGet(x -> x + 500));
- System.out.println("648");
+ System.out.println("145");
}
}
@@ -124,6 +132,14 @@
public MyCalendarNoOverride(int year, int month, int dayOfMonth) {
super(year, month, dayOfMonth);
}
+
+ public Instant superToInstant() {
+ return super.toInstant();
+ }
+
+ public ZonedDateTime superToZonedDateTime() {
+ return super.toZonedDateTime();
+ }
}
// static class MyDateOverride extends Date {
@@ -143,6 +159,10 @@
public MyDateNoOverride(long date) {
super(date);
}
+
+ public Instant superToInstant() {
+ return super.toInstant();
+ }
}
static class MyAtomicInteger extends AtomicInteger {
@@ -150,5 +170,9 @@
public MyAtomicInteger(int initialValue) {
super(initialValue);
}
+
+ public int superGetAndUpdate(IntUnaryOperator op) {
+ return super.getAndUpdate(op);
+ }
}
}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/APIConversionFinalWarningTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/APIConversionFinalWarningTest.java
deleted file mode 100644
index 752c2e6..0000000
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/APIConversionFinalWarningTest.java
+++ /dev/null
@@ -1,69 +0,0 @@
-// Copyright (c) 2019, 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.desugar.desugaredlibrary.conversiontests;
-
-import static org.hamcrest.CoreMatchers.startsWith;
-
-import com.android.tools.r8.TestRuntime.DexRuntime;
-import com.android.tools.r8.ToolHelper.DexVm;
-import com.android.tools.r8.utils.AndroidApiLevel;
-import com.android.tools.r8.utils.StringUtils;
-import java.nio.file.Path;
-import java.util.LongSummaryStatistics;
-import org.junit.Test;
-
-public class APIConversionFinalWarningTest extends APIConversionTestBase {
-
- @Test
- public void testFinalMethod() throws Exception {
- Path customLib = testForD8().addProgramClasses(CustomLibClass.class).compile().writeToZip();
- testForD8()
- .setMinApi(AndroidApiLevel.B)
- .addProgramClasses(Executor.class)
- .addLibraryClasses(CustomLibClass.class)
- .enableCoreLibraryDesugaring(AndroidApiLevel.B)
- .compile()
- .assertInfoMessageThatMatches(
- startsWith(
- "Desugared library API conversion: cannot wrap final methods"
- + " [java.util.LongSummaryStatistics"))
- .addDesugaredCoreLibraryRunClassPath(
- this::buildDesugaredLibraryWithConversionExtension, AndroidApiLevel.B)
- .addRunClasspathFiles(customLib)
- .run(new DexRuntime(DexVm.ART_9_0_0_HOST), Executor.class)
- .assertSuccessWithOutput(
- StringUtils.lines(
- "Unsupported conversion for java.util.LongSummaryStatistics. See compilation time"
- + " infos for more details."));
- }
-
- static class Executor {
-
- public static void main(String[] args) {
- LongSummaryStatistics statistics = new LongSummaryStatistics();
- statistics.accept(3L);
- try {
- makeCall(statistics);
- } catch (RuntimeException e) {
- System.out.println(e.getMessage());
- }
- }
-
- @SuppressWarnings("ResultOfMethodCallIgnored")
- static void makeCall(LongSummaryStatistics statistics) {
- CustomLibClass.call(statistics);
- }
- }
-
- // This class will be put at compilation time as library and on the runtime class path.
- // This class is convenient for easy testing. Each method plays the role of methods in the
- // platform APIs for which argument/return values need conversion.
- static class CustomLibClass {
-
- public static long call(LongSummaryStatistics stats) {
- return stats.getMax();
- }
- }
-}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/APIConversionTestBase.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/APIConversionTestBase.java
index 6825616..7d816e5 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/APIConversionTestBase.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/APIConversionTestBase.java
@@ -4,11 +4,11 @@
package com.android.tools.r8.desugar.desugaredlibrary.conversiontests;
+import com.android.tools.r8.TestRuntime.CfRuntime;
import com.android.tools.r8.TestRuntime.CfVm;
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.desugar.desugaredlibrary.CoreLibDesugarTestBase;
import com.android.tools.r8.utils.AndroidApiLevel;
-import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
@@ -25,26 +25,26 @@
"JDK8 javac is required to avoid dealing with modules and JDK8 is not checked-in on"
+ " windows",
!ToolHelper.isWindows());
- File conversionFolder = temp.newFolder("conversions");
- File stubsFolder = temp.newFolder("stubs");
+
+ CfRuntime runtime = new CfRuntime(CfVm.JDK8);
+ Path conversionFolder = temp.newFolder("conversions").toPath();
// Compile the stubs to be able to compile the conversions.
- ToolHelper.runJavac(
- CfVm.JDK8,
- null,
- stubsFolder.toPath(),
- getAllFilesWithSuffixInDirectory(CONVERSION_FOLDER.resolve("stubs"), "java"));
+ Path stubsJar =
+ javac(runtime)
+ .addSourceFiles(
+ getAllFilesWithSuffixInDirectory(CONVERSION_FOLDER.resolve("stubs"), "java"))
+ .compile();
// Compile the conversions using the stubs.
- ArrayList<Path> classPath = new ArrayList<>();
- classPath.add(stubsFolder.toPath());
- ToolHelper.runJavac(
- CfVm.JDK8,
- classPath,
- conversionFolder.toPath(),
- getAllFilesWithSuffixInDirectory(CONVERSION_FOLDER.resolve("conversions"), "java"));
+ javac(runtime)
+ .setOutputPath(conversionFolder)
+ .addClasspathFiles(stubsJar)
+ .addSourceFiles(
+ getAllFilesWithSuffixInDirectory(CONVERSION_FOLDER.resolve("conversions"), "java"))
+ .compile();
- Path[] classes = getAllFilesWithSuffixInDirectory(conversionFolder.toPath(), "class");
+ Path[] classes = getAllFilesWithSuffixInDirectory(conversionFolder, "class");
assert classes.length > 0
: "Something went wrong during compilation, check the runJavac return value for debugging.";
return classes;
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/SummaryStatisticsConversionTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/SummaryStatisticsConversionTest.java
new file mode 100644
index 0000000..3e65742
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/SummaryStatisticsConversionTest.java
@@ -0,0 +1,132 @@
+// Copyright (c) 2019, 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.desugar.desugaredlibrary.conversiontests;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ToolHelper.DexVm.Version;
+import com.android.tools.r8.utils.StringUtils;
+import java.nio.file.Path;
+import java.util.Arrays;
+import java.util.DoubleSummaryStatistics;
+import java.util.IntSummaryStatistics;
+import java.util.LongSummaryStatistics;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class SummaryStatisticsConversionTest extends APIConversionTestBase {
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ // Below 7 XXXSummaryStatistics are not present and conversions are pointless.
+ return getTestParameters()
+ .withDexRuntimesStartingFromIncluding(Version.V7_0_0)
+ .withAllApiLevels()
+ .build();
+ }
+
+ public SummaryStatisticsConversionTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testStats() throws Exception {
+ Path customLib =
+ testForD8()
+ .setMinApi(parameters.getApiLevel())
+ .addProgramClasses(CustomLibClass.class)
+ .compile()
+ .writeToZip();
+ testForD8()
+ .setMinApi(parameters.getApiLevel())
+ .addProgramClasses(Executor.class)
+ .addLibraryClasses(CustomLibClass.class)
+ .enableCoreLibraryDesugaring(parameters.getApiLevel())
+ .compile()
+ .addDesugaredCoreLibraryRunClassPath(
+ this::buildDesugaredLibraryWithConversionExtension, parameters.getApiLevel())
+ .addRunClasspathFiles(customLib)
+ .run(parameters.getRuntime(), Executor.class)
+ .assertSuccessWithOutput(
+ StringUtils.lines(
+ "2", "1", "42", "42", "42", "1", "42", "42", "42", "1", "42.0", "42.0", "42.0"));
+ }
+
+ static class Executor {
+
+ public static void main(String[] args) {
+ // The realTest represents scenario applicable in Android apps, subsequent tests use
+ // mocked CustomLib to ensure all cases are correct.
+ realTest();
+ longTest();
+ intTest();
+ doubleTest();
+ }
+
+ private static void realTest() {
+ System.out.println("foo".subSequence(0, 2).codePoints().summaryStatistics().getCount());
+ }
+
+ public static void longTest() {
+ long[] longs = new long[1];
+ longs[0] = 42L;
+ LongSummaryStatistics mix =
+ CustomLibClass.mix(Arrays.stream(longs).summaryStatistics(), new LongSummaryStatistics());
+ System.out.println(mix.getCount());
+ System.out.println(mix.getMin());
+ System.out.println(mix.getMax());
+ System.out.println(mix.getSum());
+ }
+
+ public static void intTest() {
+ int[] ints = new int[1];
+ ints[0] = 42;
+ IntSummaryStatistics mix =
+ CustomLibClass.mix(Arrays.stream(ints).summaryStatistics(), new IntSummaryStatistics());
+ System.out.println(mix.getCount());
+ System.out.println(mix.getMin());
+ System.out.println(mix.getMax());
+ System.out.println(mix.getSum());
+ }
+
+ public static void doubleTest() {
+ double[] doubles = new double[1];
+ doubles[0] = 42L;
+ DoubleSummaryStatistics mix =
+ CustomLibClass.mix(
+ Arrays.stream(doubles).summaryStatistics(), new DoubleSummaryStatistics());
+ System.out.println(mix.getCount());
+ System.out.println(mix.getMin());
+ System.out.println(mix.getMax());
+ System.out.println(mix.getSum());
+ }
+ }
+
+ // This class will be put at compilation time as library and on the runtime class path.
+ // This class is convenient for easy testing. Each method plays the role of methods in the
+ // platform APIs for which argument/return values need conversion.
+ static class CustomLibClass {
+
+ public static LongSummaryStatistics mix(
+ LongSummaryStatistics stats1, LongSummaryStatistics stats2) {
+ return stats1;
+ }
+
+ public static IntSummaryStatistics mix(
+ IntSummaryStatistics stats1, IntSummaryStatistics stats2) {
+ return stats1;
+ }
+
+ public static DoubleSummaryStatistics mix(
+ DoubleSummaryStatistics stats1, DoubleSummaryStatistics stats2) {
+ return stats1;
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/desugar_jdk_libs.json b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/desugar_jdk_libs.json
index ab1a617..6e791b6 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/desugar_jdk_libs.json
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/desugar_jdk_libs.json
@@ -1,6 +1,6 @@
{
"configuration_format_version": 2,
- "version": "0.7.0",
+ "version": "0.8.0",
"required_compilation_api_level": 26,
"library_flags": [
{
@@ -21,6 +21,9 @@
"api_level_below_or_equal": 23,
"rewrite_prefix": {
"j$.util.Optional": "java.util.Optional",
+ "j$.util.LongSummaryStatistics": "java.util.LongSummaryStatistics",
+ "j$.util.IntSummaryStatistics": "java.util.IntSummaryStatistics",
+ "j$.util.DoubleSummaryStatistics": "java.util.DoubleSummaryStatistics",
"java.util.stream.": "j$.util.stream.",
"java.util.function.": "j$.util.function.",
"java.util.Comparators": "j$.util.Comparators",
@@ -134,7 +137,10 @@
"java.util.Optional": "j$.util.OptionalConversions",
"java.util.OptionalDouble": "j$.util.OptionalConversions",
"java.util.OptionalInt": "j$.util.OptionalConversions",
- "java.util.OptionalLong": "j$.util.OptionalConversions"
+ "java.util.OptionalLong": "j$.util.OptionalConversions",
+ "java.util.LongSummaryStatistics": "j$.util.LongSummaryStatisticsConversions",
+ "java.util.IntSummaryStatistics": "j$.util.IntSummaryStatisticsConversions",
+ "java.util.DoubleSummaryStatistics": "j$.util.DoubleSummaryStatisticsConversions"
}
}
],
@@ -143,6 +149,9 @@
"-keepclassmembers class j$.util.concurrent.ConcurrentHashMap { int sizeCtl; int transferIndex; long baseCount; int cellsBusy; }",
"-keepclassmembers class j$.util.concurrent.ConcurrentHashMap$CounterCell { long value; }",
"-keepclassmembers enum * { public static **[] values(); public static ** valueOf(java.lang.String); }",
- "-keeppackagenames j$"
+ "-keeppackagenames j$",
+ "-keepclassmembers class j$.util.IntSummaryStatistics { long count; long sum; int min; int max; }",
+ "-keepclassmembers class j$.util.LongSummaryStatistics { long count; long sum; long min; long max; }",
+ "-keepclassmembers class j$.util.DoubleSummaryStatistics { long count; double sum; double min; double max; }"
]
}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/desugaredlibraryjdktests/Jdk11AtomicTests.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/desugaredlibraryjdktests/Jdk11AtomicTests.java
index ff60941..db0a09a 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/desugaredlibraryjdktests/Jdk11AtomicTests.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/desugaredlibraryjdktests/Jdk11AtomicTests.java
@@ -15,6 +15,7 @@
import com.android.tools.r8.ToolHelper.DexVm.Version;
import com.android.tools.r8.utils.BooleanUtils;
import com.android.tools.r8.utils.StringUtils;
+import java.io.File;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Collections;
@@ -62,11 +63,14 @@
@BeforeClass
public static void compileAtomicClasses() throws Exception {
- ToolHelper.runJavac(
- CfVm.JDK11,
- Collections.singletonList(Paths.get(JDK_TESTS_BUILD_DIR + "testng-6.10.jar")),
- ATOMIC_COMPILED_TESTS_FOLDER,
- ATOMIC_TESTS_FILES);
+ File atomicClassesDir = new File(ATOMIC_COMPILED_TESTS_FOLDER.toString());
+ assert atomicClassesDir.exists() || atomicClassesDir.mkdirs();
+ javac(CfVm.JDK11, getStaticTemp())
+ .addClasspathFiles(
+ Collections.singletonList(Paths.get(JDK_TESTS_BUILD_DIR + "testng-6.10.jar")))
+ .addSourceFiles(ATOMIC_TESTS_FILES)
+ .setOutputPath(ATOMIC_COMPILED_TESTS_FOLDER)
+ .compile();
}
@Test
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/desugaredlibraryjdktests/Jdk11ConcurrentMapTests.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/desugaredlibraryjdktests/Jdk11ConcurrentMapTests.java
index b983d33..71458f0 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/desugaredlibraryjdktests/Jdk11ConcurrentMapTests.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/desugaredlibraryjdktests/Jdk11ConcurrentMapTests.java
@@ -22,6 +22,7 @@
import com.android.tools.r8.utils.BooleanUtils;
import com.android.tools.r8.utils.StringUtils;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import java.io.File;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
@@ -73,27 +74,35 @@
}
@BeforeClass
- public static void compileAtomicClasses() throws Exception {
- ToolHelper.runJavac(
- CfVm.JDK11,
- Collections.singletonList(Paths.get(JDK_TESTS_BUILD_DIR + "testng-6.10.jar")),
- CONCURRENT_COMPILED_TESTS_FOLDER,
- getAllFilesWithSuffixInDirectory(CONCURRENT_TESTS_FOLDER, JAVA_EXTENSION));
- List<Path> concHashFilesAndDependencies = new ArrayList<>();
- Collections.addAll(
- concHashFilesAndDependencies,
- getAllFilesWithSuffixInDirectory(CONCURRENT_HASH_TESTS_FOLDER, JAVA_EXTENSION));
- Collections.addAll(concHashFilesAndDependencies, SUPPORT_LIBS);
- ToolHelper.runJavac(
- CfVm.JDK11,
- Collections.singletonList(Paths.get(JDK_TESTS_BUILD_DIR + "testng-6.10.jar")),
- CONCURRENT_HASH_COMPILED_TESTS_FOLDER,
- concHashFilesAndDependencies.toArray(new Path[0]));
+ public static void compileConcurrentClasses() throws Exception {
+ File concurrentClassesDir = new File(CONCURRENT_COMPILED_TESTS_FOLDER.toString());
+ assert concurrentClassesDir.exists() || concurrentClassesDir.mkdirs();
+ javac(CfVm.JDK11, getStaticTemp())
+ .addClasspathFiles(
+ Collections.singletonList(Paths.get(JDK_TESTS_BUILD_DIR + "testng-6.10.jar")))
+ .addSourceFiles(getAllFilesWithSuffixInDirectory(CONCURRENT_TESTS_FOLDER, JAVA_EXTENSION))
+ .setOutputPath(CONCURRENT_COMPILED_TESTS_FOLDER)
+ .compile();
CONCURRENT_COMPILED_TESTS_FILES =
getAllFilesWithSuffixInDirectory(CONCURRENT_COMPILED_TESTS_FOLDER, CLASS_EXTENSION);
+ assert CONCURRENT_COMPILED_TESTS_FILES.length > 0;
+
+ List<Path> concurrentHashFilesAndDependencies = new ArrayList<>();
+ Collections.addAll(
+ concurrentHashFilesAndDependencies,
+ getAllFilesWithSuffixInDirectory(CONCURRENT_HASH_TESTS_FOLDER, JAVA_EXTENSION));
+ Collections.addAll(concurrentHashFilesAndDependencies, SUPPORT_LIBS);
+ Path[] classesToCompile = concurrentHashFilesAndDependencies.toArray(new Path[0]);
+ File concurrentHashClassesDir = new File(CONCURRENT_HASH_COMPILED_TESTS_FOLDER.toString());
+ assert concurrentHashClassesDir.exists() || concurrentHashClassesDir.mkdirs();
+ javac(CfVm.JDK11, getStaticTemp())
+ .addClasspathFiles(
+ Collections.singletonList(Paths.get(JDK_TESTS_BUILD_DIR + "testng-6.10.jar")))
+ .addSourceFiles(classesToCompile)
+ .setOutputPath(CONCURRENT_HASH_COMPILED_TESTS_FOLDER)
+ .compile();
CONCURRENT_HASH_COMPILED_TESTS_FILES =
getAllFilesWithSuffixInDirectory(CONCURRENT_HASH_COMPILED_TESTS_FOLDER, CLASS_EXTENSION);
- assert CONCURRENT_COMPILED_TESTS_FILES.length > 0;
assert CONCURRENT_HASH_COMPILED_TESTS_FILES.length > 0;
}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/desugaredlibraryjdktests/Jdk11CoreLibTestBase.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/desugaredlibraryjdktests/Jdk11CoreLibTestBase.java
index f9ee2cc..9a0b63b 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/desugaredlibraryjdktests/Jdk11CoreLibTestBase.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/desugaredlibraryjdktests/Jdk11CoreLibTestBase.java
@@ -72,27 +72,46 @@
@BeforeClass
public static void compileJavaBaseExtensions() throws Exception {
- if (!new File(JDK_11_JAVA_BASE_EXTENSION_CLASSES_DIR.toString()).exists()) {
- List<String> options =
- Arrays.asList(
- "--add-reads",
- "java.base=ALL-UNNAMED",
- "--patch-module",
- "java.base=" + JDK_11_JAVA_BASE_EXTENSION_FILES_DIR);
- ToolHelper.runJavac(
- CfVm.JDK11,
- Collections.singletonList(Paths.get(JDK_TESTS_BUILD_DIR + "testng-6.10.jar")),
- JDK_11_JAVA_BASE_EXTENSION_CLASSES_DIR,
- options,
- getJavaBaseExtensionsFiles());
- }
+ File extensionClassesDir = new File(JDK_11_JAVA_BASE_EXTENSION_CLASSES_DIR.toString());
+ assert extensionClassesDir.exists() || extensionClassesDir.mkdirs();
+ List<String> options =
+ Arrays.asList(
+ "--add-reads",
+ "java.base=ALL-UNNAMED",
+ "--patch-module",
+ "java.base=" + JDK_11_JAVA_BASE_EXTENSION_FILES_DIR);
+ javac(CfVm.JDK11, getStaticTemp())
+ .addOptions(options)
+ .addClasspathFiles(
+ Collections.singletonList(Paths.get(JDK_TESTS_BUILD_DIR + "testng-6.10.jar")))
+ .addSourceFiles(getJavaBaseExtensionsFiles())
+ .setOutputPath(JDK_11_JAVA_BASE_EXTENSION_CLASSES_DIR)
+ .compile();
JDK_11_JAVA_BASE_EXTENSION_COMPILED_FILES =
getAllFilesWithSuffixInDirectory(JDK_11_JAVA_BASE_EXTENSION_CLASSES_DIR, CLASS_EXTENSION);
assert JDK_11_JAVA_BASE_EXTENSION_COMPILED_FILES.length > 0;
}
- protected Path buildDesugaredLibraryWithJavaBaseExtension(AndroidApiLevel apiLevel) {
+ private String getTestNGKeepRules() {
+ // Keep data providers and their annotations.
+ return "-keepclasseswithmembers class * {\n"
+ + " @org.testng.annotations.DataProvider <methods>;\n"
+ + "}\n"
+ + "-keepattributes *Annotation*\n"
+ // Do not even attempt to shrink testNG (unrelated to desugared lib shrinking goal).
+ + "-keep class org.testng.** { *; }\n"
+ // There are missing classes in testNG.
+ + "-dontwarn";
+ }
+
+ protected Path buildDesugaredLibraryWithJavaBaseExtension(
+ AndroidApiLevel apiLevel, String keepRules, boolean shrink) {
+ // there are missing classes from testNG.
+ keepRules = getTestNGKeepRules() + keepRules;
return buildDesugaredLibrary(
- apiLevel, "", false, ImmutableList.copyOf(JDK_11_JAVA_BASE_EXTENSION_COMPILED_FILES));
+ apiLevel,
+ keepRules,
+ shrink,
+ ImmutableList.copyOf(JDK_11_JAVA_BASE_EXTENSION_COMPILED_FILES));
}
}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/desugaredlibraryjdktests/Jdk11MathTests.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/desugaredlibraryjdktests/Jdk11MathTests.java
index 9d84630..9f64237 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/desugaredlibraryjdktests/Jdk11MathTests.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/desugaredlibraryjdktests/Jdk11MathTests.java
@@ -12,6 +12,7 @@
import com.android.tools.r8.TestParametersCollection;
import com.android.tools.r8.TestRuntime.CfVm;
import com.android.tools.r8.ToolHelper;
+import java.io.File;
import java.nio.file.Path;
import java.nio.file.Paths;
import org.junit.Assume;
@@ -65,9 +66,19 @@
@BeforeClass
public static void compileMathClasses() throws Exception {
- ToolHelper.runJavac(CfVm.JDK11, null, JDK_11_MATH_TESTS_DIR, JDK_11_MATH_JAVA_FILES);
- ToolHelper.runJavac(
- CfVm.JDK11, null, JDK_11_STRICT_MATH_TESTS_DIR, JDK_11_STRICT_MATH_JAVA_FILES);
+ File mathClassesDir = new File(JDK_11_MATH_TESTS_DIR.toString());
+ assert mathClassesDir.exists() || mathClassesDir.mkdirs();
+ javac(CfVm.JDK11, getStaticTemp())
+ .addSourceFiles(JDK_11_MATH_JAVA_FILES)
+ .setOutputPath(JDK_11_MATH_TESTS_DIR)
+ .compile();
+
+ File strictMathClassesDir = new File(JDK_11_STRICT_MATH_TESTS_DIR.toString());
+ assert strictMathClassesDir.exists() || strictMathClassesDir.mkdirs();
+ javac(CfVm.JDK11, getStaticTemp())
+ .addSourceFiles(JDK_11_STRICT_MATH_JAVA_FILES)
+ .setOutputPath(JDK_11_STRICT_MATH_TESTS_DIR)
+ .compile();
}
@Test
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/desugaredlibraryjdktests/Jdk11ObjectsTests.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/desugaredlibraryjdktests/Jdk11ObjectsTests.java
index 39cfe0c..730ee1b 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/desugaredlibraryjdktests/Jdk11ObjectsTests.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/desugaredlibraryjdktests/Jdk11ObjectsTests.java
@@ -13,6 +13,7 @@
import com.android.tools.r8.TestRuntime.CfVm;
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.ToolHelper.DexVm;
+import java.io.File;
import java.nio.file.Path;
import java.nio.file.Paths;
import org.junit.Assume;
@@ -52,11 +53,12 @@
@BeforeClass
public static void compileObjectsClass() throws Exception {
- ToolHelper.runJavac(
- CfVm.JDK11,
- null,
- JDK_11_OBJECTS_TESTS_DIR,
- JDK_11_OBJECTS_JAVA_DIR.resolve(BASIC_OBJECTS_TEST + JAVA_EXTENSION));
+ File objectsDir = new File(JDK_11_OBJECTS_TESTS_DIR.toString());
+ assert objectsDir.exists() || objectsDir.mkdirs();
+ javac(CfVm.JDK11, getStaticTemp())
+ .addSourceFiles(JDK_11_OBJECTS_JAVA_DIR.resolve(BASIC_OBJECTS_TEST + JAVA_EXTENSION))
+ .setOutputPath(JDK_11_OBJECTS_TESTS_DIR)
+ .compile();
}
@Test
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/desugaredlibraryjdktests/Jdk11StreamTests.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/desugaredlibraryjdktests/Jdk11StreamTests.java
index adc0928..784d320 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/desugaredlibraryjdktests/Jdk11StreamTests.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/desugaredlibraryjdktests/Jdk11StreamTests.java
@@ -13,11 +13,11 @@
import com.android.tools.r8.D8TestCompileResult;
import com.android.tools.r8.D8TestRunResult;
import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestParametersCollection;
import com.android.tools.r8.TestRuntime.CfVm;
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.ToolHelper.DexVm.Version;
import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.BooleanUtils;
import com.android.tools.r8.utils.StringUtils;
import java.io.File;
import java.nio.file.Path;
@@ -33,23 +33,28 @@
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
@RunWith(Parameterized.class)
public class Jdk11StreamTests extends Jdk11CoreLibTestBase {
private final TestParameters parameters;
+ private final boolean shrinkDesugaredLibrary;
- @Parameterized.Parameters(name = "{0}")
- public static TestParametersCollection data() {
+ @Parameters(name = "{1}, shrinkDesugaredLibrary: {0}")
+ public static List<Object[]> data() {
// TODO(134732760): Support Dalvik VMs, currently fails because libjavacrypto is required and
// present only in ART runtimes.
- return getTestParameters()
- .withDexRuntimesStartingFromIncluding(Version.V5_1_1)
- .withAllApiLevels()
- .build();
+ return buildParameters(
+ BooleanUtils.values(),
+ getTestParameters()
+ .withDexRuntimesStartingFromIncluding(Version.V5_1_1)
+ .withAllApiLevels()
+ .build());
}
- public Jdk11StreamTests(TestParameters parameters) {
+ public Jdk11StreamTests(boolean shrinkDesugaredLibrary, TestParameters parameters) {
+ this.shrinkDesugaredLibrary = shrinkDesugaredLibrary;
this.parameters = parameters;
}
@@ -65,80 +70,74 @@
return files;
}
- private static String[] RUNNABLE_TESTS =
- new String[]{
- // Disabled because explicit cast done on a wrapped value.
- //"org/openjdk/tests/java/util/SplittableRandomTest.java",
-
- // Working tests
- "org/openjdk/tests/java/util/MapTest.java",
- "org/openjdk/tests/java/util/FillableStringTest.java",
- "org/openjdk/tests/java/util/stream/ForEachOpTest.java",
- "org/openjdk/tests/java/util/stream/CollectionAndMapModifyStreamTest.java",
- "org/openjdk/tests/java/util/stream/GroupByOpTest.java",
- // Disabled because time to run > 1 min.
- // "org/openjdk/tests/java/util/stream/InfiniteStreamWithLimitOpTest.java",
- "org/openjdk/tests/java/util/stream/PrimitiveAverageOpTest.java",
- "org/openjdk/tests/java/util/stream/TeeOpTest.java",
- "org/openjdk/tests/java/util/stream/MinMaxTest.java",
- "org/openjdk/tests/java/util/stream/ConcatTest.java",
- // Disabled because time to run > 1 min.
- // "org/openjdk/tests/java/util/stream/CountLargeTest.java",
- "org/openjdk/tests/java/util/stream/StreamParSeqTest.java",
- "org/openjdk/tests/java/util/stream/ReduceByOpTest.java",
- "org/openjdk/tests/java/util/stream/ConcatOpTest.java",
- "org/openjdk/tests/java/util/stream/IntReduceTest.java",
- "org/openjdk/tests/java/util/stream/SortedOpTest.java",
- "org/openjdk/tests/java/util/stream/MatchOpTest.java",
- // Disabled because time to run > 1 min.
- // "org/openjdk/tests/java/util/stream/RangeTest.java",
- "org/openjdk/tests/java/util/stream/IntSliceOpTest.java",
- "org/openjdk/tests/java/util/stream/SequentialOpTest.java",
- "org/openjdk/tests/java/util/stream/PrimitiveSumTest.java",
- "org/openjdk/tests/java/util/stream/IterateTest.java",
- "org/openjdk/tests/java/util/stream/ReduceTest.java",
- "org/openjdk/tests/java/util/stream/IntUniqOpTest.java",
-
- // J9 failure
- "org/openjdk/tests/java/util/stream/SpliteratorTest.java",
- // Disabled because time to run > 1 min.
- // "org/openjdk/tests/java/util/stream/CollectorsTest.java",
- "org/openjdk/tests/java/util/stream/WhileOpStatefulTest.java",
- "org/openjdk/tests/java/util/stream/WhileOpTest.java",
-
- // J9 security
- "org/openjdk/tests/java/util/stream/CountTest.java",
- // Disabled because time to run > 1 min.
- // "org/openjdk/tests/java/util/stream/FlatMapOpTest.java",
- "org/openjdk/tests/java/util/stream/StreamCloseTest.java",
- "org/openjdk/tests/java/util/stream/DoublePrimitiveOpsTests.java",
- // Disabled because time to run > 1 min.
- // "org/openjdk/tests/java/util/stream/StreamSpliteratorTest.java",
- "org/openjdk/tests/java/util/stream/CollectAndSummaryStatisticsTest.java",
-
- // Foreach problem
- // Disabled because time to run > 1 min.
- // "org/openjdk/tests/java/util/stream/StreamLinkTest.java",
- "org/openjdk/tests/java/util/stream/FindFirstOpTest.java",
- "org/openjdk/tests/java/util/stream/FindAnyOpTest.java",
- // Disabled because time to run > 1 min.
- // "org/openjdk/tests/java/util/stream/StreamBuilderTest.java",
- // Disabled because time to run > 1 min.
- // "org/openjdk/tests/java/util/stream/SliceOpTest.java",
- "org/openjdk/tests/java/util/stream/DistinctOpTest.java",
- "org/openjdk/tests/java/util/stream/MapOpTest.java",
- // Disabled because time to run > 1 min.
- // "org/openjdk/tests/java/util/stream/ToArrayOpTest.java",
-
- // J9 Random problem
- "org/openjdk/tests/java/util/stream/LongPrimitiveOpsTests.java",
- "org/openjdk/tests/java/util/stream/IntPrimitiveOpsTests.java"
+ private static String[] FAILING_RUNNABLE_TESTS =
+ new String[] {
+ // J9 failure.
+ "org/openjdk/tests/java/util/stream/SpliteratorTest.java",
+ "org/openjdk/tests/java/util/stream/WhileOpStatefulTest.java",
+ "org/openjdk/tests/java/util/stream/IterateTest.java",
+ "org/openjdk/tests/java/util/stream/WhileOpTest.java",
+ // forEach failure
+ "org/openjdk/tests/java/util/stream/FindFirstOpTest.java",
+ "org/openjdk/tests/java/util/stream/MapOpTest.java",
+ // Disabled because explicit cast done on a wrapped value.
+ // "org/openjdk/tests/java/util/SplittableRandomTest.java",
+ // Assertion error
+ "org/openjdk/tests/java/util/stream/StreamCloseTest.java",
+ "org/openjdk/tests/java/util/stream/CollectAndSummaryStatisticsTest.java",
+ "org/openjdk/tests/java/util/stream/CountTest.java",
+ // J9 Random problem
+ "org/openjdk/tests/java/util/stream/LongPrimitiveOpsTests.java",
+ "org/openjdk/tests/java/util/stream/IntPrimitiveOpsTests.java",
+ "org/openjdk/tests/java/util/stream/DistinctOpTest.java",
+ "org/openjdk/tests/java/util/stream/DoublePrimitiveOpsTests.java"
};
- private static Map<String, String> getRunnableTests() {
+ // Disabled because time to run > 1 min for each test.
+ // Can be used for experimentation/testing purposes.
+ private static String[] LONG_RUNNING_TESTS =
+ new String[] {
+ "org/openjdk/tests/java/util/stream/InfiniteStreamWithLimitOpTest.java",
+ "org/openjdk/tests/java/util/stream/CountLargeTest.java",
+ "org/openjdk/tests/java/util/stream/RangeTest.java",
+ "org/openjdk/tests/java/util/stream/CollectorsTest.java",
+ "org/openjdk/tests/java/util/stream/FlatMapOpTest.java",
+ "org/openjdk/tests/java/util/stream/StreamSpliteratorTest.java",
+ "org/openjdk/tests/java/util/stream/StreamLinkTest.java",
+ "org/openjdk/tests/java/util/stream/StreamBuilderTest.java",
+ "org/openjdk/tests/java/util/stream/SliceOpTest.java",
+ "org/openjdk/tests/java/util/stream/ToArrayOpTest.java"
+ };
+
+ private static String[] SUCCESSFUL_RUNNABLE_TESTS =
+ new String[] {
+ "org/openjdk/tests/java/util/MapTest.java",
+ "org/openjdk/tests/java/util/FillableStringTest.java",
+ "org/openjdk/tests/java/util/stream/ForEachOpTest.java",
+ "org/openjdk/tests/java/util/stream/CollectionAndMapModifyStreamTest.java",
+ "org/openjdk/tests/java/util/stream/GroupByOpTest.java",
+ "org/openjdk/tests/java/util/stream/PrimitiveAverageOpTest.java",
+ "org/openjdk/tests/java/util/stream/TeeOpTest.java",
+ "org/openjdk/tests/java/util/stream/MinMaxTest.java",
+ "org/openjdk/tests/java/util/stream/ConcatTest.java",
+ "org/openjdk/tests/java/util/stream/StreamParSeqTest.java",
+ "org/openjdk/tests/java/util/stream/ReduceByOpTest.java",
+ "org/openjdk/tests/java/util/stream/ConcatOpTest.java",
+ "org/openjdk/tests/java/util/stream/IntReduceTest.java",
+ "org/openjdk/tests/java/util/stream/SortedOpTest.java",
+ "org/openjdk/tests/java/util/stream/MatchOpTest.java",
+ "org/openjdk/tests/java/util/stream/IntSliceOpTest.java",
+ "org/openjdk/tests/java/util/stream/SequentialOpTest.java",
+ "org/openjdk/tests/java/util/stream/PrimitiveSumTest.java",
+ "org/openjdk/tests/java/util/stream/ReduceTest.java",
+ "org/openjdk/tests/java/util/stream/IntUniqOpTest.java",
+ "org/openjdk/tests/java/util/stream/FindAnyOpTest.java"
+ };
+
+ private static Map<String, String> getRunnableTests(String[] tests) {
IdentityHashMap<String, String> pathToName = new IdentityHashMap<>();
int javaExtSize = JAVA_EXTENSION.length();
- for (String runnableTest : RUNNABLE_TESTS) {
+ for (String runnableTest : tests) {
String nameWithoutJavaExt = runnableTest.substring(0, runnableTest.length() - javaExtSize);
pathToName.put(
JDK_11_STREAM_TEST_CLASSES_DIR + "/" + nameWithoutJavaExt + CLASS_EXTENSION,
@@ -165,91 +164,121 @@
@BeforeClass
public static void compileJdk11StreamTests() throws Exception {
- if (!new File(JDK_11_STREAM_TEST_CLASSES_DIR.toString()).exists()) {
- List<String> options =
- Arrays.asList(
- "--add-reads",
- "java.base=ALL-UNNAMED",
- "--patch-module",
- "java.base=" + JDK_11_JAVA_BASE_EXTENSION_CLASSES_DIR);
- ToolHelper.runJavac(
- CfVm.JDK11,
- Collections.singletonList(Paths.get(JDK_TESTS_BUILD_DIR + "testng-6.10.jar")),
- JDK_11_STREAM_TEST_CLASSES_DIR,
- options,
- getJdk11StreamTestFiles());
- }
+ File streamClassesDir = new File(JDK_11_STREAM_TEST_CLASSES_DIR.toString());
+ assert streamClassesDir.exists() || streamClassesDir.mkdirs();
+ List<String> options =
+ Arrays.asList(
+ "--add-reads",
+ "java.base=ALL-UNNAMED",
+ "--patch-module",
+ "java.base=" + JDK_11_JAVA_BASE_EXTENSION_CLASSES_DIR);
+ javac(CfVm.JDK11, getStaticTemp())
+ .addOptions(options)
+ .addClasspathFiles(
+ Collections.singletonList(Paths.get(JDK_TESTS_BUILD_DIR + "testng-6.10.jar")))
+ .addSourceFiles(getJdk11StreamTestFiles())
+ .setOutputPath(JDK_11_STREAM_TEST_CLASSES_DIR)
+ .compile();
JDK_11_STREAM_TEST_COMPILED_FILES =
getAllFilesWithSuffixInDirectory(JDK_11_STREAM_TEST_CLASSES_DIR, CLASS_EXTENSION);
assert JDK_11_STREAM_TEST_COMPILED_FILES.length > 0;
}
-
@Test
public void testStream() throws Exception {
+ Assume.assumeFalse(
+ "getAllFilesWithSuffixInDirectory() seems to find different files on Windows",
+ ToolHelper.isWindows());
Assume.assumeTrue(
"Requires Java base extensions, should add it when not desugaring",
parameters.getApiLevel().getLevel() < AndroidApiLevel.N.getLevel());
- // TODO(b/137876068): It seems to fail on windows because the method.
- // getAllFilesWithSuffixInDirectory() finds different files on Windows (To be confirmed), so
- // compilation is then different and raises an error.
- Assume.assumeFalse(ToolHelper.isWindows());
- Map<String, String> runnableTests = getRunnableTests();
- String verbosity = "2";
+
+ D8TestCompileResult compileResult = compileStreamTestsToDex();
+ runSuccessfulTests(compileResult);
+ runFailingTests(compileResult);
+ }
+
+ private D8TestCompileResult compileStreamTestsToDex() throws Exception {
+ KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
List<Path> filesToCompile =
Arrays.stream(JDK_11_STREAM_TEST_COMPILED_FILES)
.filter(file -> !file.toString().contains("lang/invoke"))
.collect(Collectors.toList());
- D8TestCompileResult compileResult =
- testForD8()
- .addProgramFiles(filesToCompile)
- .addProgramFiles(getPathsFiles())
- .addProgramFiles(getSafeVarArgsFile())
- .addProgramFiles(testNGSupportProgramFiles())
- .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P))
- .setMinApi(parameters.getApiLevel())
- .enableCoreLibraryDesugaring(parameters.getApiLevel())
- .compile()
- .addDesugaredCoreLibraryRunClassPath(
- this::buildDesugaredLibraryWithJavaBaseExtension, parameters.getApiLevel())
- .withArtFrameworks()
- .withArt6Plus64BitsLib();
- int numSuccesses = 0;
- int numHardFailures = 0;
+ return testForD8()
+ .addProgramFiles(filesToCompile)
+ .addProgramFiles(getPathsFiles())
+ .addProgramFiles(getSafeVarArgsFile())
+ .addProgramFiles(testNGSupportProgramFiles())
+ .addOptionsModification(opt -> opt.testing.trackDesugaredAPIConversions = true)
+ .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P))
+ .setMinApi(parameters.getApiLevel())
+ .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
+ .compile()
+ .addDesugaredCoreLibraryRunClassPath(
+ this::buildDesugaredLibraryWithJavaBaseExtension,
+ parameters.getApiLevel(),
+ keepRuleConsumer.get(),
+ shrinkDesugaredLibrary)
+ .withArtFrameworks()
+ .withArt6Plus64BitsLib();
+ }
+
+ private void runSuccessfulTests(D8TestCompileResult compileResult) throws Exception {
+ String verbosity = "2"; // Increase verbosity for debugging.
+ Map<String, String> runnableTests = getRunnableTests(SUCCESSFUL_RUNNABLE_TESTS);
for (String path : runnableTests.keySet()) {
assert runnableTests.get(path) != null;
D8TestRunResult result =
compileResult.run(
parameters.getRuntime(), "TestNGMainRunner", verbosity, runnableTests.get(path));
- if (result.getStdOut().endsWith(StringUtils.lines("SUCCESS"))) {
+ assertTrue(
+ result
+ .getStdOut()
+ .endsWith(
+ StringUtils.lines("Tests result in " + runnableTests.get(path) + ": SUCCESS")));
+ }
+ }
+
+ private void runFailingTests(D8TestCompileResult compileResult) throws Exception {
+ // For failing runnable tests, we just ensure that they do not fail due to desugaring, but
+ // due to an expected failure (missing API, etc.).
+ String verbosity = "2"; // Increase verbosity for debugging.
+ Map<String, String> runnableTests = getRunnableTests(FAILING_RUNNABLE_TESTS);
+ for (String path : runnableTests.keySet()) {
+ assert runnableTests.get(path) != null;
+ D8TestRunResult result =
+ compileResult.run(
+ parameters.getRuntime(), "TestNGMainRunner", verbosity, runnableTests.get(path));
+ if (result
+ .getStdOut()
+ .endsWith(
+ StringUtils.lines("Tests result in " + runnableTests.get(path) + ": SUCCESS"))) {
+ // The test succeeds, this can happen on tests succeeding only on high API levels.
assertTrue(
- result
- .getStdOut()
- .endsWith(
- StringUtils.lines("Tests result in " + runnableTests.get(path) + ": SUCCESS")));
- numSuccesses++;
+ parameters.getRuntime().asDex().getMinApiLevel().getLevel()
+ >= AndroidApiLevel.N.getLevel());
+ } else if (result.getStdOut().contains("java.lang.NoSuchMethodError")
+ && Arrays.stream(missingDesugaredMethods())
+ .anyMatch(method -> result.getStdOut().contains(method))) {
+ // TODO(b/134732760): support Java 9 APIs.
+ } else if (result
+ .getStdOut()
+ .contains("java.lang.NoSuchMethodError: No interface method forEach")) {
+ // TODO(b/134732760): fix tests no to use Iterable#forEach
+ } else if (result.getStdOut().contains("in class Ljava/util/Random")
+ && result.getStdOut().contains("java.lang.NoSuchMethodError")) {
+ // TODO(b/134732760): Random Java 9 Apis, support or do not use them.
+ } else if (result.getStdOut().contains("java.lang.AssertionError")) {
+ // TODO(b/134732760): Investigate and fix these issues.
+ } else if (result.getStdErr().contains("$r8$wrapper$")) {
+ // Use of high library API on low API device, cannot do anything about this.
+ assertTrue(
+ parameters.getRuntime().asDex().getMinApiLevel().getLevel()
+ < AndroidApiLevel.N.getLevel());
} else {
- if (result.getStdOut().contains("java.lang.NoSuchMethodError")
- && Arrays.stream(missingDesugaredMethods())
- .anyMatch(method -> result.getStdOut().contains(method))) {
- // TODO(b/134732760): support Java 9 APIs.
- } else if (result
- .getStdOut()
- .contains("java.lang.NoSuchMethodError: No interface method forEach")) {
- // TODO(b/134732760): fix tests no to use Iterable#forEach
- } else if (result.getStdOut().contains("in class Ljava/util/Random")
- && result.getStdOut().contains("java.lang.NoSuchMethodError")) {
- // TODO(b/134732760): Random Java 9 Apis, support or do not use them.
- } else if (result.getStdOut().contains("java.lang.AssertionError")) {
- // TODO(b/134732760): Investigate and fix these issues.
- numHardFailures++;
- } else {
- String errorMessage = "STDOUT:\n" + result.getStdOut() + "STDERR:\n" + result.getStdErr();
- fail(errorMessage);
- }
+ String errorMessage = "STDOUT:\n" + result.getStdOut() + "STDERR:\n" + result.getStdErr();
+ fail(errorMessage);
}
}
- assertTrue(numSuccesses > 20);
- assertTrue(numHardFailures <= 6);
}
}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/r8ondex/HelloWorldCompiledOnArtTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/r8ondex/HelloWorldCompiledOnArtTest.java
index bc2d017..86a4edb 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/r8ondex/HelloWorldCompiledOnArtTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/r8ondex/HelloWorldCompiledOnArtTest.java
@@ -26,7 +26,6 @@
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
-import java.util.Collections;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -44,13 +43,11 @@
public static void compilePathBackport() throws Exception {
assumeTrue("JDK8 is not checked-in on Windows", !ToolHelper.isWindows());
pathMock = getStaticTemp().newFolder("PathMock").toPath();
- ProcessResult processResult =
- ToolHelper.runJavac(
- CfVm.JDK8,
- Collections.emptyList(),
- pathMock,
- getAllFilesWithSuffixInDirectory(Paths.get("src/test/r8OnArtBackport"), "java"));
- assertEquals(0, processResult.exitCode);
+ javac(CfVm.JDK8, getStaticTemp())
+ .setOutputPath(pathMock)
+ .addSourceFiles(
+ getAllFilesWithSuffixInDirectory(Paths.get("src/test/r8OnArtBackport"), "java"))
+ .compile();
}
public static Path[] getPathBackport() throws Exception {
diff --git a/src/test/java/com/android/tools/r8/heap/HeapDumpTest.java b/src/test/java/com/android/tools/r8/heap/HeapDumpTest.java
new file mode 100644
index 0000000..19a3c15
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/heap/HeapDumpTest.java
@@ -0,0 +1,34 @@
+// Copyright (c) 2019, 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.heap;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.utils.HeapUtils;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import org.junit.Test;
+
+public class HeapDumpTest extends TestBase {
+
+ @Test
+ public void testHeapDump() throws Exception {
+ Path heapDumpDir = temp.newFolder().toPath();
+ Path heapDump = heapDumpDir.resolve("test.hprof");
+ HeapUtils.dumpHeap(heapDump, true);
+ assertTrue(heapDump.toFile().exists());
+ String header = "JAVA PROFILE 1.0.2";
+ assertTrue(heapDump.toFile().length() > header.length());
+ try (InputStream is = Files.newInputStream(heapDump)) {
+ byte[] buffer = new byte[header.length()];
+ int bytes = is.read(buffer);
+ assertEquals(buffer.length, bytes);
+ assertEquals(header, new String(buffer));
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/internal/proto/Proto2ShrinkingTest.java b/src/test/java/com/android/tools/r8/internal/proto/Proto2ShrinkingTest.java
index 506e33a..2071b82 100644
--- a/src/test/java/com/android/tools/r8/internal/proto/Proto2ShrinkingTest.java
+++ b/src/test/java/com/android/tools/r8/internal/proto/Proto2ShrinkingTest.java
@@ -78,6 +78,7 @@
.addProgramFiles(PROGRAM_FILES)
.addKeepMainRule("proto2.TestClass")
.addKeepRuleFiles(PROTOBUF_LITE_PROGUARD_RULES)
+ .addKeepRules(allGeneratedMessageLiteSubtypesAreInstantiatedRule())
.addKeepRules(alwaysInlineNewSingularGeneratedExtensionRule())
.addOptionsModification(
options -> {
@@ -85,18 +86,6 @@
options.protoShrinking().enableGeneratedMessageLiteShrinking = true;
options.protoShrinking().enableGeneratedExtensionRegistryShrinking = true;
options.enableStringSwitchConversion = true;
-
- // TODO(b/144003629): If devirtualization is enabled, then we insert a cast to
- // PartiallyUsed$Enum$EnumVerifier in MessageSchema.parseOneofField. This causes
- // us to retain PartiallyUsed$Enum$EnumVerifier.<clinit>(), which creates an
- // instance of PartiallyUsed$Enum$EnumVerifier, which causes the virtual
- // method PartiallyUsed$Enum$EnumVerifier.isInRange() to become live, which in
- // turn causes the type PartiallyUsed$Enum to become live.
- //
- // Note: This is *not* a general problem, since it only manifests if there is only
- // a single proto enum in the entire program. In other tests, this issue does not
- // appear.
- options.enableDevirtualization = false;
})
.allowAccessModification(allowAccessModification)
.allowUnusedProguardConfigurationRules()
diff --git a/src/test/java/com/android/tools/r8/internal/proto/Proto3ShrinkingTest.java b/src/test/java/com/android/tools/r8/internal/proto/Proto3ShrinkingTest.java
index 2a4b79e..7a67427 100644
--- a/src/test/java/com/android/tools/r8/internal/proto/Proto3ShrinkingTest.java
+++ b/src/test/java/com/android/tools/r8/internal/proto/Proto3ShrinkingTest.java
@@ -38,7 +38,7 @@
return buildParameters(
BooleanUtils.values(),
BooleanUtils.values(),
- getTestParameters().withAllRuntimes().build());
+ getTestParameters().withAllRuntimesAndApiLevels().build());
}
public Proto3ShrinkingTest(
@@ -64,7 +64,7 @@
.allowAccessModification(allowAccessModification)
.allowUnusedProguardConfigurationRules()
.minification(enableMinification)
- .setMinApi(parameters.getRuntime())
+ .setMinApi(parameters.getApiLevel())
.compile()
.inspect(
outputInspector -> {
diff --git a/src/test/java/com/android/tools/r8/internal/proto/ProtoShrinkingTestBase.java b/src/test/java/com/android/tools/r8/internal/proto/ProtoShrinkingTestBase.java
index 9a88fe1..19b3857 100644
--- a/src/test/java/com/android/tools/r8/internal/proto/ProtoShrinkingTestBase.java
+++ b/src/test/java/com/android/tools/r8/internal/proto/ProtoShrinkingTestBase.java
@@ -69,6 +69,14 @@
}
}
+ static String allGeneratedMessageLiteSubtypesAreInstantiatedRule() {
+ return StringUtils.lines(
+ "-if class * extends com.google.protobuf.GeneratedMessageLite",
+ "-keep,allowobfuscation class <1> {",
+ " <init>(...);",
+ "}");
+ }
+
static String alwaysInlineNewSingularGeneratedExtensionRule() {
return StringUtils.lines(
"-alwaysinline class com.google.protobuf.GeneratedMessageLite {",
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/R8InliningTest.java b/src/test/java/com/android/tools/r8/ir/optimize/R8InliningTest.java
index 5e0e77f..a9385a6 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/R8InliningTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/R8InliningTest.java
@@ -123,7 +123,7 @@
o.inliningInstructionLimit = 6;
// Tests depend on nullability of receiver and argument in general. Learning very accurate
// nullability from actual usage in tests bothers what we want to test.
- o.enableCallSiteOptimizationInfoPropagation = false;
+ o.enablePropagationOfDynamicTypesAtCallSites = false;
});
}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeDirectNegativeTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeDirectNegativeTest.java
new file mode 100644
index 0000000..a0bafae
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeDirectNegativeTest.java
@@ -0,0 +1,91 @@
+// Copyright (c) 2019, 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.optimize.callsites.constants;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.ir.optimize.info.CallSiteOptimizationInfo;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class InvokeDirectNegativeTest extends TestBase {
+
+ private static final Class<?> MAIN = Main.class;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ private final TestParameters parameters;
+
+ public InvokeDirectNegativeTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(InvokeDirectNegativeTest.class)
+ .addKeepMainRule(MAIN)
+ .enableClassInliningAnnotations()
+ .enableInliningAnnotations()
+ .addOptionsModification(o -> {
+ o.testing.callSiteOptimizationInfoInspector = this::callSiteOptimizationInfoInspect;
+ })
+ .setMinApi(parameters.getApiLevel())
+ .run(parameters.getRuntime(), MAIN)
+ .assertSuccessWithOutputLines("null", "non-null")
+ .inspect(this::inspect);
+ }
+
+ private void callSiteOptimizationInfoInspect(DexEncodedMethod encodedMethod) {
+ assert encodedMethod.method.name.toString().equals("test")
+ : "Unexpected revisit: " + encodedMethod.toSourceString();
+ CallSiteOptimizationInfo callSiteOptimizationInfo = encodedMethod.getCallSiteOptimizationInfo();
+ assert callSiteOptimizationInfo.getDynamicUpperBoundType(1).isDefinitelyNotNull();
+ assert callSiteOptimizationInfo.getAbstractArgumentValue(1).isUnknown();
+ }
+
+ private void inspect(CodeInspector inspector) {
+ ClassSubject main = inspector.clazz(MAIN);
+ assertThat(main, isPresent());
+ MethodSubject test = main.uniqueMethodWithName("test");
+ assertThat(test, isPresent());
+ // Should not optimize branches since the value of `arg` is unsure.
+ assertTrue(test.streamInstructions().anyMatch(InstructionSubject::isIf));
+ }
+
+ @NeverClassInline
+ static class Main {
+ public static void main(String... args) {
+ Main obj = new Main();
+ obj.test("null"); // calls test with "null".
+ obj.test("nul"); // calls test with "nul".
+ }
+
+ @NeverInline
+ private void test(String arg) {
+ if (arg.contains("null")) {
+ System.out.println("null");
+ } else {
+ System.out.println("non-null");
+ }
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeDirectPositiveTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeDirectPositiveTest.java
new file mode 100644
index 0000000..e4200b8
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeDirectPositiveTest.java
@@ -0,0 +1,95 @@
+// Copyright (c) 2019, 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.optimize.callsites.constants;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.ir.analysis.value.AbstractValue;
+import com.android.tools.r8.ir.optimize.info.CallSiteOptimizationInfo;
+import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class InvokeDirectPositiveTest extends TestBase {
+
+ private static final Class<?> MAIN = Main.class;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ private final TestParameters parameters;
+
+ public InvokeDirectPositiveTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(InvokeDirectPositiveTest.class)
+ .addKeepMainRule(MAIN)
+ .enableClassInliningAnnotations()
+ .enableInliningAnnotations()
+ .addOptionsModification(o -> {
+ o.testing.callSiteOptimizationInfoInspector = this::callSiteOptimizationInfoInspect;
+ })
+ .setMinApi(parameters.getApiLevel())
+ .addOptionsModification(InternalOptions::enablePropagationOfConstantsAtCallSites)
+ .run(parameters.getRuntime(), MAIN)
+ .assertSuccessWithOutputLines("non-null")
+ .inspect(this::inspect);
+ }
+
+ private void callSiteOptimizationInfoInspect(DexEncodedMethod encodedMethod) {
+ assert encodedMethod.method.name.toString().equals("test")
+ : "Unexpected revisit: " + encodedMethod.toSourceString();
+ CallSiteOptimizationInfo callSiteOptimizationInfo = encodedMethod.getCallSiteOptimizationInfo();
+ assert callSiteOptimizationInfo.getDynamicUpperBoundType(1).isDefinitelyNotNull();
+ AbstractValue abstractValue = callSiteOptimizationInfo.getAbstractArgumentValue(1);
+ assert abstractValue.isSingleStringValue()
+ && abstractValue.asSingleStringValue().getDexString().toString().equals("nul");
+ }
+
+ private void inspect(CodeInspector inspector) {
+ ClassSubject main = inspector.clazz(MAIN);
+ assertThat(main, isPresent());
+ MethodSubject test = main.uniqueMethodWithName("test");
+ assertThat(test, isPresent());
+ // Can optimize branches since `arg` is definitely "nul", i.e., not containing "null".
+ assertTrue(test.streamInstructions().noneMatch(InstructionSubject::isIf));
+ }
+
+ @NeverClassInline
+ static class Main {
+ public static void main(String... args) {
+ Main obj = new Main();
+ obj.test("nul"); // calls test with "nul".
+ }
+
+ @NeverInline
+ private void test(String arg) {
+ if (arg.contains("null")) {
+ System.out.println("null");
+ } else {
+ System.out.println("non-null");
+ }
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeInterfaceNegativeTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeInterfaceNegativeTest.java
new file mode 100644
index 0000000..7f9aedb
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeInterfaceNegativeTest.java
@@ -0,0 +1,105 @@
+// Copyright (c) 2019, 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.optimize.callsites.constants;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.ir.optimize.info.CallSiteOptimizationInfo;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class InvokeInterfaceNegativeTest extends TestBase {
+
+ private static final Class<?> MAIN = Main.class;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ private final TestParameters parameters;
+
+ public InvokeInterfaceNegativeTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(InvokeInterfaceNegativeTest.class)
+ .addKeepMainRule(MAIN)
+ .enableMergeAnnotations()
+ .enableClassInliningAnnotations()
+ .enableInliningAnnotations()
+ .addOptionsModification(o -> {
+ // To prevent invoke-interface from being rewritten to invoke-virtual w/ a single target.
+ o.enableDevirtualization = false;
+ o.testing.callSiteOptimizationInfoInspector = this::callSiteOptimizationInfoInspect;
+ })
+ .setMinApi(parameters.getApiLevel())
+ .run(parameters.getRuntime(), MAIN)
+ .assertSuccessWithOutputLines("null", "non-null")
+ .inspect(this::inspect);
+ }
+
+ private void callSiteOptimizationInfoInspect(DexEncodedMethod encodedMethod) {
+ assert encodedMethod.method.name.toString().equals("m")
+ : "Unexpected revisit: " + encodedMethod.toSourceString();
+ CallSiteOptimizationInfo callSiteOptimizationInfo = encodedMethod.getCallSiteOptimizationInfo();
+ assert callSiteOptimizationInfo.getDynamicUpperBoundType(1).isDefinitelyNotNull();
+ assert callSiteOptimizationInfo.getAbstractArgumentValue(1).isUnknown();
+ }
+
+ private void inspect(CodeInspector inspector) {
+ ClassSubject i = inspector.clazz(I.class);
+ assertThat(i, isPresent());
+ ClassSubject a = inspector.clazz(A.class);
+ assertThat(a, isPresent());
+ MethodSubject a_m = a.uniqueMethodWithName("m");
+ assertThat(a_m, isPresent());
+ // Should not optimize branches since the value of `arg` is unsure.
+ assertTrue(a_m.streamInstructions().anyMatch(InstructionSubject::isIf));
+ }
+
+ @NeverMerge
+ interface I {
+ void m(String arg);
+ }
+
+ @NeverClassInline
+ static class A implements I {
+ @NeverInline
+ @Override
+ public void m(String arg) {
+ if (arg.contains("null")) {
+ System.out.println("null");
+ } else {
+ System.out.println("non-null");
+ }
+ }
+ }
+
+ static class Main {
+ public static void main(String... args) {
+ I i = new A();
+ i.m("null"); // calls A.m() with "null".
+ i.m("nul"); // calls A.m() with "nul".
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeInterfacePositiveTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeInterfacePositiveTest.java
new file mode 100644
index 0000000..76d8e03
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeInterfacePositiveTest.java
@@ -0,0 +1,125 @@
+// Copyright (c) 2019, 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.optimize.callsites.constants;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.ir.analysis.value.AbstractValue;
+import com.android.tools.r8.ir.optimize.info.CallSiteOptimizationInfo;
+import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class InvokeInterfacePositiveTest extends TestBase {
+
+ private static final Class<?> MAIN = Main.class;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ private final TestParameters parameters;
+
+ public InvokeInterfacePositiveTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(InvokeInterfacePositiveTest.class)
+ .addKeepMainRule(MAIN)
+ .enableClassInliningAnnotations()
+ .enableInliningAnnotations()
+ .addOptionsModification(InternalOptions::enablePropagationOfConstantsAtCallSites)
+ .addOptionsModification(o -> {
+ // To prevent invoke-interface from being rewritten to invoke-virtual w/ a single target.
+ o.enableDevirtualization = false;
+ o.testing.callSiteOptimizationInfoInspector = this::callSiteOptimizationInfoInspect;
+ })
+ .setMinApi(parameters.getApiLevel())
+ .run(parameters.getRuntime(), MAIN)
+ .assertSuccessWithOutputLines("non-null")
+ .inspect(this::inspect);
+ }
+
+ private void callSiteOptimizationInfoInspect(DexEncodedMethod encodedMethod) {
+ assert encodedMethod.method.name.toString().equals("m")
+ : "Unexpected revisit: " + encodedMethod.toSourceString();
+ CallSiteOptimizationInfo callSiteOptimizationInfo = encodedMethod.getCallSiteOptimizationInfo();
+ assert callSiteOptimizationInfo.getDynamicUpperBoundType(1).isDefinitelyNotNull();
+ AbstractValue abstractValue = callSiteOptimizationInfo.getAbstractArgumentValue(1);
+ assert abstractValue.isSingleStringValue()
+ && abstractValue.asSingleStringValue().getDexString().toString().equals("nul");
+ }
+
+ private void inspect(CodeInspector inspector) {
+ ClassSubject i = inspector.clazz(I.class);
+ assertThat(i, isPresent());
+ ClassSubject a = inspector.clazz(A.class);
+ assertThat(a, isPresent());
+ MethodSubject a_m = a.uniqueMethodWithName("m");
+ assertThat(a_m, isPresent());
+ // Can optimize branches since `arg` is definitely "nul", i.e., not containing "null".
+ assertTrue(a_m.streamInstructions().noneMatch(InstructionSubject::isIf));
+ ClassSubject b = inspector.clazz(B.class);
+ assertThat(b, isPresent());
+ MethodSubject b_m = b.uniqueMethodWithName("m");
+ assertThat(b_m, isPresent());
+ // Can optimize branches since `arg` is definitely "nul", i.e., not containing "null".
+ assertTrue(b_m.streamInstructions().noneMatch(InstructionSubject::isIf));
+ }
+
+ interface I {
+ void m(String arg);
+ }
+
+ @NeverClassInline
+ static class A implements I {
+ @NeverInline
+ @Override
+ public void m(String arg) {
+ if (arg.contains("null")) {
+ System.out.println("null");
+ } else {
+ System.out.println("non-null");
+ }
+ }
+ }
+
+ @NeverClassInline
+ static class B implements I {
+ @NeverInline
+ @Override
+ public void m(String arg) {
+ if (arg.contains("null")) {
+ System.out.println("null");
+ } else {
+ System.out.println("non-null");
+ }
+ }
+ }
+
+ static class Main {
+ public static void main(String... args) {
+ I i = System.currentTimeMillis() > 0 ? new A() : new B();
+ i.m("nul"); // calls A.m() with "nul".
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeStaticNegativeTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeStaticNegativeTest.java
new file mode 100644
index 0000000..cacb6a3
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeStaticNegativeTest.java
@@ -0,0 +1,87 @@
+// Copyright (c) 2019, 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.optimize.callsites.constants;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.ir.optimize.info.CallSiteOptimizationInfo;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class InvokeStaticNegativeTest extends TestBase {
+
+ private static final Class<?> MAIN = Main.class;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ private final TestParameters parameters;
+
+ public InvokeStaticNegativeTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(InvokeStaticNegativeTest.class)
+ .addKeepMainRule(MAIN)
+ .enableInliningAnnotations()
+ .addOptionsModification(o -> {
+ o.testing.callSiteOptimizationInfoInspector = this::callSiteOptimizationInfoInspect;
+ })
+ .setMinApi(parameters.getApiLevel())
+ .run(parameters.getRuntime(), MAIN)
+ .assertSuccessWithOutputLines("null", "non-null")
+ .inspect(this::inspect);
+ }
+
+ private void callSiteOptimizationInfoInspect(DexEncodedMethod encodedMethod) {
+ assert encodedMethod.method.name.toString().equals("test")
+ : "Unexpected revisit: " + encodedMethod.toSourceString();
+ CallSiteOptimizationInfo callSiteOptimizationInfo = encodedMethod.getCallSiteOptimizationInfo();
+ assert callSiteOptimizationInfo.getDynamicUpperBoundType(0).isDefinitelyNotNull();
+ assert callSiteOptimizationInfo.getAbstractArgumentValue(0).isUnknown();
+ }
+
+ private void inspect(CodeInspector inspector) {
+ ClassSubject main = inspector.clazz(MAIN);
+ assertThat(main, isPresent());
+ MethodSubject test = main.uniqueMethodWithName("test");
+ assertThat(test, isPresent());
+ // Should not optimize branches since the value of `arg` is unsure.
+ assertTrue(test.streamInstructions().anyMatch(InstructionSubject::isIf));
+ }
+
+ static class Main {
+ public static void main(String... args) {
+ test("null"); // calls test with "null".
+ test("nul"); // calls test with "nul".
+ }
+
+ @NeverInline
+ static void test(String arg) {
+ if (arg.contains("null")) {
+ System.out.println("null");
+ } else {
+ System.out.println("non-null");
+ }
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeStaticPositiveTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeStaticPositiveTest.java
new file mode 100644
index 0000000..1d92c6e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeStaticPositiveTest.java
@@ -0,0 +1,91 @@
+// Copyright (c) 2019, 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.optimize.callsites.constants;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.ir.analysis.value.AbstractValue;
+import com.android.tools.r8.ir.optimize.info.CallSiteOptimizationInfo;
+import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class InvokeStaticPositiveTest extends TestBase {
+
+ private static final Class<?> MAIN = Main.class;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ private final TestParameters parameters;
+
+ public InvokeStaticPositiveTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(InvokeStaticPositiveTest.class)
+ .addKeepMainRule(MAIN)
+ .enableInliningAnnotations()
+ .addOptionsModification(o -> {
+ o.testing.callSiteOptimizationInfoInspector = this::callSiteOptimizationInfoInspect;
+ })
+ .setMinApi(parameters.getApiLevel())
+ .addOptionsModification(InternalOptions::enablePropagationOfConstantsAtCallSites)
+ .run(parameters.getRuntime(), MAIN)
+ .assertSuccessWithOutputLines("non-null")
+ .inspect(this::inspect);
+ }
+
+ private void callSiteOptimizationInfoInspect(DexEncodedMethod encodedMethod) {
+ assert encodedMethod.method.name.toString().equals("test")
+ : "Unexpected revisit: " + encodedMethod.toSourceString();
+ CallSiteOptimizationInfo callSiteOptimizationInfo = encodedMethod.getCallSiteOptimizationInfo();
+ assert callSiteOptimizationInfo.getDynamicUpperBoundType(0).isDefinitelyNotNull();
+ AbstractValue abstractValue = callSiteOptimizationInfo.getAbstractArgumentValue(0);
+ assert abstractValue.isSingleStringValue()
+ && abstractValue.asSingleStringValue().getDexString().toString().equals("nul");
+ }
+
+ private void inspect(CodeInspector inspector) {
+ ClassSubject main = inspector.clazz(MAIN);
+ assertThat(main, isPresent());
+ MethodSubject test = main.uniqueMethodWithName("test");
+ assertThat(test, isPresent());
+ // Can optimize branches since `arg` is definitely "nul", i.e., not containing "null".
+ assertTrue(test.streamInstructions().noneMatch(InstructionSubject::isIf));
+ }
+
+ static class Main {
+ public static void main(String... args) {
+ test("nul"); // calls test with "nul".
+ }
+
+ @NeverInline
+ static void test(String arg) {
+ if (arg.contains("null")) {
+ System.out.println("null");
+ } else {
+ System.out.println("non-null");
+ }
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeVirtualNegativeTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeVirtualNegativeTest.java
new file mode 100644
index 0000000..b772bff
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeVirtualNegativeTest.java
@@ -0,0 +1,146 @@
+// Copyright (c) 2019, 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.optimize.callsites.constants;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.ir.optimize.info.CallSiteOptimizationInfo;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class InvokeVirtualNegativeTest extends TestBase {
+
+ private static final Class<?> MAIN = Main.class;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ private final TestParameters parameters;
+
+ public InvokeVirtualNegativeTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(InvokeVirtualNegativeTest.class)
+ .addKeepMainRule(MAIN)
+ .enableMergeAnnotations()
+ .enableClassInliningAnnotations()
+ .enableInliningAnnotations()
+ .addOptionsModification(o -> {
+ o.testing.callSiteOptimizationInfoInspector = this::callSiteOptimizationInfoInspect;
+ })
+ .setMinApi(parameters.getApiLevel())
+ .run(parameters.getRuntime(), MAIN)
+ .assertSuccessWithOutputLines("null", "non-null", "null", "non-null")
+ .inspect(this::inspect);
+ }
+
+ private void callSiteOptimizationInfoInspect(DexEncodedMethod encodedMethod) {
+ String methodName = encodedMethod.method.name.toString();
+ assert methodName.equals("m") || methodName.equals("test")
+ : "Unexpected revisit: " + encodedMethod.toSourceString();
+ CallSiteOptimizationInfo callSiteOptimizationInfo = encodedMethod.getCallSiteOptimizationInfo();
+ if (methodName.equals("m")) {
+ assert callSiteOptimizationInfo.getDynamicUpperBoundType(1).isDefinitelyNotNull();
+ assert callSiteOptimizationInfo.getAbstractArgumentValue(1).isUnknown();
+ } else {
+ assert methodName.equals("test");
+ assert callSiteOptimizationInfo.getDynamicUpperBoundType(0).isDefinitelyNotNull();
+ assert callSiteOptimizationInfo.getAbstractArgumentValue(0).isUnknown();
+ }
+ }
+
+ private void inspect(CodeInspector inspector) {
+ ClassSubject a = inspector.clazz(A.class);
+ assertThat(a, isPresent());
+
+ MethodSubject a_m = a.uniqueMethodWithName("m");
+ assertThat(a_m, isPresent());
+ // Should not optimize branches since the value of `arg` is unsure.
+ assertTrue(a_m.streamInstructions().anyMatch(InstructionSubject::isIf));
+
+ ClassSubject b = inspector.clazz(B.class);
+ assertThat(b, isPresent());
+
+ MethodSubject b_m = b.uniqueMethodWithName("m");
+ assertThat(b_m, isPresent());
+ // Should not optimize branches since the value of `arg` is unsure.
+ assertTrue(a_m.streamInstructions().anyMatch(InstructionSubject::isIf));
+ }
+
+ @NeverMerge
+ @NeverClassInline
+ static class A {
+ @NeverInline
+ void m(String arg) {
+ if (arg.contains("null")) {
+ System.out.println("null");
+ } else {
+ System.out.println("non-null");
+ }
+ }
+
+ @NeverInline
+ @Override
+ public String toString() {
+ return "A";
+ }
+ }
+
+ @NeverClassInline
+ static class B extends A {
+ @NeverInline
+ @Override
+ void m(String arg) {
+ // Same as A#m.
+ if (arg.contains("null")) {
+ System.out.println("null");
+ } else {
+ System.out.println("non-null");
+ }
+ }
+
+ @NeverInline
+ @Override
+ public String toString() {
+ return "B";
+ }
+ }
+
+ static class Main {
+ public static void main(String... args) {
+ A a = new A();
+ test(a); // calls A.m() with "null".
+ a.m("nul"); // calls A.m() with "nul".
+ B b = new B();
+ test(b); // calls B.m() with "null".
+ b.m("nul"); // calls B.m() with "nul".
+ }
+
+ @NeverInline
+ static void test(A arg) {
+ arg.m("null");
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeVirtualPositiveTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeVirtualPositiveTest.java
new file mode 100644
index 0000000..3541836
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeVirtualPositiveTest.java
@@ -0,0 +1,129 @@
+// Copyright (c) 2019, 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.optimize.callsites.constants;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.ir.analysis.value.AbstractValue;
+import com.android.tools.r8.ir.optimize.info.CallSiteOptimizationInfo;
+import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class InvokeVirtualPositiveTest extends TestBase {
+ private static final Class<?> MAIN = Main.class;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ private final TestParameters parameters;
+
+ public InvokeVirtualPositiveTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(InvokeVirtualPositiveTest.class)
+ .addKeepMainRule(MAIN)
+ .enableMergeAnnotations()
+ .enableClassInliningAnnotations()
+ .enableInliningAnnotations()
+ .addOptionsModification(o -> {
+ o.testing.callSiteOptimizationInfoInspector = this::callSiteOptimizationInfoInspect;
+ })
+ .setMinApi(parameters.getApiLevel())
+ .addOptionsModification(InternalOptions::enablePropagationOfConstantsAtCallSites)
+ .run(parameters.getRuntime(), MAIN)
+ .assertSuccessWithOutputLines("non-null", "null")
+ .inspect(this::inspect);
+ }
+
+ private void callSiteOptimizationInfoInspect(DexEncodedMethod encodedMethod) {
+ assert encodedMethod.method.name.toString().equals("m")
+ : "Unexpected revisit: " + encodedMethod.toSourceString();
+ CallSiteOptimizationInfo callSiteOptimizationInfo = encodedMethod.getCallSiteOptimizationInfo();
+ assert callSiteOptimizationInfo.getDynamicUpperBoundType(1).isDefinitelyNotNull();
+ AbstractValue abstractValue = callSiteOptimizationInfo.getAbstractArgumentValue(1);
+ if (encodedMethod.method.holder.toSourceString().endsWith("$A")) {
+ assert abstractValue.isSingleStringValue()
+ && abstractValue.asSingleStringValue().getDexString().toString().equals("nul");
+ } else {
+ assert abstractValue.isUnknown();
+ }
+ }
+
+ private void inspect(CodeInspector inspector) {
+ ClassSubject a = inspector.clazz(A.class);
+ assertThat(a, isPresent());
+
+ MethodSubject a_m = a.uniqueMethodWithName("m");
+ assertThat(a_m, isPresent());
+ // Can optimize branches since `arg` is definitely "nul", i.e., not containing "null".
+ assertTrue(a_m.streamInstructions().noneMatch(InstructionSubject::isIf));
+
+ ClassSubject b = inspector.clazz(B.class);
+ assertThat(b, isPresent());
+
+ MethodSubject b_m = b.uniqueMethodWithName("m");
+ assertThat(b_m, isPresent());
+ // Should not optimize branches since the value of `arg` is unsure.
+ assertTrue(b_m.streamInstructions().anyMatch(InstructionSubject::isIf));
+ }
+
+ @NeverMerge
+ @NeverClassInline
+ static class A {
+ @NeverInline
+ void m(String arg) {
+ if (arg.contains("null")) {
+ System.out.println("null");
+ } else {
+ System.out.println("non-null");
+ }
+ }
+ }
+
+ @NeverClassInline
+ static class B extends A {
+ @NeverInline
+ @Override
+ void m(String arg) {
+ // Same as A#m.
+ if (arg.contains("null")) {
+ System.out.println("null");
+ } else {
+ System.out.println("non-null");
+ }
+ }
+ }
+
+ static class Main {
+ public static void main(String... args) {
+ A a = System.currentTimeMillis() > 0 ? new A() : new B();
+ a.m("nul"); // calls A.m() with "nul".
+
+ A b = new B(); // with the exact type:
+ b.m("null"); // calls B.m() with "null".
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeDirectNegativeTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeDirectNegativeTest.java
index fc40fd9..f350e73 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeDirectNegativeTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeDirectNegativeTest.java
@@ -12,6 +12,9 @@
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.optimize.info.CallSiteOptimizationInfo;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.android.tools.r8.utils.codeinspector.InstructionSubject;
@@ -42,12 +45,26 @@
.addKeepMainRule(MAIN)
.enableClassInliningAnnotations()
.enableInliningAnnotations()
+ .addOptionsModification(o -> {
+ o.testing.callSiteOptimizationInfoInspector = this::callSiteOptimizationInfoInspect;
+ })
.setMinApi(parameters.getApiLevel())
.run(parameters.getRuntime(), MAIN)
.assertSuccessWithOutputLines("Sub1", "Sub2")
.inspect(this::inspect);
}
+ private void callSiteOptimizationInfoInspect(DexEncodedMethod encodedMethod) {
+ assert encodedMethod.method.name.toString().equals("test")
+ : "Unexpected revisit: " + encodedMethod.toSourceString();
+ CallSiteOptimizationInfo callSiteOptimizationInfo = encodedMethod.getCallSiteOptimizationInfo();
+ TypeLatticeElement upperBoundType = callSiteOptimizationInfo.getDynamicUpperBoundType(1);
+ assert upperBoundType.isDefinitelyNotNull();
+ assert upperBoundType.isClassType()
+ && upperBoundType.asClassTypeLatticeElement()
+ .getClassType().toSourceString().endsWith("$Base");
+ }
+
private void inspect(CodeInspector inspector) {
ClassSubject main = inspector.clazz(MAIN);
assertThat(main, isPresent());
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeDirectPositiveTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeDirectPositiveTest.java
index a1b889c..baea5e4 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeDirectPositiveTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeDirectPositiveTest.java
@@ -13,6 +13,9 @@
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.optimize.info.CallSiteOptimizationInfo;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.android.tools.r8.utils.codeinspector.InstructionSubject;
@@ -44,12 +47,34 @@
.enableMergeAnnotations()
.enableClassInliningAnnotations()
.enableInliningAnnotations()
+ .addOptionsModification(o -> {
+ o.testing.callSiteOptimizationInfoInspector = this::callSiteOptimizationInfoInspect;
+ })
.setMinApi(parameters.getApiLevel())
.run(parameters.getRuntime(), MAIN)
.assertSuccessWithOutputLines("Sub1")
.inspect(this::inspect);
}
+ private void callSiteOptimizationInfoInspect(DexEncodedMethod encodedMethod) {
+ String methodName = encodedMethod.method.name.toString();
+ assert methodName.equals("<init>") || methodName.equals("test")
+ : "Unexpected revisit: " + encodedMethod.toSourceString();
+ CallSiteOptimizationInfo callSiteOptimizationInfo = encodedMethod.getCallSiteOptimizationInfo();
+ TypeLatticeElement upperBoundType;
+ if (methodName.equals("test")) {
+ upperBoundType = callSiteOptimizationInfo.getDynamicUpperBoundType(1);
+ } else {
+ // For testing purpose, `Base` is not merged and kept. The system correctly caught that, when
+ // the default initializer is invoked, the receiver had a refined type, `Sub1`.
+ upperBoundType = callSiteOptimizationInfo.getDynamicUpperBoundType(0);
+ }
+ assert upperBoundType.isDefinitelyNotNull();
+ assert upperBoundType.isClassType()
+ && upperBoundType.asClassTypeLatticeElement()
+ .getClassType().toSourceString().endsWith("$Sub1");
+ }
+
private void inspect(CodeInspector inspector) {
ClassSubject main = inspector.clazz(MAIN);
assertThat(main, isPresent());
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeInterfaceNegativeTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeInterfaceNegativeTest.java
index f309eb8..14f9b2f 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeInterfaceNegativeTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeInterfaceNegativeTest.java
@@ -13,6 +13,9 @@
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.optimize.info.CallSiteOptimizationInfo;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.android.tools.r8.utils.codeinspector.InstructionSubject;
@@ -47,6 +50,7 @@
.addOptionsModification(o -> {
// To prevent invoke-interface from being rewritten to invoke-virtual w/ a single target.
o.enableDevirtualization = false;
+ o.testing.callSiteOptimizationInfoInspector = this::callSiteOptimizationInfoInspect;
})
.setMinApi(parameters.getApiLevel())
.run(parameters.getRuntime(), MAIN)
@@ -54,6 +58,17 @@
.inspect(this::inspect);
}
+ private void callSiteOptimizationInfoInspect(DexEncodedMethod encodedMethod) {
+ assert encodedMethod.method.name.toString().equals("m")
+ : "Unexpected revisit: " + encodedMethod.toSourceString();
+ CallSiteOptimizationInfo callSiteOptimizationInfo = encodedMethod.getCallSiteOptimizationInfo();
+ TypeLatticeElement upperBoundType = callSiteOptimizationInfo.getDynamicUpperBoundType(1);
+ assert upperBoundType.isDefinitelyNotNull();
+ assert upperBoundType.isClassType()
+ && upperBoundType.asClassTypeLatticeElement()
+ .getClassType().toSourceString().endsWith("$Base");
+ }
+
private void inspect(CodeInspector inspector) {
ClassSubject i = inspector.clazz(I.class);
assertThat(i, isPresent());
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeInterfacePositiveTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeInterfacePositiveTest.java
index 7b8818e..9f1308d 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeInterfacePositiveTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeInterfacePositiveTest.java
@@ -13,6 +13,9 @@
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.optimize.info.CallSiteOptimizationInfo;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.android.tools.r8.utils.codeinspector.InstructionSubject;
@@ -47,6 +50,7 @@
.addOptionsModification(o -> {
// To prevent invoke-interface from being rewritten to invoke-virtual w/ a single target.
o.enableDevirtualization = false;
+ o.testing.callSiteOptimizationInfoInspector = this::callSiteOptimizationInfoInspect;
})
.setMinApi(parameters.getApiLevel())
.run(parameters.getRuntime(), MAIN)
@@ -54,6 +58,23 @@
.inspect(this::inspect);
}
+ private void callSiteOptimizationInfoInspect(DexEncodedMethod encodedMethod) {
+ assert encodedMethod.method.name.toString().equals("m")
+ : "Unexpected revisit: " + encodedMethod.toSourceString();
+ CallSiteOptimizationInfo callSiteOptimizationInfo = encodedMethod.getCallSiteOptimizationInfo();
+ TypeLatticeElement upperBoundType = callSiteOptimizationInfo.getDynamicUpperBoundType(1);
+ assert upperBoundType.isDefinitelyNotNull();
+ if (encodedMethod.method.holder.toSourceString().endsWith("$A")) {
+ assert upperBoundType.isClassType()
+ && upperBoundType.asClassTypeLatticeElement()
+ .getClassType().toSourceString().endsWith("$Sub1");
+ } else {
+ assert upperBoundType.isClassType()
+ && upperBoundType.asClassTypeLatticeElement()
+ .getClassType().toSourceString().endsWith("$Base");
+ }
+ }
+
private void inspect(CodeInspector inspector) {
ClassSubject i = inspector.clazz(I.class);
assertThat(i, isPresent());
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeStaticNegativeTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeStaticNegativeTest.java
index b63d571..6291f4d 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeStaticNegativeTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeStaticNegativeTest.java
@@ -11,6 +11,9 @@
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.optimize.info.CallSiteOptimizationInfo;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.android.tools.r8.utils.codeinspector.InstructionSubject;
@@ -40,12 +43,26 @@
.addInnerClasses(InvokeStaticNegativeTest.class)
.addKeepMainRule(MAIN)
.enableInliningAnnotations()
+ .addOptionsModification(o -> {
+ o.testing.callSiteOptimizationInfoInspector = this::callSiteOptimizationInfoInspect;
+ })
.setMinApi(parameters.getApiLevel())
.run(parameters.getRuntime(), MAIN)
.assertSuccessWithOutputLines("Sub1", "Sub2")
.inspect(this::inspect);
}
+ private void callSiteOptimizationInfoInspect(DexEncodedMethod encodedMethod) {
+ assert encodedMethod.method.name.toString().equals("test")
+ : "Unexpected revisit: " + encodedMethod.toSourceString();
+ CallSiteOptimizationInfo callSiteOptimizationInfo = encodedMethod.getCallSiteOptimizationInfo();
+ TypeLatticeElement upperBoundType = callSiteOptimizationInfo.getDynamicUpperBoundType(0);
+ assert upperBoundType.isDefinitelyNotNull();
+ assert upperBoundType.isClassType()
+ && upperBoundType.asClassTypeLatticeElement()
+ .getClassType().toSourceString().endsWith("$Base");
+ }
+
private void inspect(CodeInspector inspector) {
ClassSubject main = inspector.clazz(MAIN);
assertThat(main, isPresent());
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeStaticPositiveTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeStaticPositiveTest.java
index e8b75c5..e3bd783 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeStaticPositiveTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeStaticPositiveTest.java
@@ -12,6 +12,9 @@
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.optimize.info.CallSiteOptimizationInfo;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.android.tools.r8.utils.codeinspector.InstructionSubject;
@@ -42,12 +45,30 @@
.addKeepMainRule(MAIN)
.enableMergeAnnotations()
.enableInliningAnnotations()
+ .addOptionsModification(o -> {
+ o.testing.callSiteOptimizationInfoInspector = this::callSiteOptimizationInfoInspect;
+ })
.setMinApi(parameters.getApiLevel())
.run(parameters.getRuntime(), MAIN)
.assertSuccessWithOutputLines("Sub1")
.inspect(this::inspect);
}
+ private void callSiteOptimizationInfoInspect(DexEncodedMethod encodedMethod) {
+ String methodName = encodedMethod.method.name.toString();
+ assert methodName.equals("<init>") || methodName.equals("test")
+ : "Unexpected revisit: " + encodedMethod.toSourceString();
+ CallSiteOptimizationInfo callSiteOptimizationInfo = encodedMethod.getCallSiteOptimizationInfo();
+ // `arg` for `test` or the receiver of `Base#<init>`.
+ // For testing purpose, `Base` is not merged and kept. The system correctly caught that, when
+ // the default initializer is invoked, the receiver had a refined type, `Sub1`.
+ TypeLatticeElement upperBoundType = callSiteOptimizationInfo.getDynamicUpperBoundType(0);
+ assert upperBoundType.isDefinitelyNotNull();
+ assert upperBoundType.isClassType()
+ && upperBoundType.asClassTypeLatticeElement()
+ .getClassType().toSourceString().endsWith("$Sub1");
+ }
+
private void inspect(CodeInspector inspector) {
ClassSubject main = inspector.clazz(MAIN);
assertThat(main, isPresent());
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeVirtualNegativeTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeVirtualNegativeTest.java
index 6902f40..e62f6b4 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeVirtualNegativeTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeVirtualNegativeTest.java
@@ -13,6 +13,9 @@
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.optimize.info.CallSiteOptimizationInfo;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.android.tools.r8.utils.codeinspector.InstructionSubject;
@@ -44,12 +47,32 @@
.enableMergeAnnotations()
.enableClassInliningAnnotations()
.enableInliningAnnotations()
+ .addOptionsModification(o -> {
+ o.testing.callSiteOptimizationInfoInspector = this::callSiteOptimizationInfoInspect;
+ })
.setMinApi(parameters.getApiLevel())
.run(parameters.getRuntime(), MAIN)
.assertSuccessWithOutputLines("A:Sub1", "A:Sub2", "B:Sub1", "B:Sub2")
.inspect(this::inspect);
}
+ private void callSiteOptimizationInfoInspect(DexEncodedMethod encodedMethod) {
+ String methodName = encodedMethod.method.name.toString();
+ assert methodName.equals("m") || methodName.equals("test")
+ : "Unexpected revisit: " + encodedMethod.toSourceString();
+ CallSiteOptimizationInfo callSiteOptimizationInfo = encodedMethod.getCallSiteOptimizationInfo();
+ if (methodName.equals("m")) {
+ TypeLatticeElement upperBoundType = callSiteOptimizationInfo.getDynamicUpperBoundType(1);
+ assert upperBoundType.isDefinitelyNotNull();
+ assert upperBoundType.isClassType()
+ && upperBoundType.asClassTypeLatticeElement()
+ .getClassType().toSourceString().endsWith("$Base");
+ } else {
+ assert methodName.equals("test");
+ assert callSiteOptimizationInfo.getDynamicUpperBoundType(0).isDefinitelyNotNull();
+ }
+ }
+
private void inspect(CodeInspector inspector) {
ClassSubject a = inspector.clazz(A.class);
assertThat(a, isPresent());
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeVirtualPositiveTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeVirtualPositiveTest.java
index 5f3dda0..b6dae68 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeVirtualPositiveTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeVirtualPositiveTest.java
@@ -13,6 +13,9 @@
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.optimize.info.CallSiteOptimizationInfo;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.android.tools.r8.utils.codeinspector.InstructionSubject;
@@ -44,12 +47,34 @@
.enableMergeAnnotations()
.enableClassInliningAnnotations()
.enableInliningAnnotations()
+ .addOptionsModification(o -> {
+ o.testing.callSiteOptimizationInfoInspector = this::callSiteOptimizationInfoInspect;
+ })
.setMinApi(parameters.getApiLevel())
.run(parameters.getRuntime(), MAIN)
.assertSuccessWithOutputLines("A:Sub1", "B:Sub1")
.inspect(this::inspect);
}
+ private void callSiteOptimizationInfoInspect(DexEncodedMethod encodedMethod) {
+ String methodName = encodedMethod.method.name.toString();
+ assert methodName.equals("<init>") || methodName.equals("m")
+ : "Unexpected revisit: " + encodedMethod.toSourceString();
+ CallSiteOptimizationInfo callSiteOptimizationInfo = encodedMethod.getCallSiteOptimizationInfo();
+ TypeLatticeElement upperBoundType;
+ if (methodName.equals("m")) {
+ upperBoundType = callSiteOptimizationInfo.getDynamicUpperBoundType(1);
+ } else {
+ // For testing purpose, `Base` is not merged and kept. The system correctly caught that, when
+ // the default initializer is invoked, the receiver had a refined type, `Sub1`.
+ upperBoundType = callSiteOptimizationInfo.getDynamicUpperBoundType(0);
+ }
+ assert upperBoundType.isDefinitelyNotNull();
+ assert upperBoundType.isClassType()
+ && upperBoundType.asClassTypeLatticeElement()
+ .getClassType().toSourceString().endsWith("$Sub1");
+ }
+
private void inspect(CodeInspector inspector) {
ClassSubject a = inspector.clazz(A.class);
assertThat(a, isPresent());
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeDirectNegativeTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeDirectNegativeTest.java
index d84cf8e..def280a 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeDirectNegativeTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeDirectNegativeTest.java
@@ -12,6 +12,7 @@
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.android.tools.r8.utils.codeinspector.InstructionSubject;
@@ -42,12 +43,19 @@
.addKeepMainRule(MAIN)
.enableClassInliningAnnotations()
.enableInliningAnnotations()
+ .addOptionsModification(o -> {
+ o.testing.callSiteOptimizationInfoInspector = this::callSiteOptimizationInfoInspect;
+ })
.setMinApi(parameters.getApiLevel())
.run(parameters.getRuntime(), MAIN)
.assertSuccessWithOutputLines("null", "non-null")
.inspect(this::inspect);
}
+ private void callSiteOptimizationInfoInspect(DexEncodedMethod encodedMethod) {
+ assert false : "Unexpected revisit: " + encodedMethod.toSourceString();
+ }
+
private void inspect(CodeInspector inspector) {
ClassSubject main = inspector.clazz(MAIN);
assertThat(main, isPresent());
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeDirectPositiveTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeDirectPositiveTest.java
index ef0c90b..e24eadd 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeDirectPositiveTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeDirectPositiveTest.java
@@ -12,6 +12,8 @@
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.ir.optimize.info.CallSiteOptimizationInfo;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.android.tools.r8.utils.codeinspector.InstructionSubject;
@@ -42,12 +44,22 @@
.addKeepMainRule(MAIN)
.enableClassInliningAnnotations()
.enableInliningAnnotations()
+ .addOptionsModification(o -> {
+ o.testing.callSiteOptimizationInfoInspector = this::callSiteOptimizationInfoInspect;
+ })
.setMinApi(parameters.getApiLevel())
.run(parameters.getRuntime(), MAIN)
.assertSuccessWithOutputLines("non-null")
.inspect(this::inspect);
}
+ private void callSiteOptimizationInfoInspect(DexEncodedMethod encodedMethod) {
+ assert encodedMethod.method.name.toString().equals("test")
+ : "Unexpected revisit: " + encodedMethod.toSourceString();
+ CallSiteOptimizationInfo callSiteOptimizationInfo = encodedMethod.getCallSiteOptimizationInfo();
+ assert callSiteOptimizationInfo.getDynamicUpperBoundType(1).isDefinitelyNotNull();
+ }
+
private void inspect(CodeInspector inspector) {
ClassSubject main = inspector.clazz(MAIN);
assertThat(main, isPresent());
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeInterfaceNegativeTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeInterfaceNegativeTest.java
index 7481286..80b24a5 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeInterfaceNegativeTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeInterfaceNegativeTest.java
@@ -13,6 +13,9 @@
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.optimize.info.CallSiteOptimizationInfo;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.android.tools.r8.utils.codeinspector.InstructionSubject;
@@ -47,6 +50,7 @@
.addOptionsModification(o -> {
// To prevent invoke-interface from being rewritten to invoke-virtual w/ a single target.
o.enableDevirtualization = false;
+ o.testing.callSiteOptimizationInfoInspector = this::callSiteOptimizationInfoInspect;
})
.setMinApi(parameters.getApiLevel())
.run(parameters.getRuntime(), MAIN)
@@ -54,6 +58,17 @@
.inspect(this::inspect);
}
+ private void callSiteOptimizationInfoInspect(DexEncodedMethod encodedMethod) {
+ assert encodedMethod.method.name.toString().equals("m")
+ : "Unexpected revisit: " + encodedMethod.toSourceString();
+ CallSiteOptimizationInfo callSiteOptimizationInfo = encodedMethod.getCallSiteOptimizationInfo();
+ TypeLatticeElement upperBoundType = callSiteOptimizationInfo.getDynamicUpperBoundType(1);
+ assert upperBoundType.isNullable();
+ assert upperBoundType.isClassType()
+ && upperBoundType.asClassTypeLatticeElement()
+ .getClassType().toSourceString().endsWith("$A");
+ }
+
private void inspect(CodeInspector inspector) {
ClassSubject i = inspector.clazz(I.class);
assertThat(i, isPresent());
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeInterfacePositiveTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeInterfacePositiveTest.java
index 613db78..6cbbad3 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeInterfacePositiveTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeInterfacePositiveTest.java
@@ -12,6 +12,8 @@
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.ir.optimize.info.CallSiteOptimizationInfo;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.android.tools.r8.utils.codeinspector.InstructionSubject;
@@ -45,6 +47,7 @@
.addOptionsModification(o -> {
// To prevent invoke-interface from being rewritten to invoke-virtual w/ a single target.
o.enableDevirtualization = false;
+ o.testing.callSiteOptimizationInfoInspector = this::callSiteOptimizationInfoInspect;
})
.setMinApi(parameters.getApiLevel())
.run(parameters.getRuntime(), MAIN)
@@ -52,6 +55,13 @@
.inspect(this::inspect);
}
+ private void callSiteOptimizationInfoInspect(DexEncodedMethod encodedMethod) {
+ assert encodedMethod.method.name.toString().equals("m")
+ : "Unexpected revisit: " + encodedMethod.toSourceString();
+ CallSiteOptimizationInfo callSiteOptimizationInfo = encodedMethod.getCallSiteOptimizationInfo();
+ assert callSiteOptimizationInfo.getDynamicUpperBoundType(1).isDefinitelyNotNull();
+ }
+
private void inspect(CodeInspector inspector) {
ClassSubject i = inspector.clazz(I.class);
assertThat(i, isPresent());
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeInterfaceWithRefinedReceiverTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeInterfaceWithRefinedReceiverTest.java
index 601b19b..8e9e61f 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeInterfaceWithRefinedReceiverTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeInterfaceWithRefinedReceiverTest.java
@@ -14,6 +14,8 @@
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.ir.optimize.info.CallSiteOptimizationInfo;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.android.tools.r8.utils.codeinspector.InstructionSubject;
@@ -48,6 +50,7 @@
.addOptionsModification(o -> {
// To prevent invoke-interface from being rewritten to invoke-virtual w/ a single target.
o.enableDevirtualization = false;
+ o.testing.callSiteOptimizationInfoInspector = this::callSiteOptimizationInfoInspect;
})
.setMinApi(parameters.getApiLevel())
.run(parameters.getRuntime(), MAIN)
@@ -55,6 +58,17 @@
.inspect(this::inspect);
}
+ private void callSiteOptimizationInfoInspect(DexEncodedMethod encodedMethod) {
+ assert encodedMethod.method.name.toString().equals("m")
+ : "Unexpected revisit: " + encodedMethod.toSourceString();
+ CallSiteOptimizationInfo callSiteOptimizationInfo = encodedMethod.getCallSiteOptimizationInfo();
+ if (encodedMethod.method.holder.toSourceString().endsWith("$C")) {
+ assert callSiteOptimizationInfo.getDynamicUpperBoundType(1).isDefinitelyNotNull();
+ } else {
+ assert callSiteOptimizationInfo.getDynamicUpperBoundType(1).isDefinitelyNull();
+ }
+ }
+
private void inspect(CodeInspector inspector) {
ClassSubject i = inspector.clazz(I.class);
assertThat(i, isPresent());
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeStaticNegativeTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeStaticNegativeTest.java
index f796efe..1f3d6cc 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeStaticNegativeTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeStaticNegativeTest.java
@@ -11,6 +11,7 @@
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.android.tools.r8.utils.codeinspector.InstructionSubject;
@@ -40,12 +41,19 @@
.addInnerClasses(InvokeStaticNegativeTest.class)
.addKeepMainRule(MAIN)
.enableInliningAnnotations()
+ .addOptionsModification(o -> {
+ o.testing.callSiteOptimizationInfoInspector = this::callSiteOptimizationInfoInspect;
+ })
.setMinApi(parameters.getApiLevel())
.run(parameters.getRuntime(), MAIN)
.assertSuccessWithOutputLines("null", "non-null")
.inspect(this::inspect);
}
+ private void callSiteOptimizationInfoInspect(DexEncodedMethod encodedMethod) {
+ assert false : "Unexpected revisit: " + encodedMethod.toSourceString();
+ }
+
private void inspect(CodeInspector inspector) {
ClassSubject main = inspector.clazz(MAIN);
assertThat(main, isPresent());
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeStaticPositiveTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeStaticPositiveTest.java
index 401855c..4743089 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeStaticPositiveTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeStaticPositiveTest.java
@@ -11,6 +11,8 @@
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.ir.optimize.info.CallSiteOptimizationInfo;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.android.tools.r8.utils.codeinspector.InstructionSubject;
@@ -40,12 +42,22 @@
.addInnerClasses(InvokeStaticPositiveTest.class)
.addKeepMainRule(MAIN)
.enableInliningAnnotations()
+ .addOptionsModification(o -> {
+ o.testing.callSiteOptimizationInfoInspector = this::callSiteOptimizationInfoInspect;
+ })
.setMinApi(parameters.getApiLevel())
.run(parameters.getRuntime(), MAIN)
.assertSuccessWithOutputLines("non-null")
.inspect(this::inspect);
}
+ private void callSiteOptimizationInfoInspect(DexEncodedMethod encodedMethod) {
+ assert encodedMethod.method.name.toString().equals("test")
+ : "Unexpected revisit: " + encodedMethod.toSourceString();
+ CallSiteOptimizationInfo callSiteOptimizationInfo = encodedMethod.getCallSiteOptimizationInfo();
+ assert callSiteOptimizationInfo.getDynamicUpperBoundType(0).isDefinitelyNotNull();
+ }
+
private void inspect(CodeInspector inspector) {
ClassSubject main = inspector.clazz(MAIN);
assertThat(main, isPresent());
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeVirtualNegativeTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeVirtualNegativeTest.java
index 93588d4..cd20498 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeVirtualNegativeTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeVirtualNegativeTest.java
@@ -13,6 +13,9 @@
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.optimize.info.CallSiteOptimizationInfo;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.android.tools.r8.utils.codeinspector.InstructionSubject;
@@ -44,12 +47,32 @@
.enableMergeAnnotations()
.enableClassInliningAnnotations()
.enableInliningAnnotations()
+ .addOptionsModification(o -> {
+ o.testing.callSiteOptimizationInfoInspector = this::callSiteOptimizationInfoInspect;
+ })
.setMinApi(parameters.getApiLevel())
.run(parameters.getRuntime(), MAIN)
.assertSuccessWithOutputLines("null", "A", "null", "B")
.inspect(this::inspect);
}
+ private void callSiteOptimizationInfoInspect(DexEncodedMethod encodedMethod) {
+ String methodName = encodedMethod.method.name.toString();
+ assert methodName.equals("m") || methodName.equals("test")
+ : "Unexpected revisit: " + encodedMethod.toSourceString();
+ CallSiteOptimizationInfo callSiteOptimizationInfo = encodedMethod.getCallSiteOptimizationInfo();
+ if (methodName.equals("m")) {
+ TypeLatticeElement upperBoundType = callSiteOptimizationInfo.getDynamicUpperBoundType(1);
+ assert upperBoundType.isNullable();
+ assert upperBoundType.isClassType()
+ && upperBoundType.asClassTypeLatticeElement()
+ .getClassType().equals(encodedMethod.method.holder);
+ } else {
+ assert methodName.equals("test");
+ assert callSiteOptimizationInfo.getDynamicUpperBoundType(0).isDefinitelyNotNull();
+ }
+ }
+
private void inspect(CodeInspector inspector) {
ClassSubject a = inspector.clazz(A.class);
assertThat(a, isPresent());
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeVirtualPositiveTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeVirtualPositiveTest.java
index f8ad42c..6ab99d7 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeVirtualPositiveTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeVirtualPositiveTest.java
@@ -13,6 +13,9 @@
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.optimize.info.CallSiteOptimizationInfo;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.android.tools.r8.utils.codeinspector.InstructionSubject;
@@ -44,12 +47,31 @@
.enableMergeAnnotations()
.enableClassInliningAnnotations()
.enableInliningAnnotations()
+ .addOptionsModification(o -> {
+ o.testing.callSiteOptimizationInfoInspector = this::callSiteOptimizationInfoInspect;
+ })
.setMinApi(parameters.getApiLevel())
.run(parameters.getRuntime(), MAIN)
.assertSuccessWithOutputLines("A", "null")
.inspect(this::inspect);
}
+ private void callSiteOptimizationInfoInspect(DexEncodedMethod encodedMethod) {
+ assert encodedMethod.method.name.toString().equals("m")
+ : "Unexpected revisit: " + encodedMethod.toSourceString();
+ CallSiteOptimizationInfo callSiteOptimizationInfo = encodedMethod.getCallSiteOptimizationInfo();
+ TypeLatticeElement upperBoundType = callSiteOptimizationInfo.getDynamicUpperBoundType(1);
+ assert upperBoundType.isClassType()
+ && upperBoundType.asClassTypeLatticeElement()
+ .getClassType().toSourceString().endsWith("$A");
+ if (encodedMethod.method.holder.toSourceString().endsWith("$A")) {
+ assert upperBoundType.isDefinitelyNotNull();
+ } else {
+ assert encodedMethod.method.holder.toSourceString().endsWith("$B");
+ assert upperBoundType.isNullable();
+ }
+ }
+
private void inspect(CodeInspector inspector) {
ClassSubject a = inspector.clazz(A.class);
assertThat(a, isPresent());
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeVirtualWithRefinedReceiverTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeVirtualWithRefinedReceiverTest.java
index a9a62d9..354ba3b 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeVirtualWithRefinedReceiverTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeVirtualWithRefinedReceiverTest.java
@@ -14,6 +14,8 @@
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.ir.optimize.info.CallSiteOptimizationInfo;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.android.tools.r8.utils.codeinspector.InstructionSubject;
@@ -45,12 +47,26 @@
.enableMergeAnnotations()
.enableClassInliningAnnotations()
.enableInliningAnnotations()
+ .addOptionsModification(o -> {
+ o.testing.callSiteOptimizationInfoInspector = this::callSiteOptimizationInfoInspect;
+ })
.setMinApi(parameters.getApiLevel())
.run(parameters.getRuntime(), MAIN)
.assertSuccessWithOutputLines("null", "C")
.inspect(this::inspect);
}
+ private void callSiteOptimizationInfoInspect(DexEncodedMethod encodedMethod) {
+ assert encodedMethod.method.name.toString().equals("m")
+ : "Unexpected revisit: " + encodedMethod.toSourceString();
+ CallSiteOptimizationInfo callSiteOptimizationInfo = encodedMethod.getCallSiteOptimizationInfo();
+ if (encodedMethod.method.holder.toSourceString().endsWith("$C")) {
+ assert callSiteOptimizationInfo.getDynamicUpperBoundType(1).isDefinitelyNotNull();
+ } else {
+ assert callSiteOptimizationInfo.getDynamicUpperBoundType(1).isDefinitelyNull();
+ }
+ }
+
private void inspect(CodeInspector inspector) {
ClassSubject a = inspector.clazz(A.class);
assertThat(a, isPresent());
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/KeptMethodTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/KeptMethodTest.java
index d5b13af..3822a49 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/KeptMethodTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/KeptMethodTest.java
@@ -12,6 +12,7 @@
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.android.tools.r8.utils.codeinspector.InstructionSubject;
@@ -44,12 +45,19 @@
.addKeepClassAndMembersRules(A.class)
.enableClassInliningAnnotations()
.enableInliningAnnotations()
+ .addOptionsModification(o -> {
+ o.testing.callSiteOptimizationInfoInspector = this::callSiteOptimizationInfoInspect;
+ })
.setMinApi(parameters.getApiLevel())
.run(parameters.getRuntime(), MAIN)
.assertSuccessWithOutputLines("non-null", "non-null")
.inspect(this::inspect);
}
+ private void callSiteOptimizationInfoInspect(DexEncodedMethod encodedMethod) {
+ assert false : "Unexpected revisit: " + encodedMethod.toSourceString();
+ }
+
private void inspect(CodeInspector inspector) {
ClassSubject a = inspector.clazz(A.class);
assertThat(a, isPresent());
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/LibraryMethodOverridesTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/LibraryMethodOverridesTest.java
index 6e890de..ba8c1fd 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/LibraryMethodOverridesTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/LibraryMethodOverridesTest.java
@@ -13,6 +13,7 @@
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
import com.android.tools.r8.ToolHelper.DexVm.Version;
+import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.android.tools.r8.utils.codeinspector.InstructionSubject;
@@ -54,6 +55,9 @@
.addClasspathClasses(LibClass.class)
.addKeepMainRule(MAIN)
.enableInliningAnnotations()
+ .addOptionsModification(o -> {
+ o.testing.callSiteOptimizationInfoInspector = this::callSiteOptimizationInfoInspect;
+ })
.setMinApi(parameters.getRuntime())
.compile()
.addRunClasspathFiles(libraryCompileResult.writeToZip())
@@ -62,6 +66,10 @@
.inspect(this::inspect);
}
+ private void callSiteOptimizationInfoInspect(DexEncodedMethod encodedMethod) {
+ assert false : "Unexpected revisit: " + encodedMethod.toSourceString();
+ }
+
private void inspect(CodeInspector inspector) {
ClassSubject customPredicate = inspector.clazz(CustomPredicate.class);
assertThat(customPredicate, isPresent());
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/BuilderWithNonArgumentFieldValueTest.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/BuilderWithNonArgumentFieldValueTest.java
new file mode 100644
index 0000000..3fdb772
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/BuilderWithNonArgumentFieldValueTest.java
@@ -0,0 +1,84 @@
+// Copyright (c) 2019, 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.optimize.classinliner;
+
+import static org.junit.Assert.assertNotEquals;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class BuilderWithNonArgumentFieldValueTest extends TestBase {
+
+ private final boolean enableClassInlining;
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{1}, class inlining: {0}")
+ public static List<Object[]> data() {
+ return buildParameters(
+ BooleanUtils.values(), getTestParameters().withAllRuntimesAndApiLevels().build());
+ }
+
+ public BuilderWithNonArgumentFieldValueTest(
+ boolean enableClassInlining, TestParameters parameters) {
+ this.enableClassInlining = enableClassInlining;
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(BuilderWithNonArgumentFieldValueTest.class)
+ .addKeepMainRule(TestClass.class)
+ .addOptionsModification(options -> options.enableClassInlining = enableClassInlining)
+ .enableInliningAnnotations()
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspect(this::inspect)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutputLines("Hello world!");
+ }
+
+ private void inspect(CodeInspector inspector) {
+ ClassSubject builderClassSubject = inspector.clazz(Builder.class);
+ assertNotEquals(enableClassInlining, builderClassSubject.isPresent());
+ }
+
+ static class TestClass {
+
+ public static void main(String[] args) {
+ System.out.println(new Builder().world().build());
+ }
+ }
+
+ static class Builder {
+
+ String suffix;
+
+ Builder() {
+ System.out.print("Hello");
+ this.suffix = "!";
+ }
+
+ @NeverInline
+ Builder world() {
+ System.out.print(" world");
+ return this;
+ }
+
+ @NeverInline
+ String build() {
+ return suffix;
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/EnumMemberValuePropagationTest.java b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/EnumMemberValuePropagationTest.java
new file mode 100644
index 0000000..ae2b510
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/EnumMemberValuePropagationTest.java
@@ -0,0 +1,103 @@
+// Copyright (c) 2019, 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.optimize.membervaluepropagation;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class EnumMemberValuePropagationTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public EnumMemberValuePropagationTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(EnumMemberValuePropagationTest.class)
+ .addKeepMainRule(TestClass.class)
+ .enableInliningAnnotations()
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspect(this::inspect)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutputLines("Hello world!");
+ }
+
+ private void inspect(CodeInspector inspector) {
+ ClassSubject testClassSubject = inspector.clazz(TestClass.class);
+ assertThat(testClassSubject, isPresent());
+ assertThat(
+ testClassSubject.uniqueMethodWithName("deadDueToFieldValuePropagation"), not(isPresent()));
+ assertThat(
+ testClassSubject.uniqueMethodWithName("deadDueToReturnValuePropagation"), not(isPresent()));
+
+ // Verify that there are no more conditional instructions.
+ MethodSubject mainMethodSubject = testClassSubject.mainMethod();
+ assertThat(mainMethodSubject, isPresent());
+ assertTrue(mainMethodSubject.streamInstructions().noneMatch(InstructionSubject::isIf));
+ }
+
+ static class TestClass {
+
+ static MyEnum INSTANCE = MyEnum.A;
+
+ public static void main(String[] args) {
+ if (INSTANCE == MyEnum.A) {
+ System.out.print("Hello");
+ } else {
+ deadDueToFieldValuePropagation();
+ }
+ if (get() == MyEnum.A) {
+ System.out.println(" world!");
+ } else {
+ deadDueToReturnValuePropagation();
+ }
+ }
+
+ @NeverInline
+ static MyEnum get() {
+ return MyEnum.A;
+ }
+
+ @NeverInline
+ static void deadDueToFieldValuePropagation() {
+ throw new RuntimeException();
+ }
+
+ @NeverInline
+ static void deadDueToReturnValuePropagation() {
+ throw new RuntimeException();
+ }
+ }
+
+ enum MyEnum {
+ A,
+ B
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/InstanceFieldValuePropagationTest.java b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/InstanceFieldValuePropagationTest.java
new file mode 100644
index 0000000..a3d644f
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/InstanceFieldValuePropagationTest.java
@@ -0,0 +1,136 @@
+// Copyright (c) 2019, 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.optimize.membervaluepropagation;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class InstanceFieldValuePropagationTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public InstanceFieldValuePropagationTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(InstanceFieldValuePropagationTest.class)
+ .addKeepMainRule(TestClass.class)
+ .enableClassInliningAnnotations()
+ .enableInliningAnnotations()
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspect(this::inspect)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(
+ StringUtils.times(StringUtils.lines("A", "42", "Hello world!"), 2));
+ }
+
+ private void inspect(CodeInspector inspector) {
+ ClassSubject testClassSubject = inspector.clazz(TestClass.class);
+ assertThat(testClassSubject, isPresent());
+
+ // Verify that all instance-get instructions in testDefinitelyNotNull() has been removed by
+ // member value propagation.
+ MethodSubject testDefinitelyNotNullMethodSubject =
+ testClassSubject.uniqueMethodWithName("testDefinitelyNotNull");
+ assertThat(testDefinitelyNotNullMethodSubject, isPresent());
+ assertTrue(
+ testDefinitelyNotNullMethodSubject
+ .streamInstructions()
+ .noneMatch(InstructionSubject::isInstanceGet));
+ // TODO(b/125282093): Should be able to remove the new-instance instruction since the instance
+ // ends up being unused.
+ assertTrue(
+ testDefinitelyNotNullMethodSubject
+ .streamInstructions()
+ .anyMatch(InstructionSubject::isNewInstance));
+
+ // Verify that all instance-get instructions in testMaybeNull() has been removed by member value
+ // propagation.
+ MethodSubject testMaybeNullMethodSubject =
+ testClassSubject.uniqueMethodWithName("testMaybeNull");
+ assertThat(testMaybeNullMethodSubject, isPresent());
+ // TODO(b/125282093): Should synthesize a null-check and still propagate the field values even
+ // when the receiver is nullable.
+ assertTrue(
+ testMaybeNullMethodSubject
+ .streamInstructions()
+ .anyMatch(InstructionSubject::isInstanceGet));
+ // TODO(b/125282093): Should be able to remove the new-instance instruction since the instance
+ // ends up being unused.
+ assertTrue(
+ testMaybeNullMethodSubject
+ .streamInstructions()
+ .anyMatch(InstructionSubject::isNewInstance));
+
+ ClassSubject aClassSubject = inspector.clazz(A.class);
+ assertThat(aClassSubject, isPresent());
+ // TODO(b/125282093): Need to remove the instance-put instructions in A.<init>(). This can not
+ // be done safely by the time we process A.<init>(), so some kind of post-processing is needed.
+ assertEquals(3, aClassSubject.allInstanceFields().size());
+ }
+
+ static class TestClass {
+
+ public static void main(String[] args) {
+ testDefinitelyNotNull();
+ testMaybeNull();
+ }
+
+ @NeverInline
+ static void testDefinitelyNotNull() {
+ A a = new A();
+ System.out.println(a.e);
+ System.out.println(a.i);
+ System.out.println(a.s);
+ }
+
+ @NeverInline
+ static void testMaybeNull() {
+ A a = System.currentTimeMillis() >= 0 ? new A() : null;
+ System.out.println(a.e);
+ System.out.println(a.i);
+ System.out.println(a.s);
+ }
+ }
+
+ @NeverClassInline
+ static class A {
+
+ MyEnum e = MyEnum.A;
+ int i = 42;
+ String s = "Hello world!";
+ }
+
+ enum MyEnum {
+ A,
+ B
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/InstanceFieldValuePropagationWithMultipleInstanceInitializersTest.java b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/InstanceFieldValuePropagationWithMultipleInstanceInitializersTest.java
new file mode 100644
index 0000000..d2b739e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/InstanceFieldValuePropagationWithMultipleInstanceInitializersTest.java
@@ -0,0 +1,87 @@
+// Copyright (c) 2019, 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.optimize.membervaluepropagation;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class InstanceFieldValuePropagationWithMultipleInstanceInitializersTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public InstanceFieldValuePropagationWithMultipleInstanceInitializersTest(
+ TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(InstanceFieldValuePropagationWithMultipleInstanceInitializersTest.class)
+ .addKeepMainRule(TestClass.class)
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspect(this::inspect)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutputLines("Hello world!");
+ }
+
+ private void inspect(CodeInspector inspector) {
+ ClassSubject testClassSubject = inspector.clazz(TestClass.class);
+ assertThat(testClassSubject, isPresent());
+
+ // Verify that the instance-get instruction in main() is still present in main(), since the
+ // value of `a.greeting` depends on the constructor being used.
+ MethodSubject mainMethodSubject = testClassSubject.mainMethod();
+ assertThat(mainMethodSubject, isPresent());
+ assertTrue(mainMethodSubject.streamInstructions().anyMatch(InstructionSubject::isInstanceGet));
+
+ // Verify that the `greeting` field is still present.
+ ClassSubject aClassSubject = inspector.clazz(A.class);
+ assertThat(aClassSubject, isPresent());
+ assertThat(aClassSubject.uniqueFieldWithName("greeting"), isPresent());
+ }
+
+ static class TestClass {
+
+ public static void main(String[] args) {
+ A a = System.currentTimeMillis() >= 0 ? new A() : new A(new Object());
+ System.out.println(a.greeting);
+ }
+ }
+
+ @NeverClassInline
+ static class A {
+
+ String greeting;
+
+ A() {
+ this.greeting = "Hello world!";
+ }
+
+ A(Object unused) {
+ this.greeting = ":-(";
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetClassTest.java b/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetClassTest.java
index 90aeb7c..aa8e1d5 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetClassTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetClassTest.java
@@ -135,7 +135,7 @@
// In `getMainClass`, a call with `null`, which will throw NPE, is replaced with null throwing
// code. Then, remaining call with non-null argument made getClass() replaceable.
// Disable the propagation of call site information to separate the tests.
- options.enableCallSiteOptimizationInfoPropagation = false;
+ options.enablePropagationOfDynamicTypesAtCallSites = false;
}
@Test
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/staticizer/InvokeStaticWithNullOutvalueTest.java b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/InvokeStaticWithNullOutvalueTest.java
new file mode 100644
index 0000000..2635741
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/InvokeStaticWithNullOutvalueTest.java
@@ -0,0 +1,106 @@
+// Copyright (c) 2019, 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.optimize.staticizer;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.FieldSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import com.google.common.collect.ImmutableList;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class InvokeStaticWithNullOutvalueTest extends TestBase {
+ private static final Class<?> MAIN = Main.class;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ // TODO(b/112831361): support for class staticizer in CF backend.
+ return getTestParameters().withDexRuntimes().withAllApiLevels().build();
+ }
+
+ private final TestParameters parameters;
+
+ public InvokeStaticWithNullOutvalueTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(InvokeStaticWithNullOutvalueTest.class)
+ .addKeepMainRule(MAIN)
+ .enableInliningAnnotations()
+ .enableClassInliningAnnotations()
+ .setMinApi(parameters.getApiLevel())
+ .run(parameters.getRuntime(), MAIN)
+ .assertSuccessWithOutputLines("Companion#boo", "Companion#foo")
+ .inspect(this::inspect);
+ }
+
+ private void inspect(CodeInspector inspector) {
+ // Check if the instance is gone.
+ ClassSubject host = inspector.clazz(Host.class);
+ assertThat(host, isPresent());
+ FieldSubject instance = host.uniqueFieldWithName("companion");
+ assertThat(instance, not(isPresent()));
+
+ ClassSubject companion = inspector.clazz(Host.Companion.class);
+ assertThat(companion, not(isPresent()));
+
+ // Check if the candidate methods are staticized (if necessary) and migrated.
+ for (String name : ImmutableList.of("boo", "foo")) {
+ MethodSubject oo = host.uniqueMethodWithName(name);
+ assertThat(oo, isPresent());
+ assertTrue(oo.isStatic());
+ assertTrue(
+ oo.streamInstructions().anyMatch(
+ i -> i.isInvokeVirtual()
+ && i.getMethod().toSourceString().contains("PrintStream.println")));
+ }
+ }
+
+ @NeverClassInline
+ static class Host {
+ private static final Companion companion = new Companion();
+
+ static class Companion {
+ @NeverInline
+ private static Object boo() {
+ System.out.println("Companion#boo");
+ return null;
+ }
+
+ @NeverInline
+ void foo() {
+ // Return value is not used, hence invoke-static without out-value.
+ boo();
+ System.out.println("Companion#foo");
+ }
+ }
+
+ @NeverInline
+ static void bar() {
+ companion.foo();
+ }
+ }
+
+ static class Main {
+ public static void main(String[] args) {
+ Host.bar();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/string/StringIsEmptyTest.java b/src/test/java/com/android/tools/r8/ir/optimize/string/StringIsEmptyTest.java
index 8869a01..0ac155c 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/string/StringIsEmptyTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/string/StringIsEmptyTest.java
@@ -15,6 +15,7 @@
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
import com.android.tools.r8.TestRunResult;
+import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.StringUtils;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
@@ -35,7 +36,7 @@
@Parameterized.Parameters(name = "{0}")
public static TestParametersCollection data() {
- return getTestParameters().withAllRuntimes().build();
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
}
private final TestParameters parameters;
@@ -53,6 +54,13 @@
.assertSuccessWithOutput(JAVA_OUTPUT);
}
+ private void configure(InternalOptions options) {
+ // This test wants to check if compile-time computation is not applied to non-null,
+ // non-constant value. In a simple test setting, call-site optimization knows the argument is
+ // always a non-null, specific constant, but that is beyond the scope of this test.
+ options.enablePropagationOfConstantsAtCallSites = false;
+ }
+
private void test(TestRunResult result, int expectedStringIsEmptyCount) throws Exception {
CodeInspector codeInspector = result.inspector();
ClassSubject mainClass = codeInspector.clazz(MAIN);
@@ -62,7 +70,7 @@
MethodSubject wrapper = mainClass.uniqueMethodWithName("wrapper");
assertThat(wrapper, isPresent());
- // Because of nullable, non-constant argument, isEmpty() should remain.
+ // Due to nullable, non-constant argument (w/o call-site optimization), isEmpty() should remain.
assertEquals(1, countCall(wrapper, "String", "isEmpty"));
}
@@ -74,7 +82,8 @@
testForD8()
.debug()
.addProgramClasses(MAIN)
- .setMinApi(parameters.getRuntime())
+ .setMinApi(parameters.getApiLevel())
+ .addOptionsModification(this::configure)
.run(parameters.getRuntime(), MAIN)
.assertSuccessWithOutput(JAVA_OUTPUT);
test(result, 3);
@@ -83,7 +92,8 @@
testForD8()
.release()
.addProgramClasses(MAIN)
- .setMinApi(parameters.getRuntime())
+ .setMinApi(parameters.getApiLevel())
+ .addOptionsModification(this::configure)
.run(parameters.getRuntime(), MAIN)
.assertSuccessWithOutput(JAVA_OUTPUT);
test(result, 1);
@@ -97,7 +107,8 @@
.enableProguardTestOptions()
.enableInliningAnnotations()
.addKeepMainRule(MAIN)
- .setMinApi(parameters.getRuntime())
+ .setMinApi(parameters.getApiLevel())
+ .addOptionsModification(this::configure)
.run(parameters.getRuntime(), MAIN)
.assertSuccessWithOutput(JAVA_OUTPUT);
test(result, 1);
@@ -107,7 +118,7 @@
@NeverInline
static boolean wrapper(String arg) {
- // Cannot be computed at compile time.
+ // Cannot be computed at compile time (unless call-site optimization is enabled).
return arg.isEmpty();
}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/string/StringValueOfTest.java b/src/test/java/com/android/tools/r8/ir/optimize/string/StringValueOfTest.java
index 85cf999..f9d9d21 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/string/StringValueOfTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/string/StringValueOfTest.java
@@ -58,7 +58,7 @@
// Disable the propagation of call site information to test String#valueOf optimization with
// nullable argument. Otherwise, e.g., we know that only `null` is used for `hideNPE`, and then
// simplify everything in that method.
- options.enableCallSiteOptimizationInfoPropagation = false;
+ options.enablePropagationOfDynamicTypesAtCallSites = false;
options.testing.forceNameReflectionOptimization = true;
}
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameTest.java
index 160dc3f..e42b66b 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameTest.java
@@ -14,7 +14,6 @@
import static org.junit.Assume.assumeTrue;
import com.android.tools.r8.KotlinTestBase;
-import com.android.tools.r8.KotlinTestCompileResult;
import com.android.tools.r8.R8TestCompileResult;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.ToolHelper;
@@ -110,15 +109,18 @@
compileResult.writeToZip(r8ProcessedLibZip);
String appFolder = PKG_PREFIX + "/supertype_app";
- KotlinTestCompileResult kotlinTestCompileResult =
- testForKotlin()
+ ProcessResult kotlinTestCompileResult =
+ kotlinc(parameters.getRuntime().asCf())
.addClasspathFiles(r8ProcessedLibZip)
.addSourceFiles(getKotlinFileInTest(appFolder, "main"))
- .compile();
+ // TODO(b/143687784): update to just .compile() once fixed.
+ .setOutputPath(temp.newFolder().toPath())
+ .compileRaw();
+
// TODO(b/143687784): should be able to compile!
- assertNotEquals(0, kotlinTestCompileResult.exitCode());
+ assertNotEquals(0, kotlinTestCompileResult.exitCode);
assertThat(
- kotlinTestCompileResult.stderr(),
+ kotlinTestCompileResult.stderr,
containsString("unresolved supertypes: " + pkg + ".supertype_lib.internal.Itf"));
}
@@ -157,16 +159,18 @@
compileResult.writeToZip(r8ProcessedLibZip);
String appFolder = PKG_PREFIX + "/extension_app";
- KotlinTestCompileResult kotlinTestCompileResult =
- testForKotlin()
+ ProcessResult kotlinTestCompileResult =
+ kotlinc(parameters.getRuntime().asCf())
.addClasspathFiles(r8ProcessedLibZip)
.addSourceFiles(getKotlinFileInTest(appFolder, "main"))
- .compile();
+ // TODO(b/143687784): update to just .compile() once fixed.
+ .setOutputPath(temp.newFolder().toPath())
+ .compileRaw();
// TODO(b/143687784): should be able to compile!
- assertNotEquals(0, kotlinTestCompileResult.exitCode());
+ assertNotEquals(0, kotlinTestCompileResult.exitCode);
assertThat(
- kotlinTestCompileResult.stderr(),
+ kotlinTestCompileResult.stderr,
containsString("unresolved supertypes: " + pkg + ".extension_lib.Super"));
- assertThat(kotlinTestCompileResult.stderr(), containsString("unresolved reference: doStuff"));
+ assertThat(kotlinTestCompileResult.stderr, containsString("unresolved reference: doStuff"));
}
}
diff --git a/src/test/java/com/android/tools/r8/regress/b142682636/Regress142682636.java b/src/test/java/com/android/tools/r8/regress/b142682636/Regress142682636.java
new file mode 100644
index 0000000..2bef90c
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/regress/b142682636/Regress142682636.java
@@ -0,0 +1,20 @@
+// Copyright (c) 2019, 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.regress.b142682636;
+
+class Regress142682636 {
+ static void foo(long v, byte[] a, int s, int b) {
+ for (int i = s; i < s + b; i++) {
+ a[i] = (byte) v;
+ v = v >> 8;
+ }
+ }
+
+ static void bar(int s, long v, byte[] a, int b) {
+ for (int i = s; i < s + b; i++) {
+ a[i] = (byte) v;
+ v = v >> 8;
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/regress/b142682636/Regress142682636Runner.java b/src/test/java/com/android/tools/r8/regress/b142682636/Regress142682636Runner.java
new file mode 100644
index 0000000..42a64b5
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/regress/b142682636/Regress142682636Runner.java
@@ -0,0 +1,43 @@
+// Copyright (c) 2019, 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.regress.b142682636;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.code.MoveWide;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.util.Arrays;
+import org.junit.Test;
+
+public class Regress142682636Runner extends TestBase {
+ private final Class<?> testClass = Regress142682636.class;
+
+ @Test
+ public void test() throws Exception {
+ CodeInspector inspector = testForD8()
+ .addProgramClasses(testClass)
+ .release()
+ .compile()
+ .inspector();
+ ClassSubject clazz = inspector.clazz(testClass);
+ assertThat(clazz, isPresent());
+ MethodSubject foo = clazz.uniqueMethodWithName("foo");
+ assertThat(foo, isPresent());
+ checkNoMoveWide(foo);
+ MethodSubject bar = clazz.uniqueMethodWithName("bar");
+ assertThat(bar, isPresent());
+ checkNoMoveWide(bar);
+ }
+
+ private void checkNoMoveWide(MethodSubject m) {
+ assertTrue(Arrays.stream(m.getMethod().getCode().asDexCode().instructions)
+ .noneMatch(i -> i instanceof MoveWide));
+ }
+
+}
diff --git a/src/test/java/com/android/tools/r8/resolution/InvokeVirtualOnInterfaceTest.java b/src/test/java/com/android/tools/r8/resolution/InvokeVirtualOnInterfaceTest.java
index ea9b756..6c2effe 100644
--- a/src/test/java/com/android/tools/r8/resolution/InvokeVirtualOnInterfaceTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/InvokeVirtualOnInterfaceTest.java
@@ -4,24 +4,21 @@
package com.android.tools.r8.resolution;
import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
+import static org.objectweb.asm.Opcodes.INVOKEINTERFACE;
+import static org.objectweb.asm.Opcodes.INVOKEVIRTUAL;
import com.android.tools.r8.CompilationFailedException;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.ToolHelper.DexVm.Version;
import com.android.tools.r8.utils.DescriptorUtils;
import org.hamcrest.Matcher;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
-import org.objectweb.asm.ClassWriter;
-import org.objectweb.asm.Label;
-import org.objectweb.asm.MethodVisitor;
-import org.objectweb.asm.Opcodes;
-import org.objectweb.asm.util.ASMifier;
@RunWith(Parameterized.class)
public class InvokeVirtualOnInterfaceTest extends TestBase {
@@ -41,7 +38,7 @@
public void testReference() throws Exception {
testForRuntime(parameters.getRuntime(), parameters.getApiLevel())
.addProgramClasses(I.class, C1.class, C2.class)
- .addProgramClassFileData(DumpMain.dump())
+ .addProgramClassFileData(transformMain())
.run(parameters.getRuntime(), Main.class)
.assertFailureWithErrorThatMatches(getExpectedFailureMatcher(false));
}
@@ -51,7 +48,7 @@
try {
testForR8(parameters.getBackend())
.addProgramClasses(I.class, C1.class, C2.class)
- .addProgramClassFileData(DumpMain.dump())
+ .addProgramClassFileData(transformMain())
.addKeepMainRule(Main.class)
.setMinApi(parameters.getApiLevel())
.compile()
@@ -115,92 +112,20 @@
}
}
- static class DumpMain implements Opcodes {
-
- public static void main(String[] args) throws Exception {
- ASMifier.main(
- new String[] {"-debug", ToolHelper.getClassFileForTestClass(Main.class).toString()});
- }
-
- public static byte[] dump() {
-
- ClassWriter classWriter = new ClassWriter(0);
- MethodVisitor methodVisitor;
-
- classWriter.visit(
- V1_8,
- ACC_SUPER,
- DescriptorUtils.getBinaryNameFromJavaType(Main.class.getName()),
- null,
- "java/lang/Object",
- null);
-
- {
- methodVisitor = classWriter.visitMethod(0, "<init>", "()V", null, null);
- methodVisitor.visitCode();
- methodVisitor.visitVarInsn(ALOAD, 0);
- methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
- methodVisitor.visitInsn(RETURN);
- methodVisitor.visitMaxs(1, 1);
- methodVisitor.visitEnd();
- }
-
- {
- methodVisitor =
- classWriter.visitMethod(
- ACC_PUBLIC | ACC_STATIC, "main", "([Ljava/lang/String;)V", null, null);
- methodVisitor.visitCode();
- methodVisitor.visitVarInsn(ALOAD, 0);
- methodVisitor.visitInsn(ARRAYLENGTH);
- methodVisitor.visitInsn(ICONST_2);
- methodVisitor.visitInsn(IREM);
- Label label0 = new Label();
- methodVisitor.visitJumpInsn(IFNE, label0);
- methodVisitor.visitTypeInsn(
- NEW, "com/android/tools/r8/resolution/InvokeVirtualOnInterfaceTest$C1");
- methodVisitor.visitInsn(DUP);
- methodVisitor.visitMethodInsn(
- INVOKESPECIAL,
- "com/android/tools/r8/resolution/InvokeVirtualOnInterfaceTest$C1",
- "<init>",
- "()V",
- false);
- Label label1 = new Label();
- methodVisitor.visitJumpInsn(GOTO, label1);
- methodVisitor.visitLabel(label0);
- methodVisitor.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
- methodVisitor.visitTypeInsn(
- NEW, "com/android/tools/r8/resolution/InvokeVirtualOnInterfaceTest$C2");
- methodVisitor.visitInsn(DUP);
- methodVisitor.visitMethodInsn(
- INVOKESPECIAL,
- "com/android/tools/r8/resolution/InvokeVirtualOnInterfaceTest$C2",
- "<init>",
- "()V",
- false);
- methodVisitor.visitLabel(label1);
- methodVisitor.visitFrame(
- Opcodes.F_SAME1,
- 0,
- null,
- 1,
- new Object[] {"com/android/tools/r8/resolution/InvokeVirtualOnInterfaceTest$I"});
- methodVisitor.visitVarInsn(ASTORE, 1);
- methodVisitor.visitVarInsn(ALOAD, 1);
- // Manually changed from INVOKEINTERFACE & true => INVOKEVIRTUAL & false
- methodVisitor.visitMethodInsn(
- INVOKEVIRTUAL,
- "com/android/tools/r8/resolution/InvokeVirtualOnInterfaceTest$I",
- "f",
- "()V",
- false);
- methodVisitor.visitInsn(RETURN);
- methodVisitor.visitMaxs(2, 2);
- methodVisitor.visitEnd();
- }
- classWriter.visitEnd();
-
- return classWriter.toByteArray();
- }
+ private static byte[] transformMain() throws Exception {
+ String binaryNameForI = DescriptorUtils.getBinaryNameFromJavaType(I.class.getTypeName());
+ return transformer(Main.class)
+ .transformMethodInsnInMethod(
+ "main",
+ (opcode, owner, name, descriptor, isInterface, continuation) -> {
+ if (owner.equals(binaryNameForI) && name.equals("f")) {
+ assertEquals(INVOKEINTERFACE, opcode);
+ assertTrue(isInterface);
+ continuation.visitMethodInsn(INVOKEVIRTUAL, owner, name, descriptor, false);
+ } else {
+ continuation.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
+ }
+ })
+ .transform();
}
}
diff --git a/src/test/java/com/android/tools/r8/resolution/SingleTargetExecutionTest.java b/src/test/java/com/android/tools/r8/resolution/SingleTargetExecutionTest.java
index cf2be59..ef0e967 100644
--- a/src/test/java/com/android/tools/r8/resolution/SingleTargetExecutionTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/SingleTargetExecutionTest.java
@@ -4,10 +4,8 @@
package com.android.tools.r8.resolution;
import com.android.tools.r8.AsmTestBase;
-import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.ToolHelper.DexVm.Version;
-import com.android.tools.r8.VmTestRunner;
-import com.android.tools.r8.VmTestRunner.IgnoreIfVmOlderOrEqualThan;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
import com.android.tools.r8.resolution.singletarget.Main;
import com.android.tools.r8.resolution.singletarget.one.AbstractSubClass;
import com.android.tools.r8.resolution.singletarget.one.AbstractTopClass;
@@ -16,40 +14,94 @@
import com.android.tools.r8.resolution.singletarget.one.SubSubClassOne;
import com.android.tools.r8.resolution.singletarget.one.SubSubClassThree;
import com.android.tools.r8.resolution.singletarget.one.SubSubClassTwo;
+import com.android.tools.r8.utils.StringUtils;
import com.google.common.collect.ImmutableList;
-import java.util.ArrayList;
import java.util.List;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
-@RunWith(VmTestRunner.class)
+@RunWith(Parameterized.class)
public class SingleTargetExecutionTest extends AsmTestBase {
- public static List<Class> CLASSES = ImmutableList.of(
- InterfaceWithDefault.class,
- AbstractTopClass.class,
- AbstractSubClass.class,
- SubSubClassOne.class,
- SubSubClassTwo.class,
- SubSubClassThree.class,
- Main.class
- );
+ public static List<Class<?>> CLASSES =
+ ImmutableList.of(
+ InterfaceWithDefault.class,
+ AbstractTopClass.class,
+ AbstractSubClass.class,
+ SubSubClassOne.class,
+ SubSubClassTwo.class,
+ SubSubClassThree.class,
+ Main.class);
public static List<byte[]> ASM_CLASSES = ImmutableList.of(
getBytesFromAsmClass(IrrelevantInterfaceWithDefaultDump::dump)
);
+ public static final String EXPECTED =
+ StringUtils.lines(
+ "SubSubClassOne",
+ "SubSubClassOne",
+ "AbstractTopClass",
+ "SubSubClassOne",
+ "AbstractTopClass",
+ "com.android.tools.r8.resolution.singletarget.one.AbstractSubClass",
+ "InterfaceWithDefault",
+ "InterfaceWithDefault",
+ "ICCE",
+ "com.android.tools.r8.resolution.singletarget.one.SubSubClassTwo",
+ "com.android.tools.r8.resolution.singletarget.one.SubSubClassTwo",
+ "AbstractTopClass",
+ "com.android.tools.r8.resolution.singletarget.one.SubSubClassTwo",
+ "AbstractTopClass",
+ "com.android.tools.r8.resolution.singletarget.one.AbstractSubClass",
+ "InterfaceWithDefault",
+ "com.android.tools.r8.resolution.singletarget.one.SubSubClassTwo",
+ "InterfaceWithDefault",
+ "InterfaceWithDefault",
+ "InterfaceWithDefault",
+ "ICCE",
+ "InterfaceWithDefault",
+ "com.android.tools.r8.resolution.singletarget.one.SubSubClassTwo",
+ "InterfaceWithDefault",
+ "InterfaceWithDefault",
+ "com.android.tools.r8.resolution.singletarget.one.SubSubClassTwo",
+ "InterfaceWithDefault",
+ "InterfaceWithDefault",
+ "InterfaceWithDefault",
+ "ICCE");
+
+ public final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public SingleTargetExecutionTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
@Test
- // TODO(b/72208584) The desugared version of this test masks ICCE.
- @IgnoreIfVmOlderOrEqualThan(Version.V7_0_0)
- public void runSingleTargetTest() throws Exception {
- List<byte[]> allBytes = new ArrayList<>();
- allBytes.addAll(ASM_CLASSES);
- for (Class clazz : CLASSES) {
- allBytes.add(ToolHelper.getClassAsBytes(clazz));
- }
- ensureSameOutput(Main.class.getCanonicalName(),
- ToolHelper.getMinApiLevelForDexVm(),
- allBytes.toArray(new byte[allBytes.size()][]));
+ public void testReference() throws Exception {
+ testForRuntime(parameters)
+ .addProgramClasses(CLASSES)
+ .addProgramClassFileData(ASM_CLASSES)
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutput(EXPECTED);
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .noMinification()
+ .addProgramClasses(CLASSES)
+ .addProgramClassFileData(ASM_CLASSES)
+ .addKeepMainRule(Main.class)
+ .setMinApi(parameters.getApiLevel())
+ .run(parameters.getRuntime(), Main.class)
+ // TODO(b/144085169) R8 masks ICCE.
+ .assertSuccessWithOutput(EXPECTED.replace("ICCE", "InterfaceWithDefault"));
}
}
diff --git a/src/test/java/com/android/tools/r8/resolution/SingleTargetLookupTest.java b/src/test/java/com/android/tools/r8/resolution/SingleTargetLookupTest.java
index f91f1bd..a2df4aa 100644
--- a/src/test/java/com/android/tools/r8/resolution/SingleTargetLookupTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/SingleTargetLookupTest.java
@@ -21,7 +21,6 @@
import com.android.tools.r8.resolution.singletarget.one.AbstractSubClass;
import com.android.tools.r8.resolution.singletarget.one.AbstractTopClass;
import com.android.tools.r8.resolution.singletarget.one.InterfaceWithDefault;
-import com.android.tools.r8.resolution.singletarget.one.IrrelevantInterfaceWithDefault;
import com.android.tools.r8.resolution.singletarget.one.IrrelevantInterfaceWithDefaultDump;
import com.android.tools.r8.resolution.singletarget.one.SubSubClassOne;
import com.android.tools.r8.resolution.singletarget.one.SubSubClassThree;
@@ -233,13 +232,7 @@
manyTargets(
"overriddenInOtherInterface",
AbstractTopClass.class,
- InterfaceWithDefault.class,
- IrrelevantInterfaceWithDefault.class),
- manyTargets(
- "overriddenInOtherInterface",
- SubSubClassOne.class,
- InterfaceWithDefault.class,
- IrrelevantInterfaceWithDefault.class),
+ InterfaceWithDefault.class),
manyTargets(
"abstractMethod",
ThirdAbstractTopClass.class,
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/AbstractAllTest.java b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/AbstractAllTest.java
index 63ef57f..c9a1c5e 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/AbstractAllTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/AbstractAllTest.java
@@ -48,11 +48,10 @@
buildClasses(CLASSES, Collections.emptyList()).build(), Main.class);
DexMethod method = SingleTargetLookupTest.buildMethod(B.class, "f", appInfo);
ResolutionResult resolutionResult = appInfo.resolveMethod(method.holder, method);
- List<DexEncodedMethod> resolutionTargets = resolutionResult.asListOfTargets();
- assertEquals(1, resolutionTargets.size());
+ DexEncodedMethod resolutionTarget = resolutionResult.getSingleTarget();
// Currently R8 will resolve to L::f as that is the first in the topological search.
// Resolution may return any of the matches, so it is valid if this expectation changes.
- assertEquals(L.class.getTypeName(), resolutionTargets.get(0).method.holder.toSourceString());
+ assertEquals(L.class.getTypeName(), resolutionTarget.method.holder.toSourceString());
}
@Test
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultLeftAbstractRightTest.java b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultLeftAbstractRightTest.java
index b1afb50..075e399 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultLeftAbstractRightTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultLeftAbstractRightTest.java
@@ -10,24 +10,17 @@
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
import com.android.tools.r8.TestRuntime;
-import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.ResolutionResult;
import com.android.tools.r8.resolution.SingleTargetLookupTest;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.utils.DescriptorUtils;
import com.google.common.collect.ImmutableList;
import java.util.Collection;
import java.util.Collections;
-import java.util.List;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
-import org.objectweb.asm.ClassWriter;
-import org.objectweb.asm.MethodVisitor;
-import org.objectweb.asm.Opcodes;
-import org.objectweb.asm.util.ASMifier;
@RunWith(Parameterized.class)
public class DefaultLeftAbstractRightTest extends TestBase {
@@ -53,21 +46,20 @@
AppInfoWithLiveness appInfo =
SingleTargetLookupTest.createAppInfoWithLiveness(
buildClasses(CLASSES, Collections.emptyList())
- .addClassProgramData(Collections.singletonList(DumpB.dump()))
+ .addClassProgramData(Collections.singletonList(transformB()))
.build(),
Main.class);
DexMethod method = SingleTargetLookupTest.buildMethod(B.class, "f", appInfo);
ResolutionResult resolutionResult = appInfo.resolveMethod(method.holder, method);
- List<DexEncodedMethod> resolutionTargets = resolutionResult.asListOfTargets();
- assertEquals(1, resolutionTargets.size());
- assertEquals(L.class.getTypeName(), resolutionTargets.get(0).method.holder.toSourceString());
+ DexEncodedMethod resolutionTarget = resolutionResult.getSingleTarget();
+ assertEquals(L.class.getTypeName(), resolutionTarget.method.holder.toSourceString());
}
@Test
public void testReference() throws Exception {
testForRuntime(parameters)
.addProgramClasses(CLASSES)
- .addProgramClassFileData(DumpB.dump())
+ .addProgramClassFileData(transformB())
.run(parameters.getRuntime(), Main.class)
.assertSuccessWithOutputLines("L::f");
}
@@ -76,7 +68,7 @@
public void testR8() throws Exception {
testForR8(parameters.getBackend())
.addProgramClasses(CLASSES)
- .addProgramClassFileData(DumpB.dump())
+ .addProgramClassFileData(transformB())
.addKeepMainRule(Main.class)
.setMinApi(parameters.getApiLevel())
.run(parameters.getRuntime(), Main.class)
@@ -109,42 +101,7 @@
}
}
- static class DumpB implements Opcodes {
-
- public static void main(String[] args) throws Exception {
- ASMifier.main(
- new String[] {"-debug", ToolHelper.getClassFileForTestClass(B.class).toString()});
- }
-
- public static byte[] dump() throws Exception {
-
- ClassWriter classWriter = new ClassWriter(0);
- MethodVisitor methodVisitor;
-
- classWriter.visit(
- V1_8,
- ACC_PUBLIC | ACC_SUPER,
- DescriptorUtils.getBinaryNameFromJavaType(B.class.getTypeName()),
- null,
- "java/lang/Object",
- new String[] {
- DescriptorUtils.getBinaryNameFromJavaType(L.class.getTypeName()),
- // Manually added 'implements R'.
- DescriptorUtils.getBinaryNameFromJavaType(R.class.getTypeName())
- });
-
- {
- methodVisitor = classWriter.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
- methodVisitor.visitCode();
- methodVisitor.visitVarInsn(ALOAD, 0);
- methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
- methodVisitor.visitInsn(RETURN);
- methodVisitor.visitMaxs(1, 1);
- methodVisitor.visitEnd();
- }
- classWriter.visitEnd();
-
- return classWriter.toByteArray();
- }
+ private static byte[] transformB() throws Exception {
+ return transformer(B.class).setImplements(L.class, R.class).transform();
}
}
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultRightAbstractLeftTest.java b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultRightAbstractLeftTest.java
index e889a05..58d0891 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultRightAbstractLeftTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultRightAbstractLeftTest.java
@@ -10,24 +10,17 @@
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
import com.android.tools.r8.TestRuntime;
-import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.ResolutionResult;
import com.android.tools.r8.resolution.SingleTargetLookupTest;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.utils.DescriptorUtils;
import com.google.common.collect.ImmutableList;
import java.util.Collection;
import java.util.Collections;
-import java.util.List;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
-import org.objectweb.asm.ClassWriter;
-import org.objectweb.asm.MethodVisitor;
-import org.objectweb.asm.Opcodes;
-import org.objectweb.asm.util.ASMifier;
@RunWith(Parameterized.class)
public class DefaultRightAbstractLeftTest extends TestBase {
@@ -53,21 +46,20 @@
AppInfoWithLiveness appInfo =
SingleTargetLookupTest.createAppInfoWithLiveness(
buildClasses(CLASSES, Collections.emptyList())
- .addClassProgramData(Collections.singletonList(DumpB.dump()))
+ .addClassProgramData(Collections.singletonList(transformB()))
.build(),
Main.class);
DexMethod method = SingleTargetLookupTest.buildMethod(B.class, "f", appInfo);
ResolutionResult resolutionResult = appInfo.resolveMethod(method.holder, method);
- List<DexEncodedMethod> resolutionTargets = resolutionResult.asListOfTargets();
- assertEquals(1, resolutionTargets.size());
- assertEquals(R.class.getTypeName(), resolutionTargets.get(0).method.holder.toSourceString());
+ DexEncodedMethod resolutionTarget = resolutionResult.getSingleTarget();
+ assertEquals(R.class.getTypeName(), resolutionTarget.method.holder.toSourceString());
}
@Test
public void testReference() throws Exception {
testForRuntime(parameters.getRuntime(), parameters.getApiLevel())
.addProgramClasses(CLASSES)
- .addProgramClassFileData(DumpB.dump())
+ .addProgramClassFileData(transformB())
.run(parameters.getRuntime(), Main.class)
.assertSuccessWithOutputLines("R::f");
}
@@ -76,7 +68,7 @@
public void testR8() throws Exception {
testForR8(parameters.getBackend())
.addProgramClasses(CLASSES)
- .addProgramClassFileData(DumpB.dump())
+ .addProgramClassFileData(transformB())
.addKeepMainRule(Main.class)
.setMinApi(parameters.getApiLevel())
.run(parameters.getRuntime(), Main.class)
@@ -109,42 +101,7 @@
}
}
- static class DumpB implements Opcodes {
-
- public static void main(String[] args) throws Exception {
- ASMifier.main(
- new String[] {"-debug", ToolHelper.getClassFileForTestClass(B.class).toString()});
- }
-
- public static byte[] dump() throws Exception {
-
- ClassWriter classWriter = new ClassWriter(0);
- MethodVisitor methodVisitor;
-
- classWriter.visit(
- V1_8,
- ACC_PUBLIC | ACC_SUPER,
- DescriptorUtils.getBinaryNameFromJavaType(B.class.getTypeName()),
- null,
- "java/lang/Object",
- new String[] {
- // Manually added 'implements L'.
- DescriptorUtils.getBinaryNameFromJavaType(L.class.getTypeName()),
- DescriptorUtils.getBinaryNameFromJavaType(R.class.getTypeName())
- });
-
- {
- methodVisitor = classWriter.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
- methodVisitor.visitCode();
- methodVisitor.visitVarInsn(ALOAD, 0);
- methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
- methodVisitor.visitInsn(RETURN);
- methodVisitor.visitMaxs(1, 1);
- methodVisitor.visitEnd();
- }
- classWriter.visitEnd();
-
- return classWriter.toByteArray();
- }
+ private static byte[] transformB() throws Exception {
+ return transformer(B.class).setImplements(L.class, R.class).transform();
}
}
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAbstractLeftTest.java b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAbstractLeftTest.java
index 38e564f..f5a99fa 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAbstractLeftTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAbstractLeftTest.java
@@ -11,7 +11,6 @@
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
import com.android.tools.r8.TestRuntime;
-import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.ToolHelper.DexVm.Version;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexMethod;
@@ -19,19 +18,13 @@
import com.android.tools.r8.resolution.SingleTargetLookupTest;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.utils.AndroidApiLevel;
-import com.android.tools.r8.utils.DescriptorUtils;
import com.google.common.collect.ImmutableList;
import java.util.Collection;
import java.util.Collections;
-import java.util.List;
import org.hamcrest.Matcher;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
-import org.objectweb.asm.ClassWriter;
-import org.objectweb.asm.MethodVisitor;
-import org.objectweb.asm.Opcodes;
-import org.objectweb.asm.util.ASMifier;
@RunWith(Parameterized.class)
public class DefaultTopAbstractLeftTest extends TestBase {
@@ -57,21 +50,20 @@
AppInfoWithLiveness appInfo =
SingleTargetLookupTest.createAppInfoWithLiveness(
buildClasses(CLASSES, Collections.emptyList())
- .addClassProgramData(Collections.singletonList(DumpB.dump()))
+ .addClassProgramData(Collections.singletonList(transformB()))
.build(),
Main.class);
DexMethod method = SingleTargetLookupTest.buildMethod(B.class, "f", appInfo);
ResolutionResult resolutionResult = appInfo.resolveMethod(method.holder, method);
- List<DexEncodedMethod> resolutionTargets = resolutionResult.asListOfTargets();
- assertEquals(1, resolutionTargets.size());
- assertEquals(L.class.getTypeName(), resolutionTargets.get(0).method.holder.toSourceString());
+ DexEncodedMethod resolutionTarget = resolutionResult.getSingleTarget();
+ assertEquals(L.class.getTypeName(), resolutionTarget.method.holder.toSourceString());
}
@Test
public void testReference() throws Exception {
testForRuntime(parameters)
.addProgramClasses(CLASSES)
- .addProgramClassFileData(DumpB.dump())
+ .addProgramClassFileData(transformB())
.run(parameters.getRuntime(), Main.class)
.assertFailureWithErrorThatMatches(getExpectedErrorMatcher(false));
}
@@ -80,7 +72,7 @@
public void testR8() throws Exception {
testForR8(parameters.getBackend())
.addProgramClasses(CLASSES)
- .addProgramClassFileData(DumpB.dump())
+ .addProgramClassFileData(transformB())
.addKeepMainRule(Main.class)
.setMinApi(parameters.getApiLevel())
.run(parameters.getRuntime(), Main.class)
@@ -130,42 +122,7 @@
}
}
- static class DumpB implements Opcodes {
-
- public static void main(String[] args) throws Exception {
- ASMifier.main(
- new String[] {"-debug", ToolHelper.getClassFileForTestClass(B.class).toString()});
- }
-
- public static byte[] dump() throws Exception {
-
- ClassWriter classWriter = new ClassWriter(0);
- MethodVisitor methodVisitor;
-
- classWriter.visit(
- V1_8,
- ACC_PUBLIC | ACC_SUPER,
- DescriptorUtils.getBinaryNameFromJavaType(B.class.getTypeName()),
- null,
- "java/lang/Object",
- new String[] {
- // Manually added 'implements L'.
- DescriptorUtils.getBinaryNameFromJavaType(L.class.getTypeName()),
- DescriptorUtils.getBinaryNameFromJavaType(R.class.getTypeName())
- });
-
- {
- methodVisitor = classWriter.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
- methodVisitor.visitCode();
- methodVisitor.visitVarInsn(ALOAD, 0);
- methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
- methodVisitor.visitInsn(RETURN);
- methodVisitor.visitMaxs(1, 1);
- methodVisitor.visitEnd();
- }
- classWriter.visitEnd();
-
- return classWriter.toByteArray();
- }
+ private static byte[] transformB() throws Exception {
+ return transformer(B.class).setImplements(L.class, R.class).transform();
}
}
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAbstractRightTest.java b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAbstractRightTest.java
index 1601394..bd2d4ac 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAbstractRightTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAbstractRightTest.java
@@ -11,7 +11,6 @@
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
import com.android.tools.r8.TestRuntime;
-import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.ToolHelper.DexVm.Version;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexMethod;
@@ -19,19 +18,13 @@
import com.android.tools.r8.resolution.SingleTargetLookupTest;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.utils.AndroidApiLevel;
-import com.android.tools.r8.utils.DescriptorUtils;
import com.google.common.collect.ImmutableList;
import java.util.Collection;
import java.util.Collections;
-import java.util.List;
import org.hamcrest.Matcher;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
-import org.objectweb.asm.ClassWriter;
-import org.objectweb.asm.MethodVisitor;
-import org.objectweb.asm.Opcodes;
-import org.objectweb.asm.util.ASMifier;
@RunWith(Parameterized.class)
public class DefaultTopAbstractRightTest extends TestBase {
@@ -57,21 +50,20 @@
AppInfoWithLiveness appInfo =
SingleTargetLookupTest.createAppInfoWithLiveness(
buildClasses(CLASSES, Collections.emptyList())
- .addClassProgramData(Collections.singletonList(DumpB.dump()))
+ .addClassProgramData(Collections.singletonList(transformB()))
.build(),
Main.class);
DexMethod method = SingleTargetLookupTest.buildMethod(B.class, "f", appInfo);
ResolutionResult resolutionResult = appInfo.resolveMethod(method.holder, method);
- List<DexEncodedMethod> resolutionTargets = resolutionResult.asListOfTargets();
- assertEquals(1, resolutionTargets.size());
- assertEquals(R.class.getTypeName(), resolutionTargets.get(0).method.holder.toSourceString());
+ DexEncodedMethod resolutionTarget = resolutionResult.getSingleTarget();
+ assertEquals(R.class.getTypeName(), resolutionTarget.method.holder.toSourceString());
}
@Test
public void testReference() throws Exception {
testForRuntime(parameters)
.addProgramClasses(CLASSES)
- .addProgramClassFileData(DumpB.dump())
+ .addProgramClassFileData(transformB())
.run(parameters.getRuntime(), Main.class)
.assertFailureWithErrorThatMatches(getExpectedErrorMatcher(false));
}
@@ -80,7 +72,7 @@
public void testR8() throws Exception {
testForR8(parameters.getBackend())
.addProgramClasses(CLASSES)
- .addProgramClassFileData(DumpB.dump())
+ .addProgramClassFileData(transformB())
.addKeepMainRule(Main.class)
.setMinApi(parameters.getApiLevel())
.run(parameters.getRuntime(), Main.class)
@@ -124,48 +116,13 @@
// Intentionally empty.
}
+ private static byte[] transformB() throws Exception {
+ return transformer(B.class).setImplements(L.class, R.class).transform();
+ }
+
static class Main {
public static void main(String[] args) {
new B().f();
}
}
-
- static class DumpB implements Opcodes {
-
- public static void main(String[] args) throws Exception {
- ASMifier.main(
- new String[] {"-debug", ToolHelper.getClassFileForTestClass(B.class).toString()});
- }
-
- public static byte[] dump() {
-
- ClassWriter classWriter = new ClassWriter(0);
- MethodVisitor methodVisitor;
-
- classWriter.visit(
- V1_8,
- ACC_PUBLIC | ACC_SUPER,
- DescriptorUtils.getBinaryNameFromJavaType(B.class.getTypeName()),
- null,
- "java/lang/Object",
- new String[] {
- DescriptorUtils.getBinaryNameFromJavaType(L.class.getTypeName()),
- // Manually added 'implements R'.
- DescriptorUtils.getBinaryNameFromJavaType(R.class.getTypeName())
- });
-
- {
- methodVisitor = classWriter.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
- methodVisitor.visitCode();
- methodVisitor.visitVarInsn(ALOAD, 0);
- methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
- methodVisitor.visitInsn(RETURN);
- methodVisitor.visitMaxs(1, 1);
- methodVisitor.visitEnd();
- }
- classWriter.visitEnd();
-
- return classWriter.toByteArray();
- }
- }
}
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAndBothTest.java b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAndBothTest.java
index c827786..af19bf1 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAndBothTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAndBothTest.java
@@ -5,34 +5,26 @@
import static org.hamcrest.CoreMatchers.containsString;
import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
import static org.junit.Assume.assumeTrue;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
import com.android.tools.r8.TestRuntime;
-import com.android.tools.r8.TestRuntime.CfVm;
-import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.ToolHelper.DexVm.Version;
-import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.ResolutionResult;
import com.android.tools.r8.resolution.SingleTargetLookupTest;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.utils.AndroidApiLevel;
-import com.android.tools.r8.utils.DescriptorUtils;
import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
import java.util.Collections;
+import java.util.HashSet;
import java.util.List;
-import org.hamcrest.Matcher;
+import java.util.Set;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
-import org.objectweb.asm.ClassWriter;
-import org.objectweb.asm.MethodVisitor;
-import org.objectweb.asm.Opcodes;
-import org.objectweb.asm.util.ASMifier;
@RunWith(Parameterized.class)
public class DefaultTopAndBothTest extends TestBase {
@@ -57,69 +49,38 @@
AppInfoWithLiveness appInfo =
SingleTargetLookupTest.createAppInfoWithLiveness(
buildClasses(CLASSES, Collections.emptyList())
- .addClassProgramData(Collections.singletonList(DumpB.dump()))
+ .addClassProgramData(Collections.singletonList(transformB()))
.build(),
Main.class);
DexMethod method = SingleTargetLookupTest.buildMethod(B.class, "f", appInfo);
ResolutionResult resolutionResult = appInfo.resolveMethod(method.holder, method);
- List<DexEncodedMethod> resolutionTargets = resolutionResult.asListOfTargets();
- assertEquals(2, resolutionTargets.size());
- assertTrue(
- resolutionTargets.stream()
- .anyMatch(m -> m.method.holder.toSourceString().equals(L.class.getTypeName())));
- assertTrue(
- resolutionTargets.stream()
- .anyMatch(m -> m.method.holder.toSourceString().equals(R.class.getTypeName())));
+ Set<String> holders = new HashSet<>();
+ resolutionResult
+ .asFailedResolution()
+ .forEachFailureDependency(
+ clazz -> fail("Unexpected class dependency"),
+ target -> holders.add(target.method.holder.toSourceString()));
+ assertEquals(ImmutableSet.of(L.class.getTypeName(), R.class.getTypeName()), holders);
}
@Test
public void testReference() throws Exception {
testForRuntime(parameters)
.addProgramClasses(CLASSES)
- .addProgramClassFileData(DumpB.dump())
+ .addProgramClassFileData(transformB())
.run(parameters.getRuntime(), Main.class)
- .assertFailureWithErrorThatMatches(getExpectedError(false));
+ .assertFailureWithErrorThatMatches(containsString("IncompatibleClassChangeError"));
}
@Test
public void testR8() throws Exception {
testForR8(parameters.getBackend())
.addProgramClasses(CLASSES)
- .addProgramClassFileData(DumpB.dump())
+ .addProgramClassFileData(transformB())
.addKeepMainRule(Main.class)
.setMinApi(parameters.getApiLevel())
.run(parameters.getRuntime(), Main.class)
- .assertFailureWithErrorThatMatches(getExpectedError(true));
- }
-
- private boolean isDesugaringDefaultInterfaceMethods() {
- return parameters.getApiLevel() != null
- && parameters.getApiLevel().getLevel() < AndroidApiLevel.N.getLevel();
- }
-
- private Matcher<String> getExpectedError(boolean isR8) {
- // TODO(b/144085169): JDK 11 execution produces a different error condition on the R8 output?
- if (isR8
- && parameters.getRuntime().isCf()
- && parameters.getRuntime().asCf().getVm() == CfVm.JDK11) {
- return containsString("AbstractMethodError");
- }
- // TODO(b/72208584): Default interface method desugaring changes error behavior.
- if (isDesugaringDefaultInterfaceMethods()) {
- if (isR8) {
- // TODO(b/144085169): Maybe R8 introduces another error due to removal of targets?
- return containsString("AbstractMethodError");
- }
- // Dalvik fails with a verify error instead of the runtime failure (unless R8 removed the
- // methods as indicated by the above.
- if (parameters.getRuntime().asDex().getVm().getVersion().isOlderThanOrEqual(Version.V4_4_4)) {
- return containsString("VerifyError");
- }
- return containsString("AbstractMethodError");
- }
- // Reference result should be an incompatible class change error due to the two non-abstract
- // methods in the maximally specific set.
- return containsString("IncompatibleClassChangeError");
+ .assertFailureWithErrorThatMatches(containsString("IncompatibleClassChangeError"));
}
public interface T {
@@ -154,42 +115,7 @@
}
}
- static class DumpB implements Opcodes {
-
- public static void main(String[] args) throws Exception {
- ASMifier.main(
- new String[] {"-debug", ToolHelper.getClassFileForTestClass(B.class).toString()});
- }
-
- public static byte[] dump() {
-
- ClassWriter classWriter = new ClassWriter(0);
- MethodVisitor methodVisitor;
-
- classWriter.visit(
- V1_8,
- ACC_PUBLIC | ACC_SUPER,
- DescriptorUtils.getBinaryNameFromJavaType(B.class.getTypeName()),
- null,
- "java/lang/Object",
- new String[] {
- DescriptorUtils.getBinaryNameFromJavaType(L.class.getTypeName()),
- // Manually added 'implements R'.
- DescriptorUtils.getBinaryNameFromJavaType(R.class.getTypeName())
- });
-
- {
- methodVisitor = classWriter.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
- methodVisitor.visitCode();
- methodVisitor.visitVarInsn(ALOAD, 0);
- methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
- methodVisitor.visitInsn(RETURN);
- methodVisitor.visitMaxs(1, 1);
- methodVisitor.visitEnd();
- }
- classWriter.visitEnd();
-
- return classWriter.toByteArray();
- }
+ public static byte[] transformB() throws Exception {
+ return transformer(B.class).setImplements(L.class, R.class).transform();
}
}
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAndLeftTest.java b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAndLeftTest.java
index a860e90..7ca9525 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAndLeftTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAndLeftTest.java
@@ -48,9 +48,8 @@
buildClasses(CLASSES, Collections.emptyList()).build(), Main.class);
DexMethod method = SingleTargetLookupTest.buildMethod(B.class, "f", appInfo);
ResolutionResult resolutionResult = appInfo.resolveMethod(method.holder, method);
- List<DexEncodedMethod> resolutionTargets = resolutionResult.asListOfTargets();
- assertEquals(1, resolutionTargets.size());
- assertEquals(L.class.getTypeName(), resolutionTargets.get(0).method.holder.toSourceString());
+ DexEncodedMethod resolutionTarget = resolutionResult.getSingleTarget();
+ assertEquals(L.class.getTypeName(), resolutionTarget.method.holder.toSourceString());
}
@Test
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAndRightTest.java b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAndRightTest.java
index 8e9b843..1cbd668 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAndRightTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAndRightTest.java
@@ -48,9 +48,8 @@
buildClasses(CLASSES, Collections.emptyList()).build(), Main.class);
DexMethod method = SingleTargetLookupTest.buildMethod(B.class, "f", appInfo);
ResolutionResult resolutionResult = appInfo.resolveMethod(method.holder, method);
- List<DexEncodedMethod> resolutionTargets = resolutionResult.asListOfTargets();
- assertEquals(1, resolutionTargets.size());
- assertEquals(R.class.getTypeName(), resolutionTargets.get(0).method.holder.toSourceString());
+ DexEncodedMethod resolutionTarget = resolutionResult.getSingleTarget();
+ assertEquals(R.class.getTypeName(), resolutionTarget.method.holder.toSourceString());
}
@Test
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/TwoDefaultMethodsWithoutTopTest.java b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/TwoDefaultMethodsWithoutTopTest.java
index ae678cd..13de4d4 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/TwoDefaultMethodsWithoutTopTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/TwoDefaultMethodsWithoutTopTest.java
@@ -5,7 +5,7 @@
import static org.hamcrest.CoreMatchers.containsString;
import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
import static org.junit.Assume.assumeTrue;
import com.android.tools.r8.TestBase;
@@ -13,25 +13,19 @@
import com.android.tools.r8.TestParametersCollection;
import com.android.tools.r8.TestRunResult;
import com.android.tools.r8.TestRuntime;
-import com.android.tools.r8.TestRuntime.CfVm;
-import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.ResolutionResult;
import com.android.tools.r8.resolution.SingleTargetLookupTest;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.utils.AndroidApiLevel;
-import com.android.tools.r8.utils.DescriptorUtils;
import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
import java.util.Collections;
+import java.util.HashSet;
import java.util.List;
+import java.util.Set;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
-import org.objectweb.asm.ClassWriter;
-import org.objectweb.asm.MethodVisitor;
-import org.objectweb.asm.Opcodes;
-import org.objectweb.asm.util.ASMifier;
@RunWith(Parameterized.class)
public class TwoDefaultMethodsWithoutTopTest extends TestBase {
@@ -57,56 +51,44 @@
AppInfoWithLiveness appInfo =
SingleTargetLookupTest.createAppInfoWithLiveness(
buildClasses(CLASSES, Collections.emptyList())
- .addClassProgramData(Collections.singletonList(DumpB.dump()))
+ .addClassProgramData(Collections.singletonList(transformB()))
.build(),
Main.class);
DexMethod method = SingleTargetLookupTest.buildMethod(B.class, "f", appInfo);
ResolutionResult resolutionResult = appInfo.resolveMethod(method.holder, method);
- List<DexEncodedMethod> resolutionTargets = resolutionResult.asListOfTargets();
- assertEquals(2, resolutionTargets.size());
- assertTrue(
- resolutionTargets.stream()
- .anyMatch(m -> m.method.holder.toSourceString().equals(I.class.getTypeName())));
- assertTrue(
- resolutionTargets.stream()
- .anyMatch(m -> m.method.holder.toSourceString().equals(J.class.getTypeName())));
+ Set<String> holders = new HashSet<>();
+ resolutionResult
+ .asFailedResolution()
+ .forEachFailureDependency(
+ clazz -> fail("Unexpected class dependency"),
+ m -> holders.add(m.method.holder.toSourceString()));
+ assertEquals(ImmutableSet.of(I.class.getTypeName(), J.class.getTypeName()), holders);
}
@Test
public void testReference() throws Exception {
testForRuntime(parameters)
.addProgramClasses(CLASSES)
- .addProgramClassFileData(DumpB.dump())
+ .addProgramClassFileData(transformB())
.run(parameters.getRuntime(), Main.class)
- .apply(r -> checkResult(r, false));
+ .assertFailureWithErrorThatMatches(containsString("IncompatibleClassChangeError"));
}
@Test
public void testR8() throws Exception {
testForR8(parameters.getBackend())
.addProgramClasses(CLASSES)
- .addProgramClassFileData(DumpB.dump())
+ .addProgramClassFileData(transformB())
.addKeepMainRule(Main.class)
.setMinApi(parameters.getApiLevel())
.run(parameters.getRuntime(), Main.class)
- .apply(r -> checkResult(r, true));
+ .apply(r -> checkResultR8(r));
}
- private void checkResult(TestRunResult<?> runResult, boolean isR8) {
- // TODO(b/144085169): JDK 11 execution produces a different error condition on the R8 output?
- if (isR8
- && parameters.getRuntime().isCf()
- && parameters.getRuntime().asCf().getVm() == CfVm.JDK11) {
- runResult.assertFailureWithErrorThatMatches(containsString("AbstractMethodError"));
- } else if (parameters.isDexRuntime()
- && parameters.getApiLevel().isLessThan(AndroidApiLevel.N)) {
- if (isR8) {
- // TODO(b/144085169): Maybe R8 introduces another error due to removal of targets?
- runResult.assertFailureWithErrorThatMatches(containsString("AbstractMethodError"));
- } else {
- // TODO(b/72208584): Desugare changes error result.
- runResult.assertSuccessWithOutputLines("I::f");
- }
+ private void checkResultR8(TestRunResult<?> runResult) {
+ // TODO(b/144085169): R8/CF produces incorrect result.
+ if (parameters.getRuntime().isCf()) {
+ runResult.assertFailureWithErrorThatMatches(containsString("NullPointerException"));
} else {
runResult.assertFailureWithErrorThatMatches(containsString("IncompatibleClassChangeError"));
}
@@ -136,46 +118,7 @@
}
}
- private static class DumpB implements Opcodes {
-
- public static void main(String[] args) throws Exception {
- ASMifier.main(
- new String[] {"-debug", ToolHelper.getClassFileForTestClass(B.class).toString()});
- }
-
- public static byte[] dump() {
-
- ClassWriter classWriter = new ClassWriter(0);
- MethodVisitor methodVisitor;
-
- classWriter.visit(
- V1_8,
- ACC_PUBLIC | ACC_SUPER,
- DescriptorUtils.getBinaryNameFromJavaType(B.class.getTypeName()),
- null,
- DescriptorUtils.getBinaryNameFromJavaType(A.class.getTypeName()),
- new String[] {
- // Manually added 'implements J'.
- DescriptorUtils.getBinaryNameFromJavaType(J.class.getTypeName()),
- });
-
- {
- methodVisitor = classWriter.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
- methodVisitor.visitCode();
- methodVisitor.visitVarInsn(ALOAD, 0);
- methodVisitor.visitMethodInsn(
- INVOKESPECIAL,
- DescriptorUtils.getBinaryNameFromJavaType(A.class.getTypeName()),
- "<init>",
- "()V",
- false);
- methodVisitor.visitInsn(RETURN);
- methodVisitor.visitMaxs(1, 1);
- methodVisitor.visitEnd();
- }
- classWriter.visitEnd();
-
- return classWriter.toByteArray();
- }
+ private static byte[] transformB() throws Exception {
+ return transformer(B.class).setImplements(J.class).transform();
}
}
diff --git a/src/test/java/com/android/tools/r8/rewrite/assertions/ChromuimAssertionHookMock.java b/src/test/java/com/android/tools/r8/rewrite/assertions/ChromuimAssertionHookMock.java
index 466d57a..f29c3ca 100644
--- a/src/test/java/com/android/tools/r8/rewrite/assertions/ChromuimAssertionHookMock.java
+++ b/src/test/java/com/android/tools/r8/rewrite/assertions/ChromuimAssertionHookMock.java
@@ -4,105 +4,9 @@
package com.android.tools.r8.rewrite.assertions;
-import org.objectweb.asm.ClassWriter;
-import org.objectweb.asm.Label;
-import org.objectweb.asm.MethodVisitor;
-import org.objectweb.asm.Opcodes;
-
public class ChromuimAssertionHookMock {
public static void assertFailureHandler(AssertionError assertion) {
System.out.println("Got AssertionError " + assertion);
}
}
-/* Below is an asmified dump of the above class */
-
-class ChromuimAssertionHookMockDump implements Opcodes {
-
- public static byte[] dump() {
-
- ClassWriter classWriter = new ClassWriter(0);
- MethodVisitor methodVisitor;
-
- classWriter.visit(
- V1_8,
- ACC_PUBLIC | ACC_SUPER,
- "com/android/tools/r8/rewrite/assertions/ChromuimAssertionHookMock",
- null,
- "java/lang/Object",
- null);
-
- classWriter.visitSource("ChromuimAssertionHookMock.java", null);
-
- {
- methodVisitor = classWriter.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
- methodVisitor.visitCode();
- Label label0 = new Label();
- methodVisitor.visitLabel(label0);
- methodVisitor.visitLineNumber(7, label0);
- methodVisitor.visitVarInsn(ALOAD, 0);
- methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
- methodVisitor.visitInsn(RETURN);
- Label label1 = new Label();
- methodVisitor.visitLabel(label1);
- methodVisitor.visitLocalVariable(
- "this",
- "Lcom/android/tools/r8/rewrite/assertions/ChromuimAssertionHookMock;",
- null,
- label0,
- label1,
- 0);
- methodVisitor.visitMaxs(1, 1);
- methodVisitor.visitEnd();
- }
- {
- methodVisitor =
- classWriter.visitMethod(
- ACC_PUBLIC | ACC_STATIC,
- "assertFailureHandler",
- "(Ljava/lang/AssertionError;)V",
- null,
- null);
- methodVisitor.visitCode();
- Label label0 = new Label();
- methodVisitor.visitLabel(label0);
- methodVisitor.visitLineNumber(9, label0);
- methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
- methodVisitor.visitTypeInsn(NEW, "java/lang/StringBuilder");
- methodVisitor.visitInsn(DUP);
- methodVisitor.visitMethodInsn(
- INVOKESPECIAL, "java/lang/StringBuilder", "<init>", "()V", false);
- methodVisitor.visitLdcInsn("Got AssertionError ");
- methodVisitor.visitMethodInsn(
- INVOKEVIRTUAL,
- "java/lang/StringBuilder",
- "append",
- "(Ljava/lang/String;)Ljava/lang/StringBuilder;",
- false);
- methodVisitor.visitVarInsn(ALOAD, 0);
- methodVisitor.visitMethodInsn(
- INVOKEVIRTUAL,
- "java/lang/StringBuilder",
- "append",
- "(Ljava/lang/Object;)Ljava/lang/StringBuilder;",
- false);
- methodVisitor.visitMethodInsn(
- INVOKEVIRTUAL, "java/lang/StringBuilder", "toString", "()Ljava/lang/String;", false);
- methodVisitor.visitMethodInsn(
- INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
- Label label1 = new Label();
- methodVisitor.visitLabel(label1);
- methodVisitor.visitLineNumber(10, label1);
- methodVisitor.visitInsn(RETURN);
- Label label2 = new Label();
- methodVisitor.visitLabel(label2);
- methodVisitor.visitLocalVariable(
- "assertion", "Ljava/lang/AssertionError;", null, label0, label2, 0);
- methodVisitor.visitMaxs(3, 1);
- methodVisitor.visitEnd();
- }
- classWriter.visitEnd();
-
- return classWriter.toByteArray();
- }
-}
diff --git a/src/test/java/com/android/tools/r8/rewrite/assertions/ClassWithAssertions.java b/src/test/java/com/android/tools/r8/rewrite/assertions/ClassWithAssertions.java
index 17139be..49fe85e 100644
--- a/src/test/java/com/android/tools/r8/rewrite/assertions/ClassWithAssertions.java
+++ b/src/test/java/com/android/tools/r8/rewrite/assertions/ClassWithAssertions.java
@@ -4,13 +4,6 @@
package com.android.tools.r8.rewrite.assertions;
-import org.objectweb.asm.ClassWriter;
-import org.objectweb.asm.FieldVisitor;
-import org.objectweb.asm.Label;
-import org.objectweb.asm.MethodVisitor;
-import org.objectweb.asm.Opcodes;
-import org.objectweb.asm.Type;
-
public class ClassWithAssertions {
int x = 0;
@@ -33,238 +26,3 @@
new ClassWithAssertions(Integer.parseInt(args[0])).getX();
}
}
-
-/* Below is an asmified dump of the above class */
-
-class ClassWithAssertionsDump implements Opcodes {
-
- public static byte[] dump() {
-
- ClassWriter classWriter = new ClassWriter(0);
- FieldVisitor fieldVisitor;
- MethodVisitor methodVisitor;
-
- classWriter.visit(
- V1_8,
- ACC_PUBLIC | ACC_SUPER,
- "com/android/tools/r8/rewrite/assertions/ClassWithAssertions",
- null,
- "java/lang/Object",
- null);
-
- classWriter.visitSource("ClassWithAssertions.java", null);
-
- {
- fieldVisitor = classWriter.visitField(0, "x", "I", null, null);
- fieldVisitor.visitEnd();
- }
- {
- fieldVisitor =
- classWriter.visitField(
- ACC_FINAL | ACC_STATIC | ACC_SYNTHETIC, "$assertionsDisabled", "Z", null, null);
- fieldVisitor.visitEnd();
- }
- {
- methodVisitor = classWriter.visitMethod(0, "<init>", "(I)V", null, null);
- methodVisitor.visitCode();
- Label label0 = new Label();
- methodVisitor.visitLabel(label0);
- methodVisitor.visitLineNumber(10, label0);
- methodVisitor.visitVarInsn(ALOAD, 0);
- methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
- Label label1 = new Label();
- methodVisitor.visitLabel(label1);
- methodVisitor.visitLineNumber(8, label1);
- methodVisitor.visitVarInsn(ALOAD, 0);
- methodVisitor.visitInsn(ICONST_0);
- methodVisitor.visitFieldInsn(
- PUTFIELD, "com/android/tools/r8/rewrite/assertions/ClassWithAssertions", "x", "I");
- Label label2 = new Label();
- methodVisitor.visitLabel(label2);
- methodVisitor.visitLineNumber(11, label2);
- methodVisitor.visitVarInsn(ALOAD, 0);
- methodVisitor.visitVarInsn(ILOAD, 1);
- methodVisitor.visitFieldInsn(
- PUTFIELD, "com/android/tools/r8/rewrite/assertions/ClassWithAssertions", "x", "I");
- Label label3 = new Label();
- methodVisitor.visitLabel(label3);
- methodVisitor.visitLineNumber(12, label3);
- methodVisitor.visitInsn(RETURN);
- Label label4 = new Label();
- methodVisitor.visitLabel(label4);
- methodVisitor.visitLocalVariable(
- "this",
- "Lcom/android/tools/r8/rewrite/assertions/ClassWithAssertions;",
- null,
- label0,
- label4,
- 0);
- methodVisitor.visitLocalVariable("x", "I", null, label0, label4, 1);
- methodVisitor.visitMaxs(2, 2);
- methodVisitor.visitEnd();
- }
- {
- methodVisitor = classWriter.visitMethod(0, "condition", "()Z", null, null);
- methodVisitor.visitCode();
- Label label0 = new Label();
- methodVisitor.visitLabel(label0);
- methodVisitor.visitLineNumber(15, label0);
- methodVisitor.visitVarInsn(ALOAD, 0);
- methodVisitor.visitFieldInsn(
- GETFIELD, "com/android/tools/r8/rewrite/assertions/ClassWithAssertions", "x", "I");
- methodVisitor.visitInsn(ICONST_1);
- Label label1 = new Label();
- methodVisitor.visitJumpInsn(IF_ICMPNE, label1);
- methodVisitor.visitInsn(ICONST_1);
- Label label2 = new Label();
- methodVisitor.visitJumpInsn(GOTO, label2);
- methodVisitor.visitLabel(label1);
- methodVisitor.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
- methodVisitor.visitInsn(ICONST_0);
- methodVisitor.visitLabel(label2);
- methodVisitor.visitFrame(Opcodes.F_SAME1, 0, null, 1, new Object[] {Opcodes.INTEGER});
- methodVisitor.visitInsn(IRETURN);
- Label label3 = new Label();
- methodVisitor.visitLabel(label3);
- methodVisitor.visitLocalVariable(
- "this",
- "Lcom/android/tools/r8/rewrite/assertions/ClassWithAssertions;",
- null,
- label0,
- label3,
- 0);
- methodVisitor.visitMaxs(2, 1);
- methodVisitor.visitEnd();
- }
- {
- methodVisitor = classWriter.visitMethod(0, "getX", "()I", null, null);
- methodVisitor.visitCode();
- Label label0 = new Label();
- methodVisitor.visitLabel(label0);
- methodVisitor.visitLineNumber(19, label0);
- methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
- methodVisitor.visitLdcInsn("1");
- methodVisitor.visitMethodInsn(
- INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
- Label label1 = new Label();
- methodVisitor.visitLabel(label1);
- methodVisitor.visitLineNumber(20, label1);
- methodVisitor.visitFieldInsn(
- GETSTATIC,
- "com/android/tools/r8/rewrite/assertions/ClassWithAssertions",
- "$assertionsDisabled",
- "Z");
- Label label2 = new Label();
- methodVisitor.visitJumpInsn(IFNE, label2);
- methodVisitor.visitVarInsn(ALOAD, 0);
- methodVisitor.visitMethodInsn(
- INVOKEVIRTUAL,
- "com/android/tools/r8/rewrite/assertions/ClassWithAssertions",
- "condition",
- "()Z",
- false);
- methodVisitor.visitJumpInsn(IFNE, label2);
- methodVisitor.visitTypeInsn(NEW, "java/lang/AssertionError");
- methodVisitor.visitInsn(DUP);
- methodVisitor.visitMethodInsn(
- INVOKESPECIAL, "java/lang/AssertionError", "<init>", "()V", false);
- methodVisitor.visitInsn(ATHROW);
- methodVisitor.visitLabel(label2);
- methodVisitor.visitLineNumber(21, label2);
- methodVisitor.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
- methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
- methodVisitor.visitLdcInsn("2");
- methodVisitor.visitMethodInsn(
- INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
- Label label3 = new Label();
- methodVisitor.visitLabel(label3);
- methodVisitor.visitLineNumber(22, label3);
- methodVisitor.visitVarInsn(ALOAD, 0);
- methodVisitor.visitFieldInsn(
- GETFIELD, "com/android/tools/r8/rewrite/assertions/ClassWithAssertions", "x", "I");
- methodVisitor.visitInsn(IRETURN);
- Label label4 = new Label();
- methodVisitor.visitLabel(label4);
- methodVisitor.visitLocalVariable(
- "this",
- "Lcom/android/tools/r8/rewrite/assertions/ClassWithAssertions;",
- null,
- label0,
- label4,
- 0);
- methodVisitor.visitMaxs(2, 1);
- methodVisitor.visitEnd();
- }
- {
- methodVisitor =
- classWriter.visitMethod(
- ACC_PUBLIC | ACC_STATIC, "main", "([Ljava/lang/String;)V", null, null);
- methodVisitor.visitCode();
- Label label0 = new Label();
- methodVisitor.visitLabel(label0);
- methodVisitor.visitLineNumber(26, label0);
- methodVisitor.visitTypeInsn(
- NEW, "com/android/tools/r8/rewrite/assertions/ClassWithAssertions");
- methodVisitor.visitInsn(DUP);
- methodVisitor.visitVarInsn(ALOAD, 0);
- methodVisitor.visitInsn(ICONST_0);
- methodVisitor.visitInsn(AALOAD);
- methodVisitor.visitMethodInsn(
- INVOKESTATIC, "java/lang/Integer", "parseInt", "(Ljava/lang/String;)I", false);
- methodVisitor.visitMethodInsn(
- INVOKESPECIAL,
- "com/android/tools/r8/rewrite/assertions/ClassWithAssertions",
- "<init>",
- "(I)V",
- false);
- methodVisitor.visitMethodInsn(
- INVOKEVIRTUAL,
- "com/android/tools/r8/rewrite/assertions/ClassWithAssertions",
- "getX",
- "()I",
- false);
- methodVisitor.visitInsn(POP);
- Label label1 = new Label();
- methodVisitor.visitLabel(label1);
- methodVisitor.visitLineNumber(27, label1);
- methodVisitor.visitInsn(RETURN);
- Label label2 = new Label();
- methodVisitor.visitLabel(label2);
- methodVisitor.visitLocalVariable("args", "[Ljava/lang/String;", null, label0, label2, 0);
- methodVisitor.visitMaxs(4, 1);
- methodVisitor.visitEnd();
- }
- {
- methodVisitor = classWriter.visitMethod(ACC_STATIC, "<clinit>", "()V", null, null);
- methodVisitor.visitCode();
- Label label0 = new Label();
- methodVisitor.visitLabel(label0);
- methodVisitor.visitLineNumber(7, label0);
- methodVisitor.visitLdcInsn(
- Type.getType("Lcom/android/tools/r8/rewrite/assertions/ClassWithAssertions;"));
- methodVisitor.visitMethodInsn(
- INVOKEVIRTUAL, "java/lang/Class", "desiredAssertionStatus", "()Z", false);
- Label label1 = new Label();
- methodVisitor.visitJumpInsn(IFNE, label1);
- methodVisitor.visitInsn(ICONST_1);
- Label label2 = new Label();
- methodVisitor.visitJumpInsn(GOTO, label2);
- methodVisitor.visitLabel(label1);
- methodVisitor.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
- methodVisitor.visitInsn(ICONST_0);
- methodVisitor.visitLabel(label2);
- methodVisitor.visitFrame(Opcodes.F_SAME1, 0, null, 1, new Object[] {Opcodes.INTEGER});
- methodVisitor.visitFieldInsn(
- PUTSTATIC,
- "com/android/tools/r8/rewrite/assertions/ClassWithAssertions",
- "$assertionsDisabled",
- "Z");
- methodVisitor.visitInsn(RETURN);
- methodVisitor.visitMaxs(1, 0);
- methodVisitor.visitEnd();
- }
- classWriter.visitEnd();
-
- return classWriter.toByteArray();
- }
-}
diff --git a/src/test/java/com/android/tools/r8/rewrite/assertions/RemoveAssertionsTest.java b/src/test/java/com/android/tools/r8/rewrite/assertions/RemoveAssertionsTest.java
index 8dad81d..ab80fe5 100644
--- a/src/test/java/com/android/tools/r8/rewrite/assertions/RemoveAssertionsTest.java
+++ b/src/test/java/com/android/tools/r8/rewrite/assertions/RemoveAssertionsTest.java
@@ -23,6 +23,7 @@
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.io.IOException;
import java.nio.file.Path;
import java.util.Collection;
import java.util.function.Function;
@@ -189,7 +190,7 @@
private static R8TestCompileResult compileWithAccessModification(Backend backend)
throws CompilationFailedException {
return testForR8(staticTemp, backend)
- .addProgramClassFileData(ClassWithAssertionsDump.dump())
+ .addProgramClasses(ClassWithAssertions.class)
.addKeepMainRule(ClassWithAssertions.class)
.addOptionsModification(o -> o.enableInlining = false)
.allowAccessModification()
@@ -200,7 +201,7 @@
private static R8TestCompileResult compileCf(InternalOptions.AssertionProcessing assertionsState)
throws CompilationFailedException {
return testForR8(staticTemp, Backend.CF)
- .addProgramClassFileData(ClassWithAssertionsDump.dump())
+ .addProgramClasses(ClassWithAssertions.class)
.debug()
.noTreeShaking()
.noMinification()
@@ -219,10 +220,11 @@
}
private static R8TestCompileResult compileRegress110887293(Function<byte[], byte[]> rewriter)
- throws CompilationFailedException {
+ throws CompilationFailedException, IOException {
return testForR8(staticTemp, Backend.DEX)
.addProgramClassFileData(
- rewriter.apply(ClassWithAssertionsDump.dump()), ChromuimAssertionHookMockDump.dump())
+ rewriter.apply(ToolHelper.getClassAsBytes(ClassWithAssertions.class)))
+ .addProgramClasses(ChromuimAssertionHookMock.class)
.setMinApi(AndroidApiLevel.B)
.debug()
.noTreeShaking()
@@ -230,7 +232,8 @@
.compile();
}
- private static CompilationResults compileAll(Backend backend) throws CompilationFailedException {
+ private static CompilationResults compileAll(Backend backend)
+ throws CompilationFailedException, IOException {
R8TestCompileResult withAccess = compileWithAccessModification(backend);
if (backend == Backend.CF) {
return new CompilationResults(
@@ -337,7 +340,7 @@
private D8TestCompileResult compileD8(InternalOptions.AssertionProcessing assertionsState)
throws CompilationFailedException {
return testForD8()
- .addProgramClassFileData(ClassWithAssertionsDump.dump())
+ .addProgramClasses(ClassWithAssertions.class)
.debug()
.setMinApi(AndroidApiLevel.B)
.addOptionsModification(o -> o.assertionProcessing = assertionsState)
@@ -348,7 +351,7 @@
InternalOptions.AssertionProcessing assertionsState) throws Exception {
Path program =
testForR8(Backend.CF)
- .addProgramClassFileData(ClassWithAssertionsDump.dump())
+ .addProgramClasses(ClassWithAssertions.class)
.debug()
.setMinApi(AndroidApiLevel.B)
.noTreeShaking()
@@ -373,11 +376,11 @@
}
private D8TestCompileResult compileD8Regress110887293(Function<byte[], byte[]> rewriter)
- throws CompilationFailedException {
+ throws CompilationFailedException, IOException {
return testForD8()
.addProgramClassFileData(
- rewriter.apply(ClassWithAssertionsDump.dump()),
- rewriter.apply(ChromuimAssertionHookMockDump.dump()))
+ rewriter.apply(ToolHelper.getClassAsBytes(ClassWithAssertions.class)),
+ rewriter.apply(ToolHelper.getClassAsBytes(ChromuimAssertionHookMock.class)))
.debug()
.setMinApi(AndroidApiLevel.B)
.compile();
diff --git a/src/test/java/com/android/tools/r8/rewrite/enums/EnumOptimizationTest.java b/src/test/java/com/android/tools/r8/rewrite/enums/EnumOptimizationTest.java
index 63f69d7..9da1f4a 100644
--- a/src/test/java/com/android/tools/r8/rewrite/enums/EnumOptimizationTest.java
+++ b/src/test/java/com/android/tools/r8/rewrite/enums/EnumOptimizationTest.java
@@ -81,19 +81,21 @@
}
assertOrdinalReplacedWithConst(clazz.uniqueMethodWithName("inlined"), 1);
assertOrdinalReplacedWithConst(clazz.uniqueMethodWithName("inSwitch"), 11);
+ assertOrdinalReplacedWithConst(clazz.uniqueMethodWithName("differentTypeStaticField"), 1);
+ assertOrdinalReplacedWithConst(clazz.uniqueMethodWithName("nonStaticGet"), 1);
} else {
assertOrdinalWasNotReplaced(clazz.uniqueMethodWithName("simple"));
assertOrdinalWasNotReplaced(clazz.uniqueMethodWithName("local"));
assertOrdinalWasNotReplaced(clazz.uniqueMethodWithName("multipleUsages"));
assertOrdinalWasNotReplaced(clazz.uniqueMethodWithName("inlined"));
assertOrdinalWasNotReplaced(clazz.uniqueMethodWithName("inSwitch"));
+ assertOrdinalWasNotReplaced(clazz.uniqueMethodWithName("differentTypeStaticField"));
+ assertOrdinalWasNotReplaced(clazz.uniqueMethodWithName("nonStaticGet"));
}
assertOrdinalWasNotReplaced(clazz.uniqueMethodWithName("libraryType"));
- assertOrdinalWasNotReplaced(clazz.uniqueMethodWithName("wrongTypeStaticField"));
assertOrdinalWasNotReplaced(clazz.uniqueMethodWithName("nonValueStaticField"));
assertOrdinalWasNotReplaced(clazz.uniqueMethodWithName("phi"));
- assertOrdinalWasNotReplaced(clazz.uniqueMethodWithName("nonStaticGet"));
}
@Test
@@ -123,20 +125,22 @@
String expectedConst = parameters.isDexRuntime() ? "1TWO" : "TWO";
assertNameReplacedWithConst(clazz.uniqueMethodWithName("multipleUsages"), expectedConst);
assertNameReplacedWithConst(clazz.uniqueMethodWithName("inlined"), "TWO");
+ assertNameReplacedWithConst(clazz.uniqueMethodWithName("differentTypeStaticField"), "DOWN");
+ assertNameReplacedWithConst(clazz.uniqueMethodWithName("nonStaticGet"), "TWO");
} else {
assertNameWasNotReplaced(clazz.uniqueMethodWithName("simple"));
assertNameWasNotReplaced(clazz.uniqueMethodWithName("local"));
assertNameWasNotReplaced(clazz.uniqueMethodWithName("multipleUsages"));
assertNameWasNotReplaced(clazz.uniqueMethodWithName("inlined"));
+ assertNameWasNotReplaced(clazz.uniqueMethodWithName("differentTypeStaticField"));
+ assertNameWasNotReplaced(clazz.uniqueMethodWithName("nonStaticGet"));
}
// TODO(jakew) this should be allowed!
assertNameWasNotReplaced(clazz.uniqueMethodWithName("libraryType"));
- assertNameWasNotReplaced(clazz.uniqueMethodWithName("wrongTypeStaticField"));
assertNameWasNotReplaced(clazz.uniqueMethodWithName("nonValueStaticField"));
assertNameWasNotReplaced(clazz.uniqueMethodWithName("phi"));
- assertNameWasNotReplaced(clazz.uniqueMethodWithName("nonStaticGet"));
}
@Test
@@ -173,18 +177,22 @@
assertToStringReplacedWithConst(clazz.uniqueMethodWithName("local"), "TWO");
assertToStringReplacedWithConst(clazz.uniqueMethodWithName("multipleUsages"), "TWO");
assertToStringReplacedWithConst(clazz.uniqueMethodWithName("inlined"), "TWO");
+ assertToStringReplacedWithConst(clazz.uniqueMethodWithName("nonValueStaticField"), "TWO");
+ assertToStringReplacedWithConst(
+ clazz.uniqueMethodWithName("differentTypeStaticField"), "DOWN");
+ assertToStringReplacedWithConst(clazz.uniqueMethodWithName("nonStaticGet"), "TWO");
} else {
assertToStringWasNotReplaced(clazz.uniqueMethodWithName("noToString"));
assertToStringWasNotReplaced(clazz.uniqueMethodWithName("local"));
assertToStringWasNotReplaced(clazz.uniqueMethodWithName("multipleUsages"));
assertToStringWasNotReplaced(clazz.uniqueMethodWithName("inlined"));
+ assertToStringWasNotReplaced(clazz.uniqueMethodWithName("nonValueStaticField"));
+ assertToStringWasNotReplaced(clazz.uniqueMethodWithName("differentTypeStaticField"));
+ assertToStringWasNotReplaced(clazz.uniqueMethodWithName("nonStaticGet"));
}
assertToStringWasNotReplaced(clazz.uniqueMethodWithName("libraryType"));
- assertToStringWasNotReplaced(clazz.uniqueMethodWithName("wrongTypeStaticField"));
- assertToStringWasNotReplaced(clazz.uniqueMethodWithName("nonValueStaticField"));
assertToStringWasNotReplaced(clazz.uniqueMethodWithName("phi"));
- assertToStringWasNotReplaced(clazz.uniqueMethodWithName("nonStaticGet"));
}
private static void assertOrdinalReplacedWithConst(MethodSubject method, int expectedConst) {
diff --git a/src/test/java/com/android/tools/r8/rewrite/enums/Names.java b/src/test/java/com/android/tools/r8/rewrite/enums/Names.java
index e9e4c9f..8f09a80 100644
--- a/src/test/java/com/android/tools/r8/rewrite/enums/Names.java
+++ b/src/test/java/com/android/tools/r8/rewrite/enums/Names.java
@@ -57,8 +57,9 @@
return TimeUnit.SECONDS.name();
}
+ @AssumeMayHaveSideEffects
@NeverInline
- private static String wrongTypeStaticField() {
+ private static String differentTypeStaticField() {
return Number.DOWN.name();
}
@@ -88,7 +89,7 @@
System.out.println(multipleUsages());
System.out.println(inlined());
System.out.println(libraryType());
- System.out.println(wrongTypeStaticField());
+ System.out.println(differentTypeStaticField());
System.out.println(nonValueStaticField());
System.out.println(phi(true));
System.out.println(nonStaticGet());
diff --git a/src/test/java/com/android/tools/r8/rewrite/enums/Ordinals.java b/src/test/java/com/android/tools/r8/rewrite/enums/Ordinals.java
index b439e67..534b16c 100644
--- a/src/test/java/com/android/tools/r8/rewrite/enums/Ordinals.java
+++ b/src/test/java/com/android/tools/r8/rewrite/enums/Ordinals.java
@@ -69,7 +69,7 @@
}
@NeverInline
- private static long wrongTypeStaticField() {
+ private static long differentTypeStaticField() {
return Number.DOWN.ordinal();
}
@@ -100,7 +100,7 @@
System.out.println(inlined());
System.out.println(inSwitch());
System.out.println(libraryType());
- System.out.println(wrongTypeStaticField());
+ System.out.println(differentTypeStaticField());
System.out.println(nonValueStaticField());
System.out.println(phi(true));
System.out.println(nonStaticGet());
diff --git a/src/test/java/com/android/tools/r8/rewrite/enums/ToStrings.java b/src/test/java/com/android/tools/r8/rewrite/enums/ToStrings.java
index 44d7309..3617c8b 100644
--- a/src/test/java/com/android/tools/r8/rewrite/enums/ToStrings.java
+++ b/src/test/java/com/android/tools/r8/rewrite/enums/ToStrings.java
@@ -94,11 +94,13 @@
return TimeUnit.SECONDS.toString();
}
+ @AssumeMayHaveSideEffects
@NeverInline
- private static String wrongTypeStaticField() {
+ private static String differentTypeStaticField() {
return NoToString.DOWN.toString();
}
+ @AssumeMayHaveSideEffects
@NeverInline
private static String nonValueStaticField() {
return NoToString.DEFAULT.toString();
@@ -128,7 +130,7 @@
System.out.println(multipleUsages());
System.out.println(inlined());
System.out.println(libraryType());
- System.out.println(wrongTypeStaticField());
+ System.out.println(differentTypeStaticField());
System.out.println(nonValueStaticField());
System.out.println(phi(true));
System.out.println(nonStaticGet());
diff --git a/src/test/java/com/android/tools/r8/shaking/EffectivelyFinalInstanceFieldsTest.java b/src/test/java/com/android/tools/r8/shaking/EffectivelyFinalInstanceFieldsTest.java
index ea96ead..92d16ae 100644
--- a/src/test/java/com/android/tools/r8/shaking/EffectivelyFinalInstanceFieldsTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/EffectivelyFinalInstanceFieldsTest.java
@@ -56,43 +56,50 @@
.enableMergeAnnotations()
.setMinApi(parameters.getRuntime())
.compile()
- .inspect(codeInspector -> {
- ClassSubject main = codeInspector.clazz(MAIN);
- assertThat(main, isPresent());
+ .inspect(
+ codeInspector -> {
+ ClassSubject main = codeInspector.clazz(MAIN);
+ assertThat(main, isPresent());
- MethodSubject mainMethod = main.mainMethod();
- assertThat(mainMethod, isPresent());
+ MethodSubject mainMethod = main.mainMethod();
+ assertThat(mainMethod, isPresent());
- assertTrue(
- mainMethod.streamInstructions().noneMatch(
- i -> i.isConstString("Dead code: 1", JumboStringMode.ALLOW)));
- // TODO(b/138913138): effectively final, and default value is set.
- assertFalse(
- mainMethod.streamInstructions().noneMatch(
- i -> i.isConstString("Dead code: 2", JumboStringMode.ALLOW)));
- // TODO(b/138913138): not trivial; assigned only once in <init>
- assertFalse(
- mainMethod.streamInstructions().noneMatch(
- i -> i.isConstString("Dead code: 3", JumboStringMode.ALLOW)));
- assertTrue(
- mainMethod.streamInstructions().noneMatch(
- i -> i.isConstString("Dead code: 4", JumboStringMode.ALLOW)));
- // TODO(b/138913138): effectively final, and default value is set.
- assertFalse(
- mainMethod.streamInstructions().noneMatch(
- i -> i.isConstString("Dead code: 5", JumboStringMode.ALLOW)));
- // TODO(b/138913138): not trivial; assigned multiple times, but within a certain range.
- assertFalse(
- mainMethod.streamInstructions().noneMatch(
- i -> i.isConstString("Dead code: 6", JumboStringMode.ALLOW)));
- assertTrue(
- mainMethod.streamInstructions().noneMatch(
- i -> i.isConstString("Dead code: 7", JumboStringMode.ALLOW)));
- // TODO(b/138913138): effectively final, and default value is set.
- assertFalse(
- mainMethod.streamInstructions().noneMatch(
- i -> i.isConstString("Dead code: 8", JumboStringMode.ALLOW)));
- })
+ assertTrue(
+ mainMethod
+ .streamInstructions()
+ .noneMatch(i -> i.isConstString("Dead code: 1", JumboStringMode.ALLOW)));
+ assertTrue(
+ mainMethod
+ .streamInstructions()
+ .noneMatch(i -> i.isConstString("Dead code: 2", JumboStringMode.ALLOW)));
+ // TODO(b/138913138): not trivial; assigned only once in <init>
+ assertFalse(
+ mainMethod
+ .streamInstructions()
+ .noneMatch(i -> i.isConstString("Dead code: 3", JumboStringMode.ALLOW)));
+ assertTrue(
+ mainMethod
+ .streamInstructions()
+ .noneMatch(i -> i.isConstString("Dead code: 4", JumboStringMode.ALLOW)));
+ assertTrue(
+ mainMethod
+ .streamInstructions()
+ .noneMatch(i -> i.isConstString("Dead code: 5", JumboStringMode.ALLOW)));
+ // TODO(b/138913138): not trivial; assigned multiple times, but within a certain
+ // range.
+ assertFalse(
+ mainMethod
+ .streamInstructions()
+ .noneMatch(i -> i.isConstString("Dead code: 6", JumboStringMode.ALLOW)));
+ assertTrue(
+ mainMethod
+ .streamInstructions()
+ .noneMatch(i -> i.isConstString("Dead code: 7", JumboStringMode.ALLOW)));
+ assertTrue(
+ mainMethod
+ .streamInstructions()
+ .noneMatch(i -> i.isConstString("Dead code: 8", JumboStringMode.ALLOW)));
+ })
.run(parameters.getRuntime(), MAIN)
.assertSuccessWithOutputLines("The end");
}
diff --git a/src/test/java/com/android/tools/r8/shaking/ReturnTypeTest.java b/src/test/java/com/android/tools/r8/shaking/ReturnTypeTest.java
index ecd6635..cc02426 100644
--- a/src/test/java/com/android/tools/r8/shaking/ReturnTypeTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ReturnTypeTest.java
@@ -83,7 +83,7 @@
// Call site optimization propagation will conclude that the input of B...Caller#call is
// always null, and replace the last call with null-throwing instruction.
// However, we want to test return type and parameter type are kept in this scenario.
- o.enableCallSiteOptimizationInfoPropagation = false;
+ o.enablePropagationOfDynamicTypesAtCallSites = false;
o.enableInlining = false;
})
.run(parameters.getRuntime(), MAIN)
diff --git a/src/test/java/com/android/tools/r8/shaking/clinit/ClassInitializedByClassForNameTest.java b/src/test/java/com/android/tools/r8/shaking/clinit/ClassInitializedByClassForNameTest.java
new file mode 100644
index 0000000..169bfa5
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/clinit/ClassInitializedByClassForNameTest.java
@@ -0,0 +1,66 @@
+// Copyright (c) 2019, 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.clinit;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class ClassInitializedByClassForNameTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public ClassInitializedByClassForNameTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(ClassInitializedByClassForNameTest.class)
+ .addKeepMainRule(TestClass.class)
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspect(this::inspect)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutputLines("Hello world!");
+ }
+
+ private void inspect(CodeInspector inspector) {
+ // Check that A.<clinit>() is not removed.
+ ClassSubject aClassSubject = inspector.clazz(A.class);
+ assertThat(aClassSubject, isPresent());
+ assertThat(aClassSubject.clinit(), isPresent());
+ }
+
+ static class TestClass {
+
+ public static void main(String[] args) throws ClassNotFoundException {
+ Class.forName("com.android.tools.r8.shaking.clinit.ClassInitializedByClassForNameTest$A");
+ }
+ }
+
+ static class A {
+
+ static {
+ System.out.println("Hello world!");
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/clinit/ClassInitializedByInvokeStaticTest.java b/src/test/java/com/android/tools/r8/shaking/clinit/ClassInitializedByInvokeStaticTest.java
new file mode 100644
index 0000000..8036f2f
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/clinit/ClassInitializedByInvokeStaticTest.java
@@ -0,0 +1,70 @@
+// Copyright (c) 2019, 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.clinit;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class ClassInitializedByInvokeStaticTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public ClassInitializedByInvokeStaticTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(ClassInitializedByInvokeStaticTest.class)
+ .addKeepMainRule(TestClass.class)
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspect(this::inspect)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutputLines("Hello world!");
+ }
+
+ private void inspect(CodeInspector inspector) {
+ // Check that A.<clinit>() is not removed.
+ ClassSubject aClassSubject = inspector.clazz(A.class);
+ assertThat(aClassSubject, isPresent());
+ assertThat(aClassSubject.clinit(), isPresent());
+ }
+
+ static class TestClass {
+
+ public static void main(String[] args) {
+ System.out.println(A.getExclamationMark());
+ }
+ }
+
+ static class A {
+
+ static {
+ System.out.print("Hello world");
+ }
+
+ static String getExclamationMark() {
+ return "!";
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/clinit/ClassInitializedByKeepRuleTest.java b/src/test/java/com/android/tools/r8/shaking/clinit/ClassInitializedByKeepRuleTest.java
new file mode 100644
index 0000000..2e31c07
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/clinit/ClassInitializedByKeepRuleTest.java
@@ -0,0 +1,57 @@
+// Copyright (c) 2019, 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.clinit;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class ClassInitializedByKeepRuleTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public ClassInitializedByKeepRuleTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(ClassInitializedByKeepRuleTest.class)
+ .addKeepClassRules(TestClass.class)
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspect(this::inspect);
+ }
+
+ private void inspect(CodeInspector inspector) {
+ // Check that A.<clinit>() is not removed.
+ ClassSubject aClassSubject = inspector.clazz(TestClass.class);
+ assertThat(aClassSubject, isPresent());
+ assertThat(aClassSubject.clinit(), isPresent());
+ }
+
+ static class TestClass {
+
+ static {
+ System.out.print("Hello world");
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/clinit/ClassInitializedByNewInstanceTest.java b/src/test/java/com/android/tools/r8/shaking/clinit/ClassInitializedByNewInstanceTest.java
new file mode 100644
index 0000000..2349839
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/clinit/ClassInitializedByNewInstanceTest.java
@@ -0,0 +1,66 @@
+// Copyright (c) 2019, 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.clinit;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class ClassInitializedByNewInstanceTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public ClassInitializedByNewInstanceTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(ClassInitializedByNewInstanceTest.class)
+ .addKeepMainRule(TestClass.class)
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspect(this::inspect)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutputLines("Hello world!");
+ }
+
+ private void inspect(CodeInspector inspector) {
+ // Check that A.<clinit>() is not removed.
+ ClassSubject aClassSubject = inspector.clazz(A.class);
+ assertThat(aClassSubject, isPresent());
+ assertThat(aClassSubject.clinit(), isPresent());
+ }
+
+ static class TestClass {
+
+ public static void main(String[] args) {
+ new A();
+ }
+ }
+
+ static class A {
+
+ static {
+ System.out.println("Hello world!");
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/clinit/ClassInitializedByStaticGetTest.java b/src/test/java/com/android/tools/r8/shaking/clinit/ClassInitializedByStaticGetTest.java
new file mode 100644
index 0000000..9501a21
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/clinit/ClassInitializedByStaticGetTest.java
@@ -0,0 +1,68 @@
+// Copyright (c) 2019, 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.clinit;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class ClassInitializedByStaticGetTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public ClassInitializedByStaticGetTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(ClassInitializedByStaticGetTest.class)
+ .addKeepMainRule(TestClass.class)
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspect(this::inspect)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutputLines("Hello world!");
+ }
+
+ private void inspect(CodeInspector inspector) {
+ // Check that A.<clinit>() is not removed.
+ ClassSubject aClassSubject = inspector.clazz(A.class);
+ assertThat(aClassSubject, isPresent());
+ assertThat(aClassSubject.clinit(), isPresent());
+ }
+
+ static class TestClass {
+
+ public static void main(String[] args) {
+ System.out.println(A.EXCLAMATION_MARK);
+ }
+ }
+
+ static class A {
+
+ static String EXCLAMATION_MARK = "!";
+
+ static {
+ System.out.print("Hello world");
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/clinit/ClassNotInitializedByCheckCastTest.java b/src/test/java/com/android/tools/r8/shaking/clinit/ClassNotInitializedByCheckCastTest.java
new file mode 100644
index 0000000..0a1914f
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/clinit/ClassNotInitializedByCheckCastTest.java
@@ -0,0 +1,69 @@
+// Copyright (c) 2019, 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.clinit;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class ClassNotInitializedByCheckCastTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public ClassNotInitializedByCheckCastTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(ClassNotInitializedByCheckCastTest.class)
+ .addKeepMainRule(TestClass.class)
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspect(this::inspect)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutputLines("null");
+ }
+
+ private void inspect(CodeInspector inspector) {
+ // Check that A.<clinit>() is removed.
+ ClassSubject aClassSubject = inspector.clazz(A.class);
+ assertThat(aClassSubject, isPresent());
+ assertThat(aClassSubject.clinit(), not(isPresent()));
+ }
+
+ static class TestClass {
+
+ static Object object = System.currentTimeMillis() >= 0 ? null : new Object();
+
+ public static void main(String[] args) {
+ System.out.println((A) object);
+ }
+ }
+
+ static class A {
+
+ static {
+ System.out.println("Hello world!");
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/clinit/ClassNotInitializedByConstClassTest.java b/src/test/java/com/android/tools/r8/shaking/clinit/ClassNotInitializedByConstClassTest.java
new file mode 100644
index 0000000..f40cc94
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/clinit/ClassNotInitializedByConstClassTest.java
@@ -0,0 +1,67 @@
+// Copyright (c) 2019, 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.clinit;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.R8TestRunResult;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class ClassNotInitializedByConstClassTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public ClassNotInitializedByConstClassTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void test() throws Exception {
+ R8TestRunResult result =
+ testForR8(parameters.getBackend())
+ .addInnerClasses(ClassNotInitializedByConstClassTest.class)
+ .addKeepMainRule(TestClass.class)
+ .setMinApi(parameters.getApiLevel())
+ .run(parameters.getRuntime(), TestClass.class);
+
+ // Check that A.<clinit>() is removed.
+ CodeInspector inspector = result.inspector();
+ ClassSubject aClassSubject = inspector.clazz(A.class);
+ assertThat(aClassSubject, isPresent());
+ assertThat(aClassSubject.clinit(), not(isPresent()));
+
+ result.assertSuccessWithOutputLines(aClassSubject.getFinalName());
+ }
+
+ static class TestClass {
+
+ public static void main(String[] args) {
+ System.out.println(A.class.getName());
+ }
+ }
+
+ static class A {
+
+ static {
+ System.out.println("Hello world!");
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/clinit/ClassNotInitializedByInstanceOfTest.java b/src/test/java/com/android/tools/r8/shaking/clinit/ClassNotInitializedByInstanceOfTest.java
new file mode 100644
index 0000000..cc64abf
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/clinit/ClassNotInitializedByInstanceOfTest.java
@@ -0,0 +1,71 @@
+// Copyright (c) 2019, 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.clinit;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class ClassNotInitializedByInstanceOfTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public ClassNotInitializedByInstanceOfTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(ClassNotInitializedByInstanceOfTest.class)
+ .addKeepMainRule(TestClass.class)
+ .addOptionsModification(
+ options -> options.testing.enableCheckCastAndInstanceOfRemoval = false)
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspect(this::inspect)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutputLines("false");
+ }
+
+ private void inspect(CodeInspector inspector) {
+ // Check that A.<clinit>() is removed.
+ ClassSubject aClassSubject = inspector.clazz(A.class);
+ assertThat(aClassSubject, isPresent());
+ assertThat(aClassSubject.clinit(), not(isPresent()));
+ }
+
+ static class TestClass {
+
+ static Object object = System.currentTimeMillis() >= 0 ? new Object() : null;
+
+ public static void main(String[] args) {
+ System.out.println(object instanceof A);
+ }
+ }
+
+ static class A {
+
+ static {
+ System.out.println("Hello world!");
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/clinit/InterfaceInitializedByImplementationTest.java b/src/test/java/com/android/tools/r8/shaking/clinit/InterfaceInitializedByImplementationTest.java
new file mode 100644
index 0000000..3f68961
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/clinit/InterfaceInitializedByImplementationTest.java
@@ -0,0 +1,97 @@
+// Copyright (c) 2019, 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.clinit;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class InterfaceInitializedByImplementationTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters()
+ .withAllRuntimes()
+ // Ensure default interface methods are supported.
+ .withApiLevelsStartingAtIncluding(AndroidApiLevel.N)
+ .build();
+ }
+
+ public InterfaceInitializedByImplementationTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(InterfaceInitializedByImplementationTest.class)
+ .addKeepMainRule(TestClass.class)
+ .enableInliningAnnotations()
+ .setMinApi(parameters.getApiLevel())
+ .noMinification()
+ .compile()
+ .inspect(this::inspect)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutputLines("Hello world!");
+ }
+
+ private void inspect(CodeInspector inspector) {
+ // Check that A is not removed.
+ ClassSubject aClasssubject = inspector.clazz(A.class);
+ assertThat(aClasssubject, isPresent());
+
+ // Check that I.<clinit>() is not removed.
+ ClassSubject iClassSubject = inspector.clazz(I.class);
+ assertThat(iClassSubject, isPresent());
+ assertThat(iClassSubject.clinit(), isPresent());
+
+ // Check that B.<clinit>() is not removed.
+ ClassSubject bClassSubject = inspector.clazz(B.class);
+ assertThat(bClassSubject, isPresent());
+ assertThat(bClassSubject.clinit(), isPresent());
+ }
+
+ static class TestClass {
+
+ public static void main(String[] args) {
+ new A().m();
+ }
+ }
+
+ interface I {
+
+ B B_INSTANCE = new B();
+
+ // TODO(b/144266257): If tree shaking removes this method, then I.<clinit>() won't be run when
+ // A is being class initialized.
+ @NeverInline
+ default void m() {
+ System.out.println(" world!");
+ }
+ }
+
+ static class A implements I {}
+
+ static class B {
+
+ static {
+ System.out.print("Hello");
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptSingletonIsNotCyclicTest.java b/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptSingletonIsNotCyclicTest.java
index e094a13..ecfc895 100644
--- a/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptSingletonIsNotCyclicTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptSingletonIsNotCyclicTest.java
@@ -92,12 +92,14 @@
QueryNode mainMethod = inspector.method(mainMethodRef).assertNotRenamed().assertKeptBy(root);
// TestClass.<init> is kept by TestClass.main.
QueryNode testInit = inspector.method(testInitRef).assertPresent().assertKeptBy(mainMethod);
- // Foo.<clinit> is kept by TestClass.<init>
- QueryNode fooClInit = inspector.method(fooClInitRef).assertPresent().assertKeptBy(testInit);
+ // The type Foo is kept by TestClass.<init>
+ QueryNode fooClassNode = inspector.clazz(fooClassRef).assertRenamed().assertKeptBy(testInit);
+ // Foo.<clinit> is kept by Foo
+ QueryNode fooClInit = inspector.method(fooClInitRef).assertPresent().assertKeptBy(fooClassNode);
+ // The type Foo is also kept by Foo.<clinit>
+ fooClassNode.assertKeptBy(fooClInit);
// Foo.<init> is kept by Foo.<clinit>
QueryNode fooInit = inspector.method(fooInitRef).assertPresent().assertKeptBy(fooClInit);
- // The type Foo is kept by the class constructor of Foo and the instance initializer.
- inspector.clazz(fooClassRef).assertRenamed().assertKeptBy(fooClInit).assertKeptBy(fooInit);
}
public static final class FooStaticMethod {
diff --git a/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptViaClassInitializerTestRunner.java b/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptViaClassInitializerTestRunner.java
index 3c5c230..707a415 100644
--- a/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptViaClassInitializerTestRunner.java
+++ b/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptViaClassInitializerTestRunner.java
@@ -14,6 +14,8 @@
import com.android.tools.r8.NeverClassInline;
import com.android.tools.r8.NeverMerge;
import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.ToolHelper.DexVm.Version;
import com.android.tools.r8.origin.Origin;
@@ -70,25 +72,31 @@
private static final String CLASS_NAME = KeptViaClassInitializerTestRunner.class.getTypeName();
private static final String EXPECTED = StringUtils.lines("I'm an A");
- private final Backend backend;
+ private final TestParameters parameters;
@Parameters(name = "{0}")
- public static Backend[] data() {
- return ToolHelper.getBackends();
+ public static TestParametersCollection data() {
+ return getTestParameters()
+ .withCfRuntimes()
+ .withDexRuntimesStartingFromIncluding(Version.V7_0_0)
+ .withAllApiLevels()
+ .build();
}
- public KeptViaClassInitializerTestRunner(Backend backend) {
- this.backend = backend;
+ public KeptViaClassInitializerTestRunner(TestParameters parameters) {
+ this.parameters = parameters;
}
- public static String KEPT_REASON_SUFFIX = StringUtils.lines(
- // The full reason is not shared between CF and DEX due to desugaring.
- "| void " + CLASS_NAME + "$T.<clinit>()",
- "|- is referenced from:",
- "| void " + CLASS_NAME + "$Main.main(java.lang.String[])",
- "|- is referenced in keep rule:",
- "| -keep class " + CLASS_NAME + "$Main { void main(java.lang.String[]); }"
- );
+ public static String KEPT_REASON_SUFFIX =
+ StringUtils.lines(
+ // The full reason is not shared between CF and DEX due to desugaring.
+ "| void " + CLASS_NAME + "$T.<clinit>()",
+ "|- is reachable from:",
+ "| com.android.tools.r8.shaking.keptgraph.KeptViaClassInitializerTestRunner$T",
+ "|- is referenced from:",
+ "| void " + CLASS_NAME + "$Main.main(java.lang.String[])",
+ "|- is referenced in keep rule:",
+ "| -keep class " + CLASS_NAME + "$Main { void main(java.lang.String[]); }");
@Test
public void testKeptMethod() throws Exception {
@@ -99,18 +107,18 @@
WhyAreYouKeepingConsumer consumer = new WhyAreYouKeepingConsumer(null);
GraphInspector inspector =
- testForR8(backend)
+ testForR8(parameters.getBackend())
.enableGraphInspector(consumer)
.addProgramClassesAndInnerClasses(Main.class, A.class, T.class)
.addKeepMethodRules(mainMethod)
.setMinApi(AndroidApiLevel.N)
.apply(
b -> {
- if (backend == Backend.DEX) {
+ if (parameters.isDexRuntime()) {
b.addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.N));
}
})
- .run(Main.class)
+ .run(parameters.getRuntime(), Main.class)
.assertSuccessWithOutput(EXPECTED)
.graphInspector();
@@ -124,7 +132,7 @@
// TODO(b/124499108): Currently synthetic lambda classes are referenced,
// should be their originating context.
- if (backend == Backend.DEX) {
+ if (parameters.isDexRuntime()) {
assertThat(baos.toString(), containsString("-$$Lambda$"));
} else {
assertThat(baos.toString(), not(containsString("-$$Lambda$")));
diff --git a/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java b/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
new file mode 100644
index 0000000..8157f47
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
@@ -0,0 +1,199 @@
+// Copyright (c) 2019, 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.transformers;
+
+import static org.objectweb.asm.Opcodes.ASM7;
+
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.naming.MemberNaming.MethodSignature;
+import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.transformers.MethodTransformer.MethodContext;
+import com.android.tools.r8.utils.DescriptorUtils;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+import org.objectweb.asm.ClassReader;
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.MethodVisitor;
+
+public class ClassFileTransformer {
+
+ /**
+ * Basic algorithm for transforming the content of a class file.
+ *
+ * <p>The provided transformers are nested in the order given: the first in the list will receive
+ * is call back first, if it forwards to 'super' then the seconds call back will be called, etc,
+ * until finally the writer will be called. If the writer is not called the effect is as if the
+ * callback was never called and its content will not be in the result.
+ */
+ public static byte[] transform(
+ byte[] bytes,
+ List<ClassTransformer> classTransformers,
+ List<MethodTransformer> methodTransformers) {
+ ClassReader reader = new ClassReader(bytes);
+ ClassWriter writer = new ClassWriter(reader, 0);
+ ClassVisitor subvisitor = new InnerMostClassTransformer(writer, methodTransformers);
+ for (int i = classTransformers.size() - 1; i >= 0; i--) {
+ classTransformers.get(i).setSubVisitor(subvisitor);
+ subvisitor = classTransformers.get(i);
+ }
+ reader.accept(subvisitor, 0);
+ return writer.toByteArray();
+ }
+
+ // Inner-most bride from the class transformation to the method transformers.
+ private static class InnerMostClassTransformer extends ClassVisitor {
+ ClassReference classReference;
+ final List<MethodTransformer> methodTransformers;
+
+ InnerMostClassTransformer(ClassWriter writer, List<MethodTransformer> methodTransformers) {
+ super(ASM7, writer);
+ this.methodTransformers = methodTransformers;
+ }
+
+ @Override
+ public void visit(
+ int version,
+ int access,
+ String name,
+ String signature,
+ String superName,
+ String[] interfaces) {
+ super.visit(version, access, name, signature, superName, interfaces);
+ classReference = Reference.classFromBinaryName(name);
+ }
+
+ @Override
+ public MethodVisitor visitMethod(
+ int access, String name, String descriptor, String signature, String[] exceptions) {
+ MethodContext context = createMethodContext(access, name, descriptor);
+ MethodVisitor subvisitor = super.visitMethod(access, name, descriptor, signature, exceptions);
+ for (int i = methodTransformers.size() - 1; i >= 0; i--) {
+ MethodTransformer transformer = methodTransformers.get(i);
+ transformer.setSubVisitor(subvisitor);
+ transformer.setContext(context);
+ subvisitor = transformer;
+ }
+ return subvisitor;
+ }
+
+ private MethodContext createMethodContext(int access, String name, String descriptor) {
+ // Maybe clean up this parsing of info as it is not very nice.
+ MethodSignature methodSignature = MethodSignature.fromSignature(name, descriptor);
+ MethodReference methodReference =
+ Reference.method(
+ classReference,
+ name,
+ Arrays.stream(methodSignature.parameters)
+ .map(DescriptorUtils::javaTypeToDescriptor)
+ .map(Reference::typeFromDescriptor)
+ .collect(Collectors.toList()),
+ methodSignature.type.equals("void")
+ ? null
+ : Reference.typeFromDescriptor(
+ DescriptorUtils.javaTypeToDescriptor(methodSignature.type)));
+ return new MethodContext(methodReference, access);
+ }
+ }
+
+ // Transformer utilities.
+
+ private final byte[] bytes;
+ private final List<ClassTransformer> classTransformers = new ArrayList<>();
+ private final List<MethodTransformer> methodTransformers = new ArrayList<>();
+
+ private ClassFileTransformer(byte[] bytes) {
+ this.bytes = bytes;
+ }
+
+ public static ClassFileTransformer create(byte[] bytes) {
+ return new ClassFileTransformer(bytes);
+ }
+
+ public static ClassFileTransformer create(Class<?> clazz) throws IOException {
+ return create(ToolHelper.getClassAsBytes(clazz));
+ }
+
+ public byte[] transform() {
+ return ClassFileTransformer.transform(bytes, classTransformers, methodTransformers);
+ }
+
+ /** Base addition of a transformer on the class. */
+ public ClassFileTransformer addClassTransformer(ClassTransformer transformer) {
+ classTransformers.add(transformer);
+ return this;
+ }
+
+ /** Base addtion of a transformer on methods. */
+ public ClassFileTransformer addMethodTransformer(MethodTransformer transformer) {
+ methodTransformers.add(transformer);
+ return this;
+ }
+
+ /** Unconditionally replace the implements clause of a class. */
+ public ClassFileTransformer setImplements(Class<?>... interfaces) {
+ return addClassTransformer(
+ new ClassTransformer() {
+ @Override
+ public void visit(
+ int version,
+ int access,
+ String name,
+ String signature,
+ String superName,
+ String[] ignoredInterfaces) {
+ super.visit(
+ version,
+ access,
+ name,
+ signature,
+ superName,
+ Arrays.stream(interfaces)
+ .map(clazz -> DescriptorUtils.getBinaryNameFromJavaType(clazz.getTypeName()))
+ .toArray(String[]::new));
+ }
+ });
+ }
+
+ /** Abstraction of the MethodVisitor.visitMethodInsn method with its continuation. */
+ @FunctionalInterface
+ public interface MethodInsnTransform {
+ void visitMethodInsn(
+ int opcode,
+ String owner,
+ String name,
+ String descriptor,
+ boolean isInterface,
+ MethodInsnTransformContinuation continuation);
+ }
+
+ /** Continuation for transforming a method. Will continue with the super visitor if called. */
+ @FunctionalInterface
+ public interface MethodInsnTransformContinuation {
+ void visitMethodInsn(
+ int opcode, String owner, String name, String descriptor, boolean isInterface);
+ }
+
+ public ClassFileTransformer transformMethodInsnInMethod(
+ String methodName, MethodInsnTransform transform) {
+ return addMethodTransformer(
+ new MethodTransformer() {
+ @Override
+ public void visitMethodInsn(
+ int opcode, String owner, String name, String descriptor, boolean isInterface) {
+ if (getContext().method.getMethodName().equals(methodName)) {
+ transform.visitMethodInsn(
+ opcode, owner, name, descriptor, isInterface, super::visitMethodInsn);
+ } else {
+ super.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
+ }
+ }
+ });
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/transformers/ClassTransformer.java b/src/test/java/com/android/tools/r8/transformers/ClassTransformer.java
new file mode 100644
index 0000000..885257e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/transformers/ClassTransformer.java
@@ -0,0 +1,25 @@
+// Copyright (c) 2019, 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.transformers;
+
+import static org.objectweb.asm.Opcodes.ASM7;
+
+import org.objectweb.asm.ClassVisitor;
+
+/**
+ * Class for transforming the content of a class.
+ *
+ * <p>This is just a simple wrapper on the ASM ClassVisitor interface.
+ */
+public class ClassTransformer extends ClassVisitor {
+ public ClassTransformer() {
+ super(ASM7, null);
+ }
+
+ // Package internals.
+
+ void setSubVisitor(ClassVisitor visitor) {
+ this.cv = visitor;
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/transformers/MethodTransformer.java b/src/test/java/com/android/tools/r8/transformers/MethodTransformer.java
new file mode 100644
index 0000000..a5ffa82
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/transformers/MethodTransformer.java
@@ -0,0 +1,57 @@
+// Copyright (c) 2019, 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.transformers;
+
+import static org.objectweb.asm.Opcodes.ASM7;
+
+import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.references.MethodReference;
+import org.objectweb.asm.MethodVisitor;
+
+/**
+ * Class for transforming the content of a method.
+ *
+ * <p>This is just a simple wrapper on the ASM MethodVisitor interface with some added methods for
+ * obtaining context information.
+ */
+public class MethodTransformer extends MethodVisitor {
+
+ static class MethodContext {
+ public final MethodReference method;
+ public final int accessFlags;
+
+ public MethodContext(MethodReference method, int accessFlags) {
+ this.method = method;
+ this.accessFlags = accessFlags;
+ }
+ }
+
+ private MethodContext context;
+
+ public MethodTransformer() {
+ super(ASM7, null);
+ }
+
+ public ClassReference getHolder() {
+ return getContext().method.getHolderClass();
+ }
+
+ public MethodReference getMethod() {
+ return getContext().method;
+ }
+
+ // Package internals.
+
+ MethodContext getContext() {
+ return context;
+ }
+
+ void setSubVisitor(MethodVisitor visitor) {
+ this.mv = visitor;
+ }
+
+ void setContext(MethodContext context) {
+ this.context = context;
+ }
+}
diff --git a/tools/r8_release.py b/tools/r8_release.py
index f312919..a848beb 100755
--- a/tools/r8_release.py
+++ b/tools/r8_release.py
@@ -245,7 +245,9 @@
def g4_change(version, r8version):
return subprocess.check_output(
- 'g4 change --desc "Update R8 to version %s %s"' % (version, r8version),
+ 'g4 change --desc "Update R8 to version %s %s\n\n'
+ 'IGNORE_COMPLIANCELINT=D8 and R8 are built externally to produce a fully '
+ 'tested R8lib"' % (version, r8version),
shell=True)