Nest desugaring should raise warning
- Desugaring should raise warnings and no errors
- Updated tests to catch warnings
- Logic now relies on nest host equality, so the logic
partially works if the nest host is absent, and the
map kept in desugaring are smaller.
Bug:132682295
Change-Id: I10bcaa692bd191ea71357b51a06b73d3bcc80e2f
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/D8NestBasedAccessDesugaring.java b/src/main/java/com/android/tools/r8/ir/desugar/D8NestBasedAccessDesugaring.java
index 3b0a46b..c79e13a 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/D8NestBasedAccessDesugaring.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/D8NestBasedAccessDesugaring.java
@@ -34,18 +34,14 @@
// - Optimize bridges (Bridges processed concurrently).
public class D8NestBasedAccessDesugaring extends NestBasedAccessDesugaring {
- // Map the nest host to its nest members, including the nest host itself.
- private final Map<DexType, List<DexType>> metNests = new ConcurrentHashMap<>();
+ // Maps a nest host to a class met which has that nest host.
+ // The value is used because the nest host might be missing.
+ private final Map<DexType, DexClass> metNestHosts = new ConcurrentHashMap<>();
public D8NestBasedAccessDesugaring(AppView<?> appView) {
super(appView);
}
- private List<DexType> getNestFor(DexClass clazz) {
- DexType nestHostType = clazz.getNestHost();
- return metNests.computeIfAbsent(nestHostType, host -> extractNest(clazz));
- }
-
public void rewriteNestBasedAccesses(
DexEncodedMethod encodedMethod, IRCode code, AppView<?> appView) {
DexClass currentClass = appView.definitionFor(encodedMethod.method.holder);
@@ -53,7 +49,7 @@
if (!currentClass.isInANest()) {
return;
}
- List<DexType> nest = getNestFor(currentClass);
+ metNestHosts.put(currentClass.getNestHost(), currentClass);
ListIterator<BasicBlock> blocks = code.listIterator();
while (blocks.hasNext()) {
@@ -66,7 +62,7 @@
DexMethod methodCalled = invokeMethod.getInvokedMethod();
DexEncodedMethod encodedMethodCalled = appView.definitionFor(methodCalled);
if (encodedMethodCalled != null
- && invokeRequiresRewriting(encodedMethodCalled, nest, encodedMethod.method.holder)) {
+ && invokeRequiresRewriting(encodedMethodCalled, currentClass, appView)) {
DexMethod bridge = ensureInvokeBridge(encodedMethodCalled);
if (encodedMethodCalled.isInstanceInitializer()) {
instructions.previous();
@@ -87,7 +83,7 @@
DexEncodedField encodedField =
appView.definitionFor(instruction.asFieldInstruction().getField());
if (encodedField != null
- && fieldAccessRequiresRewriting(encodedField, nest, encodedMethod.method.holder)) {
+ && fieldAccessRequiresRewriting(encodedField, currentClass, appView)) {
if (instruction.isInstanceGet() || instruction.isStaticGet()) {
DexMethod bridge = ensureFieldAccessBridge(encodedField, true);
instructions.replaceCurrentInstruction(
@@ -104,11 +100,10 @@
}
}
- private void processNestsConcurrently(
- List<List<DexType>> liveNests, ExecutorService executorService) throws ExecutionException {
+ private void processNestsConcurrently(ExecutorService executorService) throws ExecutionException {
List<Future<?>> futures = new ArrayList<>();
- for (List<DexType> nest : liveNests) {
- futures.add(asyncProcessNest(nest, executorService));
+ for (DexClass clazz : metNestHosts.values()) {
+ futures.add(asyncProcessNest(clazz, executorService));
}
ThreadUtils.awaitFutures(futures);
}
@@ -116,8 +111,7 @@
public void desugarNestBasedAccess(
DexApplication.Builder<?> builder, ExecutorService executorService, IRConverter converter)
throws ExecutionException {
- List<List<DexType>> metNests = new ArrayList<>(this.metNests.values());
- processNestsConcurrently(metNests, executorService);
+ processNestsConcurrently(executorService);
addDeferredBridges();
synthetizeNestConstructor(builder);
optimizeDeferredBridgesConcurrently(executorService, converter);
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/NestBasedAccessDesugaring.java b/src/main/java/com/android/tools/r8/ir/desugar/NestBasedAccessDesugaring.java
index 865049c..8086767 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/NestBasedAccessDesugaring.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/NestBasedAccessDesugaring.java
@@ -2,6 +2,7 @@
import com.android.tools.r8.dex.Constants;
import com.android.tools.r8.errors.CompilationError;
+import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.ClassAccessFlags;
import com.android.tools.r8.graph.DexAnnotationSet;
@@ -21,6 +22,7 @@
import com.android.tools.r8.ir.conversion.IRConverter;
import com.android.tools.r8.origin.SynthesizedOrigin;
import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.StringDiagnostic;
import com.android.tools.r8.utils.ThreadUtils;
import java.util.ArrayList;
import java.util.Collection;
@@ -71,11 +73,12 @@
}
// Extract the list of types in the programClass' nest, of host hostClass
- List<DexType> extractNest(DexClass clazz) {
+ private List<DexType> extractNest(DexClass clazz) {
assert clazz != null;
DexClass hostClass = clazz.isNestHost() ? clazz : appView.definitionFor(clazz.getNestHost());
if (hostClass == null) {
- throw abortCompilationDueToMissingNestHost(clazz);
+ reportMissingNestHost(clazz);
+ return null;
}
List<DexType> classesInNest =
new ArrayList<>(hostClass.getNestMembersClassAttributes().size() + 1);
@@ -86,26 +89,33 @@
return classesInNest;
}
- Future<?> asyncProcessNest(List<DexType> nest, ExecutorService executorService) {
+ Future<?> asyncProcessNest(DexClass clazz, ExecutorService executorService) {
return executorService.submit(
() -> {
- processNest(nest);
+ List<DexType> nest = extractNest(clazz);
+ if (nest != null) {
+ processNest(nest);
+ }
return null; // we want a Callable not a Runnable to be able to throw
});
}
private void processNest(List<DexType> nest) {
+ boolean reported = false;
for (DexType type : nest) {
DexClass clazz = appView.definitionFor(type);
if (clazz == null) {
- // TODO(b/130529338) We could throw only a warning if a class is missing.
- throw abortCompilationDueToIncompleteNest(nest);
- }
- if (shouldProcessClassInNest(clazz, nest)) {
- NestBasedAccessDesugaringUseRegistry registry =
- new NestBasedAccessDesugaringUseRegistry(nest, clazz);
- for (DexEncodedMethod method : clazz.methods()) {
- method.registerCodeReferences(registry);
+ if (!reported) {
+ reportIncompleteNest(nest);
+ reported = true;
+ }
+ } else {
+ if (shouldProcessClassInNest(clazz, nest)) {
+ NestBasedAccessDesugaringUseRegistry registry =
+ new NestBasedAccessDesugaringUseRegistry(clazz);
+ for (DexEncodedMethod method : clazz.methods()) {
+ method.registerCodeReferences(registry);
+ }
}
}
}
@@ -139,7 +149,7 @@
ThreadUtils.awaitFutures(futures);
}
- private RuntimeException abortCompilationDueToIncompleteNest(List<DexType> nest) {
+ private void reportIncompleteNest(List<DexType> nest) {
List<String> programClassesFromNest = new ArrayList<>();
List<String> unavailableClasses = new ArrayList<>();
List<String> classPathClasses = new ArrayList<>();
@@ -174,17 +184,26 @@
.append(String.join(", ", classPathClasses))
.append(" from the same nest are on class path).");
}
- throw new CompilationError(stringBuilder.toString());
+ if (!libraryClasses.isEmpty()) {
+ throw new CompilationError(stringBuilder.toString());
+ }
+ // TODO (b/132676197): Use desugaring warning
+ appView.options().reporter.warning(new StringDiagnostic(stringBuilder.toString()));
}
- private RuntimeException abortCompilationDueToMissingNestHost(DexClass compiledClass) {
- String nestHostName = compiledClass.getNestHostClassAttribute().getNestHost().getName();
- throw new CompilationError(
+ private void reportMissingNestHost(DexClass compiledClass) {
+ String nestHostName = compiledClass.getNestHost().getName();
+ String message =
"Class "
+ compiledClass.type.getName()
+ " requires its nest host "
+ nestHostName
- + " to be on program or class path for compilation to succeed.");
+ + " to be on program or class path for compilation to succeed.";
+ if (compiledClass.isLibraryClass()) {
+ throw new CompilationError(message);
+ }
+ // TODO (b/132676197): Use desugaring warning
+ appView.options().reporter.warning(new StringDiagnostic(message));
}
private DexProgramClass createNestAccessConstructor() {
@@ -286,19 +305,27 @@
}
static boolean invokeRequiresRewriting(
- DexEncodedMethod method, List<DexType> contextNest, DexType contextType) {
+ DexEncodedMethod method, DexClass contextClass, AppView<?> appView) {
+ assert method != null;
// Rewrite only when targeting other nest members private fields.
- return method.accessFlags.isPrivate()
- && method.method.holder != contextType
- && contextNest.contains(method.method.holder);
+ if (!method.accessFlags.isPrivate() || method.method.holder == contextClass.type) {
+ return false;
+ }
+ DexClass methodHolder = appView.definitionFor(method.method.holder);
+ assert methodHolder != null; // from encodedMethod
+ return methodHolder.getNestHost() == contextClass.getNestHost();
}
static boolean fieldAccessRequiresRewriting(
- DexEncodedField field, List<DexType> contextNest, DexType contextType) {
+ DexEncodedField field, DexClass contextClass, AppView<?> appView) {
+ assert field != null;
// Rewrite only when targeting other nest members private fields.
- return field.accessFlags.isPrivate()
- && field.field.holder != contextType
- && contextNest.contains(field.field.holder);
+ if (!field.accessFlags.isPrivate() || field.field.holder == contextClass.type) {
+ return false;
+ }
+ DexClass fieldHolder = appView.definitionFor(field.field.holder);
+ assert fieldHolder != null; // from encodedField
+ return fieldHolder.getNestHost() == contextClass.getNestHost();
}
private boolean holderRequiresBridge(DexClass holder) {
@@ -310,7 +337,11 @@
return true;
}
assert holder.isLibraryClass();
- throw abortCompilationDueToIncompleteNest(extractNest(holder));
+ List<DexType> nest = extractNest(holder);
+ assert nest != null : "Should be a compilation error if missing nest host on library class.";
+ reportIncompleteNest(nest);
+ throw new Unreachable(
+ "Incomplete nest due to missing library class should raise a compilation error.");
}
DexMethod ensureFieldAccessBridge(DexEncodedField field, boolean isGet) {
@@ -358,19 +389,16 @@
protected class NestBasedAccessDesugaringUseRegistry extends UseRegistry {
- private final List<DexType> nest;
private final DexClass currentClass;
- NestBasedAccessDesugaringUseRegistry(List<DexType> nest, DexClass currentClass) {
+ NestBasedAccessDesugaringUseRegistry(DexClass currentClass) {
super(appView.options().itemFactory);
- this.nest = nest;
this.currentClass = currentClass;
}
private boolean registerInvoke(DexMethod method) {
DexEncodedMethod encodedMethod = appView.definitionFor(method);
- if (encodedMethod != null
- && invokeRequiresRewriting(encodedMethod, nest, currentClass.type)) {
+ if (encodedMethod != null && invokeRequiresRewriting(encodedMethod, currentClass, appView)) {
ensureInvokeBridge(encodedMethod);
return true;
}
@@ -380,7 +408,7 @@
private boolean registerFieldAccess(DexField field, boolean isGet) {
DexEncodedField encodedField = appView.definitionFor(field);
if (encodedField != null
- && fieldAccessRequiresRewriting(encodedField, nest, currentClass.type)) {
+ && fieldAccessRequiresRewriting(encodedField, currentClass, appView)) {
ensureFieldAccessBridge(encodedField, isGet);
return true;
}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/NestedPrivateMethodLense.java b/src/main/java/com/android/tools/r8/ir/desugar/NestedPrivateMethodLense.java
index a37b8ac..c782edf 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/NestedPrivateMethodLense.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/NestedPrivateMethodLense.java
@@ -11,20 +11,15 @@
import com.android.tools.r8.graph.GraphLense.NestedGraphLense;
import com.android.tools.r8.ir.code.Invoke;
import com.google.common.collect.ImmutableMap;
-import java.util.IdentityHashMap;
-import java.util.List;
-import java.util.Map;
public class NestedPrivateMethodLense extends NestedGraphLense {
private final AppView<?> appView;
// Map from nestHost to nest members including nest hosts
- private final Map<DexType, List<DexType>> nestMap;
private final DexType nestConstructorType;
public NestedPrivateMethodLense(
AppView<?> appView,
- Map<DexType, List<DexType>> nestMap,
DexType nestConstructorType,
GraphLense previousLense) {
super(
@@ -36,23 +31,16 @@
previousLense,
appView.dexItemFactory());
this.appView = appView;
- // We do not want a concurrent Map here.
- assert nestMap instanceof IdentityHashMap;
- this.nestMap = nestMap;
this.nestConstructorType = nestConstructorType;
}
- private List<DexType> getNestFor(DexType type) {
- DexClass clazz = appView.definitionFor(type);
- DexType hostType = clazz.isNestHost() ? clazz.type : clazz.getNestHost();
- return nestMap.get(hostType);
- }
-
private DexMethod lookupFieldForMethod(DexField field, DexMethod context, boolean isGet) {
DexEncodedField encodedField = appView.definitionFor(field);
+ DexClass contextClass = appView.definitionFor(context.holder);
+ assert contextClass != null;
if (encodedField != null
&& NestBasedAccessDesugaring.fieldAccessRequiresRewriting(
- encodedField, getNestFor(context.holder), context.holder)) {
+ encodedField, contextClass, appView)) {
return NestBasedAccessDesugaring.computeFieldBridge(encodedField, isGet, appView);
}
return null;
@@ -107,9 +95,11 @@
return previous;
}
DexEncodedMethod encodedMethod = appView.definitionFor(method);
+ DexClass contextClass = appView.definitionFor(context.holder);
+ assert contextClass != null;
if (encodedMethod == null
|| !NestBasedAccessDesugaring.invokeRequiresRewriting(
- encodedMethod, getNestFor(context.holder), context.holder)) {
+ encodedMethod, contextClass, appView)) {
return previous;
}
DexMethod bridge;
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/R8NestBasedAccessDesugaring.java b/src/main/java/com/android/tools/r8/ir/desugar/R8NestBasedAccessDesugaring.java
index e3e14a3..977c7d9 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/R8NestBasedAccessDesugaring.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/R8NestBasedAccessDesugaring.java
@@ -7,9 +7,10 @@
import com.android.tools.r8.graph.GraphLense;
import com.android.tools.r8.utils.ThreadUtils;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.IdentityHashMap;
import java.util.List;
-import java.util.Map;
+import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
@@ -20,7 +21,7 @@
// - Add bridges to be processed by further passes (Sequential).
public class R8NestBasedAccessDesugaring extends NestBasedAccessDesugaring {
- private Map<DexType, List<DexType>> nestMap = new IdentityHashMap<>();
+ private boolean atLeast1Nest = false;
public R8NestBasedAccessDesugaring(AppView<?> appView) {
super(appView);
@@ -31,16 +32,17 @@
return appView.graphLense();
}
computeAndProcessNestsConcurrently(executorService);
- if (nestMap.isEmpty()) {
+ if (!atLeast1Nest) {
+ // Common path when no class file was generated by JDK11+.
return appView.graphLense();
}
addDeferredBridges();
- return new NestedPrivateMethodLense(
- appView, nestMap, getNestConstructorType(), appView.graphLense());
+ return new NestedPrivateMethodLense(appView, getNestConstructorType(), appView.graphLense());
}
private void computeAndProcessNestsConcurrently(ExecutorService executorService)
throws ExecutionException {
+ Set<DexType> nestHosts = Collections.newSetFromMap(new IdentityHashMap<>());
List<Future<?>> futures = new ArrayList<>();
// It is possible that a nest member is on the program path but its nest host
// is only in the class path (or missing, raising an error).
@@ -49,10 +51,10 @@
for (DexProgramClass clazz : appView.appInfo().classes()) {
if (clazz.isInANest()) {
DexType hostType = clazz.getNestHost();
- if (!nestMap.containsKey(hostType)) {
- List<DexType> nest = extractNest(clazz);
- nestMap.put(hostType, nest);
- futures.add(asyncProcessNest(nest, executorService));
+ if (!nestHosts.contains(hostType)) {
+ nestHosts.add(hostType);
+ atLeast1Nest = true;
+ futures.add(asyncProcessNest(clazz, executorService));
}
}
}
diff --git a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestCompilationErrorTest.java b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestCompilationErrorTest.java
deleted file mode 100644
index 7b6e9f0..0000000
--- a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestCompilationErrorTest.java
+++ /dev/null
@@ -1,107 +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.nestaccesscontrol;
-
-import static com.android.tools.r8.desugar.nestaccesscontrol.NestAccessControlTestUtils.CLASSES_PATH;
-import static com.android.tools.r8.desugar.nestaccesscontrol.NestAccessControlTestUtils.CLASS_NAMES;
-import static com.android.tools.r8.utils.FileUtils.CLASS_EXTENSION;
-import static java.util.stream.Collectors.toList;
-import static org.hamcrest.core.StringContains.containsString;
-import static org.hamcrest.core.StringEndsWith.endsWith;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
-import com.android.tools.r8.TestBase;
-import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.ToolHelper.DexVm;
-import java.nio.file.Path;
-import java.util.List;
-import org.hamcrest.Matcher;
-import org.junit.Assume;
-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 NestCompilationErrorTest extends TestBase {
-
- public NestCompilationErrorTest(TestParameters parameters) {
- this.parameters = parameters;
- }
-
- private final TestParameters parameters;
-
- @Parameters(name = "{0}")
- public static TestParametersCollection data() {
- return getTestParameters()
- .withDexRuntime(DexVm.Version.first())
- .withDexRuntime(DexVm.Version.last())
- .withAllApiLevels()
- .build();
- }
-
- @Test
- public void testErrorD8() {
- // TODO (b/132147492): use diagnosis handler
- Assume.assumeTrue(parameters.isDexRuntime());
- testMissingNestHostError(true);
- testIncompleteNestError(true);
- }
-
- @Test
- public void testErrorR8() {
- // TODO (b/132147492): use diagnosis handler
- Assume.assumeTrue(parameters.isDexRuntime());
- testMissingNestHostError(false);
- testIncompleteNestError(false);
- }
-
- private void compileOnlyClassesMatching(Matcher<String> matcher, boolean d8) throws Exception {
- List<Path> matchingClasses =
- CLASS_NAMES.stream()
- .filter(matcher::matches)
- .map(name -> CLASSES_PATH.resolve(name + CLASS_EXTENSION))
- .collect(toList());
- if (d8) {
- testForD8()
- .setMinApi(parameters.getApiLevel())
- .addProgramFiles(matchingClasses)
- .addOptionsModification(options -> options.enableNestBasedAccessDesugaring = true)
- .compile();
- } else {
- testForR8(parameters.getBackend())
- .noTreeShaking()
- .noMinification()
- .addKeepAllAttributes()
- .setMinApi(parameters.getApiLevel())
- .addProgramFiles(matchingClasses)
- .addOptionsModification(options -> options.enableNestBasedAccessDesugaring = true)
- .compile();
- }
- }
-
- private void testMissingNestHostError(boolean d8) {
- try {
- Matcher<String> innerClassMatcher =
- containsString("BasicNestHostWithInnerClassMethods$BasicNestedClass");
- compileOnlyClassesMatching(innerClassMatcher, d8);
- fail("Should have raised an exception for missing nest host");
- } catch (Exception e) {
- assertTrue(e.getCause().getMessage().contains("requires its nest host"));
- }
- }
-
- private void testIncompleteNestError(boolean d8) {
- try {
- Matcher<String> innerClassMatcher = endsWith("BasicNestHostWithInnerClassMethods");
- compileOnlyClassesMatching(innerClassMatcher, d8);
- fail("Should have raised an exception for incomplete nest");
- } catch (Exception e) {
- assertTrue(e.getCause().getMessage().contains("requires its nest mates"));
- }
- }
-}
diff --git a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestCompilationExceptionTest.java b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestCompilationExceptionTest.java
new file mode 100644
index 0000000..601e634
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestCompilationExceptionTest.java
@@ -0,0 +1,148 @@
+// 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.nestaccesscontrol;
+
+import static com.android.tools.r8.desugar.nestaccesscontrol.NestAccessControlTestUtils.CLASSES_PATH;
+import static com.android.tools.r8.desugar.nestaccesscontrol.NestAccessControlTestUtils.CLASS_NAMES;
+import static com.android.tools.r8.utils.FileUtils.CLASS_EXTENSION;
+import static java.util.stream.Collectors.toList;
+import static org.hamcrest.core.StringContains.containsString;
+import static org.hamcrest.core.StringEndsWith.endsWith;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestCompileResult;
+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.DexVm;
+import java.nio.file.Path;
+import java.util.List;
+import org.hamcrest.Matcher;
+import org.junit.Assume;
+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 NestCompilationExceptionTest extends TestBase {
+
+ public NestCompilationExceptionTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters()
+ .withCfRuntimesStartingFromIncluding(CfVm.JDK11)
+ .withDexRuntime(DexVm.Version.first())
+ .withDexRuntime(DexVm.Version.last())
+ .withAllApiLevels()
+ .build();
+ }
+
+ @Test
+ public void testWarningD8() throws Exception {
+ // TODO (b/132676197): use desugaring handling
+ Assume.assumeTrue(parameters.isDexRuntime());
+ testIncompleteNestWarning(true);
+ testMissingNestHostWarning(true);
+ }
+
+ @Test
+ public void testWarningR8() throws Exception {
+ // TODO (b/132676197): use desugaring handling
+ // TODO (b/132676197): Cf backend should raise a warning
+ // Remove Assume when fixed.
+ Assume.assumeTrue(parameters.isDexRuntime());
+ testIncompleteNestWarning(false);
+ testMissingNestHostWarning(false);
+ }
+
+ @Test
+ public void testErrorR8() {
+ // TODO (b/132676197): Cf back should raise an error
+ // TODO (b/132676197): Dex back-end should raise an error
+ // Remove Assume when fixed.
+ Assume.assumeTrue(false);
+ testMissingNestHostError();
+ testIncompleteNestError();
+ }
+
+ private TestCompileResult compileOnlyClassesMatching(
+ Matcher<String> matcher, boolean d8, boolean ignoreMissingClasses) throws Exception {
+ List<Path> matchingClasses =
+ CLASS_NAMES.stream()
+ .filter(matcher::matches)
+ .map(name -> CLASSES_PATH.resolve(name + CLASS_EXTENSION))
+ .collect(toList());
+ if (d8) {
+ return testForD8()
+ .setMinApi(parameters.getApiLevel())
+ .addProgramFiles(matchingClasses)
+ .addOptionsModification(options -> options.enableNestBasedAccessDesugaring = true)
+ .compile();
+ } else {
+ return testForR8(parameters.getBackend())
+ .noTreeShaking()
+ .noMinification()
+ .addKeepAllAttributes()
+ .setMinApi(parameters.getApiLevel())
+ .addProgramFiles(matchingClasses)
+ .addOptionsModification(
+ options -> {
+ options.enableNestBasedAccessDesugaring = true;
+ options.ignoreMissingClasses = ignoreMissingClasses;
+ })
+ .compile();
+ }
+ }
+
+ private void testMissingNestHostError() {
+ try {
+ Matcher<String> innerClassMatcher =
+ containsString("BasicNestHostWithInnerClassMethods$BasicNestedClass");
+ compileOnlyClassesMatching(innerClassMatcher, false, false);
+ fail("Should have raised an exception for missing nest host");
+ } catch (Exception e) {
+ assertTrue(e.getCause().getMessage().contains("requires its nest host"));
+ }
+ }
+
+ private void testIncompleteNestError() {
+ try {
+ Matcher<String> innerClassMatcher = endsWith("BasicNestHostWithInnerClassMethods");
+ compileOnlyClassesMatching(innerClassMatcher, false, false);
+ fail("Should have raised an exception for incomplete nest");
+ } catch (Exception e) {
+ assertTrue(e.getCause().getMessage().contains("requires its nest mates"));
+ }
+ }
+
+ private void testMissingNestHostWarning(boolean d8) throws Exception {
+ Matcher<String> innerClassMatcher =
+ containsString("BasicNestHostWithInnerClassMethods$BasicNestedClass");
+ TestCompileResult compileResult = compileOnlyClassesMatching(innerClassMatcher, d8, true);
+ assertTrue(compileResult.getDiagnosticMessages().getWarnings().size() >= 1);
+ assertTrue(
+ compileResult.getDiagnosticMessages().getWarnings().stream()
+ .anyMatch(
+ warning -> warning.getDiagnosticMessage().contains("requires its nest host")));
+ }
+
+ private void testIncompleteNestWarning(boolean d8) throws Exception {
+ Matcher<String> innerClassMatcher = endsWith("BasicNestHostWithInnerClassMethods");
+ TestCompileResult compileResult = compileOnlyClassesMatching(innerClassMatcher, d8, true);
+ assertTrue(compileResult.getDiagnosticMessages().getWarnings().size() >= 1);
+ assertTrue(
+ compileResult.getDiagnosticMessages().getWarnings().stream()
+ .anyMatch(
+ warning -> warning.getDiagnosticMessage().contains("requires its nest mates")));
+ }
+}