Version 1.5.40 Cherry pick: Add desugar-specific diagnostics for missing types. CL: https://r8-review.googlesource.com/c/r8/+/38363 Bug: 118842646 Change-Id: I96fbea63c86d1c3ac3b147f88e2658d351b2c107
diff --git a/src/main/java/com/android/tools/r8/Version.java b/src/main/java/com/android/tools/r8/Version.java index 7d84af5..7c7249b 100644 --- a/src/main/java/com/android/tools/r8/Version.java +++ b/src/main/java/com/android/tools/r8/Version.java
@@ -11,7 +11,7 @@ // This field is accessed from release scripts using simple pattern matching. // Therefore, changing this field could break our release scripts. - public static final String LABEL = "1.5.39"; + public static final String LABEL = "1.5.40"; private Version() { }
diff --git a/src/main/java/com/android/tools/r8/errors/DesugarDiagnostic.java b/src/main/java/com/android/tools/r8/errors/DesugarDiagnostic.java new file mode 100644 index 0000000..59711e9 --- /dev/null +++ b/src/main/java/com/android/tools/r8/errors/DesugarDiagnostic.java
@@ -0,0 +1,11 @@ +// 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.errors; + +import com.android.tools.r8.Diagnostic; +import com.android.tools.r8.KeepForSubclassing; + +/** Common interface type for all diagnostics related to desugaring. */ +@KeepForSubclassing +public interface DesugarDiagnostic extends Diagnostic {}
diff --git a/src/main/java/com/android/tools/r8/errors/InterfaceDesugarDiagnostic.java b/src/main/java/com/android/tools/r8/errors/InterfaceDesugarDiagnostic.java new file mode 100644 index 0000000..7bd18fd --- /dev/null +++ b/src/main/java/com/android/tools/r8/errors/InterfaceDesugarDiagnostic.java
@@ -0,0 +1,12 @@ +// 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.errors; + +import com.android.tools.r8.KeepForSubclassing; + +/** Common interface type for all diagnostics related to interface-method desugaring. */ +@KeepForSubclassing +public interface InterfaceDesugarDiagnostic extends DesugarDiagnostic { + +}
diff --git a/src/main/java/com/android/tools/r8/errors/InterfaceDesugarMissingTypeDiagnostic.java b/src/main/java/com/android/tools/r8/errors/InterfaceDesugarMissingTypeDiagnostic.java new file mode 100644 index 0000000..b3f3fd6 --- /dev/null +++ b/src/main/java/com/android/tools/r8/errors/InterfaceDesugarMissingTypeDiagnostic.java
@@ -0,0 +1,88 @@ +// 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.errors; + +import com.android.tools.r8.Keep; +import com.android.tools.r8.origin.Origin; +import com.android.tools.r8.position.Position; +import com.android.tools.r8.references.ClassReference; + +/** + * Diagnostic for missing types needed for correct desugaring of default/static interface methods. + */ +@Keep +public class InterfaceDesugarMissingTypeDiagnostic implements DesugarDiagnostic { + + private final Origin origin; + private final Position position; + private final ClassReference missingType; + private final ClassReference contextType; + + // Note: the implementing context is not yet made part of the public API as the context could be + // both in the implements clause or from a lambda class in a member. + private final ClassReference implementingContextType; + + public InterfaceDesugarMissingTypeDiagnostic( + Origin origin, + Position position, + ClassReference missingType, + ClassReference contextType, + ClassReference implementingContextType) { + assert origin != null; + assert position != null; + assert missingType != null; + assert contextType != null; + this.origin = origin; + this.position = position; + this.missingType = missingType; + this.contextType = contextType; + // The implementing context is optional. + this.implementingContextType = implementingContextType; + } + + /** Get the origin of a class leading to this warning. */ + @Override + public Origin getOrigin() { + return origin; + } + + /** Get additional position information about the context leading to this warning. */ + @Override + public Position getPosition() { + return position; + } + + /** Get the type that is missing. */ + public ClassReference getMissingType() { + return missingType; + } + + /** Get the type that requires knowledge of the missing type. */ + public ClassReference getContextType() { + return contextType; + } + + @Override + public String getDiagnosticMessage() { + StringBuilder builder = + new StringBuilder() + .append("Type `") + .append(missingType.getTypeName()) + .append("` was not found, ") + .append("it is required for default or static interface methods desugaring of `"); + if (position != Position.UNKNOWN) { + builder.append(position.getDescription()); + } else { + builder.append(contextType.getTypeName()); + } + builder.append("`"); + if (implementingContextType != null) { + builder + .append(" This missing interface is declared in the direct hierarchy of `") + .append(implementingContextType) + .append("`"); + } + return builder.toString(); + } +}
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 b949b72..3bec073 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
@@ -12,7 +12,6 @@ import com.android.tools.r8.graph.DexCallSite; import com.android.tools.r8.graph.DexClass; import com.android.tools.r8.graph.DexEncodedMethod; -import com.android.tools.r8.graph.DexItem; import com.android.tools.r8.graph.DexItemFactory; import com.android.tools.r8.graph.DexLibraryClass; import com.android.tools.r8.graph.DexMethod; @@ -32,8 +31,8 @@ import com.android.tools.r8.ir.conversion.IRConverter; import com.android.tools.r8.ir.desugar.DefaultMethodsHelper.Collection; import com.android.tools.r8.origin.Origin; +import com.android.tools.r8.position.MethodPosition; import com.android.tools.r8.utils.InternalOptions; -import com.android.tools.r8.utils.StringDiagnostic; import com.google.common.collect.Sets; import java.util.ListIterator; import java.util.Map; @@ -95,11 +94,6 @@ new ConcurrentHashMap<>(); /** - * A set of dexitems we have reported missing to dedupe warnings. - */ - private final Set<DexItem> reportedMissing = Sets.newConcurrentHashSet(); - - /** * Defines a minor variation in desugaring. */ public enum Flavor { @@ -490,44 +484,14 @@ public void warnMissingInterface( DexClass classToDesugar, DexClass implementing, DexType missing) { - // TODO think about using a common deduplicating mechanic with Enqueuer - if (!reportedMissing.add(missing)) { - return; - } - StringBuilder builder = new StringBuilder(); - builder - .append("Interface `") - .append(missing.toSourceString()) - .append("` not found. It's needed to make sure desugaring of `") - .append(classToDesugar.toSourceString()) - .append("` is correct. Desugaring will assume that this interface has no default method."); - if (classToDesugar != implementing) { - builder - .append(" This missing interface is declared in the direct hierarchy of `") - .append(implementing) - .append("`"); - } - options.reporter.warning( - new StringDiagnostic(builder.toString(), classToDesugar.getOrigin())); + options.warningMissingInterfaceForDesugar(classToDesugar, implementing, missing); } private void warnMissingType(DexMethod referencedFrom, DexType missing) { - // TODO think about using a common deduplicating mechanic with Enqueuer - if (!reportedMissing.add(missing)) { - return; - } - DexMethod originalReferencedFrom = - appView.graphLense().getOriginalMethodSignature(referencedFrom); - StringBuilder builder = new StringBuilder(); - builder - .append("Type `") - .append(missing.toSourceString()) - .append("` was not found, ") - .append("it is required for default or static interface methods desugaring of `") - .append(originalReferencedFrom.toSourceString()) - .append("`"); - options.reporter.warning( - new StringDiagnostic(builder.toString(), getMethodOrigin(originalReferencedFrom))); + DexMethod method = appView.graphLense().getOriginalMethodSignature(referencedFrom); + Origin origin = getMethodOrigin(method); + MethodPosition position = new MethodPosition(method); + options.warningMissingTypeForDesugar(origin, position, missing, method.holder); } private Origin getMethodOrigin(DexMethod method) {
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 8ff922b..1765a64 100644 --- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java +++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -13,9 +13,12 @@ import com.android.tools.r8.Version; import com.android.tools.r8.dex.Marker; import com.android.tools.r8.errors.CompilationError; +import com.android.tools.r8.errors.InterfaceDesugarMissingTypeDiagnostic; import com.android.tools.r8.errors.InvalidDebugInfoException; import com.android.tools.r8.experimental.graphinfo.GraphConsumer; +import com.android.tools.r8.graph.DexClass; import com.android.tools.r8.graph.DexEncodedMethod; +import com.android.tools.r8.graph.DexItem; import com.android.tools.r8.graph.DexItemFactory; import com.android.tools.r8.graph.DexMethod; import com.android.tools.r8.graph.DexType; @@ -23,6 +26,8 @@ import com.android.tools.r8.ir.optimize.Inliner; import com.android.tools.r8.naming.InterfaceMethodNameMinifier; import com.android.tools.r8.origin.Origin; +import com.android.tools.r8.position.Position; +import com.android.tools.r8.references.Reference; import com.android.tools.r8.shaking.ProguardConfiguration; import com.android.tools.r8.shaking.ProguardConfigurationRule; import com.android.tools.r8.utils.IROrdering.IdentityIROrdering; @@ -30,6 +35,7 @@ import com.google.common.base.Equivalence.Wrapper; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Sets; import java.nio.file.Path; import java.util.ArrayList; import java.util.Comparator; @@ -452,6 +458,37 @@ return assertionsEnabled; } + /** A set of dexitems we have reported missing to dedupe warnings. */ + private final Set<DexItem> reportedMissingForDesugaring = Sets.newConcurrentHashSet(); + + public void warningMissingTypeForDesugar( + Origin origin, Position position, DexType missingType, DexType contextType) { + if (reportedMissingForDesugaring.add(missingType)) { + reporter.warning( + new InterfaceDesugarMissingTypeDiagnostic( + origin, + position, + Reference.classFromDescriptor(missingType.toDescriptorString()), + Reference.classFromDescriptor(contextType.toDescriptorString()), + null)); + } + } + + public void warningMissingInterfaceForDesugar( + DexClass classToDesugar, DexClass implementing, DexType missing) { + if (reportedMissingForDesugaring.add(missing)) { + reporter.warning( + new InterfaceDesugarMissingTypeDiagnostic( + classToDesugar.getOrigin(), + Position.UNKNOWN, + Reference.classFromDescriptor(missing.toDescriptorString()), + Reference.classFromDescriptor(classToDesugar.getType().toDescriptorString()), + classToDesugar == implementing + ? null + : Reference.classFromDescriptor(implementing.getType().toDescriptorString()))); + } + } + public void warningMissingEnclosingMember(DexType clazz, Origin origin, int version) { TypeVersionPair pair = new TypeVersionPair(version, clazz); synchronized (missingEnclosingMembers) {
diff --git a/src/test/java/com/android/tools/r8/TestCompileResult.java b/src/test/java/com/android/tools/r8/TestCompileResult.java index d63626a..5444252 100644 --- a/src/test/java/com/android/tools/r8/TestCompileResult.java +++ b/src/test/java/com/android/tools/r8/TestCompileResult.java
@@ -117,6 +117,7 @@ } public CR addRunClasspathClasses(List<Class<?>> classpath) { + assert getBackend() == Backend.CF; try { Path path = state.getNewTempFolder().resolve("runtime-classes.jar"); ArchiveConsumer consumer = new ArchiveConsumer(path);
diff --git a/src/test/java/com/android/tools/r8/desugar/DesugarMissingTypeClassTest.java b/src/test/java/com/android/tools/r8/desugar/DesugarMissingTypeClassTest.java new file mode 100644 index 0000000..1269b34 --- /dev/null +++ b/src/test/java/com/android/tools/r8/desugar/DesugarMissingTypeClassTest.java
@@ -0,0 +1,113 @@ +// 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; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import com.android.tools.r8.D8TestBuilder; +import com.android.tools.r8.D8TestCompileResult; +import com.android.tools.r8.Diagnostic; +import com.android.tools.r8.TestBase; +import com.android.tools.r8.TestDiagnosticMessages; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestParametersCollection; +import com.android.tools.r8.errors.DesugarDiagnostic; +import com.android.tools.r8.errors.InterfaceDesugarMissingTypeDiagnostic; +import com.android.tools.r8.position.Position; +import com.android.tools.r8.references.Reference; +import com.android.tools.r8.utils.AndroidApiLevel; +import com.android.tools.r8.utils.StringUtils; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +@RunWith(Parameterized.class) +public class DesugarMissingTypeClassTest extends TestBase { + + static final String EXPECTED = StringUtils.lines("Hello, world"); + + private final TestParameters parameters; + + @Parameterized.Parameters(name = "{0}") + public static TestParametersCollection data() { + return getTestParameters().withAllRuntimes().withAllApiLevels().build(); + } + + public DesugarMissingTypeClassTest(TestParameters parameters) { + this.parameters = parameters; + } + + boolean supportsDefaultInterfaceMethods() { + return parameters.getRuntime().isCf() + || AndroidApiLevel.N.getLevel() <= parameters.getApiLevel().getLevel(); + } + + @Test + public void test() throws Exception { + if (parameters.isCfRuntime()) { + testForJvm() + .addProgramClasses(TestClass.class, MyClass.class, MissingInterface.class) + .run(parameters.getRuntime(), TestClass.class) + .assertSuccessWithOutput(EXPECTED); + } else { + D8TestBuilder builder = + testForD8() + .addProgramClasses(TestClass.class, MyClass.class) + .setMinApi(parameters.getApiLevel()); + TestDiagnosticMessages messages = builder.getState().getDiagnosticsMessages(); + D8TestCompileResult compileResult = builder.compile(); + if (supportsDefaultInterfaceMethods()) { + messages.assertNoMessages(); + compileResult + .addRunClasspathFiles( + testForD8() + .setMinApi(parameters.getApiLevel()) + .addProgramClasses(MissingInterface.class) + .compile() + .writeToZip()) + .run(parameters.getRuntime(), TestClass.class) + .assertSuccessWithOutput(EXPECTED); + } else { + messages.assertOnlyWarnings(); + assertEquals(1, messages.getWarnings().size()); + Diagnostic diagnostic = messages.getWarnings().get(0); + assertTrue(diagnostic instanceof DesugarDiagnostic); + assertTrue(diagnostic instanceof InterfaceDesugarMissingTypeDiagnostic); + InterfaceDesugarMissingTypeDiagnostic desugarWarning = (InterfaceDesugarMissingTypeDiagnostic) diagnostic; + assertEquals( + Reference.classFromClass(MissingInterface.class), desugarWarning.getMissingType()); + assertEquals(Reference.classFromClass(MyClass.class), desugarWarning.getContextType()); + assertEquals(Position.UNKNOWN, desugarWarning.getPosition()); + } + } + } + + public interface MissingInterface { + void foo(); + + default void bar() { + foo(); + } + } + + public static class MyClass implements MissingInterface { + + @Override + public void foo() { + System.out.println("Hello, world"); + } + } + + static class TestClass { + + public static void baz(MissingInterface fn) { + fn.bar(); + } + + public static void main(String[] args) { + baz(new MyClass()); + } + } +}
diff --git a/src/test/java/com/android/tools/r8/desugar/DesugarMissingTypeLambdaTest.java b/src/test/java/com/android/tools/r8/desugar/DesugarMissingTypeLambdaTest.java new file mode 100644 index 0000000..8f304c6 --- /dev/null +++ b/src/test/java/com/android/tools/r8/desugar/DesugarMissingTypeLambdaTest.java
@@ -0,0 +1,107 @@ +// 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; + +import static org.hamcrest.CoreMatchers.containsString; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import com.android.tools.r8.D8TestBuilder; +import com.android.tools.r8.D8TestCompileResult; +import com.android.tools.r8.Diagnostic; +import com.android.tools.r8.TestBase; +import com.android.tools.r8.TestDiagnosticMessages; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestParametersCollection; +import com.android.tools.r8.errors.DesugarDiagnostic; +import com.android.tools.r8.errors.InterfaceDesugarMissingTypeDiagnostic; +import com.android.tools.r8.position.Position; +import com.android.tools.r8.references.Reference; +import com.android.tools.r8.utils.AndroidApiLevel; +import com.android.tools.r8.utils.StringUtils; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +@RunWith(Parameterized.class) +public class DesugarMissingTypeLambdaTest extends TestBase { + + static final String EXPECTED = StringUtils.lines("Hello, world"); + + private final TestParameters parameters; + + @Parameterized.Parameters(name = "{0}") + public static TestParametersCollection data() { + return getTestParameters().withAllRuntimes().withAllApiLevels().build(); + } + + public DesugarMissingTypeLambdaTest(TestParameters parameters) { + this.parameters = parameters; + } + + boolean supportsDefaultInterfaceMethods() { + return parameters.getRuntime().isCf() + || AndroidApiLevel.N.getLevel() <= parameters.getApiLevel().getLevel(); + } + + @Test + public void test() throws Exception { + if (parameters.isCfRuntime()) { + testForJvm() + .addProgramClasses(TestClass.class, MissingInterface.class) + .run(parameters.getRuntime(), TestClass.class) + .assertSuccessWithOutput(EXPECTED); + } else { + D8TestBuilder builder = + testForD8().addProgramClasses(TestClass.class).setMinApi(parameters.getApiLevel()); + TestDiagnosticMessages messages = builder.getState().getDiagnosticsMessages(); + D8TestCompileResult compileResult = builder.compile(); + if (supportsDefaultInterfaceMethods()) { + messages.assertNoMessages(); + compileResult + .addRunClasspathFiles( + testForD8() + .setMinApi(parameters.getApiLevel()) + .addProgramClasses(MissingInterface.class) + .compile() + .writeToZip()) + .run(parameters.getRuntime(), TestClass.class) + .assertSuccessWithOutput(EXPECTED); + } else { + messages.assertOnlyWarnings(); + assertEquals(1, messages.getWarnings().size()); + Diagnostic diagnostic = messages.getWarnings().get(0); + assertTrue(diagnostic instanceof DesugarDiagnostic); + assertTrue(diagnostic instanceof InterfaceDesugarMissingTypeDiagnostic); + InterfaceDesugarMissingTypeDiagnostic desugarWarning = (InterfaceDesugarMissingTypeDiagnostic) diagnostic; + assertEquals( + Reference.classFromClass(MissingInterface.class), desugarWarning.getMissingType()); + // TODO(b/132671303): The context class should not be the synthesized lambda class. + assertThat(desugarWarning.getContextType().getDescriptor(), containsString("$$Lambda")); + // TODO(b/132671303): The position info should be the method context. + assertEquals(Position.UNKNOWN, desugarWarning.getPosition()); + } + } + } + + public interface MissingInterface { + void foo(); + + default void bar() { + foo(); + } + } + + static class TestClass { + + public static void baz(MissingInterface fn) { + fn.bar(); + } + + public static void main(String[] args) { + baz(() -> System.out.println("Hello, world")); + } + } +}
diff --git a/src/test/java/com/android/tools/r8/desugar/DesugarMissingTypeStaticInvokeTest.java b/src/test/java/com/android/tools/r8/desugar/DesugarMissingTypeStaticInvokeTest.java new file mode 100644 index 0000000..7422eb8 --- /dev/null +++ b/src/test/java/com/android/tools/r8/desugar/DesugarMissingTypeStaticInvokeTest.java
@@ -0,0 +1,100 @@ +// 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; + +import static org.hamcrest.CoreMatchers.containsString; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import com.android.tools.r8.D8TestBuilder; +import com.android.tools.r8.D8TestCompileResult; +import com.android.tools.r8.Diagnostic; +import com.android.tools.r8.TestBase; +import com.android.tools.r8.TestDiagnosticMessages; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestParametersCollection; +import com.android.tools.r8.errors.DesugarDiagnostic; +import com.android.tools.r8.errors.InterfaceDesugarMissingTypeDiagnostic; +import com.android.tools.r8.references.Reference; +import com.android.tools.r8.utils.AndroidApiLevel; +import com.android.tools.r8.utils.StringUtils; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +@RunWith(Parameterized.class) +public class DesugarMissingTypeStaticInvokeTest extends TestBase { + + static final String EXPECTED = StringUtils.lines("Hello, world"); + + private final TestParameters parameters; + + @Parameterized.Parameters(name = "{0}") + public static TestParametersCollection data() { + return getTestParameters().withAllRuntimes().withAllApiLevels().build(); + } + + public DesugarMissingTypeStaticInvokeTest(TestParameters parameters) { + this.parameters = parameters; + } + + boolean supportsDefaultInterfaceMethods() { + return parameters.getRuntime().isCf() + || AndroidApiLevel.N.getLevel() <= parameters.getApiLevel().getLevel(); + } + + @Test + public void test() throws Exception { + if (parameters.isCfRuntime()) { + testForJvm() + .addProgramClasses(TestClass.class, MissingInterface.class) + .run(parameters.getRuntime(), TestClass.class) + .assertSuccessWithOutput(EXPECTED); + } else { + D8TestBuilder builder = + testForD8().addProgramClasses(TestClass.class).setMinApi(parameters.getApiLevel()); + TestDiagnosticMessages messages = builder.getState().getDiagnosticsMessages(); + D8TestCompileResult compileResult = builder.compile(); + if (supportsDefaultInterfaceMethods()) { + messages.assertNoMessages(); + compileResult + .addRunClasspathFiles( + testForD8() + .setMinApi(parameters.getApiLevel()) + .addProgramClasses(MissingInterface.class) + .compile() + .writeToZip()) + .run(parameters.getRuntime(), TestClass.class) + .assertSuccessWithOutput(EXPECTED); + } else { + messages.assertOnlyWarnings(); + assertEquals(1, messages.getWarnings().size()); + Diagnostic diagnostic = messages.getWarnings().get(0); + assertTrue(diagnostic instanceof DesugarDiagnostic); + assertTrue(diagnostic instanceof InterfaceDesugarMissingTypeDiagnostic); + InterfaceDesugarMissingTypeDiagnostic desugarWarning = (InterfaceDesugarMissingTypeDiagnostic) diagnostic; + assertEquals( + Reference.classFromClass(MissingInterface.class), desugarWarning.getMissingType()); + assertEquals(Reference.classFromClass(TestClass.class), desugarWarning.getContextType()); + assertThat( + desugarWarning.getPosition().getDescription(), + containsString(TestClass.class.getTypeName() + ".main")); + } + } + } + + public interface MissingInterface { + static void foo() { + System.out.println("Hello, world"); + } + } + + static class TestClass { + + public static void main(String[] args) { + MissingInterface.foo(); + } + } +}