Merge commit '0b579d0bee06cfb404c5f18c4827aee0d3a4e771' into 1.7.9-dev
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 e2ad67e..773f4ad 100644 --- a/src/main/java/com/android/tools/r8/graph/AppInfo.java +++ b/src/main/java/com/android/tools/r8/graph/AppInfo.java
@@ -10,6 +10,7 @@ import com.android.tools.r8.graph.ResolutionResult.NoSuchMethodResult; 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.ImmutableMap; import com.google.common.collect.ImmutableMap.Builder; import com.google.common.collect.ImmutableSet; @@ -45,6 +46,10 @@ copyMetadataFromPrevious(previous); } + protected InternalOptions options() { + return app.options; + } + public void copyMetadataFromPrevious(AppInfo previous) { this.synthesizedClasses.putAll(previous.synthesizedClasses); }
diff --git a/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java b/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java index ec90464..460885c 100644 --- a/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java +++ b/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java
@@ -325,7 +325,6 @@ assert checkIfObsolete(); if (!isSubtype(invocationContext, method.holder)) { DexClass contextClass = definitionFor(invocationContext); - isSubtype(invocationContext, method.holder); throw new CompilationError( "Illegal invoke-super to " + method.toSourceString() + " from class " + invocationContext, contextClass != null ? contextClass.getOrigin() : Origin.unknown());
diff --git a/src/main/java/com/android/tools/r8/graph/FieldAccessInfo.java b/src/main/java/com/android/tools/r8/graph/FieldAccessInfo.java index 2af1aa5..079b8bb 100644 --- a/src/main/java/com/android/tools/r8/graph/FieldAccessInfo.java +++ b/src/main/java/com/android/tools/r8/graph/FieldAccessInfo.java
@@ -28,4 +28,6 @@ boolean isReadOnlyIn(DexEncodedMethod method); boolean isWritten(); + + boolean isWrittenOutside(DexEncodedMethod method); }
diff --git a/src/main/java/com/android/tools/r8/graph/FieldAccessInfoImpl.java b/src/main/java/com/android/tools/r8/graph/FieldAccessInfoImpl.java index b6c54cb..fcd4f5c 100644 --- a/src/main/java/com/android/tools/r8/graph/FieldAccessInfoImpl.java +++ b/src/main/java/com/android/tools/r8/graph/FieldAccessInfoImpl.java
@@ -146,6 +146,23 @@ return writesWithContexts != null && !writesWithContexts.isEmpty(); } + /** + * Returns true if this field is written by a method in the program other than {@param method}. + */ + @Override + public boolean isWrittenOutside(DexEncodedMethod method) { + if (writesWithContexts != null) { + for (Set<DexEncodedMethod> encodedWriteContexts : writesWithContexts.values()) { + for (DexEncodedMethod encodedWriteContext : encodedWriteContexts) { + if (encodedWriteContext != method) { + return true; + } + } + } + } + return false; + } + public boolean recordRead(DexField access, DexEncodedMethod context) { if (readsWithContexts == null) { readsWithContexts = new IdentityHashMap<>();
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/ProtoEnqueuerExtension.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/ProtoEnqueuerExtension.java index ac6bcde..0ca8fac 100644 --- a/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/ProtoEnqueuerExtension.java +++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/ProtoEnqueuerExtension.java
@@ -144,13 +144,13 @@ boolean encodedValueStorageIsLive; if (enqueuer.isFieldLive(encodedValueStorage)) { - // Mark that the field is written by reflection such that we do not optimize field reads - // into loading the default value of the field. - enqueuer.registerFieldWrite(encodedValueStorage.field, dynamicMethod); - // Map/required fields cannot be removed. Therefore, we mark such fields as both read and - // written such that we cannot optimize any field reads or writes. - if (reachesMapOrRequiredField(protoFieldInfo)) { - enqueuer.registerFieldRead(encodedValueStorage.field, dynamicMethod); + if (enqueuer.isFieldRead(encodedValueStorage) + || enqueuer.isFieldWrittenOutsideDefaultConstructor(encodedValueStorage)) { + // Mark that the field is both read and written by reflection such that we do not + // (i) optimize field reads into loading the default value of the field or (ii) remove + // field writes to proto fields that could be read using reflection by the proto + // library. + enqueuer.registerFieldAccess(encodedValueStorage.field, dynamicMethod); } encodedValueStorageIsLive = true; } else if (reachesMapOrRequiredField(protoFieldInfo)) {
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CallGraphBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/CallGraphBuilder.java index d1ca480..8078fe0 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/CallGraphBuilder.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/CallGraphBuilder.java
@@ -134,10 +134,18 @@ } private void addTarget(DexEncodedMethod callee, boolean likelySpuriousCallEdge) { - if (!callee.accessFlags.isAbstract()) { - assert callee.isProgramMethod(appView); - getOrCreateNode(callee).addCallerConcurrently(caller, likelySpuriousCallEdge); + if (callee.accessFlags.isAbstract()) { + // Not a valid target. + return; } + if (appView.appInfo().isPinned(callee.method)) { + // Since the callee is kept, we cannot inline it into the caller, and we also cannot collect + // any optimization info for the method. Therefore, we drop the call edge to reduce the + // total number of call graph edges, which should lead to fewer call graph cycles. + return; + } + assert callee.isProgramMethod(appView); + getOrCreateNode(callee).addCallerConcurrently(caller, likelySpuriousCallEdge); } private void processInvoke(Type originalType, DexMethod originalMethod) {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryAPIConverter.java b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryAPIConverter.java index f0eda16..708cabb8 100644 --- a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryAPIConverter.java +++ b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryAPIConverter.java
@@ -139,6 +139,7 @@ // We look up everywhere to see if there is a supertype/interface implementing the method... LinkedList<DexType> workList = new LinkedList<>(); Collections.addAll(workList, theClass.interfaces.values); + boolean foundOverrideToRewrite = false; // There is no methods with desugared types on Object. if (theClass.superType != factory.objectType) { workList.add(theClass.superType); @@ -158,10 +159,14 @@ } DexEncodedMethod dexEncodedMethod = dexClass.lookupVirtualMethod(method); if (dexEncodedMethod != null) { - return true; + if (appView.options().desugaredLibraryConfiguration.getEmulateLibraryInterface() + .containsKey(dexClass.type)) { + return false; + } + foundOverrideToRewrite = true; } } - return false; + return foundOverrideToRewrite; } private synchronized void generateCallBack(DexClass dexClass, DexEncodedMethod originalMethod) { @@ -169,7 +174,7 @@ methodWithVivifiedTypeInSignature(originalMethod.method, dexClass.type, appView); CfCode cfCode = new APIConverterWrapperCfCodeProvider( - appView, originalMethod.method, null, this, dexClass.isInterface()) + appView, originalMethod.method, null, this, dexClass.isInterface()) .generateCfCode(); DexEncodedMethod newDexEncodedMethod = wrapperSynthesizor.newSynthesizedMethod(methodToInstall, originalMethod, cfCode); @@ -256,7 +261,7 @@ // Return conversion added only if return value is used. if (invokeMethod.outValue() != null && invokeMethod.outValue().numberOfUsers() + invokeMethod.outValue().numberOfPhiUsers() - > 0) { + > 0) { returnConversion = createReturnConversionAndReplaceUses(code, invokeMethod, returnType, newReturnType); }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryWrapperSynthesizer.java b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryWrapperSynthesizer.java index 95db2cf..2bf43f2 100644 --- a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryWrapperSynthesizer.java +++ b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryWrapperSynthesizer.java
@@ -95,6 +95,7 @@ public class DesugaredLibraryWrapperSynthesizer { public static final String WRAPPER_PREFIX = "$r8$wrapper$"; + public static final String WRAPPER_DESCRIPTOR_PREFIX = "L" + WRAPPER_PREFIX; public static final String TYPE_WRAPPER_SUFFIX = "$-WRP"; public static final String VIVIFIED_TYPE_WRAPPER_SUFFIX = "$-V-WRP"; @@ -118,6 +119,10 @@ this.converter = converter; } + public static boolean isSynthesizedWrapper(DexType clazz) { + return clazz.descriptor.toString().startsWith(WRAPPER_DESCRIPTOR_PREFIX); + } + boolean hasSynthesized(DexType type) { return generatedWrappers.contains(type); }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java b/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java index 87350e7..263da6c 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
@@ -264,14 +264,6 @@ return false; } - DexClass holder = appView.definitionFor(singleTarget.method.holder); - if (holder.isInterface()) { - // Art978_virtual_interfaceTest correctly expects an IncompatibleClassChangeError exception at - // runtime. - whyAreYouNotInliningReporter.reportIncompatibleClassChangeError(); - return false; - } - // Don't inline if target is synchronized. if (singleTarget.accessFlags.isSynchronized()) { whyAreYouNotInliningReporter.reportSynchronizedMethod();
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/inliner/NopWhyAreYouNotInliningReporter.java b/src/main/java/com/android/tools/r8/ir/optimize/inliner/NopWhyAreYouNotInliningReporter.java index 818b8c2..b5c96d4 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/inliner/NopWhyAreYouNotInliningReporter.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/inliner/NopWhyAreYouNotInliningReporter.java
@@ -43,9 +43,6 @@ public void reportInaccessible() {} @Override - public void reportIncompatibleClassChangeError() {} - - @Override public void reportIncorrectArity(int numberOfArguments, int arity) {} @Override
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/inliner/WhyAreYouNotInliningReporter.java b/src/main/java/com/android/tools/r8/ir/optimize/inliner/WhyAreYouNotInliningReporter.java index c560944..1e869eb 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/inliner/WhyAreYouNotInliningReporter.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/inliner/WhyAreYouNotInliningReporter.java
@@ -60,8 +60,6 @@ public abstract void reportInaccessible(); - public abstract void reportIncompatibleClassChangeError(); - public abstract void reportIncorrectArity(int numberOfArguments, int arity); public abstract void reportInlineeDoesNotHaveCode();
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/inliner/WhyAreYouNotInliningReporterImpl.java b/src/main/java/com/android/tools/r8/ir/optimize/inliner/WhyAreYouNotInliningReporterImpl.java index 6bc09fd..dcc122b 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/inliner/WhyAreYouNotInliningReporterImpl.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/inliner/WhyAreYouNotInliningReporterImpl.java
@@ -88,11 +88,6 @@ } @Override - public void reportIncompatibleClassChangeError() { - print("invoke may fail with an IncompatibleClassChangeError."); - } - - @Override public void reportIncorrectArity(int numberOfArguments, int arity) { print( "number of arguments ("
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/string/StringOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/string/StringOptimizer.java index 7728c1b..2751153 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/string/StringOptimizer.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/string/StringOptimizer.java
@@ -205,7 +205,7 @@ invoke.getLocalInfo()); affectedValues.addAll(invoke.outValue().affectedValues()); it.replaceCurrentInstruction(new ConstString(newOutValue, resultString, throwingInfo)); - numberOfSimplifiedConversions++; + numberOfSimplifiedOperations++; continue; }
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 4762db0..45f5bed 100644 --- a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java +++ b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
@@ -880,6 +880,31 @@ } } + private DexEncodedMethod validateSingleVirtualTarget( + DexEncodedMethod singleTarget, DexEncodedMethod resolutionResult) { + assert resolutionResult.isValidVirtualTarget(options()); + + if (singleTarget == null || singleTarget == DexEncodedMethod.SENTINEL) { + return null; + } + + // Art978_virtual_interfaceTest correctly expects an IncompatibleClassChangeError exception + // at runtime. + if (isInvalidSingleVirtualTarget(singleTarget, resolutionResult)) { + return null; + } + + return singleTarget; + } + + private boolean isInvalidSingleVirtualTarget( + DexEncodedMethod singleTarget, DexEncodedMethod resolutionResult) { + assert resolutionResult.isValidVirtualTarget(options()); + // Art978_virtual_interfaceTest correctly expects an IncompatibleClassChangeError exception + // at runtime. + return !singleTarget.accessFlags.isAtLeastAsVisibleAs(resolutionResult.accessFlags); + } + /** For mapping invoke virtual instruction to single target method. */ public DexEncodedMethod lookupSingleVirtualTarget(DexMethod method, DexType invocationContext) { assert checkIfObsolete(); @@ -903,11 +928,13 @@ if (receiverLowerBoundType != null) { if (receiverLowerBoundType.getClassType() == refinedReceiverType) { ResolutionResult resolutionResult = resolveMethod(method.holder, method, false); - if (resolutionResult.isValidVirtualTargetForDynamicDispatch()) { + if (resolutionResult.hasSingleTarget() + && resolutionResult.isValidVirtualTargetForDynamicDispatch()) { ResolutionResult refinedResolutionResult = resolveMethod(refinedReceiverType, method); if (refinedResolutionResult.hasSingleTarget() && refinedResolutionResult.isValidVirtualTargetForDynamicDispatch()) { - return refinedResolutionResult.asSingleTarget(); + return validateSingleVirtualTarget( + refinedResolutionResult.asSingleTarget(), resolutionResult.asSingleTarget()); } } return null; @@ -942,29 +969,29 @@ // First get the target for the holder type. ResolutionResult topMethod = resolveMethodOnClass(holder, method); // We might hit none or multiple targets. Both make this fail at runtime. - if (!topMethod.hasSingleTarget() || !topMethod.isValidVirtualTarget(app().options)) { + if (!topMethod.hasSingleTarget() || !topMethod.isValidVirtualTarget(options())) { method.setSingleVirtualMethodCache(refinedReceiverType, null); return null; } // Now, resolve the target with the refined receiver type. - if (refinedReceiverIsStrictSubType) { - topMethod = resolveMethodOnClass(refinedHolder, method); - } - DexEncodedMethod topSingleTarget = topMethod.asSingleTarget(); + ResolutionResult refinedResolutionResult = + refinedReceiverIsStrictSubType ? resolveMethodOnClass(refinedHolder, method) : topMethod; + DexEncodedMethod topSingleTarget = refinedResolutionResult.asSingleTarget(); DexClass topHolder = definitionFor(topSingleTarget.method.holder); // We need to know whether the top method is from an interface, as that would allow it to be // shadowed by a default method from an interface further down. boolean topIsFromInterface = topHolder.isInterface(); // Now look at all subtypes and search for overrides. DexEncodedMethod result = - findSingleTargetFromSubtypes( - refinedReceiverType, - method, - topSingleTarget, - !refinedHolder.accessFlags.isAbstract(), - topIsFromInterface); - // Map the failure case of SENTINEL to null. - result = result == DexEncodedMethod.SENTINEL ? null : result; + validateSingleVirtualTarget( + findSingleTargetFromSubtypes( + refinedReceiverType, + method, + topSingleTarget, + !refinedHolder.accessFlags.isAbstract(), + topIsFromInterface), + topMethod.asSingleTarget()); + assert result != DexEncodedMethod.SENTINEL; method.setSingleVirtualMethodCache(refinedReceiverType, result); return result; } @@ -1099,11 +1126,13 @@ if (receiverLowerBoundType != null) { if (receiverLowerBoundType.getClassType() == refinedReceiverType) { ResolutionResult resolutionResult = resolveMethod(method.holder, method, true); - if (resolutionResult.isValidVirtualTargetForDynamicDispatch()) { + if (resolutionResult.hasSingleTarget() + && resolutionResult.isValidVirtualTargetForDynamicDispatch()) { ResolutionResult refinedResolutionResult = resolveMethod(refinedReceiverType, method); if (refinedResolutionResult.hasSingleTarget() && refinedResolutionResult.isValidVirtualTargetForDynamicDispatch()) { - return refinedResolutionResult.asSingleTarget(); + return validateSingleVirtualTarget( + refinedResolutionResult.asSingleTarget(), resolutionResult.asSingleTarget()); } } return null; @@ -1119,11 +1148,8 @@ } // First check that there is a target for this invoke-interface to hit. If there is none, // this will fail at runtime. - ResolutionResult topTarget = resolveMethodOnInterface(holder, method); - if (!topTarget.isValidVirtualTarget(app().options)) { - return null; - } - if (topTarget.asResultOfResolve() == null) { + DexEncodedMethod topTarget = resolveMethodOnInterface(holder, method).asSingleTarget(); + if (topTarget == null || !topTarget.isValidVirtualTarget(options())) { return null; } // For kept types we cannot ensure a single target. @@ -1152,19 +1178,18 @@ // override them, so we ignore interface methods here. Otherwise, we would look up // default methods that are factually never used. } else if (!clazz.accessFlags.isAbstract()) { - ResolutionResult resolutionResult = resolveMethodOnClass(clazz, method); - if (resolutionResult.hasSingleTarget()) { - if ((result != null) && (result != resolutionResult.asSingleTarget())) { - return null; - } else { - result = resolutionResult.asSingleTarget(); - } - } else { + DexEncodedMethod resolutionResult = resolveMethodOnClass(clazz, method).asSingleTarget(); + if (resolutionResult == null || isInvalidSingleVirtualTarget(resolutionResult, topTarget)) { // This will fail at runtime. return null; } + if (result != null && result != resolutionResult) { + return null; + } + result = resolutionResult; } } + assert result == null || !isInvalidSingleVirtualTarget(result, topTarget); return result == null || !result.isVirtualMethod() ? null : result; }
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 1d209ea..0689533 100644 --- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java +++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -1786,6 +1786,23 @@ return liveFields.contains(field); } + public boolean isFieldRead(DexEncodedField field) { + FieldAccessInfoImpl info = fieldAccessInfoCollection.get(field.field); + return info != null && info.isRead(); + } + + public boolean isFieldWrittenOutsideDefaultConstructor(DexEncodedField field) { + FieldAccessInfoImpl info = fieldAccessInfoCollection.get(field.field); + if (info == null) { + return false; + } + DexClass clazz = appView.definitionFor(field.field.holder); + DexEncodedMethod defaultInitializer = clazz.getDefaultInitializer(); + return defaultInitializer != null + ? info.isWrittenOutside(defaultInitializer) + : info.isWritten(); + } + private boolean isInstantiatedOrHasInstantiatedSubtype(DexProgramClass clazz) { return directAndIndirectlyInstantiatedTypes.contains(clazz); }
diff --git a/src/main/java/com/android/tools/r8/utils/ProgramClassCollection.java b/src/main/java/com/android/tools/r8/utils/ProgramClassCollection.java index 1aef50b..21f78a7 100644 --- a/src/main/java/com/android/tools/r8/utils/ProgramClassCollection.java +++ b/src/main/java/com/android/tools/r8/utils/ProgramClassCollection.java
@@ -9,6 +9,7 @@ import com.android.tools.r8.graph.DexProgramClass; import com.android.tools.r8.graph.DexType; import com.android.tools.r8.ir.desugar.BackportedMethodRewriter; +import com.android.tools.r8.ir.desugar.DesugaredLibraryWrapperSynthesizer; import com.android.tools.r8.ir.desugar.InterfaceMethodRewriter; import com.android.tools.r8.ir.desugar.LambdaRewriter; import com.android.tools.r8.ir.desugar.NestBasedAccessDesugaring; @@ -85,6 +86,7 @@ || BackportedMethodRewriter.hasRewrittenMethodPrefix(a.type) || InterfaceMethodRewriter.hasDispatchClassSuffix(a.type) || NestBasedAccessDesugaring.isNestConstructor(a.type) + || DesugaredLibraryWrapperSynthesizer.isSynthesizedWrapper(a.type) || a.type.descriptor.toString().equals(TwrCloseResourceRewriter.UTILITY_CLASS_DESCRIPTOR); } }
diff --git a/src/test/java/com/android/tools/r8/classlookup/LibraryClassExtendsProgramClassTest.java b/src/test/java/com/android/tools/r8/classlookup/LibraryClassExtendsProgramClassTest.java index 231f2c1..5cbb40d 100644 --- a/src/test/java/com/android/tools/r8/classlookup/LibraryClassExtendsProgramClassTest.java +++ b/src/test/java/com/android/tools/r8/classlookup/LibraryClassExtendsProgramClassTest.java
@@ -5,21 +5,25 @@ package com.android.tools.r8.classlookup; import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent; -import static org.hamcrest.CoreMatchers.containsString; import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; -import static org.junit.Assume.assumeFalse; import static org.junit.Assume.assumeTrue; import com.android.tools.r8.CompilationFailedException; -import com.android.tools.r8.R8TestCompileResult; +import com.android.tools.r8.Diagnostic; +import com.android.tools.r8.R8FullTestBuilder; import com.android.tools.r8.TestBase; import com.android.tools.r8.TestParameters; import com.android.tools.r8.TestParametersCollection; import com.android.tools.r8.jasmin.JasminBuilder; +import com.android.tools.r8.utils.AndroidApiLevel; import com.android.tools.r8.utils.codeinspector.CodeInspector; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.ImmutableSet.Builder; import java.util.List; +import java.util.Set; +import org.hamcrest.CoreMatchers; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; @@ -37,6 +41,13 @@ @RunWith(Parameterized.class) public class LibraryClassExtendsProgramClassTest extends TestBase { + private static String[] libraryClassesExtendingTestCase = + new String[] { + "android.test.InstrumentationTestCase", + "android.test.AndroidTestCase", + "android.test.suitebuilder.TestSuiteBuilder$FailedToCreateTests" + }; + private static List<byte[]> junitClasses; @BeforeClass @@ -57,8 +68,15 @@ this.parameters = parameters; } + // The default library in the test runner is chosen to be the first one with at least the API + // level. The junit testing framework was removed in P. + private boolean libraryContainsJUnit() { + return parameters.isDexRuntime() + && parameters.getApiLevel().getLevel() < AndroidApiLevel.P.getLevel(); + } + private void checkClassesInResult(CodeInspector inspector) { - if (parameters.isDexRuntime()) { + if (libraryContainsJUnit()) { noClassesInResult(inspector); } else { testCaseClassInResult(inspector); @@ -76,9 +94,21 @@ assertThat(inspector.clazz(TestClass.class), isPresent()); } + private void checkDiagnostics(List<Diagnostic> diagnostics) { + Builder<String> builder = ImmutableSet.builder(); + for (String libraryClass : libraryClassesExtendingTestCase) { + builder.add( + "Library class " + libraryClass + " extends program class junit.framework.TestCase"); + } + Set<String> expected = builder.build(); + for (Diagnostic diagnostic : diagnostics) { + assertThat(expected, CoreMatchers.hasItem(diagnostic.getDiagnosticMessage())); + } + assertEquals(expected.size(), diagnostics.size()); + } + @Test public void testFullMode() throws Exception { - assumeFalse(parameters.getApiLevel().getLevel() == 28); testForR8(parameters.getBackend()) .setMinApi(parameters.getApiLevel()) .addProgramClasses(TestClass.class) @@ -93,7 +123,6 @@ @Test public void testCompatibilityMode() throws Exception { - assumeFalse(parameters.getApiLevel().getLevel() == 28); testForR8Compat(parameters.getBackend()) .setMinApi(parameters.getApiLevel()) .addProgramClasses(TestClass.class) @@ -119,56 +148,52 @@ } @Test - public void testFullModeError() { - assumeFalse(parameters.getApiLevel().getLevel() == 28); - assumeTrue("Only run for Dex backend", parameters.isDexRuntime()); + public void testFullModeError() throws Exception { + R8FullTestBuilder builder = + testForR8(parameters.getBackend()) + .setMinApi(parameters.getApiLevel()) + .addProgramClasses(TestClass.class) + .addProgramClassFileData(junitClasses) + .addKeepAllClassesRule() + .addOptionsModification(options -> options.lookupLibraryBeforeProgram = false); + if (!libraryContainsJUnit()) { + builder.compile().inspect(this::testCaseClassInResult).assertNoMessages(); + return; + } try { - testForR8(parameters.getBackend()) - .setMinApi(parameters.getApiLevel()) - .addProgramClasses(TestClass.class) - .addProgramClassFileData(junitClasses) - .addKeepAllClassesRule() - .addOptionsModification(options -> options.lookupLibraryBeforeProgram = false) - .compile(); - fail("Succeeded in full mode"); + builder.compileWithExpectedDiagnostics( + diagnostics -> { + diagnostics.assertOnlyErrors(); + checkDiagnostics(diagnostics.getErrors()); + }); + fail("Expected compilation failure"); } catch (CompilationFailedException e) { - // Ignore. + // Expected exception. } } @Test public void testCompatibilityModeWarning() throws Exception { - assumeFalse(parameters.getApiLevel().getLevel() == 28); - assumeTrue("Only run for Dex backend", parameters.isDexRuntime()); - R8TestCompileResult result = - testForR8Compat(parameters.getBackend()) - .setMinApi(parameters.getApiLevel()) - .addProgramClasses(TestClass.class) - .addProgramClassFileData(junitClasses) - .addKeepAllClassesRule() - .addOptionsModification(options -> options.lookupLibraryBeforeProgram = false) - .compile() - .assertOnlyWarnings(); - - String[] libraryClassesExtendingTestCase = new String[]{ - "android.test.InstrumentationTestCase", - "android.test.AndroidTestCase", - "android.test.suitebuilder.TestSuiteBuilder$FailedToCreateTests" - }; - - result.getDiagnosticMessages().assertWarningsCount(libraryClassesExtendingTestCase.length); - - for (String name : libraryClassesExtendingTestCase) { - result - .assertWarningMessageThatMatches( - containsString( - "Library class " + name + " extends program class junit.framework.TestCase")); - } + testForR8Compat(parameters.getBackend()) + .setMinApi(parameters.getApiLevel()) + .addProgramClasses(TestClass.class) + .addProgramClassFileData(junitClasses) + .addKeepAllClassesRule() + .addOptionsModification(options -> options.lookupLibraryBeforeProgram = false) + .compileWithExpectedDiagnostics( + diagnostics -> { + if (libraryContainsJUnit()) { + diagnostics.assertOnlyWarnings(); + checkDiagnostics(diagnostics.getWarnings()); + } else { + diagnostics.assertNoMessages(); + } + }) + .inspect(this::testCaseClassInResult); } @Test public void testWithDontWarn() throws Exception { - assumeTrue("Only run for Dex backend", parameters.isDexRuntime()); testForR8(parameters.getBackend()) .setMinApi(parameters.getApiLevel()) .addProgramClassFileData(junitClasses)
diff --git a/src/test/java/com/android/tools/r8/desugar/corelib/CustomCollectionTest.java b/src/test/java/com/android/tools/r8/desugar/corelib/CustomCollectionTest.java index 9a8bb6b..71cbfcd 100644 --- a/src/test/java/com/android/tools/r8/desugar/corelib/CustomCollectionTest.java +++ b/src/test/java/com/android/tools/r8/desugar/corelib/CustomCollectionTest.java
@@ -11,8 +11,10 @@ import com.android.tools.r8.R8TestRunResult; import com.android.tools.r8.TestParameters; import com.android.tools.r8.ToolHelper.DexVm; +import com.android.tools.r8.ir.desugar.DesugaredLibraryWrapperSynthesizer; import com.android.tools.r8.utils.BooleanUtils; import com.android.tools.r8.utils.codeinspector.CodeInspector; +import com.android.tools.r8.utils.codeinspector.FoundClassSubject; import com.android.tools.r8.utils.codeinspector.InstructionSubject; import com.android.tools.r8.utils.codeinspector.InstructionSubject.JumboStringMode; import com.android.tools.r8.utils.codeinspector.MethodSubject; @@ -57,8 +59,6 @@ public void testCustomCollectionD8() throws Exception { // TODO(b/142377475). Assume.assumeTrue(!shrinkDesugaredLibrary); - // TODO(b/142377161). - Assume.assumeTrue(parameters.getRuntime().asDex().getVm().isNewerThan(DexVm.ART_4_4_4_HOST)); KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters); D8TestRunResult d8TestRunResult = testForD8() @@ -66,7 +66,9 @@ .setMinApi(parameters.getApiLevel()) .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer) .compile() - .inspect(inspector -> this.assertCustomCollectionCallsCorrect(inspector, false)) + .inspect(inspector -> { + this.assertNoWrappers(inspector); + this.assertCustomCollectionCallsCorrect(inspector, false);}) .addDesugaredCoreLibraryRunClassPath( this::buildDesugaredLibrary, parameters.getApiLevel(), @@ -89,8 +91,6 @@ @Test public void testCustomCollectionR8() throws Exception { - // TODO(b/142377161). - Assume.assumeTrue(parameters.getRuntime().asDex().getVm().isNewerThan(DexVm.ART_4_4_4_HOST)); KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters); R8TestRunResult r8TestRunResult = testForR8(Backend.DEX) @@ -104,7 +104,9 @@ }) .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer) .compile() - .inspect(inspector -> this.assertCustomCollectionCallsCorrect(inspector, true)) + .inspect(inspector -> { + this.assertNoWrappers(inspector); + this.assertCustomCollectionCallsCorrect(inspector, true);}) .addDesugaredCoreLibraryRunClassPath( this::buildDesugaredLibrary, parameters.getApiLevel(), @@ -125,6 +127,11 @@ } } + private void assertNoWrappers(CodeInspector inspector) { + assertTrue(inspector.allClasses().stream().noneMatch(cl -> cl.getOriginalName().startsWith( + DesugaredLibraryWrapperSynthesizer.WRAPPER_PREFIX))); + } + private void assertCustomCollectionCallsCorrect(CodeInspector inspector, boolean r8) { MethodSubject direct = inspector.clazz(EXECUTOR).uniqueMethodWithName("directTypes"); // TODO(b/134732760): Due to memberRebinding, R8 is not as precise as D8 regarding
diff --git a/src/test/java/com/android/tools/r8/desugar/corelib/conversionTests/MoreFunctionConversionTest.java b/src/test/java/com/android/tools/r8/desugar/corelib/conversionTests/MoreFunctionConversionTest.java index 80a61e7..fd3af23 100644 --- a/src/test/java/com/android/tools/r8/desugar/corelib/conversionTests/MoreFunctionConversionTest.java +++ b/src/test/java/com/android/tools/r8/desugar/corelib/conversionTests/MoreFunctionConversionTest.java
@@ -38,12 +38,12 @@ this::buildDesugaredLibraryWithConversionExtension, AndroidApiLevel.B) .addRunClasspathFiles(customLib) .run(new DexRuntime(DexVm.ART_9_0_0_HOST), Executor.class) - // TODO(clement): Make output reliable and put back expected results. - .assertSuccess(); + .assertSuccessWithOutput(StringUtils.lines("6","6","6","6","6")); } // If we have the exact same lambda in both, but one implements j$..Function and the other // java..Function, ART is obviously very confused. + // This happens for instance when String::length function is used both in CustomLib and Executor. private void assertNoDuplicateLambdas(Path program, Path customLib) throws Exception { CodeInspector programInspector = new CodeInspector(program); CodeInspector customLibInspector = new CodeInspector(customLib); @@ -76,30 +76,34 @@ } public static void oneParameterReturn() { - Function<Object, String> toString = Object::toString; + Function<Object, String> toString = getObjectStringConv(); Function<Object, Integer> oneParam = CustomLibClass.oneParameterReturn(toString); System.out.println(oneParam.apply(new Object())); } public static void twoParametersReturn() { - Function<Object, String> toString = Object::toString; + Function<Object, String> toString = getObjectStringConv(); Function<String, Integer> length = String::length; Function<Object, Integer> twoParam = CustomLibClass.twoParametersReturn(toString, length); System.out.println(twoParam.apply(new Object())); } public static void oneParameter() { - Function<Object, String> toString = Object::toString; + Function<Object, String> toString = getObjectStringConv(); int res = CustomLibClass.oneParameter(toString); System.out.println(res); } public static void twoParameters() { - Function<Object, String> toString = Object::toString; + Function<Object, String> toString = getObjectStringConv(); Function<String, Integer> length = String::length; int res = CustomLibClass.twoParameters(toString, length); System.out.println(res); } + + public static Function<Object, String> getObjectStringConv() { + return (Object o) -> o.getClass().getSimpleName(); + } } // This class will be put at compilation time as library and on the runtime class path. @@ -130,13 +134,14 @@ return f1.andThen(f2).apply(new Object()); } - // Following functions are defined to avoid name collision with the program. + // Following functions are defined to avoid name collision with the program. Name collision + // happens for instance when String::length function is used both in CustomLib and Executor. public static Function<String, Integer> getStringIntConv() { return (String s) -> 1 + s.length() - 1; } public static Function<Object, String> getObjectStringConv() { - return (Object o) -> "" + o.toString() + ""; + return (Object o) -> "" + o.getClass().getSimpleName() + ""; } } }
diff --git a/src/test/java/com/android/tools/r8/desugar/corelib/conversionTests/WrapperMergeTest.java b/src/test/java/com/android/tools/r8/desugar/corelib/conversionTests/WrapperMergeTest.java new file mode 100644 index 0000000..c4d1880 --- /dev/null +++ b/src/test/java/com/android/tools/r8/desugar/corelib/conversionTests/WrapperMergeTest.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.desugar.corelib.conversionTests; + +import static org.junit.Assert.assertEquals; +import com.android.tools.r8.TestRuntime.DexRuntime; +import com.android.tools.r8.ToolHelper.DexVm; +import com.android.tools.r8.ir.desugar.DesugaredLibraryWrapperSynthesizer; +import com.android.tools.r8.utils.AndroidApiLevel; +import com.android.tools.r8.utils.StringUtils; +import com.android.tools.r8.utils.codeinspector.CodeInspector; +import java.nio.file.Path; +import java.util.Arrays; +import org.junit.Test; + +public class WrapperMergeTest extends APIConversionTestBase { + + @Test + public void testWrapperMerge() throws Exception { + // Multiple wrapper classes have to be merged here. + Path path1 = testForD8() + .addProgramClasses(Executor1.class) + .setMinApi(AndroidApiLevel.B) + .enableCoreLibraryDesugaring(AndroidApiLevel.B) + .compile() + .inspect(this::assertWrappers) + .writeToZip(); + Path path2 = testForD8() + .addProgramClasses(Executor2.class) + .setMinApi(AndroidApiLevel.B) + .enableCoreLibraryDesugaring(AndroidApiLevel.B) + .compile() + .inspect(this::assertWrappers) + .writeToZip(); + testForD8() + .addProgramFiles(path1,path2) + .compile() + .addDesugaredCoreLibraryRunClassPath( + this::buildDesugaredLibraryWithConversionExtension, AndroidApiLevel.B) + .run(new DexRuntime(DexVm.ART_9_0_0_HOST), Executor1.class) + .assertSuccessWithOutput( + StringUtils.lines("[1, 2, 3]")); + + } + + private void assertWrappers(CodeInspector inspector) { + assertEquals(2,inspector.allClasses().stream().filter(c -> c.getOriginalName().contains( + DesugaredLibraryWrapperSynthesizer.WRAPPER_PREFIX)).count()); + } + + static class Executor1 { + + public static void main(String[] args) { + int[] ints = new int[3]; + Arrays.setAll(ints,x->x+1); + System.out.println(Arrays.toString(ints)); + } + } + + static class Executor2 { + + public static void main(String[] args) { + int[] ints = new int[3]; + Arrays.setAll(ints,x->x+2); + System.out.println(Arrays.toString(ints)); + } + } + +}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/inliner/interfacemethods/InlineDefaultInterfaceMethodTest.java b/src/test/java/com/android/tools/r8/ir/optimize/inliner/interfacemethods/InlineDefaultInterfaceMethodTest.java index 26a4f2f..df9ab8d 100644 --- a/src/test/java/com/android/tools/r8/ir/optimize/inliner/interfacemethods/InlineDefaultInterfaceMethodTest.java +++ b/src/test/java/com/android/tools/r8/ir/optimize/inliner/interfacemethods/InlineDefaultInterfaceMethodTest.java
@@ -4,53 +4,58 @@ package com.android.tools.r8.ir.optimize.inliner.interfacemethods; -import static com.android.tools.r8.ir.desugar.InterfaceMethodRewriter.COMPANION_CLASS_NAME_SUFFIX; -import static com.android.tools.r8.ir.desugar.InterfaceMethodRewriter.DEFAULT_METHOD_PREFIX; -import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent; -import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.Assert.assertEquals; 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.utils.AndroidApiLevel; +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 org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; +@RunWith(Parameterized.class) public class InlineDefaultInterfaceMethodTest extends TestBase { + private final TestParameters parameters; + + @Parameters(name = "{0}") + public static TestParametersCollection data() { + return getTestParameters().withAllRuntimesAndApiLevels().build(); + } + + public InlineDefaultInterfaceMethodTest(TestParameters parameters) { + this.parameters = parameters; + } + @Test public void test() throws Exception { String expectedOutput = StringUtils.lines("Hello world!"); CodeInspector inspector = - testForR8(Backend.DEX) + testForR8(parameters.getBackend()) .addInnerClasses(InlineDefaultInterfaceMethodTest.class) .addKeepMainRule(TestClass.class) - .setMinApi(AndroidApiLevel.M) + .setMinApi(parameters.getApiLevel()) .enableClassInliningAnnotations() .enableMergeAnnotations() .noMinification() - .run(TestClass.class) + .run(parameters.getRuntime(), TestClass.class) .assertSuccessWithOutput(expectedOutput) .inspector(); - // TODO(b/124017330): interface methods should have been inlined into C.method(). - ClassSubject classSubject = - inspector.clazz(I.class.getTypeName() + COMPANION_CLASS_NAME_SUFFIX); - assertThat(classSubject, isPresent()); - assertThat(classSubject.uniqueMethodWithName(DEFAULT_METHOD_PREFIX + "hello"), isPresent()); - assertThat(classSubject.uniqueMethodWithName(DEFAULT_METHOD_PREFIX + "space"), isPresent()); - assertThat(classSubject.uniqueMethodWithName(DEFAULT_METHOD_PREFIX + "world"), isPresent()); + // After inlining, only one class remains, namely TestClass. + assertEquals(1, inspector.allClasses().size()); } static class TestClass { public static void main(String[] args) { - C obj = new C(); - obj.method(); + new C().method(); } } @@ -73,7 +78,6 @@ @NeverClassInline static class C implements I { - @NeverInline public void method() { // invoke-virtual hello();
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/inliner/interfacemethods/InlineStaticInterfaceMethodTest.java b/src/test/java/com/android/tools/r8/ir/optimize/inliner/interfacemethods/InlineStaticInterfaceMethodTest.java index 9eb57fb..49dee4b 100644 --- a/src/test/java/com/android/tools/r8/ir/optimize/inliner/interfacemethods/InlineStaticInterfaceMethodTest.java +++ b/src/test/java/com/android/tools/r8/ir/optimize/inliner/interfacemethods/InlineStaticInterfaceMethodTest.java
@@ -4,37 +4,47 @@ package com.android.tools.r8.ir.optimize.inliner.interfacemethods; -import static com.android.tools.r8.ir.desugar.InterfaceMethodRewriter.COMPANION_CLASS_NAME_SUFFIX; -import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent; -import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.Assert.assertEquals; import com.android.tools.r8.TestBase; -import com.android.tools.r8.utils.AndroidApiLevel; +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 org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; +@RunWith(Parameterized.class) public class InlineStaticInterfaceMethodTest extends TestBase { + private final TestParameters parameters; + + @Parameters(name = "{0}") + public static TestParametersCollection data() { + return getTestParameters().withAllRuntimesAndApiLevels().build(); + } + + public InlineStaticInterfaceMethodTest(TestParameters parameters) { + this.parameters = parameters; + } + @Test public void test() throws Exception { String expectedOutput = StringUtils.lines("Hello world!"); CodeInspector inspector = - testForR8(Backend.DEX) + testForR8(parameters.getBackend()) .addInnerClasses(InlineStaticInterfaceMethodTest.class) .addKeepMainRule(TestClass.class) - .setMinApi(AndroidApiLevel.M) - .run(TestClass.class) + .setMinApi(parameters.getApiLevel()) + .run(parameters.getRuntime(), TestClass.class) .assertSuccessWithOutput(expectedOutput) .inspector(); - // TODO(b/124017330): greet() should have been inlined into main(). - ClassSubject classSubject = - inspector.clazz(I.class.getTypeName() + COMPANION_CLASS_NAME_SUFFIX); - assertThat(classSubject, isPresent()); - assertThat(classSubject.uniqueMethodWithName("greet"), isPresent()); + // After inlining of I.greet(), only one class remains, namely TestClass. + assertEquals(1, inspector.allClasses().size()); } static class TestClass {
diff --git a/tools/r8_release.py b/tools/r8_release.py index 7ce3582..4d9273c 100755 --- a/tools/r8_release.py +++ b/tools/r8_release.py
@@ -130,8 +130,6 @@ def prepare_google3(): - utils.check_prodacces() - # Check if an existing client exists. if ':update-r8:' in subprocess.check_output('g4 myclients', shell=True): print "Remove the existing 'update-r8' client before continuing." @@ -217,7 +215,7 @@ result.add_argument('--version', required=True, help='The new version of R8 (e.g., 1.4.51)') - result.add_argument('--no_sync', + result.add_argument('--no-sync', '--no_sync', default=False, action='store_true', help='Do not sync repos before uploading') @@ -246,6 +244,9 @@ def main(): args = parse_options() targets_to_run = [] + if args.google3 or args.studio: + utils.check_prodacces() + if args.google3: targets_to_run.append(prepare_google3()) if args.studio: